Thursday, March 5, 2015

BkP'15 - School Bus - Web Challenges (Prudential/Symphony/North Eastern University/Museum of Fine Arts/Longwood Medical/Bringham Circle)

Many of these web challenges I surprised myself with. I learned PHP a long time ago, and barely used it ever since. Often I would look over and not see a vulnerability, or mistake the vulnerability. It wasn't until deeply researching the key lines of code did I find the actual vulnerability. Because of these vulnerabilities ability to hide under careful reading, these challenges have taught me to be suspect of any PHP code.

As a side note, Boston Key Party laid out their challenges this year on a map with 4 train routes. So these challenges are stops along one train route in Boston, but I prefer to image Mister Roger's trolley making its way through the land of web development make-believe.

Prudential

<html>
<head>
 <title>level1</title>
    <link rel='stylesheet' href='style.css' type='text/css'>
</head>
<body>

<?php
require 'flag.php';

if (isset($_GET['name']) and isset($_GET['password'])) {
    if ($_GET['name'] == $_GET['password'])
        print 'Your password can not be your name.';
    else if (sha1($_GET['name']) === sha1($_GET['password']))
      die('Flag: '.$flag);
    else
        print '<p class="alert">Invalid password.</p>';
}
?>

<section class="login">
 <div class="title">
  <a href="./index.txt">Level 1</a>
 </div>

 <form method="get">
  <input type="text" required name="name" placeholder="Name"/><br/>
  <input type="text" required name="password" placeholder="Password" /><br/>
  <input type="submit"/>
 </form>
</section>
</body>
</html>


At first look, I focused on the sha1() comparison of the name and password. Since the name and password couldn't be the same, I thought this would be dealing with sha hash extension attack. However, I'm not all that familiar with this attack.

This was a 25 point challenge, so upon a second inspection, I became suspicious of the == comparison between the name and password inputs. In PHP, one typically uses a strcmp() or === to compare strings. See this StackOverflow for more.

In order to trick the == comparison, I made name and password two different arrays. One with two elements, the other with one. Though I could've made both with one element with different values, or I could have made one a array, and the other a string "Array".

Why?  Because when the values are passed to sha1(), this function is expecting a string. When PHP converts an array to a string, the value will always be "Array"

http://52.10.107.64:8001/?name[]=x&password[]=x&name[]=x

Symphony

<html>
<head>
 <title>level2</title>
    <link rel='stylesheet' href='style.css' type='text/css'>
</head>
<body>

<?php
require 'flag.php';

if (isset($_GET['password'])) {
 if (is_numeric($_GET['password'])){
  if (strlen($_GET['password']) < 4){
   if ($_GET['password'] > 999)
    die('Flag: '.$flag);
   else
    print '<p class="alert">Too little</p>';
  } else
    print '<p class="alert">Too long</p>';
 } else
  print '<p class="alert">Password is not numeric</p>';
}
?>

<section class="login">
        <div class="title">
                <a href="./index.txt">Level 2</a>
        </div>

        <form method="get">
                <input type="text" required name="password" placeholder="Password" /><br/>
                <input type="submit"/>
        </form>
</section>
</body>
</html>

This was a bit easier, as their was no intimidating crypto to distract us. We just need a 3 digit number whose value is 1000 or more. The hexadecimal "FFF" would work, but we'd need two more characters for the leading "0x" to signify it as such.

Because the GET parameters are strings, I rtfm'd the PHP manual to see how it converts strings to integers.I saw one promising value in the example: 1e10. Course that's 4 characters, but 9e9 worked out just find. For those unfamiliar with this notation, 9e9 is shorthand for the scientific notation of 9 times 10 to the 9th power, which I'm sure must be larger than 999.

http://52.10.107.64:8002?password=9e9

North Eastern University

<html>
<head>
 <title>level3</title>
    <link rel='stylesheet' href='style.css' type='text/css'>
</head>
<body>

<?php
require 'flag.php';

if (isset($_GET['password'])) {
    if (strcmp($_GET['password'], $flag) == 0)
  die('Flag: '.$flag);
    else
  print '<p class="alert">Invalid password.</p>';
}
?>

<section class="login">
        <div class="title">
                <a href="./index.txt">Level 3</a>
        </div>

        <form method="get">
                <input type="text" required name="password" placeholder="Password" /><br/>
                <input type="submit"/>
        </form>
</section>
</body>
</html>



So we're comparing the password value against an unknown string. From the Prudential challenge, you might think that if we set password as an array, that PHP would convert it to a string (as the function expects), and that string would be "Array", since this always occurs, right? Of course not.

RTFM'ing again we see a cautionary tale full of suspense and drama on the strcmp() page. It seems if you pass strcmp() an array, it'll return NULL and a PHP Warning. Null, 0, what's the difference, right == comparison? Thanks for the flag.

http://52.10.107.64:8003?password[]=x

Museum of Fine Arts

<html>
<head>
 <title>level4</title>
    <link rel='stylesheet' href='style.css' type='text/css'>
</head>
<body>

<?php
session_start(); 

require 'flag.php';

if (isset ($_GET['password'])) {
    if ($_GET['password'] == $_SESSION['password'])
        die ('Flag: '.$flag);
    else
        print '<p class="alert">Wrong guess.</p>';
}

// Unpredictable seed
mt_srand((microtime() ^ rand(1, 10000)) % rand(1, 10000) + rand(1, 10000));
?>

<section class="login">
        <div class="title">
                <a href="./index.txt">Level 4</a>
        </div>

  <ul class="list">
  <?php
  for ($i=0; $i<3; $i++)
   print '<li>' . mt_rand (0, 0xffffff) . '</li>';
  $_SESSION['password'] = mt_rand (0, 0xffffff);
  ?>
  </ul>

        <form method="get">
                <input type="text" required name="password" placeholder="Next number" /><br/>
                <input type="submit"/>
        </form>
</section>
</body>
</html>


Now we're getting into some deeper waters, while at the same time the vulnerabilities are less hidden. Here the vulnerability is small seed space. First time I looked at this, I was intimidated by the (microtime() ^ rand(1, 10000)) part of the seed, and thought this challenge was going to be more hassle than it was worth.

But then I remember a piece of advice a wise man once twitted me.

Of course! Why didn't I think to bruteforce it? So on second pass, I notice the less scary half "% rand(1, 10000) + rand(1, 10000)".

The mod operator basically negates any fancy work in the first part of the seed equation and says the maximum number can be 10,000, and then it may have another 10,000 added on. So effective range of all seeds are 2 through 20,000. A lot less intimidating.

All we need to do, is view the page and capture the first number, then create a loop that bruteforces through 20000 seeds. I'm too lazy to stand up my own webserver, or use a PHP shell. Instead, I use http://writecodeonline.com/php/, and write the following code


for ($i=2; $i<20001; $i++) {
 mt_srand($i);
 print mt_rand (0, 0xffffff) . ' - ' . $i. ',';
}


This didn't work out as well as planned, as WriteCodeOnline only spits out around 700 values a run, so I stubbornly have to update the for loop rather than doing something simpler like using a PHP shell. Eventually I find the first value in the output, and with that I now have the seed. With the seed, I have it print out 4 values, and find another flag.

Longwood Medical

<html>
<head>
 <title>level5</title>
    <link rel='stylesheet' href='style.css' type='text/css'>
</head>
<body>

<?php

require 'flag.php';

if (isset ($_GET['name']) and isset ($_GET['password'])) {
    $name = $_GET['name'];
    $password = $_GET['password'];

    if (ctype_alnum ($name) and ctype_alnum ($password)) {
        $request = 'SELECT login FROM user where login = ' . $name . ' AND password = ' . $password . ';';
        $db = new SQLite3 (sha1($flag).'.db', SQLITE3_OPEN_READONLY); // Ghetto anti-database-download
        $result = $db->querySingle ($request);
        $db->close ();

        if ($result === FALSE)
            echo '<p class="alert">"Invalid login or password</p>';
        else
            die('Flag: ' . $flag);
    } else
        echo '<p class="alert">Invalid chars detected</p>';
}
?>

<section class="login">
        <div class="title">
                <a href="./index.txt">Level 5</a>
        </div>

        <form method="get">
                <input type="text" required name="name" placeholder="Name"/><br/>
                <input type="text" required name="password" placeholder="Password" /><br/>
                <input type="submit"/>
        </form>
</section>
</body>
</html>

An alphanumeric only SQL Injection, not even spaces? Obviously the vulnerability has to be with PHP ctype_alnum(). Sadly this is not the case. See, PHP operates on quantum level. If you expect code to work the way you read it, PHP will do something unexpected, but if you expect PHP to be inconsistent, that's when PHP does what it supposed to do.

PHP - Inconsistently inconsistent.

So lets atack the SQL request. Lets rtfm SQLite3, and throw some reserved words at it. Nope, nothing. It's odd though, the values are encapsulated in quotes. I wonder if we could make it say: login = login AND password = password; No, it wouldn't be that easy, obviously I'm missing something. Oh look, a flag...yes, it was that easy.

http://52.10.107.64:8005?name=name&password=password

Bringham Circle

<html>
<head>
 <title>level6</title>
    <link rel='stylesheet' href='style.css' type='text/css'>
</head>
<body>

<?php
require 'flag.php';

if (isset ($_GET['password'])) {
 if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE)
  echo '<p class="alert">You password must be alphanumeric</p>';
 else if (strpos ($_GET['password'], '--') !== FALSE)
  die('Flag: ' . $flag);
 else
  echo '<p class="alert">Invalid password</p>';
}
?>

<section class="login">
        <div class="title">
                <a href="./index.txt">Level 6</a>
        </div>

        <form method="get">
                <input type="text" required name="password" placeholder="Password" /><br/>
                <input type="submit"/>
        </form>
</section>
</body>
</html>


Nope, this doesn't look good at all. I was able to mess up strcmp() with an array, then I might be able to mess up strpos() the exact same way. But if I expect PHP not to work, it'll work. But if I expect it to work, then it won't. This circle can go forever. Let's ask Trolley!

  • Trolley: (Ding! Ding! Ding! Ding! Toot!)

Hmm, that's deep Trolly. Real deep. Alright, so we send password as an array and....

http://52.10.107.64:8006?password[]=x

It works! Good job Trolly!!

No comments:

Post a Comment