Back to blog

Nền Tảng Bảo Mật Web: CIA Triad & Threat Modeling

securityweb-securityowaspthreat-modelingtutorial
Nền Tảng Bảo Mật Web: CIA Triad & Threat Modeling

Chào mừng đến với Giai Đoạn 1 của Lộ Trình Học Bảo Mật Web! Bài viết này bao gồm các khái niệm bảo mật nền tảng mà mọi developer phải hiểu trước khi viết bất kỳ dòng code production nào.

Bảo mật không phải là ghi nhớ một danh sách lỗ hổng — mà là phát triển tư duy bảo mật. Những nền tảng này cung cấp cho bạn các mô hình tư duy để lý luận về bảo mật trong bất kỳ tình huống nào, trên bất kỳ ngôn ngữ hay framework nào.

Sau bài viết này, bạn sẽ có bộ công cụ khái niệm để suy nghĩ như một security engineer: xác định những gì cần bảo vệ, những gì đe dọa nó, và cách xây dựng hệ thống tự bảo vệ ở nhiều lớp.

Bạn Sẽ Học Được

✅ Hiểu CIA Triad và áp dụng cho quyết định ứng dụng thực tế
✅ Thực hiện threat modeling bằng framework STRIDE
✅ Lập bản đồ attack surface trong ứng dụng của bạn
✅ Thiết kế hệ thống với defense in depth
✅ Áp dụng nguyên tắc least privilege
✅ Hiểu cơ bản về zero trust architecture
✅ Tích hợp yêu cầu bảo mật vào quy trình phát triển


CIA Triad: Nền Tảng Của Bảo Mật

Mọi quyết định bảo mật đều quy về ba thuộc tính. Chúng cùng nhau tạo thành CIA Triad — nền tảng của bảo mật thông tin.

Confidentiality (Bảo Mật)

Confidentiality có nghĩa là bảo vệ dữ liệu khỏi truy cập trái phép. Chỉ những người đúng mới có thể đọc thông tin nhạy cảm.

Ví dụ trong ứng dụng web:

  • Mật khẩu được lưu dạng hash, không bao giờ là plaintext
  • User A không thể đọc tin nhắn riêng tư của User B
  • API keys không bị expose trong JavaScript phía client
  • Database credentials không được commit vào source code
  • HTTPS mã hóa dữ liệu trong quá trình truyền

Khi confidentiality bị vi phạm:

  • SQL injection làm lộ toàn bộ bảng user
  • Insecure Direct Object Reference (IDOR) expose dữ liệu của user khác
  • File .env bị rò rỉ, lộ database credentials
  • Log các trường nhạy cảm (mật khẩu, thẻ tín dụng) ra file log

Kỹ thuật triển khai:

  • Mã hóa at rest (cấp database, cấp field)
  • Mã hóa in transit (TLS/HTTPS)
  • Kiểm soát truy cập (ai có thể đọc gì)
  • Che dấu dữ liệu (hiển thị ****1234 thay vì số thẻ đầy đủ)

Integrity (Toàn Vẹn)

Integrity có nghĩa là đảm bảo dữ liệu chính xác và không bị sửa đổi bởi các bên trái phép — có chủ ý hoặc vô tình.

Ví dụ trong ứng dụng web:

  • JWT token được ký số (payload bị tamper sẽ bị từ chối)
  • Checksum trên file download
  • Database transactions với đảm bảo ACID
  • Input validation từ chối dữ liệu không đúng định dạng
  • Audit log ghi lại ai thay đổi gì

Khi integrity bị vi phạm:

  • Kẻ tấn công sửa JWT payload (đổi "role": "user" thành "role": "admin")
  • Giá trong giỏ hàng bị thao túng phía client
  • Kẻ tấn công thực hiện man-in-the-middle và sửa response API
  • SQL injection chèn hoặc sửa đổi bản ghi

