最近一直忙着实习面试,在准备面试的过程确实也补充了很多自己以前的知识盲区,虽然有点考前恶补的意思,但能学到知识总归是好的。当然,面试的结果也还算满意,虽然还没有拿到自己心中T0的大厂offer,但也获得了很多其他乙方包括某个大厂甲方的实习offer。师哥们也陆续去实习了,老师这边压着比较多的项目正在推进,大都是渗透方向的,包括这个月的业务,所以趁着实习面试的尾声,好好的干一波嘿嘿。
一、Result
这个月的业务挖洞成果对自己来说还是很满意的,除了漏洞数量外,漏洞质量也比较高。具体就不多说了,言归正传,在本月的业务渗透测试里发现一个很有趣的sql注入,也是自己之前的一个盲区——order by排序注入,一直想着在实战中接触这种类型的注入场景,还真就来了。
二、Process
话不多说,上接口:
https://xxxx.com/xxx/xxx/xxx/getAreaSpotPageList?&pageindex=1&pagesize=20&passAreaId=2&orderby=pass_area_spot_id+desc&userId=1181163&hotelId=5&brandId=5
这是一个比较正常的get类型的获取数据接口,但orderby参数比较惹眼,因为参数的值为pass_area_spot_id+desc,desc表示的是sql中的降序排序,却以orderby参数值的方式输入,所以就对该参数进行了测试:
Step 1:
令orderby=pass_area_spot_id+desc,返回数据如下:
Step 2:
令orderby=pass_area_spot_id+asc,返回数据如下:
(解释一下,两种情况下返回内容不一致)
Step 3:
这里其实大致可以判断存在排序注入的可能,但还是有歧义,需要进一步进行判断,这里采用基于时间的盲注辅助判断:
Payload1:pass_area_spot_id+desc,(select*from(select+sleep(3)union/**/select+1)a)
返回如下:
该请求在5s多时得到响应。
Payload2:pass_area_spot_id+desc,(select*from(select+sleep(6)union/**/select+1)a)
返回如下:
该请求在8s多时得到响应。
在不考虑测试环境本身的网络状况,上述测试过程可以证明漏洞的存在。
Step 4:
Time-based还是不太方便,并且时间较长。所以更换注入手法,直观输出注入结果:
Payload:pass_area_spot_id+desc,if(ascii(substr(database(),? ,1))=?,1,(select%201%20from%20information_schema.tables))
通过布尔类型的盲注,利用上述payload进行fuzz(省略通过二分法判断当前数据库长度的步骤):
查找对应ascii所代表的字符,获得当前数据库名:xxxxxxxx,测试结束。
三、Review
在整个测试阶段里,虽然注入成功,但是也引发了自己的两个问题,这两个问题也是自己在面试的过程中所碰到的。
Q1、为什么预编译不太好防order by注入(就mysql数据库来谈)?
A1:
这个问题不管在面试的过程中还是个人的平时积累过程中都能够折射出个人的学习深度,很遗憾,我并没有在面试的过程中很好的回答这个问题,其实并不是不知道这个问题的答案,而是不太好组织语言去系统性的带扩展的回答它,并且在之前的挖洞经历里没有对应的场景支撑,所以还是比较遗憾吧,另一方面更说明自己的知识体系太弱,表面徘徊较多。
言归正传,为什么预编译不太好防order by注入?这个问题等于是在问为什么在预编译中order by后不能参数化?一般的sql执行片段代码(预编译方式)是这样的:
Connection conn = DBConnect.getConnection();
String sql = " SELECT xxx FROM xxx WHERE xxx = ? ";
ps = conn.prepareStatement(sql);
ps.setString(1, xxx); //或者ps.setInt(1, username);
rs = ps.executeQuery();
当我们输入xxx=abc时,预编译的代码会自动给abc加上引号,变成'abc',sql语句也变成了:
String sql = " SELECT xxx FROM xxx WHERE xxx = 'abc' ";
这样可以有效的防止用户的输入直接拼接在sql语句之后,譬如用户输入xxx=' or '1'='1时,实际上的sql语句却为:
String sql = " SELECT xxx FROM xxx WHERE xxx = '' or '1'='1' ";
无法达到攻击的效果。说回order by,order by的用法一般为order by [字段名] [desc/asc],如果对order by之后的输入进行参数化,那么sql语句将会变成这样:
String sql = " SELECT xxx FROM xxx WHERE xxx ='xxx' order by 'xxx' "
这样会造成sql语法错误,因为此时'xxx'是字符串而不是字段名。
那么为什么预编译的函数在参数化时非要把输入加上引号呢?如果没有引号不就可以防御order by注入了吗?是的,但确实没有不加引号的预编译的方法哈哈。
引伸来说,这样的sql注入不仅仅发生在order by处,任何需要字符串且不能够加引号的地方都有可能发生类似的注入,因为不能参数化的位置,不管怎么拼接,最终都是和使用“+”号拼接字符串的功效一样。
那么又引出了新的问题,既然预编译防不了order by,那么该怎么样防?答案是采用白名单的方式,因为order by之后跟的字段名肯定是有限的并且是数据库中已经存在的字段,只要对这些有限字段设置白名单,任何不在白名单内的数据统一报错,那么就可以解决啦。
Q2、面对时间较长的Time-based或者Boolean-Base,除了用工具自动化跑结果外,有没有其他较好的方式?
A2
答案是利用Dnslog,当时面试的时候比较紧张,一直想着payload的改进和工具自动化,把这个给忘了,其实也跟平时使用这个技巧不多的原因有关,所以认了。。不过在面试过后确实对Dnslog又强化了一波,的确很好用。直接上链接把->Dnslog-<,这个博主关于Dnslog在各个场景下的使用技巧总结的十分详细了。
四、Re-review
参与面试的过程其实是对自己之前所积累的技术体系的一种测试,更重要的对我来说收获最多是了解圈内的大牛们都在关注着哪些他们感兴趣的方向和技术细节,在丢人的同时的确获得了很多自己想要的信息哈哈。也发现了自己在hack的过程中深度的确不够,容易满足于眼前的成果,挖洞是一方面,学习知识也是。因为hack的过程永远比挖到漏洞更重要。
五、To-Do
1、接下来的时间里继续靶场的练习,回顾之前浮躁状态下所忽略的技术细节,直到hack清楚为止。
2、思考自己的知识框架和技术框架,乘早跳出渗透的圈子,接触真正的企业体系安全建设。
3、培养好奇心,保持好奇心。