[EVI$ION 7기] OWASP top 10 / A09 Security Logging and Monitoring Failures

김예원·2024년 11월 24일

Security Logging and Monitoring Failures

애플리케이션의 보안 이벤트를 적절히 기록하고 이를 실시간으로 모니터링하지 않아 발생하는 취약점

보안로그와 모니터링은 사이버 공격을 탐지하고 대응하는 첫 번째 방어선.
적절한 로깅이 이루어지지 않을 경우 공격자의 시스템 침입을 인지하지 못하고, 사건 발생 후에도 원인 추적이나 공격 경로 분석이 어려움.
+ 컴플라이언스(법적/규제) 요구사항을 충족하지 못할 가능성이 있음

발생 원인

1) 중요 이벤트를 로깅하지 않음
2) 불충분한 로깅 정보: 기록된 로그가 불완전하거나, 추적 가능성을 낮추는 방식으로 저장되는 경우
3) 실시간 모니터링의 부족: 로그 데이터를 실시간으로 분석하지 않아 의심스러운 활동이나 침입을 신속히 감지하지 못하는 경우
4) 적절한 경고 메커니즘의 부재: 의심스러운 활동에 대해 알림 시스템이 없거나 잘못 설정된 경우
5) 로그 보호 미비: 로그 데이터가 조작, 삭제 또는 무단 접근에 취약한 경우

주의: 하지만 로깅/경고 이벤트를 사용자에게 표시하면 정보 유출에 취약해짐 (*A01:2021-Broken Access Control)

예방 대책

1) 모든 로그인, 액세스 제어 및 서버 측 입력 검증 실패를 충분한 사용자 컨텍스트와 함께 기록
-> 의심스럽거나 악의적인 계정을 식별하고, 지연된 포렌식 분석을 허용할만큼 충분한 시간동안 보관할 수 있는지 확인
2) 로그 관리 솔루션에서 쉽게 사용할 수 있는 형식으로 로그가 생성되는지 확인
3) 로깅 또는 모니터링 시스템에 대한 주입이나 공격을 방지하기 위해 로그 데이터가 올바르게 인코딩되었는지 확인
4) 고가 거래에는 변조나 삭제를 방지하기 위한 무결성 제어를 통해 감사 추적을 실시(추가적인 전용 데이터베이스 테이블을 포함)
5) DevSecOps 팀은 의심스러운 활동을 감지하고 신속하게 대응할 수 있도록 효과적인 모니터링과 경고 시스템 구축
6) NIST(National Institute of Standards and Technology) 800-61r2 이상과 같은 사고 대응 및 복구 계획을 수립 및 채택

주목할만한 CWE

CWE-778 불충분한 로깅

발생 원인
  • 로그인 시도 실패 등 보안에 중요한 이벤트가 제대로 기록되지 않으면 흔적이 없기 때문에 악의적인 행동을 감지하기 더 어려워짐
<예제 1>
<system.serviceModel>
  <behaviors>
    <serviceBehaviors>
      <behavior name="NewBehavior">
        <serviceSecurityAudit auditLogLocation="Default"
          suppressAuditFailure="false"
          serviceAuthorizationAuditLevel="None"
          messageAuthenticationAuditLevel="None" />
      ...
    </serviceBehaviors>
  </behaviors>
</system.serviceModel>
  • WCF(Windows Communication Foundation)에서 서비스 보안 감사 기능을 설정하는 코드. 애플리케이션 보안을 강화하기 위해 WCF의 보안 관련 이벤트(인증 성공/실패 등..)를 기록하는 기능을 활성화함

🔴문제점: serviceAuthorizationAuditLevel=”None”
-> 서비스 권한 부여 이벤트(성공/실패)를 기록하지 않고 있음
messageAuthenticationAuditLevel=”None”
-> 메세지 인증 이벤트(성공/실패)를 기록하지 않고 있음

💡해결방안:

    <system.serviceModel>
      <behaviors>
        <serviceBehaviors>
          <behavior name="NewBehavior">
            <serviceSecurityAudit auditLogLocation="Default"
              suppressAuditFailure="false"
              serviceAuthorizationAuditLevel="SuccessAndFailure"
              messageAuthenticationAuditLevel="SuccessAndFailure" />
          ...
        </serviceBehaviors>
      </behaviors>
    </system.serviceModel>

serviceAuthorizationAuditLevel=”SuccessAndFailure”
-> 권한 부여 이벤트의 성공과 실패 모두 기록
성공: 누가 성공적으로 접근했는지 확인
실패: 잘못된 접근 시도를 탐지
messageAuthenticationAuditLevel=”SuccessAndFailure”
-> 메시지 인증 이벤트의 성공과 실패 모두 기록
성공: 인증된 메시지의 타임스탬프, 발신자 정보를 기록
실패: 잘못된 인증 시도를 탐지
auditLogLocation=”Default”
-> 로그를 기록할 기본 위치를 지정(Windows 이벤트 로그 등)
suppressAuditFailure=”false”
-> 로깅 실패 시 예외 처리

