mirror of
https://github.com/github/codeql.git
synced 2025-12-24 04:36:35 +01:00
Add unsafe-deserialization support for Jabsorb
This is partly extracted from https://github.com/github/codeql/pull/5954
This commit is contained in:
@@ -15,7 +15,8 @@ may have unforeseen effects, such as the execution of arbitrary code.
|
||||
<p>
|
||||
There are many different serialization frameworks. This query currently
|
||||
supports Kryo, XmlDecoder, XStream, SnakeYaml, JYaml, JsonIO, YAMLBeans, HessianBurlap, Castor, Burlap,
|
||||
Jackson and Java IO serialization through <code>ObjectInputStream</code>/<code>ObjectOutputStream</code>.
|
||||
Jackson, Jabsorb and Java IO serialization through
|
||||
<code>ObjectInputStream</code>/<code>ObjectOutputStream</code>.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
@@ -100,6 +101,10 @@ Blog posts by the developer of Jackson libraries:
|
||||
<a href="https://cowtowncoder.medium.com/on-jackson-cves-dont-panic-here-is-what-you-need-to-know-54cd0d6e8062">On Jackson CVEs: Don’t Panic — Here is what you need to know</a>
|
||||
<a href="https://cowtowncoder.medium.com/jackson-2-10-safe-default-typing-2d018f0ce2ba">Jackson 2.10: Safe Default Typing</a>
|
||||
</li>
|
||||
<li>
|
||||
Jabsorb documentation on deserialization:
|
||||
<a href="https://github.com/Servoy/jabsorb/blob/master/src/org/jabsorb/">Jabsorb JSON Serializer</a>.
|
||||
</li>
|
||||
</references>
|
||||
|
||||
</qhelp>
|
||||
|
||||
26
java/ql/src/semmle/code/java/frameworks/Jabsorb.qll
Normal file
26
java/ql/src/semmle/code/java/frameworks/Jabsorb.qll
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Provides classes for working with the Jabsorb JSON-RPC ORB framework.
|
||||
*/
|
||||
|
||||
import java
|
||||
|
||||
/** The class `org.jabsorb.JSONSerializer`. */
|
||||
class JabsorbSerializer extends RefType {
|
||||
JabsorbSerializer() { this.hasQualifiedName("org.jabsorb", "JSONSerializer") }
|
||||
}
|
||||
|
||||
/** The deserialization method `unmarshall`. */
|
||||
class JabsorbUnmarshallMethod extends Method {
|
||||
JabsorbUnmarshallMethod() {
|
||||
this.getDeclaringType().getASupertype*() instanceof JabsorbSerializer and
|
||||
this.getName() = "unmarshall"
|
||||
}
|
||||
}
|
||||
|
||||
/** The deserialization method `fromJSON`. */
|
||||
class JabsorbFromJsonMethod extends Method {
|
||||
JabsorbFromJsonMethod() {
|
||||
this.getDeclaringType().getASupertype*() instanceof JabsorbSerializer and
|
||||
this.getName() = "fromJSON"
|
||||
}
|
||||
}
|
||||
@@ -77,14 +77,6 @@ class SetPolymorphicTypeValidatorSource extends DataFlow::ExprNode {
|
||||
}
|
||||
}
|
||||
|
||||
/** Holds if `fromNode` to `toNode` is a dataflow step that resolves a class. */
|
||||
predicate resolveClassStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
|
||||
exists(ReflectiveClassIdentifierMethodAccess ma |
|
||||
ma.getArgument(0) = fromNode.asExpr() and
|
||||
ma = toNode.asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `fromNode` to `toNode` is a dataflow step that creates a Jackson parser.
|
||||
*
|
||||
@@ -153,22 +145,3 @@ predicate hasArgumentWithUnsafeJacksonAnnotation(MethodAccess call) {
|
||||
hasJsonTypeInfoAnnotation(argType.(ParameterizedType).getATypeArgument())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `fromNode` to `toNode` is a dataflow step that looks like resolving a class.
|
||||
* A method probably resolves a class if it takes a string, returns a type descriptor,
|
||||
* and its name contains "resolve", "load", etc.
|
||||
*
|
||||
* Any method call that satisfies the rule above is assumed to propagate taint from its string arguments,
|
||||
* so methods that accept user-controlled data but sanitize it or use it for some
|
||||
* completely different purpose before returning a type descriptor could result in false positives.
|
||||
*/
|
||||
predicate looksLikeResolveClassStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
|
||||
exists(MethodAccess ma, Method m, Expr arg | m = ma.getMethod() and arg = ma.getAnArgument() |
|
||||
m.getReturnType() instanceof JacksonTypeDescriptorType and
|
||||
m.getName().toLowerCase().regexpMatch("(.*)(resolve|load|class|type)(.*)") and
|
||||
arg.getType() instanceof TypeString and
|
||||
arg = fromNode.asExpr() and
|
||||
ma = toNode.asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ private import semmle.code.java.frameworks.YamlBeans
|
||||
private import semmle.code.java.frameworks.HessianBurlap
|
||||
private import semmle.code.java.frameworks.Castor
|
||||
private import semmle.code.java.frameworks.Jackson
|
||||
private import semmle.code.java.frameworks.Jabsorb
|
||||
private import semmle.code.java.frameworks.apache.Lang
|
||||
private import semmle.code.java.Reflection
|
||||
|
||||
@@ -184,6 +185,13 @@ predicate unsafeDeserialization(MethodAccess ma, Expr sink) {
|
||||
hasArgumentWithUnsafeJacksonAnnotation(ma)
|
||||
) and
|
||||
not exists(SafeObjectMapperConfig config | config.hasFlowToExpr(ma.getQualifier()))
|
||||
or
|
||||
m instanceof JabsorbUnmarshallMethod and
|
||||
sink = ma.getArgument(2) and
|
||||
any(UnsafeTypeConfig config).hasFlowToExpr(ma.getArgument(1))
|
||||
or
|
||||
m instanceof JabsorbFromJsonMethod and
|
||||
sink = ma.getArgument(0)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -243,9 +251,36 @@ class UnsafeDeserializationConfig extends TaintTracking::Configuration {
|
||||
}
|
||||
}
|
||||
|
||||
/** Holds if `fromNode` to `toNode` is a dataflow step that resolves a class. */
|
||||
predicate resolveClassStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
|
||||
exists(ReflectiveClassIdentifierMethodAccess ma |
|
||||
ma.getArgument(0) = fromNode.asExpr() and
|
||||
ma = toNode.asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `fromNode` to `toNode` is a dataflow step that looks like resolving a class.
|
||||
* A method probably resolves a class if it takes a string, returns a type descriptor,
|
||||
* and its name contains "resolve", "load", etc.
|
||||
*
|
||||
* Any method call that satisfies the rule above is assumed to propagate taint from its string arguments,
|
||||
* so methods that accept user-controlled data but sanitize it or use it for some
|
||||
* completely different purpose before returning a type descriptor could result in false positives.
|
||||
*/
|
||||
predicate looksLikeResolveClassStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
|
||||
exists(MethodAccess ma, Method m, Expr arg | m = ma.getMethod() and arg = ma.getAnArgument() |
|
||||
m.getReturnType() instanceof JacksonTypeDescriptorType and
|
||||
m.getName().toLowerCase().regexpMatch("(?i).*(resolve|load|class|type).*") and
|
||||
arg.getType() instanceof TypeString and
|
||||
arg = fromNode.asExpr() and
|
||||
ma = toNode.asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks flow from a remote source to a type descriptor (e.g. a `java.lang.Class` instance)
|
||||
* passed to a Jackson deserialization method.
|
||||
* passed to a deserialization method.
|
||||
*
|
||||
* If this is user-controlled, arbitrary code could be executed while instantiating the user-specified type.
|
||||
*/
|
||||
@@ -256,7 +291,12 @@ class UnsafeTypeConfig extends TaintTracking2::Configuration {
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
exists(MethodAccess ma, int i, Expr arg | i > 0 and ma.getArgument(i) = arg |
|
||||
ma.getMethod() instanceof ObjectMapperReadMethod and
|
||||
(
|
||||
ma.getMethod() instanceof ObjectMapperReadMethod
|
||||
or
|
||||
ma.getMethod() instanceof JabsorbUnmarshallMethod
|
||||
) and
|
||||
// Note `JacksonTypeDescriptorType` includes plain old `java.lang.Class`
|
||||
arg.getType() instanceof JacksonTypeDescriptorType and
|
||||
arg = sink.asExpr()
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user