NextJS - HackTricks

# NextJS

> [!TIP] Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
> Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

> Support HackTricks
> - Check the subscription plans!
> - Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
> - Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.

## General Architecture of a Next.js Application

### Typical File Structure

A standard Next.js project follows a specific file and directory structure that facilitates its features like routing, API endpoints, and static asset management. Here's a typical layout:

    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

### Core Directories and Files

- public/: Hosts static assets such as images, fonts, and other files. Files here are accessible at the root path (`/`).
- app/: Central directory for your application’s pages, layouts, components, and API routes. Embraces the App Router paradigm, enabling advanced routing features and server-client component segregation.
- app/layout.tsx: Defines the root layout for your application, wrapping around all pages and providing consistent UI elements like headers, footers, and navigation bars.
- app/page.tsx: Serves as the entry point for the root route `/`, rendering the home page.
- app/[route]/page.tsx: Handles static and dynamic routes. Each folder within `app/` represents a route segment, and `page.tsx` within those folders corresponds to the route's component.
- app/api/: Contains API routes, allowing you to create serverless functions that handle HTTP requests. These routes replace the traditional `pages/api` directory.
- app/components/: Houses reusable React components that can be utilized across different pages and layouts.
- app/styles/: Contains global CSS files and CSS Modules for component-scoped styling.
- app/utils/: Includes utility functions, helper modules, and other non-UI logic that can be shared across the application.
- .env.local: Stores environment variables specific to the local development environment. These variables are not committed to version control.
- next.config.js: Customizes Next.js behavior, including webpack configurations, environment variables, and security settings.
- tsconfig.json: Configures TypeScript settings for the project, enabling type checking and other TypeScript features.
- package.json: Manages project dependencies, scripts, and metadata.
- README.md: Provides documentation and information about the project, including setup instructions, usage guidelines, and other relevant details.
- yarn.lock / package-lock.json: Locks the project’s dependencies to specific versions, ensuring consistent installations across different environments.

## Client-Side in Next.js

### File-Based Routing in the `app` Directory

The `app` directory is the cornerstone of routing in the latest Next.js versions. It leverages the filesystem to define routes, making route management intuitive and scalable.

Handling the Root Path /

File Structure:

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

Key Files:

- `app/page.tsx`: Handles requests to the root path `/`.
- `app/layout.tsx`: Defines the layout for the application, wrapping around all pages.

Implementation:

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

Explanation:

- Route Definition: The `page.tsx` file directly under the `app` directory corresponds to the `/` route.
- Rendering: This component renders the content for the home page.
- Layout Integration: The `HomePage` component is wrapped by the `layout.tsx`, which can include headers, footers, and other common elements.

Handling Other Static Paths

Example: `/about` Route

File Structure:

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

Implementation:

    // app/about/page.tsx

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

Explanation:

- Route Definition: The `page.tsx` file inside the `about` folder corresponds to the `/about` route.
- Rendering: This component renders the content for the about page.

Dynamic Routes

Dynamic routes allow handling paths with variable segments, enabling applications to display content based on parameters like IDs, slugs, etc.

Example: `/posts/[id]` Route

File Structure:

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

Implementation:

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

Explanation:

- Dynamic Segment: `[id]` denotes a dynamic segment in the route, capturing the `id` parameter from the URL.
- Accessing Parameters: The `params` object contains the dynamic parameters, accessible within the component.
- Route Matching: Any path matching `/posts/*`, such as `/posts/1`, `/posts/abc`, etc., will be handled by this component.

Nested Routes

Next.js supports nested routing, allowing for hierarchical route structures that mirror the directory layout.

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
    └── ...

Implementation:

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

Explanation:

- Deep Nesting: The `page.tsx` file inside `dashboard/settings/profile/` corresponds to the `/dashboard/settings/profile` route.
- Hierarchy Reflection: The directory structure reflects the URL path, enhancing maintainability and clarity.

Catch-All Routes

Catch-all routes handle multiple nested segments or unknown paths, providing flexibility in route handling.

Example: `/*` Route

File Structure:

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

Implementation:

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

Explanation:

