Skip to content

Commit 3175db2

Browse files
committed
upgrade fastAPI remote sources
1 parent 6ee5865 commit 3175db2

File tree

1 file changed

+54
-101
lines changed

1 file changed

+54
-101
lines changed

python/ql/src/experimental/Security/CWE-409/DecompressionBombs.ql

Lines changed: 54 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -376,9 +376,9 @@ module Pandas {
376376
}
377377

378378
module FileAndFormRemoteFlowSource {
379-
class FastAPI extends DataFlow::Node {
379+
class FastAPI extends RemoteFlowSource::Range {
380380
FastAPI() {
381-
exists(API::Node fastApiParam |
381+
exists(API::Node fastApiParam, Expr fastApiUploadFile |
382382
fastApiParam =
383383
API::moduleImport("fastapi")
384384
.getMember("FastAPI")
@@ -387,75 +387,72 @@ module FileAndFormRemoteFlowSource {
387387
.getReturn()
388388
.getParameter(0)
389389
.getKeywordParameter(_) and
390-
API::moduleImport("fastapi")
391-
.getMember("UploadFile")
392-
.getASubclass*()
393-
.getAValueReachableFromSource()
394-
.asExpr() =
395-
fastApiParam.asSource().asExpr().(Parameter).getAnnotation().getASubExpression*()
390+
fastApiUploadFile =
391+
API::moduleImport("fastapi")
392+
.getMember("UploadFile")
393+
.getASubclass*()
394+
.getAValueReachableFromSource()
395+
.asExpr()
396396
|
397-
// in the case of List of files
397+
fastApiUploadFile =
398+
fastApiParam.asSource().asExpr().(Parameter).getAnnotation().getASubExpression*() and
399+
// Multiple Uploaded files as list of fastapi.UploadFile
398400
exists(For f, Attribute attr, DataFlow::Node a, DataFlow::Node b |
399401
fastApiParam.getAValueReachableFromSource().asExpr() = f.getIter().getASubExpression*()
400402
|
401-
// file.file in following
402-
// def upload(files: List[UploadFile] = File(...)):
403-
// for file in files:
404-
// **file.file**
405-
// thanks Arthur Baars for helping me in following
406-
TaintTracking::localTaint(a, b) and
407-
a.asExpr() = f.getIter() and
408-
b.asExpr() = attr.getObject() and
403+
TaintTracking::localExprTaint(f.getIter(), attr.getObject()) and
409404
attr.getName() = ["filename", "content_type", "headers", "file", "read"] and
410405
this.asExpr() = attr
411406
)
412407
or
408+
// one Uploaded file as fastapi.UploadFile
413409
this =
414410
[
415-
fastApiParam.asSource(),
416-
fastApiParam.getMember(["filename", "content_type", "headers", "file"]).asSource(),
417-
fastApiParam.getMember("read").getReturn().asSource(),
418-
// file-like object, I'm trying to not do additional work here by using already existing file-like objs if it is possible
419-
// fastApiParam.getMember("file").getAMember().asSource(),
411+
fastApiParam.getMember(["filename", "content_type", "headers"]).asSource(),
412+
fastApiParam
413+
.getMember("file")
414+
.getMember(["readlines", "readline", "read"])
415+
.getReturn()
416+
.asSource(), fastApiParam.getMember("read").getReturn().asSource()
420417
]
421-
)
422-
or
423-
exists(API::Node fastApiParam |
424-
fastApiParam =
425-
API::moduleImport("fastapi")
426-
.getMember("FastAPI")
427-
.getReturn()
428-
.getMember("post")
429-
.getReturn()
430-
.getParameter(0)
431-
.getKeywordParameter(_) and
432-
API::moduleImport("fastapi")
433-
.getMember("File")
434-
.getASubclass*()
435-
.getAValueReachableFromSource()
436-
.asExpr() =
437-
fastApiParam.asSource().asExpr().(Parameter).getAnnotation().getASubExpression*()
438-
|
439-
// in the case of List of files
440-
exists(For f, Attribute attr, DataFlow::Node a, DataFlow::Node b |
441-
fastApiParam.getAValueReachableFromSource().asExpr() = f.getIter().getASubExpression*()
442-
|
443-
// file.file in following
444-
// def upload(files: List[UploadFile] = File(...)):
445-
// for file in files:
446-
// **file.file**
447-
// thanks Arthur Baars for helping me in following
448-
TaintTracking::localTaint(a, b) and
449-
a.asExpr() = f.getIter() and
450-
b.asExpr() = attr.getObject() and
451-
attr.getName() = "file" and
452-
this.asExpr() = attr
453-
)
454-
or
455-
this = fastApiParam.asSource()
456418
) and
457419
exists(this.getLocation().getFile().getRelativePath())
458420
}
421+
422+
override string getSourceType() { result = "HTTP FORM" }
423+
}
424+
}
425+
426+
module BombsConfig implements DataFlow::ConfigSig {
427+
predicate isSource(DataFlow::Node source) {
428+
source instanceof RemoteFlowSource and
429+
// or
430+
// source instanceof FileAndFormRemoteFlowSource::FastAPI
431+
exists(source.getLocation().getFile().getRelativePath()) and
432+
not source.getLocation().getFile().getRelativePath().matches("venv")
433+
}
434+
435+
predicate isSink(DataFlow::Node sink) {
436+
(
437+
sink =
438+
[
439+
ZipFile::isSink(), Gzip::isSink(), Lzma::isSink(), Bz2::isSink(), TarFile::isSink(),
440+
Shutil::isSink(), Pandas::isSink()
441+
] or
442+
any()
443+
) and
444+
exists(sink.getLocation().getFile().getRelativePath()) and
445+
not sink.getLocation().getFile().getRelativePath().matches("venv")
446+
}
447+
448+
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
449+
(
450+
isAdditionalTaintStepTextIOWrapper(nodeFrom, nodeTo) or
451+
ZipFile::isAdditionalTaintStep(nodeFrom, nodeTo) or
452+
TarFile::isAdditionalTaintStep(nodeFrom, nodeTo)
453+
) and
454+
exists(nodeTo.getLocation().getFile().getRelativePath()) and
455+
not nodeTo.getLocation().getFile().getRelativePath().matches("venv")
459456
}
460457
}
461458

@@ -480,50 +477,6 @@ predicate isAdditionalTaintStepTextIOWrapper(DataFlow::Node nodeFrom, DataFlow::
480477
exists(nodeTo.getLocation().getFile().getRelativePath())
481478
}
482479

483-
module BombsConfig implements DataFlow::ConfigSig {
484-
// borrowed from UnsafeUnpackQuery.qll
485-
predicate isSource(DataFlow::Node source) {
486-
source instanceof RemoteFlowSource
487-
or
488-
exists(MethodCallNode args |
489-
args = source.(AttrRead).getObject().getALocalSource() and
490-
args =
491-
[
492-
API::moduleImport("argparse")
493-
.getMember("ArgumentParser")
494-
.getReturn()
495-
.getMember("parse_args")
496-
.getACall(), API::moduleImport("os").getMember("getenv").getACall(),
497-
API::moduleImport("os").getMember("environ").getMember("get").getACall()
498-
]
499-
)
500-
or
501-
source instanceof FileAndFormRemoteFlowSource::FastAPI
502-
or
503-
source = TarFile::tarfileInstance().getACall()
504-
or
505-
source = ZipFile::zipFileClass().getACall()
506-
}
507-
508-
predicate isSink(DataFlow::Node sink) {
509-
sink =
510-
[
511-
ZipFile::isSink(), Gzip::isSink(), Lzma::isSink(), Bz2::isSink(), TarFile::isSink(),
512-
Shutil::isSink(), Pandas::isSink()
513-
] and
514-
exists(sink.getLocation().getFile().getRelativePath())
515-
}
516-
517-
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
518-
(
519-
isAdditionalTaintStepTextIOWrapper(nodeFrom, nodeTo) or
520-
ZipFile::isAdditionalTaintStep(nodeFrom, nodeTo) or
521-
TarFile::isAdditionalTaintStep(nodeFrom, nodeTo)
522-
) and
523-
exists(nodeTo.getLocation().getFile().getRelativePath())
524-
}
525-
}
526-
527480
module Bombs = TaintTracking::Global<BombsConfig>;
528481

529482
import Bombs::PathGraph

0 commit comments

Comments
 (0)