Chainsaw - Hack The Box

I learned a bit about Ethereum and smart contracts while doing the Chainsaw box from Hack the Box. There’s a command injection vulnerability in a smart contract that gives me a shell. Then after doing some googling on IPFS filesystem, I find an encrypted SSH key for another user which I can crack. To get root access I use another smart contract to change the password used by a SUID binary running as root, then find the flag hidden in the slack space for root.txt


  • Find a smart contract source code and address located on the FTP server
  • The contract contains a command injection vulnerability that get us RCE and a shell on the system
  • There is an IPFS filesystem on the box and we find an encrypted SSH key for user bobby
  • After cracking the key we can log in as user bobby and get the user flag
  • We then find a SUID binary and another smart contract running on a separate instance of ganache-cli
  • By using the contract we can change the password and then get root access through the SUID binary
  • The root.txt file doesn’t contain the system flag but a hint that we need to keep looking further
  • I found the flag using bmap to look at the slack space in root.txt


# nmap -p-
Starting Nmap 7.70 ( ) at 2019-06-16 21:26 EDT
Nmap scan report for chainsaw.htb (
Host is up (0.021s latency).
Not shown: 65532 closed ports
21/tcp   open  ftp
22/tcp   open  ssh
9810/tcp open  unknown

FTP server

Anonymous access is allowed on the FTP server and there’s a few files I can download.

# ftp
Connected to
220 (vsFTPd 3.0.3)
Name ( anonymous
331 Please specify the 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 Jun 16 21:30 address.txt
226 Directory send OK.

Ethereum smart contract #1

The address.txt file contains an Ethereum checksumed address:


The WeaponizedPing.sol file contains the source code of a smart contract. The contract itself doesn’t seem to do much: you can only get/set the domain variable.

pragma solidity ^0.4.24;

contract WeaponizedPing 
  string store = "";

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

  function setDomain(string _value) public 
      store = _value;

The WeaponizedPing.json file has a bunch of information, including the source code, the transactionHash and the compiler used to compile the program.

"source": "pragma solidity ^0.4.24;\n\n\ncontract WeaponizedPing {\n\n ...
  "sourcePath": "/opt/WeaponizedPing/WeaponizedPing.sol",
  "ast": {
    "absolutePath": "/opt/WeaponizedPing/WeaponizedPing.sol",
    "exportedSymbols": {
      "WeaponizedPing": [

"compiler": {
    "name": "solc",
    "version": "0.4.24+commit.e67f0147.Emscripten.clang"
  "networks": {
    "1543936419890": {
      "events": {},
      "links": {},
      "address": "0xaf6ce61d342b48cc992820a154fe0f533e5e487c",
      "transactionHash": "0x5e94c662f1048fca58c07e16506f1636391f757b07c1b6bb6fbb4380769e99e1"
  "schemaVersion": "2.0.1",
  "updatedAt": "2018-12-04T15:24:57.205Z"

To compile and play with the smart contract I used which has a JavaScript VM to run the compiled code. The service running on port 9810 is probably a Web3 service so I configured Remix’s environment to use the Web3 service running on the box.

I opened the source file I downloaded from the server:

Then I selected the same compiler version specified in the JSON file:

There’s a few warnings after compiling but they are probably safe to ignore:

Once we have the file compiled we can deploy a new contract or use an existing one if we know the address. Here, we have an address from address.txt: 0xCeC270D64E45aDc8C6057C764f13448d500de096. Once I enter the address, I can see the deployed contract and get the domain assigned to the contract:

The name WeaponizedPing is a hint. When we set a domain then do a getDomain on it, the box does a ping back to the IP specified:

There is a simple command injection in the code that pings the domain/IP and we can execute other commands such as nc to get a reverse shell:

After getting the reverse shell I dropped my SSH public key into the /home/administrator/.ssh/authorized_keys file so I can log in directly.

InterPlanetary File System

The /home/administrator directory contains a CSV file chainsaw-emp.csv with the list of employees.

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 bobby user is the only active user according to the CSV and is also the only user that has a valid login shell and a home directory:

bobby:x:1000:1000:Bobby Axelrod:/home/bobby:/bin/bash
administrator:x:1001:1001:Chuck Rhoades,,,,IT Administrator:/home/administrator:/bin/bash
administrator@chainsaw:~$ ls -l /home
total 8
drwxr-x--- 10 administrator administrator 4096 Jun 16 21:55 administrator
drwxr-x---  9 bobby         bobby         4096 Jan 23 09:03 bobby

The /home/administrator/maintain directory has a python script that generates OpenSSL private/public keys.

The sub-directory pub contains the public keys for a few users including bobby:

administrator@chainsaw:~/maintain/pub$ ls -l 
total 20
-rw-rw-r-- 1 administrator administrator 380 Dec 13  2018
-rw-rw-r-- 1 administrator administrator 380 Dec 13  2018
-rw-rw-r-- 1 administrator administrator 380 Dec 13  2018
-rw-rw-r-- 1 administrator administrator 380 Dec 13  2018
-rw-rw-r-- 1 administrator administrator 380 Dec 13  2018

I noticed that there is an .ipfs directory inside the administrator home directory:

administrator@chainsaw:~$ ls -l .ipfs
total 28
drwxr-xr-x 41 administrator administrator 4096 Jan 23 09:27 blocks
-rw-rw----  1 administrator administrator 5273 Dec 13  2018 config
drwxr-xr-x  2 administrator administrator 4096 Jan 23 09:27 datastore
-rw-------  1 administrator administrator  190 Dec 13  2018 datastore_spec
drwx------  2 administrator administrator 4096 Dec 13  2018 keystore
-rw-r--r--  1 administrator administrator    2 Dec 13  2018 version

I didn’t know what IPFS was so I did some research and found that it’s, a distributed file-system.

To see the files that are uploaded to the file system, I used:

administrator@chainsaw:~/.ipfs$ ipfs refs local

Then I dumped the content of everything into a single big file:

ipfs cat QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y >> out.txt
ipfs cat QmPctBY8tq2TpPufHuQUbe2sCxoy2wD5YRB6kdce35ZwAx >> out.txt
ipfs cat QmfFUFGiPQA5Wr9tM7K6A6VRCkem6KqssgcwQGgStRWvf7 >> out.txt
ipfs cat QmbwWcNc7TZBUDFzwW7eUTAyLE2hhwhHiTXqempi1CgUwB >> out.txt
ipfs cat QmdL9t1YP99v4a2wyXFYAQJtbD9zKnPrugFLQWXBXb82sn >> out.txt
ipfs cat QmZZRTyhDpL5Jgift1cHbAhexeE1m2Hw8x8g7rTcPahDvo >> out.txt
ipfs cat QmUH2FceqvTSAvn6oqm8M49TNDqowktkEx4LgpBx746HRS >> out.txt
ipfs cat QmcMCDdN1qDaa2vaN654nA4Jzr6Zv9yGSBjKPk26iFJJ4M >> out.txt
ipfs cat QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB >> out.txt
ipfs cat Qmc7rLAhEh17UpguAsEyS4yfmAbeqSeSEz4mZZRNcW52vV >> out.txt

I found an email for user bobby:

I base64 decoded the message:

There’s an attachment in the email with an SSH private key: bobby.key.enc

The key is encrypted but the password is found in rockyou.txt:

Now we can log in as bobby with the SSH key:

root@ragingunicorn:~/htb/chainsaw# ssh -i bobby.key bobby@
Enter passphrase for key 'bobby.key': 
bobby@chainsaw:~$ cat user.txt

Ethereum smart contract #2

The /home/bobby/projects/ChainsawClub directory has another smart contract ChainsawClub.sol:

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;

The ChainsawClub binary is SUID so this is likely our target:

$ ls -l
total 148
-rwsr-xr-x 1 root root  16544 Jan 12 04:23 ChainsawClub

The program requires credentials to log in.

I tried using nobody and 7b455ca1ffcb9f3828cfdde4a396139e that I found in the source but that didn’t work. The password looks like an MD5 hash but I couldn’t crack it either.

I saw that an address.txt file is created when I first launch the program.

bobby@chainsaw:~/projects/ChainsawClub$ cat address.txt 

I disassembled the binary with Ghidra to see how it works and saw that it simply executes another binary from root’s home directory. I don’t have access to root yet so I can’t disassemble the /root/ChainsawClub/dist/ChainsawClub/ChainsawClub file.

The program is probably looking at the contract to get the username and password. I have the address so I should be able to invoke the setUsername and setPassword methods to change the credentials and then log in. I compiled the contract and pointed it at the address 0xCeC270D64E45aDc8C6057C764f13448d500de096 from the address.txt but I wasn’t able to pull any data from it. It probably doesn’t exist in the blockchain.

After looking around the system for a while, I found a 2nd instance of ganache-cli running locally on port 63991. I port forwarded 63991 using SSH so I could access it from Remix and found that the contract is working and I can pull data from it:

I changed the password to the MD5 value of yolo1234 and changed to approval status to true:

I tried logging in but I need funds

I used the transfer method to add 1000 ether then I was able to log in:

Looks like I’m root but there’s one more step left:

I found the flag hidden in the slack space of the root.txt file. I used the bmap utility already installed on the system.