Lucene search

K
hackeroneZer0ttlH1:895772
HistoryJun 11, 2020 - 2:35 a.m.

h1-ctf: [h1-2006 2020] Write up for H1-2006 CTF

2020-06-1102:35:28
zer0ttl
hackerone.com
147

I huffed and puffed my way up a flight of stairs into a dimly lit, dusty room, looking for Sherlock. As I made way through scattered books, I exclaimed, "Sherlock, wake up! It’s that time of the year. h1-ctf, a chance to get an invitation to hackerone’s live hacking event. “zer0ttl, of course! Your excitement is inversely proportional to the number of times you were invited to the event,” scoffed Sherlock sarcastically.

Though what he said was true, I assured that this time it was different. I had been learning and getting better over the last year. I was more confident that this time I will be able to complete it.

I continued, “So, I came across this tweet saying that h1-ctf is live. I doubted if I was good enough to try for it. Then Hacker0x01 tweeted that they are extending the CTF through June 10, 2020. It was June 5 already.”

Nodding his head in pity, Sherlock said, “You think now is the time to give this thing a try? Good job zer0ttl! Better luck next time! People have been at this for days now and what makes you think you can get this?”

I was adamant, “Come on. It’s the weekend amidst a pandemic. It’s not like you have other cases to handle. Your ‘art of deduction’ and my ‘technical’ skills can help us pull this off together.”

And then I heard the golden words! “Ok then, zer0ttl, you asked for it. Here goes nothing! What do we have till now?”

Now, that I finally had Sherlock onboard, I put out all my cards on the table. “Awesome. We make a great team. So, this guy @martenmickos needs to approve May bug bounty payments but he has lost his login details for BountyPay. We have to help him retrieve the creds.”

A visibly disgruntled, Sherlock said, “Of course, we have to do the job for a clumsy person.”

{F863050}

“The link in the tweet https://hackerone.com/h1-ctf gives us our in-scope targets. It seems to be a wild card domain. I always get excited about wildcards,” I almost shrieked.

{F863051}

Starting to get intrigued, Sherlock shared, “You know what to do zer0ttl. The usual suspects. Start with the asset discovery. What do you use for that?”. “I use amass. It performs mapping of attack surfaces and external asset discovery using open source information gathering.”

I started the enumeration using amass. While waiting for the results I couldn’t help but notice the low wifi reception. “Mrs Hudson needs to upgrade wifi here. It is painfully slow.”

Almost like it made no difference, Sherlock said, “Oh yeah, regarding that. You see those boxes in that corner. Those are the new access points. They have been there for a month now. I am just not motivated enough to install them.”

I glanced at the direction Sherlock was pointing at. I could not believe my eyes. This man has a stock of new access points waiting to be installed. I was about to lash out at Sherlock, but my attention was diverted by the output on my laptop screen. The amass tool returned some results.

{F863054}

“I got some domains back. Sweet! I will check what is hosted on each of these websites. So, domains bountypay.h1ctf.com, www.bountypay.h1ctf.com point to the same application.”

{F863055}

“The login dropdown on the top right of the screen has two links. Customers points to https://app.bountypay.h1ctf.com/ and staff points to https://staff.bountypay.h1ctf.com/,” I continued. The wappalyzer extension says that this looks like a application that uses jquery 1.12.4, bootstrap framework.”

{F863057}

I was looking at the source of the page. There was a twitter account at the bottom of the page – bountypayhq. “Hmm, the company has a twitter account,” I said aloud. “I will look for something in the twitter account. You continue with the other domains,” said Sherlock.

“Let’s have a look at Customers link first. It poitns to https://app.bountypay.h1ctf.com/. Wappalyzer shows the same for the site.”

{F863058}

Mocking me, he said, “Try admin/admin or admin/password, it works. It has come in handy many a time. Remember how we locked out Mrs Hudson out of her router.”

“No, this is h1-ctf. I wish it was this simple,” I nodded, as I disregarded Sherlock’s advice. “Staff points to https://staff.bountypay.h1ctf.com/?template=login which also looks like using jquery.”

{F863059}

“Wait. Try it here,” exclaimed Sherlock. “It’s not that simple Sherlock!”

“Well, if you don’t try you will never know.”

I took a deep breath and keyed in ‘admin/admin’. “See it’s not that simple. Invalid Username / Password combination. Happy?”

{F863060}

“Well, atleast you tried. That’s what matters. When will you learn zer0ttl?” he shrugged. “What about the other domain software.bountypay.h1ctf.com?”

{F863062}

