(중요) Nextjs 미들웨어 보안취약점 대응하기

dante Yoon·2025년 3월 29일
6

NextJS

목록 보기
9/9

CVE-2025-29927

Next.js 미들웨어 취약점인 CVE-2025-29927 알아보기
CVE-2025-29927은 Next.js 미들웨어 권한 인증을 우회할 수 있는 보안 취약점입니다. Next.js는 미들웨어 실행에서 무한 재귀를 방지하기 위해 x-midd![](https://velog.velcdn.com/images/jay/post/6192fd7b-70ad-42d9-b6d7-efc2f21b1934/image.png) leware-subrequest를 사용합니다. 해당 키값을 프록시 도구를 사용해 사용자가 임의로 주입하면 미들웨어 인증을 무효화 하여 서버의 주요 리소스에 접근이 가능할 수 있고 민감 데이터에 대한 무단 액세스가 호용될 수 있습니다.

미들웨어와 취약점

외부에서 들어오는 요청을 처리할 때 매번 중복적으로 각 페이지에서 구현해야 하는 로직을 별도 모듈로 재사용하기 위해 레일즈나 라라벨과 같은 다른 언어의 프레임워크에서도 미들웨어 개념을 사용합니다. Next.js에서의 미들웨어는 엄밀하게 이야기하면 다른 프레임워크의 미들웨어와 약간 다릅니다. Next.js의 미들웨어는 간단한 체크를 통해 서버 컴포넌트 코드가 실행되기 전에 다른 페이지로 라우팅을 수행하기 위한 공간이고 앱에서 유일하게 인증 역할을 수행하는 기능으로는 적절하지 않기 때문입니다.
Next.js팀은 이번 취약점 대응이후 공식문서에서 미들웨어의 사용 사례중 인증과 관련된 내용을 삭제했습니다.

미들웨어는 다음과 같이 간단한 쿠키 값 존재를 확인하는 용도로 사용하는 것은 괜찮으나 앱 전체에서 인증을 담당하는 서버의 유일한 인증 컴포넌트 역할을 수행하기에는 적절하지 않습니다.

export function middleware(request) {
  const sessionCookie = request.cookies.get('session');
  if (!sessionCookie) return Response.redirect('/login');
}

내부적으로 데이터베이스를 참조한다거나 쿠키의 유효성 검사를 확인하는 로직 수행은 서버 컴포넌트에서 추가로 수행해야 합니다.


// lib/auth.ts
export async function validateSession(token?: string) {
  if (!token) return null
  
  const user = await db.user.findUnique({
    where: { sessionToken: token },
    select: { id: true, name: true, role: true }
  })

  return user
}



// app/dashboard/page.tsx
import { cookies } from 'next/headers'
import { redirect } from 'next/navigation'
import { validateSession } from '@/lib/auth'

export default async function DashboardPage() {
  // Get cookies from headers
  const cookieStore = cookies()
  const sessionCookie = cookieStore.get('session')?.value

  // Validate session on server
  const user = await validateSession(sessionCookie)

  // Redirect if validation fails
  if (!user) {
    redirect('/login')
  }

  // Render protected content
  return (
    <div>
      <h1>Welcome, {user.name}</h1>
      {/* Protected content */}
    </div>
  )
}

x-middleware-subrequest

x-middleware-subrequest 헤더 값은 서버 외부에서 주입되는 것은 의도되지 않은 사용방법이며 Next.js 내부적으로 미들웨어 간의 무한 재귀 호출을 수행하는 것을 막기 위해 사용됩니다. Next.js 내부적으로는 다음과 같은 코드가 작성되어 있는데 헤더값을 검색 콜론으로 나누어 리스트를 생성한 후 해당 길이를 MAX_RECURSION_DEPTH와 비교하여 카운트가 임계 값을 충족하거나 초과하는 경우 미들웨어를 우회하는는 로직을 수행합니다.

export const run = withTaggedErrors(async function runWithTaggedErrors(params) {
  const runtime = await getRuntimeContext(params)
  const subreq = params.request.headers[`x-middleware-subrequest`]
  const subrequests = typeof subreq === 'string' ? subreq.split(':') : []

  const MAX_RECURSION_DEPTH = 5
  const depth = subrequests.reduce(
    (acc, curr) => (curr === params.name ? acc + 1 : acc),
    0
  )

  if (depth >= MAX_RECURSION_DEPTH) {
    return {
      waitUntil: Promise.resolve(),
      response: new runtime.context.Response(null, {
        headers: {
          'x-middleware-next': '1',
        },
      }),
    }
  }

데모

데모 코드를 공개합니다: https://github.com/dante01yoon/CVE-2025-29927

시나리오는 다음과 같습니다. /admin 경로 페이지는 auth 쿠키를 포함하고 있지 않는 요청에게는 보여지면 안되어 미들웨어에서 쿠키 존재 여부를 판단합니다.

다음은 미들웨어 코드입니다.

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // Check if the request is for the admin page
  if (request.nextUrl.pathname.startsWith('/admin')) {
    // Check for the authentication cookie
    const isAuthenticated = request.cookies.has('auth');
    
    if (!isAuthenticated) {
      // Redirect to login page if not authenticated
      return NextResponse.redirect(new URL('/login', request.url));
    }
  }

  return NextResponse.next();
}

// Define which paths this middleware should run on
export const config = {
  matcher: ['/admin/:path*'],
}; 

취약점이 존재하는 15.2.2 버전을 사용해 curl로 특정 헤더값을 붙여 /admin 페이지를 요청하면 미들웨어를 생략하고 페이지를 응답하는 것을 볼 수 있습니다.

x-middleware-subrequest: src/middleware:src/middleware:src/middleware:src/middleware:src/middleware

취약점 패치가 적용된 15.2.3 버전부터는 동일한 요청에 /login 페이지로 리다이렉트 되는 것을 확인할 수 있습니다.

보안 취약점이 존재하는 Next.js 버전

대부분의 이전 버전들이 취약점에 노출되어 있기 때문에 취약점 대응을 위해 빨리 Next.js 버전을 업데이트 해야 합니다. 다음은 취약점에 노출된 버전입니다.

  • Next.js 11.1.4 부터 13.5.6 까지

  • Next.js 14.2.25 버전 이전의 14.x 버전

  • Next.js 15.2.3 버전 이전의 15.x 버전

취약점 대상이 아닌 어플리케이션

미들웨어를 사용하지 않거나 아래 서비스를 사용해 Vercel, Netlify를 이용해 호스팅하고 있는 어플리케이션이면 위 취약점의 대상이 아닙니다. 하지만 많은 회사에서는 셀프호스팅을 통해 서비스를 제공하고 있을 것이기에 버전 업그레이드를 통해 취약점 패치를 진행해야 합니다.

버전 업그레이드 대신에 선택할 수 있는 대안

호환성 문제로 버전 업데이트가 불가한 경우가 있을 수 있습니다.

  • 셀프 호스트로 Next.js 운영시 Next.js 서버 앞단에 Nginx와 같은 웹서버를 사용해 x-middleware-subrequest 헤더가 포함된 요청은 에러 응답으로 내려주기
server {
    listen 80;
    server_name yourdomain.com;

    location / {
        # Block requests containing the x-middleware-subrequest header
        if ($http_x_middleware_subrequest != "") {
            return 403;
        }
        
        # Proxy the request to your Next.js application
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}
  • 서버 컴포넌트에서도 인증 로직을 수행하기
  • cloud flare 사용시 WAF 규칙을 옵트인하기
  • vercel로 호스팅을 대신하기(???)
profile
성장을 향한 작은 몸부림의 흔적들

1개의 댓글

comment-user-thumbnail
2025년 3월 30일
답글 달기