소프트웨어 개발보안 가이드 분석(2021) : 부적절한 인가

취약점에 대한 이해

"부적절한 인가" 문제는 시스템 내 중요 자원에 대한 접근 통제가 제대로 이루어지지 않아 발생하는 보안 취약점입니다. 이는 사용자가 부여받지 않은 권한으로 시스템 자원에 접근하는 경우를 말하며, 공격자에게는 시스템을 손쉽게 조작할 수 있는 기회를 제공합니다.

부적절한 인가는 주로 접근 제어 리스트(ACL)의 미흡한 구현, 인증 절차의 취약점, 권한 상승 공격을 통해 발생합니다. 예를 들어, 웹 애플리케이션에서 관리자 권한을 체크하지 않고 중요 기능을 수행하는 경우, 인가되지 않은 사용자도 중요 기능을 실행할 수 있게 되어 정보의 무단 조회, 변경, 삭제 등의 보안 사고로 이어질 수 있습니다.

API 인증이 제대로 이루어지지 않아, 인증받지 않은 사용자가 타인의 금융 정보에 접근할 수 있는 사건이 발생

실제로 부적절한 인가로 인한 보안 사고는 대한민국에서 여러 차례 발생했으며, 그 규모와 영향력도 상당했습니다. 예를 들어, 2014년에는 KB국민카드, 롯데카드, NH농협은행에서 발생한 개인정보 대량 유출 사건으로 약 2천만 명의 개인 정보가 노출되었고, KT에서는 약 1천2백만 명의 개인 정보가 유출되는 사건이 있었습니다. 이 외에도 다수의 금융 기관, 쇼핑몰, 포털 사이트에서도 유사한 유형의 사고가 발생했습니다. 이러한 사고들은 접근 제어의 실패로 인한 것으로, 기업과 기관은 이를 통해 보안 관리의 중요성을 깨달아야 합니다.

 

공격 메커니즘

프로그램이 모든 가능한 실행경로에 대해서 접근제어를 검사하지 않거나 불완전하게 검사하는 경우, 공격자는 접근 가능한 실행경로를 통해 정보를 유출할 수 있습니다.

공격자는 다양한 방법으로 부적절한 인가 취약점을 이용합니다. 가장 흔한 방법 중 하나는 권한 상승 공격으로, 시스템 내에서의 일반 사용자가 관리자와 같은 높은 권한을 획득하여 시스템을 조작합니다. 또한, 세션 하이재킹을 통해 이미 인증된 사용자의 권한을 탈취하여 악의적인 행위를 수행할 수도 있습니다.

 

취약한 코드와 안전한 코드 예시

취약한 코드 예시

public class AccountService {
  public Account getAccount(String userId) {
    // 인증 절차 없이 계정 정보를 반환
    return accountRepository.findByUserId(userId);
  }
}

위 코드는 사용자 인증을 거치지 않고 계정 정보를 조회하는 방식으로, 보안에 취약합니다.

안전한 코드 예시:

public class AccountService {
  public Account getAccount(String userId, User authenticatedUser) {
    if(authenticatedUser == null || !authenticatedUser.getId().equals(userId)) {
      throw new UnauthorizedAccessException();
    }
    return accountRepository.findByUserId(userId);
  }
}

안전한 코드는 사용자가 인증된 상태에서만 계정 정보를 조회할 수 있도록 검사하는 로직을 추가하여 보안성을 강화합니다.

 

JAVA 코드 응용 사례 : 쿠키를 이용한 사용자 검증 로직

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.ServletException;
import java.io.IOException;

public class UserValidation {

    public boolean isValidUser(HttpServletRequest request) throws ServletException, IOException {
        Cookie[] cookies = request.getCookies();
        String loginID = null;

        // 쿠키에서 loginID 찾기
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if ("loginID".equals(cookie.getName())) {
                    loginID = cookie.getValue();
                    break;
                }
            }
        }

        // loginID 존재 여부 및 유효성 검사
        if (loginID != null && validateLoginID(loginID)) {
            // 정당한 사용자
            return true;
        } else {
            // 정당하지 않은 사용자 또는 loginID가 없음
            return false;
        }
    }

    // loginID의 유효성을 검사하는 메서드
    private boolean validateLoginID(String loginID) {
        // 이 예제에서는 단순히 loginID의 유효성을 검사하는 로직을 구현합니다.
        // 실제로는 데이터베이스나 세션 관리 시스템을 통해
        // 제공된 loginID가 현재 활성화된, 유효한 세션에 속하는지 확인해야 합니다.

        // 예시: loginID가 데이터베이스에 존재하고, 세션이 활성화 상태인지 확인
        // 이 부분은 애플리케이션의 세션 관리 로직에 따라 다를 수 있습니다.
        boolean isValid = checkDatabaseForLoginID(loginID);

        return isValid;
    }

    // 데이터베이스에서 loginID를 검증하는 메서드 (가상 코드)
    private boolean checkDatabaseForLoginID(String loginID) {
        // 데이터베이스 조회 및 세션 상태 확인 로직
        // 예: "SELECT * FROM users WHERE loginID = ? AND session_active = true"
        return true; // 여기서는 단순화를 위해 항상 true를 반환합니다.
    }
}

이 코드는 HttpServletRequest 객체로부터 쿠키 배열을 가져와, loginID라는 이름의 쿠키를 찾습니다. 찾은 loginID 값의 유효성을 검증하는 과정을 거친 후, 해당 loginID가 유효하다면 요청을 정당한 사용자로 간주합니다. validateLoginID 메서드 내에서는 이 loginID가 현재 유효한 세션에 속하는지를 검사하는 로직을 구현해야 하며, 이는 일반적으로 데이터베이스 조회 등을 통해 수행됩니다.

이러한 접근 방식은 파라미터 변조 공격에 대응하여 애플리케이션의 보안을 강화하는 데 유용합니다. 다만, 쿠키를 사용할 때는 HTTPS와 같은 안전한 프로토콜을 사용하여 데이터 전송의 보안을 강화하는 것이 중요합니다.

  • 이전 소프트웨어 개발보안 가이드 분석(2021) : 적절한 인증 없는 중요 기능 허용
  • 다음 소프트웨어 개발보안 가이드 분석(2021) : 중요한 자원에 대한 잘못된 권한 설정