add jsonpickle and pexpect libs in case of unsafe decoding and secondary command execution, add proper test cases

This commit is contained in:
amammad
2024-02-25 17:15:35 +04:00
parent 3e6b4a161b
commit ab219902a9
13 changed files with 169 additions and 2 deletions

View File

@@ -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

View 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" }
}
}

View File

@@ -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()

View 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() }
}
}

View File

@@ -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

View File

@@ -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()
}
}
}

View File

@@ -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}

View File

@@ -0,0 +1,2 @@
testFailures
failures

View File

@@ -0,0 +1,2 @@
import python
import experimental.meta.ConceptsTest

View 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)

View File

@@ -0,0 +1,2 @@
testFailures
failures

View File

@@ -0,0 +1,2 @@
import python
import experimental.meta.ConceptsTest

View File

@@ -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