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는 생성자에서 호출되는 함수의 하위 클래스를 먼저 확인하기 때문입니다.
if (fitz == null) {
synchronized (this) {
if (fitz == null) {
fitz = new Fitzer();
}
}
}
return fitz;
Fitzer()
개체만 할당되는 것을 보장하려 하지만 이 코드를 호출할 때마다 동기화 비용을 지불하지는 않으려는 경우가 있습니다. 이런 경우를 double-checked locking이라고 합니다.Fitzer()
개체를 할당합니다. 자세한 내용은 "Double-Checked Locking is Broken" Declaration을 참조하십시오[1].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()
...
}
finalize()
메서드는 super.finalize()
를 호출해야 합니다.finalize()
메서드로 super.finalize()
를 호출하는 것이 좋은 방법이라고 설명합니다[1].super.finalize()
호출이 생략된 것입니다.
protected void finalize() {
discardNative();
}