Kỹ thuật triển khai:

  • Chữ ký mật mã (JWT, HMAC, digital signatures)
  • Input validation phía server (không bao giờ tin client)
  • Parameterized queries (ngăn sửa đổi dữ liệu qua injection)
  • Checksums và xác minh hash
  • Audit log bất biến

Availability (Khả Dụng)

Availability có nghĩa là hệ thống và dữ liệu có thể truy cập bởi người dùng được ủy quyền khi họ cần.

Ví dụ trong ứng dụng web:

  • Rate limiting ngăn một user duy nhất làm quá tải API
  • Auto-scaling xử lý traffic spikes
  • Health checks tự động restart service bị lỗi
  • CDN caching giảm tải cho origin server
  • Database replication cho phép failover

Khi availability bị vi phạm:

  • Tấn công Denial of Service (DoS) làm tràn ngập server
  • Một query thiếu index gây slow query làm sập database
  • Memory leak dần dần cạn kiệt tài nguyên server
  • Upload file độc hại lấp đầy dung lượng đĩa
  • Sự cố dependency cascade đến service của bạn

Kỹ thuật triển khai:

  • Rate limiting và throttling
  • Circuit breakers cho external dependencies
  • Caching (Redis, CDN)
  • Horizontal scaling và load balancing
  • Health monitoring và auto-restart

CIA Trong Thực Tế: Cân Bằng Trade-offs

Quyết định bảo mật thực tế bao gồm cân bằng cả ba:

Kịch bảnCIATrade-off
Tin nhắn mã hóa end-to-end✅ Cao✅ Cao⚠️ Trung bìnhTin nhắn không thể khôi phục nếu mất key
API public read-only⚠️ Thấp✅ Cao✅ CaoDữ liệu công khai nhưng phải chính xác
Admin override khẩn cấp⚠️ Thấp⚠️ Thấp✅ CaoBreak-glass access hy sinh C và I cho A
Ứng dụng offline-first✅ Cao⚠️ Trung bình✅ CaoThay đổi local có thể xung đột với server

Insight quan trọng: Tối đa hóa cả ba đồng thời là không thể. Bảo mật là về việc đưa ra trade-offs có ý thức dựa trên threat model của bạn — không phải đạt được sự hoàn hảo.


Threat Modeling: Suy Nghĩ Như Kẻ Tấn Công

Threat modeling là quá trình xác định một cách có hệ thống những gì có thể xảy ra sai với ứng dụng của bạn. Làm điều này trước khi viết code, không phải sau khi bị tấn công.

Mục tiêu: biết rủi ro của mình để có thể ưu tiên phòng thủ.

Framework 4 Câu Hỏi

Mọi threat model đều trả lời bốn câu hỏi:

  1. Chúng ta đang xây dựng gì? — Hiểu hệ thống
  2. Điều gì có thể xảy ra sai? — Xác định các mối đe dọa
  3. Chúng ta sẽ làm gì về điều đó? — Lập kế hoạch giảm thiểu
  4. Chúng ta đã làm đủ tốt chưa? — Xác nhận mô hình

Bước 1: Hiểu Những Gì Bạn Đang Xây Dựng

Bắt đầu với Data Flow Diagram (DFD). Lập bản đồ:

  • External entities — Users, dịch vụ bên thứ ba, hệ thống khác
  • Processes — Các thành phần ứng dụng của bạn
  • Data stores — Databases, file systems, caches
  • Data flows — Cách dữ liệu di chuyển giữa chúng
  • Trust boundaries — Ranh giới phân tách các mức độ tin tưởng khác nhau

Ví dụ cho một blog đơn giản có authentication:

Trust boundaries cần đánh dấu:

  • Internet → Load Balancer (input không tin cậy vào đây)
  • API → Database (ranh giới nội bộ tin cậy)
  • API → Email Service (bên ngoài, một phần tin cậy)

Bước 2: Xác Định Mối Đe Dọa Với STRIDE

STRIDE là từ viết tắt cho sáu loại mối đe dọa, được phát triển bởi Microsoft. Với mỗi thành phần trong DFD, hỏi: "Điều này có thể bị STRIDE không?"

