Почему mysql_real_escape_string() не предотвращает взлом?

У меня есть сайт, который взломали сегодня. Журналы сервера возвращали что-то вроде этого как попытки хакера:

www.site.com/notifications.php?PID=7&id=999999.9%20union%20all%20select%20%28select%20distinct%20concat%280x7e%2C0x27%2Cunhex%28Hex%28cast%28schema_name%20as%20char%29%29% 29% 2C0x27% 2C0x7e% 29% 20from% 20% 60information_schema% 60.schemata% 20limit% 201% 2C1% 29% 2C0x31303235343830303536% 2C0x31303235343830303536% 2C0x31303235343830303536--

Но я использовал mysql_real_escape_string() в моем коде:

if (isset($_GET['id']) && $_GET['id'] != '') {
   $id = mysql_real_escape_string($_GET['id']); 
} else {
   $id = '';
}

if ($id == '') {
   $stmt = "SELECT * FROM tbln13 ORDER BY id DESC"; 
} else {
   $stmt = "SELECT * FROM tbln13 WHERE id = $id";
}

$NewsResult = mysql_query($stmt) or die (mysql_error());

Почему мой сайт не смог предотвратить эту атаку?

5 ответов

Решение

Потому что escape_string добавляет косые черты и такие кавычки. В вашем запросе не было ни кавычек, ни строки, которую они отправили.

Ваш запрос не содержит в себе STRING, похоже, он ожидает int. Если вы ожидали целое число, вы должны были проверить, что это int, или принудительно установить его в int, прежде чем использовать его в запросе. Экранирование значения в виде строки с последующим использованием его как int не будет работать.

Переключитесь на подготовленные операторы в MySQLi или PDO.

Введенный SQL-запрос выглядит следующим образом

SELECT * FROM tbln13 WHERE id = 999999.9 
union all select 
 (select distinct concat(0x7e,0x27,unhex(Hex(cast(schema_name as char))),0x27,0x7e) 
  from `information_schema`.schemata 
  limit 1,1), 
 0x31303235343830303536, 0x31303235343830303536, 0x31303235343830303536--

как видите, вам сделали инъекцию, потому что вы только что это допустили!

Вы ожидали число, но не проверяли его! Таким образом, вы получили номер и что-то еще.

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

if (!preg_match('/^\d+$/', $id))
    die("ERROR: invalid id"); // error, don't continue

Поскольку ваша экранированная переменная не является строкой, следовательно, она не находится внутри кавычек в вашем запросе. Если вы хотите быстрое решение, вы можете изменить свой запрос на:

$stmt = "SELECT * FROM tbln13 WHERE id = '$id'";

Это не стандартное использование для числового сравнения, но должно работать.

Используйте подготовленные заявления, которые в большинстве случаев предотвратят SQL injections,

Простой и понятный путеводитель по подготовленным высказываниям можно найти на этом сайте:

Бобби Столы

Более того, вы должны прекратить использовать MYSQL, он устарел и будет удален в будущих реализациях. использование MySQLi или же PDO вместо.

Как уже упоминалось, вы должны отказаться от канавы mysql_* функции и вместо этого использовали подготовленные заявления через mysqli или же PDO,

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

$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
if (false === $id) {
   // you do not have a valid integer value passed. Do something.
} else {
   // continue with your prepared statement
}
Другие вопросы по тегам