Skip to content

Commit a2841b4

Browse files
authored
Merge pull request #1763 from markshannon/python-cwe-312
Python: Two new queries for CWE-312.
2 parents c9275fd + 3f740d6 commit a2841b4

30 files changed

+595
-72
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Improvements to Python analysis
2+
3+
4+
## General improvements
5+
6+
7+
8+
## New queries
9+
10+
| **Query** | **Tags** | **Purpose** |
11+
|-----------|----------|-------------|
12+
| Clear-text logging of sensitive information (`py/clear-text-logging-sensitive-data`) | security, external/cwe/cwe-312 | Finds instances where sensitive information is logged without encryption or hashing. Results are shown on LGTM by default. |
13+
| Clear-text storage of sensitive information (`py/clear-text-storage-sensitive-data`) | security, external/cwe/cwe-312 | Finds instances where sensitive information is stored without encryption or hashing. Results are shown on LGTM by default. |
14+
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<include src="CleartextStorage.qhelp" /></qhelp>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* @name Clear-text logging of sensitive information
3+
* @description Logging sensitive information without encryption or hashing can
4+
* expose it to an attacker.
5+
* @kind path-problem
6+
* @problem.severity error
7+
* @precision high
8+
* @id py/clear-text-logging-sensitive-data
9+
* @tags security
10+
* external/cwe/cwe-312
11+
* external/cwe/cwe-315
12+
* external/cwe/cwe-359
13+
*/
14+
15+
import python
16+
import semmle.python.security.Paths
17+
18+
import semmle.python.security.TaintTracking
19+
import semmle.python.security.SensitiveData
20+
import semmle.python.security.ClearText
21+
22+
23+
class CleartextLoggingConfiguration extends TaintTracking::Configuration {
24+
25+
CleartextLoggingConfiguration() { this = "ClearTextLogging" }
26+
27+
override predicate isSource(DataFlow::Node src, TaintKind kind) {
28+
src.asCfgNode().(SensitiveData::Source).isSourceOf(kind)
29+
}
30+
31+
override predicate isSink(DataFlow::Node sink, TaintKind kind) {
32+
sink.asCfgNode() instanceof ClearTextLogging::Sink and
33+
kind instanceof SensitiveData
34+
}
35+
36+
}
37+
38+
39+
from CleartextLoggingConfiguration config, TaintedPathSource source, TaintedPathSink sink
40+
where config.hasFlowPath(source, sink)
41+
select sink.getSink(), source, sink, "Sensitive data returned by $@ is logged here.",
42+
source.getSource(), source.getCfgNode().(SensitiveData::Source).repr()
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
6+
<overview>
7+
<p>
8+
Sensitive information that is stored unencrypted is accessible to an attacker
9+
who gains access to the storage. This is particularly important for cookies,
10+
which are stored on the machine of the end-user.
11+
</p>
12+
</overview>
13+
14+
<recommendation>
15+
<p>
16+
Ensure that sensitive information is always encrypted before being stored.
17+
If possible, avoid placing sensitive information in cookies altogether.
18+
Instead, prefer storing, in the cookie, a key that can be used to look up the
19+
sensitive information.
20+
</p>
21+
<p>
22+
In general, decrypt sensitive information only at the point where it is
23+
necessary for it to be used in cleartext.
24+
</p>
25+
26+
<p>
27+
28+
Be aware that external processes often store the <code>standard
29+
out</code> and <code>standard error</code> streams of the application,
30+
causing logged sensitive information to be stored as well.
31+
32+
</p>
33+
34+
</recommendation>
35+
36+
<example>
37+
<p>
38+
The following example code stores user credentials (in this case, their password) in a cookie in plain text:
39+
</p>
40+
<sample src="examples/password_in_cookie.py"/>
41+
<p>
42+
Instead, the credentials should be encrypted, for instance by using the <code>cryptography</code> module, or not stored at all.
43+
</p>
44+
</example>
45+
46+
<references>
47+
48+
<li>M. Dowd, J. McDonald and J. Schuhm, <i>The Art of Software Security Assessment</i>, 1st Edition, Chapter 2 - 'Common Vulnerabilities of Encryption', p. 43. Addison Wesley, 2006.</li>
49+
<li>M. Howard and D. LeBlanc, <i>Writing Secure Code</i>, 2nd Edition, Chapter 9 - 'Protecting Secret Data', p. 299. Microsoft, 2002.</li>
50+
51+
</references>
52+
</qhelp>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* @name Clear-text storage of sensitive information
3+
* @description Sensitive information stored without encryption or hashing can expose it to an
4+
* attacker.
5+
* @kind path-problem
6+
* @problem.severity error
7+
* @precision high
8+
* @id py/clear-text-storage-sensitive-data
9+
* @tags security
10+
* external/cwe/cwe-312
11+
* external/cwe/cwe-315
12+
* external/cwe/cwe-359
13+
*/
14+
15+
import python
16+
import semmle.python.security.Paths
17+
18+
import semmle.python.security.TaintTracking
19+
import semmle.python.security.SensitiveData
20+
import semmle.python.security.ClearText
21+
22+
class CleartextStorageConfiguration extends TaintTracking::Configuration {
23+
24+
CleartextStorageConfiguration() { this = "ClearTextStorage" }
25+
26+
override predicate isSource(DataFlow::Node src, TaintKind kind) {
27+
src.asCfgNode().(SensitiveData::Source).isSourceOf(kind)
28+
}
29+
30+
override predicate isSink(DataFlow::Node sink, TaintKind kind) {
31+
sink.asCfgNode() instanceof ClearTextStorage::Sink and
32+
kind instanceof SensitiveData
33+
}
34+
35+
}
36+
37+
38+
from CleartextStorageConfiguration config, TaintedPathSource source, TaintedPathSink sink
39+
where config.hasFlowPath(source, sink)
40+
select sink.getSink(), source, sink, "Sensitive data from $@ is stored here.",
41+
source.getSource(), source.getCfgNode().(SensitiveData::Source).repr()
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from flask import Flask, make_response, request
2+
3+
app = Flask("Leak password")
4+
5+
@app.route('/')
6+
def index():
7+
password = request.args.get("password")
8+
resp = make_response(render_template(...))
9+
resp.set_cookie("password", password)
10+
return resp
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import python
2+
import semmle.python.security.TaintTracking
3+
4+
class OpenFile extends TaintKind {
5+
6+
OpenFile() { this = "file.open" }
7+
8+
override string repr() { result = "an open file" }
9+
10+
11+
}
12+
13+
class OpenFileConfiguration extends TaintTracking::Configuration {
14+
15+
OpenFileConfiguration() { this = "Open file configuration" }
16+
17+
override predicate isSource(DataFlow::Node src, TaintKind kind) {
18+
theOpenFunction().(FunctionObject).getACall() = src.asCfgNode() and
19+
kind instanceof OpenFile
20+
}
21+
22+
override predicate isSink(DataFlow::Node sink, TaintKind kind) {
23+
none()
24+
}
25+
26+
}

