Back to Blog
high SEVERITY6 min read

How signature verification bypass happens in Node.js crypto and how to fix it

A high-severity signature verification bypass was discovered in `apps/panel/panel.js` where the `JMkey` variable was passed directly to `Buffer.from(JMkey, 'hex')` without validating its format. An attacker could supply a malformed hex string to cause silent failures or unexpected behavior in the RSA-SHA256 verification process, potentially bypassing signature checks entirely. The fix adds strict hex format validation before processing.

O
By Orbis AppSec
Published July 31, 2026Reviewed July 31, 2026

Answer Summary

This is a signature verification bypass vulnerability (CWE-347) in Node.js where `Buffer.from(JMkey, 'hex')` processes user-controlled input without format validation. Malformed hex strings can cause silent failures in the RSA-SHA256 verification flow, allowing attackers to bypass signature checks. The fix validates that `JMkey` exists, contains only valid hexadecimal characters (`/^[0-9a-fA-F]+$/`), and has an even length before processing.

Vulnerability at a Glance

cweCWE-347 (Improper Verification of Cryptographic Signature)
fixAdded regex validation to ensure JMkey is a valid, even-length hex string
riskAttackers can bypass signature verification to inject malicious data
languageNode.js (JavaScript)
root causeMissing validation of JMkey hex string before Buffer.from() conversion
vulnerabilitySignature Verification Bypass via Malformed Hex Input

Introduction

In apps/panel/panel.js, a high-severity signature verification bypass was discovered at line 65. The code handles share code verification using RSA-SHA256 signatures, but the JMkey variable—containing the signature data—was passed directly to Buffer.from(JMkey, 'hex') without any format or content validation.

This matters because signature verification is a critical security boundary. When the verification code accepts malformed input, it can fail silently or produce unexpected results, potentially allowing attackers to bypass the entire signature check and inject malicious share codes into the system.

The vulnerable pattern looked like this:

let Tex = crypto.createVerify('RSA-SHA256')
Tex.update(ccb, 'hex')
let acc = Buffer.from(JMkey, 'hex')  // JMkey used without validation!

The Vulnerability Explained

What's Actually Happening

The panel.js file implements a share code verification system that uses RSA-SHA256 digital signatures. When a user submits a share code, the system extracts a signature value (JMkey) and attempts to verify it against a public key.

The core issue is that Buffer.from(JMkey, 'hex') has permissive behavior with malformed input:

  1. Empty strings: Buffer.from('', 'hex') returns an empty buffer without error
  2. Odd-length strings: Buffer.from('abc', 'hex') silently drops the last character
  3. Invalid characters: Non-hex characters may be silently ignored or produce unexpected bytes
  4. Null/undefined: These can cause the verification to behave unpredictably

The Attack Scenario

An attacker crafts a malicious share code with a specially formatted JMkey value. Consider this exploitation path:

  1. Attacker creates a share code with JMkey set to an odd-length hex string like "abc"
  2. Buffer.from("abc", 'hex') silently produces a buffer from just "ab" (dropping the c)
  3. The truncated signature data may cause the verification to fail in unexpected ways—or worse, if the attacker can control other parameters, they might craft a collision
  4. If the error handling doesn't properly reject the request, the malicious data could be accepted

The real-world impact is severe: this is a Node.js library where vulnerabilities affect all downstream consumers. Any application using this package's share code feature could be tricked into accepting forged or tampered data.

Why Silent Failures Are Dangerous

Unlike many functions that throw errors on bad input, Buffer.from() with hex encoding is designed to be lenient. This is useful for data processing but dangerous for security-critical code:

// These all "succeed" without throwing errors:
Buffer.from('', 'hex')        // Empty buffer
Buffer.from('zz', 'hex')      // Empty buffer (invalid hex)
Buffer.from('abc', 'hex')     // Buffer with 1 byte (odd length truncated)

When signature verification code doesn't validate input, these silent behaviors can cascade into security bypasses.

The Fix

The fix adds explicit validation of JMkey before any cryptographic operations occur. Here's the before and after comparison:

