Sharky CTF

SharkyCTF is a 2-day CTF featuring low medium to medium difficulty Pentest / Web / Forensics / Steganography / Crypto / Pwn / Reverse jeopardy challenges. Beginners can participate, but more advanced challenges will be there.
@sharkyctf #sharkyctf
May 9 2020

Sharky CTF

I couldn't spend the full 2 days on this one, but I did have some fun with it on a Saturday morning. The PCAPs and web challenges were a lot of fun, and I learned how to use protocol settings in Wireshark. i wish I'd had more time to dig into XXE, so that's on my training to-do list.

Welcome (Misc)

Join us on discord :-)

Just a simple welcome and flag format message. The flag was in the announcements:

basic LSB (Steganography)

I intercepted an image in the communication of 2 sharkies from a shark gang. Those sharks knew I was listening and they hid a message in this image.
Do you think you can do something about it?

We're provided with the pretty_cat.png file, so I dropped it an online LSB decoder (, which revealed the message:

Well done, you managed to use the classic LSB method. Now you know that there's nothing in the sea this fish would fear. Other fish run from bigger things. That's their instinct. But this fish doesn't run from anything. He doesn't fear. Here is your flag:

Pain in the Ass (Forensics)

It looks like someone dumped our database. Please help us know what has been leaked...

We're provided a pain-in-the-ass.pcapng file, and at the top I can see an HTML form request that includes some SQLi:


which URL decodes to:


This returned:
/login?returnurl=undefined&error=invalid input syntax for type integer: "PostgreSQL 12.2 (Debian 12.2-2.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit"

so, the version information on the database and server is leaked...

The next request:

returns the database "vulnerablenode"

and so on...using SQLi to query the database and receiving information back in the Location header field of the 302 redirects.

Eventually, we come across:
Location: /login?returnurl=undefined&error=invalid input syntax for type integer: "th3_fl4g_1s_n0t_h3r3"

which really is not the flag, but let's keep looking...

Location: /login?returnurl=undefined&error=invalid input syntax for type integer: "h3r3_1s_n0t_th3_fl4g"

is also not the flag...

We get some more of these:

Right after these, we get a long string of "No data returned from the query" redirects, and no more interesting redirects.

Looking at the original SQLi query POSTs instead, we can see they are blind brute forcing the "dev_username" and "dev_password" from "developpers" by collecting substring components of the content:


This runs through all of the standard keyboard characters in position Y, for each character position X in the password:


When it finds the next character in the password that returns a valid result (i.e. the substring at that position X is correct), it starts the cycle over again from a-z, A-Z, 0-9, and special characters.

Looking at where they stop at each offset position we can gather up the username and password that they leaked :
"dev_username" = k3vin
"dev_password" = shkCTF{4lm0st_h1dd3n_3xtr4ct10n_0e18e336adc8236a0452cd570f74542}

Well, that was a pain in the ass.

Containment Forever (Web)

Hello, welcome on "Containment Forever"! There are 2 categories of posts, only the first is available, get access to the posts on the flag category to retrieve the flag.

The home page just has a welcome message:

Welcome on Containment Forever!
This forum was created to keep you sane from the isolation of the global Confinement!

Feel free to read whatever you want from our writers.

Links at the top go to Confinement and Flag

Flag contains:

ObjectId Details Pseudo Name Date

Not indexed at the moment Not indexed at the moment N1CE_ONE Flag_first_part Sat Mar 21 2020 09:13:22 GMT+0000 (Coordinated Universal Time)

Not indexed at the moment Not indexed at the moment W3LLD0NE Flag_second_part Mon Apr 13 2020 15:50:18 GMT+0000 (Coordinated Universal Time)

Confinement contains:

ObjectId Details Pseudo Name Date

5e70da94d7b1600013655bb5 Confinement basics Thotonox Confinement basics Tue Mar 17 2020 14:11:32 GMT+0000 (Coordinated Universal Time)

5e7e4f48d7b1600013655bb9 Confined together enstro Confined together Fri Mar 27 2020 19:08:56 GMT+0000 (Coordinated Universal Time)

5e83642bd7b1600013655bba Eating anything DarkClown Eating anything Tue Mar 31 2020 15:39:23 GMT+0000 (Coordinated Universal Time)

5e8ee635d7b1600013655bbd Toilet Paper Fever EverySingleOne Toilet Paper Fever Thu Apr 09 2020 09:09:09 GMT+0000 (Coordinated Universal Time)

Each of these object IDs look big, but they differ in only a few positions:


Since we're given the datetimes for all of the flag and non-flag objects, that probably plays a role in the ID generation

And if we click on any of the object names, we're taken to


So, taking the first date, and converting it to UNIX epoch time, we get:


and converting that to hex, we get:


and that's the start of the object ID, leaving only the last character (somehwere in the HEX range) to guess. So, our first flag object ID should be:


and X = 8 gives us the first half of the flag:


Our section ObjectID should be:


and with X = F, we get the rest of the flag:


which gives us:

RattataTACACS (Network)

Silence is gold. I listen to every move on this network. And I think I got something interesting.

We're given the Chall.pcapng. There is some TACACS communication but it is encrypted. Further down, though, there's a TFTP packet that contains:

enable secret 5 $1$cPBj$qwX7keZqu6vF1UqNZxgCU0

This is a Cisco type 5 encrypted password, which is a salted MD5 hash, so tough to crack with my resources - maybe with rainbow tables and a dedicated GPU, but not with my setup and not with the sort of flag I'm expecting.

A later data packet contains:

username cisco password 7 05080F1C2243
This Cisco type 7 password cracks quickly to:

tacacs-server host key 7 0325612F2835701E1D5D3F2033
This Cisco type 7 password cracks quickly to:

This seems to be a dead-end, so far... Let's look into decrypting those TACACS packets ( it looks like we can do that in the Wireshark preferences. Selecting the TACACS+ protocol from the menu we can provide the shared secret, which we decoded as AZDNZ1234FED, and look at the decrypted TACACS packets for the flag:


Penteeeeest (Network)

You've found the website of a web designer, you know, the kind of guy that tells you "You can't hack me, and even if you do, what's the point?". It might be a good target to practice your pentesting skills!

There are two flags in this challenge, the first one is located in the home directory of the user, the second one is in the root directory.

We're provided with an OpenVPN ovpn file, that configures us as:

So, we're just looking at as hosts to attack.


finds .2 and .3 with open ports

Nmap scan report for
Host is up (0.11s latency).
Not shown: 998 closed ports
22/tcp open ssh
80/tcp open http
MAC Address: 02:42:96:8D:4D:C3 (Unknown)

Nmap scan report for
Host is up (0.11s latency).
Not shown: 999 closed ports
22/tcp open ssh
MAC Address: 02:42:83:28:A4:32 (Unknown)

Browsing to we get a personal site and blog, which a note at the bottom says is also hosted on Github. The link there takes us to Michael-SharkyMaster/website, so we've got the source code for the whole site.

The login.php shows that the answer to the password reset secret question is Badger, although that reset functionality may not be set up and may just send an email.

