新秀企业网站系统代码审计学习(复现)

[TOC]

参考资料:

代码审计常见的三种方法(PHP篇)

PHP代码审计初次尝试之新秀企业网站系统

审计思路通过脑图大概总结如下:

image-20200912095322738

了解系统

CMS名称:新秀企业网站系统PHP版

image-20200912095625466

看着界面就知道是用于企业打广告(x)和发布信息,招聘等功能的系统。且存在前台用户登录和后台管理系统。存在搜索功能。

防护策略

IP登录限制 - 猜测伪造IP注入

限制了前台、后台的登录次数限制、注册限制、可能会影响到后面SQL注入漏洞的测试。

image-20200912100248513

可能出现的漏洞:伪造IP进行注入攻击

数据库监控,在注册的地方看看ip 是否被带入了数据库。

image-20200912102325325

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
//获取客户端IP
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 过滤

前台存在留言功能。

image-20200912103732317

提交后,我们登录后台管理员进行查看留言内容。 发现并没有执行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
//获取post
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('<','&#60;',$str);
$str = str_replace('>','&#62;',$str);
$str = str_replace('?','&#63;',$str);
$str = str_replace('%','&#37;',$str);
$str = str_replace(chr(39),'&#39;',$str);
$str = str_replace(chr(34),'&#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
//获取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]:'');
}
}
1
2
3
4
5
6
7
8
9
10
//设置cookie
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 监控,然后再搜索框进行搜索。

image-20200912161945551

如图,123,被带入SQL语句进行查询,单引号闭合。将关键字在整个文件夹中搜索:

image-20200912162117271

这里先rawurldecode 解码,然后带入拼接进入查询。

然后下断点进行分析:

image-20200912165411168

直接上sqlmap。

image-20200912155851842

后台任意文件删除漏洞

/admin/deal.php

image-20200912144227587

此处采用了白名单的形式,只能删除 指定的三个目录下的文件。但是忽略了可以用../来绕过。

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,简单设置一下就可以了。

image-20200912152353172

image-20200912152108244

漏洞演示

后台编辑语言文件设置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

image-20200912173700069

image-20200912173822681

$path 可控的,这里只需要把$path 改为一个.php 后缀的就可以GetShell 了。

image-20200912174519203

image-20200912174743837

审计总结

基本上是照搬照抄别人的思路来搞的,总比躺着玩手机强,也遇到了一些问题,比如Burp Suite 设置 域名和ip 绑定,phpstorm 调试分析,总之,能学到东西就行。学到了思路,下一步就是复现thinkphp 的历史漏洞,和thinkcmf 或者其他php框架的历史漏洞,学习完之后就去找cms进行实战!(其实这个已经搁置了快半年了。

一个不能孜孜不倦,始终处于新知识、新技术学习状态下的安全爱好者,必然会被超越和取代。

Author: m0nk3y
Link: https://hack-for.fun/844d1b07.html
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.