보안 모범 사례
이 가이드는 프로덕션 환경에서 TCPDF-Next를 배포하기 위한 실행 가능한 보안 권장 사항을 제공합니다. 이 사례를 따르면 PDF 생성 파이프라인이 엔터프라이즈 보안 표준을 충족하도록 보장할 수 있습니다.
입력 검증 (writeHtml 전 HTML 정리)
사용자 제공 HTML에서 PDF를 생성할 때는 HTML 렌더러에 전달하기 전에 항상 입력을 정리하세요. TCPDF-Next의 HtmlRenderer는 HTML을 충실하게 파싱하고 렌더링하므로, 정리하지 않으면 악성 마크업이 악용될 수 있습니다.
use YeeeFang\TcpdfNext\Html\HtmlRenderer;
// 위험: 원시 사용자 입력을 직접 전달하지 마세요
// $renderer->writeHtml($userInput);
// 안전: 전용 라이브러리로 먼저 정리
$clean = \HTMLPurifier::getInstance()->purify($userInput);
$renderer->writeHtml($clean);핵심 규칙:
- 렌더링 전에
<script>,<iframe>,<object>,<embed>,<link>태그를 제거합니다. href및src속성에서javascript:및data:URI 스킴을 제거합니다.- 레이아웃에 필요한 CSS 속성만 허용합니다(
position: fixed없음, CSS 값에url()없음). - 문자 인코딩을 검증합니다 — 렌더러에 전달하기 전에 입력이 유효한 UTF-8인지 확인합니다.
인증서 관리 (안전한 저장 및 교체)
저장소 계층
| 방법 | 보안 수준 | 사용 사례 |
|---|---|---|
| 하드웨어 보안 모듈 (HSM) | 최고 | 엔터프라이즈, 규제 산업 |
| 클라우드 KMS (AWS KMS, Azure Key Vault, GCP KMS) | 높음 | 클라우드 네이티브 배포 |
| 강력한 암호구문을 가진 PKCS#12 파일 | 중간 | 소규모 배포 |
| PEM 파일 (암호화됨) | 중-하 | 개발, 테스트 |
| PEM 파일 (암호화되지 않음) | 최저 | 프로덕션에서 절대 사용 금지 |
교체 정책
- 만료 최소 30일 전에 인증서를 갱신합니다.
- 30일, 14일, 7일, 1일 임계값에서 자동 알림으로 만료를 모니터링합니다.
- 손상된 인증서는 발급 CA를 통해 즉시 해지합니다.
- 모든 인증서 수명주기 작업에 대한 서명된 감사 로그를 유지합니다.
use YeeeFang\TcpdfNext\Certificate\CertificateStore;
$store = new CertificateStore();
$store->loadFromDirectory('/etc/tcpdf-next/certs/', '*.pem');
$activeCert = $store->getActiveCertificate('document-signing');
if ($activeCert->getExpirationDate() < new \DateTimeImmutable('+30 days')) {
$logger->warning('Signing certificate expires soon', [
'subject' => $activeCert->getSubject(),
'expires' => $activeCert->getExpirationDate()->format('Y-m-d'),
]);
}DANGER
개인 키를 소스 코드 저장소, 공유 파일시스템의 암호화되지 않은 파일, 저장 시 암호화 없는 데이터베이스 컬럼, 로그 파일에 절대 저장하지 마세요.
비밀번호 처리 (SASLprep 및 강력한 비밀번호)
PDF 암호화 비밀번호를 설정할 때, 플랫폼 간 일관된 비밀번호 처리를 보장하기 위해 SASLprep(RFC 4013)을 통한 유니코드 정규화를 적용합니다:
// TCPDF-Next는 비밀번호에 SASLprep을 자동으로 적용합니다
$pdf->setEncryption()
->setAlgorithm(EncryptionAlgorithm::AES256)
->setUserPassword('pässwörd-with-ünïcöde') // SASLprep이 내부적으로 정규화
->setOwnerPassword($strongOwnerPassword)
->apply();비밀번호 정책 권장사항:
- 사용자 비밀번호는 최소 12자, 소유자 비밀번호는 최소 20자.
- 소유자 비밀번호에는 암호학적으로 안전한 난수 생성기(
random_bytes())를 사용합니다. - 소스 코드에 비밀번호를 하드코딩하지 않습니다 — 환경 변수 또는 시크릿 관리자에서 로드합니다.
- 사용 후
sodium_memzero()로 메모리에서 비밀번호를 삭제합니다.
SSRF 방지 (이미지, TSA, OCSP의 URL 검증)
TCPDF-Next는 기본적으로 SSRF를 차단하지만, 정당한 외부 리소스에 대한 허용 목록을 구성해야 합니다:
use YeeeFang\TcpdfNext\Security\NetworkPolicy;
$networkPolicy = NetworkPolicy::create()
->denyPrivateNetworks() // 10.x, 172.16.x, 192.168.x 차단
->denyLoopback() // 127.0.0.1 차단
->denyLinkLocal() // 169.254.x 차단
->allowDomain('cdn.yourcompany.com') // 이미지
->allowDomain('timestamp.digicert.com') // TSA
->allowDomain('ocsp.digicert.com') // OCSP
->setMaxRedirects(3)
->setRequestTimeout(10);
$pdf = PdfDocument::create()
->setNetworkPolicy($networkPolicy)
->build();체크리스트:
- 가져오기 전에 모든 URL을 검증합니다(스킴, 호스트, 포트).
- TSA 및 OCSP 응답자 도메인을 명시적으로 허용 목록에 추가합니다.
file://,gopher://,ftp://및 기타 비-HTTP(S) 스킴을 차단합니다.- 보안 모니터링을 위해 차단된 모든 요청을 로깅합니다.
파일 경로 검증 (경로 탐색 방지)
사용자 제공 파일 경로(예: 폰트 파일, 이미지, 출력 대상)를 받을 때:
use YeeeFang\TcpdfNext\Security\ResourcePolicy;
$resourcePolicy = ResourcePolicy::strict()
->allowLocalDirectory('/app/public/assets/')
->allowLocalDirectory('/app/storage/fonts/')
->denyAllRemote();
$pdf = PdfDocument::create()
->setResourcePolicy($resourcePolicy)
->build();규칙:
- 사용자 입력을 파일 경로에 직접 연결하지 않습니다.
- 경로를 절대 정규 형식으로 해석하고 허용된 디렉토리에 대해 검증합니다.
.., 널 바이트 또는 인쇄 불가능한 문자가 포함된 경로를 거부합니다.- 프로덕션에서는
ResourcePolicy::strict()를 사용합니다 — 기본적으로 모든 접근을 거부합니다.
배포 보안 (Docker 및 파일 권한)
Docker 구성
FROM php:8.5-fpm-alpine
# 비루트 사용자로 실행
RUN addgroup -S tcpdf && adduser -S tcpdf -G tcpdf
USER tcpdf
# 위험한 PHP 함수 비활성화
RUN echo "disable_functions = exec,passthru,shell_exec,system,proc_open,popen" \
>> /usr/local/etc/php/conf.d/security.ini
# 읽기 전용 파일시스템 (쓰기 가능한 볼륨은 명시적으로 마운트)
# docker run --read-only --tmpfs /tmp ...파일 권한
# 인증서 디렉토리: 웹 서버 사용자만 읽을 수 있음
chown -R www-data:www-data /etc/tcpdf-next/certs/
chmod 700 /etc/tcpdf-next/certs/
chmod 600 /etc/tcpdf-next/certs/*.p12
chmod 600 /etc/tcpdf-next/certs/*.pem
# 출력 디렉토리: 웹 서버 사용자만 쓸 수 있음
chown -R www-data:www-data /var/lib/tcpdf-next/output/
chmod 700 /var/lib/tcpdf-next/output/
# 임시 디렉토리: 쓰기 가능하되 전체 읽기 불가
chown -R www-data:www-data /tmp/tcpdf-next/
chmod 700 /tmp/tcpdf-next/브라우저에 표시되는 PDF를 위한 콘텐츠 보안 정책
브라우저에서 PDF를 인라인으로 제공할 때, 임베딩 공격을 방지하기 위해 적절한 HTTP 헤더를 설정합니다:
return response($pdf->toString(), 200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'inline; filename="document.pdf"',
'Content-Security-Policy' => "default-src 'none'; plugin-types application/pdf",
'X-Content-Type-Options' => 'nosniff',
'X-Frame-Options' => 'DENY',
'Cache-Control' => 'no-store, no-cache, must-revalidate',
]);민감한 데이터가 포함된 PDF의 경우, 브라우저 내 렌더링 대신 다운로드를 강제하기 위해 Content-Disposition: attachment를 사용하는 것이 좋습니다.
감사 로깅 권장사항
보안에 민감한 모든 PDF 작업에 대한 포괄적인 감사 로깅을 구성합니다:
use YeeeFang\TcpdfNext\Security\AuditLogger;
AuditLogger::configure([
'channel' => 'tcpdf-security',
'log_signing' => true,
'log_encryption' => true,
'log_validation' => true,
'log_key_access' => true,
'log_tsa_requests' => true,
'log_resource_access' => true, // 이미지/폰트 로딩 로깅
'log_blocked_requests' => true, // SSRF 차단 로깅
'redact_sensitive' => true, // 로그에서 비밀번호/키 편집
]);모니터링 항목:
- 실패한 서명 검증 시도 (문서 변조 가능성).
- 인증서 만료 경고.
- TSA 통신 실패.
- 비정상적인 서명 볼륨 (키 손상 가능성).
- 차단된 SSRF 시도 (공격 탐색 가능성).
- 예상치 못한 경로에서의 리소스 로딩.
PDF 권한에 대한 최소 권한 원칙
PDF 문서 권한을 설정할 때 필요한 최소한의 접근만 부여합니다:
use YeeeFang\TcpdfNext\Encryption\Permissions;
// 제한적: 읽기 전용 문서
$pdf->setEncryption()
->setPermissions(Permissions::ACCESSIBILITY) // 스크린 리더 접근만
->setUserPassword('reader')
->setOwnerPassword($strongOwnerPassword)
->apply();
// 보통: 인쇄 가능한 문서
$pdf->setEncryption()
->setPermissions(
Permissions::PRINT_HIGH_QUALITY
| Permissions::ACCESSIBILITY
)
->apply();
// 피해야 함: 모든 권한을 부여하면 암호화의 목적이 무의미해집니다
// Permissions::ALL은 사용 가능하지만 거의 사용하지 않아야 합니다권한 가이드라인:
- 수신자가 문서를 편집해야 하는 경우가 아니면
MODIFY_CONTENTS를 부여하지 않습니다. - 스크린 리더 호환성을 위해 항상
ACCESSIBILITY를 부여합니다(많은 관할 지역에서 법적 요구사항). - 특별한 이유가 없다면
PRINT_LOW_QUALITY대신PRINT_HIGH_QUALITY를 사용합니다. - 부여된 각 권한의 근거를 코드에 문서화합니다.
더 읽을거리
- 보안 개요 — 보안 아키텍처 및 설계 철학
- PAdES B-LTA — 서명 수준 및 구현
- 고급 암호화 — 알고리즘 요구사항