James Moger
2014-09-07 f9c661ef5d2a422f246b3a089bee06470ae1d431
Merged #164 "Sanitize page parameters for XSS vulerabilities"
3 files added
33 files modified
512 ■■■■ changed files
.classpath 1 ●●●● patch | view | raw | blame | history
build.moxie 1 ●●●● patch | view | raw | blame | history
gitblit.iml 11 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/DaggerModule.java 11 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/FederationClient.java 5 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/MigrateTickets.java 5 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/ReindexTickets.java 5 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/GitblitManager.java 6 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/IRuntimeManager.java 8 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/RuntimeManager.java 21 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/JSoupXssFilter.java 87 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/XssFilter.java 64 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/GitBlitWebApp.java 14 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/GitblitParamUrlCodingStrategy.java 42 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/GitblitWicketApp.java 3 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/MarkupProcessor.java 30 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/SafeTextModel.java 96 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/WicketUtils.java 5 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/BlobPage.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/DocPage.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/DocsPage.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/EditTicketPage.java 8 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/NewTicketPage.java 8 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/RepositoryPage.java 3 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/SummaryPage.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/CommentPanel.java 5 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/MarkdownTextArea.java 6 ●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/AuthenticationManagerTest.java 5 ●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/BranchTicketServiceTest.java 6 ●●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/FileTicketServiceTest.java 6 ●●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/HtpasswdAuthenticationTest.java 8 ●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/LdapAuthenticationTest.java 8 ●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/LuceneExecutorTest.java 5 ●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/RedisTicketServiceTest.java 6 ●●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/RedmineAuthenticationTest.java 8 ●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/mock/MockRuntimeManager.java 7 ●●●●● patch | view | raw | blame | history
.classpath
@@ -77,6 +77,7 @@
    <classpathentry kind="lib" path="ext/commons-pool2-2.0.jar" sourcepath="ext/src/commons-pool2-2.0.jar" />
    <classpathentry kind="lib" path="ext/pf4j-0.8.0.jar" sourcepath="ext/src/pf4j-0.8.0.jar" />
    <classpathentry kind="lib" path="ext/tika-core-1.5.jar" sourcepath="ext/src/tika-core-1.5.jar" />
    <classpathentry kind="lib" path="ext/jsoup-1.7.3.jar" sourcepath="ext/src/jsoup-1.7.3.jar" />
    <classpathentry kind="lib" path="ext/junit-4.11.jar" sourcepath="ext/src/junit-4.11.jar" />
    <classpathentry kind="lib" path="ext/hamcrest-core-1.3.jar" sourcepath="ext/src/hamcrest-core-1.3.jar" />
    <classpathentry kind="lib" path="ext/selenium-java-2.28.0.jar" sourcepath="ext/src/selenium-java-2.28.0.jar" />
build.moxie
@@ -176,6 +176,7 @@
- compile 'redis.clients:jedis:2.3.1' :war
- compile 'ro.fortsoft.pf4j:pf4j:0.8.0' :war
- compile 'org.apache.tika:tika-core:1.5' :war
- compile 'org.jsoup:jsoup:1.7.3' :war
- test 'junit'
# Dependencies for Selenium web page testing
- test 'org.seleniumhq.selenium:selenium-java:${selenium.version}' @jar
gitblit.iml
@@ -801,6 +801,17 @@
        </SOURCES>
      </library>
    </orderEntry>
    <orderEntry type="module-library">
      <library name="jsoup-1.7.3.jar">
        <CLASSES>
          <root url="jar://$MODULE_DIR$/ext/jsoup-1.7.3.jar!/" />
        </CLASSES>
        <JAVADOC />
        <SOURCES>
          <root url="jar://$MODULE_DIR$/ext/src/jsoup-1.7.3.jar!/" />
        </SOURCES>
      </library>
    </orderEntry>
    <orderEntry type="module-library" scope="TEST">
      <library name="junit-4.11.jar">
        <CLASSES>
