Tải lên tệp là một tính năng phổ biến trong các ứng dụng web hiện đại. Người dùng có thể gửi hình ảnh, video, tài liệu và các loại tệp khác để tương tác với dịch vụ. Tuy nhiên, việc tải tệp lên cũng tiềm ẩn những nguy cơ bảo mật. Bài viết này sẽ cung cấp cho bạn hướng dẫn trực quan, chuyên sâu để bảo mật tệp tải lên trong ứng dụng Node.js Express. Hãy làm theo để đảm bảo ứng dụng của bạn vẫn an toàn và hiệu quả.
1. Hiểu về rủi ro khi tải tệp lên
Trước khi đi sâu vào các biện pháp bảo mật, điều cần thiết là phải hiểu những rủi ro tiềm ẩn liên quan đến việc tải tệp lên. Một số rủi ro này bao gồm:
- Tải lên tệp độc hại : Kẻ tấn công có thể tải lên các tệp chứa tập lệnh độc hại có thể gây hại cho ứng dụng hoặc máy chủ của bạn.
- Tấn công từ chối dịch vụ (DoS) : Một số lượng lớn tệp tải lên có thể làm cạn kiệt tài nguyên máy chủ, khiến ứng dụng không phản hồi.
- Phơi bày dữ liệu nhạy cảm : Người dùng trái phép có thể có quyền truy cập vào các tệp chứa thông tin nhạy cảm.
2. Thiết lập ứng dụng Node.js Express cơ bản
Để trình bày cách bảo mật tệp tải lên, hãy thiết lập một ứng dụng Node.js Express cơ bản. Bắt đầu bằng cách cài đặt các gói cần thiết:
1 2 3 | <span class="token function">npm</span> init -y <span class="token function">npm</span> <span class="token function">install</span> express multer |
Tiếp theo, tạo tệp app.js
và nhập các mô-đun cần thiết:
1 2 3 4 5 6 7 8 9 10 | <span class="token keyword">const</span> express <span class="token operator">=</span> <span class="token function">require</span> <span class="token punctuation">(</span> <span class="token string">'express'</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">const</span> multer <span class="token operator">=</span> <span class="token function">require</span> <span class="token punctuation">(</span> <span class="token string">'multer'</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token function">express</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">const</span> port <span class="token operator">=</span> process <span class="token punctuation">.</span> env <span class="token punctuation">.</span> <span class="token constant">PORT</span> <span class="token operator">||</span> <span class="token number">3000</span> <span class="token punctuation">;</span> app <span class="token punctuation">.</span> <span class="token function">listen</span> <span class="token punctuation">(</span> port <span class="token punctuation">,</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> console <span class="token punctuation">.</span> <span class="token function">log</span> <span class="token punctuation">(</span> <span class="token template-string"><span class="token template-punctuation string">`</span> <span class="token string">Server running at http://localhost:</span> <span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span> port <span class="token interpolation-punctuation punctuation">}</span></span> <span class="token template-punctuation string">`</span></span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
3. Triển khai tải tệp lên bằng Multer
Multer là một phần mềm trung gian phổ biến để xử lý các tệp tải lên trong Express. Bắt đầu bằng cách định cấu hình Multer và thiết lập công cụ lưu trữ:
1 2 3 4 5 6 7 8 9 10 11 12 | <span class="token keyword">const</span> storage <span class="token operator">=</span> multer <span class="token punctuation">.</span> <span class="token function">diskStorage</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> <span class="token function-variable function">destination</span> <span class="token operator">:</span> <span class="token punctuation">(</span> <span class="token parameter">req <span class="token punctuation">,</span> file <span class="token punctuation">,</span> cb</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">cb</span> <span class="token punctuation">(</span> <span class="token keyword">null</span> <span class="token punctuation">,</span> <span class="token string">'./uploads'</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">,</span> <span class="token function-variable function">filename</span> <span class="token operator">:</span> <span class="token punctuation">(</span> <span class="token parameter">req <span class="token punctuation">,</span> file <span class="token punctuation">,</span> cb</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> uniqueSuffix <span class="token operator">=</span> Date <span class="token punctuation">.</span> <span class="token function">now</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">'-'</span> <span class="token operator">+</span> Math <span class="token punctuation">.</span> <span class="token function">round</span> <span class="token punctuation">(</span> Math <span class="token punctuation">.</span> <span class="token function">random</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">1e9</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token function">cb</span> <span class="token punctuation">(</span> <span class="token keyword">null</span> <span class="token punctuation">,</span> file <span class="token punctuation">.</span> fieldname <span class="token operator">+</span> <span class="token string">'-'</span> <span class="token operator">+</span> uniqueSuffix <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">const</span> upload <span class="token operator">=</span> <span class="token function">multer</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> storage <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
Bây giờ, hãy tạo một tuyến đường để tải tệp lên:
1 2 3 4 | app <span class="token punctuation">.</span> <span class="token function">post</span> <span class="token punctuation">(</span> <span class="token string">'/upload'</span> <span class="token punctuation">,</span> upload <span class="token punctuation">.</span> <span class="token function">single</span> <span class="token punctuation">(</span> <span class="token string">'file'</span> <span class="token punctuation">)</span> <span class="token punctuation">,</span> <span class="token punctuation">(</span> <span class="token parameter">req <span class="token punctuation">,</span> res</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> res <span class="token punctuation">.</span> <span class="token function">status</span> <span class="token punctuation">(</span> <span class="token number">200</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">send</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> message <span class="token operator">:</span> <span class="token string">'File uploaded successfully.'</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
4. Bảo mật tệp tải lên
4.1 Kích thước tệp giới hạn
Biện pháp bảo mật đầu tiên là giới hạn kích thước tệp. Điều này có thể giúp ngăn chặn các cuộc tấn công DoS và giảm nguy cơ hết tài nguyên máy chủ. Đặt giới hạn kích thước tệp khi định cấu hình Multer:
1 2 3 4 5 | <span class="token keyword">const</span> upload <span class="token operator">=</span> <span class="token function">multer</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> storage <span class="token punctuation">,</span> limits <span class="token operator">:</span> <span class="token punctuation">{</span> fileSize <span class="token operator">:</span> <span class="token number">2</span> <span class="token operator">*</span> <span class="token number">1024</span> <span class="token operator">*</span> <span class="token number">1024</span> <span class="token punctuation">}</span> <span class="token punctuation">,</span> <span class="token comment">// 2MB</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
4.2 Xác thực các loại tệp
Đảm bảo rằng chỉ các loại tệp cụ thể mới được phép. Điều này làm giảm nguy cơ tải lên tệp độc hại. Thêm chức năng lọc tệp vào cấu hình Multer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <span class="token keyword">const</span> allowedFileTypes <span class="token operator">=</span> <span class="token punctuation">[</span> <span class="token string">'image/jpeg'</span> <span class="token punctuation">,</span> <span class="token string">'image/png'</span> <span class="token punctuation">,</span> <span class="token string">'image/gif'</span> <span class="token punctuation">]</span> <span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token function-variable function">fileFilter</span> <span class="token operator">=</span> <span class="token punctuation">(</span> <span class="token parameter">req <span class="token punctuation">,</span> file <span class="token punctuation">,</span> cb</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> allowedFileTypes <span class="token punctuation">.</span> <span class="token function">includes</span> <span class="token punctuation">(</span> file <span class="token punctuation">.</span> mimetype <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">cb</span> <span class="token punctuation">(</span> <span class="token keyword">null</span> <span class="token punctuation">,</span> <span class="token boolean">true</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token function">cb</span> <span class="token punctuation">(</span> <span class="token keyword">null</span> <span class="token punctuation">,</span> <span class="token boolean">false</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">;</span> <span class="token keyword">const</span> upload <span class="token operator">=</span> <span class="token function">multer</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> storage <span class="token punctuation">,</span> limits <span class="token operator">:</span> <span class="token punctuation">{</span> fileSize <span class="token operator">:</span> <span class="token number">2</span> <span class="token operator">*</span> <span class="token number">1024</span> <span class="token operator">*</span> <span class="token number">1024</span> <span class="token punctuation">}</span> <span class="token punctuation">,</span> fileFilter <span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
4.3 Xử lý các tệp bị từ chối
Khi một tệp bị từ chối, điều cần thiết là cung cấp cho người dùng thông báo lỗi thích hợp. Cập nhật tuyến /upload
để xử lý các tệp bị từ chối:
1 2 3 4 5 6 7 | app <span class="token punctuation">.</span> <span class="token function">post</span> <span class="token punctuation">(</span> <span class="token string">'/upload'</span> <span class="token punctuation">,</span> upload <span class="token punctuation">.</span> <span class="token function">single</span> <span class="token punctuation">(</span> <span class="token string">'file'</span> <span class="token punctuation">)</span> <span class="token punctuation">,</span> <span class="token punctuation">(</span> <span class="token parameter">req <span class="token punctuation">,</span> res</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> <span class="token operator">!</span> req <span class="token punctuation">.</span> file <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> res <span class="token punctuation">.</span> <span class="token function">status</span> <span class="token punctuation">(</span> <span class="token number">400</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">send</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> message <span class="token operator">:</span> <span class="token string">'Invalid file type or file too large.'</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> res <span class="token punctuation">.</span> <span class="token function">status</span> <span class="token punctuation">(</span> <span class="token number">200</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">send</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> message <span class="token operator">:</span> <span class="token string">'File uploaded successfully.'</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
4.4 Quét tệp để tìm phần mềm độc hại
Để tăng cường bảo vệ ứng dụng của bạn, hãy quét các tệp đã tải lên để tìm phần mềm độc hại. Một tùy chọn là sử dụng công cụ chống vi-rút ClamAV. Cài đặt gói clamscan
:
1 2 | <span class="token function">npm</span> <span class="token function">install</span> clamscan |
Sau đó, nhập và định cấu hình mô-đun ClamScan:
1 2 3 4 5 6 7 8 9 10 11 | <span class="token keyword">const</span> <span class="token punctuation">{</span> NodeClam <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span> <span class="token punctuation">(</span> <span class="token string">'clamscan'</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">const</span> clamscan <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">NodeClam</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> clamscan <span class="token punctuation">.</span> <span class="token function">init</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> clamdscan <span class="token operator">:</span> <span class="token punctuation">{</span> path <span class="token operator">:</span> <span class="token string">'/usr/bin/clamdscan'</span> <span class="token punctuation">,</span> <span class="token comment">// Path to clamdscan binary on your server</span> config_file <span class="token operator">:</span> <span class="token string">'/etc/clamd.d/scan.conf'</span> <span class="token punctuation">,</span> <span class="token comment">// Path to ClamAV config file on your server</span> <span class="token punctuation">}</span> <span class="token punctuation">,</span> preference <span class="token operator">:</span> <span class="token string">'clamdscan'</span> <span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
Quét tệp đã tải lên để tìm phần mềm độc hại trong /upload
route:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | app <span class="token punctuation">.</span> <span class="token function">post</span> <span class="token punctuation">(</span> <span class="token string">'/upload'</span> <span class="token punctuation">,</span> upload <span class="token punctuation">.</span> <span class="token function">single</span> <span class="token punctuation">(</span> <span class="token string">'file'</span> <span class="token punctuation">)</span> <span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span> <span class="token parameter">req <span class="token punctuation">,</span> res</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> <span class="token operator">!</span> req <span class="token punctuation">.</span> file <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> res <span class="token punctuation">.</span> <span class="token function">status</span> <span class="token punctuation">(</span> <span class="token number">400</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">send</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> message <span class="token operator">:</span> <span class="token string">'Invalid file type or file too large.'</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> scanResult <span class="token operator">=</span> <span class="token keyword">await</span> clamscan <span class="token punctuation">.</span> <span class="token function">scan_file</span> <span class="token punctuation">(</span> req <span class="token punctuation">.</span> file <span class="token punctuation">.</span> path <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> scanResult <span class="token punctuation">.</span> is_infected <span class="token punctuation">)</span> <span class="token punctuation">{</span> fs <span class="token punctuation">.</span> <span class="token function">unlinkSync</span> <span class="token punctuation">(</span> req <span class="token punctuation">.</span> file <span class="token punctuation">.</span> path <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token comment">// Delete infected file</span> <span class="token keyword">return</span> res <span class="token punctuation">.</span> <span class="token function">status</span> <span class="token punctuation">(</span> <span class="token number">400</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">send</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> message <span class="token operator">:</span> <span class="token string">'File is infected with malware.'</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> res <span class="token punctuation">.</span> <span class="token function">status</span> <span class="token punctuation">(</span> <span class="token number">200</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">send</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> message <span class="token operator">:</span> <span class="token string">'File uploaded successfully.'</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span> error <span class="token punctuation">)</span> <span class="token punctuation">{</span> res <span class="token punctuation">.</span> <span class="token function">status</span> <span class="token punctuation">(</span> <span class="token number">500</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">send</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> message <span class="token operator">:</span> <span class="token string">'Error scanning file for malware.'</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
Đừng quên nhập mô-đun fs
:
1 2 | <span class="token keyword">const</span> fs <span class="token operator">=</span> <span class="token function">require</span> <span class="token punctuation">(</span> <span class="token string">'fs'</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
4.5 Lưu trữ tệp bên ngoài Web Root
Lưu trữ các tệp đã tải lên bên ngoài web root giúp ngăn truy cập trực tiếp vào các tệp đó. Trong ví dụ này, chúng tôi sẽ sử dụng thư mục tải lên, nằm ngoài thư mục gốc web của bạn.
1 2 3 4 5 6 7 | <span class="token keyword">const</span> storage <span class="token operator">=</span> multer <span class="token punctuation">.</span> <span class="token function">diskStorage</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> <span class="token function-variable function">destination</span> <span class="token operator">:</span> <span class="token punctuation">(</span> <span class="token parameter">req <span class="token punctuation">,</span> file <span class="token punctuation">,</span> cb</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">cb</span> <span class="token punctuation">(</span> <span class="token keyword">null</span> <span class="token punctuation">,</span> <span class="token string">'../uploads'</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">,</span> <span class="token comment">// ...</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
4.6 Cung cấp tệp một cách an toàn
Để cung cấp tệp một cách an toàn, hãy tạo một tuyến mới xác minh quyền của người dùng và cung cấp tệp bằng phương thức res.sendFile():
1 2 3 4 5 6 7 8 | app <span class="token punctuation">.</span> <span class="token function">get</span> <span class="token punctuation">(</span> <span class="token string">'/files/:filename'</span> <span class="token punctuation">,</span> <span class="token punctuation">(</span> <span class="token parameter">req <span class="token punctuation">,</span> res</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// Verify user authorization here</span> <span class="token keyword">const</span> filename <span class="token operator">=</span> req <span class="token punctuation">.</span> params <span class="token punctuation">.</span> filename <span class="token punctuation">;</span> <span class="token keyword">const</span> filePath <span class="token operator">=</span> path <span class="token punctuation">.</span> <span class="token function">join</span> <span class="token punctuation">(</span> <span class="token string">'../uploads'</span> <span class="token punctuation">,</span> filename <span class="token punctuation">)</span> <span class="token punctuation">;</span> res <span class="token punctuation">.</span> <span class="token function">sendFile</span> <span class="token punctuation">(</span> filePath <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
Đừng quên nhập mô-đun path
:
1 2 | <span class="token keyword">const</span> path <span class="token operator">=</span> <span class="token function">require</span> <span class="token punctuation">(</span> <span class="token string">'path'</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
Phần kết luận
Bằng cách làm theo hướng dẫn toàn diện này, bạn có thể tạo hệ thống tải lên tệp an toàn trong ứng dụng Node.js Express. Việc triển khai các biện pháp bảo mật phù hợp, chẳng hạn như giới hạn kích thước tệp, xác thực loại tệp, quét tệp để tìm phần mềm độc hại và phân phát tệp một cách an toàn, sẽ giúp bảo vệ ứng dụng của bạn khỏi nhiều rủi ro liên quan đến việc tải tệp lên.
Và cuối cùng
Như mọi khi, tôi hy vọng bạn thích bài viết này và có một cái gì đó mới. Xin cảm ơn và hẹn gặp lại các bạn trong những bài viết tiếp theo!
Nếu các bạn thích bài viết này thì hãy cho mình 1 like và subscribe để ủng hộ mình nhé. Cảm ơn.