← Back to Writeups
CUSTOMMediumWeb

Hard 0

XESXOR9/10/20262 min read
#web#custom#medium#filter_bypass#script_tag_escape#semicolonless_html_entities#svg_xml_entity_decoding

Hard 0

Platform: spbctf | Category: Web | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2026-03-15 | Status: Solved Techniques: filter_bypass, script_tag_escape, semicolonless_html_entities, svg_xml_entity_decoding

Summary

XSS challenge with regex filter blocking () and ; characters. Bypass using SVG script context with semicolon-less HTML entities (&#40 instead of () to call prompt('sibears') with exact string argument.

Hard 0 - spbctf (SiBears CTF)

Description

Attack our bot to get the flag! Solve the SiBears XSS level and submit your payload. The challenge is hosted on the SiBears XSS School platform. The goal is to craft an XSS payload that calls prompt("sibears") with the exact string argument, bypassing a filter function.

Analysis

The escape function filters dangerous characters using regex:

javascript
function escape(s) {
  s = s.replace(/[\(\)\+\-\[\]\.\={}\;\:]/g, '_');
  return '<script> var foo = "' + s + '"; </scr' + 'ipt>';  
}

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

Filtered characters: ( ) + - [ ] . = { } ; :

Key observations:

  1. Input is placed inside a double-quoted JS string within <script> tag
  2. The sandbox requires prompt("sibears") with exact string (strict equality ===)
  3. Template literals won't work because they pass arrays, not strings
  4. HTML entities with semicolons fail because ; is filtered

Critical insight: HTML numeric entities work WITHOUT trailing semicolons (&#40 instead of &#40;), and SVG <script> tags decode HTML entities before JavaScript execution. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Solution

Winning Payload

</script><svg><script>prompt&#40"sibears"&#41</script></svg>

Step-by-step breakdown:

  1. </script> - Closes the original script tag, escaping the JS string context
  2. <svg><script> - Opens SVG context where XML entity decoding applies to script content
  3. &#40 and &#41 - Semicolon-less HTML entities for ( and ) - bypass the filter completely
  4. Browser decodes to prompt("sibears") - exact string argument passes strict equality check ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Why filter bypass works:

  • No literal ( or ) characters in payload
  • No semicolons in the HTML entities
  • Only safe characters: &, #, digits, letters, quotes

Failed attempts:

  1. Template literals: prompt\sibears`passes["sibears"]array, fails=== "sibears"` check
  2. Entities with semicolons: &#40; becomes &#40_ due to ; being filtered
javascript
// Final decoded result in browser:
prompt("sibears")  // Returns flag

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