Chữ cáiMối Đe DọaVi PhạmVí Dụ
SSpoofing (Giả mạo)AuthenticationKẻ tấn công giả vờ là user khác
TTampering (Giả mạo dữ liệu)IntegrityKẻ tấn công sửa đổi dữ liệu trong transit
RRepudiation (Chối bỏ)Non-repudiationUser chối bỏ đã đặt hàng
IInformation Disclosure (Rò rỉ thông tin)ConfidentialityError message lộ stack trace
DDenial of ServiceAvailabilityFlood requests làm sập server
EElevation of Privilege (Leo thang đặc quyền)AuthorizationUser thường truy cập endpoint admin

Áp Dụng STRIDE Cho Ví Dụ Blog

Login endpoint (POST /login):

STRIDEMối Đe DọaGiảm Thiểu
SKẻ tấn công brute-force credentialsRate limiting, khóa tài khoản, CAPTCHA
TRequest bị tamper trong transitHTTPS/TLS
RUser chối bỏ đã đăng nhậpAudit log với IP, timestamp, user agent
IError message tiết lộ "user không tồn tại" vs "sai mật khẩu"Error chung: "Thông tin đăng nhập không hợp lệ"
DFlood login requestsRate limit theo IP và theo tài khoản
EBypass login hoàn toànXác thực session phía server ở mọi request

Database (PostgreSQL):

STRIDEMối Đe DọaGiảm Thiểu
SKẻ tấn công kết nối trực tiếp DBFirewall rules, không expose công khai
TSQL injection sửa đổi bản ghiParameterized queries, ORM
RDBA chối bỏ đã thực hiện thay đổiDatabase audit logging
ISQL injection làm lộ các bảng nhạy cảmParameterized queries, DB user least privilege
DQuery gây full table scan, làm sập DBQuery timeouts, indexes, connection pooling
EApp DB user có đặc quyền SUPERUSERDùng tài khoản DB least-privilege

Bước 3: Đánh Giá và Ưu Tiên Mối Đe Dọa

Không phải mối đe dọa nào cũng như nhau. Dùng điểm DREAD hoặc ma trận Rủi Ro = Khả Năng × Tác Động đơn giản hơn:

Ví dụ ưu tiên:

Mối Đe DọaKhả NăngTác ĐộngRủi RoƯu Tiên
SQL injection trên loginCaoCaoTới HạnSửa trước
Brute force loginCaoTrung bìnhCaoSửa sớm
Admin DB userThấpCaoCaoSửa sớm
Thiếu audit logsTrung bìnhThấpTrung bìnhLên kế hoạch
Bypass CAPTCHAThấpThấpThấpSau

Bước 4: Định Nghĩa Giảm Thiểu

Với mỗi mối đe dọa, định nghĩa một biện pháp giảm thiểu cụ thể:

Mối đe dọa: SQL injection trên login endpoint
Rủi ro: Tới Hạn
Giảm thiểu: Dùng parameterized queries qua ORM (TypeORM/SQLAlchemy)
Xác nhận: Code review + automated SQLi scanner trong CI/CD
Người phụ trách: Backend team
Timeline: Trước release tiếp theo

Phân Tích Attack Surface

Attack surface của bạn là mọi thứ có thể tiếp cận bởi kẻ tấn công:

Quy tắc giảm thiểu attack surface:

  • Tắt các tính năng không dùng (xóa routes không dùng, tắt debug endpoints)
  • Giảm thiểu các cổng expose (chỉ 443 công khai, còn lại internal)
  • Xóa dependencies không dùng
  • Thu hồi các quyền không cần thiết
  • Tắt tài khoản mặc định và đổi credentials mặc định

Defense in Depth: Không Bao Giờ Dựa Vào Một Lớp

Defense in depth là nguyên tắc rằng bảo mật nên đến từ nhiều lớp độc lập — để nếu một lớp thất bại, các lớp khác vẫn bảo vệ hệ thống.