“It says 401 Unauthorized,” I frowned. “It replies with a 401 for anything I throw at it.”

{F863061}

“Well, aren’t you a cheeky self centered bastard!” he chuckled again. “The last one -api.bountypay.h1ctf.com.”

{F863064}

“Wouldn’t the next logical step be to do content discovery on each of the domains? Baby steps zer0ttl!” Sherlock was surprisingly optimistic.

I fired up ffuf for content discovery. “This new tool is blazing fast. This is now my goto tool for content discovery.”

{F863065}

The room was filled with the whirring sound of my laptop fan. “A thousand threads? Well, my room heating is broken. I wonder if your laptop fan would help me fix it,” said Sherlock. I waited patiently for the scan to complete. It took three minutes to run a list with 1.2M words. “Not bad,” I said.

{F863066}

I was rather dissapointed at the results. Nothing interesting popped up. “Well, I won’t be able to complete this. Nothing popped up. People use their own wordlists. I don’t have any. A hacker is only good as his wordlist is. I cannot do this.”

Sherlock, took in a deep breath and said, “Well, wouldn’t you make a wonderful fortune teller, zer0ttl. Stop jumping to conclusions. If not this, then try another wordlist. In the meantime, I am getting us some coffee. We have a long day ahead. This bed is going to be uncomfortable. Let’s move this to our workspace.

I let out a chuckle and asked,“By workspace you mean that rusty table with a visible layer of dust over it?”
Staring back with a death stare, Sherlock said, “Yes, that

The workplace in question, is basically a table that has a monitor, a keyboard, a mouse, a half eaten pizza, pizza boxes, bottles, books and papers. It had space for another planet, but none for my laptop. I started ffuf using the raft list this time, set it on the chair that I was sitting on and started cleaning the table. I though to myself, ‘I really want to finish this ctf this time. This would act as a morale booster for me. We should really get a cleaning service here. It’s quite filthy.’

“Well, you missed a spot.”

My chain of thoughts was broken by a rather authoratative voice. That is when I realised – I am the cleaning service!

“Well there seems to be a .git repo left by the developer on app.bountypay.h1ctf.com.” said Sherlock. “I wonder what secerts are hidden in that repo,” sipping on his coffee. I dropped what I was doing and rushed to the laptop. And, indeed there it was, waiting for me. A ‘.git’ had returned a 403 from the list. I grabbed my laptop and hooked it onto the monitor, keyboard and the mouse on the table.

{F863068}

Based on my previous experiences, I was aware about .git/HEAD and .git/config files inside a git repo. The HEAD is like the current branch and configuration about a git repo is stored in config. Within the next few seconds, I had sent requests to those endpoints as clockwork. Sometimes the devs mess up their web server config. Leaving these files open in the wild. And they did here too.

{F863072}

{F863071}

“Ah ha, finally!” I exclaimed. “The devs indeed messed up. The .git/config file is accessible. It points to a remote origin at https://github.com/bounty-pay-code/request-logger.git I am going to download the repo and check what’s inside that repo.” Sherlock was rather interested in something outside the window. It wasn’t something new. His attention span is worse than that of a cat. I proceeded with the download of the git repo. To my suprise, I was able to download the repo.

{F863070}

I had learnt that whenever anybody commits to git you have to write a message using the ‘-m’ flag. So, I incorporated checking git logs into my methodology. Sometimes devs write descriptive messages which shouldn’t be public.

{F863069}

Well, nothing to see here other than an entry for Create logger.php. Next step is to examine the logger.php file.

{F863074}

“Anything interesting in the request-logger repo?” said Sherlock as he pulled a chair next to me. “I see you have not had your coffee yet.” He picked my cup and emptied it into his. “I will get you another one. You don’t seem to need it anyway.”

I wasn’t surprised that he had paid little or no attention to what I was saying.

“So, the ip address, the uri, the method, and the parameters all get json-ified and then base64 encoded, a timestamp is appended to it and then stored in a file called bp_web_trace.log,” said Sherlock squinting at the monitor. “Quick, check if that file is still there on the server. Also, check if it is there on other

I pointed copied filename, appended it to the url, https://app.bountypay.h1ctf.com/bp_web_trace.log and opened it in my browser thinking this won’t work.

{F863078}

I was surprised to find the file is present on the server and immediately clicked on cancel. Sherlock pointed out,“Why would you do that? Don’t you want to see what’s inside there?”

“Oh boy, yes we will.” I said. I like using the terminal more. I curled the file and saved it locally as bp_web_trace.log

