コードの質が低いと、予測できない動作につながります。ユーザーの視点には、それがしばしば使い勝手の悪さとなって現れます。攻撃者にとっては、予期せぬ方法でシステムにストレスを与える機会となります。
...
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" は、"上に点が付いたラテン語の大文字 I" になります。これは、Example 1
のように "script" という単語が検証でキャッチされないなどの予期しない結果につながる場合があり、クロスサイト スクリプティングの脆弱性を引き起こす可能性があります。
...
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
では、width
の値が 10 であるため、開発者は box.area
が 10 の倍数のランダム整数であると予想します。しかし実際は、常にハードコーディングされた 0 の値です。コンパイル時定数で宣言された static 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
であることを確認してから、それを誤って間接参照しています。foo
が if
ステートメントでチェックされたときに null
になっていると、null
間接参照 が発生し、これが NULL ポインター例外の原因となります。例 2: 次のコードでは、プログラマは変数
if (foo is null) {
foo.SetBar(val);
...
}
foo
は null
ではないと仮定しており、オブジェクトを間接参照することによってこの仮定を確認しています。しかし、その後プログラマが foo
と null
を比較したときに、この仮定は成り立たなくなります。foo
が if
ステートメントでチェックされたときに null
であるとすれば、間接参照されたときにも null
である可能性があり、これが NULL ポインタ例外の原因となる場合があります。間接参照が安全でないか、後で実行するチェックが不要であるかのいずれかです。例 3: 次のコードでは、プログラマは明示的に変数
foo.SetBar(val);
...
if (foo is not null) {
...
}
foo
を null
に設定しています。続いて、オブジェクトの null
値をチェックする前に foo
を間接参照しています。
Foo foo = null;
...
foo.SetBar(val);
...
}
null
であるかを確認する前に、null
の可能性があるポインタを間接参照する場合です。チェック後間接参照のエラーが発生するのは、プログラムが null
に対して明示的チェックを実行したにも関わらず、null
であることが判明しているポインタを間接参照した場合です。この種のエラーの多くは、タイプミスかプログラマの不注意が原因です。格納後間接参照のエラーは、プログラムが明示的にポインタを null
に設定し、後でそのポインタを間接参照した場合に発生します。このエラーは通常、変数の宣言時にプログラマがその変数を null
に初期化したことが原因で発生します。ptr
は NULL
ではないと仮定してします。この仮定は、プログラマがポインタを間接参照したときに明らかになります。その後プログラマが ptr
と NULL
を比較したときに、この仮定は成り立たなくなります。ptr
が if
ステートメントでチェックされたときに NULL
であるとすれば、間接参照されたときにも NULL
である可能性があり、これがセグメンテーション違反の原因となる場合があります。例 2: 次のコードでは、プログラマは変数
ptr->field = val;
...
if (ptr != NULL) {
...
}
ptr
が NULL
であることを確認してから、続いてそれを誤って間接参照しています。ptr
が if
ステートメントでチェックされたときに NULL
になっていると、null
Dereference が発生し、これがセグメンテーション違反の原因となります。例 3: 次のコードでは、プログラマが文字列
if (ptr == null) {
ptr->field = val;
...
}
'\0'
(実際は 0) または NULL
を忘れたため、NULL ポインタを間接参照し、セグメンテーション違反を引き起こしています。例 4: 次のコードでは、プログラマは明示的に変数
if (ptr == '\0') {
*ptr = val;
...
}
ptr
を NULL
に設定しています。続いて、オブジェクトの null
値をチェックする前に ptr
を間接参照しています。
*ptr = NULL;
...
ptr->field = val;
...
}
null
に対して明示的チェックを実行したにも関わらず、null
であることが判明しているオブジェクトを間接参照した場合です。この種のエラーの多くは、タイプミスかプログラマの不注意が原因です。foo
が null
であることを確認してから、続いてそれを誤って間接参照しています。foo
が if
ステートメントでチェックされたときに null
になっていると、null
Dereference が発生し、これが NULL ポインタ例外の原因となります。
if (foo == null) {
foo.setBar(val);
...
}
unsigned char
キャストを int
に戻しますが、その戻り値は char
タイプに割り当てられます。EOF
と区別できなくなる可能性があります。EOF
を比較します。
char c;
while ( (c = getchar()) != '\n' && c != EOF ) {
...
}
getchar()
の戻り値は char
にキャストされ、EOF
(int
) と比較されます。c
が 8 ビットの符号付きの値で、EOF
が 32 ビットの符号付きの値であると家庭した場合、0xFF で表わされる文字を getchar()
が返すと、c
の値は EOF
と比較して、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
の値が、次の File System 関数の第 1 パラメーター (バージョン番号) に渡されている。
__xmknod
2
の値が、次のワイド文字列関数の第 3 パラメーター (グループ引数) に渡されている。
__wcstod_internal
__wcstof_internal
_wcstol_internal
__wcstold_internal
__wcstoul_internal
3
の値が、次の File System 関数の第 1 パラメーター (バージョン番号) として渡されている。
__xstat
__lxstat
__fxstat
__xstat64
__lxstat64
__fxstat64
FILE *sysfile = fopen(test.file, "w+");
FILE insecureFile = *sysfile;
sysfile
は insecureFile
の割り当てで間接参照されるため、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
の値を 2 回設定しています。
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 パッケージの一部です。返されたライターに書き込むたびに、指定された優先度 (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 は、現在のアクティビティをバックグラウンドに送信する必要がある場合や、システムリソースの不足時に現在のアクティビティを一時的に破壊する必要がある場合に、これらのコールバックを呼び出します。 Camera
オブジェクトを適切に解放しないため、アクティビティが原因で他のアプリケーション (または同じアプリケーションの将来のインスタンス) はカメラにアクセスできません。 さらに、アクティビティの一時停止中に Camera
インスタンスを所有し続けた場合、電池が無駄に消費されてしまうため、ユーザー体験の質が低下することがあります。Camera
オブジェクトを解放するために使用されるべきベースの onPause()
メソッドを上書きもせず、シャットダウン処理中に適切に解放もしない Android アクティビティを記述しています。
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()
コールバックで解放されていないメディアオブジェクトを割り当てます。Android OS は、現在のアクティビティをバックグラウンドに送信する必要がある場合や、システムリソースの不足時に現在のアクティビティを一時的に破壊する必要がある場合に、これらのコールバックを呼び出します。アクティビティがメディアオブジェクトを適切に解放できないと、Android のメディアハードウェアに対する (他のアプリケーションまたは同じアプリケーションによる) 後続のアクセスが、ソフトウェア実装に左右される、あるいは完全に失敗することさえあります。多数の解放されていないメディアインスタンスをオープンしたままにしておくと、Android が例外を発生させ、事実上 Denial of Service を引き起す可能性があります。さらに、アクティビティの一時停止中に media インスタンスを所有し続けた場合、電池が無駄に消費されてしまうため、ユーザー体験の質が低下することがあります。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()
コールバックで閉じられていない Android SQLite データベースハンドラを割り当てます。Android OS は、現在のアクティビティをバックグラウンドに送信する必要がある場合や、システムリソースの不足時に現在のアクティビティを一時的に破壊する必要がある場合に、これらのコールバックを呼び出します。データベースを適切に閉じることができないと、アクティビティが常に再起動されている場合、使用可能なカーソルのデバイスが使い果たされてしまう可能性があります。また、実装の内容によっては、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
パスワードなどの本来アクセスできない情報にアクセスできます。例外を発生させる 1 つの方法として、過度に長い引数を 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();
}