Phép so sánh kinh điển: một lâu đài thời trung cổ không chỉ dựa vào bức tường ngoài. Nó có hào nước, tường ngoài, cổng thành, tường trong, lính gác và tháp pháo. Kẻ tấn công phải đánh bại tất cả những thứ này.

Mô Hình Lớp Bảo Mật

Mỗi lớp độc lập. Nếu kẻ tấn công bypass WAF (Lớp 1), họ vẫn phải đối mặt với TLS ở Lớp 2, auth ở Lớp 3, input validation ở Lớp 4, và parameterized queries ở Lớp 5.

Defense in Depth Thực Tế Cho Web API

Hãy theo dõi cách defense in depth bảo vệ chống lại một nỗ lực SQL injection:

Tấn công: POST /api/login với body {"email": "admin'--", "password": "x"}

LớpPhòng ThủĐiều Gì Xảy Ra
Network (WAF)WAF rule phát hiện pattern SQLiRequest bị chặn ✅
Rate Limiting5 lần đăng nhập thất bại → chặn IP—
Input ValidationValidation định dạng email từ chối admin'--Request bị chặn ✅
Application LogicAuth service dùng ORM với parameterized queriesQuery an toàn ✅
DatabaseDB user chỉ có SELECT trên bảng usersKhông thể sửa đổi ✅
MonitoringPattern đăng nhập bất thường kích hoạt alertĐội bảo mật được thông báo ✅

Ngay cả khi WAF bỏ sót tấn công, bốn lớp khác vẫn bắt được. Không có điểm lỗi đơn.

Anti-Pattern Defense in Depth

❌ Security through obscurity — Ẩn API endpoints hoặc dùng port không chuẩn không phải là lớp bảo mật. Kẻ tấn công có thể phát hiện chúng.

❌ Trust but don't verify — Tin tưởng rằng load balancer validate auth, nên API không kiểm tra. Nếu LB bị cấu hình sai, bạn không có bảo vệ nào.

❌ Single authentication check — Chỉ validate auth ở gateway, không trong các microservice riêng lẻ. Nếu một service được truy cập trực tiếp (vi phạm mạng nội bộ), không có auth nào áp dụng.

❌ All-or-nothing access — Hoặc full admin hoặc không có quyền truy cập. Thiếu quyền chi tiết có nghĩa là một tài khoản bị xâm phạm cho toàn bộ quyền truy cập.


Nguyên Tắc Least Privilege

Nguyên tắc least privilege (PoLP) nói rằng mọi hệ thống, người dùng và process chỉ nên có quyền tối thiểu cần thiết để thực hiện chức năng của nó — không hơn.

Tại Sao Quan Trọng

Nếu kẻ tấn công xâm phạm một thành phần có đặc quyền quá mức, họ kế thừa những đặc quyền đó. Giới hạn quyền giới hạn blast radius.

Least Privilege Trong Thực Tế

Tài khoản database:

-- ❌ XẤU: App dùng tài khoản superuser
-- Kẻ tấn công có thể DROP TABLE, CREATE USER, đọc pg_shadow
 
-- ✅ TỐT: App dùng tài khoản bị hạn chế
CREATE USER blog_app WITH PASSWORD 'secure_password';
GRANT SELECT, INSERT, UPDATE ON posts TO blog_app;
GRANT SELECT, INSERT ON comments TO blog_app;
GRANT SELECT ON users TO blog_app;
-- KHÔNG THỂ: DELETE posts, truy cập schema khác, tạo bảng

Quyền file system:

# ❌ XẤU: App chạy với quyền root, đọc/ghi mọi nơi
# ✅ TỐT: App chạy với user riêng có quyền tối thiểu
useradd -r -s /bin/false blog_app
chown -R blog_app:blog_app /var/www/blog
chmod 750 /var/www/blog
chmod 640 /var/www/blog/config/database.yml

API keys và service accounts:

// ❌ XẤU: Một admin API key dùng ở mọi nơi
{
  "stripe_key": "sk_live_admin_full_access_key"
}
 
