前一篇文章讲述了应用安全问题中的登录失败和防止暴力破解账号的问题,这篇文章讲述下应用安全中的xss攻击和跨站脚本攻击问题的处理。
- xss攻击分类
- 服务端处理xss攻击
- 客户端处理xss攻击
- 服务端处理跨站脚本攻击
1. 什么是xss攻击?
XSS全称是Cross Site Scripting即跨站脚本,当目标网站目标用户浏览器渲染HTML文档的过程中,出现了不被预期的脚本指令并执行时,XSS就发生了。 这里我们主要注意四点:1、目标网站目标用户;2、浏览器;3、不被预期;4、脚本。
2. xss分类
常见的XSS分类有三种类型:反射型XSS(非持久型)、存储型XSS、和DOM型XSS。
2.1 反射型XSS
浏览器发出请求的时候,XSS代码出现在URL中,作为输入提交到后台服务端,服务端解析响应后,XSS代码随相应内容一起传回给浏览器,最后浏览器解析执行XSS代码,这个过程就行一次反射,所以被称为反射型XSS。
- 一个简单的例子:
访问 https:www.xxx.com/geturl?x=<script>alert(12);</script>
如果输入x的值未经任何过滤就直接输出,提交:
https:www.xxx.com/geturl?x=<script>alert(12);</script>
则alert()函数会在浏览器触发。
2.2 存储型XSS
存储型XSS和反射型XSS的差别仅在于存储型XSS在服务器解析参数之后会把解析结果存储在数据库中,下次请求目标页面的时候不用再注入XSS代码。
2.3 DOM型XSS
DOM型XSS攻击 是指前端页面中渲染富文本类容时,在后台接口的相应数据植入xss脚本,则在页面中在响应出响应的代码攻击。
3. xss攻击处理办法
3.1 XSS攻击后端处理方式
使用jsoup处理存储型和反射型XSS攻击
3.1.1 maven引入jsoup
<!-- xss过滤组件 -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.9.2</version>
</dependency>
3.1.2 拦截防止xss注入 通过Jsoup过滤请求参数内的特定字符
/**
* 拦截防止xss注入 通过Jsoup过滤请求参数内的特定字符
*
* @author yangwk
*/
public class XssFilter implements Filter {
private static final Logger LOGGER = LogManager.getLogger();
/**
* 是否过滤富文本内容
*/
private static boolean isIncludeRichText = false;
public List<String> excludes = new ArrayList<>();
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("xss filter is open");
}
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
if (handleExcludeURL(req, resp)) {
filterChain.doFilter(request, response);
return;
}
//设置客户端 cookie
HttpServletResponse response2 = (HttpServletResponse) response;
response2.addHeader("Set-Cookie", "uid=112; Path=/; HttpOnly");
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper(
(HttpServletRequest) request, isIncludeRichText);
filterChain.doFilter(xssRequest, response);
}
private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) {
if (excludes == null || excludes.isEmpty()) {
return false;
}
String url = request.getServletPath();
for (String pattern : excludes) {
Pattern p = Pattern.compile("^" + pattern);
Matcher m = p.matcher(url);
if (m.find()) {
return true;
}
}
return false;
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("xss filter init ====================");
}
String isIncludeRichTextStr = filterConfig.getInitParameter("isIncludeRichText");
if (StringUtils.isNotBlank(isIncludeRichTextStr)) {
isIncludeRichText = BooleanUtils.toBoolean(isIncludeRichTextStr);
}
String temp = filterConfig.getInitParameter("excludes");
if (temp != null) {
String[] url = temp.split(",");
for (int i = 0; url != null && i < url.length; i++) {
excludes.add(url[i]);
}
}
}
@Override
public void destroy() {
}
}
3.1.3 在springBoot中注册xssFilter
/**
* @author tangping
* @date 2019/10/30 0030 20:04
* @Desc:
*/
public class XSSConfig {
/**
* xss过滤拦截器
*/
@Bean
public FilterRegistrationBean xssFilterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new XssFilter());
filterRegistrationBean.setOrder(1);
filterRegistrationBean.setEnabled(true);
filterRegistrationBean.addUrlPatterns("/*");
Map<String, String> initParameters = Maps.newHashMap();
initParameters.put("excludes", "/favicon.ico,/img/*,/js/*,/css/*");
initParameters.put("isIncludeRichText", "true");
filterRegistrationBean.setInitParameters(initParameters);
return filterRegistrationBean;
}
}
3.1.4 xss非法标签过滤工具类 过滤html中的xss字符
/**
* xss非法标签过滤工具类 过滤html中的xss字符
*
* @author xbz
*/
public class JsoupUtil {
/**
* 使用自带的basicWithImages 白名单 允许的便签有a,b,blockquote,br,cite,code,dd,dl,dt,em,i,li,ol,p,pre,q,small,span,
* strike,strong,sub,sup,u,ul,img 以及a标签的href,img标签的src,align,alt,height,width,title属性
*/
private static final Whitelist WHITELIST = Whitelist.basicWithImages();
/**
* 配置过滤化参数,不对代码进行格式化
*/
private static final Document.OutputSettings OUTPUTSETTINGS = new Document.OutputSettings()
.prettyPrint(false);
static {
// 富文本编辑时一些样式是使用style来进行实现的
// 比如红色字体 style="color:red;"
// 所以需要给所有标签添加style属性
WHITELIST.addAttributes(":all", "style");
}
public static String clean(String content) {
if (StringUtils.isNotBlank(content)) {
content = content.trim();
}
return Jsoup.clean(content, "", WHITELIST, OUTPUTSETTINGS);
}
public static void main(String[] args) throws IOException {
String text = " <a href=\"http://www.baidu.com/a\" οnclick=\"alert(1);\">sss</a><script>alert(0);</script>sss ";
System.out.println(clean(text));
}
}
3.1.6 定义XssHttpServletRequestWrapper 适配修改request中的参数
/**
* <code>{@link XssHttpServletRequestWrapper}</code>
*
* @author
*/
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
HttpServletRequest orgRequest = null;
private boolean isIncludeRichText = false;
public XssHttpServletRequestWrapper(HttpServletRequest request, boolean isIncludeRichText) {
super(request);
orgRequest = request;
this.isIncludeRichText = isIncludeRichText;
}
/**
* 覆盖getParameter方法,将参数名和参数值都做xss过滤。<br/> 如果需要获得原始的值,则通过super.getParameterValues(name)来获取<br/>
* getParameterNames,getParameterValues和getParameterMap也可能需要覆盖
*/
@Override
public String getParameter(String name) {
Boolean flag = "content".equals(name) || name.endsWith("WithHtml");
if (flag && !isIncludeRichText) {
return super.getParameter(name);
}
name = JsoupUtil.clean(name);
String value = super.getParameter(name);
if (StringUtils.isNotBlank(value)) {
value = JsoupUtil.clean(value);
}
return value;
}
@Override
public String[] getParameterValues(String name) {
String[] arr = super.getParameterValues(name);
if (arr != null) {
for (int i = 0; i < arr.length; i++) {
arr[i] = JsoupUtil.clean(arr[i]);
}
}
return arr;
}
/**
* 覆盖getHeader方法,将参数名和参数值都做xss过滤。<br/> 如果需要获得原始的值,则通过super.getHeaders(name)来获取<br/> getHeaderNames
* 也可能需要覆盖
*/
@Override
public String getHeader(String name) {
name = JsoupUtil.clean(name);
String value = super.getHeader(name);
if (StringUtils.isNotBlank(value)) {
value = JsoupUtil.clean(value);
}
return value;
}
/**
* 获取最原始的request
*/
public HttpServletRequest getOrgRequest() {
return orgRequest;
}
/**
* 获取最原始的request的静态方法
*/
public static HttpServletRequest getOrgRequest(HttpServletRequest req) {
if (req instanceof XssHttpServletRequestWrapper) {
return ((XssHttpServletRequestWrapper) req).getOrgRequest();
}
return req;
}
}
3.2 DOM型xss攻击前端处理方式
前端处理xss攻击主要是在渲染页面时对富文本字段进行xss过滤,使用工具参考 前端xss攻击组件
4 项目权限控制,控制权限越权问题处理
本身项目是前后端分离,最开始的权限认证只是使用jwtToken接口权限鉴别,但是未根据角色对后台接口权限进行鉴别,考虑两个方案:
- 自定义注解在响应的方法上加上注解配置响应的角色,然后使用aop切面编程进行扫描标记有注解的方法进行权限控制,缺点:对代码的倾入性比较高,整改起来比较麻烦。
- 第二种方案使用filter根据数据库配置响应角色具有哪些权限,然后在根据这个规则进行判断,优点:代码侵入性较低;缺点:需要整理每个角色对应哪些权限,并入库。 综上,最后还是选择了第二种方案,这样对原有代码改动较少,容易配置。
4.1 定义权限控制filter
/**
* @author tangping
* @date 2019/10/31 0031 16:07
* @Desc: 接口权限控制
*/
@Slf4j
public class AuthorityFilter implements javax.servlet.Filter {
private BackInterfaceInfoService backInterfaceInfoService;
public AuthorityFilter(BackInterfaceInfoService backInterfaceInfoService) {
this.backInterfaceInfoService = backInterfaceInfoService;
}
/**
* 白名单需要过滤掉的地址
*/
private List<String> whiteList;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
String excludes = filterConfig.getInitParameter("excludes");
whiteList = Splitter.on(",").trimResults().omitEmptyStrings().splitToList(excludes);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String uri = request.getRequestURI();
log.info("获取uri:" + uri);
//白名单地址 控制
if (handleExcludeURL(request, response, whiteList)) {
filterChain.doFilter(request, response);
return;
}
// 根据权限表获取配置
// 登录用户roleType
String roleType =
StringUtils.isEmpty(JWTContextUtil.getRoleType()) ? JWTContextUtil.getUserType()
: JWTContextUtil.getRoleType();
log.info("获取到用户的角色为:{}", roleType);
//
List<String> userRoleList = this.backInterfaceInfoService.findByRoleType(roleType);
if (handleExcludeURL(request, response, userRoleList)) {
filterChain.doFilter(request, response);
return;
}
log.error("accessDenied path : {}", request.getServletPath());
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
response.getWriter().write(JSONObject
.toJSONString(APIResult.failed(new APIBusinessException(APIResultCodeEnums.ACCESS_DENIED,
APIResultCodeEnums.ACCESS_DENIED.desc()))));
response.flushBuffer();
}
@Override
public void destroy() {
this.whiteList = Lists.newArrayList();
}
/**
* 处理白名单情况
*/
private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response,
List<String> whiteList) {
if (CollectionUtils.isEmpty(whiteList)) {
return false;
}
String url = request.getServletPath();
for (String pattern : whiteList) {
Pattern p = Pattern.compile("^" + pattern);
Matcher m = p.matcher(url);
if (m.find()) {
return true;
}
}
return false;
}
}
4.2 在SpringBoot中注册权限控制filter
/**
* @author tangping
* @date 2019/10/31 0031 17:06
* @Desc:
*/
@Configuration
public class AuthorityConfig {
@Resource
private BackInterfaceInfoService backInterfaceInfoService;
/**
* xss过滤拦截器
*/
@Bean
public FilterRegistrationBean authorityFilterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new AuthorityFilter(backInterfaceInfoService));
filterRegistrationBean.setOrder(1);
filterRegistrationBean.setEnabled(true);
filterRegistrationBean.addUrlPatterns("/*");
Map<String, String> initParameters = Maps.newHashMap();
String excludes = Joiner.on(",")
.join("/swagger-ui.html", "/webjars/*",
"/swagger-resources/*", "/v2/*", Constants.PREFEX + "/auth/*");
initParameters.put("excludes", excludes);
filterRegistrationBean.setInitParameters(initParameters);
return filterRegistrationBean;
}
}
结束语
- 这篇文章整理分析了常见xss攻击类型,总结了xss攻击常见处理方式,学习了如何控制越权等问题。