-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Java: Delayed unsafe deserialization #8501
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
Changes from 4 commits
f76899d
8ba5db2
07eb058
1bd1be1
5717926
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/** | ||
* Provides classes and predicates related to RabbitMQ. | ||
*/ | ||
|
||
import java | ||
private import semmle.code.java.dataflow.ExternalFlow | ||
|
||
/** | ||
* Defines remote sources in RabbitMQ. | ||
*/ | ||
private class RabbitMQSource extends SourceModelCsv { | ||
override predicate row(string row) { | ||
row = | ||
[ | ||
// soruces for RabbitMQ 4.x | ||
"com.rabbitmq.client;Command;true;getContentHeader;();;ReturnValue;remote", | ||
"com.rabbitmq.client;Command;true;getContentBody;();;ReturnValue;remote", | ||
"com.rabbitmq.client;Consumer;true;handleDelivery;(String,Envelope,BasicProperties,byte[]);;Parameter[3];remote", | ||
"com.rabbitmq.client;QueueingConsumer;true;nextDelivery;;;ReturnValue;remote", | ||
"com.rabbitmq.client;RpcServer;true;handleCall;(Delivery,BasicProperties);;Parameter[0];remote", | ||
"com.rabbitmq.client;RpcServer;true;handleCall;(BasicProperties,byte[],BasicProperties);;Parameter[1];remote", | ||
"com.rabbitmq.client;RpcServer;true;handleCall;(byte[],BasicProperties);;Parameter[0];remote", | ||
"com.rabbitmq.client;RpcServer;true;preprocessReplyProperties;(Delivery,Builder);;Parameter[0];remote", | ||
"com.rabbitmq.client;RpcServer;true;postprocessReplyProperties;(Delivery,Builder);;Parameter[0];remote", | ||
"com.rabbitmq.client;RpcServer;true;handleCast;(Delivery);;Parameter[0];remote", | ||
"com.rabbitmq.client;RpcServer;true;handleCast;(BasicProperties,byte[]);;Parameter[1];remote", | ||
"com.rabbitmq.client;RpcServer;true;handleCast;(byte[]);;Parameter[0];remote", | ||
"com.rabbitmq.client;StringRpcServer;true;handleStringCall;;;Parameter[0];remote", | ||
"com.rabbitmq.client;RpcClient;true;doCall;;;ReturnValue;remote", | ||
"com.rabbitmq.client;RpcClient;true;primitiveCall;;;ReturnValue;remote", | ||
"com.rabbitmq.client;RpcClient;true;responseCall;;;ReturnValue;remote", | ||
"com.rabbitmq.client;RpcClient;true;stringCall;(String);;ReturnValue;remote", | ||
"com.rabbitmq.client;RpcClient;true;mapCall;;;ReturnValue;remote", | ||
"com.rabbitmq.client.impl;Frame;true;getInputStream;();;ReturnValue;remote", | ||
"com.rabbitmq.client.impl;Frame;true;getPayload;();;ReturnValue;remote", | ||
"com.rabbitmq.client.impl;FrameHandler;true;readFrame;();;ReturnValue;remote", | ||
] | ||
} | ||
} | ||
|
||
/** | ||
* Defines flow steps in RabbitMQ. | ||
*/ | ||
private class RabbitMQSummaryCsv extends SummaryModelCsv { | ||
override predicate row(string row) { | ||
row = | ||
[ | ||
// flow steps for RabbitMQ 4.x | ||
"com.rabbitmq.client;GetResponse;true;GetResponse;;;Argument[2];Argument[-1];taint", | ||
"com.rabbitmq.client;GetResponse;true;getBody;();;Argument[-1];ReturnValue;taint", | ||
"com.rabbitmq.client;RpcClient$Response;true;getBody;();;Argument[-1];ReturnValue;taint", | ||
"com.rabbitmq.client;QueueingConsumer$Delivery;true;getBody;();;Argument[-1];ReturnValue;taint", | ||
"com.rabbitmq.client.impl;Frame;false;fromBodyFragment;(int,byte[],int,int);;Argument[1];ReturnValue;taint", | ||
"com.rabbitmq.client.impl;Frame;false;readFrom;(DataInputStream);;Argument[0];ReturnValue;taint", | ||
"com.rabbitmq.client.impl;Frame;true;writeTo;(DataOutputStream);;Argument[-1];Argument[0];taint", | ||
] | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -256,6 +256,8 @@ class UnsafeDeserializationConfig extends TaintTracking::Configuration { | |||||
createJacksonTreeNodeStep(pred, succ) | ||||||
or | ||||||
intentFlowsToParcel(pred, succ) | ||||||
or | ||||||
byteArrayFieldFlowStep(pred, succ) | ||||||
} | ||||||
|
||||||
override predicate isSanitizer(DataFlow::Node node) { | ||||||
|
@@ -519,3 +521,14 @@ private predicate joddJsonParserConfiguredUnsafely(Expr parserExpr) { | |||||
not config.hasFlow(getASafelyConfiguredParser(), parser) | ||||||
) | ||||||
} | ||||||
|
||||||
/** | ||||||
* Holds if `fromNode` to `toNode` is a dataflow step that stores data to a byte array field. | ||||||
*/ | ||||||
private predicate byteArrayFieldFlowStep(DataFlow::Node fromNode, DataFlow::Node toNode) { | ||||||
exists(FieldAccess access, Field field | field = access.getField() | | ||||||
field.getType().hasName("byte[]") and | ||||||
access = fromNode.asExpr() and | ||||||
field.getAnAccess() = toNode.asExpr() | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably not an issue, but should this cast the access to
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe. I'm going to move this to a separate PR, let's discuss it there. Thanks for the suggestion! |
||||||
) | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
--- | ||
category: majorAnalysis | ||
--- | ||
* Updated `java/unsafe-deserialization` query to detect delayed unsafe deserialization. | ||
* Added flow steps for `java.io.DataInput` and `java.io.ObjectInput` implementations. | ||
* Added flow sources and steps for RabbitMQ. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import java | ||
import semmle.code.java.dataflow.TaintTracking | ||
import semmle.code.java.dataflow.FlowSources | ||
import TestUtilities.InlineFlowTest | ||
|
||
class Conf extends TaintTracking::Configuration { | ||
Conf() { this = "qltest:frameworks:rabbitmq" } | ||
|
||
override predicate isSource(DataFlow::Node node) { node instanceof RemoteFlowSource } | ||
|
||
override predicate isSink(DataFlow::Node node) { | ||
exists(MethodAccess ma | ma.getMethod().hasName("sink") | node.asExpr() = ma.getAnArgument()) | ||
} | ||
} | ||
|
||
class HasFlowTest extends InlineFlowTest { | ||
override DataFlow::Configuration getValueFlowConfig() { none() } | ||
|
||
override DataFlow::Configuration getTaintFlowConfig() { result = any(Conf c) } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import java | ||
import semmle.code.java.dataflow.FlowSources | ||
import TestUtilities.InlineExpectationsTest | ||
|
||
class SourceTest extends InlineExpectationsTest { | ||
SourceTest() { this = "SourceTest" } | ||
|
||
override string getARelevantTag() { result = "source" } | ||
|
||
override predicate hasActualResult(Location location, string element, string tag, string value) { | ||
tag = "source" and | ||
exists(RemoteFlowSource source | | ||
not source.asParameter().getCallable().getDeclaringType().hasName("DefaultConsumer") and | ||
source.getLocation() = location and | ||
element = source.toString() and | ||
value = "" | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import com.rabbitmq.client.DefaultConsumer; | ||
import com.rabbitmq.client.Channel; | ||
import com.rabbitmq.client.AMQP; | ||
import com.rabbitmq.client.Envelope; | ||
import com.rabbitmq.client.QueueingConsumer; | ||
|
||
public class Test { | ||
|
||
public void defaultConsumerTest(Channel channel) { | ||
DefaultConsumer consumer = new DefaultConsumer(channel) { | ||
|
||
@Override | ||
public void handleDelivery( | ||
String consumerTag, Envelope envelope, AMQP.BasicProperties properties, | ||
byte[] body) { // $source | ||
|
||
sink(body); // $hasTaintFlow | ||
} | ||
}; | ||
} | ||
|
||
public void queueingConsumerTest(QueueingConsumer consumer) { | ||
while (true) { | ||
QueueingConsumer.Delivery delivery = consumer.nextDelivery(); // $source | ||
sink(delivery.getBody()); // $hasTaintFlow | ||
delivery = consumer.nextDelivery(42); // $source | ||
sink(delivery.getBody()); // $hasTaintFlow | ||
} | ||
} | ||
|
||
private void sink(byte[] data) { | ||
|
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
//semmle-extractor-options: --javac-args -cp ${testdir}/../../stubs/rabbitmq-4.12.0 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import java.io.ByteArrayInputStream; | ||
import java.io.DataInput; | ||
import java.io.DataInputStream; | ||
import java.io.ObjectInputStream; | ||
import java.net.Socket; | ||
|
||
public class DelayedDeserialization { | ||
|
||
public void test(Socket socket) throws Exception { | ||
DataInputStream in = new DataInputStream(socket.getInputStream()); | ||
ObjectHolder holder = new ObjectHolder(); | ||
holder.init(in); | ||
holder.getObject(); | ||
} | ||
} | ||
|
||
class ObjectHolder { | ||
|
||
private final byte[] buffer = new byte[1024]; | ||
|
||
public void init(DataInput input) throws Exception { | ||
input.readFully(buffer, 0, buffer.length); | ||
} | ||
|
||
public Object getObject() throws Exception { | ||
ByteArrayInputStream bais = new ByteArrayInputStream(buffer); | ||
ObjectInputStream ois = new ObjectInputStream(bais); | ||
return ois.readObject(); // $unsafeDeserialization | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe it would be good to also add a test where an assignment to a ...
class ObjectHolder {
private byte[] buffer;
public void init(byte[] tainted) throws Exception {
buffer = tainted;
}
public Object getObject() throws Exception {
ByteArrayInputStream bais = new ByteArrayInputStream(buffer);
ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject(); // $unsafeDeserialization
}
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the idea! |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.