目录
1. 代码质量:比较Locale相关的数据未指定适当的Locale
奇安信全卫士
JdbcTemplate,拼接SQL语句,如果有参数,尽量使用query(String sql, Object[] args)等方法,参数使用占位符?拼接,这些方法底层都使用了预编译。
Mybatis框架,$会引发SQL注入的风险。
#{}实质就是使用了预编译防止SQL注入,使用替换占位符?的方式,如果参数中存在注入到语句,也会被当作参数的一部分,而不会被执行。
- select * from user where user_name = ${userName}
- select * from user where user_name = #{userName}
如果userName参数为【"张三" or 1=1】,两种符号最终的SQL会不一样。
- -- 最终会查出全量数据
- select * from user where user_name = "张三" or 1=1
- -- 查出user_name为【"张三" or 1=1】的user
- select * from user where user_name = '"张三" or 1=1'
好处:
SQL会首先编译成数据库可以识别的语句,同时预编译一次,如果多次执行,节省编译时间。
SQL中参数使用占位符,防止SQL注入。
Mybatis框架,有些情况下无法使用#{},只能使用${},比如说参数是表名称、字段名称、备注等,无法使用#{},这种情况需要对参数进行校验,防止存在SQL注入的风险。
下面的正则会校验是否存在某些敏感操作的关键字,可根据情况适当增减。
static String reg = "(?:')|(?:--)|(/\\*(?:.|[\\n\\r])*?\\*/)|" + "(\\b(select|update|and|or|delete|insert|trancate|char|into|substr|ascii|declare|exec|count|master|into|drop|execute)\\b)";
如果获取到的是一个完整的SQL语句,那么也需要对完整的SQL语句做一个校验。
如下正则是校验一个查询语句中是否包含了其他敏感操作,可根据实际情况增减。
- /**
- * 是否为查询语句正则
- */
- public static final String SQL_REX = "(?i)^(\\s*)(select)(\\s+)(((?!([\\s\\t\\n]|[(]+)(insert|delete|update|drop|alter|execute|declare|exec)(\\s+)).)+)$";
XSS跨站脚本攻击,指的是恶意攻击者往Web页面里插入恶意html代码。分为反射型XSS和存储型XSS。
实质的区别是反射型的风险来源是web请求的数据,存储型是数据库查出来的数据包含风险。
由此看来,如果从每个接口的入参出手,对每个参数进行校验、过滤、编码,那么就能从实质上解决反射型和存储型数据所附带的XSS风险操作。
实际拦截器并不能完全防止XSS,因为存储型的XSS,数据中存储的数据不仅仅通过web接口增加,所以其实想要完全避免存储型的漏洞,还需要对接口响应数据进行过滤。可以通过实现ResponseBodyAdvice<T>接口实现。
在过滤器中,进行如下操作:
对特殊字符(如`<、>、'、"`以及`<script>、javascript`等进行过滤。
根据数据将要置于HTML上下文中的不同位置(HTML标签、HTML属性、JavaScript脚本、CSS、URL),对所有不可信数据进行恰当的输出编码。
设置HttpOnly属性,避免攻击者利用跨站脚本漏洞进行Cookie劫持攻击.
具体实现原理:
重写HttpServletRequestWrapper,重写getParameter(),getParameterValues(),getHeader(),getHeaders()在这几个方法中,对参数进行过滤。
创建过滤器覆盖原来的HttpServletRequest
Response设置HttpOnly属性,避免攻击者利用跨站脚本漏洞进行Cookie劫持攻击
相关代码
- /**
- * 过滤器
- */
- @Slf4j
- public class HttpRequestFilter implements Filter {
-
- @Override
- public void init(FilterConfig filterConfig) throws ServletException {
-
- }
-
- @SneakyThrows
- @Override
- public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
- HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
- HttpServletResponse response = (HttpServletResponse) servletResponse;
-
- response.setHeader("Set-Cookie","cookiename=cookievalue; path=/; Domain=domainvaule; Max-age=seconds; HttpOnly");
- ParamHttpServletRequestWrapper request = new ParamHttpServletRequestWrapper(httpServletRequest);
-
- chain.doFilter(request, response);
- }
-
- @Override
- public void destroy() {
-
- }
- }
- /**
- * HttpServletRequestWrapper装饰类
- */
- @Slf4j
- public class ParamHttpServletRequestWrapper extends HttpServletRequestWrapper {
- HttpServletRequest orgRequest = null;
- private String currentUrl;
- private Map<String, String[]> parameterMap;
- private Map<String, String> headerMap;
-
- public ParamHttpServletRequestWrapper(HttpServletRequest request) {
- super(request);
- orgRequest = request;
- this.setCurrentUrl(request.getRequestURI());
- parameterMap = request.getParameterMap();
- headerMap = new HashMap<>();
- }
-
- private void setCurrentUrl(String url){
- this.currentUrl = url;
- }
-
- public String getCurrentUrl(){
- return this.currentUrl;
- }
-
- /**
- * 获取原始的request
- * @return
- */
- public HttpServletRequest getOrgRequest() {
- return orgRequest;
- }
-
- /**
- * 获取最原始的request的静态方法
- * @return
- */
- public static HttpServletRequest getOrgRequest(HttpServletRequest request) {
- if (request instanceof ParamHttpServletRequestWrapper) {
- return ((ParamHttpServletRequestWrapper) request).getOrgRequest();
- }
- return request;
- }
-
- /**
- * 获取所有参数名
- * @return 返回所有参数名
- */
- @Override
- public Enumeration<String> getParameterNames() {
- Vector<String> vector = new Vector<>(parameterMap.keySet());
- return vector.elements();
- }
-
-
- /**
- * 覆盖getParameter方法,将参数名和参数值都做xss & sql过滤。<br/>
- * 如果需要获得原始的值,则通过super.getParameterValues(name)来获取<br/>
- * getParameterNames,getParameterValues和getParameterMap也可能需要覆盖
- */
- @Override
- public String getParameter(String name) {
- String[] results = parameterMap.get(name);
- if (results == null || results.length <= 0)
- return null;
- else {
- String value = results[0];
- return XssEncodeUtil.stripXSS(value);
- }
- }
-
- /**
- * 获取指定参数名的所有值的数组,如:checkbox的所有数据 接收数组变量 ,如checkobx类型
- */
- @Override
- public String[] getParameterValues(String name) {
- String[] results = parameterMap.get(name);
- if (results == null || results.length <= 0)
- return null;
- else {
- int length = results.length;
- for (int i = 0; i < length; i++) {
- results[i] = XssEncodeUtil.stripXSS(results[i]);
- }
- return results;
- }
- }
-
- public void addHeader(String name, String value) {
- this.headerMap.put(name, value);
- }
-
- /**
- * 覆盖getHeader方法,将参数名和参数值都做xss & sql过滤。<br/>
- * 如果需要获得原始的值,则通过super.getHeaders(name)来获取<br/>
- * getHeaderNames 也可能需要覆盖
- */
- @Override
- public String getHeader(String name) {
- String headerValue = headerMap.get(name);
- if (headerValue != null) {
- return XssEncodeUtil.stripXSS(headerValue);
- }
- return XssEncodeUtil.stripXSS(super.getHeader(name));
- }
-
- @Override
- public Enumeration<String> getHeaders(String name) {
- ArrayList<String> values = Collections.list(super.getHeaders(name));
- if (headerMap.containsKey(name)) {
- values.add(headerMap.get(name));
- }
- return Collections.enumeration(values);
- }
-
- @Override
- public BufferedReader getReader() throws IOException {
- return new BufferedReader(new InputStreamReader(getInputStream()));
- }
-
- @Override
- public ServletInputStream getInputStream() throws IOException {
- return super.getInputStream();
- }
-
-
-
- }
- /**
- * ESAPI工具类,防止xss,SQL注入等漏洞
- */
- public class XssEncodeUtil {
-
- public static String encodeForHTML(String str){
- return StringEscapeUtils.escapeHtml(str);
- }
-
- public static String encodeForJavaScript(String str){
- return StringEscapeUtils.escapeJavaScript(str);
- }
-
- public static String encodeForSQLParam(String param){
- return StringEscapeUtils.escapeSql(param);
- }
-
- /**
- * 防止xss跨脚本攻击(ESAPI编码)
- */
- public static String stripXSS(String value) {
- if (StringUtils.isNotEmpty(value)) {
- Pattern scriptPattern = Pattern.compile(
- "<[\r\n| | ]*script[\r\n| | ]*>(.*?)</[\r\n| | ]*script[\r\n| | ]*>", Pattern.CASE_INSENSITIVE);
- if(scriptPattern.matcher(value).find()){
- return encodeForHTML(value);
- }
- scriptPattern = Pattern.compile("src[\r\n| | ]*=[\r\n| | ]*[\\\"|\\\'](.*?)[\\\"|\\\']",
- Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
- if(scriptPattern.matcher(value).find()){
- return encodeForHTML(value);
- }
- // Remove any lonesome </script> tag
- scriptPattern = Pattern.compile("</[\r\n| | ]*script[\r\n| | ]*>", Pattern.CASE_INSENSITIVE);
- if(scriptPattern.matcher(value).find()){
- return encodeForHTML(value);
- }
- // Remove any lonesome <script ...> tag
- scriptPattern = Pattern.compile("<[\r\n| | ]*script(.*?)>",
- Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
- if(scriptPattern.matcher(value).find()){
- return encodeForHTML(value);
- }
- // Avoid eval(...) expressions
- scriptPattern = Pattern.compile("eval\\((.*?)\\)",
- Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
- if(scriptPattern.matcher(value).find()){
- return encodeForHTML(value);
- }
- // Avoid e-xpression(...) expressions
- scriptPattern = Pattern.compile("e-xpression\\((.*?)\\)",
- Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
- if(scriptPattern.matcher(value).find()){
- return encodeForHTML(value);
- }
- // Avoid javascript:... expressions
- scriptPattern = Pattern.compile("javascript[\r\n| | ]*:[\r\n| | ]*", Pattern.CASE_INSENSITIVE);
- if(scriptPattern.matcher(value).find()){
- return encodeForJavaScript(value);
- }
- // Avoid vbscript:... expressions
- scriptPattern = Pattern.compile("vbscript[\r\n| | ]*:[\r\n| | ]*", Pattern.CASE_INSENSITIVE);
- if(scriptPattern.matcher(value).find()){
- return encodeForJavaScript(value);
- }
- // Avoid onload= expressions
- scriptPattern = Pattern.compile("onload(.*?)=",
- Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
- if(scriptPattern.matcher(value).find()){
- return encodeForHTML(value);
- }
-
- }
- return value;
- }
-
-
- }
String.toUpperCase()和String.toLowerCase()应指定相应的Locale。
将字符转换为大写字符。在英文Locale中,会将`title`转换为`TITLE`;在土耳其Locale中,会将`title`转换为`T?TLE`,其中的`?`是拉丁字母的`I`。
"aaa".toUpperCase(Locale.ENGLISH)
不能直接使用`==`或`!=`操作符来比较的两个基本数据类型的包装类型的值,因为这些操作符比较的是对象的引用而不是对象的值。
不过由于Java的缓存机制,所以如果基本类型的包装类是一个整数且在-128和127之间,或是布尔类型true或false,或者是'\u0000'和'\u007f'之间的字符文本,可以使用`==`或`!=`进行比较。也就是说,如果使用了基本类型的包装类型(除去Boolean或Byte),则会缓存或记住一个值区间。对于值区间内的值,使用`==`或`!=`会返回正确的值,而对于值区间外的值,将返回对象地址的比较结果。
允许日志记录未经验证的用户输入,会导致日志伪造攻击。一般日志伪造出现的原因是,日志文件中存在一些修改linux系统的敏感操作的指令。这些指令一般都需要另起一行,所以一般包含换行符。所以对输出日志进行编码,即可防止此种情况发生。
- //对字符串进行编码
- StringEscapeUtils.escapeJavaScript(str);
配置文件中采用明文存储密码,所有能够访问该文件的人都能访问该密码,将会降低系统安全性。
使用jasypt加密工具将配置文件中的敏感信息加密。
- <dependency>
- <groupId>org.jasypt</groupId>
- <artifactId>jasypt</artifactId>
- <version>需要依赖的版本</version>
- </dependency>
常量属性名避免带key,使用的话最好将值进行加密。
程序创建或分配流资源后,不进行合理释放,将会降低系统性能。攻击者可能会通过耗尽资源池的方式发起拒绝服务攻击。
- //jdk1.8之后提供,try(){}可以自动关闭资源,防止忘记关闭资源
- try (InputStream inputStream = new FileInputStream("file")){
-
- }catch(){
-
- }
常量的参数名尽量不要使用username、password、pwd、addressee、name等敏感字段,会被漏扫工具监测到敏感信息泄漏。如果存在的话,需要对字段的值进行加密,在使用的时候进行解密。