YAMLwaf
Writeup for YAMLwaf (Web) - Tsuku CTF (2025) 💜
Last updated
Writeup for YAMLwaf (Web) - Tsuku CTF (2025) 💜
Last updated
YAML is awesome!!
The challenge comes with source code, server.js
is most relevant.
The app processes POST requests, parses YAML from the request body, and attempts to read a file specified in the YAML content.
WAF checks if:
'flag'
is in the raw request body → 403.
'/'
, '\\'
, '!!'
, or '<'
are present → 403.
If checks pass, it tries to access data.file
.
'flag'
Blocks direct keyword usage
Use Unicode, split string, etc.
'/'
, '\\'
Blocks path traversal
Use symlinks or local files
'!!'
, '<'
Blocks YAML tag injection
No known bypass with these blocked
The challenge description already gave a sample curl command.
Imagine if it gave the flag 😆 It doesn't since "flag" is a blocked keyword.
I tried a lot of suggestions from ChatGPT; unicode chars, splitting the flag into variables, adding newlines etc. Nothing worked and it kept going in circles.
I searched through previous CTF writeups but wasn't getting anywhere. Eventually, I swapped the ChatGPT model from o4
to o3
and found a working solution.
Here's the logic behind the payload:
1. YAML directive
%TAG !b! tag:yaml.org,2002:
declares a handle !b!
. Any tag that starts with !b!
is expanded to tag:yaml.org,2002:
.
Directive text contains none of the blocked substrings.
2. Binary tag
!b!binary
therefore becomes the official core tag tag:yaml.org,2002:binary
(!binary
).
Only a single !
is used → no !!
. No <
or /
.
3. Base‑64 value
"ZmxhZy50eHQ="
is base‑64 for the ASCII bytes flag.txt
.
The four ASCII letters f l a g never appear in the raw request, so req.body.includes("flag")
is false.
4. js‑yaml
decoding
With the default (safe) schema, !binary
is still recognised. js‑yaml
converts it to a Node Buffer
containing the bytes flag.txt
.
No dangerous function tags are involved, so the payload is accepted.
5. File read
The application later executes fs.existsSync(filePath)
and fs.readFileSync(filePath,'utf8')
. Both fs
calls accept either a string or a Buffer as the path, so the Buffer works.
From this point onward the blacklist is already satisfied and no further checks occur.
6. Response
The server reads the real flag.txt
from disk and returns its contents in the HTTP response.
Mission accomplished.
Flag: TsukuCTF25{YAML_1s_d33p!}
I checked this from J0r1an but we can't use the <
character, and it specifies versions < 4.0, while this challenge uses the latest version of js-yaml
(^4.1.0
).