{F863080}

“The dev must have forgotten about the file,” I said. “It’s there for a reason, zer0ttl. You are on the right path,” said Sherlock. A few seconds, later we were looking at the decoded version of the file. “I see, somebody is showing off their bash-fu skills,” chuckled Sherlock.

{F863081}

“Well, it did take some time. I am slowly getting there, Sherlock. This is good. I have a username, a password and a challenge_answer. I wonder what that is. I am going to try these creds. See if they work.”

“I am in. I have access to app.bountypay.h1ctf.com.” I sighed. I was about to take a sip of my coffee when I realised it is empty. “Mrs. Hudson has a pot ready downstairs. And also maybe you are forgeting about the 2FA screen,”said Sherlock. “I will take a look at it when I am back!”

“While you are at it, would you mind getting me another cup? Thank you!

As I climbed down the stairs, I thought to myself, “I do not understand why I tolerate him. Mrs Hudson is also kind to him. I really wonder why?” After exchanging pleasantries with Mrs. Hudson, I was on my way up when I heard, “Ah ha. This is so simple!”

I rushed and spilled some coffee on the stairs. “I did it. Bypassed the 2FA for you, here you go,” said Sherlock.

{F863083}

“Ok. This is nice. But how, and why did you not wait for me?” I asked sternly. “It is simple, my friend zer0ttl.” Sherlock continued,“You see the after the login the app asks for a 2fa code. I entered my name there and observed the request/response in burp. Along with ‘username’, ‘password’ a ‘challenge’ and ‘challenge_answer’ parameters are passed in a POST request. You were wondering what the challenge_answer was in bp_web_trace.log file, it was this 2fa code. The challenge part of the request got my attention. It looked like a hash. It had 32 characters in it. MD5 – the first thing that came to my mind. So I put in my name as the ‘challenge_answer’ and md5sum of my name as ‘challenge’. And viola. Entry granted. You do know the md5sum of your name, right zer0ttl?”

I interuppted, “You remember the md5sum of your name?”

To which, he calmly replied, “Yes ofcourse. It comes in handy. You should too. Not the entire thing. Just the beginning and the end.” I looked at the burp requests. He was right.

{F863084}

I retried it with my name and it worked. “Patterns. You tend to recognise patterns over time zer0ttl. All animals recognize patterns. Some get good at it,” said Sherlock while walking back to the window.

{F863085}

Upon successful authentication, a cookie named token was granted. The value looked like a base64 encoded version of a json. The ‘eyJ’ at the beginning helped me deduce this. “Patterns,” I thought to myself.

{F863087}

{F863086}

The decoded cookie was indeed a json object. The account_id and hash. After playing with the account_id and hash, I concluded that I could control the value of account_id.

{F863088}

However fiddling with the hash rendered my request useless.

{F863089}

With that information and a sip of coffee, I continued to explore the app on app.bountypay.h1ctf.com. The BountyPay Dashboard just had one feature. The user could lookup their transactions based on the month and year.

{F863090}

{F863091}

The burp request for loading transaction gave some interesting output - url. What if I visit the url directly? It was the next thing I tried. I got a [Missing or invalid Token].

{F863299}

I wondered if there are transactions from previous months. I sent the request to burp intruder and selected the ‘cluster bomb’ as attack type. It helps you enumerate multiple fields at the same time. It’s a neat trick I learnt in one of the preivous ctfs. I selected the month and the year fields as positions where the payloads will be inserted. Set the payload options as 1 – 12 for month and 2010 to 2020 and fired away.

{F863300}

All the requests returned ‘[]’ for transactions and the ones from the year 2010 returned ‘Month or Year field invalid’. I took a deep breath and a sip from my cup. “What’s the progress zer0ttl?” Sherlock suddenly remembered that we were working on the h1-ctf. After bringing him to speed with the progress, I said “I don’t know what to do ahead.”

“Well, let me walk you through what you have found here. You control the username parameter using the cookie, the request to /statements?month=03&year=2018 returns a response with api.bountypay.h1ctf.com, you cannot access the url from api.bountypay.h1ctf.com and the app.bountypay.h1ctf.com and api.bountypay.h1ctf.com are somehow connected,”said Sherlock.

“Do you think that app.bountypay.h1ctf.com is making requests to api.bountypay.h1ctf.com in the backend? I control the username parameter. Maybe, I can use it to do something.” I asked. “Well, time to find it out,” Sherlock answered.

