SaferSlingPostValidatorImpl.java 15.8 KB
/*
 * Decompiled with CFR 0_118.
 * 
 * Could not load the following classes:
 *  com.day.text.Text
 *  org.apache.felix.scr.annotations.Activate
 *  org.apache.felix.scr.annotations.Component
 *  org.apache.felix.scr.annotations.Properties
 *  org.apache.felix.scr.annotations.Property
 *  org.apache.felix.scr.annotations.Reference
 *  org.apache.felix.scr.annotations.Service
 *  org.apache.sling.api.SlingHttpServletRequest
 *  org.apache.sling.api.request.RequestParameter
 *  org.apache.sling.api.resource.Resource
 *  org.osgi.service.component.ComponentContext
 *  org.slf4j.Logger
 *  org.slf4j.LoggerFactory
 */
package com.day.cq.wcm.foundation.security.impl;

import com.day.cq.wcm.foundation.forms.attachments.AttachmentDataSource;
import com.day.cq.wcm.foundation.security.AttachmentTypeBlacklistService;
import com.day.cq.wcm.foundation.security.SaferSlingPostValidator;
import com.day.text.Text;
import java.util.Arrays;
import java.util.Collection;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.request.RequestParameter;
import org.apache.sling.api.resource.Resource;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(label="%security.saferslingpostvalidator.name", description="%security.saferslingpostvalidator.description", metatype=1, configurationPid="com.day.cq.wcm.foundation.security.SaferSlingPostValidatorImpl")
@Service(serviceFactory=1, value={SaferSlingPostValidator.class})
@Properties(value={@Property(name="service.description", value={"Safer Sling Post Validator"}, propertyPrivate=1)})
public class SaferSlingPostValidatorImpl
implements SaferSlingPostValidator {
    private static final String OPERATION_PARAMETER = ":operation";
    private static final String TYPEHINT_SUFFIX = "@TypeHint";
    private static final String RESOURCETYPE_PARAMETER = "sling:resourceType";
    private static final String RESOURCESUPERTYPE_PARAMETER = "sling:resourceSuperType";
    private static final String JCR_PRIMARYTYPE = "jcr:primaryType";
    private static final String JCR_MIXINTYPES = "jcr:mixinTypes";
    private static final String BINARY = "Binary";
    protected final Logger logger;
    @Property(value={"jcr:description", "jcr:title", "jcr:lastModified", "jcr:primaryType", "jcr:mixinTypes", "sling:resourceType", "sling:resourceSuperType", "cq:tags"}, label="%security.saferslingpostvalidator.whitelist.name", description="%security.saferslingpostvalidator.whitelist.description")
    private static final String PARAMETER_WHITELIST = "parameter.whitelist";
    @Property(value={}, label="%security.saferslingpostvalidator.prefix.name", description="%security.saferslingpostvalidator.prefix.description", cardinality=Integer.MAX_VALUE)
    private static final String PARAMETER_WHITELIST_PREFIXES = "parameter.whitelist.prefixes";
    @Property(value={}, label="%security.saferslingpostvalidator.binary.name", description="%security.saferslingpostvalidator.binary.description", cardinality=Integer.MAX_VALUE)
    private static final String BINARY_PARAMETER_WHITELIST = "binary.parameter.whitelist";
    @Property(value={"@TypeHint", "@DefaultValue", "@UseDefaultWhenMissing", "@IgnoreBlanks", "@ValueFrom", "@Delete", "@Patch"}, label="%security.saferslingpostvalidator.modifier.name", description="%security.saferslingpostvalidator.modifier.description")
    private static final String MODIFIER_WHITELIST = "modifier.whitelist";
    @Property(value={"delete", "nop"}, label="%security.saferslingpostvalidator.operation.name", description="%security.saferslingpostvalidator.operation.description")
    private static final String OPERATION_WHITELIST = "operation.whitelist";
    @Property(value={}, label="%security.saferslingpostvalidator.operation.prefix.name", description="%security.saferslingpostvalidator.operation.prefix.description", cardinality=Integer.MAX_VALUE)
    private static final String OPERATION_WHITELIST_PREFIXES = "operation.whitelist.prefixes";
    @Property(value={"cq:CalendarEvent", "nt:unstructured", "nt:folder", "nt:file", "nt:resource", "sling:Folder", "sling:OrderedFolder", "Binary", "Boolean", "Date", "Double", "Long", "Name", "Path", "String", "String[]"}, label="%security.saferslingpostvalidator.typehint.name", description="%security.saferslingpostvalidator.typehint.description")
    private static final String TYPEHINT_WHITELIST = "typehint.whitelist";
    @Property(value={}, label="%security.saferslingpostvalidator.resourcetype.name", description="%security.saferslingpostvalidator.resourcetype.description", cardinality=Integer.MAX_VALUE)
    private static final String RESOURCETYPE_WHITELIST = "resourcetype.whitelist";
    @Reference
    private AttachmentTypeBlacklistService attachmentTypeBlacklist;
    private Set<String> parameterWhiteList;
    private Set<String> parameterWhiteListPrefixes;
    private Set<String> binaryParameterWhiteList;
    private Set<String> modifierWhiteList;
    private Set<String> operationWhiteList;
    private Set<String> operationWhiteListPrefixes;
    private Set<String> typeHintWhiteList;
    private Set<String> resourceTypeWhiteList;

    public SaferSlingPostValidatorImpl() {
        this.logger = LoggerFactory.getLogger(this.getClass());
        this.parameterWhiteList = new HashSet<String>();
        this.parameterWhiteListPrefixes = new HashSet<String>();
        this.binaryParameterWhiteList = new HashSet<String>();
        this.modifierWhiteList = new HashSet<String>();
        this.operationWhiteList = new HashSet<String>();
        this.operationWhiteListPrefixes = new HashSet<String>();
        this.typeHintWhiteList = new HashSet<String>();
        this.resourceTypeWhiteList = new HashSet<String>();
    }

    @Activate
    protected void activate(ComponentContext ctx) {
        Object o = ctx.getProperties().get("parameter.whitelist");
        if (o instanceof String[]) {
            this.parameterWhiteList = new HashSet<String>();
            this.parameterWhiteList.addAll(Arrays.asList((String[])o));
        }
        if ((o = ctx.getProperties().get("parameter.whitelist.prefixes")) instanceof String[]) {
            this.parameterWhiteListPrefixes = new HashSet<String>();
            this.parameterWhiteListPrefixes.addAll(Arrays.asList((String[])o));
        }
        if ((o = ctx.getProperties().get("binary.parameter.whitelist")) instanceof String[]) {
            this.binaryParameterWhiteList = new HashSet<String>();
            this.binaryParameterWhiteList.addAll(Arrays.asList((String[])o));
        }
        if ((o = ctx.getProperties().get("modifier.whitelist")) instanceof String[]) {
            this.modifierWhiteList = new HashSet<String>();
            this.modifierWhiteList.addAll(Arrays.asList((String[])o));
        }
        if ((o = ctx.getProperties().get("operation.whitelist")) instanceof String[]) {
            this.operationWhiteList = new HashSet<String>();
            this.operationWhiteList.addAll(Arrays.asList((String[])o));
        }
        if ((o = ctx.getProperties().get("operation.whitelist.prefixes")) instanceof String[]) {
            this.operationWhiteListPrefixes = new HashSet<String>();
            this.operationWhiteListPrefixes.addAll(Arrays.asList((String[])o));
        }
        if ((o = ctx.getProperties().get("typehint.whitelist")) instanceof String[]) {
            this.typeHintWhiteList = new HashSet<String>();
            this.typeHintWhiteList.addAll(Arrays.asList((String[])o));
        }
        if ((o = ctx.getProperties().get("resourcetype.whitelist")) instanceof String[]) {
            this.resourceTypeWhiteList = new HashSet<String>();
            this.resourceTypeWhiteList.addAll(Arrays.asList((String[])o));
        }
    }

    @Override
    public /* varargs */ boolean reject(SlingHttpServletRequest request, String ... whitelistPatterns) {
        Enumeration paramNames = request.getParameterNames();
        while (paramNames.hasMoreElements()) {
            String name;
            Object o = paramNames.nextElement();
            if (!(o instanceof String)) {
                return true;
            }
            String fullName = (String)o;
            if (this.parameterWhiteList.contains(fullName)) continue;
            if (fullName.startsWith("../") || fullName.endsWith("/..") || fullName.contains("/../") || fullName.startsWith("/")) {
                return true;
            }
            String prename = this.leafName(fullName);
            int atidx = prename.lastIndexOf(64);
            if (atidx >= 0) {
                String modifier = prename.substring(atidx);
                name = prename.substring(0, atidx);
                if (this.checkModifier(request, name, fullName, modifier)) {
                    return true;
                }
            } else {
                name = prename;
            }
            if (":operation".equals(name) && this.checkOperation(request, name, fullName)) {
                return true;
            }
            if (("jcr:primaryType".equals(name) || "jcr:mixinTypes".equals(name)) && this.checkJCRTypes(request, name, fullName)) {
                return true;
            }
            if (("sling:resourceType".equals(name) || "sling:resourceSuperType".equals(name)) && this.checkResourceTypes(request, name, fullName)) {
                return true;
            }
            if (name.contains(":") && !name.startsWith(":") && !this.parameterWhiteList.contains(name) && this.checkParameterPrefixes(name) && this.checkParameterPatterns(name, whitelistPatterns)) {
                return true;
            }
            if (":applyTo".equals(name) && this.validateApplyTo(request)) {
                return true;
            }
            if (!this.validateUploads(request, name)) continue;
            return true;
        }
        return false;
    }

    private boolean checkParameterPrefixes(String name) {
        for (String prefix : this.parameterWhiteListPrefixes) {
            if (!name.startsWith(prefix)) continue;
            return false;
        }
        return true;
    }

    private /* varargs */ boolean checkParameterPatterns(String name, String ... patterns) {
        if (patterns == null || patterns.length == 0) {
            return false;
        }
        for (String patternVal : patterns) {
            try {
                Pattern p = Pattern.compile(patternVal);
                Matcher m = p.matcher(name);
                if (!m.matches()) continue;
                return false;
            }
            catch (PatternSyntaxException e) {
                this.logger.warn("invalid pattern [{}] provided to SaferSlingPostValidator: ", (Object)patternVal, (Object)e);
            }
        }
        return true;
    }

    private String leafName(String name) {
        int slashidx = name.lastIndexOf(47);
        if (slashidx >= 0) {
            return name.substring(slashidx + 1);
        }
        return name;
    }

    private boolean checkResourceTypes(SlingHttpServletRequest request, String name, String fullName) {
        String[] values = request.getParameterValues(fullName);
        if (values != null) {
            if (values.length > 1) {
                return true;
            }
            if (!this.resourceTypeWhiteList.contains(values[0])) {
                return true;
            }
        } else {
            return true;
        }
        return false;
    }

    private boolean checkJCRTypes(SlingHttpServletRequest request, String name, String fullName) {
        String[] values = request.getParameterValues(fullName);
        if (values != null) {
            for (String type : values) {
                if (this.typeHintWhiteList.contains(type)) continue;
                return true;
            }
        }
        return false;
    }

    private boolean checkOperation(SlingHttpServletRequest request, String name, String fullName) {
        String[] values = request.getParameterValues(fullName);
        if (values != null) {
            if (values.length > 1) {
                return true;
            }
            if (!"".equals(values[0]) && !this.operationWhiteList.contains(values[0])) {
                for (String prefix : this.operationWhiteListPrefixes) {
                    if (!values[0].startsWith(prefix)) continue;
                    return false;
                }
                return true;
            }
        }
        return false;
    }

    private boolean checkModifier(SlingHttpServletRequest request, String name, String fullName, String modifier) {
        if (!"".equals(modifier) && !this.modifierWhiteList.contains(modifier)) {
            return true;
        }
        if (this.checkTypeHint(request, name, fullName, modifier)) {
            return true;
        }
        return false;
    }

    private boolean checkTypeHint(SlingHttpServletRequest request, String name, String fullName, String modifier) {
        String[] values;
        if ("@TypeHint".equals(modifier) && (values = request.getParameterValues(fullName)) != null) {
            if (values.length > 1) {
                return true;
            }
            String type = values[0];
            if (!"".equals(type) && !this.typeHintWhiteList.contains(type)) {
                return true;
            }
            if ("Binary".equals(type) && name.contains(".") && !this.binaryParameterWhiteList.contains(name)) {
                return true;
            }
        }
        return false;
    }

    private boolean validateApplyTo(SlingHttpServletRequest request) {
        String[] paths = request.getParameterValues(":applyTo");
        if (paths == null) {
            return false;
        }
        Integer depth = (Integer)request.getAttribute(SaferSlingPostValidator.POST_DEPTH_ATTRIBUTE);
        for (String path : paths) {
            if (path == null || !this.validateApplyToPath(path, request.getResource().getPath(), depth != null ? depth : 0)) continue;
            return true;
        }
        return false;
    }

    private boolean validateApplyToPath(String applyToPath, String resourcePath, int depth) {
        String canonPath = Text.makeCanonicalPath((String)applyToPath);
        if (!canonPath.startsWith(resourcePath)) {
            return true;
        }
        String testPath = canonPath.substring(resourcePath.length());
        if (testPath.length() > 0 && testPath.charAt(0) == '/') {
            testPath = testPath.substring(1);
        }
        if (testPath.length() == 0) {
            return false;
        }
        int testDepth = testPath.split("/").length;
        if (testDepth > depth) {
            return true;
        }
        return false;
    }

    private boolean validateUploads(SlingHttpServletRequest request, String name) {
        RequestParameter[] rps = request.getRequestParameters(name);
        if (rps == null) {
            return false;
        }
        for (RequestParameter rp : rps) {
            AttachmentDataSource ads;
            if (rp.isFormField() || !this.attachmentTypeBlacklist.reject(ads = new AttachmentDataSource(rp))) continue;
            return true;
        }
        return false;
    }

    protected void bindAttachmentTypeBlacklist(AttachmentTypeBlacklistService attachmentTypeBlacklistService) {
        this.attachmentTypeBlacklist = attachmentTypeBlacklistService;
    }

    protected void unbindAttachmentTypeBlacklist(AttachmentTypeBlacklistService attachmentTypeBlacklistService) {
        if (this.attachmentTypeBlacklist == attachmentTypeBlacklistService) {
            this.attachmentTypeBlacklist = null;
        }
    }
}