Skip to content

Commit 6a77b89

Browse files
authored
Merge pull request github#6155 from RasmusWL/port-cleartext-queries
Python: Port cleartext queries
2 parents fc71a64 + e5d6599 commit 6a77b89

39 files changed

+1276
-246
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lgtm,codescanning
2+
* Added `HTTP::Server::CookieWrite` concept for statements that sets a cookie in an HTTP response, along with modeling of this in supported web frameworks (aiohttp/flask/django/tornado/twisted).

python/ql/src/Security/CWE-312/CleartextLogging.ql

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,13 @@
1414
*/
1515

1616
import python
17-
import semmle.python.security.Paths
18-
import semmle.python.dataflow.TaintTracking
19-
import semmle.python.security.SensitiveData
20-
import semmle.python.security.ClearText
17+
private import semmle.python.dataflow.new.DataFlow
18+
import DataFlow::PathGraph
19+
import semmle.python.security.dataflow.CleartextLogging::CleartextLogging
2120

22-
class CleartextLoggingConfiguration extends TaintTracking::Configuration {
23-
CleartextLoggingConfiguration() { this = "ClearTextLogging" }
24-
25-
override predicate isSource(DataFlow::Node src, TaintKind kind) {
26-
src.asCfgNode().(SensitiveData::Source).isSourceOf(kind)
27-
}
28-
29-
override predicate isSink(DataFlow::Node sink, TaintKind kind) {
30-
sink.asCfgNode() instanceof ClearTextLogging::Sink and
31-
kind instanceof SensitiveData
32-
}
33-
}
34-
35-
from CleartextLoggingConfiguration config, TaintedPathSource source, TaintedPathSink sink
36-
where config.hasFlowPath(source, sink)
37-
select sink.getSink(), source, sink, "Sensitive data returned by $@ is logged here.",
38-
source.getSource(), source.getCfgNode().(SensitiveData::Source).repr()
21+
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, string classification
22+
where
23+
config.hasFlowPath(source, sink) and
24+
classification = source.getNode().(Source).getClassification()
25+
select sink.getNode(), source, sink, "$@ is logged here.", source.getNode(),
26+
"Sensitive data (" + classification + ")"

python/ql/src/Security/CWE-312/CleartextStorage.ql

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,13 @@
1414
*/
1515

1616
import python
17-
import semmle.python.security.Paths
18-
import semmle.python.dataflow.TaintTracking
19-
import semmle.python.security.SensitiveData
20-
import semmle.python.security.ClearText
17+
private import semmle.python.dataflow.new.DataFlow
18+
import DataFlow::PathGraph
19+
import semmle.python.security.dataflow.CleartextStorage::CleartextStorage
2120

22-
class CleartextStorageConfiguration extends TaintTracking::Configuration {
23-
CleartextStorageConfiguration() { this = "ClearTextStorage" }
24-
25-
override predicate isSource(DataFlow::Node src, TaintKind kind) {
26-
src.asCfgNode().(SensitiveData::Source).isSourceOf(kind)
27-
}
28-
29-
override predicate isSink(DataFlow::Node sink, TaintKind kind) {
30-
sink.asCfgNode() instanceof ClearTextStorage::Sink and
31-
kind instanceof SensitiveData
32-
}
33-
}
34-
35-
from CleartextStorageConfiguration config, TaintedPathSource source, TaintedPathSink sink
36-
where config.hasFlowPath(source, sink)
37-
select sink.getSink(), source, sink, "Sensitive data from $@ is stored here.", source.getSource(),
38-
source.getCfgNode().(SensitiveData::Source).repr()
21+
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, string classification
22+
where
23+
config.hasFlowPath(source, sink) and
24+
classification = source.getNode().(Source).getClassification()
25+
select sink.getNode(), source, sink, "$@ is stored here.", source.getNode(),
26+
"Sensitive data (" + classification + ")"
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* @name Clear-text logging of sensitive information
3+
* @description OLD QUERY: Logging sensitive information without encryption or hashing can
4+
* expose it to an attacker.
5+
* @kind path-problem
6+
* @problem.severity error
7+
* @id py/old/clear-text-logging-sensitive-data
8+
* @deprecated
9+
*/
10+
11+
import python
12+
import semmle.python.security.Paths
13+
import semmle.python.dataflow.TaintTracking
14+
import semmle.python.security.SensitiveData
15+
import semmle.python.security.ClearText
16+
17+
class CleartextLoggingConfiguration extends TaintTracking::Configuration {
18+
CleartextLoggingConfiguration() { this = "ClearTextLogging" }
19+
20+
override predicate isSource(DataFlow::Node src, TaintKind kind) {
21+
src.asCfgNode().(SensitiveData::Source).isSourceOf(kind)
22+
}
23+
24+
override predicate isSink(DataFlow::Node sink, TaintKind kind) {
25+
sink.asCfgNode() instanceof ClearTextLogging::Sink and
26+
kind instanceof SensitiveData
27+
}
28+
}
29+
30+
from CleartextLoggingConfiguration config, TaintedPathSource source, TaintedPathSink sink
31+
where config.hasFlowPath(source, sink)
32+
select sink.getSink(), source, sink, "Sensitive data returned by $@ is logged here.",
33+
source.getSource(), source.getCfgNode().(SensitiveData::Source).repr()
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* @name Clear-text storage of sensitive information
3+
* @description OLD QUERY: Sensitive information stored without encryption or hashing can expose it to an
4+
* attacker.
5+
* @kind path-problem
6+
* @problem.severity error
7+
* @id py/old/clear-text-storage-sensitive-data
8+
* @deprecated
9+
*/
10+
11+
import python
12+
import semmle.python.security.Paths
13+
import semmle.python.dataflow.TaintTracking
14+
import semmle.python.security.SensitiveData
15+
import semmle.python.security.ClearText
16+
17+
class CleartextStorageConfiguration extends TaintTracking::Configuration {
18+
CleartextStorageConfiguration() { this = "ClearTextStorage" }
19+
20+
override predicate isSource(DataFlow::Node src, TaintKind kind) {
21+
src.asCfgNode().(SensitiveData::Source).isSourceOf(kind)
22+
}
23+
24+
override predicate isSink(DataFlow::Node sink, TaintKind kind) {
25+
sink.asCfgNode() instanceof ClearTextStorage::Sink and
26+
kind instanceof SensitiveData
27+
}
28+
}
29+
30+
from CleartextStorageConfiguration config, TaintedPathSource source, TaintedPathSink sink
31+
where config.hasFlowPath(source, sink)
32+
select sink.getSink(), source, sink, "Sensitive data from $@ is stored here.", source.getSource(),
33+
source.getCfgNode().(SensitiveData::Source).repr()

