downloads | documentation | faq | getting help | mailing lists | licenses | wiki | reporting bugs | php.net sites | links | conferences | my php.net

search for in the

Сообщения об ошибках> <Шифрование хранилища базы данных
[edit] Last updated: Fri, 25 May 2012

view this page in

SQL-инъекции

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

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

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

Пример #1 Постраничный вывод результата... и создание суперпользователя в PostgreSQL

<?php

$offset 
$argv[0]; // внимание, нет проверки вводимых данных!
$query  "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
$result pg_query($conn$query);

?>
Обычно пользователи кликают по ссылкам 'вперед' и 'назад', вследствие чего значение переменной $offset заносится в URL. Скрипт ожидает, что $offset - десятичное число. Однако, взломщик может попытаться взломать систему, присоединив к URL дополнительную строку, обработанную функцией urlencode():
0;
insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
    select 'crack', usesysid, 't','t','crack'
    from pg_shadow where usename='postgres';
--
Если это произойдет, скрипт предоставит взломщику доступ к базе с правами суперпользователя. Заметим, что значение 0; использовано для того, чтобы задать правильное смещение для первого запроса и корректно его завершить.

Замечание:

Часто используемой техникой для игнорирования SQL-парсером оставшейся части запроса является использование --, означающей комментарий.

Еще один вероятный способ получить пароли учетных записей в БД - атака страниц, предоставляющих поиск по базе. Взломщику нужно лишь проверить, используется ли в запросе передаваемая на сервер и необрабатываемая надлежащим образом переменная. Это может быть один из устанавливаемых на предыдущей странице фильтров, таких как WHERE, ORDER BY, LIMIT и OFFSET, используемых при построении запросов SELECT. В случае, если используемая вами база данных поддерживает конструкцию UNION, взломщик может присоединить к оригинальному запросу еще один дополнительный, для извлечения пользовательских паролей. Настоятельно рекомендуем использовать только зашифрованные пароли.

Пример #2 Листинг статей... и некоторых паролей (для любой базы данных)

<?php

$query  
"SELECT id, name, inserted, size FROM products
                  WHERE size = '
$size'
                  ORDER BY 
$order LIMIT $limit$offset;";
$result odbc_exec($conn$query);

?>
Статическая часть запроса может комбинироваться с другим SELECT-запросом, который выведет все пароли:
'
union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable;
--
Если этот запрос (использующий ' и --) присоединить к значению одной из переменных, используемых для формирования $query, то запрос заметно преобразится.

Команды UPDATE также могут использоваться для атаки. Опять же, есть угроза разделения инструкции на несколько частей и присоединения дополнительного запроса. Также взломщик может видоизменить выражение SET. В этом случае потенциальному взломщику необходимо обладать некоторой дополнительной информацией о структуре базы данных для успешного манипулирования запросами. Эту информацию можно получить, проанализировав используемые в форме имена переменных, либо просто перебирая все наиболее распространенные варианты названия соответствующих полей (а их не так уж и много).

Пример #3 От восстановления пароля... до получения дополнительных привилегий (для любой базы данных)

<?php
$query 
"UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';";
?>
Но злоумышленник может ввести значение ' or uid like'%admin%'; -- для переменной $uid для изменения пароля администратора или просто присвоить переменной $pwd значение "hehehe', admin='yes', trusted=100 " (с завершающими пробелами) для получения дополнительных привелегий. При выполнении запросы переплетаются:
<?php

// $uid == ' or uid like'%admin%'; --
$query "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%'; --";

// $pwd == "hehehe', admin='yes', trusted=100 "
$query "UPDATE usertable SET pwd='hehehe', admin='yes', trusted=100 WHERE
...;"
;

?>

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

Пример #4 Выполнение команд операционной системы на сервере (для базы MSSQL)

<?php

$query  
"SELECT * FROM products WHERE id LIKE '%$prod%'";
$result mssql_query($query);

?>
Если взломщик введет значениме a%' exec master..xp_cmdshell 'net user test testpass /ADD' -- для переменной $prod, тогда запрос $query будет выглядеть так:
<?php

$query  
"SELECT * FROM products
                    WHERE id LIKE '%a%'
                    exec master..xp_cmdshell 'net user test testpass /ADD'--"
;
$result mssql_query($query);

?>
MSSQL сервер выполняет SQL-команды в пакетном режиме, в том числе и операции по заведению локальных учетных записей базы данных. В случае, если приложение работает с привилегиями администратора sa и сервис MSSQL запущен с необходимыми привилегиями, то выполнив приведенные выше действия, взломщик получит аккаунт для доступа к серверу.

Замечание:

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

Рабочий пример проблем, вызываемых SQL-инъекцией
Авторство изображения принадлежит » xkcd

Способы защиты

Хотя по-прежнему очевидно, что взломщик должен обладать по крайней мере некоторыми знаниями о структуре базы данных чтобы провести успешную атаку, получить эту информацию зачастую очень просто. Например, если база данных является частью open-source или другого публично доступного программного пакета с инсталляцией по умолчанию, эта информация является полностью открытой и доступной. Эти данные также могут быть получены из закрытого проекта, даже если он закодирован, усложнен, или скомпилирован, и даже из вашего личного кода через отображение сообщений об ошибках. К другим методам относится использование распространенных (легко угадываемых) названий таблиц и столбцов. Например, форма логина, которая использует таблицу 'users' c названиями столбцов 'id', 'username' и 'password'.

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

  • Никогда не соединяйтесь с базой данных, используя учетную запись владельца базы данных или суперпользователя. Всегда старайтесь использовать специально созданных пользователей с максимально ограниченными правами.
  • Всегда проверяйте введенные данные на соответствие ожидаемому типу. В PHP есть множество функций для проверки данных: начиная от простейших функций для работы с переменными и функций определения типа символов (таких как is_numeric() и ctype_digit() соответственно) и заканчивая Perl-совместимыми регулярными выражениями.
  • В случае, если приложение ожидает цифровой ввод, примените функцию is_numeric() для проверки введенных данных, или принудительно укажите их тип при помощи settype(), или просто используйте числовое представление при помощи функции sprintf().

    Пример #5 Более безопасная реализация постраничной навигации

    <?php

    settype
    ($offset'integer');
    $query "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";

    // обратите внимание на формат %d, использование %s было бы бессмысленно
    $query sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %d;",
                     
    $offset);

    ?>

  • Экранируйте любой нецифровой ввод, используемый в запросах к БД при помощи специальных экранирующих функций, специфичных для используемой вами базы данных (например, mysql_real_escape_string(), sqlite_escape_string() и т.д.). Если нет экранирующей функции, специально разработанной для вашей базы данных, то, возможно, функции addslashes() и str_replace() смогут оказаться полезными (в зависимости от типа базы данных). Обратите внимание на первый пример. Следует помнить, что одного использования кавычек в запросе мало, это легко обойти.
  • Ни в коем случае не выводите никакой информации о БД, особенно о ее структуре. Также ознакомьтесь с соответствующими разделами документации: "Сообщения об ошибках" и "Функции обработки и логирования ошибок".
  • Вы можете использовать хранимые процедуры и заранее определенные курсоры для абстрагированной работы с данными, не предоставляя пользователям прямого доступа к данным и представлениям, но это решение имеет свои особенности.

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



