All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.h3xstream.findsecbugs.serial.DeserializationGadgetDetector Maven / Gradle / Ivy

Go to download

Core module of the project. It include all the FindBugs detectors. The resulting jar is the published plugin.

There is a newer version: 1.13.0
Show newest version
/**
 * Find Security Bugs
 * Copyright (c) Philippe Arteau, All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3.0 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.
 */
package com.h3xstream.findsecbugs.serial;

import com.h3xstream.findsecbugs.common.ByteCode;
import com.h3xstream.findsecbugs.common.InterfaceUtils;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.Detector;
import edu.umd.cs.findbugs.Priorities;
import edu.umd.cs.findbugs.ba.*;
import org.apache.bcel.classfile.*;
import org.apache.bcel.classfile.Constant;
import org.apache.bcel.classfile.ConstantUtf8;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InvokeInstruction;
import org.apache.bcel.generic.ObjectType;

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

public class DeserializationGadgetDetector implements Detector {
    private static final String DESERIALIZATION_GADGET_TYPE = "DESERIALIZATION_GADGET";

    private static final List DANGEROUS_APIS = Arrays.asList(
            "java/lang/reflect/Method", //
            "java/lang/reflect/Constructor", //
            "org/springframework/beans/BeanUtils", //
            "org/apache/commons/beanutils/BeanUtils", //
            "org/apache/commons/beanutils/PropertyUtils", //
            "org/springframework/util/ReflectionUtils");

    List classesToIgnoreInReadObjectMethod = Arrays.asList("java/io/ObjectInputStream","java/lang/Object");

    private BugReporter bugReporter;

    private static List READ_DESERIALIZATION_METHODS = Arrays.asList("readObject", //
            "readUnshared", "readArray", "readResolve");

    public DeserializationGadgetDetector(BugReporter bugReporter) {
        this.bugReporter = bugReporter;
    }

    @Override
    public void visitClassContext(ClassContext classContext) {
        JavaClass javaClass = classContext.getJavaClass();

        boolean isSerializable = InterfaceUtils.isSubtype(javaClass, "java.io.Serializable");
        boolean isInvocationHandler = InterfaceUtils.isSubtype(javaClass, "java.lang.reflect.InvocationHandler");
        boolean useDangerousApis = false;
        boolean customReadObjectMethod = false;
        boolean customInvokeMethod = false;
        boolean hasMethodField = false;
        if(!isSerializable) return; //Nothing to see, move on.


        dangerousApis: for(Constant c : javaClass.getConstantPool().getConstantPool()) {
            if (c instanceof ConstantUtf8) {
                ConstantUtf8 utf8 = (ConstantUtf8) c;
                String constantValue = String.valueOf(utf8.getBytes());
                //System.out.println(constantValue);
                if (DANGEROUS_APIS.contains(constantValue)) {
                    useDangerousApis = true;
                    break dangerousApis;
                }
            }

        }

        for (Method m : javaClass.getMethods()) {

            if(!customReadObjectMethod && READ_DESERIALIZATION_METHODS.contains(m.getName())) {
                try {
                    customReadObjectMethod = hasCustomReadObject(m,classContext,classesToIgnoreInReadObjectMethod);
                } catch (CFGBuilderException e) {
                } catch (DataflowAnalysisException e) {
                }
            }
            else if(!customInvokeMethod && "invoke".equals(m.getName())) {
                try {
                    customInvokeMethod = hasCustomReadObject(m,classContext,classesToIgnoreInReadObjectMethod);
                } catch (CFGBuilderException e) {
                } catch (DataflowAnalysisException e) {
                }
            }
        }


        for(Field f : javaClass.getFields()) {
            if((f.getName().toLowerCase().contains("method") && f.getType().equals(new ObjectType("java.lang.String"))) ||
                    f.getType().equals(new ObjectType("java.reflect.Method"))) {
                hasMethodField = true;
            }
        }

        if((isSerializable && customReadObjectMethod) || (isInvocationHandler && customInvokeMethod)) {
            int priority = useDangerousApis ? Priorities.NORMAL_PRIORITY : Priorities.LOW_PRIORITY;

            bugReporter.reportBug(new BugInstance(this, DESERIALIZATION_GADGET_TYPE, priority) //
                    .addClass(javaClass));
        } else if (isSerializable && hasMethodField && useDangerousApis) {
            bugReporter.reportBug(new BugInstance(this, DESERIALIZATION_GADGET_TYPE, Priorities.LOW_PRIORITY) //
                    .addClass(javaClass));
        }
    }


    /**
     * Check if the readObject is doing multiple external call beyond the basic readByte, readBoolean, etc..
     * @param m
     * @param classContext
     * @return
     * @throws CFGBuilderException
     * @throws DataflowAnalysisException
     */
    private boolean hasCustomReadObject(Method m, ClassContext classContext,List classesToIgnore) throws CFGBuilderException, DataflowAnalysisException {

        ConstantPoolGen cpg = classContext.getConstantPoolGen();
        CFG cfg = classContext.getCFG(m);
        int count = 0;

        for (Iterator i = cfg.locationIterator(); i.hasNext(); ) {
            Location location = i.next();

            Instruction inst = location.getHandle().getInstruction();
            //ByteCode.printOpCode(inst,cpg);
            if(inst instanceof InvokeInstruction) {
                InvokeInstruction invoke = (InvokeInstruction) inst;
                if(!READ_DESERIALIZATION_METHODS.contains(invoke.getMethodName(cpg)) && !classesToIgnore.contains(invoke.getClassName(cpg))) {
                    count +=1;
                }
            }
        }
        return count > 3;
    }

    @Override
    public void report() {
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy