前言
前一天时间,我在星球中发表过一篇文章《我抓到了几个典型的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方法已经帮我们做了判断,源码如下:

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