NextJS

Reading time: 22 minutes

tip

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

HackTricks 지원하기

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

일반적인 파일 구조

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

lua
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 요청을 처리하는 서버리스 함수를 생성할 수 있습니다. 이러한 경로는 전통적인 pages/api 디렉토리를 대체합니다.
  • app/components/: 다양한 페이지와 레이아웃에서 활용할 수 있는 재사용 가능한 React 구성 요소를 보관합니다.
  • app/styles/: 구성 요소 범위 스타일링을 위한 전역 CSS 파일 및 CSS 모듈을 포함합니다.
  • 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 버전에서 라우팅의 초석입니다. 파일 시스템을 활용하여 경로를 정의하여 라우트 관리를 직관적이고 확장 가능하게 만듭니다.

루트 경로 / 처리

파일 구조:

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

주요 파일:

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

구현:

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 경로

파일 구조:

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

구현:

tsx
// 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] 경로

파일 구조:

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

구현:

tsx
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 경로

파일 구조:

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

구현:

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

설명:

  • 깊은 중첩: dashboard/settings/profile/ 내부의 page.tsx 파일은 /dashboard/settings/profile 경로에 해당합니다.
  • 계층 반영: 디렉토리 구조는 URL 경로를 반영하여 유지 관리성과 명확성을 향상시킵니다.
모든 경로 처리

모든 경로 처리는 여러 중첩 세그먼트 또는 알 수 없는 경로를 처리하여 경로 처리의 유연성을 제공합니다.