<예제 2>
if LoginUser(){
// Login successful
RunProgram();
} else {
// Login unsuccessful
LoginRetry();
}
  • 사용자 인증 시도를 처리하는 코드. 로그인이 실패하면 재시도가 이루어짐.
  • LoginUser( ) 함수가 호출되어 사용자 인증을 수행
    -> 성공: 성공 메시지를 로그에 기록하고 프로그램 실행을 위해 RunProgram( ) 호출
    -> 실패: 실패 메시지를 로그에 기록하고 LoginRetry( )로 다시 로그인 시도

🔴문제점:
(1) 로그 미작성으로 로그인 실패 이벤트를 기록하지 않음.
브루트포스 공격 탐지 불가(공격자가 비밀번호를 반복적으로 시도하더라도 로그가 없어 이를 탐지할 수 없음), 보안 사고 분석 이 어려워짐(문제가 발생했을때, 실패한 로그인 시도에 대한 기록이 없어 누가 공격을 시도했는지 추적이 어려움)
(2) 감사추적의 부재로 성공 여부와 관계없이 로그인 시도에 대한 기록이 없으면 시스템 사용 이력을 추적할 수 없음.
(3) 컴플라이언스 요구사항 미충족으로, 일부 보안 규제는 실패한 로그인 시도를 기록할 것을 요구하며 이를 준수하지 않을 시 법적/금전적 문제가 발생할 수 있음.

💡해결방안:

    if LoginUser() {
        // Login successful
        log.warn("Login by user successful.");
        RunProgram();
    } else {
        // Login unsuccessful
        log.warn("Login attempt by user failed, trying again.");
        LoginRetry();
    }

(1) 로그 작성 추가: log.warn( )을 사용해 성공/실패 이벤트를 기록(-> 성공/실패 로그를 통해 브루트포스 공격이나 권한 남용을 탐지 가능)
(2) 로그 내용 설계: 로그 메시지에 민감한 정보(비밀번호, 원래 입력된 사용자 이름)를 포함하지 않음.
(3) 운영 효율성 향상: 실패한 로그인 시도를 기록함으로써 이상 징후 탐지에 필요한 정보를 제공
(4) 컴플라이언스 준수

<예제 3>
az storage logging update --account-name --account-key --services b --log d --retention 90
  • Azure Storage Logging에서 블롭 서비스의 로그 설정을 관리하는 방법을 보여주는 코드.

🔴문제점:
(1) --log d는 삭제 이벤트만 기록하도록 설정
(2) 블롭 서비스에서 발생하는 다른 중요한 이벤트(읽기와 쓰기)는 기록되지 않음.
(3) 스토리지의 주요 이벤트를 기록하지 않아 데이터 무결성 및 보안성이 저하

💡해결방안:

    az storage logging update --account-name --account-key --services b --log rwd --retention 90

(1) 포괄적인 로그 설정: --log rwd를 사용하여 읽기, 쓰기, 삭제 이벤트 모두를 기록, 로그 범위를 넓힘으로써 데이터 사용 이력을 완전히 추적
(2) 보안강화: 삭제 외에 쓰기 이벤트를 기록하여 누가 데이터를 변경했는지 파악 가능. 읽기 로그를 통해 민감한 데이터에 대한 비정상적 접근 탐지 가능
(3)

    Set-AzStorageServiceLoggingProperty -ServiceType Queue -LoggingOperations read,write,delete -RetentionDays 5 -Context $MyContextObject

- --retention 90로그 데이터를 90일동안 보관하여 충분한 분석 기간을 제공
(4) 다른 서비스에 대한 적용: -ServiceType Queue
를 통해 Azure Queue 서비스의 로그를 설정, 다양한 Azure 서비스에서 동일한 방식으로 설정 가능
(5) 유연한 로그 설정: -LoggingOperations read,write,delete를 통해 읽기, 쓰기, 삭제 이벤트 기록
(6) 짧은 보존 기간: -RetentionDays 5를 설정하여 로그 데이터를 5일 동안만 보관, 짧은 보존 기간은 스토리지 비용을 절약하면서도 기본적인 보안 로그 기록을 유지

CWE-117 로그에 대한 부적절한 출력 중화

발생 원인
  • 로그 출력 시 출력 중립화를 올바르게 수행하지 않아서 발생하는 보안 취약점.
    악의적인 사용자 입력이 로그에 기록될 때 발생, 로그를 분석하거나 처리하는 과정에서 오해를 일으키거나 악의적인 행위로 이어질 수 있음.

