/** * 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.injection; import com.h3xstream.findsecbugs.FindSecBugsGlobalConfig; import edu.umd.cs.findbugs.BugReporter; import edu.umd.cs.findbugs.ba.AnalysisContext; import edu.umd.cs.findbugs.io.IO; import edu.umd.cs.findbugs.util.ClassName; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; import org.apache.bcel.Repository; import org.apache.bcel.classfile.JavaClass; import org.apache.bcel.generic.ConstantPoolGen; import org.apache.bcel.generic.InstructionHandle; import org.apache.bcel.generic.InvokeInstruction; /** * Detector designed for extension to detect basic injections with a list of * full method names with specified injectable arguments as taint sinks * * @author David Formanek (Y Soft Corporation, a.s.) */ public abstract class BasicInjectionDetector extends AbstractInjectionDetector { private final Map<String, InjectionPoint> injectionMap = new HashMap<String, InjectionPoint>(); private static final SinksLoader SINKS_LOADER = new SinksLoader(); protected BasicInjectionDetector(BugReporter bugReporter) { super(bugReporter); loadCustomConfigFiles(); } @Override protected InjectionPoint getInjectionPoint(InvokeInstruction invoke, ConstantPoolGen cpg, InstructionHandle handle) { assert invoke != null && cpg != null; //1. Verify if the class used has a known sink String fullMethodName = getFullMethodName(invoke, cpg); //This will skip the most common lookup if ("java/lang/Object.<init>()V".equals(fullMethodName)) { return InjectionPoint.NONE; } InjectionPoint injectionPoint = injectionMap.get(fullMethodName); if (injectionPoint != null) { return injectionPoint; } try { //2. Verify if the super classes match a known sink JavaClass classDef = Repository.lookupClass(invoke.getClassName(cpg)); for (JavaClass superClass : classDef.getSuperClasses()) { if ("java.lang.Object".equals(superClass.getClassName())) { continue; } String superClassFullMethodName = superClass.getClassName().replace('.','/') + "." + invoke.getMethodName(cpg) + invoke.getSignature(cpg); injectionPoint = injectionMap.get(superClassFullMethodName); if (injectionPoint != null) { return injectionPoint; } } } catch (ClassNotFoundException e) { AnalysisContext.reportMissingClass(e); } return InjectionPoint.NONE; } protected void loadConfiguredSinks(InputStream stream, String bugType) throws IOException { SINKS_LOADER.loadSinks(stream, bugType, new SinksLoader.InjectionPointReceiver() { @Override public void receiveInjectionPoint(String fullMethodName, InjectionPoint injectionPoint) { addParsedInjectionPoint(fullMethodName, injectionPoint); } }); } /** * Loads taint sinks from configuration * * @param filename name of the configuration file * @param bugType type of an injection bug */ protected void loadConfiguredSinks(String filename, String bugType) { SINKS_LOADER.loadConfiguredSinks(filename, bugType, new SinksLoader.InjectionPointReceiver() { @Override public void receiveInjectionPoint(String fullMethodName, InjectionPoint injectionPoint) { addParsedInjectionPoint(fullMethodName, injectionPoint); } }); } /** * Loads taint sinks from custom file. The file name is passed using system property based on the current class name.<br /> * <br /> * Example for Linux/Mac OS X:<ul> * <li>-Dfindsecbugs.injection.customconfigfile.SqlInjectionDetector="/tmp/sql-custom.txt|SQL_INJECTION_HIBERNATE:/tmp/sql2-custom.txt|SQL_INJECTION_HIBERNATE"</li> * <li>-Dfindsecbugs.injection.customconfigfile.ScriptInjectionDetector="/tmp/script-engine-custom.txt|SCRIPT_ENGINE_INJECTION:/tmp/el-custom.txt|EL_INJECTION"</li> * </ul> * Example for Windows:<ul> * <li>-Dfindsecbugs.injection.customconfigfile.SqlInjectionDetector="C:\Temp\sql-custom.txt|SQL_INJECTION_HIBERNATE;C:\Temp\sql2-custom.txt|SQL_INJECTION_HIBERNATE"</li> * <li>-Dfindsecbugs.injection.customconfigfile.ScriptInjectionDetector="C:\Temp\script-engine-custom.txt|SCRIPT_ENGINE_INJECTION;C:\Temp\el-custom.txt|EL_INJECTION"</li> * </ul> */ protected void loadCustomConfigFiles() { String customConfigFile = FindSecBugsGlobalConfig.getInstance().getCustomConfigFile(getClass().getSimpleName()); if (customConfigFile != null && !customConfigFile.isEmpty()) { for (String configFile : customConfigFile.split(File.pathSeparator)) { String[] injectionDefinition = configFile.split(Pattern.quote("|")); if (injectionDefinition.length != 2 || injectionDefinition[0].trim().isEmpty() || injectionDefinition[1].trim().isEmpty()) { AnalysisContext.logError("Wrong injection config file definition: " + configFile + ". Syntax: fileName|bugType, example: sql-custom.txt|SQL_INJECTION_HIBERNATE"); continue; } loadCustomSinks(injectionDefinition[0], injectionDefinition[1]); } } } /** * Loads taint sinks configuration file from file system. If the file doesn't exist on file system, loads the file from classpath. * * @param fileName name of the configuration file * @param bugType type of an injection bug */ protected void loadCustomSinks(String fileName, String bugType) { InputStream stream = null; try { File file = new File(fileName); if (file.exists()) { stream = new FileInputStream(file); loadConfiguredSinks(stream, bugType); } else { stream = getClass().getClassLoader().getResourceAsStream(fileName); loadConfiguredSinks(stream, bugType); } } catch (Exception ex) { throw new RuntimeException("Cannot load custom injection sinks from " + fileName, ex); } finally { IO.close(stream); } } /** * Loads a single taint sink (like a line of configuration) * * @param line specification of the sink * @param bugType type of an injection bug */ protected void loadSink(String line, String bugType) { SINKS_LOADER.loadSink(line, bugType, new SinksLoader.InjectionPointReceiver() { @Override public void receiveInjectionPoint(String fullMethodName, InjectionPoint injectionPoint) { addParsedInjectionPoint(fullMethodName, injectionPoint); } }); } protected void addParsedInjectionPoint(String fullMethodName, InjectionPoint injectionPoint) { assert !injectionMap.containsKey(fullMethodName): "Duplicate method name loaded: "+fullMethodName; injectionMap.put(fullMethodName, injectionPoint); } private String getFullMethodName(InvokeInstruction invoke, ConstantPoolGen cpg) { return ClassName.toSlashedClassName(invoke.getReferenceType(cpg).toString()) + "." + invoke.getMethodName(cpg) + invoke.getSignature(cpg); } }