src/main/java/com/gitblit/DaggerModule.java
@@ -38,7 +38,9 @@
import com.gitblit.transport.ssh.IPublicKeyManager;
import com.gitblit.transport.ssh.MemoryKeyManager;
import com.gitblit.transport.ssh.NullKeyManager;
import com.gitblit.utils.JSoupXssFilter;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.XssFilter;
import com.gitblit.wicket.GitBlitWebApp;
import dagger.Module;
@@ -54,6 +56,7 @@
    library = true,
    injects = {
            IStoredSettings.class,
            XssFilter.class,
            // core managers
            IRuntimeManager.class,
@@ -79,8 +82,12 @@
        return new FileSettings();
    }
    @Provides @Singleton IRuntimeManager provideRuntimeManager(IStoredSettings settings) {
        return new RuntimeManager(settings);
    @Provides @Singleton XssFilter provideXssFilter() {
        return new JSoupXssFilter();
    }
    @Provides @Singleton IRuntimeManager provideRuntimeManager(IStoredSettings settings, XssFilter xssFilter) {
        return new RuntimeManager(settings, xssFilter);
    }
    @Provides @Singleton IPluginManager providePluginManager(IRuntimeManager runtimeManager) {
src/main/java/com/gitblit/FederationClient.java
@@ -36,6 +36,8 @@
import com.gitblit.service.FederationPullService;
import com.gitblit.utils.FederationUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.XssFilter;
import com.gitblit.utils.XssFilter.AllowXssFilter;
/**
 * Command-line client to pull federated Gitblit repositories.
@@ -92,7 +94,8 @@
        }
        // configure the Gitblit singleton for minimal, non-server operation
        RuntimeManager runtime = new RuntimeManager(settings, baseFolder).start();
        XssFilter xssFilter = new AllowXssFilter();
        RuntimeManager runtime = new RuntimeManager(settings, xssFilter, baseFolder).start();
        NoopNotificationManager notifications = new NoopNotificationManager().start();
        UserManager users = new UserManager(runtime, null).start();
        RepositoryManager repositories = new RepositoryManager(runtime, null, users).start();
src/main/java/com/gitblit/MigrateTickets.java
@@ -39,6 +39,8 @@
import com.gitblit.tickets.ITicketService;
import com.gitblit.tickets.RedisTicketService;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.XssFilter;
import com.gitblit.utils.XssFilter.AllowXssFilter;
/**
 * A command-line tool to move all tickets from one ticket service to another.
@@ -134,7 +136,8 @@
        settings.overrideSetting(Keys.web.activityCacheDays, 0);
        settings.overrideSetting(ITicketService.SETTING_UPDATE_DIFFSTATS, false);
        IRuntimeManager runtimeManager = new RuntimeManager(settings, baseFolder).start();
        XssFilter xssFilter = new AllowXssFilter();
        IRuntimeManager runtimeManager = new RuntimeManager(settings, xssFilter, baseFolder).start();
        IRepositoryManager repositoryManager = new RepositoryManager(runtimeManager, null, null).start();
        String inputServiceName = settings.getString(Keys.tickets.service, BranchTicketService.class.getSimpleName());
src/main/java/com/gitblit/ReindexTickets.java
@@ -33,6 +33,8 @@
import com.gitblit.tickets.ITicketService;
import com.gitblit.tickets.RedisTicketService;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.XssFilter;
import com.gitblit.utils.XssFilter.AllowXssFilter;
/**
 * A command-line tool to reindex all tickets in all repositories when the
@@ -126,7 +128,8 @@
        settings.overrideSetting(Keys.git.enableMirroring, false);
        settings.overrideSetting(Keys.web.activityCacheDays, 0);
        IRuntimeManager runtimeManager = new RuntimeManager(settings, baseFolder).start();
        XssFilter xssFilter = new AllowXssFilter();
        IRuntimeManager runtimeManager = new RuntimeManager(settings, xssFilter, baseFolder).start();
        IRepositoryManager repositoryManager = new RepositoryManager(runtimeManager, null, null).start();
        String serviceName = settings.getString(Keys.tickets.service, BranchTicketService.class.getSimpleName());
src/main/java/com/gitblit/manager/GitblitManager.java
@@ -79,6 +79,7 @@
import com.gitblit.transport.ssh.IPublicKeyManager;
import com.gitblit.transport.ssh.SshKey;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.XssFilter;
import com.gitblit.utils.HttpUtils;
import com.gitblit.utils.JsonUtils;
import com.gitblit.utils.ObjectCache;
@@ -663,6 +664,11 @@
        return runtimeManager.getStatus();
    }
    @Override
    public XssFilter getXssFilter() {
        return runtimeManager.getXssFilter();
    }
    /*
     * NOTIFICATION MANAGER
     */
src/main/java/com/gitblit/manager/IRuntimeManager.java
@@ -24,6 +24,7 @@
import com.gitblit.IStoredSettings;
import com.gitblit.models.ServerSettings;
import com.gitblit.models.ServerStatus;
import com.gitblit.utils.XssFilter;
public interface IRuntimeManager extends IManager {
@@ -151,4 +152,11 @@
      * @since 1.4.0
     */
    boolean updateSettings(Map<String, String> updatedSettings);
    /**
     * Returns the HTML sanitizer used to clean user content.
     *
     * @return the HTML sanitizer
     */
    XssFilter getXssFilter();
}
src/main/java/com/gitblit/manager/RuntimeManager.java
@@ -32,12 +32,15 @@
import com.gitblit.models.ServerStatus;
import com.gitblit.models.SettingModel;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.XssFilter;
public class RuntimeManager implements IRuntimeManager {
    private final Logger logger = LoggerFactory.getLogger(getClass());
    private final IStoredSettings settings;
    private final XssFilter xssFilter;
    private final ServerStatus serverStatus;
@@ -47,14 +50,15 @@
    private TimeZone timezone;
    public RuntimeManager(IStoredSettings settings) {
        this(settings, null);
    public RuntimeManager(IStoredSettings settings, XssFilter xssFilter) {
        this(settings, xssFilter, null);
    }
    public RuntimeManager(IStoredSettings settings, File baseFolder) {
    public RuntimeManager(IStoredSettings settings, XssFilter xssFilter, File baseFolder) {
        this.settings = settings;
        this.settingsModel = new ServerSettings();
        this.serverStatus = new ServerStatus();
        this.xssFilter = xssFilter;
        this.baseFolder = baseFolder == null ? new File("") : baseFolder;
    }
@@ -262,4 +266,15 @@
        serverStatus.heapFree = Runtime.getRuntime().freeMemory();
        return serverStatus;
    }
    /**
     * Returns the XSS filter.
     *
     * @return the XSS filter
     */
    @Override
    public XssFilter getXssFilter() {
        return xssFilter;
    }
}
src/main/java/com/gitblit/utils/JSoupXssFilter.java
New file
@@ -0,0 +1,87 @@
/*
 * Copyright 2014 gitblit.com.
 *
 * 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 com.gitblit.utils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.safety.Cleaner;
import org.jsoup.safety.Whitelist;
/**
 * Implementation of an XSS filter based on JSoup.
 *
 * @author James Moger
 *
 */
public class JSoupXssFilter implements XssFilter {
     private final Cleaner none;
     private final Cleaner relaxed;
     public JSoupXssFilter() {
         none = new Cleaner(Whitelist.none());
         relaxed = new Cleaner(getRelaxedWhiteList());
    }
    @Override
    public String none(String input) {
        return clean(input, none);
    }
    @Override
    public String relaxed(String input) {
        return clean(input, relaxed);
    }
    protected String clean(String input, Cleaner cleaner) {
        Document unsafe = Jsoup.parse(input);
        Document safe = cleaner.clean(unsafe);
        return safe.body().html();
    }
    /**
     * Builds & returns a loose HTML whitelist similar to Github.
     *
     * https://github.com/github/markup/tree/master#html-sanitization
     * @return a loose HTML whitelist
     */
    protected Whitelist getRelaxedWhiteList() {
        return new Whitelist()
        .addTags(
                "a", "b", "blockquote", "br", "caption", "cite", "code", "col",
                "colgroup", "dd", "del", "div", "dl", "dt", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr",
                "i", "img", "ins", "kbd", "li", "ol", "p", "pre", "q", "samp", "small", "strike", "strong",
                "sub", "sup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "tt", "u",
                "ul", "var")
        .addAttributes("a", "href", "title")
        .addAttributes("blockquote", "cite")
        .addAttributes("col", "span", "width")
        .addAttributes("colgroup", "span", "width")
        .addAttributes("img", "align", "alt", "height", "src", "title", "width")
        .addAttributes("ol", "start", "type")
        .addAttributes("q", "cite")
        .addAttributes("table", "summary", "width")
        .addAttributes("td", "abbr", "axis", "colspan", "rowspan", "width")
        .addAttributes("th", "abbr", "axis", "colspan", "rowspan", "scope", "width")
        .addAttributes("ul", "type")
        .addEnforcedAttribute("a", "rel", "nofollow")
        ;
    }
}
src/main/java/com/gitblit/utils/XssFilter.java
New file
@@ -0,0 +1,64 @@
/*
 * Copyright 2014 gitblit.com.
 *
 * 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 com.gitblit.utils;
/**
 * Defines the contract for an XSS filter implementation.
 *
 * @author James Moger
 *
 */
public interface XssFilter {
    /**
     * Returns a filtered version of the input value that contains no html
     * elements.
     *
     * @param input
     * @return a plain text value
     */
    String none(String input);
    /**
     * Returns a filtered version of the input that contains structural html
     * elements.
     *
     * @param input
     * @return a filtered html value
     */
    String relaxed(String input);
    /**
     * A NOOP XSS filter.
     *
     * @author James Moger
     *
     */
    public class AllowXssFilter implements XssFilter {
        @Override
        public String none(String input) {
            return input;
        }
        @Override
        public String relaxed(String input) {
            return input;
        }
    }
}
src/main/java/com/gitblit/wicket/GitBlitWebApp.java
@@ -46,6 +46,7 @@
import com.gitblit.manager.IUserManager;
import com.gitblit.tickets.ITicketService;
import com.gitblit.transport.ssh.IPublicKeyManager;
import com.gitblit.utils.XssFilter;
import com.gitblit.wicket.pages.ActivityPage;
import com.gitblit.wicket.pages.BlamePage;
import com.gitblit.wicket.pages.BlobDiffPage;
@@ -100,6 +101,8 @@
    private final IStoredSettings settings;
    private final XssFilter xssFilter;
    private final IRuntimeManager runtimeManager;
    private final IPluginManager pluginManager;
@@ -134,6 +137,7 @@
        super();
        this.settings = runtimeManager.getSettings();
        this.xssFilter = runtimeManager.getXssFilter();
        this.runtimeManager = runtimeManager;
        this.pluginManager = pluginManager;
        this.notificationManager = notificationManager;
@@ -251,7 +255,7 @@
        if (!settings.getBoolean(Keys.web.mountParameters, true)) {
            parameters = new String[] {};
        }
        mount(new GitblitParamUrlCodingStrategy(settings, location, clazz, parameters));
        mount(new GitblitParamUrlCodingStrategy(settings, xssFilter, location, clazz, parameters));
        // map the mount point to the cache control definition
        if (clazz.isAnnotationPresent(CacheControl.class)) {
@@ -308,6 +312,14 @@
    }
    /* (non-Javadoc)
     * @see com.gitblit.wicket.Webapp#xssFilter()
     */
    @Override
    public XssFilter xssFilter() {
        return xssFilter;
    }
    /* (non-Javadoc)
     * @see com.gitblit.wicket.Webapp#isDebugMode()
     */
    @Override
src/main/java/com/gitblit/wicket/GitblitParamUrlCodingStrategy.java
@@ -15,13 +15,13 @@
 */
package com.gitblit.wicket;
import java.text.MessageFormat;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.wicket.IRequestTarget;
import org.apache.wicket.Page;
import org.apache.wicket.protocol.http.request.WebRequestCodingStrategy;
import org.apache.wicket.request.RequestParameters;
import org.apache.wicket.request.target.coding.MixedParamUrlCodingStrategy;
import org.apache.wicket.util.string.AppendingStringBuffer;
@@ -30,6 +30,7 @@
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
import com.gitblit.utils.XssFilter;
/**
 * Simple subclass of mixed parameter url coding strategy that works around the
@@ -49,6 +50,8 @@
    private IStoredSettings settings;
    private XssFilter xssFilter;
    /**
     * Construct.
     *
@@ -62,12 +65,14 @@
     */
    public <C extends Page> GitblitParamUrlCodingStrategy(
            IStoredSettings settings,
            XssFilter xssFilter,
            String mountPath,
            Class<C> bookmarkablePageClass, String[] parameterNames) {
        super(mountPath, bookmarkablePageClass, parameterNames);
        this.parameterNames = parameterNames;
        this.settings = settings;
        this.xssFilter = xssFilter;
    }
    /**
@@ -111,10 +116,37 @@
     */
    @Override
    public IRequestTarget decode(RequestParameters requestParameters) {
        final String parametersFragment = requestParameters.getPath().substring(
                getMountPath().length());
        logger.debug(MessageFormat
                .format("REQ: {0} PARAMS {1}", getMountPath(), parametersFragment));
        Map<String, Object> parameterMap = (Map<String, Object>) requestParameters.getParameters();
        for (Map.Entry<String, Object> entry : parameterMap.entrySet()) {
            String parameter = entry.getKey();
            if (parameter.startsWith(WebRequestCodingStrategy.NAME_SPACE)) {
                // ignore Wicket parameters
                continue;
            }
            // sanitize Gitblit request parameters
            Object o = entry.getValue();
            if (o instanceof String) {
                String value = o.toString();
                String safeValue = xssFilter.none(value);
                if (!value.equals(safeValue)) {
                    logger.warn("XSS filter triggered on {} URL parameter: {}={}",
                            getMountPath(), parameter, value);
                    parameterMap.put(parameter, safeValue);
                }
            } else if (o instanceof String[]) {
                String[] values = (String[]) o;
                for (int i = 0; i < values.length; i++) {
                    String value = values[i].toString();
                    String safeValue = xssFilter.none(value);
                    if (!value.equals(safeValue)) {
                        logger.warn("XSS filter triggered on {} URL parameter: {}={}",
                                getMountPath(), parameter, value);
                        values[i] = safeValue;
                    }
                }
            }
        }
        return super.decode(requestParameters);
    }
src/main/java/com/gitblit/wicket/GitblitWicketApp.java
@@ -17,6 +17,7 @@
import com.gitblit.manager.IUserManager;
import com.gitblit.tickets.ITicketService;
import com.gitblit.transport.ssh.IPublicKeyManager;
import com.gitblit.utils.XssFilter;
public interface GitblitWicketApp {
@@ -30,6 +31,8 @@
    public abstract IStoredSettings settings();
    public abstract XssFilter xssFilter();
    /**
     * Is Gitblit running in debug mode?
     *
src/main/java/com/gitblit/wicket/MarkupProcessor.java
@@ -60,6 +60,7 @@
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.MarkdownUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.XssFilter;
import com.gitblit.wicket.pages.DocPage;
import com.google.common.base.Joiner;
@@ -80,11 +81,9 @@
    private final IStoredSettings settings;
    public MarkupProcessor(IStoredSettings settings) {
        this.settings = settings;
    }
    private final XssFilter xssFilter;
    public List<String> getMarkupExtensions() {
    public static List<String> getMarkupExtensions(IStoredSettings settings) {
        List<String> list = new ArrayList<String>();
        list.addAll(settings.getStrings(Keys.web.confluenceExtensions));
        list.addAll(settings.getStrings(Keys.web.markdownExtensions));
@@ -95,8 +94,17 @@
        return list;
    }
    public MarkupProcessor(IStoredSettings settings, XssFilter xssFilter) {
        this.settings = settings;
        this.xssFilter = xssFilter;
    }
    public List<String> getMarkupExtensions() {
        return getMarkupExtensions(settings);
    }
    public List<String> getAllExtensions() {
        List<String> list = getMarkupExtensions();
        List<String> list = getMarkupExtensions(settings);
        list.add("txt");
        list.add("TXT");
        return list;
@@ -295,7 +303,11 @@
        MarkupParser parser = new MarkupParser(lang);
        parser.setBuilder(builder);
        parser.parse(doc.markup);
        doc.html = writer.toString();
        final String content = writer.toString();
        final String safeContent = xssFilter.relaxed(content);
        doc.html = safeContent;
    }
    /**
@@ -345,7 +357,11 @@
                return new Rendering(url, name);
            }
        };
        doc.html = MarkdownUtils.transformMarkdown(doc.markup, renderer);
        final String content = MarkdownUtils.transformMarkdown(doc.markup, renderer);
        final String safeContent = xssFilter.relaxed(content);
        doc.html = safeContent;
    }
    private String getWicketUrl(Class<? extends Page> pageClass, final String repositoryName, final String commitId, final String document) {
src/main/java/com/gitblit/wicket/SafeTextModel.java
New file
@@ -0,0 +1,96 @@
package com.gitblit.wicket;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.util.lang.Objects;
import org.parboiled.common.StringUtils;
import org.slf4j.LoggerFactory;
public class SafeTextModel implements IModel<String> {
    private static final long serialVersionUID = 1L;
    public enum Mode {
        relaxed, none
    }
    private final Mode mode;
    private String value;
    public static SafeTextModel none() {
        return new SafeTextModel(Mode.none);
    }
    public static SafeTextModel none(String value) {
        return new SafeTextModel(Mode.none);
    }
    public static SafeTextModel relaxed() {
        return new SafeTextModel(Mode.relaxed);
    }
    public static SafeTextModel relaxed(String value) {
        return new SafeTextModel(Mode.relaxed);
    }
    public SafeTextModel(Mode mode) {
        this.mode = mode;
    }
    public SafeTextModel(String value, Mode mode) {
        this.value = value;
        this.mode = mode;
    }
    @Override
    public void detach() {
    }
    @Override
    public String getObject() {
        if (StringUtils.isEmpty(value)) {
            return value;
        }
        String safeValue;
        switch (mode) {
        case none:
            safeValue = GitBlitWebApp.get().xssFilter().none(value);
            break;
        default:
            safeValue = GitBlitWebApp.get().xssFilter().relaxed(value);
            break;
        }
        if (!value.equals(safeValue)) {
            LoggerFactory.getLogger(getClass()).warn("XSS filter trigggered on suspicious form field value {}",
                    value);
        }
        return safeValue;
    }
    @Override
    public void setObject(String input) {
        this.value = input;
    }
    @Override
    public int hashCode()
    {
        return Objects.hashCode(value);
    }
    @Override
    public boolean equals(Object obj)
    {
        if (this == obj)
        {
            return true;
        }
        if (!(obj instanceof Model<?>))
        {
            return false;
        }
        Model<?> that = (Model<?>)obj;
        return Objects.equal(value, that.getObject());
    }
}
src/main/java/com/gitblit/wicket/WicketUtils.java
@@ -42,6 +42,7 @@
import com.gitblit.Constants;
import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.FederationPullStatus;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
import com.gitblit.models.FederationModel;
import com.gitblit.models.Metric;
@@ -186,9 +187,9 @@
            return newImage(wicketId, "file_settings_16x16.png");
        }
        MarkupProcessor processor = new MarkupProcessor(GitBlitWebApp.get().settings());
        String ext = StringUtils.getFileExtension(filename).toLowerCase();
        if (processor.getMarkupExtensions().contains(ext)) {
        IStoredSettings settings = GitBlitWebApp.get().settings();
        if (MarkupProcessor.getMarkupExtensions(settings).contains(ext)) {
            return newImage(wicketId, "file_world_16x16.png");
        }
        return newImage(wicketId, "file_16x16.png");
src/main/java/com/gitblit/wicket/pages/BlobPage.java
@@ -79,7 +79,7 @@
            }
            // see if we should redirect to the doc page
            MarkupProcessor processor = new MarkupProcessor(app().settings());
            MarkupProcessor processor = new MarkupProcessor(app().settings(), app().xssFilter());
            for (String ext : processor.getMarkupExtensions()) {
                if (ext.equals(extension)) {
                    setResponsePage(DocPage.class, params);
src/main/java/com/gitblit/wicket/pages/DocPage.java
@@ -43,7 +43,7 @@
        super(params);
        final String path = WicketUtils.getPath(params).replace("%2f", "/").replace("%2F", "/");
        MarkupProcessor processor = new MarkupProcessor(app().settings());
        MarkupProcessor processor = new MarkupProcessor(app().settings(), app().xssFilter());
        Repository r = getRepository();
        RevCommit commit = JGitUtils.getCommit(r, objectId);
src/main/java/com/gitblit/wicket/pages/DocsPage.java
@@ -49,7 +49,7 @@
    public DocsPage(PageParameters params) {
        super(params);
        MarkupProcessor processor = new MarkupProcessor(app().settings());
        MarkupProcessor processor = new MarkupProcessor(app().settings(), app().xssFilter());
        Repository r = getRepository();
        RevCommit head = JGitUtils.getCommit(r, null);
src/main/java/com/gitblit/wicket/pages/EditTicketPage.java
@@ -50,6 +50,8 @@
import com.gitblit.tickets.TicketResponsible;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.SafeTextModel;
import com.gitblit.wicket.SafeTextModel.Mode;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.MarkdownTextArea;
@@ -110,8 +112,8 @@
        }
        typeModel = Model.of(ticket.type);
        titleModel = Model.of(ticket.title);
        topicModel = Model.of(ticket.topic == null ? "" : ticket.topic);
        titleModel = SafeTextModel.none(ticket.title);
        topicModel = SafeTextModel.none(ticket.topic == null ? "" : ticket.topic);
        responsibleModel = Model.of();
        milestoneModel = Model.of();
        mergeToModel = Model.of(ticket.mergeTo == null ? getRepositoryModel().mergeTo : ticket.mergeTo);
@@ -134,7 +136,7 @@
        form.add(new TextField<String>("title", titleModel));
        form.add(new TextField<String>("topic", topicModel));
        final IModel<String> markdownPreviewModel = new Model<String>();
        final SafeTextModel markdownPreviewModel = new SafeTextModel(Mode.none);
        descriptionPreview = new Label("descriptionPreview", markdownPreviewModel);
        descriptionPreview.setEscapeModelStrings(false);
        descriptionPreview.setOutputMarkupId(true);
src/main/java/com/gitblit/wicket/pages/NewTicketPage.java
@@ -46,6 +46,8 @@
import com.gitblit.tickets.TicketResponsible;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.SafeTextModel;
import com.gitblit.wicket.SafeTextModel.Mode;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.MarkdownTextArea;
@@ -87,8 +89,8 @@
        }
        typeModel = Model.of(TicketModel.Type.defaultType);
        titleModel = Model.of();
        topicModel = Model.of();
        titleModel = SafeTextModel.none();
        topicModel = SafeTextModel.none();
        mergeToModel = Model.of(Repository.shortenRefName(getRepositoryModel().mergeTo));
        responsibleModel = Model.of();
        milestoneModel = Model.of();
@@ -103,7 +105,7 @@
        form.add(new TextField<String>("title", titleModel));
        form.add(new TextField<String>("topic", topicModel));
        final IModel<String> markdownPreviewModel = new Model<String>();
        final SafeTextModel markdownPreviewModel = new SafeTextModel(Mode.none);
        descriptionPreview = new Label("descriptionPreview", markdownPreviewModel);
        descriptionPreview.setEscapeModelStrings(false);
        descriptionPreview.setOutputMarkupId(true);
src/main/java/com/gitblit/wicket/pages/RepositoryPage.java
@@ -550,7 +550,8 @@
        String html;
        switch (model.commitMessageRenderer) {
        case MARKDOWN:
            html = MessageFormat.format("<div class='commit_message'>{0}</div>", content);
            String safeContent = app().xssFilter().relaxed(content);
            html = MessageFormat.format("<div class='commit_message'>{0}</div>", safeContent);
            break;
        default:
            html = MessageFormat.format("<pre class='commit_message'>{0}</pre>", content);
src/main/java/com/gitblit/wicket/pages/SummaryPage.java
@@ -138,7 +138,7 @@
            MarkupDocument markupDoc = null;
            RevCommit head = JGitUtils.getCommit(r, null);
            if (head != null) {
                MarkupProcessor processor = new MarkupProcessor(app().settings());
                MarkupProcessor processor = new MarkupProcessor(app().settings(), app().xssFilter());
                markupDoc = processor.getReadme(r, repositoryName, getBestCommitId(head));
            }
            if (markupDoc == null || markupDoc.markup == null) {
src/main/java/com/gitblit/wicket/panels/CommentPanel.java
@@ -19,13 +19,14 @@
import org.apache.wicket.ajax.markup.html.form.AjaxButton;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.TicketModel;
import com.gitblit.models.TicketModel.Change;
import com.gitblit.models.UserModel;
import com.gitblit.wicket.SafeTextModel;
import com.gitblit.wicket.SafeTextModel.Mode;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.pages.BasePage;
@@ -89,7 +90,7 @@
            }
        }.setVisible(ticket != null && ticket.number > 0));
        final IModel<String> markdownPreviewModel = new Model<String>();
        final SafeTextModel markdownPreviewModel = new SafeTextModel(Mode.none);
        markdownPreview = new Label("markdownPreview", markdownPreviewModel);
        markdownPreview.setEscapeModelStrings(false);
        markdownPreview.setOutputMarkupId(true);
src/main/java/com/gitblit/wicket/panels/MarkdownTextArea.java
@@ -20,12 +20,12 @@
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.TextArea;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.util.time.Duration;
import com.gitblit.utils.MarkdownUtils;
import com.gitblit.wicket.GitBlitWebApp;
import com.gitblit.wicket.SafeTextModel;
public class MarkdownTextArea extends TextArea {
@@ -35,7 +35,7 @@
    protected String text = "";
    public MarkdownTextArea(String id, final IModel<String> previewModel, final Label previewLabel) {
    public MarkdownTextArea(String id, final SafeTextModel previewModel, final Label previewLabel) {
        super(id);
        setModel(new PropertyModel(this, "text"));
        add(new AjaxFormComponentUpdatingBehavior("onblur") {
@@ -65,7 +65,7 @@
        setOutputMarkupId(true);
    }
    protected void renderPreview(IModel<String> previewModel) {
    protected void renderPreview(SafeTextModel previewModel) {
        if (text == null) {
            return;
        }
src/test/java/com/gitblit/tests/AuthenticationManagerTest.java
@@ -26,6 +26,8 @@
import com.gitblit.manager.UserManager;
import com.gitblit.models.UserModel;
import com.gitblit.tests.mock.MemorySettings;
import com.gitblit.utils.XssFilter;
import com.gitblit.utils.XssFilter.AllowXssFilter;
/**
 * Class for testing local authentication.
@@ -42,7 +44,8 @@
    }
    IAuthenticationManager newAuthenticationManager() {
        RuntimeManager runtime = new RuntimeManager(getSettings(), GitBlitSuite.BASEFOLDER).start();
        XssFilter xssFilter = new AllowXssFilter();
        RuntimeManager runtime = new RuntimeManager(getSettings(), xssFilter, GitBlitSuite.BASEFOLDER).start();
        users = new UserManager(runtime, null).start();
        AuthenticationManager auth = new AuthenticationManager(runtime, users).start();
        return auth;
src/test/java/com/gitblit/tests/BranchTicketServiceTest.java
@@ -29,6 +29,8 @@
import com.gitblit.models.RepositoryModel;
import com.gitblit.tickets.BranchTicketService;
import com.gitblit.tickets.ITicketService;
import com.gitblit.utils.XssFilter;
import com.gitblit.utils.XssFilter.AllowXssFilter;
/**
 * Tests the branch ticket service.
@@ -50,8 +52,8 @@
    protected ITicketService getService(boolean deleteAll) throws Exception {
        IStoredSettings settings = getSettings(deleteAll);
        IRuntimeManager runtimeManager = new RuntimeManager(settings).start();
        XssFilter xssFilter = new AllowXssFilter();
        IRuntimeManager runtimeManager = new RuntimeManager(settings, xssFilter).start();
        IPluginManager pluginManager = new PluginManager(runtimeManager).start();
        INotificationManager notificationManager = new NotificationManager(settings).start();
        IUserManager userManager = new UserManager(runtimeManager, pluginManager).start();
src/test/java/com/gitblit/tests/FileTicketServiceTest.java
@@ -29,6 +29,8 @@
import com.gitblit.models.RepositoryModel;
import com.gitblit.tickets.FileTicketService;
import com.gitblit.tickets.ITicketService;
import com.gitblit.utils.XssFilter;
import com.gitblit.utils.XssFilter.AllowXssFilter;
/**
 * Tests the file ticket service.
@@ -49,8 +51,8 @@
    protected ITicketService getService(boolean deleteAll) throws Exception {
        IStoredSettings settings = getSettings(deleteAll);
        IRuntimeManager runtimeManager = new RuntimeManager(settings).start();
        XssFilter xssFilter = new AllowXssFilter();
        IRuntimeManager runtimeManager = new RuntimeManager(settings, xssFilter).start();
        IPluginManager pluginManager = new PluginManager(runtimeManager).start();
        INotificationManager notificationManager = new NotificationManager(settings).start();
        IUserManager userManager = new UserManager(runtimeManager, pluginManager).start();
src/test/java/com/gitblit/tests/HtpasswdAuthenticationTest.java
@@ -32,6 +32,8 @@
import com.gitblit.manager.UserManager;
import com.gitblit.models.UserModel;
import com.gitblit.tests.mock.MemorySettings;
import com.gitblit.utils.XssFilter;
import com.gitblit.utils.XssFilter.AllowXssFilter;
/**
 * Test the Htpasswd user service.
@@ -74,7 +76,8 @@
    }
    private HtpasswdAuthProvider newHtpasswdAuthentication(IStoredSettings settings) {
        RuntimeManager runtime = new RuntimeManager(settings, GitBlitSuite.BASEFOLDER).start();
        XssFilter xssFilter = new AllowXssFilter();
        RuntimeManager runtime = new RuntimeManager(settings, xssFilter, GitBlitSuite.BASEFOLDER).start();
        UserManager users = new UserManager(runtime, null).start();
        HtpasswdAuthProvider htpasswd = new HtpasswdAuthProvider();
        htpasswd.setup(runtime, users);
@@ -82,7 +85,8 @@
    }
    private AuthenticationManager newAuthenticationManager(IStoredSettings settings) {
        RuntimeManager runtime = new RuntimeManager(settings, GitBlitSuite.BASEFOLDER).start();
        XssFilter xssFilter = new AllowXssFilter();
        RuntimeManager runtime = new RuntimeManager(settings, xssFilter, GitBlitSuite.BASEFOLDER).start();
        UserManager users = new UserManager(runtime, null).start();
        HtpasswdAuthProvider htpasswd = new HtpasswdAuthProvider();
        htpasswd.setup(runtime, users);
src/test/java/com/gitblit/tests/LdapAuthenticationTest.java
@@ -39,6 +39,8 @@
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.tests.mock.MemorySettings;
import com.gitblit.utils.XssFilter;
import com.gitblit.utils.XssFilter.AllowXssFilter;
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
@@ -96,7 +98,8 @@
    }
    private LdapAuthProvider newLdapAuthentication(IStoredSettings settings) {
        RuntimeManager runtime = new RuntimeManager(settings, GitBlitSuite.BASEFOLDER).start();
        XssFilter xssFilter = new AllowXssFilter();
        RuntimeManager runtime = new RuntimeManager(settings, xssFilter, GitBlitSuite.BASEFOLDER).start();
        userManager = new UserManager(runtime, null).start();
        LdapAuthProvider ldap = new LdapAuthProvider();
        ldap.setup(runtime, userManager);
@@ -104,7 +107,8 @@
    }
    private AuthenticationManager newAuthenticationManager(IStoredSettings settings) {
        RuntimeManager runtime = new RuntimeManager(settings, GitBlitSuite.BASEFOLDER).start();
        XssFilter xssFilter = new AllowXssFilter();
        RuntimeManager runtime = new RuntimeManager(settings, xssFilter, GitBlitSuite.BASEFOLDER).start();
        AuthenticationManager auth = new AuthenticationManager(runtime, userManager);
        auth.addAuthenticationProvider(newLdapAuthentication(settings));
        return auth;
src/test/java/com/gitblit/tests/LuceneExecutorTest.java
@@ -34,6 +34,8 @@
import com.gitblit.tests.mock.MemorySettings;
import com.gitblit.utils.FileUtils;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.XssFilter;
import com.gitblit.utils.XssFilter.AllowXssFilter;
/**
 * Tests Lucene indexing and querying.
@@ -48,7 +50,8 @@
    private LuceneService newLuceneExecutor() {
        MemorySettings settings = new MemorySettings();
        settings.put(Keys.git.repositoriesFolder, GitBlitSuite.REPOSITORIES);
        RuntimeManager runtime = new RuntimeManager(settings, GitBlitSuite.BASEFOLDER).start();
        XssFilter xssFilter = new AllowXssFilter();
        RuntimeManager runtime = new RuntimeManager(settings, xssFilter, GitBlitSuite.BASEFOLDER).start();
        UserManager users = new UserManager(runtime, null).start();
        RepositoryManager repos = new RepositoryManager(runtime, null, users);
        return new LuceneService(settings, repos);
src/test/java/com/gitblit/tests/RedisTicketServiceTest.java
@@ -30,6 +30,8 @@
import com.gitblit.models.RepositoryModel;
import com.gitblit.tickets.ITicketService;
import com.gitblit.tickets.RedisTicketService;
import com.gitblit.utils.XssFilter;
import com.gitblit.utils.XssFilter.AllowXssFilter;
/**
 * Tests the Redis ticket service.
@@ -57,8 +59,8 @@
    protected ITicketService getService(boolean deleteAll) throws Exception {
        IStoredSettings settings = getSettings(deleteAll);
        IRuntimeManager runtimeManager = new RuntimeManager(settings).start();
        XssFilter xssFilter = new AllowXssFilter();
        IRuntimeManager runtimeManager = new RuntimeManager(settings, xssFilter).start();
        IPluginManager pluginManager = new PluginManager(runtimeManager).start();
        INotificationManager notificationManager = new NotificationManager(settings).start();
        IUserManager userManager = new UserManager(runtimeManager, pluginManager).start();
src/test/java/com/gitblit/tests/RedmineAuthenticationTest.java
@@ -13,6 +13,8 @@
import com.gitblit.manager.UserManager;
import com.gitblit.models.UserModel;
import com.gitblit.tests.mock.MemorySettings;
import com.gitblit.utils.XssFilter;
import com.gitblit.utils.XssFilter.AllowXssFilter;
public class RedmineAuthenticationTest extends GitblitUnitTest {
@@ -25,7 +27,8 @@
    }
    RedmineAuthProvider newRedmineAuthentication(IStoredSettings settings) {
        RuntimeManager runtime = new RuntimeManager(settings, GitBlitSuite.BASEFOLDER).start();
        XssFilter xssFilter = new AllowXssFilter();
        RuntimeManager runtime = new RuntimeManager(settings, xssFilter, GitBlitSuite.BASEFOLDER).start();
        UserManager users = new UserManager(runtime, null).start();
        RedmineAuthProvider redmine = new RedmineAuthProvider();
        redmine.setup(runtime, users);
@@ -37,7 +40,8 @@
    }
    AuthenticationManager newAuthenticationManager() {
        RuntimeManager runtime = new RuntimeManager(getSettings(), GitBlitSuite.BASEFOLDER).start();
        XssFilter xssFilter = new AllowXssFilter();
        RuntimeManager runtime = new RuntimeManager(getSettings(), xssFilter, GitBlitSuite.BASEFOLDER).start();
        UserManager users = new UserManager(runtime, null).start();
        RedmineAuthProvider redmine = new RedmineAuthProvider();
        redmine.setup(runtime, users);
src/test/java/com/gitblit/tests/mock/MockRuntimeManager.java
@@ -28,6 +28,8 @@
import com.gitblit.models.ServerSettings;
import com.gitblit.models.ServerStatus;
import com.gitblit.models.SettingModel;
import com.gitblit.utils.XssFilter;
import com.gitblit.utils.XssFilter.AllowXssFilter;
public class MockRuntimeManager implements IRuntimeManager {
@@ -148,6 +150,11 @@
    }
    @Override
    public XssFilter getXssFilter() {
        return new AllowXssFilter();
    }
    @Override
    public boolean updateSettings(Map<String, String> updatedSettings) {
        return settings.saveSettings(updatedSettings);
    }