提问人:Blankman 提问时间:12/2/2008 最后编辑:FloernBlankman 更新时间:7/12/2022 访问量:302600
“Bobby Tables”XKCD 漫画中的 SQL 注入是如何工作的?
How does the SQL injection from the "Bobby Tables" XKCD comic work?
问:
看看:
(来源: https://xkcd.com/327/)
这个 SQL 有什么作用:
Robert'); DROP TABLE STUDENTS; --
我知道两者并且用于评论,但是这个词不是也被评论了吗,因为它是同一行的一部分?'
--
DROP
答:
它删除了 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 的精明评论再次编辑
评论
INSERT
INSERT
SELECT
Students
查询结束,不开始评论。然后,它删除 students 表并注释应该执行的查询的其余部分。');
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 是:
INSERT INTO Students 值 ( 'Robert Tables` )
但是聪明的用户可以提供他们想要的任何东西:
INSERT INTO Students 值 ( 'Robert'); DROP TABLE Students; --' )
你得到的是:
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');
(例如)添加新学生,其中变量内容直接从 HTML 表单中取出,无需检查格式或转义特殊字符。$xxx
因此,如果包含数据库,程序将直接在数据库上执行以下请求:$firstName
Robert'); DROP TABLE students; --
INSERT INTO 'students' ('first_name', 'last_name') VALUES ('Robert'); DROP TABLE students; --', 'XKCD');
即。它将提前终止 INSERT 语句,执行破解者想要的任何恶意代码,然后注释掉可能存在的任何剩余代码。
嗯,我太慢了,我已经在橙色带子里看到了 8 个答案...... :-)似乎是一个热门话题。
单引号是字符串的开头和结尾。分号是语句的结尾。因此,如果他们像这样进行选择:
Select *
From Students
Where (Name = '<NameGetsInsertedHere>')
SQL 将变为:
Select *
From Students
Where (Name = 'Robert'); DROP TABLE STUDENTS; --')
-- ^-------------------------------^
在某些系统上,会先运行,然后是语句!消息是:不要将值嵌入到你的 SQL 中。请改用参数!select
drop
正如其他人已经指出的那样,关闭原始语句,然后是第二个语句。大多数框架,包括 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 字符串将如下所示:peter
secret
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 注入,尽管如果您能够提供多个语句,则可以执行更具破坏性的事情。
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 注入变得不可能。
评论
这是它的工作原理: 假设管理员正在查找学生的记录
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; --
双破折号 (--) 将只注释掉查询的剩余部分。
这很危险,因为它可以使密码身份验证无效(如果存在)
第一个将进行正常搜索。
如果该帐户具有足够的权限,则第二个帐户将删除表学生(通常,学校管理员帐户将运行此类查询,并具有上面讨论的权限)。
评论
SELECT* FROM sutdents ...
- 你忘记了“s”。这就是你掉落的东西。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 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! :) *************/
...
评论
'