Skip to content

Commit

Permalink
Ensuring secure XSLT processing everywhere
Browse files Browse the repository at this point in the history
  • Loading branch information
ilgrosso committed Mar 9, 2018
1 parent 222a486 commit 726231f
Show file tree
Hide file tree
Showing 6 changed files with 268 additions and 14 deletions.
Expand Up @@ -20,11 +20,10 @@

import java.util.HashMap;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.cxf.staxutils.StaxUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
Expand Down Expand Up @@ -52,11 +51,8 @@ public void setAuthorizations(final String authorizations) {
private void init() {
authMap = new HashMap<Pair<String, String>, String>();

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
try {
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(getClass().getResource("/" + authorizations).openStream());
Document doc = StaxUtils.read(getClass().getResource("/" + authorizations).openStream());
doc.getDocumentElement().normalize();

Node authNode = null;
Expand Down
21 changes: 18 additions & 3 deletions core/src/main/java/org/apache/syncope/core/report/ReportJob.java
Expand Up @@ -24,8 +24,11 @@
import java.util.zip.Deflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.xml.XMLConstants;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
Expand All @@ -40,6 +43,7 @@
import org.apache.syncope.core.rest.data.ReportDataBinder;
import org.apache.syncope.core.util.ApplicationContextProvider;
import org.apache.syncope.core.util.ExceptionUtil;
import org.apache.syncope.core.util.VoidURIResolver;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
Expand All @@ -62,6 +66,18 @@ public class ReportJob implements Job {
*/
private static final Logger LOG = LoggerFactory.getLogger(ReportJob.class);

private static final SAXTransformerFactory TRANSFORMER_FACTORY;

static {
TRANSFORMER_FACTORY = (SAXTransformerFactory) TransformerFactory.newInstance();
TRANSFORMER_FACTORY.setURIResolver(new VoidURIResolver());
try {
TRANSFORMER_FACTORY.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
} catch (TransformerConfigurationException e) {
LOG.error("Could not enable secure XML processing", e);
}
}

/**
* Report DAO.
*/
Expand Down Expand Up @@ -119,8 +135,7 @@ public void execute(final JobExecutionContext context) throws JobExecutionExcept
ZipOutputStream zos = new ZipOutputStream(baos);
zos.setLevel(Deflater.BEST_COMPRESSION);
try {
SAXTransformerFactory tFactory = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
handler = tFactory.newTransformerHandler();
handler = TRANSFORMER_FACTORY.newTransformerHandler();
Transformer serializer = handler.getTransformer();
serializer.setOutputProperty(OutputKeys.ENCODING, SyncopeConstants.DEFAULT_ENCODING);
serializer.setOutputProperty(OutputKeys.INDENT, "yes");
Expand Down Expand Up @@ -153,7 +168,7 @@ public void execute(final JobExecutionContext context) throws JobExecutionExcept
if (reportletClass != null) {
Reportlet<ReportletConf> autowired =
(Reportlet<ReportletConf>) ApplicationContextProvider.getBeanFactory().
createBean(reportletClass, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
createBean(reportletClass, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
autowired.setConf(reportletConf);

// invoke reportlet
Expand Down
Expand Up @@ -29,13 +29,13 @@
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipInputStream;
import javax.xml.transform.stream.StreamSource;
import org.apache.cocoon.optional.pipeline.components.sax.fop.FopSerializer;
import org.apache.cocoon.pipeline.NonCachingPipeline;
import org.apache.cocoon.pipeline.Pipeline;
import org.apache.cocoon.sax.SAXPipelineComponent;
import org.apache.cocoon.sax.component.XMLGenerator;
import org.apache.cocoon.sax.component.XMLSerializer;
import org.apache.cocoon.sax.component.XSLTTransformer;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.syncope.common.report.ReportletConf;
Expand Down Expand Up @@ -195,28 +195,32 @@ public void exportExecutionResult(final OutputStream os, final ReportExec report

switch (format) {
case HTML:
XSLTTransformer xsl2html = new XSLTTransformer(getClass().getResource("/report/report2html.xsl"));
XSLTTransformer xsl2html = new XSLTTransformer(
new StreamSource(getClass().getResourceAsStream("/report/report2html.xsl")));
xsl2html.setParameters(parameters);
pipeline.addComponent(xsl2html);
pipeline.addComponent(XMLSerializer.createXHTMLSerializer());
break;

case PDF:
XSLTTransformer xsl2pdf = new XSLTTransformer(getClass().getResource("/report/report2fo.xsl"));
XSLTTransformer xsl2pdf = new XSLTTransformer(
new StreamSource(getClass().getResourceAsStream("/report/report2fo.xsl")));
xsl2pdf.setParameters(parameters);
pipeline.addComponent(xsl2pdf);
pipeline.addComponent(new FopSerializer(MimeConstants.MIME_PDF));
break;

case RTF:
XSLTTransformer xsl2rtf = new XSLTTransformer(getClass().getResource("/report/report2fo.xsl"));
XSLTTransformer xsl2rtf = new XSLTTransformer(
new StreamSource(getClass().getResourceAsStream("/report/report2fo.xsl")));
xsl2rtf.setParameters(parameters);
pipeline.addComponent(xsl2rtf);
pipeline.addComponent(new FopSerializer(MimeConstants.MIME_RTF));
break;

case CSV:
XSLTTransformer xsl2csv = new XSLTTransformer(getClass().getResource("/report/report2csv.xsl"));
XSLTTransformer xsl2csv = new XSLTTransformer(
new StreamSource(getClass().getResourceAsStream("/report/report2csv.xsl")));
xsl2csv.setParameters(parameters);
pipeline.addComponent(xsl2csv);
pipeline.addComponent(new TextSerializer());
Expand Down
@@ -0,0 +1,201 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.syncope.core.rest.controller;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
import javax.xml.XMLConstants;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import org.apache.cocoon.pipeline.SetupException;
import org.apache.cocoon.pipeline.caching.CacheKey;
import org.apache.cocoon.pipeline.component.CachingPipelineComponent;
import org.apache.cocoon.pipeline.util.StringRepresentation;
import org.apache.cocoon.sax.AbstractSAXTransformer;
import org.apache.cocoon.sax.SAXConsumer;
import org.apache.cocoon.sax.util.SAXConsumerAdapter;
import org.apache.syncope.core.util.VoidURIResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class XSLTTransformer extends AbstractSAXTransformer implements CachingPipelineComponent {

private static final Logger LOG = LoggerFactory.getLogger(XSLTTransformer.class);

/**
* A generic transformer factory to parse XSLTs.
*/
private static final SAXTransformerFactory TRAX_FACTORY = createNewSAXTransformerFactory();

/**
* The XSLT parameters name pattern.
*/
private static final Pattern XSLT_PARAMETER_NAME_PATTERN = Pattern.compile("[a-zA-Z_][\\w\\-\\.]*");

/**
* The XSLT parameters reference.
*/
private Map<String, Object> parameters;

/**
* The XSLT Template reference.
*/
private Templates templates;

private Source source;

public XSLTTransformer(final Source source) {
super();
this.load(source, null);
}

/**
* Creates a new transformer reading the XSLT from the Source source and setting the TransformerFactory attributes.
*
* This constructor is useful when users want to perform XSLT transformation using <a
* href="http://xml.apache.org/xalan-j/xsltc_usage.html">xsltc</a>.
*
* @param source the XSLT source
* @param attributes the Transformer Factory attributes
*/
public XSLTTransformer(final Source source, final Map<String, Object> attributes) {
super();
this.load(source, attributes);
}

/**
* Method useful to create a new transformer reading the XSLT from the URL source and setting the Transformer
* Factory attributes.
*
* This method is useful when users want to perform XSLT transformation using <a
* href="http://xml.apache.org/xalan-j/xsltc_usage.html">xsltc</a>.
*
* @param source the XSLT source
* @param attributes the Transformer Factory attributes
*/
private void load(final Source source, final Map<String, Object> attributes) {
if (source == null) {
throw new IllegalArgumentException("The parameter 'source' mustn't be null.");
}

this.source = source;

this.load(this.source, this.source.toString(), attributes);
}

private void load(final Source source, final String localCacheKey, final Map<String, Object> attributes) {
LOG.debug("{} local cache miss: {}", getClass().getSimpleName(), localCacheKey);

// XSLT has to be parsed
final SAXTransformerFactory transformerFactory;
if (attributes == null || attributes.isEmpty()) {
transformerFactory = TRAX_FACTORY;
} else {
transformerFactory = createNewSAXTransformerFactory();
for (Map.Entry<String, Object> attribute : attributes.entrySet()) {
transformerFactory.setAttribute(attribute.getKey(), attribute.getValue());
}
}

try {
this.templates = transformerFactory.newTemplates(source);
} catch (TransformerConfigurationException e) {
throw new SetupException("Impossible to read XSLT from '" + source + "', see nested exception", e);
}
}

/**
* Sets the XSLT parameters to be applied to XSLT stylesheet.
*
* @param parameters the XSLT parameters to be applied to XSLT stylesheet
*/
public void setParameters(final Map<String, ? extends Object> parameters) {
if (parameters == null) {
this.parameters = null;
} else {
this.parameters = new HashMap<String, Object>(parameters);
}
}

/**
* {@inheritDoc}
*/
@Override
protected void setSAXConsumer(final SAXConsumer consumer) {
TransformerHandler transformerHandler;
try {
transformerHandler = TRAX_FACTORY.newTransformerHandler(this.templates);
} catch (Exception e) {
throw new SetupException("Could not initialize transformer handler.", e);
}

if (this.parameters != null) {
final Transformer transformer = transformerHandler.getTransformer();

for (Map.Entry<String, Object> entry : this.parameters.entrySet()) {
final String name = entry.getKey();

// is valid XSLT parameter name
if (XSLT_PARAMETER_NAME_PATTERN.matcher(name).matches()) {
transformer.setParameter(name, entry.getValue());
}
}
}

final SAXResult result = new SAXResult();
result.setHandler(consumer);
// According to TrAX specs, all TransformerHandlers are LexicalHandlers
result.setLexicalHandler(consumer);
transformerHandler.setResult(result);

final SAXConsumerAdapter saxConsumerAdapter = new SAXConsumerAdapter();
saxConsumerAdapter.setContentHandler(transformerHandler);
super.setSAXConsumer(saxConsumerAdapter);
}

@Override
public CacheKey constructCacheKey() {
return null;
}

/**
* Utility method to create a new transformer factory.
*
* @return a new transformer factory
*/
private static SAXTransformerFactory createNewSAXTransformerFactory() {
SAXTransformerFactory transformerFactory = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
transformerFactory.setURIResolver(new VoidURIResolver());
try {
transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
} catch (TransformerConfigurationException e) {
LOG.error("Could not enable secure XML processing", e);
}
return transformerFactory;
}

@Override
public String toString() {
return StringRepresentation.buildString(this, "src=<" + this.source + ">");
}
}
Expand Up @@ -40,6 +40,7 @@
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.xml.XMLConstants;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
Expand Down Expand Up @@ -346,6 +347,8 @@ public void export(final OutputStream os, final String wfTablePrefix)

StreamResult streamResult = new StreamResult(os);
final SAXTransformerFactory transformerFactory = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
transformerFactory.setURIResolver(new VoidURIResolver());
transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);

TransformerHandler handler = transformerFactory.newTransformerHandler();
Transformer serializer = handler.getTransformer();
Expand Down
@@ -0,0 +1,35 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.syncope.core.util;

import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.URIResolver;

/**
* This implementation disallows any XSLT include, for security reasons.
*/
public class VoidURIResolver implements URIResolver {

@Override
public Source resolve(final String href, final String base) throws TransformerException {
return null;
}

}

0 comments on commit 726231f

Please sign in to comment.