Before (Vulnerable)

try {
  let Tex = crypto.createVerify('RSA-SHA256')
  Tex.update(ccb, 'hex')
  let acc = Buffer.from(JMkey, 'hex')
  // ... verification continues
}

After (Secure)

if (!JMkey || !/^[0-9a-fA-F]+$/.test(JMkey) || JMkey.length % 2 !== 0) { 
  e.reply(`[liangshi-calc] 不正确的签名,可能是数据缺失或被篡改`); 
  return false 
}
try {
  let Tex = crypto.createVerify('RSA-SHA256')
  Tex.update(ccb, 'hex')
  let acc = Buffer.from(JMkey, 'hex')
  // ... verification continues
}

What the Validation Does

The new validation line performs three critical checks:

  1. !JMkey: Rejects null, undefined, or empty strings
  2. !/^[0-9a-fA-F]+$/.test(JMkey): Ensures only valid hexadecimal characters (0-9, a-f, A-F) are present
  3. JMkey.length % 2 !== 0: Ensures even length (each byte requires exactly 2 hex characters)

This fix was applied to both verification code paths in the file (lines 68 and 132), ensuring consistent protection throughout the module.

Why This Works

By validating before processing:
- Malformed input is rejected immediately with a clear error message
- The cryptographic code only receives properly formatted data
- Silent failures become impossible because invalid input never reaches Buffer.from()

Prevention & Best Practices

1. Validate Before Cryptographic Operations

Always validate input parameters before passing them to cryptographic functions:

function isValidHexString(str) {
  return str && 
         typeof str === 'string' && 
         /^[0-9a-fA-F]+$/.test(str) && 
         str.length % 2 === 0;
}

if (!isValidHexString(signature)) {
  throw new Error('Invalid signature format');
}

2. Fail Closed, Not Open

When signature verification encounters any error or unexpected condition, the default behavior should be to reject the data:

// Good: Explicit rejection on any failure
if (!isValidSignature(data, signature)) {
  return { valid: false, error: 'Verification failed' };
}

// Bad: Implicit acceptance if verification "doesn't fail"
try {
  verify(data, signature);
  return { valid: true };
} catch (e) {
  // What if verification silently produced wrong results?
}

3. Use Type-Safe Wrappers

Create wrapper functions that enforce validation:

function safeHexToBuffer(hexString) {
  if (!hexString || !/^[0-9a-fA-F]+$/.test(hexString) || hexString.length % 2 !== 0) {
    throw new TypeError('Invalid hex string');
  }
  return Buffer.from(hexString, 'hex');
}

4. Security Testing for Cryptographic Code

Test signature verification with edge cases:
- Empty strings
- Odd-length hex strings
- Strings with invalid characters
- Very long strings
- Unicode characters that look like hex digits

Key Takeaways

  • Never trust Buffer.from() to validate hex input—it silently handles malformed data in ways that can break security assumptions
  • The JMkey signature variable required explicit regex validation (/^[0-9a-fA-F]+$/) before being used in RSA-SHA256 verification
  • Both verification code paths in panel.js (lines 68 and 132) needed the same fix—inconsistent validation creates exploitable gaps
  • Silent failures in cryptographic code are security vulnerabilities—always validate input so errors are explicit and handled properly
  • Library vulnerabilities cascade to all consumers—this Node.js package fix protects every downstream application

How Orbis AppSec Detected This

  • Source: The JMkey variable extracted from user-supplied share code data
  • Sink: Buffer.from(JMkey, 'hex') at apps/panel/panel.js:65 and line 132
  • Missing control: No validation of hex string format, length, or content before cryptographic processing
  • CWE: CWE-347 (Improper Verification of Cryptographic Signature)
  • Fix: Added regex validation (/^[0-9a-fA-F]+$/) and length check (JMkey.length % 2 !== 0) before Buffer conversion

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

This signature verification bypass demonstrates a subtle but critical security issue: cryptographic code that doesn't validate its inputs can fail in ways that benefit attackers. The Buffer.from() function's lenient handling of malformed hex strings, combined with the lack of input validation for JMkey, created a path for attackers to potentially bypass signature verification entirely.

