계: Code Quality

코드 품질이 낮으면 예측할 수 없는 동작이 발생합니다. 사용자 입장에서는 사용 편의성이 떨어지는 것으로 나타나는 경우가 많습니다. 공격자에게는 예상치 못한 방법으로 시스템에 부담을 줄 수 있는 기회가 됩니다.

4 개 항목 찾음
취약점
Abstract
바이트 배열을 String으로 변환하면 데이터가 손실될 수 있습니다.
Explanation
바이트 배열의 데이터를 String으로 변환할 때 해당되는 문자 집합 외부의 데이터가 어떻게 변환되는지는 지정되지 않습니다. 따라서 데이터가 손실되거나, 적절한 보안 대책을 따르기 위해 이진 데이터가 필요할 때 보안 수준이 낮아질 수 있습니다.

예제 1: 다음 코드는 해시를 만들기 위해 데이터를 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 해시의 안정성이 떨어질 수 있으며, 충돌이 발생하기가 훨씬 쉬워집니다. 특히 기본 문자 집합 외부의 모든 데이터가 물음표 등의 같은 값으로 표현되는 경우에는 더욱 그러합니다.
References
[1] STR03-J. Do not encode noncharacter data as a string CERT
[2] When 'EFBFBD' and Friends Come Knocking: Observations of Byte Array to String Conversions GDS Security
[3] Standards Mapping - Common Weakness Enumeration CWE ID 486
desc.semantic.java.code_correctness_byte_array_to_string_conversion
Abstract
NaN 비교에서 항상 오류가 발생합니다.
Explanation
NaN과의 비교는 항상 false로 평가됩니다. 단, NaN은 순서가 지정되지 않으므로 != 연산자를 사용하는 경우에는 항상 true로 평가됩니다.

예제 1: 다음 코드는 변수가 NaN이 아님을 확인합니다.


...
if (result == Double.NaN){
//something went wrong
throw new RuntimeException("Something went wrong, NaN found");
}
...


이 코드는 resultNaN이 아님을 확인하려고 합니다. 그러나 NaN에서 == 연산자를 사용하는 경우 결과 값은 항상 false이므로 이 검사에서는 예외가 발생하지 않습니다.
References
[1] NUM07-J. Do not attempt comparisons with NaN CERT
[2] Java Language Specification Chapter 4. Types, Values, and Variables Oracle
[3] INJECT-9: Prevent injection of exceptional floating point values Oracle
[4] Standards Mapping - Common Weakness Enumeration CWE ID 486
desc.structural.java.code_correctness_comparison_with_nan
Abstract
해당 클래스 이름을 기반으로 개체 유형을 결정하면 예상치 못한 동작이 나타나거나 공격자가 악성 클래스를 삽입할 수 있습니다.
Explanation
공격자는 프로그램이 악성 코드를 실행하도록 만들기 위해 의도적으로 클래스 이름을 복제할 수 있습니다. 이러한 이유로 클래스 이름은 좋은 형식 식별자가 아니며 지정된 개체에 대한 신뢰를 부여하는 기준으로 사용해서는 안 됩니다.

예제 1: 다음 코드에서는 inputReader 개체의 입력을 신뢰할지 여부를 해당 클래스 이름을 기준으로 결정합니다. 공격자가 악성 명령을 실행하는 inputReader의 구현을 제공할 수 있는 경우, 이 코드는 개체의 양성 버전과 악성 버전을 구별할 수 없습니다.


if (inputReader.GetType().FullName == "CompanyX.Transaction.Monetary")
{
processTransaction(inputReader);
}
References
[1] Standards Mapping - Common Weakness Enumeration CWE ID 486
desc.dataflow.dotnet.code_correctness_erroneous_class_compare
Abstract
해당 클래스 이름을 기반으로 개체 유형을 결정하면 예상치 못한 동작이 나타나거나 공격자가 악성 클래스를 삽입할 수 있습니다.
Explanation
공격자는 프로그램이 악성 코드를 실행하도록 만들기 위해 의도적으로 클래스 이름을 복제할 수 있습니다. 이러한 이유로 클래스 이름은 좋은 형식 식별자가 아니며 지정된 개체에 대한 신뢰를 부여하는 기준으로 사용해서는 안 됩니다.

