前言

前一天时间,我在星球中发表过一篇文章《我抓到了几个典型的BUG》,广受好评,今天接着这个话题继续聊聊,我使用代码检测工具扫描出来的安全漏洞。

1. 访问权限问题

我们在日常开发过程中,为了让代码增加更多的灵活性,变得更加通用。

有时候,会使用反射技术。

类似于这样的写法:

Field[] declaredFields = Object.class.getDeclaredFields();
for (int i=0; i<declaredFields.length; i++) {
    Field declaredField = declaredFields[i];
    declaredField.setAccessible(true);
    //其他的业务逻辑
}

其实对于public类型的字段,可以不需要加上declaredField.setAccessible(true);判断。

因此,我们需要先判断字段的类型。

好在,Spring已经帮我们封装了一个设置字段访问权限的方法。

Field[] declaredFields = Object.class.getDeclaredFields();
for (int i = 0; i < declaredFields.length; i++) {
    Field declaredField = declaredFields[i];
    //declaredField.setAccessible(true);
    ReflectionUtils.makeAccessible(declaredField);;
}

我们可以直接将declaredField.setAccessible(true);改成ReflectionUtils.makeAccessible方法。

因为makeAccessible方法已经帮我们做了判断,源码如下:

image

2. 不安全的SSL协议

有时候,我们的系统需要访问第三方https协议的接口,有可能他们的开发环境或者测试环境的证书有问题,这时候在发送请求时,我们需要忽略证书的认证。

private HttpClient getIgnoreCerValidHttpClient() throws NoSuchAlgorithmException, KeyManagementException {
    SSLContext context = SSLContext.getInstance("SSL");
    context.init(null, new TrustManager[]{new X509TrustManager() {
        @Override
        public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {

        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[]{};
        }
    }
    }, new java.security.SecureRandom());
    SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(context, NoopHostnameVerifier.INSTANCE);
    return HttpClientBuilder.create().setSSLSocketFactory(sslConnectionSocketFactory).build();
}

这样的代码在网上一搜一大把。

很多人都是使用SSLContext.getInstance("SSL")方法创建的SSLContext对象的。

其实SSL协议是由Netscape提出,这个版本由于设计缺陷,并不安全,很快被发现有严重漏洞,已经废弃。

而SSL3.0写成RFC,开始流行。但2015年已经不安全,必须禁用。

后来,互联网标准化组织ISOC接替NetScape公司,发布了SSL的升级版TLS1.0版,但功能不太完善。

发展到现在已经到了TLS1.3,它还在制订中,支持0-rtt,大幅增进安全性,砍掉了aead之外的加密方式。

因此,我们需要将SSL缓存TLS1.3。

上面的代码,只需做如下改到即可:

...
SSLContext context = SSLContext.getInstance("TLS1.3");
...

其他的逻辑保持不变。

3. 没有关闭IO流程

我在代码扫描的过程中,发现最多的是空指针问题。

其次,就是没有关闭IO流的问题。

不骗你,有些高级开发工程师,有些也忘记了关闭IO流。

比如这样的代码:

OutputStream outputStream = null;
try {
  outputStream = getOutputStream();
  EasyExcelFactory.write(outputStream)
  .withTemplate(this.getClass().getResourceAsStream("/temp/text.xlsx")).build();
} finally {
   if(outputStream != null) {
      outputStream.close();
   }
}

上面的这段代码,我们如果第一眼看上去,好像没有问题。

但仔细看看,会发现这段代码不光使用了outputStream,还使用了inputStream。

this.getClass().getResourceAsStream("/temp/text.xlsx")方法就是使用inputStream读取excel文件中的数据。

这个方法返回的是InputStream对象,因此上面的代码存在IO流没有关闭的问题。

优化一下:

OutputStream outputStream = null;
InputStream inputStream = null;
try {
  outputStream = getOutputStream();
  inputStream = this.getClass().getResourceAsStream("/temp/text.xlsx");
  EasyExcelFactory.write(outputStream)
  .withTemplate(inputStream).build();
} finally {
   if(outputStream != null) {
      outputStream.close();
   }
   if(inputStream != null) {
     inputStream.close();
   }
}

但很不幸的是,这样改造之后,任然可能出现问题,如果outputStream在关闭IO流是抛了异常,也会导致inputStream的IO流关闭失败。

要不outputStream和inputStream在关闭时都用try...catch捕获一下异常?

其实不用这么麻烦,可以使用java7之后推荐的try...resources的写法。

例如:

try(InputStream inputStream = this.getClass().getResourceAsStream("/temp/text.xlsx");
  OutputStream outputStream = getOutputStream();) {
  EasyExcelFactory.write(outputStream)
  .withTemplate(inputStream).build();
}

InputStream和OutputStream类都实现了Closeable接口,JDK底层自带帮我们实现了关闭IO流等资源的功能。

4. 不好的常量命名

使用代码扫描工具nortify,还扫描出了一个让我印象非常深刻的问题,即:不好的常量命名。

有位同事,为了提升访问数据的性能,在他的业务代码中使用redis保存数据。

他当时使用了普通的串key/value结构,保存数据。

他的key是规定的,因此使用了一个常量来定义这个key。

例如:

private static final String KEY = "123456";

这就是一个非常典型的常量命名问题。

KEY是一个非常容易混淆,并且带有迷惑性的名字。

结果这样的命名,直接把代码扫描工具扫码出来了。

因此,我们在日常开发中,无论是给常量、变量和方法取名,尽量做到见名之意,提升代码的可读性和维护成本。

这个名字可以改成:

private static final String OFFICIAL_INDEX_PAGE_KEY = "123456";
最后修改:2026 年 06 月 06 日
如果觉得我的文章对你有用,请随意赞赏