org.zaproxy.zap.extension.ascanrules.PersistentXSSUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.zaproxy.zap.extension.ascanrules.PersistentXSSUtils.java

Source

/*
 * Zed Attack Proxy (ZAP) and its related class files.
 *
 * ZAP is an HTTP/HTTPS proxy for assessing web application security.
 *
 * Copyright 2013 The ZAP Development Team
 *
 * Licensed 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.zaproxy.zap.extension.ascanrules;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.collections.map.ReferenceMap;
import org.apache.log4j.Logger;
import org.parosproxy.paros.db.DatabaseException;
import org.parosproxy.paros.model.HistoryReference;
import org.parosproxy.paros.model.Model;
import org.parosproxy.paros.network.HttpMalformedHeaderException;
import org.parosproxy.paros.network.HttpMessage;

public class PersistentXSSUtils {

    private static int uniqueIndex;
    public static String PXSS_PREFIX = "zApPX";
    public static String PXSS_POSTFIX = "sS";
    private static Map<String, UserDataSource> map;
    private static Map<String, HashSet<Integer>> sourceToSinks;
    /**
     * A {@code Map} to cache the URIs used by source messages ({@code UserDataSource}).
     *
     * <p>The URIs will be different {@code String} objects (see {@code URI#toString()}) while
     * representing the same URI. This happens for each parameter attacked per source message which
     * would lead to multiple duplicated {@code String}s.
     *
     * @see #getCachedItem(Map, String)
     * @see UserDataSource#UserDataSource(HttpMessage, String)
     * @see org.apache.commons.httpclient.URI#toString()
     */
    private static Map<String, String> cachedUris;
    /**
     * A {@code Map} to cache the parameter names used by source messages ({@code UserDataSource}).
     *
     * <p>The parameter names will be different {@code String} objects (see {@code Variant}
     * implementations) while representing the same parameter names. This happens for each parameter
     * attacked per source message which would lead to multiple duplicated {@code String}s.
     *
     * @see #getCachedItem(Map, String)
     * @see UserDataSource#UserDataSource(HttpMessage, String)
     * @see org.parosproxy.paros.core.scanner.Variant
     */
    private static Map<String, String> cachedParams;

    private static Logger log = Logger.getLogger(PersistentXSSUtils.class);

    static {
        reset();
    }

    public static String getUniqueValue(HttpMessage msg, String param) {
        String uniqueVal = PXSS_PREFIX + uniqueIndex++ + PXSS_POSTFIX;
        map.put(uniqueVal, new UserDataSource(msg, param));
        return uniqueVal;
    }

    public static void testForSink(HttpMessage msg) {
        String body = msg.getResponseBody().toString();
        int start = body.indexOf(PXSS_PREFIX);
        while (start > 0) {
            int end = body.indexOf(PXSS_POSTFIX, start);
            if (end > 0) {
                String uniqueVal = body.substring(start, end + PXSS_POSTFIX.length());
                UserDataSource source = map.get(uniqueVal);
                if (source != null) {
                    setSinkForSource(source, msg);
                }
                start = body.indexOf(PXSS_PREFIX, end);
            } else {
                break;
            }
        }
    }

    public static void setSinkForSource(HttpMessage sourceMsg, String param, HttpMessage sinkMsg) {
        setSinkForSource(new UserDataSource(sourceMsg, param), sinkMsg);
    }

    private static void setSinkForSource(UserDataSource source, HttpMessage sinkMsg) {
        if (log.isDebugEnabled()) {
            log.debug("setSinkForSource src=" + source.getUri() + " param=" + source.getParam() + " sink="
                    + sinkMsg.getRequestHeader().getURI());
        }
        HashSet<Integer> sinks = sourceToSinks.get(source.toString());
        if (sinks == null) {
            sinks = new HashSet<>();
        }
        try {
            HistoryReference hRef = new HistoryReference(Model.getSingleton().getSession(),
                    HistoryReference.TYPE_SCANNER_TEMPORARY, sinkMsg);
            sinks.add(Integer.valueOf(hRef.getHistoryId()));
            sourceToSinks.put(source.toString(), sinks);
        } catch (HttpMalformedHeaderException | DatabaseException e) {
            log.warn("Failed to persist HTTP message to database:", e);
        }
    }

    /**
     * Gets the IDs of the sink messages for the given message and parameter.
     *
     * @param sourceMsg the source message
     * @param param the parameter being tested
     * @return the IDs of the messages that match the given source message and parameter, {@code
     *     null} if no matches
     * @see #getMessage(int)
     */
    public static Set<Integer> getSinksIdsForSource(HttpMessage sourceMsg, String param) {
        UserDataSource source = new UserDataSource(sourceMsg, param);
        if (log.isDebugEnabled()) {
            log.debug("getSinksIdsForSource src=" + source.getUri() + " param=" + param + " sinks="
                    + sourceToSinks.get(source.toString()));
        }
        return sourceToSinks.get(source.toString());
    }

    /** Resets the state of {@code PersistentXSSUtils}. */
    @SuppressWarnings("unchecked")
    public static void reset() {
        uniqueIndex = 0;
        map = new HashMap<>();
        sourceToSinks = new HashMap<>();
        cachedUris = Collections.synchronizedMap(new ReferenceMap(ReferenceMap.SOFT, ReferenceMap.SOFT));
        cachedParams = Collections.synchronizedMap(new ReferenceMap(ReferenceMap.SOFT, ReferenceMap.SOFT));
    }

    /**
     * Gets the message with the given ID.
     *
     * @param sinkMsgId the ID of the message
     * @return the message with the given ID, or {@code null} if it was not possible to obtain the
     *     message
     * @see #getSinksIdsForSource(HttpMessage, String)
     */
    public static HttpMessage getMessage(int sinkMsgId) {
        try {
            return new HistoryReference(sinkMsgId).getHttpMessage();
        } catch (HttpMalformedHeaderException | DatabaseException e) {
            log.warn("Failed to read HTTP message from database:", e);
        }
        return null;
    }

    private static String getCachedItem(Map<String, String> map, String item) {
        String cachedItem = map.get(item);
        if (cachedItem != null) {
            return cachedItem;
        }
        map.put(item, item);
        return item;
    }

    private static class UserDataSource {

        private final String uri;
        private final String param;
        private final String stringRepresentation;

        public UserDataSource(HttpMessage sourceMsg, String param) {
            super();
            this.uri = getCachedItem(cachedUris, sourceMsg.getRequestHeader().getURI().toString());
            this.param = getCachedItem(cachedParams, param);
            this.stringRepresentation = uri + "#" + param;
        }

        @Override
        public String toString() {
            return stringRepresentation;
        }

        public String getUri() {
            return uri;
        }

        public String getParam() {
            return param;
        }
    }
}