001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2022, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.core.net;
015
016import java.io.IOException;
017import java.io.InputStream;
018import java.io.InvalidClassException;
019import java.io.ObjectInputFilter;
020import java.io.ObjectInputStream;
021import java.io.ObjectStreamClass;
022import java.util.ArrayList;
023import java.util.List;
024
025/**
026 * HardenedObjectInputStream restricts the set of classes that can be
027 * deserialized to a set of explicitly whitelisted classes. This prevents
028 * certain type of attacks from being successful.
029 * 
030 * <p>
031 * It is assumed that classes in the "java.lang" and "java.util" packages are
032 * always authorized.
033 * </p>
034 * 
035 * @author Ceki G&uuml;lc&uuml;
036 * @since 1.2.0
037 */
038public class HardenedObjectInputStream extends ObjectInputStream {
039
040    final private List<String> whitelistedClassNames;
041    final private static String[] JAVA_PACKAGES = new String[] { "java.lang", "java.util" };
042    final private static int DEPTH_LIMIT = 16;
043    final private static int ARRAY_LIMIT = 10000;
044
045    public HardenedObjectInputStream(InputStream in, String[] whitelist) throws IOException {
046        super(in);
047        this.initObjectFilter();
048        this.whitelistedClassNames = new ArrayList<String>();
049        if (whitelist != null) {
050            for (int i = 0; i < whitelist.length; i++) {
051                this.whitelistedClassNames.add(whitelist[i]);
052            }
053        }
054    }
055
056    private void initObjectFilter() {
057        this.setObjectInputFilter(ObjectInputFilter.Config.createFilter(
058                "maxarray=" + ARRAY_LIMIT + ";maxdepth=" + DEPTH_LIMIT + ";"
059        ));
060    }
061    public HardenedObjectInputStream(InputStream in, List<String> whitelist) throws IOException {
062        super(in);
063        this.initObjectFilter();
064        this.whitelistedClassNames = new ArrayList<String>();
065        this.whitelistedClassNames.addAll(whitelist);
066    }
067
068    @Override
069    protected Class<?> resolveClass(ObjectStreamClass anObjectStreamClass) throws IOException, ClassNotFoundException {
070
071        String incomingClassName = anObjectStreamClass.getName();
072
073        if (!isWhitelisted(incomingClassName)) {
074            throw new InvalidClassException("Unauthorized deserialization attempt", anObjectStreamClass.getName());
075        }
076
077        return super.resolveClass(anObjectStreamClass);
078    }
079
080    private boolean isWhitelisted(String incomingClassName) {
081        for (int i = 0; i < JAVA_PACKAGES.length; i++) {
082            if (incomingClassName.startsWith(JAVA_PACKAGES[i]))
083                return true;
084        }
085        for (String whiteListed : whitelistedClassNames) {
086            if (incomingClassName.equals(whiteListed))
087                return true;
088        }
089        return false;
090    }
091
092    protected void addToWhitelist(List<String> additionalAuthorizedClasses) {
093        whitelistedClassNames.addAll(additionalAuthorizedClasses);
094    }
095}