最近在测试我司的某个业务,发现了多处XSS,并且在SRC上被外界白帽子爆出很多处反射型XSS与存储型XSS,所以根据现有的常用DZ版本进行了一次分析,先引带一下以前wooyun上某人的漏洞,下面是截取的某位总结的Discuz给二次开发所挖的4大坑:

image

好吧,废话不多说,下面是回顾以前所分析的DiscuzX系列的存储型XSS:

image

首先发表帖子,在帖子内容里面提交payload:

[align="onmouseover="alert(1)]

image

发表帖子后在内容里面发现双引号被实例化:

image

下面来看一下后台实例化的代码:

/template/default/forum/viewthread_node_body.htm

image

看来在后端已经做了过滤了(对DZ不太熟,在后端咋定义$post[message]的html实例化并未找到)

当管理员或版主去编辑该帖子的时候可直接触发XSS:

image

image

到这里咱们的alert被放到了div标签里面触发了。。。。

再回溯一下编辑用户回复或帖子的触发过程:

image

这里主要是获取text的内容,看看$()函数的定义:

image

跟踪到common.js:

image

这里document.getElementById是原生的javascript。。。

这里的id="e_textarea",代表一个对象,对象的内容就是评论的内容

image

由于原生javascript的原因,会将对应id的值的内容将双引号渲染回来。。。

接着就是:

var wysiwyg = (BROWSER.ie || BROWSER.firefox || (BROWSER.opera >= 9)) && parseInt('1') == 1 ? 1 : 0;

给wysiwyg赋值判断浏览器,然后开始渲染编辑框:

image

估计这里最重要的函数就是bbcode2html了,跟踪到static/js/bbcode.js

image

这里我们的payload是:

[align="onmouseover="alert(1)]

那么align=这段函数应该就被执行替换了,最后的payload str的值为:

<div align="xxo" onmouseover="alert(1)">xx</div>

/static/js/editor.js

里面定义了neweditor函数:

image

调用了writeEditorContents函数,顾名思义肯定是关键的数据写入函数(数据从数据库取出后的处理):

function writeEditorContents(text) {
    if(wysiwyg) {
        if(text == '' && (BROWSER.firefox || BROWSER.opera)) {
            text = '<p></p>';
        }
        if(initialized && !(BROWSER.firefox && BROWSER.firefox >= '3' || BROWSER.opera)) {
            editdoc.body.innerHTML = text;
        } else {
            text = '<!DOCTYPE html PUBLIC "-/' + '/W3C/' + '/DTD XHTML 1.0 Transitional/' + '/EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">' +
                '<html><head id="editorheader"><meta http-equiv="Content-Type" content="text/html; charset=' + charset + '" />' +
                (BROWSER.ie && BROWSER.ie > 7 ? '<meta http-equiv="X-UA-Compatible" content="IE=7" />' : '' ) +
                '<link rel="stylesheet" type="text/css" href="data/cache/style_' + STYLEID + '_wysiwyg.css?' + VERHASH + '" />' +
                (BROWSER.ie ? '<script>window.onerror = function() { return true; }</script>' : '') +
                '</head><body>' + text + '</body></html>';
            editdoc.designMode = allowhtml ? 'on' : 'off';
            editdoc = editwin.document;
            editdoc.open('text/html', 'replace');
            editdoc.write(text);
            editdoc.close();
            if(!BROWSER.ie) {
                var scriptNode = document.createElement("script");
                scriptNode.type = "text/javascript";
                scriptNode.text = 'window.onerror = function() { return true; }';
                editdoc.getElementById('editorheader').appendChild(scriptNode);
            }
            editdoc.body.contentEditable = true;
            editdoc.body.spellcheck = false;
            initialized = true;
            if(BROWSER.safari) {
                editdoc.onclick = safariSel;
            }
        }
        if(BROWSER.ie && BROWSER.ie <= 8) {
            checkpostbg = /<style[^>]+name="editorpostbg"[^>]*>body{background-image:url\("([^\[\<\r\n;'\"\?\(\)]+?)"\);}<\/style>/ig;
            var matches = checkpostbg.exec(text);
            if(matches != null) {
                editdoc.body.innerHTML += '<style type="text/css" name="editorpostbg">body{background-image:url("'+matches[1]+'");}</style>';
            }
        }
    } else {
        textobj.value = text;
    }

    setEditorStyle();

}

直接带入text的内容造成了XSS。。。。

0day? 还是 开发失误?

最新版的DiscuzX3.2存储型XSS

image

问题还是在前端的js框架里面,这次问题出在discuz.html:

/template/default/forum/discuz.html

这是Dz的首页输出模板,漏洞在四宫格输出标题的地方:

<div class="newimgbox">
    <h4><span class="tit_newimg"></span>{lang latest_images}</h4>
    <div class="module cl slidebox_grid" style="width:218px">
        <script type="text/javascript">
        var slideSpeed = 5000;
        var slideImgsize = [218,200];
        var slideBorderColor = '{$_G['style']['specialborder']}';
        var slideBgColor = '{$_G['style']['commonbg']}';
        var slideImgs = new Array();
        var slideImgLinks = new Array();
        var slideImgTexts = new Array();
        var slideSwitchColor = '{$_G['style']['tabletext']}';
        var slideSwitchbgColor = '{$_G['style']['commonbg']}';
        var slideSwitchHiColor = '{$_G['style']['specialborder']}';
        {eval $k = 1;}
        <!--{loop $grids['slide'] $stid $svalue}-->
            slideImgs[<!--{echo $k}-->] = '$svalue[image]';
            slideImgLinks[<!--{echo $k}-->] = '{$svalue[url]}';
            slideImgTexts[<!--{echo $k}-->] = '$svalue[subject]';
            {eval $k++;}
        <!--{/loop}-->
        </script>
        <script language="javascript" type="text/javascript" src="{$_G[setting][jspath]}forum_slide.js?{VERHASH}"></script>
    </div>
</div>

问题在echo的地方,直接输出了:

slideImgs[<!--{echo $k}-->] = '$svalue[image]';
slideImgLinks[<!--{echo $k}-->] = '{$svalue[url]}';
slideImgTexts[<!--{echo $k}-->] = '$svalue[subject]';

这里的标题是我们唯一可控的,前提是我们要开启首页四宫格的功能,庆幸的是一般Discuz都会在首页开启四宫格,如图所示:

image

在任意模块发正常的标题显示是这样的:

image

很明显是插在<script></script>标签内的,如果能通过单引号闭合来另外构造我们自己的XSS Payload那就OK了,看看js代码里面有木有引入过滤函数:

image

image

很明显没有单引号的过滤。。。。

那在发帖的时候在标题插入payload:

';alert(document.cookie);//

image

提交后后台更新缓存后首页弹出cookie:

image

看一下payload插入位置:

image

同样的问题还出在主题推荐的地方:

/template/default/forum/recommend.htm

<div class="cl" style="width: {$_G[forum][modrecommend][imagewidth]}px; margin: 0 auto; float:left;">
        <script type="text/javascript">
        var slideSpeed = 5000;
        var slideImgsize = [{$_G[forum][modrecommend][imagewidth]},{$_G['forum'][modrecommend][imageheight]}];
        var slideBorderColor = '{$_G['style']['specialborder']}';
        var slideBgColor = '{$_G['style']['commonbg']}';
        var slideImgs = new Array();
        var slideImgLinks = new Array();
        var slideImgTexts = new Array();
        var slideSwitchColor = '{$_G['style']['tabletext']}';
        var slideSwitchbgColor = '{$_G['style']['commonbg']}';
        var slideSwitchHiColor = '{$_G['style']['specialborder']}';
        <!--{loop $_G['forum']['recommendlist']['images'] $k $imginfo}-->
            slideImgs[<!--{echo $k+1}-->] = '$imginfo[filename]';
            slideImgLinks[<!--{echo $k+1}-->] = 'forum.php?mod=viewthread&tid=$imginfo[tid]';
            slideImgTexts[<!--{echo $k+1}-->] = '$imginfo[subject]';
        <!--{/loop}-->
        </script>
        <script language="javascript" type="text/javascript" src="{$_G[setting][jspath]}forum_slide.js?{VERHASH}"></script>
    </div>

修复方案:

slideImgTexts[<!--{echo $k+1}-->] 改为 '<!--{echo htmlspecialchars($imginfo[subject],ENT_QUOTES)}-->'

这也是官网出patch前的临时修复方案啦。。

htmlspecialchars(str,ENT_QUOTES)

后面带的ENT_QUOTES表示包括单引号在内的特殊字符都会被html实例化。。

11月27日

Discuz反射型DOM XSS:

问题出现在/static/image/admincp/getcolor.htm文件中:

