로그 인젝션(개행 문자 미처리로 인한 로그 위조)

Log Injection

설명

로그 인젝션은 신뢰할 수 없는 데이터가 출력 중립화 없이 줄 단위 로그에 기록될 때 발생합니다. 캐리지 리턴(\r, CR) 또는 라인 피드(\n, LF)는 레코드 경계를 새로 만들거나 변경할 수 있습니다. 공격자는 이를 이용해 가짜 로그 항목을 삽입하고, 자동 파싱을 손상시키거나, 모니터링 및 포렌식 분석을 오도할 수 있습니다.

Python의 logging API는 msg에 모든 위치 인자(args)를 대입해 최종 메시지를 만듭니다. 따라서 문자열 연결뿐 아니라 logging.info("User: %s", user)처럼 형식화 인자를 분리한 호출도 user에 포함된 CR/LF를 자동으로 중립화하지 않습니다.

잠재적 영향

  • 로그 위조: 가짜 로그 라인을 삽입하거나 심각도와 형식을 조작해 보이게 할 수 있습니다.
  • 자동 분석 교란: 레코드 형식을 깨뜨려 수집기나 SIEM의 파싱 및 경고 규칙을 오작동시킬 수 있습니다.
  • 포렌식 무결성 훼손: 사건 타임라인과 사용자 활동 추적을 어렵게 만들어 조사와 대응을 지연시킬 수 있습니다.

해결 방법

  • 줄 단위 로그에서는 신뢰할 수 없는 모든 메시지와 형식화 인자의 CR 및 LF를 로거에 전달하기 전에 안전한 문자로 치환합니다.
  • 레코드 구분 문자를 이스케이프하고 이벤트마다 정확히 하나의 레코드를 출력하는, 검증된 직렬화 도구나 포매터를 사용합니다. 키-값 필드를 사용한다는 사실만으로 안전한 구조화 로깅이 보장되지는 않습니다.
  • Python의 기본 압축형 json.dumps(...)는 CR, LF 및 U+0000부터 U+001F까지의 제어 문자를 이스케이프합니다. 줄 단위 로그에서는 indent를 사용한 보기 좋은 출력이나 공격자가 제어하는 직렬화 옵션을 사용하지 않습니다.
  • 허용 목록은 허용되는 값 전체가 유한하며 신뢰할 수 있는 CR/LF 없는 리터럴일 때만 안전 근거로 사용합니다. 형식 및 길이 검증만으로는 레코드 구분 문자가 중립화되지 않습니다.
  • 로그를 HTML로 표시한다면 해당 뷰어의 출력 지점에서 HTML 인코딩을 별도로 적용합니다. HTML 이스케이프는 저장되는 로그의 CR/LF를 중립화하지 않습니다.

예시

변경 전

python
import logging
from flask import Flask, request

app = Flask(__name__)

@app.route('/search')
def search():
    term = request.args.get('term', '')
    # 취약: 형식화 인자를 분리해도 term의 CR/LF는 그대로 남습니다.
    logging.info("Search term: %s", term)
    return "ok"

변경 후

CR/LF 치환

python
import logging
from flask import Flask, request

app = Flask(__name__)

@app.route('/search')
def search_safe():
    term = request.args.get('term', '')
    safe_term = term.replace("\r", " ").replace("\n", " ")
    logging.info("Search term: %s", safe_term)
    return "ok"

압축형 JSON 직렬화

python
import json
import logging
from flask import request

def log_search():
    term = request.args.get('term', '')
    logging.info(json.dumps({"event": "search", "term": term}))

설명:

  • 변경 전: term의 \r 또는 \n은 형식화 이후에도 남으므로 로그가 여러 레코드처럼 보일 수 있습니다.
  • CR/LF 치환: 두 레코드 구분 문자를 모두 안전한 한 줄 문자로 바꾼 뒤 기록합니다.
  • 압축형 JSON: 기본 json.dumps(...)가 문자열 내부의 제어 문자를 이스케이프합니다. indent를 사용하면 직렬화 결과 자체가 여러 줄이 되므로 줄 단위 로그에는 사용하지 않습니다.

적용 시 주의사항

실제 로그의 레코드 경계는 핸들러와 포매터 구성에 따라 달라집니다. 최종 출력에서 CR/LF가 안전하게 처리되는지 확인하고, 로그 뷰어의 HTML 인코딩도 별도로 적용하세요.

참조