Heim > Artikel > Backend-Entwicklung > PHP-Datenbanksicherheit – SQL-Injection und vorbeugende Maßnahmen
Viele Webentwickler sind sich nicht darüber im Klaren, dass SQL-Abfragen manipuliert werden können, und behandeln SQL-Abfragen daher als vertrauenswürdige Befehle. Sie wussten nicht, dass SQL-Abfragen Zugriffskontrollen und damit Authentifizierungs- und Berechtigungsprüfungen umgehen können. Darüber hinaus ist es möglich, Befehle auf Host-Betriebssystemebene über SQL-Abfragen auszuführen.
Direkte SQL-Befehlsinjektion ist eine Technik, die häufig von Angreifern verwendet wird, um vorhandene SQL-Anweisungen zu erstellen oder zu ändern, um versteckte Daten abzurufen, Schlüsselwerte zu überschreiben oder sogar Befehle des Datenbank-Host-Betriebssystems auszuführen. Dies wird dadurch erreicht, dass die Anwendung Benutzereingaben entgegennimmt und diese mit statischen Parametern in einer SQL-Abfrage kombiniert. Im Folgenden werden einige reale Beispiele aufgeführt.
Aufgrund der fehlenden Validierung der eingegebenen Daten und der Verbindung über einen Superuser oder ein anderes Datenbankkonto mit der Berechtigung zum Erstellen neuer Benutzer kann ein Angreifer einen neuen Superuser in der Datenbank erstellen.
Beispiel #1 Ein Code, der die Seitenanzeige von Daten implementiert... kann auch zum Erstellen eines Superusers (PostgreSQL-System) verwendet werden.
<?php $offset = $argv[0]; // 注意,没有输入验证! $query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;"; $result = pg_query($conn, $query); ?>
Allgemeine Benutzer klicken auf die Links „Vorherige Seite“ und „Nächste Seite“, in denen $offset zusammengefasst wurde. Der ursprüngliche Code geht nur davon aus, dass $offset ein numerischer Wert ist. Wenn jedoch jemand versucht, die folgende Anweisung zu urlencode() und sie dann zur URL hinzufügt:
0; insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd) select 'crack', usesysid, 't','t','crack' from pg_shadow where usename='postgres'; --
, kann er einen Superuser erstellen. Beachten Sie, dass 0; lediglich dazu dient, einen korrekten Offset bereitzustellen, um die ursprüngliche Abfrage abzuschließen, damit keine Fehler auftreten.
Hinweis:
-- ist ein SQL-Kommentarzeichen, das im Allgemeinen verwendet werden kann, um den SQL-Interpreter anzuweisen, die folgenden Anweisungen zu ignorieren.
Eine Möglichkeit, an das Passwort zu gelangen, besteht darin, die Seite mit den Suchergebnissen zu nutzen. Der Angreifer muss lediglich herausfinden, welche Variablen für SQL-Anweisungen übermittelt und falsch behandelt wurden. Solche Variablen werden normalerweise in bedingten Anweisungen in SELECT-Abfragen verwendet, z. B. WHERE, ORDER BY, LIMIT und OFFSET. Wenn die Datenbank das UNION-Konstrukt unterstützt, kann ein Angreifer auch eine vollständige SQL-Abfrage an die ursprüngliche Anweisung anhängen, um das Passwort aus einer beliebigen Datentabelle zu erhalten. Daher ist es wichtig, das Passwortfeld zu verschlüsseln.
Beispiel #2 Artikel anzeigen... und ein Passwort (beliebiges Datenbanksystem)
<?php $query = "SELECT id, name, inserted, size FROM products WHERE size = '$size' ORDER BY $order LIMIT $limit, $offset;"; $result = odbc_exec($conn, $query); ?>
Sie können der ursprünglichen Abfrage eine weitere SELECT-Abfrage hinzufügen, um das Passwort zu erhalten:
' union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable; --
Wenn die obige Anweisung (mit ' und --) zu einer Variablen in $query hinzugefügt wird, treten Probleme auf.
UPDATE in SQL ist ebenfalls anfällig. Diese Abfrage kann auch wie im obigen Beispiel in eine andere vollständige Anfrage eingefügt oder angehängt werden. Angreifer greifen jedoch lieber auf die SET-Klausel zurück, um einige Daten in der Tabelle zu ändern. In diesem Fall müssen Sie die Struktur der Datenbank kennen, um die Abfrage erfolgreich ändern zu können. Felder können anhand der Variablennamen im Formular erraten oder mit roher Gewalt geknackt werden. Es gibt nicht viele Möglichkeiten, die Felder zu benennen, in denen Benutzernamen und Passwörter gespeichert werden.
Beispiel Nr. 3 Vom Zurücksetzen des Passworts... bis zur Erlangung weiterer Berechtigungen (jedes Datenbanksystem)
<?php $query = "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';"; ?>
Aber ein böswilliger Benutzer wird ' oder uid wie '�min%'; Übermitteln Sie den Wert von $uid als Variablenwert, um das Administratorkennwort zu ändern, oder übermitteln Sie den Wert von $pwd als „hehehe“, admin=‘yes‘, vertrauenswürdig=100“ (mit einem Leerzeichen dahinter), um weitere Berechtigungen zu erhalten. Dadurch wird die Abfrage effektiv wie folgt:
<?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 ...;"; ?>
Das folgende schreckliche Beispiel zeigt, wie Systembefehle für einige Datenbanken ausgeführt werden.
Beispiel #4 Angriff auf das Betriebssystem des Hosts, auf dem sich die Datenbank befindet (MSSQL Server)
<?php $query = "SELECT * FROM products WHERE id LIKE '%$prod%'"; $result = mssql_query($query); ?>
Wenn der Angriff einem%' exec master..xp_cmdshell 'net-Benutzer übermittelt wird test testpass /ADD' – Als Wert der Variablen $prod wird $query zu
<?php $query = "SELECT * FROM products WHERE id LIKE '%a%' exec master..xp_cmdshell 'net user test testpass /ADD'--"; $result = mssql_query($query); ?>
Der MSSQL-Server führt diese SQL-Anweisung aus, einschließlich des Befehls dahinter zum Hinzufügen von Benutzern zum System. Wenn dieses Programm als sa ausgeführt wird und der MSSQLSERVER-Dienst über ausreichende Berechtigungen verfügt, kann der Angreifer ein Systemkonto erhalten, um auf den Host zuzugreifen.
Hinweis:
Obwohl sich das obige Beispiel auf ein bestimmtes Datenbanksystem bezieht, bedeutet dies nicht, dass ähnliche Angriffe nicht auch auf andere Datenbanksysteme ausgeführt werden können. Bei Verwendung unterschiedlicher Methoden können verschiedene Datenbanken leiden.
Vorbeugende Maßnahmen
Einige Leute trösten sich möglicherweise damit, dass der Angreifer die Informationen über die Datenbankstruktur kennen muss, um den oben genannten Angriff auszuführen. Ja, das ist es. Aber niemand kann garantieren, dass Angreifer diese Informationen nicht erhalten. Sobald sie dies tun, besteht die Gefahr, dass die Datenbank geleakt wird. Wenn Sie für den Zugriff auf die Datenbank ein Open-Source-Softwarepaket verwenden, beispielsweise ein Forenprogramm, ist es für einen Angreifer leicht, an den entsprechenden Code zu gelangen. Das Risiko ist noch größer, wenn der Code schlecht gestaltet ist.
这些攻击总是建立在发掘安全意识不强的代码上的。所以,永远不要信任外界输入的数据,特别是来自于客户端的,包括选择框、表单隐藏域和 cookie。就如上面的第一个例子那样,就算是正常的查询也有可能造成灾难。
永远不要使用超级用户或所有者帐号去连接数据库。要用权限被严格限制的帐号。
检查输入的数据是否具有所期望的数据格式。PHP 有很多可以用于检查输入的函数,从简单的变量函数和字符类型函数(比如 is_numeric(), ctype_digit())到复杂的Perl 兼容正则表达式函数都可以完成这个工作。
如果程序等待输入一个数字,可以考虑使用 is_numeric() 来检查,或者直接使用 settype() 来转换它的类型,也可以用 sprintf() 把它格式化为数字。
Example #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_escape_string() 和 sql_escape_string())把用户提交上来的非数字数据进行转义。如果数据库没有专门的敏感字符转义功能的话 addslashes() 和 str_replace() 可以代替完成这个工作。看看第一个例子,此例显示仅在查询的静态部分加上引号是不够的,查询很容易被攻破。
要不择手段避免显示出任何有关数据库的信心,尤其是数据库结构。
也可以选择使用数据库的存储过程和预定义指针等特性来抽象数库访问,使用户不能直接访问数据表和视图。但这个办法又有别的影响。
除此之外,在允许的情况下,使用代码或数据库系统保存查询日志也是一个好办法。显然,日志并不能防止任何攻击,但利用它可以跟踪到哪个程序曾经被尝试攻击过。日志本身没用,要查阅其中包含的信息才行。毕竟,更多的信息总比没有要好。