[보안] 7분 읽기

[보안] Base64 보안 모범 사례

웹 애플리케이션에서 Base64 인코딩을 사용할 때의 중요한 보안 고려사항.

2025년 1월 | security

// BASE64는 암호화가 아님

가장 중요한 보안 오해는 Base64를 암호화로 취급하는 것입니다. Base64는 단순한 인코딩일 뿐이며, 키나 패스워드 없이도 완전히 가역적입니다.

누구나 Base64 데이터를 즉시 디코딩할 수 있습니다. 보안이나 데이터 보호를 위해 Base64에 의존하지 마세요.

// ❌ 잘못된 방법: Base64를 '보안'으로 사용
const password = 'secret123';
const encoded = btoa(password); // c2VjcmV0MTIz
// 누구나 이것을 즉시 디코딩할 수 있습니다!

// ✅ 올바른 방법: 적절한 패스워드 해싱
import bcrypt from 'bcrypt';
const password = 'secret123';
const hashedPassword = await bcrypt.hash(password, 12);
// 이것은 실제로 안전합니다

// 입력 검증

인젝션 공격과 애플리케이션 오류를 방지하기 위해 항상 Base64 입력을 검증하세요:

적절한 검증은 악의적인 입력이 보안 문제나 애플리케이션 크래시를 일으키는 것을 방지합니다.

// Base64 형식 검증
function isValidBase64(str) {
    // 형식 확인: 유효한 Base64 문자만
    const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
    if (!base64Regex.test(str)) {
        return false;
    }
    
    // 길이 확인 (4의 배수여야 함)
    if (str.length % 4 !== 0) {
        return false;
    }
    
    // 디코딩 테스트
    try {
        atob(str);
        return true;
    } catch (e) {
        return false;
    }
}

// 검증이 포함된 안전한 디코딩
function safeBase64Decode(input, maxLength = 10000) {
    if (typeof input !== 'string') {
        throw new Error('입력은 문자열이어야 합니다');
    }
    
    if (input.length > maxLength) {
        throw new Error('입력이 너무 깁니다');
    }
    
    if (!isValidBase64(input)) {
        throw new Error('유효하지 않은 Base64');
    }
    
    return atob(input);
}

// XSS 방지

Base64 데이터는 디코딩할 때 악의적인 스크립트를 포함할 수 있습니다. 디코딩된 콘텐츠를 표시할 때는 항상 출력을 새니타이즈하세요:

사용자로부터의 Base64 입력을 절대 신뢰하지 마세요. HTML로 렌더링하기 전에 항상 새니타이즈하세요.

// ❌ 위험: 직접 HTML 주입
function displayDecodedData(base64) {
    const decoded = atob(base64);
    document.innerHTML = decoded; // XSS 취약점!
}

// ✅ 안전: 출력 새니타이즈
function safeDisplayDecodedData(base64) {
    const decoded = atob(base64);
    
    // 텍스트 노드 생성 (HTML 실행 없음)
    const textNode = document.createTextNode(decoded);
    container.appendChild(textNode);
    
    // 또는 HTML 엔티티 이스케이프
    const escaped = decoded
        .replace(/&/g, '&')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#x27;');
    
    container.innerHTML = escaped;
}

// 크기 제한

Base64 인코딩은 데이터 크기를 약 33% 증가시킵니다. DoS 공격을 방지하기 위해 크기 제한을 구현하세요:

제한 없는 Base64 입력은 과도한 메모리와 처리 능력을 소모할 수 있습니다.

  • > 최대 입력 길이 제한 설정
  • > 처리 중 메모리 사용량 모니터링
  • > 긴 작업에 대한 타임아웃 구현
  • > 대용량 데이터 세트에는 스트리밍 사용
  • > 인코딩 전 압축 고려
  • > Base64 작업에 대한 속도 제한

// 안전한 토큰 처리

토큰이나 민감한 ID에 Base64를 사용할 때는 보안 모범 사례를 따르세요:

적절한 토큰 처리는 무단 액세스와 토큰 기반 공격을 방지합니다.

// 안전한 토큰 생성
function createSecureToken() {
    // 암호학적으로 안전한 랜덤 값 사용
    const array = new Uint8Array(32);
    crypto.getRandomValues(array);
    
    // Base64로 변환하고 URL 안전하게 만들기
    const base64 = btoa(String.fromCharCode(...array))
        .replace(/\+/g, '-')
        .replace(/\//g, '_')
        .replace(/=/g, '');
    
    return base64;
}

// 안전한 토큰 검증
function validateToken(token, expectedLength = 43) {
    if (typeof token !== 'string') {
        return false;
    }
    
    // 길이 확인
    if (token.length !== expectedLength) {
        return false;
    }
    
    // URL 안전 Base64 형식 확인
    const urlSafeBase64Regex = /^[A-Za-z0-9_-]+$/;
    return urlSafeBase64Regex.test(token);
}

// 데이터 URI 보안

Base64 데이터 URI는 악의적인 콘텐츠를 포함할 수 있습니다. 데이터 URI를 항상 검증하고 새니타이즈하세요:

악의적인 데이터 URI는 스크립트를 실행하거나, 외부 리소스를 로드하거나, 부적절한 콘텐츠를 포함할 수 있습니다.

// 데이터 URI 검증
function validateDataURI(dataUri, allowedTypes = ['image/png', 'image/jpeg']) {
    const dataUriRegex = /^data:([a-zA-Z0-9][a-zA-Z0-9\/+]*);base64,(.+)$/;
    const match = dataUri.match(dataUriRegex);
    
    if (!match) {
        throw new Error('유효하지 않은 데이터 URI 형식');
    }
    
    const [, mimeType, base64Data] = match;
    
    // MIME 타입 검증
    if (!allowedTypes.includes(mimeType)) {
        throw new Error(`지원되지 않는 MIME 타입: ${mimeType}`);
    }
    
    // Base64 데이터 검증
    if (!isValidBase64(base64Data)) {
        throw new Error('데이터 URI의 유효하지 않은 Base64');
    }
    
    // 크기 확인
    const sizeEstimate = (base64Data.length * 3) / 4;
    if (sizeEstimate > 1024 * 1024) { // 1MB 제한
        throw new Error('데이터 URI가 너무 큽니다');
    }
    
    return { mimeType, base64Data };
}

// 안전한 구현 체크리스트

  • > Base64를 암호화나 보안 조치로 사용하지 않기
  • > 항상 Base64 입력 형식과 길이 검증
  • > 표시하기 전에 디코딩된 출력 새니타이즈
  • > 크기 제한과 타임아웃 구현
  • > 웹 애플리케이션에는 URL 안전 Base64 사용
  • > 데이터 URI의 MIME 타입 검증
  • > 토큰에는 암호학적으로 안전한 랜덤 값 사용
  • > 적절한 오류 처리 구현
  • > 잠재적인 DoS 공격 모니터링
  • > Base64 처리 코드의 정기적인 보안 감사