当前位置:WooYun >> 漏洞信息

漏洞概要 关注数(37) 关注此漏洞

缺陷编号: WooYun-2014-54387

漏洞标题: 建站之星Sitestar前台Getshell一枚

相关厂商: 建站之星

漏洞作者: ′雨。认证白帽子

提交时间: 2014-03-23 17:54

公开时间: 2014-06-21 17:55

漏洞类型: 设计缺陷/逻辑错误

危害等级: 高

自评Rank: 20

漏洞状态: 厂商已经确认

漏洞来源: http://www.wooyun.org,如有疑问或需要帮助请联系 help@wooyun.org

Tags标签: 无

11人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2014-03-23: 细节已通知厂商并且等待厂商处理中
2014-03-23: 厂商已经确认,细节仅向厂商公开
2014-03-26: 细节向第三方安全合作伙伴开放(绿盟科技唐朝安全巡航无声信息
2014-05-17: 细节向核心白帽子及相关领域专家公开
2014-05-27: 细节向普通白帽子公开
2014-06-06: 细节向实习白帽子公开
2014-06-21: 细节向公众公开

简要描述:

看sitestar 在某数字公司还是属于一般应用的, 就准备提数字了。
太坑了 然后果断拒绝提交详情。 还是提到乌云来把。

不知道sitestar在乌云是不是属于一般应用的? 狗哥给个回应哈。

Sitestar 前台Getshell。 无需登录。

详细说明:

在官方论坛上下的最新版



在 install/index.php中



code 区域
define('IN_CONTEXT', 1);
include_once('load.php');
?>

包含进来 那再继续看看。



code 区域
$lockfile = ROOT.'/install.lock';
$pattern_db = '/[0-9a-zA-Z]*$/';
if(!preg_match($pattern_db, $db_name)||!preg_match($pattern_db, $db_user)){
echo '1001';exit;
}
if(file_exists($lockfile) && ($_a=='template' || $_a=='setting' || $_a=='check')) {
exit('please delete install.lock!');
}





这里判断了Lock 而且if(file_exists($lockfile) && ($_a=='template' || $_a=='setting' || $_a=='check')



关键这里是一个and 而不是一个or 只要不满足后面的 也就不会退出了。

继续看后面的码。



code 区域
if($_a=='template'){
include P_TPL."/template.php";
}else if($_a=='check'){
include P_TPL."/check.php";
}else if($_a=='setting'){
$default_tpl = ParamHolder::get("default_tpl","jixie-110118-a16");
$_SESSION['default_tpl'] = $default_tpl;
include P_TPL."/setting.php";
}else if($_a=='result'){
$domain = $_SERVER['HTTP_HOST'];
if(isset($_SERVER['SERVER_ADDR'])){
$ip = $_SERVER['SERVER_ADDR'];
}else{
$ip='**.**.**.**';
}
$version = 'sitestar_v2.7_build131012';
$system = preg_replace('/\s/','',PHP_OS);
$vphp = PHP_VERSION;
$vmysql = $_SESSION['vmysql'];
$tpl_name = $_SESSION['default_tpl'];
$http = new Http("http://**.**.**.**/feedback.php?domain=$domain&ip=$ip&version=$version&vphp=$vphp&vmysql=$vmysql&tpl_name=$tpl_name&vos=$system");
$http->get();
include P_TPL."/result.php";

create_file($version);


}else if($_a=='checkconnection'){
$link = @mysql_connect($db_host,$db_user,$db_pwd);
if (!$link) {
echo '1001';
exit;
}
$r = mysql_select_db($db_name,$link);
if(!$r){
echo '1002';
exit;
}
}else if($_a=="create"){
$link = mysql_connect($db_host,$db_user,$db_pwd);
if (!$link) {
echo '1001';
exit;
}
$r = mysql_select_db($db_name,$link);
if(!$r){
echo '1002';
exit;
}

$rtn = create_table($db_name,$db_prefix,INSTALL_ROOT.'/../sql/basic.sql');
if(!empty($rtn)){
echo '1005';
exit;
}
mysql_query("INSERT INTO `".$db_prefix."parameters` (`id`, `key`, `val`) VALUES (NULL, 'DEFAULT_TPL', '".$_SESSION['default_tpl']."')");
//uploadcopy(ROOT."/template/".$_SESSION['default_tpl']."/".$_SESSION['default_tpl']."_2_upload/image",ROOT."/upload/image");
//uploadcopy(ROOT."/template/".$_SESSION['default_tpl']."/".$_SESSION['default_tpl']."_2_upload/flash",ROOT."/upload/flash");
if($demo=='1'){
create_table($db_name,$db_prefix,ROOT."/template/".$_SESSION['default_tpl']."/".$_SESSION['default_tpl'].'_2_sample.sql');
} else {
mysql_query("INSERT INTO `".$db_prefix."static_contents` (`id` ,`title` ,`content` ,`create_time` ,`s_locale` ,`published` ,`for_roles`) VALUES ('1', '', NULL , '', 'zh_CN', '1', '{member}{admin}{guest}');");
mysql_query("INSERT INTO `".$db_prefix."static_contents` (`id` ,`title` ,`content` ,`create_time` ,`s_locale` ,`published` ,`for_roles`) VALUES ('2', '', NULL , '', 'zh_CN', '1', '{member}{admin}{guest}');");
mysql_query("INSERT INTO `".$db_prefix."static_contents` (`id` ,`title` ,`content` ,`create_time` ,`s_locale` ,`published` ,`for_roles`) VALUES ('3', '', NULL , '', 'en', '1', '{member}{admin}{guest}');");
mysql_query("INSERT INTO `".$db_prefix."static_contents` (`id` ,`title` ,`content` ,`create_time` ,`s_locale` ,`published` ,`for_roles`) VALUES ('4', '', NULL , '', 'en', '1', '{member}{admin}{guest}');");
}

echo '1003';
}else if($_a=="createadmin"){

$link = mysql_connect($db_host,$db_user,$db_pwd);
if (!$link) {
echo '1001';
exit;
}
$r = mysql_select_db($db_name,$link);
if(!$r){
echo '1002';
exit;
}
mysql_query("set names utf8");
mysql_select_db($db_name,$link);
$mysql_query = mysql_query("select VERSION()");
$mysql_row = mysql_fetch_row($mysql_query);
$vmysql = $mysql_row[0];
$_SESSION['vmysql'] = $mysql_row[0];
$passwd = sha1($admin_pwd);
$tme = time();
if ($link) {
create_config($db_host1,$db_user,$db_pwd,$db_name,$db_prefix,$db_port);
}
$query = mysql_query("insert into ".$db_prefix."users(login,passwd,email,lastlog_time,rstpwdreq_time,active,s_role) values('$admin_name','$passwd','admin@**.**.**.**','$tme','0','1','{admin}')");
$insert_id = mysql_insert_id();
$query = mysql_query("insert into ".$db_prefix."user_extends(total_saving,total_payment,balance,user_id) values('0.00','0.00','0.00','$insert_id')");
if($query){
echo '1004';
}





可以看到 除开 template setting 和check 还有其他的

来找找哪个可以利用的。

code 区域
}else if($_a=="createadmin"){

$link = mysql_connect($db_host,$db_user,$db_pwd);
if (!$link) {
echo '1001';
exit;
}
$r = mysql_select_db($db_name,$link);
if(!$r){
echo '1002';
exit;
}
mysql_query("set names utf8");
mysql_select_db($db_name,$link);
$mysql_query = mysql_query("select VERSION()");
$mysql_row = mysql_fetch_row($mysql_query);
$vmysql = $mysql_row[0];
$_SESSION['vmysql'] = $mysql_row[0];
$passwd = sha1($admin_pwd);
$tme = time();
if ($link) {
create_config($db_host1,$db_user,$db_pwd,$db_name,$db_prefix,$db_port);
}
$query = mysql_query("insert into ".$db_prefix."users(login,passwd,email,lastlog_time,rstpwdreq_time,active,s_role) values('$admin_name','$passwd','admin@**.**.**.**','$tme','0','1','{admin}')");
$insert_id = mysql_insert_id();
$query = mysql_query("insert into ".$db_prefix."user_extends(total_saving,total_payment,balance,user_id) values('0.00','0.00','0.00','$insert_id')");





我所利用的是这个。 貌似可以直接添加一个管理。 但是不甘心, 看看能不能直接Getshell。



code 区域
$link = mysql_connect($db_host,$db_user,$db_pwd);
if (!$link) {
echo '1001';
exit;
}
$r = mysql_select_db($db_name,$link);
if(!$r){
echo '1002';
exit;
}





首先看这里。 要检测mysql是否能够连接得上, 并且$db_name 得存在这个mysql中。



code 区域
$_a = ParamHolder::get("_a","");
$_m = ParamHolder::get("_m","frontpage");
$db_host1 = ParamHolder::get("db_host","");
$db_user = ParamHolder::get("db_user","");
$db_pwd = ParamHolder::get("db_pwd","");
$db_name = ParamHolder::get("db_name","");
$db_prefix = ParamHolder::get("db_prefix","");
$db_port = ParamHolder::get("db_port","");
$admin_name = ParamHolder::get("admin_name","");
$admin_pwd = ParamHolder::get("admin_pwd","");
$demo = ParamHolder::get("demo","");
$db_host = $db_host1.":".$db_port;





可以看到哪些参数都是可控的。

我们自己搭建一个mysql 可外联就行了。

code 区域
$pattern_db = '/[0-9a-zA-Z]*$/';



code 区域
if(!preg_match($pattern_db, $db_name)||!preg_match($pattern_db, $db_user)){
echo '1001';exit;
}



匹配出除开0-9 a-z A-Z 以外的就退出。



code 区域
$passwd = sha1($admin_pwd);
$tme = time();
if ($link) {
create_config($db_host1,$db_user,$db_pwd,$db_name,$db_prefix,$db_port);
}





这里 跟进去



code 区域
function create_config($host,$user,$pwd,$dnname,$pre,$port){
$str = "";
$str .= "<?php \n";
$str.="if (!defined('IN_CONTEXT')) die('access violation error!');\n";
$str.="class Config {\n";
$str .= "public static \$mysql_ext = 'mysql';\n";
$str .= "public static \$db_host = '$host';\n";
$str .= "public static \$db_user = '$user';\n";
$str .= "public static \$db_pass = '$pwd';\n";
$str .= "public static \$db_name = '$dnname';\n";
$str .= "public static \$port = '$port';\n";
$str .= "public static \$mysqli_charset = 'utf8';\n";
$str .= "public static \$tbl_prefix = '$pre';\n";
$str .= "public static \$cookie_prefix = '".randomStr(6)."_';\n";
$str .= "public static \$enable_db_debug = false;\n";
$str .= "}?>\n";
file_put_contents("../config.php",$str);





可以看到 直接写到一个php文件里了。 这时候 好像一切都ok了。

可是 一切又没有那么的容易。

s1.jpg



access violation error!

是不能含有单引号的。 但是在这里 关键的是 他没有过滤转义符。。



然后就进入了无尽的测试当中。。



code 区域
function create_config($host,$user,$pwd,$dnname,$pre,$port){
$str = "";
$str .= "<?php \n";
$str.="if (!defined('IN_CONTEXT')) die('access violation error!');\n";
$str.="class Config {\n";
$str .= "public static \$mysql_ext = 'mysql';\n";
$str .= "public static \$db_host = '$host';\n";
$str .= "public static \$db_user = '$user';\n";
$str .= "public static \$db_pass = '$pwd';\n";
$str .= "public static \$db_name = '$dnname';\n";
$str .= "public static \$port = '$port';\n";
$str .= "public static \$mysqli_charset = 'utf8';\n";
$str .= "public static \$tbl_prefix = '$pre';\n";
$str .= "public static \$cookie_prefix = '".randomStr(6)."_';\n";
$str .= "public static \$enable_db_debug = false;\n";
$str .= "}?>\n";
file_put_contents("../config.php",$str);



看这个 理论上来说 只有

code 区域
$str .= "public static \$mysqli_charset = 'utf8';\n";
$str .= "public static \$cookie_prefix = '".randomStr(6)."_';\n";
$str .= "public static \$enable_db_debug = false;\n";



这三行不可控, 但是 由于

code 区域
$str .= "public static \$db_host = '$host';\n";
$str .= "public static \$db_user = '$user';\n";
$str .= "public static \$db_pass = '$pwd';\n";
$str .= "public static \$db_name = '$dnname';\n";



这四个 会用来连接 如果连接不上的话 就退出了。 就算不上能随意控制。

code 区域
$str .= "public static \$port = '$port';\n";
$str .= "public static \$mysqli_charset = 'utf8';\n";
$str .= "public static \$tbl_prefix = '$pre';\n";



就剩下了这两个可控。 但是中间还有了个不可控的。

如果两个可控的挨在一起的话 可以这样

public static $port = '\';

public static $tbl_prefix = ';phpinfo();/*';

但是由于中间多了一个不可控的 所以不能直接这样。。

如果可控的两行没挨在一起的话

那么可控的必须要三行了 才能执行了。。 那怎么办呢?

DB_HOST 肯定是不能改的 要不就连不上了。

那就要从 DB_NAME db_user 和 db_pwd 下手了。

code 区域
$pattern_db = '/[0-9a-zA-Z]*$/';
if(!preg_match($pattern_db, $db_name)||!preg_match($pattern_db, $db_user)){
echo '1001';exit;
}



这里的正则 验证了 db_name 和 db_user 但是 这中间连接的是一个or。

那只要让一个匹配不出除开09 az AZ以外的就行了。

那就让db_name匹配不出 因为我测试了db_name 在我创建数据库的时候无法添加符号的。



这样只有从db_user 和 db_pwd 下手了。

在本地的mysql里 建一个含有特殊字符的 账户 和 密码。

让账户为test@localhost\ 密码为;/*

这样类似的就行了。

来测试测试。

漏洞证明:

s5.jpg





首先建立一个账户。 要记得对一些字符转义。



然后 访问

s6.jpg



来看看配置文件。

s7.jpg



直接访问首页。



s8.jpg





成功getshell。

修复方案:

说得很清楚啦。



低调求20.

版权声明:转载请注明来源 ′雨。@乌云


漏洞回应

厂商回应:

危害等级:高

漏洞Rank:15

确认时间:2014-03-23 21:46

厂商回复:

漏洞利用的条件比较严格,按照表述至少还需要知道mysql的相关权限,而这个对于外部客户来说是几乎很难的一个前提,但是鉴于你的严格的逻辑分析和如此清楚的描述,给15分以表谢意,我们会尽快发布安全补丁。

最新状态:

2014-03-27:我们已经发布了安全补丁,请相关用户在后台点击升级就可以,为了让用户有个升级期,漏洞先不提前公开,让大家能够有时间去升级。谢谢白帽子的支持和理解。


漏洞评价:

对本漏洞信息进行评价,以更好的反馈信息的价值,包括信息客观性,内容是否完整以及是否具备学习价值

漏洞评价(共0人评价):
登陆后才能进行评分

评价

  1. 2014-03-23 17:57 | adm1n ( 普通白帽子 | Rank:216 漏洞数:66 | 只是一个渣渣而已。。。)
    2

    给力

  2. 2014-03-23 18:01 | 寂寞的瘦子 ( 普通白帽子 | Rank:242 漏洞数:53 | ☯☯☯☯☯☯☯☯☯☯)
    1

    少年很赞!

  3. 2014-03-23 20:51 | felixk3y ( 普通白帽子 | Rank:523 漏洞数:41 | php python jsp)
    1

    我也提了几个sitestar的,没有通用奖励 应该只有Q币。

  4. 2014-03-23 21:58 | ′ 雨。 认证白帽子 ( 普通白帽子 | Rank:1332 漏洞数:198 | Only Code Never Lie To Me.)
    1

    亲 是不需要本地的mysql密码的。 直接自己搭建一个mysql 开启外联 就可以了。 并不需要本地的mysql密码。

  5. 2014-03-23 22:00 | ′ 雨。 认证白帽子 ( 普通白帽子 | Rank:1332 漏洞数:198 | Only Code Never Lie To Me.)
    2

    @建站之星 你看我测试的时候 都是用的远程的好么。 并不需要知道他原本的mysql密码的。 你们可以具体测试测试。

  6. 2014-03-24 11:54 | evil_webshell ( 路人 | Rank:0 漏洞数:2 | 致力于web层面的安全,热爱黑客技术,正在...)
    3

    少年加油

  7. 2014-03-24 20:00 | ′ 雨。 认证白帽子 ( 普通白帽子 | Rank:1332 漏洞数:198 | Only Code Never Lie To Me.)
    3

    看来 厂商还是没懂我的意思 。 那个连接的时候 并不需要拿到网站mysql的权限。 而是 自己搭建一个Mysql环境, 开启远程连接 然后去连接。 所以 利用着 并不苛刻。 只要这个文件存在 一般就可以利用。

  8. 2014-03-27 10:45 | 建站之星(乌云厂商)
    1

    @′ 雨。 你好,不是我们没有懂你的意思,作为产品维护者和厂商,我们无论任何漏洞信息我们都会第一时间处理和发布补丁,你说的情况我们已经进行了处理和发布了补丁。请大家在后台升级就行。

  9. 2014-03-27 20:17 | 疯子 ( 普通白帽子 | Rank:259 漏洞数:45 | 世人笑我太疯癫,我笑世人看不穿~)
    1

    @建站之星 你又傻X了,自己在回复的时候没说清楚,别人给你解释, 你还说已经修复,呵呵呵呵呵!

  10. 2014-03-28 17:11 | Morker ( 普通白帽子 | Rank:139 漏洞数:24 | 双手是最重要的、、、)
    1

    坐等了

  11. 2014-06-21 19:06 | CplusHua ( 普通白帽子 | Rank:264 漏洞数:36 | 乌云奖金:-1)
    0

    @′ 雨。 厉害,跟当时DEDE一个变量覆盖,覆盖mysql host,外联mysql的差不多~ 思路很好,值得借鉴~ 赞!

  12. 2014-06-21 19:40 | wefgod ( 核心白帽子 | Rank:1825 漏洞数:183 | 力不从心)
    0

    看来厂商并不是真的清楚这漏洞在干什么,虽然我PHP很菜,但是我也大概明白了

  13. 2014-06-24 10:22 | char ( 路人 | Rank:13 漏洞数:3 | 中国平安,不只保险这么简单。)
    1

    最开始厂商没看懂,后来发现你们都在评论后,仔细一看,我擦,竟然是连接攻击者自己的数据库就可以利用,后来横竖不管怎么样,必须得狡辩一下就闪人,面子还是要的。

  14. 2014-06-24 10:23 | ′ 雨。 认证白帽子 ( 普通白帽子 | Rank:1332 漏洞数:198 | Only Code Never Lie To Me.)
    0

    @char 哈哈 不过还是正确修复了的

  15. 2014-06-24 10:27 | char ( 路人 | Rank:13 漏洞数:3 | 中国平安,不只保险这么简单。)
    0

    @′ 雨。 修复是一回事,狡辩是另一回事。哈哈

  16. 2015-03-22 19:09 | 拨开乌云 ( 路人 | Rank:6 漏洞数:4 )
    0

    经典啊~~~

  17. 2016-01-22 22:04 | 李小弟 ( 路人 | Rank:1 漏洞数:1 | \-)(-/)
    0

    尽管过了很久,但是楼主测试出漏洞纯粹是毅力加运气。为什么呢?因为这句if(!preg_match($pattern_db, $db_name)||!preg_match($pattern_db, $db_user))不存在逻辑上问题,!ture || !true 才能让这句绕过去。真正问题的是正则表达/[0-9a-zA-Z]*$/。。问题就在于那个*字符,这个字符是表示{0,}结尾处的字符匹配0或0次以上,表达式preg_match('/[0-9a-zA-Z]*$/', '@@aaa@@@')返回的永远是1,匹配出来的是空字符。。。。看到你那or解释和程序逻辑总觉得那里不对,我也是刚注意到的。。学习共勉

  18. 2016-01-24 22:15 | ′雨。 认证白帽子 ( 普通白帽子 | Rank:1332 漏洞数:198 | Only Code Never Lie To Me.)
    0

    @李小弟 好像没说那里有啥大错呀。if(file_exists($lockfile) && ($_a=='template' || $_a=='setting' || $_a=='check')) { exit('please delete install.lock!'); } 说的这里 , 只要$_a 不为后面三个的其中的一个就可以继续让安装文件走下去。

登录后才能发表评论,请先 登录