Checking up on that, the reset question is actually just a pull-down of 4 animals (including Badger, and the response is that the feature is disabled.

png_upload.php shows that the cookie username is Michael and the password is in the creds.txt file. The directory is set to /blog/uploads. It will attempt to limit uploads to png (checked by mime image/png) and to 2MB

Both /admin/upload.php and /admin/png_upload.php require logging in. That said, browsing to /admin/creds.txt reveals the password Badger1992.

Logging in, we're told that some feature is currently disabled. Browsing over to /admin/upload.php, we still get the message telling us we need to be logged in. There is commented code in /admin/login.php showing his intent to store the login credentials in a cookie with fields username and password. I tried setting those cookies, and reloading the page, and we're in!

Now, we have a chance to upload a PNG. I prepped a reverse TCP shell called exploit.php and attempted upload it just changing the Content-Type in the upload request, but that didn't work. Just renaming, catches on an Invalid mimetype error. Grabbing a valid PNG from the Internet, we can submit it successfully, and we can find it at

Now, we just need to get enough of a PNG file to be recognised as such, and include some PHP shell code:


except I keep getting a 500 Internal Server Error whenever I upload the file...
and if I delete too much I get an Invalid mimetype.

I tried a bunch of different PNG files as a basis, without success, and tried the 32x32_resize_bypass_use_LFI.png from PayloadAllTheThings, which did upload, but didn't execute any code.

I'm pretty sure it's doing a CRC check, on top of everything else, but I didn't have the tools or time to pull that off...

XXExternalXX (Web)

One of your customer all proud of his new platform asked you to audit it. To show him that you can get information on his server, he hid a file "flag.txt" at the server's root.

This page has a welcome message and refers us to the "news" section. There are menu links to "Home" and "Show stored data". The former takes us to this welcome page, and the latter displays a single news item at:


17/09/2019 the platform is now online, the fonctionnalities it contains will be audited by one of our society partenairs

An attempt at simple path traversal:


returns the following including a permission denied error:

Warning: file_get_contents(../../../../../etc/shadow): failed to open stream: Permission denied in /var/www/html/index.php on line 20

Warning: DOMDocument::loadXML(): Empty string supplied as input in /var/www/html/index.php on line 23

Warning: simplexml_import_dom(): Invalid Nodetype to import in /var/www/html/index.php on line 24

Notice: Trying to get property 'data' of non-object in /var/www/html/index.php on line 25

OK, well, the title does point us towards, XXE, and we know that it is fetching files (using file_get_contents) directly from the server and it parses XML documents.

Looking at ./data.xml, we can see:

  17/09/2019 the platform is now online, the fonctionnalities it contains will be audited by one of our society partenairs

Trying to see the index.php source code that runs this page, I tried /?xml=index.php, which returned the "news item":

What are you doing ? :'(

I need to spend some time with XXE, apparently.

WebFugu (Web)

A new site listing the different species of fugu fish has appeared on the net. Used by many researchers, it is nevertheless vulnerable. Find the vulnerability and exploit it to recover some of the website configuration.

This web page has a table of fish, but that's it.

Looking at the source code, we can see a comment near the end:
<!-- Loading fish list script -->
<script src="/js/script.js"></script>

Best we have a look at that script...(**my comments**)

**creating an asynchronous function called populateList**
async function populateList() {

**defines a whack load of content encoded in base64**
let b64ListContent = "PGRpdj4gPHRhYmxlIGJvcmRlcj0iMSI%2BIDx0cj4gPHRoPk5hbWU8L3RoPiA8dGg%2BRGlzY292ZXJ5IHllYXI8L3RoPiA8dGg%2BRGlzY292ZXJlciBuYW1lPC90aD4gPC90cj4gPHRyIHRoOmVhY2ggPSJmaXNoIDogJHtmaXNoZXN9Ij4gPHRkIHRoOnV0ZXh0PSIke2Zpc2gubmFtZX0iPi4uLjwvdGQ%2BIDx0ZCB0aDp1dGV4dD0iJHtmaXNoLmRpc2NvdmVyeVllYXJ9Ij4uLi48L3RkPiA8dGQgdGg6dXRleHQ9IiR7ZmlzaC5kaXNjb3ZlcmVyTmFtZX0iPi4uLjwvdGQ%2BIDwvdHI%2BIDwvdGFibGU%2BIDwvZGl2PiAgICAgIAo=";

**that decodes to:
<div> <table border="1"6Ѡ9ѠѠF66fW'V#FF`Q\ݙ\\O  XXH\ ٚ\\H]^Hٚ\ [Y_H  <td th:utext="${fish.discoveryYear}">...</td> <td th:utext="${fish.discovererName}">...</td6F&S`H ]**

**define the variable renderURL as the string /process**
let renderURL = "/process";

**define the listContent variable to await the fetch process from location.origin (which the console.log tells me is - not surprisingling) plus /render plus ?page= plus the base64 content:<b64ListContent>**
let listContent = await fetch(location.origin + renderURL + "?page=" + b64ListContent)

**Once it receives that it will take the response and convert it to text**
.then(res => res.text());

**the returned text is sent to the fishList.innerHTML**
fishList.innerHTML = listContent;

**the script listens for additional DOM content and adds that using the addFish function**
document.addEventListener('DOMContentLoaded', function() {
var popup = M.Modal.init(addFish);

**and the page runs the populateList function above**

That seems to direct us towards adding base64-encoded content that will be rendered by the page to get us the flag, and it looks like we do that by setting the addFish Variable.

The addEventListener attaches a 'DOMContentLoaded' event to the document, so that whenever the DOM Content is fully loaded, the document will use the Material Design function M.Modal.init to initialise the addFish element, which should be a dialog box or confirmation message or similar.

Currently, however, addFish is empty, and my attempts to define the variable have not been successful. I'm getting better at JavaScript, but I clearly need some more practice.


Final Score: 685
Final Rank: 295/828