知学云安全编码规范
Java代码典型安全问题规范整理¶
目录¶
- 跨站脚本:存储型XSS-
java
高等级缺陷
- 跨站脚本:基于DOM的XSS-
javaScript
高等级缺陷
- 输入验证:重定向-
java
高等级缺陷
- 输入验证:路径遍历-
java
高等级缺陷
- 输入验证:路径遍历:ZIP条目覆盖-
java
中等级缺陷
- 输入验证:访问权限修饰符控制-
java
中等级缺陷
- 代码注入:SQL注入-
javaScript
高等级缺陷
- 代码注入:HTTP响应截断-
java
中等级缺陷
- 代码注入:HTTP响应截断:Cookies-
java
中等级缺陷
- 代码注入:JavaScript劫持-
javaScript
中等级缺陷
- 代码注入:资源注入-
java
中等级缺陷
- 代码注入:JavaScript劫持:易受攻击的框架-
javaScript
低等级缺陷
- 有风险的SSL:过于广泛的信任证书-
java
中等级缺陷
- 密码管理:不安全的随机数-
java
中等级缺陷
- 密码管理:不安全的哈希算法-
java
低等级缺陷
- 密码管理:弱加密:不安全的操作模式-
java
低等级缺陷
- 资源管理:资源未释放:文件-
java
中等级缺陷
- 资源管理:资源未释放:流-
java
中等级缺陷
- 资源管理:资源未释放:同步锁-
java
中等级缺陷
- 资源管理:格式化缺陷-
java
中等级缺陷
- 输入验证:有风险的资源使用-
java
低等级缺陷
- 输入验证:拒绝服务:StringBuilder-
java
低等级缺陷
- API误用:使用不必要的线程安全类-
java
低等级缺陷
- API误用:仅重写了equals()和hashCode()中的一个-
java
低等级缺陷
- API误用:忽略返回值-
java
低等级缺陷
- API误用:缺少对方法返回值的null检查-
java
低等级缺陷
- 代码质量:双重检查锁定-
java
中等级缺陷
- 代码质量:Cookie:未经过SSL加密-
java
中等级缺陷
- 代码质量:Cookie:路径范围过大-
java
中等级缺陷
- 代码质量:使用==或!=比较基本数据类型的包装类-
java
中等级缺陷
- 代码质量:比较Locale相关的数据未指定适当的Locale-
java
中等级缺陷
- 代码质量:null引用-
java
中等级缺陷
- 代码质量:不正确的块划分-
java
中等级缺陷
- 代码质量:过于宽松的跨文档消息目标-
javaScript
中等级缺陷
- 代码质量:匿名内部类使用非静态初始化器-
java
低等级缺陷
- 代码质量:使用单个字符字符串获取索引位置-
java
低等级缺陷
- 代码质量:Optional没有在调用isPresent()后访问值-
java
低等级缺陷
- 代码质量:方法返回参数类型为泛型类型通配符类型-
java
低等级缺陷
- 代码质量:在循环外调用wait()和await()-
java
低等级缺陷
- 代码质量:JavaEE程序:直接使用线程-
java
低等级缺陷
- 代码质量:Cookie:持久-
java
低等级缺陷
- 代码质量:日志记录:使用系统输出流-
java
低等级缺陷
- 代码质量:非final的public static字段-
java
低等级缺陷
- 代码质量:硬编码文件分隔符-
java
低等级缺陷
- 代码质量:使用浮点数进行精度计算-
java
低等级缺陷
- 代码质量:使用已过时的元素-
java
低等级缺陷
- 代码质量:变量和方法同名-
java
低等级缺陷
- 代码质量:冗余的null检查-
java
低等级缺陷
- 代码质量:使用equals()来判断字符串是否为空-
java
低等级缺陷
- 代码质量:循环中拼接字符串-
java
低等级缺陷
- 代码质量:byte数组转为String时未指定编码-
java
低等级缺陷
- 代码质量:不安全的数组声明-
java
低等级缺陷
- 代码质量:可序列号类中存在可序列号的敏感信息-
java
低等级缺陷
- 代码质量:HTML中包含硬编码域名-
javaScript
低等级缺陷
- 代码质量:Vue:v-if与v-for在同一个元素上使用-
javaScript
低等级缺陷
- 代码质量:Vue:v-for没有使用key-
javaScript
低等级缺陷
- 代码质量:Vue:组件样式被污染-
javaScript
低等级缺陷
内容详情¶
- 跨站脚本:存储型XSS
高等级缺陷
说明:存储型XSS是指应用程序通过Web请求获取不可信赖的数据,并且在未检验数据是否存在XSS代码的情况下,将其存入数据库。当程序下一次从数据库中获取该数据时,致使页面再次执行XSS代码。存储型XSS可以持续攻击用户,在用户提交了包含XSS代码的数据存储到数据库后,每当用户在浏览网页查询对应数据库中的数据时,那些包含XSS代码的数据就会在服务器解析并加载,当浏览器读到XSS代码后,会当做正常的HTML和JS解析并执行,于是发生存储型XSS攻击。
/**修改建议:
为了避免存储型XSS攻击,建议采用以下方式进行防御:
(1)对从数据库或其它后端数据存储获取不可信赖的数据进行合理验证(如年龄只能是数字),对特殊字符(如<、>、'、"以及<script>、javascript等进行过滤。
(2)根据数据将要置于HTML上下文中的不同位置(HTML标签、HTML属性、JavaScript脚本、CSS、URL),对所有不可信数据进行恰当的输出编码。
例如:采用OWASP ESAPI对数据输出HTML上下文中不同位置,编码方法如下。
*/
//HTML encode
ESAPI.encoder().encodeForHTML(inputData);
//HTML attribute encode
ESAPI.encoder().encodeForHTMLAttribute(inputData);
//JavaScript encode
ESAPI.encoder().encodeForJavaScript(inputData);
//CSS encode
ESAPI.encoder().encodeForCSS(inputData);
//URL encode
ESAPI.encoder().encodeForURL(inputData);
//(3)设置HttpOnly属性,避免攻击者利用跨站脚本漏洞进行Cookie劫持攻击。在Java EE中,给Cookie添加HttpOnly的代码如下:
response.setHeader("Set-Cookie","cookiename=cookievalue; path=/; Domain=domainvaule; Max- age=seconds; HttpOnly");
- 跨站脚本:基于DOM的XSS
高等级缺陷
说明:应用程序的客户端代码从document.location、request.url、document.URL、document.referrer或其他任何攻击者可以修改的浏览器对象获取数据,如果未验证数据是否存在恶意代码的情况下,就将其动态更新到页面的DOM节点,应用程序将易于受到基于DOM的XSS攻击。
- 输入验证:重定向
高等级缺陷
说明:应用程序允许未验证的用户输入控制重定向中的URL,攻击通过构建URL,使用户重定向到任意URL,利用这个漏洞可以诱使用户访问某个页面,挂马、密码记录、下载任意文件等,常被用来钓鱼。
修复建议:防止重定向漏洞的方法是创建一份合法URL列表,用户只能从中进行选择,进行重定向操作。
反例:
public class RedirectServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException {
...
String query = request.getQueryString();
if (query.contains("url")) {
String url = request.getParameter("url");
response.sendRedirect(url);
}
}
}
正例:
// companyUrl为系统配置,所以不会受外部影响
@Value("${companyUrl}")
private String companyUrl;
@RequestMapping(value = "/callback", method = RequestMethod.GET)
@JSON("errorCount")
public String ycOauth(RequestContext context) {
...
return "redirect:https://"+companyUrl+"/zxy-student-web/";
}
- 输入验证:路径遍历
高等级缺陷
说明:应用程序对用户可控制的输入未经合理校验,就传送给一个文件API。攻击者可能会使用一些特殊的字符(如“..”和“/”)摆脱受保护的限制,访问一些受保护的文件或目录。
反例:
String path = getInputPath();
if (path.startsWith("/safe_dir/")){
File f = new File(path);
f.delete()
}
/**
攻击者可能提供类似下面的输入:/safe_dir/../important.dat程序假定路径是有效的,因为它是以“/safe_dir/”开头的,但是“../”将导致程序删 除“important.dat”文件的父目录。
*/
修复建议:
(1) 程序对非受信的用户输入做过滤和验证,对网站用户提交的文件路径进行硬编码或统一编码,过滤非法字符。
(2)对文件后缀进行白名单控制,拒绝包含了恶意的符号或空字节。
(3)合理配置Web服务器的目录访问权限。
// 增加过滤器
public String filter (String data) {
Pattern pattern = Pattern.compile("[\\s\\\\/:\\*\\?\\\"<>\\I]") ;
Matcher matcher = pattern.matcher(data) ;
data = matcher.replaceA1ll("") ;
return data;
}
- 输入验证:路径遍历:ZIP条目覆盖
中等级缺陷
说明:程序在解压zip文件时,由于没有对文件名进行合法性的校验,而是直接将文件名拼接在待解压目录后面,导致可以将文件解压到正常解压缩路径之外并覆盖可执行文件,从而等待系统或用户调用他们实现代码执行(也可能是覆盖配置文件或其他可敏感文件)。
修复建议:防止ZIP条目覆盖导致路径遍历可以通过判定zipEntry路径是否在指定路径内,或者使用一些最新的解压jar包来解压文件。
反例:
// 例1:以下代码示例解压zip文件。
File sourceFile = new File(sourceName);
ZipFile zipFile = null;
try {
zipFile = new ZipFile(sourceFile,"UTF-8");
} catch (IOException exception) {
exception.printStackTrace();
System.out.println("解压文件不存在!");
}
Enumeration e = zipFile.getEntries();
while(e.hasMoreElements()) {
ZipEntry zipEntry = (ZipEntry)e.nextElement();
// 代码示例未验证zipEntry.getName()
System.out.println(zipEntry.getName());
File f = new File(targetFile,zipEntry.getName());
f.getParentFile().mkdirs();
f.createNewFile();
InputStream is = zipFile.getInputStream(zipEntry);
FileOutputStream fos = new FileOutputStream(f);
int length = 0;
byte[] b = new byte[1024];
while((length=is.read(b, 0, 1024))!=-1) {
fos.write(b, 0, length);
}
is.close();
fos.close();
}
if (zipFile != null) {
zipFile.close();
}
// 代码示例未验证zipEntry.getName(),如果zip文件放在/tmp/目录中,zip条目为../etc/hosts,且应用程序在必要的权限下运行,则会导致系统的hosts文件被覆盖。
// 例2:以下代码使用org.zeroturnaround.zip.ZipUtil解压zip文件。
public void unZip(String zipPath,String targetPath) {
...
ZipUtil.unpack(new File(zipPath), new File(targetPath));
...
}
// 代码示例中如果使用了zt-zip 1.13之前版本。攻击者可借助带有目录遍历名称的zip文件利用该漏洞写入任意文件。
正例:
// 例1:下面的validateFileDir函数来限制zip条目文件路径只在许可的目录内。
// 限制文件在许可的目录内
public static String validateFileDir(String fileName, String permitDirectory) throws IOException{
File file= new File(fileName);
String canonicalFilePath= checkFile.getCanonicalPath();
File permitDir = new File(permitDirectory);
String canonicalPermitDir = permitDir.getCanonicalPath();
if (canonicalFilePath.startsWith(canonicalPermitDir)){
return canonicalFilePath;
}else{
throw new IllegalStateException("文件不在许可的目录内");
}
}
//可以解压zip文件过程中使用validateFileDir函数来限制zip条目文件只在许可目录内。
//例2:使用当前zt-zip 1.13以及之后的版本来进行解压文件。
- 输入验证:访问权限修饰符控制·
中等级缺陷
说明:AccessibleObject类是Field、Method和Constructor对象的基类,能够允许反射对象修改访问权限修饰符,绕过由Java访问修饰符提供的访问控制检查。它让程序员能够更改私有字段或调用私有方法,这在通常情况下是不允许的
修复建议:通过有权限的类更改访问权限修饰符,并确保修改的访问权限修饰符参数不能被攻击者控制。
反例:
// 例如:以下代码片段中,将Field将accessible标记设置为true。
Class clazz = User.class;
Field field = clazz.getField("name");
field.setAccessible(true);
...
- 代码注入:SQL注入
高等级缺陷
说明:SQL注入是一种数据库攻击手段。攻击者通过向应用程序提交恶意代码来改变原SQL语句的含义,进而执行任意SQL命令,达到入侵数据库乃至操作系统的目的。
- 代码注入:HTTP响应截断
中等级缺陷
说明:程序从一个不可信赖的数据源获取数据,未进行验证就置于HTTP头文件中发给用户,可能会导致HTTP响应截断攻击。
修改建议:防止HTTP响应截断攻击的最安全的方法是创建一份安全字符白名单,只接受完全由这些受认可的字符组成的输入出现在HTTP响应头文件中。
反例:
// 例如:下列代码片段中,程序从HTTP请求中获取author的值,并将其设置到HTTP响应文件的cookie中。
String author = request.getParameter(AUTHOR_PARAM);
...
Cookie cookie = new Cookie("author", author);
cookie.setMaxAge(cookieExpiration);
response.addCookie(cookie);
/**
如果请求中提交的是一个“Jane Smith”字符串,那么包含该cookie的HTTP响应可能表现为以下形式:HTTP/1.1 200 OK
...
Set-Cookie: author=Jane Smith
...
那么如果攻击者提交的是一个恶意字符串,比如“Wiley Hacker\r\nHTTP/1.1 200 OK\r\n...”,那么HTTP响应就会被分割成以下形式的两个响应:
HTTP/1.1 200 OK
...
Set-Cookie: author=Wiley Hacker
HTTP/1.1 200 OK
...
这样第二个响应已完全由攻击者控制,攻击者可以用所需的头文件和正文内容构建该响应实施攻击。
*/
正例:
// 例如:以下代码片段中,验证了author的值是否由标准的字母数字字符组成。
String author = request.getParameter(AUTHOR_PARAM);
if (Pattern.matches("[0-9A-Za-z]+", author)) {
...
Cookie cookie = new Cookie("author", author);
cookie.setMaxAge(cookieExpiration);
response.addCookie(cookie);
}
中等级缺陷
说明:HTTP响应截断是由于应用程序未对用户提交的数据进行严格过滤,当用户恶意提交包含 CR(回车,即URL编码%0d或r)和 LF(换行符,即URL编码%0a或n)的HTTP请求,服务器可能会创建两个 HTTP 响应,攻击者可以控制第二个响应并加载攻击。攻击者可控制响应的内容构造 XSS 攻击,其中响应中包含恶意的 JavaScript 或其它代码在用户的浏览器中执行,也有可能让用户重定向到攻击者控制的Web内容或在用户的主机上执行恶意操作。
修改建议:防止HTTP响应截断攻击的最安全的方法是创建一份安全字符白名单,只接受完全由这些受认可的字符组成的输入出现在HTTP响应头文件中。
反例:
// 例1:下列代码片段中,程序读取HTTP请求参数AUTHOR_PARAM的值,并将它设置到HTTP响应的Cookie中。
String author = request.getParameter(AUTHOR_PARAM);
...
Cookie cookie = new Cookie("author", author);
cookie.setMaxAge(cookieExpiration);
response.addCookie(cookie);
/**
正常情况下,如果请求中提交参数是字符串“Jane Smith”,那么包含Cookie的HTTP响应头格式如下:
HTTP/1.1 200 OK
...
Set-Cookie: author=Jane Smith
...
但是如果攻击者提交一个恶意字符串,比如“Wiley Hacker\r\nHTTP/1.1 200 OK\r\n...”,那么HTTP应答就会被分割成下面两个响应:
HTTP/1.1 200 OK
...
Set-Cookie: author=Wiley Hacker
HTTP/1.1 200 OK
这样第二个响应已完全由攻击者控制,攻击者可以用所需的头文件和正文内容构建该响应实施攻击。
*/
//例2:以下Android代码会从Activity中直接取值,并将其置于一个HTTP响应的cookie头文件中,而该值很可能是受用户控制的。
...
CookieManager cookieManager = CookieManager.getInstance();
Intent intent = activity.getIntent();
String value = intent.getStringExtra("value");
cookieManager.setCookie("url", value);
...
正例:
// 例如:以下代码片段中,验证了para的值是否由标准的字母数字字符组成。
String para = request.getParameter(para);
if (Pattern.matches("[0-9A-Za-z]+", para)) {
...
Cookie cookie = new Cookie("para", para);
cookie.setMaxAge(expirationAge);
response.addCookie(cookie);
}
-
代码注入:JavaScript劫持
中等级缺陷
-
代码注入:资源注入
中等级缺陷
说明:使用用户输入控制资源标识符,借此攻击者可以访问或修改其他受保护的系统资源。当满足以下两个条件时,就会发生资源注入:
(1)攻击者可以指定已使用的标识符来访问系统资源。例如,攻击者能够指定用来连接到网络资源的端口号。
(2)攻击者可以通过指定特定资源来获取某种权限,而这种权限在一般情况下是不可能获得的。例如,程序可能会允许攻击者把敏感信息传输到第三方服务器。
修改建议: 为了避免资源注入漏洞攻击,可以采用黑名单或白名单策略。黑名单会有选择地拒绝或避免潜在的危险字符。但是,任何这样一份黑名单都不可能是完整的,而且将随着时间的推移而过时。比较好的方法是创建白名单,允许其中的字符出现在资源名称中,且只接受完全由这些被认可的字符所组成的输入。
反例:
// 例1:下面的代码片段从HTTP请求获取端口号,并使用此端口号创建一个套接字,而不进行任何验证。使用代理的用户可以修改此端口并获得与服务器的直接连接。 String port = request.getParameter("port"); ... ServerSocket serverSocket = new ServerSocket(port); Socket socket = serverSocket.accept(); ... // 这种利用用户输入影响的资源可能存在风险。 // 例2:下面的代码利用WebView的File域协议读取任意可读文件或受害应用的私有文件。 WebView webView=(WebView) findViewById(R.id.webView); String url= getIntent().getData().toString(); webView.loadUrl(url); // 查看hosts文件:adb shell am start -n com.mytest/.MainActivity -d file:///system/etc/hosts // 查看应用私有文件:adb shell am start -n /data/data/com.cn.test/databases/user.db
-
代码注入:JavaScript劫持:易受攻击的框架
低等级缺陷
说明:
-
有风险的SSL:过于广泛的信任证书·
中等级缺陷
说明:盗用证书颁发机构的数量正在不断增加,即使由CA签名的证书也不应该信任,拥有这些盗用证书的攻击者可能会拦截这些CA的SSL/TLS信息流。
修复建议:不要直接信任用户证书,并对证书进行判断和处理。
反例:
// 例如:下列代码中获取可接受的发行者为空,意味着服务端将会默认信任用户证书,并不会对证书进行判断。 public X509Certificate[] getAcceptedIssuers() { return null; }
正例:
// 例如:下列代码中获取可接受的发行者不为空,并对证书进行判断。 public X509Certificate[] getAcceptedIssuers() { return x509TrustManager.getAcceptedIssuers(); }
-
密码管理:不安全的随机数
中等级缺陷
说明:Java API中提供了java.util.Random类实现PRNG(),该PRNG是可移植和可重复的,如果两个java.util.Random类的实例使用相同的种子,会在所有Java实现中生成相同的数值序列。在安全性要求较高的应用中,应使用更安全的随机数生成器,如java.security.SecureRandom类。
反例:
// 例如:下面代码片段中,使用了java.util.Random类,该类对每一个指定的种子值生成同一个序列。 import java.util.Random; // ... public static void main (String args[]) { // ... for (int i = 0; i < 10; i++) { Random random = new Random(123456); int number = random.nextInt(21); ... } }
正例:
// 例如:下面代码片段中,使用java.security.SecureRandom来生成更安全的随机数。 import java.security.SecureRandom; import java.security.NoSuchAlgorithmException; // ... public static void main (String args[]) { try { SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); for (int i = 0; i < 10; i++) { int number = random.nextInt(21); // ... } } catch (NoSuchAlgorithmException nsae) { // ... } }
-
密码管理:不安全的哈希算法
低等级缺陷
说明:在安全性要求较高的系统中,不应使用被业界公认的不安全的哈希算法(如MD2、MD4、MD5、SHA、SHA1等)来保证数据的完整性。
正例:
// 例如:下面代码片段中,使用SHA-256算法取代MD5算法保证数据完整性。 ... byte[] b = str.getBytes(); MessageDigest md = null; try { md = MessageDigest.getInstance("SHA-256"); md.update(b); ... } catch (NoSuchAlgorithmException e) { ...
-
密码管理:弱加密:不安全的操作模式
低等级缺陷
说明:块密码又称为分组加密,一次加密明文中的一个块。将明文按一定的位长分组,明文组经过加密运算得到密文组,密文组经过解密运算(加密运算的逆运算),还原成明文组。这种加密算法共有四种操作模式用于描述如何重复地应用密码的单块操作来安全的转换大于块的数据量,分别是电子代码(ECB)、密码块链(CBC)、密码反馈(CFB)以及输出反馈(OFB)。其中ECB模式下相同的明文块总是会得到相同的密文,故不能抵挡回放攻击,而CBC模式则没有这个缺陷。
修改建议:加密大于块的数据时,应该避免使用ECB模式。由于CBC模式不会对相同的明文块生成相同的密文块,所以CBC模式更好。然而,CBC模式效率较低,并且在和SSL一起使用时会造成严重风险。可以改用CCM(Counter with CBC-MAC)模式,如果更注重性能,在可用的情况下则使用GCM(Galois/Counter)模式。
例如:以下代码将AES密码用于CBC模式。
反例:
// 例如:以下代码将AES密码用于ECB模式。 ... Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC"); cipher.init(Cipher.ENCRYPT_MODE, createSecretKey(seed)); ...
正例:
// 例如:以下代码将AES密码用于CBC模式。 ... Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC"); cipher.init(Cipher.ENCRYPT_MODE, createSecretKey(seed)); ...
-
资源管理:资源未释放:文件
中等级缺陷
说明:程序创建或分配文件句柄后,不进行合理释放,将会降低系统性能,攻击者还可能会通过耗尽资源池的方式发起拒绝服务攻击。
反例:
// 下面代码片段中,创建了一个文件句柄zFile,未进行合理释放 public void getZipContents(String fileName){ ZipFile zFile = null; try { zFile = new ZipFile(fileName); ... } catch (IOException e) { e.printStackTrace(); } }
正例:
// 例如:下面代码片段中,使用完之前创建的文件句柄zf后,在finally代码块中进行了释放。 public void getZipContents(String fileName){ ZipFile zFile = null; try { zFile = new ZipFile(fileName); ... } catch (IOException e) { e.printStackTrace(); }finally{ if(zFile!=null){ try { zFile.close(); } catch (IOException e) { e.printStackTrace(); } } } }
-
资源管理:资源未释放:流
中等级缺陷
说明:程序创建或分配流资源后,不进行合理释放,将会降低系统性能。攻击者可能会通过耗尽资源池的方式发起拒绝服务攻击。
反例:
// 例如:在下面Java方法中,创建I/O流对象后未进行合理释放,程序依靠Java虚拟机的垃圾回收机制释放I/O流资源,事实上,程序不能确定何时调用虚拟机的finalize()方法。在繁忙的程序环境下,可能导致Java虚拟机不能有效的使用I/O对象。 ... public void processFile(String filePath){ try { FileInputStream fis = new FileInputStream(filePath); InputStreamReader isr = new InputStreamReader(fis); BufferedReader br = new BufferedReader(isr); String line=""; while((line=br.readLine())!=null){ processLine(line); } } catch (FileNotFoundException e) { log(e); } catch (IOException e){ log(e); } } ...
正例:
// 例如:下面代码片段中,在finally代码块中对流资源进行了合理的释放。 public void processFile(String filePath){ FileInputStream fis = null; InputStreamReader isr = null; BufferedReader br = null; try { fis = new FileInputStream(filePath); isr = new InputStreamReader(fis); br = new BufferedReader(isr); String line=""; while((line=br.readLine())!=null){ //processLine(line); } } catch (FileNotFoundException e) { //log(e); } catch (IOException e){ //log(e); }finally{ if(br!=null){ try { br.close(); } catch (IOException e) { //log(e); } }
-
资源管理:资源未释放:同步锁
中等级缺陷
说明:程序创建或分配同步锁资源后,不进行合理释放,将会降低系统性能甚至可能会导致死锁,攻击者可能会通过耗尽资源池的方式发起拒绝服务攻击。检查程序逻辑,保证任何情况下,程序持有的锁都能正常释放。
反例:
// 例如:下面代码片段中只有在dosomething()不抛出异常的情况下才会正常运行,但如果在执行dosomething()方法时发生异常,程序将无法释放其持有的锁。 ... TestLock lock = new TestLock (); lock.lock(); dosomething(); lock.unlock();
正例:
// 例如:下列代码片段中,在finally代码块中调用Lock.unlock()方法,可以保证不管是否发生异常,总会释放锁。 public final class Client { public void doSomething(File file) { final Lock lock = new ReentrantLock(); InputStream in = null; lock.lock(); try { in = new FileInputStream(file); // Perform operations on the open file } catch (FileNotFoundException fnf) { // Forward to handler } finally { lock.unlock(); if (in != null) { try { in.close(); } catch (IOException e) { // Forward to handler } } } }
-
资源管理:格式化缺陷
中等级缺陷
说明:格式化对象是非线程安全的,java.text.Format中的parse()和format()方法包含一个可导致用户看到其他用户数据的race condition。
修复建议:
(1)将格式化对象定义成局部变量,但是每调用一次方法意味创建一个格式化对象,浪费内存。
(2)方法加同步锁synchronized,在同一时刻,只有一个线程可以执行类中的某个方法。这样性能较差,每次都要等待锁释放后其他线程才能进入。
(3)使用第三方库joda-time,由第三方考虑线程不安全的问题。
(4)使用ThreadLocal:每个线程拥有自己的格式化对象
反例:
// 例如:下面代码片段中,定义了一个成员变量(静态的日期格式对象)。 public class DateFormat extends Thread{ private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); private String name; private String dateStr; public DateFormat(String name, String dateStr) { this.name = name; this.dateStr = dateStr; } @Override public void run() { try { Date date = sdf.parse(dateStr); System.out.println("线程"+name+"运行"+": date:"+date); } catch (ParseException e) { e.printStackTrace(); } } public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(3); executorService.execute(new DateFormat("A", "2017-06-10")); executorService.execute(new DateFormat("B", "2016-06-06")); executorService.shutdown(); } } // 如上代码中输出会有两种情况,一种情况是报错,还有一种情况是两个线程输出一致。出现这种情况的原因是因为SimpleDateFormat类内部有一个Calendar对象引用,它用来储存和这个SimpleDateFormat相关的日期信息。这样就会导致一个问题,如果SimpleDateFormat是static的, 那么多个thread之间就会共享SimpleDateFormat, 同时也是共享Calendar引用。在高并发的情况下,容易出现幻读成员变量的现象。
-
输入验证:有风险的资源使用
低等级缺陷
说明:拒绝服务是攻击者通过极度消耗应用资源,以致程序崩溃或其他合法用户无法进行使用的一种攻击方式。系统中有一些方法存在着一定风险,当方法所需的数据来源于不可信赖的数据源时,可能会导致拒绝服务。从程序源代码角度讲,对涉及到系统资源的外部数据应该进行严格校验,防止无限制的输入。谨慎使用线程阻塞的API,防止浪费系统资源甚至崩溃。
反例:
// 例1:下面代码片段中,解压文件前,未检查文件大小,攻击者可以通过提供一个超大文件,实施DOS攻击。 static final int SIZE= 512; ... public static void unZip(BufferedInputStream bin){ BufferedOutputStream bop= null; ZipInputStream zi = new ZipInputStream(bin); ZipEntry zentry; while ((zentry= zi.getNextEntry()) != null) { int count; byte data[] = new byte[SIZE]; FileOutputStream fos = new FileOutputStream(zentry.getName()); bop= new BufferedOutputStream(fos, SIZE); // 未经检查大小直接读取 while ((count = zi.read(data, 0, SIZE)) != -1) { bop.write(data, 0, count); } bop.flush(); bop.close(); } zi.close(); } // 例2:下列代码片段中使用了waitFor方法,意味着直到该进程结束才能继续执行后续代码,不正确的处理输入输出流有可能死锁,导致程序持续浪费资源甚至崩溃。 process.waitFor();
正例:
// 例如:下面代码片段中,对解压文件进行验证,超过50M,将抛出异常。 static final int MAX= 0x3200000; // 50MB // ... // write the files to the disk, but only if file is not insanely big if (entry.getSize() > MAX) { throw new IllegalStateException("File to be unzipped is huge."); } if (entry.getSize() == -1) { throw new IllegalStateException("File to be unzipped might be huge."); } FileOutputStream fos = new FileOutputStream(entry.getName()); bop = new BufferedOutputStream(fos, SIZE); while ((count = zis.read(data, 0, SIZE)) != -1) { bop.write(data, 0, count); }
-
输入验证:拒绝服务:StringBuilder
低等级缺陷
说明:当StringBuilder或者StringBuffer使用append添加数据时,如果StringBuilder或者StringBuffer对象内部数组不足以存入所有数据时,会重新调整调整基础数组的大小来存放新的数据。而旧数组将继续留在堆中,一直等待系统回收。当旧数组过多时,可能导致堆栈溢出,从而拒绝服务。应检查程序逻辑,对用户输入的大小进行了合理校验,以避免系统堆栈溢出而拒绝服务。
反例:
// 例如:在以下代码片段中,使用了不信任的数据添加到StringBuilder中。 ... String data = request.getParameter("data"); StringBuilder sb = new StringBuilder(); sb.append(data); ...
正例:
// 例子1:考虑用谷歌guava static String concatString(Object... args) { Joiner joiner = Joiner.on("").useForNull("null"); return joiner.join(args); } // 预估内容大小设置初始值 String data = request.getParameter("data"); StringBuilder sb = new StringBuilder(20); sb.append(data);
-
API误用:使用不必要的线程安全类
低等级缺陷
说明:Java中的Vector、Stack、Hashtable和StringBuffer中方法都实现了同步,以确保它们是线程安全的,同时性能也会相对下降,当不需要线程安全时,可以使用替代集合。
修改建议:在线程安全的情况下分别使用ArrayList或LinkedList,Deque,HashMap,StringBuilder来替换Vector,Stack,Hashtable,StringBuffer。
反例:
// 例如:在下面的示例中,局部变量不会导致线程问题,却使用StringBuffer进行字符串拼接。 public int modifyUsers(List<User> users) { StringBuffer stringBuilder = new StringBuffer(); for (int i < 0;i < users.size;i++) { stringBuffer.append(users.getId()); stringBuffer.append(" "); } String string = stringBuffer.toString(); ... }
正例:
// 例如:在下面的示例中,使用StringBuilder进行字符串拼接。 public int modifyUsers(List<User> users) { StringBuilder stringBuilder = new StringBuilder(); for (int i < 0;i < users.size;i++) { stringBuilder.append(users.getId()); stringBuilder.append(" "); } String string = stringBuilder.toString(); ... }
-
API误用:仅重写了equals()和hashCode()中的一个
低等级缺陷
说明:在仅重写类的equals()方法和hashCode()方法中的一个时,由于顶层基类Object的 public boolean equals(Object obj)方法逻辑是,在比较任何非空引用x和y时,当且仅当x和y引用同一对象时返回true。而在重写了equals()方法后,通常也必须重写hashCode()方法,这样时为了维护hashCode()方法的常规协定,协定声明相等对象必须具有相同的哈希码,即:
(1)当obj1.equals(obj2)为true时,obj1.hashCode() == obj2.hashCode()必须为true
(2)当obj1.hashCode() == obj2.hashCode()为false时,obj1.equals(obj2)必须为false
如果不遵循这个准则,当这个重写类的对象保存在一个集合中时,可能就会引发一些问题。比如保存到HashMap或HashSet集合中,相同对象的哈希码必须相等,这一点十分重要。
-
API误用:忽略返回值
低等级缺陷
说明:一般在开发过程中,程序员凭借经验可以断言某些用户事件或条件,例如某个函数永远不会执行失败。但是,攻击者往往会故意触发一些异常情况,导致冲破了程序员的断言,给系统带来了不稳定性,利用不正确的行为触发出发程序的漏洞。例如,程序调用删除权限函数,但不检查返回值判断是否成功删除权限,则程序将继续以更高权限运行。
反例:
// 例如:在下面的代码片段中,程序没有对read()返回值做判断。 ... int bytesToRead = 1024; byte[] byteArray = new byte[bytesToRead]; streamFileInput = new FileInputStream("C:\\file.txt"); streamFileInput.read(byteArray); ...
正例:
if (!temp.delete()) { // todo }
-
API误用:缺少对方法返回值的null检查
低等级缺陷
说明:程序没有对有可能返回null的方法返回值进行检查,可能会导致NullPointException。程序应对可能返回null的方法的返回值进行检查,避免产生NullPointException。
反例:
String data = System.getenv("ADD"); if (data.equalsIgnoreCase("XXX") ){ //todo }
-
代码质量:双重检查锁定
中等级缺陷
说明:在程序开发中,有时需要推迟一些高开销的对象初始化操作,并且只有在使用这些对象时才进行初始化,此时开发者可能会采用延迟初始化。但要正确实现线程安全的延迟初始化需要一些技巧,否则容易出现问题。如果需要对实例字段使用线程安全的延迟初始化,请使用基于volatile的延迟初始化的方案。如果需要对静态字段使用线程安全的延迟初始化,基于类初始化的方案。
反例:
// 例1:下面代码示例中,延迟初始化对象不是线程安全的。 public class UnsafeLazyInitialization { private static Instance instance; public static Instance getInstance() { if (instance == null) { //1:Thread A executed instance = new Instance(); //2:Thread B executed } return instance; } } // 在例1中,假设A线程执行代码1的同时,B线程执行代码2。此时,线程A可能会看到instance引用的对象还没有完成初始化。对应的解决方式,可以对getInstance()做同步处理来实现线程安全的延迟初始化。示例代码如下: // 例2:对getInstance()同步处理来实现线程安全的延迟初始化。 public class SafeLazyInitialization { private static Instance instance; public synchronized static Instance getInstance() { if (instance == null) { instance = new Instance(); } return instance; } } // 在例2代码示例中,如果getInstance()被多个线程频繁的调用,将会导致程序执行性能的下降。对此,开发人员想通过双重检查锁定来降低同步的开销。示例代码如下: // 例3:下面代码示例中,使用双重校验锁来实现延迟初始化。 public class DoubleCheckedLocking { //1 private static Instance instance; //2 public static Instance getInstance() { //3 if (instance == null) { //4:first check synchronized (DoubleCheckedLocking.class) { //5:lock if (instance == null) //6:second check instance = new Instance(); //7:problem } //8 } //9 return instance; //10 } //11 } // 在例3的代码示例中,开发者试图通过检测第一次instance不为null,就不需要执行下面的加锁和初始化操作。希望可以大幅降低例2同步方法带来的性能开销。然而,这是一个错误的优化。在线程执行到第4行代码读取到instance不为null时,instance引用的对象有可能还没有完成初始化。原因如下:第7行(instance = new Singleton();)创建一个对象。这一行代码可以分解为如下的三行伪代码: memory = allocate(); //1:分配对象的内存空间 ctorInstance(memory); //2:初始化对象 instance = memory; //3:设置instance指向刚分配的内存地址 上面三行伪代码中的2和3之间,可能会被重排序。2和3之间重排序之后的执行时序如下: memory = allocate(); //1:分配对象的内存空间 instance = memory; //3:设置instance指向刚分配的内存地址 //注意,此时对象还没有被初始化 ctorInstance(memory); //2:初始化对象 //根据Java语言规范《The Java Language Specification, Java SE 7 Edition》,所有线程在执行java程序时必须要遵守intra-thread semantics。intra-thread semantics保证重排序不会改变单线程内的程序执行结果。但是当多线程执行时,线程可能会看到一个还没有被初始化的对象。
正例:
// 例1:基于volatile的双重检查锁定的解决方案。 public class SafeDoubleCheckedLocking { private volatile static Instance instance; public static Instance getInstance() { if (instance == null) { synchronized (SafeDoubleCheckedLocking.class) { if (instance == null){ instance = new Instance(); // volatile instance } } return instance; } } // 这个解决方案需要JDK5或更高版本,因为从JDK5开始使用新的JSR-133内存模型规范,这个规范增强了volatile的语义。 // 例2:基于类初始化的解决方案。 public class InstanceFactory { private static class InstanceHolder { public static Instance instance = new Instance(); } public static Instance getInstance() { return InstanceHolder.instance ; //InstanceHolder class is initialized } } // JVM在类的初始化阶段(即在Class被加载后,且被线程使用之前),会执行类的初始化。在执行类的初始化期间,JVM会去获取一个锁。这个锁可以同步多个线程对同一个类的初始化。
-
代码质量:Cookie:未经过SSL加密
中等级缺陷
说明:使用未加密的网络传输通道发送Cookie易受到网络截取攻击。创建Cookie时,需要将secure标记设置为true,表明浏览器必须通过HTTPS来加密传输Cookie。
修改建议:为Cookie添加secure标记,要求浏览器通过HTTPS发送Cookie,这样有助于保护Cookie值的保密性。
反例:
Cookie cookie = new Cookie("SessionID", sessionID); response.addCookie(cookie);
正例:
Cookie cookie = new Cookie("captcha", uuid); cookie.setHttpOnly(true); cookie.setPath(cookieConfigMap.get(GlobalConstant.COOKIE_PATH_KEY)); cookie.setMaxAge(120 + delta); cookie.setSecure(true);
-
代码质量:Cookie:路径范围过大
中等级缺陷
说明:将Cookie设置为可从根上下文路径(“/”)访问它,这将使Cookie暴露在域中的所有Web应用程序下。例如:一个Web应用程序部署在http://qtthanj.com/Test上,当用户登录时,应用程序使用路径“/”设置Session ID的Cookie。
Cookie cookie = new Cookie("sessionID", sessionID);
cookie.setPath("/");
如果攻击者在http://qtthanj.com/hackertest上创建另一个应用程序,并在上面的应用程序中发布了该站点的链接。当用户点击该链接时,用户的浏览器会将/Test设置的Cookie发送到在/hackertest上运行的应用程序。攻击者就这样窃取了用户的Session ID。
反例:
Cookie cookie = new Cookie("sessionID", sessionID); cookie.setPath("/");
正例:
setCookie("SessionId", getSessionID(), 0, "/Test", "qtthanj.com", true, true);
-
代码质量:使用==或!=比较基本数据类型的包装类
中等级缺陷
说明:不能直接使用==或!=操作符来比较的两个基本数据类型的包装类型的值,因为这些操作符比较的是对象的引用而不是对象的值。不过由于Java的缓存机制,所以如果基本类型的包装类是一个整数且在-128和127之间,或是布尔类型true或false,或者是'\u0000'和'\u007f'之间的字符文本,可以使用==或!=进行比较。也就是说,如果使用了基本类型的包装类型(除去Boolean或Byte),则会缓存或记住一个值区间。对于值区间内的值,使用==或!=会返回正确的值,而对于值区间外的值,将返回对象地址的比较结果。
反例:
Integer integer1 = 100; Integer integer2 = 100; Integer integer3 = 520; Integer integer4 = 520; System.out.println(integer1 == integer2); System.out.println(integer3 == integer4);
正例
Integer integer1 = 100; Integer integer2 = 100; Integer integer3 = 520; Integer integer4 = 520; System.out.println(integer1.equals(integer2)); System.out.println(integer3.equals(integer4));
-
代码质量:比较Locale相关的数据未指定适当的Locale
中等级缺陷
说明:在比较数据时,如果可能与Locale设置相关,则应指定相应的Locale。
反例:
// 例1:下面代码示例中,使用了Locale相关的String.toUpperCase()将字符转换为大写字符。在英文Locale中,会将"title"转换为"TITLE";在土耳其Locale中,会将"title"转换为"T?TLE",其中的?是拉丁字母的"I"。 "title".toUpperCase(); "TITLE".toLowerCase();
正例:
// 例1:下面代码示例将Locale设置为英文,从而避免产生意外的问题。 "title".toUpperCase(Locale.ENGLISH); "TITLE".toLowerCase(Locale.ENGLISH); // 例2:可以在对字符串处理前,将默认的Locale设置为English。 Locale.setDefault(Locale.ENGLISH); "title".toUpperCase(); "TITLE".toLowerCase();
-
代码质量:null引用
中等级缺陷
说明:程序间接引用了可能为null的变量,从而引发空指针异常。
反例:
Data data = null; ... data.setId(id);
正例:
Data data = null ... if(data != null){ data.setId(id); }
-
代码质量:不正确的块划分
中等级缺陷
说明:代码没有显式地分隔一个块,该块的目的是包含两个或多个语句,从而产生一个逻辑错误。
反例:
// 例1:下面代码片段中,程序员错误的认为x=1;y=1会在当x==0的时候赋值(缩进代表了其意图)。而y=1在x无论何值都会执行。 ... if(x == 0) x = 1; y = 1; ... // 例2:下面代码片段中,程序员错误的认为x=1和y=1会在当x==0的时候赋值(同行代表了其意图)。而y=1在x无论何值都会执行。 ... if(x == 0)x = 1;y = 1; ... // 例3:下面代码片段中,程序员错误的认为x=1和y=1会在当x==0的时候赋值。而x=1和y=1在x无论何值都会执行。 ... if(x == 0);{ x = 1; y = 1; } ...
正例:
if(x == 0){ x = 1; y = 1; }
-
代码质量:过于宽松的跨文档消息目标
中等级缺陷
-
代码质量:匿名内部类使用非静态初始化器
低等级缺陷
说明:当匿名内部类拥有某个对象的实例时,如果匿名内部类被其他对象返回并持有,那么使用这个对象可能导致内存泄漏。并且这种写法语义非常模糊,不利于后期维护。
反例:
// 例如:下列代码中匿名内部类使用了非静态初始化器。 Map source = new HashMap(){ { // Noncompliant put("firstName", "John"); put("lastName", "Smith"); } }; ...
正例:
// 例如:下列代码中显示声明集合并添加数据。 Map source = new HashMap(); // ... source.put("firstName", "John"); source.put("lastName", "Smith");
-
代码质量:使用单个字符字符串获取索引位置
低等级缺陷
说明:使用单个字符字符串的indexOf或lastIndexOf方法获取字符索引位置。可以通过使用char参数的indexOf或lastIndexOf方法来提高性能。
反例:
String myStr = "Hello World"; // ... int pos = myStr.indexOf("W"); // Noncompliant ...
正例:
// 例如:下列代码中使用char参数的indexOf方法。 String myStr = "Hello World"; // ... int pos = myStr.indexOf('W'); ...
-
代码质量:Optional没有在调用isPresent()后访问值
低等级缺陷
说明:Optional方法中包含的值可以使用get()方法访问,但是如果没有值,它会抛出一个NoSuchElementException异常。应该总是在调用Optional的get()方法之前调用isPresent()方法。
反例:
// 例如:下列代码中Optional在调用get()方法前未调用isPresent()判断。 Optional<String> value = this.getOptionalValue(); // ... String stringValue = value.get(); // Noncompliant
正例:
// 例如:下列代码中Optional在调用get()方法前调用isPresent()判断。 Optional<String> value = this.getOptionalValue(); // ... if (value.isPresent()) { String stringValue = value.get(); }
-
代码质量:方法返回参数类型为泛型类型通配符类型
低等级缺陷
说明:当方法返回参数类型为泛型通配符类型时,类型推断相对复杂,语义模糊,不利于后期维护,有可能导致类型转换错误。不要将泛型通配符类型作为方法返回参数类型,明确返回值类型。
反例:
// 例如:下列代码中泛型通配符类型在返回参数中使用,无法明确List中类型。 List<? extends Animal> getAnimals(){...}
正例:
List<Animal> getAnimals(){...}
-
代码质量:在循环外调用wait()和await()
低等级缺陷
说明:在while循环外使用wait()和await()方法可能导致无限期阻塞拒绝服务。wait()和await()方法应始终在循环中使用。
反例:
// 例如:下面不符合规则的代码,可能遇到恶意通知、虚假唤醒等问题。 synchronized (object) { if (<condition does not hold>) { object.wait(); } // Proceed when condition holds }
正例:
// 例如:下面这段符合规则的代码在while循环中调用wait()方法,从而在wait()调用的前后都对条件进行检查。 synchronized (object) { while (<condition does not hold>) { object.wait(); } // Proceed when condition holds }
-
代码质量:JavaEE程序:直接使用线程
低等级缺陷
说明:JAVA EE标准禁止在某些环境下使用Web应用程序中的线程管理,因为此时使用线程管理非常容易出错。线程管理起来很困难,可能还会以不可预知的方式干扰应用程序容器。即使容器没有受到干扰,线程管理通常还会导致各种难以发现的错误,如死锁、竞争条件及其他同步错误等。
反例:
// 例如:下面代码片段中,在Servlet的doGet()方法中,直接创建了一个线程对象。 public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // Perform servlet tasks. ... // Create a new thread to handle background processing. Runnable r = new Runnable() { public void run() { // Process and store request statistics. ... } }; new Thread(r).start(); }
正例:
// 建议使用线程池、Spring等提供的线程管理功能来替代直接使用操作线程。 参考:CommonThreadPool.java
-
代码质量:Cookie:持久
低等级缺陷
说明:过于持久的Cookie可能危及帐户安全或泄露用户信息。创建Cookie时默认为非永久性的Cookie。但程序员可以通过给Cookie设置属性的方法指定Cookie保留在浏览器中的时长,私人信息存储于Cookie中的时间越长,信息泄露的可能性越大,因为攻击者有足够长的时间进行攻击来窃取数据。
修改建议:不要在持久的Cookie中存储敏感数据,确保在合理的时间内清除持久Cookie中与服务器端存储的相关联的所有数据。
反例:
Cookie cookie = new Cookie("usernameCookie", username); cookie.setMaxAge(60*60*24*365*0.5);
-
代码质量:日志记录:使用系统输出流
低等级缺陷
说明 :通过系统输出流(标准错误流)打印或日志功能将系统数据或调试信息输出到本地文件或屏幕时,在一些类似Eclipse的程序,为了让错误信息更加显眼,会将错误信息以红色文本的形式通过System.err输出到控制台上,更容易发生内部信息泄露。程序不应通过系统输出流或程序日志将系统数据或调试信息输出程序。
反例:
// 例如:下面代码片段中,printStackTrace()方法内部使用了标准错误流输出异常的堆栈信息,攻击者可能会利用这些堆栈信息制定相应的攻击计划。 try{ //todo }catch(Exception e){ e.printStackTrace(); }
-
代码质量:非final的public static字段
低等级缺陷
说明:程序中定义了非final的public static字段,该字段可被外部类进行更改。如果希望将字段作为常数值公开,则应将该字段声明为public static final,否则就将该字段声明为private。
反例:
// 例如:在下列代码中,类中字段被声明为public、static和非final的。 public class MysqlDBConfig { public static String driver = "com.mysql.jdbc.Driver"; public static String dbName = "username"; public static String password = "password"; public static String userName = "root"; public static String url = "jdbc:mysql://127.0.0.1:3306/" + dbName; } // 在这种情况下,恶意代码可能会更改这些字段的值使程序出现意外行为。
-
代码质量:硬编码文件分隔符
低等级缺陷
说明:路径分割符号问题,不同的操作系统不同。在程序中不要硬性编码与平台相关的任何常量,比如行分隔符,文件分隔符,路径分隔符等等。例如文件分隔符,Windows系统使用"\"或"/",而UNIX系统则使用"/"。应用程序需要在不同的平台上运行时,使用硬编码文件分隔符会导致应用程序逻辑执行错误,并有可能导致拒绝服务。
修复建议:不应使用硬编码文件分隔符,而应使用语言库提供的独立于平台的API,如java.io.File.separator,也可以通过使用java.lang.System.getProperty("file.separator")来获取。
反例:
// 例如:以下代码使用硬编码文件分隔符来打开文件: File file = new File(dirName + "\\" + fileName);
正例:
// 例如:针对示例代码的修改方法是: File file = new File(dirName + File.separator + fileName);
-
代码质量:使用浮点数进行精度计算
低等级缺陷
说明:Java中浮点数采用的是IEEE 754标准,所以在精确计算中使用浮点类型会发生精度缺失从而产生不正确的数值。
修改建议:程序中避免使用浮点数进行精确计算,可以考虑采用整数类型或用于精确表达小数的BigDecimal类型替代。
反例:
// 例如:下面代码输出为false和0.060000000000000005 ... public static void main(String[] args){ double a = 0.05 + 0.01; System.out.println(a == 0.06); System.out.println(a); } ...
-
代码质量:使用已过时的元素
低等级缺陷
说明:随着编程语言的发展和程序的不断迭代,往往会因为某些原因弃用部分函数,或使用新函数替代之前的。如果程序中使用了@Deprecated注释的元素或者一些以及废弃的方法,这些元素或者方法可能会带来安全隐患。
-
代码质量:变量和方法同名
低等级缺陷
说明:变量与方法同名,容易导致程序混乱或产生错误的代码。不要使用容易造成混淆的命名。
-
代码质量:冗余的null检查
低等级缺陷
说明:程序对一个引用的变量进行了null检查,但是在这之前,程序中已经对该变量进行了null检查,或是对该变量进行了一些可能会引发null异常的操作(取值、转换)。检查程序逻辑,删除不必要的null检查代码。
-
代码质量:使用equals()来判断字符串是否为空
低等级缺陷
说明:程序中使用equals方法来判断字符串是否为空,这样会降低系统性能。使用判断字符串长度的方法,判断字符串是否为空,这样会提升系统性能。
正例:
// 参考apache common包的StringUtils方法 public static boolean isBlank(String str) { int strLen; if (str != null && (strLen = str.length()) != 0) { for(int i = 0; i < strLen; ++i) { if (!Character.isWhitespace(str.charAt(i))) { return false; } } return true; } else { return true; } }
-
代码质量:循环中拼接字符串
低等级缺陷
说明:字符串对象是不可改变的,拼接和修改字符串对象,最后都会创建一个新的字符串对象。而在循环中拼接字符串将会产生很多的对象,浪费系统运行时间和空间。在循环语句结构中,需要保证线程安全可以使用StringBuffer代替String进行拼接,不需要时可以使用StringBuilder代替String进行拼接。
-
代码质量:byte数组转为String时未指定编码
低等级缺陷
说明:在将字节数组的数据转换为String时如果未设定转换编码,可能会导致数据丢失。应避免将可能包含非字符数据的byte数组转换为String对象,如果必须将byte数组转String,应对其进行编码。
反例:
// 例如:下面代码片段中,将数据转换为字符串,以便创建hash值。 byte[] byteArray = byte[BUFSIZE]; FileInputStream fileInputStream = new FileInputStream("fileName"); int count = fileInputStream.read(byteArray); String fileString = new String(byteArray); String fileSHA256Hex = DigestUtils.sha256Hex(fileString); // 当文件的大小小于字节数组容量SIZE时,只要文件myFile中的信息已编码为与默认字符集相同,此方式正确。但是,如果使用不同的编码方式,或者为二进制文件,则信息将会丢失。进而导致生成的SHA散列值的可靠性降低。
正例:
// 例1:下面代码片段中,避免了将可能包含非字符数据的byte数组转换为String对象。 byte[] byteArray = byte[BUFSIZE]; FileInputStream fileInputStream = new FileInputStream("fileName"); int count = fileInputStream.read(byteArr); byte[] fileSHA256 = DigestUtils.sha256(byteArray); // 例2:下面代码片段中,byte数组转String时指定了编码。 byte[] byteArray = byte[BUFSIZE]; FileInputStream fileInputStream = new FileInputStream("fileName"); int count = fileInputStream.read(byteArray); String fileString = new String(byteArray,"utf-8"); String fileSHA256Hex = DigestUtils.sha256Hex(fileString);
-
代码质量:不安全的数组声明
低等级缺陷
说明:程序声明了一个final的public static数组,虽然数组被声明为final,保证了数组对象本身只分配一次,但是由于数组是公共的,但是仍然可以改变数组中存储的值。检查程序逻辑,判断是否需要final的public static数组。
反例:
// 例如:下面代码片段中,程序错误的声明了一个final的public static数组 public final class urlTool extends Applet { public final static URL[] urls; ... }
-
代码质量:可序列号类中存在可序列号的敏感信息
低等级缺陷
说明:在可序列化类中存在敏感信息,当对象被序列化时,类中的敏感信息将会存储。攻击者可能会序列化该对象,并获取敏感信息。在存在敏感信息的可序列化类中,当序列化对象时,忽略,拒绝或者加密敏感信息。
反例:
// 例如:下列代码中类实现了序列化,且存在敏感信息password。攻击者获取该对象序列化的文件后,将会获取此用户密码。 class Person implements Serializable{ private String name; private String password; public Person(String name,String password) { this.setName(name); this.setPassword(password); } ... }
正例:
例1:以下代码敏感信息使用transient关键字修饰,将不会参与序列化过程: class Person implements Serializable{ private String name; private transient String password; public Person(String name,String password) { this.setName(name); this.setPassword(password); } ... }
-
代码质量:HTML中包含硬编码域名
低等级缺陷
说明:
-
代码质量:Vue:v-if与v-for在同一个元素上使用
低等级缺陷
说明:
-
代码质量:Vue:v-for没有使用key
低等级缺陷
说明:
-
代码质量:Vue:组件样式被污染
低等级缺陷
说明:
-
…