- Catch-All Segment: `[...slug]` captures all remaining path segments as an array.
- Usage: Useful for handling dynamic routing scenarios like user-generated paths, nested categories, etc.
- Route Matching: Paths like `/anything/here`, `/foo/bar/baz`, etc., are handled by this component.

### Potential Client-Side Vulnerabilities

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

Cross-Site Scripting (XSS)

XSS attacks occur when malicious scripts are injected into trusted websites. Attackers can execute scripts in users' browsers, stealing data or performing actions on behalf of the user.

Example of Vulnerable Code:

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

Why It's Vulnerable: Using `dangerouslySetInnerHTML` with untrusted input allows attackers to inject malicious scripts.

Client-Side Template Injection

Occurs when user inputs are improperly handled in templates, allowing attackers to inject and execute templates or expressions.

Example of Vulnerable Code:

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

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

Why It's Vulnerable: If `template` or `data` includes malicious content, it can lead to execution of unintended code.

Client Path Traversal

It's a vulnerability that allows attackers to manipulate client-side paths to perform unintended actions, such as 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.

Example of Vulnerable Code:

A Next.js application allows users to upload and download files. The download feature is implemented on the client side, where users can specify the file path to download.

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

#### Attack Scenario

1. Attacker's Objective: Perform a CSRF attack to delete a critical file (e.g., `admin/config.json`) by manipulating the `filePath`.
2. Exploiting CSPT:
   - Malicious Input: The attacker crafts a URL with a manipulated `filePath` such as `../deleteFile/config.json`.
   - Resulting API Call: The client-side code makes a request to `/api/files/../deleteFile/config.json`.
   - Server's Handling: If the server does not validate the `filePath`, it processes the request, potentially deleting or exposing sensitive files.
3. Executing CSRF:
   - Crafted Link: The attacker sends the victim a link or embeds a malicious script that triggers the download request with the manipulated `filePath`.
   - Outcome: The victim unknowingly executes the action, leading to unauthorized file access or deletion.

#### Why It's Vulnerable

- Lack of Input Validation: The client-side allows arbitrary `filePath` inputs, enabling path traversal.
- Trusting Client Inputs: The server-side API trusts and processes the `filePath` without sanitization.
- Potential API Actions: If the API endpoint performs state-changing actions (e.g., delete, modify files), it can be exploited via CSPT.

## Server-Side in Next.js

### Server-Side Rendering (SSR)

Pages are rendered on the server on each request, ensuring that the user receives fully rendered HTML. In this case you should create your own custom server to process the requests.

Use Cases:

- Dynamic content that changes frequently.
- SEO optimization, as search engines can crawl the fully rendered page.

Implementation:

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

Pages are pre-rendered at build time, resulting in faster load times and reduced server load.

Use Cases:

- Content that doesn't change frequently.
- Blogs, documentation, marketing pages.

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

### Serverless Functions (API Routes)

Next.js allows the creation of API endpoints as serverless functions. These functions run on-demand without the need for a dedicated server.

Use Cases:

- Handling form submissions.
- Interacting with databases.
- Processing data or integrating with third-party APIs.

Implementation:

With the introduction of the `app` directory in Next.js 13, routing and API handling have become more flexible and powerful. This modern approach aligns closely with the file-based routing system but introduces enhanced capabilities, including support for server and client components.

#### Basic Route Handler

File Structure:

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

Implementation:

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

Explanation:

- Location: API routes are placed under the `app/api/` directory.
- File Naming: Each API endpoint resides in its own folder containing a `route.js` or `route.ts` file.
- Exported Functions: Instead of a single default export, specific HTTP method functions (e.g., `GET`, `POST`) are exported.
- Response Handling: Use the `Response` constructor to return responses, allowing more control over headers and status codes.

#### How to handle other paths and methods:

Handling Specific HTTP Methods

Next.js 13+ allows you to define handlers for specific HTTP methods within the same `route.js` or `route.ts` file, promoting clearer and more organized code.

Example:

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

Explanation:

- Multiple Exports: Each HTTP method (`GET`, `PUT`, `DELETE`) has its own exported function.
- Parameters: The second argument provides access to route parameters via `params`.
- Enhanced Responses: Greater control over response objects, enabling precise header and status code management.

Catch-All and Nested Routes

Next.js 13+ supports advanced routing features like catch-all routes and nested API routes, allowing for more dynamic and scalable API structures.