출력 중립화? 사용자 입력 데이터를 출력 목적에 맞게 변환하거나 필터링하여, 악성코드나 특수문자가 애플리케이션의 정상 동작을 방해하지 않도록 처리하는 과정

로그 파일에 적절히 중립화되지 않은 데이터를 기록하면
->
1) 로그 조작: 악의적인 입력이 로그를 변조하거나 추가적인 로그 항목으로 보이게 만듦.
2) 로그 분석 실패: 로그 분석 툴이나 관리자에게 잘못된 정보를 제공
3) 잠재적 시스템 공격: 악의적인 페이로드가 로그를 통해 시스템에서 실행되거나 보안 문제를 야기

<예제 1>
String val = request.getParameter("val");
try {

int value = Integer.parseInt(val);
}
catch (NumberFormatException) {
log.info("Failed to parse val = " + val);
}
...
  • 요청 객체에서 정수 값을 읽으려고 시도하는 코드. parseInt 호출이 실패하면 입력은 무슨 일이 일어났는지 나타내는 오류메시지와 함께 기록됨.
  • val : 사용자가 요청 파라미터를 통해 전달하는 값이다.
  • Integer.parseInt(val) : 입력값을 정수로 변환하려 시도, 변환이 실패하면 NumberFormatException이 발생
  • log.info : 변환 실패 시, 입력값과 함께 오류 메시지를 기록

🔴문제점:
(1) 로그 출력 시 사용자 입력값의 검증 부족: 입력값에 포함된 특수 문자가 제대로 처리되지 않고 로그에 그대로 기록함

  • %0a → 줄바꿈, %3d → =와 같은 URL 인코딩된 문자들이 로그 출력 시 디코딩되어 잘못된 로그 형식을 유발
    (2) 로그 메시지 중립화 미적용: 로그 기록 전에 특수문자나 잠재적 악성 입력값을 이스케이프하거나 필터링x

💡해결방안:
(1) 사용자 입력 중립화: 특수문자를 이스케이프 처리하거나 제거하여 악성 입력을 무력화함

        String val = request.getParameter("val");
        **val = sanitizeInput(val); // 입력값 중립화**
        try {
            int value = Integer.parseInt(val);
        } catch (NumberFormatException e) {
            log.info("Failed to parse val = " + val); // 안전하게 로그 기록
        }
        

(2) 로그 메시지 템플릿 사용: 로그 출력 시 문자열 연결 대신 템플릿 기반 메서드를 사용하면 중립화를 자동으로 수행
(3) 민감 데이터 기록 금지

CWE-223 보안 관련 정보의 생략

발생 원인
  • 보안 관련 정보(실패한 인증 시도, 권한 부족 오류)가 로그에 기록되지 않거나 불완전하게 기록되었을 때 발생
<예제 1>
function login($userName,$password){
		if(authenticate($userName,$password)){
				return True;
		}
		else{
				incrementLoginAttempts($userName);
				if(recentLoginAttempts($userName) > 5){
						writeLog("Failed login attempt by User: " . $userName . " at " + date('r') );
				}
		}
}
  • 의심스러운 여러 로그인 시도를 기록하는 코드.
  • authenticate($userName, $password): 사용자 인증 수행
  • 성공 시 True를 반환, 실패 시 로그인 실패 처리 로직 실행
  • incrementLoginAttempts($userName): 로그인 실패 횟수를 증가
  • recentLoginAttempts($userName): 최근 로그인 시도 횟수를 반환
  • 로그 작성 조건: 최근 실패 횟수가 5회를 초과할 경우에만 로그를 기록

🔴문제점:
(1) 로그 누락 발생:

  • 실패한 모든 로그인 시도가 기록되지 않음
  • 실패 횟수가 5 이하인 경우는 로그에 남지 않으므로, 초기 공격 징후를 놓칠 수 있음
    (2) 공격자의 우회 가능성
    (3) 보안 관리의 어려움

💡해결방안:
(1) 모든 실패한 로그인 시도 기록: 로그는 실패한 시도의 전체 내역을 보여야 하며, 특정 임계값에 의존해서는 안됨

        function login($userName, $password) {
            if (authenticate($userName, $password)) {
                return True; // 인증 성공
            } else {
                incrementLoginAttempts($userName); // 실패 횟수 증가
                writeLog("Failed login attempt by User: " . $userName . " at " . date('r'));
            }
        }

(2) 로그의 민감한 정보보호: 로그에 사용자 이름 등 민감한 정```php
writeLog("Failed login attempt at " . date('r') . ". Username hash: " . hash('sha256', $userName));

    ```