add a note add a note User Contributed Notes SQL-инъекции
Richard dot Corfield at gmail dot com 18-Nov-2011 02:01
The best way has got to be parameterised queries. Then it doesn't matter what the user types in the data goes to the database as a value.

A quick search online shows some possibilities in PHP which is great! Even on this site - http://php.net/manual/en/pdo.prepared-statements.php
which also gives the reasons this is good both for security and performance.
kirby4 at live dot ca 19-Feb-2011 02:35
A good way to counter SQL injection for queries of type SELECT is use hash function on data by PHP and the database server.
For example, it is possible to use the MySQL function MD5 () to produce a hash of data-server side , and the equivalent function in php web-server side.

<?php
$login
= mysql_query("select f_uname, f_passwd from t_user where MD5(f_uname) = '".md5($uname)."' and MD5(f_passwd)='".md5($passwd)."'");
?>

Thus, the injected requests will be crushed and it will become much more difficult to obtain data in the database. Use both sides of the hash result in a comparison of hash, not the execution of the injected queries.

Unfortunately, it probably does not work with other types of queries.
smunday at visionaryweb dot com 17-Dec-2010 02:58
Another suggestion would be to build a series of DB procedures / functions that you give the DB user access to to manipulate or select data. That way, all input would run through this exposed interface and all parameters are forced to be typecast (or rejected).
Anonymous 26-Sep-2010 09:16
Pangolin is an automatic SQL injection penetration testing tool developed by NOSEC.
Its goal is to detect and take advantage of SQL injection vulnerabilities on web applications. Once it detects one or more SQL injections on the target host, the user can choose among a variety of options to perform an extensive back-end database management system fingerprint, retrieve DBMS session user and database, enumerate users, password hashes, privileges, databases, dump entire or user"s specific DBMS tables/columns, run his own SQL statement, read specific files on the file system and more.
wang dot liang dot com at gmail dot com 11-Mar-2010 03:11
another way to stop sql injection when you odbc_*: create two users,
one has only select permission,
the other has only delete, update, and insert permission,

so you can use select-only user to call odbc_exec while you don't have to check the sql injection; and you use d/u/i only user to update database by calling odbc_prepare and odbc_execute.
fyrye 06-Aug-2009 03:59
Another way to prevent SQL injections as opposed to binary, is to use URL Encoding or Hex Encoding.
I haven't seen a complete example of stopping SQL Injections, most refer to use the mysql_real_escape_string function or param statements.

