[보안]
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, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
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 처리 코드의 정기적인 보안 감사