mirror of
https://github.com/github/codeql.git
synced 2026-02-20 17:03:41 +01:00
add jsonpickle and pexpect libs in case of unsafe decoding and secondary command execution, add proper test cases
This commit is contained in:
@@ -34,6 +34,7 @@ private import semmle.python.frameworks.Idna
|
||||
private import semmle.python.frameworks.Invoke
|
||||
private import semmle.python.frameworks.Jmespath
|
||||
private import semmle.python.frameworks.Joblib
|
||||
private import semmle.python.frameworks.JsonPickle
|
||||
private import semmle.python.frameworks.Ldap
|
||||
private import semmle.python.frameworks.Ldap3
|
||||
private import semmle.python.frameworks.Libtaxii
|
||||
@@ -48,6 +49,7 @@ private import semmle.python.frameworks.Oracledb
|
||||
private import semmle.python.frameworks.Pandas
|
||||
private import semmle.python.frameworks.Paramiko
|
||||
private import semmle.python.frameworks.Peewee
|
||||
private import semmle.python.frameworks.Pexpect
|
||||
private import semmle.python.frameworks.Phoenixdb
|
||||
private import semmle.python.frameworks.Psycopg
|
||||
private import semmle.python.frameworks.Psycopg2
|
||||
|
||||
32
python/ql/lib/semmle/python/frameworks/JsonPickle.qll
Normal file
32
python/ql/lib/semmle/python/frameworks/JsonPickle.qll
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `jsonpickle` PyPI package.
|
||||
* See https://pypi.org/project/jsonpickle/.
|
||||
*/
|
||||
|
||||
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 models for the `jsonpickle` PyPI package.
|
||||
* See https://pypi.org/project/jsonpickle/.
|
||||
*/
|
||||
private module Jsonpickle {
|
||||
/**
|
||||
* A Call to `jsonpickle.decode`.
|
||||
* See https://jsonpickle.readthedocs.io/en/latest/api.html#jsonpickle.decode
|
||||
*/
|
||||
private class JsonpickleDecode extends Decoding::Range, API::CallNode {
|
||||
JsonpickleDecode() { this = API::moduleImport("jsonpickle").getMember("decode").getACall() }
|
||||
|
||||
override predicate mayExecuteInput() { any() }
|
||||
|
||||
override DataFlow::Node getAnInput() { result = this.getParameter(0, "string").asSink() }
|
||||
|
||||
override DataFlow::Node getOutput() { result = this }
|
||||
|
||||
override string getFormat() { result = "pickle" }
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ private import semmle.python.ApiGraphs
|
||||
* See https://pypi.org/project/paramiko/.
|
||||
*/
|
||||
private module Paramiko {
|
||||
/*
|
||||
/**
|
||||
* The first argument of `paramiko.ProxyCommand`.
|
||||
*
|
||||
* the `paramiko.ProxyCommand` is equivalent of `ssh -o ProxyCommand="CMD"`
|
||||
@@ -22,7 +22,6 @@ private module Paramiko {
|
||||
*
|
||||
* See https://paramiko.pydata.org/docs/reference/api/paramiko.eval.html
|
||||
*/
|
||||
|
||||
class ParamikoProxyCommand extends SystemCommandExecution::Range, API::CallNode {
|
||||
ParamikoProxyCommand() {
|
||||
this = API::moduleImport("paramiko").getMember("ProxyCommand").getACall()
|
||||
|
||||
46
python/ql/lib/semmle/python/frameworks/Pexpect.qll
Normal file
46
python/ql/lib/semmle/python/frameworks/Pexpect.qll
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `pexpect` PyPI package.
|
||||
* See https://pypi.org/project/pexpect/.
|
||||
*/
|
||||
|
||||
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 models for the `pexpect` PyPI package.
|
||||
* See https://pypi.org/project/pexpect/.
|
||||
*/
|
||||
private module Pexpect {
|
||||
/**
|
||||
* The calls to `pexpect.*` functions that execute commands
|
||||
* See https://pexpect.readthedocs.io/en/stable/api/pexpect.html#pexpect.spawn
|
||||
* See https://pexpect.readthedocs.io/en/stable/api/pexpect.html#pexpect.run
|
||||
*/
|
||||
class PexpectCommandExec extends SystemCommandExecution::Range, API::CallNode {
|
||||
PexpectCommandExec() {
|
||||
this = API::moduleImport("pexpect").getMember(["run", "runu", "spawn", "spawnu"]).getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getCommand() { result = this.getParameter(0, "command").asSink() }
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `pexpect.popen_spawn.PopenSpawn`
|
||||
* See https://pexpect.readthedocs.io/en/stable/api/popen_spawn.html#pexpect.popen_spawn.PopenSpawn
|
||||
*/
|
||||
class PexpectPopenSpawn extends SystemCommandExecution::Range, API::CallNode {
|
||||
PexpectPopenSpawn() {
|
||||
this =
|
||||
API::moduleImport("pexpect").getMember("popen_spawn").getMember("PopenSpawn").getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getCommand() { result = this.getParameter(0, "cmd").asSink() }
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) { none() }
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ private import experimental.semmle.python.frameworks.Werkzeug
|
||||
private import experimental.semmle.python.frameworks.LDAP
|
||||
private import experimental.semmle.python.frameworks.Netmiko
|
||||
private import experimental.semmle.python.frameworks.Paramiko
|
||||
private import experimental.semmle.python.frameworks.Pexpect
|
||||
private import experimental.semmle.python.frameworks.Scrapli
|
||||
private import experimental.semmle.python.frameworks.JWT
|
||||
private import experimental.semmle.python.frameworks.Csv
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `pexpect` PyPI package.
|
||||
* See https://pypi.org/project/pexpect/.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.ApiGraphs
|
||||
import experimental.semmle.python.Concepts
|
||||
|
||||
/**
|
||||
* Provides models for the `pexpect` PyPI package.
|
||||
* See https://pypi.org/project/pexpect/.
|
||||
*/
|
||||
private module Pexpect {
|
||||
/**
|
||||
* The calls to `pexpect.pxssh.pxssh` functions that execute commands
|
||||
* See https://pexpect.readthedocs.io/en/stable/api/pxssh.html
|
||||
*/
|
||||
class PexpectCommandExec extends SecondaryCommandInjection {
|
||||
PexpectCommandExec() {
|
||||
this =
|
||||
API::moduleImport("pexpect")
|
||||
.getMember("pxssh")
|
||||
.getMember("pxssh")
|
||||
.getReturn()
|
||||
.getMember(["send", "sendline"])
|
||||
.getACall()
|
||||
.getParameter(0, "s")
|
||||
.asSink()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from fastapi import FastAPI
|
||||
from pexpect import pxssh
|
||||
|
||||
ssh = pxssh.pxssh()
|
||||
hostname = "localhost"
|
||||
username = "username"
|
||||
password = "password"
|
||||
ssh.login(hostname, username, password)
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/bad1")
|
||||
async def bad1(cmd: str):
|
||||
ssh.send(cmd) # $ result=BAD getSecondaryCommand=cmd
|
||||
ssh.prompt()
|
||||
ssh.sendline(cmd) # $ result=BAD getSecondaryCommand=cmd
|
||||
ssh.prompt()
|
||||
ssh.logout()
|
||||
return {"success": stdout}
|
||||
@@ -0,0 +1,2 @@
|
||||
testFailures
|
||||
failures
|
||||
@@ -0,0 +1,2 @@
|
||||
import python
|
||||
import experimental.meta.ConceptsTest
|
||||
15
python/ql/test/library-tests/frameworks/jsonpickle/Decode.py
Normal file
15
python/ql/test/library-tests/frameworks/jsonpickle/Decode.py
Normal file
@@ -0,0 +1,15 @@
|
||||
import os
|
||||
|
||||
import jsonpickle
|
||||
|
||||
|
||||
class Thing(object):
|
||||
def __reduce__(self):
|
||||
return os.system, ("curl 127.0.0.1:1234",)
|
||||
|
||||
|
||||
obj = Thing()
|
||||
|
||||
pickledObj = jsonpickle.encode(obj)
|
||||
objUnPickled = jsonpickle.decode(pickledObj, safe=True) # $ decodeInput=pickledObj decodeOutput=jsonpickle.decode(..) decodeFormat=pickle decodeMayExecuteInput
|
||||
print(objUnPickled.name)
|
||||
@@ -0,0 +1,2 @@
|
||||
testFailures
|
||||
failures
|
||||
@@ -0,0 +1,2 @@
|
||||
import python
|
||||
import experimental.meta.ConceptsTest
|
||||
@@ -0,0 +1,9 @@
|
||||
import pexpect
|
||||
from pexpect import popen_spawn
|
||||
|
||||
cmd = "ls -la"
|
||||
result = pexpect.run(cmd) # $ getCommand=cmd
|
||||
result = pexpect.runu(cmd) # $ getCommand=cmd
|
||||
result = pexpect.spawn(cmd) # $ getCommand=cmd
|
||||
result = pexpect.spawnu(cmd) # $ getCommand=cmd
|
||||
result = popen_spawn.PopenSpawn(cmd) # $ getCommand=cmd
|
||||
Reference in New Issue
Block a user