├── .DS_Store ├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .gitignore ├── .gitmodules ├── EkoParty2021_slides.pdf ├── HacktivityCon2021_slides.pdf ├── LICENSE ├── README.md ├── databases └── dubbo_2.7.8.zip ├── exercises ├── HelloWorld.ql ├── dubbo.qll ├── exercise1.ql ├── exercise2.ql ├── exercise3.ql ├── exercise4.ql ├── exercise5.ql ├── exercise6.ql ├── exercise7.ql ├── exercise8.ql ├── models.qll ├── playground.ql ├── qlpack.yml └── reverse_partial_dataflow.ql ├── workshop.md └── workspace.code-workspace /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/github/codeql-dubbo-workshop/c2f3da0de188328d5771218b9b1492082bdff299/.DS_Store -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.203.0/containers/python-3/.devcontainer/base.Dockerfile 2 | 3 | # [Choice] Python version (use -bullseye variants on local arm64/Apple Silicon): 3, 3.10, 3.9, 3.8, 3.7, 3.6, 3-bullseye, 3.10-bullseye, 3.9-bullseye, 3.8-bullseye, 3.7-bullseye, 3.6-bullseye, 3-buster, 3.10-buster, 3.9-buster, 3.8-buster, 3.7-buster, 3.6-buster 4 | ARG VARIANT="3.7" 5 | FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT} 6 | 7 | # [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 8 | ARG NODE_VERSION="none" 9 | RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi 10 | 11 | # [Optional] If your pip requirements rarely change, uncomment this section to add them to the image. 12 | # COPY requirements.txt /tmp/pip-tmp/ 13 | # RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \ 14 | # && rm -rf /tmp/pip-tmp 15 | 16 | # [Optional] Uncomment this section to install additional OS packages. 17 | # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 18 | # && apt-get -y install --no-install-recommends 19 | 20 | # [Optional] Uncomment this line to install global node packages. 21 | # RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 22 | 23 | RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 24 | && apt-get -y install --no-install-recommends fuse 25 | RUN curl -OL https://github.com/github/codeql-cli-binaries/releases/download/v2.7.2/codeql-linux64.zip && \ 26 | unzip codeql-linux64.zip && \ 27 | mv codeql /opt 28 | ENV PATH="/opt/codeql:${PATH}" -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: 2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.203.0/containers/python-3 3 | { 4 | "name": "Python 3", 5 | "runArgs": ["--init", "--privileged"], 6 | // privileged is used to be able to run libfuse which is required by nvim appImage 7 | // perhaps its enough with `--cap-add SYS_ADMIN --device /dev/fuse` or additionally `--security-opt apparmor:unconfined` but need to try it 8 | "postCreateCommand": "git submodule update --init", 9 | "build": { 10 | "dockerfile": "Dockerfile", 11 | "context": "..", 12 | "args": { 13 | // Update 'VARIANT' to pick a Python version: 3, 3.10, 3.9, 3.8, 3.7, 3.6 14 | // Append -bullseye or -buster to pin to an OS version. 15 | // Use -bullseye variants on local on arm64/Apple Silicon. 16 | "VARIANT": "3.7", 17 | // Options 18 | "NODE_VERSION": "lts/*" 19 | } 20 | }, 21 | 22 | // Set *default* container specific settings.json values on container create. 23 | "settings": { 24 | "python.pythonPath": "/usr/local/bin/python", 25 | "python.languageServer": "Pylance", 26 | "python.linting.enabled": true, 27 | "python.linting.pylintEnabled": true, 28 | "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", 29 | "python.formatting.blackPath": "/usr/local/py-utils/bin/black", 30 | "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", 31 | "python.linting.banditPath": "/usr/local/py-utils/bin/bandit", 32 | "python.linting.flake8Path": "/usr/local/py-utils/bin/flake8", 33 | "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy", 34 | "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle", 35 | "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle", 36 | "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint" 37 | }, 38 | 39 | // Add the IDs of extensions you want installed when the container is created. 40 | "extensions": [ 41 | "ms-python.python", 42 | "ms-python.vscode-pylance" 43 | ], 44 | 45 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 46 | // "forwardPorts": [], 47 | 48 | // Use 'postCreateCommand' to run commands after the container is created. 49 | // "postCreateCommand": "pip3 install --user -r requirements.txt", 50 | 51 | // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. 52 | "remoteUser": "vscode", 53 | "features": { 54 | "git": "os-provided", 55 | "github-cli": "latest", 56 | "sshd": "latest", 57 | "homebrew": "latest", 58 | "java": "lts", 59 | "maven": "latest" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | .vscode/settings.json 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "codeql"] 2 | path = codeql 3 | url = https://github.com/github/codeql.git 4 | -------------------------------------------------------------------------------- /EkoParty2021_slides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/github/codeql-dubbo-workshop/c2f3da0de188328d5771218b9b1492082bdff299/EkoParty2021_slides.pdf -------------------------------------------------------------------------------- /HacktivityCon2021_slides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/github/codeql-dubbo-workshop/c2f3da0de188328d5771218b9b1492082bdff299/HacktivityCon2021_slides.pdf -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 GitHub 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CodeQL as an audit oracle: Dubbo Workshop 2 | 3 | 5th and 6th October 2021 4 | 5 | ## Preparation 6 | 7 | For this workshop, you are invited to actively participate by writing CodeQL queries in Visual Studio Code. To do this, you will need to have Visual Studio Code up and running. 8 | 9 | ### Running locally 10 | 11 | 1. Install Visual Studio Code. 12 | 13 | 2. Clone the repository (https://github.com/github/codeql-dubbo-workshop) locally. Make sure to get the submodules. For example with the command line 14 | 15 | ``` 16 | git clone --recursive https://github.com/github/codeql-dubbo-workshop.git 17 | ``` 18 | 19 | 3. Open the repository folder in Visual Studio Code. 20 | 21 | 4. Install the CodeQL extension for Visual Studio Code, from the Visual Studio Code extensions marketplace. (Use the "Extensions" icon on the left of Visual Studio Code). 22 | 23 | 5. Click on the CodeQL icon on the left, dismiss the dialog if needed, then select "Add a CodeQL database/From an archive". Navigate to the `databases` folder and select `dubbo_2.7.8.zip`. 24 | 25 | 6. Go back to the CodeQL view (click on the CodeQL icon on the left if necessary). Hover over the database and select "Set Current Database". 26 | 27 | 7. Open the file `HelloWorld.ql` in VScode. (Use the Explorer icon on the left of Visual Studio Code, and locate the file in the root of the repository). 28 | 29 | 8. Right-click on the file, and select "CodeQL: Run query". You should see the "CodeQL Query Results" window on the right hand side. 30 | 31 | 9. Proceed to the [main content](workshop.md). 32 | 33 | 34 | ## :books: Resources 35 | - For more advanced CodeQL development in future, you may wish to set up the [CodeQL starter workspace](https://codeql.github.com/docs/codeql-for-visual-studio-code/setting-up-codeql-in-visual-studio-code/#using-the-starter-workspace) for all languages. 36 | - [CodeQL overview](https://codeql.github.com/docs/codeql-overview/) 37 | - [CodeQL for Java](https://codeql.github.com/docs/codeql-language-guides/codeql-for-java/) 38 | - [Analyzing data flow in Java](https://codeql.github.com/docs/codeql-language-guides/analyzing-data-flow-in-java/) 39 | - [Using the CodeQL extension for VS Code](https://codeql.github.com/docs/codeql-for-visual-studio-code/) 40 | - CodeQL on [GitHub Learning Lab](https://lab.github.com/search?q=codeql) 41 | - CodeQL on [GitHub Security Lab](https://codeql.com) 42 | -------------------------------------------------------------------------------- /databases/dubbo_2.7.8.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/github/codeql-dubbo-workshop/c2f3da0de188328d5771218b9b1492082bdff299/databases/dubbo_2.7.8.zip -------------------------------------------------------------------------------- /exercises/HelloWorld.ql: -------------------------------------------------------------------------------- 1 | import java 2 | 3 | select "Hola, EkoParty!" 4 | -------------------------------------------------------------------------------- /exercises/dubbo.qll: -------------------------------------------------------------------------------- 1 | import java 2 | import semmle.code.java.dataflow.FlowSources 3 | 4 | class NotifyListener extends RefType { 5 | NotifyListener() { 6 | this.hasQualifiedName("org.apache.dubbo.registry", "NotifyListener") 7 | } 8 | } 9 | 10 | class ConfigurationListener extends RefType { 11 | ConfigurationListener() { 12 | this.hasQualifiedName("org.apache.dubbo.common.config.configcenter", "ConfigurationListener") 13 | } 14 | } 15 | 16 | class ConfigurationListenerProcessMethod extends Method { 17 | ConfigurationListenerProcessMethod() { 18 | this.getName() = "process" and 19 | this.getDeclaringType().getASupertype*() instanceof ConfigurationListener 20 | } 21 | } 22 | 23 | class NotifyListenerNotifyMethod extends Method { 24 | NotifyListenerNotifyMethod() { 25 | this.getName() = "notify" and 26 | this.getDeclaringType().getASupertype*() instanceof NotifyListener 27 | } 28 | } 29 | 30 | class DubboListener extends RemoteFlowSource { 31 | DubboListener() { 32 | (exists(NotifyListenerNotifyMethod m | 33 | this.asParameter() = m.getAParameter() 34 | ) or 35 | exists(ConfigurationListenerProcessMethod m | 36 | this.asParameter() = m.getAParameter() 37 | )) and 38 | not this.getLocation().getFile().getRelativePath().matches("%/src/test/%") 39 | } 40 | override string getSourceType() { result = "Dubbo Listener Source" } 41 | } 42 | 43 | /** Dubbo URL taint steps */ 44 | class URLTaintStep extends TaintTracking::AdditionalTaintStep { 45 | override predicate step(DataFlow::Node n1, DataFlow::Node n2) { 46 | exists(MethodAccess ma | 47 | ma.getMethod().getName().matches("get%") and 48 | ma.getMethod().getDeclaringType().hasQualifiedName("org.apache.dubbo.common", "URL") and 49 | n1.asExpr() = ma.getQualifier() and 50 | n2.asExpr() = ma 51 | ) 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /exercises/exercise1.ql: -------------------------------------------------------------------------------- 1 | import java 2 | import semmle.code.java.dataflow.FlowSources 3 | 4 | from RemoteFlowSource source 5 | where 6 | not source.getLocation().getFile().getRelativePath().matches("%/src/test/%") 7 | select 8 | source, 9 | source.getEnclosingCallable().getDeclaringType(), 10 | source.getLocation().getFile().getBaseName(), 11 | source.getSourceType() -------------------------------------------------------------------------------- /exercises/exercise2.ql: -------------------------------------------------------------------------------- 1 | import java 2 | import semmle.code.java.dataflow.FlowSources 3 | 4 | /** The ChannelInboundHandler class */ 5 | class ChannelInboundHandler extends Class { 6 | ChannelInboundHandler() { 7 | this.getASourceSupertype*().hasQualifiedName("io.netty.channel", "ChannelInboundHandler") 8 | } 9 | } 10 | 11 | /** The ChannelInboundHandlerl.channelRead method */ 12 | class ChannelReadMethod extends Method { 13 | ChannelReadMethod() { 14 | this.getName() = ["channelRead", "channelRead0", "messageReceived"] and 15 | this.getDeclaringType() instanceof ChannelInboundHandler 16 | } 17 | } 18 | 19 | /** The ByteToMessageDecoder class */ 20 | class ByteToMessageDecoder extends Class { 21 | ByteToMessageDecoder() { 22 | this.getASourceSupertype*().hasQualifiedName("io.netty.handler.codec", "ByteToMessageDecoder") 23 | } 24 | } 25 | 26 | /** The ByteToMessageDecoder.decode method */ 27 | class DecodeMethod extends Method { 28 | DecodeMethod() { 29 | this.getName() = ["decode", "decodeLast"] and 30 | this.getDeclaringType() instanceof ByteToMessageDecoder 31 | } 32 | } 33 | 34 | /** The ChannelInboundHandlerl.channelRead(1) source */ 35 | class ChannelReadSource extends RemoteFlowSource { 36 | ChannelReadSource() { 37 | exists(ChannelReadMethod m | 38 | this.asParameter() = m.getParameter(1) 39 | ) 40 | } 41 | override string getSourceType() { result = "Netty Handler Source" } 42 | } 43 | 44 | /** The ByteToMessageDecoder.decode(1) source */ 45 | class DecodeSource extends RemoteFlowSource { 46 | DecodeSource() { 47 | exists(DecodeMethod m | 48 | this.asParameter() = m.getParameter(1) 49 | ) 50 | } 51 | override string getSourceType() { result = "Netty Decoder Source" } 52 | } 53 | 54 | from RemoteFlowSource source 55 | where 56 | ( 57 | source instanceof ChannelReadSource or 58 | source instanceof DecodeSource 59 | ) and 60 | not source.getLocation().getFile().getRelativePath().matches("%/src/test/%") 61 | select 62 | source, 63 | source.getEnclosingCallable().getDeclaringType(), 64 | source.getSourceType() -------------------------------------------------------------------------------- /exercises/exercise3.ql: -------------------------------------------------------------------------------- 1 | /** 2 | * @kind path-problem 3 | */ 4 | import java 5 | import semmle.code.java.dataflow.TaintTracking 6 | import DataFlow::PathGraph 7 | 8 | 9 | class DubboCodecDecodeBodyMethod extends Method { 10 | DubboCodecDecodeBodyMethod() { 11 | this.getName() = "decodeBody" and 12 | this.getDeclaringType().hasQualifiedName("org.apache.dubbo.rpc.protocol.dubbo", "DubboCodec") 13 | } 14 | } 15 | 16 | class ObjectInputReadMethod extends Method { 17 | ObjectInputReadMethod() { 18 | this.getName().matches("read%") and 19 | this.getDeclaringType() 20 | .getASourceSupertype*() 21 | .hasQualifiedName("org.apache.dubbo.common.serialize", "ObjectInput") 22 | } 23 | } 24 | 25 | class SerializationDeserializeMethod extends Method { 26 | SerializationDeserializeMethod() { 27 | this.getName() = "deserialize" and 28 | this.getDeclaringType().hasQualifiedName("org.apache.dubbo.common.serialize", "Serialization") 29 | } 30 | } 31 | 32 | class InsecureConfig extends TaintTracking::Configuration { 33 | InsecureConfig() { this = "InsecureConfig" } 34 | 35 | override predicate isSource(DataFlow::Node source) { 36 | exists(DubboCodecDecodeBodyMethod m | 37 | m.getParameter([1,2]) = source.asParameter() 38 | ) 39 | } 40 | 41 | override predicate isSink(DataFlow::Node sink) { 42 | exists(MethodAccess ma | 43 | ma.getMethod() instanceof ObjectInputReadMethod and 44 | ma.getQualifier() = sink.asExpr() 45 | ) 46 | } 47 | 48 | override predicate isAdditionalTaintStep(DataFlow::Node n1, DataFlow::Node n2) { 49 | exists(MethodAccess ma | 50 | ma.getMethod() instanceof SerializationDeserializeMethod and 51 | ma.getArgument(1) = n1.asExpr() and 52 | ma = n2.asExpr() 53 | ) 54 | } 55 | } 56 | 57 | from InsecureConfig conf, DataFlow::PathNode source, DataFlow::PathNode sink 58 | where conf.hasFlowPath(source, sink) 59 | select sink, source, sink, "unsafe deserialization" 60 | -------------------------------------------------------------------------------- /exercises/exercise4.ql: -------------------------------------------------------------------------------- 1 | import java 2 | 3 | class ObjectInputClass extends RefType { 4 | ObjectInputClass() { 5 | this.getASourceSupertype*().hasQualifiedName("org.apache.dubbo.common.serialize", "ObjectInput") 6 | } 7 | } 8 | 9 | class ObjectInputReadCall extends MethodAccess { 10 | ObjectInputReadCall() { 11 | exists(Method m | 12 | this.getMethod() = m and 13 | m.getName().matches("read%") and 14 | m.getDeclaringType() instanceof ObjectInputClass 15 | ) 16 | } 17 | } 18 | 19 | from ObjectInputReadCall call 20 | where 21 | not call.getEnclosingCallable().getDeclaringType() instanceof ObjectInputClass and 22 | not call.getLocation().getFile().getRelativePath().matches("%/src/test/%") 23 | select 24 | call, 25 | call.getEnclosingCallable(), 26 | call.getEnclosingCallable().getDeclaringType() -------------------------------------------------------------------------------- /exercises/exercise5.ql: -------------------------------------------------------------------------------- 1 | import java 2 | 3 | class PojoUtilsRealizeMethod extends Method { 4 | PojoUtilsRealizeMethod() { 5 | this.getName() = "realize" and 6 | this.getDeclaringType().getName() = "PojoUtils" 7 | } 8 | } 9 | 10 | class JavaBeanSerializeUtilDeserializeMethod extends Method { 11 | JavaBeanSerializeUtilDeserializeMethod() { 12 | this.getName() = "deserialize" and 13 | this.getDeclaringType().getName() = "JavaBeanSerializeUtil" 14 | } 15 | } 16 | 17 | from MethodAccess ma 18 | where 19 | ( 20 | ma.getMethod() instanceof PojoUtilsRealizeMethod or 21 | ma.getMethod() instanceof JavaBeanSerializeUtilDeserializeMethod 22 | ) and 23 | not ma.getEnclosingCallable().getDeclaringType() = ma.getMethod().getDeclaringType() and 24 | not ma.getLocation().getFile().getRelativePath().matches("%/src/test/%") 25 | select ma, ma.getEnclosingCallable().getDeclaringType() -------------------------------------------------------------------------------- /exercises/exercise6.ql: -------------------------------------------------------------------------------- 1 | import java 2 | import semmle.code.java.security.UnsafeDeserializationQuery 3 | 4 | from UnsafeDeserializationSink node 5 | where 6 | not node.getLocation().getFile().getRelativePath().matches("%/src/test/%") 7 | select 8 | node.asExpr().getParent().(Call).getCallee().getDeclaringType(), // deserializing class 9 | node.asExpr().getParent(), // deserializing method 10 | node.asExpr().getParent().(Call).getEnclosingCallable().getDeclaringType() // enclosing class 11 | -------------------------------------------------------------------------------- /exercises/exercise7.ql: -------------------------------------------------------------------------------- 1 | import java 2 | import semmle.code.java.dataflow.FlowSources 3 | 4 | class NotifyListener extends RefType { 5 | NotifyListener() { 6 | this.hasQualifiedName("org.apache.dubbo.registry", "NotifyListener") 7 | } 8 | } 9 | 10 | class ConfigurationListener extends RefType { 11 | ConfigurationListener() { 12 | this.hasQualifiedName("org.apache.dubbo.common.config.configcenter", "ConfigurationListener") 13 | } 14 | } 15 | 16 | class ConfigurationListenerProcessMethod extends Method { 17 | ConfigurationListenerProcessMethod() { 18 | this.getName() = "process" and 19 | this.getDeclaringType().getASupertype*() instanceof ConfigurationListener 20 | } 21 | } 22 | 23 | class NotifyListenerNotifyMethod extends Method { 24 | NotifyListenerNotifyMethod() { 25 | this.getName() = "notify" and 26 | this.getDeclaringType().getASupertype*() instanceof NotifyListener 27 | } 28 | } 29 | 30 | class DubboListener extends RemoteFlowSource { 31 | DubboListener() { 32 | (exists(NotifyListenerNotifyMethod m | 33 | this.asParameter() = m.getAParameter() 34 | ) or 35 | exists(ConfigurationListenerProcessMethod m | 36 | this.asParameter() = m.getAParameter() 37 | )) and 38 | not this.getLocation().getFile().getRelativePath().matches("%/src/test/%") 39 | } 40 | override string getSourceType() { result = "Dubbo Listener Source" } 41 | } 42 | 43 | from DubboListener l 44 | select 45 | l, 46 | l.asParameter().getCallable(), 47 | l.asParameter().getCallable().getDeclaringType() 48 | 49 | 50 | -------------------------------------------------------------------------------- /exercises/exercise8.ql: -------------------------------------------------------------------------------- 1 | /** 2 | * @name Injection in Java Script Engine 3 | * @description Evaluation of user-controlled data using the Java Script Engine may 4 | * lead to remote code execution. 5 | * @kind path-problem 6 | * @problem.severity error 7 | * @precision high 8 | * @id java/unsafe-eval 9 | * @tags security 10 | * external/cwe/cwe-094 11 | */ 12 | 13 | import java 14 | import semmle.code.java.dataflow.FlowSources 15 | import DataFlow::PathGraph 16 | import dubbo 17 | import models 18 | 19 | /** A method of ScriptEngine that allows code injection. */ 20 | class ScriptEngineMethod extends Method { 21 | ScriptEngineMethod() { 22 | this.getDeclaringType().getASupertype*().hasQualifiedName("javax.script", "ScriptEngine") and 23 | this.hasName("eval") 24 | or 25 | this.getDeclaringType().getASupertype*().hasQualifiedName("javax.script", "Compilable") and 26 | this.hasName("compile") 27 | or 28 | this.getDeclaringType().getASupertype*().hasQualifiedName("javax.script", "ScriptEngineFactory") and 29 | this.hasName(["getProgram", "getMethodCallSyntax"]) 30 | } 31 | } 32 | 33 | /** The context class `org.mozilla.javascript.Context` of Rhino Java Script Engine. */ 34 | class RhinoContext extends RefType { 35 | RhinoContext() { this.hasQualifiedName("org.mozilla.javascript", "Context") } 36 | } 37 | 38 | /** A method that evaluates a Rhino expression with `org.mozilla.javascript.Context`. */ 39 | class RhinoEvaluateExpressionMethod extends Method { 40 | RhinoEvaluateExpressionMethod() { 41 | this.getDeclaringType().getAnAncestor*() instanceof RhinoContext and 42 | this.hasName([ 43 | "evaluateString", "evaluateReader", "compileFunction", "compileReader", "compileString" 44 | ]) 45 | } 46 | } 47 | 48 | /** 49 | * A method that compiles a Rhino expression with 50 | * `org.mozilla.javascript.optimizer.ClassCompiler`. 51 | */ 52 | class RhinoCompileClassMethod extends Method { 53 | RhinoCompileClassMethod() { 54 | this.getDeclaringType() 55 | .getASupertype*() 56 | .hasQualifiedName("org.mozilla.javascript.optimizer", "ClassCompiler") and 57 | this.hasName("compileToClassFiles") 58 | } 59 | } 60 | 61 | /** 62 | * A method that defines a Java class from a Rhino expression with 63 | * `org.mozilla.javascript.GeneratedClassLoader`. 64 | */ 65 | class RhinoDefineClassMethod extends Method { 66 | RhinoDefineClassMethod() { 67 | this.getDeclaringType() 68 | .getASupertype*() 69 | .hasQualifiedName("org.mozilla.javascript", "GeneratedClassLoader") and 70 | this.hasName("defineClass") 71 | } 72 | } 73 | 74 | /** 75 | * Holds if `ma` is a call to a `ScriptEngineMethod` and `sink` is an argument that 76 | * will be executed. 77 | */ 78 | predicate isScriptArgument(MethodAccess ma, Expr sink) { 79 | exists(ScriptEngineMethod m | 80 | m = ma.getMethod() and 81 | if m.getDeclaringType().getASupertype*().hasQualifiedName("javax.script", "ScriptEngineFactory") 82 | then sink = ma.getArgument(_) // all arguments allow script injection 83 | else sink = ma.getArgument(0) 84 | ) 85 | } 86 | 87 | /** 88 | * Holds if a Rhino expression evaluation method is vulnerable to code injection. 89 | */ 90 | predicate evaluatesRhinoExpression(MethodAccess ma, Expr sink) { 91 | exists(RhinoEvaluateExpressionMethod m | m = ma.getMethod() | 92 | ( 93 | if ma.getMethod().getName() = "compileReader" 94 | then sink = ma.getArgument(0) // The first argument is the input reader 95 | else sink = ma.getArgument(1) // The second argument is the JavaScript or Java input 96 | ) and 97 | not exists(MethodAccess ca | 98 | ca.getMethod().hasName(["initSafeStandardObjects", "setClassShutter"]) and // safe mode or `ClassShutter` constraint is enforced 99 | ma.getQualifier() = ca.getQualifier().(VarAccess).getVariable().getAnAccess() 100 | ) 101 | ) 102 | } 103 | 104 | /** 105 | * Holds if a Rhino expression compilation method is vulnerable to code injection. 106 | */ 107 | predicate compilesScript(MethodAccess ma, Expr sink) { 108 | exists(RhinoCompileClassMethod m | m = ma.getMethod() | sink = ma.getArgument(0)) 109 | } 110 | 111 | /** 112 | * Holds if a Rhino class loading method is vulnerable to code injection. 113 | */ 114 | predicate definesRhinoClass(MethodAccess ma, Expr sink) { 115 | exists(RhinoDefineClassMethod m | m = ma.getMethod() | sink = ma.getArgument(1)) 116 | } 117 | 118 | /** A script injection sink. */ 119 | class ScriptInjectionSink extends DataFlow::ExprNode { 120 | MethodAccess methodAccess; 121 | 122 | ScriptInjectionSink() { 123 | isScriptArgument(methodAccess, this.getExpr()) or 124 | evaluatesRhinoExpression(methodAccess, this.getExpr()) or 125 | compilesScript(methodAccess, this.getExpr()) or 126 | definesRhinoClass(methodAccess, this.getExpr()) 127 | } 128 | 129 | /** An access to the method associated with this sink. */ 130 | MethodAccess getMethodAccess() { result = methodAccess } 131 | } 132 | 133 | /** 134 | * A taint tracking configuration that tracks flow from `RemoteFlowSource` to an argument 135 | * of a method call that executes injected script. 136 | */ 137 | class ScriptInjectionConfiguration extends TaintTracking::Configuration { 138 | ScriptInjectionConfiguration() { this = "ScriptInjectionConfiguration" } 139 | 140 | override predicate isSource(DataFlow::Node source) { 141 | source instanceof RemoteFlowSource 142 | } 143 | 144 | override predicate isSink(DataFlow::Node sink) { 145 | sink instanceof ScriptInjectionSink 146 | } 147 | } 148 | 149 | from DataFlow::PathNode source, DataFlow::PathNode sink, ScriptInjectionConfiguration conf 150 | where conf.hasFlowPath(source, sink) 151 | select sink.getNode().(ScriptInjectionSink).getMethodAccess(), source, sink, 152 | "Java Script Engine evaluate $@.", source.getNode(), "user input" 153 | 154 | -------------------------------------------------------------------------------- /exercises/models.qll: -------------------------------------------------------------------------------- 1 | import java 2 | import semmle.code.java.dataflow.FlowSources 3 | 4 | /** A call to the method `stream` declared in a collection type. */ 5 | class CollectionStreamCall extends MethodAccess { 6 | CollectionStreamCall() { this.getMethod().(CollectionMethod).getName() = "stream" } 7 | } 8 | 9 | /** Track taint from `x` to `x.stream()` where `x` is a collection. */ 10 | class CollectionStreamTaintStep extends TaintTracking::AdditionalTaintStep { 11 | override predicate step(DataFlow::Node n1, DataFlow::Node n2) { 12 | exists(CollectionStreamCall call | 13 | n1.asExpr() = call.getQualifier() and 14 | n2.asExpr() = call 15 | ) 16 | } 17 | } 18 | 19 | /** The interface `java.util.stream.Stream`. */ 20 | class TypeStream extends Interface { 21 | TypeStream() { this.hasQualifiedName("java.util.stream", "Stream") } 22 | } 23 | 24 | /** A method declared in a stream type, that is, a subtype of `java.util.stream.Stream`. */ 25 | class StreamMethod extends Method { 26 | StreamMethod() { this.getDeclaringType().getASourceSupertype+() instanceof TypeStream } 27 | } 28 | 29 | /** A call to the method `collect` declared in a stream type. */ 30 | class StreamCollectCall extends MethodAccess { 31 | StreamCollectCall() { this.getMethod().(StreamMethod).getName() = "collect" } 32 | } 33 | 34 | /** Track taint from `stream` to `stream.collect(lambda)`. */ 35 | class StreamCollectTaintStep extends TaintTracking::AdditionalTaintStep { 36 | override predicate step(DataFlow::Node n1, DataFlow::Node n2) { 37 | exists(StreamCollectCall call | 38 | n1.asExpr() = call.getQualifier() and 39 | n2.asExpr() = call 40 | ) 41 | } 42 | } 43 | 44 | /** A call to the method `filter` declared in a stream type. */ 45 | class StreamFilterCall extends MethodAccess { 46 | StreamFilterCall() { this.getMethod().(StreamMethod).getName() = "filter" } 47 | } 48 | 49 | /** Track taint from `stream` to `stream.filter(lambda)`. */ 50 | class StreamFilterTaintStep extends TaintTracking::AdditionalTaintStep { 51 | override predicate step(DataFlow::Node n1, DataFlow::Node n2) { 52 | exists(MethodAccess ma | 53 | ma instanceof StreamFilterCall and 54 | n1.asExpr() = ma.getQualifier() and 55 | n2.asExpr() = ma 56 | ) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /exercises/playground.ql: -------------------------------------------------------------------------------- 1 | import java 2 | 3 | from Method m 4 | select m 5 | -------------------------------------------------------------------------------- /exercises/qlpack.yml: -------------------------------------------------------------------------------- 1 | name: codeql/dubbo-workshop 2 | dependencies: 3 | codeql/java-all: '*' 4 | -------------------------------------------------------------------------------- /exercises/reverse_partial_dataflow.ql: -------------------------------------------------------------------------------- 1 | import java 2 | import semmle.code.java.dataflow.DataFlow 3 | import semmle.code.java.dataflow.FlowSources 4 | import DataFlow 5 | import PartialPathGraph 6 | 7 | class PartialTaintConfig extends DataFlow::Configuration { 8 | PartialTaintConfig() { this = "PartialTaintConfig" } 9 | 10 | override int explorationLimit() { result = 5 } 11 | 12 | override predicate isSource(DataFlow::Node source) { 13 | source instanceof RemoteFlowSource 14 | } 15 | 16 | override predicate isSink(DataFlow::Node sink) { 17 | exists(MethodAccess ma | 18 | ma.getMethod().hasName("load") and 19 | ma.getMethod().getDeclaringType().hasName("Yaml") and 20 | sink.asExpr() = ma.getAnArgument() 21 | ) 22 | } 23 | } 24 | 25 | from PartialPathNode n, int dist 26 | where 27 | any(PartialTaintConfig c).hasPartialFlowRev(n, _, dist) and 28 | n.getNode() instanceof DataFlow::ExplicitParameterNode and 29 | dist > 0 30 | select dist, n.getNode().getEnclosingCallable().getDeclaringType(), n.getNode().getEnclosingCallable(), n -------------------------------------------------------------------------------- /workshop.md: -------------------------------------------------------------------------------- 1 | # CodeQL as an audit oracle workshop 2 | 3 | EkoParty, 5th and 6th October 2021 4 | 5 | Presented by **@pwntester** 6 | 7 | Discord: **EkoParty** 8 | Invite: https://discord.gg/HX2dgEFp 9 | 10 | Documentation & tools: https://codeql.github.com 11 | 12 | Workshop format: This is a hands-on workshop where you will be using the CodeQL Visual Studio Extension to write CodeQL. 13 | 14 | Please feel free to ask questions at any time. If we run out of time, this is not a problem. We will just stop at an appropriate point. You can complete the remaining material in your own time if you want to. You are encouraged to experiment as you go along. Hints and solutions are provided. Where you see an arrow like this you can click to expand it: 15 | 16 |
17 | 18 | Hints 19 | 20 | Here are some hints. 21 |
22 | 23 | 24 | # Setup 25 | 26 | Follow the instructions in the [README](README.md) - you want to have [this repository](https://github.com/CodeQLWorkshops/DubboWorkshop) open in Visual Studio Code. Make sure that the extension and CodeQL CLI are the latest versions. 27 | 28 | The databases are included in the snapshot in the [databases](databases/) folder. You can also create your own databases using the CodeQL CLI. 29 | 30 | If you already cloned the repo, `git pull` to get the latest changes. 31 | 32 | # Exercise 1: Find the Dubbo attack surface known to CodeQL 33 | 34 | - Find all sources in Dubbo codebase 35 | - Exclude those ones with paths matching */src/test/* 36 | - Select the source, enclosing class and source type 37 | 38 | You should get 10 results. 39 | 40 |
41 | Hints 42 | 43 | ```ql 44 | import java 45 | import semmle.code.java.dataflow.FlowSources 46 | 47 | from RemoteFlowSource source 48 | where ... 49 | select ... 50 | ``` 51 | 52 |
53 | 54 | 55 |
56 | Solution 57 | 58 | ```ql 59 | import java 60 | import semmle.code.java.dataflow.FlowSources 61 | 62 | from RemoteFlowSource source 63 | where 64 | not source.getLocation().getFile().getRelativePath().matches("%/src/test/%") 65 | select 66 | source, 67 | source.getEnclosingCallable().getDeclaringType(), 68 | source.getSourceType() 69 | ``` 70 | 71 |
72 | 73 | # Exercise 2: Model Netty sources 74 | 75 | - Model and enumerate all Netty sources 76 | 77 | You should get 6 results. 78 | 79 |
80 | Hints 81 | 82 | A source can be added gloabally, rather than to a specific TaintTracking configuration, by extending `semmle.code.java.dataflow.FlowSources.RemoteFlowSource`: 83 | 84 | ```ql 85 | class NettySource extends RemoteFlowSource { 86 | NettySource() { 87 | ... 88 | } 89 | override string getSourceType() { result = "Netty Source" } 90 | } 91 | ``` 92 | 93 | The required APIs can be modelled with: 94 | 95 | ```ql 96 | /** The ChannelInboundHandler class */ 97 | class ChannelInboundHandler extends Class { 98 | ChannelInboundHandler() { 99 | this.getASourceSupertype*().hasQualifiedName("io.netty.channel", "ChannelInboundHandler") 100 | } 101 | } 102 | 103 | /** The ChannelInboundHandlerl.channelRead method */ 104 | class ChannelReadMethod extends Method { 105 | ChannelReadMethod() { 106 | this.getName() = ["channelRead", "channelRead0", "messageReceived"] and 107 | this.getDeclaringType() instanceof ChannelInboundHandler 108 | } 109 | } 110 | ``` 111 | 112 | and 113 | 114 | ```ql 115 | /** The ByteToMessageDecoder class */ 116 | class ByteToMessageDecoder extends Class { 117 | ByteToMessageDecoder() { 118 | this.getASourceSupertype*().hasQualifiedName("io.netty.handler.codec", "ByteToMessageDecoder") 119 | } 120 | } 121 | 122 | /** The ByteToMessageDecoder.decode method */ 123 | class DecodeMethod extends Method { 124 | DecodeMethod() { 125 | this.getName() = ["decode", "decodeLast"] and 126 | this.getDeclaringType() instanceof ByteToMessageDecoder 127 | } 128 | } 129 | ``` 130 | 131 |
132 | 133 |
134 | Solution 135 | 136 | ```ql 137 | import java 138 | import semmle.code.java.dataflow.FlowSources 139 | 140 | /** The ChannelInboundHandler class */ 141 | class ChannelInboundHandler extends Class { 142 | ChannelInboundHandler() { 143 | this.getASourceSupertype*().hasQualifiedName("io.netty.channel", "ChannelInboundHandler") 144 | } 145 | } 146 | 147 | /** The ChannelInboundHandlerl.channelRead method */ 148 | class ChannelReadMethod extends Method { 149 | ChannelReadMethod() { 150 | this.getName() = ["channelRead", "channelRead0", "messageReceived"] and 151 | this.getDeclaringType() instanceof ChannelInboundHandler 152 | } 153 | } 154 | 155 | /** The ByteToMessageDecoder class */ 156 | class ByteToMessageDecoder extends Class { 157 | ByteToMessageDecoder() { 158 | this.getASourceSupertype*().hasQualifiedName("io.netty.handler.codec", "ByteToMessageDecoder") 159 | } 160 | } 161 | 162 | /** The ByteToMessageDecoder.decode method */ 163 | class DecodeMethod extends Method { 164 | DecodeMethod() { 165 | this.getName() = ["decode", "decodeLast"] and 166 | this.getDeclaringType() instanceof ByteToMessageDecoder 167 | } 168 | } 169 | 170 | /** The ChannelInboundHandlerl.channelRead(1) source */ 171 | class ChannelReadSource extends RemoteFlowSource { 172 | ChannelReadSource() { 173 | exists(ChannelReadMethod m | 174 | this.asParameter() = m.getParameter(1) 175 | ) 176 | } 177 | override string getSourceType() { result = "Netty Handler Source" } 178 | } 179 | 180 | /** The ByteToMessageDecoder.decode(1) source */ 181 | class DecodeSource extends RemoteFlowSource { 182 | DecodeSource() { 183 | exists(DecodeMethod m | 184 | this.asParameter() = m.getParameter(1) 185 | ) 186 | } 187 | override string getSourceType() { result = "Netty Decoder Source" } 188 | } 189 | 190 | from RemoteFlowSource source 191 | where 192 | ( 193 | source instanceof ChannelReadSource or 194 | source instanceof DecodeSource 195 | ) and 196 | not source.getLocation().getFile().getRelativePath().matches("%/src/test/%") 197 | select 198 | source, 199 | source.getEnclosingCallable().getDeclaringType(), 200 | source.getSourceType() 201 | ``` 202 | 203 |
204 | 205 | * Explore some of the results by clicking on them 206 | * Explore autocomplete 207 | * Explore pop-up help 208 | * Jump to the QL class definition 209 | * Use the AST viewer. Right-click on any Ruby code and select "CodeQL: View AST". 210 | * Look at query history 211 | 212 | # Exercise 3: Variant analysis (Taint Tracking) 213 | 214 | - Find all variants of CVE-2020-11995 215 | - Use the TaintTracking library 216 | 217 | You should get 8 results. 218 | 219 |
220 | Hints 221 | 222 | - A `TaintTracking` query boilerplate: 223 | 224 | ```ql 225 | /** 226 | * @kind path-problem 227 | */ 228 | import java 229 | import semmle.code.java.dataflow.TaintTracking 230 | import DataFlow::PathGraph 231 | 232 | class MyConfig extends TaintTracking::Configuration { 233 | MyConfig() { this = "MyConfig" } 234 | 235 | override predicate isSource(DataFlow::Node source) { 236 | ... 237 | } 238 | 239 | override predicate isSink(DataFlow::Node sink) { 240 | ... 241 | } 242 | 243 | override predicate isAdditionalTaintStep(DataFlow::Node n1, DataFlow::Node n2) { 244 | ... 245 | } 246 | } 247 | 248 | from MyConfig conf, DataFlow::PathNode source, DataFlow::PathNode sink 249 | where conf.hasFlowPath(source, sink) 250 | select sink, source, sink, "dataflow was found" 251 | ``` 252 | 253 | - The relevant APIs can be modelled with: 254 | 255 | ```ql 256 | class DubboCodecDecodeBodyMethod extends Method { 257 | DubboCodecDecodeBodyMethod() { 258 | this.getName() = "decodeBody" and 259 | this.getDeclaringType().hasQualifiedName("org.apache.dubbo.rpc.protocol.dubbo", "DubboCodec") 260 | } 261 | } 262 | 263 | class ObjectInputReadMethod extends Method { 264 | ObjectInputReadMethod() { 265 | this.getName().matches("read%") and 266 | this.getDeclaringType() 267 | .getASourceSupertype*() 268 | .hasQualifiedName("org.apache.dubbo.common.serialize", "ObjectInput") 269 | } 270 | } 271 | 272 | class SerializationDeserializeMethod extends Method { 273 | SerializationDeserializeMethod() { 274 | this.getName() = "deserialize" and 275 | this.getDeclaringType().hasQualifiedName("org.apache.dubbo.common.serialize", "Serialization") 276 | } 277 | } 278 | ``` 279 | 280 | - A method call is represented with `MethodAccess` in CodeQL 281 | - `instanceof` operator allows you to enforce CodeQL classes 282 | 283 |
284 | 285 |
286 | Solution 287 | 288 | ```ql 289 | /** 290 | * @kind path-problem 291 | */ 292 | import java 293 | import semmle.code.java.dataflow.TaintTracking 294 | import DataFlow::PathGraph 295 | 296 | 297 | class DubboCodecDecodeBodyMethod extends Method { 298 | DubboCodecDecodeBodyMethod() { 299 | this.getName() = "decodeBody" and 300 | this.getDeclaringType().hasQualifiedName("org.apache.dubbo.rpc.protocol.dubbo", "DubboCodec") 301 | } 302 | } 303 | 304 | class ObjectInputReadMethod extends Method { 305 | ObjectInputReadMethod() { 306 | this.getName().matches("read%") and 307 | this.getDeclaringType() 308 | .getASourceSupertype*() 309 | .hasQualifiedName("org.apache.dubbo.common.serialize", "ObjectInput") 310 | } 311 | } 312 | 313 | class SerializationDeserializeMethod extends Method { 314 | SerializationDeserializeMethod() { 315 | this.getName() = "deserialize" and 316 | this.getDeclaringType().hasQualifiedName("org.apache.dubbo.common.serialize", "Serialization") 317 | } 318 | } 319 | 320 | class InsecureConfig extends TaintTracking::Configuration { 321 | InsecureConfig() { this = "InsecureConfig" } 322 | 323 | override predicate isSource(DataFlow::Node source) { 324 | exists(DubboCodecDecodeBodyMethod m | 325 | m.getParameter(1) = source.asParameter() 326 | ) 327 | } 328 | 329 | override predicate isSink(DataFlow::Node sink) { 330 | exists(MethodAccess ma | 331 | ma.getMethod() instanceof ObjectInputReadMethod and 332 | ma.getQualifier() = sink.asExpr() 333 | ) 334 | } 335 | 336 | override predicate isAdditionalTaintStep(DataFlow::Node n1, DataFlow::Node n2) { 337 | exists(MethodAccess ma | 338 | ma.getMethod() instanceof SerializationDeserializeMethod and 339 | ma.getArgument(1) = n1.asExpr() and 340 | ma = n2.asExpr() 341 | ) 342 | } 343 | } 344 | 345 | from InsecureConfig conf, DataFlow::PathNode source, DataFlow::PathNode sink 346 | where conf.hasFlowPath(source, sink) 347 | select sink, source, sink, "unsafe deserialization" 348 | ``` 349 | 350 |
351 | 352 | # Exercise 4: Semantic matches 353 | 354 | - Find all calls to `ObjectInput.read*()` methods semantically 355 | - Exclude calls to `read*` within the `ObjectInput` class itself 356 | - Exclude calls on files with a path matching `*/src/test/*` 357 | - Output the call, the enclosing method and the enclosing class 358 | 359 | This should give 14 results. 360 | 361 |
362 | Hints 363 | 364 | - You can model classes implementing `ObjecInput` with: 365 | 366 | ```ql 367 | class ObjectInputClass extends RefType { 368 | ObjectInputClass() { 369 | this.getASourceSupertype*().hasQualifiedName("org.apache.dubbo.common.serialize", "ObjectInput") 370 | } 371 | } 372 | ``` 373 | 374 | - Model calls to `ObjectInput.read*` method with a class 375 | 376 | ```ql 377 | class ObjectInputReadCall extends MethodAccess { 378 | ObjectInputReadCall() { 379 | .. 380 | } 381 | } 382 | ``` 383 | 384 |
385 | 386 |
387 | Solution 388 | 389 | ```ql 390 | import java 391 | 392 | class ObjectInputClass extends RefType { 393 | ObjectInputClass() { 394 | this.getASourceSupertype*().hasQualifiedName("org.apache.dubbo.common.serialize", "ObjectInput") 395 | } 396 | } 397 | 398 | class ObjectInputReadCall extends MethodAccess { 399 | ObjectInputReadCall() { 400 | exists(Method m | 401 | this.getMethod() = m and 402 | m.getName().matches("read%") and 403 | m.getDeclaringType() instanceof ObjectInputClass 404 | ) 405 | } 406 | } 407 | 408 | from ObjectInputReadCall call 409 | where 410 | not call.getEnclosingCallable().getDeclaringType() instanceof ObjectInputClass and 411 | not call.getLocation().getFile().getRelativePath().matches("%/src/test/%") 412 | select 413 | call, 414 | call.getEnclosingCallable(), 415 | call.getEnclosingCallable().getDeclaringType() 416 | ``` 417 | 418 |
419 | 420 | # Exercise 5: Scaling manual results 421 | 422 | - Find (semantically) all uses of: 423 | - `PojoUtil.realize()` 424 | - `JavaBeanSerializeUtil.deserialize()` 425 | - As usual exclude results on test files 426 | 427 | This should give 9 results. 428 | 429 |
430 | Hints 431 | 432 | - Exclude calls from `PojoUtils` and `JavaBeanSerializeUtil` 433 | - The relevant APIs needed for this query are: 434 | 435 | ```ql 436 | class PojoUtilsRealizeMethod extends Method { 437 | PojoUtilsRealizeMethod() { 438 | this.getName() = "realize" and 439 | this.getDeclaringType().getName() = "PojoUtils" 440 | } 441 | } 442 | 443 | class JavaBeanSerializeUtilDeserializeMethod extends Method { 444 | JavaBeanSerializeUtilDeserializeMethod() { 445 | this.getName() = "deserialize" and 446 | this.getDeclaringType().getName() = "JavaBeanSerializeUtil" 447 | } 448 | } 449 | ``` 450 | 451 |
452 | 453 |
454 | Solution 455 | 456 | ```ql 457 | import java 458 | 459 | class PojoUtilsRealizeMethod extends Method { 460 | PojoUtilsRealizeMethod() { 461 | this.getName() = "realize" and 462 | this.getDeclaringType().getName() = "PojoUtils" 463 | } 464 | } 465 | 466 | class JavaBeanSerializeUtilDeserializeMethod extends Method { 467 | JavaBeanSerializeUtilDeserializeMethod() { 468 | this.getName() = "deserialize" and 469 | this.getDeclaringType().getName() = "JavaBeanSerializeUtil" 470 | } 471 | } 472 | 473 | from MethodAccess ma 474 | where 475 | ( 476 | ma.getMethod() instanceof PojoUtilsRealizeMethod or 477 | ma.getMethod() instanceof JavaBeanSerializeUtilDeserializeMethod 478 | ) and 479 | not ma.getEnclosingCallable().getDeclaringType() = ma.getMethod().getDeclaringType() and 480 | not ma.getLocation().getFile().getRelativePath().matches("%/src/test/%") 481 | select ma, ma.getEnclosingCallable().getDeclaringType() 482 | ``` 483 | 484 |
485 | 486 | # Exercise 6: Semantic sinks heatmap 487 | 488 | - Find all calls to an unsafe deserialization sinks known to CodeQL 489 | - Reuse `UnsafeDeserializationSink` class from `semmle.code.java.security.UnsafeDeserializationQuery` 490 | - Select sink class, method and call enclosing class 491 | 492 | This should give 23 results. 493 | 494 |
495 | Hints 496 | 497 | - Reuse `UnsafeDeserializationSink` from `semmle.code.java.security.UnsafeDeserializationQuery`: 498 | 499 | ```ql 500 | import java 501 | import semmle.code.java.security.UnsafeDeserializationQuery 502 | 503 | from UnsafeDeserializationSink node 504 | where ... 505 | select ... 506 | ``` 507 | 508 |
509 | 510 | 511 |
512 | Solution 513 | 514 | ```ql 515 | import java 516 | import semmle.code.java.security.UnsafeDeserializationQuery 517 | 518 | from UnsafeDeserializationSink node 519 | where 520 | not node.getLocation().getFile().getRelativePath().matches("%/src/test/%") 521 | select 522 | node.asExpr().getParent().(Call).getCallee().getDeclaringType(), // deserializing class 523 | node.asExpr().getParent(), // deserializing method 524 | node.asExpr().getParent().(Call).getEnclosingCallable().getDeclaringType() // enclosing class 525 | ``` 526 | 527 |
528 | 529 | # Exercise 7: Configuration Centers 530 | 531 | - Model Dubbo Registry abstraction as a new source 532 | - Model Dubbo Configuration Center abstraction as a new source 533 | - List all these new sources 534 | 535 | There should be 10 results. 536 | 537 |
538 | Hints 539 | 540 | - The relevant APIs for this query are: 541 | 542 | ```ql 543 | class NotifyListener extends RefType { 544 | NotifyListener() { 545 | this.hasQualifiedName("org.apache.dubbo.registry", "NotifyListener") 546 | } 547 | } 548 | 549 | class ConfigurationListener extends RefType { 550 | ConfigurationListener() { 551 | this.hasQualifiedName("org.apache.dubbo.common.config.configcenter", "ConfigurationListener") 552 | } 553 | } 554 | 555 | class ConfigurationListenerProcessMethod extends Method { 556 | ConfigurationListenerProcessMethod() { 557 | this.getName() = "process" and 558 | this.getDeclaringType().getASupertype*() instanceof ConfigurationListener 559 | } 560 | } 561 | 562 | class NotifyListenerNotifyMethod extends Method { 563 | NotifyListenerNotifyMethod() { 564 | this.getName() = "notify" and 565 | this.getDeclaringType().getASupertype*() instanceof NotifyListener 566 | } 567 | } 568 | ``` 569 | 570 |
571 | 572 |
573 | Solution 574 | 575 | ```ql 576 | import java 577 | import semmle.code.java.dataflow.FlowSources 578 | 579 | class NotifyListener extends RefType { 580 | NotifyListener() { 581 | this.hasQualifiedName("org.apache.dubbo.registry", "NotifyListener") 582 | } 583 | } 584 | 585 | class ConfigurationListener extends RefType { 586 | ConfigurationListener() { 587 | this.hasQualifiedName("org.apache.dubbo.common.config.configcenter", "ConfigurationListener") 588 | } 589 | } 590 | 591 | class ConfigurationListenerProcessMethod extends Method { 592 | ConfigurationListenerProcessMethod() { 593 | this.getName() = "process" and 594 | this.getDeclaringType().getASupertype*() instanceof ConfigurationListener 595 | } 596 | } 597 | 598 | class NotifyListenerNotifyMethod extends Method { 599 | NotifyListenerNotifyMethod() { 600 | this.getName() = "notify" and 601 | this.getDeclaringType().getASupertype*() instanceof NotifyListener 602 | } 603 | } 604 | 605 | class DubboListener extends RemoteFlowSource { 606 | DubboListener() { 607 | (exists(NotifyListenerNotifyMethod m | 608 | this.asParameter() = m.getAParameter() 609 | ) or 610 | exists(ConfigurationListenerProcessMethod m | 611 | this.asParameter() = m.getAParameter() 612 | )) and 613 | not this.getLocation().getFile().getRelativePath().matches("%/src/test/%") 614 | } 615 | override string getSourceType() { result = "Dubbo Listener Source" } 616 | } 617 | 618 | from DubboListener l 619 | select 620 | l, 621 | l.asParameter().getCallable(), 622 | l.asParameter().getCallable().getDeclaringType() 623 | ``` 624 | 625 |
626 | 627 | # Exercise 8: Script Injection 628 | 629 | - Reuse [`ScriptInjection`](https://github.com/github/codeql/blob/main/java/ql/src/experimental/Security/CWE/CWE-094/ScriptInjection.ql) query 630 | - Add sources from exercise 7 631 | - Add a new TaintStep for `URL` methods: 632 | ```ql 633 | class URLTaintStep extends TaintTracking::AdditionalTaintStep { 634 | override predicate step(DataFlow::Node n1, DataFlow::Node n2) { 635 | exists(MethodAccess ma | 636 | ma.getMethod().getName().matches("get%") and 637 | ma.getMethod().getDeclaringType().hasQualifiedName("org.apache.dubbo.common", "URL") and 638 | n1.asExpr() = ma.getQualifier() and 639 | n2.asExpr() = ma 640 | ) 641 | } 642 | } 643 | ``` 644 | - Import local `models.qll` file to bring some unmerged library taint steps 645 | 646 | There should be 2 results. 647 | 648 |
649 | Solution 650 | 651 | ```ql 652 | /** 653 | * @name Injection in Java Script Engine 654 | * @description Evaluation of user-controlled data using the Java Script Engine may 655 | * lead to remote code execution. 656 | * @kind path-problem 657 | * @problem.severity error 658 | * @precision high 659 | * @id java/unsafe-eval 660 | * @tags security 661 | * external/cwe/cwe-094 662 | */ 663 | 664 | import java 665 | import semmle.code.java.dataflow.FlowSources 666 | import DataFlow::PathGraph 667 | import models 668 | import dubbo 669 | 670 | /** A method of ScriptEngine that allows code injection. */ 671 | class ScriptEngineMethod extends Method { 672 | ScriptEngineMethod() { 673 | this.getDeclaringType().getASupertype*().hasQualifiedName("javax.script", "ScriptEngine") and 674 | this.hasName("eval") 675 | or 676 | this.getDeclaringType().getASupertype*().hasQualifiedName("javax.script", "Compilable") and 677 | this.hasName("compile") 678 | or 679 | this.getDeclaringType().getASupertype*().hasQualifiedName("javax.script", "ScriptEngineFactory") and 680 | this.hasName(["getProgram", "getMethodCallSyntax"]) 681 | } 682 | } 683 | 684 | /** The context class `org.mozilla.javascript.Context` of Rhino Java Script Engine. */ 685 | class RhinoContext extends RefType { 686 | RhinoContext() { this.hasQualifiedName("org.mozilla.javascript", "Context") } 687 | } 688 | 689 | /** A method that evaluates a Rhino expression with `org.mozilla.javascript.Context`. */ 690 | class RhinoEvaluateExpressionMethod extends Method { 691 | RhinoEvaluateExpressionMethod() { 692 | this.getDeclaringType().getAnAncestor*() instanceof RhinoContext and 693 | this.hasName([ 694 | "evaluateString", "evaluateReader", "compileFunction", "compileReader", "compileString" 695 | ]) 696 | } 697 | } 698 | 699 | /** 700 | * A method that compiles a Rhino expression with 701 | * `org.mozilla.javascript.optimizer.ClassCompiler`. 702 | */ 703 | class RhinoCompileClassMethod extends Method { 704 | RhinoCompileClassMethod() { 705 | this.getDeclaringType() 706 | .getASupertype*() 707 | .hasQualifiedName("org.mozilla.javascript.optimizer", "ClassCompiler") and 708 | this.hasName("compileToClassFiles") 709 | } 710 | } 711 | 712 | /** 713 | * A method that defines a Java class from a Rhino expression with 714 | * `org.mozilla.javascript.GeneratedClassLoader`. 715 | */ 716 | class RhinoDefineClassMethod extends Method { 717 | RhinoDefineClassMethod() { 718 | this.getDeclaringType() 719 | .getASupertype*() 720 | .hasQualifiedName("org.mozilla.javascript", "GeneratedClassLoader") and 721 | this.hasName("defineClass") 722 | } 723 | } 724 | 725 | /** 726 | * Holds if `ma` is a call to a `ScriptEngineMethod` and `sink` is an argument that 727 | * will be executed. 728 | */ 729 | predicate isScriptArgument(MethodAccess ma, Expr sink) { 730 | exists(ScriptEngineMethod m | 731 | m = ma.getMethod() and 732 | if m.getDeclaringType().getASupertype*().hasQualifiedName("javax.script", "ScriptEngineFactory") 733 | then sink = ma.getArgument(_) // all arguments allow script injection 734 | else sink = ma.getArgument(0) 735 | ) 736 | } 737 | 738 | /** 739 | * Holds if a Rhino expression evaluation method is vulnerable to code injection. 740 | */ 741 | predicate evaluatesRhinoExpression(MethodAccess ma, Expr sink) { 742 | exists(RhinoEvaluateExpressionMethod m | m = ma.getMethod() | 743 | ( 744 | if ma.getMethod().getName() = "compileReader" 745 | then sink = ma.getArgument(0) // The first argument is the input reader 746 | else sink = ma.getArgument(1) // The second argument is the JavaScript or Java input 747 | ) and 748 | not exists(MethodAccess ca | 749 | ca.getMethod().hasName(["initSafeStandardObjects", "setClassShutter"]) and // safe mode or `ClassShutter` constraint is enforced 750 | ma.getQualifier() = ca.getQualifier().(VarAccess).getVariable().getAnAccess() 751 | ) 752 | ) 753 | } 754 | 755 | /** 756 | * Holds if a Rhino expression compilation method is vulnerable to code injection. 757 | */ 758 | predicate compilesScript(MethodAccess ma, Expr sink) { 759 | exists(RhinoCompileClassMethod m | m = ma.getMethod() | sink = ma.getArgument(0)) 760 | } 761 | 762 | /** 763 | * Holds if a Rhino class loading method is vulnerable to code injection. 764 | */ 765 | predicate definesRhinoClass(MethodAccess ma, Expr sink) { 766 | exists(RhinoDefineClassMethod m | m = ma.getMethod() | sink = ma.getArgument(1)) 767 | } 768 | 769 | /** A script injection sink. */ 770 | class ScriptInjectionSink extends DataFlow::ExprNode { 771 | MethodAccess methodAccess; 772 | 773 | ScriptInjectionSink() { 774 | isScriptArgument(methodAccess, this.getExpr()) or 775 | evaluatesRhinoExpression(methodAccess, this.getExpr()) or 776 | compilesScript(methodAccess, this.getExpr()) or 777 | definesRhinoClass(methodAccess, this.getExpr()) 778 | } 779 | 780 | /** An access to the method associated with this sink. */ 781 | MethodAccess getMethodAccess() { result = methodAccess } 782 | } 783 | 784 | /** 785 | * A taint tracking configuration that tracks flow from `RemoteFlowSource` to an argument 786 | * of a method call that executes injected script. 787 | */ 788 | class ScriptInjectionConfiguration extends TaintTracking::Configuration { 789 | ScriptInjectionConfiguration() { this = "ScriptInjectionConfiguration" } 790 | 791 | override predicate isSource(DataFlow::Node source) { 792 | source instanceof RemoteFlowSource 793 | } 794 | 795 | override predicate isSink(DataFlow::Node sink) { 796 | sink instanceof ScriptInjectionSink 797 | } 798 | } 799 | 800 | from DataFlow::PathNode source, DataFlow::PathNode sink, ScriptInjectionConfiguration conf 801 | where conf.hasFlowPath(source, sink) 802 | select sink.getNode().(ScriptInjectionSink).getMethodAccess(), source, sink, 803 | "Java Script Engine evaluate $@.", source.getNode(), "user input" 804 | ``` 805 | 806 |
807 | 808 | # Next steps 809 | 810 | * For tools and documentation, visit https://codeql.github.com 811 | * Join our slack at ghsecuritylab.slack.com 812 | * Enable CodeQL analysis for your own repos 813 | -------------------------------------------------------------------------------- /workspace.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | }, 6 | { 7 | "name": "[pwntester_dubbo_v2.7.8_3b2cfa9 source archive]", 8 | "uri": "codeql-zip-archive://0-176/Users/pwntester/Library/Application Support/Code/User/workspaceStorage/dadf1408d827bac79f4b19534832602b/GitHub.vscode-codeql/dubbo_2.7.8/pwntester_dubbo_v2.7.8_3b2cfa9/src.zip" 9 | } 10 | ], 11 | "settings": {} 12 | } --------------------------------------------------------------------------------