python/ql/src/semmle/python/objects/Modules.qll

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -435,8 +435,11 @@ class AbsentModuleAttributeObjectInternal extends ObjectInternal, TAbsentModuleA
435435

436436
override predicate subscriptUnknown() { any() }
437437

438-
/* We know what this is called, but not its innate name */
439-
override string getName() { none() }
438+
/* We know what this is called, but not its innate name.
439+
* However, if we are looking for things by name, this is a reasonable approximation */
440+
override string getName() {
441+
this = TAbsentModuleAttribute(_, result)
442+
}
440443

441444
override predicate contextSensitiveCallee() { none() }
442445

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import python
2+
import semmle.python.security.TaintTracking
3+
import semmle.python.security.SensitiveData
4+
import semmle.python.dataflow.Files
5+
import semmle.python.web.Http
6+
7+
module ClearTextStorage {
8+
9+
abstract class Sink extends TaintSink {
10+
override predicate sinks(TaintKind kind) {
11+
kind instanceof SensitiveData
12+
}
13+
}
14+
15+
class CookieStorageSink extends Sink {
16+
CookieStorageSink() {
17+
any(CookieSet cookie).getValue() = this
18+
}
19+
}
20+
21+
class FileStorageSink extends Sink {
22+
FileStorageSink() {
23+
exists(CallNode call, AttrNode meth, string name |
24+
any(OpenFile fd).taints(meth.getObject(name)) and
25+
call.getFunction() = meth and
26+
call.getAnArg() = this |
27+
name = "write"
28+
)
29+
}
30+
}
31+
32+
}
33+
34+
module ClearTextLogging {
35+
36+
abstract class Sink extends TaintSink {
37+
override predicate sinks(TaintKind kind) {
38+
kind instanceof SensitiveData
39+
}
40+
}
41+
42+
class PrintSink extends Sink {
43+
PrintSink() {
44+
exists(CallNode call |
45+
call.getAnArg() = this and
46+
thePrintFunction().(FunctionObject).getACall() = call
47+
)
48+
}
49+
}
50+
51+
class LoggingSink extends Sink {
52+
LoggingSink() {
53+
exists(CallNode call, AttrNode meth, string name |
54+
call.getFunction() = meth and
55+
meth.getObject(name).(NameNode).getId().matches("logg%") and
56+
call.getAnArg() = this |
57+
name = "error" or
58+
name = "warn" or
59+
name = "warning" or
60+
name = "debug" or
61+
name = "info"
62+
)
63+
}
64+
}
65+
66+
}

0 commit comments

Comments
 (0)