来自“ Bobby Tables” XKCD漫画的SQL注入如何工作?


1092

只看:

XKCD条 (来源:https : //xkcd.com/327/

此SQL的作用是:

Robert'); DROP TABLE STUDENTS; --

我知道这两个'--是征求意见,但不字DROP得到的评论,以及因为它是在同一行的一部分吗?


16
如果您收听#31的Stack Overflow播客(2008年11月27日),他们实际上会对此进行讨论。
EBGreen

93
在MySQL中,'不用于注释。即使有,在它前面也没有空格,因此它只能在它前面的字符串结尾。

45
就XKCD而言,如果对某些漫画有任何疑问,您可以随时访问Explain XKCD并找出答案。甚至还有一个XKCD Wiki,它对一些棘手的漫画如XKCD geohashing很有帮助
Anatoli

13
我相信,此链接必须记录在这里:bobby-tables.com
亚略“的

2
beta.companieshouse.gov.uk/company/10542519是一家名为的咨询公司的注册。DROP TABLE“ COMPANIES”;-LTD
Alex Dupuy

Answers:


1115

它放下学生桌。

学校程序中的原始代码可能看起来像

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的敏锐评论重新编辑


3
嗯,在参数
两边

60
@PhiLho:如果原始语句为INSERT,则括号会更有意义。它还可以解释为什么数据库连接不处于只读模式。
dan04

3
正如@ dan04解释的那样,使用括起来更有意义INSERT。倒SELECT想,该表无论如何都不会运行,因为表中的“插入小鲍比表”已经删除了该表。
ypercubeᵀᴹ

10
实际上,在此示例中,第一个查询(“添加新记录...”)将失败,因为Students期望的不仅仅是一个列(原始/正确的语句提供了两列)。也就是说,第二列的存在有助于说明为什么需要评论;而且由于无法更改Bobby的名字,所以最好仅保留这一观察结果作为注脚。
eggyal 2013年

7
根据Explain XKCD,Bobby的姓氏-至少是他母亲的姓氏是Roberts。我不确定是否可以通过改正来提高答案的清晰度。
WBT

611

假设该名称用于变量中$Name

然后,您运行以下查询

INSERT INTO Students VALUES ( '$Name' )

该代码错误地放置了用户提供的任何变量。

您希望SQL是:

插入学生值(' Robert Tables`)

但是聪明的用户可以提供他们想要的任何东西:

插入学生值(' Robert'); DROP TABLE Students; --')

您得到的是:

INSERT INTO Students VALUES ( 'Robert' );  DROP TABLE STUDENTS; --' )

--只评论了该行的其余部分。


87
这比最高票数好得多,因为它解释了右括号。
TimBüthe2010年

1
顺便说一句,由于删除了学生表,因此漫画中的学校负责人或XSS都无法知道,他不知道是谁做的。
xryl669

@ xryl669在这种情况下,日志非常有用...有时会记录所有查询,有时其他记录的信息可以帮助您推断出罪魁祸首。
inemanja

165

正如其他所有人已经指出的那样,');关闭原始语句,然后再执行第二条语句。到目前为止,大多数框架(包括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注入。


71

不,'不是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个答案了... :-)似乎是一个受欢迎的话题。


39

TL; DR

-应用程序接受输入,在这种情况下为“ 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_stringPHP函数。为了增加保护,许多数据库系统都支持预备语句。如果在后端正确实现,则预备语句可以通过将数据输入与其余命令在语义上分开来使SQL注入无法进行。


30

假设您天真地编写了这样的学生创建方法:

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命令已执行...

这就是为什么绑定参数是一件好事。


26

单引号是字符串的开头和结尾。分号是语句的结尾。因此,如果他们正在做这样的选择:

Select *
From Students
Where (Name = '<NameGetsInsertedHere>')

SQL将变为:

Select *
From Students
Where (Name = 'Robert'); DROP TABLE STUDENTS; --')
--             ^-------------------------------^

在某些系统上,select会先运行,然后运行drop语句!消息是:不要将值嵌入SQL。而是使用参数!


18

');结束了查询,它不会开始一个注释。然后,它删除了students表并注释了应该执行的其余查询。


17

数据库的作者可能做了一个

sql = "SELECT * FROM STUDENTS WHERE (STUDENT_NAME = '" + student_name + "') AND other stuff";
execute(sql);

如果student_name是给定的,则使用名称“ Robert”进行选择,然后删除表。“-”部分将给定查询的其余部分更改为注释。


这是我的第一个想法,但是在结尾的括号后面出现语法错误,不是吗?
PhiLho

3
这就是为什么末尾有一个-,指示其余文本是注释,应忽略。

17

在这种情况下,'不是注释字符。它用于分隔字符串文字。这位漫画家基于这样的想法,即有关学校在某个地方具有如下所示的动态sql:

$sql = "INSERT INTO `Students` (FirstName, LastName) VALUES ('" . $fname . "', '" . $lname . "')";

因此,现在'字符在程序员期望之前结束了字符串文字。结合; 字符结束语句,攻击者现在可以添加所需的任何sql。最后的-注释是为了确保原始语句中剩余的所有sql都不会阻止查询在服务器上编译。

FWIW,我还认为所涉及的漫画有一个重要的细节错误:如果您正在考虑清理数据库输入(正如漫画所暗示的那样),那么您仍然做错了。相反,您应该考虑隔离数据库输入,正确的方法是通过参数化查询。


16

'SQL中的字符用于字符串常量。在这种情况下,它用于结束字符串常量,而不用于注释。


7

它是这样工作的:假设管理员正在寻找学生的记录

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;
DevWL

4

您无需输入表单数据即可进行SQL注入。

之前没有人指出这一点,所以我可能会提醒一些人。

通常,我们将尝试修补表单输入。但这不是您唯一会受到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"

使用PDO的PHP易受攻击的代码示例:

<?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!!! :( *************/
...

解决方案-使用PDO prepare()和bindParam()方法:

<?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! :) *************/
...
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.