코드 품질이 낮으면 예측할 수 없는 동작이 발생합니다. 사용자 입장에서는 사용 편의성이 떨어지는 것으로 나타나는 경우가 많습니다. 공격자에게는 예상치 못한 방법으로 시스템에 부담을 줄 수 있는 기회가 됩니다.
Camera
개체를 참조합니다.Camera
개체를 사용하려 합니다. 리소스를 다시 획득하지 않은 Camera
개체에 대한 자세한 참조는 예외를 발생시키며 예외가 발생하지 않은 경우, 응용 프로그램 손상을 일으킬 수 있습니다.startPreview()
가 미리 릴리스한 Camera
개체에서 호출됩니다.
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()
함수는 Thread
개체 내의 모든 위치에서 ThreadDeath
예외를 발생시켜 개체가 일치하지 않는 상태가 되고 리소스가 누출될 수 있습니다. 이 API는 본질적으로 안전하지 않으므로 오래 전부터 더 이상 사용되지 않습니다.Thread.stop()
을 잘못 호출합니다.
...
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
인터페이스는 복제되면 정상적으로 작동하지 않을 수 있는 클래스의 완전한 복제(deep cloning)를 보장하지 않습니다. ICloneable
을 구현하고 다른 개체에 대한 기존의 참조를 포함하는 개체만 복사하는 단순 복제(shallow-cloning)만을 구현하는 클래스는 비정상적으로 작동할 수 있습니다. 개체 및 모든 참조된 개체를 복제하는 완전한 복제(deep cloning)가 clone 메서드의 일반적으로 예상되는 작동이므로 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 사양에는 boxing 변환이 다음과 같이 설명되어 있습니다. 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");
}
==
및 !=
연산자는 같은 개체에 포함된 문자열을 비교하도록 사용할 때에만 예상대로 작동합니다. 이를 위한 가장 일반적인 방법은 인턴(intern)된 문자열에 대한 것이고, 여기에서 이 문자열은 String
클래스에 의해 유지되는 개체의 풀에 추가할 수 있습니다. 한 번 문자열이 인턴(intern)되면, 모든 문자열 사용은 동일한 개체를 사용하고 같은 연산자가 예상대로 작동합니다. 모든 문자열 리터럴 및 문자열 값 상수는 자동으로 인턴(intern)됩니다. 다른 문자열은 String.intern()
을 수동으로 호출하여 인턴할 수 있어 현재 문자열의 정규 인스턴스를 반환하고, 필요한 경우 하나를 생성합니다.