mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-14 17:58:59 +00:00
Merged HEAD-BUG-FIX (5.1/Cloud) to HEAD (5.1/Cloud)
93834: Merged 5.0.N (5.0.1) to HEAD-BUG-FIX (5.1/Cloud) 93734: Merged V4.2-BUG-FIX (4.2.5) to 5.0.N (5.0.1) - SOURCE/root/projects/remote-api/config/alfresco/templates/webscripts/org/alfresco/collaboration/calendar.get.html.ftl was removed from 5.0 as part of an EOL activity 93559: Merged V4.2.4 (4.2.4) to V4.2-BUG-FIX (4.2.5) 93544: Merged DEV to V4.2.4 (4.2.4) 93482: MNT-13174: /share/service/components/form {htmlid} unsanitized: XSS vulnerability - Add html escape to avoid XSS vulnerability 93545: Merged DEV to V4.2.4 (4.2.4) 93508 : MNT-13177 : /alfresco/wcservice/search/keyword {l} unsanitized: XSS vulnerability - Escape search.localeId property 93549: Merged DEV to V4.2.4 (4.2.4) 93540 : MNT-13173 : /share/service/components/form {destination} unsanitized: XSS vulnerability - Add html escape to avoid XSS vulnerability 93555: Merged DEV to V4.2.4 (4.2.4) 93476: MNT-13178: /alfresco/wcservice/api/search/person {l} unsanitized: XSS vulnerability - Add url escape to avoid XSS vulnerability 93556: Merged DEV to V4.2.4 (4.2.4) 93477: MNT-13176 : /alfresco/wcservice/collaboration/calendar {nodeRef} unsanitized: XSS vulnerability - Added ?html built-in processing for nodeRef argument. 93718: Merged V4.2.4 (4.2.4) to V4.2-BUG-FIX (4.2.5) 93671: Merged DEV to PATCHES/V4.2.4 (4.2.4) 93661: MNT-13180: go through all API URI and confirm all parameters are sanitized - Add unit test that checks all webscripts for sanitized parameters 93672: MNT-13190: /alfresco/wcservice/sample/blog/search {q} unsanitized: XSS vulnerability Add html escape to fix XSS vulnerability 93691: MNT-13190: /alfresco/wcservice/sample/blog/search {q} unsanitized: XSS vulnerability Patch imported blogsearch template git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@94995 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
260
source/test-java/org/alfresco/repo/web/scripts/XssVulnerabilityTest.java
Executable file
260
source/test-java/org/alfresco/repo/web/scripts/XssVulnerabilityTest.java
Executable file
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2015 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.alfresco.repo.web.scripts;
|
||||
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||
import org.alfresco.repo.web.scripts.tenant.TenantAdminSystemTest;
|
||||
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.extensions.webscripts.DeclarativeRegistry;
|
||||
import org.springframework.extensions.webscripts.Description;
|
||||
import org.springframework.extensions.webscripts.Status;
|
||||
import org.springframework.extensions.webscripts.TestWebScriptServer.DeleteRequest;
|
||||
import org.springframework.extensions.webscripts.TestWebScriptServer.GetRequest;
|
||||
import org.springframework.extensions.webscripts.TestWebScriptServer.PostRequest;
|
||||
import org.springframework.extensions.webscripts.TestWebScriptServer.PutRequest;
|
||||
import org.springframework.extensions.webscripts.TestWebScriptServer.Request;
|
||||
import org.springframework.extensions.webscripts.TestWebScriptServer.Response;
|
||||
import org.springframework.extensions.webscripts.WebScript;
|
||||
import org.springframework.extensions.webscripts.WebScriptException;
|
||||
|
||||
/**
|
||||
* https://issues.alfresco.com/jira/browse/MNT-13180
|
||||
*
|
||||
* Customer would like to be sure that we protect ourselves against unsanitized user inputs that can lead to XSS vulnerabilities.
|
||||
* This probably means (at least) implementing a Unit Test framework that injects into each webscript listed at:
|
||||
* http://localhost:8080/alfresco/wcservice/index/uri/
|
||||
* and for each documented parameter malicious input such as those used by our customer to detect the ones he found
|
||||
*
|
||||
* @author Viachaslau Tsikhanovich
|
||||
*/
|
||||
public class XssVulnerabilityTest extends BaseWebScriptTest
|
||||
{
|
||||
private static Log logger = LogFactory.getLog(TenantAdminSystemTest.class);
|
||||
|
||||
private DeclarativeRegistry webscriptsRegistry;
|
||||
|
||||
static final String START_ARG = "{";
|
||||
static final String END_ARG = "}";
|
||||
|
||||
static final String[] METHODS_TO_CHECK_ARRAY = { "GET", "DELETE", "POST", "PUT" };
|
||||
static final Set<String> METHODS_TO_CHECK_SET = new HashSet<String>(Arrays.asList(METHODS_TO_CHECK_ARRAY));
|
||||
static final String[] FORMATS_TO_CHECK_ARRAY = { "html" };
|
||||
static final Set<String> FORMATS_TO_CHECK_SET = new HashSet<String>(Arrays.asList(FORMATS_TO_CHECK_ARRAY));
|
||||
static final String[] URI_TO_SKIP_ARRAY = { ".rss", ".atom" }; // javascript is not executed for feeds
|
||||
|
||||
static final String MALARG1 = "<script>alert('XSS')</script>";
|
||||
static final String MALARG2 = "</script><script>alert('XSS')</script>";
|
||||
static final String MALARG3 = "\"</script><script>alert('XSS')</script>";
|
||||
static final String MALARG4 = "'\"</style></script><script>alert('XSS')</script>";
|
||||
static final String[] MALICIOUS_ARGS = { MALARG1, MALARG2, MALARG3, MALARG4 };
|
||||
static final String[] SKIP_WEBSCRIPT_CHECK_ARRAY = {
|
||||
"org/alfresco/cmis/client/cmisbrowser/federatedquery.get", /** argument is put into form's textarea but javascript is not executed **/
|
||||
"org/alfresco/cmis/test.post.desc.xml"
|
||||
};
|
||||
static final Set<String> SKIP_WEBSCRIPT_CHECK_ID_SET = new HashSet<String>(Arrays.asList(SKIP_WEBSCRIPT_CHECK_ARRAY));
|
||||
|
||||
protected void setUp() throws Exception
|
||||
{
|
||||
super.setUp();
|
||||
this.webscriptsRegistry = (DeclarativeRegistry)getServer().getApplicationContext().getBean("webscripts.registry.prototype");
|
||||
|
||||
setDefaultRunAs(AuthenticationUtil.getAdminUserName());
|
||||
}
|
||||
|
||||
protected void tearDown() throws Exception
|
||||
{
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
public void testXssVulnerability() throws Throwable
|
||||
{
|
||||
webscriptsRegistry.reset();
|
||||
final int scriptsSize = webscriptsRegistry.getWebScripts().size();
|
||||
int i = 0, successCount = 0, wserrcount = 0, vulnCount = 0;
|
||||
LinkedList<String> vulnerabileURLS = new LinkedList<String>();
|
||||
for(WebScript ws : webscriptsRegistry.getWebScripts())
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("progress: " + ++i + "/" + scriptsSize);
|
||||
}
|
||||
|
||||
Description wsDesc = ws.getDescription();
|
||||
if (SKIP_WEBSCRIPT_CHECK_ID_SET.contains(wsDesc.getId()))
|
||||
{
|
||||
// skip
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean isMethodCheck = METHODS_TO_CHECK_SET.contains(wsDesc.getMethod());
|
||||
boolean isFormatCheck = FORMATS_TO_CHECK_SET.contains(wsDesc.getDefaultFormat());
|
||||
if (isMethodCheck && isFormatCheck)
|
||||
{
|
||||
for (String malArg : MALICIOUS_ARGS)
|
||||
{
|
||||
String[] uris = wsDesc.getURIs();
|
||||
for (String uri : uris)
|
||||
{
|
||||
if (isUriSkip(uri))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// always parse url because we cannot rely on getArguments():
|
||||
// - sometimes getArguments() returns null although URI has arguments
|
||||
// - sometimes getArguments() returns set of args that does not contain args from url
|
||||
List<String> parsedArgs = parseArgsFromURI(uri);
|
||||
if (0 == parsedArgs.size())
|
||||
{
|
||||
// no arguments in uri, skip
|
||||
continue;
|
||||
}
|
||||
|
||||
String url = substituteMaliciousArgInURI(uri, parsedArgs, malArg);
|
||||
Response resp;
|
||||
try
|
||||
{
|
||||
resp = sendRequest(createRequest(wsDesc.getMethod(), url), -1);
|
||||
}
|
||||
catch (WebScriptException e)
|
||||
{
|
||||
// skip webscript errors
|
||||
++ wserrcount;
|
||||
continue;
|
||||
}
|
||||
|
||||
String respString = resp.getContentAsString();
|
||||
if (resp.getStatus() == Status.STATUS_OK)
|
||||
{
|
||||
++successCount;
|
||||
}
|
||||
|
||||
// do case insensitive check because argument can be converted to lowercase on page
|
||||
if (respString.toLowerCase().contains(malArg.toLowerCase()))
|
||||
{
|
||||
vulnerabileURLS.add(wsDesc.getMethod() + " " + url);
|
||||
vulnCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("OK html responses count: " + successCount);
|
||||
logger.debug("Webscript errors count: " + wserrcount);
|
||||
logger.debug("Vulnerabile URLs count: " + vulnCount);
|
||||
}
|
||||
|
||||
for (String url : vulnerabileURLS)
|
||||
{
|
||||
logger.warn("Vulnerabile URL: " + url);
|
||||
}
|
||||
assertTrue("Vulnerabile URLs found: " + vulnerabileURLS, vulnerabileURLS.size() == 0);
|
||||
}
|
||||
|
||||
private boolean isUriSkip(String uri)
|
||||
{
|
||||
for (String uriPart : URI_TO_SKIP_ARRAY)
|
||||
{
|
||||
if (uri.contains(uriPart))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<String> parseArgsFromURI(String uri)
|
||||
{
|
||||
List<String> args = new LinkedList<String>();
|
||||
int startBracketInd = uri.indexOf(START_ARG, 0);
|
||||
while (startBracketInd != -1)
|
||||
{
|
||||
int endBracketInd = uri.indexOf(END_ARG, startBracketInd);
|
||||
if (endBracketInd != -1)
|
||||
{
|
||||
String arg = uri.substring(startBracketInd + 1, endBracketInd);
|
||||
if (arg.endsWith("?"))
|
||||
{
|
||||
// optional argument
|
||||
arg = arg.substring(0, arg.length() - 1);
|
||||
}
|
||||
args.add(arg);
|
||||
// search next argument
|
||||
startBracketInd = uri.indexOf(START_ARG, endBracketInd);
|
||||
}
|
||||
else
|
||||
{
|
||||
// no ending bracket
|
||||
throw new AlfrescoRuntimeException("Invalid webscript URI : " + uri);
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
private Request createRequest(String method, String url) throws Exception
|
||||
{
|
||||
switch (method)
|
||||
{
|
||||
case "DELETE":
|
||||
return new DeleteRequest(url);
|
||||
case "GET":
|
||||
return new GetRequest(url);
|
||||
case "PUT":
|
||||
return new PutRequest(url, "{}", "application/json");
|
||||
case "POST":
|
||||
return new PostRequest(url, "{}", "application/json");
|
||||
default:
|
||||
throw new InvalidArgumentException("HTTP method not supported");
|
||||
}
|
||||
}
|
||||
|
||||
private String substituteMaliciousArgInURI(String uri, List<String> urlArgs, String malArg)
|
||||
{
|
||||
String url = uri;
|
||||
|
||||
// substitute malicious arguments
|
||||
for (String arg : urlArgs)
|
||||
{
|
||||
url = url.replace(START_ARG + arg + END_ARG, "a" + malArg);
|
||||
url = url.replace(START_ARG + arg + "?" + END_ARG, "a" + malArg);
|
||||
}
|
||||
|
||||
if (url.contains(START_ARG) || url.contains(END_ARG))
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Arguments were not properly substituted: " + url);
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user