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

How does the SQL injection from the "Bobby Tables" XKCD comic work?

提问人:Blankman 提问时间:12/2/2008 最后编辑:FloernBlankman 更新时间:7/12/2022 访问量:302600

问:

看看:

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

这个 SQL 有什么作用:

Robert'); DROP TABLE STUDENTS; --

我知道两者并且用于评论,但是这个词不是也被评论了吗,因为它是同一行的一部分?'--DROP

安全 验证 SQL 注入

评论

102赞 Lightness Races in Orbit 9/1/2011
在MySQL中,不用于注释。即使它是,它前面也没有空格,所以它只能结束它前面的字符串。'
16赞 Arioch 'The 11/1/2012
我相信这个链接必须记录在这里:bobby-tables.com
18赞 EBGreen 12/2/2008
如果你听 Stack Overflow 播客 #31(2008 年 11 月 27 日),他们实际上会讨论这个问题。
52赞 Anatoli 9/15/2011
就 XKCD 而言,如果对某些漫画有任何疑问,您可以随时前往 解释 XKCD 并找出您的答案。甚至还有一个 XKCD wiki,这对于一些棘手的漫画(如 XKCD 地理哈希)非常有帮助
5赞 Alex Dupuy 1/5/2017
beta.companieshouse.gov.uk/company/10542519 是名为 的咨询公司的注册;下拉列表 “公司”;-- LTD

答:

1203赞 20 revs, 9 users 72%Will #1

它删除了 students 表。

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

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

这是将文本输入添加到查询中的幼稚方法,正如您将看到的,这是非常糟糕的

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

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

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

向 Students 表添加一条新记录,其 Name 值为“Robert”

删除 Students 表格

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

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

根据 dan04 的精明评论再次编辑

评论

3赞 PhiLho 12/3/2008
嗯,参数周围带括号的 WHERE 很不寻常,但至少它避免了语法错误...... :-)
66赞 dan04 8/10/2010
@PhiLho:如果原始语句是 ,那么括号会更有意义。它还将解释为什么数据库连接不处于只读模式。INSERT
4赞 ypercubeᵀᴹ 1/22/2013
正如 @dan04 所解释的,括号与 .向后想想,无论如何都不会运行,因为表中的 Little Bobby Tables 的插入已经删除了表。INSERTSELECT
11赞 eggyal 4/28/2013
实际上,在此示例中,第一个查询(“添加新记录...”)将失败,因为需要的不仅仅是一列(原始/正确的语句提供了两列)。也就是说,第二列的存在有助于说明为什么需要注释;由于人们无法更改 Bobby 的名字,因此最好保持原样,只留下这个观察结果作为脚注。Students
8赞 WBT 8/4/2016
鲍比的姓氏 - 或者至少是他母亲的姓氏,是罗伯茨,根据 Explain XKCD。不过,我不确定纠正这一点是否会提高答案的清晰度。
21赞 Jorn 12/2/2008 #2

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

19赞 Rockcoder 12/2/2008 #3

SQL 中的字符用于字符串常量。在本例中,它用于结束字符串常量,而不是用于注释。'

19赞 Paul Tomblin 12/2/2008 #4

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

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

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

评论

0赞 PhiLho 12/2/2008
这是我的第一个想法,但你得到一个带有尾随右括号的语法错误,不是吗?
4赞 12/2/2008
这就是为什么在末尾有一个 -- 表示剩余的文本是注释,应该被忽略。
670赞 sinoth 12/2/2008 #5

假设该名称用于变量 .然后运行以下查询:$Name

INSERT INTO Students VALUES ( '$Name' )

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