The fix is straightforward—a single line of regex validation before processing—but its impact is significant. By ensuring JMkey contains only valid hexadecimal characters and has an even length, the code now fails explicitly on bad input rather than proceeding with corrupted data.

When writing cryptographic verification code, remember: validate everything, trust nothing, and make failures loud and explicit.

References

Frequently Asked Questions

What is a signature verification bypass?

A signature verification bypass occurs when an application fails to properly validate cryptographic signatures, allowing attackers to submit unsigned or maliciously signed data that the system accepts as legitimate.

How do you prevent signature verification bypass in Node.js?

Always validate input parameters before cryptographic operations—check that hex strings contain only valid characters (0-9, a-f, A-F), have appropriate lengths, and are not null or undefined before passing them to Buffer.from() or crypto functions.

What CWE is signature verification bypass?

CWE-347 (Improper Verification of Cryptographic Signature) covers vulnerabilities where an application does not properly verify that data has been signed by a trusted entity.

Is try-catch enough to prevent signature verification bypass?

No, try-catch alone is insufficient because Buffer.from() with 'hex' encoding may silently produce unexpected results with malformed input rather than throwing an error, allowing the verification to proceed with corrupted data.

Can static analysis detect signature verification bypass?

Yes, static analysis tools can detect patterns where user-controlled input flows into cryptographic functions without validation, flagging potential signature verification bypass vulnerabilities.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #27

Related Articles

medium

How Insufficient Password Hashing Cost Factor Happens in Node.js and How to Fix It

A bcrypt password hashing implementation in the User.js model was using a cost factor of 10, which falls below OWASP's 2024 recommendation of 12 for applications handling sensitive data. This fix upgrades the salt rounds from 10 to 12, increasing the computational work required to crack passwords by approximately 4x, significantly improving protection against brute-force attacks on this e-commerce platform.

high

How Arbitrary Code Execution via Template Imports Happens in JavaScript (lodash) and How to Fix It

A high-severity arbitrary code execution vulnerability (CVE-2026-4800) was discovered in lodash's template function, specifically in how it handles the `imports` option with untrusted input. The fix upgrades lodash from version 4.17.21 to 4.18.0 in the project's `package.json` and `yarn.lock`, eliminating the attack surface where crafted template imports could execute arbitrary code on the server.

critical

How Server-Side Request Forgery (SSRF) happens in Node.js IP address parsing and how to fix it

A critical SSRF vulnerability (CVE-2026-69192) was discovered in the ip-address npm package version 10.2.0, which could allow attackers to bypass IP address validation and access internal services. The fix upgrades the dependency to version 10.3.1, which properly handles edge cases in IP address parsing that previously allowed trust-boundary bypasses.

critical

How Sensitive Data Exposure happens in Python web applications and how to fix it

A critical sensitive data exposure vulnerability was discovered in `nodes/google_gemini.py` where the Google Gemini API key was returned in plaintext through a web endpoint. The fix masks the token in API responses, preventing credential theft from any client that queries the token endpoint. This protects downstream users of this Node.js library from unauthorized access to their Google Gemini services.

high

How Authentication Bypass happens in Next.js App Router with Turbopack and how to fix it

A critical authentication bypass vulnerability (CVE-2026-64642) was discovered in Next.js versions prior to 16.2.11, specifically affecting App Router applications using Turbopack with a single locale configuration. This vulnerability allowed attackers to bypass middleware and proxy protections, potentially gaining unauthorized access to protected routes and resources that should have been secured by authentication checks.

critical

How SQL Injection Happens in CSV-to-SQL Converters and How to Fix It

A critical SQL injection vulnerability was discovered in the `csv2sql()` function in `src/data/converter/csv.js`, where CSV data and table names were directly interpolated into SQL INSERT statements without sanitization. The fix implements input validation through identifier sanitization and proper value escaping, eliminating the attack surface while preserving legitimate functionality.