I went back to doing content discovery on api.bountypay.h1ctf.com. I found a open redirect on the api.bountypay.h1ctf.com. The front page of api.bountypay.h1ctf.com has a link https://api.bountypay.h1ctf.com/redirect?url=https://www.google.com/search?q=REST+API. The url parameter seemed to be vulnerable to open redirection but with a whitelist. It allowed www.google.com, api.bountypay.h1ctf.com, software.bountypay.h1ctf.com but did not allow redirect to any other domains including app.bountypay.h1ctf.com.

“So I have an open redirect on api.bountypay.h1ctf.com and I control the username parameter in ‘https://api.bountypay.h1ctf.com/api/accounts/F8gHiqSdpK/statements?month=03&year=2018’. What could I possibly do with this?” I asked myself. And then it struck me like a bolt of lightning. “Maybe I could try path injection in username parameter and make requests to the redirect parameter?” I said aloud. “I see you are evolving my friend. Excellent idea!” Sherlock approved.

Time to flex my python skills or as Sherlock Holmes would call it ‘The serpent langua’. I quickly scripted a small function to try various payloads.

{F863303}

It worked like a charm. I felt like a sorcerer.

{F863304}

“Perfect,” I was excited. I quickly tried to reach the redirect endpoint. After what felt like an eternity I was able to make requests to the redirect path which expects a url parameter.

{F863305}

“This is good progress. Do you remember the 401 you got for the software.bountypay.h1ctf.com domain? Do you think you could access that using this?” Sherlock said as soon he saw the output matching the one from the browser.

{F863308}

“Let me give it a try,” I said while keying in the input to the hammer function.

{F863309}

“It doesn’t return a 401 Unauthorized!” I said. “Well, it doesn’t return a 200 OK either,” said Sherlock. I suspected that something was wrong with the payload. Inspecting the reply in burp, I found out that the trailing /statements?month=03&year=2018 in the original request was causing a problem. A simple # at the end of the payload fixed the challenge.

{F863314}

“What do you say to that Sherlock? Well, it is time to see what is on that domain.” I had recently learnt how to use the turbo intruder and ‘boy oh boy’ that thing was fast. I believe every hacker should keep this tool handy. Some changes to the hammer function had the turbo intruder script ready in no time. This time I decided to use the raft list first.

{F863315}

{F863319}

The turbointruder did pay off. I got a hit for the uploads directory along with css, images andjs. The uploads directory had an apk file inside it. “Splendid job zer0ttl. Grab the apk, let’s see what’s inside it. I will go get Mrs. Hudson’s phone for this,” and Sherlock disappeared.

{F863320}

Mrs. Hudson once had a problem with her phone. Since then that phone has become our test android phone. We use it to analyse suspicious android applications. We had rooted the phone, installed some required tools like frida and as a service to Mrs. Hudson removed some bloatware from the phone.

By the time Sherlock was back with Mrs. Hudson’s phone, I compelted the primary analysis of the apk using the jadx tool. “So, the package name is bounty.pay; the main activity is bounty.pay.MainActivity and there are fours other activites; namely PartOneActivity, PartOneActivity, PartThreeActivity and CongratsActivity. There is a…” I was suddenly interrupted with “Where is it? Where is it? It was right here last night?”

“Are you looking for the usb cable?” I asked. “Yes! Have you se…”I interrupted “It is here with me. Pass me the phone,” Sherlock shrugged. “Don’t like it when somebody interrupts you do you?” I continuted. “There is a firebase database url in strings.xml. App doesn’t use any native libraries.”
“So this should be a piece of cake?” claimed Sherlock. “We will see,” I said while installing the apk to the phone using adb.

I launched the application after installing it. “What are you waiting for? Enter the damn username and twitter handle already?” said Sherlock. “I am doing it, will you be patient!” After geting greeted by a blank screen in PartOneActivity, and clicking on the BountyPay icon for a couple of times, I exclaimed “Now what?”.

{F863321}

“Look at the source!” he replied. I opened up the decompiled PartOneActivity.java file. The onCreate function particularly caught my attention.

{F863328}

“Sherlock, look at this line of code,” I said. “It has a getIntent().getData() function. Some getQueryParameter(“start”) and queryParameter.equals("PartTwoActivity"). If this condition is met then PartTwoActivity is started. Need to figure out what this line does.”

“So getIntent() returns the intent that started the current activity. GetData() retrieves data that the current intent is operating on. This URI specifies the name of the data; often it uses the content: scheme, specifying data in a content provider. Other schemes may be handled by specific activities, such as http: by the web browser. it says in the documentation. “I do not understand what is going on. Why is java so difficult to understand!” I said. “Baby steps, zer0ttl. One thing at a time. So, the intent expects an URI. How does android expect URI to be? Check out AndroidManifest.xml,” he continued.

