最近在测试我司的某个业务,发现了多处XSS,并且在SRC上被外界白帽子爆出很多处反射型XSS与存储型XSS,所以根据现有的常用DZ版本进行了一次分析,先引带一下以前wooyun上某人的漏洞,下面是截取的某位总结的Discuz给二次开发所挖的4大坑:
好吧,废话不多说,下面是回顾以前所分析的DiscuzX系列的存储型XSS:
首先发表帖子,在帖子内容里面提交payload:
[align="onmouseover="alert(1)]
发表帖子后在内容里面发现双引号被实例化:
下面来看一下后台实例化的代码:
/template/default/forum/viewthread_node_body.htm
看来在后端已经做了过滤了(对DZ不太熟,在后端咋定义$post[message]的html实例化并未找到)
当管理员或版主去编辑该帖子的时候可直接触发XSS:
到这里咱们的alert被放到了div标签里面触发了。。。。
再回溯一下编辑用户回复或帖子的触发过程:
这里主要是获取text的内容,看看$()函数的定义:
跟踪到common.js:
这里document.getElementById是原生的javascript。。。
这里的id="e_textarea",代表一个对象,对象的内容就是评论的内容
由于原生javascript的原因,会将对应id的值的内容将双引号渲染回来。。。
接着就是:
var wysiwyg = (BROWSER.ie || BROWSER.firefox || (BROWSER.opera >= 9)) && parseInt('1') == 1 ? 1 : 0;
给wysiwyg赋值判断浏览器,然后开始渲染编辑框:
估计这里最重要的函数就是bbcode2html了,跟踪到static/js/bbcode.js
这里我们的payload是:
[align="onmouseover="alert(1)]
那么align=这段函数应该就被执行替换了,最后的payload str的值为:
<div align="xxo" onmouseover="alert(1)">xx</div>
/static/js/editor.js
里面定义了neweditor函数:
调用了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
问题还是在前端的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都会在首页开启四宫格,如图所示:
在任意模块发正常的标题显示是这样的:
很明显是插在<script></script>标签内的,如果能通过单引号闭合来另外构造我们自己的XSS Payload那就OK了,看看js代码里面有木有引入过滤函数:
很明显没有单引号的过滤。。。。
那在发帖的时候在标题插入payload:
';alert(document.cookie);//
提交后后台更新缓存后首页弹出cookie:
看一下payload插入位置:
同样的问题还出在主题推荐的地方:
/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执行。
修复:将文件/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+'")');
}