// ✅ TỐT: Các key có phạm vi cho từng mục đích
{
  "stripe_read_key": "rk_live_readonly_key",   // cho báo cáo
  "stripe_charge_key": "sk_live_charges_only"  // cho thanh toán
}

AWS IAM (Infrastructure):

// ❌ XẤU: Lambda function có AdministratorAccess
 
// ✅ TỐT: Lambda function có chính xác những gì cần
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": [
      "s3:GetObject",
      "s3:PutObject"
    ],
    "Resource": "arn:aws:s3:::my-blog-uploads/*"
  }]
}

Checklist Least Privilege

✅ Database users chỉ có các bảng và thao tác họ cần
✅ Application process chạy với user không phải root
✅ API keys và tokens có scope tối thiểu cần thiết
✅ Cloud IAM roles tuân theo least privilege
✅ Truy cập admin yêu cầu credentials riêng (không chỉ là role flag)
✅ Service-to-service calls dùng credentials riêng cho mỗi service
✅ Credentials được xoay định kỳ


Fail-Safe Defaults

Fail-safe defaults có nghĩa là khi có sự cố hoặc chưa có quyết định, hệ thống mặc định ở trạng thái an toàn nhất.

Ví Dụ Trong Code

❌ Fail-open (nguy hiểm):

function canAccessResource(user: User, resourceId: string): boolean {
  try {
    return checkPermission(user, resourceId);
  } catch (error) {
    // Lỗi trong kiểm tra quyền — giả định được phép!
    return true; // ← SAI: mặc định là cấp quyền truy cập
  }
}

✅ Fail-secure (đúng):

function canAccessResource(user: User, resourceId: string): boolean {
  try {
    return checkPermission(user, resourceId);
  } catch (error) {
    logger.error('Permission check failed', { userId: user.id, resourceId, error });
    return false; // ← ĐÚNG: mặc định là từ chối truy cập
  }
}

❌ Mặc định CORS quá rộng:

// Cho phép mọi origin — fail open
app.use(cors());

✅ Mặc định CORS hạn chế:

app.use(cors({
  origin: process.env.ALLOWED_ORIGINS?.split(',') ?? [],
  credentials: true,
}));

❌ Thiếu kiểm tra authorization:

// Nếu middleware thất bại, request đi qua
app.get('/admin/users', optionalAuth, listUsers);

✅ Yêu cầu authorization rõ ràng:

// Phải pass auth, không có fallback
app.get('/admin/users', requireAuth, requireRole('admin'), listUsers);

Zero Trust Architecture

Zero trust là cách tiếp cận giả định không có trust ngầm — ngay cả với các request từ bên trong mạng của bạn. Mọi request phải được xác thực và phân quyền, mọi lúc.

Mô hình bảo mật mạng cổ điển là "castle and moat": tin tưởng mọi thứ bên trong perimeter mạng, không tin gì bên ngoài. Zero trust thay thế bằng "không bao giờ tin, luôn luôn xác minh."

Nguyên Tắc Zero Trust

Zero Trust vs. Bảo Mật Perimeter

Khía CạnhBảo Mật PerimeterZero Trust
Mô hình trustTin tưởng bên trong mạngKhông tin gì mặc định
Kiểm soát truy cậpDựa trên vị trí mạngDựa trên danh tính và ngữ cảnh
Lateral movementDễ dàng (bên trong được tin)Khó (mỗi bước cần auth)
Remote workRủi ro (phức tạp VPN)Tự nhiên (danh tính là perimeter)
CloudKhó triển khaiPhù hợp tự nhiên
Tác động vi phạmLớn (bên trong được tin)Hạn chế (không có trust ngầm)

Zero Trust Trong Ứng Dụng Web

Ngay cả không có cơ sở hạ tầng zero trust đầy đủ, bạn có thể áp dụng các nguyên tắc trong ứng dụng:

Xác minh mọi request rõ ràng:

// ❌ Tư duy perimeter: tin tưởng mạng nội bộ
app.get('/internal/admin', (req, res) => {
  // "Endpoint này chỉ truy cập từ mạng nội bộ, nên an toàn"
  return res.json(await getAllUsers());
});
 
// ✅ Zero trust: xác thực mọi request bất kể nguồn gốc
app.get('/internal/admin', requireServiceToken, requireScope('admin:read'), async (req, res) => {
  return res.json(await getAllUsers());
});

Giả định vi phạm trong thiết kế:

// Thiết kế như thể kẻ tấn công đã ở bên trong:
// - Mã hóa dữ liệu nhạy cảm ngay cả trong database của bạn
// - Dùng credentials riêng cho mỗi service
// - Log mọi truy cập vào tài nguyên nhạy cảm
// - Phân đoạn dữ liệu để một service bị xâm phạm không đọc được mọi thứ
 
const sensitiveField = encrypt(data.ssn, process.env.FIELD_ENCRYPTION_KEY);
await db.query(
  'INSERT INTO users (email, ssn_encrypted) VALUES ($1, $2)',
  [data.email, sensitiveField]
);

Vòng Đời Phát Triển An Toàn (SDLC)

Bảo mật nên là một phần của mọi giai đoạn phát triển — không phải là phần bổ sung trước khi release.

Bảo Mật Trong Từng Giai Đoạn

Yêu Cầu Bảo Mật

Khi viết user stories, bao gồm yêu cầu bảo mật:

Tính năng: Đăng nhập user
 
Yêu cầu chức năng:
  Là user, tôi muốn đăng nhập bằng email và mật khẩu.
 
Yêu cầu bảo mật:
  - Mật khẩu phải được hash với bcrypt (cost factor ≥ 12)
  - Rate limit: tối đa 5 lần thất bại mỗi 15 phút mỗi IP
  - Khóa tài khoản sau 10 lần thất bại; yêu cầu xác minh email để mở khóa
  - Log tất cả nỗ lực đăng nhập (thành công và thất bại) với IP và user agent
  - Trả về "Thông tin đăng nhập không hợp lệ" cho cả email sai và mật khẩu sai
  - Session token phải là random 128-bit, lưu server-side
  - Session hết hạn sau 24 giờ không hoạt động

Triết Lý Kiểm Tra Đầu Vào

Không bao giờ tin đầu vào. Mọi dữ liệu đến từ bên ngoài ranh giới ứng dụng phải được validate trước khi sử dụng.

Nguồn đầu vào không tin cậy:

  • HTTP request body
  • Query parameters và URL parameters
  • HTTP headers (bao gồm Cookie, Authorization, User-Agent)
  • File uploads
  • WebSocket messages
  • Dữ liệu từ external APIs (ngay cả đối tác "tin cậy")
  • Dữ liệu từ database của chính bạn (nếu được tạo từ user input)
// ❌ XẤU: Tin client
app.post('/posts', async (req, res) => {
  const { title, content, authorId } = req.body;
  await db.query('INSERT INTO posts ...', [title, content, authorId]);
});
 
// ✅ TỐT: Validate mọi thứ, override những gì client không nên kiểm soát
app.post('/posts', requireAuth, async (req, res) => {
  const schema = z.object({
    title: z.string().min(1).max(200).trim(),
    content: z.string().min(1).max(50000),
    // authorId đến từ session đã xác thực, KHÔNG phải từ request body
  });
 
  const { title, content } = schema.parse(req.body);
  const authorId = req.user.id; // Từ auth middleware, không phải user input
 
  await db.query('INSERT INTO posts ...', [title, content, authorId]);
});

Mã Hóa Đầu Ra

Mã hóa đầu ra theo ngữ cảnh nó xuất hiện. Cùng một dữ liệu cần mã hóa khác nhau trong HTML, JavaScript, URLs và SQL.

// Ngữ cảnh HTML: mã hóa < > & " '
const safeHtml = escapeHtml(userInput);
// ví dụ: <script> trở thành &lt;script&gt;
 