“Under the PartOneActivity, I see a scheme as one and host as part. This URI has to be one://part?” I asked. “Yes indeed. One can have custom URIs in android apps. You already know the queryParameter and its value. What are you waiting for?” said Sherlock. The adb documentation has information about how to start an activity with an intent. Specifiy the component with -n and pass the data URI with -d. Using this information I crossed my fingers and typed in the command.

{F863334}

To my surprise the phone screen refreshed and the app now displayed PartTwoActivity.

{F863336}

“Time to take a look at PartTwoActivity.java”. PartTwoActivity also expected a similar intent URI data as the part one. Two query parameters, first one named two with value equal to light and the second named light with value equal to on. “This should be simple,” I said. Well, it wasn’t.

{F863335}

The phone screen refreshed and the PartTwoActivity was still seen on the screen. What went wrong? “It’s always the minor details zer0ttl.” I noticed that the debug message after the command finished running was the detail Sherlock was talking about. In PartOneActivity there was just one parameter=value. However PartTwoActivity expects two parameters and the command somehow only passed the first parameter. The & sign and part after it was missing. I then knew that I had to escape the ‘&’ sign with ''.

{F863337}

I checked the message and both the parameters where now being passed to PartTwoActivity. The phone screen flashed and an input field was exposed along with a hash – 459a6f79ad9b13cbcb5f692d2cc7a94d. Hashes.org is a neat website to look up hashes. I quickly looked up the hash value. It was a md5sum of ‘Token’. “Is the the answer expected in the input field?” I fumbled while handling the phone and almost dropped the phone. “Careful there. We do not have the budget for a new phone,” exclaimed Sherlock.

{F863339}

Token did not work. After a careful review of the code I figured out why.

{F863341}

The value expected was X-Token. After giving it the correct value, the app flashed PartThreeActivity. “Just one more obstacle to go to complete the android part,” I said.

By this time I had somewhat mastered the art of reading java. Quickly going through PartThreeActivity.java I figured out the required intent parameters and their values. Passing it to the intent was also not a challenge as we had already done that in PartTwoActivity. This time the ‘=’ sign along with the ‘&’ needed escaping.

{F863346}

Once that was done, we were expected to submit a leaked hash into the app. Now going through the source code for PartThreeActivity, I had noticed some calls to Log.d() inside the performPostCall function. Log is an API in android to send log output. You can view these messages with logcat.

I started checking logs in the logcat using adb and indeed there was a hash leaked in the messages. I salvaged a host HOST IS: : http://api.bountypay.h1ctf.com and a header value HEADER VALUE AND HASH : X-Token: 8e9998ee3137ca9ade8f372739f062c1 from the log messages as well. I was hoping this would be useful ahead.

{F863349}

PartThreeActivity was completed. We were greeted with the CongratsActivity and a toast after clicking on the bountypay button “Information leaked here will help with other challenges.”

{F863350}

It was evening and I did not realise the amount of time we had spent on the android app challenges. It was only when Mrs. Hudson made her way into the room with tea and biscuits. “How does he live in this mess? He should get this cleaned up!” she mumbled while making place for the tea on the table.
“Oh, Mrs. Hudson thank you so much. And here is your phone. zer0ttl’s cousin is fine. It was rather a mis-understanding between the two,” said Sherlock handing over the phone to Mrs. Hudson. I was curious and surprised at that statement.

“I see you lads haven’t had your lunch yet. I will get you some sandwiches.”

“Thankyou Mrs. Hudson. That would be lovely. Isn’t Mrs Hudson a lovely lady?” said Sherlock walking towards the window with his

Tea was as refreshing as the Congrats from the app. There was much more to go. The X-Token header was the missing piece in making requests to api.bountypay.h1ctf.com. Once we had that setup in burp, we could make requests without getting the Missing or invalid Token response.

{F863352}

I suspected there could be other api endpoints on the server. I ran the raft list through ffuf but this time with the additional X-Token header. There was a staff api endpoint on the server.

{F863353}

{F863354}

“Did you find anything interesting?” Sherlock asked me from the corner. “I found a staff endpoint. It is leaking some staff names and staff ids. Other than that I am not able to do anything else.” I replied.

