본문 바로가기
  • 개발 삽질 블로그
프로그래밍/Java Programming

Java Secure Coding Rule

by 갹둥 2023. 4. 20.

소프트웨어의 중요성

소프트웨어의 사소한 결함이 목숨을 빼앗아 갈 수도 있음!

 

소프트웨어 개발보안: 안전한 소프트웨어 개발을 위해 보안을 고려하여 기능을 설계 및 구현하고 잠재적인 보안취약점(보안약점)을 제거하는 소프트웨어 개발 과정에서의 일련의 보안활동

 

SSDL: SDL 단계마다 필수적인 보안과 무결성을 보증하는 소프트웨어 방법론

 

보안약점 vs 보안 취약점

-보안약점(SW weekness): 해킹 등 실제 보안사고에 악용될 수 있는 보안 취약점의 근본 원인

-보안 취약점(SW Vulnerability): 소프트웨어 실행 시점에 보안약점이 원인이 되어 발생하는 실제적인 위협

 

안전한 소프트웨어: 보안 기능을 수행하는 소프트웨어 X, 시스템을 신뢰할 수 있는 상태로 유지할 수 있도록 만들어진 소프트웨어

 

 

#Rule 22. Minimize the scope of variables

-변수의 영역 범위를 최소화하라

-공통적인 프로그래밍 오류를 피할 수 있음. 코드의 가독성이 높아짐

- 사용되지 않은 변수들 쉽게 탐지 가능, 유지보수성 향상

-가비지 콜렉터가 좀 더 빠르게 객체 회수 가능

public class Scope{
	public static void main(String[] args){
    	int i = 0;
        for(i = 0; i < 10; i++){
        	// ...
           }
        	//...
    }
}

/*
	문제점: i가 for문에서만 사용되는데 메서드 영역 내에서 선언됨
    가능한 영역 범위를 최소화하는 것이 좋음
*/

Solution

for(int i = 0; i < 10; i++){
	//...
}

 

 

public class Foo{
	private int count;
    
    public void counter(){
    	count = 0;
        // ...
    }
    
    public boolean condition(){
     //Not use count
     }
     
     //...
}

counter() 메서드에서만 사용되는 count 변수를 메서드 밖에서 선언함

-> 메서드를 복사하여 사용할 경우 재사용성 감소, 분석의 효율성도 감소

public void counter(){
	int count;
   	//...
}

메서드의 지역변수로 선언하는 것이 적절

 

 

#Rule 23 Minimize the scope of the @SupperessWarnings annotation

-@SuppressWarnings 주석의 범위를 최소화하라

-@SuppressWarnings 주석은 Class, method, 변수 선언 등에 사용될 수 있음

-범위를 최소화해서 좀 더 작은 영역에 대한 경고만을 억제하도록 하는 것이 중요함

-미검사 경고는 코드가 타입-안전하다는 것이 보장되었을 때만 허용될 수 있음

@SupperessWarnings("unchecked") //전체 클래스에 대한 미검사 경고 억제
class Legacy{
	Set s = new HashSet(); //제네릭을 사용 안 함
    public final void doLogic(int a, char c){
    	s.add(a);
        s.add(c); //Type-unsafe
    }
}

나중에 ClassCastException이 발생할 수 있음

 

Solution

class Legacy{
	@SupperessWarnings("unchecked")
    Set s = new HashSet();
    public final void doLogic(int a, char c){
    	s.add(a);
        s.add(c);
    }
}

 

-범위를 줄이지 못 하면 런타임 예외가 발생할 수 있고 타입-안전성을 보장하지 못할 수 있음

*타입 안정성을 높인다는 것은 의도하지 않는 타입의 객체가 저장되는 것을 막고, 저장된 객체를 꺼내올 때 원래의 타입과 다른 타입으로 잘못 형변환되어 발생할 수 있는 오류를 줄어준다는 것이다.

 

#Rule 24. Minimize the accessibility of classes and their members

-클래스와 멤버들에 대한 접근성을 최소화하라

-접근 수정자를 통해 클래스와 클래스 멤버들(클래스, 인터페이스, 필드, 메서드)에 대해 접근성을 지정할 수 있음

*접근 수정자

private: 클래스 내에서만 접근 가능

default: 같은 패키지 내에서만 접근 가능

protected: 같은 패키지와 하위 클래스에서만 접근 가능

public: 모두 접근 가능

-최소한의 접근성만을 허용하여 악성코드가 안정성을 훼손할 수 있는 기회를 최소화해야 함

-데이터 멤버들을 private로 선언하고 엑세스 가능한 래퍼 메서드를 제공하라, 필드는 private 선언 권고

Pulbic final class Point{
	private final int x;
    
    public Point(int x){ //pulbic 생성자를 사용하면 변수를 private으로 선언 한 의미가 없어짐
    	this.x = x;
    }
    
    public int getX(){
    	return x;
    }
}