예시: /* 경로

파일 구조:

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

구현:

tsx
// 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 세그먼트: [...slug]는 모든 남은 경로 세그먼트를 배열로 캡처합니다.
  • 사용법: 사용자 생성 경로, 중첩 카테고리 등과 같은 동적 라우팅 시나리오를 처리하는 데 유용합니다.
  • 경로 일치: /anything/here, /foo/bar/baz와 같은 경로는 이 구성 요소에 의해 처리됩니다.

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

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

교차 사이트 스크립팅 (XSS)

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

취약한 코드의 예:

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

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

클라이언트 측 템플릿 주입

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

취약한 코드 예시:

jsx
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에 악의적인 내용이 포함되면 의도하지 않은 코드 실행으로 이어질 수 있습니다.

클라이언트 경로 탐색

이는 공격자가 클라이언트 측 경로를 조작하여 Cross-Site Request Forgery (CSRF)와 같은 의도하지 않은 작업을 수행할 수 있게 하는 취약점입니다. 서버 측 경로 탐색이 서버의 파일 시스템을 목표로 하는 것과 달리, CSPT는 클라이언트 측 메커니즘을 악용하여 합법적인 API 요청을 악의적인 엔드포인트로 리다이렉트하는 데 중점을 둡니다.

취약한 코드의 예:

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

jsx
// 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 악용:
  • 악의적인 입력: 공격자는 filePath를 조작한 URL을 생성합니다. 예: ../deleteFile/config.json.
  • 결과 API 호출: 클라이언트 측 코드가 /api/files/../deleteFile/config.json에 요청을 보냅니다.
  • 서버의 처리: 서버가 filePath를 검증하지 않으면 요청을 처리하여 민감한 파일을 삭제하거나 노출할 수 있습니다.
  1. CSRF 실행:
  • 조작된 링크: 공격자는 피해자에게 링크를 보내거나 조작된 filePath로 다운로드 요청을 트리거하는 악성 스크립트를 삽입합니다.
  • 결과: 피해자는 알지 못하게 작업을 실행하여 무단 파일 접근 또는 삭제로 이어집니다.

왜 취약한가

  • 입력 검증 부족: 클라이언트 측에서 임의의 filePath 입력을 허용하여 경로 탐색이 가능하게 합니다.
  • 클라이언트 입력 신뢰: 서버 측 API가 filePath를 신뢰하고 정화 없이 처리합니다.
  • 잠재적 API 작업: API 엔드포인트가 상태 변경 작업(예: 파일 삭제, 수정)을 수행하는 경우 CSPT를 통해 악용될 수 있습니다.

Next.js의 서버 측

서버 측 렌더링 (SSR)

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

사용 사례:

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

구현:

jsx
// 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)

페이지는 빌드 시간에 미리 렌더링되어 더 빠른 로드 시간과 감소된 서버 부하를 제공합니다.

사용 사례:

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

구현:

jsx
// 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

Serverless Functions (API Routes)

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

사용 사례:

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

구현:

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

기본 라우트 핸들러

파일 구조:

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

구현:

javascript
// 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 파일이 포함된 자체 폴더에 위치합니다.
  • 내보낸 함수: 단일 기본 내보내기 대신 특정 HTTP 메서드 함수(예: GET, POST)가 내보내집니다.
  • 응답 처리: Response 생성자를 사용하여 응답을 반환하며, 헤더 및 상태 코드에 대한 더 많은 제어를 허용합니다.

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

특정 HTTP 메서드 처리

Next.js 13+는 동일한 route.js 또는 route.ts 파일 내에서 특정 HTTP 메서드에 대한 핸들러를 정의할 수 있도록 하여 더 명확하고 조직적인 코드를 촉진합니다.

예:

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

설명:

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

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

Catch-All 경로 예:

javascript
// 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에 유용합니다.

중첩 경로 예:

javascript
// 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 경로 처리

pages 디렉토리의 API 경로 (Next.js 12 및 이전 버전)

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

기본 API 경로

파일 구조:

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

구현:

javascript
javascriptCopy code// pages/api/hello.js

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

설명:

  • 위치: API 경로는 pages/api/ 디렉토리 아래에 있습니다.
  • 내보내기: 핸들러 함수를 정의하려면 export default를 사용합니다.
  • 함수 시그니처: 핸들러는 req (HTTP 요청) 및 res (HTTP 응답) 객체를 받습니다.
  • 라우팅: 파일 이름(hello.js)은 엔드포인트 /api/hello에 매핑됩니다.

동적 API 경로

파일 구조:

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

구현:

javascript
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 메서드를 처리하지만, 코드 구조를 각 메서드를 명시적으로 처리하도록 구성하여 더 나은 명확성과 유지 관리를 할 수 있습니다.

예제:

javascript
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 경로에 접근할 수 있는지 제어하여 교차 출처 리소스 공유(CORS) 취약점을 완화합니다.

잘못된 구성 예:

javascript
// 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는 또한 middleware.ts 파일 내의 모든 API 경로에서 구성할 수 있습니다:

javascript
// 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를 사용하는 것입니다:

js
import "server-only"

주요 파일 및 역할

middleware.ts / middleware.js

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

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

실행 흐름:

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

예시 사용 사례:

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

샘플 구성:

typescript
// 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

위치: 프로젝트의 루트.

목적: Next.js 동작을 구성하고, 기능을 활성화 또는 비활성화하며, webpack 구성을 사용자 정의하고, 환경 변수를 설정하고, 여러 보안 기능을 구성합니다.

주요 보안 구성:

보안 헤더

보안 헤더는 브라우저에 콘텐츠를 처리하는 방법을 지시하여 애플리케이션의 보안을 강화합니다. 이들은 교차 사이트 스크립팅(XSS), 클릭재킹, MIME 타입 스니핑과 같은 다양한 공격을 완화하는 데 도움을 줍니다:

  • 콘텐츠 보안 정책 (CSP)
  • X-Frame-Options
  • X-Content-Type-Options
  • 엄격한 전송 보안 (HSTS)
  • 리퍼러 정책

예시:

javascript
// 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는 성능을 위해 이미지를 최적화하지만, 잘못된 구성은 신뢰할 수 없는 소스가 악성 콘텐츠를 주입할 수 있는 보안 취약점으로 이어질 수 있습니다.

잘못된 구성 예:

javascript
// next.config.js

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

문제:

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

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

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

환경 변수 노출

API 키 및 데이터베이스 자격 증명과 같은 민감한 정보를 클라이언트에 노출하지 않고 안전하게 관리합니다.

a. 민감한 변수 노출

잘못된 구성 예:

javascript
// 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), 클라이언트 측에서 접근할 수 있게 됩니다.

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

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

리다이렉트

응용 프로그램 내에서 URL 리다이렉션 및 재작성 관리를 통해 사용자가 적절하게 안내되도록 하여 열린 리다이렉트 취약점이 발생하지 않도록 합니다.

a. 열린 리다이렉트 취약점

잘못된 구성 예:

javascript
// next.config.js

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

문제:

  • 동적 목적지: 사용자가 임의의 URL을 지정할 수 있어 오픈 리디렉션 공격을 가능하게 합니다.
  • 사용자 입력 신뢰: 검증 없이 사용자가 제공한 URL로 리디렉션하면 피싱, 악성 소프트웨어 배포 또는 자격 증명 도용으로 이어질 수 있습니다.

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

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

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

사용자가 원래 도메인을 신뢰하면 의도치 않게 해로운 웹사이트로 이동할 수 있습니다.

Webpack 구성

Next.js 애플리케이션에 대한 Webpack 구성을 사용자 정의하십시오. 주의하지 않으면 보안 취약점을 초래할 수 있습니다.

a. 민감한 모듈 노출

잘못된 구성 예:

javascript
// next.config.js

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

문제:

  • 민감한 경로 노출: 민감한 디렉토리를 별칭 처리하고 클라이언트 측 접근을 허용하면 기밀 정보가 유출될 수 있습니다.
  • 비밀 번들링: 민감한 파일이 클라이언트를 위해 번들링되면, 그 내용이 소스 맵이나 클라이언트 측 코드를 검사하여 접근할 수 있게 됩니다.

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

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

pages/_app.jspages/_document.js

pages/_app.js

목적: 기본 App 구성 요소를 재정의하여 전역 상태, 스타일 및 레이아웃 구성 요소를 허용합니다.

사용 사례:

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

예시:

jsx
// 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> 태그 수정.
  • 메타 태그 또는 사용자 정의 스크립트 추가.
  • 서드파티 글꼴 통합.

예시:

jsx
// 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과 같은 플랫폼에서 배포 옵션이 제한될 수 있습니다.

예시:

javascript
// 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 키와 같은 변수를 .env.local에 저장 (버전 관리에서 제외).
  • 변수를 안전하게 접근: process.env.VARIABLE_NAME을 사용하여 환경 변수에 접근.
  • 클라이언트에 비밀 노출 금지: 민감한 변수는 서버 측에서만 사용되도록 보장.

예시:

javascript
// 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_로 접두사를 붙이십시오.

인증 및 권한 부여

접근 방식:

  • 세션 기반 인증: 쿠키를 사용하여 사용자 세션을 관리합니다.
  • 토큰 기반 인증: 상태 비저장 인증을 위해 JWT를 구현합니다.
  • 타사 제공업체: next-auth와 같은 라이브러리를 사용하여 OAuth 제공업체(예: Google, GitHub)와 통합합니다.

보안 관행:

  • 보안 쿠키: HttpOnly, Secure, 및 SameSite 속성을 설정합니다.
  • 비밀번호 해싱: 비밀번호를 저장하기 전에 항상 해시합니다.
  • 입력 검증: 입력을 검증하고 정리하여 주입 공격을 방지합니다.

예시:

javascript
// 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 컴포넌트를 사용하여 자동 이미지 최적화를 수행합니다.
  • 코드 분할: 동적 임포트를 활용하여 코드를 분할하고 초기 로드 시간을 줄입니다.
  • 캐싱: API 응답 및 정적 자산에 대한 캐싱 전략을 구현합니다.
  • 지연 로딩: 필요할 때만 컴포넌트나 자산을 로드합니다.

예시:

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

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

tip

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

HackTricks 지원하기