“Did you say a staff id? Oh zer0ttl, remember the twitter account you found in the beginning? I was looking at it and there was some activity on the account. Sandra Allison is their new employee and she has tweeted her id card which has her staff id. I couldn’t find anything else. I wonder if you can use that here?” said Sherlock. “And when were you planning to tell me this piece of intel?”

“When the time was right. Now, is the time I see when you have found the staff endpoint,” replied Sherlock with a sheepish smile. I quickly looked up the twitter account for Bountypay and Sandra Allison was in thier list of followers. She had indeed tweeted a picture of her id which leaked the seemingly random staff id – STF:8FJ3KFISL3. This was the fastest progress we’d made during the day. “At this pace, we would be done before dinner!” I said in an excited tone. Little did we know what lay ahead of us.

{F863355}

It had been more than an hour I had made no progress with the staff endpoint. The tea lay half finished besides the laptop and I had hungrily devoured the sandwiches Mrs. Hudson had sent. “Why can’t I get this?” I banged my hand on my table. Sherlock who was now back on the bed said, “Go back to basics zer0ttl. What’s an API? What can you do with an API?”

Almost like giving an exam, I replied, “APIs are endpoints to interact with an application. You can get/put/update/delete information using an API.”
“Well, you see ze0rttl, you are just trying to get information, have you tried to do anything else here?” he asked. That’s when I knew what I was missing. I immediately changed the request method to POST in burp and I saw something other than Sam and Brian.

{F863356}

How did I not think of this myself? Missing Parameter. Now I already knew two parameters from looking at Sam and Brian for the past hour. name parameter had a response of Missing Parameter but staff_id displayed the response Invalid Staff ID.

{F863357}

“Finally something new!” I said. “Take a step back now and then. Reflect on what information you have. It helps,” said Sherlock. He was right. Taking a break, helps your brain process the information it collects. I did not want to say it aloud. I fake smiled. “You know I am right. You just don’t want to accept it. That doesn’t make me wrong though,” smirked Sherlock.

After providing Sandra’s staff id it still said Invalid Staff ID. “Maybe it expects the input in a particular format. Just the way it reflects in Sam’s and Brian’s case.”

{F863358}

“Staff Member Account Created” I took a deep breath. “I have access to Sandra’s credentials.” I said. After a couple of tries, I was able to use the credentials to log in to staff.bountypay.h1ctf.com.

{F863359}

I noticed a few functionalities inside the staff app. Support Tickets feature helped the user to look up their support tickets. The replies to the support ticket were disabled. It displayed the user’s name and profile picture.

{F863361}

The Profile section of the application allowed a user to change their profile name and avatar (profile picture). The profile name and change avatar filtered all the important characters. There was no scope for injection. Everytime I changed the Sandra’s profile name and avatar, I’d be assigned a new token by the server.

{F863362}

The new profile name and avatar would then be reflected into the page with all special characters stripped.

{F863364}

The avatar part was being reflected into a class name. “This is unusual,” I thought.

{F863365}

And, lastly the report this page feature allowed you to report an url to admin. The admin views the url. The url parameter is embedded in every page. It was always a base64 encoded of the current path.

{F863366}

{F863367}

{F863369}

There was a javascript file that was referenced by the web application. There was a reference to an admin feature where a an admin user could upgrade a user to an admin. It was pretty clear that what was to be done next. “I can report a link to an admin user. An admin user will visit that link. I want to make the new hire, Sandra, to an admin user. I need to exploit the report functionality such that when the admin user visits a malicious URL, Sandra becomes an admin. Great,” I said. “Well this should be interesting,” Sherlock was up and next to me in no time.

{F863370}

I tried to recreate the request for upgrading an user in burp. Sherlock exclaimed, “If it were only that simple.” “Doesn’t hurt to try, now does it? - Was it you who said this before?” We both laughed.

{F863371}

The document.location.hash part in the end of the js file was interesting. The document.location.hash returns the part of the url after the # sign. The tab1, tab2, tab3 where the names of classes for Home, Support Tickets and Profile sections of the applications. The tab1, tab2, tab3 where the names of classes for Home, Support Tickets and Profile sections of the applications. “So I can redirect an user based on the #tab passed in the url and the js file takes care of trigger the click,” I noted.

{F863372}

{F863374}

{F863373}

upgradeToAdmin and sendReport are class names. Remember once we spent an entire weekend learning javascript and jquery learning because Mycroft wanted to upgrade some features on his website? That was cumbersome. It’s paying off now.”

“It is part of the job. You never know what information can be utilised in what manner. The more you know, the better you get at your job, zer0ttl,” Sherlock said.

