• 源码漏洞扫描


    目录

    扫描工具

    高危

    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注入,使用替换占位符?的方式,如果参数中存在注入到语句,也会被当作参数的一部分,而不会被执行。

    1. select * from user where user_name = ${userName}
    2. select * from user where user_name = #{userName}

    如果userName参数为【"张三" or 1=1】,两种符号最终的SQL会不一样。

    1. -- 最终会查出全量数据
    2. select * from user where user_name = "张三" or 1=1
    3. -- 查出user_name为【"张三" or 1=1】的user
    4. 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语句做一个校验。

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

    1. /**
    2. * 是否为查询语句正则
    3. */
    4. 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劫持攻击

    相关代码

    1. /**
    2. * 过滤器
    3. */
    4. @Slf4j
    5. public class HttpRequestFilter implements Filter {
    6. @Override
    7. public void init(FilterConfig filterConfig) throws ServletException {
    8. }
    9. @SneakyThrows
    10. @Override
    11. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
    12. HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
    13. HttpServletResponse response = (HttpServletResponse) servletResponse;
    14. response.setHeader("Set-Cookie","cookiename=cookievalue; path=/; Domain=domainvaule; Max-age=seconds; HttpOnly");
    15. ParamHttpServletRequestWrapper request = new ParamHttpServletRequestWrapper(httpServletRequest);
    16. chain.doFilter(request, response);
    17. }
    18. @Override
    19. public void destroy() {
    20. }
    21. }
     
    
    1. /**
    2. * HttpServletRequestWrapper装饰类
    3. */
    4. @Slf4j
    5. public class ParamHttpServletRequestWrapper extends HttpServletRequestWrapper {
    6. HttpServletRequest orgRequest = null;
    7. private String currentUrl;
    8. private Map<String, String[]> parameterMap;
    9. private Map<String, String> headerMap;
    10. public ParamHttpServletRequestWrapper(HttpServletRequest request) {
    11. super(request);
    12. orgRequest = request;
    13. this.setCurrentUrl(request.getRequestURI());
    14. parameterMap = request.getParameterMap();
    15. headerMap = new HashMap<>();
    16. }
    17. private void setCurrentUrl(String url){
    18. this.currentUrl = url;
    19. }
    20. public String getCurrentUrl(){
    21. return this.currentUrl;
    22. }
    23. /**
    24. * 获取原始的request
    25. * @return
    26. */
    27. public HttpServletRequest getOrgRequest() {
    28. return orgRequest;
    29. }
    30. /**
    31. * 获取最原始的request的静态方法
    32. * @return
    33. */
    34. public static HttpServletRequest getOrgRequest(HttpServletRequest request) {
    35. if (request instanceof ParamHttpServletRequestWrapper) {
    36. return ((ParamHttpServletRequestWrapper) request).getOrgRequest();
    37. }
    38. return request;
    39. }
    40. /**
    41. * 获取所有参数名
    42. * @return 返回所有参数名
    43. */
    44. @Override
    45. public Enumeration<String> getParameterNames() {
    46. Vector<String> vector = new Vector<>(parameterMap.keySet());
    47. return vector.elements();
    48. }
    49. /**
    50. * 覆盖getParameter方法,将参数名和参数值都做xss & sql过滤。<br/>
    51. * 如果需要获得原始的值,则通过super.getParameterValues(name)来获取<br/>
    52. * getParameterNames,getParameterValues和getParameterMap也可能需要覆盖
    53. */
    54. @Override
    55. public String getParameter(String name) {
    56. String[] results = parameterMap.get(name);
    57. if (results == null || results.length <= 0)
    58. return null;
    59. else {
    60. String value = results[0];
    61. return XssEncodeUtil.stripXSS(value);
    62. }
    63. }
    64. /**
    65. * 获取指定参数名的所有值的数组,如:checkbox的所有数据 接收数组变量 ,如checkobx类型
    66. */
    67. @Override
    68. public String[] getParameterValues(String name) {
    69. String[] results = parameterMap.get(name);
    70. if (results == null || results.length <= 0)
    71. return null;
    72. else {
    73. int length = results.length;
    74. for (int i = 0; i < length; i++) {
    75. results[i] = XssEncodeUtil.stripXSS(results[i]);
    76. }
    77. return results;
    78. }
    79. }
    80. public void addHeader(String name, String value) {
    81. this.headerMap.put(name, value);
    82. }
    83. /**
    84. * 覆盖getHeader方法,将参数名和参数值都做xss & sql过滤。<br/>
    85. * 如果需要获得原始的值,则通过super.getHeaders(name)来获取<br/>
    86. * getHeaderNames 也可能需要覆盖
    87. */
    88. @Override
    89. public String getHeader(String name) {
    90. String headerValue = headerMap.get(name);
    91. if (headerValue != null) {
    92. return XssEncodeUtil.stripXSS(headerValue);
    93. }
    94. return XssEncodeUtil.stripXSS(super.getHeader(name));
    95. }
    96. @Override
    97. public Enumeration<String> getHeaders(String name) {
    98. ArrayList<String> values = Collections.list(super.getHeaders(name));
    99. if (headerMap.containsKey(name)) {
    100. values.add(headerMap.get(name));
    101. }
    102. return Collections.enumeration(values);
    103. }
    104. @Override
    105. public BufferedReader getReader() throws IOException {
    106. return new BufferedReader(new InputStreamReader(getInputStream()));
    107. }
    108. @Override
    109. public ServletInputStream getInputStream() throws IOException {
    110. return super.getInputStream();
    111. }
    112. }
     
    
    1. /**
    2. * ESAPI工具类,防止xss,SQL注入等漏洞
    3. */
    4. public class XssEncodeUtil {
    5. public static String encodeForHTML(String str){
    6. return StringEscapeUtils.escapeHtml(str);
    7. }
    8. public static String encodeForJavaScript(String str){
    9. return StringEscapeUtils.escapeJavaScript(str);
    10. }
    11. public static String encodeForSQLParam(String param){
    12. return StringEscapeUtils.escapeSql(param);
    13. }
    14. /**
    15. * 防止xss跨脚本攻击(ESAPI编码)
    16. */
    17. public static String stripXSS(String value) {
    18. if (StringUtils.isNotEmpty(value)) {
    19. Pattern scriptPattern = Pattern.compile(
    20. "<[\r\n| | ]*script[\r\n| | ]*>(.*?)</[\r\n| | ]*script[\r\n| | ]*>", Pattern.CASE_INSENSITIVE);
    21. if(scriptPattern.matcher(value).find()){
    22. return encodeForHTML(value);
    23. }
    24. scriptPattern = Pattern.compile("src[\r\n| | ]*=[\r\n| | ]*[\\\"|\\\'](.*?)[\\\"|\\\']",
    25. Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
    26. if(scriptPattern.matcher(value).find()){
    27. return encodeForHTML(value);
    28. }
    29. // Remove any lonesome </script> tag
    30. scriptPattern = Pattern.compile("</[\r\n| | ]*script[\r\n| | ]*>", Pattern.CASE_INSENSITIVE);
    31. if(scriptPattern.matcher(value).find()){
    32. return encodeForHTML(value);
    33. }
    34. // Remove any lonesome <script ...> tag
    35. scriptPattern = Pattern.compile("<[\r\n| | ]*script(.*?)>",
    36. Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
    37. if(scriptPattern.matcher(value).find()){
    38. return encodeForHTML(value);
    39. }
    40. // Avoid eval(...) expressions
    41. scriptPattern = Pattern.compile("eval\\((.*?)\\)",
    42. Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
    43. if(scriptPattern.matcher(value).find()){
    44. return encodeForHTML(value);
    45. }
    46. // Avoid e-xpression(...) expressions
    47. scriptPattern = Pattern.compile("e-xpression\\((.*?)\\)",
    48. Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
    49. if(scriptPattern.matcher(value).find()){
    50. return encodeForHTML(value);
    51. }
    52. // Avoid javascript:... expressions
    53. scriptPattern = Pattern.compile("javascript[\r\n| | ]*:[\r\n| | ]*", Pattern.CASE_INSENSITIVE);
    54. if(scriptPattern.matcher(value).find()){
    55. return encodeForJavaScript(value);
    56. }
    57. // Avoid vbscript:... expressions
    58. scriptPattern = Pattern.compile("vbscript[\r\n| | ]*:[\r\n| | ]*", Pattern.CASE_INSENSITIVE);
    59. if(scriptPattern.matcher(value).find()){
    60. return encodeForJavaScript(value);
    61. }
    62. // Avoid onload= expressions
    63. scriptPattern = Pattern.compile("onload(.*?)=",
    64. Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
    65. if(scriptPattern.matcher(value).find()){
    66. return encodeForHTML(value);
    67. }
    68. }
    69. return value;
    70. }
    71. }

    中危

    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系统的敏感操作的指令。这些指令一般都需要另起一行,所以一般包含换行符。所以对输出日志进行编码,即可防止此种情况发生。

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

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

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

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

    1. <dependency>
    2. <groupId>org.jasypt</groupId>
    3. <artifactId>jasypt</artifactId>
    4. <version>需要依赖的版本</version>
    5. </dependency>

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

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

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

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

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

    7. 敏感信息泄漏

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

  • 相关阅读:
    103.(cesium之家)cesium蜂巢图(正方形)
    第二证券:产业资本真金白银传递市场信心
    电力电子的一些知识
    为什么大模型计算的时候只会利用KVcache来存放KV矩阵,Q矩阵每次不一样?
    AI人工智能学习之回归分析
    亚马逊云科技顾凡解读云计算助力初创快速抢滩生成式AI新风口
    【第6节】Lagent & AgentLego 智能体应用搭建
    HarmonyOS鸿蒙原生应用开发设计- 服务组件库
    阿里双十一交易核心链路产品--RocketMQ 底层原理及性能调优实战
    Spring Boot面试题
  • 原文地址:https://blog.csdn.net/qq_37252429/article/details/125613895