赞
踩
一个Springboot+MyBatis+Redis+MySQL的辣鸡小项目。奈何再小的项目也需要保证安全,今天提交给测试部门做渗透测试,打回来两个网络安全漏洞,网上有很多“模糊”的修改办法,我们来看看具体怎么修补吧。
不安全的HTTP方法一般包括:TRACE、PUT、DELETE、COPY 等。其中最常见的为TRACE方法可以回显服务器收到的请求,主要用于测试或诊断,恶意攻击者可以利用该方法进行跨站跟踪攻击(即XST攻击),从而进行网站钓鱼、盗取管理员cookie等。其他说明方式如表所示:
名称 | 介绍 |
---|---|
PUT | 向指定的目录上传文件 |
DELETE | 删除指定的资源 |
COPY | 将特定的资源复制到特定位置 |
SEARCH | 在一个目录路径中搜索资源 |
MOVE | 将特定资源移动到特定位置 |
PROPFIND | 获取与指定资源有关的信息 |
OPTION | 预检请求,可用于检测服务器允许的http方法,和跨域有关 |
我被检测到的是存在OPTION请求,应该是仅允许GET,POST,HEAD就没事。
当应用程序的新网页中包含不受信任的、未经恰当验证或转义的数据时,或者使用可以创建 HTML或JavaScript 的浏览器 API 更新现有的网页时,就会出现 XSS 缺陷。XSS 让攻击者能够在受害者的浏览器中执行脚本,并劫持用户会话、破坏网站或将用户重定向到恶意站点。
已知的跨站脚本攻击漏洞有三种:
1、存储型跨站脚本攻击:用户输入的文本信息保存到数据库中,并能够在页面展示的功能点,例如用户留言、发送站内消息、个人信息修改等功能点;
2、反射型跨站脚本攻击:URL参数需要在页面显示的功能点都可能存在反射型跨站脚本攻击,例如站内搜索、查询功能点;
3、基于DOM跨站脚本攻击:涉及DOM对象的页面程序,包括但不限于:document.URL、document.URLUnencoded、document.location、document.referrer、window.location。
检测到我的项目应该是不能过滤敏感字符,例如参数中包含script、img、src等等
在Springboot项目的启动类中,增加一个方法即可,如下:
addMethod是屏蔽的方法。
//拦截不安全的http请求 @Bean public ConfigurableServletWebServerFactory configurableServletWebServerFactory() { TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(); factory.addContextCustomizers(context -> { SecurityConstraint securityConstraint = new SecurityConstraint(); securityConstraint.setUserConstraint("CONFIDENTIAL"); SecurityCollection collection = new SecurityCollection(); collection.addPattern("/*"); collection.addMethod("PUT"); collection.addMethod("DELETE"); collection.addMethod("TRACE"); collection.addMethod("OPTIONS"); collection.addMethod("PATCH"); collection.addMethod("COPY"); collection.addMethod("SEARCH"); collection.addMethod("PROPFIND"); securityConstraint.addCollection(collection); context.addConstraint(securityConstraint); }); return factory; }
加完后如何测试?
下载一个postman插件并安装(安装包网上很多,解压后,双击.exe文件就能安装),在method选择你要测试的方法即可。
先列网上的解决方案,我只是将敏感字符都过滤掉,并非最终办法。
1.对用户输入的数据进行严格过滤,包括但不限于以下字符及字符串
Javascript script src img { } ( ) < > = , . ; : " ‘ # ! / * \ `
2. 根据页面的输出背景环境,对输出进行编码;
3. 建议使用一个统一的规则库,对用户的所有输入进行安全性验证,验证不通过应直接拒绝用户的请求;
4. 对于富文本框,使用白名单控制输入,而不是黑名单;
5. 在Cookie 上设置HTTPOnly 标志,从而禁止客户端脚本访问Cookie。
建立一个过滤器,把敏感字符都过滤掉,这里先列一下可能存在的敏感字符:
String badStr = "|script|src|img|onerror|{|}|'|*|$|\"|<|>|javascript|(|)|=|,|:|!|`|.";
在启动类的上加上注解,文件位置:
@ServletComponentScan("com.xx.filter.safeFilter")
最后建立一个SafeFilter.java
package com.ouma.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; import java.util.Enumeration; import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @Slf4j @Component @WebFilter(value = "/filter") public class safeFilter implements Filter { @Override public void destroy() { // TODO Auto-generated method stub } @Override public void init(FilterConfig arg0) throws ServletException { // TODO Auto-generated method stub } @Override public void doFilter(ServletRequest args0, ServletResponse args1, FilterChain chain) throws ServletException, IOException { HttpServletRequest req=(HttpServletRequest)args0; HttpServletResponse res=(HttpServletResponse)args1; //获得所有请求参数名 Enumeration params = req.getParameterNames(); //获取url的 String sql = ""; String url=req.getRequestURI().replaceAll("\\\\","/").toLowerCase(); System.out.println(url); String method=""; String action=""; while (params.hasMoreElements()) { //得到参数名 String name = params.nextElement().toString(); System.out.println("name===========================" + name + "--"); //得到参数对应值 String[] value = req.getParameterValues(name); for (int i = 0; i < value.length; i++) { sql = sql + value[i]; } if(name.equalsIgnoreCase("method")){ method=value[0]; } if(name.equalsIgnoreCase("action")){ action=value[0]; } } if( ("".equals(method)&&(!"".equals(action)||url.indexOf(".do")>=0))){ //csrf跨站攻击 String referer = req.getHeader("referer"); System.err.println(referer); //if (referer == null || !referer.contains(req.getServerName())) { if (referer != null && !referer.contains(req.getServerName())) { System.out.println("【管理端refer校验未通过】referer = "+referer+" ,未包含"+req.getServerName()); //如果 链接地址来自其他网站,则返回错误页面 req.setAttribute("retInfo","疑似跨站伪造请求!"); req.getRequestDispatcher("/WEB-INF/jsp/error.jsp").forward(req, res); } } //有sql关键字,跳转到error.html if (sqlValidate(sql)) { System.out.println("sql error"); req.setAttribute("retInfo","您发送请求中的参数中含有非法字符!"); req.getRequestDispatcher("/WEB-INF/jsp/error.jsp").forward(req, res); //throw new IOException("您发送请求中的参数中含有非法字符!"+sql); } else { res.setHeader("Set-Cookie", "Secrue; HTTPOnly; "); chain.doFilter(args0,args1); } } //效验 protected static boolean sqlValidate(String str) { str = str.toLowerCase();//统一转为小写 // String badStr = "'|select|update|and|or|delete|insert|truncate|char|into" // + "|substr|declare|exec|master|drop|execute|" // + "union|;|--|+|,|like|//|/|%|#|*|$|\"|http|cr|lf|<|>|(|)";//过滤掉的sql关键字,可以手动添加 // String badStr = "'|;|--|+|//|/|%|#|*|$|\"|cr|lf|<|>|(|)";//过滤掉的sql关键字,可以手动添加 //String badStr = "script|src|img|onerror|{|}|'|%|*|$|\"|<|>";//过滤掉的sql关键字,可以手动添加 String badStr = "";//过滤掉的sql关键字,可以手动添加 badStr = "'|and|exec|execute|insert|select|delete|update|count|drop|*|chr|mid|master|%25|truncate|" + "char|declare|sitename|net user|xp_cmdshell|;|or|-|+|,|like'|and|exec|execute|insert|create|drop|" + "table|from|grant|use|group_concat|column_name|" + "information_schema.columns|table_schema|union|where|select|delete|update|order|by|count|*|" + "chr|mid|master|truncate|char|declare|or|;|-|--|+|,|like|//|/|#"; badStr += "|script|src|img|onerror|{|}|'|*|$|\"|<|>|javascript|(|)|=|,|:|!|`|."; String[] badStrs = badStr.split("\\|"); for (int i = 0; i < badStrs.length; i++) { if (str.indexOf(badStrs[i]) >= 0) { System.out.println("【管理端特殊字符校验未通过】当前解析串 :"+str+" ,其中未通过校验字符:"+badStrs[i]); return true; } } return false; } }
就大功告成啦。
不过不能得意太早,这个过滤器可能把业务中的某些参数给过滤掉,加完这个过滤器一定要仔细测试系统功能是否被拦截到了。
20201224更新,高兴早了,测试部门又通过双重URL编码给我XSS攻击了。
例如输入:%25%33%63,意为:%0c,再次编码意为:<。这样又把尖括号注入进来了。所有的编码在最后贴一下。
在上面的过滤器类的方法中增加如下代码:
str = convertPercent(str);
str = URLDecoder.decode(str);
调用以下方法convertPercent:这个方法是过滤只输入%的情况
//判断是否为16进制数 public static boolean isHex(char c){ if(((c >= '0') && (c <= '9')) || ((c >= 'a') && (c <= 'f')) || ((c >= 'A') && (c <= 'F'))) return true; else return false; } public static String convertPercent(String str){ StringBuilder sb = new StringBuilder(str); for(int i = 0; i < sb.length(); i++){ char c = sb.charAt(i); //判断是否为转码符号% if(c == '%'){ if(((i + 1) < sb.length() -1) && ((i + 2) < sb.length() - 1)){ char first = sb.charAt(i + 1); char second = sb.charAt(i + 2); //如只是普通的%则转为%25 if(!(isHex(first) && isHex(second))) sb.insert(i+1, "25"); } else{//如只是普通的%则转为%25 sb.insert(i+1, "25"); } } } return sb.toString(); }
关于所有的URL编码:https://blog.csdn.net/weixin_34409357/article/details/91565794
链接: 关于所有的URL编码
关于如何在前端防止XSS攻击:https://segmentfault.com/a/1190000016551188
链接: 关于如何在前端防止XSS攻击
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。