[Ký sự NCSC] Giai đoạn 2: Xây Web Cơ bản – Phần 2: Viết code

person writing on white paper

🐔 “Ký sự NCSC” là chuyên mục bài viết về thời gian mình thực tập tại Trung tâm Giám sát An toàn Không gian mạng Quốc gia (National Cyber Security Center), thuộc Cục An toàn Thông tin, Bộ Thông tin và Truyền thông. Nghe tên Trung tâm thì khủng thế thôi, còn mình thì… gà, đến để “cục tác lá chanh”, học từ những bài học đơn giản nhất.

Trong chuyên mục này, mình sẽ chia sẻ những nhiệm vụ và bài tập mình đã trải qua, cũng như một vài kỷ niệm vui vẻ hay ho trong thời gian ở Trung tâm.

Bài viết này là phần tiếp theo của chuỗi bài làm web PHP. Nếu chưa, bạn có thể đón đọc phần trước tại:

Cài cắm xong rồi thì ta bắt tay vào code thôi. Yêu cầu của anh chị là “không được clone mã nguồn của người khác”, nhưng tôi hỏi thì anh tôi lại bảo “clone về đọc hiểu là được!”. Dù sao con đường trước mắt cũng mù tịt, tôi cũng không từ mọi thủ đoạn để làm được cái mình muốn.

Ở chiếc máy local của mình, tôi sử dụng XAMPP (như đã viết trong bài trước) để có thể hiểu được mọi thứ dễ dàng hơn.

Để tạo dựng trang web với XAMPP, bạn cần:

Apache với MySQL mà hiện màu xanh là bình yên đã đến với bạn nhé. Còn nếu nó đỏ màu thì bạn phải đọc Logs xem tại sao không khởi động được.
  • Tạo mới thư mục cho web và đưa nó vào folder của XAMPP
  • Mở XAMPP panel và khởi động các mục Apache, MySQL
  • Truy cập đường dẫn http://localhost/<web_name> với web_name là tên bạn đặt cho website, chính là tên folder bạn đã đặt ở bước 1.

Bước 1: Lên khung trang web với PHP

Về cơ bản, trang web sẽ có những trang chính sau đây:

  • Trang chủ, chưa đăng nhập và đã đăng nhập (Index – Home)
  • Trang đăng ký – đăng nhập (Auth)
  • Trang chỉnh sửa (thêm, sửa, xoá) bài viết
  • Trang chỉnh sửa (thêm, sửa, xoá) người dùng

Bạn cẩn thận hơn thì làm công tác phân tích yêu cầu rồi thiết kế, vẽ use-case, các thể loại sơ đồ UML khác ra. Thời gian tôi chẳng có nhiều nên tôi… xông vào code luôn, vì cái web cũng không quá phức tạp.

Trong số hướng dẫn tôi đọc, có trang này tôi đã chọn đi theo vì nó sát với yêu cầu đề bài:

Step-by-Step PHP Tutorials for Beginners – Creating your PHP Program FROM SCRATCH: Basic Authentication, Membership and CRUD Functionalities

Tuy nhiên, không phải lấy code của người ta về là xong. Tôi đánh giá cao việc họ chỉ bảo tận tình từng bước, song tutorial này cũng có hai vấn đề chính:

  • Không có sự nhất quán trong code nhúng trong bài blog và code trên GitHub của họ. Có một vài chỗ có sự khác nhau, nên bạn cũng phải để ý mà xem cái nào sai để sửa.
  • Bản thân cái code chưa đầy đủ và hoàn thiện theo đúng yêu cầu đề bài. Chúng ta vẫn cần xoá đi những thứ không cần và viết thêm một số thứ.

Nhiệm vụ còn lại của bạn là phải đọc hiểu và tự code tiếp nhé.

Bước 2: Xây dựng cơ sở dữ liệu

Một lần nữa, tôi theo triết lý Marie Kondo là tối giản nhất mọi thứ có thể. Một cái database phức tạp có lẽ chẳng bao giờ là thứ có thể “spark joy” nơi tôi, nên tôi chỉ đưa vào những thứ đơn giản:

Bạn có thể tạo cơ sở dữ liệu này bằng cách truy cập localhost/phpmyadmin sau khi khởi động XAMPP, sử dụng GUI trong trình duyệt. Cách này là dễ nhất.

Hoặc bạn cũng có thể lựa chọn sinh mã .sql từ các phần mềm vẽ ER diagram (Entity-Relationship Diagram) vốn được dùng để thiết kế database, rồi sau đó chạy mã này.

