Thực hành bảo mật tốt nhất
Hướng dẫn này cung cấp khuyến nghị bảo mật thực tiễn cho triển khai TCPDF-Next trong môi trường production. Tuân theo các thực hành này đảm bảo pipeline tạo PDF đáp ứng tiêu chuẩn bảo mật enterprise.
Validation Input (làm sạch HTML trước writeHtml)
Khi tạo PDF từ HTML do người dùng cung cấp, luôn làm sạch input trước khi truyền cho HTML renderer. HtmlRenderer của TCPDF-Next phân tích và render HTML chính xác, nghĩa là markup độc hại có thể bị khai thác nếu không được làm sạch.
use YeeeFang\TcpdfNext\Html\HtmlRenderer;
// NGUY HIỂM: Không bao giờ truyền input người dùng thô trực tiếp
// $renderer->writeHtml($userInput);
// AN TOÀN: Làm sạch trước bằng thư viện chuyên dụng
$clean = \HTMLPurifier::getInstance()->purify($userInput);
$renderer->writeHtml($clean);Quy tắc chính:
- Loại bỏ tag
<script>,<iframe>,<object>,<embed>và<link>trước khi render. - Xóa URI scheme
javascript:vàdata:khỏi attributehrefvàsrc. - Giới hạn thuộc tính CSS cho phép ở những gì cần cho layout (không
position: fixed, khôngurl()trong giá trị CSS). - Xác thực character encoding — đảm bảo input là UTF-8 hợp lệ trước khi truyền cho renderer.
Quản lý Certificate (lưu trữ và rotation an toàn)
Thứ bậc lưu trữ
| Phương pháp | Mức bảo mật | Use case |
|---|---|---|
| Hardware Security Module (HSM) | Cao nhất | Môi trường production, ngành được quản lý |
| Cloud KMS (AWS KMS, Azure Key Vault, GCP KMS) | Cao | Triển khai cloud-native |
| File PKCS#12 với passphrase mạnh | Trung bình | Triển khai nhỏ |
| File PEM (mã hóa) | Trung bình-Thấp | Phát triển, testing |
| File PEM (không mã hóa) | Thấp nhất | Không bao giờ dùng production |
Chính sách rotation
- Gia hạn certificate ít nhất 30 ngày trước khi hết hạn.
- Giám sát hết hạn với cảnh báo tự động ở ngưỡng 30, 14, 7 và 1 ngày.
- Thu hồi certificate bị xâm phạm ngay lập tức qua CA phát hành.
- Duy trì audit log đã ký cho mọi thao tác lifecycle certificate.
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
Không bao giờ lưu private key trong repository mã nguồn, file không mã hóa trên filesystem chia sẻ, cột database không có mã hóa lúc nghỉ, hoặc file log.
Xử lý mật khẩu (SASLprep và mật khẩu mạnh)
Khi thiết lập mật khẩu mã hóa PDF, áp dụng chuẩn hóa Unicode qua SASLprep (RFC 4013) để đảm bảo xử lý mật khẩu nhất quán:
// TCPDF-Next tự động áp dụng SASLprep cho mật khẩu
$pdf->setEncryption()
->setAlgorithm(EncryptionAlgorithm::AES256)
->setUserPassword('pässwörd-with-ünïcöde') // SASLprep chuẩn hóa nội bộ
->setOwnerPassword($strongOwnerPassword)
->apply();Khuyến nghị chính sách mật khẩu:
- Tối thiểu 12 ký tự cho user password, 20 ký tự cho owner password.
- Dùng random generator an toàn mật mã cho owner password (
random_bytes()). - Không bao giờ hardcode mật khẩu trong mã nguồn — tải từ biến môi trường hoặc secret manager.
- Xóa mật khẩu khỏi bộ nhớ sau khi dùng với
sodium_memzero().
Ngăn SSRF (validation URL cho ảnh, TSA, OCSP)
TCPDF-Next chặn SSRF mặc định, nhưng bạn phải cấu hình allowlist cho tài nguyên bên ngoài hợp pháp:
use YeeeFang\TcpdfNext\Security\NetworkPolicy;
$networkPolicy = NetworkPolicy::create()
->denyPrivateNetworks() // Chặn 10.x, 172.16.x, 192.168.x
->denyLoopback() // Chặn 127.0.0.1
->denyLinkLocal() // Chặn 169.254.x
->allowDomain('cdn.yourcompany.com') // Ảnh
->allowDomain('timestamp.digicert.com') // TSA
->allowDomain('ocsp.digicert.com') // OCSP
->setMaxRedirects(3)
->setRequestTimeout(10);
$pdf = PdfDocument::create()
->setNetworkPolicy($networkPolicy)
->build();Checklist:
- Xác thực mọi URL trước khi fetch (scheme, host, port).
- Allowlist rõ ràng domain TSA và OCSP responder.
- Chặn
file://,gopher://,ftp://, và scheme non-HTTP(S) khác. - Ghi log mọi request bị chặn để giám sát bảo mật.
Validation đường dẫn file (ngăn Path Traversal)
Khi nhận đường dẫn file do người dùng cung cấp:
use YeeeFang\TcpdfNext\Security\ResourcePolicy;
$resourcePolicy = ResourcePolicy::strict()
->allowLocalDirectory('/app/public/assets/')
->allowLocalDirectory('/app/storage/fonts/')
->denyAllRemote();
$pdf = PdfDocument::create()
->setResourcePolicy($resourcePolicy)
->build();Quy tắc:
- Không bao giờ nối input người dùng trực tiếp vào đường dẫn file.
- Resolve đường dẫn sang dạng canonical tuyệt đối và xác thực theo thư mục được phép.
- Từ chối đường dẫn chứa
.., null byte, hoặc ký tự không in được. - Dùng
ResourcePolicy::strict()trong production — từ chối mọi truy cập mặc định.
Bảo mật triển khai (Docker và quyền file)
Cấu hình Docker
FROM php:8.5-fpm-alpine
# Chạy dưới user không phải root
RUN addgroup -S tcpdf && adduser -S tcpdf -G tcpdf
USER tcpdf
# Tắt function PHP nguy hiểm
RUN echo "disable_functions = exec,passthru,shell_exec,system,proc_open,popen" \
>> /usr/local/etc/php/conf.d/security.ini
# Filesystem chỉ đọc (mount volume ghi được rõ ràng)
# docker run --read-only --tmpfs /tmp ...Quyền file
# Thư mục certificate: chỉ user web server đọc được
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
# Thư mục output: chỉ user web server ghi được
chown -R www-data:www-data /var/lib/tcpdf-next/output/
chmod 700 /var/lib/tcpdf-next/output/
# Thư mục tạm: ghi được, không đọc được bởi mọi người
chown -R www-data:www-data /tmp/tcpdf-next/
chmod 700 /tmp/tcpdf-next/Content Security Policy cho PDF hiển thị trong trình duyệt
Khi phục vụ PDF inline trong trình duyệt, thiết lập HTTP header phù hợp:
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',
]);Cho PDF chứa dữ liệu nhạy cảm, ưu tiên Content-Disposition: attachment để bắt buộc download thay vì render trong trình duyệt.
Khuyến nghị Audit Logging
Cấu hình audit logging toàn diện cho mọi thao tác PDF nhạy cảm bảo mật:
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 tải ảnh/font
'log_blocked_requests' => true, // Log chặn SSRF
'redact_sensitive' => true, // Ẩn mật khẩu/key khỏi log
]);Giám sát:
- Nỗ lực validation chữ ký thất bại (có thể giả mạo document).
- Cảnh báo hết hạn certificate.
- Lỗi giao tiếp TSA.
- Khối lượng ký bất thường (có thể key bị xâm phạm).
- Nỗ lực SSRF bị chặn (có thể dò tấn công).
- Tải tài nguyên từ đường dẫn không mong đợi.
Nguyên tắc đặc quyền tối thiểu cho PDF Permission
Khi thiết lập permission document PDF, chỉ cấp quyền truy cập tối thiểu cần thiết:
use YeeeFang\TcpdfNext\Encryption\Permissions;
// HẠN CHẾ: Document chỉ đọc
$pdf->setEncryption()
->setPermissions(Permissions::ACCESSIBILITY) // Chỉ truy cập trình đọc màn hình
->setUserPassword('reader')
->setOwnerPassword($strongOwnerPassword)
->apply();
// VỪA PHẢI: Document in được
$pdf->setEncryption()
->setPermissions(
Permissions::PRINT_HIGH_QUALITY
| Permissions::ACCESSIBILITY
)
->apply();
// TRÁNH: Cấp mọi permission làm mất ý nghĩa mã hóa
// Permissions::ALL có sẵn nhưng hiếm khi nên dùngHướng dẫn permission:
- Không bao giờ cấp
MODIFY_CONTENTStrừ khi người nhận cần chỉnh sửa document. - Luôn cấp
ACCESSIBILITYcho tương thích trình đọc màn hình (yêu cầu pháp lý ở nhiều khu vực). - Dùng
PRINT_HIGH_QUALITYthay vìPRINT_LOW_QUALITYtrừ khi có lý do cụ thể. - Ghi chú lý do cho mỗi permission được cấp trong code.
Đọc thêm
- Tổng quan bảo mật — Kiến trúc bảo mật và triết lý thiết kế
- PAdES B-LTA — Các cấp chữ ký và implement
- Mã hóa nâng cao — Yêu cầu thuật toán