Several examples at http://en.wikipedia.org/wiki/SQL_injection

Which will stop \x00, \n, \r, \, ', " and \x1a based attacks.
Alot depends on your SQL query structure, though vector level attacks are still viable.

Other than that build your own regex replacement to protect specific queries that could alter or compromise your database/results for specific sections of your processing pages.
Also use unique table and field names. Not just putting _ infront of them...
Example, don't store User/s or Customer/s information in a table named the same.
And NEVER use the same form field names for database field names.
bendikt [at] armed [dot] nu 27-Jul-2009 07:31
i just played around with the array_walk function.
It suddenly struck me that almost all super globals are arrays.
So what i discovered was that i can apply the array_walk function to the super globals. Doing so you automatically run a function call through the super globals
With this piece of code i wrote you should be able to secure most of you input data.

<?php

class secure
{
    function
secureSuperGlobalGET(&$value, $key)
    {
       
$_GET[$key] = htmlspecialchars(stripslashes($_GET[$key]));
       
$_GET[$key] = str_ireplace("script", "blocked", $_GET[$key]);
       
$_GET[$key] = mysql_escape_string($_GET[$key]);
        return
$_GET[$key];
    }
   
    function
secureSuperGlobalPOST(&$value, $key)
    {
       
$_POST[$key] = htmlspecialchars(stripslashes($_POST[$key]));
       
$_POST[$key] = str_ireplace("script", "blocked", $_POST[$key]);
       
$_POST[$key] = mysql_escape_string($_POST[$key]);
        return
$_POST[$key];
    }
       
    function
secureGlobals()
    {
       
array_walk($_GET, array($this, 'secureSuperGlobalGET'));
       
array_walk($_POST, array($this, 'secureSuperGlobalPOST'));
    }
}

?>
Note that you can modify this in anyway to suit your needs.
The Script has been tested.
Quietust 02-Jun-2009 05:23
An anonymous comment below suggests using PEAR with prepare() / execute() - though it was posted 3 years ago, it is still true today, though it's even easier now since PDO is included in most distributions. For SET/WHERE clauses and INSERT statements, just prepare the query with question marks in place of the dynamic parameters, bind each value in, then execute it, and it'll do all of the escaping for you, rendering the query immune to injection. Dynamic substitution of ORDER BY or LIMIT clauses has to be done the old fashioned way, though, so you still need to be careful with those.

Even without PDO, if you're using Postgres, you've already got the means to use parameterized queries, and if you're using MySQL, you simply need to ignore the outdated "mysql" extension and use "mysqli" instead.
fOV 12-Nov-2008 04:53
The best prevention is to deactivate master..xp_cmdshell.
Run in isql the command `sp_configure 'xp_cmdshell''
If the value for "config value" is 1 then make in the isql
`sp_configure 'xp_cmdshell',0'.
If you want see the options from your mssql-Server make
`sp_configure 'show advanced options',1'
and then `sp_configure'
jaimthorn at yahoo dot com 13-Oct-2008 02:32
dark dot avenger at email dot cz wrote:

"I think that easy way to protect against SQL injection is to convert inputted data into binary format, so that whatever input is, in sql query it will consist only of 1s and 0s."

Unless there is a 1-to-1 correspondence between your inputted data and the characters in your 'binary' format, a SELECT query wouldn't work anymore.  Not a binary format, but it makes my point: MIME encoding the text 'Dark Avenger' results in 'RGFyayBBdmVuZ2Vy'.  If I wanted to look up anyone with 'Avenger' in his/her name, then 'Avenger' would be encoded as 'QXZlbmdlcg==' which clearly wouldn't result in a hit on 'RGFyayBBdmVuZ2Vy'.

If there IS a 1-to-1 correspondence, then EITHER your solution only makes it a bit harder to perform a SQL injection (a hacker would have to figure out what mapping was used between the text and the 'binary' format), OR you've come up with simply another way to escaping your data.  Either isn't a terribly good solution to the SQL injection problem.
dark dot avenger at email dot cz 14-Aug-2008 01:09
I think that easy way to protect against SQL injection is to convert inputted data into binary format, so that whatever input is, in sql query it will consist only of 1s and 0s.
valerylourie at gmail dot com 15-Apr-2008 12:23
Note that PHP 5 introduced filters that you can use for untrusted user input:
http://us.php.net/manual/en/intro.filter.php
ctm at etheon dot net 01-Aug-2006 08:39
This is a very helpful document from the MySQL site (in .pdf format) :

http://dev.mysql.com/tech-resources/articles/
guide-to-php-security-ch3.pdf
16-Mar-2006 09:48
If you use the PEAR package and prepare() / execute() your queries,
you will hardly have to worry about any of this. Of course, it's still
a good idea to make sure you're putting valid data in your database...

 
show source | credits | stats | sitemap | contact | advertising | mirror sites