コードの質が低いと、予測できない動作につながります。ユーザーの視点には、それがしばしば使い勝手の悪さとなって現れます。攻撃者にとっては、予期せぬ方法でシステムにストレスを与える機会となります。
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()
が意図しているスレッドとは別のスレッドを通知する可能性があります。start()
をコールする代わりに、スレッドの run()
メソッドをコールします。Thread
オブジェクトの run()
メソッドの直接コールはバグです。プログラマは制御の新規スレッドを開始しようとして、誤って start()
の代わりに run()
をコールしたため、run()
メソッドがコーラーの支配下のスレッドで実行されます。start()
の代わりに誤って run()
をコールしています。
Thread thr = new Thread() {
public void run() {
...
}
};
thr.run();
stop()
メソッドというリソースをコールしています。Thread
オブジェクトの stop()
メソッドの直接コールはバグです。プログラマーはスレッドの実行を中止しようとするかもしれませんが、スレッドの停止は適切な方法ではありません。Thread
の stop()
関数は ThreadDeath
例外を Thread
オブジェクトのどこかで引き起こす可能性があり、オブジェクトは不整合な状態になり、漏えいの可能性のあるリソースになります。API は本質的に安全ではないため、かなり前に廃止されています。Thread.stop()
をコールしている Java プログラムです。
...
public static void main(String[] args){
...
Thread thr = new Thread() {
public void run() {
...
}
};
...
thr.start();
...
thr.stop();
...
}
clone()
メソッドを実装しますが、Cloneable
インターフェイスは実装しません。clone()
と名付けられたメソッドを実装するため、プログラマはこのクラスで Cloneable
インターフェイスの実装を意図したようです。しかし、クラスは 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
のボクシングされたプリミティブ型を使用して、2 つの 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()
メソッドと比較してください。==
または !=
を使用し、2 つのオブジェクトの値ではなく、オブジェクトが等価かどうかを比較します。このような場合、2 つの参照が等価でない可能性が高いのです。
if (args[0] == STRING_CONSTANT) {
logger.info("miracle");
}
==
および !=
演算子は、等価であるオブジェクトに含まれる文字列を比較するために使用されるときにのみ、予期されるとおりに動作します。これが発生する一般的な状況は、文字列が抑留され、String
クラスにより保持されるオブジェクトのプールにこの文字列が追加される場合です。文字列が一度抑留されると、同じオブジェクトと等価演算子を使用する文字列のすべての使用は、予期されるとおりに動作します。すべての文字列リテラルおよび文字列値の定数は、自動的に抑留されます。その他の文字列は String.intern()
を呼び出して手動で抑留できます。これにより、現在の文字列の正準な (規則に沿った) インスタンスが返されます (必要な場合には、正準なインスタンスが作成されます)。