Chỉ upload một tấm ảnh cũng có thể thành RCE: Lỗ hổng Next.js cho thấy mặt tối của dependency chain

Một lỗ hổng critical trong libheif có thể biến file AVIF được xử lý bởi Next.js Image Optimization thành đường dẫn tới remote code execution. Vấn đề đáng chú ý không chỉ là bug, mà là chuỗi phụ thuộc Next.js → sharp → libheif cho thấy ứng dụng có thể kế thừa rủi ro từ những tầng mà developer chưa từng trực tiếp chọn hay gọi.

Chỉ upload một tấm ảnh cũng có thể thành RCE: Lỗ hổng Next.js cho thấy mặt tối của dependency chain

Một website cho phép người dùng đưa ảnh vào hệ thống thường được xem là đang xử lý dữ liệu “thụ động”. Nhưng cuối tháng 8/2026, một lỗ hổng mới trong hệ sinh thái Next.js nhắc lại một thực tế khó chịu: chỉ cần server cố đọc một file ảnh được chế tạo đặc biệt, code của kẻ tấn công về lý thuyết có thể chạy trong chính tiến trình ứng dụng.

Ngày 25/8/2026, nhóm Next.js phát hành hai bản vá bảo mật khẩn cấp 15.5.2416.3.3. Một trong hai lỗi được đánh giá Critical với điểm CVSS v4 9,5: Unauthenticated Remote Code Execution in Image Optimization API when AVIF files are used, theo advisory GHSA-2xp9-vwfh-vxw4.

Điều gây chú ý là lỗi không bắt đầu trong code của Next.js.

Next.js gọi thư viện xử lý ảnh sharp cho Image Optimization. Ở tầng thấp hơn, pipeline xử lý AVIF/HEIF phụ thuộc vào libheif. Chính libheif chứa lỗi heap buffer overflow có thể bị kích hoạt khi decode một file HEIC/HEIF/AVIF được tạo đặc biệt.

Chuỗi có thể được hình dung như sau:

Ứng dụng Next.js → Image Optimization API → sharp → libheif → dữ liệu AVIF không tin cậy.

Developer có thể chỉ viết một component hoặc một flow upload ảnh bình thường. Nhưng security boundary thực tế lại kéo dài xuống nhiều tầng native code mà project không trực tiếp sở hữu.

Đó là phần đáng nói nhất của sự cố này.

Không chỉ là “Next.js vừa có thêm một lỗ hổng”.

Nó là một ví dụ rất rõ về dependency chain risk: một framework có thể trở thành bề mặt tấn công vì một thư viện sâu bên dưới có lỗi memory-safety.

“Upload một tấm ảnh là RCE” đúng, nhưng cần một điều kiện quan trọng

Tiêu đề nghe đáng sợ, và trong cấu hình phù hợp thì rủi ro thực sự nghiêm trọng. Nhưng cần diễn đạt chính xác.

Không phải mọi ứng dụng Next.js có chức năng upload ảnh đều tự động bị khai thác.

Để lỗ hổng này đi tới đường nguy hiểm, một file AVIF/HEIF do attacker kiểm soát phải được đưa vào pipeline decode/optimize bị ảnh hưởng.

Nếu ứng dụng chỉ nhận file rồi lưu nguyên blob vào object storage mà không decode, resize hoặc tối ưu bằng stack bị lỗi, việc upload đơn thuần chưa đủ để kích hoạt bug.

Ngược lại, nếu ứng dụng cho phép user-controlled AVIF đi vào Image Optimization API hoặc một service server-side sử dụng cùng thư viện dễ tổn thương, file ảnh trở thành input cho native decoder.

Và đây là chỗ “ảnh” không còn là dữ liệu vô hại.

Ảnh cũng là một định dạng file phức tạp cần được parse như PDF hay video

Người dùng thường nghĩ một ảnh chỉ là ma trận pixel.

Trên đĩa, một file ảnh hiện đại phức tạp hơn rất nhiều.

Nó có thể chứa:

  • container và box metadata;
  • nhiều image item;
  • alpha plane;
  • color profile;
  • thumbnail;
  • transformation;
  • grid/tile;
  • auxiliary image;
  • bit depth khác nhau;
  • codec bitstream nén bên trong.

HEIF và AVIF đặc biệt giàu cấu trúc vì chúng dựa trên ISO Base Media File Format, cùng họ container với nhiều công nghệ multimedia hiện đại.