Hoặc “trâu” hơn nữa, bạn có thể tự viết. Dù sao thì chúng ta vẫn cần có script SQL, để có thể tạo dựng database trong server sau này (do sẽ không có GUI).

Bước 3: Vẩy đũa thần “phù phép” UI

Giao diện trang web từ code thuần lúc nào cũng là thứ “thập cổ lai hi” cứ như còn rơi rớt lại từ thời Web 1.0:

Style lại hay không là tuỳ bạn, vì yêu cầu chủ yếu là tính năng thay vì hình thức, nhưng nhìn đẹp đẹp chút thì cũng có hứng làm hơn.

Thiết kế của tôi cho ứng dụng di động (giả tưởng) cho hãng tương cà Heinz, lựa chọn của nhóm chúng tôi trong môn Thiết kế quảng bá sản phẩm (Product Promotion Design). Trường cho học ba môn bổ trợ, tôi chọn cả ba là những học phần thiết kế. Mãi đến hè năm lớp 12 tôi vẫn ngồi làm portfolio, nếu tôi không làm IT, hẳn là tôi đã đi vẽ vời.

Tôi vẽ Figma từ năm cấp 3. Cũng đã là một người làm media “máu mặt” (thiết kế đồ hoạ, chụp ảnh, quay phim, hậu kỳ đủ cả). Đã kinh qua câu chuyện kinh điển final1 final2 rồi final.final. Nhưng code CSS thì không mấy khi trực tiếp nhúng tay làm. Tự thấy thiết kế của mình cũng “ổn áp” không đến nỗi nào, song tôi vẫn tức lắm khi mấy thằng bạn làm front-end cứ “ăn bớt” thiết kế của tôi, kêu là nhiều thứ quá làm không nổi.

Góc flex: Thiết kế của tôi cho project cuối kỳ môn Kỹ thuật phần mềm (Software Engineering), một trang web bán mỹ phẩm làm đẹp. Tôi biết ơn nhất là anh em gánh code cho tôi để tôi có thời gian tha hồ múa bút. Dĩ nhiên là tôi cũng phải nói cho bạn hiểu, “múa bút” không phải dễ mà làm được đâu… Tôi mất đi nhiều nước mắt với thiết kế hơn là với lập trình.

Giờ thì tôi đã hiểu tiếng lòng của chúng nó. Đống ID với class loạn lên, style cái này đè lên cái kia, hoặc có chỗ set mãi không được làm tôi ngớ cả người. Nhiều element quá ngồi căn trối chết, lại còn chưa kể vụ responsive, tự điều chỉnh cho phù hợp các kích cỡ màn hình khác nhau. Cuối cùng thì tôi vẫn thấy đơn giản là chân ái:

Tôi nhìn trang web trước mặt mà thấy lòng tự trọng và danh dự của một designer bị tổn thương nghiêm trọng. Nhưng thằng lập trình viên trong tôi vỗ vai hắn và bảo “Tao thấy thế này là quá ổn rồi.”

Vụ CSS này thì tuỳ bạn nhé, cơ bản là không phải đồ đem đi bán nên cũng không cần tô sơn tỉ mỉ quá. Tôi có mấy thằng bạn chuyên làm web, chúng nó styling cũng ổn mà nhanh hơn tôi nhiều. Thật là, đến cuối ngày vẫn là câu chuyện kỹ năng…

Bước 4: Nâng cao bảo mật

Đây là phần lấy đi nhiều nước mắt nước mũi nhất của tôi. Không hiểu thế nào, tôi cứ hỏi mấy anh bạn AI thì chúng nó toàn chỉ cho những nước đi “cùi bắp” nhất trước tiên! Rồi sau đó tôi nhận ra mình vẫn tự đấm được chính mình, lại lật đật đi hỏi thì chúng nó tỉnh bơ: “À, thực ra là có một cách tốt hơn để bảo mật trang web của bạn.”

Vậy là tôi phải học từng cái một, xong lại phải đập đi, sửa lại thành cái mới. Điển hình với lỗi SQL Injection:

SQL Injection

AI nó khuyên tôi dùng hàm real_escape_string() của mysql. Tôi cứ nghĩ như thế là xong, hoá ra vẫn có thể bypass được. Các kỹ thuật inject nâng cao như boolean-based, hex encode, magic quote hay kinh điển là truy vấn UNION sẽ khiến cho web tôi ói máu.

Kết quả cuối cùng là phải sử dụng một thứ gọi là prepared statement, với hàm prepare() có sẵn trong PHP.

Video của Computerphile, thứ đầu tiên khai sáng cho tôi về lỗ hổng này.

