[TOC]
参考资料:
代码审计常见的三种方法(PHP篇)
PHP代码审计初次尝试之新秀企业网站系统
审计思路通过脑图大概总结如下:
了解系统
CMS名称:新秀企业网站系统PHP版
看着界面就知道是用于企业打广告(x)和发布信息,招聘等功能的系统。且存在前台用户登录和后台管理系统。存在搜索功能。
防护策略
IP登录限制 - 猜测伪造IP注入
限制了前台、后台的登录次数限制、注册限制、可能会影响到后面SQL注入漏洞的测试。
可能出现的漏洞:伪造IP进行注入攻击
数据库监控,在注册的地方看看ip
是否被带入了数据库。
1
| select * from php_shisiusafe where saf_ip = '127.0.0.1' and saf_action = 'register'
|
全局定位到获取用户iP的代码部分:
phpstorm 搜索 获取IP,即可。
include/function.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function get_ip() { if(getenv('HTTP_CLIENT_IP') && strcasecmp(getenv('HTTP_CLIENT_IP'),'unknown')) { $ip = getenv('HTTP_CLIENT_IP'); }elseif(getenv('HTTP_X_FORWARDED_FOR') && strcasecmp(getenv('HTTP_X_FORWARDED_FOR'),'unknown')){ $ip = getenv('HTTP_X_FORWARDED_FOR'); }elseif(getenv('REMOTE_ADDR') && strcasecmp(getenv('REMOTE_ADDR'),'unknown')){ $ip = getenv('REMOTE_ADDR'); }elseif(isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'],'unknown')){ $ip = $_SERVER['REMOTE_ADDR']; }else{ $ip = '0.0.0.0'; } if(!is_numeric(str_replace('.','',$ip))) { $ip = '0.0.0.0'; } return $ip; }
|
可以发现,当ip 除去.
后,如果不是纯数字,那么就设置为 0.0.0.0 。因此通过伪造IP进行注入是行不通了。
XSS 过滤
前台存在留言功能。
提交后,我们登录后台管理员进行查看留言内容。 发现并没有执行js代码。
index/module/info_main.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| 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
函数进行执行,跟进该函数。
1 2 3 4 5
| function post($val,$filter = 'strict') { return $filter(isset($_POST[$val])?$_POST[$val]:''); }
|
通过了strict
条件的过滤函数,找到这个的定义处。
include/function.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function strict($str) { if(S_MAGIC_QUOTES_GPC) { $str = stripslashes($str); } $str = str_replace('<','<',$str); $str = str_replace('>','>',$str); $str = str_replace('?','?',$str); $str = str_replace('%','%',$str); $str = str_replace(chr(39),''',$str); $str = str_replace(chr(34),'"',$str); $str = str_replace(chr(13).chr(10),'<br />',$str); return $str; }
|
可以看到html 的闭合标签被转义了,所以没法XSS
CSRF
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| 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); $obj->set_where(''); $obj->set_where('use_id = '.$global['user_id']); $obj->edit(); $info_text = $lang['over']; }else{ $info_text = $lang['old_pwd_error']; } } $smarty->assign('info_text',$info_text); $smarty->assign('link_text',$lang['go_back']); $smarty->assign('link_href',url(array('entrance'=>$global['entrance'],'channel'=>'user','mod'=>'profile'))); }
|
CSRF 修改用户密码,需要旧密码,行不通。
可控变量过滤
session 过滤
1 2 3 4 5 6 7 8 9 10
| 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 过滤
1 2 3 4 5 6 7 8 9 10
| function set_cookie($name,$value,$filter = 'strict',$expire = 0) { if($expire == 0) { setcookie($name,$filter($value)); }else{ setcookie($name,$filter($value),$expire); } }
|
admin 登录
admin/module/info_main.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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 = '返回重新登录'; }else{ ……
|
user 登录
index/module/info_main.php
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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'); if(strlen($username) > 30){$username = substr($username,30);} if(strlen($password) > 30){$password = substr($password,30);} if($username == '' or $password == '')
|
可以看到,基本都经过了带有过滤的函数处理。所以像,SQL注入和XSS 这种需要构造特殊符号的漏洞几乎很难了
漏洞分析
基于功能点去测,先高危后低危。后台一般防御较弱,从后台突破较容易。而后台存在的功能有:图片、文件、模板管理、删除、留言审核,等其他功能。
前台搜索框SQL 注入
开启MySQL 监控,然后再搜索框进行搜索。
如图,123,被带入SQL语句进行查询,单引号闭合。将关键字在整个文件夹中搜索:
这里先rawurldecode 解码,然后带入拼接进入查询。
然后下断点进行分析:
直接上sqlmap。
后台任意文件删除漏洞
/admin/deal.php
此处采用了白名单的形式,只能删除 指定的三个目录下的文件。但是忽略了可以用../
来绕过。
1 2 3 4
| if(substr($path,0,strlen($dir[$i])) == $dir[$i]) { $flag = true; }
|
substr 从第$path
的第一个字母开始往后判断,截取path前半部分长度和白名单是否相等,即是否是白名单里的那几个目录,是,然后unlink
删除掉。
成功删除文件时,返回1。
这里我遇到了一个问题,就是这个域名是通过MAMP修改的本地HOSTS文件解析的,然后找到了一篇文章,https://www.jianshu.com/p/3018b2697bb0,简单设置一下就可以了。
后台编辑语言文件设置GetShell
成功编辑后,回显 编辑语言包成功。那么在整个文件中搜索即可定位到代码。
**/admin/module/file/deal.php**
1 2 3 4 5 6 7 8 9 10
| 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)))); }
|
在该函数中,经过post()
函数过滤,但是对于$lang_text
的过滤规则是no_filter
,跟进查看一下该规则。
/include/function.php,104行。
1 2 3 4 5 6 7 8 9
| function no_filter($str) { if(S_MAGIC_QUOTES_GPC) { $str = stripslashes($str); } return $str; }
|
这里没有过滤 $lang_text
就通过file_put_contents
写入文件,那么这里就可以写WebShell。
同样的,下断点来调试分析。
step into ,慢慢点,可以看到这里没有任何过滤,写入webshell
$path
可控的,这里只需要把$path
改为一个.php
后缀的就可以GetShell 了。
审计总结
基本上是照搬照抄别人的思路来搞的,总比躺着玩手机强,也遇到了一些问题,比如Burp Suite 设置 域名和ip 绑定,phpstorm 调试分析,总之,能学到东西就行。学到了思路,下一步就是复现thinkphp 的历史漏洞,和thinkcmf 或者其他php框架的历史漏洞,学习完之后就去找cms进行实战!(其实这个已经搁置了快半年了。
一个不能孜孜不倦,始终处于新知识、新技术学习状态下的安全爱好者,必然会被超越和取代。