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

只是看着:

XKCD地带(来源:https://xkcd.com/327/

这个SQL做什么:

Robert'); DROP TABLE STUDENTS; --

我知道'--都是用于注释的,但是DROP这个词不是也被注释了吗,因为它是同一行的一部分?

274865 次浏览

它把学生的桌子。

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

q = "INSERT INTO Students VALUES ('" + FNMName.Text + "', '" + LName.Text + "')";

这是将文本输入添加到查询中的简单方法,并且是非常糟糕,您将看到。

在将第一个名称、中间名称文本框文本名称(即Robert'); DROP TABLE STUDENTS; --)和最后一个名称文本框文本名称(我们称之为Derper)的值与查询的其余部分连接起来之后,结果实际上是两个查询,由语句终止符(分号)分隔。第二个查询已经注入进入第一个查询。当代码对数据库执行此查询时,它将如下所示

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

用简单的英语,大致翻译为两个查询:

将Name值为“Robert”的新记录添加到学生表

删除学生表

第二个查询之后的所有内容都是标记为评论--', 'Derper')

学生姓名中的'不是注释,而是结束的字符串分隔符。由于学生姓名是一个字符串,因此在语法上需要它来完成假设的查询。注入攻击只起作用当SQL查询时,它们将结果注入有效的SQL

根据dan04

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

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

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

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

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

假设该名称在变量$Name中使用。然后运行此查询:

INSERT INTO Students VALUES ( '$Name' )

代码错误地将用户提供的任何内容作为变量。您希望SQL为:

插入学生价值观(“Robert Tables”)

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

插入学生价值观(“Robert'); DROP TABLE学生;--”)

你得到的是:

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

--只注释该行的其余部分。

在这种情况下,'不是注释字符。它用于分隔字符串文字。漫画艺术家指望有问题的学校在某个地方有动态sql,看起来像这样:

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

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

FWIW,我还认为有问题的漫画有一个重要的细节错误:如果你<强>消毒你的数据库输入,正如漫画所暗示的那样,你仍然做错了。相反,你应该从隔离中的角度思考你的数据库输入,正确的方法是通过参数化查询/准备好的语句。

假设你天真地写了一个这样的学生创作方法:

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命令被执行…

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

不,'不是SQL中的注释,而是分隔符。

妈妈认为数据库程序员提出了一个请求,看起来像:

INSERT INTO 'students' ('first_name', 'last_name') VALUES ('$firstName', '$lastName');

(例如)添加新学生,其中$xxx变量内容直接从超文本标记语言表单中取出,而不检查格式也不转义特殊字符。

因此,如果$firstName包含Robert'); DROP TABLE students; --,数据库程序将直接在数据库上执行以下请求:

INSERT INTO 'students' ('first_name', 'last_name') VALUES ('Robert'); DROP TABLE students; --', 'XKCD');

即。它将提前终止插入语句,执行破解者想要的任何恶意代码,然后注释掉可能存在的任何剩余代码。

嗯,我太慢了,我在橙色带之前已经看到了8个答案……:-)一个热门话题,似乎。

单引号是字符串的开始和结束。分号是语句的结尾。所以如果他们像这样做选择:

Select *From StudentsWhere (Name = '<NameGetsInsertedHere>')

SQL将成为:

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

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

正如其他人已经指出的那样,');关闭原始语句,然后是第二条语句。大多数框架,包括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注入,尽管如果您能够提供多个语句,您可能会做更具破坏性的事情。

太长别读

-- The application accepts input, in this case 'Nancy', without attempting to-- sanitize the input, such as by escaping special charactersschool=> INSERT INTO students VALUES ('Nancy');INSERT 0 1
-- SQL injection occurs when input into a database command is manipulated to-- cause the database server to execute arbitrary SQLschool=> INSERT INTO students VALUES ('Robert'); DROP TABLE students; --');INSERT 0 1DROP TABLE
-- The student records are now gone - it could have been even worse!school=> SELECT * FROM students;ERROR:  relation "students" does not existLINE 1: SELECT * FROM students;^

这将删除(删除)学生表。

此答案中的所有代码示例都在PostgreSQL 9.1.2数据库服务器上运行。

为了弄清楚发生了什么,让我们用一个仅包含name字段的简单表尝试一下,并添加一行:

school=> CREATE TABLE students (name TEXT PRIMARY KEY);NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "students_pkey" for table "students"CREATE TABLEschool=> INSERT INTO students VALUES ('John');INSERT 0 1

假设应用程序使用以下SQL将数据插入表:

INSERT INTO students VALUES ('foobar');

foobar替换为学生的实际姓名。正常的插入操作如下所示:

--                            Input:   Nancyschool=> INSERT INTO students VALUES ('Nancy');INSERT 0 1

当我们查询表时,我们得到这个:

school=> SELECT * FROM students;name-------JohnNancy(2 rows)

当我们将Little Bobby Tables的名字插入表中时会发生什么?

--                            Input:   Robert'); DROP TABLE students; --school=> INSERT INTO students VALUES ('Robert'); DROP TABLE students; --');INSERT 0 1DROP TABLE

此处的SQL注入是终止语句的学生名称的结果,并包含一个单独的DROP TABLE命令;输入末尾的两个破折号旨在注释掉任何可能导致错误的剩余代码。输出的最后一行确认数据库服务器已删除该表。

重要的是要注意,在INSERT操作期间,应用程序没有检查任何特殊字符的输入,因此允许在SQL命令中输入任意输入。这意味着恶意用户可以在通常用于用户输入的字段中插入特殊符号,例如引号以及任意SQL代码,从而导致数据库系统执行它,因此SQL注入

结果呢?

school=> SELECT * FROM students;ERROR:  relation "students" does not existLINE 1: SELECT * FROM students;^

SQL注入相当于操作系统或应用程序中的远程任意代码执行漏洞。成功的SQL注入攻击的潜在影响不容低估——根据数据库系统和应用程序配置,攻击者可以利用它导致数据丢失(如本例)、未经授权访问数据,甚至在主机本身上执行任意代码。

正如XKCD漫画所指出的,防止SQL注入攻击的一种方法是清理数据库输入,例如通过转义特殊字符,以便它们不能修改底层SQL命令,因此不能导致任意SQL代码的执行。这可以在应用程序级别完成,一些参数化查询的实现通过清理输入来操作。

然而,在应用程序级别清理输入可能不会阻止更高级的SQL注入技术。例如,有一些方法可以绕过#0 PHP函数。为了增加保护,许多数据库系统支持准备好的声明。如果在后端正确实现,准备好的语句可以通过将数据输入视为与命令的其余部分在语义上分离来使SQL注入成为不可能。

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

Robert'); DROP TABLE STUDENTS; --

由于管理员帐户具有高权限,因此可以从此帐户中删除表。

从请求中检索用户名的代码是

现在查询将是这样的(搜索学生表)

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

由于用户输入未被过滤,上述查询已被操作为2部分

Select * from student where username='Robert');
DROP TABLE STUDENTS; --

双破折号(--)只会注释掉查询的剩余部分。

这是危险的,因为它可以取消密码身份验证,如果存在

第一个将进行正常搜索。

如果帐户具有足够的权限,第二个将删除表学生(通常学校管理员帐户将运行此类查询并具有上面提到的权限)。

您不需要输入表单数据来进行SQL注入。

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

大多数情况下,我们将尝试修补表单输入。但这并不是唯一可以通过SQL注入攻击的地方。您可以使用通过GET请求发送数据的URL进行非常简单的攻击;考虑休耕的例子:

<a href="/show?id=1">show something</a>

你的URL看起来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准备()和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! :) *************/...