Muốn hiển thị hoặc resize ảnh, decoder phải đọc các cấu trúc này, cấp phát bộ nhớ, ghép plane, chuyển màu, scale và cuối cùng tạo buffer pixel.

Chỉ cần một phép tính kích thước sai ở một bước, parser có thể ghi ra ngoài vùng nhớ đã được cấp phát.

Đó chính là lớp lỗi xuất hiện trong libheif.

Lỗi gốc nằm ở libheif, không phải ở một đoạn JavaScript của Next.js

Advisory upstream của libheif, GHSA-g89c-p67h-r497, mô tả lỗi trong hàm scale_nearest_neighbor().

Một file HEIC/HEIF/AVIF được chế tạo đặc biệt có thể tạo ra tình huống liên quan các alpha plane lồng nhau với cách biểu diễn khác nhau.

Trong điều kiện lỗi, code cấp phát một vùng nhớ theo giả định mẫu dữ liệu 8-bit nhưng một nhánh xử lý khác lại ghi dữ liệu theo kích thước 16-bit.

Kết quả là heap buffer overflow: dữ liệu bị ghi vượt ra khỏi vùng nhớ hợp lệ.

Điểm khiến advisory được xếp Critical là phần dữ liệu ghi tràn có thể bị attacker kiểm soát. Nhóm nghiên cứu cho biết họ đã đạt code execution trên nhiều ứng dụng sử dụng thư viện.

Điều này không đồng nghĩa mọi deployment sẽ bị chiếm quyền với xác suất 100%. Khai thác memory corruption phụ thuộc build, allocator, hệ điều hành, sandbox và nhiều điều kiện runtime.

Nhưng khi nhà phát triển thư viện xác nhận một primitive ghi ngoài vùng nhớ có attacker-controlled content và có working code-execution exploit, đây không còn là lỗi crash thông thường.

Tại sao một bug C++ sâu phía dưới lại trở thành advisory của Next.js?

Vì abstraction không xóa dependency.

Next.js cung cấp next/image và Image Optimization API để developer không phải tự viết hệ thống resize, chuyển format và cache ảnh.

Đằng sau API thân thiện đó là code xử lý ảnh hiệu năng cao.

sharp là một package Node.js phổ biến cho các tác vụ như resize JPEG, PNG, WebP, AVIF và TIFF.

Sharp lại dựa vào các native image-processing component. Với AVIF/HEIF, libheif nằm trong chuỗi decode.

Khi Next.js nhận một nguồn ảnh cần optimize, framework không tự viết codec AV1 hay parser HEIF bằng JavaScript.

Nó chuyển công việc xuống stack native.

Do đó, một lỗ hổng ở tầng thấp có thể đi xuyên qua abstraction và xuất hiện ở endpoint public của framework.

Đây là supply-chain vulnerability, nhưng không phải kiểu “package npm bị chiếm tài khoản”

Cụm từ software supply chain thường gợi tới một maintainer bị hack, package độc hại được publish lên npm hoặc dependency bị cài backdoor.

Trường hợp này khác.

Không có bằng chứng libheif bị cài mã độc.

Đây là một security bug thông thường trong dependency hợp pháp.

Nhưng về mặt rủi ro, nó vẫn thuộc câu chuyện supply chain vì ứng dụng phía trên kế thừa bề mặt tấn công từ code của nhà cung cấp khác.

Một dependency chain có thể nguy hiểm theo ít nhất ba cách:

  • dependency bị chèn mã độc;
  • dependency hợp pháp có lỗ hổng;
  • dependency an toàn khi dùng độc lập nhưng bị ghép vào context mới tạo attack surface ngoài dự kiến.

RCE qua AVIF thuộc loại thứ hai, đồng thời minh họa rất rõ loại thứ ba: một image decoder native trở thành network attack surface vì framework đặt nó sau một API web có thể xử lý input do user kiểm soát.

Developer có thể chưa bao giờ viết “libheif” trong package.json

Đây là phần đáng lo của dependency transitivity.

Một developer audit package.json có thể thấy next.

Họ có thể thấy sharp hoặc thậm chí không thấy vì framework/runtime xử lý phần đó.

Nhưng libheif có thể tồn tại ở tầng binary/native dependency bên dưới.

Nói cách khác, inventory ở cấp package manager chưa chắc trả lời đầy đủ câu hỏi:

“Production process đang chạy code của những thư viện nào?”

Đây là lý do SBOM cho ứng dụng hiện đại không thể chỉ dừng ở npm tree.

