源码漏洞扫描

目录

扫描工具

高危

1. SQL注入

1.1. 预编译

1.2. 参数校验

1.3. SQL语句校验

2. XSS漏洞

2.1 过滤器

中危

1. 代码质量:比较Locale相关的数据未指定适当的Locale

2. 代码质量:使用==或!=比较基本数据类型的包装类

3. 输入验证:日志伪造

4. 密码管理:配置文件中的明文密码

5. 密码管理:硬编码加密密钥

6. 资源管理:资源未释放:流

7. 敏感信息泄漏


扫描工具

奇安信全卫士

高危

1. SQL注入

1.1. 预编译

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注入。  

1.2. 参数校验

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)";

1.3. SQL语句校验

如果获取到的是一个完整的SQL语句,那么也需要对完整的SQL语句做一个校验。

如下正则是校验一个查询语句中是否包含了其他敏感操作,可根据实际情况增减。

/** 
* 是否为查询语句正则 
*/ 
public static final String SQL_REX = "(?i)^(\\s*)(select)(\\s+)(((?!([\\s\\t\\n]|[(]+)(insert|delete|update|drop|alter|execute|declare|exec)(\\s+)).)+)$";

2. XSS漏洞

XSS跨站脚本攻击,指的是恶意攻击者往Web页面里插入恶意html代码分为反射型XSS和存储型XSS

实质的区别是反射型的风险来源是web请求的数据,存储型是数据库查出来的数据包含风险。

由此看来,如果从每个接口的入参出手,对每个参数进行校验、过滤、编码,那么就能从实质上解决反射型和存储型数据所附带的XSS风险操作。

实际拦截器并不能完全防止XSS,因为存储型的XSS,数据中存储的数据不仅仅通过web接口增加,所以其实想要完全避免存储型的漏洞,还需要对接口响应数据进行过滤。可以通过实现ResponseBodyAdvice<T>接口实现。

2.1 过滤器

在过滤器中,进行如下操作:

  • 对特殊字符(如`<、>、'、"`以及`<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;
    }


}

中危

1. 代码质量:比较Locale相关的数据未指定适当的Locale

String.toUpperCase()和String.toLowerCase()应指定相应的Locale。

将字符转换为大写字符。在英文Locale中,会将`title`转换为`TITLE`;在土耳其Locale中,会将`title`转换为`T?TLE`,其中的`?`是拉丁字母的`I`。

"aaa".toUpperCase(Locale.ENGLISH)

2. 代码质量:使用==或!=比较基本数据类型的包装类

不能直接使用`==`或`!=`操作符来比较的两个基本数据类型的包装类型的值,因为这些操作符比较的是对象的引用而不是对象的值。

不过由于Java的缓存机制,所以如果基本类型的包装类是一个整数且在-128和127之间,或是布尔类型true或false,或者是'\u0000'和'\u007f'之间的字符文本,可以使用`==`或`!=`进行比较。也就是说,如果使用了基本类型的包装类型(除去Boolean或Byte),则会缓存或记住一个值区间。对于值区间内的值,使用`==`或`!=`会返回正确的值,而对于值区间外的值,将返回对象地址的比较结果。

3. 输入验证:日志伪造

允许日志记录未经验证的用户输入,会导致日志伪造攻击。一般日志伪造出现的原因是,日志文件中存在一些修改linux系统的敏感操作的指令。这些指令一般都需要另起一行,所以一般包含换行符。所以对输出日志进行编码,即可防止此种情况发生。

//对字符串进行编码 
StringEscapeUtils.escapeJavaScript(str);

4. 密码管理:配置文件中的明文密码

配置文件中采用明文存储密码,所有能够访问该文件的人都能访问该密码,将会降低系统安全性。

使用jasypt加密工具将配置文件中的敏感信息加密。

<dependency> 
    <groupId>org.jasypt</groupId> 
    <artifactId>jasypt</artifactId>
    <version>需要依赖的版本</version> 
</dependency>

5. 密码管理:硬编码加密密钥

常量属性名避免带key,使用的话最好将值进行加密。

6. 资源管理:资源未释放:流

程序创建或分配流资源后,不进行合理释放,将会降低系统性能。攻击者可能会通过耗尽资源池的方式发起拒绝服务攻击。

//jdk1.8之后提供,try(){}可以自动关闭资源,防止忘记关闭资源 
try (InputStream inputStream = new FileInputStream("file")){
 
}catch(){ 

}

7. 敏感信息泄漏

常量的参数名尽量不要使用username、password、pwd、addressee、name等敏感字段,会被漏扫工具监测到敏感信息泄漏。如果存在的话,需要对字段的值进行加密,在使用的时候进行解密。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值