Skip to content

Java: Static initialization vector #6357

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Aug 26, 2021
Merged

Conversation

artem-smotrakov
Copy link
Contributor

When a cipher is used in certain modes, it needs an initialization vector (IV). IVs are used to randomize the encryption, therefore they should be unique and ideally unpredictable. Otherwise, the same plaintexts result in same ciphertexts under a given secret key. If a static IV is used for encryption, this lets an attacker learn if the same data pieces are transfered or stored, or this can help the attacker run a dictionary attack.

I'd like to add a query detecting static IVs:

  • The query looks for data flows from static byte array initialization to Cipher.init() calls.
  • Only ciphers initialized for encryption are flagged.
  • The query focuses only static initialization vectors. Therefore it skips cases when the IV is modified. For that reason, the query ignores cases when the IV is filled out by Random instead of SecureRandom.

At first, the query used to be much simpler. Then, I tested it on ~1000 open source projects which uncovered several FPs. Fixing those FPs made the query a bit more complex.

The query finds the following vulnerabilities:

  • CVE-2020-5408 in Spring Security. Please note that the issues has not been patched. They only deprecated the vulnerable methods. Therefore, the query detects the issue on the latest version of Spring Security.
  • OFBIZ-12281 in Apache OFBiz
  • PDFBOX-4191 in Apache PDFBox

- Added StaticInitializationVector.ql
- Added StaticInitializationVector.qhelp
- Added tests
Copy link
Contributor

@Marcono1234 Marcono1234 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hopefully the following review comments are useful.
The maintainers can probably provide a more in-depth review.

*/
private class StaticByteArrayCreation extends ArrayCreationExpr {
StaticByteArrayCreation() {
this.getType().(Array).getElementType().(PrimitiveType).getName() = "byte" and
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be better to use getComponentType() instead of getElementType(), because the above would also hold for byte[][][] while with getComponentType() it would only hold for byte[].

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This expression doesn't check the dimension. Both methods work fine here, I don't really see why getComponentType() might be better here. The docs for getElementType() says:

Gets the type of the elements used to construct this array type.

For example, the element type of Object[][] is Object.

That's exactly what is necessary here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But will a multi-dimensional byte array (e.g. byte[][][]), or one of its sub arrays, ever be used as initialization vector (and would the other parts of this query even handle that correctly)?

Because StaticByteArrayCreation is currently matching that, e.g. new byte[5][].

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Marcono is right, you really do only want byte[] here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't consider multi-dimensional arrays. I think it may make sense to support them. For example, an application can use a set of static IVs like this:

byte[][] ivs = new byte[][] {
  new byte[] { 1, 2, 3 ... },
  new byte[] { 4, 5, 6, ...}
  ...
}
...
GCMParameterSpec ivSpec = new GCMParameterSpec(128, ivs[i]);

I added test cases encryptWithOneOfStaticIvs01, encryptWithOneOfStaticIvs02 and encryptWithOneOfStaticZeroIvs that use multi-dimensional arrays, and updated the initializedWithConstants() predicate.

array.getInit().getAnInit().getAChildExpr() instanceof IntegerLiteral covers the following case:

byte[][] ivs = new byte[][] {
  new byte[8],
  new byte[16]
};

This expression looks weird to me, but I could not find a better way to cover the case above. While debugging this case, I saw the child elements of the ArrayInits are instances of IntegerLiteral and TypeAccess. Please let me know if there is a better way.

StaticByteArrayCreation still uses getElementType() because it wants to check only the type but not dimensions. If we decide to cover only one-dimension arrays, then we can use getElementType(). I would prefer to support multi-dimensional arrays though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While debugging this case, I saw the child elements of the ArrayInits are instances of IntegerLiteral and TypeAccess.

The VSCode extension AST viewer shows that for me as well, and manually quering ArrayCreationExpr and ArrayInit seems to confirm that.
To me that looks like a bug in the extractor, that ArrayCreationExpr inside an ArrayInit is modelled as (malformed) ArrayInit. Might be worth creating an issue for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me that looks like a bug in the extractor, that ArrayCreationExpr inside an ArrayInit is modelled as (malformed) ArrayInit. Might be worth creating an issue for this?

Yes, I also expected to see ArrayCreationExpr inside. @smowton Does it look like a bug? Or, are we missing something?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a bug

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created #6552 (comment)

)
or
exists(StaticMethodAccess ma |
ma.getMethod().hasQualifiedName("java.util", "Arrays", "copyOf") and
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not really updating an existing array, instead it creates a copy of it. Might be good to add a comment here explaining the rationale since otherwise the class name ArrayUpdate is rather misleading.

If you want to cover copying an existing array then this might also have to consider Arrays.copyOfRange and calls to clone() of an array type.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also interested in the answer to this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not really updating an existing array, instead it creates a copy of it. Might be good to add a comment here explaining the rationale since otherwise the class name ArrayUpdate is rather misleading.

The class ArrayUpdate covers two cases:

  1. when content of the array is updated, for example, by System.arraycopy()
  2. when the array reference is updated, for example, by Arrays.copyOf()

I can update the qldoc for the class with the above. I can also split the class to two -- one class for each case.

I agree that ArrayUpdate may look misleading. Unfortunately, I didn't find a better name. Do you have a better name?

If you want to cover copying an existing array then this might also have to consider Arrays.copyOfRange and calls to clone() of an array type.

Sure, I can cover these methods as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need to model any of these functions: they naturally interrupt dataflow from a static array to its usage by overwriting the variable or field the reference is stored in.

If a dataflow path is found anyhow then most likely there is a control-flow route around the assignment and it's genuinely possible for the static array to be used as an IV.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need to model any of these functions: they naturally interrupt dataflow from a static array to its usage by overwriting the variable or field the reference is stored in.

If I remember correctly, while testing the query, these functions didn't interrupt dataflow. That sometimes resulted in some false-positives. But I might be missing something. I did a quick test and looks like you're right. I'll remove these functions.

*/
private class StaticByteArrayCreation extends ArrayCreationExpr {
StaticByteArrayCreation() {
this.getType().(Array).getElementType().(PrimitiveType).getName() = "byte" and
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Marcono is right, you really do only want byte[] here

)
or
exists(StaticMethodAccess ma |
ma.getMethod().hasQualifiedName("java.util", "Arrays", "copyOf") and
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also interested in the answer to this

}

override predicate isSanitizer(DataFlow::Node node) {
exists(ArrayUpdate update | update.getArray() = node.asExpr())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering the existing side-condition on your source that it never flows to an array update, why this sanitizer? Can you construct any case where both sanitizer and side-condition are required? If they're redundant, it's best to drop the side-condition and just use the sanitizer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The side-condition helps to get rid of some false-positives. For example, see encryptWithRandomIvByteByByte() test. Unfortunately, the sanitizer doesn't help here. But you're right, the sanitizer is redundant. I'll remove it.

@artem-smotrakov
Copy link
Contributor Author

@Marcono1234 @smowton Thanks for the review. I've addressed your comments. I'm going to file a bug for this soon.

@smowton smowton merged commit 7a0555e into github:main Aug 26, 2021
@artem-smotrakov artem-smotrakov deleted the static-iv branch August 26, 2021 13:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants