Java代码审计之跨站脚本攻击

  • A+

关于跨站脚本攻击

  跨站脚本攻击(Cross Site Scripting),为不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS。恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页面时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的目的。

审计思路

  XSS一般分为反射型、存储型、DOM型。DOM型比较特殊。下面主要是围绕存储型和反射型进行整理。XSS的产生主要是因为前端传递的数据未经检验就被作为动态内容提交给用户浏览器执行。那么也就是说,核心的要点有:
* 参数用户前端传递可控
* 在view层会对用户的输入进行输出

  而且XSS如果一处接口存在,那么很大可能全部接口都是存在类似的安全问题的。
  所以要点首先肯定是挖掘一条完整的攻击链,不是简单的例如没有过滤器,没发现输出编码的相关函数就可以断定存在xss缺陷了,例如一些webservice很多都是没有view层渲染的,主要是进行数据的封装和传递。
  反射型跟存储型的区别主要是存储型会经过DAO层进行类似数据库的更新或者写入操作,最终在view层进行展示。
  所以整个审计过程要点还是定位用户的输入输出,也就是梳理数据交互以及前端展示的过程。找到一条完整的利用链之后,就是结合现有的安全措施(输出编码、过滤器等)进行判断,例如是否存在绕过的可能,或者是没有任何安全防护可直接造成攻击

快速定位输入输出

  所谓输入输出,主要是前端渲染还有后端处理,例如前端EL表达式接受数据,展示内容,后端接受用户输入,写入数据库完成注册业务等。
  首先是前端渲染,主要是看前端渲染的内容是什么,可以简单的定位前端输出的常见标识,然后结合后端逻辑进行判断。例如发现前端进行了用户个人信息的展示,那么我们就可以关注后端注册接口,是否对用户名、地址等信息进行xss的相关防护。
  常见的view层展示数据方式如下:
* JSP表达式
<%=%>,用于将已经声明的变量或者表达式输出到网页上面。
* 相关案例:
获取前端输入的id,然后展示在JSP页面中:
java
<%
String id = request.getParameter("id");
%>
<%=id%>

* EL表达式
EL(Express Lanuage)表达式可以嵌入在jsp页面内部,减少jsp脚本的编写,EL出现的目的是要替代jsp页面中脚本的编写。
${expression},用于指定要输出的内容,可以是字符串,也可以是由EL运算符组成的表达式。
* 相关案例:
例如在request域中存储了一个名为"user"的对象,我们不需要通过${requestScope.user}取出相应的对象,只需要${user}即可取出相应的对象,获取对应的属性的话只需要调用对应的get方法即可。
java
<li>${user.getUsername()}</li>
<li>${user.getRole()}</li>
<li>${user.getDepartment()}</li>

  从pageContext域,request域,session域,application域中获取属性,在某个域中获取后将不在向后寻找。
* JSTL标签

  JSTL(JSP Standard Tag Library),JSP标准标签库,可以嵌入在jsp页面中使用标签的形式完成业务逻辑等功能。jstl出现的目的同el一样也是要代替jsp页面中的脚本代码。
  常见标签如下:
  (1)
  用于将表达式的值输出到JSP页面中,类似于JSP表达式<%=表达式%>,或EL表达式${expression}。
  例如:结合EL表达式,登陆后从session里获取当前用户的用户名,显示当前登陆的用户:
<c:out value="${user.getUsername()}"/>
  (2)
  条件判断标签:根据不同的条件处理不同的业务。
  例如用标签根据是否登陆显示不同的内容,若相关session中的user不为空,则说明登陆成功,则显示user对象中的用户名和注销按钮:
<c:if test="${!empty user}">
<li>${user.name}</li>
<li><a href="./logout.action">注销</a></li>
</c:if>

  (3)
  循环标签:可以根据循环条件,遍历数组和集合类中的所有或部分数据。
  例如,使用标签遍历商品菜单:
<c:forEach var="pt" items="${requestScope.products}" varStatus="status">
<tr>
<!-- 商品名称 -->
<td>
<!-- 输出商品名称 -->
${pt.name }
</td>
<td>
<!-- 输出商品地址 -->
${pt.area }
</td>
<td>
<!-- 输出商品价格 -->
${pt.price }
</td>
</tr>
</c:forEach>

  通过上面的例子可以看到,利用EL表达式结合JSTL标签,可以完成对应的前端渲染工作,那么对应实际的跨站脚本的挖掘,也可以简单的通过这些标签进行定位,例如标签经常用户循环的列表输出,如果目标应用是一个管理后台,那么很可能是用于用户展示,那么很可能存在存储型的XSS,可以定位对应的前端页面,联动整个后端处理过程,进行对应的审计
  然后就是后端处理,实际业务中一般在前端与后台之间互相传递参数,一般主要是数据封装、入库等,通过查看后端处理可以快速的定位我们前端传入的内容是否写入了数据库(存储XSS),亦或是存在什么安全措施等。
  所以关注点主要在如何快速定位后端如何封装数据,封装什么数据。什么参数是前端传输且可控的,以常见的Spring MVC和Spring Boot为例,常见的有:
  (1)ModelAndView
  例如这里会通过id进行用户信息的查询,然后封装后跳转到stulist.jsp,那么我们可以进一步在stulist.jsp视图层查看展示的相关用户信息是否可控以及,例如用户名、地址等。然后这里通过service层与数据库进行了交互,如果可控的话那么很大可能会存在存储型XSS。
java
/*
根据id查询用户
*/
@RequestMapping(value = "/sid/{sid}",method=RequestMethod.GET)
public ModelAndView getUserById(@PathVariable String sid){ //sid参数对应方法上的路径参数{sid}
ModelAndView mv = new ModelAndView("stulist"); //跳转到stulist.jsp
User user = userService.getUserById(sid);
mv.addObject("user",user);
return mv;
}

  (2)ModelMap
java
@RequestMapping(value = "/sid/{sid}",method=RequestMethod.GET)
public String getUserById(ModelMap modelMap,String sid) {
User user = userService.getUserById(sid);
modelMap.addAttribute("userInfo", user);
return "stulist";
}

  (3)Model
java
@RequestMapping("/testModel")
public String testModel(Model model,String email) {
model.addAttribute("email",email);
return "success";
}

  通过搜索ModelAndView、ModelMap、Model等关键字,我们可以快速定位哪些接口映射是与前端展示相关的,通过这些接口定位对应的jsp、html,定位对应的输出内容。
  当然了,类似request对象的各种方法,同样也是关注点,例如request.getParameter()。
  综上,可以总结出一些常用的关键字:
<%=
${
<c:out
<c:if
<c:forEach
ModelAndView
ModelMap
Model
request.getParameter
request.setAttribute

  结合这些关键字,可以快速定位整个前后端交互,处理和渲染的过程。举个例子:
  访问auditView接口,传递auditId参数,并且将其值封装在Model里,然后返回相关jsp页面view:
```java
@RequestMapping("/auditView")
public String auditView(Model model,HttpSession session,
@RequestParam("auditId") String auditId) {
try{
MpDataAuditChangeDto mpDataAuditChangeDto = mpDataAuditChangeService.selectByPrimaryKey(auditId);
if(UserModelContext.get().getUserName().equals(mpDataAuditChangeDto.getCreateUser())){
PageMessageUtil.saveErrorMessage(session, "用户不能审核自己创建的信息");
return "redirect:" + REQUEST_BASE_PATH + "auditList";
}
BdsCurrencyRelationshipDto BdsCurrencyRelationshipDto = bdsCurrencyRelationshipDtoService.selectByPrimaryKey(mpDataAuditChangeDto.getRefId());
model.addAttribute("BdsCurrencyRelationshipDto", BdsCurrencyRelationshipDto);
model.addAttribute("changeType",mpDataAuditChangeDto.getChangeType());
model.addAttribute("auditId",auditId);
}catch(Exception e){

    }
    return "view";
}

&emsp;&emsp;在对应的jsp页面view.jsp直接把auditId的值直接写入到jsp页面中通过EL表达式进行展示:java

```
  当然也可以从前端页面出发,例如通过搜索EL表达式发现auditId在前端渲染了,然后再定位对应的接口,查看整个交互过程。
  上述就是整个前后端交互,处理和渲染的过程,在实际的跨站脚本攻击漏洞挖掘中,发现这样完整的一条攻击链就可以了,剩下的就是检查是否存在对应的输出编码(上例中是没有的),或者后端过滤器、接口是否进行了相关的过滤操作等,如果没有,这里可以在调用接口时将
auditId的值设置为对应的js语句,即可触发XSS。

安全措施检查

  跨站脚本攻击的主要防护思路一个是过滤敏感输入,一个是输出编码,两个过程都可以通过过滤器和一些具体的方法进行实现。在找到了完整的交互过程后,就是审计是否存在相关的安全措施或者安全措施是否存在缺陷可绕过了。常见的安全措施如下:
* 过滤敏感输入
* 输出编码

  一般会用到filter来对request进行过滤,排除相关的恶意js输入,同时也可以针对response输出进行统一的编码处理。
  定位相关的filter可以通过查看搜索如下文件或关键字:
* web.xml
  通过xml可以快速定位系统现有的过滤器并查看其具体实现,检查是否存在跨站脚本的防护。例如:
xml
<filter>
<filter-name>xssFilter</filter-name>
<filter-class>com.security.filter.XssFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>xssFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

* @WebFilter
  通过@WebFilter关键字可以快速定位使用注解定义的过滤器,通过检查其具体实现,检查是否存在跨站脚本的防护。例如:
java
@WebFilter(filterName = "XssFilter", urlPatterns = { "/*" })
public class XssFilter implements Filter {
//具体实现
}

  定位到过滤器具体实现后,主要是检查以下几个方面:
 (1)获取数据的方式
  常见的过滤器使用request.getParameterNames()方法进行数据获取,并不能覆盖所有的数据提交方式,同理,request.getParameter()亦然。所以需要根据实际的场景进行检查。
  例如常见的application/json这种提交方式明显是不可以使用上述方法获得参数内容并进行检查的。
  审计的时候也可以针对一些特殊的业务场景进行定位,例如进行上传Excel批量导入数据时,使用multipart/form-data进行提交,数据为对应的IO流,过滤器也可能无法识别其中是否包含xss关键字,还有文件上传时我们的文件名等。那么就可以通过搜索例如poi组件的特殊方法,定位对应的excel解析业务的具体实现,如果满足写入数据库且前端显示导入内容的话,此时很大可能存在过滤缺陷导致存储XSS。
 (2)Filter执行顺序
  主要是过滤器的实际业务,最常见的双重URL编码绕过xss很大可能就是Filter执行顺序的问题。
 (3)过滤的规则内容
  整个过滤器的核心,检查的关键点主要是是否涵盖常见的xss利用场景以及相关的业务接口:
常见xss位置如下
* 在内联的JavaScript中的字符串、变量或者方法名中
* 可以自行进行新建标签
* 在标签的属性中
- src/href属性
- value属性
- style属性
* ......

  结合上述场景,举一个简单的例子:
  相关的过滤规则如下:
```java
private static List getXssPatternList() {
List ret = new ArrayList();

    ret.add(new Object[] { "<(no)?script[^>]*>.*?</(no)?script>", Pattern.CASE_INSENSITIVE });
    ret.add(new Object[] { "<(no)?iframe[^>]*>.*?</(no)?iframe>", Pattern.CASE_INSENSITIVE });
    ret.add(new Object[] { "eval\((.*?)\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL });
    ret.add(new Object[] { "expression\((.*?)\)",
            Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL });
    ret.add(new Object[] { "(javascript:|vbscript:|view-source:)*", Pattern.CASE_INSENSITIVE });
    ret.add(new Object[] { "<("[^"]*"|'[^']*'|[^'">])*>",
            Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL });
    ret.add(new Object[] {
            "(window\.location|window\.|\.location|document\.cookie|document\.|alert\(.*?\)|window\.open\()*",
            Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL });
    ret.add(new Object[] {
            "<+\s*\w*\s*(oncontrolselect|oncopy|oncut|ondataavailable|ondatasetchanged|ondatasetcomplete|ondblclick|ondeactivate|ondrag|ondragend|ondragenter|ondragleave|ondragover|ondragstart|ondrop|οnerrοr=|onerroupdate|onfilterchange|onfinish|onfocus|onfocusin|onfocusout|onhelp|onkeydown|onkeypress|onkeyup|onlayoutcomplete|onload|onlosecapture|onmousedown|onmouseenter|onmouseleave|onmousemove|onmousout|onmouseover|onmouseup|onmousewheel|onmove|onmoveend|onmovestart|onabort|onactivate|onafterprint|onafterupdate|onbefore|onbeforeactivate|onbeforecopy|onbeforecut|onbeforedeactivate|onbeforeeditocus|onbeforepaste|onbeforeprint|onbeforeunload|onbeforeupdate|onblur|onbounce|oncellchange|onchange|onclick|oncontextmenu|onpaste|onpropertychange|onreadystatechange|onreset|onresize|onresizend|onresizestart|onrowenter|onrowexit|onrowsdelete|onrowsinserted|onscroll|onselect|onselectionchange|onselectstart|onstart|onstop|onsubmit|onunload)+\s*=+",
            Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL });
    return ret;
}

&emsp;&emsp;这里的规则是无法覆盖如下场景的:html

&emsp;&emsp;所以我们可以结合EL表达式,全局搜索value="${,查看是否在view层能找到实际的业务场景,然后结合后端查看实际的请求接口,找到一条完整的xss利用链,就可以进行XSS利用了。
&emsp;&emsp;输出编码是比较有效解决xss问题的方式,但是比较烦琐,**一般会通过全局过滤器filter结合一些实际的方法对response统一编码输出**。<br>
&emsp;&emsp;常见的方式如下:
* commons-lang-2.5.jar相关函数<br>
java
StringEscapeUtils.escapeHtml(string);
StringEscapeUtils.escapeJavaScript(string);
StringEscapeUtils.escapeSql(string);
* org.springframework.web.util.HtmlUtilsjava
String string = HtmlUtils.htmlEscape(userinput); //转义
* ESAPIjava
ESAPI.encoder().encodeForHTML
```
* ......

  在审计的时候有时候发现相关的过滤器或者view层输出做了HTML实体编码,很多人可能就放弃了,直接统一将对应的输入输出成HTML实体编码状态。这种方式的确可以防御绝大多数场景下的xss,但是并不是绝对的,甚至可能影响实际业务。以下列举一些需要额外检查的编码场景:
* 内联的JavaScript代码块
  为了加快网页的加载速度,通常把一个数据通过JSON的方式内联到HTML中,内联的JavaScript代码块使用JS编码进行相关的转译,其内嵌的JSON数据不可以简单使用 HTML实体编码进行转译,因为转义”后,JSON 格式会被破坏

xss_codeAudit_9.png
* 链接内容
  根据浏览器解析顺序,若链接内容使用html实体编码,在实际解析时候会进行还原,并不影响实际的触发,例如:
html
<iframe src="[用户可控]"/>
<iframe src="javaScript:alert(1)"/>
<iframe src="javaScript:alert('1')">
<iframe src="javaScript:alert(1)"/>

  所以实际防护应该根据实际业务情况禁用“JavaScript:、data:”协议,非法schema等。结合业务需要判断是否https:、http:或者//开头。
  在实际代码审计中,可以通过如下正则,快速定位(一般来说存在链接的属性一般是src和href):
java
(href|src)=(.*?)${

总结

  在实际的代码审计中,可以根据一些关键字快速定位对应的接口,也可以结合实际的业务,例如上传导入这种xss重灾区等。然后针对过滤器等相关的安全措施进行对应的检查,考虑编码绕过等各种场景。最大化的发掘xss相关风险。

相关推荐: Laravel 8 反序列化分析

forward laravel的版本已经到了8;这里分析一个laravel8的反序列化漏洞,但是让我感到意外的是,这个漏洞竟然在低版本的laravel上依然可以存在,从根本来说这个漏洞是laravel的mockery组件漏洞,没想到一直没修; 本文涉及知识点实…

Copyright ©  CN-SEC中文网  版权所有. CN-SEC.COM
  • 文章目录
  • icon