비신뢰 코드가 Point 인스턴스를 생성하고 private 변수인 x에 접근할 수 있음

 

Solution 1. public 메서드를 가진 final 클래스

*최상위 클래스는 private으로 선언할 수 없음

final class Point{
	private final int x;
    
    Point(int x){
    	this.x =x;
    }
    
    public int GetX{
    	return x;
    }
}

 

#Solution 26. Always provide feedback about the resulting of a method

-메서드 결과값에 대해 항상 피드백을 제공하라

-클라이언트가 올바른 결과와 그렇지 않은 결과를 구분할 수 있도록 하고 어떠한 올바르지 않은 결과도 신중하게 처리할 수 있도록 지원해야 함

-유효한 값 vs 오류 값 vs 예외

-모호한 반환값 사용 x

pubic void updateNode(int id, int newValue){
	Node current = root;
    while(current != null){
    	if(current.getId() == id ){
        	current.setValue(newValue); //여기서 오류가 발생할 가능성도 있음;
            break; //어떤 노드를 수정했는지, 수정이 일어났는지, 성공했는지 알 수 없음
        }
        current = current.next;
    }
}

적절한 반환값이 없어 수정이 일어났는지, 어느 노드가 수정됐는지, 성공했는지 여부를 알 수 없다.

 

Solution1. Boolean

pubic boolean updateNode(int id, int newValue){
	Node current = root;
    while(current != null){
    	if(current.getId() == id ){
        	current.setValue(newValue); //여기서 오류가 발생할 가능성도 있음;
            return true; //수정됨
        }
        current = current.next;
    }
    return false;
}

 

Solution2. 수정된 노드 반환, Null 반환값

pubic Node updateNode(int id, int newValue){
	Node current = root;
    while(current != null){
    	if(current.getId() == id ){
        	current.setValue(newValue); //여기서 오류가 발생할 가능성도 있음;
            return current;
        }
    }
    return null;
}

*인밴드 표시자, NullPointerException이 발생할 수 있음

 

Solution3. Exception 

pubic Node updateNode(int id, int newValue) throws NodeNotFoundException{
	Node current = root;
    while(current != null){
    	if(current.getId() == id ){
        	current.setValue(newValue); //여기서 오류가 발생할 가능성도 있음;
            return current;
        }
    }
    throw new NodeNotFoundException(); //예외처리
}

*예외처리가 항상 적절한 것은 아님

 

#Rule 28. Do not attach significance to the ordinal associated with an enum

-enum에서 부여한 순서번호에 의미를 두지 말라

*enum: 자바의 열거형 타입, 열거 상수 위치 번호를 반환하는 ordinal()이라는 메서드를 가진다

-enum 상수의 ordinal()애 의미를 부여하면 오류를 발생시킬 수 있음

-ordinal()값은 프로그래머가 세팅할 수 없는 값이기 때문에 사용을 지양해야 함

enum HydroCarbon{
	METHANE, ETHANE, PROPANE, BUTANE, PENTANE, HEXANE, HEPTANE, OCTANE, NONANE, DECANE;
    
    public int getNumberOfCarbons(){
    	return ordinal() + 1;
    }
}

순서가 변경되거나 새로운 상수가 추가되면 유지보수에 문제가 있음

일반적으로 정수 값을 유도해내기 위해 순서번호를 이용하는 것은 프로그램의 유지보수성을 감소시키고 오류를 초래할 수 있음

enum HydroCarbon{
	METHANE(1), ETHANE(2), PROPANE(3), BUTANE(4), PENTANE(5), HEXANE(6), BENZENE(6), HEPTANE(7), OCTANE(8),
    NONANE(9), DECANE(10);
    
    private int numOfCarbon;
    private HydroCarbon(int n){
    	numOfCarbon = n;
    }
    public int getnumOfCarbon(){
    	return numOfCarbon;
    }
}

 

#Rule 29. Be aware of numeric promotion behavior

-숫자의 확대변환 동작을 주의하라

*숫자의 확대변환: 산술 연산자의 피연산자들을 공통 타입으로 변환해서 연산이 수행될 수 잇도록 하기 위한 것

1. 피연산자들이 레퍼런스 타입이면 언박싱 수행

2. double로 변환

3. float로 변환

4. long

5. int

-정수에서 실수로의 변환은 정확도를 떨어뜨릴 수 있음

int x = 214783642
x += 1.0f

위 연산식에서는 연산을 수행하기 전에 int에서 float으로 확대변환이 일어난다. 그 과정에서 float의 유효숫자 범위를 넘어가기 때문에 반올림이 되어 정확하지 않은 숫자로 변환된다. 따라서 결과값은 정확도가 떨어지는 값이 나옴

double x = 21474836442;
x += 1.0;