“So #tab1 triggers a click on that particular class. I can inject into a class name using the change profile section. This means that if I can inject the class name upgradeToAdmin tab1 into the class name and request a page that has this injected class along with #tab1, the page should trigger the javascript function $(".upgradeToAdmin").click(function(). This is cool." I said.

Upon examining the source of the page I realised that the profile section is always present in the page but hidden. However, I cannot send the home page or the profile section as the user’s avatar needs to reflect my injected class names. The ticket detail page at /template=ticket&ticket_id=3582 was the perfect candidate as this page would reflect the injected class irrespective of who was viewing the page.

{F863376}

I set the profile avatar and verified if the injected class name was indeed being reflected in the page.

{F863378}

{F863377}

“Time to check if our theory for document.location.hash and class names works.” I visited the URL https://staff.bountypay.h1ctf.com/?template=home#tab1

{F863379}

The burp history recorded request going to /admin/upgrade?username=undefined. I was happy with the progress. “Baby steps,” I mumbled.

“Now you just need to find a way to pass the username to that function,” said Sherlock. “I either need to find an html injection or find a page which has an input field with name attribute set to username,” I said.

“The comment section has a disabled input field and the profile section has an input field with name set to profile_name. Both are not useful,” I continued. Sherlock was smiling, he knew something, “If I tell you, how will you learn zer0ttl?”, he replied. “Let me open the window for you. Maybe the fresh air might trigger something in your mind.” he said as he walked towards the window.

The draft of cold breeze was definately refreshing. I closed my eyes for a bit, took a deep breath, “I got to finish this!” I said to my self. Content discovery did not find anything new. “Maybe you start again,” Sherlock suggested.

Dejected I cliked on logout and was redirected to the login page and there was an input field. I hurried to check the attribute of that input field. It said ‘name=”username”’. “I found it. I found it. “It was right there all this time. How did I miss this?” I exclaimed.

“It’s the simple things in life, zer0ttl,” Sherlock smiled.

{F863380}

I now needed to somehow stitch the login page and the ticket detail page so that a request with the username=sandra.allison is sent to admin/upgrade. I tried adding multiple templates but it displayed only the last template. The login page accepted GET request, where the parameter username was reflected in the input field.

{F863382}

I had seen this behaviour earlier. “What if I pass the template as an array?” I thought.

{F863383}

The array trick had worked. The login page and the ticket detail page were rendered on the same page.

I immediately added the document hash and the username parameter to the url and fired it away. https://staff.bountypay.h1ctf.com/?template[]=login&username=sandra.allison&template[]=ticket&ticket_id=3582#tab1

{F863384}

I reported the base64 encoded URL and there was a new token in the response. I pasted the new token in my browser’s cookie section. And I refreshed the page.

{F863385}

{F863387}

There was a new Admin section. I had escallated sandra.allison to admin. “Fresh air did help Sherlock. I have the credentials for marten.mickos.” The login creds were for app.bountypay.h1ctf.com as brian’s credentials were the same as I had discovered in the log file on app.bountypay.h1ctf.com.

{F863386}

“This is over. It was a gruelling journey. Finally it’s done,” I bypassed the 2fa using the same trick used for Brian’s creds and was greeted with the dashboard. The original tweet mentioned transactions for May 2020. I changed the month to 05 and clicked on Load Transactions. The Pay button was waiting to be clicked.

{F863389}

The smirk was wiped off my face like sand castles on a beach.

{F863388}

There was another 2fa to be bypassed. “Ha ha ha. You thought it was over?” Sherlock laughed. I gathered whatever energy I had left in me, “Damn this!” and clicked on Send Challenge. I wanted to get done with this quickly. The browser sent a POST request with app_style as the parameter. This was a css file. The response contained a challenge_timeout and a challenge value.

{F863391}

The lenght of the 2FA code was 7 characters.

{F863390}

When a code was entered on the screen, the browser made a POST request with the challenge_timeout, challenge and challenge_answer in the POST body. I knew what had to be done. I remebered a video I’d seen by @liveoverflow, where he spoke about data exfiltration using css. This had to be it.

I had a sick feeling in my stomach. I mumbled, “I have never done this before.”. Sherlock said, “Well, now’s a great opportunity to learn then.”

After spending a couple of hours, combing through how css selectors work and how they can be used to manipulate data I came up with a strategy.

“Listen to this. Css selectors can work on html elements, element attributes and attribute values,” I explained my strategy, “The article by Mike Gualtieri mentions that attribute values can be extracted using css injection. So I think the 2fa code is embedded inside the 2fa app in this fashion - <htmlElement blah=”2FA-CODE” ></htmlElement>. If I can find the element and attribute that stores the 2FA code I can exfil that code.”