Container base image, native library, OS package và binary được bundle cũng phải nằm trong tầm nhìn.

Next.js chọn tắt AVIF optimization thay vì chờ dependency chain cập nhật

Advisory Next.js cho biết bản vá ở 15.5.2416.3.3.

Điểm thú vị là Next.js không thể “sửa” trực tiếp logic lỗi bên trong libheif bằng một vài dòng JavaScript.

Thay vào đó, framework chọn biện pháp phòng thủ ở tầng mình kiểm soát: tạm thời vô hiệu hóa AVIF optimization cho tới khi fix upstream lan xuống toàn chuỗi.

Đây là một pattern bảo mật quan trọng.

Khi dependency bên dưới chưa chắc đã được mọi môi trường cập nhật đồng thời, lớp gọi phía trên có thể đóng attack path lại trước.

Về performance, đây là regression có chủ ý.

Về security, đó là lựa chọn đúng: mất tối ưu ảnh tốt hơn để attacker có thể chạy code trên server.

libheif 1.23.2 được phát hành như một security release

Cũng trong ngày 25/8, dự án libheif phát hành v1.23.2.

Release notes mô tả đây là security and bugfix release và khuyến cáo tất cả người dùng nâng cấp.

Ngoài lỗi liên quan scale_nearest_neighbor(), bản 1.23.2 còn sửa thêm một lỗi out-of-bounds read/write khác trong xử lý derived item và pixel plane; dự án cho biết một working code-execution exploit cũng được xác nhận cho lỗi đó.

Điều này nhấn mạnh một vấn đề khác của parser native: khi một đợt fuzzing hoặc audit tìm thấy một primitive memory corruption, thường có khả năng các bug cùng họ vẫn còn gần đó.

Security patch không chỉ là thay một dòng.

Nó thường kéo theo hardening sâu hơn về validation, kích thước plane, exception handling và giới hạn tài nguyên.

Tại sao AVIF trở thành mắt xích thú vị?

AVIF hấp dẫn với web vì nén tốt.

Next.js documentation ghi nhận AVIF có thể cho file nhỏ hơn WebP trong nhiều trường hợp, đổi lại chi phí encode cao hơn.

Nhưng một format hiện đại cũng kéo theo parser hiện đại.

AVIF không chỉ là “JPEG mới hơn”.

Nó dùng AV1 làm codec ảnh bên trong container HEIF/ISOBMFF, có nhiều metadata và cách tổ chức item linh hoạt.

Mỗi feature mới tạo thêm state và branch trong parser.

Không có gì chứng minh AVIF intrinsically nguy hiểm hơn mọi format khác. JPEG, PNG, WebP và TIFF đều từng có CVE nghiêm trọng.

Nhưng quy luật chung vẫn đúng: định dạng càng phức tạp, parser càng lớn và security testing càng quan trọng.

Web server xử lý ảnh thực ra đang chạy một compiler mini

Một cách hữu ích để nhìn image pipeline là so sánh nó với compiler.

Nó nhận một file do bên ngoài cung cấp.

Nó parse cấu trúc phức tạp.

Nó cấp phát memory theo metadata.

Nó giải mã bitstream.

Nó tạo representation nội bộ.

Nó biến đổi rồi xuất một representation mới.

Đây là lý do các dịch vụ image optimization nên được xem như untrusted-input execution engine, không phải utility vô hại.

File ảnh cần mức cảnh giác gần với PDF converter, video transcoder, archive extractor hay document parser.

RCE trong image processor nguy hiểm hơn RCE trong trình xem ảnh desktop ở điểm nào?

Vì server thường xử lý input một cách tự động.

Trong desktop attack truyền thống, attacker có thể cần nạn nhân tải và mở file.

Trong server-side media pipeline, user chỉ cần đưa file vào workflow phù hợp.

Không cần nhân viên mở file bằng tay.

Thumbnail generator, optimizer hoặc moderation service có thể tự động parse nó ngay sau upload.

Điều này biến một file upload endpoint thành bề mặt tấn công không cần user interaction theo cách CVSS mô tả.

Nhưng file extension và MIME type không phải security boundary

Nhiều ứng dụng bảo vệ upload bằng cách kiểm tra:

“Tên file có đuôi .jpg không?”

hoặc:

