程式碼品質不佳,會導致無法預料的行為。從使用者的角度來看,這通常表現為可用性不佳。對於攻擊者而言,這提供了以意想不到的方式向系統施加壓力的機會。
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
,除了 !=
運算子之外,這個運算子一律會估算為 true
,因為 NaN
處於未排序狀態。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()
來以手動控制在內部,這將會回傳目前字串的標準實例,必要時也會建立一個實例。