Sherlock curiously enquired, “Have you tested for simple things like html injection or xss attacks? Have you confirmed that this is indeed a css exfil challenge?” I confirmed that app special characters like ‘, “, <, > are filtered.

“I needed one thing to confirm css injection. A public server which could serve my malicious css and I could receive call backs from the injected css. This server had to be an https capable server. I had learnt this the hard way from many ctfs before.

Also, I had to make sure that my server serves the css with content-type as text/css and not text/plain” I was thinking aloud when Sherlock interupped, “Your blog to host the css and burp collaborator for callbacks. Come on then, wrap this up quickly. I haven’t got all night. Chop chop.”

I created a simple poc based on the original css at the url https://www.bountypay.h1ctf.com/css/uni_2fa_style.css. I replaced the image url with a callback for my burp collaborator server.

{F863392}

I sent the POST request with app_style parameter and polled the collaborator client. There were two reqeusts from ip 3.21.98.146. This was the IP address of *.bountypay.h1ctf.com server. The HTML request was for /cssLoaded endpoint.

{F863394}

“This is indeed a ‘data exfiltration using css injection’ challenge, Sherlock.” Next I had to enumerate the html elements on the page. I incorporated the changes to search for the usual suspects – body, head, input, a tags.

{F863394}

I got callbacks for body, input and head elements. “Sherlock, I am pretty confident that this input tag must contain the 2fa code. Now, I need to find the names of the attributes.”

{F863395}

I checked the source for the 2FA page and came up with a list of candidates. The value attribute of an input elements hold the value for the current input. I modified the css file to incorporate the changes. The mozilla developer documentation for css selectors was quite helpful in figuring out the syntax. The input[value] selects input elements with value attribute set. The call back to ‘/input_elm_value_attrib’ confirmed this.

{F863398}

{F863396}

“Time for data exfil,” I said. The input[value*="a"] selects input elements with attribute named value which contains substring a. This way you can create call backs for characters a-z,A-Z,0-9. I added these callbacks to the css file.

{F863397}

The collaborator client had received 7 callbacks.

{F863400}

The callbacks where however not in the correct order. I stared at the screen for a while, thinking about the attack. “So I have a code, not in correct order. I need to brute force it. There is a timestamp for 2 minutes. I need to perform css injection. Collect the the characters, create word list, brute force the 2FA before the timestamp expires. A symphony of multiple things put together to crack the 2FA code. This has to be the last stage. I am tired.” I said.

“You can do this zer0ttl. You have to see this through,” Sherlock motivated me.

I wrote a python code to create a brute-force worldlist for the 2FA code and a turbo intruder script to brute force the 2FA.

{F863401}

{F863402}

I mentally made a note of all the steps and the order in which they should be performed. “Send the 2FA reqeust with injected css. Copy the challenge_timeout and challenge from the response and paste it in the turbo intruder. Poll the collaborator client for 2FA characters. Paste those chars in the python code and generate the wordlist. Copy the name of the wordlist and paste it in turbointruder, attack and keep your fingers crossed.” I sighed repeating the steps aloud. “God speed to you zer0ttl.”

The first time only 6 characters were returned meaning one of the characters was repeated. I geared up once again for the attack. After a few retries, I was able to get 7 call backs. By the time I clicked on attack in turbointruder my throat was dry as the Sahara. “That was intense!” said Sherlock. We stared for something to pop up in turbointruder, and there it was.

{F863405}

One of the requests had returned 200 status. “Could this be it?” I thought. Before I could click on the link, Sherlock snatched the mouse and clicked on the request.

{F863406}

“CTF Challenge Completed!” I was overwhelmed, I could not believe what I was seeing onscreen. I yelled and shrieked in excitement, “It’s done!” Sherlock said, “You are scaring Mrs. Hudson” I had not realised the time. I got up from the chair and walked to the window. It was chilly outside. Puncturing my perfect moment, he said, “You do know you have to write the report for this for a chance to win the invitation to the private event right?”

“Let me enjoy this moment Sherlock. I have an idea for the report.”

“Care for a bite? All this brain draining excersises has gotten me hungry. Let’s hope the place near the station is still open. Here take your coat. Also, let’s hope that this report of yours is seen by the right people and acts as an advertisement to get us some more work,” said Sherlock as he made his way downstairs.

Impact

The flag : 736c635d8842751b8aafa556154eb9f3