"Bobby Tables" XKCD 漫画中的 SQL 注入是如何工作的?
Posted
技术标签:
【中文标题】"Bobby Tables" XKCD 漫画中的 SQL 注入是如何工作的?【英文标题】:How does the SQL injection from the "Bobby Tables" XKCD comic work? 【发布时间】:2010-09-24 20:17:49 【问题描述】:只是看着:
(来源:https://xkcd.com/327/)
这个 SQL 做了什么:
Robert'); DROP TABLE STUDENTS; --
我知道'
和--
都用于cmets,但是DROP
这个词不是也被评论了,因为它是同一行的一部分吗?
【问题讨论】:
如果你听Stack Overflow Podcast #31(2008 年 11 月 27 日),他们实际上会讨论这个问题。 在 mysql 中,'
不适用于 comments。即使是,它之前也没有空格,所以它只能结束它之前的字符串。
就XKCD而言,如果对某些漫画有任何疑问,您可以随时去Explain XKCD找到答案。甚至还有一个XKCD wiki,这对于XKCD geohashing等一些棘手的漫画很有帮助
我相信这个链接一定要记录在这里:bobby-tables.com
beta.companieshouse.gov.uk/company/10542519 是一家名为 ;删除表“公司”;-- LTD
【参考方案1】:
TL;DR
-- 应用程序接受输入,在本例中为“Nancy”,但不尝试 -- 清理输入,例如转义特殊字符 学校=> 插入学生价值观(“南希”); 插入 0 1 -- SQL 注入发生在数据库命令的输入被操纵到 -- 使数据库服务器执行任意 SQL 学校=> 插入学生价值观(“罗伯特”); DROP TABLE 学生; --'); 插入 0 1 删除表 ——学生记录现在不见了——情况可能更糟! 学校=> 从学生中选择 *; 错误:关系“学生”不存在 第 1 行:从学生中选择 *; ^
这会删除(删除)学生表。
(此答案中的所有代码示例均在 PostgreSQL 9.1.2 数据库服务器上运行。)
为了弄清楚发生了什么,让我们用一个只包含名称字段的简单表来试试这个并添加一行:
school=> CREATE TABLE 学生(名称 TEXT PRIMARY KEY); 注意:CREATE TABLE / PRIMARY KEY 将为表“students”创建隐式索引“students_pkey” 创建表 学校=> 插入学生价值观(“约翰”); 插入 0 1
假设应用程序使用以下 SQL 向表中插入数据:
插入学生价值观('foobar');
将foobar
替换为学生的实际姓名。正常的插入操作如下所示:
-- 输入:南希 学校=> 插入学生价值观(“南希”); 插入 0 1
当我们查询表时,我们得到:
学校=> 从学生中选择 *; 姓名 -------- 约翰 南希 (2 行)
当我们将 Little Bobby Tables 的名字插入表格时会发生什么?
-- 输入:罗伯特'); DROP TABLE 学生; -- 学校=> 插入学生价值观(“罗伯特”); DROP TABLE 学生; --'); 插入 0 1 删除表
这里的SQL注入是学生的名字终止语句并包含一个单独的DROP TABLE
命令的结果;输入末尾的两个破折号旨在注释掉任何可能导致错误的剩余代码。输出的最后一行确认数据库服务器已删除该表。
请务必注意,在INSERT
操作期间,应用程序不会检查输入是否有任何特殊字符,因此允许将任意输入输入到 SQL 命令中。这意味着恶意用户可以在通常用于用户输入的字段中插入特殊符号(例如引号)以及任意 SQL 代码,以使数据库系统执行它,因此SQL injection。
结果?
学校=> 从学生中选择 *; 错误:关系“学生”不存在 第 1 行:从学生中选择 *; ^
SQL 注入在数据库中相当于操作系统或应用程序中的远程arbitrary code execution 漏洞。成功的 SQL 注入攻击的潜在影响不可低估——取决于数据库系统和应用程序配置,攻击者可以利用它来导致数据丢失(如本例所示)、未经授权访问数据,甚至执行主机本身上的任意代码。
正如 XKCD 漫画所指出的,防止 SQL 注入攻击的一种方法是清理数据库输入,例如转义特殊字符,这样它们就无法修改底层 SQL 命令,因此不会导致执行任意 SQL 代码。这可以在应用程序级别完成,并且参数化查询的一些实现通过清理输入来操作。
但是,在应用程序级别清理输入可能不会阻止更高级的 SQL 注入技术。例如,there are ways to circumvent the mysql_real_escape_string
php function。为了增加保护,许多数据库系统支持prepared statements。如果在后端正确实施,准备好的语句可以通过将数据输入与命令的其余部分在语义上分开,从而使 SQL 注入成为不可能。
【讨论】:
SqlParameters 未经过处理。它们仅被解释为数据,从不被解释为代码。这是做事的正确方法,保持数据和代码分开【参考方案2】:在这种情况下,'
不是注释字符。它用于分隔字符串文字。漫画家认为,所讨论的学校在某处有动态 sql,看起来像这样:
$sql = "INSERT INTO `Students` (FirstName, LastName) VALUES ('" . $fname . "', '" . $lname . "')";
所以现在'
字符在程序员预期之前结束了字符串文字。结合;
字符来结束语句,攻击者现在可以添加(注入)他们想要的任何sql。末尾的--
注释是为了确保原始语句中的任何剩余 sql 都不会阻止查询在服务器上编译。
FWIW,我也认为有问题的漫画有一个重要的细节错误:如果你清理你的数据库输入,正如漫画所暗示的那样,你还在做错误的。相反,您应该考虑隔离您的数据库输入,而正确的做法是通过参数化查询/准备好的语句。
【讨论】:
【参考方案3】:假设名称用于变量$Name
。
然后你运行这个查询:
INSERT INTO Students VALUES ( '$Name' )
代码错误地将用户提供的任何内容作为变量放置。
您希望 SQL 是:
插入学生价值观('Robert Tables`)
但是聪明的用户可以提供他们想要的任何东西:
插入学生价值观 ('Robert');放表学生; --')
你得到的是:
INSERT INTO Students VALUES ( 'Robert' ); DROP TABLE STUDENTS; --' )
--
只匹配该行的其余部分。
【讨论】:
这比最高票数要好得多,因为它解释了右括号。 对了,漫画里的校长根本没办法察觉到XSS,因为学生表被删除了,他也不知道是谁干的。 @xryl669 日志在这种情况下非常有用...有时会记录所有查询,有时其他记录的信息可以帮助您推断罪魁祸首。【参考方案4】:SQL注入不需要输入表单数据。
之前没有人指出这一点,所以我可能会提醒你们中的一些人。
大多数情况下,我们会尝试修补表单输入。但这并不是您可能受到 SQL 注入攻击的唯一地方。您可以使用通过 GET 请求发送数据的 URL 进行非常简单的攻击; 考虑下面的例子:
<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 替换为真实的表名。如果他把你的桌子名字写对了,他们就会清空你的桌子! (很容易用简单的脚本暴力破解这个网址)
您的查询将如下所示...
"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! :) *************/
...
【讨论】:
这个漏洞可以用 $id = str_replace(';' , '', $_GET['id']); ??【参考方案5】:它会删除学生表。
学校程序中的原始代码可能看起来像
q = "INSERT INTO Students VALUES ('" + FNMName.Text + "', '" + LName.Text + "')";
这是将文本输入添加到查询中的幼稚方法,并且非常糟糕,正如您将看到的那样。
在名字的值之后,中间名文本框FNMName.Text(即Robert'); DROP TABLE STUDENTS; --
)和姓氏文本框LName.Text(我们称之为Derper
) 与查询的其余部分连接,结果现在实际上是 两个查询,由 statement terminator(分号)分隔。第二个查询已注入到第一个查询中。当代码对数据库执行此查询时,它将如下所示
INSERT INTO Students VALUES ('Robert'); DROP TABLE Students; --', 'Derper')
用简单的英语大致翻译为两个查询:
在 Student 表中添加一条 Name 值为“Robert”的新记录
和
删除学生表
第二个查询之后的所有内容都是marked as a comment: --', 'Derper')
学生姓名中的'
不是评论,它是结束语string delimiter。由于学生的姓名是一个字符串,因此在语法上需要它来完成假设查询。注入攻击仅在注入的 SQL 查询产生有效 SQL 时才起作用。
根据dan04的精明评论再次编辑
【讨论】:
嗯,带括号的 WHERE 参数是相当不寻常的,但至少它避免了语法错误... :-) @PhiLho:如果原始语句是INSERT
,那么括号会更有意义。它还可以解释为什么数据库连接不处于只读模式。
正如@dan04 解释的那样,INSERT
的括号更有意义。往回想,SELECT
无论如何都不会运行,因为在表中插入 Little Bobby 表会已经删除了表。
实际上,在此示例中,第一个查询(“添加新记录...”)将失败,因为 Students
期望的不仅仅是一列(原始/正确的语句提供了两列) .也就是说,第二列的存在有助于说明为什么需要评论;由于无法更改 Bobby 的名字,因此最好保持原样,仅将这一观察结果作为脚注。
Bobby 的姓氏 - 或者至少是他母亲的姓氏,是 Roberts,来自 Explain XKCD。不过,我不确定纠正它会提高答案的清晰度。【参考方案6】:
它是这样工作的: 假设管理员正在查找学生的记录
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; --
由于用户输入没有经过过滤,上述查询被分为两部分
Select * from student where username='Robert');
DROP TABLE STUDENTS; --
双破折号 (--) 只会注释掉查询的剩余部分。
这很危险,因为它可以使密码身份验证无效(如果存在)
第一个将进行正常搜索。
如果帐户有足够的权限,第二个将删除表学生(通常学校管理员帐户将运行此类查询并具有上述权限)。
【讨论】:
SELECT* FROM sutdents ...
- 你忘了一个“s”。这就是你丢弃的东西。 DROP TABLE STUDENTS;
【参考方案7】:
不,'
在 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');
即。它会提前终止插入语句,执行破解者想要的任何恶意代码,然后注释掉可能存在的任何剩余代码。
嗯,我太慢了,我已经在橙色带中看到了 8 个答案... :-) 似乎是一个热门话题。
【讨论】:
【参考方案8】:正如其他人已经指出的那样,');
关闭了原始语句,然后是第二条语句。大多数框架,包括像 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 注入,尽管如果你能够提供多条语句,你可以做更具破坏性的事情。
【讨论】:
【参考方案9】:单引号是字符串的开始和结束。分号是语句的结尾。因此,如果他们进行这样的选择:
Select *
From Students
Where (Name = '<NameGetsInsertedHere>')
SQL 会变成:
Select *
From Students
Where (Name = 'Robert'); DROP TABLE STUDENTS; --')
-- ^-------------------------------^
在某些系统上,select
会首先运行,然后是 drop
语句!消息是:不要将值嵌入到您的 SQL 中。而是使用参数!
【讨论】:
【参考方案10】:');
结束查询,它不开始评论。然后,它删除了 students 表,并对应该执行的其余查询进行了 cmets。
【讨论】:
【参考方案11】:SQL 中的'
字符用于字符串常量。在这种情况下,它用于结束字符串常量而不是用于注释。
【讨论】:
【参考方案12】:数据库的作者可能做了一个
sql = "SELECT * FROM STUDENTS WHERE (STUDENT_NAME = '" + student_name + "') AND other stuff";
execute(sql);
如果 student_name 是给定的,则使用名称“Robert”进行选择,然后删除表。 “--”部分将给定查询的其余部分更改为注释。
【讨论】:
这是我的第一个想法,但你得到一个尾括号的语法错误,不是吗? 所以最后有一个--,表示剩下的文字是注释,应该忽略。【参考方案13】:假设你天真地写了这样一个学生创建方法:
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 --')
分号结束插入命令并开始另一个; -- cmets 排除了该行的其余部分。执行 DROP TABLE 命令...
这就是为什么绑定参数是一件好事。
【讨论】:
以上是关于"Bobby Tables" XKCD 漫画中的 SQL 注入是如何工作的?的主要内容,如果未能解决你的问题,请参考以下文章
Sqoop "import-all-tables" 无法导入所有表
安装OpenStack ValueError: Tables "migrate_version" have non utf8 co
报错解决 error: "net.bridge.bridge-nf-call-ip6tables" is an unknown key