// Ngữ cảnh URL: mã hóa ký tự đặc biệt
const safeUrl = encodeURIComponent(userInput);
// ví dụ: "hello world" trở thành "hello%20world"
 
// Ngữ cảnh JavaScript: mã hóa cho JS strings
const safeJs = JSON.stringify(userInput);
// Escape đúng cách dấu ngoặc kép và ký tự đặc biệt
 
// Ngữ cảnh SQL: dùng parameterized queries (không bao giờ nối chuỗi)
await db.query('SELECT * FROM users WHERE email = $1', [userInput]);

Kết Hợp Tất Cả: Ví Dụ Thực Tế

Hãy áp dụng tất cả các khái niệm này cho một tính năng thực: hệ thống comment của user trên blog.

Threat Modeling Hệ Thống Comment

Chúng ta đang xây dựng gì?

  • User gửi comment trên bài viết blog
  • Comment được lưu trong database
  • Comment được hiển thị cho tất cả khách

Điều gì có thể xảy ra sai? (STRIDE)

Mối Đe DọaLoạiRủi Ro
Kẻ tấn công đăng XSS payload trong commentTampering + Info DisclosureTới Hạn
Bot spam làm ngập phần commentDoSCao
User đăng comment với danh nghĩa người khácSpoofingCao
Admin xóa comment, user chối bỏ điều đó đã xảy raRepudiationTrung bình
IDOR: sửa/xóa comment của user khácElevation of PrivilegeCao

Defense in depth cho comments:

Checklist triển khai:

  • ✅ Rate limit gửi comment theo user
  • ✅ Yêu cầu xác thực để đăng
  • ✅ Validate độ dài comment (min 1, max 2000 ký tự)
  • ✅ Sanitize HTML để chỉ cho phép các tag an toàn (ví dụ: <b>, <i>) hoặc strip toàn bộ HTML
  • ✅ Lưu author_id từ session, không bao giờ từ request body
  • ✅ Kiểm tra ownership trước khi cho phép sửa/xóa
  • ✅ Parameterize tất cả database queries
  • ✅ HTML-encode nội dung comment khi render
  • ✅ Log tất cả hành động comment với timestamp, user, IP

Lỗi Phổ Biến Của Người Mới Bắt Đầu

Lỗi 1: Validate Chỉ Trên Client

// ❌ Chỉ validate ở frontend
<input type="email" required maxlength="100" />
 
// ✅ Luôn validate ở server cũng vậy
app.post('/subscribe', (req, res) => {
  const { email } = req.body;
  if (!isValidEmail(email) || email.length > 100) {
    return res.status(400).json({ error: 'Email không hợp lệ' });
  }
  // ...
});

Validate phía client là UX. Validate phía server là bảo mật. Kẻ tấn công sẽ dùng curl hoặc Burp Suite để bypass hoàn toàn frontend của bạn.

Lỗi 2: Tin Tưởng Internal Services

// ❌ "Đây là internal microservice, không cần auth"
app.get('/internal/user/:id', (req, res) => {
  return User.findById(req.params.id);
});
 
// ✅ Zero trust: xác thực mọi service-to-service call
app.get('/internal/user/:id', verifyServiceToken, (req, res) => {
  return User.findById(req.params.id);
});

Nếu mạng nội bộ của bạn bao giờ bị xâm phạm (lateral movement, cấu hình sai cloud, insider độc hại), các internal services không được bảo vệ trở thành mục tiêu dễ dàng.

Lỗi 3: Error Messages Nhiều Thông Tin

// ❌ Tiết lộ thông tin về hệ thống
try {
  await loginUser(email, password);
} catch (error) {
  if (error.code === 'USER_NOT_FOUND') {
    res.status(401).json({ error: 'Không tìm thấy user' }); // Xác nhận user không tồn tại
  } else if (error.code === 'WRONG_PASSWORD') {
    res.status(401).json({ error: 'Sai mật khẩu' }); // Xác nhận user tồn tại
  }
}
 