INSERT INTO Students 值 ( 'Robert Tables` )

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

INSERT INTO Students 值 ( 'Robert'); DROP TABLE Students; --' )

你得到的是:

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

唯一的注释是该行的其余部分。--

评论

96赞 Tim Büthe 8/13/2010
这比得票最高的要好得多,因为它解释了右括号。
2赞 xryl669 2/28/2020
顺便说一句,漫画中的校长没有办法知道,或者XSS,因为学生表被删除了,他不知道是谁干的。
2赞 inemanja 3/6/2020
@xryl669 在这种情况下,日志非常有用......有时会记录所有查询,有时其他记录的信息可以帮助您推断罪魁祸首。
21赞 Joel Coehoorn 12/2/2008 #6

在本例中,不是注释字符。它用于分隔字符串文字。这位漫画家寄希望于这样的想法,即所讨论的学校在某处有动态 sql,如下所示:'

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

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

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

32赞 Dan Vinton 12/2/2008 #7

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

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 命令...

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

73赞 PhiLho 12/2/2008 #8

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

妈妈认为数据库程序员提出了一个请求,如下所示:

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

(例如)添加新学生,其中变量内容直接从 HTML 表单中取出,无需检查格式或转义特殊字符。$xxx

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

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

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

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

30赞 CodeAndCats 12/2/2008 #9

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

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

SQL 将变为:

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

在某些系统上,会先运行,然后是语句!消息是:不要将值嵌入到你的 SQL 中。请改用参数!selectdrop

175赞 Johannes Fahrenkrug 9/14/2011 #10

正如其他人已经指出的那样,关闭原始语句,然后是第二个语句。大多数框架,包括 PHP 等语言,现在都有默认的安全设置,不允许在一个 SQL 字符串中使用多个语句。例如,在 PHP 中,使用该函数只能在一个 SQL 字符串中运行多个语句。');mysqli_multi_query

但是,您可以通过 SQL 注入来操作现有 SQL 语句,而无需添加第二条语句。假设您有一个登录系统,它通过以下简单的选择来检查用户名和密码:

$query="SELECT * FROM users WHERE username='" . $_REQUEST['user'] . "' and (password='".$_REQUEST['pass']."')";
$result=mysql_query($query);

如果提供用户名和密码,则生成的 SQL 字符串将如下所示:petersecret

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 注入,尽管如果您能够提供多个语句,则可以执行更具破坏性的事情。

42赞 bwDraco 9/27/2011 #11

TL;博士

-- The application accepts input, in this case 'Nancy', without attempting to
-- sanitize the input, such as by escaping special characters
school=> 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 SQL
school=> INSERT INTO students VALUES ('Robert'); DROP TABLE students; --');
INSERT 0 1
DROP TABLE

-- The student records are now gone - it could have been even worse!
school=> SELECT * FROM students;
ERROR:  relation "students" does not exist
LINE 1: SELECT * FROM students;
                      ^

这将删除(删除)student 表。

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

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

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

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

INSERT INTO students VALUES ('foobar');

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

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

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

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

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

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

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

请务必注意,在操作过程中,应用程序不会检查输入中是否有任何特殊字符,因此允许在 SQL 命令中输入任意输入。这意味着恶意用户可以在通常用于用户输入的字段中插入特殊符号(如引号)以及任意 SQL 代码,以导致数据库系统执行它,从而进行 SQL 注入INSERT

结果呢?

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

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

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

但是,在应用程序级别清理输入可能不会阻止更高级的 SQL 注入技术。例如,有一些方法可以规避 PHP 函数mysql_real_escape_string。为了增加保护,许多数据库系统都支持预准备语句。如果在后端正确实现,预准备语句可以通过将数据输入在语义上与命令的其余部分分开来使 SQL 注入变得不可能。

评论

0赞 Charlieface 3/7/2021
未清理 SqlParameters。它们只是被解释为数据,而从不被解释为代码。这是正确的做事方式,将数据和代码分开
9赞 Noname 12/2/2013 #12

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

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

由于未对用户输入进行清理,因此上述查询分为 2 个部分

Select * from student where username='Robert'); 

DROP TABLE STUDENTS; --

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

这很危险,因为它可以使密码身份验证无效(如果存在)

第一个将进行正常搜索。

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

评论

0赞 DevWL 7/31/2019
SELECT* FROM sutdents ...- 你忘记了“s”。这就是你掉落的东西。DROP TABLE STUDENTS;
7赞 DevWL 7/31/2019 #13

您无需输入表单数据即可进行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 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! :) *************/
...

评论

0赞 jzuhri 10/25/2021
这个漏洞可以通过 $id = str_replace(';' , '', $_GET['id']); ??