“Header Content-Type có phải image/* không?”

Những kiểm tra đó hữu ích cho UX, nhưng không đủ để bảo vệ parser.

Attacker kiểm soát request có thể giả MIME type.

File extension cũng có thể đổi tên.

Security boundary thực sự nằm ở decoder có validate cấu trúc nội dung chính xác hay không.

Vì vậy, upload validation không thay thế việc patch thư viện.

“Tôi dùng Next.js nhưng không dùng next/image” thì sao?

Exposure phải được đánh giá theo code path.

Advisory của Next.js nhắm tới Image Optimization API.

Nếu ứng dụng không sử dụng đường xử lý ảnh bị ảnh hưởng hoặc một hosting layer đã thay thế hoàn toàn optimizer đó, rủi ro có thể khác.

Netlify, chẳng hạn, công bố ngày 25/8 rằng các site Next.js trên nền tảng của họ không chạy code path bị lỗi vì request /_next/image được rewrite sang Netlify Image CDN ở edge.

Đây là ví dụ cho thấy package version chỉ là một phần của exposure analysis.

Hai ứng dụng cùng chạy Next.js 16.3.2 nhưng deployment architecture khác nhau có thể có attack surface khác nhau.

Managed hosting và self-hosting tạo hai mô hình patch khác nhau

Khi self-host Next.js trong container hoặc VM, đội vận hành kiểm soát dependency, image optimizer và rollout.

Đổi lại, chính đội đó chịu trách nhiệm theo dõi advisory, rebuild image và redeploy.

Managed platform có thể chặn một code path ở edge, thay optimizer bằng service riêng hoặc áp mitigation trước khi application package được cập nhật.

Điều này không có nghĩa managed hosting luôn an toàn hơn.

Nó chỉ chuyển một phần responsibility sang platform.

Câu hỏi security architecture vì thế không đơn giản là “cloud hay self-host?”.

Nó là:

ai sở hữu patch SLA cho từng lớp trong dependency chain?

Sự cố này cho thấy SBOM chỉ hữu ích nếu có thể trả lời “dependency đang chạy ở đâu”

Software Bill of Materials thường được mô tả như danh sách thành phần.

Nhưng danh sách tĩnh chưa đủ.

Một tổ chức cần biết:

  • service nào đang chạy Next.js phiên bản nào;
  • image optimizer nào được dùng;
  • sharp/libvips/libheif phiên bản nào thực sự có trong runtime;
  • container nào chưa được rebuild sau security update;
  • endpoint nào xử lý file từ user;
  • deployment nào được platform bên ngoài mitigate.

Đây là sự khác nhau giữa inventory và exploitability context.

Một dependency vulnerable nhưng không reachable không có cùng mức ưu tiên với dependency vulnerable nằm ngay sau unauthenticated upload endpoint.

Dependency scanner có thể báo “Next.js critical” nhưng root cause lại nằm sâu hơn

Khi advisory được gắn với package next, Dependabot hoặc scanner có thể chỉ ra rằng project cần nâng Next.js.

Đó là remediation đúng ở cấp ứng dụng.

Nhưng root-cause analysis cần đi sâu hơn.

Nếu không hiểu libheif là nguồn lỗi, đội security có thể bỏ sót các service khác không dùng Next.js nhưng vẫn gọi cùng image stack.

Ví dụ, một microservice xử lý media bằng sharp trực tiếp, một công cụ CLI dùng libheif hoặc một pipeline khác dựa trên cùng decoder cũng có thể cần được đánh giá.

Đây là một nguyên tắc quan trọng của vulnerability management:

đừng chỉ patch product được nêu trong headline; hãy truy tới component gốc.

Một CVE/advisory có thể lan ngang qua nhiều ecosystem

Native library thường được nhúng vào nhiều ngôn ngữ và framework.

Một lỗi ở libheif không “thuộc JavaScript”.

Nó có thể xuất hiện trong:

  • Node.js image processor;
  • CMS;
  • desktop image application;
  • Python binding;
  • mobile pipeline;
  • server media converter.

Vì vậy, cách tổ chức advisory theo package ecosystem đôi khi che giấu blast radius thật.

Framework chỉ là nơi bug trở nên dễ thấy vì nó đưa thư viện vào một workflow phổ biến.

Điều đáng chú ý: Next.js đẩy security release lên sớm một ngày

Ngày 20/8, Next.js ban đầu báo trước một security release dự kiến ngày 26/8 để xử lý một lỗ hổng Critical.

Đến ngày 25/8, lịch phát hành được đẩy sớm một ngày sau khi xuất hiện thêm một Critical vulnerability từ upstream dependency.

Kết quả là 15.5.24 và 16.3.3 cùng xử lý hai RCE nghiêm trọng.

Lỗ hổng AVIF là một.

Lỗ hổng còn lại, CVE-2026-75604/GHSA-p293-qw3h-jr36, liên quan path handling trên server Windows trong một số cấu hình Next.js.

Hai bug có root cause hoàn toàn khác nhau nhưng xuất hiện trong cùng security train.

Điều này phản ánh một thay đổi lớn của framework hiện đại: maintainer không chỉ quản lý code của mình, họ còn phải điều phối security response với hàng loạt upstream component.

Framework càng “batteries included”, blast radius càng rộng

Một framework full-stack hấp dẫn vì developer không phải chọn hàng chục package riêng.

Routing có sẵn.

SSR có sẵn.

Image optimization có sẵn.

Bundler có sẵn.

Server action, cache, middleware và deployment adapter đều được tích hợp.

Nhưng convenience tạo một mặt trái.

Mỗi feature built-in kéo thêm dependency và code path vào trusted computing base.

Developer viết ít code hơn, nhưng application không nhất thiết chạy ít code hơn.

Đôi khi nó chạy nhiều hơn, chỉ là code thuộc về framework và dependency.

“Ít dependency” cũng chưa chắc an toàn nếu dependency rất sâu

Có một niềm tin phổ biến rằng chỉ cần giảm số package npm là supply-chain risk sẽ giảm mạnh.

Điều đó đúng một phần.

Nhưng dependency count không đo được complexity của binary.

Một package duy nhất có thể bundle hoặc link với hàng chục codec native.

Một image library có thể mang theo:

  • libvips;
  • libjpeg;
  • libpng;
  • libwebp;
  • libheif;
  • codec AV1;
  • color-management library.

Do đó, security metric tốt hơn không chỉ là “bao nhiêu package?”.

Nó còn là “bao nhiêu parser nhận dữ liệu không tin cậy?”.

Parser là một trong những dependency đáng sandbox nhất

Nếu một component bắt buộc phải xử lý file do user cung cấp, defense-in-depth rất quan trọng.

Một kiến trúc tốt có thể tách image transcoding khỏi application server chính.

Ví dụ về mặt nguyên tắc:

  • chạy media processor trong service riêng;
  • giảm quyền filesystem;
  • không để service giữ database credential không cần thiết;
  • giới hạn outbound network;
  • giới hạn CPU, RAM và thời gian xử lý;
  • dùng immutable container;
  • ghi log và rebuild nhanh khi codec có advisory.

Sandbox không chữa heap overflow.

Nhưng nếu decoder bị compromise, nó làm giảm giá trị mà attacker nhận được sau exploitation.

RCE nguy hiểm nhất khi image worker có quá nhiều quyền

Giả sử một image optimizer chạy chung process với web application.

Process đó có thể đọc:

  • environment variable;
  • database credential;
  • API key;
  • session secret;
  • filesystem ứng dụng;
  • internal network.

Trong trường hợp đó, memory corruption chuyển thành rủi ro toàn hệ thống.

Nếu image worker được cô lập với quyền tối thiểu, blast radius nhỏ hơn.

Đây chính là giá trị của least privilege ở backend media pipeline.

Cập nhật package.json chưa chắc đã cập nhật binary đang chạy

Với dependency native, remediation có một bẫy vận hành.

Developer có thể sửa version trong source repository nhưng production container vẫn chứa layer cũ.

Hoặc base image của hệ điều hành vẫn có libheif cũ được link động.

Hoặc package manager cache binary artifact cũ.

Do đó, patch workflow phải kết thúc bằng rebuild và redeploy runtime thực tế, không phải chỉ merge pull request.

Google Cloud trong security bulletin về sự cố này cũng khuyến nghị các workload tự host Next.js nâng lên 15.5.24/16.3.3, đồng thời nếu dùng libheif thì theo dõi bản vá upstream/OS và rebuild container image để bảo đảm tiến trình đang chạy nhận dependency mới.

Phiên bản Next.js nào bị ảnh hưởng?

Theo advisory chính thức:

  • Next.js từ 10.0.0 tới trước 15.5.24;
  • Next.js 16.x trước 16.3.3.

Hai bản được Next.js liệt kê là patched:

  • 15.5.24 cho Maintenance LTS;
  • 16.3.3 cho Active LTS.

Nếu ứng dụng đang ở line cũ hơn không còn được support, cách an toàn không phải tự suy luận rằng “bản tôi dùng không nằm trong bảng nên chắc không sao”.

Root dependency libheif có phạm vi ảnh hưởng riêng.

Ứng dụng legacy cần audit thực tế và lên kế hoạch nâng lên line được hỗ trợ.

Việc cần làm trước tiên: nâng Next.js, không chờ exploit xuất hiện ngoài thực tế

Với các deployment bị ảnh hưởng, Next.js khuyến nghị nâng ngay lên bản patched.

Về mặt vận hành, checklist hợp lý gồm:

  1. Nâng Next.js lên 15.5.24 hoặc 16.3.3 trở lên.
  2. Rebuild và redeploy application/container thay vì chỉ cập nhật lockfile.
  3. Xác minh runtime không còn dùng libheif dễ tổn thương ở các service xử lý ảnh khác.
  4. Kiểm tra toàn bộ nơi AVIF/HEIF từ user hoặc remote source có thể đi vào decoder.
  5. Nếu không thể patch tức thời, ngắt đường optimize/decode AVIF bị ảnh hưởng thay vì chỉ lọc theo extension.
  6. Giảm quyền của image-processing worker nếu pipeline đang chạy cùng credential với application chính.

Đây là defensive remediation, không cần chờ có indicator of compromise mới làm.

Không có bằng chứng public đủ để nói đang có mass exploitation

Ở thời điểm công bố, advisory xác nhận exploitability ở cấp code execution trong thử nghiệm của researcher.

Điều đó khác với việc có chiến dịch khai thác hàng loạt trên Internet.

Hai câu cần tách:

“Bug có khả năng RCE không?” – advisory đánh giá là có.

“Attacker đang khai thác diện rộng ngoài thực tế chưa?” – cần telemetry và báo cáo incident độc lập.

Không nên hạ mức ưu tiên chỉ vì chưa thấy mass exploitation.

Nhưng cũng không nên biến một vulnerability disclosure thành tuyên bố rằng mọi server Next.js đã bị compromise.

CVSS 9,5 nói về mức độ nghiêm trọng, không nói xác suất mỗi site sẽ bị hack

CVSS của advisory Next.js đánh giá:

  • Attack Vector: Network;
  • Attack Complexity: Low;
  • Privileges Required: None;
  • User Interaction: None;
  • Confidentiality/Integrity/Availability impact: High.

Nhưng CVSS không encode đầy đủ kiến trúc của từng deployment.

Attack Requirements ở advisory được đánh dấu là Present, phản ánh việc cần có điều kiện deployment/execution để đường lỗi reachable.

Do đó, đội security cần vừa tôn trọng severity, vừa xác định reachability.

Đây là lý do “reachability analysis” ngày càng quan trọng

Một enterprise có thể có hàng nghìn CVE trong dependency inventory.

Không thể patch mọi thứ với cùng tốc độ.

Ưu tiên tốt cần trả lời:

  • library vulnerable có thực sự được load không;
  • hàm bị lỗi có reachable từ request ngoài không;
  • input có do unauthenticated user kiểm soát không;
  • process chạy quyền gì;
  • có sandbox/WAF/platform mitigation không;
  • exploit primitive là DoS, leak hay RCE.

Lỗ hổng AVIF là ví dụ textbook của một dependency có reachability rất đáng quan tâm vì image optimizer vốn được thiết kế để xử lý nội dung từ bên ngoài.

Security của framework ngày nay là security của cả graph dependency

Cách nghĩ “Next.js có an toàn không?” ngày càng ít hữu ích.

Một ứng dụng web hiện đại là graph.

React.

Next.js.

Node.js.

Native addon.

Image library.

Compression codec.

OS libc.

Container.

CDN.

Cloud runtime.

Mỗi lớp có advisory và patch cadence riêng.

Không lớp nào đứng độc lập.

Một update nhỏ ở framework có thể thực chất là mitigation cho một bug C++ rất sâu

Đây là lý do changelog security đôi khi khó đọc nếu chỉ nhìn semantic version.

Từ góc developer, nâng next từ 16.3.2 lên 16.3.3 trông như một patch release bình thường.

Nhưng behavior phía sau có thể thay đổi đáng kể, chẳng hạn tắt hẳn AVIF optimization để cắt một đường RCE trong native decoder.

Patch version nhỏ không đồng nghĩa blast radius nhỏ.

AI coding làm dependency chain còn dễ bị lãng quên hơn

Năm 2026, ngày càng nhiều project được scaffold hoặc mở rộng bằng coding agent.

Agent có thể thêm package để hoàn thành feature rất nhanh.

Developer nhìn thấy kết quả chạy được nhưng không nhất thiết hiểu toàn bộ dependency graph mới.

Điều này không có nghĩa AI coding tạo ra lỗi libheif.

Nhưng tốc độ thêm abstraction và package làm cho dependency awareness trở nên quan trọng hơn.

Security review phải theo kịp tốc độ tạo code.

Lockfile giúp reproducibility, nhưng cũng có thể khóa một dependency dễ tổn thương

Lockfile là một thành tựu quan trọng của package management.

Nó bảo đảm production dùng cùng phiên bản đã test.

Nhưng mặt còn lại là nó cố định dependency cho tới khi có người chủ động cập nhật.

Điều tốt cho reproducibility có thể làm security patch không tự lan tới project.

Do đó, lockfile cần đi cùng automated advisory monitoring.

Không thể “set and forget”.

SemVer không phải security contract của transitive dependency

Một ứng dụng có thể pin Next.js ở một patch version.

Nhưng native binary bên dưới lại được resolve khác theo platform, architecture hoặc build process.

Điều này khiến việc tái hiện security state phức tạp hơn package.json rất nhiều.

Trong incident response, câu hỏi cần là:

“binary nào đang chạy trong production process hôm nay?”

không chỉ:

“source repo khai báo version nào?”

Dependency chain cũng là lý do security patch cần disclosure coordination

Khi lỗi nằm ở libheif nhưng exploit path đi qua sharp và Next.js, công bố quá sớm ở một tầng có thể khiến downstream maintainer chưa kịp đóng đường khai thác.

Ngược lại, chờ quá lâu làm người dùng upstream không biết phải patch.

Coordinated disclosure phải đồng bộ:

  • fix upstream;
  • release dependency;
  • downstream mitigation;
  • hosting provider protection;
  • public advisory.

Việc Next.js đẩy lịch security release lên sớm cho thấy cadence này đôi khi phải thay đổi ngay khi upstream issue xuất hiện.

Tại sao việc tắt một feature có thể là security feature tốt nhất?

Engineering thường ưu tiên availability và compatibility.

Security incident đôi khi đảo thứ tự đó.

Nếu feature X mở ra code path RCE chưa thể sửa chắc chắn ngay, lựa chọn an toàn có thể là vô hiệu hóa X.

Trong trường hợp này, AVIF optimization là feature tối ưu hiệu suất/băng thông.

Nó không phải chức năng cốt lõi quyết định website có chạy được hay không.

Tắt nó tạm thời có chi phí thấp hơn rất nhiều so với giữ attack surface Critical.

Đừng biến image processing thành một phần của monolith nếu không cần

Một kiến trúc monolithic đơn giản thường hợp lý cho sản phẩm nhỏ.

Nhưng khi ứng dụng xử lý lượng lớn file không tin cậy, media processing là ứng viên tốt để tách boundary.

Không nhất thiết phải xây microservice phức tạp.

Ngay cả một worker process chạy user riêng, container riêng và credential tối thiểu cũng tạo thêm lớp bảo vệ.

Nguyên tắc là:

parser không cần quyền đọc secret thì đừng cho nó quyền đọc secret.

WAF khó chữa hoàn toàn một parser bug trong file binary

WAF rất hữu ích với pattern HTTP rõ ràng.

Nhưng một file binary hợp lệ về mặt container và chỉ kích hoạt bug sâu trong decoder khó được lọc đáng tin cậy chỉ bằng signature ở edge.

Attacker có thể thay metadata, padding và encoding trong khi vẫn giữ primitive lỗi.

Do đó, WAF là defense-in-depth chứ không thay thế library patch.

Content moderation cũng không đồng nghĩa content safety

Một hệ thống có thể quét ảnh để tìm NSFW, malware hoặc nội dung bị cấm.

Nhưng scanner muốn phân tích ảnh cũng phải decode ảnh.

Nếu scanner dùng cùng vulnerable library, nó có thể trở thành nạn nhân đầu tiên.

Đây là nghịch lý thường gặp của file security: công cụ kiểm tra file chính nó cũng là parser.

Vì thế architecture an toàn phải sandbox cả validation pipeline.

Lỗ hổng này là lời nhắc về memory safety của media stack

Phần lớn web framework hiện đại được viết bằng JavaScript/TypeScript, Rust hoặc ngôn ngữ có memory safety tốt hơn C/C++ truyền thống.

Nhưng khi cần codec nhanh, hệ thống vẫn đi xuống native layer.

JPEG decoder, video codec, font parser, PDF engine và image conversion đều chứa lượng lớn C/C++ lâu đời.

Do đó, application viết bằng ngôn ngữ memory-safe vẫn có thể có memory corruption nếu FFI gọi vào native library.

Boundary của memory safety là toàn bộ process, không phải file source chính.

Một dependency không cần độc hại để trở thành supply-chain risk

Đây có thể là bài học lớn nhất.

Supply chain security thường tập trung vào trust:

maintainer này có đáng tin không?

package có bị typosquat không?

registry có bị compromise không?

Nhưng còn một chiều khác: quality và exploitability của code hoàn toàn hợp pháp.

libheif là dự án thật.

sharp là package thật.

Next.js là framework được duy trì tích cực.

Không mắt xích nào cần bị “hack” để cả chuỗi tạo ra RCE.

Chỉ cần một bug đủ sâu.

Dependency graph càng sâu, thời gian phản ứng càng quan trọng

Khi root bug được fix ở upstream, bản vá phải lan qua nhiều lớp.

libheif phát hành.

binary package hoặc OS distro cập nhật.

sharp/libvips chain nhận bản mới.

framework nhận dependency.

ứng dụng rebuild.

container redeploy.

Nếu mỗi bước mất vài ngày, vulnerable window kéo dài.

Next.js cắt ngắn chuỗi bằng cách disable feature ở lớp framework.

Đây là giá trị của mitigation tại điểm gần attacker nhất mà maintainer có quyền kiểm soát.

Vulnerability management tương lai sẽ giống graph traversal hơn checklist

Một CVE mới xuất hiện.

Câu hỏi không chỉ là “ta có package này không?”.

Nó phải trở thành:

  1. Component gốc là gì?
  2. Nó đi vào sản phẩm qua đường dependency nào?
  3. Những service nào load component đó?
  4. Input nào có thể chạm tới code vulnerable?
  5. Process có quyền gì?
  6. Platform có mitigation nào?
  7. Bản vá cần rebuild những artifact nào?

Đây là graph problem.

Và khi software hiện đại ngày càng phụ thuộc nhiều tầng, graph đó chỉ tiếp tục lớn hơn.

“Chỉ một tấm ảnh” là headline, nhưng dependency chain mới là câu chuyện thật

AVIF RCE gây chú ý vì nó biến một hành động rất bình thường – xử lý ảnh – thành một vulnerability Critical.

Nhưng nếu chỉ nhớ rằng “AVIF từng có bug”, chúng ta sẽ bỏ lỡ phần quan trọng.

Ứng dụng Next.js không tự viết decoder.

Developer không tự gọi hàm C++ bị lỗi.

Người dùng chỉ gửi dữ liệu.

Framework làm phần còn lại.

Và chính sức mạnh của abstraction đã làm đường đi của rủi ro khó nhìn hơn.

Một framework giúp developer không phải nghĩ về codec.

Nhưng attacker vẫn có thể nghĩ về codec.

Đó là mặt tối của dependency chain:

thứ developer không cần biết để xây feature vẫn có thể là thứ họ buộc phải hiểu khi security incident xảy ra.

Điều các đội Next.js nên làm ngay

Nếu đang vận hành Next.js, hành động ngắn gọn nhất là:

  • nâng lên 15.5.24 hoặc 16.3.3 trở lên;
  • rebuild và redeploy toàn bộ runtime;
  • kiểm tra các media service khác có dùng libheif/sharp hay không;
  • xác định user-controlled AVIF/HEIF có đi vào server-side decoder ở đâu;
  • tách image processing khỏi credential và network privilege không cần thiết;
  • theo dõi advisory của cả framework lẫn native dependency.

Và quan trọng hơn, đừng coi đây là một việc “vá Next.js rồi xong”.

Ngày mai bug có thể nằm ở WebP.

Ở parser font.

Ở PDF renderer.

Ở compression library.

Framework khác, dependency khác, cùng một mô hình.

Trong phần mềm hiện đại, attack surface không chỉ là code bạn viết. Nó là toàn bộ code bạn tin tưởng đủ để chạy thay mình.

Nguồn tham khảo

Chia sẻ