使用过滤器,jsoup过滤XSS脚本
千山暮雪CN 人气:0springboot使用过滤器,jsoup过滤XSS脚本
- 背景:略
- 目标:完成request请求中的脚本过滤
- 技术:filter,jsoup,requestWapper
1.把可能包含脚本的参数位置分析一下
post
/put
/delete
: 请求的参数中,有可能是表单提交、也有可能是使用了@requestBody注解,那么参数就是json格式,位于request的流中。get
/options
等:可能存在于url参数中,也有可能是表单提交的预请求中,所以一般在能想到的位置都有可能存在,包括header中。
2.分析实现过程
2.1首先要从request请求中将各个需要过滤位置的参数取出来
2.2然后将参数取出来进行过滤
2.3将过滤后的参数重新包装成request传递下去
2.4在这期间,
- 需要准备好jsoup过滤脚本的工具类;
- 需要自定义一个过滤器,并且在过滤器中添加匹配条件,如:那些url不需要过滤,那些请求方式必须进行过滤;
- 对过滤器进行配置,是否开启,设置在整个过滤器链中的位置,设置过滤的白名单或者黑名单
- 所以就很清晰了我们过滤需要哪些类,哪些配置了
一个filter
一个requestWapper
一个jsoup工具类
一个filter的配置类
2.5进行数据测试
3.代码实现过程
3.1.jsoup依赖:
<!--screen xss --> <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.9.2</version> </dependency>
3.2jsoup工具类:JsoupUtil
import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.safety.Whitelist; import java.io.FileNotFoundException; import java.io.IOException; /** * @Auther: qianshanmuxue * @Date: 2019/2/27 19:32 * @Description: xss Illegal label filtering */ public class JsoupUtil { private static final Whitelist whitelist = Whitelist.simpleText();//jsoup白名单种类,有四种,每一种针对的标签类型不一样,具体的可以ctrl+左键点击simpleText,在jsoup源码中有响应的注释和标签名单 //add myself white list label private static final Document.OutputSettings outputSettings = new Document.OutputSettings().prettyPrint(false); static { whitelist.addAttributes(":all", "style").addTags("p").addTags("strong");//将自定义标签添加进白名单,除开白名单之外的标签都会被过滤 whitelist.preserveRelativeLinks(true);//这个配置的意思的过滤如果找不到成对的标签,就只过滤单个标签,而不用把后面所有的文本都进行过滤。 //(之前在这个问题上折腾了很久,当<script>标签只有一个时,会<script>标签后面的数据全部过滤) } public static String clean(String content) { //过滤方法 return Jsoup.clean(content, "", whitelist, outputSettings); } //test main public static void main(String[] args) throws FileNotFoundException, IOException { String text = "<a href=\"http://www.baidu.com/a\" onclick=\"alert(1);\"><strong><p>sss</p></strong></a><script>alert(0);</script>sss"; System.out.println(clean(text)); } }
3.3request包装类XssHttpServletRequestWrapper
import java.io.*; import java.util.*; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import com.xxx.utils.JsoupUtil; import org.jsoup.nodes.Document; import org.springframework.util.StringUtils; /** * @Auther: qianshanmuxue * @Date: 2019/2/27 16:24 * @Description:request wapper use to get request parameter and request bdoy data and wapper another request */ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { //因为我们需要获取request中的数据,所以需要继承java底层中HttpServletRequestWrapper这个类,重写父类中的某些方法,获取相应位置的参数 private HttpServletRequest orgRequest = null; private static final Document.OutputSettings outputSettings = new Document.OutputSettings().prettyPrint(false); public XssHttpServletRequestWrapper(HttpServletRequest request) { super(request); orgRequest = request; } @Override public ServletInputStream getInputStream() throws IOException {//get BufferedReader br = new BufferedReader(new InputStreamReader(orgRequest.getInputStream())); String line = br.readLine(); String result = ""; if (line != null) { result += clean(line); } return new WrappedServletInputStream(new ByteArrayInputStream(result.getBytes())); } @Override public String getParameter(String name) { if (("content".equals(name) || name.endsWith("WithHtml"))) { return super.getParameter(name); } name = clean(name); String value = super.getParameter(name); if (!StringUtils.isEmpty(value)) { value = clean(value); } return value; } @Override public Map getParameterMap() { Map map = super.getParameterMap(); // 返回值Map Map<String, String> returnMap = new HashMap<String, String>(); Iterator entries = map.entrySet().iterator(); Map.Entry entry; String name = ""; String value = ""; while (entries.hasNext()) { entry = (Map.Entry) entries.next(); name = (String) entry.getKey(); Object valueObj = entry.getValue(); if (null == valueObj) { value = ""; } else if (valueObj instanceof String[]) { String[] values = (String[]) valueObj; for (int i = 0; i < values.length; i++) { value = values[i] + ","; } value = value.substring(0, value.length() - 1); } else { value = valueObj.toString(); } returnMap.put(name, clean(value).trim()); } return returnMap; } @Override public String[] getParameterValues(String name) { String[] arr = super.getParameterValues(name); if (arr != null) { for (int i = 0; i < arr.length; i++) { arr[i] = clean(arr[i]); } } return arr; } /** * get org request * * @return */ public HttpServletRequest getOrgRequest() { return orgRequest; } /** * wapper request */ public static HttpServletRequest getOrgRequest(HttpServletRequest req) { if (req instanceof XssHttpServletRequestWrapper) { return ((XssHttpServletRequestWrapper) req).getOrgRequest(); } return req; } public String clean(String content) { String result = JsoupUtil.clean(content); return result; } private class WrappedServletInputStream extends ServletInputStream { public void setStream(InputStream stream) { this.stream = stream; } private InputStream stream; public WrappedServletInputStream(InputStream stream) { this.stream = stream; } @Override public int read() throws IOException { return stream.read(); } @Override public boolean isFinished() { return true; } @Override public boolean isReady() { return true; } @Override public void setReadListener(ReadListener readListener) { } } }
3.4filter-XssFilter
import org.apache.commons.lang.BooleanUtils; import org.apache.commons.lang.StringUtils; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @Auther: qianshanmuxue * @Date: 2019/2/27 16:25 * @Description: */ //@WebFilter //@Component 在这里可以不用这个注解,以为后面我们会在config中去配置这个filter,在这里只需要实现 Filter 接口实现相应的方法就ok public class XssFilter implements Filter { private static boolean IS_INCLUDE_RICH_TEXT = false;//用于接收配置中的参数,决定这个过滤器是否开启 public List<String> excludes = new ArrayList<String>();//用于接收配置中的参数,决定哪些是不需要过滤的url(在这里,也可以修改handleExcludeURL()方法中相应的代码,使其变更为只需要过滤的url) @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; if (handleExcludeURL(req, resp)) { chain.doFilter(request, response); return; } XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request); chain.doFilter(xssRequest, response); } /** *此方法是决定对当前url是否执行过滤, *在这里没有使用请求方法(post/put)来匹配,因为在本项目中使用url匹配更适合(因为get和其他请求方式也需要进行过滤),如果你有兴趣可以把这个方法更改为匹配请求方法进行过滤 **/ private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) { if ((excludes == null || excludes.isEmpty())&&IS_INCLUDE_RICH_TEXT) { 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; } /** *过滤器初始化,从配置类中获取参数,用于初始化两个参数(是否开启,排除指定的url list) * */ @Override public void init(FilterConfig arg0) throws ServletException { String isIncludeRichText = arg0.getInitParameter("isIncludeRichText"); if (StringUtils.isNotBlank(isIncludeRichText)) { IS_INCLUDE_RICH_TEXT = BooleanUtils.toBoolean(isIncludeRichText); } String temp = arg0.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.5filter的配置类:XssConfig
import com.xxx.filter.XssFilter; import com.google.common.collect.Maps; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.Map; /** * @Auther: qianshanmuxue * @Date: 2019/2/27 16:49 * @Description: xss filter config */ @Configuration public class XssConfig { @Bean public FilterRegistrationBean xssFilterRegistrationBean() { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); filterRegistrationBean.setFilter(new XssFilter()); filterRegistrationBean.setOrder(1);//filter order ,set it first filterRegistrationBean.setEnabled(true); filterRegistrationBean.addUrlPatterns("/*"); //set filter all url mapping Map<String, String> initParameters = Maps.newHashMap(); initParameters.put("excludes", "/oauth/token");///white list url initParameters.put("isIncludeRichText", "true");//enable or disable filterRegistrationBean.setInitParameters(initParameters); return filterRegistrationBean; } }
调试截图:
请求:
程序截图:
运行结果:
可以看到body中 的脚本已经被过滤了,
然后其他的截图我就不发了,还有一种思路就是在过滤器中把字符转义。
感谢luckpet大佬的提示
1 BufferedReader 使用完需要关闭 ;
2 对于一些拿postman等工具的朋友,拼接json的话会有换行 这里result += clean(line); 需要改成: while((line = br.readLine()) != null){ if (line != null) { result += line; } }
使用jsoup防止XSS攻击
前阵子项目国测后,打开一个项目页面,莫名其妙弹出xss,搜了全局也没找到alert("xss"),问了一下项目经理,原来是国测做防注入的时候,在添加数据的时候做的,一脸懵逼。
查了一下资料,以前做项目的时候都没想到这个问题,如果保存一段script脚本,查数据的时候,这段脚本就会被执行,这东西后果挺严重啊,如果是在桌面外弹框,执行个挖矿脚本,这玩意不得了啊,厉害,长知识了。。。
<dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.11.3</version> </dependency>
以上为个人经验,希望能给大家一个参考,也希望大家多多支持。
加载全部内容