CSAW CTF'18 write-up
CSAW CTF'18 write-up
https://ctf.csaw.io の write-up です。
50 - Web - Ldab
問題概要
LDAP ユーザの検索ページが与えられる。
ポイント
- LDAP 認証
解法
LDAP 認証のクエリに対して injection をかける。
*))(|(objectClass=*
をフォームに入力して検索すると flag を取得できる。
flag{ld4p_inj3ction_i5_a_th1ng}
100 - Web - SSO
問題概要
OAuth 2 で保護された web ページにadminとしてログインできればok
ポイント
- OAuth2
解法
OAuth 2 の認証の流れに従ってリクエストを発行していき、admin の token を取得できればok。
参考:
www.aperikube.fr www.digitalocean.com
200 - Web - Hacker Movie Club
問題概要
ポイント
- Cache poisoning
- X-Forwarded-Host ヘッダ
TL;DR
- htmlテンプレートファイル(サーバサイドでキャッシュされている)を偽ファイルをキャッシュさせる
- 偽ファイルにXSSを仕込んでおく
- Report 機能を使って XSS を仕込んだページに admin を訪問させる
解法
/cdn.js
が読み込まれているので、中身を見てみます。
for (let t of document.head.children) { if (t.tagName !== 'SCRIPT') continue; let { cdn, src } = t.dataset; if (cdn === undefined || src === undefined) continue; fetch(`//${cdn}/cdn/${src}`,{ headers: { 'X-Forwarded-Host':cdn }} ).then(r=>r.blob()).then(b=> { let u = URL.createObjectURL(b); let s = document.createElement('script'); s.src = u; document.head.appendChild(s); }); }
これは、
<head>
タグの中から<script data-cdn="..." data-src="..." ...>
となっているようなタグを見つける//${cdn}/cdn/${src}
からスクリプトを取得して新たな<script>
タグとして追加する
という処理になります。
<head>
タグの中で <script data-cdn="..." data-src="..." ...>
となっているようなタグは以下の2つです。
<script data-src="mustache.min.js" data-cdn="827eb0db2088a89ecce9f4b29bddb733418294fc.hm.vulnerable.services"></script> <script data-src="app.js" data-cdn="827eb0db2088a89ecce9f4b29bddb733418294fc.hm.vulnerable.services"></script>
mustache.min.js
はおそらく単なるライブラリなので、 app.js
のほうのタグにより取得されるスクリプトを見てみます。
var token = null; Promise.all([ fetch('/api/movies').then(r=>r.json()), fetch(`//827eb0db2088a89ecce9f4b29bddb733418294fc.hm.vulnerable.services/cdn/main.mst`).then(r=>r.text()), new Promise((resolve) => { if (window.loaded_recapcha === true) return resolve(); window.loaded_recapcha = resolve; }), new Promise((resolve) => { if (window.loaded_mustache === true) return resolve(); window.loaded_mustache = resolve; }) ]).then(([user, view])=>{ document.getElementById('content').innerHTML = Mustache.render(view,user); grecaptcha.render(document.getElementById("captcha"), { sitekey: '6Lc8ymwUAAAAAM7eBFxU1EBMjzrfC5By7HUYUud5', theme: 'dark', callback: t=> { token = t; document.getElementById('report').disabled = false; } }); let hidden = true; document.getElementById('report').onclick = () => { if (hidden) { document.getElementById("captcha").parentElement.style.display='block'; document.getElementById('report').disabled = true; hidden = false; return; } fetch('/api/report',{ method: 'POST', body: JSON.stringify({token:token}) }).then(r=>r.json()).then(j=>{ if (j.success) { // The admin is on her way to check the page alert("Neo... nobody has ever done this before."); alert("That's why it's going to work."); } else { alert("Dodge this."); } }); } });
このコードから以下のことが分かります。
/api/movies
からなにかのデータを取得しているようだ//827eb0db2088a89ecce9f4b29bddb733418294fc.hm.vulnerable.services/cdn/main.mst
からなにかのデータを取得しているようだ- report をしたときの処理のコメント
// The admin is on her way to check the page
を見る限り、report を送ると admin がページを見に来てくれるようだ
まず、 console で fetch('/api/movies').then(r=>r.json()).then(j=>console.log(JSON.stringify(j)))
と実行して /api/movies
から取得したデータの中身を見てみてます。
{"admin":false,"movies":[{"admin_only":false,"length":"1 Hour, 54 Minutes","name":"WarGames","year":1983},{"admin_only":false,"length":"0 Hours, 31 Minutes","name":"Kung Fury","year":2015},{"admin_only":false,"length":"2 Hours, 6 Minutes","name":"Sneakers","year":1992},{"admin_only":false,"length":"1 Hour, 39 Minutes","name":"Swordfish","year":2001},{"admin_only":false,"length":"2 Hours, 6 Minutes","name":"The Karate Kid","year":1984},{"admin_only":false,"length":"1 Hour, 23 Minutes","name":"Ghost in the Shell","year":1995},{"admin_only":false,"length":"5 Hours, 16 Minutes","name":"Serial Experiments Lain","year":1998},{"admin_only":false,"length":"2 Hours, 16 Minutes","name":"The Matrix","year":1999},{"admin_only":false,"length":"1 Hour, 57 Minutes","name":"Blade Runner","year":1982},{"admin_only":false,"length":"2 Hours, 43 Minutes","name":"Blade Runner 2049","year":2017},{"admin_only":false,"length":"1 Hour, 47 Minutes","name":"Hackers","year":1995},{"admin_only":false,"length":"1 Hour, 36 Minutes","name":"TRON","year":1982},{"admin_only":false,"length":"2 Hours, 5 Minutes","name":"Tron: Legacy","year":2010},{"admin_only":false,"length":"2 Hours, 25 Minutes","name":"Minority Report","year":2002},{"admin_only":false,"length":"2 Hours, 37 Minutes","name":"eXistenZ","year":1999},{"admin_only":true,"length":"22 Hours, 17 Minutes","name":"[REDACTED]","year":2018}]}
movies の一番最後に {"admin_only":true,"length":"22 Hours, 17 Minutes","name":"[REDACTED]","year":2018}
なるデータがあります。
どうやら、admin として /api/movies
にアクセスすると moives の一番最後の部分にに含まれていそうな気がしてきます。
次に、 fetch(
//827eb0db2088a89ecce9f4b29bddb733418294fc.hm.vulnerable.services/cdn/main.mst).then(r=>r.text()).then(text=>console.log(text))
を実行して main.mst
を取得して中身を見てみます。
<div class="header"> Hacker Movie Club </div> {{#admin}} <div class="header admin"> Welcome to the desert of the real. </div> {{/admin}} <table class="movies"> <thead> <th>Name</th><th>Year</th><th>Length</th> </thead> <tbody> {{#movies}} {{^admin_only}} <tr> <td>{{ name }}</td> <td>{{ year }}</td> <td>{{ length }}</td> </tr> {{/admin_only}} {{/movies}} </tbody> </table> <div class="captcha"> <div id="captcha"></div> </div> <button id="report" type="submit" class="report"></button>
なにやらtemplateのようです。これが映画一覧の表示部分のtemplateになっているようです。
これらの観察をまとめると、以下の通りの手順で flag を取得できる。
- cdn.js で設定している
X-Forwarded-Host
の値を自サーバのIPに偽装して自サーバの偽の main.mst をキャッシュさせる。 - 自サーバの偽の main.mst に XSS を仕込んでおき、 movies の値を自サーバに送信するようにする。
- report をして、admin が XSS を仕込んだ main.mst を使ってくれるようにする。
参考:
lud1161.github.io portswigger.net
400 - Web - No Vulnerable Services
問題概要
ポイント
解法
Comming soon ...