Prepared Statement là một kỹ thuật trong PHP giúp tránh được SQL Injection. Khi sử dụng prepared statements, bạn sẽ tạo ra một mẫu truy vấn SQL và gửi nó lên máy chủ trước. Máy chủ sau đó sẽ biên dịch mẫu truy vấn và lưu nó lại. Sau đó, bạn sẽ gửi các tham số cần thiết để điền vào mẫu truy vấn đó, và máy chủ sẽ chèn chúng vào mẫu đã biên dịch.

Ví dụ về cách sử dụng Prepared Statements trong PHP:

// Chuẩn bị một câu lệnh SQL
$stmt = $conn->prepare("INSERT INTO MyGuests (firstname, lastname, email) VALUES (?, ?, ?)");
$stmt->bind_param("sss", $firstname, $lastname, $email);

// Thay thế các dấu hỏi chấm bằng các giá trị thực
$firstname = "John";
$lastname = "Doe";
$email = "john@example.com";
$stmt->execute();

$stmt->close();
$conn->close();

Trong ví dụ trên, hàm bind_param() được sử dụng để liên kết các tham số với các dấu hỏi chấm trong câu lệnh SQL. Tham số đầu tiên của hàm này là một chuỗi chứa các ký tự chỉ ra kiểu dữ liệu cho từng tham số. Trong trường hợp này, “sss” tương ứng với ba tham số là chuỗi (string).

Bằng cách này, prepared statements giúp tăng cường bảo mật cho ứng dụng web của bạn bằng cách ngăn chặn SQL Injection.

CSRF

Web này có kha khá cái form, từ đăng kí – đăng nhập đến mấy chức năng thêm-sửa-xoá. Tôi mất rất lâu mới có thể nhận ra web mình có thể bị đấm bởi tấn công CSRF, cho phép kẻ xấu submit cái form cũ bao nhiêu lần cũng được.

Cách phòng tránh tấn công này tương đối dễ hiểu: Sinh ra một token duy nhất cho mỗi phiên/ form, lưu nó, và đưa nó vào trong form của tôi như một trường được ẩn đi. Mỗi khi người dùng gửi form, tôi sẽ cần phải check xem token này có hợp lệ hay không. Cái này gọi là CSRF Token.

Nguồn: Rajeev Ranjan, Medium.

CSRF, hay Cross-Site Request Forgery, là một kỹ thuật tấn công mà trong đó kẻ xấu lợi dụng quyền hạn của người dùng đối với một website để thực hiện những hành động mà người dùng không hề biết. Kẻ tấn công sẽ tạo ra những requests giả mạo và gửi chúng từ người dùng đến server. Do đó, server sẽ xử lý những requests này như thể chúng đến từ chính người dùng, dẫn đến những hậu quả không mong muốn.

CSRF Token là một biện pháp phòng chống tấn công CSRF. Khi người dùng tạo ra một session, hệ thống sẽ tạo ra một CSRF Token duy nhất và gán nó cho session đó. Token này sau đó sẽ được chèn vào mỗi form trên website. Khi người dùng submit một form, CSRF Token này sẽ được gửi cùng với form đến server. Server sau đó sẽ kiểm tra token này: nếu token không hợp lệ hoặc không khớp với token đã được lưu trước đó, server sẽ từ chối xử lý request.

Sử dụng CSRF Token, chúng ta có thể đảm bảo rằng mỗi form chỉ có thể được submit một cách hợp lệ thông qua chính website của chúng ta, từ chối mọi request giả mạo từ bên ngoài.

Hashing

Tôi biết điều tôi sắp nói ra đây sẽ khiến tôi trông như một đứa đần trong mắt bạn, nhưng tôi cũng vẫn đành phải nói:

Trang admin, phiên bản đầu tiên. Tôi ngồi nhìn cái màn hình này 5 phút và cảm thấy có gì đó sai sai, song vẫn không nhận thức được cái sai sai đó là gì. Chỉ sau khi đi rửa mặt (và tự tát mình vài cái), tôi mới nhận ra nó đang là thứ minh chứng cho việc người ta sẽ cười vào mặt tôi nếu tôi bảo tôi đi thực tập cho NCSC.

Bạn có hiểu điều tôi muốn nói chứ? Tôi đã lưu mật khẩu của người dùng dưới dạng plain text. Không băm (hash) lẫn không muối (salt) gì hết.