Catch-All Route Example:

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

Explanation:

- Syntax: `[...]` denotes a catch-all segment, capturing all nested paths.
- Usage: Useful for APIs that need to handle varying route depths or dynamic segments.

Nested Routes Example:

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

Explanation:

- Deep Nesting: Allows for hierarchical API structures, reflecting resource relationships.
- Parameter Access: Easily access multiple route parameters via the `params` object.

Handling API routes in Next.js 12 and Earlier

## API Routes in the `pages` Directory (Next.js 12 and Earlier)

Before Next.js 13 introduced the `app` directory and enhanced routing capabilities, API routes were primarily defined within the `pages` directory. This approach is still widely used and supported in Next.js 12 and earlier versions.

#### Basic API Route

File Structure:

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

Implementation:

    javascriptCopy code// pages/api/hello.js

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

Explanation:

- Location: API routes reside under the `pages/api/` directory.
- Export: Use `export default` to define the handler function.
- Function Signature: The handler receives `req` (HTTP request) and `res` (HTTP response) objects.
- Routing: The file name (`hello.js`) maps to the endpoint `/api/hello`.

#### Dynamic API Routes

File Structure:

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

Implementation:

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

Explanation:

- 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.).

#### Handling Different HTTP Methods

While the basic API route example handles all HTTP methods within a single function, you can structure your code to handle each method explicitly for better clarity and maintainability.

Example:

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

Best Practices:

- Separation of Concerns: Clearly separate logic for different HTTP methods.
- Response Consistency: Ensure consistent response structures for ease of client-side handling.
- Error Handling: Gracefully handle unsupported methods and unexpected errors.

### CORS Configuration

Control which origins can access your API routes, mitigating Cross-Origin Resource Sharing (CORS) vulnerabilities.

Bad Configuration Example:

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

Note that CORS can also be configured in all the API routes inside the `middleware.ts` file:

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

Problem:

- `Access-Control-Allow-Origin: '*'`: Permits any website to access the API, potentially allowing malicious sites to interact with your API without restrictions.
- Wide Method Allowance: Allowing all methods can enable attackers to perform unwanted actions.

How attackers exploit it:

Attackers can craft malicious websites that make requests to your API, potentially abusing functionalities like data retrieval, data manipulation, or triggering unwanted actions on behalf of authenticated users.

CORS - Misconfigurations & Bypass

### Server code exposure in Client Side

It's can easy to use code used by the server also in code exposed and used by the client side, the best way to ensure that a file of code is never exposed in the client side is by using this import at the beginning of the file:

    import "server-only"

## Key Files and Their Roles

### `middleware.ts` / `middleware.js`

Location: Root of the project or within `src/`.

Purpose: Executes code in the server-side serverless function before a request is processed, allowing for tasks like authentication, redirects, or modifying responses.

Execution Flow:

1. Incoming Request: The middleware intercepts the request.
2. Processing: Performs operations based on the request (e.g., check authentication).
3. Response Modification: Can alter the response or pass control to the next handler.

Example Use Cases:

- Redirecting unauthenticated users.
- Adding custom headers.
- Logging requests.

Sample Configuration:

    // 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: Root of the project.

Purpose: Configures Next.js behavior, enabling or disabling features, customizing webpack configurations, setting environment variables, and configuring several security features.

Key Security Configurations:

Security Headers

Security headers enhance the security of your application by instructing browsers on how to handle content. They help mitigate various attacks like Cross-Site Scripting (XSS), Clickjacking, and MIME type sniffing:

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

Examples:

    // 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...
            ],
          },
        ]
      },
    }

Image Optimization Settings

Next.js optimizes images for performance, but misconfigurations can lead to security vulnerabilities, such as allowing untrusted sources to inject malicious content.

Bad Configuration Example:

    // next.config.js

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

Problem:

- `'*'`: Permits images to be loaded from any external source, including untrusted or malicious domains. Attackers can host images containing malicious payloads or content that misleads users.
- Another problem might be to allow a domain where anyone can upload an image (like `raw.githubusercontent.com`)

How attackers abuse it:

By injecting images from malicious sources, attackers can perform phishing attacks, display misleading information, or exploit vulnerabilities in image rendering libraries.

