MNT-21118: quickshare xss prevention (#536)

* MNT-21118: quickshare xss prevention

The selected way to prevent from xss attacks is forcing browsers to download files by adding Content-Disposition to the response headers as it is done in V1.

Forcing browsers to download files will always be the true for QuickShareContentGet, 
QuickShareThumbnailContentGet extends QuickShareContentGet, therefore the attach value will be overridden according to the url parameter "a".

In the test, the thumbnail must be generated by a logged in user before sharing the link to the document.
This commit is contained in:
Oussama Messeguem
2020-02-28 09:58:35 +00:00
committed by GitHub
parent f34faec4b3
commit 2b3003a84f
3 changed files with 85 additions and 60 deletions

View File

@@ -136,7 +136,10 @@ public class QuickShareContentGet extends ContentGet implements ServletContextAw
throw new InvalidNodeRefException(nodeRef); throw new InvalidNodeRefException(nodeRef);
} }
executeImpl(nodeRef, params, req, res, null); // MNT-21118 (XSS prevention)
// Force the attachment in case of asking for the content file only
// (will be overridden for thumbnails)
executeImpl(nodeRef, params, req, res, null, true);
return null; return null;
} }
@@ -160,7 +163,7 @@ public class QuickShareContentGet extends ContentGet implements ServletContextAw
} }
} }
protected void executeImpl(NodeRef nodeRef, Map<String, String> templateVars, WebScriptRequest req, WebScriptResponse res, Map<String, Object> model) throws IOException protected void executeImpl(NodeRef nodeRef, Map<String, String> templateVars, WebScriptRequest req, WebScriptResponse res, Map<String, Object> model, boolean attach) throws IOException
{ {
// render content // render content
QName propertyQName = ContentModel.PROP_CONTENT; QName propertyQName = ContentModel.PROP_CONTENT;
@@ -178,9 +181,6 @@ public class QuickShareContentGet extends ContentGet implements ServletContextAw
} }
} }
// determine attachment
boolean attach = Boolean.valueOf(req.getParameter("a"));
// Stream the content // Stream the content
streamContentLocal(req, res, nodeRef, attach, propertyQName, model); streamContentLocal(req, res, nodeRef, attach, propertyQName, model);
} }

View File

@@ -81,7 +81,7 @@ public class QuickShareThumbnailContentGet extends QuickShareContentGet
} }
@Override @Override
protected void executeImpl(NodeRef nodeRef, Map<String, String> templateVars, WebScriptRequest req, WebScriptResponse res, Map<String, Object> model) throws IOException protected void executeImpl(NodeRef nodeRef, Map<String, String> templateVars, WebScriptRequest req, WebScriptResponse res, Map<String, Object> model, boolean attach) throws IOException
{ {
String thumbnailName = templateVars.get("thumbnailname"); String thumbnailName = templateVars.get("thumbnailname");
if (thumbnailName == null) if (thumbnailName == null)
@@ -188,7 +188,10 @@ public class QuickShareThumbnailContentGet extends QuickShareContentGet
} }
} }
super.executeImpl(thumbnailNodeRef, templateVars, req, res, model); // determine attachment
attach = Boolean.valueOf(req.getParameter("a"));
super.executeImpl(thumbnailNodeRef, templateVars, req, res, model, attach);
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
{ {

View File

@@ -358,6 +358,28 @@ public class QuickShareRestApiTest extends BaseWebScriptTest
assertFalse(nodeService.hasAspect(copyNodeRef, QuickShareModel.ASPECT_QSHARE)); assertFalse(nodeService.hasAspect(copyNodeRef, QuickShareModel.ASPECT_QSHARE));
} }
public void testContentDispositionInResponseHeader() throws IOException, JSONException
{
checkTransformer();
String testNodeRef_3 = testNode.toString().replace("://", "/");
// Thumbnail creation by user one to genuinely create the thumbnail and allow the sharedId to get it
sendRequest(new GetRequest(AUTH_CONTENT_THUMBNAIL_URL.replace("{node_ref_3}", testNodeRef_3).replace("{thumbnailname}", "doclib")), 200, USER_ONE);
Response rsp = sendRequest(new PostRequest(SHARE_URL.replace("{node_ref_3}", testNodeRef_3), "", APPLICATION_JSON), 200, USER_ONE);
JSONObject jsonRsp = new JSONObject(new JSONTokener(rsp.getContentAsString()));
String sharedId = jsonRsp.getString("sharedId");
// In case of requesting the content only, Content-Disposition should be present to force browsers to download the file
rsp = sendRequest(new GetRequest(SHARE_CONTENT_URL.replace("{shared_id}", sharedId)), 200, USER_TWO);
assertNotNull("The response should contain a Content-Disposition entry in the header", rsp.getHeader("Content-Disposition"));
// In case of requesting the thumbnail, Content-Disposition should not be present
rsp = sendRequest(new GetRequest(SHARE_CONTENT_THUMBNAIL_URL.replace("{shared_id}", sharedId).replace("{thumbnailname}", "doclib")), 200, USER_TWO);
assertNull("The response should not contain a Content-Disposition entry in the header", rsp.getHeader("Content-Disposition"));
}
private void createUser(String userName) private void createUser(String userName)
{ {
if (! authenticationService.authenticationExists(userName)) if (! authenticationService.authenticationExists(userName))