CSAW CTF'18 write-up

CSAW CTF'18 write-up

https://ctf.csaw.io の write-up です。

50 - Web - Ldab

問題概要

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

  1. htmlテンプレートファイル(サーバサイドでキャッシュされている)を偽ファイルをキャッシュさせる
  2. 偽ファイルにXSSを仕込んでおく
  3. 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 ...