python/ql/src/semmle/python/Concepts.qll

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,39 @@ module FileSystemAccess {
7272
}
7373
}
7474

75+
/**
76+
* A data flow node that writes data to the file system access.
77+
*
78+
* Extend this class to refine existing API models. If you want to model new APIs,
79+
* extend `FileSystemWriteAccess::Range` instead.
80+
*/
81+
class FileSystemWriteAccess extends FileSystemAccess {
82+
override FileSystemWriteAccess::Range range;
83+
84+
/**
85+
* Gets a node that represents data to be written to the file system (possibly with
86+
* some transformation happening before it is written, like JSON encoding).
87+
*/
88+
DataFlow::Node getADataNode() { result = range.getADataNode() }
89+
}
90+
91+
/** Provides a class for modeling new file system writes. */
92+
module FileSystemWriteAccess {
93+
/**
94+
* A data flow node that writes data to the file system access.
95+
*
96+
* Extend this class to model new APIs. If you want to refine existing API models,
97+
* extend `FileSystemWriteAccess` instead.
98+
*/
99+
abstract class Range extends FileSystemAccess::Range {
100+
/**
101+
* Gets a node that represents data to be written to the file system (possibly with
102+
* some transformation happening before it is written, like JSON encoding).
103+
*/
104+
abstract DataFlow::Node getADataNode();
105+
}
106+
}
107+
75108
/** Provides classes for modeling path-related APIs. */
76109
module Path {
77110
/**
@@ -235,6 +268,35 @@ private class EncodingAdditionalTaintStep extends TaintTracking::AdditionalTaint
235268
}
236269
}
237270

271+
/**
272+
* A data-flow node that logs data.
273+
*
274+
* Extend this class to refine existing API models. If you want to model new APIs,
275+
* extend `Logging::Range` instead.
276+
*/
277+
class Logging extends DataFlow::Node {
278+
Logging::Range range;
279+
280+
Logging() { this = range }
281+
282+
/** Gets an input that is logged. */
283+
DataFlow::Node getAnInput() { result = range.getAnInput() }
284+
}
285+
286+
/** Provides a class for modeling new logging mechanisms. */
287+
module Logging {
288+
/**
289+
* A data-flow node that logs data.
290+
*
291+
* Extend this class to model new APIs. If you want to refine existing API models,
292+
* extend `Logging` instead.
293+
*/
294+
abstract class Range extends DataFlow::Node {
295+
/** Gets an input that is logged. */
296+
abstract DataFlow::Node getAnInput();
297+
}
298+
}
299+
238300
/**
239301
* A data-flow node that dynamically executes Python code.
240302
*
@@ -594,6 +656,62 @@ module HTTP {
594656
abstract DataFlow::Node getRedirectLocation();
595657
}
596658
}
659+
660+
/**
661+
* A data-flow node that sets a cookie in an HTTP response.
662+
*
663+
* Extend this class to refine existing API models. If you want to model new APIs,
664+
* extend `HTTP::CookieWrite::Range` instead.
665+
*/
666+
class CookieWrite extends DataFlow::Node {
667+
CookieWrite::Range range;
668+
669+
CookieWrite() { this = range }
670+
671+
/**
672+
* Gets the argument, if any, specifying the raw cookie header.
673+
*/
674+
DataFlow::Node getHeaderArg() { result = range.getHeaderArg() }
675+
676+
/**
677+
* Gets the argument, if any, specifying the cookie name.
678+
*/
679+
DataFlow::Node getNameArg() { result = range.getNameArg() }
680+
681+
/**
682+
* Gets the argument, if any, specifying the cookie value.
683+
*/
684+
DataFlow::Node getValueArg() { result = range.getValueArg() }
685+
}
686+
687+
/** Provides a class for modeling new cookie writes on HTTP responses. */
688+
module CookieWrite {
689+
/**
690+
* A data-flow node that sets a cookie in an HTTP response.
691+
*
692+
* Note: we don't require that this redirect must be sent to a client (a kind of
693+
* "if a tree falls in a forest and nobody hears it" situation).
694+
*
695+
* Extend this class to model new APIs. If you want to refine existing API models,
696+
* extend `HttpResponse` instead.
697+
*/
698+
abstract class Range extends DataFlow::Node {
699+
/**
700+
* Gets the argument, if any, specifying the raw cookie header.
701+
*/
702+
abstract DataFlow::Node getHeaderArg();
703+
704+
/**
705+
* Gets the argument, if any, specifying the cookie name.
706+
*/
707+
abstract DataFlow::Node getNameArg();
708+
709+
/**
710+
* Gets the argument, if any, specifying the cookie value.
711+
*/
712+
abstract DataFlow::Node getValueArg();
713+
}
714+
}
597715
}
598716
}
599717

0 commit comments

Comments
 (0)