From 3f0a3266aa61617fb374c0d49e006dfabe429fc0 Mon Sep 17 00:00:00 2001 From: haby0 Date: Thu, 8 Apr 2021 17:14:03 +0800 Subject: [PATCH 01/28] [Java] CWE-348: Use of less trusted source --- .../CWE/CWE-348/UseOfLessTrustedSource.java | 74 +++++++ .../CWE/CWE-348/UseOfLessTrustedSource.qhelp | 38 ++++ .../CWE/CWE-348/UseOfLessTrustedSource.ql | 44 ++++ .../CWE/CWE-348/UseOfLessTrustedSourceLib.qll | 88 ++++++++ .../CWE-348/UseOfLessTrustedSource.expected | 14 ++ .../CWE-348/UseOfLessTrustedSource.java | 74 +++++++ .../CWE-348/UseOfLessTrustedSource.qlref | 1 + .../query-tests/security/CWE-348/options | 2 + .../slf4j-api-1.6.4/org/slf4j/Logger.java | 127 +++++++++++ .../org/slf4j/LoggerFactory.java | 21 ++ .../slf4j-api-1.6.4/org/slf4j/Marker.java | 30 +++ .../core/annotation/AliasFor.java | 21 ++ .../stereotype/Controller.java | 15 +- .../org/springframework/util/ObjectUtils.java | 202 ++++++++++++++++++ .../org/springframework/util/StringUtils.java | 30 +++ .../web/bind/annotation/GetMapping.java | 51 +++++ .../web/bind/annotation/Mapping.java | 4 + .../web/bind/annotation/RequestMapping.java | 23 +- .../web/bind/annotation/RequestParam.java | 23 +- .../web/bind/annotation/ResponseBody.java | 13 ++ 20 files changed, 885 insertions(+), 10 deletions(-) create mode 100644 java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.java create mode 100644 java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp create mode 100644 java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql create mode 100644 java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll create mode 100644 java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.expected create mode 100644 java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.java create mode 100644 java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.qlref create mode 100644 java/ql/test/experimental/query-tests/security/CWE-348/options create mode 100644 java/ql/test/stubs/slf4j-api-1.6.4/org/slf4j/Logger.java create mode 100644 java/ql/test/stubs/slf4j-api-1.6.4/org/slf4j/LoggerFactory.java create mode 100644 java/ql/test/stubs/slf4j-api-1.6.4/org/slf4j/Marker.java create mode 100644 java/ql/test/stubs/springframework-5.2.3/org/springframework/core/annotation/AliasFor.java create mode 100644 java/ql/test/stubs/springframework-5.2.3/org/springframework/util/ObjectUtils.java create mode 100644 java/ql/test/stubs/springframework-5.2.3/org/springframework/util/StringUtils.java create mode 100644 java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/GetMapping.java create mode 100644 java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/Mapping.java create mode 100644 java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/ResponseBody.java diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.java b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.java new file mode 100644 index 000000000000..d338134ed3ed --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.java @@ -0,0 +1,74 @@ +import javax.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +public class UseOfLessTrustedSource { + + private static final Logger log = LoggerFactory.getLogger(UseOfLessTrustedSource.class); + + @GetMapping(value = "bad1") + @ResponseBody + public String bad1(HttpServletRequest request) { + String remoteAddr = ""; + if (request != null) { + remoteAddr = request.getHeader("X-FORWARDED-FOR"); + remoteAddr = remoteAddr.split(",")[0]; + if (remoteAddr == null || "".equals(remoteAddr)) { + remoteAddr = request.getRemoteAddr(); + } + } + return remoteAddr; + } + + @GetMapping(value = "bad2") + public void bad2(HttpServletRequest request) { + String ip = request.getHeader("X-Forwarded-For"); + + log.debug("getClientIP header X-Forwarded-For:{}", ip); + + if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase("unknown", ip)) { + ip = request.getHeader("Proxy-Client-IP"); + log.debug("getClientIP header Proxy-Client-IP:{}", ip); + } + if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase("unknown", ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + log.debug("getClientIP header WL-Proxy-Client-IP:{}", ip); + } + if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase("unknown", ip)) { + ip = request.getHeader("HTTP_CLIENT_IP"); + log.debug("getClientIP header HTTP_CLIENT_IP:{}", ip); + } + if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase("unknown", ip)) { + ip = request.getHeader("HTTP_X_FORWARDED_FOR"); + log.debug("getClientIP header HTTP_X_FORWARDED_FOR:{}", ip); + } + if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase("unknown", ip)) { + ip = request.getHeader("X-Real-IP"); + log.debug("getClientIP header X-Real-IP:{}", ip); + } + if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase("unknown", ip)) { + ip = request.getRemoteAddr(); + log.debug("getRemoteAddr IP:{}", ip); + } + System.out.println("client ip is: " + ip); + } + + @GetMapping(value = "good1") + @ResponseBody + public String good1(HttpServletRequest request) { + String remoteAddr = ""; + if (request != null) { + remoteAddr = request.getHeader("X-FORWARDED-FOR"); + remoteAddr = remoteAddr.split(",")[remoteAddr.split(",").length - 1]; // good + if (remoteAddr == null || "".equals(remoteAddr)) { + remoteAddr = request.getRemoteAddr(); + } + } + return remoteAddr; + } +} \ No newline at end of file diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp new file mode 100644 index 000000000000..1c3a4485cbd8 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp @@ -0,0 +1,38 @@ + + + +

The software obtains the original client IP address through the http header X-Forwarded-For, which is used to ensure +security or track it in the log for statistical or other reasons. Attackers can use X-Forwarded-For Spoofing software.

+ +
+ + +

When the software is not using a proxy server, get the last ip.

+ +
+ + +

The following examples show the bad case and the good case respectively. Bad case, such as bad1 to bad2. +In the bad1 method, the value of X-Forwarded-For in header is split, and the first value of +the split array is obtained. Good case, such as good1, split the value of X-Forwarded-For in header +and get the last value of the split array.

+ + + +
+ + +
  • Prevent IP address spoofing with X-Forwarded-For header when using AWS ELB and Clojure Ring: + +Prevent IP address spoofing with... +
  • + +
  • Security Rule Zero: A Warning about X-Forwarded-For: + +Security Rule Zero: A Warning about X-Forwarded-For +
  • + +
    +
    diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql new file mode 100644 index 000000000000..15d665d37abd --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql @@ -0,0 +1,44 @@ +/** + * @name X-Forwarded-For spoofing + * @description The software obtains the client ip through `X-Forwarded-For`, + * and the attacker can modify the value of `X-Forwarded-For` to forge the ip. + * @kind path-problem + * @problem.severity error + * @precision high + * @id java/use-of-less-trusted-source + * @tags security + * external/cwe/cwe-348 + */ + +import java +import UseOfLessTrustedSourceLib +import semmle.code.java.dataflow.FlowSources +import DataFlow::PathGraph + +class UseOfLessTrustedSourceConfig extends TaintTracking::Configuration { + UseOfLessTrustedSourceConfig() { this = "UseOfLessTrustedSourceConfig" } + + override predicate isSource(DataFlow::Node source) { source instanceof UseOfLessTrustedSource } + + override predicate isSink(DataFlow::Node sink) { sink instanceof UseOfLessTrustedSink } + + /** + * When using `,` split request data and not taking the first value of + * the array, it is considered as `good`. + */ + override predicate isSanitizer(DataFlow::Node node) { + exists(ArrayAccess aa, MethodAccess ma | aa.getArray() = ma | + ma.getQualifier() = node.asExpr() and + ma.getMethod() instanceof SplitMethod and + not aa.getIndexExpr().toString() = "0" + ) + } +} + +from DataFlow::PathNode source, DataFlow::PathNode sink, UseOfLessTrustedSourceConfig conf +where + conf.hasFlowPath(source, sink) and + source.getNode().getEnclosingCallable() = sink.getNode().getEnclosingCallable() and + xffIsFirstGet(source.getNode()) +select sink.getNode(), source, sink, "X-Forwarded-For spoofing might include code from $@.", + source.getNode(), "this user input" diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll new file mode 100644 index 000000000000..0598f8438404 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll @@ -0,0 +1,88 @@ +import java +import DataFlow +import semmle.code.java.dataflow.DataFlow +import experimental.semmle.code.java.Logging + +/** A data flow source of the value of the `x-forwarded-for` field in the `header`. */ +class UseOfLessTrustedSource extends DataFlow::Node { + UseOfLessTrustedSource() { + exists(MethodAccess ma | + ma.getMethod().hasName("getHeader") and + ma.getArgument(0).toString().toLowerCase() = "\"x-forwarded-for\"" and + ma = this.asExpr() + ) + } +} + +/** A data flow sink of method return or log output or local print. */ +class UseOfLessTrustedSink extends DataFlow::Node { + UseOfLessTrustedSink() { + exists(ReturnStmt rs | rs.getResult() = this.asExpr()) + or + exists(MethodAccess ma | + ma.getMethod().getName() in ["print", "println"] and + ( + ma.getMethod().getDeclaringType().hasQualifiedName("java.io", "PrintWriter") or + ma.getMethod().getDeclaringType().hasQualifiedName("java.io", "PrintStream") + ) and + ma.getAnArgument() = this.asExpr() + ) + or + exists(LoggingCall lc | lc.getAnArgument() = this.asExpr()) + } +} + +/** A method that split string. */ +class SplitMethod extends Method { + SplitMethod() { + this.getNumberOfParameters() = 1 and + this.hasQualifiedName("java.lang", "String", "split") + } +} + +/** + * A call to the ServletRequest.getHeader method and the argument are + * `wl-proxy-client-ip`/`proxy-client-ip`/`http_client_ip`/`http_x_forwarded_for`/`x-real-ip`. + */ +class HeaderIpCall extends MethodAccess { + HeaderIpCall() { + this.getMethod().hasName("getHeader") and + this.getMethod() + .getDeclaringType() + .getASupertype*() + .hasQualifiedName("javax.servlet", "ServletRequest") and + this.getArgument(0).toString().toLowerCase() in [ + "\"wl-proxy-client-ip\"", "\"proxy-client-ip\"", "\"http_client_ip\"", + "\"http_x_forwarded_for\"", "\"x-real-ip\"" + ] + } +} + +/** A call to `ServletRequest.getRemoteAddr` method. */ +class RemoteAddrCall extends MethodAccess { + RemoteAddrCall() { + this.getMethod().hasName("getRemoteAddr") and + this.getMethod() + .getDeclaringType() + .getASupertype*() + .hasQualifiedName("javax.servlet", "ServletRequest") + } +} + +/** The first one in the method to get the ip value through `x-forwarded-for`. */ +predicate xffIsFirstGet(Node node) { + exists(HeaderIpCall hic | + node.getEnclosingCallable() = hic.getEnclosingCallable() and + node.getLocation().getEndLine() < hic.getLocation().getEndLine() + ) + or + exists(RemoteAddrCall rac | + node.getEnclosingCallable() = rac.getEnclosingCallable() and + node.getLocation().getEndLine() < rac.getLocation().getEndLine() + ) + or + not exists(HeaderIpCall hic, RemoteAddrCall rac | + node.getEnclosingCallable() = hic.getEnclosingCallable() and + node.getEnclosingCallable() = rac.getEnclosingCallable() + ) +} diff --git a/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.expected b/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.expected new file mode 100644 index 000000000000..a71ad9629ceb --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.expected @@ -0,0 +1,14 @@ +edges +| UseOfLessTrustedSource.java:19:26:19:61 | getHeader(...) : String | UseOfLessTrustedSource.java:25:16:25:25 | remoteAddr | +| UseOfLessTrustedSource.java:30:21:30:56 | getHeader(...) : String | UseOfLessTrustedSource.java:32:60:32:61 | ip | +| UseOfLessTrustedSource.java:30:21:30:56 | getHeader(...) : String | UseOfLessTrustedSource.java:58:28:58:48 | ... + ... | +nodes +| UseOfLessTrustedSource.java:19:26:19:61 | getHeader(...) : String | semmle.label | getHeader(...) : String | +| UseOfLessTrustedSource.java:25:16:25:25 | remoteAddr | semmle.label | remoteAddr | +| UseOfLessTrustedSource.java:30:21:30:56 | getHeader(...) : String | semmle.label | getHeader(...) : String | +| UseOfLessTrustedSource.java:32:60:32:61 | ip | semmle.label | ip | +| UseOfLessTrustedSource.java:58:28:58:48 | ... + ... | semmle.label | ... + ... | +#select +| UseOfLessTrustedSource.java:25:16:25:25 | remoteAddr | UseOfLessTrustedSource.java:19:26:19:61 | getHeader(...) : String | UseOfLessTrustedSource.java:25:16:25:25 | remoteAddr | X-Forwarded-For spoofing might include code from $@. | UseOfLessTrustedSource.java:19:26:19:61 | getHeader(...) | this user input | +| UseOfLessTrustedSource.java:32:60:32:61 | ip | UseOfLessTrustedSource.java:30:21:30:56 | getHeader(...) : String | UseOfLessTrustedSource.java:32:60:32:61 | ip | X-Forwarded-For spoofing might include code from $@. | UseOfLessTrustedSource.java:30:21:30:56 | getHeader(...) | this user input | +| UseOfLessTrustedSource.java:58:28:58:48 | ... + ... | UseOfLessTrustedSource.java:30:21:30:56 | getHeader(...) : String | UseOfLessTrustedSource.java:58:28:58:48 | ... + ... | X-Forwarded-For spoofing might include code from $@. | UseOfLessTrustedSource.java:30:21:30:56 | getHeader(...) | this user input | diff --git a/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.java b/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.java new file mode 100644 index 000000000000..d338134ed3ed --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.java @@ -0,0 +1,74 @@ +import javax.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +public class UseOfLessTrustedSource { + + private static final Logger log = LoggerFactory.getLogger(UseOfLessTrustedSource.class); + + @GetMapping(value = "bad1") + @ResponseBody + public String bad1(HttpServletRequest request) { + String remoteAddr = ""; + if (request != null) { + remoteAddr = request.getHeader("X-FORWARDED-FOR"); + remoteAddr = remoteAddr.split(",")[0]; + if (remoteAddr == null || "".equals(remoteAddr)) { + remoteAddr = request.getRemoteAddr(); + } + } + return remoteAddr; + } + + @GetMapping(value = "bad2") + public void bad2(HttpServletRequest request) { + String ip = request.getHeader("X-Forwarded-For"); + + log.debug("getClientIP header X-Forwarded-For:{}", ip); + + if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase("unknown", ip)) { + ip = request.getHeader("Proxy-Client-IP"); + log.debug("getClientIP header Proxy-Client-IP:{}", ip); + } + if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase("unknown", ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + log.debug("getClientIP header WL-Proxy-Client-IP:{}", ip); + } + if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase("unknown", ip)) { + ip = request.getHeader("HTTP_CLIENT_IP"); + log.debug("getClientIP header HTTP_CLIENT_IP:{}", ip); + } + if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase("unknown", ip)) { + ip = request.getHeader("HTTP_X_FORWARDED_FOR"); + log.debug("getClientIP header HTTP_X_FORWARDED_FOR:{}", ip); + } + if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase("unknown", ip)) { + ip = request.getHeader("X-Real-IP"); + log.debug("getClientIP header X-Real-IP:{}", ip); + } + if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase("unknown", ip)) { + ip = request.getRemoteAddr(); + log.debug("getRemoteAddr IP:{}", ip); + } + System.out.println("client ip is: " + ip); + } + + @GetMapping(value = "good1") + @ResponseBody + public String good1(HttpServletRequest request) { + String remoteAddr = ""; + if (request != null) { + remoteAddr = request.getHeader("X-FORWARDED-FOR"); + remoteAddr = remoteAddr.split(",")[remoteAddr.split(",").length - 1]; // good + if (remoteAddr == null || "".equals(remoteAddr)) { + remoteAddr = request.getRemoteAddr(); + } + } + return remoteAddr; + } +} \ No newline at end of file diff --git a/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.qlref b/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.qlref new file mode 100644 index 000000000000..3c547a2c8714 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.qlref @@ -0,0 +1 @@ +experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql \ No newline at end of file diff --git a/java/ql/test/experimental/query-tests/security/CWE-348/options b/java/ql/test/experimental/query-tests/security/CWE-348/options new file mode 100644 index 000000000000..40bcee8c133d --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-348/options @@ -0,0 +1,2 @@ +//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../../stubs/springframework-5.2.3/:${tes +tdir}/../../../../stubs/slf4j-api-1.6.4/:${testdir}/../../../../stubs/apache-commons-lang3-3.7/ \ No newline at end of file diff --git a/java/ql/test/stubs/slf4j-api-1.6.4/org/slf4j/Logger.java b/java/ql/test/stubs/slf4j-api-1.6.4/org/slf4j/Logger.java new file mode 100644 index 000000000000..1d8b7ea23189 --- /dev/null +++ b/java/ql/test/stubs/slf4j-api-1.6.4/org/slf4j/Logger.java @@ -0,0 +1,127 @@ +package org.slf4j; + +public interface Logger { + String ROOT_LOGGER_NAME = "ROOT"; + + String getName(); + + boolean isTraceEnabled(); + + void trace(String var1); + + void trace(String var1, Object var2); + + void trace(String var1, Object var2, Object var3); + + void trace(String var1, Object[] var2); + + void trace(String var1, Throwable var2); + + boolean isTraceEnabled(Marker var1); + + void trace(Marker var1, String var2); + + void trace(Marker var1, String var2, Object var3); + + void trace(Marker var1, String var2, Object var3, Object var4); + + void trace(Marker var1, String var2, Object[] var3); + + void trace(Marker var1, String var2, Throwable var3); + + boolean isDebugEnabled(); + + void debug(String var1); + + void debug(String var1, Object var2); + + void debug(String var1, Object var2, Object var3); + + void debug(String var1, Object[] var2); + + void debug(String var1, Throwable var2); + + boolean isDebugEnabled(Marker var1); + + void debug(Marker var1, String var2); + + void debug(Marker var1, String var2, Object var3); + + void debug(Marker var1, String var2, Object var3, Object var4); + + void debug(Marker var1, String var2, Object[] var3); + + void debug(Marker var1, String var2, Throwable var3); + + boolean isInfoEnabled(); + + void info(String var1); + + void info(String var1, Object var2); + + void info(String var1, Object var2, Object var3); + + void info(String var1, Object[] var2); + + void info(String var1, Throwable var2); + + boolean isInfoEnabled(Marker var1); + + void info(Marker var1, String var2); + + void info(Marker var1, String var2, Object var3); + + void info(Marker var1, String var2, Object var3, Object var4); + + void info(Marker var1, String var2, Object[] var3); + + void info(Marker var1, String var2, Throwable var3); + + boolean isWarnEnabled(); + + void warn(String var1); + + void warn(String var1, Object var2); + + void warn(String var1, Object[] var2); + + void warn(String var1, Object var2, Object var3); + + void warn(String var1, Throwable var2); + + boolean isWarnEnabled(Marker var1); + + void warn(Marker var1, String var2); + + void warn(Marker var1, String var2, Object var3); + + void warn(Marker var1, String var2, Object var3, Object var4); + + void warn(Marker var1, String var2, Object[] var3); + + void warn(Marker var1, String var2, Throwable var3); + + boolean isErrorEnabled(); + + void error(String var1); + + void error(String var1, Object var2); + + void error(String var1, Object var2, Object var3); + + void error(String var1, Object[] var2); + + void error(String var1, Throwable var2); + + boolean isErrorEnabled(Marker var1); + + void error(Marker var1, String var2); + + void error(Marker var1, String var2, Object var3); + + void error(Marker var1, String var2, Object var3, Object var4); + + void error(Marker var1, String var2, Object[] var3); + + void error(Marker var1, String var2, Throwable var3); +} \ No newline at end of file diff --git a/java/ql/test/stubs/slf4j-api-1.6.4/org/slf4j/LoggerFactory.java b/java/ql/test/stubs/slf4j-api-1.6.4/org/slf4j/LoggerFactory.java new file mode 100644 index 000000000000..21ef158130a8 --- /dev/null +++ b/java/ql/test/stubs/slf4j-api-1.6.4/org/slf4j/LoggerFactory.java @@ -0,0 +1,21 @@ +package org.slf4j; + +import java.io.IOException; +import java.net.URL; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; + +public final class LoggerFactory { + + public static Logger getLogger(String name) { + return null; + } + + public static Logger getLogger(Class clazz) { + return null; + } + +} \ No newline at end of file diff --git a/java/ql/test/stubs/slf4j-api-1.6.4/org/slf4j/Marker.java b/java/ql/test/stubs/slf4j-api-1.6.4/org/slf4j/Marker.java new file mode 100644 index 000000000000..e6ac4846a791 --- /dev/null +++ b/java/ql/test/stubs/slf4j-api-1.6.4/org/slf4j/Marker.java @@ -0,0 +1,30 @@ +package org.slf4j; + +import java.io.Serializable; +import java.util.Iterator; + +public interface Marker extends Serializable { + String ANY_MARKER = "*"; + String ANY_NON_NULL_MARKER = "+"; + + String getName(); + + void add(Marker var1); + + boolean remove(Marker var1); + + /** @deprecated */ + boolean hasChildren(); + + boolean hasReferences(); + + Iterator iterator(); + + boolean contains(Marker var1); + + boolean contains(String var1); + + boolean equals(Object var1); + + int hashCode(); +} diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/core/annotation/AliasFor.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/core/annotation/AliasFor.java new file mode 100644 index 000000000000..edfe917400b7 --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/core/annotation/AliasFor.java @@ -0,0 +1,21 @@ +package org.springframework.core.annotation; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +@Documented +public @interface AliasFor { + @AliasFor("attribute") + String value() default ""; + + @AliasFor("value") + String attribute() default ""; + + Class annotation() default Annotation.class; +} diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/stereotype/Controller.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/stereotype/Controller.java index 0bfb67d99c18..9b1751fa2ae3 100644 --- a/java/ql/test/stubs/springframework-5.2.3/org/springframework/stereotype/Controller.java +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/stereotype/Controller.java @@ -1,9 +1,14 @@ package org.springframework.stereotype; -import java.lang.annotation.*; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; -@Target(value=ElementType.TYPE) -@Retention(value=RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) @Documented -@Component -public @interface Controller { } +public @interface Controller { + String value() default ""; +} diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/util/ObjectUtils.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/util/ObjectUtils.java new file mode 100644 index 000000000000..a5a51786d209 --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/util/ObjectUtils.java @@ -0,0 +1,202 @@ +package org.springframework.util; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.Optional; +import java.util.StringJoiner; +import org.springframework.lang.Nullable; + +public abstract class ObjectUtils { + + private static final int INITIAL_HASH = 7; + private static final int MULTIPLIER = 31; + private static final String EMPTY_STRING = ""; + private static final String NULL_STRING = "null"; + private static final String ARRAY_START = "{"; + private static final String ARRAY_END = "}"; + private static final String EMPTY_ARRAY = "{}"; + private static final String ARRAY_ELEMENT_SEPARATOR = ", "; + private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; + + public ObjectUtils() { + } + + public static boolean isCheckedException(Throwable ex) { + return false; + } + + public static boolean isCompatibleWithThrowsClause(Throwable ex, @Nullable Class... declaredExceptions) { + return false; + } + + public static boolean isArray(@Nullable Object obj) { + return false; + } + + public static boolean isEmpty(@Nullable Object[] array) { + return false; + } + + public static boolean isEmpty(@Nullable Object obj) { + return false; + } + + @Nullable + public static Object unwrapOptional(@Nullable Object obj) { + return null; + } + + public static boolean containsElement(@Nullable Object[] array, Object element) { + return true; + } + + public static boolean containsConstant(Enum[] enumValues, String constant) { + return true; + } + + public static boolean containsConstant(Enum[] enumValues, String constant, boolean caseSensitive) { + return true; + } + + public static > E caseInsensitiveValueOf(E[] enumValues, String constant) { + return null; + } + + public static A[] addObjectToArray(@Nullable A[] array, @Nullable O obj) { + return null; + } + + public static Object[] toObjectArray(@Nullable Object source) { + return null; + } + + public static boolean nullSafeEquals(@Nullable Object o1, @Nullable Object o2) { + return false; + } + + private static boolean arrayEquals(Object o1, Object o2) { + return false; + } + + public static int nullSafeHashCode(@Nullable Object obj) { + return 1; + } + + public static int nullSafeHashCode(@Nullable Object[] array) { + return 1; + } + + public static int nullSafeHashCode(@Nullable boolean[] array) { + return 1; + } + + public static int nullSafeHashCode(@Nullable byte[] array) { + return 1; + } + + public static int nullSafeHashCode(@Nullable char[] array) { + return 1; + } + + public static int nullSafeHashCode(@Nullable double[] array) { + return 1; + } + + public static int nullSafeHashCode(@Nullable float[] array) { + return 1; + } + + public static int nullSafeHashCode(@Nullable int[] array) { + return 1; + } + + public static int nullSafeHashCode(@Nullable long[] array) { + return 1; + } + + public static int nullSafeHashCode(@Nullable short[] array) { + return 1; + } + + /** @deprecated */ + @Deprecated + public static int hashCode(boolean bool) { + return 1; + } + + /** @deprecated */ + @Deprecated + public static int hashCode(double dbl) { + return 1; + } + + /** @deprecated */ + @Deprecated + public static int hashCode(float flt) { + return 1; + } + + /** @deprecated */ + @Deprecated + public static int hashCode(long lng) { + return 1; + } + + public static String identityToString(@Nullable Object obj) { + return ""; + } + + public static String getIdentityHexString(Object obj) { + return ""; + } + + public static String getDisplayString(@Nullable Object obj) { + return ""; + } + + public static String nullSafeClassName(@Nullable Object obj) { + return ""; + } + + public static String nullSafeToString(@Nullable Object obj) { + return ""; + } + + public static String nullSafeToString(@Nullable Object[] array) { + return ""; + } + + public static String nullSafeToString(@Nullable boolean[] array) { + return ""; + } + + public static String nullSafeToString(@Nullable byte[] array) { + return ""; + } + + public static String nullSafeToString(@Nullable char[] array) { + return ""; + } + + public static String nullSafeToString(@Nullable double[] array) { + return ""; + } + + public static String nullSafeToString(@Nullable float[] array) { + return ""; + } + + public static String nullSafeToString(@Nullable int[] array) { + return ""; + } + + public static String nullSafeToString(@Nullable long[] array) { + return ""; + } + + public static String nullSafeToString(@Nullable short[] array) { + return ""; + } +} diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/util/StringUtils.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/util/StringUtils.java new file mode 100644 index 000000000000..a78d47e44c6a --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/util/StringUtils.java @@ -0,0 +1,30 @@ +package org.springframework.util; + +import java.io.ByteArrayOutputStream; +import java.nio.charset.Charset; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Deque; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Properties; +import java.util.Set; +import java.util.StringJoiner; +import java.util.StringTokenizer; +import java.util.TimeZone; +import org.springframework.lang.Nullable; + +public abstract class StringUtils { + + @Deprecated + public static boolean isEmpty(@Nullable Object str) { + return true; + } + +} diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/GetMapping.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/GetMapping.java new file mode 100644 index 000000000000..541c7cd4e217 --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/GetMapping.java @@ -0,0 +1,51 @@ +package org.springframework.web.bind.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.core.annotation.AliasFor; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@RequestMapping( + method = {RequestMethod.GET} +) +public @interface GetMapping { + @AliasFor( + annotation = RequestMapping.class + ) + String name() default ""; + + @AliasFor( + annotation = RequestMapping.class + ) + String[] value() default {}; + + @AliasFor( + annotation = RequestMapping.class + ) + String[] path() default {}; + + @AliasFor( + annotation = RequestMapping.class + ) + String[] params() default {}; + + @AliasFor( + annotation = RequestMapping.class + ) + String[] headers() default {}; + + @AliasFor( + annotation = RequestMapping.class + ) + String[] consumes() default {}; + + @AliasFor( + annotation = RequestMapping.class + ) + String[] produces() default {}; +} diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/Mapping.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/Mapping.java new file mode 100644 index 000000000000..5f269bbcbb87 --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/Mapping.java @@ -0,0 +1,4 @@ +package org.springframework.web.bind.annotation; + +public @interface Mapping { +} diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestMapping.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestMapping.java index 73ff8d1b03af..093065ca5f16 100644 --- a/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestMapping.java +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestMapping.java @@ -1,11 +1,32 @@ package org.springframework.web.bind.annotation; -import java.lang.annotation.*; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.core.annotation.AliasFor; @Target(value={ElementType.METHOD,ElementType.TYPE}) @Retention(value=RetentionPolicy.RUNTIME) @Documented +@Mapping public @interface RequestMapping { + String name() default ""; + + @AliasFor("path") + String[] value() default {}; + + @AliasFor("value") + String[] path() default {}; RequestMethod[] method() default {}; + + String[] params() default {}; + + String[] headers() default {}; + + String[] consumes() default {}; + + String[] produces() default {}; } diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestParam.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestParam.java index 5ae52ad123fa..56094811c376 100644 --- a/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestParam.java +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestParam.java @@ -1,8 +1,23 @@ package org.springframework.web.bind.annotation; -import java.lang.annotation.*; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.core.annotation.AliasFor; -@Target(value=ElementType.PARAMETER) -@Retention(value=RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) @Documented -public @interface RequestParam { } +public @interface RequestParam { + @AliasFor("name") + String value() default ""; + + @AliasFor("value") + String name() default ""; + + boolean required() default true; + + String defaultValue() default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n"; +} diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/ResponseBody.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/ResponseBody.java new file mode 100644 index 000000000000..4f21b6daaaf0 --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/ResponseBody.java @@ -0,0 +1,13 @@ +package org.springframework.web.bind.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface ResponseBody { +} From 86ef2588f1599cbf0c1ed578676b0e51728823d6 Mon Sep 17 00:00:00 2001 From: haby0 Date: Thu, 8 Apr 2021 17:55:29 +0800 Subject: [PATCH 02/28] Restore @Component annotation --- .../org/springframework/stereotype/Controller.java | 1 + 1 file changed, 1 insertion(+) diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/stereotype/Controller.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/stereotype/Controller.java index 9b1751fa2ae3..a4a49fffb90a 100644 --- a/java/ql/test/stubs/springframework-5.2.3/org/springframework/stereotype/Controller.java +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/stereotype/Controller.java @@ -9,6 +9,7 @@ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented +@Component public @interface Controller { String value() default ""; } From 21004006d652b82213ff502bc3609b57afa626a9 Mon Sep 17 00:00:00 2001 From: haby0 Date: Thu, 8 Apr 2021 19:17:04 +0800 Subject: [PATCH 03/28] Update java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll Co-authored-by: Chris Smowton --- .../Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll index 0598f8438404..a3204dc109a4 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll @@ -41,7 +41,7 @@ class SplitMethod extends Method { } /** - * A call to the ServletRequest.getHeader method and the argument are + * A call to the ServletRequest.getHeader method with an argument * `wl-proxy-client-ip`/`proxy-client-ip`/`http_client_ip`/`http_x_forwarded_for`/`x-real-ip`. */ class HeaderIpCall extends MethodAccess { From bfbfe7af13617476188fa08f69d5f670727c721f Mon Sep 17 00:00:00 2001 From: haby0 Date: Thu, 8 Apr 2021 19:21:58 +0800 Subject: [PATCH 04/28] Update java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp Co-authored-by: Chris Smowton --- .../Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp index 1c3a4485cbd8..031aa0d11fdb 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp @@ -24,9 +24,8 @@ and get the last value of the split array.

    -
  • Prevent IP address spoofing with X-Forwarded-For header when using AWS ELB and Clojure Ring: - -Prevent IP address spoofing with... +
  • Dennis Schneider: +Prevent IP address spoofing with X-Forwarded-For header when using AWS ELB and Clojure Ring
  • Security Rule Zero: A Warning about X-Forwarded-For: From 1da48ed4d10b1ed7ed9585c647db3094f1d813e3 Mon Sep 17 00:00:00 2001 From: haby0 Date: Thu, 8 Apr 2021 19:22:14 +0800 Subject: [PATCH 05/28] Update java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp Co-authored-by: Chris Smowton --- .../Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp index 031aa0d11fdb..6b2eb2e7eed1 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp @@ -28,9 +28,7 @@ and get the last value of the split array.

    Prevent IP address spoofing with X-Forwarded-For header when using AWS ELB and Clojure Ring
  • -
  • Security Rule Zero: A Warning about X-Forwarded-For: - -Security Rule Zero: A Warning about X-Forwarded-For +
  • Security Rule Zero: A Warning about X-Forwarded-For
  • From 8296abcea84e89166608fa603025101c4c8eca47 Mon Sep 17 00:00:00 2001 From: haby0 Date: Mon, 19 Apr 2021 20:59:47 +0800 Subject: [PATCH 06/28] Fix Modify the ql query (the qhelp part is not modified). --- .../CWE/CWE-348/UseOfLessTrustedSource.java | 38 +++-- .../CWE/CWE-348/UseOfLessTrustedSource.qhelp | 9 +- .../CWE/CWE-348/UseOfLessTrustedSource.ql | 27 ++-- .../CWE/CWE-348/UseOfLessTrustedSourceLib.qll | 146 +++++++++++------- .../CWE-348/UseOfLessTrustedSource.expected | 56 +++++-- .../CWE-348/UseOfLessTrustedSource.java | 38 +++-- .../query-tests/security/CWE-348/options | 3 +- .../beans/factory/annotation/Autowired.java | 14 ++ 8 files changed, 213 insertions(+), 118 deletions(-) create mode 100644 java/ql/test/stubs/springframework-5.2.3/org/springframework/beans/factory/annotation/Autowired.java diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.java b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.java index d338134ed3ed..22d7100223ce 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.java +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.java @@ -2,6 +2,7 @@ import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; @@ -11,24 +12,13 @@ public class UseOfLessTrustedSource { private static final Logger log = LoggerFactory.getLogger(UseOfLessTrustedSource.class); - @GetMapping(value = "bad1") - @ResponseBody - public String bad1(HttpServletRequest request) { - String remoteAddr = ""; - if (request != null) { - remoteAddr = request.getHeader("X-FORWARDED-FOR"); - remoteAddr = remoteAddr.split(",")[0]; - if (remoteAddr == null || "".equals(remoteAddr)) { - remoteAddr = request.getRemoteAddr(); - } - } - return remoteAddr; - } + @Autowired + private HttpServletRequest request; - @GetMapping(value = "bad2") - public void bad2(HttpServletRequest request) { + @GetMapping(value = "bad1") + public void bad1(HttpServletRequest request) { String ip = request.getHeader("X-Forwarded-For"); - + log.debug("getClientIP header X-Forwarded-For:{}", ip); if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase("unknown", ip)) { @@ -58,6 +48,14 @@ public void bad2(HttpServletRequest request) { System.out.println("client ip is: " + ip); } + @GetMapping(value = "bad2") + public void bad2(HttpServletRequest request) { + String ip = getClientIP(); + if (!StringUtils.startsWith(ip, "192.168.")) { + new Exception("ip illegal"); + } + } + @GetMapping(value = "good1") @ResponseBody public String good1(HttpServletRequest request) { @@ -71,4 +69,12 @@ public String good1(HttpServletRequest request) { } return remoteAddr; } + + protected String getClientIP() { + String xfHeader = request.getHeader("X-Forwarded-For"); + if (xfHeader == null) { + return request.getRemoteAddr(); + } + return xfHeader.split(",")[0]; + } } \ No newline at end of file diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp index 1c3a4485cbd8..6b2eb2e7eed1 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp @@ -24,14 +24,11 @@ and get the last value of the split array.

    -
  • Prevent IP address spoofing with X-Forwarded-For header when using AWS ELB and Clojure Ring: - -Prevent IP address spoofing with... +
  • Dennis Schneider: +Prevent IP address spoofing with X-Forwarded-For header when using AWS ELB and Clojure Ring
  • -
  • Security Rule Zero: A Warning about X-Forwarded-For: - -Security Rule Zero: A Warning about X-Forwarded-For +
  • Security Rule Zero: A Warning about X-Forwarded-For
  • diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql index 15d665d37abd..3c3feeb8f031 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql @@ -1,20 +1,23 @@ /** - * @name X-Forwarded-For spoofing + * @name IP address spoofing * @description The software obtains the client ip through `X-Forwarded-For`, * and the attacker can modify the value of `X-Forwarded-For` to forge the ip. * @kind path-problem * @problem.severity error * @precision high - * @id java/use-of-less-trusted-source + * @id java/ip-address-spoofing * @tags security * external/cwe/cwe-348 */ import java import UseOfLessTrustedSourceLib +import semmle.code.java.dataflow.DataFlow2 +import semmle.code.java.dataflow.TaintTracking2 import semmle.code.java.dataflow.FlowSources import DataFlow::PathGraph +/** Taint-tracking configuration tracing flow from get method request sources to output jsonp data. */ class UseOfLessTrustedSourceConfig extends TaintTracking::Configuration { UseOfLessTrustedSourceConfig() { this = "UseOfLessTrustedSourceConfig" } @@ -23,22 +26,26 @@ class UseOfLessTrustedSourceConfig extends TaintTracking::Configuration { override predicate isSink(DataFlow::Node sink) { sink instanceof UseOfLessTrustedSink } /** - * When using `,` split request data and not taking the first value of - * the array, it is considered as `good`. + * When using `,` split request data and not taking the first value of the array, it is considered as `good`. */ override predicate isSanitizer(DataFlow::Node node) { exists(ArrayAccess aa, MethodAccess ma | aa.getArray() = ma | ma.getQualifier() = node.asExpr() and ma.getMethod() instanceof SplitMethod and - not aa.getIndexExpr().toString() = "0" + not aa.getIndexExpr().(CompileTimeConstantExpr).getIntValue() = 0 + ) + } + + override predicate isAdditionalTaintStep(DataFlow::Node prod, DataFlow::Node succ) { + exists(MethodAccess ma | + ma.getAnArgument() = prod.asExpr() and + ma = succ.asExpr() and + ma.getMethod().getReturnType() instanceof BooleanType ) } } from DataFlow::PathNode source, DataFlow::PathNode sink, UseOfLessTrustedSourceConfig conf -where - conf.hasFlowPath(source, sink) and - source.getNode().getEnclosingCallable() = sink.getNode().getEnclosingCallable() and - xffIsFirstGet(source.getNode()) -select sink.getNode(), source, sink, "X-Forwarded-For spoofing might include code from $@.", +where conf.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "IP address spoofing might include code from $@.", source.getNode(), "this user input" diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll index 0598f8438404..ac72be64f5d5 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll @@ -1,24 +1,105 @@ import java import DataFlow -import semmle.code.java.dataflow.DataFlow +import semmle.code.java.dataflow.TaintTracking2 +import semmle.code.java.security.QueryInjection import experimental.semmle.code.java.Logging -/** A data flow source of the value of the `x-forwarded-for` field in the `header`. */ +/** + * A data flow source of the client ip obtained according to the remote endpoint identifier specified + * in the header (`X-Forwarded-For`, `X-Real-IP`, `Proxy-Client-IP`, etc.). + * + * For example: `ServletRequest.getHeader("X-Forwarded-For")`. + */ class UseOfLessTrustedSource extends DataFlow::Node { UseOfLessTrustedSource() { exists(MethodAccess ma | ma.getMethod().hasName("getHeader") and - ma.getArgument(0).toString().toLowerCase() = "\"x-forwarded-for\"" and + ma.getArgument(0).(CompileTimeConstantExpr).getStringValue().toLowerCase() in [ + "x-forwarded-for", "x-real-ip", "proxy-client-ip", "wl-proxy-client-ip", + "http_x_forwarded_for", "http_x_forwarded", "http_x_cluster_client_ip", "http_client_ip", + "http_forwarded_for", "http_forwarded", "http_via", "remote_addr" + ] and ma = this.asExpr() ) } } -/** A data flow sink of method return or log output or local print. */ -class UseOfLessTrustedSink extends DataFlow::Node { - UseOfLessTrustedSink() { - exists(ReturnStmt rs | rs.getResult() = this.asExpr()) - or +/** A data flow sink for ip address forgery vulnerabilities. */ +abstract class UseOfLessTrustedSink extends DataFlow::Node { } + +/** + * A data flow sink for the if condition, which does not include the null judgment of the remote client ip address. + * + * For example: `if (!StringUtils.startsWith(ipAddr, "192.168.")){...` determine whether the client ip starts + * with `192.168.`, and the program can be deceived by forging the ip address. + * `if (remoteAddr == null || "".equals(remoteAddr)) {...` judging whether the client ip is a null value, + * it needs to be excluded + */ +private class IfConditionSink extends UseOfLessTrustedSink { + IfConditionSink() { + exists(IfStmt is | + is.getCondition() = this.asExpr() and + not exists(EQExpr eqe | + eqe.getAnOperand() instanceof NullLiteral and + is.getCondition() = eqe.getParent*() + ) and + not exists(NEExpr nee | + nee.getAnOperand() instanceof NullLiteral and + is.getCondition() = nee.getParent*() + ) and + not exists(MethodAccess ma | + ma.getMethod().hasName("equals") and + ma.getMethod().getNumberOfParameters() = 1 and + ( + ma.getQualifier().(CompileTimeConstantExpr).getStringValue() = "" or + ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() = "" + ) and + is.getCondition() = ma.getParent*() + ) and + not exists(MethodAccess ma | + ma.getMethod().hasName("equalsIgnoreCase") and + ma.getMethod().getNumberOfParameters() = 1 and + ( + ma.getQualifier().(CompileTimeConstantExpr).getStringValue() = "unknown" or + ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() = "unknown" + ) and + is.getCondition() = ma.getParent*() + ) and + not exists(MethodAccess ma | + ma.getMethod().getName() in ["isEmpty", "isNotEmpty"] and + ma.getMethod().getNumberOfParameters() = 1 and + is.getCondition() = ma.getParent*() + ) and + not exists(MethodAccess ma | + ( + ma.getMethod().hasQualifiedName("org.apache.commons.lang3", "StringUtils", "isBlank") or + ma.getMethod().hasQualifiedName("org.apache.commons.lang3", "StringUtils", "isNotBlank") + ) and + is.getCondition() = ma.getParent*() + ) and + not exists(MethodAccess ma | + ma.getMethod() + .hasQualifiedName("org.apache.commons.lang3", "StringUtils", "equalsIgnoreCase") and + ma.getAnArgument().(CompileTimeConstantExpr).getStringValue() = "unknown" and + is.getCondition() = ma.getParent*() + ) + ) + } +} + +/** A data flow sink for sql operation. */ +private class SqlOperationSink extends UseOfLessTrustedSink { + SqlOperationSink() { this instanceof QueryInjectionSink } +} + +/** A data flow sink for log operation. */ +private class LogOperationSink extends UseOfLessTrustedSink { + LogOperationSink() { exists(LoggingCall lc | lc.getAnArgument() = this.asExpr()) } +} + +/** A data flow sink for local output. */ +private class PrintSink extends UseOfLessTrustedSink { + PrintSink() { exists(MethodAccess ma | ma.getMethod().getName() in ["print", "println"] and ( @@ -27,8 +108,6 @@ class UseOfLessTrustedSink extends DataFlow::Node { ) and ma.getAnArgument() = this.asExpr() ) - or - exists(LoggingCall lc | lc.getAnArgument() = this.asExpr()) } } @@ -39,50 +118,3 @@ class SplitMethod extends Method { this.hasQualifiedName("java.lang", "String", "split") } } - -/** - * A call to the ServletRequest.getHeader method and the argument are - * `wl-proxy-client-ip`/`proxy-client-ip`/`http_client_ip`/`http_x_forwarded_for`/`x-real-ip`. - */ -class HeaderIpCall extends MethodAccess { - HeaderIpCall() { - this.getMethod().hasName("getHeader") and - this.getMethod() - .getDeclaringType() - .getASupertype*() - .hasQualifiedName("javax.servlet", "ServletRequest") and - this.getArgument(0).toString().toLowerCase() in [ - "\"wl-proxy-client-ip\"", "\"proxy-client-ip\"", "\"http_client_ip\"", - "\"http_x_forwarded_for\"", "\"x-real-ip\"" - ] - } -} - -/** A call to `ServletRequest.getRemoteAddr` method. */ -class RemoteAddrCall extends MethodAccess { - RemoteAddrCall() { - this.getMethod().hasName("getRemoteAddr") and - this.getMethod() - .getDeclaringType() - .getASupertype*() - .hasQualifiedName("javax.servlet", "ServletRequest") - } -} - -/** The first one in the method to get the ip value through `x-forwarded-for`. */ -predicate xffIsFirstGet(Node node) { - exists(HeaderIpCall hic | - node.getEnclosingCallable() = hic.getEnclosingCallable() and - node.getLocation().getEndLine() < hic.getLocation().getEndLine() - ) - or - exists(RemoteAddrCall rac | - node.getEnclosingCallable() = rac.getEnclosingCallable() and - node.getLocation().getEndLine() < rac.getLocation().getEndLine() - ) - or - not exists(HeaderIpCall hic, RemoteAddrCall rac | - node.getEnclosingCallable() = hic.getEnclosingCallable() and - node.getEnclosingCallable() = rac.getEnclosingCallable() - ) -} diff --git a/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.expected b/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.expected index a71ad9629ceb..65bbc8d218b5 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.expected @@ -1,14 +1,48 @@ edges -| UseOfLessTrustedSource.java:19:26:19:61 | getHeader(...) : String | UseOfLessTrustedSource.java:25:16:25:25 | remoteAddr | -| UseOfLessTrustedSource.java:30:21:30:56 | getHeader(...) : String | UseOfLessTrustedSource.java:32:60:32:61 | ip | -| UseOfLessTrustedSource.java:30:21:30:56 | getHeader(...) : String | UseOfLessTrustedSource.java:58:28:58:48 | ... + ... | +| UseOfLessTrustedSource.java:20:21:20:56 | getHeader(...) : String | UseOfLessTrustedSource.java:22:60:22:61 | ip | +| UseOfLessTrustedSource.java:20:21:20:56 | getHeader(...) : String | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | +| UseOfLessTrustedSource.java:25:18:25:53 | getHeader(...) : String | UseOfLessTrustedSource.java:26:64:26:65 | ip | +| UseOfLessTrustedSource.java:25:18:25:53 | getHeader(...) : String | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | +| UseOfLessTrustedSource.java:29:18:29:56 | getHeader(...) : String | UseOfLessTrustedSource.java:30:67:30:68 | ip | +| UseOfLessTrustedSource.java:29:18:29:56 | getHeader(...) : String | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | +| UseOfLessTrustedSource.java:33:18:33:52 | getHeader(...) : String | UseOfLessTrustedSource.java:34:63:34:64 | ip | +| UseOfLessTrustedSource.java:33:18:33:52 | getHeader(...) : String | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | +| UseOfLessTrustedSource.java:37:18:37:58 | getHeader(...) : String | UseOfLessTrustedSource.java:38:69:38:70 | ip | +| UseOfLessTrustedSource.java:37:18:37:58 | getHeader(...) : String | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | +| UseOfLessTrustedSource.java:41:18:41:47 | getHeader(...) : String | UseOfLessTrustedSource.java:42:58:42:59 | ip | +| UseOfLessTrustedSource.java:41:18:41:47 | getHeader(...) : String | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | +| UseOfLessTrustedSource.java:53:21:53:33 | getClientIP(...) : String | UseOfLessTrustedSource.java:54:13:54:51 | !... | +| UseOfLessTrustedSource.java:74:27:74:62 | getHeader(...) : String | UseOfLessTrustedSource.java:78:16:78:37 | ...[...] : String | +| UseOfLessTrustedSource.java:78:16:78:37 | ...[...] : String | UseOfLessTrustedSource.java:53:21:53:33 | getClientIP(...) : String | nodes -| UseOfLessTrustedSource.java:19:26:19:61 | getHeader(...) : String | semmle.label | getHeader(...) : String | -| UseOfLessTrustedSource.java:25:16:25:25 | remoteAddr | semmle.label | remoteAddr | -| UseOfLessTrustedSource.java:30:21:30:56 | getHeader(...) : String | semmle.label | getHeader(...) : String | -| UseOfLessTrustedSource.java:32:60:32:61 | ip | semmle.label | ip | -| UseOfLessTrustedSource.java:58:28:58:48 | ... + ... | semmle.label | ... + ... | +| UseOfLessTrustedSource.java:20:21:20:56 | getHeader(...) : String | semmle.label | getHeader(...) : String | +| UseOfLessTrustedSource.java:22:60:22:61 | ip | semmle.label | ip | +| UseOfLessTrustedSource.java:25:18:25:53 | getHeader(...) : String | semmle.label | getHeader(...) : String | +| UseOfLessTrustedSource.java:26:64:26:65 | ip | semmle.label | ip | +| UseOfLessTrustedSource.java:29:18:29:56 | getHeader(...) : String | semmle.label | getHeader(...) : String | +| UseOfLessTrustedSource.java:30:67:30:68 | ip | semmle.label | ip | +| UseOfLessTrustedSource.java:33:18:33:52 | getHeader(...) : String | semmle.label | getHeader(...) : String | +| UseOfLessTrustedSource.java:34:63:34:64 | ip | semmle.label | ip | +| UseOfLessTrustedSource.java:37:18:37:58 | getHeader(...) : String | semmle.label | getHeader(...) : String | +| UseOfLessTrustedSource.java:38:69:38:70 | ip | semmle.label | ip | +| UseOfLessTrustedSource.java:41:18:41:47 | getHeader(...) : String | semmle.label | getHeader(...) : String | +| UseOfLessTrustedSource.java:42:58:42:59 | ip | semmle.label | ip | +| UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | semmle.label | ... + ... | +| UseOfLessTrustedSource.java:53:21:53:33 | getClientIP(...) : String | semmle.label | getClientIP(...) : String | +| UseOfLessTrustedSource.java:54:13:54:51 | !... | semmle.label | !... | +| UseOfLessTrustedSource.java:74:27:74:62 | getHeader(...) : String | semmle.label | getHeader(...) : String | +| UseOfLessTrustedSource.java:78:16:78:37 | ...[...] : String | semmle.label | ...[...] : String | #select -| UseOfLessTrustedSource.java:25:16:25:25 | remoteAddr | UseOfLessTrustedSource.java:19:26:19:61 | getHeader(...) : String | UseOfLessTrustedSource.java:25:16:25:25 | remoteAddr | X-Forwarded-For spoofing might include code from $@. | UseOfLessTrustedSource.java:19:26:19:61 | getHeader(...) | this user input | -| UseOfLessTrustedSource.java:32:60:32:61 | ip | UseOfLessTrustedSource.java:30:21:30:56 | getHeader(...) : String | UseOfLessTrustedSource.java:32:60:32:61 | ip | X-Forwarded-For spoofing might include code from $@. | UseOfLessTrustedSource.java:30:21:30:56 | getHeader(...) | this user input | -| UseOfLessTrustedSource.java:58:28:58:48 | ... + ... | UseOfLessTrustedSource.java:30:21:30:56 | getHeader(...) : String | UseOfLessTrustedSource.java:58:28:58:48 | ... + ... | X-Forwarded-For spoofing might include code from $@. | UseOfLessTrustedSource.java:30:21:30:56 | getHeader(...) | this user input | +| UseOfLessTrustedSource.java:22:60:22:61 | ip | UseOfLessTrustedSource.java:20:21:20:56 | getHeader(...) : String | UseOfLessTrustedSource.java:22:60:22:61 | ip | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:20:21:20:56 | getHeader(...) | this user input | +| UseOfLessTrustedSource.java:26:64:26:65 | ip | UseOfLessTrustedSource.java:25:18:25:53 | getHeader(...) : String | UseOfLessTrustedSource.java:26:64:26:65 | ip | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:25:18:25:53 | getHeader(...) | this user input | +| UseOfLessTrustedSource.java:30:67:30:68 | ip | UseOfLessTrustedSource.java:29:18:29:56 | getHeader(...) : String | UseOfLessTrustedSource.java:30:67:30:68 | ip | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:29:18:29:56 | getHeader(...) | this user input | +| UseOfLessTrustedSource.java:34:63:34:64 | ip | UseOfLessTrustedSource.java:33:18:33:52 | getHeader(...) : String | UseOfLessTrustedSource.java:34:63:34:64 | ip | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:33:18:33:52 | getHeader(...) | this user input | +| UseOfLessTrustedSource.java:38:69:38:70 | ip | UseOfLessTrustedSource.java:37:18:37:58 | getHeader(...) : String | UseOfLessTrustedSource.java:38:69:38:70 | ip | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:37:18:37:58 | getHeader(...) | this user input | +| UseOfLessTrustedSource.java:42:58:42:59 | ip | UseOfLessTrustedSource.java:41:18:41:47 | getHeader(...) : String | UseOfLessTrustedSource.java:42:58:42:59 | ip | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:41:18:41:47 | getHeader(...) | this user input | +| UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | UseOfLessTrustedSource.java:20:21:20:56 | getHeader(...) : String | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:20:21:20:56 | getHeader(...) | this user input | +| UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | UseOfLessTrustedSource.java:25:18:25:53 | getHeader(...) : String | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:25:18:25:53 | getHeader(...) | this user input | +| UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | UseOfLessTrustedSource.java:29:18:29:56 | getHeader(...) : String | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:29:18:29:56 | getHeader(...) | this user input | +| UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | UseOfLessTrustedSource.java:33:18:33:52 | getHeader(...) : String | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:33:18:33:52 | getHeader(...) | this user input | +| UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | UseOfLessTrustedSource.java:37:18:37:58 | getHeader(...) : String | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:37:18:37:58 | getHeader(...) | this user input | +| UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | UseOfLessTrustedSource.java:41:18:41:47 | getHeader(...) : String | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:41:18:41:47 | getHeader(...) | this user input | +| UseOfLessTrustedSource.java:54:13:54:51 | !... | UseOfLessTrustedSource.java:74:27:74:62 | getHeader(...) : String | UseOfLessTrustedSource.java:54:13:54:51 | !... | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:74:27:74:62 | getHeader(...) | this user input | diff --git a/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.java b/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.java index d338134ed3ed..22d7100223ce 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.java +++ b/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.java @@ -2,6 +2,7 @@ import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; @@ -11,24 +12,13 @@ public class UseOfLessTrustedSource { private static final Logger log = LoggerFactory.getLogger(UseOfLessTrustedSource.class); - @GetMapping(value = "bad1") - @ResponseBody - public String bad1(HttpServletRequest request) { - String remoteAddr = ""; - if (request != null) { - remoteAddr = request.getHeader("X-FORWARDED-FOR"); - remoteAddr = remoteAddr.split(",")[0]; - if (remoteAddr == null || "".equals(remoteAddr)) { - remoteAddr = request.getRemoteAddr(); - } - } - return remoteAddr; - } + @Autowired + private HttpServletRequest request; - @GetMapping(value = "bad2") - public void bad2(HttpServletRequest request) { + @GetMapping(value = "bad1") + public void bad1(HttpServletRequest request) { String ip = request.getHeader("X-Forwarded-For"); - + log.debug("getClientIP header X-Forwarded-For:{}", ip); if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase("unknown", ip)) { @@ -58,6 +48,14 @@ public void bad2(HttpServletRequest request) { System.out.println("client ip is: " + ip); } + @GetMapping(value = "bad2") + public void bad2(HttpServletRequest request) { + String ip = getClientIP(); + if (!StringUtils.startsWith(ip, "192.168.")) { + new Exception("ip illegal"); + } + } + @GetMapping(value = "good1") @ResponseBody public String good1(HttpServletRequest request) { @@ -71,4 +69,12 @@ public String good1(HttpServletRequest request) { } return remoteAddr; } + + protected String getClientIP() { + String xfHeader = request.getHeader("X-Forwarded-For"); + if (xfHeader == null) { + return request.getRemoteAddr(); + } + return xfHeader.split(",")[0]; + } } \ No newline at end of file diff --git a/java/ql/test/experimental/query-tests/security/CWE-348/options b/java/ql/test/experimental/query-tests/security/CWE-348/options index 40bcee8c133d..7edf075ba8e8 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-348/options +++ b/java/ql/test/experimental/query-tests/security/CWE-348/options @@ -1,2 +1 @@ -//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../../stubs/springframework-5.2.3/:${tes -tdir}/../../../../stubs/slf4j-api-1.6.4/:${testdir}/../../../../stubs/apache-commons-lang3-3.7/ \ No newline at end of file +//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../../stubs/springframework-5.2.3/:${testdir}/../../../../stubs/slf4j-api-1.6.4/:${testdir}/../../../../stubs/apache-commons-lang3-3.7/ \ No newline at end of file diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/beans/factory/annotation/Autowired.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/beans/factory/annotation/Autowired.java new file mode 100644 index 000000000000..8fcef9793603 --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/beans/factory/annotation/Autowired.java @@ -0,0 +1,14 @@ +package org.springframework.beans.factory.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Autowired { + boolean required() default true; +} \ No newline at end of file From 00531588840481bfa6f67692e555d1203f09b359 Mon Sep 17 00:00:00 2001 From: haby0 Date: Tue, 20 Apr 2021 10:58:54 +0800 Subject: [PATCH 07/28] update qhelp file and ql comments --- .../CWE/CWE-348/UseOfLessTrustedSource.qhelp | 12 +++++++----- .../Security/CWE/CWE-348/UseOfLessTrustedSource.ql | 11 ++++++----- .../CWE/CWE-348/UseOfLessTrustedSourceLib.qll | 3 +-- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp index 6b2eb2e7eed1..48923b85f025 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp @@ -3,8 +3,9 @@ "qhelp.dtd"> -

    The software obtains the original client IP address through the http header X-Forwarded-For, which is used to ensure -security or track it in the log for statistical or other reasons. Attackers can use X-Forwarded-For Spoofing software.

    +

    The software obtains the original client IP address through the http header (X-Forwarded-For or X-Real-IP or Proxy-Client-IP +etc.), which is used to ensure security or track it in the log for statistical or other reasons. Attackers can forge the value of these identifiers to attack the +software.

    @@ -15,9 +16,10 @@ security or track it in the log for statistical or other reasons. Attackers can

    The following examples show the bad case and the good case respectively. Bad case, such as bad1 to bad2. -In the bad1 method, the value of X-Forwarded-For in header is split, and the first value of -the split array is obtained. Good case, such as good1, split the value of X-Forwarded-For in header -and get the last value of the split array.

    +In the bad1 method, obtain the client ip according to the specified identifier from the header for local +output and logging. In the bad2 method, the client ip is obtained and judged according to the specified identifier +from the header. When used for permission verification, it can be bypassed by forging the ip. Good case, such as +good1, split the value of X-Forwarded-For in header and get the last value of the split array.

    diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql index 3c3feeb8f031..df019a7c765d 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql @@ -1,7 +1,8 @@ /** * @name IP address spoofing - * @description The software obtains the client ip through `X-Forwarded-For`, - * and the attacker can modify the value of `X-Forwarded-For` to forge the ip. + * @description The software obtains the client ip from the remote endpoint identifier specified (`X-Forwarded-For`, + * `X-Real-IP`, `Proxy-Client-IP`, etc.) in the header and uses it. Attackers can modify these The value + * of the identifier to forge the client ip. * @kind path-problem * @problem.severity error * @precision high @@ -12,12 +13,12 @@ import java import UseOfLessTrustedSourceLib -import semmle.code.java.dataflow.DataFlow2 -import semmle.code.java.dataflow.TaintTracking2 import semmle.code.java.dataflow.FlowSources import DataFlow::PathGraph -/** Taint-tracking configuration tracing flow from get method request sources to output jsonp data. */ +/** + * Taint-tracking configuration tracing flow from obtain client ip to use the client ip. + */ class UseOfLessTrustedSourceConfig extends TaintTracking::Configuration { UseOfLessTrustedSourceConfig() { this = "UseOfLessTrustedSourceConfig" } diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll index ac72be64f5d5..64bf84380922 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll @@ -1,12 +1,11 @@ import java import DataFlow -import semmle.code.java.dataflow.TaintTracking2 import semmle.code.java.security.QueryInjection import experimental.semmle.code.java.Logging /** * A data flow source of the client ip obtained according to the remote endpoint identifier specified - * in the header (`X-Forwarded-For`, `X-Real-IP`, `Proxy-Client-IP`, etc.). + * (`X-Forwarded-For`, `X-Real-IP`, `Proxy-Client-IP`, etc.) in the header. * * For example: `ServletRequest.getHeader("X-Forwarded-For")`. */ From b60bffaf83fb8cbd87ea87e76751d783a8a6275a Mon Sep 17 00:00:00 2001 From: haby0 Date: Tue, 20 Apr 2021 19:31:59 +0800 Subject: [PATCH 08/28] Update java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll Co-authored-by: Chris Smowton --- .../Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll index 64bf84380922..98c754c77273 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll @@ -102,8 +102,7 @@ private class PrintSink extends UseOfLessTrustedSink { exists(MethodAccess ma | ma.getMethod().getName() in ["print", "println"] and ( - ma.getMethod().getDeclaringType().hasQualifiedName("java.io", "PrintWriter") or - ma.getMethod().getDeclaringType().hasQualifiedName("java.io", "PrintStream") + ma.getMethod().getDeclaringType().hasQualifiedName("java.io", ["PrintWriter", "PrintStream"]) ) and ma.getAnArgument() = this.asExpr() ) From 0b1637a4099416de032b701bd20d812bfaa7560e Mon Sep 17 00:00:00 2001 From: haby0 Date: Tue, 20 Apr 2021 19:32:39 +0800 Subject: [PATCH 09/28] Update java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp Co-authored-by: Chris Smowton --- .../Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp index 48923b85f025..3b56a9650bd0 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp @@ -3,7 +3,7 @@ "qhelp.dtd"> -

    The software obtains the original client IP address through the http header (X-Forwarded-For or X-Real-IP or Proxy-Client-IP +

    An original client IP address is retrieved from an http header (X-Forwarded-For or X-Real-IP or Proxy-Client-IP etc.), which is used to ensure security or track it in the log for statistical or other reasons. Attackers can forge the value of these identifiers to attack the software.

    From d82878ac3b833bfbc006836f01895fa34bd00542 Mon Sep 17 00:00:00 2001 From: haby0 Date: Tue, 20 Apr 2021 19:33:06 +0800 Subject: [PATCH 10/28] Update java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp Co-authored-by: Chris Smowton --- .../Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp index 3b56a9650bd0..e195cda1cdd3 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp @@ -4,8 +4,8 @@

    An original client IP address is retrieved from an http header (X-Forwarded-For or X-Real-IP or Proxy-Client-IP -etc.), which is used to ensure security or track it in the log for statistical or other reasons. Attackers can forge the value of these identifiers to attack the -software.

    +etc.), which is used to ensure security or track it in the log for statistical or other reasons. Attackers can forge the value of these identifiers to +bypass a ban-list, for example.

    From 9ece4dac0ff03b68d84e639517686b0721dcba09 Mon Sep 17 00:00:00 2001 From: haby0 Date: Tue, 20 Apr 2021 19:33:47 +0800 Subject: [PATCH 11/28] Update java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp Co-authored-by: Chris Smowton --- .../Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp index e195cda1cdd3..c060eaf3e511 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp @@ -10,7 +10,7 @@ bypass a ban-list, for example.

    -

    When the software is not using a proxy server, get the last ip.

    +

    Do not trust the values of HTTP headers allegedly identifying the originating IP. If you are aware your application will run behind some reverse proxies then the last entry of a X-Forwarded-For header value may be more trustworthy than the rest of it because some reverse proxies append the IP address they observed to the end of any remote-supplied header.

    From 408dd31d3cf2b35bf524648e621553f6028c0a30 Mon Sep 17 00:00:00 2001 From: haby0 Date: Tue, 20 Apr 2021 19:34:37 +0800 Subject: [PATCH 12/28] Update java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp Co-authored-by: Chris Smowton --- .../Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp index c060eaf3e511..15508f049770 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp @@ -15,11 +15,10 @@ bypass a ban-list, for example.

    -

    The following examples show the bad case and the good case respectively. Bad case, such as bad1 to bad2. -In the bad1 method, obtain the client ip according to the specified identifier from the header for local -output and logging. In the bad2 method, the client ip is obtained and judged according to the specified identifier -from the header. When used for permission verification, it can be bypassed by forging the ip. Good case, such as -good1, split the value of X-Forwarded-For in header and get the last value of the split array.

    +

    The following examples show the bad case and the good case respectively. +In the bad1 method, the client ip is obtained from an HTTP header for local +output and logging. In the bad2 method, the client ip the X-Forwarded-For is split into comma-separated values, but the less-trustworthy first one is used. Both of these examples could be deceived by providing a forged HTTP header. The method +good1 similarly splits an X-Forwarded-For value, but uses the last, more-trustworthy entry.

    From 9e87f4ec4e1e97baafa7b2677aeb87e8dd7d82e7 Mon Sep 17 00:00:00 2001 From: haby0 Date: Tue, 20 Apr 2021 19:35:34 +0800 Subject: [PATCH 13/28] Update java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql Co-authored-by: Chris Smowton --- .../Security/CWE/CWE-348/UseOfLessTrustedSource.ql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql index df019a7c765d..89c0dbd615a0 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql @@ -27,7 +27,8 @@ class UseOfLessTrustedSourceConfig extends TaintTracking::Configuration { override predicate isSink(DataFlow::Node sink) { sink instanceof UseOfLessTrustedSink } /** - * When using `,` split request data and not taking the first value of the array, it is considered as `good`. + * Splitting a header value by `,` and taking an entry other than the first is sanitizing, because + * later entries may originate from more-trustworthy intermediate proxies, not the original client. */ override predicate isSanitizer(DataFlow::Node node) { exists(ArrayAccess aa, MethodAccess ma | aa.getArray() = ma | From b1ee864ad94a24a36245e1796a0c03e13adedfe3 Mon Sep 17 00:00:00 2001 From: haby0 Date: Tue, 20 Apr 2021 19:35:52 +0800 Subject: [PATCH 14/28] Update java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql Co-authored-by: Chris Smowton --- .../Security/CWE/CWE-348/UseOfLessTrustedSource.ql | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql index 89c0dbd615a0..80b5deec5782 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql @@ -1,7 +1,6 @@ /** * @name IP address spoofing - * @description The software obtains the client ip from the remote endpoint identifier specified (`X-Forwarded-For`, - * `X-Real-IP`, `Proxy-Client-IP`, etc.) in the header and uses it. Attackers can modify these The value + * @description A remote endpoint identifier is read from an HTTP header. Attackers can modify the value * of the identifier to forge the client ip. * @kind path-problem * @problem.severity error From 3e376f95c49bdbc9d313d33f268c121f821a1abb Mon Sep 17 00:00:00 2001 From: haby0 Date: Tue, 20 Apr 2021 19:36:16 +0800 Subject: [PATCH 15/28] Update java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql Co-authored-by: Chris Smowton --- .../experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql index 80b5deec5782..e1d2a28c9397 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql @@ -16,7 +16,7 @@ import semmle.code.java.dataflow.FlowSources import DataFlow::PathGraph /** - * Taint-tracking configuration tracing flow from obtain client ip to use the client ip. + * Taint-tracking configuration tracing flow from obtaining a client ip from an HTTP header to a sensitive use. */ class UseOfLessTrustedSourceConfig extends TaintTracking::Configuration { UseOfLessTrustedSourceConfig() { this = "UseOfLessTrustedSourceConfig" } From 84f00c21df471cc39f38f4c5b467895f4b2f7280 Mon Sep 17 00:00:00 2001 From: haby0 Date: Wed, 21 Apr 2021 15:38:41 +0800 Subject: [PATCH 16/28] update IfConditionSink. --- .../CWE/CWE-348/UseOfLessTrustedSourceLib.qll | 94 ++++++++++--------- 1 file changed, 48 insertions(+), 46 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll index 98c754c77273..fa9ec4da9bb6 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll @@ -38,49 +38,53 @@ private class IfConditionSink extends UseOfLessTrustedSink { IfConditionSink() { exists(IfStmt is | is.getCondition() = this.asExpr() and - not exists(EQExpr eqe | - eqe.getAnOperand() instanceof NullLiteral and - is.getCondition() = eqe.getParent*() - ) and - not exists(NEExpr nee | - nee.getAnOperand() instanceof NullLiteral and - is.getCondition() = nee.getParent*() - ) and - not exists(MethodAccess ma | - ma.getMethod().hasName("equals") and - ma.getMethod().getNumberOfParameters() = 1 and - ( - ma.getQualifier().(CompileTimeConstantExpr).getStringValue() = "" or - ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() = "" - ) and - is.getCondition() = ma.getParent*() - ) and - not exists(MethodAccess ma | - ma.getMethod().hasName("equalsIgnoreCase") and - ma.getMethod().getNumberOfParameters() = 1 and - ( - ma.getQualifier().(CompileTimeConstantExpr).getStringValue() = "unknown" or - ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() = "unknown" - ) and - is.getCondition() = ma.getParent*() - ) and - not exists(MethodAccess ma | - ma.getMethod().getName() in ["isEmpty", "isNotEmpty"] and - ma.getMethod().getNumberOfParameters() = 1 and - is.getCondition() = ma.getParent*() - ) and - not exists(MethodAccess ma | - ( - ma.getMethod().hasQualifiedName("org.apache.commons.lang3", "StringUtils", "isBlank") or - ma.getMethod().hasQualifiedName("org.apache.commons.lang3", "StringUtils", "isNotBlank") - ) and - is.getCondition() = ma.getParent*() - ) and - not exists(MethodAccess ma | - ma.getMethod() - .hasQualifiedName("org.apache.commons.lang3", "StringUtils", "equalsIgnoreCase") and - ma.getAnArgument().(CompileTimeConstantExpr).getStringValue() = "unknown" and - is.getCondition() = ma.getParent*() + ( + exists(MethodAccess ma | + ma.getMethod().getName() in ["equals", "equalsIgnoreCase"] and + ma.getMethod().getDeclaringType() instanceof TypeString and + ma.getMethod().getNumberOfParameters() = 1 and + not ma.getQualifier().(CompileTimeConstantExpr).getStringValue().toLowerCase() in [ + "", "unknown", ":" + ] and + not ma.getArgument(0).(CompileTimeConstantExpr).getStringValue().toLowerCase() in [ + "", "unknown", ":" + ] and + is.getCondition() = ma.getParent*() + ) + or + exists(MethodAccess ma | + ma.getMethod().hasName("contains") and + ma.getMethod().getDeclaringType() instanceof TypeString and + ma.getMethod().getNumberOfParameters() = 1 and + ma.getAnArgument().(CompileTimeConstantExpr).getStringValue().toLowerCase() in [ + "", "unknown" + ] and + is.getCondition() = ma.getParent*() + ) + or + exists(MethodAccess ma | + ma.getMethod().hasName("startsWith") and + ma.getMethod() + .getDeclaringType() + .hasQualifiedName(["org.apache.commons.lang3", "org.apache.commons.lang"], + "StringUtils") and + ma.getMethod().getNumberOfParameters() = 2 and + ma.getAnArgument().(CompileTimeConstantExpr).getStringValue() != "" and + is.getCondition() = ma.getParent*() + ) + or + exists(MethodAccess ma | + ma.getMethod().getName() in ["equals", "equalsIgnoreCase"] and + ma.getMethod() + .getDeclaringType() + .hasQualifiedName(["org.apache.commons.lang3", "org.apache.commons.lang"], + "StringUtils") and + ma.getMethod().getNumberOfParameters() = 2 and + not ma.getAnArgument().(CompileTimeConstantExpr).getStringValue().toLowerCase() in [ + "", "unknown", ":" + ] and + is.getCondition() = ma.getParent*() + ) ) ) } @@ -101,9 +105,7 @@ private class PrintSink extends UseOfLessTrustedSink { PrintSink() { exists(MethodAccess ma | ma.getMethod().getName() in ["print", "println"] and - ( - ma.getMethod().getDeclaringType().hasQualifiedName("java.io", ["PrintWriter", "PrintStream"]) - ) and + ma.getMethod().getDeclaringType().hasQualifiedName("java.io", ["PrintWriter", "PrintStream"]) and ma.getAnArgument() = this.asExpr() ) } From 454324781d6115cbff7eb1f90a2d1bb2d99a9fd4 Mon Sep 17 00:00:00 2001 From: haby0 Date: Thu, 22 Apr 2021 11:59:33 +0800 Subject: [PATCH 17/28] delete IfStmt --- .../CWE/CWE-348/UseOfLessTrustedSource.ql | 8 -- .../CWE/CWE-348/UseOfLessTrustedSourceLib.qll | 97 +++++++++---------- .../CWE-348/UseOfLessTrustedSource.expected | 6 +- 3 files changed, 48 insertions(+), 63 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql index e1d2a28c9397..3c1d2a33ec12 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql @@ -36,14 +36,6 @@ class UseOfLessTrustedSourceConfig extends TaintTracking::Configuration { not aa.getIndexExpr().(CompileTimeConstantExpr).getIntValue() = 0 ) } - - override predicate isAdditionalTaintStep(DataFlow::Node prod, DataFlow::Node succ) { - exists(MethodAccess ma | - ma.getAnArgument() = prod.asExpr() and - ma = succ.asExpr() and - ma.getMethod().getReturnType() instanceof BooleanType - ) - } } from DataFlow::PathNode source, DataFlow::PathNode sink, UseOfLessTrustedSourceConfig conf diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll index fa9ec4da9bb6..29baa90875c0 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll @@ -27,66 +27,59 @@ class UseOfLessTrustedSource extends DataFlow::Node { abstract class UseOfLessTrustedSink extends DataFlow::Node { } /** - * A data flow sink for the if condition, which does not include the null judgment of the remote client ip address. + * A data flow sink for remote client ip comparison. * * For example: `if (!StringUtils.startsWith(ipAddr, "192.168.")){...` determine whether the client ip starts * with `192.168.`, and the program can be deceived by forging the ip address. - * `if (remoteAddr == null || "".equals(remoteAddr)) {...` judging whether the client ip is a null value, - * it needs to be excluded */ -private class IfConditionSink extends UseOfLessTrustedSink { - IfConditionSink() { - exists(IfStmt is | - is.getCondition() = this.asExpr() and +private class CompareSink extends UseOfLessTrustedSink { + CompareSink() { + exists(MethodAccess ma | + ma.getMethod().getName() in ["equals", "equalsIgnoreCase"] and + ma.getMethod().getDeclaringType() instanceof TypeString and + ma.getMethod().getNumberOfParameters() = 1 and ( - exists(MethodAccess ma | - ma.getMethod().getName() in ["equals", "equalsIgnoreCase"] and - ma.getMethod().getDeclaringType() instanceof TypeString and - ma.getMethod().getNumberOfParameters() = 1 and - not ma.getQualifier().(CompileTimeConstantExpr).getStringValue().toLowerCase() in [ - "", "unknown", ":" - ] and - not ma.getArgument(0).(CompileTimeConstantExpr).getStringValue().toLowerCase() in [ - "", "unknown", ":" - ] and - is.getCondition() = ma.getParent*() - ) - or - exists(MethodAccess ma | - ma.getMethod().hasName("contains") and - ma.getMethod().getDeclaringType() instanceof TypeString and - ma.getMethod().getNumberOfParameters() = 1 and - ma.getAnArgument().(CompileTimeConstantExpr).getStringValue().toLowerCase() in [ - "", "unknown" - ] and - is.getCondition() = ma.getParent*() - ) + ma.getArgument(0) = this.asExpr() and + not ma.getQualifier().(CompileTimeConstantExpr).getStringValue().toLowerCase() in [ + "", "unknown", ":" + ] or - exists(MethodAccess ma | - ma.getMethod().hasName("startsWith") and - ma.getMethod() - .getDeclaringType() - .hasQualifiedName(["org.apache.commons.lang3", "org.apache.commons.lang"], - "StringUtils") and - ma.getMethod().getNumberOfParameters() = 2 and - ma.getAnArgument().(CompileTimeConstantExpr).getStringValue() != "" and - is.getCondition() = ma.getParent*() - ) - or - exists(MethodAccess ma | - ma.getMethod().getName() in ["equals", "equalsIgnoreCase"] and - ma.getMethod() - .getDeclaringType() - .hasQualifiedName(["org.apache.commons.lang3", "org.apache.commons.lang"], - "StringUtils") and - ma.getMethod().getNumberOfParameters() = 2 and - not ma.getAnArgument().(CompileTimeConstantExpr).getStringValue().toLowerCase() in [ - "", "unknown", ":" - ] and - is.getCondition() = ma.getParent*() - ) + ma.getQualifier() = this.asExpr() and + not ma.getArgument(0).(CompileTimeConstantExpr).getStringValue().toLowerCase() in [ + "", "unknown", ":" + ] ) ) + or + exists(MethodAccess ma | + ma.getMethod().hasName("contains") and + ma.getMethod().getDeclaringType() instanceof TypeString and + ma.getMethod().getNumberOfParameters() = 1 and + ma.getQualifier() = this.asExpr() and + ma.getArgument(0).(CompileTimeConstantExpr).getStringValue().toLowerCase() in ["", "unknown"] + ) + or + exists(MethodAccess ma, int i | + ma.getMethod().hasName("startsWith") and + ma.getMethod() + .getDeclaringType() + .hasQualifiedName(["org.apache.commons.lang3", "org.apache.commons.lang"], "StringUtils") and + ma.getMethod().getNumberOfParameters() = 2 and + ma.getArgument(i) = this.asExpr() and + ma.getArgument(1 - i).(CompileTimeConstantExpr).getStringValue() != "" + ) + or + exists(MethodAccess ma, int i | + ma.getMethod().getName() in ["equals", "equalsIgnoreCase"] and + ma.getMethod() + .getDeclaringType() + .hasQualifiedName(["org.apache.commons.lang3", "org.apache.commons.lang"], "StringUtils") and + ma.getMethod().getNumberOfParameters() = 2 and + ma.getArgument(i) = this.asExpr() and + not ma.getArgument(1 - i).(CompileTimeConstantExpr).getStringValue().toLowerCase() in [ + "", "unknown", ":" + ] + ) } } diff --git a/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.expected b/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.expected index 65bbc8d218b5..1ece0b21ee01 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.expected @@ -11,7 +11,7 @@ edges | UseOfLessTrustedSource.java:37:18:37:58 | getHeader(...) : String | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | | UseOfLessTrustedSource.java:41:18:41:47 | getHeader(...) : String | UseOfLessTrustedSource.java:42:58:42:59 | ip | | UseOfLessTrustedSource.java:41:18:41:47 | getHeader(...) : String | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | -| UseOfLessTrustedSource.java:53:21:53:33 | getClientIP(...) : String | UseOfLessTrustedSource.java:54:13:54:51 | !... | +| UseOfLessTrustedSource.java:53:21:53:33 | getClientIP(...) : String | UseOfLessTrustedSource.java:54:37:54:38 | ip | | UseOfLessTrustedSource.java:74:27:74:62 | getHeader(...) : String | UseOfLessTrustedSource.java:78:16:78:37 | ...[...] : String | | UseOfLessTrustedSource.java:78:16:78:37 | ...[...] : String | UseOfLessTrustedSource.java:53:21:53:33 | getClientIP(...) : String | nodes @@ -29,7 +29,7 @@ nodes | UseOfLessTrustedSource.java:42:58:42:59 | ip | semmle.label | ip | | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | semmle.label | ... + ... | | UseOfLessTrustedSource.java:53:21:53:33 | getClientIP(...) : String | semmle.label | getClientIP(...) : String | -| UseOfLessTrustedSource.java:54:13:54:51 | !... | semmle.label | !... | +| UseOfLessTrustedSource.java:54:37:54:38 | ip | semmle.label | ip | | UseOfLessTrustedSource.java:74:27:74:62 | getHeader(...) : String | semmle.label | getHeader(...) : String | | UseOfLessTrustedSource.java:78:16:78:37 | ...[...] : String | semmle.label | ...[...] : String | #select @@ -45,4 +45,4 @@ nodes | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | UseOfLessTrustedSource.java:33:18:33:52 | getHeader(...) : String | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:33:18:33:52 | getHeader(...) | this user input | | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | UseOfLessTrustedSource.java:37:18:37:58 | getHeader(...) : String | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:37:18:37:58 | getHeader(...) | this user input | | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | UseOfLessTrustedSource.java:41:18:41:47 | getHeader(...) : String | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:41:18:41:47 | getHeader(...) | this user input | -| UseOfLessTrustedSource.java:54:13:54:51 | !... | UseOfLessTrustedSource.java:74:27:74:62 | getHeader(...) : String | UseOfLessTrustedSource.java:54:13:54:51 | !... | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:74:27:74:62 | getHeader(...) | this user input | +| UseOfLessTrustedSource.java:54:37:54:38 | ip | UseOfLessTrustedSource.java:74:27:74:62 | getHeader(...) : String | UseOfLessTrustedSource.java:54:37:54:38 | ip | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:74:27:74:62 | getHeader(...) | this user input | From aaef4ef22b90b60bed45b45c4cd6b5d49562e56c Mon Sep 17 00:00:00 2001 From: haby0 Date: Thu, 22 Apr 2021 18:52:55 +0800 Subject: [PATCH 18/28] Update java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll Co-authored-by: Chris Smowton --- .../Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll index 29baa90875c0..e7f6f17e9aef 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll @@ -56,7 +56,7 @@ private class CompareSink extends UseOfLessTrustedSink { ma.getMethod().getDeclaringType() instanceof TypeString and ma.getMethod().getNumberOfParameters() = 1 and ma.getQualifier() = this.asExpr() and - ma.getArgument(0).(CompileTimeConstantExpr).getStringValue().toLowerCase() in ["", "unknown"] + not ma.getArgument(0).(CompileTimeConstantExpr).getStringValue().toLowerCase() in ["", "unknown"] ) or exists(MethodAccess ma, int i | From 9b4442be8bd23859e8e55bb145250ae1fe87d6ee Mon Sep 17 00:00:00 2001 From: haby0 Date: Thu, 22 Apr 2021 19:01:55 +0800 Subject: [PATCH 19/28] Fix some errors --- .../CWE/CWE-348/UseOfLessTrustedSourceLib.qll | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll index 29baa90875c0..796fa9d3fc39 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll @@ -56,27 +56,27 @@ private class CompareSink extends UseOfLessTrustedSink { ma.getMethod().getDeclaringType() instanceof TypeString and ma.getMethod().getNumberOfParameters() = 1 and ma.getQualifier() = this.asExpr() and - ma.getArgument(0).(CompileTimeConstantExpr).getStringValue().toLowerCase() in ["", "unknown"] + not ma.getArgument(0).(CompileTimeConstantExpr).getStringValue().toLowerCase() in ["", "unknown"] ) or - exists(MethodAccess ma, int i | + exists(MethodAccess ma | ma.getMethod().hasName("startsWith") and ma.getMethod() .getDeclaringType() .hasQualifiedName(["org.apache.commons.lang3", "org.apache.commons.lang"], "StringUtils") and ma.getMethod().getNumberOfParameters() = 2 and - ma.getArgument(i) = this.asExpr() and - ma.getArgument(1 - i).(CompileTimeConstantExpr).getStringValue() != "" + ma.getAnArgument() = this.asExpr() and + ma.getAnArgument().(CompileTimeConstantExpr).getStringValue() != "" ) or - exists(MethodAccess ma, int i | + exists(MethodAccess ma | ma.getMethod().getName() in ["equals", "equalsIgnoreCase"] and ma.getMethod() .getDeclaringType() .hasQualifiedName(["org.apache.commons.lang3", "org.apache.commons.lang"], "StringUtils") and ma.getMethod().getNumberOfParameters() = 2 and - ma.getArgument(i) = this.asExpr() and - not ma.getArgument(1 - i).(CompileTimeConstantExpr).getStringValue().toLowerCase() in [ + ma.getAnArgument() = this.asExpr() and + not ma.getAnArgument().(CompileTimeConstantExpr).getStringValue().toLowerCase() in [ "", "unknown", ":" ] ) From 407dcea75144aeba01a682df9bef3e705af0b84d Mon Sep 17 00:00:00 2001 From: haby0 Date: Thu, 22 Apr 2021 19:20:54 +0800 Subject: [PATCH 20/28] add String type startsWith --- .../Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll index 796fa9d3fc39..be2aef5e9c7e 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll @@ -52,11 +52,13 @@ private class CompareSink extends UseOfLessTrustedSink { ) or exists(MethodAccess ma | - ma.getMethod().hasName("contains") and + ma.getMethod().getName() in ["contains", "startsWith"] and ma.getMethod().getDeclaringType() instanceof TypeString and ma.getMethod().getNumberOfParameters() = 1 and ma.getQualifier() = this.asExpr() and - not ma.getArgument(0).(CompileTimeConstantExpr).getStringValue().toLowerCase() in ["", "unknown"] + not ma.getArgument(0).(CompileTimeConstantExpr).getStringValue().toLowerCase() in [ + "", "unknown" + ] ) or exists(MethodAccess ma | From 5be9fbbc5a299fa83f37556bdace2e3f87058fe1 Mon Sep 17 00:00:00 2001 From: haby0 Date: Tue, 27 Apr 2021 14:12:33 +0800 Subject: [PATCH 21/28] Remove LogOperationSink and PrintSink --- .../CWE/CWE-348/UseOfLessTrustedSource.java | 37 ---- .../CWE/CWE-348/UseOfLessTrustedSource.qhelp | 3 +- .../CWE/CWE-348/UseOfLessTrustedSourceLib.qll | 16 -- .../CWE-348/UseOfLessTrustedSource.expected | 53 +---- .../CWE-348/UseOfLessTrustedSource.java | 37 ---- .../query-tests/security/CWE-348/options | 2 +- .../slf4j-api-1.6.4/org/slf4j/Logger.java | 127 ----------- .../org/slf4j/LoggerFactory.java | 21 -- .../slf4j-api-1.6.4/org/slf4j/Marker.java | 30 --- .../org/springframework/util/ObjectUtils.java | 202 ------------------ .../org/springframework/util/StringUtils.java | 30 --- 11 files changed, 10 insertions(+), 548 deletions(-) delete mode 100644 java/ql/test/stubs/slf4j-api-1.6.4/org/slf4j/Logger.java delete mode 100644 java/ql/test/stubs/slf4j-api-1.6.4/org/slf4j/LoggerFactory.java delete mode 100644 java/ql/test/stubs/slf4j-api-1.6.4/org/slf4j/Marker.java delete mode 100644 java/ql/test/stubs/springframework-5.2.3/org/springframework/util/ObjectUtils.java delete mode 100644 java/ql/test/stubs/springframework-5.2.3/org/springframework/util/StringUtils.java diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.java b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.java index 22d7100223ce..35a66dc2731d 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.java +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.java @@ -1,7 +1,5 @@ import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @@ -10,46 +8,11 @@ @Controller public class UseOfLessTrustedSource { - private static final Logger log = LoggerFactory.getLogger(UseOfLessTrustedSource.class); - @Autowired private HttpServletRequest request; @GetMapping(value = "bad1") public void bad1(HttpServletRequest request) { - String ip = request.getHeader("X-Forwarded-For"); - - log.debug("getClientIP header X-Forwarded-For:{}", ip); - - if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase("unknown", ip)) { - ip = request.getHeader("Proxy-Client-IP"); - log.debug("getClientIP header Proxy-Client-IP:{}", ip); - } - if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase("unknown", ip)) { - ip = request.getHeader("WL-Proxy-Client-IP"); - log.debug("getClientIP header WL-Proxy-Client-IP:{}", ip); - } - if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase("unknown", ip)) { - ip = request.getHeader("HTTP_CLIENT_IP"); - log.debug("getClientIP header HTTP_CLIENT_IP:{}", ip); - } - if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase("unknown", ip)) { - ip = request.getHeader("HTTP_X_FORWARDED_FOR"); - log.debug("getClientIP header HTTP_X_FORWARDED_FOR:{}", ip); - } - if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase("unknown", ip)) { - ip = request.getHeader("X-Real-IP"); - log.debug("getClientIP header X-Real-IP:{}", ip); - } - if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase("unknown", ip)) { - ip = request.getRemoteAddr(); - log.debug("getRemoteAddr IP:{}", ip); - } - System.out.println("client ip is: " + ip); - } - - @GetMapping(value = "bad2") - public void bad2(HttpServletRequest request) { String ip = getClientIP(); if (!StringUtils.startsWith(ip, "192.168.")) { new Exception("ip illegal"); diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp index 15508f049770..6d5404a26836 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp @@ -16,8 +16,7 @@ bypass a ban-list, for example.

    The following examples show the bad case and the good case respectively. -In the bad1 method, the client ip is obtained from an HTTP header for local -output and logging. In the bad2 method, the client ip the X-Forwarded-For is split into comma-separated values, but the less-trustworthy first one is used. Both of these examples could be deceived by providing a forged HTTP header. The method +In the bad1 method, the client ip the X-Forwarded-For is split into comma-separated values, but the less-trustworthy first one is used. Both of these examples could be deceived by providing a forged HTTP header. The method good1 similarly splits an X-Forwarded-For value, but uses the last, more-trustworthy entry.

    diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll index be2aef5e9c7e..1639d1c82d12 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll @@ -90,22 +90,6 @@ private class SqlOperationSink extends UseOfLessTrustedSink { SqlOperationSink() { this instanceof QueryInjectionSink } } -/** A data flow sink for log operation. */ -private class LogOperationSink extends UseOfLessTrustedSink { - LogOperationSink() { exists(LoggingCall lc | lc.getAnArgument() = this.asExpr()) } -} - -/** A data flow sink for local output. */ -private class PrintSink extends UseOfLessTrustedSink { - PrintSink() { - exists(MethodAccess ma | - ma.getMethod().getName() in ["print", "println"] and - ma.getMethod().getDeclaringType().hasQualifiedName("java.io", ["PrintWriter", "PrintStream"]) and - ma.getAnArgument() = this.asExpr() - ) - } -} - /** A method that split string. */ class SplitMethod extends Method { SplitMethod() { diff --git a/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.expected b/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.expected index 1ece0b21ee01..df238e4c0a32 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.expected @@ -1,48 +1,11 @@ edges -| UseOfLessTrustedSource.java:20:21:20:56 | getHeader(...) : String | UseOfLessTrustedSource.java:22:60:22:61 | ip | -| UseOfLessTrustedSource.java:20:21:20:56 | getHeader(...) : String | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | -| UseOfLessTrustedSource.java:25:18:25:53 | getHeader(...) : String | UseOfLessTrustedSource.java:26:64:26:65 | ip | -| UseOfLessTrustedSource.java:25:18:25:53 | getHeader(...) : String | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | -| UseOfLessTrustedSource.java:29:18:29:56 | getHeader(...) : String | UseOfLessTrustedSource.java:30:67:30:68 | ip | -| UseOfLessTrustedSource.java:29:18:29:56 | getHeader(...) : String | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | -| UseOfLessTrustedSource.java:33:18:33:52 | getHeader(...) : String | UseOfLessTrustedSource.java:34:63:34:64 | ip | -| UseOfLessTrustedSource.java:33:18:33:52 | getHeader(...) : String | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | -| UseOfLessTrustedSource.java:37:18:37:58 | getHeader(...) : String | UseOfLessTrustedSource.java:38:69:38:70 | ip | -| UseOfLessTrustedSource.java:37:18:37:58 | getHeader(...) : String | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | -| UseOfLessTrustedSource.java:41:18:41:47 | getHeader(...) : String | UseOfLessTrustedSource.java:42:58:42:59 | ip | -| UseOfLessTrustedSource.java:41:18:41:47 | getHeader(...) : String | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | -| UseOfLessTrustedSource.java:53:21:53:33 | getClientIP(...) : String | UseOfLessTrustedSource.java:54:37:54:38 | ip | -| UseOfLessTrustedSource.java:74:27:74:62 | getHeader(...) : String | UseOfLessTrustedSource.java:78:16:78:37 | ...[...] : String | -| UseOfLessTrustedSource.java:78:16:78:37 | ...[...] : String | UseOfLessTrustedSource.java:53:21:53:33 | getClientIP(...) : String | +| UseOfLessTrustedSource.java:16:21:16:33 | getClientIP(...) : String | UseOfLessTrustedSource.java:17:37:17:38 | ip | +| UseOfLessTrustedSource.java:37:27:37:62 | getHeader(...) : String | UseOfLessTrustedSource.java:41:16:41:37 | ...[...] : String | +| UseOfLessTrustedSource.java:41:16:41:37 | ...[...] : String | UseOfLessTrustedSource.java:16:21:16:33 | getClientIP(...) : String | nodes -| UseOfLessTrustedSource.java:20:21:20:56 | getHeader(...) : String | semmle.label | getHeader(...) : String | -| UseOfLessTrustedSource.java:22:60:22:61 | ip | semmle.label | ip | -| UseOfLessTrustedSource.java:25:18:25:53 | getHeader(...) : String | semmle.label | getHeader(...) : String | -| UseOfLessTrustedSource.java:26:64:26:65 | ip | semmle.label | ip | -| UseOfLessTrustedSource.java:29:18:29:56 | getHeader(...) : String | semmle.label | getHeader(...) : String | -| UseOfLessTrustedSource.java:30:67:30:68 | ip | semmle.label | ip | -| UseOfLessTrustedSource.java:33:18:33:52 | getHeader(...) : String | semmle.label | getHeader(...) : String | -| UseOfLessTrustedSource.java:34:63:34:64 | ip | semmle.label | ip | -| UseOfLessTrustedSource.java:37:18:37:58 | getHeader(...) : String | semmle.label | getHeader(...) : String | -| UseOfLessTrustedSource.java:38:69:38:70 | ip | semmle.label | ip | -| UseOfLessTrustedSource.java:41:18:41:47 | getHeader(...) : String | semmle.label | getHeader(...) : String | -| UseOfLessTrustedSource.java:42:58:42:59 | ip | semmle.label | ip | -| UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | semmle.label | ... + ... | -| UseOfLessTrustedSource.java:53:21:53:33 | getClientIP(...) : String | semmle.label | getClientIP(...) : String | -| UseOfLessTrustedSource.java:54:37:54:38 | ip | semmle.label | ip | -| UseOfLessTrustedSource.java:74:27:74:62 | getHeader(...) : String | semmle.label | getHeader(...) : String | -| UseOfLessTrustedSource.java:78:16:78:37 | ...[...] : String | semmle.label | ...[...] : String | +| UseOfLessTrustedSource.java:16:21:16:33 | getClientIP(...) : String | semmle.label | getClientIP(...) : String | +| UseOfLessTrustedSource.java:17:37:17:38 | ip | semmle.label | ip | +| UseOfLessTrustedSource.java:37:27:37:62 | getHeader(...) : String | semmle.label | getHeader(...) : String | +| UseOfLessTrustedSource.java:41:16:41:37 | ...[...] : String | semmle.label | ...[...] : String | #select -| UseOfLessTrustedSource.java:22:60:22:61 | ip | UseOfLessTrustedSource.java:20:21:20:56 | getHeader(...) : String | UseOfLessTrustedSource.java:22:60:22:61 | ip | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:20:21:20:56 | getHeader(...) | this user input | -| UseOfLessTrustedSource.java:26:64:26:65 | ip | UseOfLessTrustedSource.java:25:18:25:53 | getHeader(...) : String | UseOfLessTrustedSource.java:26:64:26:65 | ip | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:25:18:25:53 | getHeader(...) | this user input | -| UseOfLessTrustedSource.java:30:67:30:68 | ip | UseOfLessTrustedSource.java:29:18:29:56 | getHeader(...) : String | UseOfLessTrustedSource.java:30:67:30:68 | ip | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:29:18:29:56 | getHeader(...) | this user input | -| UseOfLessTrustedSource.java:34:63:34:64 | ip | UseOfLessTrustedSource.java:33:18:33:52 | getHeader(...) : String | UseOfLessTrustedSource.java:34:63:34:64 | ip | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:33:18:33:52 | getHeader(...) | this user input | -| UseOfLessTrustedSource.java:38:69:38:70 | ip | UseOfLessTrustedSource.java:37:18:37:58 | getHeader(...) : String | UseOfLessTrustedSource.java:38:69:38:70 | ip | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:37:18:37:58 | getHeader(...) | this user input | -| UseOfLessTrustedSource.java:42:58:42:59 | ip | UseOfLessTrustedSource.java:41:18:41:47 | getHeader(...) : String | UseOfLessTrustedSource.java:42:58:42:59 | ip | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:41:18:41:47 | getHeader(...) | this user input | -| UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | UseOfLessTrustedSource.java:20:21:20:56 | getHeader(...) : String | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:20:21:20:56 | getHeader(...) | this user input | -| UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | UseOfLessTrustedSource.java:25:18:25:53 | getHeader(...) : String | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:25:18:25:53 | getHeader(...) | this user input | -| UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | UseOfLessTrustedSource.java:29:18:29:56 | getHeader(...) : String | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:29:18:29:56 | getHeader(...) | this user input | -| UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | UseOfLessTrustedSource.java:33:18:33:52 | getHeader(...) : String | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:33:18:33:52 | getHeader(...) | this user input | -| UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | UseOfLessTrustedSource.java:37:18:37:58 | getHeader(...) : String | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:37:18:37:58 | getHeader(...) | this user input | -| UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | UseOfLessTrustedSource.java:41:18:41:47 | getHeader(...) : String | UseOfLessTrustedSource.java:48:28:48:48 | ... + ... | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:41:18:41:47 | getHeader(...) | this user input | -| UseOfLessTrustedSource.java:54:37:54:38 | ip | UseOfLessTrustedSource.java:74:27:74:62 | getHeader(...) : String | UseOfLessTrustedSource.java:54:37:54:38 | ip | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:74:27:74:62 | getHeader(...) | this user input | +| UseOfLessTrustedSource.java:17:37:17:38 | ip | UseOfLessTrustedSource.java:37:27:37:62 | getHeader(...) : String | UseOfLessTrustedSource.java:17:37:17:38 | ip | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:37:27:37:62 | getHeader(...) | this user input | diff --git a/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.java b/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.java index 22d7100223ce..35a66dc2731d 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.java +++ b/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.java @@ -1,7 +1,5 @@ import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @@ -10,46 +8,11 @@ @Controller public class UseOfLessTrustedSource { - private static final Logger log = LoggerFactory.getLogger(UseOfLessTrustedSource.class); - @Autowired private HttpServletRequest request; @GetMapping(value = "bad1") public void bad1(HttpServletRequest request) { - String ip = request.getHeader("X-Forwarded-For"); - - log.debug("getClientIP header X-Forwarded-For:{}", ip); - - if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase("unknown", ip)) { - ip = request.getHeader("Proxy-Client-IP"); - log.debug("getClientIP header Proxy-Client-IP:{}", ip); - } - if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase("unknown", ip)) { - ip = request.getHeader("WL-Proxy-Client-IP"); - log.debug("getClientIP header WL-Proxy-Client-IP:{}", ip); - } - if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase("unknown", ip)) { - ip = request.getHeader("HTTP_CLIENT_IP"); - log.debug("getClientIP header HTTP_CLIENT_IP:{}", ip); - } - if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase("unknown", ip)) { - ip = request.getHeader("HTTP_X_FORWARDED_FOR"); - log.debug("getClientIP header HTTP_X_FORWARDED_FOR:{}", ip); - } - if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase("unknown", ip)) { - ip = request.getHeader("X-Real-IP"); - log.debug("getClientIP header X-Real-IP:{}", ip); - } - if (StringUtils.isBlank(ip) || StringUtils.equalsIgnoreCase("unknown", ip)) { - ip = request.getRemoteAddr(); - log.debug("getRemoteAddr IP:{}", ip); - } - System.out.println("client ip is: " + ip); - } - - @GetMapping(value = "bad2") - public void bad2(HttpServletRequest request) { String ip = getClientIP(); if (!StringUtils.startsWith(ip, "192.168.")) { new Exception("ip illegal"); diff --git a/java/ql/test/experimental/query-tests/security/CWE-348/options b/java/ql/test/experimental/query-tests/security/CWE-348/options index 7edf075ba8e8..05f93394f49a 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-348/options +++ b/java/ql/test/experimental/query-tests/security/CWE-348/options @@ -1 +1 @@ -//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../../stubs/springframework-5.2.3/:${testdir}/../../../../stubs/slf4j-api-1.6.4/:${testdir}/../../../../stubs/apache-commons-lang3-3.7/ \ No newline at end of file +//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../../stubs/springframework-5.2.3/:${testdir}/../../../../stubs/apache-commons-lang3-3.7/ \ No newline at end of file diff --git a/java/ql/test/stubs/slf4j-api-1.6.4/org/slf4j/Logger.java b/java/ql/test/stubs/slf4j-api-1.6.4/org/slf4j/Logger.java deleted file mode 100644 index 1d8b7ea23189..000000000000 --- a/java/ql/test/stubs/slf4j-api-1.6.4/org/slf4j/Logger.java +++ /dev/null @@ -1,127 +0,0 @@ -package org.slf4j; - -public interface Logger { - String ROOT_LOGGER_NAME = "ROOT"; - - String getName(); - - boolean isTraceEnabled(); - - void trace(String var1); - - void trace(String var1, Object var2); - - void trace(String var1, Object var2, Object var3); - - void trace(String var1, Object[] var2); - - void trace(String var1, Throwable var2); - - boolean isTraceEnabled(Marker var1); - - void trace(Marker var1, String var2); - - void trace(Marker var1, String var2, Object var3); - - void trace(Marker var1, String var2, Object var3, Object var4); - - void trace(Marker var1, String var2, Object[] var3); - - void trace(Marker var1, String var2, Throwable var3); - - boolean isDebugEnabled(); - - void debug(String var1); - - void debug(String var1, Object var2); - - void debug(String var1, Object var2, Object var3); - - void debug(String var1, Object[] var2); - - void debug(String var1, Throwable var2); - - boolean isDebugEnabled(Marker var1); - - void debug(Marker var1, String var2); - - void debug(Marker var1, String var2, Object var3); - - void debug(Marker var1, String var2, Object var3, Object var4); - - void debug(Marker var1, String var2, Object[] var3); - - void debug(Marker var1, String var2, Throwable var3); - - boolean isInfoEnabled(); - - void info(String var1); - - void info(String var1, Object var2); - - void info(String var1, Object var2, Object var3); - - void info(String var1, Object[] var2); - - void info(String var1, Throwable var2); - - boolean isInfoEnabled(Marker var1); - - void info(Marker var1, String var2); - - void info(Marker var1, String var2, Object var3); - - void info(Marker var1, String var2, Object var3, Object var4); - - void info(Marker var1, String var2, Object[] var3); - - void info(Marker var1, String var2, Throwable var3); - - boolean isWarnEnabled(); - - void warn(String var1); - - void warn(String var1, Object var2); - - void warn(String var1, Object[] var2); - - void warn(String var1, Object var2, Object var3); - - void warn(String var1, Throwable var2); - - boolean isWarnEnabled(Marker var1); - - void warn(Marker var1, String var2); - - void warn(Marker var1, String var2, Object var3); - - void warn(Marker var1, String var2, Object var3, Object var4); - - void warn(Marker var1, String var2, Object[] var3); - - void warn(Marker var1, String var2, Throwable var3); - - boolean isErrorEnabled(); - - void error(String var1); - - void error(String var1, Object var2); - - void error(String var1, Object var2, Object var3); - - void error(String var1, Object[] var2); - - void error(String var1, Throwable var2); - - boolean isErrorEnabled(Marker var1); - - void error(Marker var1, String var2); - - void error(Marker var1, String var2, Object var3); - - void error(Marker var1, String var2, Object var3, Object var4); - - void error(Marker var1, String var2, Object[] var3); - - void error(Marker var1, String var2, Throwable var3); -} \ No newline at end of file diff --git a/java/ql/test/stubs/slf4j-api-1.6.4/org/slf4j/LoggerFactory.java b/java/ql/test/stubs/slf4j-api-1.6.4/org/slf4j/LoggerFactory.java deleted file mode 100644 index 21ef158130a8..000000000000 --- a/java/ql/test/stubs/slf4j-api-1.6.4/org/slf4j/LoggerFactory.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.slf4j; - -import java.io.IOException; -import java.net.URL; -import java.util.Arrays; -import java.util.Enumeration; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.List; - -public final class LoggerFactory { - - public static Logger getLogger(String name) { - return null; - } - - public static Logger getLogger(Class clazz) { - return null; - } - -} \ No newline at end of file diff --git a/java/ql/test/stubs/slf4j-api-1.6.4/org/slf4j/Marker.java b/java/ql/test/stubs/slf4j-api-1.6.4/org/slf4j/Marker.java deleted file mode 100644 index e6ac4846a791..000000000000 --- a/java/ql/test/stubs/slf4j-api-1.6.4/org/slf4j/Marker.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.slf4j; - -import java.io.Serializable; -import java.util.Iterator; - -public interface Marker extends Serializable { - String ANY_MARKER = "*"; - String ANY_NON_NULL_MARKER = "+"; - - String getName(); - - void add(Marker var1); - - boolean remove(Marker var1); - - /** @deprecated */ - boolean hasChildren(); - - boolean hasReferences(); - - Iterator iterator(); - - boolean contains(Marker var1); - - boolean contains(String var1); - - boolean equals(Object var1); - - int hashCode(); -} diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/util/ObjectUtils.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/util/ObjectUtils.java deleted file mode 100644 index a5a51786d209..000000000000 --- a/java/ql/test/stubs/springframework-5.2.3/org/springframework/util/ObjectUtils.java +++ /dev/null @@ -1,202 +0,0 @@ -package org.springframework.util; - -import java.lang.reflect.Array; -import java.util.Arrays; -import java.util.Collection; -import java.util.Map; -import java.util.Optional; -import java.util.StringJoiner; -import org.springframework.lang.Nullable; - -public abstract class ObjectUtils { - - private static final int INITIAL_HASH = 7; - private static final int MULTIPLIER = 31; - private static final String EMPTY_STRING = ""; - private static final String NULL_STRING = "null"; - private static final String ARRAY_START = "{"; - private static final String ARRAY_END = "}"; - private static final String EMPTY_ARRAY = "{}"; - private static final String ARRAY_ELEMENT_SEPARATOR = ", "; - private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; - - public ObjectUtils() { - } - - public static boolean isCheckedException(Throwable ex) { - return false; - } - - public static boolean isCompatibleWithThrowsClause(Throwable ex, @Nullable Class... declaredExceptions) { - return false; - } - - public static boolean isArray(@Nullable Object obj) { - return false; - } - - public static boolean isEmpty(@Nullable Object[] array) { - return false; - } - - public static boolean isEmpty(@Nullable Object obj) { - return false; - } - - @Nullable - public static Object unwrapOptional(@Nullable Object obj) { - return null; - } - - public static boolean containsElement(@Nullable Object[] array, Object element) { - return true; - } - - public static boolean containsConstant(Enum[] enumValues, String constant) { - return true; - } - - public static boolean containsConstant(Enum[] enumValues, String constant, boolean caseSensitive) { - return true; - } - - public static > E caseInsensitiveValueOf(E[] enumValues, String constant) { - return null; - } - - public static A[] addObjectToArray(@Nullable A[] array, @Nullable O obj) { - return null; - } - - public static Object[] toObjectArray(@Nullable Object source) { - return null; - } - - public static boolean nullSafeEquals(@Nullable Object o1, @Nullable Object o2) { - return false; - } - - private static boolean arrayEquals(Object o1, Object o2) { - return false; - } - - public static int nullSafeHashCode(@Nullable Object obj) { - return 1; - } - - public static int nullSafeHashCode(@Nullable Object[] array) { - return 1; - } - - public static int nullSafeHashCode(@Nullable boolean[] array) { - return 1; - } - - public static int nullSafeHashCode(@Nullable byte[] array) { - return 1; - } - - public static int nullSafeHashCode(@Nullable char[] array) { - return 1; - } - - public static int nullSafeHashCode(@Nullable double[] array) { - return 1; - } - - public static int nullSafeHashCode(@Nullable float[] array) { - return 1; - } - - public static int nullSafeHashCode(@Nullable int[] array) { - return 1; - } - - public static int nullSafeHashCode(@Nullable long[] array) { - return 1; - } - - public static int nullSafeHashCode(@Nullable short[] array) { - return 1; - } - - /** @deprecated */ - @Deprecated - public static int hashCode(boolean bool) { - return 1; - } - - /** @deprecated */ - @Deprecated - public static int hashCode(double dbl) { - return 1; - } - - /** @deprecated */ - @Deprecated - public static int hashCode(float flt) { - return 1; - } - - /** @deprecated */ - @Deprecated - public static int hashCode(long lng) { - return 1; - } - - public static String identityToString(@Nullable Object obj) { - return ""; - } - - public static String getIdentityHexString(Object obj) { - return ""; - } - - public static String getDisplayString(@Nullable Object obj) { - return ""; - } - - public static String nullSafeClassName(@Nullable Object obj) { - return ""; - } - - public static String nullSafeToString(@Nullable Object obj) { - return ""; - } - - public static String nullSafeToString(@Nullable Object[] array) { - return ""; - } - - public static String nullSafeToString(@Nullable boolean[] array) { - return ""; - } - - public static String nullSafeToString(@Nullable byte[] array) { - return ""; - } - - public static String nullSafeToString(@Nullable char[] array) { - return ""; - } - - public static String nullSafeToString(@Nullable double[] array) { - return ""; - } - - public static String nullSafeToString(@Nullable float[] array) { - return ""; - } - - public static String nullSafeToString(@Nullable int[] array) { - return ""; - } - - public static String nullSafeToString(@Nullable long[] array) { - return ""; - } - - public static String nullSafeToString(@Nullable short[] array) { - return ""; - } -} diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/util/StringUtils.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/util/StringUtils.java deleted file mode 100644 index a78d47e44c6a..000000000000 --- a/java/ql/test/stubs/springframework-5.2.3/org/springframework/util/StringUtils.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.springframework.util; - -import java.io.ByteArrayOutputStream; -import java.nio.charset.Charset; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Deque; -import java.util.Enumeration; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Locale; -import java.util.Properties; -import java.util.Set; -import java.util.StringJoiner; -import java.util.StringTokenizer; -import java.util.TimeZone; -import org.springframework.lang.Nullable; - -public abstract class StringUtils { - - @Deprecated - public static boolean isEmpty(@Nullable Object str) { - return true; - } - -} From b0f745365d8924351b814d0c0f2194621ca90c57 Mon Sep 17 00:00:00 2001 From: haby0 Date: Wed, 28 Apr 2021 14:32:25 +0800 Subject: [PATCH 22/28] Node type restriction --- .../Security/CWE/CWE-348/UseOfLessTrustedSource.ql | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql index 3c1d2a33ec12..3e25dba427ce 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql @@ -35,6 +35,12 @@ class UseOfLessTrustedSourceConfig extends TaintTracking::Configuration { ma.getMethod() instanceof SplitMethod and not aa.getIndexExpr().(CompileTimeConstantExpr).getIntValue() = 0 ) + or + node.getType().hasName("Object") + or + node.getType() instanceof PrimitiveType + or + node.getType() instanceof BoxedType } } From e813257431654f2b39eed2d9c972b000d1dd48f3 Mon Sep 17 00:00:00 2001 From: haby0 Date: Thu, 29 Apr 2021 21:23:52 +0800 Subject: [PATCH 23/28] use hardCode --- .../CWE/CWE-348/UseOfLessTrustedSource.java | 8 ++++++ .../CWE/CWE-348/UseOfLessTrustedSource.qhelp | 2 +- .../CWE/CWE-348/UseOfLessTrustedSource.ql | 2 -- .../CWE/CWE-348/UseOfLessTrustedSourceLib.qll | 25 +++++++++---------- .../CWE-348/UseOfLessTrustedSource.expected | 15 +++++++---- .../CWE-348/UseOfLessTrustedSource.java | 8 ++++++ 6 files changed, 39 insertions(+), 21 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.java b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.java index 35a66dc2731d..689246340da1 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.java +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.java @@ -19,6 +19,14 @@ public void bad1(HttpServletRequest request) { } } + @GetMapping(value = "bad2") + public void bad2(HttpServletRequest request) { + String ip = getClientIP(); + if (!"127.0.0.1".equals(ip)) { + new Exception("ip illegal"); + } + } + @GetMapping(value = "good1") @ResponseBody public String good1(HttpServletRequest request) { diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp index 6d5404a26836..5e43b9b1cfed 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp @@ -16,7 +16,7 @@ bypass a ban-list, for example.

    The following examples show the bad case and the good case respectively. -In the bad1 method, the client ip the X-Forwarded-For is split into comma-separated values, but the less-trustworthy first one is used. Both of these examples could be deceived by providing a forged HTTP header. The method +In bad1 method and bad2 method, the client ip the X-Forwarded-For is split into comma-separated values, but the less-trustworthy first one is used. Both of these examples could be deceived by providing a forged HTTP header. The method good1 similarly splits an X-Forwarded-For value, but uses the last, more-trustworthy entry.

    diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql index 3e25dba427ce..14ae34442636 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql @@ -36,8 +36,6 @@ class UseOfLessTrustedSourceConfig extends TaintTracking::Configuration { not aa.getIndexExpr().(CompileTimeConstantExpr).getIntValue() = 0 ) or - node.getType().hasName("Object") - or node.getType() instanceof PrimitiveType or node.getType() instanceof BoxedType diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll index 1639d1c82d12..708460cdaa1c 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll @@ -1,5 +1,6 @@ import java import DataFlow +import semmle.code.java.frameworks.Networking import semmle.code.java.security.QueryInjection import experimental.semmle.code.java.Logging @@ -40,14 +41,10 @@ private class CompareSink extends UseOfLessTrustedSink { ma.getMethod().getNumberOfParameters() = 1 and ( ma.getArgument(0) = this.asExpr() and - not ma.getQualifier().(CompileTimeConstantExpr).getStringValue().toLowerCase() in [ - "", "unknown", ":" - ] + ma.getQualifier().(CompileTimeConstantExpr).getStringValue() instanceof PrivateHostName or ma.getQualifier() = this.asExpr() and - not ma.getArgument(0).(CompileTimeConstantExpr).getStringValue().toLowerCase() in [ - "", "unknown", ":" - ] + ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() instanceof PrivateHostName ) ) or @@ -56,9 +53,10 @@ private class CompareSink extends UseOfLessTrustedSink { ma.getMethod().getDeclaringType() instanceof TypeString and ma.getMethod().getNumberOfParameters() = 1 and ma.getQualifier() = this.asExpr() and - not ma.getArgument(0).(CompileTimeConstantExpr).getStringValue().toLowerCase() in [ - "", "unknown" - ] + ma.getAnArgument() + .(CompileTimeConstantExpr) + .getStringValue() + .regexpMatch("^((10\\.((1\\d{2})?|(2[0-4]\\d)?|(25[0-5])?|([1-9]\\d|[0-9])?)(\\.)?)|(192\\.168\\.)|172\\.(1[6789]|2[0-9]|3[01])\\.)((1\\d{2})?|(2[0-4]\\d)?|(25[0-5])?|([1-9]\\d|[0-9])?)(\\.)?((1\\d{2})?|(2[0-4]\\d)?|(25[0-5])?|([1-9]\\d|[0-9])?)$") ) or exists(MethodAccess ma | @@ -68,7 +66,10 @@ private class CompareSink extends UseOfLessTrustedSink { .hasQualifiedName(["org.apache.commons.lang3", "org.apache.commons.lang"], "StringUtils") and ma.getMethod().getNumberOfParameters() = 2 and ma.getAnArgument() = this.asExpr() and - ma.getAnArgument().(CompileTimeConstantExpr).getStringValue() != "" + ma.getAnArgument() + .(CompileTimeConstantExpr) + .getStringValue() + .regexpMatch("^((10\\.((1\\d{2})?|(2[0-4]\\d)?|(25[0-5])?|([1-9]\\d|[0-9])?)(\\.)?)|(192\\.168\\.)|172\\.(1[6789]|2[0-9]|3[01])\\.)((1\\d{2})?|(2[0-4]\\d)?|(25[0-5])?|([1-9]\\d|[0-9])?)(\\.)?((1\\d{2})?|(2[0-4]\\d)?|(25[0-5])?|([1-9]\\d|[0-9])?)$") ) or exists(MethodAccess ma | @@ -78,9 +79,7 @@ private class CompareSink extends UseOfLessTrustedSink { .hasQualifiedName(["org.apache.commons.lang3", "org.apache.commons.lang"], "StringUtils") and ma.getMethod().getNumberOfParameters() = 2 and ma.getAnArgument() = this.asExpr() and - not ma.getAnArgument().(CompileTimeConstantExpr).getStringValue().toLowerCase() in [ - "", "unknown", ":" - ] + ma.getAnArgument().(CompileTimeConstantExpr).getStringValue() instanceof PrivateHostName ) } } diff --git a/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.expected b/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.expected index df238e4c0a32..ff9cbc24a20b 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.expected @@ -1,11 +1,16 @@ edges | UseOfLessTrustedSource.java:16:21:16:33 | getClientIP(...) : String | UseOfLessTrustedSource.java:17:37:17:38 | ip | -| UseOfLessTrustedSource.java:37:27:37:62 | getHeader(...) : String | UseOfLessTrustedSource.java:41:16:41:37 | ...[...] : String | -| UseOfLessTrustedSource.java:41:16:41:37 | ...[...] : String | UseOfLessTrustedSource.java:16:21:16:33 | getClientIP(...) : String | +| UseOfLessTrustedSource.java:24:21:24:33 | getClientIP(...) : String | UseOfLessTrustedSource.java:25:33:25:34 | ip | +| UseOfLessTrustedSource.java:45:27:45:62 | getHeader(...) : String | UseOfLessTrustedSource.java:49:16:49:37 | ...[...] : String | +| UseOfLessTrustedSource.java:49:16:49:37 | ...[...] : String | UseOfLessTrustedSource.java:16:21:16:33 | getClientIP(...) : String | +| UseOfLessTrustedSource.java:49:16:49:37 | ...[...] : String | UseOfLessTrustedSource.java:24:21:24:33 | getClientIP(...) : String | nodes | UseOfLessTrustedSource.java:16:21:16:33 | getClientIP(...) : String | semmle.label | getClientIP(...) : String | | UseOfLessTrustedSource.java:17:37:17:38 | ip | semmle.label | ip | -| UseOfLessTrustedSource.java:37:27:37:62 | getHeader(...) : String | semmle.label | getHeader(...) : String | -| UseOfLessTrustedSource.java:41:16:41:37 | ...[...] : String | semmle.label | ...[...] : String | +| UseOfLessTrustedSource.java:24:21:24:33 | getClientIP(...) : String | semmle.label | getClientIP(...) : String | +| UseOfLessTrustedSource.java:25:33:25:34 | ip | semmle.label | ip | +| UseOfLessTrustedSource.java:45:27:45:62 | getHeader(...) : String | semmle.label | getHeader(...) : String | +| UseOfLessTrustedSource.java:49:16:49:37 | ...[...] : String | semmle.label | ...[...] : String | #select -| UseOfLessTrustedSource.java:17:37:17:38 | ip | UseOfLessTrustedSource.java:37:27:37:62 | getHeader(...) : String | UseOfLessTrustedSource.java:17:37:17:38 | ip | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:37:27:37:62 | getHeader(...) | this user input | +| UseOfLessTrustedSource.java:17:37:17:38 | ip | UseOfLessTrustedSource.java:45:27:45:62 | getHeader(...) : String | UseOfLessTrustedSource.java:17:37:17:38 | ip | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:45:27:45:62 | getHeader(...) | this user input | +| UseOfLessTrustedSource.java:25:33:25:34 | ip | UseOfLessTrustedSource.java:45:27:45:62 | getHeader(...) : String | UseOfLessTrustedSource.java:25:33:25:34 | ip | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:45:27:45:62 | getHeader(...) | this user input | diff --git a/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.java b/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.java index 35a66dc2731d..689246340da1 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.java +++ b/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.java @@ -19,6 +19,14 @@ public void bad1(HttpServletRequest request) { } } + @GetMapping(value = "bad2") + public void bad2(HttpServletRequest request) { + String ip = getClientIP(); + if (!"127.0.0.1".equals(ip)) { + new Exception("ip illegal"); + } + } + @GetMapping(value = "good1") @ResponseBody public String good1(HttpServletRequest request) { From 711a74c9c9a4fc2a0e925f09617f3c0fad9c3699 Mon Sep 17 00:00:00 2001 From: haby0 Date: Fri, 30 Apr 2021 10:31:40 +0800 Subject: [PATCH 24/28] Eliminate false positives\ --- .../Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll index 708460cdaa1c..807ebd013d77 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll @@ -41,10 +41,12 @@ private class CompareSink extends UseOfLessTrustedSink { ma.getMethod().getNumberOfParameters() = 1 and ( ma.getArgument(0) = this.asExpr() and - ma.getQualifier().(CompileTimeConstantExpr).getStringValue() instanceof PrivateHostName + ma.getQualifier().(CompileTimeConstantExpr).getStringValue() instanceof PrivateHostName and + not ma.getQualifier().(CompileTimeConstantExpr).getStringValue() = "0:0:0:0:0:0:0:1" or ma.getQualifier() = this.asExpr() and - ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() instanceof PrivateHostName + ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() instanceof PrivateHostName and + not ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() = "0:0:0:0:0:0:0:1" ) ) or @@ -79,7 +81,8 @@ private class CompareSink extends UseOfLessTrustedSink { .hasQualifiedName(["org.apache.commons.lang3", "org.apache.commons.lang"], "StringUtils") and ma.getMethod().getNumberOfParameters() = 2 and ma.getAnArgument() = this.asExpr() and - ma.getAnArgument().(CompileTimeConstantExpr).getStringValue() instanceof PrivateHostName + ma.getAnArgument().(CompileTimeConstantExpr).getStringValue() instanceof PrivateHostName and + not ma.getAnArgument().(CompileTimeConstantExpr).getStringValue() = "0:0:0:0:0:0:0:1" ) } } From 8142810455cf4266e6b4266053daa8908ded3f79 Mon Sep 17 00:00:00 2001 From: haby0 Date: Fri, 30 Apr 2021 16:54:28 +0800 Subject: [PATCH 25/28] Update java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp Co-authored-by: Chris Smowton --- .../Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp index 5e43b9b1cfed..61848f111ad0 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp @@ -4,7 +4,7 @@

    An original client IP address is retrieved from an http header (X-Forwarded-For or X-Real-IP or Proxy-Client-IP -etc.), which is used to ensure security or track it in the log for statistical or other reasons. Attackers can forge the value of these identifiers to +etc.), which is used to ensure security. Attackers can forge the value of these identifiers to bypass a ban-list, for example.

    From 0691cac5ab2429644cfc84d5acced219f7ed6a99 Mon Sep 17 00:00:00 2001 From: haby0 Date: Fri, 30 Apr 2021 16:54:41 +0800 Subject: [PATCH 26/28] Update java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll Co-authored-by: Chris Smowton --- .../Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll index 807ebd013d77..04df6aa5dd60 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll @@ -58,7 +58,7 @@ private class CompareSink extends UseOfLessTrustedSink { ma.getAnArgument() .(CompileTimeConstantExpr) .getStringValue() - .regexpMatch("^((10\\.((1\\d{2})?|(2[0-4]\\d)?|(25[0-5])?|([1-9]\\d|[0-9])?)(\\.)?)|(192\\.168\\.)|172\\.(1[6789]|2[0-9]|3[01])\\.)((1\\d{2})?|(2[0-4]\\d)?|(25[0-5])?|([1-9]\\d|[0-9])?)(\\.)?((1\\d{2})?|(2[0-4]\\d)?|(25[0-5])?|([1-9]\\d|[0-9])?)$") + .regexpMatch("^((10\\.((1\\d{2})?|(2[0-4]\\d)?|(25[0-5])?|([1-9]\\d|[0-9])?)(\\.)?)|(192\\.168\\.)|172\\.(1[6789]|2[0-9]|3[01])\\.)((1\\d{2})?|(2[0-4]\\d)?|(25[0-5])?|([1-9]\\d|[0-9])?)(\\.)?((1\\d{2})?|(2[0-4]\\d)?|(25[0-5])?|([1-9]\\d|[0-9])?)$") // Matches IP-address-like strings ) or exists(MethodAccess ma | From f41301f8f58f48ebb2663deae3108a12ed738936 Mon Sep 17 00:00:00 2001 From: haby0 Date: Fri, 30 Apr 2021 16:55:17 +0800 Subject: [PATCH 27/28] Update java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.java Co-authored-by: Chris Smowton --- .../CWE/CWE-348/UseOfLessTrustedSource.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.java b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.java index 689246340da1..ad959cb2dc8b 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.java +++ b/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.java @@ -30,15 +30,13 @@ public void bad2(HttpServletRequest request) { @GetMapping(value = "good1") @ResponseBody public String good1(HttpServletRequest request) { - String remoteAddr = ""; - if (request != null) { - remoteAddr = request.getHeader("X-FORWARDED-FOR"); - remoteAddr = remoteAddr.split(",")[remoteAddr.split(",").length - 1]; // good - if (remoteAddr == null || "".equals(remoteAddr)) { - remoteAddr = request.getRemoteAddr(); - } + String ip = request.getHeader("X-FORWARDED-FOR"); + String[] parts = ip.split(","); + // Good: if this application runs behind a reverse proxy it may append the real remote IP to the end of any client-supplied X-Forwarded-For header. + ip = parts[parts.length - 1]; + if (!StringUtils.startsWith(ip, "192.168.")) { + new Exception("ip illegal"); } - return remoteAddr; } protected String getClientIP() { @@ -48,4 +46,4 @@ protected String getClientIP() { } return xfHeader.split(",")[0]; } -} \ No newline at end of file +} From fdcc517b9fcb8917775feb5977cb050d4bfc9f94 Mon Sep 17 00:00:00 2001 From: haby0 Date: Fri, 30 Apr 2021 17:43:34 +0800 Subject: [PATCH 28/28] UseOfLessTrustedSource -> ClientSuppliedIpUsedInSecurityCheck" --- ... ClientSuppliedIpUsedInSecurityCheck.java} | 6 ++--- ...ClientSuppliedIpUsedInSecurityCheck.qhelp} | 2 +- ...=> ClientSuppliedIpUsedInSecurityCheck.ql} | 17 ++++++++----- ...lientSuppliedIpUsedInSecurityCheckLib.qll} | 25 +++++++++---------- ...ientSuppliedIpUsedInSecurityCheck.expected | 16 ++++++++++++ ... ClientSuppliedIpUsedInSecurityCheck.java} | 18 ++++++------- .../ClientSuppliedIpUsedInSecurityCheck.qlref | 1 + .../CWE-348/UseOfLessTrustedSource.expected | 16 ------------ .../CWE-348/UseOfLessTrustedSource.qlref | 1 - 9 files changed, 52 insertions(+), 50 deletions(-) rename java/ql/src/experimental/Security/CWE/CWE-348/{UseOfLessTrustedSource.java => ClientSuppliedIpUsedInSecurityCheck.java} (92%) rename java/ql/src/experimental/Security/CWE/CWE-348/{UseOfLessTrustedSource.qhelp => ClientSuppliedIpUsedInSecurityCheck.qhelp} (96%) rename java/ql/src/experimental/Security/CWE/CWE-348/{UseOfLessTrustedSource.ql => ClientSuppliedIpUsedInSecurityCheck.ql} (69%) rename java/ql/src/experimental/Security/CWE/CWE-348/{UseOfLessTrustedSourceLib.qll => ClientSuppliedIpUsedInSecurityCheckLib.qll} (77%) create mode 100644 java/ql/test/experimental/query-tests/security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.expected rename java/ql/test/experimental/query-tests/security/CWE-348/{UseOfLessTrustedSource.java => ClientSuppliedIpUsedInSecurityCheck.java} (73%) create mode 100644 java/ql/test/experimental/query-tests/security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.qlref delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.expected delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.qlref diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.java b/java/ql/src/experimental/Security/CWE/CWE-348/ClientSuppliedIpUsedInSecurityCheck.java similarity index 92% rename from java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.java rename to java/ql/src/experimental/Security/CWE/CWE-348/ClientSuppliedIpUsedInSecurityCheck.java index ad959cb2dc8b..93a860981d1d 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.java +++ b/java/ql/src/experimental/Security/CWE/CWE-348/ClientSuppliedIpUsedInSecurityCheck.java @@ -6,7 +6,7 @@ import org.springframework.web.bind.annotation.ResponseBody; @Controller -public class UseOfLessTrustedSource { +public class ClientSuppliedIpUsedInSecurityCheck { @Autowired private HttpServletRequest request; @@ -31,12 +31,12 @@ public void bad2(HttpServletRequest request) { @ResponseBody public String good1(HttpServletRequest request) { String ip = request.getHeader("X-FORWARDED-FOR"); - String[] parts = ip.split(","); // Good: if this application runs behind a reverse proxy it may append the real remote IP to the end of any client-supplied X-Forwarded-For header. - ip = parts[parts.length - 1]; + ip = ip.split(",")[ip.split(",").length - 1]; if (!StringUtils.startsWith(ip, "192.168.")) { new Exception("ip illegal"); } + return ip; } protected String getClientIP() { diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp b/java/ql/src/experimental/Security/CWE/CWE-348/ClientSuppliedIpUsedInSecurityCheck.qhelp similarity index 96% rename from java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp rename to java/ql/src/experimental/Security/CWE/CWE-348/ClientSuppliedIpUsedInSecurityCheck.qhelp index 61848f111ad0..fd62ab2968b2 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-348/ClientSuppliedIpUsedInSecurityCheck.qhelp @@ -19,7 +19,7 @@ bypass a ban-list, for example.

    In bad1 method and bad2 method, the client ip the X-Forwarded-For is split into comma-separated values, but the less-trustworthy first one is used. Both of these examples could be deceived by providing a forged HTTP header. The method good1 similarly splits an X-Forwarded-For value, but uses the last, more-trustworthy entry.

    - +
    diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql b/java/ql/src/experimental/Security/CWE/CWE-348/ClientSuppliedIpUsedInSecurityCheck.ql similarity index 69% rename from java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql rename to java/ql/src/experimental/Security/CWE/CWE-348/ClientSuppliedIpUsedInSecurityCheck.ql index 14ae34442636..78d8bfee5f06 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-348/ClientSuppliedIpUsedInSecurityCheck.ql @@ -11,19 +11,23 @@ */ import java -import UseOfLessTrustedSourceLib +import ClientSuppliedIpUsedInSecurityCheckLib import semmle.code.java.dataflow.FlowSources import DataFlow::PathGraph /** * Taint-tracking configuration tracing flow from obtaining a client ip from an HTTP header to a sensitive use. */ -class UseOfLessTrustedSourceConfig extends TaintTracking::Configuration { - UseOfLessTrustedSourceConfig() { this = "UseOfLessTrustedSourceConfig" } +class ClientSuppliedIpUsedInSecurityCheckConfig extends TaintTracking::Configuration { + ClientSuppliedIpUsedInSecurityCheckConfig() { this = "ClientSuppliedIpUsedInSecurityCheckConfig" } - override predicate isSource(DataFlow::Node source) { source instanceof UseOfLessTrustedSource } + override predicate isSource(DataFlow::Node source) { + source instanceof ClientSuppliedIpUsedInSecurityCheck + } - override predicate isSink(DataFlow::Node sink) { sink instanceof UseOfLessTrustedSink } + override predicate isSink(DataFlow::Node sink) { + sink instanceof ClientSuppliedIpUsedInSecurityCheckSink + } /** * Splitting a header value by `,` and taking an entry other than the first is sanitizing, because @@ -42,7 +46,8 @@ class UseOfLessTrustedSourceConfig extends TaintTracking::Configuration { } } -from DataFlow::PathNode source, DataFlow::PathNode sink, UseOfLessTrustedSourceConfig conf +from + DataFlow::PathNode source, DataFlow::PathNode sink, ClientSuppliedIpUsedInSecurityCheckConfig conf where conf.hasFlowPath(source, sink) select sink.getNode(), source, sink, "IP address spoofing might include code from $@.", source.getNode(), "this user input" diff --git a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll b/java/ql/src/experimental/Security/CWE/CWE-348/ClientSuppliedIpUsedInSecurityCheckLib.qll similarity index 77% rename from java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll rename to java/ql/src/experimental/Security/CWE/CWE-348/ClientSuppliedIpUsedInSecurityCheckLib.qll index 04df6aa5dd60..fd9353d3414b 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-348/UseOfLessTrustedSourceLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-348/ClientSuppliedIpUsedInSecurityCheckLib.qll @@ -10,8 +10,8 @@ import experimental.semmle.code.java.Logging * * For example: `ServletRequest.getHeader("X-Forwarded-For")`. */ -class UseOfLessTrustedSource extends DataFlow::Node { - UseOfLessTrustedSource() { +class ClientSuppliedIpUsedInSecurityCheck extends DataFlow::Node { + ClientSuppliedIpUsedInSecurityCheck() { exists(MethodAccess ma | ma.getMethod().hasName("getHeader") and ma.getArgument(0).(CompileTimeConstantExpr).getStringValue().toLowerCase() in [ @@ -25,7 +25,7 @@ class UseOfLessTrustedSource extends DataFlow::Node { } /** A data flow sink for ip address forgery vulnerabilities. */ -abstract class UseOfLessTrustedSink extends DataFlow::Node { } +abstract class ClientSuppliedIpUsedInSecurityCheckSink extends DataFlow::Node { } /** * A data flow sink for remote client ip comparison. @@ -33,7 +33,7 @@ abstract class UseOfLessTrustedSink extends DataFlow::Node { } * For example: `if (!StringUtils.startsWith(ipAddr, "192.168.")){...` determine whether the client ip starts * with `192.168.`, and the program can be deceived by forging the ip address. */ -private class CompareSink extends UseOfLessTrustedSink { +private class CompareSink extends ClientSuppliedIpUsedInSecurityCheckSink { CompareSink() { exists(MethodAccess ma | ma.getMethod().getName() in ["equals", "equalsIgnoreCase"] and @@ -55,10 +55,7 @@ private class CompareSink extends UseOfLessTrustedSink { ma.getMethod().getDeclaringType() instanceof TypeString and ma.getMethod().getNumberOfParameters() = 1 and ma.getQualifier() = this.asExpr() and - ma.getAnArgument() - .(CompileTimeConstantExpr) - .getStringValue() - .regexpMatch("^((10\\.((1\\d{2})?|(2[0-4]\\d)?|(25[0-5])?|([1-9]\\d|[0-9])?)(\\.)?)|(192\\.168\\.)|172\\.(1[6789]|2[0-9]|3[01])\\.)((1\\d{2})?|(2[0-4]\\d)?|(25[0-5])?|([1-9]\\d|[0-9])?)(\\.)?((1\\d{2})?|(2[0-4]\\d)?|(25[0-5])?|([1-9]\\d|[0-9])?)$") // Matches IP-address-like strings + ma.getAnArgument().(CompileTimeConstantExpr).getStringValue().regexpMatch(getIpAddressRegex()) // Matches IP-address-like strings ) or exists(MethodAccess ma | @@ -68,10 +65,7 @@ private class CompareSink extends UseOfLessTrustedSink { .hasQualifiedName(["org.apache.commons.lang3", "org.apache.commons.lang"], "StringUtils") and ma.getMethod().getNumberOfParameters() = 2 and ma.getAnArgument() = this.asExpr() and - ma.getAnArgument() - .(CompileTimeConstantExpr) - .getStringValue() - .regexpMatch("^((10\\.((1\\d{2})?|(2[0-4]\\d)?|(25[0-5])?|([1-9]\\d|[0-9])?)(\\.)?)|(192\\.168\\.)|172\\.(1[6789]|2[0-9]|3[01])\\.)((1\\d{2})?|(2[0-4]\\d)?|(25[0-5])?|([1-9]\\d|[0-9])?)(\\.)?((1\\d{2})?|(2[0-4]\\d)?|(25[0-5])?|([1-9]\\d|[0-9])?)$") + ma.getAnArgument().(CompileTimeConstantExpr).getStringValue().regexpMatch(getIpAddressRegex()) ) or exists(MethodAccess ma | @@ -88,7 +82,7 @@ private class CompareSink extends UseOfLessTrustedSink { } /** A data flow sink for sql operation. */ -private class SqlOperationSink extends UseOfLessTrustedSink { +private class SqlOperationSink extends ClientSuppliedIpUsedInSecurityCheckSink { SqlOperationSink() { this instanceof QueryInjectionSink } } @@ -99,3 +93,8 @@ class SplitMethod extends Method { this.hasQualifiedName("java.lang", "String", "split") } } + +string getIpAddressRegex() { + result = + "^((10\\.((1\\d{2})?|(2[0-4]\\d)?|(25[0-5])?|([1-9]\\d|[0-9])?)(\\.)?)|(192\\.168\\.)|172\\.(1[6789]|2[0-9]|3[01])\\.)((1\\d{2})?|(2[0-4]\\d)?|(25[0-5])?|([1-9]\\d|[0-9])?)(\\.)?((1\\d{2})?|(2[0-4]\\d)?|(25[0-5])?|([1-9]\\d|[0-9])?)$" +} diff --git a/java/ql/test/experimental/query-tests/security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.expected b/java/ql/test/experimental/query-tests/security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.expected new file mode 100644 index 000000000000..5627f9541f18 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.expected @@ -0,0 +1,16 @@ +edges +| ClientSuppliedIpUsedInSecurityCheck.java:16:21:16:33 | getClientIP(...) : String | ClientSuppliedIpUsedInSecurityCheck.java:17:37:17:38 | ip | +| ClientSuppliedIpUsedInSecurityCheck.java:24:21:24:33 | getClientIP(...) : String | ClientSuppliedIpUsedInSecurityCheck.java:25:33:25:34 | ip | +| ClientSuppliedIpUsedInSecurityCheck.java:43:27:43:62 | getHeader(...) : String | ClientSuppliedIpUsedInSecurityCheck.java:47:16:47:37 | ...[...] : String | +| ClientSuppliedIpUsedInSecurityCheck.java:47:16:47:37 | ...[...] : String | ClientSuppliedIpUsedInSecurityCheck.java:16:21:16:33 | getClientIP(...) : String | +| ClientSuppliedIpUsedInSecurityCheck.java:47:16:47:37 | ...[...] : String | ClientSuppliedIpUsedInSecurityCheck.java:24:21:24:33 | getClientIP(...) : String | +nodes +| ClientSuppliedIpUsedInSecurityCheck.java:16:21:16:33 | getClientIP(...) : String | semmle.label | getClientIP(...) : String | +| ClientSuppliedIpUsedInSecurityCheck.java:17:37:17:38 | ip | semmle.label | ip | +| ClientSuppliedIpUsedInSecurityCheck.java:24:21:24:33 | getClientIP(...) : String | semmle.label | getClientIP(...) : String | +| ClientSuppliedIpUsedInSecurityCheck.java:25:33:25:34 | ip | semmle.label | ip | +| ClientSuppliedIpUsedInSecurityCheck.java:43:27:43:62 | getHeader(...) : String | semmle.label | getHeader(...) : String | +| ClientSuppliedIpUsedInSecurityCheck.java:47:16:47:37 | ...[...] : String | semmle.label | ...[...] : String | +#select +| ClientSuppliedIpUsedInSecurityCheck.java:17:37:17:38 | ip | ClientSuppliedIpUsedInSecurityCheck.java:43:27:43:62 | getHeader(...) : String | ClientSuppliedIpUsedInSecurityCheck.java:17:37:17:38 | ip | IP address spoofing might include code from $@. | ClientSuppliedIpUsedInSecurityCheck.java:43:27:43:62 | getHeader(...) | this user input | +| ClientSuppliedIpUsedInSecurityCheck.java:25:33:25:34 | ip | ClientSuppliedIpUsedInSecurityCheck.java:43:27:43:62 | getHeader(...) : String | ClientSuppliedIpUsedInSecurityCheck.java:25:33:25:34 | ip | IP address spoofing might include code from $@. | ClientSuppliedIpUsedInSecurityCheck.java:43:27:43:62 | getHeader(...) | this user input | diff --git a/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.java b/java/ql/test/experimental/query-tests/security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.java similarity index 73% rename from java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.java rename to java/ql/test/experimental/query-tests/security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.java index 689246340da1..93a860981d1d 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.java +++ b/java/ql/test/experimental/query-tests/security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.java @@ -6,7 +6,7 @@ import org.springframework.web.bind.annotation.ResponseBody; @Controller -public class UseOfLessTrustedSource { +public class ClientSuppliedIpUsedInSecurityCheck { @Autowired private HttpServletRequest request; @@ -30,15 +30,13 @@ public void bad2(HttpServletRequest request) { @GetMapping(value = "good1") @ResponseBody public String good1(HttpServletRequest request) { - String remoteAddr = ""; - if (request != null) { - remoteAddr = request.getHeader("X-FORWARDED-FOR"); - remoteAddr = remoteAddr.split(",")[remoteAddr.split(",").length - 1]; // good - if (remoteAddr == null || "".equals(remoteAddr)) { - remoteAddr = request.getRemoteAddr(); - } + String ip = request.getHeader("X-FORWARDED-FOR"); + // Good: if this application runs behind a reverse proxy it may append the real remote IP to the end of any client-supplied X-Forwarded-For header. + ip = ip.split(",")[ip.split(",").length - 1]; + if (!StringUtils.startsWith(ip, "192.168.")) { + new Exception("ip illegal"); } - return remoteAddr; + return ip; } protected String getClientIP() { @@ -48,4 +46,4 @@ protected String getClientIP() { } return xfHeader.split(",")[0]; } -} \ No newline at end of file +} diff --git a/java/ql/test/experimental/query-tests/security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.qlref b/java/ql/test/experimental/query-tests/security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.qlref new file mode 100644 index 000000000000..476249f5fd9a --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.qlref @@ -0,0 +1 @@ +experimental/Security/CWE/CWE-348/ClientSuppliedIpUsedInSecurityCheck.ql \ No newline at end of file diff --git a/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.expected b/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.expected deleted file mode 100644 index ff9cbc24a20b..000000000000 --- a/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.expected +++ /dev/null @@ -1,16 +0,0 @@ -edges -| UseOfLessTrustedSource.java:16:21:16:33 | getClientIP(...) : String | UseOfLessTrustedSource.java:17:37:17:38 | ip | -| UseOfLessTrustedSource.java:24:21:24:33 | getClientIP(...) : String | UseOfLessTrustedSource.java:25:33:25:34 | ip | -| UseOfLessTrustedSource.java:45:27:45:62 | getHeader(...) : String | UseOfLessTrustedSource.java:49:16:49:37 | ...[...] : String | -| UseOfLessTrustedSource.java:49:16:49:37 | ...[...] : String | UseOfLessTrustedSource.java:16:21:16:33 | getClientIP(...) : String | -| UseOfLessTrustedSource.java:49:16:49:37 | ...[...] : String | UseOfLessTrustedSource.java:24:21:24:33 | getClientIP(...) : String | -nodes -| UseOfLessTrustedSource.java:16:21:16:33 | getClientIP(...) : String | semmle.label | getClientIP(...) : String | -| UseOfLessTrustedSource.java:17:37:17:38 | ip | semmle.label | ip | -| UseOfLessTrustedSource.java:24:21:24:33 | getClientIP(...) : String | semmle.label | getClientIP(...) : String | -| UseOfLessTrustedSource.java:25:33:25:34 | ip | semmle.label | ip | -| UseOfLessTrustedSource.java:45:27:45:62 | getHeader(...) : String | semmle.label | getHeader(...) : String | -| UseOfLessTrustedSource.java:49:16:49:37 | ...[...] : String | semmle.label | ...[...] : String | -#select -| UseOfLessTrustedSource.java:17:37:17:38 | ip | UseOfLessTrustedSource.java:45:27:45:62 | getHeader(...) : String | UseOfLessTrustedSource.java:17:37:17:38 | ip | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:45:27:45:62 | getHeader(...) | this user input | -| UseOfLessTrustedSource.java:25:33:25:34 | ip | UseOfLessTrustedSource.java:45:27:45:62 | getHeader(...) : String | UseOfLessTrustedSource.java:25:33:25:34 | ip | IP address spoofing might include code from $@. | UseOfLessTrustedSource.java:45:27:45:62 | getHeader(...) | this user input | diff --git a/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.qlref b/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.qlref deleted file mode 100644 index 3c547a2c8714..000000000000 --- a/java/ql/test/experimental/query-tests/security/CWE-348/UseOfLessTrustedSource.qlref +++ /dev/null @@ -1 +0,0 @@ -experimental/Security/CWE/CWE-348/UseOfLessTrustedSource.ql \ No newline at end of file