피연산자를 double 타입으로 바꾸면 유효범위를 넘어가지 않음

cf) memory를 아껴야 하는 프로그램이라면 정확도가 좀 떨어지더라도 float을 사용하는 방법을 선택할 수 있음

 

 

#Rule 30 Enable compile-time type checking of variable arity parameter  types

-가변형 매개변수의 타입에 대해 컴파일 검사를 시행하라

-가변형 매개변수로 Object나 제네릭 타입이 사용될 경우 컴파일 때 타입 검사를 하는 것은 비효율적

-메서드의 매개변수에 가장 가능성 있는 특정 타입을 사용함으로써 컴파일 시에 가변형 메서드에 대한 강력한 타입 검사를 수행하도록 하라

-런타임 에러 처리보다 컴파일시 걸러주는 것이 더 안정적임

<T> double sum(T... args){
 	//...
}

제네릭 타입으로 받았기 때문에 컴파일러가 Number 타입이 아닌 매개변수를 걸러주지 못하기 때문에 비효율적이다.

<T extends Number> double sum(T... args){
	//...
}

Number타입을 상속받은 제네릭 클래스로 구체화하여 런타임 오류 발생 가능성을 줄였다. 

 

 

 

#Rule 31. Do not apply public final to constants whose value might change in later release

-이후의 릴리즈에서 변경될 수 있는 상수에 public final을 사용하지 말라

-public final 필드를 읽는 어떤 컴파일 단위에서도 그 필드 값을 인라인으로 삽입하도록 허용

-클래스 선언이 수정되어 필드에 다른 값을 주게 되어도 public final 필드값을 읽는 컴파일 단위는 재컴파일 될 때까지 여전히 이전 값으로 인식

-참조한 코드를 재컴파일하지 않으면 변경사항이 적용되지 않음

-수학 상수가 아니라면 static final 클래스 변수로 선언하지 않는 것이 좋음

class Foo{
	public static final int version = 1;
}

class Bar{
	...{
    	System.out.println(Foo.version);
    }
}

Foo의 version이라는 필드가 변경되어도 Bar를 재컴파일 하지 않으면 올바르지 못한 값을 출력한다.

 

class Foo{
	private static int version = 1;
    public static final int getVersion(){
    	return version;
    }
}

class Bar{
	...{
    	System.out.println(Foo.getVersion());
    }
}

 

 

 

#Rule 33. Prefer user-defined exceptions over more general exception types

-일반적인 예외 타입 보다는 사용자 -정의 예외를 사용하라

-예외는 그 타입에 의해서 포착됨

-특정한 목적의 예외를 정의하는 것이 나음

-일반적인 예외 타입을 발생시키는 것은 코드를 이해하고 유지하기 어렵게 만들고, 예외처리 메커니즘의 장점을 무산시킴

*Exception은 CheckedException과 UncheckedException이 있음. RunTimeException은 UncheckedException임

-try-catch문으로 구체적인 예외를 따로 처리하는 것이 좋음

 

try{
	doSomething();
}catch(Throwable e){ //모든 예외가 하나의 catch문으로 잡힘
	switch(e.getMessage()){
    	case "file not found":
        	//...
            break;
        //...
        default: throw e;
    }
}

에러 메세지가 변경되면 의도대로 예외처리가 작동하지 않음

 

public class TimeoutException extends Exception{
	//...
}

 //...

try{

}catch(TimeoutException te){
 	//...
}catch(FileNotFoundException fe){
    //...
}

구체적인 사용자 정의 예외를 정의하고 사용하여 적절하게 예외처리를 할 수 있음

 

 

#Rule 37:Do not shadow or obscure identifiers in subscopes

-부분영역의 식별자들을 섀도잉하거나 차폐하지 말라

*Obscure: 차폐현상

단순이름이 패키지명, 타입(클래스)명 혹은 변수명으로 해석될 수 있는 문맥에서 타입보다는 변수명으로 패키지보다는 타입명으로 해석됨

 

*Shadowing: 섀도잉 현상

지역변수가 해당 영역에서 이름이 같은 다른 변수에 대한 접근을 불가하게 만듬

 

-어떤 식별자도 그것을 포함하고 있는 영역에서 다른 식별자를 차폐하거나 섀도잉해서는 안된다. 

class MyVector{
	private int val = 1;
    public void doLogic(){
    	int val; //메서드 변수가 인스턴스 변수 이름을 가림
    }
}

doLogic() 함수에서는 똑같은 이름의 변수를 사용하였기 때문에 인스턴스 변수 val에 접근할 수 없다. 

class MyVector{
	private int val = 1;
    private void doLogic(){
    	int newValue;
    }
}

변수의 이름을 다르게 설정하면 필드 섀도잉 현상을 피할 수 있다. 

'프로그래밍 > Java Programming' 카테고리의 다른 글

JDBC Programming  (0) 2022.12.10