-> 사용자 이름 대신 해시값을 기록하여 민감한 정보를 보호
(3) 실패 임계값 초과 시 경고 로그 추가: 모든 실패 시도를 기록하되, 임계값 초과 시 경고 수준의 로그를 추가로 작성

        function login($userName, $password) {
    if (authenticate($userName, $password)) {
        return True; // 인증 성공
    } else {
        incrementLoginAttempts($userName); // 실패 횟수 증가
        writeLog("Failed login attempt by User: " . $userName . " at " . date('r'));
        if (recentLoginAttempts($userName) > 5) { // 5회를 초과하면 추가 경고 로그
            writeLog("WARNING: Multiple failed login attempts detected for User: " . $userName);
        }
    }
}

CWE-532 로그 파일에 민감한 정보 삽입

발생 원인
  • 민감한 정보를 로그에 기록하여 정보 노출 위험을 초래
  • 애플리케이션이 동작 중 발생하는 이벤트를 기록하기 위해 로그를 사용하는 경우, 의도치 않게 민감한 정보(예: 비밀번호, 세션토큰, 개인 식별정보)를 포함시키면 보안 위협으로 이어질 수 있음

ex)

// 사용자가 입력한 비밀번호를 로그에 기록
public boolean authenticate(String username, String password) {
    if (isValidUser(username, password)) {
        log.info("User authenticated: " + username + ", Password: " + password); // 비밀번호 노출
        return true;
    } else {
        log.info("Failed login attempt for user: " + username + ", Password: " + password); // 비밀번호 노출
        return false;
    }
}

: 사용자의 비밀번호를 로그에 직접 기록함. 만약 로그 파일이 외부에 노출되거나 공격자가 접근할 경우 비밀번호가 유출되는 위험.

<예제 1>
locationClient = new LocationClient(이것, 이것, 이것);
locationClient.connect();
currentUser.setLocation(locationClient.getLastLocation());
...

catch(Exception e) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("죄송합니다. 이 애플리케이션에서 오류가 발생했습니다.");
AlertDialog alert = builder.create();
alert.show();
Log.e("ExampleActivity", "예외 발생: " + e + " User에서:" + User.toString());
}
  • 현재 사용자에 대한 위치 정보를 저장하는 코드.
    애플리케이션이 예외를 만나면 사용자 객체를 로그에 기록함.
    사용자 객체에는 위치 정보가 포함되어 있으므로 사용자 위치도 로그에 기록됨.
<예제 2>
public BankAccount getUserBankAccount(String username, String accountNumber) {
BankAccount userAccount = null;
String query = null;
try {
if (isAuthorizedUser(username)) {
query = "SELECT * FROM accounts WHERE owner = "
+ username + " AND accountID = " + accountNumber;
DatabaseManager dbManager = new DatabaseManager();
Connection conn = dbManager.getConnection();
Statement stmt = conn.createStatement();
ResultSet queryResult = stmt.executeQuery(query);
userAccount = (BankAccount)queryResult.getObject(accountNumber);
}
} catch (SQLException ex) {
String logMessage = "Unable to retrieve account information from database,\nquery: " + query;
Logger.getLogger(BankManager.class.getName()).log(Level.SEVERE, logMessage, ex);
}
return userAccount;
}
  • getUserBankAccount 메서드는 제공된 사용자 이름과 계좌번호를 사용하여 데이터베이스를 쿼리하여 데이터베이스에서 은행 계좌 객체를 검색하는 기능을 수행함. 데이터베이스를 쿼리할 때 SQLException이 발생하면 오류 메시지가 생성되어 그 파일에 출력.

🔴문제점: 생성된 오류 메시지에는 데이터베이스 또는 쿼리 로직에 민감한 정보를 포함할 수 있는 데이터베이스 쿼리에 대한 정보가 포함됨. 위의 코드에서 오류 메시지는 데이터베이스에서 사용된 테이블 이름과 열 이름을 노출함. 이 데이터는 SQL injection과 같이 데이터베이스에 직접 액세스하는 다른 공격에 사용될 수 있음.

🌟

  1. 민감한 정보 최소화
    • 로그에 저장하는 정보는 꼭 필요한 항목만 포함
    • 비밀번호, 세션토큰, 신용카드 정보, 개인식별번호는 절대 기록 X
  2. 로그 보호
    • 로그 파일에 접근 권한을 엄격히 제어
    • 민감한 로그는 암호화하거나 별도로 관리
  3. 로그 레벨 관리
    • 디버깅 목적으로 사용한 로깅 코드를 배포 전에 제거하거나, 로깅 수준을 제한함
  4. 데이터 정제
    • 민감한 데이터를 포함한 메시지를 로그에 기록하기 전에 정제 또는 마스킹(**)**

0개의 댓글