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/: 애플리케이션의 페이지, 레이아웃, 컴포넌트 및 API 라우트를 위한 중심 디렉터리입니다. 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>
);
}

설명:

  • 라우트 정의: page.tsx 파일은 app 디렉터리 바로 아래에 위치하며 / 라우트에 해당합니다.
  • 렌더링: 이 컴포넌트는 홈 페이지의 콘텐츠를 렌더링합니다.
  • 레이아웃 통합: 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>
)
}

설명:

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

동적 라우트를 사용하면 가변 세그먼트를 가진 경로를 처리할 수 있어, ID나 slug 같은 파라미터에 따라 애플리케이션이 콘텐츠를 표시할 수 있습니다.

예시: /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>
);
}

설명:

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

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

Example: /dashboard/settings/profile Route

File Structure:

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>
);
}

설명:

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

Catch-All 라우트는 여러 중첩 세그먼트나 알 수 없는 경로를 처리하여 라우트 처리에 유연성을 제공합니다.

예시: /* 라우트

파일 구조:

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 등)는 이 컴포넌트에 의해 처리됩니다.

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

Next.js가 안전한 기반을 제공하지만, 부적절한 코딩 관행은 취약점을 초래할 수 있습니다. 주요 클라이언트 측 취약점은 다음과 같습니다:

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)와 같은 의도하지 않은 동작을 수행하게 할 수 있습니다. 서버의 파일시스템을 대상으로 하는 server-side path traversal과 달리, CSPT는 정당한 API 요청을 악의적인 엔드포인트로 우회시키기 위해 클라이언트 측 메커니즘을 악용하는 데 중점을 둡니다.

취약한 코드 예:

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 악용:
  • Malicious Input: 공격자는 ../deleteFile/config.json와 같은 조작된 filePath를 포함한 URL을 제작.
  • Resulting API Call: 클라이언트 측 코드가 /api/files/../deleteFile/config.json로 요청을 보냄.
  • 서버 처리: 서버가 filePath를 검증하지 않으면 요청을 처리하여 민감한 파일을 삭제하거나 노출할 수 있음.
  1. CSRF 실행:
  • Crafted Link: 공격자가 피해자에게 링크를 보내거나 조작된 스크립트를 삽입하여 조작된 filePath로 다운로드 요청을 발생시킴.
  • Outcome: 피해자가 의도치 않게 해당 동작을 실행하여 권한 없는 파일 접근 또는 삭제가 발생.

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

Static Site Generation (SSG)

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

Use Cases:

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

Implementation:

// 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 엔드포인트를 서버리스 함수로 생성할 수 있습니다. 이러한 함수들은 전용 서버 없이도 필요 시(on-demand) 실행됩니다.

사용 사례:

  • 폼 제출 처리.
  • 데이터베이스와의 상호작용.
  • 데이터 처리 또는 서드파티 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 파일을 포함하는 별도의 폴더에 위치합니다.
  • 내보낸 함수: 단일 default 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" },
})
}

설명:

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

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

Catch-All Route 예시:

// 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" },
}
)
}

설명:

  • 깊은 중첩: 리소스 관계를 반영하는 계층형 API 구조를 허용합니다.
  • 매개변수 접근: params 객체를 통해 여러 라우트 매개변수에 쉽게 접근할 수 있습니다.
Next.js 12 및 이전에서의 API 라우트 처리

API Routes in the pages Directory (Next.js 12 및 이전)

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

기본 API 라우트

파일 구조:

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 라우트는 pages/api/ 디렉터리 아래에 위치합니다.
  • Export: 핸들러 함수를 정의할 때 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`);
}
}

설명:

  • 동적 세그먼트: 대괄호([id].js)는 동적 라우트 세그먼트를 나타냅니다.
  • 파라미터 접근: req.query.id를 사용하여 동적 매개변수에 접근하세요.
  • 메서드 처리: 조건문을 사용해 서로 다른 HTTP 메서드(GET, PUT, DELETE 등)를 처리하세요.

서로 다른 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`);
}
}

모범 사례:

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

CORS 구성

어떤 출처가 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와 상호작용할 수 있습니다.
  • 광범위한 메서드 허용: 모든 메서드를 허용하면 공격자가 원치 않는 동작을 수행할 수 있습니다.

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

공격자는 악의적인 웹사이트를 만들어 API에 요청을 보내고, 데이터 조회나 조작과 같은 기능을 남용하거나 인증된 사용자를 대신해 원치 않는 동작을 발생시킬 수 있습니다.

CORS - Misconfigurations & Bypass

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

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

import "server-only"

핵심 파일과 역할

middleware.ts / middleware.js

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

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

실행 흐름:

  1. 들어오는 요청: middleware가 요청을 가로챕니다.
  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*"],
}

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
},
}

문제:

  • '*': 신뢰할 수 없거나 악의적인 도메인을 포함해 모든 외부 소스에서 이미지를 로드하도록 허용합니다. 공격자는 악성 페이로드를 포함한 이미지나 사용자를 오도하는 콘텐츠를 호스팅할 수 있습니다.
  • 또 다른 문제는 누구나 이미지를 업로드할 수 있는 도메인(예: raw.githubusercontent.com)을 허용하는 것입니다.

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

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

환경 변수 노출

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

a. 민감한 변수 노출

잘못된 구성 예시:

// next.config.js

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

문제:

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

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

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

리디렉션

애플리케이션 내에서 URL 리디렉션과 재작성(rewrites)을 관리하여 사용자가 적절히 이동되도록 하면서 open redirect vulnerabilities를 유발하지 않도록 하세요.

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 attacks를 가능하게 합니다.
  • 사용자 입력 신뢰 (Trusting User Input): 사용자가 제공한 URL로 검증 없이 리디렉션하면 phishing, malware distribution 또는 credential theft로 이어질 수 있습니다.

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

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

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

원래 도메인을 신뢰하는 사용자는 자신도 모르게 악성 웹사이트로 이동할 수 있습니다.

Webpack 구성

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
},
}

Problem:

  • Exposing Sensitive Paths: 민감한 디렉터리를 alias 처리하고 클라이언트 측 접근을 허용하면 기밀 정보가 leak될 수 있습니다.
  • Bundling Secrets: 민감한 파일이 클라이언트용으로 번들되면, 그 내용은 source maps를 통해 또는 클라이언트 측 코드를 검사하여 접근 가능해집니다.

How attackers abuse it:

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

pages/_app.jspages/_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 파일 사용: API keys와 같은 변수를 .env.local에 저장(버전 관리에서 제외).
  • 변수에 안전하게 접근: 환경 변수에 접근할 때 process.env.VARIABLE_NAME 사용.
  • 클라이언트에 비밀 노출 금지: 민감한 변수는 반드시 서버 측에서만 사용되도록 보장.

예시:

// 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_로 접두어를 붙이세요.

인증 및 권한 부여

접근 방식:

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

보안 관행:

  • 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 컴포넌트를 사용하세요.
  • 코드 분할: dynamic imports를 활용해 코드를 분할하고 초기 로드 시간을 줄이세요.
  • 캐싱: 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 열거 (소스 맵을 통해 해시에서 함수 이름으로)

최신 Next.js는 서버에서 실행되지만 클라이언트에서 호출되는 “Server Actions”를 사용합니다. 프로덕션 환경에서는 이러한 호출이 불투명합니다: 모든 POSTs는 공통 엔드포인트로 들어가며 Next-Action 헤더에 전송되는 빌드 특화 해시로 구분됩니다. 예:

POST /
Next-Action: a9f8e2b4c7d1...

productionBrowserSourceMaps가 활성화되어 있으면, minified JS chunks에는 createServerReference(...) 호출이 포함되어 있어 action hash와 원래 함수 이름 사이의 매핑을 복구할 수 있을 만큼의 구조(및 관련 source maps)를 leak 합니다. 이를 통해 Next-Action에서 관찰된 해시들을 deleteUserAccount()exportFinancialData() 같은 구체적 대상으로 변환할 수 있습니다.

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

다운로드한 JS chunks에서 createServerReference를 검색하여 hash와 함수/소스 심볼을 추출합니다. 유용한 패턴 두 가지:

# 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: server action 해시(16진수 40자 이상)
  • 그룹 2: source map이 있을 때 원본 함수로 해석할 수 있는 symbol 또는 경로

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:
    • Name-driven triage (e.g., transferFunds, exportFinancialData).
    • Track coverage across builds by function name (hashes rotate across builds).

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

프록시에서 관찰된 유효한 POST를 템플릿으로 사용하고 Next-Action 값을 변경하여 다른 발견된 액션을 타깃으로 삼으세요:

# Before
Next-Action: a9f8e2b4c7d1

# After
Next-Action: b7e3f9a2d8c5

Repeater에서 재생하여 접근할 수 없는 액션들의 authorization, input validation 및 business logic을 테스트하세요.

Burp 자동화

  • NextjsServerActionAnalyzer (Burp extension)는 위 작업을 Burp에서 자동화합니다:
  • 프록시 기록을 스캔하여 JS chunks를 찾아 createServerReference(...) 항목을 추출하고, 사용 가능하면 source maps를 파싱합니다.
  • 검색 가능한 hash↔function-name 사전을 유지하고 function name 기준으로 빌드 간 중복을 제거합니다.
  • 유효한 템플릿 POST를 찾아 대상 action의 hash로 교체된 상태의 전송 준비된 Repeater 탭을 엽니다.
  • Repo: https://github.com/Adversis/NextjsServerActionAnalyzer

주의사항 및 제한

  • 번들/source maps에서 이름을 복구하려면 production 환경에서 productionBrowserSourceMaps를 활성화해야 합니다.
  • Function-name 공개 자체는 취약점이 아닙니다; 이를 활용해 탐색을 안내하고 각 action의 authorization을 테스트하세요.

참고 자료

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 지원하기