A technical walkthrough documenting my approach to solving the Kioptrix Level 4 vulnerable machine in a controlled lab environment.
π Introduction
Kioptrix Level 4 doesn't hinge on one dramatic exploit the way earlier levels do. Instead it chains together several smaller access-control and input-validation failures: a login form that reacts to SQL injection but doesn't fully cave to it, a directory listing that leaks application source, a parameter that discloses another user's password outright, a restricted shell around SSH access, and a MySQL UDF that turns a database login into root.
This write-up follows that chain end to end, including the dead end that came before the actual way in.
β οΈ Disclaimer This walkthrough was completed in an isolated lab environment on an intentionally vulnerable virtual machine. It is shared for educational and defensive learning purposes only.
π₯οΈ Lab Environment
Component Description
Attacker Machine Kali Linux
Target Machine Kioptrix Level 4 (172.30.46.33)
Virtualization VMware Workstation
Network Private Virtual Network
π― Objectives
Discover the target host and exposed services
Enumerate the web login application
Identify and exploit an authentication or access-control flaw
Obtain valid credentials
Gain SSH access and escape any restricted shell in place
Escalate privileges to root
Document findings professionally
πΊοΈ Assessment Flow
ββββββββββββββββββ ββββββββββββββββββ ββββββββββββββββββ ββββββββββββββββββ ββββββββββββββββββ ββββββββββββββββββ
β Reconnaissance βββΆβ SQLi Login βββΆβ IDOR Password βββΆβ Restricted SSH βββΆβ Shell Breakout βββΆβ Privilege Esc. β
ββββββββββββββββββ ββββββββββββββββββ ββββββββββββββββββ ββββββββββββββββββ ββββββββββββββββββ ββββββββββββββββββ
π Phase 1 β Reconnaissance
Host discovery on the lab network was done with arp-scan:
sudo arp-scan --localnet
arp-scan results
This identified the target at 172.30.46.33. A full Nmap service scan followed:
Port Protocol Service Version
22 tcp ssh OpenSSH 4.7p1 Debian 8ubuntu1.2 (protocol 2.0)
80 tcp http Apache httpd 2.2.8 (Ubuntu) PHP/5.2.4-2ubuntu5.6 with Suhosin-Patch
139 tcp netbios-ssn Samba smbd 3.X - 4.X (workgroup: WORKGROUP)
445 tcp netbios-ssn Samba smbd 3.0.28a (workgroup: WORKGROUP)
Nmap port scan results
Key findings:
A web application on port 80, alongside SSH and Samba
Old Apache/PHP/Samba versions consistent with the rest of the Kioptrix series
The web app was the most promising entry point to start with
π Phase 2 β Web Application Enumeration
Browsing to the target presented a "LigGoat" member login form:
LigGoat member login page
The natural first move against any login form is testing for SQL injection. A classic authentication-bypass payload was tried in the login fields:
Username: john
Password: ' or 1=1 #
SQL injection attempt returning wrong credentials
This particular payload didn't bypass the login β the application returned "Wrong Username or Password." The query was clearly being touched by the input (a naive concatenation would normally fall to ' or 1=1 #), but this form wasn't giving up that easily, so it was worth widening the enumeration rather than brute-forcing SQLi payloads blindly.
Why it matters: A login form resisting one canned SQLi payload doesn't mean the application is safe β it can just mean the real weakness is somewhere else on the same site.
π Phase 3 β Directory Listing Disclosure
Poking at the application's directory structure turned up an exposed, browsable directory:
Directory listing of /robert exposing robert.php
Index of /robert was accessible directly, listing robert.php β a file that should not have been enumerable or servable as a directory listing. This is a classic Apache misconfiguration (missing Options -Indexes) leaking the existence and layout of application-internal files that were never meant to be browsed directly.
With a username (robert) now surfaced from the directory name itself, and the login form confirmed to be database-backed, the login form was retried with valid-looking values for the john account instead, using the credentials referenced by the app's own logic:
Successful login as john
The login succeeded, confirming john as a valid application account.
π Phase 4 β IDOR Password Disclosure
Once authenticated, the application exposed a member control panel. Rather than trusting session state alone, the panel accepted a username parameter directly in the URL:
http://172.30.46.33/member.php?username=john
IDOR disclosing john's plaintext password
The page rendered the corresponding account's password in cleartext:
Username : john
Password : MyNameIsJohn
This is a textbook Insecure Direct Object Reference (IDOR) β the application authorized the request based on being logged in at all, not on whether the logged-in user actually owned the username being requested. Any authenticated session could pull any other account's credentials just by changing the parameter.
Why it matters: Authentication answers "who are you?" β authorization has to separately answer "are you allowed to see this specific record?" This app only ever asked the first question.
π Phase 5 β SSH Access and Restricted Shell
With john:MyNameIsJohn in hand, SSH access was attempted:
SSH login landing in a restricted LigGoat shell
$ ssh -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa john@172.30.46.33
john@172.30.46.33's password:
Welcome to LigGoat Security Systems - We are Watching
== Welcome LigGoat Employee ==
LigGoat Shell is in place so you don't screw up
Type '?' or 'help' to get the list of allowed commands
john:~$ ?
cd clear echo exit help ll lpath ls
The login succeeded, but landed in a heavily restricted shell (lshell) exposing only a small allowlist of commands β no whoami, no bash, no direct command execution.
πͺ Phase 6 β Restricted Shell Breakout
lshell allowlists commands, not the full expressiveness of the tools it does allow. echo was still available, and critically the shell evaluates certain patterns through Python β which meant a Python builtin could be smuggled through:
lshell breakout via os.system
john:~$ echo os.system('/bin/bsh')
sh: /bin/bsh: not found
sh: Syntax error: "(" unexpected
john:~$ echo os.system('/bin/bash')
john@Kioptrix4:~$ whoami
john
The first attempt had a typo (/bin/bsh); correcting it to /bin/bash broke out of the restricted lshell environment entirely and dropped into a normal interactive Bash shell as john.
Lesson: Restricting a shell to an allowlist of commands doesn't help if one of the allowed commands is itself an interpreter (or gets evaluated by one) capable of spawning an arbitrary process.
π Phase 7 β Privilege Escalation via MySQL UDF
With a real shell, local privilege escalation avenues were explored. MySQL was reachable with no root password configured:
mysql -u root
A sys_exec() user-defined function (UDF) was already present on this MySQL install β a known Kioptrix Level 4 setup used to demonstrate UDF-based command execution as the MySQL service account (commonly root-equivalent in this lab):
First sudoers write attempt failing with a syntax error
mysql> SELECT sys_exec('echo "john ALL=(ALL:ALL) ALL" >> /etc/sudoers');
mysql> quit
john@Kioptrix4:/var/www$ sudo su
sudoers file: syntax error, line 25 <<<
sudo: parse error in /etc/sudoers near line 25
The first attempt appended a malformed line directly into /etc/sudoers, corrupting it and breaking sudo entirely (the shell-quoting around the embedded " characters didn't survive intact). The fix was to write a properly quoted rule into a drop-in file under /etc/sudoers.d/ instead β the standard, syntax-isolated way sudoers rules are meant to be added β and set the permissions sudoers.d requires:
Corrected sudoers.d write followed by successful sudo su to root
mysql> SELECT sys_exec('echo "john ALL=(ALL:ALL) ALL" > /etc/sudoers.d/john && chmod 440 /etc/sudoers.d/john');
mysql> quit
john@Kioptrix4:/var/www$ sudo su
[sudo] password for john:
root@Kioptrix4:/var/www# whoami
root
sudo su now succeeded, and whoami confirmed full root access.
π§ Skills Practiced
Network Reconnaissance (arp-scan, Nmap)
Web Login Form Enumeration and SQL Injection Testing
Directory Listing / Information Disclosure Identification
Insecure Direct Object Reference (IDOR) Exploitation
Restricted Shell (lshell) Breakout
MySQL User-Defined Function Abuse
Linux sudoers / sudoers.d Mechanics
Technical Documentation
π Lessons Learned
A resisted SQLi payload isn't the same as a secure form. The login form pushed back on one canned payload, but a directory-listing leak on the same application handed over everything the injection was trying to reach.
IDOR vulnerabilities hide in plain sight. member.php?username=john looked like an ordinary parameter, but the app never checked whether the logged-in user actually owned the account being requested.
Restricted shells need to restrict the right thing. lshell's allowlist blocked obvious commands like bash, but echo combined with Python evaluation of os.system() was enough to defeat it completely.
A stray UDF is a loaded gun. sys_exec() sitting on a reachable MySQL instance turned "I have database credentials" into "I have root," with no additional vulnerability required.
sudoers should never be edited with a raw >> and a shell one-liner. The first attempt corrupted the file and broke sudo system-wide until a properly isolated sudoers.d entry replaced it β a good reminder that even the "exploit succeeded" step can go wrong if the mechanics aren't respected.
π Conclusion
Kioptrix Level 4 didn't come down to a single CVE β it came down to a chain of ordinary web application mistakes (an over-permissive directory, a missing per-record authorization check) feeding into a chain of ordinary system mistakes (a leftover UDF, a restricted shell with a gap). Individually, several of these findings look minor. Chained together, they're a full compromise.
π References
arp-scan
Nmap
SQL Injection (authentication bypass testing)
Insecure Direct Object Reference (IDOR / OWASP A01: Broken Access Control)
lshell restricted shell
MySQL User-Defined Functions (sys_exec)
Kioptrix Level 4 Documentation
π€ Author
Zain Sial Cybersecurity Student π GitHub: github.com/zainsial866
Top comments (0)