From dc58f6fa8748fe947f6a039d5dc332e4cc48cadf Mon Sep 17 00:00:00 2001
From: dilanbhalla
Date: Thu, 25 Jun 2020 11:39:09 -0700
Subject: [PATCH 001/433] function/class synatax
---
.../Classes/NamingConventionsClasses.qhelp | 30 +++++++++++++++++++
.../src/Classes/NamingConventionsClasses.ql | 17 +++++++++++
.../NamingConventionsFunctions.qhelp | 30 +++++++++++++++++++
.../Functions/NamingConventionsFunctions.ql | 17 +++++++++++
.../Naming/NamingConventionsClasses.expected | 1 +
.../Naming/NamingConventionsClasses.py | 11 +++++++
.../Naming/NamingConventionsClasses.qlref | 1 +
.../NamingConventionsFunctions.expected | 1 +
.../general/NamingConventionsFunctions.py | 9 ++++++
.../general/NamingConventionsFunctions.qlref | 1 +
10 files changed, 118 insertions(+)
create mode 100644 python/ql/src/Classes/NamingConventionsClasses.qhelp
create mode 100644 python/ql/src/Classes/NamingConventionsClasses.ql
create mode 100644 python/ql/src/Functions/NamingConventionsFunctions.qhelp
create mode 100644 python/ql/src/Functions/NamingConventionsFunctions.ql
create mode 100644 python/ql/test/query-tests/Classes/Naming/NamingConventionsClasses.expected
create mode 100644 python/ql/test/query-tests/Classes/Naming/NamingConventionsClasses.py
create mode 100644 python/ql/test/query-tests/Classes/Naming/NamingConventionsClasses.qlref
create mode 100644 python/ql/test/query-tests/Functions/general/NamingConventionsFunctions.expected
create mode 100644 python/ql/test/query-tests/Functions/general/NamingConventionsFunctions.py
create mode 100644 python/ql/test/query-tests/Functions/general/NamingConventionsFunctions.qlref
diff --git a/python/ql/src/Classes/NamingConventionsClasses.qhelp b/python/ql/src/Classes/NamingConventionsClasses.qhelp
new file mode 100644
index 000000000000..7a6c0ca13dd8
--- /dev/null
+++ b/python/ql/src/Classes/NamingConventionsClasses.qhelp
@@ -0,0 +1,30 @@
+
+
+
+
+
+A class name that begins with a lowercase letter does not follow standard
+naming conventions. This decreases code readability. For example, class background
.
+
+
+
+
+
+
+Write the class name beginning with an uppercase letter. For example, class Background
.
+
+
+
+
+
+
+
+ Guido van Rossum, Barry Warsaw, Nick Coghlan PEP 8 -- Style Guide for Python Code
+
+
+
+
+
+
diff --git a/python/ql/src/Classes/NamingConventionsClasses.ql b/python/ql/src/Classes/NamingConventionsClasses.ql
new file mode 100644
index 000000000000..a1744f5d3e6e
--- /dev/null
+++ b/python/ql/src/Classes/NamingConventionsClasses.ql
@@ -0,0 +1,17 @@
+/**
+ * @name Misnamed class
+ * @description A class name that begins with a lowercase letter decreases readability.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision medium
+ * @id python/misnamed-type
+ * @tags maintainability
+ */
+
+import python
+
+from Class c
+where
+ c.inSource() and
+ not c.getName().substring(0, 1).toUpperCase() = c.getName().substring(0, 1)
+select c, "Class names should start in uppercase."
diff --git a/python/ql/src/Functions/NamingConventionsFunctions.qhelp b/python/ql/src/Functions/NamingConventionsFunctions.qhelp
new file mode 100644
index 000000000000..15014045542c
--- /dev/null
+++ b/python/ql/src/Functions/NamingConventionsFunctions.qhelp
@@ -0,0 +1,30 @@
+
+
+
+
+
+A function name that begins with an uppercase letter does not follow standard
+naming conventions. This decreases code readability. For example, Jump
.
+
+
+
+
+
+
+Write the function name beginning with an lowercase letter. For example, jump
.
+
+
+
+
+
+
+
+ Guido van Rossum, Barry Warsaw, Nick Coghlan PEP 8 -- Style Guide for Python Code
+
+
+
+
+
+
diff --git a/python/ql/src/Functions/NamingConventionsFunctions.ql b/python/ql/src/Functions/NamingConventionsFunctions.ql
new file mode 100644
index 000000000000..3ed619bc084e
--- /dev/null
+++ b/python/ql/src/Functions/NamingConventionsFunctions.ql
@@ -0,0 +1,17 @@
+/**
+ * @name Misnamed function
+ * @description A function name that begins with an uppercase letter decreases readability.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision medium
+ * @id python/misnamed-function
+ * @tags maintainability
+ */
+
+import python
+
+from Function f
+where
+ f.inSource() and
+ not f.getName().substring(0, 1).toLowerCase() = f.getName().substring(0, 1)
+select f, "Function names should start in lowercase."
diff --git a/python/ql/test/query-tests/Classes/Naming/NamingConventionsClasses.expected b/python/ql/test/query-tests/Classes/Naming/NamingConventionsClasses.expected
new file mode 100644
index 000000000000..8e6dea7fce4a
--- /dev/null
+++ b/python/ql/test/query-tests/Classes/Naming/NamingConventionsClasses.expected
@@ -0,0 +1 @@
+| NamingConventionsClasses.py:2:1:2:14 | Class badName | Class names should start in uppercase. |
diff --git a/python/ql/test/query-tests/Classes/Naming/NamingConventionsClasses.py b/python/ql/test/query-tests/Classes/Naming/NamingConventionsClasses.py
new file mode 100644
index 000000000000..c07bdb57234a
--- /dev/null
+++ b/python/ql/test/query-tests/Classes/Naming/NamingConventionsClasses.py
@@ -0,0 +1,11 @@
+# BAD, do not start class or interface name with lowercase letter
+class badName:
+
+ def hello(self):
+ print("hello")
+
+# Good, class name starts with capital letter
+class GoodName:
+
+ def hello(self):
+ print("hello")
\ No newline at end of file
diff --git a/python/ql/test/query-tests/Classes/Naming/NamingConventionsClasses.qlref b/python/ql/test/query-tests/Classes/Naming/NamingConventionsClasses.qlref
new file mode 100644
index 000000000000..01ad29859da2
--- /dev/null
+++ b/python/ql/test/query-tests/Classes/Naming/NamingConventionsClasses.qlref
@@ -0,0 +1 @@
+Classes/NamingConventionsClasses.ql
\ No newline at end of file
diff --git a/python/ql/test/query-tests/Functions/general/NamingConventionsFunctions.expected b/python/ql/test/query-tests/Functions/general/NamingConventionsFunctions.expected
new file mode 100644
index 000000000000..d87fe6c16f3e
--- /dev/null
+++ b/python/ql/test/query-tests/Functions/general/NamingConventionsFunctions.expected
@@ -0,0 +1 @@
+| NamingConventionsFunctions.py:4:5:4:25 | Function HelloWorld | Function names should start in lowercase. |
diff --git a/python/ql/test/query-tests/Functions/general/NamingConventionsFunctions.py b/python/ql/test/query-tests/Functions/general/NamingConventionsFunctions.py
new file mode 100644
index 000000000000..fb3e89ab8e92
--- /dev/null
+++ b/python/ql/test/query-tests/Functions/general/NamingConventionsFunctions.py
@@ -0,0 +1,9 @@
+class Test:
+
+ # BAD, do not start function name with uppercase letter
+ def HelloWorld(self):
+ print("hello world")
+
+ # GOOD, function name starts with lowercase letter
+ def hello_world(self):
+ print("hello world")
\ No newline at end of file
diff --git a/python/ql/test/query-tests/Functions/general/NamingConventionsFunctions.qlref b/python/ql/test/query-tests/Functions/general/NamingConventionsFunctions.qlref
new file mode 100644
index 000000000000..da4780ecbe9d
--- /dev/null
+++ b/python/ql/test/query-tests/Functions/general/NamingConventionsFunctions.qlref
@@ -0,0 +1 @@
+Functions/NamingConventionsFunctions.ql
\ No newline at end of file
From dc73fcc4e856cbfc3ea5967013dcf858679563f8 Mon Sep 17 00:00:00 2001
From: dilanbhalla
Date: Wed, 1 Jul 2020 09:54:58 -0700
Subject: [PATCH 002/433] moved to experimental
---
.../{ => experimental}/Classes/NamingConventionsClasses.qhelp | 0
.../src/{ => experimental}/Classes/NamingConventionsClasses.ql | 0
.../Functions/NamingConventionsFunctions.qhelp | 0
.../{ => experimental}/Functions/NamingConventionsFunctions.ql | 0
.../query-tests/Classes/Naming/NamingConventionsClasses.expected | 0
.../query-tests/Classes/Naming/NamingConventionsClasses.py | 0
.../query-tests/Classes/Naming/NamingConventionsClasses.qlref | 1 +
.../Functions/general/NamingConventionsFunctions.expected | 0
.../query-tests/Functions/general/NamingConventionsFunctions.py | 0
.../Functions/general/NamingConventionsFunctions.qlref | 1 +
.../query-tests/Classes/Naming/NamingConventionsClasses.qlref | 1 -
.../Functions/general/NamingConventionsFunctions.qlref | 1 -
12 files changed, 2 insertions(+), 2 deletions(-)
rename python/ql/src/{ => experimental}/Classes/NamingConventionsClasses.qhelp (100%)
rename python/ql/src/{ => experimental}/Classes/NamingConventionsClasses.ql (100%)
rename python/ql/src/{ => experimental}/Functions/NamingConventionsFunctions.qhelp (100%)
rename python/ql/src/{ => experimental}/Functions/NamingConventionsFunctions.ql (100%)
rename python/ql/test/{ => experimental}/query-tests/Classes/Naming/NamingConventionsClasses.expected (100%)
rename python/ql/test/{ => experimental}/query-tests/Classes/Naming/NamingConventionsClasses.py (100%)
create mode 100644 python/ql/test/experimental/query-tests/Classes/Naming/NamingConventionsClasses.qlref
rename python/ql/test/{ => experimental}/query-tests/Functions/general/NamingConventionsFunctions.expected (100%)
rename python/ql/test/{ => experimental}/query-tests/Functions/general/NamingConventionsFunctions.py (100%)
create mode 100644 python/ql/test/experimental/query-tests/Functions/general/NamingConventionsFunctions.qlref
delete mode 100644 python/ql/test/query-tests/Classes/Naming/NamingConventionsClasses.qlref
delete mode 100644 python/ql/test/query-tests/Functions/general/NamingConventionsFunctions.qlref
diff --git a/python/ql/src/Classes/NamingConventionsClasses.qhelp b/python/ql/src/experimental/Classes/NamingConventionsClasses.qhelp
similarity index 100%
rename from python/ql/src/Classes/NamingConventionsClasses.qhelp
rename to python/ql/src/experimental/Classes/NamingConventionsClasses.qhelp
diff --git a/python/ql/src/Classes/NamingConventionsClasses.ql b/python/ql/src/experimental/Classes/NamingConventionsClasses.ql
similarity index 100%
rename from python/ql/src/Classes/NamingConventionsClasses.ql
rename to python/ql/src/experimental/Classes/NamingConventionsClasses.ql
diff --git a/python/ql/src/Functions/NamingConventionsFunctions.qhelp b/python/ql/src/experimental/Functions/NamingConventionsFunctions.qhelp
similarity index 100%
rename from python/ql/src/Functions/NamingConventionsFunctions.qhelp
rename to python/ql/src/experimental/Functions/NamingConventionsFunctions.qhelp
diff --git a/python/ql/src/Functions/NamingConventionsFunctions.ql b/python/ql/src/experimental/Functions/NamingConventionsFunctions.ql
similarity index 100%
rename from python/ql/src/Functions/NamingConventionsFunctions.ql
rename to python/ql/src/experimental/Functions/NamingConventionsFunctions.ql
diff --git a/python/ql/test/query-tests/Classes/Naming/NamingConventionsClasses.expected b/python/ql/test/experimental/query-tests/Classes/Naming/NamingConventionsClasses.expected
similarity index 100%
rename from python/ql/test/query-tests/Classes/Naming/NamingConventionsClasses.expected
rename to python/ql/test/experimental/query-tests/Classes/Naming/NamingConventionsClasses.expected
diff --git a/python/ql/test/query-tests/Classes/Naming/NamingConventionsClasses.py b/python/ql/test/experimental/query-tests/Classes/Naming/NamingConventionsClasses.py
similarity index 100%
rename from python/ql/test/query-tests/Classes/Naming/NamingConventionsClasses.py
rename to python/ql/test/experimental/query-tests/Classes/Naming/NamingConventionsClasses.py
diff --git a/python/ql/test/experimental/query-tests/Classes/Naming/NamingConventionsClasses.qlref b/python/ql/test/experimental/query-tests/Classes/Naming/NamingConventionsClasses.qlref
new file mode 100644
index 000000000000..7ed945d782c4
--- /dev/null
+++ b/python/ql/test/experimental/query-tests/Classes/Naming/NamingConventionsClasses.qlref
@@ -0,0 +1 @@
+experimental/Classes/NamingConventionsClasses.ql
\ No newline at end of file
diff --git a/python/ql/test/query-tests/Functions/general/NamingConventionsFunctions.expected b/python/ql/test/experimental/query-tests/Functions/general/NamingConventionsFunctions.expected
similarity index 100%
rename from python/ql/test/query-tests/Functions/general/NamingConventionsFunctions.expected
rename to python/ql/test/experimental/query-tests/Functions/general/NamingConventionsFunctions.expected
diff --git a/python/ql/test/query-tests/Functions/general/NamingConventionsFunctions.py b/python/ql/test/experimental/query-tests/Functions/general/NamingConventionsFunctions.py
similarity index 100%
rename from python/ql/test/query-tests/Functions/general/NamingConventionsFunctions.py
rename to python/ql/test/experimental/query-tests/Functions/general/NamingConventionsFunctions.py
diff --git a/python/ql/test/experimental/query-tests/Functions/general/NamingConventionsFunctions.qlref b/python/ql/test/experimental/query-tests/Functions/general/NamingConventionsFunctions.qlref
new file mode 100644
index 000000000000..0204694de0a3
--- /dev/null
+++ b/python/ql/test/experimental/query-tests/Functions/general/NamingConventionsFunctions.qlref
@@ -0,0 +1 @@
+experimental/Functions/NamingConventionsFunctions.ql
\ No newline at end of file
diff --git a/python/ql/test/query-tests/Classes/Naming/NamingConventionsClasses.qlref b/python/ql/test/query-tests/Classes/Naming/NamingConventionsClasses.qlref
deleted file mode 100644
index 01ad29859da2..000000000000
--- a/python/ql/test/query-tests/Classes/Naming/NamingConventionsClasses.qlref
+++ /dev/null
@@ -1 +0,0 @@
-Classes/NamingConventionsClasses.ql
\ No newline at end of file
diff --git a/python/ql/test/query-tests/Functions/general/NamingConventionsFunctions.qlref b/python/ql/test/query-tests/Functions/general/NamingConventionsFunctions.qlref
deleted file mode 100644
index da4780ecbe9d..000000000000
--- a/python/ql/test/query-tests/Functions/general/NamingConventionsFunctions.qlref
+++ /dev/null
@@ -1 +0,0 @@
-Functions/NamingConventionsFunctions.ql
\ No newline at end of file
From 26b030f8ccaf1b1922623a035e15cef239ec7314 Mon Sep 17 00:00:00 2001
From: dilanbhalla
Date: Tue, 7 Jul 2020 10:52:26 -0700
Subject: [PATCH 003/433] fixed pr suggestions
---
.../Classes/NamingConventionsClasses.qhelp | 2 +-
.../Classes/NamingConventionsClasses.ql | 14 ++++++++++----
.../Functions/NamingConventionsFunctions.qhelp | 2 +-
.../Functions/NamingConventionsFunctions.ql | 14 ++++++++++----
4 files changed, 22 insertions(+), 10 deletions(-)
diff --git a/python/ql/src/experimental/Classes/NamingConventionsClasses.qhelp b/python/ql/src/experimental/Classes/NamingConventionsClasses.qhelp
index 7a6c0ca13dd8..1a7f4bc45a4f 100644
--- a/python/ql/src/experimental/Classes/NamingConventionsClasses.qhelp
+++ b/python/ql/src/experimental/Classes/NamingConventionsClasses.qhelp
@@ -22,7 +22,7 @@ Write the class name beginning with an uppercase letter. For example, clas
Guido van Rossum, Barry Warsaw, Nick Coghlan PEP 8 -- Style Guide for Python Code
-
+ Python Class Names
diff --git a/python/ql/src/experimental/Classes/NamingConventionsClasses.ql b/python/ql/src/experimental/Classes/NamingConventionsClasses.ql
index a1744f5d3e6e..d4919c3ece8d 100644
--- a/python/ql/src/experimental/Classes/NamingConventionsClasses.ql
+++ b/python/ql/src/experimental/Classes/NamingConventionsClasses.ql
@@ -3,15 +3,21 @@
* @description A class name that begins with a lowercase letter decreases readability.
* @kind problem
* @problem.severity recommendation
- * @precision medium
- * @id python/misnamed-type
+ * @id py/misnamed-class
* @tags maintainability
*/
import python
-from Class c
+from Class c, string first_char
where
c.inSource() and
- not c.getName().substring(0, 1).toUpperCase() = c.getName().substring(0, 1)
+ first_char = c.getName().prefix(1) and
+ not first_char = first_char.toUpperCase() and
+ not exists(Class c1, string first_char1 |
+ c1 != c and
+ c1.getLocation().getFile() = c.getLocation().getFile() and
+ first_char1 = c1.getName().prefix(1) and
+ not first_char1 = first_char1.toUpperCase()
+ )
select c, "Class names should start in uppercase."
diff --git a/python/ql/src/experimental/Functions/NamingConventionsFunctions.qhelp b/python/ql/src/experimental/Functions/NamingConventionsFunctions.qhelp
index 15014045542c..46d948592ff2 100644
--- a/python/ql/src/experimental/Functions/NamingConventionsFunctions.qhelp
+++ b/python/ql/src/experimental/Functions/NamingConventionsFunctions.qhelp
@@ -22,7 +22,7 @@ Write the function name beginning with an lowercase letter. For example, j
Guido van Rossum, Barry Warsaw, Nick Coghlan PEP 8 -- Style Guide for Python Code
-
+ Python Function and Variable Names
diff --git a/python/ql/src/experimental/Functions/NamingConventionsFunctions.ql b/python/ql/src/experimental/Functions/NamingConventionsFunctions.ql
index 3ed619bc084e..80dc0e99cd8e 100644
--- a/python/ql/src/experimental/Functions/NamingConventionsFunctions.ql
+++ b/python/ql/src/experimental/Functions/NamingConventionsFunctions.ql
@@ -3,15 +3,21 @@
* @description A function name that begins with an uppercase letter decreases readability.
* @kind problem
* @problem.severity recommendation
- * @precision medium
- * @id python/misnamed-function
+ * @id py/misnamed-function
* @tags maintainability
*/
import python
-from Function f
+from Function f, string first_char
where
f.inSource() and
- not f.getName().substring(0, 1).toLowerCase() = f.getName().substring(0, 1)
+ first_char = f.getName().prefix(1) and
+ not first_char = first_char.toLowerCase() and
+ not exists(Function f1, string first_char1 |
+ f1 != f and
+ f1.getLocation().getFile() = f.getLocation().getFile() and
+ first_char1 = f1.getName().prefix(1) and
+ not first_char1 = first_char1.toLowerCase()
+ )
select f, "Function names should start in lowercase."
From 8119fd2ad1e2d009991f9a12969ccd55054163c4 Mon Sep 17 00:00:00 2001
From: haby0
Date: Thu, 18 Feb 2021 18:11:10 +0800
Subject: [PATCH 004/433] *)add JsonHijacking ql query
---
.../Security/CWE/CWE-352/JsonHijacking.java | 119 ++++++++++++++++++
.../Security/CWE/CWE-352/JsonHijacking.qhelp | 35 ++++++
.../src/Security/CWE/CWE-352/JsonHijacking.ql | 32 +++++
.../Security/CWE/CWE-352/JsonHijackingLib.qll | 92 ++++++++++++++
.../Security/CWE/CWE-352/JsonStringLib.qll | 42 +++++++
.../security/CWE-352/JsonHijacking.expected | 48 +++++++
.../security/CWE-352/JsonHijacking.java | 119 ++++++++++++++++++
.../security/CWE-352/JsonHijacking.qlref | 1 +
.../query-tests/security/CWE-352/options | 1 +
.../com/alibaba/fastjson/JSON.java | 4 +
.../stereotype/Controller.java | 14 +++
.../core/annotation/AliasFor.class | Bin 0 -> 385 bytes
.../core/annotation/AliasFor.java | 10 ++
.../web/bind/annotation/GetMapping.class | Bin 0 -> 504 bytes
.../web/bind/annotation/GetMapping.java | 19 +++
.../web/bind/annotation/RequestMapping.java | 13 ++
.../web/bind/annotation/ResponseBody.class | Bin 0 -> 184 bytes
.../web/bind/annotation/ResponseBody.java | 4 +
18 files changed, 553 insertions(+)
create mode 100644 java/ql/src/Security/CWE/CWE-352/JsonHijacking.java
create mode 100644 java/ql/src/Security/CWE/CWE-352/JsonHijacking.qhelp
create mode 100644 java/ql/src/Security/CWE/CWE-352/JsonHijacking.ql
create mode 100644 java/ql/src/Security/CWE/CWE-352/JsonHijackingLib.qll
create mode 100644 java/ql/src/Security/CWE/CWE-352/JsonStringLib.qll
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonHijacking.expected
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonHijacking.java
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonHijacking.qlref
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/options
create mode 100644 java/ql/test/stubs/spring-context-5.3.2/org/springframework/stereotype/Controller.java
create mode 100644 java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/annotation/AliasFor.class
create mode 100644 java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/annotation/AliasFor.java
create mode 100644 java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/GetMapping.class
create mode 100644 java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/GetMapping.java
create mode 100644 java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/RequestMapping.java
create mode 100644 java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/ResponseBody.class
create mode 100644 java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/ResponseBody.java
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonHijacking.java b/java/ql/src/Security/CWE/CWE-352/JsonHijacking.java
new file mode 100644
index 000000000000..d08d436fa07b
--- /dev/null
+++ b/java/ql/src/Security/CWE/CWE-352/JsonHijacking.java
@@ -0,0 +1,119 @@
+import com.alibaba.fastjson.JSONObject;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.gson.Gson;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Random;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+@Controller
+public class JsonHijacking {
+
+ private static HashMap hashMap = new HashMap();
+
+ static {
+ hashMap.put("username","admin");
+ hashMap.put("password","123456");
+ }
+
+
+ @GetMapping(value = "jsonp1")
+ @ResponseBody
+ public String bad1(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+
+ Gson gson = new Gson();
+ String result = gson.toJson(hashMap);
+ resultStr = jsonpCallback + "(" + result + ")";
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp2")
+ @ResponseBody
+ public String bad2(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+
+ resultStr = jsonpCallback + "(" + JSONObject.toJSONString(hashMap) + ")";
+
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp3")
+ @ResponseBody
+ public String bad3(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp4")
+ @ResponseBody
+ public String bad4(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String restr = JSONObject.toJSONString(hashMap);
+ resultStr = jsonpCallback + "(" + restr + ");";
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp5")
+ @ResponseBody
+ public void bad5(HttpServletRequest request,
+ HttpServletResponse response) throws Exception {
+ response.setContentType("application/json");
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ PrintWriter pw = null;
+ Gson gson = new Gson();
+ String result = gson.toJson(hashMap);
+
+ String resultStr = null;
+ pw = response.getWriter();
+ resultStr = jsonpCallback + "(" + result + ")";
+ pw.println(resultStr);
+ }
+
+ @GetMapping(value = "jsonp6")
+ @ResponseBody
+ public void bad6(HttpServletRequest request,
+ HttpServletResponse response) throws Exception {
+ response.setContentType("application/json");
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ PrintWriter pw = null;
+ ObjectMapper mapper = new ObjectMapper();
+ String result = mapper.writeValueAsString(hashMap);
+ String resultStr = null;
+ pw = response.getWriter();
+ resultStr = jsonpCallback + "(" + result + ")";
+ pw.println(resultStr);
+ }
+
+ @GetMapping(value = "jsonp7")
+ @ResponseBody
+ public String good(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+
+ String val = "";
+ Random random = new Random();
+ for (int i = 0; i < 10; i++) {
+ val += String.valueOf(random.nextInt(10));
+ }
+ // good
+ jsonpCallback = jsonpCallback + "_" + val;
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
+
+ public static String getJsonStr(Object result) {
+ return JSONObject.toJSONString(result);
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonHijacking.qhelp b/java/ql/src/Security/CWE/CWE-352/JsonHijacking.qhelp
new file mode 100644
index 000000000000..38e1845f9929
--- /dev/null
+++ b/java/ql/src/Security/CWE/CWE-352/JsonHijacking.qhelp
@@ -0,0 +1,35 @@
+
+
+
+The software uses external input as the function name to wrap JSON data and return it to the client as a request response. When there is a cross-domain problem,
+there is a problem of sensitive information leakage.
+
+
+
+
+The function name verification processing for external input can effectively prevent the leakage of sensitive information.
+
+
+
+
+The following example shows the case of no verification processing and verification processing for the external input function name.
+
+
+
+
+
+
+
+OWASPLondon20161124_JSON_Hijacking_Gareth_Heyes:
+JSON hijacking.
+
+
+Practical JSONP Injection:
+
+ Completely controllable from the URL (GET variable)
+.
+
+
+
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonHijacking.ql b/java/ql/src/Security/CWE/CWE-352/JsonHijacking.ql
new file mode 100644
index 000000000000..a6a6d2475f09
--- /dev/null
+++ b/java/ql/src/Security/CWE/CWE-352/JsonHijacking.ql
@@ -0,0 +1,32 @@
+/**
+ * @name JSON Hijacking
+ * @description User-controlled callback function names that are not verified are vulnerable
+ * to json hijacking attacks.
+ * @kind path-problem
+ * @problem.severity error
+ * @precision high
+ * @id java/Json-hijacking
+ * @tags security
+ * external/cwe/cwe-352
+ */
+
+import java
+import JsonHijackingLib
+import semmle.code.java.dataflow.FlowSources
+import DataFlow::PathGraph
+
+/** Taint-tracking configuration tracing flow from remote sources to output jsonp data. */
+class JsonHijackingConfig extends TaintTracking::Configuration {
+ JsonHijackingConfig() { this = "JsonHijackingConfig" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof JsonHijackingSink }
+}
+
+from DataFlow::PathNode source, DataFlow::PathNode sink, JsonHijackingConfig conf
+where
+ conf.hasFlowPath(source, sink) and
+ exists(JsonHijackingFlowConfig jhfc | jhfc.hasFlowTo(sink.getNode()))
+select sink.getNode(), source, sink, "Json Hijacking query might include code from $@.",
+ source.getNode(), "this user input"
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonHijackingLib.qll b/java/ql/src/Security/CWE/CWE-352/JsonHijackingLib.qll
new file mode 100644
index 000000000000..ba91a6670bfd
--- /dev/null
+++ b/java/ql/src/Security/CWE/CWE-352/JsonHijackingLib.qll
@@ -0,0 +1,92 @@
+import java
+import DataFlow
+import JsonStringLib
+import semmle.code.java.dataflow.DataFlow
+import semmle.code.java.dataflow.FlowSources
+import semmle.code.java.frameworks.spring.SpringController
+
+/** A data flow sink for unvalidated user input that is used to jsonp. */
+abstract class JsonHijackingSink extends DataFlow::Node { }
+
+/** Use ```print```, ```println```, ```write``` to output result. */
+private class WriterPrintln extends JsonHijackingSink {
+ WriterPrintln() {
+ exists(MethodAccess ma |
+ ma.getMethod().getName().regexpMatch("print|println|write") and
+ ma.getMethod()
+ .getDeclaringType()
+ .getASourceSupertype*()
+ .hasQualifiedName("java.io", "PrintWriter") and
+ ma.getArgument(0) = this.asExpr()
+ )
+ }
+}
+
+/** Spring Request Method return result. */
+private class SpringReturn extends JsonHijackingSink {
+ SpringReturn() {
+ exists(ReturnStmt rs, Method m | m = rs.getEnclosingCallable() |
+ m instanceof SpringRequestMappingMethod and
+ rs.getResult() = this.asExpr()
+ )
+ }
+}
+
+/** A concatenate expression using `(` and `)` or `);`. */
+class JsonHijackingExpr extends AddExpr {
+ JsonHijackingExpr() {
+ getRightOperand().toString().regexpMatch("\"\\)\"|\"\\);\"") and
+ getLeftOperand()
+ .(AddExpr)
+ .getLeftOperand()
+ .(AddExpr)
+ .getRightOperand()
+ .toString()
+ .regexpMatch("\"\\(\"")
+ }
+
+ /** Get the jsonp function name of this expression */
+ Expr getFunctionName() {
+ result = getLeftOperand().(AddExpr).getLeftOperand().(AddExpr).getLeftOperand()
+ }
+
+ /** Get the json data of this expression */
+ Expr getJsonExpr() { result = getLeftOperand().(AddExpr).getRightOperand() }
+}
+
+/** A data flow configuration tracing flow from remote sources to jsonp function name. */
+class RemoteFlowConfig extends DataFlow2::Configuration {
+ RemoteFlowConfig() { this = "RemoteFlowConfig" }
+
+ override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(JsonHijackingExpr jhe | jhe.getFunctionName() = sink.asExpr())
+ }
+}
+
+/** A data flow configuration tracing flow from json data to splicing jsonp data. */
+class JsonDataFlowConfig extends DataFlow2::Configuration {
+ JsonDataFlowConfig() { this = "JsonDataFlowConfig" }
+
+ override predicate isSource(DataFlow::Node src) { src instanceof JsonpStringSource }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(JsonHijackingExpr jhe | jhe.getJsonExpr() = sink.asExpr())
+ }
+}
+
+/** Taint-tracking configuration tracing flow from user-controllable function name jsonp data to output jsonp data. */
+class JsonHijackingFlowConfig extends TaintTracking::Configuration {
+ JsonHijackingFlowConfig() { this = "JsonHijackingFlowConfig" }
+
+ override predicate isSource(DataFlow::Node src) {
+ exists(JsonHijackingExpr jhe, JsonDataFlowConfig jdfc, RemoteFlowConfig rfc |
+ jhe = src.asExpr() and
+ jdfc.hasFlowTo(DataFlow::exprNode(jhe.getJsonExpr())) and
+ rfc.hasFlowTo(DataFlow::exprNode(jhe.getFunctionName()))
+ )
+ }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof JsonHijackingSink }
+}
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonStringLib.qll b/java/ql/src/Security/CWE/CWE-352/JsonStringLib.qll
new file mode 100644
index 000000000000..0da8bc860d11
--- /dev/null
+++ b/java/ql/src/Security/CWE/CWE-352/JsonStringLib.qll
@@ -0,0 +1,42 @@
+import java
+import semmle.code.java.dataflow.DataFlow
+import semmle.code.java.dataflow.FlowSources
+import DataFlow::PathGraph
+
+/** Json string type data */
+abstract class JsonpStringSource extends DataFlow::Node { }
+
+/** Convert to String using Gson library. */
+private class GsonString extends JsonpStringSource {
+ GsonString() {
+ exists(MethodAccess ma, Method m | ma.getMethod() = m |
+ m.hasName("toJson") and
+ m.getDeclaringType().getASupertype*().hasQualifiedName("com.google.gson", "Gson") and
+ this.asExpr() = ma
+ )
+ }
+}
+
+/** Convert to String using Fastjson library. */
+private class FastjsonString extends JsonpStringSource {
+ FastjsonString() {
+ exists(MethodAccess ma, Method m | ma.getMethod() = m |
+ m.hasName("toJSONString") and
+ m.getDeclaringType().getASupertype*().hasQualifiedName("com.alibaba.fastjson", "JSON") and
+ this.asExpr() = ma
+ )
+ }
+}
+
+/** Convert to String using Jackson library. */
+private class JacksonString extends JsonpStringSource {
+ JacksonString() {
+ exists(MethodAccess ma, Method m | ma.getMethod() = m |
+ m.hasName("writeValueAsString") and
+ m.getDeclaringType()
+ .getASupertype*()
+ .hasQualifiedName("com.fasterxml.jackson.databind", "ObjectMapper") and
+ this.asExpr() = ma
+ )
+ }
+}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonHijacking.expected b/java/ql/test/experimental/query-tests/security/CWE-352/JsonHijacking.expected
new file mode 100644
index 000000000000..8efc3be1673b
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonHijacking.expected
@@ -0,0 +1,48 @@
+edges
+| JsonHijacking.java:28:32:28:68 | getParameter(...) : String | JsonHijacking.java:33:16:33:24 | resultStr |
+| JsonHijacking.java:32:21:32:54 | ... + ... : String | JsonHijacking.java:33:16:33:24 | resultStr |
+| JsonHijacking.java:40:32:40:68 | getParameter(...) : String | JsonHijacking.java:44:16:44:24 | resultStr |
+| JsonHijacking.java:42:21:42:80 | ... + ... : String | JsonHijacking.java:44:16:44:24 | resultStr |
+| JsonHijacking.java:51:32:51:68 | getParameter(...) : String | JsonHijacking.java:54:16:54:24 | resultStr |
+| JsonHijacking.java:53:21:53:55 | ... + ... : String | JsonHijacking.java:54:16:54:24 | resultStr |
+| JsonHijacking.java:61:32:61:68 | getParameter(...) : String | JsonHijacking.java:64:16:64:24 | resultStr |
+| JsonHijacking.java:63:21:63:54 | ... + ... : String | JsonHijacking.java:64:16:64:24 | resultStr |
+| JsonHijacking.java:72:32:72:68 | getParameter(...) : String | JsonHijacking.java:80:20:80:28 | resultStr |
+| JsonHijacking.java:79:21:79:54 | ... + ... : String | JsonHijacking.java:80:20:80:28 | resultStr |
+| JsonHijacking.java:88:32:88:68 | getParameter(...) : String | JsonHijacking.java:95:20:95:28 | resultStr |
+| JsonHijacking.java:94:21:94:54 | ... + ... : String | JsonHijacking.java:95:20:95:28 | resultStr |
+| JsonHijacking.java:102:32:102:68 | getParameter(...) : String | JsonHijacking.java:113:16:113:24 | resultStr |
+nodes
+| JsonHijacking.java:28:32:28:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonHijacking.java:32:21:32:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonHijacking.java:33:16:33:24 | resultStr | semmle.label | resultStr |
+| JsonHijacking.java:33:16:33:24 | resultStr | semmle.label | resultStr |
+| JsonHijacking.java:40:32:40:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonHijacking.java:42:21:42:80 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonHijacking.java:44:16:44:24 | resultStr | semmle.label | resultStr |
+| JsonHijacking.java:44:16:44:24 | resultStr | semmle.label | resultStr |
+| JsonHijacking.java:51:32:51:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonHijacking.java:53:21:53:55 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonHijacking.java:54:16:54:24 | resultStr | semmle.label | resultStr |
+| JsonHijacking.java:54:16:54:24 | resultStr | semmle.label | resultStr |
+| JsonHijacking.java:61:32:61:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonHijacking.java:63:21:63:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonHijacking.java:64:16:64:24 | resultStr | semmle.label | resultStr |
+| JsonHijacking.java:64:16:64:24 | resultStr | semmle.label | resultStr |
+| JsonHijacking.java:72:32:72:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonHijacking.java:79:21:79:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonHijacking.java:80:20:80:28 | resultStr | semmle.label | resultStr |
+| JsonHijacking.java:80:20:80:28 | resultStr | semmle.label | resultStr |
+| JsonHijacking.java:88:32:88:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonHijacking.java:94:21:94:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonHijacking.java:95:20:95:28 | resultStr | semmle.label | resultStr |
+| JsonHijacking.java:95:20:95:28 | resultStr | semmle.label | resultStr |
+| JsonHijacking.java:102:32:102:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonHijacking.java:113:16:113:24 | resultStr | semmle.label | resultStr |
+#select
+| JsonHijacking.java:33:16:33:24 | resultStr | JsonHijacking.java:28:32:28:68 | getParameter(...) : String | JsonHijacking.java:33:16:33:24 | resultStr | Json Hijacking query might include code from $@. | JsonHijacking.java:28:32:28:68 | getParameter(...) | this user input |
+| JsonHijacking.java:44:16:44:24 | resultStr | JsonHijacking.java:40:32:40:68 | getParameter(...) : String | JsonHijacking.java:44:16:44:24 | resultStr | Json Hijacking query might include code from $@. | JsonHijacking.java:40:32:40:68 | getParameter(...) | this user input |
+| JsonHijacking.java:54:16:54:24 | resultStr | JsonHijacking.java:51:32:51:68 | getParameter(...) : String | JsonHijacking.java:54:16:54:24 | resultStr | Json Hijacking query might include code from $@. | JsonHijacking.java:51:32:51:68 | getParameter(...) | this user input |
+| JsonHijacking.java:64:16:64:24 | resultStr | JsonHijacking.java:61:32:61:68 | getParameter(...) : String | JsonHijacking.java:64:16:64:24 | resultStr | Json Hijacking query might include code from $@. | JsonHijacking.java:61:32:61:68 | getParameter(...) | this user input |
+| JsonHijacking.java:80:20:80:28 | resultStr | JsonHijacking.java:72:32:72:68 | getParameter(...) : String | JsonHijacking.java:80:20:80:28 | resultStr | Json Hijacking query might include code from $@. | JsonHijacking.java:72:32:72:68 | getParameter(...) | this user input |
+| JsonHijacking.java:95:20:95:28 | resultStr | JsonHijacking.java:88:32:88:68 | getParameter(...) : String | JsonHijacking.java:95:20:95:28 | resultStr | Json Hijacking query might include code from $@. | JsonHijacking.java:88:32:88:68 | getParameter(...) | this user input |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonHijacking.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonHijacking.java
new file mode 100644
index 000000000000..9b473e0610ca
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonHijacking.java
@@ -0,0 +1,119 @@
+import com.alibaba.fastjson.JSONObject;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.gson.Gson;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Random;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+@Controller
+public class JsonHijacking {
+
+ private static HashMap hashMap = new HashMap();
+
+ static {
+ hashMap.put("username","admin");
+ hashMap.put("password","123456");
+ }
+
+
+ @GetMapping(value = "jsonp1")
+ @ResponseBody
+ public String bad1(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+
+ Gson gson = new Gson();
+ String result = gson.toJson(hashMap);
+ resultStr = jsonpCallback + "(" + result + ")";
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp2")
+ @ResponseBody
+ public String bad2(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+
+ resultStr = jsonpCallback + "(" + JSONObject.toJSONString(hashMap) + ")";
+
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp3")
+ @ResponseBody
+ public String bad3(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp4")
+ @ResponseBody
+ public String bad4(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String restr = JSONObject.toJSONString(hashMap);
+ resultStr = jsonpCallback + "(" + restr + ");";
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp5")
+ @ResponseBody
+ public void bad5(HttpServletRequest request,
+ HttpServletResponse response) throws Exception {
+ response.setContentType("application/json");
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ PrintWriter pw = null;
+ Gson gson = new Gson();
+ String result = gson.toJson(hashMap);
+
+ String resultStr = null;
+ pw = response.getWriter();
+ resultStr = jsonpCallback + "(" + result + ")";
+ pw.println(resultStr);
+ }
+
+ @GetMapping(value = "jsonp6")
+ @ResponseBody
+ public void bad6(HttpServletRequest request,
+ HttpServletResponse response) throws Exception {
+ response.setContentType("application/json");
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ PrintWriter pw = null;
+ ObjectMapper mapper = new ObjectMapper();
+ String result = mapper.writeValueAsString(hashMap);
+ String resultStr = null;
+ pw = response.getWriter();
+ resultStr = jsonpCallback + "(" + result + ")";
+ pw.println(resultStr);
+ }
+
+ @GetMapping(value = "jsonp7")
+ @ResponseBody
+ public String good(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+
+ String val = "";
+ Random random = new Random();
+ for (int i = 0; i < 10; i++) {
+ val += String.valueOf(random.nextInt(10));
+ }
+ // good
+ jsonpCallback = jsonpCallback + "_" + val;
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
+
+ public static String getJsonStr(Object result) {
+ return JSONObject.toJSONString(result);
+ }
+}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonHijacking.qlref b/java/ql/test/experimental/query-tests/security/CWE-352/JsonHijacking.qlref
new file mode 100644
index 000000000000..e79471b3c1ed
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonHijacking.qlref
@@ -0,0 +1 @@
+Security/CWE/CWE-352/JsonHijacking.ql
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/options b/java/ql/test/experimental/query-tests/security/CWE-352/options
new file mode 100644
index 000000000000..3676b8e38b69
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/options
@@ -0,0 +1 @@
+//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/apache-http-4.4.13/:${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../../stubs/fastjson-1.2.74/:${testdir}/../../../../stubs/gson-2.8.6/:${testdir}/../../../../stubs/jackson-databind-2.10/:${testdir}/../../../../stubs/springframework-5.2.3/:${testdir}/../../../../stubs/spring-context-5.3.2/:${testdir}/../../../../stubs/spring-web-5.3.2/:${testdir}/../../../../stubs/spring-core-5.3.2/
diff --git a/java/ql/test/stubs/fastjson-1.2.74/com/alibaba/fastjson/JSON.java b/java/ql/test/stubs/fastjson-1.2.74/com/alibaba/fastjson/JSON.java
index b71e890e9b79..99e2873d3750 100644
--- a/java/ql/test/stubs/fastjson-1.2.74/com/alibaba/fastjson/JSON.java
+++ b/java/ql/test/stubs/fastjson-1.2.74/com/alibaba/fastjson/JSON.java
@@ -26,6 +26,10 @@
import com.alibaba.fastjson.parser.deserializer.ParseProcess;
public abstract class JSON {
+ public static String toJSONString(Object object) {
+ return null;
+ }
+
public static Object parse(String text) {
return null;
}
diff --git a/java/ql/test/stubs/spring-context-5.3.2/org/springframework/stereotype/Controller.java b/java/ql/test/stubs/spring-context-5.3.2/org/springframework/stereotype/Controller.java
new file mode 100644
index 000000000000..9b1751fa2ae3
--- /dev/null
+++ b/java/ql/test/stubs/spring-context-5.3.2/org/springframework/stereotype/Controller.java
@@ -0,0 +1,14 @@
+package org.springframework.stereotype;
+
+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})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Controller {
+ String value() default "";
+}
diff --git a/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/annotation/AliasFor.class b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/annotation/AliasFor.class
new file mode 100644
index 0000000000000000000000000000000000000000..438065489278b92503abc2ad8d75716c5e56ac08
GIT binary patch
literal 385
zcmb7=!Ab)$5QhJ0x31kDdrH&Kx0=)qG#3PM4!PcXZrOKO@(l3m};gAd?CiL-}x
zJqaEL=Fj~6^G&|KKRyB6W0qr@<21(^Vbrp1G~wdrcD3b}m1S3}bqdDS4}|lDb3So0
z-aYCKH#QMKxO{0`GCTd`S`$rab#IG=`O1e{#kVeF6L_cJeRx%s4_fgdPA#nAxb#7`
zj5*1|vPl9`tbG$Iy);(DbZ?q>Y=pc21QTZcMbG6{R|0?4KmBGoU|o}(H;@|2R}C^k
ghLPwaQNxHF$I?t>JeJBL3UL&FIx;a%x-6Xh0OC_*bpQYW
literal 0
HcmV?d00001
diff --git a/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/annotation/AliasFor.java b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/annotation/AliasFor.java
new file mode 100644
index 000000000000..3a823fade5b0
--- /dev/null
+++ b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/annotation/AliasFor.java
@@ -0,0 +1,10 @@
+package org.springframework.core.annotation;
+
+public @interface AliasFor {
+ @AliasFor("attribute")
+ String value() default "";
+
+ @AliasFor("value")
+ String attribute() default "";
+
+}
diff --git a/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/GetMapping.class b/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/GetMapping.class
new file mode 100644
index 0000000000000000000000000000000000000000..5392ca0ebc1593d6bfa2f7d765ee4ca169fb260f
GIT binary patch
literal 504
zcma)&y-ve06orr5G%4k$Ewlp@8)_FUu~3N#3Bgi?)JiO!oYW02i5(KVeK!UkfQLfd
z3?&dTFj%_xlg>TI=i~G39l#Za0geNl1Q;-QTBMR;Fd9$SVk3AWbj;^AS316C=-+5<
ztgy=HTe%W0u?%2nZA9WoH5`o>f62T|*k=Ym6S+tWhIV9h;Zj+SS#FjtD#y;;xIB_~
zDxp)|dubm;mXYs88HC|<=CoC*d{Tu96Imr8>11m1m={?Yb44Ckk!ycGS!yuAF9#FEVXJbh%nj0^%G
u-TFC+dFlH8Nm;4MC5#O62q7eGj&Kvy7#SEDn1GlW=t>44%>pEu7+3+XjWw+R
literal 0
HcmV?d00001
diff --git a/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/ResponseBody.java b/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/ResponseBody.java
new file mode 100644
index 000000000000..b2134009968b
--- /dev/null
+++ b/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/ResponseBody.java
@@ -0,0 +1,4 @@
+package org.springframework.web.bind.annotation;
+
+public @interface ResponseBody {
+}
From 872a000a33e1b0d6fb2efeb926c7ed9e18725bd5 Mon Sep 17 00:00:00 2001
From: haby0
Date: Wed, 24 Feb 2021 20:36:12 +0800
Subject: [PATCH 005/433] *)update to JSONP injection
---
.../Security/CWE/CWE-352/JsonpInjection.java | 170 +++++++++++++++++
.../Security/CWE/CWE-352/JsonpInjection.qhelp | 35 ++++
.../Security/CWE/CWE-352/JsonpInjection.ql | 55 ++++++
.../CWE/CWE-352/JsonpInjectionLib.qll | 92 ++++++++++
.../security/CWE-352/JsonpInjection.expected | 60 ++++++
.../security/CWE-352/JsonpInjection.java | 171 ++++++++++++++++++
.../security/CWE-352/JsonpInjection.qlref | 1 +
7 files changed, 584 insertions(+)
create mode 100644 java/ql/src/Security/CWE/CWE-352/JsonpInjection.java
create mode 100644 java/ql/src/Security/CWE/CWE-352/JsonpInjection.qhelp
create mode 100644 java/ql/src/Security/CWE/CWE-352/JsonpInjection.ql
create mode 100644 java/ql/src/Security/CWE/CWE-352/JsonpInjectionLib.qll
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.expected
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.java
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.qlref
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonpInjection.java b/java/ql/src/Security/CWE/CWE-352/JsonpInjection.java
new file mode 100644
index 000000000000..8b4e7cc005ef
--- /dev/null
+++ b/java/ql/src/Security/CWE/CWE-352/JsonpInjection.java
@@ -0,0 +1,170 @@
+import com.alibaba.fastjson.JSONObject;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.gson.Gson;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Random;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+@Controller
+public class JsonpInjection {
+private static HashMap hashMap = new HashMap();
+
+ static {
+ hashMap.put("username","admin");
+ hashMap.put("password","123456");
+ }
+
+
+ @GetMapping(value = "jsonp1")
+ @ResponseBody
+ public String bad1(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+
+ Gson gson = new Gson();
+ String result = gson.toJson(hashMap);
+ resultStr = jsonpCallback + "(" + result + ")";
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp2")
+ @ResponseBody
+ public String bad2(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+
+ resultStr = jsonpCallback + "(" + JSONObject.toJSONString(hashMap) + ")";
+
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp3")
+ @ResponseBody
+ public String bad3(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp4")
+ @ResponseBody
+ public String bad4(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String restr = JSONObject.toJSONString(hashMap);
+ resultStr = jsonpCallback + "(" + restr + ");";
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp5")
+ @ResponseBody
+ public void bad5(HttpServletRequest request,
+ HttpServletResponse response) throws Exception {
+ response.setContentType("application/json");
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ PrintWriter pw = null;
+ Gson gson = new Gson();
+ String result = gson.toJson(hashMap);
+
+ String resultStr = null;
+ pw = response.getWriter();
+ resultStr = jsonpCallback + "(" + result + ")";
+ pw.println(resultStr);
+ }
+
+ @GetMapping(value = "jsonp6")
+ @ResponseBody
+ public void bad6(HttpServletRequest request,
+ HttpServletResponse response) throws Exception {
+ response.setContentType("application/json");
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ PrintWriter pw = null;
+ ObjectMapper mapper = new ObjectMapper();
+ String result = mapper.writeValueAsString(hashMap);
+ String resultStr = null;
+ pw = response.getWriter();
+ resultStr = jsonpCallback + "(" + result + ")";
+ pw.println(resultStr);
+ }
+
+ @GetMapping(value = "jsonp7")
+ @ResponseBody
+ public String good(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+
+ String val = "";
+ Random random = new Random();
+ for (int i = 0; i < 10; i++) {
+ val += String.valueOf(random.nextInt(10));
+ }
+ // good
+ jsonpCallback = jsonpCallback + "_" + val;
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp8")
+ @ResponseBody
+ public String good1(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+
+ String token = request.getParameter("token");
+
+ // good
+ if (verifToken(token)){
+ System.out.println(token);
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
+
+ return "error";
+ }
+
+ @GetMapping(value = "jsonp9")
+ @ResponseBody
+ public String good2(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+
+ String referer = request.getHeader("Referer");
+
+ boolean result = verifReferer(referer);
+ // good
+ if (result){
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
+
+ return "error";
+ }
+
+ public static String getJsonStr(Object result) {
+ return JSONObject.toJSONString(result);
+ }
+
+ public static boolean verifToken(String token){
+ if (token != "xxxx"){
+ return false;
+ }
+ return true;
+ }
+
+ public static boolean verifReferer(String referer){
+ if (!referer.startsWith("http://test.com/")){
+ return false;
+ }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonpInjection.qhelp b/java/ql/src/Security/CWE/CWE-352/JsonpInjection.qhelp
new file mode 100644
index 000000000000..b063b409d3a5
--- /dev/null
+++ b/java/ql/src/Security/CWE/CWE-352/JsonpInjection.qhelp
@@ -0,0 +1,35 @@
+
+
+
+The software uses external input as the function name to wrap JSON data and return it to the client as a request response. When there is a cross-domain problem,
+there is a problem of sensitive information leakage.
+
+
+
+
+Adding `Referer` or random `token` verification processing can effectively prevent the leakage of sensitive information.
+
+
+
+
+The following example shows the case of no verification processing and verification processing for the external input function name.
+
+
+
+
+
+
+
+OWASPLondon20161124_JSON_Hijacking_Gareth_Heyes:
+JSON hijacking.
+
+
+Practical JSONP Injection:
+
+ Completely controllable from the URL (GET variable)
+.
+
+
+
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonpInjection.ql b/java/ql/src/Security/CWE/CWE-352/JsonpInjection.ql
new file mode 100644
index 000000000000..e7ca2d41e343
--- /dev/null
+++ b/java/ql/src/Security/CWE/CWE-352/JsonpInjection.ql
@@ -0,0 +1,55 @@
+/**
+ * @name JSON Hijacking
+ * @description User-controlled callback function names that are not verified are vulnerable
+ * to json hijacking attacks.
+ * @kind path-problem
+ * @problem.severity error
+ * @precision high
+ * @id java/Json-hijacking
+ * @tags security
+ * external/cwe/cwe-352
+ */
+
+import java
+import JsonpInjectionLib
+import semmle.code.java.dataflow.FlowSources
+import semmle.code.java.deadcode.WebEntryPoints
+import DataFlow::PathGraph
+
+class VerifAuth extends DataFlow::BarrierGuard {
+ VerifAuth() {
+ exists(MethodAccess ma, Node prod, Node succ |
+ this = ma and
+ ma.getMethod().getName().regexpMatch("(?i).*(token|auth|referer).*") and
+ prod instanceof RemoteFlowSource and
+ succ.asExpr() = ma.getAnArgument() and
+ ma.getMethod().getAParameter().getName().regexpMatch("(?i).*(token|auth|referer).*") and
+ localFlowStep*(prod, succ)
+ )
+ }
+
+ override predicate checks(Expr e, boolean branch) {
+ exists(ReturnStmt rs |
+ e = rs.getResult() and
+ branch = true
+ )
+ }
+}
+
+/** Taint-tracking configuration tracing flow from remote sources to output jsonp data. */
+class JsonpInjectionConfig extends TaintTracking::Configuration {
+ JsonpInjectionConfig() { this = "JsonpInjectionConfig" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof JsonpInjectionSink }
+
+ override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { guard instanceof VerifAuth }
+}
+
+from DataFlow::PathNode source, DataFlow::PathNode sink, JsonpInjectionConfig conf
+where
+ conf.hasFlowPath(source, sink) and
+ exists(JsonpInjectionFlowConfig jhfc | jhfc.hasFlowTo(sink.getNode()))
+select sink.getNode(), source, sink, "Json Hijacking query might include code from $@.",
+ source.getNode(), "this user input"
\ No newline at end of file
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonpInjectionLib.qll b/java/ql/src/Security/CWE/CWE-352/JsonpInjectionLib.qll
new file mode 100644
index 000000000000..4294a5e8c8f2
--- /dev/null
+++ b/java/ql/src/Security/CWE/CWE-352/JsonpInjectionLib.qll
@@ -0,0 +1,92 @@
+import java
+import DataFlow
+import JsonStringLib
+import semmle.code.java.dataflow.DataFlow
+import semmle.code.java.dataflow.FlowSources
+import semmle.code.java.frameworks.spring.SpringController
+
+/** A data flow sink for unvalidated user input that is used to jsonp. */
+abstract class JsonpInjectionSink extends DataFlow::Node { }
+
+/** Use ```print```, ```println```, ```write``` to output result. */
+private class WriterPrintln extends JsonpInjectionSink {
+ WriterPrintln() {
+ exists(MethodAccess ma |
+ ma.getMethod().getName().regexpMatch("print|println|write") and
+ ma.getMethod()
+ .getDeclaringType()
+ .getASourceSupertype*()
+ .hasQualifiedName("java.io", "PrintWriter") and
+ ma.getArgument(0) = this.asExpr()
+ )
+ }
+}
+
+/** Spring Request Method return result. */
+private class SpringReturn extends JsonpInjectionSink {
+ SpringReturn() {
+ exists(ReturnStmt rs, Method m | m = rs.getEnclosingCallable() |
+ m instanceof SpringRequestMappingMethod and
+ rs.getResult() = this.asExpr()
+ )
+ }
+}
+
+/** A concatenate expression using `(` and `)` or `);`. */
+class JsonpInjectionExpr extends AddExpr {
+ JsonpInjectionExpr() {
+ getRightOperand().toString().regexpMatch("\"\\)\"|\"\\);\"") and
+ getLeftOperand()
+ .(AddExpr)
+ .getLeftOperand()
+ .(AddExpr)
+ .getRightOperand()
+ .toString()
+ .regexpMatch("\"\\(\"")
+ }
+
+ /** Get the jsonp function name of this expression */
+ Expr getFunctionName() {
+ result = getLeftOperand().(AddExpr).getLeftOperand().(AddExpr).getLeftOperand()
+ }
+
+ /** Get the json data of this expression */
+ Expr getJsonExpr() { result = getLeftOperand().(AddExpr).getRightOperand() }
+}
+
+/** A data flow configuration tracing flow from remote sources to jsonp function name. */
+class RemoteFlowConfig extends DataFlow2::Configuration {
+ RemoteFlowConfig() { this = "RemoteFlowConfig" }
+
+ override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(JsonpInjectionExpr jhe | jhe.getFunctionName() = sink.asExpr())
+ }
+}
+
+/** A data flow configuration tracing flow from json data to splicing jsonp data. */
+class JsonDataFlowConfig extends DataFlow2::Configuration {
+ JsonDataFlowConfig() { this = "JsonDataFlowConfig" }
+
+ override predicate isSource(DataFlow::Node src) { src instanceof JsonpStringSource }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(JsonpInjectionExpr jhe | jhe.getJsonExpr() = sink.asExpr())
+ }
+}
+
+/** Taint-tracking configuration tracing flow from user-controllable function name jsonp data to output jsonp data. */
+class JsonpInjectionFlowConfig extends DataFlow::Configuration {
+ JsonpInjectionFlowConfig() { this = "JsonpInjectionFlowConfig" }
+
+ override predicate isSource(DataFlow::Node src) {
+ exists(JsonpInjectionExpr jhe, JsonDataFlowConfig jdfc, RemoteFlowConfig rfc |
+ jhe = src.asExpr() and
+ jdfc.hasFlowTo(DataFlow::exprNode(jhe.getJsonExpr())) and
+ rfc.hasFlowTo(DataFlow::exprNode(jhe.getFunctionName()))
+ )
+ }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof JsonpInjectionSink }
+}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.expected
new file mode 100644
index 000000000000..019af8f5c055
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.expected
@@ -0,0 +1,60 @@
+edges
+| JsonpInjection.java:28:32:28:68 | getParameter(...) : String | JsonpInjection.java:33:16:33:24 | resultStr |
+| JsonpInjection.java:32:21:32:54 | ... + ... : String | JsonpInjection.java:33:16:33:24 | resultStr |
+| JsonpInjection.java:40:32:40:68 | getParameter(...) : String | JsonpInjection.java:44:16:44:24 | resultStr |
+| JsonpInjection.java:42:21:42:80 | ... + ... : String | JsonpInjection.java:44:16:44:24 | resultStr |
+| JsonpInjection.java:51:32:51:68 | getParameter(...) : String | JsonpInjection.java:54:16:54:24 | resultStr |
+| JsonpInjection.java:53:21:53:55 | ... + ... : String | JsonpInjection.java:54:16:54:24 | resultStr |
+| JsonpInjection.java:61:32:61:68 | getParameter(...) : String | JsonpInjection.java:64:16:64:24 | resultStr |
+| JsonpInjection.java:63:21:63:54 | ... + ... : String | JsonpInjection.java:64:16:64:24 | resultStr |
+| JsonpInjection.java:72:32:72:68 | getParameter(...) : String | JsonpInjection.java:80:20:80:28 | resultStr |
+| JsonpInjection.java:79:21:79:54 | ... + ... : String | JsonpInjection.java:80:20:80:28 | resultStr |
+| JsonpInjection.java:88:32:88:68 | getParameter(...) : String | JsonpInjection.java:95:20:95:28 | resultStr |
+| JsonpInjection.java:94:21:94:54 | ... + ... : String | JsonpInjection.java:95:20:95:28 | resultStr |
+| JsonpInjection.java:102:32:102:68 | getParameter(...) : String | JsonpInjection.java:113:16:113:24 | resultStr |
+| JsonpInjection.java:128:25:128:59 | ... + ... : String | JsonpInjection.java:129:20:129:28 | resultStr |
+| JsonpInjection.java:147:25:147:59 | ... + ... : String | JsonpInjection.java:148:20:148:28 | resultStr |
+nodes
+| JsonpInjection.java:28:32:28:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpInjection.java:32:21:32:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjection.java:33:16:33:24 | resultStr | semmle.label | resultStr |
+| JsonpInjection.java:33:16:33:24 | resultStr | semmle.label | resultStr |
+| JsonpInjection.java:40:32:40:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpInjection.java:42:21:42:80 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjection.java:44:16:44:24 | resultStr | semmle.label | resultStr |
+| JsonpInjection.java:44:16:44:24 | resultStr | semmle.label | resultStr |
+| JsonpInjection.java:51:32:51:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpInjection.java:53:21:53:55 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjection.java:54:16:54:24 | resultStr | semmle.label | resultStr |
+| JsonpInjection.java:54:16:54:24 | resultStr | semmle.label | resultStr |
+| JsonpInjection.java:61:32:61:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpInjection.java:63:21:63:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjection.java:64:16:64:24 | resultStr | semmle.label | resultStr |
+| JsonpInjection.java:64:16:64:24 | resultStr | semmle.label | resultStr |
+| JsonpInjection.java:72:32:72:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpInjection.java:79:21:79:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjection.java:80:20:80:28 | resultStr | semmle.label | resultStr |
+| JsonpInjection.java:80:20:80:28 | resultStr | semmle.label | resultStr |
+| JsonpInjection.java:88:32:88:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpInjection.java:94:21:94:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjection.java:95:20:95:28 | resultStr | semmle.label | resultStr |
+| JsonpInjection.java:95:20:95:28 | resultStr | semmle.label | resultStr |
+| JsonpInjection.java:102:32:102:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpInjection.java:113:16:113:24 | resultStr | semmle.label | resultStr |
+| JsonpInjection.java:128:25:128:59 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjection.java:129:20:129:28 | resultStr | semmle.label | resultStr |
+| JsonpInjection.java:147:25:147:59 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjection.java:148:20:148:28 | resultStr | semmle.label | resultStr |
+#select
+| JsonpInjection.java:33:16:33:24 | resultStr | JsonpInjection.java:28:32:28:68 | getParameter(...) : String | JsonpInjection.java:33:16:33:24 | resultStr | Json Hijacking query
+might include code from $@. | JsonpInjection.java:28:32:28:68 | getParameter(...) | this user input |
+| JsonpInjection.java:44:16:44:24 | resultStr | JsonpInjection.java:40:32:40:68 | getParameter(...) : String | JsonpInjection.java:44:16:44:24 | resultStr | Json Hijacking query
+might include code from $@. | JsonpInjection.java:40:32:40:68 | getParameter(...) | this user input |
+| JsonpInjection.java:54:16:54:24 | resultStr | JsonpInjection.java:51:32:51:68 | getParameter(...) : String | JsonpInjection.java:54:16:54:24 | resultStr | Json Hijacking query
+might include code from $@. | JsonpInjection.java:51:32:51:68 | getParameter(...) | this user input |
+| JsonpInjection.java:64:16:64:24 | resultStr | JsonpInjection.java:61:32:61:68 | getParameter(...) : String | JsonpInjection.java:64:16:64:24 | resultStr | Json Hijacking query
+might include code from $@. | JsonpInjection.java:61:32:61:68 | getParameter(...) | this user input |
+| JsonpInjection.java:80:20:80:28 | resultStr | JsonpInjection.java:72:32:72:68 | getParameter(...) : String | JsonpInjection.java:80:20:80:28 | resultStr | Json Hijacking query
+might include code from $@. | JsonpInjection.java:72:32:72:68 | getParameter(...) | this user input |
+| JsonpInjection.java:95:20:95:28 | resultStr | JsonpInjection.java:88:32:88:68 | getParameter(...) : String | JsonpInjection.java:95:20:95:28 | resultStr | Json Hijacking query
+might include code from $@. | JsonpInjection.java:88:32:88:68 | getParameter(...) | this user input |
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.java
new file mode 100644
index 000000000000..df3aa2c02fe9
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.java
@@ -0,0 +1,171 @@
+import com.alibaba.fastjson.JSONObject;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.gson.Gson;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Random;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+@Controller
+public class JsonpInjection {
+
+ private static HashMap hashMap = new HashMap();
+
+ static {
+ hashMap.put("username","admin");
+ hashMap.put("password","123456");
+ }
+
+
+ @GetMapping(value = "jsonp1")
+ @ResponseBody
+ public String bad1(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+
+ Gson gson = new Gson();
+ String result = gson.toJson(hashMap);
+ resultStr = jsonpCallback + "(" + result + ")";
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp2")
+ @ResponseBody
+ public String bad2(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+
+ resultStr = jsonpCallback + "(" + JSONObject.toJSONString(hashMap) + ")";
+
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp3")
+ @ResponseBody
+ public String bad3(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp4")
+ @ResponseBody
+ public String bad4(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String restr = JSONObject.toJSONString(hashMap);
+ resultStr = jsonpCallback + "(" + restr + ");";
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp5")
+ @ResponseBody
+ public void bad5(HttpServletRequest request,
+ HttpServletResponse response) throws Exception {
+ response.setContentType("application/json");
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ PrintWriter pw = null;
+ Gson gson = new Gson();
+ String result = gson.toJson(hashMap);
+
+ String resultStr = null;
+ pw = response.getWriter();
+ resultStr = jsonpCallback + "(" + result + ")";
+ pw.println(resultStr);
+ }
+
+ @GetMapping(value = "jsonp6")
+ @ResponseBody
+ public void bad6(HttpServletRequest request,
+ HttpServletResponse response) throws Exception {
+ response.setContentType("application/json");
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ PrintWriter pw = null;
+ ObjectMapper mapper = new ObjectMapper();
+ String result = mapper.writeValueAsString(hashMap);
+ String resultStr = null;
+ pw = response.getWriter();
+ resultStr = jsonpCallback + "(" + result + ")";
+ pw.println(resultStr);
+ }
+
+ @GetMapping(value = "jsonp7")
+ @ResponseBody
+ public String good(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+
+ String val = "";
+ Random random = new Random();
+ for (int i = 0; i < 10; i++) {
+ val += String.valueOf(random.nextInt(10));
+ }
+ // good
+ jsonpCallback = jsonpCallback + "_" + val;
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp8")
+ @ResponseBody
+ public String good1(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+
+ String token = request.getParameter("token");
+
+ // good
+ if (verifToken(token)){
+ System.out.println(token);
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
+
+ return "error";
+ }
+
+ @GetMapping(value = "jsonp9")
+ @ResponseBody
+ public String good2(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+
+ String referer = request.getHeader("Referer");
+
+ boolean result = verifReferer(referer);
+ // good
+ if (result){
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
+
+ return "error";
+ }
+
+ public static String getJsonStr(Object result) {
+ return JSONObject.toJSONString(result);
+ }
+
+ public static boolean verifToken(String token){
+ if (token != "xxxx"){
+ return false;
+ }
+ return true;
+ }
+
+ public static boolean verifReferer(String referer){
+ if (!referer.startsWith("http://test.com/")){
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.qlref b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.qlref
new file mode 100644
index 000000000000..6ad4b8acda76
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.qlref
@@ -0,0 +1 @@
+Security/CWE/CWE-352/JsonpInjection.ql
From 6fe8bafc7d35f77d5db38d0802b192b6abb45dd1 Mon Sep 17 00:00:00 2001
From: haby0
Date: Wed, 24 Feb 2021 20:59:51 +0800
Subject: [PATCH 006/433] *)update
---
.../Security/CWE/CWE-352/JsonHijacking.java | 119 ------------------
.../Security/CWE/CWE-352/JsonHijacking.qhelp | 35 ------
.../src/Security/CWE/CWE-352/JsonHijacking.ql | 32 -----
.../Security/CWE/CWE-352/JsonHijackingLib.qll | 92 --------------
.../security/CWE-352/JsonHijacking.expected | 48 -------
.../security/CWE-352/JsonHijacking.java | 119 ------------------
.../security/CWE-352/JsonHijacking.qlref | 1 -
7 files changed, 446 deletions(-)
delete mode 100644 java/ql/src/Security/CWE/CWE-352/JsonHijacking.java
delete mode 100644 java/ql/src/Security/CWE/CWE-352/JsonHijacking.qhelp
delete mode 100644 java/ql/src/Security/CWE/CWE-352/JsonHijacking.ql
delete mode 100644 java/ql/src/Security/CWE/CWE-352/JsonHijackingLib.qll
delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonHijacking.expected
delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonHijacking.java
delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonHijacking.qlref
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonHijacking.java b/java/ql/src/Security/CWE/CWE-352/JsonHijacking.java
deleted file mode 100644
index d08d436fa07b..000000000000
--- a/java/ql/src/Security/CWE/CWE-352/JsonHijacking.java
+++ /dev/null
@@ -1,119 +0,0 @@
-import com.alibaba.fastjson.JSONObject;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.google.gson.Gson;
-import java.io.PrintWriter;
-import java.util.HashMap;
-import java.util.Random;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.ResponseBody;
-
-@Controller
-public class JsonHijacking {
-
- private static HashMap hashMap = new HashMap();
-
- static {
- hashMap.put("username","admin");
- hashMap.put("password","123456");
- }
-
-
- @GetMapping(value = "jsonp1")
- @ResponseBody
- public String bad1(HttpServletRequest request) {
- String resultStr = null;
- String jsonpCallback = request.getParameter("jsonpCallback");
-
- Gson gson = new Gson();
- String result = gson.toJson(hashMap);
- resultStr = jsonpCallback + "(" + result + ")";
- return resultStr;
- }
-
- @GetMapping(value = "jsonp2")
- @ResponseBody
- public String bad2(HttpServletRequest request) {
- String resultStr = null;
- String jsonpCallback = request.getParameter("jsonpCallback");
-
- resultStr = jsonpCallback + "(" + JSONObject.toJSONString(hashMap) + ")";
-
- return resultStr;
- }
-
- @GetMapping(value = "jsonp3")
- @ResponseBody
- public String bad3(HttpServletRequest request) {
- String resultStr = null;
- String jsonpCallback = request.getParameter("jsonpCallback");
- String jsonStr = getJsonStr(hashMap);
- resultStr = jsonpCallback + "(" + jsonStr + ")";
- return resultStr;
- }
-
- @GetMapping(value = "jsonp4")
- @ResponseBody
- public String bad4(HttpServletRequest request) {
- String resultStr = null;
- String jsonpCallback = request.getParameter("jsonpCallback");
- String restr = JSONObject.toJSONString(hashMap);
- resultStr = jsonpCallback + "(" + restr + ");";
- return resultStr;
- }
-
- @GetMapping(value = "jsonp5")
- @ResponseBody
- public void bad5(HttpServletRequest request,
- HttpServletResponse response) throws Exception {
- response.setContentType("application/json");
- String jsonpCallback = request.getParameter("jsonpCallback");
- PrintWriter pw = null;
- Gson gson = new Gson();
- String result = gson.toJson(hashMap);
-
- String resultStr = null;
- pw = response.getWriter();
- resultStr = jsonpCallback + "(" + result + ")";
- pw.println(resultStr);
- }
-
- @GetMapping(value = "jsonp6")
- @ResponseBody
- public void bad6(HttpServletRequest request,
- HttpServletResponse response) throws Exception {
- response.setContentType("application/json");
- String jsonpCallback = request.getParameter("jsonpCallback");
- PrintWriter pw = null;
- ObjectMapper mapper = new ObjectMapper();
- String result = mapper.writeValueAsString(hashMap);
- String resultStr = null;
- pw = response.getWriter();
- resultStr = jsonpCallback + "(" + result + ")";
- pw.println(resultStr);
- }
-
- @GetMapping(value = "jsonp7")
- @ResponseBody
- public String good(HttpServletRequest request) {
- String resultStr = null;
- String jsonpCallback = request.getParameter("jsonpCallback");
-
- String val = "";
- Random random = new Random();
- for (int i = 0; i < 10; i++) {
- val += String.valueOf(random.nextInt(10));
- }
- // good
- jsonpCallback = jsonpCallback + "_" + val;
- String jsonStr = getJsonStr(hashMap);
- resultStr = jsonpCallback + "(" + jsonStr + ")";
- return resultStr;
- }
-
- public static String getJsonStr(Object result) {
- return JSONObject.toJSONString(result);
- }
-}
\ No newline at end of file
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonHijacking.qhelp b/java/ql/src/Security/CWE/CWE-352/JsonHijacking.qhelp
deleted file mode 100644
index 38e1845f9929..000000000000
--- a/java/ql/src/Security/CWE/CWE-352/JsonHijacking.qhelp
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-The software uses external input as the function name to wrap JSON data and return it to the client as a request response. When there is a cross-domain problem,
-there is a problem of sensitive information leakage.
-
-
-
-
-The function name verification processing for external input can effectively prevent the leakage of sensitive information.
-
-
-
-
-The following example shows the case of no verification processing and verification processing for the external input function name.
-
-
-
-
-
-
-
-OWASPLondon20161124_JSON_Hijacking_Gareth_Heyes:
-JSON hijacking.
-
-
-Practical JSONP Injection:
-
- Completely controllable from the URL (GET variable)
-.
-
-
-
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonHijacking.ql b/java/ql/src/Security/CWE/CWE-352/JsonHijacking.ql
deleted file mode 100644
index a6a6d2475f09..000000000000
--- a/java/ql/src/Security/CWE/CWE-352/JsonHijacking.ql
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * @name JSON Hijacking
- * @description User-controlled callback function names that are not verified are vulnerable
- * to json hijacking attacks.
- * @kind path-problem
- * @problem.severity error
- * @precision high
- * @id java/Json-hijacking
- * @tags security
- * external/cwe/cwe-352
- */
-
-import java
-import JsonHijackingLib
-import semmle.code.java.dataflow.FlowSources
-import DataFlow::PathGraph
-
-/** Taint-tracking configuration tracing flow from remote sources to output jsonp data. */
-class JsonHijackingConfig extends TaintTracking::Configuration {
- JsonHijackingConfig() { this = "JsonHijackingConfig" }
-
- override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
-
- override predicate isSink(DataFlow::Node sink) { sink instanceof JsonHijackingSink }
-}
-
-from DataFlow::PathNode source, DataFlow::PathNode sink, JsonHijackingConfig conf
-where
- conf.hasFlowPath(source, sink) and
- exists(JsonHijackingFlowConfig jhfc | jhfc.hasFlowTo(sink.getNode()))
-select sink.getNode(), source, sink, "Json Hijacking query might include code from $@.",
- source.getNode(), "this user input"
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonHijackingLib.qll b/java/ql/src/Security/CWE/CWE-352/JsonHijackingLib.qll
deleted file mode 100644
index ba91a6670bfd..000000000000
--- a/java/ql/src/Security/CWE/CWE-352/JsonHijackingLib.qll
+++ /dev/null
@@ -1,92 +0,0 @@
-import java
-import DataFlow
-import JsonStringLib
-import semmle.code.java.dataflow.DataFlow
-import semmle.code.java.dataflow.FlowSources
-import semmle.code.java.frameworks.spring.SpringController
-
-/** A data flow sink for unvalidated user input that is used to jsonp. */
-abstract class JsonHijackingSink extends DataFlow::Node { }
-
-/** Use ```print```, ```println```, ```write``` to output result. */
-private class WriterPrintln extends JsonHijackingSink {
- WriterPrintln() {
- exists(MethodAccess ma |
- ma.getMethod().getName().regexpMatch("print|println|write") and
- ma.getMethod()
- .getDeclaringType()
- .getASourceSupertype*()
- .hasQualifiedName("java.io", "PrintWriter") and
- ma.getArgument(0) = this.asExpr()
- )
- }
-}
-
-/** Spring Request Method return result. */
-private class SpringReturn extends JsonHijackingSink {
- SpringReturn() {
- exists(ReturnStmt rs, Method m | m = rs.getEnclosingCallable() |
- m instanceof SpringRequestMappingMethod and
- rs.getResult() = this.asExpr()
- )
- }
-}
-
-/** A concatenate expression using `(` and `)` or `);`. */
-class JsonHijackingExpr extends AddExpr {
- JsonHijackingExpr() {
- getRightOperand().toString().regexpMatch("\"\\)\"|\"\\);\"") and
- getLeftOperand()
- .(AddExpr)
- .getLeftOperand()
- .(AddExpr)
- .getRightOperand()
- .toString()
- .regexpMatch("\"\\(\"")
- }
-
- /** Get the jsonp function name of this expression */
- Expr getFunctionName() {
- result = getLeftOperand().(AddExpr).getLeftOperand().(AddExpr).getLeftOperand()
- }
-
- /** Get the json data of this expression */
- Expr getJsonExpr() { result = getLeftOperand().(AddExpr).getRightOperand() }
-}
-
-/** A data flow configuration tracing flow from remote sources to jsonp function name. */
-class RemoteFlowConfig extends DataFlow2::Configuration {
- RemoteFlowConfig() { this = "RemoteFlowConfig" }
-
- override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
-
- override predicate isSink(DataFlow::Node sink) {
- exists(JsonHijackingExpr jhe | jhe.getFunctionName() = sink.asExpr())
- }
-}
-
-/** A data flow configuration tracing flow from json data to splicing jsonp data. */
-class JsonDataFlowConfig extends DataFlow2::Configuration {
- JsonDataFlowConfig() { this = "JsonDataFlowConfig" }
-
- override predicate isSource(DataFlow::Node src) { src instanceof JsonpStringSource }
-
- override predicate isSink(DataFlow::Node sink) {
- exists(JsonHijackingExpr jhe | jhe.getJsonExpr() = sink.asExpr())
- }
-}
-
-/** Taint-tracking configuration tracing flow from user-controllable function name jsonp data to output jsonp data. */
-class JsonHijackingFlowConfig extends TaintTracking::Configuration {
- JsonHijackingFlowConfig() { this = "JsonHijackingFlowConfig" }
-
- override predicate isSource(DataFlow::Node src) {
- exists(JsonHijackingExpr jhe, JsonDataFlowConfig jdfc, RemoteFlowConfig rfc |
- jhe = src.asExpr() and
- jdfc.hasFlowTo(DataFlow::exprNode(jhe.getJsonExpr())) and
- rfc.hasFlowTo(DataFlow::exprNode(jhe.getFunctionName()))
- )
- }
-
- override predicate isSink(DataFlow::Node sink) { sink instanceof JsonHijackingSink }
-}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonHijacking.expected b/java/ql/test/experimental/query-tests/security/CWE-352/JsonHijacking.expected
deleted file mode 100644
index 8efc3be1673b..000000000000
--- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonHijacking.expected
+++ /dev/null
@@ -1,48 +0,0 @@
-edges
-| JsonHijacking.java:28:32:28:68 | getParameter(...) : String | JsonHijacking.java:33:16:33:24 | resultStr |
-| JsonHijacking.java:32:21:32:54 | ... + ... : String | JsonHijacking.java:33:16:33:24 | resultStr |
-| JsonHijacking.java:40:32:40:68 | getParameter(...) : String | JsonHijacking.java:44:16:44:24 | resultStr |
-| JsonHijacking.java:42:21:42:80 | ... + ... : String | JsonHijacking.java:44:16:44:24 | resultStr |
-| JsonHijacking.java:51:32:51:68 | getParameter(...) : String | JsonHijacking.java:54:16:54:24 | resultStr |
-| JsonHijacking.java:53:21:53:55 | ... + ... : String | JsonHijacking.java:54:16:54:24 | resultStr |
-| JsonHijacking.java:61:32:61:68 | getParameter(...) : String | JsonHijacking.java:64:16:64:24 | resultStr |
-| JsonHijacking.java:63:21:63:54 | ... + ... : String | JsonHijacking.java:64:16:64:24 | resultStr |
-| JsonHijacking.java:72:32:72:68 | getParameter(...) : String | JsonHijacking.java:80:20:80:28 | resultStr |
-| JsonHijacking.java:79:21:79:54 | ... + ... : String | JsonHijacking.java:80:20:80:28 | resultStr |
-| JsonHijacking.java:88:32:88:68 | getParameter(...) : String | JsonHijacking.java:95:20:95:28 | resultStr |
-| JsonHijacking.java:94:21:94:54 | ... + ... : String | JsonHijacking.java:95:20:95:28 | resultStr |
-| JsonHijacking.java:102:32:102:68 | getParameter(...) : String | JsonHijacking.java:113:16:113:24 | resultStr |
-nodes
-| JsonHijacking.java:28:32:28:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonHijacking.java:32:21:32:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonHijacking.java:33:16:33:24 | resultStr | semmle.label | resultStr |
-| JsonHijacking.java:33:16:33:24 | resultStr | semmle.label | resultStr |
-| JsonHijacking.java:40:32:40:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonHijacking.java:42:21:42:80 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonHijacking.java:44:16:44:24 | resultStr | semmle.label | resultStr |
-| JsonHijacking.java:44:16:44:24 | resultStr | semmle.label | resultStr |
-| JsonHijacking.java:51:32:51:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonHijacking.java:53:21:53:55 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonHijacking.java:54:16:54:24 | resultStr | semmle.label | resultStr |
-| JsonHijacking.java:54:16:54:24 | resultStr | semmle.label | resultStr |
-| JsonHijacking.java:61:32:61:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonHijacking.java:63:21:63:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonHijacking.java:64:16:64:24 | resultStr | semmle.label | resultStr |
-| JsonHijacking.java:64:16:64:24 | resultStr | semmle.label | resultStr |
-| JsonHijacking.java:72:32:72:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonHijacking.java:79:21:79:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonHijacking.java:80:20:80:28 | resultStr | semmle.label | resultStr |
-| JsonHijacking.java:80:20:80:28 | resultStr | semmle.label | resultStr |
-| JsonHijacking.java:88:32:88:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonHijacking.java:94:21:94:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonHijacking.java:95:20:95:28 | resultStr | semmle.label | resultStr |
-| JsonHijacking.java:95:20:95:28 | resultStr | semmle.label | resultStr |
-| JsonHijacking.java:102:32:102:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonHijacking.java:113:16:113:24 | resultStr | semmle.label | resultStr |
-#select
-| JsonHijacking.java:33:16:33:24 | resultStr | JsonHijacking.java:28:32:28:68 | getParameter(...) : String | JsonHijacking.java:33:16:33:24 | resultStr | Json Hijacking query might include code from $@. | JsonHijacking.java:28:32:28:68 | getParameter(...) | this user input |
-| JsonHijacking.java:44:16:44:24 | resultStr | JsonHijacking.java:40:32:40:68 | getParameter(...) : String | JsonHijacking.java:44:16:44:24 | resultStr | Json Hijacking query might include code from $@. | JsonHijacking.java:40:32:40:68 | getParameter(...) | this user input |
-| JsonHijacking.java:54:16:54:24 | resultStr | JsonHijacking.java:51:32:51:68 | getParameter(...) : String | JsonHijacking.java:54:16:54:24 | resultStr | Json Hijacking query might include code from $@. | JsonHijacking.java:51:32:51:68 | getParameter(...) | this user input |
-| JsonHijacking.java:64:16:64:24 | resultStr | JsonHijacking.java:61:32:61:68 | getParameter(...) : String | JsonHijacking.java:64:16:64:24 | resultStr | Json Hijacking query might include code from $@. | JsonHijacking.java:61:32:61:68 | getParameter(...) | this user input |
-| JsonHijacking.java:80:20:80:28 | resultStr | JsonHijacking.java:72:32:72:68 | getParameter(...) : String | JsonHijacking.java:80:20:80:28 | resultStr | Json Hijacking query might include code from $@. | JsonHijacking.java:72:32:72:68 | getParameter(...) | this user input |
-| JsonHijacking.java:95:20:95:28 | resultStr | JsonHijacking.java:88:32:88:68 | getParameter(...) : String | JsonHijacking.java:95:20:95:28 | resultStr | Json Hijacking query might include code from $@. | JsonHijacking.java:88:32:88:68 | getParameter(...) | this user input |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonHijacking.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonHijacking.java
deleted file mode 100644
index 9b473e0610ca..000000000000
--- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonHijacking.java
+++ /dev/null
@@ -1,119 +0,0 @@
-import com.alibaba.fastjson.JSONObject;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.google.gson.Gson;
-import java.io.PrintWriter;
-import java.util.HashMap;
-import java.util.Random;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.ResponseBody;
-
-@Controller
-public class JsonHijacking {
-
- private static HashMap hashMap = new HashMap();
-
- static {
- hashMap.put("username","admin");
- hashMap.put("password","123456");
- }
-
-
- @GetMapping(value = "jsonp1")
- @ResponseBody
- public String bad1(HttpServletRequest request) {
- String resultStr = null;
- String jsonpCallback = request.getParameter("jsonpCallback");
-
- Gson gson = new Gson();
- String result = gson.toJson(hashMap);
- resultStr = jsonpCallback + "(" + result + ")";
- return resultStr;
- }
-
- @GetMapping(value = "jsonp2")
- @ResponseBody
- public String bad2(HttpServletRequest request) {
- String resultStr = null;
- String jsonpCallback = request.getParameter("jsonpCallback");
-
- resultStr = jsonpCallback + "(" + JSONObject.toJSONString(hashMap) + ")";
-
- return resultStr;
- }
-
- @GetMapping(value = "jsonp3")
- @ResponseBody
- public String bad3(HttpServletRequest request) {
- String resultStr = null;
- String jsonpCallback = request.getParameter("jsonpCallback");
- String jsonStr = getJsonStr(hashMap);
- resultStr = jsonpCallback + "(" + jsonStr + ")";
- return resultStr;
- }
-
- @GetMapping(value = "jsonp4")
- @ResponseBody
- public String bad4(HttpServletRequest request) {
- String resultStr = null;
- String jsonpCallback = request.getParameter("jsonpCallback");
- String restr = JSONObject.toJSONString(hashMap);
- resultStr = jsonpCallback + "(" + restr + ");";
- return resultStr;
- }
-
- @GetMapping(value = "jsonp5")
- @ResponseBody
- public void bad5(HttpServletRequest request,
- HttpServletResponse response) throws Exception {
- response.setContentType("application/json");
- String jsonpCallback = request.getParameter("jsonpCallback");
- PrintWriter pw = null;
- Gson gson = new Gson();
- String result = gson.toJson(hashMap);
-
- String resultStr = null;
- pw = response.getWriter();
- resultStr = jsonpCallback + "(" + result + ")";
- pw.println(resultStr);
- }
-
- @GetMapping(value = "jsonp6")
- @ResponseBody
- public void bad6(HttpServletRequest request,
- HttpServletResponse response) throws Exception {
- response.setContentType("application/json");
- String jsonpCallback = request.getParameter("jsonpCallback");
- PrintWriter pw = null;
- ObjectMapper mapper = new ObjectMapper();
- String result = mapper.writeValueAsString(hashMap);
- String resultStr = null;
- pw = response.getWriter();
- resultStr = jsonpCallback + "(" + result + ")";
- pw.println(resultStr);
- }
-
- @GetMapping(value = "jsonp7")
- @ResponseBody
- public String good(HttpServletRequest request) {
- String resultStr = null;
- String jsonpCallback = request.getParameter("jsonpCallback");
-
- String val = "";
- Random random = new Random();
- for (int i = 0; i < 10; i++) {
- val += String.valueOf(random.nextInt(10));
- }
- // good
- jsonpCallback = jsonpCallback + "_" + val;
- String jsonStr = getJsonStr(hashMap);
- resultStr = jsonpCallback + "(" + jsonStr + ")";
- return resultStr;
- }
-
- public static String getJsonStr(Object result) {
- return JSONObject.toJSONString(result);
- }
-}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonHijacking.qlref b/java/ql/test/experimental/query-tests/security/CWE-352/JsonHijacking.qlref
deleted file mode 100644
index e79471b3c1ed..000000000000
--- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonHijacking.qlref
+++ /dev/null
@@ -1 +0,0 @@
-Security/CWE/CWE-352/JsonHijacking.ql
From f795d5e0d3b4f1a7fc5446755020786c3faecae1 Mon Sep 17 00:00:00 2001
From: haby0
Date: Sat, 27 Feb 2021 16:25:17 +0800
Subject: [PATCH 007/433] update JSONP Injection ql
---
.../Security/CWE/CWE-352/JsonpInjection.ql | 45 +++++---
.../CWE/CWE-352/JsonpInjectionFilterLib.qll | 77 +++++++++++++
.../CWE/CWE-352/JsonpInjectionLib.qll | 65 ++++++++++-
.../CWE/CWE-352/JsonpInjectionServlet.java | 60 ++++++++++
.../CWE/CWE-352/JsonpInjectionServlet1.java | 64 +++++++++++
.../CWE/CWE-352/JsonpInjectionServlet2.java | 50 +++++++++
.../semmle/code/java/frameworks/Servlets.qll | 27 +++++
.../security/CWE-352/JsonpInjection.expected | 106 +++++++++---------
.../security/CWE-352/JsonpInjection.java | 13 ++-
.../core/annotation/AliasFor.class | Bin 385 -> 0 bytes
.../web/bind/annotation/GetMapping.class | Bin 504 -> 0 bytes
.../web/bind/annotation/RequestMapping.java | 2 +
.../web/bind/annotation/RequestMethod.java | 15 +++
.../web/bind/annotation/ResponseBody.class | Bin 184 -> 0 bytes
14 files changed, 448 insertions(+), 76 deletions(-)
create mode 100644 java/ql/src/Security/CWE/CWE-352/JsonpInjectionFilterLib.qll
create mode 100644 java/ql/src/Security/CWE/CWE-352/JsonpInjectionServlet.java
create mode 100644 java/ql/src/Security/CWE/CWE-352/JsonpInjectionServlet1.java
create mode 100644 java/ql/src/Security/CWE/CWE-352/JsonpInjectionServlet2.java
delete mode 100644 java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/annotation/AliasFor.class
delete mode 100644 java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/GetMapping.class
create mode 100644 java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/RequestMethod.java
delete mode 100644 java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/ResponseBody.class
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonpInjection.ql b/java/ql/src/Security/CWE/CWE-352/JsonpInjection.ql
index e7ca2d41e343..53ee6182511b 100644
--- a/java/ql/src/Security/CWE/CWE-352/JsonpInjection.ql
+++ b/java/ql/src/Security/CWE/CWE-352/JsonpInjection.ql
@@ -1,55 +1,68 @@
/**
- * @name JSON Hijacking
+ * @name JSONP Injection
* @description User-controlled callback function names that are not verified are vulnerable
- * to json hijacking attacks.
+ * to jsonp injection attacks.
* @kind path-problem
* @problem.severity error
* @precision high
- * @id java/Json-hijacking
+ * @id java/JSONP-Injection
* @tags security
* external/cwe/cwe-352
*/
import java
import JsonpInjectionLib
+import JsonpInjectionFilterLib
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.deadcode.WebEntryPoints
import DataFlow::PathGraph
-class VerifAuth extends DataFlow::BarrierGuard {
- VerifAuth() {
+
+/** If there is a method to verify `token`, `auth`, `referer`, and `origin`, it will not pass. */
+class ServletVerifAuth extends DataFlow::BarrierGuard {
+ ServletVerifAuth() {
exists(MethodAccess ma, Node prod, Node succ |
- this = ma and
- ma.getMethod().getName().regexpMatch("(?i).*(token|auth|referer).*") and
+ ma.getMethod().getName().regexpMatch("(?i).*(token|auth|referer|origin).*") and
prod instanceof RemoteFlowSource and
succ.asExpr() = ma.getAnArgument() and
- ma.getMethod().getAParameter().getName().regexpMatch("(?i).*(token|auth|referer).*") and
- localFlowStep*(prod, succ)
+ ma.getMethod().getAParameter().getName().regexpMatch("(?i).*(token|auth|referer|origin).*") and
+ localFlowStep*(prod, succ) and
+ this = ma
)
}
override predicate checks(Expr e, boolean branch) {
- exists(ReturnStmt rs |
- e = rs.getResult() and
+ exists(Node node |
+ node instanceof JsonpInjectionSink and
+ e = node.asExpr() and
branch = true
)
}
}
-/** Taint-tracking configuration tracing flow from remote sources to output jsonp data. */
+/** Taint-tracking configuration tracing flow from get method request sources to output jsonp data. */
class JsonpInjectionConfig extends TaintTracking::Configuration {
JsonpInjectionConfig() { this = "JsonpInjectionConfig" }
- override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
+ override predicate isSource(DataFlow::Node source) { source instanceof GetHttpRequestSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof JsonpInjectionSink }
- override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { guard instanceof VerifAuth }
+ override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
+ guard instanceof ServletVerifAuth
+ }
+
+ override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
+ exists(MethodAccess ma |
+ isRequestGetParamMethod(ma) and pred.asExpr() = ma.getQualifier() and succ.asExpr() = ma
+ )
+ }
}
from DataFlow::PathNode source, DataFlow::PathNode sink, JsonpInjectionConfig conf
where
+ not checks() = false and
conf.hasFlowPath(source, sink) and
exists(JsonpInjectionFlowConfig jhfc | jhfc.hasFlowTo(sink.getNode()))
-select sink.getNode(), source, sink, "Json Hijacking query might include code from $@.",
- source.getNode(), "this user input"
\ No newline at end of file
+select sink.getNode(), source, sink, "Jsonp Injection query might include code from $@.",
+ source.getNode(), "this user input"
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonpInjectionFilterLib.qll b/java/ql/src/Security/CWE/CWE-352/JsonpInjectionFilterLib.qll
new file mode 100644
index 000000000000..b349bed26414
--- /dev/null
+++ b/java/ql/src/Security/CWE/CWE-352/JsonpInjectionFilterLib.qll
@@ -0,0 +1,77 @@
+/**
+ * @name JSONP Injection
+ * @description User-controlled callback function names that are not verified are vulnerable
+ * to json hijacking attacks.
+ * @kind path-problem
+ */
+
+import java
+import DataFlow
+import semmle.code.java.dataflow.FlowSources
+import semmle.code.java.dataflow.TaintTracking2
+import DataFlow::PathGraph
+
+class FilterVerifAuth extends DataFlow::BarrierGuard {
+ FilterVerifAuth() {
+ exists(MethodAccess ma, Node prod, Node succ |
+ ma.getMethod().getName().regexpMatch("(?i).*(token|auth|referer|origin).*") and
+ prod instanceof RemoteFlowSource and
+ succ.asExpr() = ma.getAnArgument() and
+ ma.getMethod().getAParameter().getName().regexpMatch("(?i).*(token|auth|referer|origin).*") and
+ localFlowStep*(prod, succ) and
+ this = ma
+ )
+ }
+
+ override predicate checks(Expr e, boolean branch) {
+ exists(Node node |
+ node instanceof DoFilterMethodSink and
+ e = node.asExpr() and
+ branch = true
+ )
+ }
+}
+
+/** A data flow source for `Filter.doFilter` method paramters. */
+private class DoFilterMethodSource extends DataFlow::Node {
+ DoFilterMethodSource() {
+ exists(Method m |
+ isDoFilterMethod(m) and
+ m.getAParameter().getAnAccess() = this.asExpr()
+ )
+ }
+}
+
+/** A data flow sink for `FilterChain.doFilter` method qualifying expression. */
+private class DoFilterMethodSink extends DataFlow::Node {
+ DoFilterMethodSink() {
+ exists(MethodAccess ma, Method m | ma.getMethod() = m |
+ m.hasName("doFilter") and
+ m.getDeclaringType*().hasQualifiedName("javax.servlet", "FilterChain") and
+ ma.getQualifier() = this.asExpr()
+ )
+ }
+}
+
+/** Taint-tracking configuration tracing flow from `doFilter` method paramter source to output
+ * `FilterChain.doFilter` method qualifying expression.
+ * */
+class DoFilterMethodConfig extends TaintTracking::Configuration {
+ DoFilterMethodConfig() { this = "DoFilterMethodConfig" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof DoFilterMethodSource }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof DoFilterMethodSink }
+
+ override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
+ guard instanceof FilterVerifAuth
+ }
+}
+
+/** Implement class modeling verification for `Filter.doFilter`, return false if it fails. */
+boolean checks() {
+ exists(DataFlow::PathNode source, DataFlow::PathNode sink, DoFilterMethodConfig conf |
+ conf.hasFlowPath(source, sink) and
+ result = false
+ )
+}
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonpInjectionLib.qll b/java/ql/src/Security/CWE/CWE-352/JsonpInjectionLib.qll
index 4294a5e8c8f2..3f7304258232 100644
--- a/java/ql/src/Security/CWE/CWE-352/JsonpInjectionLib.qll
+++ b/java/ql/src/Security/CWE/CWE-352/JsonpInjectionLib.qll
@@ -5,6 +5,69 @@ import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.frameworks.spring.SpringController
+/** Holds if `m` is a method of some override of `HttpServlet.doGet`. */
+private predicate isGetServletMethod(Method m) {
+ isServletRequestMethod(m) and m.getName() = "doGet"
+}
+
+/** Holds if `m` is a method of some override of `HttpServlet.doGet`. */
+private predicate isGetSpringControllerMethod(Method m) {
+ exists(Annotation a |
+ a = m.getAnAnnotation() and
+ a.getType().hasQualifiedName("org.springframework.web.bind.annotation", "GetMapping")
+ )
+ or
+ exists(Annotation a |
+ a = m.getAnAnnotation() and
+ a.getType().hasQualifiedName("org.springframework.web.bind.annotation", "RequestMapping") and
+ a.getValue("method").toString().regexpMatch("RequestMethod.GET|\\{...\\}")
+ )
+}
+
+/** Method parameters use the annotation `@RequestParam` or the parameter type is `ServletRequest`, `String`, `Object` */
+predicate checkSpringMethodParameterType(Method m, int i) {
+ m.getParameter(i).getType() instanceof ServletRequest
+ or
+ exists(Parameter p |
+ p = m.getParameter(i) and
+ p.hasAnnotation() and
+ p.getAnAnnotation()
+ .getType()
+ .hasQualifiedName("org.springframework.web.bind.annotation", "RequestParam") and
+ p.getType().getName().regexpMatch("String|Object")
+ )
+ or
+ exists(Parameter p |
+ p = m.getParameter(i) and
+ not p.hasAnnotation() and
+ p.getType().getName().regexpMatch("String|Object")
+ )
+}
+
+/** A data flow source for get method request parameters. */
+abstract class GetHttpRequestSource extends DataFlow::Node { }
+
+/** A data flow source for servlet get method request parameters. */
+private class ServletGetHttpRequestSource extends GetHttpRequestSource {
+ ServletGetHttpRequestSource() {
+ exists(Method m |
+ isGetServletMethod(m) and
+ m.getParameter(0).getAnAccess() = this.asExpr()
+ )
+ }
+}
+
+/** A data flow source for spring controller get method request parameters. */
+private class SpringGetHttpRequestSource extends GetHttpRequestSource {
+ SpringGetHttpRequestSource() {
+ exists(SpringControllerMethod scm, int i |
+ isGetSpringControllerMethod(scm) and
+ checkSpringMethodParameterType(scm, i) and
+ scm.getParameter(i).getAnAccess() = this.asExpr()
+ )
+ }
+}
+
/** A data flow sink for unvalidated user input that is used to jsonp. */
abstract class JsonpInjectionSink extends DataFlow::Node { }
@@ -26,7 +89,7 @@ private class WriterPrintln extends JsonpInjectionSink {
private class SpringReturn extends JsonpInjectionSink {
SpringReturn() {
exists(ReturnStmt rs, Method m | m = rs.getEnclosingCallable() |
- m instanceof SpringRequestMappingMethod and
+ isGetSpringControllerMethod(m) and
rs.getResult() = this.asExpr()
)
}
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonpInjectionServlet.java b/java/ql/src/Security/CWE/CWE-352/JsonpInjectionServlet.java
new file mode 100644
index 000000000000..916cd9bf676e
--- /dev/null
+++ b/java/ql/src/Security/CWE/CWE-352/JsonpInjectionServlet.java
@@ -0,0 +1,60 @@
+import com.google.gson.Gson;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class JsonpInjectionServlet extends HttpServlet {
+
+ private static HashMap hashMap = new HashMap();
+
+ static {
+ hashMap.put("username","admin");
+ hashMap.put("password","123456");
+ }
+
+ private static final long serialVersionUID = 1L;
+
+ private String key = "test";
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ String jsonpCallback = req.getParameter("jsonpCallback");
+
+ PrintWriter pw = null;
+ Gson gson = new Gson();
+ String result = gson.toJson(hashMap);
+
+ String resultStr = null;
+ pw = resp.getWriter();
+ resultStr = jsonpCallback + "(" + result + ")";
+ pw.println(resultStr);
+ pw.flush();
+ }
+
+ @Override
+ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ String jsonpCallback = req.getParameter("jsonpCallback");
+ PrintWriter pw = null;
+ Gson gson = new Gson();
+ String result = gson.toJson(hashMap);
+
+ String resultStr = null;
+ pw = resp.getWriter();
+ resultStr = jsonpCallback + "(" + result + ")";
+ pw.println(resultStr);
+ pw.flush();
+ }
+
+ @Override
+ public void init(ServletConfig config) throws ServletException {
+ this.key = config.getInitParameter("key");
+ System.out.println("初始化" + this.key);
+ super.init(config);
+ }
+
+}
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonpInjectionServlet1.java b/java/ql/src/Security/CWE/CWE-352/JsonpInjectionServlet1.java
new file mode 100644
index 000000000000..14ef76275b1d
--- /dev/null
+++ b/java/ql/src/Security/CWE/CWE-352/JsonpInjectionServlet1.java
@@ -0,0 +1,64 @@
+import com.google.gson.Gson;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class JsonpInjectionServlet1 extends HttpServlet {
+
+ private static HashMap hashMap = new HashMap();
+
+ static {
+ hashMap.put("username","admin");
+ hashMap.put("password","123456");
+ }
+
+ private static final long serialVersionUID = 1L;
+
+ private String key = "test";
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ doPost(req, resp);
+ }
+
+ @Override
+ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ resp.setContentType("application/json");
+ String jsonpCallback = req.getParameter("jsonpCallback");
+ PrintWriter pw = null;
+ Gson gson = new Gson();
+ String jsonResult = gson.toJson(hashMap);
+
+ String referer = req.getHeader("Referer");
+
+ boolean result = verifReferer(referer);
+
+ // good
+ if (result){
+ String resultStr = null;
+ pw = resp.getWriter();
+ resultStr = jsonpCallback + "(" + jsonResult + ")";
+ pw.println(resultStr);
+ pw.flush();
+ }
+ }
+
+ public static boolean verifReferer(String referer){
+ if (!referer.startsWith("http://test.com/")){
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void init(ServletConfig config) throws ServletException {
+ this.key = config.getInitParameter("key");
+ System.out.println("初始化" + this.key);
+ super.init(config);
+ }
+
+}
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonpInjectionServlet2.java b/java/ql/src/Security/CWE/CWE-352/JsonpInjectionServlet2.java
new file mode 100644
index 000000000000..bbfbc2dc4360
--- /dev/null
+++ b/java/ql/src/Security/CWE/CWE-352/JsonpInjectionServlet2.java
@@ -0,0 +1,50 @@
+import com.google.gson.Gson;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class JsonpInjectionServlet2 extends HttpServlet {
+
+ private static HashMap hashMap = new HashMap();
+
+ static {
+ hashMap.put("username","admin");
+ hashMap.put("password","123456");
+ }
+
+ private static final long serialVersionUID = 1L;
+
+ private String key = "test";
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ doPost(req, resp);
+ }
+
+ @Override
+ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ resp.setContentType("application/json");
+ String jsonpCallback = req.getParameter("jsonpCallback");
+ PrintWriter pw = null;
+ Gson gson = new Gson();
+ String result = gson.toJson(hashMap);
+
+ String resultStr = null;
+ pw = resp.getWriter();
+ resultStr = jsonpCallback + "(" + result + ")";
+ pw.println(resultStr);
+ pw.flush();
+ }
+
+ @Override
+ public void init(ServletConfig config) throws ServletException {
+ this.key = config.getInitParameter("key");
+ System.out.println("初始化" + this.key);
+ super.init(config);
+ }
+
+}
diff --git a/java/ql/src/semmle/code/java/frameworks/Servlets.qll b/java/ql/src/semmle/code/java/frameworks/Servlets.qll
index 3fad8c4e18b3..b2054dc30cb7 100644
--- a/java/ql/src/semmle/code/java/frameworks/Servlets.qll
+++ b/java/ql/src/semmle/code/java/frameworks/Servlets.qll
@@ -337,3 +337,30 @@ predicate isRequestGetParamMethod(MethodAccess ma) {
ma.getMethod() instanceof ServletRequestGetParameterMapMethod or
ma.getMethod() instanceof HttpServletRequestGetQueryStringMethod
}
+
+
+/**
+ * A class that has `javax.servlet.Filter` as an ancestor.
+ */
+class FilterClass extends Class {
+ FilterClass() { getAnAncestor().hasQualifiedName("javax.servlet", "Filter") }
+}
+
+
+/**
+ * The interface `javax.servlet.FilterChain`
+ */
+class FilterChain extends RefType {
+ FilterChain() {
+ hasQualifiedName("javax.servlet", "FilterChain")
+ }
+}
+
+/** Holds if `m` is a request handler method (for example `doGet` or `doPost`). */
+predicate isDoFilterMethod(Method m) {
+ m.getDeclaringType() instanceof FilterClass and
+ m.getNumberOfParameters() = 3 and
+ m.getParameter(0).getType() instanceof ServletRequest and
+ m.getParameter(1).getType() instanceof ServletResponse and
+ m.getParameter(2).getType() instanceof FilterChain
+}
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.expected
index 019af8f5c055..7e3069cf1d93 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.expected
@@ -1,60 +1,60 @@
edges
-| JsonpInjection.java:28:32:28:68 | getParameter(...) : String | JsonpInjection.java:33:16:33:24 | resultStr |
-| JsonpInjection.java:32:21:32:54 | ... + ... : String | JsonpInjection.java:33:16:33:24 | resultStr |
-| JsonpInjection.java:40:32:40:68 | getParameter(...) : String | JsonpInjection.java:44:16:44:24 | resultStr |
-| JsonpInjection.java:42:21:42:80 | ... + ... : String | JsonpInjection.java:44:16:44:24 | resultStr |
-| JsonpInjection.java:51:32:51:68 | getParameter(...) : String | JsonpInjection.java:54:16:54:24 | resultStr |
-| JsonpInjection.java:53:21:53:55 | ... + ... : String | JsonpInjection.java:54:16:54:24 | resultStr |
-| JsonpInjection.java:61:32:61:68 | getParameter(...) : String | JsonpInjection.java:64:16:64:24 | resultStr |
-| JsonpInjection.java:63:21:63:54 | ... + ... : String | JsonpInjection.java:64:16:64:24 | resultStr |
-| JsonpInjection.java:72:32:72:68 | getParameter(...) : String | JsonpInjection.java:80:20:80:28 | resultStr |
+| JsonpInjection.java:29:32:29:38 | request : HttpServletRequest | JsonpInjection.java:34:16:34:24 | resultStr |
+| JsonpInjection.java:33:21:33:54 | ... + ... : String | JsonpInjection.java:34:16:34:24 | resultStr |
+| JsonpInjection.java:41:32:41:38 | request : HttpServletRequest | JsonpInjection.java:45:16:45:24 | resultStr |
+| JsonpInjection.java:43:21:43:80 | ... + ... : String | JsonpInjection.java:45:16:45:24 | resultStr |
+| JsonpInjection.java:52:32:52:38 | request : HttpServletRequest | JsonpInjection.java:55:16:55:24 | resultStr |
+| JsonpInjection.java:54:21:54:55 | ... + ... : String | JsonpInjection.java:55:16:55:24 | resultStr |
+| JsonpInjection.java:62:32:62:38 | request : HttpServletRequest | JsonpInjection.java:65:16:65:24 | resultStr |
+| JsonpInjection.java:64:21:64:54 | ... + ... : String | JsonpInjection.java:65:16:65:24 | resultStr |
+| JsonpInjection.java:72:32:72:38 | request : HttpServletRequest | JsonpInjection.java:80:20:80:28 | resultStr |
| JsonpInjection.java:79:21:79:54 | ... + ... : String | JsonpInjection.java:80:20:80:28 | resultStr |
-| JsonpInjection.java:88:32:88:68 | getParameter(...) : String | JsonpInjection.java:95:20:95:28 | resultStr |
-| JsonpInjection.java:94:21:94:54 | ... + ... : String | JsonpInjection.java:95:20:95:28 | resultStr |
-| JsonpInjection.java:102:32:102:68 | getParameter(...) : String | JsonpInjection.java:113:16:113:24 | resultStr |
-| JsonpInjection.java:128:25:128:59 | ... + ... : String | JsonpInjection.java:129:20:129:28 | resultStr |
-| JsonpInjection.java:147:25:147:59 | ... + ... : String | JsonpInjection.java:148:20:148:28 | resultStr |
+| JsonpInjection.java:87:32:87:38 | request : HttpServletRequest | JsonpInjection.java:94:20:94:28 | resultStr |
+| JsonpInjection.java:93:21:93:54 | ... + ... : String | JsonpInjection.java:94:20:94:28 | resultStr |
+| JsonpInjection.java:101:32:101:38 | request : HttpServletRequest | JsonpInjection.java:112:16:112:24 | resultStr |
+| JsonpInjection.java:127:25:127:59 | ... + ... : String | JsonpInjection.java:128:20:128:28 | resultStr |
+| JsonpInjection.java:148:25:148:59 | ... + ... : String | JsonpInjection.java:149:20:149:28 | resultStr |
nodes
-| JsonpInjection.java:28:32:28:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpInjection.java:32:21:32:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjection.java:33:16:33:24 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:33:16:33:24 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:40:32:40:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpInjection.java:42:21:42:80 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjection.java:44:16:44:24 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:44:16:44:24 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:51:32:51:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpInjection.java:53:21:53:55 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjection.java:54:16:54:24 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:54:16:54:24 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:61:32:61:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpInjection.java:63:21:63:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjection.java:64:16:64:24 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:64:16:64:24 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:72:32:72:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpInjection.java:29:32:29:38 | request : HttpServletRequest | semmle.label | request : HttpServletRequest |
+| JsonpInjection.java:33:21:33:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjection.java:34:16:34:24 | resultStr | semmle.label | resultStr |
+| JsonpInjection.java:34:16:34:24 | resultStr | semmle.label | resultStr |
+| JsonpInjection.java:41:32:41:38 | request : HttpServletRequest | semmle.label | request : HttpServletRequest |
+| JsonpInjection.java:43:21:43:80 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjection.java:45:16:45:24 | resultStr | semmle.label | resultStr |
+| JsonpInjection.java:45:16:45:24 | resultStr | semmle.label | resultStr |
+| JsonpInjection.java:52:32:52:38 | request : HttpServletRequest | semmle.label | request : HttpServletRequest |
+| JsonpInjection.java:54:21:54:55 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjection.java:55:16:55:24 | resultStr | semmle.label | resultStr |
+| JsonpInjection.java:55:16:55:24 | resultStr | semmle.label | resultStr |
+| JsonpInjection.java:62:32:62:38 | request : HttpServletRequest | semmle.label | request : HttpServletRequest |
+| JsonpInjection.java:64:21:64:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjection.java:65:16:65:24 | resultStr | semmle.label | resultStr |
+| JsonpInjection.java:65:16:65:24 | resultStr | semmle.label | resultStr |
+| JsonpInjection.java:72:32:72:38 | request : HttpServletRequest | semmle.label | request : HttpServletRequest |
| JsonpInjection.java:79:21:79:54 | ... + ... : String | semmle.label | ... + ... : String |
| JsonpInjection.java:80:20:80:28 | resultStr | semmle.label | resultStr |
| JsonpInjection.java:80:20:80:28 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:88:32:88:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpInjection.java:94:21:94:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjection.java:95:20:95:28 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:95:20:95:28 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:102:32:102:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpInjection.java:113:16:113:24 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:128:25:128:59 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjection.java:129:20:129:28 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:147:25:147:59 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjection.java:148:20:148:28 | resultStr | semmle.label | resultStr |
+| JsonpInjection.java:87:32:87:38 | request : HttpServletRequest | semmle.label | request : HttpServletRequest |
+| JsonpInjection.java:93:21:93:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjection.java:94:20:94:28 | resultStr | semmle.label | resultStr |
+| JsonpInjection.java:94:20:94:28 | resultStr | semmle.label | resultStr |
+| JsonpInjection.java:101:32:101:38 | request : HttpServletRequest | semmle.label | request : HttpServletRequest |
+| JsonpInjection.java:112:16:112:24 | resultStr | semmle.label | resultStr |
+| JsonpInjection.java:127:25:127:59 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjection.java:128:20:128:28 | resultStr | semmle.label | resultStr |
+| JsonpInjection.java:148:25:148:59 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjection.java:149:20:149:28 | resultStr | semmle.label | resultStr |
#select
-| JsonpInjection.java:33:16:33:24 | resultStr | JsonpInjection.java:28:32:28:68 | getParameter(...) : String | JsonpInjection.java:33:16:33:24 | resultStr | Json Hijacking query
-might include code from $@. | JsonpInjection.java:28:32:28:68 | getParameter(...) | this user input |
-| JsonpInjection.java:44:16:44:24 | resultStr | JsonpInjection.java:40:32:40:68 | getParameter(...) : String | JsonpInjection.java:44:16:44:24 | resultStr | Json Hijacking query
-might include code from $@. | JsonpInjection.java:40:32:40:68 | getParameter(...) | this user input |
-| JsonpInjection.java:54:16:54:24 | resultStr | JsonpInjection.java:51:32:51:68 | getParameter(...) : String | JsonpInjection.java:54:16:54:24 | resultStr | Json Hijacking query
-might include code from $@. | JsonpInjection.java:51:32:51:68 | getParameter(...) | this user input |
-| JsonpInjection.java:64:16:64:24 | resultStr | JsonpInjection.java:61:32:61:68 | getParameter(...) : String | JsonpInjection.java:64:16:64:24 | resultStr | Json Hijacking query
-might include code from $@. | JsonpInjection.java:61:32:61:68 | getParameter(...) | this user input |
-| JsonpInjection.java:80:20:80:28 | resultStr | JsonpInjection.java:72:32:72:68 | getParameter(...) : String | JsonpInjection.java:80:20:80:28 | resultStr | Json Hijacking query
-might include code from $@. | JsonpInjection.java:72:32:72:68 | getParameter(...) | this user input |
-| JsonpInjection.java:95:20:95:28 | resultStr | JsonpInjection.java:88:32:88:68 | getParameter(...) : String | JsonpInjection.java:95:20:95:28 | resultStr | Json Hijacking query
-might include code from $@. | JsonpInjection.java:88:32:88:68 | getParameter(...) | this user input |
\ No newline at end of file
+| JsonpInjection.java:34:16:34:24 | resultStr | JsonpInjection.java:29:32:29:38 | request : HttpServletRequest | JsonpInjection.java:34:16:34:24 |
+resultStr | Jsonp Injection query might include code from $@. | JsonpInjection.java:29:32:29:38 | request | this user input |
+| JsonpInjection.java:45:16:45:24 | resultStr | JsonpInjection.java:41:32:41:38 | request : HttpServletRequest | JsonpInjection.java:45:16:45:24 |
+resultStr | Jsonp Injection query might include code from $@. | JsonpInjection.java:41:32:41:38 | request | this user input |
+| JsonpInjection.java:55:16:55:24 | resultStr | JsonpInjection.java:52:32:52:38 | request : HttpServletRequest | JsonpInjection.java:55:16:55:24 |
+resultStr | Jsonp Injection query might include code from $@. | JsonpInjection.java:52:32:52:38 | request | this user input |
+| JsonpInjection.java:65:16:65:24 | resultStr | JsonpInjection.java:62:32:62:38 | request : HttpServletRequest | JsonpInjection.java:65:16:65:24 |
+resultStr | Jsonp Injection query might include code from $@. | JsonpInjection.java:62:32:62:38 | request | this user input |
+| JsonpInjection.java:80:20:80:28 | resultStr | JsonpInjection.java:72:32:72:38 | request : HttpServletRequest | JsonpInjection.java:80:20:80:28 |
+resultStr | Jsonp Injection query might include code from $@. | JsonpInjection.java:72:32:72:38 | request | this user input |
+| JsonpInjection.java:94:20:94:28 | resultStr | JsonpInjection.java:87:32:87:38 | request : HttpServletRequest | JsonpInjection.java:94:20:94:28 |
+resultStr | Jsonp Injection query might include code from $@. | JsonpInjection.java:87:32:87:38 | request | this user input |
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.java
index df3aa2c02fe9..9f079513a8b9 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.java
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.java
@@ -8,11 +8,12 @@
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class JsonpInjection {
-
private static HashMap hashMap = new HashMap();
static {
@@ -21,7 +22,7 @@ public class JsonpInjection {
}
- @GetMapping(value = "jsonp1")
+ @GetMapping(value = "jsonp1", produces="text/javascript")
@ResponseBody
public String bad1(HttpServletRequest request) {
String resultStr = null;
@@ -68,7 +69,6 @@ public String bad4(HttpServletRequest request) {
@ResponseBody
public void bad5(HttpServletRequest request,
HttpServletResponse response) throws Exception {
- response.setContentType("application/json");
String jsonpCallback = request.getParameter("jsonpCallback");
PrintWriter pw = null;
Gson gson = new Gson();
@@ -84,7 +84,6 @@ public void bad5(HttpServletRequest request,
@ResponseBody
public void bad6(HttpServletRequest request,
HttpServletResponse response) throws Exception {
- response.setContentType("application/json");
String jsonpCallback = request.getParameter("jsonpCallback");
PrintWriter pw = null;
ObjectMapper mapper = new ObjectMapper();
@@ -141,8 +140,10 @@ public String good2(HttpServletRequest request) {
String referer = request.getHeader("Referer");
boolean result = verifReferer(referer);
+
+ boolean test = result;
// good
- if (result){
+ if (test){
String jsonStr = getJsonStr(hashMap);
resultStr = jsonpCallback + "(" + jsonStr + ")";
return resultStr;
@@ -168,4 +169,4 @@ public static boolean verifReferer(String referer){
}
return true;
}
-}
+}
\ No newline at end of file
diff --git a/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/annotation/AliasFor.class b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/annotation/AliasFor.class
deleted file mode 100644
index 438065489278b92503abc2ad8d75716c5e56ac08..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 385
zcmb7=!Ab)$5QhJ0x31kDdrH&Kx0=)qG#3PM4!PcXZrOKO@(l3m};gAd?CiL-}x
zJqaEL=Fj~6^G&|KKRyB6W0qr@<21(^Vbrp1G~wdrcD3b}m1S3}bqdDS4}|lDb3So0
z-aYCKH#QMKxO{0`GCTd`S`$rab#IG=`O1e{#kVeF6L_cJeRx%s4_fgdPA#nAxb#7`
zj5*1|vPl9`tbG$Iy);(DbZ?q>Y=pc21QTZcMbG6{R|0?4KmBGoU|o}(H;@|2R}C^k
ghLPwaQNxHF$I?t>JeJBL3UL&FIx;a%x-6Xh0OC_*bpQYW
diff --git a/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/GetMapping.class b/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/GetMapping.class
deleted file mode 100644
index 5392ca0ebc1593d6bfa2f7d765ee4ca169fb260f..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 504
zcma)&y-ve06orr5G%4k$Ewlp@8)_FUu~3N#3Bgi?)JiO!oYW02i5(KVeK!UkfQLfd
z3?&dTFj%_xlg>TI=i~G39l#Za0geNl1Q;-QTBMR;Fd9$SVk3AWbj;^AS316C=-+5<
ztgy=HTe%W0u?%2nZA9WoH5`o>f62T|*k=Ym6S+tWhIV9h;Zj+SS#FjtD#y;;xIB_~
zDxp)|dubm;mXYs88HC|<=CoC*d{Tu96Imr8>11m1m={?Yb44Ckk!ycGS!yuAF9#FEVXJbh%nj0^%G
u-TFC+dFlH8Nm;4MC5#O62q7eGj&Kvy7#SEDn1GlW=t>44%>pEu7+3+XjWw+R
From 95d1994196dc52ad516b7c6df06e673be8906d9b Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Mon, 1 Mar 2021 22:06:52 +0000
Subject: [PATCH 008/433] Query to check sensitive cookies without the HttpOnly
flag set
---
.../CWE-1004/SensitiveCookieNotHttpOnly.java | 44 +++
.../CWE-1004/SensitiveCookieNotHttpOnly.qhelp | 27 ++
.../CWE-1004/SensitiveCookieNotHttpOnly.ql | 194 ++++++++++
.../SensitiveCookieNotHttpOnly.expected | 13 +
.../CWE-1004/SensitiveCookieNotHttpOnly.java | 57 +++
.../CWE-1004/SensitiveCookieNotHttpOnly.qlref | 1 +
.../query-tests/security/CWE-1004/options | 1 +
.../javax/ws/rs/core/Cookie.java | 187 +++++++++
.../javax/ws/rs/core/NewCookie.java | 359 ++++++++++++++++++
.../javax/servlet/http/Cookie.java | 33 ++
.../servlet/http/HttpServletResponse.java | 6 +
11 files changed, 922 insertions(+)
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.java
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.qhelp
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.java
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.qlref
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-1004/options
create mode 100644 java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/Cookie.java
create mode 100644 java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/NewCookie.java
diff --git a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.java b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.java
new file mode 100644
index 000000000000..48d80707ff83
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.java
@@ -0,0 +1,44 @@
+class SensitiveCookieNotHttpOnly {
+ // GOOD - Create a sensitive cookie with the `HttpOnly` flag set.
+ public void addCookie(String jwt_token, HttpServletRequest request, HttpServletResponse response) {
+ Cookie jwtCookie =new Cookie("jwt_token", jwt_token);
+ jwtCookie.setPath("/");
+ jwtCookie.setMaxAge(3600*24*7);
+ jwtCookie.setHttpOnly(true);
+ response.addCookie(jwtCookie);
+ }
+
+ // BAD - Create a sensitive cookie without the `HttpOnly` flag set.
+ public void addCookie2(String jwt_token, String userId, HttpServletRequest request, HttpServletResponse response) {
+ Cookie jwtCookie =new Cookie("jwt_token", jwt_token);
+ jwtCookie.setPath("/");
+ jwtCookie.setMaxAge(3600*24*7);
+ response.addCookie(jwtCookie);
+ }
+
+ // GOOD - Set a sensitive cookie header with the `HttpOnly` flag set.
+ public void addCookie3(String authId, HttpServletRequest request, HttpServletResponse response) {
+ response.addHeader("Set-Cookie", "token=" +authId + ";HttpOnly;Secure");
+ }
+
+ // BAD - Set a sensitive cookie header without the `HttpOnly` flag set.
+ public void addCookie4(String authId, HttpServletRequest request, HttpServletResponse response) {
+ response.addHeader("Set-Cookie", "token=" +authId + ";Secure");
+ }
+
+ // GOOD - Set a sensitive cookie header using the class `javax.ws.rs.core.Cookie` with the `HttpOnly` flag set through string concatenation.
+ public void addCookie5(String accessKey, HttpServletRequest request, HttpServletResponse response) {
+ response.setHeader("Set-Cookie", new NewCookie("session-access-key", accessKey, "/", null, null, 0, true) + ";HttpOnly");
+ }
+
+ // BAD - Set a sensitive cookie header using the class `javax.ws.rs.core.Cookie` without the `HttpOnly` flag set.
+ public void addCookie6(String accessKey, HttpServletRequest request, HttpServletResponse response) {
+ response.setHeader("Set-Cookie", new NewCookie("session-access-key", accessKey, "/", null, null, 0, true).toString());
+ }
+
+ // GOOD - Set a sensitive cookie header using the class `javax.ws.rs.core.Cookie` with the `HttpOnly` flag set through the constructor.
+ public void addCookie7(String accessKey, HttpServletRequest request, HttpServletResponse response) {
+ NewCookie accessKeyCookie = new NewCookie("session-access-key", accessKey, "/", null, null, 0, true, true);
+ response.setHeader("Set-Cookie", accessKeyCookie.toString());
+ }
+}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.qhelp b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.qhelp
new file mode 100644
index 000000000000..880ed767be98
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.qhelp
@@ -0,0 +1,27 @@
+
+
+
+
+ Cross-Site Scripting (XSS) is categorized as one of the OWASP Top 10 Security Vulnerabilities. The HttpOnly
flag directs compatible browsers to prevent client-side script from accessing cookies. Including the HttpOnly flag in the Set-Cookie HTTP response header for a sensitive cookie helps mitigate the risk associated with XSS where an attacker's script code attempts to read the contents of a cookie and exfiltrate information obtained.
+
+
+
+ Use the HttpOnly
flag when generating a cookie containing sensitive information to help mitigate the risk of client side script accessing the protected cookie.
+
+
+
+ The following example shows two ways of generating sensitive cookies. In the 'BAD' cases, the HttpOnly
flag is not set. In the 'GOOD' cases, the HttpOnly
flag is set.
+
+
+
+
+
+ PortSwigger:
+ Cookie without HttpOnly flag set
+
+
+ OWASP:
+ HttpOnly
+
+
+
diff --git a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
new file mode 100644
index 000000000000..bf4e60134c9a
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
@@ -0,0 +1,194 @@
+/**
+ * @name Sensitive cookies without the HttpOnly response header set
+ * @description Sensitive cookies without 'HttpOnly' leaves session cookies vulnerable to an XSS attack.
+ * @kind path-problem
+ * @id java/sensitive-cookie-not-httponly
+ * @tags security
+ * external/cwe/cwe-1004
+ */
+
+import java
+import semmle.code.java.frameworks.Servlets
+import semmle.code.java.dataflow.FlowSources
+import semmle.code.java.dataflow.TaintTracking
+import DataFlow::PathGraph
+
+/** Gets a regular expression for matching common names of sensitive cookies. */
+string getSensitiveCookieNameRegex() { result = "(?i).*(auth|session|token|key|credential).*" }
+
+/** Holds if a string is concatenated with the name of a sensitive cookie. */
+predicate isSensitiveCookieNameExpr(Expr expr) {
+ expr.(StringLiteral)
+ .getRepresentedString()
+ .toLowerCase()
+ .regexpMatch(getSensitiveCookieNameRegex()) or
+ isSensitiveCookieNameExpr(expr.(AddExpr).getAnOperand())
+}
+
+/** Holds if a string is concatenated with the `HttpOnly` flag. */
+predicate hasHttpOnlyExpr(Expr expr) {
+ expr.(StringLiteral).getRepresentedString().toLowerCase().matches("%httponly%") or
+ hasHttpOnlyExpr(expr.(AddExpr).getAnOperand())
+}
+
+/** The method call `Set-Cookie` of `addHeader` or `setHeader`. */
+class SetCookieMethodAccess extends MethodAccess {
+ SetCookieMethodAccess() {
+ (
+ this.getMethod() instanceof ResponseAddHeaderMethod or
+ this.getMethod() instanceof ResponseSetHeaderMethod
+ ) and
+ this.getArgument(0).(StringLiteral).getRepresentedString().toLowerCase() = "set-cookie"
+ }
+}
+
+/** Sensitive cookie name used in a `Cookie` constructor or a `Set-Cookie` call. */
+class SensitiveCookieNameExpr extends Expr {
+ SensitiveCookieNameExpr() {
+ isSensitiveCookieNameExpr(this) and
+ (
+ exists(
+ ClassInstanceExpr cie // new Cookie("jwt_token", token)
+ |
+ (
+ cie.getConstructor().getDeclaringType().hasQualifiedName("javax.servlet.http", "Cookie") or
+ cie.getConstructor()
+ .getDeclaringType()
+ .getASupertype*()
+ .hasQualifiedName("javax.ws.rs.core", "Cookie") or
+ cie.getConstructor()
+ .getDeclaringType()
+ .getASupertype*()
+ .hasQualifiedName("jakarta.ws.rs.core", "Cookie")
+ ) and
+ DataFlow::localExprFlow(this, cie.getArgument(0))
+ )
+ or
+ exists(
+ SetCookieMethodAccess ma // response.addHeader("Set-Cookie: token=" +authId + ";HttpOnly;Secure")
+ |
+ DataFlow::localExprFlow(this, ma.getArgument(1))
+ )
+ )
+ }
+}
+
+/** Sink of adding a cookie to the HTTP response. */
+class CookieResponseSink extends DataFlow::ExprNode {
+ CookieResponseSink() {
+ exists(MethodAccess ma |
+ (
+ ma.getMethod() instanceof ResponseAddCookieMethod or
+ ma instanceof SetCookieMethodAccess
+ ) and
+ ma.getAnArgument() = this.getExpr()
+ )
+ }
+}
+
+/** Holds if the `node` is a method call of `setHttpOnly(true)` on a cookie. */
+predicate setHttpOnlyMethodAccess(DataFlow::Node node) {
+ exists(
+ MethodAccess addCookie, Variable cookie, MethodAccess m // jwtCookie.setHttpOnly(true)
+ |
+ addCookie.getMethod() instanceof ResponseAddCookieMethod and
+ addCookie.getArgument(0) = cookie.getAnAccess() and
+ m.getMethod().getName() = "setHttpOnly" and
+ m.getArgument(0).(BooleanLiteral).getBooleanValue() = true and
+ m.getQualifier() = cookie.getAnAccess() and
+ node.asExpr() = cookie.getAnAccess()
+ )
+}
+
+/** Holds if the `node` is a method call of `Set-Cookie` header with the `HttpOnly` flag whose cookie name is sensitive. */
+predicate setHttpOnlyInSetCookie(DataFlow::Node node) {
+ exists(SetCookieMethodAccess sa |
+ hasHttpOnlyExpr(node.asExpr()) and
+ DataFlow::localExprFlow(node.asExpr(), sa.getArgument(1))
+ )
+}
+
+/** Holds if the `node` is an invocation of a JAX-RS `NewCookie` constructor that sets `HttpOnly` to true. */
+predicate setHttpOnlyInNewCookie(DataFlow::Node node) {
+ exists(ClassInstanceExpr cie |
+ cie.getConstructor().getDeclaringType().hasName("NewCookie") and
+ DataFlow::localExprFlow(node.asExpr(), cie.getArgument(0)) and
+ (
+ cie.getNumArgument() = 6 and cie.getArgument(5).(BooleanLiteral).getBooleanValue() = true // NewCookie(Cookie cookie, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
+ or
+ cie.getNumArgument() = 8 and
+ cie.getArgument(6).getType() instanceof BooleanType and
+ cie.getArgument(7).(BooleanLiteral).getBooleanValue() = true // NewCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly)
+ or
+ cie.getNumArgument() = 10 and cie.getArgument(9).(BooleanLiteral).getBooleanValue() = true // NewCookie(String name, String value, String path, String domain, int version, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
+ )
+ )
+}
+
+/**
+ * Holds if the node is a test method indicated by:
+ * a) in a test directory such as `src/test/java`
+ * b) in a test package whose name has the word `test`
+ * c) in a test class whose name has the word `test`
+ * d) in a test class implementing a test framework such as JUnit or TestNG
+ */
+predicate isTestMethod(DataFlow::Node node) {
+ exists(MethodAccess ma, Method m |
+ node.asExpr() = ma.getAnArgument() and
+ m = ma.getEnclosingCallable() and
+ (
+ m.getDeclaringType().getName().toLowerCase().matches("%test%") or // Simple check to exclude test classes to reduce FPs
+ m.getDeclaringType().getPackage().getName().toLowerCase().matches("%test%") or // Simple check to exclude classes in test packages to reduce FPs
+ exists(m.getLocation().getFile().getAbsolutePath().indexOf("/src/test/java")) or // Match test directory structure of build tools like maven
+ m instanceof TestMethod // Test method of a test case implementing a test framework such as JUnit or TestNG
+ )
+ )
+}
+
+/** A taint configuration tracking flow from a sensitive cookie without HttpOnly flag set to its HTTP response. */
+class MissingHttpOnlyConfiguration extends TaintTracking::Configuration {
+ MissingHttpOnlyConfiguration() { this = "MissingHttpOnlyConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) {
+ source.asExpr() instanceof SensitiveCookieNameExpr
+ }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof CookieResponseSink }
+
+ override predicate isSanitizer(DataFlow::Node node) {
+ // cookie.setHttpOnly(true)
+ setHttpOnlyMethodAccess(node)
+ or
+ // response.addHeader("Set-Cookie: token=" +authId + ";HttpOnly;Secure")
+ setHttpOnlyInSetCookie(node)
+ or
+ // new NewCookie("session-access-key", accessKey, "/", null, null, 0, true, true)
+ setHttpOnlyInNewCookie(node)
+ or
+ // Test class or method
+ isTestMethod(node)
+ }
+
+ override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
+ exists(
+ ClassInstanceExpr cie // `NewCookie` constructor
+ |
+ cie.getAnArgument() = pred.asExpr() and
+ cie = succ.asExpr() and
+ cie.getConstructor().getDeclaringType().hasName("NewCookie")
+ )
+ or
+ exists(
+ MethodAccess ma // `toString` call on a cookie object
+ |
+ ma.getQualifier() = pred.asExpr() and
+ ma.getMethod().hasName("toString") and
+ ma = succ.asExpr()
+ )
+ }
+}
+
+from DataFlow::PathNode source, DataFlow::PathNode sink, MissingHttpOnlyConfiguration c
+where c.hasFlowPath(source, sink)
+select sink.getNode(), source, sink, "$@ doesn't have the HttpOnly flag set.", source.getNode(),
+ "This sensitive cookie"
diff --git a/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected
new file mode 100644
index 000000000000..84d6be3863ac
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected
@@ -0,0 +1,13 @@
+edges
+| SensitiveCookieNotHttpOnly.java:22:38:22:48 | "jwt_token" : String | SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie |
+| SensitiveCookieNotHttpOnly.java:49:56:49:75 | "session-access-key" : String | SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) |
+nodes
+| SensitiveCookieNotHttpOnly.java:22:38:22:48 | "jwt_token" : String | semmle.label | "jwt_token" : String |
+| SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie | semmle.label | jwtCookie |
+| SensitiveCookieNotHttpOnly.java:39:42:39:69 | ... + ... | semmle.label | ... + ... |
+| SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) | semmle.label | toString(...) |
+| SensitiveCookieNotHttpOnly.java:49:56:49:75 | "session-access-key" : String | semmle.label | "session-access-key" : String |
+#select
+| SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie | SensitiveCookieNotHttpOnly.java:22:38:22:48 | "jwt_token" : String | SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:22:38:22:48 | "jwt_token" | This sensitive cookie |
+| SensitiveCookieNotHttpOnly.java:39:42:39:69 | ... + ... | SensitiveCookieNotHttpOnly.java:39:42:39:69 | ... + ... | SensitiveCookieNotHttpOnly.java:39:42:39:69 | ... + ... | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:39:42:39:69 | ... + ... | This sensitive cookie |
+| SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) | SensitiveCookieNotHttpOnly.java:49:56:49:75 | "session-access-key" : String | SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:49:56:49:75 | "session-access-key" | This sensitive cookie |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.java b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.java
new file mode 100644
index 000000000000..5e4f349f7c88
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.java
@@ -0,0 +1,57 @@
+import java.io.IOException;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.ServletException;
+
+import javax.ws.rs.core.NewCookie;
+
+class SensitiveCookieNotHttpOnly {
+ // GOOD - Tests adding a sensitive cookie with the `HttpOnly` flag set.
+ public void addCookie(String jwt_token, HttpServletRequest request, HttpServletResponse response) {
+ Cookie jwtCookie =new Cookie("jwt_token", jwt_token);
+ jwtCookie.setPath("/");
+ jwtCookie.setMaxAge(3600*24*7);
+ jwtCookie.setHttpOnly(true);
+ response.addCookie(jwtCookie);
+ }
+
+ // BAD - Tests adding a sensitive cookie without the `HttpOnly` flag set.
+ public void addCookie2(String jwt_token, String userId, HttpServletRequest request, HttpServletResponse response) {
+ Cookie jwtCookie =new Cookie("jwt_token", jwt_token);
+ Cookie userIdCookie =new Cookie("user_id", userId.toString());
+ jwtCookie.setPath("/");
+ userIdCookie.setPath("/");
+ jwtCookie.setMaxAge(3600*24*7);
+ userIdCookie.setMaxAge(3600*24*7);
+ response.addCookie(jwtCookie);
+ response.addCookie(userIdCookie);
+ }
+
+ // GOOD - Tests set a sensitive cookie header with the `HttpOnly` flag set.
+ public void addCookie3(String authId, HttpServletRequest request, HttpServletResponse response) {
+ response.addHeader("Set-Cookie", "token=" +authId + ";HttpOnly;Secure");
+ }
+
+ // BAD - Tests set a sensitive cookie header without the `HttpOnly` flag set.
+ public void addCookie4(String authId, HttpServletRequest request, HttpServletResponse response) {
+ response.addHeader("Set-Cookie", "token=" +authId + ";Secure");
+ }
+
+ // GOOD - Tests set a sensitive cookie header using the class `javax.ws.rs.core.Cookie` with the `HttpOnly` flag set through string concatenation.
+ public void addCookie5(String accessKey, HttpServletRequest request, HttpServletResponse response) {
+ response.setHeader("Set-Cookie", new NewCookie("session-access-key", accessKey, "/", null, null, 0, true) + ";HttpOnly");
+ }
+
+ // BAD - Tests set a sensitive cookie header using the class `javax.ws.rs.core.Cookie` without the `HttpOnly` flag set.
+ public void addCookie6(String accessKey, HttpServletRequest request, HttpServletResponse response) {
+ response.setHeader("Set-Cookie", new NewCookie("session-access-key", accessKey, "/", null, null, 0, true).toString());
+ }
+
+ // GOOD - Tests set a sensitive cookie header using the class `javax.ws.rs.core.Cookie` with the `HttpOnly` flag set through the constructor.
+ public void addCookie7(String accessKey, HttpServletRequest request, HttpServletResponse response) {
+ NewCookie accessKeyCookie = new NewCookie("session-access-key", accessKey, "/", null, null, 0, true, true);
+ response.setHeader("Set-Cookie", accessKeyCookie.toString());
+ }
+}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.qlref b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.qlref
new file mode 100644
index 000000000000..cc2baaf6f7b2
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
diff --git a/java/ql/test/experimental/query-tests/security/CWE-1004/options b/java/ql/test/experimental/query-tests/security/CWE-1004/options
new file mode 100644
index 000000000000..7f2b253fb20d
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-1004/options
@@ -0,0 +1 @@
+// semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../../stubs/jsr311-api-1.1.1
\ No newline at end of file
diff --git a/java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/Cookie.java b/java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/Cookie.java
new file mode 100644
index 000000000000..4e4c7585c35d
--- /dev/null
+++ b/java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/Cookie.java
@@ -0,0 +1,187 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright (c) 2010-2015 Oracle and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * http://glassfish.java.net/public/CDDL+GPL_1_1.html
+ * or packager/legal/LICENSE.txt. See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at packager/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * Oracle designates this particular file as subject to the "Classpath"
+ * exception as provided by Oracle in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+
+package javax.ws.rs.core;
+
+/**
+ * Represents the value of a HTTP cookie, transferred in a request.
+ * RFC 2109 specifies the legal characters for name,
+ * value, path and domain. The default version of 1 corresponds to RFC 2109.
+ *
+ * @author Paul Sandoz
+ * @author Marc Hadley
+ * @see IETF RFC 2109
+ * @since 1.0
+ */
+public class Cookie {
+ /**
+ * Cookies using the default version correspond to RFC 2109.
+ */
+ public static final int DEFAULT_VERSION = 1;
+
+ /**
+ * Create a new instance.
+ *
+ * @param name the name of the cookie.
+ * @param value the value of the cookie.
+ * @param path the URI path for which the cookie is valid.
+ * @param domain the host domain for which the cookie is valid.
+ * @param version the version of the specification to which the cookie complies.
+ * @throws IllegalArgumentException if name is {@code null}.
+ */
+ public Cookie(final String name, final String value, final String path, final String domain, final int version)
+ throws IllegalArgumentException {
+ }
+
+ /**
+ * Create a new instance.
+ *
+ * @param name the name of the cookie.
+ * @param value the value of the cookie.
+ * @param path the URI path for which the cookie is valid.
+ * @param domain the host domain for which the cookie is valid.
+ * @throws IllegalArgumentException if name is {@code null}.
+ */
+ public Cookie(final String name, final String value, final String path, final String domain)
+ throws IllegalArgumentException {
+ }
+
+ /**
+ * Create a new instance.
+ *
+ * @param name the name of the cookie.
+ * @param value the value of the cookie.
+ * @throws IllegalArgumentException if name is {@code null}.
+ */
+ public Cookie(final String name, final String value)
+ throws IllegalArgumentException {
+ }
+
+ /**
+ * Creates a new instance of {@code Cookie} by parsing the supplied string.
+ *
+ * @param value the cookie string.
+ * @return the newly created {@code Cookie}.
+ * @throws IllegalArgumentException if the supplied string cannot be parsed
+ * or is {@code null}.
+ */
+ public static Cookie valueOf(final String value) {
+ return null;
+ }
+
+ /**
+ * Get the name of the cookie.
+ *
+ * @return the cookie name.
+ */
+ public String getName() {
+ return null;
+ }
+
+ /**
+ * Get the value of the cookie.
+ *
+ * @return the cookie value.
+ */
+ public String getValue() {
+ return null;
+ }
+
+ /**
+ * Get the version of the cookie.
+ *
+ * @return the cookie version.
+ */
+ public int getVersion() {
+ return -1;
+ }
+
+ /**
+ * Get the domain of the cookie.
+ *
+ * @return the cookie domain.
+ */
+ public String getDomain() {
+ return null;
+ }
+
+ /**
+ * Get the path of the cookie.
+ *
+ * @return the cookie path.
+ */
+ public String getPath() {
+ return null;
+ }
+
+ /**
+ * Convert the cookie to a string suitable for use as the value of the
+ * corresponding HTTP header.
+ *
+ * @return a stringified cookie.
+ */
+ @Override
+ public String toString() {
+ return null;
+ }
+
+ /**
+ * Generate a hash code by hashing all of the cookies properties.
+ *
+ * @return the cookie hash code.
+ */
+ @Override
+ public int hashCode() {
+ return -1;
+ }
+
+ /**
+ * Compare for equality.
+ *
+ * @param obj the object to compare to.
+ * @return {@code true}, if the object is a {@code Cookie} with the same
+ * value for all properties, {@code false} otherwise.
+ */
+ @SuppressWarnings({"StringEquality", "RedundantIfStatement"})
+ @Override
+ public boolean equals(final Object obj) {
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/NewCookie.java b/java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/NewCookie.java
new file mode 100644
index 000000000000..43a4899e0ca3
--- /dev/null
+++ b/java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/NewCookie.java
@@ -0,0 +1,359 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright (c) 2010-2015 Oracle and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * http://glassfish.java.net/public/CDDL+GPL_1_1.html
+ * or packager/legal/LICENSE.txt. See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at packager/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * Oracle designates this particular file as subject to the "Classpath"
+ * exception as provided by Oracle in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+
+package javax.ws.rs.core;
+
+import java.util.Date;
+
+/**
+ * Used to create a new HTTP cookie, transferred in a response.
+ *
+ * @author Paul Sandoz
+ * @author Marc Hadley
+ * @see IETF RFC 2109
+ * @since 1.0
+ */
+public class NewCookie extends Cookie {
+
+ /**
+ * Specifies that the cookie expires with the current application/browser session.
+ */
+ public static final int DEFAULT_MAX_AGE = -1;
+
+ private final String comment;
+ private final int maxAge;
+ private final Date expiry;
+ private final boolean secure;
+ private final boolean httpOnly;
+
+ /**
+ * Create a new instance.
+ *
+ * @param name the name of the cookie.
+ * @param value the value of the cookie.
+ * @throws IllegalArgumentException if name is {@code null}.
+ */
+ public NewCookie(String name, String value) {
+ this(name, value, null, null, DEFAULT_VERSION, null, DEFAULT_MAX_AGE, null, false, false);
+ }
+
+ /**
+ * Create a new instance.
+ *
+ * @param name the name of the cookie.
+ * @param value the value of the cookie.
+ * @param path the URI path for which the cookie is valid.
+ * @param domain the host domain for which the cookie is valid.
+ * @param comment the comment.
+ * @param maxAge the maximum age of the cookie in seconds.
+ * @param secure specifies whether the cookie will only be sent over a secure connection.
+ * @throws IllegalArgumentException if name is {@code null}.
+ */
+ public NewCookie(String name,
+ String value,
+ String path,
+ String domain,
+ String comment,
+ int maxAge,
+ boolean secure) {
+ this(name, value, path, domain, DEFAULT_VERSION, comment, maxAge, null, secure, false);
+ }
+
+ /**
+ * Create a new instance.
+ *
+ * @param name the name of the cookie.
+ * @param value the value of the cookie.
+ * @param path the URI path for which the cookie is valid.
+ * @param domain the host domain for which the cookie is valid.
+ * @param comment the comment.
+ * @param maxAge the maximum age of the cookie in seconds.
+ * @param secure specifies whether the cookie will only be sent over a secure connection.
+ * @param httpOnly if {@code true} make the cookie HTTP only, i.e. only visible as part of an HTTP request.
+ * @throws IllegalArgumentException if name is {@code null}.
+ * @since 2.0
+ */
+ public NewCookie(String name,
+ String value,
+ String path,
+ String domain,
+ String comment,
+ int maxAge,
+ boolean secure,
+ boolean httpOnly) {
+ this(name, value, path, domain, DEFAULT_VERSION, comment, maxAge, null, secure, httpOnly);
+ }
+
+ /**
+ * Create a new instance.
+ *
+ * @param name the name of the cookie
+ * @param value the value of the cookie
+ * @param path the URI path for which the cookie is valid
+ * @param domain the host domain for which the cookie is valid
+ * @param version the version of the specification to which the cookie complies
+ * @param comment the comment
+ * @param maxAge the maximum age of the cookie in seconds
+ * @param secure specifies whether the cookie will only be sent over a secure connection
+ * @throws IllegalArgumentException if name is {@code null}.
+ */
+ public NewCookie(String name,
+ String value,
+ String path,
+ String domain,
+ int version,
+ String comment,
+ int maxAge,
+ boolean secure) {
+ this(name, value, path, domain, version, comment, maxAge, null, secure, false);
+ }
+
+ /**
+ * Create a new instance.
+ *
+ * @param name the name of the cookie
+ * @param value the value of the cookie
+ * @param path the URI path for which the cookie is valid
+ * @param domain the host domain for which the cookie is valid
+ * @param version the version of the specification to which the cookie complies
+ * @param comment the comment
+ * @param maxAge the maximum age of the cookie in seconds
+ * @param expiry the cookie expiry date.
+ * @param secure specifies whether the cookie will only be sent over a secure connection
+ * @param httpOnly if {@code true} make the cookie HTTP only, i.e. only visible as part of an HTTP request.
+ * @throws IllegalArgumentException if name is {@code null}.
+ * @since 2.0
+ */
+ public NewCookie(String name,
+ String value,
+ String path,
+ String domain,
+ int version,
+ String comment,
+ int maxAge,
+ Date expiry,
+ boolean secure,
+ boolean httpOnly) {
+ super(name, value, path, domain, version);
+ this.comment = comment;
+ this.maxAge = maxAge;
+ this.expiry = expiry;
+ this.secure = secure;
+ this.httpOnly = httpOnly;
+ }
+
+ /**
+ * Create a new instance copying the information in the supplied cookie.
+ *
+ * @param cookie the cookie to clone.
+ * @throws IllegalArgumentException if cookie is {@code null}.
+ */
+ public NewCookie(Cookie cookie) {
+ this(cookie, null, DEFAULT_MAX_AGE, null, false, false);
+ }
+
+ /**
+ * Create a new instance supplementing the information in the supplied cookie.
+ *
+ * @param cookie the cookie to clone.
+ * @param comment the comment.
+ * @param maxAge the maximum age of the cookie in seconds.
+ * @param secure specifies whether the cookie will only be sent over a secure connection.
+ * @throws IllegalArgumentException if cookie is {@code null}.
+ */
+ public NewCookie(Cookie cookie, String comment, int maxAge, boolean secure) {
+ this(cookie, comment, maxAge, null, secure, false);
+ }
+
+ /**
+ * Create a new instance supplementing the information in the supplied cookie.
+ *
+ * @param cookie the cookie to clone.
+ * @param comment the comment.
+ * @param maxAge the maximum age of the cookie in seconds.
+ * @param expiry the cookie expiry date.
+ * @param secure specifies whether the cookie will only be sent over a secure connection.
+ * @param httpOnly if {@code true} make the cookie HTTP only, i.e. only visible as part of an HTTP request.
+ * @throws IllegalArgumentException if cookie is {@code null}.
+ * @since 2.0
+ */
+ public NewCookie(Cookie cookie, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly) {
+ super(cookie == null ? null : cookie.getName(),
+ cookie == null ? null : cookie.getValue(),
+ cookie == null ? null : cookie.getPath(),
+ cookie == null ? null : cookie.getDomain(),
+ cookie == null ? Cookie.DEFAULT_VERSION : cookie.getVersion());
+ this.comment = comment;
+ this.maxAge = maxAge;
+ this.expiry = expiry;
+ this.secure = secure;
+ this.httpOnly = httpOnly;
+ }
+
+ /**
+ * Creates a new instance of NewCookie by parsing the supplied string.
+ *
+ * @param value the cookie string.
+ * @return the newly created {@code NewCookie}.
+ * @throws IllegalArgumentException if the supplied string cannot be parsed
+ * or is {@code null}.
+ */
+ public static NewCookie valueOf(String value) {
+ return null;
+ }
+
+ /**
+ * Get the comment associated with the cookie.
+ *
+ * @return the comment or null if none set
+ */
+ public String getComment() {
+ return null;
+ }
+
+ /**
+ * Get the maximum age of the the cookie in seconds. Cookies older than
+ * the maximum age are discarded. A cookie can be unset by sending a new
+ * cookie with maximum age of 0 since it will overwrite any existing cookie
+ * and then be immediately discarded. The default value of {@code -1} indicates
+ * that the cookie will be discarded at the end of the browser/application session.
+ *
+ * Note that it is recommended to use {@code Max-Age} to control cookie
+ * expiration, however some browsers do not understand {@code Max-Age}, in which case
+ * setting {@link #getExpiry()} Expires} parameter may be necessary.
+ *
+ *
+ * @return the maximum age in seconds.
+ * @see #getExpiry()
+ */
+ public int getMaxAge() {
+ return -1;
+ }
+
+ /**
+ * Get the cookie expiry date. Cookies whose expiry date has passed are discarded.
+ * A cookie can be unset by setting a new cookie with an expiry date in the past,
+ * typically the lowest possible date that can be set.
+ *
+ * Note that it is recommended to use {@link #getMaxAge() Max-Age} to control cookie
+ * expiration, however some browsers do not understand {@code Max-Age}, in which case
+ * setting {@code Expires} parameter may be necessary.
+ *
+ *
+ * @return cookie expiry date or {@code null} if no expiry date was set.
+ * @see #getMaxAge()
+ * @since 2.0
+ */
+ public Date getExpiry() {
+ return null;
+ }
+
+ /**
+ * Whether the cookie will only be sent over a secure connection. Defaults
+ * to {@code false}.
+ *
+ * @return {@code true} if the cookie will only be sent over a secure connection,
+ * {@code false} otherwise.
+ */
+ public boolean isSecure() {
+ return false;
+ }
+
+ /**
+ * Returns {@code true} if this cookie contains the {@code HttpOnly} attribute.
+ * This means that the cookie should not be accessible to scripting engines,
+ * like javascript.
+ *
+ * @return {@code true} if this cookie should be considered http only, {@code false}
+ * otherwise.
+ * @since 2.0
+ */
+ public boolean isHttpOnly() {
+ return false;
+ }
+
+ /**
+ * Obtain a new instance of a {@link Cookie} with the same name, value, path,
+ * domain and version as this {@code NewCookie}. This method can be used to
+ * obtain an object that can be compared for equality with another {@code Cookie};
+ * since a {@code Cookie} will never compare equal to a {@code NewCookie}.
+ *
+ * @return a {@link Cookie}
+ */
+ public Cookie toCookie() {
+ return null;
+ }
+
+ /**
+ * Convert the cookie to a string suitable for use as the value of the
+ * corresponding HTTP header.
+ *
+ * @return a stringified cookie.
+ */
+ @Override
+ public String toString() {
+ return null;
+ }
+
+ /**
+ * Generate a hash code by hashing all of the properties.
+ *
+ * @return the hash code.
+ */
+ @Override
+ public int hashCode() {
+ return -1;
+ }
+
+ /**
+ * Compare for equality. Use {@link #toCookie()} to compare a
+ * {@code NewCookie} to a {@code Cookie} considering only the common
+ * properties.
+ *
+ * @param obj the object to compare to
+ * @return true if the object is a {@code NewCookie} with the same value for
+ * all properties, false otherwise.
+ */
+ @SuppressWarnings({"StringEquality", "RedundantIfStatement"})
+ @Override
+ public boolean equals(Object obj) {
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/java/ql/test/stubs/servlet-api-2.4/javax/servlet/http/Cookie.java b/java/ql/test/stubs/servlet-api-2.4/javax/servlet/http/Cookie.java
index d8348e13e2ef..a93fec853e06 100644
--- a/java/ql/test/stubs/servlet-api-2.4/javax/servlet/http/Cookie.java
+++ b/java/ql/test/stubs/servlet-api-2.4/javax/servlet/http/Cookie.java
@@ -32,6 +32,7 @@ public class Cookie implements Cloneable {
private String path; // ;Path=VALUE ... URLs that see the cookie
private boolean secure; // ;Secure ... e.g. use SSL
private int version = 0; // ;Version=1 ... means RFC 2109++ style
+ private boolean isHttpOnly = false;
public Cookie(String name, String value) {
this.name = name;
@@ -81,4 +82,36 @@ public int getVersion() {
}
public void setVersion(int v) {
}
+
+ /**
+ * Marks or unmarks this Cookie as HttpOnly.
+ *
+ * If isHttpOnly is set to true, this cookie is
+ * marked as HttpOnly, by adding the HttpOnly attribute
+ * to it.
+ *
+ *
HttpOnly cookies are not supposed to be exposed to
+ * client-side scripting code, and may therefore help mitigate certain
+ * kinds of cross-site scripting attacks.
+ *
+ * @param isHttpOnly true if this cookie is to be marked as
+ * HttpOnly, false otherwise
+ *
+ * @since Servlet 3.0
+ */
+ public void setHttpOnly(boolean isHttpOnly) {
+ this.isHttpOnly = isHttpOnly;
+ }
+
+ /**
+ * Checks whether this Cookie has been marked as HttpOnly.
+ *
+ * @return true if this Cookie has been marked as HttpOnly,
+ * false otherwise
+ *
+ * @since Servlet 3.0
+ */
+ public boolean isHttpOnly() {
+ return isHttpOnly;
+ }
}
diff --git a/java/ql/test/stubs/servlet-api-2.4/javax/servlet/http/HttpServletResponse.java b/java/ql/test/stubs/servlet-api-2.4/javax/servlet/http/HttpServletResponse.java
index 2971e0233902..162ac0db3cc3 100644
--- a/java/ql/test/stubs/servlet-api-2.4/javax/servlet/http/HttpServletResponse.java
+++ b/java/ql/test/stubs/servlet-api-2.4/javax/servlet/http/HttpServletResponse.java
@@ -24,6 +24,7 @@
package javax.servlet.http;
import java.io.IOException;
+import java.util.Collection;
import javax.servlet.ServletResponse;
public interface HttpServletResponse extends ServletResponse {
@@ -44,6 +45,11 @@ public interface HttpServletResponse extends ServletResponse {
public void addIntHeader(String name, int value);
public void setStatus(int sc);
public void setStatus(int sc, String sm);
+ public int getStatus();
+ public String getHeader(String name);
+ public Collection getHeaders(String name);
+ public Collection getHeaderNames();
+
public static final int SC_CONTINUE = 100;
public static final int SC_SWITCHING_PROTOCOLS = 101;
From b366ffa69e98e21ac8998bb08a8815fc4cae6b4f Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Wed, 3 Mar 2021 13:38:18 +0000
Subject: [PATCH 009/433] Revamp source of the query
---
.../CWE-1004/SensitiveCookieNotHttpOnly.qhelp | 2 +-
.../CWE-1004/SensitiveCookieNotHttpOnly.ql | 116 +++++++++---------
.../SensitiveCookieNotHttpOnly.expected | 12 +-
.../javax/ws/rs/core/Cookie.java | 59 +++------
.../javax/ws/rs/core/NewCookie.java | 36 ------
5 files changed, 82 insertions(+), 143 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.qhelp b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.qhelp
index 880ed767be98..ee3e8a4181a9 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.qhelp
+++ b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.qhelp
@@ -2,7 +2,7 @@
- Cross-Site Scripting (XSS) is categorized as one of the OWASP Top 10 Security Vulnerabilities. The HttpOnly
flag directs compatible browsers to prevent client-side script from accessing cookies. Including the HttpOnly flag in the Set-Cookie HTTP response header for a sensitive cookie helps mitigate the risk associated with XSS where an attacker's script code attempts to read the contents of a cookie and exfiltrate information obtained.
+ Cross-Site Scripting (XSS) is categorized as one of the OWASP Top 10 Security Vulnerabilities. The HttpOnly
flag directs compatible browsers to prevent client-side script from accessing cookies. Including the HttpOnly
flag in the Set-Cookie HTTP response header for a sensitive cookie helps mitigate the risk associated with XSS where an attacker's script code attempts to read the contents of a cookie and exfiltrate information obtained.
diff --git a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
index bf4e60134c9a..842e8b7eabbd 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
@@ -9,7 +9,6 @@
import java
import semmle.code.java.frameworks.Servlets
-import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking
import DataFlow::PathGraph
@@ -18,8 +17,8 @@ string getSensitiveCookieNameRegex() { result = "(?i).*(auth|session|token|key|c
/** Holds if a string is concatenated with the name of a sensitive cookie. */
predicate isSensitiveCookieNameExpr(Expr expr) {
- expr.(StringLiteral)
- .getRepresentedString()
+ expr.(CompileTimeConstantExpr)
+ .getStringValue()
.toLowerCase()
.regexpMatch(getSensitiveCookieNameRegex()) or
isSensitiveCookieNameExpr(expr.(AddExpr).getAnOperand())
@@ -27,7 +26,7 @@ predicate isSensitiveCookieNameExpr(Expr expr) {
/** Holds if a string is concatenated with the `HttpOnly` flag. */
predicate hasHttpOnlyExpr(Expr expr) {
- expr.(StringLiteral).getRepresentedString().toLowerCase().matches("%httponly%") or
+ expr.(CompileTimeConstantExpr).getStringValue().toLowerCase().matches("%httponly%") or
hasHttpOnlyExpr(expr.(AddExpr).getAnOperand())
}
@@ -38,37 +37,34 @@ class SetCookieMethodAccess extends MethodAccess {
this.getMethod() instanceof ResponseAddHeaderMethod or
this.getMethod() instanceof ResponseSetHeaderMethod
) and
- this.getArgument(0).(StringLiteral).getRepresentedString().toLowerCase() = "set-cookie"
+ this.getArgument(0).(CompileTimeConstantExpr).getStringValue().toLowerCase() = "set-cookie"
}
}
/** Sensitive cookie name used in a `Cookie` constructor or a `Set-Cookie` call. */
class SensitiveCookieNameExpr extends Expr {
SensitiveCookieNameExpr() {
- isSensitiveCookieNameExpr(this) and
- (
- exists(
- ClassInstanceExpr cie // new Cookie("jwt_token", token)
- |
- (
- cie.getConstructor().getDeclaringType().hasQualifiedName("javax.servlet.http", "Cookie") or
- cie.getConstructor()
- .getDeclaringType()
- .getASupertype*()
- .hasQualifiedName("javax.ws.rs.core", "Cookie") or
- cie.getConstructor()
- .getDeclaringType()
- .getASupertype*()
- .hasQualifiedName("jakarta.ws.rs.core", "Cookie")
- ) and
- DataFlow::localExprFlow(this, cie.getArgument(0))
- )
- or
- exists(
- SetCookieMethodAccess ma // response.addHeader("Set-Cookie: token=" +authId + ";HttpOnly;Secure")
- |
- DataFlow::localExprFlow(this, ma.getArgument(1))
- )
+ exists(
+ ClassInstanceExpr cie, Expr e // new Cookie("jwt_token", token)
+ |
+ (
+ cie.getConstructor().getDeclaringType().hasQualifiedName("javax.servlet.http", "Cookie") or
+ cie.getConstructor()
+ .getDeclaringType()
+ .getASupertype*()
+ .hasQualifiedName(["javax.ws.rs.core", "jakarta.ws.rs.core"], "Cookie")
+ ) and
+ this = cie and
+ isSensitiveCookieNameExpr(e) and
+ DataFlow::localExprFlow(e, cie.getArgument(0))
+ )
+ or
+ exists(
+ SetCookieMethodAccess ma, Expr e // response.addHeader("Set-Cookie: token=" +authId + ";HttpOnly;Secure")
+ |
+ this = ma.getArgument(1) and
+ isSensitiveCookieNameExpr(e) and
+ DataFlow::localExprFlow(e, ma.getArgument(1))
)
}
}
@@ -78,15 +74,20 @@ class CookieResponseSink extends DataFlow::ExprNode {
CookieResponseSink() {
exists(MethodAccess ma |
(
- ma.getMethod() instanceof ResponseAddCookieMethod or
- ma instanceof SetCookieMethodAccess
- ) and
- ma.getAnArgument() = this.getExpr()
+ ma.getMethod() instanceof ResponseAddCookieMethod and
+ this.getExpr() = ma.getArgument(0)
+ or
+ ma instanceof SetCookieMethodAccess and
+ this.getExpr() = ma.getArgument(1)
+ )
)
}
}
-/** Holds if the `node` is a method call of `setHttpOnly(true)` on a cookie. */
+/**
+ * Holds if `node` is an access to a variable which has `setHttpOnly(true)` called on it and is also
+ * the first argument to a call to the method `addCookie` of `javax.servlet.http.HttpServletResponse`.
+ */
predicate setHttpOnlyMethodAccess(DataFlow::Node node) {
exists(
MethodAccess addCookie, Variable cookie, MethodAccess m // jwtCookie.setHttpOnly(true)
@@ -100,7 +101,10 @@ predicate setHttpOnlyMethodAccess(DataFlow::Node node) {
)
}
-/** Holds if the `node` is a method call of `Set-Cookie` header with the `HttpOnly` flag whose cookie name is sensitive. */
+/**
+ * Holds if `node` is a string that contains `httponly` and which flows to the second argument
+ * of a method to set a cookie.
+ */
predicate setHttpOnlyInSetCookie(DataFlow::Node node) {
exists(SetCookieMethodAccess sa |
hasHttpOnlyExpr(node.asExpr()) and
@@ -108,20 +112,17 @@ predicate setHttpOnlyInSetCookie(DataFlow::Node node) {
)
}
-/** Holds if the `node` is an invocation of a JAX-RS `NewCookie` constructor that sets `HttpOnly` to true. */
-predicate setHttpOnlyInNewCookie(DataFlow::Node node) {
- exists(ClassInstanceExpr cie |
- cie.getConstructor().getDeclaringType().hasName("NewCookie") and
- DataFlow::localExprFlow(node.asExpr(), cie.getArgument(0)) and
- (
- cie.getNumArgument() = 6 and cie.getArgument(5).(BooleanLiteral).getBooleanValue() = true // NewCookie(Cookie cookie, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
- or
- cie.getNumArgument() = 8 and
- cie.getArgument(6).getType() instanceof BooleanType and
- cie.getArgument(7).(BooleanLiteral).getBooleanValue() = true // NewCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly)
- or
- cie.getNumArgument() = 10 and cie.getArgument(9).(BooleanLiteral).getBooleanValue() = true // NewCookie(String name, String value, String path, String domain, int version, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
- )
+/** Holds if `cie` is an invocation of a JAX-RS `NewCookie` constructor that sets `HttpOnly` to true. */
+predicate setHttpOnlyInNewCookie(ClassInstanceExpr cie) {
+ cie.getConstructedType().hasQualifiedName(["javax.ws.rs.core", "jakarta.ws.rs.core"], "NewCookie") and
+ (
+ cie.getNumArgument() = 6 and cie.getArgument(5).(BooleanLiteral).getBooleanValue() = true // NewCookie(Cookie cookie, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
+ or
+ cie.getNumArgument() = 8 and
+ cie.getArgument(6).getType() instanceof BooleanType and
+ cie.getArgument(7).(BooleanLiteral).getBooleanValue() = true // NewCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly)
+ or
+ cie.getNumArgument() = 10 and cie.getArgument(9).(BooleanLiteral).getBooleanValue() = true // NewCookie(String name, String value, String path, String domain, int version, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
)
}
@@ -145,7 +146,10 @@ predicate isTestMethod(DataFlow::Node node) {
)
}
-/** A taint configuration tracking flow from a sensitive cookie without HttpOnly flag set to its HTTP response. */
+/**
+ * A taint configuration tracking flow from a sensitive cookie without the `HttpOnly` flag
+ * set to its HTTP response.
+ */
class MissingHttpOnlyConfiguration extends TaintTracking::Configuration {
MissingHttpOnlyConfiguration() { this = "MissingHttpOnlyConfiguration" }
@@ -159,25 +163,17 @@ class MissingHttpOnlyConfiguration extends TaintTracking::Configuration {
// cookie.setHttpOnly(true)
setHttpOnlyMethodAccess(node)
or
- // response.addHeader("Set-Cookie: token=" +authId + ";HttpOnly;Secure")
+ // response.addHeader("Set-Cookie", "token=" +authId + ";HttpOnly;Secure")
setHttpOnlyInSetCookie(node)
or
// new NewCookie("session-access-key", accessKey, "/", null, null, 0, true, true)
- setHttpOnlyInNewCookie(node)
+ setHttpOnlyInNewCookie(node.asExpr())
or
// Test class or method
isTestMethod(node)
}
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
- exists(
- ClassInstanceExpr cie // `NewCookie` constructor
- |
- cie.getAnArgument() = pred.asExpr() and
- cie = succ.asExpr() and
- cie.getConstructor().getDeclaringType().hasName("NewCookie")
- )
- or
exists(
MethodAccess ma // `toString` call on a cookie object
|
diff --git a/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected
index 84d6be3863ac..e2d05a9b24d8 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected
@@ -1,13 +1,13 @@
edges
-| SensitiveCookieNotHttpOnly.java:22:38:22:48 | "jwt_token" : String | SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie |
-| SensitiveCookieNotHttpOnly.java:49:56:49:75 | "session-access-key" : String | SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) |
+| SensitiveCookieNotHttpOnly.java:22:27:22:60 | new Cookie(...) : Cookie | SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie |
+| SensitiveCookieNotHttpOnly.java:49:42:49:113 | new NewCookie(...) : NewCookie | SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) |
nodes
-| SensitiveCookieNotHttpOnly.java:22:38:22:48 | "jwt_token" : String | semmle.label | "jwt_token" : String |
+| SensitiveCookieNotHttpOnly.java:22:27:22:60 | new Cookie(...) : Cookie | semmle.label | new Cookie(...) : Cookie |
| SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie | semmle.label | jwtCookie |
| SensitiveCookieNotHttpOnly.java:39:42:39:69 | ... + ... | semmle.label | ... + ... |
+| SensitiveCookieNotHttpOnly.java:49:42:49:113 | new NewCookie(...) : NewCookie | semmle.label | new NewCookie(...) : NewCookie |
| SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) | semmle.label | toString(...) |
-| SensitiveCookieNotHttpOnly.java:49:56:49:75 | "session-access-key" : String | semmle.label | "session-access-key" : String |
#select
-| SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie | SensitiveCookieNotHttpOnly.java:22:38:22:48 | "jwt_token" : String | SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:22:38:22:48 | "jwt_token" | This sensitive cookie |
+| SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie | SensitiveCookieNotHttpOnly.java:22:27:22:60 | new Cookie(...) : Cookie | SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:22:27:22:60 | new Cookie(...) | This sensitive cookie |
| SensitiveCookieNotHttpOnly.java:39:42:39:69 | ... + ... | SensitiveCookieNotHttpOnly.java:39:42:39:69 | ... + ... | SensitiveCookieNotHttpOnly.java:39:42:39:69 | ... + ... | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:39:42:39:69 | ... + ... | This sensitive cookie |
-| SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) | SensitiveCookieNotHttpOnly.java:49:56:49:75 | "session-access-key" : String | SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:49:56:49:75 | "session-access-key" | This sensitive cookie |
+| SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) | SensitiveCookieNotHttpOnly.java:49:42:49:113 | new NewCookie(...) : NewCookie | SensitiveCookieNotHttpOnly.java:49:42:49:124 | toString(...) | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:49:42:49:113 | new NewCookie(...) | This sensitive cookie |
diff --git a/java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/Cookie.java b/java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/Cookie.java
index 4e4c7585c35d..f810600a7660 100644
--- a/java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/Cookie.java
+++ b/java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/Cookie.java
@@ -51,10 +51,16 @@
* @since 1.0
*/
public class Cookie {
+
/**
* Cookies using the default version correspond to RFC 2109.
*/
public static final int DEFAULT_VERSION = 1;
+ private final String name;
+ private final String value;
+ private final int version;
+ private final String path;
+ private final String domain;
/**
* Create a new instance.
@@ -68,6 +74,11 @@ public class Cookie {
*/
public Cookie(final String name, final String value, final String path, final String domain, final int version)
throws IllegalArgumentException {
+ this.name = name;
+ this.value = value;
+ this.version = version;
+ this.domain = domain;
+ this.path = path;
}
/**
@@ -81,6 +92,7 @@ public Cookie(final String name, final String value, final String path, final St
*/
public Cookie(final String name, final String value, final String path, final String domain)
throws IllegalArgumentException {
+ this(name, value, path, domain, DEFAULT_VERSION);
}
/**
@@ -92,6 +104,7 @@ public Cookie(final String name, final String value, final String path, final St
*/
public Cookie(final String name, final String value)
throws IllegalArgumentException {
+ this(name, value, null, null);
}
/**
@@ -112,7 +125,7 @@ public static Cookie valueOf(final String value) {
* @return the cookie name.
*/
public String getName() {
- return null;
+ return name;
}
/**
@@ -121,7 +134,7 @@ public String getName() {
* @return the cookie value.
*/
public String getValue() {
- return null;
+ return value;
}
/**
@@ -130,7 +143,7 @@ public String getValue() {
* @return the cookie version.
*/
public int getVersion() {
- return -1;
+ return version;
}
/**
@@ -139,7 +152,7 @@ public int getVersion() {
* @return the cookie domain.
*/
public String getDomain() {
- return null;
+ return domain;
}
/**
@@ -148,40 +161,6 @@ public String getDomain() {
* @return the cookie path.
*/
public String getPath() {
- return null;
- }
-
- /**
- * Convert the cookie to a string suitable for use as the value of the
- * corresponding HTTP header.
- *
- * @return a stringified cookie.
- */
- @Override
- public String toString() {
- return null;
- }
-
- /**
- * Generate a hash code by hashing all of the cookies properties.
- *
- * @return the cookie hash code.
- */
- @Override
- public int hashCode() {
- return -1;
- }
-
- /**
- * Compare for equality.
- *
- * @param obj the object to compare to.
- * @return {@code true}, if the object is a {@code Cookie} with the same
- * value for all properties, {@code false} otherwise.
- */
- @SuppressWarnings({"StringEquality", "RedundantIfStatement"})
- @Override
- public boolean equals(final Object obj) {
- return true;
+ return path;
}
-}
\ No newline at end of file
+}
diff --git a/java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/NewCookie.java b/java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/NewCookie.java
index 43a4899e0ca3..7f2e3ec05351 100644
--- a/java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/NewCookie.java
+++ b/java/ql/test/stubs/jsr311-api-1.1.1/javax/ws/rs/core/NewCookie.java
@@ -320,40 +320,4 @@ public boolean isHttpOnly() {
public Cookie toCookie() {
return null;
}
-
- /**
- * Convert the cookie to a string suitable for use as the value of the
- * corresponding HTTP header.
- *
- * @return a stringified cookie.
- */
- @Override
- public String toString() {
- return null;
- }
-
- /**
- * Generate a hash code by hashing all of the properties.
- *
- * @return the hash code.
- */
- @Override
- public int hashCode() {
- return -1;
- }
-
- /**
- * Compare for equality. Use {@link #toCookie()} to compare a
- * {@code NewCookie} to a {@code Cookie} considering only the common
- * properties.
- *
- * @param obj the object to compare to
- * @return true if the object is a {@code NewCookie} with the same value for
- * all properties, false otherwise.
- */
- @SuppressWarnings({"StringEquality", "RedundantIfStatement"})
- @Override
- public boolean equals(Object obj) {
- return true;
- }
}
\ No newline at end of file
From 1b1c3f953b9ebb828e161e4b36d67075a7bca510 Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Wed, 3 Mar 2021 13:54:26 +0000
Subject: [PATCH 010/433] Remove localflow from the source
---
.../CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
index 842e8b7eabbd..258b2545b277 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
@@ -45,7 +45,7 @@ class SetCookieMethodAccess extends MethodAccess {
class SensitiveCookieNameExpr extends Expr {
SensitiveCookieNameExpr() {
exists(
- ClassInstanceExpr cie, Expr e // new Cookie("jwt_token", token)
+ ClassInstanceExpr cie // new Cookie("jwt_token", token)
|
(
cie.getConstructor().getDeclaringType().hasQualifiedName("javax.servlet.http", "Cookie") or
@@ -55,16 +55,14 @@ class SensitiveCookieNameExpr extends Expr {
.hasQualifiedName(["javax.ws.rs.core", "jakarta.ws.rs.core"], "Cookie")
) and
this = cie and
- isSensitiveCookieNameExpr(e) and
- DataFlow::localExprFlow(e, cie.getArgument(0))
+ isSensitiveCookieNameExpr(cie.getArgument(0))
)
or
exists(
- SetCookieMethodAccess ma, Expr e // response.addHeader("Set-Cookie: token=" +authId + ";HttpOnly;Secure")
+ SetCookieMethodAccess ma // response.addHeader("Set-Cookie: token=" +authId + ";HttpOnly;Secure")
|
this = ma.getArgument(1) and
- isSensitiveCookieNameExpr(e) and
- DataFlow::localExprFlow(e, ma.getArgument(1))
+ isSensitiveCookieNameExpr(this)
)
}
}
From 502cf38fccef4c0b2a1120f2fe175571ec1b9cb5 Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Wed, 3 Mar 2021 14:07:43 +0000
Subject: [PATCH 011/433] Use concise API
---
.../Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
index 258b2545b277..d9104cbad970 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
@@ -48,9 +48,8 @@ class SensitiveCookieNameExpr extends Expr {
ClassInstanceExpr cie // new Cookie("jwt_token", token)
|
(
- cie.getConstructor().getDeclaringType().hasQualifiedName("javax.servlet.http", "Cookie") or
- cie.getConstructor()
- .getDeclaringType()
+ cie.getConstructedType().hasQualifiedName("javax.servlet.http", "Cookie") or
+ cie.getConstructedType()
.getASupertype*()
.hasQualifiedName(["javax.ws.rs.core", "jakarta.ws.rs.core"], "Cookie")
) and
From 86dde6eab16f9f28cc6bb68c8b2ed7fa8c40e3bd Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Wed, 17 Feb 2021 11:34:05 +0100
Subject: [PATCH 012/433] Python: start of port
---
.../src/Security/CWE-327/InsecureProtocol.ql | 299 +++++++++++++++++-
.../examples/secure_default_protocol.py | 13 +
.../CWE-327/examples/secure_protocol.py | 11 +
3 files changed, 307 insertions(+), 16 deletions(-)
create mode 100644 python/ql/src/Security/CWE-327/examples/secure_default_protocol.py
create mode 100644 python/ql/src/Security/CWE-327/examples/secure_protocol.py
diff --git a/python/ql/src/Security/CWE-327/InsecureProtocol.ql b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
index d1ae714b6be6..6f2feedce22f 100644
--- a/python/ql/src/Security/CWE-327/InsecureProtocol.ql
+++ b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
@@ -10,7 +10,270 @@
*/
import python
+import semmle.python.ApiGraphs
+// The idea is to track flow from the creation of an insecure context to a use
+// such as `wrap_socket`. There should be a data-flow path for each insecure version
+// and each path should have a version specific sanitizer. This will allow fluent api
+// style code to block the paths one by one.
+//
+// class InsecureContextCreation extends DataFlow::CfgNode {
+// override CallNode node;
+// InsecureContextCreation() {
+// this = API::moduleImport("ssl").getMember("SSLContext").getACall() and
+// insecure_version().asCfgNode() in [node.getArg(0), node.getArgByName("protocol")]
+// }
+// }
+// class InsecureSSLContextCreation extends DataFlow::CfgNode {
+// override CallNode node;
+// InsecureSSLContextCreation() {
+// this = API::moduleImport("ssl").getMember("create_default_context").getACall()
+// or
+// this = API::moduleImport("ssl").getMember("SSLContext").getACall() and
+// API::moduleImport("ssl").getMember("PROTOCOL_TLS").getAUse().asCfgNode() in [
+// node.getArg(0), node.getArgByName("protocol")
+// ]
+// }
+// }
+abstract class ContextCreation extends DataFlow::CfgNode {
+ abstract DataFlow::CfgNode getProtocol();
+}
+
+class SSLContextCreation extends ContextCreation {
+ override CallNode node;
+
+ SSLContextCreation() { this = API::moduleImport("ssl").getMember("SSLContext").getACall() }
+
+ override DataFlow::CfgNode getProtocol() {
+ result.getNode() in [node.getArg(0), node.getArgByName("protocol")]
+ }
+}
+
+class PyOpenSSLContextCreation extends ContextCreation {
+ override CallNode node;
+
+ PyOpenSSLContextCreation() {
+ this = API::moduleImport("pyOpenSSL").getMember("SSL").getMember("Context").getACall()
+ }
+
+ override DataFlow::CfgNode getProtocol() {
+ result.getNode() in [node.getArg(0), node.getArgByName("method")]
+ }
+}
+
+abstract class ConnectionCreation extends DataFlow::CfgNode {
+ abstract DataFlow::CfgNode getContext();
+}
+
+class WrapSocketCall extends ConnectionCreation {
+ override CallNode node;
+
+ WrapSocketCall() { node.getFunction().(AttrNode).getName() = "wrap_socket" }
+
+ override DataFlow::CfgNode getContext() {
+ result.getNode() = node.getFunction().(AttrNode).getObject()
+ }
+}
+
+class ConnectionCall extends ConnectionCreation {
+ override CallNode node;
+
+ ConnectionCall() {
+ this = API::moduleImport("pyOpenSSL").getMember("SSL").getMember("Connection").getACall()
+ }
+
+ override DataFlow::CfgNode getContext() {
+ result.getNode() in [node.getArg(0), node.getArgByName("context")]
+ }
+}
+
+abstract class TlsLibrary extends string {
+ TlsLibrary() { this in ["ssl"] }
+
+ abstract string specific_insecure_version_name();
+
+ abstract string unspecific_version_name();
+
+ abstract API::Node version_constants();
+
+ DataFlow::Node insecure_version() {
+ result = version_constants().getMember(specific_insecure_version_name()).getAUse()
+ }
+
+ DataFlow::Node unspecific_version() {
+ result = version_constants().getMember(unspecific_version_name()).getAUse()
+ }
+
+ abstract DataFlow::CfgNode default_context_creation();
+
+ abstract ContextCreation specific_context_creation();
+
+ ContextCreation insecure_context_creation() {
+ result = specific_context_creation() and
+ result.getProtocol() = insecure_version()
+ }
+
+ DataFlow::CfgNode unspecific_context_creation() {
+ result = default_context_creation()
+ or
+ result = specific_context_creation() and
+ result.(ContextCreation).getProtocol() = unspecific_version()
+ }
+
+ abstract ConnectionCreation connection_creation();
+}
+
+class Ssl extends TlsLibrary {
+ Ssl() { this = "ssl" }
+
+ override string specific_insecure_version_name() {
+ result in [
+ "PROTOCOL_SSLv2", "PROTOCOL_SSLv3", "PROTOCOL_SSLv23", "PROTOCOL_TLSv1", "PROTOCOL_TLSv1_1"
+ ]
+ }
+
+ override string unspecific_version_name() { result = "PROTOCOL_TLS" }
+
+ override API::Node version_constants() { result = API::moduleImport("ssl") }
+
+ override DataFlow::CfgNode default_context_creation() {
+ result = API::moduleImport("ssl").getMember("create_default_context").getACall()
+ }
+
+ override ContextCreation specific_context_creation() { result instanceof SSLContextCreation }
+
+ override ConnectionCreation connection_creation() { result instanceof WrapSocketCall }
+}
+
+class PyOpenSSL extends TlsLibrary {
+ PyOpenSSL() { this = "pyOpenSSL" }
+
+ override string specific_insecure_version_name() {
+ result in ["SSLv2_METHOD", "SSLv23_METHOD", "SSLv3_METHOD", "TLSv1_METHOD", "TLSv1_1_METHOD"]
+ }
+
+ override string unspecific_version_name() { result = "TLS_METHOD" }
+
+ override API::Node version_constants() {
+ result = API::moduleImport("pyOpenSSL").getMember("SSL")
+ }
+
+ override DataFlow::CfgNode default_context_creation() { none() }
+
+ override ContextCreation specific_context_creation() {
+ result instanceof PyOpenSSLContextCreation
+ }
+
+ override ConnectionCreation connection_creation() { result instanceof ConnectionCall }
+}
+
+module ssl {
+ string insecure_version_name() {
+ result = "PROTOCOL_SSLv2" or
+ result = "PROTOCOL_SSLv3" or
+ result = "PROTOCOL_SSLv23" or
+ result = "PROTOCOL_TLSv1" or
+ result = "PROTOCOL_TLSv1_1"
+ }
+
+ DataFlow::Node insecure_version() {
+ result = API::moduleImport("ssl").getMember(insecure_version_name()).getAUse()
+ }
+}
+
+module pyOpenSSL {
+ string insecure_version_name() {
+ result = "SSLv2_METHOD" or
+ result = "SSLv23_METHOD" or
+ result = "SSLv3_METHOD" or
+ result = "TLSv1_METHOD" or
+ result = "TLSv1_1_METHOD"
+ }
+
+ DataFlow::Node insecure_version() {
+ result =
+ API::moduleImport("pyOpenSSL").getMember("SSL").getMember(insecure_version_name()).getAUse()
+ }
+}
+
+class InsecureContextConfiguration extends DataFlow::Configuration {
+ TlsLibrary library;
+
+ InsecureContextConfiguration() { this = library + ["AllowsTLSv1", "AllowsTLSv1_1"] }
+
+ override predicate isSource(DataFlow::Node source) {
+ source = library.unspecific_context_creation()
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ sink = library.connection_creation().getContext()
+ }
+
+ abstract string flag();
+
+ override predicate isBarrierOut(DataFlow::Node node) {
+ exists(AugAssign aa, AttrNode attr |
+ aa.getOperation().getOp() instanceof BitOr and
+ aa.getTarget() = attr.getNode() and
+ attr.getName() = "options" and
+ attr.getObject() = node.asCfgNode() and
+ aa.getValue() = API::moduleImport("ssl").getMember(flag()).getAUse().asExpr()
+ )
+ }
+}
+
+class AllowsTLSv1 extends InsecureContextConfiguration {
+ AllowsTLSv1() { this = library + "AllowsTLSv1" }
+
+ override string flag() { result = "OP_NO_TLSv1" }
+}
+
+class AllowsTLSv1_1 extends InsecureContextConfiguration {
+ AllowsTLSv1_1() { this = library + "AllowsTLSv1_1" }
+
+ override string flag() { result = "OP_NO_TLSv1_2" }
+}
+
+predicate unsafe_connection_creation(DataFlow::Node node) {
+ exists(AllowsTLSv1 c | c.hasFlowTo(node)) or
+ exists(AllowsTLSv1_1 c | c.hasFlowTo(node)) //or
+ // node = API::moduleImport("ssl").getMember("wrap_socket").getACall()
+}
+
+predicate unsafe_context_creation(DataFlow::Node node) {
+ exists(TlsLibrary l | l.insecure_context_creation() = node)
+}
+
+// class InsecureTLSContextConfiguration extends DataFlow::Configuration {
+// InsecureTLSContextConfiguration() { this in ["AllowsTLSv1", "AllowsTLSv1_1"] }
+// override predicate isSource(DataFlow::Node source) {
+// source instanceof InsecureSSLContextCreation
+// }
+// override predicate isSink(DataFlow::Node sink) { sink = any(WrapSocketCall c).getContext() }
+// abstract string flag();
+// override predicate isBarrierOut(DataFlow::Node node) {
+// exists(AugAssign aa, AttrNode attr |
+// aa.getOperation().getOp() instanceof BitOr and
+// aa.getTarget() = attr.getNode() and
+// attr.getName() = "options" and
+// attr.getObject() = node.asCfgNode() and
+// aa.getValue() = API::moduleImport("ssl").getMember(flag()).getAUse().asExpr()
+// )
+// }
+// }
+// class AllowsTLSv1 extends InsecureTLSContextConfiguration {
+// AllowsTLSv1() { this = "AllowsTLSv1" }
+// override string flag() { result = "OP_NO_TLSv1" }
+// }
+// class AllowsTLSv1_1 extends InsecureTLSContextConfiguration {
+// AllowsTLSv1_1() { this = "AllowsTLSv1_1" }
+// override string flag() { result = "OP_NO_TLSv1_1" }
+// }
+// predicate unsafe_wrap_socket_call(DataFlow::Node node) {
+// exists(AllowsTLSv1 c | c.hasFlowTo(node)) or
+// exists(AllowsTLSv1_1 c | c.hasFlowTo(node)) or
+// node = API::moduleImport("ssl").getMember("wrap_socket").getACall()
+// }
private ModuleValue the_ssl_module() { result = Module::named("ssl") }
FunctionValue ssl_wrap_socket() { result = the_ssl_module().attr("wrap_socket") }
@@ -21,20 +284,24 @@ private ModuleValue the_pyOpenSSL_module() { result = Value::named("pyOpenSSL.SS
ClassValue the_pyOpenSSL_Context_class() { result = Value::named("pyOpenSSL.SSL.Context") }
-string insecure_version_name() {
- // For `pyOpenSSL.SSL`
- result = "SSLv2_METHOD" or
- result = "SSLv23_METHOD" or
- result = "SSLv3_METHOD" or
- result = "TLSv1_METHOD" or
- // For the `ssl` module
- result = "PROTOCOL_SSLv2" or
- result = "PROTOCOL_SSLv3" or
- result = "PROTOCOL_SSLv23" or
- result = "PROTOCOL_TLS" or
- result = "PROTOCOL_TLSv1"
-}
-
+// Since version 3.6, it is fine to call `ssl.SSLContext(protocol=PROTOCOL_TLS)`
+// if one also specifies either OP_NO_TLSv1 (introduced in 3.2)
+// or SSLContext.minimum_version other than TLSVersion.TLSv1 (introduced in 3.7)
+// See https://docs.python.org/3/library/ssl.html?highlight=ssl#ssl.SSLContext
+// and https://docs.python.org/3/library/ssl.html?highlight=ssl#protocol-versions
+// FP reported here: https://github.com/github/codeql/issues/2554
+// string insecure_version_name() {
+// // For `pyOpenSSL.SSL`
+// result = "SSLv2_METHOD" or
+// result = "SSLv23_METHOD" or
+// result = "SSLv3_METHOD" or
+// result = "TLSv1_METHOD" or
+// // For the `ssl` module
+// result = "PROTOCOL_SSLv2" or
+// result = "PROTOCOL_SSLv3" or
+// result = "PROTOCOL_SSLv23" or
+// result = "PROTOCOL_TLSv1"
+// }
/*
* A syntactic check for cases where points-to analysis cannot infer the presence of
* a protocol constant, e.g. if it has been removed in later versions of the `ssl`
@@ -71,7 +338,7 @@ predicate unsafe_ssl_wrap_socket_call(
named_argument = "protocol" and
method_name = "ssl.SSLContext"
) and
- insecure_version = insecure_version_name() and
+ insecure_version = ssl::insecure_version_name() and
(
call.getArgByName(named_argument).pointsTo(the_ssl_module().attr(insecure_version))
or
@@ -81,7 +348,7 @@ predicate unsafe_ssl_wrap_socket_call(
predicate unsafe_pyOpenSSL_Context_call(CallNode call, string insecure_version) {
call = the_pyOpenSSL_Context_class().getACall() and
- insecure_version = insecure_version_name() and
+ insecure_version = pyOpenSSL::insecure_version_name() and
call.getArg(0).pointsTo(the_pyOpenSSL_module().attr(insecure_version))
}
diff --git a/python/ql/src/Security/CWE-327/examples/secure_default_protocol.py b/python/ql/src/Security/CWE-327/examples/secure_default_protocol.py
new file mode 100644
index 000000000000..83c6dbbba0e0
--- /dev/null
+++ b/python/ql/src/Security/CWE-327/examples/secure_default_protocol.py
@@ -0,0 +1,13 @@
+# taken from https://docs.python.org/3/library/ssl.html?highlight=ssl#ssl.SSLContext
+
+import socket
+import ssl
+
+hostname = 'www.python.org'
+context = ssl.create_default_context()
+context.options |= ssl.OP_NO_TLSv1 # This added by me
+context.options |= ssl.OP_NO_TLSv1_1 # This added by me
+
+with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
diff --git a/python/ql/src/Security/CWE-327/examples/secure_protocol.py b/python/ql/src/Security/CWE-327/examples/secure_protocol.py
new file mode 100644
index 000000000000..94b3557d0172
--- /dev/null
+++ b/python/ql/src/Security/CWE-327/examples/secure_protocol.py
@@ -0,0 +1,11 @@
+import socket
+import ssl
+
+hostname = 'www.python.org'
+context = ssl.SSLContext(ssl.PROTOCOL_TLS)
+context.options |= ssl.OP_NO_TLSv1
+context.options |= ssl.OP_NO_TLSv1_1 # This added by me
+
+with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
From 72b37a5b1bb344dcdd9b4e2d1172c540df678408 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Wed, 17 Feb 2021 13:21:33 +0100
Subject: [PATCH 013/433] Python: factor out barrier
---
.../src/Security/CWE-327/InsecureProtocol.ql | 58 ++++++++++++++++---
1 file changed, 51 insertions(+), 7 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/InsecureProtocol.ql b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
index 6f2feedce22f..6b5b2e75d22a 100644
--- a/python/ql/src/Security/CWE-327/InsecureProtocol.ql
+++ b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
@@ -87,6 +87,46 @@ class ConnectionCall extends ConnectionCreation {
}
}
+class ProtocolRestriction extends DataFlow::CfgNode {
+ abstract DataFlow::CfgNode getContext();
+
+ abstract string getRestriction();
+}
+
+class OptionsAugOr extends ProtocolRestriction {
+ string restriction;
+
+ OptionsAugOr() {
+ exists(AugAssign aa, AttrNode attr |
+ aa.getOperation().getOp() instanceof BitOr and
+ aa.getTarget() = attr.getNode() and
+ attr.getName() = "options" and
+ attr.getObject() = node and
+ aa.getValue() = API::moduleImport("ssl").getMember(restriction).getAUse().asExpr()
+ )
+ }
+
+ override DataFlow::CfgNode getContext() { result = this }
+
+ override string getRestriction() { result = restriction }
+}
+
+class SetOptionsCall extends ProtocolRestriction {
+ override CallNode node;
+
+ SetOptionsCall() { node.getFunction().(AttrNode).getName() = "set_options" }
+
+ override DataFlow::CfgNode getContext() {
+ result.getNode() = node.getFunction().(AttrNode).getObject()
+ }
+
+ override string getRestriction() {
+ API::moduleImport("PyOpenSSL").getMember("SSL").getMember(result).getAUse().asCfgNode() in [
+ node.getArg(0), node.getArgByName("options")
+ ]
+ }
+}
+
abstract class TlsLibrary extends string {
TlsLibrary() { this in ["ssl"] }
@@ -121,6 +161,8 @@ abstract class TlsLibrary extends string {
}
abstract ConnectionCreation connection_creation();
+
+ abstract ProtocolRestriction protocol_restriction();
}
class Ssl extends TlsLibrary {
@@ -143,6 +185,8 @@ class Ssl extends TlsLibrary {
override ContextCreation specific_context_creation() { result instanceof SSLContextCreation }
override ConnectionCreation connection_creation() { result instanceof WrapSocketCall }
+
+ override ProtocolRestriction protocol_restriction() { result instanceof OptionsAugOr }
}
class PyOpenSSL extends TlsLibrary {
@@ -165,6 +209,8 @@ class PyOpenSSL extends TlsLibrary {
}
override ConnectionCreation connection_creation() { result instanceof ConnectionCall }
+
+ override ProtocolRestriction protocol_restriction() { result instanceof OptionsAugOr }
}
module ssl {
@@ -212,12 +258,10 @@ class InsecureContextConfiguration extends DataFlow::Configuration {
abstract string flag();
override predicate isBarrierOut(DataFlow::Node node) {
- exists(AugAssign aa, AttrNode attr |
- aa.getOperation().getOp() instanceof BitOr and
- aa.getTarget() = attr.getNode() and
- attr.getName() = "options" and
- attr.getObject() = node.asCfgNode() and
- aa.getValue() = API::moduleImport("ssl").getMember(flag()).getAUse().asExpr()
+ exists(ProtocolRestriction r |
+ r = library.protocol_restriction() and
+ node = r.getContext() and
+ r.getRestriction() = flag()
)
}
}
@@ -231,7 +275,7 @@ class AllowsTLSv1 extends InsecureContextConfiguration {
class AllowsTLSv1_1 extends InsecureContextConfiguration {
AllowsTLSv1_1() { this = library + "AllowsTLSv1_1" }
- override string flag() { result = "OP_NO_TLSv1_2" }
+ override string flag() { result = "OP_NO_TLSv1_1" }
}
predicate unsafe_connection_creation(DataFlow::Node node) {
From 7ed018aff6c9f79a3ea60ef8ae4d2f2067b81442 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Wed, 17 Feb 2021 14:30:28 +0100
Subject: [PATCH 014/433] Python: refactor into modules and turn on the
pyOpenSSL module
---
.../src/Security/CWE-327/InsecureProtocol.ql | 385 +++++++-----------
1 file changed, 138 insertions(+), 247 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/InsecureProtocol.ql b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
index 6b5b2e75d22a..81b3558907e3 100644
--- a/python/ql/src/Security/CWE-327/InsecureProtocol.ql
+++ b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
@@ -39,96 +39,18 @@ abstract class ContextCreation extends DataFlow::CfgNode {
abstract DataFlow::CfgNode getProtocol();
}
-class SSLContextCreation extends ContextCreation {
- override CallNode node;
-
- SSLContextCreation() { this = API::moduleImport("ssl").getMember("SSLContext").getACall() }
-
- override DataFlow::CfgNode getProtocol() {
- result.getNode() in [node.getArg(0), node.getArgByName("protocol")]
- }
-}
-
-class PyOpenSSLContextCreation extends ContextCreation {
- override CallNode node;
-
- PyOpenSSLContextCreation() {
- this = API::moduleImport("pyOpenSSL").getMember("SSL").getMember("Context").getACall()
- }
-
- override DataFlow::CfgNode getProtocol() {
- result.getNode() in [node.getArg(0), node.getArgByName("method")]
- }
-}
-
abstract class ConnectionCreation extends DataFlow::CfgNode {
abstract DataFlow::CfgNode getContext();
}
-class WrapSocketCall extends ConnectionCreation {
- override CallNode node;
-
- WrapSocketCall() { node.getFunction().(AttrNode).getName() = "wrap_socket" }
-
- override DataFlow::CfgNode getContext() {
- result.getNode() = node.getFunction().(AttrNode).getObject()
- }
-}
-
-class ConnectionCall extends ConnectionCreation {
- override CallNode node;
-
- ConnectionCall() {
- this = API::moduleImport("pyOpenSSL").getMember("SSL").getMember("Connection").getACall()
- }
-
- override DataFlow::CfgNode getContext() {
- result.getNode() in [node.getArg(0), node.getArgByName("context")]
- }
-}
-
class ProtocolRestriction extends DataFlow::CfgNode {
abstract DataFlow::CfgNode getContext();
abstract string getRestriction();
}
-class OptionsAugOr extends ProtocolRestriction {
- string restriction;
-
- OptionsAugOr() {
- exists(AugAssign aa, AttrNode attr |
- aa.getOperation().getOp() instanceof BitOr and
- aa.getTarget() = attr.getNode() and
- attr.getName() = "options" and
- attr.getObject() = node and
- aa.getValue() = API::moduleImport("ssl").getMember(restriction).getAUse().asExpr()
- )
- }
-
- override DataFlow::CfgNode getContext() { result = this }
-
- override string getRestriction() { result = restriction }
-}
-
-class SetOptionsCall extends ProtocolRestriction {
- override CallNode node;
-
- SetOptionsCall() { node.getFunction().(AttrNode).getName() = "set_options" }
-
- override DataFlow::CfgNode getContext() {
- result.getNode() = node.getFunction().(AttrNode).getObject()
- }
-
- override string getRestriction() {
- API::moduleImport("PyOpenSSL").getMember("SSL").getMember(result).getAUse().asCfgNode() in [
- node.getArg(0), node.getArgByName("options")
- ]
- }
-}
-
abstract class TlsLibrary extends string {
- TlsLibrary() { this in ["ssl"] }
+ TlsLibrary() { this in ["ssl", "pyOpenSSL"] }
abstract string specific_insecure_version_name();
@@ -160,85 +82,149 @@ abstract class TlsLibrary extends string {
result.(ContextCreation).getProtocol() = unspecific_version()
}
+ /** A connection is created in an outright insecure manner. */
+ abstract DataFlow::CfgNode insecure_connection_creation();
+
+ /** A connection is created from a context. */
abstract ConnectionCreation connection_creation();
abstract ProtocolRestriction protocol_restriction();
}
-class Ssl extends TlsLibrary {
- Ssl() { this = "ssl" }
+module ssl {
+ class SSLContextCreation extends ContextCreation {
+ override CallNode node;
+
+ SSLContextCreation() { this = API::moduleImport("ssl").getMember("SSLContext").getACall() }
- override string specific_insecure_version_name() {
- result in [
- "PROTOCOL_SSLv2", "PROTOCOL_SSLv3", "PROTOCOL_SSLv23", "PROTOCOL_TLSv1", "PROTOCOL_TLSv1_1"
- ]
+ override DataFlow::CfgNode getProtocol() {
+ result.getNode() in [node.getArg(0), node.getArgByName("protocol")]
+ }
}
- override string unspecific_version_name() { result = "PROTOCOL_TLS" }
+ class WrapSocketCall extends ConnectionCreation {
+ override CallNode node;
- override API::Node version_constants() { result = API::moduleImport("ssl") }
+ WrapSocketCall() { node.getFunction().(AttrNode).getName() = "wrap_socket" }
- override DataFlow::CfgNode default_context_creation() {
- result = API::moduleImport("ssl").getMember("create_default_context").getACall()
+ override DataFlow::CfgNode getContext() {
+ result.getNode() = node.getFunction().(AttrNode).getObject()
+ }
}
- override ContextCreation specific_context_creation() { result instanceof SSLContextCreation }
+ class OptionsAugOr extends ProtocolRestriction {
+ string restriction;
- override ConnectionCreation connection_creation() { result instanceof WrapSocketCall }
+ OptionsAugOr() {
+ exists(AugAssign aa, AttrNode attr |
+ aa.getOperation().getOp() instanceof BitOr and
+ aa.getTarget() = attr.getNode() and
+ attr.getName() = "options" and
+ attr.getObject() = node and
+ aa.getValue() = API::moduleImport("ssl").getMember(restriction).getAUse().asExpr()
+ )
+ }
- override ProtocolRestriction protocol_restriction() { result instanceof OptionsAugOr }
-}
+ override DataFlow::CfgNode getContext() { result = this }
-class PyOpenSSL extends TlsLibrary {
- PyOpenSSL() { this = "pyOpenSSL" }
-
- override string specific_insecure_version_name() {
- result in ["SSLv2_METHOD", "SSLv23_METHOD", "SSLv3_METHOD", "TLSv1_METHOD", "TLSv1_1_METHOD"]
+ override string getRestriction() { result = restriction }
}
- override string unspecific_version_name() { result = "TLS_METHOD" }
+ class Ssl extends TlsLibrary {
+ Ssl() { this = "ssl" }
- override API::Node version_constants() {
- result = API::moduleImport("pyOpenSSL").getMember("SSL")
- }
+ override string specific_insecure_version_name() {
+ result in [
+ "PROTOCOL_SSLv2", "PROTOCOL_SSLv3", "PROTOCOL_SSLv23", "PROTOCOL_TLSv1",
+ "PROTOCOL_TLSv1_1"
+ ]
+ }
- override DataFlow::CfgNode default_context_creation() { none() }
+ override string unspecific_version_name() { result = "PROTOCOL_TLS" }
- override ContextCreation specific_context_creation() {
- result instanceof PyOpenSSLContextCreation
- }
+ override API::Node version_constants() { result = API::moduleImport("ssl") }
- override ConnectionCreation connection_creation() { result instanceof ConnectionCall }
+ override DataFlow::CfgNode default_context_creation() {
+ result = API::moduleImport("ssl").getMember("create_default_context").getACall()
+ }
- override ProtocolRestriction protocol_restriction() { result instanceof OptionsAugOr }
-}
+ override ContextCreation specific_context_creation() { result instanceof SSLContextCreation }
-module ssl {
- string insecure_version_name() {
- result = "PROTOCOL_SSLv2" or
- result = "PROTOCOL_SSLv3" or
- result = "PROTOCOL_SSLv23" or
- result = "PROTOCOL_TLSv1" or
- result = "PROTOCOL_TLSv1_1"
- }
+ override DataFlow::CfgNode insecure_connection_creation() {
+ result = API::moduleImport("ssl").getMember("wrap_socket").getACall()
+ }
- DataFlow::Node insecure_version() {
- result = API::moduleImport("ssl").getMember(insecure_version_name()).getAUse()
+ override ConnectionCreation connection_creation() { result instanceof WrapSocketCall }
+
+ override ProtocolRestriction protocol_restriction() { result instanceof OptionsAugOr }
}
}
module pyOpenSSL {
- string insecure_version_name() {
- result = "SSLv2_METHOD" or
- result = "SSLv23_METHOD" or
- result = "SSLv3_METHOD" or
- result = "TLSv1_METHOD" or
- result = "TLSv1_1_METHOD"
+ class PyOpenSSLContextCreation extends ContextCreation {
+ override CallNode node;
+
+ PyOpenSSLContextCreation() {
+ this = API::moduleImport("pyOpenSSL").getMember("SSL").getMember("Context").getACall()
+ }
+
+ override DataFlow::CfgNode getProtocol() {
+ result.getNode() in [node.getArg(0), node.getArgByName("method")]
+ }
}
- DataFlow::Node insecure_version() {
- result =
- API::moduleImport("pyOpenSSL").getMember("SSL").getMember(insecure_version_name()).getAUse()
+ class ConnectionCall extends ConnectionCreation {
+ override CallNode node;
+
+ ConnectionCall() {
+ this = API::moduleImport("pyOpenSSL").getMember("SSL").getMember("Connection").getACall()
+ }
+
+ override DataFlow::CfgNode getContext() {
+ result.getNode() in [node.getArg(0), node.getArgByName("context")]
+ }
+ }
+
+ class SetOptionsCall extends ProtocolRestriction {
+ override CallNode node;
+
+ SetOptionsCall() { node.getFunction().(AttrNode).getName() = "set_options" }
+
+ override DataFlow::CfgNode getContext() {
+ result.getNode() = node.getFunction().(AttrNode).getObject()
+ }
+
+ override string getRestriction() {
+ API::moduleImport("PyOpenSSL").getMember("SSL").getMember(result).getAUse().asCfgNode() in [
+ node.getArg(0), node.getArgByName("options")
+ ]
+ }
+ }
+
+ class PyOpenSSL extends TlsLibrary {
+ PyOpenSSL() { this = "pyOpenSSL" }
+
+ override string specific_insecure_version_name() {
+ result in ["SSLv2_METHOD", "SSLv23_METHOD", "SSLv3_METHOD", "TLSv1_METHOD", "TLSv1_1_METHOD"]
+ }
+
+ override string unspecific_version_name() { result = "TLS_METHOD" }
+
+ override API::Node version_constants() {
+ result = API::moduleImport("pyOpenSSL").getMember("SSL")
+ }
+
+ override DataFlow::CfgNode default_context_creation() { none() }
+
+ override ContextCreation specific_context_creation() {
+ result instanceof PyOpenSSLContextCreation
+ }
+
+ override DataFlow::CfgNode insecure_connection_creation() { none() }
+
+ override ConnectionCreation connection_creation() { result instanceof ConnectionCall }
+
+ override ProtocolRestriction protocol_restriction() { result instanceof SetOptionsCall }
}
}
@@ -278,129 +264,34 @@ class AllowsTLSv1_1 extends InsecureContextConfiguration {
override string flag() { result = "OP_NO_TLSv1_1" }
}
-predicate unsafe_connection_creation(DataFlow::Node node) {
- exists(AllowsTLSv1 c | c.hasFlowTo(node)) or
- exists(AllowsTLSv1_1 c | c.hasFlowTo(node)) //or
- // node = API::moduleImport("ssl").getMember("wrap_socket").getACall()
-}
-
-predicate unsafe_context_creation(DataFlow::Node node) {
- exists(TlsLibrary l | l.insecure_context_creation() = node)
-}
-
-// class InsecureTLSContextConfiguration extends DataFlow::Configuration {
-// InsecureTLSContextConfiguration() { this in ["AllowsTLSv1", "AllowsTLSv1_1"] }
-// override predicate isSource(DataFlow::Node source) {
-// source instanceof InsecureSSLContextCreation
-// }
-// override predicate isSink(DataFlow::Node sink) { sink = any(WrapSocketCall c).getContext() }
-// abstract string flag();
-// override predicate isBarrierOut(DataFlow::Node node) {
-// exists(AugAssign aa, AttrNode attr |
-// aa.getOperation().getOp() instanceof BitOr and
-// aa.getTarget() = attr.getNode() and
-// attr.getName() = "options" and
-// attr.getObject() = node.asCfgNode() and
-// aa.getValue() = API::moduleImport("ssl").getMember(flag()).getAUse().asExpr()
-// )
-// }
-// }
-// class AllowsTLSv1 extends InsecureTLSContextConfiguration {
-// AllowsTLSv1() { this = "AllowsTLSv1" }
-// override string flag() { result = "OP_NO_TLSv1" }
-// }
-// class AllowsTLSv1_1 extends InsecureTLSContextConfiguration {
-// AllowsTLSv1_1() { this = "AllowsTLSv1_1" }
-// override string flag() { result = "OP_NO_TLSv1_1" }
-// }
-// predicate unsafe_wrap_socket_call(DataFlow::Node node) {
-// exists(AllowsTLSv1 c | c.hasFlowTo(node)) or
-// exists(AllowsTLSv1_1 c | c.hasFlowTo(node)) or
-// node = API::moduleImport("ssl").getMember("wrap_socket").getACall()
-// }
-private ModuleValue the_ssl_module() { result = Module::named("ssl") }
-
-FunctionValue ssl_wrap_socket() { result = the_ssl_module().attr("wrap_socket") }
-
-ClassValue ssl_Context_class() { result = the_ssl_module().attr("SSLContext") }
-
-private ModuleValue the_pyOpenSSL_module() { result = Value::named("pyOpenSSL.SSL") }
-
-ClassValue the_pyOpenSSL_Context_class() { result = Value::named("pyOpenSSL.SSL.Context") }
-
-// Since version 3.6, it is fine to call `ssl.SSLContext(protocol=PROTOCOL_TLS)`
-// if one also specifies either OP_NO_TLSv1 (introduced in 3.2)
-// or SSLContext.minimum_version other than TLSVersion.TLSv1 (introduced in 3.7)
-// See https://docs.python.org/3/library/ssl.html?highlight=ssl#ssl.SSLContext
-// and https://docs.python.org/3/library/ssl.html?highlight=ssl#protocol-versions
-// FP reported here: https://github.com/github/codeql/issues/2554
-// string insecure_version_name() {
-// // For `pyOpenSSL.SSL`
-// result = "SSLv2_METHOD" or
-// result = "SSLv23_METHOD" or
-// result = "SSLv3_METHOD" or
-// result = "TLSv1_METHOD" or
-// // For the `ssl` module
-// result = "PROTOCOL_SSLv2" or
-// result = "PROTOCOL_SSLv3" or
-// result = "PROTOCOL_SSLv23" or
-// result = "PROTOCOL_TLSv1"
-// }
-/*
- * A syntactic check for cases where points-to analysis cannot infer the presence of
- * a protocol constant, e.g. if it has been removed in later versions of the `ssl`
- * library.
- */
-
-bindingset[named_argument]
-predicate probable_insecure_ssl_constant(
- CallNode call, string insecure_version, string named_argument
-) {
- exists(ControlFlowNode arg |
- arg = call.getArgByName(named_argument) or
- arg = call.getArg(0)
- |
- arg.(AttrNode).getObject(insecure_version).pointsTo(the_ssl_module())
- or
- arg.(NameNode).getId() = insecure_version and
- exists(Import imp |
- imp.getAnImportedModuleName() = "ssl" and
- imp.getAName().getAsname().(Name).getId() = insecure_version
- )
- )
+predicate unsafe_connection_creation(DataFlow::Node node, string insecure_version) {
+ exists(AllowsTLSv1 c | c.hasFlowTo(node)) and
+ insecure_version = "TLSv1"
+ or
+ exists(AllowsTLSv1_1 c | c.hasFlowTo(node)) and
+ insecure_version = "TLSv1"
+ or
+ exists(TlsLibrary l | l.insecure_connection_creation() = node) and
+ insecure_version = "[multiple]"
}
-predicate unsafe_ssl_wrap_socket_call(
- CallNode call, string method_name, string insecure_version, string named_argument
-) {
- (
- call = ssl_wrap_socket().getACall() and
- method_name = "deprecated method ssl.wrap_socket" and
- named_argument = "ssl_version"
- or
- call = ssl_Context_class().getACall() and
- named_argument = "protocol" and
- method_name = "ssl.SSLContext"
- ) and
- insecure_version = ssl::insecure_version_name() and
- (
- call.getArgByName(named_argument).pointsTo(the_ssl_module().attr(insecure_version))
- or
- probable_insecure_ssl_constant(call, insecure_version, named_argument)
+predicate unsafe_context_creation(DataFlow::Node node, string insecure_version) {
+ exists(TlsLibrary l, ContextCreation cc | cc = l.insecure_context_creation() |
+ cc = node and insecure_version = cc.getProtocol().toString()
)
}
-predicate unsafe_pyOpenSSL_Context_call(CallNode call, string insecure_version) {
- call = the_pyOpenSSL_Context_class().getACall() and
- insecure_version = pyOpenSSL::insecure_version_name() and
- call.getArg(0).pointsTo(the_pyOpenSSL_module().attr(insecure_version))
-}
-
-from CallNode call, string method_name, string insecure_version
+from DataFlow::Node node, string insecure_version
where
- unsafe_ssl_wrap_socket_call(call, method_name, insecure_version, _)
+ unsafe_connection_creation(node, insecure_version)
or
- unsafe_pyOpenSSL_Context_call(call, insecure_version) and method_name = "pyOpenSSL.SSL.Context"
-select call,
- "Insecure SSL/TLS protocol version " + insecure_version + " specified in call to " + method_name +
- "."
+ unsafe_context_creation(node, insecure_version)
+select node, "Insecure SSL/TLS protocol version " + insecure_version //+ " specified in call to " + method_name + "."
+// from CallNode call, string method_name, string insecure_version
+// where
+// unsafe_ssl_wrap_socket_call(call, method_name, insecure_version, _)
+// or
+// unsafe_pyOpenSSL_Context_call(call, insecure_version) and method_name = "pyOpenSSL.SSL.Context"
+// select call,
+// "Insecure SSL/TLS protocol version " + insecure_version + " specified in call to " + method_name +
+// "."
From 186db7f43ec254792ca3b0ded440fd0a06080711 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Sun, 21 Feb 2021 12:07:48 +0100
Subject: [PATCH 015/433] Python: factor into modules and files
---
.../src/Security/CWE-327/FluentApiModel.qll | 54 ++++
.../src/Security/CWE-327/InsecureProtocol.ql | 268 +-----------------
python/ql/src/Security/CWE-327/PyOpenSSL.qll | 73 +++++
python/ql/src/Security/CWE-327/Ssl.qll | 106 +++++++
.../src/Security/CWE-327/TlsLibraryModel.qll | 80 ++++++
5 files changed, 315 insertions(+), 266 deletions(-)
create mode 100644 python/ql/src/Security/CWE-327/FluentApiModel.qll
create mode 100644 python/ql/src/Security/CWE-327/PyOpenSSL.qll
create mode 100644 python/ql/src/Security/CWE-327/Ssl.qll
create mode 100644 python/ql/src/Security/CWE-327/TlsLibraryModel.qll
diff --git a/python/ql/src/Security/CWE-327/FluentApiModel.qll b/python/ql/src/Security/CWE-327/FluentApiModel.qll
new file mode 100644
index 000000000000..4c2278b86942
--- /dev/null
+++ b/python/ql/src/Security/CWE-327/FluentApiModel.qll
@@ -0,0 +1,54 @@
+import python
+import TlsLibraryModel
+
+class InsecureContextConfiguration extends DataFlow::Configuration {
+ TlsLibrary library;
+
+ InsecureContextConfiguration() { this = library + ["AllowsTLSv1", "AllowsTLSv1_1"] }
+
+ override predicate isSource(DataFlow::Node source) {
+ source = library.unspecific_context_creation()
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ sink = library.connection_creation().getContext()
+ }
+
+ abstract string flag();
+
+ override predicate isBarrierOut(DataFlow::Node node) {
+ exists(ProtocolRestriction r |
+ r = library.protocol_restriction() and
+ node = r.getContext() and
+ r.getRestriction() = flag()
+ )
+ }
+}
+
+class AllowsTLSv1 extends InsecureContextConfiguration {
+ AllowsTLSv1() { this = library + "AllowsTLSv1" }
+
+ override string flag() { result = "TLSv1" }
+}
+
+class AllowsTLSv1_1 extends InsecureContextConfiguration {
+ AllowsTLSv1_1() { this = library + "AllowsTLSv1_1" }
+
+ override string flag() { result = "TLSv1_1" }
+}
+
+predicate unsafe_connection_creation(DataFlow::Node node, ProtocolVersion insecure_version) {
+ exists(AllowsTLSv1 c | c.hasFlowTo(node)) and
+ insecure_version = "TLSv1"
+ or
+ exists(AllowsTLSv1_1 c | c.hasFlowTo(node)) and
+ insecure_version = "TLSv1_1"
+ or
+ exists(TlsLibrary l | l.insecure_connection_creation(insecure_version) = node)
+}
+
+predicate unsafe_context_creation(DataFlow::Node node, string insecure_version) {
+ exists(TlsLibrary l, ContextCreation cc | cc = l.insecure_context_creation(insecure_version) |
+ cc = node
+ )
+}
diff --git a/python/ql/src/Security/CWE-327/InsecureProtocol.ql b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
index 81b3558907e3..b873e5f3ce8f 100644
--- a/python/ql/src/Security/CWE-327/InsecureProtocol.ql
+++ b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
@@ -10,277 +10,13 @@
*/
import python
-import semmle.python.ApiGraphs
+import FluentApiModel
+// string foo(ProtocolRestriction r) { result = r.getRestriction() }
// The idea is to track flow from the creation of an insecure context to a use
// such as `wrap_socket`. There should be a data-flow path for each insecure version
// and each path should have a version specific sanitizer. This will allow fluent api
// style code to block the paths one by one.
-//
-// class InsecureContextCreation extends DataFlow::CfgNode {
-// override CallNode node;
-// InsecureContextCreation() {
-// this = API::moduleImport("ssl").getMember("SSLContext").getACall() and
-// insecure_version().asCfgNode() in [node.getArg(0), node.getArgByName("protocol")]
-// }
-// }
-// class InsecureSSLContextCreation extends DataFlow::CfgNode {
-// override CallNode node;
-// InsecureSSLContextCreation() {
-// this = API::moduleImport("ssl").getMember("create_default_context").getACall()
-// or
-// this = API::moduleImport("ssl").getMember("SSLContext").getACall() and
-// API::moduleImport("ssl").getMember("PROTOCOL_TLS").getAUse().asCfgNode() in [
-// node.getArg(0), node.getArgByName("protocol")
-// ]
-// }
-// }
-abstract class ContextCreation extends DataFlow::CfgNode {
- abstract DataFlow::CfgNode getProtocol();
-}
-
-abstract class ConnectionCreation extends DataFlow::CfgNode {
- abstract DataFlow::CfgNode getContext();
-}
-
-class ProtocolRestriction extends DataFlow::CfgNode {
- abstract DataFlow::CfgNode getContext();
-
- abstract string getRestriction();
-}
-
-abstract class TlsLibrary extends string {
- TlsLibrary() { this in ["ssl", "pyOpenSSL"] }
-
- abstract string specific_insecure_version_name();
-
- abstract string unspecific_version_name();
-
- abstract API::Node version_constants();
-
- DataFlow::Node insecure_version() {
- result = version_constants().getMember(specific_insecure_version_name()).getAUse()
- }
-
- DataFlow::Node unspecific_version() {
- result = version_constants().getMember(unspecific_version_name()).getAUse()
- }
-
- abstract DataFlow::CfgNode default_context_creation();
-
- abstract ContextCreation specific_context_creation();
-
- ContextCreation insecure_context_creation() {
- result = specific_context_creation() and
- result.getProtocol() = insecure_version()
- }
-
- DataFlow::CfgNode unspecific_context_creation() {
- result = default_context_creation()
- or
- result = specific_context_creation() and
- result.(ContextCreation).getProtocol() = unspecific_version()
- }
-
- /** A connection is created in an outright insecure manner. */
- abstract DataFlow::CfgNode insecure_connection_creation();
-
- /** A connection is created from a context. */
- abstract ConnectionCreation connection_creation();
-
- abstract ProtocolRestriction protocol_restriction();
-}
-
-module ssl {
- class SSLContextCreation extends ContextCreation {
- override CallNode node;
-
- SSLContextCreation() { this = API::moduleImport("ssl").getMember("SSLContext").getACall() }
-
- override DataFlow::CfgNode getProtocol() {
- result.getNode() in [node.getArg(0), node.getArgByName("protocol")]
- }
- }
-
- class WrapSocketCall extends ConnectionCreation {
- override CallNode node;
-
- WrapSocketCall() { node.getFunction().(AttrNode).getName() = "wrap_socket" }
-
- override DataFlow::CfgNode getContext() {
- result.getNode() = node.getFunction().(AttrNode).getObject()
- }
- }
-
- class OptionsAugOr extends ProtocolRestriction {
- string restriction;
-
- OptionsAugOr() {
- exists(AugAssign aa, AttrNode attr |
- aa.getOperation().getOp() instanceof BitOr and
- aa.getTarget() = attr.getNode() and
- attr.getName() = "options" and
- attr.getObject() = node and
- aa.getValue() = API::moduleImport("ssl").getMember(restriction).getAUse().asExpr()
- )
- }
-
- override DataFlow::CfgNode getContext() { result = this }
-
- override string getRestriction() { result = restriction }
- }
-
- class Ssl extends TlsLibrary {
- Ssl() { this = "ssl" }
-
- override string specific_insecure_version_name() {
- result in [
- "PROTOCOL_SSLv2", "PROTOCOL_SSLv3", "PROTOCOL_SSLv23", "PROTOCOL_TLSv1",
- "PROTOCOL_TLSv1_1"
- ]
- }
-
- override string unspecific_version_name() { result = "PROTOCOL_TLS" }
-
- override API::Node version_constants() { result = API::moduleImport("ssl") }
-
- override DataFlow::CfgNode default_context_creation() {
- result = API::moduleImport("ssl").getMember("create_default_context").getACall()
- }
-
- override ContextCreation specific_context_creation() { result instanceof SSLContextCreation }
-
- override DataFlow::CfgNode insecure_connection_creation() {
- result = API::moduleImport("ssl").getMember("wrap_socket").getACall()
- }
-
- override ConnectionCreation connection_creation() { result instanceof WrapSocketCall }
-
- override ProtocolRestriction protocol_restriction() { result instanceof OptionsAugOr }
- }
-}
-
-module pyOpenSSL {
- class PyOpenSSLContextCreation extends ContextCreation {
- override CallNode node;
-
- PyOpenSSLContextCreation() {
- this = API::moduleImport("pyOpenSSL").getMember("SSL").getMember("Context").getACall()
- }
-
- override DataFlow::CfgNode getProtocol() {
- result.getNode() in [node.getArg(0), node.getArgByName("method")]
- }
- }
-
- class ConnectionCall extends ConnectionCreation {
- override CallNode node;
-
- ConnectionCall() {
- this = API::moduleImport("pyOpenSSL").getMember("SSL").getMember("Connection").getACall()
- }
-
- override DataFlow::CfgNode getContext() {
- result.getNode() in [node.getArg(0), node.getArgByName("context")]
- }
- }
-
- class SetOptionsCall extends ProtocolRestriction {
- override CallNode node;
-
- SetOptionsCall() { node.getFunction().(AttrNode).getName() = "set_options" }
-
- override DataFlow::CfgNode getContext() {
- result.getNode() = node.getFunction().(AttrNode).getObject()
- }
-
- override string getRestriction() {
- API::moduleImport("PyOpenSSL").getMember("SSL").getMember(result).getAUse().asCfgNode() in [
- node.getArg(0), node.getArgByName("options")
- ]
- }
- }
-
- class PyOpenSSL extends TlsLibrary {
- PyOpenSSL() { this = "pyOpenSSL" }
-
- override string specific_insecure_version_name() {
- result in ["SSLv2_METHOD", "SSLv23_METHOD", "SSLv3_METHOD", "TLSv1_METHOD", "TLSv1_1_METHOD"]
- }
-
- override string unspecific_version_name() { result = "TLS_METHOD" }
-
- override API::Node version_constants() {
- result = API::moduleImport("pyOpenSSL").getMember("SSL")
- }
-
- override DataFlow::CfgNode default_context_creation() { none() }
-
- override ContextCreation specific_context_creation() {
- result instanceof PyOpenSSLContextCreation
- }
-
- override DataFlow::CfgNode insecure_connection_creation() { none() }
-
- override ConnectionCreation connection_creation() { result instanceof ConnectionCall }
-
- override ProtocolRestriction protocol_restriction() { result instanceof SetOptionsCall }
- }
-}
-
-class InsecureContextConfiguration extends DataFlow::Configuration {
- TlsLibrary library;
-
- InsecureContextConfiguration() { this = library + ["AllowsTLSv1", "AllowsTLSv1_1"] }
-
- override predicate isSource(DataFlow::Node source) {
- source = library.unspecific_context_creation()
- }
-
- override predicate isSink(DataFlow::Node sink) {
- sink = library.connection_creation().getContext()
- }
-
- abstract string flag();
-
- override predicate isBarrierOut(DataFlow::Node node) {
- exists(ProtocolRestriction r |
- r = library.protocol_restriction() and
- node = r.getContext() and
- r.getRestriction() = flag()
- )
- }
-}
-
-class AllowsTLSv1 extends InsecureContextConfiguration {
- AllowsTLSv1() { this = library + "AllowsTLSv1" }
-
- override string flag() { result = "OP_NO_TLSv1" }
-}
-
-class AllowsTLSv1_1 extends InsecureContextConfiguration {
- AllowsTLSv1_1() { this = library + "AllowsTLSv1_1" }
-
- override string flag() { result = "OP_NO_TLSv1_1" }
-}
-
-predicate unsafe_connection_creation(DataFlow::Node node, string insecure_version) {
- exists(AllowsTLSv1 c | c.hasFlowTo(node)) and
- insecure_version = "TLSv1"
- or
- exists(AllowsTLSv1_1 c | c.hasFlowTo(node)) and
- insecure_version = "TLSv1"
- or
- exists(TlsLibrary l | l.insecure_connection_creation() = node) and
- insecure_version = "[multiple]"
-}
-
-predicate unsafe_context_creation(DataFlow::Node node, string insecure_version) {
- exists(TlsLibrary l, ContextCreation cc | cc = l.insecure_context_creation() |
- cc = node and insecure_version = cc.getProtocol().toString()
- )
-}
-
from DataFlow::Node node, string insecure_version
where
unsafe_connection_creation(node, insecure_version)
diff --git a/python/ql/src/Security/CWE-327/PyOpenSSL.qll b/python/ql/src/Security/CWE-327/PyOpenSSL.qll
new file mode 100644
index 000000000000..b9a68648f935
--- /dev/null
+++ b/python/ql/src/Security/CWE-327/PyOpenSSL.qll
@@ -0,0 +1,73 @@
+import python
+import semmle.python.ApiGraphs
+import TlsLibraryModel
+
+class PyOpenSSLContextCreation extends ContextCreation {
+ override CallNode node;
+
+ PyOpenSSLContextCreation() {
+ this = API::moduleImport("OpenSSL").getMember("SSL").getMember("Context").getACall()
+ }
+
+ override DataFlow::CfgNode getProtocol() {
+ result.getNode() in [node.getArg(0), node.getArgByName("method")]
+ }
+}
+
+class ConnectionCall extends ConnectionCreation {
+ override CallNode node;
+
+ ConnectionCall() {
+ this = API::moduleImport("OpenSSL").getMember("SSL").getMember("Connection").getACall()
+ }
+
+ override DataFlow::CfgNode getContext() {
+ result.getNode() in [node.getArg(0), node.getArgByName("context")]
+ }
+}
+
+class SetOptionsCall extends ProtocolRestriction {
+ override CallNode node;
+
+ SetOptionsCall() { node.getFunction().(AttrNode).getName() = "set_options" }
+
+ override DataFlow::CfgNode getContext() {
+ result.getNode() = node.getFunction().(AttrNode).getObject()
+ }
+
+ override ProtocolVersion getRestriction() {
+ API::moduleImport("OpenSSL").getMember("SSL").getMember("OP_NO_" + result).getAUse().asCfgNode() in [
+ node.getArg(0), node.getArgByName("options")
+ ]
+ }
+}
+
+class PyOpenSSL extends TlsLibrary {
+ PyOpenSSL() { this = "pyOpenSSL" }
+
+ override string specific_insecure_version_name(ProtocolVersion version) {
+ version in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1"] and
+ result = version + "_METHOD"
+ }
+
+ override string unspecific_version_name() {
+ result in [
+ "TLS_METHOD", // This is not actually available in pyOpenSSL yet
+ "SSLv23_METHOD" // This is what can negotiate TLS 1.3 (indeed, I know, I did test that..)
+ ]
+ }
+
+ override API::Node version_constants() { result = API::moduleImport("OpenSSL").getMember("SSL") }
+
+ override DataFlow::CfgNode default_context_creation() { none() }
+
+ override ContextCreation specific_context_creation() {
+ result instanceof PyOpenSSLContextCreation
+ }
+
+ override DataFlow::CfgNode insecure_connection_creation(ProtocolVersion version) { none() }
+
+ override ConnectionCreation connection_creation() { result instanceof ConnectionCall }
+
+ override ProtocolRestriction protocol_restriction() { result instanceof SetOptionsCall }
+}
diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll
new file mode 100644
index 000000000000..369149fd6384
--- /dev/null
+++ b/python/ql/src/Security/CWE-327/Ssl.qll
@@ -0,0 +1,106 @@
+import python
+import semmle.python.ApiGraphs
+import semmle.python.dataflow.new.internal.Attributes as Attributes
+import TlsLibraryModel
+
+class SSLContextCreation extends ContextCreation {
+ override CallNode node;
+
+ SSLContextCreation() { this = API::moduleImport("ssl").getMember("SSLContext").getACall() }
+
+ override DataFlow::CfgNode getProtocol() {
+ result.getNode() in [node.getArg(0), node.getArgByName("protocol")]
+ }
+}
+
+class WrapSocketCall extends ConnectionCreation {
+ override CallNode node;
+
+ WrapSocketCall() { node.getFunction().(AttrNode).getName() = "wrap_socket" }
+
+ override DataFlow::CfgNode getContext() {
+ result.getNode() = node.getFunction().(AttrNode).getObject()
+ }
+}
+
+class OptionsAugOr extends ProtocolRestriction {
+ ProtocolVersion restriction;
+
+ OptionsAugOr() {
+ exists(AugAssign aa, AttrNode attr |
+ aa.getOperation().getOp() instanceof BitOr and
+ aa.getTarget() = attr.getNode() and
+ attr.getName() = "options" and
+ attr.getObject() = node and
+ API::moduleImport("ssl").getMember("OP_NO_" + restriction).getAUse().asExpr() in [
+ aa.getValue(), aa.getValue().getAChildNode()
+ ]
+ )
+ }
+
+ override DataFlow::CfgNode getContext() { result = this }
+
+ override ProtocolVersion getRestriction() { result = restriction }
+}
+
+class ContextSetVersion extends ProtocolRestriction {
+ string restriction;
+
+ ContextSetVersion() {
+ exists(Attributes::AttrWrite aw |
+ aw.getObject().asCfgNode() = node and
+ aw.getAttributeName() = "minimum_version" and
+ aw.getValue() =
+ API::moduleImport("ssl").getMember("TLSVersion").getMember(restriction).getAUse()
+ )
+ }
+
+ override DataFlow::CfgNode getContext() { result = this }
+
+ override ProtocolVersion getRestriction() { result.lessThan(restriction) }
+}
+
+class Ssl extends TlsLibrary {
+ Ssl() { this = "ssl" }
+
+ override string specific_insecure_version_name(ProtocolVersion version) {
+ version in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1"] and
+ result = "PROTOCOL_" + version
+ // result in ["PROTOCOL_SSLv2", "PROTOCOL_SSLv3", "PROTOCOL_TLSv1", "PROTOCOL_TLSv1_1"]
+ }
+
+ override string unspecific_version_name() {
+ result =
+ "PROTOCOL_" +
+ [
+ "TLS",
+ // This can negotiate a TLS 1.3 connection (!)
+ // see https://docs.python.org/3/library/ssl.html#ssl-contexts
+ "SSLv23"
+ ]
+ }
+
+ override API::Node version_constants() { result = API::moduleImport("ssl") }
+
+ override DataFlow::CfgNode default_context_creation() {
+ result = API::moduleImport("ssl").getMember("create_default_context").getACall() //and
+ // see https://docs.python.org/3/library/ssl.html#context-creation
+ // version in ["TLSv1", "TLSv1_1"]
+ }
+
+ override ContextCreation specific_context_creation() { result instanceof SSLContextCreation }
+
+ override DataFlow::CfgNode insecure_connection_creation(ProtocolVersion version) {
+ result = API::moduleImport("ssl").getMember("wrap_socket").getACall() and
+ insecure_version(version).asCfgNode() =
+ result.asCfgNode().(CallNode).getArgByName("ssl_version")
+ }
+
+ override ConnectionCreation connection_creation() { result instanceof WrapSocketCall }
+
+ override ProtocolRestriction protocol_restriction() {
+ result instanceof OptionsAugOr
+ or
+ result instanceof ContextSetVersion
+ }
+}
diff --git a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
new file mode 100644
index 000000000000..41db81ad1c80
--- /dev/null
+++ b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
@@ -0,0 +1,80 @@
+import python
+import semmle.python.ApiGraphs
+import Ssl
+import PyOpenSSL
+
+/**
+ * A specific protocol version.
+ * We use this to identify a protocol.
+ */
+class ProtocolVersion extends string {
+ ProtocolVersion() { this in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"] }
+
+ predicate lessThan(ProtocolVersion version) {
+ this = "SSLv2" and version = "SSLv3"
+ or
+ this = "TLSv1" and version = ["TLSv1_1", "TLSv1_2", "TLSv1_3"]
+ or
+ this = ["TLSv1", "TLSv1_1"] and version = ["TLSv1_2", "TLSv1_3"]
+ or
+ this = ["TLSv1", "TLSv1_1", "TLSv1_2"] and version = "TLSv1_3"
+ }
+}
+
+abstract class ContextCreation extends DataFlow::CfgNode {
+ abstract DataFlow::CfgNode getProtocol();
+}
+
+abstract class ConnectionCreation extends DataFlow::CfgNode {
+ abstract DataFlow::CfgNode getContext();
+}
+
+abstract class ProtocolRestriction extends DataFlow::CfgNode {
+ abstract DataFlow::CfgNode getContext();
+
+ abstract ProtocolVersion getRestriction();
+}
+
+abstract class TlsLibrary extends string {
+ TlsLibrary() { this in ["ssl", "pyOpenSSL"] }
+
+ /** The name of a specific protocol version, known to be insecure. */
+ abstract string specific_insecure_version_name(ProtocolVersion version);
+
+ /** The name of an unspecific protocol version, say TLS, known to have insecure insatnces. */
+ abstract string unspecific_version_name();
+
+ abstract API::Node version_constants();
+
+ DataFlow::Node insecure_version(ProtocolVersion version) {
+ result = version_constants().getMember(specific_insecure_version_name(version)).getAUse()
+ }
+
+ DataFlow::Node unspecific_version() {
+ result = version_constants().getMember(unspecific_version_name()).getAUse()
+ }
+
+ abstract DataFlow::CfgNode default_context_creation();
+
+ abstract ContextCreation specific_context_creation();
+
+ ContextCreation insecure_context_creation(ProtocolVersion version) {
+ result = specific_context_creation() and
+ result.getProtocol() = insecure_version(version)
+ }
+
+ DataFlow::CfgNode unspecific_context_creation() {
+ result = default_context_creation()
+ or
+ result = specific_context_creation() and
+ result.(ContextCreation).getProtocol() = unspecific_version()
+ }
+
+ /** A connection is created in an outright insecure manner. */
+ abstract DataFlow::CfgNode insecure_connection_creation(ProtocolVersion version);
+
+ /** A connection is created from a context. */
+ abstract ConnectionCreation connection_creation();
+
+ abstract ProtocolRestriction protocol_restriction();
+}
From 87e1a062ea750a3b2acc95d75583099c055defe9 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Sun, 21 Feb 2021 12:08:11 +0100
Subject: [PATCH 016/433] Python: fluent api tests
---
.../Security/CWE-327/pyOpenSSL_fluent.py | 31 +++++++
.../Security/CWE-327/ssl_fluent.py | 87 +++++++++++++++++++
2 files changed, 118 insertions(+)
create mode 100644 python/ql/test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py
create mode 100644 python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
diff --git a/python/ql/test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py b/python/ql/test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py
new file mode 100644
index 000000000000..028c318be668
--- /dev/null
+++ b/python/ql/test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py
@@ -0,0 +1,31 @@
+import socket
+from OpenSSL import SSL
+
+def test_fluent():
+ hostname = 'www.python.org'
+ context = SSL.Context(SSL.SSLv23_METHOD)
+
+ conn = SSL.Connection(context, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
+ r = conn.connect((hostname, 443))
+ print(conn.get_protocol_version_name())
+
+
+def test_fluent_no_TLSv1():
+ hostname = 'www.python.org'
+ context = SSL.Context(SSL.SSLv23_METHOD)
+ context.set_options(SSL.OP_NO_TLSv1)
+
+ conn = SSL.Connection(context, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
+ r = conn.connect((hostname, 443))
+ print(conn.get_protocol_version_name())
+
+
+def test_fluent_safe():
+ hostname = 'www.python.org'
+ context = SSL.Context(SSL.SSLv23_METHOD)
+ context.set_options(SSL.OP_NO_TLSv1)
+ context.set_options(SSL.OP_NO_TLSv1_1)
+
+ conn = SSL.Connection(context, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
+ r = conn.connect((hostname, 443))
+ print(r, conn.get_protocol_version_name())
diff --git a/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py b/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
new file mode 100644
index 000000000000..9f9e01294cb5
--- /dev/null
+++ b/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
@@ -0,0 +1,87 @@
+import socket
+import ssl
+
+def test_fluent_tls():
+ hostname = 'www.python.org'
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS)
+
+ with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+
+def test_fluent_tls_no_TLSv1():
+ hostname = 'www.python.org'
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS)
+ context.options |= ssl.OP_NO_TLSv1
+
+ with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+def test_fluent_tls_safe():
+ hostname = 'www.python.org'
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS)
+ context.options |= ssl.OP_NO_TLSv1
+ context.options |= ssl.OP_NO_TLSv1_1
+
+ with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+def test_fluent_ssl():
+ hostname = 'www.python.org'
+ context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+
+ with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+
+def test_fluent_ssl_no_TLSv1():
+ hostname = 'www.python.org'
+ context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ context.options |= ssl.OP_NO_TLSv1
+
+ with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+def test_fluent_ssl_safe():
+ hostname = 'www.python.org'
+ context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ context.options |= ssl.OP_NO_TLSv1
+ context.options |= ssl.OP_NO_TLSv1_1
+
+ with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+def test_fluent_ssl_safe_combined():
+ hostname = 'www.python.org'
+ context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
+
+ with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+# From Python 3.7
+# see https://docs.python.org/3/library/ssl.html#ssl.SSLContext.minimum_version
+def test_fluent_ssl_unsafe_version():
+ hostname = 'www.python.org'
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS)
+ context.minimum_version = ssl.TLSVersion.TLSv1_1
+
+ with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+def test_fluent_ssl_safe_version():
+ hostname = 'www.python.org'
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS)
+ context.minimum_version = ssl.TLSVersion.TLSv1_3
+
+ with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
From ea8c6f04e2ba137b6501152f22b93f642e17b372 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Sun, 21 Feb 2021 12:09:56 +0100
Subject: [PATCH 017/433] Python: Update old test and qlhelp
---
.../Security/CWE-327/InsecureProtocol.qhelp | 6 ++---
python/ql/src/Security/CWE-327/ReadMe.md | 24 +++++++++++++++++++
.../Security/CWE-327/InsecureProtocol.py | 8 +++----
3 files changed, 31 insertions(+), 7 deletions(-)
create mode 100644 python/ql/src/Security/CWE-327/ReadMe.md
diff --git a/python/ql/src/Security/CWE-327/InsecureProtocol.qhelp b/python/ql/src/Security/CWE-327/InsecureProtocol.qhelp
index 63545129cc79..4a7364ca14e2 100644
--- a/python/ql/src/Security/CWE-327/InsecureProtocol.qhelp
+++ b/python/ql/src/Security/CWE-327/InsecureProtocol.qhelp
@@ -13,8 +13,8 @@
Ensure that a modern, strong protocol is used. All versions of SSL,
- and TLS 1.0 are known to be vulnerable to attacks. Using TLS 1.1 or
- above is strongly recommended.
+ and TLS versions 1.0 and 1.1 are known to be vulnerable to attacks.
+ Using TLS 1.2 or above is strongly recommended.
@@ -30,7 +30,7 @@
All cases should be updated to use a secure protocol, such as
- PROTOCOL_TLSv1_1
.
+ PROTOCOL_TLSv1_2
.
Note that ssl.wrap_socket
has been deprecated in
diff --git a/python/ql/src/Security/CWE-327/ReadMe.md b/python/ql/src/Security/CWE-327/ReadMe.md
new file mode 100644
index 000000000000..c5330a78fda9
--- /dev/null
+++ b/python/ql/src/Security/CWE-327/ReadMe.md
@@ -0,0 +1,24 @@
+# Current status (Feb 2021)
+
+This should be kept up to date; the world is moving fast and protocols are being broken.
+
+## Protocols
+
+- All versions of SSL are insecure
+- TLS 1.0 and TLS 1.1 are insecure
+- TLS 1.2 have some issues. but TLS 1.3 is not widely supported
+
+## Conection methods
+
+- `ssl.wrap_socket` is creating insecure connections, use `SSLContext.wrap_socket` instead. [link](https://docs.python.org/3/library/ssl.html#ssl.wrap_socket)
+ > Deprecated since version 3.7: Since Python 3.2 and 2.7.9, it is recommended to use the `SSLContext.wrap_socket()` instead of `wrap_socket()`. The top-level function is limited and creates an insecure client socket without server name indication or hostname matching.
+- Default consteructors are fine, a sluent api is used to constrain possible protocols later.
+
+## Current recomendation
+
+TLS 1.2 or TLS 1.3
+
+## Queries
+
+- `InsecureProtocol` detects uses of insecure protocols.
+- `InsecureDefaultProtocol` detect default constructions, this is no longer unsafe.
diff --git a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.py b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.py
index 5a6d044aa6de..cb21a6623c9e 100644
--- a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.py
+++ b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.py
@@ -1,5 +1,5 @@
import ssl
-from pyOpenSSL import SSL
+from OpenSSL import SSL
from ssl import SSLContext
# true positives
@@ -33,9 +33,9 @@
# secure versions
-ssl.wrap_socket(ssl_version=ssl.PROTOCOL_TLSv1_1)
-SSLContext(protocol=ssl.PROTOCOL_TLSv1_1)
-SSL.Context(SSL.TLSv1_1_METHOD)
+ssl.wrap_socket(ssl_version=ssl.PROTOCOL_TLSv1_2)
+SSLContext(protocol=ssl.PROTOCOL_TLSv1_2)
+SSL.Context(SSL.TLSv1_2_METHOD)
# possibly insecure default
ssl.wrap_socket()
From 3b856010f28ade83481b3e9fe553c4bb56f0f432 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Tue, 23 Feb 2021 19:29:30 +0100
Subject: [PATCH 018/433] Python: add TODO comment
---
python/ql/src/Security/CWE-327/Ssl.qll | 1 +
1 file changed, 1 insertion(+)
diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll
index 369149fd6384..e3247d5143c3 100644
--- a/python/ql/src/Security/CWE-327/Ssl.qll
+++ b/python/ql/src/Security/CWE-327/Ssl.qll
@@ -32,6 +32,7 @@ class OptionsAugOr extends ProtocolRestriction {
aa.getTarget() = attr.getNode() and
attr.getName() = "options" and
attr.getObject() = node and
+ // TODO: Use something like BoolExpr::impliesValue here
API::moduleImport("ssl").getMember("OP_NO_" + restriction).getAUse().asExpr() in [
aa.getValue(), aa.getValue().getAChildNode()
]
From d5171fc043cd4a66372780bcbe5c758cd7c594e2 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Feb 2021 21:12:34 +0100
Subject: [PATCH 019/433] Python: Comment everything
---
.../src/Security/CWE-327/FluentApiModel.qll | 15 +++++++++++++
.../src/Security/CWE-327/InsecureProtocol.ql | 13 ------------
python/ql/src/Security/CWE-327/PyOpenSSL.qll | 2 +-
python/ql/src/Security/CWE-327/Ssl.qll | 17 ++++++++++-----
.../src/Security/CWE-327/TlsLibraryModel.qll | 21 ++++++++++++++++---
5 files changed, 46 insertions(+), 22 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/FluentApiModel.qll b/python/ql/src/Security/CWE-327/FluentApiModel.qll
index 4c2278b86942..3dc12e2f434b 100644
--- a/python/ql/src/Security/CWE-327/FluentApiModel.qll
+++ b/python/ql/src/Security/CWE-327/FluentApiModel.qll
@@ -1,6 +1,11 @@
import python
import TlsLibraryModel
+/**
+ * Configuration to track flow from the creation of a context to
+ * that context being used to create a connection.
+ * Flow is broken if the insecure protocol of interest is being restricted.
+ */
class InsecureContextConfiguration extends DataFlow::Configuration {
TlsLibrary library;
@@ -25,28 +30,38 @@ class InsecureContextConfiguration extends DataFlow::Configuration {
}
}
+/** Configuration to specifically track the insecure protocol TLS 1.0 */
class AllowsTLSv1 extends InsecureContextConfiguration {
AllowsTLSv1() { this = library + "AllowsTLSv1" }
override string flag() { result = "TLSv1" }
}
+/** Configuration to specifically track the insecure protocol TLS 1.1 */
class AllowsTLSv1_1 extends InsecureContextConfiguration {
AllowsTLSv1_1() { this = library + "AllowsTLSv1_1" }
override string flag() { result = "TLSv1_1" }
}
+/**
+ * A connection is created from a context allowing an insecure protocol,
+ * and that protocol has not been restricted appropriately.
+ */
predicate unsafe_connection_creation(DataFlow::Node node, ProtocolVersion insecure_version) {
+ // Connection created from a context allowing TLS 1.0.
exists(AllowsTLSv1 c | c.hasFlowTo(node)) and
insecure_version = "TLSv1"
or
+ // Connection created from a context allowing TLS 1.1.
exists(AllowsTLSv1_1 c | c.hasFlowTo(node)) and
insecure_version = "TLSv1_1"
or
+ // Connection created from a context for an insecure protocol.
exists(TlsLibrary l | l.insecure_connection_creation(insecure_version) = node)
}
+/** A connection is created insecurely without reference to a context. */
predicate unsafe_context_creation(DataFlow::Node node, string insecure_version) {
exists(TlsLibrary l, ContextCreation cc | cc = l.insecure_context_creation(insecure_version) |
cc = node
diff --git a/python/ql/src/Security/CWE-327/InsecureProtocol.ql b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
index b873e5f3ce8f..c902d9a284d4 100644
--- a/python/ql/src/Security/CWE-327/InsecureProtocol.ql
+++ b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
@@ -12,22 +12,9 @@
import python
import FluentApiModel
-// string foo(ProtocolRestriction r) { result = r.getRestriction() }
-// The idea is to track flow from the creation of an insecure context to a use
-// such as `wrap_socket`. There should be a data-flow path for each insecure version
-// and each path should have a version specific sanitizer. This will allow fluent api
-// style code to block the paths one by one.
from DataFlow::Node node, string insecure_version
where
unsafe_connection_creation(node, insecure_version)
or
unsafe_context_creation(node, insecure_version)
select node, "Insecure SSL/TLS protocol version " + insecure_version //+ " specified in call to " + method_name + "."
-// from CallNode call, string method_name, string insecure_version
-// where
-// unsafe_ssl_wrap_socket_call(call, method_name, insecure_version, _)
-// or
-// unsafe_pyOpenSSL_Context_call(call, insecure_version) and method_name = "pyOpenSSL.SSL.Context"
-// select call,
-// "Insecure SSL/TLS protocol version " + insecure_version + " specified in call to " + method_name +
-// "."
diff --git a/python/ql/src/Security/CWE-327/PyOpenSSL.qll b/python/ql/src/Security/CWE-327/PyOpenSSL.qll
index b9a68648f935..a89c7ff08867 100644
--- a/python/ql/src/Security/CWE-327/PyOpenSSL.qll
+++ b/python/ql/src/Security/CWE-327/PyOpenSSL.qll
@@ -59,7 +59,7 @@ class PyOpenSSL extends TlsLibrary {
override API::Node version_constants() { result = API::moduleImport("OpenSSL").getMember("SSL") }
- override DataFlow::CfgNode default_context_creation() { none() }
+ override ContextCreation default_context_creation() { none() }
override ContextCreation specific_context_creation() {
result instanceof PyOpenSSLContextCreation
diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll
index e3247d5143c3..ba91b39bdeb4 100644
--- a/python/ql/src/Security/CWE-327/Ssl.qll
+++ b/python/ql/src/Security/CWE-327/Ssl.qll
@@ -13,6 +13,16 @@ class SSLContextCreation extends ContextCreation {
}
}
+class SSLDefaultContextCreation extends ContextCreation {
+ SSLDefaultContextCreation() {
+ this = API::moduleImport("ssl").getMember("create_default_context").getACall()
+ }
+
+ // Allowed insecure versions are "TLSv1" and "TLSv1_1"
+ // see https://docs.python.org/3/library/ssl.html#context-creation
+ override DataFlow::CfgNode getProtocol() { none() }
+}
+
class WrapSocketCall extends ConnectionCreation {
override CallNode node;
@@ -67,7 +77,6 @@ class Ssl extends TlsLibrary {
override string specific_insecure_version_name(ProtocolVersion version) {
version in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1"] and
result = "PROTOCOL_" + version
- // result in ["PROTOCOL_SSLv2", "PROTOCOL_SSLv3", "PROTOCOL_TLSv1", "PROTOCOL_TLSv1_1"]
}
override string unspecific_version_name() {
@@ -83,10 +92,8 @@ class Ssl extends TlsLibrary {
override API::Node version_constants() { result = API::moduleImport("ssl") }
- override DataFlow::CfgNode default_context_creation() {
- result = API::moduleImport("ssl").getMember("create_default_context").getACall() //and
- // see https://docs.python.org/3/library/ssl.html#context-creation
- // version in ["TLSv1", "TLSv1_1"]
+ override ContextCreation default_context_creation() {
+ result instanceof SSLDefaultContextCreation
}
override ContextCreation specific_context_creation() { result instanceof SSLContextCreation }
diff --git a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
index 41db81ad1c80..36e58acd9266 100644
--- a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
+++ b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
@@ -21,17 +21,24 @@ class ProtocolVersion extends string {
}
}
+/** The creation of a context. */
abstract class ContextCreation extends DataFlow::CfgNode {
+ /** Gets the requested protocol if any. */
abstract DataFlow::CfgNode getProtocol();
}
+/** The creation of a connection from a context. */
abstract class ConnectionCreation extends DataFlow::CfgNode {
+ /** Gets the context used to create the connection. */
abstract DataFlow::CfgNode getContext();
}
+/** A context is being restricted on which protocols it can accepts. */
abstract class ProtocolRestriction extends DataFlow::CfgNode {
+ /** Gets the context being restricted. */
abstract DataFlow::CfgNode getContext();
+ /** Gets the protocol version being disallowed. */
abstract ProtocolVersion getRestriction();
}
@@ -41,28 +48,35 @@ abstract class TlsLibrary extends string {
/** The name of a specific protocol version, known to be insecure. */
abstract string specific_insecure_version_name(ProtocolVersion version);
- /** The name of an unspecific protocol version, say TLS, known to have insecure insatnces. */
+ /** The name of an unspecific protocol version, say TLS, known to have insecure instances. */
abstract string unspecific_version_name();
+ /** The module or class holding the version constants. */
abstract API::Node version_constants();
+ /** A dataflow node representing a specific protocol version, known to be insecure. */
DataFlow::Node insecure_version(ProtocolVersion version) {
result = version_constants().getMember(specific_insecure_version_name(version)).getAUse()
}
+ /** A dataflow node representing an unspecific protocol version, say TLS, known to have insecure instances. */
DataFlow::Node unspecific_version() {
result = version_constants().getMember(unspecific_version_name()).getAUse()
}
- abstract DataFlow::CfgNode default_context_creation();
+ /** The creation of a context with a deafult protocol. */
+ abstract ContextCreation default_context_creation();
+ /** The creation of a context with a specific protocol. */
abstract ContextCreation specific_context_creation();
+ /** The creation of a context with a specific protocol version, known to be insecure. */
ContextCreation insecure_context_creation(ProtocolVersion version) {
result = specific_context_creation() and
result.getProtocol() = insecure_version(version)
}
+ /** The creation of a context with an unspecific protocol version, say TLS, known to have insecure instances. */
DataFlow::CfgNode unspecific_context_creation() {
result = default_context_creation()
or
@@ -70,11 +84,12 @@ abstract class TlsLibrary extends string {
result.(ContextCreation).getProtocol() = unspecific_version()
}
- /** A connection is created in an outright insecure manner. */
+ /** A connection is created in an insecure manner, not from a context. */
abstract DataFlow::CfgNode insecure_connection_creation(ProtocolVersion version);
/** A connection is created from a context. */
abstract ConnectionCreation connection_creation();
+ /** A context is being restricted on which protocols it can accepts. */
abstract ProtocolRestriction protocol_restriction();
}
From 9e696ff0fbaac008a12bfa1a7241a0f0140bbc9d Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Feb 2021 21:58:00 +0100
Subject: [PATCH 020/433] Python: Add false negative to test
---
.../ql/test/query-tests/Security/CWE-327/ssl_fluent.py | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py b/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
index 9f9e01294cb5..252ab0a9e49a 100644
--- a/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
+++ b/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
@@ -85,3 +85,13 @@ def test_fluent_ssl_safe_version():
with socket.create_connection((hostname, 443)) as sock:
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
print(ssock.version())
+
+# Taken from https://docs.python.org/3/library/ssl.html#context-creation
+def test_fluent_explicitly_unsafe():
+ hostname = 'www.python.org'
+ context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
+ context.options &= ~ssl.OP_NO_SSLv3 # This not recognized
+
+ with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock: # SSLv3 not flagged here
+ print(ssock.version())
From 60525ec3018924898d47d72d133cb0b1431a2bea Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Feb 2021 21:58:30 +0100
Subject: [PATCH 021/433] Python: Also track offending call update test
expectations at this point
---
.../src/Security/CWE-327/FluentApiModel.qll | 20 +++++++---
.../src/Security/CWE-327/InsecureProtocol.ql | 16 ++++++--
.../CWE-327/InsecureProtocol.expected | 38 ++++++++++++-------
3 files changed, 50 insertions(+), 24 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/FluentApiModel.qll b/python/ql/src/Security/CWE-327/FluentApiModel.qll
index 3dc12e2f434b..23922a905950 100644
--- a/python/ql/src/Security/CWE-327/FluentApiModel.qll
+++ b/python/ql/src/Security/CWE-327/FluentApiModel.qll
@@ -48,22 +48,30 @@ class AllowsTLSv1_1 extends InsecureContextConfiguration {
* A connection is created from a context allowing an insecure protocol,
* and that protocol has not been restricted appropriately.
*/
-predicate unsafe_connection_creation(DataFlow::Node node, ProtocolVersion insecure_version) {
+predicate unsafe_connection_creation(
+ DataFlow::Node node, ProtocolVersion insecure_version, CallNode call
+) {
// Connection created from a context allowing TLS 1.0.
- exists(AllowsTLSv1 c | c.hasFlowTo(node)) and
+ exists(AllowsTLSv1 c, ContextCreation cc | c.hasFlow(cc, node) | cc.getNode() = call) and
insecure_version = "TLSv1"
or
// Connection created from a context allowing TLS 1.1.
- exists(AllowsTLSv1_1 c | c.hasFlowTo(node)) and
+ exists(AllowsTLSv1_1 c, ContextCreation cc | c.hasFlow(cc, node) | cc.getNode() = call) and
insecure_version = "TLSv1_1"
or
// Connection created from a context for an insecure protocol.
- exists(TlsLibrary l | l.insecure_connection_creation(insecure_version) = node)
+ exists(TlsLibrary l, DataFlow::CfgNode cc |
+ cc = l.insecure_connection_creation(insecure_version)
+ |
+ cc = node and
+ cc.getNode() = call
+ )
}
/** A connection is created insecurely without reference to a context. */
-predicate unsafe_context_creation(DataFlow::Node node, string insecure_version) {
+predicate unsafe_context_creation(DataFlow::Node node, string insecure_version, CallNode call) {
exists(TlsLibrary l, ContextCreation cc | cc = l.insecure_context_creation(insecure_version) |
- cc = node
+ cc = node and
+ cc.getNode() = call
)
}
diff --git a/python/ql/src/Security/CWE-327/InsecureProtocol.ql b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
index c902d9a284d4..51247dbd75aa 100644
--- a/python/ql/src/Security/CWE-327/InsecureProtocol.ql
+++ b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
@@ -12,9 +12,17 @@
import python
import FluentApiModel
-from DataFlow::Node node, string insecure_version
+string callName(AstNode call) {
+ result = call.(Name).getId()
+ or
+ exists(Attribute a | a = call | result = callName(a.getObject()) + "." + a.getName())
+}
+
+from DataFlow::Node node, string insecure_version, CallNode call
where
- unsafe_connection_creation(node, insecure_version)
+ unsafe_connection_creation(node, insecure_version, call)
or
- unsafe_context_creation(node, insecure_version)
-select node, "Insecure SSL/TLS protocol version " + insecure_version //+ " specified in call to " + method_name + "."
+ unsafe_context_creation(node, insecure_version, call)
+select node, "Insecure SSL/TLS protocol version " + insecure_version + " specified in $@ ", call,
+ "call to " + callName(call.getFunction().getNode())
+//+ " specified in call to " + method_name + "."
diff --git a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
index 33542e4a048e..7b646f9fd3e8 100644
--- a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
+++ b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
@@ -1,14 +1,24 @@
-| InsecureProtocol.py:6:1:6:47 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version PROTOCOL_SSLv2 specified in call to deprecated method ssl.wrap_socket. |
-| InsecureProtocol.py:7:1:7:47 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version PROTOCOL_SSLv3 specified in call to deprecated method ssl.wrap_socket. |
-| InsecureProtocol.py:8:1:8:47 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version PROTOCOL_TLSv1 specified in call to deprecated method ssl.wrap_socket. |
-| InsecureProtocol.py:10:1:10:39 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version PROTOCOL_SSLv2 specified in call to ssl.SSLContext. |
-| InsecureProtocol.py:11:1:11:39 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version PROTOCOL_SSLv3 specified in call to ssl.SSLContext. |
-| InsecureProtocol.py:12:1:12:39 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version PROTOCOL_TLSv1 specified in call to ssl.SSLContext. |
-| InsecureProtocol.py:14:1:14:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2_METHOD specified in call to pyOpenSSL.SSL.Context. |
-| InsecureProtocol.py:15:1:15:30 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv23_METHOD specified in call to pyOpenSSL.SSL.Context. |
-| InsecureProtocol.py:16:1:16:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv3_METHOD specified in call to pyOpenSSL.SSL.Context. |
-| InsecureProtocol.py:17:1:17:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version TLSv1_METHOD specified in call to pyOpenSSL.SSL.Context. |
-| InsecureProtocol.py:32:1:32:19 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2_METHOD specified in call to pyOpenSSL.SSL.Context. |
-| InsecureProtocol.py:48:1:48:43 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version PROTOCOL_SSLv2 specified in call to deprecated method ssl.wrap_socket. |
-| InsecureProtocol.py:49:1:49:35 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version PROTOCOL_SSLv2 specified in call to ssl.SSLContext. |
-| InsecureProtocol.py:52:1:52:33 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv23_METHOD specified in call to ssl.SSLContext. |
+| InsecureProtocol.py:6:1:6:47 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified in $@ | InsecureProtocol.py:6:1:6:47 | ControlFlowNode for Attribute() | call to ssl.wrap_socket |
+| InsecureProtocol.py:7:1:7:47 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv3 specified in $@ | InsecureProtocol.py:7:1:7:47 | ControlFlowNode for Attribute() | call to ssl.wrap_socket |
+| InsecureProtocol.py:8:1:8:47 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version TLSv1 specified in $@ | InsecureProtocol.py:8:1:8:47 | ControlFlowNode for Attribute() | call to ssl.wrap_socket |
+| InsecureProtocol.py:10:1:10:39 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version SSLv2 specified in $@ | InsecureProtocol.py:10:1:10:39 | ControlFlowNode for SSLContext() | call to SSLContext |
+| InsecureProtocol.py:11:1:11:39 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version SSLv3 specified in $@ | InsecureProtocol.py:11:1:11:39 | ControlFlowNode for SSLContext() | call to SSLContext |
+| InsecureProtocol.py:12:1:12:39 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version TLSv1 specified in $@ | InsecureProtocol.py:12:1:12:39 | ControlFlowNode for SSLContext() | call to SSLContext |
+| InsecureProtocol.py:14:1:14:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified in $@ | InsecureProtocol.py:14:1:14:29 | ControlFlowNode for Attribute() | call to SSL.Context |
+| InsecureProtocol.py:16:1:16:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv3 specified in $@ | InsecureProtocol.py:16:1:16:29 | ControlFlowNode for Attribute() | call to SSL.Context |
+| InsecureProtocol.py:17:1:17:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version TLSv1 specified in $@ | InsecureProtocol.py:17:1:17:29 | ControlFlowNode for Attribute() | call to SSL.Context |
+| InsecureProtocol.py:32:1:32:19 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified in $@ | InsecureProtocol.py:32:1:32:19 | ControlFlowNode for Attribute() | call to SSL.Context |
+| InsecureProtocol.py:48:1:48:43 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified in $@ | InsecureProtocol.py:48:1:48:43 | ControlFlowNode for Attribute() | call to ssl.wrap_socket |
+| InsecureProtocol.py:49:1:49:35 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version SSLv2 specified in $@ | InsecureProtocol.py:49:1:49:35 | ControlFlowNode for SSLContext() | call to SSLContext |
+| pyOpenSSL_fluent.py:8:27:8:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | pyOpenSSL_fluent.py:6:15:6:44 | ControlFlowNode for Attribute() | call to SSL.Context |
+| pyOpenSSL_fluent.py:8:27:8:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | pyOpenSSL_fluent.py:6:15:6:44 | ControlFlowNode for Attribute() | call to SSL.Context |
+| pyOpenSSL_fluent.py:18:27:18:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | pyOpenSSL_fluent.py:15:15:15:44 | ControlFlowNode for Attribute() | call to SSL.Context |
+| ssl_fluent.py:9:14:9:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:6:15:6:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:9:14:9:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:6:15:6:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:19:14:19:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:15:15:15:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:37:14:37:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:34:15:34:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:37:14:37:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:34:15:34:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:47:14:47:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:43:15:43:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:77:14:77:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:73:15:73:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:96:14:96:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:92:15:92:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context |
+| ssl_fluent.py:96:14:96:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:92:15:92:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context |
From 7a1d953fcac8d159440345dd26dd13b4136f1481 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Tue, 2 Mar 2021 22:46:01 +0100
Subject: [PATCH 022/433] Python: More tests
---
.../CWE-327/InsecureProtocol.expected | 14 ++-
.../Security/CWE-327/ssl_fluent.py | 96 +++++++++++++++++++
2 files changed, 107 insertions(+), 3 deletions(-)
diff --git a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
index 7b646f9fd3e8..e116662ce659 100644
--- a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
+++ b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
@@ -19,6 +19,14 @@
| ssl_fluent.py:37:14:37:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:34:15:34:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
| ssl_fluent.py:37:14:37:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:34:15:34:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
| ssl_fluent.py:47:14:47:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:43:15:43:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:77:14:77:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:73:15:73:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:96:14:96:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:92:15:92:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context |
-| ssl_fluent.py:96:14:96:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:92:15:92:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context |
+| ssl_fluent.py:75:14:75:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:71:15:71:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:75:14:75:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:71:15:71:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:128:15:128:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:128:15:128:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:104:14:104:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:104:14:104:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:173:14:173:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:169:15:169:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:192:14:192:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:188:15:188:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context |
+| ssl_fluent.py:192:14:192:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:188:15:188:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context |
diff --git a/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py b/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
index 252ab0a9e49a..eceacaebe215 100644
--- a/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
+++ b/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
@@ -66,6 +66,102 @@ def test_fluent_ssl_safe_combined():
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
print(ssock.version())
+def test_fluent_ssl_unsafe_combined_wrongly():
+ hostname = 'www.python.org'
+ context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ context.options |= ssl.OP_NO_TLSv1 & ssl.OP_NO_TLSv1_1
+
+ with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+def test_fluent_ssl_safe_combined_multiple():
+ hostname = 'www.python.org'
+ context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_TLSv1_2
+
+ with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+
+def create_relaxed_context():
+ return ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+
+def create_secure_context():
+ context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
+ return context
+
+def create_connection(context):
+ with socket.create_connection(('www.python.org', 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+def test_delegated_context_unsafe():
+ context = create_relaxed_context()
+ with socket.create_connection(('www.python.org', 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+def test_delegated_context_safe():
+ context = create_secure_context()
+ with socket.create_connection(('www.python.org', 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+def test_delegated_context_made_safe():
+ context = create_relaxed_context()
+ context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
+ with socket.create_connection(('www.python.org', 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+def test_delegated_context_made_unsafe():
+ context = create_secure_context()
+ context.options &= ~ssl.OP_NO_TLSv1_1
+ with socket.create_connection(('www.python.org', 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+def test_delegated_connection_unsafe():
+ context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ create_connection(context)
+
+def test_delegated_connection_safe():
+ context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
+ create_connection(context)
+
+def test_delegated_connection_made_safe():
+ context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
+ create_connection(context)
+
+def test_delegated_connection_made_unsafe():
+ context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
+ context.options &= ~ssl.OP_NO_TLSv1_1
+ create_connection(context)
+
+def test_delegated_unsafe():
+ context = create_relaxed_context()
+ create_connection(context)
+
+def test_delegated_safe():
+ context = create_secure_context()
+ create_connection(context)
+
+def test_delegated_made_safe():
+ context = create_relaxed_context()
+ context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
+ create_connection(context)
+
+def test_delegated_made_unsafe():
+ context = create_secure_context()
+ context.options &= ~ssl.OP_NO_TLSv1_1
+ create_connection(context)
+
# From Python 3.7
# see https://docs.python.org/3/library/ssl.html#ssl.SSLContext.minimum_version
def test_fluent_ssl_unsafe_version():
From 97d26687fed6b102c0c2be075ec6a0350a9ef246 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Mon, 1 Mar 2021 15:04:48 +0100
Subject: [PATCH 023/433] Python: Improve logic of bit fields
---
python/ql/src/Security/CWE-327/Ssl.qll | 31 +++++++++++++++++++++-----
1 file changed, 26 insertions(+), 5 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll
index ba91b39bdeb4..4caa0ae7302e 100644
--- a/python/ql/src/Security/CWE-327/Ssl.qll
+++ b/python/ql/src/Security/CWE-327/Ssl.qll
@@ -37,15 +37,17 @@ class OptionsAugOr extends ProtocolRestriction {
ProtocolVersion restriction;
OptionsAugOr() {
- exists(AugAssign aa, AttrNode attr |
+ exists(AugAssign aa, AttrNode attr, Expr flag |
aa.getOperation().getOp() instanceof BitOr and
aa.getTarget() = attr.getNode() and
attr.getName() = "options" and
attr.getObject() = node and
- // TODO: Use something like BoolExpr::impliesValue here
- API::moduleImport("ssl").getMember("OP_NO_" + restriction).getAUse().asExpr() in [
- aa.getValue(), aa.getValue().getAChildNode()
- ]
+ flag = API::moduleImport("ssl").getMember("OP_NO_" + restriction).getAUse().asExpr() and
+ (
+ aa.getValue() = flag
+ or
+ impliesValue(aa.getValue(), flag, false, false)
+ )
)
}
@@ -54,6 +56,25 @@ class OptionsAugOr extends ProtocolRestriction {
override ProtocolVersion getRestriction() { result = restriction }
}
+/** Whether `part` evaluates to `partIsTrue` if `whole` evaluates to `wholeIsTrue`. */
+predicate impliesValue(BinaryExpr whole, Expr part, boolean partIsTrue, boolean wholeIsTrue) {
+ whole.getOp() instanceof BitAnd and
+ (
+ wholeIsTrue = true and partIsTrue = true and part in [whole.getLeft(), whole.getRight()]
+ or
+ wholeIsTrue = true and
+ impliesValue([whole.getLeft(), whole.getRight()], part, partIsTrue, wholeIsTrue)
+ )
+ or
+ whole.getOp() instanceof BitOr and
+ (
+ wholeIsTrue = false and partIsTrue = false and part in [whole.getLeft(), whole.getRight()]
+ or
+ wholeIsTrue = false and
+ impliesValue([whole.getLeft(), whole.getRight()], part, partIsTrue, wholeIsTrue)
+ )
+}
+
class ContextSetVersion extends ProtocolRestriction {
string restriction;
From cbbc7b2bcd160bdfc268252644f35daf4c1bcfd9 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Wed, 3 Mar 2021 23:42:48 +0100
Subject: [PATCH 024/433] Python: support unrestrictions Also pyOpenSSL allows
SSL 2 and SSL 3 on `SSLv23`
---
.../src/Security/CWE-327/FluentApiModel.qll | 62 ++++++++--------
.../src/Security/CWE-327/InsecureProtocol.ql | 26 +++++--
python/ql/src/Security/CWE-327/PyOpenSSL.qll | 18 +++--
python/ql/src/Security/CWE-327/Ssl.qll | 63 ++++++++++++----
.../src/Security/CWE-327/TlsLibraryModel.qll | 48 +++++++++++--
.../CWE-327/InsecureProtocol.expected | 72 ++++++++++---------
6 files changed, 200 insertions(+), 89 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/FluentApiModel.qll b/python/ql/src/Security/CWE-327/FluentApiModel.qll
index 23922a905950..d222af704997 100644
--- a/python/ql/src/Security/CWE-327/FluentApiModel.qll
+++ b/python/ql/src/Security/CWE-327/FluentApiModel.qll
@@ -8,40 +8,44 @@ import TlsLibraryModel
*/
class InsecureContextConfiguration extends DataFlow::Configuration {
TlsLibrary library;
+ ProtocolVersion tracked_version;
- InsecureContextConfiguration() { this = library + ["AllowsTLSv1", "AllowsTLSv1_1"] }
+ InsecureContextConfiguration() {
+ this = library + "Allows" + tracked_version and
+ tracked_version.isInsecure()
+ }
+
+ ProtocolVersion getTrackedVersion() { result = tracked_version }
override predicate isSource(DataFlow::Node source) {
- source = library.unspecific_context_creation()
+ // source = library.unspecific_context_creation()
+ exists(ProtocolUnrestriction pu |
+ pu = library.protocol_unrestriction() and
+ pu.getUnrestriction() = tracked_version
+ |
+ source = pu.getContext()
+ )
}
override predicate isSink(DataFlow::Node sink) {
sink = library.connection_creation().getContext()
}
- abstract string flag();
-
override predicate isBarrierOut(DataFlow::Node node) {
exists(ProtocolRestriction r |
r = library.protocol_restriction() and
node = r.getContext() and
- r.getRestriction() = flag()
+ r.getRestriction() = tracked_version
)
}
-}
-
-/** Configuration to specifically track the insecure protocol TLS 1.0 */
-class AllowsTLSv1 extends InsecureContextConfiguration {
- AllowsTLSv1() { this = library + "AllowsTLSv1" }
-
- override string flag() { result = "TLSv1" }
-}
-/** Configuration to specifically track the insecure protocol TLS 1.1 */
-class AllowsTLSv1_1 extends InsecureContextConfiguration {
- AllowsTLSv1_1() { this = library + "AllowsTLSv1_1" }
-
- override string flag() { result = "TLSv1_1" }
+ override predicate isBarrierIn(DataFlow::Node node) {
+ exists(ProtocolUnrestriction r |
+ r = library.protocol_unrestriction() and
+ node = r.getContext() and
+ r.getUnrestriction() = tracked_version
+ )
+ }
}
/**
@@ -49,22 +53,22 @@ class AllowsTLSv1_1 extends InsecureContextConfiguration {
* and that protocol has not been restricted appropriately.
*/
predicate unsafe_connection_creation(
- DataFlow::Node node, ProtocolVersion insecure_version, CallNode call
+ DataFlow::Node creation, ProtocolVersion insecure_version, DataFlow::Node source, boolean specific
) {
- // Connection created from a context allowing TLS 1.0.
- exists(AllowsTLSv1 c, ContextCreation cc | c.hasFlow(cc, node) | cc.getNode() = call) and
- insecure_version = "TLSv1"
- or
- // Connection created from a context allowing TLS 1.1.
- exists(AllowsTLSv1_1 c, ContextCreation cc | c.hasFlow(cc, node) | cc.getNode() = call) and
- insecure_version = "TLSv1_1"
+ // Connection created from a context allowing `insecure_version`.
+ exists(InsecureContextConfiguration c, ProtocolUnrestriction cc | c.hasFlow(cc, creation) |
+ insecure_version = c.getTrackedVersion() and
+ source = cc and
+ specific = false
+ )
or
- // Connection created from a context for an insecure protocol.
+ // Connection created from a context specifying `insecure_version`.
exists(TlsLibrary l, DataFlow::CfgNode cc |
cc = l.insecure_connection_creation(insecure_version)
|
- cc = node and
- cc.getNode() = call
+ creation = cc and
+ source = cc and
+ specific = true
)
}
diff --git a/python/ql/src/Security/CWE-327/InsecureProtocol.ql b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
index 51247dbd75aa..194cc1f5ec1d 100644
--- a/python/ql/src/Security/CWE-327/InsecureProtocol.ql
+++ b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
@@ -18,11 +18,25 @@ string callName(AstNode call) {
exists(Attribute a | a = call | result = callName(a.getObject()) + "." + a.getName())
}
-from DataFlow::Node node, string insecure_version, CallNode call
+string sourceName(DataFlow::Node source) {
+ result = "call to " + callName(source.asCfgNode().(CallNode).getFunction().getNode())
+ or
+ not source.asCfgNode() instanceof CallNode and
+ not source instanceof ContextCreation and
+ result = "context modification"
+}
+
+string verb(boolean specific) {
+ specific = true and result = "specified"
+ or
+ specific = false and result = "allowed"
+}
+
+from DataFlow::Node creation, string insecure_version, DataFlow::Node source, boolean specific
where
- unsafe_connection_creation(node, insecure_version, call)
+ unsafe_connection_creation(creation, insecure_version, source, specific)
or
- unsafe_context_creation(node, insecure_version, call)
-select node, "Insecure SSL/TLS protocol version " + insecure_version + " specified in $@ ", call,
- "call to " + callName(call.getFunction().getNode())
-//+ " specified in call to " + method_name + "."
+ unsafe_context_creation(creation, insecure_version, source.asCfgNode()) and specific = true
+select creation,
+ "Insecure SSL/TLS protocol version " + insecure_version + " " + verb(specific) + " by $@ ",
+ source, sourceName(source)
diff --git a/python/ql/src/Security/CWE-327/PyOpenSSL.qll b/python/ql/src/Security/CWE-327/PyOpenSSL.qll
index a89c7ff08867..3c36c568ef5b 100644
--- a/python/ql/src/Security/CWE-327/PyOpenSSL.qll
+++ b/python/ql/src/Security/CWE-327/PyOpenSSL.qll
@@ -26,6 +26,8 @@ class ConnectionCall extends ConnectionCreation {
}
}
+// This cannot be used to unrestrict,
+// see https://www.pyopenssl.org/en/stable/api/ssl.html#OpenSSL.SSL.Context.set_options
class SetOptionsCall extends ProtocolRestriction {
override CallNode node;
@@ -42,6 +44,10 @@ class SetOptionsCall extends ProtocolRestriction {
}
}
+class UnspecificPyOpenSSLContextCreation extends PyOpenSSLContextCreation, UnspecificContextCreation {
+ UnspecificPyOpenSSLContextCreation() { library = "pyOpenSSL" }
+}
+
class PyOpenSSL extends TlsLibrary {
PyOpenSSL() { this = "pyOpenSSL" }
@@ -50,11 +56,9 @@ class PyOpenSSL extends TlsLibrary {
result = version + "_METHOD"
}
- override string unspecific_version_name() {
- result in [
- "TLS_METHOD", // This is not actually available in pyOpenSSL yet
- "SSLv23_METHOD" // This is what can negotiate TLS 1.3 (indeed, I know, I did test that..)
- ]
+ override string unspecific_version_name(ProtocolFamily family) {
+ // `"TLS_METHOD"` is not actually available in pyOpenSSL yet, but should be coming soon..
+ result = family + "_METHOD"
}
override API::Node version_constants() { result = API::moduleImport("OpenSSL").getMember("SSL") }
@@ -70,4 +74,8 @@ class PyOpenSSL extends TlsLibrary {
override ConnectionCreation connection_creation() { result instanceof ConnectionCall }
override ProtocolRestriction protocol_restriction() { result instanceof SetOptionsCall }
+
+ override ProtocolUnrestriction protocol_unrestriction() {
+ result instanceof UnspecificPyOpenSSLContextCreation
+ }
}
diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll
index 4caa0ae7302e..e749bb9bc3c9 100644
--- a/python/ql/src/Security/CWE-327/Ssl.qll
+++ b/python/ql/src/Security/CWE-327/Ssl.qll
@@ -56,6 +56,31 @@ class OptionsAugOr extends ProtocolRestriction {
override ProtocolVersion getRestriction() { result = restriction }
}
+class OptionsAugAndNot extends ProtocolUnrestriction {
+ ProtocolVersion restriction;
+
+ OptionsAugAndNot() {
+ exists(AugAssign aa, AttrNode attr, Expr flag, UnaryExpr notFlag |
+ aa.getOperation().getOp() instanceof BitAnd and
+ aa.getTarget() = attr.getNode() and
+ attr.getName() = "options" and
+ attr.getObject() = node and
+ notFlag.getOp() instanceof Invert and
+ notFlag.getOperand() = flag and
+ flag = API::moduleImport("ssl").getMember("OP_NO_" + restriction).getAUse().asExpr() and
+ (
+ aa.getValue() = notFlag
+ or
+ impliesValue(aa.getValue(), notFlag, true, true)
+ )
+ )
+ }
+
+ override DataFlow::CfgNode getContext() { result = this }
+
+ override ProtocolVersion getUnrestriction() { result = restriction }
+}
+
/** Whether `part` evaluates to `partIsTrue` if `whole` evaluates to `wholeIsTrue`. */
predicate impliesValue(BinaryExpr whole, Expr part, boolean partIsTrue, boolean wholeIsTrue) {
whole.getOp() instanceof BitAnd and
@@ -75,8 +100,8 @@ predicate impliesValue(BinaryExpr whole, Expr part, boolean partIsTrue, boolean
)
}
-class ContextSetVersion extends ProtocolRestriction {
- string restriction;
+class ContextSetVersion extends ProtocolRestriction, ProtocolUnrestriction {
+ ProtocolVersion restriction;
ContextSetVersion() {
exists(Attributes::AttrWrite aw |
@@ -90,6 +115,21 @@ class ContextSetVersion extends ProtocolRestriction {
override DataFlow::CfgNode getContext() { result = this }
override ProtocolVersion getRestriction() { result.lessThan(restriction) }
+
+ override ProtocolVersion getUnrestriction() {
+ restriction = result or restriction.lessThan(result)
+ }
+}
+
+class UnspecificSSLContextCreation extends SSLContextCreation, UnspecificContextCreation {
+ UnspecificSSLContextCreation() { library = "ssl" }
+
+ override ProtocolVersion getUnrestriction() {
+ result = UnspecificContextCreation.super.getUnrestriction() and
+ // These are turned off by default
+ // see https://docs.python.org/3/library/ssl.html#ssl-contexts
+ not result in ["SSLv2", "SSLv3"]
+ }
}
class Ssl extends TlsLibrary {
@@ -100,16 +140,7 @@ class Ssl extends TlsLibrary {
result = "PROTOCOL_" + version
}
- override string unspecific_version_name() {
- result =
- "PROTOCOL_" +
- [
- "TLS",
- // This can negotiate a TLS 1.3 connection (!)
- // see https://docs.python.org/3/library/ssl.html#ssl-contexts
- "SSLv23"
- ]
- }
+ override string unspecific_version_name(ProtocolFamily family) { result = "PROTOCOL_" + family }
override API::Node version_constants() { result = API::moduleImport("ssl") }
@@ -132,4 +163,12 @@ class Ssl extends TlsLibrary {
or
result instanceof ContextSetVersion
}
+
+ override ProtocolUnrestriction protocol_unrestriction() {
+ result instanceof OptionsAugAndNot
+ or
+ result instanceof ContextSetVersion
+ or
+ result instanceof UnspecificSSLContextCreation
+ }
}
diff --git a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
index 36e58acd9266..97ceec006887 100644
--- a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
+++ b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
@@ -19,6 +19,13 @@ class ProtocolVersion extends string {
or
this = ["TLSv1", "TLSv1_1", "TLSv1_2"] and version = "TLSv1_3"
}
+
+ predicate isInsecure() { this in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1"] }
+}
+
+/** An unspecific protocol version */
+class ProtocolFamily extends string {
+ ProtocolFamily() { this in ["SSLv23", "TLS"] }
}
/** The creation of a context. */
@@ -42,6 +49,34 @@ abstract class ProtocolRestriction extends DataFlow::CfgNode {
abstract ProtocolVersion getRestriction();
}
+/** A context is being relaxed on which protocols it can accepts. */
+abstract class ProtocolUnrestriction extends DataFlow::CfgNode {
+ /** Gets the context being relaxed. */
+ abstract DataFlow::CfgNode getContext();
+
+ /** Gets the protocol version being allowed. */
+ abstract ProtocolVersion getUnrestriction();
+}
+
+abstract class UnspecificContextCreation extends ContextCreation, ProtocolUnrestriction {
+ TlsLibrary library;
+ ProtocolFamily family;
+
+ UnspecificContextCreation() { this.getProtocol() = library.unspecific_version(family) }
+
+ override DataFlow::CfgNode getContext() { result = this }
+
+ override ProtocolVersion getUnrestriction() {
+ family = "TLS" and
+ result in ["TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"]
+ or
+ // This can negotiate a TLS 1.3 connection (!)
+ // see https://docs.python.org/3/library/ssl.html#ssl-contexts
+ family = "SSLv23" and
+ result in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"]
+ }
+}
+
abstract class TlsLibrary extends string {
TlsLibrary() { this in ["ssl", "pyOpenSSL"] }
@@ -49,7 +84,7 @@ abstract class TlsLibrary extends string {
abstract string specific_insecure_version_name(ProtocolVersion version);
/** The name of an unspecific protocol version, say TLS, known to have insecure instances. */
- abstract string unspecific_version_name();
+ abstract string unspecific_version_name(ProtocolFamily family);
/** The module or class holding the version constants. */
abstract API::Node version_constants();
@@ -60,8 +95,8 @@ abstract class TlsLibrary extends string {
}
/** A dataflow node representing an unspecific protocol version, say TLS, known to have insecure instances. */
- DataFlow::Node unspecific_version() {
- result = version_constants().getMember(unspecific_version_name()).getAUse()
+ DataFlow::Node unspecific_version(ProtocolFamily family) {
+ result = version_constants().getMember(unspecific_version_name(family)).getAUse()
}
/** The creation of a context with a deafult protocol. */
@@ -77,11 +112,11 @@ abstract class TlsLibrary extends string {
}
/** The creation of a context with an unspecific protocol version, say TLS, known to have insecure instances. */
- DataFlow::CfgNode unspecific_context_creation() {
+ DataFlow::CfgNode unspecific_context_creation(ProtocolFamily family) {
result = default_context_creation()
or
result = specific_context_creation() and
- result.(ContextCreation).getProtocol() = unspecific_version()
+ result.(ContextCreation).getProtocol() = unspecific_version(family)
}
/** A connection is created in an insecure manner, not from a context. */
@@ -92,4 +127,7 @@ abstract class TlsLibrary extends string {
/** A context is being restricted on which protocols it can accepts. */
abstract ProtocolRestriction protocol_restriction();
+
+ /** A context is being relaxed on which protocols it can accepts. */
+ abstract ProtocolUnrestriction protocol_unrestriction();
}
diff --git a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
index e116662ce659..afd9cc15d9f4 100644
--- a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
+++ b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
@@ -1,32 +1,40 @@
-| InsecureProtocol.py:6:1:6:47 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified in $@ | InsecureProtocol.py:6:1:6:47 | ControlFlowNode for Attribute() | call to ssl.wrap_socket |
-| InsecureProtocol.py:7:1:7:47 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv3 specified in $@ | InsecureProtocol.py:7:1:7:47 | ControlFlowNode for Attribute() | call to ssl.wrap_socket |
-| InsecureProtocol.py:8:1:8:47 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version TLSv1 specified in $@ | InsecureProtocol.py:8:1:8:47 | ControlFlowNode for Attribute() | call to ssl.wrap_socket |
-| InsecureProtocol.py:10:1:10:39 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version SSLv2 specified in $@ | InsecureProtocol.py:10:1:10:39 | ControlFlowNode for SSLContext() | call to SSLContext |
-| InsecureProtocol.py:11:1:11:39 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version SSLv3 specified in $@ | InsecureProtocol.py:11:1:11:39 | ControlFlowNode for SSLContext() | call to SSLContext |
-| InsecureProtocol.py:12:1:12:39 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version TLSv1 specified in $@ | InsecureProtocol.py:12:1:12:39 | ControlFlowNode for SSLContext() | call to SSLContext |
-| InsecureProtocol.py:14:1:14:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified in $@ | InsecureProtocol.py:14:1:14:29 | ControlFlowNode for Attribute() | call to SSL.Context |
-| InsecureProtocol.py:16:1:16:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv3 specified in $@ | InsecureProtocol.py:16:1:16:29 | ControlFlowNode for Attribute() | call to SSL.Context |
-| InsecureProtocol.py:17:1:17:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version TLSv1 specified in $@ | InsecureProtocol.py:17:1:17:29 | ControlFlowNode for Attribute() | call to SSL.Context |
-| InsecureProtocol.py:32:1:32:19 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified in $@ | InsecureProtocol.py:32:1:32:19 | ControlFlowNode for Attribute() | call to SSL.Context |
-| InsecureProtocol.py:48:1:48:43 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified in $@ | InsecureProtocol.py:48:1:48:43 | ControlFlowNode for Attribute() | call to ssl.wrap_socket |
-| InsecureProtocol.py:49:1:49:35 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version SSLv2 specified in $@ | InsecureProtocol.py:49:1:49:35 | ControlFlowNode for SSLContext() | call to SSLContext |
-| pyOpenSSL_fluent.py:8:27:8:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | pyOpenSSL_fluent.py:6:15:6:44 | ControlFlowNode for Attribute() | call to SSL.Context |
-| pyOpenSSL_fluent.py:8:27:8:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | pyOpenSSL_fluent.py:6:15:6:44 | ControlFlowNode for Attribute() | call to SSL.Context |
-| pyOpenSSL_fluent.py:18:27:18:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | pyOpenSSL_fluent.py:15:15:15:44 | ControlFlowNode for Attribute() | call to SSL.Context |
-| ssl_fluent.py:9:14:9:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:6:15:6:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:9:14:9:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:6:15:6:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:19:14:19:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:15:15:15:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:37:14:37:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:34:15:34:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:37:14:37:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:34:15:34:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:47:14:47:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:43:15:43:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:75:14:75:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:71:15:71:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:75:14:75:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:71:15:71:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:128:15:128:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:128:15:128:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:104:14:104:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:104:14:104:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:173:14:173:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:169:15:169:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:192:14:192:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 specified in $@ | ssl_fluent.py:188:15:188:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context |
-| ssl_fluent.py:192:14:192:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 specified in $@ | ssl_fluent.py:188:15:188:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context |
+| InsecureProtocol.py:6:1:6:47 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified by $@ | InsecureProtocol.py:6:1:6:47 | ControlFlowNode for Attribute() | call to ssl.wrap_socket |
+| InsecureProtocol.py:7:1:7:47 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv3 specified by $@ | InsecureProtocol.py:7:1:7:47 | ControlFlowNode for Attribute() | call to ssl.wrap_socket |
+| InsecureProtocol.py:8:1:8:47 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version TLSv1 specified by $@ | InsecureProtocol.py:8:1:8:47 | ControlFlowNode for Attribute() | call to ssl.wrap_socket |
+| InsecureProtocol.py:10:1:10:39 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version SSLv2 specified by $@ | InsecureProtocol.py:10:1:10:39 | ControlFlowNode for SSLContext() | call to SSLContext |
+| InsecureProtocol.py:11:1:11:39 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version SSLv3 specified by $@ | InsecureProtocol.py:11:1:11:39 | ControlFlowNode for SSLContext() | call to SSLContext |
+| InsecureProtocol.py:12:1:12:39 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version TLSv1 specified by $@ | InsecureProtocol.py:12:1:12:39 | ControlFlowNode for SSLContext() | call to SSLContext |
+| InsecureProtocol.py:14:1:14:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified by $@ | InsecureProtocol.py:14:1:14:29 | ControlFlowNode for Attribute() | call to SSL.Context |
+| InsecureProtocol.py:16:1:16:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv3 specified by $@ | InsecureProtocol.py:16:1:16:29 | ControlFlowNode for Attribute() | call to SSL.Context |
+| InsecureProtocol.py:17:1:17:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version TLSv1 specified by $@ | InsecureProtocol.py:17:1:17:29 | ControlFlowNode for Attribute() | call to SSL.Context |
+| InsecureProtocol.py:32:1:32:19 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified by $@ | InsecureProtocol.py:32:1:32:19 | ControlFlowNode for Attribute() | call to SSL.Context |
+| InsecureProtocol.py:48:1:48:43 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified by $@ | InsecureProtocol.py:48:1:48:43 | ControlFlowNode for Attribute() | call to ssl.wrap_socket |
+| InsecureProtocol.py:49:1:49:35 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version SSLv2 specified by $@ | InsecureProtocol.py:49:1:49:35 | ControlFlowNode for SSLContext() | call to SSLContext |
+| pyOpenSSL_fluent.py:8:27:8:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv2 allowed by $@ | pyOpenSSL_fluent.py:6:15:6:44 | ControlFlowNode for Attribute() | call to SSL.Context |
+| pyOpenSSL_fluent.py:8:27:8:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv3 allowed by $@ | pyOpenSSL_fluent.py:6:15:6:44 | ControlFlowNode for Attribute() | call to SSL.Context |
+| pyOpenSSL_fluent.py:8:27:8:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | pyOpenSSL_fluent.py:6:15:6:44 | ControlFlowNode for Attribute() | call to SSL.Context |
+| pyOpenSSL_fluent.py:8:27:8:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | pyOpenSSL_fluent.py:6:15:6:44 | ControlFlowNode for Attribute() | call to SSL.Context |
+| pyOpenSSL_fluent.py:18:27:18:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv2 allowed by $@ | pyOpenSSL_fluent.py:15:15:15:44 | ControlFlowNode for Attribute() | call to SSL.Context |
+| pyOpenSSL_fluent.py:18:27:18:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv3 allowed by $@ | pyOpenSSL_fluent.py:15:15:15:44 | ControlFlowNode for Attribute() | call to SSL.Context |
+| pyOpenSSL_fluent.py:18:27:18:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | pyOpenSSL_fluent.py:15:15:15:44 | ControlFlowNode for Attribute() | call to SSL.Context |
+| pyOpenSSL_fluent.py:29:27:29:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv2 allowed by $@ | pyOpenSSL_fluent.py:25:15:25:44 | ControlFlowNode for Attribute() | call to SSL.Context |
+| pyOpenSSL_fluent.py:29:27:29:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv3 allowed by $@ | pyOpenSSL_fluent.py:25:15:25:44 | ControlFlowNode for Attribute() | call to SSL.Context |
+| ssl_fluent.py:9:14:9:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:6:15:6:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:9:14:9:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:6:15:6:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:19:14:19:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:15:15:15:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:37:14:37:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:34:15:34:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:37:14:37:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:34:15:34:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:47:14:47:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:43:15:43:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:75:14:75:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:71:15:71:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:75:14:75:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:71:15:71:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:128:15:128:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:128:15:128:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:144:5:144:11 | ControlFlowNode for context | context modification |
+| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:162:5:162:11 | ControlFlowNode for context | context modification |
+| ssl_fluent.py:104:14:104:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:104:14:104:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:124:14:124:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:122:5:122:11 | ControlFlowNode for context | context modification |
+| ssl_fluent.py:173:14:173:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:170:5:170:11 | ControlFlowNode for context | context modification |
+| ssl_fluent.py:192:14:192:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv3 allowed by $@ | ssl_fluent.py:189:5:189:11 | ControlFlowNode for context | context modification |
From ee038373571b4d3fe2490c2fd19eaeb9afbbfa49 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Wed, 3 Mar 2021 23:46:18 +0100
Subject: [PATCH 025/433] Python: small refactor
---
python/ql/src/Security/CWE-327/PyOpenSSL.qll | 5 +----
python/ql/src/Security/CWE-327/Ssl.qll | 5 +----
python/ql/src/Security/CWE-327/TlsLibraryModel.qll | 7 ++++---
3 files changed, 6 insertions(+), 11 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/PyOpenSSL.qll b/python/ql/src/Security/CWE-327/PyOpenSSL.qll
index 3c36c568ef5b..d2cfb74b3ab9 100644
--- a/python/ql/src/Security/CWE-327/PyOpenSSL.qll
+++ b/python/ql/src/Security/CWE-327/PyOpenSSL.qll
@@ -51,10 +51,7 @@ class UnspecificPyOpenSSLContextCreation extends PyOpenSSLContextCreation, Unspe
class PyOpenSSL extends TlsLibrary {
PyOpenSSL() { this = "pyOpenSSL" }
- override string specific_insecure_version_name(ProtocolVersion version) {
- version in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1"] and
- result = version + "_METHOD"
- }
+ override string specific_version_name(ProtocolVersion version) { result = version + "_METHOD" }
override string unspecific_version_name(ProtocolFamily family) {
// `"TLS_METHOD"` is not actually available in pyOpenSSL yet, but should be coming soon..
diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll
index e749bb9bc3c9..66a821e83ac8 100644
--- a/python/ql/src/Security/CWE-327/Ssl.qll
+++ b/python/ql/src/Security/CWE-327/Ssl.qll
@@ -135,10 +135,7 @@ class UnspecificSSLContextCreation extends SSLContextCreation, UnspecificContext
class Ssl extends TlsLibrary {
Ssl() { this = "ssl" }
- override string specific_insecure_version_name(ProtocolVersion version) {
- version in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1"] and
- result = "PROTOCOL_" + version
- }
+ override string specific_version_name(ProtocolVersion version) { result = "PROTOCOL_" + version }
override string unspecific_version_name(ProtocolFamily family) { result = "PROTOCOL_" + family }
diff --git a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
index 97ceec006887..3ab880e8bd9e 100644
--- a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
+++ b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
@@ -80,8 +80,8 @@ abstract class UnspecificContextCreation extends ContextCreation, ProtocolUnrest
abstract class TlsLibrary extends string {
TlsLibrary() { this in ["ssl", "pyOpenSSL"] }
- /** The name of a specific protocol version, known to be insecure. */
- abstract string specific_insecure_version_name(ProtocolVersion version);
+ /** The name of a specific protocol version. */
+ abstract string specific_version_name(ProtocolVersion version);
/** The name of an unspecific protocol version, say TLS, known to have insecure instances. */
abstract string unspecific_version_name(ProtocolFamily family);
@@ -91,7 +91,8 @@ abstract class TlsLibrary extends string {
/** A dataflow node representing a specific protocol version, known to be insecure. */
DataFlow::Node insecure_version(ProtocolVersion version) {
- result = version_constants().getMember(specific_insecure_version_name(version)).getAUse()
+ version.isInsecure() and
+ result = version_constants().getMember(specific_version_name(version)).getAUse()
}
/** A dataflow node representing an unspecific protocol version, say TLS, known to have insecure instances. */
From de9469bbfc1769e6e705165b6e53efa12a0c97eb Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Thu, 4 Mar 2021 00:01:44 +0100
Subject: [PATCH 026/433] Python: complete `ssl.create_default_context`
---
python/ql/src/Security/CWE-327/Ssl.qll | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll
index 66a821e83ac8..d4886219d084 100644
--- a/python/ql/src/Security/CWE-327/Ssl.qll
+++ b/python/ql/src/Security/CWE-327/Ssl.qll
@@ -132,6 +132,15 @@ class UnspecificSSLContextCreation extends SSLContextCreation, UnspecificContext
}
}
+class UnspecificSSLDefaultContextCreation extends SSLDefaultContextCreation, ProtocolUnrestriction {
+ override DataFlow::CfgNode getContext() { result = this }
+
+ // see https://docs.python.org/3/library/ssl.html#ssl.create_default_context
+ override ProtocolVersion getUnrestriction() {
+ result in ["TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"]
+ }
+}
+
class Ssl extends TlsLibrary {
Ssl() { this = "ssl" }
@@ -167,5 +176,7 @@ class Ssl extends TlsLibrary {
result instanceof ContextSetVersion
or
result instanceof UnspecificSSLContextCreation
+ or
+ result instanceof UnspecificSSLDefaultContextCreation
}
}
From d02c5298725695e1fe3dc663922619a7f0a8b35d Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Thu, 4 Mar 2021 00:06:36 +0100
Subject: [PATCH 027/433] Python: Update annotation
---
python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py b/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
index eceacaebe215..ab65e3bd2068 100644
--- a/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
+++ b/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
@@ -186,7 +186,7 @@ def test_fluent_ssl_safe_version():
def test_fluent_explicitly_unsafe():
hostname = 'www.python.org'
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
- context.options &= ~ssl.OP_NO_SSLv3 # This not recognized
+ context.options &= ~ssl.OP_NO_SSLv3
with socket.create_connection((hostname, 443)) as sock:
with context.wrap_socket(sock, server_hostname=hostname) as ssock: # SSLv3 not flagged here
From c5577cb09a073c2fb4f6feeca887c437d5fbe2fb Mon Sep 17 00:00:00 2001
From: haby0
Date: Thu, 4 Mar 2021 19:54:49 +0800
Subject: [PATCH 028/433] Fix the problem
---
.../Security/CWE/CWE-352/JsonpInjection.ql | 68 --------
.../CWE/CWE-352/JsonpInjectionFilterLib.qll | 77 ---------
.../CWE/CWE-352/JsonpInjectionLib.qll | 155 ------------------
.../Security/CWE/CWE-352/JsonStringLib.qll | 0
.../CWE/CWE-352/JsonpController.java} | 48 +-----
.../Security/CWE/CWE-352/JsonpInjection.java | 0
.../Security/CWE/CWE-352/JsonpInjection.qhelp | 2 +-
.../Security/CWE/CWE-352/JsonpInjection.ql | 64 ++++++++
.../CWE/CWE-352/JsonpInjectionLib.qll | 130 +++++++++++++++
.../CWE/CWE-352/JsonpInjectionServlet1.java | 0
.../CWE/CWE-352/JsonpInjectionServlet2.java | 0
.../Security/CWE/CWE-352/RefererFilter.java | 43 +++++
.../semmle/code/java/frameworks/Servlets.qll | 12 +-
.../security/CWE-352/JsonpController.java | 128 +++++++++++++++
.../security/CWE-352/JsonpInjection.expected | 60 -------
.../CWE-352/JsonpInjectionServlet1.java | 64 ++++++++
.../CWE-352/JsonpInjectionServlet2.java} | 16 +-
.../CWE-352/JsonpInjection_1.expected | 60 +++++++
.../CWE-352/JsonpInjection_2.expected | 78 +++++++++
.../CWE-352/JsonpInjection_3.expected | 66 ++++++++
.../query-tests/security/CWE-352/Readme | 3 +
.../security/CWE-352/RefererFilter.java | 43 +++++
.../gson-2.8.6/com/google/gson/Gson.java | 7 +
.../org/springframework/util/StringUtils.java | 8 +
.../javax/servlet/Filter.java | 13 ++
.../javax/servlet/FilterChain.java | 8 +
.../javax/servlet/FilterConfig.java | 3 +
.../javax/servlet/ServletException.java | 8 +
.../javax/servlet/ServletRequest.java | 87 ++++++++++
.../javax/servlet/ServletResponse.java | 39 +++++
.../servlet/http/HttpServletRequest.java | 116 +++++++++++++
.../servlet/http/HttpServletResponse.java | 106 ++++++++++++
32 files changed, 1084 insertions(+), 428 deletions(-)
delete mode 100644 java/ql/src/Security/CWE/CWE-352/JsonpInjection.ql
delete mode 100644 java/ql/src/Security/CWE/CWE-352/JsonpInjectionFilterLib.qll
delete mode 100644 java/ql/src/Security/CWE/CWE-352/JsonpInjectionLib.qll
rename java/ql/src/{ => experimental}/Security/CWE/CWE-352/JsonStringLib.qll (100%)
rename java/ql/{test/experimental/query-tests/security/CWE-352/JsonpInjection.java => src/experimental/Security/CWE/CWE-352/JsonpController.java} (72%)
rename java/ql/src/{ => experimental}/Security/CWE/CWE-352/JsonpInjection.java (100%)
rename java/ql/src/{ => experimental}/Security/CWE/CWE-352/JsonpInjection.qhelp (96%)
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll
rename java/ql/src/{ => experimental}/Security/CWE/CWE-352/JsonpInjectionServlet1.java (100%)
rename java/ql/src/{ => experimental}/Security/CWE/CWE-352/JsonpInjectionServlet2.java (100%)
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-352/RefererFilter.java
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpController.java
delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.expected
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionServlet1.java
rename java/ql/{src/Security/CWE/CWE-352/JsonpInjectionServlet.java => test/experimental/query-tests/security/CWE-352/JsonpInjectionServlet2.java} (75%)
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_1.expected
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_2.expected
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_3.expected
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/Readme
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/RefererFilter.java
create mode 100644 java/ql/test/stubs/gson-2.8.6/com/google/gson/Gson.java
create mode 100644 java/ql/test/stubs/spring-core-5.3.2/org/springframework/util/StringUtils.java
create mode 100644 java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/Filter.java
create mode 100644 java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/FilterChain.java
create mode 100644 java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/FilterConfig.java
create mode 100644 java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/ServletException.java
create mode 100644 java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/ServletRequest.java
create mode 100644 java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/ServletResponse.java
create mode 100644 java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/http/HttpServletRequest.java
create mode 100644 java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/http/HttpServletResponse.java
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonpInjection.ql b/java/ql/src/Security/CWE/CWE-352/JsonpInjection.ql
deleted file mode 100644
index 53ee6182511b..000000000000
--- a/java/ql/src/Security/CWE/CWE-352/JsonpInjection.ql
+++ /dev/null
@@ -1,68 +0,0 @@
-/**
- * @name JSONP Injection
- * @description User-controlled callback function names that are not verified are vulnerable
- * to jsonp injection attacks.
- * @kind path-problem
- * @problem.severity error
- * @precision high
- * @id java/JSONP-Injection
- * @tags security
- * external/cwe/cwe-352
- */
-
-import java
-import JsonpInjectionLib
-import JsonpInjectionFilterLib
-import semmle.code.java.dataflow.FlowSources
-import semmle.code.java.deadcode.WebEntryPoints
-import DataFlow::PathGraph
-
-
-/** If there is a method to verify `token`, `auth`, `referer`, and `origin`, it will not pass. */
-class ServletVerifAuth extends DataFlow::BarrierGuard {
- ServletVerifAuth() {
- exists(MethodAccess ma, Node prod, Node succ |
- ma.getMethod().getName().regexpMatch("(?i).*(token|auth|referer|origin).*") and
- prod instanceof RemoteFlowSource and
- succ.asExpr() = ma.getAnArgument() and
- ma.getMethod().getAParameter().getName().regexpMatch("(?i).*(token|auth|referer|origin).*") and
- localFlowStep*(prod, succ) and
- this = ma
- )
- }
-
- override predicate checks(Expr e, boolean branch) {
- exists(Node node |
- node instanceof JsonpInjectionSink and
- e = node.asExpr() and
- branch = true
- )
- }
-}
-
-/** Taint-tracking configuration tracing flow from get method request sources to output jsonp data. */
-class JsonpInjectionConfig extends TaintTracking::Configuration {
- JsonpInjectionConfig() { this = "JsonpInjectionConfig" }
-
- override predicate isSource(DataFlow::Node source) { source instanceof GetHttpRequestSource }
-
- override predicate isSink(DataFlow::Node sink) { sink instanceof JsonpInjectionSink }
-
- override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
- guard instanceof ServletVerifAuth
- }
-
- override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
- exists(MethodAccess ma |
- isRequestGetParamMethod(ma) and pred.asExpr() = ma.getQualifier() and succ.asExpr() = ma
- )
- }
-}
-
-from DataFlow::PathNode source, DataFlow::PathNode sink, JsonpInjectionConfig conf
-where
- not checks() = false and
- conf.hasFlowPath(source, sink) and
- exists(JsonpInjectionFlowConfig jhfc | jhfc.hasFlowTo(sink.getNode()))
-select sink.getNode(), source, sink, "Jsonp Injection query might include code from $@.",
- source.getNode(), "this user input"
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonpInjectionFilterLib.qll b/java/ql/src/Security/CWE/CWE-352/JsonpInjectionFilterLib.qll
deleted file mode 100644
index b349bed26414..000000000000
--- a/java/ql/src/Security/CWE/CWE-352/JsonpInjectionFilterLib.qll
+++ /dev/null
@@ -1,77 +0,0 @@
-/**
- * @name JSONP Injection
- * @description User-controlled callback function names that are not verified are vulnerable
- * to json hijacking attacks.
- * @kind path-problem
- */
-
-import java
-import DataFlow
-import semmle.code.java.dataflow.FlowSources
-import semmle.code.java.dataflow.TaintTracking2
-import DataFlow::PathGraph
-
-class FilterVerifAuth extends DataFlow::BarrierGuard {
- FilterVerifAuth() {
- exists(MethodAccess ma, Node prod, Node succ |
- ma.getMethod().getName().regexpMatch("(?i).*(token|auth|referer|origin).*") and
- prod instanceof RemoteFlowSource and
- succ.asExpr() = ma.getAnArgument() and
- ma.getMethod().getAParameter().getName().regexpMatch("(?i).*(token|auth|referer|origin).*") and
- localFlowStep*(prod, succ) and
- this = ma
- )
- }
-
- override predicate checks(Expr e, boolean branch) {
- exists(Node node |
- node instanceof DoFilterMethodSink and
- e = node.asExpr() and
- branch = true
- )
- }
-}
-
-/** A data flow source for `Filter.doFilter` method paramters. */
-private class DoFilterMethodSource extends DataFlow::Node {
- DoFilterMethodSource() {
- exists(Method m |
- isDoFilterMethod(m) and
- m.getAParameter().getAnAccess() = this.asExpr()
- )
- }
-}
-
-/** A data flow sink for `FilterChain.doFilter` method qualifying expression. */
-private class DoFilterMethodSink extends DataFlow::Node {
- DoFilterMethodSink() {
- exists(MethodAccess ma, Method m | ma.getMethod() = m |
- m.hasName("doFilter") and
- m.getDeclaringType*().hasQualifiedName("javax.servlet", "FilterChain") and
- ma.getQualifier() = this.asExpr()
- )
- }
-}
-
-/** Taint-tracking configuration tracing flow from `doFilter` method paramter source to output
- * `FilterChain.doFilter` method qualifying expression.
- * */
-class DoFilterMethodConfig extends TaintTracking::Configuration {
- DoFilterMethodConfig() { this = "DoFilterMethodConfig" }
-
- override predicate isSource(DataFlow::Node source) { source instanceof DoFilterMethodSource }
-
- override predicate isSink(DataFlow::Node sink) { sink instanceof DoFilterMethodSink }
-
- override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
- guard instanceof FilterVerifAuth
- }
-}
-
-/** Implement class modeling verification for `Filter.doFilter`, return false if it fails. */
-boolean checks() {
- exists(DataFlow::PathNode source, DataFlow::PathNode sink, DoFilterMethodConfig conf |
- conf.hasFlowPath(source, sink) and
- result = false
- )
-}
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonpInjectionLib.qll b/java/ql/src/Security/CWE/CWE-352/JsonpInjectionLib.qll
deleted file mode 100644
index 3f7304258232..000000000000
--- a/java/ql/src/Security/CWE/CWE-352/JsonpInjectionLib.qll
+++ /dev/null
@@ -1,155 +0,0 @@
-import java
-import DataFlow
-import JsonStringLib
-import semmle.code.java.dataflow.DataFlow
-import semmle.code.java.dataflow.FlowSources
-import semmle.code.java.frameworks.spring.SpringController
-
-/** Holds if `m` is a method of some override of `HttpServlet.doGet`. */
-private predicate isGetServletMethod(Method m) {
- isServletRequestMethod(m) and m.getName() = "doGet"
-}
-
-/** Holds if `m` is a method of some override of `HttpServlet.doGet`. */
-private predicate isGetSpringControllerMethod(Method m) {
- exists(Annotation a |
- a = m.getAnAnnotation() and
- a.getType().hasQualifiedName("org.springframework.web.bind.annotation", "GetMapping")
- )
- or
- exists(Annotation a |
- a = m.getAnAnnotation() and
- a.getType().hasQualifiedName("org.springframework.web.bind.annotation", "RequestMapping") and
- a.getValue("method").toString().regexpMatch("RequestMethod.GET|\\{...\\}")
- )
-}
-
-/** Method parameters use the annotation `@RequestParam` or the parameter type is `ServletRequest`, `String`, `Object` */
-predicate checkSpringMethodParameterType(Method m, int i) {
- m.getParameter(i).getType() instanceof ServletRequest
- or
- exists(Parameter p |
- p = m.getParameter(i) and
- p.hasAnnotation() and
- p.getAnAnnotation()
- .getType()
- .hasQualifiedName("org.springframework.web.bind.annotation", "RequestParam") and
- p.getType().getName().regexpMatch("String|Object")
- )
- or
- exists(Parameter p |
- p = m.getParameter(i) and
- not p.hasAnnotation() and
- p.getType().getName().regexpMatch("String|Object")
- )
-}
-
-/** A data flow source for get method request parameters. */
-abstract class GetHttpRequestSource extends DataFlow::Node { }
-
-/** A data flow source for servlet get method request parameters. */
-private class ServletGetHttpRequestSource extends GetHttpRequestSource {
- ServletGetHttpRequestSource() {
- exists(Method m |
- isGetServletMethod(m) and
- m.getParameter(0).getAnAccess() = this.asExpr()
- )
- }
-}
-
-/** A data flow source for spring controller get method request parameters. */
-private class SpringGetHttpRequestSource extends GetHttpRequestSource {
- SpringGetHttpRequestSource() {
- exists(SpringControllerMethod scm, int i |
- isGetSpringControllerMethod(scm) and
- checkSpringMethodParameterType(scm, i) and
- scm.getParameter(i).getAnAccess() = this.asExpr()
- )
- }
-}
-
-/** A data flow sink for unvalidated user input that is used to jsonp. */
-abstract class JsonpInjectionSink extends DataFlow::Node { }
-
-/** Use ```print```, ```println```, ```write``` to output result. */
-private class WriterPrintln extends JsonpInjectionSink {
- WriterPrintln() {
- exists(MethodAccess ma |
- ma.getMethod().getName().regexpMatch("print|println|write") and
- ma.getMethod()
- .getDeclaringType()
- .getASourceSupertype*()
- .hasQualifiedName("java.io", "PrintWriter") and
- ma.getArgument(0) = this.asExpr()
- )
- }
-}
-
-/** Spring Request Method return result. */
-private class SpringReturn extends JsonpInjectionSink {
- SpringReturn() {
- exists(ReturnStmt rs, Method m | m = rs.getEnclosingCallable() |
- isGetSpringControllerMethod(m) and
- rs.getResult() = this.asExpr()
- )
- }
-}
-
-/** A concatenate expression using `(` and `)` or `);`. */
-class JsonpInjectionExpr extends AddExpr {
- JsonpInjectionExpr() {
- getRightOperand().toString().regexpMatch("\"\\)\"|\"\\);\"") and
- getLeftOperand()
- .(AddExpr)
- .getLeftOperand()
- .(AddExpr)
- .getRightOperand()
- .toString()
- .regexpMatch("\"\\(\"")
- }
-
- /** Get the jsonp function name of this expression */
- Expr getFunctionName() {
- result = getLeftOperand().(AddExpr).getLeftOperand().(AddExpr).getLeftOperand()
- }
-
- /** Get the json data of this expression */
- Expr getJsonExpr() { result = getLeftOperand().(AddExpr).getRightOperand() }
-}
-
-/** A data flow configuration tracing flow from remote sources to jsonp function name. */
-class RemoteFlowConfig extends DataFlow2::Configuration {
- RemoteFlowConfig() { this = "RemoteFlowConfig" }
-
- override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
-
- override predicate isSink(DataFlow::Node sink) {
- exists(JsonpInjectionExpr jhe | jhe.getFunctionName() = sink.asExpr())
- }
-}
-
-/** A data flow configuration tracing flow from json data to splicing jsonp data. */
-class JsonDataFlowConfig extends DataFlow2::Configuration {
- JsonDataFlowConfig() { this = "JsonDataFlowConfig" }
-
- override predicate isSource(DataFlow::Node src) { src instanceof JsonpStringSource }
-
- override predicate isSink(DataFlow::Node sink) {
- exists(JsonpInjectionExpr jhe | jhe.getJsonExpr() = sink.asExpr())
- }
-}
-
-/** Taint-tracking configuration tracing flow from user-controllable function name jsonp data to output jsonp data. */
-class JsonpInjectionFlowConfig extends DataFlow::Configuration {
- JsonpInjectionFlowConfig() { this = "JsonpInjectionFlowConfig" }
-
- override predicate isSource(DataFlow::Node src) {
- exists(JsonpInjectionExpr jhe, JsonDataFlowConfig jdfc, RemoteFlowConfig rfc |
- jhe = src.asExpr() and
- jdfc.hasFlowTo(DataFlow::exprNode(jhe.getJsonExpr())) and
- rfc.hasFlowTo(DataFlow::exprNode(jhe.getFunctionName()))
- )
- }
-
- override predicate isSink(DataFlow::Node sink) { sink instanceof JsonpInjectionSink }
-}
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonStringLib.qll b/java/ql/src/experimental/Security/CWE/CWE-352/JsonStringLib.qll
similarity index 100%
rename from java/ql/src/Security/CWE/CWE-352/JsonStringLib.qll
rename to java/ql/src/experimental/Security/CWE/CWE-352/JsonStringLib.qll
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.java b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpController.java
similarity index 72%
rename from java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.java
rename to java/ql/src/experimental/Security/CWE/CWE-352/JsonpController.java
index 9f079513a8b9..84a172a7aebd 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.java
+++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpController.java
@@ -3,17 +3,14 @@
import com.google.gson.Gson;
import java.io.PrintWriter;
import java.util.HashMap;
-import java.util.Random;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
-public class JsonpInjection {
+public class JsonpController {
private static HashMap hashMap = new HashMap();
static {
@@ -96,54 +93,13 @@ public void bad6(HttpServletRequest request,
@GetMapping(value = "jsonp7")
@ResponseBody
- public String good(HttpServletRequest request) {
- String resultStr = null;
- String jsonpCallback = request.getParameter("jsonpCallback");
-
- String val = "";
- Random random = new Random();
- for (int i = 0; i < 10; i++) {
- val += String.valueOf(random.nextInt(10));
- }
- // good
- jsonpCallback = jsonpCallback + "_" + val;
- String jsonStr = getJsonStr(hashMap);
- resultStr = jsonpCallback + "(" + jsonStr + ")";
- return resultStr;
- }
-
- @GetMapping(value = "jsonp8")
- @ResponseBody
public String good1(HttpServletRequest request) {
String resultStr = null;
- String jsonpCallback = request.getParameter("jsonpCallback");
String token = request.getParameter("token");
- // good
if (verifToken(token)){
- System.out.println(token);
- String jsonStr = getJsonStr(hashMap);
- resultStr = jsonpCallback + "(" + jsonStr + ")";
- return resultStr;
- }
-
- return "error";
- }
-
- @GetMapping(value = "jsonp9")
- @ResponseBody
- public String good2(HttpServletRequest request) {
- String resultStr = null;
- String jsonpCallback = request.getParameter("jsonpCallback");
-
- String referer = request.getHeader("Referer");
-
- boolean result = verifReferer(referer);
-
- boolean test = result;
- // good
- if (test){
+ String jsonpCallback = request.getParameter("jsonpCallback");
String jsonStr = getJsonStr(hashMap);
resultStr = jsonpCallback + "(" + jsonStr + ")";
return resultStr;
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonpInjection.java b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.java
similarity index 100%
rename from java/ql/src/Security/CWE/CWE-352/JsonpInjection.java
rename to java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.java
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonpInjection.qhelp b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp
similarity index 96%
rename from java/ql/src/Security/CWE/CWE-352/JsonpInjection.qhelp
rename to java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp
index b063b409d3a5..bb5d628ac0b7 100644
--- a/java/ql/src/Security/CWE/CWE-352/JsonpInjection.qhelp
+++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp
@@ -16,7 +16,7 @@ there is a problem of sensitive information leakage.
The following example shows the case of no verification processing and verification processing for the external input function name.
-
+
diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql
new file mode 100644
index 000000000000..f3ae25daa034
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql
@@ -0,0 +1,64 @@
+/**
+ * @name JSONP Injection
+ * @description User-controlled callback function names that are not verified are vulnerable
+ * to jsonp injection attacks.
+ * @kind path-problem
+ * @problem.severity error
+ * @precision high
+ * @id java/JSONP-Injection
+ * @tags security
+ * external/cwe/cwe-352
+ */
+
+import java
+import JsonpInjectionLib
+import semmle.code.java.dataflow.FlowSources
+import semmle.code.java.deadcode.WebEntryPoints
+import semmle.code.java.security.XSS
+import DataFlow::PathGraph
+
+/** Determine whether there is a verification method for the remote streaming source data flow path method. */
+predicate existsFilterVerificationMethod() {
+ exists(MethodAccess ma,Node existsNode, Method m|
+ ma.getMethod() instanceof VerificationMethodClass and
+ existsNode.asExpr() = ma and
+ m = getAnMethod(existsNode.getEnclosingCallable()) and
+ isDoFilterMethod(m)
+ )
+}
+
+/** Determine whether there is a verification method for the remote streaming source data flow path method. */
+predicate existsServletVerificationMethod(Node checkNode) {
+ exists(MethodAccess ma,Node existsNode|
+ ma.getMethod() instanceof VerificationMethodClass and
+ existsNode.asExpr() = ma and
+ getAnMethod(existsNode.getEnclosingCallable()) = getAnMethod(checkNode.getEnclosingCallable())
+ )
+}
+
+/** Taint-tracking configuration tracing flow from get method request sources to output jsonp data. */
+class RequestResponseFlowConfig extends TaintTracking::Configuration {
+ RequestResponseFlowConfig() { this = "RequestResponseFlowConfig" }
+
+ override predicate isSource(DataFlow::Node source) {
+ source instanceof RemoteFlowSource and
+ getAnMethod(source.getEnclosingCallable()) instanceof RequestGetMethod
+ }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof XssSink }
+
+ override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
+ exists(MethodAccess ma |
+ isRequestGetParamMethod(ma) and pred.asExpr() = ma.getQualifier() and succ.asExpr() = ma
+ )
+ }
+}
+
+from DataFlow::PathNode source, DataFlow::PathNode sink, RequestResponseFlowConfig conf
+where
+ not existsServletVerificationMethod(source.getNode()) and
+ not existsFilterVerificationMethod() and
+ conf.hasFlowPath(source, sink) and
+ exists(JsonpInjectionFlowConfig jhfc | jhfc.hasFlowTo(sink.getNode()))
+select sink.getNode(), source, sink, "Jsonp Injection query might include code from $@.",
+ source.getNode(), "this user input"
\ No newline at end of file
diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll
new file mode 100644
index 000000000000..b8964524a9f8
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll
@@ -0,0 +1,130 @@
+import java
+import DataFlow
+import JsonStringLib
+import semmle.code.java.security.XSS
+import semmle.code.java.dataflow.DataFlow
+import semmle.code.java.dataflow.FlowSources
+import semmle.code.java.frameworks.spring.SpringController
+
+/** Taint-tracking configuration tracing flow from user-controllable function name jsonp data to output jsonp data. */
+class VerificationMethodFlowConfig extends TaintTracking::Configuration {
+ VerificationMethodFlowConfig() { this = "VerificationMethodFlowConfig" }
+
+ override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess ma, BarrierGuard bg |
+ ma.getMethod().getAParameter().getName().regexpMatch("(?i).*(token|auth|referer|origin).*") and
+ bg = ma and
+ sink.asExpr() = ma.getAnArgument()
+ )
+ }
+}
+
+/** The parameter name of the method is `token`, `auth`, `referer`, `origin`. */
+class VerificationMethodClass extends Method {
+ VerificationMethodClass() {
+ exists(MethodAccess ma, BarrierGuard bg, VerificationMethodFlowConfig vmfc, Node node |
+ this = ma.getMethod() and
+ this.getAParameter().getName().regexpMatch("(?i).*(token|auth|referer|origin).*") and
+ bg = ma and
+ node.asExpr() = ma.getAnArgument() and
+ vmfc.hasFlowTo(node)
+ )
+ }
+}
+
+/** Get Callable by recursive method. */
+Callable getAnMethod(Callable call) {
+ result = call
+ or
+ result = getAnMethod(call.getAReference().getEnclosingCallable())
+}
+
+abstract class RequestGetMethod extends Method { }
+
+/** Holds if `m` is a method of some override of `HttpServlet.doGet`. */
+private class ServletGetMethod extends RequestGetMethod {
+ ServletGetMethod() {
+ exists(Method m |
+ m = this and
+ isServletRequestMethod(m) and
+ m.getName() = "doGet"
+ )
+ }
+}
+
+/** Holds if `m` is a method of some override of `HttpServlet.doGet`. */
+private class SpringControllerGetMethod extends RequestGetMethod {
+ SpringControllerGetMethod() {
+ exists(Annotation a |
+ a = this.getAnAnnotation() and
+ a.getType().hasQualifiedName("org.springframework.web.bind.annotation", "GetMapping")
+ )
+ or
+ exists(Annotation a |
+ a = this.getAnAnnotation() and
+ a.getType().hasQualifiedName("org.springframework.web.bind.annotation", "RequestMapping") and
+ a.getValue("method").toString().regexpMatch("RequestMethod.GET|\\{...\\}")
+ )
+ }
+}
+
+/** A concatenate expression using `(` and `)` or `);`. */
+class JsonpInjectionExpr extends AddExpr {
+ JsonpInjectionExpr() {
+ getRightOperand().toString().regexpMatch("\"\\)\"|\"\\);\"") and
+ getLeftOperand()
+ .(AddExpr)
+ .getLeftOperand()
+ .(AddExpr)
+ .getRightOperand()
+ .toString()
+ .regexpMatch("\"\\(\"")
+ }
+
+ /** Get the jsonp function name of this expression */
+ Expr getFunctionName() {
+ result = getLeftOperand().(AddExpr).getLeftOperand().(AddExpr).getLeftOperand()
+ }
+
+ /** Get the json data of this expression */
+ Expr getJsonExpr() { result = getLeftOperand().(AddExpr).getRightOperand() }
+}
+
+/** A data flow configuration tracing flow from remote sources to jsonp function name. */
+class RemoteFlowConfig extends DataFlow2::Configuration {
+ RemoteFlowConfig() { this = "RemoteFlowConfig" }
+
+ override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(JsonpInjectionExpr jhe | jhe.getFunctionName() = sink.asExpr())
+ }
+}
+
+/** A data flow configuration tracing flow from json data to splicing jsonp data. */
+class JsonDataFlowConfig extends DataFlow2::Configuration {
+ JsonDataFlowConfig() { this = "JsonDataFlowConfig" }
+
+ override predicate isSource(DataFlow::Node src) { src instanceof JsonpStringSource }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(JsonpInjectionExpr jhe | jhe.getJsonExpr() = sink.asExpr())
+ }
+}
+
+/** Taint-tracking configuration tracing flow from user-controllable function name jsonp data to output jsonp data. */
+class JsonpInjectionFlowConfig extends TaintTracking::Configuration {
+ JsonpInjectionFlowConfig() { this = "JsonpInjectionFlowConfig" }
+
+ override predicate isSource(DataFlow::Node src) {
+ exists(JsonpInjectionExpr jhe, JsonDataFlowConfig jdfc, RemoteFlowConfig rfc |
+ jhe = src.asExpr() and
+ jdfc.hasFlowTo(DataFlow::exprNode(jhe.getJsonExpr())) and
+ rfc.hasFlowTo(DataFlow::exprNode(jhe.getFunctionName()))
+ )
+ }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof XssSink }
+}
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonpInjectionServlet1.java b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionServlet1.java
similarity index 100%
rename from java/ql/src/Security/CWE/CWE-352/JsonpInjectionServlet1.java
rename to java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionServlet1.java
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonpInjectionServlet2.java b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionServlet2.java
similarity index 100%
rename from java/ql/src/Security/CWE/CWE-352/JsonpInjectionServlet2.java
rename to java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionServlet2.java
diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/RefererFilter.java b/java/ql/src/experimental/Security/CWE/CWE-352/RefererFilter.java
new file mode 100644
index 000000000000..97444932ae17
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-352/RefererFilter.java
@@ -0,0 +1,43 @@
+import java.io.IOException;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.util.StringUtils;
+
+public class RefererFilter implements Filter {
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ }
+
+ @Override
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
+ HttpServletRequest request = (HttpServletRequest) servletRequest;
+ HttpServletResponse response = (HttpServletResponse) servletResponse;
+ String refefer = request.getHeader("Referer");
+ boolean result = verifReferer(refefer);
+ if (result){
+ filterChain.doFilter(servletRequest, servletResponse);
+ }
+ response.sendError(444, "Referer xxx.");
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+ public static boolean verifReferer(String referer){
+ if (StringUtils.isEmpty(referer)){
+ return false;
+ }
+ if (referer.startsWith("http://www.baidu.com/")){
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/java/ql/src/semmle/code/java/frameworks/Servlets.qll b/java/ql/src/semmle/code/java/frameworks/Servlets.qll
index b2054dc30cb7..5cccf62122ff 100644
--- a/java/ql/src/semmle/code/java/frameworks/Servlets.qll
+++ b/java/ql/src/semmle/code/java/frameworks/Servlets.qll
@@ -338,7 +338,6 @@ predicate isRequestGetParamMethod(MethodAccess ma) {
ma.getMethod() instanceof HttpServletRequestGetQueryStringMethod
}
-
/**
* A class that has `javax.servlet.Filter` as an ancestor.
*/
@@ -346,21 +345,18 @@ class FilterClass extends Class {
FilterClass() { getAnAncestor().hasQualifiedName("javax.servlet", "Filter") }
}
-
/**
* The interface `javax.servlet.FilterChain`
*/
-class FilterChain extends RefType {
- FilterChain() {
- hasQualifiedName("javax.servlet", "FilterChain")
- }
+class FilterChain extends Interface {
+ FilterChain() { hasQualifiedName("javax.servlet", "FilterChain") }
}
-/** Holds if `m` is a request handler method (for example `doGet` or `doPost`). */
+/** Holds if `m` is a filter handler method (for example `doFilter`). */
predicate isDoFilterMethod(Method m) {
m.getDeclaringType() instanceof FilterClass and
m.getNumberOfParameters() = 3 and
m.getParameter(0).getType() instanceof ServletRequest and
m.getParameter(1).getType() instanceof ServletResponse and
m.getParameter(2).getType() instanceof FilterChain
-}
\ No newline at end of file
+}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpController.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpController.java
new file mode 100644
index 000000000000..cf860c756409
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpController.java
@@ -0,0 +1,128 @@
+import com.alibaba.fastjson.JSONObject;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.gson.Gson;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+@Controller
+public class JsonpController {
+ private static HashMap hashMap = new HashMap();
+
+ static {
+ hashMap.put("username","admin");
+ hashMap.put("password","123456");
+ }
+
+
+ @GetMapping(value = "jsonp1", produces="text/javascript")
+ @ResponseBody
+ public String bad1(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+
+ Gson gson = new Gson();
+ String result = gson.toJson(hashMap);
+ resultStr = jsonpCallback + "(" + result + ")";
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp2")
+ @ResponseBody
+ public String bad2(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+
+ resultStr = jsonpCallback + "(" + JSONObject.toJSONString(hashMap) + ")";
+
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp3")
+ @ResponseBody
+ public String bad3(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp4")
+ @ResponseBody
+ public String bad4(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String restr = JSONObject.toJSONString(hashMap);
+ resultStr = jsonpCallback + "(" + restr + ");";
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp5")
+ @ResponseBody
+ public void bad5(HttpServletRequest request,
+ HttpServletResponse response) throws Exception {
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ PrintWriter pw = null;
+ Gson gson = new Gson();
+ String result = gson.toJson(hashMap);
+
+ String resultStr = null;
+ pw = response.getWriter();
+ resultStr = jsonpCallback + "(" + result + ")";
+ pw.println(resultStr);
+ }
+
+ @GetMapping(value = "jsonp6")
+ @ResponseBody
+ public void bad6(HttpServletRequest request,
+ HttpServletResponse response) throws Exception {
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ PrintWriter pw = null;
+ ObjectMapper mapper = new ObjectMapper();
+ String result = mapper.writeValueAsString(hashMap);
+ String resultStr = null;
+ pw = response.getWriter();
+ resultStr = jsonpCallback + "(" + result + ")";
+ pw.println(resultStr);
+ }
+
+ @GetMapping(value = "jsonp7")
+ @ResponseBody
+ public String good1(HttpServletRequest request) {
+ String resultStr = null;
+
+ String token = request.getParameter("token");
+
+ if (verifToken(token)){
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
+
+ return "error";
+ }
+
+ public static String getJsonStr(Object result) {
+ return JSONObject.toJSONString(result);
+ }
+
+ public static boolean verifToken(String token){
+ if (token != "xxxx"){
+ return false;
+ }
+ return true;
+ }
+
+ public static boolean verifReferer(String referer){
+ if (!referer.startsWith("http://test.com/")){
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.expected
deleted file mode 100644
index 7e3069cf1d93..000000000000
--- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.expected
+++ /dev/null
@@ -1,60 +0,0 @@
-edges
-| JsonpInjection.java:29:32:29:38 | request : HttpServletRequest | JsonpInjection.java:34:16:34:24 | resultStr |
-| JsonpInjection.java:33:21:33:54 | ... + ... : String | JsonpInjection.java:34:16:34:24 | resultStr |
-| JsonpInjection.java:41:32:41:38 | request : HttpServletRequest | JsonpInjection.java:45:16:45:24 | resultStr |
-| JsonpInjection.java:43:21:43:80 | ... + ... : String | JsonpInjection.java:45:16:45:24 | resultStr |
-| JsonpInjection.java:52:32:52:38 | request : HttpServletRequest | JsonpInjection.java:55:16:55:24 | resultStr |
-| JsonpInjection.java:54:21:54:55 | ... + ... : String | JsonpInjection.java:55:16:55:24 | resultStr |
-| JsonpInjection.java:62:32:62:38 | request : HttpServletRequest | JsonpInjection.java:65:16:65:24 | resultStr |
-| JsonpInjection.java:64:21:64:54 | ... + ... : String | JsonpInjection.java:65:16:65:24 | resultStr |
-| JsonpInjection.java:72:32:72:38 | request : HttpServletRequest | JsonpInjection.java:80:20:80:28 | resultStr |
-| JsonpInjection.java:79:21:79:54 | ... + ... : String | JsonpInjection.java:80:20:80:28 | resultStr |
-| JsonpInjection.java:87:32:87:38 | request : HttpServletRequest | JsonpInjection.java:94:20:94:28 | resultStr |
-| JsonpInjection.java:93:21:93:54 | ... + ... : String | JsonpInjection.java:94:20:94:28 | resultStr |
-| JsonpInjection.java:101:32:101:38 | request : HttpServletRequest | JsonpInjection.java:112:16:112:24 | resultStr |
-| JsonpInjection.java:127:25:127:59 | ... + ... : String | JsonpInjection.java:128:20:128:28 | resultStr |
-| JsonpInjection.java:148:25:148:59 | ... + ... : String | JsonpInjection.java:149:20:149:28 | resultStr |
-nodes
-| JsonpInjection.java:29:32:29:38 | request : HttpServletRequest | semmle.label | request : HttpServletRequest |
-| JsonpInjection.java:33:21:33:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjection.java:34:16:34:24 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:34:16:34:24 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:41:32:41:38 | request : HttpServletRequest | semmle.label | request : HttpServletRequest |
-| JsonpInjection.java:43:21:43:80 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjection.java:45:16:45:24 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:45:16:45:24 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:52:32:52:38 | request : HttpServletRequest | semmle.label | request : HttpServletRequest |
-| JsonpInjection.java:54:21:54:55 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjection.java:55:16:55:24 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:55:16:55:24 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:62:32:62:38 | request : HttpServletRequest | semmle.label | request : HttpServletRequest |
-| JsonpInjection.java:64:21:64:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjection.java:65:16:65:24 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:65:16:65:24 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:72:32:72:38 | request : HttpServletRequest | semmle.label | request : HttpServletRequest |
-| JsonpInjection.java:79:21:79:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjection.java:80:20:80:28 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:80:20:80:28 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:87:32:87:38 | request : HttpServletRequest | semmle.label | request : HttpServletRequest |
-| JsonpInjection.java:93:21:93:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjection.java:94:20:94:28 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:94:20:94:28 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:101:32:101:38 | request : HttpServletRequest | semmle.label | request : HttpServletRequest |
-| JsonpInjection.java:112:16:112:24 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:127:25:127:59 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjection.java:128:20:128:28 | resultStr | semmle.label | resultStr |
-| JsonpInjection.java:148:25:148:59 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjection.java:149:20:149:28 | resultStr | semmle.label | resultStr |
-#select
-| JsonpInjection.java:34:16:34:24 | resultStr | JsonpInjection.java:29:32:29:38 | request : HttpServletRequest | JsonpInjection.java:34:16:34:24 |
-resultStr | Jsonp Injection query might include code from $@. | JsonpInjection.java:29:32:29:38 | request | this user input |
-| JsonpInjection.java:45:16:45:24 | resultStr | JsonpInjection.java:41:32:41:38 | request : HttpServletRequest | JsonpInjection.java:45:16:45:24 |
-resultStr | Jsonp Injection query might include code from $@. | JsonpInjection.java:41:32:41:38 | request | this user input |
-| JsonpInjection.java:55:16:55:24 | resultStr | JsonpInjection.java:52:32:52:38 | request : HttpServletRequest | JsonpInjection.java:55:16:55:24 |
-resultStr | Jsonp Injection query might include code from $@. | JsonpInjection.java:52:32:52:38 | request | this user input |
-| JsonpInjection.java:65:16:65:24 | resultStr | JsonpInjection.java:62:32:62:38 | request : HttpServletRequest | JsonpInjection.java:65:16:65:24 |
-resultStr | Jsonp Injection query might include code from $@. | JsonpInjection.java:62:32:62:38 | request | this user input |
-| JsonpInjection.java:80:20:80:28 | resultStr | JsonpInjection.java:72:32:72:38 | request : HttpServletRequest | JsonpInjection.java:80:20:80:28 |
-resultStr | Jsonp Injection query might include code from $@. | JsonpInjection.java:72:32:72:38 | request | this user input |
-| JsonpInjection.java:94:20:94:28 | resultStr | JsonpInjection.java:87:32:87:38 | request : HttpServletRequest | JsonpInjection.java:94:20:94:28 |
-resultStr | Jsonp Injection query might include code from $@. | JsonpInjection.java:87:32:87:38 | request | this user input |
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionServlet1.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionServlet1.java
new file mode 100644
index 000000000000..14ef76275b1d
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionServlet1.java
@@ -0,0 +1,64 @@
+import com.google.gson.Gson;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class JsonpInjectionServlet1 extends HttpServlet {
+
+ private static HashMap hashMap = new HashMap();
+
+ static {
+ hashMap.put("username","admin");
+ hashMap.put("password","123456");
+ }
+
+ private static final long serialVersionUID = 1L;
+
+ private String key = "test";
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ doPost(req, resp);
+ }
+
+ @Override
+ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ resp.setContentType("application/json");
+ String jsonpCallback = req.getParameter("jsonpCallback");
+ PrintWriter pw = null;
+ Gson gson = new Gson();
+ String jsonResult = gson.toJson(hashMap);
+
+ String referer = req.getHeader("Referer");
+
+ boolean result = verifReferer(referer);
+
+ // good
+ if (result){
+ String resultStr = null;
+ pw = resp.getWriter();
+ resultStr = jsonpCallback + "(" + jsonResult + ")";
+ pw.println(resultStr);
+ pw.flush();
+ }
+ }
+
+ public static boolean verifReferer(String referer){
+ if (!referer.startsWith("http://test.com/")){
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void init(ServletConfig config) throws ServletException {
+ this.key = config.getInitParameter("key");
+ System.out.println("初始化" + this.key);
+ super.init(config);
+ }
+
+}
diff --git a/java/ql/src/Security/CWE/CWE-352/JsonpInjectionServlet.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionServlet2.java
similarity index 75%
rename from java/ql/src/Security/CWE/CWE-352/JsonpInjectionServlet.java
rename to java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionServlet2.java
index 916cd9bf676e..bbfbc2dc4360 100644
--- a/java/ql/src/Security/CWE/CWE-352/JsonpInjectionServlet.java
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionServlet2.java
@@ -4,12 +4,11 @@
import java.util.HashMap;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
-import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-public class JsonpInjectionServlet extends HttpServlet {
+public class JsonpInjectionServlet2 extends HttpServlet {
private static HashMap hashMap = new HashMap();
@@ -23,21 +22,12 @@ public class JsonpInjectionServlet extends HttpServlet {
private String key = "test";
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- String jsonpCallback = req.getParameter("jsonpCallback");
-
- PrintWriter pw = null;
- Gson gson = new Gson();
- String result = gson.toJson(hashMap);
-
- String resultStr = null;
- pw = resp.getWriter();
- resultStr = jsonpCallback + "(" + result + ")";
- pw.println(resultStr);
- pw.flush();
+ doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ resp.setContentType("application/json");
String jsonpCallback = req.getParameter("jsonpCallback");
PrintWriter pw = null;
Gson gson = new Gson();
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_1.expected b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_1.expected
new file mode 100644
index 000000000000..a89d03b67a7b
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_1.expected
@@ -0,0 +1,60 @@
+edges
+| JsonpController.java:26:32:26:68 | getParameter(...) : String | JsonpController.java:31:16:31:24 | resultStr |
+| JsonpController.java:30:21:30:54 | ... + ... : String | JsonpController.java:31:16:31:24 | resultStr |
+| JsonpController.java:38:32:38:68 | getParameter(...) : String | JsonpController.java:42:16:42:24 | resultStr |
+| JsonpController.java:40:21:40:80 | ... + ... : String | JsonpController.java:42:16:42:24 | resultStr |
+| JsonpController.java:49:32:49:68 | getParameter(...) : String | JsonpController.java:52:16:52:24 | resultStr |
+| JsonpController.java:51:21:51:55 | ... + ... : String | JsonpController.java:52:16:52:24 | resultStr |
+| JsonpController.java:59:32:59:68 | getParameter(...) : String | JsonpController.java:62:16:62:24 | resultStr |
+| JsonpController.java:61:21:61:54 | ... + ... : String | JsonpController.java:62:16:62:24 | resultStr |
+| JsonpController.java:69:32:69:68 | getParameter(...) : String | JsonpController.java:77:20:77:28 | resultStr |
+| JsonpController.java:76:21:76:54 | ... + ... : String | JsonpController.java:77:20:77:28 | resultStr |
+| JsonpController.java:84:32:84:68 | getParameter(...) : String | JsonpController.java:91:20:91:28 | resultStr |
+| JsonpController.java:90:21:90:54 | ... + ... : String | JsonpController.java:91:20:91:28 | resultStr |
+| JsonpController.java:99:24:99:52 | getParameter(...) : String | JsonpController.java:101:24:101:28 | token |
+| JsonpController.java:102:36:102:72 | getParameter(...) : String | JsonpController.java:105:20:105:28 | resultStr |
+| JsonpController.java:104:25:104:59 | ... + ... : String | JsonpController.java:105:20:105:28 | resultStr |
+nodes
+| JsonpController.java:26:32:26:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:30:21:30:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:31:16:31:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:31:16:31:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:38:32:38:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:40:21:40:80 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:42:16:42:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:42:16:42:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:49:32:49:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:51:21:51:55 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:52:16:52:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:52:16:52:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:59:32:59:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:61:21:61:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:62:16:62:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:62:16:62:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:69:32:69:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:76:21:76:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:77:20:77:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:77:20:77:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:84:32:84:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:90:21:90:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:91:20:91:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:91:20:91:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:99:24:99:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:101:24:101:28 | token | semmle.label | token |
+| JsonpController.java:102:36:102:72 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:104:25:104:59 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:105:20:105:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:105:20:105:28 | resultStr | semmle.label | resultStr |
+#select
+| JsonpController.java:31:16:31:24 | resultStr | JsonpController.java:26:32:26:68 | getParameter(...) : String | JsonpController.java:31:16:31:24 |
+ resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:26:32:26:68 | getParameter(...) | this user input |
+| JsonpController.java:42:16:42:24 | resultStr | JsonpController.java:38:32:38:68 | getParameter(...) : String | JsonpController.java:42:16:42:24 |
+ resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:38:32:38:68 | getParameter(...) | this user input |
+| JsonpController.java:52:16:52:24 | resultStr | JsonpController.java:49:32:49:68 | getParameter(...) : String | JsonpController.java:52:16:52:24 |
+ resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:49:32:49:68 | getParameter(...) | this user input |
+| JsonpController.java:62:16:62:24 | resultStr | JsonpController.java:59:32:59:68 | getParameter(...) : String | JsonpController.java:62:16:62:24 |
+ resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:59:32:59:68 | getParameter(...) | this user input |
+| JsonpController.java:77:20:77:28 | resultStr | JsonpController.java:69:32:69:68 | getParameter(...) : String | JsonpController.java:77:20:77:28 |
+ resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:69:32:69:68 | getParameter(...) | this user input |
+| JsonpController.java:91:20:91:28 | resultStr | JsonpController.java:84:32:84:68 | getParameter(...) : String | JsonpController.java:91:20:91:28 |
+ resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:84:32:84:68 | getParameter(...) | this user input |
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_2.expected b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_2.expected
new file mode 100644
index 000000000000..4b12308a212d
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_2.expected
@@ -0,0 +1,78 @@
+edges
+| JsonpController.java:26:32:26:68 | getParameter(...) : String | JsonpController.java:31:16:31:24 | resultStr |
+| JsonpController.java:30:21:30:54 | ... + ... : String | JsonpController.java:31:16:31:24 | resultStr |
+| JsonpController.java:38:32:38:68 | getParameter(...) : String | JsonpController.java:42:16:42:24 | resultStr |
+| JsonpController.java:40:21:40:80 | ... + ... : String | JsonpController.java:42:16:42:24 | resultStr |
+| JsonpController.java:49:32:49:68 | getParameter(...) : String | JsonpController.java:52:16:52:24 | resultStr |
+| JsonpController.java:51:21:51:55 | ... + ... : String | JsonpController.java:52:16:52:24 | resultStr |
+| JsonpController.java:59:32:59:68 | getParameter(...) : String | JsonpController.java:62:16:62:24 | resultStr |
+| JsonpController.java:61:21:61:54 | ... + ... : String | JsonpController.java:62:16:62:24 | resultStr |
+| JsonpController.java:69:32:69:68 | getParameter(...) : String | JsonpController.java:77:20:77:28 | resultStr |
+| JsonpController.java:76:21:76:54 | ... + ... : String | JsonpController.java:77:20:77:28 | resultStr |
+| JsonpController.java:84:32:84:68 | getParameter(...) : String | JsonpController.java:91:20:91:28 | resultStr |
+| JsonpController.java:90:21:90:54 | ... + ... : String | JsonpController.java:91:20:91:28 | resultStr |
+| JsonpController.java:99:24:99:52 | getParameter(...) : String | JsonpController.java:101:24:101:28 | token |
+| JsonpController.java:102:36:102:72 | getParameter(...) : String | JsonpController.java:105:20:105:28 | resultStr |
+| JsonpController.java:104:25:104:59 | ... + ... : String | JsonpController.java:105:20:105:28 | resultStr |
+| JsonpInjectionServlet1.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet1.java:45:24:45:32 | resultStr |
+| JsonpInjectionServlet1.java:36:26:36:49 | getHeader(...) : String | JsonpInjectionServlet1.java:38:39:38:45 | referer |
+| JsonpInjectionServlet1.java:44:25:44:62 | ... + ... : String | JsonpInjectionServlet1.java:45:24:45:32 | resultStr |
+| JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr |
+| JsonpInjectionServlet2.java:38:21:38:54 | ... + ... : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr |
+nodes
+| JsonpController.java:26:32:26:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:30:21:30:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:31:16:31:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:31:16:31:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:38:32:38:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:40:21:40:80 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:42:16:42:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:42:16:42:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:49:32:49:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:51:21:51:55 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:52:16:52:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:52:16:52:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:59:32:59:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:61:21:61:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:62:16:62:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:62:16:62:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:69:32:69:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:76:21:76:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:77:20:77:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:77:20:77:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:84:32:84:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:90:21:90:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:91:20:91:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:91:20:91:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:99:24:99:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:101:24:101:28 | token | semmle.label | token |
+| JsonpController.java:102:36:102:72 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:104:25:104:59 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:105:20:105:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:105:20:105:28 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet1.java:31:32:31:64 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpInjectionServlet1.java:36:26:36:49 | getHeader(...) : String | semmle.label | getHeader(...) : String |
+| JsonpInjectionServlet1.java:38:39:38:45 | referer | semmle.label | referer |
+| JsonpInjectionServlet1.java:44:25:44:62 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjectionServlet1.java:45:24:45:32 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet1.java:45:24:45:32 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpInjectionServlet2.java:38:21:38:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | semmle.label | resultStr |
+#select
+| JsonpController.java:31:16:31:24 | resultStr | JsonpController.java:26:32:26:68 | getParameter(...) : String | JsonpController.java:31:16:31:24 |
+ resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:26:32:26:68 | getParameter(...) | this user input |
+| JsonpController.java:42:16:42:24 | resultStr | JsonpController.java:38:32:38:68 | getParameter(...) : String | JsonpController.java:42:16:42:24 |
+ resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:38:32:38:68 | getParameter(...) | this user input |
+| JsonpController.java:52:16:52:24 | resultStr | JsonpController.java:49:32:49:68 | getParameter(...) : String | JsonpController.java:52:16:52:24 |
+ resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:49:32:49:68 | getParameter(...) | this user input |
+| JsonpController.java:62:16:62:24 | resultStr | JsonpController.java:59:32:59:68 | getParameter(...) : String | JsonpController.java:62:16:62:24 |
+ resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:59:32:59:68 | getParameter(...) | this user input |
+| JsonpController.java:77:20:77:28 | resultStr | JsonpController.java:69:32:69:68 | getParameter(...) : String | JsonpController.java:77:20:77:28 |
+ resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:69:32:69:68 | getParameter(...) | this user input |
+| JsonpController.java:91:20:91:28 | resultStr | JsonpController.java:84:32:84:68 | getParameter(...) : String | JsonpController.java:91:20:91:28 |
+ resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:84:32:84:68 | getParameter(...) | this user input |
+| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServle
+t2.java:39:20:39:28 | resultStr | Jsonp Injection query might include code from $@. | JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) |
+ this user input |
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_3.expected b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_3.expected
new file mode 100644
index 000000000000..8e33ca6984c6
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_3.expected
@@ -0,0 +1,66 @@
+edges
+| JsonpController.java:26:32:26:68 | getParameter(...) : String | JsonpController.java:31:16:31:24 | resultStr |
+| JsonpController.java:30:21:30:54 | ... + ... : String | JsonpController.java:31:16:31:24 | resultStr |
+| JsonpController.java:38:32:38:68 | getParameter(...) : String | JsonpController.java:42:16:42:24 | resultStr |
+| JsonpController.java:40:21:40:80 | ... + ... : String | JsonpController.java:42:16:42:24 | resultStr |
+| JsonpController.java:49:32:49:68 | getParameter(...) : String | JsonpController.java:52:16:52:24 | resultStr |
+| JsonpController.java:51:21:51:55 | ... + ... : String | JsonpController.java:52:16:52:24 | resultStr |
+| JsonpController.java:59:32:59:68 | getParameter(...) : String | JsonpController.java:62:16:62:24 | resultStr |
+| JsonpController.java:61:21:61:54 | ... + ... : String | JsonpController.java:62:16:62:24 | resultStr |
+| JsonpController.java:69:32:69:68 | getParameter(...) : String | JsonpController.java:77:20:77:28 | resultStr |
+| JsonpController.java:76:21:76:54 | ... + ... : String | JsonpController.java:77:20:77:28 | resultStr |
+| JsonpController.java:84:32:84:68 | getParameter(...) : String | JsonpController.java:91:20:91:28 | resultStr |
+| JsonpController.java:90:21:90:54 | ... + ... : String | JsonpController.java:91:20:91:28 | resultStr |
+| JsonpController.java:99:24:99:52 | getParameter(...) : String | JsonpController.java:101:24:101:28 | token |
+| JsonpController.java:102:36:102:72 | getParameter(...) : String | JsonpController.java:105:20:105:28 | resultStr |
+| JsonpController.java:104:25:104:59 | ... + ... : String | JsonpController.java:105:20:105:28 | resultStr |
+| JsonpInjectionServlet1.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet1.java:45:24:45:32 | resultStr |
+| JsonpInjectionServlet1.java:36:26:36:49 | getHeader(...) : String | JsonpInjectionServlet1.java:38:39:38:45 | referer |
+| JsonpInjectionServlet1.java:44:25:44:62 | ... + ... : String | JsonpInjectionServlet1.java:45:24:45:32 | resultStr |
+| JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr |
+| JsonpInjectionServlet2.java:38:21:38:54 | ... + ... : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr |
+| RefererFilter.java:22:26:22:53 | getHeader(...) : String | RefererFilter.java:23:39:23:45 | refefer |
+nodes
+| JsonpController.java:26:32:26:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:30:21:30:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:31:16:31:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:31:16:31:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:38:32:38:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:40:21:40:80 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:42:16:42:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:42:16:42:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:49:32:49:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:51:21:51:55 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:52:16:52:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:52:16:52:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:59:32:59:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:61:21:61:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:62:16:62:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:62:16:62:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:69:32:69:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:76:21:76:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:77:20:77:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:77:20:77:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:84:32:84:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:90:21:90:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:91:20:91:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:91:20:91:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:99:24:99:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:101:24:101:28 | token | semmle.label | token |
+| JsonpController.java:102:36:102:72 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:104:25:104:59 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:105:20:105:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:105:20:105:28 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet1.java:31:32:31:64 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpInjectionServlet1.java:36:26:36:49 | getHeader(...) : String | semmle.label | getHeader(...) : String |
+| JsonpInjectionServlet1.java:38:39:38:45 | referer | semmle.label | referer |
+| JsonpInjectionServlet1.java:44:25:44:62 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjectionServlet1.java:45:24:45:32 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet1.java:45:24:45:32 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpInjectionServlet2.java:38:21:38:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | semmle.label | resultStr |
+| RefererFilter.java:22:26:22:53 | getHeader(...) : String | semmle.label | getHeader(...) : String |
+| RefererFilter.java:23:39:23:45 | refefer | semmle.label | refefer |
+#select
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/Readme b/java/ql/test/experimental/query-tests/security/CWE-352/Readme
new file mode 100644
index 000000000000..15715d6187c9
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/Readme
@@ -0,0 +1,3 @@
+1. The JsonpInjection_1.expected result is obtained through the test of `JsonpController.java`.
+2. The JsonpInjection_2.expected result is obtained through the test of `JsonpController.java`, `JsonpInjectionServlet1.java`, `JsonpInjectionServlet2.java`.
+3. The JsonpInjection_3.expected result is obtained through the test of `JsonpController.java`, `JsonpInjectionServlet1.java`, `JsonpInjectionServlet2.java`, `RefererFilter.java`.
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/RefererFilter.java b/java/ql/test/experimental/query-tests/security/CWE-352/RefererFilter.java
new file mode 100644
index 000000000000..97444932ae17
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/RefererFilter.java
@@ -0,0 +1,43 @@
+import java.io.IOException;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.util.StringUtils;
+
+public class RefererFilter implements Filter {
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ }
+
+ @Override
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
+ HttpServletRequest request = (HttpServletRequest) servletRequest;
+ HttpServletResponse response = (HttpServletResponse) servletResponse;
+ String refefer = request.getHeader("Referer");
+ boolean result = verifReferer(refefer);
+ if (result){
+ filterChain.doFilter(servletRequest, servletResponse);
+ }
+ response.sendError(444, "Referer xxx.");
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+ public static boolean verifReferer(String referer){
+ if (StringUtils.isEmpty(referer)){
+ return false;
+ }
+ if (referer.startsWith("http://www.baidu.com/")){
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/java/ql/test/stubs/gson-2.8.6/com/google/gson/Gson.java b/java/ql/test/stubs/gson-2.8.6/com/google/gson/Gson.java
new file mode 100644
index 000000000000..bbe53dc2a5f6
--- /dev/null
+++ b/java/ql/test/stubs/gson-2.8.6/com/google/gson/Gson.java
@@ -0,0 +1,7 @@
+package com.google.gson;
+
+public final class Gson {
+ public String toJson(Object src) {
+ return null;
+ }
+}
diff --git a/java/ql/test/stubs/spring-core-5.3.2/org/springframework/util/StringUtils.java b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/util/StringUtils.java
new file mode 100644
index 000000000000..6ee07f84593f
--- /dev/null
+++ b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/util/StringUtils.java
@@ -0,0 +1,8 @@
+package org.springframework.util;
+
+public abstract class StringUtils {
+
+ public static boolean isEmpty(Object str) {
+ return str == null || "".equals(str);
+ }
+}
\ No newline at end of file
diff --git a/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/Filter.java b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/Filter.java
new file mode 100644
index 000000000000..5833e3c909da
--- /dev/null
+++ b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/Filter.java
@@ -0,0 +1,13 @@
+package javax.servlet;
+
+import java.io.IOException;
+
+public interface Filter {
+ default void init(FilterConfig filterConfig) throws ServletException {
+ }
+
+ void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;
+
+ default void destroy() {
+ }
+}
diff --git a/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/FilterChain.java b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/FilterChain.java
new file mode 100644
index 000000000000..6a1dfc588b6a
--- /dev/null
+++ b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/FilterChain.java
@@ -0,0 +1,8 @@
+package javax.servlet;
+
+import java.io.IOException;
+
+public interface FilterChain {
+ void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
+}
+
diff --git a/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/FilterConfig.java b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/FilterConfig.java
new file mode 100644
index 000000000000..66c13eb54f0b
--- /dev/null
+++ b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/FilterConfig.java
@@ -0,0 +1,3 @@
+package javax.servlet;
+
+public interface FilterConfig {}
diff --git a/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/ServletException.java b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/ServletException.java
new file mode 100644
index 000000000000..ce5f7c4465ae
--- /dev/null
+++ b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/ServletException.java
@@ -0,0 +1,8 @@
+package javax.servlet;
+
+public class ServletException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public ServletException() {
+ }
+}
diff --git a/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/ServletRequest.java b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/ServletRequest.java
new file mode 100644
index 000000000000..4ee0026d0668
--- /dev/null
+++ b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/ServletRequest.java
@@ -0,0 +1,87 @@
+package javax.servlet;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.Map;
+
+public interface ServletRequest {
+ Object getAttribute(String var1);
+
+ Enumeration getAttributeNames();
+
+ String getCharacterEncoding();
+
+ void setCharacterEncoding(String var1) throws UnsupportedEncodingException;
+
+ int getContentLength();
+
+ long getContentLengthLong();
+
+ String getContentType();
+
+ ServletInputStream getInputStream() throws IOException;
+
+ String getParameter(String var1);
+
+ Enumeration getParameterNames();
+
+ String[] getParameterValues(String var1);
+
+ Map getParameterMap();
+
+ String getProtocol();
+
+ String getScheme();
+
+ String getServerName();
+
+ int getServerPort();
+
+ BufferedReader getReader() throws IOException;
+
+ String getRemoteAddr();
+
+ String getRemoteHost();
+
+ void setAttribute(String var1, Object var2);
+
+ void removeAttribute(String var1);
+
+ Locale getLocale();
+
+ Enumeration getLocales();
+
+ boolean isSecure();
+
+ RequestDispatcher getRequestDispatcher(String var1);
+
+ /** @deprecated */
+ @Deprecated
+ String getRealPath(String var1);
+
+ int getRemotePort();
+
+ String getLocalName();
+
+ String getLocalAddr();
+
+ int getLocalPort();
+
+ ServletContext getServletContext();
+
+ AsyncContext startAsync() throws IllegalStateException;
+
+ AsyncContext startAsync(ServletRequest var1, ServletResponse var2) throws IllegalStateException;
+
+ boolean isAsyncStarted();
+
+ boolean isAsyncSupported();
+
+ AsyncContext getAsyncContext();
+
+ DispatcherType getDispatcherType();
+}
+
diff --git a/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/ServletResponse.java b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/ServletResponse.java
new file mode 100644
index 000000000000..0aa6121e686d
--- /dev/null
+++ b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/ServletResponse.java
@@ -0,0 +1,39 @@
+package javax.servlet;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Locale;
+
+public interface ServletResponse {
+ String getCharacterEncoding();
+
+ String getContentType();
+
+ ServletOutputStream getOutputStream() throws IOException;
+
+ PrintWriter getWriter() throws IOException;
+
+ void setCharacterEncoding(String var1);
+
+ void setContentLength(int var1);
+
+ void setContentLengthLong(long var1);
+
+ void setContentType(String var1);
+
+ void setBufferSize(int var1);
+
+ int getBufferSize();
+
+ void flushBuffer() throws IOException;
+
+ void resetBuffer();
+
+ boolean isCommitted();
+
+ void reset();
+
+ void setLocale(Locale var1);
+
+ Locale getLocale();
+}
diff --git a/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/http/HttpServletRequest.java b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/http/HttpServletRequest.java
new file mode 100644
index 000000000000..02d53a96a333
--- /dev/null
+++ b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/http/HttpServletRequest.java
@@ -0,0 +1,116 @@
+package javax.servlet.http;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Map;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+
+public interface HttpServletRequest extends ServletRequest {
+ String BASIC_AUTH = "BASIC";
+ String FORM_AUTH = "FORM";
+ String CLIENT_CERT_AUTH = "CLIENT_CERT";
+ String DIGEST_AUTH = "DIGEST";
+
+ String getAuthType();
+
+ Cookie[] getCookies();
+
+ long getDateHeader(String var1);
+
+ String getHeader(String var1);
+
+ Enumeration getHeaders(String var1);
+
+ Enumeration getHeaderNames();
+
+ int getIntHeader(String var1);
+
+ default HttpServletMapping getHttpServletMapping() {
+ return new HttpServletMapping() {
+ public String getMatchValue() {
+ return "";
+ }
+
+ public String getPattern() {
+ return "";
+ }
+
+ public String getServletName() {
+ return "";
+ }
+
+ public MappingMatch getMappingMatch() {
+ return null;
+ }
+ };
+ }
+
+ String getMethod();
+
+ String getPathInfo();
+
+ String getPathTranslated();
+
+ default PushBuilder newPushBuilder() {
+ return null;
+ }
+
+ String getContextPath();
+
+ String getQueryString();
+
+ String getRemoteUser();
+
+ boolean isUserInRole(String var1);
+
+ Principal getUserPrincipal();
+
+ String getRequestedSessionId();
+
+ String getRequestURI();
+
+ StringBuffer getRequestURL();
+
+ String getServletPath();
+
+ HttpSession getSession(boolean var1);
+
+ HttpSession getSession();
+
+ String changeSessionId();
+
+ boolean isRequestedSessionIdValid();
+
+ boolean isRequestedSessionIdFromCookie();
+
+ boolean isRequestedSessionIdFromURL();
+
+ /** @deprecated */
+ @Deprecated
+ boolean isRequestedSessionIdFromUrl();
+
+ boolean authenticate(HttpServletResponse var1) throws IOException, ServletException;
+
+ void login(String var1, String var2) throws ServletException;
+
+ void logout() throws ServletException;
+
+ Collection getParts() throws IOException, ServletException;
+
+ Part getPart(String var1) throws IOException, ServletException;
+
+ T upgrade(Class var1) throws IOException, ServletException;
+
+ default Map getTrailerFields() {
+ return Collections.emptyMap();
+ }
+
+ default boolean isTrailerFieldsReady() {
+ return false;
+ }
+}
+
diff --git a/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/http/HttpServletResponse.java b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/http/HttpServletResponse.java
new file mode 100644
index 000000000000..0a2c6af0913c
--- /dev/null
+++ b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/http/HttpServletResponse.java
@@ -0,0 +1,106 @@
+package javax.servlet.http;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+import java.util.function.Supplier;
+import javax.servlet.ServletResponse;
+
+public interface HttpServletResponse extends ServletResponse {
+ int SC_CONTINUE = 100;
+ int SC_SWITCHING_PROTOCOLS = 101;
+ int SC_OK = 200;
+ int SC_CREATED = 201;
+ int SC_ACCEPTED = 202;
+ int SC_NON_AUTHORITATIVE_INFORMATION = 203;
+ int SC_NO_CONTENT = 204;
+ int SC_RESET_CONTENT = 205;
+ int SC_PARTIAL_CONTENT = 206;
+ int SC_MULTIPLE_CHOICES = 300;
+ int SC_MOVED_PERMANENTLY = 301;
+ int SC_MOVED_TEMPORARILY = 302;
+ int SC_FOUND = 302;
+ int SC_SEE_OTHER = 303;
+ int SC_NOT_MODIFIED = 304;
+ int SC_USE_PROXY = 305;
+ int SC_TEMPORARY_REDIRECT = 307;
+ int SC_BAD_REQUEST = 400;
+ int SC_UNAUTHORIZED = 401;
+ int SC_PAYMENT_REQUIRED = 402;
+ int SC_FORBIDDEN = 403;
+ int SC_NOT_FOUND = 404;
+ int SC_METHOD_NOT_ALLOWED = 405;
+ int SC_NOT_ACCEPTABLE = 406;
+ int SC_PROXY_AUTHENTICATION_REQUIRED = 407;
+ int SC_REQUEST_TIMEOUT = 408;
+ int SC_CONFLICT = 409;
+ int SC_GONE = 410;
+ int SC_LENGTH_REQUIRED = 411;
+ int SC_PRECONDITION_FAILED = 412;
+ int SC_REQUEST_ENTITY_TOO_LARGE = 413;
+ int SC_REQUEST_URI_TOO_LONG = 414;
+ int SC_UNSUPPORTED_MEDIA_TYPE = 415;
+ int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
+ int SC_EXPECTATION_FAILED = 417;
+ int SC_INTERNAL_SERVER_ERROR = 500;
+ int SC_NOT_IMPLEMENTED = 501;
+ int SC_BAD_GATEWAY = 502;
+ int SC_SERVICE_UNAVAILABLE = 503;
+ int SC_GATEWAY_TIMEOUT = 504;
+ int SC_HTTP_VERSION_NOT_SUPPORTED = 505;
+
+ void addCookie(Cookie var1);
+
+ boolean containsHeader(String var1);
+
+ String encodeURL(String var1);
+
+ String encodeRedirectURL(String var1);
+
+ /** @deprecated */
+ @Deprecated
+ String encodeUrl(String var1);
+
+ /** @deprecated */
+ @Deprecated
+ String encodeRedirectUrl(String var1);
+
+ void sendError(int var1, String var2) throws IOException;
+
+ void sendError(int var1) throws IOException;
+
+ void sendRedirect(String var1) throws IOException;
+
+ void setDateHeader(String var1, long var2);
+
+ void addDateHeader(String var1, long var2);
+
+ void setHeader(String var1, String var2);
+
+ void addHeader(String var1, String var2);
+
+ void setIntHeader(String var1, int var2);
+
+ void addIntHeader(String var1, int var2);
+
+ void setStatus(int var1);
+
+ /** @deprecated */
+ @Deprecated
+ void setStatus(int var1, String var2);
+
+ int getStatus();
+
+ String getHeader(String var1);
+
+ Collection getHeaders(String var1);
+
+ Collection getHeaderNames();
+
+ default void setTrailerFields(Supplier
+
+ Note that ssl.wrap_socket
has been deprecated in
+ Python 3.7. The recommended alternatives are:
+
+
+ ssl.SSLContext
- supported in Python 2.7.9,
+ 3.2, and later versions
+ ssl.create_default_context
- a convenience function,
+ supported in Python 3.4 and later versions.
+
+
+ Even when you use these alternatives, you should
+ ensure that a safe protocol is used. The following code illustrates
+ how to use flags (available since Python 3.2) or the `minimum_version`
+ field (favored since Python 3.7) to restrict the protocols accepted when
+ creating a connection.
+
+
+
Wikipedia: Transport Layer Security.
Python 3 documentation: class ssl.SSLContext.
Python 3 documentation: ssl.wrap_socket.
+ Python 3 documentation: notes on context creation.
+ Python 3 documentation: notes on security considerations.
pyOpenSSL documentation: An interface to the SSL-specific parts of OpenSSL.
diff --git a/python/ql/src/Security/CWE-327/examples/secure_default_protocol.py b/python/ql/src/Security/CWE-327/examples/secure_default_protocol.py
index 83c6dbbba0e0..535a97f0b934 100644
--- a/python/ql/src/Security/CWE-327/examples/secure_default_protocol.py
+++ b/python/ql/src/Security/CWE-327/examples/secure_default_protocol.py
@@ -1,13 +1,9 @@
-# taken from https://docs.python.org/3/library/ssl.html?highlight=ssl#ssl.SSLContext
-
-import socket
import ssl
-hostname = 'www.python.org'
-context = ssl.create_default_context()
-context.options |= ssl.OP_NO_TLSv1 # This added by me
-context.options |= ssl.OP_NO_TLSv1_1 # This added by me
+# Using flags to restrict the protocol
+context = ssl.SSLContext()
+context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
-with socket.create_connection((hostname, 443)) as sock:
- with context.wrap_socket(sock, server_hostname=hostname) as ssock:
- print(ssock.version())
+# Declaring a minimum version to restrict the protocol
+context = ssl.create_default_context()
+context.minimum_version = ssl.TLSVersion.TLSv1_2
From 4094b184074ca1c4b20e4ad599bef6d536f518c3 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Mon, 15 Mar 2021 16:28:08 +0100
Subject: [PATCH 044/433] Python: Clean up tests
---
.../CWE-327/InsecureProtocol.expected | 10 ++---
.../Security/CWE-327/InsecureProtocol.py | 39 ++++++-------------
2 files changed, 17 insertions(+), 32 deletions(-)
diff --git a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
index f4202a4634d5..db30e41b4809 100644
--- a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
+++ b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
@@ -5,11 +5,11 @@
| InsecureProtocol.py:11:1:11:39 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version SSLv3 specified by $@ | InsecureProtocol.py:11:1:11:39 | ControlFlowNode for SSLContext() | call to SSLContext |
| InsecureProtocol.py:12:1:12:39 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version TLSv1 specified by $@ | InsecureProtocol.py:12:1:12:39 | ControlFlowNode for SSLContext() | call to SSLContext |
| InsecureProtocol.py:14:1:14:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified by $@ | InsecureProtocol.py:14:1:14:29 | ControlFlowNode for Attribute() | call to SSL.Context |
-| InsecureProtocol.py:16:1:16:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv3 specified by $@ | InsecureProtocol.py:16:1:16:29 | ControlFlowNode for Attribute() | call to SSL.Context |
-| InsecureProtocol.py:17:1:17:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version TLSv1 specified by $@ | InsecureProtocol.py:17:1:17:29 | ControlFlowNode for Attribute() | call to SSL.Context |
-| InsecureProtocol.py:32:1:32:19 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified by $@ | InsecureProtocol.py:32:1:32:19 | ControlFlowNode for Attribute() | call to SSL.Context |
-| InsecureProtocol.py:48:1:48:43 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified by $@ | InsecureProtocol.py:48:1:48:43 | ControlFlowNode for Attribute() | call to ssl.wrap_socket |
-| InsecureProtocol.py:49:1:49:35 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version SSLv2 specified by $@ | InsecureProtocol.py:49:1:49:35 | ControlFlowNode for SSLContext() | call to SSLContext |
+| InsecureProtocol.py:15:1:15:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv3 specified by $@ | InsecureProtocol.py:15:1:15:29 | ControlFlowNode for Attribute() | call to SSL.Context |
+| InsecureProtocol.py:16:1:16:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version TLSv1 specified by $@ | InsecureProtocol.py:16:1:16:29 | ControlFlowNode for Attribute() | call to SSL.Context |
+| InsecureProtocol.py:19:1:19:19 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified by $@ | InsecureProtocol.py:19:1:19:19 | ControlFlowNode for Attribute() | call to SSL.Context |
+| InsecureProtocol.py:23:1:23:43 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2 specified by $@ | InsecureProtocol.py:23:1:23:43 | ControlFlowNode for Attribute() | call to ssl.wrap_socket |
+| InsecureProtocol.py:24:1:24:35 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version SSLv2 specified by $@ | InsecureProtocol.py:24:1:24:35 | ControlFlowNode for SSLContext() | call to SSLContext |
| pyOpenSSL_fluent.py:8:27:8:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv2 allowed by $@ | pyOpenSSL_fluent.py:6:15:6:44 | ControlFlowNode for Attribute() | call to SSL.Context |
| pyOpenSSL_fluent.py:8:27:8:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv3 allowed by $@ | pyOpenSSL_fluent.py:6:15:6:44 | ControlFlowNode for Attribute() | call to SSL.Context |
| pyOpenSSL_fluent.py:8:27:8:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | pyOpenSSL_fluent.py:6:15:6:44 | ControlFlowNode for Attribute() | call to SSL.Context |
diff --git a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.py b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.py
index cb21a6623c9e..3ff1207b527f 100644
--- a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.py
+++ b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.py
@@ -2,7 +2,7 @@
from OpenSSL import SSL
from ssl import SSLContext
-# true positives
+# insecure versions specified
ssl.wrap_socket(ssl_version=ssl.PROTOCOL_SSLv2)
ssl.wrap_socket(ssl_version=ssl.PROTOCOL_SSLv3)
ssl.wrap_socket(ssl_version=ssl.PROTOCOL_TLSv1)
@@ -12,41 +12,26 @@
SSLContext(protocol=ssl.PROTOCOL_TLSv1)
SSL.Context(SSL.SSLv2_METHOD)
-SSL.Context(SSL.SSLv23_METHOD)
SSL.Context(SSL.SSLv3_METHOD)
SSL.Context(SSL.TLSv1_METHOD)
-# not relevant
-wrap_socket(ssl_version=ssl.PROTOCOL_SSLv3)
-wrap_socket(ssl_version=ssl.PROTOCOL_TLSv1)
-wrap_socket(ssl_version=ssl.PROTOCOL_SSLv2)
-
-Context(SSL.SSLv3_METHOD)
-Context(SSL.TLSv1_METHOD)
-Context(SSL.SSLv2_METHOD)
-Context(SSL.SSLv23_METHOD)
-
-# true positive using flow
-
METHOD = SSL.SSLv2_METHOD
SSL.Context(METHOD)
-# secure versions
+# importing the protocol constant directly
+from ssl import PROTOCOL_SSLv2
+ssl.wrap_socket(ssl_version=PROTOCOL_SSLv2)
+SSLContext(protocol=PROTOCOL_SSLv2)
+# secure versions specified
ssl.wrap_socket(ssl_version=ssl.PROTOCOL_TLSv1_2)
SSLContext(protocol=ssl.PROTOCOL_TLSv1_2)
SSL.Context(SSL.TLSv1_2_METHOD)
-# possibly insecure default
-ssl.wrap_socket()
-context = SSLContext()
+# possibly secure versions specified
+SSLContext(protocol=ssl.PROTOCOL_SSLv23)
+SSLContext(protocol=ssl.PROTOCOL_TLS)
+SSLContext(protocol=ssl.PROTOCOL_TLS_CLIENT)
+SSLContext(protocol=ssl.PROTOCOL_TLS_SERVER)
-# importing the protocol constant directly
-
-from ssl import PROTOCOL_SSLv2
-
-ssl.wrap_socket(ssl_version=PROTOCOL_SSLv2)
-SSLContext(protocol=PROTOCOL_SSLv2)
-
-# FP for insecure default
-ssl.SSLContext(ssl.SSLv23_METHOD)
+SSL.Context(SSL.SSLv23_METHOD)
From 731f4559b415dce07d8118707a42081bcba42c08 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Mon, 15 Mar 2021 17:23:58 +0100
Subject: [PATCH 045/433] Python: update test expectations
---
.../Security/CWE-327/InsecureDefaultProtocol.expected | 2 --
1 file changed, 2 deletions(-)
diff --git a/python/ql/test/query-tests/Security/CWE-327/InsecureDefaultProtocol.expected b/python/ql/test/query-tests/Security/CWE-327/InsecureDefaultProtocol.expected
index 1e389aefdc1e..e69de29bb2d1 100644
--- a/python/ql/test/query-tests/Security/CWE-327/InsecureDefaultProtocol.expected
+++ b/python/ql/test/query-tests/Security/CWE-327/InsecureDefaultProtocol.expected
@@ -1,2 +0,0 @@
-| InsecureProtocol.py:41:1:41:17 | ControlFlowNode for Attribute() | Call to deprecated method ssl.wrap_socket does not specify a protocol, which may result in an insecure default being used. |
-| InsecureProtocol.py:42:11:42:22 | ControlFlowNode for SSLContext() | Call to ssl.SSLContext does not specify a protocol, which may result in an insecure default being used. |
From 87f3ba26843b8f861df47b9ca03226926aa58b5b Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Mon, 15 Mar 2021 17:24:39 +0100
Subject: [PATCH 046/433] Python: add tests for `ssl.PROTOCOL_TLS_SERVER` and
`ssl.PROTOCOL_TLS_CLIENT`
---
.../CWE-327/InsecureProtocol.expected | 36 +++++++++----------
.../Security/CWE-327/ssl_fluent.py | 18 ++++++++++
2 files changed, 36 insertions(+), 18 deletions(-)
diff --git a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
index db30e41b4809..3f383ea52978 100644
--- a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
+++ b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
@@ -22,21 +22,21 @@
| ssl_fluent.py:9:14:9:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:6:15:6:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
| ssl_fluent.py:9:14:9:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:6:15:6:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
| ssl_fluent.py:19:14:19:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:15:15:15:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:37:14:37:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:34:15:34:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:37:14:37:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:34:15:34:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:47:14:47:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:43:15:43:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:75:14:75:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:71:15:71:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:75:14:75:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:71:15:71:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:128:15:128:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:128:15:128:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:144:5:144:11 | ControlFlowNode for context | context modification |
-| ssl_fluent.py:98:14:98:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:162:5:162:11 | ControlFlowNode for context | context modification |
-| ssl_fluent.py:104:14:104:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:104:14:104:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:89:12:89:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:124:14:124:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:122:5:122:11 | ControlFlowNode for context | context modification |
-| ssl_fluent.py:173:14:173:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:170:5:170:11 | ControlFlowNode for context | context modification |
-| ssl_fluent.py:192:14:192:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv3 allowed by $@ | ssl_fluent.py:189:5:189:11 | ControlFlowNode for context | context modification |
-| ssl_fluent.py:192:14:192:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:188:15:188:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context |
-| ssl_fluent.py:192:14:192:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:188:15:188:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context |
+| ssl_fluent.py:55:14:55:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:52:15:52:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:55:14:55:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:52:15:52:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:65:14:65:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:61:15:61:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:93:14:93:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:89:15:89:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:93:14:93:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:89:15:89:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:116:14:116:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:107:12:107:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:116:14:116:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:146:15:146:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:116:14:116:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:107:12:107:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:116:14:116:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:146:15:146:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:116:14:116:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:162:5:162:11 | ControlFlowNode for context | context modification |
+| ssl_fluent.py:116:14:116:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:180:5:180:11 | ControlFlowNode for context | context modification |
+| ssl_fluent.py:122:14:122:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:107:12:107:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:122:14:122:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:107:12:107:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:142:14:142:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:140:5:140:11 | ControlFlowNode for context | context modification |
+| ssl_fluent.py:191:14:191:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:188:5:188:11 | ControlFlowNode for context | context modification |
+| ssl_fluent.py:210:14:210:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv3 allowed by $@ | ssl_fluent.py:207:5:207:11 | ControlFlowNode for context | context modification |
+| ssl_fluent.py:210:14:210:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:206:15:206:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context |
+| ssl_fluent.py:210:14:210:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:206:15:206:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context |
diff --git a/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py b/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
index 577f342765ee..ceddaa32f091 100644
--- a/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
+++ b/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
@@ -19,6 +19,24 @@ def test_fluent_tls_no_TLSv1():
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
print(ssock.version())
+def test_fluent_tls_client_no_TLSv1():
+ hostname = 'www.python.org'
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
+ context.options |= ssl.OP_NO_TLSv1
+
+ with socket.create_connection((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
+def test_fluent_tls_server_no_TLSv1():
+ hostname = 'www.python.org'
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
+ context.options |= ssl.OP_NO_TLSv1
+
+ with socket.create_server((hostname, 443)) as sock:
+ with context.wrap_socket(sock, server_hostname=hostname) as ssock:
+ print(ssock.version())
+
def test_fluent_tls_safe():
hostname = 'www.python.org'
context = ssl.SSLContext(ssl.PROTOCOL_TLS)
From 514a69c47ad79318883483f159662503bc7358b0 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Mon, 15 Mar 2021 17:30:01 +0100
Subject: [PATCH 047/433] Python: Support `ssl.PROTOCOL_TLS_SERVER` and
`ssl.PROTOCOL_TLS_CLIENT`
---
python/ql/src/Security/CWE-327/Ssl.qll | 6 +++++-
.../query-tests/Security/CWE-327/InsecureProtocol.expected | 2 ++
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll
index d4886219d084..be16138b9612 100644
--- a/python/ql/src/Security/CWE-327/Ssl.qll
+++ b/python/ql/src/Security/CWE-327/Ssl.qll
@@ -146,7 +146,11 @@ class Ssl extends TlsLibrary {
override string specific_version_name(ProtocolVersion version) { result = "PROTOCOL_" + version }
- override string unspecific_version_name(ProtocolFamily family) { result = "PROTOCOL_" + family }
+ override string unspecific_version_name(ProtocolFamily family) {
+ family = "SSLv23" and result = "PROTOCOL_" + family
+ or
+ family = "TLS" and result = "PROTOCOL_" + family + ["", "_CLIENT", "_SERVER"]
+ }
override API::Node version_constants() { result = API::moduleImport("ssl") }
diff --git a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
index 3f383ea52978..e578a335d846 100644
--- a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
+++ b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
@@ -22,6 +22,8 @@
| ssl_fluent.py:9:14:9:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:6:15:6:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
| ssl_fluent.py:9:14:9:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:6:15:6:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
| ssl_fluent.py:19:14:19:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:15:15:15:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:28:14:28:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:24:15:24:53 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:37:14:37:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:33:15:33:53 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
| ssl_fluent.py:55:14:55:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:52:15:52:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
| ssl_fluent.py:55:14:55:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:52:15:52:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
| ssl_fluent.py:65:14:65:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:61:15:61:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
From 9a962305236c194d18d80d6321bf11ddc6eabe8b Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Mon, 15 Mar 2021 17:35:30 +0100
Subject: [PATCH 048/433] Python: Add changenote
---
python/change-notes/2021-03-15-port-insecure-protocol.md | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 python/change-notes/2021-03-15-port-insecure-protocol.md
diff --git a/python/change-notes/2021-03-15-port-insecure-protocol.md b/python/change-notes/2021-03-15-port-insecure-protocol.md
new file mode 100644
index 000000000000..c92f387b29a0
--- /dev/null
+++ b/python/change-notes/2021-03-15-port-insecure-protocol.md
@@ -0,0 +1,2 @@
+lgtm,codescanning
+* Ported use of insecure SSL/TLS version (`py/insecure-protocol`) query to use new data-flow library. This might result in different results, but overall a more robust and accurate analysis.
From 98204a15a6ad8c8dd5dfb42269bfdd153cfc1827 Mon Sep 17 00:00:00 2001
From: haby0
Date: Wed, 17 Mar 2021 15:28:04 +0800
Subject: [PATCH 049/433] Fix the problem
---
.../Security/CWE/CWE-352/JsonStringLib.qll | 2 +-
.../Security/CWE/CWE-352/JsonpInjection.java | 113 ++++++----
.../Security/CWE/CWE-352/JsonpInjection.qhelp | 11 +-
.../Security/CWE/CWE-352/JsonpInjection.ql | 24 ++-
.../CWE/CWE-352/JsonpInjectionLib.qll | 66 +++---
.../Security/CWE/CWE-598/SensitiveGetQuery.ql | 10 -
.../semmle/code/java/frameworks/Servlets.qll | 11 +
.../security/CWE-352/JsonpInjection.qlref | 1 -
.../JsonpController.java | 105 +++++++--
.../JsonpInjection.expected | 87 ++++++++
.../JsonpInjection.qlref | 1 +
.../JsonpInjectionServlet1.java | 0
.../JsonpInjectionServlet2.java | 0
.../RefererFilter.java | 0
.../CWE-352/JsonpInjectionWithFilter/options | 1 +
.../JsonpController.java | 107 +++++++--
.../JsonpInjection.expected | 76 +++++++
.../JsonpInjection.qlref | 1 +
.../options | 1 +
.../JsonpController.java | 203 ++++++++++++++++++
.../JsonpInjection.expected | 92 ++++++++
.../JsonpInjection.qlref | 1 +
.../JsonpInjectionServlet1.java | 0
.../JsonpInjectionServlet2.java | 0
.../options | 1 +
.../CWE-352/JsonpInjection_1.expected | 60 ------
.../CWE-352/JsonpInjection_2.expected | 78 -------
.../CWE-352/JsonpInjection_3.expected | 66 ------
.../query-tests/security/CWE-352/Readme | 3 -
.../security/CWE-352/RefererFilter.java | 43 ----
.../query-tests/security/CWE-352/options | 1 -
.../core/annotation/AliasFor.java | 13 +-
.../core/io/InputStreamSource.java | 8 +
.../org/springframework/core/io/Resource.java | 46 ++++
.../org/springframework/lang/Nullable.java | 13 ++
.../springframework/util/FileCopyUtils.java | 53 +++++
.../org/springframework/util/StringUtils.java | 2 +-
.../web/bind/annotation/GetMapping.java | 36 +++-
.../web/bind/annotation/Mapping.java | 4 +
.../web/bind/annotation/RequestMapping.java | 19 +-
.../web/bind/annotation/RequestParam.java | 23 ++
.../web/bind/annotation/ResponseBody.java | 9 +
.../web/multipart/MultipartFile.java | 38 ++++
.../javax/servlet/annotation/WebServlet.java | 30 +++
44 files changed, 1073 insertions(+), 386 deletions(-)
delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.qlref
rename java/ql/test/experimental/query-tests/security/CWE-352/{ => JsonpInjectionWithFilter}/JsonpController.java (54%)
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjection.expected
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjection.qlref
rename java/ql/{src/experimental/Security/CWE/CWE-352 => test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter}/JsonpInjectionServlet1.java (100%)
rename java/ql/{src/experimental/Security/CWE/CWE-352 => test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter}/JsonpInjectionServlet2.java (100%)
rename java/ql/{src/experimental/Security/CWE/CWE-352 => test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter}/RefererFilter.java (100%)
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/options
rename java/ql/{src/experimental/Security/CWE/CWE-352 => test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController}/JsonpController.java (54%)
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpInjection.expected
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpInjection.qlref
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/options
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpController.java
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjection.expected
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjection.qlref
rename java/ql/test/experimental/query-tests/security/CWE-352/{ => JsonpInjectionWithSpringControllerAndServlet}/JsonpInjectionServlet1.java (100%)
rename java/ql/test/experimental/query-tests/security/CWE-352/{ => JsonpInjectionWithSpringControllerAndServlet}/JsonpInjectionServlet2.java (100%)
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/options
delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_1.expected
delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_2.expected
delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_3.expected
delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/Readme
delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/RefererFilter.java
delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/options
create mode 100644 java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/io/InputStreamSource.java
create mode 100644 java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/io/Resource.java
create mode 100644 java/ql/test/stubs/spring-core-5.3.2/org/springframework/lang/Nullable.java
create mode 100644 java/ql/test/stubs/spring-core-5.3.2/org/springframework/util/FileCopyUtils.java
create mode 100644 java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/Mapping.java
create mode 100644 java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/RequestParam.java
create mode 100644 java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/multipart/MultipartFile.java
create mode 100644 java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/annotation/WebServlet.java
diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/JsonStringLib.qll b/java/ql/src/experimental/Security/CWE/CWE-352/JsonStringLib.qll
index 0da8bc860d11..5cc52e97e338 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonStringLib.qll
+++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonStringLib.qll
@@ -3,7 +3,7 @@ import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.FlowSources
import DataFlow::PathGraph
-/** Json string type data */
+/** Json string type data. */
abstract class JsonpStringSource extends DataFlow::Node { }
/** Convert to String using Gson library. */
diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.java b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.java
index 8b4e7cc005ef..7f479a8c0230 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.java
+++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.java
@@ -1,31 +1,39 @@
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.HashMap;
-import java.util.Random;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.multipart.MultipartFile;
@Controller
public class JsonpInjection {
-private static HashMap hashMap = new HashMap();
+
+ private static HashMap hashMap = new HashMap();
static {
hashMap.put("username","admin");
hashMap.put("password","123456");
}
+ private String name = null;
+
@GetMapping(value = "jsonp1")
@ResponseBody
public String bad1(HttpServletRequest request) {
String resultStr = null;
String jsonpCallback = request.getParameter("jsonpCallback");
-
Gson gson = new Gson();
String result = gson.toJson(hashMap);
resultStr = jsonpCallback + "(" + result + ")";
@@ -37,9 +45,7 @@ public String bad1(HttpServletRequest request) {
public String bad2(HttpServletRequest request) {
String resultStr = null;
String jsonpCallback = request.getParameter("jsonpCallback");
-
resultStr = jsonpCallback + "(" + JSONObject.toJSONString(hashMap) + ")";
-
return resultStr;
}
@@ -67,7 +73,6 @@ public String bad4(HttpServletRequest request) {
@ResponseBody
public void bad5(HttpServletRequest request,
HttpServletResponse response) throws Exception {
- response.setContentType("application/json");
String jsonpCallback = request.getParameter("jsonpCallback");
PrintWriter pw = null;
Gson gson = new Gson();
@@ -83,7 +88,6 @@ public void bad5(HttpServletRequest request,
@ResponseBody
public void bad6(HttpServletRequest request,
HttpServletResponse response) throws Exception {
- response.setContentType("application/json");
String jsonpCallback = request.getParameter("jsonpCallback");
PrintWriter pw = null;
ObjectMapper mapper = new ObjectMapper();
@@ -94,60 +98,96 @@ public void bad6(HttpServletRequest request,
pw.println(resultStr);
}
- @GetMapping(value = "jsonp7")
+ @RequestMapping(value = "jsonp7", method = RequestMethod.GET)
@ResponseBody
- public String good(HttpServletRequest request) {
+ public String bad7(HttpServletRequest request) {
String resultStr = null;
String jsonpCallback = request.getParameter("jsonpCallback");
-
- String val = "";
- Random random = new Random();
- for (int i = 0; i < 10; i++) {
- val += String.valueOf(random.nextInt(10));
- }
- // good
- jsonpCallback = jsonpCallback + "_" + val;
- String jsonStr = getJsonStr(hashMap);
- resultStr = jsonpCallback + "(" + jsonStr + ")";
+ Gson gson = new Gson();
+ String result = gson.toJson(hashMap);
+ resultStr = jsonpCallback + "(" + result + ")";
return resultStr;
}
+
@GetMapping(value = "jsonp8")
@ResponseBody
public String good1(HttpServletRequest request) {
String resultStr = null;
- String jsonpCallback = request.getParameter("jsonpCallback");
-
String token = request.getParameter("token");
-
- // good
if (verifToken(token)){
- System.out.println(token);
+ String jsonpCallback = request.getParameter("jsonpCallback");
String jsonStr = getJsonStr(hashMap);
resultStr = jsonpCallback + "(" + jsonStr + ")";
return resultStr;
}
-
return "error";
}
+
@GetMapping(value = "jsonp9")
@ResponseBody
public String good2(HttpServletRequest request) {
String resultStr = null;
+ String token = request.getParameter("token");
+ boolean result = verifToken(token);
+ if (result){
+ return "";
+ }
String jsonpCallback = request.getParameter("jsonpCallback");
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
- String referer = request.getHeader("Referer");
+ @RequestMapping(value = "jsonp10")
+ @ResponseBody
+ public String good3(HttpServletRequest request) {
+ JSONObject parameterObj = readToJSONObect(request);
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String restr = JSONObject.toJSONString(hashMap);
+ resultStr = jsonpCallback + "(" + restr + ");";
+ return resultStr;
+ }
- boolean result = verifReferer(referer);
- // good
- if (result){
- String jsonStr = getJsonStr(hashMap);
- resultStr = jsonpCallback + "(" + jsonStr + ")";
- return resultStr;
+ @RequestMapping(value = "jsonp11")
+ @ResponseBody
+ public String good4(@RequestParam("file") MultipartFile file,HttpServletRequest request) {
+ if(null == file){
+ return "upload file error";
}
+ String fileName = file.getOriginalFilename();
+ System.out.println("file operations");
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String restr = JSONObject.toJSONString(hashMap);
+ resultStr = jsonpCallback + "(" + restr + ");";
+ return resultStr;
+ }
- return "error";
+ public static JSONObject readToJSONObect(HttpServletRequest request){
+ String jsonText = readPostContent(request);
+ JSONObject jsonObj = JSONObject.parseObject(jsonText, JSONObject.class);
+ return jsonObj;
+ }
+
+ public static String readPostContent(HttpServletRequest request){
+ BufferedReader in= null;
+ String content = null;
+ String line = null;
+ try {
+ in = new BufferedReader(new InputStreamReader(request.getInputStream(),"UTF-8"));
+ StringBuilder buf = new StringBuilder();
+ while ((line = in.readLine()) != null) {
+ buf.append(line);
+ }
+ content = buf.toString();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ String uri = request.getRequestURI();
+ return content;
}
public static String getJsonStr(Object result) {
@@ -160,11 +200,4 @@ public static boolean verifToken(String token){
}
return true;
}
-
- public static boolean verifReferer(String referer){
- if (!referer.startsWith("http://test.com/")){
- return false;
- }
- return true;
- }
}
\ No newline at end of file
diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp
index bb5d628ac0b7..93c167d6c2ca 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp
+++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp
@@ -3,18 +3,21 @@
"qhelp.dtd">
-The software uses external input as the function name to wrap JSON data and return it to the client as a request response. When there is a cross-domain problem,
-there is a problem of sensitive information leakage.
+The software uses external input as the function name to wrap JSON data and returns it to the client as a request response.
+When there is a cross-domain problem, the problem of sensitive information leakage may occur.
-Adding `Referer` or random `token` verification processing can effectively prevent the leakage of sensitive information.
+Adding Referer
/Origin
or random token
verification processing can effectively prevent the leakage of sensitive information.
-The following example shows the case of no verification processing and verification processing for the external input function name.
+The following examples show the bad case and the good case respectively. Bad case, such as bad1
to bad7
,
+will cause information leakage problems when there are cross-domain problems. In a good case, for example, in the good1
+method and the good2
method, use the verifToken
method to do the random token
Verification can
+solve the problem of information leakage caused by cross-domain.
diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql
index f3ae25daa034..068469328eae 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql
@@ -14,25 +14,25 @@ import java
import JsonpInjectionLib
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.deadcode.WebEntryPoints
-import semmle.code.java.security.XSS
import DataFlow::PathGraph
/** Determine whether there is a verification method for the remote streaming source data flow path method. */
predicate existsFilterVerificationMethod() {
- exists(MethodAccess ma,Node existsNode, Method m|
+ exists(MethodAccess ma, Node existsNode, Method m |
ma.getMethod() instanceof VerificationMethodClass and
existsNode.asExpr() = ma and
- m = getAnMethod(existsNode.getEnclosingCallable()) and
+ m = getACallingCallableOrSelf(existsNode.getEnclosingCallable()) and
isDoFilterMethod(m)
)
}
/** Determine whether there is a verification method for the remote streaming source data flow path method. */
predicate existsServletVerificationMethod(Node checkNode) {
- exists(MethodAccess ma,Node existsNode|
+ exists(MethodAccess ma, Node existsNode |
ma.getMethod() instanceof VerificationMethodClass and
existsNode.asExpr() = ma and
- getAnMethod(existsNode.getEnclosingCallable()) = getAnMethod(checkNode.getEnclosingCallable())
+ getACallingCallableOrSelf(existsNode.getEnclosingCallable()) =
+ getACallingCallableOrSelf(checkNode.getEnclosingCallable())
)
}
@@ -40,13 +40,15 @@ predicate existsServletVerificationMethod(Node checkNode) {
class RequestResponseFlowConfig extends TaintTracking::Configuration {
RequestResponseFlowConfig() { this = "RequestResponseFlowConfig" }
- override predicate isSource(DataFlow::Node source) {
- source instanceof RemoteFlowSource and
- getAnMethod(source.getEnclosingCallable()) instanceof RequestGetMethod
- }
+ override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof XssSink }
+ /** Eliminate the method of calling the node is not the get method. */
+ override predicate isSanitizer(DataFlow::Node node) {
+ not getACallingCallableOrSelf(node.getEnclosingCallable()) instanceof RequestGetMethod
+ }
+
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(MethodAccess ma |
isRequestGetParamMethod(ma) and pred.asExpr() = ma.getQualifier() and succ.asExpr() = ma
@@ -60,5 +62,5 @@ where
not existsFilterVerificationMethod() and
conf.hasFlowPath(source, sink) and
exists(JsonpInjectionFlowConfig jhfc | jhfc.hasFlowTo(sink.getNode()))
-select sink.getNode(), source, sink, "Jsonp Injection query might include code from $@.",
- source.getNode(), "this user input"
\ No newline at end of file
+select sink.getNode(), source, sink, "Jsonp response might include code from $@.", source.getNode(),
+ "this user input"
diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll
index b8964524a9f8..d0e00bcb634f 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll
+++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll
@@ -6,28 +6,25 @@ import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.frameworks.spring.SpringController
-/** Taint-tracking configuration tracing flow from user-controllable function name jsonp data to output jsonp data. */
+/** Taint-tracking configuration tracing flow from untrusted inputs to verification of remote user input. */
class VerificationMethodFlowConfig extends TaintTracking::Configuration {
VerificationMethodFlowConfig() { this = "VerificationMethodFlowConfig" }
override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma, BarrierGuard bg |
+ exists(MethodAccess ma |
ma.getMethod().getAParameter().getName().regexpMatch("(?i).*(token|auth|referer|origin).*") and
- bg = ma and
- sink.asExpr() = ma.getAnArgument()
+ ma.getAnArgument() = sink.asExpr()
)
}
}
-/** The parameter name of the method is `token`, `auth`, `referer`, `origin`. */
+/** The parameter names of this method are token/auth/referer/origin. */
class VerificationMethodClass extends Method {
VerificationMethodClass() {
- exists(MethodAccess ma, BarrierGuard bg, VerificationMethodFlowConfig vmfc, Node node |
+ exists(MethodAccess ma, VerificationMethodFlowConfig vmfc, Node node |
this = ma.getMethod() and
- this.getAParameter().getName().regexpMatch("(?i).*(token|auth|referer|origin).*") and
- bg = ma and
node.asExpr() = ma.getAnArgument() and
vmfc.hasFlowTo(node)
)
@@ -35,38 +32,43 @@ class VerificationMethodClass extends Method {
}
/** Get Callable by recursive method. */
-Callable getAnMethod(Callable call) {
+Callable getACallingCallableOrSelf(Callable call) {
result = call
or
- result = getAnMethod(call.getAReference().getEnclosingCallable())
+ result = getACallingCallableOrSelf(call.getAReference().getEnclosingCallable())
}
abstract class RequestGetMethod extends Method { }
-/** Holds if `m` is a method of some override of `HttpServlet.doGet`. */
+/** Override method of `doGet` of `Servlet` subclass. */
private class ServletGetMethod extends RequestGetMethod {
- ServletGetMethod() {
- exists(Method m |
- m = this and
- isServletRequestMethod(m) and
- m.getName() = "doGet"
- )
+ ServletGetMethod() { this instanceof DoGetServletMethod }
+}
+
+/** The method of SpringController class processing `get` request. */
+abstract class SpringControllerGetMethod extends RequestGetMethod { }
+
+/** Method using `GetMapping` annotation in SpringController class. */
+class SpringControllerGetMappingGetMethod extends SpringControllerGetMethod {
+ SpringControllerGetMappingGetMethod() {
+ this.getAnAnnotation()
+ .getType()
+ .hasQualifiedName("org.springframework.web.bind.annotation", "GetMapping")
}
}
-/** Holds if `m` is a method of some override of `HttpServlet.doGet`. */
-private class SpringControllerGetMethod extends RequestGetMethod {
- SpringControllerGetMethod() {
- exists(Annotation a |
- a = this.getAnAnnotation() and
- a.getType().hasQualifiedName("org.springframework.web.bind.annotation", "GetMapping")
- )
- or
- exists(Annotation a |
- a = this.getAnAnnotation() and
- a.getType().hasQualifiedName("org.springframework.web.bind.annotation", "RequestMapping") and
- a.getValue("method").toString().regexpMatch("RequestMethod.GET|\\{...\\}")
- )
+/** The method that uses the `RequestMapping` annotation in the SpringController class and only handles the get request. */
+class SpringControllerRequestMappingGetMethod extends SpringControllerGetMethod {
+ SpringControllerRequestMappingGetMethod() {
+ this.getAnAnnotation()
+ .getType()
+ .hasQualifiedName("org.springframework.web.bind.annotation", "RequestMapping") and
+ this.getAnAnnotation().getValue("method").toString().regexpMatch("RequestMethod.GET|\\{...\\}") and
+ not exists(MethodAccess ma |
+ ma.getMethod() instanceof ServletRequestGetBodyMethod and
+ this = getACallingCallableOrSelf(ma.getEnclosingCallable())
+ ) and
+ not this.getAParamType().getName() = "MultipartFile"
}
}
@@ -83,12 +85,12 @@ class JsonpInjectionExpr extends AddExpr {
.regexpMatch("\"\\(\"")
}
- /** Get the jsonp function name of this expression */
+ /** Get the jsonp function name of this expression. */
Expr getFunctionName() {
result = getLeftOperand().(AddExpr).getLeftOperand().(AddExpr).getLeftOperand()
}
- /** Get the json data of this expression */
+ /** Get the json data of this expression. */
Expr getJsonExpr() { result = getLeftOperand().(AddExpr).getRightOperand() }
}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-598/SensitiveGetQuery.ql b/java/ql/src/experimental/Security/CWE/CWE-598/SensitiveGetQuery.ql
index bc9850cfddb9..c381595af14b 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-598/SensitiveGetQuery.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-598/SensitiveGetQuery.ql
@@ -23,16 +23,6 @@ class SensitiveInfoExpr extends Expr {
}
}
-/** Holds if `m` is a method of some override of `HttpServlet.doGet`. */
-private predicate isGetServletMethod(Method m) {
- isServletRequestMethod(m) and m.getName() = "doGet"
-}
-
-/** The `doGet` method of `HttpServlet`. */
-class DoGetServletMethod extends Method {
- DoGetServletMethod() { isGetServletMethod(this) }
-}
-
/** Holds if `ma` is (perhaps indirectly) called from the `doGet` method of `HttpServlet`. */
predicate isReachableFromServletDoGet(MethodAccess ma) {
ma.getEnclosingCallable() instanceof DoGetServletMethod
diff --git a/java/ql/src/semmle/code/java/frameworks/Servlets.qll b/java/ql/src/semmle/code/java/frameworks/Servlets.qll
index 5cccf62122ff..7ac452affa99 100644
--- a/java/ql/src/semmle/code/java/frameworks/Servlets.qll
+++ b/java/ql/src/semmle/code/java/frameworks/Servlets.qll
@@ -354,9 +354,20 @@ class FilterChain extends Interface {
/** Holds if `m` is a filter handler method (for example `doFilter`). */
predicate isDoFilterMethod(Method m) {
+ m.getName().matches("doFilter") and
m.getDeclaringType() instanceof FilterClass and
m.getNumberOfParameters() = 3 and
m.getParameter(0).getType() instanceof ServletRequest and
m.getParameter(1).getType() instanceof ServletResponse and
m.getParameter(2).getType() instanceof FilterChain
}
+
+/** Holds if `m` is a method of some override of `HttpServlet.doGet`. */
+predicate isGetServletMethod(Method m) {
+ isServletRequestMethod(m) and m.getName() = "doGet"
+}
+
+/** The `doGet` method of `HttpServlet`. */
+class DoGetServletMethod extends Method {
+ DoGetServletMethod() { isGetServletMethod(this) }
+}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.qlref b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.qlref
deleted file mode 100644
index 6ad4b8acda76..000000000000
--- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.qlref
+++ /dev/null
@@ -1 +0,0 @@
-Security/CWE/CWE-352/JsonpInjection.ql
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpController.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpController.java
similarity index 54%
rename from java/ql/test/experimental/query-tests/security/CWE-352/JsonpController.java
rename to java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpController.java
index cf860c756409..e5b5e70a38d7 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpController.java
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpController.java
@@ -1,16 +1,24 @@
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.HashMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.multipart.MultipartFile;
@Controller
public class JsonpController {
+
private static HashMap hashMap = new HashMap();
static {
@@ -18,13 +26,14 @@ public class JsonpController {
hashMap.put("password","123456");
}
+ private String name = null;
+
- @GetMapping(value = "jsonp1", produces="text/javascript")
+ @GetMapping(value = "jsonp1")
@ResponseBody
public String bad1(HttpServletRequest request) {
String resultStr = null;
String jsonpCallback = request.getParameter("jsonpCallback");
-
Gson gson = new Gson();
String result = gson.toJson(hashMap);
resultStr = jsonpCallback + "(" + result + ")";
@@ -36,9 +45,7 @@ public String bad1(HttpServletRequest request) {
public String bad2(HttpServletRequest request) {
String resultStr = null;
String jsonpCallback = request.getParameter("jsonpCallback");
-
resultStr = jsonpCallback + "(" + JSONObject.toJSONString(hashMap) + ")";
-
return resultStr;
}
@@ -91,23 +98,98 @@ public void bad6(HttpServletRequest request,
pw.println(resultStr);
}
- @GetMapping(value = "jsonp7")
+ @RequestMapping(value = "jsonp7", method = RequestMethod.GET)
@ResponseBody
- public String good1(HttpServletRequest request) {
+ public String bad7(HttpServletRequest request) {
String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ Gson gson = new Gson();
+ String result = gson.toJson(hashMap);
+ resultStr = jsonpCallback + "(" + result + ")";
+ return resultStr;
+ }
- String token = request.getParameter("token");
+ @GetMapping(value = "jsonp8")
+ @ResponseBody
+ public String good1(HttpServletRequest request) {
+ String resultStr = null;
+ String token = request.getParameter("token");
if (verifToken(token)){
String jsonpCallback = request.getParameter("jsonpCallback");
String jsonStr = getJsonStr(hashMap);
resultStr = jsonpCallback + "(" + jsonStr + ")";
return resultStr;
}
-
return "error";
}
+
+ @GetMapping(value = "jsonp9")
+ @ResponseBody
+ public String good2(HttpServletRequest request) {
+ String resultStr = null;
+ String token = request.getParameter("token");
+ boolean result = verifToken(token);
+ if (result){
+ return "";
+ }
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
+
+ @RequestMapping(value = "jsonp10")
+ @ResponseBody
+ public String good3(HttpServletRequest request) {
+ JSONObject parameterObj = readToJSONObect(request);
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String restr = JSONObject.toJSONString(hashMap);
+ resultStr = jsonpCallback + "(" + restr + ");";
+ return resultStr;
+ }
+
+ @RequestMapping(value = "jsonp11")
+ @ResponseBody
+ public String good4(@RequestParam("file") MultipartFile file,HttpServletRequest request) {
+ if(null == file){
+ return "upload file error";
+ }
+ String fileName = file.getOriginalFilename();
+ System.out.println("file operations");
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String restr = JSONObject.toJSONString(hashMap);
+ resultStr = jsonpCallback + "(" + restr + ");";
+ return resultStr;
+ }
+
+ public static JSONObject readToJSONObect(HttpServletRequest request){
+ String jsonText = readPostContent(request);
+ JSONObject jsonObj = JSONObject.parseObject(jsonText, JSONObject.class);
+ return jsonObj;
+ }
+
+ public static String readPostContent(HttpServletRequest request){
+ BufferedReader in= null;
+ String content = null;
+ String line = null;
+ try {
+ in = new BufferedReader(new InputStreamReader(request.getInputStream(),"UTF-8"));
+ StringBuilder buf = new StringBuilder();
+ while ((line = in.readLine()) != null) {
+ buf.append(line);
+ }
+ content = buf.toString();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ String uri = request.getRequestURI();
+ return content;
+ }
+
public static String getJsonStr(Object result) {
return JSONObject.toJSONString(result);
}
@@ -118,11 +200,4 @@ public static boolean verifToken(String token){
}
return true;
}
-
- public static boolean verifReferer(String referer){
- if (!referer.startsWith("http://test.com/")){
- return false;
- }
- return true;
- }
}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjection.expected
new file mode 100644
index 000000000000..501565f2b4ec
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjection.expected
@@ -0,0 +1,87 @@
+edges
+| JsonpController.java:36:32:36:68 | getParameter(...) : String | JsonpController.java:40:16:40:24 | resultStr |
+| JsonpController.java:39:21:39:54 | ... + ... : String | JsonpController.java:40:16:40:24 | resultStr |
+| JsonpController.java:47:32:47:68 | getParameter(...) : String | JsonpController.java:49:16:49:24 | resultStr |
+| JsonpController.java:48:21:48:80 | ... + ... : String | JsonpController.java:49:16:49:24 | resultStr |
+| JsonpController.java:56:32:56:68 | getParameter(...) : String | JsonpController.java:59:16:59:24 | resultStr |
+| JsonpController.java:58:21:58:55 | ... + ... : String | JsonpController.java:59:16:59:24 | resultStr |
+| JsonpController.java:66:32:66:68 | getParameter(...) : String | JsonpController.java:69:16:69:24 | resultStr |
+| JsonpController.java:68:21:68:54 | ... + ... : String | JsonpController.java:69:16:69:24 | resultStr |
+| JsonpController.java:76:32:76:68 | getParameter(...) : String | JsonpController.java:84:20:84:28 | resultStr |
+| JsonpController.java:83:21:83:54 | ... + ... : String | JsonpController.java:84:20:84:28 | resultStr |
+| JsonpController.java:91:32:91:68 | getParameter(...) : String | JsonpController.java:98:20:98:28 | resultStr |
+| JsonpController.java:97:21:97:54 | ... + ... : String | JsonpController.java:98:20:98:28 | resultStr |
+| JsonpController.java:105:32:105:68 | getParameter(...) : String | JsonpController.java:109:16:109:24 | resultStr |
+| JsonpController.java:108:21:108:54 | ... + ... : String | JsonpController.java:109:16:109:24 | resultStr |
+| JsonpController.java:117:24:117:52 | getParameter(...) : String | JsonpController.java:118:24:118:28 | token |
+| JsonpController.java:119:36:119:72 | getParameter(...) : String | JsonpController.java:122:20:122:28 | resultStr |
+| JsonpController.java:121:25:121:59 | ... + ... : String | JsonpController.java:122:20:122:28 | resultStr |
+| JsonpController.java:132:24:132:52 | getParameter(...) : String | JsonpController.java:133:37:133:41 | token |
+| JsonpController.java:137:32:137:68 | getParameter(...) : String | JsonpController.java:140:16:140:24 | resultStr |
+| JsonpController.java:139:21:139:55 | ... + ... : String | JsonpController.java:140:16:140:24 | resultStr |
+| JsonpController.java:150:21:150:54 | ... + ... : String | JsonpController.java:151:16:151:24 | resultStr |
+| JsonpController.java:165:21:165:54 | ... + ... : String | JsonpController.java:166:16:166:24 | resultStr |
+| JsonpInjectionServlet1.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet1.java:45:24:45:32 | resultStr |
+| JsonpInjectionServlet1.java:36:26:36:49 | getHeader(...) : String | JsonpInjectionServlet1.java:38:39:38:45 | referer |
+| JsonpInjectionServlet1.java:44:25:44:62 | ... + ... : String | JsonpInjectionServlet1.java:45:24:45:32 | resultStr |
+| JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr |
+| JsonpInjectionServlet2.java:38:21:38:54 | ... + ... : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr |
+| RefererFilter.java:22:26:22:53 | getHeader(...) : String | RefererFilter.java:23:39:23:45 | refefer |
+nodes
+| JsonpController.java:36:32:36:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:39:21:39:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:40:16:40:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:40:16:40:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:47:32:47:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:48:21:48:80 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:49:16:49:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:49:16:49:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:56:32:56:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:58:21:58:55 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:59:16:59:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:59:16:59:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:66:32:66:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:68:21:68:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:69:16:69:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:69:16:69:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:76:32:76:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:83:21:83:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:84:20:84:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:84:20:84:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:91:32:91:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:97:21:97:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:98:20:98:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:98:20:98:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:105:32:105:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:108:21:108:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:109:16:109:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:109:16:109:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:117:24:117:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:118:24:118:28 | token | semmle.label | token |
+| JsonpController.java:119:36:119:72 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:121:25:121:59 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:122:20:122:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:122:20:122:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:132:24:132:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:133:37:133:41 | token | semmle.label | token |
+| JsonpController.java:137:32:137:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:139:21:139:55 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:140:16:140:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:140:16:140:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:150:21:150:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:151:16:151:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:165:21:165:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:166:16:166:24 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet1.java:31:32:31:64 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpInjectionServlet1.java:36:26:36:49 | getHeader(...) : String | semmle.label | getHeader(...) : String |
+| JsonpInjectionServlet1.java:38:39:38:45 | referer | semmle.label | referer |
+| JsonpInjectionServlet1.java:44:25:44:62 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjectionServlet1.java:45:24:45:32 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet1.java:45:24:45:32 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpInjectionServlet2.java:38:21:38:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | semmle.label | resultStr |
+| RefererFilter.java:22:26:22:53 | getHeader(...) : String | semmle.label | getHeader(...) : String |
+| RefererFilter.java:23:39:23:45 | refefer | semmle.label | refefer |
+#select
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjection.qlref b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjection.qlref
new file mode 100644
index 000000000000..3f5fc4506696
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjection.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-352/JsonpInjection.ql
diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionServlet1.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjectionServlet1.java
similarity index 100%
rename from java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionServlet1.java
rename to java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjectionServlet1.java
diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionServlet2.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjectionServlet2.java
similarity index 100%
rename from java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionServlet2.java
rename to java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjectionServlet2.java
diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/RefererFilter.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/RefererFilter.java
similarity index 100%
rename from java/ql/src/experimental/Security/CWE/CWE-352/RefererFilter.java
rename to java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/RefererFilter.java
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/options b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/options
new file mode 100644
index 000000000000..c53e31e467fa
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/options
@@ -0,0 +1 @@
+//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../stubs/apache-http-4.4.13/:${testdir}/../../../../../stubs/servlet-api-2.4:${testdir}/../../../../../stubs/fastjson-1.2.74/:${testdir}/../../../../../stubs/gson-2.8.6/:${testdir}/../../../../../stubs/jackson-databind-2.10/:${testdir}/../../../../../stubs/spring-context-5.3.2/:${testdir}/../../../../../stubs/spring-web-5.3.2/:${testdir}/../../../../../stubs/spring-core-5.3.2/:${testdir}/../../../../../stubs/tomcat-embed-core-9.0.41/
diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpController.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpController.java
similarity index 54%
rename from java/ql/src/experimental/Security/CWE/CWE-352/JsonpController.java
rename to java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpController.java
index 84a172a7aebd..e5b5e70a38d7 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpController.java
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpController.java
@@ -1,16 +1,24 @@
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.HashMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.multipart.MultipartFile;
@Controller
public class JsonpController {
+
private static HashMap hashMap = new HashMap();
static {
@@ -18,13 +26,14 @@ public class JsonpController {
hashMap.put("password","123456");
}
+ private String name = null;
+
- @GetMapping(value = "jsonp1", produces="text/javascript")
+ @GetMapping(value = "jsonp1")
@ResponseBody
public String bad1(HttpServletRequest request) {
String resultStr = null;
String jsonpCallback = request.getParameter("jsonpCallback");
-
Gson gson = new Gson();
String result = gson.toJson(hashMap);
resultStr = jsonpCallback + "(" + result + ")";
@@ -36,9 +45,7 @@ public String bad1(HttpServletRequest request) {
public String bad2(HttpServletRequest request) {
String resultStr = null;
String jsonpCallback = request.getParameter("jsonpCallback");
-
resultStr = jsonpCallback + "(" + JSONObject.toJSONString(hashMap) + ")";
-
return resultStr;
}
@@ -91,23 +98,98 @@ public void bad6(HttpServletRequest request,
pw.println(resultStr);
}
- @GetMapping(value = "jsonp7")
+ @RequestMapping(value = "jsonp7", method = RequestMethod.GET)
@ResponseBody
- public String good1(HttpServletRequest request) {
+ public String bad7(HttpServletRequest request) {
String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ Gson gson = new Gson();
+ String result = gson.toJson(hashMap);
+ resultStr = jsonpCallback + "(" + result + ")";
+ return resultStr;
+ }
- String token = request.getParameter("token");
+ @GetMapping(value = "jsonp8")
+ @ResponseBody
+ public String good1(HttpServletRequest request) {
+ String resultStr = null;
+ String token = request.getParameter("token");
if (verifToken(token)){
String jsonpCallback = request.getParameter("jsonpCallback");
String jsonStr = getJsonStr(hashMap);
resultStr = jsonpCallback + "(" + jsonStr + ")";
return resultStr;
}
-
return "error";
}
+
+ @GetMapping(value = "jsonp9")
+ @ResponseBody
+ public String good2(HttpServletRequest request) {
+ String resultStr = null;
+ String token = request.getParameter("token");
+ boolean result = verifToken(token);
+ if (result){
+ return "";
+ }
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
+
+ @RequestMapping(value = "jsonp10")
+ @ResponseBody
+ public String good3(HttpServletRequest request) {
+ JSONObject parameterObj = readToJSONObect(request);
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String restr = JSONObject.toJSONString(hashMap);
+ resultStr = jsonpCallback + "(" + restr + ");";
+ return resultStr;
+ }
+
+ @RequestMapping(value = "jsonp11")
+ @ResponseBody
+ public String good4(@RequestParam("file") MultipartFile file,HttpServletRequest request) {
+ if(null == file){
+ return "upload file error";
+ }
+ String fileName = file.getOriginalFilename();
+ System.out.println("file operations");
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String restr = JSONObject.toJSONString(hashMap);
+ resultStr = jsonpCallback + "(" + restr + ");";
+ return resultStr;
+ }
+
+ public static JSONObject readToJSONObect(HttpServletRequest request){
+ String jsonText = readPostContent(request);
+ JSONObject jsonObj = JSONObject.parseObject(jsonText, JSONObject.class);
+ return jsonObj;
+ }
+
+ public static String readPostContent(HttpServletRequest request){
+ BufferedReader in= null;
+ String content = null;
+ String line = null;
+ try {
+ in = new BufferedReader(new InputStreamReader(request.getInputStream(),"UTF-8"));
+ StringBuilder buf = new StringBuilder();
+ while ((line = in.readLine()) != null) {
+ buf.append(line);
+ }
+ content = buf.toString();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ String uri = request.getRequestURI();
+ return content;
+ }
+
public static String getJsonStr(Object result) {
return JSONObject.toJSONString(result);
}
@@ -118,11 +200,4 @@ public static boolean verifToken(String token){
}
return true;
}
-
- public static boolean verifReferer(String referer){
- if (!referer.startsWith("http://test.com/")){
- return false;
- }
- return true;
- }
-}
\ No newline at end of file
+}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpInjection.expected
new file mode 100644
index 000000000000..91d23cebbdac
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpInjection.expected
@@ -0,0 +1,76 @@
+edges
+| JsonpController.java:36:32:36:68 | getParameter(...) : String | JsonpController.java:40:16:40:24 | resultStr |
+| JsonpController.java:39:21:39:54 | ... + ... : String | JsonpController.java:40:16:40:24 | resultStr |
+| JsonpController.java:47:32:47:68 | getParameter(...) : String | JsonpController.java:49:16:49:24 | resultStr |
+| JsonpController.java:48:21:48:80 | ... + ... : String | JsonpController.java:49:16:49:24 | resultStr |
+| JsonpController.java:56:32:56:68 | getParameter(...) : String | JsonpController.java:59:16:59:24 | resultStr |
+| JsonpController.java:58:21:58:55 | ... + ... : String | JsonpController.java:59:16:59:24 | resultStr |
+| JsonpController.java:66:32:66:68 | getParameter(...) : String | JsonpController.java:69:16:69:24 | resultStr |
+| JsonpController.java:68:21:68:54 | ... + ... : String | JsonpController.java:69:16:69:24 | resultStr |
+| JsonpController.java:76:32:76:68 | getParameter(...) : String | JsonpController.java:84:20:84:28 | resultStr |
+| JsonpController.java:83:21:83:54 | ... + ... : String | JsonpController.java:84:20:84:28 | resultStr |
+| JsonpController.java:91:32:91:68 | getParameter(...) : String | JsonpController.java:98:20:98:28 | resultStr |
+| JsonpController.java:97:21:97:54 | ... + ... : String | JsonpController.java:98:20:98:28 | resultStr |
+| JsonpController.java:105:32:105:68 | getParameter(...) : String | JsonpController.java:109:16:109:24 | resultStr |
+| JsonpController.java:108:21:108:54 | ... + ... : String | JsonpController.java:109:16:109:24 | resultStr |
+| JsonpController.java:117:24:117:52 | getParameter(...) : String | JsonpController.java:118:24:118:28 | token |
+| JsonpController.java:119:36:119:72 | getParameter(...) : String | JsonpController.java:122:20:122:28 | resultStr |
+| JsonpController.java:121:25:121:59 | ... + ... : String | JsonpController.java:122:20:122:28 | resultStr |
+| JsonpController.java:132:24:132:52 | getParameter(...) : String | JsonpController.java:133:37:133:41 | token |
+| JsonpController.java:137:32:137:68 | getParameter(...) : String | JsonpController.java:140:16:140:24 | resultStr |
+| JsonpController.java:139:21:139:55 | ... + ... : String | JsonpController.java:140:16:140:24 | resultStr |
+| JsonpController.java:150:21:150:54 | ... + ... : String | JsonpController.java:151:16:151:24 | resultStr |
+| JsonpController.java:165:21:165:54 | ... + ... : String | JsonpController.java:166:16:166:24 | resultStr |
+nodes
+| JsonpController.java:36:32:36:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:39:21:39:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:40:16:40:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:40:16:40:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:47:32:47:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:48:21:48:80 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:49:16:49:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:49:16:49:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:56:32:56:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:58:21:58:55 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:59:16:59:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:59:16:59:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:66:32:66:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:68:21:68:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:69:16:69:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:69:16:69:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:76:32:76:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:83:21:83:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:84:20:84:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:84:20:84:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:91:32:91:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:97:21:97:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:98:20:98:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:98:20:98:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:105:32:105:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:108:21:108:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:109:16:109:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:109:16:109:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:117:24:117:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:118:24:118:28 | token | semmle.label | token |
+| JsonpController.java:119:36:119:72 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:121:25:121:59 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:122:20:122:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:122:20:122:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:132:24:132:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:133:37:133:41 | token | semmle.label | token |
+| JsonpController.java:137:32:137:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:139:21:139:55 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:140:16:140:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:140:16:140:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:150:21:150:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:151:16:151:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:165:21:165:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:166:16:166:24 | resultStr | semmle.label | resultStr |
+#select
+| JsonpController.java:40:16:40:24 | resultStr | JsonpController.java:36:32:36:68 | getParameter(...) : String | JsonpController.java:40:16:40:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:36:32:36:68 | getParameter(...) | this user input |
+| JsonpController.java:49:16:49:24 | resultStr | JsonpController.java:47:32:47:68 | getParameter(...) : String | JsonpController.java:49:16:49:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:47:32:47:68 | getParameter(...) | this user input |
+| JsonpController.java:59:16:59:24 | resultStr | JsonpController.java:56:32:56:68 | getParameter(...) : String | JsonpController.java:59:16:59:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:56:32:56:68 | getParameter(...) | this user input |
+| JsonpController.java:69:16:69:24 | resultStr | JsonpController.java:66:32:66:68 | getParameter(...) : String | JsonpController.java:69:16:69:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:66:32:66:68 | getParameter(...) | this user input |
+| JsonpController.java:84:20:84:28 | resultStr | JsonpController.java:76:32:76:68 | getParameter(...) : String | JsonpController.java:84:20:84:28 | resultStr | Jsonp response might include code from $@. | JsonpController.java:76:32:76:68 | getParameter(...) | this user input |
+| JsonpController.java:98:20:98:28 | resultStr | JsonpController.java:91:32:91:68 | getParameter(...) : String | JsonpController.java:98:20:98:28 | resultStr | Jsonp response might include code from $@. | JsonpController.java:91:32:91:68 | getParameter(...) | this user input |
+| JsonpController.java:109:16:109:24 | resultStr | JsonpController.java:105:32:105:68 | getParameter(...) : String | JsonpController.java:109:16:109:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:105:32:105:68 | getParameter(...) | this user input |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpInjection.qlref b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpInjection.qlref
new file mode 100644
index 000000000000..3f5fc4506696
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpInjection.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-352/JsonpInjection.ql
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/options b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/options
new file mode 100644
index 000000000000..c53e31e467fa
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/options
@@ -0,0 +1 @@
+//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../stubs/apache-http-4.4.13/:${testdir}/../../../../../stubs/servlet-api-2.4:${testdir}/../../../../../stubs/fastjson-1.2.74/:${testdir}/../../../../../stubs/gson-2.8.6/:${testdir}/../../../../../stubs/jackson-databind-2.10/:${testdir}/../../../../../stubs/spring-context-5.3.2/:${testdir}/../../../../../stubs/spring-web-5.3.2/:${testdir}/../../../../../stubs/spring-core-5.3.2/:${testdir}/../../../../../stubs/tomcat-embed-core-9.0.41/
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpController.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpController.java
new file mode 100644
index 000000000000..e5b5e70a38d7
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpController.java
@@ -0,0 +1,203 @@
+import com.alibaba.fastjson.JSONObject;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.gson.Gson;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.multipart.MultipartFile;
+
+@Controller
+public class JsonpController {
+
+ private static HashMap hashMap = new HashMap();
+
+ static {
+ hashMap.put("username","admin");
+ hashMap.put("password","123456");
+ }
+
+ private String name = null;
+
+
+ @GetMapping(value = "jsonp1")
+ @ResponseBody
+ public String bad1(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ Gson gson = new Gson();
+ String result = gson.toJson(hashMap);
+ resultStr = jsonpCallback + "(" + result + ")";
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp2")
+ @ResponseBody
+ public String bad2(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ resultStr = jsonpCallback + "(" + JSONObject.toJSONString(hashMap) + ")";
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp3")
+ @ResponseBody
+ public String bad3(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp4")
+ @ResponseBody
+ public String bad4(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String restr = JSONObject.toJSONString(hashMap);
+ resultStr = jsonpCallback + "(" + restr + ");";
+ return resultStr;
+ }
+
+ @GetMapping(value = "jsonp5")
+ @ResponseBody
+ public void bad5(HttpServletRequest request,
+ HttpServletResponse response) throws Exception {
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ PrintWriter pw = null;
+ Gson gson = new Gson();
+ String result = gson.toJson(hashMap);
+
+ String resultStr = null;
+ pw = response.getWriter();
+ resultStr = jsonpCallback + "(" + result + ")";
+ pw.println(resultStr);
+ }
+
+ @GetMapping(value = "jsonp6")
+ @ResponseBody
+ public void bad6(HttpServletRequest request,
+ HttpServletResponse response) throws Exception {
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ PrintWriter pw = null;
+ ObjectMapper mapper = new ObjectMapper();
+ String result = mapper.writeValueAsString(hashMap);
+ String resultStr = null;
+ pw = response.getWriter();
+ resultStr = jsonpCallback + "(" + result + ")";
+ pw.println(resultStr);
+ }
+
+ @RequestMapping(value = "jsonp7", method = RequestMethod.GET)
+ @ResponseBody
+ public String bad7(HttpServletRequest request) {
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ Gson gson = new Gson();
+ String result = gson.toJson(hashMap);
+ resultStr = jsonpCallback + "(" + result + ")";
+ return resultStr;
+ }
+
+
+ @GetMapping(value = "jsonp8")
+ @ResponseBody
+ public String good1(HttpServletRequest request) {
+ String resultStr = null;
+ String token = request.getParameter("token");
+ if (verifToken(token)){
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
+ return "error";
+ }
+
+
+ @GetMapping(value = "jsonp9")
+ @ResponseBody
+ public String good2(HttpServletRequest request) {
+ String resultStr = null;
+ String token = request.getParameter("token");
+ boolean result = verifToken(token);
+ if (result){
+ return "";
+ }
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
+
+ @RequestMapping(value = "jsonp10")
+ @ResponseBody
+ public String good3(HttpServletRequest request) {
+ JSONObject parameterObj = readToJSONObect(request);
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String restr = JSONObject.toJSONString(hashMap);
+ resultStr = jsonpCallback + "(" + restr + ");";
+ return resultStr;
+ }
+
+ @RequestMapping(value = "jsonp11")
+ @ResponseBody
+ public String good4(@RequestParam("file") MultipartFile file,HttpServletRequest request) {
+ if(null == file){
+ return "upload file error";
+ }
+ String fileName = file.getOriginalFilename();
+ System.out.println("file operations");
+ String resultStr = null;
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String restr = JSONObject.toJSONString(hashMap);
+ resultStr = jsonpCallback + "(" + restr + ");";
+ return resultStr;
+ }
+
+ public static JSONObject readToJSONObect(HttpServletRequest request){
+ String jsonText = readPostContent(request);
+ JSONObject jsonObj = JSONObject.parseObject(jsonText, JSONObject.class);
+ return jsonObj;
+ }
+
+ public static String readPostContent(HttpServletRequest request){
+ BufferedReader in= null;
+ String content = null;
+ String line = null;
+ try {
+ in = new BufferedReader(new InputStreamReader(request.getInputStream(),"UTF-8"));
+ StringBuilder buf = new StringBuilder();
+ while ((line = in.readLine()) != null) {
+ buf.append(line);
+ }
+ content = buf.toString();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ String uri = request.getRequestURI();
+ return content;
+ }
+
+ public static String getJsonStr(Object result) {
+ return JSONObject.toJSONString(result);
+ }
+
+ public static boolean verifToken(String token){
+ if (token != "xxxx"){
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjection.expected
new file mode 100644
index 000000000000..c2bcab77d4d4
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjection.expected
@@ -0,0 +1,92 @@
+edges
+| JsonpController.java:36:32:36:68 | getParameter(...) : String | JsonpController.java:40:16:40:24 | resultStr |
+| JsonpController.java:39:21:39:54 | ... + ... : String | JsonpController.java:40:16:40:24 | resultStr |
+| JsonpController.java:47:32:47:68 | getParameter(...) : String | JsonpController.java:49:16:49:24 | resultStr |
+| JsonpController.java:48:21:48:80 | ... + ... : String | JsonpController.java:49:16:49:24 | resultStr |
+| JsonpController.java:56:32:56:68 | getParameter(...) : String | JsonpController.java:59:16:59:24 | resultStr |
+| JsonpController.java:58:21:58:55 | ... + ... : String | JsonpController.java:59:16:59:24 | resultStr |
+| JsonpController.java:66:32:66:68 | getParameter(...) : String | JsonpController.java:69:16:69:24 | resultStr |
+| JsonpController.java:68:21:68:54 | ... + ... : String | JsonpController.java:69:16:69:24 | resultStr |
+| JsonpController.java:76:32:76:68 | getParameter(...) : String | JsonpController.java:84:20:84:28 | resultStr |
+| JsonpController.java:83:21:83:54 | ... + ... : String | JsonpController.java:84:20:84:28 | resultStr |
+| JsonpController.java:91:32:91:68 | getParameter(...) : String | JsonpController.java:98:20:98:28 | resultStr |
+| JsonpController.java:97:21:97:54 | ... + ... : String | JsonpController.java:98:20:98:28 | resultStr |
+| JsonpController.java:105:32:105:68 | getParameter(...) : String | JsonpController.java:109:16:109:24 | resultStr |
+| JsonpController.java:108:21:108:54 | ... + ... : String | JsonpController.java:109:16:109:24 | resultStr |
+| JsonpController.java:117:24:117:52 | getParameter(...) : String | JsonpController.java:118:24:118:28 | token |
+| JsonpController.java:119:36:119:72 | getParameter(...) : String | JsonpController.java:122:20:122:28 | resultStr |
+| JsonpController.java:121:25:121:59 | ... + ... : String | JsonpController.java:122:20:122:28 | resultStr |
+| JsonpController.java:132:24:132:52 | getParameter(...) : String | JsonpController.java:133:37:133:41 | token |
+| JsonpController.java:137:32:137:68 | getParameter(...) : String | JsonpController.java:140:16:140:24 | resultStr |
+| JsonpController.java:139:21:139:55 | ... + ... : String | JsonpController.java:140:16:140:24 | resultStr |
+| JsonpController.java:150:21:150:54 | ... + ... : String | JsonpController.java:151:16:151:24 | resultStr |
+| JsonpController.java:165:21:165:54 | ... + ... : String | JsonpController.java:166:16:166:24 | resultStr |
+| JsonpInjectionServlet1.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet1.java:45:24:45:32 | resultStr |
+| JsonpInjectionServlet1.java:36:26:36:49 | getHeader(...) : String | JsonpInjectionServlet1.java:38:39:38:45 | referer |
+| JsonpInjectionServlet1.java:44:25:44:62 | ... + ... : String | JsonpInjectionServlet1.java:45:24:45:32 | resultStr |
+| JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr |
+| JsonpInjectionServlet2.java:38:21:38:54 | ... + ... : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr |
+nodes
+| JsonpController.java:36:32:36:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:39:21:39:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:40:16:40:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:40:16:40:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:47:32:47:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:48:21:48:80 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:49:16:49:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:49:16:49:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:56:32:56:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:58:21:58:55 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:59:16:59:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:59:16:59:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:66:32:66:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:68:21:68:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:69:16:69:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:69:16:69:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:76:32:76:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:83:21:83:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:84:20:84:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:84:20:84:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:91:32:91:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:97:21:97:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:98:20:98:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:98:20:98:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:105:32:105:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:108:21:108:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:109:16:109:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:109:16:109:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:117:24:117:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:118:24:118:28 | token | semmle.label | token |
+| JsonpController.java:119:36:119:72 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:121:25:121:59 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:122:20:122:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:122:20:122:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:132:24:132:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:133:37:133:41 | token | semmle.label | token |
+| JsonpController.java:137:32:137:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:139:21:139:55 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:140:16:140:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:140:16:140:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:150:21:150:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:151:16:151:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:165:21:165:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:166:16:166:24 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet1.java:31:32:31:64 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpInjectionServlet1.java:36:26:36:49 | getHeader(...) : String | semmle.label | getHeader(...) : String |
+| JsonpInjectionServlet1.java:38:39:38:45 | referer | semmle.label | referer |
+| JsonpInjectionServlet1.java:44:25:44:62 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjectionServlet1.java:45:24:45:32 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet1.java:45:24:45:32 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpInjectionServlet2.java:38:21:38:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | semmle.label | resultStr |
+| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | semmle.label | resultStr |
+#select
+| JsonpController.java:40:16:40:24 | resultStr | JsonpController.java:36:32:36:68 | getParameter(...) : String | JsonpController.java:40:16:40:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:36:32:36:68 | getParameter(...) | this user input |
+| JsonpController.java:49:16:49:24 | resultStr | JsonpController.java:47:32:47:68 | getParameter(...) : String | JsonpController.java:49:16:49:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:47:32:47:68 | getParameter(...) | this user input |
+| JsonpController.java:59:16:59:24 | resultStr | JsonpController.java:56:32:56:68 | getParameter(...) : String | JsonpController.java:59:16:59:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:56:32:56:68 | getParameter(...) | this user input |
+| JsonpController.java:69:16:69:24 | resultStr | JsonpController.java:66:32:66:68 | getParameter(...) : String | JsonpController.java:69:16:69:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:66:32:66:68 | getParameter(...) | this user input |
+| JsonpController.java:84:20:84:28 | resultStr | JsonpController.java:76:32:76:68 | getParameter(...) : String | JsonpController.java:84:20:84:28 | resultStr | Jsonp response might include code from $@. | JsonpController.java:76:32:76:68 | getParameter(...) | this user input |
+| JsonpController.java:98:20:98:28 | resultStr | JsonpController.java:91:32:91:68 | getParameter(...) : String | JsonpController.java:98:20:98:28 | resultStr | Jsonp response might include code from $@. | JsonpController.java:91:32:91:68 | getParameter(...) | this user input |
+| JsonpController.java:109:16:109:24 | resultStr | JsonpController.java:105:32:105:68 | getParameter(...) : String | JsonpController.java:109:16:109:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:105:32:105:68 | getParameter(...) | this user input |
+| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr | Jsonp response might include code from $@. | JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) | this user input |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjection.qlref b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjection.qlref
new file mode 100644
index 000000000000..3f5fc4506696
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjection.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-352/JsonpInjection.ql
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionServlet1.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjectionServlet1.java
similarity index 100%
rename from java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionServlet1.java
rename to java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjectionServlet1.java
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionServlet2.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjectionServlet2.java
similarity index 100%
rename from java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionServlet2.java
rename to java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjectionServlet2.java
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/options b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/options
new file mode 100644
index 000000000000..c53e31e467fa
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/options
@@ -0,0 +1 @@
+//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../stubs/apache-http-4.4.13/:${testdir}/../../../../../stubs/servlet-api-2.4:${testdir}/../../../../../stubs/fastjson-1.2.74/:${testdir}/../../../../../stubs/gson-2.8.6/:${testdir}/../../../../../stubs/jackson-databind-2.10/:${testdir}/../../../../../stubs/spring-context-5.3.2/:${testdir}/../../../../../stubs/spring-web-5.3.2/:${testdir}/../../../../../stubs/spring-core-5.3.2/:${testdir}/../../../../../stubs/tomcat-embed-core-9.0.41/
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_1.expected b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_1.expected
deleted file mode 100644
index a89d03b67a7b..000000000000
--- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_1.expected
+++ /dev/null
@@ -1,60 +0,0 @@
-edges
-| JsonpController.java:26:32:26:68 | getParameter(...) : String | JsonpController.java:31:16:31:24 | resultStr |
-| JsonpController.java:30:21:30:54 | ... + ... : String | JsonpController.java:31:16:31:24 | resultStr |
-| JsonpController.java:38:32:38:68 | getParameter(...) : String | JsonpController.java:42:16:42:24 | resultStr |
-| JsonpController.java:40:21:40:80 | ... + ... : String | JsonpController.java:42:16:42:24 | resultStr |
-| JsonpController.java:49:32:49:68 | getParameter(...) : String | JsonpController.java:52:16:52:24 | resultStr |
-| JsonpController.java:51:21:51:55 | ... + ... : String | JsonpController.java:52:16:52:24 | resultStr |
-| JsonpController.java:59:32:59:68 | getParameter(...) : String | JsonpController.java:62:16:62:24 | resultStr |
-| JsonpController.java:61:21:61:54 | ... + ... : String | JsonpController.java:62:16:62:24 | resultStr |
-| JsonpController.java:69:32:69:68 | getParameter(...) : String | JsonpController.java:77:20:77:28 | resultStr |
-| JsonpController.java:76:21:76:54 | ... + ... : String | JsonpController.java:77:20:77:28 | resultStr |
-| JsonpController.java:84:32:84:68 | getParameter(...) : String | JsonpController.java:91:20:91:28 | resultStr |
-| JsonpController.java:90:21:90:54 | ... + ... : String | JsonpController.java:91:20:91:28 | resultStr |
-| JsonpController.java:99:24:99:52 | getParameter(...) : String | JsonpController.java:101:24:101:28 | token |
-| JsonpController.java:102:36:102:72 | getParameter(...) : String | JsonpController.java:105:20:105:28 | resultStr |
-| JsonpController.java:104:25:104:59 | ... + ... : String | JsonpController.java:105:20:105:28 | resultStr |
-nodes
-| JsonpController.java:26:32:26:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:30:21:30:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:31:16:31:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:31:16:31:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:38:32:38:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:40:21:40:80 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:42:16:42:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:42:16:42:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:49:32:49:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:51:21:51:55 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:52:16:52:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:52:16:52:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:59:32:59:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:61:21:61:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:62:16:62:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:62:16:62:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:69:32:69:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:76:21:76:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:77:20:77:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:77:20:77:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:84:32:84:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:90:21:90:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:91:20:91:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:91:20:91:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:99:24:99:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:101:24:101:28 | token | semmle.label | token |
-| JsonpController.java:102:36:102:72 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:104:25:104:59 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:105:20:105:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:105:20:105:28 | resultStr | semmle.label | resultStr |
-#select
-| JsonpController.java:31:16:31:24 | resultStr | JsonpController.java:26:32:26:68 | getParameter(...) : String | JsonpController.java:31:16:31:24 |
- resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:26:32:26:68 | getParameter(...) | this user input |
-| JsonpController.java:42:16:42:24 | resultStr | JsonpController.java:38:32:38:68 | getParameter(...) : String | JsonpController.java:42:16:42:24 |
- resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:38:32:38:68 | getParameter(...) | this user input |
-| JsonpController.java:52:16:52:24 | resultStr | JsonpController.java:49:32:49:68 | getParameter(...) : String | JsonpController.java:52:16:52:24 |
- resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:49:32:49:68 | getParameter(...) | this user input |
-| JsonpController.java:62:16:62:24 | resultStr | JsonpController.java:59:32:59:68 | getParameter(...) : String | JsonpController.java:62:16:62:24 |
- resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:59:32:59:68 | getParameter(...) | this user input |
-| JsonpController.java:77:20:77:28 | resultStr | JsonpController.java:69:32:69:68 | getParameter(...) : String | JsonpController.java:77:20:77:28 |
- resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:69:32:69:68 | getParameter(...) | this user input |
-| JsonpController.java:91:20:91:28 | resultStr | JsonpController.java:84:32:84:68 | getParameter(...) : String | JsonpController.java:91:20:91:28 |
- resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:84:32:84:68 | getParameter(...) | this user input |
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_2.expected b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_2.expected
deleted file mode 100644
index 4b12308a212d..000000000000
--- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_2.expected
+++ /dev/null
@@ -1,78 +0,0 @@
-edges
-| JsonpController.java:26:32:26:68 | getParameter(...) : String | JsonpController.java:31:16:31:24 | resultStr |
-| JsonpController.java:30:21:30:54 | ... + ... : String | JsonpController.java:31:16:31:24 | resultStr |
-| JsonpController.java:38:32:38:68 | getParameter(...) : String | JsonpController.java:42:16:42:24 | resultStr |
-| JsonpController.java:40:21:40:80 | ... + ... : String | JsonpController.java:42:16:42:24 | resultStr |
-| JsonpController.java:49:32:49:68 | getParameter(...) : String | JsonpController.java:52:16:52:24 | resultStr |
-| JsonpController.java:51:21:51:55 | ... + ... : String | JsonpController.java:52:16:52:24 | resultStr |
-| JsonpController.java:59:32:59:68 | getParameter(...) : String | JsonpController.java:62:16:62:24 | resultStr |
-| JsonpController.java:61:21:61:54 | ... + ... : String | JsonpController.java:62:16:62:24 | resultStr |
-| JsonpController.java:69:32:69:68 | getParameter(...) : String | JsonpController.java:77:20:77:28 | resultStr |
-| JsonpController.java:76:21:76:54 | ... + ... : String | JsonpController.java:77:20:77:28 | resultStr |
-| JsonpController.java:84:32:84:68 | getParameter(...) : String | JsonpController.java:91:20:91:28 | resultStr |
-| JsonpController.java:90:21:90:54 | ... + ... : String | JsonpController.java:91:20:91:28 | resultStr |
-| JsonpController.java:99:24:99:52 | getParameter(...) : String | JsonpController.java:101:24:101:28 | token |
-| JsonpController.java:102:36:102:72 | getParameter(...) : String | JsonpController.java:105:20:105:28 | resultStr |
-| JsonpController.java:104:25:104:59 | ... + ... : String | JsonpController.java:105:20:105:28 | resultStr |
-| JsonpInjectionServlet1.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet1.java:45:24:45:32 | resultStr |
-| JsonpInjectionServlet1.java:36:26:36:49 | getHeader(...) : String | JsonpInjectionServlet1.java:38:39:38:45 | referer |
-| JsonpInjectionServlet1.java:44:25:44:62 | ... + ... : String | JsonpInjectionServlet1.java:45:24:45:32 | resultStr |
-| JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr |
-| JsonpInjectionServlet2.java:38:21:38:54 | ... + ... : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr |
-nodes
-| JsonpController.java:26:32:26:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:30:21:30:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:31:16:31:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:31:16:31:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:38:32:38:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:40:21:40:80 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:42:16:42:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:42:16:42:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:49:32:49:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:51:21:51:55 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:52:16:52:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:52:16:52:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:59:32:59:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:61:21:61:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:62:16:62:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:62:16:62:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:69:32:69:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:76:21:76:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:77:20:77:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:77:20:77:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:84:32:84:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:90:21:90:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:91:20:91:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:91:20:91:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:99:24:99:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:101:24:101:28 | token | semmle.label | token |
-| JsonpController.java:102:36:102:72 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:104:25:104:59 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:105:20:105:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:105:20:105:28 | resultStr | semmle.label | resultStr |
-| JsonpInjectionServlet1.java:31:32:31:64 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpInjectionServlet1.java:36:26:36:49 | getHeader(...) : String | semmle.label | getHeader(...) : String |
-| JsonpInjectionServlet1.java:38:39:38:45 | referer | semmle.label | referer |
-| JsonpInjectionServlet1.java:44:25:44:62 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjectionServlet1.java:45:24:45:32 | resultStr | semmle.label | resultStr |
-| JsonpInjectionServlet1.java:45:24:45:32 | resultStr | semmle.label | resultStr |
-| JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpInjectionServlet2.java:38:21:38:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | semmle.label | resultStr |
-| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | semmle.label | resultStr |
-#select
-| JsonpController.java:31:16:31:24 | resultStr | JsonpController.java:26:32:26:68 | getParameter(...) : String | JsonpController.java:31:16:31:24 |
- resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:26:32:26:68 | getParameter(...) | this user input |
-| JsonpController.java:42:16:42:24 | resultStr | JsonpController.java:38:32:38:68 | getParameter(...) : String | JsonpController.java:42:16:42:24 |
- resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:38:32:38:68 | getParameter(...) | this user input |
-| JsonpController.java:52:16:52:24 | resultStr | JsonpController.java:49:32:49:68 | getParameter(...) : String | JsonpController.java:52:16:52:24 |
- resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:49:32:49:68 | getParameter(...) | this user input |
-| JsonpController.java:62:16:62:24 | resultStr | JsonpController.java:59:32:59:68 | getParameter(...) : String | JsonpController.java:62:16:62:24 |
- resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:59:32:59:68 | getParameter(...) | this user input |
-| JsonpController.java:77:20:77:28 | resultStr | JsonpController.java:69:32:69:68 | getParameter(...) : String | JsonpController.java:77:20:77:28 |
- resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:69:32:69:68 | getParameter(...) | this user input |
-| JsonpController.java:91:20:91:28 | resultStr | JsonpController.java:84:32:84:68 | getParameter(...) : String | JsonpController.java:91:20:91:28 |
- resultStr | Jsonp Injection query might include code from $@. | JsonpController.java:84:32:84:68 | getParameter(...) | this user input |
-| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServle
-t2.java:39:20:39:28 | resultStr | Jsonp Injection query might include code from $@. | JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) |
- this user input |
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_3.expected b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_3.expected
deleted file mode 100644
index 8e33ca6984c6..000000000000
--- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection_3.expected
+++ /dev/null
@@ -1,66 +0,0 @@
-edges
-| JsonpController.java:26:32:26:68 | getParameter(...) : String | JsonpController.java:31:16:31:24 | resultStr |
-| JsonpController.java:30:21:30:54 | ... + ... : String | JsonpController.java:31:16:31:24 | resultStr |
-| JsonpController.java:38:32:38:68 | getParameter(...) : String | JsonpController.java:42:16:42:24 | resultStr |
-| JsonpController.java:40:21:40:80 | ... + ... : String | JsonpController.java:42:16:42:24 | resultStr |
-| JsonpController.java:49:32:49:68 | getParameter(...) : String | JsonpController.java:52:16:52:24 | resultStr |
-| JsonpController.java:51:21:51:55 | ... + ... : String | JsonpController.java:52:16:52:24 | resultStr |
-| JsonpController.java:59:32:59:68 | getParameter(...) : String | JsonpController.java:62:16:62:24 | resultStr |
-| JsonpController.java:61:21:61:54 | ... + ... : String | JsonpController.java:62:16:62:24 | resultStr |
-| JsonpController.java:69:32:69:68 | getParameter(...) : String | JsonpController.java:77:20:77:28 | resultStr |
-| JsonpController.java:76:21:76:54 | ... + ... : String | JsonpController.java:77:20:77:28 | resultStr |
-| JsonpController.java:84:32:84:68 | getParameter(...) : String | JsonpController.java:91:20:91:28 | resultStr |
-| JsonpController.java:90:21:90:54 | ... + ... : String | JsonpController.java:91:20:91:28 | resultStr |
-| JsonpController.java:99:24:99:52 | getParameter(...) : String | JsonpController.java:101:24:101:28 | token |
-| JsonpController.java:102:36:102:72 | getParameter(...) : String | JsonpController.java:105:20:105:28 | resultStr |
-| JsonpController.java:104:25:104:59 | ... + ... : String | JsonpController.java:105:20:105:28 | resultStr |
-| JsonpInjectionServlet1.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet1.java:45:24:45:32 | resultStr |
-| JsonpInjectionServlet1.java:36:26:36:49 | getHeader(...) : String | JsonpInjectionServlet1.java:38:39:38:45 | referer |
-| JsonpInjectionServlet1.java:44:25:44:62 | ... + ... : String | JsonpInjectionServlet1.java:45:24:45:32 | resultStr |
-| JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr |
-| JsonpInjectionServlet2.java:38:21:38:54 | ... + ... : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr |
-| RefererFilter.java:22:26:22:53 | getHeader(...) : String | RefererFilter.java:23:39:23:45 | refefer |
-nodes
-| JsonpController.java:26:32:26:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:30:21:30:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:31:16:31:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:31:16:31:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:38:32:38:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:40:21:40:80 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:42:16:42:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:42:16:42:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:49:32:49:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:51:21:51:55 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:52:16:52:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:52:16:52:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:59:32:59:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:61:21:61:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:62:16:62:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:62:16:62:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:69:32:69:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:76:21:76:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:77:20:77:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:77:20:77:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:84:32:84:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:90:21:90:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:91:20:91:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:91:20:91:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:99:24:99:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:101:24:101:28 | token | semmle.label | token |
-| JsonpController.java:102:36:102:72 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:104:25:104:59 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:105:20:105:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:105:20:105:28 | resultStr | semmle.label | resultStr |
-| JsonpInjectionServlet1.java:31:32:31:64 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpInjectionServlet1.java:36:26:36:49 | getHeader(...) : String | semmle.label | getHeader(...) : String |
-| JsonpInjectionServlet1.java:38:39:38:45 | referer | semmle.label | referer |
-| JsonpInjectionServlet1.java:44:25:44:62 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjectionServlet1.java:45:24:45:32 | resultStr | semmle.label | resultStr |
-| JsonpInjectionServlet1.java:45:24:45:32 | resultStr | semmle.label | resultStr |
-| JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpInjectionServlet2.java:38:21:38:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | semmle.label | resultStr |
-| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | semmle.label | resultStr |
-| RefererFilter.java:22:26:22:53 | getHeader(...) : String | semmle.label | getHeader(...) : String |
-| RefererFilter.java:23:39:23:45 | refefer | semmle.label | refefer |
-#select
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/Readme b/java/ql/test/experimental/query-tests/security/CWE-352/Readme
deleted file mode 100644
index 15715d6187c9..000000000000
--- a/java/ql/test/experimental/query-tests/security/CWE-352/Readme
+++ /dev/null
@@ -1,3 +0,0 @@
-1. The JsonpInjection_1.expected result is obtained through the test of `JsonpController.java`.
-2. The JsonpInjection_2.expected result is obtained through the test of `JsonpController.java`, `JsonpInjectionServlet1.java`, `JsonpInjectionServlet2.java`.
-3. The JsonpInjection_3.expected result is obtained through the test of `JsonpController.java`, `JsonpInjectionServlet1.java`, `JsonpInjectionServlet2.java`, `RefererFilter.java`.
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/RefererFilter.java b/java/ql/test/experimental/query-tests/security/CWE-352/RefererFilter.java
deleted file mode 100644
index 97444932ae17..000000000000
--- a/java/ql/test/experimental/query-tests/security/CWE-352/RefererFilter.java
+++ /dev/null
@@ -1,43 +0,0 @@
-import java.io.IOException;
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import org.springframework.util.StringUtils;
-
-public class RefererFilter implements Filter {
-
- @Override
- public void init(FilterConfig filterConfig) throws ServletException {
- }
-
- @Override
- public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
- HttpServletRequest request = (HttpServletRequest) servletRequest;
- HttpServletResponse response = (HttpServletResponse) servletResponse;
- String refefer = request.getHeader("Referer");
- boolean result = verifReferer(refefer);
- if (result){
- filterChain.doFilter(servletRequest, servletResponse);
- }
- response.sendError(444, "Referer xxx.");
- }
-
- @Override
- public void destroy() {
- }
-
- public static boolean verifReferer(String referer){
- if (StringUtils.isEmpty(referer)){
- return false;
- }
- if (referer.startsWith("http://www.baidu.com/")){
- return true;
- }
- return false;
- }
-}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/options b/java/ql/test/experimental/query-tests/security/CWE-352/options
deleted file mode 100644
index 3676b8e38b69..000000000000
--- a/java/ql/test/experimental/query-tests/security/CWE-352/options
+++ /dev/null
@@ -1 +0,0 @@
-//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/apache-http-4.4.13/:${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../../stubs/fastjson-1.2.74/:${testdir}/../../../../stubs/gson-2.8.6/:${testdir}/../../../../stubs/jackson-databind-2.10/:${testdir}/../../../../stubs/springframework-5.2.3/:${testdir}/../../../../stubs/spring-context-5.3.2/:${testdir}/../../../../stubs/spring-web-5.3.2/:${testdir}/../../../../stubs/spring-core-5.3.2/
diff --git a/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/annotation/AliasFor.java b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/annotation/AliasFor.java
index 3a823fade5b0..edfe917400b7 100644
--- a/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/annotation/AliasFor.java
+++ b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/annotation/AliasFor.java
@@ -1,10 +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 extends Annotation> annotation() default Annotation.class;
}
diff --git a/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/io/InputStreamSource.java b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/io/InputStreamSource.java
new file mode 100644
index 000000000000..372d06cc7383
--- /dev/null
+++ b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/io/InputStreamSource.java
@@ -0,0 +1,8 @@
+package org.springframework.core.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public interface InputStreamSource {
+ InputStream getInputStream() throws IOException;
+}
diff --git a/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/io/Resource.java b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/io/Resource.java
new file mode 100644
index 000000000000..6bd357f2228e
--- /dev/null
+++ b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/io/Resource.java
@@ -0,0 +1,46 @@
+package org.springframework.core.io;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URL;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import org.springframework.lang.Nullable;
+
+public interface Resource extends InputStreamSource {
+ boolean exists();
+
+ default boolean isReadable() {
+ return this.exists();
+ }
+
+ default boolean isOpen() {
+ return false;
+ }
+
+ default boolean isFile() {
+ return false;
+ }
+
+ URL getURL() throws IOException;
+
+ URI getURI() throws IOException;
+
+ File getFile() throws IOException;
+
+ default ReadableByteChannel readableChannel() throws IOException {
+ return null;
+ }
+
+ long contentLength() throws IOException;
+
+ long lastModified() throws IOException;
+
+ Resource createRelative(String var1) throws IOException;
+
+ @Nullable
+ String getFilename();
+
+ String getDescription();
+}
diff --git a/java/ql/test/stubs/spring-core-5.3.2/org/springframework/lang/Nullable.java b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/lang/Nullable.java
new file mode 100644
index 000000000000..44bdae10fda5
--- /dev/null
+++ b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/lang/Nullable.java
@@ -0,0 +1,13 @@
+package org.springframework.lang;
+
+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.METHOD, ElementType.PARAMETER, ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Nullable {
+}
diff --git a/java/ql/test/stubs/spring-core-5.3.2/org/springframework/util/FileCopyUtils.java b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/util/FileCopyUtils.java
new file mode 100644
index 000000000000..78d384d72660
--- /dev/null
+++ b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/util/FileCopyUtils.java
@@ -0,0 +1,53 @@
+package org.springframework.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.nio.file.Files;
+import org.springframework.lang.Nullable;
+
+public abstract class FileCopyUtils {
+ public static final int BUFFER_SIZE = 4096;
+
+ public FileCopyUtils() {
+ }
+
+ public static int copy(File in, File out) throws IOException {
+ return 1;
+ }
+
+ public static void copy(byte[] in, File out) throws IOException {}
+
+ public static byte[] copyToByteArray(File in) throws IOException {
+ return null;
+ }
+
+ public static int copy(InputStream in, OutputStream out) throws IOException {
+ return 1;
+ }
+
+ public static void copy(byte[] in, OutputStream out) throws IOException {}
+
+ public static byte[] copyToByteArray(@Nullable InputStream in) throws IOException {
+ return null;
+ }
+
+ public static int copy(Reader in, Writer out) throws IOException {
+ return 1;
+ }
+
+ public static void copy(String in, Writer out) throws IOException {}
+
+ public static String copyToString(@Nullable Reader in) throws IOException {
+ return null;
+ }
+
+ private static void close(Closeable closeable) {}
+}
diff --git a/java/ql/test/stubs/spring-core-5.3.2/org/springframework/util/StringUtils.java b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/util/StringUtils.java
index 6ee07f84593f..6ea27bbffa2f 100644
--- a/java/ql/test/stubs/spring-core-5.3.2/org/springframework/util/StringUtils.java
+++ b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/util/StringUtils.java
@@ -5,4 +5,4 @@ public abstract class StringUtils {
public static boolean isEmpty(Object str) {
return str == null || "".equals(str);
}
-}
\ No newline at end of file
+}
diff --git a/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/GetMapping.java b/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/GetMapping.java
index 1190be7b879b..541c7cd4e217 100644
--- a/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/GetMapping.java
+++ b/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/GetMapping.java
@@ -1,19 +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;
-@RequestMapping
+@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/spring-web-5.3.2/org/springframework/web/bind/annotation/Mapping.java b/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/Mapping.java
new file mode 100644
index 000000000000..5f269bbcbb87
--- /dev/null
+++ b/java/ql/test/stubs/spring-web-5.3.2/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/spring-web-5.3.2/org/springframework/web/bind/annotation/RequestMapping.java b/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/RequestMapping.java
index ddb36bce4c00..ed692a03063c 100644
--- a/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/RequestMapping.java
+++ b/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/RequestMapping.java
@@ -1,15 +1,32 @@
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.TYPE, ElementType.METHOD})
+@Retention(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/spring-web-5.3.2/org/springframework/web/bind/annotation/RequestParam.java b/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/RequestParam.java
new file mode 100644
index 000000000000..56094811c376
--- /dev/null
+++ b/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/RequestParam.java
@@ -0,0 +1,23 @@
+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.PARAMETER})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+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/spring-web-5.3.2/org/springframework/web/bind/annotation/ResponseBody.java b/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/ResponseBody.java
index b2134009968b..4f21b6daaaf0 100644
--- a/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/ResponseBody.java
+++ b/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/ResponseBody.java
@@ -1,4 +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 {
}
diff --git a/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/multipart/MultipartFile.java b/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/multipart/MultipartFile.java
new file mode 100644
index 000000000000..93ea3439fede
--- /dev/null
+++ b/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/multipart/MultipartFile.java
@@ -0,0 +1,38 @@
+package org.springframework.web.multipart;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.springframework.core.io.InputStreamSource;
+import org.springframework.core.io.Resource;
+import org.springframework.lang.Nullable;
+import org.springframework.util.FileCopyUtils;
+
+public interface MultipartFile extends InputStreamSource {
+ String getName();
+
+ @Nullable
+ String getOriginalFilename();
+
+ @Nullable
+ String getContentType();
+
+ boolean isEmpty();
+
+ long getSize();
+
+ byte[] getBytes() throws IOException;
+
+ InputStream getInputStream() throws IOException;
+
+ default Resource getResource() {
+ return null;
+ }
+
+ void transferTo(File var1) throws IOException, IllegalStateException;
+
+ default void transferTo(Path dest) throws IOException, IllegalStateException {
+ }
+}
diff --git a/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/annotation/WebServlet.java b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/annotation/WebServlet.java
new file mode 100644
index 000000000000..cc255efc00ff
--- /dev/null
+++ b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/annotation/WebServlet.java
@@ -0,0 +1,30 @@
+package javax.servlet.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})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface WebServlet {
+ String name() default "";
+
+ String[] value() default {};
+
+ String[] urlPatterns() default {};
+
+ int loadOnStartup() default -1;
+
+ boolean asyncSupported() default false;
+
+ String smallIcon() default "";
+
+ String largeIcon() default "";
+
+ String description() default "";
+
+ String displayName() default "";
+}
From 15206fd2ce3d53168caa2d0250c2b9036dccfca9 Mon Sep 17 00:00:00 2001
From: haby0
Date: Wed, 17 Mar 2021 15:52:05 +0800
Subject: [PATCH 050/433] JsonpInjection.ql autoformatted
---
java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql
index 068469328eae..eb4fe4e5b66a 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql
@@ -46,7 +46,7 @@ class RequestResponseFlowConfig extends TaintTracking::Configuration {
/** Eliminate the method of calling the node is not the get method. */
override predicate isSanitizer(DataFlow::Node node) {
- not getACallingCallableOrSelf(node.getEnclosingCallable()) instanceof RequestGetMethod
+ not getACallingCallableOrSelf(node.getEnclosingCallable()) instanceof RequestGetMethod
}
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
From 164b383fda25be40906e216a53581c5fec4da4df Mon Sep 17 00:00:00 2001
From: yoff
Date: Fri, 19 Mar 2021 19:12:13 +0100
Subject: [PATCH 051/433] Update
python/ql/test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py
Co-authored-by: Rasmus Wriedt Larsen
---
.../test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/python/ql/test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py b/python/ql/test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py
index 028c318be668..e4205c498244 100644
--- a/python/ql/test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py
+++ b/python/ql/test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py
@@ -29,3 +29,11 @@ def test_fluent_safe():
conn = SSL.Connection(context, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
r = conn.connect((hostname, 443))
print(r, conn.get_protocol_version_name())
+
+def test_safe_by_construction():
+ hostname = 'www.python.org'
+ context = SSL.Context(SSL.TLSv1_2_METHOD)
+
+ conn = SSL.Connection(context, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
+ r = conn.connect((hostname, 443))
+ print(conn.get_protocol_version_name())
From 1385b2264234bc6fa3deaaeb7537ee47d89c47fe Mon Sep 17 00:00:00 2001
From: Dilan
Date: Fri, 19 Mar 2021 16:43:29 -0700
Subject: [PATCH 052/433] pr fixes, typo in qhelp file and helper method for
queries
---
.../Classes/NamingConventionsClasses.qhelp | 2 +-
.../Classes/NamingConventionsClasses.ql | 17 ++++++++-----
.../NamingConventionsFunctions.qhelp | 2 +-
.../Functions/NamingConventionsFunctions.ql | 25 +++++++++++--------
4 files changed, 28 insertions(+), 18 deletions(-)
diff --git a/python/ql/src/experimental/Classes/NamingConventionsClasses.qhelp b/python/ql/src/experimental/Classes/NamingConventionsClasses.qhelp
index 1a7f4bc45a4f..a928668dc6f7 100644
--- a/python/ql/src/experimental/Classes/NamingConventionsClasses.qhelp
+++ b/python/ql/src/experimental/Classes/NamingConventionsClasses.qhelp
@@ -21,7 +21,7 @@ Write the class name beginning with an uppercase letter. For example, clas
- Guido van Rossum, Barry Warsaw, Nick Coghlan PEP 8 -- Style Guide for Python Code
+ Guido van Rossum, Barry Warsaw, Nick Coghlan PEP 8 -- Style Guide for Python Code
Python Class Names
diff --git a/python/ql/src/experimental/Classes/NamingConventionsClasses.ql b/python/ql/src/experimental/Classes/NamingConventionsClasses.ql
index d4919c3ece8d..f16d242eadf3 100644
--- a/python/ql/src/experimental/Classes/NamingConventionsClasses.ql
+++ b/python/ql/src/experimental/Classes/NamingConventionsClasses.ql
@@ -9,15 +9,20 @@
import python
-from Class c, string first_char
+predicate lower_case_class(Class c) {
+ exists(string first_char |
+ first_char = c.getName().prefix(1) and
+ not first_char = first_char.toUpperCase()
+ )
+}
+
+from Class c
where
c.inSource() and
- first_char = c.getName().prefix(1) and
- not first_char = first_char.toUpperCase() and
- not exists(Class c1, string first_char1 |
+ lower_case_class(c) and
+ not exists(Class c1 |
c1 != c and
c1.getLocation().getFile() = c.getLocation().getFile() and
- first_char1 = c1.getName().prefix(1) and
- not first_char1 = first_char1.toUpperCase()
+ lower_case_class(c1)
)
select c, "Class names should start in uppercase."
diff --git a/python/ql/src/experimental/Functions/NamingConventionsFunctions.qhelp b/python/ql/src/experimental/Functions/NamingConventionsFunctions.qhelp
index 46d948592ff2..b4ff738782c9 100644
--- a/python/ql/src/experimental/Functions/NamingConventionsFunctions.qhelp
+++ b/python/ql/src/experimental/Functions/NamingConventionsFunctions.qhelp
@@ -21,7 +21,7 @@ Write the function name beginning with an lowercase letter. For example, j
- Guido van Rossum, Barry Warsaw, Nick Coghlan PEP 8 -- Style Guide for Python Code
+ Guido van Rossum, Barry Warsaw, Nick Coghlan PEP 8 -- Style Guide for Python Code
Python Function and Variable Names
diff --git a/python/ql/src/experimental/Functions/NamingConventionsFunctions.ql b/python/ql/src/experimental/Functions/NamingConventionsFunctions.ql
index 80dc0e99cd8e..d2868af22c99 100644
--- a/python/ql/src/experimental/Functions/NamingConventionsFunctions.ql
+++ b/python/ql/src/experimental/Functions/NamingConventionsFunctions.ql
@@ -9,15 +9,20 @@
import python
-from Function f, string first_char
+predicate upper_case_function(Function func) {
+ exists(string first_char |
+ first_char = func.getName().prefix(1) and
+ not first_char = first_char.toLowerCase()
+ )
+}
+
+from Function func
where
- f.inSource() and
- first_char = f.getName().prefix(1) and
- not first_char = first_char.toLowerCase() and
- not exists(Function f1, string first_char1 |
- f1 != f and
- f1.getLocation().getFile() = f.getLocation().getFile() and
- first_char1 = f1.getName().prefix(1) and
- not first_char1 = first_char1.toLowerCase()
+ func.inSource() and
+ upper_case_function(func) and
+ not exists(Function func1 |
+ func1 != func and
+ func1.getLocation().getFile() = func.getLocation().getFile() and
+ upper_case_function(func1)
)
-select f, "Function names should start in lowercase."
+select func, "Function names should start in lowercase."
From 26bac9f425c2ecab54759797da19bcc1586ed95d Mon Sep 17 00:00:00 2001
From: ihsinme
Date: Sun, 21 Mar 2021 15:25:29 +0300
Subject: [PATCH 053/433] Apply suggestions from code review
Co-authored-by: Robert Marsh
---
.../CWE-783/OperatorPrecedenceLogicErrorWhenUseBoolType.ql | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBoolType.ql b/cpp/ql/src/experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBoolType.ql
index 1a116a83dbf7..034df703bc30 100644
--- a/cpp/ql/src/experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBoolType.ql
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBoolType.ql
@@ -15,12 +15,12 @@
import cpp
import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
-/** Holds, if it is an expression, a boolean increment. */
+/** Holds if `exp` increments a boolean value. */
predicate incrementBoolType(Expr exp) {
exp.(IncrementOperation).getOperand().getType() instanceof BoolType
}
-/** Holds, if this is an expression, applies a minus to a boolean type. */
+/** Holds if `exp` applies the unary minus operator to a boolean type. */
predicate revertSignBoolType(Expr exp) {
exp.(AssignExpr).getRValue().(UnaryMinusExpr).getAnOperand().getType() instanceof BoolType and
exp.(AssignExpr).getLValue().getType() instanceof BoolType
From 0200aedc2efc939b96170951d848d440fcbb8e4a Mon Sep 17 00:00:00 2001
From: yo-h <55373593+yo-h@users.noreply.github.com>
Date: Sun, 21 Mar 2021 12:55:25 -0400
Subject: [PATCH 054/433] Java 16: adjust test `options`
---
java/ql/test/library-tests/dataflow/records/options | 2 +-
java/ql/test/library-tests/dataflow/taint-format/options | 1 -
java/ql/test/library-tests/printAst/options | 2 +-
java/ql/test/library-tests/ssa/options | 2 +-
java/ql/test/query-tests/StringFormat/options | 1 -
5 files changed, 3 insertions(+), 5 deletions(-)
delete mode 100644 java/ql/test/library-tests/dataflow/taint-format/options
delete mode 100644 java/ql/test/query-tests/StringFormat/options
diff --git a/java/ql/test/library-tests/dataflow/records/options b/java/ql/test/library-tests/dataflow/records/options
index 81817b377852..fc57fe025b9a 100644
--- a/java/ql/test/library-tests/dataflow/records/options
+++ b/java/ql/test/library-tests/dataflow/records/options
@@ -1 +1 @@
-//semmle-extractor-options: --javac-args --enable-preview -source 15 -target 15
+//semmle-extractor-options: --javac-args -source 16 -target 16
diff --git a/java/ql/test/library-tests/dataflow/taint-format/options b/java/ql/test/library-tests/dataflow/taint-format/options
deleted file mode 100644
index 81817b377852..000000000000
--- a/java/ql/test/library-tests/dataflow/taint-format/options
+++ /dev/null
@@ -1 +0,0 @@
-//semmle-extractor-options: --javac-args --enable-preview -source 15 -target 15
diff --git a/java/ql/test/library-tests/printAst/options b/java/ql/test/library-tests/printAst/options
index 81817b377852..e41003a6e3cf 100644
--- a/java/ql/test/library-tests/printAst/options
+++ b/java/ql/test/library-tests/printAst/options
@@ -1 +1 @@
-//semmle-extractor-options: --javac-args --enable-preview -source 15 -target 15
+//semmle-extractor-options: --javac-args --enable-preview -source 16 -target 16
diff --git a/java/ql/test/library-tests/ssa/options b/java/ql/test/library-tests/ssa/options
index 81817b377852..e41003a6e3cf 100644
--- a/java/ql/test/library-tests/ssa/options
+++ b/java/ql/test/library-tests/ssa/options
@@ -1 +1 @@
-//semmle-extractor-options: --javac-args --enable-preview -source 15 -target 15
+//semmle-extractor-options: --javac-args --enable-preview -source 16 -target 16
diff --git a/java/ql/test/query-tests/StringFormat/options b/java/ql/test/query-tests/StringFormat/options
deleted file mode 100644
index 81817b377852..000000000000
--- a/java/ql/test/query-tests/StringFormat/options
+++ /dev/null
@@ -1 +0,0 @@
-//semmle-extractor-options: --javac-args --enable-preview -source 15 -target 15
From 73e940de74bb326664db451b97fa58e67e683400 Mon Sep 17 00:00:00 2001
From: Artem Smotrakov
Date: Tue, 2 Feb 2021 17:37:14 +0100
Subject: [PATCH 055/433] Added query for Jakarta EL injections
- Added JakartaExpressionInjection.ql
- Added a qhelp file with examples
---
.../Security/CWE/CWE-094/InjectionLib.qll | 14 +++
.../CWE-094/JakartaExpressionInjection.qhelp | 65 ++++++++++++
.../CWE/CWE-094/JakartaExpressionInjection.ql | 20 ++++
.../CWE-094/JakartaExpressionInjectionLib.qll | 98 +++++++++++++++++++
.../Security/CWE/CWE-094/JexlInjectionLib.qll | 13 +--
.../SaferExpressionEvaluationWithJuel.java | 10 ++
.../UnsafeExpressionEvaluationWithJuel.java | 5 +
7 files changed, 213 insertions(+), 12 deletions(-)
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-094/InjectionLib.qll
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.qhelp
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.ql
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-094/SaferExpressionEvaluationWithJuel.java
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-094/UnsafeExpressionEvaluationWithJuel.java
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/InjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/InjectionLib.qll
new file mode 100644
index 000000000000..adab79d6f5c3
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/InjectionLib.qll
@@ -0,0 +1,14 @@
+import java
+import semmle.code.java.dataflow.FlowSources
+
+/**
+ * Holds if `fromNode` to `toNode` is a dataflow step that returns data from
+ * a bean by calling one of its getters.
+ */
+predicate returnsDataFromBean(DataFlow::Node fromNode, DataFlow::Node toNode) {
+ exists(MethodAccess ma, Method m | ma.getMethod() = m |
+ m instanceof GetterMethod and
+ ma.getQualifier() = fromNode.asExpr() and
+ ma = toNode.asExpr()
+ )
+}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.qhelp b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.qhelp
new file mode 100644
index 000000000000..6e6b5f633947
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.qhelp
@@ -0,0 +1,65 @@
+
+
+
+
+
+Jakarta Expression Language (EL) is an expression language for Java applications.
+There are a single language specification and multiple implementations
+such as Glassfish, Juel, Apache Commons EL, etc.
+The language allows invocation of methods available in the JVM.
+If an expression is built using attacker-controlled data,
+and then evaluated, then it may allow the attacker to run arbitrary code.
+
+
+
+
+
+It is generally recommended to avoid using untrusted data in an EL expression.
+Before using untrusted data to build an EL expressoin, the data should be validated
+to ensure it is not evaluated as expression language. If the EL implementaion offers
+configuring a sandbox for EL expression, they should be run in a restircitive sandbox
+that allows accessing only explicitly allowed classes. If the EL implementation
+does not allow sandboxing, consider using other expressiong language implementations
+with sandboxing capabilities such as Apache Commons JEXL or the Spring Expression Language.
+
+
+
+
+
+The following example shows how untrusted data is used to build and run an expression
+using the JUEL interpreter:
+
+
+
+
+JUEL does not allow to run expression in a sandbox. To prevent running arbitrary code,
+incoming data has to be checked before including to an expression. The next example
+uses a Regex pattern to check whether a user tries to run an allowed exression or not:
+
+
+
+
+
+
+
+ Eclipse Foundation:
+ Jakarta Expression Language.
+
+
+ Jakarta EE documentation:
+ Jakarta Expression Language API
+
+
+ OWASP:
+ Expression Language Injection.
+
+
+ JUEL:
+ Home page
+
+
+ Apache Foundation:
+ Apache Commons EL
+
+
+
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.ql b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.ql
new file mode 100644
index 000000000000..b94c98201dea
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.ql
@@ -0,0 +1,20 @@
+/**
+ * @name Java EE Expression Language injection
+ * @description Evaluation of a user-controlled Jave EE expression
+ * may lead to arbitrary code execution.
+ * @kind path-problem
+ * @problem.severity error
+ * @precision high
+ * @id java/javaee-expression-injection
+ * @tags security
+ * external/cwe/cwe-094
+ */
+
+import java
+import JavaEEExpressionInjectionLib
+import DataFlow::PathGraph
+
+from DataFlow::PathNode source, DataFlow::PathNode sink, JavaEEExpressionInjectionConfig conf
+where conf.hasFlowPath(source, sink)
+select sink.getNode(), source, sink, "Java EE Expression Language injection from $@.",
+ source.getNode(), "this user input"
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll
new file mode 100644
index 000000000000..d43b31c98881
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll
@@ -0,0 +1,98 @@
+import java
+import InjectionLib
+import semmle.code.java.dataflow.FlowSources
+import semmle.code.java.dataflow.TaintTracking
+
+/**
+ * A taint-tracking configuration for unsafe user input
+ * that is used to construct and evaluate a Java EE expression.
+ */
+class JavaEEExpressionInjectionConfig extends TaintTracking::Configuration {
+ JavaEEExpressionInjectionConfig() { this = "JavaEEExpressionInjectionConfig" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof ExpressionEvaluationSink }
+
+ override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
+ any(TaintPropagatingCall c).taintFlow(fromNode, toNode) or
+ returnsDataFromBean(fromNode, toNode)
+ }
+}
+
+/**
+ * A sink for Expresssion Language injection vulnerabilities,
+ * i.e. method calls that run evaluation of a Java EE expression.
+ */
+private class ExpressionEvaluationSink extends DataFlow::ExprNode {
+ ExpressionEvaluationSink() {
+ exists(MethodAccess ma, Method m, Expr taintFrom |
+ ma.getMethod() = m and taintFrom = this.asExpr()
+ |
+ m.getDeclaringType() instanceof ValueExpression and
+ m.hasName(["getValue", "setValue"]) and
+ ma.getQualifier() = taintFrom
+ or
+ m.getDeclaringType() instanceof MethodExpression and
+ m.hasName("invoke") and
+ ma.getQualifier() = taintFrom
+ or
+ m.getDeclaringType() instanceof LambdaExpression and
+ m.hasName("invoke") and
+ ma.getQualifier() = taintFrom
+ or
+ m.getDeclaringType() instanceof ELProcessor and
+ m.hasName(["eval", "getValue", "setValue"]) and
+ ma.getArgument(0) = taintFrom
+ )
+ }
+}
+
+/**
+ * Defines method calls that propagate tainted expressions.
+ */
+private class TaintPropagatingCall extends Call {
+ Expr taintFromExpr;
+
+ TaintPropagatingCall() {
+ taintFromExpr = this.getArgument(1) and
+ exists(Method m | this.(MethodAccess).getMethod() = m |
+ m.getDeclaringType() instanceof ExpressionFactory and
+ m.hasName(["createValueExpression", "createMethodExpression"]) and
+ taintFromExpr.getType() instanceof TypeString
+ )
+ or
+ exists(Constructor c | this.(ConstructorCall).getConstructor() = c |
+ c.getDeclaringType() instanceof LambdaExpression and
+ taintFromExpr.getType() instanceof ValueExpression
+ )
+ }
+
+ /**
+ * Holds if `fromNode` to `toNode` is a dataflow step that propagates
+ * tainted data.
+ */
+ predicate taintFlow(DataFlow::Node fromNode, DataFlow::Node toNode) {
+ fromNode.asExpr() = taintFromExpr and toNode.asExpr() = this
+ }
+}
+
+private class ELProcessor extends RefType {
+ ELProcessor() { hasQualifiedName("javax.el", "ELProcessor") }
+}
+
+private class ExpressionFactory extends RefType {
+ ExpressionFactory() { hasQualifiedName("javax.el", "ExpressionFactory") }
+}
+
+private class ValueExpression extends RefType {
+ ValueExpression() { hasQualifiedName("javax.el", "ValueExpression") }
+}
+
+private class MethodExpression extends RefType {
+ MethodExpression() { hasQualifiedName("javax.el", "MethodExpression") }
+}
+
+private class LambdaExpression extends RefType {
+ LambdaExpression() { hasQualifiedName("javax.el", "LambdaExpression") }
+}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll
index 561d7e46ae90..51084a9862ce 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll
@@ -1,4 +1,5 @@
import java
+import InjectionLib
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking
@@ -152,18 +153,6 @@ private predicate createsJexlEngine(DataFlow::Node fromNode, DataFlow::Node toNo
)
}
-/**
- * Holds if `fromNode` to `toNode` is a dataflow step that returns data from
- * a bean by calling one of its getters.
- */
-private predicate returnsDataFromBean(DataFlow::Node fromNode, DataFlow::Node toNode) {
- exists(MethodAccess ma, Method m | ma.getMethod() = m |
- m instanceof GetterMethod and
- ma.getQualifier() = fromNode.asExpr() and
- ma = toNode.asExpr()
- )
-}
-
/**
* A methods in the `JexlEngine` class that gets or sets a property with a JEXL expression.
*/
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/SaferExpressionEvaluationWithJuel.java b/java/ql/src/experimental/Security/CWE/CWE-094/SaferExpressionEvaluationWithJuel.java
new file mode 100644
index 000000000000..54fb9a0ed36c
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/SaferExpressionEvaluationWithJuel.java
@@ -0,0 +1,10 @@
+String input = getRemoteUserInput();
+String pattern = "(inside|outside)\\.(temperature|humidity)";
+if (!input.matches(pattern)) {
+ throw new IllegalArgumentException("Unexpected exression");
+}
+String expression = "${" + input + "}";
+ExpressionFactory factory = new de.odysseus.el.ExpressionFactoryImpl();
+ValueExpression e = factory.createValueExpression(context, expression, Object.class);
+SimpleContext context = getContext();
+Object result = e.getValue(context);
\ No newline at end of file
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/UnsafeExpressionEvaluationWithJuel.java b/java/ql/src/experimental/Security/CWE/CWE-094/UnsafeExpressionEvaluationWithJuel.java
new file mode 100644
index 000000000000..27afa0fcb497
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/UnsafeExpressionEvaluationWithJuel.java
@@ -0,0 +1,5 @@
+String expression = "${" + getRemoteUserInput() + "}";
+ExpressionFactory factory = new de.odysseus.el.ExpressionFactoryImpl();
+ValueExpression e = factory.createValueExpression(context, expression, Object.class);
+SimpleContext context = getContext();
+Object result = e.getValue(context);
\ No newline at end of file
From adb1ed380ac4736860eac8ea6cfadb8f38497db5 Mon Sep 17 00:00:00 2001
From: Artem Smotrakov
Date: Sun, 21 Mar 2021 20:36:47 +0300
Subject: [PATCH 056/433] Added tests for Jakarta expression injection
---
.../CWE/CWE-094/JakartaExpressionInjection.ql | 10 +--
.../CWE-094/JakartaExpressionInjectionLib.qll | 8 +-
.../JakartaExpressionInjection.expected | 59 +++++++++++++
.../CWE-094/JakartaExpressionInjection.java | 87 +++++++++++++++++++
.../CWE-094/JakartaExpressionInjection.qlref | 1 +
.../query-tests/security/CWE-094/options | 3 +-
.../stubs/java-ee-el/javax/el/ELContext.java | 3 +
.../stubs/java-ee-el/javax/el/ELManager.java | 5 ++
.../java-ee-el/javax/el/ELProcessor.java | 7 ++
.../javax/el/ExpressionFactory.java | 17 ++++
.../java-ee-el/javax/el/LambdaExpression.java | 8 ++
.../java-ee-el/javax/el/MethodExpression.java | 5 ++
.../javax/el/StandardELContext.java | 5 ++
.../java-ee-el/javax/el/ValueExpression.java | 6 ++
.../de/odysseus/el/ExpressionFactoryImpl.java | 5 ++
.../de/odysseus/el/util/SimpleContext.java | 5 ++
16 files changed, 223 insertions(+), 11 deletions(-)
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.expected
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.java
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.qlref
create mode 100644 java/ql/test/stubs/java-ee-el/javax/el/ELContext.java
create mode 100644 java/ql/test/stubs/java-ee-el/javax/el/ELManager.java
create mode 100644 java/ql/test/stubs/java-ee-el/javax/el/ELProcessor.java
create mode 100644 java/ql/test/stubs/java-ee-el/javax/el/ExpressionFactory.java
create mode 100644 java/ql/test/stubs/java-ee-el/javax/el/LambdaExpression.java
create mode 100644 java/ql/test/stubs/java-ee-el/javax/el/MethodExpression.java
create mode 100644 java/ql/test/stubs/java-ee-el/javax/el/StandardELContext.java
create mode 100644 java/ql/test/stubs/java-ee-el/javax/el/ValueExpression.java
create mode 100644 java/ql/test/stubs/juel-2.2/de/odysseus/el/ExpressionFactoryImpl.java
create mode 100644 java/ql/test/stubs/juel-2.2/de/odysseus/el/util/SimpleContext.java
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.ql b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.ql
index b94c98201dea..8190ec3d61f1 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.ql
@@ -1,6 +1,6 @@
/**
- * @name Java EE Expression Language injection
- * @description Evaluation of a user-controlled Jave EE expression
+ * @name Jakarta Expression Language injection
+ * @description Evaluation of a user-controlled expression
* may lead to arbitrary code execution.
* @kind path-problem
* @problem.severity error
@@ -11,10 +11,10 @@
*/
import java
-import JavaEEExpressionInjectionLib
+import JakartaExpressionInjectionLib
import DataFlow::PathGraph
-from DataFlow::PathNode source, DataFlow::PathNode sink, JavaEEExpressionInjectionConfig conf
+from DataFlow::PathNode source, DataFlow::PathNode sink, JakartaExpressionInjectionConfig conf
where conf.hasFlowPath(source, sink)
-select sink.getNode(), source, sink, "Java EE Expression Language injection from $@.",
+select sink.getNode(), source, sink, "Jakarta Expression Language injection from $@.",
source.getNode(), "this user input"
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll
index d43b31c98881..bc22f7c32572 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll
@@ -5,10 +5,10 @@ import semmle.code.java.dataflow.TaintTracking
/**
* A taint-tracking configuration for unsafe user input
- * that is used to construct and evaluate a Java EE expression.
+ * that is used to construct and evaluate an expression.
*/
-class JavaEEExpressionInjectionConfig extends TaintTracking::Configuration {
- JavaEEExpressionInjectionConfig() { this = "JavaEEExpressionInjectionConfig" }
+class JakartaExpressionInjectionConfig extends TaintTracking::Configuration {
+ JakartaExpressionInjectionConfig() { this = "JakartaExpressionInjectionConfig" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
@@ -22,7 +22,7 @@ class JavaEEExpressionInjectionConfig extends TaintTracking::Configuration {
/**
* A sink for Expresssion Language injection vulnerabilities,
- * i.e. method calls that run evaluation of a Java EE expression.
+ * i.e. method calls that run evaluation of an expression.
*/
private class ExpressionEvaluationSink extends DataFlow::ExprNode {
ExpressionEvaluationSink() {
diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.expected
new file mode 100644
index 000000000000..111cc81541c2
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.expected
@@ -0,0 +1,59 @@
+edges
+| JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:24:31:24:40 | expression : String |
+| JakartaExpressionInjection.java:24:31:24:40 | expression : String | JakartaExpressionInjection.java:30:24:30:33 | expression : String |
+| JakartaExpressionInjection.java:24:31:24:40 | expression : String | JakartaExpressionInjection.java:37:24:37:33 | expression : String |
+| JakartaExpressionInjection.java:24:31:24:40 | expression : String | JakartaExpressionInjection.java:44:24:44:33 | expression : String |
+| JakartaExpressionInjection.java:24:31:24:40 | expression : String | JakartaExpressionInjection.java:54:24:54:33 | expression : String |
+| JakartaExpressionInjection.java:24:31:24:40 | expression : String | JakartaExpressionInjection.java:61:24:61:33 | expression : String |
+| JakartaExpressionInjection.java:24:31:24:40 | expression : String | JakartaExpressionInjection.java:70:24:70:33 | expression : String |
+| JakartaExpressionInjection.java:24:31:24:40 | expression : String | JakartaExpressionInjection.java:79:24:79:33 | expression : String |
+| JakartaExpressionInjection.java:30:24:30:33 | expression : String | JakartaExpressionInjection.java:32:28:32:37 | expression |
+| JakartaExpressionInjection.java:37:24:37:33 | expression : String | JakartaExpressionInjection.java:39:32:39:41 | expression |
+| JakartaExpressionInjection.java:44:24:44:33 | expression : String | JakartaExpressionInjection.java:49:13:49:28 | lambdaExpression |
+| JakartaExpressionInjection.java:48:49:48:104 | new LambdaExpression(...) : LambdaExpression | JakartaExpressionInjection.java:49:13:49:28 | lambdaExpression |
+| JakartaExpressionInjection.java:54:24:54:33 | expression : String | JakartaExpressionInjection.java:56:32:56:41 | expression |
+| JakartaExpressionInjection.java:61:24:61:33 | expression : String | JakartaExpressionInjection.java:64:33:64:96 | createValueExpression(...) : ValueExpression |
+| JakartaExpressionInjection.java:61:24:61:33 | expression : String | JakartaExpressionInjection.java:65:13:65:13 | e |
+| JakartaExpressionInjection.java:61:24:61:33 | expression : String | JakartaExpressionInjection.java:65:13:65:13 | e : ValueExpression |
+| JakartaExpressionInjection.java:64:33:64:96 | createValueExpression(...) : ValueExpression | JakartaExpressionInjection.java:48:49:48:104 | new LambdaExpression(...) : LambdaExpression |
+| JakartaExpressionInjection.java:64:33:64:96 | createValueExpression(...) : ValueExpression | JakartaExpressionInjection.java:65:13:65:13 | e |
+| JakartaExpressionInjection.java:64:33:64:96 | createValueExpression(...) : ValueExpression | JakartaExpressionInjection.java:65:13:65:13 | e : ValueExpression |
+| JakartaExpressionInjection.java:65:13:65:13 | e : ValueExpression | JakartaExpressionInjection.java:48:49:48:104 | new LambdaExpression(...) : LambdaExpression |
+| JakartaExpressionInjection.java:70:24:70:33 | expression : String | JakartaExpressionInjection.java:73:33:73:96 | createValueExpression(...) : ValueExpression |
+| JakartaExpressionInjection.java:70:24:70:33 | expression : String | JakartaExpressionInjection.java:74:13:74:13 | e |
+| JakartaExpressionInjection.java:70:24:70:33 | expression : String | JakartaExpressionInjection.java:74:13:74:13 | e : ValueExpression |
+| JakartaExpressionInjection.java:73:33:73:96 | createValueExpression(...) : ValueExpression | JakartaExpressionInjection.java:48:49:48:104 | new LambdaExpression(...) : LambdaExpression |
+| JakartaExpressionInjection.java:73:33:73:96 | createValueExpression(...) : ValueExpression | JakartaExpressionInjection.java:74:13:74:13 | e |
+| JakartaExpressionInjection.java:73:33:73:96 | createValueExpression(...) : ValueExpression | JakartaExpressionInjection.java:74:13:74:13 | e : ValueExpression |
+| JakartaExpressionInjection.java:74:13:74:13 | e : ValueExpression | JakartaExpressionInjection.java:48:49:48:104 | new LambdaExpression(...) : LambdaExpression |
+| JakartaExpressionInjection.java:79:24:79:33 | expression : String | JakartaExpressionInjection.java:83:13:83:13 | e |
+nodes
+| JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
+| JakartaExpressionInjection.java:24:31:24:40 | expression : String | semmle.label | expression : String |
+| JakartaExpressionInjection.java:30:24:30:33 | expression : String | semmle.label | expression : String |
+| JakartaExpressionInjection.java:32:28:32:37 | expression | semmle.label | expression |
+| JakartaExpressionInjection.java:37:24:37:33 | expression : String | semmle.label | expression : String |
+| JakartaExpressionInjection.java:39:32:39:41 | expression | semmle.label | expression |
+| JakartaExpressionInjection.java:44:24:44:33 | expression : String | semmle.label | expression : String |
+| JakartaExpressionInjection.java:48:49:48:104 | new LambdaExpression(...) : LambdaExpression | semmle.label | new LambdaExpression(...) : LambdaExpression |
+| JakartaExpressionInjection.java:49:13:49:28 | lambdaExpression | semmle.label | lambdaExpression |
+| JakartaExpressionInjection.java:54:24:54:33 | expression : String | semmle.label | expression : String |
+| JakartaExpressionInjection.java:56:32:56:41 | expression | semmle.label | expression |
+| JakartaExpressionInjection.java:61:24:61:33 | expression : String | semmle.label | expression : String |
+| JakartaExpressionInjection.java:64:33:64:96 | createValueExpression(...) : ValueExpression | semmle.label | createValueExpression(...) : ValueExpression |
+| JakartaExpressionInjection.java:65:13:65:13 | e | semmle.label | e |
+| JakartaExpressionInjection.java:65:13:65:13 | e : ValueExpression | semmle.label | e : ValueExpression |
+| JakartaExpressionInjection.java:70:24:70:33 | expression : String | semmle.label | expression : String |
+| JakartaExpressionInjection.java:73:33:73:96 | createValueExpression(...) : ValueExpression | semmle.label | createValueExpression(...) : ValueExpression |
+| JakartaExpressionInjection.java:74:13:74:13 | e | semmle.label | e |
+| JakartaExpressionInjection.java:74:13:74:13 | e : ValueExpression | semmle.label | e : ValueExpression |
+| JakartaExpressionInjection.java:79:24:79:33 | expression : String | semmle.label | expression : String |
+| JakartaExpressionInjection.java:83:13:83:13 | e | semmle.label | e |
+#select
+| JakartaExpressionInjection.java:32:28:32:37 | expression | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:32:28:32:37 | expression | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) | this user input |
+| JakartaExpressionInjection.java:39:32:39:41 | expression | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:39:32:39:41 | expression | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) | this user input |
+| JakartaExpressionInjection.java:49:13:49:28 | lambdaExpression | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:49:13:49:28 | lambdaExpression | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) | this user input |
+| JakartaExpressionInjection.java:56:32:56:41 | expression | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:56:32:56:41 | expression | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) | this user input |
+| JakartaExpressionInjection.java:65:13:65:13 | e | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:65:13:65:13 | e | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) | this user input |
+| JakartaExpressionInjection.java:74:13:74:13 | e | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:74:13:74:13 | e | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) | this user input |
+| JakartaExpressionInjection.java:83:13:83:13 | e | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:83:13:83:13 | e | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) | this user input |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.java b/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.java
new file mode 100644
index 000000000000..a9fb2d45d6f4
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.java
@@ -0,0 +1,87 @@
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+import javax.el.ELContext;
+import javax.el.ELManager;
+import javax.el.ELProcessor;
+import javax.el.ExpressionFactory;
+import javax.el.LambdaExpression;
+import javax.el.MethodExpression;
+import javax.el.StandardELContext;
+import javax.el.ValueExpression;
+
+public class JakartaExpressionInjection {
+
+ private static void testWithSocket(Consumer action) throws IOException {
+ try (ServerSocket serverSocket = new ServerSocket(0)) {
+ try (Socket socket = serverSocket.accept()) {
+ byte[] bytes = new byte[1024];
+ int n = socket.getInputStream().read(bytes);
+ String expression = new String(bytes, 0, n);
+ action.accept(expression);
+ }
+ }
+ }
+
+ private static void testWithELProcessorEval() throws IOException {
+ testWithSocket(expression -> {
+ ELProcessor processor = new ELProcessor();
+ processor.eval(expression);
+ });
+ }
+
+ private static void testWithELProcessorGetValue() throws IOException {
+ testWithSocket(expression -> {
+ ELProcessor processor = new ELProcessor();
+ processor.getValue(expression, Object.class);
+ });
+ }
+
+ private static void testWithLambdaExpressionInvoke() throws IOException {
+ testWithSocket(expression -> {
+ ExpressionFactory factory = ELManager.getExpressionFactory();
+ StandardELContext context = new StandardELContext(factory);
+ ValueExpression valueExpression = factory.createValueExpression(context, expression, Object.class);
+ LambdaExpression lambdaExpression = new LambdaExpression(new ArrayList<>(), valueExpression);
+ lambdaExpression.invoke(context, new Object[0]);
+ });
+ }
+
+ private static void testWithELProcessorSetValue() throws IOException {
+ testWithSocket(expression -> {
+ ELProcessor processor = new ELProcessor();
+ processor.setValue(expression, new Object());
+ });
+ }
+
+ private static void testWithJuelValueExpressionGetValue() throws IOException {
+ testWithSocket(expression -> {
+ ExpressionFactory factory = new de.odysseus.el.ExpressionFactoryImpl();
+ ELContext context = new de.odysseus.el.util.SimpleContext();
+ ValueExpression e = factory.createValueExpression(context, expression, Object.class);
+ e.getValue(context);
+ });
+ }
+
+ private static void testWithJuelValueExpressionSetValue() throws IOException {
+ testWithSocket(expression -> {
+ ExpressionFactory factory = new de.odysseus.el.ExpressionFactoryImpl();
+ ELContext context = new de.odysseus.el.util.SimpleContext();
+ ValueExpression e = factory.createValueExpression(context, expression, Object.class);
+ e.setValue(context, new Object());
+ });
+ }
+
+ private static void testWithJuelMethodExpressionInvoke() throws IOException {
+ testWithSocket(expression -> {
+ ExpressionFactory factory = new de.odysseus.el.ExpressionFactoryImpl();
+ ELContext context = new de.odysseus.el.util.SimpleContext();
+ MethodExpression e = factory.createMethodExpression(context, expression, Object.class, new Class[0]);
+ e.invoke(context, new Object[0]);
+ });
+ }
+
+}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.qlref b/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.qlref
new file mode 100644
index 000000000000..3154ee5ccad4
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-094/JakartaExpressionInjection.ql
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/options b/java/ql/test/experimental/query-tests/security/CWE-094/options
index a8e30ce30b40..ec3354ea41ce 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-094/options
+++ b/java/ql/test/experimental/query-tests/security/CWE-094/options
@@ -1,2 +1 @@
-//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/springframework-5.2.3:${testdir}/../../../../stubs/mvel2-2.4.7:${testdir}/../../../../stubs/jsr223-api:${testdir}/../../../../stubs/apache-commons-jexl-2.1.1:${testdir}/../../../../stubs/apache-commons-jexl-3.1:${testdir}/../../../../stubs/scriptengine
-
+//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/springframework-5.2.3:${testdir}/../../../../stubs/mvel2-2.4.7:${testdir}/../../../../stubs/jsr223-api:${testdir}/../../../../stubs/apache-commons-jexl-2.1.1:${testdir}/../../../../stubs/apache-commons-jexl-3.1:${testdir}/../../../../stubs/scriptengine:${testdir}/../../../../stubs/java-ee-el:${testdir}/../../../../stubs/juel-2.2
\ No newline at end of file
diff --git a/java/ql/test/stubs/java-ee-el/javax/el/ELContext.java b/java/ql/test/stubs/java-ee-el/javax/el/ELContext.java
new file mode 100644
index 000000000000..ce3840c69c8d
--- /dev/null
+++ b/java/ql/test/stubs/java-ee-el/javax/el/ELContext.java
@@ -0,0 +1,3 @@
+package javax.el;
+
+public class ELContext {}
diff --git a/java/ql/test/stubs/java-ee-el/javax/el/ELManager.java b/java/ql/test/stubs/java-ee-el/javax/el/ELManager.java
new file mode 100644
index 000000000000..7d24f739a3f8
--- /dev/null
+++ b/java/ql/test/stubs/java-ee-el/javax/el/ELManager.java
@@ -0,0 +1,5 @@
+package javax.el;
+
+public class ELManager {
+ public static ExpressionFactory getExpressionFactory() { return null; }
+}
\ No newline at end of file
diff --git a/java/ql/test/stubs/java-ee-el/javax/el/ELProcessor.java b/java/ql/test/stubs/java-ee-el/javax/el/ELProcessor.java
new file mode 100644
index 000000000000..3c523e685c5e
--- /dev/null
+++ b/java/ql/test/stubs/java-ee-el/javax/el/ELProcessor.java
@@ -0,0 +1,7 @@
+package javax.el;
+
+public class ELProcessor {
+ public Object eval(String expression) { return null; }
+ public Object getValue(String expression, Class> expectedType) { return null; }
+ public void setValue(String expression, Object value) {}
+}
diff --git a/java/ql/test/stubs/java-ee-el/javax/el/ExpressionFactory.java b/java/ql/test/stubs/java-ee-el/javax/el/ExpressionFactory.java
new file mode 100644
index 000000000000..31ff79169acb
--- /dev/null
+++ b/java/ql/test/stubs/java-ee-el/javax/el/ExpressionFactory.java
@@ -0,0 +1,17 @@
+package javax.el;
+
+public class ExpressionFactory {
+ public MethodExpression createMethodExpression(ELContext context, String expression, Class> expectedReturnType,
+ Class>[] expectedParamTypes) {
+
+ return null;
+ }
+
+ public ValueExpression createValueExpression(ELContext context, String expression, Class> expectedType) {
+ return null;
+ }
+
+ public ValueExpression createValueExpression(Object instance, Class> expectedType) {
+ return null;
+ }
+}
diff --git a/java/ql/test/stubs/java-ee-el/javax/el/LambdaExpression.java b/java/ql/test/stubs/java-ee-el/javax/el/LambdaExpression.java
new file mode 100644
index 000000000000..4be01e9d2e4b
--- /dev/null
+++ b/java/ql/test/stubs/java-ee-el/javax/el/LambdaExpression.java
@@ -0,0 +1,8 @@
+package javax.el;
+
+import java.util.List;
+
+public class LambdaExpression {
+ public LambdaExpression(List formalParameters, ValueExpression expression) {}
+ public Object invoke(Object... args) { return null; }
+}
diff --git a/java/ql/test/stubs/java-ee-el/javax/el/MethodExpression.java b/java/ql/test/stubs/java-ee-el/javax/el/MethodExpression.java
new file mode 100644
index 000000000000..ac50ece80e36
--- /dev/null
+++ b/java/ql/test/stubs/java-ee-el/javax/el/MethodExpression.java
@@ -0,0 +1,5 @@
+package javax.el;
+
+public class MethodExpression {
+ public Object invoke(ELContext context, Object[] params) { return null; }
+}
diff --git a/java/ql/test/stubs/java-ee-el/javax/el/StandardELContext.java b/java/ql/test/stubs/java-ee-el/javax/el/StandardELContext.java
new file mode 100644
index 000000000000..22e3f2a9fc13
--- /dev/null
+++ b/java/ql/test/stubs/java-ee-el/javax/el/StandardELContext.java
@@ -0,0 +1,5 @@
+package javax.el;
+
+public class StandardELContext extends ELContext {
+ public StandardELContext(ExpressionFactory factory) {}
+}
diff --git a/java/ql/test/stubs/java-ee-el/javax/el/ValueExpression.java b/java/ql/test/stubs/java-ee-el/javax/el/ValueExpression.java
new file mode 100644
index 000000000000..9a231215640a
--- /dev/null
+++ b/java/ql/test/stubs/java-ee-el/javax/el/ValueExpression.java
@@ -0,0 +1,6 @@
+package javax.el;
+
+public class ValueExpression {
+ public Object getValue(ELContext context) { return null; }
+ public void setValue(ELContext context, Object value) {}
+}
diff --git a/java/ql/test/stubs/juel-2.2/de/odysseus/el/ExpressionFactoryImpl.java b/java/ql/test/stubs/juel-2.2/de/odysseus/el/ExpressionFactoryImpl.java
new file mode 100644
index 000000000000..a555cf88dee0
--- /dev/null
+++ b/java/ql/test/stubs/juel-2.2/de/odysseus/el/ExpressionFactoryImpl.java
@@ -0,0 +1,5 @@
+package de.odysseus.el;
+
+import javax.el.ExpressionFactory;
+
+public class ExpressionFactoryImpl extends ExpressionFactory {}
diff --git a/java/ql/test/stubs/juel-2.2/de/odysseus/el/util/SimpleContext.java b/java/ql/test/stubs/juel-2.2/de/odysseus/el/util/SimpleContext.java
new file mode 100644
index 000000000000..aa4f449e5fa5
--- /dev/null
+++ b/java/ql/test/stubs/juel-2.2/de/odysseus/el/util/SimpleContext.java
@@ -0,0 +1,5 @@
+package de.odysseus.el.util;
+
+import javax.el.ELContext;
+
+public class SimpleContext extends ELContext {}
From 6c246994035a35925d870f97abc58c04162d53f9 Mon Sep 17 00:00:00 2001
From: Artem Smotrakov
Date: Sun, 21 Mar 2021 21:16:00 +0300
Subject: [PATCH 057/433] Cover both javax.el and jakarta.el packages
---
.../CWE-094/JakartaExpressionInjectionLib.qll | 22 +++++++++++--------
1 file changed, 13 insertions(+), 9 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll
index bc22f7c32572..22f421b8bf8c 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll
@@ -77,22 +77,26 @@ private class TaintPropagatingCall extends Call {
}
}
-private class ELProcessor extends RefType {
- ELProcessor() { hasQualifiedName("javax.el", "ELProcessor") }
+private class JakartaType extends RefType {
+ JakartaType() { getPackage().hasName(["javax.el", "jakarta.el"]) }
}
-private class ExpressionFactory extends RefType {
- ExpressionFactory() { hasQualifiedName("javax.el", "ExpressionFactory") }
+private class ELProcessor extends JakartaType {
+ ELProcessor() { hasName("ELProcessor") }
}
-private class ValueExpression extends RefType {
- ValueExpression() { hasQualifiedName("javax.el", "ValueExpression") }
+private class ExpressionFactory extends JakartaType {
+ ExpressionFactory() { hasName("ExpressionFactory") }
}
-private class MethodExpression extends RefType {
- MethodExpression() { hasQualifiedName("javax.el", "MethodExpression") }
+private class ValueExpression extends JakartaType {
+ ValueExpression() { hasName("ValueExpression") }
+}
+
+private class MethodExpression extends JakartaType {
+ MethodExpression() { hasName("MethodExpression") }
}
private class LambdaExpression extends RefType {
- LambdaExpression() { hasQualifiedName("javax.el", "LambdaExpression") }
+ LambdaExpression() { hasName("LambdaExpression") }
}
From 701b935564521d64cc35dc51753493f4dc2782f6 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Mon, 22 Mar 2021 00:57:43 +0100
Subject: [PATCH 058/433] Python: Add example of QuerySet chain (django)
---
python/ql/test/library-tests/frameworks/django/SqlExecution.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/python/ql/test/library-tests/frameworks/django/SqlExecution.py b/python/ql/test/library-tests/frameworks/django/SqlExecution.py
index 67a6ee81a5d1..fba2ccdc73e4 100644
--- a/python/ql/test/library-tests/frameworks/django/SqlExecution.py
+++ b/python/ql/test/library-tests/frameworks/django/SqlExecution.py
@@ -27,3 +27,6 @@ def test_model():
raw = RawSQL("so raw")
User.objects.annotate(val=raw) # $getSql="so raw"
+
+ # chaining QuerySet calls
+ User.objects.using("db-name").exclude(username="admin").extra("some sql") # $ MISSING: getSql="some sql"
From c8a6e837b57513df5c8ccd081e193be3443987d1 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Mon, 22 Mar 2021 14:36:29 +0100
Subject: [PATCH 059/433] Python: Model QuerySet chains in django
---
.../2021-03-22-django-queryset-chains.md | 2 +
.../src/semmle/python/frameworks/Django.qll | 120 +++++++-----------
.../frameworks/django/SqlExecution.py | 2 +-
3 files changed, 50 insertions(+), 74 deletions(-)
create mode 100644 python/change-notes/2021-03-22-django-queryset-chains.md
diff --git a/python/change-notes/2021-03-22-django-queryset-chains.md b/python/change-notes/2021-03-22-django-queryset-chains.md
new file mode 100644
index 000000000000..116a75733609
--- /dev/null
+++ b/python/change-notes/2021-03-22-django-queryset-chains.md
@@ -0,0 +1,2 @@
+lgtm,codescanning
+* Improved modeling of `django` to recognize QuerySet chains such as `User.objects.using("db-name").exclude(username="admin").extra("some sql")`. This can lead to new results for `py/sql-injection`.
diff --git a/python/ql/src/semmle/python/frameworks/Django.qll b/python/ql/src/semmle/python/frameworks/Django.qll
index 9f56bd5a2992..384fdf69a15e 100644
--- a/python/ql/src/semmle/python/frameworks/Django.qll
+++ b/python/ql/src/semmle/python/frameworks/Django.qll
@@ -8,6 +8,7 @@ private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.Concepts
+private import semmle.python.ApiGraphs
private import semmle.python.frameworks.PEP249
private import semmle.python.regex
@@ -104,9 +105,6 @@ private module Django {
// -------------------------------------------------------------------------
// django.db.models
// -------------------------------------------------------------------------
- // NOTE: The modelling of django models is currently fairly incomplete.
- // It does not fully take `Model`s, `Manager`s, `and QuerySet`s into account.
- // It simply identifies some common dangerous cases.
/** Gets a reference to the `django.db.models` module. */
private DataFlow::Node models(DataFlow::TypeTracker t) {
t.start() and
@@ -123,71 +121,51 @@ private module Django {
/** Provides models for the `django.db.models` module. */
module models {
- /** Provides models for the `django.db.models.Model` class. */
+ /**
+ * Provides models for the `django.db.models.Model` class and subclasses.
+ *
+ * See https://docs.djangoproject.com/en/3.1/topics/db/models/.
+ */
module Model {
- /** Gets a reference to the `django.db.models.Model` class. */
- private DataFlow::Node classRef(DataFlow::TypeTracker t) {
- t.start() and
- result = DataFlow::importNode("django.db.models.Model")
- or
- t.startInAttr("Model") and
- result = models()
- or
- // subclass
- result.asExpr().(ClassExpr).getABase() = classRef(t.continue()).asExpr()
- or
- exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
+ /** Gets a reference to the `flask.views.View` class or any subclass. */
+ API::Node subclassRef() {
+ result =
+ API::moduleImport("django")
+ .getMember("db")
+ .getMember("models")
+ .getMember("Model")
+ .getASubclass*()
}
-
- /** Gets a reference to the `django.db.models.Model` class. */
- DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
- }
-
- /** Gets a reference to the `objects` object of a django model. */
- private DataFlow::Node objects(DataFlow::TypeTracker t) {
- t.startInAttr("objects") and
- result = Model::classRef()
- or
- exists(DataFlow::TypeTracker t2 | result = objects(t2).track(t2, t))
}
- /** Gets a reference to the `objects` object of a model. */
- DataFlow::Node objects() { result = objects(DataFlow::TypeTracker::end()) }
-
/**
- * Gets a reference to the attribute `attr_name` of an `objects` object.
- * WARNING: Only holds for a few predefined attributes.
+ * Gets a reference to the Manager (django.db.models.Manager) for a django Model,
+ * accessed by `.objects`.
*/
- private DataFlow::Node objects_attr(DataFlow::TypeTracker t, string attr_name) {
- attr_name in ["annotate", "extra", "raw"] and
- t.startInAttr(attr_name) and
- result = objects()
- or
- // Due to bad performance when using normal setup with `objects_attr(t2, attr_name).track(t2, t)`
- // we have inlined that code and forced a join
- exists(DataFlow::TypeTracker t2 |
- exists(DataFlow::StepSummary summary |
- objects_attr_first_join(t2, attr_name, result, summary) and
- t = t2.append(summary)
- )
- )
- }
+ API::Node manager() { result = Model::subclassRef().getMember("objects") }
- pragma[nomagic]
- private predicate objects_attr_first_join(
- DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
- DataFlow::StepSummary summary
- ) {
- DataFlow::StepSummary::step(objects_attr(t2, attr_name), res, summary)
+ /**
+ * Gets a method with `name` that returns a QuerySet.
+ * This method can originate on a QuerySet or a Manager.
+ *
+ * See https://docs.djangoproject.com/en/3.1/ref/models/querysets/
+ */
+ API::Node querySetReturningMethod(string name) {
+ name in [
+ "none", "all", "filter", "exclude", "complex_filter", "union", "intersection",
+ "difference", "select_for_update", "select_related", "prefetch_related", "order_by",
+ "distinct", "reverse", "defer", "only", "using", "annotate", "extra", "raw",
+ "datetimes", "dates", "values", "values_list"
+ ] and
+ result = [manager(), querySet()].getMember(name)
}
/**
- * Gets a reference to the attribute `attr_name` of an `objects` object.
- * WARNING: Only holds for a few predefined attributes.
+ * Gets a reference to a QuerySet (django.db.models.query.QuerySet).
+ *
+ * See https://docs.djangoproject.com/en/3.1/ref/models/querysets/
*/
- DataFlow::Node objects_attr(string attr_name) {
- result = objects_attr(DataFlow::TypeTracker::end(), attr_name)
- }
+ API::Node querySet() { result = querySetReturningMethod(_).getReturn() }
/** Gets a reference to the `django.db.models.expressions` module. */
private DataFlow::Node expressions(DataFlow::TypeTracker t) {
@@ -253,14 +231,13 @@ private module Django {
*
* See https://docs.djangoproject.com/en/3.1/ref/models/querysets/#annotate
*/
- private class ObjectsAnnotate extends SqlExecution::Range, DataFlow::CfgNode {
- override CallNode node;
+ private class ObjectsAnnotate extends SqlExecution::Range, DataFlow::CallCfgNode {
ControlFlowNode sql;
ObjectsAnnotate() {
- node.getFunction() = django::db::models::objects_attr("annotate").asCfgNode() and
- django::db::models::expressions::RawSQL::instance(sql).asCfgNode() in [
- node.getArg(_), node.getArgByName(_)
+ this = django::db::models::querySetReturningMethod("annotate").getACall() and
+ django::db::models::expressions::RawSQL::instance(sql) in [
+ this.getArg(_), this.getArgByName(_)
]
}
@@ -274,12 +251,10 @@ private module Django {
* - https://docs.djangoproject.com/en/3.1/topics/db/sql/#django.db.models.Manager.raw
* - https://docs.djangoproject.com/en/3.1/ref/models/querysets/#raw
*/
- private class ObjectsRaw extends SqlExecution::Range, DataFlow::CfgNode {
- override CallNode node;
-
- ObjectsRaw() { node.getFunction() = django::db::models::objects_attr("raw").asCfgNode() }
+ private class ObjectsRaw extends SqlExecution::Range, DataFlow::CallCfgNode {
+ ObjectsRaw() { this = django::db::models::querySetReturningMethod("raw").getACall() }
- override DataFlow::Node getSql() { result.asCfgNode() = node.getArg(0) }
+ override DataFlow::Node getSql() { result = this.getArg(0) }
}
/**
@@ -287,14 +262,13 @@ private module Django {
*
* See https://docs.djangoproject.com/en/3.1/ref/models/querysets/#extra
*/
- private class ObjectsExtra extends SqlExecution::Range, DataFlow::CfgNode {
- override CallNode node;
-
- ObjectsExtra() { node.getFunction() = django::db::models::objects_attr("extra").asCfgNode() }
+ private class ObjectsExtra extends SqlExecution::Range, DataFlow::CallCfgNode {
+ ObjectsExtra() { this = django::db::models::querySetReturningMethod("extra").getACall() }
override DataFlow::Node getSql() {
- result.asCfgNode() =
- [node.getArg([0, 1, 3, 4]), node.getArgByName(["select", "where", "tables", "order_by"])]
+ result in [
+ this.getArg([0, 1, 3, 4]), this.getArgByName(["select", "where", "tables", "order_by"])
+ ]
}
}
diff --git a/python/ql/test/library-tests/frameworks/django/SqlExecution.py b/python/ql/test/library-tests/frameworks/django/SqlExecution.py
index fba2ccdc73e4..449983fc898c 100644
--- a/python/ql/test/library-tests/frameworks/django/SqlExecution.py
+++ b/python/ql/test/library-tests/frameworks/django/SqlExecution.py
@@ -29,4 +29,4 @@ def test_model():
User.objects.annotate(val=raw) # $getSql="so raw"
# chaining QuerySet calls
- User.objects.using("db-name").exclude(username="admin").extra("some sql") # $ MISSING: getSql="some sql"
+ User.objects.using("db-name").exclude(username="admin").extra("some sql") # $ getSql="some sql"
From 08c3bf26d558d1bfaf0504fa1a0ba6837910829a Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Tue, 16 Mar 2021 03:18:26 +0000
Subject: [PATCH 060/433] Update the query to accommodate more cases
---
.../CWE-1004/SensitiveCookieNotHttpOnly.ql | 73 +++++++++++++++++--
.../SensitiveCookieNotHttpOnly.expected | 8 ++
.../CWE-1004/SensitiveCookieNotHttpOnly.java | 57 +++++++++++++++
3 files changed, 130 insertions(+), 8 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
index 693dad68082c..ac33a6cecb2b 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
@@ -33,7 +33,23 @@ predicate isSensitiveCookieNameExpr(Expr expr) {
/** Holds if a string is concatenated with the `HttpOnly` flag. */
predicate hasHttpOnlyExpr(Expr expr) {
- expr.(CompileTimeConstantExpr).getStringValue().toLowerCase().matches("%httponly%") or
+ (
+ expr.(CompileTimeConstantExpr).getStringValue().toLowerCase().matches("%httponly%")
+ or
+ exists(
+ StaticMethodAccess ma // String.format("%s=%s;HttpOnly", "sessionkey", sessionKey)
+ |
+ ma.getType().getName() = "String" and
+ ma.getMethod().getName() = "format" and
+ ma.getArgument(0)
+ .(CompileTimeConstantExpr)
+ .getStringValue()
+ .toLowerCase()
+ .matches("%httponly%") and
+ expr = ma
+ )
+ )
+ or
hasHttpOnlyExpr(expr.(AddExpr).getAnOperand())
}
@@ -56,6 +72,40 @@ class CookieClass extends RefType {
}
}
+/** Holds if the `Expr` expr is evaluated to boolean true. */
+predicate isBooleanTrue(Expr expr) {
+ expr.(CompileTimeConstantExpr).getBooleanValue() = true or
+ expr.(VarAccess).getVariable().getAnAssignedValue().(CompileTimeConstantExpr).getBooleanValue() =
+ true
+}
+
+/** Holds if the method or a wrapper method sets the `HttpOnly` flag. */
+predicate setHttpOnlyInCookie(MethodAccess ma) {
+ ma.getMethod().getName() = "setHttpOnly" and
+ (
+ isBooleanTrue(ma.getArgument(0)) // boolean literal true
+ or
+ exists(
+ MethodAccess mpa, int i // runtime assignment of boolean value true
+ |
+ TaintTracking::localTaint(DataFlow::parameterNode(mpa.getMethod().getParameter(i)),
+ DataFlow::exprNode(ma.getArgument(0))) and
+ isBooleanTrue(mpa.getArgument(i))
+ )
+ )
+ or
+ exists(MethodAccess mca |
+ ma.getMethod().calls(mca.getMethod()) and
+ setHttpOnlyInCookie(mca)
+ )
+}
+
+/** Holds if the method or a wrapper method removes a cookie. */
+predicate removeCookie(MethodAccess ma) {
+ ma.getMethod().getName() = "setMaxAge" and
+ ma.getArgument(0).(IntegerLiteral).getIntValue() = 0
+}
+
/** Sensitive cookie name used in a `Cookie` constructor or a `Set-Cookie` call. */
class SensitiveCookieNameExpr extends Expr {
SensitiveCookieNameExpr() { isSensitiveCookieNameExpr(this) }
@@ -69,11 +119,16 @@ class CookieResponseSink extends DataFlow::ExprNode {
ma.getMethod() instanceof ResponseAddCookieMethod and
this.getExpr() = ma.getArgument(0) and
not exists(
- MethodAccess ma2 // cookie.setHttpOnly(true)
+ MethodAccess ma2 // a method or wrapper method that invokes cookie.setHttpOnly(true)
|
- ma2.getMethod().getName() = "setHttpOnly" and
- ma2.getArgument(0).(BooleanLiteral).getBooleanValue() = true and
- DataFlow::localExprFlow(ma2.getQualifier(), this.getExpr())
+ (
+ setHttpOnlyInCookie(ma2) or
+ removeCookie(ma2)
+ ) and
+ (
+ DataFlow::localExprFlow(ma2.getQualifier(), this.getExpr()) or
+ DataFlow::localExprFlow(ma2, this.getExpr())
+ )
)
or
ma instanceof SetCookieMethodAccess and
@@ -92,13 +147,15 @@ class CookieResponseSink extends DataFlow::ExprNode {
predicate setHttpOnlyInNewCookie(ClassInstanceExpr cie) {
cie.getConstructedType().hasQualifiedName(["javax.ws.rs.core", "jakarta.ws.rs.core"], "NewCookie") and
(
- cie.getNumArgument() = 6 and cie.getArgument(5).(BooleanLiteral).getBooleanValue() = true // NewCookie(Cookie cookie, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
+ cie.getNumArgument() = 6 and
+ isBooleanTrue(cie.getArgument(5)) // NewCookie(Cookie cookie, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
or
cie.getNumArgument() = 8 and
cie.getArgument(6).getType() instanceof BooleanType and
- cie.getArgument(7).(BooleanLiteral).getBooleanValue() = true // NewCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly)
+ isBooleanTrue(cie.getArgument(7)) // NewCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly)
or
- cie.getNumArgument() = 10 and cie.getArgument(9).(BooleanLiteral).getBooleanValue() = true // NewCookie(String name, String value, String path, String domain, int version, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
+ cie.getNumArgument() = 10 and
+ isBooleanTrue(cie.getArgument(9)) // NewCookie(String name, String value, String path, String domain, int version, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
)
}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected
index 8fa688bef2a6..dae98e92e673 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected
@@ -7,6 +7,9 @@ edges
| SensitiveCookieNotHttpOnly.java:70:28:70:35 | "token=" : String | SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString |
| SensitiveCookieNotHttpOnly.java:70:28:70:43 | ... + ... : String | SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString |
| SensitiveCookieNotHttpOnly.java:70:28:70:55 | ... + ... : String | SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString |
+| SensitiveCookieNotHttpOnly.java:88:35:88:51 | "Presto-UI-Token" : String | SensitiveCookieNotHttpOnly.java:91:16:91:21 | cookie : Cookie |
+| SensitiveCookieNotHttpOnly.java:91:16:91:21 | cookie : Cookie | SensitiveCookieNotHttpOnly.java:102:25:102:64 | createAuthenticationCookie(...) : Cookie |
+| SensitiveCookieNotHttpOnly.java:102:25:102:64 | createAuthenticationCookie(...) : Cookie | SensitiveCookieNotHttpOnly.java:103:28:103:33 | cookie |
nodes
| SensitiveCookieNotHttpOnly.java:24:33:24:43 | "jwt_token" : String | semmle.label | "jwt_token" : String |
| SensitiveCookieNotHttpOnly.java:31:28:31:36 | jwtCookie | semmle.label | jwtCookie |
@@ -21,6 +24,10 @@ nodes
| SensitiveCookieNotHttpOnly.java:70:28:70:43 | ... + ... : String | semmle.label | ... + ... : String |
| SensitiveCookieNotHttpOnly.java:70:28:70:55 | ... + ... : String | semmle.label | ... + ... : String |
| SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | semmle.label | secString |
+| SensitiveCookieNotHttpOnly.java:88:35:88:51 | "Presto-UI-Token" : String | semmle.label | "Presto-UI-Token" : String |
+| SensitiveCookieNotHttpOnly.java:91:16:91:21 | cookie : Cookie | semmle.label | cookie : Cookie |
+| SensitiveCookieNotHttpOnly.java:102:25:102:64 | createAuthenticationCookie(...) : Cookie | semmle.label | createAuthenticationCookie(...) : Cookie |
+| SensitiveCookieNotHttpOnly.java:103:28:103:33 | cookie | semmle.label | cookie |
#select
| SensitiveCookieNotHttpOnly.java:31:28:31:36 | jwtCookie | SensitiveCookieNotHttpOnly.java:24:33:24:43 | "jwt_token" : String | SensitiveCookieNotHttpOnly.java:31:28:31:36 | jwtCookie | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:24:33:24:43 | "jwt_token" | This sensitive cookie |
| SensitiveCookieNotHttpOnly.java:42:42:42:69 | ... + ... | SensitiveCookieNotHttpOnly.java:42:42:42:49 | "token=" : String | SensitiveCookieNotHttpOnly.java:42:42:42:69 | ... + ... | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:42:42:42:49 | "token=" | This sensitive cookie |
@@ -31,3 +38,4 @@ nodes
| SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | SensitiveCookieNotHttpOnly.java:70:28:70:35 | "token=" : String | SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:70:28:70:35 | "token=" | This sensitive cookie |
| SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | SensitiveCookieNotHttpOnly.java:70:28:70:43 | ... + ... : String | SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:70:28:70:43 | ... + ... | This sensitive cookie |
| SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | SensitiveCookieNotHttpOnly.java:70:28:70:55 | ... + ... : String | SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:70:28:70:55 | ... + ... | This sensitive cookie |
+| SensitiveCookieNotHttpOnly.java:103:28:103:33 | cookie | SensitiveCookieNotHttpOnly.java:88:35:88:51 | "Presto-UI-Token" : String | SensitiveCookieNotHttpOnly.java:103:28:103:33 | cookie | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:88:35:88:51 | "Presto-UI-Token" | This sensitive cookie |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.java b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.java
index 337a99cc096d..fdc831d78366 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.java
+++ b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.java
@@ -71,6 +71,63 @@ public void addCookie9(String authId, HttpServletRequest request, HttpServletRes
response.addHeader("Set-Cookie", secString);
}
+ // GOOD - Tests set a sensitive cookie header with the `HttpOnly` flag set using `String.format(...)`.
+ public void addCookie10(HttpServletRequest request, HttpServletResponse response) {
+ response.addHeader("SET-COOKIE", String.format("%s=%s;HttpOnly", "sessionkey", request.getSession().getAttribute("sessionkey")));
+ }
+
+ public Cookie createHttpOnlyAuthenticationCookie(HttpServletRequest request, String jwt) {
+ String PRESTO_UI_COOKIE = "Presto-UI-Token";
+ Cookie cookie = new Cookie(PRESTO_UI_COOKIE, jwt);
+ cookie.setHttpOnly(true);
+ cookie.setPath("/ui");
+ return cookie;
+ }
+
+ public Cookie createAuthenticationCookie(HttpServletRequest request, String jwt) {
+ String PRESTO_UI_COOKIE = "Presto-UI-Token";
+ Cookie cookie = new Cookie(PRESTO_UI_COOKIE, jwt);
+ cookie.setPath("/ui");
+ return cookie;
+ }
+
+ // GOOD - Tests set a sensitive cookie header with the `HttpOnly` flag set using a wrapper method.
+ public void addCookie11(HttpServletRequest request, HttpServletResponse response, String jwt) {
+ Cookie cookie = createHttpOnlyAuthenticationCookie(request, jwt);
+ response.addCookie(cookie);
+ }
+
+ // BAD - Tests set a sensitive cookie header without the `HttpOnly` flag set using a wrapper method.
+ public void addCookie12(HttpServletRequest request, HttpServletResponse response, String jwt) {
+ Cookie cookie = createAuthenticationCookie(request, jwt);
+ response.addCookie(cookie);
+ }
+
+ private Cookie createCookie(String name, String value, Boolean httpOnly){
+ Cookie cookie = null;
+ cookie = new Cookie(name, value);
+ cookie.setDomain("/");
+ cookie.setHttpOnly(httpOnly);
+
+ //for production https
+ cookie.setSecure(true);
+
+ cookie.setMaxAge(60*60*24*30);
+ cookie.setPath("/");
+
+ return cookie;
+ }
+
+ // GOOD - Tests set a sensitive cookie header with the `HttpOnly` flag set through a boolean variable using a wrapper method.
+ public void addCookie13(HttpServletRequest request, HttpServletResponse response, String refreshToken) {
+ response.addCookie(createCookie("refresh_token", refreshToken, true));
+ }
+
+ // BAD - Tests set a sensitive cookie header with the `HttpOnly` flag not set through a boolean variable using a wrapper method.
+ public void addCookie14(HttpServletRequest request, HttpServletResponse response, String refreshToken) {
+ response.addCookie(createCookie("refresh_token", refreshToken, false));
+ }
+
// GOOD - CSRF token doesn't need to have the `HttpOnly` flag set.
public void addCsrfCookie(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// Spring put the CSRF token in session attribute "_csrf"
From fe0e7f5eac232e2aae155fe332c57c9e30e61cdf Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Thu, 25 Mar 2021 01:45:13 +0000
Subject: [PATCH 061/433] Change method check to taint flow
---
.../CWE-1004/SensitiveCookieNotHttpOnly.ql | 70 ++++++++++---------
.../SensitiveCookieNotHttpOnly.expected | 10 +--
.../CWE-1004/SensitiveCookieNotHttpOnly.java | 18 ++++-
3 files changed, 58 insertions(+), 40 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
index ac33a6cecb2b..db9adc2ee09b 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
@@ -11,6 +11,7 @@ import java
import semmle.code.java.dataflow.FlowSteps
import semmle.code.java.frameworks.Servlets
import semmle.code.java.dataflow.TaintTracking
+import semmle.code.java.dataflow.TaintTracking2
import DataFlow::PathGraph
/** Gets a regular expression for matching common names of sensitive cookies. */
@@ -31,28 +32,6 @@ predicate isSensitiveCookieNameExpr(Expr expr) {
isSensitiveCookieNameExpr(expr.(AddExpr).getAnOperand())
}
-/** Holds if a string is concatenated with the `HttpOnly` flag. */
-predicate hasHttpOnlyExpr(Expr expr) {
- (
- expr.(CompileTimeConstantExpr).getStringValue().toLowerCase().matches("%httponly%")
- or
- exists(
- StaticMethodAccess ma // String.format("%s=%s;HttpOnly", "sessionkey", sessionKey)
- |
- ma.getType().getName() = "String" and
- ma.getMethod().getName() = "format" and
- ma.getArgument(0)
- .(CompileTimeConstantExpr)
- .getStringValue()
- .toLowerCase()
- .matches("%httponly%") and
- expr = ma
- )
- )
- or
- hasHttpOnlyExpr(expr.(AddExpr).getAnOperand())
-}
-
/** The method call `Set-Cookie` of `addHeader` or `setHeader`. */
class SetCookieMethodAccess extends MethodAccess {
SetCookieMethodAccess() {
@@ -64,6 +43,22 @@ class SetCookieMethodAccess extends MethodAccess {
}
}
+/**
+ * A taint configuration tracking flow from the text `httponly` to argument 1 of
+ * `SetCookieMethodAccess`.
+ */
+class MatchesHttpOnlyConfiguration extends TaintTracking2::Configuration {
+ MatchesHttpOnlyConfiguration() { this = "MatchesHttpOnlyConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) {
+ source.asExpr().(CompileTimeConstantExpr).getStringValue().toLowerCase().matches("%httponly%")
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ sink.asExpr() = any(SetCookieMethodAccess ma).getArgument(1)
+ }
+}
+
/** The cookie class of Java EE. */
class CookieClass extends RefType {
CookieClass() {
@@ -79,7 +74,7 @@ predicate isBooleanTrue(Expr expr) {
true
}
-/** Holds if the method or a wrapper method sets the `HttpOnly` flag. */
+/** Holds if the method call sets the `HttpOnly` flag. */
predicate setHttpOnlyInCookie(MethodAccess ma) {
ma.getMethod().getName() = "setHttpOnly" and
(
@@ -93,14 +88,24 @@ predicate setHttpOnlyInCookie(MethodAccess ma) {
isBooleanTrue(mpa.getArgument(i))
)
)
- or
- exists(MethodAccess mca |
- ma.getMethod().calls(mca.getMethod()) and
- setHttpOnlyInCookie(mca)
- )
}
-/** Holds if the method or a wrapper method removes a cookie. */
+/**
+ * A taint configuration tracking flow of a method or a wrapper method that sets
+ * the `HttpOnly` flag.
+ */
+class SetHttpOnlyInCookieConfiguration extends TaintTracking2::Configuration {
+ SetHttpOnlyInCookieConfiguration() { this = "SetHttpOnlyInCookieConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) { any() }
+
+ override predicate isSink(DataFlow::Node sink) {
+ sink.asExpr() =
+ any(MethodAccess ma | ma.getMethod() instanceof ResponseAddCookieMethod).getArgument(0)
+ }
+}
+
+/** Holds if the method call removes a cookie. */
predicate removeCookie(MethodAccess ma) {
ma.getMethod().getName() = "setMaxAge" and
ma.getArgument(0).(IntegerLiteral).getIntValue() = 0
@@ -125,15 +130,14 @@ class CookieResponseSink extends DataFlow::ExprNode {
setHttpOnlyInCookie(ma2) or
removeCookie(ma2)
) and
- (
- DataFlow::localExprFlow(ma2.getQualifier(), this.getExpr()) or
- DataFlow::localExprFlow(ma2, this.getExpr())
+ exists(SetHttpOnlyInCookieConfiguration cc |
+ cc.hasFlow(DataFlow::exprNode(ma2.getQualifier()), this)
)
)
or
ma instanceof SetCookieMethodAccess and
this.getExpr() = ma.getArgument(1) and
- not hasHttpOnlyExpr(this.getExpr()) // response.addHeader("Set-Cookie", "token=" +authId + ";HttpOnly;Secure")
+ not exists(MatchesHttpOnlyConfiguration cc | cc.hasFlowToExpr(ma.getArgument(1))) // response.addHeader("Set-Cookie", "token=" +authId + ";HttpOnly;Secure")
) and
not isTestMethod(ma) // Test class or method
)
diff --git a/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected
index dae98e92e673..a9d126ca4a97 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.expected
@@ -8,8 +8,8 @@ edges
| SensitiveCookieNotHttpOnly.java:70:28:70:43 | ... + ... : String | SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString |
| SensitiveCookieNotHttpOnly.java:70:28:70:55 | ... + ... : String | SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString |
| SensitiveCookieNotHttpOnly.java:88:35:88:51 | "Presto-UI-Token" : String | SensitiveCookieNotHttpOnly.java:91:16:91:21 | cookie : Cookie |
-| SensitiveCookieNotHttpOnly.java:91:16:91:21 | cookie : Cookie | SensitiveCookieNotHttpOnly.java:102:25:102:64 | createAuthenticationCookie(...) : Cookie |
-| SensitiveCookieNotHttpOnly.java:102:25:102:64 | createAuthenticationCookie(...) : Cookie | SensitiveCookieNotHttpOnly.java:103:28:103:33 | cookie |
+| SensitiveCookieNotHttpOnly.java:91:16:91:21 | cookie : Cookie | SensitiveCookieNotHttpOnly.java:110:25:110:64 | createAuthenticationCookie(...) : Cookie |
+| SensitiveCookieNotHttpOnly.java:110:25:110:64 | createAuthenticationCookie(...) : Cookie | SensitiveCookieNotHttpOnly.java:111:28:111:33 | cookie |
nodes
| SensitiveCookieNotHttpOnly.java:24:33:24:43 | "jwt_token" : String | semmle.label | "jwt_token" : String |
| SensitiveCookieNotHttpOnly.java:31:28:31:36 | jwtCookie | semmle.label | jwtCookie |
@@ -26,8 +26,8 @@ nodes
| SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | semmle.label | secString |
| SensitiveCookieNotHttpOnly.java:88:35:88:51 | "Presto-UI-Token" : String | semmle.label | "Presto-UI-Token" : String |
| SensitiveCookieNotHttpOnly.java:91:16:91:21 | cookie : Cookie | semmle.label | cookie : Cookie |
-| SensitiveCookieNotHttpOnly.java:102:25:102:64 | createAuthenticationCookie(...) : Cookie | semmle.label | createAuthenticationCookie(...) : Cookie |
-| SensitiveCookieNotHttpOnly.java:103:28:103:33 | cookie | semmle.label | cookie |
+| SensitiveCookieNotHttpOnly.java:110:25:110:64 | createAuthenticationCookie(...) : Cookie | semmle.label | createAuthenticationCookie(...) : Cookie |
+| SensitiveCookieNotHttpOnly.java:111:28:111:33 | cookie | semmle.label | cookie |
#select
| SensitiveCookieNotHttpOnly.java:31:28:31:36 | jwtCookie | SensitiveCookieNotHttpOnly.java:24:33:24:43 | "jwt_token" : String | SensitiveCookieNotHttpOnly.java:31:28:31:36 | jwtCookie | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:24:33:24:43 | "jwt_token" | This sensitive cookie |
| SensitiveCookieNotHttpOnly.java:42:42:42:69 | ... + ... | SensitiveCookieNotHttpOnly.java:42:42:42:49 | "token=" : String | SensitiveCookieNotHttpOnly.java:42:42:42:69 | ... + ... | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:42:42:42:49 | "token=" | This sensitive cookie |
@@ -38,4 +38,4 @@ nodes
| SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | SensitiveCookieNotHttpOnly.java:70:28:70:35 | "token=" : String | SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:70:28:70:35 | "token=" | This sensitive cookie |
| SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | SensitiveCookieNotHttpOnly.java:70:28:70:43 | ... + ... : String | SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:70:28:70:43 | ... + ... | This sensitive cookie |
| SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | SensitiveCookieNotHttpOnly.java:70:28:70:55 | ... + ... : String | SensitiveCookieNotHttpOnly.java:71:42:71:50 | secString | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:70:28:70:55 | ... + ... | This sensitive cookie |
-| SensitiveCookieNotHttpOnly.java:103:28:103:33 | cookie | SensitiveCookieNotHttpOnly.java:88:35:88:51 | "Presto-UI-Token" : String | SensitiveCookieNotHttpOnly.java:103:28:103:33 | cookie | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:88:35:88:51 | "Presto-UI-Token" | This sensitive cookie |
+| SensitiveCookieNotHttpOnly.java:111:28:111:33 | cookie | SensitiveCookieNotHttpOnly.java:88:35:88:51 | "Presto-UI-Token" : String | SensitiveCookieNotHttpOnly.java:111:28:111:33 | cookie | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:88:35:88:51 | "Presto-UI-Token" | This sensitive cookie |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.java b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.java
index fdc831d78366..0bb912f6ce67 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.java
+++ b/java/ql/test/experimental/query-tests/security/CWE-1004/SensitiveCookieNotHttpOnly.java
@@ -91,6 +91,14 @@ public Cookie createAuthenticationCookie(HttpServletRequest request, String jwt)
return cookie;
}
+ public Cookie removeAuthenticationCookie(HttpServletRequest request, String jwt) {
+ String PRESTO_UI_COOKIE = "Presto-UI-Token";
+ Cookie cookie = new Cookie(PRESTO_UI_COOKIE, jwt);
+ cookie.setPath("/ui");
+ cookie.setMaxAge(0);
+ return cookie;
+ }
+
// GOOD - Tests set a sensitive cookie header with the `HttpOnly` flag set using a wrapper method.
public void addCookie11(HttpServletRequest request, HttpServletResponse response, String jwt) {
Cookie cookie = createHttpOnlyAuthenticationCookie(request, jwt);
@@ -103,6 +111,12 @@ public void addCookie12(HttpServletRequest request, HttpServletResponse response
response.addCookie(cookie);
}
+ // GOOD - Tests remove a sensitive cookie header without the `HttpOnly` flag set using a wrapper method.
+ public void addCookie13(HttpServletRequest request, HttpServletResponse response, String jwt) {
+ Cookie cookie = removeAuthenticationCookie(request, jwt);
+ response.addCookie(cookie);
+ }
+
private Cookie createCookie(String name, String value, Boolean httpOnly){
Cookie cookie = null;
cookie = new Cookie(name, value);
@@ -119,12 +133,12 @@ private Cookie createCookie(String name, String value, Boolean httpOnly){
}
// GOOD - Tests set a sensitive cookie header with the `HttpOnly` flag set through a boolean variable using a wrapper method.
- public void addCookie13(HttpServletRequest request, HttpServletResponse response, String refreshToken) {
+ public void addCookie14(HttpServletRequest request, HttpServletResponse response, String refreshToken) {
response.addCookie(createCookie("refresh_token", refreshToken, true));
}
// BAD - Tests set a sensitive cookie header with the `HttpOnly` flag not set through a boolean variable using a wrapper method.
- public void addCookie14(HttpServletRequest request, HttpServletResponse response, String refreshToken) {
+ public void addCookie15(HttpServletRequest request, HttpServletResponse response, String refreshToken) {
response.addCookie(createCookie("refresh_token", refreshToken, false));
}
From 57bd3f3c1459c04aa0098a254522bcc405f86d80 Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Thu, 25 Mar 2021 10:44:26 +0000
Subject: [PATCH 062/433] Optimize the taint flow source
---
.../CWE-1004/SensitiveCookieNotHttpOnly.ql | 19 ++++++-------------
1 file changed, 6 insertions(+), 13 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
index db9adc2ee09b..f1bc7879b8a6 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
@@ -97,7 +97,10 @@ predicate setHttpOnlyInCookie(MethodAccess ma) {
class SetHttpOnlyInCookieConfiguration extends TaintTracking2::Configuration {
SetHttpOnlyInCookieConfiguration() { this = "SetHttpOnlyInCookieConfiguration" }
- override predicate isSource(DataFlow::Node source) { any() }
+ override predicate isSource(DataFlow::Node source) {
+ source.asExpr() =
+ any(MethodAccess ma | setHttpOnlyInCookie(ma) or removeCookie(ma)).getQualifier()
+ }
override predicate isSink(DataFlow::Node sink) {
sink.asExpr() =
@@ -123,21 +126,11 @@ class CookieResponseSink extends DataFlow::ExprNode {
(
ma.getMethod() instanceof ResponseAddCookieMethod and
this.getExpr() = ma.getArgument(0) and
- not exists(
- MethodAccess ma2 // a method or wrapper method that invokes cookie.setHttpOnly(true)
- |
- (
- setHttpOnlyInCookie(ma2) or
- removeCookie(ma2)
- ) and
- exists(SetHttpOnlyInCookieConfiguration cc |
- cc.hasFlow(DataFlow::exprNode(ma2.getQualifier()), this)
- )
- )
+ not exists(SetHttpOnlyInCookieConfiguration cc | cc.hasFlowTo(this))
or
ma instanceof SetCookieMethodAccess and
this.getExpr() = ma.getArgument(1) and
- not exists(MatchesHttpOnlyConfiguration cc | cc.hasFlowToExpr(ma.getArgument(1))) // response.addHeader("Set-Cookie", "token=" +authId + ";HttpOnly;Secure")
+ not exists(MatchesHttpOnlyConfiguration cc | cc.hasFlowTo(this)) // response.addHeader("Set-Cookie", "token=" +authId + ";HttpOnly;Secure")
) and
not isTestMethod(ma) // Test class or method
)
From 2576c86ebf2e8a87a8cbe5165ae2ab95a7bec155 Mon Sep 17 00:00:00 2001
From: alexet
Date: Thu, 25 Mar 2021 16:16:16 +0000
Subject: [PATCH 063/433] Docs: Update the language specification for changes
to super.
---
.../ql-language-reference/ql-language-specification.rst | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/docs/codeql/ql-language-reference/ql-language-specification.rst b/docs/codeql/ql-language-reference/ql-language-specification.rst
index 0fe542105046..ad88b791019e 100644
--- a/docs/codeql/ql-language-reference/ql-language-specification.rst
+++ b/docs/codeql/ql-language-reference/ql-language-specification.rst
@@ -1116,8 +1116,6 @@ A super expression may only occur in a QL program as the receiver expression for
If a super expression includes a ``type``, then that type must be a class that the enclosing class inherits from.
-If the super expression does not include a type, then the enclosing class must have a single declared base type, and that base type must be a class.
-
The value of a super expression is the same as the value of ``this`` in the named tuple.
Casts
@@ -1169,7 +1167,12 @@ A valid call with results *resolves* to a set of predicates. The ways a call can
- If the call has no receiver and the predicate name is a selection identifier, then the qualifier is resolved as a module (see "`Module resolution <#module-resolution>`__"). The identifier is then resolved in the exported predicate environment of the qualifier module.
-- If the call has a super expression as the receiver, then it resolves to a member predicate in a class the enclosing class inherits from. If the super expression is unqualified, then the super-class is the single class that the current class inherits from. If there is not exactly one such class, then the program is invalid. Otherwise the super-class is the class named by the qualifier of the super expression. The predicate is resolved by looking up its name and arity in the exported predicate environment of the super-class.
+- If the call has a super expression as the receiver, then it resolves to a member predicate in a class that the enclosing class inherits from:
+ - If the super expression is unqualified and there is a single class that the current class inherits from then the super-class is that class.
+ - If the super expression is unqualified and there is are multiple classes that the current class inherits from then the super-class is the domain type.
+ - Otherwise the super-class is the class named by the qualifier of the super expression.
+
+ The predicate is resolved by looking up its name and arity in the exported predicate environment of the super-class.
- If the type of the receiver is the same as the enclosing class, the predicate is resolved by looking up its name and arity in the visible predicate environment of the class.
From 2ca95166d916cc89bf2d7086aa664ef3757c95e4 Mon Sep 17 00:00:00 2001
From: Porcuiney Hairs
Date: Wed, 13 Jan 2021 01:32:54 +0530
Subject: [PATCH 064/433] Java : add query to detect insecure loading of Dex
File
---
.../CWE/CWE-094/InsecureDexLoading.qhelp | 44 ++++++++
.../CWE/CWE-094/InsecureDexLoading.ql | 20 ++++
.../CWE/CWE-094/InsecureDexLoading.qll | 100 ++++++++++++++++++
.../CWE/CWE-094/InsecureDexLoadingBad.java | 32 ++++++
.../CWE/CWE-094/InsecureDexLoadingGood.java | 23 ++++
5 files changed, 219 insertions(+)
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.qhelp
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.ql
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.qll
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoadingBad.java
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoadingGood.java
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.qhelp b/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.qhelp
new file mode 100644
index 000000000000..feda3af3fc26
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.qhelp
@@ -0,0 +1,44 @@
+
+
+
+
+Shared world writable storage spaces are not secure to load Dex libraries from. A malicious actor can replace a dex file with a maliciously crafted file
+which when loaded by the app can lead to code execution.
+
+
+
+
+
+ Loading a file from private storage instead of a world writable one can prevent this issue.
+ As the attacker cannot access files stored by the app in its private storage.
+
+
+
+
+
+ The following example loads a Dex file from a shared world writable location. in this case,
+ since the `/sdcard` directory is on external storage, any one can read/write to the location.
+ bypassing all Android security policies. Hence, this is insecure.
+
+
+
+
+ The next example loads a Dex file stored inside the app's private storage.
+ This is not exploitable as nobody else except the app can access the data stored here.
+
+
+
+
+
+
+ Android Documentation:
+ Data and file storage overview
+ .
+
+
+ Android Documentation:
+ DexClassLoader
+ .
+
+
+
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.ql b/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.ql
new file mode 100644
index 000000000000..58d9844d38aa
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.ql
@@ -0,0 +1,20 @@
+/**
+ * @name Insecure loading of an Android Dex File
+ * @description Loading a DEX library located in a world-readable/ writable location such as
+ * a SD card can cause arbitary code execution vulnerabilities.
+ * @kind path-problem
+ * @problem.severity error
+ * @precision high
+ * @id java/android-insecure-dex-loading
+ * @tags security
+ * external/cwe/cwe-094
+ */
+
+import java
+import InsecureDexLoading
+import DataFlow::PathGraph
+
+from DataFlow::PathNode source, DataFlow::PathNode sink, InsecureDexConfiguration conf
+where conf.hasFlowPath(source, sink)
+select sink.getNode(), source, sink, "Potential arbitary code execution due to $@.",
+ source.getNode(), "a value loaded from a world readable/writable source."
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.qll b/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.qll
new file mode 100644
index 000000000000..2a4b387be7e3
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.qll
@@ -0,0 +1,100 @@
+import java
+import semmle.code.java.dataflow.FlowSources
+
+/**
+ * A taint-tracking configuration fordetecting unsafe use of a
+ * `DexClassLoader` by an Android app.
+ */
+class InsecureDexConfiguration extends TaintTracking::Configuration {
+ InsecureDexConfiguration() { this = "Insecure Dex File Load" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof InsecureDexSource }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof InsecureDexSink }
+
+ override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
+ flowStep(pred, succ)
+ }
+}
+
+/** A data flow source for insecure Dex class loading vulnerabilities. */
+abstract class InsecureDexSource extends DataFlow::Node { }
+
+/** A data flow sink for insecure Dex class loading vulnerabilities. */
+abstract class InsecureDexSink extends DataFlow::Node { }
+
+private predicate flowStep(DataFlow::Node pred, DataFlow::Node succ) {
+ // propagate from a `java.io.File` via the `File.getAbsolutePath` call.
+ exists(MethodAccess m |
+ m.getMethod().getDeclaringType() instanceof TypeFile and
+ m.getMethod().hasName("getAbsolutePath") and
+ m.getQualifier() = pred.asExpr() and
+ m = succ.asExpr()
+ )
+ or
+ // propagate from a `java.io.File` via the `File.toString` call.
+ exists(MethodAccess m |
+ m.getMethod().getDeclaringType() instanceof TypeFile and
+ m.getMethod().hasName("toString") and
+ m.getQualifier() = pred.asExpr() and
+ m = succ.asExpr()
+ )
+ or
+ // propagate to newly created `File` if the parent directory of the new `File` is tainted
+ exists(ConstructorCall cc |
+ cc.getConstructedType() instanceof TypeFile and
+ cc.getArgument(0) = pred.asExpr() and
+ cc = succ.asExpr()
+ )
+}
+
+/**
+ * An argument to a `DexClassLoader` call taken as a sink for
+ * insecure Dex class loading vulnerabilities.
+ */
+private class DexClassLoader extends InsecureDexSink {
+ DexClassLoader() {
+ exists(ConstructorCall cc |
+ cc.getConstructedType().hasQualifiedName("dalvik.system", "DexClassLoader")
+ |
+ this.asExpr() = cc.getArgument(0)
+ )
+ }
+}
+
+/**
+ * An `File` instance which reads from an SD card
+ * taken as a source for insecure Dex class loading vulnerabilities.
+ */
+private class ExternalFile extends InsecureDexSource {
+ ExternalFile() {
+ exists(ConstructorCall cc, Argument a |
+ cc.getConstructedType() instanceof TypeFile and
+ a = cc.getArgument(0) and
+ a.(CompileTimeConstantExpr).getStringValue().matches("%sdcard%")
+ |
+ this.asExpr() = a
+ )
+ }
+}
+
+/**
+ * A directory or file which may be stored in an world writable directory
+ * taken as a source for insecure Dex class loading vulnerabilities.
+ */
+private class ExternalStorageDirSource extends InsecureDexSource {
+ ExternalStorageDirSource() {
+ exists(Method m |
+ m.getDeclaringType().hasQualifiedName("android.os", "Environment") and
+ m.hasName("getExternalStorageDirectory")
+ or
+ m.getDeclaringType().hasQualifiedName("android.content", "Context") and
+ m.hasName([
+ "getExternalFilesDir", "getExternalFilesDirs", "getExternalMediaDirs",
+ "getExternalCacheDir", "getExternalCacheDirs"
+ ])
+ |
+ this.asExpr() = m.getAReference()
+ )
+ }
+}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoadingBad.java b/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoadingBad.java
new file mode 100644
index 000000000000..d8fdd828f4fb
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoadingBad.java
@@ -0,0 +1,32 @@
+
+import android.app.Application;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.os.Bundle;
+
+import dalvik.system.DexClassLoader;
+import dalvik.system.DexFile;
+
+public class InsecureDexLoading extends Application {
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ updateChecker();
+ }
+
+ private void updateChecker() {
+ try {
+ File file = new File("/sdcard/updater.apk");
+ if (file.exists() && file.isFile() && file.length() <= 1000) {
+ DexClassLoader cl = new DexClassLoader(file.getAbsolutePath(), getCacheDir().getAbsolutePath(), null,
+ getClassLoader());
+ int version = (int) cl.loadClass("my.package.class").getDeclaredMethod("myMethod").invoke(null);
+ if (Build.VERSION.SDK_INT < version) {
+ Toast.makeText(this, "Securely loaded Dex!", Toast.LENGTH_LONG).show();
+ }
+ }
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoadingGood.java b/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoadingGood.java
new file mode 100644
index 000000000000..e45e3938f7b8
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoadingGood.java
@@ -0,0 +1,23 @@
+public class SecureDexLoading extends Application {
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ updateChecker();
+ }
+
+ private void updateChecker() {
+ try {
+ File file = new File(getCacheDir() + "/updater.apk");
+ if (file.exists() && file.isFile() && file.length() <= 1000) {
+ DexClassLoader cl = new DexClassLoader(file.getAbsolutePath(), getCacheDir().getAbsolutePath(), null,
+ getClassLoader());
+ int version = (int) cl.loadClass("my.package.class").getDeclaredMethod("myMethod").invoke(null);
+ if (Build.VERSION.SDK_INT < version) {
+ Toast.makeText(this, "Securely loaded Dex!", Toast.LENGTH_LONG).show();
+ }
+ }
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+}
\ No newline at end of file
From 62a0775cf69c8bda45aeb9b0cdf60f28dafd9c72 Mon Sep 17 00:00:00 2001
From: yoff
Date: Thu, 25 Mar 2021 23:09:11 +0100
Subject: [PATCH 065/433] Update
python/ql/src/Security/CWE-327/examples/secure_protocol.py
Co-authored-by: Rasmus Wriedt Larsen
---
python/ql/src/Security/CWE-327/examples/secure_protocol.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/ql/src/Security/CWE-327/examples/secure_protocol.py b/python/ql/src/Security/CWE-327/examples/secure_protocol.py
index 94b3557d0172..e349bdae832d 100644
--- a/python/ql/src/Security/CWE-327/examples/secure_protocol.py
+++ b/python/ql/src/Security/CWE-327/examples/secure_protocol.py
@@ -4,7 +4,7 @@
hostname = 'www.python.org'
context = ssl.SSLContext(ssl.PROTOCOL_TLS)
context.options |= ssl.OP_NO_TLSv1
-context.options |= ssl.OP_NO_TLSv1_1 # This added by me
+context.options |= ssl.OP_NO_TLSv1_1
with socket.create_connection((hostname, 443)) as sock:
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
From 2b257318f1c9e1d4a27f9022d94a08fc3e4cc3da Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Thu, 25 Mar 2021 23:22:24 +0100
Subject: [PATCH 066/433] Python: more precise comment
---
python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.py b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.py
index 3ff1207b527f..ab80ed47dacd 100644
--- a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.py
+++ b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.py
@@ -28,7 +28,7 @@
SSLContext(protocol=ssl.PROTOCOL_TLSv1_2)
SSL.Context(SSL.TLSv1_2_METHOD)
-# possibly secure versions specified
+# insecure versions allowed by specified range
SSLContext(protocol=ssl.PROTOCOL_SSLv23)
SSLContext(protocol=ssl.PROTOCOL_TLS)
SSLContext(protocol=ssl.PROTOCOL_TLS_CLIENT)
From 54dad57cf4a22a9d01a434d0633606fa8adf7c8f Mon Sep 17 00:00:00 2001
From: yoff
Date: Fri, 26 Mar 2021 00:25:40 +0100
Subject: [PATCH 067/433] Update
python/ql/test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py
Co-authored-by: Rasmus Wriedt Larsen
---
python/ql/test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/python/ql/test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py b/python/ql/test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py
index e4205c498244..fa7714118828 100644
--- a/python/ql/test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py
+++ b/python/ql/test/query-tests/Security/CWE-327/pyOpenSSL_fluent.py
@@ -23,6 +23,8 @@ def test_fluent_no_TLSv1():
def test_fluent_safe():
hostname = 'www.python.org'
context = SSL.Context(SSL.SSLv23_METHOD)
+ context.set_options(SSL.OP_NO_SSLv2)
+ context.set_options(SSL.OP_NO_SSLv3)
context.set_options(SSL.OP_NO_TLSv1)
context.set_options(SSL.OP_NO_TLSv1_1)
From 554404575d243ffdff19559e8cbbf4c300019d32 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Mar 2021 00:29:40 +0100
Subject: [PATCH 068/433] Python: fix typo and name.
---
python/ql/src/Security/CWE-327/ReadMe.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/ql/src/Security/CWE-327/ReadMe.md b/python/ql/src/Security/CWE-327/ReadMe.md
index c5330a78fda9..680ae56f4934 100644
--- a/python/ql/src/Security/CWE-327/ReadMe.md
+++ b/python/ql/src/Security/CWE-327/ReadMe.md
@@ -12,7 +12,7 @@ This should be kept up to date; the world is moving fast and protocols are being
- `ssl.wrap_socket` is creating insecure connections, use `SSLContext.wrap_socket` instead. [link](https://docs.python.org/3/library/ssl.html#ssl.wrap_socket)
> Deprecated since version 3.7: Since Python 3.2 and 2.7.9, it is recommended to use the `SSLContext.wrap_socket()` instead of `wrap_socket()`. The top-level function is limited and creates an insecure client socket without server name indication or hostname matching.
-- Default consteructors are fine, a sluent api is used to constrain possible protocols later.
+- Default consteructors are fine, a fluent api is used to constrain possible protocols later.
## Current recomendation
From 9488b8bb18fa89614480024700f51c3500f96725 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Mar 2021 00:31:56 +0100
Subject: [PATCH 069/433] Python: actually rename
---
python/ql/src/Security/CWE-327/{ReadMe.md => README.md} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename python/ql/src/Security/CWE-327/{ReadMe.md => README.md} (100%)
diff --git a/python/ql/src/Security/CWE-327/ReadMe.md b/python/ql/src/Security/CWE-327/README.md
similarity index 100%
rename from python/ql/src/Security/CWE-327/ReadMe.md
rename to python/ql/src/Security/CWE-327/README.md
From d33b04cd965de99180434c998f96a15f98c1f5fa Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Fri, 26 Mar 2021 02:33:40 +0000
Subject: [PATCH 070/433] Query to detect plaintext credentials in Java
properties files
---
.../CWE-555/CredentialsInPropertiesFile.qhelp | 45 ++++++++++
.../CWE-555/CredentialsInPropertiesFile.ql | 87 +++++++++++++++++++
.../CWE/CWE-555/configuration.properties | 26 ++++++
.../CredentialsInPropertiesFile.expected | 5 ++
.../CWE-555/CredentialsInPropertiesFile.qlref | 1 +
.../security/CWE-555/configuration.properties | 37 ++++++++
.../security/CWE-555/messages.properties | 8 ++
7 files changed, 209 insertions(+)
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.qhelp
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.ql
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-555/configuration.properties
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.expected
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.qlref
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-555/configuration.properties
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-555/messages.properties
diff --git a/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.qhelp b/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.qhelp
new file mode 100644
index 000000000000..afbd40685bad
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.qhelp
@@ -0,0 +1,45 @@
+
+
+
+
+ Credentials management issues occur when credentials are stored in plaintext in
+ an application’s properties file. Common credentials include but are not limited
+ to LDAP, mail, database, proxy account, and so on. Storing plaintext credentials
+ in a properties file allows anyone who can read the file access to the protected
+ resource. Good credentials management guidelines require that credentials never
+ be stored in plaintext.
+
+
+
+
+
+ Credentials stored in properties files should be encrypted and recycled regularly.
+ In a Java EE deployment scenario, utilities provided by application servers like
+ keystore and password vault can be used to encrypt and manage credentials.
+
+
+
+
+
+ In the first example, the credentials of a LDAP and datasource properties are stored
+ in cleartext in the properties file.
+
+
+
+ In the second example, the credentials of a LDAP and datasource properties are stored
+ in the encrypted format.
+
+
+
+
+
+
+ OWASP:
+ Password Plaintext Storage
+
+
+ Medium (Rajeev Shukla):
+ Encrypting database password in the application.properties file
+
+
+
diff --git a/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.ql b/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.ql
new file mode 100644
index 000000000000..2ab074e56d85
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.ql
@@ -0,0 +1,87 @@
+/**
+ * @name Cleartext Credentials in Properties File
+ * @description Finds cleartext credentials in Java properties files.
+ * @kind problem
+ * @id java/credentials-in-properties
+ * @tags security
+ * external/cwe/cwe-555
+ * external/cwe/cwe-256
+ * external/cwe/cwe-260
+ */
+
+import java
+import semmle.code.configfiles.ConfigFiles
+
+private string suspicious() {
+ result = "%password%" or
+ result = "%passwd%" or
+ result = "%account%" or
+ result = "%accnt%" or
+ result = "%credential%" or
+ result = "%token%" or
+ result = "%secret%" or
+ result = "%access%key%"
+}
+
+private string nonSuspicious() {
+ result = "%hashed%" or
+ result = "%encrypted%" or
+ result = "%crypt%"
+}
+
+/** Holds if the value is not cleartext credentials. */
+bindingset[value]
+predicate isNotCleartextCredentials(string value) {
+ value = "" // Empty string
+ or
+ value.length() < 7 // Typical credentials are no less than 6 characters
+ or
+ value.matches("% %") // Sentences containing spaces
+ or
+ value.regexpMatch(".*[^a-zA-Z\\d]{3,}.*") // Contain repeated non-alphanumeric characters such as a fake password pass**** or ????
+ or
+ value.matches("@%") // Starts with the "@" sign
+ or
+ value.regexpMatch("\\$\\{.*\\}") // Variable placeholder ${credentials}
+ or
+ value.matches("%=") // A basic check of encrypted credentials ending with padding characters
+ or
+ value.matches("ENC(%)") // Encrypted value
+ or
+ value.toLowerCase().matches(suspicious()) // Could be message properties or fake passwords
+}
+
+/**
+ * Holds if the credentials are in a non-production properties file indicated by:
+ * a) in a non-production directory
+ * b) with a non-production file name
+ */
+predicate isNonProdCredentials(CredentialsConfig cc) {
+ cc.getFile().getAbsolutePath().matches(["%dev%", "%test%", "%sample%"]) and
+ not cc.getFile().getAbsolutePath().matches("%codeql%") // CodeQL test cases
+}
+
+/** The properties file with configuration key/value pairs. */
+class ConfigProperties extends ConfigPair {
+ ConfigProperties() { this.getFile().getBaseName().toLowerCase().matches("%.properties") }
+}
+
+/** The credentials configuration property. */
+class CredentialsConfig extends ConfigProperties {
+ CredentialsConfig() {
+ this.getNameElement().getName().trim().toLowerCase().matches(suspicious()) and
+ not this.getNameElement().getName().trim().toLowerCase().matches(nonSuspicious())
+ }
+
+ string getName() { result = this.getNameElement().getName().trim() }
+
+ string getValue() { result = this.getValueElement().getValue().trim() }
+}
+
+from CredentialsConfig cc
+where
+ not isNotCleartextCredentials(cc.getValue()) and
+ not isNonProdCredentials(cc)
+select cc,
+ "Plaintext credentials " + cc.getName() + " have cleartext value " + cc.getValue() +
+ " in properties file."
diff --git a/java/ql/src/experimental/Security/CWE/CWE-555/configuration.properties b/java/ql/src/experimental/Security/CWE/CWE-555/configuration.properties
new file mode 100644
index 000000000000..55e8b0d86da2
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-555/configuration.properties
@@ -0,0 +1,26 @@
+#***************************** LDAP Credentials *****************************************#
+
+ldap.ldapHost = ldap.example.com
+ldap.ldapPort = 636
+ldap.loginDN = cn=Directory Manager
+
+#### BAD: LDAP credentials are stored in cleartext ####
+ldap.password = mysecpass
+
+#### GOOD: LDAP credentials are stored in the encrypted format ####
+ldap.password = eFRZ3Cqo5zDJWMYLiaEupw==
+
+ldap.domain1 = example
+ldap.domain2 = com
+ldap.url= ldaps://ldap.example.com:636/dc=example,dc=com
+
+#*************************** MS SQL Database Connection **********************************#
+datasource1.driverClassName = com.microsoft.sqlserver.jdbc.SQLServerDriver
+datasource1.url = jdbc:sqlserver://ms.example.com\\exampledb:1433;
+datasource1.username = sa
+
+#### BAD: Datasource credentials are stored in cleartext ####
+datasource1.password = Passw0rd@123
+
+#### GOOD: Datasource credentials are stored in the encrypted format ####
+datasource1.password = VvOgflYS1EUzJdVNDoBcnA==
diff --git a/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.expected b/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.expected
new file mode 100644
index 000000000000..0ce339139324
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.expected
@@ -0,0 +1,5 @@
+| configuration.properties:6:1:6:25 | ldap.password=mysecpass | Plaintext credentials ldap.password have cleartext value mysecpass in properties file. |
+| configuration.properties:18:1:18:35 | datasource1.password=Passw0rd@123 | Plaintext credentials datasource1.password have cleartext value Passw0rd@123 in properties file. |
+| configuration.properties:25:1:25:31 | mail.password=MysecPWxWa@1993 | Plaintext credentials mail.password have cleartext value MysecPWxWa@1993 in properties file. |
+| configuration.properties:33:1:33:50 | com.example.aws.s3.access_key=AKMAMQPBYMCD6YSAYCBA | Plaintext credentials com.example.aws.s3.access_key have cleartext value AKMAMQPBYMCD6YSAYCBA in properties file. |
+| configuration.properties:34:1:34:70 | com.example.aws.s3.secret_key=8lMPSfWzZq+wcWtck5+QPLOJDZzE783pS09/IO3k | Plaintext credentials com.example.aws.s3.secret_key have cleartext value 8lMPSfWzZq+wcWtck5+QPLOJDZzE783pS09/IO3k in properties file. |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.qlref b/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.qlref
new file mode 100644
index 000000000000..e2536bfe8835
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.ql
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-555/configuration.properties b/java/ql/test/experimental/query-tests/security/CWE-555/configuration.properties
new file mode 100644
index 000000000000..a044161f0971
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-555/configuration.properties
@@ -0,0 +1,37 @@
+#***************************** LDAP Credentials *****************************************#
+ldap.ldapHost = ldap.example.com
+ldap.ldapPort = 636
+ldap.loginDN = cn=Directory Manager
+#### BAD: LDAP credentials are stored in cleartext ####
+ldap.password = mysecpass
+#### GOOD: LDAP credentials are stored in the encrypted format ####
+ldap.password = eFRZ3Cqo5zDJWMYLiaEupw==
+ldap.domain1 = example
+ldap.domain2 = com
+ldap.url= ldaps://ldap.example.com:636/dc=example,dc=com
+
+#*************************** MS SQL Database Connection **********************************#
+datasource1.driverClassName = com.microsoft.sqlserver.jdbc.SQLServerDriver
+datasource1.url = jdbc:sqlserver://ms.example.com\\exampledb:1433;
+datasource1.username = sa
+#### BAD: Datasource credentials are stored in cleartext ####
+datasource1.password = Passw0rd@123
+#### GOOD: Datasource credentials are stored in the encrypted format ####
+datasource1.password = VvOgflYS1EUzJdVNDoBcnA==
+
+#*************************** Mail Connection **********************************#
+mail.username = test@example.com
+#### BAD: Mail credentials are stored in cleartext ####
+mail.password = MysecPWxWa@1993
+#### GOOD: Mail credentials are stored in the encrypted format ####
+mail.password = M*********@1993
+
+#*************************** AWS S3 Connection **********************************#
+com.example.aws.s3.bucket_name=com-bucket-1
+com.example.aws.s3.directory_name=com-directory-1
+#### BAD: Access keys are stored in properties file in cleartext ####
+com.example.aws.s3.access_key=AKMAMQPBYMCD6YSAYCBA
+com.example.aws.s3.secret_key=8lMPSfWzZq+wcWtck5+QPLOJDZzE783pS09/IO3k
+#### GOOD: Access keys are not stored in properties file ####
+com.example.aws.s3.access_key=${ENV:AWS_ACCESS_KEY_ID}
+com.example.aws.s3.secret_key=${ENV:AWS_SECRET_ACCESS_KEY}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-555/messages.properties b/java/ql/test/experimental/query-tests/security/CWE-555/messages.properties
new file mode 100644
index 000000000000..fac63ec23e85
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-555/messages.properties
@@ -0,0 +1,8 @@
+prompt.username=Username
+prompt.password=Password
+
+forgot_password.error=Please enter a valid email address.
+reset_password.error=Passwords must match and not be empty.
+
+login.password_expired=Your current password has expired. Please reset your password.
+login.login_failure=Unable to verify username or password. Please try again.
From 936757b4bf385addba174a37c24f029781110553 Mon Sep 17 00:00:00 2001
From: yoff
Date: Fri, 26 Mar 2021 08:05:51 +0100
Subject: [PATCH 071/433] Update
python/ql/src/Security/CWE-327/FluentApiModel.qll
Co-authored-by: Rasmus Wriedt Larsen
---
python/ql/src/Security/CWE-327/FluentApiModel.qll | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/FluentApiModel.qll b/python/ql/src/Security/CWE-327/FluentApiModel.qll
index d222af704997..4d63a14bdbc9 100644
--- a/python/ql/src/Security/CWE-327/FluentApiModel.qll
+++ b/python/ql/src/Security/CWE-327/FluentApiModel.qll
@@ -39,13 +39,7 @@ class InsecureContextConfiguration extends DataFlow::Configuration {
)
}
- override predicate isBarrierIn(DataFlow::Node node) {
- exists(ProtocolUnrestriction r |
- r = library.protocol_unrestriction() and
- node = r.getContext() and
- r.getUnrestriction() = tracked_version
- )
- }
+ override predicate isBarrierIn(DataFlow::Node node) { this.isSource(node) }
}
/**
From f1619f1ee8f6c5df2aa4d4b862deb46626bed2db Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Mar 2021 08:18:11 +0100
Subject: [PATCH 072/433] Python: "source" -> "contextOrigin"
---
.../ql/src/Security/CWE-327/InsecureProtocol.ql | 17 +++++++++--------
1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/InsecureProtocol.ql b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
index 194cc1f5ec1d..b945f2e609b7 100644
--- a/python/ql/src/Security/CWE-327/InsecureProtocol.ql
+++ b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
@@ -18,11 +18,11 @@ string callName(AstNode call) {
exists(Attribute a | a = call | result = callName(a.getObject()) + "." + a.getName())
}
-string sourceName(DataFlow::Node source) {
- result = "call to " + callName(source.asCfgNode().(CallNode).getFunction().getNode())
+string originName(DataFlow::Node contextOrigin) {
+ result = "call to " + callName(contextOrigin.asCfgNode().(CallNode).getFunction().getNode())
or
- not source.asCfgNode() instanceof CallNode and
- not source instanceof ContextCreation and
+ not contextOrigin.asCfgNode() instanceof CallNode and
+ not contextOrigin instanceof ContextCreation and
result = "context modification"
}
@@ -32,11 +32,12 @@ string verb(boolean specific) {
specific = false and result = "allowed"
}
-from DataFlow::Node creation, string insecure_version, DataFlow::Node source, boolean specific
+from
+ DataFlow::Node creation, string insecure_version, DataFlow::Node contextOrigin, boolean specific
where
- unsafe_connection_creation(creation, insecure_version, source, specific)
+ unsafe_connection_creation(creation, insecure_version, contextOrigin, specific)
or
- unsafe_context_creation(creation, insecure_version, source.asCfgNode()) and specific = true
+ unsafe_context_creation(creation, insecure_version, contextOrigin.asCfgNode()) and specific = true
select creation,
"Insecure SSL/TLS protocol version " + insecure_version + " " + verb(specific) + " by $@ ",
- source, sourceName(source)
+ contextOrigin, originName(contextOrigin)
From e936540863385a7ff8437f890bc7bba24b00c547 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Mar 2021 08:22:09 +0100
Subject: [PATCH 073/433] Python: remove internal import
---
python/ql/src/Security/CWE-327/Ssl.qll | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll
index be16138b9612..88aaebf67baf 100644
--- a/python/ql/src/Security/CWE-327/Ssl.qll
+++ b/python/ql/src/Security/CWE-327/Ssl.qll
@@ -1,6 +1,5 @@
import python
import semmle.python.ApiGraphs
-import semmle.python.dataflow.new.internal.Attributes as Attributes
import TlsLibraryModel
class SSLContextCreation extends ContextCreation {
@@ -104,7 +103,7 @@ class ContextSetVersion extends ProtocolRestriction, ProtocolUnrestriction {
ProtocolVersion restriction;
ContextSetVersion() {
- exists(Attributes::AttrWrite aw |
+ exists(DataFlow::AttrWrite aw |
aw.getObject().asCfgNode() = node and
aw.getAttributeName() = "minimum_version" and
aw.getValue() =
From b21672c81cbc9a82f56e54093652b6a741dbe3e6 Mon Sep 17 00:00:00 2001
From: Alexander Eyers-Taylor
Date: Fri, 26 Mar 2021 11:14:09 +0000
Subject: [PATCH 074/433] Apply suggestions from code review
Co-authored-by: Shati Patel <42641846+shati-patel@users.noreply.github.com>
Co-authored-by: Marcono1234
---
.../ql-language-reference/ql-language-specification.rst | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/codeql/ql-language-reference/ql-language-specification.rst b/docs/codeql/ql-language-reference/ql-language-specification.rst
index ad88b791019e..fb89100b4018 100644
--- a/docs/codeql/ql-language-reference/ql-language-specification.rst
+++ b/docs/codeql/ql-language-reference/ql-language-specification.rst
@@ -1168,9 +1168,9 @@ A valid call with results *resolves* to a set of predicates. The ways a call can
- If the call has no receiver and the predicate name is a selection identifier, then the qualifier is resolved as a module (see "`Module resolution <#module-resolution>`__"). The identifier is then resolved in the exported predicate environment of the qualifier module.
- If the call has a super expression as the receiver, then it resolves to a member predicate in a class that the enclosing class inherits from:
- - If the super expression is unqualified and there is a single class that the current class inherits from then the super-class is that class.
- - If the super expression is unqualified and there is are multiple classes that the current class inherits from then the super-class is the domain type.
- - Otherwise the super-class is the class named by the qualifier of the super expression.
+ - If the super expression is unqualified and there is a single class that the current class inherits from, then the super-class is that class.
+ - If the super expression is unqualified and there are multiple classes that the current class inherits from, then the super-class is the domain type.
+ - Otherwise, the super-class is the class named by the qualifier of the super expression.
The predicate is resolved by looking up its name and arity in the exported predicate environment of the super-class.
From 1be2be843dd4a0afca316598e227d4a89aea5433 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Mar 2021 13:08:23 +0100
Subject: [PATCH 075/433] Python: update test expectations
---
.../test/query-tests/Security/CWE-327/InsecureProtocol.expected | 2 --
1 file changed, 2 deletions(-)
diff --git a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
index e578a335d846..f4cd96eb82e2 100644
--- a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
+++ b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
@@ -17,8 +17,6 @@
| pyOpenSSL_fluent.py:18:27:18:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv2 allowed by $@ | pyOpenSSL_fluent.py:15:15:15:44 | ControlFlowNode for Attribute() | call to SSL.Context |
| pyOpenSSL_fluent.py:18:27:18:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv3 allowed by $@ | pyOpenSSL_fluent.py:15:15:15:44 | ControlFlowNode for Attribute() | call to SSL.Context |
| pyOpenSSL_fluent.py:18:27:18:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | pyOpenSSL_fluent.py:15:15:15:44 | ControlFlowNode for Attribute() | call to SSL.Context |
-| pyOpenSSL_fluent.py:29:27:29:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv2 allowed by $@ | pyOpenSSL_fluent.py:25:15:25:44 | ControlFlowNode for Attribute() | call to SSL.Context |
-| pyOpenSSL_fluent.py:29:27:29:33 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv3 allowed by $@ | pyOpenSSL_fluent.py:25:15:25:44 | ControlFlowNode for Attribute() | call to SSL.Context |
| ssl_fluent.py:9:14:9:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:6:15:6:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
| ssl_fluent.py:9:14:9:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:6:15:6:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
| ssl_fluent.py:19:14:19:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:15:15:15:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
From 2e948da3b4a5de3e0bc56d4ad0d19f4f787d5473 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Mar 2021 13:08:45 +0100
Subject: [PATCH 076/433] Python: suggested refactor
---
python/ql/src/Security/CWE-327/FluentApiModel.qll | 5 ++---
python/ql/src/Security/CWE-327/InsecureProtocol.ql | 4 +++-
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/FluentApiModel.qll b/python/ql/src/Security/CWE-327/FluentApiModel.qll
index 4d63a14bdbc9..543c32799bc9 100644
--- a/python/ql/src/Security/CWE-327/FluentApiModel.qll
+++ b/python/ql/src/Security/CWE-327/FluentApiModel.qll
@@ -67,9 +67,8 @@ predicate unsafe_connection_creation(
}
/** A connection is created insecurely without reference to a context. */
-predicate unsafe_context_creation(DataFlow::Node node, string insecure_version, CallNode call) {
+predicate unsafe_context_creation(DataFlow::CallCfgNode call, string insecure_version) {
exists(TlsLibrary l, ContextCreation cc | cc = l.insecure_context_creation(insecure_version) |
- cc = node and
- cc.getNode() = call
+ cc = call
)
}
diff --git a/python/ql/src/Security/CWE-327/InsecureProtocol.ql b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
index b945f2e609b7..a716b8948807 100644
--- a/python/ql/src/Security/CWE-327/InsecureProtocol.ql
+++ b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
@@ -37,7 +37,9 @@ from
where
unsafe_connection_creation(creation, insecure_version, contextOrigin, specific)
or
- unsafe_context_creation(creation, insecure_version, contextOrigin.asCfgNode()) and specific = true
+ unsafe_context_creation(creation, insecure_version) and
+ contextOrigin = creation and
+ specific = true
select creation,
"Insecure SSL/TLS protocol version " + insecure_version + " " + verb(specific) + " by $@ ",
contextOrigin, originName(contextOrigin)
From 7d7cbc49db35d0b05f12594984ac36ef529699a1 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Mar 2021 14:20:38 +0100
Subject: [PATCH 077/433] Fix comments. This induced fixing the code, since
things were wired up wrongly. Currently the only implementation of
`insecure_connection_creation` is `ssl.wrap_socket`, which is also the sole
target of py/insecure-default-protocol`, so perhaps this part should be
turned off?
---
.../src/Security/CWE-327/FluentApiModel.qll | 39 +++++++++++++------
.../src/Security/CWE-327/InsecureProtocol.ql | 18 ++++++---
2 files changed, 40 insertions(+), 17 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/FluentApiModel.qll b/python/ql/src/Security/CWE-327/FluentApiModel.qll
index 543c32799bc9..169996081dce 100644
--- a/python/ql/src/Security/CWE-327/FluentApiModel.qll
+++ b/python/ql/src/Security/CWE-327/FluentApiModel.qll
@@ -43,16 +43,21 @@ class InsecureContextConfiguration extends DataFlow::Configuration {
}
/**
- * A connection is created from a context allowing an insecure protocol,
- * and that protocol has not been restricted appropriately.
+ * Holds if `conectionCreation` marks the creation of a connetion based on the contex
+ * found at `contextOrigin` and allowing `insecure_version`.
+ * `specific` is true iff the context if configured for a specific protocol version rather
+ * than for a family of protocols.
*/
-predicate unsafe_connection_creation(
- DataFlow::Node creation, ProtocolVersion insecure_version, DataFlow::Node source, boolean specific
+predicate unsafe_connection_creation_with_context(
+ DataFlow::Node connectionCreation, ProtocolVersion insecure_version, DataFlow::Node contextOrigin,
+ boolean specific
) {
// Connection created from a context allowing `insecure_version`.
- exists(InsecureContextConfiguration c, ProtocolUnrestriction cc | c.hasFlow(cc, creation) |
+ exists(InsecureContextConfiguration c, ProtocolUnrestriction co |
+ c.hasFlow(co, connectionCreation)
+ |
insecure_version = c.getTrackedVersion() and
- source = cc and
+ contextOrigin = co and
specific = false
)
or
@@ -60,15 +65,27 @@ predicate unsafe_connection_creation(
exists(TlsLibrary l, DataFlow::CfgNode cc |
cc = l.insecure_connection_creation(insecure_version)
|
- creation = cc and
- source = cc and
+ connectionCreation = cc and
+ contextOrigin = cc and
specific = true
)
}
-/** A connection is created insecurely without reference to a context. */
-predicate unsafe_context_creation(DataFlow::CallCfgNode call, string insecure_version) {
+/**
+ * Holds if `conectionCreation` marks the creation of a connetion witout reference to a context
+ * and allowing `insecure_version`.
+ * `specific` is true iff the context if configured for a specific protocol version rather
+ * than for a family of protocols.
+ */
+predicate unsafe_connection_creation_without_context(
+ DataFlow::CallCfgNode connectionCreation, string insecure_version
+) {
+ exists(TlsLibrary l | connectionCreation = l.insecure_connection_creation(insecure_version))
+}
+
+/** Holds if `contextCreation` is creating a context ties to a specific insecure version. */
+predicate unsafe_context_creation(DataFlow::CallCfgNode contextCreation, string insecure_version) {
exists(TlsLibrary l, ContextCreation cc | cc = l.insecure_context_creation(insecure_version) |
- cc = call
+ contextCreation = cc
)
}
diff --git a/python/ql/src/Security/CWE-327/InsecureProtocol.ql b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
index a716b8948807..974c36ddb0cb 100644
--- a/python/ql/src/Security/CWE-327/InsecureProtocol.ql
+++ b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
@@ -33,13 +33,19 @@ string verb(boolean specific) {
}
from
- DataFlow::Node creation, string insecure_version, DataFlow::Node contextOrigin, boolean specific
+ DataFlow::Node connectionCreation, string insecure_version, DataFlow::Node protocolConfiguration,
+ boolean specific
where
- unsafe_connection_creation(creation, insecure_version, contextOrigin, specific)
+ unsafe_connection_creation_with_context(connectionCreation, insecure_version,
+ protocolConfiguration, specific)
or
- unsafe_context_creation(creation, insecure_version) and
- contextOrigin = creation and
+ unsafe_connection_creation_without_context(connectionCreation, insecure_version) and
+ protocolConfiguration = connectionCreation and
specific = true
-select creation,
+ or
+ unsafe_context_creation(protocolConfiguration, insecure_version) and
+ connectionCreation = protocolConfiguration and
+ specific = true
+select connectionCreation,
"Insecure SSL/TLS protocol version " + insecure_version + " " + verb(specific) + " by $@ ",
- contextOrigin, originName(contextOrigin)
+ protocolConfiguration, originName(protocolConfiguration)
From 8155334fa7b4b72015e1b75bf2563b0b96c699bb Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Mar 2021 15:57:07 +0100
Subject: [PATCH 078/433] Python: More elaborate qldoc also refactor code to
match
---
.../src/Security/CWE-327/FluentApiModel.qll | 48 +++++++++++++------
1 file changed, 33 insertions(+), 15 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/FluentApiModel.qll b/python/ql/src/Security/CWE-327/FluentApiModel.qll
index 169996081dce..d4eb13a133d3 100644
--- a/python/ql/src/Security/CWE-327/FluentApiModel.qll
+++ b/python/ql/src/Security/CWE-327/FluentApiModel.qll
@@ -2,9 +2,21 @@ import python
import TlsLibraryModel
/**
- * Configuration to track flow from the creation of a context to
- * that context being used to create a connection.
- * Flow is broken if the insecure protocol of interest is being restricted.
+ * Configuration to determine the state of a context being used to create
+ * a conection.
+ *
+ * The state is in terms of whether a specific protocol is allowed. This is
+ * either true or false when the context is created and can then be modified
+ * later by either restricting or unrestricting the protocol (see the predicates
+ * `isRestriction` and `isUnrestriction`).
+ *
+ * Since we are interested in the final state, we want the flow to start from
+ * the last unrestriction, so we disallow flow into unrestrictions. We also
+ * model the creation as an unrestriction of everything it allows, to account
+ * for the common case where the creation plays the role of "last unrestriction".
+ *
+ * Since we really want "the last unrestriction, not nullified by a restriction",
+ * we also disallow flow into restrictions.
*/
class InsecureContextConfiguration extends DataFlow::Configuration {
TlsLibrary library;
@@ -17,29 +29,35 @@ class InsecureContextConfiguration extends DataFlow::Configuration {
ProtocolVersion getTrackedVersion() { result = tracked_version }
- override predicate isSource(DataFlow::Node source) {
- // source = library.unspecific_context_creation()
- exists(ProtocolUnrestriction pu |
- pu = library.protocol_unrestriction() and
- pu.getUnrestriction() = tracked_version
- |
- source = pu.getContext()
- )
- }
+ override predicate isSource(DataFlow::Node source) { this.isUnrestriction(source) }
override predicate isSink(DataFlow::Node sink) {
sink = library.connection_creation().getContext()
}
- override predicate isBarrierOut(DataFlow::Node node) {
+ override predicate isBarrierIn(DataFlow::Node node) {
+ this.isRestriction(node)
+ or
+ this.isUnrestriction(node)
+ }
+
+ private predicate isRestriction(DataFlow::Node node) {
exists(ProtocolRestriction r |
r = library.protocol_restriction() and
- node = r.getContext() and
r.getRestriction() = tracked_version
+ |
+ node = r.getContext()
)
}
- override predicate isBarrierIn(DataFlow::Node node) { this.isSource(node) }
+ private predicate isUnrestriction(DataFlow::Node node) {
+ exists(ProtocolUnrestriction pu |
+ pu = library.protocol_unrestriction() and
+ pu.getUnrestriction() = tracked_version
+ |
+ node = pu.getContext()
+ )
+ }
}
/**
From 98dfe1a00a14e6dfc8c27d84cfeb2ee363515f56 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Mar 2021 17:27:43 +0100
Subject: [PATCH 079/433] Python: Elaborate qldoc and renames to match
---
python/ql/src/Security/CWE-327/Ssl.qll | 34 ++++++++++++++++++--------
1 file changed, 24 insertions(+), 10 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll
index 88aaebf67baf..110b4bf185a1 100644
--- a/python/ql/src/Security/CWE-327/Ssl.qll
+++ b/python/ql/src/Security/CWE-327/Ssl.qll
@@ -45,7 +45,7 @@ class OptionsAugOr extends ProtocolRestriction {
(
aa.getValue() = flag
or
- impliesValue(aa.getValue(), flag, false, false)
+ impliesBitSet(aa.getValue(), flag, false, false)
)
)
}
@@ -70,7 +70,7 @@ class OptionsAugAndNot extends ProtocolUnrestriction {
(
aa.getValue() = notFlag
or
- impliesValue(aa.getValue(), notFlag, true, true)
+ impliesBitSet(aa.getValue(), notFlag, true, true)
)
)
}
@@ -80,22 +80,36 @@ class OptionsAugAndNot extends ProtocolUnrestriction {
override ProtocolVersion getUnrestriction() { result = restriction }
}
-/** Whether `part` evaluates to `partIsTrue` if `whole` evaluates to `wholeIsTrue`. */
-predicate impliesValue(BinaryExpr whole, Expr part, boolean partIsTrue, boolean wholeIsTrue) {
+/**
+ * Holds if
+ * for every bit, _b_:
+ * `wholeHasBitSet` represents that _b_ is set in `whole`
+ * implies
+ * `partHasBitSet` represents that _b_ is set in `part`
+ *
+ * As an example take `whole` = `part1 & part2`. Then
+ * `impliesBitSet(whole, part1, true, true)` holds
+ * because for any bit in `whole`, if that bit is set it must also be set in `part1`.
+ *
+ * Similarly for `whole` = `part1 | part2`. Here
+ * `impliesBitSet(whole, part1, false, false)` holds
+ * because for any bit in `whole`, if that bit is not set, it cannot be set in `part1`.
+ */
+predicate impliesBitSet(BinaryExpr whole, Expr part, boolean partHasBitSet, boolean wholeHasBitSet) {
whole.getOp() instanceof BitAnd and
(
- wholeIsTrue = true and partIsTrue = true and part in [whole.getLeft(), whole.getRight()]
+ wholeHasBitSet = true and partHasBitSet = true and part in [whole.getLeft(), whole.getRight()]
or
- wholeIsTrue = true and
- impliesValue([whole.getLeft(), whole.getRight()], part, partIsTrue, wholeIsTrue)
+ wholeHasBitSet = true and
+ impliesBitSet([whole.getLeft(), whole.getRight()], part, partHasBitSet, wholeHasBitSet)
)
or
whole.getOp() instanceof BitOr and
(
- wholeIsTrue = false and partIsTrue = false and part in [whole.getLeft(), whole.getRight()]
+ wholeHasBitSet = false and partHasBitSet = false and part in [whole.getLeft(), whole.getRight()]
or
- wholeIsTrue = false and
- impliesValue([whole.getLeft(), whole.getRight()], part, partIsTrue, wholeIsTrue)
+ wholeHasBitSet = false and
+ impliesBitSet([whole.getLeft(), whole.getRight()], part, partHasBitSet, wholeHasBitSet)
)
}
From 470b4d86582595101439a043ab4cd4db935b11a2 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Mar 2021 17:35:36 +0100
Subject: [PATCH 080/433] Python: Add missing qldoc
---
python/ql/src/Security/CWE-327/TlsLibraryModel.qll | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
index 3ab880e8bd9e..f66d663a7e2a 100644
--- a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
+++ b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
@@ -58,6 +58,10 @@ abstract class ProtocolUnrestriction extends DataFlow::CfgNode {
abstract ProtocolVersion getUnrestriction();
}
+/**
+ * A context is being created with a range of allowed protocols.
+ * This also serves as unrestricting these protocols.
+ */
abstract class UnspecificContextCreation extends ContextCreation, ProtocolUnrestriction {
TlsLibrary library;
ProtocolFamily family;
@@ -77,6 +81,7 @@ abstract class UnspecificContextCreation extends ContextCreation, ProtocolUnrest
}
}
+/** A model of a TLS library. */
abstract class TlsLibrary extends string {
TlsLibrary() { this in ["ssl", "pyOpenSSL"] }
From 44d62df3f72719d8ba57a2519877db2a179f4ec4 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Mar 2021 17:51:18 +0100
Subject: [PATCH 081/433] Python: Fix model of `TLS` and add reference
---
python/ql/src/Security/CWE-327/TlsLibraryModel.qll | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
index f66d663a7e2a..245a60b02955 100644
--- a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
+++ b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
@@ -71,11 +71,14 @@ abstract class UnspecificContextCreation extends ContextCreation, ProtocolUnrest
override DataFlow::CfgNode getContext() { result = this }
override ProtocolVersion getUnrestriction() {
+ // see https://www.openssl.org/docs/man1.1.0/man3/TLS_method.html
family = "TLS" and
- result in ["TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"]
+ result in ["SSLv3", "TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"]
or
// This can negotiate a TLS 1.3 connection (!)
- // see https://docs.python.org/3/library/ssl.html#ssl-contexts
+ // see
+ // - https://docs.python.org/3/library/ssl.html#ssl-contexts
+ // - https://www.openssl.org/docs/man1.0.2/man3/TLSv1_method.html
family = "SSLv23" and
result in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"]
}
From a72b1340eb6bb3bdbea9cabd6bdd942a9365b848 Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Fri, 26 Mar 2021 16:51:43 +0000
Subject: [PATCH 082/433] Add a comment on how to run the query
---
.../Security/CWE/CWE-016/InsecureSpringActuatorConfig.ql | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-016/InsecureSpringActuatorConfig.ql b/java/ql/src/experimental/Security/CWE/CWE-016/InsecureSpringActuatorConfig.ql
index 3acd22e767a3..772ac6cd2091 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-016/InsecureSpringActuatorConfig.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-016/InsecureSpringActuatorConfig.ql
@@ -8,6 +8,14 @@
* external/cwe-016
*/
+/*
+ * Note this query requires properties files to be indexed before it can produce results.
+ * If creating your own database with the CodeQL CLI, you should run
+ * `codeql database index-files --language=properties ...`
+ * If using lgtm.com, you should add `properties_files: true` to the index block of your
+ * lgtm.yml file (see https://lgtm.com/help/lgtm/java-extraction)
+ */
+
import java
import semmle.code.configfiles.ConfigFiles
import semmle.code.xml.MavenPom
From e0352fe7638223a1d998bdd26752e358d2ec8e92 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Mar 2021 23:26:24 +0100
Subject: [PATCH 083/433] Python: remove deprecated section of qhelp file
---
python/ql/src/Security/CWE-327/InsecureProtocol.qhelp | 6 ------
1 file changed, 6 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/InsecureProtocol.qhelp b/python/ql/src/Security/CWE-327/InsecureProtocol.qhelp
index cfcebd0930d3..9ecc7da0d60b 100644
--- a/python/ql/src/Security/CWE-327/InsecureProtocol.qhelp
+++ b/python/ql/src/Security/CWE-327/InsecureProtocol.qhelp
@@ -32,12 +32,6 @@
All cases should be updated to use a secure protocol, such as
PROTOCOL_TLSv1_2
.
-
- Note that ssl.wrap_socket
has been deprecated in
- Python 3.7. A preferred alternative is to use
- ssl.SSLContext
, which is supported in Python 2.7.9 and
- 3.2 and later versions.
-
Note that ssl.wrap_socket
has been deprecated in
Python 3.7. The recommended alternatives are:
From bf81122fc6b26584e85d4f0b7b4f854a2296471b Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 26 Mar 2021 23:37:19 +0100
Subject: [PATCH 084/433] Python: fix typo and add linebreaks
---
python/ql/src/Security/CWE-327/FluentApiModel.qll | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/FluentApiModel.qll b/python/ql/src/Security/CWE-327/FluentApiModel.qll
index d4eb13a133d3..803b7adab40e 100644
--- a/python/ql/src/Security/CWE-327/FluentApiModel.qll
+++ b/python/ql/src/Security/CWE-327/FluentApiModel.qll
@@ -63,7 +63,8 @@ class InsecureContextConfiguration extends DataFlow::Configuration {
/**
* Holds if `conectionCreation` marks the creation of a connetion based on the contex
* found at `contextOrigin` and allowing `insecure_version`.
- * `specific` is true iff the context if configured for a specific protocol version rather
+ *
+ * `specific` is true iff the context is configured for a specific protocol version rather
* than for a family of protocols.
*/
predicate unsafe_connection_creation_with_context(
@@ -92,7 +93,8 @@ predicate unsafe_connection_creation_with_context(
/**
* Holds if `conectionCreation` marks the creation of a connetion witout reference to a context
* and allowing `insecure_version`.
- * `specific` is true iff the context if configured for a specific protocol version rather
+ *
+ * `specific` is true iff the context is configured for a specific protocol version rather
* than for a family of protocols.
*/
predicate unsafe_connection_creation_without_context(
From bd863884479d4373e17717208441bbe6423d831b Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Sat, 27 Mar 2021 01:07:15 +0100
Subject: [PATCH 085/433] Python: Add typetracker to constrain attribute.
---
python/ql/src/Security/CWE-327/Ssl.qll | 20 +++++++++++++++++++-
1 file changed, 19 insertions(+), 1 deletion(-)
diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll
index 110b4bf185a1..7f1415233903 100644
--- a/python/ql/src/Security/CWE-327/Ssl.qll
+++ b/python/ql/src/Security/CWE-327/Ssl.qll
@@ -22,10 +22,28 @@ class SSLDefaultContextCreation extends ContextCreation {
override DataFlow::CfgNode getProtocol() { none() }
}
+/** Gets a reference to an `ssl.Context` instance. */
+private DataFlow::LocalSourceNode sslContextInstance(DataFlow::TypeTracker t) {
+ t.start() and
+ result = API::moduleImport("ssl").getMember(["SSLContext", "create_default_context"]).getACall()
+ or
+ exists(DataFlow::TypeTracker t2 | result = sslContextInstance(t2).track(t2, t))
+}
+
+/** Gets a reference to an `ssl.Context` instance. */
+DataFlow::Node sslContextInstance() {
+ sslContextInstance(DataFlow::TypeTracker::end()).flowsTo(result)
+}
+
class WrapSocketCall extends ConnectionCreation {
override CallNode node;
- WrapSocketCall() { node.getFunction().(AttrNode).getName() = "wrap_socket" }
+ WrapSocketCall() {
+ exists(DataFlow::AttrRead call | node.getFunction() = call.asCfgNode() |
+ call.getAttributeName() = "wrap_socket" and
+ call.getObject() = sslContextInstance()
+ )
+ }
override DataFlow::CfgNode getContext() {
result.getNode() = node.getFunction().(AttrNode).getObject()
From a53cbc1631c2cfc994f74d4c5ce9e39f6c38abbc Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Sat, 27 Mar 2021 00:11:01 +0000
Subject: [PATCH 086/433] Update qldoc and make the query more readable
---
.../CWE-555/CredentialsInPropertiesFile.qhelp | 4 +-
.../CWE-555/CredentialsInPropertiesFile.ql | 22 +++--
.../CWE-555/CredentialsInPropertiesFile.ql | 84 +++++++++++++++++++
.../CWE-555/CredentialsInPropertiesFile.qlref | 1 -
4 files changed, 101 insertions(+), 10 deletions(-)
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.ql
delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.qlref
diff --git a/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.qhelp b/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.qhelp
index afbd40685bad..0869b8862601 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.qhelp
+++ b/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.qhelp
@@ -15,7 +15,7 @@
Credentials stored in properties files should be encrypted and recycled regularly.
In a Java EE deployment scenario, utilities provided by application servers like
- keystore and password vault can be used to encrypt and manage credentials.
+ keystores and password vaults can be used to encrypt and manage credentials.
@@ -27,7 +27,7 @@
In the second example, the credentials of a LDAP and datasource properties are stored
- in the encrypted format.
+ in an encrypted format.
diff --git a/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.ql b/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.ql
index 2ab074e56d85..1e9b99060968 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.ql
@@ -9,10 +9,18 @@
* external/cwe/cwe-260
*/
+/*
+ * Note this query requires properties files to be indexed before it can produce results.
+ * If creating your own database with the CodeQL CLI, you should run
+ * `codeql database index-files --language=properties ...`
+ * If using lgtm.com, you should add `properties_files: true` to the index block of your
+ * lgtm.yml file (see https://lgtm.com/help/lgtm/java-extraction)
+ */
+
import java
import semmle.code.configfiles.ConfigFiles
-private string suspicious() {
+private string possibleSecretName() {
result = "%password%" or
result = "%passwd%" or
result = "%account%" or
@@ -23,7 +31,7 @@ private string suspicious() {
result = "%access%key%"
}
-private string nonSuspicious() {
+private string possibleEncryptedSecretName() {
result = "%hashed%" or
result = "%encrypted%" or
result = "%crypt%"
@@ -48,7 +56,8 @@ predicate isNotCleartextCredentials(string value) {
or
value.matches("ENC(%)") // Encrypted value
or
- value.toLowerCase().matches(suspicious()) // Could be message properties or fake passwords
+ // Could be a message property for UI display or fake passwords, e.g. login.password_expired=Your current password has expired.
+ value.toLowerCase().matches(possibleSecretName())
}
/**
@@ -57,8 +66,7 @@ predicate isNotCleartextCredentials(string value) {
* b) with a non-production file name
*/
predicate isNonProdCredentials(CredentialsConfig cc) {
- cc.getFile().getAbsolutePath().matches(["%dev%", "%test%", "%sample%"]) and
- not cc.getFile().getAbsolutePath().matches("%codeql%") // CodeQL test cases
+ cc.getFile().getAbsolutePath().matches(["%dev%", "%test%", "%sample%"])
}
/** The properties file with configuration key/value pairs. */
@@ -69,8 +77,8 @@ class ConfigProperties extends ConfigPair {
/** The credentials configuration property. */
class CredentialsConfig extends ConfigProperties {
CredentialsConfig() {
- this.getNameElement().getName().trim().toLowerCase().matches(suspicious()) and
- not this.getNameElement().getName().trim().toLowerCase().matches(nonSuspicious())
+ this.getNameElement().getName().trim().toLowerCase().matches(possibleSecretName()) and
+ not this.getNameElement().getName().trim().toLowerCase().matches(possibleEncryptedSecretName())
}
string getName() { result = this.getNameElement().getName().trim() }
diff --git a/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.ql b/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.ql
new file mode 100644
index 000000000000..bb7495dc4d9a
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.ql
@@ -0,0 +1,84 @@
+/**
+ * @name Cleartext Credentials in Properties File
+ * @description Finds cleartext credentials in Java properties files.
+ * @kind problem
+ * @id java/credentials-in-properties
+ * @tags security
+ * external/cwe/cwe-555
+ * external/cwe/cwe-256
+ * external/cwe/cwe-260
+ */
+
+/*
+ * Note this query requires properties files to be indexed before it can produce results.
+ * If creating your own database with the CodeQL CLI, you should run
+ * `codeql database index-files --language=properties ...`
+ * If using lgtm.com, you should add `properties_files: true` to the index block of your
+ * lgtm.yml file (see https://lgtm.com/help/lgtm/java-extraction)
+ */
+
+import java
+import semmle.code.configfiles.ConfigFiles
+
+private string possibleSecretName() {
+ result = "%password%" or
+ result = "%passwd%" or
+ result = "%account%" or
+ result = "%accnt%" or
+ result = "%credential%" or
+ result = "%token%" or
+ result = "%secret%" or
+ result = "%access%key%"
+}
+
+private string possibleEncryptedSecretName() {
+ result = "%hashed%" or
+ result = "%encrypted%" or
+ result = "%crypt%"
+}
+
+/** Holds if the value is not cleartext credentials. */
+bindingset[value]
+predicate isNotCleartextCredentials(string value) {
+ value = "" // Empty string
+ or
+ value.length() < 7 // Typical credentials are no less than 6 characters
+ or
+ value.matches("% %") // Sentences containing spaces
+ or
+ value.regexpMatch(".*[^a-zA-Z\\d]{3,}.*") // Contain repeated non-alphanumeric characters such as a fake password pass**** or ????
+ or
+ value.matches("@%") // Starts with the "@" sign
+ or
+ value.regexpMatch("\\$\\{.*\\}") // Variable placeholder ${credentials}
+ or
+ value.matches("%=") // A basic check of encrypted credentials ending with padding characters
+ or
+ value.matches("ENC(%)") // Encrypted value
+ or
+ // Could be a message property for UI display or fake passwords, e.g. login.password_expired=Your current password has expired.
+ value.toLowerCase().matches(possibleSecretName())
+}
+
+/** The properties file with configuration key/value pairs. */
+class ConfigProperties extends ConfigPair {
+ ConfigProperties() { this.getFile().getBaseName().toLowerCase().matches("%.properties") }
+}
+
+/** The credentials configuration property. */
+class CredentialsConfig extends ConfigProperties {
+ CredentialsConfig() {
+ this.getNameElement().getName().trim().toLowerCase().matches(possibleSecretName()) and
+ not this.getNameElement().getName().trim().toLowerCase().matches(possibleEncryptedSecretName())
+ }
+
+ string getName() { result = this.getNameElement().getName().trim() }
+
+ string getValue() { result = this.getValueElement().getValue().trim() }
+}
+
+from CredentialsConfig cc
+where not isNotCleartextCredentials(cc.getValue())
+select cc,
+ "Plaintext credentials " + cc.getName() + " have cleartext value " + cc.getValue() +
+ " in properties file."
diff --git a/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.qlref b/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.qlref
deleted file mode 100644
index e2536bfe8835..000000000000
--- a/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.qlref
+++ /dev/null
@@ -1 +0,0 @@
-experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.ql
\ No newline at end of file
From 7a511c5682ca1aec9368a8e1f0c2106fd0912408 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Sat, 27 Mar 2021 02:20:59 +0100
Subject: [PATCH 087/433] Python: update naming
---
python/ql/src/Security/CWE-327/InsecureProtocol.ql | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/InsecureProtocol.ql b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
index 974c36ddb0cb..37aab84795e8 100644
--- a/python/ql/src/Security/CWE-327/InsecureProtocol.ql
+++ b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
@@ -18,11 +18,12 @@ string callName(AstNode call) {
exists(Attribute a | a = call | result = callName(a.getObject()) + "." + a.getName())
}
-string originName(DataFlow::Node contextOrigin) {
- result = "call to " + callName(contextOrigin.asCfgNode().(CallNode).getFunction().getNode())
+string configName(DataFlow::Node protocolConfiguration) {
+ result =
+ "call to " + callName(protocolConfiguration.asCfgNode().(CallNode).getFunction().getNode())
or
- not contextOrigin.asCfgNode() instanceof CallNode and
- not contextOrigin instanceof ContextCreation and
+ not protocolConfiguration.asCfgNode() instanceof CallNode and
+ not protocolConfiguration instanceof ContextCreation and
result = "context modification"
}
@@ -48,4 +49,4 @@ where
specific = true
select connectionCreation,
"Insecure SSL/TLS protocol version " + insecure_version + " " + verb(specific) + " by $@ ",
- protocolConfiguration, originName(protocolConfiguration)
+ protocolConfiguration, configName(protocolConfiguration)
From 16902c2f56993f4ca29f3f05ca1ce1ac35b656be Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Sat, 27 Mar 2021 02:40:13 +0100
Subject: [PATCH 088/433] Python: handle default argument
---
python/ql/src/Security/CWE-327/Ssl.qll | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll
index 7f1415233903..928a5f74e2d3 100644
--- a/python/ql/src/Security/CWE-327/Ssl.qll
+++ b/python/ql/src/Security/CWE-327/Ssl.qll
@@ -160,6 +160,10 @@ class UnspecificSSLContextCreation extends SSLContextCreation, UnspecificContext
// These are turned off by default
// see https://docs.python.org/3/library/ssl.html#ssl-contexts
not result in ["SSLv2", "SSLv3"]
+ or
+ // The default argument is TLS and the SSL versions are turned off by default.
+ not exists(this.getProtocol()) and
+ result in ["TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"]
}
}
From 6d72b4fd39b51315cf0720c35455bb3b66447cae Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Sat, 27 Mar 2021 03:10:43 +0100
Subject: [PATCH 089/433] Python: Limit pretty printing to relevant nodes
---
.../src/Security/CWE-327/InsecureProtocol.ql | 37 ++++++++++++++++++-
1 file changed, 35 insertions(+), 2 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/InsecureProtocol.ql b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
index 37aab84795e8..19f6acf5512c 100644
--- a/python/ql/src/Security/CWE-327/InsecureProtocol.ql
+++ b/python/ql/src/Security/CWE-327/InsecureProtocol.ql
@@ -12,13 +12,46 @@
import python
import FluentApiModel
-string callName(AstNode call) {
+// Helper for pretty printer `configName`.
+// This is a consequence of missing pretty priting.
+// We do not want to evaluate our bespoke pretty printer
+// for all `DataFlow::Node`s so we define a sub class of interesting ones.
+class ProtocolConfiguration extends DataFlow::Node {
+ ProtocolConfiguration() {
+ unsafe_connection_creation_with_context(_, _, this, _)
+ or
+ unsafe_connection_creation_without_context(this, _)
+ or
+ unsafe_context_creation(this, _)
+ }
+}
+
+// Helper for pretty printer `callName`.
+// This is a consequence of missing pretty priting.
+// We do not want to evaluate our bespoke pretty printer
+// for all `AstNode`s so we define a sub class of interesting ones.
+//
+// Note that AstNode is abstract and AstNode_ is a library class, so
+// we have to extend @py_ast_node.
+class Namable extends @py_ast_node {
+ Namable() {
+ exists(ProtocolConfiguration protocolConfiguration |
+ this = protocolConfiguration.asCfgNode().(CallNode).getFunction().getNode()
+ )
+ or
+ exists(Namable attr | this = attr.(Attribute).getObject())
+ }
+
+ string toString() { result = "AstNode" }
+}
+
+string callName(Namable call) {
result = call.(Name).getId()
or
exists(Attribute a | a = call | result = callName(a.getObject()) + "." + a.getName())
}
-string configName(DataFlow::Node protocolConfiguration) {
+string configName(ProtocolConfiguration protocolConfiguration) {
result =
"call to " + callName(protocolConfiguration.asCfgNode().(CallNode).getFunction().getNode())
or
From 5ce3f9d6ff6eb740d8c6add31fd7c95fa79f6453 Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Sun, 28 Mar 2021 03:15:06 +0000
Subject: [PATCH 090/433] Update qldoc and enhance the query
---
.../CWE-555/CredentialsInPropertiesFile.qhelp | 6 +-
.../CWE-555/CredentialsInPropertiesFile.ql | 84 +++++++++++++------
.../CredentialsInPropertiesFile.expected | 10 +--
.../CWE-555/CredentialsInPropertiesFile.ql | 81 ++++++++++++------
.../security/CWE-555/PropertiesUtils.java | 57 +++++++++++++
.../security/CWE-555/messages.properties | 1 +
6 files changed, 179 insertions(+), 60 deletions(-)
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-555/PropertiesUtils.java
diff --git a/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.qhelp b/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.qhelp
index 0869b8862601..5549f188f1f4 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.qhelp
+++ b/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.qhelp
@@ -3,7 +3,7 @@
Credentials management issues occur when credentials are stored in plaintext in
- an application’s properties file. Common credentials include but are not limited
+ an application's properties file. Common credentials include but are not limited
to LDAP, mail, database, proxy account, and so on. Storing plaintext credentials
in a properties file allows anyone who can read the file access to the protected
resource. Good credentials management guidelines require that credentials never
@@ -21,12 +21,12 @@
- In the first example, the credentials of a LDAP and datasource properties are stored
+ In the first example, the credentials for the LDAP and datasource properties are stored
in cleartext in the properties file.
- In the second example, the credentials of a LDAP and datasource properties are stored
+ In the second example, the credentials for the LDAP and datasource properties are stored
in an encrypted format.
diff --git a/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.ql b/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.ql
index 1e9b99060968..8bbc0d00a462 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.ql
@@ -14,28 +14,22 @@
* If creating your own database with the CodeQL CLI, you should run
* `codeql database index-files --language=properties ...`
* If using lgtm.com, you should add `properties_files: true` to the index block of your
- * lgtm.yml file (see https://lgtm.com/help/lgtm/java-extraction)
+ * lgtm.yml file (see https://lgtm.com/help/lgtm/java-extraction#customizing-index)
*/
import java
import semmle.code.configfiles.ConfigFiles
+import semmle.code.java.dataflow.FlowSources
private string possibleSecretName() {
- result = "%password%" or
- result = "%passwd%" or
- result = "%account%" or
- result = "%accnt%" or
- result = "%credential%" or
- result = "%token%" or
- result = "%secret%" or
- result = "%access%key%"
+ result =
+ [
+ "%password%", "%passwd%", "%account%", "%accnt%", "%credential%", "%token%", "%secret%",
+ "%access%key%"
+ ]
}
-private string possibleEncryptedSecretName() {
- result = "%hashed%" or
- result = "%encrypted%" or
- result = "%crypt%"
-}
+private string possibleEncryptedSecretName() { result = ["%hashed%", "%encrypted%", "%crypt%"] }
/** Holds if the value is not cleartext credentials. */
bindingset[value]
@@ -69,27 +63,63 @@ predicate isNonProdCredentials(CredentialsConfig cc) {
cc.getFile().getAbsolutePath().matches(["%dev%", "%test%", "%sample%"])
}
-/** The properties file with configuration key/value pairs. */
-class ConfigProperties extends ConfigPair {
- ConfigProperties() { this.getFile().getBaseName().toLowerCase().matches("%.properties") }
-}
-
/** The credentials configuration property. */
-class CredentialsConfig extends ConfigProperties {
+class CredentialsConfig extends ConfigPair {
CredentialsConfig() {
this.getNameElement().getName().trim().toLowerCase().matches(possibleSecretName()) and
- not this.getNameElement().getName().trim().toLowerCase().matches(possibleEncryptedSecretName())
+ not this.getNameElement().getName().trim().toLowerCase().matches(possibleEncryptedSecretName()) and
+ not isNotCleartextCredentials(this.getValueElement().getValue().trim())
}
string getName() { result = this.getNameElement().getName().trim() }
string getValue() { result = this.getValueElement().getValue().trim() }
+
+ /** Returns a description of this vulnerability. */
+ string getConfigDesc() {
+ exists(
+ LoadCredentialsConfiguration cc, DataFlow::Node source, DataFlow::Node sink, MethodAccess ma
+ |
+ this.getName() = source.asExpr().(CompileTimeConstantExpr).getStringValue() and
+ cc.hasFlow(source, sink) and
+ ma.getArgument(0) = sink.asExpr() and
+ result = "Plaintext credentials " + this.getName() + " are loaded in " + ma
+ )
+ or
+ not exists(LoadCredentialsConfiguration cc, DataFlow::Node source, DataFlow::Node sink |
+ this.getName() = source.asExpr().(CompileTimeConstantExpr).getStringValue() and
+ cc.hasFlow(source, sink)
+ ) and
+ result =
+ "Plaintext credentials " + this.getName() + " have cleartext value " + this.getValue() +
+ " in properties file"
+ }
+}
+
+/**
+ * A dataflow configuration tracking flow of a method that loads a credentials property.
+ */
+class LoadCredentialsConfiguration extends DataFlow::Configuration {
+ LoadCredentialsConfiguration() { this = "LoadCredentialsConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) {
+ exists(CredentialsConfig cc |
+ source.asExpr().(CompileTimeConstantExpr).getStringValue() = cc.getName()
+ )
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ sink.asExpr() =
+ any(MethodAccess ma |
+ ma.getMethod()
+ .getDeclaringType()
+ .getASupertype*()
+ .hasQualifiedName("java.util", "Properties") and
+ ma.getMethod().getName() = "getProperty"
+ ).getArgument(0)
+ }
}
from CredentialsConfig cc
-where
- not isNotCleartextCredentials(cc.getValue()) and
- not isNonProdCredentials(cc)
-select cc,
- "Plaintext credentials " + cc.getName() + " have cleartext value " + cc.getValue() +
- " in properties file."
+where not isNonProdCredentials(cc)
+select cc, cc.getConfigDesc()
diff --git a/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.expected b/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.expected
index 0ce339139324..1bdc004a446f 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.expected
@@ -1,5 +1,5 @@
-| configuration.properties:6:1:6:25 | ldap.password=mysecpass | Plaintext credentials ldap.password have cleartext value mysecpass in properties file. |
-| configuration.properties:18:1:18:35 | datasource1.password=Passw0rd@123 | Plaintext credentials datasource1.password have cleartext value Passw0rd@123 in properties file. |
-| configuration.properties:25:1:25:31 | mail.password=MysecPWxWa@1993 | Plaintext credentials mail.password have cleartext value MysecPWxWa@1993 in properties file. |
-| configuration.properties:33:1:33:50 | com.example.aws.s3.access_key=AKMAMQPBYMCD6YSAYCBA | Plaintext credentials com.example.aws.s3.access_key have cleartext value AKMAMQPBYMCD6YSAYCBA in properties file. |
-| configuration.properties:34:1:34:70 | com.example.aws.s3.secret_key=8lMPSfWzZq+wcWtck5+QPLOJDZzE783pS09/IO3k | Plaintext credentials com.example.aws.s3.secret_key have cleartext value 8lMPSfWzZq+wcWtck5+QPLOJDZzE783pS09/IO3k in properties file. |
+| configuration.properties:6:1:6:25 | ldap.password=mysecpass | Plaintext credentials ldap.password are loaded in getProperty(...) |
+| configuration.properties:18:1:18:35 | datasource1.password=Passw0rd@123 | Plaintext credentials datasource1.password are loaded in getProperty(...) |
+| configuration.properties:25:1:25:31 | mail.password=MysecPWxWa@1993 | Plaintext credentials mail.password are loaded in getProperty(...) |
+| configuration.properties:33:1:33:50 | com.example.aws.s3.access_key=AKMAMQPBYMCD6YSAYCBA | Plaintext credentials com.example.aws.s3.access_key are loaded in getProperty(...) |
+| configuration.properties:34:1:34:70 | com.example.aws.s3.secret_key=8lMPSfWzZq+wcWtck5+QPLOJDZzE783pS09/IO3k | Plaintext credentials com.example.aws.s3.secret_key are loaded in getProperty(...) |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.ql b/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.ql
index bb7495dc4d9a..b6890e4ac9fc 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.ql
+++ b/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.ql
@@ -14,28 +14,22 @@
* If creating your own database with the CodeQL CLI, you should run
* `codeql database index-files --language=properties ...`
* If using lgtm.com, you should add `properties_files: true` to the index block of your
- * lgtm.yml file (see https://lgtm.com/help/lgtm/java-extraction)
+ * lgtm.yml file (see https://lgtm.com/help/lgtm/java-extraction#customizing-index)
*/
import java
import semmle.code.configfiles.ConfigFiles
+import semmle.code.java.dataflow.FlowSources
private string possibleSecretName() {
- result = "%password%" or
- result = "%passwd%" or
- result = "%account%" or
- result = "%accnt%" or
- result = "%credential%" or
- result = "%token%" or
- result = "%secret%" or
- result = "%access%key%"
+ result =
+ [
+ "%password%", "%passwd%", "%account%", "%accnt%", "%credential%", "%token%", "%secret%",
+ "%access%key%"
+ ]
}
-private string possibleEncryptedSecretName() {
- result = "%hashed%" or
- result = "%encrypted%" or
- result = "%crypt%"
-}
+private string possibleEncryptedSecretName() { result = ["%hashed%", "%encrypted%", "%crypt%"] }
/** Holds if the value is not cleartext credentials. */
bindingset[value]
@@ -60,25 +54,62 @@ predicate isNotCleartextCredentials(string value) {
value.toLowerCase().matches(possibleSecretName())
}
-/** The properties file with configuration key/value pairs. */
-class ConfigProperties extends ConfigPair {
- ConfigProperties() { this.getFile().getBaseName().toLowerCase().matches("%.properties") }
-}
-
/** The credentials configuration property. */
-class CredentialsConfig extends ConfigProperties {
+class CredentialsConfig extends ConfigPair {
CredentialsConfig() {
this.getNameElement().getName().trim().toLowerCase().matches(possibleSecretName()) and
- not this.getNameElement().getName().trim().toLowerCase().matches(possibleEncryptedSecretName())
+ not this.getNameElement().getName().trim().toLowerCase().matches(possibleEncryptedSecretName()) and
+ not isNotCleartextCredentials(this.getValueElement().getValue().trim())
}
string getName() { result = this.getNameElement().getName().trim() }
string getValue() { result = this.getValueElement().getValue().trim() }
+
+ /** Returns a description of this vulnerability. */
+ string getConfigDesc() {
+ exists(
+ LoadCredentialsConfiguration cc, DataFlow::Node source, DataFlow::Node sink, MethodAccess ma
+ |
+ this.getName() = source.asExpr().(CompileTimeConstantExpr).getStringValue() and
+ cc.hasFlow(source, sink) and
+ ma.getArgument(0) = sink.asExpr() and
+ result = "Plaintext credentials " + this.getName() + " are loaded in " + ma
+ )
+ or
+ not exists(LoadCredentialsConfiguration cc, DataFlow::Node source, DataFlow::Node sink |
+ this.getName() = source.asExpr().(CompileTimeConstantExpr).getStringValue() and
+ cc.hasFlow(source, sink)
+ ) and
+ result =
+ "Plaintext credentials " + this.getName() + " have cleartext value " + this.getValue() +
+ " in properties file"
+ }
+}
+
+/**
+ * A dataflow configuration tracking flow of a method that loads a credentials property.
+ */
+class LoadCredentialsConfiguration extends DataFlow::Configuration {
+ LoadCredentialsConfiguration() { this = "LoadCredentialsConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) {
+ exists(CredentialsConfig cc |
+ source.asExpr().(CompileTimeConstantExpr).getStringValue() = cc.getName()
+ )
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ sink.asExpr() =
+ any(MethodAccess ma |
+ ma.getMethod()
+ .getDeclaringType()
+ .getASupertype*()
+ .hasQualifiedName("java.util", "Properties") and
+ ma.getMethod().getName() = "getProperty"
+ ).getArgument(0)
+ }
}
from CredentialsConfig cc
-where not isNotCleartextCredentials(cc.getValue())
-select cc,
- "Plaintext credentials " + cc.getName() + " have cleartext value " + cc.getValue() +
- " in properties file."
+select cc, cc.getConfigDesc()
diff --git a/java/ql/test/experimental/query-tests/security/CWE-555/PropertiesUtils.java b/java/ql/test/experimental/query-tests/security/CWE-555/PropertiesUtils.java
new file mode 100644
index 000000000000..b54995a9967e
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-555/PropertiesUtils.java
@@ -0,0 +1,57 @@
+import java.io.IOException;
+import java.util.Properties;
+
+public class PropertiesUtils {
+ /* Properties declaration. */
+ private static Properties properties;
+
+ /** Static block to initializing the properties. */
+ static {
+ properties = new Properties();
+ try {
+ properties.load(PropertiesUtils.class.getClassLoader().getResourceAsStream("configuration.properties"));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /** Returns the LDAP DN property value. */
+ public static String getLdapDN() {
+ return properties.getProperty("ldap.loginDN");
+ }
+
+ /** Returns the LDAP password property value. */
+ public static String getLdapPassword() {
+ return properties.getProperty("ldap.password");
+ }
+
+ /** Returns the SQL Server username property value. */
+ public static String getMSDataSourceUserName() {
+ return properties.getProperty("datasource1.username");
+ }
+
+ /** Returns the SQL Server password property value. */
+ public static String getMSDataSourcePassword() {
+ return properties.getProperty("datasource1.password");
+ }
+
+ /** Returns the mail account property value. */
+ public static String getMailUserName() {
+ return properties.getProperty("mail.username");
+ }
+
+ /** Returns the mail password property value. */
+ public static String getMailPassword() {
+ return properties.getProperty("mail.password");
+ }
+
+ /** Returns the AWS Access Key property value. */
+ public static String getAWSAccessKey() {
+ return properties.getProperty("com.example.aws.s3.access_key");
+ }
+
+ /** Returns the AWS Secret Key property value. */
+ public static String getAWSSecretKey() {
+ return properties.getProperty("com.example.aws.s3.secret_key");
+ }
+}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-555/messages.properties b/java/ql/test/experimental/query-tests/security/CWE-555/messages.properties
index fac63ec23e85..27a244c60716 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-555/messages.properties
+++ b/java/ql/test/experimental/query-tests/security/CWE-555/messages.properties
@@ -1,3 +1,4 @@
+# GOOD: UI display messages; not credentials
prompt.username=Username
prompt.password=Password
From 093c63ea3b15cd33b08b8bd1d1058ffc3c6dd075 Mon Sep 17 00:00:00 2001
From: ihsinme
Date: Sun, 28 Mar 2021 23:42:36 +0300
Subject: [PATCH 091/433] Update
OperatorPrecedenceLogicErrorWhenUseBoolType.expected
---
.../tests/OperatorPrecedenceLogicErrorWhenUseBoolType.expected | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/OperatorPrecedenceLogicErrorWhenUseBoolType.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/OperatorPrecedenceLogicErrorWhenUseBoolType.expected
index 76062fc360a3..1209c7e78307 100644
--- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/OperatorPrecedenceLogicErrorWhenUseBoolType.expected
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/OperatorPrecedenceLogicErrorWhenUseBoolType.expected
@@ -1,4 +1,4 @@
-| test.cpp:10:3:10:10 | ... = ... | this expression needs attention |
+| test.cpp:10:8:10:10 | - ... | this expression needs attention |
| test.cpp:12:3:12:6 | ... ++ | this expression needs attention |
| test.cpp:13:3:13:6 | ++ ... | this expression needs attention |
| test.cpp:14:6:14:21 | ... = ... | this expression needs attention |
From 3f215d0954ea56c34f90837e259553be82675f76 Mon Sep 17 00:00:00 2001
From: ihsinme
Date: Sun, 28 Mar 2021 23:43:22 +0300
Subject: [PATCH 092/433] Update OperatorPrecedenceLogicErrorWhenUseBoolType.ql
---
...ratorPrecedenceLogicErrorWhenUseBoolType.ql | 18 ++++++++++++------
1 file changed, 12 insertions(+), 6 deletions(-)
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBoolType.ql b/cpp/ql/src/experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBoolType.ql
index 034df703bc30..4f30f112eb07 100644
--- a/cpp/ql/src/experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBoolType.ql
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBoolType.ql
@@ -13,17 +13,17 @@
*/
import cpp
-import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
+import semmle.code.cpp.valuenumbering.HashCons
/** Holds if `exp` increments a boolean value. */
-predicate incrementBoolType(Expr exp) {
- exp.(IncrementOperation).getOperand().getType() instanceof BoolType
+predicate incrementBoolType(IncrementOperation exp) {
+ exp.getOperand().getType() instanceof BoolType
}
/** Holds if `exp` applies the unary minus operator to a boolean type. */
-predicate revertSignBoolType(Expr exp) {
- exp.(AssignExpr).getRValue().(UnaryMinusExpr).getAnOperand().getType() instanceof BoolType and
- exp.(AssignExpr).getLValue().getType() instanceof BoolType
+predicate revertSignBoolType(UnaryMinusExpr exp) {
+ exp.getAnOperand().getType() instanceof BoolType and
+ exp.getFullyConverted().getType() instanceof BoolType
}
/** Holds, if this is an expression, uses comparison and assignment outside of execution precedence. */
@@ -33,6 +33,12 @@ predicate assignBoolType(Expr exp) {
exp.isCondition() and
not co.isParenthesised() and
not exp.(AssignExpr).getLValue().getType() instanceof BoolType and
+ not exists(Expr exbl |
+ hashCons(exbl.(AssignExpr).getLValue()) = hashCons(exp.(AssignExpr).getLValue()) and
+ not exbl.isCondition() and
+ exbl.(AssignExpr).getRValue().getType() instanceof BoolType and
+ exbl.(AssignExpr).getLValue().getType() = exp.(AssignExpr).getLValue().getType()
+ ) and
co.getLeftOperand() instanceof FunctionCall and
not co.getRightOperand().getType() instanceof BoolType and
not co.getRightOperand().getValue() = "0" and
From 0775d35591511db2003b3b7453c6bfc2284ba250 Mon Sep 17 00:00:00 2001
From: haby0
Date: Mon, 29 Mar 2021 12:02:37 +0800
Subject: [PATCH 093/433] update VerificationMethodFlowConfig, add if test
---
.../Security/CWE/CWE-352/JsonpInjection.java | 35 ++--
.../Security/CWE/CWE-352/JsonpInjection.qhelp | 2 +-
.../Security/CWE/CWE-352/JsonpInjection.ql | 27 ++--
.../CWE/CWE-352/JsonpInjectionLib.qll | 47 ++++--
.../JsonpController.java | 35 ++--
.../JsonpInjection.expected | 138 ++++++++--------
.../JsonpController.java | 37 +++--
.../JsonpInjection.expected | 147 ++++++++---------
.../JsonpController.java | 37 +++--
.../JsonpInjection.expected | 150 +++++++++---------
10 files changed, 362 insertions(+), 293 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.java b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.java
index 7f479a8c0230..8e2a0c9005ff 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.java
+++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.java
@@ -26,9 +26,6 @@ public class JsonpInjection {
hashMap.put("password","123456");
}
- private String name = null;
-
-
@GetMapping(value = "jsonp1")
@ResponseBody
public String bad1(HttpServletRequest request) {
@@ -77,7 +74,6 @@ public void bad5(HttpServletRequest request,
PrintWriter pw = null;
Gson gson = new Gson();
String result = gson.toJson(hashMap);
-
String resultStr = null;
pw = response.getWriter();
resultStr = jsonpCallback + "(" + result + ")";
@@ -109,13 +105,25 @@ public String bad7(HttpServletRequest request) {
return resultStr;
}
-
@GetMapping(value = "jsonp8")
@ResponseBody
- public String good1(HttpServletRequest request) {
+ public String bad8(HttpServletRequest request) {
String resultStr = null;
String token = request.getParameter("token");
- if (verifToken(token)){
+ boolean result = verifToken(token); //Just check.
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
+
+
+ @GetMapping(value = "jsonp9")
+ @ResponseBody
+ public String good1(HttpServletRequest request) {
+ String resultStr = null;
+ String referer = request.getParameter("referer");
+ if (verifReferer(referer)){
String jsonpCallback = request.getParameter("jsonpCallback");
String jsonStr = getJsonStr(hashMap);
resultStr = jsonpCallback + "(" + jsonStr + ")";
@@ -125,7 +133,7 @@ public String good1(HttpServletRequest request) {
}
- @GetMapping(value = "jsonp9")
+ @GetMapping(value = "jsonp10")
@ResponseBody
public String good2(HttpServletRequest request) {
String resultStr = null;
@@ -140,7 +148,7 @@ public String good2(HttpServletRequest request) {
return resultStr;
}
- @RequestMapping(value = "jsonp10")
+ @RequestMapping(value = "jsonp11")
@ResponseBody
public String good3(HttpServletRequest request) {
JSONObject parameterObj = readToJSONObect(request);
@@ -151,7 +159,7 @@ public String good3(HttpServletRequest request) {
return resultStr;
}
- @RequestMapping(value = "jsonp11")
+ @RequestMapping(value = "jsonp12")
@ResponseBody
public String good4(@RequestParam("file") MultipartFile file,HttpServletRequest request) {
if(null == file){
@@ -200,4 +208,11 @@ public static boolean verifToken(String token){
}
return true;
}
+
+ public static boolean verifReferer(String str){
+ if (str != "xxxx"){
+ return false;
+ }
+ return true;
+ }
}
\ No newline at end of file
diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp
index 93c167d6c2ca..2712f30a3f2b 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp
+++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp
@@ -14,7 +14,7 @@ When there is a cross-domain problem, the problem of sensitive information leaka
-The following examples show the bad case and the good case respectively. Bad case, such as bad1
to bad7
,
+
The following examples show the bad case and the good case respectively. Bad case, such as bad1
to bad8
,
will cause information leakage problems when there are cross-domain problems. In a good case, for example, in the good1
method and the good2
method, use the verifToken
method to do the random token
Verification can
solve the problem of information leakage caused by cross-domain.
diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql
index eb4fe4e5b66a..6e333d839930 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql
@@ -18,20 +18,18 @@ import DataFlow::PathGraph
/** Determine whether there is a verification method for the remote streaming source data flow path method. */
predicate existsFilterVerificationMethod() {
- exists(MethodAccess ma, Node existsNode, Method m |
- ma.getMethod() instanceof VerificationMethodClass and
- existsNode.asExpr() = ma and
- m = getACallingCallableOrSelf(existsNode.getEnclosingCallable()) and
+ exists(DataFlow::Node source, DataFlow::Node sink, VerificationMethodFlowConfig vmfc, Method m |
+ vmfc.hasFlow(source, sink) and
+ m = getACallingCallableOrSelf(source.getEnclosingCallable()) and
isDoFilterMethod(m)
)
}
/** Determine whether there is a verification method for the remote streaming source data flow path method. */
predicate existsServletVerificationMethod(Node checkNode) {
- exists(MethodAccess ma, Node existsNode |
- ma.getMethod() instanceof VerificationMethodClass and
- existsNode.asExpr() = ma and
- getACallingCallableOrSelf(existsNode.getEnclosingCallable()) =
+ exists(DataFlow::Node source, DataFlow::Node sink, VerificationMethodFlowConfig vmfc |
+ vmfc.hasFlow(source, sink) and
+ getACallingCallableOrSelf(source.getEnclosingCallable()) =
getACallingCallableOrSelf(checkNode.getEnclosingCallable())
)
}
@@ -40,13 +38,14 @@ predicate existsServletVerificationMethod(Node checkNode) {
class RequestResponseFlowConfig extends TaintTracking::Configuration {
RequestResponseFlowConfig() { this = "RequestResponseFlowConfig" }
- override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
-
- override predicate isSink(DataFlow::Node sink) { sink instanceof XssSink }
+ override predicate isSource(DataFlow::Node source) {
+ source instanceof RemoteFlowSource and
+ getACallingCallableOrSelf(source.getEnclosingCallable()) instanceof RequestGetMethod
+ }
- /** Eliminate the method of calling the node is not the get method. */
- override predicate isSanitizer(DataFlow::Node node) {
- not getACallingCallableOrSelf(node.getEnclosingCallable()) instanceof RequestGetMethod
+ override predicate isSink(DataFlow::Node sink) {
+ sink instanceof XssSink and
+ getACallingCallableOrSelf(sink.getEnclosingCallable()) instanceof RequestGetMethod
}
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll
index d0e00bcb634f..bf90926f72f0 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll
+++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll
@@ -3,30 +3,47 @@ import DataFlow
import JsonStringLib
import semmle.code.java.security.XSS
import semmle.code.java.dataflow.DataFlow
+import semmle.code.java.dataflow.DataFlow3
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.frameworks.spring.SpringController
-/** Taint-tracking configuration tracing flow from untrusted inputs to verification of remote user input. */
-class VerificationMethodFlowConfig extends TaintTracking::Configuration {
- VerificationMethodFlowConfig() { this = "VerificationMethodFlowConfig" }
+/** A data flow configuration is tracing flow from the access to the authentication method of token/auth/referer/origin to if condition. */
+class VerificationMethodToIfFlowConfig extends DataFlow3::Configuration {
+ VerificationMethodToIfFlowConfig() { this = "VerificationMethodToIfFlowConfig" }
- override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
+ override predicate isSource(DataFlow::Node src) {
+ exists(MethodAccess ma, BarrierGuard bg | ma = bg |
+ (
+ ma.getMethod().getAParameter().getName().regexpMatch("(?i).*(token|auth|referer|origin).*")
+ or
+ ma.getMethod().getName().regexpMatch("(?i).*(token|auth|referer|origin).*")
+ ) and
+ ma = src.asExpr()
+ )
+ }
override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma |
- ma.getMethod().getAParameter().getName().regexpMatch("(?i).*(token|auth|referer|origin).*") and
- ma.getAnArgument() = sink.asExpr()
- )
+ exists(IfStmt is | is.getCondition() = sink.asExpr())
}
}
-/** The parameter names of this method are token/auth/referer/origin. */
-class VerificationMethodClass extends Method {
- VerificationMethodClass() {
- exists(MethodAccess ma, VerificationMethodFlowConfig vmfc, Node node |
- this = ma.getMethod() and
- node.asExpr() = ma.getAnArgument() and
- vmfc.hasFlowTo(node)
+/** Taint-tracking configuration tracing flow from untrusted inputs to verification of remote user input. */
+class VerificationMethodFlowConfig extends TaintTracking2::Configuration {
+ VerificationMethodFlowConfig() { this = "VerificationMethodFlowConfig" }
+
+ override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess ma, BarrierGuard bg, int i, VerificationMethodToIfFlowConfig vmtifc |
+ ma = bg
+ |
+ (
+ ma.getMethod().getParameter(i).getName().regexpMatch("(?i).*(token|auth|referer|origin).*")
+ or
+ ma.getMethod().getName().regexpMatch("(?i).*(token|auth|referer|origin).*")
+ ) and
+ ma.getArgument(i) = sink.asExpr() and
+ vmtifc.hasFlow(exprNode(ma), _)
)
}
}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpController.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpController.java
index e5b5e70a38d7..e875da2f6995 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpController.java
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpController.java
@@ -26,9 +26,6 @@ public class JsonpController {
hashMap.put("password","123456");
}
- private String name = null;
-
-
@GetMapping(value = "jsonp1")
@ResponseBody
public String bad1(HttpServletRequest request) {
@@ -77,7 +74,6 @@ public void bad5(HttpServletRequest request,
PrintWriter pw = null;
Gson gson = new Gson();
String result = gson.toJson(hashMap);
-
String resultStr = null;
pw = response.getWriter();
resultStr = jsonpCallback + "(" + result + ")";
@@ -109,13 +105,25 @@ public String bad7(HttpServletRequest request) {
return resultStr;
}
-
@GetMapping(value = "jsonp8")
@ResponseBody
- public String good1(HttpServletRequest request) {
+ public String bad8(HttpServletRequest request) {
String resultStr = null;
String token = request.getParameter("token");
- if (verifToken(token)){
+ boolean result = verifToken(token); //Just check.
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
+
+
+ @GetMapping(value = "jsonp9")
+ @ResponseBody
+ public String good1(HttpServletRequest request) {
+ String resultStr = null;
+ String referer = request.getParameter("referer");
+ if (verifReferer(referer)){
String jsonpCallback = request.getParameter("jsonpCallback");
String jsonStr = getJsonStr(hashMap);
resultStr = jsonpCallback + "(" + jsonStr + ")";
@@ -125,7 +133,7 @@ public String good1(HttpServletRequest request) {
}
- @GetMapping(value = "jsonp9")
+ @GetMapping(value = "jsonp10")
@ResponseBody
public String good2(HttpServletRequest request) {
String resultStr = null;
@@ -140,7 +148,7 @@ public String good2(HttpServletRequest request) {
return resultStr;
}
- @RequestMapping(value = "jsonp10")
+ @RequestMapping(value = "jsonp11")
@ResponseBody
public String good3(HttpServletRequest request) {
JSONObject parameterObj = readToJSONObect(request);
@@ -151,7 +159,7 @@ public String good3(HttpServletRequest request) {
return resultStr;
}
- @RequestMapping(value = "jsonp11")
+ @RequestMapping(value = "jsonp12")
@ResponseBody
public String good4(@RequestParam("file") MultipartFile file,HttpServletRequest request) {
if(null == file){
@@ -200,4 +208,11 @@ public static boolean verifToken(String token){
}
return true;
}
+
+ public static boolean verifReferer(String str){
+ if (str != "xxxx"){
+ return false;
+ }
+ return true;
+ }
}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjection.expected
index 501565f2b4ec..3da805c6a698 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjection.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjection.expected
@@ -1,80 +1,76 @@
edges
-| JsonpController.java:36:32:36:68 | getParameter(...) : String | JsonpController.java:40:16:40:24 | resultStr |
-| JsonpController.java:39:21:39:54 | ... + ... : String | JsonpController.java:40:16:40:24 | resultStr |
-| JsonpController.java:47:32:47:68 | getParameter(...) : String | JsonpController.java:49:16:49:24 | resultStr |
-| JsonpController.java:48:21:48:80 | ... + ... : String | JsonpController.java:49:16:49:24 | resultStr |
-| JsonpController.java:56:32:56:68 | getParameter(...) : String | JsonpController.java:59:16:59:24 | resultStr |
-| JsonpController.java:58:21:58:55 | ... + ... : String | JsonpController.java:59:16:59:24 | resultStr |
-| JsonpController.java:66:32:66:68 | getParameter(...) : String | JsonpController.java:69:16:69:24 | resultStr |
-| JsonpController.java:68:21:68:54 | ... + ... : String | JsonpController.java:69:16:69:24 | resultStr |
-| JsonpController.java:76:32:76:68 | getParameter(...) : String | JsonpController.java:84:20:84:28 | resultStr |
-| JsonpController.java:83:21:83:54 | ... + ... : String | JsonpController.java:84:20:84:28 | resultStr |
-| JsonpController.java:91:32:91:68 | getParameter(...) : String | JsonpController.java:98:20:98:28 | resultStr |
-| JsonpController.java:97:21:97:54 | ... + ... : String | JsonpController.java:98:20:98:28 | resultStr |
-| JsonpController.java:105:32:105:68 | getParameter(...) : String | JsonpController.java:109:16:109:24 | resultStr |
-| JsonpController.java:108:21:108:54 | ... + ... : String | JsonpController.java:109:16:109:24 | resultStr |
-| JsonpController.java:117:24:117:52 | getParameter(...) : String | JsonpController.java:118:24:118:28 | token |
-| JsonpController.java:119:36:119:72 | getParameter(...) : String | JsonpController.java:122:20:122:28 | resultStr |
-| JsonpController.java:121:25:121:59 | ... + ... : String | JsonpController.java:122:20:122:28 | resultStr |
-| JsonpController.java:132:24:132:52 | getParameter(...) : String | JsonpController.java:133:37:133:41 | token |
-| JsonpController.java:137:32:137:68 | getParameter(...) : String | JsonpController.java:140:16:140:24 | resultStr |
-| JsonpController.java:139:21:139:55 | ... + ... : String | JsonpController.java:140:16:140:24 | resultStr |
-| JsonpController.java:150:21:150:54 | ... + ... : String | JsonpController.java:151:16:151:24 | resultStr |
-| JsonpController.java:165:21:165:54 | ... + ... : String | JsonpController.java:166:16:166:24 | resultStr |
+| JsonpController.java:33:32:33:68 | getParameter(...) : String | JsonpController.java:37:16:37:24 | resultStr |
+| JsonpController.java:36:21:36:54 | ... + ... : String | JsonpController.java:37:16:37:24 | resultStr |
+| JsonpController.java:44:32:44:68 | getParameter(...) : String | JsonpController.java:46:16:46:24 | resultStr |
+| JsonpController.java:45:21:45:80 | ... + ... : String | JsonpController.java:46:16:46:24 | resultStr |
+| JsonpController.java:53:32:53:68 | getParameter(...) : String | JsonpController.java:56:16:56:24 | resultStr |
+| JsonpController.java:55:21:55:55 | ... + ... : String | JsonpController.java:56:16:56:24 | resultStr |
+| JsonpController.java:63:32:63:68 | getParameter(...) : String | JsonpController.java:66:16:66:24 | resultStr |
+| JsonpController.java:65:21:65:54 | ... + ... : String | JsonpController.java:66:16:66:24 | resultStr |
+| JsonpController.java:73:32:73:68 | getParameter(...) : String | JsonpController.java:80:20:80:28 | resultStr |
+| JsonpController.java:79:21:79:54 | ... + ... : String | JsonpController.java:80:20:80:28 | resultStr |
+| JsonpController.java:87:32:87:68 | getParameter(...) : String | JsonpController.java:94:20:94:28 | resultStr |
+| JsonpController.java:93:21:93:54 | ... + ... : String | JsonpController.java:94:20:94:28 | resultStr |
+| JsonpController.java:101:32:101:68 | getParameter(...) : String | JsonpController.java:105:16:105:24 | resultStr |
+| JsonpController.java:104:21:104:54 | ... + ... : String | JsonpController.java:105:16:105:24 | resultStr |
+| JsonpController.java:114:32:114:68 | getParameter(...) : String | JsonpController.java:117:16:117:24 | resultStr |
+| JsonpController.java:116:21:116:55 | ... + ... : String | JsonpController.java:117:16:117:24 | resultStr |
+| JsonpController.java:127:36:127:72 | getParameter(...) : String | JsonpController.java:130:20:130:28 | resultStr |
+| JsonpController.java:129:25:129:59 | ... + ... : String | JsonpController.java:130:20:130:28 | resultStr |
+| JsonpController.java:145:32:145:68 | getParameter(...) : String | JsonpController.java:148:16:148:24 | resultStr |
+| JsonpController.java:147:21:147:55 | ... + ... : String | JsonpController.java:148:16:148:24 | resultStr |
+| JsonpController.java:158:21:158:54 | ... + ... : String | JsonpController.java:159:16:159:24 | resultStr |
+| JsonpController.java:173:21:173:54 | ... + ... : String | JsonpController.java:174:16:174:24 | resultStr |
| JsonpInjectionServlet1.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet1.java:45:24:45:32 | resultStr |
-| JsonpInjectionServlet1.java:36:26:36:49 | getHeader(...) : String | JsonpInjectionServlet1.java:38:39:38:45 | referer |
| JsonpInjectionServlet1.java:44:25:44:62 | ... + ... : String | JsonpInjectionServlet1.java:45:24:45:32 | resultStr |
| JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr |
| JsonpInjectionServlet2.java:38:21:38:54 | ... + ... : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr |
-| RefererFilter.java:22:26:22:53 | getHeader(...) : String | RefererFilter.java:23:39:23:45 | refefer |
nodes
-| JsonpController.java:36:32:36:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:39:21:39:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:40:16:40:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:40:16:40:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:47:32:47:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:48:21:48:80 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:49:16:49:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:49:16:49:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:56:32:56:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:58:21:58:55 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:59:16:59:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:59:16:59:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:66:32:66:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:68:21:68:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:69:16:69:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:69:16:69:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:76:32:76:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:83:21:83:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:84:20:84:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:84:20:84:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:91:32:91:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:97:21:97:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:98:20:98:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:98:20:98:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:105:32:105:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:108:21:108:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:109:16:109:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:109:16:109:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:117:24:117:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:118:24:118:28 | token | semmle.label | token |
-| JsonpController.java:119:36:119:72 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:121:25:121:59 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:122:20:122:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:122:20:122:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:132:24:132:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:133:37:133:41 | token | semmle.label | token |
-| JsonpController.java:137:32:137:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:139:21:139:55 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:140:16:140:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:140:16:140:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:150:21:150:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:151:16:151:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:165:21:165:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:166:16:166:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:33:32:33:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:36:21:36:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:37:16:37:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:37:16:37:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:44:32:44:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:45:21:45:80 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:46:16:46:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:46:16:46:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:53:32:53:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:55:21:55:55 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:56:16:56:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:56:16:56:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:63:32:63:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:65:21:65:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:66:16:66:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:66:16:66:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:73:32:73:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:79:21:79:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:80:20:80:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:80:20:80:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:87:32:87:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:93:21:93:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:94:20:94:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:94:20:94:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:101:32:101:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:104:21:104:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:105:16:105:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:105:16:105:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:114:32:114:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:116:21:116:55 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:117:16:117:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:117:16:117:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:127:36:127:72 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:129:25:129:59 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:130:20:130:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:130:20:130:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:145:32:145:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:147:21:147:55 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:148:16:148:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:148:16:148:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:158:21:158:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:159:16:159:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:173:21:173:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:174:16:174:24 | resultStr | semmle.label | resultStr |
| JsonpInjectionServlet1.java:31:32:31:64 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpInjectionServlet1.java:36:26:36:49 | getHeader(...) : String | semmle.label | getHeader(...) : String |
-| JsonpInjectionServlet1.java:38:39:38:45 | referer | semmle.label | referer |
| JsonpInjectionServlet1.java:44:25:44:62 | ... + ... : String | semmle.label | ... + ... : String |
| JsonpInjectionServlet1.java:45:24:45:32 | resultStr | semmle.label | resultStr |
| JsonpInjectionServlet1.java:45:24:45:32 | resultStr | semmle.label | resultStr |
@@ -82,6 +78,4 @@ nodes
| JsonpInjectionServlet2.java:38:21:38:54 | ... + ... : String | semmle.label | ... + ... : String |
| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | semmle.label | resultStr |
| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | semmle.label | resultStr |
-| RefererFilter.java:22:26:22:53 | getHeader(...) : String | semmle.label | getHeader(...) : String |
-| RefererFilter.java:23:39:23:45 | refefer | semmle.label | refefer |
#select
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpController.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpController.java
index e5b5e70a38d7..4c60b356cfb4 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpController.java
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpController.java
@@ -26,9 +26,6 @@ public class JsonpController {
hashMap.put("password","123456");
}
- private String name = null;
-
-
@GetMapping(value = "jsonp1")
@ResponseBody
public String bad1(HttpServletRequest request) {
@@ -77,7 +74,6 @@ public void bad5(HttpServletRequest request,
PrintWriter pw = null;
Gson gson = new Gson();
String result = gson.toJson(hashMap);
-
String resultStr = null;
pw = response.getWriter();
resultStr = jsonpCallback + "(" + result + ")";
@@ -109,13 +105,25 @@ public String bad7(HttpServletRequest request) {
return resultStr;
}
-
@GetMapping(value = "jsonp8")
@ResponseBody
- public String good1(HttpServletRequest request) {
+ public String bad8(HttpServletRequest request) {
String resultStr = null;
String token = request.getParameter("token");
- if (verifToken(token)){
+ boolean result = verifToken(token); //Just check.
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
+
+
+ @GetMapping(value = "jsonp9")
+ @ResponseBody
+ public String good1(HttpServletRequest request) {
+ String resultStr = null;
+ String referer = request.getParameter("referer");
+ if (verifReferer(referer)){
String jsonpCallback = request.getParameter("jsonpCallback");
String jsonStr = getJsonStr(hashMap);
resultStr = jsonpCallback + "(" + jsonStr + ")";
@@ -125,7 +133,7 @@ public String good1(HttpServletRequest request) {
}
- @GetMapping(value = "jsonp9")
+ @GetMapping(value = "jsonp10")
@ResponseBody
public String good2(HttpServletRequest request) {
String resultStr = null;
@@ -140,7 +148,7 @@ public String good2(HttpServletRequest request) {
return resultStr;
}
- @RequestMapping(value = "jsonp10")
+ @RequestMapping(value = "jsonp11")
@ResponseBody
public String good3(HttpServletRequest request) {
JSONObject parameterObj = readToJSONObect(request);
@@ -151,7 +159,7 @@ public String good3(HttpServletRequest request) {
return resultStr;
}
- @RequestMapping(value = "jsonp11")
+ @RequestMapping(value = "jsonp12")
@ResponseBody
public String good4(@RequestParam("file") MultipartFile file,HttpServletRequest request) {
if(null == file){
@@ -200,4 +208,11 @@ public static boolean verifToken(String token){
}
return true;
}
-}
+
+ public static boolean verifReferer(String str){
+ if (str != "xxxx"){
+ return false;
+ }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpInjection.expected
index 91d23cebbdac..2e4bc97ff973 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpInjection.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpInjection.expected
@@ -1,76 +1,77 @@
edges
-| JsonpController.java:36:32:36:68 | getParameter(...) : String | JsonpController.java:40:16:40:24 | resultStr |
-| JsonpController.java:39:21:39:54 | ... + ... : String | JsonpController.java:40:16:40:24 | resultStr |
-| JsonpController.java:47:32:47:68 | getParameter(...) : String | JsonpController.java:49:16:49:24 | resultStr |
-| JsonpController.java:48:21:48:80 | ... + ... : String | JsonpController.java:49:16:49:24 | resultStr |
-| JsonpController.java:56:32:56:68 | getParameter(...) : String | JsonpController.java:59:16:59:24 | resultStr |
-| JsonpController.java:58:21:58:55 | ... + ... : String | JsonpController.java:59:16:59:24 | resultStr |
-| JsonpController.java:66:32:66:68 | getParameter(...) : String | JsonpController.java:69:16:69:24 | resultStr |
-| JsonpController.java:68:21:68:54 | ... + ... : String | JsonpController.java:69:16:69:24 | resultStr |
-| JsonpController.java:76:32:76:68 | getParameter(...) : String | JsonpController.java:84:20:84:28 | resultStr |
-| JsonpController.java:83:21:83:54 | ... + ... : String | JsonpController.java:84:20:84:28 | resultStr |
-| JsonpController.java:91:32:91:68 | getParameter(...) : String | JsonpController.java:98:20:98:28 | resultStr |
-| JsonpController.java:97:21:97:54 | ... + ... : String | JsonpController.java:98:20:98:28 | resultStr |
-| JsonpController.java:105:32:105:68 | getParameter(...) : String | JsonpController.java:109:16:109:24 | resultStr |
-| JsonpController.java:108:21:108:54 | ... + ... : String | JsonpController.java:109:16:109:24 | resultStr |
-| JsonpController.java:117:24:117:52 | getParameter(...) : String | JsonpController.java:118:24:118:28 | token |
-| JsonpController.java:119:36:119:72 | getParameter(...) : String | JsonpController.java:122:20:122:28 | resultStr |
-| JsonpController.java:121:25:121:59 | ... + ... : String | JsonpController.java:122:20:122:28 | resultStr |
-| JsonpController.java:132:24:132:52 | getParameter(...) : String | JsonpController.java:133:37:133:41 | token |
-| JsonpController.java:137:32:137:68 | getParameter(...) : String | JsonpController.java:140:16:140:24 | resultStr |
-| JsonpController.java:139:21:139:55 | ... + ... : String | JsonpController.java:140:16:140:24 | resultStr |
-| JsonpController.java:150:21:150:54 | ... + ... : String | JsonpController.java:151:16:151:24 | resultStr |
-| JsonpController.java:165:21:165:54 | ... + ... : String | JsonpController.java:166:16:166:24 | resultStr |
+| JsonpController.java:33:32:33:68 | getParameter(...) : String | JsonpController.java:37:16:37:24 | resultStr |
+| JsonpController.java:36:21:36:54 | ... + ... : String | JsonpController.java:37:16:37:24 | resultStr |
+| JsonpController.java:44:32:44:68 | getParameter(...) : String | JsonpController.java:46:16:46:24 | resultStr |
+| JsonpController.java:45:21:45:80 | ... + ... : String | JsonpController.java:46:16:46:24 | resultStr |
+| JsonpController.java:53:32:53:68 | getParameter(...) : String | JsonpController.java:56:16:56:24 | resultStr |
+| JsonpController.java:55:21:55:55 | ... + ... : String | JsonpController.java:56:16:56:24 | resultStr |
+| JsonpController.java:63:32:63:68 | getParameter(...) : String | JsonpController.java:66:16:66:24 | resultStr |
+| JsonpController.java:65:21:65:54 | ... + ... : String | JsonpController.java:66:16:66:24 | resultStr |
+| JsonpController.java:73:32:73:68 | getParameter(...) : String | JsonpController.java:80:20:80:28 | resultStr |
+| JsonpController.java:79:21:79:54 | ... + ... : String | JsonpController.java:80:20:80:28 | resultStr |
+| JsonpController.java:87:32:87:68 | getParameter(...) : String | JsonpController.java:94:20:94:28 | resultStr |
+| JsonpController.java:93:21:93:54 | ... + ... : String | JsonpController.java:94:20:94:28 | resultStr |
+| JsonpController.java:101:32:101:68 | getParameter(...) : String | JsonpController.java:105:16:105:24 | resultStr |
+| JsonpController.java:104:21:104:54 | ... + ... : String | JsonpController.java:105:16:105:24 | resultStr |
+| JsonpController.java:114:32:114:68 | getParameter(...) : String | JsonpController.java:117:16:117:24 | resultStr |
+| JsonpController.java:116:21:116:55 | ... + ... : String | JsonpController.java:117:16:117:24 | resultStr |
+| JsonpController.java:127:36:127:72 | getParameter(...) : String | JsonpController.java:130:20:130:28 | resultStr |
+| JsonpController.java:129:25:129:59 | ... + ... : String | JsonpController.java:130:20:130:28 | resultStr |
+| JsonpController.java:145:32:145:68 | getParameter(...) : String | JsonpController.java:148:16:148:24 | resultStr |
+| JsonpController.java:147:21:147:55 | ... + ... : String | JsonpController.java:148:16:148:24 | resultStr |
+| JsonpController.java:158:21:158:54 | ... + ... : String | JsonpController.java:159:16:159:24 | resultStr |
+| JsonpController.java:173:21:173:54 | ... + ... : String | JsonpController.java:174:16:174:24 | resultStr |
nodes
-| JsonpController.java:36:32:36:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:39:21:39:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:40:16:40:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:40:16:40:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:47:32:47:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:48:21:48:80 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:49:16:49:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:49:16:49:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:56:32:56:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:58:21:58:55 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:59:16:59:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:59:16:59:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:66:32:66:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:68:21:68:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:69:16:69:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:69:16:69:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:76:32:76:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:83:21:83:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:84:20:84:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:84:20:84:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:91:32:91:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:97:21:97:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:98:20:98:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:98:20:98:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:105:32:105:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:108:21:108:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:109:16:109:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:109:16:109:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:117:24:117:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:118:24:118:28 | token | semmle.label | token |
-| JsonpController.java:119:36:119:72 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:121:25:121:59 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:122:20:122:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:122:20:122:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:132:24:132:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:133:37:133:41 | token | semmle.label | token |
-| JsonpController.java:137:32:137:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:139:21:139:55 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:140:16:140:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:140:16:140:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:150:21:150:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:151:16:151:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:165:21:165:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:166:16:166:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:33:32:33:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:36:21:36:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:37:16:37:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:37:16:37:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:44:32:44:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:45:21:45:80 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:46:16:46:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:46:16:46:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:53:32:53:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:55:21:55:55 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:56:16:56:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:56:16:56:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:63:32:63:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:65:21:65:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:66:16:66:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:66:16:66:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:73:32:73:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:79:21:79:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:80:20:80:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:80:20:80:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:87:32:87:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:93:21:93:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:94:20:94:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:94:20:94:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:101:32:101:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:104:21:104:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:105:16:105:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:105:16:105:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:114:32:114:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:116:21:116:55 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:117:16:117:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:117:16:117:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:127:36:127:72 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:129:25:129:59 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:130:20:130:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:130:20:130:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:145:32:145:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:147:21:147:55 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:148:16:148:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:148:16:148:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:158:21:158:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:159:16:159:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:173:21:173:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:174:16:174:24 | resultStr | semmle.label | resultStr |
#select
-| JsonpController.java:40:16:40:24 | resultStr | JsonpController.java:36:32:36:68 | getParameter(...) : String | JsonpController.java:40:16:40:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:36:32:36:68 | getParameter(...) | this user input |
-| JsonpController.java:49:16:49:24 | resultStr | JsonpController.java:47:32:47:68 | getParameter(...) : String | JsonpController.java:49:16:49:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:47:32:47:68 | getParameter(...) | this user input |
-| JsonpController.java:59:16:59:24 | resultStr | JsonpController.java:56:32:56:68 | getParameter(...) : String | JsonpController.java:59:16:59:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:56:32:56:68 | getParameter(...) | this user input |
-| JsonpController.java:69:16:69:24 | resultStr | JsonpController.java:66:32:66:68 | getParameter(...) : String | JsonpController.java:69:16:69:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:66:32:66:68 | getParameter(...) | this user input |
-| JsonpController.java:84:20:84:28 | resultStr | JsonpController.java:76:32:76:68 | getParameter(...) : String | JsonpController.java:84:20:84:28 | resultStr | Jsonp response might include code from $@. | JsonpController.java:76:32:76:68 | getParameter(...) | this user input |
-| JsonpController.java:98:20:98:28 | resultStr | JsonpController.java:91:32:91:68 | getParameter(...) : String | JsonpController.java:98:20:98:28 | resultStr | Jsonp response might include code from $@. | JsonpController.java:91:32:91:68 | getParameter(...) | this user input |
-| JsonpController.java:109:16:109:24 | resultStr | JsonpController.java:105:32:105:68 | getParameter(...) : String | JsonpController.java:109:16:109:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:105:32:105:68 | getParameter(...) | this user input |
+| JsonpController.java:37:16:37:24 | resultStr | JsonpController.java:33:32:33:68 | getParameter(...) : String | JsonpController.java:37:16:37:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:33:32:33:68 | getParameter(...) | this user input |
+| JsonpController.java:46:16:46:24 | resultStr | JsonpController.java:44:32:44:68 | getParameter(...) : String | JsonpController.java:46:16:46:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:44:32:44:68 | getParameter(...) | this user input |
+| JsonpController.java:56:16:56:24 | resultStr | JsonpController.java:53:32:53:68 | getParameter(...) : String | JsonpController.java:56:16:56:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:53:32:53:68 | getParameter(...) | this user input |
+| JsonpController.java:66:16:66:24 | resultStr | JsonpController.java:63:32:63:68 | getParameter(...) : String | JsonpController.java:66:16:66:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:63:32:63:68 | getParameter(...) | this user input |
+| JsonpController.java:80:20:80:28 | resultStr | JsonpController.java:73:32:73:68 | getParameter(...) : String | JsonpController.java:80:20:80:28 | resultStr | Jsonp response might include code from $@. | JsonpController.java:73:32:73:68 | getParameter(...) | this user input |
+| JsonpController.java:94:20:94:28 | resultStr | JsonpController.java:87:32:87:68 | getParameter(...) : String | JsonpController.java:94:20:94:28 | resultStr | Jsonp response might include code from $@. | JsonpController.java:87:32:87:68 | getParameter(...) | this user input |
+| JsonpController.java:105:16:105:24 | resultStr | JsonpController.java:101:32:101:68 | getParameter(...) : String | JsonpController.java:105:16:105:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:101:32:101:68 | getParameter(...) | this user input |
+| JsonpController.java:117:16:117:24 | resultStr | JsonpController.java:114:32:114:68 | getParameter(...) : String | JsonpController.java:117:16:117:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:114:32:114:68 | getParameter(...) | this user input |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpController.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpController.java
index e5b5e70a38d7..4c60b356cfb4 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpController.java
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpController.java
@@ -26,9 +26,6 @@ public class JsonpController {
hashMap.put("password","123456");
}
- private String name = null;
-
-
@GetMapping(value = "jsonp1")
@ResponseBody
public String bad1(HttpServletRequest request) {
@@ -77,7 +74,6 @@ public void bad5(HttpServletRequest request,
PrintWriter pw = null;
Gson gson = new Gson();
String result = gson.toJson(hashMap);
-
String resultStr = null;
pw = response.getWriter();
resultStr = jsonpCallback + "(" + result + ")";
@@ -109,13 +105,25 @@ public String bad7(HttpServletRequest request) {
return resultStr;
}
-
@GetMapping(value = "jsonp8")
@ResponseBody
- public String good1(HttpServletRequest request) {
+ public String bad8(HttpServletRequest request) {
String resultStr = null;
String token = request.getParameter("token");
- if (verifToken(token)){
+ boolean result = verifToken(token); //Just check.
+ String jsonpCallback = request.getParameter("jsonpCallback");
+ String jsonStr = getJsonStr(hashMap);
+ resultStr = jsonpCallback + "(" + jsonStr + ")";
+ return resultStr;
+ }
+
+
+ @GetMapping(value = "jsonp9")
+ @ResponseBody
+ public String good1(HttpServletRequest request) {
+ String resultStr = null;
+ String referer = request.getParameter("referer");
+ if (verifReferer(referer)){
String jsonpCallback = request.getParameter("jsonpCallback");
String jsonStr = getJsonStr(hashMap);
resultStr = jsonpCallback + "(" + jsonStr + ")";
@@ -125,7 +133,7 @@ public String good1(HttpServletRequest request) {
}
- @GetMapping(value = "jsonp9")
+ @GetMapping(value = "jsonp10")
@ResponseBody
public String good2(HttpServletRequest request) {
String resultStr = null;
@@ -140,7 +148,7 @@ public String good2(HttpServletRequest request) {
return resultStr;
}
- @RequestMapping(value = "jsonp10")
+ @RequestMapping(value = "jsonp11")
@ResponseBody
public String good3(HttpServletRequest request) {
JSONObject parameterObj = readToJSONObect(request);
@@ -151,7 +159,7 @@ public String good3(HttpServletRequest request) {
return resultStr;
}
- @RequestMapping(value = "jsonp11")
+ @RequestMapping(value = "jsonp12")
@ResponseBody
public String good4(@RequestParam("file") MultipartFile file,HttpServletRequest request) {
if(null == file){
@@ -200,4 +208,11 @@ public static boolean verifToken(String token){
}
return true;
}
-}
+
+ public static boolean verifReferer(String str){
+ if (str != "xxxx"){
+ return false;
+ }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjection.expected
index c2bcab77d4d4..d90d51ab5524 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjection.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjection.expected
@@ -1,79 +1,76 @@
edges
-| JsonpController.java:36:32:36:68 | getParameter(...) : String | JsonpController.java:40:16:40:24 | resultStr |
-| JsonpController.java:39:21:39:54 | ... + ... : String | JsonpController.java:40:16:40:24 | resultStr |
-| JsonpController.java:47:32:47:68 | getParameter(...) : String | JsonpController.java:49:16:49:24 | resultStr |
-| JsonpController.java:48:21:48:80 | ... + ... : String | JsonpController.java:49:16:49:24 | resultStr |
-| JsonpController.java:56:32:56:68 | getParameter(...) : String | JsonpController.java:59:16:59:24 | resultStr |
-| JsonpController.java:58:21:58:55 | ... + ... : String | JsonpController.java:59:16:59:24 | resultStr |
-| JsonpController.java:66:32:66:68 | getParameter(...) : String | JsonpController.java:69:16:69:24 | resultStr |
-| JsonpController.java:68:21:68:54 | ... + ... : String | JsonpController.java:69:16:69:24 | resultStr |
-| JsonpController.java:76:32:76:68 | getParameter(...) : String | JsonpController.java:84:20:84:28 | resultStr |
-| JsonpController.java:83:21:83:54 | ... + ... : String | JsonpController.java:84:20:84:28 | resultStr |
-| JsonpController.java:91:32:91:68 | getParameter(...) : String | JsonpController.java:98:20:98:28 | resultStr |
-| JsonpController.java:97:21:97:54 | ... + ... : String | JsonpController.java:98:20:98:28 | resultStr |
-| JsonpController.java:105:32:105:68 | getParameter(...) : String | JsonpController.java:109:16:109:24 | resultStr |
-| JsonpController.java:108:21:108:54 | ... + ... : String | JsonpController.java:109:16:109:24 | resultStr |
-| JsonpController.java:117:24:117:52 | getParameter(...) : String | JsonpController.java:118:24:118:28 | token |
-| JsonpController.java:119:36:119:72 | getParameter(...) : String | JsonpController.java:122:20:122:28 | resultStr |
-| JsonpController.java:121:25:121:59 | ... + ... : String | JsonpController.java:122:20:122:28 | resultStr |
-| JsonpController.java:132:24:132:52 | getParameter(...) : String | JsonpController.java:133:37:133:41 | token |
-| JsonpController.java:137:32:137:68 | getParameter(...) : String | JsonpController.java:140:16:140:24 | resultStr |
-| JsonpController.java:139:21:139:55 | ... + ... : String | JsonpController.java:140:16:140:24 | resultStr |
-| JsonpController.java:150:21:150:54 | ... + ... : String | JsonpController.java:151:16:151:24 | resultStr |
-| JsonpController.java:165:21:165:54 | ... + ... : String | JsonpController.java:166:16:166:24 | resultStr |
+| JsonpController.java:33:32:33:68 | getParameter(...) : String | JsonpController.java:37:16:37:24 | resultStr |
+| JsonpController.java:36:21:36:54 | ... + ... : String | JsonpController.java:37:16:37:24 | resultStr |
+| JsonpController.java:44:32:44:68 | getParameter(...) : String | JsonpController.java:46:16:46:24 | resultStr |
+| JsonpController.java:45:21:45:80 | ... + ... : String | JsonpController.java:46:16:46:24 | resultStr |
+| JsonpController.java:53:32:53:68 | getParameter(...) : String | JsonpController.java:56:16:56:24 | resultStr |
+| JsonpController.java:55:21:55:55 | ... + ... : String | JsonpController.java:56:16:56:24 | resultStr |
+| JsonpController.java:63:32:63:68 | getParameter(...) : String | JsonpController.java:66:16:66:24 | resultStr |
+| JsonpController.java:65:21:65:54 | ... + ... : String | JsonpController.java:66:16:66:24 | resultStr |
+| JsonpController.java:73:32:73:68 | getParameter(...) : String | JsonpController.java:80:20:80:28 | resultStr |
+| JsonpController.java:79:21:79:54 | ... + ... : String | JsonpController.java:80:20:80:28 | resultStr |
+| JsonpController.java:87:32:87:68 | getParameter(...) : String | JsonpController.java:94:20:94:28 | resultStr |
+| JsonpController.java:93:21:93:54 | ... + ... : String | JsonpController.java:94:20:94:28 | resultStr |
+| JsonpController.java:101:32:101:68 | getParameter(...) : String | JsonpController.java:105:16:105:24 | resultStr |
+| JsonpController.java:104:21:104:54 | ... + ... : String | JsonpController.java:105:16:105:24 | resultStr |
+| JsonpController.java:114:32:114:68 | getParameter(...) : String | JsonpController.java:117:16:117:24 | resultStr |
+| JsonpController.java:116:21:116:55 | ... + ... : String | JsonpController.java:117:16:117:24 | resultStr |
+| JsonpController.java:127:36:127:72 | getParameter(...) : String | JsonpController.java:130:20:130:28 | resultStr |
+| JsonpController.java:129:25:129:59 | ... + ... : String | JsonpController.java:130:20:130:28 | resultStr |
+| JsonpController.java:145:32:145:68 | getParameter(...) : String | JsonpController.java:148:16:148:24 | resultStr |
+| JsonpController.java:147:21:147:55 | ... + ... : String | JsonpController.java:148:16:148:24 | resultStr |
+| JsonpController.java:158:21:158:54 | ... + ... : String | JsonpController.java:159:16:159:24 | resultStr |
+| JsonpController.java:173:21:173:54 | ... + ... : String | JsonpController.java:174:16:174:24 | resultStr |
| JsonpInjectionServlet1.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet1.java:45:24:45:32 | resultStr |
-| JsonpInjectionServlet1.java:36:26:36:49 | getHeader(...) : String | JsonpInjectionServlet1.java:38:39:38:45 | referer |
| JsonpInjectionServlet1.java:44:25:44:62 | ... + ... : String | JsonpInjectionServlet1.java:45:24:45:32 | resultStr |
| JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr |
| JsonpInjectionServlet2.java:38:21:38:54 | ... + ... : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr |
nodes
-| JsonpController.java:36:32:36:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:39:21:39:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:40:16:40:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:40:16:40:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:47:32:47:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:48:21:48:80 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:49:16:49:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:49:16:49:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:56:32:56:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:58:21:58:55 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:59:16:59:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:59:16:59:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:66:32:66:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:68:21:68:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:69:16:69:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:69:16:69:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:76:32:76:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:83:21:83:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:84:20:84:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:84:20:84:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:91:32:91:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:97:21:97:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:98:20:98:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:98:20:98:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:105:32:105:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:108:21:108:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:109:16:109:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:109:16:109:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:117:24:117:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:118:24:118:28 | token | semmle.label | token |
-| JsonpController.java:119:36:119:72 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:121:25:121:59 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:122:20:122:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:122:20:122:28 | resultStr | semmle.label | resultStr |
-| JsonpController.java:132:24:132:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:133:37:133:41 | token | semmle.label | token |
-| JsonpController.java:137:32:137:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpController.java:139:21:139:55 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:140:16:140:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:140:16:140:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:150:21:150:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:151:16:151:24 | resultStr | semmle.label | resultStr |
-| JsonpController.java:165:21:165:54 | ... + ... : String | semmle.label | ... + ... : String |
-| JsonpController.java:166:16:166:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:33:32:33:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:36:21:36:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:37:16:37:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:37:16:37:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:44:32:44:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:45:21:45:80 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:46:16:46:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:46:16:46:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:53:32:53:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:55:21:55:55 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:56:16:56:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:56:16:56:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:63:32:63:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:65:21:65:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:66:16:66:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:66:16:66:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:73:32:73:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:79:21:79:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:80:20:80:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:80:20:80:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:87:32:87:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:93:21:93:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:94:20:94:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:94:20:94:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:101:32:101:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:104:21:104:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:105:16:105:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:105:16:105:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:114:32:114:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:116:21:116:55 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:117:16:117:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:117:16:117:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:127:36:127:72 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:129:25:129:59 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:130:20:130:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:130:20:130:28 | resultStr | semmle.label | resultStr |
+| JsonpController.java:145:32:145:68 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JsonpController.java:147:21:147:55 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:148:16:148:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:148:16:148:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:158:21:158:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:159:16:159:24 | resultStr | semmle.label | resultStr |
+| JsonpController.java:173:21:173:54 | ... + ... : String | semmle.label | ... + ... : String |
+| JsonpController.java:174:16:174:24 | resultStr | semmle.label | resultStr |
| JsonpInjectionServlet1.java:31:32:31:64 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| JsonpInjectionServlet1.java:36:26:36:49 | getHeader(...) : String | semmle.label | getHeader(...) : String |
-| JsonpInjectionServlet1.java:38:39:38:45 | referer | semmle.label | referer |
| JsonpInjectionServlet1.java:44:25:44:62 | ... + ... : String | semmle.label | ... + ... : String |
| JsonpInjectionServlet1.java:45:24:45:32 | resultStr | semmle.label | resultStr |
| JsonpInjectionServlet1.java:45:24:45:32 | resultStr | semmle.label | resultStr |
@@ -82,11 +79,12 @@ nodes
| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | semmle.label | resultStr |
| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | semmle.label | resultStr |
#select
-| JsonpController.java:40:16:40:24 | resultStr | JsonpController.java:36:32:36:68 | getParameter(...) : String | JsonpController.java:40:16:40:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:36:32:36:68 | getParameter(...) | this user input |
-| JsonpController.java:49:16:49:24 | resultStr | JsonpController.java:47:32:47:68 | getParameter(...) : String | JsonpController.java:49:16:49:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:47:32:47:68 | getParameter(...) | this user input |
-| JsonpController.java:59:16:59:24 | resultStr | JsonpController.java:56:32:56:68 | getParameter(...) : String | JsonpController.java:59:16:59:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:56:32:56:68 | getParameter(...) | this user input |
-| JsonpController.java:69:16:69:24 | resultStr | JsonpController.java:66:32:66:68 | getParameter(...) : String | JsonpController.java:69:16:69:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:66:32:66:68 | getParameter(...) | this user input |
-| JsonpController.java:84:20:84:28 | resultStr | JsonpController.java:76:32:76:68 | getParameter(...) : String | JsonpController.java:84:20:84:28 | resultStr | Jsonp response might include code from $@. | JsonpController.java:76:32:76:68 | getParameter(...) | this user input |
-| JsonpController.java:98:20:98:28 | resultStr | JsonpController.java:91:32:91:68 | getParameter(...) : String | JsonpController.java:98:20:98:28 | resultStr | Jsonp response might include code from $@. | JsonpController.java:91:32:91:68 | getParameter(...) | this user input |
-| JsonpController.java:109:16:109:24 | resultStr | JsonpController.java:105:32:105:68 | getParameter(...) : String | JsonpController.java:109:16:109:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:105:32:105:68 | getParameter(...) | this user input |
+| JsonpController.java:37:16:37:24 | resultStr | JsonpController.java:33:32:33:68 | getParameter(...) : String | JsonpController.java:37:16:37:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:33:32:33:68 | getParameter(...) | this user input |
+| JsonpController.java:46:16:46:24 | resultStr | JsonpController.java:44:32:44:68 | getParameter(...) : String | JsonpController.java:46:16:46:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:44:32:44:68 | getParameter(...) | this user input |
+| JsonpController.java:56:16:56:24 | resultStr | JsonpController.java:53:32:53:68 | getParameter(...) : String | JsonpController.java:56:16:56:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:53:32:53:68 | getParameter(...) | this user input |
+| JsonpController.java:66:16:66:24 | resultStr | JsonpController.java:63:32:63:68 | getParameter(...) : String | JsonpController.java:66:16:66:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:63:32:63:68 | getParameter(...) | this user input |
+| JsonpController.java:80:20:80:28 | resultStr | JsonpController.java:73:32:73:68 | getParameter(...) : String | JsonpController.java:80:20:80:28 | resultStr | Jsonp response might include code from $@. | JsonpController.java:73:32:73:68 | getParameter(...) | this user input |
+| JsonpController.java:94:20:94:28 | resultStr | JsonpController.java:87:32:87:68 | getParameter(...) : String | JsonpController.java:94:20:94:28 | resultStr | Jsonp response might include code from $@. | JsonpController.java:87:32:87:68 | getParameter(...) | this user input |
+| JsonpController.java:105:16:105:24 | resultStr | JsonpController.java:101:32:101:68 | getParameter(...) : String | JsonpController.java:105:16:105:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:101:32:101:68 | getParameter(...) | this user input |
+| JsonpController.java:117:16:117:24 | resultStr | JsonpController.java:114:32:114:68 | getParameter(...) : String | JsonpController.java:117:16:117:24 | resultStr | Jsonp response might include code from $@. | JsonpController.java:114:32:114:68 | getParameter(...) | this user input |
| JsonpInjectionServlet2.java:39:20:39:28 | resultStr | JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) : String | JsonpInjectionServlet2.java:39:20:39:28 | resultStr | Jsonp response might include code from $@. | JsonpInjectionServlet2.java:31:32:31:64 | getParameter(...) | this user input |
From 1349bf7b0ba8b83ee86d5fbdc46b207e32439cd1 Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Tue, 30 Mar 2021 02:57:58 +0000
Subject: [PATCH 094/433] Create a .qll file to reuse the code and add check of
Spring properties
---
.../CWE-555/CredentialsInPropertiesFile.ql | 93 +--------------
.../CredentialsInPropertiesFile.qll | 107 ++++++++++++++++++
.../CredentialsInPropertiesFile.expected | 10 +-
.../CWE-555/CredentialsInPropertiesFile.ql | 93 +--------------
.../security/CWE-555/MailConfig.java | 11 ++
.../security/CWE-555/PropertiesUtils.java | 10 --
.../query-tests/security/CWE-555/options | 1 +
.../beans/factory/annotation/Value.java | 11 ++
8 files changed, 137 insertions(+), 199 deletions(-)
create mode 100644 java/ql/src/experimental/semmle/code/java/frameworks/CredentialsInPropertiesFile.qll
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-555/MailConfig.java
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-555/options
create mode 100644 java/ql/test/stubs/springframework-5.2.3/org/springframework/beans/factory/annotation/Value.java
diff --git a/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.ql b/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.ql
index 8bbc0d00a462..d4ebe902cf75 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.ql
@@ -18,41 +18,7 @@
*/
import java
-import semmle.code.configfiles.ConfigFiles
-import semmle.code.java.dataflow.FlowSources
-
-private string possibleSecretName() {
- result =
- [
- "%password%", "%passwd%", "%account%", "%accnt%", "%credential%", "%token%", "%secret%",
- "%access%key%"
- ]
-}
-
-private string possibleEncryptedSecretName() { result = ["%hashed%", "%encrypted%", "%crypt%"] }
-
-/** Holds if the value is not cleartext credentials. */
-bindingset[value]
-predicate isNotCleartextCredentials(string value) {
- value = "" // Empty string
- or
- value.length() < 7 // Typical credentials are no less than 6 characters
- or
- value.matches("% %") // Sentences containing spaces
- or
- value.regexpMatch(".*[^a-zA-Z\\d]{3,}.*") // Contain repeated non-alphanumeric characters such as a fake password pass**** or ????
- or
- value.matches("@%") // Starts with the "@" sign
- or
- value.regexpMatch("\\$\\{.*\\}") // Variable placeholder ${credentials}
- or
- value.matches("%=") // A basic check of encrypted credentials ending with padding characters
- or
- value.matches("ENC(%)") // Encrypted value
- or
- // Could be a message property for UI display or fake passwords, e.g. login.password_expired=Your current password has expired.
- value.toLowerCase().matches(possibleSecretName())
-}
+import experimental.semmle.code.java.frameworks.CredentialsInPropertiesFile
/**
* Holds if the credentials are in a non-production properties file indicated by:
@@ -63,63 +29,6 @@ predicate isNonProdCredentials(CredentialsConfig cc) {
cc.getFile().getAbsolutePath().matches(["%dev%", "%test%", "%sample%"])
}
-/** The credentials configuration property. */
-class CredentialsConfig extends ConfigPair {
- CredentialsConfig() {
- this.getNameElement().getName().trim().toLowerCase().matches(possibleSecretName()) and
- not this.getNameElement().getName().trim().toLowerCase().matches(possibleEncryptedSecretName()) and
- not isNotCleartextCredentials(this.getValueElement().getValue().trim())
- }
-
- string getName() { result = this.getNameElement().getName().trim() }
-
- string getValue() { result = this.getValueElement().getValue().trim() }
-
- /** Returns a description of this vulnerability. */
- string getConfigDesc() {
- exists(
- LoadCredentialsConfiguration cc, DataFlow::Node source, DataFlow::Node sink, MethodAccess ma
- |
- this.getName() = source.asExpr().(CompileTimeConstantExpr).getStringValue() and
- cc.hasFlow(source, sink) and
- ma.getArgument(0) = sink.asExpr() and
- result = "Plaintext credentials " + this.getName() + " are loaded in " + ma
- )
- or
- not exists(LoadCredentialsConfiguration cc, DataFlow::Node source, DataFlow::Node sink |
- this.getName() = source.asExpr().(CompileTimeConstantExpr).getStringValue() and
- cc.hasFlow(source, sink)
- ) and
- result =
- "Plaintext credentials " + this.getName() + " have cleartext value " + this.getValue() +
- " in properties file"
- }
-}
-
-/**
- * A dataflow configuration tracking flow of a method that loads a credentials property.
- */
-class LoadCredentialsConfiguration extends DataFlow::Configuration {
- LoadCredentialsConfiguration() { this = "LoadCredentialsConfiguration" }
-
- override predicate isSource(DataFlow::Node source) {
- exists(CredentialsConfig cc |
- source.asExpr().(CompileTimeConstantExpr).getStringValue() = cc.getName()
- )
- }
-
- override predicate isSink(DataFlow::Node sink) {
- sink.asExpr() =
- any(MethodAccess ma |
- ma.getMethod()
- .getDeclaringType()
- .getASupertype*()
- .hasQualifiedName("java.util", "Properties") and
- ma.getMethod().getName() = "getProperty"
- ).getArgument(0)
- }
-}
-
from CredentialsConfig cc
where not isNonProdCredentials(cc)
select cc, cc.getConfigDesc()
diff --git a/java/ql/src/experimental/semmle/code/java/frameworks/CredentialsInPropertiesFile.qll b/java/ql/src/experimental/semmle/code/java/frameworks/CredentialsInPropertiesFile.qll
new file mode 100644
index 000000000000..9577789ce0c0
--- /dev/null
+++ b/java/ql/src/experimental/semmle/code/java/frameworks/CredentialsInPropertiesFile.qll
@@ -0,0 +1,107 @@
+/**
+ * Provides classes for analyzing properties files.
+ */
+
+import java
+import semmle.code.configfiles.ConfigFiles
+import semmle.code.java.dataflow.FlowSources
+import semmle.code.java.frameworks.Properties
+
+private string possibleSecretName() {
+ result =
+ [
+ "%password%", "%passwd%", "%account%", "%accnt%", "%credential%", "%token%", "%secret%",
+ "%access%key%"
+ ]
+}
+
+private string possibleEncryptedSecretName() { result = ["%hashed%", "%encrypted%", "%crypt%"] }
+
+/** Holds if the value is not cleartext credentials. */
+bindingset[value]
+predicate isNotCleartextCredentials(string value) {
+ value = "" // Empty string
+ or
+ value.length() < 7 // Typical credentials are no less than 6 characters
+ or
+ value.matches("% %") // Sentences containing spaces
+ or
+ value.regexpMatch(".*[^a-zA-Z\\d]{3,}.*") // Contain repeated non-alphanumeric characters such as a fake password pass**** or ????
+ or
+ value.matches("@%") // Starts with the "@" sign
+ or
+ value.regexpMatch("\\$\\{.*\\}") // Variable placeholder ${credentials}
+ or
+ value.matches("%=") // A basic check of encrypted credentials ending with padding characters
+ or
+ value.matches("ENC(%)") // Encrypted value
+ or
+ // Could be a message property for UI display or fake passwords, e.g. login.password_expired=Your current password has expired.
+ value.toLowerCase().matches(possibleSecretName())
+}
+
+/** The credentials configuration property. */
+class CredentialsConfig extends ConfigPair {
+ CredentialsConfig() {
+ this.getNameElement().getName().trim().toLowerCase().matches(possibleSecretName()) and
+ not this.getNameElement().getName().trim().toLowerCase().matches(possibleEncryptedSecretName()) and
+ not isNotCleartextCredentials(this.getValueElement().getValue().trim())
+ }
+
+ string getName() { result = this.getNameElement().getName().trim() }
+
+ string getValue() { result = this.getValueElement().getValue().trim() }
+
+ /** Returns a description of this vulnerability. */
+ string getConfigDesc() {
+ exists(
+ // getProperty(...)
+ LoadCredentialsConfiguration cc, DataFlow::Node source, DataFlow::Node sink, MethodAccess ma
+ |
+ this.getName() = source.asExpr().(CompileTimeConstantExpr).getStringValue() and
+ cc.hasFlow(source, sink) and
+ ma.getArgument(0) = sink.asExpr() and
+ result = "Plaintext credentials " + this.getName() + " are loaded in Java Properties " + ma
+ )
+ or
+ exists(
+ // @Value("${mail.password}")
+ Annotation a
+ |
+ a.getType().hasQualifiedName("org.springframework.beans.factory.annotation", "Value") and
+ a.getAValue().(CompileTimeConstantExpr).getStringValue() = "${" + this.getName() + "}" and
+ result = "Plaintext credentials " + this.getName() + " are loaded in Spring annotation " + a
+ )
+ or
+ not exists(LoadCredentialsConfiguration cc, DataFlow::Node source, DataFlow::Node sink |
+ this.getName() = source.asExpr().(CompileTimeConstantExpr).getStringValue() and
+ cc.hasFlow(source, sink)
+ ) and
+ not exists(Annotation a |
+ a.getType().hasQualifiedName("org.springframework.beans.factory.annotation", "Value") and
+ a.getAValue().(CompileTimeConstantExpr).getStringValue() = "${" + this.getName() + "}"
+ ) and
+ result =
+ "Plaintext credentials " + this.getName() + " have cleartext value " + this.getValue() +
+ " in properties file"
+ }
+}
+
+/**
+ * A dataflow configuration tracking flow of cleartext credentials stored in a properties file
+ * to a `Properties.getProperty(...)` method call.
+ */
+class LoadCredentialsConfiguration extends DataFlow::Configuration {
+ LoadCredentialsConfiguration() { this = "LoadCredentialsConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) {
+ exists(CredentialsConfig cc |
+ source.asExpr().(CompileTimeConstantExpr).getStringValue() = cc.getName()
+ )
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ sink.asExpr() =
+ any(MethodAccess ma | ma.getMethod() instanceof PropertiesGetPropertyMethod).getArgument(0)
+ }
+}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.expected b/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.expected
index 1bdc004a446f..371a4765c53b 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.expected
@@ -1,5 +1,5 @@
-| configuration.properties:6:1:6:25 | ldap.password=mysecpass | Plaintext credentials ldap.password are loaded in getProperty(...) |
-| configuration.properties:18:1:18:35 | datasource1.password=Passw0rd@123 | Plaintext credentials datasource1.password are loaded in getProperty(...) |
-| configuration.properties:25:1:25:31 | mail.password=MysecPWxWa@1993 | Plaintext credentials mail.password are loaded in getProperty(...) |
-| configuration.properties:33:1:33:50 | com.example.aws.s3.access_key=AKMAMQPBYMCD6YSAYCBA | Plaintext credentials com.example.aws.s3.access_key are loaded in getProperty(...) |
-| configuration.properties:34:1:34:70 | com.example.aws.s3.secret_key=8lMPSfWzZq+wcWtck5+QPLOJDZzE783pS09/IO3k | Plaintext credentials com.example.aws.s3.secret_key are loaded in getProperty(...) |
+| configuration.properties:6:1:6:25 | ldap.password=mysecpass | Plaintext credentials ldap.password are loaded in Java Properties getProperty(...) |
+| configuration.properties:18:1:18:35 | datasource1.password=Passw0rd@123 | Plaintext credentials datasource1.password are loaded in Java Properties getProperty(...) |
+| configuration.properties:25:1:25:31 | mail.password=MysecPWxWa@1993 | Plaintext credentials mail.password are loaded in Spring annotation Value |
+| configuration.properties:33:1:33:50 | com.example.aws.s3.access_key=AKMAMQPBYMCD6YSAYCBA | Plaintext credentials com.example.aws.s3.access_key are loaded in Java Properties getProperty(...) |
+| configuration.properties:34:1:34:70 | com.example.aws.s3.secret_key=8lMPSfWzZq+wcWtck5+QPLOJDZzE783pS09/IO3k | Plaintext credentials com.example.aws.s3.secret_key are loaded in Java Properties getProperty(...) |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.ql b/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.ql
index b6890e4ac9fc..f085317218c0 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.ql
+++ b/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.ql
@@ -18,98 +18,7 @@
*/
import java
-import semmle.code.configfiles.ConfigFiles
-import semmle.code.java.dataflow.FlowSources
-
-private string possibleSecretName() {
- result =
- [
- "%password%", "%passwd%", "%account%", "%accnt%", "%credential%", "%token%", "%secret%",
- "%access%key%"
- ]
-}
-
-private string possibleEncryptedSecretName() { result = ["%hashed%", "%encrypted%", "%crypt%"] }
-
-/** Holds if the value is not cleartext credentials. */
-bindingset[value]
-predicate isNotCleartextCredentials(string value) {
- value = "" // Empty string
- or
- value.length() < 7 // Typical credentials are no less than 6 characters
- or
- value.matches("% %") // Sentences containing spaces
- or
- value.regexpMatch(".*[^a-zA-Z\\d]{3,}.*") // Contain repeated non-alphanumeric characters such as a fake password pass**** or ????
- or
- value.matches("@%") // Starts with the "@" sign
- or
- value.regexpMatch("\\$\\{.*\\}") // Variable placeholder ${credentials}
- or
- value.matches("%=") // A basic check of encrypted credentials ending with padding characters
- or
- value.matches("ENC(%)") // Encrypted value
- or
- // Could be a message property for UI display or fake passwords, e.g. login.password_expired=Your current password has expired.
- value.toLowerCase().matches(possibleSecretName())
-}
-
-/** The credentials configuration property. */
-class CredentialsConfig extends ConfigPair {
- CredentialsConfig() {
- this.getNameElement().getName().trim().toLowerCase().matches(possibleSecretName()) and
- not this.getNameElement().getName().trim().toLowerCase().matches(possibleEncryptedSecretName()) and
- not isNotCleartextCredentials(this.getValueElement().getValue().trim())
- }
-
- string getName() { result = this.getNameElement().getName().trim() }
-
- string getValue() { result = this.getValueElement().getValue().trim() }
-
- /** Returns a description of this vulnerability. */
- string getConfigDesc() {
- exists(
- LoadCredentialsConfiguration cc, DataFlow::Node source, DataFlow::Node sink, MethodAccess ma
- |
- this.getName() = source.asExpr().(CompileTimeConstantExpr).getStringValue() and
- cc.hasFlow(source, sink) and
- ma.getArgument(0) = sink.asExpr() and
- result = "Plaintext credentials " + this.getName() + " are loaded in " + ma
- )
- or
- not exists(LoadCredentialsConfiguration cc, DataFlow::Node source, DataFlow::Node sink |
- this.getName() = source.asExpr().(CompileTimeConstantExpr).getStringValue() and
- cc.hasFlow(source, sink)
- ) and
- result =
- "Plaintext credentials " + this.getName() + " have cleartext value " + this.getValue() +
- " in properties file"
- }
-}
-
-/**
- * A dataflow configuration tracking flow of a method that loads a credentials property.
- */
-class LoadCredentialsConfiguration extends DataFlow::Configuration {
- LoadCredentialsConfiguration() { this = "LoadCredentialsConfiguration" }
-
- override predicate isSource(DataFlow::Node source) {
- exists(CredentialsConfig cc |
- source.asExpr().(CompileTimeConstantExpr).getStringValue() = cc.getName()
- )
- }
-
- override predicate isSink(DataFlow::Node sink) {
- sink.asExpr() =
- any(MethodAccess ma |
- ma.getMethod()
- .getDeclaringType()
- .getASupertype*()
- .hasQualifiedName("java.util", "Properties") and
- ma.getMethod().getName() = "getProperty"
- ).getArgument(0)
- }
-}
+import experimental.semmle.code.java.frameworks.CredentialsInPropertiesFile
from CredentialsConfig cc
select cc, cc.getConfigDesc()
diff --git a/java/ql/test/experimental/query-tests/security/CWE-555/MailConfig.java b/java/ql/test/experimental/query-tests/security/CWE-555/MailConfig.java
new file mode 100644
index 000000000000..bef99dac3ea7
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-555/MailConfig.java
@@ -0,0 +1,11 @@
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class MailConfig {
+ @Value("${mail.password}")
+ private String mailPassword;
+
+ @Value("${mail.username}")
+ private String mailUserName;
+}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-555/PropertiesUtils.java b/java/ql/test/experimental/query-tests/security/CWE-555/PropertiesUtils.java
index b54995a9967e..f33ef39ee077 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-555/PropertiesUtils.java
+++ b/java/ql/test/experimental/query-tests/security/CWE-555/PropertiesUtils.java
@@ -35,16 +35,6 @@ public static String getMSDataSourcePassword() {
return properties.getProperty("datasource1.password");
}
- /** Returns the mail account property value. */
- public static String getMailUserName() {
- return properties.getProperty("mail.username");
- }
-
- /** Returns the mail password property value. */
- public static String getMailPassword() {
- return properties.getProperty("mail.password");
- }
-
/** Returns the AWS Access Key property value. */
public static String getAWSAccessKey() {
return properties.getProperty("com.example.aws.s3.access_key");
diff --git a/java/ql/test/experimental/query-tests/security/CWE-555/options b/java/ql/test/experimental/query-tests/security/CWE-555/options
new file mode 100644
index 000000000000..cf3d60de0e5c
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-555/options
@@ -0,0 +1 @@
+// semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/springframework-5.2.3
\ No newline at end of file
diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/beans/factory/annotation/Value.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/beans/factory/annotation/Value.java
new file mode 100644
index 000000000000..67c38cf5d20b
--- /dev/null
+++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/beans/factory/annotation/Value.java
@@ -0,0 +1,11 @@
+package org.springframework.beans.factory.annotation;
+
+public @interface Value {
+
+ /**
+ * The actual value expression such as #{systemProperties.myProp}
+ * or property placeholder such as ${my.app.myProp}
.
+ */
+ String value();
+
+}
\ No newline at end of file
From 8159098dc0e47627a2386d8682e36f3d0445ca3e Mon Sep 17 00:00:00 2001
From: Mathias Vorreiter Pedersen
Date: Wed, 31 Mar 2021 11:32:01 +0200
Subject: [PATCH 095/433] C++: Add test from issue #5190.
---
.../dataflow/taint-tests/smart_pointer.cpp | 10 ++++++++++
cpp/ql/test/library-tests/dataflow/taint-tests/stl.h | 2 +-
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/smart_pointer.cpp b/cpp/ql/test/library-tests/dataflow/taint-tests/smart_pointer.cpp
index f45260228e8e..1f222cbae3e2 100644
--- a/cpp/ql/test/library-tests/dataflow/taint-tests/smart_pointer.cpp
+++ b/cpp/ql/test/library-tests/dataflow/taint-tests/smart_pointer.cpp
@@ -65,4 +65,14 @@ void test_shared_field_member() {
std::unique_ptr p = std::make_unique(source(), 0);
sink(p->x); // $ MISSING: ast,ir
sink(p->y); // not tainted
+}
+
+void getNumber(std::shared_ptr ptr) {
+ *ptr = source();
+}
+
+int test_from_issue_5190() {
+ std::shared_ptr p(new int);
+ getNumber(p);
+ sink(*p); // $ MISSING: ast,ir
}
\ No newline at end of file
diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/stl.h b/cpp/ql/test/library-tests/dataflow/taint-tests/stl.h
index 09e77c5a3b67..1382552bdcaf 100644
--- a/cpp/ql/test/library-tests/dataflow/taint-tests/stl.h
+++ b/cpp/ql/test/library-tests/dataflow/taint-tests/stl.h
@@ -348,7 +348,7 @@ namespace std {
class shared_ptr {
public:
shared_ptr() noexcept;
- explicit shared_ptr(T*);
+ explicit shared_ptr(T*); shared_ptr(const shared_ptr&) noexcept;
template shared_ptr(const shared_ptr&) noexcept;
template shared_ptr(shared_ptr&&) noexcept;
From 9ff894bf839a098aad951f8d5df23b6f339da14b Mon Sep 17 00:00:00 2001
From: Mathias Vorreiter Pedersen
Date: Wed, 31 Mar 2021 11:42:09 +0200
Subject: [PATCH 096/433] C++: Add support for AST dataflow out of functions
that take a smart pointer by value.
---
.../cpp/dataflow/internal/DataFlowUtil.qll | 14 ++++
.../code/cpp/dataflow/internal/FlowVar.qll | 72 ++++++++++++++++++-
.../dataflow/internal/TaintTrackingUtil.qll | 6 +-
cpp/ql/src/semmle/code/cpp/exprs/Call.qll | 1 +
.../dataflow/taint-tests/localTaint.expected | 22 ++++++
.../dataflow/taint-tests/smart_pointer.cpp | 10 +--
6 files changed, 118 insertions(+), 7 deletions(-)
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll
index 0a6d459ec79b..baf5f72b75b6 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll
@@ -199,6 +199,8 @@ class DefinitionByReferenceOrIteratorNode extends PartialDefinitionNode {
this.getPartialDefinition() instanceof DefinitionByReference
or
this.getPartialDefinition() instanceof DefinitionByIterator
+ or
+ this.getPartialDefinition() instanceof DefinitionBySmartPointer
)
}
@@ -330,6 +332,18 @@ class IteratorPartialDefinitionNode extends PartialDefinitionNode {
override Node getPreUpdateNode() { pd.definesExpressions(_, result.asExpr()) }
}
+/**
+ * INTERNAL: do not use.
+ *
+ * A synthetic data flow node used for flow into a collection when a smart pointer
+ * write occurs in a callee.
+ */
+class SmartPointerPartialDefinitionNode extends PartialDefinitionNode {
+ override SmartPointerPartialDefinition pd;
+
+ override Node getPreUpdateNode() { pd.definesExpressions(_, result.asExpr()) }
+}
+
/**
* A post-update node on the `e->f` in `f(&e->f)` (and other forms).
*/
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/FlowVar.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/FlowVar.qll
index f3aa94a79920..21ba4dff36d3 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/FlowVar.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/FlowVar.qll
@@ -7,6 +7,7 @@ private import semmle.code.cpp.controlflow.SSA
private import semmle.code.cpp.dataflow.internal.SubBasicBlocks
private import semmle.code.cpp.dataflow.internal.AddressFlow
private import semmle.code.cpp.models.implementations.Iterator
+private import semmle.code.cpp.models.interfaces.PointerWrapper
/**
* A conceptual variable that is assigned only once, like an SSA variable. This
@@ -243,6 +244,54 @@ private module PartialDefinitions {
}
}
+ class SmartPointerPartialDefinition extends PartialDefinition {
+ Variable pointer;
+ Expr innerDefinedExpr;
+
+ SmartPointerPartialDefinition() {
+ exists(Expr convertedInner |
+ not this instanceof Conversion and
+ valueToUpdate(convertedInner, this.getFullyConverted(), node) and
+ innerDefinedExpr = convertedInner.getUnconverted() and
+ innerDefinedExpr = getAPointerWrapperAccess(pointer)
+ )
+ or
+ // iterators passed by value without a copy constructor
+ exists(Call call |
+ call = node and
+ call.getAnArgument() = innerDefinedExpr and
+ innerDefinedExpr = this and
+ this = getAPointerWrapperAccess(pointer) and
+ not call instanceof OverloadedPointerDereferenceExpr
+ )
+ or
+ // iterators passed by value with a copy constructor
+ exists(Call call, ConstructorCall copy |
+ copy.getTarget() instanceof CopyConstructor and
+ call = node and
+ call.getAnArgument() = copy and
+ copy.getArgument(0) = getAPointerWrapperAccess(pointer) and
+ innerDefinedExpr = this and
+ this = copy and
+ not call instanceof OverloadedPointerDereferenceExpr
+ )
+ }
+
+ deprecated override predicate partiallyDefines(Variable v) { v = pointer }
+
+ deprecated override predicate partiallyDefinesThis(ThisExpr e) { none() }
+
+ override predicate definesExpressions(Expr inner, Expr outer) {
+ inner = innerDefinedExpr and
+ outer = this
+ }
+
+ override predicate partiallyDefinesVariableAt(Variable v, ControlFlowNode cfn) {
+ v = pointer and
+ cfn = node
+ }
+ }
+
/**
* A partial definition that's a definition via an output iterator.
*/
@@ -256,6 +305,15 @@ private module PartialDefinitions {
class DefinitionByReference extends VariablePartialDefinition {
DefinitionByReference() { exists(Call c | this = c.getAnArgument() or this = c.getQualifier()) }
}
+
+ /**
+ * A partial definition that's a definition via a smart pointer being passed into a function.
+ */
+ class DefinitionBySmartPointer extends SmartPointerPartialDefinition {
+ DefinitionBySmartPointer() {
+ exists(Call c | this = c.getAnArgument() or this = c.getQualifier())
+ }
+ }
}
import PartialDefinitions
@@ -296,7 +354,8 @@ module FlowVar_internal {
// treating them as immutable, but for data flow it gives better results in
// practice to make the variable synonymous with its contents.
not v.getUnspecifiedType() instanceof ReferenceType and
- not v instanceof IteratorParameter
+ not v instanceof IteratorParameter and
+ not v instanceof PointerWrapperParameter
}
/**
@@ -648,6 +707,8 @@ module FlowVar_internal {
)
or
p instanceof IteratorParameter
+ or
+ p instanceof PointerWrapperParameter
}
/**
@@ -832,10 +893,19 @@ module FlowVar_internal {
)
}
+ Call getAPointerWrapperAccess(Variable pointer) {
+ pointer.getUnspecifiedType() instanceof PointerWrapper and
+ [result.getQualifier(), result.getAnArgument()] = pointer.getAnAccess()
+ }
+
class IteratorParameter extends Parameter {
IteratorParameter() { this.getUnspecifiedType() instanceof Iterator }
}
+ class PointerWrapperParameter extends Parameter {
+ PointerWrapperParameter() { this.getUnspecifiedType() instanceof PointerWrapper }
+ }
+
/**
* Holds if `v` is initialized to have value `assignedExpr`.
*/
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/TaintTrackingUtil.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/TaintTrackingUtil.qll
index 1ef340c4f213..591f461c8eb9 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/TaintTrackingUtil.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/TaintTrackingUtil.qll
@@ -11,6 +11,7 @@
private import semmle.code.cpp.models.interfaces.DataFlow
private import semmle.code.cpp.models.interfaces.Taint
private import semmle.code.cpp.models.interfaces.Iterator
+private import semmle.code.cpp.models.interfaces.PointerWrapper
private module DataFlow {
import semmle.code.cpp.dataflow.internal.DataFlowUtil
@@ -141,7 +142,10 @@ private predicate noFlowFromChildExpr(Expr e) {
or
e instanceof LogicalOrExpr
or
- e instanceof Call
+ // Allow taint from `operator*` on smart pointers.
+ exists(Call call | e = call |
+ not call.getTarget() = any(PointerWrapper wrapper).getAnUnwrapperFunction()
+ )
or
e instanceof SizeofOperator
or
diff --git a/cpp/ql/src/semmle/code/cpp/exprs/Call.qll b/cpp/ql/src/semmle/code/cpp/exprs/Call.qll
index 349efdcee103..6f6f710ac4b3 100644
--- a/cpp/ql/src/semmle/code/cpp/exprs/Call.qll
+++ b/cpp/ql/src/semmle/code/cpp/exprs/Call.qll
@@ -314,6 +314,7 @@ class OverloadedPointerDereferenceFunction extends Function {
* T1 operator*(const T2 &);
* T1 a; T2 b;
* a = *b;
+ * ```
*/
class OverloadedPointerDereferenceExpr extends FunctionCall {
OverloadedPointerDereferenceExpr() {
diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected b/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected
index 4444ea13267f..9b1ad0d4f10a 100644
--- a/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected
+++ b/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected
@@ -3223,24 +3223,30 @@
| smart_pointer.cpp:11:30:11:50 | call to make_shared | smart_pointer.cpp:12:11:12:11 | p | |
| smart_pointer.cpp:11:30:11:50 | call to make_shared | smart_pointer.cpp:13:10:13:10 | p | |
| smart_pointer.cpp:11:52:11:57 | call to source | smart_pointer.cpp:11:30:11:50 | call to make_shared | TAINT |
+| smart_pointer.cpp:12:10:12:10 | call to operator* [post update] | smart_pointer.cpp:13:10:13:10 | p | |
| smart_pointer.cpp:12:11:12:11 | p | smart_pointer.cpp:12:10:12:10 | call to operator* | TAINT |
| smart_pointer.cpp:12:11:12:11 | ref arg p | smart_pointer.cpp:13:10:13:10 | p | |
| smart_pointer.cpp:17:32:17:54 | call to make_shared | smart_pointer.cpp:18:11:18:11 | p | |
| smart_pointer.cpp:17:32:17:54 | call to make_shared | smart_pointer.cpp:19:10:19:10 | p | |
+| smart_pointer.cpp:18:10:18:10 | ref arg call to operator* | smart_pointer.cpp:19:10:19:10 | p | |
| smart_pointer.cpp:18:11:18:11 | p | smart_pointer.cpp:18:10:18:10 | call to operator* | TAINT |
| smart_pointer.cpp:18:11:18:11 | ref arg p | smart_pointer.cpp:19:10:19:10 | p | |
| smart_pointer.cpp:23:30:23:50 | call to make_unique | smart_pointer.cpp:24:11:24:11 | p | |
| smart_pointer.cpp:23:30:23:50 | call to make_unique | smart_pointer.cpp:25:10:25:10 | p | |
| smart_pointer.cpp:23:52:23:57 | call to source | smart_pointer.cpp:23:30:23:50 | call to make_unique | TAINT |
+| smart_pointer.cpp:24:10:24:10 | call to operator* [post update] | smart_pointer.cpp:25:10:25:10 | p | |
| smart_pointer.cpp:24:11:24:11 | p | smart_pointer.cpp:24:10:24:10 | call to operator* | TAINT |
| smart_pointer.cpp:24:11:24:11 | ref arg p | smart_pointer.cpp:25:10:25:10 | p | |
| smart_pointer.cpp:29:32:29:54 | call to make_unique | smart_pointer.cpp:30:11:30:11 | p | |
| smart_pointer.cpp:29:32:29:54 | call to make_unique | smart_pointer.cpp:31:10:31:10 | p | |
+| smart_pointer.cpp:30:10:30:10 | ref arg call to operator* | smart_pointer.cpp:31:10:31:10 | p | |
| smart_pointer.cpp:30:11:30:11 | p | smart_pointer.cpp:30:10:30:10 | call to operator* | TAINT |
| smart_pointer.cpp:30:11:30:11 | ref arg p | smart_pointer.cpp:31:10:31:10 | p | |
| smart_pointer.cpp:35:30:35:50 | call to make_shared | smart_pointer.cpp:37:6:37:6 | p | |
| smart_pointer.cpp:35:30:35:50 | call to make_shared | smart_pointer.cpp:38:10:38:10 | p | |
| smart_pointer.cpp:35:30:35:50 | call to make_shared | smart_pointer.cpp:39:11:39:11 | p | |
+| smart_pointer.cpp:37:5:37:5 | call to operator* [post update] | smart_pointer.cpp:38:10:38:10 | p | |
+| smart_pointer.cpp:37:5:37:5 | call to operator* [post update] | smart_pointer.cpp:39:11:39:11 | p | |
| smart_pointer.cpp:37:5:37:17 | ... = ... | smart_pointer.cpp:37:5:37:5 | call to operator* [post update] | |
| smart_pointer.cpp:37:6:37:6 | p | smart_pointer.cpp:37:5:37:5 | call to operator* | TAINT |
| smart_pointer.cpp:37:6:37:6 | ref arg p | smart_pointer.cpp:38:10:38:10 | p | |
@@ -3251,6 +3257,8 @@
| smart_pointer.cpp:43:29:43:51 | call to unique_ptr | smart_pointer.cpp:45:6:45:6 | p | |
| smart_pointer.cpp:43:29:43:51 | call to unique_ptr | smart_pointer.cpp:46:10:46:10 | p | |
| smart_pointer.cpp:43:29:43:51 | call to unique_ptr | smart_pointer.cpp:47:11:47:11 | p | |
+| smart_pointer.cpp:45:5:45:5 | call to operator* [post update] | smart_pointer.cpp:46:10:46:10 | p | |
+| smart_pointer.cpp:45:5:45:5 | call to operator* [post update] | smart_pointer.cpp:47:11:47:11 | p | |
| smart_pointer.cpp:45:5:45:17 | ... = ... | smart_pointer.cpp:45:5:45:5 | call to operator* [post update] | |
| smart_pointer.cpp:45:6:45:6 | p | smart_pointer.cpp:45:5:45:5 | call to operator* | TAINT |
| smart_pointer.cpp:45:6:45:6 | ref arg p | smart_pointer.cpp:46:10:46:10 | p | |
@@ -3268,7 +3276,21 @@
| smart_pointer.cpp:65:28:65:46 | call to make_unique | smart_pointer.cpp:67:10:67:10 | p | |
| smart_pointer.cpp:65:48:65:53 | call to source | smart_pointer.cpp:65:28:65:46 | call to make_unique | TAINT |
| smart_pointer.cpp:65:58:65:58 | 0 | smart_pointer.cpp:65:28:65:46 | call to make_unique | TAINT |
+| smart_pointer.cpp:66:10:66:10 | p | smart_pointer.cpp:66:11:66:11 | call to operator-> | TAINT |
| smart_pointer.cpp:66:10:66:10 | ref arg p | smart_pointer.cpp:67:10:67:10 | p | |
+| smart_pointer.cpp:67:10:67:10 | p | smart_pointer.cpp:67:11:67:11 | call to operator-> | TAINT |
+| smart_pointer.cpp:70:37:70:39 | ptr | smart_pointer.cpp:70:37:70:39 | ptr | |
+| smart_pointer.cpp:70:37:70:39 | ptr | smart_pointer.cpp:71:4:71:6 | ptr | |
+| smart_pointer.cpp:71:3:71:3 | call to operator* [post update] | smart_pointer.cpp:70:37:70:39 | ptr | |
+| smart_pointer.cpp:71:3:71:17 | ... = ... | smart_pointer.cpp:71:3:71:3 | call to operator* [post update] | |
+| smart_pointer.cpp:71:4:71:6 | ptr | smart_pointer.cpp:71:3:71:3 | call to operator* | TAINT |
+| smart_pointer.cpp:71:4:71:6 | ref arg ptr | smart_pointer.cpp:70:37:70:39 | ptr | |
+| smart_pointer.cpp:71:10:71:15 | call to source | smart_pointer.cpp:71:3:71:17 | ... = ... | |
+| smart_pointer.cpp:75:26:75:33 | call to shared_ptr | smart_pointer.cpp:76:13:76:13 | p | |
+| smart_pointer.cpp:75:26:75:33 | call to shared_ptr | smart_pointer.cpp:77:9:77:9 | p | |
+| smart_pointer.cpp:76:13:76:13 | call to shared_ptr [post update] | smart_pointer.cpp:77:9:77:9 | p | |
+| smart_pointer.cpp:76:13:76:13 | p | smart_pointer.cpp:76:13:76:13 | call to shared_ptr | |
+| smart_pointer.cpp:77:9:77:9 | p | smart_pointer.cpp:77:8:77:8 | call to operator* | TAINT |
| standalone_iterators.cpp:39:45:39:51 | source1 | standalone_iterators.cpp:39:45:39:51 | source1 | |
| standalone_iterators.cpp:39:45:39:51 | source1 | standalone_iterators.cpp:40:11:40:17 | source1 | |
| standalone_iterators.cpp:39:45:39:51 | source1 | standalone_iterators.cpp:41:12:41:18 | source1 | |
diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/smart_pointer.cpp b/cpp/ql/test/library-tests/dataflow/taint-tests/smart_pointer.cpp
index 1f222cbae3e2..e5d835d3426f 100644
--- a/cpp/ql/test/library-tests/dataflow/taint-tests/smart_pointer.cpp
+++ b/cpp/ql/test/library-tests/dataflow/taint-tests/smart_pointer.cpp
@@ -35,16 +35,16 @@ void test_reverse_taint_shared() {
std::shared_ptr p = std::make_shared();
*p = source();
- sink(p); // $ MISSING: ast,ir
- sink(*p); // $ MISSING: ast,ir
+ sink(p); // $ ast MISSING: ir
+ sink(*p); // $ ast MISSING: ir
}
void test_reverse_taint_unique() {
std::unique_ptr p = std::unique_ptr();
*p = source();
- sink(p); // $ MISSING: ast,ir
- sink(*p); // $ MISSING: ast,ir
+ sink(p); // $ ast MISSING: ir
+ sink(*p); // $ ast MISSING: ir
}
void test_shared_get() {
@@ -74,5 +74,5 @@ void getNumber(std::shared_ptr ptr) {
int test_from_issue_5190() {
std::shared_ptr p(new int);
getNumber(p);
- sink(*p); // $ MISSING: ast,ir
+ sink(*p); // $ ast MISSING: ir
}
\ No newline at end of file
From ecbce88ec78710f67c0160f7ae58e58c027fb73c Mon Sep 17 00:00:00 2001
From: Mathias Vorreiter Pedersen
Date: Wed, 31 Mar 2021 22:23:50 +0200
Subject: [PATCH 097/433] C++: Fix comment.
---
cpp/ql/src/semmle/code/cpp/dataflow/internal/FlowVar.qll | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/FlowVar.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/FlowVar.qll
index 21ba4dff36d3..29b6cd691a45 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/FlowVar.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/FlowVar.qll
@@ -256,7 +256,7 @@ private module PartialDefinitions {
innerDefinedExpr = getAPointerWrapperAccess(pointer)
)
or
- // iterators passed by value without a copy constructor
+ // pointer wrappers passed by value without a copy constructor
exists(Call call |
call = node and
call.getAnArgument() = innerDefinedExpr and
@@ -265,7 +265,7 @@ private module PartialDefinitions {
not call instanceof OverloadedPointerDereferenceExpr
)
or
- // iterators passed by value with a copy constructor
+ // pointer wrappers passed by value with a copy constructor
exists(Call call, ConstructorCall copy |
copy.getTarget() instanceof CopyConstructor and
call = node and
From 480ce39618efaa47d7a51b43a80a044aca7d6532 Mon Sep 17 00:00:00 2001
From: Luke Cartey <5377966+lcartey@users.noreply.github.com>
Date: Thu, 1 Apr 2021 11:23:31 +0100
Subject: [PATCH 098/433] C#: Exclude jump-to-def information for elements with
too many locations
In databases which include multiple duplicated files, we can get an
explosion of definition locations that can cause this query to produce
too many results for the CodeQL toolchain. This commit restricts the
definitions.ql query to producing definition/uses for definitions with
fewer than 10 locations. This replicates the logic used in the C++
definitions.qll library which faces similar problems.
---
csharp/ql/src/definitions.qll | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/csharp/ql/src/definitions.qll b/csharp/ql/src/definitions.qll
index 2004ad0d2187..c1b456f4dbf2 100644
--- a/csharp/ql/src/definitions.qll
+++ b/csharp/ql/src/definitions.qll
@@ -187,5 +187,11 @@ cached
Declaration definitionOf(Use use, string kind) {
result = use.getDefinition() and
result.fromSource() and
- kind = use.getUseType()
+ kind = use.getUseType() and
+ // Some entities have many locations. This can arise for files that
+ // are duplicated multiple times in the database at different
+ // locations. Rather than letting the result set explode, we just
+ // exclude results that are "too ambiguous" -- we could also arbitrarily
+ // pick one location later on.
+ strictcount(result.getLocation()) < 10
}
From 32a8b9a857eb66a5b471de771dff380a61654e7b Mon Sep 17 00:00:00 2001
From: Mathias Vorreiter Pedersen
Date: Tue, 6 Apr 2021 08:56:14 +0200
Subject: [PATCH 099/433] C++: Move copy constructor to its own line and accept
test changes.
---
.../dataflow/taint-tests/localTaint.expected | 238 +++++++++---------
.../library-tests/dataflow/taint-tests/stl.h | 3 +-
2 files changed, 121 insertions(+), 120 deletions(-)
diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected b/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected
index 9b1ad0d4f10a..e10014278187 100644
--- a/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected
+++ b/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected
@@ -3410,125 +3410,125 @@
| stl.h:292:30:292:40 | call to allocator | stl.h:292:21:292:41 | noexcept(...) | TAINT |
| stl.h:292:30:292:40 | call to allocator | stl.h:292:21:292:41 | noexcept(...) | TAINT |
| stl.h:292:53:292:63 | 0 | stl.h:292:46:292:64 | (no string representation) | TAINT |
-| stl.h:388:9:388:9 | Unknown literal | stl.h:388:9:388:9 | constructor init of field first | TAINT |
-| stl.h:388:9:388:9 | Unknown literal | stl.h:388:9:388:9 | constructor init of field first | TAINT |
-| stl.h:388:9:388:9 | Unknown literal | stl.h:388:9:388:9 | constructor init of field first | TAINT |
-| stl.h:388:9:388:9 | Unknown literal | stl.h:388:9:388:9 | constructor init of field first | TAINT |
-| stl.h:388:9:388:9 | Unknown literal | stl.h:388:9:388:9 | constructor init of field first | TAINT |
-| stl.h:388:9:388:9 | Unknown literal | stl.h:388:9:388:9 | constructor init of field second | TAINT |
-| stl.h:388:9:388:9 | Unknown literal | stl.h:388:9:388:9 | constructor init of field second | TAINT |
-| stl.h:388:9:388:9 | Unknown literal | stl.h:388:9:388:9 | constructor init of field second | TAINT |
-| stl.h:388:9:388:9 | Unknown literal | stl.h:388:9:388:9 | constructor init of field second | TAINT |
-| stl.h:388:9:388:9 | Unknown literal | stl.h:388:9:388:9 | constructor init of field second | TAINT |
-| stl.h:388:9:388:9 | constructor init of field first [post-this] | stl.h:388:9:388:9 | constructor init of field second [pre-this] | |
-| stl.h:388:9:388:9 | constructor init of field first [post-this] | stl.h:388:9:388:9 | constructor init of field second [pre-this] | |
-| stl.h:388:9:388:9 | constructor init of field first [post-this] | stl.h:388:9:388:9 | constructor init of field second [pre-this] | |
-| stl.h:388:9:388:9 | constructor init of field first [post-this] | stl.h:388:9:388:9 | constructor init of field second [pre-this] | |
-| stl.h:388:9:388:9 | constructor init of field first [post-this] | stl.h:388:9:388:9 | constructor init of field second [pre-this] | |
-| stl.h:388:9:388:9 | constructor init of field first [pre-this] | stl.h:388:9:388:9 | constructor init of field second [pre-this] | |
-| stl.h:388:9:388:9 | constructor init of field first [pre-this] | stl.h:388:9:388:9 | constructor init of field second [pre-this] | |
-| stl.h:388:9:388:9 | constructor init of field first [pre-this] | stl.h:388:9:388:9 | constructor init of field second [pre-this] | |
-| stl.h:388:9:388:9 | constructor init of field first [pre-this] | stl.h:388:9:388:9 | constructor init of field second [pre-this] | |
-| stl.h:388:9:388:9 | constructor init of field first [pre-this] | stl.h:388:9:388:9 | constructor init of field second [pre-this] | |
-| stl.h:388:9:388:9 | this | stl.h:388:9:388:9 | constructor init of field first [pre-this] | |
-| stl.h:388:9:388:9 | this | stl.h:388:9:388:9 | constructor init of field first [pre-this] | |
-| stl.h:388:9:388:9 | this | stl.h:388:9:388:9 | constructor init of field first [pre-this] | |
-| stl.h:388:9:388:9 | this | stl.h:388:9:388:9 | constructor init of field first [pre-this] | |
-| stl.h:388:9:388:9 | this | stl.h:388:9:388:9 | constructor init of field first [pre-this] | |
-| stl.h:395:3:395:3 | this | stl.h:395:36:395:43 | constructor init of field first [pre-this] | |
-| stl.h:395:3:395:3 | this | stl.h:395:36:395:43 | constructor init of field first [pre-this] | |
-| stl.h:395:3:395:3 | this | stl.h:395:36:395:43 | constructor init of field first [pre-this] | |
-| stl.h:395:3:395:3 | this | stl.h:395:36:395:43 | constructor init of field first [pre-this] | |
-| stl.h:395:3:395:3 | this | stl.h:395:36:395:43 | constructor init of field first [pre-this] | |
-| stl.h:395:3:395:6 | this | stl.h:395:36:395:43 | constructor init of field first [pre-this] | |
-| stl.h:395:18:395:18 | x | stl.h:395:42:395:42 | x | |
-| stl.h:395:18:395:18 | x | stl.h:395:42:395:42 | x | |
-| stl.h:395:18:395:18 | x | stl.h:395:42:395:42 | x | |
-| stl.h:395:18:395:18 | x | stl.h:395:42:395:42 | x | |
-| stl.h:395:18:395:18 | x | stl.h:395:42:395:42 | x | |
-| stl.h:395:18:395:18 | x | stl.h:395:42:395:42 | x | |
-| stl.h:395:31:395:31 | y | stl.h:395:53:395:53 | y | |
-| stl.h:395:31:395:31 | y | stl.h:395:53:395:53 | y | |
-| stl.h:395:31:395:31 | y | stl.h:395:53:395:53 | y | |
-| stl.h:395:31:395:31 | y | stl.h:395:53:395:53 | y | |
-| stl.h:395:31:395:31 | y | stl.h:395:53:395:53 | y | |
-| stl.h:395:31:395:31 | y | stl.h:395:53:395:53 | y | |
-| stl.h:395:36:395:43 | call to unknown function | stl.h:395:36:395:43 | constructor init of field first | TAINT |
-| stl.h:395:36:395:43 | constructor init of field first [post-this] | stl.h:395:46:395:54 | constructor init of field second [pre-this] | |
-| stl.h:395:36:395:43 | constructor init of field first [post-this] | stl.h:395:46:395:54 | constructor init of field second [pre-this] | |
-| stl.h:395:36:395:43 | constructor init of field first [post-this] | stl.h:395:46:395:54 | constructor init of field second [pre-this] | |
-| stl.h:395:36:395:43 | constructor init of field first [post-this] | stl.h:395:46:395:54 | constructor init of field second [pre-this] | |
-| stl.h:395:36:395:43 | constructor init of field first [post-this] | stl.h:395:46:395:54 | constructor init of field second [pre-this] | |
-| stl.h:395:36:395:43 | constructor init of field first [post-this] | stl.h:395:46:395:54 | constructor init of field second [pre-this] | |
-| stl.h:395:36:395:43 | constructor init of field first [pre-this] | stl.h:395:46:395:54 | constructor init of field second [pre-this] | |
-| stl.h:395:36:395:43 | constructor init of field first [pre-this] | stl.h:395:46:395:54 | constructor init of field second [pre-this] | |
-| stl.h:395:36:395:43 | constructor init of field first [pre-this] | stl.h:395:46:395:54 | constructor init of field second [pre-this] | |
-| stl.h:395:36:395:43 | constructor init of field first [pre-this] | stl.h:395:46:395:54 | constructor init of field second [pre-this] | |
-| stl.h:395:36:395:43 | constructor init of field first [pre-this] | stl.h:395:46:395:54 | constructor init of field second [pre-this] | |
-| stl.h:395:36:395:43 | constructor init of field first [pre-this] | stl.h:395:46:395:54 | constructor init of field second [pre-this] | |
-| stl.h:395:42:395:42 | x | stl.h:395:36:395:43 | constructor init of field first | TAINT |
-| stl.h:395:42:395:42 | x | stl.h:395:36:395:43 | constructor init of field first | TAINT |
-| stl.h:395:42:395:42 | x | stl.h:395:36:395:43 | constructor init of field first | TAINT |
-| stl.h:395:42:395:42 | x | stl.h:395:36:395:43 | constructor init of field first | TAINT |
-| stl.h:395:42:395:42 | x | stl.h:395:36:395:43 | constructor init of field first | TAINT |
-| stl.h:395:46:395:54 | call to unknown function | stl.h:395:46:395:54 | constructor init of field second | TAINT |
-| stl.h:395:53:395:53 | y | stl.h:395:46:395:54 | constructor init of field second | TAINT |
-| stl.h:395:53:395:53 | y | stl.h:395:46:395:54 | constructor init of field second | TAINT |
-| stl.h:395:53:395:53 | y | stl.h:395:46:395:54 | constructor init of field second | TAINT |
-| stl.h:395:53:395:53 | y | stl.h:395:46:395:54 | constructor init of field second | TAINT |
-| stl.h:395:53:395:53 | y | stl.h:395:46:395:54 | constructor init of field second | TAINT |
-| stl.h:401:87:401:87 | x | stl.h:401:87:401:87 | x | |
-| stl.h:401:87:401:87 | x | stl.h:401:87:401:87 | x | |
-| stl.h:401:87:401:87 | x | stl.h:401:87:401:87 | x | |
-| stl.h:401:87:401:87 | x | stl.h:401:87:401:87 | x | |
-| stl.h:401:87:401:87 | x | stl.h:401:87:401:87 | x | |
-| stl.h:401:87:401:87 | x | stl.h:401:87:401:87 | x | |
-| stl.h:401:87:401:87 | x | stl.h:401:87:401:87 | x | |
-| stl.h:401:87:401:87 | x | stl.h:402:58:402:58 | x | |
-| stl.h:401:87:401:87 | x | stl.h:402:58:402:58 | x | |
-| stl.h:401:87:401:87 | x | stl.h:402:58:402:58 | x | |
-| stl.h:401:87:401:87 | x | stl.h:402:58:402:58 | x | |
-| stl.h:401:87:401:87 | x | stl.h:402:58:402:58 | x | |
-| stl.h:401:87:401:87 | x | stl.h:402:58:402:58 | x | |
-| stl.h:401:87:401:87 | x | stl.h:402:58:402:58 | x | |
-| stl.h:401:95:401:95 | y | stl.h:401:95:401:95 | y | |
-| stl.h:401:95:401:95 | y | stl.h:401:95:401:95 | y | |
-| stl.h:401:95:401:95 | y | stl.h:401:95:401:95 | y | |
-| stl.h:401:95:401:95 | y | stl.h:401:95:401:95 | y | |
-| stl.h:401:95:401:95 | y | stl.h:401:95:401:95 | y | |
-| stl.h:401:95:401:95 | y | stl.h:401:95:401:95 | y | |
-| stl.h:401:95:401:95 | y | stl.h:401:95:401:95 | y | |
-| stl.h:401:95:401:95 | y | stl.h:402:79:402:79 | y | |
-| stl.h:401:95:401:95 | y | stl.h:402:79:402:79 | y | |
-| stl.h:401:95:401:95 | y | stl.h:402:79:402:79 | y | |
-| stl.h:401:95:401:95 | y | stl.h:402:79:402:79 | y | |
-| stl.h:401:95:401:95 | y | stl.h:402:79:402:79 | y | |
-| stl.h:401:95:401:95 | y | stl.h:402:79:402:79 | y | |
-| stl.h:401:95:401:95 | y | stl.h:402:79:402:79 | y | |
-| stl.h:402:58:402:58 | x | stl.h:402:41:402:56 | call to forward | |
-| stl.h:402:58:402:58 | x | stl.h:402:41:402:56 | call to forward | |
-| stl.h:402:58:402:58 | x | stl.h:402:41:402:56 | call to forward | |
-| stl.h:402:58:402:58 | x | stl.h:402:41:402:56 | call to forward | |
-| stl.h:402:58:402:58 | x | stl.h:402:41:402:56 | call to forward | |
-| stl.h:402:58:402:58 | x | stl.h:402:41:402:56 | call to forward | |
-| stl.h:402:62:402:77 | call to forward | stl.h:402:3:402:82 | call to pair | TAINT |
-| stl.h:402:62:402:77 | call to forward | stl.h:402:3:402:82 | call to pair | TAINT |
-| stl.h:402:62:402:77 | call to forward | stl.h:402:3:402:82 | call to pair | TAINT |
-| stl.h:402:62:402:77 | call to forward | stl.h:402:3:402:82 | call to pair | TAINT |
-| stl.h:402:62:402:77 | call to forward | stl.h:402:3:402:82 | call to pair | TAINT |
-| stl.h:402:62:402:77 | call to forward | stl.h:402:3:402:82 | call to pair | TAINT |
-| stl.h:402:79:402:79 | y | stl.h:402:3:402:82 | call to pair | TAINT |
-| stl.h:402:79:402:79 | y | stl.h:402:3:402:82 | call to pair | TAINT |
-| stl.h:402:79:402:79 | y | stl.h:402:3:402:82 | call to pair | TAINT |
-| stl.h:402:79:402:79 | y | stl.h:402:3:402:82 | call to pair | TAINT |
-| stl.h:402:79:402:79 | y | stl.h:402:3:402:82 | call to pair | TAINT |
-| stl.h:402:79:402:79 | y | stl.h:402:3:402:82 | call to pair | TAINT |
-| stl.h:402:79:402:79 | y | stl.h:402:62:402:77 | call to forward | |
-| stl.h:402:79:402:79 | y | stl.h:402:62:402:77 | call to forward | |
-| stl.h:402:79:402:79 | y | stl.h:402:62:402:77 | call to forward | |
-| stl.h:402:79:402:79 | y | stl.h:402:62:402:77 | call to forward | |
-| stl.h:402:79:402:79 | y | stl.h:402:62:402:77 | call to forward | |
-| stl.h:402:79:402:79 | y | stl.h:402:62:402:77 | call to forward | |
+| stl.h:389:9:389:9 | Unknown literal | stl.h:389:9:389:9 | constructor init of field first | TAINT |
+| stl.h:389:9:389:9 | Unknown literal | stl.h:389:9:389:9 | constructor init of field first | TAINT |
+| stl.h:389:9:389:9 | Unknown literal | stl.h:389:9:389:9 | constructor init of field first | TAINT |
+| stl.h:389:9:389:9 | Unknown literal | stl.h:389:9:389:9 | constructor init of field first | TAINT |
+| stl.h:389:9:389:9 | Unknown literal | stl.h:389:9:389:9 | constructor init of field first | TAINT |
+| stl.h:389:9:389:9 | Unknown literal | stl.h:389:9:389:9 | constructor init of field second | TAINT |
+| stl.h:389:9:389:9 | Unknown literal | stl.h:389:9:389:9 | constructor init of field second | TAINT |
+| stl.h:389:9:389:9 | Unknown literal | stl.h:389:9:389:9 | constructor init of field second | TAINT |
+| stl.h:389:9:389:9 | Unknown literal | stl.h:389:9:389:9 | constructor init of field second | TAINT |
+| stl.h:389:9:389:9 | Unknown literal | stl.h:389:9:389:9 | constructor init of field second | TAINT |
+| stl.h:389:9:389:9 | constructor init of field first [post-this] | stl.h:389:9:389:9 | constructor init of field second [pre-this] | |
+| stl.h:389:9:389:9 | constructor init of field first [post-this] | stl.h:389:9:389:9 | constructor init of field second [pre-this] | |
+| stl.h:389:9:389:9 | constructor init of field first [post-this] | stl.h:389:9:389:9 | constructor init of field second [pre-this] | |
+| stl.h:389:9:389:9 | constructor init of field first [post-this] | stl.h:389:9:389:9 | constructor init of field second [pre-this] | |
+| stl.h:389:9:389:9 | constructor init of field first [post-this] | stl.h:389:9:389:9 | constructor init of field second [pre-this] | |
+| stl.h:389:9:389:9 | constructor init of field first [pre-this] | stl.h:389:9:389:9 | constructor init of field second [pre-this] | |
+| stl.h:389:9:389:9 | constructor init of field first [pre-this] | stl.h:389:9:389:9 | constructor init of field second [pre-this] | |
+| stl.h:389:9:389:9 | constructor init of field first [pre-this] | stl.h:389:9:389:9 | constructor init of field second [pre-this] | |
+| stl.h:389:9:389:9 | constructor init of field first [pre-this] | stl.h:389:9:389:9 | constructor init of field second [pre-this] | |
+| stl.h:389:9:389:9 | constructor init of field first [pre-this] | stl.h:389:9:389:9 | constructor init of field second [pre-this] | |
+| stl.h:389:9:389:9 | this | stl.h:389:9:389:9 | constructor init of field first [pre-this] | |
+| stl.h:389:9:389:9 | this | stl.h:389:9:389:9 | constructor init of field first [pre-this] | |
+| stl.h:389:9:389:9 | this | stl.h:389:9:389:9 | constructor init of field first [pre-this] | |
+| stl.h:389:9:389:9 | this | stl.h:389:9:389:9 | constructor init of field first [pre-this] | |
+| stl.h:389:9:389:9 | this | stl.h:389:9:389:9 | constructor init of field first [pre-this] | |
+| stl.h:396:3:396:3 | this | stl.h:396:36:396:43 | constructor init of field first [pre-this] | |
+| stl.h:396:3:396:3 | this | stl.h:396:36:396:43 | constructor init of field first [pre-this] | |
+| stl.h:396:3:396:3 | this | stl.h:396:36:396:43 | constructor init of field first [pre-this] | |
+| stl.h:396:3:396:3 | this | stl.h:396:36:396:43 | constructor init of field first [pre-this] | |
+| stl.h:396:3:396:3 | this | stl.h:396:36:396:43 | constructor init of field first [pre-this] | |
+| stl.h:396:3:396:6 | this | stl.h:396:36:396:43 | constructor init of field first [pre-this] | |
+| stl.h:396:18:396:18 | x | stl.h:396:42:396:42 | x | |
+| stl.h:396:18:396:18 | x | stl.h:396:42:396:42 | x | |
+| stl.h:396:18:396:18 | x | stl.h:396:42:396:42 | x | |
+| stl.h:396:18:396:18 | x | stl.h:396:42:396:42 | x | |
+| stl.h:396:18:396:18 | x | stl.h:396:42:396:42 | x | |
+| stl.h:396:18:396:18 | x | stl.h:396:42:396:42 | x | |
+| stl.h:396:31:396:31 | y | stl.h:396:53:396:53 | y | |
+| stl.h:396:31:396:31 | y | stl.h:396:53:396:53 | y | |
+| stl.h:396:31:396:31 | y | stl.h:396:53:396:53 | y | |
+| stl.h:396:31:396:31 | y | stl.h:396:53:396:53 | y | |
+| stl.h:396:31:396:31 | y | stl.h:396:53:396:53 | y | |
+| stl.h:396:31:396:31 | y | stl.h:396:53:396:53 | y | |
+| stl.h:396:36:396:43 | call to unknown function | stl.h:396:36:396:43 | constructor init of field first | TAINT |
+| stl.h:396:36:396:43 | constructor init of field first [post-this] | stl.h:396:46:396:54 | constructor init of field second [pre-this] | |
+| stl.h:396:36:396:43 | constructor init of field first [post-this] | stl.h:396:46:396:54 | constructor init of field second [pre-this] | |
+| stl.h:396:36:396:43 | constructor init of field first [post-this] | stl.h:396:46:396:54 | constructor init of field second [pre-this] | |
+| stl.h:396:36:396:43 | constructor init of field first [post-this] | stl.h:396:46:396:54 | constructor init of field second [pre-this] | |
+| stl.h:396:36:396:43 | constructor init of field first [post-this] | stl.h:396:46:396:54 | constructor init of field second [pre-this] | |
+| stl.h:396:36:396:43 | constructor init of field first [post-this] | stl.h:396:46:396:54 | constructor init of field second [pre-this] | |
+| stl.h:396:36:396:43 | constructor init of field first [pre-this] | stl.h:396:46:396:54 | constructor init of field second [pre-this] | |
+| stl.h:396:36:396:43 | constructor init of field first [pre-this] | stl.h:396:46:396:54 | constructor init of field second [pre-this] | |
+| stl.h:396:36:396:43 | constructor init of field first [pre-this] | stl.h:396:46:396:54 | constructor init of field second [pre-this] | |
+| stl.h:396:36:396:43 | constructor init of field first [pre-this] | stl.h:396:46:396:54 | constructor init of field second [pre-this] | |
+| stl.h:396:36:396:43 | constructor init of field first [pre-this] | stl.h:396:46:396:54 | constructor init of field second [pre-this] | |
+| stl.h:396:36:396:43 | constructor init of field first [pre-this] | stl.h:396:46:396:54 | constructor init of field second [pre-this] | |
+| stl.h:396:42:396:42 | x | stl.h:396:36:396:43 | constructor init of field first | TAINT |
+| stl.h:396:42:396:42 | x | stl.h:396:36:396:43 | constructor init of field first | TAINT |
+| stl.h:396:42:396:42 | x | stl.h:396:36:396:43 | constructor init of field first | TAINT |
+| stl.h:396:42:396:42 | x | stl.h:396:36:396:43 | constructor init of field first | TAINT |
+| stl.h:396:42:396:42 | x | stl.h:396:36:396:43 | constructor init of field first | TAINT |
+| stl.h:396:46:396:54 | call to unknown function | stl.h:396:46:396:54 | constructor init of field second | TAINT |
+| stl.h:396:53:396:53 | y | stl.h:396:46:396:54 | constructor init of field second | TAINT |
+| stl.h:396:53:396:53 | y | stl.h:396:46:396:54 | constructor init of field second | TAINT |
+| stl.h:396:53:396:53 | y | stl.h:396:46:396:54 | constructor init of field second | TAINT |
+| stl.h:396:53:396:53 | y | stl.h:396:46:396:54 | constructor init of field second | TAINT |
+| stl.h:396:53:396:53 | y | stl.h:396:46:396:54 | constructor init of field second | TAINT |
+| stl.h:402:87:402:87 | x | stl.h:402:87:402:87 | x | |
+| stl.h:402:87:402:87 | x | stl.h:402:87:402:87 | x | |
+| stl.h:402:87:402:87 | x | stl.h:402:87:402:87 | x | |
+| stl.h:402:87:402:87 | x | stl.h:402:87:402:87 | x | |
+| stl.h:402:87:402:87 | x | stl.h:402:87:402:87 | x | |
+| stl.h:402:87:402:87 | x | stl.h:402:87:402:87 | x | |
+| stl.h:402:87:402:87 | x | stl.h:402:87:402:87 | x | |
+| stl.h:402:87:402:87 | x | stl.h:403:58:403:58 | x | |
+| stl.h:402:87:402:87 | x | stl.h:403:58:403:58 | x | |
+| stl.h:402:87:402:87 | x | stl.h:403:58:403:58 | x | |
+| stl.h:402:87:402:87 | x | stl.h:403:58:403:58 | x | |
+| stl.h:402:87:402:87 | x | stl.h:403:58:403:58 | x | |
+| stl.h:402:87:402:87 | x | stl.h:403:58:403:58 | x | |
+| stl.h:402:87:402:87 | x | stl.h:403:58:403:58 | x | |
+| stl.h:402:95:402:95 | y | stl.h:402:95:402:95 | y | |
+| stl.h:402:95:402:95 | y | stl.h:402:95:402:95 | y | |
+| stl.h:402:95:402:95 | y | stl.h:402:95:402:95 | y | |
+| stl.h:402:95:402:95 | y | stl.h:402:95:402:95 | y | |
+| stl.h:402:95:402:95 | y | stl.h:402:95:402:95 | y | |
+| stl.h:402:95:402:95 | y | stl.h:402:95:402:95 | y | |
+| stl.h:402:95:402:95 | y | stl.h:402:95:402:95 | y | |
+| stl.h:402:95:402:95 | y | stl.h:403:79:403:79 | y | |
+| stl.h:402:95:402:95 | y | stl.h:403:79:403:79 | y | |
+| stl.h:402:95:402:95 | y | stl.h:403:79:403:79 | y | |
+| stl.h:402:95:402:95 | y | stl.h:403:79:403:79 | y | |
+| stl.h:402:95:402:95 | y | stl.h:403:79:403:79 | y | |
+| stl.h:402:95:402:95 | y | stl.h:403:79:403:79 | y | |
+| stl.h:402:95:402:95 | y | stl.h:403:79:403:79 | y | |
+| stl.h:403:58:403:58 | x | stl.h:403:41:403:56 | call to forward | |
+| stl.h:403:58:403:58 | x | stl.h:403:41:403:56 | call to forward | |
+| stl.h:403:58:403:58 | x | stl.h:403:41:403:56 | call to forward | |
+| stl.h:403:58:403:58 | x | stl.h:403:41:403:56 | call to forward | |
+| stl.h:403:58:403:58 | x | stl.h:403:41:403:56 | call to forward | |
+| stl.h:403:58:403:58 | x | stl.h:403:41:403:56 | call to forward | |
+| stl.h:403:62:403:77 | call to forward | stl.h:403:3:403:82 | call to pair | TAINT |
+| stl.h:403:62:403:77 | call to forward | stl.h:403:3:403:82 | call to pair | TAINT |
+| stl.h:403:62:403:77 | call to forward | stl.h:403:3:403:82 | call to pair | TAINT |
+| stl.h:403:62:403:77 | call to forward | stl.h:403:3:403:82 | call to pair | TAINT |
+| stl.h:403:62:403:77 | call to forward | stl.h:403:3:403:82 | call to pair | TAINT |
+| stl.h:403:62:403:77 | call to forward | stl.h:403:3:403:82 | call to pair | TAINT |
+| stl.h:403:79:403:79 | y | stl.h:403:3:403:82 | call to pair | TAINT |
+| stl.h:403:79:403:79 | y | stl.h:403:3:403:82 | call to pair | TAINT |
+| stl.h:403:79:403:79 | y | stl.h:403:3:403:82 | call to pair | TAINT |
+| stl.h:403:79:403:79 | y | stl.h:403:3:403:82 | call to pair | TAINT |
+| stl.h:403:79:403:79 | y | stl.h:403:3:403:82 | call to pair | TAINT |
+| stl.h:403:79:403:79 | y | stl.h:403:3:403:82 | call to pair | TAINT |
+| stl.h:403:79:403:79 | y | stl.h:403:62:403:77 | call to forward | |
+| stl.h:403:79:403:79 | y | stl.h:403:62:403:77 | call to forward | |
+| stl.h:403:79:403:79 | y | stl.h:403:62:403:77 | call to forward | |
+| stl.h:403:79:403:79 | y | stl.h:403:62:403:77 | call to forward | |
+| stl.h:403:79:403:79 | y | stl.h:403:62:403:77 | call to forward | |
+| stl.h:403:79:403:79 | y | stl.h:403:62:403:77 | call to forward | |
| string.cpp:25:12:25:17 | call to source | string.cpp:29:7:29:7 | a | |
| string.cpp:26:16:26:20 | 123 | string.cpp:26:16:26:21 | call to basic_string | TAINT |
| string.cpp:26:16:26:21 | call to basic_string | string.cpp:30:7:30:7 | b | |
diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/stl.h b/cpp/ql/test/library-tests/dataflow/taint-tests/stl.h
index 1382552bdcaf..44550c3df768 100644
--- a/cpp/ql/test/library-tests/dataflow/taint-tests/stl.h
+++ b/cpp/ql/test/library-tests/dataflow/taint-tests/stl.h
@@ -348,7 +348,8 @@ namespace std {
class shared_ptr {
public:
shared_ptr() noexcept;
- explicit shared_ptr(T*); shared_ptr(const shared_ptr&) noexcept;
+ explicit shared_ptr(T*);
+ shared_ptr(const shared_ptr&) noexcept;
template shared_ptr(const shared_ptr&) noexcept;
template shared_ptr(shared_ptr&&) noexcept;
From 8e11abca40b0b60a590bc9e245917d5796b72fd3 Mon Sep 17 00:00:00 2001
From: Taus Brock-Nannestad
Date: Tue, 6 Apr 2021 17:39:41 +0200
Subject: [PATCH 100/433] Revert "Merge pull request #5552 from
RasmusWL/revert-import-change"
This reverts commit 49d1937dc42ed2afe51a82191deec05e9f58ac12, reversing
changes made to d4877a9038ae9d94b35d4a543d38599802b3f934.
---
python/ql/src/semmle/python/Files.qll | 30 +++++++++++++++++++
python/ql/src/semmle/python/Module.qll | 7 ++++-
.../modules/entry_point/hash_bang/main.py | 7 +++++
.../modules/entry_point/hash_bang/module.py | 2 ++
.../namespace_package_main.py | 2 ++
.../namespace_package_module.py | 1 +
.../entry_point/hash_bang/package/__init__.py | 2 ++
.../hash_bang/package/package_main.py | 2 ++
.../hash_bang/package/package_module.py | 1 +
.../modules/entry_point/modules.expected | 16 ++++++++++
.../modules/entry_point/modules.ql | 4 +++
.../modules/entry_point/name_main/main.py | 8 +++++
.../modules/entry_point/name_main/module.py | 2 ++
.../namespace_package_main.py | 2 ++
.../namespace_package_module.py | 1 +
.../entry_point/name_main/package/__init__.py | 2 ++
.../name_main/package/package_main.py | 2 ++
.../name_main/package/package_module.py | 1 +
.../entry_point/no_py_extension/main.secretpy | 6 ++++
.../entry_point/no_py_extension/module.py | 2 ++
.../namespace_package_main.py | 2 ++
.../namespace_package_module.py | 1 +
.../no_py_extension/package/__init__.py | 2 ++
.../no_py_extension/package/package_main.py | 2 ++
.../no_py_extension/package/package_module.py | 1 +
.../library-tests/modules/entry_point/options | 1 +
26 files changed, 108 insertions(+), 1 deletion(-)
create mode 100755 python/ql/test/3/library-tests/modules/entry_point/hash_bang/main.py
create mode 100644 python/ql/test/3/library-tests/modules/entry_point/hash_bang/module.py
create mode 100644 python/ql/test/3/library-tests/modules/entry_point/hash_bang/namespace_package/namespace_package_main.py
create mode 100644 python/ql/test/3/library-tests/modules/entry_point/hash_bang/namespace_package/namespace_package_module.py
create mode 100644 python/ql/test/3/library-tests/modules/entry_point/hash_bang/package/__init__.py
create mode 100644 python/ql/test/3/library-tests/modules/entry_point/hash_bang/package/package_main.py
create mode 100644 python/ql/test/3/library-tests/modules/entry_point/hash_bang/package/package_module.py
create mode 100644 python/ql/test/3/library-tests/modules/entry_point/modules.expected
create mode 100644 python/ql/test/3/library-tests/modules/entry_point/modules.ql
create mode 100755 python/ql/test/3/library-tests/modules/entry_point/name_main/main.py
create mode 100644 python/ql/test/3/library-tests/modules/entry_point/name_main/module.py
create mode 100644 python/ql/test/3/library-tests/modules/entry_point/name_main/namespace_package/namespace_package_main.py
create mode 100644 python/ql/test/3/library-tests/modules/entry_point/name_main/namespace_package/namespace_package_module.py
create mode 100644 python/ql/test/3/library-tests/modules/entry_point/name_main/package/__init__.py
create mode 100644 python/ql/test/3/library-tests/modules/entry_point/name_main/package/package_main.py
create mode 100644 python/ql/test/3/library-tests/modules/entry_point/name_main/package/package_module.py
create mode 100755 python/ql/test/3/library-tests/modules/entry_point/no_py_extension/main.secretpy
create mode 100644 python/ql/test/3/library-tests/modules/entry_point/no_py_extension/module.py
create mode 100644 python/ql/test/3/library-tests/modules/entry_point/no_py_extension/namespace_package/namespace_package_main.py
create mode 100644 python/ql/test/3/library-tests/modules/entry_point/no_py_extension/namespace_package/namespace_package_module.py
create mode 100644 python/ql/test/3/library-tests/modules/entry_point/no_py_extension/package/__init__.py
create mode 100644 python/ql/test/3/library-tests/modules/entry_point/no_py_extension/package/package_main.py
create mode 100644 python/ql/test/3/library-tests/modules/entry_point/no_py_extension/package/package_module.py
create mode 100644 python/ql/test/3/library-tests/modules/entry_point/options
diff --git a/python/ql/src/semmle/python/Files.qll b/python/ql/src/semmle/python/Files.qll
index ef6484fbdc6e..83ba92f0abcc 100644
--- a/python/ql/src/semmle/python/Files.qll
+++ b/python/ql/src/semmle/python/Files.qll
@@ -72,6 +72,33 @@ class File extends Container {
* are specified to be extracted.
*/
string getContents() { file_contents(this, result) }
+
+ /** Holds if this file is likely to get executed directly, and thus act as an entry point for execution. */
+ predicate maybeExecutedDirectly() {
+ // Only consider files in the source code, and not things like the standard library
+ exists(this.getRelativePath()) and
+ (
+ // The file doesn't have the extension `.py` but still contains Python statements
+ not this.getExtension().matches("py%") and
+ exists(Stmt s | s.getLocation().getFile() = this)
+ or
+ // The file contains the usual `if __name__ == '__main__':` construction
+ exists(If i, Name name, StrConst main, Cmpop op |
+ i.getScope().(Module).getFile() = this and
+ op instanceof Eq and
+ i.getTest().(Compare).compares(name, op, main) and
+ name.getId() = "__name__" and
+ main.getText() = "__main__"
+ )
+ or
+ // The file contains a `#!` line referencing the python interpreter
+ exists(Comment c |
+ c.getLocation().getFile() = this and
+ c.getLocation().getStartLine() = 1 and
+ c.getText().regexpMatch("^#! */.*python(2|3)?[ \\\\t]*$")
+ )
+ )
+ }
}
private predicate occupied_line(File f, int n) {
@@ -121,6 +148,9 @@ class Folder extends Container {
this.getBaseName().regexpMatch("[^\\d\\W]\\w*") and
result = this.getParent().getImportRoot(n)
}
+
+ /** Holds if execution may start in a file in this directory. */
+ predicate mayContainEntryPoint() { any(File f | f.getParent() = this).maybeExecutedDirectly() }
}
/**
diff --git a/python/ql/src/semmle/python/Module.qll b/python/ql/src/semmle/python/Module.qll
index fcf1c0b29252..8a420a800ea6 100644
--- a/python/ql/src/semmle/python/Module.qll
+++ b/python/ql/src/semmle/python/Module.qll
@@ -204,8 +204,13 @@ private string moduleNameFromBase(Container file) {
string moduleNameFromFile(Container file) {
exists(string basename |
basename = moduleNameFromBase(file) and
- legalShortName(basename) and
+ legalShortName(basename)
+ |
result = moduleNameFromFile(file.getParent()) + "." + basename
+ or
+ // If execution can start in the folder containing this module, then we will assume `file` can
+ // be imported as an absolute import, and hence return `basename` as a possible name.
+ file.getParent().(Folder).mayContainEntryPoint() and result = basename
)
or
isPotentialSourcePackage(file) and
diff --git a/python/ql/test/3/library-tests/modules/entry_point/hash_bang/main.py b/python/ql/test/3/library-tests/modules/entry_point/hash_bang/main.py
new file mode 100755
index 000000000000..ad619e5cbd83
--- /dev/null
+++ b/python/ql/test/3/library-tests/modules/entry_point/hash_bang/main.py
@@ -0,0 +1,7 @@
+#! /usr/bin/python3
+print(__file__)
+import module
+import package
+import namespace_package
+import namespace_package.namespace_package_main
+print(module.message)
diff --git a/python/ql/test/3/library-tests/modules/entry_point/hash_bang/module.py b/python/ql/test/3/library-tests/modules/entry_point/hash_bang/module.py
new file mode 100644
index 000000000000..36206ca60b75
--- /dev/null
+++ b/python/ql/test/3/library-tests/modules/entry_point/hash_bang/module.py
@@ -0,0 +1,2 @@
+print(__file__.split("entry_point")[1])
+message = "Hello world!"
diff --git a/python/ql/test/3/library-tests/modules/entry_point/hash_bang/namespace_package/namespace_package_main.py b/python/ql/test/3/library-tests/modules/entry_point/hash_bang/namespace_package/namespace_package_main.py
new file mode 100644
index 000000000000..5db80f18a278
--- /dev/null
+++ b/python/ql/test/3/library-tests/modules/entry_point/hash_bang/namespace_package/namespace_package_main.py
@@ -0,0 +1,2 @@
+print(__file__.split("entry_point")[1])
+import namespace_package.namespace_package_module
diff --git a/python/ql/test/3/library-tests/modules/entry_point/hash_bang/namespace_package/namespace_package_module.py b/python/ql/test/3/library-tests/modules/entry_point/hash_bang/namespace_package/namespace_package_module.py
new file mode 100644
index 000000000000..567a23d59ce3
--- /dev/null
+++ b/python/ql/test/3/library-tests/modules/entry_point/hash_bang/namespace_package/namespace_package_module.py
@@ -0,0 +1 @@
+print(__file__.split("entry_point")[1])
diff --git a/python/ql/test/3/library-tests/modules/entry_point/hash_bang/package/__init__.py b/python/ql/test/3/library-tests/modules/entry_point/hash_bang/package/__init__.py
new file mode 100644
index 000000000000..ca14a9f5804e
--- /dev/null
+++ b/python/ql/test/3/library-tests/modules/entry_point/hash_bang/package/__init__.py
@@ -0,0 +1,2 @@
+print(__file__.split("entry_point")[1])
+from . import package_main
diff --git a/python/ql/test/3/library-tests/modules/entry_point/hash_bang/package/package_main.py b/python/ql/test/3/library-tests/modules/entry_point/hash_bang/package/package_main.py
new file mode 100644
index 000000000000..158b12678e3b
--- /dev/null
+++ b/python/ql/test/3/library-tests/modules/entry_point/hash_bang/package/package_main.py
@@ -0,0 +1,2 @@
+print(__file__.split("entry_point")[1])
+from . import package_module
diff --git a/python/ql/test/3/library-tests/modules/entry_point/hash_bang/package/package_module.py b/python/ql/test/3/library-tests/modules/entry_point/hash_bang/package/package_module.py
new file mode 100644
index 000000000000..567a23d59ce3
--- /dev/null
+++ b/python/ql/test/3/library-tests/modules/entry_point/hash_bang/package/package_module.py
@@ -0,0 +1 @@
+print(__file__.split("entry_point")[1])
diff --git a/python/ql/test/3/library-tests/modules/entry_point/modules.expected b/python/ql/test/3/library-tests/modules/entry_point/modules.expected
new file mode 100644
index 000000000000..e5f6b300074c
--- /dev/null
+++ b/python/ql/test/3/library-tests/modules/entry_point/modules.expected
@@ -0,0 +1,16 @@
+| main | hash_bang/main.py:0:0:0:0 | Script main |
+| main | name_main/main.py:0:0:0:0 | Module main |
+| module | hash_bang/module.py:0:0:0:0 | Module module |
+| module | name_main/module.py:0:0:0:0 | Module module |
+| package | hash_bang/package:0:0:0:0 | Package package |
+| package | name_main/package:0:0:0:0 | Package package |
+| package | no_py_extension/package:0:0:0:0 | Package package |
+| package.__init__ | hash_bang/package/__init__.py:0:0:0:0 | Module package.__init__ |
+| package.__init__ | name_main/package/__init__.py:0:0:0:0 | Module package.__init__ |
+| package.__init__ | no_py_extension/package/__init__.py:0:0:0:0 | Module package.__init__ |
+| package.package_main | hash_bang/package/package_main.py:0:0:0:0 | Module package.package_main |
+| package.package_main | name_main/package/package_main.py:0:0:0:0 | Module package.package_main |
+| package.package_main | no_py_extension/package/package_main.py:0:0:0:0 | Module package.package_main |
+| package.package_module | hash_bang/package/package_module.py:0:0:0:0 | Module package.package_module |
+| package.package_module | name_main/package/package_module.py:0:0:0:0 | Module package.package_module |
+| package.package_module | no_py_extension/package/package_module.py:0:0:0:0 | Module package.package_module |
diff --git a/python/ql/test/3/library-tests/modules/entry_point/modules.ql b/python/ql/test/3/library-tests/modules/entry_point/modules.ql
new file mode 100644
index 000000000000..10c390e8616c
--- /dev/null
+++ b/python/ql/test/3/library-tests/modules/entry_point/modules.ql
@@ -0,0 +1,4 @@
+import python
+
+from Module m
+select m.getName(), m
diff --git a/python/ql/test/3/library-tests/modules/entry_point/name_main/main.py b/python/ql/test/3/library-tests/modules/entry_point/name_main/main.py
new file mode 100755
index 000000000000..08ec212383bb
--- /dev/null
+++ b/python/ql/test/3/library-tests/modules/entry_point/name_main/main.py
@@ -0,0 +1,8 @@
+print(__file__)
+import module
+import package
+import namespace_package
+import namespace_package.namespace_package_main
+
+if __name__ == '__main__':
+ print(module.message)
diff --git a/python/ql/test/3/library-tests/modules/entry_point/name_main/module.py b/python/ql/test/3/library-tests/modules/entry_point/name_main/module.py
new file mode 100644
index 000000000000..36206ca60b75
--- /dev/null
+++ b/python/ql/test/3/library-tests/modules/entry_point/name_main/module.py
@@ -0,0 +1,2 @@
+print(__file__.split("entry_point")[1])
+message = "Hello world!"
diff --git a/python/ql/test/3/library-tests/modules/entry_point/name_main/namespace_package/namespace_package_main.py b/python/ql/test/3/library-tests/modules/entry_point/name_main/namespace_package/namespace_package_main.py
new file mode 100644
index 000000000000..5db80f18a278
--- /dev/null
+++ b/python/ql/test/3/library-tests/modules/entry_point/name_main/namespace_package/namespace_package_main.py
@@ -0,0 +1,2 @@
+print(__file__.split("entry_point")[1])
+import namespace_package.namespace_package_module
diff --git a/python/ql/test/3/library-tests/modules/entry_point/name_main/namespace_package/namespace_package_module.py b/python/ql/test/3/library-tests/modules/entry_point/name_main/namespace_package/namespace_package_module.py
new file mode 100644
index 000000000000..567a23d59ce3
--- /dev/null
+++ b/python/ql/test/3/library-tests/modules/entry_point/name_main/namespace_package/namespace_package_module.py
@@ -0,0 +1 @@
+print(__file__.split("entry_point")[1])
diff --git a/python/ql/test/3/library-tests/modules/entry_point/name_main/package/__init__.py b/python/ql/test/3/library-tests/modules/entry_point/name_main/package/__init__.py
new file mode 100644
index 000000000000..ca14a9f5804e
--- /dev/null
+++ b/python/ql/test/3/library-tests/modules/entry_point/name_main/package/__init__.py
@@ -0,0 +1,2 @@
+print(__file__.split("entry_point")[1])
+from . import package_main
diff --git a/python/ql/test/3/library-tests/modules/entry_point/name_main/package/package_main.py b/python/ql/test/3/library-tests/modules/entry_point/name_main/package/package_main.py
new file mode 100644
index 000000000000..158b12678e3b
--- /dev/null
+++ b/python/ql/test/3/library-tests/modules/entry_point/name_main/package/package_main.py
@@ -0,0 +1,2 @@
+print(__file__.split("entry_point")[1])
+from . import package_module
diff --git a/python/ql/test/3/library-tests/modules/entry_point/name_main/package/package_module.py b/python/ql/test/3/library-tests/modules/entry_point/name_main/package/package_module.py
new file mode 100644
index 000000000000..567a23d59ce3
--- /dev/null
+++ b/python/ql/test/3/library-tests/modules/entry_point/name_main/package/package_module.py
@@ -0,0 +1 @@
+print(__file__.split("entry_point")[1])
diff --git a/python/ql/test/3/library-tests/modules/entry_point/no_py_extension/main.secretpy b/python/ql/test/3/library-tests/modules/entry_point/no_py_extension/main.secretpy
new file mode 100755
index 000000000000..e2673d4da786
--- /dev/null
+++ b/python/ql/test/3/library-tests/modules/entry_point/no_py_extension/main.secretpy
@@ -0,0 +1,6 @@
+print(__file__)
+import module
+import package
+import namespace_package
+import namespace_package.namespace_package_main
+print(module.message)
diff --git a/python/ql/test/3/library-tests/modules/entry_point/no_py_extension/module.py b/python/ql/test/3/library-tests/modules/entry_point/no_py_extension/module.py
new file mode 100644
index 000000000000..36206ca60b75
--- /dev/null
+++ b/python/ql/test/3/library-tests/modules/entry_point/no_py_extension/module.py
@@ -0,0 +1,2 @@
+print(__file__.split("entry_point")[1])
+message = "Hello world!"
diff --git a/python/ql/test/3/library-tests/modules/entry_point/no_py_extension/namespace_package/namespace_package_main.py b/python/ql/test/3/library-tests/modules/entry_point/no_py_extension/namespace_package/namespace_package_main.py
new file mode 100644
index 000000000000..5db80f18a278
--- /dev/null
+++ b/python/ql/test/3/library-tests/modules/entry_point/no_py_extension/namespace_package/namespace_package_main.py
@@ -0,0 +1,2 @@
+print(__file__.split("entry_point")[1])
+import namespace_package.namespace_package_module
diff --git a/python/ql/test/3/library-tests/modules/entry_point/no_py_extension/namespace_package/namespace_package_module.py b/python/ql/test/3/library-tests/modules/entry_point/no_py_extension/namespace_package/namespace_package_module.py
new file mode 100644
index 000000000000..567a23d59ce3
--- /dev/null
+++ b/python/ql/test/3/library-tests/modules/entry_point/no_py_extension/namespace_package/namespace_package_module.py
@@ -0,0 +1 @@
+print(__file__.split("entry_point")[1])
diff --git a/python/ql/test/3/library-tests/modules/entry_point/no_py_extension/package/__init__.py b/python/ql/test/3/library-tests/modules/entry_point/no_py_extension/package/__init__.py
new file mode 100644
index 000000000000..ca14a9f5804e
--- /dev/null
+++ b/python/ql/test/3/library-tests/modules/entry_point/no_py_extension/package/__init__.py
@@ -0,0 +1,2 @@
+print(__file__.split("entry_point")[1])
+from . import package_main
diff --git a/python/ql/test/3/library-tests/modules/entry_point/no_py_extension/package/package_main.py b/python/ql/test/3/library-tests/modules/entry_point/no_py_extension/package/package_main.py
new file mode 100644
index 000000000000..158b12678e3b
--- /dev/null
+++ b/python/ql/test/3/library-tests/modules/entry_point/no_py_extension/package/package_main.py
@@ -0,0 +1,2 @@
+print(__file__.split("entry_point")[1])
+from . import package_module
diff --git a/python/ql/test/3/library-tests/modules/entry_point/no_py_extension/package/package_module.py b/python/ql/test/3/library-tests/modules/entry_point/no_py_extension/package/package_module.py
new file mode 100644
index 000000000000..567a23d59ce3
--- /dev/null
+++ b/python/ql/test/3/library-tests/modules/entry_point/no_py_extension/package/package_module.py
@@ -0,0 +1 @@
+print(__file__.split("entry_point")[1])
diff --git a/python/ql/test/3/library-tests/modules/entry_point/options b/python/ql/test/3/library-tests/modules/entry_point/options
new file mode 100644
index 000000000000..6beceeb08ed0
--- /dev/null
+++ b/python/ql/test/3/library-tests/modules/entry_point/options
@@ -0,0 +1 @@
+semmle-extractor-options: --lang=3 --path bogus -R . --filter=include:**/*.secretpy
From b44db460f613164309f6d8f0726f8b1f2658edb6 Mon Sep 17 00:00:00 2001
From: Taus
Date: Tue, 6 Apr 2021 19:55:43 +0000
Subject: [PATCH 101/433] Python: Only track modules that are imported
---
python/ql/src/semmle/python/Module.qll | 21 ++++++++++++++++---
.../modules/entry_point/modules.expected | 2 --
2 files changed, 18 insertions(+), 5 deletions(-)
diff --git a/python/ql/src/semmle/python/Module.qll b/python/ql/src/semmle/python/Module.qll
index 8a420a800ea6..48d0e9e1a3d9 100644
--- a/python/ql/src/semmle/python/Module.qll
+++ b/python/ql/src/semmle/python/Module.qll
@@ -201,6 +201,20 @@ private string moduleNameFromBase(Container file) {
file instanceof File and result = file.getStem()
}
+/**
+ * Holds if `file` may be transitively imported from a file that may serve as the entry point of
+ * the execution.
+ */
+private predicate transitively_imported_from_entry_point(File file) {
+ file.getExtension().matches("%py%") and
+ exists(File importer |
+ importer.getParent() = file.getParent() and
+ exists(ImportExpr i | i.getLocation().getFile() = importer and i.getName() = file.getStem())
+ |
+ importer.maybeExecutedDirectly() or transitively_imported_from_entry_point(importer)
+ )
+}
+
string moduleNameFromFile(Container file) {
exists(string basename |
basename = moduleNameFromBase(file) and
@@ -208,9 +222,10 @@ string moduleNameFromFile(Container file) {
|
result = moduleNameFromFile(file.getParent()) + "." + basename
or
- // If execution can start in the folder containing this module, then we will assume `file` can
- // be imported as an absolute import, and hence return `basename` as a possible name.
- file.getParent().(Folder).mayContainEntryPoint() and result = basename
+ // If `file` is a transitive import of a file that's executed directly, we allow references
+ // to it by its `basename`.
+ transitively_imported_from_entry_point(file) and
+ result = basename
)
or
isPotentialSourcePackage(file) and
diff --git a/python/ql/test/3/library-tests/modules/entry_point/modules.expected b/python/ql/test/3/library-tests/modules/entry_point/modules.expected
index e5f6b300074c..cdc743a360d1 100644
--- a/python/ql/test/3/library-tests/modules/entry_point/modules.expected
+++ b/python/ql/test/3/library-tests/modules/entry_point/modules.expected
@@ -1,5 +1,3 @@
-| main | hash_bang/main.py:0:0:0:0 | Script main |
-| main | name_main/main.py:0:0:0:0 | Module main |
| module | hash_bang/module.py:0:0:0:0 | Module module |
| module | name_main/module.py:0:0:0:0 | Module module |
| package | hash_bang/package:0:0:0:0 | Package package |
From 43ae7462b44b4fc9561ee1cc6c246507e32b04c0 Mon Sep 17 00:00:00 2001
From: Taus
Date: Tue, 6 Apr 2021 19:55:43 +0000
Subject: [PATCH 102/433] Python: Only track modules that are imported
This greatly restricts the set of modules that have a new name under
this scheme.
One change to the tests was needed, which reflects the fact that the
two `main.py` files no longer have the name `main` (which makes sense,
since they're never imported under this name).
---
python/ql/src/semmle/python/Module.qll | 21 ++++++++++++++++---
.../modules/entry_point/modules.expected | 2 --
2 files changed, 18 insertions(+), 5 deletions(-)
diff --git a/python/ql/src/semmle/python/Module.qll b/python/ql/src/semmle/python/Module.qll
index 8a420a800ea6..48d0e9e1a3d9 100644
--- a/python/ql/src/semmle/python/Module.qll
+++ b/python/ql/src/semmle/python/Module.qll
@@ -201,6 +201,20 @@ private string moduleNameFromBase(Container file) {
file instanceof File and result = file.getStem()
}
+/**
+ * Holds if `file` may be transitively imported from a file that may serve as the entry point of
+ * the execution.
+ */
+private predicate transitively_imported_from_entry_point(File file) {
+ file.getExtension().matches("%py%") and
+ exists(File importer |
+ importer.getParent() = file.getParent() and
+ exists(ImportExpr i | i.getLocation().getFile() = importer and i.getName() = file.getStem())
+ |
+ importer.maybeExecutedDirectly() or transitively_imported_from_entry_point(importer)
+ )
+}
+
string moduleNameFromFile(Container file) {
exists(string basename |
basename = moduleNameFromBase(file) and
@@ -208,9 +222,10 @@ string moduleNameFromFile(Container file) {
|
result = moduleNameFromFile(file.getParent()) + "." + basename
or
- // If execution can start in the folder containing this module, then we will assume `file` can
- // be imported as an absolute import, and hence return `basename` as a possible name.
- file.getParent().(Folder).mayContainEntryPoint() and result = basename
+ // If `file` is a transitive import of a file that's executed directly, we allow references
+ // to it by its `basename`.
+ transitively_imported_from_entry_point(file) and
+ result = basename
)
or
isPotentialSourcePackage(file) and
diff --git a/python/ql/test/3/library-tests/modules/entry_point/modules.expected b/python/ql/test/3/library-tests/modules/entry_point/modules.expected
index e5f6b300074c..cdc743a360d1 100644
--- a/python/ql/test/3/library-tests/modules/entry_point/modules.expected
+++ b/python/ql/test/3/library-tests/modules/entry_point/modules.expected
@@ -1,5 +1,3 @@
-| main | hash_bang/main.py:0:0:0:0 | Script main |
-| main | name_main/main.py:0:0:0:0 | Module main |
| module | hash_bang/module.py:0:0:0:0 | Module module |
| module | name_main/module.py:0:0:0:0 | Module module |
| package | hash_bang/package:0:0:0:0 | Package package |
From acf8fd0f038aaafed40c7d3e8643024dcc2a8f97 Mon Sep 17 00:00:00 2001
From: yoff
Date: Tue, 6 Apr 2021 22:45:03 +0200
Subject: [PATCH 103/433] Apply suggestions from code review
Co-authored-by: Rasmus Wriedt Larsen
---
.../src/Security/CWE-327/FluentApiModel.qll | 22 ++++++++----------
python/ql/src/Security/CWE-327/Ssl.qll | 23 ++++---------------
.../src/Security/CWE-327/TlsLibraryModel.qll | 2 +-
3 files changed, 16 insertions(+), 31 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/FluentApiModel.qll b/python/ql/src/Security/CWE-327/FluentApiModel.qll
index 803b7adab40e..c6836ba60579 100644
--- a/python/ql/src/Security/CWE-327/FluentApiModel.qll
+++ b/python/ql/src/Security/CWE-327/FluentApiModel.qll
@@ -3,7 +3,8 @@ import TlsLibraryModel
/**
* Configuration to determine the state of a context being used to create
- * a conection.
+ * a conection. There is one configuration for each pair of `TlsLibrary` and `ProtocolVersion`,
+ * such that a single configuration only tracks contexts where a specific `ProtocolVersion` is allowed.
*
* The state is in terms of whether a specific protocol is allowed. This is
* either true or false when the context is created and can then be modified
@@ -72,20 +73,18 @@ predicate unsafe_connection_creation_with_context(
boolean specific
) {
// Connection created from a context allowing `insecure_version`.
- exists(InsecureContextConfiguration c, ProtocolUnrestriction co |
- c.hasFlow(co, connectionCreation)
+ exists(InsecureContextConfiguration c |
+ c.hasFlow(contextOrigin, connectionCreation)
|
insecure_version = c.getTrackedVersion() and
- contextOrigin = co and
+ contextOrigin instanceof ProtocolUnrestriction and
specific = false
)
or
// Connection created from a context specifying `insecure_version`.
- exists(TlsLibrary l, DataFlow::CfgNode cc |
- cc = l.insecure_connection_creation(insecure_version)
- |
- connectionCreation = cc and
- contextOrigin = cc and
+ exists(TlsLibrary l |
+ connectionCreation = l.insecure_connection_creation(insecure_version) and
+ contextOrigin = connectionCreation and
specific = true
)
}
@@ -105,7 +104,6 @@ predicate unsafe_connection_creation_without_context(
/** Holds if `contextCreation` is creating a context ties to a specific insecure version. */
predicate unsafe_context_creation(DataFlow::CallCfgNode contextCreation, string insecure_version) {
- exists(TlsLibrary l, ContextCreation cc | cc = l.insecure_context_creation(insecure_version) |
- contextCreation = cc
- )
+ contextCreation instanceof ContextCreation and
+ exists(TlsLibrary l | contextCreation = l.insecure_context_creation(insecure_version))
}
diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll
index 928a5f74e2d3..9def499fb118 100644
--- a/python/ql/src/Security/CWE-327/Ssl.qll
+++ b/python/ql/src/Security/CWE-327/Ssl.qll
@@ -23,30 +23,17 @@ class SSLDefaultContextCreation extends ContextCreation {
}
/** Gets a reference to an `ssl.Context` instance. */
-private DataFlow::LocalSourceNode sslContextInstance(DataFlow::TypeTracker t) {
- t.start() and
- result = API::moduleImport("ssl").getMember(["SSLContext", "create_default_context"]).getACall()
- or
- exists(DataFlow::TypeTracker t2 | result = sslContextInstance(t2).track(t2, t))
-}
-
-/** Gets a reference to an `ssl.Context` instance. */
-DataFlow::Node sslContextInstance() {
- sslContextInstance(DataFlow::TypeTracker::end()).flowsTo(result)
+API::Node sslContextInstance() {
+ result = API::moduleImport("ssl").getMember(["SSLContext", "create_default_context"]).getReturn()
}
-class WrapSocketCall extends ConnectionCreation {
- override CallNode node;
-
+class WrapSocketCall extends ConnectionCreation, DataFlow::CallCfgNode {
WrapSocketCall() {
- exists(DataFlow::AttrRead call | node.getFunction() = call.asCfgNode() |
- call.getAttributeName() = "wrap_socket" and
- call.getObject() = sslContextInstance()
- )
+ this = sslContextInstance().getMember("wrap_socket").getACall()
}
override DataFlow::CfgNode getContext() {
- result.getNode() = node.getFunction().(AttrNode).getObject()
+ result = this.getFunction().(DataFlow::AttrRead).getObject()
}
}
diff --git a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
index 245a60b02955..73ab52be8070 100644
--- a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
+++ b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
@@ -84,7 +84,7 @@ abstract class UnspecificContextCreation extends ContextCreation, ProtocolUnrest
}
}
-/** A model of a TLS library. */
+/** A model of a SSL/TLS library. */
abstract class TlsLibrary extends string {
TlsLibrary() { this in ["ssl", "pyOpenSSL"] }
From 062668444220f9b00476c92df3dd1c6b52b33148 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Tue, 6 Apr 2021 22:55:32 +0200
Subject: [PATCH 104/433] Python: small cleanups enabled by review
---
python/ql/src/Security/CWE-327/FluentApiModel.qll | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/FluentApiModel.qll b/python/ql/src/Security/CWE-327/FluentApiModel.qll
index c6836ba60579..652a4bae2a10 100644
--- a/python/ql/src/Security/CWE-327/FluentApiModel.qll
+++ b/python/ql/src/Security/CWE-327/FluentApiModel.qll
@@ -3,7 +3,7 @@ import TlsLibraryModel
/**
* Configuration to determine the state of a context being used to create
- * a conection. There is one configuration for each pair of `TlsLibrary` and `ProtocolVersion`,
+ * a connection. There is one configuration for each pair of `TlsLibrary` and `ProtocolVersion`,
* such that a single configuration only tracks contexts where a specific `ProtocolVersion` is allowed.
*
* The state is in terms of whether a specific protocol is allowed. This is
@@ -73,11 +73,8 @@ predicate unsafe_connection_creation_with_context(
boolean specific
) {
// Connection created from a context allowing `insecure_version`.
- exists(InsecureContextConfiguration c |
- c.hasFlow(contextOrigin, connectionCreation)
- |
+ exists(InsecureContextConfiguration c | c.hasFlow(contextOrigin, connectionCreation) |
insecure_version = c.getTrackedVersion() and
- contextOrigin instanceof ProtocolUnrestriction and
specific = false
)
or
@@ -104,6 +101,5 @@ predicate unsafe_connection_creation_without_context(
/** Holds if `contextCreation` is creating a context ties to a specific insecure version. */
predicate unsafe_context_creation(DataFlow::CallCfgNode contextCreation, string insecure_version) {
- contextCreation instanceof ContextCreation and
exists(TlsLibrary l | contextCreation = l.insecure_context_creation(insecure_version))
}
From a44490b47053f6b7339a1c26b01070dfa878d73c Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Tue, 6 Apr 2021 22:56:07 +0200
Subject: [PATCH 105/433] Python: remove unused file
---
.../src/Security/CWE-327/examples/secure_protocol.py | 11 -----------
1 file changed, 11 deletions(-)
delete mode 100644 python/ql/src/Security/CWE-327/examples/secure_protocol.py
diff --git a/python/ql/src/Security/CWE-327/examples/secure_protocol.py b/python/ql/src/Security/CWE-327/examples/secure_protocol.py
deleted file mode 100644
index e349bdae832d..000000000000
--- a/python/ql/src/Security/CWE-327/examples/secure_protocol.py
+++ /dev/null
@@ -1,11 +0,0 @@
-import socket
-import ssl
-
-hostname = 'www.python.org'
-context = ssl.SSLContext(ssl.PROTOCOL_TLS)
-context.options |= ssl.OP_NO_TLSv1
-context.options |= ssl.OP_NO_TLSv1_1
-
-with socket.create_connection((hostname, 443)) as sock:
- with context.wrap_socket(sock, server_hostname=hostname) as ssock:
- print(ssock.version())
From 094d2f3b7d3d628b7e1a8e7d3999f6ce235bb642 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Tue, 6 Apr 2021 22:59:58 +0200
Subject: [PATCH 106/433] Python: clean up tests
---
.../Security/CWE-327/ssl_fluent.py | 59 ++-----------------
1 file changed, 6 insertions(+), 53 deletions(-)
diff --git a/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py b/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
index ceddaa32f091..0f7620f21ea1 100644
--- a/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
+++ b/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
@@ -56,58 +56,11 @@ def test_fluent_ssl():
print(ssock.version())
-def test_fluent_ssl_no_TLSv1():
- hostname = 'www.python.org'
- context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
- context.options |= ssl.OP_NO_TLSv1
-
- with socket.create_connection((hostname, 443)) as sock:
- with context.wrap_socket(sock, server_hostname=hostname) as ssock:
- print(ssock.version())
-
-def test_fluent_ssl_safe():
- hostname = 'www.python.org'
- context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
- context.options |= ssl.OP_NO_TLSv1
- context.options |= ssl.OP_NO_TLSv1_1
-
- with socket.create_connection((hostname, 443)) as sock:
- with context.wrap_socket(sock, server_hostname=hostname) as ssock:
- print(ssock.version())
-
-def test_fluent_ssl_safe_combined():
- hostname = 'www.python.org'
- context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
- context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
-
- with socket.create_connection((hostname, 443)) as sock:
- with context.wrap_socket(sock, server_hostname=hostname) as ssock:
- print(ssock.version())
-
-def test_fluent_ssl_unsafe_combined_wrongly():
- hostname = 'www.python.org'
- context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
- context.options |= ssl.OP_NO_TLSv1 & ssl.OP_NO_TLSv1_1
-
- with socket.create_connection((hostname, 443)) as sock:
- with context.wrap_socket(sock, server_hostname=hostname) as ssock:
- print(ssock.version())
-
-def test_fluent_ssl_safe_combined_multiple():
- hostname = 'www.python.org'
- context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
- context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_TLSv1_2
-
- with socket.create_connection((hostname, 443)) as sock:
- with context.wrap_socket(sock, server_hostname=hostname) as ssock:
- print(ssock.version())
-
-
def create_relaxed_context():
- return ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ return ssl.SSLContext(ssl.PROTOCOL_TLS)
def create_secure_context():
- context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS)
context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
return context
@@ -143,21 +96,21 @@ def test_delegated_context_made_unsafe():
print(ssock.version())
def test_delegated_connection_unsafe():
- context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS)
create_connection(context)
def test_delegated_connection_safe():
- context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS)
context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
create_connection(context)
def test_delegated_connection_made_safe():
- context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS)
context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
create_connection(context)
def test_delegated_connection_made_unsafe():
- context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS)
context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
context.options &= ~ssl.OP_NO_TLSv1_1
create_connection(context)
From fb95c488e8854e816ef8da12450ad262b47867a7 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Wed, 7 Apr 2021 08:20:52 +0200
Subject: [PATCH 107/433] Python: format
---
python/ql/src/Security/CWE-327/Ssl.qll | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll
index 9def499fb118..26928e31cb0b 100644
--- a/python/ql/src/Security/CWE-327/Ssl.qll
+++ b/python/ql/src/Security/CWE-327/Ssl.qll
@@ -28,9 +28,7 @@ API::Node sslContextInstance() {
}
class WrapSocketCall extends ConnectionCreation, DataFlow::CallCfgNode {
- WrapSocketCall() {
- this = sslContextInstance().getMember("wrap_socket").getACall()
- }
+ WrapSocketCall() { this = sslContextInstance().getMember("wrap_socket").getACall() }
override DataFlow::CfgNode getContext() {
result = this.getFunction().(DataFlow::AttrRead).getObject()
From a0e3e3afaf2456f9ad80c9fd9d639c2daecb1373 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Wed, 7 Apr 2021 08:22:36 +0200
Subject: [PATCH 108/433] Python: adjust test expectations
---
.../CWE-327/InsecureProtocol.expected | 29 +++++++++----------
1 file changed, 13 insertions(+), 16 deletions(-)
diff --git a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
index f4cd96eb82e2..57b19cef0b17 100644
--- a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
+++ b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected
@@ -24,19 +24,16 @@
| ssl_fluent.py:37:14:37:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:33:15:33:53 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
| ssl_fluent.py:55:14:55:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:52:15:52:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
| ssl_fluent.py:55:14:55:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:52:15:52:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:65:14:65:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:61:15:61:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:93:14:93:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:89:15:89:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:93:14:93:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:89:15:89:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:116:14:116:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:107:12:107:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:116:14:116:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:146:15:146:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:116:14:116:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:107:12:107:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:116:14:116:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:146:15:146:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:116:14:116:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:162:5:162:11 | ControlFlowNode for context | context modification |
-| ssl_fluent.py:116:14:116:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:180:5:180:11 | ControlFlowNode for context | context modification |
-| ssl_fluent.py:122:14:122:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:107:12:107:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:122:14:122:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:107:12:107:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
-| ssl_fluent.py:142:14:142:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:140:5:140:11 | ControlFlowNode for context | context modification |
-| ssl_fluent.py:191:14:191:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:188:5:188:11 | ControlFlowNode for context | context modification |
-| ssl_fluent.py:210:14:210:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv3 allowed by $@ | ssl_fluent.py:207:5:207:11 | ControlFlowNode for context | context modification |
-| ssl_fluent.py:210:14:210:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:206:15:206:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context |
-| ssl_fluent.py:210:14:210:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:206:15:206:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context |
+| ssl_fluent.py:69:14:69:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:60:12:60:43 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:69:14:69:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:99:15:99:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:69:14:69:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:60:12:60:43 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:69:14:69:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:99:15:99:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:69:14:69:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:115:5:115:11 | ControlFlowNode for context | context modification |
+| ssl_fluent.py:69:14:69:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:133:5:133:11 | ControlFlowNode for context | context modification |
+| ssl_fluent.py:75:14:75:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:60:12:60:43 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:75:14:75:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:60:12:60:43 | ControlFlowNode for Attribute() | call to ssl.SSLContext |
+| ssl_fluent.py:95:14:95:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:93:5:93:11 | ControlFlowNode for context | context modification |
+| ssl_fluent.py:144:14:144:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:141:5:141:11 | ControlFlowNode for context | context modification |
+| ssl_fluent.py:163:14:163:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv3 allowed by $@ | ssl_fluent.py:160:5:160:11 | ControlFlowNode for context | context modification |
+| ssl_fluent.py:163:14:163:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:159:15:159:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context |
+| ssl_fluent.py:163:14:163:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:159:15:159:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context |
From f22db2a30b305ba15019fb77463e42c2022cfe86 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Wed, 7 Apr 2021 08:32:21 +0200
Subject: [PATCH 109/433] Python: One family to rule them all...
---
python/ql/src/Security/CWE-327/TlsLibraryModel.qll | 13 ++++---------
1 file changed, 4 insertions(+), 9 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
index 73ab52be8070..82c251f3f810 100644
--- a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
+++ b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll
@@ -71,15 +71,10 @@ abstract class UnspecificContextCreation extends ContextCreation, ProtocolUnrest
override DataFlow::CfgNode getContext() { result = this }
override ProtocolVersion getUnrestriction() {
- // see https://www.openssl.org/docs/man1.1.0/man3/TLS_method.html
- family = "TLS" and
- result in ["SSLv3", "TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"]
- or
- // This can negotiate a TLS 1.3 connection (!)
- // see
- // - https://docs.python.org/3/library/ssl.html#ssl-contexts
- // - https://www.openssl.org/docs/man1.0.2/man3/TLSv1_method.html
- family = "SSLv23" and
+ // There is only one family, the two names are aliases in OpenSSL.
+ // see https://github.com/openssl/openssl/blob/13888e797c5a3193e91d71e5f5a196a2d68d266f/include/openssl/ssl.h.in#L1953-L1955
+ family in ["SSLv23", "TLS"] and
+ // see https://docs.python.org/3/library/ssl.html#ssl-contexts
result in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"]
}
}
From a006a92f8dd93e5503108e02762188f63fad288b Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Wed, 7 Apr 2021 08:32:40 +0200
Subject: [PATCH 110/433] Python: Expand commentary
---
python/ql/src/Security/CWE-327/Ssl.qll | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll
index 26928e31cb0b..fc4d13bffae9 100644
--- a/python/ql/src/Security/CWE-327/Ssl.qll
+++ b/python/ql/src/Security/CWE-327/Ssl.qll
@@ -141,13 +141,15 @@ class UnspecificSSLContextCreation extends SSLContextCreation, UnspecificContext
UnspecificSSLContextCreation() { library = "ssl" }
override ProtocolVersion getUnrestriction() {
+ // Case: A protocol argument is present.
result = UnspecificContextCreation.super.getUnrestriction() and
// These are turned off by default
// see https://docs.python.org/3/library/ssl.html#ssl-contexts
not result in ["SSLv2", "SSLv3"]
or
- // The default argument is TLS and the SSL versions are turned off by default.
+ // Case: No protocol arguemnt is present.
not exists(this.getProtocol()) and
+ // The default argument is TLS and the SSL versions are turned off by default.
result in ["TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"]
}
}
From 9c3b7e81c7c8b6ed00ef22f3e43485e777e9d7f3 Mon Sep 17 00:00:00 2001
From: ihsinme
Date: Tue, 6 Apr 2021 10:31:06 +0300
Subject: [PATCH 111/433] Add files via upload
---
...trolFlowManagementWhenUsingBitOperations.c | 4 +
...FlowManagementWhenUsingBitOperations.qhelp | 28 +++++++
...rolFlowManagementWhenUsingBitOperations.ql | 80 +++++++++++++++++++
3 files changed, 112 insertions(+)
create mode 100644 cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementWhenUsingBitOperations.c
create mode 100644 cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementWhenUsingBitOperations.qhelp
create mode 100644 cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementWhenUsingBitOperations.ql
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementWhenUsingBitOperations.c b/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementWhenUsingBitOperations.c
new file mode 100644
index 000000000000..276318431186
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementWhenUsingBitOperations.c
@@ -0,0 +1,4 @@
+if(len>0 & memset(buf,0,len)) return 1; // BAD: `memset` will be called regardless of the value of the `len` variable. moreover, one cannot be sure that it will happen after verification
+...
+if(len>0 && memset(buf,0,len)) return 1; // GOOD: `memset` will be called after the `len` variable has been checked.
+...
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementWhenUsingBitOperations.qhelp b/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementWhenUsingBitOperations.qhelp
new file mode 100644
index 000000000000..f49d5a4fe78f
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementWhenUsingBitOperations.qhelp
@@ -0,0 +1,28 @@
+
+
+
+Using bitwise operations can be a mistake in some situations. For example, if parameters are evaluated in an expression and the function should be called only upon certain test results. These bitwise operations look suspicious and require developer attention.
+
+
+
+
+
+We recommend that you evaluate the correctness of using the specified bit operations.
+
+
+
+The following example demonstrates the erroneous and fixed use of bit and logical operations.
+
+
+
+
+
+
+ CWE Common Weakness Enumeration:
+ CWE-691: Insufficient Control Flow Management.
+
+
+
+
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementWhenUsingBitOperations.ql b/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementWhenUsingBitOperations.ql
new file mode 100644
index 000000000000..a599fc19fde3
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementWhenUsingBitOperations.ql
@@ -0,0 +1,80 @@
+/**
+ * @name Errors When Using Bit Operations
+ * @description --Using bitwise operations can be a mistake in some situations.
+ * --For example, if parameters are evaluated in an expression and the function should be called only upon certain test results.
+ * --These bitwise operations look suspicious and require developer attention.
+ * @kind problem
+ * @id cpp/errors-when-using-bit-operations
+ * @problem.severity warning
+ * @precision medium
+ * @tags correctness
+ * security
+ * external/cwe/cwe-691
+ */
+
+import cpp
+import semmle.code.cpp.valuenumbering.GlobalValueNumbering
+
+/**
+ * Dangerous uses of bit operations.
+ * For example: `if(intA>0 & intA<10 & charBuf&myFunc(charBuf[intA]))`.
+ * In this case, the function will be called in any case, and even the sequence of the call is not guaranteed.
+ */
+class DangerousBitOperations extends Expr {
+ FunctionCall bfc;
+
+ /**
+ * The assignment indicates the conscious use of the bit operator.
+ * Use in comparison, conversion, or return value indicates conscious use of the bit operator.
+ * The use of shifts and bitwise operations on any element of an expression indicates a conscious use of the bitwise operator.
+ */
+ DangerousBitOperations() {
+ bfc = this.(BinaryBitwiseOperation).getRightOperand() and
+ not this.getParent*() instanceof AssignExpr and
+ not this.getParent*() instanceof Initializer and
+ not this.getParent*() instanceof ReturnStmt and
+ not this.getParent*() instanceof EqualityOperation and
+ not this.getParent*() instanceof UnaryLogicalOperation and
+ not this.getParent*() instanceof BinaryLogicalOperation and
+ not this.(BinaryBitwiseOperation).getAChild*() instanceof BitwiseXorExpr and
+ not this.(BinaryBitwiseOperation).getAChild*() instanceof LShiftExpr and
+ not this.(BinaryBitwiseOperation).getAChild*() instanceof RShiftExpr
+ }
+
+ /** Holds when part of a bit expression is used in a logical operation. */
+ predicate useInLogicalOperations() {
+ exists(BinaryLogicalOperation blop, Expr exp |
+ blop.getAChild*() = exp and
+ exp.(FunctionCall).getTarget() = bfc.getTarget() and
+ not exp.getParent() instanceof ComparisonOperation and
+ not exp.getParent() instanceof BinaryBitwiseOperation
+ )
+ }
+
+ /** Holds when part of a bit expression is used as part of another supply. For example, as an argument to another function. */
+ predicate useInOtherCalls() {
+ bfc.hasQualifier() or
+ bfc.getTarget() instanceof Operator or
+ exists(FunctionCall fc | fc.getAnArgument().getAChild*() = this) or
+ bfc.getTarget() instanceof BuiltInFunction
+ }
+
+ /** Holds when the bit expression contains both arguments and a function call. */
+ predicate dangerousArgumentChecking() {
+ not this.(BinaryBitwiseOperation).getLeftOperand() instanceof Call and
+ globalValueNumber(this.(BinaryBitwiseOperation).getLeftOperand().getAChild*()) =
+ globalValueNumber(bfc.getAnArgument())
+ }
+
+ /** Holds when function calls are present in the bit expression. */
+ predicate functionCallsInBitsExpression() {
+ this.(BinaryBitwiseOperation).getLeftOperand().getAChild*() instanceof FunctionCall
+ }
+}
+
+from DangerousBitOperations dbo
+where
+ not dbo.useInOtherCalls() and
+ dbo.useInLogicalOperations() and
+ (not dbo.functionCallsInBitsExpression() or dbo.dangerousArgumentChecking())
+select dbo, "this bit expression needs your attention"
From ed2a8db8c9e4b5e60032b97ad2028e4342459200 Mon Sep 17 00:00:00 2001
From: ihsinme
Date: Tue, 6 Apr 2021 10:32:05 +0300
Subject: [PATCH 112/433] Add files via upload
---
...wManagementWhenUsingBitOperations.expected | 2 ++
...FlowManagementWhenUsingBitOperations.qlref | 1 +
.../Security/CWE/CWE-691/semmle/tests/test.c | 28 +++++++++++++++++++
3 files changed, 31 insertions(+)
create mode 100644 cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/InsufficientControlFlowManagementWhenUsingBitOperations.expected
create mode 100644 cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/InsufficientControlFlowManagementWhenUsingBitOperations.qlref
create mode 100644 cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/test.c
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/InsufficientControlFlowManagementWhenUsingBitOperations.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/InsufficientControlFlowManagementWhenUsingBitOperations.expected
new file mode 100644
index 000000000000..6aa02a61eaf6
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/InsufficientControlFlowManagementWhenUsingBitOperations.expected
@@ -0,0 +1,2 @@
+| test.c:8:6:8:51 | ... & ... | this bit expression needs your attention |
+| test.c:10:6:10:30 | ... & ... | this bit expression needs your attention |
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/InsufficientControlFlowManagementWhenUsingBitOperations.qlref b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/InsufficientControlFlowManagementWhenUsingBitOperations.qlref
new file mode 100644
index 000000000000..9bf28db3c8a8
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/InsufficientControlFlowManagementWhenUsingBitOperations.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementWhenUsingBitOperations.ql
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/test.c b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/test.c
new file mode 100644
index 000000000000..fc30f7a6e97e
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/test.c
@@ -0,0 +1,28 @@
+int tmpFunction(){
+ return 5;
+}
+void workFunction_0(char *s) {
+ int intSize;
+ char buf[80];
+ if(intSize>0 && intSize<80 && memset(buf,0,intSize)) return; // GOOD
+ if(intSize>0 & intSize<80 & memset(buf,0,intSize)) return; // BAD
+ if(intSize>0 && tmpFunction()) return;
+ if(intSize<0 & tmpFunction()) return; // BAD
+}
+void workFunction_1(char *s) {
+ int intA,intB;
+
+ if(intA + intB) return; // BAD
+ if(intA + intB>4) return; // GOOD
+ if(intA>0 && (intA + intB)) return; // BAD
+ while(intA>0)
+ {
+ if(intB - intA<10) break;
+ intA--;
+ }while(intA>0); // BAD
+ while(intA>0)
+ {
+ if(intB - intA<10) break;
+ intA--;
+ } // GOOD
+}
From 36de496d47b6fb95058920cb3cf7412a5001af95 Mon Sep 17 00:00:00 2001
From: ihsinme
Date: Tue, 6 Apr 2021 10:35:28 +0300
Subject: [PATCH 113/433] Add files via upload
---
...rolFlowManagementAfterRefactoringTheCode.c | 17 +++
...lowManagementAfterRefactoringTheCode.qhelp | 28 +++++
...olFlowManagementAfterRefactoringTheCode.ql | 118 ++++++++++++++++++
3 files changed, 163 insertions(+)
create mode 100644 cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.c
create mode 100644 cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.qhelp
create mode 100644 cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.ql
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.c b/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.c
new file mode 100644
index 000000000000..3ba47068244d
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.c
@@ -0,0 +1,17 @@
+while(flagsLoop)
+{
+ ...
+ if(flagsIf) break;
+ ...
+}while(flagsLoop); // BAD: when exiting through `break`, it is possible to get into an eternal loop.
+...
+while(flagsLoop)
+{
+ ...
+ if(flagsIf) break;
+ ...
+} // GOOD: coreten cycle
+...
+if(intA+intB) return 1; // BAD: possibly no comparison
+...
+if(intA+intB>intC) return 1; // GOOD: correct comparison
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.qhelp b/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.qhelp
new file mode 100644
index 000000000000..2c485dfd0cf4
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.qhelp
@@ -0,0 +1,28 @@
+
+
+
+In some situations, after code refactoring, parts of the old constructs may remain. They are correctly accepted by the compiler, but can critically affect program execution. For example, if you switch from `do {...} while ();` to `while () {...}` with errors, you run the risk of running out of resources. These code snippets look suspicious and require the developer's attention.
+
+
+
+
+
+We recommend that you use more explicit code transformations.
+
+
+
+The following example demonstrates the erroneous and corrected sections of the code.
+
+
+
+
+
+
+ CWE Common Weakness Enumeration:
+ CWE-691: Insufficient Control Flow Management.
+
+
+
+
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.ql b/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.ql
new file mode 100644
index 000000000000..6b9639fe8b9b
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.ql
@@ -0,0 +1,118 @@
+/**
+ * @name Errors After Refactoring
+ * @description --In some situations, after code refactoring, parts of the old constructs may remain.
+ * --They are correctly accepted by the compiler, but can critically affect program execution.
+ * --For example, if you switch from `do {...} while ();` to `while () {...}` with errors, you run the risk of running out of resources.
+ * --These code snippets look suspicious and require the developer's attention.
+ * @kind problem
+ * @id cpp/errors-after-refactoring
+ * @problem.severity warning
+ * @precision medium
+ * @tags correctness
+ * security
+ * external/cwe/cwe-691
+ */
+
+import cpp
+import semmle.code.cpp.valuenumbering.HashCons
+import semmle.code.cpp.valuenumbering.GlobalValueNumbering
+
+/**
+ * Using `while` directly after the body of another` while`.
+ */
+class UsingWhileAfterWhile extends WhileStmt {
+ /**
+ * Using a loop call after another loop has finished running can result in an eternal loop.
+ * For example, perhaps as a result of refactoring, the `do ... while ()` loop was incorrectly corrected.
+ * Even in the case of deliberate use of such an expression, it is better to correct it.
+ */
+ UsingWhileAfterWhile() {
+ exists(WhileStmt wh1 |
+ wh1.getStmt().getAChild*().(BreakStmt).(ControlFlowNode).getASuccessor().getASuccessor() =
+ this and
+ hashCons(wh1.getCondition()) = hashCons(this.getCondition()) and
+ this.getStmt() instanceof EmptyStmt
+ )
+ or
+ exists(ForStmt fr1 |
+ fr1.getStmt().getAChild*().(BreakStmt).(ControlFlowNode).getASuccessor().getASuccessor() =
+ this and
+ hashCons(fr1.getCondition()) = hashCons(this.getCondition()) and
+ this.getStmt() instanceof EmptyStmt
+ )
+ }
+}
+
+/**
+ * Using arithmetic in comparison.
+ */
+class UsingArithmeticInComparison extends BinaryArithmeticOperation {
+ /**
+ * Using arithmetic operations in a comparison operation can be dangerous.
+ * For example, part of the comparison may have been lost as a result of refactoring.
+ * Even if you deliberately use such an expression, it is better to add an explicit comparison.
+ */
+ UsingArithmeticInComparison() {
+ this.getParent*() instanceof IfStmt and
+ not this.getAChild*().isConstant() and
+ not this.getParent*() instanceof Call and
+ not this.getParent*() instanceof AssignExpr and
+ not this.getParent*() instanceof RemExpr and
+ not this.getParent*() instanceof AssignBitwiseOperation and
+ not this.getParent*() instanceof AssignArithmeticOperation and
+ not this.getParent*() instanceof EqualityOperation and
+ not this.getParent*() instanceof RelationalOperation
+ }
+
+ /** Holds when the expression is inside the loop body. */
+ predicate insideTheLoop() { exists(Loop lp | lp.getStmt().getAChild*() = this.getParent*()) }
+
+ /** Holds when the expression is used in binary operations. */
+ predicate workingWithValue() {
+ this.getParent*() instanceof BinaryBitwiseOperation or
+ this.getParent*() instanceof NotExpr
+ }
+
+ /** Holds when the expression contains a pointer. */
+ predicate workingWithPointer() {
+ this.getAChild*().getFullyConverted().getType() instanceof DerivedType
+ }
+
+ /** Holds when a null comparison expression exists. */
+ predicate compareWithZero() {
+ exists(Expr exp |
+ exp instanceof ComparisonOperation and
+ (
+ globalValueNumber(exp.getAChild*()) = globalValueNumber(this) or
+ hashCons(exp.getAChild*()) = hashCons(this)
+ ) and
+ (
+ exp.(ComparisonOperation).getLeftOperand().getValue() = "0" or
+ exp.(ComparisonOperation).getRightOperand().getValue() = "0"
+ )
+ )
+ }
+
+ /** Holds when a comparison expression exists. */
+ predicate compareWithOutZero() {
+ exists(Expr exp |
+ exp instanceof ComparisonOperation and
+ (
+ globalValueNumber(exp.getAChild*()) = globalValueNumber(this) or
+ hashCons(exp.getAChild*()) = hashCons(this)
+ )
+ )
+ }
+}
+
+from Expr exp, WhileStmt wst
+where
+ exp instanceof UsingArithmeticInComparison and
+ not exp.(UsingArithmeticInComparison).workingWithValue() and
+ not exp.(UsingArithmeticInComparison).workingWithPointer() and
+ not exp.(UsingArithmeticInComparison).insideTheLoop() and
+ not exp.(UsingArithmeticInComparison).compareWithZero() and
+ exp.(UsingArithmeticInComparison).compareWithOutZero()
+ or
+ wst instanceof UsingWhileAfterWhile and exp = wst.getCondition()
+select exp, "this expression needs your attention"
From cbf158ea6bbd4f5f98c5e1ad997fe6a58902e993 Mon Sep 17 00:00:00 2001
From: ihsinme
Date: Tue, 6 Apr 2021 10:36:22 +0300
Subject: [PATCH 114/433] Add files via upload
---
...ManagementAfterRefactoringTheCode.expected | 3 ++
...lowManagementAfterRefactoringTheCode.qlref | 1 +
.../Security/CWE/CWE-691/semmle/tests/test.c | 28 +++++++++++++++++++
3 files changed, 32 insertions(+)
create mode 100644 cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/InsufficientControlFlowManagementAfterRefactoringTheCode.expected
create mode 100644 cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/InsufficientControlFlowManagementAfterRefactoringTheCode.qlref
create mode 100644 cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/test.c
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/InsufficientControlFlowManagementAfterRefactoringTheCode.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/InsufficientControlFlowManagementAfterRefactoringTheCode.expected
new file mode 100644
index 000000000000..cc1b5cf46d80
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/InsufficientControlFlowManagementAfterRefactoringTheCode.expected
@@ -0,0 +1,3 @@
+| test.c:15:6:15:16 | ... + ... | this expression needs your attention |
+| test.c:17:17:17:27 | ... + ... | this expression needs your attention |
+| test.c:22:10:22:15 | ... > ... | this expression needs your attention |
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/InsufficientControlFlowManagementAfterRefactoringTheCode.qlref b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/InsufficientControlFlowManagementAfterRefactoringTheCode.qlref
new file mode 100644
index 000000000000..496d5f1b7be6
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/InsufficientControlFlowManagementAfterRefactoringTheCode.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.ql
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/test.c b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/test.c
new file mode 100644
index 000000000000..fc30f7a6e97e
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/test.c
@@ -0,0 +1,28 @@
+int tmpFunction(){
+ return 5;
+}
+void workFunction_0(char *s) {
+ int intSize;
+ char buf[80];
+ if(intSize>0 && intSize<80 && memset(buf,0,intSize)) return; // GOOD
+ if(intSize>0 & intSize<80 & memset(buf,0,intSize)) return; // BAD
+ if(intSize>0 && tmpFunction()) return;
+ if(intSize<0 & tmpFunction()) return; // BAD
+}
+void workFunction_1(char *s) {
+ int intA,intB;
+
+ if(intA + intB) return; // BAD
+ if(intA + intB>4) return; // GOOD
+ if(intA>0 && (intA + intB)) return; // BAD
+ while(intA>0)
+ {
+ if(intB - intA<10) break;
+ intA--;
+ }while(intA>0); // BAD
+ while(intA>0)
+ {
+ if(intB - intA<10) break;
+ intA--;
+ } // GOOD
+}
From 6c69c1aeeb10737bd63bc95ed8c7759342116cdb Mon Sep 17 00:00:00 2001
From: Taus
Date: Wed, 7 Apr 2021 10:47:21 +0000
Subject: [PATCH 115/433] Python: Minor cleanup
---
python/ql/src/semmle/python/Files.qll | 5 +----
python/ql/src/semmle/python/Module.qll | 2 +-
2 files changed, 2 insertions(+), 5 deletions(-)
diff --git a/python/ql/src/semmle/python/Files.qll b/python/ql/src/semmle/python/Files.qll
index 83ba92f0abcc..6eb6b2a18aca 100644
--- a/python/ql/src/semmle/python/Files.qll
+++ b/python/ql/src/semmle/python/Files.qll
@@ -74,7 +74,7 @@ class File extends Container {
string getContents() { file_contents(this, result) }
/** Holds if this file is likely to get executed directly, and thus act as an entry point for execution. */
- predicate maybeExecutedDirectly() {
+ predicate isPossibleEntryPoint() {
// Only consider files in the source code, and not things like the standard library
exists(this.getRelativePath()) and
(
@@ -148,9 +148,6 @@ class Folder extends Container {
this.getBaseName().regexpMatch("[^\\d\\W]\\w*") and
result = this.getParent().getImportRoot(n)
}
-
- /** Holds if execution may start in a file in this directory. */
- predicate mayContainEntryPoint() { any(File f | f.getParent() = this).maybeExecutedDirectly() }
}
/**
diff --git a/python/ql/src/semmle/python/Module.qll b/python/ql/src/semmle/python/Module.qll
index 48d0e9e1a3d9..753be2605d2e 100644
--- a/python/ql/src/semmle/python/Module.qll
+++ b/python/ql/src/semmle/python/Module.qll
@@ -211,7 +211,7 @@ private predicate transitively_imported_from_entry_point(File file) {
importer.getParent() = file.getParent() and
exists(ImportExpr i | i.getLocation().getFile() = importer and i.getName() = file.getStem())
|
- importer.maybeExecutedDirectly() or transitively_imported_from_entry_point(importer)
+ importer.isPossibleEntryPoint() or transitively_imported_from_entry_point(importer)
)
}
From 38daeb4df23057c160c744387316c012ea80f92a Mon Sep 17 00:00:00 2001
From: yoff
Date: Wed, 7 Apr 2021 15:50:51 +0200
Subject: [PATCH 116/433] Apply suggestions from code review
Co-authored-by: Rasmus Wriedt Larsen
---
python/ql/src/Security/CWE-327/FluentApiModel.qll | 4 ++--
python/ql/src/Security/CWE-327/Ssl.qll | 3 ++-
python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py | 2 ++
3 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/python/ql/src/Security/CWE-327/FluentApiModel.qll b/python/ql/src/Security/CWE-327/FluentApiModel.qll
index 652a4bae2a10..b5d6823c44c2 100644
--- a/python/ql/src/Security/CWE-327/FluentApiModel.qll
+++ b/python/ql/src/Security/CWE-327/FluentApiModel.qll
@@ -65,8 +65,8 @@ class InsecureContextConfiguration extends DataFlow::Configuration {
* Holds if `conectionCreation` marks the creation of a connetion based on the contex
* found at `contextOrigin` and allowing `insecure_version`.
*
- * `specific` is true iff the context is configured for a specific protocol version rather
- * than for a family of protocols.
+ * `specific` is true iff the context is configured for a specific protocol version (`ssl.PROTOCOL_TLSv1_2`) rather
+ * than for a family of protocols (`ssl.PROTOCOL_TLS`).
*/
predicate unsafe_connection_creation_with_context(
DataFlow::Node connectionCreation, ProtocolVersion insecure_version, DataFlow::Node contextOrigin,
diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll
index fc4d13bffae9..2ca7dcce1f90 100644
--- a/python/ql/src/Security/CWE-327/Ssl.qll
+++ b/python/ql/src/Security/CWE-327/Ssl.qll
@@ -149,7 +149,8 @@ class UnspecificSSLContextCreation extends SSLContextCreation, UnspecificContext
or
// Case: No protocol arguemnt is present.
not exists(this.getProtocol()) and
- // The default argument is TLS and the SSL versions are turned off by default.
+ // The default argument is TLS and the SSL versions are turned off by default since Python 3.6
+ // see https://docs.python.org/3.6/library/ssl.html#ssl.SSLContext
result in ["TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"]
}
}
diff --git a/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py b/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
index 0f7620f21ea1..a8e491a42f1e 100644
--- a/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
+++ b/python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py
@@ -49,6 +49,8 @@ def test_fluent_tls_safe():
def test_fluent_ssl():
hostname = 'www.python.org'
+ # notice that `ssl.PROTOCOL_SSLv23` is just a deprecated alias for `ssl.PROTOCOL_TLS`.
+ # Therefore, we only have this one test using PROTOCOL_SSLv23, to show that we handle this alias correctly.
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
with socket.create_connection((hostname, 443)) as sock:
From 80ac2aff268cd11501e8aaea05842c63bbc7afb4 Mon Sep 17 00:00:00 2001
From: Artem Smotrakov
Date: Wed, 7 Apr 2021 20:55:03 +0300
Subject: [PATCH 117/433] Fixed typos
Co-authored-by: Marcono1234
---
.../CWE-094/JakartaExpressionInjection.qhelp | 18 +++++++++---------
.../SaferExpressionEvaluationWithJuel.java | 4 ++--
2 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.qhelp b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.qhelp
index 6e6b5f633947..8a9e4e7276f0 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.qhelp
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.qhelp
@@ -4,22 +4,22 @@
Jakarta Expression Language (EL) is an expression language for Java applications.
-There are a single language specification and multiple implementations
+There is a single language specification and multiple implementations
such as Glassfish, Juel, Apache Commons EL, etc.
The language allows invocation of methods available in the JVM.
If an expression is built using attacker-controlled data,
-and then evaluated, then it may allow the attacker to run arbitrary code.
+and then evaluated, it may allow the attacker to run arbitrary code.
It is generally recommended to avoid using untrusted data in an EL expression.
-Before using untrusted data to build an EL expressoin, the data should be validated
-to ensure it is not evaluated as expression language. If the EL implementaion offers
-configuring a sandbox for EL expression, they should be run in a restircitive sandbox
+Before using untrusted data to build an EL expression, the data should be validated
+to ensure it is not evaluated as expression language. If the EL implementation offers
+configuring a sandbox for EL expressions, they should be run in a restrictive sandbox
that allows accessing only explicitly allowed classes. If the EL implementation
-does not allow sandboxing, consider using other expressiong language implementations
+does not support sandboxing, consider using other expression language implementations
with sandboxing capabilities such as Apache Commons JEXL or the Spring Expression Language.
@@ -32,9 +32,9 @@ using the JUEL interpreter:
-JUEL does not allow to run expression in a sandbox. To prevent running arbitrary code,
-incoming data has to be checked before including to an expression. The next example
-uses a Regex pattern to check whether a user tries to run an allowed exression or not:
+JUEL does not support to run expressions in a sandbox. To prevent running arbitrary code,
+incoming data has to be checked before including it in an expression. The next example
+uses a Regex pattern to check whether a user tries to run an allowed expression or not:
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/SaferExpressionEvaluationWithJuel.java b/java/ql/src/experimental/Security/CWE/CWE-094/SaferExpressionEvaluationWithJuel.java
index 54fb9a0ed36c..3dfaaead68a5 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-094/SaferExpressionEvaluationWithJuel.java
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/SaferExpressionEvaluationWithJuel.java
@@ -1,10 +1,10 @@
String input = getRemoteUserInput();
String pattern = "(inside|outside)\\.(temperature|humidity)";
if (!input.matches(pattern)) {
- throw new IllegalArgumentException("Unexpected exression");
+ throw new IllegalArgumentException("Unexpected expression");
}
String expression = "${" + input + "}";
ExpressionFactory factory = new de.odysseus.el.ExpressionFactoryImpl();
ValueExpression e = factory.createValueExpression(context, expression, Object.class);
SimpleContext context = getContext();
-Object result = e.getValue(context);
\ No newline at end of file
+Object result = e.getValue(context);
From 3d8e173c5770a32804cbffb798ce63cfaddb795d Mon Sep 17 00:00:00 2001
From: Artem Smotrakov
Date: Wed, 7 Apr 2021 20:59:07 +0300
Subject: [PATCH 118/433] Removed a reference to Apache Commons EL
---
.../Security/CWE/CWE-094/JakartaExpressionInjection.qhelp | 4 ----
1 file changed, 4 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.qhelp b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.qhelp
index 8a9e4e7276f0..a6d66c1d1d16 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.qhelp
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.qhelp
@@ -57,9 +57,5 @@ uses a Regex pattern to check whether a user tries to run an allowed expression
JUEL:
Home page
-
- Apache Foundation:
- Apache Commons EL
-
From c13ee0859acb4222aae8d4071d039494edfc782a Mon Sep 17 00:00:00 2001
From: Artem Smotrakov
Date: Wed, 7 Apr 2021 21:02:21 +0300
Subject: [PATCH 119/433] LambdaExpression should extend JakartaType
---
.../Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll
index 22f421b8bf8c..5b652d3d8e65 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll
@@ -97,6 +97,6 @@ private class MethodExpression extends JakartaType {
MethodExpression() { hasName("MethodExpression") }
}
-private class LambdaExpression extends RefType {
+private class LambdaExpression extends JakartaType {
LambdaExpression() { hasName("LambdaExpression") }
}
From a764a79090e17ce22b7e1a3d8927dc80f0effd1b Mon Sep 17 00:00:00 2001
From: Artem Smotrakov
Date: Wed, 7 Apr 2021 21:12:21 +0300
Subject: [PATCH 120/433] Always bind arguments in TaintPropagatingCall
---
.../CWE-094/JakartaExpressionInjectionLib.qll | 20 ++++++++++---------
.../JakartaExpressionInjection.expected | 18 -----------------
2 files changed, 11 insertions(+), 27 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll
index 5b652d3d8e65..e3a3994c8818 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll
@@ -56,15 +56,17 @@ private class TaintPropagatingCall extends Call {
TaintPropagatingCall() {
taintFromExpr = this.getArgument(1) and
- exists(Method m | this.(MethodAccess).getMethod() = m |
- m.getDeclaringType() instanceof ExpressionFactory and
- m.hasName(["createValueExpression", "createMethodExpression"]) and
- taintFromExpr.getType() instanceof TypeString
- )
- or
- exists(Constructor c | this.(ConstructorCall).getConstructor() = c |
- c.getDeclaringType() instanceof LambdaExpression and
- taintFromExpr.getType() instanceof ValueExpression
+ (
+ exists(Method m | this.(MethodAccess).getMethod() = m |
+ m.getDeclaringType() instanceof ExpressionFactory and
+ m.hasName(["createValueExpression", "createMethodExpression"]) and
+ taintFromExpr.getType() instanceof TypeString
+ )
+ or
+ exists(Constructor c | this.(ConstructorCall).getConstructor() = c |
+ c.getDeclaringType() instanceof LambdaExpression and
+ taintFromExpr.getType() instanceof ValueExpression
+ )
)
}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.expected
index 111cc81541c2..e0d699b14e3a 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.expected
@@ -10,22 +10,9 @@ edges
| JakartaExpressionInjection.java:30:24:30:33 | expression : String | JakartaExpressionInjection.java:32:28:32:37 | expression |
| JakartaExpressionInjection.java:37:24:37:33 | expression : String | JakartaExpressionInjection.java:39:32:39:41 | expression |
| JakartaExpressionInjection.java:44:24:44:33 | expression : String | JakartaExpressionInjection.java:49:13:49:28 | lambdaExpression |
-| JakartaExpressionInjection.java:48:49:48:104 | new LambdaExpression(...) : LambdaExpression | JakartaExpressionInjection.java:49:13:49:28 | lambdaExpression |
| JakartaExpressionInjection.java:54:24:54:33 | expression : String | JakartaExpressionInjection.java:56:32:56:41 | expression |
-| JakartaExpressionInjection.java:61:24:61:33 | expression : String | JakartaExpressionInjection.java:64:33:64:96 | createValueExpression(...) : ValueExpression |
| JakartaExpressionInjection.java:61:24:61:33 | expression : String | JakartaExpressionInjection.java:65:13:65:13 | e |
-| JakartaExpressionInjection.java:61:24:61:33 | expression : String | JakartaExpressionInjection.java:65:13:65:13 | e : ValueExpression |
-| JakartaExpressionInjection.java:64:33:64:96 | createValueExpression(...) : ValueExpression | JakartaExpressionInjection.java:48:49:48:104 | new LambdaExpression(...) : LambdaExpression |
-| JakartaExpressionInjection.java:64:33:64:96 | createValueExpression(...) : ValueExpression | JakartaExpressionInjection.java:65:13:65:13 | e |
-| JakartaExpressionInjection.java:64:33:64:96 | createValueExpression(...) : ValueExpression | JakartaExpressionInjection.java:65:13:65:13 | e : ValueExpression |
-| JakartaExpressionInjection.java:65:13:65:13 | e : ValueExpression | JakartaExpressionInjection.java:48:49:48:104 | new LambdaExpression(...) : LambdaExpression |
-| JakartaExpressionInjection.java:70:24:70:33 | expression : String | JakartaExpressionInjection.java:73:33:73:96 | createValueExpression(...) : ValueExpression |
| JakartaExpressionInjection.java:70:24:70:33 | expression : String | JakartaExpressionInjection.java:74:13:74:13 | e |
-| JakartaExpressionInjection.java:70:24:70:33 | expression : String | JakartaExpressionInjection.java:74:13:74:13 | e : ValueExpression |
-| JakartaExpressionInjection.java:73:33:73:96 | createValueExpression(...) : ValueExpression | JakartaExpressionInjection.java:48:49:48:104 | new LambdaExpression(...) : LambdaExpression |
-| JakartaExpressionInjection.java:73:33:73:96 | createValueExpression(...) : ValueExpression | JakartaExpressionInjection.java:74:13:74:13 | e |
-| JakartaExpressionInjection.java:73:33:73:96 | createValueExpression(...) : ValueExpression | JakartaExpressionInjection.java:74:13:74:13 | e : ValueExpression |
-| JakartaExpressionInjection.java:74:13:74:13 | e : ValueExpression | JakartaExpressionInjection.java:48:49:48:104 | new LambdaExpression(...) : LambdaExpression |
| JakartaExpressionInjection.java:79:24:79:33 | expression : String | JakartaExpressionInjection.java:83:13:83:13 | e |
nodes
| JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
@@ -35,18 +22,13 @@ nodes
| JakartaExpressionInjection.java:37:24:37:33 | expression : String | semmle.label | expression : String |
| JakartaExpressionInjection.java:39:32:39:41 | expression | semmle.label | expression |
| JakartaExpressionInjection.java:44:24:44:33 | expression : String | semmle.label | expression : String |
-| JakartaExpressionInjection.java:48:49:48:104 | new LambdaExpression(...) : LambdaExpression | semmle.label | new LambdaExpression(...) : LambdaExpression |
| JakartaExpressionInjection.java:49:13:49:28 | lambdaExpression | semmle.label | lambdaExpression |
| JakartaExpressionInjection.java:54:24:54:33 | expression : String | semmle.label | expression : String |
| JakartaExpressionInjection.java:56:32:56:41 | expression | semmle.label | expression |
| JakartaExpressionInjection.java:61:24:61:33 | expression : String | semmle.label | expression : String |
-| JakartaExpressionInjection.java:64:33:64:96 | createValueExpression(...) : ValueExpression | semmle.label | createValueExpression(...) : ValueExpression |
| JakartaExpressionInjection.java:65:13:65:13 | e | semmle.label | e |
-| JakartaExpressionInjection.java:65:13:65:13 | e : ValueExpression | semmle.label | e : ValueExpression |
| JakartaExpressionInjection.java:70:24:70:33 | expression : String | semmle.label | expression : String |
-| JakartaExpressionInjection.java:73:33:73:96 | createValueExpression(...) : ValueExpression | semmle.label | createValueExpression(...) : ValueExpression |
| JakartaExpressionInjection.java:74:13:74:13 | e | semmle.label | e |
-| JakartaExpressionInjection.java:74:13:74:13 | e : ValueExpression | semmle.label | e : ValueExpression |
| JakartaExpressionInjection.java:79:24:79:33 | expression : String | semmle.label | expression : String |
| JakartaExpressionInjection.java:83:13:83:13 | e | semmle.label | e |
#select
From eb9b41acab71dc9d72a835af2a100ed80b16292d Mon Sep 17 00:00:00 2001
From: ihsinme
Date: Wed, 7 Apr 2021 21:31:12 +0300
Subject: [PATCH 121/433] Apply suggestions from code review
Co-authored-by: Mathias Vorreiter Pedersen
---
...ufficientControlFlowManagementWhenUsingBitOperations.ql | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementWhenUsingBitOperations.ql b/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementWhenUsingBitOperations.ql
index a599fc19fde3..7b441da9288a 100644
--- a/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementWhenUsingBitOperations.ql
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementWhenUsingBitOperations.ql
@@ -1,8 +1,7 @@
/**
* @name Errors When Using Bit Operations
- * @description --Using bitwise operations can be a mistake in some situations.
- * --For example, if parameters are evaluated in an expression and the function should be called only upon certain test results.
- * --These bitwise operations look suspicious and require developer attention.
+ * @description Unlike the binary operations `||` and `&&`, there is no sequence point after evaluating an
+ * operand of a bitwise operation like `|` or `&`. If left-to-right evaluation is expected this may be confusing.
* @kind problem
* @id cpp/errors-when-using-bit-operations
* @problem.severity warning
@@ -77,4 +76,4 @@ where
not dbo.useInOtherCalls() and
dbo.useInLogicalOperations() and
(not dbo.functionCallsInBitsExpression() or dbo.dangerousArgumentChecking())
-select dbo, "this bit expression needs your attention"
+select dbo, "This bitwise operation appears in a context where a Boolean operation is expected."
From ed34c96357e95fa58b19b5f5c5759e0a7053810f Mon Sep 17 00:00:00 2001
From: ihsinme
Date: Wed, 7 Apr 2021 21:40:49 +0300
Subject: [PATCH 122/433] Update
InsufficientControlFlowManagementWhenUsingBitOperations.ql
---
.../InsufficientControlFlowManagementWhenUsingBitOperations.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementWhenUsingBitOperations.ql b/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementWhenUsingBitOperations.ql
index 7b441da9288a..c387fb923be2 100644
--- a/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementWhenUsingBitOperations.ql
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementWhenUsingBitOperations.ql
@@ -29,7 +29,7 @@ class DangerousBitOperations extends Expr {
*/
DangerousBitOperations() {
bfc = this.(BinaryBitwiseOperation).getRightOperand() and
- not this.getParent*() instanceof AssignExpr and
+ not this.getParent*() instanceof Assignment and
not this.getParent*() instanceof Initializer and
not this.getParent*() instanceof ReturnStmt and
not this.getParent*() instanceof EqualityOperation and
From 675de07c3e7698d5137b520f870107e642c9b05d Mon Sep 17 00:00:00 2001
From: Dilan
Date: Wed, 7 Apr 2021 15:04:18 -0700
Subject: [PATCH 123/433] autoformat ql
---
python/ql/src/experimental/Classes/NamingConventionsClasses.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/ql/src/experimental/Classes/NamingConventionsClasses.ql b/python/ql/src/experimental/Classes/NamingConventionsClasses.ql
index f16d242eadf3..0ab11b4197c8 100644
--- a/python/ql/src/experimental/Classes/NamingConventionsClasses.ql
+++ b/python/ql/src/experimental/Classes/NamingConventionsClasses.ql
@@ -10,7 +10,7 @@
import python
predicate lower_case_class(Class c) {
- exists(string first_char |
+ exists(string first_char |
first_char = c.getName().prefix(1) and
not first_char = first_char.toUpperCase()
)
From 8ce5c46e05dbf11bb3581490601ff9fc04dd3631 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Sat, 27 Mar 2021 18:57:46 +0100
Subject: [PATCH 124/433] Python: Minor refactor
modName/clsName _is_ shorter, but also looks way worse :D
---
.../ql/src/semmle/python/frameworks/Django.qll | 17 +++++++++--------
1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/python/ql/src/semmle/python/frameworks/Django.qll b/python/ql/src/semmle/python/frameworks/Django.qll
index 02d5030bb1e8..ebc1d1bdfa24 100644
--- a/python/ql/src/semmle/python/frameworks/Django.qll
+++ b/python/ql/src/semmle/python/frameworks/Django.qll
@@ -2081,20 +2081,21 @@ private module Django {
module Field {
/** Gets a reference to the `django.forms.fields.Field` class or any subclass. */
API::Node subclassRef() {
- exists(string modName, string clsName |
+ exists(string moduleName, string className |
// canonical definition
result =
API::moduleImport("django")
.getMember("forms")
- .getMember(modName)
- .getMember(clsName)
+ .getMember(moduleName)
+ .getMember(className)
.getASubclass*()
or
// alias from `django.forms`
- result = API::moduleImport("django").getMember("forms").getMember(clsName).getASubclass*()
+ result =
+ API::moduleImport("django").getMember("forms").getMember(className).getASubclass*()
|
- modName = "fields" and
- clsName in [
+ moduleName = "fields" and
+ className in [
"Field",
// Known subclasses
"BooleanField", "IntegerField", "CharField", "SlugField", "DateTimeField",
@@ -2106,8 +2107,8 @@ private module Django {
]
or
// Known subclasses from `django.forms.models`
- modName = "models" and
- clsName in ["ModelChoiceField", "ModelMultipleChoiceField", "InlineForeignKeyField"]
+ moduleName = "models" and
+ className in ["ModelChoiceField", "ModelMultipleChoiceField", "InlineForeignKeyField"]
)
or
// other Field subclasses defined in Django
From 322bdcb7034c9ccd10747c991f4c66bfa7a4c1b5 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Sat, 27 Mar 2021 18:58:37 +0100
Subject: [PATCH 125/433] Python: Port Django view modeling to API graphs
---
.../src/semmle/python/frameworks/Django.qll | 434 ++----------------
1 file changed, 43 insertions(+), 391 deletions(-)
diff --git a/python/ql/src/semmle/python/frameworks/Django.qll b/python/ql/src/semmle/python/frameworks/Django.qll
index ebc1d1bdfa24..77cb337d2f60 100644
--- a/python/ql/src/semmle/python/frameworks/Django.qll
+++ b/python/ql/src/semmle/python/frameworks/Django.qll
@@ -1519,402 +1519,54 @@ private module Django {
/** Provides models for the `django.views` module */
module views {
/**
- * Gets a reference to the attribute `attr_name` of the `django.views` module.
- * WARNING: Only holds for a few predefined attributes.
- */
- private DataFlow::Node views_attr(DataFlow::TypeTracker t, string attr_name) {
- // for 1.11.x, see: https://github.com/django/django/blob/stable/1.11.x/django/views/__init__.py
- attr_name in ["generic", "View"] and
- (
- t.start() and
- result = DataFlow::importNode("django.views" + "." + attr_name)
- or
- t.startInAttr(attr_name) and
- result = views()
- )
- or
- // Due to bad performance when using normal setup with `views_attr(t2, attr_name).track(t2, t)`
- // we have inlined that code and forced a join
- exists(DataFlow::TypeTracker t2 |
- exists(DataFlow::StepSummary summary |
- views_attr_first_join(t2, attr_name, result, summary) and
- t = t2.append(summary)
- )
- )
- }
-
- pragma[nomagic]
- private predicate views_attr_first_join(
- DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
- DataFlow::StepSummary summary
- ) {
- DataFlow::StepSummary::step(views_attr(t2, attr_name), res, summary)
- }
-
- /**
- * Gets a reference to the attribute `attr_name` of the `django.views` module.
- * WARNING: Only holds for a few predefined attributes.
+ * Provides models for the `django.views.generic.View` class and subclasses.
+ *
+ * See
+ * - https://docs.djangoproject.com/en/3.1/topics/class-based-views/
+ * - https://docs.djangoproject.com/en/3.1/ref/class-based-views/
*/
- private DataFlow::Node views_attr(string attr_name) {
- result = views_attr(DataFlow::TypeTracker::end(), attr_name)
- }
-
- // -------------------------------------------------------------------------
- // django.views.generic
- // -------------------------------------------------------------------------
- /** Gets a reference to the `django.views.generic` module. */
- DataFlow::Node generic() { result = views_attr("generic") }
-
- /** Provides models for the `django.views.generic` module */
- module generic {
- /**
- * Gets a reference to the attribute `attr_name` of the `django.views.generic` module.
- * WARNING: Only holds for a few predefined attributes.
- */
- private DataFlow::Node generic_attr(DataFlow::TypeTracker t, string attr_name) {
- // for 3.1.x see: https://github.com/django/django/blob/stable/3.1.x/django/views/generic/__init__.py
- // same for 1.11.x see: https://github.com/django/django/blob/stable/1.11.x/django/views/generic/__init__.py
- attr_name in [
- "View", "TemplateView", "RedirectView", "ArchiveIndexView", "YearArchiveView",
- "MonthArchiveView", "WeekArchiveView", "DayArchiveView", "TodayArchiveView",
- "DateDetailView", "DetailView", "FormView", "CreateView", "UpdateView", "DeleteView",
- "ListView", "GenericViewError",
- // modules
- "base", "dates", "detail", "edit", "list"
- ] and
- (
- t.start() and
- result = DataFlow::importNode("django.views.generic" + "." + attr_name)
- or
- t.startInAttr(attr_name) and
- result = generic()
- )
- or
- // Due to bad performance when using normal setup with `generic_attr(t2, attr_name).track(t2, t)`
- // we have inlined that code and forced a join
- exists(DataFlow::TypeTracker t2 |
- exists(DataFlow::StepSummary summary |
- generic_attr_first_join(t2, attr_name, result, summary) and
- t = t2.append(summary)
- )
- )
- }
-
- pragma[nomagic]
- private predicate generic_attr_first_join(
- DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
- DataFlow::StepSummary summary
- ) {
- DataFlow::StepSummary::step(generic_attr(t2, attr_name), res, summary)
- }
-
- /**
- * Gets a reference to the attribute `attr_name` of the `django.views.generic` module.
- * WARNING: Only holds for a few predefined attributes.
- */
- private DataFlow::Node generic_attr(string attr_name) {
- result = generic_attr(DataFlow::TypeTracker::end(), attr_name)
- }
-
- // -------------------------------------------------------------------------
- // django.views.generic.base
- // -------------------------------------------------------------------------
- /** Gets a reference to the `django.views.generic.base` module. */
- DataFlow::Node base() { result = generic_attr("base") }
-
- /** Provides models for the `django.views.generic.base` module */
- module base {
- /**
- * Gets a reference to the attribute `attr_name` of the `django.views.generic.base` module.
- * WARNING: Only holds for a few predefined attributes.
- */
- private DataFlow::Node base_attr(DataFlow::TypeTracker t, string attr_name) {
- attr_name in ["RedirectView", "TemplateView", "View"] and
- (
- t.start() and
- result = DataFlow::importNode("django.views.generic.base" + "." + attr_name)
- or
- t.startInAttr(attr_name) and
- result = base()
- )
- or
- // Due to bad performance when using normal setup with `base_attr(t2, attr_name).track(t2, t)`
- // we have inlined that code and forced a join
- exists(DataFlow::TypeTracker t2 |
- exists(DataFlow::StepSummary summary |
- base_attr_first_join(t2, attr_name, result, summary) and
- t = t2.append(summary)
- )
- )
- }
-
- pragma[nomagic]
- private predicate base_attr_first_join(
- DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
- DataFlow::StepSummary summary
- ) {
- DataFlow::StepSummary::step(base_attr(t2, attr_name), res, summary)
- }
-
- /**
- * Gets a reference to the attribute `attr_name` of the `django.views.generic.base` module.
- * WARNING: Only holds for a few predefined attributes.
- */
- DataFlow::Node base_attr(string attr_name) {
- result = base_attr(DataFlow::TypeTracker::end(), attr_name)
- }
- }
-
- // -------------------------------------------------------------------------
- // django.views.generic.dates
- // -------------------------------------------------------------------------
- /** Gets a reference to the `django.views.generic.dates` module. */
- DataFlow::Node dates() { result = generic_attr("dates") }
-
- /** Provides models for the `django.views.generic.dates` module */
- module dates {
- /**
- * Gets a reference to the attribute `attr_name` of the `django.views.generic.dates` module.
- * WARNING: Only holds for a few predefined attributes.
- */
- private DataFlow::Node dates_attr(DataFlow::TypeTracker t, string attr_name) {
- attr_name in [
+ module View {
+ /** Gets a reference to the `django.views.generic.View` class or any subclass. */
+ API::Node subclassRef() {
+ exists(string moduleName, string className |
+ // canonical definition
+ result =
+ API::moduleImport("django")
+ .getMember("views")
+ .getMember("generic")
+ .getMember(moduleName)
+ .getMember(className)
+ .getASubclass*()
+ or
+ // alias from `django.view.generic`
+ result =
+ API::moduleImport("django")
+ .getMember("view")
+ .getMember("generic")
+ .getMember(className)
+ .getASubclass*()
+ |
+ moduleName = "base" and
+ className in ["RedirectView", "TemplateView", "View"]
+ or
+ moduleName = "dates" and
+ className in [
"ArchiveIndexView", "DateDetailView", "DayArchiveView", "MonthArchiveView",
"TodayArchiveView", "WeekArchiveView", "YearArchiveView"
- ] and
- (
- t.start() and
- result = DataFlow::importNode("django.views.generic.dates" + "." + attr_name)
- or
- t.startInAttr(attr_name) and
- result = dates()
- )
- or
- // Due to bad performance when using normal setup with `dates_attr(t2, attr_name).track(t2, t)`
- // we have inlined that code and forced a join
- exists(DataFlow::TypeTracker t2 |
- exists(DataFlow::StepSummary summary |
- dates_attr_first_join(t2, attr_name, result, summary) and
- t = t2.append(summary)
- )
- )
- }
-
- pragma[nomagic]
- private predicate dates_attr_first_join(
- DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
- DataFlow::StepSummary summary
- ) {
- DataFlow::StepSummary::step(dates_attr(t2, attr_name), res, summary)
- }
-
- /**
- * Gets a reference to the attribute `attr_name` of the `django.views.generic.dates` module.
- * WARNING: Only holds for a few predefined attributes.
- */
- DataFlow::Node dates_attr(string attr_name) {
- result = dates_attr(DataFlow::TypeTracker::end(), attr_name)
- }
- }
-
- // -------------------------------------------------------------------------
- // django.views.generic.detail
- // -------------------------------------------------------------------------
- /** Gets a reference to the `django.views.generic.detail` module. */
- DataFlow::Node detail() { result = generic_attr("detail") }
-
- /** Provides models for the `django.views.generic.detail` module */
- module detail {
- /**
- * Gets a reference to the attribute `attr_name` of the `django.views.generic.detail` module.
- * WARNING: Only holds for a few predefined attributes.
- */
- private DataFlow::Node detail_attr(DataFlow::TypeTracker t, string attr_name) {
- attr_name in ["DetailView"] and
- (
- t.start() and
- result = DataFlow::importNode("django.views.generic.detail" + "." + attr_name)
- or
- t.startInAttr(attr_name) and
- result = detail()
- )
- or
- // Due to bad performance when using normal setup with `detail_attr(t2, attr_name).track(t2, t)`
- // we have inlined that code and forced a join
- exists(DataFlow::TypeTracker t2 |
- exists(DataFlow::StepSummary summary |
- detail_attr_first_join(t2, attr_name, result, summary) and
- t = t2.append(summary)
- )
- )
- }
-
- pragma[nomagic]
- private predicate detail_attr_first_join(
- DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
- DataFlow::StepSummary summary
- ) {
- DataFlow::StepSummary::step(detail_attr(t2, attr_name), res, summary)
- }
-
- /**
- * Gets a reference to the attribute `attr_name` of the `django.views.generic.detail` module.
- * WARNING: Only holds for a few predefined attributes.
- */
- DataFlow::Node detail_attr(string attr_name) {
- result = detail_attr(DataFlow::TypeTracker::end(), attr_name)
- }
- }
-
- // -------------------------------------------------------------------------
- // django.views.generic.edit
- // -------------------------------------------------------------------------
- /** Gets a reference to the `django.views.generic.edit` module. */
- DataFlow::Node edit() { result = generic_attr("edit") }
-
- /** Provides models for the `django.views.generic.edit` module */
- module edit {
- /**
- * Gets a reference to the attribute `attr_name` of the `django.views.generic.edit` module.
- * WARNING: Only holds for a few predefined attributes.
- */
- private DataFlow::Node edit_attr(DataFlow::TypeTracker t, string attr_name) {
- attr_name in ["CreateView", "DeleteView", "FormView", "UpdateView"] and
- (
- t.start() and
- result = DataFlow::importNode("django.views.generic.edit" + "." + attr_name)
- or
- t.startInAttr(attr_name) and
- result = edit()
- )
- or
- // Due to bad performance when using normal setup with `edit_attr(t2, attr_name).track(t2, t)`
- // we have inlined that code and forced a join
- exists(DataFlow::TypeTracker t2 |
- exists(DataFlow::StepSummary summary |
- edit_attr_first_join(t2, attr_name, result, summary) and
- t = t2.append(summary)
- )
- )
- }
-
- pragma[nomagic]
- private predicate edit_attr_first_join(
- DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
- DataFlow::StepSummary summary
- ) {
- DataFlow::StepSummary::step(edit_attr(t2, attr_name), res, summary)
- }
-
- /**
- * Gets a reference to the attribute `attr_name` of the `django.views.generic.edit` module.
- * WARNING: Only holds for a few predefined attributes.
- */
- DataFlow::Node edit_attr(string attr_name) {
- result = edit_attr(DataFlow::TypeTracker::end(), attr_name)
- }
- }
-
- // -------------------------------------------------------------------------
- // django.views.generic.list
- // -------------------------------------------------------------------------
- /** Gets a reference to the `django.views.generic.list` module. */
- DataFlow::Node list() { result = generic_attr("list") }
-
- /** Provides models for the `django.views.generic.list` module */
- module list {
- /**
- * Gets a reference to the attribute `attr_name` of the `django.views.generic.list` module.
- * WARNING: Only holds for a few predefined attributes.
- */
- private DataFlow::Node list_attr(DataFlow::TypeTracker t, string attr_name) {
- attr_name in ["ListView"] and
- (
- t.start() and
- result = DataFlow::importNode("django.views.generic.list" + "." + attr_name)
- or
- t.startInAttr(attr_name) and
- result = list()
- )
- or
- // Due to bad performance when using normal setup with `list_attr(t2, attr_name).track(t2, t)`
- // we have inlined that code and forced a join
- exists(DataFlow::TypeTracker t2 |
- exists(DataFlow::StepSummary summary |
- list_attr_first_join(t2, attr_name, result, summary) and
- t = t2.append(summary)
- )
- )
- }
-
- pragma[nomagic]
- private predicate list_attr_first_join(
- DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
- DataFlow::StepSummary summary
- ) {
- DataFlow::StepSummary::step(list_attr(t2, attr_name), res, summary)
- }
-
- /**
- * Gets a reference to the attribute `attr_name` of the `django.views.generic.list` module.
- * WARNING: Only holds for a few predefined attributes.
- */
- DataFlow::Node list_attr(string attr_name) {
- result = list_attr(DataFlow::TypeTracker::end(), attr_name)
- }
- }
-
- /**
- * Provides models for the `django.views.generic.View` class and subclasses.
- *
- * See
- * - https://docs.djangoproject.com/en/3.1/topics/class-based-views/
- * - https://docs.djangoproject.com/en/3.1/ref/class-based-views/
- */
- module View {
- /** Gets a reference to the `django.views.generic.View` class or any subclass. */
- private DataFlow::Node subclassRef(DataFlow::TypeTracker t) {
- t.start() and
- result =
- generic_attr([
- "View",
- // Known Views
- "TemplateView", "RedirectView", "ArchiveIndexView", "YearArchiveView",
- "MonthArchiveView", "WeekArchiveView", "DayArchiveView", "TodayArchiveView",
- "DateDetailView", "DetailView", "FormView", "CreateView", "UpdateView",
- "DeleteView", "ListView"
- ])
+ ]
or
- // aliases
- t.start() and
- (
- // django.views.View
- result = views_attr("View")
- or
- // django.views.generic.base.*
- result = base::base_attr(_)
- or
- // django.views.generic.dates.*
- result = dates::dates_attr(_)
- or
- // django.views.generic.detail.*
- result = detail::detail_attr(_)
- or
- // django.views.generic.edit.*
- result = edit::edit_attr(_)
- or
- // django.views.generic.list.*
- result = list::list_attr(_)
- )
+ moduleName = "detail" and
+ className = "DetailView"
or
- // subclasses in project code
- result.asExpr().(ClassExpr).getABase() = subclassRef(t.continue()).asExpr()
+ moduleName = "edit" and
+ className in ["CreateView", "DeleteView", "FormView", "UpdateView"]
or
- exists(DataFlow::TypeTracker t2 | result = subclassRef(t2).track(t2, t))
- }
-
- /** Gets a reference to the `django.views.generic.View` class or any subclass. */
- DataFlow::Node subclassRef() { result = subclassRef(DataFlow::TypeTracker::end()) }
+ moduleName = "list" and
+ className = "ListView"
+ )
+ or
+ // `django.views.View` alias
+ result = API::moduleImport("django").getMember("views").getMember("View").getASubclass*()
}
}
}
@@ -2389,7 +2041,7 @@ private module Django {
*/
class DjangoViewClassFromSuperClass extends DjangoViewClass {
DjangoViewClassFromSuperClass() {
- this.getABase() = django::views::generic::View::subclassRef().asExpr()
+ this.getABase() = django::views::View::subclassRef().getAUse().asExpr()
}
}
From b7483a539430ddb05012044db1a6d36ee43b9fcb Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Tue, 6 Apr 2021 10:46:36 +0200
Subject: [PATCH 126/433] Python: Add modeledSubclassRef for Django
views/fields/forms
---
.../src/semmle/python/frameworks/Django.qll | 54 +++++++++----------
1 file changed, 26 insertions(+), 28 deletions(-)
diff --git a/python/ql/src/semmle/python/frameworks/Django.qll b/python/ql/src/semmle/python/frameworks/Django.qll
index 77cb337d2f60..c22f06c9a953 100644
--- a/python/ql/src/semmle/python/frameworks/Django.qll
+++ b/python/ql/src/semmle/python/frameworks/Django.qll
@@ -1526,8 +1526,11 @@ private module Django {
* - https://docs.djangoproject.com/en/3.1/ref/class-based-views/
*/
module View {
- /** Gets a reference to the `django.views.generic.View` class or any subclass. */
- API::Node subclassRef() {
+ /**
+ * Get a references to: the `django.views.generic.View` class, or any subclass
+ * that has explicitly been modeled in the CodeQL libraries.
+ */
+ API::Node modeledSubclassRef() {
exists(string moduleName, string className |
// canonical definition
result =
@@ -1536,7 +1539,6 @@ private module Django {
.getMember("generic")
.getMember(moduleName)
.getMember(className)
- .getASubclass*()
or
// alias from `django.view.generic`
result =
@@ -1544,7 +1546,6 @@ private module Django {
.getMember("view")
.getMember("generic")
.getMember(className)
- .getASubclass*()
|
moduleName = "base" and
className in ["RedirectView", "TemplateView", "View"]
@@ -1566,8 +1567,11 @@ private module Django {
)
or
// `django.views.View` alias
- result = API::moduleImport("django").getMember("views").getMember("View").getASubclass*()
+ result = API::moduleImport("django").getMember("views").getMember("View")
}
+
+ /** Gets a reference to the `django.views.generic.View` class or any subclass. */
+ API::Node subclassRef() { result = modeledSubclassRef().getASubclass*() }
}
}
@@ -1638,29 +1642,29 @@ private module Django {
* See https://docs.djangoproject.com/en/3.1/ref/forms/api/
*/
module Form {
- /** Gets a reference to the `django.forms.forms.BaseForm` class or any subclass. */
- API::Node subclassRef() {
+ /**
+ * Get a references to: the `django.forms.forms.BaseForm` class, or any subclass
+ * that has explicitly been modeled in the CodeQL libraries.
+ */
+ API::Node modeledSubclassRef() {
// canonical definition
result =
API::moduleImport("django")
.getMember("forms")
.getMember("forms")
.getMember(["BaseForm", "Form"])
- .getASubclass*()
or
result =
API::moduleImport("django")
.getMember("forms")
.getMember("models")
.getMember(["BaseModelForm", "ModelForm"])
- .getASubclass*()
or
// aliases from `django.forms`
result =
API::moduleImport("django")
.getMember("forms")
.getMember(["BaseForm", "Form", "BaseModelForm", "ModelForm"])
- .getASubclass*()
or
// other Form subclasses defined in Django
result =
@@ -1669,7 +1673,6 @@ private module Django {
.getMember("admin")
.getMember("forms")
.getMember(["AdminAuthenticationForm", "AdminPasswordChangeForm"])
- .getASubclass*()
or
result =
API::moduleImport("django")
@@ -1677,7 +1680,6 @@ private module Django {
.getMember("admin")
.getMember("helpers")
.getMember("ActionForm")
- .getASubclass*()
or
result =
API::moduleImport("django")
@@ -1686,7 +1688,6 @@ private module Django {
.getMember("views")
.getMember("main")
.getMember("ChangeListSearchForm")
- .getASubclass*()
or
result =
API::moduleImport("django")
@@ -1698,7 +1699,6 @@ private module Django {
"AdminPasswordChangeForm", "PasswordChangeForm", "AuthenticationForm",
"UserCreationForm"
])
- .getASubclass*()
or
result =
API::moduleImport("django")
@@ -1706,22 +1706,22 @@ private module Django {
.getMember("flatpages")
.getMember("forms")
.getMember("FlatpageForm")
- .getASubclass*()
or
result =
API::moduleImport("django")
.getMember("forms")
.getMember("formsets")
.getMember("ManagementForm")
- .getASubclass*()
or
result =
API::moduleImport("django")
.getMember("forms")
.getMember("models")
.getMember(["ModelForm", "BaseModelForm"])
- .getASubclass*()
}
+
+ /** Gets a reference to the `django.forms.forms.BaseForm` class or any subclass. */
+ API::Node subclassRef() { result = modeledSubclassRef().getASubclass*() }
}
/**
@@ -1731,8 +1731,11 @@ private module Django {
* See https://docs.djangoproject.com/en/3.1/ref/forms/fields/
*/
module Field {
- /** Gets a reference to the `django.forms.fields.Field` class or any subclass. */
- API::Node subclassRef() {
+ /**
+ * Get a references to: the `django.forms.fields.Field` class, or any subclass
+ * that has explicitly been modeled in the CodeQL libraries.
+ */
+ API::Node modeledSubclassRef() {
exists(string moduleName, string className |
// canonical definition
result =
@@ -1740,11 +1743,9 @@ private module Django {
.getMember("forms")
.getMember(moduleName)
.getMember(className)
- .getASubclass*()
or
// alias from `django.forms`
- result =
- API::moduleImport("django").getMember("forms").getMember(className).getASubclass*()
+ result = API::moduleImport("django").getMember("forms").getMember(className)
|
moduleName = "fields" and
className in [
@@ -1770,7 +1771,6 @@ private module Django {
.getMember("auth")
.getMember("forms")
.getMember(["ReadOnlyPasswordHashField", "UsernameField"])
- .getASubclass*()
or
result =
API::moduleImport("django")
@@ -1783,7 +1783,6 @@ private module Django {
"MultiLineStringField", "MultiPointField", "MultiPolygonField", "PointField",
"PolygonField"
])
- .getASubclass*()
or
result =
API::moduleImport("django")
@@ -1792,7 +1791,6 @@ private module Django {
.getMember("forms")
.getMember("array")
.getMember(["SimpleArrayField", "SplitArrayField"])
- .getASubclass*()
or
result =
API::moduleImport("django")
@@ -1801,7 +1799,6 @@ private module Django {
.getMember("forms")
.getMember("hstore")
.getMember("HStoreField")
- .getASubclass*()
or
result =
API::moduleImport("django")
@@ -1813,15 +1810,16 @@ private module Django {
"BaseRangeField", "DateRangeField", "DateTimeRangeField", "DecimalRangeField",
"IntegerRangeField"
])
- .getASubclass*()
or
result =
API::moduleImport("django")
.getMember("forms")
.getMember("models")
.getMember(["InlineForeignKeyField", "ModelChoiceField", "ModelMultipleChoiceField"])
- .getASubclass*()
}
+
+ /** Gets a reference to the `django.forms.fields.Field` class or any subclass. */
+ API::Node subclassRef() { result = modeledSubclassRef().getASubclass*() }
}
}
From 83477439a1a94af960d8edc5c5c2818da2614bcf Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Sun, 28 Mar 2021 14:13:32 +0200
Subject: [PATCH 127/433] Python: Make django views/fields/forms class modeling
extensible
This also requires that we make this part of the modeling public, which I guess
is step we want to take eventually anyway!
I'm not quite sure whether the modules `Django::Views` and `Django::Forms` are
actually helpful, or whether we should just have their modules available as
`Django::View`, `Django::Form`, and `Django::Field`...
---
.../src/semmle/python/frameworks/Django.qll | 537 +++++++++---------
1 file changed, 279 insertions(+), 258 deletions(-)
diff --git a/python/ql/src/semmle/python/frameworks/Django.qll b/python/ql/src/semmle/python/frameworks/Django.qll
index c22f06c9a953..f41920c86b8a 100644
--- a/python/ql/src/semmle/python/frameworks/Django.qll
+++ b/python/ql/src/semmle/python/frameworks/Django.qll
@@ -16,7 +16,284 @@ private import semmle.python.regex
* Provides models for the `django` PyPI package.
* See https://www.djangoproject.com/.
*/
-private module Django {
+module Django {
+ /** Provides models for the `django.views` module */
+ module Views {
+ /**
+ * Provides models for the `django.views.generic.View` class and subclasses.
+ *
+ * See
+ * - https://docs.djangoproject.com/en/3.1/topics/class-based-views/
+ * - https://docs.djangoproject.com/en/3.1/ref/class-based-views/
+ */
+ module View {
+ /**
+ * An `API::Node` for references to `django.views.generic.View` or any subclass
+ * that has explicitly been modeled in the CodeQL libraries.
+ */
+ abstract class ModeledSubclass extends API::Node {
+ override string toString() { result = this.(API::Node).toString() }
+ }
+
+ /** A Django view subclass in the `django` package. */
+ private class DjangoViewSubclassesInDjango extends ModeledSubclass {
+ DjangoViewSubclassesInDjango() {
+ exists(string moduleName, string className |
+ // canonical definition
+ this =
+ API::moduleImport("django")
+ .getMember("views")
+ .getMember("generic")
+ .getMember(moduleName)
+ .getMember(className)
+ or
+ // aliases from `django.views.generic`
+ this =
+ API::moduleImport("django")
+ .getMember("views")
+ .getMember("generic")
+ .getMember(className)
+ |
+ moduleName = "base" and
+ className in ["RedirectView", "TemplateView", "View"]
+ or
+ moduleName = "dates" and
+ className in [
+ "ArchiveIndexView", "DateDetailView", "DayArchiveView", "MonthArchiveView",
+ "TodayArchiveView", "WeekArchiveView", "YearArchiveView"
+ ]
+ or
+ moduleName = "detail" and
+ className = "DetailView"
+ or
+ moduleName = "edit" and
+ className in ["CreateView", "DeleteView", "FormView", "UpdateView"]
+ or
+ moduleName = "list" and
+ className = "ListView"
+ )
+ or
+ // `django.views.View` alias
+ this = API::moduleImport("django").getMember("views").getMember("View")
+ }
+ }
+
+ /** Gets a reference to the `django.views.generic.View` class or any subclass. */
+ API::Node subclassRef() { result = any(ModeledSubclass subclass).getASubclass*() }
+ }
+ }
+
+ /** Provides models for django forms (defined in the `django.forms` module) */
+ module Forms {
+ /**
+ * Provides models for the `django.forms.forms.BaseForm` class and subclasses. This
+ * is usually used by the `django.forms.forms.Form` class, which is also available
+ * under the more commonly used alias `django.forms.Form`.
+ *
+ * See https://docs.djangoproject.com/en/3.1/ref/forms/api/
+ */
+ module Form {
+ /**
+ * An `API::Node` for references to `django.forms.forms.BaseForm` or any subclass
+ * that has explicitly been modeled in the CodeQL libraries.
+ */
+ abstract class ModeledSubclass extends API::Node {
+ override string toString() { result = this.(API::Node).toString() }
+ }
+
+ /** A Django form subclass in the `django` package. */
+ private class DjangoFormSubclassesInDjango extends ModeledSubclass {
+ DjangoFormSubclassesInDjango() {
+ // canonical definition
+ this =
+ API::moduleImport("django")
+ .getMember("forms")
+ .getMember("forms")
+ .getMember(["BaseForm", "Form"])
+ or
+ this =
+ API::moduleImport("django")
+ .getMember("forms")
+ .getMember("models")
+ .getMember(["BaseModelForm", "ModelForm"])
+ or
+ // aliases from `django.forms`
+ this =
+ API::moduleImport("django")
+ .getMember("forms")
+ .getMember(["BaseForm", "Form", "BaseModelForm", "ModelForm"])
+ or
+ // other Form subclasses defined in Django
+ this =
+ API::moduleImport("django")
+ .getMember("contrib")
+ .getMember("admin")
+ .getMember("forms")
+ .getMember(["AdminAuthenticationForm", "AdminPasswordChangeForm"])
+ or
+ this =
+ API::moduleImport("django")
+ .getMember("contrib")
+ .getMember("admin")
+ .getMember("helpers")
+ .getMember("ActionForm")
+ or
+ this =
+ API::moduleImport("django")
+ .getMember("contrib")
+ .getMember("admin")
+ .getMember("views")
+ .getMember("main")
+ .getMember("ChangeListSearchForm")
+ or
+ this =
+ API::moduleImport("django")
+ .getMember("contrib")
+ .getMember("auth")
+ .getMember("forms")
+ .getMember([
+ "PasswordResetForm", "UserChangeForm", "SetPasswordForm",
+ "AdminPasswordChangeForm", "PasswordChangeForm", "AuthenticationForm",
+ "UserCreationForm"
+ ])
+ or
+ this =
+ API::moduleImport("django")
+ .getMember("contrib")
+ .getMember("flatpages")
+ .getMember("forms")
+ .getMember("FlatpageForm")
+ or
+ this =
+ API::moduleImport("django")
+ .getMember("forms")
+ .getMember("formsets")
+ .getMember("ManagementForm")
+ or
+ this =
+ API::moduleImport("django")
+ .getMember("forms")
+ .getMember("models")
+ .getMember(["ModelForm", "BaseModelForm"])
+ }
+ }
+
+ /** Gets a reference to the `django.forms.forms.BaseForm` class or any subclass. */
+ API::Node subclassRef() { result = any(ModeledSubclass subclass).getASubclass*() }
+ }
+
+ /**
+ * Provides models for the `django.forms.fields.Field` class and subclasses. This is
+ * also available under the more commonly used alias `django.forms.Field`.
+ *
+ * See https://docs.djangoproject.com/en/3.1/ref/forms/fields/
+ */
+ module Field {
+ /**
+ * An `API::Node` for references to `django.forms.fields.Field` or any subclass
+ * that has explicitly been modeled in the CodeQL libraries.
+ */
+ abstract class ModeledSubclass extends API::Node {
+ override string toString() { result = this.(API::Node).toString() }
+ }
+
+ /** A Django field subclass in the `django` package. */
+ private class DjangoFieldSubclassesInDjango extends ModeledSubclass {
+ DjangoFieldSubclassesInDjango() {
+ exists(string moduleName, string className |
+ // canonical definition
+ this =
+ API::moduleImport("django")
+ .getMember("forms")
+ .getMember(moduleName)
+ .getMember(className)
+ or
+ // aliases from `django.forms`
+ this = API::moduleImport("django").getMember("forms").getMember(className)
+ |
+ moduleName = "fields" and
+ className in [
+ "Field",
+ // Known subclasses
+ "BooleanField", "IntegerField", "CharField", "SlugField", "DateTimeField",
+ "EmailField", "DateField", "TimeField", "DurationField", "DecimalField",
+ "FloatField", "GenericIPAddressField", "UUIDField", "JSONField", "FilePathField",
+ "NullBooleanField", "URLField", "TypedChoiceField", "FileField", "ImageField",
+ "RegexField", "ChoiceField", "MultipleChoiceField", "ComboField", "MultiValueField",
+ "SplitDateTimeField", "TypedMultipleChoiceField", "BaseTemporalField"
+ ]
+ or
+ // Known subclasses from `django.forms.models`
+ moduleName = "models" and
+ className in ["ModelChoiceField", "ModelMultipleChoiceField", "InlineForeignKeyField"]
+ )
+ or
+ // other Field subclasses defined in Django
+ this =
+ API::moduleImport("django")
+ .getMember("contrib")
+ .getMember("auth")
+ .getMember("forms")
+ .getMember(["ReadOnlyPasswordHashField", "UsernameField"])
+ or
+ this =
+ API::moduleImport("django")
+ .getMember("contrib")
+ .getMember("gis")
+ .getMember("forms")
+ .getMember("fields")
+ .getMember([
+ "GeometryCollectionField", "GeometryField", "LineStringField",
+ "MultiLineStringField", "MultiPointField", "MultiPolygonField", "PointField",
+ "PolygonField"
+ ])
+ or
+ this =
+ API::moduleImport("django")
+ .getMember("contrib")
+ .getMember("postgres")
+ .getMember("forms")
+ .getMember("array")
+ .getMember(["SimpleArrayField", "SplitArrayField"])
+ or
+ this =
+ API::moduleImport("django")
+ .getMember("contrib")
+ .getMember("postgres")
+ .getMember("forms")
+ .getMember("hstore")
+ .getMember("HStoreField")
+ or
+ this =
+ API::moduleImport("django")
+ .getMember("contrib")
+ .getMember("postgres")
+ .getMember("forms")
+ .getMember("ranges")
+ .getMember([
+ "BaseRangeField", "DateRangeField", "DateTimeRangeField", "DecimalRangeField",
+ "IntegerRangeField"
+ ])
+ or
+ this =
+ API::moduleImport("django")
+ .getMember("forms")
+ .getMember("models")
+ .getMember(["InlineForeignKeyField", "ModelChoiceField", "ModelMultipleChoiceField"])
+ }
+ }
+
+ /** Gets a reference to the `django.forms.fields.Field` class or any subclass. */
+ API::Node subclassRef() { result = any(ModeledSubclass subclass).getASubclass*() }
+ }
+ }
+}
+
+/**
+ * Provides models for the `django` PyPI package (that we are not quite ready to publicly expose yet).
+ * See https://www.djangoproject.com/.
+ */
+private module PrivateDjango {
// ---------------------------------------------------------------------------
// django
// ---------------------------------------------------------------------------
@@ -1510,71 +1787,6 @@ private module Django {
}
}
- // -------------------------------------------------------------------------
- // django.views
- // -------------------------------------------------------------------------
- /** Gets a reference to the `django.views` module. */
- DataFlow::Node views() { result = django_attr("views") }
-
- /** Provides models for the `django.views` module */
- module views {
- /**
- * Provides models for the `django.views.generic.View` class and subclasses.
- *
- * See
- * - https://docs.djangoproject.com/en/3.1/topics/class-based-views/
- * - https://docs.djangoproject.com/en/3.1/ref/class-based-views/
- */
- module View {
- /**
- * Get a references to: the `django.views.generic.View` class, or any subclass
- * that has explicitly been modeled in the CodeQL libraries.
- */
- API::Node modeledSubclassRef() {
- exists(string moduleName, string className |
- // canonical definition
- result =
- API::moduleImport("django")
- .getMember("views")
- .getMember("generic")
- .getMember(moduleName)
- .getMember(className)
- or
- // alias from `django.view.generic`
- result =
- API::moduleImport("django")
- .getMember("view")
- .getMember("generic")
- .getMember(className)
- |
- moduleName = "base" and
- className in ["RedirectView", "TemplateView", "View"]
- or
- moduleName = "dates" and
- className in [
- "ArchiveIndexView", "DateDetailView", "DayArchiveView", "MonthArchiveView",
- "TodayArchiveView", "WeekArchiveView", "YearArchiveView"
- ]
- or
- moduleName = "detail" and
- className = "DetailView"
- or
- moduleName = "edit" and
- className in ["CreateView", "DeleteView", "FormView", "UpdateView"]
- or
- moduleName = "list" and
- className = "ListView"
- )
- or
- // `django.views.View` alias
- result = API::moduleImport("django").getMember("views").getMember("View")
- }
-
- /** Gets a reference to the `django.views.generic.View` class or any subclass. */
- API::Node subclassRef() { result = modeledSubclassRef().getASubclass*() }
- }
- }
-
// -------------------------------------------------------------------------
// django.shortcuts
// -------------------------------------------------------------------------
@@ -1632,197 +1844,6 @@ private module Django {
}
}
- /** Provides models for django forms (defined in the `django.forms` module) */
- module Forms {
- /**
- * Provides models for the `django.forms.forms.BaseForm` class and subclasses. This
- * is usually used by the `django.forms.forms.Form` class, which is also available
- * under the more commonly used alias `django.forms.Form`.
- *
- * See https://docs.djangoproject.com/en/3.1/ref/forms/api/
- */
- module Form {
- /**
- * Get a references to: the `django.forms.forms.BaseForm` class, or any subclass
- * that has explicitly been modeled in the CodeQL libraries.
- */
- API::Node modeledSubclassRef() {
- // canonical definition
- result =
- API::moduleImport("django")
- .getMember("forms")
- .getMember("forms")
- .getMember(["BaseForm", "Form"])
- or
- result =
- API::moduleImport("django")
- .getMember("forms")
- .getMember("models")
- .getMember(["BaseModelForm", "ModelForm"])
- or
- // aliases from `django.forms`
- result =
- API::moduleImport("django")
- .getMember("forms")
- .getMember(["BaseForm", "Form", "BaseModelForm", "ModelForm"])
- or
- // other Form subclasses defined in Django
- result =
- API::moduleImport("django")
- .getMember("contrib")
- .getMember("admin")
- .getMember("forms")
- .getMember(["AdminAuthenticationForm", "AdminPasswordChangeForm"])
- or
- result =
- API::moduleImport("django")
- .getMember("contrib")
- .getMember("admin")
- .getMember("helpers")
- .getMember("ActionForm")
- or
- result =
- API::moduleImport("django")
- .getMember("contrib")
- .getMember("admin")
- .getMember("views")
- .getMember("main")
- .getMember("ChangeListSearchForm")
- or
- result =
- API::moduleImport("django")
- .getMember("contrib")
- .getMember("auth")
- .getMember("forms")
- .getMember([
- "PasswordResetForm", "UserChangeForm", "SetPasswordForm",
- "AdminPasswordChangeForm", "PasswordChangeForm", "AuthenticationForm",
- "UserCreationForm"
- ])
- or
- result =
- API::moduleImport("django")
- .getMember("contrib")
- .getMember("flatpages")
- .getMember("forms")
- .getMember("FlatpageForm")
- or
- result =
- API::moduleImport("django")
- .getMember("forms")
- .getMember("formsets")
- .getMember("ManagementForm")
- or
- result =
- API::moduleImport("django")
- .getMember("forms")
- .getMember("models")
- .getMember(["ModelForm", "BaseModelForm"])
- }
-
- /** Gets a reference to the `django.forms.forms.BaseForm` class or any subclass. */
- API::Node subclassRef() { result = modeledSubclassRef().getASubclass*() }
- }
-
- /**
- * Provides models for the `django.forms.fields.Field` class and subclasses. This is
- * also available under the more commonly used alias `django.forms.Field`.
- *
- * See https://docs.djangoproject.com/en/3.1/ref/forms/fields/
- */
- module Field {
- /**
- * Get a references to: the `django.forms.fields.Field` class, or any subclass
- * that has explicitly been modeled in the CodeQL libraries.
- */
- API::Node modeledSubclassRef() {
- exists(string moduleName, string className |
- // canonical definition
- result =
- API::moduleImport("django")
- .getMember("forms")
- .getMember(moduleName)
- .getMember(className)
- or
- // alias from `django.forms`
- result = API::moduleImport("django").getMember("forms").getMember(className)
- |
- moduleName = "fields" and
- className in [
- "Field",
- // Known subclasses
- "BooleanField", "IntegerField", "CharField", "SlugField", "DateTimeField",
- "EmailField", "DateField", "TimeField", "DurationField", "DecimalField", "FloatField",
- "GenericIPAddressField", "UUIDField", "JSONField", "FilePathField",
- "NullBooleanField", "URLField", "TypedChoiceField", "FileField", "ImageField",
- "RegexField", "ChoiceField", "MultipleChoiceField", "ComboField", "MultiValueField",
- "SplitDateTimeField", "TypedMultipleChoiceField", "BaseTemporalField"
- ]
- or
- // Known subclasses from `django.forms.models`
- moduleName = "models" and
- className in ["ModelChoiceField", "ModelMultipleChoiceField", "InlineForeignKeyField"]
- )
- or
- // other Field subclasses defined in Django
- result =
- API::moduleImport("django")
- .getMember("contrib")
- .getMember("auth")
- .getMember("forms")
- .getMember(["ReadOnlyPasswordHashField", "UsernameField"])
- or
- result =
- API::moduleImport("django")
- .getMember("contrib")
- .getMember("gis")
- .getMember("forms")
- .getMember("fields")
- .getMember([
- "GeometryCollectionField", "GeometryField", "LineStringField",
- "MultiLineStringField", "MultiPointField", "MultiPolygonField", "PointField",
- "PolygonField"
- ])
- or
- result =
- API::moduleImport("django")
- .getMember("contrib")
- .getMember("postgres")
- .getMember("forms")
- .getMember("array")
- .getMember(["SimpleArrayField", "SplitArrayField"])
- or
- result =
- API::moduleImport("django")
- .getMember("contrib")
- .getMember("postgres")
- .getMember("forms")
- .getMember("hstore")
- .getMember("HStoreField")
- or
- result =
- API::moduleImport("django")
- .getMember("contrib")
- .getMember("postgres")
- .getMember("forms")
- .getMember("ranges")
- .getMember([
- "BaseRangeField", "DateRangeField", "DateTimeRangeField", "DecimalRangeField",
- "IntegerRangeField"
- ])
- or
- result =
- API::moduleImport("django")
- .getMember("forms")
- .getMember("models")
- .getMember(["InlineForeignKeyField", "ModelChoiceField", "ModelMultipleChoiceField"])
- }
-
- /** Gets a reference to the `django.forms.fields.Field` class or any subclass. */
- API::Node subclassRef() { result = modeledSubclassRef().getASubclass*() }
- }
- }
-
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
@@ -2039,7 +2060,7 @@ private module Django {
*/
class DjangoViewClassFromSuperClass extends DjangoViewClass {
DjangoViewClassFromSuperClass() {
- this.getABase() = django::views::View::subclassRef().getAUse().asExpr()
+ this.getABase() = Django::Views::View::subclassRef().getAUse().asExpr()
}
}
From b39a3ab12ccd57ec76fc22b58d2fde78aadf38c7 Mon Sep 17 00:00:00 2001
From: Artem Smotrakov
Date: Thu, 8 Apr 2021 20:32:10 +0300
Subject: [PATCH 128/433] Added setVariable() sink
---
.../CWE-094/JakartaExpressionInjectionLib.qll | 4 +++
.../JakartaExpressionInjection.expected | 31 +++++++++++--------
.../CWE-094/JakartaExpressionInjection.java | 7 +++++
.../java-ee-el/javax/el/ELProcessor.java | 1 +
4 files changed, 30 insertions(+), 13 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll
index e3a3994c8818..b1c5d1e8ae20 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll
@@ -44,6 +44,10 @@ private class ExpressionEvaluationSink extends DataFlow::ExprNode {
m.getDeclaringType() instanceof ELProcessor and
m.hasName(["eval", "getValue", "setValue"]) and
ma.getArgument(0) = taintFrom
+ or
+ m.getDeclaringType() instanceof ELProcessor and
+ m.hasName("setVariable") and
+ ma.getArgument(1) = taintFrom
)
}
}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.expected
index e0d699b14e3a..ef002aefdf5c 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.expected
@@ -5,15 +5,17 @@ edges
| JakartaExpressionInjection.java:24:31:24:40 | expression : String | JakartaExpressionInjection.java:44:24:44:33 | expression : String |
| JakartaExpressionInjection.java:24:31:24:40 | expression : String | JakartaExpressionInjection.java:54:24:54:33 | expression : String |
| JakartaExpressionInjection.java:24:31:24:40 | expression : String | JakartaExpressionInjection.java:61:24:61:33 | expression : String |
-| JakartaExpressionInjection.java:24:31:24:40 | expression : String | JakartaExpressionInjection.java:70:24:70:33 | expression : String |
-| JakartaExpressionInjection.java:24:31:24:40 | expression : String | JakartaExpressionInjection.java:79:24:79:33 | expression : String |
+| JakartaExpressionInjection.java:24:31:24:40 | expression : String | JakartaExpressionInjection.java:68:24:68:33 | expression : String |
+| JakartaExpressionInjection.java:24:31:24:40 | expression : String | JakartaExpressionInjection.java:77:24:77:33 | expression : String |
+| JakartaExpressionInjection.java:24:31:24:40 | expression : String | JakartaExpressionInjection.java:86:24:86:33 | expression : String |
| JakartaExpressionInjection.java:30:24:30:33 | expression : String | JakartaExpressionInjection.java:32:28:32:37 | expression |
| JakartaExpressionInjection.java:37:24:37:33 | expression : String | JakartaExpressionInjection.java:39:32:39:41 | expression |
| JakartaExpressionInjection.java:44:24:44:33 | expression : String | JakartaExpressionInjection.java:49:13:49:28 | lambdaExpression |
| JakartaExpressionInjection.java:54:24:54:33 | expression : String | JakartaExpressionInjection.java:56:32:56:41 | expression |
-| JakartaExpressionInjection.java:61:24:61:33 | expression : String | JakartaExpressionInjection.java:65:13:65:13 | e |
-| JakartaExpressionInjection.java:70:24:70:33 | expression : String | JakartaExpressionInjection.java:74:13:74:13 | e |
-| JakartaExpressionInjection.java:79:24:79:33 | expression : String | JakartaExpressionInjection.java:83:13:83:13 | e |
+| JakartaExpressionInjection.java:61:24:61:33 | expression : String | JakartaExpressionInjection.java:63:43:63:52 | expression |
+| JakartaExpressionInjection.java:68:24:68:33 | expression : String | JakartaExpressionInjection.java:72:13:72:13 | e |
+| JakartaExpressionInjection.java:77:24:77:33 | expression : String | JakartaExpressionInjection.java:81:13:81:13 | e |
+| JakartaExpressionInjection.java:86:24:86:33 | expression : String | JakartaExpressionInjection.java:90:13:90:13 | e |
nodes
| JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
| JakartaExpressionInjection.java:24:31:24:40 | expression : String | semmle.label | expression : String |
@@ -26,16 +28,19 @@ nodes
| JakartaExpressionInjection.java:54:24:54:33 | expression : String | semmle.label | expression : String |
| JakartaExpressionInjection.java:56:32:56:41 | expression | semmle.label | expression |
| JakartaExpressionInjection.java:61:24:61:33 | expression : String | semmle.label | expression : String |
-| JakartaExpressionInjection.java:65:13:65:13 | e | semmle.label | e |
-| JakartaExpressionInjection.java:70:24:70:33 | expression : String | semmle.label | expression : String |
-| JakartaExpressionInjection.java:74:13:74:13 | e | semmle.label | e |
-| JakartaExpressionInjection.java:79:24:79:33 | expression : String | semmle.label | expression : String |
-| JakartaExpressionInjection.java:83:13:83:13 | e | semmle.label | e |
+| JakartaExpressionInjection.java:63:43:63:52 | expression | semmle.label | expression |
+| JakartaExpressionInjection.java:68:24:68:33 | expression : String | semmle.label | expression : String |
+| JakartaExpressionInjection.java:72:13:72:13 | e | semmle.label | e |
+| JakartaExpressionInjection.java:77:24:77:33 | expression : String | semmle.label | expression : String |
+| JakartaExpressionInjection.java:81:13:81:13 | e | semmle.label | e |
+| JakartaExpressionInjection.java:86:24:86:33 | expression : String | semmle.label | expression : String |
+| JakartaExpressionInjection.java:90:13:90:13 | e | semmle.label | e |
#select
| JakartaExpressionInjection.java:32:28:32:37 | expression | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:32:28:32:37 | expression | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) | this user input |
| JakartaExpressionInjection.java:39:32:39:41 | expression | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:39:32:39:41 | expression | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) | this user input |
| JakartaExpressionInjection.java:49:13:49:28 | lambdaExpression | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:49:13:49:28 | lambdaExpression | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) | this user input |
| JakartaExpressionInjection.java:56:32:56:41 | expression | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:56:32:56:41 | expression | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) | this user input |
-| JakartaExpressionInjection.java:65:13:65:13 | e | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:65:13:65:13 | e | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) | this user input |
-| JakartaExpressionInjection.java:74:13:74:13 | e | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:74:13:74:13 | e | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) | this user input |
-| JakartaExpressionInjection.java:83:13:83:13 | e | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:83:13:83:13 | e | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) | this user input |
+| JakartaExpressionInjection.java:63:43:63:52 | expression | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:63:43:63:52 | expression | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) | this user input |
+| JakartaExpressionInjection.java:72:13:72:13 | e | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:72:13:72:13 | e | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) | this user input |
+| JakartaExpressionInjection.java:81:13:81:13 | e | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:81:13:81:13 | e | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) | this user input |
+| JakartaExpressionInjection.java:90:13:90:13 | e | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:90:13:90:13 | e | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:22:25:22:47 | getInputStream(...) | this user input |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.java b/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.java
index a9fb2d45d6f4..2e1d1e55b020 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.java
+++ b/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.java
@@ -57,6 +57,13 @@ private static void testWithELProcessorSetValue() throws IOException {
});
}
+ private static void testWithELProcessorSetVariable() throws IOException {
+ testWithSocket(expression -> {
+ ELProcessor processor = new ELProcessor();
+ processor.setVariable("test", expression);
+ });
+ }
+
private static void testWithJuelValueExpressionGetValue() throws IOException {
testWithSocket(expression -> {
ExpressionFactory factory = new de.odysseus.el.ExpressionFactoryImpl();
diff --git a/java/ql/test/stubs/java-ee-el/javax/el/ELProcessor.java b/java/ql/test/stubs/java-ee-el/javax/el/ELProcessor.java
index 3c523e685c5e..95895fabc648 100644
--- a/java/ql/test/stubs/java-ee-el/javax/el/ELProcessor.java
+++ b/java/ql/test/stubs/java-ee-el/javax/el/ELProcessor.java
@@ -4,4 +4,5 @@ public class ELProcessor {
public Object eval(String expression) { return null; }
public Object getValue(String expression, Class> expectedType) { return null; }
public void setValue(String expression, Object value) {}
+ public void setVariable(String var, String expression) {}
}
From d73ba13b28b42b6e0c3d2ef6f1072ff52c00dc58 Mon Sep 17 00:00:00 2001
From: Dilan
Date: Thu, 8 Apr 2021 11:41:58 -0700
Subject: [PATCH 129/433] autoformat fix
---
.../ql/src/experimental/Functions/NamingConventionsFunctions.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/ql/src/experimental/Functions/NamingConventionsFunctions.ql b/python/ql/src/experimental/Functions/NamingConventionsFunctions.ql
index d2868af22c99..dcfe280c0bf6 100644
--- a/python/ql/src/experimental/Functions/NamingConventionsFunctions.ql
+++ b/python/ql/src/experimental/Functions/NamingConventionsFunctions.ql
@@ -10,7 +10,7 @@
import python
predicate upper_case_function(Function func) {
- exists(string first_char |
+ exists(string first_char |
first_char = func.getName().prefix(1) and
not first_char = first_char.toLowerCase()
)
From a6b486a448ca621a3567ef5c4bd87067702e79b4 Mon Sep 17 00:00:00 2001
From: ihsinme
Date: Thu, 8 Apr 2021 22:01:43 +0300
Subject: [PATCH 130/433] Update
InsufficientControlFlowManagementWhenUsingBitOperations.ql
---
...ontrolFlowManagementWhenUsingBitOperations.ql | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementWhenUsingBitOperations.ql b/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementWhenUsingBitOperations.ql
index c387fb923be2..8a7a3fff1d02 100644
--- a/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementWhenUsingBitOperations.ql
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementWhenUsingBitOperations.ql
@@ -19,7 +19,7 @@ import semmle.code.cpp.valuenumbering.GlobalValueNumbering
* For example: `if(intA>0 & intA<10 & charBuf&myFunc(charBuf[intA]))`.
* In this case, the function will be called in any case, and even the sequence of the call is not guaranteed.
*/
-class DangerousBitOperations extends Expr {
+class DangerousBitOperations extends BinaryBitwiseOperation {
FunctionCall bfc;
/**
@@ -28,16 +28,16 @@ class DangerousBitOperations extends Expr {
* The use of shifts and bitwise operations on any element of an expression indicates a conscious use of the bitwise operator.
*/
DangerousBitOperations() {
- bfc = this.(BinaryBitwiseOperation).getRightOperand() and
+ bfc = this.getRightOperand() and
not this.getParent*() instanceof Assignment and
not this.getParent*() instanceof Initializer and
not this.getParent*() instanceof ReturnStmt and
not this.getParent*() instanceof EqualityOperation and
not this.getParent*() instanceof UnaryLogicalOperation and
not this.getParent*() instanceof BinaryLogicalOperation and
- not this.(BinaryBitwiseOperation).getAChild*() instanceof BitwiseXorExpr and
- not this.(BinaryBitwiseOperation).getAChild*() instanceof LShiftExpr and
- not this.(BinaryBitwiseOperation).getAChild*() instanceof RShiftExpr
+ not this.getAChild*() instanceof BitwiseXorExpr and
+ not this.getAChild*() instanceof LShiftExpr and
+ not this.getAChild*() instanceof RShiftExpr
}
/** Holds when part of a bit expression is used in a logical operation. */
@@ -60,14 +60,14 @@ class DangerousBitOperations extends Expr {
/** Holds when the bit expression contains both arguments and a function call. */
predicate dangerousArgumentChecking() {
- not this.(BinaryBitwiseOperation).getLeftOperand() instanceof Call and
- globalValueNumber(this.(BinaryBitwiseOperation).getLeftOperand().getAChild*()) =
+ not this.getLeftOperand() instanceof Call and
+ globalValueNumber(this.getLeftOperand().getAChild*()) =
globalValueNumber(bfc.getAnArgument())
}
/** Holds when function calls are present in the bit expression. */
predicate functionCallsInBitsExpression() {
- this.(BinaryBitwiseOperation).getLeftOperand().getAChild*() instanceof FunctionCall
+ this.getLeftOperand().getAChild*() instanceof FunctionCall
}
}
From 02eb447a359cba04c6d29b49957fbe095235d3e6 Mon Sep 17 00:00:00 2001
From: ihsinme
Date: Thu, 8 Apr 2021 22:04:08 +0300
Subject: [PATCH 131/433] Update
InsufficientControlFlowManagementWhenUsingBitOperations.expected
---
...icientControlFlowManagementWhenUsingBitOperations.expected | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/InsufficientControlFlowManagementWhenUsingBitOperations.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/InsufficientControlFlowManagementWhenUsingBitOperations.expected
index 6aa02a61eaf6..d11bbd446a61 100644
--- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/InsufficientControlFlowManagementWhenUsingBitOperations.expected
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/InsufficientControlFlowManagementWhenUsingBitOperations.expected
@@ -1,2 +1,2 @@
-| test.c:8:6:8:51 | ... & ... | this bit expression needs your attention |
-| test.c:10:6:10:30 | ... & ... | this bit expression needs your attention |
+| test.c:8:6:8:51 | ... & ... | This bitwise operation appears in a context where a Boolean operation is expected. |
+| test.c:10:6:10:30 | ... & ... | This bitwise operation appears in a context where a Boolean operation is expected. |
From 3d117243e4acd9c3fd48bc13807e5e22dbf4aebc Mon Sep 17 00:00:00 2001
From: ihsinme
Date: Thu, 8 Apr 2021 22:05:31 +0300
Subject: [PATCH 132/433] Update test.c
---
.../query-tests/Security/CWE/CWE-691/semmle/tests/test.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/test.c b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/test.c
index fc30f7a6e97e..e2fccf587366 100644
--- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/test.c
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/test.c
@@ -5,9 +5,9 @@ void workFunction_0(char *s) {
int intSize;
char buf[80];
if(intSize>0 && intSize<80 && memset(buf,0,intSize)) return; // GOOD
- if(intSize>0 & intSize<80 & memset(buf,0,intSize)) return; // BAD
+ if(intSize>0 & intSize<80 & memset(buf,0,intSize)) return; // BAD [NOT DETECTED]
if(intSize>0 && tmpFunction()) return;
- if(intSize<0 & tmpFunction()) return; // BAD
+ if(intSize<0 & tmpFunction()) return; // BAD [NOT DETECTED]
}
void workFunction_1(char *s) {
int intA,intB;
From 9b3ccade439293370532322017df0605449d7b62 Mon Sep 17 00:00:00 2001
From: ihsinme
Date: Thu, 8 Apr 2021 22:06:35 +0300
Subject: [PATCH 133/433] Update test.c
---
.../query-tests/Security/CWE/CWE-691/semmle/tests/test.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/test.c b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/test.c
index fc30f7a6e97e..1db60826b60a 100644
--- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/test.c
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-691/semmle/tests/test.c
@@ -12,14 +12,14 @@ void workFunction_0(char *s) {
void workFunction_1(char *s) {
int intA,intB;
- if(intA + intB) return; // BAD
+ if(intA + intB) return; // BAD [NOT DETECTED]
if(intA + intB>4) return; // GOOD
- if(intA>0 && (intA + intB)) return; // BAD
+ if(intA>0 && (intA + intB)) return; // BAD [NOT DETECTED]
while(intA>0)
{
if(intB - intA<10) break;
intA--;
- }while(intA>0); // BAD
+ }while(intA>0); // BAD [NOT DETECTED]
while(intA>0)
{
if(intB - intA<10) break;
From 956311457db86b5aeb1f22ffd458df154c31c20f Mon Sep 17 00:00:00 2001
From: Erik Krogh Kristensen
Date: Thu, 8 Apr 2021 21:15:50 +0200
Subject: [PATCH 134/433] fixed bad SourceNode X SourceNode join in HTTP model
---
javascript/ql/src/semmle/javascript/frameworks/Express.qll | 3 +--
javascript/ql/src/semmle/javascript/frameworks/Fastify.qll | 3 +--
2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/javascript/ql/src/semmle/javascript/frameworks/Express.qll b/javascript/ql/src/semmle/javascript/frameworks/Express.qll
index bd1249e92681..17bfed8afdac 100644
--- a/javascript/ql/src/semmle/javascript/frameworks/Express.qll
+++ b/javascript/ql/src/semmle/javascript/frameworks/Express.qll
@@ -688,8 +688,7 @@ module Express {
override RouteHandler getRouteHandler() { result = rh }
override Expr getNameExpr() {
- exists(DataFlow::PropWrite write |
- getAHeaderSource().flowsTo(write.getBase()) and
+ exists(DataFlow::PropWrite write | getAHeaderSource().getAPropertyWrite() = write |
result = write.getPropertyNameExpr()
)
}
diff --git a/javascript/ql/src/semmle/javascript/frameworks/Fastify.qll b/javascript/ql/src/semmle/javascript/frameworks/Fastify.qll
index cca2ec8122bf..3e96cea462cb 100644
--- a/javascript/ql/src/semmle/javascript/frameworks/Fastify.qll
+++ b/javascript/ql/src/semmle/javascript/frameworks/Fastify.qll
@@ -283,8 +283,7 @@ module Fastify {
override RouteHandler getRouteHandler() { result = rh }
override Expr getNameExpr() {
- exists(DataFlow::PropWrite write |
- this.getAHeaderSource().flowsTo(write.getBase()) and
+ exists(DataFlow::PropWrite write | getAHeaderSource().getAPropertyWrite() = write |
result = write.getPropertyNameExpr()
)
}
From e5bce548de872e4257ab1ff4896d10c6836f21c3 Mon Sep 17 00:00:00 2001
From: Erik Krogh Kristensen
Date: Thu, 8 Apr 2021 21:37:32 +0200
Subject: [PATCH 135/433] add nomagic on mayHaveStringValue
---
javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll | 1 +
1 file changed, 1 insertion(+)
diff --git a/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll b/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll
index 8835b14af05e..aafbc4c81a99 100644
--- a/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll
+++ b/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll
@@ -96,6 +96,7 @@ module DataFlow {
predicate accessesGlobal(string g) { globalVarRef(g).flowsTo(this) }
/** Holds if this node may evaluate to the string `s`, possibly through local data flow. */
+ pragma[nomagic]
predicate mayHaveStringValue(string s) {
getAPredecessor().mayHaveStringValue(s)
or
From 7f01586bf130494ea9eeb65d05d8ae9c58b63737 Mon Sep 17 00:00:00 2001
From: Erik Krogh Kristensen
Date: Fri, 9 Apr 2021 01:15:46 +0200
Subject: [PATCH 136/433] fix bad join order in `getDocumentedParameter`
---
javascript/ql/src/semmle/javascript/JSDoc.qll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/javascript/ql/src/semmle/javascript/JSDoc.qll b/javascript/ql/src/semmle/javascript/JSDoc.qll
index bee65bc9ab3b..e7fb681da2a9 100644
--- a/javascript/ql/src/semmle/javascript/JSDoc.qll
+++ b/javascript/ql/src/semmle/javascript/JSDoc.qll
@@ -144,7 +144,7 @@ class JSDocParamTag extends JSDocTag {
/** Gets the parameter this tag refers to, if it can be determined. */
Variable getDocumentedParameter() {
exists(Parameterized parm | parm.getDocumentation() = getParent() |
- result = parm.getParameterVariable(getName())
+ result = pragma[only_bind_out](parm).getParameterVariable(getName())
)
}
}
From 11304b2ae15fed18f1452bd1e01720351d84e1a4 Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Fri, 9 Apr 2021 02:21:59 +0000
Subject: [PATCH 137/433] Update qldoc and change the wrapper method
implementation
---
.../CWE-1004/SensitiveCookieNotHttpOnly.ql | 42 +++++++++----------
1 file changed, 21 insertions(+), 21 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
index f1bc7879b8a6..d566bd539453 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
@@ -1,6 +1,8 @@
/**
* @name Sensitive cookies without the HttpOnly response header set
- * @description Sensitive cookies without 'HttpOnly' leaves session cookies vulnerable to an XSS attack.
+ * @description Sensitive cookies without the 'HttpOnly' flag set leaves session cookies vulnerable to
+ * an XSS attack. This query checks whether 'HttpOnly' is not set in a Java Cookie object
+ * or the 'Set-Cookie' HTTP header.
* @kind path-problem
* @id java/sensitive-cookie-not-httponly
* @tags security
@@ -25,14 +27,14 @@ string getCsrfCookieNameRegex() { result = "(?i).*(csrf).*" }
* they are special cookies implementing the Synchronizer Token Pattern that can be used in JavaScript.
*/
predicate isSensitiveCookieNameExpr(Expr expr) {
- exists(string s | s = expr.(CompileTimeConstantExpr).getStringValue().toLowerCase() |
+ exists(string s | s = expr.(CompileTimeConstantExpr).getStringValue() |
s.regexpMatch(getSensitiveCookieNameRegex()) and not s.regexpMatch(getCsrfCookieNameRegex())
)
or
isSensitiveCookieNameExpr(expr.(AddExpr).getAnOperand())
}
-/** The method call `Set-Cookie` of `addHeader` or `setHeader`. */
+/** A method call that sets a `Set-Cookie` header. */
class SetCookieMethodAccess extends MethodAccess {
SetCookieMethodAccess() {
(
@@ -59,7 +61,7 @@ class MatchesHttpOnlyConfiguration extends TaintTracking2::Configuration {
}
}
-/** The cookie class of Java EE. */
+/** A class descended from `javax.servlet.http.Cookie` or `javax/jakarta.ws.rs.core.Cookie`. */
class CookieClass extends RefType {
CookieClass() {
this.getASupertype*()
@@ -67,25 +69,23 @@ class CookieClass extends RefType {
}
}
-/** Holds if the `Expr` expr is evaluated to boolean true. */
-predicate isBooleanTrue(Expr expr) {
+/** Holds if the `expr` is `true` or a variable that is ever assigned `true`. */
+predicate mayBeBooleanTrue(Expr expr) {
expr.(CompileTimeConstantExpr).getBooleanValue() = true or
expr.(VarAccess).getVariable().getAnAssignedValue().(CompileTimeConstantExpr).getBooleanValue() =
true
}
/** Holds if the method call sets the `HttpOnly` flag. */
-predicate setHttpOnlyInCookie(MethodAccess ma) {
+predicate setsCookieHttpOnly(MethodAccess ma) {
ma.getMethod().getName() = "setHttpOnly" and
(
- isBooleanTrue(ma.getArgument(0)) // boolean literal true
+ ma.getArgument(0).(CompileTimeConstantExpr).getBooleanValue() = true
or
- exists(
- MethodAccess mpa, int i // runtime assignment of boolean value true
- |
- TaintTracking::localTaint(DataFlow::parameterNode(mpa.getMethod().getParameter(i)),
- DataFlow::exprNode(ma.getArgument(0))) and
- isBooleanTrue(mpa.getArgument(i))
+ // any use of setHttpOnly(x) where x isn't false is probably safe
+ not exists(VarAccess va |
+ ma.getArgument(0).(VarAccess).getVariable().getAnAccess() = va and
+ va.getVariable().getAnAssignedValue().(CompileTimeConstantExpr).getBooleanValue() = false
)
)
}
@@ -99,7 +99,7 @@ class SetHttpOnlyInCookieConfiguration extends TaintTracking2::Configuration {
override predicate isSource(DataFlow::Node source) {
source.asExpr() =
- any(MethodAccess ma | setHttpOnlyInCookie(ma) or removeCookie(ma)).getQualifier()
+ any(MethodAccess ma | setsCookieHttpOnly(ma) or removeCookie(ma)).getQualifier()
}
override predicate isSink(DataFlow::Node sink) {
@@ -108,18 +108,18 @@ class SetHttpOnlyInCookieConfiguration extends TaintTracking2::Configuration {
}
}
-/** Holds if the method call removes a cookie. */
+/** Holds if `ma` removes a cookie. */
predicate removeCookie(MethodAccess ma) {
ma.getMethod().getName() = "setMaxAge" and
ma.getArgument(0).(IntegerLiteral).getIntValue() = 0
}
-/** Sensitive cookie name used in a `Cookie` constructor or a `Set-Cookie` call. */
+/** A sensitive cookie name. */
class SensitiveCookieNameExpr extends Expr {
SensitiveCookieNameExpr() { isSensitiveCookieNameExpr(this) }
}
-/** Sink of adding a cookie to the HTTP response. */
+/** A cookie that is added to an HTTP response, used as a sink in `MissingHttpOnlyConfiguration`. */
class CookieResponseSink extends DataFlow::ExprNode {
CookieResponseSink() {
exists(MethodAccess ma |
@@ -145,14 +145,14 @@ predicate setHttpOnlyInNewCookie(ClassInstanceExpr cie) {
cie.getConstructedType().hasQualifiedName(["javax.ws.rs.core", "jakarta.ws.rs.core"], "NewCookie") and
(
cie.getNumArgument() = 6 and
- isBooleanTrue(cie.getArgument(5)) // NewCookie(Cookie cookie, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
+ mayBeBooleanTrue(cie.getArgument(5)) // NewCookie(Cookie cookie, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
or
cie.getNumArgument() = 8 and
cie.getArgument(6).getType() instanceof BooleanType and
- isBooleanTrue(cie.getArgument(7)) // NewCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly)
+ mayBeBooleanTrue(cie.getArgument(7)) // NewCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly)
or
cie.getNumArgument() = 10 and
- isBooleanTrue(cie.getArgument(9)) // NewCookie(String name, String value, String path, String domain, int version, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
+ mayBeBooleanTrue(cie.getArgument(9)) // NewCookie(String name, String value, String path, String domain, int version, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly)
)
}
From 53daa7c43685304e6e7463890bcf4d8b77d59ee6 Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Wed, 17 Mar 2021 12:10:19 +0100
Subject: [PATCH 138/433] Java: Migrate LDAP injection sinks to CSV format
---
.../code/java/dataflow/ExternalFlow.qll | 1 +
.../code/java/security/LdapInjection.qll | 107 ++++++++----------
2 files changed, 48 insertions(+), 60 deletions(-)
diff --git a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
index 4bb79e84ead9..94bca372bb02 100644
--- a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
+++ b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
@@ -76,6 +76,7 @@ private module Frameworks {
private import semmle.code.java.frameworks.ApacheHttp
private import semmle.code.java.frameworks.apache.Lang
private import semmle.code.java.frameworks.guava.Guava
+ private import semmle.code.java.security.LdapInjection
}
private predicate sourceModelCsv(string row) {
diff --git a/java/ql/src/semmle/code/java/security/LdapInjection.qll b/java/ql/src/semmle/code/java/security/LdapInjection.qll
index 2c1f493c6917..f5e0994c68f9 100644
--- a/java/ql/src/semmle/code/java/security/LdapInjection.qll
+++ b/java/ql/src/semmle/code/java/security/LdapInjection.qll
@@ -6,6 +6,7 @@ import semmle.code.java.frameworks.Jndi
import semmle.code.java.frameworks.UnboundId
import semmle.code.java.frameworks.SpringLdap
import semmle.code.java.frameworks.ApacheLdap
+private import semmle.code.java.dataflow.ExternalFlow
/** A data flow sink for unvalidated user input that is used to construct LDAP queries. */
abstract class LdapInjectionSink extends DataFlow::Node { }
@@ -28,68 +29,54 @@ class LdapInjectionAdditionalTaintStep extends Unit {
/** Default sink for LDAP injection vulnerabilities. */
private class DefaultLdapInjectionSink extends LdapInjectionSink {
- DefaultLdapInjectionSink() {
- exists(MethodAccess ma, Method m, int index |
- ma.getMethod() = m and
- ma.getArgument(index) = this.asExpr() and
- ldapInjectionSinkMethod(m, index)
- )
- }
-}
-
-/** Holds if the method parameter at `index` is susceptible to an LDAP injection attack. */
-private predicate ldapInjectionSinkMethod(Method m, int index) {
- jndiLdapInjectionSinkMethod(m, index) or
- unboundIdLdapInjectionSinkMethod(m, index) or
- springLdapInjectionSinkMethod(m, index) or
- apacheLdapInjectionSinkMethod(m, index)
-}
-
-/** Holds if the JNDI method parameter at `index` is susceptible to an LDAP injection attack. */
-private predicate jndiLdapInjectionSinkMethod(Method m, int index) {
- m.getDeclaringType().getAnAncestor() instanceof TypeDirContext and
- m.hasName("search") and
- index in [0 .. 1]
-}
-
-/** Holds if the UnboundID method parameter at `index` is susceptible to an LDAP injection attack. */
-private predicate unboundIdLdapInjectionSinkMethod(Method m, int index) {
- exists(Parameter param | m.getParameter(index) = param and not param.isVarargs() |
- m instanceof MethodUnboundIdLDAPConnectionSearch or
- m instanceof MethodUnboundIdLDAPConnectionAsyncSearch or
- m instanceof MethodUnboundIdLDAPConnectionSearchForEntry
- )
+ DefaultLdapInjectionSink() { sinkNode(this, "ldap") }
}
-/** Holds if the Spring method parameter at `index` is susceptible to an LDAP injection attack. */
-private predicate springLdapInjectionSinkMethod(Method m, int index) {
- // LdapTemplate.authenticate, LdapTemplate.find* or LdapTemplate.search* method
- (
- m instanceof MethodSpringLdapTemplateAuthenticate or
- m instanceof MethodSpringLdapTemplateFind or
- m instanceof MethodSpringLdapTemplateFindOne or
- m instanceof MethodSpringLdapTemplateSearch or
- m instanceof MethodSpringLdapTemplateSearchForContext or
- m instanceof MethodSpringLdapTemplateSearchForObject
- ) and
- (
- // Parameter index is 1 (DN or query) or 2 (filter) if method is not authenticate
- index in [0 .. 1] and
- not m instanceof MethodSpringLdapTemplateAuthenticate
- or
- // But it's not the last parameter in case of authenticate method (last param is password)
- index in [0 .. 1] and
- index < m.getNumberOfParameters() - 1 and
- m instanceof MethodSpringLdapTemplateAuthenticate
- )
-}
-
-/** Holds if the Apache LDAP API method parameter at `index` is susceptible to an LDAP injection attack. */
-private predicate apacheLdapInjectionSinkMethod(Method m, int index) {
- exists(Parameter param | m.getParameter(index) = param and not param.isVarargs() |
- m.getDeclaringType().getAnAncestor() instanceof TypeApacheLdapConnection and
- m.hasName("search")
- )
+private class DefaultLdapInjectionSinkModel extends SinkModelCsv {
+ override predicate row(string row) {
+ row =
+ [
+ // jndi
+ "javax.naming.directory;DirContext;true;search;;;Argument[0..1];ldap",
+ // apache
+ "org.apache.directory.ldap.client.api;LdapConnection;true;search;;;Argument[0..2];ldap",
+ // UnboundID: search
+ "com.unboundid.ldap.sdk;LDAPConnection;false;search;(ReadOnlySearchRequest);;Argument[0];ldap",
+ "com.unboundid.ldap.sdk;LDAPConnection;false;search;(SearchRequest);;Argument[0];ldap",
+ "com.unboundid.ldap.sdk;LDAPConnection;false;search;(SearchResultListener,String,SearchScope,DereferencePolicy,int,int,boolean,Filter,String[]);;Argument[0..7];ldap",
+ "com.unboundid.ldap.sdk;LDAPConnection;false;search;(SearchResultListener,String,SearchScope,DereferencePolicy,int,int,boolean,String,String[]);;Argument[0..7];ldap",
+ "com.unboundid.ldap.sdk;LDAPConnection;false;search;(SearchResultListener,String,SearchScope,Filter,String[]]);;Argument[0..3];ldap",
+ "com.unboundid.ldap.sdk;LDAPConnection;false;search;(SearchResultListener,String,SearchScope,String,String[]]);;Argument[0..3];ldap",
+ "com.unboundid.ldap.sdk;LDAPConnection;false;search;(String,SearchScope,DereferencePolicy,int,int,boolean,Filter,String[]]);;Argument[0..6];ldap",
+ "com.unboundid.ldap.sdk;LDAPConnection;false;search;(String,SearchScope,DereferencePolicy,int,int,boolean,String,String[]]);;Argument[0..6];ldap",
+ "com.unboundid.ldap.sdk;LDAPConnection;false;search;(String,SearchScope,Filter,String[]]);;Argument[0..2];ldap",
+ "com.unboundid.ldap.sdk;LDAPConnection;false;search;(String,SearchScope,String,String[]]);;Argument[0..2];ldap",
+ // UnboundID: searchForEntry
+ "com.unboundid.ldap.sdk;LDAPConnection;false;searchForEntry;(ReadOnlySearchRequest);;Argument[0];ldap",
+ "com.unboundid.ldap.sdk;LDAPConnection;false;searchForEntry;(SearchRequest);;Argument[0];ldap",
+ "com.unboundid.ldap.sdk;LDAPConnection;false;searchForEntry;(String,SearchScope,DereferencePolicy,int,boolean,Filter,String[]);;Argument[0..5];ldap",
+ "com.unboundid.ldap.sdk;LDAPConnection;false;searchForEntry;(String,SearchScope,DereferencePolicy,int,boolean,String,String[]);;Argument[0..5];ldap",
+ "com.unboundid.ldap.sdk;LDAPConnection;false;searchForEntry;(String,SearchScope,Filter,String[]);;Argument[0..2];ldap",
+ "com.unboundid.ldap.sdk;LDAPConnection;false;searchForEntry;(String,SearchScope,String,String[]);;Argument[0..2];ldap",
+ // UnboundID: asyncSearch
+ "com.unboundid.ldap.sdk;LDAPConnection;false;asyncSearch;;;Argument[0];ldap",
+ // Spring
+ "org.springframework.ldap.core;LdapTemplate;false;find;;;Argument[0..1];ldap",
+ "org.springframework.ldap.core;LdapTemplate;false;findOne;;;Argument[0..1];ldap",
+ "org.springframework.ldap.core;LdapTemplate;false;search;;;Argument[0..1];ldap",
+ "org.springframework.ldap.core;LdapTemplate;false;searchForContext;;;Argument[0..1];ldap",
+ "org.springframework.ldap.core;LdapTemplate;false;searchForObject;;;Argument[0..1];ldap",
+ "org.springframework.ldap.core;LdapTemplate;false;authenticate;;;Argument[0];ldap",
+ "org.springframework.ldap.core;LdapTemplate;false;authenticate;(Name,String,String);;Argument[1];ldap",
+ "org.springframework.ldap.core;LdapTemplate;false;authenticate;(Name,String,String,AuthenticatedLdapEntryContextCallback);;Argument[1];ldap",
+ "org.springframework.ldap.core;LdapTemplate;false;authenticate;(Name,String,String,AuthenticatedLdapEntryContextCallback,AuthenticationErrorCallback);;Argument[1];ldap",
+ "org.springframework.ldap.core;LdapTemplate;false;authenticate;(Name,String,String,AuthenticationErrorCallback);;Argument[1];ldap",
+ "org.springframework.ldap.core;LdapTemplate;false;authenticate;(String,String,String);;Argument[1];ldap",
+ "org.springframework.ldap.core;LdapTemplate;false;authenticate;(String,String,String,AuthenticatedLdapEntryContextCallback);;Argument[1];ldap",
+ "org.springframework.ldap.core;LdapTemplate;false;authenticate;(String,String,String,AuthenticatedLdapEntryContextCallback,AuthenticationErrorCallback);;Argument[1];ldap",
+ "org.springframework.ldap.core;LdapTemplate;false;authenticate;(String,String,String,AuthenticationErrorCallback);;Argument[1];ldap"
+ ]
+ }
}
/** A sanitizer that clears the taint on (boxed) primitive types. */
From b9ce1aefc09f72f53a3ecc1940fe396a679433be Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Tue, 30 Mar 2021 09:31:38 +0200
Subject: [PATCH 139/433] Java: Convert unsafe URL opening sinks to CSV format
---
java/ql/src/Security/CWE/CWE-319/HttpsUrls.ql | 17 ++---------------
.../semmle/code/java/dataflow/ExternalFlow.qll | 9 ++++++++-
2 files changed, 10 insertions(+), 16 deletions(-)
diff --git a/java/ql/src/Security/CWE/CWE-319/HttpsUrls.ql b/java/ql/src/Security/CWE/CWE-319/HttpsUrls.ql
index 306bf27ab9c0..d1bce930054a 100644
--- a/java/ql/src/Security/CWE/CWE-319/HttpsUrls.ql
+++ b/java/ql/src/Security/CWE/CWE-319/HttpsUrls.ql
@@ -13,6 +13,7 @@ import java
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.frameworks.Networking
import DataFlow::PathGraph
+private import semmle.code.java.dataflow.ExternalFlow
class HTTPString extends StringLiteral {
HTTPString() {
@@ -30,26 +31,12 @@ class HTTPString extends StringLiteral {
}
}
-class URLOpenMethod extends Method {
- URLOpenMethod() {
- this.getDeclaringType().getQualifiedName() = "java.net.URL" and
- (
- this.getName() = "openConnection" or
- this.getName() = "openStream"
- )
- }
-}
-
class HTTPStringToURLOpenMethodFlowConfig extends TaintTracking::Configuration {
HTTPStringToURLOpenMethodFlowConfig() { this = "HttpsUrls::HTTPStringToURLOpenMethodFlowConfig" }
override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof HTTPString }
- override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess m |
- sink.asExpr() = m.getQualifier() and m.getMethod() instanceof URLOpenMethod
- )
- }
+ override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "open-url") }
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
exists(UrlConstructorCall u |
diff --git a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
index 4bb79e84ead9..ece2c7cff749 100644
--- a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
+++ b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
@@ -184,7 +184,14 @@ private predicate sourceModelCsv(string row) {
]
}
-private predicate sinkModelCsv(string row) { none() }
+private predicate sinkModelCsv(string row) {
+ row =
+ [
+ // Open URL
+ "java.net;URL;false;openConnection;;;Argument[-1];open-url",
+ "java.net;URL;false;openStream;;;Argument[-1];open-url"
+ ]
+}
private predicate summaryModelCsv(string row) {
row =
From 9e2832a82d7e3f51c7967c8aafee2d48c01b8b2c Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Tue, 30 Mar 2021 16:24:32 +0200
Subject: [PATCH 140/433] Java: Convert zipslip sinks to CSV format
---
java/ql/src/Security/CWE/CWE-022/ZipSlip.ql | 31 ++-----------------
.../code/java/dataflow/ExternalFlow.qll | 16 +++++++++-
2 files changed, 17 insertions(+), 30 deletions(-)
diff --git a/java/ql/src/Security/CWE/CWE-022/ZipSlip.ql b/java/ql/src/Security/CWE/CWE-022/ZipSlip.ql
index 7d74f8b79ac4..80de02d70679 100644
--- a/java/ql/src/Security/CWE/CWE-022/ZipSlip.ql
+++ b/java/ql/src/Security/CWE/CWE-022/ZipSlip.ql
@@ -17,6 +17,7 @@ import semmle.code.java.dataflow.SSA
import semmle.code.java.dataflow.TaintTracking
import DataFlow
import PathGraph
+private import semmle.code.java.dataflow.ExternalFlow
/**
* A method that returns the name of an archive entry.
@@ -33,34 +34,6 @@ class ArchiveEntryNameMethod extends Method {
}
}
-/**
- * An expression that will be treated as the destination of a write.
- */
-class WrittenFileName extends Expr {
- WrittenFileName() {
- // Constructors that write to their first argument.
- exists(ConstructorCall ctr | this = ctr.getArgument(0) |
- exists(Class c | ctr.getConstructor() = c.getAConstructor() |
- c.hasQualifiedName("java.io", "FileOutputStream") or
- c.hasQualifiedName("java.io", "RandomAccessFile") or
- c.hasQualifiedName("java.io", "FileWriter")
- )
- )
- or
- // Methods that write to their n'th argument
- exists(MethodAccess call, int n | this = call.getArgument(n) |
- call.getMethod().getDeclaringType().hasQualifiedName("java.nio.file", "Files") and
- (
- call.getMethod().getName().regexpMatch("new.*Reader|newOutputStream|create.*") and n = 0
- or
- call.getMethod().hasName("copy") and n = 1
- or
- call.getMethod().hasName("move") and n = 1
- )
- )
- }
-}
-
/**
* Holds if `n1` to `n2` is a dataflow step that converts between `String`,
* `File`, and `Path`.
@@ -151,7 +124,7 @@ class ZipSlipConfiguration extends TaintTracking::Configuration {
source.asExpr().(MethodAccess).getMethod() instanceof ArchiveEntryNameMethod
}
- override predicate isSink(Node sink) { sink.asExpr() instanceof WrittenFileName }
+ override predicate isSink(Node sink) { sinkNode(sink, "create-file") }
override predicate isAdditionalTaintStep(Node n1, Node n2) {
filePathStep(n1, n2) or fileTaintStep(n1, n2)
diff --git a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
index ece2c7cff749..2aa0b1d14e47 100644
--- a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
+++ b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
@@ -189,7 +189,21 @@ private predicate sinkModelCsv(string row) {
[
// Open URL
"java.net;URL;false;openConnection;;;Argument[-1];open-url",
- "java.net;URL;false;openStream;;;Argument[-1];open-url"
+ "java.net;URL;false;openStream;;;Argument[-1];open-url",
+ // Create file
+ "java.io;FileOutputStream;false;FileOutputStream;;;Argument[0];create-file",
+ "java.io;RandomAccessFile;false;RandomAccessFile;;;Argument[0];create-file",
+ "java.io;FileWriter;false;FileWriter;;;Argument[0];create-file",
+ "java.nio.file;Files;false;move;;;Argument[1];create-file",
+ "java.nio.file;Files;false;copy;;;Argument[1];create-file",
+ "java.nio.file;Files;false;newOutputStream;;;Argument[0];create-file",
+ "java.nio.file;Files;false;newBufferedReader;;;Argument[0];create-file",
+ "java.nio.file;Files;false;createDirectory;;;Argument[0];create-file",
+ "java.nio.file;Files;false;createFile;;;Argument[0];create-file",
+ "java.nio.file;Files;false;createLink;;;Argument[0];create-file",
+ "java.nio.file;Files;false;createSymbolicLink;;;Argument[0];create-file",
+ "java.nio.file;Files;false;createTempDirectory;;;Argument[0];create-file",
+ "java.nio.file;Files;false;createTempFile;;;Argument[0];create-file"
]
}
From 0a6aef71a2f957386f895d594f227a0c67c387be Mon Sep 17 00:00:00 2001
From: Mathias Vorreiter Pedersen
Date: Fri, 9 Apr 2021 12:29:13 +0200
Subject: [PATCH 141/433] C++: Respond to review comments.
---
.../cpp/dataflow/internal/DataFlowUtil.qll | 4 +-
.../code/cpp/dataflow/internal/FlowVar.qll | 76 +++++++++----------
.../dataflow/taint-tests/localTaint.expected | 2 -
3 files changed, 37 insertions(+), 45 deletions(-)
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll
index baf5f72b75b6..810c74e3e699 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll
@@ -326,7 +326,7 @@ private class VariablePartialDefinitionNode extends PartialDefinitionNode {
* A synthetic data flow node used for flow into a collection when an iterator
* write occurs in a callee.
*/
-class IteratorPartialDefinitionNode extends PartialDefinitionNode {
+private class IteratorPartialDefinitionNode extends PartialDefinitionNode {
override IteratorPartialDefinition pd;
override Node getPreUpdateNode() { pd.definesExpressions(_, result.asExpr()) }
@@ -338,7 +338,7 @@ class IteratorPartialDefinitionNode extends PartialDefinitionNode {
* A synthetic data flow node used for flow into a collection when a smart pointer
* write occurs in a callee.
*/
-class SmartPointerPartialDefinitionNode extends PartialDefinitionNode {
+private class SmartPointerPartialDefinitionNode extends PartialDefinitionNode {
override SmartPointerPartialDefinition pd;
override Node getPreUpdateNode() { pd.definesExpressions(_, result.asExpr()) }
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/FlowVar.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/FlowVar.qll
index 29b6cd691a45..0e5d6ea5fa74 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/FlowVar.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/FlowVar.qll
@@ -159,18 +159,14 @@ private module PartialDefinitions {
Expr innerDefinedExpr;
IteratorPartialDefinition() {
- exists(Expr convertedInner |
- not this instanceof Conversion and
- valueToUpdate(convertedInner, this.getFullyConverted(), node) and
- innerDefinedExpr = convertedInner.getUnconverted() and
- (
- innerDefinedExpr.(Call).getQualifier() = getAnIteratorAccess(collection)
- or
- innerDefinedExpr.(Call).getQualifier() = collection.getAnAccess() and
- collection instanceof IteratorParameter
- ) and
- innerDefinedExpr.(Call).getTarget() instanceof IteratorPointerDereferenceMemberOperator
- )
+ innerDefinedExpr = getInnerDefinedExpr(this, node) and
+ (
+ innerDefinedExpr.(Call).getQualifier() = getAnIteratorAccess(collection)
+ or
+ innerDefinedExpr.(Call).getQualifier() = collection.getAnAccess() and
+ collection instanceof IteratorParameter
+ ) and
+ innerDefinedExpr.(Call).getTarget() instanceof IteratorPointerDereferenceMemberOperator
or
// iterators passed by value without a copy constructor
exists(Call call |
@@ -208,16 +204,18 @@ private module PartialDefinitions {
}
}
+ private Expr getInnerDefinedExpr(Expr e, ControlFlowNode node) {
+ not e instanceof Conversion and
+ exists(Expr convertedInner |
+ valueToUpdate(convertedInner, e.getFullyConverted(), node) and
+ result = convertedInner.getUnconverted()
+ )
+ }
+
class VariablePartialDefinition extends PartialDefinition {
Expr innerDefinedExpr;
- VariablePartialDefinition() {
- not this instanceof Conversion and
- exists(Expr convertedInner |
- valueToUpdate(convertedInner, this.getFullyConverted(), node) and
- innerDefinedExpr = convertedInner.getUnconverted()
- )
- }
+ VariablePartialDefinition() { innerDefinedExpr = getInnerDefinedExpr(this, node) }
deprecated override predicate partiallyDefines(Variable v) {
innerDefinedExpr = v.getAnAccess()
@@ -249,30 +247,15 @@ private module PartialDefinitions {
Expr innerDefinedExpr;
SmartPointerPartialDefinition() {
- exists(Expr convertedInner |
- not this instanceof Conversion and
- valueToUpdate(convertedInner, this.getFullyConverted(), node) and
- innerDefinedExpr = convertedInner.getUnconverted() and
- innerDefinedExpr = getAPointerWrapperAccess(pointer)
- )
+ innerDefinedExpr = pragma[only_bind_out](getInnerDefinedExpr(this, node)) and
+ innerDefinedExpr = getAPointerWrapperAccess(pointer, -1)
or
- // pointer wrappers passed by value without a copy constructor
+ // Pointer wrappers passed to a function by value.
exists(Call call |
call = node and
call.getAnArgument() = innerDefinedExpr and
innerDefinedExpr = this and
- this = getAPointerWrapperAccess(pointer) and
- not call instanceof OverloadedPointerDereferenceExpr
- )
- or
- // pointer wrappers passed by value with a copy constructor
- exists(Call call, ConstructorCall copy |
- copy.getTarget() instanceof CopyConstructor and
- call = node and
- call.getAnArgument() = copy and
- copy.getArgument(0) = getAPointerWrapperAccess(pointer) and
- innerDefinedExpr = this and
- this = copy and
+ this = getAPointerWrapperAccess(pointer, 0) and
not call instanceof OverloadedPointerDereferenceExpr
)
}
@@ -893,9 +876,20 @@ module FlowVar_internal {
)
}
- Call getAPointerWrapperAccess(Variable pointer) {
- pointer.getUnspecifiedType() instanceof PointerWrapper and
- [result.getQualifier(), result.getAnArgument()] = pointer.getAnAccess()
+ /**
+ * Gets either:
+ * - A call to an unwrapper function, where an access to `pointer` is the qualifier, or
+ * - A call to the `CopyConstructor` that copies the value of an access to `pointer`.
+ */
+ Call getAPointerWrapperAccess(Variable pointer, int n) {
+ exists(PointerWrapper wrapper | wrapper = pointer.getUnspecifiedType().stripType() |
+ n = -1 and
+ result.getQualifier() = pointer.getAnAccess() and
+ result.getTarget() = wrapper.getAnUnwrapperFunction()
+ or
+ result.getArgument(n) = pointer.getAnAccess() and
+ result.getTarget() instanceof CopyConstructor
+ )
}
class IteratorParameter extends Parameter {
diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected b/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected
index e10014278187..a5c8f00ecabb 100644
--- a/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected
+++ b/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected
@@ -3223,7 +3223,6 @@
| smart_pointer.cpp:11:30:11:50 | call to make_shared | smart_pointer.cpp:12:11:12:11 | p | |
| smart_pointer.cpp:11:30:11:50 | call to make_shared | smart_pointer.cpp:13:10:13:10 | p | |
| smart_pointer.cpp:11:52:11:57 | call to source | smart_pointer.cpp:11:30:11:50 | call to make_shared | TAINT |
-| smart_pointer.cpp:12:10:12:10 | call to operator* [post update] | smart_pointer.cpp:13:10:13:10 | p | |
| smart_pointer.cpp:12:11:12:11 | p | smart_pointer.cpp:12:10:12:10 | call to operator* | TAINT |
| smart_pointer.cpp:12:11:12:11 | ref arg p | smart_pointer.cpp:13:10:13:10 | p | |
| smart_pointer.cpp:17:32:17:54 | call to make_shared | smart_pointer.cpp:18:11:18:11 | p | |
@@ -3234,7 +3233,6 @@
| smart_pointer.cpp:23:30:23:50 | call to make_unique | smart_pointer.cpp:24:11:24:11 | p | |
| smart_pointer.cpp:23:30:23:50 | call to make_unique | smart_pointer.cpp:25:10:25:10 | p | |
| smart_pointer.cpp:23:52:23:57 | call to source | smart_pointer.cpp:23:30:23:50 | call to make_unique | TAINT |
-| smart_pointer.cpp:24:10:24:10 | call to operator* [post update] | smart_pointer.cpp:25:10:25:10 | p | |
| smart_pointer.cpp:24:11:24:11 | p | smart_pointer.cpp:24:10:24:10 | call to operator* | TAINT |
| smart_pointer.cpp:24:11:24:11 | ref arg p | smart_pointer.cpp:25:10:25:10 | p | |
| smart_pointer.cpp:29:32:29:54 | call to make_unique | smart_pointer.cpp:30:11:30:11 | p | |
From f329c3fdab6064ffd82b2a3a54de826395c08c9e Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Wed, 31 Mar 2021 10:09:58 +0200
Subject: [PATCH 142/433] Java: Convert insecure bean validation sink to CSV
format
---
.../CWE/CWE-094/InsecureBeanValidation.ql | 21 ++-----------------
.../code/java/dataflow/ExternalFlow.qll | 4 +++-
2 files changed, 5 insertions(+), 20 deletions(-)
diff --git a/java/ql/src/Security/CWE/CWE-094/InsecureBeanValidation.ql b/java/ql/src/Security/CWE/CWE-094/InsecureBeanValidation.ql
index 6b8ab0851329..e4ee42008a17 100644
--- a/java/ql/src/Security/CWE/CWE-094/InsecureBeanValidation.ql
+++ b/java/ql/src/Security/CWE/CWE-094/InsecureBeanValidation.ql
@@ -13,6 +13,7 @@ import java
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.FlowSources
import DataFlow::PathGraph
+private import semmle.code.java.dataflow.ExternalFlow
/**
* A message interpolator Type that perform Expression Language (EL) evaluations
@@ -50,19 +51,6 @@ class SetMessageInterpolatorCall extends MethodAccess {
predicate isSafe() { not this.getAnArgument().getType() instanceof ELMessageInterpolatorType }
}
-/**
- * A method named `buildConstraintViolationWithTemplate` declared on a subtype
- * of `javax.validation.ConstraintValidatorContext`.
- */
-class BuildConstraintViolationWithTemplateMethod extends Method {
- BuildConstraintViolationWithTemplateMethod() {
- this.getDeclaringType()
- .getASupertype*()
- .hasQualifiedName("javax.validation", "ConstraintValidatorContext") and
- this.hasName("buildConstraintViolationWithTemplate")
- }
-}
-
/**
* Taint tracking BeanValidationConfiguration describing the flow of data from user input
* to the argument of a method that builds constraint error messages.
@@ -72,12 +60,7 @@ class BeanValidationConfig extends TaintTracking::Configuration {
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
- override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma |
- ma.getMethod() instanceof BuildConstraintViolationWithTemplateMethod and
- sink.asExpr() = ma.getArgument(0)
- )
- }
+ override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "bean-validation") }
}
from BeanValidationConfig cfg, DataFlow::PathNode source, DataFlow::PathNode sink
diff --git a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
index 2aa0b1d14e47..337f26d82c63 100644
--- a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
+++ b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
@@ -203,7 +203,9 @@ private predicate sinkModelCsv(string row) {
"java.nio.file;Files;false;createLink;;;Argument[0];create-file",
"java.nio.file;Files;false;createSymbolicLink;;;Argument[0];create-file",
"java.nio.file;Files;false;createTempDirectory;;;Argument[0];create-file",
- "java.nio.file;Files;false;createTempFile;;;Argument[0];create-file"
+ "java.nio.file;Files;false;createTempFile;;;Argument[0];create-file",
+ // Bean validation
+ "javax.validation;ConstraintValidatorContext;true;buildConstraintViolationWithTemplate;;;Argument[0];bean-validation"
]
}
From 0b7a6671dd75b8cd1ab54cac2b7d0cbd8604dd2a Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Wed, 31 Mar 2021 10:16:14 +0200
Subject: [PATCH 143/433] Java: Convert header splitting sinks to CSV format
---
.../code/java/dataflow/ExternalFlow.qll | 1 +
.../code/java/security/ResponseSplitting.qll | 43 +++++++------------
2 files changed, 17 insertions(+), 27 deletions(-)
diff --git a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
index 337f26d82c63..f728c0f9fcaf 100644
--- a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
+++ b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
@@ -76,6 +76,7 @@ private module Frameworks {
private import semmle.code.java.frameworks.ApacheHttp
private import semmle.code.java.frameworks.apache.Lang
private import semmle.code.java.frameworks.guava.Guava
+ private import semmle.code.java.security.ResponseSplitting
}
private predicate sourceModelCsv(string row) {
diff --git a/java/ql/src/semmle/code/java/security/ResponseSplitting.qll b/java/ql/src/semmle/code/java/security/ResponseSplitting.qll
index d09e6567b152..040174d50b50 100644
--- a/java/ql/src/semmle/code/java/security/ResponseSplitting.qll
+++ b/java/ql/src/semmle/code/java/security/ResponseSplitting.qll
@@ -5,41 +5,30 @@ import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.frameworks.Servlets
import semmle.code.java.frameworks.JaxWS
+private import semmle.code.java.dataflow.ExternalFlow
/** A sink that is vulnerable to an HTTP header splitting attack. */
-abstract class HeaderSplittingSink extends DataFlow::Node { }
+class HeaderSplittingSink extends DataFlow::Node {
+ HeaderSplittingSink() { sinkNode(this, "header-splitting") }
+}
+
+private class HeaderSplittingSinkModel extends SinkModelCsv {
+ override predicate row(string row) {
+ row =
+ [
+ "javax.servlet.http;HttpServletResponse;false;addCookie;;;Argument[0];header-splitting",
+ "javax.servlet.http;HttpServletResponse;false;addHeader;;;Argument;header-splitting",
+ "javax.servlet.http;HttpServletResponse;false;setHeader;;;Argument;header-splitting",
+ "javax.ws.rs.core;ResponseBuilder;false;header;;;Argument[1];header-splitting"
+ ]
+ }
+}
/** A source that introduces data considered safe to use by a header splitting source. */
abstract class SafeHeaderSplittingSource extends DataFlow::Node {
SafeHeaderSplittingSource() { this instanceof RemoteFlowSource }
}
-/** A sink that identifies a Java Servlet or JaxWs method that is vulnerable to an HTTP header splitting attack. */
-private class ServletHeaderSplittingSink extends HeaderSplittingSink {
- ServletHeaderSplittingSink() {
- exists(ResponseAddCookieMethod m, MethodAccess ma |
- ma.getMethod() = m and
- this.asExpr() = ma.getArgument(0)
- )
- or
- exists(ResponseAddHeaderMethod m, MethodAccess ma |
- ma.getMethod() = m and
- this.asExpr() = ma.getAnArgument()
- )
- or
- exists(ResponseSetHeaderMethod m, MethodAccess ma |
- ma.getMethod() = m and
- this.asExpr() = ma.getAnArgument()
- )
- or
- exists(JaxRsResponseBuilder builder, Method m |
- m = builder.getAMethod() and m.getName() = "header"
- |
- this.asExpr() = m.getAReference().getArgument(1)
- )
- }
-}
-
/** A default source that introduces data considered safe to use by a header splitting source. */
private class DefaultSafeHeaderSplittingSource extends SafeHeaderSplittingSource {
DefaultSafeHeaderSplittingSource() {
From 17fd758df1ec1c600ef8aef4454485b6c2bf5123 Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Wed, 31 Mar 2021 10:31:21 +0200
Subject: [PATCH 144/433] Java: Convert XSS sinks to CSV format
---
.../code/java/dataflow/ExternalFlow.qll | 1 +
java/ql/src/semmle/code/java/security/XSS.qll | 28 ++++++++-----------
2 files changed, 13 insertions(+), 16 deletions(-)
diff --git a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
index f728c0f9fcaf..402bb7fd7476 100644
--- a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
+++ b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
@@ -77,6 +77,7 @@ private module Frameworks {
private import semmle.code.java.frameworks.apache.Lang
private import semmle.code.java.frameworks.guava.Guava
private import semmle.code.java.security.ResponseSplitting
+ private import semmle.code.java.security.XSS
}
private predicate sourceModelCsv(string row) {
diff --git a/java/ql/src/semmle/code/java/security/XSS.qll b/java/ql/src/semmle/code/java/security/XSS.qll
index f51bc0bbdf56..9791eed203b8 100644
--- a/java/ql/src/semmle/code/java/security/XSS.qll
+++ b/java/ql/src/semmle/code/java/security/XSS.qll
@@ -29,33 +29,29 @@ class XssAdditionalTaintStep extends Unit {
abstract predicate step(DataFlow::Node node1, DataFlow::Node node2);
}
+private class DefaultXssSinkModel extends SinkModelCsv {
+ override predicate row(string row) {
+ row =
+ [
+ "javax.servlet.http;HttpServletResponse;false;sendError;(int,String);;Argument[1];xss",
+ "android.webkit;WebView;false;loadData;;;Argument[0];xss",
+ "android.webkit;WebView;false;loadUrl;;;Argument[0];xss",
+ "android.webkit;WebView;false;loadDataWithBaseURL;;;Argument[1];xss"
+ ]
+ }
+}
+
/** A default sink representing methods susceptible to XSS attacks. */
private class DefaultXssSink extends XssSink {
DefaultXssSink() {
sinkNode(this, "xss")
or
- exists(HttpServletResponseSendErrorMethod m, MethodAccess ma |
- ma.getMethod() = m and
- this.asExpr() = ma.getArgument(1)
- )
- or
exists(ServletWriterSourceToWritingMethodFlowConfig writer, MethodAccess ma |
ma.getMethod() instanceof WritingMethod and
writer.hasFlowToExpr(ma.getQualifier()) and
this.asExpr() = ma.getArgument(_)
)
or
- exists(Method m |
- m.getDeclaringType() instanceof TypeWebView and
- (
- m.getAReference().getArgument(0) = this.asExpr() and m.getName() = "loadData"
- or
- m.getAReference().getArgument(0) = this.asExpr() and m.getName() = "loadUrl"
- or
- m.getAReference().getArgument(1) = this.asExpr() and m.getName() = "loadDataWithBaseURL"
- )
- )
- or
exists(SpringRequestMappingMethod requestMappingMethod, ReturnStmt rs |
requestMappingMethod = rs.getEnclosingCallable() and
this.asExpr() = rs.getResult() and
From e544faed6de23be9821771087b22c7ffb87a5005 Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Thu, 1 Apr 2021 09:11:47 +0200
Subject: [PATCH 145/433] Java: Convert unsafe hostname verification sinks to
CSV format
---
.../Security/CWE/CWE-297/UnsafeHostnameVerification.ql | 10 ++--------
java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll | 5 ++++-
2 files changed, 6 insertions(+), 9 deletions(-)
diff --git a/java/ql/src/Security/CWE/CWE-297/UnsafeHostnameVerification.ql b/java/ql/src/Security/CWE/CWE-297/UnsafeHostnameVerification.ql
index 9c060565f284..058a1f9e1695 100644
--- a/java/ql/src/Security/CWE/CWE-297/UnsafeHostnameVerification.ql
+++ b/java/ql/src/Security/CWE/CWE-297/UnsafeHostnameVerification.ql
@@ -15,6 +15,7 @@ import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.Encryption
import DataFlow::PathGraph
+private import semmle.code.java.dataflow.ExternalFlow
/**
* Holds if `m` always returns `true` ignoring any exceptional flow.
@@ -49,14 +50,7 @@ class TrustAllHostnameVerifierConfiguration extends DataFlow::Configuration {
source.asExpr().(ClassInstanceExpr).getConstructedType() instanceof TrustAllHostnameVerifier
}
- override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma, Method m |
- (m instanceof SetDefaultHostnameVerifierMethod or m instanceof SetHostnameVerifierMethod) and
- ma.getMethod() = m
- |
- ma.getArgument(0) = sink.asExpr()
- )
- }
+ override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "set-hostname") }
override predicate isBarrier(DataFlow::Node barrier) {
// ignore nodes that are in functions that intentionally disable hostname verification
diff --git a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
index 402bb7fd7476..ba329d99f21b 100644
--- a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
+++ b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
@@ -207,7 +207,10 @@ private predicate sinkModelCsv(string row) {
"java.nio.file;Files;false;createTempDirectory;;;Argument[0];create-file",
"java.nio.file;Files;false;createTempFile;;;Argument[0];create-file",
// Bean validation
- "javax.validation;ConstraintValidatorContext;true;buildConstraintViolationWithTemplate;;;Argument[0];bean-validation"
+ "javax.validation;ConstraintValidatorContext;true;buildConstraintViolationWithTemplate;;;Argument[0];bean-validation",
+ // Set hostname
+ "javax.net.ssl;HttpsURLConnection;true;setDefaultHostnameVerifier;;;Argument[0];set-hostname",
+ "javax.net.ssl;HttpsURLConnection;true;setHostnameVerifier;;;Argument[0];set-hostname"
]
}
From 3e53484bb3b942c58f2bf1b90301a9779ebc34aa Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Thu, 1 Apr 2021 09:21:49 +0200
Subject: [PATCH 146/433] Java: Convert Google HTTP client API parseAs sink to
CSV format
---
.../code/java/dataflow/ExternalFlow.qll | 1 +
.../frameworks/google/GoogleHttpClientApi.qll | 22 +++++++------------
2 files changed, 9 insertions(+), 14 deletions(-)
diff --git a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
index ba329d99f21b..154ec98f2ad8 100644
--- a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
+++ b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
@@ -76,6 +76,7 @@ private module Frameworks {
private import semmle.code.java.frameworks.ApacheHttp
private import semmle.code.java.frameworks.apache.Lang
private import semmle.code.java.frameworks.guava.Guava
+ private import semmle.code.java.frameworks.google.GoogleHttpClientApi
private import semmle.code.java.security.ResponseSplitting
private import semmle.code.java.security.XSS
}
diff --git a/java/ql/src/semmle/code/java/frameworks/google/GoogleHttpClientApi.qll b/java/ql/src/semmle/code/java/frameworks/google/GoogleHttpClientApi.qll
index ccc446892f12..07e30711b449 100644
--- a/java/ql/src/semmle/code/java/frameworks/google/GoogleHttpClientApi.qll
+++ b/java/ql/src/semmle/code/java/frameworks/google/GoogleHttpClientApi.qll
@@ -2,14 +2,7 @@ import java
import semmle.code.java.Serializability
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.DataFlow5
-
-/** The method `parseAs` in `com.google.api.client.http.HttpResponse`. */
-private class ParseAsMethod extends Method {
- ParseAsMethod() {
- this.getDeclaringType().hasQualifiedName("com.google.api.client.http", "HttpResponse") and
- this.hasName("parseAs")
- }
-}
+private import semmle.code.java.dataflow.ExternalFlow
private class TypeLiteralToParseAsFlowConfiguration extends DataFlow5::Configuration {
TypeLiteralToParseAsFlowConfiguration() {
@@ -18,16 +11,17 @@ private class TypeLiteralToParseAsFlowConfiguration extends DataFlow5::Configura
override predicate isSource(DataFlow::Node source) { source.asExpr() instanceof TypeLiteral }
- override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma |
- ma.getAnArgument() = sink.asExpr() and
- ma.getMethod() instanceof ParseAsMethod
- )
- }
+ override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "google-parse-as") }
TypeLiteral getSourceWithFlowToParseAs() { hasFlow(DataFlow::exprNode(result), _) }
}
+private class ParseAsSinkModel extends SinkModelCsv {
+ override predicate row(string row) {
+ row = ["com.google.api.client.http;HttpResponse;false;parseAs;;;Argument;google-parse-as"]
+ }
+}
+
/** A field that is deserialized by `HttpResponse.parseAs`. */
class HttpResponseParseAsDeserializableField extends DeserializableField {
HttpResponseParseAsDeserializableField() {
From 87d42b02c0c49ff717c941d6ea5290b6ca3c40df Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Wed, 31 Mar 2021 10:21:12 +0200
Subject: [PATCH 147/433] Java: Convert other sinks
---
.../CWE/CWE-209/StackTraceExposure.ql | 11 +-
.../Security/CWE/CWE-036/OpenStream.ql | 15 +-
.../Security/CWE/CWE-074/XsltInjectionLib.qll | 41 +++--
.../Security/CWE/CWE-094/JexlInjectionLib.qll | 139 +++++++--------
.../Security/CWE/CWE-094/MvelInjectionLib.qll | 56 +++---
.../CWE/CWE-326/InsufficientKeySize.ql | 23 +--
.../Security/CWE/CWE-327/SslLib.qll | 20 ++-
.../Security/CWE/CWE-346/UnvalidatedCors.ql | 29 ++-
.../Security/CWE/CWE-522/InsecureLdapAuth.ql | 26 +--
.../Security/CWE/CWE-643/XPathInjection.ql | 23 +--
.../Security/CWE/CWE-652/XQueryInjection.ql | 7 +-
.../CWE/CWE-652/XQueryInjectionLib.qll | 12 ++
.../Security/CWE/CWE-917/OgnlInjectionLib.qll | 26 ++-
.../Security/CWE/CWE-918/RequestForgery.ql | 5 +-
.../Security/CWE/CWE-918/RequestForgery.qll | 168 ++++--------------
.../code/java/dataflow/ExternalFlow.qll | 23 ++-
.../java/security/UnsafeDeserialization.qll | 32 ++--
17 files changed, 302 insertions(+), 354 deletions(-)
diff --git a/java/ql/src/Security/CWE/CWE-209/StackTraceExposure.ql b/java/ql/src/Security/CWE/CWE-209/StackTraceExposure.ql
index c9c1e2917c0e..1f49d5cb06ac 100644
--- a/java/ql/src/Security/CWE/CWE-209/StackTraceExposure.ql
+++ b/java/ql/src/Security/CWE/CWE-209/StackTraceExposure.ql
@@ -16,6 +16,7 @@ import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.security.XSS
+private import semmle.code.java.dataflow.ExternalFlow
/**
* One of the `printStackTrace()` overloads on `Throwable`.
@@ -37,10 +38,12 @@ class ServletWriterSourceToPrintStackTraceMethodFlowConfig extends TaintTracking
override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof ServletWriterSource }
- override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma |
- sink.asExpr() = ma.getAnArgument() and ma.getMethod() instanceof PrintStackTraceMethod
- )
+ override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "print-stack-trace") }
+}
+
+private class PrintStackTraceSinkModel extends SinkModelCsv {
+ override predicate row(string row) {
+ row = ["java.lang;Throwable;true;printStackTrace;;;Argument;print-stack-trace"]
}
}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-036/OpenStream.ql b/java/ql/src/experimental/Security/CWE/CWE-036/OpenStream.ql
index 871d6bb4737c..6f75b3fc8ae5 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-036/OpenStream.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-036/OpenStream.ql
@@ -15,6 +15,7 @@ import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.ExternalFlow
import DataFlow::PathGraph
+private import semmle.code.java.dataflow.ExternalFlow
class URLConstructor extends ClassInstanceExpr {
URLConstructor() { this.getConstructor().getDeclaringType() instanceof TypeUrl }
@@ -39,13 +40,7 @@ class RemoteURLToOpenStreamFlowConfig extends TaintTracking::Configuration {
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
- override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess m |
- sink.asExpr() = m.getQualifier() and m.getMethod() instanceof URLOpenStreamMethod
- )
- or
- sinkNode(sink, "url-open-stream")
- }
+ override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "url-open-stream") }
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
exists(URLConstructor u |
@@ -55,6 +50,12 @@ class RemoteURLToOpenStreamFlowConfig extends TaintTracking::Configuration {
}
}
+private class URLOpenStreamSinkModel extends SinkModelCsv {
+ override predicate row(string row) {
+ row = ["java.net;URL;false;openStream;;;Argument[-1];url-open-stream"]
+ }
+}
+
from DataFlow::PathNode source, DataFlow::PathNode sink, MethodAccess call
where
sink.getNode().asExpr() = call.getQualifier() and
diff --git a/java/ql/src/experimental/Security/CWE/CWE-074/XsltInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-074/XsltInjectionLib.qll
index 4ba0eb6d0b11..bc0c8352a20f 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-074/XsltInjectionLib.qll
+++ b/java/ql/src/experimental/Security/CWE/CWE-074/XsltInjectionLib.qll
@@ -2,6 +2,7 @@ import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.XmlParsers
import DataFlow
+private import semmle.code.java.dataflow.ExternalFlow
/**
* A taint-tracking configuration for unvalidated user input that is used in XSLT transformation.
@@ -103,15 +104,20 @@ class TypeXsltPackage extends Class {
/** A data flow sink for unvalidated user input that is used in XSLT transformation. */
class XsltInjectionSink extends DataFlow::ExprNode {
- XsltInjectionSink() {
- exists(MethodAccess ma, Method m | m = ma.getMethod() and ma.getQualifier() = this.getExpr() |
- ma instanceof TransformerTransform or
- m instanceof XsltTransformerTransformMethod or
- m instanceof Xslt30TransformerTransformMethod or
- m instanceof Xslt30TransformerApplyTemplatesMethod or
- m instanceof Xslt30TransformerCallFunctionMethod or
- m instanceof Xslt30TransformerCallTemplateMethod
- )
+ XsltInjectionSink() { sinkNode(this, "xslt") }
+}
+
+private class XsltInjectionSinkModel extends SinkModelCsv {
+ override predicate row(string row) {
+ row =
+ [
+ "net.sf.saxon.s9api;XsltTransformer;false;transform;;;Argument[-1];xslt",
+ "net.sf.saxon.s9api;Xslt30Transformer;false;transform;;;Argument[-1];xslt",
+ "net.sf.saxon.s9api;Xslt30Transformer;false;applyTemplates;;;Argument[-1];xslt",
+ "net.sf.saxon.s9api;Xslt30Transformer;false;callFunction;;;Argument[-1];xslt",
+ "net.sf.saxon.s9api;Xslt30Transformer;false;callTemplate;;;Argument[-1];xslt",
+ "javax.xml.transform;Transformer;false;transform;;;Argument[-1];xslt"
+ ]
}
}
@@ -186,16 +192,21 @@ private class TransformerFactoryWithSecureProcessingFeatureFlowConfig extends Da
)
}
- override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma |
- sink.asExpr() = ma.getQualifier() and
- ma.getMethod().getDeclaringType() instanceof TransformerFactory
- )
- }
+ override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "xslt-transformer") }
override int fieldFlowBranchLimit() { result = 0 }
}
+private class TransformerFactorySinkModel extends SinkModelCsv {
+ override predicate row(string row) {
+ row =
+ [
+ "javax.xml.transform;TransformerFactory;false;;;;Argument[-1];xslt-transformer",
+ "javax.xml.transform.sax;SAXTransformerFactory;false;;;;Argument[-1];xslt-transformer"
+ ]
+ }
+}
+
/** A `ParserConfig` specific to `TransformerFactory`. */
private class TransformerFactoryFeatureConfig extends ParserConfig {
TransformerFactoryFeatureConfig() {
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll
index 561d7e46ae90..8fcde750e54c 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll
@@ -1,6 +1,7 @@
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking
+private import semmle.code.java.dataflow.ExternalFlow
/**
* A taint-tracking configuration for unsafe user input
@@ -21,7 +22,7 @@ class JexlInjectionConfig extends TaintTracking::Configuration {
}
/**
- * A sink for Expresssion Language injection vulnerabilities via Jexl,
+ * A sink for Expression Language injection vulnerabilities via Jexl,
* i.e. method calls that run evaluation of a JEXL expression.
*
* Creating a `Callable` from a tainted JEXL expression or script is considered as a sink
@@ -30,18 +31,41 @@ class JexlInjectionConfig extends TaintTracking::Configuration {
* maybe stored in an object field and then reached by a different flow.
*/
private class JexlEvaluationSink extends DataFlow::ExprNode {
- JexlEvaluationSink() {
- exists(MethodAccess ma, Method m, Expr taintFrom |
- ma.getMethod() = m and taintFrom = this.asExpr()
- |
- m instanceof DirectJexlEvaluationMethod and ma.getQualifier() = taintFrom
- or
- m instanceof CreateJexlCallableMethod and ma.getQualifier() = taintFrom
- or
- m instanceof JexlEngineGetSetPropertyMethod and
- taintFrom.getType() instanceof TypeString and
- ma.getAnArgument() = taintFrom
- )
+ JexlEvaluationSink() { sinkNode(this, "jexl") }
+}
+
+private class JexlEvaluationSinkModel extends SinkModelCsv {
+ override predicate row(string row) {
+ row =
+ [
+ // Direct JEXL evaluation
+ "org.apache.commons.jexl2;Expression;false;evaluate;;;Argument[-1];jexl",
+ "org.apache.commons.jexl3;JexlExpression;false;evaluate;;;Argument[-1];jexl",
+ "org.apache.commons.jexl2;Script;false;execute;;;Argument[-1];jexl",
+ "org.apache.commons.jexl3;JexlScript;false;execute;;;Argument[-1];jexl",
+ "org.apache.commons.jexl2;JxltEngine$Expression;false;evaluate;;;Argument[-1];jexl",
+ "org.apache.commons.jexl3;JxltEngine$Expression;false;evaluate;;;Argument[-1];jexl",
+ "org.apache.commons.jexl2;JxltEngine$Expression;false;prepare;;;Argument[-1];jexl",
+ "org.apache.commons.jexl3;JxltEngine$Expression;false;prepare;;;Argument[-1];jexl",
+ "org.apache.commons.jexl2;JxltEngine$Template;false;evaluate;;;Argument[-1];jexl",
+ "org.apache.commons.jexl3;JxltEngine$Template;false;evaluate;;;Argument[-1];jexl",
+ "org.apache.commons.jexl2;UnifiedJEXL$Expression;false;evaluate;;;Argument[-1];jexl",
+ "org.apache.commons.jexl3;UnifiedJEXL$Expression;false;evaluate;;;Argument[-1];jexl",
+ "org.apache.commons.jexl2;UnifiedJEXL$Expression;false;prepare;;;Argument[-1];jexl",
+ "org.apache.commons.jexl3;UnifiedJEXL$Expression;false;prepare;;;Argument[-1];jexl",
+ "org.apache.commons.jexl2;UnifiedJEXL$Template;false;evaluate;;;Argument[-1];jexl",
+ "org.apache.commons.jexl3;UnifiedJEXL$Template;false;evaluate;;;Argument[-1];jexl",
+ // JEXL callable
+ "org.apache.commons.jexl2;Expression;false;callable;;;Argument[-1];jexl",
+ "org.apache.commons.jexl3;JexlExpression;false;callable;;;Argument[-1];jexl",
+ "org.apache.commons.jexl2;Script;false;callable;;;Argument[-1];jexl",
+ "org.apache.commons.jexl3;JexlScript;false;callable;;;Argument[-1];jexl",
+ // Methods in the `JexlEngine` class that gets or sets a property with a JEXL expression.
+ "org.apache.commons.jexl2;JexlEngine;false;getProperty;;;Argument[1..2];jexl",
+ "org.apache.commons.jexl3;JexlEngine;false;getProperty;;;Argument[1..2];jexl",
+ "org.apache.commons.jexl2;JexlEngine;false;setProperty;;;Argument[1];jexl",
+ "org.apache.commons.jexl3;JexlEngine;false;setProperty;;;Argument[1];jexl"
+ ]
}
}
@@ -98,22 +122,36 @@ private class SandboxedJexlFlowConfig extends DataFlow2::Configuration {
override predicate isSource(DataFlow::Node node) { node instanceof SandboxedJexlSource }
- override predicate isSink(DataFlow::Node node) {
- exists(MethodAccess ma, Method m | ma.getMethod() = m |
- (
- m instanceof CreateJexlScriptMethod or
- m instanceof CreateJexlExpressionMethod or
- m instanceof CreateJexlTemplateMethod
- ) and
- ma.getQualifier() = node.asExpr()
- )
- }
+ override predicate isSink(DataFlow::Node node) { sinkNode(node, "sandboxed-jexl") }
override predicate isAdditionalFlowStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
createsJexlEngine(fromNode, toNode)
}
}
+private class SandboxedJexlEvaluationSinkModel extends SinkModelCsv {
+ override predicate row(string row) {
+ row =
+ [
+ // CreateJexlScriptMethod
+ "org.apache.commons.jexl2;JexlEngine;false;createScript;;;Argument[-1];sandboxed-jexl",
+ "org.apache.commons.jexl3;JexlEngine;false;createScript;;;Argument[-1];sandboxed-jexl",
+ // CreateJexlExpressionMethod
+ "org.apache.commons.jexl2;UnifiedJEXL;false;parse;;;Argument[-1];sandboxed-jexl",
+ "org.apache.commons.jexl3;UnifiedJEXL;false;parse;;;Argument[-1];sandboxed-jexl",
+ "org.apache.commons.jexl2;JxltEngine;false;createExpression;;;Argument[-1];sandboxed-jexl",
+ "org.apache.commons.jexl3;JxltEngine;false;createExpression;;;Argument[-1];sandboxed-jexl",
+ "org.apache.commons.jexl2;JexlEngine;false;createExpression;;;Argument[-1];sandboxed-jexl",
+ "org.apache.commons.jexl3;JexlEngine;false;createExpression;;;Argument[-1];sandboxed-jexl",
+ // CreateJexlTemplateMethod
+ "org.apache.commons.jexl2;JxltEngine;false;createTemplate;;;Argument[-1];sandboxed-jexl",
+ "org.apache.commons.jexl3;JxltEngine;false;createTemplate;;;Argument[-1];sandboxed-jexl",
+ "org.apache.commons.jexl2;UnifiedJEXL;false;createTemplate;;;Argument[-1];sandboxed-jexl",
+ "org.apache.commons.jexl3;UnifiedJEXL;false;createTemplate;;;Argument[-1];sandboxed-jexl"
+ ]
+ }
+}
+
/**
* Defines a data flow source for JEXL engines configured with a sandbox.
*/
@@ -164,35 +202,6 @@ private predicate returnsDataFromBean(DataFlow::Node fromNode, DataFlow::Node to
)
}
-/**
- * A methods in the `JexlEngine` class that gets or sets a property with a JEXL expression.
- */
-private class JexlEngineGetSetPropertyMethod extends Method {
- JexlEngineGetSetPropertyMethod() {
- getDeclaringType() instanceof JexlEngine and
- hasName(["getProperty", "setProperty"])
- }
-}
-
-/**
- * A method that triggers direct evaluation of JEXL expressions.
- */
-private class DirectJexlEvaluationMethod extends Method {
- DirectJexlEvaluationMethod() {
- getDeclaringType() instanceof JexlExpression and hasName("evaluate")
- or
- getDeclaringType() instanceof JexlScript and hasName("execute")
- or
- getDeclaringType() instanceof JxltEngineExpression and hasName(["evaluate", "prepare"])
- or
- getDeclaringType() instanceof JxltEngineTemplate and hasName("evaluate")
- or
- getDeclaringType() instanceof UnifiedJexlExpression and hasName(["evaluate", "prepare"])
- or
- getDeclaringType() instanceof UnifiedJexlTemplate and hasName("evaluate")
- }
-}
-
/**
* A method that creates a JEXL script.
*/
@@ -200,16 +209,6 @@ private class CreateJexlScriptMethod extends Method {
CreateJexlScriptMethod() { getDeclaringType() instanceof JexlEngine and hasName("createScript") }
}
-/**
- * A method that creates a `Callable` for a JEXL expression or script.
- */
-private class CreateJexlCallableMethod extends Method {
- CreateJexlCallableMethod() {
- (getDeclaringType() instanceof JexlExpression or getDeclaringType() instanceof JexlScript) and
- hasName("callable")
- }
-}
-
/**
* A method that creates a JEXL template.
*/
@@ -267,22 +266,6 @@ private class JexlUberspect extends Interface {
}
}
-private class JxltEngineExpression extends NestedType {
- JxltEngineExpression() { getEnclosingType() instanceof JxltEngine and hasName("Expression") }
-}
-
-private class JxltEngineTemplate extends NestedType {
- JxltEngineTemplate() { getEnclosingType() instanceof JxltEngine and hasName("Template") }
-}
-
-private class UnifiedJexlExpression extends NestedType {
- UnifiedJexlExpression() { getEnclosingType() instanceof UnifiedJexl and hasName("Expression") }
-}
-
-private class UnifiedJexlTemplate extends NestedType {
- UnifiedJexlTemplate() { getEnclosingType() instanceof UnifiedJexl and hasName("Template") }
-}
-
private class Reader extends RefType {
Reader() { hasQualifiedName("java.io", "Reader") }
}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/MvelInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/MvelInjectionLib.qll
index a6cf891330f0..dec268a2a4bc 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-094/MvelInjectionLib.qll
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/MvelInjectionLib.qll
@@ -1,6 +1,7 @@
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking
+private import semmle.code.java.dataflow.ExternalFlow
/**
* A taint-tracking configuration for unsafe user input
@@ -30,36 +31,31 @@ class MvelInjectionConfig extends TaintTracking::Configuration {
* i.e. methods that run evaluation of a MVEL expression.
*/
class MvelEvaluationSink extends DataFlow::ExprNode {
- MvelEvaluationSink() {
- exists(StaticMethodAccess ma, Method m | m = ma.getMethod() |
- (
- m instanceof MvelEvalMethod or
- m instanceof TemplateRuntimeEvaluationMethod
- ) and
- ma.getArgument(0) = asExpr()
- )
- or
- exists(MethodAccess ma, Method m | m = ma.getMethod() |
- m instanceof MvelScriptEngineEvaluationMethod and
- ma.getArgument(0) = asExpr()
- )
- or
- exists(MethodAccess ma, Method m | m = ma.getMethod() |
- (
- m instanceof ExecutableStatementEvaluationMethod or
- m instanceof CompiledExpressionEvaluationMethod or
- m instanceof CompiledAccExpressionEvaluationMethod or
- m instanceof AccessorEvaluationMethod or
- m instanceof CompiledScriptEvaluationMethod or
- m instanceof MvelCompiledScriptEvaluationMethod
- ) and
- ma.getQualifier() = asExpr()
- )
- or
- exists(StaticMethodAccess ma, Method m | m = ma.getMethod() |
- m instanceof MvelRuntimeEvaluationMethod and
- ma.getArgument(1) = asExpr()
- )
+ MvelEvaluationSink() { sinkNode(this, "mvel") }
+}
+
+private class MvelEvaluationSinkModel extends SinkModelCsv {
+ override predicate row(string row) {
+ row =
+ [
+ "org.mvel2.jsr223;MvelScriptEngine;false;evaluate;;;Argument[0];mvel",
+ "org.mvel2.jsr223;MvelScriptEngine;false;eval;;;Argument[0];mvel",
+ "org.mvel2.compiler;ExecutableStatement;false;getValue;;;Argument[-1];mvel",
+ "org.mvel2.compiler;CompiledExpression;false;getDirectValue;;;Argument[-1];mvel",
+ "org.mvel2.compiler;CompiledAccExpression;false;getValue;;;Argument[-1];mvel",
+ "org.mvel2.compiler;Accessor;false;getValue;;;Argument[-1];mvel",
+ "javax.script;CompiledScript;false;eval;;;Argument[-1];mvel",
+ "org.mvel2.jsr223;MvelCompiledScript;false;eval;;;Argument[-1];mvel",
+ "org.mvel2;MVEL;false;eval;;;Argument[0];mvel",
+ "org.mvel2;MVEL;false;executeExpression;;;Argument[0];mvel",
+ "org.mvel2;MVEL;false;evalToBoolean;;;Argument[0];mvel",
+ "org.mvel2;MVEL;false;evalToString;;;Argument[0];mvel",
+ "org.mvel2;MVEL;false;executeAllExpression;;;Argument[0];mvel",
+ "org.mvel2;MVEL;false;executeSetExpression;;;Argument[0];mvel",
+ "org.mvel2.templates;TemplateRuntime;false;eval;;;Argument[0];mvel",
+ "org.mvel2.templates;TemplateRuntime;false;execute;;;Argument[0];mvel",
+ "org.mvel2;MVELRuntime;false;execute;;;Argument[1];mvel"
+ ]
}
}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.ql b/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.ql
index 155d05abfae3..ca86f42c561a 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.ql
@@ -12,6 +12,7 @@
import java
import semmle.code.java.security.Encryption
import semmle.code.java.dataflow.TaintTracking
+private import semmle.code.java.dataflow.ExternalFlow
/** The Java class `java.security.spec.ECGenParameterSpec`. */
class ECGenParameterSpec extends RefType {
@@ -55,11 +56,12 @@ class KeyGeneratorInitConfiguration extends TaintTracking::Configuration {
exists(JavaxCryptoKeyGenerator jcg | jcg = source.asExpr())
}
- override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma |
- ma.getMethod() instanceof KeyGeneratorInitMethod and
- sink.asExpr() = ma.getQualifier()
- )
+ override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "keygen") }
+}
+
+private class KeyGeneratorInitSinkModel extends SinkModelCsv {
+ override predicate row(string row) {
+ row = ["javax.crypto;KeyGenerator;false;init;;;Argument[-1];keygen"]
}
}
@@ -71,11 +73,12 @@ class KeyPairGeneratorInitConfiguration extends TaintTracking::Configuration {
exists(JavaSecurityKeyPairGenerator jkg | jkg = source.asExpr())
}
- override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma |
- ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- sink.asExpr() = ma.getQualifier()
- )
+ override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "keypairgen") }
+}
+
+private class KeyPairGeneratorInitSinkModel extends SinkModelCsv {
+ override predicate row(string row) {
+ row = ["java.security;KeyPairGenerator;false;initialize;;;Argument[-1];keypairgen"]
}
}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-327/SslLib.qll b/java/ql/src/experimental/Security/CWE/CWE-327/SslLib.qll
index bfa2530b07e7..3845953f0aab 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-327/SslLib.qll
+++ b/java/ql/src/experimental/Security/CWE/CWE-327/SslLib.qll
@@ -3,6 +3,7 @@ import semmle.code.java.security.Encryption
import semmle.code.java.dataflow.TaintTracking
import DataFlow
import PathGraph
+private import semmle.code.java.dataflow.ExternalFlow
/**
* A taint-tracking configuration for unsafe SSL and TLS versions.
@@ -12,11 +13,20 @@ class UnsafeTlsVersionConfig extends TaintTracking::Configuration {
override predicate isSource(DataFlow::Node source) { source.asExpr() instanceof UnsafeTlsVersion }
- override predicate isSink(DataFlow::Node sink) {
- sink instanceof SslContextGetInstanceSink or
- sink instanceof CreateSslParametersSink or
- sink instanceof SslParametersSetProtocolsSink or
- sink instanceof SetEnabledProtocolsSink
+ override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "ssl") }
+}
+
+private class UnsafeTlsVersionSinkModel extends SinkModelCsv {
+ override predicate row(string row) {
+ row =
+ [
+ "javax.net.ssl;SSLContext;false;getInstance;;;Argument[0];ssl",
+ "javax.net.ssl;SSLParameters;false;SSLParameters;;;Argument[1];ssl",
+ "javax.net.ssl;SSLParameters;false;setProtocols;;;Argument[0];ssl",
+ "javax.net.ssl;SSLSocket;false;setEnabledProtocols;;;Argument[0];ssl",
+ "javax.net.ssl;SSLServerSocket;false;setEnabledProtocols;;;Argument[0];ssl",
+ "javax.net.ssl;SSLEngine;false;setEnabledProtocols;;;Argument[0];ssl"
+ ]
}
}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-346/UnvalidatedCors.ql b/java/ql/src/experimental/Security/CWE/CWE-346/UnvalidatedCors.ql
index c5a6c36d6a61..2fe6569318fc 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-346/UnvalidatedCors.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-346/UnvalidatedCors.ql
@@ -15,6 +15,7 @@ import semmle.code.java.frameworks.Servlets
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.TaintTracking2
import DataFlow::PathGraph
+private import semmle.code.java.dataflow.ExternalFlow
/**
* Holds if `header` sets `Access-Control-Allow-Credentials` to `true`. This ensures fair chances of exploitability.
@@ -29,33 +30,29 @@ private predicate setsAllowCredentials(MethodAccess header) {
header.getArgument(1).(CompileTimeConstantExpr).getStringValue().toLowerCase() = "true"
}
-private class CorsProbableCheckAccess extends MethodAccess {
- CorsProbableCheckAccess() {
- getMethod().hasName("contains") and
- getMethod().getDeclaringType().getASourceSupertype*() instanceof CollectionType
- or
- getMethod().hasName("containsKey") and
- getMethod().getDeclaringType().getASourceSupertype*() instanceof MapType
- or
- getMethod().hasName("equals") and
- getQualifier().getType() instanceof TypeString
- }
-}
-
private Expr getAccessControlAllowOriginHeaderName() {
result.(CompileTimeConstantExpr).getStringValue().toLowerCase() = "access-control-allow-origin"
}
/**
- * This taintflow2 configuration checks if there is a flow from source node towards CorsProbableCheckAccess methods.
+ * This taintflow2 configuration checks if there is a flow from source node towards probably CORS checking methods.
*/
class CorsSourceReachesCheckConfig extends TaintTracking2::Configuration {
CorsSourceReachesCheckConfig() { this = "CorsOriginConfig" }
override predicate isSource(DataFlow::Node source) { any(CorsOriginConfig c).hasFlow(source, _) }
- override predicate isSink(DataFlow::Node sink) {
- sink.asExpr() = any(CorsProbableCheckAccess check).getAnArgument()
+ override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "cors") }
+}
+
+private class CorsProbableCheckAccessSinkModel extends SinkModelCsv {
+ override predicate row(string row) {
+ row =
+ [
+ "java.util;Collection;true;contains;;;Argument;cors",
+ "java.util;Map;true;containsKey;;;Argument;cors",
+ "java.lang;String;true;equals;;;Argument;cors"
+ ]
}
}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
index 4ce2b8b7134c..3c445934ad33 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
@@ -16,6 +16,7 @@ import semmle.code.java.frameworks.Jndi
import semmle.code.java.frameworks.Networking
import semmle.code.java.dataflow.TaintTracking
import DataFlow::PathGraph
+private import semmle.code.java.dataflow.ExternalFlow
/**
* Insecure (non-SSL, non-private) LDAP URL string literal.
@@ -145,12 +146,7 @@ class InsecureUrlFlowConfig extends TaintTracking::Configuration {
override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof InsecureLdapUrl }
/** Sink of directory context creation. */
- override predicate isSink(DataFlow::Node sink) {
- exists(ConstructorCall cc |
- cc.getConstructedType().getASupertype*() instanceof TypeDirContext and
- sink.asExpr() = cc.getArgument(0)
- )
- }
+ override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "ldap") }
/** Method call of `env.put()`. */
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
@@ -176,11 +172,12 @@ class BasicAuthFlowConfig extends DataFlow::Configuration {
}
/** Sink of directory context creation. */
- override predicate isSink(DataFlow::Node sink) {
- exists(ConstructorCall cc |
- cc.getConstructedType().getASupertype*() instanceof TypeDirContext and
- sink.asExpr() = cc.getArgument(0)
- )
+ override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "ldap") }
+}
+
+private class LdapSinkModel extends SinkModelCsv {
+ override predicate row(string row) {
+ row = ["javax.naming.directory;DirContext;true;.ctor;;;Argument[0];ldap"]
}
}
@@ -198,12 +195,7 @@ class SSLFlowConfig extends DataFlow::Configuration {
}
/** Sink of directory context creation. */
- override predicate isSink(DataFlow::Node sink) {
- exists(ConstructorCall cc |
- cc.getConstructedType().getASupertype*() instanceof TypeDirContext and
- sink.asExpr() = cc.getArgument(0)
- )
- }
+ override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "ldap") }
}
from DataFlow::PathNode source, DataFlow::PathNode sink, InsecureUrlFlowConfig config
diff --git a/java/ql/src/experimental/Security/CWE/CWE-643/XPathInjection.ql b/java/ql/src/experimental/Security/CWE/CWE-643/XPathInjection.ql
index e5a29df46d59..598f42b0c703 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-643/XPathInjection.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-643/XPathInjection.ql
@@ -15,6 +15,7 @@ import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.security.XmlParsers
import DataFlow::PathGraph
+private import semmle.code.java.dataflow.ExternalFlow
class XPathInjectionConfiguration extends TaintTracking::Configuration {
XPathInjectionConfiguration() { this = "XPathInjection" }
@@ -25,16 +26,18 @@ class XPathInjectionConfiguration extends TaintTracking::Configuration {
}
class XPathInjectionSink extends DataFlow::ExprNode {
- XPathInjectionSink() {
- exists(Method m, MethodAccess ma | ma.getMethod() = m |
- m.getDeclaringType().hasQualifiedName("javax.xml.xpath", "XPath") and
- (m.hasName("evaluate") or m.hasName("compile")) and
- ma.getArgument(0) = this.getExpr()
- or
- m.getDeclaringType().hasQualifiedName("org.dom4j", "Node") and
- (m.hasName("selectNodes") or m.hasName("selectSingleNode")) and
- ma.getArgument(0) = this.getExpr()
- )
+ XPathInjectionSink() { sinkNode(this, "xpath") }
+}
+
+private class XPathInjectionSinkModel extends SinkModelCsv {
+ override predicate row(string row) {
+ row =
+ [
+ "javax.xml.xpath;XPath;false;compile;;;Argument[0];xpath",
+ "javax.xml.xpath;XPath;false;evaluate;;;Argument[0];xpath",
+ "org.dom4j;Node;false;selectNodes;;;Argument[0];xpath",
+ "org.dom4j;Node;false;selectSingleNode;;;Argument[0];xpath"
+ ]
}
}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-652/XQueryInjection.ql b/java/ql/src/experimental/Security/CWE/CWE-652/XQueryInjection.ql
index 0bb85272f085..3b8e43c5d7d2 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-652/XQueryInjection.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-652/XQueryInjection.ql
@@ -14,6 +14,7 @@ import java
import semmle.code.java.dataflow.FlowSources
import XQueryInjectionLib
import DataFlow::PathGraph
+private import semmle.code.java.dataflow.ExternalFlow
/**
* A taint-tracking configuration tracing flow from remote sources, through an XQuery parser, to its eventual execution.
@@ -23,11 +24,7 @@ class XQueryInjectionConfig extends TaintTracking::Configuration {
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
- override predicate isSink(DataFlow::Node sink) {
- sink.asExpr() = any(XQueryPreparedExecuteCall xpec).getPreparedExpression() or
- sink.asExpr() = any(XQueryExecuteCall xec).getExecuteQueryArgument() or
- sink.asExpr() = any(XQueryExecuteCommandCall xecc).getExecuteCommandArgument()
- }
+ override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "xquery") }
/**
* Holds if taint from the input `pred` to a `prepareExpression` call flows to the returned prepared expression `succ`.
diff --git a/java/ql/src/experimental/Security/CWE/CWE-652/XQueryInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-652/XQueryInjectionLib.qll
index 2a4019f2c9a9..13452e4e55dc 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-652/XQueryInjectionLib.qll
+++ b/java/ql/src/experimental/Security/CWE/CWE-652/XQueryInjectionLib.qll
@@ -1,4 +1,5 @@
import java
+private import semmle.code.java.dataflow.ExternalFlow
/** A call to `XQConnection.prepareExpression`. */
class XQueryParserCall extends MethodAccess {
@@ -66,3 +67,14 @@ class XQueryExecuteCommandCall extends MethodAccess {
/** Return this execute command argument. */
Expr getExecuteCommandArgument() { result = this.getArgument(0) }
}
+
+private class XQuerySinkModel extends SinkModelCsv {
+ override predicate row(string row) {
+ row =
+ [
+ "javax.xml.xquery;XQPreparedExpression;true;executeQuery;;;Argument[-1];xquery",
+ "javax.xml.xquery;XQExpression;true;executeQuery;;;Argument[0];xquery",
+ "javax.xml.xquery;XQExpression;true;executeCommand;;;Argument[0];xquery"
+ ]
+ }
+}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-917/OgnlInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-917/OgnlInjectionLib.qll
index 569e18a29c38..002e06aaafdc 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-917/OgnlInjectionLib.qll
+++ b/java/ql/src/experimental/Security/CWE/CWE-917/OgnlInjectionLib.qll
@@ -2,6 +2,7 @@ import java
import semmle.code.java.dataflow.FlowSources
import DataFlow
import DataFlow::PathGraph
+private import semmle.code.java.dataflow.ExternalFlow
/**
* A taint-tracking configuration for unvalidated user input that is used in OGNL EL evaluation.
@@ -82,12 +83,25 @@ predicate ognlInjectionSinkMethod(Method m, int index) {
/** A data flow sink for unvalidated user input that is used in OGNL EL evaluation. */
class OgnlInjectionSink extends DataFlow::ExprNode {
- OgnlInjectionSink() {
- exists(MethodAccess ma, Method m, int index |
- ma.getMethod() = m and
- (ma.getArgument(index) = this.getExpr() or ma.getQualifier() = this.getExpr()) and
- ognlInjectionSinkMethod(m, index)
- )
+ OgnlInjectionSink() { sinkNode(this, "ognl") }
+}
+
+private class OgnlInjectionSinkModel extends SinkModelCsv {
+ override predicate row(string row) {
+ row =
+ [
+ "org.apache.commons.ognl;Ognl;false;setValue;;;Argument[-1..0];ognl",
+ "org.apache.commons.ognl;Ognl;false;getValue;;;Argument[-1..0];ognl",
+ "ognl;Ognl;false;setValue;;;Argument[-1..0];ognl",
+ "ognl;Ognl;false;getValue;;;Argument[-1..0];ognl",
+ "org.apache.commons.ognl;Node;true;setValue;;;Argument[-1..0];ognl",
+ "org.apache.commons.ognl;Node;true;getValue;;;Argument[-1..0];ognl",
+ "ognl;Node;true;setValue;;;Argument[-1..0];ognl",
+ "ognl;Node;true;getValue;;;Argument[-1..0];ognl",
+ "com.opensymphony.xwork2.ognl;OgnlUtil;false;setValue;;;Argument[-1..0];ognl",
+ "com.opensymphony.xwork2.ognl;OgnlUtil;false;getValue;;;Argument[-1..0];ognl",
+ "com.opensymphony.xwork2.ognl;OgnlUtil;false;callMethod;;;Argument[-1..0];ognl"
+ ]
}
}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-918/RequestForgery.ql b/java/ql/src/experimental/Security/CWE/CWE-918/RequestForgery.ql
index c3bf787881fa..6b0333da4709 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-918/RequestForgery.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-918/RequestForgery.ql
@@ -14,13 +14,16 @@ import java
import semmle.code.java.dataflow.FlowSources
import RequestForgery
import DataFlow::PathGraph
+private import semmle.code.java.dataflow.ExternalFlow
class RequestForgeryConfiguration extends TaintTracking::Configuration {
RequestForgeryConfiguration() { this = "Server Side Request Forgery" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
- override predicate isSink(DataFlow::Node sink) { sink instanceof RequestForgerySink }
+ override predicate isSink(DataFlow::Node sink) {
+ sink instanceof RequestForgerySink or sinkNode(sink, "request-forgery-sink")
+ }
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
requestForgeryStep(pred, succ)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-918/RequestForgery.qll b/java/ql/src/experimental/Security/CWE/CWE-918/RequestForgery.qll
index 3fc52ddca766..fa784baa7f23 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-918/RequestForgery.qll
+++ b/java/ql/src/experimental/Security/CWE/CWE-918/RequestForgery.qll
@@ -5,6 +5,7 @@ import semmle.code.java.frameworks.spring.Spring
import semmle.code.java.frameworks.JaxWS
import semmle.code.java.frameworks.javase.Http
import semmle.code.java.dataflow.DataFlow
+private import semmle.code.java.dataflow.ExternalFlow
predicate requestForgeryStep(DataFlow::Node pred, DataFlow::Node succ) {
// propagate to a URI when its host is assigned to
@@ -38,119 +39,42 @@ predicate requestForgeryStep(DataFlow::Node pred, DataFlow::Node succ) {
)
}
-/** A data flow sink for request forgery vulnerabilities. */
-abstract class RequestForgerySink extends DataFlow::Node { }
-
-/**
- * An argument to an url `openConnection` or `openStream` call
- * taken as a sink for request forgery vulnerabilities.
- */
-private class UrlOpen extends RequestForgerySink {
- UrlOpen() {
- exists(MethodAccess ma |
- ma.getMethod() instanceof UrlOpenConnectionMethod or
- ma.getMethod() instanceof UrlOpenStreamMethod
- |
- this.asExpr() = ma.getQualifier()
- )
- }
-}
-
-/**
- * An argument to an Apache `setURI` call taken as a
- * sink for request forgery vulnerabilities.
- */
-private class ApacheSetUri extends RequestForgerySink {
- ApacheSetUri() {
- exists(MethodAccess ma |
- ma.getReceiverType() instanceof ApacheHttpRequest and
- ma.getMethod().hasName("setURI")
- |
- this.asExpr() = ma.getArgument(0)
- )
- }
-}
-
-/**
- * An argument to any Apache Request Instantiation call taken as a
- * sink for request forgery vulnerabilities.
- */
-private class ApacheHttpRequestInstantiation extends RequestForgerySink {
- ApacheHttpRequestInstantiation() {
- exists(ClassInstanceExpr c | c.getConstructedType() instanceof ApacheHttpRequest |
- this.asExpr() = c.getArgument(0)
- )
- }
-}
-
-/**
- * An argument to a Apache RequestBuilder method call taken as a
- * sink for request forgery vulnerabilities.
- */
-private class ApacheHttpRequestBuilderArgument extends RequestForgerySink {
- ApacheHttpRequestBuilderArgument() {
- exists(MethodAccess ma |
- ma.getReceiverType() instanceof TypeApacheHttpRequestBuilder and
- ma.getMethod().hasName(["setURI", "get", "post", "put", "optons", "head", "delete"])
- |
- this.asExpr() = ma.getArgument(0)
- )
- }
-}
-
-/**
- * An argument to any Java.net.http.request Instantiation call taken as a
- * sink for request forgery vulnerabilities.
- */
-private class HttpRequestNewBuilder extends RequestForgerySink {
- HttpRequestNewBuilder() {
- exists(MethodAccess call |
- call.getCallee().hasName("newBuilder") and
- call.getMethod().getDeclaringType().getName() = "HttpRequest"
- |
- this.asExpr() = call.getArgument(0)
- )
- }
-}
-
-/**
- * An argument to an Http Builder `uri` call taken as a
- * sink for request forgery vulnerabilities.
- */
-private class HttpBuilderUriArgument extends RequestForgerySink {
- HttpBuilderUriArgument() {
- exists(MethodAccess ma | ma.getMethod() instanceof HttpBuilderUri |
- this.asExpr() = ma.getArgument(0)
- )
- }
-}
-
-/**
- * An argument to a Spring Rest Template method call taken as a
- * sink for request forgery vulnerabilities.
- */
-private class SpringRestTemplateArgument extends RequestForgerySink {
- SpringRestTemplateArgument() {
- exists(MethodAccess ma |
- this.asExpr() = ma.getMethod().(SpringRestTemplateUrlMethods).getUrlArgument(ma)
- )
+private class RequestForgerySinkModel extends SinkModelCsv {
+ override predicate row(string row) {
+ row =
+ [
+ "java.net;URL;false;openConnection;;;Argument[-1];request-forgery-sink",
+ "java.net;URL;false;openStream;;;Argument[-1];request-forgery-sink",
+ "org.apache.http.client.methods;HttpRequestBase;true;setURI;;;Argument[0];request-forgery-sink",
+ "org.apache.http.message;BasicHttpRequest;true;setURI;;;Argument[0];request-forgery-sink",
+ "org.apache.http.client.methods;HttpRequestBase;true;.ctor;;;Argument[0];request-forgery-sink",
+ "org.apache.http.message;BasicHttpRequest;true;.ctor;;;Argument[0];request-forgery-sink",
+ "org.apache.http.client.methods;RequestBuilder;false;setURI;;;Argument[0];request-forgery-sink",
+ "org.apache.http.client.methods;RequestBuilder;false;get;;;Argument[0];request-forgery-sink",
+ "org.apache.http.client.methods;RequestBuilder;false;post;;;Argument[0];request-forgery-sink",
+ "org.apache.http.client.methods;RequestBuilder;false;put;;;Argument[0];request-forgery-sink",
+ "org.apache.http.client.methods;RequestBuilder;false;options;;;Argument[0];request-forgery-sink",
+ "org.apache.http.client.methods;RequestBuilder;false;head;;;Argument[0];request-forgery-sink",
+ "org.apache.http.client.methods;RequestBuilder;false;delete;;;Argument[0];request-forgery-sink",
+ "java.net.http;HttpRequest$Builder;false;uri;;;Argument[0];request-forgery-sink",
+ "org.springframework.web.client;RestTemplate;false;patchForObject;;;Argument[0];request-forgery-sink",
+ "org.springframework.web.client;RestTemplate;false;getForObject;;;Argument[0];request-forgery-sink",
+ "org.springframework.web.client;RestTemplate;false;getForEntity;;;Argument[0];request-forgery-sink",
+ "org.springframework.web.client;RestTemplate;false;execute;;;Argument[0];request-forgery-sink",
+ "org.springframework.web.client;RestTemplate;false;exchange;;;Argument[0];request-forgery-sink",
+ "org.springframework.web.client;RestTemplate;false;put;;;Argument[0];request-forgery-sink",
+ "org.springframework.web.client;RestTemplate;false;postForObject;;;Argument[0];request-forgery-sink",
+ "org.springframework.web.client;RestTemplate;false;postForLocation;;;Argument[0];request-forgery-sink",
+ "org.springframework.web.client;RestTemplate;false;postForEntity;;;Argument[0];request-forgery-sink",
+ "org.springframework.web.client;RestTemplate;false;doExecute;;;Argument[0];request-forgery-sink",
+ "javax.ws.rs.client;Client;false;target;;;Argument[0];request-forgery-sink",
+ "java.net.http;HttpRequest;false;newBuilder;;;Argument[0];request-forgery-sink" // todo: this might be stricter than before. Previously the package name was not checked.
+ ]
}
}
-/**
- * An argument to `javax.ws.rs.Client`s `target` method call taken as a
- * sink for request forgery vulnerabilities.
- */
-private class JaxRsClientTarget extends RequestForgerySink {
- JaxRsClientTarget() {
- exists(MethodAccess ma |
- ma.getMethod().getDeclaringType() instanceof JaxRsClient and
- ma.getMethod().hasName("target")
- |
- this.asExpr() = ma.getArgument(0)
- )
- }
-}
+/** A data flow sink for request forgery vulnerabilities. */
+abstract class RequestForgerySink extends DataFlow::Node { }
/**
* An argument to `org.springframework.http.RequestEntity`s constructor call
@@ -166,27 +90,3 @@ private class RequestEntityUriArg extends RequestForgerySink {
)
}
}
-
-/**
- * A class representing all Spring Rest Template methods
- * which take an URL as an argument.
- */
-private class SpringRestTemplateUrlMethods extends Method {
- SpringRestTemplateUrlMethods() {
- this.getDeclaringType() instanceof SpringRestTemplate and
- this.hasName([
- "doExecute", "postForEntity", "postForLocation", "postForObject", "put", "exchange",
- "execute", "getForEntity", "getForObject", "patchForObject"
- ])
- }
-
- /**
- * Gets the argument which corresponds to a URL argument
- * passed as a `java.net.URL` object or as a string or the like
- */
- Argument getUrlArgument(MethodAccess ma) {
- // doExecute(URI url, HttpMethod method, RequestCallback requestCallback,
- // ResponseExtractor responseExtractor)
- result = ma.getArgument(0)
- }
-}
diff --git a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
index 154ec98f2ad8..3cda09c14889 100644
--- a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
+++ b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
@@ -472,7 +472,7 @@ module CsvValidation {
not type.regexpMatch("[a-zA-Z0-9_\\$]+") and
msg = "Dubious type \"" + type + "\" in " + pred + " model."
or
- not name.regexpMatch("[a-zA-Z0-9_]*") and
+ not (name.regexpMatch("[a-zA-Z0-9_]*") or name = ".ctor") and
msg = "Dubious name \"" + name + "\" in " + pred + " model."
or
not signature.regexpMatch("|\\([a-zA-Z0-9_\\.\\$<>,\\[\\]]*\\)") and
@@ -562,23 +562,34 @@ private string paramsString(Callable c) {
}
private Element interpretElement0(
- string namespace, string type, boolean subtypes, string name, string signature
+ string namespace, string type, boolean subtypes, string name, string signature, string ext
) {
elementSpec(namespace, type, subtypes, name, signature, _) and
exists(RefType t | t = interpretType(namespace, type, subtypes) |
exists(Member m |
result = m and
m.getDeclaringType() = t and
- m.hasName(name)
+ (
+ m.hasName(name)
+ or
+ name = ".ctor" and m.hasName(t.getName())
+ )
|
signature = "" or
m.(Callable).getSignature() = any(string nameprefix) + signature or
paramsString(m) = signature
- )
+ ) and
+ ext = ""
or
result = t and
name = "" and
- signature = ""
+ signature = "" and
+ ext = "Annotated"
+ or
+ result = t.getAMember() and
+ name = "" and
+ signature = "" and
+ ext = ""
)
}
@@ -586,7 +597,7 @@ private Element interpretElement(
string namespace, string type, boolean subtypes, string name, string signature, string ext
) {
elementSpec(namespace, type, subtypes, name, signature, ext) and
- exists(Element e | e = interpretElement0(namespace, type, subtypes, name, signature) |
+ exists(Element e | e = interpretElement0(namespace, type, subtypes, name, signature, ext) |
ext = "" and result = e
or
ext = "Annotated" and result.(Annotatable).getAnAnnotation().getType() = e
diff --git a/java/ql/src/semmle/code/java/security/UnsafeDeserialization.qll b/java/ql/src/semmle/code/java/security/UnsafeDeserialization.qll
index ab809f07d6d1..d84ee67bca5c 100644
--- a/java/ql/src/semmle/code/java/security/UnsafeDeserialization.qll
+++ b/java/ql/src/semmle/code/java/security/UnsafeDeserialization.qll
@@ -3,6 +3,7 @@ import semmle.code.java.frameworks.XStream
import semmle.code.java.frameworks.SnakeYaml
import semmle.code.java.frameworks.FastJson
import semmle.code.java.frameworks.apache.Lang
+private import semmle.code.java.dataflow.ExternalFlow
class ObjectInputStreamReadObjectMethod extends Method {
ObjectInputStreamReadObjectMethod() {
@@ -26,11 +27,16 @@ class SafeXStream extends DataFlow2::Configuration {
src.asExpr()
}
- override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma |
- sink.asExpr() = ma.getQualifier() and
- ma.getMethod() instanceof XStreamReadObjectMethod
- )
+ override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "safe-xstream") }
+}
+
+private class SafeXStreamSinkModel extends SinkModelCsv {
+ override predicate row(string row) {
+ row =
+ [
+ "com.thoughtworks.xstream;XStream;false;unmarshal;;;Argument[-1];safe-xstream",
+ "com.thoughtworks.xstream;XStream;false;fromXML;;;Argument[-1];safe-xstream"
+ ]
}
}
@@ -42,11 +48,17 @@ class SafeKryo extends DataFlow2::Configuration {
src.asExpr()
}
- override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma |
- sink.asExpr() = ma.getQualifier() and
- ma.getMethod() instanceof KryoReadObjectMethod
- )
+ override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "safe-kryo") }
+}
+
+private class SafeKryoSinkModel extends SinkModelCsv {
+ override predicate row(string row) {
+ row =
+ [
+ "com.esotericsoftware.kryo;Kryo;false;readObjectOrNull;;;Argument[-1];safe-kryo",
+ "com.esotericsoftware.kryo;Kryo;false;readObject;;;Argument[-1];safe-kryo",
+ "com.esotericsoftware.kryo;Kryo;false;readClassAndObject;;;Argument[-1];safe-kryo"
+ ]
}
}
From 351f35d9bce68e79b921511ba91f9f17172de7c5 Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Fri, 9 Apr 2021 13:13:49 +0200
Subject: [PATCH 148/433] Revert "Java: Convert other sinks"
This reverts commit 87d42b02c0c49ff717c941d6ea5290b6ca3c40df.
---
.../CWE/CWE-209/StackTraceExposure.ql | 11 +-
.../Security/CWE/CWE-036/OpenStream.ql | 15 +-
.../Security/CWE/CWE-074/XsltInjectionLib.qll | 41 ++---
.../Security/CWE/CWE-094/JexlInjectionLib.qll | 139 ++++++++-------
.../Security/CWE/CWE-094/MvelInjectionLib.qll | 56 +++---
.../CWE/CWE-326/InsufficientKeySize.ql | 23 ++-
.../Security/CWE/CWE-327/SslLib.qll | 20 +--
.../Security/CWE/CWE-346/UnvalidatedCors.ql | 29 +--
.../Security/CWE/CWE-522/InsecureLdapAuth.ql | 26 ++-
.../Security/CWE/CWE-643/XPathInjection.ql | 23 ++-
.../Security/CWE/CWE-652/XQueryInjection.ql | 7 +-
.../CWE/CWE-652/XQueryInjectionLib.qll | 12 --
.../Security/CWE/CWE-917/OgnlInjectionLib.qll | 26 +--
.../Security/CWE/CWE-918/RequestForgery.ql | 5 +-
.../Security/CWE/CWE-918/RequestForgery.qll | 168 ++++++++++++++----
.../code/java/dataflow/ExternalFlow.qll | 23 +--
.../java/security/UnsafeDeserialization.qll | 32 ++--
17 files changed, 354 insertions(+), 302 deletions(-)
diff --git a/java/ql/src/Security/CWE/CWE-209/StackTraceExposure.ql b/java/ql/src/Security/CWE/CWE-209/StackTraceExposure.ql
index 1f49d5cb06ac..c9c1e2917c0e 100644
--- a/java/ql/src/Security/CWE/CWE-209/StackTraceExposure.ql
+++ b/java/ql/src/Security/CWE/CWE-209/StackTraceExposure.ql
@@ -16,7 +16,6 @@ import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.security.XSS
-private import semmle.code.java.dataflow.ExternalFlow
/**
* One of the `printStackTrace()` overloads on `Throwable`.
@@ -38,12 +37,10 @@ class ServletWriterSourceToPrintStackTraceMethodFlowConfig extends TaintTracking
override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof ServletWriterSource }
- override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "print-stack-trace") }
-}
-
-private class PrintStackTraceSinkModel extends SinkModelCsv {
- override predicate row(string row) {
- row = ["java.lang;Throwable;true;printStackTrace;;;Argument;print-stack-trace"]
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess ma |
+ sink.asExpr() = ma.getAnArgument() and ma.getMethod() instanceof PrintStackTraceMethod
+ )
}
}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-036/OpenStream.ql b/java/ql/src/experimental/Security/CWE/CWE-036/OpenStream.ql
index 6f75b3fc8ae5..871d6bb4737c 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-036/OpenStream.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-036/OpenStream.ql
@@ -15,7 +15,6 @@ import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.ExternalFlow
import DataFlow::PathGraph
-private import semmle.code.java.dataflow.ExternalFlow
class URLConstructor extends ClassInstanceExpr {
URLConstructor() { this.getConstructor().getDeclaringType() instanceof TypeUrl }
@@ -40,7 +39,13 @@ class RemoteURLToOpenStreamFlowConfig extends TaintTracking::Configuration {
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
- override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "url-open-stream") }
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess m |
+ sink.asExpr() = m.getQualifier() and m.getMethod() instanceof URLOpenStreamMethod
+ )
+ or
+ sinkNode(sink, "url-open-stream")
+ }
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
exists(URLConstructor u |
@@ -50,12 +55,6 @@ class RemoteURLToOpenStreamFlowConfig extends TaintTracking::Configuration {
}
}
-private class URLOpenStreamSinkModel extends SinkModelCsv {
- override predicate row(string row) {
- row = ["java.net;URL;false;openStream;;;Argument[-1];url-open-stream"]
- }
-}
-
from DataFlow::PathNode source, DataFlow::PathNode sink, MethodAccess call
where
sink.getNode().asExpr() = call.getQualifier() and
diff --git a/java/ql/src/experimental/Security/CWE/CWE-074/XsltInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-074/XsltInjectionLib.qll
index bc0c8352a20f..4ba0eb6d0b11 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-074/XsltInjectionLib.qll
+++ b/java/ql/src/experimental/Security/CWE/CWE-074/XsltInjectionLib.qll
@@ -2,7 +2,6 @@ import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.XmlParsers
import DataFlow
-private import semmle.code.java.dataflow.ExternalFlow
/**
* A taint-tracking configuration for unvalidated user input that is used in XSLT transformation.
@@ -104,20 +103,15 @@ class TypeXsltPackage extends Class {
/** A data flow sink for unvalidated user input that is used in XSLT transformation. */
class XsltInjectionSink extends DataFlow::ExprNode {
- XsltInjectionSink() { sinkNode(this, "xslt") }
-}
-
-private class XsltInjectionSinkModel extends SinkModelCsv {
- override predicate row(string row) {
- row =
- [
- "net.sf.saxon.s9api;XsltTransformer;false;transform;;;Argument[-1];xslt",
- "net.sf.saxon.s9api;Xslt30Transformer;false;transform;;;Argument[-1];xslt",
- "net.sf.saxon.s9api;Xslt30Transformer;false;applyTemplates;;;Argument[-1];xslt",
- "net.sf.saxon.s9api;Xslt30Transformer;false;callFunction;;;Argument[-1];xslt",
- "net.sf.saxon.s9api;Xslt30Transformer;false;callTemplate;;;Argument[-1];xslt",
- "javax.xml.transform;Transformer;false;transform;;;Argument[-1];xslt"
- ]
+ XsltInjectionSink() {
+ exists(MethodAccess ma, Method m | m = ma.getMethod() and ma.getQualifier() = this.getExpr() |
+ ma instanceof TransformerTransform or
+ m instanceof XsltTransformerTransformMethod or
+ m instanceof Xslt30TransformerTransformMethod or
+ m instanceof Xslt30TransformerApplyTemplatesMethod or
+ m instanceof Xslt30TransformerCallFunctionMethod or
+ m instanceof Xslt30TransformerCallTemplateMethod
+ )
}
}
@@ -192,21 +186,16 @@ private class TransformerFactoryWithSecureProcessingFeatureFlowConfig extends Da
)
}
- override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "xslt-transformer") }
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess ma |
+ sink.asExpr() = ma.getQualifier() and
+ ma.getMethod().getDeclaringType() instanceof TransformerFactory
+ )
+ }
override int fieldFlowBranchLimit() { result = 0 }
}
-private class TransformerFactorySinkModel extends SinkModelCsv {
- override predicate row(string row) {
- row =
- [
- "javax.xml.transform;TransformerFactory;false;;;;Argument[-1];xslt-transformer",
- "javax.xml.transform.sax;SAXTransformerFactory;false;;;;Argument[-1];xslt-transformer"
- ]
- }
-}
-
/** A `ParserConfig` specific to `TransformerFactory`. */
private class TransformerFactoryFeatureConfig extends ParserConfig {
TransformerFactoryFeatureConfig() {
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll
index 8fcde750e54c..561d7e46ae90 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll
@@ -1,7 +1,6 @@
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking
-private import semmle.code.java.dataflow.ExternalFlow
/**
* A taint-tracking configuration for unsafe user input
@@ -22,7 +21,7 @@ class JexlInjectionConfig extends TaintTracking::Configuration {
}
/**
- * A sink for Expression Language injection vulnerabilities via Jexl,
+ * A sink for Expresssion Language injection vulnerabilities via Jexl,
* i.e. method calls that run evaluation of a JEXL expression.
*
* Creating a `Callable` from a tainted JEXL expression or script is considered as a sink
@@ -31,41 +30,18 @@ class JexlInjectionConfig extends TaintTracking::Configuration {
* maybe stored in an object field and then reached by a different flow.
*/
private class JexlEvaluationSink extends DataFlow::ExprNode {
- JexlEvaluationSink() { sinkNode(this, "jexl") }
-}
-
-private class JexlEvaluationSinkModel extends SinkModelCsv {
- override predicate row(string row) {
- row =
- [
- // Direct JEXL evaluation
- "org.apache.commons.jexl2;Expression;false;evaluate;;;Argument[-1];jexl",
- "org.apache.commons.jexl3;JexlExpression;false;evaluate;;;Argument[-1];jexl",
- "org.apache.commons.jexl2;Script;false;execute;;;Argument[-1];jexl",
- "org.apache.commons.jexl3;JexlScript;false;execute;;;Argument[-1];jexl",
- "org.apache.commons.jexl2;JxltEngine$Expression;false;evaluate;;;Argument[-1];jexl",
- "org.apache.commons.jexl3;JxltEngine$Expression;false;evaluate;;;Argument[-1];jexl",
- "org.apache.commons.jexl2;JxltEngine$Expression;false;prepare;;;Argument[-1];jexl",
- "org.apache.commons.jexl3;JxltEngine$Expression;false;prepare;;;Argument[-1];jexl",
- "org.apache.commons.jexl2;JxltEngine$Template;false;evaluate;;;Argument[-1];jexl",
- "org.apache.commons.jexl3;JxltEngine$Template;false;evaluate;;;Argument[-1];jexl",
- "org.apache.commons.jexl2;UnifiedJEXL$Expression;false;evaluate;;;Argument[-1];jexl",
- "org.apache.commons.jexl3;UnifiedJEXL$Expression;false;evaluate;;;Argument[-1];jexl",
- "org.apache.commons.jexl2;UnifiedJEXL$Expression;false;prepare;;;Argument[-1];jexl",
- "org.apache.commons.jexl3;UnifiedJEXL$Expression;false;prepare;;;Argument[-1];jexl",
- "org.apache.commons.jexl2;UnifiedJEXL$Template;false;evaluate;;;Argument[-1];jexl",
- "org.apache.commons.jexl3;UnifiedJEXL$Template;false;evaluate;;;Argument[-1];jexl",
- // JEXL callable
- "org.apache.commons.jexl2;Expression;false;callable;;;Argument[-1];jexl",
- "org.apache.commons.jexl3;JexlExpression;false;callable;;;Argument[-1];jexl",
- "org.apache.commons.jexl2;Script;false;callable;;;Argument[-1];jexl",
- "org.apache.commons.jexl3;JexlScript;false;callable;;;Argument[-1];jexl",
- // Methods in the `JexlEngine` class that gets or sets a property with a JEXL expression.
- "org.apache.commons.jexl2;JexlEngine;false;getProperty;;;Argument[1..2];jexl",
- "org.apache.commons.jexl3;JexlEngine;false;getProperty;;;Argument[1..2];jexl",
- "org.apache.commons.jexl2;JexlEngine;false;setProperty;;;Argument[1];jexl",
- "org.apache.commons.jexl3;JexlEngine;false;setProperty;;;Argument[1];jexl"
- ]
+ JexlEvaluationSink() {
+ exists(MethodAccess ma, Method m, Expr taintFrom |
+ ma.getMethod() = m and taintFrom = this.asExpr()
+ |
+ m instanceof DirectJexlEvaluationMethod and ma.getQualifier() = taintFrom
+ or
+ m instanceof CreateJexlCallableMethod and ma.getQualifier() = taintFrom
+ or
+ m instanceof JexlEngineGetSetPropertyMethod and
+ taintFrom.getType() instanceof TypeString and
+ ma.getAnArgument() = taintFrom
+ )
}
}
@@ -122,36 +98,22 @@ private class SandboxedJexlFlowConfig extends DataFlow2::Configuration {
override predicate isSource(DataFlow::Node node) { node instanceof SandboxedJexlSource }
- override predicate isSink(DataFlow::Node node) { sinkNode(node, "sandboxed-jexl") }
+ override predicate isSink(DataFlow::Node node) {
+ exists(MethodAccess ma, Method m | ma.getMethod() = m |
+ (
+ m instanceof CreateJexlScriptMethod or
+ m instanceof CreateJexlExpressionMethod or
+ m instanceof CreateJexlTemplateMethod
+ ) and
+ ma.getQualifier() = node.asExpr()
+ )
+ }
override predicate isAdditionalFlowStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
createsJexlEngine(fromNode, toNode)
}
}
-private class SandboxedJexlEvaluationSinkModel extends SinkModelCsv {
- override predicate row(string row) {
- row =
- [
- // CreateJexlScriptMethod
- "org.apache.commons.jexl2;JexlEngine;false;createScript;;;Argument[-1];sandboxed-jexl",
- "org.apache.commons.jexl3;JexlEngine;false;createScript;;;Argument[-1];sandboxed-jexl",
- // CreateJexlExpressionMethod
- "org.apache.commons.jexl2;UnifiedJEXL;false;parse;;;Argument[-1];sandboxed-jexl",
- "org.apache.commons.jexl3;UnifiedJEXL;false;parse;;;Argument[-1];sandboxed-jexl",
- "org.apache.commons.jexl2;JxltEngine;false;createExpression;;;Argument[-1];sandboxed-jexl",
- "org.apache.commons.jexl3;JxltEngine;false;createExpression;;;Argument[-1];sandboxed-jexl",
- "org.apache.commons.jexl2;JexlEngine;false;createExpression;;;Argument[-1];sandboxed-jexl",
- "org.apache.commons.jexl3;JexlEngine;false;createExpression;;;Argument[-1];sandboxed-jexl",
- // CreateJexlTemplateMethod
- "org.apache.commons.jexl2;JxltEngine;false;createTemplate;;;Argument[-1];sandboxed-jexl",
- "org.apache.commons.jexl3;JxltEngine;false;createTemplate;;;Argument[-1];sandboxed-jexl",
- "org.apache.commons.jexl2;UnifiedJEXL;false;createTemplate;;;Argument[-1];sandboxed-jexl",
- "org.apache.commons.jexl3;UnifiedJEXL;false;createTemplate;;;Argument[-1];sandboxed-jexl"
- ]
- }
-}
-
/**
* Defines a data flow source for JEXL engines configured with a sandbox.
*/
@@ -202,6 +164,35 @@ private predicate returnsDataFromBean(DataFlow::Node fromNode, DataFlow::Node to
)
}
+/**
+ * A methods in the `JexlEngine` class that gets or sets a property with a JEXL expression.
+ */
+private class JexlEngineGetSetPropertyMethod extends Method {
+ JexlEngineGetSetPropertyMethod() {
+ getDeclaringType() instanceof JexlEngine and
+ hasName(["getProperty", "setProperty"])
+ }
+}
+
+/**
+ * A method that triggers direct evaluation of JEXL expressions.
+ */
+private class DirectJexlEvaluationMethod extends Method {
+ DirectJexlEvaluationMethod() {
+ getDeclaringType() instanceof JexlExpression and hasName("evaluate")
+ or
+ getDeclaringType() instanceof JexlScript and hasName("execute")
+ or
+ getDeclaringType() instanceof JxltEngineExpression and hasName(["evaluate", "prepare"])
+ or
+ getDeclaringType() instanceof JxltEngineTemplate and hasName("evaluate")
+ or
+ getDeclaringType() instanceof UnifiedJexlExpression and hasName(["evaluate", "prepare"])
+ or
+ getDeclaringType() instanceof UnifiedJexlTemplate and hasName("evaluate")
+ }
+}
+
/**
* A method that creates a JEXL script.
*/
@@ -209,6 +200,16 @@ private class CreateJexlScriptMethod extends Method {
CreateJexlScriptMethod() { getDeclaringType() instanceof JexlEngine and hasName("createScript") }
}
+/**
+ * A method that creates a `Callable` for a JEXL expression or script.
+ */
+private class CreateJexlCallableMethod extends Method {
+ CreateJexlCallableMethod() {
+ (getDeclaringType() instanceof JexlExpression or getDeclaringType() instanceof JexlScript) and
+ hasName("callable")
+ }
+}
+
/**
* A method that creates a JEXL template.
*/
@@ -266,6 +267,22 @@ private class JexlUberspect extends Interface {
}
}
+private class JxltEngineExpression extends NestedType {
+ JxltEngineExpression() { getEnclosingType() instanceof JxltEngine and hasName("Expression") }
+}
+
+private class JxltEngineTemplate extends NestedType {
+ JxltEngineTemplate() { getEnclosingType() instanceof JxltEngine and hasName("Template") }
+}
+
+private class UnifiedJexlExpression extends NestedType {
+ UnifiedJexlExpression() { getEnclosingType() instanceof UnifiedJexl and hasName("Expression") }
+}
+
+private class UnifiedJexlTemplate extends NestedType {
+ UnifiedJexlTemplate() { getEnclosingType() instanceof UnifiedJexl and hasName("Template") }
+}
+
private class Reader extends RefType {
Reader() { hasQualifiedName("java.io", "Reader") }
}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/MvelInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/MvelInjectionLib.qll
index dec268a2a4bc..a6cf891330f0 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-094/MvelInjectionLib.qll
+++ b/java/ql/src/experimental/Security/CWE/CWE-094/MvelInjectionLib.qll
@@ -1,7 +1,6 @@
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking
-private import semmle.code.java.dataflow.ExternalFlow
/**
* A taint-tracking configuration for unsafe user input
@@ -31,31 +30,36 @@ class MvelInjectionConfig extends TaintTracking::Configuration {
* i.e. methods that run evaluation of a MVEL expression.
*/
class MvelEvaluationSink extends DataFlow::ExprNode {
- MvelEvaluationSink() { sinkNode(this, "mvel") }
-}
-
-private class MvelEvaluationSinkModel extends SinkModelCsv {
- override predicate row(string row) {
- row =
- [
- "org.mvel2.jsr223;MvelScriptEngine;false;evaluate;;;Argument[0];mvel",
- "org.mvel2.jsr223;MvelScriptEngine;false;eval;;;Argument[0];mvel",
- "org.mvel2.compiler;ExecutableStatement;false;getValue;;;Argument[-1];mvel",
- "org.mvel2.compiler;CompiledExpression;false;getDirectValue;;;Argument[-1];mvel",
- "org.mvel2.compiler;CompiledAccExpression;false;getValue;;;Argument[-1];mvel",
- "org.mvel2.compiler;Accessor;false;getValue;;;Argument[-1];mvel",
- "javax.script;CompiledScript;false;eval;;;Argument[-1];mvel",
- "org.mvel2.jsr223;MvelCompiledScript;false;eval;;;Argument[-1];mvel",
- "org.mvel2;MVEL;false;eval;;;Argument[0];mvel",
- "org.mvel2;MVEL;false;executeExpression;;;Argument[0];mvel",
- "org.mvel2;MVEL;false;evalToBoolean;;;Argument[0];mvel",
- "org.mvel2;MVEL;false;evalToString;;;Argument[0];mvel",
- "org.mvel2;MVEL;false;executeAllExpression;;;Argument[0];mvel",
- "org.mvel2;MVEL;false;executeSetExpression;;;Argument[0];mvel",
- "org.mvel2.templates;TemplateRuntime;false;eval;;;Argument[0];mvel",
- "org.mvel2.templates;TemplateRuntime;false;execute;;;Argument[0];mvel",
- "org.mvel2;MVELRuntime;false;execute;;;Argument[1];mvel"
- ]
+ MvelEvaluationSink() {
+ exists(StaticMethodAccess ma, Method m | m = ma.getMethod() |
+ (
+ m instanceof MvelEvalMethod or
+ m instanceof TemplateRuntimeEvaluationMethod
+ ) and
+ ma.getArgument(0) = asExpr()
+ )
+ or
+ exists(MethodAccess ma, Method m | m = ma.getMethod() |
+ m instanceof MvelScriptEngineEvaluationMethod and
+ ma.getArgument(0) = asExpr()
+ )
+ or
+ exists(MethodAccess ma, Method m | m = ma.getMethod() |
+ (
+ m instanceof ExecutableStatementEvaluationMethod or
+ m instanceof CompiledExpressionEvaluationMethod or
+ m instanceof CompiledAccExpressionEvaluationMethod or
+ m instanceof AccessorEvaluationMethod or
+ m instanceof CompiledScriptEvaluationMethod or
+ m instanceof MvelCompiledScriptEvaluationMethod
+ ) and
+ ma.getQualifier() = asExpr()
+ )
+ or
+ exists(StaticMethodAccess ma, Method m | m = ma.getMethod() |
+ m instanceof MvelRuntimeEvaluationMethod and
+ ma.getArgument(1) = asExpr()
+ )
}
}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.ql b/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.ql
index ca86f42c561a..155d05abfae3 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.ql
@@ -12,7 +12,6 @@
import java
import semmle.code.java.security.Encryption
import semmle.code.java.dataflow.TaintTracking
-private import semmle.code.java.dataflow.ExternalFlow
/** The Java class `java.security.spec.ECGenParameterSpec`. */
class ECGenParameterSpec extends RefType {
@@ -56,12 +55,11 @@ class KeyGeneratorInitConfiguration extends TaintTracking::Configuration {
exists(JavaxCryptoKeyGenerator jcg | jcg = source.asExpr())
}
- override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "keygen") }
-}
-
-private class KeyGeneratorInitSinkModel extends SinkModelCsv {
- override predicate row(string row) {
- row = ["javax.crypto;KeyGenerator;false;init;;;Argument[-1];keygen"]
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess ma |
+ ma.getMethod() instanceof KeyGeneratorInitMethod and
+ sink.asExpr() = ma.getQualifier()
+ )
}
}
@@ -73,12 +71,11 @@ class KeyPairGeneratorInitConfiguration extends TaintTracking::Configuration {
exists(JavaSecurityKeyPairGenerator jkg | jkg = source.asExpr())
}
- override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "keypairgen") }
-}
-
-private class KeyPairGeneratorInitSinkModel extends SinkModelCsv {
- override predicate row(string row) {
- row = ["java.security;KeyPairGenerator;false;initialize;;;Argument[-1];keypairgen"]
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess ma |
+ ma.getMethod() instanceof KeyPairGeneratorInitMethod and
+ sink.asExpr() = ma.getQualifier()
+ )
}
}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-327/SslLib.qll b/java/ql/src/experimental/Security/CWE/CWE-327/SslLib.qll
index 3845953f0aab..bfa2530b07e7 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-327/SslLib.qll
+++ b/java/ql/src/experimental/Security/CWE/CWE-327/SslLib.qll
@@ -3,7 +3,6 @@ import semmle.code.java.security.Encryption
import semmle.code.java.dataflow.TaintTracking
import DataFlow
import PathGraph
-private import semmle.code.java.dataflow.ExternalFlow
/**
* A taint-tracking configuration for unsafe SSL and TLS versions.
@@ -13,20 +12,11 @@ class UnsafeTlsVersionConfig extends TaintTracking::Configuration {
override predicate isSource(DataFlow::Node source) { source.asExpr() instanceof UnsafeTlsVersion }
- override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "ssl") }
-}
-
-private class UnsafeTlsVersionSinkModel extends SinkModelCsv {
- override predicate row(string row) {
- row =
- [
- "javax.net.ssl;SSLContext;false;getInstance;;;Argument[0];ssl",
- "javax.net.ssl;SSLParameters;false;SSLParameters;;;Argument[1];ssl",
- "javax.net.ssl;SSLParameters;false;setProtocols;;;Argument[0];ssl",
- "javax.net.ssl;SSLSocket;false;setEnabledProtocols;;;Argument[0];ssl",
- "javax.net.ssl;SSLServerSocket;false;setEnabledProtocols;;;Argument[0];ssl",
- "javax.net.ssl;SSLEngine;false;setEnabledProtocols;;;Argument[0];ssl"
- ]
+ override predicate isSink(DataFlow::Node sink) {
+ sink instanceof SslContextGetInstanceSink or
+ sink instanceof CreateSslParametersSink or
+ sink instanceof SslParametersSetProtocolsSink or
+ sink instanceof SetEnabledProtocolsSink
}
}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-346/UnvalidatedCors.ql b/java/ql/src/experimental/Security/CWE/CWE-346/UnvalidatedCors.ql
index 2fe6569318fc..c5a6c36d6a61 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-346/UnvalidatedCors.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-346/UnvalidatedCors.ql
@@ -15,7 +15,6 @@ import semmle.code.java.frameworks.Servlets
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.TaintTracking2
import DataFlow::PathGraph
-private import semmle.code.java.dataflow.ExternalFlow
/**
* Holds if `header` sets `Access-Control-Allow-Credentials` to `true`. This ensures fair chances of exploitability.
@@ -30,29 +29,33 @@ private predicate setsAllowCredentials(MethodAccess header) {
header.getArgument(1).(CompileTimeConstantExpr).getStringValue().toLowerCase() = "true"
}
+private class CorsProbableCheckAccess extends MethodAccess {
+ CorsProbableCheckAccess() {
+ getMethod().hasName("contains") and
+ getMethod().getDeclaringType().getASourceSupertype*() instanceof CollectionType
+ or
+ getMethod().hasName("containsKey") and
+ getMethod().getDeclaringType().getASourceSupertype*() instanceof MapType
+ or
+ getMethod().hasName("equals") and
+ getQualifier().getType() instanceof TypeString
+ }
+}
+
private Expr getAccessControlAllowOriginHeaderName() {
result.(CompileTimeConstantExpr).getStringValue().toLowerCase() = "access-control-allow-origin"
}
/**
- * This taintflow2 configuration checks if there is a flow from source node towards probably CORS checking methods.
+ * This taintflow2 configuration checks if there is a flow from source node towards CorsProbableCheckAccess methods.
*/
class CorsSourceReachesCheckConfig extends TaintTracking2::Configuration {
CorsSourceReachesCheckConfig() { this = "CorsOriginConfig" }
override predicate isSource(DataFlow::Node source) { any(CorsOriginConfig c).hasFlow(source, _) }
- override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "cors") }
-}
-
-private class CorsProbableCheckAccessSinkModel extends SinkModelCsv {
- override predicate row(string row) {
- row =
- [
- "java.util;Collection;true;contains;;;Argument;cors",
- "java.util;Map;true;containsKey;;;Argument;cors",
- "java.lang;String;true;equals;;;Argument;cors"
- ]
+ override predicate isSink(DataFlow::Node sink) {
+ sink.asExpr() = any(CorsProbableCheckAccess check).getAnArgument()
}
}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
index 3c445934ad33..4ce2b8b7134c 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
@@ -16,7 +16,6 @@ import semmle.code.java.frameworks.Jndi
import semmle.code.java.frameworks.Networking
import semmle.code.java.dataflow.TaintTracking
import DataFlow::PathGraph
-private import semmle.code.java.dataflow.ExternalFlow
/**
* Insecure (non-SSL, non-private) LDAP URL string literal.
@@ -146,7 +145,12 @@ class InsecureUrlFlowConfig extends TaintTracking::Configuration {
override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof InsecureLdapUrl }
/** Sink of directory context creation. */
- override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "ldap") }
+ override predicate isSink(DataFlow::Node sink) {
+ exists(ConstructorCall cc |
+ cc.getConstructedType().getASupertype*() instanceof TypeDirContext and
+ sink.asExpr() = cc.getArgument(0)
+ )
+ }
/** Method call of `env.put()`. */
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
@@ -172,12 +176,11 @@ class BasicAuthFlowConfig extends DataFlow::Configuration {
}
/** Sink of directory context creation. */
- override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "ldap") }
-}
-
-private class LdapSinkModel extends SinkModelCsv {
- override predicate row(string row) {
- row = ["javax.naming.directory;DirContext;true;.ctor;;;Argument[0];ldap"]
+ override predicate isSink(DataFlow::Node sink) {
+ exists(ConstructorCall cc |
+ cc.getConstructedType().getASupertype*() instanceof TypeDirContext and
+ sink.asExpr() = cc.getArgument(0)
+ )
}
}
@@ -195,7 +198,12 @@ class SSLFlowConfig extends DataFlow::Configuration {
}
/** Sink of directory context creation. */
- override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "ldap") }
+ override predicate isSink(DataFlow::Node sink) {
+ exists(ConstructorCall cc |
+ cc.getConstructedType().getASupertype*() instanceof TypeDirContext and
+ sink.asExpr() = cc.getArgument(0)
+ )
+ }
}
from DataFlow::PathNode source, DataFlow::PathNode sink, InsecureUrlFlowConfig config
diff --git a/java/ql/src/experimental/Security/CWE/CWE-643/XPathInjection.ql b/java/ql/src/experimental/Security/CWE/CWE-643/XPathInjection.ql
index 598f42b0c703..e5a29df46d59 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-643/XPathInjection.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-643/XPathInjection.ql
@@ -15,7 +15,6 @@ import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.security.XmlParsers
import DataFlow::PathGraph
-private import semmle.code.java.dataflow.ExternalFlow
class XPathInjectionConfiguration extends TaintTracking::Configuration {
XPathInjectionConfiguration() { this = "XPathInjection" }
@@ -26,18 +25,16 @@ class XPathInjectionConfiguration extends TaintTracking::Configuration {
}
class XPathInjectionSink extends DataFlow::ExprNode {
- XPathInjectionSink() { sinkNode(this, "xpath") }
-}
-
-private class XPathInjectionSinkModel extends SinkModelCsv {
- override predicate row(string row) {
- row =
- [
- "javax.xml.xpath;XPath;false;compile;;;Argument[0];xpath",
- "javax.xml.xpath;XPath;false;evaluate;;;Argument[0];xpath",
- "org.dom4j;Node;false;selectNodes;;;Argument[0];xpath",
- "org.dom4j;Node;false;selectSingleNode;;;Argument[0];xpath"
- ]
+ XPathInjectionSink() {
+ exists(Method m, MethodAccess ma | ma.getMethod() = m |
+ m.getDeclaringType().hasQualifiedName("javax.xml.xpath", "XPath") and
+ (m.hasName("evaluate") or m.hasName("compile")) and
+ ma.getArgument(0) = this.getExpr()
+ or
+ m.getDeclaringType().hasQualifiedName("org.dom4j", "Node") and
+ (m.hasName("selectNodes") or m.hasName("selectSingleNode")) and
+ ma.getArgument(0) = this.getExpr()
+ )
}
}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-652/XQueryInjection.ql b/java/ql/src/experimental/Security/CWE/CWE-652/XQueryInjection.ql
index 3b8e43c5d7d2..0bb85272f085 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-652/XQueryInjection.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-652/XQueryInjection.ql
@@ -14,7 +14,6 @@ import java
import semmle.code.java.dataflow.FlowSources
import XQueryInjectionLib
import DataFlow::PathGraph
-private import semmle.code.java.dataflow.ExternalFlow
/**
* A taint-tracking configuration tracing flow from remote sources, through an XQuery parser, to its eventual execution.
@@ -24,7 +23,11 @@ class XQueryInjectionConfig extends TaintTracking::Configuration {
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
- override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "xquery") }
+ override predicate isSink(DataFlow::Node sink) {
+ sink.asExpr() = any(XQueryPreparedExecuteCall xpec).getPreparedExpression() or
+ sink.asExpr() = any(XQueryExecuteCall xec).getExecuteQueryArgument() or
+ sink.asExpr() = any(XQueryExecuteCommandCall xecc).getExecuteCommandArgument()
+ }
/**
* Holds if taint from the input `pred` to a `prepareExpression` call flows to the returned prepared expression `succ`.
diff --git a/java/ql/src/experimental/Security/CWE/CWE-652/XQueryInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-652/XQueryInjectionLib.qll
index 13452e4e55dc..2a4019f2c9a9 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-652/XQueryInjectionLib.qll
+++ b/java/ql/src/experimental/Security/CWE/CWE-652/XQueryInjectionLib.qll
@@ -1,5 +1,4 @@
import java
-private import semmle.code.java.dataflow.ExternalFlow
/** A call to `XQConnection.prepareExpression`. */
class XQueryParserCall extends MethodAccess {
@@ -67,14 +66,3 @@ class XQueryExecuteCommandCall extends MethodAccess {
/** Return this execute command argument. */
Expr getExecuteCommandArgument() { result = this.getArgument(0) }
}
-
-private class XQuerySinkModel extends SinkModelCsv {
- override predicate row(string row) {
- row =
- [
- "javax.xml.xquery;XQPreparedExpression;true;executeQuery;;;Argument[-1];xquery",
- "javax.xml.xquery;XQExpression;true;executeQuery;;;Argument[0];xquery",
- "javax.xml.xquery;XQExpression;true;executeCommand;;;Argument[0];xquery"
- ]
- }
-}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-917/OgnlInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-917/OgnlInjectionLib.qll
index 002e06aaafdc..569e18a29c38 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-917/OgnlInjectionLib.qll
+++ b/java/ql/src/experimental/Security/CWE/CWE-917/OgnlInjectionLib.qll
@@ -2,7 +2,6 @@ import java
import semmle.code.java.dataflow.FlowSources
import DataFlow
import DataFlow::PathGraph
-private import semmle.code.java.dataflow.ExternalFlow
/**
* A taint-tracking configuration for unvalidated user input that is used in OGNL EL evaluation.
@@ -83,25 +82,12 @@ predicate ognlInjectionSinkMethod(Method m, int index) {
/** A data flow sink for unvalidated user input that is used in OGNL EL evaluation. */
class OgnlInjectionSink extends DataFlow::ExprNode {
- OgnlInjectionSink() { sinkNode(this, "ognl") }
-}
-
-private class OgnlInjectionSinkModel extends SinkModelCsv {
- override predicate row(string row) {
- row =
- [
- "org.apache.commons.ognl;Ognl;false;setValue;;;Argument[-1..0];ognl",
- "org.apache.commons.ognl;Ognl;false;getValue;;;Argument[-1..0];ognl",
- "ognl;Ognl;false;setValue;;;Argument[-1..0];ognl",
- "ognl;Ognl;false;getValue;;;Argument[-1..0];ognl",
- "org.apache.commons.ognl;Node;true;setValue;;;Argument[-1..0];ognl",
- "org.apache.commons.ognl;Node;true;getValue;;;Argument[-1..0];ognl",
- "ognl;Node;true;setValue;;;Argument[-1..0];ognl",
- "ognl;Node;true;getValue;;;Argument[-1..0];ognl",
- "com.opensymphony.xwork2.ognl;OgnlUtil;false;setValue;;;Argument[-1..0];ognl",
- "com.opensymphony.xwork2.ognl;OgnlUtil;false;getValue;;;Argument[-1..0];ognl",
- "com.opensymphony.xwork2.ognl;OgnlUtil;false;callMethod;;;Argument[-1..0];ognl"
- ]
+ OgnlInjectionSink() {
+ exists(MethodAccess ma, Method m, int index |
+ ma.getMethod() = m and
+ (ma.getArgument(index) = this.getExpr() or ma.getQualifier() = this.getExpr()) and
+ ognlInjectionSinkMethod(m, index)
+ )
}
}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-918/RequestForgery.ql b/java/ql/src/experimental/Security/CWE/CWE-918/RequestForgery.ql
index 6b0333da4709..c3bf787881fa 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-918/RequestForgery.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-918/RequestForgery.ql
@@ -14,16 +14,13 @@ import java
import semmle.code.java.dataflow.FlowSources
import RequestForgery
import DataFlow::PathGraph
-private import semmle.code.java.dataflow.ExternalFlow
class RequestForgeryConfiguration extends TaintTracking::Configuration {
RequestForgeryConfiguration() { this = "Server Side Request Forgery" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
- override predicate isSink(DataFlow::Node sink) {
- sink instanceof RequestForgerySink or sinkNode(sink, "request-forgery-sink")
- }
+ override predicate isSink(DataFlow::Node sink) { sink instanceof RequestForgerySink }
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
requestForgeryStep(pred, succ)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-918/RequestForgery.qll b/java/ql/src/experimental/Security/CWE/CWE-918/RequestForgery.qll
index fa784baa7f23..3fc52ddca766 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-918/RequestForgery.qll
+++ b/java/ql/src/experimental/Security/CWE/CWE-918/RequestForgery.qll
@@ -5,7 +5,6 @@ import semmle.code.java.frameworks.spring.Spring
import semmle.code.java.frameworks.JaxWS
import semmle.code.java.frameworks.javase.Http
import semmle.code.java.dataflow.DataFlow
-private import semmle.code.java.dataflow.ExternalFlow
predicate requestForgeryStep(DataFlow::Node pred, DataFlow::Node succ) {
// propagate to a URI when its host is assigned to
@@ -39,42 +38,119 @@ predicate requestForgeryStep(DataFlow::Node pred, DataFlow::Node succ) {
)
}
-private class RequestForgerySinkModel extends SinkModelCsv {
- override predicate row(string row) {
- row =
- [
- "java.net;URL;false;openConnection;;;Argument[-1];request-forgery-sink",
- "java.net;URL;false;openStream;;;Argument[-1];request-forgery-sink",
- "org.apache.http.client.methods;HttpRequestBase;true;setURI;;;Argument[0];request-forgery-sink",
- "org.apache.http.message;BasicHttpRequest;true;setURI;;;Argument[0];request-forgery-sink",
- "org.apache.http.client.methods;HttpRequestBase;true;.ctor;;;Argument[0];request-forgery-sink",
- "org.apache.http.message;BasicHttpRequest;true;.ctor;;;Argument[0];request-forgery-sink",
- "org.apache.http.client.methods;RequestBuilder;false;setURI;;;Argument[0];request-forgery-sink",
- "org.apache.http.client.methods;RequestBuilder;false;get;;;Argument[0];request-forgery-sink",
- "org.apache.http.client.methods;RequestBuilder;false;post;;;Argument[0];request-forgery-sink",
- "org.apache.http.client.methods;RequestBuilder;false;put;;;Argument[0];request-forgery-sink",
- "org.apache.http.client.methods;RequestBuilder;false;options;;;Argument[0];request-forgery-sink",
- "org.apache.http.client.methods;RequestBuilder;false;head;;;Argument[0];request-forgery-sink",
- "org.apache.http.client.methods;RequestBuilder;false;delete;;;Argument[0];request-forgery-sink",
- "java.net.http;HttpRequest$Builder;false;uri;;;Argument[0];request-forgery-sink",
- "org.springframework.web.client;RestTemplate;false;patchForObject;;;Argument[0];request-forgery-sink",
- "org.springframework.web.client;RestTemplate;false;getForObject;;;Argument[0];request-forgery-sink",
- "org.springframework.web.client;RestTemplate;false;getForEntity;;;Argument[0];request-forgery-sink",
- "org.springframework.web.client;RestTemplate;false;execute;;;Argument[0];request-forgery-sink",
- "org.springframework.web.client;RestTemplate;false;exchange;;;Argument[0];request-forgery-sink",
- "org.springframework.web.client;RestTemplate;false;put;;;Argument[0];request-forgery-sink",
- "org.springframework.web.client;RestTemplate;false;postForObject;;;Argument[0];request-forgery-sink",
- "org.springframework.web.client;RestTemplate;false;postForLocation;;;Argument[0];request-forgery-sink",
- "org.springframework.web.client;RestTemplate;false;postForEntity;;;Argument[0];request-forgery-sink",
- "org.springframework.web.client;RestTemplate;false;doExecute;;;Argument[0];request-forgery-sink",
- "javax.ws.rs.client;Client;false;target;;;Argument[0];request-forgery-sink",
- "java.net.http;HttpRequest;false;newBuilder;;;Argument[0];request-forgery-sink" // todo: this might be stricter than before. Previously the package name was not checked.
- ]
+/** A data flow sink for request forgery vulnerabilities. */
+abstract class RequestForgerySink extends DataFlow::Node { }
+
+/**
+ * An argument to an url `openConnection` or `openStream` call
+ * taken as a sink for request forgery vulnerabilities.
+ */
+private class UrlOpen extends RequestForgerySink {
+ UrlOpen() {
+ exists(MethodAccess ma |
+ ma.getMethod() instanceof UrlOpenConnectionMethod or
+ ma.getMethod() instanceof UrlOpenStreamMethod
+ |
+ this.asExpr() = ma.getQualifier()
+ )
}
}
-/** A data flow sink for request forgery vulnerabilities. */
-abstract class RequestForgerySink extends DataFlow::Node { }
+/**
+ * An argument to an Apache `setURI` call taken as a
+ * sink for request forgery vulnerabilities.
+ */
+private class ApacheSetUri extends RequestForgerySink {
+ ApacheSetUri() {
+ exists(MethodAccess ma |
+ ma.getReceiverType() instanceof ApacheHttpRequest and
+ ma.getMethod().hasName("setURI")
+ |
+ this.asExpr() = ma.getArgument(0)
+ )
+ }
+}
+
+/**
+ * An argument to any Apache Request Instantiation call taken as a
+ * sink for request forgery vulnerabilities.
+ */
+private class ApacheHttpRequestInstantiation extends RequestForgerySink {
+ ApacheHttpRequestInstantiation() {
+ exists(ClassInstanceExpr c | c.getConstructedType() instanceof ApacheHttpRequest |
+ this.asExpr() = c.getArgument(0)
+ )
+ }
+}
+
+/**
+ * An argument to a Apache RequestBuilder method call taken as a
+ * sink for request forgery vulnerabilities.
+ */
+private class ApacheHttpRequestBuilderArgument extends RequestForgerySink {
+ ApacheHttpRequestBuilderArgument() {
+ exists(MethodAccess ma |
+ ma.getReceiverType() instanceof TypeApacheHttpRequestBuilder and
+ ma.getMethod().hasName(["setURI", "get", "post", "put", "optons", "head", "delete"])
+ |
+ this.asExpr() = ma.getArgument(0)
+ )
+ }
+}
+
+/**
+ * An argument to any Java.net.http.request Instantiation call taken as a
+ * sink for request forgery vulnerabilities.
+ */
+private class HttpRequestNewBuilder extends RequestForgerySink {
+ HttpRequestNewBuilder() {
+ exists(MethodAccess call |
+ call.getCallee().hasName("newBuilder") and
+ call.getMethod().getDeclaringType().getName() = "HttpRequest"
+ |
+ this.asExpr() = call.getArgument(0)
+ )
+ }
+}
+
+/**
+ * An argument to an Http Builder `uri` call taken as a
+ * sink for request forgery vulnerabilities.
+ */
+private class HttpBuilderUriArgument extends RequestForgerySink {
+ HttpBuilderUriArgument() {
+ exists(MethodAccess ma | ma.getMethod() instanceof HttpBuilderUri |
+ this.asExpr() = ma.getArgument(0)
+ )
+ }
+}
+
+/**
+ * An argument to a Spring Rest Template method call taken as a
+ * sink for request forgery vulnerabilities.
+ */
+private class SpringRestTemplateArgument extends RequestForgerySink {
+ SpringRestTemplateArgument() {
+ exists(MethodAccess ma |
+ this.asExpr() = ma.getMethod().(SpringRestTemplateUrlMethods).getUrlArgument(ma)
+ )
+ }
+}
+
+/**
+ * An argument to `javax.ws.rs.Client`s `target` method call taken as a
+ * sink for request forgery vulnerabilities.
+ */
+private class JaxRsClientTarget extends RequestForgerySink {
+ JaxRsClientTarget() {
+ exists(MethodAccess ma |
+ ma.getMethod().getDeclaringType() instanceof JaxRsClient and
+ ma.getMethod().hasName("target")
+ |
+ this.asExpr() = ma.getArgument(0)
+ )
+ }
+}
/**
* An argument to `org.springframework.http.RequestEntity`s constructor call
@@ -90,3 +166,27 @@ private class RequestEntityUriArg extends RequestForgerySink {
)
}
}
+
+/**
+ * A class representing all Spring Rest Template methods
+ * which take an URL as an argument.
+ */
+private class SpringRestTemplateUrlMethods extends Method {
+ SpringRestTemplateUrlMethods() {
+ this.getDeclaringType() instanceof SpringRestTemplate and
+ this.hasName([
+ "doExecute", "postForEntity", "postForLocation", "postForObject", "put", "exchange",
+ "execute", "getForEntity", "getForObject", "patchForObject"
+ ])
+ }
+
+ /**
+ * Gets the argument which corresponds to a URL argument
+ * passed as a `java.net.URL` object or as a string or the like
+ */
+ Argument getUrlArgument(MethodAccess ma) {
+ // doExecute(URI url, HttpMethod method, RequestCallback requestCallback,
+ // ResponseExtractor responseExtractor)
+ result = ma.getArgument(0)
+ }
+}
diff --git a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
index 3cda09c14889..154ec98f2ad8 100644
--- a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
+++ b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
@@ -472,7 +472,7 @@ module CsvValidation {
not type.regexpMatch("[a-zA-Z0-9_\\$]+") and
msg = "Dubious type \"" + type + "\" in " + pred + " model."
or
- not (name.regexpMatch("[a-zA-Z0-9_]*") or name = ".ctor") and
+ not name.regexpMatch("[a-zA-Z0-9_]*") and
msg = "Dubious name \"" + name + "\" in " + pred + " model."
or
not signature.regexpMatch("|\\([a-zA-Z0-9_\\.\\$<>,\\[\\]]*\\)") and
@@ -562,34 +562,23 @@ private string paramsString(Callable c) {
}
private Element interpretElement0(
- string namespace, string type, boolean subtypes, string name, string signature, string ext
+ string namespace, string type, boolean subtypes, string name, string signature
) {
elementSpec(namespace, type, subtypes, name, signature, _) and
exists(RefType t | t = interpretType(namespace, type, subtypes) |
exists(Member m |
result = m and
m.getDeclaringType() = t and
- (
- m.hasName(name)
- or
- name = ".ctor" and m.hasName(t.getName())
- )
+ m.hasName(name)
|
signature = "" or
m.(Callable).getSignature() = any(string nameprefix) + signature or
paramsString(m) = signature
- ) and
- ext = ""
+ )
or
result = t and
name = "" and
- signature = "" and
- ext = "Annotated"
- or
- result = t.getAMember() and
- name = "" and
- signature = "" and
- ext = ""
+ signature = ""
)
}
@@ -597,7 +586,7 @@ private Element interpretElement(
string namespace, string type, boolean subtypes, string name, string signature, string ext
) {
elementSpec(namespace, type, subtypes, name, signature, ext) and
- exists(Element e | e = interpretElement0(namespace, type, subtypes, name, signature, ext) |
+ exists(Element e | e = interpretElement0(namespace, type, subtypes, name, signature) |
ext = "" and result = e
or
ext = "Annotated" and result.(Annotatable).getAnAnnotation().getType() = e
diff --git a/java/ql/src/semmle/code/java/security/UnsafeDeserialization.qll b/java/ql/src/semmle/code/java/security/UnsafeDeserialization.qll
index d84ee67bca5c..ab809f07d6d1 100644
--- a/java/ql/src/semmle/code/java/security/UnsafeDeserialization.qll
+++ b/java/ql/src/semmle/code/java/security/UnsafeDeserialization.qll
@@ -3,7 +3,6 @@ import semmle.code.java.frameworks.XStream
import semmle.code.java.frameworks.SnakeYaml
import semmle.code.java.frameworks.FastJson
import semmle.code.java.frameworks.apache.Lang
-private import semmle.code.java.dataflow.ExternalFlow
class ObjectInputStreamReadObjectMethod extends Method {
ObjectInputStreamReadObjectMethod() {
@@ -27,16 +26,11 @@ class SafeXStream extends DataFlow2::Configuration {
src.asExpr()
}
- override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "safe-xstream") }
-}
-
-private class SafeXStreamSinkModel extends SinkModelCsv {
- override predicate row(string row) {
- row =
- [
- "com.thoughtworks.xstream;XStream;false;unmarshal;;;Argument[-1];safe-xstream",
- "com.thoughtworks.xstream;XStream;false;fromXML;;;Argument[-1];safe-xstream"
- ]
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess ma |
+ sink.asExpr() = ma.getQualifier() and
+ ma.getMethod() instanceof XStreamReadObjectMethod
+ )
}
}
@@ -48,17 +42,11 @@ class SafeKryo extends DataFlow2::Configuration {
src.asExpr()
}
- override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "safe-kryo") }
-}
-
-private class SafeKryoSinkModel extends SinkModelCsv {
- override predicate row(string row) {
- row =
- [
- "com.esotericsoftware.kryo;Kryo;false;readObjectOrNull;;;Argument[-1];safe-kryo",
- "com.esotericsoftware.kryo;Kryo;false;readObject;;;Argument[-1];safe-kryo",
- "com.esotericsoftware.kryo;Kryo;false;readClassAndObject;;;Argument[-1];safe-kryo"
- ]
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess ma |
+ sink.asExpr() = ma.getQualifier() and
+ ma.getMethod() instanceof KryoReadObjectMethod
+ )
}
}
From affdedd840743f7ad814e08d0a9ccb5a4b659725 Mon Sep 17 00:00:00 2001
From: Taus
Date: Fri, 9 Apr 2021 12:02:07 +0000
Subject: [PATCH 149/433] Python: Add missing builtins to `API::builtin`
We were missing out on `None`, `True`, and `False` as these do not
appear as actual attributes of the `builtins` module in Python 3
(because they are elevated to the status of keywords there)
The simple solution, then, is to just always include them directly.
---
python/ql/src/semmle/python/ApiGraphs.qll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/ql/src/semmle/python/ApiGraphs.qll b/python/ql/src/semmle/python/ApiGraphs.qll
index 5aab059d58e6..5f1fbbb96269 100644
--- a/python/ql/src/semmle/python/ApiGraphs.qll
+++ b/python/ql/src/semmle/python/ApiGraphs.qll
@@ -363,7 +363,7 @@ module API {
n.isGlobal() and
n.isLoad() and
name = n.getId() and
- name = any(Builtins::Builtin b).getName()
+ name in [any(Builtins::Builtin b).getName(), "None", "True", "False"]
)
}
From 6874b8d4b3a899cb38db47fb460f4f93c256f272 Mon Sep 17 00:00:00 2001
From: Tom Hvitved
Date: Fri, 9 Apr 2021 14:23:10 +0200
Subject: [PATCH 150/433] Data flow: Prevent bad join-order in `pathStep`
---
.../code/cpp/dataflow/internal/DataFlowImpl.qll | 11 ++++-------
.../code/cpp/dataflow/internal/DataFlowImpl2.qll | 11 ++++-------
.../code/cpp/dataflow/internal/DataFlowImpl3.qll | 11 ++++-------
.../code/cpp/dataflow/internal/DataFlowImpl4.qll | 11 ++++-------
.../code/cpp/dataflow/internal/DataFlowImplLocal.qll | 11 ++++-------
.../code/cpp/ir/dataflow/internal/DataFlowImpl.qll | 11 ++++-------
.../code/cpp/ir/dataflow/internal/DataFlowImpl2.qll | 11 ++++-------
.../code/cpp/ir/dataflow/internal/DataFlowImpl3.qll | 11 ++++-------
.../code/cpp/ir/dataflow/internal/DataFlowImpl4.qll | 11 ++++-------
.../code/csharp/dataflow/internal/DataFlowImpl.qll | 11 ++++-------
.../code/csharp/dataflow/internal/DataFlowImpl2.qll | 11 ++++-------
.../code/csharp/dataflow/internal/DataFlowImpl3.qll | 11 ++++-------
.../code/csharp/dataflow/internal/DataFlowImpl4.qll | 11 ++++-------
.../code/csharp/dataflow/internal/DataFlowImpl5.qll | 11 ++++-------
.../code/java/dataflow/internal/DataFlowImpl.qll | 11 ++++-------
.../code/java/dataflow/internal/DataFlowImpl2.qll | 11 ++++-------
.../code/java/dataflow/internal/DataFlowImpl3.qll | 11 ++++-------
.../code/java/dataflow/internal/DataFlowImpl4.qll | 11 ++++-------
.../code/java/dataflow/internal/DataFlowImpl5.qll | 11 ++++-------
.../python/dataflow/new/internal/DataFlowImpl.qll | 11 ++++-------
.../python/dataflow/new/internal/DataFlowImpl2.qll | 11 ++++-------
.../python/dataflow/new/internal/DataFlowImpl3.qll | 11 ++++-------
.../python/dataflow/new/internal/DataFlowImpl4.qll | 11 ++++-------
23 files changed, 92 insertions(+), 161 deletions(-)
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll
index 8b446d28b86d..ab29838988dd 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll
@@ -2132,12 +2132,9 @@ private module Stage4 {
}
bindingset[node, cc, config]
- private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
- exists(Cc cc0 |
- cc = pragma[only_bind_into](cc0) and
- localFlowEntry(node, config) and
- result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
- )
+ LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ localFlowEntry(node, config) and
+ result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
private predicate localStep(
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
conf = mid.getConfiguration() and
cc = mid.getCallContext() and
sc = mid.getSummaryCtx() and
- localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
+ localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
ap0 = mid.getAp()
|
localFlowBigStep(midnode, node, true, _, conf, localCC) and
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll
index 8b446d28b86d..ab29838988dd 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll
@@ -2132,12 +2132,9 @@ private module Stage4 {
}
bindingset[node, cc, config]
- private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
- exists(Cc cc0 |
- cc = pragma[only_bind_into](cc0) and
- localFlowEntry(node, config) and
- result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
- )
+ LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ localFlowEntry(node, config) and
+ result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
private predicate localStep(
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
conf = mid.getConfiguration() and
cc = mid.getCallContext() and
sc = mid.getSummaryCtx() and
- localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
+ localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
ap0 = mid.getAp()
|
localFlowBigStep(midnode, node, true, _, conf, localCC) and
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll
index 8b446d28b86d..ab29838988dd 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll
@@ -2132,12 +2132,9 @@ private module Stage4 {
}
bindingset[node, cc, config]
- private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
- exists(Cc cc0 |
- cc = pragma[only_bind_into](cc0) and
- localFlowEntry(node, config) and
- result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
- )
+ LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ localFlowEntry(node, config) and
+ result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
private predicate localStep(
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
conf = mid.getConfiguration() and
cc = mid.getCallContext() and
sc = mid.getSummaryCtx() and
- localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
+ localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
ap0 = mid.getAp()
|
localFlowBigStep(midnode, node, true, _, conf, localCC) and
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll
index 8b446d28b86d..ab29838988dd 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll
@@ -2132,12 +2132,9 @@ private module Stage4 {
}
bindingset[node, cc, config]
- private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
- exists(Cc cc0 |
- cc = pragma[only_bind_into](cc0) and
- localFlowEntry(node, config) and
- result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
- )
+ LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ localFlowEntry(node, config) and
+ result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
private predicate localStep(
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
conf = mid.getConfiguration() and
cc = mid.getCallContext() and
sc = mid.getSummaryCtx() and
- localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
+ localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
ap0 = mid.getAp()
|
localFlowBigStep(midnode, node, true, _, conf, localCC) and
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll
index 8b446d28b86d..ab29838988dd 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll
@@ -2132,12 +2132,9 @@ private module Stage4 {
}
bindingset[node, cc, config]
- private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
- exists(Cc cc0 |
- cc = pragma[only_bind_into](cc0) and
- localFlowEntry(node, config) and
- result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
- )
+ LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ localFlowEntry(node, config) and
+ result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
private predicate localStep(
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
conf = mid.getConfiguration() and
cc = mid.getCallContext() and
sc = mid.getSummaryCtx() and
- localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
+ localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
ap0 = mid.getAp()
|
localFlowBigStep(midnode, node, true, _, conf, localCC) and
diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll
index 8b446d28b86d..ab29838988dd 100644
--- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll
+++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll
@@ -2132,12 +2132,9 @@ private module Stage4 {
}
bindingset[node, cc, config]
- private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
- exists(Cc cc0 |
- cc = pragma[only_bind_into](cc0) and
- localFlowEntry(node, config) and
- result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
- )
+ LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ localFlowEntry(node, config) and
+ result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
private predicate localStep(
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
conf = mid.getConfiguration() and
cc = mid.getCallContext() and
sc = mid.getSummaryCtx() and
- localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
+ localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
ap0 = mid.getAp()
|
localFlowBigStep(midnode, node, true, _, conf, localCC) and
diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll
index 8b446d28b86d..ab29838988dd 100644
--- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll
+++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll
@@ -2132,12 +2132,9 @@ private module Stage4 {
}
bindingset[node, cc, config]
- private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
- exists(Cc cc0 |
- cc = pragma[only_bind_into](cc0) and
- localFlowEntry(node, config) and
- result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
- )
+ LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ localFlowEntry(node, config) and
+ result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
private predicate localStep(
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
conf = mid.getConfiguration() and
cc = mid.getCallContext() and
sc = mid.getSummaryCtx() and
- localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
+ localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
ap0 = mid.getAp()
|
localFlowBigStep(midnode, node, true, _, conf, localCC) and
diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll
index 8b446d28b86d..ab29838988dd 100644
--- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll
+++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll
@@ -2132,12 +2132,9 @@ private module Stage4 {
}
bindingset[node, cc, config]
- private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
- exists(Cc cc0 |
- cc = pragma[only_bind_into](cc0) and
- localFlowEntry(node, config) and
- result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
- )
+ LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ localFlowEntry(node, config) and
+ result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
private predicate localStep(
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
conf = mid.getConfiguration() and
cc = mid.getCallContext() and
sc = mid.getSummaryCtx() and
- localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
+ localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
ap0 = mid.getAp()
|
localFlowBigStep(midnode, node, true, _, conf, localCC) and
diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll
index 8b446d28b86d..ab29838988dd 100644
--- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll
+++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll
@@ -2132,12 +2132,9 @@ private module Stage4 {
}
bindingset[node, cc, config]
- private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
- exists(Cc cc0 |
- cc = pragma[only_bind_into](cc0) and
- localFlowEntry(node, config) and
- result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
- )
+ LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ localFlowEntry(node, config) and
+ result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
private predicate localStep(
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
conf = mid.getConfiguration() and
cc = mid.getCallContext() and
sc = mid.getSummaryCtx() and
- localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
+ localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
ap0 = mid.getAp()
|
localFlowBigStep(midnode, node, true, _, conf, localCC) and
diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll
index 8b446d28b86d..ab29838988dd 100644
--- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll
+++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll
@@ -2132,12 +2132,9 @@ private module Stage4 {
}
bindingset[node, cc, config]
- private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
- exists(Cc cc0 |
- cc = pragma[only_bind_into](cc0) and
- localFlowEntry(node, config) and
- result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
- )
+ LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ localFlowEntry(node, config) and
+ result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
private predicate localStep(
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
conf = mid.getConfiguration() and
cc = mid.getCallContext() and
sc = mid.getSummaryCtx() and
- localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
+ localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
ap0 = mid.getAp()
|
localFlowBigStep(midnode, node, true, _, conf, localCC) and
diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll
index 8b446d28b86d..ab29838988dd 100644
--- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll
+++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll
@@ -2132,12 +2132,9 @@ private module Stage4 {
}
bindingset[node, cc, config]
- private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
- exists(Cc cc0 |
- cc = pragma[only_bind_into](cc0) and
- localFlowEntry(node, config) and
- result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
- )
+ LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ localFlowEntry(node, config) and
+ result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
private predicate localStep(
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
conf = mid.getConfiguration() and
cc = mid.getCallContext() and
sc = mid.getSummaryCtx() and
- localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
+ localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
ap0 = mid.getAp()
|
localFlowBigStep(midnode, node, true, _, conf, localCC) and
diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll
index 8b446d28b86d..ab29838988dd 100644
--- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll
+++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll
@@ -2132,12 +2132,9 @@ private module Stage4 {
}
bindingset[node, cc, config]
- private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
- exists(Cc cc0 |
- cc = pragma[only_bind_into](cc0) and
- localFlowEntry(node, config) and
- result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
- )
+ LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ localFlowEntry(node, config) and
+ result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
private predicate localStep(
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
conf = mid.getConfiguration() and
cc = mid.getCallContext() and
sc = mid.getSummaryCtx() and
- localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
+ localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
ap0 = mid.getAp()
|
localFlowBigStep(midnode, node, true, _, conf, localCC) and
diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll
index 8b446d28b86d..ab29838988dd 100644
--- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll
+++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll
@@ -2132,12 +2132,9 @@ private module Stage4 {
}
bindingset[node, cc, config]
- private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
- exists(Cc cc0 |
- cc = pragma[only_bind_into](cc0) and
- localFlowEntry(node, config) and
- result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
- )
+ LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ localFlowEntry(node, config) and
+ result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
private predicate localStep(
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
conf = mid.getConfiguration() and
cc = mid.getCallContext() and
sc = mid.getSummaryCtx() and
- localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
+ localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
ap0 = mid.getAp()
|
localFlowBigStep(midnode, node, true, _, conf, localCC) and
diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll
index 8b446d28b86d..ab29838988dd 100644
--- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll
+++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll
@@ -2132,12 +2132,9 @@ private module Stage4 {
}
bindingset[node, cc, config]
- private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
- exists(Cc cc0 |
- cc = pragma[only_bind_into](cc0) and
- localFlowEntry(node, config) and
- result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
- )
+ LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ localFlowEntry(node, config) and
+ result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
private predicate localStep(
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
conf = mid.getConfiguration() and
cc = mid.getCallContext() and
sc = mid.getSummaryCtx() and
- localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
+ localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
ap0 = mid.getAp()
|
localFlowBigStep(midnode, node, true, _, conf, localCC) and
diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll
index 8b446d28b86d..ab29838988dd 100644
--- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll
+++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll
@@ -2132,12 +2132,9 @@ private module Stage4 {
}
bindingset[node, cc, config]
- private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
- exists(Cc cc0 |
- cc = pragma[only_bind_into](cc0) and
- localFlowEntry(node, config) and
- result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
- )
+ LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ localFlowEntry(node, config) and
+ result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
private predicate localStep(
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
conf = mid.getConfiguration() and
cc = mid.getCallContext() and
sc = mid.getSummaryCtx() and
- localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
+ localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
ap0 = mid.getAp()
|
localFlowBigStep(midnode, node, true, _, conf, localCC) and
diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll
index 8b446d28b86d..ab29838988dd 100644
--- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll
+++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll
@@ -2132,12 +2132,9 @@ private module Stage4 {
}
bindingset[node, cc, config]
- private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
- exists(Cc cc0 |
- cc = pragma[only_bind_into](cc0) and
- localFlowEntry(node, config) and
- result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
- )
+ LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ localFlowEntry(node, config) and
+ result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
private predicate localStep(
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
conf = mid.getConfiguration() and
cc = mid.getCallContext() and
sc = mid.getSummaryCtx() and
- localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
+ localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
ap0 = mid.getAp()
|
localFlowBigStep(midnode, node, true, _, conf, localCC) and
diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll
index 8b446d28b86d..ab29838988dd 100644
--- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll
+++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll
@@ -2132,12 +2132,9 @@ private module Stage4 {
}
bindingset[node, cc, config]
- private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
- exists(Cc cc0 |
- cc = pragma[only_bind_into](cc0) and
- localFlowEntry(node, config) and
- result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
- )
+ LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ localFlowEntry(node, config) and
+ result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
private predicate localStep(
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
conf = mid.getConfiguration() and
cc = mid.getCallContext() and
sc = mid.getSummaryCtx() and
- localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
+ localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
ap0 = mid.getAp()
|
localFlowBigStep(midnode, node, true, _, conf, localCC) and
diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll
index 8b446d28b86d..ab29838988dd 100644
--- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll
+++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll
@@ -2132,12 +2132,9 @@ private module Stage4 {
}
bindingset[node, cc, config]
- private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
- exists(Cc cc0 |
- cc = pragma[only_bind_into](cc0) and
- localFlowEntry(node, config) and
- result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
- )
+ LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ localFlowEntry(node, config) and
+ result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
private predicate localStep(
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
conf = mid.getConfiguration() and
cc = mid.getCallContext() and
sc = mid.getSummaryCtx() and
- localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
+ localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
ap0 = mid.getAp()
|
localFlowBigStep(midnode, node, true, _, conf, localCC) and
diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll
index 8b446d28b86d..ab29838988dd 100644
--- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll
+++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll
@@ -2132,12 +2132,9 @@ private module Stage4 {
}
bindingset[node, cc, config]
- private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
- exists(Cc cc0 |
- cc = pragma[only_bind_into](cc0) and
- localFlowEntry(node, config) and
- result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
- )
+ LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ localFlowEntry(node, config) and
+ result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
private predicate localStep(
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
conf = mid.getConfiguration() and
cc = mid.getCallContext() and
sc = mid.getSummaryCtx() and
- localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
+ localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
ap0 = mid.getAp()
|
localFlowBigStep(midnode, node, true, _, conf, localCC) and
diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl.qll
index 8b446d28b86d..ab29838988dd 100644
--- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl.qll
+++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl.qll
@@ -2132,12 +2132,9 @@ private module Stage4 {
}
bindingset[node, cc, config]
- private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
- exists(Cc cc0 |
- cc = pragma[only_bind_into](cc0) and
- localFlowEntry(node, config) and
- result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
- )
+ LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ localFlowEntry(node, config) and
+ result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
private predicate localStep(
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
conf = mid.getConfiguration() and
cc = mid.getCallContext() and
sc = mid.getSummaryCtx() and
- localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
+ localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
ap0 = mid.getAp()
|
localFlowBigStep(midnode, node, true, _, conf, localCC) and
diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl2.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl2.qll
index 8b446d28b86d..ab29838988dd 100644
--- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl2.qll
+++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl2.qll
@@ -2132,12 +2132,9 @@ private module Stage4 {
}
bindingset[node, cc, config]
- private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
- exists(Cc cc0 |
- cc = pragma[only_bind_into](cc0) and
- localFlowEntry(node, config) and
- result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
- )
+ LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ localFlowEntry(node, config) and
+ result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
private predicate localStep(
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
conf = mid.getConfiguration() and
cc = mid.getCallContext() and
sc = mid.getSummaryCtx() and
- localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
+ localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
ap0 = mid.getAp()
|
localFlowBigStep(midnode, node, true, _, conf, localCC) and
diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl3.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl3.qll
index 8b446d28b86d..ab29838988dd 100644
--- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl3.qll
+++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl3.qll
@@ -2132,12 +2132,9 @@ private module Stage4 {
}
bindingset[node, cc, config]
- private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
- exists(Cc cc0 |
- cc = pragma[only_bind_into](cc0) and
- localFlowEntry(node, config) and
- result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
- )
+ LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ localFlowEntry(node, config) and
+ result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
private predicate localStep(
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
conf = mid.getConfiguration() and
cc = mid.getCallContext() and
sc = mid.getSummaryCtx() and
- localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
+ localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
ap0 = mid.getAp()
|
localFlowBigStep(midnode, node, true, _, conf, localCC) and
diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl4.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl4.qll
index 8b446d28b86d..ab29838988dd 100644
--- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl4.qll
+++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl4.qll
@@ -2132,12 +2132,9 @@ private module Stage4 {
}
bindingset[node, cc, config]
- private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
- exists(Cc cc0 |
- cc = pragma[only_bind_into](cc0) and
- localFlowEntry(node, config) and
- result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
- )
+ LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ localFlowEntry(node, config) and
+ result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
private predicate localStep(
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
conf = mid.getConfiguration() and
cc = mid.getCallContext() and
sc = mid.getSummaryCtx() and
- localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
+ localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
ap0 = mid.getAp()
|
localFlowBigStep(midnode, node, true, _, conf, localCC) and
From d2b874f21764b93dad29e2f950b3e0f95f882bad Mon Sep 17 00:00:00 2001
From: Taus
Date: Fri, 9 Apr 2021 14:19:00 +0000
Subject: [PATCH 151/433] Python: Use API graphs in PEP249 support
Because the replacement extension point now extends `API::Node`, I
modified the `toString` method of the latter to have an empty body.
The alternative would be to require everyone to provide a `toString`
predicate for their extensions, but seeing as these will usually be
pointing to already existing API graph nodes, this seems silly.
(This may be the reason why the equivalent method in the JS libs has
such an implementation.)
---
python/ql/src/semmle/python/ApiGraphs.qll | 2 +-
.../src/semmle/python/frameworks/Django.qll | 7 ++-
.../src/semmle/python/frameworks/MySQLdb.qll | 16 +-----
.../frameworks/MysqlConnectorPython.qll | 55 +------------------
.../src/semmle/python/frameworks/PEP249.qll | 35 ++++++------
.../src/semmle/python/frameworks/Psycopg2.qll | 16 +-----
.../src/semmle/python/frameworks/PyMySQL.qll | 16 +-----
.../src/semmle/python/frameworks/Stdlib.qll | 15 +----
8 files changed, 37 insertions(+), 125 deletions(-)
diff --git a/python/ql/src/semmle/python/ApiGraphs.qll b/python/ql/src/semmle/python/ApiGraphs.qll
index 5aab059d58e6..9689fb06a77c 100644
--- a/python/ql/src/semmle/python/ApiGraphs.qll
+++ b/python/ql/src/semmle/python/ApiGraphs.qll
@@ -157,7 +157,7 @@ module API {
/**
* Gets a textual representation of this element.
*/
- abstract string toString();
+ string toString() { none() }
/**
* Gets a path of the given `length` from the root to this node.
diff --git a/python/ql/src/semmle/python/frameworks/Django.qll b/python/ql/src/semmle/python/frameworks/Django.qll
index 02d5030bb1e8..56dc12edbf4d 100644
--- a/python/ql/src/semmle/python/frameworks/Django.qll
+++ b/python/ql/src/semmle/python/frameworks/Django.qll
@@ -78,8 +78,11 @@ private module Django {
/** Gets a reference to the `django.db` module. */
DataFlow::Node db() { result = django_attr("db") }
- class DjangoDb extends PEP249Module {
- DjangoDb() { this = db() }
+ /**
+ * `django.db` implements PEP249, providing ways to execute SQL statements against a database.
+ */
+ private class DjangoDb extends PEP249ModuleApiNode {
+ DjangoDb() { this = API::moduleImport("django").getMember("db") }
}
/** Provides models for the `django.db` module. */
diff --git a/python/ql/src/semmle/python/frameworks/MySQLdb.qll b/python/ql/src/semmle/python/frameworks/MySQLdb.qll
index 7013c6f5d02b..b36bf87c7d9f 100644
--- a/python/ql/src/semmle/python/frameworks/MySQLdb.qll
+++ b/python/ql/src/semmle/python/frameworks/MySQLdb.qll
@@ -9,6 +9,7 @@ private import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.Concepts
+private import semmle.python.ApiGraphs
private import PEP249
/**
@@ -21,19 +22,8 @@ private module MySQLdb {
// ---------------------------------------------------------------------------
// MySQLdb
// ---------------------------------------------------------------------------
- /** Gets a reference to the `MySQLdb` module. */
- private DataFlow::Node moduleMySQLdb(DataFlow::TypeTracker t) {
- t.start() and
- result = DataFlow::importNode("MySQLdb")
- or
- exists(DataFlow::TypeTracker t2 | result = moduleMySQLdb(t2).track(t2, t))
- }
-
- /** Gets a reference to the `MySQLdb` module. */
- DataFlow::Node moduleMySQLdb() { result = moduleMySQLdb(DataFlow::TypeTracker::end()) }
-
/** MySQLdb implements PEP 249, providing ways to execute SQL statements against a database. */
- class MySQLdb extends PEP249Module {
- MySQLdb() { this = moduleMySQLdb() }
+ class MySQLdb extends PEP249ModuleApiNode {
+ MySQLdb() { this = API::moduleImport("MySQLdb") }
}
}
diff --git a/python/ql/src/semmle/python/frameworks/MysqlConnectorPython.qll b/python/ql/src/semmle/python/frameworks/MysqlConnectorPython.qll
index 1722bdcb51ee..97cd4fd162ce 100644
--- a/python/ql/src/semmle/python/frameworks/MysqlConnectorPython.qll
+++ b/python/ql/src/semmle/python/frameworks/MysqlConnectorPython.qll
@@ -9,6 +9,7 @@ private import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.Concepts
+private import semmle.python.ApiGraphs
private import PEP249
/**
@@ -21,64 +22,14 @@ private module MysqlConnectorPython {
// ---------------------------------------------------------------------------
// mysql
// ---------------------------------------------------------------------------
- /** Gets a reference to the `mysql` module. */
- private DataFlow::Node mysql(DataFlow::TypeTracker t) {
- t.start() and
- result = DataFlow::importNode("mysql")
- or
- exists(DataFlow::TypeTracker t2 | result = mysql(t2).track(t2, t))
- }
-
- /** Gets a reference to the `mysql` module. */
- DataFlow::Node mysql() { result = mysql(DataFlow::TypeTracker::end()) }
-
- /**
- * Gets a reference to the attribute `attr_name` of the `mysql` module.
- * WARNING: Only holds for a few predefined attributes.
- */
- private DataFlow::Node mysql_attr(DataFlow::TypeTracker t, string attr_name) {
- attr_name in ["connector"] and
- (
- t.start() and
- result = DataFlow::importNode("mysql" + "." + attr_name)
- or
- t.startInAttr(attr_name) and
- result = mysql()
- )
- or
- // Due to bad performance when using normal setup with `mysql_attr(t2, attr_name).track(t2, t)`
- // we have inlined that code and forced a join
- exists(DataFlow::TypeTracker t2 |
- exists(DataFlow::StepSummary summary |
- mysql_attr_first_join(t2, attr_name, result, summary) and
- t = t2.append(summary)
- )
- )
- }
-
- pragma[nomagic]
- private predicate mysql_attr_first_join(
- DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary
- ) {
- DataFlow::StepSummary::step(mysql_attr(t2, attr_name), res, summary)
- }
-
- /**
- * Gets a reference to the attribute `attr_name` of the `mysql` module.
- * WARNING: Only holds for a few predefined attributes.
- */
- private DataFlow::Node mysql_attr(string attr_name) {
- result = mysql_attr(DataFlow::TypeTracker::end(), attr_name)
- }
-
/** Provides models for the `mysql` module. */
module mysql {
/**
* The mysql.connector module
* See https://dev.mysql.com/doc/connector-python/en/connector-python-example-connecting.html
*/
- class MysqlConnector extends PEP249Module {
- MysqlConnector() { this = mysql_attr("connector") }
+ class MysqlConnector extends PEP249ModuleApiNode {
+ MysqlConnector() { this = API::moduleImport("mysql").getMember("connector") }
}
}
}
diff --git a/python/ql/src/semmle/python/frameworks/PEP249.qll b/python/ql/src/semmle/python/frameworks/PEP249.qll
index 1a5d888949f0..c449ae92bf24 100644
--- a/python/ql/src/semmle/python/frameworks/PEP249.qll
+++ b/python/ql/src/semmle/python/frameworks/PEP249.qll
@@ -7,20 +7,23 @@ private import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.Concepts
+private import semmle.python.ApiGraphs
-/** A module implementing PEP 249. Extend this class for implementations. */
-abstract class PEP249Module extends DataFlow::Node { }
+/**
+ * A module implementing PEP 249. Extend this class for implementations.
+ *
+ * DEPRECATED: Extend `PEP249ModuleApiNode` instead.
+ */
+abstract deprecated class PEP249Module extends DataFlow::Node { }
-/** Gets a reference to a connect call. */
-private DataFlow::Node connect(DataFlow::TypeTracker t) {
- t.startInAttr("connect") and
- result instanceof PEP249Module
- or
- exists(DataFlow::TypeTracker t2 | result = connect(t2).track(t2, t))
-}
+/**
+ * An abstract class encompassing API graph nodes that implement PEP 249.
+ * Extend this class for implementations.
+ */
+abstract class PEP249ModuleApiNode extends API::Node { }
/** Gets a reference to a connect call. */
-DataFlow::Node connect() { result = connect(DataFlow::TypeTracker::end()) }
+DataFlow::Node connect() { result = any(PEP249ModuleApiNode a).getMember("connect").getAUse() }
/**
* Provides models for the `db.Connection` class
@@ -43,10 +46,8 @@ module Connection {
abstract class InstanceSource extends DataFlow::Node { }
/** A direct instantiation of `db.Connection`. */
- private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode {
- override CallNode node;
-
- ClassInstantiation() { node.getFunction() = connect().asCfgNode() }
+ private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
+ ClassInstantiation() { this.getFunction() = connect() }
}
/** Gets a reference to an instance of `db.Connection`. */
@@ -115,10 +116,8 @@ private DataFlow::Node execute(DataFlow::TypeTracker t) {
DataFlow::Node execute() { result = execute(DataFlow::TypeTracker::end()) }
/** A call to the `execute` method on a cursor (or on a connection). */
-private class ExecuteCall extends SqlExecution::Range, DataFlow::CfgNode {
- override CallNode node;
-
- ExecuteCall() { node.getFunction() = execute().asCfgNode() }
+private class ExecuteCall extends SqlExecution::Range, DataFlow::CallCfgNode {
+ ExecuteCall() { this.getFunction() = execute() }
override DataFlow::Node getSql() {
result.asCfgNode() in [node.getArg(0), node.getArgByName("sql")]
diff --git a/python/ql/src/semmle/python/frameworks/Psycopg2.qll b/python/ql/src/semmle/python/frameworks/Psycopg2.qll
index 96beee3f0111..1cc20026801b 100644
--- a/python/ql/src/semmle/python/frameworks/Psycopg2.qll
+++ b/python/ql/src/semmle/python/frameworks/Psycopg2.qll
@@ -9,6 +9,7 @@ private import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.Concepts
+private import semmle.python.ApiGraphs
private import PEP249
/**
@@ -21,19 +22,8 @@ private module Psycopg2 {
// ---------------------------------------------------------------------------
// Psycopg
// ---------------------------------------------------------------------------
- /** Gets a reference to the `psycopg2` module. */
- private DataFlow::Node psycopg2(DataFlow::TypeTracker t) {
- t.start() and
- result = DataFlow::importNode("psycopg2")
- or
- exists(DataFlow::TypeTracker t2 | result = psycopg2(t2).track(t2, t))
- }
-
- /** Gets a reference to the `psycopg2` module. */
- DataFlow::Node psycopg2() { result = psycopg2(DataFlow::TypeTracker::end()) }
-
/** psycopg2 implements PEP 249, providing ways to execute SQL statements against a database. */
- class Psycopg2 extends PEP249Module {
- Psycopg2() { this = psycopg2() }
+ class Psycopg2 extends PEP249ModuleApiNode {
+ Psycopg2() { this = API::moduleImport("psycopg2") }
}
}
diff --git a/python/ql/src/semmle/python/frameworks/PyMySQL.qll b/python/ql/src/semmle/python/frameworks/PyMySQL.qll
index 954d2f690dd7..c078302b73f1 100644
--- a/python/ql/src/semmle/python/frameworks/PyMySQL.qll
+++ b/python/ql/src/semmle/python/frameworks/PyMySQL.qll
@@ -7,6 +7,7 @@ private import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.Concepts
+private import semmle.python.ApiGraphs
private import PEP249
/**
@@ -14,19 +15,8 @@ private import PEP249
* See https://pypi.org/project/PyMySQL/
*/
private module PyMySQL {
- /** Gets a reference to the `pymysql` module. */
- private DataFlow::Node pymysql(DataFlow::TypeTracker t) {
- t.start() and
- result = DataFlow::importNode("pymysql")
- or
- exists(DataFlow::TypeTracker t2 | result = pymysql(t2).track(t2, t))
- }
-
- /** Gets a reference to the `pymysql` module. */
- DataFlow::Node pymysql() { result = pymysql(DataFlow::TypeTracker::end()) }
-
/** PyMySQL implements PEP 249, providing ways to execute SQL statements against a database. */
- class PyMySQLPEP249 extends PEP249Module {
- PyMySQLPEP249() { this = pymysql() }
+ class PyMySQLPEP249 extends PEP249ModuleApiNode {
+ PyMySQLPEP249() { this = API::moduleImport("pymysql") }
}
}
diff --git a/python/ql/src/semmle/python/frameworks/Stdlib.qll b/python/ql/src/semmle/python/frameworks/Stdlib.qll
index 2626d1585ff0..718da24aa88c 100644
--- a/python/ql/src/semmle/python/frameworks/Stdlib.qll
+++ b/python/ql/src/semmle/python/frameworks/Stdlib.qll
@@ -1523,24 +1523,13 @@ private module Stdlib {
// ---------------------------------------------------------------------------
// sqlite3
// ---------------------------------------------------------------------------
- /** Gets a reference to the `sqlite3` module. */
- private DataFlow::Node sqlite3(DataFlow::TypeTracker t) {
- t.start() and
- result = DataFlow::importNode("sqlite3")
- or
- exists(DataFlow::TypeTracker t2 | result = sqlite3(t2).track(t2, t))
- }
-
- /** Gets a reference to the `sqlite3` module. */
- DataFlow::Node sqlite3() { result = sqlite3(DataFlow::TypeTracker::end()) }
-
/**
* sqlite3 implements PEP 249, providing ways to execute SQL statements against a database.
*
* See https://devdocs.io/python~3.9/library/sqlite3
*/
- class Sqlite3 extends PEP249Module {
- Sqlite3() { this = sqlite3() }
+ class Sqlite3 extends PEP249ModuleApiNode {
+ Sqlite3() { this = API::moduleImport("sqlite3") }
}
}
From f1306163692e580df95f0bbc65fa0821729d7132 Mon Sep 17 00:00:00 2001
From: Tom Hvitved
Date: Fri, 9 Apr 2021 16:22:58 +0200
Subject: [PATCH 152/433] Data flow: Make `getLocalCc` private again
---
cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll | 2 +-
cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll | 2 +-
cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll | 2 +-
cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll | 2 +-
.../src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll | 2 +-
.../src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll | 2 +-
.../src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll | 2 +-
.../src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll | 2 +-
.../src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll | 2 +-
.../src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll | 2 +-
.../src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll | 2 +-
.../src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll | 2 +-
.../src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll | 2 +-
.../src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll | 2 +-
java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll | 2 +-
.../ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll | 2 +-
.../ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll | 2 +-
.../ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll | 2 +-
.../ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll | 2 +-
.../ql/src/semmle/python/dataflow/new/internal/DataFlowImpl.qll | 2 +-
.../src/semmle/python/dataflow/new/internal/DataFlowImpl2.qll | 2 +-
.../src/semmle/python/dataflow/new/internal/DataFlowImpl3.qll | 2 +-
.../src/semmle/python/dataflow/new/internal/DataFlowImpl4.qll | 2 +-
23 files changed, 23 insertions(+), 23 deletions(-)
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll
index ab29838988dd..9498e51e7e61 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll
@@ -2132,7 +2132,7 @@ private module Stage4 {
}
bindingset[node, cc, config]
- LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
localFlowEntry(node, config) and
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll
index ab29838988dd..9498e51e7e61 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll
@@ -2132,7 +2132,7 @@ private module Stage4 {
}
bindingset[node, cc, config]
- LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
localFlowEntry(node, config) and
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll
index ab29838988dd..9498e51e7e61 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll
@@ -2132,7 +2132,7 @@ private module Stage4 {
}
bindingset[node, cc, config]
- LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
localFlowEntry(node, config) and
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll
index ab29838988dd..9498e51e7e61 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll
@@ -2132,7 +2132,7 @@ private module Stage4 {
}
bindingset[node, cc, config]
- LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
localFlowEntry(node, config) and
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll
index ab29838988dd..9498e51e7e61 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll
@@ -2132,7 +2132,7 @@ private module Stage4 {
}
bindingset[node, cc, config]
- LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
localFlowEntry(node, config) and
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll
index ab29838988dd..9498e51e7e61 100644
--- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll
+++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll
@@ -2132,7 +2132,7 @@ private module Stage4 {
}
bindingset[node, cc, config]
- LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
localFlowEntry(node, config) and
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll
index ab29838988dd..9498e51e7e61 100644
--- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll
+++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll
@@ -2132,7 +2132,7 @@ private module Stage4 {
}
bindingset[node, cc, config]
- LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
localFlowEntry(node, config) and
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll
index ab29838988dd..9498e51e7e61 100644
--- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll
+++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll
@@ -2132,7 +2132,7 @@ private module Stage4 {
}
bindingset[node, cc, config]
- LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
localFlowEntry(node, config) and
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll
index ab29838988dd..9498e51e7e61 100644
--- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll
+++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll
@@ -2132,7 +2132,7 @@ private module Stage4 {
}
bindingset[node, cc, config]
- LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
localFlowEntry(node, config) and
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll
index ab29838988dd..9498e51e7e61 100644
--- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll
+++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll
@@ -2132,7 +2132,7 @@ private module Stage4 {
}
bindingset[node, cc, config]
- LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
localFlowEntry(node, config) and
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll
index ab29838988dd..9498e51e7e61 100644
--- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll
+++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll
@@ -2132,7 +2132,7 @@ private module Stage4 {
}
bindingset[node, cc, config]
- LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
localFlowEntry(node, config) and
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll
index ab29838988dd..9498e51e7e61 100644
--- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll
+++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll
@@ -2132,7 +2132,7 @@ private module Stage4 {
}
bindingset[node, cc, config]
- LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
localFlowEntry(node, config) and
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll
index ab29838988dd..9498e51e7e61 100644
--- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll
+++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll
@@ -2132,7 +2132,7 @@ private module Stage4 {
}
bindingset[node, cc, config]
- LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
localFlowEntry(node, config) and
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll
index ab29838988dd..9498e51e7e61 100644
--- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll
+++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll
@@ -2132,7 +2132,7 @@ private module Stage4 {
}
bindingset[node, cc, config]
- LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
localFlowEntry(node, config) and
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll
index ab29838988dd..9498e51e7e61 100644
--- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll
+++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll
@@ -2132,7 +2132,7 @@ private module Stage4 {
}
bindingset[node, cc, config]
- LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
localFlowEntry(node, config) and
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll
index ab29838988dd..9498e51e7e61 100644
--- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll
+++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll
@@ -2132,7 +2132,7 @@ private module Stage4 {
}
bindingset[node, cc, config]
- LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
localFlowEntry(node, config) and
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll
index ab29838988dd..9498e51e7e61 100644
--- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll
+++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll
@@ -2132,7 +2132,7 @@ private module Stage4 {
}
bindingset[node, cc, config]
- LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
localFlowEntry(node, config) and
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll
index ab29838988dd..9498e51e7e61 100644
--- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll
+++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll
@@ -2132,7 +2132,7 @@ private module Stage4 {
}
bindingset[node, cc, config]
- LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
localFlowEntry(node, config) and
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll
index ab29838988dd..9498e51e7e61 100644
--- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll
+++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll
@@ -2132,7 +2132,7 @@ private module Stage4 {
}
bindingset[node, cc, config]
- LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
localFlowEntry(node, config) and
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl.qll
index ab29838988dd..9498e51e7e61 100644
--- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl.qll
+++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl.qll
@@ -2132,7 +2132,7 @@ private module Stage4 {
}
bindingset[node, cc, config]
- LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
localFlowEntry(node, config) and
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl2.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl2.qll
index ab29838988dd..9498e51e7e61 100644
--- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl2.qll
+++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl2.qll
@@ -2132,7 +2132,7 @@ private module Stage4 {
}
bindingset[node, cc, config]
- LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
localFlowEntry(node, config) and
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl3.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl3.qll
index ab29838988dd..9498e51e7e61 100644
--- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl3.qll
+++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl3.qll
@@ -2132,7 +2132,7 @@ private module Stage4 {
}
bindingset[node, cc, config]
- LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
localFlowEntry(node, config) and
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl4.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl4.qll
index ab29838988dd..9498e51e7e61 100644
--- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl4.qll
+++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowImpl4.qll
@@ -2132,7 +2132,7 @@ private module Stage4 {
}
bindingset[node, cc, config]
- LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
+ private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
localFlowEntry(node, config) and
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
}
From fd8f745468736006884dcabceebf9377567209d7 Mon Sep 17 00:00:00 2001
From: Tom Hvitved
Date: Wed, 17 Mar 2021 13:35:44 +0100
Subject: [PATCH 153/433] Java: Adopt shared flow summary library and refactor
data-flow nodes.
---
config/identical-files.json | 4 +
.../semmle/code/java/dataflow/FlowSummary.qll | 49 ++
.../dataflow/internal/DataFlowDispatch.qll | 24 +-
.../java/dataflow/internal/DataFlowNodes.qll | 445 ++++++++++++
.../dataflow/internal/DataFlowPrivate.qll | 80 +--
.../java/dataflow/internal/DataFlowUtil.qll | 310 +-------
.../dataflow/internal/FlowSummaryImpl.qll | 674 ++++++++++++++++++
.../internal/FlowSummaryImplSpecific.qll | 51 ++
.../dataflow/internal/TaintTrackingUtil.qll | 8 +-
.../localAdditionalTaintStep.ql | 4 +-
10 files changed, 1285 insertions(+), 364 deletions(-)
create mode 100644 java/ql/src/semmle/code/java/dataflow/FlowSummary.qll
create mode 100644 java/ql/src/semmle/code/java/dataflow/internal/DataFlowNodes.qll
create mode 100644 java/ql/src/semmle/code/java/dataflow/internal/FlowSummaryImpl.qll
create mode 100644 java/ql/src/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll
diff --git a/config/identical-files.json b/config/identical-files.json
index 6c1c0c7409d9..3916e95a0cf0 100644
--- a/config/identical-files.json
+++ b/config/identical-files.json
@@ -56,6 +56,10 @@
"csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImplConsistency.qll",
"python/ql/src/semmle/python/dataflow/new/internal/DataFlowImplConsistency.qll"
],
+ "DataFlow Java/C# Flow Summaries": [
+ "java/ql/src/semmle/code/java/dataflow/internal/FlowSummaryImpl.qll",
+ "csharp/ql/src/semmle/code/csharp/dataflow/internal/FlowSummaryImpl.qll"
+ ],
"SsaReadPosition Java/C#": [
"java/ql/src/semmle/code/java/dataflow/internal/rangeanalysis/SsaReadPositionCommon.qll",
"csharp/ql/src/semmle/code/csharp/dataflow/internal/rangeanalysis/SsaReadPositionCommon.qll"
diff --git a/java/ql/src/semmle/code/java/dataflow/FlowSummary.qll b/java/ql/src/semmle/code/java/dataflow/FlowSummary.qll
new file mode 100644
index 000000000000..02e9548c544e
--- /dev/null
+++ b/java/ql/src/semmle/code/java/dataflow/FlowSummary.qll
@@ -0,0 +1,49 @@
+/**
+ * Provides classes and predicates for definining flow summaries.
+ */
+
+import java
+private import internal.FlowSummaryImpl as Impl
+private import internal.DataFlowDispatch
+private import internal.DataFlowPrivate
+
+// import all instances below
+private module Summaries { }
+
+class SummaryComponent = Impl::Public::SummaryComponent;
+
+/** Provides predicates for constructing summary components. */
+module SummaryComponent {
+ import Impl::Public::SummaryComponent
+
+ /** Gets a summary component that represents a qualifier. */
+ SummaryComponent qualifier() { result = argument(-1) }
+
+ /** Gets a summary component for field `f`. */
+ SummaryComponent field(Field f) { result = content(any(FieldContent c | c.getField() = f)) }
+
+ /** Gets a summary component that represents the return value of a call. */
+ SummaryComponent return() { result = return(_) }
+}
+
+class SummaryComponentStack = Impl::Public::SummaryComponentStack;
+
+/** Provides predicates for constructing stacks of summary components. */
+module SummaryComponentStack {
+ import Impl::Public::SummaryComponentStack
+
+ /** Gets a singleton stack representing a qualifier. */
+ SummaryComponentStack qualifier() { result = singleton(SummaryComponent::qualifier()) }
+
+ /** Gets a stack representing a field `f` of `object`. */
+ SummaryComponentStack fieldOf(Field f, SummaryComponentStack object) {
+ result = push(SummaryComponent::field(f), object)
+ }
+
+ /** Gets a singleton stack representing a (normal) return. */
+ SummaryComponentStack return() { result = singleton(SummaryComponent::return()) }
+}
+
+class SummarizedCallable = Impl::Public::SummarizedCallable;
+
+class RequiredSummaryComponentStack = Impl::Public::RequiredSummaryComponentStack;
diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowDispatch.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowDispatch.qll
index 223af728fe45..3a98ed628471 100644
--- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowDispatch.qll
+++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowDispatch.qll
@@ -2,9 +2,17 @@ private import java
private import DataFlowPrivate
private import DataFlowUtil
private import semmle.code.java.dataflow.InstanceAccess
-import semmle.code.java.dispatch.VirtualDispatch
+private import semmle.code.java.dataflow.FlowSummary
+private import semmle.code.java.dispatch.VirtualDispatch as VirtualDispatch
private module DispatchImpl {
+ /** Gets a viable implementation of the target of the given `Call`. */
+ Callable viableCallable(Call c) {
+ result = VirtualDispatch::viableCallable(c)
+ or
+ result.(SummarizedCallable) = c.getCallee().getSourceDeclaration()
+ }
+
/**
* Holds if the set of viable implementations that can be called by `ma`
* might be improved by knowing the call context. This is the case if the
@@ -12,7 +20,7 @@ private module DispatchImpl {
*/
private predicate mayBenefitFromCallContext(MethodAccess ma, Callable c, int i) {
exists(Parameter p |
- 2 <= strictcount(viableImpl(ma)) and
+ 2 <= strictcount(VirtualDispatch::viableImpl(ma)) and
ma.getQualifier().(VarAccess).getVariable() = p and
p.getPosition() = i and
c.getAParameter() = p and
@@ -21,7 +29,7 @@ private module DispatchImpl {
)
or
exists(OwnInstanceAccess ia |
- 2 <= strictcount(viableImpl(ma)) and
+ 2 <= strictcount(VirtualDispatch::viableImpl(ma)) and
(ia.isExplicit(ma.getQualifier()) or ia.isImplicitMethodQualifier(ma)) and
i = -1 and
c = ma.getEnclosingCallable()
@@ -60,7 +68,7 @@ private module DispatchImpl {
or
ctx.getArgument(i) = arg
|
- src = variableTrack(arg) and
+ src = VirtualDispatch::variableTrack(arg) and
srctype = getPreciseType(src) and
if src instanceof ClassInstanceExpr then exact = true else exact = false
)
@@ -93,18 +101,18 @@ private module DispatchImpl {
* restricted to those `ma`s for which a context might make a difference.
*/
Method viableImplInCallContext(MethodAccess ma, Call ctx) {
- result = viableImpl(ma) and
+ result = VirtualDispatch::viableImpl(ma) and
exists(int i, Callable c, Method def, RefType t, boolean exact |
mayBenefitFromCallContext(ma, c, i) and
c = viableCallable(ctx) and
contextArgHasType(ctx, i, t, exact) and
ma.getMethod() = def
|
- exact = true and result = exactMethodImpl(def, t.getSourceDeclaration())
+ exact = true and result = VirtualDispatch::exactMethodImpl(def, t.getSourceDeclaration())
or
exact = false and
exists(RefType t2 |
- result = viableMethodImpl(def, t.getSourceDeclaration(), t2) and
+ result = VirtualDispatch::viableMethodImpl(def, t.getSourceDeclaration(), t2) and
not failsUnification(t, t2)
)
)
@@ -117,7 +125,7 @@ private module DispatchImpl {
pragma[noinline]
private predicate unificationTargetRight(ParameterizedType t2, GenericType g) {
- exists(viableMethodImpl(_, _, t2)) and t2.getGenericType() = g
+ exists(VirtualDispatch::viableMethodImpl(_, _, t2)) and t2.getGenericType() = g
}
private predicate unificationTargets(Type t1, Type t2) {
diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowNodes.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowNodes.qll
new file mode 100644
index 000000000000..552849c2c34c
--- /dev/null
+++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowNodes.qll
@@ -0,0 +1,445 @@
+private import java
+private import semmle.code.java.dataflow.InstanceAccess
+private import semmle.code.java.dataflow.FlowSummary
+private import semmle.code.java.dataflow.TypeFlow
+private import DataFlowPrivate
+private import FlowSummaryImpl as FlowSummaryImpl
+
+cached
+private module Cached {
+ cached
+ newtype TNode =
+ TExprNode(Expr e) {
+ not e.getType() instanceof VoidType and
+ not e.getParent*() instanceof Annotation
+ } or
+ TExplicitParameterNode(Parameter p) {
+ exists(p.getCallable().getBody()) or p.getCallable() instanceof SummarizedCallable
+ } or
+ TImplicitVarargsArray(Call c) {
+ c.getCallee().isVarargs() and
+ not exists(Argument arg | arg.getCall() = c and arg.isExplicitVarargsArray())
+ } or
+ TInstanceParameterNode(Callable c) {
+ (exists(c.getBody()) or c instanceof SummarizedCallable) and
+ not c.isStatic()
+ } or
+ TImplicitInstanceAccess(InstanceAccessExt ia) { not ia.isExplicit(_) } or
+ TMallocNode(ClassInstanceExpr cie) or
+ TExplicitExprPostUpdate(Expr e) {
+ explicitInstanceArgument(_, e)
+ or
+ e instanceof Argument and not e.getType() instanceof ImmutableType
+ or
+ exists(FieldAccess fa | fa.getField() instanceof InstanceField and e = fa.getQualifier())
+ or
+ exists(ArrayAccess aa | e = aa.getArray())
+ } or
+ TImplicitExprPostUpdate(InstanceAccessExt ia) {
+ implicitInstanceArgument(_, ia)
+ or
+ exists(FieldAccess fa |
+ fa.getField() instanceof InstanceField and ia.isImplicitFieldQualifier(fa)
+ )
+ } or
+ TSummaryInternalNode(SummarizedCallable c, FlowSummaryImpl::Private::SummaryNodeState state) {
+ FlowSummaryImpl::Private::summaryNodeRange(c, state)
+ }
+
+ cached
+ predicate summaryOutNodeCached(DataFlowCall c, Node out) {
+ FlowSummaryImpl::Private::summaryOutNode(c, out, _)
+ }
+
+ cached
+ predicate summaryArgumentNodeCached(DataFlowCall c, Node arg, int i) {
+ FlowSummaryImpl::Private::summaryArgumentNode(c, arg, i)
+ }
+
+ cached
+ predicate summaryPostUpdateNodeCached(Node post, ParameterNode pre) {
+ FlowSummaryImpl::Private::summaryPostUpdateNode(post, pre)
+ }
+
+ cached
+ predicate summaryReturnNodeCached(Node ret) {
+ FlowSummaryImpl::Private::summaryReturnNode(ret, _)
+ }
+}
+
+private import Cached
+
+private predicate explicitInstanceArgument(Call call, Expr instarg) {
+ call instanceof MethodAccess and
+ instarg = call.getQualifier() and
+ not call.getCallee().isStatic()
+}
+
+private predicate implicitInstanceArgument(Call call, InstanceAccessExt ia) {
+ ia.isImplicitMethodQualifier(call) or
+ ia.isImplicitThisConstructorArgument(call)
+}
+
+module Public {
+ /**
+ * An element, viewed as a node in a data flow graph. Either an expression,
+ * a parameter, or an implicit varargs array creation.
+ */
+ class Node extends TNode {
+ /** Gets a textual representation of this element. */
+ string toString() { none() }
+
+ /** Gets the source location for this element. */
+ Location getLocation() { none() }
+
+ /** Gets the expression corresponding to this node, if any. */
+ Expr asExpr() { result = this.(ExprNode).getExpr() }
+
+ /** Gets the parameter corresponding to this node, if any. */
+ Parameter asParameter() { result = this.(ExplicitParameterNode).getParameter() }
+
+ /** Gets the type of this node. */
+ Type getType() {
+ result = this.asExpr().getType()
+ or
+ result = this.asParameter().getType()
+ or
+ exists(Parameter p |
+ result = p.getType() and
+ p.isVarargs() and
+ p = this.(ImplicitVarargsArray).getCall().getCallee().getAParameter()
+ )
+ or
+ result = this.(InstanceParameterNode).getCallable().getDeclaringType()
+ or
+ result = this.(ImplicitInstanceAccess).getInstanceAccess().getType()
+ or
+ result = this.(MallocNode).getClassInstanceExpr().getType()
+ or
+ result = this.(ImplicitPostUpdateNode).getPreUpdateNode().getType()
+ }
+
+ /** Gets the callable in which this node occurs. */
+ Callable getEnclosingCallable() {
+ result = this.asExpr().getEnclosingCallable() or
+ result = this.asParameter().getCallable() or
+ result = this.(ImplicitVarargsArray).getCall().getEnclosingCallable() or
+ result = this.(InstanceParameterNode).getCallable() or
+ result = this.(ImplicitInstanceAccess).getInstanceAccess().getEnclosingCallable() or
+ result = this.(MallocNode).getClassInstanceExpr().getEnclosingCallable() or
+ result = this.(ImplicitPostUpdateNode).getPreUpdateNode().getEnclosingCallable() or
+ this = TSummaryInternalNode(result, _)
+ }
+
+ private Type getImprovedTypeBound() {
+ exprTypeFlow(this.asExpr(), result, _) or
+ result = this.(ImplicitPostUpdateNode).getPreUpdateNode().getImprovedTypeBound()
+ }
+
+ /**
+ * Gets an upper bound on the type of this node.
+ */
+ Type getTypeBound() {
+ result = getImprovedTypeBound()
+ or
+ result = getType() and not exists(getImprovedTypeBound())
+ }
+
+ /**
+ * Holds if this element is at the specified location.
+ * The location spans column `startcolumn` of line `startline` to
+ * column `endcolumn` of line `endline` in file `filepath`.
+ * For more information, see
+ * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
+ */
+ predicate hasLocationInfo(
+ string filepath, int startline, int startcolumn, int endline, int endcolumn
+ ) {
+ getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
+ }
+ }
+
+ /**
+ * An expression, viewed as a node in a data flow graph.
+ */
+ class ExprNode extends Node, TExprNode {
+ Expr expr;
+
+ ExprNode() { this = TExprNode(expr) }
+
+ override string toString() { result = expr.toString() }
+
+ override Location getLocation() { result = expr.getLocation() }
+
+ /** Gets the expression corresponding to this node. */
+ Expr getExpr() { result = expr }
+ }
+
+ /** Gets the node corresponding to `e`. */
+ ExprNode exprNode(Expr e) { result.getExpr() = e }
+
+ /** An explicit or implicit parameter. */
+ abstract class ParameterNode extends Node {
+ /**
+ * Holds if this node is the parameter of `c` at the specified (zero-based)
+ * position. The implicit `this` parameter is considered to have index `-1`.
+ */
+ abstract predicate isParameterOf(Callable c, int pos);
+ }
+
+ /**
+ * A parameter, viewed as a node in a data flow graph.
+ */
+ class ExplicitParameterNode extends ParameterNode, TExplicitParameterNode {
+ Parameter param;
+
+ ExplicitParameterNode() { this = TExplicitParameterNode(param) }
+
+ override string toString() { result = param.toString() }
+
+ override Location getLocation() { result = param.getLocation() }
+
+ /** Gets the parameter corresponding to this node. */
+ Parameter getParameter() { result = param }
+
+ override predicate isParameterOf(Callable c, int pos) { c.getParameter(pos) = param }
+ }
+
+ /** Gets the node corresponding to `p`. */
+ ExplicitParameterNode parameterNode(Parameter p) { result.getParameter() = p }
+
+ /**
+ * An implicit varargs array creation expression.
+ *
+ * A call `f(x1, x2)` to a method `f(A... xs)` desugars to `f(new A[]{x1, x2})`,
+ * and this node corresponds to such an implicit array creation.
+ */
+ class ImplicitVarargsArray extends Node, TImplicitVarargsArray {
+ Call call;
+
+ ImplicitVarargsArray() { this = TImplicitVarargsArray(call) }
+
+ override string toString() { result = "new ..[] { .. }" }
+
+ override Location getLocation() { result = call.getLocation() }
+
+ /** Gets the call containing this varargs array creation argument. */
+ Call getCall() { result = call }
+ }
+
+ /**
+ * An instance parameter for an instance method or constructor.
+ */
+ class InstanceParameterNode extends ParameterNode, TInstanceParameterNode {
+ Callable callable;
+
+ InstanceParameterNode() { this = TInstanceParameterNode(callable) }
+
+ override string toString() { result = "parameter this" }
+
+ override Location getLocation() { result = callable.getLocation() }
+
+ /** Gets the callable containing this `this` parameter. */
+ Callable getCallable() { result = callable }
+
+ override predicate isParameterOf(Callable c, int pos) { callable = c and pos = -1 }
+ }
+
+ /**
+ * An implicit read of `this` or `A.this`.
+ */
+ class ImplicitInstanceAccess extends Node, TImplicitInstanceAccess {
+ InstanceAccessExt ia;
+
+ ImplicitInstanceAccess() { this = TImplicitInstanceAccess(ia) }
+
+ override string toString() { result = ia.toString() }
+
+ override Location getLocation() { result = ia.getLocation() }
+
+ /** Gets the instance access corresponding to this node. */
+ InstanceAccessExt getInstanceAccess() { result = ia }
+ }
+
+ /**
+ * A node associated with an object after an operation that might have
+ * changed its state.
+ *
+ * This can be either the argument to a callable after the callable returns
+ * (which might have mutated the argument), or the qualifier of a field after
+ * an update to the field.
+ *
+ * Nodes corresponding to AST elements, for example `ExprNode`, usually refer
+ * to the value before the update with the exception of `ClassInstanceExpr`,
+ * which represents the value after the constructor has run.
+ */
+ abstract class PostUpdateNode extends Node {
+ /**
+ * Gets the node before the state update.
+ */
+ abstract Node getPreUpdateNode();
+ }
+
+ /**
+ * Gets the node that occurs as the qualifier of `fa`.
+ */
+ Node getFieldQualifier(FieldAccess fa) {
+ fa.getField() instanceof InstanceField and
+ (
+ result.asExpr() = fa.getQualifier() or
+ result.(ImplicitInstanceAccess).getInstanceAccess().isImplicitFieldQualifier(fa)
+ )
+ }
+
+ /** Gets the instance argument of a non-static call. */
+ Node getInstanceArgument(Call call) {
+ result.(MallocNode).getClassInstanceExpr() = call or
+ explicitInstanceArgument(call, result.asExpr()) or
+ implicitInstanceArgument(call, result.(ImplicitInstanceAccess).getInstanceAccess())
+ }
+}
+
+private import Public
+
+private class NewExpr extends PostUpdateNode, TExprNode {
+ NewExpr() { exists(ClassInstanceExpr cie | this = TExprNode(cie)) }
+
+ override Node getPreUpdateNode() { this = TExprNode(result.(MallocNode).getClassInstanceExpr()) }
+}
+
+/**
+ * A `PostUpdateNode` that is not a `ClassInstanceExpr`.
+ */
+abstract private class ImplicitPostUpdateNode extends PostUpdateNode {
+ override Location getLocation() { result = getPreUpdateNode().getLocation() }
+
+ override string toString() { result = getPreUpdateNode().toString() + " [post update]" }
+}
+
+private class ExplicitExprPostUpdate extends ImplicitPostUpdateNode, TExplicitExprPostUpdate {
+ override Node getPreUpdateNode() { this = TExplicitExprPostUpdate(result.asExpr()) }
+}
+
+private class ImplicitExprPostUpdate extends ImplicitPostUpdateNode, TImplicitExprPostUpdate {
+ override Node getPreUpdateNode() {
+ this = TImplicitExprPostUpdate(result.(ImplicitInstanceAccess).getInstanceAccess())
+ }
+}
+
+module Private {
+ /**
+ * A data flow node that occurs as the argument of a call and is passed as-is
+ * to the callable. Arguments that are wrapped in an implicit varargs array
+ * creation are not included, but the implicitly created array is.
+ * Instance arguments are also included.
+ */
+ class ArgumentNode extends Node {
+ ArgumentNode() {
+ exists(Argument arg | this.asExpr() = arg | not arg.isVararg())
+ or
+ this instanceof ImplicitVarargsArray
+ or
+ this = getInstanceArgument(_)
+ or
+ this.(SummaryNode).isArgumentOf(_, _)
+ }
+
+ /**
+ * Holds if this argument occurs at the given position in the given call.
+ * The instance argument is considered to have index `-1`.
+ */
+ predicate argumentOf(DataFlowCall call, int pos) {
+ exists(Argument arg | this.asExpr() = arg | call = arg.getCall() and pos = arg.getPosition())
+ or
+ call = this.(ImplicitVarargsArray).getCall() and
+ pos = call.getCallee().getNumberOfParameters() - 1
+ or
+ pos = -1 and this = getInstanceArgument(call)
+ or
+ this.(SummaryNode).isArgumentOf(call, pos)
+ }
+
+ /** Gets the call in which this node is an argument. */
+ DataFlowCall getCall() { this.argumentOf(result, _) }
+ }
+
+ /** A data flow node that occurs as the result of a `ReturnStmt`. */
+ class ReturnNode extends Node {
+ ReturnNode() {
+ exists(ReturnStmt ret | this.asExpr() = ret.getResult()) or
+ this.(SummaryNode).isReturn()
+ }
+
+ /** Gets the kind of this returned value. */
+ ReturnKind getKind() { any() }
+ }
+
+ /** A data flow node that represents the output of a call. */
+ class OutNode extends Node {
+ OutNode() {
+ this.asExpr() instanceof MethodAccess
+ or
+ this.(SummaryNode).isOut(_)
+ }
+
+ /** Gets the underlying call. */
+ DataFlowCall getCall() {
+ result = this.asExpr()
+ or
+ this.(SummaryNode).isOut(result)
+ }
+ }
+
+ /**
+ * A data-flow node used to model flow summaries.
+ */
+ class SummaryNode extends Node, TSummaryInternalNode {
+ private SummarizedCallable c;
+ private FlowSummaryImpl::Private::SummaryNodeState state;
+
+ SummaryNode() { this = TSummaryInternalNode(c, state) }
+
+ override Location getLocation() { result = c.getLocation() }
+
+ override string toString() { result = "[summary] " + state + " in " + c }
+
+ /** Holds if this summary node is the `i`th argument of `call`. */
+ predicate isArgumentOf(DataFlowCall call, int i) { summaryArgumentNodeCached(call, this, i) }
+
+ /** Holds if this summary node is a return node. */
+ predicate isReturn() { summaryReturnNodeCached(this) }
+
+ /** Holds if this summary node is an out node for `call`. */
+ predicate isOut(DataFlowCall call) { summaryOutNodeCached(call, this) }
+ }
+
+ SummaryNode getSummaryNode(SummarizedCallable c, FlowSummaryImpl::Private::SummaryNodeState state) {
+ result = TSummaryInternalNode(c, state)
+ }
+}
+
+private import Private
+
+/**
+ * A node that corresponds to the value of a `ClassInstanceExpr` before the
+ * constructor has run.
+ */
+private class MallocNode extends Node, TMallocNode {
+ ClassInstanceExpr cie;
+
+ MallocNode() { this = TMallocNode(cie) }
+
+ override string toString() { result = cie.toString() + " [pre constructor]" }
+
+ override Location getLocation() { result = cie.getLocation() }
+
+ ClassInstanceExpr getClassInstanceExpr() { result = cie }
+}
+
+private class SummaryPostUpdateNode extends SummaryNode, PostUpdateNode {
+ private Node pre;
+
+ SummaryPostUpdateNode() { summaryPostUpdateNodeCached(this, pre) }
+
+ override Node getPreUpdateNode() { result = pre }
+}
diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll
index f49ca8d75d9f..c425eaa11857 100644
--- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll
+++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll
@@ -4,7 +4,8 @@ private import DataFlowImplCommon
private import DataFlowDispatch
private import semmle.code.java.controlflow.Guards
private import semmle.code.java.dataflow.SSA
-private import semmle.code.java.dataflow.TypeFlow
+private import FlowSummaryImpl as FlowSummaryImpl
+import DataFlowNodes::Private
private newtype TReturnKind = TNormalReturnKind()
@@ -26,54 +27,6 @@ OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) {
kind = TNormalReturnKind()
}
-/**
- * A data flow node that occurs as the argument of a call and is passed as-is
- * to the callable. Arguments that are wrapped in an implicit varargs array
- * creation are not included, but the implicitly created array is.
- * Instance arguments are also included.
- */
-class ArgumentNode extends Node {
- ArgumentNode() {
- exists(Argument arg | this.asExpr() = arg | not arg.isVararg())
- or
- this instanceof ImplicitVarargsArray
- or
- this = getInstanceArgument(_)
- }
-
- /**
- * Holds if this argument occurs at the given position in the given call.
- * The instance argument is considered to have index `-1`.
- */
- predicate argumentOf(DataFlowCall call, int pos) {
- exists(Argument arg | this.asExpr() = arg | call = arg.getCall() and pos = arg.getPosition())
- or
- call = this.(ImplicitVarargsArray).getCall() and
- pos = call.getCallee().getNumberOfParameters() - 1
- or
- pos = -1 and this = getInstanceArgument(call)
- }
-
- /** Gets the call in which this node is an argument. */
- DataFlowCall getCall() { this.argumentOf(result, _) }
-}
-
-/** A data flow node that occurs as the result of a `ReturnStmt`. */
-class ReturnNode extends ExprNode {
- ReturnNode() { exists(ReturnStmt ret | this.getExpr() = ret.getResult()) }
-
- /** Gets the kind of this returned value. */
- ReturnKind getKind() { any() }
-}
-
-/** A data flow node that represents the output of a call. */
-class OutNode extends ExprNode {
- OutNode() { this.getExpr() instanceof MethodAccess }
-
- /** Gets the underlying call. */
- DataFlowCall getCall() { result = this.getExpr() }
-}
-
/**
* Holds if data can flow from `node1` to `node2` through a static field.
*/
@@ -147,7 +100,7 @@ class Content extends TContent {
}
}
-private class FieldContent extends Content, TFieldContent {
+class FieldContent extends Content, TFieldContent {
InstanceField f;
FieldContent() { this = TFieldContent(f) }
@@ -161,11 +114,11 @@ private class FieldContent extends Content, TFieldContent {
}
}
-private class CollectionContent extends Content, TCollectionContent {
+class CollectionContent extends Content, TCollectionContent {
override string toString() { result = "collection" }
}
-private class ArrayContent extends Content, TArrayContent {
+class ArrayContent extends Content, TArrayContent {
override string toString() { result = "array" }
}
@@ -180,6 +133,8 @@ predicate storeStep(Node node1, Content f, PostUpdateNode node2) {
node2.getPreUpdateNode() = getFieldQualifier(fa) and
f.(FieldContent).getField() = fa.getField()
)
+ or
+ FlowSummaryImpl::Private::Steps::summaryStoreStep(node1, f, node2)
}
/**
@@ -205,6 +160,8 @@ predicate readStep(Node node1, Content f, Node node2) {
node1.asExpr() = get.getQualifier() and
node2.asExpr() = get
)
+ or
+ FlowSummaryImpl::Private::Steps::summaryReadStep(node1, f, node2)
}
/**
@@ -213,7 +170,14 @@ predicate readStep(Node node1, Content f, Node node2) {
* in `x.f = newValue`.
*/
predicate clearsContent(Node n, Content c) {
- n = any(PostUpdateNode pun | storeStep(_, c, pun)).getPreUpdateNode()
+ c instanceof FieldContent and
+ (
+ n = any(PostUpdateNode pun | storeStep(_, c, pun)).getPreUpdateNode()
+ or
+ FlowSummaryImpl::Private::Steps::summaryStoresIntoArg(c, n)
+ )
+ or
+ FlowSummaryImpl::Private::Steps::summaryClearsContent(n, c)
}
/**
@@ -221,7 +185,7 @@ predicate clearsContent(Node n, Content c) {
* possible flow. A single type is used for all numeric types to account for
* numeric conversions, and otherwise the erasure is used.
*/
-private DataFlowType getErasedRepr(Type t) {
+DataFlowType getErasedRepr(Type t) {
exists(Type e | e = t.getErasure() |
if e instanceof NumericOrCharType
then result.(BoxedType).getPrimitiveType().getName() = "double"
@@ -235,7 +199,11 @@ private DataFlowType getErasedRepr(Type t) {
}
pragma[noinline]
-DataFlowType getNodeType(Node n) { result = getErasedRepr(n.getTypeBound()) }
+DataFlowType getNodeType(Node n) {
+ result = getErasedRepr(n.getTypeBound())
+ or
+ result = FlowSummaryImpl::Private::summaryNodeType(n)
+}
/** Gets a string representation of a type returned by `getErasedRepr`. */
string ppReprType(Type t) {
@@ -337,7 +305,7 @@ predicate isImmutableOrUnobservable(Node n) {
}
/** Holds if `n` should be hidden from path explanations. */
-predicate nodeIsHidden(Node n) { none() }
+predicate nodeIsHidden(Node n) { n instanceof SummaryNode }
class LambdaCallKind = Unit;
diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowUtil.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowUtil.qll
index 828f2624ea67..030bf30bd49b 100644
--- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowUtil.qll
+++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowUtil.qll
@@ -5,280 +5,13 @@
private import java
private import DataFlowPrivate
private import semmle.code.java.dataflow.SSA
-private import semmle.code.java.dataflow.TypeFlow
private import semmle.code.java.controlflow.Guards
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.dataflow.FlowSteps
+private import semmle.code.java.dataflow.FlowSummary
import semmle.code.java.dataflow.InstanceAccess
-
-cached
-private newtype TNode =
- TExprNode(Expr e) {
- not e.getType() instanceof VoidType and
- not e.getParent*() instanceof Annotation
- } or
- TExplicitParameterNode(Parameter p) { exists(p.getCallable().getBody()) } or
- TImplicitVarargsArray(Call c) {
- c.getCallee().isVarargs() and
- not exists(Argument arg | arg.getCall() = c and arg.isExplicitVarargsArray())
- } or
- TInstanceParameterNode(Callable c) { exists(c.getBody()) and not c.isStatic() } or
- TImplicitInstanceAccess(InstanceAccessExt ia) { not ia.isExplicit(_) } or
- TMallocNode(ClassInstanceExpr cie) or
- TExplicitExprPostUpdate(Expr e) {
- explicitInstanceArgument(_, e)
- or
- e instanceof Argument and not e.getType() instanceof ImmutableType
- or
- exists(FieldAccess fa | fa.getField() instanceof InstanceField and e = fa.getQualifier())
- or
- exists(ArrayAccess aa | e = aa.getArray())
- } or
- TImplicitExprPostUpdate(InstanceAccessExt ia) {
- implicitInstanceArgument(_, ia)
- or
- exists(FieldAccess fa |
- fa.getField() instanceof InstanceField and ia.isImplicitFieldQualifier(fa)
- )
- }
-
-/**
- * An element, viewed as a node in a data flow graph. Either an expression,
- * a parameter, or an implicit varargs array creation.
- */
-class Node extends TNode {
- /** Gets a textual representation of this element. */
- string toString() { none() }
-
- /** Gets the source location for this element. */
- Location getLocation() { none() }
-
- /** Gets the expression corresponding to this node, if any. */
- Expr asExpr() { result = this.(ExprNode).getExpr() }
-
- /** Gets the parameter corresponding to this node, if any. */
- Parameter asParameter() { result = this.(ExplicitParameterNode).getParameter() }
-
- /** Gets the type of this node. */
- Type getType() {
- result = this.asExpr().getType()
- or
- result = this.asParameter().getType()
- or
- exists(Parameter p |
- result = p.getType() and
- p.isVarargs() and
- p = this.(ImplicitVarargsArray).getCall().getCallee().getAParameter()
- )
- or
- result = this.(InstanceParameterNode).getCallable().getDeclaringType()
- or
- result = this.(ImplicitInstanceAccess).getInstanceAccess().getType()
- or
- result = this.(MallocNode).getClassInstanceExpr().getType()
- or
- result = this.(ImplicitPostUpdateNode).getPreUpdateNode().getType()
- }
-
- /** Gets the callable in which this node occurs. */
- Callable getEnclosingCallable() {
- result = this.asExpr().getEnclosingCallable() or
- result = this.asParameter().getCallable() or
- result = this.(ImplicitVarargsArray).getCall().getEnclosingCallable() or
- result = this.(InstanceParameterNode).getCallable() or
- result = this.(ImplicitInstanceAccess).getInstanceAccess().getEnclosingCallable() or
- result = this.(MallocNode).getClassInstanceExpr().getEnclosingCallable() or
- result = this.(ImplicitPostUpdateNode).getPreUpdateNode().getEnclosingCallable()
- }
-
- private Type getImprovedTypeBound() {
- exprTypeFlow(this.asExpr(), result, _) or
- result = this.(ImplicitPostUpdateNode).getPreUpdateNode().getImprovedTypeBound()
- }
-
- /**
- * Gets an upper bound on the type of this node.
- */
- Type getTypeBound() {
- result = getImprovedTypeBound()
- or
- result = getType() and not exists(getImprovedTypeBound())
- }
-
- /**
- * Holds if this element is at the specified location.
- * The location spans column `startcolumn` of line `startline` to
- * column `endcolumn` of line `endline` in file `filepath`.
- * For more information, see
- * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
- */
- predicate hasLocationInfo(
- string filepath, int startline, int startcolumn, int endline, int endcolumn
- ) {
- getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
- }
-}
-
-/**
- * An expression, viewed as a node in a data flow graph.
- */
-class ExprNode extends Node, TExprNode {
- Expr expr;
-
- ExprNode() { this = TExprNode(expr) }
-
- override string toString() { result = expr.toString() }
-
- override Location getLocation() { result = expr.getLocation() }
-
- /** Gets the expression corresponding to this node. */
- Expr getExpr() { result = expr }
-}
-
-/** Gets the node corresponding to `e`. */
-ExprNode exprNode(Expr e) { result.getExpr() = e }
-
-/** An explicit or implicit parameter. */
-abstract class ParameterNode extends Node {
- /**
- * Holds if this node is the parameter of `c` at the specified (zero-based)
- * position. The implicit `this` parameter is considered to have index `-1`.
- */
- abstract predicate isParameterOf(Callable c, int pos);
-}
-
-/**
- * A parameter, viewed as a node in a data flow graph.
- */
-class ExplicitParameterNode extends ParameterNode, TExplicitParameterNode {
- Parameter param;
-
- ExplicitParameterNode() { this = TExplicitParameterNode(param) }
-
- override string toString() { result = param.toString() }
-
- override Location getLocation() { result = param.getLocation() }
-
- /** Gets the parameter corresponding to this node. */
- Parameter getParameter() { result = param }
-
- override predicate isParameterOf(Callable c, int pos) { c.getParameter(pos) = param }
-}
-
-/** Gets the node corresponding to `p`. */
-ExplicitParameterNode parameterNode(Parameter p) { result.getParameter() = p }
-
-/**
- * An implicit varargs array creation expression.
- *
- * A call `f(x1, x2)` to a method `f(A... xs)` desugars to `f(new A[]{x1, x2})`,
- * and this node corresponds to such an implicit array creation.
- */
-class ImplicitVarargsArray extends Node, TImplicitVarargsArray {
- Call call;
-
- ImplicitVarargsArray() { this = TImplicitVarargsArray(call) }
-
- override string toString() { result = "new ..[] { .. }" }
-
- override Location getLocation() { result = call.getLocation() }
-
- /** Gets the call containing this varargs array creation argument. */
- Call getCall() { result = call }
-}
-
-/**
- * An instance parameter for an instance method or constructor.
- */
-class InstanceParameterNode extends ParameterNode, TInstanceParameterNode {
- Callable callable;
-
- InstanceParameterNode() { this = TInstanceParameterNode(callable) }
-
- override string toString() { result = "parameter this" }
-
- override Location getLocation() { result = callable.getLocation() }
-
- /** Gets the callable containing this `this` parameter. */
- Callable getCallable() { result = callable }
-
- override predicate isParameterOf(Callable c, int pos) { callable = c and pos = -1 }
-}
-
-/**
- * An implicit read of `this` or `A.this`.
- */
-class ImplicitInstanceAccess extends Node, TImplicitInstanceAccess {
- InstanceAccessExt ia;
-
- ImplicitInstanceAccess() { this = TImplicitInstanceAccess(ia) }
-
- override string toString() { result = ia.toString() }
-
- override Location getLocation() { result = ia.getLocation() }
-
- InstanceAccessExt getInstanceAccess() { result = ia }
-}
-
-/**
- * A node that corresponds to the value of a `ClassInstanceExpr` before the
- * constructor has run.
- */
-private class MallocNode extends Node, TMallocNode {
- ClassInstanceExpr cie;
-
- MallocNode() { this = TMallocNode(cie) }
-
- override string toString() { result = cie.toString() + " [pre constructor]" }
-
- override Location getLocation() { result = cie.getLocation() }
-
- ClassInstanceExpr getClassInstanceExpr() { result = cie }
-}
-
-/**
- * A node associated with an object after an operation that might have
- * changed its state.
- *
- * This can be either the argument to a callable after the callable returns
- * (which might have mutated the argument), or the qualifier of a field after
- * an update to the field.
- *
- * Nodes corresponding to AST elements, for example `ExprNode`, usually refer
- * to the value before the update with the exception of `ClassInstanceExpr`,
- * which represents the value after the constructor has run.
- */
-abstract class PostUpdateNode extends Node {
- /**
- * Gets the node before the state update.
- */
- abstract Node getPreUpdateNode();
-}
-
-private class NewExpr extends PostUpdateNode, TExprNode {
- NewExpr() { exists(ClassInstanceExpr cie | this = TExprNode(cie)) }
-
- override Node getPreUpdateNode() { this = TExprNode(result.(MallocNode).getClassInstanceExpr()) }
-}
-
-/**
- * A `PostUpdateNode` that is not a `ClassInstanceExpr`.
- */
-abstract private class ImplicitPostUpdateNode extends PostUpdateNode {
- override Location getLocation() { result = getPreUpdateNode().getLocation() }
-
- override string toString() { result = getPreUpdateNode().toString() + " [post update]" }
-}
-
-private class ExplicitExprPostUpdate extends ImplicitPostUpdateNode, TExplicitExprPostUpdate {
- override Node getPreUpdateNode() { this = TExplicitExprPostUpdate(result.asExpr()) }
-}
-
-private class ImplicitExprPostUpdate extends ImplicitPostUpdateNode, TImplicitExprPostUpdate {
- override Node getPreUpdateNode() {
- this = TImplicitExprPostUpdate(result.(ImplicitInstanceAccess).getInstanceAccess())
- }
-}
+private import FlowSummaryImpl as FlowSummaryImpl
+import DataFlowNodes::Public
/** Holds if `n` is an access to an unqualified `this` at `cfgnode`. */
private predicate thisAccess(Node n, ControlFlowNode cfgnode) {
@@ -363,7 +96,13 @@ predicate hasNonlocalValue(FieldRead fr) {
/**
* Holds if data can flow from `node1` to `node2` in one local step.
*/
-predicate localFlowStep(Node node1, Node node2) { simpleLocalFlowStep(node1, node2) }
+predicate localFlowStep(Node node1, Node node2) {
+ simpleLocalFlowStep(node1, node2)
+ or
+ // Simple flow through library code is included in the exposed local
+ // step relation, even though flow is technically inter-procedural
+ FlowSummaryImpl::Private::Steps::summaryThroughStep(node1, node2, true)
+}
/**
* INTERNAL: do not use.
@@ -411,33 +150,8 @@ predicate simpleLocalFlowStep(Node node1, Node node2) {
node2.asExpr() = ma and
node1.(ArgumentNode).argumentOf(ma, argNo)
)
-}
-
-/**
- * Gets the node that occurs as the qualifier of `fa`.
- */
-Node getFieldQualifier(FieldAccess fa) {
- fa.getField() instanceof InstanceField and
- (
- result.asExpr() = fa.getQualifier() or
- result.(ImplicitInstanceAccess).getInstanceAccess().isImplicitFieldQualifier(fa)
- )
-}
-
-private predicate explicitInstanceArgument(Call call, Expr instarg) {
- call instanceof MethodAccess and instarg = call.getQualifier() and not call.getCallee().isStatic()
-}
-
-private predicate implicitInstanceArgument(Call call, InstanceAccessExt ia) {
- ia.isImplicitMethodQualifier(call) or
- ia.isImplicitThisConstructorArgument(call)
-}
-
-/** Gets the instance argument of a non-static call. */
-Node getInstanceArgument(Call call) {
- result.(MallocNode).getClassInstanceExpr() = call or
- explicitInstanceArgument(call, result.asExpr()) or
- implicitInstanceArgument(call, result.(ImplicitInstanceAccess).getInstanceAccess())
+ or
+ FlowSummaryImpl::Private::Steps::summaryLocalStep(node1, node2, true)
}
/**
diff --git a/java/ql/src/semmle/code/java/dataflow/internal/FlowSummaryImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/FlowSummaryImpl.qll
new file mode 100644
index 000000000000..3c8c94c913c3
--- /dev/null
+++ b/java/ql/src/semmle/code/java/dataflow/internal/FlowSummaryImpl.qll
@@ -0,0 +1,674 @@
+/**
+ * Provides classes and predicates for defining flow summaries.
+ *
+ * The definitions in this file are language-independent, and language-specific
+ * definitions are passed in via the `DataFlowImplSpecific` and
+ * `FlowSummaryImplSpecific` modules.
+ */
+
+private import FlowSummaryImplSpecific
+private import DataFlowImplSpecific::Private
+private import DataFlowImplSpecific::Public
+
+/** Provides classes and predicates for defining flow summaries. */
+module Public {
+ private import Private
+
+ /**
+ * A component used in a flow summary.
+ *
+ * Either a parameter or an argument at a given position, a specific
+ * content type, or a return kind.
+ */
+ class SummaryComponent extends TSummaryComponent {
+ /** Gets a textual representation of this summary component. */
+ string toString() {
+ exists(Content c | this = TContentSummaryComponent(c) and result = c.toString())
+ or
+ exists(int i | this = TParameterSummaryComponent(i) and result = "parameter " + i)
+ or
+ exists(int i | this = TArgumentSummaryComponent(i) and result = "argument " + i)
+ or
+ exists(ReturnKind rk | this = TReturnSummaryComponent(rk) and result = "return (" + rk + ")")
+ }
+ }
+
+ /** Provides predicates for constructing summary components. */
+ module SummaryComponent {
+ /** Gets a summary component for content `c`. */
+ SummaryComponent content(Content c) { result = TContentSummaryComponent(c) }
+
+ /** Gets a summary component for parameter `i`. */
+ SummaryComponent parameter(int i) { result = TParameterSummaryComponent(i) }
+
+ /** Gets a summary component for argument `i`. */
+ SummaryComponent argument(int i) { result = TArgumentSummaryComponent(i) }
+
+ /** Gets a summary component for a return of kind `rk`. */
+ SummaryComponent return(ReturnKind rk) { result = TReturnSummaryComponent(rk) }
+ }
+
+ /**
+ * A (non-empty) stack of summary components.
+ *
+ * A stack is used to represent where data is read from (input) or where it
+ * is written to (output). For example, an input stack `[Field f, Argument 0]`
+ * means that data is read from field `f` from the `0`th argument, while an
+ * output stack `[Field g, Return]` means that data is written to the field
+ * `g` of the returned object.
+ */
+ class SummaryComponentStack extends TSummaryComponentStack {
+ /** Gets the head of this stack. */
+ SummaryComponent head() {
+ this = TSingletonSummaryComponentStack(result) or
+ this = TConsSummaryComponentStack(result, _)
+ }
+
+ /** Gets the tail of this stack, if any. */
+ SummaryComponentStack tail() { this = TConsSummaryComponentStack(_, result) }
+
+ /** Gets the length of this stack. */
+ int length() {
+ this = TSingletonSummaryComponentStack(_) and result = 1
+ or
+ result = 1 + this.tail().length()
+ }
+
+ /** Gets the stack obtained by dropping the first `i` elements, if any. */
+ SummaryComponentStack drop(int i) {
+ i = 0 and result = this
+ or
+ result = this.tail().drop(i - 1)
+ }
+
+ /** Holds if this stack contains summary component `c`. */
+ predicate contains(SummaryComponent c) { c = this.drop(_).head() }
+
+ /** Gets a textual representation of this stack. */
+ string toString() {
+ exists(SummaryComponent head, SummaryComponentStack tail |
+ head = this.head() and
+ tail = this.tail() and
+ result = head + " of " + tail
+ )
+ or
+ exists(SummaryComponent c |
+ this = TSingletonSummaryComponentStack(c) and
+ result = c.toString()
+ )
+ }
+ }
+
+ /** Provides predicates for constructing stacks of summary components. */
+ module SummaryComponentStack {
+ /** Gets a singleton stack containing `c`. */
+ SummaryComponentStack singleton(SummaryComponent c) {
+ result = TSingletonSummaryComponentStack(c)
+ }
+
+ /**
+ * Gets the stack obtained by pushing `head` onto `tail`.
+ *
+ * Make sure to override `RequiredSummaryComponentStack::required()` in order
+ * to ensure that the constructed stack exists.
+ */
+ SummaryComponentStack push(SummaryComponent head, SummaryComponentStack tail) {
+ result = TConsSummaryComponentStack(head, tail)
+ }
+
+ /** Gets a singleton stack for argument `i`. */
+ SummaryComponentStack argument(int i) { result = singleton(SummaryComponent::argument(i)) }
+
+ /** Gets a singleton stack representing a return of kind `rk`. */
+ SummaryComponentStack return(ReturnKind rk) { result = singleton(SummaryComponent::return(rk)) }
+ }
+
+ /**
+ * A class that exists for QL technical reasons only (the IPA type used
+ * to represent component stacks needs to be bounded).
+ */
+ abstract class RequiredSummaryComponentStack extends SummaryComponentStack {
+ /**
+ * Holds if the stack obtained by pushing `head` onto `tail` is required.
+ */
+ abstract predicate required(SummaryComponent c);
+ }
+
+ /** A callable with a flow summary. */
+ abstract class SummarizedCallable extends DataFlowCallable {
+ /**
+ * Holds if data may flow from `input` to `output` through this callable.
+ *
+ * `preservesValue` indicates whether this is a value-preserving step
+ * or a taint-step.
+ *
+ * Input specifications are restricted to stacks that end with
+ * `SummaryComponent::argument(_)`, preceded by zero or more
+ * `SummaryComponent::return(_)` or `SummaryComponent::content(_)` components.
+ *
+ * Output specifications are restricted to stacks that end with
+ * `SummaryComponent::return(_)` or `SummaryComponent::argument(_)`.
+ *
+ * Output stacks ending with `SummaryComponent::return(_)` can be preceded by zero
+ * or more `SummaryComponent::content(_)` components.
+ *
+ * Output stacks ending with `SummaryComponent::argument(_)` can be preceded by an
+ * optional `SummaryComponent::parameter(_)` component, which in turn can be preceded
+ * by zero or more `SummaryComponent::content(_)` components.
+ */
+ pragma[nomagic]
+ predicate propagatesFlow(
+ SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
+ ) {
+ none()
+ }
+
+ /**
+ * Holds if values stored inside `content` are cleared on objects passed as
+ * the `i`th argument to this callable.
+ */
+ pragma[nomagic]
+ predicate clearsContent(int i, Content content) { none() }
+ }
+}
+
+/**
+ * Provides predicates for compiling flow summaries down to atomic local steps,
+ * read steps, and store steps.
+ */
+module Private {
+ private import Public
+ private import DataFlowImplCommon as DataFlowImplCommon
+
+ newtype TSummaryComponent =
+ TContentSummaryComponent(Content c) or
+ TParameterSummaryComponent(int i) { parameterPosition(i) } or
+ TArgumentSummaryComponent(int i) { parameterPosition(i) } or
+ TReturnSummaryComponent(ReturnKind rk)
+
+ newtype TSummaryComponentStack =
+ TSingletonSummaryComponentStack(SummaryComponent c) or
+ TConsSummaryComponentStack(SummaryComponent head, SummaryComponentStack tail) {
+ tail.(RequiredSummaryComponentStack).required(head)
+ }
+
+ pragma[nomagic]
+ private predicate summary(
+ SummarizedCallable c, SummaryComponentStack input, SummaryComponentStack output,
+ boolean preservesValue
+ ) {
+ c.propagatesFlow(input, output, preservesValue)
+ }
+
+ private newtype TSummaryNodeState =
+ TSummaryNodeInputState(SummaryComponentStack s) {
+ exists(SummaryComponentStack input |
+ summary(_, input, _, _) and
+ s = input.drop(_)
+ )
+ } or
+ TSummaryNodeOutputState(SummaryComponentStack s) {
+ exists(SummaryComponentStack output |
+ summary(_, _, output, _) and
+ s = output.drop(_)
+ )
+ }
+
+ /**
+ * A state used to break up (complex) flow summaries into atomic flow steps.
+ * For a flow summary
+ *
+ * ```ql
+ * propagatesFlow(
+ * SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
+ * )
+ * ```
+ *
+ * the following states are used:
+ *
+ * - `TSummaryNodeInputState(SummaryComponentStack s)`:
+ * this state represents that the components in `s` _have been read_ from the
+ * input.
+ * - `TSummaryNodeOutputState(SummaryComponentStack s)`:
+ * this state represents that the components in `s` _remain to be written_ to
+ * the output.
+ */
+ class SummaryNodeState extends TSummaryNodeState {
+ /** Holds if this state is a valid input state for `c`. */
+ pragma[nomagic]
+ predicate isInputState(SummarizedCallable c, SummaryComponentStack s) {
+ this = TSummaryNodeInputState(s) and
+ exists(SummaryComponentStack input |
+ summary(c, input, _, _) and
+ s = input.drop(_)
+ )
+ }
+
+ /** Holds if this state is a valid output state for `c`. */
+ pragma[nomagic]
+ predicate isOutputState(SummarizedCallable c, SummaryComponentStack s) {
+ this = TSummaryNodeOutputState(s) and
+ exists(SummaryComponentStack output |
+ summary(c, _, output, _) and
+ s = output.drop(_)
+ )
+ }
+
+ /** Gets a textual representation of this state. */
+ string toString() {
+ exists(SummaryComponentStack s |
+ this = TSummaryNodeInputState(s) and
+ result = "read: " + s
+ )
+ or
+ exists(SummaryComponentStack s |
+ this = TSummaryNodeOutputState(s) and
+ result = "to write: " + s
+ )
+ }
+ }
+
+ /**
+ * Holds if `state` represents having read the `i`th argument for `c`. In this case
+ * we are not synthesizing a data-flow node, but instead assume that a relevant
+ * parameter node already exists.
+ */
+ private predicate parameterReadState(SummarizedCallable c, SummaryNodeState state, int i) {
+ state.isInputState(c, SummaryComponentStack::argument(i))
+ }
+
+ /**
+ * Holds if a synthesized summary node is needed for the state `state` in summarized
+ * callable `c`.
+ */
+ predicate summaryNodeRange(SummarizedCallable c, SummaryNodeState state) {
+ state.isInputState(c, _) and
+ not parameterReadState(c, state, _)
+ or
+ state.isOutputState(c, _)
+ }
+
+ pragma[noinline]
+ private Node summaryNodeInputState(SummarizedCallable c, SummaryComponentStack s) {
+ exists(SummaryNodeState state | state.isInputState(c, s) |
+ result = summaryNode(c, state)
+ or
+ exists(int i |
+ parameterReadState(c, state, i) and
+ result.(ParameterNode).isParameterOf(c, i)
+ )
+ )
+ }
+
+ pragma[noinline]
+ private Node summaryNodeOutputState(SummarizedCallable c, SummaryComponentStack s) {
+ exists(SummaryNodeState state |
+ state.isOutputState(c, s) and
+ result = summaryNode(c, state)
+ )
+ }
+
+ /**
+ * Holds if a write targets `post`, which is a post-update node for the `i`th
+ * parameter of `c`.
+ */
+ private predicate isParameterPostUpdate(Node post, SummarizedCallable c, int i) {
+ post = summaryNodeOutputState(c, SummaryComponentStack::argument(i))
+ }
+
+ /** Holds if a parameter node is required for the `i`th parameter of `c`. */
+ predicate summaryParameterNodeRange(SummarizedCallable c, int i) {
+ parameterReadState(c, _, i)
+ or
+ isParameterPostUpdate(_, c, i)
+ }
+
+ private predicate callbackOutput(
+ SummarizedCallable c, SummaryComponentStack s, Node receiver, ReturnKind rk
+ ) {
+ any(SummaryNodeState state).isInputState(c, s) and
+ s.head() = TReturnSummaryComponent(rk) and
+ receiver = summaryNodeInputState(c, s.drop(1))
+ }
+
+ private Node pre(Node post) {
+ summaryPostUpdateNode(post, result)
+ or
+ not summaryPostUpdateNode(post, _) and
+ result = post
+ }
+
+ private predicate callbackInput(
+ SummarizedCallable c, SummaryComponentStack s, Node receiver, int i
+ ) {
+ any(SummaryNodeState state).isOutputState(c, s) and
+ s.head() = TParameterSummaryComponent(i) and
+ receiver = pre(summaryNodeOutputState(c, s.drop(1)))
+ }
+
+ /** Holds if a call targeting `receiver` should be synthesized inside `c`. */
+ predicate summaryCallbackRange(SummarizedCallable c, Node receiver) {
+ callbackOutput(c, _, receiver, _)
+ or
+ callbackInput(c, _, receiver, _)
+ }
+
+ /**
+ * Gets the type of synthesized summary node `n`.
+ *
+ * The type is computed based on the language-specific predicates
+ * `getContentType()`, `getReturnType()`, `getCallbackParameterType()`, and
+ * `getCallbackReturnType()`.
+ */
+ DataFlowType summaryNodeType(Node n) {
+ exists(Node pre |
+ summaryPostUpdateNode(n, pre) and
+ result = getNodeType(pre)
+ )
+ or
+ exists(SummarizedCallable c, SummaryComponentStack s, SummaryComponent head | head = s.head() |
+ n = summaryNodeInputState(c, s) and
+ (
+ exists(Content cont |
+ head = TContentSummaryComponent(cont) and result = getContentType(cont)
+ )
+ or
+ exists(ReturnKind rk |
+ head = TReturnSummaryComponent(rk) and
+ result = getCallbackReturnType(getNodeType(summaryNodeInputState(c, s.drop(1))), rk)
+ )
+ )
+ or
+ n = summaryNodeOutputState(c, s) and
+ (
+ exists(Content cont |
+ head = TContentSummaryComponent(cont) and result = getContentType(cont)
+ )
+ or
+ s.length() = 1 and
+ exists(ReturnKind rk |
+ head = TReturnSummaryComponent(rk) and
+ result = getReturnType(c, rk)
+ )
+ or
+ exists(int i | head = TParameterSummaryComponent(i) |
+ result = getCallbackParameterType(getNodeType(summaryNodeOutputState(c, s.drop(1))), i)
+ )
+ )
+ )
+ }
+
+ /** Holds if summary node `out` contains output of kind `rk` from call `c`. */
+ predicate summaryOutNode(DataFlowCall c, Node out, ReturnKind rk) {
+ exists(SummarizedCallable callable, SummaryComponentStack s, Node receiver |
+ callbackOutput(callable, s, receiver, rk) and
+ out = summaryNodeInputState(callable, s) and
+ c = summaryDataFlowCall(receiver)
+ )
+ }
+
+ /** Holds if summary node `arg` is the `i`th argument of call `c`. */
+ predicate summaryArgumentNode(DataFlowCall c, Node arg, int i) {
+ exists(SummarizedCallable callable, SummaryComponentStack s, Node receiver |
+ callbackInput(callable, s, receiver, i) and
+ arg = summaryNodeOutputState(callable, s) and
+ c = summaryDataFlowCall(receiver)
+ )
+ }
+
+ /** Holds if summary node `post` is a post-update node with pre-update node `pre`. */
+ predicate summaryPostUpdateNode(Node post, ParameterNode pre) {
+ exists(SummarizedCallable c, int i |
+ isParameterPostUpdate(post, c, i) and
+ pre.isParameterOf(c, i)
+ )
+ }
+
+ /** Holds if summary node `ret` is a return node of kind `rk`. */
+ predicate summaryReturnNode(Node ret, ReturnKind rk) {
+ exists(SummarizedCallable callable, SummaryComponentStack s |
+ ret = summaryNodeOutputState(callable, s) and
+ s = TSingletonSummaryComponentStack(TReturnSummaryComponent(rk))
+ )
+ }
+
+ /** Provides a compilation of flow summaries to atomic data-flow steps. */
+ module Steps {
+ /**
+ * Holds if there is a local step from `pred` to `succ`, which is synthesized
+ * from a flow summary.
+ */
+ predicate summaryLocalStep(Node pred, Node succ, boolean preservesValue) {
+ exists(
+ SummarizedCallable c, SummaryComponentStack inputContents,
+ SummaryComponentStack outputContents
+ |
+ summary(c, inputContents, outputContents, preservesValue) and
+ pred = summaryNodeInputState(c, inputContents) and
+ succ = summaryNodeOutputState(c, outputContents)
+ )
+ }
+
+ /**
+ * Holds if there is a read step of content `c` from `pred` to `succ`, which
+ * is synthesized from a flow summary.
+ */
+ predicate summaryReadStep(Node pred, Content c, Node succ) {
+ exists(SummarizedCallable sc, SummaryComponentStack s |
+ pred = summaryNodeInputState(sc, s.drop(1)) and
+ succ = summaryNodeInputState(sc, s) and
+ SummaryComponent::content(c) = s.head()
+ )
+ }
+
+ /**
+ * Holds if there is a store step of content `c` from `pred` to `succ`, which
+ * is synthesized from a flow summary.
+ */
+ predicate summaryStoreStep(Node pred, Content c, Node succ) {
+ exists(SummarizedCallable sc, SummaryComponentStack s |
+ pred = summaryNodeOutputState(sc, s) and
+ succ = summaryNodeOutputState(sc, s.drop(1)) and
+ SummaryComponent::content(c) = s.head()
+ )
+ }
+
+ /**
+ * Holds if values stored inside content `c` are cleared when passed as
+ * input of type `input` in `call`.
+ */
+ predicate summaryClearsContent(ArgumentNode arg, Content c) {
+ exists(DataFlowCall call, int i |
+ viableCallable(call).(SummarizedCallable).clearsContent(i, c) and
+ arg.argumentOf(call, i)
+ )
+ }
+
+ pragma[nomagic]
+ private ParameterNode summaryArgParam(
+ ArgumentNode arg, DataFlowImplCommon::ReturnKindExt rk, DataFlowImplCommon::OutNodeExt out
+ ) {
+ exists(DataFlowCall call, int pos, SummarizedCallable callable |
+ arg.argumentOf(call, pos) and
+ viableCallable(call) = callable and
+ result.isParameterOf(callable, pos) and
+ out = rk.getAnOutNode(call)
+ )
+ }
+
+ /**
+ * Holds if `arg` flows to `out` using a simple flow summary, that is, a flow
+ * summary without reads and stores.
+ *
+ * NOTE: This step should not be used in global data-flow/taint-tracking, but may
+ * be useful to include in the exposed local data-flow/taint-tracking relations.
+ */
+ predicate summaryThroughStep(ArgumentNode arg, Node out, boolean preservesValue) {
+ exists(DataFlowImplCommon::ReturnKindExt rk, DataFlowImplCommon::ReturnNodeExt ret |
+ summaryLocalStep(summaryArgParam(arg, rk, out), ret, preservesValue) and
+ ret.getKind() = rk
+ )
+ }
+
+ /**
+ * Holds if there is a read(+taint) of `c` from `arg` to `out` using a
+ * flow summary.
+ *
+ * NOTE: This step should not be used in global data-flow/taint-tracking, but may
+ * be useful to include in the exposed local data-flow/taint-tracking relations.
+ */
+ predicate summaryGetterStep(ArgumentNode arg, Content c, Node out) {
+ exists(DataFlowImplCommon::ReturnKindExt rk, Node mid, DataFlowImplCommon::ReturnNodeExt ret |
+ summaryReadStep(summaryArgParam(arg, rk, out), c, mid) and
+ summaryLocalStep(mid, ret, _) and
+ ret.getKind() = rk
+ )
+ }
+
+ /**
+ * Holds if there is a (taint+)store of `arg` into content `c` of `out` using a
+ * flow summary.
+ *
+ * NOTE: This step should not be used in global data-flow/taint-tracking, but may
+ * be useful to include in the exposed local data-flow/taint-tracking relations.
+ */
+ predicate summarySetterStep(ArgumentNode arg, Content c, Node out) {
+ exists(DataFlowImplCommon::ReturnKindExt rk, Node mid, DataFlowImplCommon::ReturnNodeExt ret |
+ summaryLocalStep(summaryArgParam(arg, rk, out), mid, _) and
+ summaryStoreStep(mid, c, ret) and
+ ret.getKind() = rk
+ )
+ }
+
+ /**
+ * Holds if data is written into content `c` of argument `arg` using a flow summary.
+ *
+ * Depending on the type of `c`, this predicate may be relevant to include in the
+ * definition of `clearsContent()`.
+ */
+ predicate summaryStoresIntoArg(Content c, Node arg) {
+ exists(
+ DataFlowImplCommon::ParamUpdateReturnKind rk, DataFlowImplCommon::ReturnNodeExt ret,
+ PostUpdateNode out
+ |
+ exists(DataFlowCall call, SummarizedCallable callable |
+ DataFlowImplCommon::getNodeEnclosingCallable(ret) = callable and
+ viableCallable(call) = callable and
+ summaryStoreStep(_, c, ret) and
+ ret.getKind() = pragma[only_bind_into](rk) and
+ out = rk.getAnOutNode(call) and
+ arg = out.getPreUpdateNode()
+ )
+ )
+ }
+ }
+
+ /**
+ * Provides a means of translating externally (e.g., CSV) defined flow
+ * summaries into a `SummarizedCallable`s.
+ */
+ module External {
+ /**
+ * Provides a means of translating an externally (e.g., CSV) defined flow
+ * summary into a `SummarizedCallable`.
+ */
+ abstract class ExternalSummaryCompilation extends string {
+ bindingset[this]
+ ExternalSummaryCompilation() { any() }
+
+ /** Holds if this flow summary is for callable `c`. */
+ abstract predicate callable(DataFlowCallable c, boolean preservesValue);
+
+ /** Holds if the `i`th input component is `c`. */
+ abstract predicate input(int i, SummaryComponent c);
+
+ /** Holds if the `i`th output component is `c`. */
+ abstract predicate output(int i, SummaryComponent c);
+
+ /**
+ * Holds if the input components starting from index `i` translate into `suffix`.
+ */
+ final predicate translateInput(int i, SummaryComponentStack suffix) {
+ exists(SummaryComponent comp | this.input(i, comp) |
+ i = max(int j | this.input(j, _)) and
+ suffix = TSingletonSummaryComponentStack(comp)
+ or
+ exists(TSummaryComponent head, SummaryComponentStack tail |
+ this.translateInputCons(i, head, tail) and
+ suffix = TConsSummaryComponentStack(head, tail)
+ )
+ )
+ }
+
+ final predicate translateInputCons(int i, SummaryComponent head, SummaryComponentStack tail) {
+ this.input(i, head) and
+ this.translateInput(i + 1, tail)
+ }
+
+ /**
+ * Holds if the output components starting from index `i` translate into `suffix`.
+ */
+ predicate translateOutput(int i, SummaryComponentStack suffix) {
+ exists(SummaryComponent comp | this.output(i, comp) |
+ i = max(int j | this.output(j, _)) and
+ suffix = TSingletonSummaryComponentStack(comp)
+ or
+ exists(TSummaryComponent head, SummaryComponentStack tail |
+ this.translateOutputCons(i, head, tail) and
+ suffix = TConsSummaryComponentStack(head, tail)
+ )
+ )
+ }
+
+ predicate translateOutputCons(int i, SummaryComponent head, SummaryComponentStack tail) {
+ this.output(i, head) and
+ this.translateOutput(i + 1, tail)
+ }
+ }
+
+ private class ExternalRequiredSummaryComponentStack extends RequiredSummaryComponentStack {
+ private SummaryComponent head;
+
+ ExternalRequiredSummaryComponentStack() {
+ any(ExternalSummaryCompilation s).translateInputCons(_, head, this) or
+ any(ExternalSummaryCompilation s).translateOutputCons(_, head, this)
+ }
+
+ override predicate required(SummaryComponent c) { c = head }
+ }
+
+ class ExternalSummarizedCallableAdaptor extends SummarizedCallable {
+ ExternalSummarizedCallableAdaptor() { any(ExternalSummaryCompilation s).callable(this, _) }
+
+ override predicate propagatesFlow(
+ SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
+ ) {
+ exists(ExternalSummaryCompilation s |
+ s.callable(this, preservesValue) and
+ s.translateInput(0, input) and
+ s.translateOutput(0, output)
+ )
+ }
+ }
+ }
+
+ /** Provides a query predicate for outputting a set of relevant flow summaries. */
+ module TestOutput {
+ /** A flow summary to include in the `summary/3` query predicate. */
+ abstract class RelevantSummarizedCallable extends SummarizedCallable {
+ /** Gets the string representation of this callable used by `summary/3`. */
+ string getFullString() { result = this.toString() }
+ }
+
+ /** A query predicate for outputting flow summaries in QL tests. */
+ query predicate summary(string callable, string flow, boolean preservesValue) {
+ exists(
+ RelevantSummarizedCallable c, SummaryComponentStack input, SummaryComponentStack output
+ |
+ callable = c.getFullString() and
+ c.propagatesFlow(input, output, preservesValue) and
+ flow = input + " -> " + output
+ )
+ }
+ }
+}
diff --git a/java/ql/src/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll b/java/ql/src/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll
new file mode 100644
index 000000000000..dae0571f0fa8
--- /dev/null
+++ b/java/ql/src/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll
@@ -0,0 +1,51 @@
+/**
+ * Provides Java specific classes and predicates for definining flow summaries.
+ */
+
+private import java
+private import DataFlowPrivate
+private import DataFlowUtil
+private import FlowSummaryImpl::Private
+private import FlowSummaryImpl::Public
+
+private module FlowSummaries {
+ private import semmle.code.java.dataflow.FlowSummary as F
+}
+
+/** Holds is `i` is a valid parameter position. */
+predicate parameterPosition(int i) { i in [-1 .. any(Parameter p).getPosition()] }
+
+/** Gets the synthesized summary data-flow node for the given values. */
+Node summaryNode(SummarizedCallable c, SummaryNodeState state) { result = getSummaryNode(c, state) }
+
+/** Gets the synthesized data-flow call for `receiver`. */
+DataFlowCall summaryDataFlowCall(Node receiver) { none() }
+
+/** Gets the type of content `c`. */
+DataFlowType getContentType(Content c) {
+ result = getErasedRepr(c.(FieldContent).getField().getType())
+ or
+ c instanceof CollectionContent and
+ result instanceof TypeObject
+ or
+ c instanceof ArrayContent and
+ result instanceof TypeObject
+}
+
+/** Gets the return type of kind `rk` for callable `c`. */
+DataFlowType getReturnType(SummarizedCallable c, ReturnKind rk) {
+ result = getErasedRepr(c.getReturnType()) and
+ exists(rk)
+}
+
+/**
+ * Gets the type of the `i`th parameter in a synthesized call that targets a
+ * callback of type `t`.
+ */
+DataFlowType getCallbackParameterType(DataFlowType t, int i) { none() }
+
+/**
+ * Gets the return type of kind `rk` in a synthesized call that targets a
+ * callback of type `t`.
+ */
+DataFlowType getCallbackReturnType(DataFlowType t, ReturnKind rk) { none() }
diff --git a/java/ql/src/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll b/java/ql/src/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll
index d7cf0a8440ae..0f20ffe62dbe 100644
--- a/java/ql/src/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll
+++ b/java/ql/src/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll
@@ -13,6 +13,7 @@ private import semmle.code.java.frameworks.Networking
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.dataflow.internal.DataFlowPrivate
import semmle.code.java.dataflow.FlowSteps
+private import FlowSummaryImpl as FlowSummaryImpl
/**
* Holds if taint can flow from `src` to `sink` in zero or more
@@ -33,7 +34,10 @@ predicate localExprTaint(Expr src, Expr sink) {
*/
predicate localTaintStep(DataFlow::Node src, DataFlow::Node sink) {
DataFlow::localFlowStep(src, sink) or
- localAdditionalTaintStep(src, sink)
+ localAdditionalTaintStep(src, sink) or
+ // Simple flow through library code is included in the exposed local
+ // step relation, even though flow is technically inter-procedural
+ FlowSummaryImpl::Private::Steps::summaryThroughStep(src, sink, false)
}
/**
@@ -61,6 +65,8 @@ private predicate localAdditionalBasicTaintStep(DataFlow::Node src, DataFlow::No
arg.isVararg() and
sink.(DataFlow::ImplicitVarargsArray).getCall() = arg.getCall()
)
+ or
+ FlowSummaryImpl::Private::Steps::summaryLocalStep(src, sink, false)
}
/**
diff --git a/java/ql/test/library-tests/dataflow/local-additional-taint/localAdditionalTaintStep.ql b/java/ql/test/library-tests/dataflow/local-additional-taint/localAdditionalTaintStep.ql
index e8cd912e58a8..41b5a413386c 100644
--- a/java/ql/test/library-tests/dataflow/local-additional-taint/localAdditionalTaintStep.ql
+++ b/java/ql/test/library-tests/dataflow/local-additional-taint/localAdditionalTaintStep.ql
@@ -2,5 +2,7 @@ import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.internal.TaintTrackingUtil
from DataFlow::Node src, DataFlow::Node sink
-where localAdditionalTaintStep(src, sink)
+where
+ localAdditionalTaintStep(src, sink) and
+ src.getLocation().getFile().getExtension() = "java"
select src, sink
From 04b0682bbf9dd698f02a20946a310a9531e65921 Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Fri, 9 Apr 2021 16:14:51 +0000
Subject: [PATCH 154/433] Use isAdditionalTaintStep and make the query more
readable
---
.../CWE-1004/SensitiveCookieNotHttpOnly.ql | 146 +++++++++---------
1 file changed, 75 insertions(+), 71 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
index d566bd539453..cb8f7412014e 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql
@@ -1,14 +1,22 @@
/**
* @name Sensitive cookies without the HttpOnly response header set
* @description Sensitive cookies without the 'HttpOnly' flag set leaves session cookies vulnerable to
- * an XSS attack. This query checks whether 'HttpOnly' is not set in a Java Cookie object
- * or the 'Set-Cookie' HTTP header.
+ * an XSS attack.
* @kind path-problem
* @id java/sensitive-cookie-not-httponly
* @tags security
* external/cwe/cwe-1004
*/
+/*
+ * Sketch of the structure of this query: we track cookie names that appear to be sensitive
+ * (e.g. `session` or `token`) to a `ServletResponse.addHeader(...)` or `.addCookie(...)`
+ * method that does not set the `httpOnly` flag. Subsidiary configurations
+ * `MatchesHttpOnlyConfiguration` and `SetHttpOnlyInCookieConfiguration` are used to establish
+ * when the `httpOnly` flag is likely to have been set, before configuration
+ * `MissingHttpOnlyConfiguration` establishes that a non-`httpOnly` cookie has a sensitive-seeming name.
+ */
+
import java
import semmle.code.java.dataflow.FlowSteps
import semmle.code.java.frameworks.Servlets
@@ -34,6 +42,11 @@ predicate isSensitiveCookieNameExpr(Expr expr) {
isSensitiveCookieNameExpr(expr.(AddExpr).getAnOperand())
}
+/** A sensitive cookie name. */
+class SensitiveCookieNameExpr extends Expr {
+ SensitiveCookieNameExpr() { isSensitiveCookieNameExpr(this) }
+}
+
/** A method call that sets a `Set-Cookie` header. */
class SetCookieMethodAccess extends MethodAccess {
SetCookieMethodAccess() {
@@ -70,36 +83,55 @@ class CookieClass extends RefType {
}
/** Holds if the `expr` is `true` or a variable that is ever assigned `true`. */
+// This could be a very large result set if computed out of context
+pragma[inline]
predicate mayBeBooleanTrue(Expr expr) {
- expr.(CompileTimeConstantExpr).getBooleanValue() = true or
- expr.(VarAccess).getVariable().getAnAssignedValue().(CompileTimeConstantExpr).getBooleanValue() =
- true
+ expr.getType() instanceof BooleanType and
+ not expr.(CompileTimeConstantExpr).getBooleanValue() = false
}
-/** Holds if the method call sets the `HttpOnly` flag. */
+/** Holds if the method call may set the `HttpOnly` flag. */
predicate setsCookieHttpOnly(MethodAccess ma) {
ma.getMethod().getName() = "setHttpOnly" and
- (
- ma.getArgument(0).(CompileTimeConstantExpr).getBooleanValue() = true
- or
- // any use of setHttpOnly(x) where x isn't false is probably safe
- not exists(VarAccess va |
- ma.getArgument(0).(VarAccess).getVariable().getAnAccess() = va and
- va.getVariable().getAnAssignedValue().(CompileTimeConstantExpr).getBooleanValue() = false
+ // any use of setHttpOnly(x) where x isn't false is probably safe
+ mayBeBooleanTrue(ma.getArgument(0))
+}
+
+/** Holds if `ma` removes a cookie. */
+predicate removesCookie(MethodAccess ma) {
+ ma.getMethod().getName() = "setMaxAge" and
+ ma.getArgument(0).(IntegerLiteral).getIntValue() = 0
+}
+
+/**
+ * Holds if the MethodAccess `ma` is a test method call indicated by:
+ * a) in a test directory such as `src/test/java`
+ * b) in a test package whose name has the word `test`
+ * c) in a test class whose name has the word `test`
+ * d) in a test class implementing a test framework such as JUnit or TestNG
+ */
+predicate isTestMethod(MethodAccess ma) {
+ exists(Method m |
+ m = ma.getEnclosingCallable() and
+ (
+ m.getDeclaringType().getName().toLowerCase().matches("%test%") or // Simple check to exclude test classes to reduce FPs
+ m.getDeclaringType().getPackage().getName().toLowerCase().matches("%test%") or // Simple check to exclude classes in test packages to reduce FPs
+ exists(m.getLocation().getFile().getAbsolutePath().indexOf("/src/test/java")) or // Match test directory structure of build tools like maven
+ m instanceof TestMethod // Test method of a test case implementing a test framework such as JUnit or TestNG
)
)
}
/**
- * A taint configuration tracking flow of a method or a wrapper method that sets
- * the `HttpOnly` flag.
+ * A taint configuration tracking flow of a method or a wrapper method that sets the `HttpOnly`
+ * flag, or one that removes a cookie, to a `ServletResponse.addCookie` call.
*/
class SetHttpOnlyInCookieConfiguration extends TaintTracking2::Configuration {
SetHttpOnlyInCookieConfiguration() { this = "SetHttpOnlyInCookieConfiguration" }
override predicate isSource(DataFlow::Node source) {
source.asExpr() =
- any(MethodAccess ma | setsCookieHttpOnly(ma) or removeCookie(ma)).getQualifier()
+ any(MethodAccess ma | setsCookieHttpOnly(ma) or removesCookie(ma)).getQualifier()
}
override predicate isSink(DataFlow::Node sink) {
@@ -108,18 +140,10 @@ class SetHttpOnlyInCookieConfiguration extends TaintTracking2::Configuration {
}
}
-/** Holds if `ma` removes a cookie. */
-predicate removeCookie(MethodAccess ma) {
- ma.getMethod().getName() = "setMaxAge" and
- ma.getArgument(0).(IntegerLiteral).getIntValue() = 0
-}
-
-/** A sensitive cookie name. */
-class SensitiveCookieNameExpr extends Expr {
- SensitiveCookieNameExpr() { isSensitiveCookieNameExpr(this) }
-}
-
-/** A cookie that is added to an HTTP response, used as a sink in `MissingHttpOnlyConfiguration`. */
+/**
+ * A cookie that is added to an HTTP response and which doesn't have `httpOnly` set, used as a sink
+ * in `MissingHttpOnlyConfiguration`.
+ */
class CookieResponseSink extends DataFlow::ExprNode {
CookieResponseSink() {
exists(MethodAccess ma |
@@ -137,11 +161,8 @@ class CookieResponseSink extends DataFlow::ExprNode {
}
}
-/**
- * Holds if `ClassInstanceExpr` cie is an invocation of a JAX-RS `NewCookie` constructor
- * that sets `HttpOnly` to true.
- */
-predicate setHttpOnlyInNewCookie(ClassInstanceExpr cie) {
+/** Holds if `cie` is an invocation of a JAX-RS `NewCookie` constructor that sets `HttpOnly` to true. */
+predicate setsHttpOnlyInNewCookie(ClassInstanceExpr cie) {
cie.getConstructedType().hasQualifiedName(["javax.ws.rs.core", "jakarta.ws.rs.core"], "NewCookie") and
(
cie.getNumArgument() = 6 and
@@ -156,42 +177,6 @@ predicate setHttpOnlyInNewCookie(ClassInstanceExpr cie) {
)
}
-/** The cookie constructor. */
-class CookieTaintPreservingConstructor extends Constructor, TaintPreservingCallable {
- CookieTaintPreservingConstructor() { this.getDeclaringType() instanceof CookieClass }
-
- override predicate returnsTaintFrom(int arg) { arg = 0 }
-}
-
-/** The method call `toString` to get a stringified cookie representation. */
-class CookieToString extends TaintPreservingCallable {
- CookieToString() {
- this.getDeclaringType() instanceof CookieClass and
- this.hasName("toString")
- }
-
- override predicate returnsTaintFrom(int arg) { arg = -1 }
-}
-
-/**
- * Holds if the MethodAccess `ma` is a test method call indicated by:
- * a) in a test directory such as `src/test/java`
- * b) in a test package whose name has the word `test`
- * c) in a test class whose name has the word `test`
- * d) in a test class implementing a test framework such as JUnit or TestNG
- */
-predicate isTestMethod(MethodAccess ma) {
- exists(Method m |
- m = ma.getEnclosingCallable() and
- (
- m.getDeclaringType().getName().toLowerCase().matches("%test%") or // Simple check to exclude test classes to reduce FPs
- m.getDeclaringType().getPackage().getName().toLowerCase().matches("%test%") or // Simple check to exclude classes in test packages to reduce FPs
- exists(m.getLocation().getFile().getAbsolutePath().indexOf("/src/test/java")) or // Match test directory structure of build tools like maven
- m instanceof TestMethod // Test method of a test case implementing a test framework such as JUnit or TestNG
- )
- )
-}
-
/**
* A taint configuration tracking flow from a sensitive cookie without the `HttpOnly` flag
* set to its HTTP response.
@@ -206,8 +191,27 @@ class MissingHttpOnlyConfiguration extends TaintTracking::Configuration {
override predicate isSink(DataFlow::Node sink) { sink instanceof CookieResponseSink }
override predicate isSanitizer(DataFlow::Node node) {
- // new NewCookie("session-access-key", accessKey, "/", null, null, 0, true, true)
- setHttpOnlyInNewCookie(node.asExpr())
+ // JAX-RS's `new NewCookie("session-access-key", accessKey, "/", null, null, 0, true, true)` and similar
+ setsHttpOnlyInNewCookie(node.asExpr())
+ }
+
+ override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
+ exists(
+ ConstructorCall cc // new Cookie(...)
+ |
+ cc.getConstructedType() instanceof CookieClass and
+ pred.asExpr() = cc.getAnArgument() and
+ succ.asExpr() = cc
+ )
+ or
+ exists(
+ MethodAccess ma // cookie.toString()
+ |
+ ma.getMethod().getName() = "toString" and
+ ma.getQualifier().getType() instanceof CookieClass and
+ pred.asExpr() = ma.getQualifier() and
+ succ.asExpr() = ma
+ )
}
}
From cc4827600bf8c1a77c342c7f82548fd910d159ea Mon Sep 17 00:00:00 2001
From: Taus
Date: Fri, 9 Apr 2021 17:11:47 +0000
Subject: [PATCH 155/433] Python: Use API graphs in `Stdlib.qll`
Eliminates _almost_ all of the bespoke type trackers found here. The
ones that remain do not fit easily inside the framework of API graphs
(at least, not yet), and I did not see any easy ways to clean them up.
They have, however, been rewritten to use `LocalSourceNode` internally,
which was the primary goal of this exercise.
I'm sure we could also clean up many of the inner modules given the more
lean presentation we have now, but this can wait for a different PR.
---
.../src/semmle/python/frameworks/Stdlib.qll | 857 +++---------------
1 file changed, 143 insertions(+), 714 deletions(-)
diff --git a/python/ql/src/semmle/python/frameworks/Stdlib.qll b/python/ql/src/semmle/python/frameworks/Stdlib.qll
index 2626d1585ff0..3441c670fde8 100644
--- a/python/ql/src/semmle/python/frameworks/Stdlib.qll
+++ b/python/ql/src/semmle/python/frameworks/Stdlib.qll
@@ -17,57 +17,7 @@ private module Stdlib {
// os
// ---------------------------------------------------------------------------
/** Gets a reference to the `os` module. */
- private DataFlow::Node os(DataFlow::TypeTracker t) {
- t.start() and
- result = DataFlow::importNode("os")
- or
- exists(DataFlow::TypeTracker t2 | result = os(t2).track(t2, t))
- }
-
- /** Gets a reference to the `os` module. */
- DataFlow::Node os() { result = os(DataFlow::TypeTracker::end()) }
-
- /**
- * Gets a reference to the attribute `attr_name` of the `os` module.
- * WARNING: Only holds for a few predefined attributes.
- *
- * For example, using `attr_name = "system"` will get all uses of `os.system`.
- */
- private DataFlow::Node os_attr(DataFlow::TypeTracker t, string attr_name) {
- attr_name in [
- "system", "popen", "popen2", "popen3", "popen4",
- // exec
- "execl", "execle", "execlp", "execlpe", "execv", "execve", "execvp", "execvpe",
- // spawn
- "spawnl", "spawnle", "spawnlp", "spawnlpe", "spawnv", "spawnve", "spawnvp", "spawnvpe",
- "posix_spawn", "posix_spawnp",
- // modules
- "path"
- ] and
- (
- t.start() and
- result = DataFlow::importNode("os." + attr_name)
- or
- t.startInAttr(attr_name) and
- result = DataFlow::importNode("os")
- )
- or
- // Due to bad performance when using normal setup with `os_attr(t2, attr_name).track(t2, t)`
- // we have inlined that code and forced a join
- exists(DataFlow::TypeTracker t2 |
- exists(DataFlow::StepSummary summary |
- os_attr_first_join(t2, attr_name, result, summary) and
- t = t2.append(summary)
- )
- )
- }
-
- pragma[nomagic]
- private predicate os_attr_first_join(
- DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary
- ) {
- DataFlow::StepSummary::step(os_attr(t2, attr_name), res, summary)
- }
+ API::Node os() { result = API::moduleImport("os") }
/**
* Gets a reference to the attribute `attr_name` of the `os` module.
@@ -75,14 +25,12 @@ private module Stdlib {
*
* For example, using `"system"` will get all uses of `os.system`.
*/
- private DataFlow::Node os_attr(string attr_name) {
- result = os_attr(DataFlow::TypeTracker::end(), attr_name)
- }
+ private API::Node os_attr(string attr_name) { result = os().getMember(attr_name) }
/** Provides models for the `os` module. */
module os {
/** Gets a reference to the `os.path` module. */
- DataFlow::Node path() { result = os_attr("path") }
+ API::Node path() { result = os_attr("path") }
/** Provides models for the `os.path` module */
module path {
@@ -92,46 +40,10 @@ private module Stdlib {
*
* For example, using `attr_name = "join"` will get all uses of `os.path.join`.
*/
- private DataFlow::Node path_attr(DataFlow::TypeTracker t, string attr_name) {
- attr_name in ["join", "normpath", "realpath", "abspath"] and
- (
- t.start() and
- result = DataFlow::importNode("os.path." + attr_name)
- or
- t.startInAttr(attr_name) and
- result = os::path()
- )
- or
- // Due to bad performance when using normal setup with `path_attr(t2, attr_name).track(t2, t)`
- // we have inlined that code and forced a join
- exists(DataFlow::TypeTracker t2 |
- exists(DataFlow::StepSummary summary |
- path_attr_first_join(t2, attr_name, result, summary) and
- t = t2.append(summary)
- )
- )
- }
-
- pragma[nomagic]
- private predicate path_attr_first_join(
- DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
- DataFlow::StepSummary summary
- ) {
- DataFlow::StepSummary::step(path_attr(t2, attr_name), res, summary)
- }
-
- /**
- * Gets a reference to the attribute `attr_name` of the `os.path` module.
- * WARNING: Only holds for a few predefined attributes.
- *
- * For example, using `attr_name = "join"` will get all uses of `os.path.join`.
- */
- DataFlow::Node path_attr(string attr_name) {
- result = path_attr(DataFlow::TypeTracker::end(), attr_name)
- }
+ API::Node path_attr(string attr_name) { result = path().getMember(attr_name) }
/** Gets a reference to the `os.path.join` function. */
- DataFlow::Node join() { result = path_attr("join") }
+ API::Node join() { result = path_attr("join") }
}
}
@@ -139,10 +51,8 @@ private module Stdlib {
* A call to `os.path.normpath`.
* See https://docs.python.org/3/library/os.path.html#os.path.normpath
*/
- private class OsPathNormpathCall extends Path::PathNormalization::Range, DataFlow::CfgNode {
- override CallNode node;
-
- OsPathNormpathCall() { node.getFunction() = os::path::path_attr("normpath").asCfgNode() }
+ private class OsPathNormpathCall extends Path::PathNormalization::Range, DataFlow::CallCfgNode {
+ OsPathNormpathCall() { this = os::path::path_attr("normpath").getACall() }
DataFlow::Node getPathArg() {
result.asCfgNode() in [node.getArg(0), node.getArgByName("path")]
@@ -166,7 +76,7 @@ private module Stdlib {
private class OsPathAbspathCall extends Path::PathNormalization::Range, DataFlow::CfgNode {
override CallNode node;
- OsPathAbspathCall() { node.getFunction() = os::path::path_attr("abspath").asCfgNode() }
+ OsPathAbspathCall() { this = os::path::path_attr("abspath").getACall() }
DataFlow::Node getPathArg() {
result.asCfgNode() in [node.getArg(0), node.getArgByName("path")]
@@ -190,7 +100,7 @@ private module Stdlib {
private class OsPathRealpathCall extends Path::PathNormalization::Range, DataFlow::CfgNode {
override CallNode node;
- OsPathRealpathCall() { node.getFunction() = os::path::path_attr("realpath").asCfgNode() }
+ OsPathRealpathCall() { this = os::path::path_attr("realpath").getACall() }
DataFlow::Node getPathArg() {
result.asCfgNode() in [node.getArg(0), node.getArgByName("path")]
@@ -214,7 +124,7 @@ private module Stdlib {
private class OsSystemCall extends SystemCommandExecution::Range, DataFlow::CfgNode {
override CallNode node;
- OsSystemCall() { node.getFunction() = os_attr("system").asCfgNode() }
+ OsSystemCall() { this = os_attr("system").getACall() }
override DataFlow::Node getCommand() { result.asCfgNode() = node.getArg(0) }
}
@@ -233,7 +143,7 @@ private module Stdlib {
OsPopenCall() {
name in ["popen", "popen2", "popen3", "popen4"] and
- node.getFunction() = os_attr(name).asCfgNode()
+ this = os_attr(name).getACall()
}
override DataFlow::Node getCommand() {
@@ -254,7 +164,7 @@ private module Stdlib {
OsExecCall() {
exists(string name |
name in ["execl", "execle", "execlp", "execlpe", "execv", "execve", "execvp", "execvpe"] and
- node.getFunction() = os_attr(name).asCfgNode()
+ this = os_attr(name).getACall()
)
}
@@ -273,7 +183,7 @@ private module Stdlib {
name in [
"spawnl", "spawnle", "spawnlp", "spawnlpe", "spawnv", "spawnve", "spawnvp", "spawnvpe"
] and
- node.getFunction() = os_attr(name).asCfgNode()
+ this = os_attr(name).getACall()
)
}
@@ -287,7 +197,7 @@ private module Stdlib {
private class OsPosixSpawnCall extends SystemCommandExecution::Range, DataFlow::CfgNode {
override CallNode node;
- OsPosixSpawnCall() { node.getFunction() = os_attr(["posix_spawn", "posix_spawnp"]).asCfgNode() }
+ OsPosixSpawnCall() { this = os_attr(["posix_spawn", "posix_spawnp"]).getACall() }
override DataFlow::Node getCommand() { result.asCfgNode() = node.getArg(0) }
}
@@ -297,7 +207,7 @@ private module Stdlib {
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
exists(CallNode call |
nodeTo.asCfgNode() = call and
- call.getFunction() = os::path::join().asCfgNode() and
+ call = os::path::join().getACall().asCfgNode() and
call.getAnArg() = nodeFrom.asCfgNode()
)
// TODO: Handle pathlib (like we do for os.path.join)
@@ -308,48 +218,7 @@ private module Stdlib {
// subprocess
// ---------------------------------------------------------------------------
/** Gets a reference to the `subprocess` module. */
- private DataFlow::Node subprocess(DataFlow::TypeTracker t) {
- t.start() and
- result = DataFlow::importNode("subprocess")
- or
- exists(DataFlow::TypeTracker t2 | result = subprocess(t2).track(t2, t))
- }
-
- /** Gets a reference to the `subprocess` module. */
- DataFlow::Node subprocess() { result = subprocess(DataFlow::TypeTracker::end()) }
-
- /**
- * Gets a reference to the attribute `attr_name` of the `subprocess` module.
- * WARNING: Only holds for a few predefined attributes.
- *
- * For example, using `attr_name = "Popen"` will get all uses of `subprocess.Popen`.
- */
- private DataFlow::Node subprocess_attr(DataFlow::TypeTracker t, string attr_name) {
- attr_name in ["Popen", "call", "check_call", "check_output", "run"] and
- (
- t.start() and
- result = DataFlow::importNode("subprocess." + attr_name)
- or
- t.startInAttr(attr_name) and
- result = subprocess()
- )
- or
- // Due to bad performance when using normal setup with `subprocess_attr(t2, attr_name).track(t2, t)`
- // we have inlined that code and forced a join
- exists(DataFlow::TypeTracker t2 |
- exists(DataFlow::StepSummary summary |
- subprocess_attr_first_join(t2, attr_name, result, summary) and
- t = t2.append(summary)
- )
- )
- }
-
- pragma[nomagic]
- private predicate subprocess_attr_first_join(
- DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary
- ) {
- DataFlow::StepSummary::step(subprocess_attr(t2, attr_name), res, summary)
- }
+ deprecated DataFlow::Node subprocess() { result = API::moduleImport("subprocess").getAUse() }
/**
* Gets a reference to the attribute `attr_name` of the `subprocess` module.
@@ -358,7 +227,7 @@ private module Stdlib {
* For example, using `attr_name = "Popen"` will get all uses of `subprocess.Popen`.
*/
private DataFlow::Node subprocess_attr(string attr_name) {
- result = subprocess_attr(DataFlow::TypeTracker::end(), attr_name)
+ result = API::moduleImport("subprocess").getMember(attr_name).getAUse()
}
/**
@@ -443,45 +312,26 @@ private module Stdlib {
// marshal
// ---------------------------------------------------------------------------
/** Gets a reference to the `marshal` module. */
- private DataFlow::Node marshal(DataFlow::TypeTracker t) {
- t.start() and
- result = DataFlow::importNode("marshal")
- or
- exists(DataFlow::TypeTracker t2 | result = marshal(t2).track(t2, t))
- }
-
- /** Gets a reference to the `marshal` module. */
- DataFlow::Node marshal() { result = marshal(DataFlow::TypeTracker::end()) }
+ deprecated DataFlow::Node marshal() { result = API::moduleImport("marshal").getAUse() }
/** Provides models for the `marshal` module. */
module marshal {
/** Gets a reference to the `marshal.loads` function. */
- private DataFlow::Node loads(DataFlow::TypeTracker t) {
- t.start() and
- result = DataFlow::importNode("marshal.loads")
- or
- t.startInAttr("loads") and
- result = marshal()
- or
- exists(DataFlow::TypeTracker t2 | result = loads(t2).track(t2, t))
+ deprecated DataFlow::Node loads() {
+ result = API::moduleImport("marshal").getMember("loads").getAUse()
}
-
- /** Gets a reference to the `marshal.loads` function. */
- DataFlow::Node loads() { result = loads(DataFlow::TypeTracker::end()) }
}
/**
* A call to `marshal.loads`
* See https://docs.python.org/3/library/marshal.html#marshal.loads
*/
- private class MarshalLoadsCall extends Decoding::Range, DataFlow::CfgNode {
- override CallNode node;
-
- MarshalLoadsCall() { node.getFunction() = marshal::loads().asCfgNode() }
+ private class MarshalLoadsCall extends Decoding::Range, DataFlow::CallCfgNode {
+ MarshalLoadsCall() { this = API::moduleImport("marshal").getMember("loads").getACall() }
override predicate mayExecuteInput() { any() }
- override DataFlow::Node getAnInput() { result.asCfgNode() = node.getArg(0) }
+ override DataFlow::Node getAnInput() { result = this.getArg(0) }
override DataFlow::Node getOutput() { result = this }
@@ -494,45 +344,28 @@ private module Stdlib {
private string pickleModuleName() { result in ["pickle", "cPickle", "_pickle"] }
/** Gets a reference to the `pickle` module. */
- private DataFlow::Node pickle(DataFlow::TypeTracker t) {
- t.start() and
- result = DataFlow::importNode(pickleModuleName())
- or
- exists(DataFlow::TypeTracker t2 | result = pickle(t2).track(t2, t))
+ deprecated DataFlow::Node pickle() {
+ result = API::moduleImport(["pickle", "cPickle", "_pickle"]).getAUse()
}
- /** Gets a reference to the `pickle` module. */
- DataFlow::Node pickle() { result = pickle(DataFlow::TypeTracker::end()) }
-
/** Provides models for the `pickle` module. */
module pickle {
/** Gets a reference to the `pickle.loads` function. */
- private DataFlow::Node loads(DataFlow::TypeTracker t) {
- t.start() and
- result = DataFlow::importNode(pickleModuleName() + ".loads")
- or
- t.startInAttr("loads") and
- result = pickle()
- or
- exists(DataFlow::TypeTracker t2 | result = loads(t2).track(t2, t))
+ DataFlow::Node loads() {
+ result = API::moduleImport(["pickle", "cPickle", "_pickle"]).getMember("loads").getAUse()
}
-
- /** Gets a reference to the `pickle.loads` function. */
- DataFlow::Node loads() { result = loads(DataFlow::TypeTracker::end()) }
}
/**
* A call to `pickle.loads`
* See https://docs.python.org/3/library/pickle.html#pickle.loads
*/
- private class PickleLoadsCall extends Decoding::Range, DataFlow::CfgNode {
- override CallNode node;
-
- PickleLoadsCall() { node.getFunction() = pickle::loads().asCfgNode() }
+ private class PickleLoadsCall extends Decoding::Range, DataFlow::CallCfgNode {
+ PickleLoadsCall() { this.getFunction() = pickle::loads() }
override predicate mayExecuteInput() { any() }
- override DataFlow::Node getAnInput() { result.asCfgNode() = node.getArg(0) }
+ override DataFlow::Node getAnInput() { result = this.getArg(0) }
override DataFlow::Node getOutput() { result = this }
@@ -543,57 +376,14 @@ private module Stdlib {
// popen2
// ---------------------------------------------------------------------------
/** Gets a reference to the `popen2` module (only available in Python 2). */
- private DataFlow::Node popen2(DataFlow::TypeTracker t) {
- t.start() and
- result = DataFlow::importNode("popen2")
- or
- exists(DataFlow::TypeTracker t2 | result = popen2(t2).track(t2, t))
- }
-
- /** Gets a reference to the `popen2` module (only available in Python 2). */
- DataFlow::Node popen2() { result = popen2(DataFlow::TypeTracker::end()) }
-
- /**
- * Gets a reference to the attribute `attr_name` of the `popen2` module.
- * WARNING: Only holds for a few predefined attributes.
- */
- private DataFlow::Node popen2_attr(DataFlow::TypeTracker t, string attr_name) {
- attr_name in [
- "popen2", "popen3", "popen4",
- // classes
- "Popen3", "Popen4"
- ] and
- (
- t.start() and
- result = DataFlow::importNode("popen2." + attr_name)
- or
- t.startInAttr(attr_name) and
- result = DataFlow::importNode("popen2")
- )
- or
- // Due to bad performance when using normal setup with `popen2_attr(t2, attr_name).track(t2, t)`
- // we have inlined that code and forced a join
- exists(DataFlow::TypeTracker t2 |
- exists(DataFlow::StepSummary summary |
- popen2_attr_first_join(t2, attr_name, result, summary) and
- t = t2.append(summary)
- )
- )
- }
-
- pragma[nomagic]
- private predicate popen2_attr_first_join(
- DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary
- ) {
- DataFlow::StepSummary::step(popen2_attr(t2, attr_name), res, summary)
- }
+ API::Node popen2() { result = API::moduleImport("popen2") }
/**
* Gets a reference to the attribute `attr_name` of the `popen2` module.
* WARNING: Only holds for a few predefined attributes.
*/
- private DataFlow::Node popen2_attr(string attr_name) {
- result = popen2_attr(DataFlow::TypeTracker::end(), attr_name)
+ private API::Node popen2_attr(string attr_name) {
+ result = API::moduleImport("popen2").getMember(attr_name)
}
/**
@@ -606,7 +396,7 @@ private module Stdlib {
Popen2PopenCall() {
exists(string name |
name in ["popen2", "popen3", "popen4", "Popen3", "Popen4"] and
- node.getFunction() = popen2_attr(name).asCfgNode()
+ this = popen2_attr(name).getACall()
)
}
@@ -619,63 +409,20 @@ private module Stdlib {
// platform
// ---------------------------------------------------------------------------
/** Gets a reference to the `platform` module. */
- private DataFlow::Node platform(DataFlow::TypeTracker t) {
- t.start() and
- result = DataFlow::importNode("platform")
- or
- exists(DataFlow::TypeTracker t2 | result = platform(t2).track(t2, t))
- }
-
- /** Gets a reference to the `platform` module. */
- DataFlow::Node platform() { result = platform(DataFlow::TypeTracker::end()) }
-
- /**
- * Gets a reference to the attribute `attr_name` of the `platform` module.
- * WARNING: Only holds for a few predefined attributes.
- */
- private DataFlow::Node platform_attr(DataFlow::TypeTracker t, string attr_name) {
- attr_name in ["popen"] and
- (
- t.start() and
- result = DataFlow::importNode("platform." + attr_name)
- or
- t.startInAttr(attr_name) and
- result = DataFlow::importNode("platform")
- )
- or
- // Due to bad performance when using normal setup with `platform_attr(t2, attr_name).track(t2, t)`
- // we have inlined that code and forced a join
- exists(DataFlow::TypeTracker t2 |
- exists(DataFlow::StepSummary summary |
- platform_attr_first_join(t2, attr_name, result, summary) and
- t = t2.append(summary)
- )
- )
- }
-
- pragma[nomagic]
- private predicate platform_attr_first_join(
- DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary
- ) {
- DataFlow::StepSummary::step(platform_attr(t2, attr_name), res, summary)
- }
+ API::Node platform() { result = API::moduleImport("platform") }
/**
* Gets a reference to the attribute `attr_name` of the `platform` module.
* WARNING: Only holds for a few predefined attributes.
*/
- private DataFlow::Node platform_attr(string attr_name) {
- result = platform_attr(DataFlow::TypeTracker::end(), attr_name)
- }
+ private API::Node platform_attr(string attr_name) { result = platform().getMember(attr_name) }
/**
* A call to the `platform.popen` function.
* See https://docs.python.org/2.7/library/platform.html#platform.popen
*/
- private class PlatformPopenCall extends SystemCommandExecution::Range, DataFlow::CfgNode {
- override CallNode node;
-
- PlatformPopenCall() { node.getFunction() = platform_attr("popen").asCfgNode() }
+ private class PlatformPopenCall extends SystemCommandExecution::Range, DataFlow::CallCfgNode {
+ PlatformPopenCall() { this = platform_attr("popen").getACall() }
override DataFlow::Node getCommand() {
result.asCfgNode() in [node.getArg(0), node.getArgByName("cmd")]
@@ -753,72 +500,24 @@ private module Stdlib {
// base64
// ---------------------------------------------------------------------------
/** Gets a reference to the `base64` module. */
- private DataFlow::Node base64(DataFlow::TypeTracker t) {
- t.start() and
- result = DataFlow::importNode("base64")
- or
- exists(DataFlow::TypeTracker t2 | result = base64(t2).track(t2, t))
- }
-
- /** Gets a reference to the `base64` module. */
- DataFlow::Node base64() { result = base64(DataFlow::TypeTracker::end()) }
-
- /**
- * Gets a reference to the attribute `attr_name` of the `base64` module.
- * WARNING: Only holds for a few predefined attributes.
- */
- private DataFlow::Node base64_attr(DataFlow::TypeTracker t, string attr_name) {
- attr_name in [
- "b64encode", "b64decode", "standard_b64encode", "standard_b64decode", "urlsafe_b64encode",
- "urlsafe_b64decode", "b32encode", "b32decode", "b16encode", "b16decode", "encodestring",
- "decodestring", "a85encode", "a85decode", "b85encode", "b85decode", "encodebytes",
- "decodebytes"
- ] and
- (
- t.start() and
- result = DataFlow::importNode("base64" + "." + attr_name)
- or
- t.startInAttr(attr_name) and
- result = base64()
- )
- or
- // Due to bad performance when using normal setup with `base64_attr(t2, attr_name).track(t2, t)`
- // we have inlined that code and forced a join
- exists(DataFlow::TypeTracker t2 |
- exists(DataFlow::StepSummary summary |
- base64_attr_first_join(t2, attr_name, result, summary) and
- t = t2.append(summary)
- )
- )
- }
-
- pragma[nomagic]
- private predicate base64_attr_first_join(
- DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary
- ) {
- DataFlow::StepSummary::step(base64_attr(t2, attr_name), res, summary)
- }
+ API::Node base64() { result = API::moduleImport("base64") }
/**
* Gets a reference to the attribute `attr_name` of the `base64` module.
* WARNING: Only holds for a few predefined attributes.
*/
- private DataFlow::Node base64_attr(string attr_name) {
- result = base64_attr(DataFlow::TypeTracker::end(), attr_name)
- }
+ private API::Node base64_attr(string attr_name) { result = base64().getMember(attr_name) }
/** A call to any of the encode functions in the `base64` module. */
- private class Base64EncodeCall extends Encoding::Range, DataFlow::CfgNode {
- override CallNode node;
+ private class Base64EncodeCall extends Encoding::Range, DataFlow::CallCfgNode {
+ string name;
Base64EncodeCall() {
- exists(string name |
- name in [
- "b64encode", "standard_b64encode", "urlsafe_b64encode", "b32encode", "b16encode",
- "encodestring", "a85encode", "b85encode", "encodebytes"
- ] and
- node.getFunction() = base64_attr(name).asCfgNode()
- )
+ name in [
+ "b64encode", "standard_b64encode", "urlsafe_b64encode", "b32encode", "b16encode",
+ "encodestring", "a85encode", "b85encode", "encodebytes"
+ ] and
+ this = base64_attr(name).getACall()
}
override DataFlow::Node getAnInput() { result.asCfgNode() = node.getArg(0) }
@@ -826,35 +525,31 @@ private module Stdlib {
override DataFlow::Node getOutput() { result = this }
override string getFormat() {
- exists(string name | node.getFunction() = base64_attr(name).asCfgNode() |
- name in [
- "b64encode", "standard_b64encode", "urlsafe_b64encode", "encodestring", "encodebytes"
- ] and
- result = "Base64"
- or
- name = "b32encode" and result = "Base32"
- or
- name = "b16encode" and result = "Base16"
- or
- name = "a85encode" and result = "Ascii85"
- or
- name = "b85encode" and result = "Base85"
- )
+ name in [
+ "b64encode", "standard_b64encode", "urlsafe_b64encode", "encodestring", "encodebytes"
+ ] and
+ result = "Base64"
+ or
+ name = "b32encode" and result = "Base32"
+ or
+ name = "b16encode" and result = "Base16"
+ or
+ name = "a85encode" and result = "Ascii85"
+ or
+ name = "b85encode" and result = "Base85"
}
}
/** A call to any of the decode functions in the `base64` module. */
- private class Base64DecodeCall extends Decoding::Range, DataFlow::CfgNode {
- override CallNode node;
+ private class Base64DecodeCall extends Decoding::Range, DataFlow::CallCfgNode {
+ string name;
Base64DecodeCall() {
- exists(string name |
- name in [
- "b64decode", "standard_b64decode", "urlsafe_b64decode", "b32decode", "b16decode",
- "decodestring", "a85decode", "b85decode", "decodebytes"
- ] and
- node.getFunction() = base64_attr(name).asCfgNode()
- )
+ name in [
+ "b64decode", "standard_b64decode", "urlsafe_b64decode", "b32decode", "b16decode",
+ "decodestring", "a85decode", "b85decode", "decodebytes"
+ ] and
+ this = base64_attr(name).getACall()
}
override predicate mayExecuteInput() { none() }
@@ -864,20 +559,18 @@ private module Stdlib {
override DataFlow::Node getOutput() { result = this }
override string getFormat() {
- exists(string name | node.getFunction() = base64_attr(name).asCfgNode() |
- name in [
- "b64decode", "standard_b64decode", "urlsafe_b64decode", "decodestring", "decodebytes"
- ] and
- result = "Base64"
- or
- name = "b32decode" and result = "Base32"
- or
- name = "b16decode" and result = "Base16"
- or
- name = "a85decode" and result = "Ascii85"
- or
- name = "b85decode" and result = "Base85"
- )
+ name in [
+ "b64decode", "standard_b64decode", "urlsafe_b64decode", "decodestring", "decodebytes"
+ ] and
+ result = "Base64"
+ or
+ name = "b32decode" and result = "Base32"
+ or
+ name = "b16decode" and result = "Base16"
+ or
+ name = "a85decode" and result = "Ascii85"
+ or
+ name = "b85decode" and result = "Base85"
}
}
@@ -885,63 +578,20 @@ private module Stdlib {
// json
// ---------------------------------------------------------------------------
/** Gets a reference to the `json` module. */
- private DataFlow::Node json(DataFlow::TypeTracker t) {
- t.start() and
- result = DataFlow::importNode("json")
- or
- exists(DataFlow::TypeTracker t2 | result = json(t2).track(t2, t))
- }
-
- /** Gets a reference to the `json` module. */
- DataFlow::Node json() { result = json(DataFlow::TypeTracker::end()) }
-
- /**
- * Gets a reference to the attribute `attr_name` of the `json` module.
- * WARNING: Only holds for a few predefined attributes.
- */
- private DataFlow::Node json_attr(DataFlow::TypeTracker t, string attr_name) {
- attr_name in ["loads", "dumps"] and
- (
- t.start() and
- result = DataFlow::importNode("json" + "." + attr_name)
- or
- t.startInAttr(attr_name) and
- result = json()
- )
- or
- // Due to bad performance when using normal setup with `json_attr(t2, attr_name).track(t2, t)`
- // we have inlined that code and forced a join
- exists(DataFlow::TypeTracker t2 |
- exists(DataFlow::StepSummary summary |
- json_attr_first_join(t2, attr_name, result, summary) and
- t = t2.append(summary)
- )
- )
- }
-
- pragma[nomagic]
- private predicate json_attr_first_join(
- DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary
- ) {
- DataFlow::StepSummary::step(json_attr(t2, attr_name), res, summary)
- }
+ API::Node json() { result = API::moduleImport("node") }
/**
* Gets a reference to the attribute `attr_name` of the `json` module.
* WARNING: Only holds for a few predefined attributes.
*/
- private DataFlow::Node json_attr(string attr_name) {
- result = json_attr(DataFlow::TypeTracker::end(), attr_name)
- }
+ private API::Node json_attr(string attr_name) { result = json().getMember(attr_name) }
/**
* A call to `json.loads`
* See https://docs.python.org/3/library/json.html#json.loads
*/
- private class JsonLoadsCall extends Decoding::Range, DataFlow::CfgNode {
- override CallNode node;
-
- JsonLoadsCall() { node.getFunction() = json_attr("loads").asCfgNode() }
+ private class JsonLoadsCall extends Decoding::Range, DataFlow::CallCfgNode {
+ JsonLoadsCall() { this = json_attr("loads").getACall() }
override predicate mayExecuteInput() { none() }
@@ -956,10 +606,8 @@ private module Stdlib {
* A call to `json.dumps`
* See https://docs.python.org/3/library/json.html#json.dumps
*/
- private class JsonDumpsCall extends Encoding::Range, DataFlow::CfgNode {
- override CallNode node;
-
- JsonDumpsCall() { node.getFunction() = json_attr("dumps").asCfgNode() }
+ private class JsonDumpsCall extends Encoding::Range, DataFlow::CallCfgNode {
+ JsonDumpsCall() { this = json_attr("dumps").getACall() }
override DataFlow::Node getAnInput() { result.asCfgNode() = node.getArg(0) }
@@ -972,15 +620,7 @@ private module Stdlib {
// cgi
// ---------------------------------------------------------------------------
/** Gets a reference to the `cgi` module. */
- private DataFlow::Node cgi(DataFlow::TypeTracker t) {
- t.start() and
- result = DataFlow::importNode("cgi")
- or
- exists(DataFlow::TypeTracker t2 | result = cgi(t2).track(t2, t))
- }
-
- /** Gets a reference to the `cgi` module. */
- DataFlow::Node cgi() { result = cgi(DataFlow::TypeTracker::end()) }
+ API::Node cgi() { result = API::moduleImport("cgi") }
/** Provides models for the `cgi` module. */
module cgi {
@@ -991,15 +631,7 @@ private module Stdlib {
*/
module FieldStorage {
/** Gets a reference to the `cgi.FieldStorage` class. */
- private DataFlow::Node classRef(DataFlow::TypeTracker t) {
- t.startInAttr("FieldStorage") and
- result = cgi()
- or
- exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
- }
-
- /** Gets a reference to the `cgi.FieldStorage` class. */
- DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
+ API::Node classRef() { result = cgi().getMember("FieldStorage") }
/**
* A source of instances of `cgi.FieldStorage`, extend this class to model new instances.
@@ -1020,145 +652,86 @@ private module Stdlib {
* if it turns out to be a problem, we'll have to refine.
*/
private class ClassInstantiation extends InstanceSource, RemoteFlowSource::Range,
- DataFlow::CfgNode {
- override CallNode node;
-
- ClassInstantiation() { node.getFunction() = classRef().asCfgNode() }
+ DataFlow::CallCfgNode {
+ ClassInstantiation() { this = classRef().getACall() }
override string getSourceType() { result = "cgi.FieldStorage" }
}
/** Gets a reference to an instance of `cgi.FieldStorage`. */
- private DataFlow::Node instance(DataFlow::TypeTracker t) {
- t.start() and
- result instanceof InstanceSource
- or
- exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
- }
-
- /** Gets a reference to an instance of `cgi.FieldStorage`. */
- DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
-
- /** Gets a reference to the `getvalue` method on a `cgi.FieldStorage` instance. */
- private DataFlow::Node getvalueRef(DataFlow::TypeTracker t) {
- t.startInAttr("getvalue") and
- result = instance()
- or
- exists(DataFlow::TypeTracker t2 | result = getvalueRef(t2).track(t2, t))
- }
+ API::Node instance() { result = classRef().getReturn() }
/** Gets a reference to the `getvalue` method on a `cgi.FieldStorage` instance. */
- DataFlow::Node getvalueRef() { result = getvalueRef(DataFlow::TypeTracker::end()) }
-
- /** Gets a reference to the result of calling the `getvalue` method on a `cgi.FieldStorage` instance. */
- private DataFlow::Node getvalueResult(DataFlow::TypeTracker t) {
- t.start() and
- result.asCfgNode().(CallNode).getFunction() = getvalueRef().asCfgNode()
- or
- exists(DataFlow::TypeTracker t2 | result = getvalueResult(t2).track(t2, t))
- }
+ API::Node getvalueRef() { result = instance().getMember("getvalue") }
/** Gets a reference to the result of calling the `getvalue` method on a `cgi.FieldStorage` instance. */
- DataFlow::Node getvalueResult() { result = getvalueResult(DataFlow::TypeTracker::end()) }
-
- /** Gets a reference to the `getfirst` method on a `cgi.FieldStorage` instance. */
- private DataFlow::Node getfirstRef(DataFlow::TypeTracker t) {
- t.startInAttr("getfirst") and
- result = instance()
- or
- exists(DataFlow::TypeTracker t2 | result = getfirstRef(t2).track(t2, t))
- }
+ API::Node getvalueResult() { result = getvalueRef().getReturn() }
/** Gets a reference to the `getfirst` method on a `cgi.FieldStorage` instance. */
- DataFlow::Node getfirstRef() { result = getfirstRef(DataFlow::TypeTracker::end()) }
-
- /** Gets a reference to the result of calling the `getfirst` method on a `cgi.FieldStorage` instance. */
- private DataFlow::Node getfirstResult(DataFlow::TypeTracker t) {
- t.start() and
- result.asCfgNode().(CallNode).getFunction() = getfirstRef().asCfgNode()
- or
- exists(DataFlow::TypeTracker t2 | result = getfirstResult(t2).track(t2, t))
- }
+ API::Node getfirstRef() { result = instance().getMember("getfirst") }
/** Gets a reference to the result of calling the `getfirst` method on a `cgi.FieldStorage` instance. */
- DataFlow::Node getfirstResult() { result = getfirstResult(DataFlow::TypeTracker::end()) }
-
- /** Gets a reference to the `getlist` method on a `cgi.FieldStorage` instance. */
- private DataFlow::Node getlistRef(DataFlow::TypeTracker t) {
- t.startInAttr("getlist") and
- result = instance()
- or
- exists(DataFlow::TypeTracker t2 | result = getlistRef(t2).track(t2, t))
- }
+ API::Node getfirstResult() { result = getfirstRef().getReturn() }
/** Gets a reference to the `getlist` method on a `cgi.FieldStorage` instance. */
- DataFlow::Node getlistRef() { result = getlistRef(DataFlow::TypeTracker::end()) }
+ API::Node getlistRef() { result = instance().getMember("getlist") }
/** Gets a reference to the result of calling the `getlist` method on a `cgi.FieldStorage` instance. */
- private DataFlow::Node getlistResult(DataFlow::TypeTracker t) {
- t.start() and
- result.asCfgNode().(CallNode).getFunction() = getlistRef().asCfgNode()
- or
- exists(DataFlow::TypeTracker t2 | result = getlistResult(t2).track(t2, t))
- }
-
- /** Gets a reference to the result of calling the `getlist` method on a `cgi.FieldStorage` instance. */
- DataFlow::Node getlistResult() { result = getlistResult(DataFlow::TypeTracker::end()) }
+ API::Node getlistResult() { result = getlistRef().getReturn() }
/** Gets a reference to a list of fields. */
- private DataFlow::Node fieldList(DataFlow::TypeTracker t) {
+ private DataFlow::LocalSourceNode fieldList(DataFlow::TypeTracker t) {
t.start() and
- (
- result = getlistResult()
- or
- result = getvalueResult()
- or
- // TODO: Should have better handling of subscripting
- result.asCfgNode().(SubscriptNode).getObject() = instance().asCfgNode()
- )
+ // TODO: Should have better handling of subscripting
+ result.asCfgNode().(SubscriptNode).getObject() = instance().getAUse().asCfgNode()
or
exists(DataFlow::TypeTracker t2 | result = fieldList(t2).track(t2, t))
}
/** Gets a reference to a list of fields. */
- DataFlow::Node fieldList() { result = fieldList(DataFlow::TypeTracker::end()) }
+ DataFlow::Node fieldList() {
+ result = getlistResult().getAUse() or
+ result = getvalueResult().getAUse() or
+ fieldList(DataFlow::TypeTracker::end()).flowsTo(result)
+ }
/** Gets a reference to a field. */
- private DataFlow::Node field(DataFlow::TypeTracker t) {
+ private DataFlow::LocalSourceNode field(DataFlow::TypeTracker t) {
t.start() and
- (
- result = getfirstResult()
- or
- result = getvalueResult()
- or
- // TODO: Should have better handling of subscripting
- result.asCfgNode().(SubscriptNode).getObject() = [instance(), fieldList()].asCfgNode()
- )
+ // TODO: Should have better handling of subscripting
+ result.asCfgNode().(SubscriptNode).getObject() =
+ [instance().getAUse(), fieldList()].asCfgNode()
or
exists(DataFlow::TypeTracker t2 | result = field(t2).track(t2, t))
}
/** Gets a reference to a field. */
- DataFlow::Node field() { result = field(DataFlow::TypeTracker::end()) }
+ DataFlow::Node field() {
+ result = getfirstResult().getAUse()
+ or
+ result = getvalueResult().getAUse()
+ or
+ field(DataFlow::TypeTracker::end()).flowsTo(result)
+ }
private class AdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
// Methods
nodeFrom = nodeTo.(DataFlow::AttrRead).getObject() and
- nodeFrom = instance() and
- nodeTo in [getvalueRef(), getfirstRef(), getlistRef()]
+ nodeFrom = instance().getAUse() and
+ nodeTo = [getvalueRef(), getfirstRef(), getlistRef()].getAUse()
or
nodeFrom.asCfgNode() = nodeTo.asCfgNode().(CallNode).getFunction() and
(
- nodeFrom = getvalueRef() and nodeTo = getvalueResult()
+ nodeFrom = getvalueRef().getAUse() and nodeTo = getvalueResult().getAnImmediateUse()
or
- nodeFrom = getfirstRef() and nodeTo = getfirstResult()
+ nodeFrom = getfirstRef().getAUse() and nodeTo = getfirstResult().getAnImmediateUse()
or
- nodeFrom = getlistRef() and nodeTo = getlistResult()
+ nodeFrom = getlistRef().getAUse() and nodeTo = getlistResult().getAnImmediateUse()
)
or
// Indexing
- nodeFrom in [instance(), fieldList()] and
+ nodeFrom in [instance().getAUse(), fieldList()] and
nodeTo.asCfgNode().(SubscriptNode).getObject() = nodeFrom.asCfgNode()
or
// Attributes on Field
@@ -1175,15 +748,7 @@ private module Stdlib {
// BaseHTTPServer (Python 2 only)
// ---------------------------------------------------------------------------
/** Gets a reference to the `BaseHTTPServer` module. */
- private DataFlow::Node baseHTTPServer(DataFlow::TypeTracker t) {
- t.start() and
- result = DataFlow::importNode("BaseHTTPServer")
- or
- exists(DataFlow::TypeTracker t2 | result = baseHTTPServer(t2).track(t2, t))
- }
-
- /** Gets a reference to the `BaseHTTPServer` module. */
- DataFlow::Node baseHTTPServer() { result = baseHTTPServer(DataFlow::TypeTracker::end()) }
+ API::Node baseHTTPServer() { result = API::moduleImport("BaseHTTPServer") }
/** Provides models for the `BaseHTTPServer` module. */
module BaseHTTPServer {
@@ -1192,18 +757,7 @@ private module Stdlib {
*/
module BaseHTTPRequestHandler {
/** Gets a reference to the `BaseHTTPServer.BaseHTTPRequestHandler` class. */
- private DataFlow::Node classRef(DataFlow::TypeTracker t) {
- t.start() and
- result = DataFlow::importNode("BaseHTTPServer" + "." + "BaseHTTPRequestHandler")
- or
- t.startInAttr("BaseHTTPRequestHandler") and
- result = baseHTTPServer()
- or
- exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
- }
-
- /** Gets a reference to the `BaseHTTPServer.BaseHTTPRequestHandler` class. */
- DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
+ API::Node classRef() { result = baseHTTPServer().getMember("BaseHTTPRequestHandler") }
}
}
@@ -1211,15 +765,7 @@ private module Stdlib {
// SimpleHTTPServer (Python 2 only)
// ---------------------------------------------------------------------------
/** Gets a reference to the `SimpleHTTPServer` module. */
- private DataFlow::Node simpleHTTPServer(DataFlow::TypeTracker t) {
- t.start() and
- result = DataFlow::importNode("SimpleHTTPServer")
- or
- exists(DataFlow::TypeTracker t2 | result = simpleHTTPServer(t2).track(t2, t))
- }
-
- /** Gets a reference to the `SimpleHTTPServer` module. */
- DataFlow::Node simpleHTTPServer() { result = simpleHTTPServer(DataFlow::TypeTracker::end()) }
+ API::Node simpleHTTPServer() { result = API::moduleImport("SimpleHTTPServer") }
/** Provides models for the `SimpleHTTPServer` module. */
module SimpleHTTPServer {
@@ -1228,18 +774,7 @@ private module Stdlib {
*/
module SimpleHTTPRequestHandler {
/** Gets a reference to the `SimpleHTTPServer.SimpleHTTPRequestHandler` class. */
- private DataFlow::Node classRef(DataFlow::TypeTracker t) {
- t.start() and
- result = DataFlow::importNode("SimpleHTTPServer" + "." + "SimpleHTTPRequestHandler")
- or
- t.startInAttr("SimpleHTTPRequestHandler") and
- result = simpleHTTPServer()
- or
- exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
- }
-
- /** Gets a reference to the `SimpleHTTPServer.SimpleHTTPRequestHandler` class. */
- DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
+ API::Node classRef() { result = simpleHTTPServer().getMember("SimpleHTTPRequestHandler") }
}
}
@@ -1247,15 +782,7 @@ private module Stdlib {
// CGIHTTPServer (Python 2 only)
// ---------------------------------------------------------------------------
/** Gets a reference to the `CGIHTTPServer` module. */
- private DataFlow::Node cgiHTTPServer(DataFlow::TypeTracker t) {
- t.start() and
- result = DataFlow::importNode("CGIHTTPServer")
- or
- exists(DataFlow::TypeTracker t2 | result = cgiHTTPServer(t2).track(t2, t))
- }
-
- /** Gets a reference to the `CGIHTTPServer` module. */
- DataFlow::Node cgiHTTPServer() { result = cgiHTTPServer(DataFlow::TypeTracker::end()) }
+ API::Node cgiHTTPServer() { result = API::moduleImport("CGIHTTPServer") }
/** Provides models for the `CGIHTTPServer` module. */
module CGIHTTPServer {
@@ -1264,18 +791,7 @@ private module Stdlib {
*/
module CGIHTTPRequestHandler {
/** Gets a reference to the `CGIHTTPServer.CGIHTTPRequestHandler` class. */
- private DataFlow::Node classRef(DataFlow::TypeTracker t) {
- t.start() and
- result = DataFlow::importNode("CGIHTTPServer" + "." + "CGIHTTPRequestHandler")
- or
- t.startInAttr("CGIHTTPRequestHandler") and
- result = cgiHTTPServer()
- or
- exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
- }
-
- /** Gets a reference to the `CGIHTTPServer.CGIHTTPRequestHandler` class. */
- DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
+ API::Node classRef() { result = cgiHTTPServer().getMember("CGIHTTPRequestHandler") }
}
}
@@ -1283,54 +799,13 @@ private module Stdlib {
// http (Python 3 only)
// ---------------------------------------------------------------------------
/** Gets a reference to the `http` module. */
- private DataFlow::Node http(DataFlow::TypeTracker t) {
- t.start() and
- result = DataFlow::importNode("http")
- or
- exists(DataFlow::TypeTracker t2 | result = http(t2).track(t2, t))
- }
-
- /** Gets a reference to the `http` module. */
- DataFlow::Node http() { result = http(DataFlow::TypeTracker::end()) }
-
- /**
- * Gets a reference to the attribute `attr_name` of the `http` module.
- * WARNING: Only holds for a few predefined attributes.
- */
- private DataFlow::Node http_attr(DataFlow::TypeTracker t, string attr_name) {
- attr_name in ["server"] and
- (
- t.start() and
- result = DataFlow::importNode("http" + "." + attr_name)
- or
- t.startInAttr(attr_name) and
- result = http()
- )
- or
- // Due to bad performance when using normal setup with `http_attr(t2, attr_name).track(t2, t)`
- // we have inlined that code and forced a join
- exists(DataFlow::TypeTracker t2 |
- exists(DataFlow::StepSummary summary |
- http_attr_first_join(t2, attr_name, result, summary) and
- t = t2.append(summary)
- )
- )
- }
-
- pragma[nomagic]
- private predicate http_attr_first_join(
- DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary
- ) {
- DataFlow::StepSummary::step(http_attr(t2, attr_name), res, summary)
- }
+ API::Node http() { result = API::moduleImport("http") }
/**
* Gets a reference to the attribute `attr_name` of the `http` module.
* WARNING: Only holds for a few predefined attributes.
*/
- private DataFlow::Node http_attr(string attr_name) {
- result = http_attr(DataFlow::TypeTracker::end(), attr_name)
- }
+ private API::Node http_attr(string attr_name) { result = http().getMember(attr_name) }
/** Provides models for the `http` module. */
module http {
@@ -1338,7 +813,7 @@ private module Stdlib {
// http.server
// -------------------------------------------------------------------------
/** Gets a reference to the `http.server` module. */
- DataFlow::Node server() { result = http_attr("server") }
+ API::Node server() { result = http_attr("server") }
/** Provides models for the `http.server` module */
module server {
@@ -1346,41 +821,7 @@ private module Stdlib {
* Gets a reference to the attribute `attr_name` of the `http.server` module.
* WARNING: Only holds for a few predefined attributes.
*/
- private DataFlow::Node server_attr(DataFlow::TypeTracker t, string attr_name) {
- attr_name in ["BaseHTTPRequestHandler", "SimpleHTTPRequestHandler", "CGIHTTPRequestHandler"] and
- (
- t.start() and
- result = DataFlow::importNode("http.server" + "." + attr_name)
- or
- t.startInAttr(attr_name) and
- result = server()
- )
- or
- // Due to bad performance when using normal setup with `server_attr(t2, attr_name).track(t2, t)`
- // we have inlined that code and forced a join
- exists(DataFlow::TypeTracker t2 |
- exists(DataFlow::StepSummary summary |
- server_attr_first_join(t2, attr_name, result, summary) and
- t = t2.append(summary)
- )
- )
- }
-
- pragma[nomagic]
- private predicate server_attr_first_join(
- DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
- DataFlow::StepSummary summary
- ) {
- DataFlow::StepSummary::step(server_attr(t2, attr_name), res, summary)
- }
-
- /**
- * Gets a reference to the attribute `attr_name` of the `http.server` module.
- * WARNING: Only holds for a few predefined attributes.
- */
- private DataFlow::Node server_attr(string attr_name) {
- result = server_attr(DataFlow::TypeTracker::end(), attr_name)
- }
+ private API::Node server_attr(string attr_name) { result = server().getMember(attr_name) }
/**
* Provides models for the `http.server.BaseHTTPRequestHandler` class (Python 3 only).
@@ -1389,7 +830,7 @@ private module Stdlib {
*/
module BaseHTTPRequestHandler {
/** Gets a reference to the `http.server.BaseHTTPRequestHandler` class. */
- DataFlow::Node classRef() { result = server_attr("BaseHTTPRequestHandler") }
+ API::Node classRef() { result = server_attr("BaseHTTPRequestHandler") }
}
/**
@@ -1399,7 +840,7 @@ private module Stdlib {
*/
module SimpleHTTPRequestHandler {
/** Gets a reference to the `http.server.SimpleHTTPRequestHandler` class. */
- DataFlow::Node classRef() { result = server_attr("SimpleHTTPRequestHandler") }
+ API::Node classRef() { result = server_attr("SimpleHTTPRequestHandler") }
}
/**
@@ -1409,7 +850,7 @@ private module Stdlib {
*/
module CGIHTTPRequestHandler {
/** Gets a reference to the `http.server.CGIHTTPRequestHandler` class. */
- DataFlow::Node classRef() { result = server_attr("CGIHTTPRequestHandler") }
+ API::Node classRef() { result = server_attr("CGIHTTPRequestHandler") }
}
}
}
@@ -1423,35 +864,23 @@ private module Stdlib {
*/
private module HTTPRequestHandler {
/** Gets a reference to the `BaseHTTPRequestHandler` class or any subclass. */
- private DataFlow::Node subclassRef(DataFlow::TypeTracker t) {
- // Python 2
- t.start() and
- result in [
+ API::Node subclassRef() {
+ result =
+ [
+ // Python 2
BaseHTTPServer::BaseHTTPRequestHandler::classRef(),
SimpleHTTPServer::SimpleHTTPRequestHandler::classRef(),
- CGIHTTPServer::CGIHTTPRequestHandler::classRef()
- ]
- or
- // Python 3
- t.start() and
- result in [
+ CGIHTTPServer::CGIHTTPRequestHandler::classRef(),
+ // Python 3
http::server::BaseHTTPRequestHandler::classRef(),
http::server::SimpleHTTPRequestHandler::classRef(),
http::server::CGIHTTPRequestHandler::classRef()
- ]
- or
- // subclasses in project code
- result.asExpr().(ClassExpr).getABase() = subclassRef(t.continue()).asExpr()
- or
- exists(DataFlow::TypeTracker t2 | result = subclassRef(t2).track(t2, t))
+ ].getASubclass*()
}
- /** Gets a reference to the `BaseHTTPRequestHandler` class or any subclass. */
- DataFlow::Node subclassRef() { result = subclassRef(DataFlow::TypeTracker::end()) }
-
/** A HTTPRequestHandler class definition (most likely in project code). */
class HTTPRequestHandlerClassDef extends Class {
- HTTPRequestHandlerClassDef() { this.getParent() = subclassRef().asExpr() }
+ HTTPRequestHandlerClassDef() { this.getParent() = subclassRef().getAUse().asExpr() }
}
/**
@@ -1475,7 +904,7 @@ private module Stdlib {
}
/** Gets a reference to an instance of the `BaseHTTPRequestHandler` class or any subclass. */
- private DataFlow::Node instance(DataFlow::TypeTracker t) {
+ private DataFlow::LocalSourceNode instance(DataFlow::TypeTracker t) {
t.start() and
result instanceof InstanceSource
or
@@ -1483,7 +912,7 @@ private module Stdlib {
}
/** Gets a reference to an instance of the `BaseHTTPRequestHandler` class or any subclass. */
- DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
+ DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
private class AdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
From 2329b3160105fd2b7bfc34af13563f740ae41df9 Mon Sep 17 00:00:00 2001
From: Mathias Vorreiter Pedersen
Date: Fri, 9 Apr 2021 20:49:45 +0200
Subject: [PATCH 156/433] C++: Replace the new SmartPointerPartialDefinition
with additional steps in AddressFlow.qll
---
.../cpp/dataflow/internal/AddressFlow.qll | 29 +++++
.../cpp/dataflow/internal/DataFlowUtil.qll | 14 ---
.../code/cpp/dataflow/internal/FlowVar.qll | 58 ---------
.../models/implementations/SmartPointer.qll | 2 +
.../cpp/models/interfaces/PointerWrapper.qll | 3 +
.../dataflow/taint-tests/localTaint.expected | 119 +++++++++++++++---
.../dataflow/taint-tests/smart_pointer.cpp | 33 +++++
7 files changed, 169 insertions(+), 89 deletions(-)
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/AddressFlow.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/AddressFlow.qll
index 7024f9d8598c..70af0b25c23a 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/AddressFlow.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/AddressFlow.qll
@@ -15,6 +15,7 @@
*/
private import cpp
+private import semmle.code.cpp.models.interfaces.PointerWrapper
/**
* Holds if `f` is an instantiation of the `std::move` or `std::forward`
@@ -58,6 +59,8 @@ private predicate pointerToLvalueStep(Expr pointerIn, Expr lvalueOut) {
pointerIn = lvalueOut.(ArrayExpr).getArrayBase().getFullyConverted()
or
pointerIn = lvalueOut.(PointerDereferenceExpr).getOperand().getFullyConverted()
+ or
+ pointerIn = lvalueOut.(OverloadedPointerDereferenceExpr).getQualifier().getFullyConverted()
}
private predicate lvalueToPointerStep(Expr lvalueIn, Expr pointerOut) {
@@ -94,6 +97,12 @@ private predicate pointerToPointerStep(Expr pointerIn, Expr pointerOut) {
private predicate lvalueToReferenceStep(Expr lvalueIn, Expr referenceOut) {
lvalueIn.getConversion() = referenceOut.(ReferenceToExpr)
+ or
+ exists(PointerWrapper wrapper, Call call | call = referenceOut |
+ referenceOut.getUnspecifiedType() instanceof ReferenceType and
+ call = wrapper.getAnUnwrapperFunction().getACallToThisFunction() and
+ lvalueIn = call.getQualifier().getFullyConverted()
+ )
}
private predicate referenceToLvalueStep(Expr referenceIn, Expr lvalueOut) {
@@ -106,6 +115,13 @@ private predicate referenceToPointerStep(Expr referenceIn, Expr pointerOut) {
stdAddressOf(call.getTarget()) and
referenceIn = call.getArgument(0).getFullyConverted()
)
+ or
+ exists(CopyConstructor copy, Call call | call = pointerOut |
+ copy.getDeclaringType() instanceof PointerWrapper and
+ call.getTarget() = copy and
+ // The 0'th argument is the value being copied.
+ referenceIn = call.getArgument(0).getFullyConverted()
+ )
}
private predicate referenceToReferenceStep(Expr referenceIn, Expr referenceOut) {
@@ -190,6 +206,19 @@ private predicate pointerToUpdate(Expr pointer, Expr outer, ControlFlowNode node
// See the `lvalueToUpdate` case for an explanation of this conjunct.
call.getType().isDeeplyConstBelow()
)
+ or
+ // Pointer wrappers behave as raw pointers for dataflow purposes.
+ outer = call.getAnArgument().getFullyConverted() and
+ exists(PointerWrapper wrapper | wrapper = outer.getType().stripTopLevelSpecifiers() |
+ not wrapper.pointsToConst()
+ )
+ or
+ outer = call.getQualifier().getFullyConverted() and
+ outer.getUnspecifiedType() instanceof PointerWrapper and
+ not (
+ call.getTarget().hasSpecifier("const") and
+ call.getType().isDeeplyConstBelow()
+ )
)
or
exists(PointerFieldAccess fa |
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll
index 810c74e3e699..fd91638b7aa1 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll
@@ -199,8 +199,6 @@ class DefinitionByReferenceOrIteratorNode extends PartialDefinitionNode {
this.getPartialDefinition() instanceof DefinitionByReference
or
this.getPartialDefinition() instanceof DefinitionByIterator
- or
- this.getPartialDefinition() instanceof DefinitionBySmartPointer
)
}
@@ -332,18 +330,6 @@ private class IteratorPartialDefinitionNode extends PartialDefinitionNode {
override Node getPreUpdateNode() { pd.definesExpressions(_, result.asExpr()) }
}
-/**
- * INTERNAL: do not use.
- *
- * A synthetic data flow node used for flow into a collection when a smart pointer
- * write occurs in a callee.
- */
-private class SmartPointerPartialDefinitionNode extends PartialDefinitionNode {
- override SmartPointerPartialDefinition pd;
-
- override Node getPreUpdateNode() { pd.definesExpressions(_, result.asExpr()) }
-}
-
/**
* A post-update node on the `e->f` in `f(&e->f)` (and other forms).
*/
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/FlowVar.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/FlowVar.qll
index 0e5d6ea5fa74..79728629a9a1 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/FlowVar.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/FlowVar.qll
@@ -242,39 +242,6 @@ private module PartialDefinitions {
}
}
- class SmartPointerPartialDefinition extends PartialDefinition {
- Variable pointer;
- Expr innerDefinedExpr;
-
- SmartPointerPartialDefinition() {
- innerDefinedExpr = pragma[only_bind_out](getInnerDefinedExpr(this, node)) and
- innerDefinedExpr = getAPointerWrapperAccess(pointer, -1)
- or
- // Pointer wrappers passed to a function by value.
- exists(Call call |
- call = node and
- call.getAnArgument() = innerDefinedExpr and
- innerDefinedExpr = this and
- this = getAPointerWrapperAccess(pointer, 0) and
- not call instanceof OverloadedPointerDereferenceExpr
- )
- }
-
- deprecated override predicate partiallyDefines(Variable v) { v = pointer }
-
- deprecated override predicate partiallyDefinesThis(ThisExpr e) { none() }
-
- override predicate definesExpressions(Expr inner, Expr outer) {
- inner = innerDefinedExpr and
- outer = this
- }
-
- override predicate partiallyDefinesVariableAt(Variable v, ControlFlowNode cfn) {
- v = pointer and
- cfn = node
- }
- }
-
/**
* A partial definition that's a definition via an output iterator.
*/
@@ -288,15 +255,6 @@ private module PartialDefinitions {
class DefinitionByReference extends VariablePartialDefinition {
DefinitionByReference() { exists(Call c | this = c.getAnArgument() or this = c.getQualifier()) }
}
-
- /**
- * A partial definition that's a definition via a smart pointer being passed into a function.
- */
- class DefinitionBySmartPointer extends SmartPointerPartialDefinition {
- DefinitionBySmartPointer() {
- exists(Call c | this = c.getAnArgument() or this = c.getQualifier())
- }
- }
}
import PartialDefinitions
@@ -876,22 +834,6 @@ module FlowVar_internal {
)
}
- /**
- * Gets either:
- * - A call to an unwrapper function, where an access to `pointer` is the qualifier, or
- * - A call to the `CopyConstructor` that copies the value of an access to `pointer`.
- */
- Call getAPointerWrapperAccess(Variable pointer, int n) {
- exists(PointerWrapper wrapper | wrapper = pointer.getUnspecifiedType().stripType() |
- n = -1 and
- result.getQualifier() = pointer.getAnAccess() and
- result.getTarget() = wrapper.getAnUnwrapperFunction()
- or
- result.getArgument(n) = pointer.getAnAccess() and
- result.getTarget() instanceof CopyConstructor
- )
- }
-
class IteratorParameter extends Parameter {
IteratorParameter() { this.getUnspecifiedType() instanceof Iterator }
}
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/SmartPointer.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/SmartPointer.qll
index 4bcf5aa3575f..4b060c234f89 100644
--- a/cpp/ql/src/semmle/code/cpp/models/implementations/SmartPointer.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/SmartPointer.qll
@@ -13,6 +13,8 @@ private class UniqueOrSharedPtr extends Class, PointerWrapper {
or
result.getClassAndName(["operator->", "get"]) = this
}
+
+ override predicate pointsToConst() { this.getTemplateArgument(0).(Type).isConst() }
}
/** Any function that unwraps a pointer wrapper class to reveal the underlying pointer. */
diff --git a/cpp/ql/src/semmle/code/cpp/models/interfaces/PointerWrapper.qll b/cpp/ql/src/semmle/code/cpp/models/interfaces/PointerWrapper.qll
index 447e7f7cedea..8948aee424b2 100644
--- a/cpp/ql/src/semmle/code/cpp/models/interfaces/PointerWrapper.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/interfaces/PointerWrapper.qll
@@ -11,4 +11,7 @@ abstract class PointerWrapper extends Class {
* that return a reference to the pointed-to object.
*/
abstract MemberFunction getAnUnwrapperFunction();
+
+ /** Holds if the type of the data that is pointed to by this pointer wrapper is `const`. */
+ abstract predicate pointsToConst();
}
diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected b/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected
index ad8b5bbb98e9..dd15fa7cd983 100644
--- a/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected
+++ b/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected
@@ -3223,47 +3223,55 @@
| smart_pointer.cpp:11:30:11:50 | call to make_shared | smart_pointer.cpp:12:11:12:11 | p | |
| smart_pointer.cpp:11:30:11:50 | call to make_shared | smart_pointer.cpp:13:10:13:10 | p | |
| smart_pointer.cpp:11:52:11:57 | call to source | smart_pointer.cpp:11:30:11:50 | call to make_shared | TAINT |
-| smart_pointer.cpp:12:11:12:11 | p | smart_pointer.cpp:12:10:12:10 | call to operator* | TAINT |
+| smart_pointer.cpp:12:11:12:11 | p | smart_pointer.cpp:12:10:12:10 | call to operator* | |
| smart_pointer.cpp:12:11:12:11 | ref arg p | smart_pointer.cpp:13:10:13:10 | p | |
| smart_pointer.cpp:17:32:17:54 | call to make_shared | smart_pointer.cpp:18:11:18:11 | p | |
| smart_pointer.cpp:17:32:17:54 | call to make_shared | smart_pointer.cpp:19:10:19:10 | p | |
+| smart_pointer.cpp:18:10:18:10 | ref arg call to operator* | smart_pointer.cpp:18:11:18:11 | p [inner post update] | |
| smart_pointer.cpp:18:10:18:10 | ref arg call to operator* | smart_pointer.cpp:19:10:19:10 | p | |
| smart_pointer.cpp:18:11:18:11 | p | smart_pointer.cpp:18:10:18:10 | call to operator* | TAINT |
+| smart_pointer.cpp:18:11:18:11 | ref arg p | smart_pointer.cpp:18:11:18:11 | p [inner post update] | |
| smart_pointer.cpp:18:11:18:11 | ref arg p | smart_pointer.cpp:19:10:19:10 | p | |
| smart_pointer.cpp:23:30:23:50 | call to make_unique | smart_pointer.cpp:24:11:24:11 | p | |
| smart_pointer.cpp:23:30:23:50 | call to make_unique | smart_pointer.cpp:25:10:25:10 | p | |
| smart_pointer.cpp:23:52:23:57 | call to source | smart_pointer.cpp:23:30:23:50 | call to make_unique | TAINT |
-| smart_pointer.cpp:24:11:24:11 | p | smart_pointer.cpp:24:10:24:10 | call to operator* | TAINT |
+| smart_pointer.cpp:24:11:24:11 | p | smart_pointer.cpp:24:10:24:10 | call to operator* | |
| smart_pointer.cpp:24:11:24:11 | ref arg p | smart_pointer.cpp:25:10:25:10 | p | |
| smart_pointer.cpp:29:32:29:54 | call to make_unique | smart_pointer.cpp:30:11:30:11 | p | |
| smart_pointer.cpp:29:32:29:54 | call to make_unique | smart_pointer.cpp:31:10:31:10 | p | |
+| smart_pointer.cpp:30:10:30:10 | ref arg call to operator* | smart_pointer.cpp:30:11:30:11 | p [inner post update] | |
| smart_pointer.cpp:30:10:30:10 | ref arg call to operator* | smart_pointer.cpp:31:10:31:10 | p | |
| smart_pointer.cpp:30:11:30:11 | p | smart_pointer.cpp:30:10:30:10 | call to operator* | TAINT |
+| smart_pointer.cpp:30:11:30:11 | ref arg p | smart_pointer.cpp:30:11:30:11 | p [inner post update] | |
| smart_pointer.cpp:30:11:30:11 | ref arg p | smart_pointer.cpp:31:10:31:10 | p | |
| smart_pointer.cpp:35:30:35:50 | call to make_shared | smart_pointer.cpp:37:6:37:6 | p | |
| smart_pointer.cpp:35:30:35:50 | call to make_shared | smart_pointer.cpp:38:10:38:10 | p | |
| smart_pointer.cpp:35:30:35:50 | call to make_shared | smart_pointer.cpp:39:11:39:11 | p | |
+| smart_pointer.cpp:37:5:37:5 | call to operator* [post update] | smart_pointer.cpp:37:6:37:6 | p [inner post update] | |
| smart_pointer.cpp:37:5:37:5 | call to operator* [post update] | smart_pointer.cpp:38:10:38:10 | p | |
| smart_pointer.cpp:37:5:37:5 | call to operator* [post update] | smart_pointer.cpp:39:11:39:11 | p | |
| smart_pointer.cpp:37:5:37:17 | ... = ... | smart_pointer.cpp:37:5:37:5 | call to operator* [post update] | |
-| smart_pointer.cpp:37:6:37:6 | p | smart_pointer.cpp:37:5:37:5 | call to operator* | TAINT |
+| smart_pointer.cpp:37:6:37:6 | p | smart_pointer.cpp:37:5:37:5 | call to operator* | |
+| smart_pointer.cpp:37:6:37:6 | ref arg p | smart_pointer.cpp:37:6:37:6 | p [inner post update] | |
| smart_pointer.cpp:37:6:37:6 | ref arg p | smart_pointer.cpp:38:10:38:10 | p | |
| smart_pointer.cpp:37:6:37:6 | ref arg p | smart_pointer.cpp:39:11:39:11 | p | |
| smart_pointer.cpp:37:10:37:15 | call to source | smart_pointer.cpp:37:5:37:17 | ... = ... | |
| smart_pointer.cpp:38:10:38:10 | ref arg p | smart_pointer.cpp:39:11:39:11 | p | |
-| smart_pointer.cpp:39:11:39:11 | p | smart_pointer.cpp:39:10:39:10 | call to operator* | TAINT |
+| smart_pointer.cpp:39:11:39:11 | p | smart_pointer.cpp:39:10:39:10 | call to operator* | |
| smart_pointer.cpp:43:29:43:51 | call to unique_ptr | smart_pointer.cpp:45:6:45:6 | p | |
| smart_pointer.cpp:43:29:43:51 | call to unique_ptr | smart_pointer.cpp:46:10:46:10 | p | |
| smart_pointer.cpp:43:29:43:51 | call to unique_ptr | smart_pointer.cpp:47:11:47:11 | p | |
+| smart_pointer.cpp:45:5:45:5 | call to operator* [post update] | smart_pointer.cpp:45:6:45:6 | p [inner post update] | |
| smart_pointer.cpp:45:5:45:5 | call to operator* [post update] | smart_pointer.cpp:46:10:46:10 | p | |
| smart_pointer.cpp:45:5:45:5 | call to operator* [post update] | smart_pointer.cpp:47:11:47:11 | p | |
| smart_pointer.cpp:45:5:45:17 | ... = ... | smart_pointer.cpp:45:5:45:5 | call to operator* [post update] | |
-| smart_pointer.cpp:45:6:45:6 | p | smart_pointer.cpp:45:5:45:5 | call to operator* | TAINT |
+| smart_pointer.cpp:45:6:45:6 | p | smart_pointer.cpp:45:5:45:5 | call to operator* | |
+| smart_pointer.cpp:45:6:45:6 | ref arg p | smart_pointer.cpp:45:6:45:6 | p [inner post update] | |
| smart_pointer.cpp:45:6:45:6 | ref arg p | smart_pointer.cpp:46:10:46:10 | p | |
| smart_pointer.cpp:45:6:45:6 | ref arg p | smart_pointer.cpp:47:11:47:11 | p | |
| smart_pointer.cpp:45:10:45:15 | call to source | smart_pointer.cpp:45:5:45:17 | ... = ... | |
| smart_pointer.cpp:46:10:46:10 | ref arg p | smart_pointer.cpp:47:11:47:11 | p | |
-| smart_pointer.cpp:47:11:47:11 | p | smart_pointer.cpp:47:10:47:10 | call to operator* | TAINT |
+| smart_pointer.cpp:47:11:47:11 | p | smart_pointer.cpp:47:10:47:10 | call to operator* | |
| smart_pointer.cpp:51:30:51:50 | call to make_shared | smart_pointer.cpp:52:10:52:10 | p | |
| smart_pointer.cpp:51:52:51:57 | call to source | smart_pointer.cpp:51:30:51:50 | call to make_shared | TAINT |
| smart_pointer.cpp:52:10:52:10 | p | smart_pointer.cpp:52:12:52:14 | call to get | |
@@ -3282,15 +3290,18 @@
| smart_pointer.cpp:70:37:70:39 | ptr | smart_pointer.cpp:70:37:70:39 | ptr | |
| smart_pointer.cpp:70:37:70:39 | ptr | smart_pointer.cpp:71:4:71:6 | ptr | |
| smart_pointer.cpp:71:3:71:3 | call to operator* [post update] | smart_pointer.cpp:70:37:70:39 | ptr | |
+| smart_pointer.cpp:71:3:71:3 | call to operator* [post update] | smart_pointer.cpp:71:4:71:6 | ptr [inner post update] | |
| smart_pointer.cpp:71:3:71:17 | ... = ... | smart_pointer.cpp:71:3:71:3 | call to operator* [post update] | |
-| smart_pointer.cpp:71:4:71:6 | ptr | smart_pointer.cpp:71:3:71:3 | call to operator* | TAINT |
+| smart_pointer.cpp:71:4:71:6 | ptr | smart_pointer.cpp:71:3:71:3 | call to operator* | |
| smart_pointer.cpp:71:4:71:6 | ref arg ptr | smart_pointer.cpp:70:37:70:39 | ptr | |
+| smart_pointer.cpp:71:4:71:6 | ref arg ptr | smart_pointer.cpp:71:4:71:6 | ptr [inner post update] | |
| smart_pointer.cpp:71:10:71:15 | call to source | smart_pointer.cpp:71:3:71:17 | ... = ... | |
| smart_pointer.cpp:75:26:75:33 | call to shared_ptr | smart_pointer.cpp:76:13:76:13 | p | |
| smart_pointer.cpp:75:26:75:33 | call to shared_ptr | smart_pointer.cpp:77:9:77:9 | p | |
-| smart_pointer.cpp:76:13:76:13 | call to shared_ptr [post update] | smart_pointer.cpp:77:9:77:9 | p | |
| smart_pointer.cpp:76:13:76:13 | p | smart_pointer.cpp:76:13:76:13 | call to shared_ptr | |
-| smart_pointer.cpp:77:9:77:9 | p | smart_pointer.cpp:77:8:77:8 | call to operator* | TAINT |
+| smart_pointer.cpp:76:13:76:13 | ref arg call to shared_ptr | smart_pointer.cpp:76:13:76:13 | p [inner post update] | |
+| smart_pointer.cpp:76:13:76:13 | ref arg call to shared_ptr | smart_pointer.cpp:77:9:77:9 | p | |
+| smart_pointer.cpp:77:9:77:9 | p | smart_pointer.cpp:77:8:77:8 | call to operator* | |
| smart_pointer.cpp:86:45:86:45 | p | smart_pointer.cpp:86:45:86:45 | p | |
| smart_pointer.cpp:86:45:86:45 | p | smart_pointer.cpp:87:3:87:3 | p | |
| smart_pointer.cpp:86:45:86:45 | p | smart_pointer.cpp:88:8:88:8 | p | |
@@ -3306,10 +3317,7 @@
| smart_pointer.cpp:87:3:87:3 | ref arg p | smart_pointer.cpp:89:8:89:8 | p | |
| smart_pointer.cpp:87:3:87:17 | ... = ... | smart_pointer.cpp:87:6:87:6 | x [post update] | |
| smart_pointer.cpp:87:3:87:17 | ... = ... | smart_pointer.cpp:88:11:88:11 | x | |
-| smart_pointer.cpp:87:4:87:4 | call to operator-> [post update] | smart_pointer.cpp:86:45:86:45 | p | |
| smart_pointer.cpp:87:4:87:4 | call to operator-> [post update] | smart_pointer.cpp:87:3:87:3 | ref arg p | |
-| smart_pointer.cpp:87:4:87:4 | call to operator-> [post update] | smart_pointer.cpp:88:8:88:8 | p | |
-| smart_pointer.cpp:87:4:87:4 | call to operator-> [post update] | smart_pointer.cpp:89:8:89:8 | p | |
| smart_pointer.cpp:87:10:87:15 | call to source | smart_pointer.cpp:87:3:87:17 | ... = ... | |
| smart_pointer.cpp:88:8:88:8 | p | smart_pointer.cpp:88:9:88:9 | call to operator-> | |
| smart_pointer.cpp:88:8:88:8 | ref arg p | smart_pointer.cpp:86:45:86:45 | p | |
@@ -3323,11 +3331,7 @@
| smart_pointer.cpp:91:3:91:3 | ref arg q | smart_pointer.cpp:94:8:94:8 | q | |
| smart_pointer.cpp:91:3:91:20 | ... = ... | smart_pointer.cpp:91:9:91:9 | x [post update] | |
| smart_pointer.cpp:91:3:91:20 | ... = ... | smart_pointer.cpp:92:14:92:14 | x | |
-| smart_pointer.cpp:91:4:91:4 | call to operator-> [post update] | smart_pointer.cpp:86:67:86:67 | q | |
| smart_pointer.cpp:91:4:91:4 | call to operator-> [post update] | smart_pointer.cpp:91:3:91:3 | ref arg q | |
-| smart_pointer.cpp:91:4:91:4 | call to operator-> [post update] | smart_pointer.cpp:92:8:92:8 | q | |
-| smart_pointer.cpp:91:4:91:4 | call to operator-> [post update] | smart_pointer.cpp:93:8:93:8 | q | |
-| smart_pointer.cpp:91:4:91:4 | call to operator-> [post update] | smart_pointer.cpp:94:8:94:8 | q | |
| smart_pointer.cpp:91:13:91:18 | call to source | smart_pointer.cpp:91:3:91:20 | ... = ... | |
| smart_pointer.cpp:92:8:92:8 | q | smart_pointer.cpp:92:9:92:9 | call to operator-> | |
| smart_pointer.cpp:92:8:92:8 | ref arg q | smart_pointer.cpp:86:67:86:67 | q | |
@@ -3346,8 +3350,80 @@
| smart_pointer.cpp:103:11:103:11 | p | smart_pointer.cpp:103:13:103:15 | call to get | |
| smart_pointer.cpp:103:11:103:11 | ref arg p | smart_pointer.cpp:104:8:104:8 | p | |
| smart_pointer.cpp:103:13:103:15 | ref arg call to get | smart_pointer.cpp:103:11:103:11 | ref arg p | |
-| smart_pointer.cpp:103:13:103:15 | ref arg call to get | smart_pointer.cpp:104:8:104:8 | p | |
| smart_pointer.cpp:104:8:104:8 | p | smart_pointer.cpp:104:9:104:9 | call to operator-> | |
+| smart_pointer.cpp:112:40:112:42 | ptr | smart_pointer.cpp:112:40:112:42 | ptr | |
+| smart_pointer.cpp:112:40:112:42 | ptr | smart_pointer.cpp:113:2:113:4 | ptr | |
+| smart_pointer.cpp:113:2:113:4 | ptr | smart_pointer.cpp:113:5:113:5 | call to operator-> | |
+| smart_pointer.cpp:113:2:113:4 | ref arg ptr | smart_pointer.cpp:112:40:112:42 | ptr | |
+| smart_pointer.cpp:113:2:113:18 | ... = ... | smart_pointer.cpp:113:7:113:7 | x [post update] | |
+| smart_pointer.cpp:113:5:113:5 | call to operator-> [post update] | smart_pointer.cpp:113:2:113:4 | ref arg ptr | |
+| smart_pointer.cpp:113:11:113:16 | call to source | smart_pointer.cpp:113:2:113:18 | ... = ... | |
+| smart_pointer.cpp:116:52:116:54 | ptr | smart_pointer.cpp:117:2:117:4 | ptr | |
+| smart_pointer.cpp:117:2:117:4 | ptr | smart_pointer.cpp:117:5:117:5 | call to operator-> | |
+| smart_pointer.cpp:117:2:117:18 | ... = ... | smart_pointer.cpp:117:7:117:7 | x [post update] | |
+| smart_pointer.cpp:117:5:117:5 | call to operator-> [post update] | smart_pointer.cpp:117:2:117:4 | ref arg ptr | |
+| smart_pointer.cpp:117:11:117:16 | call to source | smart_pointer.cpp:117:2:117:18 | ... = ... | |
+| smart_pointer.cpp:120:48:120:50 | ptr | smart_pointer.cpp:121:4:121:6 | ptr | |
+| smart_pointer.cpp:121:3:121:3 | call to operator* [post update] | smart_pointer.cpp:121:4:121:6 | ptr [inner post update] | |
+| smart_pointer.cpp:121:3:121:17 | ... = ... | smart_pointer.cpp:121:3:121:3 | call to operator* [post update] | |
+| smart_pointer.cpp:121:4:121:6 | ptr | smart_pointer.cpp:121:3:121:3 | call to operator* | |
+| smart_pointer.cpp:121:4:121:6 | ref arg ptr | smart_pointer.cpp:121:4:121:6 | ptr [inner post update] | |
+| smart_pointer.cpp:121:10:121:15 | call to source | smart_pointer.cpp:121:3:121:17 | ... = ... | |
+| smart_pointer.cpp:124:48:124:49 | p1 | smart_pointer.cpp:124:48:124:49 | p1 | |
+| smart_pointer.cpp:124:48:124:49 | p1 | smart_pointer.cpp:125:18:125:19 | p1 | |
+| smart_pointer.cpp:124:48:124:49 | p1 | smart_pointer.cpp:126:8:126:9 | p1 | |
+| smart_pointer.cpp:124:90:124:91 | p2 | smart_pointer.cpp:124:90:124:91 | p2 | |
+| smart_pointer.cpp:124:90:124:91 | p2 | smart_pointer.cpp:128:14:128:15 | p2 | |
+| smart_pointer.cpp:124:90:124:91 | p2 | smart_pointer.cpp:129:10:129:11 | p2 | |
+| smart_pointer.cpp:125:18:125:19 | p1 | smart_pointer.cpp:125:20:125:20 | call to operator-> | |
+| smart_pointer.cpp:125:18:125:19 | ref arg p1 | smart_pointer.cpp:124:48:124:49 | p1 | |
+| smart_pointer.cpp:125:18:125:19 | ref arg p1 | smart_pointer.cpp:126:8:126:9 | p1 | |
+| smart_pointer.cpp:125:18:125:22 | ref arg call to shared_ptr | smart_pointer.cpp:125:22:125:22 | q [inner post update] | |
+| smart_pointer.cpp:125:20:125:20 | call to operator-> [post update] | smart_pointer.cpp:125:18:125:19 | ref arg p1 | |
+| smart_pointer.cpp:125:22:125:22 | q | smart_pointer.cpp:125:18:125:22 | call to shared_ptr | |
+| smart_pointer.cpp:126:8:126:9 | p1 | smart_pointer.cpp:126:10:126:10 | call to operator-> | |
+| smart_pointer.cpp:126:8:126:9 | ref arg p1 | smart_pointer.cpp:124:48:124:49 | p1 | |
+| smart_pointer.cpp:126:10:126:10 | call to operator-> [post update] | smart_pointer.cpp:126:8:126:9 | ref arg p1 | |
+| smart_pointer.cpp:126:12:126:12 | q | smart_pointer.cpp:126:13:126:13 | call to operator-> | |
+| smart_pointer.cpp:128:13:128:13 | call to operator* | smart_pointer.cpp:128:13:128:15 | call to shared_ptr | TAINT |
+| smart_pointer.cpp:128:13:128:15 | ref arg call to shared_ptr | smart_pointer.cpp:124:90:124:91 | p2 | |
+| smart_pointer.cpp:128:13:128:15 | ref arg call to shared_ptr | smart_pointer.cpp:128:13:128:13 | call to operator* [inner post update] | |
+| smart_pointer.cpp:128:13:128:15 | ref arg call to shared_ptr | smart_pointer.cpp:128:14:128:15 | p2 [inner post update] | |
+| smart_pointer.cpp:128:13:128:15 | ref arg call to shared_ptr | smart_pointer.cpp:129:10:129:11 | p2 | |
+| smart_pointer.cpp:128:14:128:15 | p2 | smart_pointer.cpp:128:13:128:13 | call to operator* | TAINT |
+| smart_pointer.cpp:128:14:128:15 | ref arg p2 | smart_pointer.cpp:124:90:124:91 | p2 | |
+| smart_pointer.cpp:128:14:128:15 | ref arg p2 | smart_pointer.cpp:128:14:128:15 | p2 [inner post update] | |
+| smart_pointer.cpp:128:14:128:15 | ref arg p2 | smart_pointer.cpp:129:10:129:11 | p2 | |
+| smart_pointer.cpp:129:9:129:9 | call to operator* | smart_pointer.cpp:129:8:129:8 | call to operator* | TAINT |
+| smart_pointer.cpp:129:9:129:9 | ref arg call to operator* | smart_pointer.cpp:124:90:124:91 | p2 | |
+| smart_pointer.cpp:129:9:129:9 | ref arg call to operator* | smart_pointer.cpp:129:10:129:11 | p2 [inner post update] | |
+| smart_pointer.cpp:129:10:129:11 | p2 | smart_pointer.cpp:129:8:129:8 | call to operator* | |
+| smart_pointer.cpp:129:10:129:11 | p2 | smart_pointer.cpp:129:9:129:9 | call to operator* | TAINT |
+| smart_pointer.cpp:129:10:129:11 | ref arg p2 | smart_pointer.cpp:124:90:124:91 | p2 | |
+| smart_pointer.cpp:129:10:129:11 | ref arg p2 | smart_pointer.cpp:129:10:129:11 | p2 [inner post update] | |
+| smart_pointer.cpp:132:53:132:54 | p1 | smart_pointer.cpp:132:53:132:54 | p1 | |
+| smart_pointer.cpp:132:53:132:54 | p1 | smart_pointer.cpp:133:23:133:24 | p1 | |
+| smart_pointer.cpp:132:53:132:54 | p1 | smart_pointer.cpp:134:8:134:9 | p1 | |
+| smart_pointer.cpp:132:95:132:96 | p2 | smart_pointer.cpp:132:95:132:96 | p2 | |
+| smart_pointer.cpp:132:95:132:96 | p2 | smart_pointer.cpp:136:18:136:19 | p2 | |
+| smart_pointer.cpp:132:95:132:96 | p2 | smart_pointer.cpp:137:10:137:11 | p2 | |
+| smart_pointer.cpp:133:23:133:24 | p1 | smart_pointer.cpp:133:25:133:25 | call to operator-> | |
+| smart_pointer.cpp:133:23:133:24 | ref arg p1 | smart_pointer.cpp:132:53:132:54 | p1 | |
+| smart_pointer.cpp:133:23:133:24 | ref arg p1 | smart_pointer.cpp:134:8:134:9 | p1 | |
+| smart_pointer.cpp:134:8:134:9 | p1 | smart_pointer.cpp:134:10:134:10 | call to operator-> | |
+| smart_pointer.cpp:134:8:134:9 | ref arg p1 | smart_pointer.cpp:132:53:132:54 | p1 | |
+| smart_pointer.cpp:134:10:134:10 | call to operator-> [post update] | smart_pointer.cpp:134:8:134:9 | ref arg p1 | |
+| smart_pointer.cpp:134:12:134:12 | q | smart_pointer.cpp:134:13:134:13 | call to operator-> | |
+| smart_pointer.cpp:136:18:136:19 | p2 | smart_pointer.cpp:136:17:136:17 | call to operator* | TAINT |
+| smart_pointer.cpp:136:18:136:19 | ref arg p2 | smart_pointer.cpp:132:95:132:96 | p2 | |
+| smart_pointer.cpp:136:18:136:19 | ref arg p2 | smart_pointer.cpp:137:10:137:11 | p2 | |
+| smart_pointer.cpp:137:9:137:9 | call to operator* | smart_pointer.cpp:137:8:137:8 | call to operator* | TAINT |
+| smart_pointer.cpp:137:9:137:9 | ref arg call to operator* | smart_pointer.cpp:132:95:132:96 | p2 | |
+| smart_pointer.cpp:137:9:137:9 | ref arg call to operator* | smart_pointer.cpp:137:10:137:11 | p2 [inner post update] | |
+| smart_pointer.cpp:137:10:137:11 | p2 | smart_pointer.cpp:137:8:137:8 | call to operator* | |
+| smart_pointer.cpp:137:10:137:11 | p2 | smart_pointer.cpp:137:9:137:9 | call to operator* | TAINT |
+| smart_pointer.cpp:137:10:137:11 | ref arg p2 | smart_pointer.cpp:132:95:132:96 | p2 | |
+| smart_pointer.cpp:137:10:137:11 | ref arg p2 | smart_pointer.cpp:137:10:137:11 | p2 [inner post update] | |
| standalone_iterators.cpp:39:45:39:51 | source1 | standalone_iterators.cpp:39:45:39:51 | source1 | |
| standalone_iterators.cpp:39:45:39:51 | source1 | standalone_iterators.cpp:40:11:40:17 | source1 | |
| standalone_iterators.cpp:39:45:39:51 | source1 | standalone_iterators.cpp:41:12:41:18 | source1 | |
@@ -3383,20 +3459,26 @@
| standalone_iterators.cpp:85:35:85:36 | c1 | standalone_iterators.cpp:85:38:85:42 | call to begin | TAINT |
| standalone_iterators.cpp:85:35:85:36 | ref arg c1 | standalone_iterators.cpp:87:10:87:11 | c1 | |
| standalone_iterators.cpp:85:38:85:42 | call to begin | standalone_iterators.cpp:86:6:86:7 | i1 | |
+| standalone_iterators.cpp:86:5:86:5 | ref arg call to operator* | standalone_iterators.cpp:86:8:86:8 | call to operator-- [inner post update] | |
| standalone_iterators.cpp:86:5:86:5 | ref arg call to operator* | standalone_iterators.cpp:86:8:86:8 | ref arg call to operator-- | TAINT |
| standalone_iterators.cpp:86:5:86:5 | ref arg call to operator* | standalone_iterators.cpp:87:10:87:11 | c1 | |
| standalone_iterators.cpp:86:6:86:7 | i1 | standalone_iterators.cpp:86:8:86:8 | call to operator-- | |
| standalone_iterators.cpp:86:8:86:8 | call to operator-- | standalone_iterators.cpp:86:5:86:5 | call to operator* | TAINT |
+| standalone_iterators.cpp:86:8:86:8 | call to operator-- [inner post update] | standalone_iterators.cpp:86:6:86:7 | ref arg i1 | |
| standalone_iterators.cpp:86:8:86:8 | ref arg call to operator-- | standalone_iterators.cpp:86:6:86:7 | ref arg i1 | |
+| standalone_iterators.cpp:86:8:86:8 | ref arg call to operator-- | standalone_iterators.cpp:86:8:86:8 | call to operator-- [inner post update] | |
| standalone_iterators.cpp:86:13:86:18 | call to source | standalone_iterators.cpp:86:5:86:5 | ref arg call to operator* | TAINT |
| standalone_iterators.cpp:89:35:89:36 | c2 | standalone_iterators.cpp:89:38:89:42 | call to begin | TAINT |
| standalone_iterators.cpp:89:35:89:36 | ref arg c2 | standalone_iterators.cpp:91:10:91:11 | c2 | |
| standalone_iterators.cpp:89:38:89:42 | call to begin | standalone_iterators.cpp:90:6:90:7 | i2 | |
+| standalone_iterators.cpp:90:5:90:5 | ref arg call to operator* | standalone_iterators.cpp:90:8:90:8 | call to operator-- [inner post update] | |
| standalone_iterators.cpp:90:5:90:5 | ref arg call to operator* | standalone_iterators.cpp:90:8:90:8 | ref arg call to operator-- | TAINT |
| standalone_iterators.cpp:90:5:90:5 | ref arg call to operator* | standalone_iterators.cpp:91:10:91:11 | c2 | |
| standalone_iterators.cpp:90:6:90:7 | i2 | standalone_iterators.cpp:90:8:90:8 | call to operator-- | |
| standalone_iterators.cpp:90:8:90:8 | call to operator-- | standalone_iterators.cpp:90:5:90:5 | call to operator* | TAINT |
+| standalone_iterators.cpp:90:8:90:8 | call to operator-- [inner post update] | standalone_iterators.cpp:90:6:90:7 | ref arg i2 | |
| standalone_iterators.cpp:90:8:90:8 | ref arg call to operator-- | standalone_iterators.cpp:90:6:90:7 | ref arg i2 | |
+| standalone_iterators.cpp:90:8:90:8 | ref arg call to operator-- | standalone_iterators.cpp:90:8:90:8 | call to operator-- [inner post update] | |
| standalone_iterators.cpp:90:13:90:13 | 0 | standalone_iterators.cpp:90:5:90:5 | ref arg call to operator* | TAINT |
| standalone_iterators.cpp:98:15:98:16 | call to container | standalone_iterators.cpp:101:6:101:7 | c1 | |
| standalone_iterators.cpp:98:15:98:16 | call to container | standalone_iterators.cpp:102:6:102:7 | c1 | |
@@ -3414,10 +3496,13 @@
| standalone_iterators.cpp:102:6:102:7 | ref arg c1 | standalone_iterators.cpp:109:7:109:8 | c1 | |
| standalone_iterators.cpp:102:9:102:13 | call to begin | standalone_iterators.cpp:102:2:102:15 | ... = ... | |
| standalone_iterators.cpp:102:9:102:13 | call to begin | standalone_iterators.cpp:107:7:107:7 | b | |
+| standalone_iterators.cpp:103:2:103:2 | ref arg call to operator* | standalone_iterators.cpp:103:3:103:3 | a [inner post update] | |
| standalone_iterators.cpp:103:2:103:2 | ref arg call to operator* | standalone_iterators.cpp:103:3:103:3 | ref arg a | TAINT |
+| standalone_iterators.cpp:103:2:103:2 | ref arg call to operator* | standalone_iterators.cpp:104:7:104:7 | a | |
| standalone_iterators.cpp:103:2:103:2 | ref arg call to operator* | standalone_iterators.cpp:106:6:106:7 | c1 | |
| standalone_iterators.cpp:103:2:103:2 | ref arg call to operator* | standalone_iterators.cpp:109:7:109:8 | c1 | |
| standalone_iterators.cpp:103:3:103:3 | a | standalone_iterators.cpp:103:2:103:2 | call to operator* | TAINT |
+| standalone_iterators.cpp:103:3:103:3 | ref arg a | standalone_iterators.cpp:103:3:103:3 | a [inner post update] | |
| standalone_iterators.cpp:103:3:103:3 | ref arg a | standalone_iterators.cpp:104:7:104:7 | a | |
| standalone_iterators.cpp:103:7:103:12 | call to source | standalone_iterators.cpp:103:2:103:2 | ref arg call to operator* | TAINT |
| standalone_iterators.cpp:104:7:104:7 | a [post update] | standalone_iterators.cpp:106:6:106:7 | c1 | |
diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/smart_pointer.cpp b/cpp/ql/test/library-tests/dataflow/taint-tests/smart_pointer.cpp
index 51718510b0ad..6ab09046c4e5 100644
--- a/cpp/ql/test/library-tests/dataflow/taint-tests/smart_pointer.cpp
+++ b/cpp/ql/test/library-tests/dataflow/taint-tests/smart_pointer.cpp
@@ -102,4 +102,37 @@ void reverse_taint_smart_pointer() {
std::unique_ptr p = std::unique_ptr(new A);
taint_x(p.get());
sink(p->x); // $ ast MISSING: ir
+}
+
+struct C {
+ int z;
+ std::shared_ptr q;
+};
+
+void taint_x_shared(std::shared_ptr ptr) {
+ ptr->x = source();
+}
+
+void taint_x_shared_cref(const std::shared_ptr& ptr) {
+ ptr->x = source();
+}
+
+void getNumberCRef(const std::shared_ptr& ptr) {
+ *ptr = source();
+}
+
+int nested_shared_ptr_taint(std::shared_ptr p1, std::unique_ptr> p2) {
+ taint_x_shared(p1->q);
+ sink(p1->q->x); // $ ast MISSING: ir
+
+ getNumber(*p2);
+ sink(**p2); // $ ast MISSING: ir
+}
+
+int nested_shared_ptr_taint_cref(std::shared_ptr p1, std::unique_ptr> p2) {
+ taint_x_shared_cref(p1->q);
+ sink(p1->q->x); // $ MISSING: ast,ir
+
+ getNumberCRef(*p2);
+ sink(**p2); // $ MISSING: ast,ir
}
\ No newline at end of file
From 1510fe370df737f6e7593203bbad4fff72f4e438 Mon Sep 17 00:00:00 2001
From: Mathias Vorreiter Pedersen
Date: Fri, 9 Apr 2021 20:58:05 +0200
Subject: [PATCH 157/433] C++: Add cases for const pointer wrapper references
to AddressFlow and FlowVar.
---
.../code/cpp/dataflow/internal/AddressFlow.qll | 4 +++-
.../code/cpp/dataflow/internal/FlowVar.qll | 9 ++++++++-
.../dataflow/taint-tests/localTaint.expected | 17 +++++++++++++++++
.../dataflow/taint-tests/smart_pointer.cpp | 4 ++--
4 files changed, 30 insertions(+), 4 deletions(-)
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/AddressFlow.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/AddressFlow.qll
index 70af0b25c23a..7e76c1540e30 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/AddressFlow.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/AddressFlow.qll
@@ -247,7 +247,9 @@ private predicate referenceToUpdate(Expr reference, Expr outer, ControlFlowNode
not stdIdentityFunction(call.getTarget()) and
not stdAddressOf(call.getTarget()) and
exists(ReferenceType rt | rt = outer.getType().stripTopLevelSpecifiers() |
- not rt.getBaseType().isConst()
+ not rt.getBaseType().isConst() or
+ rt.getBaseType().getUnspecifiedType() =
+ any(PointerWrapper wrapper | not wrapper.pointsToConst())
)
) and
reference = outer
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/FlowVar.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/FlowVar.qll
index 79728629a9a1..e3f8b6f68fb5 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/FlowVar.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/FlowVar.qll
@@ -644,7 +644,14 @@ module FlowVar_internal {
predicate parameterIsNonConstReference(Parameter p) {
exists(ReferenceType refType |
refType = p.getUnderlyingType() and
- not refType.getBaseType().isConst()
+ (
+ not refType.getBaseType().isConst()
+ or
+ // A field of a parameter of type `const std::shared_ptr& p` can still be changed even though
+ // the base type of the reference is `const`.
+ refType.getBaseType().getUnspecifiedType() =
+ any(PointerWrapper wrapper | not wrapper.pointsToConst())
+ )
)
or
p instanceof IteratorParameter
diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected b/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected
index dd15fa7cd983..50d5070a1442 100644
--- a/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected
+++ b/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected
@@ -3301,6 +3301,8 @@
| smart_pointer.cpp:76:13:76:13 | p | smart_pointer.cpp:76:13:76:13 | call to shared_ptr | |
| smart_pointer.cpp:76:13:76:13 | ref arg call to shared_ptr | smart_pointer.cpp:76:13:76:13 | p [inner post update] | |
| smart_pointer.cpp:76:13:76:13 | ref arg call to shared_ptr | smart_pointer.cpp:77:9:77:9 | p | |
+| smart_pointer.cpp:76:13:76:13 | ref arg p | smart_pointer.cpp:76:13:76:13 | p [inner post update] | |
+| smart_pointer.cpp:76:13:76:13 | ref arg p | smart_pointer.cpp:77:9:77:9 | p | |
| smart_pointer.cpp:77:9:77:9 | p | smart_pointer.cpp:77:8:77:8 | call to operator* | |
| smart_pointer.cpp:86:45:86:45 | p | smart_pointer.cpp:86:45:86:45 | p | |
| smart_pointer.cpp:86:45:86:45 | p | smart_pointer.cpp:87:3:87:3 | p | |
@@ -3358,15 +3360,20 @@
| smart_pointer.cpp:113:2:113:18 | ... = ... | smart_pointer.cpp:113:7:113:7 | x [post update] | |
| smart_pointer.cpp:113:5:113:5 | call to operator-> [post update] | smart_pointer.cpp:113:2:113:4 | ref arg ptr | |
| smart_pointer.cpp:113:11:113:16 | call to source | smart_pointer.cpp:113:2:113:18 | ... = ... | |
+| smart_pointer.cpp:116:52:116:54 | ptr | smart_pointer.cpp:116:52:116:54 | ptr | |
| smart_pointer.cpp:116:52:116:54 | ptr | smart_pointer.cpp:117:2:117:4 | ptr | |
| smart_pointer.cpp:117:2:117:4 | ptr | smart_pointer.cpp:117:5:117:5 | call to operator-> | |
+| smart_pointer.cpp:117:2:117:4 | ref arg ptr | smart_pointer.cpp:116:52:116:54 | ptr | |
| smart_pointer.cpp:117:2:117:18 | ... = ... | smart_pointer.cpp:117:7:117:7 | x [post update] | |
| smart_pointer.cpp:117:5:117:5 | call to operator-> [post update] | smart_pointer.cpp:117:2:117:4 | ref arg ptr | |
| smart_pointer.cpp:117:11:117:16 | call to source | smart_pointer.cpp:117:2:117:18 | ... = ... | |
+| smart_pointer.cpp:120:48:120:50 | ptr | smart_pointer.cpp:120:48:120:50 | ptr | |
| smart_pointer.cpp:120:48:120:50 | ptr | smart_pointer.cpp:121:4:121:6 | ptr | |
+| smart_pointer.cpp:121:3:121:3 | call to operator* [post update] | smart_pointer.cpp:120:48:120:50 | ptr | |
| smart_pointer.cpp:121:3:121:3 | call to operator* [post update] | smart_pointer.cpp:121:4:121:6 | ptr [inner post update] | |
| smart_pointer.cpp:121:3:121:17 | ... = ... | smart_pointer.cpp:121:3:121:3 | call to operator* [post update] | |
| smart_pointer.cpp:121:4:121:6 | ptr | smart_pointer.cpp:121:3:121:3 | call to operator* | |
+| smart_pointer.cpp:121:4:121:6 | ref arg ptr | smart_pointer.cpp:120:48:120:50 | ptr | |
| smart_pointer.cpp:121:4:121:6 | ref arg ptr | smart_pointer.cpp:121:4:121:6 | ptr [inner post update] | |
| smart_pointer.cpp:121:10:121:15 | call to source | smart_pointer.cpp:121:3:121:17 | ... = ... | |
| smart_pointer.cpp:124:48:124:49 | p1 | smart_pointer.cpp:124:48:124:49 | p1 | |
@@ -3381,11 +3388,16 @@
| smart_pointer.cpp:125:18:125:22 | ref arg call to shared_ptr | smart_pointer.cpp:125:22:125:22 | q [inner post update] | |
| smart_pointer.cpp:125:20:125:20 | call to operator-> [post update] | smart_pointer.cpp:125:18:125:19 | ref arg p1 | |
| smart_pointer.cpp:125:22:125:22 | q | smart_pointer.cpp:125:18:125:22 | call to shared_ptr | |
+| smart_pointer.cpp:125:22:125:22 | ref arg q | smart_pointer.cpp:125:22:125:22 | q [inner post update] | |
| smart_pointer.cpp:126:8:126:9 | p1 | smart_pointer.cpp:126:10:126:10 | call to operator-> | |
| smart_pointer.cpp:126:8:126:9 | ref arg p1 | smart_pointer.cpp:124:48:124:49 | p1 | |
| smart_pointer.cpp:126:10:126:10 | call to operator-> [post update] | smart_pointer.cpp:126:8:126:9 | ref arg p1 | |
| smart_pointer.cpp:126:12:126:12 | q | smart_pointer.cpp:126:13:126:13 | call to operator-> | |
| smart_pointer.cpp:128:13:128:13 | call to operator* | smart_pointer.cpp:128:13:128:15 | call to shared_ptr | TAINT |
+| smart_pointer.cpp:128:13:128:13 | ref arg call to operator* | smart_pointer.cpp:124:90:124:91 | p2 | |
+| smart_pointer.cpp:128:13:128:13 | ref arg call to operator* | smart_pointer.cpp:128:13:128:13 | call to operator* [inner post update] | |
+| smart_pointer.cpp:128:13:128:13 | ref arg call to operator* | smart_pointer.cpp:128:14:128:15 | p2 [inner post update] | |
+| smart_pointer.cpp:128:13:128:13 | ref arg call to operator* | smart_pointer.cpp:129:10:129:11 | p2 | |
| smart_pointer.cpp:128:13:128:15 | ref arg call to shared_ptr | smart_pointer.cpp:124:90:124:91 | p2 | |
| smart_pointer.cpp:128:13:128:15 | ref arg call to shared_ptr | smart_pointer.cpp:128:13:128:13 | call to operator* [inner post update] | |
| smart_pointer.cpp:128:13:128:15 | ref arg call to shared_ptr | smart_pointer.cpp:128:14:128:15 | p2 [inner post update] | |
@@ -3410,12 +3422,17 @@
| smart_pointer.cpp:133:23:133:24 | p1 | smart_pointer.cpp:133:25:133:25 | call to operator-> | |
| smart_pointer.cpp:133:23:133:24 | ref arg p1 | smart_pointer.cpp:132:53:132:54 | p1 | |
| smart_pointer.cpp:133:23:133:24 | ref arg p1 | smart_pointer.cpp:134:8:134:9 | p1 | |
+| smart_pointer.cpp:133:25:133:25 | call to operator-> [post update] | smart_pointer.cpp:133:23:133:24 | ref arg p1 | |
| smart_pointer.cpp:134:8:134:9 | p1 | smart_pointer.cpp:134:10:134:10 | call to operator-> | |
| smart_pointer.cpp:134:8:134:9 | ref arg p1 | smart_pointer.cpp:132:53:132:54 | p1 | |
| smart_pointer.cpp:134:10:134:10 | call to operator-> [post update] | smart_pointer.cpp:134:8:134:9 | ref arg p1 | |
| smart_pointer.cpp:134:12:134:12 | q | smart_pointer.cpp:134:13:134:13 | call to operator-> | |
+| smart_pointer.cpp:136:17:136:17 | ref arg call to operator* | smart_pointer.cpp:132:95:132:96 | p2 | |
+| smart_pointer.cpp:136:17:136:17 | ref arg call to operator* | smart_pointer.cpp:136:18:136:19 | p2 [inner post update] | |
+| smart_pointer.cpp:136:17:136:17 | ref arg call to operator* | smart_pointer.cpp:137:10:137:11 | p2 | |
| smart_pointer.cpp:136:18:136:19 | p2 | smart_pointer.cpp:136:17:136:17 | call to operator* | TAINT |
| smart_pointer.cpp:136:18:136:19 | ref arg p2 | smart_pointer.cpp:132:95:132:96 | p2 | |
+| smart_pointer.cpp:136:18:136:19 | ref arg p2 | smart_pointer.cpp:136:18:136:19 | p2 [inner post update] | |
| smart_pointer.cpp:136:18:136:19 | ref arg p2 | smart_pointer.cpp:137:10:137:11 | p2 | |
| smart_pointer.cpp:137:9:137:9 | call to operator* | smart_pointer.cpp:137:8:137:8 | call to operator* | TAINT |
| smart_pointer.cpp:137:9:137:9 | ref arg call to operator* | smart_pointer.cpp:132:95:132:96 | p2 | |
diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/smart_pointer.cpp b/cpp/ql/test/library-tests/dataflow/taint-tests/smart_pointer.cpp
index 6ab09046c4e5..39cff39e3edc 100644
--- a/cpp/ql/test/library-tests/dataflow/taint-tests/smart_pointer.cpp
+++ b/cpp/ql/test/library-tests/dataflow/taint-tests/smart_pointer.cpp
@@ -131,8 +131,8 @@ int nested_shared_ptr_taint(std::shared_ptr p1, std::unique_ptr p1, std::unique_ptr> p2) {
taint_x_shared_cref(p1->q);
- sink(p1->q->x); // $ MISSING: ast,ir
+ sink(p1->q->x); // $ ast MISSING: ir
getNumberCRef(*p2);
- sink(**p2); // $ MISSING: ast,ir
+ sink(**p2); // $ ast MISSING: ir
}
\ No newline at end of file
From 720fbaf301445e2f05138292cc0ed5e902ff6da4 Mon Sep 17 00:00:00 2001
From: Taus
Date: Fri, 9 Apr 2021 19:04:49 +0000
Subject: [PATCH 158/433] Python: Fix test error.
Somehow, having to type "Node" all day long made me turn "json" into
"node"...
Also removes some bits that weren't needed after all.
---
python/ql/src/semmle/python/frameworks/Stdlib.qll | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/python/ql/src/semmle/python/frameworks/Stdlib.qll b/python/ql/src/semmle/python/frameworks/Stdlib.qll
index 3441c670fde8..1941f0c4d5bf 100644
--- a/python/ql/src/semmle/python/frameworks/Stdlib.qll
+++ b/python/ql/src/semmle/python/frameworks/Stdlib.qll
@@ -341,10 +341,8 @@ private module Stdlib {
// ---------------------------------------------------------------------------
// pickle
// ---------------------------------------------------------------------------
- private string pickleModuleName() { result in ["pickle", "cPickle", "_pickle"] }
-
/** Gets a reference to the `pickle` module. */
- deprecated DataFlow::Node pickle() {
+ DataFlow::Node pickle() {
result = API::moduleImport(["pickle", "cPickle", "_pickle"]).getAUse()
}
@@ -578,7 +576,7 @@ private module Stdlib {
// json
// ---------------------------------------------------------------------------
/** Gets a reference to the `json` module. */
- API::Node json() { result = API::moduleImport("node") }
+ API::Node json() { result = API::moduleImport("json") }
/**
* Gets a reference to the attribute `attr_name` of the `json` module.
From 4e3791dc0d05702cda6d86bc1a61562d80e2b0ed Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Fri, 9 Apr 2021 19:36:35 +0000
Subject: [PATCH 159/433] Remove LoadCredentialsConfiguration and update qldoc
---
.../CredentialsInPropertiesFile.qll | 50 ++-----------------
.../CredentialsInPropertiesFile.expected | 10 ++--
2 files changed, 8 insertions(+), 52 deletions(-)
diff --git a/java/ql/src/experimental/semmle/code/java/frameworks/CredentialsInPropertiesFile.qll b/java/ql/src/experimental/semmle/code/java/frameworks/CredentialsInPropertiesFile.qll
index 9577789ce0c0..1190e13a9a1a 100644
--- a/java/ql/src/experimental/semmle/code/java/frameworks/CredentialsInPropertiesFile.qll
+++ b/java/ql/src/experimental/semmle/code/java/frameworks/CredentialsInPropertiesFile.qll
@@ -40,7 +40,7 @@ predicate isNotCleartextCredentials(string value) {
value.toLowerCase().matches(possibleSecretName())
}
-/** The credentials configuration property. */
+/** A configuration property that appears to contain a cleartext secret. */
class CredentialsConfig extends ConfigPair {
CredentialsConfig() {
this.getNameElement().getName().trim().toLowerCase().matches(possibleSecretName()) and
@@ -48,60 +48,16 @@ class CredentialsConfig extends ConfigPair {
not isNotCleartextCredentials(this.getValueElement().getValue().trim())
}
+ /** Gets the whitespace-trimmed name of this property. */
string getName() { result = this.getNameElement().getName().trim() }
+ /** Gets the whitespace-trimmed value of this property. */
string getValue() { result = this.getValueElement().getValue().trim() }
/** Returns a description of this vulnerability. */
string getConfigDesc() {
- exists(
- // getProperty(...)
- LoadCredentialsConfiguration cc, DataFlow::Node source, DataFlow::Node sink, MethodAccess ma
- |
- this.getName() = source.asExpr().(CompileTimeConstantExpr).getStringValue() and
- cc.hasFlow(source, sink) and
- ma.getArgument(0) = sink.asExpr() and
- result = "Plaintext credentials " + this.getName() + " are loaded in Java Properties " + ma
- )
- or
- exists(
- // @Value("${mail.password}")
- Annotation a
- |
- a.getType().hasQualifiedName("org.springframework.beans.factory.annotation", "Value") and
- a.getAValue().(CompileTimeConstantExpr).getStringValue() = "${" + this.getName() + "}" and
- result = "Plaintext credentials " + this.getName() + " are loaded in Spring annotation " + a
- )
- or
- not exists(LoadCredentialsConfiguration cc, DataFlow::Node source, DataFlow::Node sink |
- this.getName() = source.asExpr().(CompileTimeConstantExpr).getStringValue() and
- cc.hasFlow(source, sink)
- ) and
- not exists(Annotation a |
- a.getType().hasQualifiedName("org.springframework.beans.factory.annotation", "Value") and
- a.getAValue().(CompileTimeConstantExpr).getStringValue() = "${" + this.getName() + "}"
- ) and
result =
"Plaintext credentials " + this.getName() + " have cleartext value " + this.getValue() +
" in properties file"
}
}
-
-/**
- * A dataflow configuration tracking flow of cleartext credentials stored in a properties file
- * to a `Properties.getProperty(...)` method call.
- */
-class LoadCredentialsConfiguration extends DataFlow::Configuration {
- LoadCredentialsConfiguration() { this = "LoadCredentialsConfiguration" }
-
- override predicate isSource(DataFlow::Node source) {
- exists(CredentialsConfig cc |
- source.asExpr().(CompileTimeConstantExpr).getStringValue() = cc.getName()
- )
- }
-
- override predicate isSink(DataFlow::Node sink) {
- sink.asExpr() =
- any(MethodAccess ma | ma.getMethod() instanceof PropertiesGetPropertyMethod).getArgument(0)
- }
-}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.expected b/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.expected
index 371a4765c53b..b857d58634b0 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-555/CredentialsInPropertiesFile.expected
@@ -1,5 +1,5 @@
-| configuration.properties:6:1:6:25 | ldap.password=mysecpass | Plaintext credentials ldap.password are loaded in Java Properties getProperty(...) |
-| configuration.properties:18:1:18:35 | datasource1.password=Passw0rd@123 | Plaintext credentials datasource1.password are loaded in Java Properties getProperty(...) |
-| configuration.properties:25:1:25:31 | mail.password=MysecPWxWa@1993 | Plaintext credentials mail.password are loaded in Spring annotation Value |
-| configuration.properties:33:1:33:50 | com.example.aws.s3.access_key=AKMAMQPBYMCD6YSAYCBA | Plaintext credentials com.example.aws.s3.access_key are loaded in Java Properties getProperty(...) |
-| configuration.properties:34:1:34:70 | com.example.aws.s3.secret_key=8lMPSfWzZq+wcWtck5+QPLOJDZzE783pS09/IO3k | Plaintext credentials com.example.aws.s3.secret_key are loaded in Java Properties getProperty(...) |
+| configuration.properties:6:1:6:25 | ldap.password=mysecpass | Plaintext credentials ldap.password have cleartext value mysecpass in properties file |
+| configuration.properties:18:1:18:35 | datasource1.password=Passw0rd@123 | Plaintext credentials datasource1.password have cleartext value Passw0rd@123 in properties file |
+| configuration.properties:25:1:25:31 | mail.password=MysecPWxWa@1993 | Plaintext credentials mail.password have cleartext value MysecPWxWa@1993 in properties file |
+| configuration.properties:33:1:33:50 | com.example.aws.s3.access_key=AKMAMQPBYMCD6YSAYCBA | Plaintext credentials com.example.aws.s3.access_key have cleartext value AKMAMQPBYMCD6YSAYCBA in properties file |
+| configuration.properties:34:1:34:70 | com.example.aws.s3.secret_key=8lMPSfWzZq+wcWtck5+QPLOJDZzE783pS09/IO3k | Plaintext credentials com.example.aws.s3.secret_key have cleartext value 8lMPSfWzZq+wcWtck5+QPLOJDZzE783pS09/IO3k in properties file |
From 0a866420563094129f0b63d7b0cddad227b520f6 Mon Sep 17 00:00:00 2001
From: Dave Bartolomeo
Date: Fri, 9 Apr 2021 16:14:03 -0400
Subject: [PATCH 160/433] C++: Refactor some side effect generation code
This change was necessary for my upcoming changes to introduce side effect instructions for indirections of smart pointers. The code to decide which parameters have which side effects appeared in both the IPA constructor for `TTranslatedSideEffect` and in `TranslatedCall`. These two versions didn't quite agree, especially once the `SideEffectFunction` model provides its own side effects instead of the defaults.
The relevant code has now been factored out into `SideEffects.qll`. This queries the model if one exists, and provides default side effects if no model exists. This fixes at least one existing issue, where we were emitting a buffer read side effect for `*this` instead of an indirect read side effect. This accounts for all of the IR diffs in the tests.
---
.../raw/internal/SideEffects.qll | 109 ++++++
.../raw/internal/TranslatedCall.qll | 114 ++----
.../raw/internal/TranslatedElement.qll | 44 +--
.../test/library-tests/ir/ir/raw_ir.expected | 337 +++++++++---------
.../ir/ssa/aliased_ssa_ir.expected | 6 +-
.../ir/ssa/aliased_ssa_ir_unsound.expected | 6 +-
.../ir/ssa/unaliased_ssa_ir.expected | 6 +-
.../ir/ssa/unaliased_ssa_ir_unsound.expected | 6 +-
8 files changed, 332 insertions(+), 296 deletions(-)
create mode 100644 cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/SideEffects.qll
diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/SideEffects.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/SideEffects.qll
new file mode 100644
index 000000000000..350127a58d17
--- /dev/null
+++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/SideEffects.qll
@@ -0,0 +1,109 @@
+/**
+ * Predicates to compute the modeled side effects of calls during IR construction.
+ *
+ * These are used in `TranslatedElement.qll` to generate the `TTranslatedSideEffect` instances, and
+ * also in `TranslatedCall.qll` to inject the actual side effect instructions.
+ */
+
+private import cpp
+private import semmle.code.cpp.ir.implementation.Opcode
+private import semmle.code.cpp.models.interfaces.SideEffect
+
+/**
+ * Holds if the specified call has a side effect that does not come from a `SideEffectFunction`
+ * model.
+ */
+private predicate hasDefaultSideEffect(Call call, ParameterIndex i, boolean buffer, boolean isWrite) {
+ not call.getTarget() instanceof SideEffectFunction and
+ (
+ exists(MemberFunction mfunc |
+ // A non-static member function, including a constructor or destructor, may write to `*this`,
+ // and may also read from `*this` if it is not a constructor.
+ i = -1 and
+ mfunc = call.getTarget() and
+ not mfunc.isStatic() and
+ buffer = false and
+ (
+ isWrite = false and not mfunc instanceof Constructor
+ or
+ isWrite = true and not mfunc instanceof ConstMemberFunction
+ )
+ )
+ or
+ exists(Expr expr |
+ // A pointer-like argument is assumed to read from the pointed-to buffer, and may write to the
+ // buffer as well unless the pointer points to a `const` value.
+ i >= 0 and
+ buffer = true and
+ expr = call.getArgument(i).getFullyConverted() and
+ exists(Type t | t = expr.getUnspecifiedType() |
+ t instanceof ArrayType or
+ t instanceof PointerType or
+ t instanceof ReferenceType
+ ) and
+ (
+ isWrite = true and
+ not call.getTarget().getParameter(i).getType().isDeeplyConstBelow()
+ or
+ isWrite = false
+ )
+ )
+ )
+}
+
+/**
+ * Returns a side effect opcode for parameter index `i` of the specified call.
+ *
+ * This predicate will return at most two results: one read side effect, and one write side effect.
+ */
+Opcode getASideEffectOpcode(Call call, ParameterIndex i) {
+ exists(boolean buffer |
+ (
+ call.getTarget().(SideEffectFunction).hasSpecificReadSideEffect(i, buffer)
+ or
+ not call.getTarget() instanceof SideEffectFunction and
+ hasDefaultSideEffect(call, i, buffer, false)
+ ) and
+ if exists(call.getTarget().(SideEffectFunction).getParameterSizeIndex(i))
+ then (
+ buffer = true and
+ result instanceof Opcode::SizedBufferReadSideEffect
+ ) else (
+ buffer = false and result instanceof Opcode::IndirectReadSideEffect
+ or
+ buffer = true and result instanceof Opcode::BufferReadSideEffect
+ )
+ )
+ or
+ exists(boolean buffer, boolean mustWrite |
+ (
+ call.getTarget().(SideEffectFunction).hasSpecificWriteSideEffect(i, buffer, mustWrite)
+ or
+ not call.getTarget() instanceof SideEffectFunction and
+ hasDefaultSideEffect(call, i, buffer, true) and
+ mustWrite = false
+ ) and
+ if exists(call.getTarget().(SideEffectFunction).getParameterSizeIndex(i))
+ then (
+ buffer = true and
+ mustWrite = false and
+ result instanceof Opcode::SizedBufferMayWriteSideEffect
+ or
+ buffer = true and
+ mustWrite = true and
+ result instanceof Opcode::SizedBufferMustWriteSideEffect
+ ) else (
+ buffer = false and
+ mustWrite = false and
+ result instanceof Opcode::IndirectMayWriteSideEffect
+ or
+ buffer = false and
+ mustWrite = true and
+ result instanceof Opcode::IndirectMustWriteSideEffect
+ or
+ buffer = true and mustWrite = false and result instanceof Opcode::BufferMayWriteSideEffect
+ or
+ buffer = true and mustWrite = true and result instanceof Opcode::BufferMustWriteSideEffect
+ )
+ )
+}
diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedCall.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedCall.qll
index 7ad1dd2c01e5..0b52937f1cb7 100644
--- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedCall.qll
+++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedCall.qll
@@ -4,6 +4,7 @@ private import semmle.code.cpp.ir.implementation.internal.OperandTag
private import semmle.code.cpp.ir.internal.CppType
private import semmle.code.cpp.models.interfaces.SideEffect
private import InstructionTag
+private import SideEffects
private import TranslatedElement
private import TranslatedExpr
private import TranslatedFunction
@@ -424,12 +425,15 @@ class TranslatedCallSideEffects extends TranslatedSideEffects, TTranslatedCallSi
}
class TranslatedStructorCallSideEffects extends TranslatedCallSideEffects {
- TranslatedStructorCallSideEffects() { getParent().(TranslatedStructorCall).hasQualifier() }
+ TranslatedStructorCallSideEffects() {
+ getParent().(TranslatedStructorCall).hasQualifier() and
+ getASideEffectOpcode(expr, -1) instanceof WriteSideEffectOpcode
+ }
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType t) {
- opcode instanceof Opcode::IndirectMayWriteSideEffect and
tag instanceof OnlyInstructionTag and
- t = getTypeForPRValue(expr.getTarget().getDeclaringType())
+ t = getTypeForPRValue(expr.getTarget().getDeclaringType()) and
+ opcode = getASideEffectOpcode(expr, -1).(WriteSideEffectOpcode)
}
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
@@ -460,9 +464,11 @@ class TranslatedSideEffect extends TranslatedElement, TTranslatedArgumentSideEff
Call call;
Expr arg;
int index;
- boolean write;
+ SideEffectOpcode sideEffectOpcode;
- TranslatedSideEffect() { this = TTranslatedArgumentSideEffect(call, arg, index, write) }
+ TranslatedSideEffect() {
+ this = TTranslatedArgumentSideEffect(call, arg, index, sideEffectOpcode)
+ }
override Locatable getAST() { result = arg }
@@ -472,13 +478,13 @@ class TranslatedSideEffect extends TranslatedElement, TTranslatedArgumentSideEff
int getArgumentIndex() { result = index }
- predicate isWrite() { write = true }
+ predicate isWrite() { sideEffectOpcode instanceof WriteSideEffectOpcode }
override string toString() {
- write = true and
+ isWrite() and
result = "(write side effect for " + arg.toString() + ")"
or
- write = false and
+ not isWrite() and
result = "(read side effect for " + arg.toString() + ")"
}
@@ -489,29 +495,31 @@ class TranslatedSideEffect extends TranslatedElement, TTranslatedArgumentSideEff
override Instruction getFirstInstruction() { result = getInstruction(OnlyInstructionTag()) }
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType type) {
- isWrite() and
- hasSpecificWriteSideEffect(opcode) and
- tag = OnlyInstructionTag() and
(
- opcode instanceof BufferAccessOpcode and
- type = getUnknownType()
- or
- not opcode instanceof BufferAccessOpcode and
- exists(Type baseType | baseType = arg.getUnspecifiedType().(DerivedType).getBaseType() |
- if baseType instanceof VoidType
- then type = getUnknownType()
- else type = getTypeForPRValueOrUnknown(baseType)
+ tag = OnlyInstructionTag() and
+ opcode = sideEffectOpcode
+ ) and
+ (
+ isWrite() and
+ (
+ opcode instanceof BufferAccessOpcode and
+ type = getUnknownType()
+ or
+ not opcode instanceof BufferAccessOpcode and
+ exists(Type baseType | baseType = arg.getUnspecifiedType().(DerivedType).getBaseType() |
+ if baseType instanceof VoidType
+ then type = getUnknownType()
+ else type = getTypeForPRValueOrUnknown(baseType)
+ )
+ or
+ index = -1 and
+ not arg.getUnspecifiedType() instanceof DerivedType and
+ type = getTypeForPRValueOrUnknown(arg.getUnspecifiedType())
)
or
- index = -1 and
- not arg.getUnspecifiedType() instanceof DerivedType and
- type = getTypeForPRValueOrUnknown(arg.getUnspecifiedType())
+ not isWrite() and
+ type = getVoidType()
)
- or
- not isWrite() and
- hasSpecificReadSideEffect(opcode) and
- tag = OnlyInstructionTag() and
- type = getVoidType()
}
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
@@ -535,7 +543,7 @@ class TranslatedSideEffect extends TranslatedElement, TTranslatedArgumentSideEff
override CppType getInstructionMemoryOperandType(InstructionTag tag, TypedOperandTag operandTag) {
not isWrite() and
- if hasSpecificReadSideEffect(any(BufferAccessOpcode op))
+ if sideEffectOpcode instanceof BufferAccessOpcode
then
result = getUnknownType() and
tag instanceof OnlyInstructionTag and
@@ -557,56 +565,6 @@ class TranslatedSideEffect extends TranslatedElement, TTranslatedArgumentSideEff
)
}
- predicate hasSpecificWriteSideEffect(Opcode op) {
- exists(boolean buffer, boolean mustWrite |
- if exists(call.getTarget().(SideEffectFunction).getParameterSizeIndex(index))
- then
- call.getTarget().(SideEffectFunction).hasSpecificWriteSideEffect(index, true, mustWrite) and
- buffer = true and
- (
- mustWrite = false and op instanceof Opcode::SizedBufferMayWriteSideEffect
- or
- mustWrite = true and op instanceof Opcode::SizedBufferMustWriteSideEffect
- )
- else (
- call.getTarget().(SideEffectFunction).hasSpecificWriteSideEffect(index, buffer, mustWrite) and
- (
- buffer = true and mustWrite = false and op instanceof Opcode::BufferMayWriteSideEffect
- or
- buffer = false and mustWrite = false and op instanceof Opcode::IndirectMayWriteSideEffect
- or
- buffer = true and mustWrite = true and op instanceof Opcode::BufferMustWriteSideEffect
- or
- buffer = false and mustWrite = true and op instanceof Opcode::IndirectMustWriteSideEffect
- )
- )
- )
- or
- not call.getTarget() instanceof SideEffectFunction and
- getArgumentIndex() != -1 and
- op instanceof Opcode::BufferMayWriteSideEffect
- or
- not call.getTarget() instanceof SideEffectFunction and
- getArgumentIndex() = -1 and
- op instanceof Opcode::IndirectMayWriteSideEffect
- }
-
- predicate hasSpecificReadSideEffect(Opcode op) {
- exists(boolean buffer |
- call.getTarget().(SideEffectFunction).hasSpecificReadSideEffect(index, buffer) and
- if exists(call.getTarget().(SideEffectFunction).getParameterSizeIndex(index))
- then buffer = true and op instanceof Opcode::SizedBufferReadSideEffect
- else (
- buffer = true and op instanceof Opcode::BufferReadSideEffect
- or
- buffer = false and op instanceof Opcode::IndirectReadSideEffect
- )
- )
- or
- not call.getTarget() instanceof SideEffectFunction and
- op instanceof Opcode::BufferReadSideEffect
- }
-
override Instruction getPrimaryInstructionForSideEffect(InstructionTag tag) {
tag = OnlyInstructionTag() and
result = getTranslatedCallInstruction(call)
diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedElement.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedElement.qll
index 1de9936ae1fb..eca659914b02 100644
--- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedElement.qll
+++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedElement.qll
@@ -12,6 +12,7 @@ private import TranslatedStmt
private import TranslatedExpr
private import IRConstruction
private import semmle.code.cpp.models.interfaces.SideEffect
+private import SideEffects
/**
* Gets the "real" parent of `expr`. This predicate treats conversions as if
@@ -635,46 +636,15 @@ newtype TTranslatedElement =
// The side effects of an allocation, i.e. `new`, `new[]` or `malloc`
TTranslatedAllocationSideEffects(AllocationExpr expr) { not ignoreExpr(expr) } or
// A precise side effect of an argument to a `Call`
- TTranslatedArgumentSideEffect(Call call, Expr expr, int n, boolean isWrite) {
- (
- expr = call.getArgument(n).getFullyConverted()
- or
- expr = call.getQualifier().getFullyConverted() and
- n = -1 and
- // Exclude calls to static member functions. They don't modify the qualifier
- not exists(MemberFunction func | func = call.getTarget() and func.isStatic())
- ) and
+ TTranslatedArgumentSideEffect(Call call, Expr expr, int n, SideEffectOpcode opcode) {
+ not ignoreExpr(expr) and
+ not ignoreExpr(call) and
(
- call.getTarget().(SideEffectFunction).hasSpecificReadSideEffect(n, _) and
- isWrite = false
- or
- call.getTarget().(SideEffectFunction).hasSpecificWriteSideEffect(n, _, _) and
- isWrite = true
- or
- not call.getTarget() instanceof SideEffectFunction and
- exists(Type t | t = expr.getUnspecifiedType() |
- t instanceof ArrayType or
- t instanceof PointerType or
- t instanceof ReferenceType
- ) and
- (
- isWrite = true and
- not call.getTarget().getParameter(n).getType().isDeeplyConstBelow()
- or
- isWrite = false
- )
+ n >= 0 and expr = call.getArgument(n).getFullyConverted()
or
- not call.getTarget() instanceof SideEffectFunction and
- n = -1 and
- (
- isWrite = true and
- not call.getTarget() instanceof ConstMemberFunction
- or
- isWrite = false
- )
+ n = -1 and expr = call.getQualifier().getFullyConverted()
) and
- not ignoreExpr(expr) and
- not ignoreExpr(call)
+ opcode = getASideEffectOpcode(call, n)
}
/**
diff --git a/cpp/ql/test/library-tests/ir/ir/raw_ir.expected b/cpp/ql/test/library-tests/ir/ir/raw_ir.expected
index a7dac56cc96a..e008e2de6596 100644
--- a/cpp/ql/test/library-tests/ir/ir/raw_ir.expected
+++ b/cpp/ql/test/library-tests/ir/ir/raw_ir.expected
@@ -42,7 +42,7 @@ bad_asts.cpp:
# 16| r16_3(int) = Constant[1] :
# 16| r16_4(int) = Call[MemberFunction] : func:r16_2, this:r16_1, 0:r16_3
# 16| mu16_5(unknown) = ^CallSideEffect : ~m?
-# 16| v16_6(void) = ^BufferReadSideEffect[-1] : &:r16_1, ~m?
+# 16| v16_6(void) = ^IndirectReadSideEffect[-1] : &:r16_1, ~m?
# 16| mu16_7(S) = ^IndirectMayWriteSideEffect[-1] : &:r16_1
# 17| v17_1(void) = NoOp :
# 14| v14_4(void) = ReturnVoid :
@@ -3385,47 +3385,46 @@ ir.cpp:
# 622| void CallMethods(String&, String*, String)
# 622| Block 0
-# 622| v622_1(void) = EnterFunction :
-# 622| mu622_2(unknown) = AliasedDefinition :
-# 622| mu622_3(unknown) = InitializeNonLocal :
-# 622| r622_4(glval) = VariableAddress[r] :
-# 622| mu622_5(String &) = InitializeParameter[r] : &:r622_4
-# 622| r622_6(String &) = Load[r] : &:r622_4, ~m?
-# 622| mu622_7(unknown) = InitializeIndirection[r] : &:r622_6
-# 622| r622_8(glval) = VariableAddress[p] :
-# 622| mu622_9(String *) = InitializeParameter[p] : &:r622_8
-# 622| r622_10(String *) = Load[p] : &:r622_8, ~m?
-# 622| mu622_11(unknown) = InitializeIndirection[p] : &:r622_10
-# 622| r622_12(glval) = VariableAddress[s] :
-# 622| mu622_13(String) = InitializeParameter[s] : &:r622_12
-# 623| r623_1(glval) = VariableAddress[r] :
-# 623| r623_2(String &) = Load[r] : &:r623_1, ~m?
-# 623| r623_3(glval) = CopyValue : r623_2
-# 623| r623_4(glval) = Convert : r623_3
-# 623| r623_5(glval) = FunctionAddress[c_str] :
-# 623| r623_6(char *) = Call[c_str] : func:r623_5, this:r623_4
-# 623| mu623_7(unknown) = ^CallSideEffect : ~m?
-# 623| v623_8(void) = ^BufferReadSideEffect[-1] : &:r623_4, ~m?
-# 624| r624_1(glval) = VariableAddress[p] :
-# 624| r624_2(String *) = Load[p] : &:r624_1, ~m?
-# 624| r624_3(String *) = Convert : r624_2
-# 624| r624_4(glval) = FunctionAddress[c_str] :
-# 624| r624_5(char *) = Call[c_str] : func:r624_4, this:r624_3
-# 624| mu624_6(unknown) = ^CallSideEffect : ~m?
-# 624| v624_7(void) = ^BufferReadSideEffect[-1] : &:r624_3, ~m?
-# 624| mu624_8(String) = ^IndirectMayWriteSideEffect[-1] : &:r624_3
-# 625| r625_1(glval) = VariableAddress[s] :
-# 625| r625_2(glval) = Convert : r625_1
-# 625| r625_3(glval) = FunctionAddress[c_str] :
-# 625| r625_4(char *) = Call[c_str] : func:r625_3, this:r625_2
-# 625| mu625_5(unknown) = ^CallSideEffect : ~m?
-# 625| v625_6(void) = ^BufferReadSideEffect[-1] : &:r625_2, ~m?
-# 626| v626_1(void) = NoOp :
-# 622| v622_14(void) = ReturnIndirection[r] : &:r622_6, ~m?
-# 622| v622_15(void) = ReturnIndirection[p] : &:r622_10, ~m?
-# 622| v622_16(void) = ReturnVoid :
-# 622| v622_17(void) = AliasedUse : ~m?
-# 622| v622_18(void) = ExitFunction :
+# 622| v622_1(void) = EnterFunction :
+# 622| mu622_2(unknown) = AliasedDefinition :
+# 622| mu622_3(unknown) = InitializeNonLocal :
+# 622| r622_4(glval) = VariableAddress[r] :
+# 622| mu622_5(String &) = InitializeParameter[r] : &:r622_4
+# 622| r622_6(String &) = Load[r] : &:r622_4, ~m?
+# 622| mu622_7(unknown) = InitializeIndirection[r] : &:r622_6
+# 622| r622_8(glval) = VariableAddress[p] :
+# 622| mu622_9(String *) = InitializeParameter[p] : &:r622_8
+# 622| r622_10(String *) = Load[p] : &:r622_8, ~m?
+# 622| mu622_11(unknown) = InitializeIndirection[p] : &:r622_10
+# 622| r622_12(glval) = VariableAddress[s] :
+# 622| mu622_13(String) = InitializeParameter[s] : &:r622_12
+# 623| r623_1(glval) = VariableAddress[r] :
+# 623| r623_2(String &) = Load[r] : &:r623_1, ~m?
+# 623| r623_3(glval) = CopyValue : r623_2
+# 623| r623_4(glval) = Convert : r623_3
+# 623| r623_5(glval) = FunctionAddress[c_str] :
+# 623| r623_6(char *) = Call[c_str] : func:r623_5, this:r623_4
+# 623| mu623_7(unknown) = ^CallSideEffect : ~m?
+# 623| v623_8(void) = ^IndirectReadSideEffect[-1] : &:r623_4, ~m?
+# 624| r624_1(glval) = VariableAddress[p] :
+# 624| r624_2(String *) = Load[p] : &:r624_1, ~m?
+# 624| r624_3(String *) = Convert : r624_2
+# 624| r624_4(glval) = FunctionAddress[c_str] :
+# 624| r624_5(char *) = Call[c_str] : func:r624_4, this:r624_3
+# 624| mu624_6(unknown) = ^CallSideEffect : ~m?
+# 624| v624_7(void) = ^IndirectReadSideEffect[-1] : &:r624_3, ~m?
+# 625| r625_1(glval) = VariableAddress[s] :
+# 625| r625_2(glval) = Convert : r625_1
+# 625| r625_3(glval) = FunctionAddress[c_str] :
+# 625| r625_4(char *) = Call[c_str] : func:r625_3, this:r625_2
+# 625| mu625_5(unknown) = ^CallSideEffect : ~m?
+# 625| v625_6(void) = ^IndirectReadSideEffect[-1] : &:r625_2, ~m?
+# 626| v626_1(void) = NoOp :
+# 622| v622_14(void) = ReturnIndirection[r] : &:r622_6, ~m?
+# 622| v622_15(void) = ReturnIndirection[p] : &:r622_10, ~m?
+# 622| v622_16(void) = ReturnVoid :
+# 622| v622_17(void) = AliasedUse : ~m?
+# 622| v622_18(void) = ExitFunction :
# 628| void C::~C()
# 628| Block 0
@@ -3575,7 +3574,7 @@ ir.cpp:
# 653| r653_4(int) = Constant[0] :
# 653| r653_5(int) = Call[InstanceMemberFunction] : func:r653_3, this:r653_2, 0:r653_4
# 653| mu653_6(unknown) = ^CallSideEffect : ~m?
-# 653| v653_7(void) = ^BufferReadSideEffect[-1] : &:r653_2, ~m?
+# 653| v653_7(void) = ^IndirectReadSideEffect[-1] : &:r653_2, ~m?
# 653| mu653_8(C) = ^IndirectMayWriteSideEffect[-1] : &:r653_2
# 654| r654_1(glval) = VariableAddress[#this] :
# 654| r654_2(C *) = Load[#this] : &:r654_1, ~m?
@@ -3584,7 +3583,7 @@ ir.cpp:
# 654| r654_5(int) = Constant[1] :
# 654| r654_6(int) = Call[InstanceMemberFunction] : func:r654_4, this:r654_3, 0:r654_5
# 654| mu654_7(unknown) = ^CallSideEffect : ~m?
-# 654| v654_8(void) = ^BufferReadSideEffect[-1] : &:r654_3, ~m?
+# 654| v654_8(void) = ^IndirectReadSideEffect[-1] : &:r654_3, ~m?
# 654| mu654_9(C) = ^IndirectMayWriteSideEffect[-1] : &:r654_3
# 655| r655_1(glval) = VariableAddress[#this] :
# 655| r655_2(C *) = Load[#this] : &:r655_1, ~m?
@@ -3592,7 +3591,7 @@ ir.cpp:
# 655| r655_4(int) = Constant[2] :
# 655| r655_5(int) = Call[InstanceMemberFunction] : func:r655_3, this:r655_2, 0:r655_4
# 655| mu655_6(unknown) = ^CallSideEffect : ~m?
-# 655| v655_7(void) = ^BufferReadSideEffect[-1] : &:r655_2, ~m?
+# 655| v655_7(void) = ^IndirectReadSideEffect[-1] : &:r655_2, ~m?
# 655| mu655_8(C) = ^IndirectMayWriteSideEffect[-1] : &:r655_2
# 656| v656_1(void) = NoOp :
# 652| v652_8(void) = ReturnIndirection[#this] : &:r652_6, ~m?
@@ -4006,7 +4005,7 @@ ir.cpp:
#-----| r0_6(String &) = CopyValue : r745_15
# 745| r745_16(String &) = Call[operator=] : func:r745_12, this:r745_11, 0:r0_6
# 745| mu745_17(unknown) = ^CallSideEffect : ~m?
-# 745| v745_18(void) = ^BufferReadSideEffect[-1] : &:r745_11, ~m?
+# 745| v745_18(void) = ^IndirectReadSideEffect[-1] : &:r745_11, ~m?
#-----| v0_7(void) = ^BufferReadSideEffect[0] : &:r0_6, ~m?
# 745| mu745_19(String) = ^IndirectMayWriteSideEffect[-1] : &:r745_11
#-----| r0_8(glval) = CopyValue : r745_16
@@ -4113,7 +4112,7 @@ ir.cpp:
#-----| r0_8(Base &) = CopyValue : r754_14
# 754| r754_15(Base &) = Call[operator=] : func:r754_10, this:r0_5, 0:r0_8
# 754| mu754_16(unknown) = ^CallSideEffect : ~m?
-#-----| v0_9(void) = ^BufferReadSideEffect[-1] : &:r0_5, ~m?
+#-----| v0_9(void) = ^IndirectReadSideEffect[-1] : &:r0_5, ~m?
#-----| v0_10(void) = ^BufferReadSideEffect[0] : &:r0_8, ~m?
#-----| mu0_11(Base) = ^IndirectMayWriteSideEffect[-1] : &:r0_5
#-----| r0_12(glval) = CopyValue : r754_15
@@ -4129,7 +4128,7 @@ ir.cpp:
#-----| r0_14(String &) = CopyValue : r754_24
# 754| r754_25(String &) = Call[operator=] : func:r754_21, this:r754_20, 0:r0_14
# 754| mu754_26(unknown) = ^CallSideEffect : ~m?
-# 754| v754_27(void) = ^BufferReadSideEffect[-1] : &:r754_20, ~m?
+# 754| v754_27(void) = ^IndirectReadSideEffect[-1] : &:r754_20, ~m?
#-----| v0_15(void) = ^BufferReadSideEffect[0] : &:r0_14, ~m?
# 754| mu754_28(String) = ^IndirectMayWriteSideEffect[-1] : &:r754_20
#-----| r0_16(glval) = CopyValue : r754_25
@@ -4220,7 +4219,7 @@ ir.cpp:
#-----| r0_8(Middle &) = CopyValue : r763_14
# 763| r763_15(Middle &) = Call[operator=] : func:r763_10, this:r0_5, 0:r0_8
# 763| mu763_16(unknown) = ^CallSideEffect : ~m?
-#-----| v0_9(void) = ^BufferReadSideEffect[-1] : &:r0_5, ~m?
+#-----| v0_9(void) = ^IndirectReadSideEffect[-1] : &:r0_5, ~m?
#-----| v0_10(void) = ^BufferReadSideEffect[0] : &:r0_8, ~m?
#-----| mu0_11(Middle) = ^IndirectMayWriteSideEffect[-1] : &:r0_5
#-----| r0_12(glval) = CopyValue : r763_15
@@ -4236,7 +4235,7 @@ ir.cpp:
#-----| r0_14(String &) = CopyValue : r763_24
# 763| r763_25(String &) = Call[operator=] : func:r763_21, this:r763_20, 0:r0_14
# 763| mu763_26(unknown) = ^CallSideEffect : ~m?
-# 763| v763_27(void) = ^BufferReadSideEffect[-1] : &:r763_20, ~m?
+# 763| v763_27(void) = ^IndirectReadSideEffect[-1] : &:r763_20, ~m?
#-----| v0_15(void) = ^BufferReadSideEffect[0] : &:r0_14, ~m?
# 763| mu763_28(String) = ^IndirectMayWriteSideEffect[-1] : &:r763_20
#-----| r0_16(glval) = CopyValue : r763_25
@@ -4505,7 +4504,7 @@ ir.cpp:
# 808| r808_5(Base &) = CopyValue : r808_4
# 808| r808_6(Base &) = Call[operator=] : func:r808_2, this:r808_1, 0:r808_5
# 808| mu808_7(unknown) = ^CallSideEffect : ~m?
-# 808| v808_8(void) = ^BufferReadSideEffect[-1] : &:r808_1, ~m?
+# 808| v808_8(void) = ^IndirectReadSideEffect[-1] : &:r808_1, ~m?
# 808| v808_9(void) = ^BufferReadSideEffect[0] : &:r808_5, ~m?
# 808| mu808_10(Base) = ^IndirectMayWriteSideEffect[-1] : &:r808_1
# 808| r808_11(glval) = CopyValue : r808_6
@@ -4525,7 +4524,7 @@ ir.cpp:
# 809| r809_14(Base &) = CopyValue : r809_13
# 809| r809_15(Base &) = Call[operator=] : func:r809_2, this:r809_1, 0:r809_14
# 809| mu809_16(unknown) = ^CallSideEffect : ~m?
-# 809| v809_17(void) = ^BufferReadSideEffect[-1] : &:r809_1, ~m?
+# 809| v809_17(void) = ^IndirectReadSideEffect[-1] : &:r809_1, ~m?
# 809| v809_18(void) = ^BufferReadSideEffect[0] : &:r809_14, ~m?
# 809| mu809_19(Base) = ^IndirectMayWriteSideEffect[-1] : &:r809_1
# 809| r809_20(glval) = CopyValue : r809_15
@@ -4545,7 +4544,7 @@ ir.cpp:
# 810| r810_14(Base &) = CopyValue : r810_13
# 810| r810_15(Base &) = Call[operator=] : func:r810_2, this:r810_1, 0:r810_14
# 810| mu810_16(unknown) = ^CallSideEffect : ~m?
-# 810| v810_17(void) = ^BufferReadSideEffect[-1] : &:r810_1, ~m?
+# 810| v810_17(void) = ^IndirectReadSideEffect[-1] : &:r810_1, ~m?
# 810| v810_18(void) = ^BufferReadSideEffect[0] : &:r810_14, ~m?
# 810| mu810_19(Base) = ^IndirectMayWriteSideEffect[-1] : &:r810_1
# 810| r810_20(glval) = CopyValue : r810_15
@@ -4577,7 +4576,7 @@ ir.cpp:
# 816| r816_6(Middle &) = CopyValue : r816_5
# 816| r816_7(Middle &) = Call[operator=] : func:r816_2, this:r816_1, 0:r816_6
# 816| mu816_8(unknown) = ^CallSideEffect : ~m?
-# 816| v816_9(void) = ^BufferReadSideEffect[-1] : &:r816_1, ~m?
+# 816| v816_9(void) = ^IndirectReadSideEffect[-1] : &:r816_1, ~m?
# 816| v816_10(void) = ^BufferReadSideEffect[0] : &:r816_6, ~m?
# 816| mu816_11(Middle) = ^IndirectMayWriteSideEffect[-1] : &:r816_1
# 816| r816_12(glval) = CopyValue : r816_7
@@ -4589,7 +4588,7 @@ ir.cpp:
# 817| r817_6(Middle &) = CopyValue : r817_5
# 817| r817_7(Middle &) = Call[operator=] : func:r817_2, this:r817_1, 0:r817_6
# 817| mu817_8(unknown) = ^CallSideEffect : ~m?
-# 817| v817_9(void) = ^BufferReadSideEffect[-1] : &:r817_1, ~m?
+# 817| v817_9(void) = ^IndirectReadSideEffect[-1] : &:r817_1, ~m?
# 817| v817_10(void) = ^BufferReadSideEffect[0] : &:r817_6, ~m?
# 817| mu817_11(Middle) = ^IndirectMayWriteSideEffect[-1] : &:r817_1
# 817| r817_12(glval) = CopyValue : r817_7
@@ -4616,7 +4615,7 @@ ir.cpp:
# 822| r822_6(Base &) = CopyValue : r822_5
# 822| r822_7(Base &) = Call[operator=] : func:r822_2, this:r822_1, 0:r822_6
# 822| mu822_8(unknown) = ^CallSideEffect : ~m?
-# 822| v822_9(void) = ^BufferReadSideEffect[-1] : &:r822_1, ~m?
+# 822| v822_9(void) = ^IndirectReadSideEffect[-1] : &:r822_1, ~m?
# 822| v822_10(void) = ^BufferReadSideEffect[0] : &:r822_6, ~m?
# 822| mu822_11(Base) = ^IndirectMayWriteSideEffect[-1] : &:r822_1
# 822| r822_12(glval) = CopyValue : r822_7
@@ -4637,7 +4636,7 @@ ir.cpp:
# 823| r823_15(Base &) = CopyValue : r823_14
# 823| r823_16(Base &) = Call[operator=] : func:r823_2, this:r823_1, 0:r823_15
# 823| mu823_17(unknown) = ^CallSideEffect : ~m?
-# 823| v823_18(void) = ^BufferReadSideEffect[-1] : &:r823_1, ~m?
+# 823| v823_18(void) = ^IndirectReadSideEffect[-1] : &:r823_1, ~m?
# 823| v823_19(void) = ^BufferReadSideEffect[0] : &:r823_15, ~m?
# 823| mu823_20(Base) = ^IndirectMayWriteSideEffect[-1] : &:r823_1
# 823| r823_21(glval) = CopyValue : r823_16
@@ -4658,7 +4657,7 @@ ir.cpp:
# 824| r824_15(Base &) = CopyValue : r824_14
# 824| r824_16(Base &) = Call[operator=] : func:r824_2, this:r824_1, 0:r824_15
# 824| mu824_17(unknown) = ^CallSideEffect : ~m?
-# 824| v824_18(void) = ^BufferReadSideEffect[-1] : &:r824_1, ~m?
+# 824| v824_18(void) = ^IndirectReadSideEffect[-1] : &:r824_1, ~m?
# 824| v824_19(void) = ^BufferReadSideEffect[0] : &:r824_15, ~m?
# 824| mu824_20(Base) = ^IndirectMayWriteSideEffect[-1] : &:r824_1
# 824| r824_21(glval) = CopyValue : r824_16
@@ -4694,7 +4693,7 @@ ir.cpp:
# 830| r830_7(Derived &) = CopyValue : r830_6
# 830| r830_8(Derived &) = Call[operator=] : func:r830_2, this:r830_1, 0:r830_7
# 830| mu830_9(unknown) = ^CallSideEffect : ~m?
-# 830| v830_10(void) = ^BufferReadSideEffect[-1] : &:r830_1, ~m?
+# 830| v830_10(void) = ^IndirectReadSideEffect[-1] : &:r830_1, ~m?
# 830| v830_11(void) = ^BufferReadSideEffect[0] : &:r830_7, ~m?
# 830| mu830_12(Derived) = ^IndirectMayWriteSideEffect[-1] : &:r830_1
# 830| r830_13(glval) = CopyValue : r830_8
@@ -4707,7 +4706,7 @@ ir.cpp:
# 831| r831_7(Derived &) = CopyValue : r831_6
# 831| r831_8(Derived &) = Call[operator=] : func:r831_2, this:r831_1, 0:r831_7
# 831| mu831_9(unknown) = ^CallSideEffect : ~m?
-# 831| v831_10(void) = ^BufferReadSideEffect[-1] : &:r831_1, ~m?
+# 831| v831_10(void) = ^IndirectReadSideEffect[-1] : &:r831_1, ~m?
# 831| v831_11(void) = ^BufferReadSideEffect[0] : &:r831_7, ~m?
# 831| mu831_12(Derived) = ^IndirectMayWriteSideEffect[-1] : &:r831_1
# 831| r831_13(glval) = CopyValue : r831_8
@@ -5688,7 +5687,7 @@ ir.cpp:
# 1044| r1044_4(float) = Constant[1.0] :
# 1044| r1044_5(char) = Call[operator()] : func:r1044_3, this:r1044_2, 0:r1044_4
# 1044| mu1044_6(unknown) = ^CallSideEffect : ~m?
-# 1044| v1044_7(void) = ^BufferReadSideEffect[-1] : &:r1044_2, ~m?
+# 1044| v1044_7(void) = ^IndirectReadSideEffect[-1] : &:r1044_2, ~m?
# 1045| r1045_1(glval) = VariableAddress[lambda_val] :
# 1045| r1045_2(glval) = VariableAddress[#temp1045:20] :
# 1045| mu1045_3(decltype([...](...){...})) = Uninitialized[#temp1045:20] : &:r1045_2
@@ -5709,7 +5708,7 @@ ir.cpp:
# 1046| r1046_4(float) = Constant[2.0] :
# 1046| r1046_5(char) = Call[operator()] : func:r1046_3, this:r1046_2, 0:r1046_4
# 1046| mu1046_6(unknown) = ^CallSideEffect : ~m?
-# 1046| v1046_7(void) = ^BufferReadSideEffect[-1] : &:r1046_2, ~m?
+# 1046| v1046_7(void) = ^IndirectReadSideEffect[-1] : &:r1046_2, ~m?
# 1047| r1047_1(glval) = VariableAddress[lambda_ref_explicit] :
# 1047| r1047_2(glval) = VariableAddress[#temp1047:29] :
# 1047| mu1047_3(decltype([...](...){...})) = Uninitialized[#temp1047:29] : &:r1047_2
@@ -5727,7 +5726,7 @@ ir.cpp:
# 1048| r1048_4(float) = Constant[3.0] :
# 1048| r1048_5(char) = Call[operator()] : func:r1048_3, this:r1048_2, 0:r1048_4
# 1048| mu1048_6(unknown) = ^CallSideEffect : ~m?
-# 1048| v1048_7(void) = ^BufferReadSideEffect[-1] : &:r1048_2, ~m?
+# 1048| v1048_7(void) = ^IndirectReadSideEffect[-1] : &:r1048_2, ~m?
# 1049| r1049_1(glval) = VariableAddress[lambda_val_explicit] :
# 1049| r1049_2(glval) = VariableAddress[#temp1049:29] :
# 1049| mu1049_3(decltype([...](...){...})) = Uninitialized[#temp1049:29] : &:r1049_2
@@ -5744,7 +5743,7 @@ ir.cpp:
# 1050| r1050_4(float) = Constant[4.0] :
# 1050| r1050_5(char) = Call[operator()] : func:r1050_3, this:r1050_2, 0:r1050_4
# 1050| mu1050_6(unknown) = ^CallSideEffect : ~m?
-# 1050| v1050_7(void) = ^BufferReadSideEffect[-1] : &:r1050_2, ~m?
+# 1050| v1050_7(void) = ^IndirectReadSideEffect[-1] : &:r1050_2, ~m?
# 1051| r1051_1(glval) = VariableAddress[lambda_mixed_explicit] :
# 1051| r1051_2(glval) = VariableAddress[#temp1051:31] :
# 1051| mu1051_3(decltype([...](...){...})) = Uninitialized[#temp1051:31] : &:r1051_2
@@ -5766,7 +5765,7 @@ ir.cpp:
# 1052| r1052_4(float) = Constant[5.0] :
# 1052| r1052_5(char) = Call[operator()] : func:r1052_3, this:r1052_2, 0:r1052_4
# 1052| mu1052_6(unknown) = ^CallSideEffect : ~m?
-# 1052| v1052_7(void) = ^BufferReadSideEffect[-1] : &:r1052_2, ~m?
+# 1052| v1052_7(void) = ^IndirectReadSideEffect[-1] : &:r1052_2, ~m?
# 1053| r1053_1(glval) = VariableAddress[r] :
# 1053| r1053_2(glval) = VariableAddress[x] :
# 1053| r1053_3(int) = Load[x] : &:r1053_2, ~m?
@@ -5804,7 +5803,7 @@ ir.cpp:
# 1055| r1055_4(float) = Constant[6.0] :
# 1055| r1055_5(char) = Call[operator()] : func:r1055_3, this:r1055_2, 0:r1055_4
# 1055| mu1055_6(unknown) = ^CallSideEffect : ~m?
-# 1055| v1055_7(void) = ^BufferReadSideEffect[-1] : &:r1055_2, ~m?
+# 1055| v1055_7(void) = ^IndirectReadSideEffect[-1] : &:r1055_2, ~m?
# 1056| v1056_1(void) = NoOp :
# 1040| v1040_10(void) = ReturnIndirection[s] : &:r1040_8, ~m?
# 1040| v1040_11(void) = ReturnVoid :
@@ -5869,7 +5868,7 @@ ir.cpp:
# 1043| r1043_16(glval) = FunctionAddress[c_str] :
# 1043| r1043_17(char *) = Call[c_str] : func:r1043_16, this:r1043_15
# 1043| mu1043_18(unknown) = ^CallSideEffect : ~m?
-# 1043| v1043_19(void) = ^BufferReadSideEffect[-1] : &:r1043_15, ~m?
+# 1043| v1043_19(void) = ^IndirectReadSideEffect[-1] : &:r1043_15, ~m?
# 1043| r1043_20(glval) = VariableAddress[#this] :
# 1043| r1043_21(lambda [] type at line 1043, col. 21 *) = Load[#this] : &:r1043_20, ~m?
# 1043| r1043_22(glval) = FieldAddress[x] : r1043_21
@@ -5921,7 +5920,7 @@ ir.cpp:
# 1045| r1045_14(glval) = FunctionAddress[c_str] :
# 1045| r1045_15(char *) = Call[c_str] : func:r1045_14, this:r1045_13
# 1045| mu1045_16(unknown) = ^CallSideEffect : ~m?
-# 1045| v1045_17(void) = ^BufferReadSideEffect[-1] : &:r1045_13, ~m?
+# 1045| v1045_17(void) = ^IndirectReadSideEffect[-1] : &:r1045_13, ~m?
# 1045| r1045_18(glval) = VariableAddress[#this] :
# 1045| r1045_19(lambda [] type at line 1045, col. 21 *) = Load[#this] : &:r1045_18, ~m?
# 1045| r1045_20(glval) = FieldAddress[x] : r1045_19
@@ -5955,7 +5954,7 @@ ir.cpp:
# 1047| r1047_16(glval) = FunctionAddress[c_str] :
# 1047| r1047_17(char *) = Call[c_str] : func:r1047_16, this:r1047_15
# 1047| mu1047_18(unknown) = ^CallSideEffect : ~m?
-# 1047| v1047_19(void) = ^BufferReadSideEffect[-1] : &:r1047_15, ~m?
+# 1047| v1047_19(void) = ^IndirectReadSideEffect[-1] : &:r1047_15, ~m?
# 1047| r1047_20(int) = Constant[0] :
# 1047| r1047_21(glval) = PointerAdd[1] : r1047_17, r1047_20
# 1047| r1047_22(char) = Load[?] : &:r1047_21, ~m?
@@ -6003,7 +6002,7 @@ ir.cpp:
# 1049| r1049_14(glval) = FunctionAddress[c_str] :
# 1049| r1049_15(char *) = Call[c_str] : func:r1049_14, this:r1049_13
# 1049| mu1049_16(unknown) = ^CallSideEffect : ~m?
-# 1049| v1049_17(void) = ^BufferReadSideEffect[-1] : &:r1049_13, ~m?
+# 1049| v1049_17(void) = ^IndirectReadSideEffect[-1] : &:r1049_13, ~m?
# 1049| r1049_18(int) = Constant[0] :
# 1049| r1049_19(glval) = PointerAdd[1] : r1049_15, r1049_18
# 1049| r1049_20(char) = Load[?] : &:r1049_19, ~m?
@@ -6034,7 +6033,7 @@ ir.cpp:
# 1051| r1051_16(glval) = FunctionAddress[c_str] :
# 1051| r1051_17(char *) = Call[c_str] : func:r1051_16, this:r1051_15
# 1051| mu1051_18(unknown) = ^CallSideEffect : ~m?
-# 1051| v1051_19(void) = ^BufferReadSideEffect[-1] : &:r1051_15, ~m?
+# 1051| v1051_19(void) = ^IndirectReadSideEffect[-1] : &:r1051_15, ~m?
# 1051| r1051_20(glval) = VariableAddress[#this] :
# 1051| r1051_21(lambda [] type at line 1051, col. 32 *) = Load[#this] : &:r1051_20, ~m?
# 1051| r1051_22(glval) = FieldAddress[x] : r1051_21
@@ -6068,7 +6067,7 @@ ir.cpp:
# 1054| r1054_16(glval) = FunctionAddress[c_str] :
# 1054| r1054_17(char *) = Call[c_str] : func:r1054_16, this:r1054_15
# 1054| mu1054_18(unknown) = ^CallSideEffect : ~m?
-# 1054| v1054_19(void) = ^BufferReadSideEffect[-1] : &:r1054_15, ~m?
+# 1054| v1054_19(void) = ^IndirectReadSideEffect[-1] : &:r1054_15, ~m?
# 1054| r1054_20(glval) = VariableAddress[#this] :
# 1054| r1054_21(lambda [] type at line 1054, col. 23 *) = Load[#this] : &:r1054_20, ~m?
# 1054| r1054_22(glval) = FieldAddress[x] : r1054_21
@@ -6095,37 +6094,37 @@ ir.cpp:
# 1077| void RangeBasedFor(vector const&)
# 1077| Block 0
-# 1077| v1077_1(void) = EnterFunction :
-# 1077| mu1077_2(unknown) = AliasedDefinition :
-# 1077| mu1077_3(unknown) = InitializeNonLocal :
-# 1077| r1077_4(glval &>) = VariableAddress[v] :
-# 1077| mu1077_5(vector &) = InitializeParameter[v] : &:r1077_4
-# 1077| r1077_6(vector &) = Load[v] : &:r1077_4, ~m?
-# 1077| mu1077_7(unknown) = InitializeIndirection[v] : &:r1077_6
-# 1078| r1078_1(glval &>) = VariableAddress[(__range)] :
-# 1078| r1078_2(glval &>) = VariableAddress[v] :
-# 1078| r1078_3(vector &) = Load[v] : &:r1078_2, ~m?
-# 1078| r1078_4(glval>) = CopyValue : r1078_3
-# 1078| r1078_5(vector &) = CopyValue : r1078_4
-# 1078| mu1078_6(vector &) = Store[(__range)] : &:r1078_1, r1078_5
-# 1078| r1078_7(glval) = VariableAddress[(__begin)] :
-# 1078| r1078_8(glval &>) = VariableAddress[(__range)] :
-# 1078| r1078_9(vector &) = Load[(__range)] : &:r1078_8, ~m?
-#-----| r0_1(glval>) = CopyValue : r1078_9
-# 1078| r1078_10(glval) = FunctionAddress[begin] :
-# 1078| r1078_11(iterator) = Call[begin] : func:r1078_10, this:r0_1
-# 1078| mu1078_12(unknown) = ^CallSideEffect : ~m?
-#-----| v0_2(void) = ^BufferReadSideEffect[-1] : &:r0_1, ~m?
-# 1078| mu1078_13(iterator) = Store[(__begin)] : &:r1078_7, r1078_11
-# 1078| r1078_14(glval) = VariableAddress[(__end)] :
-# 1078| r1078_15(glval &>) = VariableAddress[(__range)] :
-# 1078| r1078_16(vector &) = Load[(__range)] : &:r1078_15, ~m?
-#-----| r0_3(glval>) = CopyValue : r1078_16
-# 1078| r1078_17(glval) = FunctionAddress[end] :
-# 1078| r1078_18(iterator) = Call[end] : func:r1078_17, this:r0_3
-# 1078| mu1078_19(unknown) = ^CallSideEffect : ~m?
-#-----| v0_4(void) = ^BufferReadSideEffect[-1] : &:r0_3, ~m?
-# 1078| mu1078_20(iterator) = Store[(__end)] : &:r1078_14, r1078_18
+# 1077| v1077_1(void) = EnterFunction :
+# 1077| mu1077_2(unknown) = AliasedDefinition :
+# 1077| mu1077_3(unknown) = InitializeNonLocal :
+# 1077| r1077_4(glval &>) = VariableAddress[v] :
+# 1077| mu1077_5(vector &) = InitializeParameter[v] : &:r1077_4
+# 1077| r1077_6(vector &) = Load[v] : &:r1077_4, ~m?
+# 1077| mu1077_7(unknown) = InitializeIndirection[v] : &:r1077_6
+# 1078| r1078_1(glval &>) = VariableAddress[(__range)] :
+# 1078| r1078_2(glval &>) = VariableAddress[v] :
+# 1078| r1078_3(vector &) = Load[v] : &:r1078_2, ~m?
+# 1078| r1078_4(glval>) = CopyValue : r1078_3
+# 1078| r1078_5(vector &) = CopyValue : r1078_4
+# 1078| mu1078_6(vector &) = Store[(__range)] : &:r1078_1, r1078_5
+# 1078| r1078_7(glval) = VariableAddress[(__begin)] :
+# 1078| r1078_8(glval &>) = VariableAddress[(__range)] :
+# 1078| r1078_9(vector &) = Load[(__range)] : &:r1078_8, ~m?
+#-----| r0_1(glval>) = CopyValue : r1078_9
+# 1078| r1078_10(glval) = FunctionAddress[begin] :
+# 1078| r1078_11(iterator) = Call[begin] : func:r1078_10, this:r0_1
+# 1078| mu1078_12(unknown) = ^CallSideEffect : ~m?
+#-----| v0_2(void) = ^IndirectReadSideEffect[-1] : &:r0_1, ~m?
+# 1078| mu1078_13(iterator) = Store[(__begin)] : &:r1078_7, r1078_11
+# 1078| r1078_14(glval) = VariableAddress[(__end)] :
+# 1078| r1078_15(glval &>) = VariableAddress[(__range)] :
+# 1078| r1078_16(vector &) = Load[(__range)] : &:r1078_15, ~m?
+#-----| r0_3(glval>) = CopyValue : r1078_16
+# 1078| r1078_17(glval) = FunctionAddress[end] :
+# 1078| r1078_18(iterator) = Call[end] : func:r1078_17, this:r0_3
+# 1078| mu1078_19(unknown) = ^CallSideEffect : ~m?
+#-----| v0_4(void) = ^IndirectReadSideEffect[-1] : &:r0_3, ~m?
+# 1078| mu1078_20(iterator) = Store[(__end)] : &:r1078_14, r1078_18
#-----| Goto -> Block 1
# 1078| Block 1
@@ -6136,26 +6135,26 @@ ir.cpp:
# 1078| r1078_24(iterator) = Load[(__end)] : &:r1078_23, ~m?
# 1078| r1078_25(bool) = Call[operator!=] : func:r1078_22, this:r0_5, 0:r1078_24
# 1078| mu1078_26(unknown) = ^CallSideEffect : ~m?
-#-----| v0_6(void) = ^BufferReadSideEffect[-1] : &:r0_5, ~m?
+#-----| v0_6(void) = ^IndirectReadSideEffect[-1] : &:r0_5, ~m?
# 1078| v1078_27(void) = ConditionalBranch : r1078_25
#-----| False -> Block 5
#-----| True -> Block 2
# 1078| Block 2
-# 1078| r1078_28(glval) = VariableAddress[e] :
-# 1078| r1078_29(glval) = VariableAddress[(__begin)] :
-#-----| r0_7(glval) = Convert : r1078_29
-# 1078| r1078_30(glval) = FunctionAddress[operator*] :
-# 1078| r1078_31(int &) = Call[operator*] : func:r1078_30, this:r0_7
-# 1078| mu1078_32(unknown) = ^CallSideEffect : ~m?
-#-----| v0_8(void) = ^BufferReadSideEffect[-1] : &:r0_7, ~m?
-# 1078| r1078_33(int) = Load[?] : &:r1078_31, ~m?
-# 1078| mu1078_34(int) = Store[e] : &:r1078_28, r1078_33
-# 1079| r1079_1(glval) = VariableAddress[e] :
-# 1079| r1079_2(int) = Load[e] : &:r1079_1, ~m?
-# 1079| r1079_3(int) = Constant[0] :
-# 1079| r1079_4(bool) = CompareGT : r1079_2, r1079_3
-# 1079| v1079_5(void) = ConditionalBranch : r1079_4
+# 1078| r1078_28(glval) = VariableAddress[e] :
+# 1078| r1078_29(glval) = VariableAddress[(__begin)] :
+#-----| r0_7(glval) = Convert : r1078_29
+# 1078| r1078_30(glval) = FunctionAddress[operator*] :
+# 1078| r1078_31(int &) = Call[operator*] : func:r1078_30, this:r0_7
+# 1078| mu1078_32(unknown) = ^CallSideEffect : ~m?
+#-----| v0_8(void) = ^IndirectReadSideEffect[-1] : &:r0_7, ~m?
+# 1078| r1078_33(int) = Load[?] : &:r1078_31, ~m?
+# 1078| mu1078_34(int) = Store[e] : &:r1078_28, r1078_33
+# 1079| r1079_1(glval) = VariableAddress[e] :
+# 1079| r1079_2(int) = Load[e] : &:r1079_1, ~m?
+# 1079| r1079_3(int) = Constant[0] :
+# 1079| r1079_4(bool) = CompareGT : r1079_2, r1079_3
+# 1079| v1079_5(void) = ConditionalBranch : r1079_4
#-----| False -> Block 4
#-----| True -> Block 3
@@ -6169,36 +6168,36 @@ ir.cpp:
# 1078| r1078_37(glval) = FunctionAddress[operator++] :
# 1078| r1078_38(iterator &) = Call[operator++] : func:r1078_37, this:r1078_36
# 1078| mu1078_39(unknown) = ^CallSideEffect : ~m?
-# 1078| v1078_40(void) = ^BufferReadSideEffect[-1] : &:r1078_36, ~m?
+# 1078| v1078_40(void) = ^IndirectReadSideEffect[-1] : &:r1078_36, ~m?
# 1078| mu1078_41(iterator) = ^IndirectMayWriteSideEffect[-1] : &:r1078_36
# 1078| r1078_42(glval) = CopyValue : r1078_38
#-----| Goto (back edge) -> Block 1
# 1084| Block 5
-# 1084| r1084_1(glval &>) = VariableAddress[(__range)] :
-# 1084| r1084_2(glval &>) = VariableAddress[v] :
-# 1084| r1084_3(vector &) = Load[v] : &:r1084_2, ~m?
-# 1084| r1084_4(glval>) = CopyValue : r1084_3
-# 1084| r1084_5(vector &) = CopyValue : r1084_4
-# 1084| mu1084_6(vector &) = Store[(__range)] : &:r1084_1, r1084_5
-# 1084| r1084_7(glval) = VariableAddress[(__begin)] :
-# 1084| r1084_8(glval &>) = VariableAddress[(__range)] :
-# 1084| r1084_9(vector &) = Load[(__range)] : &:r1084_8, ~m?
-#-----| r0_9(glval>) = CopyValue : r1084_9
-# 1084| r1084_10(glval) = FunctionAddress[begin] :
-# 1084| r1084_11(iterator) = Call[begin] : func:r1084_10, this:r0_9
-# 1084| mu1084_12(unknown) = ^CallSideEffect : ~m?
-#-----| v0_10(void) = ^BufferReadSideEffect[-1] : &:r0_9, ~m?
-# 1084| mu1084_13(iterator) = Store[(__begin)] : &:r1084_7, r1084_11
-# 1084| r1084_14(glval) = VariableAddress[(__end)] :
-# 1084| r1084_15(glval &>) = VariableAddress[(__range)] :
-# 1084| r1084_16(vector &) = Load[(__range)] : &:r1084_15, ~m?
-#-----| r0_11(glval>) = CopyValue : r1084_16
-# 1084| r1084_17(glval) = FunctionAddress[end] :
-# 1084| r1084_18(iterator) = Call[end] : func:r1084_17, this:r0_11
-# 1084| mu1084_19(unknown) = ^CallSideEffect : ~m?
-#-----| v0_12(void) = ^BufferReadSideEffect[-1] : &:r0_11, ~m?
-# 1084| mu1084_20(iterator) = Store[(__end)] : &:r1084_14, r1084_18
+# 1084| r1084_1(glval &>) = VariableAddress[(__range)] :
+# 1084| r1084_2(glval &>) = VariableAddress[v] :
+# 1084| r1084_3(vector &) = Load[v] : &:r1084_2, ~m?
+# 1084| r1084_4(glval>) = CopyValue : r1084_3
+# 1084| r1084_5(vector &) = CopyValue : r1084_4
+# 1084| mu1084_6(vector &) = Store[(__range)] : &:r1084_1, r1084_5
+# 1084| r1084_7(glval) = VariableAddress[(__begin)] :
+# 1084| r1084_8(glval &>) = VariableAddress[(__range)] :
+# 1084| r1084_9(vector &) = Load[(__range)] : &:r1084_8, ~m?
+#-----| r0_9(glval>) = CopyValue : r1084_9
+# 1084| r1084_10(glval) = FunctionAddress[begin] :
+# 1084| r1084_11(iterator) = Call[begin] : func:r1084_10, this:r0_9
+# 1084| mu1084_12(unknown) = ^CallSideEffect : ~m?
+#-----| v0_10(void) = ^IndirectReadSideEffect[-1] : &:r0_9, ~m?
+# 1084| mu1084_13(iterator) = Store[(__begin)] : &:r1084_7, r1084_11
+# 1084| r1084_14(glval) = VariableAddress[(__end)] :
+# 1084| r1084_15(glval