+10

Vài ghi chú về CVE-2024-34351: Server-Side Request Forgery in NextJS Server Actions

Giới thiệu

Tuần vừa rồi, các researcher của Assetnote.io có công bố bài viết liên quan đến lỗi SSRF ở Server Actions của NextJS phiên bản < 14.1.1 ở link: https://www.assetnote.io/resources/research/digging-for-ssrf-in-nextjs-apps. Bài viết đã ghi rất chi tiết về lỗ hổng, tuy nhiên với bản thân mình, còn nhiều điểm mình muốn tìm hiểu thêm nên đã setup môi trường và tái hiện lại. Mình sẽ tổng hợp một vài ghi chú của mình trong bài viết sau.

image.png

Cài đặt

Cài đặt NextJS rất dễ dàng, chúng ta có thể sử dụng công cụ create-next-app. Với môi trường nodejs đã cài đặt, chạy lệnh sau:

npx create-next-app cve-2024-34351

Screenshot 2024-05-13 091948.png

Xem trên Github NextJS thì phiên bản gần nhất tồn tại lỗ hổng này là 14.1.0 được release vào 19/01/2024:

image.png

Mặc định, create-next-app sẽ cài cho ta phiên bản mới nhất, nên để thay đổi thành phiên bản lỗi, chúng ta cần làm thêm bước sau:

cd cve-2024-34351
npm i -S next@14.1.0

Screenshot 2024-05-13 092207.png

Cuối cùng, chạy lệnh sau là ta có thể truy cập vào server dev của NextJS ở http://localhost:3000

npx next dev

Screenshot 2024-05-13 091906.png

image.png

Phân tích thêm

Đây là các điều kiện cần để có thể khai thác được lỗi này:

Prerequisites
- Next.js (<14.1.1) is running in a self-hosted* manner.
- The Next.js application makes use of Server Actions.
- The Server Action performs a redirect to a relative path which starts with a /.

Điều kiện 1

Điều kiện đầu tiên là NextJS phải được self-host. Theo document của NextJS ở https://nextjs.org/docs/pages/building-your-application/deploying , khi deploy chúng ta có thể sử dụng Node.js Server để self-host.

Với code example, chạy 2 lệnh sau để build và deploy:

npx next build

image.png

npx next start

Kiểm tra với Wappalyzer (nếu host trên AWS thì sẽ được ghi ở PaaS)

image.png

Còn trong trường hợp không phải self-host (VD: sử dụng Vercel.js) như ở https://tailwindcss.com/ hoặc https://bun.sh/, phần PaaS sẽ ghi cụ thể là Vercel

image.png

Để kiểm tra phiên bản NextJS đang được sử dụng, chúng ta có thể lấy thông tin từ cmd console: next.version

image.png

hoặc tìm trong file js, thường sẽ nằm trong file main-xxxxxxxxxxxxxxxxx.js.

VD: https://alternativeto.net/_next/static/chunks/main-fbef0a7867924acf.js

image.png

Điều kiện 2*

Trước khi đi vào điều kiện này thì cùng xem thử lỗi liên quan đến _next/image component trước.

  • Ảnh không tồn tại

image.png

Với các settings mặc định, khi ta đưa ảnh SVG vào sẽ có lỗi sau:

  • Ảnh hợp lệ, dangerouslyAllowSVG is false

2_20240513093113.png

Giờ thêm config vào:

image.png

  • Ảnh hợp lệ, dangerouslyAllowSVG is true

image.png

Mặc định sẽ không cho phép lấy các ảnh bên ngoài localhost, khi truyền vào param có dạng url sẽ có lỗi sau:

image.png

Chúng ta cần thêm config vào next.config.js như trong bài blog:

image.png

và kết quả là vẫn không XSS được vì có CSP header sau:

image.png

Kiểm tra lại trong code thì NextJS đã có cấu hình CSP mặc định này: https://github.com/vercel/next.js/blob/fedb675520e0de8f360f8d2e262acdf76b4f0491/packages/next/src/shared/lib/image-config.ts#L114

image.png

Cấu hình mặc định này bắt đầu được thêm vào từ phiên bản 12.1.0

image.png

Với những thông tin trên, ta có thể probe được phiên bản NextJS bị lỗi cũng như kiểm tra được có khai thác được lỗi với component này không.

Điều kiện 2 và 3

Quay lại với điều kiện 2 và 3, thì ở đây mình muốn trả lời các câu hỏi:

  • Chúng ta cần lấy Next-Action ở đâu?
  • Việc ứng dụng redirect về relative URL như vậy liệu có phổ biến hay không?
  • Liệu chúng ta có thể phát hiện việc redirect này chỉ từ code JS của client?

Setup routes giống như trong blog khá đơn giản:

  • Đầu tiên là tạo một thư mục mới search bên trong thư mục app và tạo file page.js trong thư mục này với nội dung sau:
'use client';

import { useFormState } from 'react-dom';
import { handleSearch } from '@/app/lib/actions';

export default function Search() {
    const initialState = { keyword: "hogehoge" };
    const [state, dispatch] = useFormState(handleSearch, initialState);
    return (
        <form action={dispatch}>
            <div>
                <h1>Search</h1>
                <input
                    type="text"
                    id="keyword"
                    placeholder={state.keyword}
                />
                <button type="submit">Search</button>
            </div>
        </form>
    );
}
  • Tạo tiếp thư mục lib và thêm file actions.js:
"use server";

import { redirect } from "next/navigation";

export const handleSearch = async (data) => {
    redirect("/login");
};

Cấu trúc thư mục như sau: image.png

Truy cập đến http://localhost:3000/search chúng ta sẽ thấy giao diện và khi thực hiện search sẽ có request:

image.png

image.png

về giá trị của header Next-Action thì đã được hardcode trong HTML khi truy cập vào UI:

image.png

Nếu giữ nguyên request và chỉ sửa Host header, chúng ta sẽ gặp lỗi:

image.png

Cụ thể hơn là:

image.png

Và nếu sửa cả 2 thì cũng vẫn lỗi:

image.png

image.png

và cách giải quyết ở đây là hãy xóa Origin header đi:

image.png

Và bắt buộc chúng ta cần lấy được giá trị đúng của Next-Action:

image.png

image.png

và rất tiếc theo như mình thấy là không có cách nào để phát hiện được quá trình redirect này từ phía client, bắt buộc phải tìm ra chức năng tương ứng. Tuy nhiên, việc code redirect đến một URL bắt đầu bằng / có vẻ khá phổ biến, ngay cả trong Tutorial của NextJS cũng có sử dụng code pattern này:

image.png

Github cũng có khoảng 6K code matching:

image.png

Tham khảo


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.