How does the SQL injection from the “Bobby Tables” XKCD comic work?
只是看看:
(来源:https://xkcd.com/327/)
此SQL的作用是:
1 | Robert'); DROP TABLE STUDENTS; -- |
我知道
它把学生的桌子掉了。
学校程序中的原始代码可能看起来像
1 | q ="INSERT INTO Students VALUES ('" + FNMName.Text +"', '" + LName.Text +"')"; |
这是将文本输入添加到查询中的幼稚方法,并且非常糟糕,正如您将看到的那样。
在第一个名称、中间名文本框fnmname.text(即
1 | INSERT INTO Students VALUES ('Robert'); DROP TABLE Students; --', 'Derper') |
它用简单的英语大致翻译为两个查询:
Add a new record to the Students table with a Name value of 'Robert'
和
Delete the Students table
第二个查询之后的所有内容都标记为注释:
学生姓名中的
根据Dan04的机敏评论重新编辑
假设这个名字是在一个变量中使用的,
1 | INSERT INTO Students VALUES ( '$Name' ) |
代码错误地将用户提供的任何内容作为变量。您希望SQL是:
INSERT INTO Students VALUES ( 'Robert Tables` )
但是聪明的用户可以提供他们想要的任何东西:
INSERT INTO Students VALUES ( 'Robert'); DROP TABLE Students; --' )
你得到的是:
1 | INSERT INTO Students VALUES ( 'Robert' ); DROP TABLE STUDENTS; --' ) |
正如其他人已经指出的那样,
但是,您可以通过SQL注入操作现有的SQL语句,而不必添加第二个语句。假设您有一个登录系统,它用这个简单的选择来检查用户名和密码:
1 2 | $query="SELECT * FROM users WHERE username='" . $_REQUEST['user'] ."' and (password='".$_REQUEST['pass']."')"; $result=mysql_query($query); |
如果提供
1 | SELECT * FROM users WHERE username='peter' and (password='secret') |
一切都很好。现在假设您提供这个字符串作为密码:
1 | ' OR '1'='1 |
那么得到的SQL字符串是:
1 | SELECT * FROM users WHERE username='peter' and (password='' OR '1'='1') |
这将使您能够在不知道密码的情况下登录到任何帐户。因此,您不需要能够使用两个语句来使用SQL注入,尽管如果能够提供多个语句,您可以做更具破坏性的事情。
不,
妈妈认为数据库程序员提出的请求看起来像:
1 | INSERT INTO 'students' ('first_name', 'last_name') VALUES ('$firstName', '$lastName'); |
(例如)添加新的student,其中
因此,如果
1 | INSERT INTO 'students' ('first_name', 'last_name') VALUES ('Robert'); DROP TABLE students; --', 'XKCD'); |
也就是说,它将提前终止insert语句,执行cracker想要的任何恶意代码,然后注释掉可能存在的剩余代码。
嗯,我太慢了,在橙色的带子里我已经看到8个答案了…:-)这似乎是一个热门话题。
DR
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | -- 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; ^ |
这将删除学生表。
(此答案中的所有代码示例都在PostgreSQL 9.1.2数据库服务器上运行。)
为了清楚地说明发生了什么,让我们用一个只包含名称字段的简单表来尝试这个方法,并添加一行:
1 2 3 4 5 | 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向表中插入数据:
1 | INSERT INTO students VALUES ('foobar'); |
将
1 2 3 | -- Input: Nancy school=> INSERT INTO students VALUES ('Nancy'); INSERT 0 1 |
当我们查询表时,我们得到:
1 2 3 4 5 6 | school=> SELECT * FROM students; name ------- John Nancy (2 rows) |
当我们把小鲍比的名字放进桌子里会发生什么?
1 2 3 4 | -- Input: Robert'); DROP TABLE students; -- school=> INSERT INTO students VALUES ('Robert'); DROP TABLE students; --'); INSERT 0 1 DROP TABLE |
这里的SQL注入是学生名称终止语句并包含一个单独的
需要注意的是,在
结果如何?
1 2 3 4 | school=> SELECT * FROM students; ERROR: relation"students" does not exist LINE 1: SELECT * FROM students; ^ |
SQL注入是与操作系统或应用程序中的远程任意代码执行漏洞等效的数据库。成功的SQL注入攻击的潜在影响不可低估——根据数据库系统和应用程序配置,攻击者可以使用它造成数据丢失(在本例中),获得对数据的未授权访问,甚至在主机本身上执行任意代码。
正如XKCD Comic所指出的,防止SQL注入攻击的一种方法是清理数据库输入,例如通过转义特殊字符,使它们无法修改底层SQL命令,因此无法导致执行任意SQL代码。如果使用参数化查询,例如在ADO.NET中使用
但是,在应用程序级别清理输入可能不会停止更高级的SQL注入技术。例如,有一些方法可以绕过
假设你天真地写了这样的学生创造方法:
1 2 3 | void createStudent(String name) { database.execute("INSERT INTO students (name) VALUES ('" + name +"')"); } |
有人输入了
在数据库上运行的是此查询:
1 | INSERT INTO students (name) VALUES ('Robert'); DROP TABLE STUDENTS --') |
分号结束insert命令并启动另一个命令;—注释行的其余部分。执行drop table命令…
这就是为什么绑定参数是一件好事。
单引号是字符串的开始和结束。分号是语句的结尾。因此,如果他们这样选择:
1 2 3 | Select * From Students Where (Name = '<NameGetsInsertedHere>') |
SQL将变成:
1 2 3 4 | Select * From Students Where (Name = 'Robert'); DROP TABLE STUDENTS; --') -- ^-------------------------------^ |
在某些系统上,
在这种情况下,'不是注释字符。它用于分隔字符串文本。这位喜剧艺术家正指望着这样一个想法,那就是有关的学校在某个地方有一个动态的SQL,看起来像这样:
1 | $sql ="INSERT INTO `Students` (FirstName, LastName) VALUES ('" . $fname ."', '" . $lname ."')"; |
所以现在,'字符在程序员期望它之前结束了字符串文字。结合;字符结束语句,攻击者现在可以添加他们想要的任何SQL。结尾的--comment是为了确保原始语句中的剩余SQL不会阻止在服务器上编译查询。
fwiw,我也认为漫画有一个重要的细节是错误的:如果你正在考虑清理你的数据库输入,正如漫画所暗示的那样,你仍然在做错误的事情。相反,您应该考虑隔离数据库输入,正确的方法是通过参数化查询。
数据库的作者可能做了一个
1 2 | sql ="SELECT * FROM STUDENTS WHERE (STUDENT_NAME = '" + student_name +"') AND other stuff"; execute(sql); |
如果给定的是学生名,则选择名为"robert",然后删除表。"-"部分将给定查询的其余部分更改为注释。
SQL中的
这就是它的工作原理:假设管理员正在查找学生的记录
1 | Robert'); DROP TABLE STUDENTS; -- |
由于管理帐户具有很高的权限,因此可以从此帐户中删除表。
从请求中检索用户名的代码是
现在,查询应该是这样的(搜索学生表)
1 2 3 | String query="Select * from student where username='"+student_name+"'"; statement.executeQuery(query); //Rest of the code follows |
结果查询变为
1 | Select * from student where username='Robert'); DROP TABLE STUDENTS; -- |
由于用户输入没有被清除,所以上面的查询被操作为两部分
1 2 3 | Select * from student where username='Robert'); DROP TABLE STUDENTS; -- |
双破折号(--)只对查询的其余部分进行注释。
这是危险的,因为它可能会使密码验证无效(如果存在的话)
第一个将进行常规搜索。
如果帐户有足够的权限,第二个帐户将删除表student(通常,学校管理帐户将运行此类查询,并将具有上述权限)。