Hàm băm, hay hàm hash, là một chức năng chuyển đổi dữ liệu đầu vào với kích thước tùy ý thành một chuỗi ký tự có độ dài cố định. Điểm quan trọng của hàm băm là việc chuyển đổi này là một chiều – từ dữ liệu đầu vào, bạn có thể tạo ra chuỗi hash, nhưng từ chuỗi hash, bạn không thể khôi phục lại dữ liệu đầu vào. Điều này làm cho hàm băm trở nên cực kỳ hữu ích trong việc bảo mật, đặc biệt là khi lưu trữ mật khẩu.

Thay vì lưu trữ mật khẩu người dùng dưới dạng văn bản rõ ràng (plain text), một phương pháp thực hành tốt là lưu trữ mật khẩu dưới dạng chuỗi hash. Khi một người dùng nhập mật khẩu, mật khẩu đó được chuyển qua hàm băm, sau đó chuỗi hash được so sánh với chuỗi hash đã lưu trữ. Nếu hai chuỗi hash này khớp nhau, mật khẩu được chấp nhận là đúng. Điều này đảm bảo rằng ngay cả nếu dữ liệu mật khẩu bị lộ, mật khẩu thực sự vẫn được bảo vệ.

XSS

Lỗ hồng XSS là thứ phổ biến nhưng rà soát cho bằng hết nó thì cũng hơi vất, vậy nên tôi cũng chưa có cơ hội kiểm tra tử tế cho lắm. Những gì tôi làm chủ yếu là biến đổi (escape) hết các ký tự “nguy hiểm” trong đầu vào của người dùng khi hiển thị nó ở phía client-side.

Nguồn: Imperva.

XSS, hay Cross-Site Scripting, là một loại tấn công mà trong đó kẻ xấu chèn mã độc JavaScript vào trang web và khi người dùng truy cập trang web này, mã độc sẽ được thực thi. Điều này có thể dẫn đến việc lộ thông tin cá nhân hoặc quyền điều khiển tài khoản của người dùng.

Cách phòng chống tấn công XSS chủ yếu dựa vào việc lọc và chống lại các đầu vào độc hại từ người dùng. Cụ thể, bạn cần áp dụng hai biện pháp sau:

  1. Biến đổi (Escape) dữ liệu đầu vào: Điều này đảm bảo rằng các ký tự đặc biệt trong dữ liệu đầu vào, như “<” và “>”, không được hiểu như mã HTML hay JavaScript. Bằng cách này, bất kỳ mã độc nào mà kẻ tấn công cố gắng chèn vào trang web sẽ không thể thực thi mà chỉ được hiển thị như là văn bản thông thường.
  2. Sử dụng CSP (Content Security Policy): CSP là một cơ chế cho phép bạn kiểm soát nguồn nào có thể chạy script trên trang web của bạn. Bằng cách thiết lập CSP, bạn có thể hạn chế JavaScript chỉ được thực thi từ các nguồn tin cậy và ngăn chặn mã độc từ việc chạy trên trang web của bạn.

Nhớ rằng, không có biện pháp nào có thể bảo vệ trang web của bạn hoàn toàn khỏi tấn công XSS. Tuy nhiên, bằng cách áp dụng những biện pháp trên, bạn đã làm giảm đáng kể nguy cơ bị tấn công XSS.


Câu chuyện về bảo mật có lẽ sẽ chẳng bao giờ kết thúc, nên vấn đề thực sự luôn là cân nhắc giữa an ninh và nguồn lực sẵn có. Tôi đã mệt phờ sau những nỗ lực phía trên, vậy nên tôi đành chấp nhận mình làm được đến đó mà thôi.

Photo by Nubelson Fernandes on Unsplash

Nếu bạn vẫn còn sức lực chinh chiến, tôi cũng có vài gợi ý cho bạn:

  • Phòng tránh tấn công vét cạn (brute-force attack) bằng cách để ra các chính sách cho người dùng khi tạo tài khoản và mật khẩu, cơ chế “lockout” sau khi thử mật khẩu sai quá số lần quy định, các bài test CAPTCHA.
  • Luôn yêu cầu kết nối HTTPS để bảo vệ dữ liệu truyền qua lại giữa người dùng và máy chủ.
  • Quy định các chế độ mà trình duyệt và web phải tuân thủ như Same-Origin Policy, SameSite…
  • Kiểm tra các vector tấn công khác như File traversal, File upload (nếu web bạn có tính năng tải lên file)
  • … và nhiều những thứ nữa, nhưng tôi đã quên…

Hẹn bạn ở phần cuối, sau khi ta làm xong công cuộc lập trình thì ta sẽ đưa những thứ này deploy lên server nhé.

Bạn có thể tham khảo mã nguồn của tôi tại đây.

In