Environment Variables Exposure

Manage sensitive information like API keys and database credentials securely without exposing them to the client.

#### a. Exposing Sensitive Variables

Bad Configuration Example:

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

Problem:

- `SECRET_API_KEY`: Without the `NEXT_PUBLIC_` prefix, Next.js does not expose variables to the client. However, if mistakenly prefixed (e.g., `NEXT_PUBLIC_SECRET_API_KEY`), it becomes accessible on the client side.

How attackers abuse it:

If sensitive variables are exposed to the client, attackers can retrieve them by inspecting the client-side code or network requests, gaining unauthorized access to APIs, databases, or other services.

Redirects

Manage URL redirections and rewrites within your application, ensuring that users are directed appropriately without introducing open redirect vulnerabilities.

#### a. Open Redirect Vulnerability

Bad Configuration Example:

    // next.config.js

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

Problem:

- Dynamic Destination: Allows users to specify any URL, enabling open redirect attacks.
- Trusting User Input: Redirects to URLs provided by users without validation can lead to phishing, malware distribution, or credential theft.

How attackers abuse it:

Attackers can craft URLs that appear to originate from your domain but redirect users to malicious sites. For example:

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

Users trusting the original domain might unknowingly navigate to harmful websites.

Webpack Configuration

Customize Webpack configurations for your Next.js application, which can inadvertently introduce security vulnerabilities if not handled cautiously.

#### a. Exposing Sensitive Modules

Bad Configuration Example:

    // 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: Aliasing sensitive directories and allowing client-side access can leak confidential information.
- Bundling Secrets: If sensitive files are bundled for the client, their contents become accessible through source maps or inspecting the client-side code.

How attackers abuse it:

Attackers can access or reconstruct the application's directory structure, potentially finding and exploiting sensitive files or data.

### `pages/_app.js` and `pages/_document.js`

#### `pages/_app.js`

Purpose: Overrides the default App component, allowing for global state, styles, and layout components.

Use Cases:

- Injecting global CSS.
- Adding layout wrappers.
- Integrating state management libraries.

Example:

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

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

    export default MyApp

#### `pages/_document.js`

Purpose: Overrides the default Document, enabling customization of the HTML and Body tags.

Use Cases:

- Modifying the `<html>` or `<body>` tags.
- Adding meta tags or custom scripts.
- Integrating third-party fonts.

Example:

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

### Custom Server (Optional)

Purpose: While Next.js comes with a built-in server, you can create a custom server for advanced use cases like custom routing or integrating with existing backend services.

Note: Using a custom server can limit deployment options, especially on platforms like Vercel that optimize for Next.js's built-in server.

Example:

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

## Additional Architectural and Security Considerations

### Environment Variables and Configuration

Purpose: Manage sensitive information and configuration settings outside of the codebase.

Best Practices:

- Use `.env` Files: Store variables like API keys in `.env.local` (excluded from version control).
- Access Variables Securely: Use `process.env.VARIABLE_NAME` to access environment variables.
- Never Expose Secrets on the Client: Ensure that sensitive variables are only used server-side.

Example:

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

Note: To restrict variables to server-side only, omit them from the `env` object or prefix them with `NEXT_PUBLIC_` for client exposure.

### Authentication and Authorization

Approach:

- Session-Based Authentication: Use cookies to manage user sessions.
- Token-Based Authentication: Implement JWTs for stateless authentication.
- Third-Party Providers: Integrate with OAuth providers (e.g., Google, GitHub) using libraries like `next-auth`.

Security Practices:

- Secure Cookies: Set `HttpOnly`, `Secure`, and `SameSite` attributes.
- Password Hashing: Always hash passwords before storing them.
- Input Validation: Prevent injection attacks by validating and sanitizing inputs.

Example:

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

### Performance Optimization

Strategies:

- Image Optimization: Use Next.js's `next/image` component for automatic image optimization.
- Code Splitting: Leverage dynamic imports to split code and reduce initial load times.
- Caching: Implement caching strategies for API responses and static assets.
- Lazy Loading: Load components or assets only when they are needed.

Example:

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

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

> [!TIP] Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
> Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

> Support HackTricks
> - Check the subscription plans!
> - Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
> - Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.