Revamp source of the query

This commit is contained in:
luchua-bc
2021-03-03 13:38:18 +00:00
parent 95d1994196
commit b366ffa69e
5 changed files with 82 additions and 143 deletions

View File

@@ -2,7 +2,7 @@
<qhelp>
<overview>
<p>Cross-Site Scripting (XSS) is categorized as one of the OWASP Top 10 Security Vulnerabilities. The <code>HttpOnly</code> flag directs compatible browsers to prevent client-side script from accessing cookies. Including the HttpOnly flag in the Set-Cookie HTTP response header for a sensitive cookie helps mitigate the risk associated with XSS where an attacker's script code attempts to read the contents of a cookie and exfiltrate information obtained.</p>
<p>Cross-Site Scripting (XSS) is categorized as one of the OWASP Top 10 Security Vulnerabilities. The <code>HttpOnly</code> flag directs compatible browsers to prevent client-side script from accessing cookies. Including the <code>HttpOnly</code> flag in the Set-Cookie HTTP response header for a sensitive cookie helps mitigate the risk associated with XSS where an attacker's script code attempts to read the contents of a cookie and exfiltrate information obtained.</p>
</overview>
<recommendation>

View File

@@ -9,7 +9,6 @@
import java
import semmle.code.java.frameworks.Servlets
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking
import DataFlow::PathGraph
@@ -18,8 +17,8 @@ string getSensitiveCookieNameRegex() { result = "(?i).*(auth|session|token|key|c
/** Holds if a string is concatenated with the name of a sensitive cookie. */
predicate isSensitiveCookieNameExpr(Expr expr) {
expr.(StringLiteral)
.getRepresentedString()
expr.(CompileTimeConstantExpr)
.getStringValue()
.toLowerCase()
.regexpMatch(getSensitiveCookieNameRegex()) or
isSensitiveCookieNameExpr(expr.(AddExpr).getAnOperand())
@@ -27,7 +26,7 @@ predicate isSensitiveCookieNameExpr(Expr expr) {
/** Holds if a string is concatenated with the `HttpOnly` flag. */
predicate hasHttpOnlyExpr(Expr expr) {
expr.(StringLiteral).getRepresentedString().toLowerCase().matches("%httponly%") or
expr.(CompileTimeConstantExpr).getStringValue().toLowerCase().matches("%httponly%") or
hasHttpOnlyExpr(expr.(AddExpr).getAnOperand())
}
@@ -38,37 +37,34 @@ class SetCookieMethodAccess extends MethodAccess {
this.getMethod() instanceof ResponseAddHeaderMethod or
this.getMethod() instanceof ResponseSetHeaderMethod
) and
this.getArgument(0).(StringLiteral).getRepresentedString().toLowerCase() = "set-cookie"
this.getArgument(0).(CompileTimeConstantExpr).getStringValue().toLowerCase() = "set-cookie"
}
}
/** Sensitive cookie name used in a `Cookie` constructor or a `Set-Cookie` call. */
class SensitiveCookieNameExpr extends Expr {
SensitiveCookieNameExpr() {
isSensitiveCookieNameExpr(this) and
(
exists(
ClassInstanceExpr cie // new Cookie("jwt_token", token)
|
(
cie.getConstructor().getDeclaringType().hasQualifiedName("javax.servlet.http", "Cookie") or
cie.getConstructor()
.getDeclaringType()
.getASupertype*()
.hasQualifiedName("javax.ws.rs.core", "Cookie") or
cie.getConstructor()
.getDeclaringType()
.getASupertype*()
.hasQualifiedName("jakarta.ws.rs.core", "Cookie")
) and
DataFlow::localExprFlow(this, cie.getArgument(0))
)
or
exists(
SetCookieMethodAccess ma // response.addHeader("Set-Cookie: token=" +authId + ";HttpOnly;Secure")
|
DataFlow::localExprFlow(this, ma.getArgument(1))
)
exists(
ClassInstanceExpr cie, Expr e // new Cookie("jwt_token", token)
|
(
cie.getConstructor().getDeclaringType().hasQualifiedName("javax.servlet.http", "Cookie") or
cie.getConstructor()
.getDeclaringType()
.getASupertype*()
.hasQualifiedName(["javax.ws.rs.core", "jakarta.ws.rs.core"], "Cookie")
) and
this = cie and
isSensitiveCookieNameExpr(e) and
DataFlow::localExprFlow(e, cie.getArgument(0))
)
or
exists(
SetCookieMethodAccess ma, Expr e // response.addHeader("Set-Cookie: token=" +authId + ";HttpOnly;Secure")
|
this = ma.getArgument(1) and
isSensitiveCookieNameExpr(e) and
DataFlow::localExprFlow(e, ma.getArgument(1))
)
}
}
@@ -78,15 +74,20 @@ class CookieResponseSink extends DataFlow::ExprNode {
CookieResponseSink() {
exists(MethodAccess ma |
(
ma.getMethod() instanceof ResponseAddCookieMethod or
ma instanceof SetCookieMethodAccess
) and
ma.getAnArgument() = this.getExpr()
ma.getMethod() instanceof ResponseAddCookieMethod and
this.getExpr() = ma.getArgument(0)
or
ma instanceof SetCookieMethodAccess and
this.getExpr() = ma.getArgument(1)
)
)
}
}
/** Holds if the `node` is a method call of `setHttpOnly(true)` on a cookie. */
/**
* Holds if `node` is an access to a variable which has `setHttpOnly(true)` called on it and is also
* the first argument to a call to the method `addCookie` of `javax.servlet.http.HttpServletResponse`.
*/
predicate setHttpOnlyMethodAccess(DataFlow::Node node) {
exists(
MethodAccess addCookie, Variable cookie, MethodAccess m // jwtCookie.setHttpOnly(true)
@@ -100,7 +101,10 @@ predicate setHttpOnlyMethodAccess(DataFlow::Node node) {
)
}
/** Holds if the `node` is a method call of `Set-Cookie` header with the `HttpOnly` flag whose cookie name is sensitive. */
/**
* Holds if `node` is a string that contains `httponly` and which flows to the second argument
* of a method to set a cookie.
*/
predicate setHttpOnlyInSetCookie(DataFlow::Node node) {
exists(SetCookieMethodAccess sa |
hasHttpOnlyExpr(node.asExpr()) and
@@ -108,20 +112,17 @@ predicate setHttpOnlyInSetCookie(DataFlow::Node node) {
)
}
/** Holds if the `node` is an invocation of a JAX-RS `NewCookie` constructor that sets `HttpOnly` to true. */
predicate setHttpOnlyInNewCookie(DataFlow::Node node) {
exists(ClassInstanceExpr cie |
cie.getConstructor().getDeclaringType().hasName("NewCookie") and
DataFlow::localExprFlow(node.asExpr(), cie.getArgument(0)) and
(
cie.getNumArgument() = 6 and cie.getArgument(5).(BooleanLiteral).getBooleanValue() = true // NewCookie(Cookie cookie, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
or
cie.getNumArgument() = 8 and
cie.getArgument(6).getType() instanceof BooleanType and
cie.getArgument(7).(BooleanLiteral).getBooleanValue() = true // NewCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly)
or
cie.getNumArgument() = 10 and cie.getArgument(9).(BooleanLiteral).getBooleanValue() = true // NewCookie(String name, String value, String path, String domain, int version, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
)
/** Holds if `cie` is an invocation of a JAX-RS `NewCookie` constructor that sets `HttpOnly` to true. */
predicate setHttpOnlyInNewCookie(ClassInstanceExpr cie) {
cie.getConstructedType().hasQualifiedName(["javax.ws.rs.core", "jakarta.ws.rs.core"], "NewCookie") and
(
cie.getNumArgument() = 6 and cie.getArgument(5).(BooleanLiteral).getBooleanValue() = true // NewCookie(Cookie cookie, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
or
cie.getNumArgument() = 8 and
cie.getArgument(6).getType() instanceof BooleanType and
cie.getArgument(7).(BooleanLiteral).getBooleanValue() = true // NewCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly)
or
cie.getNumArgument() = 10 and cie.getArgument(9).(BooleanLiteral).getBooleanValue() = true // NewCookie(String name, String value, String path, String domain, int version, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
)
}
@@ -145,7 +146,10 @@ predicate isTestMethod(DataFlow::Node node) {
)
}
/** A taint configuration tracking flow from a sensitive cookie without HttpOnly flag set to its HTTP response. */
/**
* A taint configuration tracking flow from a sensitive cookie without the `HttpOnly` flag
* set to its HTTP response.
*/
class MissingHttpOnlyConfiguration extends TaintTracking::Configuration {
MissingHttpOnlyConfiguration() { this = "MissingHttpOnlyConfiguration" }
@@ -159,25 +163,17 @@ class MissingHttpOnlyConfiguration extends TaintTracking::Configuration {
// cookie.setHttpOnly(true)
setHttpOnlyMethodAccess(node)
or
// response.addHeader("Set-Cookie: token=" +authId + ";HttpOnly;Secure")
// response.addHeader("Set-Cookie", "token=" +authId + ";HttpOnly;Secure")
setHttpOnlyInSetCookie(node)
or
// new NewCookie("session-access-key", accessKey, "/", null, null, 0, true, true)
setHttpOnlyInNewCookie(node)
setHttpOnlyInNewCookie(node.asExpr())
or
// Test class or method
isTestMethod(node)
}
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(
ClassInstanceExpr cie // `NewCookie` constructor
|
cie.getAnArgument() = pred.asExpr() and
cie = succ.asExpr() and
cie.getConstructor().getDeclaringType().hasName("NewCookie")
)
or
exists(
MethodAccess ma // `toString` call on a cookie object
|

View File

@@ -1,13 +1,13 @@
edges
| SensitiveCookieNotHttpOnly.java:22:38:22:48 | "jwt_token" : String | SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie |
| SensitiveCookieNotHttpOnly.java:49:56:49:75 | "session-access-key" : String | SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) |
| SensitiveCookieNotHttpOnly.java:22:27:22:60 | new Cookie(...) : Cookie | SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie |
| SensitiveCookieNotHttpOnly.java:49:42:49:113 | new NewCookie(...) : NewCookie | SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) |
nodes
| SensitiveCookieNotHttpOnly.java:22:38:22:48 | "jwt_token" : String | semmle.label | "jwt_token" : String |
| SensitiveCookieNotHttpOnly.java:22:27:22:60 | new Cookie(...) : Cookie | semmle.label | new Cookie(...) : Cookie |
| SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie | semmle.label | jwtCookie |
| SensitiveCookieNotHttpOnly.java:39:42:39:69 | ... + ... | semmle.label | ... + ... |
| SensitiveCookieNotHttpOnly.java:49:42:49:113 | new NewCookie(...) : NewCookie | semmle.label | new NewCookie(...) : NewCookie |
| SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) | semmle.label | toString(...) |
| SensitiveCookieNotHttpOnly.java:49:56:49:75 | "session-access-key" : String | semmle.label | "session-access-key" : String |
#select
| SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie | SensitiveCookieNotHttpOnly.java:22:38:22:48 | "jwt_token" : String | SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:22:38:22:48 | "jwt_token" | This sensitive cookie |
| SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie | SensitiveCookieNotHttpOnly.java:22:27:22:60 | new Cookie(...) : Cookie | SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:22:27:22:60 | new Cookie(...) | This sensitive cookie |
| SensitiveCookieNotHttpOnly.java:39:42:39:69 | ... + ... | SensitiveCookieNotHttpOnly.java:39:42:39:69 | ... + ... | SensitiveCookieNotHttpOnly.java:39:42:39:69 | ... + ... | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:39:42:39:69 | ... + ... | This sensitive cookie |
| SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) | SensitiveCookieNotHttpOnly.java:49:56:49:75 | "session-access-key" : String | SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:49:56:49:75 | "session-access-key" | This sensitive cookie |
| SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) | SensitiveCookieNotHttpOnly.java:49:42:49:113 | new NewCookie(...) : NewCookie | SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:49:42:49:113 | new NewCookie(...) | This sensitive cookie |

View File

@@ -51,10 +51,16 @@ package javax.ws.rs.core;
* @since 1.0
*/
public class Cookie {
/**
* Cookies using the default version correspond to RFC 2109.
*/
public static final int DEFAULT_VERSION = 1;
private final String name;
private final String value;
private final int version;
private final String path;
private final String domain;
/**
* Create a new instance.
@@ -68,6 +74,11 @@ public class Cookie {
*/
public Cookie(final String name, final String value, final String path, final String domain, final int version)
throws IllegalArgumentException {
this.name = name;
this.value = value;
this.version = version;
this.domain = domain;
this.path = path;
}
/**
@@ -81,6 +92,7 @@ public class Cookie {
*/
public Cookie(final String name, final String value, final String path, final String domain)
throws IllegalArgumentException {
this(name, value, path, domain, DEFAULT_VERSION);
}
/**
@@ -92,6 +104,7 @@ public class Cookie {
*/
public Cookie(final String name, final String value)
throws IllegalArgumentException {
this(name, value, null, null);
}
/**
@@ -112,7 +125,7 @@ public class Cookie {
* @return the cookie name.
*/
public String getName() {
return null;
return name;
}
/**
@@ -121,7 +134,7 @@ public class Cookie {
* @return the cookie value.
*/
public String getValue() {
return null;
return value;
}
/**
@@ -130,7 +143,7 @@ public class Cookie {
* @return the cookie version.
*/
public int getVersion() {
return -1;
return version;
}
/**
@@ -139,7 +152,7 @@ public class Cookie {
* @return the cookie domain.
*/
public String getDomain() {
return null;
return domain;
}
/**
@@ -148,40 +161,6 @@ public class Cookie {
* @return the cookie path.
*/
public String getPath() {
return null;
return path;
}
/**
* Convert the cookie to a string suitable for use as the value of the
* corresponding HTTP header.
*
* @return a stringified cookie.
*/
@Override
public String toString() {
return null;
}
/**
* Generate a hash code by hashing all of the cookies properties.
*
* @return the cookie hash code.
*/
@Override
public int hashCode() {
return -1;
}
/**
* Compare for equality.
*
* @param obj the object to compare to.
* @return {@code true}, if the object is a {@code Cookie} with the same
* value for all properties, {@code false} otherwise.
*/
@SuppressWarnings({"StringEquality", "RedundantIfStatement"})
@Override
public boolean equals(final Object obj) {
return true;
}
}
}

View File

@@ -320,40 +320,4 @@ public class NewCookie extends Cookie {
public Cookie toCookie() {
return null;
}
/**
* Convert the cookie to a string suitable for use as the value of the
* corresponding HTTP header.
*
* @return a stringified cookie.
*/
@Override
public String toString() {
return null;
}
/**
* Generate a hash code by hashing all of the properties.
*
* @return the hash code.
*/
@Override
public int hashCode() {
return -1;
}
/**
* Compare for equality. Use {@link #toCookie()} to compare a
* {@code NewCookie} to a {@code Cookie} considering only the common
* properties.
*
* @param obj the object to compare to
* @return true if the object is a {@code NewCookie} with the same value for
* all properties, false otherwise.
*/
@SuppressWarnings({"StringEquality", "RedundantIfStatement"})
@Override
public boolean equals(Object obj) {
return true;
}
}