예제 1: 다음 코드에서는 inputReader 개체의 입력을 신뢰할지 여부를 해당 클래스 이름을 기준으로 결정합니다. 공격자가 악성 명령을 실행하는 inputReader의 구현을 제공할 수 있는 경우, 이 코드는 개체의 양성 버전과 악성 버전을 구별할 수 없습니다.


if (inputReader.getClass().getName().equals("com.example.TrustedClass")) {
input = inputReader.getInput();
...
}
References
[1] OBJ09-J. Compare classes and not class names CERT
[2] Standards Mapping - Common Weakness Enumeration CWE ID 486
desc.dataflow.java.code_correctness_erroneous_class_compare
Abstract
개체 클래스 이름을 기준으로 개체의 형식을 결정하면 예기치 못한 동작이 발생하거나 공격자가 악성 클래스를 삽입하도록 허용할 수 있습니다.
Explanation
공격자는 프로그램이 악성 코드를 실행하도록 만들기 위해 의도적으로 클래스 이름을 복제할 수 있습니다. 이러한 이유로 클래스 이름은 좋은 형식 식별자가 아니며 지정된 개체에 대한 신뢰를 부여하는 기준으로 사용해서는 안 됩니다.

예제 1: 다음 코드에서는 inputReader 개체의 입력을 신뢰할지 여부를 해당 클래스 이름을 기준으로 결정합니다. 공격자가 악성 명령을 실행하는 inputReader의 구현을 제공할 수 있는 경우, 이 코드는 개체의 양성 버전과 악성 버전을 구별할 수 없습니다.


if (inputReader::class.qualifiedName == "com.example.TrustedClass") {
input = inputReader.getInput()
...
}
References
[1] OBJ09-J. Compare classes and not class names CERT
[2] Standards Mapping - Common Weakness Enumeration CWE ID 486
desc.dataflow.kotlin.code_correctness_erroneous_class_compare
Abstract
정적 메서드는 재정의할 수는 없지만, 인스턴스 메서드로 호출하는 경우 숨겨진 것으로 표시될 수 있습니다.
Explanation
정적 메서드는 클래스의 인스턴스가 아닌 클래스 자체에 속하므로 원칙적으로 오버라이드할 수 없습니다. 정적 메서드가 하위 클래스에서 오버라이드된 것처럼 표시되는 경우도 있는데, 이로 인해 혼동을 하여 잘못된 메서드 버전을 호출하게 될 수도 있습니다.

예제 1: 다음 코드는 사용자 인증을 위한 API를 정의합니다.


class AccessLevel{
public static final int ROOT = 0;
//...
public static final int NONE = 9;
}
//...
class User {
private static int access;
public User(){
access = AccessLevel.ROOT;
}
public static int getAccessLevel(){
return access;
}
//...
}
class RegularUser extends User {
private static int access;
public RegularUser(){
access = AccessLevel.NONE;
}
public static int getAccessLevel(){
return access;
}
public static void escalatePrivilege(){
access = AccessLevel.ROOT;
}
//...
}
//...
class SecureArea {
//...
public static void doRestrictedOperation(User user){
if (user instanceof RegularUser){
if (user.getAccessLevel() == AccessLevel.ROOT){
System.out.println("doing a privileged operation");
}else{
throw new RuntimeException();
}
}
}
}


이 코드는 처음 보기에는 아무 문제가 없는 것 같습니다. 그러나 User 또는 RegularUser 클래스가 아닌 user 인스턴스에 대해 getAccessLevel() 메서드를 호출하므로 이 조건에서는 항상 true가 반환됩니다. 그리고 이 if/else 블록 부분으로 진입하기 위해 instanceof를 사용했더라도 제한된 작업이 수행됩니다.
References
[1] MET07-J. Never declare a class method that hides a method declared in a superclass or superinterface CERT
[2] Java Language Specification Chapter 8. Classes Oracle
[3] Standards Mapping - Common Weakness Enumeration CWE ID 486
desc.structural.java.code_correctness_hidden_method