只看:
(来源:https : //xkcd.com/327/)
此SQL的作用是:
Robert'); DROP TABLE STUDENTS; --
我知道这两个'
和--
是征求意见,但不字DROP
得到的评论,以及因为它是在同一行的一部分吗?
只看:
(来源:https : //xkcd.com/327/)
此SQL的作用是:
Robert'); DROP TABLE STUDENTS; --
我知道这两个'
和--
是征求意见,但不字DROP
得到的评论,以及因为它是在同一行的一部分吗?
Answers:
它放下学生桌。
学校程序中的原始代码可能看起来像
q = "INSERT INTO Students VALUES ('" + FNMName.Text + "', '" + LName.Text + "')";
如您所见,这是将文本输入添加到查询中的简单方法,并且非常糟糕。
在将名字,中间名文本框FNMName.Text(即Robert'); DROP TABLE STUDENTS; --
)和姓氏文本框LName.Text(即称为Derper
)的值与查询的其余部分连接在一起之后,结果实际上是两个查询,中间用语句终止符(分号)。第二个查询已注入到第一个查询中。当代码对数据库执行此查询时,它将看起来像这样
INSERT INTO Students VALUES ('Robert'); DROP TABLE Students; --', 'Derper')
用简单的英语粗略地翻译成两个查询:
向“学生”表中添加一个名称为“ Robert”的新记录
和
删除学生表
第二个查询之后的所有内容都标记为注释: --', 'Derper')
将'
在学生的名字是不是一个评论,它的关闭字符串分隔符。由于学生的名字是一个字符串,因此在语法上需要它来完成假设的查询。注入攻击仅在其将有效查询注入结果的SQL查询时起作用。
根据dan04的敏锐评论重新编辑
INSERT
,则括号会更有意义。它还可以解释为什么数据库连接不处于只读模式。
INSERT
。倒SELECT
想,该表无论如何都不会运行,因为表中的“插入小鲍比表”已经删除了该表。
Students
期望的不仅仅是一个列(原始/正确的语句提供了两列)。也就是说,第二列的存在有助于说明为什么需要评论;而且由于无法更改Bobby的名字,所以最好仅保留这一观察结果作为注脚。
假设该名称用于变量中$Name
。
然后,您运行以下查询:
INSERT INTO Students VALUES ( '$Name' )
该代码错误地放置了用户提供的任何变量。
您希望SQL是:
插入学生值(' Robert Tables`)
但是聪明的用户可以提供他们想要的任何东西:
插入学生值(' Robert'); DROP TABLE Students; --')
您得到的是:
INSERT INTO Students VALUES ( 'Robert' ); DROP TABLE STUDENTS; --' )
该--
只评论了该行的其余部分。
正如其他所有人已经指出的那样,');
关闭原始语句,然后再执行第二条语句。到目前为止,大多数框架(包括PHP之类的语言)都具有默认的安全设置,不允许在一个SQL字符串中包含多个语句。例如,在PHP中,使用该mysqli_multi_query
函数只能在一个SQL字符串中运行多个语句。
但是,您可以通过SQL注入操作现有的SQL语句,而不必添加第二条语句。假设您有一个登录系统,可通过以下简单选择来检查用户名和密码:
$query="SELECT * FROM users WHERE username='" . $_REQUEST['user'] . "' and (password='".$_REQUEST['pass']."')";
$result=mysql_query($query);
如果提供peter
作为用户名和secret
密码的密码,则生成的SQL字符串如下所示:
SELECT * FROM users WHERE username='peter' and (password='secret')
一切安好。现在,假设您提供以下字符串作为密码:
' OR '1'='1
然后,所得的SQL字符串将为:
SELECT * FROM users WHERE username='peter' and (password='' OR '1'='1')
这样一来,您无需知道密码即可登录任何帐户。因此,尽管可以提供多个语句,但可以做更多破坏性的工作,因此不必使用两个语句即可使用SQL注入。
不,'
不是SQL中的注释,而是定界符。
妈妈认为数据库程序员发出的请求看起来像:
INSERT INTO 'students' ('first_name', 'last_name') VALUES ('$firstName', '$lastName');
(例如)添加新学生,其中$xxx
变量内容直接从HTML表单中获取,而无需检查格式或转义特殊字符。
因此,如果$firstName
包含Robert'); DROP TABLE students; --
该数据库程序,则将直接在DB上执行以下请求:
INSERT INTO 'students' ('first_name', 'last_name') VALUES ('Robert'); DROP TABLE students; --', 'XKCD');
即。它将尽早终止insert语句,执行破解者想要的任何恶意代码,然后注释掉可能存在的任何其余代码。
嗯,我太慢了,在橙色乐队中我已经看到8个答案了... :-)似乎是一个受欢迎的话题。
-应用程序接受输入,在这种情况下为“ Nancy”,而没有尝试-清理输入,例如通过转义特殊字符 school => INSERT INTO students VALUES ('Nancy' );插入0 1 -当操纵数据库命令的输入以执行SQL注入-使数据库服务器执行任意SQL 派=> INSERT INTO students VALUES ('Robert' ); DROP TABLE 学生; -'); 插入0 1 删除表 -学生记录现在不见了-甚至可能更糟! 学校=> 选择* 来自学生; ERROR : 关于“生” 不不存在 LINE 1 :SELECT * FROM 学生; ^
(此答案中的所有代码示例均在PostgreSQL 9.1.2数据库服务器上运行。)
为了清楚说明正在发生的事情,让我们使用一个仅包含名称字段并添加一行的简单表进行尝试:
school => 创建表的学生(名称TEXT PRIMARY KEY ); 注意:CREATE TABLE / PRIMARY KEY 将创建隐含指数“students_pkey” 对表“学生” CREATE TABLE 学校=> INSERT INTO 学生VALUES ('约翰' ); 插入0 1
假设应用程序使用以下SQL将数据插入表中:
INSERT INTO 学生VALUES ('foobar的' );
替换foobar
为学生的实际姓名。普通的插入操作如下所示:
-输入:南西 学校=> INSERT INTO 学生VALUES ('南茜' ); 插入0 1
查询表时,得到以下信息:
学校=> 选择* 来自学生; 名称 ------- 约翰 南希 (2 行)
将小鲍比表的名称插入表中会发生什么?
-输入:Robert'); DROP TABLE学生;- 学校=> INSERT INTO 学生VALUES ('罗伯特' ); DROP TABLE 学生; -'); 插入0 1 删除表
此处的SQL注入是学生姓名终止该语句并包括一个单独的DROP TABLE
命令的结果;输入末尾的两个破折号用于注释掉所有否则会导致错误的剩余代码。输出的最后一行确认数据库服务器已删除该表。
重要的是要注意,在INSERT
操作过程中,应用程序不会检查输入是否有任何特殊字符,因此允许将任意输入输入到SQL命令中。这意味着恶意用户可以在通常用于用户输入的字段中插入特殊符号(例如引号)以及任意SQL代码,以使数据库系统执行该代码,从而进行SQL 注入。
结果?
学校=> 选择* 来自学生; ERROR : 关于“生” 不不存在 LINE 1 :SELECT * FROM 学生; ^
SQL注入等同于操作系统或应用程序中的远程任意代码执行漏洞。不能低估成功的SQL注入攻击的潜在影响-根据数据库系统和应用程序配置,攻击者可以使用它来导致数据丢失(在这种情况下),获得对数据的未授权访问甚至执行主机本身上的任意代码。
正如XKCD漫画所指出的那样,防止SQL注入攻击的一种方法是对数据库输入进行清理,例如转义特殊字符,以使它们无法修改基础SQL命令,从而不会导致执行任意SQL代码。如果使用参数化查询(例如SqlParameter
在ADO.NET中使用),则输入将至少自动进行清理以防止SQL注入。
但是,在应用程序级别清理输入可能不会停止更高级的SQL注入技术。例如,有一些方法可以绕过mysql_real_escape_string
PHP函数。为了增加保护,许多数据库系统都支持预备语句。如果在后端正确实现,则预备语句可以通过将数据输入与其余命令在语义上分开来使SQL注入无法进行。
假设您天真地编写了这样的学生创建方法:
void createStudent(String name) {
database.execute("INSERT INTO students (name) VALUES ('" + name + "')");
}
有人输入名字 Robert'); DROP TABLE STUDENTS; --
在数据库上运行的是此查询:
INSERT INTO students (name) VALUES ('Robert'); DROP TABLE STUDENTS --')
分号结束插入命令并开始另一个命令。-注释掉该行的其余部分。DROP TABLE命令已执行...
这就是为什么绑定参数是一件好事。
单引号是字符串的开头和结尾。分号是语句的结尾。因此,如果他们正在做这样的选择:
Select *
From Students
Where (Name = '<NameGetsInsertedHere>')
SQL将变为:
Select *
From Students
Where (Name = 'Robert'); DROP TABLE STUDENTS; --')
-- ^-------------------------------^
在某些系统上,select
会先运行,然后运行drop
语句!消息是:不要将值嵌入SQL。而是使用参数!
在这种情况下,'不是注释字符。它用于分隔字符串文字。这位漫画家基于这样的想法,即有关学校在某个地方具有如下所示的动态sql:
$sql = "INSERT INTO `Students` (FirstName, LastName) VALUES ('" . $fname . "', '" . $lname . "')";
因此,现在'字符在程序员期望之前结束了字符串文字。结合; 字符结束语句,攻击者现在可以添加所需的任何sql。最后的-注释是为了确保原始语句中剩余的所有sql都不会阻止查询在服务器上编译。
FWIW,我还认为所涉及的漫画有一个重要的细节错误:如果您正在考虑清理数据库输入(正如漫画所暗示的那样),那么您仍然做错了。相反,您应该考虑隔离数据库输入,正确的方法是通过参数化查询。
它是这样工作的:假设管理员正在寻找学生的记录
Robert'); DROP TABLE STUDENTS; --
由于管理员帐户具有较高的特权,因此可以从该帐户删除表。
从请求中检索用户名的代码是
现在查询将是这样的(以搜索student表)
String query="Select * from student where username='"+student_name+"'";
statement.executeQuery(query); //Rest of the code follows
结果查询变为
Select * from student where username='Robert'); DROP TABLE STUDENTS; --
由于未清理用户输入,因此上述查询已分为两部分
Select * from student where username='Robert');
DROP TABLE STUDENTS; --
双破折号(-)只会注释掉查询的其余部分。
这很危险,因为它可能会使密码身份验证无效(如果存在)
第一个将进行常规搜索。
如果该帐户具有足够的特权,则第二个将删除该表格的学生(通常,学校管理员帐户将运行此类查询,并且具有上述特权)。
SELECT* FROM sutdents ...
-您忘记了“ s”。这就是您要删除的内容。DROP TABLE STUDENTS;
之前没有人指出这一点,所以我可能会提醒一些人。
通常,我们将尝试修补表单输入。但这不是您唯一会受到SQL注入攻击的地方。您可以使用URL进行非常简单的攻击,该URL通过GET请求发送数据。请考虑以下示例:
<a href="/show?id=1">show something</a>
您的网址应为 http://yoursite.com/show?id=1
现在有人可以尝试这样的事情
http://yoursite.com/show?id=1;TRUNCATE table_name
尝试用实际的表名替换table_name。如果他正确设置了您的表名,他们将清空您的表!(用简单的脚本强行强制使用此URL很容易)
您的查询看起来像这样...
"SELECT * FROM page WHERE id = 4;TRUNCATE page"
<?php
...
$id = $_GET['id'];
$pdo = new PDO($database_dsn, $database_user, $database_pass);
$query = "SELECT * FROM page WHERE id = {$id}";
$stmt = $pdo->query($query);
$data = $stmt->fetch();
/************* You have lost your data!!! :( *************/
...
<?php
...
$id = $_GET['id'];
$query = 'SELECT * FROM page WHERE id = :idVal';
$stmt = $pdo->prepare($query);
$stmt->bindParam('idVal', $id, PDO::PARAM_INT);
$stmt->execute();
$data = $stmt->fetch();
/************* Your data is safe! :) *************/
...