<script type="text/javascript">
varname = location.search.substr(1);
var varnames = varname.split('|');
varname = varnames[0];
varnamev = varnames[1];
fun = varnames[2] || '';
var colors = '\
000000#000000#000033#000066#000099#0000CC#0000FF#003300#003333#003366#003399#0033CC#0033FF#006600#006633#006666#006699#0066CC#0066FF#\
333333#009900#009933#009966#009999#0099CC#0099FF#00CC00#00CC33#00CC66#00CC99#00CCCC#00CCFF#00FF00#00FF33#00FF66#00FF99#00FFCC#00FFFF#\
666666#330000#330033#330066#330099#3300CC#3300FF#333300#333333#333366#333399#3333CC#3333FF#336600#336633#336666#336699#3366CC#3366FF#\
999999#339900#339933#339966#339999#3399CC#3399FF#33CC00#33CC33#33CC66#33CC99#33CCCC#33CCFF#33FF00#33FF33#33FF66#33FF99#33FFCC#33FFFF#\
CCCCCC#660000#660033#660066#660099#6600CC#6600FF#663300#663333#663366#663399#6633CC#6633FF#666600#666633#666666#666699#6666CC#6666FF#\
FFFFFF#669900#669933#669966#669999#6699CC#6699FF#66CC00#66CC33#66CC66#66CC99#66CCCC#66CCFF#66FF00#66FF33#66FF66#66FF99#66FFCC#66FFFF#\
FF0000#990000#990033#990066#990099#9900CC#9900FF#993300#993333#993366#993399#9933CC#9933FF#996600#996633#996666#996699#9966CC#9966FF#\
00FF00#999900#999933#999966#999999#9999CC#9999FF#99CC00#99CC33#99CC66#99CC99#99CCCC#99CCFF#99FF00#99FF33#99FF66#99FF99#99FFCC#99FFFF#\
0000FF#CC0000#CC0033#CC0066#CC0099#CC00CC#CC00FF#CC3300#CC3333#CC3366#CC3399#CC33CC#CC33FF#CC6600#CC6633#CC6666#CC6699#CC66CC#CC66FF#\
FFFF00#CC9900#CC9933#CC9966#CC9999#CC99CC#CC99FF#CCCC00#CCCC33#CCCC66#CCCC99#CCCCCC#CCCCFF#CCFF00#CCFF33#CCFF66#CCFF99#CCFFCC#CCFFFF#\
00FFFF#FF0000#FF0033#FF0066#FF0099#FF00CC#FF00FF#FF3300#FF3333#FF3366#FF3399#FF33CC#FF33FF#FF6600#FF6633#FF6666#FF6699#FF66CC#FF66FF#\
FF00FF#FF9900#FF9933#FF9966#FF9999#FF99CC#FF99FF#FFCC00#FFCC33#FFCC66#FFCC99#FFCCCC#FFCCFF#FFFF00#FFFF33#FFFF66#FFFF99#FFFFCC#FFFFFF';
var colorarray = colors.split('#');
var setv = '';
function showcolors() {
        var s = '';
        for(c in colorarray) {
                s += '<em onmouseover="v(\'' + colorarray[c] + '\')" style="background-color:#' + colorarray[c] + '"></em>';
        }
        document.getElementById('colors').innerHTML = s;
}

function setvalue(obj) {
        if(varname) {
                parent.$(varname).style.backgroundColor = setv;
        }
        if(varnamev) {
                parent.$(varnamev).value = setv;
        }
        if(fun) eval('parent.'+fun+'("'+setv+'")');
}

function v(v) {
        v = v != 'transparent' ? '#' + v : 'transparent';
        document.getElementById('p').style.backgroundColor = v;
        setv = v;
        document.getElementById('pv').innerHTML = v;
}

</script>
<style>
body { margin:0px;background-color:#333; }
#h { padding:0;width:210px;height:15px;background-color:#CCC;overflow:hidden;}
#p { margin:0;display:block;float:left;font-size:0;width:140px;height:13px;background:#DDF0DF; }
#pv { margin:0;display:block;float:left;font-size:12px;width:58px;height:13px;overflow:hidden;text-align: right;font-style:normal;background:#DDF0DF; }
#colors { clear:both;width:209px; height:133px; }
#colors em, .trans { font-size:0;margin:1px 0 0 1px;width:10px;height:10px;float:left;cursor:pointer; }
.trans { background-color: #FFF; }
</style>

<body onmousedown="setvalue(document.getElementById('colorhex'))" scrolling="no">
<div id="h"><em id="p"></em><em id="pv"></em><em class="trans" onmouseover="v('transparent')" style="background-image:url('transcolor.gif')"></em></div>
<div id="colors"></div>
<script type="text/javascript">
showcolors();
try {document.getElementById('box').style.backgroundColor = cvalue;} catch(e) {}
</script>
</body>

从location.search中取得了varname,分割后,放入fun,最后进入eval执行。

image

修复:将文件/static/image/admincp/getcolor.htm的37行内容

     if(fun) eval('parent.'+fun+'("'+setv+'")');

替换为:

if(fun && (fun == 'sethtml_color' || fun == 'spaceDiy.setBgColor' || fun == 'spaceDiy.setTextColor' || fun == 'spaceDiy.setLinkColor')) {
eval('parent.'+fun+'("'+setv+'")');
}

为您推荐了相关的技术文章:

  1. Xsl Exec Webshell (aspx) - Evi1cg's blog
  2. Remote Symbol Resolution « Threat Research Blog
  3. From 404 and default pages to RCE via .cshtml webshell
  4. Transferring Backdoor Payloads by DNS AAAA records and IPv6 Address
  5. 軟體工程師的鄙視鏈 | 岚光