com.h3xstream.findsecbugs.password.HashUnsafeEqualsDetector Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of findsecbugs-plugin Show documentation
Show all versions of findsecbugs-plugin Show documentation
Core module of the project. It include all the SpotBugs detectors.
The resulting jar is the published plugin.
The 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.password;
import com.h3xstream.findsecbugs.common.StackUtils;
import com.h3xstream.findsecbugs.common.matcher.InvokeMatcherBuilder;
import com.h3xstream.findsecbugs.injection.BasicInjectionDetector;
import com.h3xstream.findsecbugs.injection.InjectionPoint;
import com.h3xstream.findsecbugs.taintanalysis.Taint;
import com.h3xstream.findsecbugs.taintanalysis.TaintFrame;
import com.h3xstream.findsecbugs.taintanalysis.TaintFrameAdditionalVisitor;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.Priorities;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import edu.umd.cs.findbugs.ba.DataflowAnalysisException;
import org.apache.bcel.generic.*;
import java.util.ArrayList;
import java.util.List;
import static com.h3xstream.findsecbugs.common.matcher.InstructionDSL.invokeInstruction;
/**
*
* Detect hash value that are compare with the equals method.
* The equals implementation is stopping the comparison of both value once a first difference is found.
* This comparison is susceptible to timing attack.
*
*
*
* if(hashInput.equals(actualHash)) {
* ....
* }
*
*/
public class HashUnsafeEqualsDetector extends BasicInjectionDetector implements TaintFrameAdditionalVisitor {
private static final String UNSAFE_HASH_EQUALS_TYPE = "UNSAFE_HASH_EQUALS";
private static final InvokeMatcherBuilder STRING_EQUALS_METHOD = invokeInstruction() //
.atClass("java/lang/String").atMethod("equals").withArgs("(Ljava/lang/Object;)Z");
private static final InvokeMatcherBuilder ARRAYS_EQUALS_METHOD = invokeInstruction() //
.atClass("java/util/Arrays").atMethod("equals").withArgs("([B[B)Z");
private static final boolean DEBUG = false;
/**
* Keyword that describe variable that are likely to be hashed value.
*/
private static final List HASH_WORDS = new ArrayList();
static {
HASH_WORDS.add("hash");
HASH_WORDS.add("md5");
HASH_WORDS.add("sha");
HASH_WORDS.add("digest");
}
/**
* The keyword "SHA" will catch many word that are unrelated to hashing.
* For example: sharedLink, shallBeRemoved, ...
* When new false positive are encounter, this list can be extended.
*/
private static final List ALLOWED_WORDS = new ArrayList();
static {
ALLOWED_WORDS.add("share"); //share shared
ALLOWED_WORDS.add("shall");
ALLOWED_WORDS.add("shad"); //shade shadow
ALLOWED_WORDS.add("sharp");
ALLOWED_WORDS.add("shap"); //shape
ALLOWED_WORDS.add("cashaccount"); // cash account
}
public HashUnsafeEqualsDetector(BugReporter bugReporter) {
super(bugReporter);
registerVisitor(this);
}
@Override
protected int getPriorityFromTaintFrame(TaintFrame fact, int offset) throws DataflowAnalysisException {
Taint rightValue = fact.getStackValue(offset);
Taint leftValue = fact.getStackValue(offset == 0 ? 1 : 0);
boolean passwordVariableLeft = leftValue.isUnknown() && leftValue.hasTag(Taint.Tag.HASH_VARIABLE);
boolean passwordVariableRight = rightValue.isUnknown() && rightValue.hasTag(Taint.Tag.HASH_VARIABLE);
//Is a constant value that was tag because the value was place in a variable name "password" at some point.
if (passwordVariableLeft || passwordVariableRight) {
return Priorities.NORMAL_PRIORITY;
} else {
return Priorities.IGNORE_PRIORITY;
}
}
@Override
protected InjectionPoint getInjectionPoint(InvokeInstruction invoke, ConstantPoolGen cpg,
InstructionHandle handle) {
if(STRING_EQUALS_METHOD.matches(invoke,cpg) || ARRAYS_EQUALS_METHOD.matches(invoke,cpg)) {
return new InjectionPoint(new int[]{0,1}, UNSAFE_HASH_EQUALS_TYPE);
}
return InjectionPoint.NONE;
}
@Override
public void visitInvoke(InvokeInstruction invoke, MethodGen methodGen, TaintFrame frameType, List parameters, ConstantPoolGen cpg) {
}
@Override
public void visitReturn(MethodGen methodGen, Taint returnValue, ConstantPoolGen cpg) throws Exception {
}
@Override
public void visitLoad(LoadInstruction instruction, MethodGen methodGen, TaintFrame frameType, int numProduced, ConstantPoolGen cpg) {
//Extract the name of the variable
int index = instruction.getIndex();
LocalVariableGen var = StackUtils.getLocalVariable(methodGen, index);
if(var == null) {
if(DEBUG) System.out.println("Unable to get field name for index "+ index + " in "+methodGen);
return;
}
String fieldName = var.getName();
boolean isHashVariable = false;
String fieldNameLower = fieldName.toLowerCase();
likelyHash : for (String password : HASH_WORDS) {
if (fieldNameLower.contains(password)) {
for (String allowedWord: ALLOWED_WORDS) {
if (fieldNameLower.contains(allowedWord)) {
break likelyHash;
}
}
isHashVariable = true;
}
}
if(!isHashVariable) {return;}
//Mark local variable
Taint passwordValue = frameType.getValue(index);
passwordValue.addTag(Taint.Tag.HASH_VARIABLE);
if(numProduced <= 0) {return;}
//Mark the stack value
try {
for(int indexStack=0;indexStack