Prototype Pollution was one of those vulnerabilities that I kept seeing in CVEs, blogs, and conference slides… but never felt like I truly understood. Reading didn't make it stick. So I decided to do what works best for me build it, break it, and learn by action.

This write-up is the result of that journey: how I learned prototype pollution by designing a deliberately vulnerable Node.js application, chaining it to Server-Side Template Injection (SSTI), and getting Remote Code Execution (RCE) step by step.

🧠 Before Building What I Thought Prototype Pollution Was

I only had a surface-level picture:

  • I knew about query parameter pollution
  • I vaguely understood "changing object properties"
  • I didn't yet understand the global inheritance impact in JavaScript

Once I slowed down and studied prototypes, everything changed.

JS objects inherit behavior and properties from their prototype chain. That means if you modify:

Object.prototype.isAdmin = true;

…every object in the app suddenly "becomes admin."

Prototype pollution is when an attacker injects keys like __proto__ or constructor.prototype into merge operations that then mutate the global prototype.

That alone was already surprising but the real power only appears when you connect the pollution to an execution sink.

🛠️ Why Node Apps Become Vulnerable

Modern Express apps take user JSON → merge it → use that object everywhere. This line is the killer:

_.merge({}, userInput);

If lodash is below v4.17.11, this merge does not block prototype keys.

So sending:

{"__proto__": {"isAdmin": true}}

…means every object in the application now returns true for .isAdmin.

🎯 Privilege escalation with 1 POST request.

🏗️ The Lab I Built to Learn (Node + Express + lodash + EJS)

To make learning realistic, I built a small application instead of a toy one-page demo.

Stack

  • Node.js + Express
  • EJS Templates
  • lodash.merge (intentionally vulnerable)
  • express-session for authentication
  • multer for uploads

Pages

/                  – Home
/auth/register     – Register
/auth/login        – Login
/dashboard         – Profile (requires login)
/messages          – Message board
/upload            – File upload
/admin             – Vulnerable admin page

Vulnerable entry: POST /update-profile → merges JSON directly → prototype pollution.

This gave me a realistic environment with sessions, pages, user data, uploads the closest feeling to "attacking an actual app."

🧪 The Full Exploit Chain (What Finally Made It Click)

The chain I learned and executed was:

Prototype Pollution → Admin Bypass → EJS Template Injection → Remote Code Execution

Step 1 Pollute Prototype

None

Result: the entire app now believes every user is admin.

Step 2 Store a Payload in Profile

None

This writes a payload into EJS-rendered HTML.

Step 3 Trigger RCE by Visiting /admin

curl http://localhost:3000/admin

The page prints the result of executing system commands because EJS renders <%= code by default.

💥 Remote Code Execution through a prototype pollution chain.

That moment seeing the RCE output printed in the admin page burned the entire concept into my brain. Reading about it never did that.

🔐 Building a Fix (The Patch)

Understanding a vuln means also understanding how to stop it. My patched version includes:

  • Sanitizing dangerous keys (__proto__, prototype, constructor)
  • Explicit admin authorization (req.session.user.isAdmin === true)
  • Rendering admin page using a safe view (admin_patched.ejs)
  • No direct ejs.render() of user content

After patching, running the exploit again simply fails no admin bypass, no RCE.

📚 What I Learned The Real Takeaways

This project taught me more than any article could:

Technical

  • Prototype pollution is invisible until another subsystem consumes polluted data
  • Exploitation usually requires chains, not isolated bugs
  • Old library versions are silently dangerous (lodash v4.17.10 was key to making it exploitable)
  • Template engines like EJS can execute arbitrary JS when unescaped

Personal

  • Building your own vulnerable environment accelerates learning 10×
  • Debugging errors is part of becoming a security engineer
  • Writing the exploit script makes the vuln feel real, not theoretical

🧭 Advice for Anyone Trying to Learn the Same Way

  • Build tiny labs not full apps
  • Always connect vulnerabilities to impact (RCE, auth bypass, data leak)
  • Record what surprised you that becomes the most valuable blog content
  • Share your work publicly people hire based on visible learning

🧰 Try the Lab Yourself

(Code & walkthroughs available on GitHub)

Repo: node-prototype-pollution-lab https://github.com/yabets143/node-prototype-pollution-lab

🏁 Final Thoughts

Prototype Pollution finally made sense to me when I chained it into something powerful. Privilege escalation. Template injection. Remote Code Execution.

If you're learning security build, break, repeat.

That's how this clicked for me and it might be how it clicks for you.