程式碼品質不佳,會導致無法預料的行為。從使用者的角度來看,這通常表現為可用性不佳。對於攻擊者而言,這提供了以意想不到的方式向系統施加壓力的機會。
...
var file:File = new File(directoryName + "\\" + fileName);
...
...
FileStream f = File.Create(directoryName + "\\" + fileName);
...
...
File file = new File(directoryName + "\\" + fileName);
...
...
os.open(directoryName + "\\" + fileName);
...
<script>
標籤。
...
public String tagProcessor(String tag){
if (tag.toUpperCase().equals("SCRIPT")){
return null;
}
//does not contain SCRIPT tag, keep processing input
...
}
...
Example 1
的問題是,在無地區設定的情況使用 java.lang.String.toUpperCase()
時,它會使用預設地區設定的規則。使用土耳其文地區設定 "title".toUpperCase()
會傳回「T\u0130TLE」,其中「\u0130」是「LATIN CAPITAL LETTER I WITH DOT ABOVE」字元。這會導致非預期的結果,例如,在 Example 1
中,這會阻止此驗證攔截「script」一字,進而可能導致 Cross-Site Scripting 弱點。
...
import java.sql.PreparedStatement;
import com.sap.sql.NativeSQLAccess;
String mssOnlyStmt = "...";
// variant 1
PreparedStatement ps =
NativeSQLAccess.prepareNativeStatement(
conn, mssOnlyStmt);
. . .
// variant 2
Statement stmt =
NativeSQLAccess.createNativeStatement(conn);
int result = stmt.execute(mssOnlyStmt);
. . .
// variant 3
CallableStatement cs =
NativeSQLAccess.prepareNativeCall(
conn, mssOnlyStmt);
. . .
...
public class Box{
public int area;
public static final int width = 10;
public static final Box box = new Box();
public static final int height = (int) (Math.random() * 100);
public Box(){
area = width * height;
}
...
}
...
Example 1
中,開發人員預期 box.area
會是一個隨機整數,這正好是 10 的倍數,因為 width
等於 10。然而實際上,它的硬式編碼值始終是 0。使用編譯時間常數宣告的靜態 final 欄位會先進行初始化,然後依序執行每個欄位。這表示,由於 height
不是編譯時間常數,所以會在宣告 box
之後宣告,因此,會在初始化 height
欄位之前呼叫建構函式。
...
class Foo{
public static final int f = Bar.b - 1;
...
}
...
class Bar{
public static final int b = Foo.f + 1;
...
}
This example is perhaps easier to identify, but would be dependent on which class is loaded first by the JVM. In this exampleFoo.f
could be either -1 or 0, andBar.b
could be either 0 or 1.
null
之前先解除參照可能為 null
的指標,將會造成「解除參照之後檢查」的錯誤。當程式執行明確的 null
檢查,但仍繼續解除參照已知為 null
的指標時,將會造成「檢查後解除參照」的錯誤。這種類型的錯誤通常是打字錯誤或程式設計師的疏忽所造成。當程式明確的將某指標設定為 null
,卻在之後解除參考該指標,則會發生儲存後解除參照錯誤。這種錯誤通常是由於程式設計師在宣告變數時將變數初始化為 null
所致。foo
是 null
,並在之後以錯誤的方式解除參照。在 if
陳述式中檢查 foo
時,若其為 null
,便會發生 null
解除參照,因此造成 Null 指標異常。範例 2:在以下程式碼中,程式設計師會假設
if (foo is null) {
foo.SetBar(val);
...
}
foo
變數不是 null
,並解除參照物件來確認這項假設。不過,程式設計師之後將對照 null
檢查 foo
,來反駁這項假設。在 if
陳述式中檢查 foo
時,若其為 null
,則在解除參照時也可為 null
,並有可能會造成 Null 指標異常。解除參照不安全,後續檢查也不必要。範例 3:在以下程式碼中,程式設計師明確地將變數
foo.SetBar(val);
...
if (foo is not null) {
...
}
foo
設定為 null
。之後,程式設計師先解除參照 foo
,再檢查物件是否為 null
值。
Foo foo = null;
...
foo.SetBar(val);
...
}
null
之前先解除參照可能為 null
的指標,將會造成「解除參照之後檢查」的錯誤。當程式執行明確的 null
檢查,但仍繼續解除參照已知為 null
的指標時,將會造成「檢查後解除參照」的錯誤。這種類型的錯誤通常是打字錯誤或程式設計師的疏忽所造成。當程式明確的將某指標設定為 null
,卻在之後解除參考該指標,則會發生儲存後解除參照錯誤。這種錯誤通常是由於程式設計師在宣告變數時將變數初始化為 null
所致。ptr
不是 NULL
。當程式設計師取消參照指標時,這個假設就變得清楚。當程式設計師檢查 ptr
為 NULL
時,這個假設其實就出現其矛盾之處。若在 if
指令中檢查 ptr
時,其可為 NULL
,則解除參照時也可為 NULL
,並產生分段錯誤。範例 2:在以下程式碼中,程式設計師確定變數
ptr->field = val;
...
if (ptr != NULL) {
...
}
ptr
是 NULL
,並在之後以錯誤的方式解除參照。若在 if
陳述式中檢查 ptr
時,其非 NULL
,就會發生 null
解除參照,從而導致分段錯誤。範例 3:在以下程式碼中,程式設計師忘記了字串
if (ptr == null) {
ptr->field = val;
...
}
'\0'
實際上是 0 還是 NULL
,因此解除參照 Null 指標,並導致分段錯誤。範例 4:在以下程式碼中,程式設計師明確地將變數
if (ptr == '\0') {
*ptr = val;
...
}
ptr
設定為 NULL
。之後,程式設計師先解除參照 ptr
,再檢查物件是否為 null
值。
*ptr = NULL;
...
ptr->field = val;
...
}
null
檢查,但仍繼續解除參照已知為 null
的物件時,將會造成「解除參照之後檢查」的錯誤。這種類型的錯誤通常是打字錯誤或程式設計師的疏忽所造成。foo
是 null
,並在之後以錯誤的方式解除參照。在 if
陳述式中檢查 foo
時,若其為 null
,便會發生 null
解除參照,因此造成 Null 指標異常。
if (foo == null) {
foo.setBar(val);
...
}
int
的 unsigned char
,但傳回值會指定為 char
類型。EOF
無法區分。EOF
做比較。
char c;
while ( (c = getchar()) != '\n' && c != EOF ) {
...
}
getchar()
的傳回值會轉換為 char
,並與 EOF
(int
) 做比較。假設 c
是 8 位元有號值,而 EOF
是 32 位元有號值,那麼若 getchar()
傳回以 0xFF 表示的字元,則在與 EOF
比較時,c
值的正負數符號將會延伸至 0xFFFFFFFF。由於 EOF
通常會定義為-1 (0xFFFFFFFF),因此迴圈會錯誤地終止。amount
在傳回的時候可能為一個負值。由於函數被宣告為傳回一個無符號的 int,因此 amount
將被間接轉換為一個無符號的值。
unsigned int readdata () {
int amount = 0;
...
if (result == ERROR)
amount = -1;
...
return amount;
}
Example 1
中的錯誤條件得到滿足,那麼 readdata()
回傳值在使用 32 位元整數的系統上將會是 4,294,967,295。accecssmainframe()
的回傳值而定,變數 amount
可能在傳回時帶負值。由於函數被宣告為傳回一個無符號值,所以 amount
將間接轉換為一個無符號的數值。
unsigned int readdata () {
int amount = 0;
...
amount = accessmainframe();
...
return amount;
}
accessmainframe()
的回傳值為 -1,那麼 readdata()
回傳值在 32 位元整數的系統上將會是 4,294,967,295。1
值傳送給第一個參數 (版本編號):
__xmknod
2
值傳送給第三個參數 (群組引數):
__wcstod_internal
__wcstof_internal
_wcstol_internal
__wcstold_internal
__wcstoul_internal
3
值傳送給第一個參數 (版本編號):
__xstat
__lxstat
__fxstat
__xstat64
__lxstat64
__fxstat64
FILE *sysfile = fopen(test.file, "w+");
FILE insecureFile = *sysfile;
insecureFile
時將 sysfile
解除參照,使用 insecureFile
便可能會導致各種問題。
FILE *sysfile = fopen(test.file, "r+");
res = fclose(sysfile);
if(res == 0){
printf("%c", getc(sysfile));
}
getc()
函數在 sysfile
的檔案串流關閉之後執行,因此 getc()
導致未定義的行為,並可能導致系統當機或導致相同或不同檔案的可能修改或讀取。
std::auto_ptr<foo> p(new foo);
foo* rawFoo = p.get();
delete rawFoo;
delete
前從管理類別分離該指標,管理類別未來將不會再使用該指標。int a = (Int32)i + (Int32)j;
會拋出無法處理的異常,並使應用程式在執行階段當機。
class Program
{
static int? i = j;
static int? j;
static void Main(string[] args)
{
j = 100;
int a = (Int32)i + (Int32)j;
Console.WriteLine(i);
Console.WriteLine(j);
Console.WriteLine(a);
}
}
aN
和 bN
設定值,但是在預設案例中,程式設計師不小心將 aN
的值設定了兩次。
switch (ctl) {
case -1:
aN = 0; bN = 0;
break;
case 0:
aN = i; bN = -i;
break;
case 1:
aN = i + NEXT_SZ; bN = i - NEXT_SZ;
break;
default:
aN = -1; aN = -1;
break;
}
StreamReader
的 Finalize()
方法最終會呼叫 Close()
,但不確定何時會呼叫 Finalize()
方法。事實上,不確定是否會呼叫 Finalize()
。在忙碌的環境中,這可能會導致 VM 用盡它所有能使用的檔案控制碼。範例 2:在正常情況下,以下程式碼會執行資料庫查詢,處理資料庫傳回的結果並關閉分配的
private void processFile(string fName) {
StreamWriter sw = new StreamWriter(fName);
string line;
while ((line = sr.ReadLine()) != null)
processLine(line);
}
SqlConnection
物件。但是,如果在執行 SQL 或處理結果時發生異常,SqlConnection
物件將不會關閉。如果這種情況時常發生的話,那麼資料庫將會用盡可用指標,並且無法再執行任何 SQL 查詢。
...
SqlConnection conn = new SqlConnection(connString);
SqlCommand cmd = new SqlCommand(queryString);
cmd.Connection = conn;
conn.Open();
SqlDataReader rdr = cmd.ExecuteReader();
HarvestResults(rdr);
conn.Connection.Close();
...
int decodeFile(char* fName)
{
char buf[BUF_SZ];
FILE* f = fopen(fName, "r");
if (!f) {
printf("cannot open %s\n", fName);
return DECODE_FAIL;
} else {
while (fgets(buf, BUF_SZ, f)) {
if (!checkChecksum(buf)) {
return DECODE_FAIL;
} else {
decodeBlock(buf);
}
}
}
fclose(f);
return DECODE_SUCCESS;
}
CALL "CBL_CREATE_FILE"
USING filename
access-mode
deny-mode
device
file-handle
END-CALL
IF return-code NOT = 0
DISPLAY "Error!"
GOBACK
ELSE
PERFORM write-data
IF ws-status-code NOT = 0
DISPLAY "Error!"
GOBACK
ELSE
DISPLAY "Success!"
END-IF
END-IF
CALL "CBL_CLOSE_FILE"
USING file-handle
END-CALL
GOBACK
.
New()
函數會建立與系統記錄常駐程式的新連線。它是 log.syslog 套件的一部分。每次寫入傳回的寫入器時,都會傳送一則含有指定優先順序 (系統記錄工具和嚴重性的組合) 和字首標籤的記錄訊息。因此,在繁忙的環境中,這可能會導致系統用盡其所有的通訊端。範例 2:在此範例中,
func TestNew() {
s, err := New(syslog.LOG_INFO|syslog.LOG_USER, "the_tag")
if err != nil {
if err.Error() == "Unix syslog delivery error" {
fmt.Println("skipping: syslogd not running")
}
fmt.Println("New() failed: %s", err)
}
}
net/smtp
套件的 Dial()
方法將傳回一個新用戶端,這個新用戶端會連線到位於 localhost 的 SMTP 伺服器。連線資源會被分配,但永遠不會透過呼叫 Close()
函數來釋放。
func testDial() {
client, _ := smtp.Dial("127.0.0.1")
client.Hello("")
}
Arena.ofConfined()
建立的資源沒有關閉。
...
Arena offHeap = Arena.ofConfined()
MemorySegment str = offHeap.allocateUtf8String("data");
...
//offHeap is never closed
BEGIN
...
F1 := UTL_FILE.FOPEN('user_dir','u12345.tmp','R',256);
UTL_FILE.GET_LINE(F1,V1,32767);
...
END;
onPause()
、onStop()
或 onDestroy()
事件處理常式中釋放 Camera
實例。onPause()
、onStop()
或 onDestroy()
回呼中釋放的 Camera
實例。 在必須將目前活動傳送至背景時,或由於系統資源過低而必須暫時破壞活動時,Android OS 會啟用這些收回 (callback)。 由於未能適當地釋放 Camera
物件,該活動會造成其他應用程式 (或甚至未來相同應用程式的實例) 無法存取相機。 而且,若在活動暫停時仍持有 Camera
實例,會因為不必要的耗電而對使用者經驗造成負面影響。onPause()
方法的 Android 活動,該方法應該用於釋放 Camera
物件,且該活動不會在其關機順序期間正確地釋放該物件。
public class UnreleasedCameraActivity extends Activity {
private Camera cam;
@Override
public void onCreate(Bundle state) {
...
}
@Override
public void onRestart() {
...
}
@Override
public void onStop() {
cam.stopPreview();
}
}
onPause()
、onStop()
,或 onDestroy()
事件處理常式中解除 MediaRecorder
、MediaPlayer
,或 AudioRecord
物件。onPause()
、onStop()
,或 onDestroy()
收回 (callback) 中解除的媒體物件。在必須將目前活動傳送至背景時,或由於系統資源過低而必須暫時破壞活動時,Android OS 會啟用這些收回 (callback)。由於未能適當地解除媒體物件,該活動會造成後續 (其他應用程式甚至相同應用程式) 對於 Android 媒體硬體的存取容錯回復至軟體實作,甚至完全失敗。若有過多未解除的媒體實例保持在開啟狀態,可能會導致 Android 異常,進而造成 Denial of Service。而且,若在活動暫停時仍持有實例,會因為不必要的耗電而對使用者經驗造成負面影響。onPause()
方法的 Android 活動,該方法應該用於釋放媒體物件,且該活動不會在其關機順序期間正確地釋放該方法。
public class UnreleasedMediaActivity extends Activity {
private MediaPlayer mp;
@Override
public void onCreate(Bundle state) {
...
}
@Override
public void onRestart() {
...
}
@Override
public void onStop() {
mp.stop();
}
}
onPause()
、onStop()
,或 onDestroy()
事件處理常式中解除 Android 資料庫處理常式。onPause()
、onStop()
,或 onDestroy()
收回 (callback) 中關閉的 Android SQLite 資料庫處理常式。在必須將目前活動傳送至背景時,或由於系統資源過低而必須暫時破壞活動時,Android OS 會啟用這些收回 (callback)。由於未能適當地關閉該資料庫,如果該活動經常重新啟動,便可能耗盡可用游標的裝置。此外,根據實作而定,Android 作業系統可能會拋出 DatabaseObjectNotClosedException
,在未能捕捉異常時導致應用程式當機。onPause()
,該方法應用於釋放資料庫物件,且該活動不會在其關機順序期間正確地釋放該方法。
public class MyDBHelper extends SQLiteOpenHelper {
...
}
public class UnreleasedDBActivity extends Activity {
private myDBHelper dbHelper;
private SQLiteDatabase db;
@Override
public void onCreate(Bundle state) {
...
db = dbHelper.getWritableDatabase();
...
}
@Override
public void onRestart() {
...
}
@Override
public void onStop() {
db.insert(cached_data); // flush cached data
}
}
sys.dba_users
的程式碼可以使用 PWD_COMPARE
程序來檢查使用者密碼。
CREATE or REPLACE procedure PWD_COMPARE(p_user VARCHAR, p_pwd VARCHAR)
AUTHID DEFINED
IS
cursor INTEGER;
...
BEGIN
IF p_user != 'SYS' THEN
cursor := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(cursor, 'SELECT password FROM SYS.DBA_USERS WHERE username = :u', DBMS_SQL.NATIVE);
DBMS_SQL.BIND_VARIABLE(cursor, ':u', p_user);
...
END IF;
END PWD_COMPARE;
sys
密碼。造成異常的一個方式就是將過長的引數傳送到 p_user
。攻擊者得知指標已洩漏後,只需要猜測指標並指派新連結變數即可。
DECLARE
x VARCHAR(32000);
i INTEGER;
j INTEGER;
r INTEGER;
password VARCHAR2(30);
BEGIN
FOR i IN 1..10000 LOOP
x:='b' || x;
END LOOP;
SYS.PWD_COMPARE(x,'password');
EXCEPTION WHEN OTHERs THEN
FOR j IN 1..10000
DBMS_SQL.BIND_VARIABLE(j, ':u', 'SYS');
DBMS_SQL.DEFINE_COLUMN(j, 1, password, 30);
r := DBMS_SQL.EXECUTE(j);
IF DBMS_SQL.FETCH_ROWS(j) > 0 THEN
DBMS_SQL.COLUMN_VALUE(j, 1, password);
EXIT;
END IF;
END LOOP;
...
END;
DATA: result TYPE demo_update,
request TYPE REF TO IF_HTTP_REQUEST,
obj TYPE REF TO CL_SQL_CONNECTION.
TRY.
...
obj = cl_sql_connection=>get_connection( `R/3*my_conn`).
FINAL(sql) = NEW cl_sql_prepared_statement(
statement = `INSERT INTO demo_update VALUES( ?, ?, ?, ?, ?, ? )`).
CATCH cx_sql_exception INTO FINAL(exc).
...
ENDTRY.
SqlConnection
物件。但是,如果在執行 SQL 或處理結果時發生異常,SqlConnection
物件將不會關閉。如果這種情況時常發生的話,那麼資料庫將會用盡可用指標,並且無法再執行任何 SQL 查詢。
...
SqlConnection conn = new SqlConnection(connString);
SqlCommand cmd = new SqlCommand(queryString);
cmd.Connection = conn;
conn.Open();
SqlDataReader rdr = cmd.ExecuteReader();
HarvestResults(rdr);
conn.Connection.Close();
...
- void insertUser:(NSString *)name {
...
sqlite3_stmt *insertStatement = nil;
NSString *insertSQL = [NSString stringWithFormat:@INSERT INTO users (name, age) VALUES (?, ?)];
const char *insert_stmt = [insertSQL UTF8String];
...
if ((result = sqlite3_prepare_v2(database, insert_stmt,-1, &insertStatement, NULL)) != SQLITE_OK) {
MyLog(@"%s: sqlite3_prepare error: %s (%d)", __FUNCTION__, sqlite3_errmsg(database), result);
return;
}
if ((result = sqlite3_step(insertStatement)) != SQLITE_DONE) {
MyLog(@"%s: step error: %s (%d)", __FUNCTION__, sqlite3_errmsg(database), result);
return;
}
...
}
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(CXN_SQL);
harvestResults(rs);
stmt.close();
func insertUser(name:String, age:int) {
let dbPath = URL(fileURLWithPath: Bundle.main.resourcePath ?? "").appendingPathComponent("test.sqlite").absoluteString
var db: OpaquePointer?
var stmt: OpaquePointer?
if sqlite3_open(dbPath, &db) != SQLITE_OK {
print("Error opening articles database.")
return
}
let queryString = "INSERT INTO users (name, age) VALUES (?,?)"
if sqlite3_prepare(db, queryString, -1, &stmt, nil) != SQLITE_OK{
let errmsg = String(cString: sqlite3_errmsg(db)!)
log("error preparing insert: \(errmsg)")
return
}
if sqlite3_bind_text(stmt, 1, name, -1, nil) != SQLITE_OK{
let errmsg = String(cString: sqlite3_errmsg(db)!)
log("failure binding name: \(errmsg)")
return
}
if sqlite3_bind_int(stmt, 2, age) != SQLITE_OK{
let errmsg = String(cString: sqlite3_errmsg(db)!)
log("failure binding name: \(errmsg)")
return
}
if sqlite3_step(stmt) != SQLITE_DONE {
let errmsg = String(cString: sqlite3_errmsg(db)!)
log("failure inserting user: \(errmsg)")
return
}
}
ZipFile
的 finalize()
方法最終會呼叫 close()
,但是並不保證在叫用 finalize()
方法前會經過多少時間。因此,在繁忙的環境中,這可能會導致 JVM 用盡它所有的檔案控制碼。範例 2:在正常條件下,以下修正會在列印出所有 zip 檔項目之後,適當地關閉檔案控制碼。但是,如果在重複透過項目時發生異常,zip 檔案控制碼就不會關閉。如果經常發生此狀況,JVM 仍然可能會耗盡所有可用的檔案控制碼。
public void printZipContents(String fName) throws ZipException, IOException, SecurityException, IllegalStateException, NoSuchElementException {
ZipFile zf = new ZipFile(fName);
Enumeration<ZipEntry> e = zf.entries();
while (e.hasMoreElements()) {
printFileInfo(e.nextElement());
}
}
public void printZipContents(String fName) throws ZipException, IOException, SecurityException, IllegalStateException, NoSuchElementException {
ZipFile zf = new ZipFile(fName);
Enumeration<ZipEntry> e = zf.entries();
while (e.hasMoreElements()) {
printFileInfo(e.nextElement());
}
zf.close();
}