// ✅ Thông báo chung không tiết lộ gì
try {
  await loginUser(email, password);
} catch (error) {
  logger.warn('Đăng nhập thất bại', { email, reason: error.code }); // Log chi tiết nội bộ
  res.status(401).json({ error: 'Thông tin đăng nhập không hợp lệ' }); // Chung cho client
}

Lỗi 4: Bảo Mật Là Phần Thêm Vào Sau

Tuần 1-3: Xây dựng tính năng
Tuần 4: "Hãy thêm bảo mật"
Tuần 5: Launch (bảo mật làm một phần)

Bảo mật được gắn vào cuối luôn không đầy đủ. Tích hợp từ đầu — mỗi user story nên có yêu cầu bảo mật, mỗi PR nên bao gồm checklist security review.


Checklist Bảo Mật Cho Mọi Tính Năng

Trước khi đánh dấu bất kỳ tính năng nào là "hoàn thành," xác minh:

Authentication & Authorization:
✅ Mọi endpoint có kiểm tra auth phù hợp
✅ Authorization được kiểm tra ở tầng data, không chỉ routing
✅ User chỉ có thể truy cập dữ liệu của mình (hoặc dữ liệu được chia sẻ rõ ràng)

Đầu Vào & Đầu Ra:
✅ Tất cả user input được validate ở server
✅ Đầu ra được mã hóa theo ngữ cảnh (HTML, JSON, URL)
✅ Error messages không tiết lộ chi tiết nội bộ

Bảo Mật Dữ Liệu:
✅ Dữ liệu nhạy cảm không được log
✅ Database queries dùng parameterized statements
✅ Các trường nhạy cảm được mã hóa at rest

Availability:
✅ Rate limiting được áp dụng cho các endpoint thay đổi state
✅ Giới hạn kích thước file upload được thực thi
✅ Query timeouts được cấu hình

Observability:
✅ Các hành động liên quan bảo mật được log (login, logout, permission denied, truy cập data)
✅ Logs không chứa dữ liệu nhạy cảm (mật khẩu, tokens, PII)


Tóm Tắt và Bài Học Chính

✅ CIA Triad (Confidentiality, Integrity, Availability) là nền tảng của mọi quyết định bảo mật
✅ Threat modeling với STRIDE giúp bạn tìm hệ thống những gì có thể xảy ra sai trước khi xây dựng
✅ Attack surface là mọi thứ kẻ tấn công có thể tiếp cận — giảm thiểu mạnh mẽ
✅ Defense in depth có nghĩa là nhiều lớp độc lập — không có điểm lỗi đơn
✅ Nguyên tắc least privilege giới hạn blast radius khi có điều gì đó bị xâm phạm
✅ Fail-safe defaults có nghĩa là hệ thống mặc định ở trạng thái an toàn khi không chắc chắn
✅ Zero trust có nghĩa là không bao giờ tin ngầm — xác thực và phân quyền mọi request
✅ Validate tất cả input phía server — validate phía client là UX, không phải bảo mật
✅ Bảo mật thuộc về mọi giai đoạn phát triển — không phải là bước cuối cùng


Tiếp Theo

Bạn đã xây dựng tư duy bảo mật của mình. Bây giờ hãy xem các lỗ hổng cụ thể ảnh hưởng đến ứng dụng thực tế:

Tiếp theo: Giai Đoạn 2: OWASP Top 10 Giải Thích — Tất cả 10 loại lỗ hổng, cách chúng hoạt động và cách ngăn chặn.


Bài Viết Liên Quan


Đây là Phần 2 của Lộ Trình Học Bảo Mật Web. Bắt đầu từ đầu hoặc nhảy đến chủ đề bạn quan tâm.

📬 Đăng ký nhận bản tin

Nhận những bài viết mới nhất qua email mỗi tuần. Không spam, hủy đăng ký bất cứ lúc nào.

Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất cứ lúc nào.

💬 Comments

Đăng nhập để bình luận

Chúng tôi sẽ không đăng bất cứ điều gì mà không có sự cho phép của bạn.