摘要: 原创出处 网络 「网络」欢迎转载,保留摘要,谢谢!
1. XSS跨站脚本攻击
① XSS漏洞介绍
跨站脚本攻击XSS是指攻击者往Web页面里插入恶意Script代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被解析执行,从而达到恶意攻击用户的目的。XSS攻击针对的是用户层面的攻击!
② XSS漏洞分类
存储型XSS: 存储型XSS,持久化,代码是存储在服务器中的,如在个人信息或发表文章等地方,插入代码,如果没有过滤或过滤不严,那么这些代码将储存到服务器中,用户访问该页面的时候触发代码执行。这种XSS比较危险,容易造成蠕虫,盗窃cookie
反射型XSS: 非持久化,需要欺骗用户自己去点击链接才能触发XSS代码(服务器中没有这样的页面和内容),一般容易出现在搜索页面
DOM型XSS: 不经过后端,DOM-XSS漏洞是基于文档对象模型(Document Objeet Model
,DOM)的一种漏洞,DOM-XSS是通过url传入参数去控制触发的,其实也属于反射型XSS。
③ 防护建议
限制用户输入,表单数据规定值得类型,例如年龄只能是int,name为字母数字组合。
对数据进行html encode处理。
过滤或移除特殊的html标签。
过滤javascript事件的标签。
2. SQL注入攻击
① SQL注入漏洞介绍
SQL注入(SQLi)是一种注入攻击,可以执行恶意SQL语句。它通过将任意SQL代码插入数据库查询,使攻击者能够完全控制Web应用程序后面的数据库服务器。攻击者可以使用SQL注入漏洞绕过应用程序安全措施;可以绕过网页或Web应用程序的身份验证和授权,并检索整个SQL数据库的内容;还可以使用SQL注入来添加,修改和删除数据库中的记录;
SQL注入漏洞可能会影响使用SQL数据库(如MySQL,Oracle,SQL Server或其他)的任何网站或Web应用程序。犯罪分子可能会利用它来未经授权访问用户的敏感数据:客户信息,个人数据,商业机密,知识产权等。SQL注入攻击是最古老,最流行,最危险的Web应用程序漏洞之一。
②防护建议
使用mybatis中#{}
可以有效防止sql注入。
使用#{}
时:
<select id="getBlogById" resultType="Blog" parameterType=”int ”> select id,title,author,content from blog where id=#{id} </select>
打印出执行的sql语句,会看到sql是这样的:
select id,title,author,content from blog where id = ?
不管输入什么参数,打印出的sql都是这样的。这是因为mybatis启用了预编译功能,在sql执行前,会先将上面的sql发送给数据库进行编译,执行时,直接使用编译好的sql,替换占位符“?”就可以了。因为sql注入只能对编译过程起作用,所以像#{}这样预编译成?的方式就很好地避免了sql注入的问题。
mybatis是如何做到sql预编译的呢?
其实在框架底层,是jdbc中的PreparedStatement
类在起作用,PreparedStatement
是我们很熟悉的Statement的子类,它的对象包含了编译好的sql语句。这种“准备好”的方式不仅能提高安全性,而且在多次执行一个sql时,能够提高效率,原因是sql已编译好,再次执行时无需再编译。
使用${}
时
<select id="orderBlog" resultType="Blog" parameterType=”map”> select id,title,author,content from blog order by ${orderParam} </select>
仔细观察,内联参数的格式由“#{xxx}
”变为了${xxx}
。如果我们给参数“orderParam
”赋值为”id”,将sql打印出来,是这样的:
select id,title,author,contet from blog order by id
显然,这样是无法阻止sql注入的,参数会直接参与sql编译,从而不能避免注入攻击。但涉及到动态表名和列名时,只能使用“${}
”这样的参数格式,所以,这样的参数需要我们在代码中手工进行处理来防止注入。
其实,这些都在Java面试库小程序上都有答案,如果你近期准备面试跳槽,建议在上面刷题,涵盖 2000+ 道 Java 面试题,几乎覆盖了所有主流技术面试题。
3. SpringBoot中如何防止XSS攻击和sql注入
对于Xss攻击和Sql注入,我们可以通过过滤器来搞定,可根据业务需要排除部分请求
① 创建Xss请求过滤类XssHttpServletRequestWraper
代码如下:
public class XssHttpServletRequestWraper extends HttpServletRequestWrapper { Logger log = LoggerFactory.getLogger(this .getClass()); public XssHttpServletRequestWraper () { super (null ); } public XssHttpServletRequestWraper (HttpServletRequest httpservletrequest) { super (httpservletrequest); } public String[] getParameterValues(String s) { String str[] = super .getParameterValues(s); if (str == null ) { return null ; } int i = str.length; String as1[] = new String[i]; for (int j = 0 ; j < i; j++) { as1[j] = cleanXSS(cleanSQLInject(str[j])); } log.info("XssHttpServletRequestWraper净化后的请求为:==========" + as1); return as1; } public String getParameter (String s) { String s1 = super .getParameter(s); if (s1 == null ) { return null ; } else { String s2 = cleanXSS(cleanSQLInject(s1)); log.info("XssHttpServletRequestWraper净化后的请求为:==========" + s2); return s2; } } @Override public ServletInputStream getInputStream () throws IOException { final ByteArrayInputStream bais = new ByteArrayInputStream(inputHandlers(super .getInputStream ()).getBytes ()); return new ServletInputStream() { @Override public int read () throws IOException { return bais.read(); } @Override public boolean isFinished () { return false ; } @Override public boolean isReady () { return false ; } @Override public void setReadListener (ReadListener readListener) { } }; } public String inputHandlers (ServletInputStream servletInputStream) { StringBuilder sb = new StringBuilder(); BufferedReader reader = null ; try { reader = new BufferedReader(new InputStreamReader(servletInputStream, Charset.forName("UTF-8" ))); String line = "" ; while ((line = reader.readLine()) != null ) { sb.append(line); } } catch (IOException e) { e.printStackTrace(); } finally { if (servletInputStream != null ) { try { servletInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (reader != null ) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } return cleanXSS(sb.toString ()); } public String cleanXSS (String src) { String temp = src; src = src.replaceAll("<" , "<" ).replaceAll(">" , ">" ); src = src.replaceAll("\\(" , "(" ).replaceAll("\\)" , ")" ); src = src.replaceAll("'" , "'" ); src = src.replaceAll(";" , ";" ); src = src.replaceAll("<" , "& lt;" ).replaceAll(">" , "& gt;" ); src = src.replaceAll("\\(" , "& #40;" ).replaceAll("\\)" , "& #41" ); src = src.replaceAll("eval\\((.*)\\)" , "" ); src = src.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']" , "\"\"" ); src = src.replaceAll("script" , "" ); src = src.replaceAll("link" , "" ); src = src.replaceAll("frame" , "" ); Pattern pattern = Pattern.compile("(eval\\((.*)\\)|script)" , Pattern.CASE_INSENSITIVE); Matcher matcher = pattern.matcher(src); src = matcher.replaceAll("" ); pattern = Pattern.compile("[\\\"\\'][\\s]*javascript:(.*)[\\\"\\']" , Pattern.CASE_INSENSITIVE); matcher = pattern.matcher(src); src = matcher.replaceAll("\"\"" ); src = src.replaceAll("script" , "" ).replaceAll(";" , "" ) .replaceAll("0x0d" , "" ).replaceAll("0x0a" , "" ); if (!temp.equals(src)) { log.error("xss攻击检查:参数含有非法攻击字符,已禁止继续访问!!" ); log.error("原始输入信息-->" + temp); throw new CustomerException("xss攻击检查:参数含有非法攻击字符,已禁止继续访问!!" ); } return src; } public void outputMsgByOutputStream (HttpServletResponse response, String msg) throws IOException { ServletOutputStream outputStream = response.getOutputStream(); response.setHeader("content-type" , "text/html;charset=UTF-8" ); byte [] dataByteArr = msg.getBytes("UTF-8" ); outputStream.write(dataByteArr); } public String cleanSQLInject (String src) { String lowSrc = src.toLowerCase(); String temp = src; String lowSrcAfter = lowSrc.replaceAll("insert" , "forbidI" ) .replaceAll("select" , "forbidS" ) .replaceAll("update" , "forbidU" ) .replaceAll("delete" , "forbidD" ).replaceAll("and" , "forbidA" ) .replaceAll("or" , "forbidO" ); if (!lowSrcAfter.equals(lowSrc)) { log.error("sql注入检查:输入信息存在SQL攻击!" ); log.error("原始输入信息-->" + temp); log.error("处理后信息-->" + lowSrc); throw new CustomerException("sql注入检查:参数含有非法攻击字符,已禁止继续访问!!" ); } return src; } }
② 把请求过滤类XssHttpServletRequestWraper
添加到Filter中,注入容器
@Component public class XssFilter implements Filter { Logger log = LoggerFactory.getLogger(this .getClass()); private final String[] excludeUrls = new String[]{ "null" }; public void doFilter (ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) arg0; HttpServletResponse response = (HttpServletResponse) arg1; String pathInfo = req.getPathInfo() == null ? "" : req.getPathInfo(); String url = req.getServletPath() + pathInfo; String uri = req.getRequestURI(); XssHttpServletRequestWraper reqW = new XssHttpServletRequestWraper(req); for (String str : excludeUrls) { if (uri.indexOf(str) >= 0 ) { arg2.doFilter(arg0, response); return ; } } arg2.doFilter(reqW, response); } public void destroy () { } public void init (FilterConfig filterconfig1) throws ServletException { } }
上述代码已经可以完成 请求参数、JSON请求体 的过滤,但对于json请求体还有其他的方式实现,有兴趣的请看下面的扩展!
扩展:还可以重写spring中的MappingJackson2HttpMessageConverter
来过滤Json请求体
因为请求体在进出Contoroller时,会经过MappingJackson2HttpMessageConverter
的一个转换,把请求体转换成我们需要的json格式,所以可以在这里边做一些修改!
@Configuration public class MyConfiguration { @Bean public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter () { MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); ObjectMapper objectMapper = new ObjectMapper(); SimpleDateFormat smt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss" ); objectMapper.setDateFormat(smt); converter.setObjectMapper(objectMapper); SimpleModule simpleModule = new SimpleModule(); simpleModule.addDeserializer(String.class,new StringDeserializer()); converter.getObjectMapper().registerModule(simpleModule); List<MediaType> list = new ArrayList<>(); list.add(MediaType.APPLICATION_JSON_UTF8); converter.setSupportedMediaTypes(list); return converter; } }
真正的过滤逻辑类StringDeserializer
:
@Component public class StringDeserializer extends JsonDeserializer <String > { @Override public String deserialize (JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { String str = jsonParser.getText().trim(); if (sqlInject(str)) { throw new CustomerException("参数含有非法攻击字符,已禁止继续访问!" ); } return xssClean(str); } public boolean sqlInject (String str) { if (StringUtils.isEmpty(str)) { return false ; } str = org.apache.commons.lang3.StringUtils.replace(str, "'" , "" ); str = org.apache.commons.lang3.StringUtils.replace(str, "\"" , "" ); str = org.apache.commons.lang3.StringUtils.replace(str, ";" , "" ); str = org.apache.commons.lang3.StringUtils.replace(str, "\\" , "" ); str = str.toLowerCase(); String[] keywords = {"master" , "truncate" , "insert" , "select" , "delete" , "update" , "declare" , "alert" ,"alter" , "drop" }; for (String keyword : keywords) { if (str.indexOf(keyword) != -1 ) { return true ; } } return false ; } public String xssClean (String value) { if (value == null || "" .equals(value)) { return value; } String[] keywords = {"<" , ">" , "<>" , "()" , ")" , "(" , "javascript:" , "script" ,"alter" , "''" ,"'" }; for (String keyword : keywords) { if (value.indexOf(keyword) != -1 ) { throw new CustomerException("参数含有非法攻击字符,已禁止继续访问!" ); } } return value; } }
使用这种形式也可以完成json请求体的过滤,但个人更推荐使用XssHttpServletRequestWraper
的形式来完成xss过滤!!