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> supplier) { + } + + default Supplier> getTrailerFields() { + return null; + } +} From 01c13c470350c2784d427c5a47d68a198a503fd1 Mon Sep 17 00:00:00 2001 From: ihsinme Date: Thu, 4 Mar 2021 16:14:11 +0300 Subject: [PATCH 029/433] Add files via upload --- ...ratorPrecedenceLogicErrorWhenUseBoolType.c | 11 +++++ ...rPrecedenceLogicErrorWhenUseBoolType.qhelp | 28 +++++++++++ ...atorPrecedenceLogicErrorWhenUseBoolType.ql | 48 +++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 cpp/ql/src/experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBoolType.c create mode 100644 cpp/ql/src/experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBoolType.qhelp create mode 100644 cpp/ql/src/experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBoolType.ql diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBoolType.c b/cpp/ql/src/experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBoolType.c new file mode 100644 index 000000000000..8458d82f7ad2 --- /dev/null +++ b/cpp/ql/src/experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBoolType.c @@ -0,0 +1,11 @@ +if(len=funcReadData()==0) return 1; // BAD: variable `len` will not equal the value returned by function `funcReadData()` +... +if((len=funcReadData())==0) return 1; // GOOD: variable `len` equal the value returned by function `funcReadData()` +... +bool a=true; +a++;// BAD: variable `a` does not change its meaning +bool b; +b=-a;// BAD: variable `b` equal `true` +... +a=false;// GOOD: variable `a` equal `false` +b=!a;// GOOD: variable `b` equal `false` diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBoolType.qhelp b/cpp/ql/src/experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBoolType.qhelp new file mode 100644 index 000000000000..8114da831feb --- /dev/null +++ b/cpp/ql/src/experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBoolType.qhelp @@ -0,0 +1,28 @@ + + + +

    Finding places of confusing use of boolean type. For example, a unary minus does not work before a boolean type and an increment always gives true.

    + + +
    + + +

    we recommend making the code simpler.

    + +
    + +

    The following example demonstrates erroneous and fixed methods for using a boolean data type.

    + + +
    + + +
  • + CERT C Coding Standard: + EXP00-C. Use parentheses for precedence of operation. +
  • + +
    +
    diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBoolType.ql b/cpp/ql/src/experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBoolType.ql new file mode 100644 index 000000000000..1a116a83dbf7 --- /dev/null +++ b/cpp/ql/src/experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBoolType.ql @@ -0,0 +1,48 @@ +/** + * @name Operator Precedence Logic Error When Use Bool Type + * @description --Finding places of confusing use of boolean type. + * --For example, a unary minus does not work before a boolean type and an increment always gives true. + * @kind problem + * @id cpp/operator-precedence-logic-error-when-use-bool-type + * @problem.severity warning + * @precision medium + * @tags correctness + * security + * external/cwe/cwe-783 + * external/cwe/cwe-480 + */ + +import cpp +import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis + +/** Holds, if it is an expression, a boolean increment. */ +predicate incrementBoolType(Expr exp) { + exp.(IncrementOperation).getOperand().getType() instanceof BoolType +} + +/** Holds, if this is an expression, applies a minus to a boolean type. */ +predicate revertSignBoolType(Expr exp) { + exp.(AssignExpr).getRValue().(UnaryMinusExpr).getAnOperand().getType() instanceof BoolType and + exp.(AssignExpr).getLValue().getType() instanceof BoolType +} + +/** Holds, if this is an expression, uses comparison and assignment outside of execution precedence. */ +predicate assignBoolType(Expr exp) { + exists(ComparisonOperation co | + exp.(AssignExpr).getRValue() = co and + exp.isCondition() and + not co.isParenthesised() and + not exp.(AssignExpr).getLValue().getType() instanceof BoolType and + co.getLeftOperand() instanceof FunctionCall and + not co.getRightOperand().getType() instanceof BoolType and + not co.getRightOperand().getValue() = "0" and + not co.getRightOperand().getValue() = "1" + ) +} + +from Expr exp +where + incrementBoolType(exp) or + revertSignBoolType(exp) or + assignBoolType(exp) +select exp, "this expression needs attention" From 10cc57428907c41e08786242051028c996935ef3 Mon Sep 17 00:00:00 2001 From: ihsinme Date: Thu, 4 Mar 2021 16:15:26 +0300 Subject: [PATCH 030/433] Add files via upload --- ...ecedenceLogicErrorWhenUseBoolType.expected | 5 ++++ ...rPrecedenceLogicErrorWhenUseBoolType.qlref | 1 + .../CWE/CWE-788/semmle/tests/test.cpp | 26 +++++++++++++++++++ 3 files changed, 32 insertions(+) create mode 100644 cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/OperatorPrecedenceLogicErrorWhenUseBoolType.expected create mode 100644 cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/OperatorPrecedenceLogicErrorWhenUseBoolType.qlref create mode 100644 cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/test.cpp 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 new file mode 100644 index 000000000000..76062fc360a3 --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/OperatorPrecedenceLogicErrorWhenUseBoolType.expected @@ -0,0 +1,5 @@ +| test.cpp:10:3: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 | +| test.cpp:16:6:16:21 | ... = ... | this expression needs attention | diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/OperatorPrecedenceLogicErrorWhenUseBoolType.qlref b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/OperatorPrecedenceLogicErrorWhenUseBoolType.qlref new file mode 100644 index 000000000000..5189abcce5d1 --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/OperatorPrecedenceLogicErrorWhenUseBoolType.qlref @@ -0,0 +1 @@ +experimental/Security/CWE/CWE-783/OperatorPrecedenceLogicErrorWhenUseBoolType.ql diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/test.cpp b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/test.cpp new file mode 100644 index 000000000000..f08d2a45757f --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/test.cpp @@ -0,0 +1,26 @@ +int tmpFunc() +{ + return 12; +} +void testFunction() +{ + int i1,i2,i3; + bool b1,b2,b3; + char c1,c2,c3; + b1 = -b2; //BAD + b1 = !b2; //GOOD + b1++; //BAD + ++b1; //BAD + if(i1=tmpFunc()!=i2) //BAD + return; + if(i1=tmpFunc()!=11) //BAD + return; + if((i1=tmpFunc())!=i2) //GOOD + return; + if((i1=tmpFunc())!=11) //GOOD + return; + if(i1=tmpFunc()!=1) //GOOD + return; + if(i1=tmpFunc()==b1) //GOOD + return; +} From 919c6b4b0aae6053a1b4b4d1e3657ac50e4a94f5 Mon Sep 17 00:00:00 2001 From: luchua-bc Date: Fri, 5 Mar 2021 02:50:54 +0000 Subject: [PATCH 031/433] Optimize flow steps --- .../CWE-1004/SensitiveCookieNotHttpOnly.ql | 36 ++++++++++--------- .../SensitiveCookieNotHttpOnly.expected | 10 ++++-- .../CWE-1004/SensitiveCookieNotHttpOnly.java | 13 +++++-- 3 files changed, 37 insertions(+), 22 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 d9104cbad970..f22e99e567c8 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql @@ -8,6 +8,7 @@ */ import java +import semmle.code.java.dataflow.FlowSteps import semmle.code.java.frameworks.Servlets import semmle.code.java.dataflow.TaintTracking import DataFlow::PathGraph @@ -41,18 +42,31 @@ class SetCookieMethodAccess extends MethodAccess { } } +/** The cookie class of Java EE. */ +class CookieClass extends RefType { + CookieClass() { + this.getASupertype*() + .hasQualifiedName(["javax.servlet.http", "javax.ws.rs.core", "jakarta.ws.rs.core"], "Cookie") + } +} + +/** The method call `toString` to get a stringified cookie representation. */ +class CookieInstanceExpr extends TaintPreservingCallable { + CookieInstanceExpr() { + this.getDeclaringType() instanceof CookieClass and + this.hasName("toString") + } + + override predicate returnsTaintFrom(int arg) { arg = -1 } +} + /** Sensitive cookie name used in a `Cookie` constructor or a `Set-Cookie` call. */ class SensitiveCookieNameExpr extends Expr { SensitiveCookieNameExpr() { exists( ClassInstanceExpr cie // new Cookie("jwt_token", token) | - ( - cie.getConstructedType().hasQualifiedName("javax.servlet.http", "Cookie") or - cie.getConstructedType() - .getASupertype*() - .hasQualifiedName(["javax.ws.rs.core", "jakarta.ws.rs.core"], "Cookie") - ) and + cie.getConstructedType() instanceof CookieClass and this = cie and isSensitiveCookieNameExpr(cie.getArgument(0)) ) @@ -169,16 +183,6 @@ class MissingHttpOnlyConfiguration extends TaintTracking::Configuration { // Test class or method isTestMethod(node) } - - override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) { - 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 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 e2d05a9b24d8..5ccd2bb19f92 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,17 @@ edges -| SensitiveCookieNotHttpOnly.java:22:27:22:60 | new Cookie(...) : Cookie | SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie | +| SensitiveCookieNotHttpOnly.java:22:28:22:61 | 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(...) | +| SensitiveCookieNotHttpOnly.java:60:37:60:115 | new NewCookie(...) : NewCookie | SensitiveCookieNotHttpOnly.java:62:42:62:47 | keyStr | nodes -| SensitiveCookieNotHttpOnly.java:22:27:22:60 | new Cookie(...) : Cookie | semmle.label | new Cookie(...) : Cookie | +| SensitiveCookieNotHttpOnly.java:22:28:22:61 | 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:60:37:60:115 | new NewCookie(...) : NewCookie | semmle.label | new NewCookie(...) : NewCookie | +| SensitiveCookieNotHttpOnly.java:62:42:62:47 | keyStr | semmle.label | keyStr | #select -| 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:28:28:28:36 | jwtCookie | SensitiveCookieNotHttpOnly.java:22:28:22:61 | new Cookie(...) : Cookie | SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:22:28:22:61 | 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: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 | +| SensitiveCookieNotHttpOnly.java:62:42:62:47 | keyStr | SensitiveCookieNotHttpOnly.java:60:37:60:115 | new NewCookie(...) : NewCookie | SensitiveCookieNotHttpOnly.java:62:42:62:47 | keyStr | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:60:37:60:115 | new NewCookie(...) | 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 5e4f349f7c88..1d1e6986b44a 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 @@ -10,7 +10,7 @@ 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); + Cookie jwtCookie = new Cookie("jwt_token", jwt_token); jwtCookie.setPath("/"); jwtCookie.setMaxAge(3600*24*7); jwtCookie.setHttpOnly(true); @@ -19,8 +19,8 @@ public void addCookie(String jwt_token, HttpServletRequest request, HttpServletR // 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()); + Cookie jwtCookie = new Cookie("jwt_token", jwt_token); + Cookie userIdCookie = new Cookie("user_id", userId); jwtCookie.setPath("/"); userIdCookie.setPath("/"); jwtCookie.setMaxAge(3600*24*7); @@ -54,4 +54,11 @@ public void addCookie7(String accessKey, HttpServletRequest request, HttpServlet NewCookie accessKeyCookie = new NewCookie("session-access-key", accessKey, "/", null, null, 0, true, true); response.setHeader("Set-Cookie", accessKeyCookie.toString()); } + + // BAD - Tests set a sensitive cookie header using the class `javax.ws.rs.core.Cookie` without the `HttpOnly` flag set. + public void addCookie8(String accessKey, HttpServletRequest request, HttpServletResponse response) { + NewCookie accessKeyCookie = new NewCookie("session-access-key", accessKey, "/", null, 0, null, 86400, true); + String keyStr = accessKeyCookie.toString(); + response.setHeader("Set-Cookie", keyStr); + } } From a93aabab408442a96a9ae8e46718f94291b269b8 Mon Sep 17 00:00:00 2001 From: luchua-bc Date: Fri, 5 Mar 2021 03:05:49 +0000 Subject: [PATCH 032/433] Add the toString() method --- .../jsr311-api-1.1.1/javax/ws/rs/core/NewCookie.java | 11 +++++++++++ .../servlet-api-2.4/javax/servlet/http/Cookie.java | 11 +++++++++++ 2 files changed, 22 insertions(+) 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 7f2e3ec05351..26279d7fe0a9 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,4 +320,15 @@ 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; + } } \ 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 a93fec853e06..47b1d883e474 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 @@ -114,4 +114,15 @@ public void setHttpOnly(boolean isHttpOnly) { public boolean isHttpOnly() { return isHttpOnly; } + + /** + * 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; + } } From 7d556b354de8cec425caab93fcdd9db0a1ac5ffc Mon Sep 17 00:00:00 2001 From: Rasmus Lerchedahl Petersen Date: Fri, 5 Mar 2021 09:16:35 +0100 Subject: [PATCH 033/433] Python: Update test annotation and expectation --- .../test/query-tests/Security/CWE-327/InsecureProtocol.expected | 2 ++ python/ql/test/query-tests/Security/CWE-327/ssl_fluent.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) 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 afd9cc15d9f4..f4202a4634d5 100644 --- a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected +++ b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected @@ -38,3 +38,5 @@ | 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 | 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 ab65e3bd2068..577f342765ee 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 @@ -189,5 +189,5 @@ def test_fluent_explicitly_unsafe(): 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 + with context.wrap_socket(sock, server_hostname=hostname) as ssock: print(ssock.version()) From 31eaa80f5b881531bc66551e568fed1ffc380325 Mon Sep 17 00:00:00 2001 From: luchua-bc Date: Sat, 6 Mar 2021 00:56:15 +0000 Subject: [PATCH 034/433] Revamp the source --- .../CWE-1004/SensitiveCookieNotHttpOnly.ql | 24 ++++------ .../SensitiveCookieNotHttpOnly.expected | 44 +++++++++++++------ .../CWE-1004/SensitiveCookieNotHttpOnly.java | 9 +++- 3 files changed, 46 insertions(+), 31 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 f22e99e567c8..229a9d503253 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql @@ -60,24 +60,16 @@ class CookieInstanceExpr extends TaintPreservingCallable { override predicate returnsTaintFrom(int arg) { arg = -1 } } +/** The cookie constructor. */ +class CookieTaintPreservingConstructor extends Constructor, TaintPreservingCallable { + CookieTaintPreservingConstructor() { this.getDeclaringType() instanceof CookieClass } + + override predicate returnsTaintFrom(int arg) { arg = 0 } +} + /** Sensitive cookie name used in a `Cookie` constructor or a `Set-Cookie` call. */ class SensitiveCookieNameExpr extends Expr { - SensitiveCookieNameExpr() { - exists( - ClassInstanceExpr cie // new Cookie("jwt_token", token) - | - cie.getConstructedType() instanceof CookieClass and - this = cie and - isSensitiveCookieNameExpr(cie.getArgument(0)) - ) - or - exists( - SetCookieMethodAccess ma // response.addHeader("Set-Cookie: token=" +authId + ";HttpOnly;Secure") - | - this = ma.getArgument(1) and - isSensitiveCookieNameExpr(this) - ) - } + SensitiveCookieNameExpr() { isSensitiveCookieNameExpr(this) } } /** Sink of adding a cookie to the HTTP response. */ 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 5ccd2bb19f92..c9fe15d4082c 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,17 +1,33 @@ edges -| SensitiveCookieNotHttpOnly.java:22:28:22:61 | 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(...) | -| SensitiveCookieNotHttpOnly.java:60:37:60:115 | new NewCookie(...) : NewCookie | SensitiveCookieNotHttpOnly.java:62:42:62:47 | keyStr | +| SensitiveCookieNotHttpOnly.java:22:33:22:43 | "jwt_token" : String | SensitiveCookieNotHttpOnly.java:29:28:29:36 | jwtCookie | +| SensitiveCookieNotHttpOnly.java:40:42:40:49 | "token=" : String | SensitiveCookieNotHttpOnly.java:40:42:40:69 | ... + ... | +| SensitiveCookieNotHttpOnly.java:40:42:40:57 | ... + ... : String | SensitiveCookieNotHttpOnly.java:40:42:40:69 | ... + ... | +| SensitiveCookieNotHttpOnly.java:50:56:50:75 | "session-access-key" : String | SensitiveCookieNotHttpOnly.java:50:42:50:124 | toString(...) | +| SensitiveCookieNotHttpOnly.java:61:51:61:70 | "session-access-key" : String | SensitiveCookieNotHttpOnly.java:63:42:63:47 | keyStr | +| SensitiveCookieNotHttpOnly.java:68:28:68:35 | "token=" : String | SensitiveCookieNotHttpOnly.java:69:42:69:50 | secString | +| SensitiveCookieNotHttpOnly.java:68:28:68:43 | ... + ... : String | SensitiveCookieNotHttpOnly.java:69:42:69:50 | secString | +| SensitiveCookieNotHttpOnly.java:68:28:68:55 | ... + ... : String | SensitiveCookieNotHttpOnly.java:69:42:69:50 | secString | nodes -| SensitiveCookieNotHttpOnly.java:22:28:22:61 | 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:60:37:60:115 | new NewCookie(...) : NewCookie | semmle.label | new NewCookie(...) : NewCookie | -| SensitiveCookieNotHttpOnly.java:62:42:62:47 | keyStr | semmle.label | keyStr | +| SensitiveCookieNotHttpOnly.java:22:33:22:43 | "jwt_token" : String | semmle.label | "jwt_token" : String | +| SensitiveCookieNotHttpOnly.java:29:28:29:36 | jwtCookie | semmle.label | jwtCookie | +| SensitiveCookieNotHttpOnly.java:40:42:40:49 | "token=" : String | semmle.label | "token=" : String | +| SensitiveCookieNotHttpOnly.java:40:42:40:57 | ... + ... : String | semmle.label | ... + ... : String | +| SensitiveCookieNotHttpOnly.java:40:42:40:69 | ... + ... | semmle.label | ... + ... | +| SensitiveCookieNotHttpOnly.java:50:42:50:124 | toString(...) | semmle.label | toString(...) | +| SensitiveCookieNotHttpOnly.java:50:56:50:75 | "session-access-key" : String | semmle.label | "session-access-key" : String | +| SensitiveCookieNotHttpOnly.java:61:51:61:70 | "session-access-key" : String | semmle.label | "session-access-key" : String | +| SensitiveCookieNotHttpOnly.java:63:42:63:47 | keyStr | semmle.label | keyStr | +| SensitiveCookieNotHttpOnly.java:68:28:68:35 | "token=" : String | semmle.label | "token=" : String | +| SensitiveCookieNotHttpOnly.java:68:28:68:43 | ... + ... : String | semmle.label | ... + ... : String | +| SensitiveCookieNotHttpOnly.java:68:28:68:55 | ... + ... : String | semmle.label | ... + ... : String | +| SensitiveCookieNotHttpOnly.java:69:42:69:50 | secString | semmle.label | secString | #select -| SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie | SensitiveCookieNotHttpOnly.java:22:28:22:61 | new Cookie(...) : Cookie | SensitiveCookieNotHttpOnly.java:28:28:28:36 | jwtCookie | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:22:28:22:61 | 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: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 | -| SensitiveCookieNotHttpOnly.java:62:42:62:47 | keyStr | SensitiveCookieNotHttpOnly.java:60:37:60:115 | new NewCookie(...) : NewCookie | SensitiveCookieNotHttpOnly.java:62:42:62:47 | keyStr | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:60:37:60:115 | new NewCookie(...) | This sensitive cookie | +| SensitiveCookieNotHttpOnly.java:29:28:29:36 | jwtCookie | SensitiveCookieNotHttpOnly.java:22:33:22:43 | "jwt_token" : String | SensitiveCookieNotHttpOnly.java:29:28:29:36 | jwtCookie | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:22:33:22:43 | "jwt_token" | This sensitive cookie | +| SensitiveCookieNotHttpOnly.java:40:42:40:69 | ... + ... | SensitiveCookieNotHttpOnly.java:40:42:40:49 | "token=" : String | SensitiveCookieNotHttpOnly.java:40:42:40:69 | ... + ... | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:40:42:40:49 | "token=" | This sensitive cookie | +| SensitiveCookieNotHttpOnly.java:40:42:40:69 | ... + ... | SensitiveCookieNotHttpOnly.java:40:42:40:57 | ... + ... : String | SensitiveCookieNotHttpOnly.java:40:42:40:69 | ... + ... | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:40:42:40:57 | ... + ... | This sensitive cookie | +| SensitiveCookieNotHttpOnly.java:40:42:40:69 | ... + ... | SensitiveCookieNotHttpOnly.java:40:42:40:69 | ... + ... | SensitiveCookieNotHttpOnly.java:40:42:40:69 | ... + ... | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:40:42:40:69 | ... + ... | This sensitive cookie | +| SensitiveCookieNotHttpOnly.java:50:42:50:124 | toString(...) | SensitiveCookieNotHttpOnly.java:50:56:50:75 | "session-access-key" : String | SensitiveCookieNotHttpOnly.java:50:42:50:124 | toString(...) | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:50:56:50:75 | "session-access-key" | This sensitive cookie | +| SensitiveCookieNotHttpOnly.java:63:42:63:47 | keyStr | SensitiveCookieNotHttpOnly.java:61:51:61:70 | "session-access-key" : String | SensitiveCookieNotHttpOnly.java:63:42:63:47 | keyStr | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:61:51:61:70 | "session-access-key" | This sensitive cookie | +| SensitiveCookieNotHttpOnly.java:69:42:69:50 | secString | SensitiveCookieNotHttpOnly.java:68:28:68:35 | "token=" : String | SensitiveCookieNotHttpOnly.java:69:42:69:50 | secString | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:68:28:68:35 | "token=" | This sensitive cookie | +| SensitiveCookieNotHttpOnly.java:69:42:69:50 | secString | SensitiveCookieNotHttpOnly.java:68:28:68:43 | ... + ... : String | SensitiveCookieNotHttpOnly.java:69:42:69:50 | secString | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:68:28:68:43 | ... + ... | This sensitive cookie | +| SensitiveCookieNotHttpOnly.java:69:42:69:50 | secString | SensitiveCookieNotHttpOnly.java:68:28:68:55 | ... + ... : String | SensitiveCookieNotHttpOnly.java:69:42:69:50 | secString | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:68:28:68:55 | ... + ... | 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 1d1e6986b44a..6572577a6976 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 @@ -19,7 +19,8 @@ public void addCookie(String jwt_token, HttpServletRequest request, HttpServletR // 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); + String tokenCookieStr = "jwt_token"; + Cookie jwtCookie = new Cookie(tokenCookieStr, jwt_token); Cookie userIdCookie = new Cookie("user_id", userId); jwtCookie.setPath("/"); userIdCookie.setPath("/"); @@ -61,4 +62,10 @@ public void addCookie8(String accessKey, HttpServletRequest request, HttpServlet String keyStr = accessKeyCookie.toString(); response.setHeader("Set-Cookie", keyStr); } + + // BAD - Tests set a sensitive cookie header using a variable without the `HttpOnly` flag set. + public void addCookie9(String authId, HttpServletRequest request, HttpServletResponse response) { + String secString = "token=" +authId + ";Secure"; + response.addHeader("Set-Cookie", secString); + } } From 48975fa7d220b7debe5d0c8a0b84c8ec64351435 Mon Sep 17 00:00:00 2001 From: luchua-bc Date: Wed, 10 Mar 2021 00:17:26 +0000 Subject: [PATCH 035/433] Replace sanitizers --- .../CWE-1004/SensitiveCookieNotHttpOnly.ql | 105 +++++++----------- 1 file changed, 41 insertions(+), 64 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 229a9d503253..716975d0486b 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql @@ -50,23 +50,6 @@ class CookieClass extends RefType { } } -/** The method call `toString` to get a stringified cookie representation. */ -class CookieInstanceExpr extends TaintPreservingCallable { - CookieInstanceExpr() { - this.getDeclaringType() instanceof CookieClass and - this.hasName("toString") - } - - override predicate returnsTaintFrom(int arg) { arg = -1 } -} - -/** The cookie constructor. */ -class CookieTaintPreservingConstructor extends Constructor, TaintPreservingCallable { - CookieTaintPreservingConstructor() { this.getDeclaringType() instanceof CookieClass } - - override predicate returnsTaintFrom(int arg) { arg = 0 } -} - /** Sensitive cookie name used in a `Cookie` constructor or a `Set-Cookie` call. */ class SensitiveCookieNameExpr extends Expr { SensitiveCookieNameExpr() { isSensitiveCookieNameExpr(this) } @@ -78,55 +61,58 @@ class CookieResponseSink extends DataFlow::ExprNode { exists(MethodAccess ma | ( ma.getMethod() instanceof ResponseAddCookieMethod and - this.getExpr() = ma.getArgument(0) + this.getExpr() = ma.getArgument(0) and + not exists( + MethodAccess ma2 // cookie.setHttpOnly(true) + | + ma2.getMethod().getName() = "setHttpOnly" and + ma2.getArgument(0).(BooleanLiteral).getBooleanValue() = true and + DataFlow::localExprFlow(ma2.getQualifier(), this.getExpr()) + ) or ma instanceof SetCookieMethodAccess and - this.getExpr() = ma.getArgument(1) + this.getExpr() = ma.getArgument(1) and + not hasHttpOnlyExpr(this.getExpr()) // response.addHeader("Set-Cookie", "token=" +authId + ";HttpOnly;Secure") ) ) } } -/** - * 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) - | - 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() - ) +/** A JAX-RS `NewCookie` constructor that sets `HttpOnly` to true. */ +class HttpOnlyNewCookie extends ClassInstanceExpr { + HttpOnlyNewCookie() { + this.getConstructedType() + .hasQualifiedName(["javax.ws.rs.core", "jakarta.ws.rs.core"], "NewCookie") and + ( + this.getNumArgument() = 6 and this.getArgument(5).(BooleanLiteral).getBooleanValue() = true // NewCookie(Cookie cookie, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly) + or + this.getNumArgument() = 8 and + this.getArgument(6).getType() instanceof BooleanType and + this.getArgument(7).(BooleanLiteral).getBooleanValue() = true // NewCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly) + or + this.getNumArgument() = 10 and this.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 `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 - DataFlow::localExprFlow(node.asExpr(), sa.getArgument(1)) - ) +/** The cookie constructor. */ +class CookieTaintPreservingConstructor extends Constructor, TaintPreservingCallable { + CookieTaintPreservingConstructor() { + this.getDeclaringType() instanceof CookieClass and + not exists(HttpOnlyNewCookie hie | hie.getConstructor() = this) + } + + override predicate returnsTaintFrom(int arg) { arg = 0 } } -/** 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) - ) +/** The method call `toString` to get a stringified cookie representation. */ +class CookieInstanceExpr extends TaintPreservingCallable { + CookieInstanceExpr() { + this.getDeclaringType() instanceof CookieClass and + this.hasName("toString") + } + + override predicate returnsTaintFrom(int arg) { arg = -1 } } /** @@ -163,15 +149,6 @@ class MissingHttpOnlyConfiguration extends TaintTracking::Configuration { 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.asExpr()) - or // Test class or method isTestMethod(node) } From 72f28513eb6478024cb184f9affe71490c8217d3 Mon Sep 17 00:00:00 2001 From: luchua-bc Date: Wed, 10 Mar 2021 12:12:27 +0000 Subject: [PATCH 036/433] Move test check to the sink --- .../CWE-1004/SensitiveCookieNotHttpOnly.ql | 44 ++++++++----------- 1 file changed, 19 insertions(+), 25 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 716975d0486b..eee4d8c89d17 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql @@ -73,34 +73,29 @@ class CookieResponseSink extends DataFlow::ExprNode { ma instanceof SetCookieMethodAccess and this.getExpr() = ma.getArgument(1) and not hasHttpOnlyExpr(this.getExpr()) // response.addHeader("Set-Cookie", "token=" +authId + ";HttpOnly;Secure") - ) + ) and + not isTestMethod(ma) // Test class or method ) } } -/** A JAX-RS `NewCookie` constructor that sets `HttpOnly` to true. */ -class HttpOnlyNewCookie extends ClassInstanceExpr { - HttpOnlyNewCookie() { - this.getConstructedType() - .hasQualifiedName(["javax.ws.rs.core", "jakarta.ws.rs.core"], "NewCookie") and - ( - this.getNumArgument() = 6 and this.getArgument(5).(BooleanLiteral).getBooleanValue() = true // NewCookie(Cookie cookie, String comment, int maxAge, Date expiry, boolean secure, boolean httpOnly) - or - this.getNumArgument() = 8 and - this.getArgument(6).getType() instanceof BooleanType and - this.getArgument(7).(BooleanLiteral).getBooleanValue() = true // NewCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly) - or - this.getNumArgument() = 10 and this.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) + ) } /** The cookie constructor. */ class CookieTaintPreservingConstructor extends Constructor, TaintPreservingCallable { - CookieTaintPreservingConstructor() { - this.getDeclaringType() instanceof CookieClass and - not exists(HttpOnlyNewCookie hie | hie.getConstructor() = this) - } + CookieTaintPreservingConstructor() { this.getDeclaringType() instanceof CookieClass } override predicate returnsTaintFrom(int arg) { arg = 0 } } @@ -122,9 +117,8 @@ class CookieInstanceExpr extends TaintPreservingCallable { * 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 +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 @@ -149,8 +143,8 @@ class MissingHttpOnlyConfiguration extends TaintTracking::Configuration { override predicate isSink(DataFlow::Node sink) { sink instanceof CookieResponseSink } override predicate isSanitizer(DataFlow::Node node) { - // Test class or method - isTestMethod(node) + // new NewCookie("session-access-key", accessKey, "/", null, null, 0, true, true) + setHttpOnlyInNewCookie(node.asExpr()) } } From f0ddfc9283607f01603a93e6a05357aa27a579ce Mon Sep 17 00:00:00 2001 From: luchua-bc Date: Wed, 10 Mar 2021 12:18:55 +0000 Subject: [PATCH 037/433] Minor qldoc changes --- .../Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql | 7 +++++-- 1 file changed, 5 insertions(+), 2 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 eee4d8c89d17..c3f9349dbc8b 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql @@ -79,7 +79,10 @@ class CookieResponseSink extends DataFlow::ExprNode { } } -/** Holds if `cie` is an invocation of a JAX-RS `NewCookie` constructor that sets `HttpOnly` to true. */ +/** + * Holds if `ClassInstanceExpr` 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 ( @@ -111,7 +114,7 @@ class CookieInstanceExpr extends TaintPreservingCallable { } /** - * Holds if the node is a test method indicated by: + * 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` From a0a1ddee86382c183b774e9cb88509a1b4aac1d9 Mon Sep 17 00:00:00 2001 From: luchua-bc Date: Wed, 10 Mar 2021 17:07:31 +0000 Subject: [PATCH 038/433] Update class name --- .../Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql | 4 ++-- 1 file changed, 2 insertions(+), 2 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 c3f9349dbc8b..985c546b5552 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql @@ -104,8 +104,8 @@ class CookieTaintPreservingConstructor extends Constructor, TaintPreservingCalla } /** The method call `toString` to get a stringified cookie representation. */ -class CookieInstanceExpr extends TaintPreservingCallable { - CookieInstanceExpr() { +class CookieToString extends TaintPreservingCallable { + CookieToString() { this.getDeclaringType() instanceof CookieClass and this.hasName("toString") } From eeac7e322ad0d450f8dac1f0dad9195e4d3b8c33 Mon Sep 17 00:00:00 2001 From: luchua-bc Date: Thu, 11 Mar 2021 13:46:32 +0000 Subject: [PATCH 039/433] Query to detect insecure configuration of Spring Boot Actuator --- .../InsecureSpringActuatorConfig.qhelp | 47 ++++++++ .../CWE-016/InsecureSpringActuatorConfig.ql | 112 ++++++++++++++++++ .../CWE/CWE-016/application.properties | 22 ++++ .../Security/CWE/CWE-016/pom_bad.xml | 50 ++++++++ .../Security/CWE/CWE-016/pom_good.xml | 50 ++++++++ .../InsecureSpringActuatorConfig.expected | 1 + .../InsecureSpringActuatorConfig.qlref | 1 + .../security/CWE-016/SensitiveInfo.java | 13 ++ .../security/CWE-016/application.properties | 14 +++ .../query-tests/security/CWE-016/pom.xml | 47 ++++++++ 10 files changed, 357 insertions(+) create mode 100644 java/ql/src/experimental/Security/CWE/CWE-016/InsecureSpringActuatorConfig.qhelp create mode 100644 java/ql/src/experimental/Security/CWE/CWE-016/InsecureSpringActuatorConfig.ql create mode 100644 java/ql/src/experimental/Security/CWE/CWE-016/application.properties create mode 100644 java/ql/src/experimental/Security/CWE/CWE-016/pom_bad.xml create mode 100644 java/ql/src/experimental/Security/CWE/CWE-016/pom_good.xml create mode 100644 java/ql/test/experimental/query-tests/security/CWE-016/InsecureSpringActuatorConfig.expected create mode 100644 java/ql/test/experimental/query-tests/security/CWE-016/InsecureSpringActuatorConfig.qlref create mode 100644 java/ql/test/experimental/query-tests/security/CWE-016/SensitiveInfo.java create mode 100644 java/ql/test/experimental/query-tests/security/CWE-016/application.properties create mode 100644 java/ql/test/experimental/query-tests/security/CWE-016/pom.xml diff --git a/java/ql/src/experimental/Security/CWE/CWE-016/InsecureSpringActuatorConfig.qhelp b/java/ql/src/experimental/Security/CWE/CWE-016/InsecureSpringActuatorConfig.qhelp new file mode 100644 index 000000000000..e201156728a4 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-016/InsecureSpringActuatorConfig.qhelp @@ -0,0 +1,47 @@ + + + +

    Spring Boot is a popular framework that facilitates the development of stand-alone applications +and micro services. Spring Boot Actuator helps to expose production-ready support features against +Spring Boot applications.

    + +

    Endpoints of Spring Boot Actuator allow to monitor and interact with a Spring Boot application. +Exposing unprotected actuator endpoints through configuration files can lead to information disclosure +or even remote code execution vulnerability.

    + +

    Rather than programmatically permitting endpoint requests or enforcing access control, frequently +developers simply leave management endpoints publicly accessible in the application configuration file +application.properties without enforcing access control through Spring Security.

    +
    + + +

    Declare the Spring Boot Starter Security module in XML configuration or programmatically enforce +security checks on management endpoints using Spring Security. Otherwise accessing management endpoints +on a different HTTP port other than the port that the web application is listening on also helps to +improve the security.

    +
    + + +

    The following examples show both 'BAD' and 'GOOD' configurations. In the 'BAD' configuration, +no security module is declared and sensitive management endpoints are exposed. In the 'GOOD' configuration, +security is enforced and only endpoints requiring exposure are exposed.

    + + + +
    + + +
  • + Spring Boot documentation: + Spring Boot Actuator: Production-ready Features +
  • +
  • + VERACODE Blog: + Exploiting Spring Boot Actuators +
  • +
  • + HackerOne Report: + Spring Actuator endpoints publicly available, leading to account takeover +
  • +
    +
    diff --git a/java/ql/src/experimental/Security/CWE/CWE-016/InsecureSpringActuatorConfig.ql b/java/ql/src/experimental/Security/CWE/CWE-016/InsecureSpringActuatorConfig.ql new file mode 100644 index 000000000000..2dc11e8e38ef --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-016/InsecureSpringActuatorConfig.ql @@ -0,0 +1,112 @@ +/** + * @name Insecure Spring Boot Actuator Configuration + * @description Exposed Spring Boot Actuator through configuration files without declarative or procedural security enforcement leads to information leak or even remote code execution. + * @kind problem + * @id java/insecure-spring-actuator-config + * @tags security + * external/cwe-016 + */ + +import java +import semmle.code.configfiles.ConfigFiles +import semmle.code.java.security.SensitiveActions +import semmle.code.xml.MavenPom + +/** The parent node of the `org.springframework.boot` group. */ +class SpringBootParent extends Parent { + SpringBootParent() { this.getGroup().getValue() = "org.springframework.boot" } +} + +/** Class of Spring Boot dependencies. */ +class SpringBootPom extends Pom { + SpringBootPom() { this.getParentElement() instanceof SpringBootParent } + + /** Holds if the Spring Boot Actuator module `spring-boot-starter-actuator` is used in the project. */ + predicate isSpringBootActuatorUsed() { + this.getADependency().getArtifact().getValue() = "spring-boot-starter-actuator" + } + + /** Holds if the Spring Boot Security module is used in the project, which brings in other security related libraries. */ + predicate isSpringBootSecurityUsed() { + this.getADependency().getArtifact().getValue() = "spring-boot-starter-security" + } +} + +/** The properties file `application.properties`. */ +class ApplicationProperties extends ConfigPair { + ApplicationProperties() { this.getFile().getBaseName() = "application.properties" } +} + +/** The configuration property `management.security.enabled`. */ +class ManagementSecurityEnabled extends ApplicationProperties { + ManagementSecurityEnabled() { this.getNameElement().getName() = "management.security.enabled" } + + string getManagementSecurityEnabled() { result = this.getValueElement().getValue() } + + predicate hasSecurityDisabled() { getManagementSecurityEnabled() = "false" } + + predicate hasSecurityEnabled() { getManagementSecurityEnabled() = "true" } +} + +/** The configuration property `management.endpoints.web.exposure.include`. */ +class ManagementEndPointInclude extends ApplicationProperties { + ManagementEndPointInclude() { + this.getNameElement().getName() = "management.endpoints.web.exposure.include" + } + + string getManagementEndPointInclude() { result = this.getValueElement().getValue().trim() } +} + +/** The configuration property `management.endpoints.web.exposure.exclude`. */ +class ManagementEndPointExclude extends ApplicationProperties { + ManagementEndPointExclude() { + this.getNameElement().getName() = "management.endpoints.web.exposure.exclude" + } + + string getManagementEndPointExclude() { result = this.getValueElement().getValue().trim() } +} + +/** Holds if an application handles sensitive information judging by its variable names. */ +predicate isProtectedApp() { + exists(VarAccess va | va.getVariable().getName().regexpMatch(getCommonSensitiveInfoRegex())) +} + +from SpringBootPom pom, ApplicationProperties ap, Dependency d +where + isProtectedApp() and + pom.isSpringBootActuatorUsed() and + not pom.isSpringBootSecurityUsed() and + ap.getFile() + .getParentContainer() + .getAbsolutePath() + .matches(pom.getFile().getParentContainer().getAbsolutePath() + "%") and // in the same sub-directory + exists(string s | s = pom.getParentElement().getVersionString() | + s.regexpMatch("1\\.[0|1|2|3|4].*") and + not exists(ManagementSecurityEnabled me | + me.hasSecurityEnabled() and me.getFile() = ap.getFile() + ) + or + s.regexpMatch("1\\.5.*") and + exists(ManagementSecurityEnabled me | me.hasSecurityDisabled() and me.getFile() = ap.getFile()) + or + s.regexpMatch("2.*") and + exists(ManagementEndPointInclude mi | + mi.getFile() = ap.getFile() and + ( + mi.getManagementEndPointInclude() = "*" // all endpoints are enabled + or + mi.getManagementEndPointInclude() + .matches([ + "%dump%", "%trace%", "%logfile%", "%shutdown%", "%startup%", "%mappings%", "%env%", + "%beans%", "%sessions%" + ]) // all endpoints apart from '/health' and '/info' are considered sensitive + ) and + not exists(ManagementEndPointExclude mx | + mx.getFile() = ap.getFile() and + mx.getManagementEndPointExclude() = mi.getManagementEndPointInclude() + ) + ) + ) and + d = pom.getADependency() and + d.getArtifact().getValue() = "spring-boot-starter-actuator" +select d, "Insecure configuration of Spring Boot Actuator exposes sensitive endpoints." diff --git a/java/ql/src/experimental/Security/CWE/CWE-016/application.properties b/java/ql/src/experimental/Security/CWE/CWE-016/application.properties new file mode 100644 index 000000000000..aa489435a12b --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-016/application.properties @@ -0,0 +1,22 @@ +#management.endpoints.web.base-path=/admin + + +#### BAD: All management endpoints are accessible #### +# vulnerable configuration (spring boot 1.0 - 1.4): exposes actuators by default + +# vulnerable configuration (spring boot 1.5+): requires value false to expose sensitive actuators +management.security.enabled=false + +# vulnerable configuration (spring boot 2+): exposes health and info only by default +management.endpoints.web.exposure.include=* + + +#### GOOD: All management endpoints have access control #### +# safe configuration (spring boot 1.0 - 1.4): exposes actuators by default +management.security.enabled=true + +# safe configuration (spring boot 1.5+): requires value false to expose sensitive actuators +management.security.enabled=true + +# safe configuration (spring boot 2+): exposes health and info only by default +management.endpoints.web.exposure.include=beans,info,health diff --git a/java/ql/src/experimental/Security/CWE/CWE-016/pom_bad.xml b/java/ql/src/experimental/Security/CWE/CWE-016/pom_bad.xml new file mode 100644 index 000000000000..9dd5c9c188b4 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-016/pom_bad.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + spring-boot-actuator-app + spring-boot-actuator-app + 1.0-SNAPSHOT + + + UTF-8 + 1.8 + 1.8 + + + + org.springframework.boot + spring-boot-starter-parent + 2.3.8.RELEASE + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-devtools + + + + + + + org.springframework.boot + spring-boot-test + + + + \ No newline at end of file diff --git a/java/ql/src/experimental/Security/CWE/CWE-016/pom_good.xml b/java/ql/src/experimental/Security/CWE/CWE-016/pom_good.xml new file mode 100644 index 000000000000..89f577f21e59 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-016/pom_good.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + spring-boot-actuator-app + spring-boot-actuator-app + 1.0-SNAPSHOT + + + UTF-8 + 1.8 + 1.8 + + + + org.springframework.boot + spring-boot-starter-parent + 2.3.8.RELEASE + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-devtools + + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-test + + + + \ No newline at end of file diff --git a/java/ql/test/experimental/query-tests/security/CWE-016/InsecureSpringActuatorConfig.expected b/java/ql/test/experimental/query-tests/security/CWE-016/InsecureSpringActuatorConfig.expected new file mode 100644 index 000000000000..486302939857 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-016/InsecureSpringActuatorConfig.expected @@ -0,0 +1 @@ +| pom.xml:29:9:32:22 | dependency | Insecure configuration of Spring Boot Actuator exposes sensitive endpoints. | diff --git a/java/ql/test/experimental/query-tests/security/CWE-016/InsecureSpringActuatorConfig.qlref b/java/ql/test/experimental/query-tests/security/CWE-016/InsecureSpringActuatorConfig.qlref new file mode 100644 index 000000000000..9cd12d5e4fb1 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-016/InsecureSpringActuatorConfig.qlref @@ -0,0 +1 @@ +experimental/Security/CWE/CWE-016/InsecureSpringActuatorConfig.ql \ No newline at end of file diff --git a/java/ql/test/experimental/query-tests/security/CWE-016/SensitiveInfo.java b/java/ql/test/experimental/query-tests/security/CWE-016/SensitiveInfo.java new file mode 100644 index 000000000000..a3ff69c1b817 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-016/SensitiveInfo.java @@ -0,0 +1,13 @@ +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +public class SensitiveInfo { + @RequestMapping + public void handleLogin(@RequestParam String username, @RequestParam String password) throws Exception { + if (!username.equals("") && password.equals("")) { + //Blank processing + } + } +} \ No newline at end of file diff --git a/java/ql/test/experimental/query-tests/security/CWE-016/application.properties b/java/ql/test/experimental/query-tests/security/CWE-016/application.properties new file mode 100644 index 000000000000..95e704f3a1a5 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-016/application.properties @@ -0,0 +1,14 @@ +#management.endpoints.web.base-path=/admin + +# vulnerable configuration (spring boot 1.0 - 1.4): exposes actuators by default + +# vulnerable configuration (spring boot 1.5+): requires value false to expose sensitive actuators +management.security.enabled=false + +# vulnerable configuration (spring boot 2+): exposes health and info only by default +management.endpoints.web.exposure.include=* +management.endpoints.web.exposure.exclude=beans + +management.endpoint.shutdown.enabled=true + +management.endpoint.health.show-details=when_authorized \ No newline at end of file diff --git a/java/ql/test/experimental/query-tests/security/CWE-016/pom.xml b/java/ql/test/experimental/query-tests/security/CWE-016/pom.xml new file mode 100644 index 000000000000..a9d5fa920c84 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-016/pom.xml @@ -0,0 +1,47 @@ + + + 4.0.0 + + spring-boot-actuator-app + spring-boot-actuator-app + 1.0-SNAPSHOT + + + UTF-8 + 1.8 + 1.8 + + + + org.springframework.boot + spring-boot-starter-parent + 2.3.8.RELEASE + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-devtools + + + + org.springframework.boot + spring-boot-test + + + + \ No newline at end of file From 0a35feef766249b88abfc78f8a9dd552664ab083 Mon Sep 17 00:00:00 2001 From: luchua-bc Date: Thu, 11 Mar 2021 17:28:07 +0000 Subject: [PATCH 040/433] Exclude CSRF cookies to reduce FPs --- .../CWE-1004/SensitiveCookieNotHttpOnly.ql | 16 +++-- .../SensitiveCookieNotHttpOnly.expected | 60 +++++++++---------- .../CWE-1004/SensitiveCookieNotHttpOnly.java | 22 ++++++- .../query-tests/security/CWE-1004/options | 2 +- .../security/web/csrf/CsrfToken.java | 50 ++++++++++++++++ 5 files changed, 113 insertions(+), 37 deletions(-) create mode 100644 java/ql/test/stubs/springframework-5.2.3/org/springframework/security/web/csrf/CsrfToken.java 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 985c546b5552..693dad68082c 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql @@ -16,12 +16,18 @@ 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. */ +/** Gets a regular expression for matching CSRF cookies. */ +string getCsrfCookieNameRegex() { result = "(?i).*(csrf).*" } + +/** + * Holds if a string is concatenated with the name of a sensitive cookie. Excludes CSRF cookies since + * they are special cookies implementing the Synchronizer Token Pattern that can be used in JavaScript. + */ predicate isSensitiveCookieNameExpr(Expr expr) { - expr.(CompileTimeConstantExpr) - .getStringValue() - .toLowerCase() - .regexpMatch(getSensitiveCookieNameRegex()) or + exists(string s | s = expr.(CompileTimeConstantExpr).getStringValue().toLowerCase() | + s.regexpMatch(getSensitiveCookieNameRegex()) and not s.regexpMatch(getCsrfCookieNameRegex()) + ) + or isSensitiveCookieNameExpr(expr.(AddExpr).getAnOperand()) } 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 c9fe15d4082c..8fa688bef2a6 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,33 +1,33 @@ edges -| SensitiveCookieNotHttpOnly.java:22:33:22:43 | "jwt_token" : String | SensitiveCookieNotHttpOnly.java:29:28:29:36 | jwtCookie | -| SensitiveCookieNotHttpOnly.java:40:42:40:49 | "token=" : String | SensitiveCookieNotHttpOnly.java:40:42:40:69 | ... + ... | -| SensitiveCookieNotHttpOnly.java:40:42:40:57 | ... + ... : String | SensitiveCookieNotHttpOnly.java:40:42:40:69 | ... + ... | -| SensitiveCookieNotHttpOnly.java:50:56:50:75 | "session-access-key" : String | SensitiveCookieNotHttpOnly.java:50:42:50:124 | toString(...) | -| SensitiveCookieNotHttpOnly.java:61:51:61:70 | "session-access-key" : String | SensitiveCookieNotHttpOnly.java:63:42:63:47 | keyStr | -| SensitiveCookieNotHttpOnly.java:68:28:68:35 | "token=" : String | SensitiveCookieNotHttpOnly.java:69:42:69:50 | secString | -| SensitiveCookieNotHttpOnly.java:68:28:68:43 | ... + ... : String | SensitiveCookieNotHttpOnly.java:69:42:69:50 | secString | -| SensitiveCookieNotHttpOnly.java:68:28:68:55 | ... + ... : String | SensitiveCookieNotHttpOnly.java:69:42:69:50 | secString | +| SensitiveCookieNotHttpOnly.java:24:33:24:43 | "jwt_token" : String | SensitiveCookieNotHttpOnly.java:31:28:31:36 | jwtCookie | +| SensitiveCookieNotHttpOnly.java:42:42:42:49 | "token=" : String | SensitiveCookieNotHttpOnly.java:42:42:42:69 | ... + ... | +| SensitiveCookieNotHttpOnly.java:42:42:42:57 | ... + ... : String | SensitiveCookieNotHttpOnly.java:42:42:42:69 | ... + ... | +| SensitiveCookieNotHttpOnly.java:52:56:52:75 | "session-access-key" : String | SensitiveCookieNotHttpOnly.java:52:42:52:124 | toString(...) | +| SensitiveCookieNotHttpOnly.java:63:51:63:70 | "session-access-key" : String | SensitiveCookieNotHttpOnly.java:65:42:65:47 | keyStr | +| 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 | nodes -| SensitiveCookieNotHttpOnly.java:22:33:22:43 | "jwt_token" : String | semmle.label | "jwt_token" : String | -| SensitiveCookieNotHttpOnly.java:29:28:29:36 | jwtCookie | semmle.label | jwtCookie | -| SensitiveCookieNotHttpOnly.java:40:42:40:49 | "token=" : String | semmle.label | "token=" : String | -| SensitiveCookieNotHttpOnly.java:40:42:40:57 | ... + ... : String | semmle.label | ... + ... : String | -| SensitiveCookieNotHttpOnly.java:40:42:40:69 | ... + ... | semmle.label | ... + ... | -| SensitiveCookieNotHttpOnly.java:50:42:50:124 | toString(...) | semmle.label | toString(...) | -| SensitiveCookieNotHttpOnly.java:50:56:50:75 | "session-access-key" : String | semmle.label | "session-access-key" : String | -| SensitiveCookieNotHttpOnly.java:61:51:61:70 | "session-access-key" : String | semmle.label | "session-access-key" : String | -| SensitiveCookieNotHttpOnly.java:63:42:63:47 | keyStr | semmle.label | keyStr | -| SensitiveCookieNotHttpOnly.java:68:28:68:35 | "token=" : String | semmle.label | "token=" : String | -| SensitiveCookieNotHttpOnly.java:68:28:68:43 | ... + ... : String | semmle.label | ... + ... : String | -| SensitiveCookieNotHttpOnly.java:68:28:68:55 | ... + ... : String | semmle.label | ... + ... : String | -| SensitiveCookieNotHttpOnly.java:69:42:69:50 | secString | semmle.label | secString | +| SensitiveCookieNotHttpOnly.java:24:33:24:43 | "jwt_token" : String | semmle.label | "jwt_token" : String | +| SensitiveCookieNotHttpOnly.java:31:28:31:36 | jwtCookie | semmle.label | jwtCookie | +| SensitiveCookieNotHttpOnly.java:42:42:42:49 | "token=" : String | semmle.label | "token=" : String | +| SensitiveCookieNotHttpOnly.java:42:42:42:57 | ... + ... : String | semmle.label | ... + ... : String | +| SensitiveCookieNotHttpOnly.java:42:42:42:69 | ... + ... | semmle.label | ... + ... | +| SensitiveCookieNotHttpOnly.java:52:42:52:124 | toString(...) | semmle.label | toString(...) | +| SensitiveCookieNotHttpOnly.java:52:56:52:75 | "session-access-key" : String | semmle.label | "session-access-key" : String | +| SensitiveCookieNotHttpOnly.java:63:51:63:70 | "session-access-key" : String | semmle.label | "session-access-key" : String | +| SensitiveCookieNotHttpOnly.java:65:42:65:47 | keyStr | semmle.label | keyStr | +| SensitiveCookieNotHttpOnly.java:70:28:70:35 | "token=" : String | semmle.label | "token=" : String | +| 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 | #select -| SensitiveCookieNotHttpOnly.java:29:28:29:36 | jwtCookie | SensitiveCookieNotHttpOnly.java:22:33:22:43 | "jwt_token" : String | SensitiveCookieNotHttpOnly.java:29:28:29:36 | jwtCookie | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:22:33:22:43 | "jwt_token" | This sensitive cookie | -| SensitiveCookieNotHttpOnly.java:40:42:40:69 | ... + ... | SensitiveCookieNotHttpOnly.java:40:42:40:49 | "token=" : String | SensitiveCookieNotHttpOnly.java:40:42:40:69 | ... + ... | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:40:42:40:49 | "token=" | This sensitive cookie | -| SensitiveCookieNotHttpOnly.java:40:42:40:69 | ... + ... | SensitiveCookieNotHttpOnly.java:40:42:40:57 | ... + ... : String | SensitiveCookieNotHttpOnly.java:40:42:40:69 | ... + ... | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:40:42:40:57 | ... + ... | This sensitive cookie | -| SensitiveCookieNotHttpOnly.java:40:42:40:69 | ... + ... | SensitiveCookieNotHttpOnly.java:40:42:40:69 | ... + ... | SensitiveCookieNotHttpOnly.java:40:42:40:69 | ... + ... | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:40:42:40:69 | ... + ... | This sensitive cookie | -| SensitiveCookieNotHttpOnly.java:50:42:50:124 | toString(...) | SensitiveCookieNotHttpOnly.java:50:56:50:75 | "session-access-key" : String | SensitiveCookieNotHttpOnly.java:50:42:50:124 | toString(...) | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:50:56:50:75 | "session-access-key" | This sensitive cookie | -| SensitiveCookieNotHttpOnly.java:63:42:63:47 | keyStr | SensitiveCookieNotHttpOnly.java:61:51:61:70 | "session-access-key" : String | SensitiveCookieNotHttpOnly.java:63:42:63:47 | keyStr | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:61:51:61:70 | "session-access-key" | This sensitive cookie | -| SensitiveCookieNotHttpOnly.java:69:42:69:50 | secString | SensitiveCookieNotHttpOnly.java:68:28:68:35 | "token=" : String | SensitiveCookieNotHttpOnly.java:69:42:69:50 | secString | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:68:28:68:35 | "token=" | This sensitive cookie | -| SensitiveCookieNotHttpOnly.java:69:42:69:50 | secString | SensitiveCookieNotHttpOnly.java:68:28:68:43 | ... + ... : String | SensitiveCookieNotHttpOnly.java:69:42:69:50 | secString | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:68:28:68:43 | ... + ... | This sensitive cookie | -| SensitiveCookieNotHttpOnly.java:69:42:69:50 | secString | SensitiveCookieNotHttpOnly.java:68:28:68:55 | ... + ... : String | SensitiveCookieNotHttpOnly.java:69:42:69:50 | secString | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:68:28:68:55 | ... + ... | This sensitive cookie | +| 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 | +| SensitiveCookieNotHttpOnly.java:42:42:42:69 | ... + ... | SensitiveCookieNotHttpOnly.java:42:42:42:57 | ... + ... : String | SensitiveCookieNotHttpOnly.java:42:42:42:69 | ... + ... | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:42:42:42:57 | ... + ... | This sensitive cookie | +| SensitiveCookieNotHttpOnly.java:42:42:42:69 | ... + ... | SensitiveCookieNotHttpOnly.java:42:42:42:69 | ... + ... | SensitiveCookieNotHttpOnly.java:42:42:42:69 | ... + ... | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:42:42:42:69 | ... + ... | This sensitive cookie | +| SensitiveCookieNotHttpOnly.java:52:42:52:124 | toString(...) | SensitiveCookieNotHttpOnly.java:52:56:52:75 | "session-access-key" : String | SensitiveCookieNotHttpOnly.java:52:42:52:124 | toString(...) | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:52:56:52:75 | "session-access-key" | This sensitive cookie | +| SensitiveCookieNotHttpOnly.java:65:42:65:47 | keyStr | SensitiveCookieNotHttpOnly.java:63:51:63:70 | "session-access-key" : String | SensitiveCookieNotHttpOnly.java:65:42:65:47 | keyStr | $@ doesn't have the HttpOnly flag set. | SensitiveCookieNotHttpOnly.java:63:51:63:70 | "session-access-key" | This sensitive cookie | +| 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 | 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 6572577a6976..337a99cc096d 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 @@ -7,6 +7,8 @@ import javax.ws.rs.core.NewCookie; +import org.springframework.security.web.csrf.CsrfToken; + class SensitiveCookieNotHttpOnly { // GOOD - Tests adding a sensitive cookie with the `HttpOnly` flag set. public void addCookie(String jwt_token, HttpServletRequest request, HttpServletResponse response) { @@ -67,5 +69,23 @@ public void addCookie8(String accessKey, HttpServletRequest request, HttpServlet public void addCookie9(String authId, HttpServletRequest request, HttpServletResponse response) { String secString = "token=" +authId + ";Secure"; response.addHeader("Set-Cookie", secString); - } + } + + // 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" + CsrfToken csrfToken = (CsrfToken) request.getAttribute("_csrf"); + + // Send the cookie only if the token has changed + String actualToken = request.getHeader("X-CSRF-TOKEN"); + if (actualToken == null || !actualToken.equals(csrfToken.getToken())) { + // Session cookie that can be used by AngularJS + String pCookieName = "CSRF-TOKEN"; + Cookie cookie = new Cookie(pCookieName, csrfToken.getToken()); + cookie.setMaxAge(-1); + cookie.setHttpOnly(false); + cookie.setPath("/"); + response.addCookie(cookie); + } + } } diff --git a/java/ql/test/experimental/query-tests/security/CWE-1004/options b/java/ql/test/experimental/query-tests/security/CWE-1004/options index 7f2b253fb20d..d61a358d97f0 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-1004/options +++ b/java/ql/test/experimental/query-tests/security/CWE-1004/options @@ -1 +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 +// semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../../stubs/jsr311-api-1.1.1:${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/security/web/csrf/CsrfToken.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/security/web/csrf/CsrfToken.java new file mode 100644 index 000000000000..bc59a2e496a5 --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/security/web/csrf/CsrfToken.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.csrf; + +import java.io.Serializable; + +/** + * Provides the information about an expected CSRF token. + * + * @author Rob Winch + * @since 3.2 + * @see DefaultCsrfToken + */ +public interface CsrfToken extends Serializable { + + /** + * Gets the HTTP header that the CSRF is populated on the response and can be placed + * on requests instead of the parameter. Cannot be null. + * @return the HTTP header that the CSRF is populated on the response and can be + * placed on requests instead of the parameter + */ + String getHeaderName(); + + /** + * Gets the HTTP parameter name that should contain the token. Cannot be null. + * @return the HTTP parameter name that should contain the token. + */ + String getParameterName(); + + /** + * Gets the token value. Cannot be null. + * @return the token value + */ + String getToken(); + +} From c8b1bc3a89b6f0c6b006449f38f4966899f0bc3d Mon Sep 17 00:00:00 2001 From: luchua-bc Date: Thu, 11 Mar 2021 21:41:34 +0000 Subject: [PATCH 041/433] Enhance the query --- .../CWE-016/InsecureSpringActuatorConfig.ql | 58 +++++++------------ .../CWE/CWE-016/application.properties | 4 +- .../security/CWE-016/application.properties | 2 +- 3 files changed, 24 insertions(+), 40 deletions(-) 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 2dc11e8e38ef..06ba0d8a288e 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-016/InsecureSpringActuatorConfig.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-016/InsecureSpringActuatorConfig.ql @@ -1,6 +1,7 @@ /** * @name Insecure Spring Boot Actuator Configuration - * @description Exposed Spring Boot Actuator through configuration files without declarative or procedural security enforcement leads to information leak or even remote code execution. + * @description Exposed Spring Boot Actuator through configuration files without declarative or procedural + * security enforcement leads to information leak or even remote code execution. * @kind problem * @id java/insecure-spring-actuator-config * @tags security @@ -9,7 +10,6 @@ import java import semmle.code.configfiles.ConfigFiles -import semmle.code.java.security.SensitiveActions import semmle.code.xml.MavenPom /** The parent node of the `org.springframework.boot` group. */ @@ -26,7 +26,10 @@ class SpringBootPom extends Pom { this.getADependency().getArtifact().getValue() = "spring-boot-starter-actuator" } - /** Holds if the Spring Boot Security module is used in the project, which brings in other security related libraries. */ + /** + * Holds if the Spring Boot Security module is used in the project, which brings in other security + * related libraries. + */ predicate isSpringBootSecurityUsed() { this.getADependency().getArtifact().getValue() = "spring-boot-starter-security" } @@ -38,14 +41,14 @@ class ApplicationProperties extends ConfigPair { } /** The configuration property `management.security.enabled`. */ -class ManagementSecurityEnabled extends ApplicationProperties { - ManagementSecurityEnabled() { this.getNameElement().getName() = "management.security.enabled" } +class ManagementSecurityConfig extends ApplicationProperties { + ManagementSecurityConfig() { this.getNameElement().getName() = "management.security.enabled" } - string getManagementSecurityEnabled() { result = this.getValueElement().getValue() } + string getValue() { result = this.getValueElement().getValue().trim() } - predicate hasSecurityDisabled() { getManagementSecurityEnabled() = "false" } + predicate hasSecurityDisabled() { getValue() = "false" } - predicate hasSecurityEnabled() { getManagementSecurityEnabled() = "true" } + predicate hasSecurityEnabled() { getValue() = "true" } } /** The configuration property `management.endpoints.web.exposure.include`. */ @@ -54,56 +57,37 @@ class ManagementEndPointInclude extends ApplicationProperties { this.getNameElement().getName() = "management.endpoints.web.exposure.include" } - string getManagementEndPointInclude() { result = this.getValueElement().getValue().trim() } -} - -/** The configuration property `management.endpoints.web.exposure.exclude`. */ -class ManagementEndPointExclude extends ApplicationProperties { - ManagementEndPointExclude() { - this.getNameElement().getName() = "management.endpoints.web.exposure.exclude" - } - - string getManagementEndPointExclude() { result = this.getValueElement().getValue().trim() } -} - -/** Holds if an application handles sensitive information judging by its variable names. */ -predicate isProtectedApp() { - exists(VarAccess va | va.getVariable().getName().regexpMatch(getCommonSensitiveInfoRegex())) + string getValue() { result = this.getValueElement().getValue().trim() } } from SpringBootPom pom, ApplicationProperties ap, Dependency d where - isProtectedApp() and pom.isSpringBootActuatorUsed() and not pom.isSpringBootSecurityUsed() and ap.getFile() .getParentContainer() .getAbsolutePath() .matches(pom.getFile().getParentContainer().getAbsolutePath() + "%") and // in the same sub-directory - exists(string s | s = pom.getParentElement().getVersionString() | - s.regexpMatch("1\\.[0|1|2|3|4].*") and - not exists(ManagementSecurityEnabled me | + exists(string springBootVersion | springBootVersion = pom.getParentElement().getVersionString() | + springBootVersion.regexpMatch("1\\.[0-4].*") and // version 1.0, 1.1, ..., 1.4 + not exists(ManagementSecurityConfig me | me.hasSecurityEnabled() and me.getFile() = ap.getFile() ) or - s.regexpMatch("1\\.5.*") and - exists(ManagementSecurityEnabled me | me.hasSecurityDisabled() and me.getFile() = ap.getFile()) + springBootVersion.matches("1.5%") and // version 1.5 + exists(ManagementSecurityConfig me | me.hasSecurityDisabled() and me.getFile() = ap.getFile()) or - s.regexpMatch("2.*") and + springBootVersion.matches("2.%") and //version 2.x exists(ManagementEndPointInclude mi | mi.getFile() = ap.getFile() and ( - mi.getManagementEndPointInclude() = "*" // all endpoints are enabled + mi.getValue() = "*" // all endpoints are enabled or - mi.getManagementEndPointInclude() + mi.getValue() .matches([ "%dump%", "%trace%", "%logfile%", "%shutdown%", "%startup%", "%mappings%", "%env%", "%beans%", "%sessions%" - ]) // all endpoints apart from '/health' and '/info' are considered sensitive - ) and - not exists(ManagementEndPointExclude mx | - mx.getFile() = ap.getFile() and - mx.getManagementEndPointExclude() = mi.getManagementEndPointInclude() + ]) // confidential endpoints to check although all endpoints apart from '/health' and '/info' are considered sensitive by Spring ) ) ) and diff --git a/java/ql/src/experimental/Security/CWE/CWE-016/application.properties b/java/ql/src/experimental/Security/CWE/CWE-016/application.properties index aa489435a12b..4f5defdd948e 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-016/application.properties +++ b/java/ql/src/experimental/Security/CWE/CWE-016/application.properties @@ -7,7 +7,7 @@ # vulnerable configuration (spring boot 1.5+): requires value false to expose sensitive actuators management.security.enabled=false -# vulnerable configuration (spring boot 2+): exposes health and info only by default +# vulnerable configuration (spring boot 2+): exposes health and info only by default, here overridden to expose everything management.endpoints.web.exposure.include=* @@ -18,5 +18,5 @@ management.security.enabled=true # safe configuration (spring boot 1.5+): requires value false to expose sensitive actuators management.security.enabled=true -# safe configuration (spring boot 2+): exposes health and info only by default +# safe configuration (spring boot 2+): exposes health and info only by default, here overridden to expose one additional endpoint which we assume is intentional and safe. management.endpoints.web.exposure.include=beans,info,health diff --git a/java/ql/test/experimental/query-tests/security/CWE-016/application.properties b/java/ql/test/experimental/query-tests/security/CWE-016/application.properties index 95e704f3a1a5..797906a3ca3b 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-016/application.properties +++ b/java/ql/test/experimental/query-tests/security/CWE-016/application.properties @@ -5,7 +5,7 @@ # vulnerable configuration (spring boot 1.5+): requires value false to expose sensitive actuators management.security.enabled=false -# vulnerable configuration (spring boot 2+): exposes health and info only by default +# vulnerable configuration (spring boot 2+): exposes health and info only by default, here overridden to expose everything management.endpoints.web.exposure.include=* management.endpoints.web.exposure.exclude=beans From 1a2e341b7c767fc4b6e21e9dc38d25110aa8b50e Mon Sep 17 00:00:00 2001 From: luchua-bc Date: Fri, 12 Mar 2021 12:19:37 +0000 Subject: [PATCH 042/433] Refactor the business logic of the query into a separate predicate --- .../CWE/CWE-016/InsecureSpringActuatorConfig.ql | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) 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 06ba0d8a288e..3acd22e767a3 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-016/InsecureSpringActuatorConfig.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-016/InsecureSpringActuatorConfig.ql @@ -60,8 +60,11 @@ class ManagementEndPointInclude extends ApplicationProperties { string getValue() { result = this.getValueElement().getValue().trim() } } -from SpringBootPom pom, ApplicationProperties ap, Dependency d -where +/** + * Holds if `ApplicationProperties` ap of a repository managed by `SpringBootPom` pom + * has a vulnerable configuration of Spring Boot Actuator management endpoints. + */ +predicate hasConfidentialEndPointExposed(SpringBootPom pom, ApplicationProperties ap) { pom.isSpringBootActuatorUsed() and not pom.isSpringBootSecurityUsed() and ap.getFile() @@ -90,7 +93,12 @@ where ]) // confidential endpoints to check although all endpoints apart from '/health' and '/info' are considered sensitive by Spring ) ) - ) and + ) +} + +from SpringBootPom pom, ApplicationProperties ap, Dependency d +where + hasConfidentialEndPointExposed(pom, ap) and d = pom.getADependency() and d.getArtifact().getValue() = "spring-boot-starter-actuator" select d, "Insecure configuration of Spring Boot Actuator exposes sensitive endpoints." From 41c9394b4bd12063e133222310a9facac689b330 Mon Sep 17 00:00:00 2001 From: Rasmus Lerchedahl Petersen Date: Sun, 14 Mar 2021 09:22:47 +0100 Subject: [PATCH 043/433] Python: update qhelp and example --- .../Security/CWE-327/InsecureProtocol.qhelp | 21 +++++++++++++++++++ .../examples/secure_default_protocol.py | 16 ++++++-------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/python/ql/src/Security/CWE-327/InsecureProtocol.qhelp b/python/ql/src/Security/CWE-327/InsecureProtocol.qhelp index 4a7364ca14e2..cfcebd0930d3 100644 --- a/python/ql/src/Security/CWE-327/InsecureProtocol.qhelp +++ b/python/ql/src/Security/CWE-327/InsecureProtocol.qhelp @@ -38,12 +38,33 @@ 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: +

    +
      +
    • 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 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 &>) = 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) = ^IndirectReadSideEffect[-1] : &:r0_11, ~m? +# 1084| mu1084_20(iterator) = Store[(__end)] : &:r1084_14, r1084_18 #-----| Goto -> Block 6 # 1084| Block 6 @@ -6209,7 +6208,7 @@ ir.cpp: # 1084| r1084_24(iterator) = Load[(__end)] : &:r1084_23, ~m? # 1084| r1084_25(bool) = Call[operator!=] : func:r1084_22, this:r0_13, 0:r1084_24 # 1084| mu1084_26(unknown) = ^CallSideEffect : ~m? -#-----| v0_14(void) = ^BufferReadSideEffect[-1] : &:r0_13, ~m? +#-----| v0_14(void) = ^IndirectReadSideEffect[-1] : &:r0_13, ~m? # 1084| v1084_27(void) = ConditionalBranch : r1084_25 #-----| False -> Block 10 #-----| True -> Block 8 @@ -6219,29 +6218,29 @@ ir.cpp: # 1084| r1084_29(glval) = FunctionAddress[operator++] : # 1084| r1084_30(iterator &) = Call[operator++] : func:r1084_29, this:r1084_28 # 1084| mu1084_31(unknown) = ^CallSideEffect : ~m? -# 1084| v1084_32(void) = ^BufferReadSideEffect[-1] : &:r1084_28, ~m? +# 1084| v1084_32(void) = ^IndirectReadSideEffect[-1] : &:r1084_28, ~m? # 1084| mu1084_33(iterator) = ^IndirectMayWriteSideEffect[-1] : &:r1084_28 # 1084| r1084_34(glval) = CopyValue : r1084_30 #-----| Goto (back edge) -> Block 6 # 1084| Block 8 -# 1084| r1084_35(glval) = VariableAddress[e] : -# 1084| r1084_36(glval) = VariableAddress[(__begin)] : -#-----| r0_15(glval) = Convert : r1084_36 -# 1084| r1084_37(glval) = FunctionAddress[operator*] : -# 1084| r1084_38(int &) = Call[operator*] : func:r1084_37, this:r0_15 -# 1084| mu1084_39(unknown) = ^CallSideEffect : ~m? -#-----| v0_16(void) = ^BufferReadSideEffect[-1] : &:r0_15, ~m? -# 1084| r1084_40(glval) = CopyValue : r1084_38 -# 1084| r1084_41(glval) = Convert : r1084_40 -# 1084| r1084_42(int &) = CopyValue : r1084_41 -# 1084| mu1084_43(int &) = Store[e] : &:r1084_35, r1084_42 -# 1085| r1085_1(glval) = VariableAddress[e] : -# 1085| r1085_2(int &) = Load[e] : &:r1085_1, ~m? -# 1085| r1085_3(int) = Load[?] : &:r1085_2, ~m? -# 1085| r1085_4(int) = Constant[5] : -# 1085| r1085_5(bool) = CompareLT : r1085_3, r1085_4 -# 1085| v1085_6(void) = ConditionalBranch : r1085_5 +# 1084| r1084_35(glval) = VariableAddress[e] : +# 1084| r1084_36(glval) = VariableAddress[(__begin)] : +#-----| r0_15(glval) = Convert : r1084_36 +# 1084| r1084_37(glval) = FunctionAddress[operator*] : +# 1084| r1084_38(int &) = Call[operator*] : func:r1084_37, this:r0_15 +# 1084| mu1084_39(unknown) = ^CallSideEffect : ~m? +#-----| v0_16(void) = ^IndirectReadSideEffect[-1] : &:r0_15, ~m? +# 1084| r1084_40(glval) = CopyValue : r1084_38 +# 1084| r1084_41(glval) = Convert : r1084_40 +# 1084| r1084_42(int &) = CopyValue : r1084_41 +# 1084| mu1084_43(int &) = Store[e] : &:r1084_35, r1084_42 +# 1085| r1085_1(glval) = VariableAddress[e] : +# 1085| r1085_2(int &) = Load[e] : &:r1085_1, ~m? +# 1085| r1085_3(int) = Load[?] : &:r1085_2, ~m? +# 1085| r1085_4(int) = Constant[5] : +# 1085| r1085_5(bool) = CompareLT : r1085_3, r1085_4 +# 1085| v1085_6(void) = ConditionalBranch : r1085_5 #-----| False -> Block 7 #-----| True -> Block 9 @@ -7525,7 +7524,7 @@ ir.cpp: # 1373| r1373_8(glval) = FunctionAddress[c_str] : # 1373| r1373_9(char *) = Call[c_str] : func:r1373_8, this:r1373_7 # 1373| mu1373_10(unknown) = ^CallSideEffect : ~m? -# 1373| v1373_11(void) = ^BufferReadSideEffect[-1] : &:r1373_7, ~m? +# 1373| v1373_11(void) = ^IndirectReadSideEffect[-1] : &:r1373_7, ~m? # 1374| r1374_1(glval) = VariableAddress[#temp1374:5] : # 1374| r1374_2(glval) = FunctionAddress[returnValue] : # 1374| r1374_3(String) = Call[returnValue] : func:r1374_2 @@ -7535,7 +7534,7 @@ ir.cpp: # 1374| r1374_7(glval) = FunctionAddress[c_str] : # 1374| r1374_8(char *) = Call[c_str] : func:r1374_7, this:r1374_6 # 1374| mu1374_9(unknown) = ^CallSideEffect : ~m? -# 1374| v1374_10(void) = ^BufferReadSideEffect[-1] : &:r1374_6, ~m? +# 1374| v1374_10(void) = ^IndirectReadSideEffect[-1] : &:r1374_6, ~m? # 1376| r1376_1(glval) = VariableAddress[#temp1376:5] : # 1376| r1376_2(glval) = FunctionAddress[defaultConstruct] : # 1376| r1376_3(String) = Call[defaultConstruct] : func:r1376_2 @@ -7589,7 +7588,7 @@ ir.cpp: # 1385| r1385_4(glval) = FunctionAddress[method] : # 1385| v1385_5(void) = Call[method] : func:r1385_4, this:r1385_1 # 1385| mu1385_6(unknown) = ^CallSideEffect : ~m? -# 1385| v1385_7(void) = ^BufferReadSideEffect[-1] : &:r1385_1, ~m? +# 1385| v1385_7(void) = ^IndirectReadSideEffect[-1] : &:r1385_1, ~m? # 1385| mu1385_8(destructor_only) = ^IndirectMayWriteSideEffect[-1] : &:r1385_1 # 1386| r1386_1(glval) = VariableAddress[#temp1386:5] : # 1386| r1386_2(glval) = FunctionAddress[returnValue] : @@ -7599,7 +7598,7 @@ ir.cpp: # 1386| r1386_6(glval) = FunctionAddress[method] : # 1386| v1386_7(void) = Call[method] : func:r1386_6, this:r1386_1 # 1386| mu1386_8(unknown) = ^CallSideEffect : ~m? -# 1386| v1386_9(void) = ^BufferReadSideEffect[-1] : &:r1386_1, ~m? +# 1386| v1386_9(void) = ^IndirectReadSideEffect[-1] : &:r1386_1, ~m? # 1386| mu1386_10(destructor_only) = ^IndirectMayWriteSideEffect[-1] : &:r1386_1 # 1388| r1388_1(glval) = VariableAddress[#temp1388:5] : # 1388| r1388_2(glval) = FunctionAddress[defaultConstruct] : @@ -7667,7 +7666,7 @@ ir.cpp: # 1397| r1397_7(glval) = FunctionAddress[method] : # 1397| v1397_8(void) = Call[method] : func:r1397_7, this:r1397_1 # 1397| mu1397_9(unknown) = ^CallSideEffect : ~m? -# 1397| v1397_10(void) = ^BufferReadSideEffect[-1] : &:r1397_1, ~m? +# 1397| v1397_10(void) = ^IndirectReadSideEffect[-1] : &:r1397_1, ~m? # 1397| mu1397_11(copy_constructor) = ^IndirectMayWriteSideEffect[-1] : &:r1397_1 # 1398| r1398_1(glval) = VariableAddress[#temp1398:5] : # 1398| r1398_2(glval) = FunctionAddress[returnValue] : @@ -7677,7 +7676,7 @@ ir.cpp: # 1398| r1398_6(glval) = FunctionAddress[method] : # 1398| v1398_7(void) = Call[method] : func:r1398_6, this:r1398_1 # 1398| mu1398_8(unknown) = ^CallSideEffect : ~m? -# 1398| v1398_9(void) = ^BufferReadSideEffect[-1] : &:r1398_1, ~m? +# 1398| v1398_9(void) = ^IndirectReadSideEffect[-1] : &:r1398_1, ~m? # 1398| mu1398_10(copy_constructor) = ^IndirectMayWriteSideEffect[-1] : &:r1398_1 # 1399| r1399_1(glval) = VariableAddress[#temp1399:5] : # 1399| r1399_2(glval) = FunctionAddress[defaultConstruct] : @@ -7851,7 +7850,7 @@ ir.cpp: # 1447| r1447_9(glval) = FunctionAddress[f] : # 1447| r1447_10(float) = Call[f] : func:r1447_9, this:r1447_8 # 1447| mu1447_11(unknown) = ^CallSideEffect : ~m? -# 1447| v1447_12(void) = ^BufferReadSideEffect[-1] : &:r1447_8, ~m? +# 1447| v1447_12(void) = ^IndirectReadSideEffect[-1] : &:r1447_8, ~m? # 1447| mu1447_13(float) = Store[f] : &:r1447_1, r1447_10 # 1448| v1448_1(void) = NoOp : # 1443| v1443_4(void) = ReturnVoid : diff --git a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected index c4019a38251e..76de50dd792b 100644 --- a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected +++ b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected @@ -1059,7 +1059,7 @@ ssa.cpp: # 241| v241_3(void) = Call[g] : func:r241_2, this:r241_1 # 241| m241_4(unknown) = ^CallSideEffect : ~m240_7 # 241| m241_5(unknown) = Chi : total:m240_7, partial:m241_4 -# 241| v241_6(void) = ^BufferReadSideEffect[-1] : &:r241_1, ~m240_9 +# 241| v241_6(void) = ^IndirectReadSideEffect[-1] : &:r241_1, m240_9 # 241| m241_7(Constructible) = ^IndirectMayWriteSideEffect[-1] : &:r241_1 # 241| m241_8(Constructible) = Chi : total:m240_9, partial:m241_7 # 242| r242_1(glval) = VariableAddress[c] : @@ -1067,7 +1067,7 @@ ssa.cpp: # 242| v242_3(void) = Call[g] : func:r242_2, this:r242_1 # 242| m242_4(unknown) = ^CallSideEffect : ~m241_5 # 242| m242_5(unknown) = Chi : total:m241_5, partial:m242_4 -# 242| v242_6(void) = ^BufferReadSideEffect[-1] : &:r242_1, ~m241_8 +# 242| v242_6(void) = ^IndirectReadSideEffect[-1] : &:r242_1, m241_8 # 242| m242_7(Constructible) = ^IndirectMayWriteSideEffect[-1] : &:r242_1 # 242| m242_8(Constructible) = Chi : total:m241_8, partial:m242_7 # 243| r243_1(glval) = VariableAddress[c2] : @@ -1084,7 +1084,7 @@ ssa.cpp: # 244| v244_3(void) = Call[g] : func:r244_2, this:r244_1 # 244| m244_4(unknown) = ^CallSideEffect : ~m243_7 # 244| m244_5(unknown) = Chi : total:m243_7, partial:m244_4 -# 244| v244_6(void) = ^BufferReadSideEffect[-1] : &:r244_1, ~m243_9 +# 244| v244_6(void) = ^IndirectReadSideEffect[-1] : &:r244_1, m243_9 # 244| m244_7(Constructible) = ^IndirectMayWriteSideEffect[-1] : &:r244_1 # 244| m244_8(Constructible) = Chi : total:m243_9, partial:m244_7 # 245| v245_1(void) = NoOp : diff --git a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected index a101e638cd08..ab79d20214a8 100644 --- a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected +++ b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected @@ -1054,7 +1054,7 @@ ssa.cpp: # 241| v241_3(void) = Call[g] : func:r241_2, this:r241_1 # 241| m241_4(unknown) = ^CallSideEffect : ~m240_7 # 241| m241_5(unknown) = Chi : total:m240_7, partial:m241_4 -# 241| v241_6(void) = ^BufferReadSideEffect[-1] : &:r241_1, ~m240_9 +# 241| v241_6(void) = ^IndirectReadSideEffect[-1] : &:r241_1, m240_9 # 241| m241_7(Constructible) = ^IndirectMayWriteSideEffect[-1] : &:r241_1 # 241| m241_8(Constructible) = Chi : total:m240_9, partial:m241_7 # 242| r242_1(glval) = VariableAddress[c] : @@ -1062,7 +1062,7 @@ ssa.cpp: # 242| v242_3(void) = Call[g] : func:r242_2, this:r242_1 # 242| m242_4(unknown) = ^CallSideEffect : ~m241_5 # 242| m242_5(unknown) = Chi : total:m241_5, partial:m242_4 -# 242| v242_6(void) = ^BufferReadSideEffect[-1] : &:r242_1, ~m241_8 +# 242| v242_6(void) = ^IndirectReadSideEffect[-1] : &:r242_1, m241_8 # 242| m242_7(Constructible) = ^IndirectMayWriteSideEffect[-1] : &:r242_1 # 242| m242_8(Constructible) = Chi : total:m241_8, partial:m242_7 # 243| r243_1(glval) = VariableAddress[c2] : @@ -1079,7 +1079,7 @@ ssa.cpp: # 244| v244_3(void) = Call[g] : func:r244_2, this:r244_1 # 244| m244_4(unknown) = ^CallSideEffect : ~m243_7 # 244| m244_5(unknown) = Chi : total:m243_7, partial:m244_4 -# 244| v244_6(void) = ^BufferReadSideEffect[-1] : &:r244_1, ~m243_9 +# 244| v244_6(void) = ^IndirectReadSideEffect[-1] : &:r244_1, m243_9 # 244| m244_7(Constructible) = ^IndirectMayWriteSideEffect[-1] : &:r244_1 # 244| m244_8(Constructible) = Chi : total:m243_9, partial:m244_7 # 245| v245_1(void) = NoOp : diff --git a/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir.expected b/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir.expected index 8b6cf17dbbbb..0b257db98a43 100644 --- a/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir.expected +++ b/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir.expected @@ -983,13 +983,13 @@ ssa.cpp: # 241| r241_2(glval) = FunctionAddress[g] : # 241| v241_3(void) = Call[g] : func:r241_2, this:r241_1 # 241| mu241_4(unknown) = ^CallSideEffect : ~m? -# 241| v241_5(void) = ^BufferReadSideEffect[-1] : &:r241_1, ~m? +# 241| v241_5(void) = ^IndirectReadSideEffect[-1] : &:r241_1, ~m? # 241| mu241_6(Constructible) = ^IndirectMayWriteSideEffect[-1] : &:r241_1 # 242| r242_1(glval) = VariableAddress[c] : # 242| r242_2(glval) = FunctionAddress[g] : # 242| v242_3(void) = Call[g] : func:r242_2, this:r242_1 # 242| mu242_4(unknown) = ^CallSideEffect : ~m? -# 242| v242_5(void) = ^BufferReadSideEffect[-1] : &:r242_1, ~m? +# 242| v242_5(void) = ^IndirectReadSideEffect[-1] : &:r242_1, ~m? # 242| mu242_6(Constructible) = ^IndirectMayWriteSideEffect[-1] : &:r242_1 # 243| r243_1(glval) = VariableAddress[c2] : # 243| mu243_2(Constructible) = Uninitialized[c2] : &:r243_1 @@ -1002,7 +1002,7 @@ ssa.cpp: # 244| r244_2(glval) = FunctionAddress[g] : # 244| v244_3(void) = Call[g] : func:r244_2, this:r244_1 # 244| mu244_4(unknown) = ^CallSideEffect : ~m? -# 244| v244_5(void) = ^BufferReadSideEffect[-1] : &:r244_1, ~m? +# 244| v244_5(void) = ^IndirectReadSideEffect[-1] : &:r244_1, ~m? # 244| mu244_6(Constructible) = ^IndirectMayWriteSideEffect[-1] : &:r244_1 # 245| v245_1(void) = NoOp : # 239| v239_4(void) = ReturnVoid : diff --git a/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir_unsound.expected b/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir_unsound.expected index 8b6cf17dbbbb..0b257db98a43 100644 --- a/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir_unsound.expected +++ b/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir_unsound.expected @@ -983,13 +983,13 @@ ssa.cpp: # 241| r241_2(glval) = FunctionAddress[g] : # 241| v241_3(void) = Call[g] : func:r241_2, this:r241_1 # 241| mu241_4(unknown) = ^CallSideEffect : ~m? -# 241| v241_5(void) = ^BufferReadSideEffect[-1] : &:r241_1, ~m? +# 241| v241_5(void) = ^IndirectReadSideEffect[-1] : &:r241_1, ~m? # 241| mu241_6(Constructible) = ^IndirectMayWriteSideEffect[-1] : &:r241_1 # 242| r242_1(glval) = VariableAddress[c] : # 242| r242_2(glval) = FunctionAddress[g] : # 242| v242_3(void) = Call[g] : func:r242_2, this:r242_1 # 242| mu242_4(unknown) = ^CallSideEffect : ~m? -# 242| v242_5(void) = ^BufferReadSideEffect[-1] : &:r242_1, ~m? +# 242| v242_5(void) = ^IndirectReadSideEffect[-1] : &:r242_1, ~m? # 242| mu242_6(Constructible) = ^IndirectMayWriteSideEffect[-1] : &:r242_1 # 243| r243_1(glval) = VariableAddress[c2] : # 243| mu243_2(Constructible) = Uninitialized[c2] : &:r243_1 @@ -1002,7 +1002,7 @@ ssa.cpp: # 244| r244_2(glval) = FunctionAddress[g] : # 244| v244_3(void) = Call[g] : func:r244_2, this:r244_1 # 244| mu244_4(unknown) = ^CallSideEffect : ~m? -# 244| v244_5(void) = ^BufferReadSideEffect[-1] : &:r244_1, ~m? +# 244| v244_5(void) = ^IndirectReadSideEffect[-1] : &:r244_1, ~m? # 244| mu244_6(Constructible) = ^IndirectMayWriteSideEffect[-1] : &:r244_1 # 245| v245_1(void) = NoOp : # 239| v239_4(void) = ReturnVoid : From b8c11503f0fcc66c07169a0d9cb413726af347ff Mon Sep 17 00:00:00 2001 From: haby0 Date: Sat, 10 Apr 2021 04:21:49 +0800 Subject: [PATCH 161/433] Update java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp Co-authored-by: Chris Smowton --- .../src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 2712f30a3f2b..f49100089028 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp @@ -4,7 +4,7 @@

    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.

    +When there is a cross-domain problem, this could lead to information leakage.

    From ebd38eaf3bd0d5f4cb15bf4724fb2bb90ad1f8a6 Mon Sep 17 00:00:00 2001 From: haby0 Date: Sat, 10 Apr 2021 04:22:08 +0800 Subject: [PATCH 162/433] Update java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp Co-authored-by: Chris Smowton --- .../src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f49100089028..e3d61a0342e3 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, this could lead to information leakage.

    -

    The following examples show the bad case and the good case respectively. Bad case, such as bad1 to bad8, +

    The following examples show the bad case and the good case respectively. Bad cases, 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.

    From d8165145c71e1c21c8e274fca2d807ad34b0001d Mon Sep 17 00:00:00 2001 From: haby0 Date: Sat, 10 Apr 2021 04:22:44 +0800 Subject: [PATCH 163/433] Update java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp Co-authored-by: Chris Smowton --- .../src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e3d61a0342e3..f8c01cf5a328 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp @@ -15,7 +15,7 @@ When there is a cross-domain problem, this could lead to information leakage.

    The following examples show the bad case and the good case respectively. Bad cases, 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 +will cause information leakage 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.

    From 1510048f7afb17ae4e63b38c7e83c2ef05c9aa11 Mon Sep 17 00:00:00 2001 From: haby0 Date: Sat, 10 Apr 2021 04:23:13 +0800 Subject: [PATCH 164/433] Update java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp Co-authored-by: Chris Smowton --- .../src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f8c01cf5a328..10c6aa056adb 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp @@ -16,7 +16,7 @@ When there is a cross-domain problem, this could lead to information leakage.

    The following examples show the bad case and the good case respectively. Bad cases, such as bad1 to bad8, will cause information leakage 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 +method and the good2 method, using the verifToken method to do random token verification solve the problem of information leakage caused by cross-domain.

    From 79c1374925f72b59ac9ad6f41ad2ad0b759c0eb5 Mon Sep 17 00:00:00 2001 From: haby0 Date: Sat, 10 Apr 2021 04:24:49 +0800 Subject: [PATCH 165/433] Update java/ql/src/semmle/code/java/frameworks/Servlets.qll Co-authored-by: Chris Smowton --- java/ql/src/semmle/code/java/frameworks/Servlets.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/src/semmle/code/java/frameworks/Servlets.qll b/java/ql/src/semmle/code/java/frameworks/Servlets.qll index 21f55d396853..a99df9816f15 100644 --- a/java/ql/src/semmle/code/java/frameworks/Servlets.qll +++ b/java/ql/src/semmle/code/java/frameworks/Servlets.qll @@ -354,7 +354,7 @@ 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.getName() ="doFilter" and m.getDeclaringType() instanceof FilterClass and m.getNumberOfParameters() = 3 and m.getParameter(0).getType() instanceof ServletRequest and From 157e4670fd5ec92f9f86b2fc23eae3eda103375d Mon Sep 17 00:00:00 2001 From: haby0 Date: Sat, 10 Apr 2021 04:25:11 +0800 Subject: [PATCH 166/433] Update java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp Co-authored-by: Chris Smowton --- .../src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 10c6aa056adb..2a3f0861cdf9 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp @@ -17,7 +17,7 @@ When there is a cross-domain problem, this could lead to information leakage.

    The following examples show the bad case and the good case respectively. Bad cases, such as bad1 to bad8, will cause information leakage when there are cross-domain problems. In a good case, for example, in the good1 method and the good2 method, using the verifToken method to do random token verification -solve the problem of information leakage caused by cross-domain.

    +solves the problem of information leakage even in the presence of cross-domain access issues.

    From 837f20108ddabb25c4c04f45c1c1324b456d2ce0 Mon Sep 17 00:00:00 2001 From: haby0 Date: Sat, 10 Apr 2021 04:25:43 +0800 Subject: [PATCH 167/433] Update java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll Co-authored-by: Chris Smowton --- .../src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 bf90926f72f0..507a93454d78 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll @@ -7,7 +7,7 @@ import semmle.code.java.dataflow.DataFlow3 import semmle.code.java.dataflow.FlowSources import semmle.code.java.frameworks.spring.SpringController -/** A data flow configuration is tracing flow from the access to the authentication method of token/auth/referer/origin to if condition. */ +/** A data flow configuration tracing flow from the result of a method whose name includes token/auth/referer/origin to an if-statement condition. */ class VerificationMethodToIfFlowConfig extends DataFlow3::Configuration { VerificationMethodToIfFlowConfig() { this = "VerificationMethodToIfFlowConfig" } From c77c7b0a982bfe6eb88911bc7ac5cfb26b58d245 Mon Sep 17 00:00:00 2001 From: haby0 Date: Sat, 10 Apr 2021 04:27:16 +0800 Subject: [PATCH 168/433] Update java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll Co-authored-by: Chris Smowton --- .../Security/CWE/CWE-352/JsonpInjectionLib.qll | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 507a93454d78..f30a31f41337 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll @@ -27,7 +27,12 @@ class VerificationMethodToIfFlowConfig extends DataFlow3::Configuration { } } -/** Taint-tracking configuration tracing flow from untrusted inputs to verification of remote user input. */ +/** Taint-tracking configuration tracing flow from untrusted inputs to an argument of a function whose result is used as an if-statement condition. +* +* For example, in the context `String userControlled = request.getHeader("xyz"); boolean isGood = checkToken(userControlled); if(isGood) { ...`, +* the flow from `checkToken`'s result to the condition of `if(isGood)` matches the configuration `VerificationMethodToIfFlowConfig` above, +* and so the flow from `getHeader(...)` to the argument to `checkToken` matches this configuration. + */ class VerificationMethodFlowConfig extends TaintTracking2::Configuration { VerificationMethodFlowConfig() { this = "VerificationMethodFlowConfig" } From 760231c0046b559279eefe79c60de6e0092a96ba Mon Sep 17 00:00:00 2001 From: haby0 Date: Sat, 10 Apr 2021 04:28:17 +0800 Subject: [PATCH 169/433] Update java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll Co-authored-by: Chris Smowton --- .../experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll | 3 +++ 1 file changed, 3 insertions(+) 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 f30a31f41337..c63473cafa86 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll @@ -60,6 +60,9 @@ Callable getACallingCallableOrSelf(Callable call) { result = getACallingCallableOrSelf(call.getAReference().getEnclosingCallable()) } +/** + * A method that is called to handle an HTTP GET request. + */ abstract class RequestGetMethod extends Method { } /** Override method of `doGet` of `Servlet` subclass. */ From 9635a36044677ba4b3849f61480a16934b99bf76 Mon Sep 17 00:00:00 2001 From: haby0 Date: Sat, 10 Apr 2021 04:29:06 +0800 Subject: [PATCH 170/433] Update java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql Co-authored-by: Chris Smowton --- .../src/experimental/Security/CWE/CWE-352/JsonpInjection.ql | 5 ++++- 1 file changed, 4 insertions(+), 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 6e333d839930..9e210dd0e7ba 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql @@ -16,7 +16,10 @@ import semmle.code.java.dataflow.FlowSources import semmle.code.java.deadcode.WebEntryPoints import DataFlow::PathGraph -/** Determine whether there is a verification method for the remote streaming source data flow path method. */ +/** + * Holds if some `Filter.doFilter` method exists in the whole program that takes some user-controlled + * input and tests it with what appears to be a token- or authentication-checking function. + */ predicate existsFilterVerificationMethod() { exists(DataFlow::Node source, DataFlow::Node sink, VerificationMethodFlowConfig vmfc, Method m | vmfc.hasFlow(source, sink) and From 4c21980d4f34ab44bb684ff23c8c113028b355f0 Mon Sep 17 00:00:00 2001 From: haby0 Date: Sat, 10 Apr 2021 04:29:30 +0800 Subject: [PATCH 171/433] Update java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll Co-authored-by: Chris Smowton --- .../src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 c63473cafa86..3ba559a81c06 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll @@ -141,7 +141,7 @@ class JsonDataFlowConfig extends DataFlow2::Configuration { } } -/** Taint-tracking configuration tracing flow from user-controllable function name jsonp data to output jsonp data. */ +/** Taint-tracking configuration tracing flow from probable jsonp data with a user-controlled function name to an outgoing HTTP entity. */ class JsonpInjectionFlowConfig extends TaintTracking::Configuration { JsonpInjectionFlowConfig() { this = "JsonpInjectionFlowConfig" } From 8a7d28a2ed8f235a82b20803da94f4f6828f26e5 Mon Sep 17 00:00:00 2001 From: haby0 Date: Sat, 10 Apr 2021 04:29:49 +0800 Subject: [PATCH 172/433] Update java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll Co-authored-by: Chris Smowton --- .../src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3ba559a81c06..5e9b447f51c7 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll @@ -130,7 +130,7 @@ class RemoteFlowConfig extends DataFlow2::Configuration { } } -/** A data flow configuration tracing flow from json data to splicing jsonp data. */ +/** A data flow configuration tracing flow from json data into the argument `json` of JSONP-like string `someFunctionName + "(" + json + ")"`. */ class JsonDataFlowConfig extends DataFlow2::Configuration { JsonDataFlowConfig() { this = "JsonDataFlowConfig" } From 8687c5c14533ac35859cedca383217c65bce5539 Mon Sep 17 00:00:00 2001 From: porcupineyhairs <61983466+porcupineyhairs@users.noreply.github.com> Date: Sat, 10 Apr 2021 04:18:35 +0530 Subject: [PATCH 173/433] Apply suggestions from code review Co-authored-by: Chris Smowton --- .../Security/CWE/CWE-094/InsecureDexLoading.qhelp | 12 ++++++------ .../Security/CWE/CWE-094/InsecureDexLoading.ql | 8 ++++---- .../Security/CWE/CWE-094/InsecureDexLoading.qll | 4 ++-- .../Security/CWE/CWE-094/InsecureDexLoadingBad.java | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.qhelp b/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.qhelp index feda3af3fc26..4a5555c1390c 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.qhelp @@ -2,29 +2,29 @@

    -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 +It is dangerous to load Dex libraries from shared world-writable storage spaces. 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. + Loading a file from private storage instead of a world-writable one can prevent this issue, + because the attacker cannot access files stored there.

    - 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. + The following example loads a Dex file from a shared world-writable location. in this case, + since the `/sdcard` directory is on external storage, anyone 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. + This is not exploitable as nobody else except the app can access the data stored there.

    diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.ql b/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.ql index 58d9844d38aa..bae3ed63d700 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.ql @@ -1,7 +1,7 @@ /** * @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. + * @description Loading a DEX library located in a world-writable location such as + * an SD card can lead to arbitrary code execution vulnerabilities. * @kind path-problem * @problem.severity error * @precision high @@ -16,5 +16,5 @@ 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." +select sink.getNode(), source, sink, "Potential arbitrary code execution due to $@.", + source.getNode(), "a value loaded from a world-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 index 2a4b387be7e3..0ee0954216e7 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.qll @@ -2,7 +2,7 @@ import java import semmle.code.java.dataflow.FlowSources /** - * A taint-tracking configuration fordetecting unsafe use of a + * A taint-tracking configuration detecting unsafe use of a * `DexClassLoader` by an Android app. */ class InsecureDexConfiguration extends TaintTracking::Configuration { @@ -63,7 +63,7 @@ private class DexClassLoader extends InsecureDexSink { } /** - * An `File` instance which reads from an SD card + * A `File` instance which reads from an SD card * taken as a source for insecure Dex class loading vulnerabilities. */ private class ExternalFile extends InsecureDexSource { diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoadingBad.java b/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoadingBad.java index d8fdd828f4fb..869b6bc571c6 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoadingBad.java +++ b/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoadingBad.java @@ -22,11 +22,11 @@ private void updateChecker() { 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(); + Toast.makeText(this, "Loaded Dex!", Toast.LENGTH_LONG).show(); } } } catch (Exception e) { // ignore } } -} \ No newline at end of file +} From a5ebe8c60055b44c2e522019df2eb14e0a7b03d5 Mon Sep 17 00:00:00 2001 From: haby0 Date: Sat, 10 Apr 2021 09:26:08 +0800 Subject: [PATCH 174/433] Update java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll Co-authored-by: Chris Smowton --- .../src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5e9b447f51c7..da1e17fd75c9 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll @@ -12,7 +12,7 @@ class VerificationMethodToIfFlowConfig extends DataFlow3::Configuration { VerificationMethodToIfFlowConfig() { this = "VerificationMethodToIfFlowConfig" } override predicate isSource(DataFlow::Node src) { - exists(MethodAccess ma, BarrierGuard bg | ma = bg | + exists(MethodAccess ma | ma instanceof BarrierGuard | ( ma.getMethod().getAParameter().getName().regexpMatch("(?i).*(token|auth|referer|origin).*") or From 650446f761fc943d7a5d7c891402ea22b46b202d Mon Sep 17 00:00:00 2001 From: haby0 Date: Sat, 10 Apr 2021 09:26:32 +0800 Subject: [PATCH 175/433] Update java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll Co-authored-by: Chris Smowton --- .../experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 da1e17fd75c9..c5b0de71fad3 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll @@ -39,8 +39,8 @@ class VerificationMethodFlowConfig extends TaintTracking2::Configuration { 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 + exists(MethodAccess ma, int i, VerificationMethodToIfFlowConfig vmtifc | + ma instanceof BarrierGuard | ( ma.getMethod().getParameter(i).getName().regexpMatch("(?i).*(token|auth|referer|origin).*") From 8b756d7f1bcf090b0a890df81fe85dd712e56e05 Mon Sep 17 00:00:00 2001 From: haby0 Date: Sat, 10 Apr 2021 09:27:03 +0800 Subject: [PATCH 176/433] Update java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql Co-authored-by: Chris Smowton --- .../src/experimental/Security/CWE/CWE-352/JsonpInjection.ql | 6 +++++- 1 file changed, 5 insertions(+), 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 9e210dd0e7ba..aa046e7cd2c6 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql @@ -28,7 +28,11 @@ predicate existsFilterVerificationMethod() { ) } -/** Determine whether there is a verification method for the remote streaming source data flow path method. */ +/** + * Holds if somewhere in the whole program some user-controlled + * input is tested with what appears to be a token- or authentication-checking function, + * and `checkNode` is reachable from any function that can reach the user-controlled input source. + */ predicate existsServletVerificationMethod(Node checkNode) { exists(DataFlow::Node source, DataFlow::Node sink, VerificationMethodFlowConfig vmfc | vmfc.hasFlow(source, sink) and From 046aeaa38cd5478670e711357d965a30077ef89f Mon Sep 17 00:00:00 2001 From: haby0 Date: Sat, 10 Apr 2021 09:37:29 +0800 Subject: [PATCH 177/433] Update java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll Co-authored-by: Chris Smowton --- .../src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 c5b0de71fad3..1a1d8487149e 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll @@ -98,7 +98,7 @@ class SpringControllerRequestMappingGetMethod extends SpringControllerGetMethod } /** A concatenate expression using `(` and `)` or `);`. */ -class JsonpInjectionExpr extends AddExpr { +class JsonpBuilderExpr extends AddExpr { JsonpInjectionExpr() { getRightOperand().toString().regexpMatch("\"\\)\"|\"\\);\"") and getLeftOperand() From eeae91e62012dce68650e2a2d696adbded055218 Mon Sep 17 00:00:00 2001 From: haby0 Date: Sat, 10 Apr 2021 09:48:55 +0800 Subject: [PATCH 178/433] Update java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll Co-authored-by: Chris Smowton --- .../src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1a1d8487149e..589a565c590f 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll @@ -100,7 +100,7 @@ class SpringControllerRequestMappingGetMethod extends SpringControllerGetMethod /** A concatenate expression using `(` and `)` or `);`. */ class JsonpBuilderExpr extends AddExpr { JsonpInjectionExpr() { - getRightOperand().toString().regexpMatch("\"\\)\"|\"\\);\"") and + getRightOperand().toString().regexpMatch("\"\\);?\"") and getLeftOperand() .(AddExpr) .getLeftOperand() From 9349e6922d50c36926a4cdf56a9bbc46c712a2c1 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Thu, 8 Apr 2021 21:37:03 +0200 Subject: [PATCH 179/433] Java: Add ToStringMethod --- .../Security/CWE/CWE-209/StackTraceExposure.ql | 3 +-- .../CWE/CWE-327/MaybeBrokenCryptoAlgorithm.ql | 3 +-- .../CWE/CWE-798/HardcodedCredentialsApiCall.ql | 4 ++-- .../Undesirable Calls/CallsToStringToString.ql | 3 +-- .../Undesirable Calls/DefaultToString.ql | 15 ++++----------- .../Security/CWE/CWE-297/InsecureLdapEndpoint.ql | 2 +- java/ql/src/semmle/code/java/JDK.qll | 10 +++++++++- java/ql/src/semmle/code/java/dispatch/ObjFlow.qll | 2 +- .../code/java/security/ControlledString.qll | 9 ++++----- 9 files changed, 24 insertions(+), 27 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..3426d9f6f62b 100644 --- a/java/ql/src/Security/CWE/CWE-209/StackTraceExposure.ql +++ b/java/ql/src/Security/CWE/CWE-209/StackTraceExposure.ql @@ -79,8 +79,7 @@ predicate stackTraceExpr(Expr exception, MethodAccess stackTraceString) { printStackCall.getAnArgument() = printWriter and printStackCall.getQualifier() = exception and stackTraceString.getQualifier() = stringWriterVar.getAnAccess() and - stackTraceString.getMethod().getName() = "toString" and - stackTraceString.getMethod().getNumberOfParameters() = 0 + stackTraceString.getMethod() instanceof ToStringMethod ) } diff --git a/java/ql/src/Security/CWE/CWE-327/MaybeBrokenCryptoAlgorithm.ql b/java/ql/src/Security/CWE/CWE-327/MaybeBrokenCryptoAlgorithm.ql index c84abe1ff75d..7b026efb7ae2 100644 --- a/java/ql/src/Security/CWE/CWE-327/MaybeBrokenCryptoAlgorithm.ql +++ b/java/ql/src/Security/CWE/CWE-327/MaybeBrokenCryptoAlgorithm.ql @@ -33,9 +33,8 @@ class InsecureAlgoLiteral extends ShortStringLiteral { } predicate objectToString(MethodAccess ma) { - exists(Method m | + exists(ToStringMethod m | m = ma.getMethod() and - m.hasName("toString") and m.getDeclaringType() instanceof TypeObject and variableTrack(ma.getQualifier()).getType().getErasure() instanceof TypeObject ) diff --git a/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsApiCall.ql b/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsApiCall.ql index 4fb3f14c9934..c76527bc5389 100644 --- a/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsApiCall.ql +++ b/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsApiCall.ql @@ -19,14 +19,14 @@ class HardcodedCredentialApiCallConfiguration extends DataFlow::Configuration { override predicate isSource(DataFlow::Node n) { n.asExpr() instanceof HardcodedExpr and - not n.asExpr().getEnclosingCallable().getName() = "toString" + not n.asExpr().getEnclosingCallable() instanceof ToStringMethod } override predicate isSink(DataFlow::Node n) { n.asExpr() instanceof CredentialsApiSink } override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { node1.asExpr().getType() instanceof TypeString and - exists(MethodAccess ma | ma.getMethod().getName().regexpMatch("getBytes|toCharArray") | + exists(MethodAccess ma | ma.getMethod().hasName(["getBytes", "toCharArray"]) | node2.asExpr() = ma and ma.getQualifier() = node1.asExpr() ) diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToStringToString.ql b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToStringToString.ql index 40a4e36d70cd..c13956694056 100644 --- a/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToStringToString.ql +++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToStringToString.ql @@ -10,9 +10,8 @@ import java -from MethodAccess ma, Method tostring +from MethodAccess ma, ToStringMethod tostring where - tostring.hasName("toString") and tostring.getDeclaringType() instanceof TypeString and ma.getMethod() = tostring select ma, "Redundant call to 'toString' on a String object." diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToString.ql b/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToString.ql index 7d9ef5b85f93..c6eaf5af2cbf 100644 --- a/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToString.ql +++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToString.ql @@ -14,20 +14,13 @@ import java import semmle.code.java.StringFormat predicate explicitToStringCall(Expr e) { - exists(MethodAccess ma, Method toString | toString = ma.getMethod() | - e = ma.getQualifier() and - toString.getName() = "toString" and - toString.getNumberOfParameters() = 0 and - not toString.isStatic() + exists(MethodAccess ma | + ma.getMethod() instanceof ToStringMethod and + e = ma.getQualifier() ) } -predicate directlyDeclaresToString(Class c) { - exists(Method m | m.getDeclaringType() = c | - m.getName() = "toString" and - m.getNumberOfParameters() = 0 - ) -} +predicate directlyDeclaresToString(Class c) { any(ToStringMethod m).getDeclaringType() = c } predicate inheritsObjectToString(Class t) { not directlyDeclaresToString(t.getSourceDeclaration()) and diff --git a/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.ql b/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.ql index 467d78ae1c48..9fa2fe596fd9 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-297/InsecureLdapEndpoint.ql @@ -68,7 +68,7 @@ predicate isBooleanTrue(Expr expr) { or exists(MethodAccess ma | expr = ma and - ma.getMethod().hasName("toString") and + ma.getMethod() instanceof ToStringMethod and ma.getQualifier().(FieldAccess).getField().hasName("TRUE") and ma.getQualifier() .(FieldAccess) diff --git a/java/ql/src/semmle/code/java/JDK.qll b/java/ql/src/semmle/code/java/JDK.qll index cf2ae1ab2078..7bc072e177cb 100644 --- a/java/ql/src/semmle/code/java/JDK.qll +++ b/java/ql/src/semmle/code/java/JDK.qll @@ -353,7 +353,7 @@ class EqualsMethod extends Method { class HashCodeMethod extends Method { HashCodeMethod() { this.hasName("hashCode") and - this.getNumberOfParameters() = 0 + this.hasNoParameters() } } @@ -365,6 +365,14 @@ class CloneMethod extends Method { } } +/** A method with the same signature as `java.lang.Object.toString`. */ +class ToStringMethod extends Method { + ToStringMethod() { + this.hasName("toString") and + this.hasNoParameters() + } +} + /** * The public static `main` method, with a single formal parameter * of type `String[]` and return type `void`. diff --git a/java/ql/src/semmle/code/java/dispatch/ObjFlow.qll b/java/ql/src/semmle/code/java/dispatch/ObjFlow.qll index 52ee910ae1d5..9ec77acf2c7c 100644 --- a/java/ql/src/semmle/code/java/dispatch/ObjFlow.qll +++ b/java/ql/src/semmle/code/java/dispatch/ObjFlow.qll @@ -194,7 +194,7 @@ private predicate source(RefType t, ObjNode n) { private predicate sink(ObjNode n) { exists(MethodAccess toString | toString.getQualifier() = n.asExpr() and - toString.getMethod().hasName("toString") + toString.getMethod() instanceof ToStringMethod ) and n.getTypeBound().getErasure() instanceof TypeObject } diff --git a/java/ql/src/semmle/code/java/security/ControlledString.qll b/java/ql/src/semmle/code/java/security/ControlledString.qll index c9f036b0ea69..3779c2c1009a 100644 --- a/java/ql/src/semmle/code/java/security/ControlledString.qll +++ b/java/ql/src/semmle/code/java/security/ControlledString.qll @@ -8,7 +8,8 @@ import semmle.code.java.Expr import semmle.code.java.security.Validation /** - * Holds if `method` is a `toString()` method on a boxed type. These never return special characters. + * Holds if `method` is a `toString()` method on a boxed type, with or without parameters. + * These never return special characters. */ private predicate boxedToString(Method method) { method.getDeclaringType() instanceof BoxedType and @@ -44,11 +45,9 @@ private predicate controlledStringProp(Expr src, Expr dest) { exists(AddExpr concatOp | concatOp = dest | src = concatOp.getAnOperand()) or // `toString()` on a safe string is safe. - exists(MethodAccess toStringCall, Method toString | + exists(MethodAccess toStringCall | src = toStringCall.getQualifier() and - toString = toStringCall.getMethod() and - toString.hasName("toString") and - toString.getNumberOfParameters() = 0 and + toStringCall.getMethod() instanceof ToStringMethod and dest = toStringCall ) } From d90527bead69c90f6a2f2ce12bce0cd82dd44ebd Mon Sep 17 00:00:00 2001 From: haby0 Date: Sat, 10 Apr 2021 10:33:21 +0800 Subject: [PATCH 180/433] JsonpInjectionExpr updated to JsonpBuilderExpr --- .../Security/CWE/CWE-352/JsonpInjectionLib.qll | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 589a565c590f..380087cdd05e 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll @@ -99,7 +99,7 @@ class SpringControllerRequestMappingGetMethod extends SpringControllerGetMethod /** A concatenate expression using `(` and `)` or `);`. */ class JsonpBuilderExpr extends AddExpr { - JsonpInjectionExpr() { + JsonpBuilderExpr() { getRightOperand().toString().regexpMatch("\"\\);?\"") and getLeftOperand() .(AddExpr) @@ -126,7 +126,7 @@ class RemoteFlowConfig extends DataFlow2::Configuration { override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource } override predicate isSink(DataFlow::Node sink) { - exists(JsonpInjectionExpr jhe | jhe.getFunctionName() = sink.asExpr()) + exists(JsonpBuilderExpr jhe | jhe.getFunctionName() = sink.asExpr()) } } @@ -137,7 +137,7 @@ class JsonDataFlowConfig extends DataFlow2::Configuration { override predicate isSource(DataFlow::Node src) { src instanceof JsonpStringSource } override predicate isSink(DataFlow::Node sink) { - exists(JsonpInjectionExpr jhe | jhe.getJsonExpr() = sink.asExpr()) + exists(JsonpBuilderExpr jhe | jhe.getJsonExpr() = sink.asExpr()) } } @@ -146,7 +146,7 @@ class JsonpInjectionFlowConfig extends TaintTracking::Configuration { JsonpInjectionFlowConfig() { this = "JsonpInjectionFlowConfig" } override predicate isSource(DataFlow::Node src) { - exists(JsonpInjectionExpr jhe, JsonDataFlowConfig jdfc, RemoteFlowConfig rfc | + exists(JsonpBuilderExpr jhe, JsonDataFlowConfig jdfc, RemoteFlowConfig rfc | jhe = src.asExpr() and jdfc.hasFlowTo(DataFlow::exprNode(jhe.getJsonExpr())) and rfc.hasFlowTo(DataFlow::exprNode(jhe.getFunctionName())) From 10be2735ec49bf10b451858863247229608e7a1e Mon Sep 17 00:00:00 2001 From: Taus Date: Sat, 10 Apr 2021 12:12:18 +0000 Subject: [PATCH 181/433] Python: Get rid of `_attr` predicates Also changes all `CfgNode`s representing calls to `CallCfgNode`s. --- .../src/semmle/python/frameworks/Stdlib.qll | 158 ++++-------------- 1 file changed, 32 insertions(+), 126 deletions(-) diff --git a/python/ql/src/semmle/python/frameworks/Stdlib.qll b/python/ql/src/semmle/python/frameworks/Stdlib.qll index 1941f0c4d5bf..326ae8b647ca 100644 --- a/python/ql/src/semmle/python/frameworks/Stdlib.qll +++ b/python/ql/src/semmle/python/frameworks/Stdlib.qll @@ -19,31 +19,15 @@ private module Stdlib { /** Gets a reference to the `os` module. */ API::Node os() { result = API::moduleImport("os") } - /** - * Gets a reference to the attribute `attr_name` of the `os` module. - * WARNING: Only holds for a few predefined attributes. - * - * For example, using `"system"` will get all uses of `os.system`. - */ - 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. */ - API::Node path() { result = os_attr("path") } + API::Node path() { result = os().getMember("path") } /** Provides models for the `os.path` module */ module path { - /** - * 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`. - */ - API::Node path_attr(string attr_name) { result = path().getMember(attr_name) } - /** Gets a reference to the `os.path.join` function. */ - API::Node join() { result = path_attr("join") } + API::Node join() { result = path().getMember("join") } } } @@ -52,7 +36,7 @@ private module Stdlib { * See https://docs.python.org/3/library/os.path.html#os.path.normpath */ private class OsPathNormpathCall extends Path::PathNormalization::Range, DataFlow::CallCfgNode { - OsPathNormpathCall() { this = os::path::path_attr("normpath").getACall() } + OsPathNormpathCall() { this = os::path().getMember("normpath").getACall() } DataFlow::Node getPathArg() { result.asCfgNode() in [node.getArg(0), node.getArgByName("path")] @@ -73,10 +57,8 @@ private module Stdlib { * A call to `os.path.abspath`. * See https://docs.python.org/3/library/os.path.html#os.path.abspath */ - private class OsPathAbspathCall extends Path::PathNormalization::Range, DataFlow::CfgNode { - override CallNode node; - - OsPathAbspathCall() { this = os::path::path_attr("abspath").getACall() } + private class OsPathAbspathCall extends Path::PathNormalization::Range, DataFlow::CallCfgNode { + OsPathAbspathCall() { this = os::path().getMember("abspath").getACall() } DataFlow::Node getPathArg() { result.asCfgNode() in [node.getArg(0), node.getArgByName("path")] @@ -97,10 +79,8 @@ private module Stdlib { * A call to `os.path.realpath`. * See https://docs.python.org/3/library/os.path.html#os.path.realpath */ - private class OsPathRealpathCall extends Path::PathNormalization::Range, DataFlow::CfgNode { - override CallNode node; - - OsPathRealpathCall() { this = os::path::path_attr("realpath").getACall() } + private class OsPathRealpathCall extends Path::PathNormalization::Range, DataFlow::CallCfgNode { + OsPathRealpathCall() { this = os::path().getMember("realpath").getACall() } DataFlow::Node getPathArg() { result.asCfgNode() in [node.getArg(0), node.getArgByName("path")] @@ -121,10 +101,8 @@ private module Stdlib { * A call to `os.system`. * See https://docs.python.org/3/library/os.html#os.system */ - private class OsSystemCall extends SystemCommandExecution::Range, DataFlow::CfgNode { - override CallNode node; - - OsSystemCall() { this = os_attr("system").getACall() } + private class OsSystemCall extends SystemCommandExecution::Range, DataFlow::CallCfgNode { + OsSystemCall() { this = os().getMember("system").getACall() } override DataFlow::Node getCommand() { result.asCfgNode() = node.getArg(0) } } @@ -137,13 +115,12 @@ private module Stdlib { * Although deprecated since version 2.6, they still work in 2.7. * See https://docs.python.org/2.7/library/os.html#os.popen2 */ - private class OsPopenCall extends SystemCommandExecution::Range, DataFlow::CfgNode { - override CallNode node; + private class OsPopenCall extends SystemCommandExecution::Range, DataFlow::CallCfgNode { string name; OsPopenCall() { name in ["popen", "popen2", "popen3", "popen4"] and - this = os_attr(name).getACall() + this = os().getMember(name).getACall() } override DataFlow::Node getCommand() { @@ -158,13 +135,11 @@ private module Stdlib { * A call to any of the `os.exec*` functions * See https://docs.python.org/3.8/library/os.html#os.execl */ - private class OsExecCall extends SystemCommandExecution::Range, DataFlow::CfgNode { - override CallNode node; - + private class OsExecCall extends SystemCommandExecution::Range, DataFlow::CallCfgNode { OsExecCall() { exists(string name | name in ["execl", "execle", "execlp", "execlpe", "execv", "execve", "execvp", "execvpe"] and - this = os_attr(name).getACall() + this = os().getMember(name).getACall() ) } @@ -175,15 +150,13 @@ private module Stdlib { * A call to any of the `os.spawn*` functions * See https://docs.python.org/3.8/library/os.html#os.spawnl */ - private class OsSpawnCall extends SystemCommandExecution::Range, DataFlow::CfgNode { - override CallNode node; - + private class OsSpawnCall extends SystemCommandExecution::Range, DataFlow::CallCfgNode { OsSpawnCall() { exists(string name | name in [ "spawnl", "spawnle", "spawnlp", "spawnlpe", "spawnv", "spawnve", "spawnvp", "spawnvpe" ] and - this = os_attr(name).getACall() + this = os().getMember(name).getACall() ) } @@ -194,10 +167,8 @@ private module Stdlib { * A call to any of the `os.posix_spawn*` functions * See https://docs.python.org/3.8/library/os.html#os.posix_spawn */ - private class OsPosixSpawnCall extends SystemCommandExecution::Range, DataFlow::CfgNode { - override CallNode node; - - OsPosixSpawnCall() { this = os_attr(["posix_spawn", "posix_spawnp"]).getACall() } + private class OsPosixSpawnCall extends SystemCommandExecution::Range, DataFlow::CallCfgNode { + OsPosixSpawnCall() { this = os().getMember(["posix_spawn", "posix_spawnp"]).getACall() } override DataFlow::Node getCommand() { result.asCfgNode() = node.getArg(0) } } @@ -218,29 +189,17 @@ private module Stdlib { // subprocess // --------------------------------------------------------------------------- /** Gets a reference to the `subprocess` module. */ - deprecated DataFlow::Node subprocess() { result = API::moduleImport("subprocess").getAUse() } - - /** - * 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(string attr_name) { - result = API::moduleImport("subprocess").getMember(attr_name).getAUse() - } + API::Node subprocess() { result = API::moduleImport("subprocess") } /** * A call to `subprocess.Popen` or helper functions (call, check_call, check_output, run) * See https://docs.python.org/3.8/library/subprocess.html#subprocess.Popen */ - private class SubprocessPopenCall extends SystemCommandExecution::Range, DataFlow::CfgNode { - override CallNode node; - + private class SubprocessPopenCall extends SystemCommandExecution::Range, DataFlow::CallCfgNode { SubprocessPopenCall() { exists(string name | name in ["Popen", "call", "check_call", "check_output", "run"] and - node.getFunction() = subprocess_attr(name).asCfgNode() + this = subprocess().getMember(name).getACall() ) } @@ -311,17 +270,6 @@ private module Stdlib { // --------------------------------------------------------------------------- // marshal // --------------------------------------------------------------------------- - /** Gets a reference to the `marshal` module. */ - 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. */ - deprecated DataFlow::Node loads() { - result = API::moduleImport("marshal").getMember("loads").getAUse() - } - } - /** * A call to `marshal.loads` * See https://docs.python.org/3/library/marshal.html#marshal.loads @@ -342,9 +290,7 @@ private module Stdlib { // pickle // --------------------------------------------------------------------------- /** Gets a reference to the `pickle` module. */ - DataFlow::Node pickle() { - result = API::moduleImport(["pickle", "cPickle", "_pickle"]).getAUse() - } + DataFlow::Node pickle() { result = API::moduleImport(["pickle", "cPickle", "_pickle"]).getAUse() } /** Provides models for the `pickle` module. */ module pickle { @@ -376,25 +322,15 @@ private module Stdlib { /** Gets a reference to the `popen2` module (only available in Python 2). */ 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 API::Node popen2_attr(string attr_name) { - result = API::moduleImport("popen2").getMember(attr_name) - } - /** * A call to any of the `popen.popen*` functions, or instantiation of a `popen.Popen*` class. * See https://docs.python.org/2.7/library/popen2.html */ - private class Popen2PopenCall extends SystemCommandExecution::Range, DataFlow::CfgNode { - override CallNode node; - + private class Popen2PopenCall extends SystemCommandExecution::Range, DataFlow::CallCfgNode { Popen2PopenCall() { exists(string name | name in ["popen2", "popen3", "popen4", "Popen3", "Popen4"] and - this = popen2_attr(name).getACall() + this = popen2().getMember(name).getACall() ) } @@ -409,18 +345,12 @@ private module Stdlib { /** Gets a reference to the `platform` module. */ 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 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::CallCfgNode { - PlatformPopenCall() { this = platform_attr("popen").getACall() } + PlatformPopenCall() { this = platform().getMember("popen").getACall() } override DataFlow::Node getCommand() { result.asCfgNode() in [node.getArg(0), node.getArgByName("cmd")] @@ -500,12 +430,6 @@ private module Stdlib { /** Gets a reference to the `base64` module. */ 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 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::CallCfgNode { string name; @@ -515,7 +439,7 @@ private module Stdlib { "b64encode", "standard_b64encode", "urlsafe_b64encode", "b32encode", "b16encode", "encodestring", "a85encode", "b85encode", "encodebytes" ] and - this = base64_attr(name).getACall() + this = base64().getMember(name).getACall() } override DataFlow::Node getAnInput() { result.asCfgNode() = node.getArg(0) } @@ -547,7 +471,7 @@ private module Stdlib { "b64decode", "standard_b64decode", "urlsafe_b64decode", "b32decode", "b16decode", "decodestring", "a85decode", "b85decode", "decodebytes" ] and - this = base64_attr(name).getACall() + this = base64().getMember(name).getACall() } override predicate mayExecuteInput() { none() } @@ -578,18 +502,12 @@ private module Stdlib { /** Gets a reference to the `json` module. */ API::Node json() { result = API::moduleImport("json") } - /** - * Gets a reference to the attribute `attr_name` of the `json` module. - * WARNING: Only holds for a few predefined attributes. - */ - 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::CallCfgNode { - JsonLoadsCall() { this = json_attr("loads").getACall() } + JsonLoadsCall() { this = json().getMember("loads").getACall() } override predicate mayExecuteInput() { none() } @@ -605,7 +523,7 @@ private module Stdlib { * See https://docs.python.org/3/library/json.html#json.dumps */ private class JsonDumpsCall extends Encoding::Range, DataFlow::CallCfgNode { - JsonDumpsCall() { this = json_attr("dumps").getACall() } + JsonDumpsCall() { this = json().getMember("dumps").getACall() } override DataFlow::Node getAnInput() { result.asCfgNode() = node.getArg(0) } @@ -799,28 +717,16 @@ private module Stdlib { /** Gets a reference to the `http` module. */ 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 API::Node http_attr(string attr_name) { result = http().getMember(attr_name) } - /** Provides models for the `http` module. */ module http { // ------------------------------------------------------------------------- // http.server // ------------------------------------------------------------------------- /** Gets a reference to the `http.server` module. */ - API::Node server() { result = http_attr("server") } + API::Node server() { result = http().getMember("server") } /** Provides models for the `http.server` module */ module server { - /** - * Gets a reference to the attribute `attr_name` of the `http.server` module. - * WARNING: Only holds for a few predefined attributes. - */ - private API::Node server_attr(string attr_name) { result = server().getMember(attr_name) } - /** * Provides models for the `http.server.BaseHTTPRequestHandler` class (Python 3 only). * @@ -828,7 +734,7 @@ private module Stdlib { */ module BaseHTTPRequestHandler { /** Gets a reference to the `http.server.BaseHTTPRequestHandler` class. */ - API::Node classRef() { result = server_attr("BaseHTTPRequestHandler") } + API::Node classRef() { result = server().getMember("BaseHTTPRequestHandler") } } /** @@ -838,7 +744,7 @@ private module Stdlib { */ module SimpleHTTPRequestHandler { /** Gets a reference to the `http.server.SimpleHTTPRequestHandler` class. */ - API::Node classRef() { result = server_attr("SimpleHTTPRequestHandler") } + API::Node classRef() { result = server().getMember("SimpleHTTPRequestHandler") } } /** @@ -848,7 +754,7 @@ private module Stdlib { */ module CGIHTTPRequestHandler { /** Gets a reference to the `http.server.CGIHTTPRequestHandler` class. */ - API::Node classRef() { result = server_attr("CGIHTTPRequestHandler") } + API::Node classRef() { result = server().getMember("CGIHTTPRequestHandler") } } } } From 17d1c77a144bd2dff785bdd9950f9668b3d1f7ab Mon Sep 17 00:00:00 2001 From: ihsinme Date: Mon, 12 Apr 2021 08:14:17 +0300 Subject: [PATCH 182/433] Update InsufficientControlFlowManagementAfterRefactoringTheCode.ql --- .../InsufficientControlFlowManagementAfterRefactoringTheCode.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.ql b/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.ql index 6b9639fe8b9b..fa8780d6e450 100644 --- a/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.ql +++ b/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.ql @@ -44,7 +44,7 @@ class UsingWhileAfterWhile extends WhileStmt { } /** - * Using arithmetic in comparison. + * Using arithmetic in a condition. */ class UsingArithmeticInComparison extends BinaryArithmeticOperation { /** From 3da88f2103406da69d287ef8880ca001e7efc084 Mon Sep 17 00:00:00 2001 From: ihsinme Date: Mon, 12 Apr 2021 08:15:36 +0300 Subject: [PATCH 183/433] Update InsufficientControlFlowManagementAfterRefactoringTheCode.c --- .../InsufficientControlFlowManagementAfterRefactoringTheCode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.c b/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.c index 3ba47068244d..1f1f10b89cc5 100644 --- a/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.c +++ b/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.c @@ -10,7 +10,7 @@ while(flagsLoop) ... if(flagsIf) break; ... -} // GOOD: coreten cycle +} // GOOD: correct cycle ... if(intA+intB) return 1; // BAD: possibly no comparison ... From 6924c6c51c4fd8235dab112580bd5c1faa40802f Mon Sep 17 00:00:00 2001 From: ihsinme Date: Mon, 12 Apr 2021 08:23:06 +0300 Subject: [PATCH 184/433] Update test.c --- .../query-tests/Security/CWE/CWE-691/semmle/tests/test.c | 4 ++++ 1 file changed, 4 insertions(+) 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 e2fccf587366..230b027b39b2 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 @@ -20,6 +20,10 @@ void workFunction_1(char *s) { if(intB - intA<10) break; intA--; }while(intA>0); // BAD + for(intA=100; intA>0; intA--) + { + if(intB - intA<10) break; + }while(intA>0); // BAD while(intA>0) { if(intB - intA<10) break; From feb3a8deb10231e6e042da3d33dec445a251d3e4 Mon Sep 17 00:00:00 2001 From: ihsinme Date: Mon, 12 Apr 2021 08:23:41 +0300 Subject: [PATCH 185/433] Update InsufficientControlFlowManagementAfterRefactoringTheCode.expected --- ...fficientControlFlowManagementAfterRefactoringTheCode.expected | 1 + 1 file changed, 1 insertion(+) 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 index cc1b5cf46d80..1eca77a526f1 100644 --- 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 @@ -1,3 +1,4 @@ | 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 | +| test.c:26:10:26:15 | ... > ... | this expression needs your attention | From 02d6de81a7e5e4cee907d5b67b7757e51178294e Mon Sep 17 00:00:00 2001 From: yoff Date: Mon, 12 Apr 2021 08:16:36 +0200 Subject: [PATCH 186/433] Apply suggestions from code review Co-authored-by: Taus --- python/ql/src/Security/CWE-327/README.md | 2 +- python/ql/src/Security/CWE-327/TlsLibraryModel.qll | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/ql/src/Security/CWE-327/README.md b/python/ql/src/Security/CWE-327/README.md index 680ae56f4934..f80dfd0a2963 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 fluent api is used to constrain possible protocols later. +- Default constructors are fine, a fluent API is used to constrain possible protocols later. ## Current recomendation diff --git a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll index 82c251f3f810..dfbc989150bc 100644 --- a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll +++ b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll @@ -116,7 +116,7 @@ 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(ProtocolFamily family) { + ContextCreation unspecific_context_creation(ProtocolFamily family) { result = default_context_creation() or result = specific_context_creation() and From 036fddfdb53cdaf356422a9bdc277d024bd69ae1 Mon Sep 17 00:00:00 2001 From: Rasmus Lerchedahl Petersen Date: Mon, 12 Apr 2021 08:18:24 +0200 Subject: [PATCH 187/433] Python: `Namable` -> `Nameable` --- python/ql/src/Security/CWE-327/InsecureProtocol.ql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/ql/src/Security/CWE-327/InsecureProtocol.ql b/python/ql/src/Security/CWE-327/InsecureProtocol.ql index 19f6acf5512c..e31bf78443b2 100644 --- a/python/ql/src/Security/CWE-327/InsecureProtocol.ql +++ b/python/ql/src/Security/CWE-327/InsecureProtocol.ql @@ -33,19 +33,19 @@ class ProtocolConfiguration extends DataFlow::Node { // // 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() { +class Nameable extends @py_ast_node { + Nameable() { exists(ProtocolConfiguration protocolConfiguration | this = protocolConfiguration.asCfgNode().(CallNode).getFunction().getNode() ) or - exists(Namable attr | this = attr.(Attribute).getObject()) + exists(Nameable attr | this = attr.(Attribute).getObject()) } string toString() { result = "AstNode" } } -string callName(Namable call) { +string callName(Nameable call) { result = call.(Name).getId() or exists(Attribute a | a = call | result = callName(a.getObject()) + "." + a.getName()) From 1b948ac2e2e44cc95d8340755f2d51bb8d4ec1c9 Mon Sep 17 00:00:00 2001 From: haby0 Date: Mon, 12 Apr 2021 15:44:39 +0800 Subject: [PATCH 188/433] Combine two Configurations into one --- .../Security/CWE/CWE-352/JsonpInjection.ql | 6 +-- .../CWE/CWE-352/JsonpInjectionLib.qll | 51 ++++++++----------- 2 files changed, 23 insertions(+), 34 deletions(-) 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 aa046e7cd2c6..a02891e8a566 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql @@ -16,7 +16,7 @@ import semmle.code.java.dataflow.FlowSources import semmle.code.java.deadcode.WebEntryPoints import DataFlow::PathGraph -/** +/** * Holds if some `Filter.doFilter` method exists in the whole program that takes some user-controlled * input and tests it with what appears to be a token- or authentication-checking function. */ @@ -28,7 +28,7 @@ predicate existsFilterVerificationMethod() { ) } -/** +/** * Holds if somewhere in the whole program some user-controlled * input is tested with what appears to be a token- or authentication-checking function, * and `checkNode` is reachable from any function that can reach the user-controlled input source. @@ -69,4 +69,4 @@ where conf.hasFlowPath(source, sink) and exists(JsonpInjectionFlowConfig jhfc | jhfc.hasFlowTo(sink.getNode())) select sink.getNode(), source, sink, "Jsonp response might include code from $@.", source.getNode(), - "this user input" + "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 index 380087cdd05e..f0a1a9de81c8 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll @@ -7,31 +7,11 @@ import semmle.code.java.dataflow.DataFlow3 import semmle.code.java.dataflow.FlowSources import semmle.code.java.frameworks.spring.SpringController -/** A data flow configuration tracing flow from the result of a method whose name includes token/auth/referer/origin to an if-statement condition. */ -class VerificationMethodToIfFlowConfig extends DataFlow3::Configuration { - VerificationMethodToIfFlowConfig() { this = "VerificationMethodToIfFlowConfig" } - - override predicate isSource(DataFlow::Node src) { - exists(MethodAccess ma | ma instanceof BarrierGuard | - ( - 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(IfStmt is | is.getCondition() = sink.asExpr()) - } -} - -/** Taint-tracking configuration tracing flow from untrusted inputs to an argument of a function whose result is used as an if-statement condition. -* -* For example, in the context `String userControlled = request.getHeader("xyz"); boolean isGood = checkToken(userControlled); if(isGood) { ...`, -* the flow from `checkToken`'s result to the condition of `if(isGood)` matches the configuration `VerificationMethodToIfFlowConfig` above, -* and so the flow from `getHeader(...)` to the argument to `checkToken` matches this configuration. +/** + * Taint-tracking configuration tracing flow from untrusted inputs to an argument of a function whose result is used as an if-statement condition. + * + * For example, in the context `String userControlled = request.getHeader("xyz"); boolean isGood = checkToken(userControlled); if(isGood) { ...`, + * the flow from `getHeader(...)` to the argument to `checkToken`, and then the flow from `checkToken`'s result to the condition of `if(isGood)`. */ class VerificationMethodFlowConfig extends TaintTracking2::Configuration { VerificationMethodFlowConfig() { this = "VerificationMethodFlowConfig" } @@ -39,16 +19,25 @@ class VerificationMethodFlowConfig extends TaintTracking2::Configuration { override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource } override predicate isSink(DataFlow::Node sink) { - exists(MethodAccess ma, int i, VerificationMethodToIfFlowConfig vmtifc | - ma instanceof BarrierGuard - | + exists(IfStmt is, Method m | is.getEnclosingCallable() = m | ( - ma.getMethod().getParameter(i).getName().regexpMatch("(?i).*(token|auth|referer|origin).*") + not m.getAParameter().getName().regexpMatch("(?i).*(token|auth|referer|origin).*") + or + not m.getName().regexpMatch("(?i).*(token|auth|referer|origin).*") + ) and + sink.asExpr() = is.getCondition() + ) + } + + override predicate isAdditionalTaintStep(DataFlow::Node prod, DataFlow::Node succ) { + exists(MethodAccess ma | + ( + ma.getMethod().getAParameter().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), _) + ma.getAnArgument() = prod.asExpr() and + ma = succ.asExpr() ) } } From 17c4bbbc4e80d5e4015593ef3c46347957e84c07 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Mon, 12 Apr 2021 09:57:40 +0200 Subject: [PATCH 189/433] allow parameters that end with "Command" in js/shell-command-constructed-from-input --- .../UnsafeShellCommandConstructionCustomizations.qll | 7 ++++++- javascript/ql/test/query-tests/Security/CWE-078/lib/lib.js | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/UnsafeShellCommandConstructionCustomizations.qll b/javascript/ql/src/semmle/javascript/security/dataflow/UnsafeShellCommandConstructionCustomizations.qll index e38e2776bbcb..20b691684741 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/UnsafeShellCommandConstructionCustomizations.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/UnsafeShellCommandConstructionCustomizations.qll @@ -53,7 +53,12 @@ module UnsafeShellCommandConstruction { class ExternalInputSource extends Source, DataFlow::ParameterNode { ExternalInputSource() { this = Exports::getALibraryInputParameter() and - not this.getName() = ["cmd", "command"] // looks to be on purpose. + not ( + // looks to be on purpose. + this.getName() = ["cmd", "command"] + or + this.getName().regexpMatch(".*(Cmd|Command)$") // ends with "Cmd" or "Command" + ) } } diff --git a/javascript/ql/test/query-tests/Security/CWE-078/lib/lib.js b/javascript/ql/test/query-tests/Security/CWE-078/lib/lib.js index 9c848024fc5b..6b053d643660 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/lib/lib.js +++ b/javascript/ql/test/query-tests/Security/CWE-078/lib/lib.js @@ -483,4 +483,9 @@ module.exports.splitConcat = function (name) { let args = ' my name is ' + name; // NOT OK let cmd = 'echo'; cp.exec(cmd + args); +} + +module.exports.myCommand = function (myCommand) { + let cmd = `cd ${cwd} ; ${myCommand}`; // OK - the parameter name suggests that it is purposely a shell command. + cp.exec(cmd); } \ No newline at end of file From 9f91dde76fb55ecea5b6fdbb81d3e837f7dfa4e7 Mon Sep 17 00:00:00 2001 From: Rasmus Lerchedahl Petersen Date: Mon, 12 Apr 2021 09:58:06 +0200 Subject: [PATCH 190/433] Python: Update test expectation after comment --- .../CWE-327/InsecureProtocol.expected | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 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 57b19cef0b17..359a8d28ba1a 100644 --- a/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected +++ b/python/ql/test/query-tests/Security/CWE-327/InsecureProtocol.expected @@ -22,18 +22,18 @@ | 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: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 | +| ssl_fluent.py:57:14:57:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:54:15:54:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext | +| ssl_fluent.py:57:14:57:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:54:15:54:49 | ControlFlowNode for Attribute() | call to ssl.SSLContext | +| ssl_fluent.py:71:14:71:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:62:12:62:43 | ControlFlowNode for Attribute() | call to ssl.SSLContext | +| ssl_fluent.py:71:14:71:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:101:15:101:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext | +| ssl_fluent.py:71:14:71:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:62:12:62:43 | ControlFlowNode for Attribute() | call to ssl.SSLContext | +| ssl_fluent.py:71:14:71:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:101:15:101:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext | +| ssl_fluent.py:71:14:71:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:117:5:117:11 | ControlFlowNode for context | context modification | +| ssl_fluent.py:71:14:71:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:135:5:135:11 | ControlFlowNode for context | context modification | +| ssl_fluent.py:77:14:77:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:62:12:62:43 | ControlFlowNode for Attribute() | call to ssl.SSLContext | +| ssl_fluent.py:77:14:77:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:62:12:62:43 | ControlFlowNode for Attribute() | call to ssl.SSLContext | +| ssl_fluent.py:97:14:97:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:95:5:95:11 | ControlFlowNode for context | context modification | +| ssl_fluent.py:146:14:146:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:143:5:143:11 | ControlFlowNode for context | context modification | +| ssl_fluent.py:165:14:165:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv3 allowed by $@ | ssl_fluent.py:162:5:162:11 | ControlFlowNode for context | context modification | +| ssl_fluent.py:165:14:165:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@ | ssl_fluent.py:161:15:161:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context | +| ssl_fluent.py:165:14:165:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@ | ssl_fluent.py:161:15:161:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context | From 3ff8e010b292598e7422f294907d950e9bfb6ad2 Mon Sep 17 00:00:00 2001 From: Rasmus Lerchedahl Petersen Date: Mon, 12 Apr 2021 10:00:07 +0200 Subject: [PATCH 191/433] Python: Refactor based on review - more natural handling of default arguments - do not assume default construction gives a family - simplifies `UnspecificSSLContextCreation` --- python/ql/src/Security/CWE-327/PyOpenSSL.qll | 8 ++++-- python/ql/src/Security/CWE-327/Ssl.qll | 25 ++++++++++--------- .../src/Security/CWE-327/TlsLibraryModel.qll | 22 ++++++++-------- 3 files changed, 29 insertions(+), 26 deletions(-) diff --git a/python/ql/src/Security/CWE-327/PyOpenSSL.qll b/python/ql/src/Security/CWE-327/PyOpenSSL.qll index d2cfb74b3ab9..55b77386f2f8 100644 --- a/python/ql/src/Security/CWE-327/PyOpenSSL.qll +++ b/python/ql/src/Security/CWE-327/PyOpenSSL.qll @@ -9,8 +9,12 @@ class PyOpenSSLContextCreation extends ContextCreation { this = API::moduleImport("OpenSSL").getMember("SSL").getMember("Context").getACall() } - override DataFlow::CfgNode getProtocol() { - result.getNode() in [node.getArg(0), node.getArgByName("method")] + override string getProtocol() { + exists(ControlFlowNode protocolArg, PyOpenSSL pyo | + protocolArg in [node.getArg(0), node.getArgByName("method")] + | + protocolArg = [pyo.specific_version(result), pyo.unspecific_version(result)].asCfgNode() + ) } } diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll index 2ca7dcce1f90..cb19fb8f11ff 100644 --- a/python/ql/src/Security/CWE-327/Ssl.qll +++ b/python/ql/src/Security/CWE-327/Ssl.qll @@ -7,8 +7,15 @@ class SSLContextCreation extends ContextCreation { SSLContextCreation() { this = API::moduleImport("ssl").getMember("SSLContext").getACall() } - override DataFlow::CfgNode getProtocol() { - result.getNode() in [node.getArg(0), node.getArgByName("protocol")] + override string getProtocol() { + exists(ControlFlowNode protocolArg, Ssl ssl | + protocolArg in [node.getArg(0), node.getArgByName("protocol")] + | + protocolArg = [ssl.specific_version(result), ssl.unspecific_version(result)].asCfgNode() + ) + or + not exists(node.getAnArg()) and + result = "TLS" } } @@ -19,7 +26,7 @@ class SSLDefaultContextCreation extends ContextCreation { // Allowed insecure versions are "TLSv1" and "TLSv1_1" // see https://docs.python.org/3/library/ssl.html#context-creation - override DataFlow::CfgNode getProtocol() { none() } + override string getProtocol() { result = "TLS" } } /** Gets a reference to an `ssl.Context` instance. */ @@ -141,17 +148,10 @@ 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 - // 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 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"] } } @@ -185,8 +185,9 @@ class Ssl extends TlsLibrary { 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") + specific_version(version).asCfgNode() = + result.asCfgNode().(CallNode).getArgByName("ssl_version") and + version.isInsecure() } override ConnectionCreation connection_creation() { result instanceof WrapSocketCall } diff --git a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll index dfbc989150bc..685485004b25 100644 --- a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll +++ b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll @@ -30,8 +30,8 @@ class ProtocolFamily extends string { /** The creation of a context. */ abstract class ContextCreation extends DataFlow::CfgNode { - /** Gets the requested protocol if any. */ - abstract DataFlow::CfgNode getProtocol(); + /** Gets the protocol version or family for this context. */ + abstract string getProtocol(); } /** The creation of a connection from a context. */ @@ -66,7 +66,7 @@ abstract class UnspecificContextCreation extends ContextCreation, ProtocolUnrest TlsLibrary library; ProtocolFamily family; - UnspecificContextCreation() { this.getProtocol() = library.unspecific_version(family) } + UnspecificContextCreation() { this.getProtocol() = family } override DataFlow::CfgNode getContext() { result = this } @@ -92,9 +92,8 @@ abstract class TlsLibrary extends string { /** 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) { - version.isInsecure() and + /** A dataflow node representing a specific protocol version. */ + DataFlow::Node specific_version(ProtocolVersion version) { result = version_constants().getMember(specific_version_name(version)).getAUse() } @@ -111,16 +110,15 @@ abstract class TlsLibrary extends string { /** 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) + result in [specific_context_creation(), default_context_creation()] and + result.getProtocol() = version and + version.isInsecure() } /** The creation of a context with an unspecific protocol version, say TLS, known to have insecure instances. */ ContextCreation unspecific_context_creation(ProtocolFamily family) { - result = default_context_creation() - or - result = specific_context_creation() and - result.(ContextCreation).getProtocol() = unspecific_version(family) + result in [specific_context_creation(), default_context_creation()] and + result.getProtocol() = family } /** A connection is created in an insecure manner, not from a context. */ From 91d28fb8b0f3591207ece13ae88fddbd85477f18 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 19 Mar 2021 19:32:21 +0100 Subject: [PATCH 192/433] cleanup in API-graphs --- .../ql/src/semmle/javascript/ApiGraphs.qll | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/ApiGraphs.qll b/javascript/ql/src/semmle/javascript/ApiGraphs.qll index a363e472e15d..96e2e93fa0b2 100644 --- a/javascript/ql/src/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/src/semmle/javascript/ApiGraphs.qll @@ -374,15 +374,13 @@ module API { exists(SSA::implicitInit([nm.getModuleVariable(), nm.getExportsVariable()])) ) ) - or - m = any(CanonicalName n | isDefined(n)).getExternalModuleName() } or MkModuleImport(string m) { imports(_, m) or - m = any(CanonicalName n | isUsed(n)).getExternalModuleName() - or any(TypeAnnotation n).hasQualifiedName(m, _) + or + any(Type t).hasUnderlyingType(m, _) } or MkClassInstance(DataFlow::ClassNode cls) { cls = trackDefNode(_) and hasSemantics(cls) } or MkAsyncFuncResult(DataFlow::FunctionNode f) { @@ -431,20 +429,6 @@ module API { ) } - private predicate isUsed(CanonicalName n) { - exists(n.(TypeName).getAnAccess()) or - exists(n.(Namespace).getAnAccess()) - } - - private predicate isDefined(CanonicalName n) { - exists(ASTNode def | - def = n.(TypeName).getADefinition() or - def = n.(Namespace).getADefinition() - | - not def.isAmbient() - ) - } - /** * Holds if `rhs` is the right-hand side of a definition of a node that should have an * incoming edge from `base` labeled `lbl` in the API graph. From cd57e61f658c6ada8043145d65d5b8b3fe53c901 Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Mon, 22 Mar 2021 15:21:30 +0000 Subject: [PATCH 193/433] Rename `MkHasUnderlyingType` to `MkTypeUse`. --- javascript/ql/src/semmle/javascript/ApiGraphs.qll | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/ApiGraphs.qll b/javascript/ql/src/semmle/javascript/ApiGraphs.qll index 96e2e93fa0b2..c0d28d913f70 100644 --- a/javascript/ql/src/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/src/semmle/javascript/ApiGraphs.qll @@ -313,7 +313,7 @@ module API { module Node { /** Gets a node whose type has the given qualified name. */ Node ofType(string moduleName, string exportedName) { - result = Impl::MkHasUnderlyingType(moduleName, exportedName).(Node).getInstance() + result = Impl::MkTypeUse(moduleName, exportedName).(Node).getInstance() } } @@ -388,11 +388,8 @@ module API { } or MkDef(DataFlow::Node nd) { rhs(_, _, nd) } or MkUse(DataFlow::Node nd) { use(_, _, nd) } or - /** - * A TypeScript type, identified by name of the type-annotation. - * This API node is exclusively used by `API::Node::ofType`. - */ - MkHasUnderlyingType(string moduleName, string exportName) { + /** A use of a TypeScript type. */ + MkTypeUse(string moduleName, string exportName) { any(TypeAnnotation n).hasQualifiedName(moduleName, exportName) or any(Type t).hasUnderlyingType(moduleName, exportName) @@ -406,7 +403,7 @@ module API { class TNonModuleDef = MkModuleExport or MkClassInstance or MkAsyncFuncResult or MkDef or MkSyntheticCallbackArg; - class TUse = MkModuleUse or MkModuleImport or MkUse or MkHasUnderlyingType; + class TUse = MkModuleUse or MkModuleImport or MkUse or MkTypeUse; private predicate hasSemantics(DataFlow::Node nd) { not nd.getTopLevel().isExterns() } @@ -584,7 +581,7 @@ module API { ) or exists(string moduleName, string exportName | - base = MkHasUnderlyingType(moduleName, exportName) and + base = MkTypeUse(moduleName, exportName) and lbl = Label::instance() and ref.(DataFlow::SourceNode).hasUnderlyingType(moduleName, exportName) ) @@ -823,7 +820,7 @@ module API { exists(string moduleName, string exportName | pred = MkModuleImport(moduleName) and lbl = Label::member(exportName) and - succ = MkHasUnderlyingType(moduleName, exportName) + succ = MkTypeUse(moduleName, exportName) ) or exists(DataFlow::Node nd, DataFlow::FunctionNode f | From cf5f838b13c6f8aa6f7eb7a24eed1752cc4d1a6f Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Mon, 12 Apr 2021 12:04:23 +0200 Subject: [PATCH 194/433] Data flow: Remove recommendation to use `unique` in `Node::getEnclosingCallable()` --- docs/ql-libraries/dataflow/dataflow.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/ql-libraries/dataflow/dataflow.md b/docs/ql-libraries/dataflow/dataflow.md index 3c46436a87a5..ea3379f3967b 100644 --- a/docs/ql-libraries/dataflow/dataflow.md +++ b/docs/ql-libraries/dataflow/dataflow.md @@ -98,10 +98,6 @@ Recommendations: also work, but the upside of `use-use` steps is that sources defined in terms of variable reads just work out of the box. It also makes certain barrier-implementations simpler. -* A predicate `DataFlowCallable Node::getEnclosingCallable()` is required, and in - order to ensure appropriate join-orders, it is important that the QL compiler knows - that this predicate is functional. It can therefore be necessary to enclose the body - of this predicate in a `unique` aggregate. The shared library does not use `localFlowStep` nor `localFlow` but users of `DataFlow.qll` may expect the existence of `DataFlow::localFlowStep` and From c7686b18388731b9a624b58bd36750863a672955 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Mon, 12 Apr 2021 13:01:14 +0200 Subject: [PATCH 195/433] C#: First try `pwsh` and then `powershell` when calling `dotnet-install.ps1` --- .../autobuilder/Semmle.Autobuild.CSharp/DotNetRule.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/csharp/autobuilder/Semmle.Autobuild.CSharp/DotNetRule.cs b/csharp/autobuilder/Semmle.Autobuild.CSharp/DotNetRule.cs index 8787406c104e..1b440aaa196c 100644 --- a/csharp/autobuilder/Semmle.Autobuild.CSharp/DotNetRule.cs +++ b/csharp/autobuilder/Semmle.Autobuild.CSharp/DotNetRule.cs @@ -209,8 +209,9 @@ public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certifi var psScriptFile = builder.Actions.PathCombine(builder.Options.RootDirectory, "install-dotnet.ps1"); builder.Actions.WriteAllText(psScriptFile, psScript); - var install = new CommandBuilder(builder.Actions). - RunCommand("powershell"). + BuildScript GetInstall(string pwsh) => + new CommandBuilder(builder.Actions). + RunCommand(pwsh). Argument("-NoProfile"). Argument("-ExecutionPolicy"). Argument("unrestricted"). @@ -219,13 +220,14 @@ public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certifi Argument("-Version"). Argument(version). Argument("-InstallDir"). - Argument(path); + Argument(path). + Script; var removeScript = new CommandBuilder(builder.Actions). RunCommand("del"). Argument(psScriptFile); - return install.Script & BuildScript.Try(removeScript.Script); + return (GetInstall("pwsh") | GetInstall("powershell")) & BuildScript.Try(removeScript.Script); } else { From 5446532e1d7de60cb4f05045fc6f37e2f323221b Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Mon, 12 Apr 2021 14:01:55 +0200 Subject: [PATCH 196/433] C#: Update auto-builder tests --- .../BuildScripts.cs | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs b/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs index 7b9f2e788811..f74e9829bc5b 100644 --- a/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs +++ b/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs @@ -981,7 +981,7 @@ public void TestDotnetVersionWindows() { actions.RunProcess["cmd.exe /C dotnet --list-sdks"] = 0; actions.RunProcessOut["cmd.exe /C dotnet --list-sdks"] = "2.1.3 [C:\\Program Files\\dotnet\\sdks]\n2.1.4 [C:\\Program Files\\dotnet\\sdks]"; - actions.RunProcess[@"cmd.exe /C powershell -NoProfile -ExecutionPolicy unrestricted -file C:\Project\install-dotnet.ps1 -Version 2.1.3 -InstallDir C:\Project\.dotnet"] = 0; + actions.RunProcess[@"cmd.exe /C pwsh -NoProfile -ExecutionPolicy unrestricted -file C:\Project\install-dotnet.ps1 -Version 2.1.3 -InstallDir C:\Project\.dotnet"] = 0; actions.RunProcess[@"cmd.exe /C del C:\Project\install-dotnet.ps1"] = 0; actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet --info"] = 0; actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet clean C:\Project\test.csproj"] = 0; @@ -1008,6 +1008,39 @@ public void TestDotnetVersionWindows() TestAutobuilderScript(autobuilder, 0, 7); } + [Fact] + public void TestDotnetVersionWindowsNoPwsh() + { + actions.RunProcess["cmd.exe /C dotnet --list-sdks"] = 0; + actions.RunProcessOut["cmd.exe /C dotnet --list-sdks"] = "2.1.3 [C:\\Program Files\\dotnet\\sdks]\n2.1.4 [C:\\Program Files\\dotnet\\sdks]"; + actions.RunProcess[@"cmd.exe /C pwsh -NoProfile -ExecutionPolicy unrestricted -file C:\Project\install-dotnet.ps1 -Version 2.1.3 -InstallDir C:\Project\.dotnet"] = 1; + actions.RunProcess[@"cmd.exe /C powershell -NoProfile -ExecutionPolicy unrestricted -file C:\Project\install-dotnet.ps1 -Version 2.1.3 -InstallDir C:\Project\.dotnet"] = 0; + actions.RunProcess[@"cmd.exe /C del C:\Project\install-dotnet.ps1"] = 0; + actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet --info"] = 0; + actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet clean C:\Project\test.csproj"] = 0; + actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet restore C:\Project\test.csproj"] = 0; + actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --auto C:\Project\.dotnet\dotnet build --no-incremental C:\Project\test.csproj"] = 0; + actions.FileExists["csharp.log"] = true; + actions.FileExists[@"C:\Project\test.csproj"] = true; + actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_TRAP_DIR"] = ""; + actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_SOURCE_ARCHIVE_DIR"] = ""; + actions.GetEnvironmentVariable["PATH"] = "/bin:/usr/bin"; + actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.cs\ntest.csproj"; + actions.EnumerateDirectories[@"C:\Project"] = ""; + var xml = new XmlDocument(); + xml.LoadXml(@" + + Exe + netcoreapp2.1 + + +"); + actions.LoadXml[@"C:\Project\test.csproj"] = xml; + + var autobuilder = CreateAutoBuilder(true, dotnetVersion: "2.1.3"); + TestAutobuilderScript(autobuilder, 0, 8); + } + [Fact] public void TestDirsProjWindows() { From b4d35b52c386072c1eb8561cf4e68cc87d292b87 Mon Sep 17 00:00:00 2001 From: Tamas Vajk Date: Tue, 2 Mar 2021 13:22:21 +0100 Subject: [PATCH 197/433] C#: Add Console.Read* to local flow sources --- .../csharp/security/dataflow/flowsources/Local.qll | 14 ++++++++++++++ .../CWE-134/ConsoleUncontrolledFormatString.cs | 13 +++++++++++++ .../CWE-134/UncontrolledFormatString.expected | 4 ++++ 3 files changed, 31 insertions(+) create mode 100644 csharp/ql/test/query-tests/Security Features/CWE-134/ConsoleUncontrolledFormatString.cs diff --git a/csharp/ql/src/semmle/code/csharp/security/dataflow/flowsources/Local.qll b/csharp/ql/src/semmle/code/csharp/security/dataflow/flowsources/Local.qll index e867c9be3ddc..b1b0bf8df850 100644 --- a/csharp/ql/src/semmle/code/csharp/security/dataflow/flowsources/Local.qll +++ b/csharp/ql/src/semmle/code/csharp/security/dataflow/flowsources/Local.qll @@ -20,3 +20,17 @@ class TextFieldSource extends LocalUserInputSource { override string getSourceType() { result = "TextBox text" } } + +/** A call to any `System.Console.Read*` method. */ +class SystemConsoleReadSource extends LocalUserInputSource { + SystemConsoleReadSource() { + this.asExpr() = + any(MethodCall call | + call.getTarget().hasQualifiedName("System.Console", "ReadLine") or + call.getTarget().hasQualifiedName("System.Console", "Read") or + call.getTarget().hasQualifiedName("System.Console", "ReadKey") + ) + } + + override string getSourceType() { result = "TextBox text" } +} diff --git a/csharp/ql/test/query-tests/Security Features/CWE-134/ConsoleUncontrolledFormatString.cs b/csharp/ql/test/query-tests/Security Features/CWE-134/ConsoleUncontrolledFormatString.cs new file mode 100644 index 000000000000..c9d4440cf787 --- /dev/null +++ b/csharp/ql/test/query-tests/Security Features/CWE-134/ConsoleUncontrolledFormatString.cs @@ -0,0 +1,13 @@ +using System; +using System; + +public class Program +{ + public static void Main() + { + var format = Console.ReadLine(); + + // BAD: Uncontrolled format string. + var x = string.Format(format, 1, 2); + } +} diff --git a/csharp/ql/test/query-tests/Security Features/CWE-134/UncontrolledFormatString.expected b/csharp/ql/test/query-tests/Security Features/CWE-134/UncontrolledFormatString.expected index bc87c96b194b..4923cd34e70d 100644 --- a/csharp/ql/test/query-tests/Security Features/CWE-134/UncontrolledFormatString.expected +++ b/csharp/ql/test/query-tests/Security Features/CWE-134/UncontrolledFormatString.expected @@ -1,8 +1,11 @@ edges +| ConsoleUncontrolledFormatString.cs:8:22:8:39 | call to method ReadLine : String | ConsoleUncontrolledFormatString.cs:11:31:11:36 | access to local variable format | | UncontrolledFormatString.cs:11:23:11:45 | access to property QueryString : NameValueCollection | UncontrolledFormatString.cs:14:23:14:26 | access to local variable path | | UncontrolledFormatString.cs:11:23:11:45 | access to property QueryString : NameValueCollection | UncontrolledFormatString.cs:17:46:17:49 | access to local variable path | | UncontrolledFormatStringBad.cs:9:25:9:47 | access to property QueryString : NameValueCollection | UncontrolledFormatStringBad.cs:12:39:12:44 | access to local variable format | nodes +| ConsoleUncontrolledFormatString.cs:8:22:8:39 | call to method ReadLine : String | semmle.label | call to method ReadLine : String | +| ConsoleUncontrolledFormatString.cs:11:31:11:36 | access to local variable format | semmle.label | access to local variable format | | UncontrolledFormatString.cs:11:23:11:45 | access to property QueryString : NameValueCollection | semmle.label | access to property QueryString : NameValueCollection | | UncontrolledFormatString.cs:14:23:14:26 | access to local variable path | semmle.label | access to local variable path | | UncontrolledFormatString.cs:17:46:17:49 | access to local variable path | semmle.label | access to local variable path | @@ -10,6 +13,7 @@ nodes | UncontrolledFormatStringBad.cs:9:25:9:47 | access to property QueryString : NameValueCollection | semmle.label | access to property QueryString : NameValueCollection | | UncontrolledFormatStringBad.cs:12:39:12:44 | access to local variable format | semmle.label | access to local variable format | #select +| ConsoleUncontrolledFormatString.cs:11:31:11:36 | access to local variable format | ConsoleUncontrolledFormatString.cs:8:22:8:39 | call to method ReadLine : String | ConsoleUncontrolledFormatString.cs:11:31:11:36 | access to local variable format | $@ flows to here and is used as a format string. | ConsoleUncontrolledFormatString.cs:8:22:8:39 | call to method ReadLine | call to method ReadLine | | UncontrolledFormatString.cs:14:23:14:26 | access to local variable path | UncontrolledFormatString.cs:11:23:11:45 | access to property QueryString : NameValueCollection | UncontrolledFormatString.cs:14:23:14:26 | access to local variable path | $@ flows to here and is used as a format string. | UncontrolledFormatString.cs:11:23:11:45 | access to property QueryString | access to property QueryString | | UncontrolledFormatString.cs:17:46:17:49 | access to local variable path | UncontrolledFormatString.cs:11:23:11:45 | access to property QueryString : NameValueCollection | UncontrolledFormatString.cs:17:46:17:49 | access to local variable path | $@ flows to here and is used as a format string. | UncontrolledFormatString.cs:11:23:11:45 | access to property QueryString | access to property QueryString | | UncontrolledFormatString.cs:34:23:34:31 | access to property Text | UncontrolledFormatString.cs:34:23:34:31 | access to property Text | UncontrolledFormatString.cs:34:23:34:31 | access to property Text | $@ flows to here and is used as a format string. | UncontrolledFormatString.cs:34:23:34:31 | access to property Text | access to property Text | From 57016ddbde804691b73bb62519bd6884a2a76d51 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Thu, 8 Apr 2021 10:10:41 +0200 Subject: [PATCH 198/433] C++: Remove `unique` wrapper from `DataFlow::Node::getEnclosingCallable()` --- .../src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll | 5 +++-- 1 file changed, 3 insertions(+), 2 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..c644c3dd2689 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll @@ -46,7 +46,7 @@ class Node extends TNode { /** * INTERNAL: Do not use. Alternative name for `getFunction`. */ - final Function getEnclosingCallable() { result = unique(Function f | f = this.getFunction() | f) } + final Function getEnclosingCallable() { result = this.getFunction() } /** Gets the type of this node. */ Type getType() { none() } // overridden in subclasses @@ -715,6 +715,7 @@ private predicate exprToDefinitionByReferenceStep(Expr exprIn, Expr argOut) { } private module FieldFlow { + private import DataFlowImplCommon private import DataFlowImplLocal private import DataFlowPrivate @@ -747,7 +748,7 @@ private module FieldFlow { exists(FieldConfiguration cfg | cfg.hasFlow(node1, node2)) and // This configuration should not be able to cross function boundaries, but // we double-check here just to be sure. - node1.getEnclosingCallable() = node2.getEnclosingCallable() + getNodeEnclosingCallable(node1) = getNodeEnclosingCallable(node2) } } From c281e54d22038c0d3dc28b6b344da4c954aa7747 Mon Sep 17 00:00:00 2001 From: luchua-bc Date: Mon, 12 Apr 2021 13:05:01 +0000 Subject: [PATCH 199/433] Remove unused files and update qldoc --- .../CWE-555/CredentialsInPropertiesFile.ql | 18 +------ .../security/CWE-555/MailConfig.java | 11 ----- .../security/CWE-555/PropertiesUtils.java | 47 ------------------- .../query-tests/security/CWE-555/options | 1 - .../beans/factory/annotation/Value.java | 11 ----- 5 files changed, 2 insertions(+), 86 deletions(-) delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-555/MailConfig.java delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-555/PropertiesUtils.java delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-555/options delete mode 100644 java/ql/test/stubs/springframework-5.2.3/org/springframework/beans/factory/annotation/Value.java 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 f085317218c0..d72743f37998 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 @@ -1,20 +1,6 @@ -/** - * @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#customizing-index) + * Note this is similar to src/experimental/Security/CWE/CWE-555/CredentialsInPropertiesFile.ql + * except we do not filter out test files. */ import java 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 deleted file mode 100644 index bef99dac3ea7..000000000000 --- a/java/ql/test/experimental/query-tests/security/CWE-555/MailConfig.java +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index f33ef39ee077..000000000000 --- a/java/ql/test/experimental/query-tests/security/CWE-555/PropertiesUtils.java +++ /dev/null @@ -1,47 +0,0 @@ -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 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/options b/java/ql/test/experimental/query-tests/security/CWE-555/options deleted file mode 100644 index cf3d60de0e5c..000000000000 --- a/java/ql/test/experimental/query-tests/security/CWE-555/options +++ /dev/null @@ -1 +0,0 @@ -// 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 deleted file mode 100644 index 67c38cf5d20b..000000000000 --- a/java/ql/test/stubs/springframework-5.2.3/org/springframework/beans/factory/annotation/Value.java +++ /dev/null @@ -1,11 +0,0 @@ -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 172d6139e206969bb2b3030910cae5bd3a19ed34 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Mon, 12 Apr 2021 15:06:10 +0200 Subject: [PATCH 200/433] support all ClientRequests in js/disabling-certificate-validation --- .../CWE-295/DisablingCertificateValidation.ql | 2 +- .../DisablingCertificateValidation.expected | 1 + .../test/query-tests/Security/CWE-295/tst2.js | 24 +++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 javascript/ql/test/query-tests/Security/CWE-295/tst2.js diff --git a/javascript/ql/src/Security/CWE-295/DisablingCertificateValidation.ql b/javascript/ql/src/Security/CWE-295/DisablingCertificateValidation.ql index 6855ec2519db..f18ff4a3535e 100644 --- a/javascript/ql/src/Security/CWE-295/DisablingCertificateValidation.ql +++ b/javascript/ql/src/Security/CWE-295/DisablingCertificateValidation.ql @@ -16,7 +16,7 @@ import javascript */ DataFlow::ObjectLiteralNode tlsOptions() { exists(DataFlow::InvokeNode invk | result.flowsTo(invk.getAnArgument()) | - invk instanceof NodeJSLib::NodeJSClientRequest + invk instanceof ClientRequest or invk = DataFlow::moduleMember("https", "Agent").getAnInstantiation() or diff --git a/javascript/ql/test/query-tests/Security/CWE-295/DisablingCertificateValidation.expected b/javascript/ql/test/query-tests/Security/CWE-295/DisablingCertificateValidation.expected index d028c5b3b30b..9b21e2f5d8bd 100644 --- a/javascript/ql/test/query-tests/Security/CWE-295/DisablingCertificateValidation.expected +++ b/javascript/ql/test/query-tests/Security/CWE-295/DisablingCertificateValidation.expected @@ -1,3 +1,4 @@ +| tst2.js:8:5:8:29 | rejectU ... : false | Disabling certificate validation is strongly discouraged. | | tst.js:15:3:15:27 | rejectU ... : false | Disabling certificate validation is strongly discouraged. | | tst.js:18:1:18:40 | process ... HORIZED | Disabling certificate validation is strongly discouraged. | | tst.js:21:3:21:27 | rejectU ... : false | Disabling certificate validation is strongly discouraged. | diff --git a/javascript/ql/test/query-tests/Security/CWE-295/tst2.js b/javascript/ql/test/query-tests/Security/CWE-295/tst2.js new file mode 100644 index 000000000000..50f5ae277146 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-295/tst2.js @@ -0,0 +1,24 @@ +const request = require('request'); + +let requestOptions = { + headers: { + "content-type": "application/json", + "accept": "application/json" + }, + rejectUnauthorized: false, + requestCert: true, + agent: false +} + +module.exports.post = (url, requestBody, apiContext) => { + Object.assign(requestOptions, { + body: JSON.stringify(requestBody), + headers : Object.assign(requestOptions.headers, apiContext) + }) + + return request.post(url, requestOptions).then((res) => { + return Promise.resolve(res.body); + }).catch((err) => { + return Promise.resolve(err); + }) +} \ No newline at end of file From 32737a17fb64fb1b4f57b8a4b65398607da39d30 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Mon, 12 Apr 2021 15:09:13 +0200 Subject: [PATCH 201/433] add change note --- .../change-notes/2021-04-12-disabling-certificate-validation.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 javascript/change-notes/2021-04-12-disabling-certificate-validation.md diff --git a/javascript/change-notes/2021-04-12-disabling-certificate-validation.md b/javascript/change-notes/2021-04-12-disabling-certificate-validation.md new file mode 100644 index 000000000000..e289c5f7276c --- /dev/null +++ b/javascript/change-notes/2021-04-12-disabling-certificate-validation.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* The query "Disabling certificate validation" (`js/disabling-certificate-validation`) has been improved to recognize many more request libraries. From 11bf9827289b1103ead2af9f3d7c0ccc30e235c1 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Mon, 12 Apr 2021 14:36:42 +0100 Subject: [PATCH 202/433] Remove superfluous linebreaks in qhelp file --- .../Security/CWE/CWE-094/InsecureDexLoading.qhelp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.qhelp b/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.qhelp index 4a5555c1390c..216bdeaebf38 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-094/InsecureDexLoading.qhelp @@ -32,13 +32,11 @@ which when loaded by the app can lead to code execution.
  • Android Documentation: - Data and file storage overview - . + Data and file storage overview.
  • Android Documentation: - DexClassLoader - . + DexClassLoader.
  • From d7c14775bf607f92ca473d34c79bbc540b8defbc Mon Sep 17 00:00:00 2001 From: ihsinme Date: Mon, 12 Apr 2021 16:56:48 +0300 Subject: [PATCH 203/433] Update InsufficientControlFlowManagementAfterRefactoringTheCode.qhelp --- ...sufficientControlFlowManagementAfterRefactoringTheCode.qhelp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.qhelp b/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.qhelp index 2c485dfd0cf4..4167ce57d65e 100644 --- a/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.qhelp +++ b/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.qhelp @@ -3,7 +3,7 @@ "qhelp.dtd"> -

    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.

    +

    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 () {...}` forgetting to remove the old construct completely, you get `while(){...}while();` which may be vulnerable. These code snippets look suspicious and require the developer's attention.

    From 58d5ad48d56fb7556b4aae4dd8e9a97946532bf8 Mon Sep 17 00:00:00 2001 From: ihsinme Date: Mon, 12 Apr 2021 17:00:34 +0300 Subject: [PATCH 204/433] Update InsufficientControlFlowManagementAfterRefactoringTheCode.ql --- ...nsufficientControlFlowManagementAfterRefactoringTheCode.ql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.ql b/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.ql index fa8780d6e450..dc5b2225a623 100644 --- a/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.ql +++ b/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.ql @@ -105,7 +105,7 @@ class UsingArithmeticInComparison extends BinaryArithmeticOperation { } } -from Expr exp, WhileStmt wst +from Expr exp where exp instanceof UsingArithmeticInComparison and not exp.(UsingArithmeticInComparison).workingWithValue() and @@ -114,5 +114,5 @@ where not exp.(UsingArithmeticInComparison).compareWithZero() and exp.(UsingArithmeticInComparison).compareWithOutZero() or - wst instanceof UsingWhileAfterWhile and exp = wst.getCondition() + exists(WhileStmt wst | wst instanceof UsingWhileAfterWhile and exp = wst.getCondition()) select exp, "this expression needs your attention" From 5aeaab7c6d7ff786c970b41f1bebce8c6040d838 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Mon, 12 Apr 2021 16:01:01 +0200 Subject: [PATCH 205/433] C++: As response to the review comments this commit adds a reference-to-pointer state to AddressFlow. A call to an unwrapper function now adds a pointer -> reference-to-pointer transition, and a ReferenceDereference adds a reference-to-pointer -> pointer transition. --- .../cpp/dataflow/internal/AddressFlow.qll | 105 +++++++++++++++--- .../dataflow/taint-tests/localTaint.expected | 49 ++++---- 2 files changed, 116 insertions(+), 38 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 7e76c1540e30..e179f7e8717b 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/AddressFlow.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/AddressFlow.qll @@ -59,8 +59,6 @@ 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) { @@ -69,6 +67,19 @@ private predicate lvalueToPointerStep(Expr lvalueIn, Expr pointerOut) { lvalueIn = pointerOut.(AddressOfExpr).getOperand().getFullyConverted() } +/** + * Since pointer wrappers behave as raw pointers, we treat the conversions from `lvalueToLvalueStepPure` + * as pointer-to-pointer steps when they involve pointer wrappers. + */ +private predicate pointerWrapperToPointerWrapperStep(Expr pointerIn, Expr pointerOut) { + pointerIn.getUnspecifiedType() instanceof PointerWrapper and + pointerIn.getConversion() = pointerOut and + pointerOut.(CStyleCast).isImplicit() + or + pointerOut.getUnspecifiedType() instanceof PointerWrapper and + pointerIn.getConversion() = pointerOut.(ReferenceDereferenceExpr) +} + private predicate pointerToPointerStep(Expr pointerIn, Expr pointerOut) { ( pointerOut instanceof PointerAddExpr @@ -93,35 +104,60 @@ private predicate pointerToPointerStep(Expr pointerIn, Expr pointerOut) { pointerIn = pointerOut.(CommaExpr).getRightOperand().getFullyConverted() or pointerIn = pointerOut.(StmtExpr).getResultExpr().getFullyConverted() + or + pointerWrapperToPointerWrapperStep(pointerIn, 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) { referenceIn.getConversion() = lvalueOut.(ReferenceDereferenceExpr) } +private predicate referenceToPointerToPointerStep(Expr referenceToPointerIn, Expr pointerOut) { + 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. + referenceToPointerIn = call.getArgument(0).getFullyConverted() + ) + or + referenceToPointerIn.getConversion() = pointerOut.(ReferenceDereferenceExpr) +} + +/** + * This predicate exists only to support "fake pointer" objects like + * smart pointers. We treat these as raw pointers for dataflow purposes. + */ +private predicate referenceToPointerToUpdate( + Expr referenceToPointer, Expr outer, ControlFlowNode node +) { + exists(Call call | + node = call and + outer = call.getAnArgument().getFullyConverted() and + not stdIdentityFunction(call.getTarget()) and + not stdAddressOf(call.getTarget()) and + exists(ReferenceType rt | rt = outer.getType().stripTopLevelSpecifiers() | + rt.getBaseType().getUnspecifiedType() = + any(PointerWrapper wrapper | not wrapper.pointsToConst()) + ) + ) and + referenceToPointer = outer + or + exists(Expr pointerMid | + referenceToPointerToPointerStep(referenceToPointer, pointerMid) and + pointerToUpdate(pointerMid, outer, node) + ) +} + private predicate referenceToPointerStep(Expr referenceIn, Expr pointerOut) { pointerOut = any(FunctionCall call | 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) { @@ -238,6 +274,16 @@ private predicate pointerToUpdate(Expr pointer, Expr outer, ControlFlowNode node pointerToPointerStep(pointer, pointerMid) and pointerToUpdate(pointerMid, outer, node) ) + or + exists(Expr referenceMid | + pointerToReferenceStep(pointer, referenceMid) and + referenceToUpdate(referenceMid, outer, node) + ) + or + exists(Expr referenceToPointerMid | + pointerToReferenceToPointerStep(pointer, referenceToPointerMid) and + referenceToPointerToUpdate(referenceToPointerMid, outer, node) + ) } private predicate referenceToUpdate(Expr reference, Expr outer, ControlFlowNode node) { @@ -247,9 +293,7 @@ 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() or - rt.getBaseType().getUnspecifiedType() = - any(PointerWrapper wrapper | not wrapper.pointsToConst()) + not rt.getBaseType().isConst() ) ) and reference = outer @@ -270,6 +314,14 @@ private predicate referenceToUpdate(Expr reference, Expr outer, ControlFlowNode ) } +private predicate pointerToReferenceStep(Expr pointerIn, Expr referenceOut) { + exists(PointerWrapper wrapper, Call call | call = referenceOut | + referenceOut.getUnspecifiedType() instanceof ReferenceType and + call = wrapper.getAnUnwrapperFunction().getACallToThisFunction() and + pointerIn = call.getQualifier().getFullyConverted() + ) +} + private predicate lvalueFromVariableAccess(VariableAccess va, Expr lvalue) { // Base case for non-reference types. lvalue = va and @@ -331,6 +383,21 @@ private predicate referenceFromVariableAccess(VariableAccess va, Expr reference) lvalueFromVariableAccess(va, prev) and lvalueToReferenceStep(prev, reference) ) + or + exists(Expr prev | + pointerFromVariableAccess(va, prev) and + pointerToReferenceStep(prev, reference) + ) +} + +private predicate pointerToReferenceToPointerStep(Expr pointerIn, Expr referenceToPointerOut) { + pointerIn.getConversion() = referenceToPointerOut.(ReferenceToExpr) + or + exists(PointerWrapper wrapper, Call call | call = referenceToPointerOut | + referenceToPointerOut.getUnspecifiedType() instanceof ReferenceType and + call = wrapper.getAnUnwrapperFunction().getACallToThisFunction() and + pointerIn = call.getQualifier().getFullyConverted() + ) } /** @@ -351,6 +418,8 @@ predicate valueToUpdate(Expr inner, Expr outer, ControlFlowNode node) { pointerToUpdate(inner, outer, node) or referenceToUpdate(inner, outer, node) + or + referenceToPointerToUpdate(inner, outer, node) ) and ( inner instanceof VariableAccess and 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 50d5070a1442..90a494de6f83 100644 --- a/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected +++ b/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected @@ -948,12 +948,16 @@ | map.cpp:170:23:170:28 | call to source | map.cpp:170:7:170:30 | ... = ... | | | map.cpp:171:7:171:9 | m10 | map.cpp:171:10:171:10 | call to operator[] | TAINT | | map.cpp:171:7:171:9 | ref arg m10 | map.cpp:252:1:252:1 | m10 | | +| map.cpp:171:10:171:10 | ref arg call to operator[] | map.cpp:171:7:171:9 | ref arg m10 | TAINT | | map.cpp:172:7:172:9 | m11 | map.cpp:172:10:172:10 | call to operator[] | TAINT | | map.cpp:172:7:172:9 | ref arg m11 | map.cpp:252:1:252:1 | m11 | | +| map.cpp:172:10:172:10 | ref arg call to operator[] | map.cpp:172:7:172:9 | ref arg m11 | TAINT | | map.cpp:173:7:173:9 | m12 | map.cpp:173:10:173:10 | call to operator[] | TAINT | | map.cpp:173:7:173:9 | ref arg m12 | map.cpp:252:1:252:1 | m12 | | +| map.cpp:173:10:173:10 | ref arg call to operator[] | map.cpp:173:7:173:9 | ref arg m12 | TAINT | | map.cpp:174:7:174:9 | m13 | map.cpp:174:10:174:10 | call to operator[] | TAINT | | map.cpp:174:7:174:9 | ref arg m13 | map.cpp:252:1:252:1 | m13 | | +| map.cpp:174:10:174:10 | ref arg call to operator[] | map.cpp:174:7:174:9 | ref arg m13 | TAINT | | map.cpp:177:27:177:29 | call to map | map.cpp:178:2:178:4 | m14 | | | map.cpp:177:27:177:29 | call to map | map.cpp:179:2:179:4 | m14 | | | map.cpp:177:27:177:29 | call to map | map.cpp:180:2:180:4 | m14 | | @@ -1651,12 +1655,16 @@ | map.cpp:322:23:322:28 | call to source | map.cpp:322:7:322:30 | ... = ... | | | map.cpp:323:7:323:9 | m10 | map.cpp:323:10:323:10 | call to operator[] | TAINT | | map.cpp:323:7:323:9 | ref arg m10 | map.cpp:438:1:438:1 | m10 | | +| map.cpp:323:10:323:10 | ref arg call to operator[] | map.cpp:323:7:323:9 | ref arg m10 | TAINT | | map.cpp:324:7:324:9 | m11 | map.cpp:324:10:324:10 | call to operator[] | TAINT | | map.cpp:324:7:324:9 | ref arg m11 | map.cpp:438:1:438:1 | m11 | | +| map.cpp:324:10:324:10 | ref arg call to operator[] | map.cpp:324:7:324:9 | ref arg m11 | TAINT | | map.cpp:325:7:325:9 | m12 | map.cpp:325:10:325:10 | call to operator[] | TAINT | | map.cpp:325:7:325:9 | ref arg m12 | map.cpp:438:1:438:1 | m12 | | +| map.cpp:325:10:325:10 | ref arg call to operator[] | map.cpp:325:7:325:9 | ref arg m12 | TAINT | | map.cpp:326:7:326:9 | m13 | map.cpp:326:10:326:10 | call to operator[] | TAINT | | map.cpp:326:7:326:9 | ref arg m13 | map.cpp:438:1:438:1 | m13 | | +| map.cpp:326:10:326:10 | ref arg call to operator[] | map.cpp:326:7:326:9 | ref arg m13 | TAINT | | map.cpp:329:37:329:39 | call to unordered_map | map.cpp:330:2:330:4 | m14 | | | map.cpp:329:37:329:39 | call to unordered_map | map.cpp:331:2:331:4 | m14 | | | map.cpp:329:37:329:39 | call to unordered_map | map.cpp:332:2:332:4 | m14 | | @@ -2447,6 +2455,9 @@ | set.cpp:55:40:55:41 | ref arg i1 | set.cpp:55:24:55:25 | i1 | | | set.cpp:55:40:55:41 | ref arg i1 | set.cpp:55:40:55:41 | i1 | | | set.cpp:55:40:55:41 | ref arg i1 | set.cpp:57:9:57:10 | i1 | | +| set.cpp:57:8:57:8 | ref arg call to operator* | set.cpp:55:30:55:31 | s1 | | +| set.cpp:57:8:57:8 | ref arg call to operator* | set.cpp:57:9:57:10 | ref arg i1 | TAINT | +| set.cpp:57:8:57:8 | ref arg call to operator* | set.cpp:126:1:126:1 | s1 | | | set.cpp:57:9:57:10 | i1 | set.cpp:57:8:57:8 | call to operator* | TAINT | | set.cpp:57:9:57:10 | ref arg i1 | set.cpp:55:24:55:25 | i1 | | | set.cpp:57:9:57:10 | ref arg i1 | set.cpp:55:40:55:41 | i1 | | @@ -2465,6 +2476,9 @@ | set.cpp:59:40:59:41 | ref arg i2 | set.cpp:59:24:59:25 | i2 | | | set.cpp:59:40:59:41 | ref arg i2 | set.cpp:59:40:59:41 | i2 | | | set.cpp:59:40:59:41 | ref arg i2 | set.cpp:61:9:61:10 | i2 | | +| set.cpp:61:8:61:8 | ref arg call to operator* | set.cpp:59:30:59:31 | s2 | | +| set.cpp:61:8:61:8 | ref arg call to operator* | set.cpp:61:9:61:10 | ref arg i2 | TAINT | +| set.cpp:61:8:61:8 | ref arg call to operator* | set.cpp:126:1:126:1 | s2 | | | set.cpp:61:9:61:10 | i2 | set.cpp:61:8:61:8 | call to operator* | TAINT | | set.cpp:61:9:61:10 | ref arg i2 | set.cpp:59:24:59:25 | i2 | | | set.cpp:61:9:61:10 | ref arg i2 | set.cpp:59:40:59:41 | i2 | | @@ -2951,6 +2965,9 @@ | set.cpp:169:40:169:41 | ref arg i1 | set.cpp:169:24:169:25 | i1 | | | set.cpp:169:40:169:41 | ref arg i1 | set.cpp:169:40:169:41 | i1 | | | set.cpp:169:40:169:41 | ref arg i1 | set.cpp:171:9:171:10 | i1 | | +| set.cpp:171:8:171:8 | ref arg call to operator* | set.cpp:169:30:169:31 | s1 | | +| set.cpp:171:8:171:8 | ref arg call to operator* | set.cpp:171:9:171:10 | ref arg i1 | TAINT | +| set.cpp:171:8:171:8 | ref arg call to operator* | set.cpp:238:1:238:1 | s1 | | | set.cpp:171:9:171:10 | i1 | set.cpp:171:8:171:8 | call to operator* | TAINT | | set.cpp:171:9:171:10 | ref arg i1 | set.cpp:169:24:169:25 | i1 | | | set.cpp:171:9:171:10 | ref arg i1 | set.cpp:169:40:169:41 | i1 | | @@ -2969,6 +2986,9 @@ | set.cpp:173:40:173:41 | ref arg i2 | set.cpp:173:24:173:25 | i2 | | | set.cpp:173:40:173:41 | ref arg i2 | set.cpp:173:40:173:41 | i2 | | | set.cpp:173:40:173:41 | ref arg i2 | set.cpp:175:9:175:10 | i2 | | +| set.cpp:175:8:175:8 | ref arg call to operator* | set.cpp:173:30:173:31 | s2 | | +| set.cpp:175:8:175:8 | ref arg call to operator* | set.cpp:175:9:175:10 | ref arg i2 | TAINT | +| set.cpp:175:8:175:8 | ref arg call to operator* | set.cpp:238:1:238:1 | s2 | | | set.cpp:175:9:175:10 | i2 | set.cpp:175:8:175:8 | call to operator* | TAINT | | set.cpp:175:9:175:10 | ref arg i2 | set.cpp:173:24:173:25 | i2 | | | set.cpp:175:9:175:10 | ref arg i2 | set.cpp:173:40:173:41 | i2 | | @@ -3223,7 +3243,7 @@ | 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* | | +| 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 | | @@ -3235,7 +3255,7 @@ | 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* | | +| 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 | | @@ -3251,13 +3271,13 @@ | 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* | | +| 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: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* | | +| smart_pointer.cpp:39:11:39:11 | p | smart_pointer.cpp:39:10:39:10 | call to operator* | TAINT | | 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 | | @@ -3265,13 +3285,13 @@ | 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* | | +| 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: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* | | +| smart_pointer.cpp:47:11:47:11 | p | smart_pointer.cpp:47:10:47:10 | call to operator* | TAINT | | 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 | | @@ -3292,7 +3312,7 @@ | 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* | | +| 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: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 | ... = ... | | @@ -3303,7 +3323,7 @@ | 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:77:9:77:9 | p | smart_pointer.cpp:77:8:77:8 | call to operator* | TAINT | | 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 | | @@ -3372,7 +3392,7 @@ | 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 | ptr | smart_pointer.cpp:121:3:121:3 | call to operator* | TAINT | | 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 | ... = ... | | @@ -3409,7 +3429,6 @@ | 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] | | @@ -3437,7 +3456,6 @@ | 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] | | @@ -3476,26 +3494,20 @@ | 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 | | @@ -3513,13 +3525,10 @@ | 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 | | From a43698802f669f94b74aedd4d4c22d60ef232263 Mon Sep 17 00:00:00 2001 From: ihsinme Date: Mon, 12 Apr 2021 17:36:50 +0300 Subject: [PATCH 206/433] Update InsufficientControlFlowManagementWhenUsingBitOperations.ql --- .../InsufficientControlFlowManagementWhenUsingBitOperations.ql | 3 +-- 1 file changed, 1 insertion(+), 2 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 8a7a3fff1d02..72d7625b5170 100644 --- a/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementWhenUsingBitOperations.ql +++ b/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementWhenUsingBitOperations.ql @@ -61,8 +61,7 @@ class DangerousBitOperations extends BinaryBitwiseOperation { /** Holds when the bit expression contains both arguments and a function call. */ predicate dangerousArgumentChecking() { not this.getLeftOperand() instanceof Call and - globalValueNumber(this.getLeftOperand().getAChild*()) = - globalValueNumber(bfc.getAnArgument()) + globalValueNumber(this.getLeftOperand().getAChild*()) = globalValueNumber(bfc.getAnArgument()) } /** Holds when function calls are present in the bit expression. */ From bb23866cec93bda0bf1e132facd78344fb7a7fe4 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Mon, 12 Apr 2021 16:33:01 +0100 Subject: [PATCH 207/433] Add missing doc comments --- .../Security/CWE/CWE-016/InsecureSpringActuatorConfig.ql | 4 ++++ 1 file changed, 4 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 772ac6cd2091..e6965959d13f 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-016/InsecureSpringActuatorConfig.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-016/InsecureSpringActuatorConfig.ql @@ -52,10 +52,13 @@ class ApplicationProperties extends ConfigPair { class ManagementSecurityConfig extends ApplicationProperties { ManagementSecurityConfig() { this.getNameElement().getName() = "management.security.enabled" } + /** Gets the whitespace-trimmed value of this property. */ string getValue() { result = this.getValueElement().getValue().trim() } + /** Holds if `management.security.enabled` is set to `false`. */ predicate hasSecurityDisabled() { getValue() = "false" } + /** Holds if `management.security.enabled` is set to `true`. */ predicate hasSecurityEnabled() { getValue() = "true" } } @@ -65,6 +68,7 @@ class ManagementEndPointInclude extends ApplicationProperties { this.getNameElement().getName() = "management.endpoints.web.exposure.include" } + /** Gets the whitespace-trimmed value of this property. */ string getValue() { result = this.getValueElement().getValue().trim() } } From dfc91b8331199ff8780aeb8b0a1ef5c14d06a6ba Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Mon, 12 Apr 2021 17:33:33 +0200 Subject: [PATCH 208/433] C#: Simplify `dotnet-install.ps1` invocation Using the pattern from https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-install-script. --- .../BuildScripts.cs | 12 +++--- .../Semmle.Autobuild.CSharp/DotNetRule.cs | 43 ++----------------- 2 files changed, 9 insertions(+), 46 deletions(-) diff --git a/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs b/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs index f74e9829bc5b..9334181fe5a6 100644 --- a/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs +++ b/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs @@ -981,8 +981,7 @@ public void TestDotnetVersionWindows() { actions.RunProcess["cmd.exe /C dotnet --list-sdks"] = 0; actions.RunProcessOut["cmd.exe /C dotnet --list-sdks"] = "2.1.3 [C:\\Program Files\\dotnet\\sdks]\n2.1.4 [C:\\Program Files\\dotnet\\sdks]"; - actions.RunProcess[@"cmd.exe /C pwsh -NoProfile -ExecutionPolicy unrestricted -file C:\Project\install-dotnet.ps1 -Version 2.1.3 -InstallDir C:\Project\.dotnet"] = 0; - actions.RunProcess[@"cmd.exe /C del C:\Project\install-dotnet.ps1"] = 0; + actions.RunProcess[@"cmd.exe /C pwsh -NoProfile -ExecutionPolicy unrestricted -Command ""[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; &([scriptblock]::Create((Invoke-WebRequest -UseBasicParsing 'https://dot.net/v1/dotnet-install.ps1'))) -Version 2.1.3 -InstallDir C:\Project\.dotnet"""] = 0; actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet --info"] = 0; actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet clean C:\Project\test.csproj"] = 0; actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet restore C:\Project\test.csproj"] = 0; @@ -1005,7 +1004,7 @@ public void TestDotnetVersionWindows() actions.LoadXml[@"C:\Project\test.csproj"] = xml; var autobuilder = CreateAutoBuilder(true, dotnetVersion: "2.1.3"); - TestAutobuilderScript(autobuilder, 0, 7); + TestAutobuilderScript(autobuilder, 0, 6); } [Fact] @@ -1013,9 +1012,8 @@ public void TestDotnetVersionWindowsNoPwsh() { actions.RunProcess["cmd.exe /C dotnet --list-sdks"] = 0; actions.RunProcessOut["cmd.exe /C dotnet --list-sdks"] = "2.1.3 [C:\\Program Files\\dotnet\\sdks]\n2.1.4 [C:\\Program Files\\dotnet\\sdks]"; - actions.RunProcess[@"cmd.exe /C pwsh -NoProfile -ExecutionPolicy unrestricted -file C:\Project\install-dotnet.ps1 -Version 2.1.3 -InstallDir C:\Project\.dotnet"] = 1; - actions.RunProcess[@"cmd.exe /C powershell -NoProfile -ExecutionPolicy unrestricted -file C:\Project\install-dotnet.ps1 -Version 2.1.3 -InstallDir C:\Project\.dotnet"] = 0; - actions.RunProcess[@"cmd.exe /C del C:\Project\install-dotnet.ps1"] = 0; + actions.RunProcess[@"cmd.exe /C pwsh -NoProfile -ExecutionPolicy unrestricted -Command ""[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; &([scriptblock]::Create((Invoke-WebRequest -UseBasicParsing 'https://dot.net/v1/dotnet-install.ps1'))) -Version 2.1.3 -InstallDir C:\Project\.dotnet"""] = 1; + actions.RunProcess[@"cmd.exe /C powershell -NoProfile -ExecutionPolicy unrestricted -Command ""[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; &([scriptblock]::Create((Invoke-WebRequest -UseBasicParsing 'https://dot.net/v1/dotnet-install.ps1'))) -Version 2.1.3 -InstallDir C:\Project\.dotnet"""] = 0; actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet --info"] = 0; actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet clean C:\Project\test.csproj"] = 0; actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet restore C:\Project\test.csproj"] = 0; @@ -1038,7 +1036,7 @@ public void TestDotnetVersionWindowsNoPwsh() actions.LoadXml[@"C:\Project\test.csproj"] = xml; var autobuilder = CreateAutoBuilder(true, dotnetVersion: "2.1.3"); - TestAutobuilderScript(autobuilder, 0, 8); + TestAutobuilderScript(autobuilder, 0, 7); } [Fact] diff --git a/csharp/autobuilder/Semmle.Autobuild.CSharp/DotNetRule.cs b/csharp/autobuilder/Semmle.Autobuild.CSharp/DotNetRule.cs index 1b440aaa196c..a456c9407db6 100644 --- a/csharp/autobuilder/Semmle.Autobuild.CSharp/DotNetRule.cs +++ b/csharp/autobuilder/Semmle.Autobuild.CSharp/DotNetRule.cs @@ -179,35 +179,8 @@ private static BuildScript DownloadDotNetVersion(Autobuilder builder, string pat if (builder.Actions.IsWindows()) { - var psScript = @"param([string]$Version, [string]$InstallDir) -add-type @"" -using System.Net; -using System.Security.Cryptography.X509Certificates; -public class TrustAllCertsPolicy : ICertificatePolicy -{ - public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) - { - return true; - } -} -""@ -$AllProtocols = [System.Net.SecurityProtocolType]'Ssl3,Tls,Tls11,Tls12' -[System.Net.ServicePointManager]::SecurityProtocol = $AllProtocols -[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy -$Script = Invoke-WebRequest -useb 'https://dot.net/v1/dotnet-install.ps1' - -$arguments = @{ - Channel = 'release' - Version = $Version - InstallDir = $InstallDir -} - -$ScriptBlock = [scriptblock]::create("".{$($Script)} $(&{$args} @arguments)"") - -Invoke-Command -ScriptBlock $ScriptBlock"; - var psScriptFile = builder.Actions.PathCombine(builder.Options.RootDirectory, "install-dotnet.ps1"); - builder.Actions.WriteAllText(psScriptFile, psScript); + var psCommand = $"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; &([scriptblock]::Create((Invoke-WebRequest -UseBasicParsing 'https://dot.net/v1/dotnet-install.ps1'))) -Version {version} -InstallDir {path}"; BuildScript GetInstall(string pwsh) => new CommandBuilder(builder.Actions). @@ -215,19 +188,11 @@ BuildScript GetInstall(string pwsh) => Argument("-NoProfile"). Argument("-ExecutionPolicy"). Argument("unrestricted"). - Argument("-file"). - Argument(psScriptFile). - Argument("-Version"). - Argument(version). - Argument("-InstallDir"). - Argument(path). + Argument("-Command"). + Argument("\"" + psCommand + "\""). Script; - var removeScript = new CommandBuilder(builder.Actions). - RunCommand("del"). - Argument(psScriptFile); - - return (GetInstall("pwsh") | GetInstall("powershell")) & BuildScript.Try(removeScript.Script); + return GetInstall("pwsh") | GetInstall("powershell"); } else { From d7f26dfc183f3e8c1a220c1aa7d7092f9bfb09e1 Mon Sep 17 00:00:00 2001 From: luchua-bc Date: Mon, 12 Apr 2021 16:19:23 +0000 Subject: [PATCH 209/433] Update stub classes and qldoc --- .../CWE-1004/SensitiveCookieNotHttpOnly.ql | 14 ++--- .../CWE-1004/SensitiveCookieNotHttpOnly.java | 2 +- .../javax/ws/rs/core/Cookie.java | 10 +-- .../javax/servlet/http/Cookie.java | 62 +++---------------- 4 files changed, 22 insertions(+), 66 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 cb8f7412014e..4c0dc624d078 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-1004/SensitiveCookieNotHttpOnly.ql @@ -82,8 +82,8 @@ 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 +/** Holds if `expr` is any boolean-typed expression other than literal `false`. */ +// Inlined because this could be a very large result set if computed out of context pragma[inline] predicate mayBeBooleanTrue(Expr expr) { expr.getType() instanceof BooleanType and @@ -123,11 +123,11 @@ predicate isTestMethod(MethodAccess ma) { } /** - * 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. + * A taint configuration tracking flow of a 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" } +class SetHttpOnlyOrRemovesCookieConfiguration extends TaintTracking2::Configuration { + SetHttpOnlyOrRemovesCookieConfiguration() { this = "SetHttpOnlyOrRemovesCookieConfiguration" } override predicate isSource(DataFlow::Node source) { source.asExpr() = @@ -150,7 +150,7 @@ class CookieResponseSink extends DataFlow::ExprNode { ( ma.getMethod() instanceof ResponseAddCookieMethod and this.getExpr() = ma.getArgument(0) and - not exists(SetHttpOnlyInCookieConfiguration cc | cc.hasFlowTo(this)) + not exists(SetHttpOnlyOrRemovesCookieConfiguration cc | cc.hasFlowTo(this)) or ma instanceof SetCookieMethodAccess and this.getExpr() = ma.getArgument(1) and 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 0bb912f6ce67..4ad7aa5dd95e 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 @@ -137,7 +137,7 @@ public void addCookie14(HttpServletRequest request, HttpServletResponse response 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. + // GOOD - Tests set a sensitive cookie header with the `HttpOnly` flag not set through a boolean variable using a wrapper method. public void addCookie15(HttpServletRequest request, HttpServletResponse response, String refreshToken) { response.addCookie(createCookie("refresh_token", refreshToken, false)); } 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 f810600a7660..2726c3d27dda 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 @@ -125,7 +125,7 @@ public static Cookie valueOf(final String value) { * @return the cookie name. */ public String getName() { - return name; + return null; } /** @@ -134,7 +134,7 @@ public String getName() { * @return the cookie value. */ public String getValue() { - return value; + return null; } /** @@ -143,7 +143,7 @@ public String getValue() { * @return the cookie version. */ public int getVersion() { - return version; + return -1; } /** @@ -152,7 +152,7 @@ public int getVersion() { * @return the cookie domain. */ public String getDomain() { - return domain; + return null; } /** @@ -161,6 +161,6 @@ public String getDomain() { * @return the cookie path. */ public String getPath() { - return path; + return null; } } 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 47b1d883e474..53d3058f9218 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 @@ -24,95 +24,51 @@ package javax.servlet.http; public class Cookie implements Cloneable { - private String name; // NAME= ... "$Name" style is reserved - private String value; // value of NAME - private String comment; // ;Comment=VALUE ... describes cookie's use - private String domain; // ;Domain=VALUE ... domain that sees cookie - private int maxAge = -1; // ;Max-Age=VALUE ... cookies auto-expire - 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; - this.value = value; } public void setComment(String purpose) { - comment = purpose; } public String getComment() { - return comment; + return null; } public void setDomain(String pattern) { - domain = pattern.toLowerCase(); // IE allegedly needs this } public String getDomain() { - return domain; + return null; } public void setMaxAge(int expiry) { - maxAge = expiry; } public int getMaxAge() { - return maxAge; + return -1; } public void setPath(String uri) { - path = uri; } public String getPath() { - return path; + return null; } public void setSecure(boolean flag) { - secure = flag; } public boolean getSecure() { - return secure; + return false; } public String getName() { - return name; + return null; } public void setValue(String newValue) { - value = newValue; } public String getValue() { - return value; + return null; } public int getVersion() { - return version; + return -1; } 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; + return false; } /** From 037e6369ce657381a15fe2df4ea0b09ed24d9802 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Mon, 12 Apr 2021 18:27:21 +0200 Subject: [PATCH 210/433] C++: Ensure all values are bound in both disjunctions. --- .../dataflow/fields/partial-definition-diff.ql | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/cpp/ql/test/library-tests/dataflow/fields/partial-definition-diff.ql b/cpp/ql/test/library-tests/dataflow/fields/partial-definition-diff.ql index d8b6b4e0e69a..fae4b06da5ad 100644 --- a/cpp/ql/test/library-tests/dataflow/fields/partial-definition-diff.ql +++ b/cpp/ql/test/library-tests/dataflow/fields/partial-definition-diff.ql @@ -19,15 +19,19 @@ class IRPartialDefNode extends IRNode { override string toString() { result = n.asPartialDefinition().toString() } } -from Node node, AST::Node astNode, IR::Node irNode, string msg +from Node node, string msg where - node.asIR() = irNode and - exists(irNode.asPartialDefinition()) and - not exists(AST::Node otherNode | otherNode.asPartialDefinition() = irNode.asPartialDefinition()) and + exists(IR::Node irNode, Expr partial | + node.asIR() = irNode and + partial = irNode.asPartialDefinition() and + not exists(AST::Node otherNode | otherNode.asPartialDefinition() = partial) + ) and msg = "IR only" or - node.asAST() = astNode and - exists(astNode.asPartialDefinition()) and - not exists(IR::Node otherNode | otherNode.asPartialDefinition() = astNode.asPartialDefinition()) and + exists(AST::Node astNode, Expr partial | + node.asAST() = astNode and + partial = astNode.asPartialDefinition() and + not exists(IR::Node otherNode | otherNode.asPartialDefinition() = partial) + ) and msg = "AST only" select node, msg From b96b6652626cbeb232748ac782130b884335725d Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Mon, 12 Apr 2021 21:40:49 +0300 Subject: [PATCH 211/433] Renaming in java/ql/src/experimental/Security/CWE/CWE-094 --- .../CWE/CWE-094/{InjectionLib.qll => FlowUtils.qll} | 2 +- .../Security/CWE/CWE-094/JakartaExpressionInjection.qhelp | 6 +++--- .../Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll | 4 ++-- .../experimental/Security/CWE/CWE-094/JexlInjectionLib.qll | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) rename java/ql/src/experimental/Security/CWE/CWE-094/{InjectionLib.qll => FlowUtils.qll} (81%) diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/InjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/FlowUtils.qll similarity index 81% rename from java/ql/src/experimental/Security/CWE/CWE-094/InjectionLib.qll rename to java/ql/src/experimental/Security/CWE/CWE-094/FlowUtils.qll index adab79d6f5c3..5371c51aa8f0 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-094/InjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-094/FlowUtils.qll @@ -5,7 +5,7 @@ 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) { +predicate hasGetterFlow(DataFlow::Node fromNode, DataFlow::Node toNode) { exists(MethodAccess ma, Method m | ma.getMethod() = m | m instanceof GetterMethod and ma.getQualifier() = fromNode.asExpr() and 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 a6d66c1d1d16..a8d3cd0fe70e 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjection.qhelp @@ -29,14 +29,14 @@ with sandboxing capabilities such as Apache Commons JEXL or the Spring Expressio The following example shows how untrusted data is used to build and run an expression using the JUEL interpreter:

    - +

    -JUEL does not support to run expressions in a sandbox. To prevent running arbitrary code, +JUEL does not support running 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/JakartaExpressionInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll index b1c5d1e8ae20..430909743647 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-094/JakartaExpressionInjectionLib.qll @@ -1,5 +1,5 @@ import java -import InjectionLib +import FlowUtils import semmle.code.java.dataflow.FlowSources import semmle.code.java.dataflow.TaintTracking @@ -16,7 +16,7 @@ class JakartaExpressionInjectionConfig extends TaintTracking::Configuration { override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) { any(TaintPropagatingCall c).taintFlow(fromNode, toNode) or - returnsDataFromBean(fromNode, toNode) + hasGetterFlow(fromNode, toNode) } } 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 51084a9862ce..89d7cb496a41 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll @@ -1,5 +1,5 @@ import java -import InjectionLib +import FlowUtils import semmle.code.java.dataflow.FlowSources import semmle.code.java.dataflow.TaintTracking @@ -17,7 +17,7 @@ class JexlInjectionConfig extends TaintTracking::Configuration { override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) { any(TaintPropagatingJexlMethodCall c).taintFlow(fromNode, toNode) or - returnsDataFromBean(fromNode, toNode) + hasGetterFlow(fromNode, toNode) } } From e0fcb1573987e27b831aa294f3c0b368d019bc3f Mon Sep 17 00:00:00 2001 From: Andrew Eisenberg Date: Mon, 12 Apr 2021 11:49:07 -0700 Subject: [PATCH 212/433] Actions: Add workflow for marking stale questions This PR adds a workflow for marking and closing issues as stale. Issues must be labeled as _question_. PRs are never marked as stale. --- .github/workflows/close-stale.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/close-stale.yml diff --git a/.github/workflows/close-stale.yml b/.github/workflows/close-stale.yml new file mode 100644 index 000000000000..6065f79aa7c1 --- /dev/null +++ b/.github/workflows/close-stale.yml @@ -0,0 +1,30 @@ +name: Mark stale issues + +on: + workflow_dispatch: + schedule: + - cron: "30 1 * * *" + +jobs: + stale: + if: github.repository == 'github/codeql' + + runs-on: ubuntu-latest + + steps: + - uses: actions/stale@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: 'This issue is stale because it has been open 14 days with no activity. Comment or remove the `stale` label in order to avoid having this issue closed in 7 days.' + close-issue-message: 'This issue was closed because it has been inactive for 7 days.' + days-before-stale: 14 + days-before-close: 7 + only-labels: question + + # do not mark PRs as stale + days-before-pr-stale: -1 + days-before-pr-close: -1 + + # Dry-run only. remove this when we are ready to actually change issues + debug-only: true + operations-per-run: 1000 From e4d74cf0986c59a3cec6f09687e81da7c4f7f0ed Mon Sep 17 00:00:00 2001 From: yoff Date: Mon, 12 Apr 2021 23:47:54 +0200 Subject: [PATCH 213/433] Apply suggestions from code review Co-authored-by: Rasmus Wriedt Larsen --- .../src/Security/CWE-327/FluentApiModel.qll | 7 ++----- python/ql/src/Security/CWE-327/PyOpenSSL.qll | 10 +++++++--- python/ql/src/Security/CWE-327/Ssl.qll | 18 ++++++++++------- .../src/Security/CWE-327/TlsLibraryModel.qll | 20 ++++++++++--------- 4 files changed, 31 insertions(+), 24 deletions(-) diff --git a/python/ql/src/Security/CWE-327/FluentApiModel.qll b/python/ql/src/Security/CWE-327/FluentApiModel.qll index b5d6823c44c2..6010417b68d6 100644 --- a/python/ql/src/Security/CWE-327/FluentApiModel.qll +++ b/python/ql/src/Security/CWE-327/FluentApiModel.qll @@ -1,4 +1,4 @@ -import python +private import python import TlsLibraryModel /** @@ -89,9 +89,6 @@ 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 is 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 @@ -99,7 +96,7 @@ predicate unsafe_connection_creation_without_context( exists(TlsLibrary l | connectionCreation = l.insecure_connection_creation(insecure_version)) } -/** Holds if `contextCreation` is creating a context ties to a specific insecure version. */ +/** Holds if `contextCreation` is creating a context tied to a specific insecure version. */ predicate unsafe_context_creation(DataFlow::CallCfgNode contextCreation, string insecure_version) { exists(TlsLibrary l | contextCreation = l.insecure_context_creation(insecure_version)) } diff --git a/python/ql/src/Security/CWE-327/PyOpenSSL.qll b/python/ql/src/Security/CWE-327/PyOpenSSL.qll index 55b77386f2f8..3cd9b064e24c 100644 --- a/python/ql/src/Security/CWE-327/PyOpenSSL.qll +++ b/python/ql/src/Security/CWE-327/PyOpenSSL.qll @@ -1,5 +1,9 @@ -import python -import semmle.python.ApiGraphs +/** + * Provides modeling of SSL/TLS functionality of the `OpenSSL` module from the `pyOpenSSL` PyPI package. + * See https://www.pyopenssl.org/en/stable/ + */ +private import python +private import semmle.python.ApiGraphs import TlsLibraryModel class PyOpenSSLContextCreation extends ContextCreation { @@ -49,7 +53,7 @@ class SetOptionsCall extends ProtocolRestriction { } class UnspecificPyOpenSSLContextCreation extends PyOpenSSLContextCreation, UnspecificContextCreation { - UnspecificPyOpenSSLContextCreation() { library = "pyOpenSSL" } + UnspecificPyOpenSSLContextCreation() { library instanceof PyOpenSSL } } class PyOpenSSL extends TlsLibrary { diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll index cb19fb8f11ff..b44ae1814151 100644 --- a/python/ql/src/Security/CWE-327/Ssl.qll +++ b/python/ql/src/Security/CWE-327/Ssl.qll @@ -1,5 +1,9 @@ -import python -import semmle.python.ApiGraphs +/** + * Provides modeling of SSL/TLS functionality of the `ssl` module from the standard library. + * See https://docs.python.org/3.9/library/ssl.html + */ +private import python +private import semmle.python.ApiGraphs import TlsLibraryModel class SSLContextCreation extends ContextCreation { @@ -145,12 +149,12 @@ class ContextSetVersion extends ProtocolRestriction, ProtocolUnrestriction { } class UnspecificSSLContextCreation extends SSLContextCreation, UnspecificContextCreation { - UnspecificSSLContextCreation() { library = "ssl" } + UnspecificSSLContextCreation() { library instanceof 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 + // These are turned off by default since Python 3.6 + // see https://docs.python.org/3.6/library/ssl.html#ssl.SSLContext not result in ["SSLv2", "SSLv3"] } } @@ -185,8 +189,8 @@ class Ssl extends TlsLibrary { override DataFlow::CfgNode insecure_connection_creation(ProtocolVersion version) { result = API::moduleImport("ssl").getMember("wrap_socket").getACall() and - specific_version(version).asCfgNode() = - result.asCfgNode().(CallNode).getArgByName("ssl_version") and + this.specific_version(version) = + result.(DataFlow::CallCfgNode).getArgByName("ssl_version") and version.isInsecure() } diff --git a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll index 685485004b25..c49113f8f68d 100644 --- a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll +++ b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll @@ -1,15 +1,15 @@ -import python -import semmle.python.ApiGraphs +private import python +private import semmle.python.ApiGraphs import Ssl import PyOpenSSL /** - * A specific protocol version. - * We use this to identify a protocol. + * A specific protocol version of SSL or TLS. */ class ProtocolVersion extends string { ProtocolVersion() { this in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"] } + /** Gets a `ProtocolVersion` that is less than this `ProtocolVersion`, if any. */ predicate lessThan(ProtocolVersion version) { this = "SSLv2" and version = "SSLv3" or @@ -20,6 +20,7 @@ class ProtocolVersion extends string { this = ["TLSv1", "TLSv1_1", "TLSv1_2"] and version = "TLSv1_3" } + /** Holds if this protocol version is known to be insecure. */ predicate isInsecure() { this in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1"] } } @@ -81,12 +82,13 @@ abstract class UnspecificContextCreation extends ContextCreation, ProtocolUnrest /** A model of a SSL/TLS library. */ abstract class TlsLibrary extends string { - TlsLibrary() { this in ["ssl", "pyOpenSSL"] } + bindingset[this] + TlsLibrary() { any() } /** 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. */ + /** Gets a name, which is a member of `version_constants`, that can be used to specify the protocol family `family`. */ abstract string unspecific_version_name(ProtocolFamily family); /** The module or class holding the version constants. */ @@ -97,12 +99,12 @@ abstract class TlsLibrary extends string { result = version_constants().getMember(specific_version_name(version)).getAUse() } - /** A dataflow node representing an unspecific protocol version, say TLS, known to have insecure instances. */ + /** Gets a dataflow node representing the protocol family `family`. */ DataFlow::Node unspecific_version(ProtocolFamily family) { result = version_constants().getMember(unspecific_version_name(family)).getAUse() } - /** The creation of a context with a deafult protocol. */ + /** The creation of a context with a default protocol. */ abstract ContextCreation default_context_creation(); /** The creation of a context with a specific protocol. */ @@ -115,7 +117,7 @@ abstract class TlsLibrary extends string { version.isInsecure() } - /** The creation of a context with an unspecific protocol version, say TLS, known to have insecure instances. */ + /** Gets a context that was created using `family`, known to have insecure instances. */ ContextCreation unspecific_context_creation(ProtocolFamily family) { result in [specific_context_creation(), default_context_creation()] and result.getProtocol() = family From b6bd782746b59aedde860320c6700320de1b395e Mon Sep 17 00:00:00 2001 From: Rasmus Lerchedahl Petersen Date: Mon, 12 Apr 2021 23:55:59 +0200 Subject: [PATCH 214/433] Python: Modernize via `CallCfgNode` --- python/ql/src/Security/CWE-327/PyOpenSSL.qll | 13 ++++--------- python/ql/src/Security/CWE-327/Ssl.qll | 14 ++++++-------- python/ql/src/Security/CWE-327/TlsLibraryModel.qll | 14 +++++++------- 3 files changed, 17 insertions(+), 24 deletions(-) diff --git a/python/ql/src/Security/CWE-327/PyOpenSSL.qll b/python/ql/src/Security/CWE-327/PyOpenSSL.qll index 3cd9b064e24c..9f42b83572ed 100644 --- a/python/ql/src/Security/CWE-327/PyOpenSSL.qll +++ b/python/ql/src/Security/CWE-327/PyOpenSSL.qll @@ -2,13 +2,12 @@ * Provides modeling of SSL/TLS functionality of the `OpenSSL` module from the `pyOpenSSL` PyPI package. * See https://www.pyopenssl.org/en/stable/ */ + private import python private import semmle.python.ApiGraphs import TlsLibraryModel -class PyOpenSSLContextCreation extends ContextCreation { - override CallNode node; - +class PyOpenSSLContextCreation extends ContextCreation, DataFlow::CallCfgNode { PyOpenSSLContextCreation() { this = API::moduleImport("OpenSSL").getMember("SSL").getMember("Context").getACall() } @@ -22,9 +21,7 @@ class PyOpenSSLContextCreation extends ContextCreation { } } -class ConnectionCall extends ConnectionCreation { - override CallNode node; - +class ConnectionCall extends ConnectionCreation, DataFlow::CallCfgNode { ConnectionCall() { this = API::moduleImport("OpenSSL").getMember("SSL").getMember("Connection").getACall() } @@ -36,9 +33,7 @@ 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; - +class SetOptionsCall extends ProtocolRestriction, DataFlow::CallCfgNode { SetOptionsCall() { node.getFunction().(AttrNode).getName() = "set_options" } override DataFlow::CfgNode getContext() { diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll index b44ae1814151..99bb24b7076e 100644 --- a/python/ql/src/Security/CWE-327/Ssl.qll +++ b/python/ql/src/Security/CWE-327/Ssl.qll @@ -2,13 +2,12 @@ * Provides modeling of SSL/TLS functionality of the `ssl` module from the standard library. * See https://docs.python.org/3.9/library/ssl.html */ + private import python private import semmle.python.ApiGraphs import TlsLibraryModel -class SSLContextCreation extends ContextCreation { - override CallNode node; - +class SSLContextCreation extends ContextCreation, DataFlow::CallCfgNode { SSLContextCreation() { this = API::moduleImport("ssl").getMember("SSLContext").getACall() } override string getProtocol() { @@ -46,7 +45,7 @@ class WrapSocketCall extends ConnectionCreation, DataFlow::CallCfgNode { } } -class OptionsAugOr extends ProtocolRestriction { +class OptionsAugOr extends ProtocolRestriction, DataFlow::CallCfgNode { ProtocolVersion restriction; OptionsAugOr() { @@ -69,7 +68,7 @@ class OptionsAugOr extends ProtocolRestriction { override ProtocolVersion getRestriction() { result = restriction } } -class OptionsAugAndNot extends ProtocolUnrestriction { +class OptionsAugAndNot extends ProtocolUnrestriction, DataFlow::CallCfgNode { ProtocolVersion restriction; OptionsAugAndNot() { @@ -127,7 +126,7 @@ predicate impliesBitSet(BinaryExpr whole, Expr part, boolean partHasBitSet, bool ) } -class ContextSetVersion extends ProtocolRestriction, ProtocolUnrestriction { +class ContextSetVersion extends ProtocolRestriction, ProtocolUnrestriction, DataFlow::CallCfgNode { ProtocolVersion restriction; ContextSetVersion() { @@ -189,8 +188,7 @@ class Ssl extends TlsLibrary { override DataFlow::CfgNode insecure_connection_creation(ProtocolVersion version) { result = API::moduleImport("ssl").getMember("wrap_socket").getACall() and - this.specific_version(version) = - result.(DataFlow::CallCfgNode).getArgByName("ssl_version") and + this.specific_version(version) = result.(DataFlow::CallCfgNode).getArgByName("ssl_version") and version.isInsecure() } diff --git a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll index c49113f8f68d..cd38ad4ab5ec 100644 --- a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll +++ b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll @@ -30,30 +30,30 @@ class ProtocolFamily extends string { } /** The creation of a context. */ -abstract class ContextCreation extends DataFlow::CfgNode { +abstract class ContextCreation extends DataFlow::Node { /** Gets the protocol version or family for this context. */ abstract string getProtocol(); } /** The creation of a connection from a context. */ -abstract class ConnectionCreation extends DataFlow::CfgNode { +abstract class ConnectionCreation extends DataFlow::Node { /** Gets the context used to create the connection. */ - abstract DataFlow::CfgNode getContext(); + abstract DataFlow::Node getContext(); } /** A context is being restricted on which protocols it can accepts. */ -abstract class ProtocolRestriction extends DataFlow::CfgNode { +abstract class ProtocolRestriction extends DataFlow::Node { /** Gets the context being restricted. */ - abstract DataFlow::CfgNode getContext(); + abstract DataFlow::Node getContext(); /** Gets the protocol version being disallowed. */ abstract ProtocolVersion getRestriction(); } /** A context is being relaxed on which protocols it can accepts. */ -abstract class ProtocolUnrestriction extends DataFlow::CfgNode { +abstract class ProtocolUnrestriction extends DataFlow::Node { /** Gets the context being relaxed. */ - abstract DataFlow::CfgNode getContext(); + abstract DataFlow::Node getContext(); /** Gets the protocol version being allowed. */ abstract ProtocolVersion getUnrestriction(); From 697b2dcde8faafcc585c41a08dd1036f2ab53e09 Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Mon, 12 Apr 2021 18:11:10 -0400 Subject: [PATCH 215/433] C++: Add missing store step for single-field struct use We have special code to handle field flow for single-field structs, but that special case was too specific. Some `Store`s to single-field structs have no `Chi` instruction, which is the case that we handled already. However, it is possible for the `Store` to have a `Chi` instruction (e.g. for `{AllAliased}`), but still have a use of the result of the `Store` directly. We now add a `PostUpdateNode` for the result of the `Store` itself in those cases, just like we already did if the `Store` had no `Chi`. --- .../cpp/ir/dataflow/internal/DataFlowUtil.qll | 15 +++++-- .../ir/dataflow/internal/PrintIRLocalFlow.qll | 29 +------------- .../dataflow/internal/PrintIRStoreSteps.qll | 33 ++++++++++++++++ .../ir/dataflow/internal/PrintIRUtilities.qll | 39 +++++++++++++++++++ .../dataflow-ir-consistency.expected | 2 + .../fields/dataflow-ir-consistency.expected | 4 ++ .../dataflow/fields/ir-path-flow.expected | 6 +-- 7 files changed, 93 insertions(+), 35 deletions(-) create mode 100644 cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/PrintIRStoreSteps.qll create mode 100644 cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/PrintIRUtilities.qll diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll index 76ca7b215dce..81a07ad9d04a 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll @@ -362,15 +362,22 @@ private class ExplicitFieldStoreQualifierNode extends PartialDefinitionNode { /** * Not every store instruction generates a chi instruction that we can attach a PostUpdateNode to. - * For instance, an update to a field of a struct containing only one field. For these cases we - * attach the PostUpdateNode to the store instruction. There's no obvious pre update node for this case - * (as the entire memory is updated), so `getPreUpdateNode` is implemented as `none()`. + * For instance, an update to a field of a struct containing only one field. Even if the store does + * have a chi instruction, a subsequent use of the result of the store may be linked directly to the + * result of the store as an inexact definition if the store totally overlaps the use. For these + * cases we attach the PostUpdateNode to the store instruction. There's no obvious pre update node + * for this case (as the entire memory is updated), so `getPreUpdateNode` is implemented as + * `none()`. */ private class ExplicitSingleFieldStoreQualifierNode extends PartialDefinitionNode { override StoreInstruction instr; ExplicitSingleFieldStoreQualifierNode() { - not exists(ChiInstruction chi | chi.getPartial() = instr) and + ( + instr.getAUse().isDefinitionInexact() + or + not exists(ChiInstruction chi | chi.getPartial() = instr) + ) and // Without this condition any store would create a `PostUpdateNode`. instr.getDestinationAddress() instanceof FieldAddressInstruction } diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/PrintIRLocalFlow.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/PrintIRLocalFlow.qll index 337dc71a3caa..16182296e400 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/PrintIRLocalFlow.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/PrintIRLocalFlow.qll @@ -6,34 +6,7 @@ private import semmle.code.cpp.ir.ValueNumbering private import semmle.code.cpp.ir.IR private import semmle.code.cpp.ir.dataflow.DataFlow private import semmle.code.cpp.ir.dataflow.internal.DataFlowUtil - -/** - * Gets a short ID for an IR dataflow node. - * - For `Instruction`s, this is just the result ID of the instruction (e.g. `m128`). - * - For `Operand`s, this is the label of the operand, prefixed with the result ID of the - * instruction and a dot (e.g. `m128.left`). - * - For `Variable`s, this is the qualified name of the variable. - */ -private string nodeId(DataFlow::Node node, int order1, int order2) { - exists(Instruction instruction | instruction = node.asInstruction() | - result = instruction.getResultId() and - order1 = instruction.getBlock().getDisplayIndex() and - order2 = instruction.getDisplayIndexInBlock() - ) - or - exists(Operand operand, Instruction instruction | - operand = node.asOperand() and - instruction = operand.getUse() - | - result = instruction.getResultId() + "." + operand.getDumpId() and - order1 = instruction.getBlock().getDisplayIndex() and - order2 = instruction.getDisplayIndexInBlock() - ) - or - result = "var(" + node.asVariable().getQualifiedName() + ")" and - order1 = 1000000 and - order2 = 0 -} +private import PrintIRUtilities /** * Gets the local dataflow from other nodes in the same function to this node. diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/PrintIRStoreSteps.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/PrintIRStoreSteps.qll new file mode 100644 index 000000000000..8c3182162170 --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/PrintIRStoreSteps.qll @@ -0,0 +1,33 @@ +/** + * Print the dataflow local store steps in IR dumps. + */ + +private import cpp +// The `ValueNumbering` library has to be imported right after `cpp` to ensure +// that the cached IR gets the same checksum here as it does in queries that use +// `ValueNumbering` without `DataFlow`. +private import semmle.code.cpp.ir.ValueNumbering +private import semmle.code.cpp.ir.IR +private import semmle.code.cpp.ir.dataflow.DataFlow +private import semmle.code.cpp.ir.dataflow.internal.DataFlowUtil +private import semmle.code.cpp.ir.dataflow.internal.DataFlowPrivate +private import PrintIRUtilities + +/** + * Property provider for local IR dataflow store steps. + */ +class LocalFlowPropertyProvider extends IRPropertyProvider { + override string getInstructionProperty(Instruction instruction, string key) { + exists(DataFlow::Node objectNode, Content content | + key = "content[" + content.toString() + "]" and + instruction = objectNode.asInstruction() and + result = + strictconcat(string element, DataFlow::Node fieldNode | + storeStep(fieldNode, content, objectNode) and + element = nodeId(fieldNode, _, _) + | + element, ", " + ) + ) + } +} diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/PrintIRUtilities.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/PrintIRUtilities.qll new file mode 100644 index 000000000000..5fc15cf986c7 --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/PrintIRUtilities.qll @@ -0,0 +1,39 @@ +/** + * Shared utilities used when printing dataflow annotations in IR dumps. + */ + +private import cpp +// The `ValueNumbering` library has to be imported right after `cpp` to ensure +// that the cached IR gets the same checksum here as it does in queries that use +// `ValueNumbering` without `DataFlow`. +private import semmle.code.cpp.ir.ValueNumbering +private import semmle.code.cpp.ir.IR +private import semmle.code.cpp.ir.dataflow.DataFlow + +/** + * Gets a short ID for an IR dataflow node. + * - For `Instruction`s, this is just the result ID of the instruction (e.g. `m128`). + * - For `Operand`s, this is the label of the operand, prefixed with the result ID of the + * instruction and a dot (e.g. `m128.left`). + * - For `Variable`s, this is the qualified name of the variable. + */ +string nodeId(DataFlow::Node node, int order1, int order2) { + exists(Instruction instruction | instruction = node.asInstruction() | + result = instruction.getResultId() and + order1 = instruction.getBlock().getDisplayIndex() and + order2 = instruction.getDisplayIndexInBlock() + ) + or + exists(Operand operand, Instruction instruction | + operand = node.asOperand() and + instruction = operand.getUse() + | + result = instruction.getResultId() + "." + operand.getDumpId() and + order1 = instruction.getBlock().getDisplayIndex() and + order2 = instruction.getDisplayIndexInBlock() + ) + or + result = "var(" + node.asVariable().getQualifiedName() + ")" and + order1 = 1000000 and + order2 = 0 +} diff --git a/cpp/ql/test/library-tests/dataflow/dataflow-tests/dataflow-ir-consistency.expected b/cpp/ql/test/library-tests/dataflow/dataflow-tests/dataflow-ir-consistency.expected index fc6c97aa2a63..db9a86fbb570 100644 --- a/cpp/ql/test/library-tests/dataflow/dataflow-tests/dataflow-ir-consistency.expected +++ b/cpp/ql/test/library-tests/dataflow/dataflow-tests/dataflow-ir-consistency.expected @@ -26,6 +26,7 @@ unreachableNodeCCtx localCallNodes postIsNotPre postHasUniquePre +| test.cpp:373:5:373:20 | Store | PostUpdateNode should have one pre-update node but has 0. | uniquePostUpdate postIsInSameCallable reverseRead @@ -82,4 +83,5 @@ postWithInFlow | test.cpp:125:3:125:11 | Chi | PostUpdateNode should not be the target of local flow. | | test.cpp:359:5:359:20 | Chi | PostUpdateNode should not be the target of local flow. | | test.cpp:373:5:373:20 | Chi | PostUpdateNode should not be the target of local flow. | +| test.cpp:373:5:373:20 | Store | PostUpdateNode should not be the target of local flow. | | test.cpp:465:3:465:15 | Chi | PostUpdateNode should not be the target of local flow. | diff --git a/cpp/ql/test/library-tests/dataflow/fields/dataflow-ir-consistency.expected b/cpp/ql/test/library-tests/dataflow/fields/dataflow-ir-consistency.expected index 63d3b2c0f48f..fe7d8360403c 100644 --- a/cpp/ql/test/library-tests/dataflow/fields/dataflow-ir-consistency.expected +++ b/cpp/ql/test/library-tests/dataflow/fields/dataflow-ir-consistency.expected @@ -20,7 +20,9 @@ unreachableNodeCCtx localCallNodes postIsNotPre postHasUniquePre +| D.cpp:57:5:57:42 | Store | PostUpdateNode should have one pre-update node but has 0. | | simple.cpp:65:5:65:22 | Store | PostUpdateNode should have one pre-update node but has 0. | +| simple.cpp:83:9:83:28 | Store | PostUpdateNode should have one pre-update node but has 0. | | simple.cpp:92:5:92:22 | Store | PostUpdateNode should have one pre-update node but has 0. | uniquePostUpdate postIsInSameCallable @@ -54,6 +56,7 @@ postWithInFlow | D.cpp:49:15:49:24 | Chi | PostUpdateNode should not be the target of local flow. | | D.cpp:56:15:56:24 | Chi | PostUpdateNode should not be the target of local flow. | | D.cpp:57:5:57:42 | Chi | PostUpdateNode should not be the target of local flow. | +| D.cpp:57:5:57:42 | Store | PostUpdateNode should not be the target of local flow. | | aliasing.cpp:9:3:9:22 | Chi | PostUpdateNode should not be the target of local flow. | | aliasing.cpp:13:3:13:21 | Chi | PostUpdateNode should not be the target of local flow. | | aliasing.cpp:17:3:17:21 | Chi | PostUpdateNode should not be the target of local flow. | @@ -150,6 +153,7 @@ postWithInFlow | simple.cpp:23:35:23:35 | Chi | PostUpdateNode should not be the target of local flow. | | simple.cpp:65:5:65:22 | Store | PostUpdateNode should not be the target of local flow. | | simple.cpp:83:9:83:28 | Chi | PostUpdateNode should not be the target of local flow. | +| simple.cpp:83:9:83:28 | Store | PostUpdateNode should not be the target of local flow. | | simple.cpp:92:5:92:22 | Store | PostUpdateNode should not be the target of local flow. | | struct_init.c:20:20:20:29 | Chi | PostUpdateNode should not be the target of local flow. | | struct_init.c:20:34:20:34 | Chi | PostUpdateNode should not be the target of local flow. | diff --git a/cpp/ql/test/library-tests/dataflow/fields/ir-path-flow.expected b/cpp/ql/test/library-tests/dataflow/fields/ir-path-flow.expected index 37a0dc3832a2..e6234ca17f77 100644 --- a/cpp/ql/test/library-tests/dataflow/fields/ir-path-flow.expected +++ b/cpp/ql/test/library-tests/dataflow/fields/ir-path-flow.expected @@ -228,8 +228,8 @@ edges | simple.cpp:65:5:65:22 | Store [i] | simple.cpp:66:12:66:12 | Store [i] | | simple.cpp:65:11:65:20 | call to user_input | simple.cpp:65:5:65:22 | Store [i] | | simple.cpp:66:12:66:12 | Store [i] | simple.cpp:67:13:67:13 | i | -| simple.cpp:83:9:83:28 | Chi [f1] | simple.cpp:84:14:84:20 | this indirection [f1] | -| simple.cpp:83:17:83:26 | call to user_input | simple.cpp:83:9:83:28 | Chi [f1] | +| simple.cpp:83:9:83:28 | Store [f1] | simple.cpp:84:14:84:20 | this indirection [f1] | +| simple.cpp:83:17:83:26 | call to user_input | simple.cpp:83:9:83:28 | Store [f1] | | simple.cpp:84:14:84:20 | this indirection [f1] | simple.cpp:84:14:84:20 | call to getf2f1 | | simple.cpp:92:5:92:22 | Store [i] | simple.cpp:93:20:93:20 | Store [i] | | simple.cpp:92:11:92:20 | call to user_input | simple.cpp:92:5:92:22 | Store [i] | @@ -494,7 +494,7 @@ nodes | simple.cpp:65:11:65:20 | call to user_input | semmle.label | call to user_input | | simple.cpp:66:12:66:12 | Store [i] | semmle.label | Store [i] | | simple.cpp:67:13:67:13 | i | semmle.label | i | -| simple.cpp:83:9:83:28 | Chi [f1] | semmle.label | Chi [f1] | +| simple.cpp:83:9:83:28 | Store [f1] | semmle.label | Store [f1] | | simple.cpp:83:17:83:26 | call to user_input | semmle.label | call to user_input | | simple.cpp:84:14:84:20 | call to getf2f1 | semmle.label | call to getf2f1 | | simple.cpp:84:14:84:20 | this indirection [f1] | semmle.label | this indirection [f1] | From afd2f58f9fa6a75a390b818a5ea33cc1ae2b63fb Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Mon, 12 Apr 2021 18:21:05 -0400 Subject: [PATCH 216/433] C++: Fix PR feedback --- .../cpp/ir/implementation/raw/internal/TranslatedCall.qll | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) 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 0b52937f1cb7..56d4c807ac85 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 @@ -495,10 +495,8 @@ class TranslatedSideEffect extends TranslatedElement, TTranslatedArgumentSideEff override Instruction getFirstInstruction() { result = getInstruction(OnlyInstructionTag()) } override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType type) { - ( - tag = OnlyInstructionTag() and - opcode = sideEffectOpcode - ) and + tag = OnlyInstructionTag() and + opcode = sideEffectOpcode and ( isWrite() and ( From be39883166a410baabd3c24e9d7c575962ac7a86 Mon Sep 17 00:00:00 2001 From: haby0 Date: Tue, 13 Apr 2021 14:10:10 +0800 Subject: [PATCH 217/433] Change the class name and comment,Use .(CompileTimeConstantExpr).getStringValue() --- .../Security/CWE/CWE-352/JsonStringLib.qll | 29 ++++++++++++++----- .../Security/CWE/CWE-352/JsonpInjection.ql | 2 +- .../CWE/CWE-352/JsonpInjectionLib.qll | 14 +++++---- 3 files changed, 32 insertions(+), 13 deletions(-) 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 5cc52e97e338..b8f1a13b119b 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonStringLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonStringLib.qll @@ -4,10 +4,15 @@ import semmle.code.java.dataflow.FlowSources import DataFlow::PathGraph /** Json string type data. */ -abstract class JsonpStringSource extends DataFlow::Node { } +abstract class JsonStringSource extends DataFlow::Node { } -/** Convert to String using Gson library. */ -private class GsonString extends JsonpStringSource { +/** + * Convert to String using Gson library. * + * + * For example, in the method access `Gson.toJson(...)`, + * the `Object` type data is converted to the `String` type data. + */ +private class GsonString extends JsonStringSource { GsonString() { exists(MethodAccess ma, Method m | ma.getMethod() = m | m.hasName("toJson") and @@ -17,8 +22,13 @@ private class GsonString extends JsonpStringSource { } } -/** Convert to String using Fastjson library. */ -private class FastjsonString extends JsonpStringSource { +/** + * Convert to String using Fastjson library. + * + * For example, in the method access `JSON.toJSONString(...)`, + * the `Object` type data is converted to the `String` type data. + */ +private class FastjsonString extends JsonStringSource { FastjsonString() { exists(MethodAccess ma, Method m | ma.getMethod() = m | m.hasName("toJSONString") and @@ -28,8 +38,13 @@ private class FastjsonString extends JsonpStringSource { } } -/** Convert to String using Jackson library. */ -private class JacksonString extends JsonpStringSource { +/** + * Convert to String using Jackson library. + * + * For example, in the method access `ObjectMapper.writeValueAsString(...)`, + * the `Object` type data is converted to the `String` type data. + */ +private class JacksonString extends JsonStringSource { JacksonString() { exists(MethodAccess ma, Method m | ma.getMethod() = m | m.hasName("writeValueAsString") and 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 a02891e8a566..927c3db03d25 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql @@ -69,4 +69,4 @@ where conf.hasFlowPath(source, sink) and exists(JsonpInjectionFlowConfig jhfc | jhfc.hasFlowTo(sink.getNode())) select sink.getNode(), source, sink, "Jsonp response might include code from $@.", source.getNode(), - "this user input" \ No newline at end of file + "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 f0a1a9de81c8..6a21ce8ff3a8 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll @@ -86,17 +86,21 @@ class SpringControllerRequestMappingGetMethod extends SpringControllerGetMethod } } -/** A concatenate expression using `(` and `)` or `);`. */ +/** + * A concatenate expression using `(` and `)` or `);`. + * + * E.g: `functionName + "(" + json + ")"` or `functionName + "(" + json + ");"` + */ class JsonpBuilderExpr extends AddExpr { JsonpBuilderExpr() { - getRightOperand().toString().regexpMatch("\"\\);?\"") and + getRightOperand().(CompileTimeConstantExpr).getStringValue().regexpMatch("\\);?") and getLeftOperand() .(AddExpr) .getLeftOperand() .(AddExpr) .getRightOperand() - .toString() - .regexpMatch("\"\\(\"") + .(CompileTimeConstantExpr) + .getStringValue() = "(" } /** Get the jsonp function name of this expression. */ @@ -123,7 +127,7 @@ class RemoteFlowConfig extends DataFlow2::Configuration { class JsonDataFlowConfig extends DataFlow2::Configuration { JsonDataFlowConfig() { this = "JsonDataFlowConfig" } - override predicate isSource(DataFlow::Node src) { src instanceof JsonpStringSource } + override predicate isSource(DataFlow::Node src) { src instanceof JsonStringSource } override predicate isSink(DataFlow::Node sink) { exists(JsonpBuilderExpr jhe | jhe.getJsonExpr() = sink.asExpr()) From c37dbb2e68c4d25c4f0702333c08f1b57c072ab2 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Sat, 10 Apr 2021 03:17:50 +0200 Subject: [PATCH 218/433] Java: Override getAPrimaryQlClass() for more classes --- java/ql/src/semmle/code/FileSystem.qll | 6 ++++++ java/ql/src/semmle/code/java/Exception.qll | 2 ++ java/ql/src/semmle/code/java/Modifier.qll | 2 ++ java/ql/src/semmle/code/java/Package.qll | 2 ++ java/ql/src/semmle/code/java/Variable.qll | 2 ++ java/ql/src/semmle/code/xml/XML.qll | 2 ++ 6 files changed, 16 insertions(+) diff --git a/java/ql/src/semmle/code/FileSystem.qll b/java/ql/src/semmle/code/FileSystem.qll index 28015d3ba4da..13908d547dc5 100755 --- a/java/ql/src/semmle/code/FileSystem.qll +++ b/java/ql/src/semmle/code/FileSystem.qll @@ -159,6 +159,8 @@ class Folder extends Container, @folder { /** Gets the URL of this folder. */ override string getURL() { result = "folder://" + getAbsolutePath() } + + override string getAPrimaryQlClass() { result = "Folder" } } /** @@ -171,6 +173,8 @@ class File extends Container, @file { /** Gets the URL of this file. */ override string getURL() { result = "file://" + this.getAbsolutePath() + ":0:0:0:0" } + + override string getAPrimaryQlClass() { result = "File" } } /** @@ -204,4 +208,6 @@ class JarFile extends File { string getManifestEntryAttribute(string entry, string key) { jarManifestEntries(this, entry, key, result) } + + override string getAPrimaryQlClass() { result = "JarFile" } } diff --git a/java/ql/src/semmle/code/java/Exception.qll b/java/ql/src/semmle/code/java/Exception.qll index 242478aa6c8f..1e6cb54be0f2 100755 --- a/java/ql/src/semmle/code/java/Exception.qll +++ b/java/ql/src/semmle/code/java/Exception.qll @@ -27,4 +27,6 @@ class Exception extends Element, @exception { override predicate hasName(string name) { this.getType().hasName(name) } override string toString() { result = this.getType().toString() } + + override string getAPrimaryQlClass() { result = "Exception" } } diff --git a/java/ql/src/semmle/code/java/Modifier.qll b/java/ql/src/semmle/code/java/Modifier.qll index 8c48ffb117ae..7fb9f9826163 100755 --- a/java/ql/src/semmle/code/java/Modifier.qll +++ b/java/ql/src/semmle/code/java/Modifier.qll @@ -8,6 +8,8 @@ import Element class Modifier extends Element, @modifier { /** Gets the element to which this modifier applies. */ Element getElement() { hasModifier(result, this) } + + override string getAPrimaryQlClass() { result = "Modifier" } } /** An element of the Java syntax tree that may have a modifier. */ diff --git a/java/ql/src/semmle/code/java/Package.qll b/java/ql/src/semmle/code/java/Package.qll index 6cb41ca559fc..466c97e561d6 100755 --- a/java/ql/src/semmle/code/java/Package.qll +++ b/java/ql/src/semmle/code/java/Package.qll @@ -29,4 +29,6 @@ class Package extends Element, Annotatable, @package { * since packages do not have locations. */ string getURL() { result = "file://:0:0:0:0" } + + override string getAPrimaryQlClass() { result = "Package" } } diff --git a/java/ql/src/semmle/code/java/Variable.qll b/java/ql/src/semmle/code/java/Variable.qll index e038f98beafb..439ee5d3f6b9 100755 --- a/java/ql/src/semmle/code/java/Variable.qll +++ b/java/ql/src/semmle/code/java/Variable.qll @@ -53,6 +53,8 @@ class LocalVariableDecl extends @localvar, LocalScopeVariable { /** Gets the initializer expression of this local variable declaration. */ override Expr getInitializer() { result = getDeclExpr().getInit() } + + override string getAPrimaryQlClass() { result = "LocalVariableDecl" } } /** A formal parameter of a callable. */ diff --git a/java/ql/src/semmle/code/xml/XML.qll b/java/ql/src/semmle/code/xml/XML.qll index 5871fed0dddd..7833f1bdb681 100755 --- a/java/ql/src/semmle/code/xml/XML.qll +++ b/java/ql/src/semmle/code/xml/XML.qll @@ -149,6 +149,8 @@ class XMLFile extends XMLParent, File { /** Gets a DTD associated with this XML file. */ XMLDTD getADTD() { xmlDTDs(result, _, _, _, this) } + + override string getAPrimaryQlClass() { result = "XMLFile" } } /** From dee974ff2d9ab9e442b03d8ece4520d0ffbcc5a8 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Tue, 13 Apr 2021 09:13:47 +0100 Subject: [PATCH 219/433] Make Call a subclass of ExprParent. All of its subclasses are in any case (via Expr or Stmt) --- java/ql/src/semmle/code/java/Expr.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/src/semmle/code/java/Expr.qll b/java/ql/src/semmle/code/java/Expr.qll index a1a1412221b6..46186b4ee703 100755 --- a/java/ql/src/semmle/code/java/Expr.qll +++ b/java/ql/src/semmle/code/java/Expr.qll @@ -1808,7 +1808,7 @@ class WildcardTypeAccess extends Expr, @wildcardtypeaccess { * This includes method calls, constructor and super constructor invocations, * and constructors invoked through class instantiation. */ -class Call extends Top, @caller { +class Call extends ExprParent, @caller { /** Gets an argument supplied in this call. */ /*abstract*/ Expr getAnArgument() { none() } From 15c103e42d25b79ec8e9e8d782052d31ec567be4 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Tue, 13 Apr 2021 10:57:15 +0200 Subject: [PATCH 220/433] C#: Remove code duplication in `BuildScripts.cs` --- .../BuildScripts.cs | 50 +++++++------------ 1 file changed, 19 insertions(+), 31 deletions(-) diff --git a/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs b/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs index 9334181fe5a6..99ad4c8f963f 100644 --- a/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs +++ b/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs @@ -976,12 +976,11 @@ Microsoft.NETCore.App 2.1.3 [/usr/local/share/dotnet/shared/Microsoft.NETCore.Ap TestAutobuilderScript(autobuilder, 0, 9); } - [Fact] - public void TestDotnetVersionWindows() + private void TestDotnetVersionWindows(Action action, int commandsRun) { actions.RunProcess["cmd.exe /C dotnet --list-sdks"] = 0; actions.RunProcessOut["cmd.exe /C dotnet --list-sdks"] = "2.1.3 [C:\\Program Files\\dotnet\\sdks]\n2.1.4 [C:\\Program Files\\dotnet\\sdks]"; - actions.RunProcess[@"cmd.exe /C pwsh -NoProfile -ExecutionPolicy unrestricted -Command ""[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; &([scriptblock]::Create((Invoke-WebRequest -UseBasicParsing 'https://dot.net/v1/dotnet-install.ps1'))) -Version 2.1.3 -InstallDir C:\Project\.dotnet"""] = 0; + action(); actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet --info"] = 0; actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet clean C:\Project\test.csproj"] = 0; actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet restore C:\Project\test.csproj"] = 0; @@ -1004,39 +1003,28 @@ public void TestDotnetVersionWindows() actions.LoadXml[@"C:\Project\test.csproj"] = xml; var autobuilder = CreateAutoBuilder(true, dotnetVersion: "2.1.3"); - TestAutobuilderScript(autobuilder, 0, 6); + TestAutobuilderScript(autobuilder, 0, commandsRun); } [Fact] - public void TestDotnetVersionWindowsNoPwsh() + public void TestDotnetVersionWindowsWithPwsh() { - actions.RunProcess["cmd.exe /C dotnet --list-sdks"] = 0; - actions.RunProcessOut["cmd.exe /C dotnet --list-sdks"] = "2.1.3 [C:\\Program Files\\dotnet\\sdks]\n2.1.4 [C:\\Program Files\\dotnet\\sdks]"; - actions.RunProcess[@"cmd.exe /C pwsh -NoProfile -ExecutionPolicy unrestricted -Command ""[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; &([scriptblock]::Create((Invoke-WebRequest -UseBasicParsing 'https://dot.net/v1/dotnet-install.ps1'))) -Version 2.1.3 -InstallDir C:\Project\.dotnet"""] = 1; - actions.RunProcess[@"cmd.exe /C powershell -NoProfile -ExecutionPolicy unrestricted -Command ""[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; &([scriptblock]::Create((Invoke-WebRequest -UseBasicParsing 'https://dot.net/v1/dotnet-install.ps1'))) -Version 2.1.3 -InstallDir C:\Project\.dotnet"""] = 0; - actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet --info"] = 0; - actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet clean C:\Project\test.csproj"] = 0; - actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet restore C:\Project\test.csproj"] = 0; - actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --auto C:\Project\.dotnet\dotnet build --no-incremental C:\Project\test.csproj"] = 0; - actions.FileExists["csharp.log"] = true; - actions.FileExists[@"C:\Project\test.csproj"] = true; - actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_TRAP_DIR"] = ""; - actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_SOURCE_ARCHIVE_DIR"] = ""; - actions.GetEnvironmentVariable["PATH"] = "/bin:/usr/bin"; - actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.cs\ntest.csproj"; - actions.EnumerateDirectories[@"C:\Project"] = ""; - var xml = new XmlDocument(); - xml.LoadXml(@" - - Exe - netcoreapp2.1 - - -"); - actions.LoadXml[@"C:\Project\test.csproj"] = xml; + TestDotnetVersionWindows(() => + { + actions.RunProcess[@"cmd.exe /C pwsh -NoProfile -ExecutionPolicy unrestricted -Command ""[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; &([scriptblock]::Create((Invoke-WebRequest -UseBasicParsing 'https://dot.net/v1/dotnet-install.ps1'))) -Version 2.1.3 -InstallDir C:\Project\.dotnet"""] = 0; + }, + 6); + } - var autobuilder = CreateAutoBuilder(true, dotnetVersion: "2.1.3"); - TestAutobuilderScript(autobuilder, 0, 7); + [Fact] + public void TestDotnetVersionWindowsWithoutPwsh() + { + TestDotnetVersionWindows(() => + { + actions.RunProcess[@"cmd.exe /C pwsh -NoProfile -ExecutionPolicy unrestricted -Command ""[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; &([scriptblock]::Create((Invoke-WebRequest -UseBasicParsing 'https://dot.net/v1/dotnet-install.ps1'))) -Version 2.1.3 -InstallDir C:\Project\.dotnet"""] = 1; + actions.RunProcess[@"cmd.exe /C powershell -NoProfile -ExecutionPolicy unrestricted -Command ""[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; &([scriptblock]::Create((Invoke-WebRequest -UseBasicParsing 'https://dot.net/v1/dotnet-install.ps1'))) -Version 2.1.3 -InstallDir C:\Project\.dotnet"""] = 0; + }, + 7); } [Fact] From 7c0b0642c89efb06eca443ba46bb7e2738807aac Mon Sep 17 00:00:00 2001 From: Rasmus Lerchedahl Petersen Date: Tue, 13 Apr 2021 11:09:27 +0200 Subject: [PATCH 221/433] Python: Add imports to make code compile --- python/ql/src/Security/CWE-327/FluentApiModel.qll | 1 + python/ql/src/Security/CWE-327/InsecureProtocol.ql | 1 + 2 files changed, 2 insertions(+) diff --git a/python/ql/src/Security/CWE-327/FluentApiModel.qll b/python/ql/src/Security/CWE-327/FluentApiModel.qll index 6010417b68d6..a66f949e72d6 100644 --- a/python/ql/src/Security/CWE-327/FluentApiModel.qll +++ b/python/ql/src/Security/CWE-327/FluentApiModel.qll @@ -1,4 +1,5 @@ private import python +private import semmle.python.dataflow.new.DataFlow import TlsLibraryModel /** diff --git a/python/ql/src/Security/CWE-327/InsecureProtocol.ql b/python/ql/src/Security/CWE-327/InsecureProtocol.ql index e31bf78443b2..99e2d83420d1 100644 --- a/python/ql/src/Security/CWE-327/InsecureProtocol.ql +++ b/python/ql/src/Security/CWE-327/InsecureProtocol.ql @@ -10,6 +10,7 @@ */ import python +import semmle.python.dataflow.new.DataFlow import FluentApiModel // Helper for pretty printer `configName`. From 178cb6c90faa726331b5b6658863b22d156130c2 Mon Sep 17 00:00:00 2001 From: Rasmus Lerchedahl Petersen Date: Tue, 13 Apr 2021 11:26:05 +0200 Subject: [PATCH 222/433] Python: Bit too eager with the modernisation... Lift type restrictions to recover results. --- python/ql/src/Security/CWE-327/Ssl.qll | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll index 99bb24b7076e..c1c6d65402ce 100644 --- a/python/ql/src/Security/CWE-327/Ssl.qll +++ b/python/ql/src/Security/CWE-327/Ssl.qll @@ -45,7 +45,7 @@ class WrapSocketCall extends ConnectionCreation, DataFlow::CallCfgNode { } } -class OptionsAugOr extends ProtocolRestriction, DataFlow::CallCfgNode { +class OptionsAugOr extends ProtocolRestriction, DataFlow::CfgNode { ProtocolVersion restriction; OptionsAugOr() { @@ -68,7 +68,7 @@ class OptionsAugOr extends ProtocolRestriction, DataFlow::CallCfgNode { override ProtocolVersion getRestriction() { result = restriction } } -class OptionsAugAndNot extends ProtocolUnrestriction, DataFlow::CallCfgNode { +class OptionsAugAndNot extends ProtocolUnrestriction, DataFlow::CfgNode { ProtocolVersion restriction; OptionsAugAndNot() { @@ -126,7 +126,7 @@ predicate impliesBitSet(BinaryExpr whole, Expr part, boolean partHasBitSet, bool ) } -class ContextSetVersion extends ProtocolRestriction, ProtocolUnrestriction, DataFlow::CallCfgNode { +class ContextSetVersion extends ProtocolRestriction, ProtocolUnrestriction, DataFlow::CfgNode { ProtocolVersion restriction; ContextSetVersion() { From 7c13163413f3167355ec449d08579c704675c7f5 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Thu, 1 Apr 2021 11:49:17 +0100 Subject: [PATCH 223/433] JS: Lift JSON accessors to JSONValue --- javascript/ql/src/semmle/javascript/JSON.qll | 29 +++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/JSON.qll b/javascript/ql/src/semmle/javascript/JSON.qll index f0f4f59e98ef..ed3f6f69637b 100644 --- a/javascript/ql/src/semmle/javascript/JSON.qll +++ b/javascript/ql/src/semmle/javascript/JSON.qll @@ -41,6 +41,21 @@ class JSONValue extends @json_value, Locatable { ) } + /** If this is an object, gets the value of property `name`. */ + JSONValue getPropValue(string name) { json_properties(this, name, result) } + + /** If this is an array, gets the value of the `i`th element. */ + JSONValue getElementValue(int i) { result = this.(JSONArray).getChild(i) } + + /** If this is a string constant, gets the value of the string. */ + string getStringValue() { result = this.(JSONString).getValue() } + + /** If this is an integer constant, gets its numeric value. */ + int getIntValue() { result = this.(JSONNumber).getValue().toInt() } + + /** If this is a boolean constant, gets its boolean value. */ + boolean getBooleanValue() { result.toString() = this.(JSONBoolean).getValue() } + override string getAPrimaryQlClass() { result = "JSONValue" } } @@ -129,13 +144,10 @@ class JSONString extends @json_string, JSONPrimitiveValue { * ``` */ class JSONArray extends @json_array, JSONValue { - /** Gets the value of the `i`th element of this array. */ - JSONValue getElementValue(int i) { result = getChild(i) } + override string getAPrimaryQlClass() { result = "JSONArray" } /** Gets the string value of the `i`th element of this array. */ - string getElementStringValue(int i) { result = getElementValue(i).(JSONString).getValue() } - - override string getAPrimaryQlClass() { result = "JSONArray" } + string getElementStringValue(int i) { result = getElementValue(i).getStringValue() } } /** @@ -148,13 +160,10 @@ class JSONArray extends @json_array, JSONValue { * ``` */ class JSONObject extends @json_object, JSONValue { - /** Gets the value of property `name` of this object. */ - JSONValue getPropValue(string name) { json_properties(this, name, result) } + override string getAPrimaryQlClass() { result = "JSONObject" } /** Gets the string value of property `name` of this object. */ - string getPropStringValue(string name) { result = getPropValue(name).(JSONString).getValue() } - - override string getAPrimaryQlClass() { result = "JSONObject" } + string getPropStringValue(string name) { result = getPropValue(name).getStringValue() } } /** From 929d9da4b4c9d9830689ad01960eb1ce22dda406 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Fri, 9 Apr 2021 11:37:57 +0100 Subject: [PATCH 224/433] JS: Migrate to new JSON API --- .../ql/src/Declarations/UnusedVariable.ql | 4 ++-- javascript/ql/src/semmle/javascript/NPM.qll | 22 +++++++++---------- javascript/ql/src/semmle/javascript/Paths.qll | 12 +++------- .../semmle/javascript/frameworks/Babel.qll | 8 +++---- .../security/dataflow/RemoteFlowSources.qll | 2 +- 5 files changed, 21 insertions(+), 27 deletions(-) diff --git a/javascript/ql/src/Declarations/UnusedVariable.ql b/javascript/ql/src/Declarations/UnusedVariable.ql index 90c3948b634f..738795571598 100644 --- a/javascript/ql/src/Declarations/UnusedVariable.ql +++ b/javascript/ql/src/Declarations/UnusedVariable.ql @@ -65,8 +65,8 @@ predicate isReactForJSX(UnusedLocal v) { v.getName() = tsconfig .getPropValue("compilerOptions") - .(JSONObject) - .getPropStringValue(["jsxFactory", "jsxFragmentFactory"]) + .getPropValue(["jsxFactory", "jsxFragmentFactory"]) + .getStringValue() ) ) } diff --git a/javascript/ql/src/semmle/javascript/NPM.qll b/javascript/ql/src/semmle/javascript/NPM.qll index 8351c4266cfc..abea13b9d723 100644 --- a/javascript/ql/src/semmle/javascript/NPM.qll +++ b/javascript/ql/src/semmle/javascript/NPM.qll @@ -41,7 +41,7 @@ class PackageJSON extends JSONObject { /** Gets information for a contributor to this package. */ ContributorInfo getAContributor() { - result = getPropValue("contributors").(JSONArray).getElementValue(_) + result = getPropValue("contributors").getElementValue(_) } /** Gets the array of files for this package. */ @@ -57,13 +57,13 @@ class PackageJSON extends JSONObject { string getBin(string cmd) { cmd = getPackageName() and result = getPropStringValue("bin") or - result = getPropValue("bin").(JSONObject).getPropStringValue(cmd) + result = getPropValue("bin").getPropValue(cmd).getStringValue() } /** Gets a manual page for this package. */ string getAManFile() { result = getPropStringValue("man") or - result = getPropValue("man").(JSONArray).getElementStringValue(_) + result = getPropValue("man").getElementValue(_).getStringValue() } /** Gets information about the directories of this package. */ @@ -191,12 +191,12 @@ class BugTrackerInfo extends JSONValue { /** Gets the bug tracker URL. */ string getUrl() { - result = this.(JSONObject).getPropStringValue("url") or - result = this.(JSONString).getValue() + result = this.getPropValue("url").getStringValue() or + result = this.getStringValue() } /** Gets the bug reporting email address. */ - string getEmail() { result = this.(JSONObject).getPropStringValue("email") } + string getEmail() { result = this.getPropValue("email").getStringValue() } } /** @@ -206,7 +206,7 @@ class ContributorInfo extends JSONValue { ContributorInfo() { exists(PackageJSON pkg | this = pkg.getPropValue("author") or - this = pkg.getPropValue("contributors").(JSONArray).getElementValue(_) + this = pkg.getPropValue("contributors").getElementValue(_) ) and (this instanceof JSONObject or this instanceof JSONString) } @@ -217,24 +217,24 @@ class ContributorInfo extends JSONValue { * homepage URL. */ private string parseInfo(int group) { - result = this.(JSONString).getValue().regexpCapture("(.*?)(?: <(.*?)>)?(?: \\((.*)?\\))", group) + result = this.getStringValue().regexpCapture("(.*?)(?: <(.*?)>)?(?: \\((.*)?\\))", group) } /** Gets the contributor's name. */ string getName() { - result = this.(JSONObject).getPropStringValue("name") or + result = this.getPropValue("name").getStringValue() or result = parseInfo(1) } /** Gets the contributor's email address. */ string getEmail() { - result = this.(JSONObject).getPropStringValue("email") or + result = this.getPropValue("email").getStringValue() or result = parseInfo(2) } /** Gets the contributor's homepage URL. */ string getUrl() { - result = this.(JSONObject).getPropStringValue("url") or + result = this.getPropValue("url").getStringValue() or result = parseInfo(3) } } diff --git a/javascript/ql/src/semmle/javascript/Paths.qll b/javascript/ql/src/semmle/javascript/Paths.qll index 37098fc130f1..a166b0d2d94c 100644 --- a/javascript/ql/src/semmle/javascript/Paths.qll +++ b/javascript/ql/src/semmle/javascript/Paths.qll @@ -236,10 +236,8 @@ private module TypeScriptOutDir { result = tsconfig .getPropValue("compilerOptions") - .(JSONObject) .getPropValue("outDir") - .(JSONString) - .getValue() + .getStringValue() } /** @@ -283,10 +281,8 @@ private module TypeScriptOutDir { result = getRootFolderFromPath(tsconfig .getPropValue("include") - .(JSONArray) .getElementValue(_) - .(JSONString) - .getValue()) + .getStringValue()) } /** @@ -297,10 +293,8 @@ private module TypeScriptOutDir { result = tsconfig .getPropValue("compilerOptions") - .(JSONObject) .getPropValue("rootDir") - .(JSONString) - .getValue() + .getStringValue() } } diff --git a/javascript/ql/src/semmle/javascript/frameworks/Babel.qll b/javascript/ql/src/semmle/javascript/frameworks/Babel.qll index 9e112f057761..bdacce9142b8 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Babel.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Babel.qll @@ -24,9 +24,9 @@ module Babel { plugins = getPropValue("plugins") and result = plugins.getElementValue(_) | - result.(JSONString).getValue() = pluginName + result.getStringValue() = pluginName or - result.(JSONArray).getElementStringValue(0) = pluginName + result.getElementValue(0).getStringValue() = pluginName ) } @@ -67,7 +67,7 @@ module Babel { JSONValue getOptions() { result = this.(JSONArray).getElementValue(1) } /** Gets a named option from the option object, if present. */ - JSONValue getOption(string name) { result = getOptions().(JSONObject).getPropValue(name) } + JSONValue getOption(string name) { result = getOptions().getPropValue(name) } /** Holds if this plugin applies to `tl`. */ predicate appliesTo(TopLevel tl) { cfg.appliesTo(tl) } @@ -186,7 +186,7 @@ module Babel { TransformReactJsxConfig() { pluginName = "transform-react-jsx" } /** Gets the name of the variable used to create JSX elements. */ - string getJsxFactoryVariableName() { result = getOption("pragma").(JSONString).getValue() } + string getJsxFactoryVariableName() { result = getOption("pragma").getStringValue() } } /** diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/RemoteFlowSources.qll b/javascript/ql/src/semmle/javascript/security/dataflow/RemoteFlowSources.qll index c5a39772cf4f..89574f4886b1 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/RemoteFlowSources.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/RemoteFlowSources.qll @@ -108,7 +108,7 @@ private class RemoteFlowSourceAccessPath extends JSONString { exists(JSONObject specs | specs.isTopLevel() and this.getFile().getBaseName() = "codeql-javascript-remote-flow-sources.json" and - this = specs.getPropValue(sourceType).(JSONArray).getElementValue(_) and + this = specs.getPropValue(sourceType).getElementValue(_) and this.getValue().regexpMatch("window(\\.\\w+)+") ) } From e77117f902a7bc62771d6900fbd615cb54e1ab54 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Tue, 13 Apr 2021 10:16:41 +0100 Subject: [PATCH 225/433] JS: Autoformat --- javascript/ql/src/semmle/javascript/NPM.qll | 4 +--- javascript/ql/src/semmle/javascript/Paths.qll | 17 +++-------------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/NPM.qll b/javascript/ql/src/semmle/javascript/NPM.qll index abea13b9d723..8c46cd5901bb 100644 --- a/javascript/ql/src/semmle/javascript/NPM.qll +++ b/javascript/ql/src/semmle/javascript/NPM.qll @@ -40,9 +40,7 @@ class PackageJSON extends JSONObject { ContributorInfo getAuthor() { result = getPropValue("author") } /** Gets information for a contributor to this package. */ - ContributorInfo getAContributor() { - result = getPropValue("contributors").getElementValue(_) - } + ContributorInfo getAContributor() { result = getPropValue("contributors").getElementValue(_) } /** Gets the array of files for this package. */ JSONArray getFiles() { result = getPropValue("files") } diff --git a/javascript/ql/src/semmle/javascript/Paths.qll b/javascript/ql/src/semmle/javascript/Paths.qll index a166b0d2d94c..64ff25c32208 100644 --- a/javascript/ql/src/semmle/javascript/Paths.qll +++ b/javascript/ql/src/semmle/javascript/Paths.qll @@ -233,11 +233,7 @@ private module TypeScriptOutDir { tsconfig.getFile().getBaseName().regexpMatch("tsconfig.*\\.json") and tsconfig.isTopLevel() and tsconfig.getFile().getParentContainer() = parent and - result = - tsconfig - .getPropValue("compilerOptions") - .getPropValue("outDir") - .getStringValue() + result = tsconfig.getPropValue("compilerOptions").getPropValue("outDir").getStringValue() } /** @@ -279,10 +275,7 @@ private module TypeScriptOutDir { pragma[inline] private string getARootDirFromInclude(JSONObject tsconfig) { result = - getRootFolderFromPath(tsconfig - .getPropValue("include") - .getElementValue(_) - .getStringValue()) + getRootFolderFromPath(tsconfig.getPropValue("include").getElementValue(_).getStringValue()) } /** @@ -290,11 +283,7 @@ private module TypeScriptOutDir { */ pragma[inline] private string getRootDir(JSONObject tsconfig) { - result = - tsconfig - .getPropValue("compilerOptions") - .getPropValue("rootDir") - .getStringValue() + result = tsconfig.getPropValue("compilerOptions").getPropValue("rootDir").getStringValue() } } From 30fbb8f1e7993ba509378c65e78d391aaa604791 Mon Sep 17 00:00:00 2001 From: Rasmus Lerchedahl Petersen Date: Tue, 13 Apr 2021 11:34:47 +0200 Subject: [PATCH 226/433] Python: clean up interface --- python/ql/src/Security/CWE-327/PyOpenSSL.qll | 2 +- python/ql/src/Security/CWE-327/Ssl.qll | 14 +++++++------- python/ql/src/Security/CWE-327/TlsLibraryModel.qll | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/python/ql/src/Security/CWE-327/PyOpenSSL.qll b/python/ql/src/Security/CWE-327/PyOpenSSL.qll index 9f42b83572ed..d886d46efefa 100644 --- a/python/ql/src/Security/CWE-327/PyOpenSSL.qll +++ b/python/ql/src/Security/CWE-327/PyOpenSSL.qll @@ -69,7 +69,7 @@ class PyOpenSSL extends TlsLibrary { result instanceof PyOpenSSLContextCreation } - override DataFlow::CfgNode insecure_connection_creation(ProtocolVersion version) { none() } + override DataFlow::Node insecure_connection_creation(ProtocolVersion version) { none() } override ConnectionCreation connection_creation() { result instanceof ConnectionCall } diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll index c1c6d65402ce..e943470e917e 100644 --- a/python/ql/src/Security/CWE-327/Ssl.qll +++ b/python/ql/src/Security/CWE-327/Ssl.qll @@ -40,7 +40,7 @@ API::Node sslContextInstance() { class WrapSocketCall extends ConnectionCreation, DataFlow::CallCfgNode { WrapSocketCall() { this = sslContextInstance().getMember("wrap_socket").getACall() } - override DataFlow::CfgNode getContext() { + override DataFlow::Node getContext() { result = this.getFunction().(DataFlow::AttrRead).getObject() } } @@ -63,7 +63,7 @@ class OptionsAugOr extends ProtocolRestriction, DataFlow::CfgNode { ) } - override DataFlow::CfgNode getContext() { result = this } + override DataFlow::Node getContext() { result = this } override ProtocolVersion getRestriction() { result = restriction } } @@ -88,7 +88,7 @@ class OptionsAugAndNot extends ProtocolUnrestriction, DataFlow::CfgNode { ) } - override DataFlow::CfgNode getContext() { result = this } + override DataFlow::Node getContext() { result = this } override ProtocolVersion getUnrestriction() { result = restriction } } @@ -138,7 +138,7 @@ class ContextSetVersion extends ProtocolRestriction, ProtocolUnrestriction, Data ) } - override DataFlow::CfgNode getContext() { result = this } + override DataFlow::Node getContext() { result = this } override ProtocolVersion getRestriction() { result.lessThan(restriction) } @@ -159,7 +159,7 @@ class UnspecificSSLContextCreation extends SSLContextCreation, UnspecificContext } class UnspecificSSLDefaultContextCreation extends SSLDefaultContextCreation, ProtocolUnrestriction { - override DataFlow::CfgNode getContext() { result = this } + override DataFlow::Node getContext() { result = this } // see https://docs.python.org/3/library/ssl.html#ssl.create_default_context override ProtocolVersion getUnrestriction() { @@ -186,9 +186,9 @@ class Ssl extends TlsLibrary { override ContextCreation specific_context_creation() { result instanceof SSLContextCreation } - override DataFlow::CfgNode insecure_connection_creation(ProtocolVersion version) { + override DataFlow::CallCfgNode insecure_connection_creation(ProtocolVersion version) { result = API::moduleImport("ssl").getMember("wrap_socket").getACall() and - this.specific_version(version) = result.(DataFlow::CallCfgNode).getArgByName("ssl_version") and + this.specific_version(version) = result.getArgByName("ssl_version") and version.isInsecure() } diff --git a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll index cd38ad4ab5ec..ba54ea3ccd42 100644 --- a/python/ql/src/Security/CWE-327/TlsLibraryModel.qll +++ b/python/ql/src/Security/CWE-327/TlsLibraryModel.qll @@ -124,7 +124,7 @@ abstract class TlsLibrary extends string { } /** A connection is created in an insecure manner, not from a context. */ - abstract DataFlow::CfgNode insecure_connection_creation(ProtocolVersion version); + abstract DataFlow::Node insecure_connection_creation(ProtocolVersion version); /** A connection is created from a context. */ abstract ConnectionCreation connection_creation(); From 45e1a61d7bc8996af158130449763b00bedd7a69 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Tue, 13 Apr 2021 10:11:37 +0100 Subject: [PATCH 227/433] Mark test as bad-but-missed This test ought ideally to be caught, but isn't by the current version of the query. --- .../security/CWE-1004/SensitiveCookieNotHttpOnly.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 4ad7aa5dd95e..627575c84034 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 @@ -137,7 +137,9 @@ public void addCookie14(HttpServletRequest request, HttpServletResponse response response.addCookie(createCookie("refresh_token", refreshToken, true)); } - // GOOD - Tests set a sensitive cookie header with the `HttpOnly` flag not set through a boolean variable using a wrapper method. + // BAD (but not detected) - Tests set a sensitive cookie header with the `HttpOnly` flag not set through a boolean variable using a wrapper method. + // This example is missed because the `cookie.setHttpOnly` call in `createCookie` is thought to maybe set the HTTP-only flag, and the `cookie` + // object flows to this `addCookie` call. public void addCookie15(HttpServletRequest request, HttpServletResponse response, String refreshToken) { response.addCookie(createCookie("refresh_token", refreshToken, false)); } From f22b11881e080e49a39773fe9c34d516e6a99b65 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Tue, 13 Apr 2021 10:28:15 +0100 Subject: [PATCH 228/433] Minimise stubs By removing all business logic from the stubs, we better test that our analysis treats them as opaque and does not rely on their internal structure --- .../javax/ws/rs/core/Cookie.java | 12 ------- .../javax/ws/rs/core/NewCookie.java | 36 +++++-------------- 2 files changed, 8 insertions(+), 40 deletions(-) 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 2726c3d27dda..6853f72ecd8d 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 @@ -56,11 +56,6 @@ 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. @@ -74,11 +69,6 @@ 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; } /** @@ -92,7 +82,6 @@ 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); } /** @@ -104,7 +93,6 @@ 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); } /** 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 26279d7fe0a9..046bb15da123 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 @@ -57,12 +57,6 @@ public class NewCookie extends Cookie { */ 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. * @@ -71,7 +65,7 @@ public class NewCookie extends 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); + super("", ""); } /** @@ -93,7 +87,7 @@ public NewCookie(String name, String comment, int maxAge, boolean secure) { - this(name, value, path, domain, DEFAULT_VERSION, comment, maxAge, null, secure, false); + super("", ""); } /** @@ -118,7 +112,7 @@ public NewCookie(String name, int maxAge, boolean secure, boolean httpOnly) { - this(name, value, path, domain, DEFAULT_VERSION, comment, maxAge, null, secure, httpOnly); + super("", ""); } /** @@ -142,7 +136,7 @@ public NewCookie(String name, String comment, int maxAge, boolean secure) { - this(name, value, path, domain, version, comment, maxAge, null, secure, false); + super("", ""); } /** @@ -171,12 +165,7 @@ public NewCookie(String name, 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; + super("", ""); } /** @@ -186,7 +175,7 @@ public NewCookie(String name, * @throws IllegalArgumentException if cookie is {@code null}. */ public NewCookie(Cookie cookie) { - this(cookie, null, DEFAULT_MAX_AGE, null, false, false); + super("", ""); } /** @@ -199,7 +188,7 @@ public NewCookie(Cookie cookie) { * @throws IllegalArgumentException if cookie is {@code null}. */ public NewCookie(Cookie cookie, String comment, int maxAge, boolean secure) { - this(cookie, comment, maxAge, null, secure, false); + super("", ""); } /** @@ -215,16 +204,7 @@ public NewCookie(Cookie cookie, String comment, int maxAge, boolean secure) { * @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; + super("", ""); } /** From 98d936d8b3e8a232355b4dacce7f4971273aba0a Mon Sep 17 00:00:00 2001 From: Taus Date: Tue, 13 Apr 2021 11:43:00 +0000 Subject: [PATCH 229/433] Python: Tornado cleanup using API graphs I wasn't able to roll out API graphs as widely in Tornado as I had hoped, since we're lacking the "def" part. This means most of the `InstanceSource` machinery will have to stay. --- .../src/semmle/python/frameworks/Tornado.qll | 195 +++--------------- 1 file changed, 32 insertions(+), 163 deletions(-) diff --git a/python/ql/src/semmle/python/frameworks/Tornado.qll b/python/ql/src/semmle/python/frameworks/Tornado.qll index a43edcaca901..6268c9f0a4dd 100644 --- a/python/ql/src/semmle/python/frameworks/Tornado.qll +++ b/python/ql/src/semmle/python/frameworks/Tornado.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.regex /** @@ -19,54 +20,13 @@ private module Tornado { // tornado // --------------------------------------------------------------------------- /** Gets a reference to the `tornado` module. */ - private DataFlow::Node tornado(DataFlow::TypeTracker t) { - t.start() and - result = DataFlow::importNode("tornado") - or - exists(DataFlow::TypeTracker t2 | result = tornado(t2).track(t2, t)) - } - - /** Gets a reference to the `tornado` module. */ - DataFlow::Node tornado() { result = tornado(DataFlow::TypeTracker::end()) } - - /** - * Gets a reference to the attribute `attr_name` of the `tornado` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node tornado_attr(DataFlow::TypeTracker t, string attr_name) { - attr_name in ["web", "httputil"] and - ( - t.start() and - result = DataFlow::importNode("tornado" + "." + attr_name) - or - t.startInAttr(attr_name) and - result = tornado() - ) - or - // Due to bad performance when using normal setup with `tornado_attr(t2, attr_name).track(t2, t)` - // we have inlined that code and forced a join - exists(DataFlow::TypeTracker t2 | - exists(DataFlow::StepSummary summary | - tornado_attr_first_join(t2, attr_name, result, summary) and - t = t2.append(summary) - ) - ) - } - - pragma[nomagic] - private predicate tornado_attr_first_join( - DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary - ) { - DataFlow::StepSummary::step(tornado_attr(t2, attr_name), res, summary) - } + API::Node tornado() { result = API::moduleImport("tornado") } /** * Gets a reference to the attribute `attr_name` of the `tornado` module. * WARNING: Only holds for a few predefined attributes. */ - private DataFlow::Node tornado_attr(string attr_name) { - result = tornado_attr(DataFlow::TypeTracker::end(), attr_name) - } + private API::Node tornado_attr(string attr_name) { result = tornado().getMember(attr_name) } /** Provides models for the `tornado` module. */ module tornado { @@ -74,7 +34,7 @@ private module Tornado { // tornado.web // ------------------------------------------------------------------------- /** Gets a reference to the `tornado.web` module. */ - DataFlow::Node web() { result = tornado_attr("web") } + API::Node web() { result = tornado_attr("web") } /** Provides models for the `tornado.web` module */ module web { @@ -82,41 +42,7 @@ private module Tornado { * Gets a reference to the attribute `attr_name` of the `tornado.web` module. * WARNING: Only holds for a few predefined attributes. */ - private DataFlow::Node web_attr(DataFlow::TypeTracker t, string attr_name) { - attr_name in ["RequestHandler", "Application"] and - ( - t.start() and - result = DataFlow::importNode("tornado.web" + "." + attr_name) - or - t.startInAttr(attr_name) and - result = web() - ) - or - // Due to bad performance when using normal setup with `web_attr(t2, attr_name).track(t2, t)` - // we have inlined that code and forced a join - exists(DataFlow::TypeTracker t2 | - exists(DataFlow::StepSummary summary | - web_attr_first_join(t2, attr_name, result, summary) and - t = t2.append(summary) - ) - ) - } - - pragma[nomagic] - private predicate web_attr_first_join( - DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, - DataFlow::StepSummary summary - ) { - DataFlow::StepSummary::step(web_attr(t2, attr_name), res, summary) - } - - /** - * Gets a reference to the attribute `attr_name` of the `tornado.web` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node web_attr(string attr_name) { - result = web_attr(DataFlow::TypeTracker::end(), attr_name) - } + private API::Node web_attr(string attr_name) { result = web().getMember(attr_name) } /** * Provides models for the `tornado.web.RequestHandler` class and subclasses. @@ -125,22 +51,11 @@ private module Tornado { */ module RequestHandler { /** Gets a reference to the `tornado.web.RequestHandler` class or any subclass. */ - private DataFlow::Node subclassRef(DataFlow::TypeTracker t) { - t.start() and - result = web_attr("RequestHandler") - or - // subclasses in project code - result.asExpr().(ClassExpr).getABase() = subclassRef(t.continue()).asExpr() - or - exists(DataFlow::TypeTracker t2 | result = subclassRef(t2).track(t2, t)) - } - - /** Gets a reference to the `tornado.web.RequestHandler` class or any subclass. */ - DataFlow::Node subclassRef() { result = subclassRef(DataFlow::TypeTracker::end()) } + API::Node subclassRef() { result = web_attr("RequestHandler").getASubclass*() } /** A RequestHandler class (most likely in project code). */ class RequestHandlerClass extends Class { - RequestHandlerClass() { this.getParent() = subclassRef().asExpr() } + RequestHandlerClass() { this.getParent() = subclassRef().getAUse().asExpr() } /** Gets a function that could handle incoming requests, if any. */ Function getARequestHandler() { @@ -151,7 +66,7 @@ private module Tornado { } /** Gets a reference to this class. */ - private DataFlow::Node getARef(DataFlow::TypeTracker t) { + private DataFlow::LocalSourceNode getARef(DataFlow::TypeTracker t) { t.start() and result.asExpr().(ClassExpr) = this.getParent() or @@ -159,7 +74,7 @@ private module Tornado { } /** Gets a reference to this class. */ - DataFlow::Node getARef() { result = this.getARef(DataFlow::TypeTracker::end()) } + DataFlow::Node getARef() { this.getARef(DataFlow::TypeTracker::end()).flowsTo(result) } } /** @@ -184,7 +99,7 @@ private module Tornado { } /** Gets a reference to an instance of the `tornado.web.RequestHandler` 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 @@ -192,10 +107,10 @@ private module Tornado { } /** Gets a reference to an instance of the `tornado.web.RequestHandler` class or any subclass. */ - DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) } + DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) } /** Gets a reference to one of the methods `get_argument`, `get_body_argument`, `get_query_argument`. */ - private DataFlow::Node argumentMethod(DataFlow::TypeTracker t) { + private DataFlow::LocalSourceNode argumentMethod(DataFlow::TypeTracker t) { t.startInAttr(["get_argument", "get_body_argument", "get_query_argument"]) and result = instance() or @@ -203,10 +118,12 @@ private module Tornado { } /** Gets a reference to one of the methods `get_argument`, `get_body_argument`, `get_query_argument`. */ - DataFlow::Node argumentMethod() { result = argumentMethod(DataFlow::TypeTracker::end()) } + DataFlow::Node argumentMethod() { + argumentMethod(DataFlow::TypeTracker::end()).flowsTo(result) + } /** Gets a reference to one of the methods `get_arguments`, `get_body_arguments`, `get_query_arguments`. */ - private DataFlow::Node argumentsMethod(DataFlow::TypeTracker t) { + private DataFlow::LocalSourceNode argumentsMethod(DataFlow::TypeTracker t) { t.startInAttr(["get_arguments", "get_body_arguments", "get_query_arguments"]) and result = instance() or @@ -217,7 +134,7 @@ private module Tornado { DataFlow::Node argumentsMethod() { result = argumentsMethod(DataFlow::TypeTracker::end()) } /** Gets a reference the `redirect` method. */ - private DataFlow::Node redirectMethod(DataFlow::TypeTracker t) { + private DataFlow::LocalSourceNode redirectMethod(DataFlow::TypeTracker t) { t.startInAttr("redirect") and result = instance() or @@ -225,10 +142,12 @@ private module Tornado { } /** Gets a reference the `redirect` method. */ - DataFlow::Node redirectMethod() { result = redirectMethod(DataFlow::TypeTracker::end()) } + DataFlow::Node redirectMethod() { + redirectMethod(DataFlow::TypeTracker::end()).flowsTo(result) + } /** Gets a reference to the `write` method. */ - private DataFlow::Node writeMethod(DataFlow::TypeTracker t) { + private DataFlow::LocalSourceNode writeMethod(DataFlow::TypeTracker t) { t.startInAttr("write") and result = instance() or @@ -236,7 +155,7 @@ private module Tornado { } /** Gets a reference to the `write` method. */ - DataFlow::Node writeMethod() { result = writeMethod(DataFlow::TypeTracker::end()) } + DataFlow::Node writeMethod() { writeMethod(DataFlow::TypeTracker::end()).flowsTo(result) } private class AdditionalTaintStep extends TaintTracking::AdditionalTaintStep { override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { @@ -279,15 +198,7 @@ private module Tornado { */ module Application { /** Gets a reference to the `tornado.web.Application` class. */ - private DataFlow::Node classRef(DataFlow::TypeTracker t) { - t.start() and - result = web_attr("Application") - or - exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t)) - } - - /** Gets a reference to the `tornado.web.Application` class. */ - DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) } + API::Node classRef() { result = web_attr("Application") } /** * A source of instances of `tornado.web.Application`, extend this class to model new instances. @@ -304,11 +215,11 @@ private module Tornado { class ClassInstantiation extends InstanceSource, DataFlow::CfgNode { override CallNode node; - ClassInstantiation() { node.getFunction() = classRef().asCfgNode() } + ClassInstantiation() { this = classRef().getACall() } } /** Gets a reference to an instance of `tornado.web.Application`. */ - private DataFlow::Node instance(DataFlow::TypeTracker t) { + private DataFlow::LocalSourceNode instance(DataFlow::TypeTracker t) { t.start() and result instanceof InstanceSource or @@ -316,10 +227,10 @@ private module Tornado { } /** Gets a reference to an instance of `tornado.web.Application`. */ - DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) } + DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) } /** Gets a reference to the `add_handlers` method. */ - private DataFlow::Node add_handlers(DataFlow::TypeTracker t) { + private DataFlow::LocalSourceNode add_handlers(DataFlow::TypeTracker t) { t.startInAttr("add_handlers") and result = instance() or @@ -327,7 +238,7 @@ private module Tornado { } /** Gets a reference to the `add_handlers` method. */ - DataFlow::Node add_handlers() { result = add_handlers(DataFlow::TypeTracker::end()) } + DataFlow::Node add_handlers() { add_handlers(DataFlow::TypeTracker::end()).flowsTo(result) } } } @@ -335,7 +246,7 @@ private module Tornado { // tornado.httputil // ------------------------------------------------------------------------- /** Gets a reference to the `tornado.httputil` module. */ - DataFlow::Node httputil() { result = tornado_attr("httputil") } + API::Node httputil() { result = tornado_attr("httputil") } /** Provides models for the `tornado.httputil` module */ module httputil { @@ -343,41 +254,7 @@ private module Tornado { * Gets a reference to the attribute `attr_name` of the `tornado.httputil` module. * WARNING: Only holds for a few predefined attributes. */ - private DataFlow::Node httputil_attr(DataFlow::TypeTracker t, string attr_name) { - attr_name in ["HTTPServerRequest"] and - ( - t.start() and - result = DataFlow::importNode("tornado.httputil" + "." + attr_name) - or - t.startInAttr(attr_name) and - result = httputil() - ) - or - // Due to bad performance when using normal setup with `httputil_attr(t2, attr_name).track(t2, t)` - // we have inlined that code and forced a join - exists(DataFlow::TypeTracker t2 | - exists(DataFlow::StepSummary summary | - httputil_attr_first_join(t2, attr_name, result, summary) and - t = t2.append(summary) - ) - ) - } - - pragma[nomagic] - private predicate httputil_attr_first_join( - DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, - DataFlow::StepSummary summary - ) { - DataFlow::StepSummary::step(httputil_attr(t2, attr_name), res, summary) - } - - /** - * Gets a reference to the attribute `attr_name` of the `tornado.httputil` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node httputil_attr(string attr_name) { - result = httputil_attr(DataFlow::TypeTracker::end(), attr_name) - } + private API::Node httputil_attr(string attr_name) { result = httputil().getMember(attr_name) } /** * Provides models for the `tornado.httputil.HttpServerRequest` class @@ -386,15 +263,7 @@ private module Tornado { */ module HttpServerRequest { /** Gets a reference to the `tornado.httputil.HttpServerRequest` class. */ - private DataFlow::Node classRef(DataFlow::TypeTracker t) { - t.start() and - result = httputil_attr("HttpServerRequest") - or - exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t)) - } - - /** Gets a reference to the `tornado.httputil.HttpServerRequest` class. */ - DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) } + API::Node classRef() { result = httputil_attr("HttpServerRequest") } /** * A source of instances of `tornado.httputil.HttpServerRequest`, extend this class to model new instances. @@ -411,7 +280,7 @@ private module Tornado { private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode { override CallNode node; - ClassInstantiation() { node.getFunction() = classRef().asCfgNode() } + ClassInstantiation() { this = classRef().getACall() } } /** Gets a reference to an instance of `tornado.httputil.HttpServerRequest`. */ From f93b68d4dc2683186a9c6513417c1a38e1fadf0a Mon Sep 17 00:00:00 2001 From: Taus Date: Tue, 13 Apr 2021 11:57:59 +0000 Subject: [PATCH 230/433] Python: Get rid of `_attr` methods --- .../src/semmle/python/frameworks/Tornado.qll | 35 ++++++------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/python/ql/src/semmle/python/frameworks/Tornado.qll b/python/ql/src/semmle/python/frameworks/Tornado.qll index 6268c9f0a4dd..509193c51600 100644 --- a/python/ql/src/semmle/python/frameworks/Tornado.qll +++ b/python/ql/src/semmle/python/frameworks/Tornado.qll @@ -22,27 +22,16 @@ private module Tornado { /** Gets a reference to the `tornado` module. */ API::Node tornado() { result = API::moduleImport("tornado") } - /** - * Gets a reference to the attribute `attr_name` of the `tornado` module. - * WARNING: Only holds for a few predefined attributes. - */ - private API::Node tornado_attr(string attr_name) { result = tornado().getMember(attr_name) } - /** Provides models for the `tornado` module. */ module tornado { // ------------------------------------------------------------------------- // tornado.web // ------------------------------------------------------------------------- /** Gets a reference to the `tornado.web` module. */ - API::Node web() { result = tornado_attr("web") } + API::Node web() { result = tornado().getMember("web") } /** Provides models for the `tornado.web` module */ module web { - /** - * Gets a reference to the attribute `attr_name` of the `tornado.web` module. - * WARNING: Only holds for a few predefined attributes. - */ - private API::Node web_attr(string attr_name) { result = web().getMember(attr_name) } /** * Provides models for the `tornado.web.RequestHandler` class and subclasses. @@ -51,7 +40,7 @@ private module Tornado { */ module RequestHandler { /** Gets a reference to the `tornado.web.RequestHandler` class or any subclass. */ - API::Node subclassRef() { result = web_attr("RequestHandler").getASubclass*() } + API::Node subclassRef() { result = web().getMember("RequestHandler").getASubclass*() } /** A RequestHandler class (most likely in project code). */ class RequestHandlerClass extends Class { @@ -198,7 +187,7 @@ private module Tornado { */ module Application { /** Gets a reference to the `tornado.web.Application` class. */ - API::Node classRef() { result = web_attr("Application") } + API::Node classRef() { result = web().getMember("Application") } /** * A source of instances of `tornado.web.Application`, extend this class to model new instances. @@ -246,15 +235,10 @@ private module Tornado { // tornado.httputil // ------------------------------------------------------------------------- /** Gets a reference to the `tornado.httputil` module. */ - API::Node httputil() { result = tornado_attr("httputil") } + API::Node httputil() { result = tornado().getMember("httputil") } /** Provides models for the `tornado.httputil` module */ module httputil { - /** - * Gets a reference to the attribute `attr_name` of the `tornado.httputil` module. - * WARNING: Only holds for a few predefined attributes. - */ - private API::Node httputil_attr(string attr_name) { result = httputil().getMember(attr_name) } /** * Provides models for the `tornado.httputil.HttpServerRequest` class @@ -263,7 +247,7 @@ private module Tornado { */ module HttpServerRequest { /** Gets a reference to the `tornado.httputil.HttpServerRequest` class. */ - API::Node classRef() { result = httputil_attr("HttpServerRequest") } + API::Node classRef() { result = httputil().getMember("HttpServerRequest") } /** * A source of instances of `tornado.httputil.HttpServerRequest`, extend this class to model new instances. @@ -284,7 +268,7 @@ private module Tornado { } /** Gets a reference to an instance of `tornado.httputil.HttpServerRequest`. */ - private DataFlow::Node instance(DataFlow::TypeTracker t) { + private DataFlow::LocalSourceNode instance(DataFlow::TypeTracker t) { t.start() and result instanceof InstanceSource or @@ -292,10 +276,11 @@ private module Tornado { } /** Gets a reference to an instance of `tornado.httputil.HttpServerRequest`. */ - DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) } + DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) } + /** Gets a reference to the `full_url` method. */ - private DataFlow::Node full_url(DataFlow::TypeTracker t) { + private DataFlow::LocalSourceNode full_url(DataFlow::TypeTracker t) { t.startInAttr("full_url") and result = instance() or @@ -303,7 +288,7 @@ private module Tornado { } /** Gets a reference to the `full_url` method. */ - DataFlow::Node full_url() { result = full_url(DataFlow::TypeTracker::end()) } + DataFlow::Node full_url() { full_url(DataFlow::TypeTracker::end()).flowsTo(result) } private class AdditionalTaintStep extends TaintTracking::AdditionalTaintStep { override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { From 1a4845f4174e51bc439b521bf42cf3ba67f5dbca Mon Sep 17 00:00:00 2001 From: Taus Date: Tue, 13 Apr 2021 12:20:08 +0000 Subject: [PATCH 231/433] Python: Restrict types a bit The `CallCfgNode` restrictions are familiar and useful. Restricting `InstanceSource` to extend `LocalSourceNode` is novel, but I think it makes sense. It will act as a good reminder to anyone extending `InstanceSource` that the node in question is a `LocalSourceNode`, which will be enforced by the return type of the internal type tracker anyway. --- .../src/semmle/python/frameworks/Tornado.qll | 33 +++++++------------ 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/python/ql/src/semmle/python/frameworks/Tornado.qll b/python/ql/src/semmle/python/frameworks/Tornado.qll index 509193c51600..f82e093cb94f 100644 --- a/python/ql/src/semmle/python/frameworks/Tornado.qll +++ b/python/ql/src/semmle/python/frameworks/Tornado.qll @@ -32,7 +32,6 @@ private module Tornado { /** Provides models for the `tornado.web` module */ module web { - /** * Provides models for the `tornado.web.RequestHandler` class and subclasses. * @@ -75,7 +74,7 @@ private module Tornado { * * Use the predicate `RequestHandler::instance()` to get references to instances of the `tornado.web.RequestHandler` class or any subclass. */ - abstract class InstanceSource extends DataFlow::Node { } + abstract class InstanceSource extends DataFlow::LocalSourceNode { } /** The `self` parameter in a method on the `tornado.web.RequestHandler` class or any subclass. */ private class SelfParam extends InstanceSource, RemoteFlowSource::Range, @@ -120,7 +119,9 @@ private module Tornado { } /** Gets a reference to one of the methods `get_arguments`, `get_body_arguments`, `get_query_arguments`. */ - DataFlow::Node argumentsMethod() { result = argumentsMethod(DataFlow::TypeTracker::end()) } + DataFlow::Node argumentsMethod() { + argumentsMethod(DataFlow::TypeTracker::end()).flowsTo(result) + } /** Gets a reference the `redirect` method. */ private DataFlow::LocalSourceNode redirectMethod(DataFlow::TypeTracker t) { @@ -198,12 +199,10 @@ private module Tornado { * * Use the predicate `Application::instance()` to get references to instances of `tornado.web.Application`. */ - abstract class InstanceSource extends DataFlow::Node { } + abstract class InstanceSource extends DataFlow::LocalSourceNode { } /** A direct instantiation of `tornado.web.Application`. */ - class ClassInstantiation extends InstanceSource, DataFlow::CfgNode { - override CallNode node; - + class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode { ClassInstantiation() { this = classRef().getACall() } } @@ -239,7 +238,6 @@ private module Tornado { /** Provides models for the `tornado.httputil` module */ module httputil { - /** * Provides models for the `tornado.httputil.HttpServerRequest` class * @@ -258,12 +256,10 @@ private module Tornado { * * Use the predicate `HttpServerRequest::instance()` to get references to instances of `tornado.httputil.HttpServerRequest`. */ - abstract class InstanceSource extends DataFlow::Node { } + abstract class InstanceSource extends DataFlow::LocalSourceNode { } /** A direct instantiation of `tornado.httputil.HttpServerRequest`. */ - private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode { - override CallNode node; - + private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode { ClassInstantiation() { this = classRef().getACall() } } @@ -277,7 +273,6 @@ private module Tornado { /** Gets a reference to an instance of `tornado.httputil.HttpServerRequest`. */ DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) } - /** Gets a reference to the `full_url` method. */ private DataFlow::LocalSourceNode full_url(DataFlow::TypeTracker t) { @@ -430,11 +425,9 @@ private module Tornado { * See https://www.tornadoweb.org/en/stable/web.html?highlight=write#tornado.web.RequestHandler.redirect */ private class TornadoRequestHandlerRedirectCall extends HTTP::Server::HttpRedirectResponse::Range, - DataFlow::CfgNode { - override CallNode node; - + DataFlow::CallCfgNode { TornadoRequestHandlerRedirectCall() { - node.getFunction() = tornado::web::RequestHandler::redirectMethod().asCfgNode() + this.getFunction() = tornado::web::RequestHandler::redirectMethod() } override DataFlow::Node getRedirectLocation() { @@ -454,11 +447,9 @@ private module Tornado { * See https://www.tornadoweb.org/en/stable/web.html?highlight=write#tornado.web.RequestHandler.write */ private class TornadoRequestHandlerWriteCall extends HTTP::Server::HttpResponse::Range, - DataFlow::CfgNode { - override CallNode node; - + DataFlow::CallCfgNode { TornadoRequestHandlerWriteCall() { - node.getFunction() = tornado::web::RequestHandler::writeMethod().asCfgNode() + this.getFunction() = tornado::web::RequestHandler::writeMethod() } override DataFlow::Node getBody() { From 7825a2cdfcc99c940cae55acc95058df534c48e4 Mon Sep 17 00:00:00 2001 From: Taus Date: Tue, 13 Apr 2021 12:48:45 +0000 Subject: [PATCH 232/433] Python: Add change note --- python/change-notes/2021-04-13-pep249-api-graphs.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 python/change-notes/2021-04-13-pep249-api-graphs.md diff --git a/python/change-notes/2021-04-13-pep249-api-graphs.md b/python/change-notes/2021-04-13-pep249-api-graphs.md new file mode 100644 index 000000000000..d06b2bffd0cf --- /dev/null +++ b/python/change-notes/2021-04-13-pep249-api-graphs.md @@ -0,0 +1,4 @@ +lgtm,codescanning +* Modelling of libraries supporting PEP249 has been changed to use API graphs. When defining new + models, the relevant extension point is now `PEP249ModuleApiNode` in the `PEP249` module, instead + of `PEP249Module`. The latter class has now been deprecated. From a404faa302b634a77fb220118d906f9e240fc02f Mon Sep 17 00:00:00 2001 From: Taus Date: Tue, 13 Apr 2021 15:05:44 +0200 Subject: [PATCH 233/433] Python: Use American English in change note Co-authored-by: intrigus-lgtm <60750685+intrigus-lgtm@users.noreply.github.com> --- python/change-notes/2021-04-13-pep249-api-graphs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/change-notes/2021-04-13-pep249-api-graphs.md b/python/change-notes/2021-04-13-pep249-api-graphs.md index d06b2bffd0cf..fb8e2f0b1fbc 100644 --- a/python/change-notes/2021-04-13-pep249-api-graphs.md +++ b/python/change-notes/2021-04-13-pep249-api-graphs.md @@ -1,4 +1,4 @@ lgtm,codescanning -* Modelling of libraries supporting PEP249 has been changed to use API graphs. When defining new +* Modeling of libraries supporting PEP249 has been changed to use API graphs. When defining new models, the relevant extension point is now `PEP249ModuleApiNode` in the `PEP249` module, instead of `PEP249Module`. The latter class has now been deprecated. From d1457995dd9fd565e18ec0c28fa5f1aa7199115b Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Tue, 13 Apr 2021 16:15:37 +0200 Subject: [PATCH 234/433] C++: Use range analysis in Overflow.qll --- cpp/ql/src/semmle/code/cpp/security/Overflow.qll | 7 +++++-- .../semmle/extreme/ArithmeticWithExtremeValues.expected | 2 -- .../query-tests/Security/CWE/CWE-190/semmle/extreme/test.c | 4 ++-- .../CWE/CWE-190/semmle/tainted/ArithmeticTainted.expected | 3 --- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/security/Overflow.qll b/cpp/ql/src/semmle/code/cpp/security/Overflow.qll index e7ad1c559e63..3f7a7b17b059 100644 --- a/cpp/ql/src/semmle/code/cpp/security/Overflow.qll +++ b/cpp/ql/src/semmle/code/cpp/security/Overflow.qll @@ -5,6 +5,7 @@ import cpp import semmle.code.cpp.controlflow.Dominance +import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis /** * Holds if the value of `use` is guarded using `abs`. @@ -94,9 +95,10 @@ predicate guardedGreater(Operation e, Expr use) { VariableAccess varUse(LocalScopeVariable v) { result = v.getAnAccess() } /** - * Holds if `e` is not guarded against overflow by `use`. + * Holds if `e` potentially overflows and `use` is an operand of `e` that is not guarded. */ predicate missingGuardAgainstOverflow(Operation e, VariableAccess use) { + convertedExprMightOverflow(e) and use = e.getAnOperand() and exists(LocalScopeVariable v | use.getTarget() = v | // overflow possible if large @@ -115,9 +117,10 @@ predicate missingGuardAgainstOverflow(Operation e, VariableAccess use) { } /** - * Holds if `e` is not guarded against underflow by `use`. + * Holds if `e` potentially underflows and `use` is an operand of `e` that is not guarded. */ predicate missingGuardAgainstUnderflow(Operation e, VariableAccess use) { + convertedExprMightOverflowNegatively(e) and use = e.getAnOperand() and exists(LocalScopeVariable v | use.getTarget() = v | // underflow possible if use is left operand and small diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/extreme/ArithmeticWithExtremeValues.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/extreme/ArithmeticWithExtremeValues.expected index a46371f36b6c..cbaf5d630795 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/extreme/ArithmeticWithExtremeValues.expected +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/extreme/ArithmeticWithExtremeValues.expected @@ -3,6 +3,4 @@ | test.c:50:3:50:5 | sc3 | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.c:49:9:49:16 | 127 | Extreme value | | test.c:59:3:59:5 | sc6 | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.c:58:9:58:16 | 127 | Extreme value | | test.c:63:3:63:5 | sc8 | $@ flows to here and is used in arithmetic, potentially causing an underflow. | test.c:62:9:62:16 | - ... | Extreme value | -| test.c:75:3:75:5 | sc1 | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.c:74:9:74:16 | 127 | Extreme value | -| test.c:76:3:76:5 | sc1 | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.c:74:9:74:16 | 127 | Extreme value | | test.c:124:9:124:9 | x | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.c:118:17:118:23 | 2147483647 | Extreme value | diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/extreme/test.c b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/extreme/test.c index 8c40d984ee0a..8760641c8e2d 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/extreme/test.c +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/extreme/test.c @@ -72,8 +72,8 @@ void test_negatives() { signed char sc1, sc2, sc3, sc4, sc5, sc6, sc7, sc8; sc1 = CHAR_MAX; - sc1 += 0; // GOOD [FALSE POSITIVE] - sc1 += -1; // GOOD [FALSE POSITIVE] + sc1 += 0; // GOOD + sc1 += -1; // GOOD sc2 = CHAR_MIN; sc2 += -1; // BAD [NOT DETECTED] sc3 = CHAR_MIN; diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/tainted/ArithmeticTainted.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/tainted/ArithmeticTainted.expected index 8bb25025b868..bdf00e0a5df5 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/tainted/ArithmeticTainted.expected +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/tainted/ArithmeticTainted.expected @@ -1,8 +1,5 @@ | test2.cpp:14:11:14:11 | v | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test2.cpp:25:22:25:23 | & ... | User-provided value | | test2.cpp:14:11:14:11 | v | $@ flows to here and is used in arithmetic, potentially causing an underflow. | test2.cpp:25:22:25:23 | & ... | User-provided value | -| test3.c:15:10:15:10 | x | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test3.c:11:15:11:18 | argv | User-provided value | -| test3.c:15:14:15:14 | y | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test3.c:11:15:11:18 | argv | User-provided value | -| test3.c:15:18:15:18 | z | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test3.c:11:15:11:18 | argv | User-provided value | | test5.cpp:17:6:17:18 | call to getTaintedInt | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test5.cpp:9:7:9:9 | buf | User-provided value | | test5.cpp:19:6:19:6 | y | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test5.cpp:9:7:9:9 | buf | User-provided value | | test5.cpp:19:6:19:6 | y | $@ flows to here and is used in arithmetic, potentially causing an underflow. | test5.cpp:9:7:9:9 | buf | User-provided value | From 10084115947e610724e8b6fd2d42e1f4ea7d7327 Mon Sep 17 00:00:00 2001 From: Taus Date: Tue, 13 Apr 2021 14:49:44 +0000 Subject: [PATCH 235/433] Python: Use API graphs in Fabric model --- .../src/semmle/python/frameworks/Fabric.qll | 360 ++---------------- 1 file changed, 41 insertions(+), 319 deletions(-) diff --git a/python/ql/src/semmle/python/frameworks/Fabric.qll b/python/ql/src/semmle/python/frameworks/Fabric.qll index 764dce0de808..6e92cddfdfd7 100644 --- a/python/ql/src/semmle/python/frameworks/Fabric.qll +++ b/python/ql/src/semmle/python/frameworks/Fabric.qll @@ -11,6 +11,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 /** * Provides classes modeling security-relevant aspects of the `fabric` PyPI package, for @@ -20,54 +21,13 @@ private import semmle.python.Concepts */ private module FabricV1 { /** Gets a reference to the `fabric` module. */ - private DataFlow::Node fabric(DataFlow::TypeTracker t) { - t.start() and - result = DataFlow::importNode("fabric") - or - exists(DataFlow::TypeTracker t2 | result = fabric(t2).track(t2, t)) - } - - /** Gets a reference to the `fabric` module. */ - DataFlow::Node fabric() { result = fabric(DataFlow::TypeTracker::end()) } - - /** - * Gets a reference to the attribute `attr_name` of the `fabric` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node fabric_attr(DataFlow::TypeTracker t, string attr_name) { - attr_name in ["api"] and - ( - t.start() and - result = DataFlow::importNode("fabric" + "." + attr_name) - or - t.startInAttr(attr_name) and - result = fabric() - ) - or - // Due to bad performance when using normal setup with `fabric_attr(t2, attr_name).track(t2, t)` - // we have inlined that code and forced a join - exists(DataFlow::TypeTracker t2 | - exists(DataFlow::StepSummary summary | - fabric_attr_first_join(t2, attr_name, result, summary) and - t = t2.append(summary) - ) - ) - } - - pragma[nomagic] - private predicate fabric_attr_first_join( - DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary - ) { - DataFlow::StepSummary::step(fabric_attr(t2, attr_name), res, summary) - } + API::Node fabric() { result = API::moduleImport("fabric") } /** * Gets a reference to the attribute `attr_name` of the `fabric` module. * WARNING: Only holds for a few predefined attributes. */ - private DataFlow::Node fabric_attr(string attr_name) { - result = fabric_attr(DataFlow::TypeTracker::end(), attr_name) - } + private API::Node fabric_attr(string attr_name) { result = fabric().getMember(attr_name) } /** Provides models for the `fabric` module. */ module fabric { @@ -75,7 +35,7 @@ private module FabricV1 { // fabric.api // ------------------------------------------------------------------------- /** Gets a reference to the `fabric.api` module. */ - DataFlow::Node api() { result = fabric_attr("api") } + API::Node api() { result = fabric_attr("api") } /** Provides models for the `fabric.api` module */ module api { @@ -83,41 +43,7 @@ private module FabricV1 { * Gets a reference to the attribute `attr_name` of the `fabric.api` module. * WARNING: Only holds for a few predefined attributes. */ - private DataFlow::Node api_attr(DataFlow::TypeTracker t, string attr_name) { - attr_name in ["run", "local", "sudo"] and - ( - t.start() and - result = DataFlow::importNode("fabric.api" + "." + attr_name) - or - t.startInAttr(attr_name) and - result = api() - ) - or - // Due to bad performance when using normal setup with `api_attr(t2, attr_name).track(t2, t)` - // we have inlined that code and forced a join - exists(DataFlow::TypeTracker t2 | - exists(DataFlow::StepSummary summary | - api_attr_first_join(t2, attr_name, result, summary) and - t = t2.append(summary) - ) - ) - } - - pragma[nomagic] - private predicate api_attr_first_join( - DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, - DataFlow::StepSummary summary - ) { - DataFlow::StepSummary::step(api_attr(t2, attr_name), res, summary) - } - - /** - * Gets a reference to the attribute `attr_name` of the `fabric.api` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node api_attr(string attr_name) { - result = api_attr(DataFlow::TypeTracker::end(), attr_name) - } + private API::Node api_attr(string attr_name) { result = api().getMember(attr_name) } /** * A call to either @@ -130,12 +56,8 @@ private module FabricV1 { * - https://docs.fabfile.org/en/1.14/api/core/operations.html#fabric.operations.sudo */ private class FabricApiLocalRunSudoCall extends SystemCommandExecution::Range, - DataFlow::CfgNode { - override CallNode node; - - FabricApiLocalRunSudoCall() { - node.getFunction() = api_attr(["local", "run", "sudo"]).asCfgNode() - } + DataFlow::CallCfgNode { + FabricApiLocalRunSudoCall() { this = api_attr(["local", "run", "sudo"]).getACall() } override DataFlow::Node getCommand() { result.asCfgNode() = [node.getArg(0), node.getArgByName("command")] @@ -153,61 +75,13 @@ private module FabricV1 { */ private module FabricV2 { /** Gets a reference to the `fabric` module. */ - private DataFlow::Node fabric(DataFlow::TypeTracker t) { - t.start() and - result = DataFlow::importNode("fabric") - or - exists(DataFlow::TypeTracker t2 | result = fabric(t2).track(t2, t)) - } - - /** Gets a reference to the `fabric` module. */ - DataFlow::Node fabric() { result = fabric(DataFlow::TypeTracker::end()) } - - /** - * Gets a reference to the attribute `attr_name` of the `fabric` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node fabric_attr(DataFlow::TypeTracker t, string attr_name) { - attr_name in [ - // connection.py - "connection", "Connection", - // group.py - "group", "SerialGroup", "ThreadingGroup", - // tasks.py - "tasks", "task" - ] and - ( - t.start() and - result = DataFlow::importNode("fabric" + "." + attr_name) - or - t.startInAttr(attr_name) and - result = fabric() - ) - or - // Due to bad performance when using normal setup with `fabric_attr(t2, attr_name).track(t2, t)` - // we have inlined that code and forced a join - exists(DataFlow::TypeTracker t2 | - exists(DataFlow::StepSummary summary | - fabric_attr_first_join(t2, attr_name, result, summary) and - t = t2.append(summary) - ) - ) - } - - pragma[nomagic] - private predicate fabric_attr_first_join( - DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary - ) { - DataFlow::StepSummary::step(fabric_attr(t2, attr_name), res, summary) - } + API::Node fabric() { result = API::moduleImport("fabric") } /** * Gets a reference to the attribute `attr_name` of the `fabric` module. * WARNING: Only holds for a few predefined attributes. */ - private DataFlow::Node fabric_attr(string attr_name) { - result = fabric_attr(DataFlow::TypeTracker::end(), attr_name) - } + private API::Node fabric_attr(string attr_name) { result = fabric().getMember(attr_name) } /** Provides models for the `fabric` module. */ module fabric { @@ -215,7 +89,7 @@ private module FabricV2 { // fabric.connection // ------------------------------------------------------------------------- /** Gets a reference to the `fabric.connection` module. */ - DataFlow::Node connection() { result = fabric_attr("connection") } + API::Node connection() { result = fabric_attr("connection") } /** Provides models for the `fabric.connection` module */ module connection { @@ -223,40 +97,8 @@ private module FabricV2 { * Gets a reference to the attribute `attr_name` of the `fabric.connection` module. * WARNING: Only holds for a few predefined attributes. */ - private DataFlow::Node connection_attr(DataFlow::TypeTracker t, string attr_name) { - attr_name in ["Connection"] and - ( - t.start() and - result = DataFlow::importNode("fabric.connection" + "." + attr_name) - or - t.startInAttr(attr_name) and - result = connection() - ) - or - // Due to bad performance when using normal setup with `connection_attr(t2, attr_name).track(t2, t)` - // we have inlined that code and forced a join - exists(DataFlow::TypeTracker t2 | - exists(DataFlow::StepSummary summary | - connection_attr_first_join(t2, attr_name, result, summary) and - t = t2.append(summary) - ) - ) - } - - pragma[nomagic] - private predicate connection_attr_first_join( - DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, - DataFlow::StepSummary summary - ) { - DataFlow::StepSummary::step(connection_attr(t2, attr_name), res, summary) - } - - /** - * Gets a reference to the attribute `attr_name` of the `fabric.connection` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node connection_attr(string attr_name) { - result = connection_attr(DataFlow::TypeTracker::end(), attr_name) + private API::Node connection_attr(string attr_name) { + result = connection().getMember(attr_name) } /** @@ -266,20 +108,10 @@ private module FabricV2 { */ module Connection { /** Gets a reference to the `fabric.connection.Connection` class. */ - private DataFlow::Node classRef(DataFlow::TypeTracker t) { - t.start() and - result = connection_attr("Connection") - or - // handle `fabric.Connection` alias - t.start() and - result = fabric_attr("Connection") - or - exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t)) + API::Node classRef() { + result in [fabric_attr("Connection"), connection_attr("Connection")] } - /** Gets a reference to the `fabric.connection.Connection` class. */ - DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) } - /** * A source of instances of `fabric.connection.Connection`, extend this class to model new instances. * @@ -289,16 +121,14 @@ private module FabricV2 { * * Use the predicate `Connection::instance()` to get references to instances of `fabric.connection.Connection`. */ - abstract class InstanceSource extends DataFlow::Node { } - - private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode { - override CallNode node; + abstract class InstanceSource extends DataFlow::LocalSourceNode { } - ClassInstantiation() { node.getFunction() = classRef().asCfgNode() } + private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode { + ClassInstantiation() { this = classRef().getACall() } } /** Gets a reference to an instance of `fabric.connection.Connection`. */ - private DataFlow::Node instance(DataFlow::TypeTracker t) { + private DataFlow::LocalSourceNode instance(DataFlow::TypeTracker t) { t.start() and result instanceof InstanceSource or @@ -306,7 +136,7 @@ private module FabricV2 { } /** Gets a reference to an instance of `fabric.connection.Connection`. */ - DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) } + DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) } /** * Gets a reference to either `run`, `sudo`, or `local` method on a @@ -317,7 +147,7 @@ private module FabricV2 { * - https://docs.fabfile.org/en/2.5/api/connection.html#fabric.connection.Connection.sudo * - https://docs.fabfile.org/en/2.5/api/connection.html#fabric.connection.Connection.local */ - private DataFlow::Node instanceRunMethods(DataFlow::TypeTracker t) { + private DataFlow::LocalSourceNode instanceRunMethods(DataFlow::TypeTracker t) { t.startInAttr(["run", "sudo", "local"]) and result = instance() or @@ -334,7 +164,7 @@ private module FabricV2 { * - https://docs.fabfile.org/en/2.5/api/connection.html#fabric.connection.Connection.local */ DataFlow::Node instanceRunMethods() { - result = instanceRunMethods(DataFlow::TypeTracker::end()) + instanceRunMethods(DataFlow::TypeTracker::end()).flowsTo(result) } } } @@ -347,11 +177,9 @@ private module FabricV2 { * - https://docs.fabfile.org/en/2.5/api/connection.html#fabric.connection.Connection.local */ private class FabricConnectionRunSudoLocalCall extends SystemCommandExecution::Range, - DataFlow::CfgNode { - override CallNode node; - + DataFlow::CallCfgNode { FabricConnectionRunSudoLocalCall() { - node.getFunction() = fabric::connection::Connection::instanceRunMethods().asCfgNode() + this.getFunction() = fabric::connection::Connection::instanceRunMethods() } override DataFlow::Node getCommand() { @@ -363,34 +191,19 @@ private module FabricV2 { // fabric.tasks // ------------------------------------------------------------------------- /** Gets a reference to the `fabric.tasks` module. */ - DataFlow::Node tasks() { result = fabric_attr("tasks") } + API::Node tasks() { result = fabric_attr("tasks") } /** Provides models for the `fabric.tasks` module */ module tasks { /** Gets a reference to the `fabric.tasks.task` decorator. */ - private DataFlow::Node task(DataFlow::TypeTracker t) { - t.start() and - result = DataFlow::importNode("fabric.tasks.task") - or - t.startInAttr("task") and - result = tasks() - or - // Handle `fabric.task` alias - t.startInAttr("task") and - result = fabric() - or - exists(DataFlow::TypeTracker t2 | result = task(t2).track(t2, t)) - } - - /** Gets a reference to the `fabric.tasks.task` decorator. */ - DataFlow::Node task() { result = task(DataFlow::TypeTracker::end()) } + API::Node task() { result in [tasks().getMember("task"), fabric().getMember("task")] } } class FabricTaskFirstParamConnectionInstance extends fabric::connection::Connection::InstanceSource, DataFlow::ParameterNode { FabricTaskFirstParamConnectionInstance() { exists(Function func | - func.getADecorator() = fabric::tasks::task().asExpr() and + func.getADecorator() = fabric::tasks::task().getAUse().asExpr() and this.getParameter() = func.getArg(0) ) } @@ -400,7 +213,7 @@ private module FabricV2 { // fabric.group // ------------------------------------------------------------------------- /** Gets a reference to the `fabric.group` module. */ - DataFlow::Node group() { result = fabric_attr("group") } + API::Node group() { result = fabric_attr("group") } /** Provides models for the `fabric.group` module */ module group { @@ -408,41 +221,7 @@ private module FabricV2 { * Gets a reference to the attribute `attr_name` of the `fabric.group` module. * WARNING: Only holds for a few predefined attributes. */ - private DataFlow::Node group_attr(DataFlow::TypeTracker t, string attr_name) { - attr_name in ["SerialGroup", "ThreadingGroup"] and - ( - t.start() and - result = DataFlow::importNode("fabric.group" + "." + attr_name) - or - t.startInAttr(attr_name) and - result = group() - ) - or - // Due to bad performance when using normal setup with `group_attr(t2, attr_name).track(t2, t)` - // we have inlined that code and forced a join - exists(DataFlow::TypeTracker t2 | - exists(DataFlow::StepSummary summary | - group_attr_first_join(t2, attr_name, result, summary) and - t = t2.append(summary) - ) - ) - } - - pragma[nomagic] - private predicate group_attr_first_join( - DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, - DataFlow::StepSummary summary - ) { - DataFlow::StepSummary::step(group_attr(t2, attr_name), res, summary) - } - - /** - * Gets a reference to the attribute `attr_name` of the `fabric.group` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node group_attr(string attr_name) { - result = group_attr(DataFlow::TypeTracker::end(), attr_name) - } + private API::Node group_attr(string attr_name) { result = group().getMember(attr_name) } /** * Provides models for the `fabric.group.Group` class and its subclasses. @@ -465,41 +244,20 @@ private module FabricV2 { * * Use `Group::subclassInstance()` predicate to get references to an instance of a subclass of `fabric.group.Group`. */ - abstract class SubclassInstanceSource extends DataFlow::Node { } - - /** Gets a reference to an instance of a subclass of `fabric.group.Group`. */ - private DataFlow::Node subclassInstance(DataFlow::TypeTracker t) { - t.start() and - result instanceof SubclassInstanceSource - or - exists(DataFlow::TypeTracker t2 | result = subclassInstance(t2).track(t2, t)) + abstract class ModeledSubclass extends API::Node { + /** Gets a string representation of this element. */ + override string toString() { result = this.(API::Node).toString() } } /** Gets a reference to an instance of a subclass of `fabric.group.Group`. */ - DataFlow::Node subclassInstance() { - result = subclassInstance(DataFlow::TypeTracker::end()) - } + API::Node subclassInstance() { result = any(ModeledSubclass m).getASubclass*().getReturn() } /** * Gets a reference to the `run` method on an instance of a subclass of `fabric.group.Group`. * * See https://docs.fabfile.org/en/2.5/api/group.html#fabric.group.Group.run */ - private DataFlow::Node subclassInstanceRunMethod(DataFlow::TypeTracker t) { - t.startInAttr("run") and - result = subclassInstance() - or - exists(DataFlow::TypeTracker t2 | result = subclassInstanceRunMethod(t2).track(t2, t)) - } - - /** - * Gets a reference to the `run` method on an instance of a subclass of `fabric.group.Group`. - * - * See https://docs.fabfile.org/en/2.5/api/group.html#fabric.group.Group.run - */ - DataFlow::Node subclassInstanceRunMethod() { - result = subclassInstanceRunMethod(DataFlow::TypeTracker::end()) - } + API::Node subclassInstanceRunMethod() { result = subclassInstance().getMember("run") } } /** @@ -507,12 +265,8 @@ private module FabricV2 { * * See https://docs.fabfile.org/en/2.5/api/group.html#fabric.group.Group.run */ - private class FabricGroupRunCall extends SystemCommandExecution::Range, DataFlow::CfgNode { - override CallNode node; - - FabricGroupRunCall() { - node.getFunction() = fabric::group::Group::subclassInstanceRunMethod().asCfgNode() - } + private class FabricGroupRunCall extends SystemCommandExecution::Range, DataFlow::CallCfgNode { + FabricGroupRunCall() { this = fabric::group::Group::subclassInstanceRunMethod().getACall() } override DataFlow::Node getCommand() { result.asCfgNode() = [node.getArg(0), node.getArgByName("command")] @@ -525,25 +279,8 @@ private module FabricV2 { * See https://docs.fabfile.org/en/2.5/api/group.html#fabric.group.SerialGroup. */ module SerialGroup { - /** Gets a reference to the `fabric.group.SerialGroup` class. */ - private DataFlow::Node classRef(DataFlow::TypeTracker t) { - t.start() and - result = group_attr("SerialGroup") - or - // Handle `fabric.SerialGroup` alias - t.start() and - result = fabric_attr("SerialGroup") - or - exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t)) - } - - /** Gets a reference to the `fabric.group.SerialGroup` class. */ - private DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) } - - private class ClassInstantiation extends Group::SubclassInstanceSource, DataFlow::CfgNode { - override CallNode node; - - ClassInstantiation() { node.getFunction() = classRef().asCfgNode() } + private class ClassInstantiation extends Group::ModeledSubclass { + ClassInstantiation() { this in [group_attr("SerialGroup"), fabric_attr("SerialGroup")] } } } @@ -553,25 +290,10 @@ private module FabricV2 { * See https://docs.fabfile.org/en/2.5/api/group.html#fabric.group.ThreadingGroup. */ module ThreadingGroup { - /** Gets a reference to the `fabric.group.ThreadingGroup` class. */ - private DataFlow::Node classRef(DataFlow::TypeTracker t) { - t.start() and - result = group_attr("ThreadingGroup") - or - // Handle `fabric.ThreadingGroup` alias - t.start() and - result = fabric_attr("ThreadingGroup") - or - exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t)) - } - - /** Gets a reference to the `fabric.group.ThreadingGroup` class. */ - DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) } - - private class ClassInstantiation extends Group::SubclassInstanceSource, DataFlow::CfgNode { - override CallNode node; - - ClassInstantiation() { node.getFunction() = classRef().asCfgNode() } + private class ClassInstantiation extends Group::ModeledSubclass { + ClassInstantiation() { + this in [group_attr("ThreadingGroup"), fabric_attr("ThreadingGroup")] + } } } } From 7f131c1f352e7829fff6fab17fa38a51dd65dee7 Mon Sep 17 00:00:00 2001 From: Taus Date: Tue, 13 Apr 2021 14:55:44 +0000 Subject: [PATCH 236/433] Python: Get rid of `_attr` predicates --- .../src/semmle/python/frameworks/Fabric.qll | 52 ++++--------------- 1 file changed, 11 insertions(+), 41 deletions(-) diff --git a/python/ql/src/semmle/python/frameworks/Fabric.qll b/python/ql/src/semmle/python/frameworks/Fabric.qll index 6e92cddfdfd7..177f5592f665 100644 --- a/python/ql/src/semmle/python/frameworks/Fabric.qll +++ b/python/ql/src/semmle/python/frameworks/Fabric.qll @@ -23,28 +23,16 @@ private module FabricV1 { /** Gets a reference to the `fabric` module. */ API::Node fabric() { result = API::moduleImport("fabric") } - /** - * Gets a reference to the attribute `attr_name` of the `fabric` module. - * WARNING: Only holds for a few predefined attributes. - */ - private API::Node fabric_attr(string attr_name) { result = fabric().getMember(attr_name) } - /** Provides models for the `fabric` module. */ module fabric { // ------------------------------------------------------------------------- // fabric.api // ------------------------------------------------------------------------- /** Gets a reference to the `fabric.api` module. */ - API::Node api() { result = fabric_attr("api") } + API::Node api() { result = fabric().getMember("api") } /** Provides models for the `fabric.api` module */ module api { - /** - * Gets a reference to the attribute `attr_name` of the `fabric.api` module. - * WARNING: Only holds for a few predefined attributes. - */ - private API::Node api_attr(string attr_name) { result = api().getMember(attr_name) } - /** * A call to either * - `fabric.api.local` @@ -57,7 +45,7 @@ private module FabricV1 { */ private class FabricApiLocalRunSudoCall extends SystemCommandExecution::Range, DataFlow::CallCfgNode { - FabricApiLocalRunSudoCall() { this = api_attr(["local", "run", "sudo"]).getACall() } + FabricApiLocalRunSudoCall() { this = api().getMember(["local", "run", "sudo"]).getACall() } override DataFlow::Node getCommand() { result.asCfgNode() = [node.getArg(0), node.getArgByName("command")] @@ -77,30 +65,16 @@ private module FabricV2 { /** Gets a reference to the `fabric` module. */ API::Node fabric() { result = API::moduleImport("fabric") } - /** - * Gets a reference to the attribute `attr_name` of the `fabric` module. - * WARNING: Only holds for a few predefined attributes. - */ - private API::Node fabric_attr(string attr_name) { result = fabric().getMember(attr_name) } - /** Provides models for the `fabric` module. */ module fabric { // ------------------------------------------------------------------------- // fabric.connection // ------------------------------------------------------------------------- /** Gets a reference to the `fabric.connection` module. */ - API::Node connection() { result = fabric_attr("connection") } + API::Node connection() { result = fabric().getMember("connection") } /** Provides models for the `fabric.connection` module */ module connection { - /** - * Gets a reference to the attribute `attr_name` of the `fabric.connection` module. - * WARNING: Only holds for a few predefined attributes. - */ - private API::Node connection_attr(string attr_name) { - result = connection().getMember(attr_name) - } - /** * Provides models for the `fabric.connection.Connection` class * @@ -109,7 +83,7 @@ private module FabricV2 { module Connection { /** Gets a reference to the `fabric.connection.Connection` class. */ API::Node classRef() { - result in [fabric_attr("Connection"), connection_attr("Connection")] + result in [fabric().getMember("Connection"), connection().getMember("Connection")] } /** @@ -136,7 +110,7 @@ private module FabricV2 { } /** Gets a reference to an instance of `fabric.connection.Connection`. */ - DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) } + DataFlow::LocalSourceNode instance() { result = instance(DataFlow::TypeTracker::end()) } /** * Gets a reference to either `run`, `sudo`, or `local` method on a @@ -191,7 +165,7 @@ private module FabricV2 { // fabric.tasks // ------------------------------------------------------------------------- /** Gets a reference to the `fabric.tasks` module. */ - API::Node tasks() { result = fabric_attr("tasks") } + API::Node tasks() { result = fabric().getMember("tasks") } /** Provides models for the `fabric.tasks` module */ module tasks { @@ -213,16 +187,10 @@ private module FabricV2 { // fabric.group // ------------------------------------------------------------------------- /** Gets a reference to the `fabric.group` module. */ - API::Node group() { result = fabric_attr("group") } + API::Node group() { result = fabric().getMember("group") } /** Provides models for the `fabric.group` module */ module group { - /** - * Gets a reference to the attribute `attr_name` of the `fabric.group` module. - * WARNING: Only holds for a few predefined attributes. - */ - private API::Node group_attr(string attr_name) { result = group().getMember(attr_name) } - /** * Provides models for the `fabric.group.Group` class and its subclasses. * @@ -280,7 +248,9 @@ private module FabricV2 { */ module SerialGroup { private class ClassInstantiation extends Group::ModeledSubclass { - ClassInstantiation() { this in [group_attr("SerialGroup"), fabric_attr("SerialGroup")] } + ClassInstantiation() { + this in [group().getMember("SerialGroup"), fabric().getMember("SerialGroup")] + } } } @@ -292,7 +262,7 @@ private module FabricV2 { module ThreadingGroup { private class ClassInstantiation extends Group::ModeledSubclass { ClassInstantiation() { - this in [group_attr("ThreadingGroup"), fabric_attr("ThreadingGroup")] + this in [group().getMember("ThreadingGroup"), fabric().getMember("ThreadingGroup")] } } } From 89a5acf6e884f2f897c068ebe8272798d0013d4d Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Tue, 13 Apr 2021 17:03:45 +0200 Subject: [PATCH 237/433] Java: Revert overriding XMLFile.getAPrimaryQlClass() Library file has to be kept in sync with the other languages, however except cpp none of them have the getAPrimaryQlClass() predicate declared in a superclass. Therefore for simplicity revert the change for Java. --- java/ql/src/semmle/code/xml/XML.qll | 2 -- 1 file changed, 2 deletions(-) diff --git a/java/ql/src/semmle/code/xml/XML.qll b/java/ql/src/semmle/code/xml/XML.qll index 7833f1bdb681..5871fed0dddd 100755 --- a/java/ql/src/semmle/code/xml/XML.qll +++ b/java/ql/src/semmle/code/xml/XML.qll @@ -149,8 +149,6 @@ class XMLFile extends XMLParent, File { /** Gets a DTD associated with this XML file. */ XMLDTD getADTD() { xmlDTDs(result, _, _, _, this) } - - override string getAPrimaryQlClass() { result = "XMLFile" } } /** From aa5258512048c2d6404acc221b6c4f66e98eab81 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Tue, 13 Apr 2021 17:17:05 +0200 Subject: [PATCH 238/433] C++: Add change-note. --- cpp/change-notes/2021-04-13-arithmetic-queries.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 cpp/change-notes/2021-04-13-arithmetic-queries.md diff --git a/cpp/change-notes/2021-04-13-arithmetic-queries.md b/cpp/change-notes/2021-04-13-arithmetic-queries.md new file mode 100644 index 000000000000..4d0f8833adc9 --- /dev/null +++ b/cpp/change-notes/2021-04-13-arithmetic-queries.md @@ -0,0 +1,2 @@ +lgtm +* The queries cpp/tainted-arithmetic, cpp/uncontrolled-arithmetic, and cpp/arithmetic-with-extreme-values have been improved to produce fewer false positives. From 7ed09904b48867367a3f6445d9187ad75c72b8d2 Mon Sep 17 00:00:00 2001 From: Taus Date: Tue, 13 Apr 2021 15:21:19 +0000 Subject: [PATCH 239/433] Python: Use API graphs for Invoke A few stragglers remain, as they are modelling the use of decorators. They will be dealt with at a later date. --- .../src/semmle/python/frameworks/Invoke.qll | 104 ++++-------------- 1 file changed, 21 insertions(+), 83 deletions(-) diff --git a/python/ql/src/semmle/python/frameworks/Invoke.qll b/python/ql/src/semmle/python/frameworks/Invoke.qll index 02db1cb5db2b..5b5a5e6e7f06 100644 --- a/python/ql/src/semmle/python/frameworks/Invoke.qll +++ b/python/ql/src/semmle/python/frameworks/Invoke.qll @@ -6,6 +6,7 @@ private import python private import semmle.python.dataflow.new.DataFlow private import semmle.python.Concepts +private import semmle.python.ApiGraphs /** * Provides models for the `invoke` PyPI package. @@ -16,102 +17,44 @@ private module Invoke { // invoke // --------------------------------------------------------------------------- /** Gets a reference to the `invoke` module. */ - private DataFlow::Node invoke(DataFlow::TypeTracker t) { - t.start() and - result = DataFlow::importNode("invoke") - or - exists(DataFlow::TypeTracker t2 | result = invoke(t2).track(t2, t)) - } - - /** Gets a reference to the `invoke` module. */ - DataFlow::Node invoke() { result = invoke(DataFlow::TypeTracker::end()) } - - /** - * Gets a reference to the attribute `attr_name` of the `invoke` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node invoke_attr(DataFlow::TypeTracker t, string attr_name) { - attr_name in ["run", "sudo", "context", "Context", "task"] and - ( - t.start() and - result = DataFlow::importNode("invoke." + attr_name) - or - t.startInAttr(attr_name) and - result = DataFlow::importNode("invoke") - ) - or - // Due to bad performance when using normal setup with `invoke_attr(t2, attr_name).track(t2, t)` - // we have inlined that code and forced a join - exists(DataFlow::TypeTracker t2 | - exists(DataFlow::StepSummary summary | - invoke_attr_first_join(t2, attr_name, result, summary) and - t = t2.append(summary) - ) - ) - } - - pragma[nomagic] - private predicate invoke_attr_first_join( - DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary - ) { - DataFlow::StepSummary::step(invoke_attr(t2, attr_name), res, summary) - } - - /** - * Gets a reference to the attribute `attr_name` of the `invoke` module. - * WARNING: Only holds for a few predefined attributes. - */ - private DataFlow::Node invoke_attr(string attr_name) { - result = invoke_attr(DataFlow::TypeTracker::end(), attr_name) - } + API::Node invoke() { result = API::moduleImport("invoke") } /** Provides models for the `invoke` module. */ module invoke { /** Gets a reference to the `invoke.context` module. */ - DataFlow::Node context() { result = invoke_attr("context") } + API::Node context() { result = invoke().getMember("context") } /** Provides models for the `invoke.context` module */ module context { /** Provides models for the `invoke.context.Context` class */ module Context { /** Gets a reference to the `invoke.context.Context` class. */ - private DataFlow::Node classRef(DataFlow::TypeTracker t) { - t.start() and - result = DataFlow::importNode("invoke.context.Context") - or - t.startInAttr("Context") and - result = invoke::context() - or - // handle invoke.Context alias - t.start() and - result = invoke_attr("Context") - or - exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t)) + API::Node classRef() { + result = + [API::moduleImport("invoke").getMember("context"), API::moduleImport("invoke")] + .getMember("Context") } - /** Gets a reference to the `invoke.context.Context` class. */ - DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) } - /** Gets a reference to an instance of `invoke.context.Context`. */ - private DataFlow::Node instance(DataFlow::TypeTracker t) { + private DataFlow::LocalSourceNode instance(DataFlow::TypeTracker t) { t.start() and - result.asCfgNode().(CallNode).getFunction() = - invoke::context::Context::classRef().asCfgNode() - or - t.start() and - exists(Function func | - func.getADecorator() = invoke_attr("task").asExpr() and - result.(DataFlow::ParameterNode).getParameter() = func.getArg(0) + ( + result = invoke::context::Context::classRef().getACall() + or + exists(Function func | + func.getADecorator() = invoke().getMember("task").getAUse().asExpr() and + result.(DataFlow::ParameterNode).getParameter() = func.getArg(0) + ) ) or exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t)) } /** Gets a reference to an instance of `invoke.context.Context`. */ - DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) } + DataFlow::LocalSourceNode instance() { result = instance(DataFlow::TypeTracker::end()) } /** Gets a reference to the `run` or `sudo` methods on a `invoke.context.Context` instance. */ - private DataFlow::Node instanceRunMethods(DataFlow::TypeTracker t) { + private DataFlow::LocalSourceNode instanceRunMethods(DataFlow::TypeTracker t) { t.startInAttr(["run", "sudo"]) and result = invoke::context::Context::instance() or @@ -120,7 +63,7 @@ private module Invoke { /** Gets a reference to the `run` or `sudo` methods on a `invoke.context.Context` instance. */ DataFlow::Node instanceRunMethods() { - result = instanceRunMethods(DataFlow::TypeTracker::end()) + instanceRunMethods(DataFlow::TypeTracker::end()).flowsTo(result) } } } @@ -131,15 +74,10 @@ private module Invoke { * - `invoke.run` or `invoke.sudo` functions (http://docs.pyinvoke.org/en/stable/api/__init__.html) * - `run` or `sudo` methods on a `invoke.context.Context` instance (http://docs.pyinvoke.org/en/stable/api/context.html#invoke.context.Context.run) */ - private class InvokeRunCommandCall extends SystemCommandExecution::Range, DataFlow::CfgNode { - override CallNode node; - + private class InvokeRunCommandCall extends SystemCommandExecution::Range, DataFlow::CallCfgNode { InvokeRunCommandCall() { - exists(DataFlow::Node callFunction | node.getFunction() = callFunction.asCfgNode() | - callFunction = invoke_attr(["run", "sudo"]) - or - callFunction = invoke::context::Context::instanceRunMethods() - ) + this = invoke().getMember(["run", "sudo"]).getACall() or + this.getFunction() = invoke::context::Context::instanceRunMethods() } override DataFlow::Node getCommand() { From 2890fe6d61566c8adce3ad63fcdaf102239f48ff Mon Sep 17 00:00:00 2001 From: Taus Date: Tue, 13 Apr 2021 15:26:54 +0000 Subject: [PATCH 240/433] Python: Use API graphs in Dill model If only all rewrites were this smooth... --- .../ql/src/semmle/python/frameworks/Dill.qll | 38 ++----------------- 1 file changed, 4 insertions(+), 34 deletions(-) diff --git a/python/ql/src/semmle/python/frameworks/Dill.qll b/python/ql/src/semmle/python/frameworks/Dill.qll index d105bd47603f..1985b7f84cf1 100644 --- a/python/ql/src/semmle/python/frameworks/Dill.qll +++ b/python/ql/src/semmle/python/frameworks/Dill.qll @@ -7,50 +7,20 @@ private import python private import semmle.python.dataflow.new.DataFlow private import semmle.python.dataflow.new.RemoteFlowSources private import semmle.python.Concepts - -private module Dill { - /** Gets a reference to the `dill` module. */ - private DataFlow::Node dill(DataFlow::TypeTracker t) { - t.start() and - result = DataFlow::importNode("dill") - or - exists(DataFlow::TypeTracker t2 | result = dill(t2).track(t2, t)) - } - - /** Gets a reference to the `dill` module. */ - DataFlow::Node dill() { result = dill(DataFlow::TypeTracker::end()) } - - /** Provides models for the `dill` module. */ - module dill { - /** Gets a reference to the `dill.loads` function. */ - private DataFlow::Node loads(DataFlow::TypeTracker t) { - t.start() and - result = DataFlow::importNode("dill.loads") - or - t.startInAttr("loads") and - result = dill() - or - exists(DataFlow::TypeTracker t2 | result = loads(t2).track(t2, t)) - } - - /** Gets a reference to the `dill.loads` function. */ - DataFlow::Node loads() { result = loads(DataFlow::TypeTracker::end()) } - } -} +private import semmle.python.ApiGraphs /** * A call to `dill.loads` * See https://pypi.org/project/dill/ (which currently refers you * to https://docs.python.org/3/library/pickle.html#pickle.loads) */ -private class DillLoadsCall extends Decoding::Range, DataFlow::CfgNode { - override CallNode node; +private class DillLoadsCall extends Decoding::Range, DataFlow::CallCfgNode { - DillLoadsCall() { node.getFunction() = Dill::dill::loads().asCfgNode() } + DillLoadsCall() { this = API::moduleImport("dill").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 } From 6e73d136702f882c7582da0dffd9ee635d53c739 Mon Sep 17 00:00:00 2001 From: haby0 Date: Tue, 13 Apr 2021 23:48:45 +0800 Subject: [PATCH 241/433] Update java/ql/src/semmle/code/java/frameworks/Servlets.qll Co-authored-by: Chris Smowton --- java/ql/src/semmle/code/java/frameworks/Servlets.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/src/semmle/code/java/frameworks/Servlets.qll b/java/ql/src/semmle/code/java/frameworks/Servlets.qll index a99df9816f15..3c57b8b7c828 100644 --- a/java/ql/src/semmle/code/java/frameworks/Servlets.qll +++ b/java/ql/src/semmle/code/java/frameworks/Servlets.qll @@ -354,7 +354,7 @@ class FilterChain extends Interface { /** Holds if `m` is a filter handler method (for example `doFilter`). */ predicate isDoFilterMethod(Method m) { - m.getName() ="doFilter" and + m.getName() = "doFilter" and m.getDeclaringType() instanceof FilterClass and m.getNumberOfParameters() = 3 and m.getParameter(0).getType() instanceof ServletRequest and From 7be45e7c5e450129253458072b766d9717d8b0ce Mon Sep 17 00:00:00 2001 From: haby0 Date: Tue, 13 Apr 2021 23:56:17 +0800 Subject: [PATCH 242/433] Update java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql Co-authored-by: Chris Smowton --- 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 927c3db03d25..f9c16a546ca0 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql @@ -52,7 +52,7 @@ class RequestResponseFlowConfig extends TaintTracking::Configuration { override predicate isSink(DataFlow::Node sink) { sink instanceof XssSink and - getACallingCallableOrSelf(sink.getEnclosingCallable()) instanceof RequestGetMethod + any(RequestGetMethod m).polyCalls*(source.getEnclosingCallable()) } override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) { From 5f7d3d0d36869014be58ae28d0a034b92b31b2c2 Mon Sep 17 00:00:00 2001 From: Taus Date: Tue, 13 Apr 2021 15:57:21 +0000 Subject: [PATCH 243/433] Python: Use API graphs in Werkzeug --- .../ql/src/semmle/python/frameworks/Flask.qll | 6 ++- .../src/semmle/python/frameworks/Werkzeug.qll | 46 +++++-------------- 2 files changed, 16 insertions(+), 36 deletions(-) diff --git a/python/ql/src/semmle/python/frameworks/Flask.qll b/python/ql/src/semmle/python/frameworks/Flask.qll index 8b045c2ee833..b4c5b5dcf8bb 100644 --- a/python/ql/src/semmle/python/frameworks/Flask.qll +++ b/python/ql/src/semmle/python/frameworks/Flask.qll @@ -401,13 +401,15 @@ module Flask { } } - private class RequestAttrMultiDict extends Werkzeug::werkzeug::datastructures::MultiDict::InstanceSource { + private class RequestAttrMultiDict extends Werkzeug::werkzeug::datastructures::MultiDict::InstanceSourceApiNode { string attr_name; RequestAttrMultiDict() { attr_name in ["args", "values", "form", "files"] and - this = request().getMember(attr_name).getAnImmediateUse() + this = request().getMember(attr_name) } + + override string toString() { result = this.(API::Node).toString() } } private class RequestAttrFiles extends RequestAttrMultiDict { diff --git a/python/ql/src/semmle/python/frameworks/Werkzeug.qll b/python/ql/src/semmle/python/frameworks/Werkzeug.qll index 172c94682521..63b8067b2664 100644 --- a/python/ql/src/semmle/python/frameworks/Werkzeug.qll +++ b/python/ql/src/semmle/python/frameworks/Werkzeug.qll @@ -5,6 +5,7 @@ private import python private import semmle.python.dataflow.new.DataFlow private import semmle.python.dataflow.new.TaintTracking +private import semmle.python.ApiGraphs /** * Provides models for the `Werkzeug` PyPI package. @@ -23,6 +24,9 @@ module Werkzeug { * See https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.MultiDict. */ module MultiDict { + /** DEPRECATED. Use `InstanceSourceApiNode` instead. */ + abstract deprecated class InstanceSource extends DataFlow::Node { } + /** * A source of instances of `werkzeug.datastructures.MultiDict`, extend this class to model new instances. * @@ -32,37 +36,16 @@ module Werkzeug { * * Use the predicate `MultiDict::instance()` to get references to instances of `werkzeug.datastructures.MultiDict`. */ - abstract class InstanceSource extends DataFlow::Node { } - - /** Gets a reference to an instance of `werkzeug.datastructures.MultiDict`. */ - 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 `werkzeug.datastructures.MultiDict`. */ - DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) } + abstract class InstanceSourceApiNode extends API::Node { } /** * Gets a reference to the `getlist` method on an instance of `werkzeug.datastructures.MultiDict`. * * See https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Headers.getlist */ - private DataFlow::Node getlist(DataFlow::TypeTracker t) { - t.startInAttr("getlist") and - result = instance() - or - exists(DataFlow::TypeTracker t2 | result = getlist(t2).track(t2, t)) + DataFlow::Node getlist() { + result = any(InstanceSourceApiNode a).getMember("getlist").getAUse() } - - /** - * Gets a reference to the `getlist` method on an instance of `werkzeug.datastructures.MultiDict`. - * - * See https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Headers.getlist - */ - DataFlow::Node getlist() { result = getlist(DataFlow::TypeTracker::end()) } } /** @@ -71,6 +54,9 @@ module Werkzeug { * See https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.FileStorage. */ module FileStorage { + /** DEPRECATED. Use `InstanceSourceApiNode` instead. */ + abstract deprecated class InstanceSource extends DataFlow::Node { } + /** * A source of instances of `werkzeug.datastructures.FileStorage`, extend this class to model new instances. * @@ -80,18 +66,10 @@ module Werkzeug { * * Use the predicate `FileStorage::instance()` to get references to instances of `werkzeug.datastructures.FileStorage`. */ - abstract class InstanceSource extends DataFlow::Node { } - - /** Gets a reference to an instance of `werkzeug.datastructures.FileStorage`. */ - private DataFlow::Node instance(DataFlow::TypeTracker t) { - t.start() and - result instanceof InstanceSource - or - exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t)) - } + abstract class InstanceSourceApiNode extends API::Node { } /** Gets a reference to an instance of `werkzeug.datastructures.FileStorage`. */ - DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) } + DataFlow::Node instance() { result = any(InstanceSourceApiNode a).getAUse() } } } } From 25b012db48c7b67f8801770d5056ac39681e3622 Mon Sep 17 00:00:00 2001 From: haby0 Date: Tue, 13 Apr 2021 23:58:28 +0800 Subject: [PATCH 244/433] Update java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql Co-authored-by: Chris Smowton --- 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 f9c16a546ca0..e3673e504100 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql @@ -47,7 +47,7 @@ class RequestResponseFlowConfig extends TaintTracking::Configuration { override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource and - getACallingCallableOrSelf(source.getEnclosingCallable()) instanceof RequestGetMethod + any(RequestGetMethod m).polyCalls*(source.getEnclosingCallable()) } override predicate isSink(DataFlow::Node sink) { From 00235ed3b39a51adbbeadc42f97cdc5cd5d5c780 Mon Sep 17 00:00:00 2001 From: haby0 Date: Tue, 13 Apr 2021 23:58:52 +0800 Subject: [PATCH 245/433] Update java/ql/src/semmle/code/java/frameworks/Servlets.qll Co-authored-by: Chris Smowton --- java/ql/src/semmle/code/java/frameworks/Servlets.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/src/semmle/code/java/frameworks/Servlets.qll b/java/ql/src/semmle/code/java/frameworks/Servlets.qll index 3c57b8b7c828..c733023bbdce 100644 --- a/java/ql/src/semmle/code/java/frameworks/Servlets.qll +++ b/java/ql/src/semmle/code/java/frameworks/Servlets.qll @@ -352,7 +352,7 @@ class FilterChain extends Interface { FilterChain() { hasQualifiedName("javax.servlet", "FilterChain") } } -/** Holds if `m` is a filter handler method (for example `doFilter`). */ +/** Holds if `m` is an implementation of `javax.servlet.Filter.doFilter`. */ predicate isDoFilterMethod(Method m) { m.getName() = "doFilter" and m.getDeclaringType() instanceof FilterClass and From 273e8ce4efb3646a3f449ed61ac73e37e8c6e1bf Mon Sep 17 00:00:00 2001 From: Taus Date: Tue, 13 Apr 2021 16:04:07 +0000 Subject: [PATCH 246/433] Python: Add change note --- python/change-notes/2021-04-13-werkzeug-api-graphs.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 python/change-notes/2021-04-13-werkzeug-api-graphs.md diff --git a/python/change-notes/2021-04-13-werkzeug-api-graphs.md b/python/change-notes/2021-04-13-werkzeug-api-graphs.md new file mode 100644 index 000000000000..041e5a31a2b7 --- /dev/null +++ b/python/change-notes/2021-04-13-werkzeug-api-graphs.md @@ -0,0 +1,5 @@ +lgtm,codescanning +* The Werkzeug model has been changed to use API graphs. When defining new models for classes based + on the `MultiDict` and `FileStorage` classes in `werkzeug.datastructures`, the relevant extension + points are now the two `InstanceSourceApiNode` classes in the `semmle.python.frameworks.Werkzeug` + module, instead of `InstanceSource`. The latter classes have now been deprecated. From 079c7e089df9cada5406147468abb5d3a4645a44 Mon Sep 17 00:00:00 2001 From: Taus Date: Tue, 13 Apr 2021 16:05:45 +0000 Subject: [PATCH 247/433] Python: Autoformat --- python/ql/src/semmle/python/frameworks/Dill.qll | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/ql/src/semmle/python/frameworks/Dill.qll b/python/ql/src/semmle/python/frameworks/Dill.qll index 1985b7f84cf1..94af905756bc 100644 --- a/python/ql/src/semmle/python/frameworks/Dill.qll +++ b/python/ql/src/semmle/python/frameworks/Dill.qll @@ -15,8 +15,7 @@ private import semmle.python.ApiGraphs * to https://docs.python.org/3/library/pickle.html#pickle.loads) */ private class DillLoadsCall extends Decoding::Range, DataFlow::CallCfgNode { - - DillLoadsCall() { this = API::moduleImport("dill").getMember("loads").getACall()} + DillLoadsCall() { this = API::moduleImport("dill").getMember("loads").getACall() } override predicate mayExecuteInput() { any() } From a6bb9ebb9ff619fea60d77cd54fe44d186bb49b8 Mon Sep 17 00:00:00 2001 From: Taus Date: Tue, 13 Apr 2021 16:08:41 +0000 Subject: [PATCH 248/433] Python: Re-introduce `abstract toString` This seems like the easier solution in the short run. --- python/ql/src/semmle/python/ApiGraphs.qll | 2 +- python/ql/src/semmle/python/frameworks/PEP249.qll | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/python/ql/src/semmle/python/ApiGraphs.qll b/python/ql/src/semmle/python/ApiGraphs.qll index 9689fb06a77c..5aab059d58e6 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. */ - string toString() { none() } + abstract string toString(); /** * Gets a path of the given `length` from the root to this node. diff --git a/python/ql/src/semmle/python/frameworks/PEP249.qll b/python/ql/src/semmle/python/frameworks/PEP249.qll index c449ae92bf24..f2b21d08e7c3 100644 --- a/python/ql/src/semmle/python/frameworks/PEP249.qll +++ b/python/ql/src/semmle/python/frameworks/PEP249.qll @@ -20,7 +20,10 @@ abstract deprecated class PEP249Module extends DataFlow::Node { } * An abstract class encompassing API graph nodes that implement PEP 249. * Extend this class for implementations. */ -abstract class PEP249ModuleApiNode extends API::Node { } +abstract class PEP249ModuleApiNode extends API::Node { + /** Gets a string representation of this element. */ + override string toString() { result = this.(API::Node).toString() } +} /** Gets a reference to a connect call. */ DataFlow::Node connect() { result = any(PEP249ModuleApiNode a).getMember("connect").getAUse() } From d853f0c40028958a292d483c3ace880e6cd5dd00 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Fri, 2 Apr 2021 18:51:02 +0200 Subject: [PATCH 249/433] Java: Add MemberType --- .../Performance/InnerClassCouldBeStatic.ql | 3 +- java/ql/src/semmle/code/java/PrintAst.qll | 4 +- java/ql/src/semmle/code/java/Type.qll | 42 ++++++++++++++++--- .../semmle/code/java/frameworks/play/Play.qll | 10 +---- 4 files changed, 41 insertions(+), 18 deletions(-) diff --git a/java/ql/src/Performance/InnerClassCouldBeStatic.ql b/java/ql/src/Performance/InnerClassCouldBeStatic.ql index a18d801549d3..5dba77761c6e 100644 --- a/java/ql/src/Performance/InnerClassCouldBeStatic.ql +++ b/java/ql/src/Performance/InnerClassCouldBeStatic.ql @@ -100,8 +100,7 @@ predicate potentiallyStatic(InnerClass c) { m = a.getEnclosingCallable() and m.getDeclaringType() = c ) and - not c instanceof AnonymousClass and - not c instanceof LocalClass and + c instanceof MemberType and forall( InnerClass other // If nested and non-static, ... | diff --git a/java/ql/src/semmle/code/java/PrintAst.qll b/java/ql/src/semmle/code/java/PrintAst.qll index 4e219603024f..6a74c15316c0 100644 --- a/java/ql/src/semmle/code/java/PrintAst.qll +++ b/java/ql/src/semmle/code/java/PrintAst.qll @@ -433,9 +433,7 @@ final class ClassInterfaceNode extends ElementNode { or result.(FieldDeclaration).getAField().getDeclaringType() = ty or - result.(NestedType).getEnclosingType().getSourceDeclaration() = ty and - not result instanceof AnonymousClass and - not result instanceof LocalClass + result.(MemberType).getEnclosingType().getSourceDeclaration() = ty or isInitBlock(ty, result) } diff --git a/java/ql/src/semmle/code/java/Type.qll b/java/ql/src/semmle/code/java/Type.qll index 10d9b8b3a31d..e81f64e4616b 100755 --- a/java/ql/src/semmle/code/java/Type.qll +++ b/java/ql/src/semmle/code/java/Type.qll @@ -517,7 +517,12 @@ class RefType extends Type, Annotatable, Modifiable, @reftype { /** Holds if this is a top-level type, which is not nested inside any other types. */ predicate isTopLevel() { this instanceof TopLevelType } - /** Holds if this type is declared in a specified package with the specified name. */ + /** + * Holds if this type is declared in a specified package with the specified name. + * + * For nested types the name of the nested type is prefixed with a `$` and appended + * to the name of the enclosing type, which might be a nested type as well. + */ predicate hasQualifiedName(string package, string type) { this.getPackage().hasName(package) and type = this.nestedName() } @@ -532,7 +537,12 @@ class RefType extends Type, Annotatable, Modifiable, @reftype { } /** - * Gets the qualified name of this type. + * Gets the qualified name of this type, consisting of the package name followed by + * a `.` and the name of this type. + * + * For nested types the name of the nested type is prefixed with a `$` and appended + * to the name of the enclosing type, which might be a nested type as well. For example: + * `java.util.Map$Entry`. */ string getQualifiedName() { exists(string pkgName | pkgName = getPackage().getName() | @@ -540,7 +550,13 @@ class RefType extends Type, Annotatable, Modifiable, @reftype { ) } - /** Gets the nested name of this type. */ + /** + * Gets the nested name of this type. + * + * If this type is not a nested type, the result is the same as `getName()`. + * Otherwise the name of the nested type is prefixed with a `$` and appended to + * the name of the enclosing type, which might be a nested type as well. + */ string nestedName() { not this instanceof NestedType and result = this.getName() or @@ -788,6 +804,21 @@ class NestedType extends RefType { } } +/** + * A nested type which is a direct member of the enclosing type, + * that is, neither an anonymous nor local class. + */ +class MemberType extends NestedType, Member { + /** + * Gets the qualified name of this member type. + * + * The qualified name consists of the package name, a `.`, the name of the declaring + * type (which might be a nested or member type as well), followed by a `$` and the + * name of this member type. For example: `java.util.Map$Entry`. + */ + override string getQualifiedName() { result = NestedType.super.getQualifiedName() } +} + /** * A class declared within another type. * @@ -797,8 +828,9 @@ class NestedType extends RefType { class NestedClass extends NestedType, Class { } /** - * An inner class is a nested class that is neither - * explicitly nor implicitly declared static. + * An inner class is a nested class that is neither explicitly nor + * implicitly declared static. This includes anonymous and local + * classes. */ class InnerClass extends NestedClass { InnerClass() { not this.isStatic() } diff --git a/java/ql/src/semmle/code/java/frameworks/play/Play.qll b/java/ql/src/semmle/code/java/frameworks/play/Play.qll index 0498ba9317c5..96c45cb22449 100644 --- a/java/ql/src/semmle/code/java/frameworks/play/Play.qll +++ b/java/ql/src/semmle/code/java/frameworks/play/Play.qll @@ -44,14 +44,8 @@ class PlayAddCsrfTokenAnnotation extends Annotation { /** * The type `play.libs.F.Promise`. */ -class PlayAsyncResultPromise extends Member { - PlayAsyncResultPromise() { - exists(Class c | - c.hasQualifiedName("play.libs", "F") and - this = c.getAMember() and - this.getQualifiedName() = "F.Promise" - ) - } +class PlayAsyncResultPromise extends MemberType { + PlayAsyncResultPromise() { hasQualifiedName("play.libs", "F$Promise") } } /** From 37dae67a0d57589a6fd97c842d05f2b5c9cf830d Mon Sep 17 00:00:00 2001 From: haby0 Date: Wed, 14 Apr 2021 09:55:24 +0800 Subject: [PATCH 250/433] Fix RequestResponseFlowConfig.isSink error --- .../experimental/Security/CWE/CWE-352/JsonpInjection.ql | 2 +- .../Security/CWE/CWE-352/JsonpInjectionLib.qll | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) 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 e3673e504100..f6f876488120 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql @@ -52,7 +52,7 @@ class RequestResponseFlowConfig extends TaintTracking::Configuration { override predicate isSink(DataFlow::Node sink) { sink instanceof XssSink and - any(RequestGetMethod m).polyCalls*(source.getEnclosingCallable()) + any(RequestGetMethod m).polyCalls*(sink.getEnclosingCallable()) } 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 6a21ce8ff3a8..5f2ad8e85321 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll @@ -77,10 +77,13 @@ class SpringControllerRequestMappingGetMethod extends SpringControllerGetMethod this.getAnAnnotation() .getType() .hasQualifiedName("org.springframework.web.bind.annotation", "RequestMapping") and - this.getAnAnnotation().getValue("method").toString().regexpMatch("RequestMethod.GET|\\{...\\}") and + ( + this.getAnAnnotation().getValue("method").(VarAccess).getVariable().getName() = "GET" or + this.getAnAnnotation().getValue("method").(ArrayInit).getSize() = 0 //Java code example: @RequestMapping(value = "test") + ) and not exists(MethodAccess ma | ma.getMethod() instanceof ServletRequestGetBodyMethod and - this = getACallingCallableOrSelf(ma.getEnclosingCallable()) + any(this).polyCalls*(ma.getEnclosingCallable()) ) and not this.getAParamType().getName() = "MultipartFile" } From e2ed0d02b0182dee63b7f0ab855dd3d145c13209 Mon Sep 17 00:00:00 2001 From: haby0 Date: Wed, 14 Apr 2021 12:34:52 +0800 Subject: [PATCH 251/433] Delete existsFilterVerificationMethod and existsServletVerificationMethod, add from get handler to filter --- .../Security/CWE/CWE-352/JsonpInjection.ql | 27 --- .../CWE/CWE-352/JsonpInjectionLib.qll | 24 +- .../JsonpController.java | 218 ------------------ .../JsonpInjection.expected | 81 ------- .../JsonpInjection.qlref | 1 - .../JsonpInjectionServlet1.java | 64 ----- .../JsonpInjectionServlet2.java | 50 ---- .../RefererFilter.java | 43 ---- .../CWE-352/JsonpInjectionWithFilter/options | 1 - .../JsonpInjection.expected | 6 - .../JsonpInjection.expected | 9 - 11 files changed, 12 insertions(+), 512 deletions(-) delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpController.java delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjection.expected delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjection.qlref delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjectionServlet1.java delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjectionServlet2.java delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/RefererFilter.java delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/options 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 f6f876488120..c28af8aaa152 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.ql @@ -16,31 +16,6 @@ import semmle.code.java.dataflow.FlowSources import semmle.code.java.deadcode.WebEntryPoints import DataFlow::PathGraph -/** - * Holds if some `Filter.doFilter` method exists in the whole program that takes some user-controlled - * input and tests it with what appears to be a token- or authentication-checking function. - */ -predicate existsFilterVerificationMethod() { - exists(DataFlow::Node source, DataFlow::Node sink, VerificationMethodFlowConfig vmfc, Method m | - vmfc.hasFlow(source, sink) and - m = getACallingCallableOrSelf(source.getEnclosingCallable()) and - isDoFilterMethod(m) - ) -} - -/** - * Holds if somewhere in the whole program some user-controlled - * input is tested with what appears to be a token- or authentication-checking function, - * and `checkNode` is reachable from any function that can reach the user-controlled input source. - */ -predicate existsServletVerificationMethod(Node checkNode) { - exists(DataFlow::Node source, DataFlow::Node sink, VerificationMethodFlowConfig vmfc | - vmfc.hasFlow(source, sink) and - getACallingCallableOrSelf(source.getEnclosingCallable()) = - getACallingCallableOrSelf(checkNode.getEnclosingCallable()) - ) -} - /** Taint-tracking configuration tracing flow from get method request sources to output jsonp data. */ class RequestResponseFlowConfig extends TaintTracking::Configuration { RequestResponseFlowConfig() { this = "RequestResponseFlowConfig" } @@ -64,8 +39,6 @@ class RequestResponseFlowConfig extends TaintTracking::Configuration { 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 response might include code from $@.", source.getNode(), 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 5f2ad8e85321..af9cebb865ce 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll @@ -42,17 +42,21 @@ class VerificationMethodFlowConfig extends TaintTracking2::Configuration { } } -/** Get Callable by recursive method. */ -Callable getACallingCallableOrSelf(Callable call) { - result = call - or - result = getACallingCallableOrSelf(call.getAReference().getEnclosingCallable()) -} - /** * A method that is called to handle an HTTP GET request. */ -abstract class RequestGetMethod extends Method { } +abstract class RequestGetMethod extends Method { + RequestGetMethod() { + not exists(DataFlow::Node source, DataFlow::Node sink, VerificationMethodFlowConfig vmfc | + vmfc.hasFlow(source, sink) and + any(this).polyCalls*(source.getEnclosingCallable()) + ) and + not exists(MethodAccess ma | + ma.getMethod() instanceof ServletRequestGetBodyMethod and + any(this).polyCalls*(ma.getEnclosingCallable()) + ) + } +} /** Override method of `doGet` of `Servlet` subclass. */ private class ServletGetMethod extends RequestGetMethod { @@ -81,10 +85,6 @@ class SpringControllerRequestMappingGetMethod extends SpringControllerGetMethod this.getAnAnnotation().getValue("method").(VarAccess).getVariable().getName() = "GET" or this.getAnAnnotation().getValue("method").(ArrayInit).getSize() = 0 //Java code example: @RequestMapping(value = "test") ) and - not exists(MethodAccess ma | - ma.getMethod() instanceof ServletRequestGetBodyMethod and - any(this).polyCalls*(ma.getEnclosingCallable()) - ) and not this.getAParamType().getName() = "MultipartFile" } } 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 deleted file mode 100644 index e875da2f6995..000000000000 --- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpController.java +++ /dev/null @@ -1,218 +0,0 @@ -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"); - } - - @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 bad8(HttpServletRequest request) { - String resultStr = null; - String token = request.getParameter("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 + ")"; - return resultStr; - } - return "error"; - } - - - @GetMapping(value = "jsonp10") - @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 = "jsonp11") - @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 = "jsonp12") - @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; - } - - 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 deleted file mode 100644 index 3da805c6a698..000000000000 --- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjection.expected +++ /dev/null @@ -1,81 +0,0 @@ -edges -| 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: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: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: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 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 deleted file mode 100644 index 3f5fc4506696..000000000000 --- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjection.qlref +++ /dev/null @@ -1 +0,0 @@ -experimental/Security/CWE/CWE-352/JsonpInjection.ql diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjectionServlet1.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjectionServlet1.java deleted file mode 100644 index 14ef76275b1d..000000000000 --- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjectionServlet1.java +++ /dev/null @@ -1,64 +0,0 @@ -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/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjectionServlet2.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjectionServlet2.java deleted file mode 100644 index bbfbc2dc4360..000000000000 --- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/JsonpInjectionServlet2.java +++ /dev/null @@ -1,50 +0,0 @@ -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/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/RefererFilter.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/RefererFilter.java deleted file mode 100644 index 97444932ae17..000000000000 --- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/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/JsonpInjectionWithFilter/options b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/options deleted file mode 100644 index c53e31e467fa..000000000000 --- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithFilter/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/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/JsonpInjectionWithSpringController/JsonpInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpInjection.expected index 2e4bc97ff973..83f2b7f206a5 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 @@ -15,9 +15,7 @@ edges | 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 | @@ -54,14 +52,10 @@ nodes | 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 | 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 d90d51ab5524..dfbe0628760b 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 @@ -15,13 +15,10 @@ edges | 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: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 | @@ -58,22 +55,16 @@ nodes | 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: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 | From 77208bcc91d7dfc0d53a8eb2f429b1deebded47b Mon Sep 17 00:00:00 2001 From: haby0 Date: Wed, 14 Apr 2021 13:14:43 +0800 Subject: [PATCH 252/433] Fix the error that there is no VerificationMethodToIfFlowConfig --- .../CWE/CWE-352/JsonpInjectionLib.qll | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) 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 af9cebb865ce..6b989c3c0cff 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll @@ -7,11 +7,32 @@ import semmle.code.java.dataflow.DataFlow3 import semmle.code.java.dataflow.FlowSources import semmle.code.java.frameworks.spring.SpringController +/** A data flow configuration tracing flow from the result of a method whose name includes token/auth/referer/origin to an if-statement condition. */ +class VerificationMethodToIfFlowConfig extends DataFlow3::Configuration { + VerificationMethodToIfFlowConfig() { this = "VerificationMethodToIfFlowConfig" } + + override predicate isSource(DataFlow::Node src) { + exists(MethodAccess ma | ma instanceof BarrierGuard | + ( + 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(IfStmt is | is.getCondition() = sink.asExpr()) + } +} + /** * Taint-tracking configuration tracing flow from untrusted inputs to an argument of a function whose result is used as an if-statement condition. * * For example, in the context `String userControlled = request.getHeader("xyz"); boolean isGood = checkToken(userControlled); if(isGood) { ...`, - * the flow from `getHeader(...)` to the argument to `checkToken`, and then the flow from `checkToken`'s result to the condition of `if(isGood)`. + * the flow from `checkToken`'s result to the condition of `if(isGood)` matches the configuration `VerificationMethodToIfFlowConfig` above, + * and so the flow from `getHeader(...)` to the argument to `checkToken` matches this configuration. */ class VerificationMethodFlowConfig extends TaintTracking2::Configuration { VerificationMethodFlowConfig() { this = "VerificationMethodFlowConfig" } @@ -19,25 +40,16 @@ class VerificationMethodFlowConfig extends TaintTracking2::Configuration { override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource } override predicate isSink(DataFlow::Node sink) { - exists(IfStmt is, Method m | is.getEnclosingCallable() = m | - ( - not m.getAParameter().getName().regexpMatch("(?i).*(token|auth|referer|origin).*") - or - not m.getName().regexpMatch("(?i).*(token|auth|referer|origin).*") - ) and - sink.asExpr() = is.getCondition() - ) - } - - override predicate isAdditionalTaintStep(DataFlow::Node prod, DataFlow::Node succ) { - exists(MethodAccess ma | + exists(MethodAccess ma, int i, VerificationMethodToIfFlowConfig vmtifc | + ma instanceof BarrierGuard + | ( - ma.getMethod().getAParameter().getName().regexpMatch("(?i).*(token|auth|referer|origin).*") + ma.getMethod().getParameter(i).getName().regexpMatch("(?i).*(token|auth|referer|origin).*") or ma.getMethod().getName().regexpMatch("(?i).*(token|auth|referer|origin).*") ) and - ma.getAnArgument() = prod.asExpr() and - ma = succ.asExpr() + ma.getArgument(i) = sink.asExpr() and + vmtifc.hasFlow(exprNode(ma), _) ) } } From 4810308b16d9c6af09f2bababb64992482a328bc Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Wed, 14 Apr 2021 09:16:31 +0200 Subject: [PATCH 253/433] C#: Add `Customizations.qll` --- csharp/ql/src/Customizations.qll | 12 ++++++++++++ csharp/ql/src/csharp.qll | 1 + 2 files changed, 13 insertions(+) create mode 100644 csharp/ql/src/Customizations.qll diff --git a/csharp/ql/src/Customizations.qll b/csharp/ql/src/Customizations.qll new file mode 100644 index 000000000000..d5378a44f0de --- /dev/null +++ b/csharp/ql/src/Customizations.qll @@ -0,0 +1,12 @@ +/** + * Contains customizations to the standard library. + * + * This module is imported by `csharp.qll`, so any customizations defined here automatically + * apply to all queries. + * + * Typical examples of customizations include adding new subclasses of abstract classes such as + * the `RemoteFlowSource` and `SummarizedCallable` classes associated with the security queries + * to model frameworks that are not covered by the standard library. + */ + +import csharp diff --git a/csharp/ql/src/csharp.qll b/csharp/ql/src/csharp.qll index 3c821c9a4433..dc187fc8d923 100644 --- a/csharp/ql/src/csharp.qll +++ b/csharp/ql/src/csharp.qll @@ -2,6 +2,7 @@ * The default C# QL library. */ +import Customizations import semmle.code.csharp.Attribute import semmle.code.csharp.Callable import semmle.code.csharp.Comments From 36fe72246b9dc5d19397fab44c86187e81feaf1a Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Wed, 14 Apr 2021 09:22:16 +0200 Subject: [PATCH 254/433] C#: Add change note --- csharp/change-notes/2021-04-14-customizations.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 csharp/change-notes/2021-04-14-customizations.md diff --git a/csharp/change-notes/2021-04-14-customizations.md b/csharp/change-notes/2021-04-14-customizations.md new file mode 100644 index 000000000000..a2f957ca9230 --- /dev/null +++ b/csharp/change-notes/2021-04-14-customizations.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* A new library, `Customizations.qll`, has been added, which allows for global customizations that affect all queries. \ No newline at end of file From 5158e7964eb51236ed6ea9308c285af55594d9cd Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 14 Apr 2021 08:25:12 +0100 Subject: [PATCH 255/433] Add change note --- java/change-notes/2021-04-14-membertype.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 java/change-notes/2021-04-14-membertype.md diff --git a/java/change-notes/2021-04-14-membertype.md b/java/change-notes/2021-04-14-membertype.md new file mode 100644 index 000000000000..910a5c914232 --- /dev/null +++ b/java/change-notes/2021-04-14-membertype.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* A CodeQL class `MemberType` is introduced to describe nested classes. Its `getQualifiedName` method returns `$`-delimited nested type names (for example, `mypackage.Outer$Middle$Inner`), where previously the same type would be named differently depending on whether it was addressed as a `NestedType` or a `Member`. From 2965a1f204a908738f56b475ae60928b9efc504d Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Wed, 14 Apr 2021 08:43:05 +0100 Subject: [PATCH 256/433] Use Thread$State as an inner-class example Map<>$Entry currently has odd generic notation that may be about to change. --- java/ql/src/semmle/code/java/Type.qll | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/ql/src/semmle/code/java/Type.qll b/java/ql/src/semmle/code/java/Type.qll index e81f64e4616b..5eaa52ee5482 100755 --- a/java/ql/src/semmle/code/java/Type.qll +++ b/java/ql/src/semmle/code/java/Type.qll @@ -542,7 +542,7 @@ class RefType extends Type, Annotatable, Modifiable, @reftype { * * For nested types the name of the nested type is prefixed with a `$` and appended * to the name of the enclosing type, which might be a nested type as well. For example: - * `java.util.Map$Entry`. + * `java.lang.Thread$State`. */ string getQualifiedName() { exists(string pkgName | pkgName = getPackage().getName() | @@ -814,7 +814,7 @@ class MemberType extends NestedType, Member { * * The qualified name consists of the package name, a `.`, the name of the declaring * type (which might be a nested or member type as well), followed by a `$` and the - * name of this member type. For example: `java.util.Map$Entry`. + * name of this member type. For example: `java.lang.Thread$State`. */ override string getQualifiedName() { result = NestedType.super.getQualifiedName() } } From 55723618a9041e18221cbce243a81f1972fbd96b Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Wed, 14 Apr 2021 10:05:50 +0200 Subject: [PATCH 257/433] Python: Apply suggestions from code review Co-authored-by: Taus --- python/ql/src/semmle/python/frameworks/Django.qll | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/ql/src/semmle/python/frameworks/Django.qll b/python/ql/src/semmle/python/frameworks/Django.qll index f41920c86b8a..4ef93e5cb60f 100644 --- a/python/ql/src/semmle/python/frameworks/Django.qll +++ b/python/ql/src/semmle/python/frameworks/Django.qll @@ -16,7 +16,7 @@ private import semmle.python.regex * Provides models for the `django` PyPI package. * See https://www.djangoproject.com/. */ -module Django { +private module Django { /** Provides models for the `django.views` module */ module Views { /** @@ -28,7 +28,7 @@ module Django { */ module View { /** - * An `API::Node` for references to `django.views.generic.View` or any subclass + * An `API::Node` representing the `django.views.generic.View` class or any subclass * that has explicitly been modeled in the CodeQL libraries. */ abstract class ModeledSubclass extends API::Node { @@ -94,7 +94,7 @@ module Django { */ module Form { /** - * An `API::Node` for references to `django.forms.forms.BaseForm` or any subclass + * An `API::Node` representing the `django.forms.forms.BaseForm` class or any subclass * that has explicitly been modeled in the CodeQL libraries. */ abstract class ModeledSubclass extends API::Node { @@ -190,7 +190,7 @@ module Django { */ module Field { /** - * An `API::Node` for references to `django.forms.fields.Field` or any subclass + * An `API::Node` representing the `django.forms.fields.Field` class or any subclass * that has explicitly been modeled in the CodeQL libraries. */ abstract class ModeledSubclass extends API::Node { From fd23e0bdda63169e2ca2c4dd86ecd3e796b8da57 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Wed, 14 Apr 2021 11:47:15 +0200 Subject: [PATCH 258/433] use more API nodes in XmlParsers, and recognize more results from parsing XML --- .../ql/src/semmle/javascript/ApiGraphs.qll | 2 +- .../javascript/frameworks/XmlParsers.qll | 155 +++++++++++++----- .../TaintTracking/BasicTaintTracking.expected | 4 + .../test/library-tests/TaintTracking/xml.js | 25 ++- 4 files changed, 139 insertions(+), 47 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/ApiGraphs.qll b/javascript/ql/src/semmle/javascript/ApiGraphs.qll index c0d28d913f70..385dc1e76d32 100644 --- a/javascript/ql/src/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/src/semmle/javascript/ApiGraphs.qll @@ -924,7 +924,7 @@ private module Label { result = member(pn) and // only consider properties with alphanumeric(-ish) names, excluding special properties // and properties whose names look like they are meant to be internal - pn.regexpMatch("(?!prototype$|__)[a-zA-Z_$][\\w\\-.$]*") + pn.regexpMatch("(?!prototype$|__)[\\w_$][\\w\\-.$]*") ) or not exists(pr.getPropertyName()) and diff --git a/javascript/ql/src/semmle/javascript/frameworks/XmlParsers.qll b/javascript/ql/src/semmle/javascript/frameworks/XmlParsers.qll index 1e6677ff9e14..94bf5b093639 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/XmlParsers.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/XmlParsers.qll @@ -2,7 +2,9 @@ * Provides classes for working with XML parser APIs. */ -import javascript as js +private import javascript as js +private import js::DataFlow as DataFlow +private import js::API as API module XML { /** @@ -12,9 +14,9 @@ module XML { /** Internal general entity. */ InternalEntity() or /** External general entity, either parsed or unparsed. */ - ExternalEntity(boolean parsed) { parsed = true or parsed = false } or + ExternalEntity(boolean parsed) { parsed = [true, false] } or /** Parameter entity, either internal or external. */ - ParameterEntity(boolean external) { external = true or external = false } + ParameterEntity(boolean external) { external = [true, false] } /** * A call to an XML parsing function. @@ -27,16 +29,19 @@ module XML { abstract predicate resolvesEntities(EntityKind kind); /** Gets a reference to a value resulting from parsing the XML. */ - js::DataFlow::Node getAResult() { none() } + DataFlow::Node getAResult() { none() } } /** * An invocation of `libxmljs.parseXml` or `libxmljs.parseXmlString`. */ class LibXmlJsParserInvocation extends ParserInvocation { + API::CallNode call; + LibXmlJsParserInvocation() { exists(string m | - this = js::DataFlow::moduleMember("libxmljs", m).getACall().asExpr() and + call = API::moduleImport("libxmljs").getMember(m).getACall() and + this = call.asExpr() and m.matches("parseXml%") ) } @@ -53,24 +58,67 @@ module XML { noent.mayHaveBooleanValue(true) ) } - } - /** - * Gets a call to method `methodName` on an instance of class `className` from module `modName`. - */ - private js::MethodCallExpr moduleMethodCall(string modName, string className, string methodName) { - exists(js::DataFlow::ModuleImportNode mod | - mod.getPath() = modName and - result = mod.getAConstructorInvocation(className).getAMethodCall(methodName).asExpr() - ) + /** + * A document from the `libxmljs` library. + * The API is based on https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/libxmljs/index.d.ts + */ + private API::Node doc() { + result = call.getReturn() + or + result = doc().getMember("encoding").getReturn() + or + result = element().getMember("doc").getReturn() + or + result = element().getMember("parent").getReturn() + } + + /** + * An `Element` from the `libxmljs` library. + */ + private API::Node element() { + result = doc().getMember(["child", "get", "node", "root"]).getReturn() + or + result = [doc(), element()].getMember(["childNodes", "find"]).getReturn().getAMember() + or + result = + element() + .getMember([ + "parent", "prevSibling", "nextSibling", "remove", "clone", "node", "child", + "prevElement", "nextElement" + ]) + .getReturn() + } + + /** + * An `Attr` from the `libxmljs` library. + */ + private API::Node attr() { + result = element().getMember("attr").getReturn() + or + result = element().getMember("attrs").getReturn().getAMember() + } + + override DataFlow::Node getAResult() { + result = [doc(), element(), attr()].getAnImmediateUse() + or + result = element().getMember(["name", "text"]).getACall() + or + result = attr().getMember(["name", "value"]).getACall() + or + result = element().getMember("namespace").getReturn().getMember(["href", "prefix"]).getACall() + } } /** * An invocation of `libxmljs.SaxParser.parseString`. */ class LibXmlJsSaxParserInvocation extends ParserInvocation { + API::Node parser; + LibXmlJsSaxParserInvocation() { - this = moduleMethodCall("libxmljs", "SaxParser", "parseString") + parser = API::moduleImport("libxmljs").getMember("SaxParser").getInstance() and + this = parser.getMember("parseString").getACall().asExpr() } override js::Expr getSourceArgument() { result = getArgument(0) } @@ -79,14 +127,21 @@ module XML { // entities are resolved by default any() } + + override DataFlow::Node getAResult() { + result = parser.getMember("on").getACall().getABoundCallbackParameter(1, _) + } } /** * An invocation of `libxmljs.SaxPushParser.push`. */ class LibXmlJsSaxPushParserInvocation extends ParserInvocation { + API::Node parser; + LibXmlJsSaxPushParserInvocation() { - this = moduleMethodCall("libxmljs", "SaxPushParser", "push") + parser = API::moduleImport("libxmljs").getMember("SaxPushParser").getInstance() and + this = parser.getMember("push").getACall().asExpr() } override js::Expr getSourceArgument() { result = getArgument(0) } @@ -95,17 +150,21 @@ module XML { // entities are resolved by default any() } + + override DataFlow::Node getAResult() { + result = parser.getMember("on").getACall().getABoundCallbackParameter(1, _) + } } /** * An invocation of `expat.Parser.parse` or `expat.Parser.write`. */ class ExpatParserInvocation extends ParserInvocation { - js::DataFlow::NewNode parser; + API::Node parser; ExpatParserInvocation() { - parser = js::DataFlow::moduleMember("node-expat", "Parser").getAnInstantiation() and - this = parser.getAMemberCall(["parse", "write"]).asExpr() + parser = API::moduleImport("node-expat").getMember("Parser").getInstance() and + this = parser.getMember(["parse", "write"]).getACall().asExpr() } override js::Expr getSourceArgument() { result = getArgument(0) } @@ -115,8 +174,8 @@ module XML { kind = InternalEntity() } - override js::DataFlow::Node getAResult() { - result = parser.getAMemberCall("on").getABoundCallbackParameter(1, _) + override DataFlow::Node getAResult() { + result = parser.getMember("on").getACall().getABoundCallbackParameter(1, _) } } @@ -125,17 +184,22 @@ module XML { */ private class DOMParserXmlParserInvocation extends XML::ParserInvocation { DOMParserXmlParserInvocation() { - exists(js::DataFlow::GlobalVarRefNode domparser | - domparser.getName() = "DOMParser" and - this = domparser.getAnInstantiation().getAMethodCall("parseFromString").asExpr() and - // type contains the string `xml`, that is, it's not `text/html` - getArgument(1).mayHaveStringValue(any(string tp | tp.matches("%xml%"))) - ) + this = + DataFlow::globalVarRef("DOMParser") + .getAnInstantiation() + .getAMethodCall("parseFromString") + .asExpr() and + // type contains the string `xml`, that is, it's not `text/html` + getArgument(1).mayHaveStringValue(any(string tp | tp.matches("%xml%"))) } override js::Expr getSourceArgument() { result = getArgument(0) } override predicate resolvesEntities(XML::EntityKind kind) { kind = InternalEntity() } + + // The result is an XMLDocument (https://developer.mozilla.org/en-US/docs/Web/API/XMLDocument). + // The API of the XMLDocument is not modelled. + override DataFlow::Node getAResult() { result.asExpr() = this } } /** @@ -143,8 +207,8 @@ module XML { */ private class IELegacyXmlParserInvocation extends XML::ParserInvocation { IELegacyXmlParserInvocation() { - exists(js::DataFlow::NewNode activeXObject, string activeXType | - activeXObject = js::DataFlow::globalVarRef("ActiveXObject").getAnInstantiation() and + exists(DataFlow::NewNode activeXObject, string activeXType | + activeXObject = DataFlow::globalVarRef("ActiveXObject").getAnInstantiation() and activeXObject.getArgument(0).asExpr().mayHaveStringValue(activeXType) and activeXType.regexpMatch("Microsoft\\.XMLDOM|Msxml.*\\.DOMDocument.*") and this = activeXObject.getAMethodCall("loadXML").asExpr() @@ -173,10 +237,10 @@ module XML { * An invocation of `xml2js`. */ private class Xml2JSInvocation extends XML::ParserInvocation { - js::DataFlow::CallNode call; + API::CallNode call; Xml2JSInvocation() { - exists(js::API::Node imp | imp = js::API::moduleImport("xml2js") | + exists(API::Node imp | imp = API::moduleImport("xml2js") | call = [imp, imp.getMember("Parser").getInstance()].getMember("parseString").getACall() and this = call.asExpr() ) @@ -189,7 +253,7 @@ module XML { none() } - override js::DataFlow::Node getAResult() { + override DataFlow::Node getAResult() { result = call.getABoundCallbackParameter(call.getNumArgument() - 1, 1) } } @@ -198,10 +262,10 @@ module XML { * An invocation of `sax`. */ private class SaxInvocation extends XML::ParserInvocation { - js::DataFlow::InvokeNode parser; + API::InvokeNode parser; SaxInvocation() { - exists(js::API::Node imp | imp = js::API::moduleImport("sax") | + exists(API::Node imp | imp = API::moduleImport("sax") | parser = imp.getMember("parser").getACall() or parser = imp.getMember("SAXParser").getAnInstantiation() @@ -216,13 +280,13 @@ module XML { none() } - override js::DataFlow::Node getAResult() { + override DataFlow::Node getAResult() { result = parser - .getAPropertyWrite(any(string s | s.matches("on%"))) - .getRhs() - .getAFunctionValue() + .getReturn() + .getMember(any(string s | s.matches("on%"))) .getAParameter() + .getAnImmediateUse() } } @@ -232,7 +296,8 @@ module XML { private class XmlJSInvocation extends XML::ParserInvocation { XmlJSInvocation() { this = - js::DataFlow::moduleMember("xml-js", ["xml2json", "xml2js", "json2xml", "js2xml"]) + API::moduleImport("xml-js") + .getMember(["xml2json", "xml2js", "json2xml", "js2xml"]) .getACall() .asExpr() } @@ -244,18 +309,18 @@ module XML { none() } - override js::DataFlow::Node getAResult() { result.asExpr() = this } + override DataFlow::Node getAResult() { result.asExpr() = this } } /** * An invocation of `htmlparser2`. */ private class HtmlParser2Invocation extends XML::ParserInvocation { - js::DataFlow::NewNode parser; + API::NewNode parser; HtmlParser2Invocation() { - parser = js::DataFlow::moduleMember("htmlparser2", "Parser").getAnInstantiation() and - this = parser.getAMemberCall("write").asExpr() + parser = API::moduleImport("htmlparser2").getMember("Parser").getAnInstantiation() and + this = parser.getReturn().getMember("write").getACall().asExpr() } override js::Expr getSourceArgument() { result = getArgument(0) } @@ -265,7 +330,7 @@ module XML { none() } - override js::DataFlow::Node getAResult() { + override DataFlow::Node getAResult() { result = parser .getArgument(0) @@ -277,7 +342,7 @@ module XML { } private class XMLParserTaintStep extends js::TaintTracking::SharedTaintStep { - override predicate deserializeStep(js::DataFlow::Node pred, js::DataFlow::Node succ) { + override predicate deserializeStep(DataFlow::Node pred, DataFlow::Node succ) { exists(XML::ParserInvocation parser | pred.asExpr() = parser.getSourceArgument() and succ = parser.getAResult() diff --git a/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected b/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected index fe0663b2adaf..8eae0bb62aa5 100644 --- a/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected +++ b/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected @@ -166,3 +166,7 @@ typeInferenceMismatch | xml.js:23:18:23:25 | source() | xml.js:20:14:20:17 | attr | | xml.js:26:27:26:34 | source() | xml.js:26:10:26:39 | convert ... (), {}) | | xml.js:34:18:34:25 | source() | xml.js:31:18:31:21 | name | +| xml.js:41:15:41:22 | source() | xml.js:44:10:44:22 | gchild.text() | +| xml.js:41:15:41:22 | source() | xml.js:49:10:49:34 | child.a ... value() | +| xml.js:41:15:41:22 | source() | xml.js:52:10:52:34 | child2. ... .name() | +| xml.js:41:15:41:22 | source() | xml.js:58:12:58:14 | str | diff --git a/javascript/ql/test/library-tests/TaintTracking/xml.js b/javascript/ql/test/library-tests/TaintTracking/xml.js index 8e3785d4a2b3..b1c6ab37ad33 100644 --- a/javascript/ql/test/library-tests/TaintTracking/xml.js +++ b/javascript/ql/test/library-tests/TaintTracking/xml.js @@ -34,4 +34,27 @@ parser.write(source()); parser.end(); -})(); \ No newline at end of file +})(); + +(function () { + var libxml = require("libxmljs"); + var xml = source(); + var xmlDoc = libxml.parseXmlString(xml); + var gchild = xmlDoc.get('//grandchild'); + sink(gchild.text()); // NOT OK + + var children = xmlDoc.root().childNodes(); + var child = children[0]; + + sink(child.attr('foo').value()); // NOT OK + + var child2 = xmlDoc.root().child() + sink(child2.attr('foo').name()); // NOT OK + + const SaxPushParser = libxml.SaxPushParser; + var parser = new SaxPushParser(); + parser.push(xml); + parser.on('characters', function (str) { + sink(str); // NOT OK + }) +}); \ No newline at end of file From 2e40d013973f8439bcc9085ff9e651b3c1af2e5c Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Wed, 14 Apr 2021 13:01:31 +0200 Subject: [PATCH 259/433] Update cpp/ql/src/semmle/code/cpp/security/Overflow.qll Co-authored-by: Geoffrey White <40627776+geoffw0@users.noreply.github.com> --- cpp/ql/src/semmle/code/cpp/security/Overflow.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/ql/src/semmle/code/cpp/security/Overflow.qll b/cpp/ql/src/semmle/code/cpp/security/Overflow.qll index 3f7a7b17b059..74b61c379257 100644 --- a/cpp/ql/src/semmle/code/cpp/security/Overflow.qll +++ b/cpp/ql/src/semmle/code/cpp/security/Overflow.qll @@ -98,7 +98,7 @@ VariableAccess varUse(LocalScopeVariable v) { result = v.getAnAccess() } * Holds if `e` potentially overflows and `use` is an operand of `e` that is not guarded. */ predicate missingGuardAgainstOverflow(Operation e, VariableAccess use) { - convertedExprMightOverflow(e) and + convertedExprMightOverflowPositively(e) and use = e.getAnOperand() and exists(LocalScopeVariable v | use.getTarget() = v | // overflow possible if large From da3650871454fa367245cd852abc552f37fd2b45 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Wed, 14 Apr 2021 14:41:22 +0200 Subject: [PATCH 260/433] Revert "C++: As response to the review comments this commit adds a reference-to-pointer state to AddressFlow. A call to an unwrapper function now adds a pointer -> reference-to-pointer transition, and a ReferenceDereference adds a reference-to-pointer -> pointer transition." This reverts commit 5aeaab7c6d7ff786c970b41f1bebce8c6040d838. --- .../cpp/dataflow/internal/AddressFlow.qll | 105 +++--------------- .../dataflow/taint-tests/localTaint.expected | 49 ++++---- 2 files changed, 38 insertions(+), 116 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 e179f7e8717b..7e76c1540e30 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/AddressFlow.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/AddressFlow.qll @@ -59,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) { @@ -67,19 +69,6 @@ private predicate lvalueToPointerStep(Expr lvalueIn, Expr pointerOut) { lvalueIn = pointerOut.(AddressOfExpr).getOperand().getFullyConverted() } -/** - * Since pointer wrappers behave as raw pointers, we treat the conversions from `lvalueToLvalueStepPure` - * as pointer-to-pointer steps when they involve pointer wrappers. - */ -private predicate pointerWrapperToPointerWrapperStep(Expr pointerIn, Expr pointerOut) { - pointerIn.getUnspecifiedType() instanceof PointerWrapper and - pointerIn.getConversion() = pointerOut and - pointerOut.(CStyleCast).isImplicit() - or - pointerOut.getUnspecifiedType() instanceof PointerWrapper and - pointerIn.getConversion() = pointerOut.(ReferenceDereferenceExpr) -} - private predicate pointerToPointerStep(Expr pointerIn, Expr pointerOut) { ( pointerOut instanceof PointerAddExpr @@ -104,60 +93,35 @@ private predicate pointerToPointerStep(Expr pointerIn, Expr pointerOut) { pointerIn = pointerOut.(CommaExpr).getRightOperand().getFullyConverted() or pointerIn = pointerOut.(StmtExpr).getResultExpr().getFullyConverted() - or - pointerWrapperToPointerWrapperStep(pointerIn, 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) { referenceIn.getConversion() = lvalueOut.(ReferenceDereferenceExpr) } -private predicate referenceToPointerToPointerStep(Expr referenceToPointerIn, Expr pointerOut) { - 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. - referenceToPointerIn = call.getArgument(0).getFullyConverted() - ) - or - referenceToPointerIn.getConversion() = pointerOut.(ReferenceDereferenceExpr) -} - -/** - * This predicate exists only to support "fake pointer" objects like - * smart pointers. We treat these as raw pointers for dataflow purposes. - */ -private predicate referenceToPointerToUpdate( - Expr referenceToPointer, Expr outer, ControlFlowNode node -) { - exists(Call call | - node = call and - outer = call.getAnArgument().getFullyConverted() and - not stdIdentityFunction(call.getTarget()) and - not stdAddressOf(call.getTarget()) and - exists(ReferenceType rt | rt = outer.getType().stripTopLevelSpecifiers() | - rt.getBaseType().getUnspecifiedType() = - any(PointerWrapper wrapper | not wrapper.pointsToConst()) - ) - ) and - referenceToPointer = outer - or - exists(Expr pointerMid | - referenceToPointerToPointerStep(referenceToPointer, pointerMid) and - pointerToUpdate(pointerMid, outer, node) - ) -} - private predicate referenceToPointerStep(Expr referenceIn, Expr pointerOut) { pointerOut = any(FunctionCall call | 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) { @@ -274,16 +238,6 @@ private predicate pointerToUpdate(Expr pointer, Expr outer, ControlFlowNode node pointerToPointerStep(pointer, pointerMid) and pointerToUpdate(pointerMid, outer, node) ) - or - exists(Expr referenceMid | - pointerToReferenceStep(pointer, referenceMid) and - referenceToUpdate(referenceMid, outer, node) - ) - or - exists(Expr referenceToPointerMid | - pointerToReferenceToPointerStep(pointer, referenceToPointerMid) and - referenceToPointerToUpdate(referenceToPointerMid, outer, node) - ) } private predicate referenceToUpdate(Expr reference, Expr outer, ControlFlowNode node) { @@ -293,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 @@ -314,14 +270,6 @@ private predicate referenceToUpdate(Expr reference, Expr outer, ControlFlowNode ) } -private predicate pointerToReferenceStep(Expr pointerIn, Expr referenceOut) { - exists(PointerWrapper wrapper, Call call | call = referenceOut | - referenceOut.getUnspecifiedType() instanceof ReferenceType and - call = wrapper.getAnUnwrapperFunction().getACallToThisFunction() and - pointerIn = call.getQualifier().getFullyConverted() - ) -} - private predicate lvalueFromVariableAccess(VariableAccess va, Expr lvalue) { // Base case for non-reference types. lvalue = va and @@ -383,21 +331,6 @@ private predicate referenceFromVariableAccess(VariableAccess va, Expr reference) lvalueFromVariableAccess(va, prev) and lvalueToReferenceStep(prev, reference) ) - or - exists(Expr prev | - pointerFromVariableAccess(va, prev) and - pointerToReferenceStep(prev, reference) - ) -} - -private predicate pointerToReferenceToPointerStep(Expr pointerIn, Expr referenceToPointerOut) { - pointerIn.getConversion() = referenceToPointerOut.(ReferenceToExpr) - or - exists(PointerWrapper wrapper, Call call | call = referenceToPointerOut | - referenceToPointerOut.getUnspecifiedType() instanceof ReferenceType and - call = wrapper.getAnUnwrapperFunction().getACallToThisFunction() and - pointerIn = call.getQualifier().getFullyConverted() - ) } /** @@ -418,8 +351,6 @@ predicate valueToUpdate(Expr inner, Expr outer, ControlFlowNode node) { pointerToUpdate(inner, outer, node) or referenceToUpdate(inner, outer, node) - or - referenceToPointerToUpdate(inner, outer, node) ) and ( inner instanceof VariableAccess and 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 90a494de6f83..50d5070a1442 100644 --- a/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected +++ b/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected @@ -948,16 +948,12 @@ | map.cpp:170:23:170:28 | call to source | map.cpp:170:7:170:30 | ... = ... | | | map.cpp:171:7:171:9 | m10 | map.cpp:171:10:171:10 | call to operator[] | TAINT | | map.cpp:171:7:171:9 | ref arg m10 | map.cpp:252:1:252:1 | m10 | | -| map.cpp:171:10:171:10 | ref arg call to operator[] | map.cpp:171:7:171:9 | ref arg m10 | TAINT | | map.cpp:172:7:172:9 | m11 | map.cpp:172:10:172:10 | call to operator[] | TAINT | | map.cpp:172:7:172:9 | ref arg m11 | map.cpp:252:1:252:1 | m11 | | -| map.cpp:172:10:172:10 | ref arg call to operator[] | map.cpp:172:7:172:9 | ref arg m11 | TAINT | | map.cpp:173:7:173:9 | m12 | map.cpp:173:10:173:10 | call to operator[] | TAINT | | map.cpp:173:7:173:9 | ref arg m12 | map.cpp:252:1:252:1 | m12 | | -| map.cpp:173:10:173:10 | ref arg call to operator[] | map.cpp:173:7:173:9 | ref arg m12 | TAINT | | map.cpp:174:7:174:9 | m13 | map.cpp:174:10:174:10 | call to operator[] | TAINT | | map.cpp:174:7:174:9 | ref arg m13 | map.cpp:252:1:252:1 | m13 | | -| map.cpp:174:10:174:10 | ref arg call to operator[] | map.cpp:174:7:174:9 | ref arg m13 | TAINT | | map.cpp:177:27:177:29 | call to map | map.cpp:178:2:178:4 | m14 | | | map.cpp:177:27:177:29 | call to map | map.cpp:179:2:179:4 | m14 | | | map.cpp:177:27:177:29 | call to map | map.cpp:180:2:180:4 | m14 | | @@ -1655,16 +1651,12 @@ | map.cpp:322:23:322:28 | call to source | map.cpp:322:7:322:30 | ... = ... | | | map.cpp:323:7:323:9 | m10 | map.cpp:323:10:323:10 | call to operator[] | TAINT | | map.cpp:323:7:323:9 | ref arg m10 | map.cpp:438:1:438:1 | m10 | | -| map.cpp:323:10:323:10 | ref arg call to operator[] | map.cpp:323:7:323:9 | ref arg m10 | TAINT | | map.cpp:324:7:324:9 | m11 | map.cpp:324:10:324:10 | call to operator[] | TAINT | | map.cpp:324:7:324:9 | ref arg m11 | map.cpp:438:1:438:1 | m11 | | -| map.cpp:324:10:324:10 | ref arg call to operator[] | map.cpp:324:7:324:9 | ref arg m11 | TAINT | | map.cpp:325:7:325:9 | m12 | map.cpp:325:10:325:10 | call to operator[] | TAINT | | map.cpp:325:7:325:9 | ref arg m12 | map.cpp:438:1:438:1 | m12 | | -| map.cpp:325:10:325:10 | ref arg call to operator[] | map.cpp:325:7:325:9 | ref arg m12 | TAINT | | map.cpp:326:7:326:9 | m13 | map.cpp:326:10:326:10 | call to operator[] | TAINT | | map.cpp:326:7:326:9 | ref arg m13 | map.cpp:438:1:438:1 | m13 | | -| map.cpp:326:10:326:10 | ref arg call to operator[] | map.cpp:326:7:326:9 | ref arg m13 | TAINT | | map.cpp:329:37:329:39 | call to unordered_map | map.cpp:330:2:330:4 | m14 | | | map.cpp:329:37:329:39 | call to unordered_map | map.cpp:331:2:331:4 | m14 | | | map.cpp:329:37:329:39 | call to unordered_map | map.cpp:332:2:332:4 | m14 | | @@ -2455,9 +2447,6 @@ | set.cpp:55:40:55:41 | ref arg i1 | set.cpp:55:24:55:25 | i1 | | | set.cpp:55:40:55:41 | ref arg i1 | set.cpp:55:40:55:41 | i1 | | | set.cpp:55:40:55:41 | ref arg i1 | set.cpp:57:9:57:10 | i1 | | -| set.cpp:57:8:57:8 | ref arg call to operator* | set.cpp:55:30:55:31 | s1 | | -| set.cpp:57:8:57:8 | ref arg call to operator* | set.cpp:57:9:57:10 | ref arg i1 | TAINT | -| set.cpp:57:8:57:8 | ref arg call to operator* | set.cpp:126:1:126:1 | s1 | | | set.cpp:57:9:57:10 | i1 | set.cpp:57:8:57:8 | call to operator* | TAINT | | set.cpp:57:9:57:10 | ref arg i1 | set.cpp:55:24:55:25 | i1 | | | set.cpp:57:9:57:10 | ref arg i1 | set.cpp:55:40:55:41 | i1 | | @@ -2476,9 +2465,6 @@ | set.cpp:59:40:59:41 | ref arg i2 | set.cpp:59:24:59:25 | i2 | | | set.cpp:59:40:59:41 | ref arg i2 | set.cpp:59:40:59:41 | i2 | | | set.cpp:59:40:59:41 | ref arg i2 | set.cpp:61:9:61:10 | i2 | | -| set.cpp:61:8:61:8 | ref arg call to operator* | set.cpp:59:30:59:31 | s2 | | -| set.cpp:61:8:61:8 | ref arg call to operator* | set.cpp:61:9:61:10 | ref arg i2 | TAINT | -| set.cpp:61:8:61:8 | ref arg call to operator* | set.cpp:126:1:126:1 | s2 | | | set.cpp:61:9:61:10 | i2 | set.cpp:61:8:61:8 | call to operator* | TAINT | | set.cpp:61:9:61:10 | ref arg i2 | set.cpp:59:24:59:25 | i2 | | | set.cpp:61:9:61:10 | ref arg i2 | set.cpp:59:40:59:41 | i2 | | @@ -2965,9 +2951,6 @@ | set.cpp:169:40:169:41 | ref arg i1 | set.cpp:169:24:169:25 | i1 | | | set.cpp:169:40:169:41 | ref arg i1 | set.cpp:169:40:169:41 | i1 | | | set.cpp:169:40:169:41 | ref arg i1 | set.cpp:171:9:171:10 | i1 | | -| set.cpp:171:8:171:8 | ref arg call to operator* | set.cpp:169:30:169:31 | s1 | | -| set.cpp:171:8:171:8 | ref arg call to operator* | set.cpp:171:9:171:10 | ref arg i1 | TAINT | -| set.cpp:171:8:171:8 | ref arg call to operator* | set.cpp:238:1:238:1 | s1 | | | set.cpp:171:9:171:10 | i1 | set.cpp:171:8:171:8 | call to operator* | TAINT | | set.cpp:171:9:171:10 | ref arg i1 | set.cpp:169:24:169:25 | i1 | | | set.cpp:171:9:171:10 | ref arg i1 | set.cpp:169:40:169:41 | i1 | | @@ -2986,9 +2969,6 @@ | set.cpp:173:40:173:41 | ref arg i2 | set.cpp:173:24:173:25 | i2 | | | set.cpp:173:40:173:41 | ref arg i2 | set.cpp:173:40:173:41 | i2 | | | set.cpp:173:40:173:41 | ref arg i2 | set.cpp:175:9:175:10 | i2 | | -| set.cpp:175:8:175:8 | ref arg call to operator* | set.cpp:173:30:173:31 | s2 | | -| set.cpp:175:8:175:8 | ref arg call to operator* | set.cpp:175:9:175:10 | ref arg i2 | TAINT | -| set.cpp:175:8:175:8 | ref arg call to operator* | set.cpp:238:1:238:1 | s2 | | | set.cpp:175:9:175:10 | i2 | set.cpp:175:8:175:8 | call to operator* | TAINT | | set.cpp:175:9:175:10 | ref arg i2 | set.cpp:173:24:173:25 | i2 | | | set.cpp:175:9:175:10 | ref arg i2 | set.cpp:173:40:173:41 | i2 | | @@ -3243,7 +3223,7 @@ | 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 | | @@ -3255,7 +3235,7 @@ | 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 | | @@ -3271,13 +3251,13 @@ | 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 | | @@ -3285,13 +3265,13 @@ | 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 | | @@ -3312,7 +3292,7 @@ | 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 | ... = ... | | @@ -3323,7 +3303,7 @@ | 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* | TAINT | +| 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 | | @@ -3392,7 +3372,7 @@ | 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* | TAINT | +| 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 | ... = ... | | @@ -3429,6 +3409,7 @@ | 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] | | @@ -3456,6 +3437,7 @@ | 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] | | @@ -3494,20 +3476,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 | | @@ -3525,10 +3513,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 | | From bc7cc2f7ceb9bf07e9ddf634435322396668d821 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Wed, 14 Apr 2021 14:50:27 +0200 Subject: [PATCH 261/433] C++: Remove rule that wasn't needed. --- .../semmle/code/cpp/dataflow/internal/AddressFlow.qll | 2 -- .../dataflow/taint-tests/localTaint.expected | 9 --------- 2 files changed, 11 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 7e76c1540e30..f6072763e1a4 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/AddressFlow.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/AddressFlow.qll @@ -59,8 +59,6 @@ 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) { 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 50d5070a1442..59ba0948d858 100644 --- a/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected +++ b/cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected @@ -3476,26 +3476,20 @@ | 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 | | @@ -3513,13 +3507,10 @@ | 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 | | From bb447d7174141dc518e6ce90367121280fec1d80 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Wed, 14 Apr 2021 16:30:43 +0200 Subject: [PATCH 262/433] C++: Make sure missingGuardAgainstOverflow (and underflow) holds when range analysis fails to deduce a bound. --- cpp/ql/src/semmle/code/cpp/security/Overflow.qll | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/security/Overflow.qll b/cpp/ql/src/semmle/code/cpp/security/Overflow.qll index 74b61c379257..465e64b9b6ca 100644 --- a/cpp/ql/src/semmle/code/cpp/security/Overflow.qll +++ b/cpp/ql/src/semmle/code/cpp/security/Overflow.qll @@ -6,6 +6,7 @@ import cpp import semmle.code.cpp.controlflow.Dominance import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis +import semmle.code.cpp.rangeanalysis.RangeAnalysisUtils /** * Holds if the value of `use` is guarded using `abs`. @@ -98,7 +99,12 @@ VariableAccess varUse(LocalScopeVariable v) { result = v.getAnAccess() } * Holds if `e` potentially overflows and `use` is an operand of `e` that is not guarded. */ predicate missingGuardAgainstOverflow(Operation e, VariableAccess use) { - convertedExprMightOverflowPositively(e) and + ( + convertedExprMightOverflowPositively(e) + or + // Ensure that the predicate holds when range analysis cannot determine an upper bound + upperBound(e.getFullyConverted()) = exprMaxVal(e.getFullyConverted()) + ) and use = e.getAnOperand() and exists(LocalScopeVariable v | use.getTarget() = v | // overflow possible if large @@ -120,7 +126,12 @@ predicate missingGuardAgainstOverflow(Operation e, VariableAccess use) { * Holds if `e` potentially underflows and `use` is an operand of `e` that is not guarded. */ predicate missingGuardAgainstUnderflow(Operation e, VariableAccess use) { - convertedExprMightOverflowNegatively(e) and + ( + convertedExprMightOverflowNegatively(e) + or + // Ensure that the predicate holds when range analysis cannot determine a lower bound + lowerBound(e.getFullyConverted()) = exprMinVal(e.getFullyConverted()) + ) and use = e.getAnOperand() and exists(LocalScopeVariable v | use.getTarget() = v | // underflow possible if use is left operand and small From 53a320a810536ae59a37c45042e712b2863a377d Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Wed, 14 Apr 2021 16:33:18 +0200 Subject: [PATCH 263/433] C++: Fix duplicate names. --- .../AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql | 2 +- .../AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.ql | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql index 012109074e9a..e4577968730e 100644 --- a/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql +++ b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql @@ -3,7 +3,7 @@ * @description The expression `buffer [strlen (buffer)] = 0` is potentially dangerous, if the variable `buffer` does not have a terminal zero, then access beyond the bounds of the allocated memory is possible, which will lead to undefined behavior. * If terminal zero is present, then the specified expression is meaningless. * @kind problem - * @id cpp/access-memory-location-after-end-buffer + * @id cpp/access-memory-location-after-end-buffer-strlen * @problem.severity warning * @precision medium * @tags correctness diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.ql b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.ql index 5311ffe2708e..e7021bda1ceb 100644 --- a/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.ql +++ b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.ql @@ -2,7 +2,7 @@ * @name Access Of Memory Location After The End Of A Buffer Using Strncat * @description Calls of the form `strncat(dest, source, sizeof (dest) - strlen (dest))` set the third argument to one more than possible. So when `dest` is full, the expression `sizeof(dest) - strlen (dest)` will be equal to one, and not zero as the programmer might think. Making a call of this type may result in a zero byte being written just outside the `dest` buffer. * @kind problem - * @id cpp/access-memory-location-after-end-buffer + * @id cpp/access-memory-location-after-end-buffer-strncat * @problem.severity warning * @precision medium * @tags correctness From ed64ed3d8d64cb27f18488cef355fa23911ba302 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Wed, 14 Apr 2021 16:45:27 +0200 Subject: [PATCH 264/433] C++: Make exprMightOverflowPositively/exprMightOverFlowNegatively hold for unanalyzable expressions. This hopefully means that expressions that do not satisfy these predicates will never overflow/underflow. --- .../semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cpp/ql/src/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll b/cpp/ql/src/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll index f3bbbddd97e4..d22a7f96a45f 100644 --- a/cpp/ql/src/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll +++ b/cpp/ql/src/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll @@ -1630,6 +1630,9 @@ private module SimpleRangeAnalysisCached { // bound of `x`, so the standard logic (above) does not work for // detecting whether it might overflow. getLowerBoundsImpl(expr.(PostfixDecrExpr)) = exprMinVal(expr) + or + // Expressions we cannot analyze could potentially overflow + not analyzableExpr(expr) } /** @@ -1657,6 +1660,9 @@ private module SimpleRangeAnalysisCached { // bound of `x`, so the standard logic (above) does not work for // detecting whether it might overflow. getUpperBoundsImpl(expr.(PostfixIncrExpr)) = exprMaxVal(expr) + or + // Expressions we cannot analyze could potentially overflow + not analyzableExpr(expr) } /** From b29f35f564f7f657a6f1fed4e63b5396d3d64220 Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Wed, 14 Apr 2021 11:15:16 -0400 Subject: [PATCH 265/433] Fix formatting --- .../src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll index 81a07ad9d04a..f5fb7309cff6 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll @@ -362,7 +362,7 @@ private class ExplicitFieldStoreQualifierNode extends PartialDefinitionNode { /** * Not every store instruction generates a chi instruction that we can attach a PostUpdateNode to. - * For instance, an update to a field of a struct containing only one field. Even if the store does + * For instance, an update to a field of a struct containing only one field. Even if the store does * have a chi instruction, a subsequent use of the result of the store may be linked directly to the * result of the store as an inexact definition if the store totally overlaps the use. For these * cases we attach the PostUpdateNode to the store instruction. There's no obvious pre update node From 392adf2a2576ac86cb66364625742cc3c562482d Mon Sep 17 00:00:00 2001 From: Andrew Eisenberg Date: Wed, 14 Apr 2021 08:25:34 -0700 Subject: [PATCH 266/433] Workflows: Remove dry-run flag for labeller --- .github/workflows/close-stale.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/close-stale.yml b/.github/workflows/close-stale.yml index 6065f79aa7c1..746a13317b22 100644 --- a/.github/workflows/close-stale.yml +++ b/.github/workflows/close-stale.yml @@ -25,6 +25,6 @@ jobs: days-before-pr-stale: -1 days-before-pr-close: -1 - # Dry-run only. remove this when we are ready to actually change issues - debug-only: true - operations-per-run: 1000 + # Uncomment for dry-run + # debug-only: true + # operations-per-run: 1000 From a7fcf52267604a257937f2a18ba2be321debea9e Mon Sep 17 00:00:00 2001 From: Taus Date: Wed, 14 Apr 2021 15:36:01 +0000 Subject: [PATCH 267/433] Python: Fix bad join in `total_cost` The recent change to `appliesTo` lead to a perturbation in the join order of this predicate, which resulted in a cartesian product between `call` and `ctx` being created (before being filtered by `appliesTo`). By splitting the intermediate result into its own helper predicate, suitably marked to prevent inlining/magic, we prevent this from happening again. --- python/ql/src/semmle/python/pointsto/PointsToContext.qll | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/python/ql/src/semmle/python/pointsto/PointsToContext.qll b/python/ql/src/semmle/python/pointsto/PointsToContext.qll index 15b28d9cdb07..e85761e39169 100644 --- a/python/ql/src/semmle/python/pointsto/PointsToContext.qll +++ b/python/ql/src/semmle/python/pointsto/PointsToContext.qll @@ -100,10 +100,14 @@ private int total_call_cost(CallNode call) { if call_to_init_or_del(call) then result = 1 else result = call_cost(call) + splay_cost(call) } +pragma[nomagic] +private int relevant_call_cost(PointsToContext ctx, CallNode call) { + ctx.appliesTo(call) and result = total_call_cost(call) +} + pragma[noinline] private int total_cost(CallNode call, PointsToContext ctx) { - ctx.appliesTo(call) and - result = total_call_cost(call) + context_cost(ctx) + result = relevant_call_cost(ctx, call) + context_cost(ctx) } cached From 97186b3d30bcfc095cd4f30cc407330c094687a6 Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Wed, 14 Apr 2021 19:30:58 +0300 Subject: [PATCH 268/433] Added comments for tests --- .../JakartaExpressionInjection.expected | 86 +++++++++---------- .../CWE-094/JakartaExpressionInjection.java | 9 ++ 2 files changed, 52 insertions(+), 43 deletions(-) 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 ef002aefdf5c..390871b54cf1 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 @@ -1,46 +1,46 @@ 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: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: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 | +| JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:25:31:25:40 | expression : String | +| JakartaExpressionInjection.java:25:31:25:40 | expression : String | JakartaExpressionInjection.java:32:24:32:33 | expression : String | +| JakartaExpressionInjection.java:25:31:25:40 | expression : String | JakartaExpressionInjection.java:40:24:40:33 | expression : String | +| JakartaExpressionInjection.java:25:31:25:40 | expression : String | JakartaExpressionInjection.java:48:24:48:33 | expression : String | +| JakartaExpressionInjection.java:25:31:25:40 | expression : String | JakartaExpressionInjection.java:59:24:59:33 | expression : String | +| JakartaExpressionInjection.java:25:31:25:40 | expression : String | JakartaExpressionInjection.java:67:24:67:33 | expression : String | +| JakartaExpressionInjection.java:25:31:25:40 | expression : String | JakartaExpressionInjection.java:75:24:75:33 | expression : String | +| JakartaExpressionInjection.java:25:31:25:40 | expression : String | JakartaExpressionInjection.java:85:24:85:33 | expression : String | +| JakartaExpressionInjection.java:25:31:25:40 | expression : String | JakartaExpressionInjection.java:95:24:95:33 | expression : String | +| JakartaExpressionInjection.java:32:24:32:33 | expression : String | JakartaExpressionInjection.java:34:28:34:37 | expression | +| JakartaExpressionInjection.java:40:24:40:33 | expression : String | JakartaExpressionInjection.java:42:32:42:41 | expression | +| JakartaExpressionInjection.java:48:24:48:33 | expression : String | JakartaExpressionInjection.java:53:13:53:28 | lambdaExpression | +| JakartaExpressionInjection.java:59:24:59:33 | expression : String | JakartaExpressionInjection.java:61:32:61:41 | expression | +| JakartaExpressionInjection.java:67:24:67:33 | expression : String | JakartaExpressionInjection.java:69:43:69:52 | expression | +| JakartaExpressionInjection.java:75:24:75:33 | expression : String | JakartaExpressionInjection.java:79:13:79:13 | e | +| JakartaExpressionInjection.java:85:24:85:33 | expression : String | JakartaExpressionInjection.java:89:13:89:13 | e | +| JakartaExpressionInjection.java:95:24:95:33 | expression : String | JakartaExpressionInjection.java:99:13:99: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: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: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 | +| JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream | +| JakartaExpressionInjection.java:25:31:25:40 | expression : String | semmle.label | expression : String | +| JakartaExpressionInjection.java:32:24:32:33 | expression : String | semmle.label | expression : String | +| JakartaExpressionInjection.java:34:28:34:37 | expression | semmle.label | expression | +| JakartaExpressionInjection.java:40:24:40:33 | expression : String | semmle.label | expression : String | +| JakartaExpressionInjection.java:42:32:42:41 | expression | semmle.label | expression | +| JakartaExpressionInjection.java:48:24:48:33 | expression : String | semmle.label | expression : String | +| JakartaExpressionInjection.java:53:13:53:28 | lambdaExpression | semmle.label | lambdaExpression | +| JakartaExpressionInjection.java:59:24:59:33 | expression : String | semmle.label | expression : String | +| JakartaExpressionInjection.java:61:32:61:41 | expression | semmle.label | expression | +| JakartaExpressionInjection.java:67:24:67:33 | expression : String | semmle.label | expression : String | +| JakartaExpressionInjection.java:69:43:69:52 | expression | semmle.label | expression | +| JakartaExpressionInjection.java:75:24:75:33 | expression : String | semmle.label | expression : String | +| JakartaExpressionInjection.java:79:13:79:13 | e | semmle.label | e | +| JakartaExpressionInjection.java:85:24:85:33 | expression : String | semmle.label | expression : String | +| JakartaExpressionInjection.java:89:13:89:13 | e | semmle.label | e | +| JakartaExpressionInjection.java:95:24:95:33 | expression : String | semmle.label | expression : String | +| JakartaExpressionInjection.java:99:13:99: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: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 | +| JakartaExpressionInjection.java:34:28:34:37 | expression | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:34:28:34:37 | expression | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) | this user input | +| JakartaExpressionInjection.java:42:32:42:41 | expression | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:42:32:42:41 | expression | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) | this user input | +| JakartaExpressionInjection.java:53:13:53:28 | lambdaExpression | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:53:13:53:28 | lambdaExpression | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) | this user input | +| JakartaExpressionInjection.java:61:32:61:41 | expression | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:61:32:61:41 | expression | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) | this user input | +| JakartaExpressionInjection.java:69:43:69:52 | expression | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:69:43:69:52 | expression | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) | this user input | +| JakartaExpressionInjection.java:79:13:79:13 | e | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:79:13:79:13 | e | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) | this user input | +| JakartaExpressionInjection.java:89:13:89:13 | e | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:89:13:89:13 | e | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) | this user input | +| JakartaExpressionInjection.java:99:13:99:13 | e | JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:99:13:99:13 | e | Jakarta Expression Language injection from $@. | JakartaExpressionInjection.java:23:25:23: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 2e1d1e55b020..ae5b6a8d5e41 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 @@ -15,6 +15,7 @@ public class JakartaExpressionInjection { + // calls a consumer with a string received from a socket private static void testWithSocket(Consumer action) throws IOException { try (ServerSocket serverSocket = new ServerSocket(0)) { try (Socket socket = serverSocket.accept()) { @@ -26,6 +27,7 @@ private static void testWithSocket(Consumer action) throws IOException { } } + // BAD (untrusted input to ELProcessor.eval) private static void testWithELProcessorEval() throws IOException { testWithSocket(expression -> { ELProcessor processor = new ELProcessor(); @@ -33,6 +35,7 @@ private static void testWithELProcessorEval() throws IOException { }); } + // BAD (untrusted input to ELProcessor.getValue) private static void testWithELProcessorGetValue() throws IOException { testWithSocket(expression -> { ELProcessor processor = new ELProcessor(); @@ -40,6 +43,7 @@ private static void testWithELProcessorGetValue() throws IOException { }); } + // BAD (untrusted input to LambdaExpression.invoke) private static void testWithLambdaExpressionInvoke() throws IOException { testWithSocket(expression -> { ExpressionFactory factory = ELManager.getExpressionFactory(); @@ -50,6 +54,7 @@ private static void testWithLambdaExpressionInvoke() throws IOException { }); } + // BAD (untrusted input to ELProcessor.setValue) private static void testWithELProcessorSetValue() throws IOException { testWithSocket(expression -> { ELProcessor processor = new ELProcessor(); @@ -57,6 +62,7 @@ private static void testWithELProcessorSetValue() throws IOException { }); } + // BAD (untrusted input to ELProcessor.setVariable) private static void testWithELProcessorSetVariable() throws IOException { testWithSocket(expression -> { ELProcessor processor = new ELProcessor(); @@ -64,6 +70,7 @@ private static void testWithELProcessorSetVariable() throws IOException { }); } + // BAD (untrusted input to ValueExpression.getValue when it was created by JUEL) private static void testWithJuelValueExpressionGetValue() throws IOException { testWithSocket(expression -> { ExpressionFactory factory = new de.odysseus.el.ExpressionFactoryImpl(); @@ -73,6 +80,7 @@ private static void testWithJuelValueExpressionGetValue() throws IOException { }); } + // BAD (untrusted input to ValueExpression.setValue when it was created by JUEL) private static void testWithJuelValueExpressionSetValue() throws IOException { testWithSocket(expression -> { ExpressionFactory factory = new de.odysseus.el.ExpressionFactoryImpl(); @@ -82,6 +90,7 @@ private static void testWithJuelValueExpressionSetValue() throws IOException { }); } + // BAD (untrusted input to MethodExpression.invoke when it was created by JUEL) private static void testWithJuelMethodExpressionInvoke() throws IOException { testWithSocket(expression -> { ExpressionFactory factory = new de.odysseus.el.ExpressionFactoryImpl(); From 897d12420b6fa5f22b0716af82af65a5db00bb56 Mon Sep 17 00:00:00 2001 From: Taus Date: Wed, 14 Apr 2021 16:49:12 +0000 Subject: [PATCH 269/433] Python: Prevent bad join in `isinstanceEvaluatesTo` In some cases, we were joining the result of `val.getClass()` against the first argument of `Types::improperSubclass` before filtering out the vast majority of tuples by the call to `isinstance_call`. To fix this, we let `isinstance_call` take care of figuring out the class of the value being tested. As a bonus, this cleans up the only other place where `isinstance_call` is used, where we _also_ want to know the class of the value being tested in the `isinstance` call. --- python/ql/src/semmle/python/pointsto/PointsTo.qll | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/python/ql/src/semmle/python/pointsto/PointsTo.qll b/python/ql/src/semmle/python/pointsto/PointsTo.qll index 9706c6846f31..48bbb283d078 100644 --- a/python/ql/src/semmle/python/pointsto/PointsTo.qll +++ b/python/ql/src/semmle/python/pointsto/PointsTo.qll @@ -1853,8 +1853,10 @@ module Expressions { private boolean isinstanceEvaluatesTo( CallNode call, PointsToContext context, ControlFlowNode use, ObjectInternal val ) { - exists(ObjectInternal cls | isinstance_call(call, use, context, val, cls) | - result = Types::improperSubclass(val.getClass(), cls) + exists(ObjectInternal cls, ObjectInternal val_cls | + isinstance_call(call, use, context, val, val_cls, cls) + | + result = Types::improperSubclass(val_cls, cls) or val = ObjectInternal::unknown() and result = maybe() or @@ -1866,12 +1868,13 @@ module Expressions { private predicate isinstance_call( CallNode call, ControlFlowNode use, PointsToContext context, ObjectInternal val, - ObjectInternal cls + ObjectInternal val_cls, ObjectInternal cls ) { exists(ControlFlowNode func, ControlFlowNode arg1 | call2(call, func, use, arg1) and points_to_isinstance(func, context) and PointsToInternal::pointsTo(use, context, val, _) and + val_cls = val.getClass() and PointsToInternal::pointsTo(arg1, context, cls, _) ) } @@ -1993,10 +1996,7 @@ module Expressions { exists(ObjectInternal sup_or_tuple | issubclass_call(_, _, _, sub, sup_or_tuple) and sub.isClass() = true or - exists(ObjectInternal val | - isinstance_call(_, _, _, val, sup_or_tuple) and - sub = val.getClass() - ) + exists(ObjectInternal val | isinstance_call(_, _, _, val, sub, sup_or_tuple)) | sup = sup_or_tuple or From b30ae3980c0a067d525e5c957497a69498bb6b0b Mon Sep 17 00:00:00 2001 From: ihsinme Date: Wed, 14 Apr 2021 20:48:20 +0300 Subject: [PATCH 270/433] Update InsufficientControlFlowManagementAfterRefactoringTheCode.ql --- .../InsufficientControlFlowManagementAfterRefactoringTheCode.ql | 1 + 1 file changed, 1 insertion(+) diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.ql b/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.ql index dc5b2225a623..163305dd0399 100644 --- a/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.ql +++ b/cpp/ql/src/experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.ql @@ -57,6 +57,7 @@ class UsingArithmeticInComparison extends BinaryArithmeticOperation { not this.getAChild*().isConstant() and not this.getParent*() instanceof Call and not this.getParent*() instanceof AssignExpr and + not this.getParent*() instanceof ArrayExpr and not this.getParent*() instanceof RemExpr and not this.getParent*() instanceof AssignBitwiseOperation and not this.getParent*() instanceof AssignArithmeticOperation and From b3bdf89fc2230f8e280036166a249c6303106d61 Mon Sep 17 00:00:00 2001 From: haby0 Date: Thu, 15 Apr 2021 10:25:40 +0800 Subject: [PATCH 271/433] rm VerificationMethodFlowConfig, use springframework-5.2.3 stub --- .../Security/CWE/CWE-352/JsonpInjection.java | 61 +---- .../Security/CWE/CWE-352/JsonpInjection.qhelp | 5 +- .../CWE/CWE-352/JsonpInjectionLib.qll | 51 ---- .../JsonpController.java | 61 +---- .../JsonpInjection.expected | 25 +- .../JsonpInjection.qlref | 0 .../options | 1 - .../JsonpController.java | 218 ------------------ .../JsonpInjection.expected | 81 ------- .../JsonpInjection.qlref | 1 - .../JsonpInjectionServlet1.java | 64 ----- .../JsonpInjectionServlet2.java | 50 ---- .../options | 1 - .../query-tests/security/CWE-352/options | 1 + .../stereotype/Controller.java | 14 -- .../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 | 8 - .../web/bind/annotation/RequestMapping.java | 32 --- .../web/bind/annotation/RequestMethod.java | 15 -- .../web/bind/annotation/RequestParam.java | 23 -- .../web/multipart/MultipartFile.java | 38 --- java/ql/test/stubs/springframework-5.2.3.zip | Bin 0 -> 57066 bytes .../boot/SpringBootConfiguration.java | 10 + .../autoconfigure/SpringBootApplication.java | 12 + .../context/annotation/Bean.java | 10 + .../context/annotation/Configuration.java | 7 + .../core/annotation/AliasFor.java | 0 .../HttpInvokerServiceExporter.java | 8 + .../RemoteInvocationSerializingExporter.java | 3 + .../stereotype/Controller.java | 14 +- .../org/springframework/util/ObjectUtils.java | 202 ++++++++++++++++ .../org/springframework/util/StringUtils.java | 30 +++ .../web/bind/annotation/GetMapping.java | 0 .../web/bind/annotation/Mapping.java | 0 .../web/bind/annotation/RequestMapping.java | 23 +- .../web/bind/annotation/RequestParam.java | 23 +- .../web/bind/annotation/ResponseBody.java | 0 40 files changed, 346 insertions(+), 866 deletions(-) rename java/ql/test/experimental/query-tests/security/CWE-352/{JsonpInjectionWithSpringController => }/JsonpController.java (76%) rename java/ql/test/experimental/query-tests/security/CWE-352/{JsonpInjectionWithSpringController => }/JsonpInjection.expected (76%) rename java/ql/test/experimental/query-tests/security/CWE-352/{JsonpInjectionWithSpringController => }/JsonpInjection.qlref (100%) delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/options delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpController.java delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjection.expected delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjection.qlref delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjectionServlet1.java delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjectionServlet2.java delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/options create mode 100644 java/ql/test/experimental/query-tests/security/CWE-352/options delete mode 100644 java/ql/test/stubs/spring-context-5.3.2/org/springframework/stereotype/Controller.java delete mode 100644 java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/io/InputStreamSource.java delete mode 100644 java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/io/Resource.java delete mode 100644 java/ql/test/stubs/spring-core-5.3.2/org/springframework/lang/Nullable.java delete mode 100644 java/ql/test/stubs/spring-core-5.3.2/org/springframework/util/FileCopyUtils.java delete mode 100644 java/ql/test/stubs/spring-core-5.3.2/org/springframework/util/StringUtils.java delete mode 100644 java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/RequestMapping.java delete 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/RequestParam.java delete mode 100644 java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/multipart/MultipartFile.java create mode 100644 java/ql/test/stubs/springframework-5.2.3.zip create mode 100644 java/ql/test/stubs/springframework-5.2.3/org/springframework/boot/SpringBootConfiguration.java create mode 100644 java/ql/test/stubs/springframework-5.2.3/org/springframework/boot/autoconfigure/SpringBootApplication.java create mode 100644 java/ql/test/stubs/springframework-5.2.3/org/springframework/context/annotation/Bean.java create mode 100644 java/ql/test/stubs/springframework-5.2.3/org/springframework/context/annotation/Configuration.java rename java/ql/test/stubs/{spring-core-5.3.2 => springframework-5.2.3}/org/springframework/core/annotation/AliasFor.java (100%) create mode 100644 java/ql/test/stubs/springframework-5.2.3/org/springframework/remoting/httpinvoker/HttpInvokerServiceExporter.java create mode 100644 java/ql/test/stubs/springframework-5.2.3/org/springframework/remoting/rmi/RemoteInvocationSerializingExporter.java create mode 100644 java/ql/test/stubs/springframework-5.2.3/org/springframework/util/ObjectUtils.java create mode 100644 java/ql/test/stubs/springframework-5.2.3/org/springframework/util/StringUtils.java rename java/ql/test/stubs/{spring-web-5.3.2 => springframework-5.2.3}/org/springframework/web/bind/annotation/GetMapping.java (100%) rename java/ql/test/stubs/{spring-web-5.3.2 => springframework-5.2.3}/org/springframework/web/bind/annotation/Mapping.java (100%) rename java/ql/test/stubs/{spring-web-5.3.2 => springframework-5.2.3}/org/springframework/web/bind/annotation/ResponseBody.java (100%) 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 8e2a0c9005ff..8f39efbc2b6a 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.java +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.java @@ -105,52 +105,9 @@ public String bad7(HttpServletRequest request) { return resultStr; } - @GetMapping(value = "jsonp8") - @ResponseBody - public String bad8(HttpServletRequest request) { - String resultStr = null; - String token = request.getParameter("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 + ")"; - return resultStr; - } - return "error"; - } - - - @GetMapping(value = "jsonp10") - @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 = "jsonp11") @ResponseBody - public String good3(HttpServletRequest request) { + public String good1(HttpServletRequest request) { JSONObject parameterObj = readToJSONObect(request); String resultStr = null; String jsonpCallback = request.getParameter("jsonpCallback"); @@ -161,7 +118,7 @@ public String good3(HttpServletRequest request) { @RequestMapping(value = "jsonp12") @ResponseBody - public String good4(@RequestParam("file") MultipartFile file,HttpServletRequest request) { + public String good2(@RequestParam("file") MultipartFile file,HttpServletRequest request) { if(null == file){ return "upload file error"; } @@ -201,18 +158,4 @@ public static String readPostContent(HttpServletRequest request){ 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 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 2a3f0861cdf9..e8fb89d3989f 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjection.qhelp @@ -14,10 +14,9 @@ When there is a cross-domain problem, this could lead to information leakage.

    -

    The following examples show the bad case and the good case respectively. Bad cases, such as bad1 to bad8, +

    The following examples show the bad case and the good case respectively. Bad cases, such as bad1 to bad7, will cause information leakage when there are cross-domain problems. In a good case, for example, in the good1 -method and the good2 method, using the verifToken method to do random token verification -solves the problem of information leakage even in the presence of cross-domain access issues.

    +method and the good2 method, When these two methods process the request, there must be a request body in the request, which does not meet the conditions of Jsonp injection.

    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 6b989c3c0cff..8220745e6672 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll @@ -7,62 +7,11 @@ import semmle.code.java.dataflow.DataFlow3 import semmle.code.java.dataflow.FlowSources import semmle.code.java.frameworks.spring.SpringController -/** A data flow configuration tracing flow from the result of a method whose name includes token/auth/referer/origin to an if-statement condition. */ -class VerificationMethodToIfFlowConfig extends DataFlow3::Configuration { - VerificationMethodToIfFlowConfig() { this = "VerificationMethodToIfFlowConfig" } - - override predicate isSource(DataFlow::Node src) { - exists(MethodAccess ma | ma instanceof BarrierGuard | - ( - 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(IfStmt is | is.getCondition() = sink.asExpr()) - } -} - -/** - * Taint-tracking configuration tracing flow from untrusted inputs to an argument of a function whose result is used as an if-statement condition. - * - * For example, in the context `String userControlled = request.getHeader("xyz"); boolean isGood = checkToken(userControlled); if(isGood) { ...`, - * the flow from `checkToken`'s result to the condition of `if(isGood)` matches the configuration `VerificationMethodToIfFlowConfig` above, - * and so the flow from `getHeader(...)` to the argument to `checkToken` matches this configuration. - */ -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, int i, VerificationMethodToIfFlowConfig vmtifc | - ma instanceof BarrierGuard - | - ( - 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), _) - ) - } -} - /** * A method that is called to handle an HTTP GET request. */ abstract class RequestGetMethod extends Method { RequestGetMethod() { - not exists(DataFlow::Node source, DataFlow::Node sink, VerificationMethodFlowConfig vmfc | - vmfc.hasFlow(source, sink) and - any(this).polyCalls*(source.getEnclosingCallable()) - ) and not exists(MethodAccess ma | ma.getMethod() instanceof ServletRequestGetBodyMethod and any(this).polyCalls*(ma.getEnclosingCallable()) 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/JsonpController.java similarity index 76% rename from java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpController.java rename to java/ql/test/experimental/query-tests/security/CWE-352/JsonpController.java index 4c60b356cfb4..c7fd850bb093 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpController.java +++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpController.java @@ -105,52 +105,9 @@ public String bad7(HttpServletRequest request) { return resultStr; } - @GetMapping(value = "jsonp8") - @ResponseBody - public String bad8(HttpServletRequest request) { - String resultStr = null; - String token = request.getParameter("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 + ")"; - return resultStr; - } - return "error"; - } - - - @GetMapping(value = "jsonp10") - @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 = "jsonp11") @ResponseBody - public String good3(HttpServletRequest request) { + public String good1(HttpServletRequest request) { JSONObject parameterObj = readToJSONObect(request); String resultStr = null; String jsonpCallback = request.getParameter("jsonpCallback"); @@ -161,7 +118,7 @@ public String good3(HttpServletRequest request) { @RequestMapping(value = "jsonp12") @ResponseBody - public String good4(@RequestParam("file") MultipartFile file,HttpServletRequest request) { + public String good2(@RequestParam("file") MultipartFile file,HttpServletRequest request) { if(null == file){ return "upload file error"; } @@ -201,18 +158,4 @@ public static String readPostContent(HttpServletRequest request){ 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 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/JsonpInjection.expected similarity index 76% rename from java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpInjection.expected rename to java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.expected index 83f2b7f206a5..14912328a1e1 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpInjection.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.expected @@ -13,12 +13,8 @@ edges | 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:129:25:129:59 | ... + ... : String | JsonpController.java:130:20:130:28 | 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 | +| JsonpController.java:115:21:115:54 | ... + ... : String | JsonpController.java:116:16:116:24 | resultStr | +| JsonpController.java:130:21:130:54 | ... + ... : String | JsonpController.java:131:16:131:24 | resultStr | nodes | JsonpController.java:33:32:33:68 | getParameter(...) : String | semmle.label | getParameter(...) : String | | JsonpController.java:36:21:36:54 | ... + ... : String | semmle.label | ... + ... : String | @@ -48,18 +44,10 @@ nodes | 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:129:25:129:59 | ... + ... : String | semmle.label | ... + ... : String | -| JsonpController.java:130:20:130:28 | resultStr | semmle.label | resultStr | -| JsonpController.java:147:21:147:55 | ... + ... : String | semmle.label | ... + ... : String | -| 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 | +| JsonpController.java:115:21:115:54 | ... + ... : String | semmle.label | ... + ... : String | +| JsonpController.java:116:16:116:24 | resultStr | semmle.label | resultStr | +| JsonpController.java:130:21:130:54 | ... + ... : String | semmle.label | ... + ... : String | +| JsonpController.java:131:16:131:24 | resultStr | semmle.label | resultStr | #select | 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 | @@ -68,4 +56,3 @@ nodes | 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/JsonpInjectionWithSpringController/JsonpInjection.qlref b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.qlref similarity index 100% rename from java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/JsonpInjection.qlref rename to java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjection.qlref 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 deleted file mode 100644 index c53e31e467fa..000000000000 --- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringController/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/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 deleted file mode 100644 index 4c60b356cfb4..000000000000 --- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpController.java +++ /dev/null @@ -1,218 +0,0 @@ -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"); - } - - @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 bad8(HttpServletRequest request) { - String resultStr = null; - String token = request.getParameter("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 + ")"; - return resultStr; - } - return "error"; - } - - - @GetMapping(value = "jsonp10") - @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 = "jsonp11") - @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 = "jsonp12") - @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; - } - - 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 deleted file mode 100644 index dfbe0628760b..000000000000 --- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjection.expected +++ /dev/null @@ -1,81 +0,0 @@ -edges -| 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:129:25:129:59 | ... + ... : String | JsonpController.java:130:20:130:28 | 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: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: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:129:25:129:59 | ... + ... : String | semmle.label | ... + ... : String | -| JsonpController.java:130:20:130:28 | resultStr | semmle.label | resultStr | -| JsonpController.java:147:21:147:55 | ... + ... : String | semmle.label | ... + ... : String | -| 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:44:25:44:62 | ... + ... : String | semmle.label | ... + ... : String | -| 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: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 | 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 deleted file mode 100644 index 3f5fc4506696..000000000000 --- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjection.qlref +++ /dev/null @@ -1 +0,0 @@ -experimental/Security/CWE/CWE-352/JsonpInjection.ql diff --git a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjectionServlet1.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjectionServlet1.java deleted file mode 100644 index 14ef76275b1d..000000000000 --- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjectionServlet1.java +++ /dev/null @@ -1,64 +0,0 @@ -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/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjectionServlet2.java b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjectionServlet2.java deleted file mode 100644 index bbfbc2dc4360..000000000000 --- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/JsonpInjectionServlet2.java +++ /dev/null @@ -1,50 +0,0 @@ -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/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/options b/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/options deleted file mode 100644 index c53e31e467fa..000000000000 --- a/java/ql/test/experimental/query-tests/security/CWE-352/JsonpInjectionWithSpringControllerAndServlet/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/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/options b/java/ql/test/experimental/query-tests/security/CWE-352/options new file mode 100644 index 000000000000..5e5f10f69456 --- /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/ 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 deleted file mode 100644 index 9b1751fa2ae3..000000000000 --- a/java/ql/test/stubs/spring-context-5.3.2/org/springframework/stereotype/Controller.java +++ /dev/null @@ -1,14 +0,0 @@ -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/io/InputStreamSource.java b/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/io/InputStreamSource.java deleted file mode 100644 index 372d06cc7383..000000000000 --- a/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/io/InputStreamSource.java +++ /dev/null @@ -1,8 +0,0 @@ -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 deleted file mode 100644 index 6bd357f2228e..000000000000 --- a/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/io/Resource.java +++ /dev/null @@ -1,46 +0,0 @@ -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 deleted file mode 100644 index 44bdae10fda5..000000000000 --- a/java/ql/test/stubs/spring-core-5.3.2/org/springframework/lang/Nullable.java +++ /dev/null @@ -1,13 +0,0 @@ -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 deleted file mode 100644 index 78d384d72660..000000000000 --- a/java/ql/test/stubs/spring-core-5.3.2/org/springframework/util/FileCopyUtils.java +++ /dev/null @@ -1,53 +0,0 @@ -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 deleted file mode 100644 index 6ea27bbffa2f..000000000000 --- a/java/ql/test/stubs/spring-core-5.3.2/org/springframework/util/StringUtils.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.springframework.util; - -public abstract class StringUtils { - - public static boolean isEmpty(Object str) { - return str == null || "".equals(str); - } -} 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 deleted file mode 100644 index ed692a03063c..000000000000 --- a/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/RequestMapping.java +++ /dev/null @@ -1,32 +0,0 @@ -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/RequestMethod.java b/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/RequestMethod.java deleted file mode 100644 index 78c90a6ef75b..000000000000 --- a/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/RequestMethod.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.springframework.web.bind.annotation; - -public enum RequestMethod { - GET, - HEAD, - POST, - PUT, - PATCH, - DELETE, - OPTIONS, - TRACE; - - private RequestMethod() { - } -} 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 deleted file mode 100644 index 56094811c376..000000000000 --- a/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/RequestParam.java +++ /dev/null @@ -1,23 +0,0 @@ -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/multipart/MultipartFile.java b/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/multipart/MultipartFile.java deleted file mode 100644 index 93ea3439fede..000000000000 --- a/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/multipart/MultipartFile.java +++ /dev/null @@ -1,38 +0,0 @@ -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/springframework-5.2.3.zip b/java/ql/test/stubs/springframework-5.2.3.zip new file mode 100644 index 0000000000000000000000000000000000000000..b54730e4bbcd561f20961940fb62c54dcee1225f GIT binary patch literal 57066 zcmcG$1yont);>&kcT0Ckr*wCxbW68%H%NDPr*tFT9nvKrT>>Kb{m>I0zjM$3dhdN5 zW63$g!Q6X2GuE8XoNLQVfr7yRJ^k5H2vhv_AHV&D0r=@)Yj19CYGSW%W$a>O|B8;4 zo{65BK_2isXn=o(0%AzAQ`8*NqvJya0@9-e0wVgCzauRnBqFOMLhtD6xT>iYx7>j4 zbyLO9I&*LWN5^eG%{D&M-)cR}r8!~olKl-5v}Cb>AMoH@ZOWIMOjv%O*m>t7o7Ldn z*bs+Kk3FA>-o3|4E{&4}*D|R&x9gIHiu6xpPy8@|wZcsTd)(YYPj#`?7PeoThw#CmOY zRj1QCQWx`T_S(Y1yYcfTua^*5hi2F-l|$7V!VKLPuv@f#lCYaM(^ylKOJM}c#|f|6 zqOHgzmsLF^XXit^%LF;*IJMx(yk3GS-dYxTo?bb3od#az3MQ7KEGt+l4+(dGd>%(= zZNHUW#Cc)nI-V$$_x@3x{!kW-#)_S*efg$`Kyw!?1Ps!N4~Fx~{qU+YR5#n3->l#_6_bUR@XlWREHyIxHZ%WFha z>uB#wy|6$WM&Eb=sUt|_IP#TV*hYk^``}hPyN53ZJl)KQ#swxL*yBO|#9uFv;|RS3 zagCxQ)@(14(=38K+dk)-taaejDLCHZr9Uh8t|Y1F`SHlQE(-UdGNT76%T)(tSsr4S zjO?8_vbePhtSDtYi^Ip1^r2n8Wql(0@;vY6KI0RorC1pPM^-{Qii=w)uvrrEGF})E zd-nJ4Fs@Y70R&ERUbH}xr!`y(@Sp`QfVKNHCP&^vs3fS!w}}J%psV- zNpsAs7$U4$>>>Hwv~D2b806r(sS>CQS4aeX;rQfxzf@gY+g@=u^%a+UtXO{q&#|R$ zu@rkmSb2^${O0`&* zjA7o=Wb{nOueQnFbL#P7MXfi52F#&%7}^J{_>>G0BHPU4x~7!?@jhl0#=`1YBYtQ zZ(v1uR(ZJfcpVkeJNhCXVHgcv5aw5uR2#`_2db&eFZe13YGmi?t{8f2?)iVeSW3u%L-S2JdN>=Z;N69Kw$Jt?W}{!yM1v$I}{l z>w46pgDW}^G>jSfg%!gSumMqd#x#~g!`w~Xl%Bta4$kDJv?L2+$rrd?{70+qgrL|M~?=jXK@Do(hn#WE`5PPD3LG%c36RX2S>31eoL+-^ zdLR@J9NXH~=CM57wmHMRFb8r*Z0HsAnjU-iB^Yk|EjR_?09V9zoMh7QYj(@ca_r~w z+$00Uo}f$)W!G2mG(2g`_Q`y61|TC8coT0OO6iWW`AiVpC~Ec}pSkXVM3qzyfceMd z?HQ2*^CAi-G;qJmhT0YJHH&I+isYE+d2JYWrdv!oe|?l(f&i9Xu1(FX zz{~=CPidd<($se4Z3XtyuzIVXSwT$8`R0;z^YNgt{h_Q=UEtBR4wuZ526yobyIcjX zPaQnx3}471x*Jq-ND>=>g_&~PxSTx*qoGmhUxjWl52nFls^d+f*DTQWm5P3ai;WybaiTE}Iym0`Ifz4AEl-C3OiOo*c@09t!G&Reu~NJG5k3*q)2dM-Dh5tY?m8 z?>TrsNAM;#QRT;wN1bpp1sQNTj2rhal@Oqmhct2GZYsFN@2fXQocA(<3;SXxwiqJk zwszI3Q}xP-K#-rweWF_Rvu2^qh}&z*55u(x*1Z;{K%xviarlLQ6z zs6N#^RVr08{N))0pX0TGr+cTHXPwWE`_a%^va>Iw;7NpB=1UD|4inGu+MTKhWjtQ8 zTY1&RXSLZ%rek3u5?h({HbR|Y+1_fP zs9gD(F$^$NGSHF+|D?)ts?!Z8JbRA4; zgfZD-^h}jIEV=V=TInKZzN2?EB;;|c=u4j?SVSb#QiJ-=LlsEZ$3>Qzd5!SJN5MIo zD~8|m2esHGSvrQ~@k2cuAy3GY(EAW=W4;K@4m?OK=IceZ);KA`%PGSPlBc+RD}`1E z8{36>JuxwnqyY`V*WLmX&_H6xqqD;}yZm8!@2d$5EMs`@C`gcmdXj>A(B-S;?VN*8 zL7zwFFXV&FTl3Ck8wFVS__@zYiPQVdqnk+?4IH~+r*#Tq%=XYAAF-GM$eFH-hs%5O zIEDyikMl`}WKG`KVxCb@1|s{o(uQM4Rnn@AXA-$MlLQ1IVp_oIf;gdQKbgm5y8=! z#wbsoebamzSByzXLk|z)I~063)6|j9d4qDJDSv^fhFkNVwii(?ZH^r0=H17~f#sW< zt#@;)t8<^doa&GpUGD0?9OK&6KWcpOtnYwm%DaQOMQ+S{VV+x3sl1fRVvKYwVO*}1 z$5CBkMrdC|h7s|)v&Gw8$!Bh1LhX> zSA)9{GJ_5W^?0a{JI4# zw1vyoOg9qv&tBl3V#1->7TH`S0^vh9zVqyLv_aPRbPM5Ew0wSf*FEPj|H76E^`!~R za})lq%7n<3F_&9ct`3xF9fY%tp4RK5Pa0}amUq;^z;LII=YsS|k?1%s)`aYpW%%nU zhM0%}xAV8IlVq&Q{Bu^n_`ad*aglofc)7TA(>SlYqL3oeZ1O2U|9-{TgnMoUdGmv z0ZF;zMnVHKt<1*==*wXox_E*(J7cxm+ zpiE0NQkLWv$tYkzz-gJAOtVh!1c=>3EYHSSQLDUb>Y)I_Rx?%In`TuGC2qq2yz)ri^IIgG1LR>QP2X~RdLA#iji!LwJ5=b^T40ruUU6hVIP$nu$KbJdMdx#K{ow=W zptNZRSS}yUZSzt^qs<}Z~#^*{dJPaMDx za(-qE7=KBQfw8`|!#_yl_z$F+=o>oP*uVaXp?K~Xup|bMCJk8P|AkaRz#EHxe*(RQ zzOz1!xb(QJw4BNygX97YJ>~GIlpKxZs0`f%!|=S6q|7MIyp)tQ{SZUx+YejRlrqCZ z(yD`_G!k?mVTIHj!01f^`RJvunt}dGr%>O(#%lsz(h{(g|BIIuH#fG|w>LC1H`KTM z`Nd_1pCkAX!U1(;<5l8ze7=CFbrLRmH5vOqr%3n(?M|mi3I*)Oz zk9v;5O){6SW-7h*5(y+Up`Pj;WqI*iDE)mcf({rD)hH$3&eb=sf&aRfw!{1&S->9T z0ZZn;*o%a5lZlMnpHgFSiGPyfR3-Ku}LLuqpdM|}fJX2ZbVo)JmpKKLvt`?eRCXt~w5IcrT9dwn{*Vv&61FgA3uH+TFVX#PGJtNzcQnj!M z*d_mo@i`bh`Q1AT%Ny!#F3nfj8gXpdbMnz@v$*%PsKbweY1!s+jHyodOvc9Uv`OIU zBCy`XHd<0WA0ZzH9!}U#FBoN?&9eV%N8|kubnyQ(-|y5<_(Lj^4^|sTm)8(~V*YdD@r$X#lVATE9?bviaQ`9o z?|n6JHYz?4Aax3`jQ+k+4qwi>FW^Dh>O|4|4U_hS3txizc`|0o_ z8_ip{31}M7&9Jdb;ES}if(u~T-aJ<6t23fh3m3^KMq+jg(u{2J)NvL}k}Wv^HJjG1 zvJ_0KAKC0C=?t}6Q?>eGRFa-ZH_BQRG2qiw+^2(NtD(X{5am(hY2i|9ZgDxVx-B*| zWg#*?GNYFkRp1*r#(-Ua3;I78@|zl%9!8{~ z8y*qSmlq%}!=_(Whi|iL3t75hj=xgrb!nU>1GnS3eLUS}x&;Q&Ox&>6AP&^g>*XHV z;4-O*2sJcOa1X@R27y5cQ5PVIdxa7u{{98Cs&_XAshCTjp5Nu^TU*;We#7sFtocbvr$_lLMSxel04&k}=v99Z{YTd3DL3+@AD*L=Y)GwcZDHa07WkTD^4@EagHEmU>9|IHol!7a>bGDoG(BObYUoJVuC8jP15I<1 z%#1$yZShb@Hk02{+V(+y#92a6&X}J?3V#hL`G^BADmC3W=4G~mkn0wgs?w3(6P;$N zVg11*!<3?Ky>L}H@V?m@d{mo{DN!AX77AOlK$QEAfD_O6o@N_bySUe#ZqhmQ<4^Wl z&3}jB1Aw3vu;l(*DE`J~quQT>Z@`nyssn76I_CsV150K{xG)qSJLwtGS_Vv_tR=;& zXI7<9YECvL-RAp*CZoV0mnlwC{dd9OdKjQ2o86oiFK54iTO(21F1^(+U5uuC!HwV@ zjQlpcqTxIVz!db(*u8JmDQ!Ge)zi85>`&)>>gVbTbcxOYHn|9J zPKp0$v;Y6F^jp3lU=0XFA`7Eb9zK+qdUM33^qSC9sIGq4v?fP`)ac3> zI=wZWS)I&faiuQxTK&n~r58+W)gbKw&O$uH}& z20vcBwse>6nu9Bg2b1Gm0K-t#cuD7nL&_c8i3DtIiMA1?kfTAnHq@TEv+alKC8s5) z;=;Ur>r2~(Z*t4YYWi2?LbxqII|2}k1gxi$=ig5?e|N?okP~q>wsw?vGO#pv_&t)d z4s#>)@WY8rt(DnA%dka5z(g18!A98y5p^4K#=SwSabY4Hc3ETHit^LDkca=EAar;Y z%lTT(ZkkrA5*T4mfpZ{#R+sHEQN63b@J+-yqASa?kEBbg@MQ(qJ0UzZ{&dB*yoY`F z)5D)jC~->Y9!lZizVH1mrNVvMK5Ei`E26(A;y;JQU#ol!ZT?b0{49*?7dH(M z_Cpl>(_Exf>=bDwA~ik(`Oeb*ZKrRU6J21Fk+nXgl9W2BO(D#EMJ zZ%+mToqDIR4yR~dDfTO`s_$)r$_=vOa2{Ta!D(+ds5-J@%k6R{z4}-$~k5wHDQCU>Uw-M;OSPdF_~dh?FK*@A_F zyVPi4xE0l&)d=)(JwslH9D=7AT#lK!MrQ4IUfi~Vs|xA-66ntFh0qEtOAtnNtIB*P79BQJHC+*e_xC-|2n+J*3Lg1h);nL)CFBf z5+LZQihTO{i~4#JC}QnwZf|341#oR4ecLBy;F}A8cYmWYfV%j;Q5oiV`7D7f;>*gt z!FDdEcqN90)n<1WNLH-2XNFVE8nz}cwIscgl%Rwu#6%1@93_$w=9}{jIkDFm9-b`h z?;-frYy)6n<~BbeP~FGJhy(@%)B^RZMA|pE0?3oFwsmq;aezuq4qw?INIrE(F zZOt`9EdxD`z`o`>O`A1^P}ufBMzv0!ACL)^%fJ_?`?C>P1P2h|qbU+?NChQ8mJl%; z7vxW33z9rg?;OY{Lx0(49PNSSqTd%3k?T7ngj$b5=uEgtaAzlAmq88Z=VWu{R6W*b ze~evO8=G!*$X$JTUx>~HiSf0pfNUt7H6+Z;*g(K2!OV|ODje-H$edl(tt5%Ww(u;n zdXTZ%acx-S_(uCsLbB1t#{(57s)&m8;CWkCp#+yf^y$#{z+I!*?F&!WWJ;>Y=pQbUr#!__G}95pg8#KX&g(4NaVfh@l#3=q=_H{Pq1C0TkvO$TXTN?W{k3ghX;HKA^}U2)#F*~!O3T`a|!+(q#2 zgEkDYwY~y9atE<>e7VVa(+NAYx{of(ogRV35WPos3RaG7B_W|9X=1;URGYWh-J$EA zu1Pb`J&BL-W}WkJ)V+GOM9(_3rQ(_Zb+c#xU^I6i#5UhKv&b1tkZQ(u4=a)fzn2=( zI!IOF6~dGZ)3^%)zXlCMbF@w(78^nUQ~Vb6%U1FRXZy!oRbSx9Q9nZA4=y3T?zW|9 ziqGTEGJ`UmniQZm!u5)tJF>4&?MUpD5e3$TVx9PO*b+TZ+2D1qmYBmK-I4bDRsaO_rE_ewbUuMRO!0mXiAz^P8~y&pM+@4jRs-wjg5a&_!ylLp$jXy6 zD!|BsoV&n9Qqe!2IS~fQUI-F-%~U}*acP0`lzyT(r+v(cjoNP_rA}?A+J?x>gmZtr z_^OIcbr5yK4@7mGm&r03t0zKHQ*nSf75cGus4H{!Qx+HoUbOSP<93}eALG{@#~C*h zp}@sDy~Ruob}L4tB#*{$YuFpfl8yZG48*Ky@K4Q76|*1S_Fd7u!f8C7aZkNWIll?2 zZjiz-Iv4x0l09Q1z6|S`|A{c}4!FzaGCf6emzP~Br#~IJe`=~L6|`srDU0oa{PRyq zhc&hF)jLQapcBAEBHypaT|m@PGzacrcBFL;rj_sKp{?CBk7u0@n{c|$OH$5xWs%6? z1%^pgy1E@L9uFTdKO}asAi(sIZP#mA_;+r5vmG*ZUWN|}Xi%zGz%Y8v6mQ+3Opp3J zphlo;){Gpn9!~j%b2v?P5~Je2tc$fto{pNsm<+16q$g!`9nG6_m*tHNt6Ke74Zm9&Lx2@yT0ikNW=5wn?Xo`Q+vJTMam4b<1Gge)eZJ zMbC6|mhG0+$htL6HgxM5p;1D^;F+;#7cd$);>NbYr_*I$3 zvDM??^M~6L3~x_^yk)+{_5Q|Z5OC4VJdgx5LQs=30v>{!LunvK&5R4(WLg6BABe@w zEEF!ms{?g>6+%K(^d$8Ubm+L$qp_(ZH=8bn#G3ZA^&7)^_@ar7xatxFq#WK!slU36 z-hIzcex=eFK5o9f=+d#p5$!2Rr;U#gb^5~1X~S;7p|o^$7JFm&F&!kyGb2I{-is4U znE5W227KCt{X8p+HU)@59HP?-uXmT5uxJvcXvvC>?6{}kU6PKW;cnOHYXWQEyv6cL z_($5V52DKB>+miS6Y``yE?Cr}NtqU3R2(E!R|-KCJ3Aqob6}-{AV%l!V(r=*kc`Y2 zlkij&>>4c-3vRvC+MiL)6UXls4`5rD@e6FWgQ4}A?3E-lZ({M}hS5O)Ym_A7hU*iX zzvk4VwBo_%EP8j~jLoDfe%8>rFh2zhp>4H=Y(xUNZOENr$n3oC(+@AzfkJ9|SPxHD zOxUj`(GNZCByG~UpofvYXbP|B$G80+M)G-5r;!akvvi<0yg$}628<0&+N;m*%uu^# zMjniU99lx_jI5J$a>VH-TcUYj1glfGY9u!Uwh@+@xLMZa{uhfptJi#IrEg5cTSyFV zKbRGQx3RJxsb0g?JwuH&nW0_l099H1G;W@JH+AeReu&TV2#i;N3~t#DmX+giBFBz* zs%q_Q@Lo-E{unHDZKeT5X5Wtqbu6X>HW@Y$+ZDVUWOCOacj6g@Be9z?m2F$XDo6zG zxR>@`%Odc5@<5Bg6FnvtJONPr+AT%2HDMazcNVAX)ypa7biQR!c?+Euc8sdvTXx(q zXp$Ji!&cQno7lrk@&oUCd$ELpz_p-H;2)Y21$pscotCSwWW-_O1IAKt8iMtqrJZB0-^d6E!Gf$YZqnc|#hP(l$mboRQ!L&LZV`iHB8{ zM=D6X_q4^gAP%z~dSTJJ`%T*mdzqos*q!i(aL19w(}c5ZXvNm`h$alGj%M437?L~`UJ>a1s(T6&bmxCde6Pgn`}7pn>E z5249QNx4RVSQxl$r+MZ&NrwgLsuAi1E1feMigMJoyb;!aY>f=RqqsbIc$R?CH_j{k zMQ`GOer;T-XJj1ZbuiG4#1%OrmIrn7k-|Prj{E`Np@S(9BUfyaX$;o9h41IaYXji; zOgH5Pn8+M{OE!tzP9@wo0@Kt`T1L+fG*HFVQyhE@_$r{KTHflZrYqC3L-H74;!sHn zUL$|TSe_pb{p=7m%lLp+632)h<}$mg{Y)PB!B#47A+a8Z)dKz#XeAj>*)9ysFq#>6 zh^$dG{xPzzPDf8K|E4gvb8YDDV7qeCQVoe(5mDsV;OPFZ$oYlLLu+hM1GLxJ6C&a+ zU!+5h*Kq5uDIzO&vcT{!`r*8{rO12H>y!OW?QO9l_iXq#@8%&X8yxfuUc|7DAt;ID zZ~+FAP>1kRu4!3;FSV@=^|D+ck6_Lgk)=HX1!Qen% z%D0)GhfYbtM1wqL&qUHWD22|{?FRZeed$~PSy^|B8*|5zBTaLARHm`RA3sks31Mzt zQ3ayWxSas#WO|)oE$xTkeD`;a5ugdE3pE#xd!z3~3I{SLK$| z8R_ys%yzVoFG@6^e^e9$=z(*e z$fMkq=-pA?mT+=Ixzx99BW}L1`e|~k?x7KiG3)I+Wetrb6dkh9qFOQrh04|57BBE= zWGe~@VA{dGuX0MQ%x*Ynje34H*66Hq+=Cj?p*>}OoVs~)D!HN~(qmvU>21#Xu86gA zC;Uvu%!tg=tm@(MicBiGaBd!Wovq_U%ciDv$%QtLVLFT)@f7N=TIh&#$@we%pLO(8 zU1o<;l=VbNJdstu)khak``i(XJUXme!b@8cagR|(tb{uW+U<%4d(V{%- zU&}bKP+{&(+1{t6qxcv&ppnOk9L_*=)tuFr{}xx;A{zc31$@~d@bG=$w^Ziu3$p)! zN&uuX4o&$^JaQmGd6)WYfL%Oxk zK>k(9^aEj(*MQym1IUtJYvX=rbo8I@umE;s?q+PH{My!7(b&Y;-q`v#4k|%$#QGf} zQrkVvr+sbt)6n-qFS1qQo~fU`C(qfSl`4HZ+=;oOeA0blh)65QT=%Za=R*j-z=mv} zZ^fx*Kc*YJhcY>1MVW>8dkxUyJVqSgZAMYo9zRrrd(p8+rb*`d-W_{Pb}zV!6xE3-(-6=W!?a z7(1{7PR3&SVmvLFXYjMh(lGtOx!5eT^o-WR7lWKHHuUtO0cb@e1Yi*t%M2a};TLLBr0MY@(`BMkv8)E`L^fO};D~J=#1|Hwr)ar4H zCe$xXnCPLjotnET@<#m=%Um&QVhd-{HZ7jdD~fO|s3_c#t`6sX$N3ZI|0ESelc#gmv-?20>d;r5ds@>KLCJ?;J9Fs@M`nih<7P z^LEQmMBYi|TKi2k}LED>Hfac5SzB+I+H=P{uxu5nng1+YF0%q>@&d#4D zvUFaCc4~SZ@uAt|*;F*Qfa_PL$9`d^d>Sk*2KL7w-ps#-i+wB^K0N>kUV!y$T;1P- zAg^x^=;;2x>FNqfN=Z8U@zII8KN;(%i7!bhX;~H9z!3;i4D=!lDrE;dDoRSiP#QEJ zQY4Sa4U}kbm_9wpnbb0jABgOqyWGEaVEnKT2U}yyZ?5_G2ayl}qyNa;zsUs*I9VI% z+Z+Al`#GHWW3d3ir~oU}{@*-639t>3KkV`cl>|od!n|XI6LrZFG<@HUIk?gujanRq zBmE`VXh!gCbvs}hO|uw-sN;q=esYupSvd18X@hJH?Xh4W*4uu3SBH?Tkaq21bxLQs<4>diP|ELB#30 zr8IW>D8vQLT?>Tr_LSMHpT;X?O-w&n0Wc~AEWQ5?R=+vGzd$SEIRc>R33l%;ViD%y zgK`d{qi0VZHeN%w>>G#V-6^j$S;4|wCeK!!+-u=n-+!$?c}~_~yfQc04lNfD?^zty z_gX{qYn1ntc`Db*tRywq$pJ09hUYyZyDZ_D7jQ-$aW8UKx!&VgX>NiX-4FvobEWTSAtVX=9iD~Li zQorv@@WO4Dd!vt!KUt`#|Ag@s02O3VAfOk&j`R}{BG!(8;lyvpRl8!%Q`3F*l!nJI zAoqkYNHsxSJ~*@G{zMuDN+Oj*8S4C0#;sUnpVGL5V#MoocbAM{8D0_uGe6{o8I2ln z`X{LRXq@Dw+GstX;y zXP$%I@cxW&;FIo!XJ8sW;7u-ma*QS6LbTjyp{1>jr?+s%V#XhJ9Fj*A*u}QX?lZFt z7vp0y!XlvGsJf21qoV>DwbXFxD_g^5NxMU+pO3XpH>s}O{M-OZ2tAjS~4 zkne;+1vhC2kW3LNm;2@hSbNcj&k>={uWn$2E(HDXGJKipe=_N6WT>7FK;Z@jVDbOM z%_sq8l$^e6AX0hAewh)j{hsCvF#cD524ivYo@8PydL6gT;*!I{C7!4m0{TejvnUZe@0tIWKl^t`p3O;O*n>$B`Ph#RpfeHM)*W+2EVHrOn-$hvPAio7)RU z2CR#sk5?BwUpdpp?kI-9SdiE1v%De&Fw8ED@WEsAJxw=lpx5e3K#hRy&7o!Xfiyp1 zKoa|W>h`9@>V^9Pkq_lTq_BeYZ-dRjsWtSxg5vhAf=$t6PCsvIUAJqlsJ*!l ze2*X1n)dPSJb$R9)Xyi`JThd>LKVjp&8qWhfD0J z&#<^7hL|?t{iMUazyj(pm=eUyLrn4N3;~UTf)*cksJLD?>#oYqg5|b;(YAYOra7Iu zV^4@LLm}uC`0|E(rp(2vK*AUNT^jYhn3K0a(aanA!@mWm-^Q_ji1){;Qx-jpXr8BPD+4fOeA%~zaCkxxm4LErp#f)xwt0zs ztCPVhfi-Oy&j6biQNm1aobzVQHgsz!%oM6%JXxV7dP(q-wIji;&z^V=5yW^eSDX-e z1uJ?b2j1?r*F6uW-t z*$OD@E*eHv*?{Ws!WN9;5wY@cXZ03?et5IpVkexW%l3v?DAgR9AbM_6TvaMsHRLg7 zJBXv%*yeMqIYY4J4iiUhpsm8GVN<@1t2viSPnkOby9n2!GZjqhbmhs&aqn`UVnMV1 zVrK2JWU^AMi|R)-a1>{9U21AcbFYGR9u|+D?ssQqH|Et9Z`I^tAzVw5)j~>9gWp^h z9NJuWG+L!C;U69OA^uf~agGR5Edb@^0~Gmd`TcK7{GPV_pv8jd3BZ&jTt8rj2M;~p z-6xd2J1yJY*Rp@9qM<cd(c*mCxq+peBR^RJ-xN#aK8{U%xGfx_q zQF_dtKE*N^>PVulFuywT$(8D@%eTqE8OfvM9;AaJk}jOu0X}FN5E8EZ0Ex=RfVu{7 z4dvUR7#ZDd@s1Hmc;b^9E$5Q_6)1gyy!`KXUW0N1?$z`w*^dMhqfw??X%*5ID`Q`V@I!Vpp*U z*`7^&E}jIpGLNveGkptUoPTr1(Vw`;NdnqKC1YZDm$7ThRn#f9;LQJ#RqJDPPZ6-5 zGtV)OGH^t_mE)3597{F)%xgwL1BA^k=d&|-rydC`AtR6;{ivY}Vj1%Edn?DNse8;J zmd_pyVfhHfn&ql@;5Q9z{ugELQ*NM$GeB{pw>YeZDjm(<-A2ZbsFcktpH$U0o|jko zf2p70^!!G${yi)3YZZf~zV-JThJP|?*!~!h)_@%LJp>RC&o8O#n=n}?OUpk7q#~8S z4G6U#s=?Vyf@(R}?d!Am^;jHbOQKR#r+=Rhl8Vvx>YalC%m?L{_l|=Icrpd&vD*-= zkM7g@GB+u=uzIxH*}fpiLYx>l{R-A7Adj{a3$3?i6`ynYm?eW6qPup6QqTv}RzBJL zhG}dV{u4XsZCJlO*(MMEJ9BZ4CEbj+Rw$&3i^*z?eESVL6o^K72Vb8~$ytP^07X_! zTXLxYI+rwc(>Ex|T{pZznw+8s5icekOA%3hn|TjzmpO`#b@Qg2TV*uz)2ZtSLg|;H ztS?5E+LGZzKRwn#QcY?y-oDuj($uN%Vws{|3LAuJi2Pg*Kb@iE`)S^GVAl;ubc7=c}+FH6tLX}@9y{o8vFkAq{^o&l|tHWKrr_r)z@WY6QYS$_V z{pp7pkCgSX14rLTYby(BR8A1O5=$*NvUR1DRd zz6z*!6DBDknKZ+(e%Go=tPYOVjeC+Dp7Zq@9VRk1!0{2=NBm&cLP)$`AKp6QE+vQ> zQ$lbw-tNqww-!q>CBF2XOi}3-yd=tpt+BS0uIL6!){cxO{$Qpr_hWC}MJ84|GTMwx zBBQvlGenk+y4&G-jgWDJl4dtzVWb552`iEhtExecxT@u+FRPF;$qP!6@-Yn-N^r9) zERZCv#nBIY9Ip)T6ux@;U#aHT2Ob(c|6=$B7{}4_R&mvtykfAkQpkjczUh^}Lsdzg z?YaVDv@Df=>%k###exC^6Mymw2eNKFsb5@z$EQr zPY(8E6Q>O+57VX{lGGd?m6p6WaiCVdr*ceDiPk#_V%%45}p>jubIL5Eom@ zz*Cbnyk(R(@b1Dm?^1F24}{&ju49KxbM z6SzOhT*GrTgLu%yJ%!;)^uRTX?1djG#Crhe{n?Sx>EJlx6RK6_yG752LCP0ec)?W{ zOJj~V^eH@YO9y4Ir7#gMjlH<@(v#q$%{p_Xl5M$CQWa<(W_pxrr5-{5jNw1u6#lh2 zN_%4~8%MyE&2Mr1dzATE;I9>Ae+Ya!H_fe`ZC)AM|8Ph=-CF2Xo2$$LL>olFA@OTm z*xw3&s{SOtKS&91{m$Ib7{EXR`ftB?#NNmt`0+oH47pOPqu|;&s2VBz(ET}j;3ni5 zVPHM0bbNLaE>%^huXNs4HlPb`4CfE-l*L+wuXok!Tl(+!w+R#9(a#PR(S~Q43L$74 zD*2UZ-dXTQbKAQf80rZ?9D#ere41K`ZJq!z4XYFErU|hW^8f6;m~5O~_izG&9xdOH z6uf*HDN9&!0k!?sCDWZOQPp9uz@H5}h>Ydo2R8X<(lo@(rWKZX5^P{k)>F@D1O`?WLx+`nJ>{R`yiBj0YSiEi>1ok80Jn?R^?_`XUR zv_KK_Uh(5!X*c%#yr!qoOvx#k2|7Q5x^lIv&iwXuI$7QlSwRimT!3o|a3Wy|55?+N zaT`yUO~zBiK)d>hyO-vRdeeVL=>H)Qa9IFw>FEcj`dRL; zHNk(D`?vGzPkr{R{!#`{fP$Y=-B-V)=zn;GfPsUfJ>Zh<@3&%%jg)@7TPx^fZuxsR z-Z(DG|Q!Wh*DpjcQ+@Izp2S3h$mc%kilX>w)cQ+n~%j~ADZ?yy1Fr~*?vhq<)o ziXPLNj-L_)l^PlmMaX3dM8b0HV(J{EaG4>yYElgtp|D>C9RpCCY7mJWpG!i`#}j?d4Wlcqu75 zM}Rf$Bw;>U0RwDJ5QN5$zPjoB!L9O!>UzlewYI?f;uZCGn{T{24t{F6r_rEzJP~Jl zfc5nA-*@bP(D&c%kZ$;oW-cQzteHoX4~NSvLAOZ=IW;zDD4MauWTd zIbXo~wU*Ssv7bNe_j{L+7qBIO(tx_W3(tTkVSPSRf}ZJtY;(oT8Hh!Z@Q17-L!EcL z5~opxxJ-3-n9{_n@?h3=FvX!iA2ia;H&L$dLYncEw9;lHLW2L~*K7NDyv>ei%9rlU z2)gd?bl{y8Y&im11f5xR_3c{hpXMo7nbz>M!8*V)1g!t5(*E3}fRVoK?}wmO#LK5* z)hBB~X$NQ;mJ)Oi*aJX~CM8tU)0q#Wou6zWd7R(fQ(<95fNA7aiD+zj$LA&LqTLH=my}^hgbmt^*zUF+MjZ%4qo>dn{KLiIU%S8d4{CNXHuxdYc#4LMtmRb|0Dy%6iN@2nI{qg> zKRxF6i1-_~^V?NkMPpOIwK-*0R0eLy47KL_U*FJ_jq*$^cc&X=&m%YgxO zC-?cWv3NZ0ANFF767Ymjf?uOOTh%bzaF3-iX%K0XHB;ho4@NUoRLmk?YhK6R4t^f` zc&dsL{@Dk#q`g{ips`0oE=!%x@W8*3-&2*SOb>JyZh)-vJSwB=-h7>4Lmw#78Wyc6 zaK&;R6G)#9g(wlp+3ESho%Nt(I{Tqm$gNacn@n9TTV2W+>c2k!6#tj78GJu3zJF2X zPk&&a4RA>YsAvUHh5pa{;Ym$@Jntz*{5`6D;}t66R}}$=RbbU}(ID9<$7|r^amtVT z;OrbP_@SiIh-5yRkQ8sX;(AfTFZykf$$VPo-LotX&%?n`flNf_FrlvLN$wuPL| z%VnppNGVH)HKc`BoIfLzzMG}t1$FdiQ86}-@q%s6ff#4+l>8*o5u@&TFv-<4Q&oLQP1O&>f$wawNB9XVzqXHntkXUY7cK%Ew#H6)!4BF6WiRTr10t_tKp|@oQN>XE;PkMFTsz|qm(+`!2ZFz@#5!ySJYO)~zBCY%EFK!T-Qb+(`sP2Co0V9N?6 zNG?U$wL}o}MK|jVNDv`r%wiOQRDGHHJ)D;tU_!>zeSzt2VF-%DFp`|j*bkyKBqQTk zsgfoSMKf*+LD>{-s%X4^L9iFlBo!VSGXkt5&?f*4wPu~3JG55`%Gorhn&bOe66ROWZ z)0nn!>BMW-;I;H=-R~1wjKgkEtg8?DeBB0#hO!vEQM%G|M0T6!o-;N#=r`%@-IbH1 z_L8z&wY@5y+epVmx{!5IkDTirv`a8{gwEVm8`rnA(kXX>7l>B#27xqmk74v35pHj5tL+B&M(TmB5` z_ZbqPy$|TGfrcTAsez@i)nRzL;K_yKNjai`h#qBqOF;?w;Rq}<#xePAy0fp0l;Mmr zG*FMuHq6V2KME(xNxC;Si26EY`c}An^l@^L2|x=LTzq-f`_)zQ1LJA5UF{f5$+ZA} z3V+=!d@rTgDwk74{`0fgREcNNa-;;JqK&6yCpzyklSGJWkCt3XvSco3YoAHd zur#!1UX*^l$HpBLvBQmNcjmzn9W^@aRo{G}zk^-9MtB|8*M3Y)zXkJGONH;KhW`YB z)f~|4^!mSo_s&$!}l7_>G*%cq8z{Z;LFhV(1b=8#)z2O4s3zZkWUpz~)#om36ZYX65V#bYL$39H}LTg12XFMplF|H2Tf3 z>%w;STZc^6JQoYZVFYVSOYV?k;d6s3jdTzyb;?+C9a*zQ<4%XMZ z++3;(bcLon4!yVXk}Il|@c1d`R~GbAjt6K$0$}j}Uv1|BPi6bYaU`-Op==Tg8D($T zdxns`_g>j#BqKs0GnEQiAu=+<=XYQC zb&uz|uglPfa7P&Q~&e;}wuDH(k$e6JpU$GRK1T7GK`^ z+od1ALV*>c!q-o{VZ?HnN1=Tw{m51HLeR3UuAyQLij#_xjJ4U!t@1l;^c}txlU#ko z;Rj@vx>{+9B$t&k9<3h{7w6IObN>=6OWAyEgl=gF*l701>~lf03+I=+ZPEYe>7)D1 z>$Y}nqgtB--?lq-V$D;7OjsdH`M;YVi@7*C+t~mS)thUcb4^e`iPY{@MI%|cjDC{l zP{8WCaHIuBe{5{Y5%OX zI}SdUMv5^cGQMr143a~Yy`~Z0-<%me$|bBLKPV-6Tf%x0*>w%HMY(e<-`l>{)|CI( z34W;^&pXHtq8ISlTTDINkDfO%HE^+ZhRqNqEUW>qC}wV8v6*6aDT{c1z@NT0x$L zxrOOo_i07>$6LWB&}@j%*w{lMEfppcNWsY*1RP6*u&juid&WCkcyBM0;Lu&1Eq059 z-CMZyufJ@wnRaQ%N|Etgsv=~Dc@g+PLd*BDQiND1lpS^l_~{&UnnKr7f#U}RCRGcn z8l#-4#y)GU{~$qsLE4@SvPl$>oA>xZ^5F;*%bw+?X7@&Q>uj_ScYL~nlgg(h_$rw4 zse}3^<>@{S=hp{t56_qj{-71KTia#Mytgv~PJq#y*f|3SFLsEtieJQ;4v5kOe8l!9 z&h}4=+1UVre?WjCR0x5WP(Oq=eetH_;OxjgtJPMv#|udp)vbdcu~9$#F8RZ&p|UjMejc%E>IDx&zK1r}K`i%bE>P4u zzml=M{i@(##A4GI+7`L0aY^tHReUW}04|6V`(=)HjvlO@cYQHzQ zv`MtcutdJ-@)}MHGf6wJf=X(jDji11f-6(`=DR+(!Ogqcb*X5{u{DB5iAI;%>4T87B|{%@-!T>Mbte(u5bHT)Tx7pi!`7MXHIsQ+F`m0O?C`_0GwuZ#IS9^p0 z7>lBzrHPTVI^an+!|_<0-0VN~RVdW45~2;Mnx?619m8(r&VCo75Q=f48@ae*w4B<8 zAgAPo_#qOH!+HnM1`nhjyh4PUe&F=-M%Jaq=7LH|9)nCT=h~GoxE|-eE*Hrd7r8ED zsjPN044+DP%2jnWSV8=Gw!({Vwh>r?&9iMBDfd6{l7u=PUi%p5;_ourj>gI5K=e6M zswbGUu1$Avtlytgx$MYQ-DK2H_GwwuYuV!sHmFD@vMHY?G%_`99^@6odqNiA`ah7W zzdL#a&wA@73Z7B0%2oq(8QK8;;`fEt^Zct9=U?AWIRXDS64FF34qHp30fFpE(dnzv*P733zNo@ZEVRntZYy?pKyq%f z0f|@0r9tg$!8HyOZK3{8?;3cIG+%uk)@1fnpj$e_*uh+4shY&Wv`tJ11sONj9>YBo;1-m&CYe>pzAD(l%bq4G|r>iIz=pFtmlaD1CDN z#YwiclmvEh6ShDZ6Z7wP3>Z5Pa&qV5 zHk!y$i)!d{c=myNR6Q>qM~h}3vid;uBiXN3Zs=llI|sdaSsD(V!1$+NDjlj%LRgbB z-z4)&_41AkGt&39dWl!QSz(bhn;dQa!k0l_a#{JHHWu2(y)5eHLY&Z8^K3&2W2}Oe zAFVi@$z5_@Hd)s#2M^{tl@g&HJ9+)}F`nFasa81B7OOV}QhNqm8V$Yt-t0yyfbitgEF@T^=Mf*`MUBIzALPy3Fd!MN! z7Qws3jblsX2V%Oym~p7bC72u@w&|+yXpuFtv2{JKd8mmY6sHqQ-uUW@P=sUYS2}*v zl&iTJLyPU5gE4s{Un<4M4;&j=u)p~c+kd`9jpNEmVn$LLGR>T3R!xoI>vTQtbm5qk zg}%qj4b5$pYu=YTx-xzbicjvoblGvJh_D`G@KW;H=&bz#TUA3ZOKn9D_iB`cyE=t; zi4%IRioPCMN%|t^!4~7=E5huG()5@e%OFUe_RSIZ(9zI%N#}Rx%3BzG)mS23hO6%k zI+ckwe2?Ysi&^rv8vLB^K~9&2#xdA2{@`HX(f9zem-jl3va6lBrf}{pLXgJ>C~I24D}_iF4JU(eYBI_2zzXxe0Ie9#t(0~ zH_Ipd*6AauQN20_1dk3Z_x4M>-&5Dq->}z~azPhzlFlZXAmaP-(j0GZ$OSY^RvEyu6l z%Cd}IAq1SJcKE-8(&k6DovoI&LpB)Qs5}aHW zTH$HLMP8wpKZH+AD~lG8-=Q>wAy?*}FDh>SP>trgA6D?}0&NL{DXnaEbU+T%#Y6nL z58Fr++;wad?>bqMVVIGg?B{;N+J|$M=ML$euPs5}t{*`&WUI6wS8h*DWb}YjWUrGcagLJ-*~IvRh_H7wkshuwH@aMTXLa9~m3Wo(pXFyLMi1H_-3r~%z}fjV^1{piZsLICc;mCoNQ4!HF&BXeYGtADQQ48zgl!HQD_C9l#^*jM zoGIWf(R29_MYrrPozX-)#fs0%8to*x8ig_;8z*&%ioTXJNv=kjWWb=|E&1!=Fu_un zk3`6Ch?_VWKd*DAu=u`pSYaPMT|8exb1^P@WMTTo$_;)(il>#Kky?~^HD8E7qV=~^ zo2f5*&a2)*yX>%>6Wc*V=Xv@R`r}>=qepYjbT~|nF0*282ghUb1FGW+mD!AWfVvJz9Xa&$Q=M0Y{c>&%NYdwY%#ZgN zn#-b&s__Vrw9XwK8xa-#F+^ghZ1Ul&iex+cQGW(fa!s6O0cr1b8`198@rCM@B`0!S zmN~O1;d4j452OI!`%6zD`+u$o=re%7n_y0;k36f-FkmB6#s&65$F-fmJUfz_E-{^MsTH7a)4-%+&xewT; zwS)6->Ew$l7V^cNw=|txie+WEb-gKhkUi30HbLTuDsp1Ms&S9Q8ZlG$wJ(}M?_!2R zCn|ACWRbPY+dEfqyKHr5e6mXEE-us2D%QrFCy#%ZAp5{2aq>WgFgvE*lk6eEh^vv) zQ^9q&Q##KqkQ`r1tZejT)oal4GDDuu3ec$Wl*4%0km}Wgdg-X}Gvqr?;i5$GB{^&K z=PWh`i7ZVONvgBOi9g$)TW3be^?a}Rc(VDk^vu(sv$9C>2GMm51^2J@fRcOf+PK|*z<20TG(zLv)%l`+8rtW1^4%Z2;+S+v>a4)gP*Gi0A}Zly4T}* z(D$PdSkP}vnm7Z~Odx8{3_8^;j(q_bA-?$P56s+L<59}H(E;5`nuJOir|S(r%BiR} za+Y-64_}<)X1u&`H(I80zSe0nwTzZ@2#X=c@*=z010@rK^xF6mtdiPew=m*4lw`c- zMRTdCGuZn)zoNTWRYhj`tgO{7D={=pm9nU_FqvZy>84Uy=-!l^K#Xz*^?ISGo{@h z-dj2Qpga%4pb@RR*Z#^yAF0vMF;|*gdnF=#wHSWcNe4PA7OUoFy#2867Z4vYa0MJ z612ny>meYDB}X6FoE$~L6*+Aqb@b5dWrKIRbG{1Msz7E+caPALE3w@lu{WIK2cPU7gPYi+5!f)x4lS2#sy?6G1o8v z0tR6jH6x>HAlvZg=hnk17CI@1P{PRg^$*<}EY0u6ENas}QNK24C>7^UHE7ClrjUc; zKG9i6N$q$1$8t#nC?q8xk|<-CSTk2^6H}3x89kSqBR}X9%omFLK(EYj?tZ!?Y5p~? zvIlisC+Va5^}~=qEv}t;V4rgDj*TXY1@=;`v2w@#NQO@KHjbotz8l^unYX=_Q?D&X zn{h1Zwti1Dy-sghb=M!$19{jHjXjNb4DWgBj$JWjv(b!UK%FOmI(y4@f>0ehC%C>B z8Pf(C02=2!Fe&?i!VRJy4dRECG;GF$xSu>Et?fKv{PK=PU&VEHwv)I=1sh(z^7ZwY zal3m+N6B$6u*4FZyQ3pGgVqXviVt<|uvK_o}H5OWl zdOl4`)_%Ulk;J^-oaAMKs7}?6W%6iMzB^=tE$W|$`E%!0)XsP_7AG>A%@z<-eDWjV z@f305rV-T7?bPB_PtentwUuZiFvZTX1`Jl7LMakK&m?W$m~VcCJ&M6^X=uliB(`{@ z?&^h+>t!SQA<@bHaZ}txT z>8|`Wp?xD|L*grAM3+@RT2O@_l6Fc#m*}-Fs>6^WWY+n3S`hr6C_cttCH1o1RTe<9 zBfwfg`LEr|)`s==5D*z74|zWsyviFq-{PNvl^B5KU_opl_K`*AgCR{6`MHv;7DrLm zDle1C92&Me+F?m^k%OcbLl+sMPpRf>lTt^#6Atz=DY1^SDj{MwqRM`(9~^s_I-|2i z^D7RYK3!Ao^y9J-`QT1NB6&yYFo7EsXB7fZ7coTPZD3uzuQFeEHaUQ?T`#dDkd( zcVun8OXs<)Bs(;pJZ|=<)g4w0EEYg6qU%oK>d#5*%symxwvOy(XD3x?Q~R}7jJ3jn z1E?H|%U{INjZzp zP6-p7Xa2m4Sld%Vi2L;Pe)rlNXp~^!1M&NNo0LE&5}>ge);QY-hL1i6;uon1k=hI8 z7SO4fhQf;KZ{Ce2LibyV$YQ1nUe3RY-@6JXk{~h=?$& z)69dEVJej?z`JVuNgo1c7-}e}LJF8+7%=($`+?%;lLE;@3~ZqD5TN_o1p-Fswt*Re zK@{O?LalPIoBC)*k?M^DxMR=XbiWD9VA!Y($%@N3)Ke$D@@;T{&rNAIUF5!V-B$&t zx829J`A=1vNiu2Hm^28>m{i--(TckFGulkillfspx5=3(-Y;U72~;m?p+iAu5^{Nh zYih2fhrWR&8$UE|LH<&N%lzcT;6!#Ocd0e%l^^-KEl-x#cqgK)DN}VCO^u87U z7KO0p@&RoF91W6>9*4EcB_{H0%$;AUGtl9m=wU(Y)odflWR@@<9J}MqL_0I~a;8;{ zImCeb^_2p_!8m1?z%yf{+wIT@%m%$@?(&xbmgWnz#lL8W%`=Z6H@X>Q1DtrIIGS~4 z$fOC)Q>pPTdemDYSr=KEUY_Uk11X1i`LXS!f8bS}zzruQiJOVEOfEHMp|G5J^Iq*r zSqpPKW+=CCzMK`c^`|%J9>IKfF`ML6jyiR`mO_5WBB z$uml6t@#deIJQq_#;GVqpaC|BC-{4NLk1bR5oXCS46<8>L`p*5LGSH-J?JNY+P#>8 zwY8yvkrmYRaay;+t|70UKcLyRa|WNK(Ptb6W_Nae(@pfI{-!+FY_*-(pBkUz8zl(W zQM+$Py(}$$EINerai9jVY!k|rcd8+CnwaaP{KGpV<%_M{>LAdN_nxxAXKz00rxviB z9^&rMV*)>O+>Y$>^?u3U-uhoq09hX<|A1zqV5 z4kLY4^{R?s9OJ0}l8pU_Vs)=5v-`0Z-z@1k#;$XD`|AZAV1!JMBj z?^*~9BTTk$zDiBY${kX6<`~MC{tx(GbO$^~1&>{49OBBRJo&16;RT16ff~D=3?*B# zKdQECv)3F|(XpzWnza@VR-ez*0#qepjKy~0m*lWGWAq>Ko^VOe?Wvuq5fN)d?-;dj zu8~aVpWbK*LBlSRd@9^BeRt^-cRTwv$-o1Iv4b4XRy*_)-Ypm2oj(5&x$|~rg(!m+ zhGObv;*Olm`oi=pD3|Ke*~*`!P&Q)wxwaJB3d&5CyDVp2PsctNZ#=JIT)Hwzlz5;I zzju)NZdMZI^2CR)`t8S`Vf!ApkhvLug?`j8PcD)3{ZX^@_yB@J)$h0u@OTdQ(bcbH zS|$Ei3DjLLJ7O*$tUY4yFJsd+{;J#FMYfQl8|V6r1_35x@n>-x6#iN6c1y2}&$c~k z@ltUsY!{d+TJklTEf+F^jBan3r92S*e#N<*<=AYklGxFh3MUAvCHJ)1L z7H#-+2?q%9Ws*OfmS0~VjK`Obe|bmoHG1ka`3kMR-Q?R1igxvD14Xn}&s$b<<1Axd znTg?b#8WY+XK*4b=H?GK&@h!*SzJLme7YJvNwRcywxD^2YrW?pJUU9YT+Sko$SAkaR3QN<7a;;ZL$?ChgI^q$e4M3-y980E+){1jUf%Y8(xZaciT4jfuSVbuhfrbJ_ z(#TY0{kJzu?M;@8=T1+zMm{|pJ}6-A6Yu?Pm$TNKd-sl@YEavuUFTM{l8u4i$^kxm zpA`au%g_o_7l7gf+vVYa$jz;+i#8DjkPs8pZ*s#JkCBcV9g8aF8J@N@D4K=eN$w9Avhfx6VIq4l9UA$GtT$f2E`6v%BZz0MkYzp`*u> zj}ZO18z-L7^)Q<5W_&YYWRGLr8WYpdO)le==cn?*8-%$zR!J@V>&IF=aywhxlyW20 z9u*=xTn~14%)idmzYkmrm4M%)TS0hKZZR>m}XS_4)wYYM$*k(Y~9mD@j8B*iRa#+ z2oTV{iFYKta0PARghX!qV&_t>z%!|i{9du-IVpUalqa1TS!MC3-;j7ESX0Ss#IM|s zdwqM=(>$XkE`yYk605sBovDl+*;NwjyJeIGKM7WzyycE3Vnh-M)|yd3@YpzO1! zTqDtyNd@_tW|pUJ)qGJ^7zpt4=0LuE_4LCq1M{}|gGUw!c%)*2Xolp^OWsh+=48LZ zeG2IL0 z?jvL(i1ncF))^aOAU`7F(|yOrgH$Cl%O>tMjrh6aCwOQDZlQ@QG>HqJw~&{xj1Z4( z6KC>zudYw56zYangcc#&*4bKne4|o6KjbE#ow zMBXM?5_!VoF2UlAYh#R^1_f*(hLtjFGr^ZDRg&g2!mLE}tPDlQ|w#Cw+ojgpoAlv#DgW%%q zXrb8;;rfhL{m@X9$In=)gKs3_Gg3(xX4lkK6X2l*(z#HXGE!W=l^dXaZCtYKM)V1y^H@{LG(yVjdYpso1?>#G_&j+nto$zw`(8DOS^sK@*3(Idk2w$W1VWigK3po02tdvtyv;M_=j$3r+6>1bn zl~|NQ?z6oX7{6(eWK4N5Pv54oH|< zMG*qUdf#sg%}l(z>3Z+`t=ErmT$BZm<%_ZNzFu@bU-yG0Yj|J-XR-nHR*2bJWzGOS^8cEEakd2o>$=m0yaOz>J z_uQ0^;MY@M*hdDeFZ$*Ax;eR-nhZ}AJU!>ctfiByJw*EP<;z@}YT4XxkpefWjt&~} zis$?wM+-n!%&8#N)bu)nGCOvBMBn={YKq%QE#7Blu_SGz1{D#Z0@*`Eb-{%!uaAs- zcv7*9`4v)m*`(N!k%|k-CM&Kh6jIYKiM@0f!^WY=;)xa|>aJE!W8L0&?#|ODl|s-qWAEL}D*@$;h#<#$W5Uv2vp7T}keIdIQtCG^$kbJGoCE z$ustZlFi{JNMT(}f+_*j6*GPTx;nmq2Dh=gW~T!Rd+G70&{nmW?^ zMvT`+-E-#-QOq2SCQCzq@g)&YjO^Z58cNaJs#Tp^OgD~LPvv)j1B;HXfAc7D9bIGmoS^1FX0y(A;`PX^!BY+9tW-mbpc{$8vmrgj z!>co<)Lz(l#A3ZEhG9OZAANqOJaUKv>xB@7Lo@MImyuOdHr^r1Bg9kD?W@^i0xHzD zxOF-M#;+yDBflmYC|~`WUd7Ih-R`SHBfX(R&5GSB$`GEqAQ_tnCm?0Di8oq3h}%4SA=2 zPJDq|&kP&s`7)KR@qS8w>HMJYrRGCgHzv9yGHOT0nvg?;NX%C3U*`r6iu9dTUma~v z`ig%G)pfP9^xKVNRnkK^$ySBMKus%FUY=u>5|pLJeaX~+U5h#kMT}a z(XryzeCI?l^$Bya_BWx{!EMb*f|)KV39K@TL!oELo@dsLswx!p9DUG0B=afVQHm!k zQ1lcq{Rz>Yjrx$` za@~?Yg)*krKd#i1Ffi}w=);quTSfPVZASH8J5T@2jZ)i~e%b5z;WI#dJaF|LdrlQX?CzvRxDgu~sO(ER=5 zL>F1rRXe-Jt}kPbXk4ZCJ{*;at@K=~o?nqhKU`RIVXq`XiZxY3r;Ic~Lsfm8nuxw& z8cKdB_z^WvU=owDM0bk`-Hk)C>wYCCB=p-KWsg?zq9i&Ur|a#+qSX8B?vQQ}8~t)M zIW$for6BHJzF{P(&0H4_OQ%cr)3dc-yG~Gt_IW-zf=Bc4soID7%%gh!T|YX`NtaB1 zmNZBu_>|k++G&wZSUSI4e9J{JWK|1zhu2!iWxzVYU-0;ZdvF>?TR}8kx$rYO!sloy z=rJbg=x8>CFY-_;OZev`t|xHQtqL1g5!}CBo6Wkw&#*RC&^j&s-Sv*K2Oj0E7Z=VB zaunfVuG^58Sn+!i4Sbgkb`7J`u~5VOw3Jy;nHl)rd3-=S=m7m&@66?I&VkI6+B{T&wL4IJCe=c^_*jxcrjrY$j|lab$Ei66smJuDmBd^fxV zt-MvHj<$hL>SGH7vpfHjZdr1)!<^?%bnzI_9+?p)q7oQ8uB(3~YAm;jRtb+#`0VBT zT=kqQlj3$OrFZCMBG^0c`9H^HjTtmAWUt_+Rjw|4Ue_!#sZBekjN1C-M|?6l^T6$J z>HDL7Ys}1IlN*Yb?1N)N%~S8Q>6Nwh9?tQV_Afa5(&bL|rw~of+9j13_2DXt-1b~D zS}BtnJ^vC?xx@0QxtHjp&nB5a@^m){nY^bA z#JzsO7{hMG(y4}__xw^>f3=-FU;ly-Sz;-rtiphU+}i4M&KCu`x$>B}Q%R!SIUJ1q z6Df}aFHc8BYWr3%J;I;+ntMkh-K4Gf)%15KnfHmK2aR7FCwvEvDj}m#A|c*9Btkj? zEaD*l;BQ`jvwen%L*m#q(8nuQC_10~%j0d{YiG3dDY$!m-1S~NDB$khv_ggqk+hzi9D0SZ z?AC;=aQ9{h9(Knan9s4Wvv8b&Jm-$ToG~;puyukXfZY-H^sw){5pcs1`0azh6iCft z=jgEql5I0%a}Ihd3AC^r@R8agp#r|W{tFUFC25IGD(Gknwh)*#>Rd8}b^v&dw#iPd zV#R3#9FSvhYH(zMhxgt}FpZ=vOdJgyjm#~K46Ofzl1$oYSIVc;Ss{>@|4P6|9*$Cx zVE>fTwieE?#7bLLxe;PJ7>5a5PDn#3E~ZIi)>!u?F_Nkk2QhGQwgdJIH;)|uHI@P#R@cA6+B&2M zHgT9tdy!28j!)?5zS|F8wkrFb1C4h--Rt4h?KE^i=%1}iNh5I`Z8#H{g*L}H|ZnC$jM%0 z7%=R*TbG6(=W|K+tlO3qf?IHR>qG42gtsJvv>@`a{gdq0AByUB?+?M<*Ww_NF&)6i zWQ&Lj_;%?Z5P@3n=gX0wec)$um0y|^))f@IOc8X2&29{_J-YRS7BK)MB+0QX9Jbl< zJqx*QZ2%nfn8x(JAK96Ox9V*tF}epBe5k374Qw4Y(J8K za1hSSU`-{i>ICIeW!}8*_cSJY{0`+dc?E3+1!^>I|So0w4HUtsv!JqKUf#C)AZ( z46QAk%zvKK23sABkU-IVS3+Je+t`RQn;62Ws~jn3WN@6YHs;MSzuQf z8`!>O)g4rrq$9Py0(X`L=3|muEPxJ3Hh;w;Vr^mIB=Iu_3Jiwy?LB?(@m^#Y80@+U zhIpvv*aCxy)Jn5wkxXn|5zhMmYji%~c2rLWxFuw5E3*Yb1$-l?-4}$ot*eEjovjVf zy~GUcx6S~-3G{QHikQRdw`RQaun+tw_iFEKK)F^h!>d_c=>FAz>670e#EK8RoG3rE zbKis#*c1HoRU@pltF*1Xi!*Qt%*4RvKRS7*m^A`*w$~Z9r%aaXfVhcSBFI5B*ajt} z<>DFkLk0x*?e29rO>B1sf-n6G1}bL#8z4|2U~mh=9_TZCmgKe!-kK+4rm& zG`{=y<$e+IrPEj^E-~u>EtWZLbAi<`&#|GC;WEz8Vj{s_t)#-hydiKGt{s&zn;ePC^ZyzoPwF zmKMykd$VpU2w%JwtYFDMVcM;fEtnUkp+uhxf-bSm3tvDM%saySFEmu$wVW&>V&r;P z1jL9t4*b%rjc+f+=0Iv(gkx6J$Ib0HSd;HI9pZ#DyW{+O2Kad{1S|yXzKA)tn-0F- zEV$`x_sy|+xC4?66n0YOx6amN`Z9uc+Qx$~{0hbk64O z;Z!hBH;`)J&wU%zZXi71GOCDx!V=tf1%j`M3I+-X(qilb2vi{z5s!+t$F4l^Wlh06 z7lil41N41ZkyAuGSXQ39^1v691oJqE?V0E2p7wv(P_LTTu1N5u5y42DK$V(3HW{pk z{RdOTRc{1Lf8r07fPJ+;XM$E01h>zC!rxkuq!OI2s~=;gxKC}fh;&p_6Gs+!?2=hV4$!CIw;VcM-0%b z8w&;W3T`9F>nZrsYT!1i{d*wD_^?Z%H83yiHHaz6rjRiH-2{Y>zIYFu=iEmtLTaCZ zL8@4ophCf}uom4Z9DIc|uyALAJ-#t2`L4QyEC zO8aW3pAvzv;LC{sRZS4qX9F7<%*h@Z@<-rgx8dOnse$2XmH#okDuR-7h>+jS2keRr zUl9$Aj02p||MQN#)h~Y4`%h70`T}=Fg)epnMjiOqsIYg!yX-Tm)dxXoVTKL%kdRGk z_^M@K+FI3rEbKNdxP}=bYdHKU7y&e52ethc8!|xGtNkNr$RSoR8caW!zk+?=sV97K zGO(Vf)&C(H%sIfvzo>vqm4WGDp5b*u$R7)<0=Hja2;`IaC*2Gp>{bOYFe6OS|56yf zt{7P1&i@M|WPSwm4Zq!C(2EWIV`0_dgq;Q=dHnETK^|Zm?dv|hd-&B1%5D?VIT1hL!kfmkOyDM3jAR4KsfsUMSs|XLF{MS_5Id@ zl=ZLf`ULO=q`*%wbn*ZE1iO@zLev(74w1V)34E<4@RJDX{-2)Yk1IDJdY%{B*LHm# z_)16M=jqV(;0uk4BpUx^5eJa*}CkayH2LWHKU;s-^8EdX{~0l}{w zOZE4K_Pde%zkDHl6(X=k*arIs`Hz}Jhy-(wjoP&>;cE+lIS2R82^;Ws7EM9~$n71C z01)wPjUR68OETQIkkI_fU>ewvhPYV~(`*OvLFSH2e@O$Y5QKX7hsnR<~ud9MGK3bMk3 zFWdvBN;lmn6}XHK7zrkn5N#X+q|H9KbAuKBy=a2jUXXrq0!2rjK=%bXR4Qa?E2p)G|-s9#G~fRV;*_KdW%fCU)HPtN#NFQ8GrbrDn}*cGhNuNnElKsOxr z474*pKOB(6%qj#3W=gQ@CJ^HBgavMNBXrse(9UVfSiK9>bk8(zjvVkUg!umOJI4BZ zzCScOEm#EDO?QL%H5opvRUm_E!TumX`=DT+19zsO)&aZ1_`p{3YXW^34;8?J>5l9@0>YLq8A<-mKXG#Wca(yn^w5%^MHKD|Y!?Ja86yIJVdx&x5ub^7 z!mfPqNzcK2J??+Y_j3}rmAe-apOjGIZ~1=BE)M3y_WVbDzq3KV+r5$R0iQbIn)F*l zNE5^7*ajos^ZHka+ZneJi5UIp=5Gmq&5aEvyzc#v2%(YGzs%G9E+4)Nkx)MI)^7oS zO&SdbB=z}wz#S>1!3z4NkR?NWV0;_AtKMzggkMOA`0f=DL1fzQ!{_$KAbgHzuugIN z#rWAt;W9ph6@tyZ-dUymwh1A<06vQ|7){=9p9*co@xbMDMg*7Zow_R=e2!)?9Jc@d z;SgqQMuaC4d3#qp_~gi7JobRUz=KPbjED;}H4VxIGr!%e9zGz`3OF|X=ljr**&?vn z|I2w^*nMFWHrV%_qda`RTChIP_RVqOx7lkEDV6y>{g0J`KV=^ivR9?H)5d{GVA}kb zR`5A%!CFoJ1<9`&Y{6_WA7MO^fk35g_XVF$56m`oZT~85vHf&qzl=gKykhU{9e4-P UfJruz5Ad%hka^zwI^^5`0C;tDe*gdg literal 0 HcmV?d00001 diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/boot/SpringBootConfiguration.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/boot/SpringBootConfiguration.java new file mode 100644 index 000000000000..483c5cc56065 --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/boot/SpringBootConfiguration.java @@ -0,0 +1,10 @@ +package org.springframework.boot; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Configuration; + +@Target(ElementType.TYPE) +@Configuration +public @interface SpringBootConfiguration {} \ No newline at end of file diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/boot/autoconfigure/SpringBootApplication.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/boot/autoconfigure/SpringBootApplication.java new file mode 100644 index 000000000000..38bbc35733e9 --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/boot/autoconfigure/SpringBootApplication.java @@ -0,0 +1,12 @@ +package org.springframework.boot.autoconfigure; + +import java.lang.annotation.Target; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; + +import org.springframework.boot.SpringBootConfiguration; + +@Target(ElementType.TYPE) +@Inherited +@SpringBootConfiguration +public @interface SpringBootApplication {} \ No newline at end of file diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/context/annotation/Bean.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/context/annotation/Bean.java new file mode 100644 index 000000000000..24d7be0f8174 --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/context/annotation/Bean.java @@ -0,0 +1,10 @@ +package org.springframework.context.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +public @interface Bean { + + String[] name() default {}; +} \ No newline at end of file diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/context/annotation/Configuration.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/context/annotation/Configuration.java new file mode 100644 index 000000000000..0024b3ba8338 --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/context/annotation/Configuration.java @@ -0,0 +1,7 @@ +package org.springframework.context.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +public @interface Configuration {} \ No newline at end of file diff --git a/java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/annotation/AliasFor.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/core/annotation/AliasFor.java similarity index 100% rename from java/ql/test/stubs/spring-core-5.3.2/org/springframework/core/annotation/AliasFor.java rename to java/ql/test/stubs/springframework-5.2.3/org/springframework/core/annotation/AliasFor.java diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/remoting/httpinvoker/HttpInvokerServiceExporter.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/remoting/httpinvoker/HttpInvokerServiceExporter.java new file mode 100644 index 000000000000..5eb56c0897b2 --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/remoting/httpinvoker/HttpInvokerServiceExporter.java @@ -0,0 +1,8 @@ +package org.springframework.remoting.httpinvoker; + +public class HttpInvokerServiceExporter extends org.springframework.remoting.rmi.RemoteInvocationSerializingExporter { + + public void setService(Object service) {} + + public void setServiceInterface(Class clazz) {} +} \ No newline at end of file diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/remoting/rmi/RemoteInvocationSerializingExporter.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/remoting/rmi/RemoteInvocationSerializingExporter.java new file mode 100644 index 000000000000..5449b83ba865 --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/remoting/rmi/RemoteInvocationSerializingExporter.java @@ -0,0 +1,3 @@ +package org.springframework.remoting.rmi; + +public abstract class RemoteInvocationSerializingExporter {} diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/stereotype/Controller.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/stereotype/Controller.java index 0bfb67d99c18..a4a49fffb90a 100644 --- a/java/ql/test/stubs/springframework-5.2.3/org/springframework/stereotype/Controller.java +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/stereotype/Controller.java @@ -1,9 +1,15 @@ package org.springframework.stereotype; -import java.lang.annotation.*; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; -@Target(value=ElementType.TYPE) -@Retention(value=RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) @Documented @Component -public @interface Controller { } +public @interface Controller { + String value() default ""; +} diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/util/ObjectUtils.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/util/ObjectUtils.java new file mode 100644 index 000000000000..a5a51786d209 --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/util/ObjectUtils.java @@ -0,0 +1,202 @@ +package org.springframework.util; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.Optional; +import java.util.StringJoiner; +import org.springframework.lang.Nullable; + +public abstract class ObjectUtils { + + private static final int INITIAL_HASH = 7; + private static final int MULTIPLIER = 31; + private static final String EMPTY_STRING = ""; + private static final String NULL_STRING = "null"; + private static final String ARRAY_START = "{"; + private static final String ARRAY_END = "}"; + private static final String EMPTY_ARRAY = "{}"; + private static final String ARRAY_ELEMENT_SEPARATOR = ", "; + private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; + + public ObjectUtils() { + } + + public static boolean isCheckedException(Throwable ex) { + return false; + } + + public static boolean isCompatibleWithThrowsClause(Throwable ex, @Nullable Class... declaredExceptions) { + return false; + } + + public static boolean isArray(@Nullable Object obj) { + return false; + } + + public static boolean isEmpty(@Nullable Object[] array) { + return false; + } + + public static boolean isEmpty(@Nullable Object obj) { + return false; + } + + @Nullable + public static Object unwrapOptional(@Nullable Object obj) { + return null; + } + + public static boolean containsElement(@Nullable Object[] array, Object element) { + return true; + } + + public static boolean containsConstant(Enum[] enumValues, String constant) { + return true; + } + + public static boolean containsConstant(Enum[] enumValues, String constant, boolean caseSensitive) { + return true; + } + + public static > E caseInsensitiveValueOf(E[] enumValues, String constant) { + return null; + } + + public static A[] addObjectToArray(@Nullable A[] array, @Nullable O obj) { + return null; + } + + public static Object[] toObjectArray(@Nullable Object source) { + return null; + } + + public static boolean nullSafeEquals(@Nullable Object o1, @Nullable Object o2) { + return false; + } + + private static boolean arrayEquals(Object o1, Object o2) { + return false; + } + + public static int nullSafeHashCode(@Nullable Object obj) { + return 1; + } + + public static int nullSafeHashCode(@Nullable Object[] array) { + return 1; + } + + public static int nullSafeHashCode(@Nullable boolean[] array) { + return 1; + } + + public static int nullSafeHashCode(@Nullable byte[] array) { + return 1; + } + + public static int nullSafeHashCode(@Nullable char[] array) { + return 1; + } + + public static int nullSafeHashCode(@Nullable double[] array) { + return 1; + } + + public static int nullSafeHashCode(@Nullable float[] array) { + return 1; + } + + public static int nullSafeHashCode(@Nullable int[] array) { + return 1; + } + + public static int nullSafeHashCode(@Nullable long[] array) { + return 1; + } + + public static int nullSafeHashCode(@Nullable short[] array) { + return 1; + } + + /** @deprecated */ + @Deprecated + public static int hashCode(boolean bool) { + return 1; + } + + /** @deprecated */ + @Deprecated + public static int hashCode(double dbl) { + return 1; + } + + /** @deprecated */ + @Deprecated + public static int hashCode(float flt) { + return 1; + } + + /** @deprecated */ + @Deprecated + public static int hashCode(long lng) { + return 1; + } + + public static String identityToString(@Nullable Object obj) { + return ""; + } + + public static String getIdentityHexString(Object obj) { + return ""; + } + + public static String getDisplayString(@Nullable Object obj) { + return ""; + } + + public static String nullSafeClassName(@Nullable Object obj) { + return ""; + } + + public static String nullSafeToString(@Nullable Object obj) { + return ""; + } + + public static String nullSafeToString(@Nullable Object[] array) { + return ""; + } + + public static String nullSafeToString(@Nullable boolean[] array) { + return ""; + } + + public static String nullSafeToString(@Nullable byte[] array) { + return ""; + } + + public static String nullSafeToString(@Nullable char[] array) { + return ""; + } + + public static String nullSafeToString(@Nullable double[] array) { + return ""; + } + + public static String nullSafeToString(@Nullable float[] array) { + return ""; + } + + public static String nullSafeToString(@Nullable int[] array) { + return ""; + } + + public static String nullSafeToString(@Nullable long[] array) { + return ""; + } + + public static String nullSafeToString(@Nullable short[] array) { + return ""; + } +} diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/util/StringUtils.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/util/StringUtils.java new file mode 100644 index 000000000000..a78d47e44c6a --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/util/StringUtils.java @@ -0,0 +1,30 @@ +package org.springframework.util; + +import java.io.ByteArrayOutputStream; +import java.nio.charset.Charset; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Deque; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Properties; +import java.util.Set; +import java.util.StringJoiner; +import java.util.StringTokenizer; +import java.util.TimeZone; +import org.springframework.lang.Nullable; + +public abstract class StringUtils { + + @Deprecated + public static boolean isEmpty(@Nullable Object str) { + return true; + } + +} diff --git a/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/GetMapping.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/GetMapping.java similarity index 100% rename from java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/GetMapping.java rename to java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/GetMapping.java diff --git a/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/Mapping.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/Mapping.java similarity index 100% rename from java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/Mapping.java rename to java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/Mapping.java diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestMapping.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestMapping.java index 73ff8d1b03af..093065ca5f16 100644 --- a/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestMapping.java +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestMapping.java @@ -1,11 +1,32 @@ package org.springframework.web.bind.annotation; -import java.lang.annotation.*; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.core.annotation.AliasFor; @Target(value={ElementType.METHOD,ElementType.TYPE}) @Retention(value=RetentionPolicy.RUNTIME) @Documented +@Mapping public @interface RequestMapping { + String name() default ""; + + @AliasFor("path") + String[] value() default {}; + + @AliasFor("value") + String[] path() default {}; RequestMethod[] method() default {}; + + String[] params() default {}; + + String[] headers() default {}; + + String[] consumes() default {}; + + String[] produces() default {}; } diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestParam.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestParam.java index 5ae52ad123fa..56094811c376 100644 --- a/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestParam.java +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestParam.java @@ -1,8 +1,23 @@ package org.springframework.web.bind.annotation; -import java.lang.annotation.*; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.core.annotation.AliasFor; -@Target(value=ElementType.PARAMETER) -@Retention(value=RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) @Documented -public @interface RequestParam { } +public @interface RequestParam { + @AliasFor("name") + String value() default ""; + + @AliasFor("value") + String name() default ""; + + boolean required() default true; + + String defaultValue() default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n"; +} diff --git a/java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/ResponseBody.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/ResponseBody.java similarity index 100% rename from java/ql/test/stubs/spring-web-5.3.2/org/springframework/web/bind/annotation/ResponseBody.java rename to java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/ResponseBody.java From bd3b3178ba2a914567d831518490967676bfb0b0 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Thu, 15 Apr 2021 09:16:51 +0100 Subject: [PATCH 272/433] Fix documentation of Modifier.qll --- java/ql/src/semmle/code/java/Modifier.qll | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/java/ql/src/semmle/code/java/Modifier.qll b/java/ql/src/semmle/code/java/Modifier.qll index 7fb9f9826163..39cbe5a3c294 100755 --- a/java/ql/src/semmle/code/java/Modifier.qll +++ b/java/ql/src/semmle/code/java/Modifier.qll @@ -18,7 +18,9 @@ abstract class Modifiable extends Element { * Holds if this element has modifier `m`. * * For most purposes, the more specialized predicates `isAbstract`, `isPublic`, etc. - * should be used, which also take implicit modifiers into account. + * should be used. + * + * Both this method and those specialized predicates take implicit modifiers into account. * For instance, non-default instance methods in interfaces are implicitly * abstract, so `isAbstract()` will hold for them even if `hasModifier("abstract")` * does not. From b4a2a9db255d64b1aa1da203e78326768b1153d5 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Thu, 15 Apr 2021 09:23:45 +0100 Subject: [PATCH 273/433] JS: Fix extraction of non-substitution template literal types --- .../ts/extractor/TypeScriptASTConverter.java | 15 +++++++++------ .../TemplateTypes/TemplateTypes.expected | 6 ++++++ .../TemplateTypes/TemplateTypes.ql | 3 +++ .../RegressionTests/TemplateTypes/tsconfig.json | 3 +++ .../RegressionTests/TemplateTypes/tst.ts | 7 +++++++ 5 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 javascript/ql/test/library-tests/TypeScript/RegressionTests/TemplateTypes/TemplateTypes.expected create mode 100644 javascript/ql/test/library-tests/TypeScript/RegressionTests/TemplateTypes/TemplateTypes.ql create mode 100644 javascript/ql/test/library-tests/TypeScript/RegressionTests/TemplateTypes/tsconfig.json create mode 100644 javascript/ql/test/library-tests/TypeScript/RegressionTests/TemplateTypes/tst.ts diff --git a/javascript/extractor/src/com/semmle/ts/extractor/TypeScriptASTConverter.java b/javascript/extractor/src/com/semmle/ts/extractor/TypeScriptASTConverter.java index 36d4f8886187..f8ad99915c90 100644 --- a/javascript/extractor/src/com/semmle/ts/extractor/TypeScriptASTConverter.java +++ b/javascript/extractor/src/com/semmle/ts/extractor/TypeScriptASTConverter.java @@ -5,7 +5,6 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; import com.google.gson.JsonArray; import com.google.gson.JsonElement; @@ -1001,10 +1000,10 @@ private Node convertConditionalExpression(JsonObject node, SourceLocation loc) t private Node convertConditionalType(JsonObject node, SourceLocation loc) throws ParseError { return new ConditionalTypeExpr( loc, - convertChild(node, "checkType"), - convertChild(node, "extendsType"), - convertChild(node, "trueType"), - convertChild(node, "falseType")); + convertChildAsType(node, "checkType"), + convertChildAsType(node, "extendsType"), + convertChildAsType(node, "trueType"), + convertChildAsType(node, "falseType")); } private SourceLocation getSourceRange(Position from, Position to) { @@ -1613,6 +1612,10 @@ private Node convertLiteralType(JsonObject node, SourceLocation loc) throws Pars literal = new Literal(loc, arg.getTokenType(), "-" + arg.getValue()); } } + if (literal instanceof TemplateLiteral) { + // A LiteralType containing a NoSubstitutionTemplateLiteral must produce a TemplateLiteralTypeExpr + return new TemplateLiteralTypeExpr(literal.getLoc(), new ArrayList<>(), ((TemplateLiteral)literal).getQuasis()); + } return literal; } @@ -1842,7 +1845,7 @@ private Node convertOmittedExpression() { } private Node convertOptionalType(JsonObject node, SourceLocation loc) throws ParseError { - return new OptionalTypeExpr(loc, convertChild(node, "type")); + return new OptionalTypeExpr(loc, convertChildAsType(node, "type")); } private ITypeExpression asType(Node node) { diff --git a/javascript/ql/test/library-tests/TypeScript/RegressionTests/TemplateTypes/TemplateTypes.expected b/javascript/ql/test/library-tests/TypeScript/RegressionTests/TemplateTypes/TemplateTypes.expected new file mode 100644 index 000000000000..e713b3e48049 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/RegressionTests/TemplateTypes/TemplateTypes.expected @@ -0,0 +1,6 @@ +| tst.ts:2:11:2:21 | `foo ${T1}` | +| tst.ts:4:45:4:49 | `foo` | +| tst.ts:4:53:4:57 | `bar` | +| tst.ts:5:46:5:50 | `foo` | +| tst.ts:5:54:5:63 | `bar ${K}` | +| tst.ts:7:15:7:19 | `foo` | diff --git a/javascript/ql/test/library-tests/TypeScript/RegressionTests/TemplateTypes/TemplateTypes.ql b/javascript/ql/test/library-tests/TypeScript/RegressionTests/TemplateTypes/TemplateTypes.ql new file mode 100644 index 000000000000..f08f5a201dda --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/RegressionTests/TemplateTypes/TemplateTypes.ql @@ -0,0 +1,3 @@ +import javascript + +query TemplateLiteralTypeExpr literalType() { any() } diff --git a/javascript/ql/test/library-tests/TypeScript/RegressionTests/TemplateTypes/tsconfig.json b/javascript/ql/test/library-tests/TypeScript/RegressionTests/TemplateTypes/tsconfig.json new file mode 100644 index 000000000000..82194fc7ab06 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/RegressionTests/TemplateTypes/tsconfig.json @@ -0,0 +1,3 @@ +{ + "include": ["."] +} diff --git a/javascript/ql/test/library-tests/TypeScript/RegressionTests/TemplateTypes/tst.ts b/javascript/ql/test/library-tests/TypeScript/RegressionTests/TemplateTypes/tst.ts new file mode 100644 index 000000000000..21718ea3b340 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/RegressionTests/TemplateTypes/tst.ts @@ -0,0 +1,7 @@ +type T1 = 'foo' | 'bar'; +type T2 = `foo ${T1}`; + +type FooToBar = K extends `foo` ? `bar` : K; +type FooToBar2 = K extends `foo` ? `bar ${K}` : K; + +type Tuple = [`foo`?]; From cb736c8c82b0a8f91e18e9dfd2b3a16f3b5194a2 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Thu, 15 Apr 2021 09:37:57 +0100 Subject: [PATCH 274/433] JS: Change note --- .../2021-04-15-typescript-template-literal-type-crash.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 javascript/change-notes/2021-04-15-typescript-template-literal-type-crash.md diff --git a/javascript/change-notes/2021-04-15-typescript-template-literal-type-crash.md b/javascript/change-notes/2021-04-15-typescript-template-literal-type-crash.md new file mode 100644 index 000000000000..e08dbd54dd69 --- /dev/null +++ b/javascript/change-notes/2021-04-15-typescript-template-literal-type-crash.md @@ -0,0 +1,3 @@ +lgtm,codescanning +* Fixed a bug that could cause extraction to fail when extracting a TypeScript + code base containing a template literal type without substitutions. From f8570bb293b257444413398aa073ee263203afd3 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Thu, 15 Apr 2021 10:16:46 +0100 Subject: [PATCH 275/433] JS: Update TRAP --- .../ts/output/trap/importNonStrings.ts.trap | 269 ++++++++++++++++-- 1 file changed, 242 insertions(+), 27 deletions(-) diff --git a/javascript/extractor/tests/ts/output/trap/importNonStrings.ts.trap b/javascript/extractor/tests/ts/output/trap/importNonStrings.ts.trap index c5fc95ddcb86..af044be98c89 100644 --- a/javascript/extractor/tests/ts/output/trap/importNonStrings.ts.trap +++ b/javascript/extractor/tests/ts/output/trap/importNonStrings.ts.trap @@ -9,34 +9,249 @@ hasLocation(#10000,#10002) #20000=@"global_scope" scopes(#20000,0) #20001=@"script;{#10000},1,1" -toplevels(#20001,0) -#20002=@"loc,{#10000},1,1,1,1" -locations_default(#20002,#10000,1,1,1,1) -hasLocation(#20001,#20002) -#20003=* -js_parse_errors(#20003,#20001,"Error: Unsupported syntax in import","type Y = import(`Foo`); -") -#20004=@"loc,{#10000},2,10,2,10" -locations_default(#20004,#10000,2,10,2,10) -hasLocation(#20003,#20004) -#20005=* -lines(#20005,#20001,"type X = import(3);"," +#20002=* +lines(#20002,#20001,"type X = import(3);"," ") -#20006=@"loc,{#10000},1,1,1,19" -locations_default(#20006,#10000,1,1,1,19) -hasLocation(#20005,#20006) -#20007=* -lines(#20007,#20001,"type Y = import(`Foo`);"," +#20003=@"loc,{#10000},1,1,1,19" +locations_default(#20003,#10000,1,1,1,19) +hasLocation(#20002,#20003) +#20004=* +lines(#20004,#20001,"type Y = import(`Foo`);"," ") -#20008=@"loc,{#10000},2,1,2,23" -locations_default(#20008,#10000,2,1,2,23) -hasLocation(#20007,#20008) -#20009=* -lines(#20009,#20001,"type Z = import(Y);"," +#20005=@"loc,{#10000},2,1,2,23" +locations_default(#20005,#10000,2,1,2,23) +hasLocation(#20004,#20005) +#20006=* +lines(#20006,#20001,"type Z = import(Y);"," ") -#20010=@"loc,{#10000},3,1,3,19" -locations_default(#20010,#10000,3,1,3,19) -hasLocation(#20009,#20010) -numlines(#20001,3,0,0) -numlines(#10000,3,0,0) +#20007=@"loc,{#10000},3,1,3,19" +locations_default(#20007,#10000,3,1,3,19) +hasLocation(#20006,#20007) +numlines(#20001,3,3,0) +#20008=* +tokeninfo(#20008,7,#20001,0,"type") +#20009=@"loc,{#10000},1,1,1,4" +locations_default(#20009,#10000,1,1,1,4) +hasLocation(#20008,#20009) +#20010=* +tokeninfo(#20010,6,#20001,1,"X") +#20011=@"loc,{#10000},1,6,1,6" +locations_default(#20011,#10000,1,6,1,6) +hasLocation(#20010,#20011) +#20012=* +tokeninfo(#20012,8,#20001,2,"=") +#20013=@"loc,{#10000},1,8,1,8" +locations_default(#20013,#10000,1,8,1,8) +hasLocation(#20012,#20013) +#20014=* +tokeninfo(#20014,7,#20001,3,"import") +#20015=@"loc,{#10000},1,10,1,15" +locations_default(#20015,#10000,1,10,1,15) +hasLocation(#20014,#20015) +#20016=* +tokeninfo(#20016,8,#20001,4,"(") +#20017=@"loc,{#10000},1,16,1,16" +locations_default(#20017,#10000,1,16,1,16) +hasLocation(#20016,#20017) +#20018=* +tokeninfo(#20018,3,#20001,5,"3") +#20019=@"loc,{#10000},1,17,1,17" +locations_default(#20019,#10000,1,17,1,17) +hasLocation(#20018,#20019) +#20020=* +tokeninfo(#20020,8,#20001,6,")") +#20021=@"loc,{#10000},1,18,1,18" +locations_default(#20021,#10000,1,18,1,18) +hasLocation(#20020,#20021) +#20022=* +tokeninfo(#20022,8,#20001,7,";") +#20023=@"loc,{#10000},1,19,1,19" +locations_default(#20023,#10000,1,19,1,19) +hasLocation(#20022,#20023) +#20024=* +tokeninfo(#20024,7,#20001,8,"type") +#20025=@"loc,{#10000},2,1,2,4" +locations_default(#20025,#10000,2,1,2,4) +hasLocation(#20024,#20025) +#20026=* +tokeninfo(#20026,6,#20001,9,"Y") +#20027=@"loc,{#10000},2,6,2,6" +locations_default(#20027,#10000,2,6,2,6) +hasLocation(#20026,#20027) +#20028=* +tokeninfo(#20028,8,#20001,10,"=") +#20029=@"loc,{#10000},2,8,2,8" +locations_default(#20029,#10000,2,8,2,8) +hasLocation(#20028,#20029) +#20030=* +tokeninfo(#20030,7,#20001,11,"import") +#20031=@"loc,{#10000},2,10,2,15" +locations_default(#20031,#10000,2,10,2,15) +hasLocation(#20030,#20031) +#20032=* +tokeninfo(#20032,8,#20001,12,"(") +#20033=@"loc,{#10000},2,16,2,16" +locations_default(#20033,#10000,2,16,2,16) +hasLocation(#20032,#20033) +#20034=* +tokeninfo(#20034,4,#20001,13,"`Foo`") +#20035=@"loc,{#10000},2,17,2,21" +locations_default(#20035,#10000,2,17,2,21) +hasLocation(#20034,#20035) +#20036=* +tokeninfo(#20036,8,#20001,14,")") +#20037=@"loc,{#10000},2,22,2,22" +locations_default(#20037,#10000,2,22,2,22) +hasLocation(#20036,#20037) +#20038=* +tokeninfo(#20038,8,#20001,15,";") +#20039=@"loc,{#10000},2,23,2,23" +locations_default(#20039,#10000,2,23,2,23) +hasLocation(#20038,#20039) +#20040=* +tokeninfo(#20040,7,#20001,16,"type") +#20041=@"loc,{#10000},3,1,3,4" +locations_default(#20041,#10000,3,1,3,4) +hasLocation(#20040,#20041) +#20042=* +tokeninfo(#20042,6,#20001,17,"Z") +#20043=@"loc,{#10000},3,6,3,6" +locations_default(#20043,#10000,3,6,3,6) +hasLocation(#20042,#20043) +#20044=* +tokeninfo(#20044,8,#20001,18,"=") +#20045=@"loc,{#10000},3,8,3,8" +locations_default(#20045,#10000,3,8,3,8) +hasLocation(#20044,#20045) +#20046=* +tokeninfo(#20046,7,#20001,19,"import") +#20047=@"loc,{#10000},3,10,3,15" +locations_default(#20047,#10000,3,10,3,15) +hasLocation(#20046,#20047) +#20048=* +tokeninfo(#20048,8,#20001,20,"(") +#20049=@"loc,{#10000},3,16,3,16" +locations_default(#20049,#10000,3,16,3,16) +hasLocation(#20048,#20049) +#20050=* +tokeninfo(#20050,6,#20001,21,"Y") +#20051=@"loc,{#10000},3,17,3,17" +locations_default(#20051,#10000,3,17,3,17) +hasLocation(#20050,#20051) +#20052=* +tokeninfo(#20052,8,#20001,22,")") +#20053=@"loc,{#10000},3,18,3,18" +locations_default(#20053,#10000,3,18,3,18) +hasLocation(#20052,#20053) +#20054=* +tokeninfo(#20054,8,#20001,23,";") +#20055=@"loc,{#10000},3,19,3,19" +locations_default(#20055,#10000,3,19,3,19) +hasLocation(#20054,#20055) +#20056=* +tokeninfo(#20056,0,#20001,24,"") +#20057=@"loc,{#10000},4,1,4,0" +locations_default(#20057,#10000,4,1,4,0) +hasLocation(#20056,#20057) +toplevels(#20001,0) +#20058=@"loc,{#10000},1,1,4,0" +locations_default(#20058,#10000,1,1,4,0) +hasLocation(#20001,#20058) +#20059=@"local_type_name;{X};{#20000}" +local_type_names(#20059,"X",#20000) +#20060=@"local_type_name;{Y};{#20000}" +local_type_names(#20060,"Y",#20000) +#20061=@"local_type_name;{Z};{#20000}" +local_type_names(#20061,"Z",#20000) +#20062=* +stmts(#20062,35,#20001,0,"type X = import(3);") +hasLocation(#20062,#20003) +stmt_containers(#20062,#20001) +#20063=* +typeexprs(#20063,1,#20062,0,"X") +hasLocation(#20063,#20011) +enclosing_stmt(#20063,#20062) +expr_containers(#20063,#20001) +literals("X","X",#20063) +typedecl(#20063,#20059) +#20064=* +typeexprs(#20064,30,#20062,1,"import(3)") +#20065=@"loc,{#10000},1,10,1,18" +locations_default(#20065,#10000,1,10,1,18) +hasLocation(#20064,#20065) +enclosing_stmt(#20064,#20062) +expr_containers(#20064,#20001) +#20066=* +typeexprs(#20066,4,#20064,0,"3") +hasLocation(#20066,#20019) +enclosing_stmt(#20066,#20062) +expr_containers(#20066,#20001) +literals("3","3",#20066) +#20067=* +stmts(#20067,35,#20001,1,"type Y ... `Foo`);") +hasLocation(#20067,#20005) +stmt_containers(#20067,#20001) +#20068=* +typeexprs(#20068,1,#20067,0,"Y") +hasLocation(#20068,#20027) +enclosing_stmt(#20068,#20067) +expr_containers(#20068,#20001) +literals("Y","Y",#20068) +typedecl(#20068,#20060) +#20069=* +typeexprs(#20069,30,#20067,1,"import(`Foo`)") +#20070=@"loc,{#10000},2,10,2,22" +locations_default(#20070,#10000,2,10,2,22) +hasLocation(#20069,#20070) +enclosing_stmt(#20069,#20067) +expr_containers(#20069,#20001) +#20071=* +typeexprs(#20071,37,#20069,0,"`Foo`") +hasLocation(#20071,#20035) +enclosing_stmt(#20071,#20067) +expr_containers(#20071,#20001) +#20072=* +typeexprs(#20072,3,#20071,0,"`Foo`") +hasLocation(#20072,#20035) +enclosing_stmt(#20072,#20067) +expr_containers(#20072,#20001) +literals("Foo","Foo",#20072) +#20073=* +stmts(#20073,35,#20001,2,"type Z = import(Y);") +hasLocation(#20073,#20007) +stmt_containers(#20073,#20001) +#20074=* +typeexprs(#20074,1,#20073,0,"Z") +hasLocation(#20074,#20043) +enclosing_stmt(#20074,#20073) +expr_containers(#20074,#20001) +literals("Z","Z",#20074) +typedecl(#20074,#20061) +#20075=* +typeexprs(#20075,30,#20073,1,"import(Y)") +#20076=@"loc,{#10000},3,10,3,18" +locations_default(#20076,#10000,3,10,3,18) +hasLocation(#20075,#20076) +enclosing_stmt(#20075,#20073) +expr_containers(#20075,#20001) +#20077=* +typeexprs(#20077,0,#20075,0,"Y") +hasLocation(#20077,#20051) +enclosing_stmt(#20077,#20073) +expr_containers(#20077,#20001) +literals("Y","Y",#20077) +typebind(#20077,#20060) +#20078=* +entry_cfg_node(#20078,#20001) +#20079=@"loc,{#10000},1,1,1,0" +locations_default(#20079,#10000,1,1,1,0) +hasLocation(#20078,#20079) +#20080=* +exit_cfg_node(#20080,#20001) +hasLocation(#20080,#20057) +successor(#20073,#20080) +successor(#20067,#20073) +successor(#20062,#20067) +successor(#20078,#20062) +numlines(#10000,3,3,0) filetype(#10000,"typescript") From 0f24db8759b822f8db969bf71f95e4c184e72c40 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Mon, 12 Apr 2021 14:41:28 +0200 Subject: [PATCH 276/433] C#: Improve performance of `SsaImpl::CallGraph::SimpleDelegateAnalysis` --- .../code/csharp/dataflow/internal/SsaImpl.qll | 152 ++++++++++++++---- 1 file changed, 121 insertions(+), 31 deletions(-) diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImpl.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImpl.qll index 41da2b8f0b5d..63d6c902fed2 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImpl.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImpl.qll @@ -268,56 +268,146 @@ private module CallGraph { ) } + /** + * A simple flow step that does not take flow through fields or flow out + * of callables into account. + */ + pragma[nomagic] private predicate delegateFlowStep(Expr pred, Expr succ) { Steps::stepClosed(pred, succ) or - exists(Call call, Callable callable | - callable.getUnboundDeclaration().canReturn(pred) and - call = succ - | - callable = call.getTarget() or - callable = call.getTarget().(Method).getAnOverrider+() or - callable = call.getTarget().(Method).getAnUltimateImplementor() or - callable = getARuntimeDelegateTarget(call, false) - ) - or pred = succ.(DelegateCreation).getArgument() or - exists(AssignableDefinition def, Assignable a | - a instanceof Field or - a instanceof Property - | - a = def.getTarget() and - succ.(AssignableRead) = a.getAnAccess() and - pred = def.getSource() - ) - or exists(AddEventExpr ae | succ.(EventAccess).getTarget() = ae.getTarget() | pred = ae.getRValue() ) } - private predicate reachesDelegateCall(Expr e) { - delegateCall(_, e, _) + private predicate delegateCreationReaches(Callable c, Expr e) { + delegateCreation(e, c, _) or - exists(Expr mid | reachesDelegateCall(mid) | delegateFlowStep(e, mid)) + exists(Expr mid | + delegateCreationReaches(c, mid) and + delegateFlowStep(mid, e) + ) } - pragma[nomagic] - private predicate delegateFlowStepReaches(Expr pred, Expr succ) { - delegateFlowStep(pred, succ) and - reachesDelegateCall(succ) + private predicate reachesDelegateCall(Expr e, Call c, boolean libraryDelegateCall) { + delegateCall(c, e, libraryDelegateCall) + or + exists(Expr mid | + reachesDelegateCall(mid, c, libraryDelegateCall) and + delegateFlowStep(e, mid) + ) } - private Expr delegateCallSource(Callable c) { - delegateCreation(result, c, _) - or - delegateFlowStepReaches(delegateCallSource(c), result) + /** + * A "busy" flow element, that is, a node in the data-flow graph that typically + * has a large fan-in or a large fan-out (or both). + * + * For such busy elements, we do not track flow directly from all delegate + * creations, but instead we first perform a flow analysis between busy elements, + * and then only in the end join up with delegate creations and delegate calls. + */ + abstract private class BusyFlowElement extends Element { + pragma[nomagic] + abstract Expr getAnInput(); + + pragma[nomagic] + abstract Expr getAnOutput(); + + /** Holds if this element can be reached from expression `e`. */ + pragma[nomagic] + private predicate exprReaches(Expr e) { + this.reachesCall(_) and + e = this.getAnInput() + or + exists(Expr mid | + this.exprReaches(mid) and + delegateFlowStep(e, mid) + ) + } + + /** + * Holds if this element can reach a delegate call `c` directly without + * passing through another busy element. + */ + pragma[nomagic] + predicate delegateCall(Call c, boolean libraryDelegateCall) { + reachesDelegateCall(this.getAnOutput(), c, libraryDelegateCall) + } + + pragma[nomagic] + private BusyFlowElement getASuccessor() { result.exprReaches(this.getAnOutput()) } + + /** + * Holds if this element reaches another busy element `other`, + * which can reach a delegate call directly without passing + * through another busy element. + */ + pragma[nomagic] + private predicate reachesCall(BusyFlowElement other) { + this = other and + other.delegateCall(_, _) + or + this.getASuccessor().reachesCall(other) + } + + /** Holds if this element is reached by a delegate creation for `c`. */ + pragma[nomagic] + predicate isReachedBy(Callable c) { + exists(BusyFlowElement pred | + pred.reachesCall(this) and + delegateCreationReaches(c, pred.getAnInput()) + ) + } + } + + private class TFieldOrProperty = @field or @property; + + private class FieldOrPropertyFlow extends BusyFlowElement, Assignable, TFieldOrProperty { + final override Expr getAnInput() { + exists(Assignable target | + target = this.getUnboundDeclaration() and + result = target.getAnAssignedValue() + ) + } + + final override AssignableRead getAnOutput() { + exists(Assignable target | + target = this.getUnboundDeclaration() and + result = target.getAnAccess() + ) + } + } + + private class CallOutputFlow extends BusyFlowElement, Callable { + final override Expr getAnInput() { this.canReturn(result) } + + final override Call getAnOutput() { + exists(Callable target | this = target.getUnboundDeclaration() | + target = result.getTarget() or + target = result.getTarget().(Method).getAnOverrider+() or + target = result.getTarget().(Method).getAnUltimateImplementor() or + target = getARuntimeDelegateTarget(result, false) + ) + } } /** Gets a run-time target for the delegate call `c`. */ + pragma[nomagic] Callable getARuntimeDelegateTarget(Call c, boolean libraryDelegateCall) { - delegateCall(c, delegateCallSource(result), libraryDelegateCall) + // directly resolvable without going through a "busy" element + exists(Expr e | + delegateCreationReaches(result, e) and + delegateCall(c, e, libraryDelegateCall) + ) + or + // resolvable by going through one or more "busy" elements + exists(BusyFlowElement busy | + busy.isReachedBy(result) and + busy.delegateCall(c, libraryDelegateCall) + ) } } From 5d05e4d22413e5e78b0e1834fddf73deaa3c8016 Mon Sep 17 00:00:00 2001 From: haby0 Date: Thu, 15 Apr 2021 17:28:53 +0800 Subject: [PATCH 277/433] Update java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll Co-authored-by: Chris Smowton --- .../src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8220745e6672..07ed446dcdf9 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll @@ -21,7 +21,7 @@ abstract class RequestGetMethod extends Method { /** Override method of `doGet` of `Servlet` subclass. */ private class ServletGetMethod extends RequestGetMethod { - ServletGetMethod() { this instanceof DoGetServletMethod } + ServletGetMethod() { isServletRequestMethod(this) and m.getName() = "doGet" } } /** The method of SpringController class processing `get` request. */ From 583d0889e257be00ee2d60b15d0a3ac401fbb644 Mon Sep 17 00:00:00 2001 From: haby0 Date: Thu, 15 Apr 2021 17:40:51 +0800 Subject: [PATCH 278/433] delete tomcat-embed-core stub, update the ServletGetMethod class --- .../CWE/CWE-352/JsonpInjectionLib.qll | 2 +- .../semmle/code/java/frameworks/Servlets.qll | 10 -- .../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 ------ .../javax/servlet/annotation/WebServlet.java | 30 ----- .../servlet/http/HttpServletRequest.java | 116 ------------------ .../servlet/http/HttpServletResponse.java | 106 ---------------- 11 files changed, 1 insertion(+), 421 deletions(-) delete mode 100644 java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/Filter.java delete mode 100644 java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/FilterChain.java delete mode 100644 java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/FilterConfig.java delete mode 100644 java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/ServletException.java delete mode 100644 java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/ServletRequest.java delete mode 100644 java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/ServletResponse.java delete mode 100644 java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/annotation/WebServlet.java delete mode 100644 java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/http/HttpServletRequest.java delete mode 100644 java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/http/HttpServletResponse.java 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 07ed446dcdf9..8c7433fd1b69 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll @@ -21,7 +21,7 @@ abstract class RequestGetMethod extends Method { /** Override method of `doGet` of `Servlet` subclass. */ private class ServletGetMethod extends RequestGetMethod { - ServletGetMethod() { isServletRequestMethod(this) and m.getName() = "doGet" } + ServletGetMethod() { isServletRequestMethod(this) and this.getName() = "doGet" } } /** The method of SpringController class processing `get` request. */ diff --git a/java/ql/src/semmle/code/java/frameworks/Servlets.qll b/java/ql/src/semmle/code/java/frameworks/Servlets.qll index c733023bbdce..08d5a80dd81b 100644 --- a/java/ql/src/semmle/code/java/frameworks/Servlets.qll +++ b/java/ql/src/semmle/code/java/frameworks/Servlets.qll @@ -361,13 +361,3 @@ predicate isDoFilterMethod(Method m) { 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/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 deleted file mode 100644 index 5833e3c909da..000000000000 --- a/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/Filter.java +++ /dev/null @@ -1,13 +0,0 @@ -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 deleted file mode 100644 index 6a1dfc588b6a..000000000000 --- a/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/FilterChain.java +++ /dev/null @@ -1,8 +0,0 @@ -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 deleted file mode 100644 index 66c13eb54f0b..000000000000 --- a/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/FilterConfig.java +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index ce5f7c4465ae..000000000000 --- a/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/ServletException.java +++ /dev/null @@ -1,8 +0,0 @@ -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 deleted file mode 100644 index 4ee0026d0668..000000000000 --- a/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/ServletRequest.java +++ /dev/null @@ -1,87 +0,0 @@ -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 deleted file mode 100644 index 0aa6121e686d..000000000000 --- a/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/ServletResponse.java +++ /dev/null @@ -1,39 +0,0 @@ -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/annotation/WebServlet.java b/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/annotation/WebServlet.java deleted file mode 100644 index cc255efc00ff..000000000000 --- a/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/annotation/WebServlet.java +++ /dev/null @@ -1,30 +0,0 @@ -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 ""; -} 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 deleted file mode 100644 index 02d53a96a333..000000000000 --- a/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/http/HttpServletRequest.java +++ /dev/null @@ -1,116 +0,0 @@ -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 deleted file mode 100644 index 0a2c6af0913c..000000000000 --- a/java/ql/test/stubs/tomcat-embed-core-9.0.41/javax/servlet/http/HttpServletResponse.java +++ /dev/null @@ -1,106 +0,0 @@ -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> supplier) { - } - - default Supplier> getTrailerFields() { - return null; - } -} From 216f204438bdd65961609081c00f71ce3b2b67c0 Mon Sep 17 00:00:00 2001 From: haby0 Date: Thu, 15 Apr 2021 19:28:25 +0800 Subject: [PATCH 279/433] delete FilterClass --- .../semmle/code/java/frameworks/Servlets.qll | 24 ------------------ java/ql/test/stubs/springframework-5.2.3.zip | Bin 57066 -> 0 bytes 2 files changed, 24 deletions(-) delete mode 100644 java/ql/test/stubs/springframework-5.2.3.zip diff --git a/java/ql/src/semmle/code/java/frameworks/Servlets.qll b/java/ql/src/semmle/code/java/frameworks/Servlets.qll index 08d5a80dd81b..1dd4373fb548 100644 --- a/java/ql/src/semmle/code/java/frameworks/Servlets.qll +++ b/java/ql/src/semmle/code/java/frameworks/Servlets.qll @@ -337,27 +337,3 @@ 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 Interface { - FilterChain() { hasQualifiedName("javax.servlet", "FilterChain") } -} - -/** Holds if `m` is an implementation of `javax.servlet.Filter.doFilter`. */ -predicate isDoFilterMethod(Method m) { - m.getName() = "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 -} diff --git a/java/ql/test/stubs/springframework-5.2.3.zip b/java/ql/test/stubs/springframework-5.2.3.zip deleted file mode 100644 index b54730e4bbcd561f20961940fb62c54dcee1225f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57066 zcmcG$1yont);>&kcT0Ckr*wCxbW68%H%NDPr*tFT9nvKrT>>Kb{m>I0zjM$3dhdN5 zW63$g!Q6X2GuE8XoNLQVfr7yRJ^k5H2vhv_AHV&D0r=@)Yj19CYGSW%W$a>O|B8;4 zo{65BK_2isXn=o(0%AzAQ`8*NqvJya0@9-e0wVgCzauRnBqFOMLhtD6xT>iYx7>j4 zbyLO9I&*LWN5^eG%{D&M-)cR}r8!~olKl-5v}Cb>AMoH@ZOWIMOjv%O*m>t7o7Ldn z*bs+Kk3FA>-o3|4E{&4}*D|R&x9gIHiu6xpPy8@|wZcsTd)(YYPj#`?7PeoThw#CmOY zRj1QCQWx`T_S(Y1yYcfTua^*5hi2F-l|$7V!VKLPuv@f#lCYaM(^ylKOJM}c#|f|6 zqOHgzmsLF^XXit^%LF;*IJMx(yk3GS-dYxTo?bb3od#az3MQ7KEGt+l4+(dGd>%(= zZNHUW#Cc)nI-V$$_x@3x{!kW-#)_S*efg$`Kyw!?1Ps!N4~Fx~{qU+YR5#n3->l#_6_bUR@XlWREHyIxHZ%WFha z>uB#wy|6$WM&Eb=sUt|_IP#TV*hYk^``}hPyN53ZJl)KQ#swxL*yBO|#9uFv;|RS3 zagCxQ)@(14(=38K+dk)-taaejDLCHZr9Uh8t|Y1F`SHlQE(-UdGNT76%T)(tSsr4S zjO?8_vbePhtSDtYi^Ip1^r2n8Wql(0@;vY6KI0RorC1pPM^-{Qii=w)uvrrEGF})E zd-nJ4Fs@Y70R&ERUbH}xr!`y(@Sp`QfVKNHCP&^vs3fS!w}}J%psV- zNpsAs7$U4$>>>Hwv~D2b806r(sS>CQS4aeX;rQfxzf@gY+g@=u^%a+UtXO{q&#|R$ zu@rkmSb2^${O0`&* zjA7o=Wb{nOueQnFbL#P7MXfi52F#&%7}^J{_>>G0BHPU4x~7!?@jhl0#=`1YBYtQ zZ(v1uR(ZJfcpVkeJNhCXVHgcv5aw5uR2#`_2db&eFZe13YGmi?t{8f2?)iVeSW3u%L-S2JdN>=Z;N69Kw$Jt?W}{!yM1v$I}{l z>w46pgDW}^G>jSfg%!gSumMqd#x#~g!`w~Xl%Bta4$kDJv?L2+$rrd?{70+qgrL|M~?=jXK@Do(hn#WE`5PPD3LG%c36RX2S>31eoL+-^ zdLR@J9NXH~=CM57wmHMRFb8r*Z0HsAnjU-iB^Yk|EjR_?09V9zoMh7QYj(@ca_r~w z+$00Uo}f$)W!G2mG(2g`_Q`y61|TC8coT0OO6iWW`AiVpC~Ec}pSkXVM3qzyfceMd z?HQ2*^CAi-G;qJmhT0YJHH&I+isYE+d2JYWrdv!oe|?l(f&i9Xu1(FX zz{~=CPidd<($se4Z3XtyuzIVXSwT$8`R0;z^YNgt{h_Q=UEtBR4wuZ526yobyIcjX zPaQnx3}471x*Jq-ND>=>g_&~PxSTx*qoGmhUxjWl52nFls^d+f*DTQWm5P3ai;WybaiTE}Iym0`Ifz4AEl-C3OiOo*c@09t!G&Reu~NJG5k3*q)2dM-Dh5tY?m8 z?>TrsNAM;#QRT;wN1bpp1sQNTj2rhal@Oqmhct2GZYsFN@2fXQocA(<3;SXxwiqJk zwszI3Q}xP-K#-rweWF_Rvu2^qh}&z*55u(x*1Z;{K%xviarlLQ6z zs6N#^RVr08{N))0pX0TGr+cTHXPwWE`_a%^va>Iw;7NpB=1UD|4inGu+MTKhWjtQ8 zTY1&RXSLZ%rek3u5?h({HbR|Y+1_fP zs9gD(F$^$NGSHF+|D?)ts?!Z8JbRA4; zgfZD-^h}jIEV=V=TInKZzN2?EB;;|c=u4j?SVSb#QiJ-=LlsEZ$3>Qzd5!SJN5MIo zD~8|m2esHGSvrQ~@k2cuAy3GY(EAW=W4;K@4m?OK=IceZ);KA`%PGSPlBc+RD}`1E z8{36>JuxwnqyY`V*WLmX&_H6xqqD;}yZm8!@2d$5EMs`@C`gcmdXj>A(B-S;?VN*8 zL7zwFFXV&FTl3Ck8wFVS__@zYiPQVdqnk+?4IH~+r*#Tq%=XYAAF-GM$eFH-hs%5O zIEDyikMl`}WKG`KVxCb@1|s{o(uQM4Rnn@AXA-$MlLQ1IVp_oIf;gdQKbgm5y8=! z#wbsoebamzSByzXLk|z)I~063)6|j9d4qDJDSv^fhFkNVwii(?ZH^r0=H17~f#sW< zt#@;)t8<^doa&GpUGD0?9OK&6KWcpOtnYwm%DaQOMQ+S{VV+x3sl1fRVvKYwVO*}1 z$5CBkMrdC|h7s|)v&Gw8$!Bh1LhX> zSA)9{GJ_5W^?0a{JI4# zw1vyoOg9qv&tBl3V#1->7TH`S0^vh9zVqyLv_aPRbPM5Ew0wSf*FEPj|H76E^`!~R za})lq%7n<3F_&9ct`3xF9fY%tp4RK5Pa0}amUq;^z;LII=YsS|k?1%s)`aYpW%%nU zhM0%}xAV8IlVq&Q{Bu^n_`ad*aglofc)7TA(>SlYqL3oeZ1O2U|9-{TgnMoUdGmv z0ZF;zMnVHKt<1*==*wXox_E*(J7cxm+ zpiE0NQkLWv$tYkzz-gJAOtVh!1c=>3EYHSSQLDUb>Y)I_Rx?%In`TuGC2qq2yz)ri^IIgG1LR>QP2X~RdLA#iji!LwJ5=b^T40ruUU6hVIP$nu$KbJdMdx#K{ow=W zptNZRSS}yUZSzt^qs<}Z~#^*{dJPaMDx za(-qE7=KBQfw8`|!#_yl_z$F+=o>oP*uVaXp?K~Xup|bMCJk8P|AkaRz#EHxe*(RQ zzOz1!xb(QJw4BNygX97YJ>~GIlpKxZs0`f%!|=S6q|7MIyp)tQ{SZUx+YejRlrqCZ z(yD`_G!k?mVTIHj!01f^`RJvunt}dGr%>O(#%lsz(h{(g|BIIuH#fG|w>LC1H`KTM z`Nd_1pCkAX!U1(;<5l8ze7=CFbrLRmH5vOqr%3n(?M|mi3I*)Oz zk9v;5O){6SW-7h*5(y+Up`Pj;WqI*iDE)mcf({rD)hH$3&eb=sf&aRfw!{1&S->9T z0ZZn;*o%a5lZlMnpHgFSiGPyfR3-Ku}LLuqpdM|}fJX2ZbVo)JmpKKLvt`?eRCXt~w5IcrT9dwn{*Vv&61FgA3uH+TFVX#PGJtNzcQnj!M z*d_mo@i`bh`Q1AT%Ny!#F3nfj8gXpdbMnz@v$*%PsKbweY1!s+jHyodOvc9Uv`OIU zBCy`XHd<0WA0ZzH9!}U#FBoN?&9eV%N8|kubnyQ(-|y5<_(Lj^4^|sTm)8(~V*YdD@r$X#lVATE9?bviaQ`9o z?|n6JHYz?4Aax3`jQ+k+4qwi>FW^Dh>O|4|4U_hS3txizc`|0o_ z8_ip{31}M7&9Jdb;ES}if(u~T-aJ<6t23fh3m3^KMq+jg(u{2J)NvL}k}Wv^HJjG1 zvJ_0KAKC0C=?t}6Q?>eGRFa-ZH_BQRG2qiw+^2(NtD(X{5am(hY2i|9ZgDxVx-B*| zWg#*?GNYFkRp1*r#(-Ua3;I78@|zl%9!8{~ z8y*qSmlq%}!=_(Whi|iL3t75hj=xgrb!nU>1GnS3eLUS}x&;Q&Ox&>6AP&^g>*XHV z;4-O*2sJcOa1X@R27y5cQ5PVIdxa7u{{98Cs&_XAshCTjp5Nu^TU*;We#7sFtocbvr$_lLMSxel04&k}=v99Z{YTd3DL3+@AD*L=Y)GwcZDHa07WkTD^4@EagHEmU>9|IHol!7a>bGDoG(BObYUoJVuC8jP15I<1 z%#1$yZShb@Hk02{+V(+y#92a6&X}J?3V#hL`G^BADmC3W=4G~mkn0wgs?w3(6P;$N zVg11*!<3?Ky>L}H@V?m@d{mo{DN!AX77AOlK$QEAfD_O6o@N_bySUe#ZqhmQ<4^Wl z&3}jB1Aw3vu;l(*DE`J~quQT>Z@`nyssn76I_CsV150K{xG)qSJLwtGS_Vv_tR=;& zXI7<9YECvL-RAp*CZoV0mnlwC{dd9OdKjQ2o86oiFK54iTO(21F1^(+U5uuC!HwV@ zjQlpcqTxIVz!db(*u8JmDQ!Ge)zi85>`&)>>gVbTbcxOYHn|9J zPKp0$v;Y6F^jp3lU=0XFA`7Eb9zK+qdUM33^qSC9sIGq4v?fP`)ac3> zI=wZWS)I&faiuQxTK&n~r58+W)gbKw&O$uH}& z20vcBwse>6nu9Bg2b1Gm0K-t#cuD7nL&_c8i3DtIiMA1?kfTAnHq@TEv+alKC8s5) z;=;Ur>r2~(Z*t4YYWi2?LbxqII|2}k1gxi$=ig5?e|N?okP~q>wsw?vGO#pv_&t)d z4s#>)@WY8rt(DnA%dka5z(g18!A98y5p^4K#=SwSabY4Hc3ETHit^LDkca=EAar;Y z%lTT(ZkkrA5*T4mfpZ{#R+sHEQN63b@J+-yqASa?kEBbg@MQ(qJ0UzZ{&dB*yoY`F z)5D)jC~->Y9!lZizVH1mrNVvMK5Ei`E26(A;y;JQU#ol!ZT?b0{49*?7dH(M z_Cpl>(_Exf>=bDwA~ik(`Oeb*ZKrRU6J21Fk+nXgl9W2BO(D#EMJ zZ%+mToqDIR4yR~dDfTO`s_$)r$_=vOa2{Ta!D(+ds5-J@%k6R{z4}-$~k5wHDQCU>Uw-M;OSPdF_~dh?FK*@A_F zyVPi4xE0l&)d=)(JwslH9D=7AT#lK!MrQ4IUfi~Vs|xA-66ntFh0qEtOAtnNtIB*P79BQJHC+*e_xC-|2n+J*3Lg1h);nL)CFBf z5+LZQihTO{i~4#JC}QnwZf|341#oR4ecLBy;F}A8cYmWYfV%j;Q5oiV`7D7f;>*gt z!FDdEcqN90)n<1WNLH-2XNFVE8nz}cwIscgl%Rwu#6%1@93_$w=9}{jIkDFm9-b`h z?;-frYy)6n<~BbeP~FGJhy(@%)B^RZMA|pE0?3oFwsmq;aezuq4qw?INIrE(F zZOt`9EdxD`z`o`>O`A1^P}ufBMzv0!ACL)^%fJ_?`?C>P1P2h|qbU+?NChQ8mJl%; z7vxW33z9rg?;OY{Lx0(49PNSSqTd%3k?T7ngj$b5=uEgtaAzlAmq88Z=VWu{R6W*b ze~evO8=G!*$X$JTUx>~HiSf0pfNUt7H6+Z;*g(K2!OV|ODje-H$edl(tt5%Ww(u;n zdXTZ%acx-S_(uCsLbB1t#{(57s)&m8;CWkCp#+yf^y$#{z+I!*?F&!WWJ;>Y=pQbUr#!__G}95pg8#KX&g(4NaVfh@l#3=q=_H{Pq1C0TkvO$TXTN?W{k3ghX;HKA^}U2)#F*~!O3T`a|!+(q#2 zgEkDYwY~y9atE<>e7VVa(+NAYx{of(ogRV35WPos3RaG7B_W|9X=1;URGYWh-J$EA zu1Pb`J&BL-W}WkJ)V+GOM9(_3rQ(_Zb+c#xU^I6i#5UhKv&b1tkZQ(u4=a)fzn2=( zI!IOF6~dGZ)3^%)zXlCMbF@w(78^nUQ~Vb6%U1FRXZy!oRbSx9Q9nZA4=y3T?zW|9 ziqGTEGJ`UmniQZm!u5)tJF>4&?MUpD5e3$TVx9PO*b+TZ+2D1qmYBmK-I4bDRsaO_rE_ewbUuMRO!0mXiAz^P8~y&pM+@4jRs-wjg5a&_!ylLp$jXy6 zD!|BsoV&n9Qqe!2IS~fQUI-F-%~U}*acP0`lzyT(r+v(cjoNP_rA}?A+J?x>gmZtr z_^OIcbr5yK4@7mGm&r03t0zKHQ*nSf75cGus4H{!Qx+HoUbOSP<93}eALG{@#~C*h zp}@sDy~Ruob}L4tB#*{$YuFpfl8yZG48*Ky@K4Q76|*1S_Fd7u!f8C7aZkNWIll?2 zZjiz-Iv4x0l09Q1z6|S`|A{c}4!FzaGCf6emzP~Br#~IJe`=~L6|`srDU0oa{PRyq zhc&hF)jLQapcBAEBHypaT|m@PGzacrcBFL;rj_sKp{?CBk7u0@n{c|$OH$5xWs%6? z1%^pgy1E@L9uFTdKO}asAi(sIZP#mA_;+r5vmG*ZUWN|}Xi%zGz%Y8v6mQ+3Opp3J zphlo;){Gpn9!~j%b2v?P5~Je2tc$fto{pNsm<+16q$g!`9nG6_m*tHNt6Ke74Zm9&Lx2@yT0ikNW=5wn?Xo`Q+vJTMam4b<1Gge)eZJ zMbC6|mhG0+$htL6HgxM5p;1D^;F+;#7cd$);>NbYr_*I$3 zvDM??^M~6L3~x_^yk)+{_5Q|Z5OC4VJdgx5LQs=30v>{!LunvK&5R4(WLg6BABe@w zEEF!ms{?g>6+%K(^d$8Ubm+L$qp_(ZH=8bn#G3ZA^&7)^_@ar7xatxFq#WK!slU36 z-hIzcex=eFK5o9f=+d#p5$!2Rr;U#gb^5~1X~S;7p|o^$7JFm&F&!kyGb2I{-is4U znE5W227KCt{X8p+HU)@59HP?-uXmT5uxJvcXvvC>?6{}kU6PKW;cnOHYXWQEyv6cL z_($5V52DKB>+miS6Y``yE?Cr}NtqU3R2(E!R|-KCJ3Aqob6}-{AV%l!V(r=*kc`Y2 zlkij&>>4c-3vRvC+MiL)6UXls4`5rD@e6FWgQ4}A?3E-lZ({M}hS5O)Ym_A7hU*iX zzvk4VwBo_%EP8j~jLoDfe%8>rFh2zhp>4H=Y(xUNZOENr$n3oC(+@AzfkJ9|SPxHD zOxUj`(GNZCByG~UpofvYXbP|B$G80+M)G-5r;!akvvi<0yg$}628<0&+N;m*%uu^# zMjniU99lx_jI5J$a>VH-TcUYj1glfGY9u!Uwh@+@xLMZa{uhfptJi#IrEg5cTSyFV zKbRGQx3RJxsb0g?JwuH&nW0_l099H1G;W@JH+AeReu&TV2#i;N3~t#DmX+giBFBz* zs%q_Q@Lo-E{unHDZKeT5X5Wtqbu6X>HW@Y$+ZDVUWOCOacj6g@Be9z?m2F$XDo6zG zxR>@`%Odc5@<5Bg6FnvtJONPr+AT%2HDMazcNVAX)ypa7biQR!c?+Euc8sdvTXx(q zXp$Ji!&cQno7lrk@&oUCd$ELpz_p-H;2)Y21$pscotCSwWW-_O1IAKt8iMtqrJZB0-^d6E!Gf$YZqnc|#hP(l$mboRQ!L&LZV`iHB8{ zM=D6X_q4^gAP%z~dSTJJ`%T*mdzqos*q!i(aL19w(}c5ZXvNm`h$alGj%M437?L~`UJ>a1s(T6&bmxCde6Pgn`}7pn>E z5249QNx4RVSQxl$r+MZ&NrwgLsuAi1E1feMigMJoyb;!aY>f=RqqsbIc$R?CH_j{k zMQ`GOer;T-XJj1ZbuiG4#1%OrmIrn7k-|Prj{E`Np@S(9BUfyaX$;o9h41IaYXji; zOgH5Pn8+M{OE!tzP9@wo0@Kt`T1L+fG*HFVQyhE@_$r{KTHflZrYqC3L-H74;!sHn zUL$|TSe_pb{p=7m%lLp+632)h<}$mg{Y)PB!B#47A+a8Z)dKz#XeAj>*)9ysFq#>6 zh^$dG{xPzzPDf8K|E4gvb8YDDV7qeCQVoe(5mDsV;OPFZ$oYlLLu+hM1GLxJ6C&a+ zU!+5h*Kq5uDIzO&vcT{!`r*8{rO12H>y!OW?QO9l_iXq#@8%&X8yxfuUc|7DAt;ID zZ~+FAP>1kRu4!3;FSV@=^|D+ck6_Lgk)=HX1!Qen% z%D0)GhfYbtM1wqL&qUHWD22|{?FRZeed$~PSy^|B8*|5zBTaLARHm`RA3sks31Mzt zQ3ayWxSas#WO|)oE$xTkeD`;a5ugdE3pE#xd!z3~3I{SLK$| z8R_ys%yzVoFG@6^e^e9$=z(*e z$fMkq=-pA?mT+=Ixzx99BW}L1`e|~k?x7KiG3)I+Wetrb6dkh9qFOQrh04|57BBE= zWGe~@VA{dGuX0MQ%x*Ynje34H*66Hq+=Cj?p*>}OoVs~)D!HN~(qmvU>21#Xu86gA zC;Uvu%!tg=tm@(MicBiGaBd!Wovq_U%ciDv$%QtLVLFT)@f7N=TIh&#$@we%pLO(8 zU1o<;l=VbNJdstu)khak``i(XJUXme!b@8cagR|(tb{uW+U<%4d(V{%- zU&}bKP+{&(+1{t6qxcv&ppnOk9L_*=)tuFr{}xx;A{zc31$@~d@bG=$w^Ziu3$p)! zN&uuX4o&$^JaQmGd6)WYfL%Oxk zK>k(9^aEj(*MQym1IUtJYvX=rbo8I@umE;s?q+PH{My!7(b&Y;-q`v#4k|%$#QGf} zQrkVvr+sbt)6n-qFS1qQo~fU`C(qfSl`4HZ+=;oOeA0blh)65QT=%Za=R*j-z=mv} zZ^fx*Kc*YJhcY>1MVW>8dkxUyJVqSgZAMYo9zRrrd(p8+rb*`d-W_{Pb}zV!6xE3-(-6=W!?a z7(1{7PR3&SVmvLFXYjMh(lGtOx!5eT^o-WR7lWKHHuUtO0cb@e1Yi*t%M2a};TLLBr0MY@(`BMkv8)E`L^fO};D~J=#1|Hwr)ar4H zCe$xXnCPLjotnET@<#m=%Um&QVhd-{HZ7jdD~fO|s3_c#t`6sX$N3ZI|0ESelc#gmv-?20>d;r5ds@>KLCJ?;J9Fs@M`nih<7P z^LEQmMBYi|TKi2k}LED>Hfac5SzB+I+H=P{uxu5nng1+YF0%q>@&d#4D zvUFaCc4~SZ@uAt|*;F*Qfa_PL$9`d^d>Sk*2KL7w-ps#-i+wB^K0N>kUV!y$T;1P- zAg^x^=;;2x>FNqfN=Z8U@zII8KN;(%i7!bhX;~H9z!3;i4D=!lDrE;dDoRSiP#QEJ zQY4Sa4U}kbm_9wpnbb0jABgOqyWGEaVEnKT2U}yyZ?5_G2ayl}qyNa;zsUs*I9VI% z+Z+Al`#GHWW3d3ir~oU}{@*-639t>3KkV`cl>|od!n|XI6LrZFG<@HUIk?gujanRq zBmE`VXh!gCbvs}hO|uw-sN;q=esYupSvd18X@hJH?Xh4W*4uu3SBH?Tkaq21bxLQs<4>diP|ELB#30 zr8IW>D8vQLT?>Tr_LSMHpT;X?O-w&n0Wc~AEWQ5?R=+vGzd$SEIRc>R33l%;ViD%y zgK`d{qi0VZHeN%w>>G#V-6^j$S;4|wCeK!!+-u=n-+!$?c}~_~yfQc04lNfD?^zty z_gX{qYn1ntc`Db*tRywq$pJ09hUYyZyDZ_D7jQ-$aW8UKx!&VgX>NiX-4FvobEWTSAtVX=9iD~Li zQorv@@WO4Dd!vt!KUt`#|Ag@s02O3VAfOk&j`R}{BG!(8;lyvpRl8!%Q`3F*l!nJI zAoqkYNHsxSJ~*@G{zMuDN+Oj*8S4C0#;sUnpVGL5V#MoocbAM{8D0_uGe6{o8I2ln z`X{LRXq@Dw+GstX;y zXP$%I@cxW&;FIo!XJ8sW;7u-ma*QS6LbTjyp{1>jr?+s%V#XhJ9Fj*A*u}QX?lZFt z7vp0y!XlvGsJf21qoV>DwbXFxD_g^5NxMU+pO3XpH>s}O{M-OZ2tAjS~4 zkne;+1vhC2kW3LNm;2@hSbNcj&k>={uWn$2E(HDXGJKipe=_N6WT>7FK;Z@jVDbOM z%_sq8l$^e6AX0hAewh)j{hsCvF#cD524ivYo@8PydL6gT;*!I{C7!4m0{TejvnUZe@0tIWKl^t`p3O;O*n>$B`Ph#RpfeHM)*W+2EVHrOn-$hvPAio7)RU z2CR#sk5?BwUpdpp?kI-9SdiE1v%De&Fw8ED@WEsAJxw=lpx5e3K#hRy&7o!Xfiyp1 zKoa|W>h`9@>V^9Pkq_lTq_BeYZ-dRjsWtSxg5vhAf=$t6PCsvIUAJqlsJ*!l ze2*X1n)dPSJb$R9)Xyi`JThd>LKVjp&8qWhfD0J z&#<^7hL|?t{iMUazyj(pm=eUyLrn4N3;~UTf)*cksJLD?>#oYqg5|b;(YAYOra7Iu zV^4@LLm}uC`0|E(rp(2vK*AUNT^jYhn3K0a(aanA!@mWm-^Q_ji1){;Qx-jpXr8BPD+4fOeA%~zaCkxxm4LErp#f)xwt0zs ztCPVhfi-Oy&j6biQNm1aobzVQHgsz!%oM6%JXxV7dP(q-wIji;&z^V=5yW^eSDX-e z1uJ?b2j1?r*F6uW-t z*$OD@E*eHv*?{Ws!WN9;5wY@cXZ03?et5IpVkexW%l3v?DAgR9AbM_6TvaMsHRLg7 zJBXv%*yeMqIYY4J4iiUhpsm8GVN<@1t2viSPnkOby9n2!GZjqhbmhs&aqn`UVnMV1 zVrK2JWU^AMi|R)-a1>{9U21AcbFYGR9u|+D?ssQqH|Et9Z`I^tAzVw5)j~>9gWp^h z9NJuWG+L!C;U69OA^uf~agGR5Edb@^0~Gmd`TcK7{GPV_pv8jd3BZ&jTt8rj2M;~p z-6xd2J1yJY*Rp@9qM<cd(c*mCxq+peBR^RJ-xN#aK8{U%xGfx_q zQF_dtKE*N^>PVulFuywT$(8D@%eTqE8OfvM9;AaJk}jOu0X}FN5E8EZ0Ex=RfVu{7 z4dvUR7#ZDd@s1Hmc;b^9E$5Q_6)1gyy!`KXUW0N1?$z`w*^dMhqfw??X%*5ID`Q`V@I!Vpp*U z*`7^&E}jIpGLNveGkptUoPTr1(Vw`;NdnqKC1YZDm$7ThRn#f9;LQJ#RqJDPPZ6-5 zGtV)OGH^t_mE)3597{F)%xgwL1BA^k=d&|-rydC`AtR6;{ivY}Vj1%Edn?DNse8;J zmd_pyVfhHfn&ql@;5Q9z{ugELQ*NM$GeB{pw>YeZDjm(<-A2ZbsFcktpH$U0o|jko zf2p70^!!G${yi)3YZZf~zV-JThJP|?*!~!h)_@%LJp>RC&o8O#n=n}?OUpk7q#~8S z4G6U#s=?Vyf@(R}?d!Am^;jHbOQKR#r+=Rhl8Vvx>YalC%m?L{_l|=Icrpd&vD*-= zkM7g@GB+u=uzIxH*}fpiLYx>l{R-A7Adj{a3$3?i6`ynYm?eW6qPup6QqTv}RzBJL zhG}dV{u4XsZCJlO*(MMEJ9BZ4CEbj+Rw$&3i^*z?eESVL6o^K72Vb8~$ytP^07X_! zTXLxYI+rwc(>Ex|T{pZznw+8s5icekOA%3hn|TjzmpO`#b@Qg2TV*uz)2ZtSLg|;H ztS?5E+LGZzKRwn#QcY?y-oDuj($uN%Vws{|3LAuJi2Pg*Kb@iE`)S^GVAl;ubc7=c}+FH6tLX}@9y{o8vFkAq{^o&l|tHWKrr_r)z@WY6QYS$_V z{pp7pkCgSX14rLTYby(BR8A1O5=$*NvUR1DRd zz6z*!6DBDknKZ+(e%Go=tPYOVjeC+Dp7Zq@9VRk1!0{2=NBm&cLP)$`AKp6QE+vQ> zQ$lbw-tNqww-!q>CBF2XOi}3-yd=tpt+BS0uIL6!){cxO{$Qpr_hWC}MJ84|GTMwx zBBQvlGenk+y4&G-jgWDJl4dtzVWb552`iEhtExecxT@u+FRPF;$qP!6@-Yn-N^r9) zERZCv#nBIY9Ip)T6ux@;U#aHT2Ob(c|6=$B7{}4_R&mvtykfAkQpkjczUh^}Lsdzg z?YaVDv@Df=>%k###exC^6Mymw2eNKFsb5@z$EQr zPY(8E6Q>O+57VX{lGGd?m6p6WaiCVdr*ceDiPk#_V%%45}p>jubIL5Eom@ zz*Cbnyk(R(@b1Dm?^1F24}{&ju49KxbM z6SzOhT*GrTgLu%yJ%!;)^uRTX?1djG#Crhe{n?Sx>EJlx6RK6_yG752LCP0ec)?W{ zOJj~V^eH@YO9y4Ir7#gMjlH<@(v#q$%{p_Xl5M$CQWa<(W_pxrr5-{5jNw1u6#lh2 zN_%4~8%MyE&2Mr1dzATE;I9>Ae+Ya!H_fe`ZC)AM|8Ph=-CF2Xo2$$LL>olFA@OTm z*xw3&s{SOtKS&91{m$Ib7{EXR`ftB?#NNmt`0+oH47pOPqu|;&s2VBz(ET}j;3ni5 zVPHM0bbNLaE>%^huXNs4HlPb`4CfE-l*L+wuXok!Tl(+!w+R#9(a#PR(S~Q43L$74 zD*2UZ-dXTQbKAQf80rZ?9D#ere41K`ZJq!z4XYFErU|hW^8f6;m~5O~_izG&9xdOH z6uf*HDN9&!0k!?sCDWZOQPp9uz@H5}h>Ydo2R8X<(lo@(rWKZX5^P{k)>F@D1O`?WLx+`nJ>{R`yiBj0YSiEi>1ok80Jn?R^?_`XUR zv_KK_Uh(5!X*c%#yr!qoOvx#k2|7Q5x^lIv&iwXuI$7QlSwRimT!3o|a3Wy|55?+N zaT`yUO~zBiK)d>hyO-vRdeeVL=>H)Qa9IFw>FEcj`dRL; zHNk(D`?vGzPkr{R{!#`{fP$Y=-B-V)=zn;GfPsUfJ>Zh<@3&%%jg)@7TPx^fZuxsR z-Z(DG|Q!Wh*DpjcQ+@Izp2S3h$mc%kilX>w)cQ+n~%j~ADZ?yy1Fr~*?vhq<)o ziXPLNj-L_)l^PlmMaX3dM8b0HV(J{EaG4>yYElgtp|D>C9RpCCY7mJWpG!i`#}j?d4Wlcqu75 zM}Rf$Bw;>U0RwDJ5QN5$zPjoB!L9O!>UzlewYI?f;uZCGn{T{24t{F6r_rEzJP~Jl zfc5nA-*@bP(D&c%kZ$;oW-cQzteHoX4~NSvLAOZ=IW;zDD4MauWTd zIbXo~wU*Ssv7bNe_j{L+7qBIO(tx_W3(tTkVSPSRf}ZJtY;(oT8Hh!Z@Q17-L!EcL z5~opxxJ-3-n9{_n@?h3=FvX!iA2ia;H&L$dLYncEw9;lHLW2L~*K7NDyv>ei%9rlU z2)gd?bl{y8Y&im11f5xR_3c{hpXMo7nbz>M!8*V)1g!t5(*E3}fRVoK?}wmO#LK5* z)hBB~X$NQ;mJ)Oi*aJX~CM8tU)0q#Wou6zWd7R(fQ(<95fNA7aiD+zj$LA&LqTLH=my}^hgbmt^*zUF+MjZ%4qo>dn{KLiIU%S8d4{CNXHuxdYc#4LMtmRb|0Dy%6iN@2nI{qg> zKRxF6i1-_~^V?NkMPpOIwK-*0R0eLy47KL_U*FJ_jq*$^cc&X=&m%YgxO zC-?cWv3NZ0ANFF767Ymjf?uOOTh%bzaF3-iX%K0XHB;ho4@NUoRLmk?YhK6R4t^f` zc&dsL{@Dk#q`g{ips`0oE=!%x@W8*3-&2*SOb>JyZh)-vJSwB=-h7>4Lmw#78Wyc6 zaK&;R6G)#9g(wlp+3ESho%Nt(I{Tqm$gNacn@n9TTV2W+>c2k!6#tj78GJu3zJF2X zPk&&a4RA>YsAvUHh5pa{;Ym$@Jntz*{5`6D;}t66R}}$=RbbU}(ID9<$7|r^amtVT z;OrbP_@SiIh-5yRkQ8sX;(AfTFZykf$$VPo-LotX&%?n`flNf_FrlvLN$wuPL| z%VnppNGVH)HKc`BoIfLzzMG}t1$FdiQ86}-@q%s6ff#4+l>8*o5u@&TFv-<4Q&oLQP1O&>f$wawNB9XVzqXHntkXUY7cK%Ew#H6)!4BF6WiRTr10t_tKp|@oQN>XE;PkMFTsz|qm(+`!2ZFz@#5!ySJYO)~zBCY%EFK!T-Qb+(`sP2Co0V9N?6 zNG?U$wL}o}MK|jVNDv`r%wiOQRDGHHJ)D;tU_!>zeSzt2VF-%DFp`|j*bkyKBqQTk zsgfoSMKf*+LD>{-s%X4^L9iFlBo!VSGXkt5&?f*4wPu~3JG55`%Gorhn&bOe66ROWZ z)0nn!>BMW-;I;H=-R~1wjKgkEtg8?DeBB0#hO!vEQM%G|M0T6!o-;N#=r`%@-IbH1 z_L8z&wY@5y+epVmx{!5IkDTirv`a8{gwEVm8`rnA(kXX>7l>B#27xqmk74v35pHj5tL+B&M(TmB5` z_ZbqPy$|TGfrcTAsez@i)nRzL;K_yKNjai`h#qBqOF;?w;Rq}<#xePAy0fp0l;Mmr zG*FMuHq6V2KME(xNxC;Si26EY`c}An^l@^L2|x=LTzq-f`_)zQ1LJA5UF{f5$+ZA} z3V+=!d@rTgDwk74{`0fgREcNNa-;;JqK&6yCpzyklSGJWkCt3XvSco3YoAHd zur#!1UX*^l$HpBLvBQmNcjmzn9W^@aRo{G}zk^-9MtB|8*M3Y)zXkJGONH;KhW`YB z)f~|4^!mSo_s&$!}l7_>G*%cq8z{Z;LFhV(1b=8#)z2O4s3zZkWUpz~)#om36ZYX65V#bYL$39H}LTg12XFMplF|H2Tf3 z>%w;STZc^6JQoYZVFYVSOYV?k;d6s3jdTzyb;?+C9a*zQ<4%XMZ z++3;(bcLon4!yVXk}Il|@c1d`R~GbAjt6K$0$}j}Uv1|BPi6bYaU`-Op==Tg8D($T zdxns`_g>j#BqKs0GnEQiAu=+<=XYQC zb&uz|uglPfa7P&Q~&e;}wuDH(k$e6JpU$GRK1T7GK`^ z+od1ALV*>c!q-o{VZ?HnN1=Tw{m51HLeR3UuAyQLij#_xjJ4U!t@1l;^c}txlU#ko z;Rj@vx>{+9B$t&k9<3h{7w6IObN>=6OWAyEgl=gF*l701>~lf03+I=+ZPEYe>7)D1 z>$Y}nqgtB--?lq-V$D;7OjsdH`M;YVi@7*C+t~mS)thUcb4^e`iPY{@MI%|cjDC{l zP{8WCaHIuBe{5{Y5%OX zI}SdUMv5^cGQMr143a~Yy`~Z0-<%me$|bBLKPV-6Tf%x0*>w%HMY(e<-`l>{)|CI( z34W;^&pXHtq8ISlTTDINkDfO%HE^+ZhRqNqEUW>qC}wV8v6*6aDT{c1z@NT0x$L zxrOOo_i07>$6LWB&}@j%*w{lMEfppcNWsY*1RP6*u&juid&WCkcyBM0;Lu&1Eq059 z-CMZyufJ@wnRaQ%N|Etgsv=~Dc@g+PLd*BDQiND1lpS^l_~{&UnnKr7f#U}RCRGcn z8l#-4#y)GU{~$qsLE4@SvPl$>oA>xZ^5F;*%bw+?X7@&Q>uj_ScYL~nlgg(h_$rw4 zse}3^<>@{S=hp{t56_qj{-71KTia#Mytgv~PJq#y*f|3SFLsEtieJQ;4v5kOe8l!9 z&h}4=+1UVre?WjCR0x5WP(Oq=eetH_;OxjgtJPMv#|udp)vbdcu~9$#F8RZ&p|UjMejc%E>IDx&zK1r}K`i%bE>P4u zzml=M{i@(##A4GI+7`L0aY^tHReUW}04|6V`(=)HjvlO@cYQHzQ zv`MtcutdJ-@)}MHGf6wJf=X(jDji11f-6(`=DR+(!Ogqcb*X5{u{DB5iAI;%>4T87B|{%@-!T>Mbte(u5bHT)Tx7pi!`7MXHIsQ+F`m0O?C`_0GwuZ#IS9^p0 z7>lBzrHPTVI^an+!|_<0-0VN~RVdW45~2;Mnx?619m8(r&VCo75Q=f48@ae*w4B<8 zAgAPo_#qOH!+HnM1`nhjyh4PUe&F=-M%Jaq=7LH|9)nCT=h~GoxE|-eE*Hrd7r8ED zsjPN044+DP%2jnWSV8=Gw!({Vwh>r?&9iMBDfd6{l7u=PUi%p5;_ourj>gI5K=e6M zswbGUu1$Avtlytgx$MYQ-DK2H_GwwuYuV!sHmFD@vMHY?G%_`99^@6odqNiA`ah7W zzdL#a&wA@73Z7B0%2oq(8QK8;;`fEt^Zct9=U?AWIRXDS64FF34qHp30fFpE(dnzv*P733zNo@ZEVRntZYy?pKyq%f z0f|@0r9tg$!8HyOZK3{8?;3cIG+%uk)@1fnpj$e_*uh+4shY&Wv`tJ11sONj9>YBo;1-m&CYe>pzAD(l%bq4G|r>iIz=pFtmlaD1CDN z#YwiclmvEh6ShDZ6Z7wP3>Z5Pa&qV5 zHk!y$i)!d{c=myNR6Q>qM~h}3vid;uBiXN3Zs=llI|sdaSsD(V!1$+NDjlj%LRgbB z-z4)&_41AkGt&39dWl!QSz(bhn;dQa!k0l_a#{JHHWu2(y)5eHLY&Z8^K3&2W2}Oe zAFVi@$z5_@Hd)s#2M^{tl@g&HJ9+)}F`nFasa81B7OOV}QhNqm8V$Yt-t0yyfbitgEF@T^=Mf*`MUBIzALPy3Fd!MN! z7Qws3jblsX2V%Oym~p7bC72u@w&|+yXpuFtv2{JKd8mmY6sHqQ-uUW@P=sUYS2}*v zl&iTJLyPU5gE4s{Un<4M4;&j=u)p~c+kd`9jpNEmVn$LLGR>T3R!xoI>vTQtbm5qk zg}%qj4b5$pYu=YTx-xzbicjvoblGvJh_D`G@KW;H=&bz#TUA3ZOKn9D_iB`cyE=t; zi4%IRioPCMN%|t^!4~7=E5huG()5@e%OFUe_RSIZ(9zI%N#}Rx%3BzG)mS23hO6%k zI+ckwe2?Ysi&^rv8vLB^K~9&2#xdA2{@`HX(f9zem-jl3va6lBrf}{pLXgJ>C~I24D}_iF4JU(eYBI_2zzXxe0Ie9#t(0~ zH_Ipd*6AauQN20_1dk3Z_x4M>-&5Dq->}z~azPhzlFlZXAmaP-(j0GZ$OSY^RvEyu6l z%Cd}IAq1SJcKE-8(&k6DovoI&LpB)Qs5}aHW zTH$HLMP8wpKZH+AD~lG8-=Q>wAy?*}FDh>SP>trgA6D?}0&NL{DXnaEbU+T%#Y6nL z58Fr++;wad?>bqMVVIGg?B{;N+J|$M=ML$euPs5}t{*`&WUI6wS8h*DWb}YjWUrGcagLJ-*~IvRh_H7wkshuwH@aMTXLa9~m3Wo(pXFyLMi1H_-3r~%z}fjV^1{piZsLICc;mCoNQ4!HF&BXeYGtADQQ48zgl!HQD_C9l#^*jM zoGIWf(R29_MYrrPozX-)#fs0%8to*x8ig_;8z*&%ioTXJNv=kjWWb=|E&1!=Fu_un zk3`6Ch?_VWKd*DAu=u`pSYaPMT|8exb1^P@WMTTo$_;)(il>#Kky?~^HD8E7qV=~^ zo2f5*&a2)*yX>%>6Wc*V=Xv@R`r}>=qepYjbT~|nF0*282ghUb1FGW+mD!AWfVvJz9Xa&$Q=M0Y{c>&%NYdwY%#ZgN zn#-b&s__Vrw9XwK8xa-#F+^ghZ1Ul&iex+cQGW(fa!s6O0cr1b8`198@rCM@B`0!S zmN~O1;d4j452OI!`%6zD`+u$o=re%7n_y0;k36f-FkmB6#s&65$F-fmJUfz_E-{^MsTH7a)4-%+&xewT; zwS)6->Ew$l7V^cNw=|txie+WEb-gKhkUi30HbLTuDsp1Ms&S9Q8ZlG$wJ(}M?_!2R zCn|ACWRbPY+dEfqyKHr5e6mXEE-us2D%QrFCy#%ZAp5{2aq>WgFgvE*lk6eEh^vv) zQ^9q&Q##KqkQ`r1tZejT)oal4GDDuu3ec$Wl*4%0km}Wgdg-X}Gvqr?;i5$GB{^&K z=PWh`i7ZVONvgBOi9g$)TW3be^?a}Rc(VDk^vu(sv$9C>2GMm51^2J@fRcOf+PK|*z<20TG(zLv)%l`+8rtW1^4%Z2;+S+v>a4)gP*Gi0A}Zly4T}* z(D$PdSkP}vnm7Z~Odx8{3_8^;j(q_bA-?$P56s+L<59}H(E;5`nuJOir|S(r%BiR} za+Y-64_}<)X1u&`H(I80zSe0nwTzZ@2#X=c@*=z010@rK^xF6mtdiPew=m*4lw`c- zMRTdCGuZn)zoNTWRYhj`tgO{7D={=pm9nU_FqvZy>84Uy=-!l^K#Xz*^?ISGo{@h z-dj2Qpga%4pb@RR*Z#^yAF0vMF;|*gdnF=#wHSWcNe4PA7OUoFy#2867Z4vYa0MJ z612ny>meYDB}X6FoE$~L6*+Aqb@b5dWrKIRbG{1Msz7E+caPALE3w@lu{WIK2cPU7gPYi+5!f)x4lS2#sy?6G1o8v z0tR6jH6x>HAlvZg=hnk17CI@1P{PRg^$*<}EY0u6ENas}QNK24C>7^UHE7ClrjUc; zKG9i6N$q$1$8t#nC?q8xk|<-CSTk2^6H}3x89kSqBR}X9%omFLK(EYj?tZ!?Y5p~? zvIlisC+Va5^}~=qEv}t;V4rgDj*TXY1@=;`v2w@#NQO@KHjbotz8l^unYX=_Q?D&X zn{h1Zwti1Dy-sghb=M!$19{jHjXjNb4DWgBj$JWjv(b!UK%FOmI(y4@f>0ehC%C>B z8Pf(C02=2!Fe&?i!VRJy4dRECG;GF$xSu>Et?fKv{PK=PU&VEHwv)I=1sh(z^7ZwY zal3m+N6B$6u*4FZyQ3pGgVqXviVt<|uvK_o}H5OWl zdOl4`)_%Ulk;J^-oaAMKs7}?6W%6iMzB^=tE$W|$`E%!0)XsP_7AG>A%@z<-eDWjV z@f305rV-T7?bPB_PtentwUuZiFvZTX1`Jl7LMakK&m?W$m~VcCJ&M6^X=uliB(`{@ z?&^h+>t!SQA<@bHaZ}txT z>8|`Wp?xD|L*grAM3+@RT2O@_l6Fc#m*}-Fs>6^WWY+n3S`hr6C_cttCH1o1RTe<9 zBfwfg`LEr|)`s==5D*z74|zWsyviFq-{PNvl^B5KU_opl_K`*AgCR{6`MHv;7DrLm zDle1C92&Me+F?m^k%OcbLl+sMPpRf>lTt^#6Atz=DY1^SDj{MwqRM`(9~^s_I-|2i z^D7RYK3!Ao^y9J-`QT1NB6&yYFo7EsXB7fZ7coTPZD3uzuQFeEHaUQ?T`#dDkd( zcVun8OXs<)Bs(;pJZ|=<)g4w0EEYg6qU%oK>d#5*%symxwvOy(XD3x?Q~R}7jJ3jn z1E?H|%U{INjZzp zP6-p7Xa2m4Sld%Vi2L;Pe)rlNXp~^!1M&NNo0LE&5}>ge);QY-hL1i6;uon1k=hI8 z7SO4fhQf;KZ{Ce2LibyV$YQ1nUe3RY-@6JXk{~h=?$& z)69dEVJej?z`JVuNgo1c7-}e}LJF8+7%=($`+?%;lLE;@3~ZqD5TN_o1p-Fswt*Re zK@{O?LalPIoBC)*k?M^DxMR=XbiWD9VA!Y($%@N3)Ke$D@@;T{&rNAIUF5!V-B$&t zx829J`A=1vNiu2Hm^28>m{i--(TckFGulkillfspx5=3(-Y;U72~;m?p+iAu5^{Nh zYih2fhrWR&8$UE|LH<&N%lzcT;6!#Ocd0e%l^^-KEl-x#cqgK)DN}VCO^u87U z7KO0p@&RoF91W6>9*4EcB_{H0%$;AUGtl9m=wU(Y)odflWR@@<9J}MqL_0I~a;8;{ zImCeb^_2p_!8m1?z%yf{+wIT@%m%$@?(&xbmgWnz#lL8W%`=Z6H@X>Q1DtrIIGS~4 z$fOC)Q>pPTdemDYSr=KEUY_Uk11X1i`LXS!f8bS}zzruQiJOVEOfEHMp|G5J^Iq*r zSqpPKW+=CCzMK`c^`|%J9>IKfF`ML6jyiR`mO_5WBB z$uml6t@#deIJQq_#;GVqpaC|BC-{4NLk1bR5oXCS46<8>L`p*5LGSH-J?JNY+P#>8 zwY8yvkrmYRaay;+t|70UKcLyRa|WNK(Ptb6W_Nae(@pfI{-!+FY_*-(pBkUz8zl(W zQM+$Py(}$$EINerai9jVY!k|rcd8+CnwaaP{KGpV<%_M{>LAdN_nxxAXKz00rxviB z9^&rMV*)>O+>Y$>^?u3U-uhoq09hX<|A1zqV5 z4kLY4^{R?s9OJ0}l8pU_Vs)=5v-`0Z-z@1k#;$XD`|AZAV1!JMBj z?^*~9BTTk$zDiBY${kX6<`~MC{tx(GbO$^~1&>{49OBBRJo&16;RT16ff~D=3?*B# zKdQECv)3F|(XpzWnza@VR-ez*0#qepjKy~0m*lWGWAq>Ko^VOe?Wvuq5fN)d?-;dj zu8~aVpWbK*LBlSRd@9^BeRt^-cRTwv$-o1Iv4b4XRy*_)-Ypm2oj(5&x$|~rg(!m+ zhGObv;*Olm`oi=pD3|Ke*~*`!P&Q)wxwaJB3d&5CyDVp2PsctNZ#=JIT)Hwzlz5;I zzju)NZdMZI^2CR)`t8S`Vf!ApkhvLug?`j8PcD)3{ZX^@_yB@J)$h0u@OTdQ(bcbH zS|$Ei3DjLLJ7O*$tUY4yFJsd+{;J#FMYfQl8|V6r1_35x@n>-x6#iN6c1y2}&$c~k z@ltUsY!{d+TJklTEf+F^jBan3r92S*e#N<*<=AYklGxFh3MUAvCHJ)1L z7H#-+2?q%9Ws*OfmS0~VjK`Obe|bmoHG1ka`3kMR-Q?R1igxvD14Xn}&s$b<<1Axd znTg?b#8WY+XK*4b=H?GK&@h!*SzJLme7YJvNwRcywxD^2YrW?pJUU9YT+Sko$SAkaR3QN<7a;;ZL$?ChgI^q$e4M3-y980E+){1jUf%Y8(xZaciT4jfuSVbuhfrbJ_ z(#TY0{kJzu?M;@8=T1+zMm{|pJ}6-A6Yu?Pm$TNKd-sl@YEavuUFTM{l8u4i$^kxm zpA`au%g_o_7l7gf+vVYa$jz;+i#8DjkPs8pZ*s#JkCBcV9g8aF8J@N@D4K=eN$w9Avhfx6VIq4l9UA$GtT$f2E`6v%BZz0MkYzp`*u> zj}ZO18z-L7^)Q<5W_&YYWRGLr8WYpdO)le==cn?*8-%$zR!J@V>&IF=aywhxlyW20 z9u*=xTn~14%)idmzYkmrm4M%)TS0hKZZR>m}XS_4)wYYM$*k(Y~9mD@j8B*iRa#+ z2oTV{iFYKta0PARghX!qV&_t>z%!|i{9du-IVpUalqa1TS!MC3-;j7ESX0Ss#IM|s zdwqM=(>$XkE`yYk605sBovDl+*;NwjyJeIGKM7WzyycE3Vnh-M)|yd3@YpzO1! zTqDtyNd@_tW|pUJ)qGJ^7zpt4=0LuE_4LCq1M{}|gGUw!c%)*2Xolp^OWsh+=48LZ zeG2IL0 z?jvL(i1ncF))^aOAU`7F(|yOrgH$Cl%O>tMjrh6aCwOQDZlQ@QG>HqJw~&{xj1Z4( z6KC>zudYw56zYangcc#&*4bKne4|o6KjbE#ow zMBXM?5_!VoF2UlAYh#R^1_f*(hLtjFGr^ZDRg&g2!mLE}tPDlQ|w#Cw+ojgpoAlv#DgW%%q zXrb8;;rfhL{m@X9$In=)gKs3_Gg3(xX4lkK6X2l*(z#HXGE!W=l^dXaZCtYKM)V1y^H@{LG(yVjdYpso1?>#G_&j+nto$zw`(8DOS^sK@*3(Idk2w$W1VWigK3po02tdvtyv;M_=j$3r+6>1bn zl~|NQ?z6oX7{6(eWK4N5Pv54oH|< zMG*qUdf#sg%}l(z>3Z+`t=ErmT$BZm<%_ZNzFu@bU-yG0Yj|J-XR-nHR*2bJWzGOS^8cEEakd2o>$=m0yaOz>J z_uQ0^;MY@M*hdDeFZ$*Ax;eR-nhZ}AJU!>ctfiByJw*EP<;z@}YT4XxkpefWjt&~} zis$?wM+-n!%&8#N)bu)nGCOvBMBn={YKq%QE#7Blu_SGz1{D#Z0@*`Eb-{%!uaAs- zcv7*9`4v)m*`(N!k%|k-CM&Kh6jIYKiM@0f!^WY=;)xa|>aJE!W8L0&?#|ODl|s-qWAEL}D*@$;h#<#$W5Uv2vp7T}keIdIQtCG^$kbJGoCE z$ustZlFi{JNMT(}f+_*j6*GPTx;nmq2Dh=gW~T!Rd+G70&{nmW?^ zMvT`+-E-#-QOq2SCQCzq@g)&YjO^Z58cNaJs#Tp^OgD~LPvv)j1B;HXfAc7D9bIGmoS^1FX0y(A;`PX^!BY+9tW-mbpc{$8vmrgj z!>co<)Lz(l#A3ZEhG9OZAANqOJaUKv>xB@7Lo@MImyuOdHr^r1Bg9kD?W@^i0xHzD zxOF-M#;+yDBflmYC|~`WUd7Ih-R`SHBfX(R&5GSB$`GEqAQ_tnCm?0Di8oq3h}%4SA=2 zPJDq|&kP&s`7)KR@qS8w>HMJYrRGCgHzv9yGHOT0nvg?;NX%C3U*`r6iu9dTUma~v z`ig%G)pfP9^xKVNRnkK^$ySBMKus%FUY=u>5|pLJeaX~+U5h#kMT}a z(XryzeCI?l^$Bya_BWx{!EMb*f|)KV39K@TL!oELo@dsLswx!p9DUG0B=afVQHm!k zQ1lcq{Rz>Yjrx$` za@~?Yg)*krKd#i1Ffi}w=);quTSfPVZASH8J5T@2jZ)i~e%b5z;WI#dJaF|LdrlQX?CzvRxDgu~sO(ER=5 zL>F1rRXe-Jt}kPbXk4ZCJ{*;at@K=~o?nqhKU`RIVXq`XiZxY3r;Ic~Lsfm8nuxw& z8cKdB_z^WvU=owDM0bk`-Hk)C>wYCCB=p-KWsg?zq9i&Ur|a#+qSX8B?vQQ}8~t)M zIW$for6BHJzF{P(&0H4_OQ%cr)3dc-yG~Gt_IW-zf=Bc4soID7%%gh!T|YX`NtaB1 zmNZBu_>|k++G&wZSUSI4e9J{JWK|1zhu2!iWxzVYU-0;ZdvF>?TR}8kx$rYO!sloy z=rJbg=x8>CFY-_;OZev`t|xHQtqL1g5!}CBo6Wkw&#*RC&^j&s-Sv*K2Oj0E7Z=VB zaunfVuG^58Sn+!i4Sbgkb`7J`u~5VOw3Jy;nHl)rd3-=S=m7m&@66?I&VkI6+B{T&wL4IJCe=c^_*jxcrjrY$j|lab$Ei66smJuDmBd^fxV zt-MvHj<$hL>SGH7vpfHjZdr1)!<^?%bnzI_9+?p)q7oQ8uB(3~YAm;jRtb+#`0VBT zT=kqQlj3$OrFZCMBG^0c`9H^HjTtmAWUt_+Rjw|4Ue_!#sZBekjN1C-M|?6l^T6$J z>HDL7Ys}1IlN*Yb?1N)N%~S8Q>6Nwh9?tQV_Afa5(&bL|rw~of+9j13_2DXt-1b~D zS}BtnJ^vC?xx@0QxtHjp&nB5a@^m){nY^bA z#JzsO7{hMG(y4}__xw^>f3=-FU;ly-Sz;-rtiphU+}i4M&KCu`x$>B}Q%R!SIUJ1q z6Df}aFHc8BYWr3%J;I;+ntMkh-K4Gf)%15KnfHmK2aR7FCwvEvDj}m#A|c*9Btkj? zEaD*l;BQ`jvwen%L*m#q(8nuQC_10~%j0d{YiG3dDY$!m-1S~NDB$khv_ggqk+hzi9D0SZ z?AC;=aQ9{h9(Knan9s4Wvv8b&Jm-$ToG~;puyukXfZY-H^sw){5pcs1`0azh6iCft z=jgEql5I0%a}Ihd3AC^r@R8agp#r|W{tFUFC25IGD(Gknwh)*#>Rd8}b^v&dw#iPd zV#R3#9FSvhYH(zMhxgt}FpZ=vOdJgyjm#~K46Ofzl1$oYSIVc;Ss{>@|4P6|9*$Cx zVE>fTwieE?#7bLLxe;PJ7>5a5PDn#3E~ZIi)>!u?F_Nkk2QhGQwgdJIH;)|uHI@P#R@cA6+B&2M zHgT9tdy!28j!)?5zS|F8wkrFb1C4h--Rt4h?KE^i=%1}iNh5I`Z8#H{g*L}H|ZnC$jM%0 z7%=R*TbG6(=W|K+tlO3qf?IHR>qG42gtsJvv>@`a{gdq0AByUB?+?M<*Ww_NF&)6i zWQ&Lj_;%?Z5P@3n=gX0wec)$um0y|^))f@IOc8X2&29{_J-YRS7BK)MB+0QX9Jbl< zJqx*QZ2%nfn8x(JAK96Ox9V*tF}epBe5k374Qw4Y(J8K za1hSSU`-{i>ICIeW!}8*_cSJY{0`+dc?E3+1!^>I|So0w4HUtsv!JqKUf#C)AZ( z46QAk%zvKK23sABkU-IVS3+Je+t`RQn;62Ws~jn3WN@6YHs;MSzuQf z8`!>O)g4rrq$9Py0(X`L=3|muEPxJ3Hh;w;Vr^mIB=Iu_3Jiwy?LB?(@m^#Y80@+U zhIpvv*aCxy)Jn5wkxXn|5zhMmYji%~c2rLWxFuw5E3*Yb1$-l?-4}$ot*eEjovjVf zy~GUcx6S~-3G{QHikQRdw`RQaun+tw_iFEKK)F^h!>d_c=>FAz>670e#EK8RoG3rE zbKis#*c1HoRU@pltF*1Xi!*Qt%*4RvKRS7*m^A`*w$~Z9r%aaXfVhcSBFI5B*ajt} z<>DFkLk0x*?e29rO>B1sf-n6G1}bL#8z4|2U~mh=9_TZCmgKe!-kK+4rm& zG`{=y<$e+IrPEj^E-~u>EtWZLbAi<`&#|GC;WEz8Vj{s_t)#-hydiKGt{s&zn;ePC^ZyzoPwF zmKMykd$VpU2w%JwtYFDMVcM;fEtnUkp+uhxf-bSm3tvDM%saySFEmu$wVW&>V&r;P z1jL9t4*b%rjc+f+=0Iv(gkx6J$Ib0HSd;HI9pZ#DyW{+O2Kad{1S|yXzKA)tn-0F- zEV$`x_sy|+xC4?66n0YOx6amN`Z9uc+Qx$~{0hbk64O z;Z!hBH;`)J&wU%zZXi71GOCDx!V=tf1%j`M3I+-X(qilb2vi{z5s!+t$F4l^Wlh06 z7lil41N41ZkyAuGSXQ39^1v691oJqE?V0E2p7wv(P_LTTu1N5u5y42DK$V(3HW{pk z{RdOTRc{1Lf8r07fPJ+;XM$E01h>zC!rxkuq!OI2s~=;gxKC}fh;&p_6Gs+!?2=hV4$!CIw;VcM-0%b z8w&;W3T`9F>nZrsYT!1i{d*wD_^?Z%H83yiHHaz6rjRiH-2{Y>zIYFu=iEmtLTaCZ zL8@4ophCf}uom4Z9DIc|uyALAJ-#t2`L4QyEC zO8aW3pAvzv;LC{sRZS4qX9F7<%*h@Z@<-rgx8dOnse$2XmH#okDuR-7h>+jS2keRr zUl9$Aj02p||MQN#)h~Y4`%h70`T}=Fg)epnMjiOqsIYg!yX-Tm)dxXoVTKL%kdRGk z_^M@K+FI3rEbKNdxP}=bYdHKU7y&e52ethc8!|xGtNkNr$RSoR8caW!zk+?=sV97K zGO(Vf)&C(H%sIfvzo>vqm4WGDp5b*u$R7)<0=Hja2;`IaC*2Gp>{bOYFe6OS|56yf zt{7P1&i@M|WPSwm4Zq!C(2EWIV`0_dgq;Q=dHnETK^|Zm?dv|hd-&B1%5D?VIT1hL!kfmkOyDM3jAR4KsfsUMSs|XLF{MS_5Id@ zl=ZLf`ULO=q`*%wbn*ZE1iO@zLev(74w1V)34E<4@RJDX{-2)Yk1IDJdY%{B*LHm# z_)16M=jqV(;0uk4BpUx^5eJa*}CkayH2LWHKU;s-^8EdX{~0l}{w zOZE4K_Pde%zkDHl6(X=k*arIs`Hz}Jhy-(wjoP&>;cE+lIS2R82^;Ws7EM9~$n71C z01)wPjUR68OETQIkkI_fU>ewvhPYV~(`*OvLFSH2e@O$Y5QKX7hsnR<~ud9MGK3bMk3 zFWdvBN;lmn6}XHK7zrkn5N#X+q|H9KbAuKBy=a2jUXXrq0!2rjK=%bXR4Qa?E2p)G|-s9#G~fRV;*_KdW%fCU)HPtN#NFQ8GrbrDn}*cGhNuNnElKsOxr z474*pKOB(6%qj#3W=gQ@CJ^HBgavMNBXrse(9UVfSiK9>bk8(zjvVkUg!umOJI4BZ zzCScOEm#EDO?QL%H5opvRUm_E!TumX`=DT+19zsO)&aZ1_`p{3YXW^34;8?J>5l9@0>YLq8A<-mKXG#Wca(yn^w5%^MHKD|Y!?Ja86yIJVdx&x5ub^7 z!mfPqNzcK2J??+Y_j3}rmAe-apOjGIZ~1=BE)M3y_WVbDzq3KV+r5$R0iQbIn)F*l zNE5^7*ajos^ZHka+ZneJi5UIp=5Gmq&5aEvyzc#v2%(YGzs%G9E+4)Nkx)MI)^7oS zO&SdbB=z}wz#S>1!3z4NkR?NWV0;_AtKMzggkMOA`0f=DL1fzQ!{_$KAbgHzuugIN z#rWAt;W9ph6@tyZ-dUymwh1A<06vQ|7){=9p9*co@xbMDMg*7Zow_R=e2!)?9Jc@d z;SgqQMuaC4d3#qp_~gi7JobRUz=KPbjED;}H4VxIGr!%e9zGz`3OF|X=ljr**&?vn z|I2w^*nMFWHrV%_qda`RTChIP_RVqOx7lkEDV6y>{g0J`KV=^ivR9?H)5d{GVA}kb zR`5A%!CFoJ1<9`&Y{6_WA7MO^fk35g_XVF$56m`oZT~85vHf&qzl=gKykhU{9e4-P UfJruz5Ad%hka^zwI^^5`0C;tDe*gdg From d269a7e717c343c34a82e0d366ab8547cb1a873e Mon Sep 17 00:00:00 2001 From: haby0 Date: Thu, 15 Apr 2021 19:33:15 +0800 Subject: [PATCH 280/433] CWE-598 reduction --- .../Security/CWE/CWE-598/SensitiveGetQuery.ql | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) 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 c381595af14b..f0f2fe905f7b 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-598/SensitiveGetQuery.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-598/SensitiveGetQuery.ql @@ -2,6 +2,8 @@ * @name Sensitive GET Query * @description Use of GET request method with sensitive query strings. * @kind path-problem + * @problem.severity warning + * @precision medium * @id java/sensitive-query-with-get * @tags security * external/cwe-598 @@ -23,6 +25,16 @@ 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 @@ -64,4 +76,4 @@ from DataFlow::PathNode source, DataFlow::PathNode sink, SensitiveGetQueryConfig where c.hasFlowPath(source, sink) select sink.getNode(), source, sink, "$@ uses the GET request method to transmit sensitive information.", source.getNode(), - "This request" + "This request" \ No newline at end of file From 0e183ab4a48acc09add7ef400b535b0159a807cc Mon Sep 17 00:00:00 2001 From: haby0 Date: Thu, 15 Apr 2021 19:49:06 +0800 Subject: [PATCH 281/433] Finish comment --- .../experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll | 3 ++- .../src/experimental/Security/CWE/CWE-598/SensitiveGetQuery.ql | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) 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 8c7433fd1b69..1bef46f99d05 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll @@ -13,8 +13,9 @@ import semmle.code.java.frameworks.spring.SpringController abstract class RequestGetMethod extends Method { RequestGetMethod() { not exists(MethodAccess ma | + // Exclude apparent GET handlers that read a request entity, because this is the principle of JSONP. ma.getMethod() instanceof ServletRequestGetBodyMethod and - any(this).polyCalls*(ma.getEnclosingCallable()) + this.polyCalls*(ma.getEnclosingCallable()) ) } } 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 f0f2fe905f7b..a9528ee2f9b0 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-598/SensitiveGetQuery.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-598/SensitiveGetQuery.ql @@ -76,4 +76,4 @@ from DataFlow::PathNode source, DataFlow::PathNode sink, SensitiveGetQueryConfig where c.hasFlowPath(source, sink) select sink.getNode(), source, sink, "$@ uses the GET request method to transmit sensitive information.", source.getNode(), - "This request" \ No newline at end of file + "This request" From 7fbc62358e11ba18c4c5bbbe4f471e665c112125 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Thu, 15 Apr 2021 13:57:44 +0200 Subject: [PATCH 282/433] C++: Accept test changes after making the exprMightOverFlow predicates more sound. --- .../CWE-190/semmle/extreme/ArithmeticWithExtremeValues.expected | 1 + .../test/query-tests/Security/CWE/CWE-190/semmle/extreme/test.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/extreme/ArithmeticWithExtremeValues.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/extreme/ArithmeticWithExtremeValues.expected index cbaf5d630795..77b08e62d838 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/extreme/ArithmeticWithExtremeValues.expected +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/extreme/ArithmeticWithExtremeValues.expected @@ -3,4 +3,5 @@ | test.c:50:3:50:5 | sc3 | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.c:49:9:49:16 | 127 | Extreme value | | test.c:59:3:59:5 | sc6 | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.c:58:9:58:16 | 127 | Extreme value | | test.c:63:3:63:5 | sc8 | $@ flows to here and is used in arithmetic, potentially causing an underflow. | test.c:62:9:62:16 | - ... | Extreme value | +| test.c:75:3:75:5 | sc1 | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.c:74:9:74:16 | 127 | Extreme value | | test.c:124:9:124:9 | x | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.c:118:17:118:23 | 2147483647 | Extreme value | diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/extreme/test.c b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/extreme/test.c index 8760641c8e2d..ee2e041db3c3 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/extreme/test.c +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/extreme/test.c @@ -72,7 +72,7 @@ void test_negatives() { signed char sc1, sc2, sc3, sc4, sc5, sc6, sc7, sc8; sc1 = CHAR_MAX; - sc1 += 0; // GOOD + sc1 += 0; // GOOD [FALSE POSITIVE] sc1 += -1; // GOOD sc2 = CHAR_MIN; sc2 += -1; // BAD [NOT DETECTED] From b359205d17c6fa39c3ad36c41df9f69d237cde76 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Thu, 15 Apr 2021 13:37:11 +0200 Subject: [PATCH 283/433] Python: Add taint tests for .get() in flask --- .../frameworks/flask/TestTaint.expected | 162 +++++++++--------- .../frameworks/flask/taint_test.py | 8 + 2 files changed, 93 insertions(+), 77 deletions(-) diff --git a/python/ql/test/library-tests/frameworks/flask/TestTaint.expected b/python/ql/test/library-tests/frameworks/flask/TestTaint.expected index 8c3b4ea57c9d..1d514b934e2b 100644 --- a/python/ql/test/library-tests/frameworks/flask/TestTaint.expected +++ b/python/ql/test/library-tests/frameworks/flask/TestTaint.expected @@ -19,80 +19,88 @@ | taint_test.py:36 | ok | test_taint | request.access_route[0] | | taint_test.py:39 | ok | test_taint | request.args | | taint_test.py:40 | ok | test_taint | request.args['key'] | -| taint_test.py:41 | ok | test_taint | request.args.getlist(..) | -| taint_test.py:44 | ok | test_taint | request.authorization | -| taint_test.py:45 | ok | test_taint | request.authorization['username'] | -| taint_test.py:46 | fail | test_taint | request.authorization.username | -| taint_test.py:49 | ok | test_taint | request.cache_control | -| taint_test.py:51 | fail | test_taint | request.cache_control.max_age | -| taint_test.py:52 | fail | test_taint | request.cache_control.max_stale | -| taint_test.py:53 | fail | test_taint | request.cache_control.min_fresh | -| taint_test.py:55 | ok | test_taint | request.content_encoding | -| taint_test.py:57 | ok | test_taint | request.content_md5 | -| taint_test.py:59 | ok | test_taint | request.content_type | -| taint_test.py:62 | ok | test_taint | request.cookies | -| taint_test.py:63 | ok | test_taint | request.cookies['key'] | -| taint_test.py:65 | ok | test_taint | request.data | -| taint_test.py:68 | ok | test_taint | request.files | -| taint_test.py:69 | ok | test_taint | request.files['key'] | -| taint_test.py:70 | fail | test_taint | request.files['key'].filename | -| taint_test.py:71 | fail | test_taint | request.files['key'].stream | -| taint_test.py:72 | ok | test_taint | request.files.getlist(..) | -| taint_test.py:73 | fail | test_taint | request.files.getlist(..)[0].filename | -| taint_test.py:74 | fail | test_taint | request.files.getlist(..)[0].stream | -| taint_test.py:77 | ok | test_taint | request.form | -| taint_test.py:78 | ok | test_taint | request.form['key'] | -| taint_test.py:79 | ok | test_taint | request.form.getlist(..) | -| taint_test.py:81 | ok | test_taint | request.get_data() | -| taint_test.py:83 | ok | test_taint | request.get_json() | -| taint_test.py:84 | ok | test_taint | request.get_json()['foo'] | -| taint_test.py:85 | ok | test_taint | request.get_json()['foo']['bar'] | -| taint_test.py:89 | ok | test_taint | request.headers | -| taint_test.py:90 | ok | test_taint | request.headers['key'] | -| taint_test.py:91 | fail | test_taint | request.headers.get_all(..) | -| taint_test.py:92 | fail | test_taint | request.headers.getlist(..) | -| taint_test.py:93 | ok | test_taint | list(..) | -| taint_test.py:94 | fail | test_taint | request.headers.to_wsgi_list() | -| taint_test.py:96 | ok | test_taint | request.json | -| taint_test.py:97 | ok | test_taint | request.json['foo'] | -| taint_test.py:98 | ok | test_taint | request.json['foo']['bar'] | -| taint_test.py:100 | ok | test_taint | request.method | -| taint_test.py:102 | ok | test_taint | request.mimetype | -| taint_test.py:104 | ok | test_taint | request.mimetype_params | -| taint_test.py:106 | ok | test_taint | request.origin | -| taint_test.py:109 | ok | test_taint | request.pragma | -| taint_test.py:111 | ok | test_taint | request.query_string | -| taint_test.py:113 | ok | test_taint | request.referrer | -| taint_test.py:115 | ok | test_taint | request.remote_addr | -| taint_test.py:117 | ok | test_taint | request.remote_user | -| taint_test.py:120 | ok | test_taint | request.stream | -| taint_test.py:121 | ok | test_taint | request.input_stream | -| taint_test.py:123 | ok | test_taint | request.url | -| taint_test.py:125 | ok | test_taint | request.user_agent | -| taint_test.py:128 | ok | test_taint | request.values | -| taint_test.py:129 | ok | test_taint | request.values['key'] | -| taint_test.py:130 | ok | test_taint | request.values.getlist(..) | -| taint_test.py:133 | ok | test_taint | request.view_args | -| taint_test.py:134 | ok | test_taint | request.view_args['key'] | -| taint_test.py:138 | ok | test_taint | request.script_root | -| taint_test.py:139 | ok | test_taint | request.url_root | -| taint_test.py:143 | ok | test_taint | request.charset | -| taint_test.py:144 | ok | test_taint | request.url_charset | -| taint_test.py:148 | ok | test_taint | request.date | -| taint_test.py:151 | ok | test_taint | request.endpoint | -| taint_test.py:156 | ok | test_taint | request.host | -| taint_test.py:157 | ok | test_taint | request.host_url | -| taint_test.py:159 | ok | test_taint | request.scheme | -| taint_test.py:161 | ok | test_taint | request.script_root | -| taint_test.py:169 | ok | test_taint | request.args | -| taint_test.py:170 | ok | test_taint | a | -| taint_test.py:171 | ok | test_taint | b | -| taint_test.py:173 | ok | test_taint | request.args['key'] | -| taint_test.py:174 | ok | test_taint | a['key'] | -| taint_test.py:175 | ok | test_taint | b['key'] | -| taint_test.py:177 | ok | test_taint | request.args.getlist(..) | -| taint_test.py:178 | ok | test_taint | a.getlist(..) | -| taint_test.py:179 | ok | test_taint | b.getlist(..) | -| taint_test.py:180 | ok | test_taint | gl(..) | -| taint_test.py:187 | ok | test_taint | req.path | -| taint_test.py:188 | ok | test_taint | gd() | +| taint_test.py:41 | ok | test_taint | request.args.get(..) | +| taint_test.py:42 | ok | test_taint | request.args.getlist(..) | +| taint_test.py:45 | ok | test_taint | request.authorization | +| taint_test.py:46 | ok | test_taint | request.authorization['username'] | +| taint_test.py:47 | fail | test_taint | request.authorization.username | +| taint_test.py:50 | ok | test_taint | request.cache_control | +| taint_test.py:52 | fail | test_taint | request.cache_control.max_age | +| taint_test.py:53 | fail | test_taint | request.cache_control.max_stale | +| taint_test.py:54 | fail | test_taint | request.cache_control.min_fresh | +| taint_test.py:56 | ok | test_taint | request.content_encoding | +| taint_test.py:58 | ok | test_taint | request.content_md5 | +| taint_test.py:60 | ok | test_taint | request.content_type | +| taint_test.py:63 | ok | test_taint | request.cookies | +| taint_test.py:64 | ok | test_taint | request.cookies['key'] | +| taint_test.py:66 | ok | test_taint | request.data | +| taint_test.py:69 | ok | test_taint | request.files | +| taint_test.py:70 | ok | test_taint | request.files['key'] | +| taint_test.py:71 | fail | test_taint | request.files['key'].filename | +| taint_test.py:72 | fail | test_taint | request.files['key'].stream | +| taint_test.py:73 | ok | test_taint | request.files.get(..) | +| taint_test.py:74 | fail | test_taint | request.files.get(..).filename | +| taint_test.py:75 | fail | test_taint | request.files.get(..).stream | +| taint_test.py:76 | ok | test_taint | request.files.getlist(..) | +| taint_test.py:77 | fail | test_taint | request.files.getlist(..)[0].filename | +| taint_test.py:78 | fail | test_taint | request.files.getlist(..)[0].stream | +| taint_test.py:81 | ok | test_taint | request.form | +| taint_test.py:82 | ok | test_taint | request.form['key'] | +| taint_test.py:83 | ok | test_taint | request.form.get(..) | +| taint_test.py:84 | ok | test_taint | request.form.getlist(..) | +| taint_test.py:86 | ok | test_taint | request.get_data() | +| taint_test.py:88 | ok | test_taint | request.get_json() | +| taint_test.py:89 | ok | test_taint | request.get_json()['foo'] | +| taint_test.py:90 | ok | test_taint | request.get_json()['foo']['bar'] | +| taint_test.py:94 | ok | test_taint | request.headers | +| taint_test.py:95 | ok | test_taint | request.headers['key'] | +| taint_test.py:96 | ok | test_taint | request.headers.get(..) | +| taint_test.py:97 | fail | test_taint | request.headers.get_all(..) | +| taint_test.py:98 | fail | test_taint | request.headers.getlist(..) | +| taint_test.py:99 | ok | test_taint | list(..) | +| taint_test.py:100 | fail | test_taint | request.headers.to_wsgi_list() | +| taint_test.py:102 | ok | test_taint | request.json | +| taint_test.py:103 | ok | test_taint | request.json['foo'] | +| taint_test.py:104 | ok | test_taint | request.json['foo']['bar'] | +| taint_test.py:106 | ok | test_taint | request.method | +| taint_test.py:108 | ok | test_taint | request.mimetype | +| taint_test.py:110 | ok | test_taint | request.mimetype_params | +| taint_test.py:112 | ok | test_taint | request.origin | +| taint_test.py:115 | ok | test_taint | request.pragma | +| taint_test.py:117 | ok | test_taint | request.query_string | +| taint_test.py:119 | ok | test_taint | request.referrer | +| taint_test.py:121 | ok | test_taint | request.remote_addr | +| taint_test.py:123 | ok | test_taint | request.remote_user | +| taint_test.py:126 | ok | test_taint | request.stream | +| taint_test.py:127 | ok | test_taint | request.input_stream | +| taint_test.py:129 | ok | test_taint | request.url | +| taint_test.py:131 | ok | test_taint | request.user_agent | +| taint_test.py:134 | ok | test_taint | request.values | +| taint_test.py:135 | ok | test_taint | request.values['key'] | +| taint_test.py:136 | ok | test_taint | request.values.get(..) | +| taint_test.py:137 | ok | test_taint | request.values.getlist(..) | +| taint_test.py:140 | ok | test_taint | request.view_args | +| taint_test.py:141 | ok | test_taint | request.view_args['key'] | +| taint_test.py:142 | ok | test_taint | request.view_args.get(..) | +| taint_test.py:146 | ok | test_taint | request.script_root | +| taint_test.py:147 | ok | test_taint | request.url_root | +| taint_test.py:151 | ok | test_taint | request.charset | +| taint_test.py:152 | ok | test_taint | request.url_charset | +| taint_test.py:156 | ok | test_taint | request.date | +| taint_test.py:159 | ok | test_taint | request.endpoint | +| taint_test.py:164 | ok | test_taint | request.host | +| taint_test.py:165 | ok | test_taint | request.host_url | +| taint_test.py:167 | ok | test_taint | request.scheme | +| taint_test.py:169 | ok | test_taint | request.script_root | +| taint_test.py:177 | ok | test_taint | request.args | +| taint_test.py:178 | ok | test_taint | a | +| taint_test.py:179 | ok | test_taint | b | +| taint_test.py:181 | ok | test_taint | request.args['key'] | +| taint_test.py:182 | ok | test_taint | a['key'] | +| taint_test.py:183 | ok | test_taint | b['key'] | +| taint_test.py:185 | ok | test_taint | request.args.getlist(..) | +| taint_test.py:186 | ok | test_taint | a.getlist(..) | +| taint_test.py:187 | ok | test_taint | b.getlist(..) | +| taint_test.py:188 | ok | test_taint | gl(..) | +| taint_test.py:195 | ok | test_taint | req.path | +| taint_test.py:196 | ok | test_taint | gd() | diff --git a/python/ql/test/library-tests/frameworks/flask/taint_test.py b/python/ql/test/library-tests/frameworks/flask/taint_test.py index 31c0c7e6121b..986bbcfec79e 100644 --- a/python/ql/test/library-tests/frameworks/flask/taint_test.py +++ b/python/ql/test/library-tests/frameworks/flask/taint_test.py @@ -38,6 +38,7 @@ def test_taint(name = "World!", number="0", foo="foo"): # $requestHandler route # By default werkzeug.datastructures.ImmutableMultiDict -- although can be changed :\ request.args, request.args['key'], + request.args.get('key'), request.args.getlist('key'), # werkzeug.datastructures.Authorization (a dict, with some properties) @@ -69,6 +70,9 @@ def test_taint(name = "World!", number="0", foo="foo"): # $requestHandler route request.files['key'], request.files['key'].filename, request.files['key'].stream, + request.files.get('key'), + request.files.get('key').filename, + request.files.get('key').stream, request.files.getlist('key'), request.files.getlist('key')[0].filename, request.files.getlist('key')[0].stream, @@ -76,6 +80,7 @@ def test_taint(name = "World!", number="0", foo="foo"): # $requestHandler route # By default werkzeug.datastructures.ImmutableMultiDict -- although can be changed :\ request.form, request.form['key'], + request.form.get('key'), request.form.getlist('key'), request.get_data(), @@ -88,6 +93,7 @@ def test_taint(name = "World!", number="0", foo="foo"): # $requestHandler route # which has same interface as werkzeug.datastructures.Headers request.headers, request.headers['key'], + request.headers.get('key'), request.headers.get_all('key'), request.headers.getlist('key'), list(request.headers), # (k, v) list @@ -127,11 +133,13 @@ def test_taint(name = "World!", number="0", foo="foo"): # $requestHandler route # werkzeug.datastructures.CombinedMultiDict, which is basically just a werkzeug.datastructures.MultiDict request.values, request.values['key'], + request.values.get('key'), request.values.getlist('key'), # dict request.view_args, request.view_args['key'], + request.view_args.get('key'), ) ensure_not_tainted( From dedf7655422036df96a3c1ffdd58e5017189d81f Mon Sep 17 00:00:00 2001 From: haby0 Date: Thu, 15 Apr 2021 22:59:22 +0800 Subject: [PATCH 284/433] Update java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll Co-authored-by: Chris Smowton --- .../experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 1bef46f99d05..6da4f87294a5 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll @@ -13,7 +13,10 @@ import semmle.code.java.frameworks.spring.SpringController abstract class RequestGetMethod extends Method { RequestGetMethod() { not exists(MethodAccess ma | - // Exclude apparent GET handlers that read a request entity, because this is the principle of JSONP. + // Exclude apparent GET handlers that read a request entity, because this likely indicates this is not in fact a GET handler. + // This is particularly a problem with Spring handlers, which can sometimes neglect to specify a request method. + // Even if it is in fact a GET handler, such a request method will be unusable in the context `