Friday, January 18, 2008

Why all good Developers and penetration testers should "Know thy API"

Trawling through an API always exhibits interesting results. A vital motto of Developers and respectable penetration testers should be "know thy API". As a Developer I will rely on the API to aid validation and at the same time I should be aware of any "gotchas" that can lie in the undergrowth when creating an Object, for example values which can be passed during instantiation that you would not normally expect. As a penetration tester I will use anomalies to my advantage to circumvent validation routines.

Knowing the values which can be used to instantiate certain Objects can yield interesting results. Let's take the java.math.BigDecimal class as an example as it is an Object I would expect to be used when testing financial applications. There have been many occasions when the "exponent hack" has come in useful as it can be used to bypass validation routines which in turn results in overflows, being able to create values less than 1 (without starting with zero or using decimal notation), and a wide range of other exceptions / unexpected behaviour when combined with other components.

I have seen Developers attempting to validate a number based on length of the string (shocking but true!). For example, it was assumed that if the length of the string was less than or equal to 4 then the number could not exceed 9999. We could envisage the code something along the lines of:

String sNumber = request.getParamater("numberNoMoreThan5Char");

//Make sure the number is less than 9999 and greater than 0.
if(sNumber.length() <= 4 && sNumber.charAt(0) != "0")
BigDecimal myNumber = new BigDecimal(sNumber);


The use of scientific notation will allow us to bypass this poor validation routine (the goal here is to instantiate BigDecimal and not to result in a NumberFormatException, which can be easily achieved). For example, if "numberNoMoreThan5Char" is set to "1e10" we meet the validation requirements as the string is equal to four characters and does not begin with zero. However, BigDecimal is then instantiated with with a value of 10000000000. Similarly, by passing "1e-9" results in the BigDecimal being instantiated with the value of 0.000000001. As expected, this will cause further complications throughout the application and will most likely lead to data degradation i.e data which is stored that would have never been expected.

A second example can be seen with the java.lang.Double class. A technique which is often used to determine if a number is valid (I found this code in the OWASP ESAPI project) goes along the lines of:

public boolean isValidNumber(String input)
{

try
{

Double.parseDouble(input);
}
catch (NumberFormatException e)
{

return false;
}
return true;
}



Now scientific notation can be used here and of course this isn't a problem as such notation results in a valid number. But before we proceed let's quickly delve into the Java API documentation and take a look at the explanation of Double.parseDouble(String):
parseDouble
public static double parseDouble(String s) throws NumberFormatException
Returns a new double initialized to the value represented by the specified String, as performed by the valueOf method of class Double.

Parameters:
s - the string to be parsed.
Returns:
the double value represented by the string argument.
Throws:
NumberFormatException - if the string does not contain a parsable double.
Since:
1.2
See Also:
valueOf(String)

It may therefore come as a surprise that passing a string with the value "NaN" (not-a-number) or "Infinity" will result in isValidNumber returning true. For example, Double.parseDouble("NaN") or Double.parseDouble("Infinity") is perfectly valid and actually returns a Double with a value set to "NaN" or "Infinity" and in a lot of situations is not what a Developer would expect or want for that matter. This is a perfect example of what happens when Developers rely on an API to perform validation without knowing the anomalies. A little investigation reveals how we are able to pass "NaN" and "Infinity" as a valid argument. Let's take a look at the code for Double.parseDouble(String):


public static double parseDouble(String s) throws NumberFormatException
{
return FloatingDecimal.readJavaFormatString(s).doubleValue();
}

Yep, just one line of code! Well it appears we will have to dig down just a little further and check out the FloatingDecimal class and see what resides in the method readJavaFormatString(String). As it turns out, this method is 249 lines long! So I have selected the interesting piece from the method:

// compare Input string to "NaN" or "Infinity"
int j = 0;
while(i <>
if(in.charAt(i) == targetChars[j]) {
i++; j++;
}
else // something is amiss, throw exception
break parseNumber;
}


// For the candidate string to be a NaN or infinity,
// all characters in input string and target char[]
// must be matched ==> j must equal targetChars.length
// and i must equal l
if( (j == targetChars.length) && (i == l) ) { // return NaN or infinity
return (potentialNaN ? new FloatingDecimal(Double.NaN) // NaN has no sign
: new FloatingDecimal(isNegative?
Double.NEGATIVE_INFINITY:
Double.POSITIVE_INFINITY)) ;


As we can see from the code comments it is clear that it has been designed to take "NaN" and "Infinity" as a valid argument. Developers can be forgiven for not expecting such values to be valid as it was not directly mentioned in the API documentation for parseDouble(String) as we saw above. Although there are actually some clues as the documentation did mention:

See Also:
valueOf(String)

As a Developer I find it vital that I need to be aware of such anomalies to keep the code well validated and will often find myself digging deeper down the Java package tree so I can manually check for anything unexpected, I can then tweak my validation accordingly and combine such details to construct a more intelligent alerting log system (if I saw the anomalies passed to my application it would make me very suspicious). As a penetration tester I will use these anomalies as a means for attacking an application, and as we have seen such techniques come in extremely useful.





No comments: