代码质量不佳会导致不可预测的行为。对于用户来说,通常表现为可用性差。对于攻击者来说,提供了以意外方式对系统施加压力的机会。
Camera
对象释放后对其进行了引用。Camera
对象释放后,代码试图使用该对象。在未重新获取 Camera
对象的情况下任何对该资源的进一步引用都将抛出异常。如果未捕获该异常,可能会导致应用程序崩溃。Camera
对象中的 startPreview()
。
public class ReuseCameraActivity extends Activity {
private Camera cam;
...
private class CameraButtonListener implements OnClickListener {
public void onClick(View v) {
if (toggle) {
cam.stopPreview();
cam.release();
}
else {
cam.startPreview();
}
toggle = !toggle;
}
}
...
}
start()
。
public class ReuseMediaPlayerActivity extends Activity {
private MediaPlayer mp;
...
private class PauseButtonListener implements OnClickListener {
public void onClick(View v) {
if (paused) {
mp.pause();
mp.release();
}
else {
mp.start();
}
paused = !paused;
}
}
...
}
flushUpdates()
将这些变化提交到磁盘中的程序。该方法会在将更新写入数据库后正确关闭数据库处理器。然而,当再次调用 flushUpdates()
时,数据库对象会在重新初始化之前被再次引用。
public class ReuseDBActivity extends Activity {
private myDBHelper dbHelper;
private SQLiteDatabase db;
@Override
public void onCreate(Bundle state) {
...
db = dbHelper.getWritableDatabase();
...
}
...
private void flushUpdates() {
db.insert(cached_data); // flush cached data
dbHelper.close();
}
...
}
...
<script src="http://applicationserver.application.com/lib/jquery/jquery-1.4.2.js" type="text/javascript"></script>
...
String
会导致数据丢失。String
后,没有说明适用字符集外的数据会发生何种变化。这会导致数据丢失,或者在需要二进制数据来确保执行正确的安全措施时,安全级别降低。
...
FileInputStream fis = new FileInputStream(myFile);
byte[] byteArr = byte[BUFSIZE];
...
int count = fis.read(byteArr);
...
String fileString = new String(byteArr);
String fileSHA256Hex = DigestUtils.sha256Hex(fileString);
// use fileSHA256Hex to validate file
...
BUFSIZE
,只要 myFile
中的信息已编码为与默认字符集相同,此方式就会很有用。但是,如果使用不同的编码方式,或者为二进制文件,则信息将会丢失。进而导致生成的 SHA 散列值的可靠性降低,并且也将更容易产生冲突,在默认字符集外的数据由相同的值(如问号)表示时,尤其如此。notify()
时将唤醒哪个线程并不确定。notify()
无法指定将唤醒哪个线程。 notifyJob()
调用 notify()
。
public synchronized notifyJob() {
flag = true;
notify();
}
...
public synchronized waitForSomething() {
while(!flag) {
try {
wait();
}
catch (InterruptedException e)
{
...
}
}
...
}
wait()
的线程,但是 notify()
可能会通知另外的线程。run()
方法,以此替代 start()
。Thread
对象的 run()
方法会是一个 bug。程序员本想启用新的控制线程,却意外地调用了 run()
,而不是 start()
,因此,在调用者的控制线程中执行了 run()
方法。run()
,而没有调用 start()
。
Thread thr = new Thread() {
public void run() {
...
}
};
thr.run();
stop()
方法,这可能会导致资源泄露。Thread
对象的 stop()
方法会是一个 bug。程序员希望阻止线程运行,但是没有意识到这种阻止线程的方式并不合适。Thread
中的 stop()
函数会导致 Thread
对象中的任意位置出现 ThreadDeath
异常,这可能会导致对象出现不一致状态,并且会泄露资源。由于此 API 本质上并不安全,因此很久以前就已弃用。Thread.stop()
。
...
public static void main(String[] args){
...
Thread thr = new Thread() {
public void run() {
...
}
};
...
thr.start();
...
thr.stop();
...
}
clone()
方法,但没有实现 Cloneable
接口。Cloneable
接口,因为它已经实现了一个名称为 clone()
的方法。然而,该类并没有实现 Cloneable
接口,同时 clone()
方法也无法正常运行。 clone()
方法将导致一个 CloneNotSupportedException.
异常
public class Kibitzer {
public Object clone() throws CloneNotSupportedException {
...
}
}
ICloneable
接口为其 Clone
方法指定的约定并不安全,应避免使用。ICloneable
接口不能保证实现深层克隆。对实施该接口的类进行克隆时,可能得不到预期的结果。这是因为这些类虽然实施了 ICloneable
接口,却只执行浅层克隆(只克隆某一对象而不克隆其当前引用的其他对象),这可能会导致意外的结果。由于深层克隆(克隆某一对象及其引用的所有对象)通常被认为是克隆方法的默认行为,因此为了避免发生错误,应避免使用 ICloneable
接口。clone()
方法会调用可以被覆盖的函数。clone()
函数调用了可覆盖的函数后,会导致克隆初始化仅部分完成,或者破坏该克隆。clone()
函数调用了可覆盖的方法。
...
class User implements Cloneable {
private String username;
private boolean valid;
public Object clone() throws CloneNotSupportedException {
final User clone = (User) super.clone();
clone.doSomething();
return clone;
}
public void doSomething(){
...
}
}
doSomething()
和其封装类并非 final
,这意味着该函数是可覆盖的,这将导致其克隆对象 clone
呈部分初始化状态,如果没有通过意外方式变通逻辑,则会导致错误。equals()
方法比较框式基元,可能会导致意外行为。equals()
方法,而非使用运算符 ==
和 !=
。Java 规范具有关于框式转换的如下说明: Boolean
或 Byte
),则仅会缓存或记住一个值区间。对于值的子集,使用 ==
或 !=
会返回正确的值,而对于此子集外的所有其他值,将返回对象地址的比较结果。
...
Integer mask0 = 100;
Integer mask1 = 100;
...
if (file0.readWriteAllPerms){
mask0 = 777;
}
if (file1.readWriteAllPerms){
mask1 = 777;
}
...
if (mask0 == mask1){
//assume file0 and file1 have same permissions
...
}
...
Example 1
中的代码会使用框式基元 Integer
尝试比较两个 int
值。如果 mask0
和 mask1
都等于 100
,则 mask0 == mask1
将返回 true
。但是,如果 mask0
和 mask1
都等于 777
,则 mask0 == maske1
现在将返回 false
,因为这些值不在这些框式基元的缓存值范围内。NaN
进行对比始终是一个错误。NaN
进行比较,得出的计算结果将始终为 false
(!=
运算符是个例外,因为 NaN
未经排序,所以始终会得出 true
结果)。NaN
。
...
if (result == Double.NaN){
//something went wrong
throw new RuntimeException("Something went wrong, NaN found");
}
...
result
并非 NaN
,但是使用带有 NaN
的运算符 ==
将始终得出 false
值,所以此检查将永远不会抛出异常。this
引用,进而导致出现漏洞。
...
class User {
private String username;
private boolean valid;
public User(String username, String password){
this.username = username;
this.valid = validateUser(username, password);
}
public boolean validateUser(String username, String password){
//validate user is real and can authenticate
...
}
public final boolean isValid(){
return valid;
}
}
validateUser
和该类并非 final
,这意味着它们是可以被覆盖的,对将覆盖此该函数的子类的变量执行初始化会允许避开 validateUser
功能。例如:
...
class Attacker extends User{
public Attacker(String username, String password){
super(username, password);
}
public boolean validateUser(String username, String password){
return true;
}
}
...
class MainClass{
public static void main(String[] args){
User hacker = new Attacker("Evil", "Hacker");
if (hacker.isValid()){
System.out.println("Attack successful!");
}else{
System.out.println("Attack failed");
}
}
}
Example 1
中的代码会输出“Attack successful!”,因为 Attacker
类会覆盖从超类 User
的构造函数调用的 validateUser()
函数,并且 Java 将首先在子类中查找从该构造函数调用的函数。inputReader
对象的类名称确认是否信任该对象的输入。如果攻击者能够提供一种执行恶意命令的 inputReader
实现方式,则此代码将无法区分该对象是善意的还是恶意的。
if (inputReader.GetType().FullName == "CompanyX.Transaction.Monetary")
{
processTransaction(inputReader);
}
inputReader
对象的类名称确认是否信任该对象的输入。如果攻击者能够提供一种执行恶意命令的 inputReader
实现方式,则此代码将无法区分该对象是善意的还是恶意的。
if (inputReader.getClass().getName().equals("com.example.TrustedClass")) {
input = inputReader.getInput();
...
}
inputReader
对象的类名称确认是否信任该对象的输入。如果攻击者能够提供一种执行恶意命令的 inputReader
实现方式,则此代码将无法区分该对象是善意的还是恶意的。
if (inputReader::class.qualifiedName == "com.example.TrustedClass") {
input = inputReader.getInput()
...
}
x = NULL
和 x != NULL
将始终为 false。NULL
的值是不确定的。它不等于任何值,甚至不等于另一个 NULL
值。同样,一个 null
值永远不会等于另一个值。例 2:以下语句将始终为 false。
checkNull BOOLEAN := x = NULL;
checkNotNull BOOLEAN := x != NULL;
equals()
方法,而不是==
或 !=
方法。==
或 !=
来比较两个字符串是否相等,其实质比较的是两个对象,而不是字符串的值。因此,若采用这个方法,两个引用将永远不会相等。
if (args[0] == STRING_CONSTANT) {
logger.info("miracle");
}
==
和 !=
标记符被用来比较相同对象中包含的字符串时,它们只会按照预期的那样运行。要出现这种情况,最常用的方法就是将字符串内置,这样一来,就可以将字符串添加到一个由 String
类维护的对象池中。一旦字符串内置,在使用该字符串时,都会采用相同的对象,相等运算符也会按照预期的那样执行。所有字符串文字和带值的字符串常量都会自动内置。其他字符串可以通过调用 String.intern()
来手动内置,并会返回一个规范的当前字符串实例,必要时也会创建一个实例。