Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 79ebd56

Browse files
committed
Python: Add library support for cookies. Update and extend sensitive data library.
1 parent ae2a68b commit 79ebd56

8 files changed

Lines changed: 287 additions & 61 deletions

File tree

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

python/ql/src/semmle/python/security/SensitiveData.qll

Lines changed: 129 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -13,91 +13,160 @@ import python
1313
import semmle.python.security.TaintTracking
1414

1515

16-
/** A regular expression that identifies strings that look like they represent secret data that are not passwords. */
17-
private string suspiciousNonPassword() {
18-
result = "(?is).*(account|accnt|(?<!un)trusted).*"
19-
}
20-
/** A regular expression that identifies strings that look like they represent secret data that are passwords. */
21-
private string suspiciousPassword() {
22-
result = "(?is).*(password|passwd).*"
23-
}
24-
25-
/** A regular expression that identifies strings that look like they represent secret data. */
26-
private string suspicious() {
27-
result = suspiciousPassword() or result = suspiciousNonPassword()
28-
}
29-
3016
/**
31-
* A string for `match` that identifies strings that look like they represent secret data that is
32-
* hashed or encrypted.
17+
* Provides heuristics for identifying names related to sensitive information.
18+
*
19+
* INTERNAL: Do not use directly.
20+
* This is copied from the javascript library, but should be language independent.
3321
*/
34-
private string nonSuspicious() {
35-
result = "(?is).*(hash|(?<!un)encrypted|\\bcrypt\\b).*"
36-
}
22+
private module HeuristicNames {
23+
24+
/**
25+
* Gets a regular expression that identifies strings that may indicate the presence of secret
26+
* or trusted data.
27+
*/
28+
string maybeSecret() { result = "(?is).*((?<!is)secret|(?<!un|is)trusted).*" }
29+
30+
/**
31+
* Gets a regular expression that identifies strings that may indicate the presence of
32+
* user names or other account information.
33+
*/
34+
string maybeAccountInfo() {
35+
result = "(?is).*acc(ou)?nt.*" or
36+
result = "(?is).*(puid|username|userid).*"
37+
}
3738

38-
/** An expression that might contain sensitive data. */
39-
abstract class SensitiveExpr extends Expr { }
40-
41-
/** A method access that might produce sensitive data. */
42-
class SensitiveCall extends SensitiveExpr, Call {
43-
SensitiveCall() {
44-
exists(string name |
45-
name = this.getFunc().(Name).getId() or
46-
name = this.getFunc().(Attribute).getName() or
47-
exists(StringObject s |
48-
this.getAnArg().refersTo(s) |
49-
name = s.getText()
50-
)
51-
|
52-
name.regexpMatch(suspicious()) and
53-
not name.regexpMatch(nonSuspicious())
54-
)
39+
/**
40+
* Gets a regular expression that identifies strings that may indicate the presence of
41+
* a password or an authorization key.
42+
*/
43+
string maybePassword() {
44+
result = "(?is).*pass(wd|word|code|phrase)(?!.*question).*" or
45+
result = "(?is).*(auth(entication|ori[sz]ation)?)key.*"
5546
}
56-
}
5747

58-
/** An access to a variable or property that might contain sensitive data. */
59-
abstract class SensitiveVariableAccess extends SensitiveExpr {
48+
/**
49+
* Gets a regular expression that identifies strings that may indicate the presence of
50+
* a certificate.
51+
*/
52+
string maybeCertificate() { result = "(?is).*(cert)(?!.*(format|name)).*" }
53+
54+
/**
55+
* Gets a regular expression that identifies strings that may indicate the presence
56+
* of sensitive data, with `classification` describing the kind of sensitive data involved.
57+
*/
58+
string maybeSensitive(SensitiveData data) {
59+
result = maybeSecret() and data instanceof SensitiveData::Secret
60+
or
61+
result = maybeAccountInfo() and data instanceof SensitiveData::Id
62+
or
63+
result = maybePassword() and data instanceof SensitiveData::Password
64+
or
65+
result = maybeCertificate() and data instanceof SensitiveData::Certificate
66+
}
6067

61-
string name;
68+
/**
69+
* Gets a regular expression that identifies strings that may indicate the presence of data
70+
* that is hashed or encrypted, and hence rendered non-sensitive.
71+
*/
72+
string notSensitive() {
73+
result = "(?is).*(redact|censor|obfuscate|hash|md5|sha|((?<!un)(en))?(crypt|code)).*"
74+
}
6275

63-
SensitiveVariableAccess() {
64-
this.(Name).getId() = name or
65-
this.(Attribute).getName() = name
66-
}
76+
bindingset[name]
77+
SensitiveData getSensitiveDataForName(string name) {
78+
name.regexpMatch(HeuristicNames::maybeSensitive(result)) and
79+
not name.regexpMatch(HeuristicNames::notSensitive())
80+
}
6781

6882
}
6983

70-
/** An access to a variable or property that might contain sensitive data. */
71-
private class BasicSensitiveVariableAccess extends SensitiveVariableAccess {
84+
abstract class SensitiveData extends TaintKind {
7285

73-
BasicSensitiveVariableAccess() {
74-
name.regexpMatch(suspicious()) and not name.regexpMatch(nonSuspicious())
75-
}
86+
bindingset[this]
87+
SensitiveData() { this = this }
7688

7789
}
7890

79-
class SensitiveData extends TaintKind {
91+
module SensitiveData {
8092

81-
SensitiveData() {
82-
this = "sensitive.data"
93+
class Secret extends SensitiveData {
94+
Secret() { this = "sensitive.data.secret" }
95+
override string repr() { result = "a secret" }
8396
}
8497

85-
}
98+
class Id extends SensitiveData {
99+
Id() { this = "sensitive.data.id" }
100+
override string repr() { result = "an ID" }
101+
}
102+
103+
class Password extends SensitiveData {
104+
Password() { this = "sensitive.data.password" }
105+
override string repr() { result = "a password" }
106+
}
107+
108+
class Certificate extends SensitiveData {
109+
Certificate() { this = "sensitive.data.certificate" }
110+
override string repr() { result = "a certificate or key" }
111+
}
86112

113+
private SensitiveData fromFunction(FunctionObject f) {
114+
result = HeuristicNames::getSensitiveDataForName(f.getName())
115+
or
116+
// This is particularly to pick up methods with an argument like "password", which
117+
// may indicate a lookup.
118+
exists(string name | name = f.getFunction().getAnArg().asName().getId() |
119+
result = HeuristicNames::getSensitiveDataForName(name)
120+
)
121+
}
87122

88-
class SensitiveDataSource extends TaintSource {
123+
abstract class Source extends TaintSource {
124+
125+
abstract string repr();
89126

90-
SensitiveDataSource() {
91-
this.(ControlFlowNode).getNode() instanceof SensitiveExpr
92127
}
93128

94-
override string toString() {
95-
result = "sensitive.data.source"
129+
private class SensitiveCallSource extends Source {
130+
131+
SensitiveData data;
132+
133+
SensitiveCallSource() {
134+
exists(FunctionObject callee |
135+
callee.getACall() = this |
136+
data = fromFunction(callee)
137+
)
138+
}
139+
140+
override predicate isSourceOf(TaintKind kind) {
141+
kind = data
142+
}
143+
144+
override string repr() {
145+
result = "Call returning " + data.repr()
146+
}
147+
96148
}
97149

98-
override predicate isSourceOf(TaintKind kind) {
99-
kind instanceof SensitiveData
150+
/** An access to a variable or property that might contain sensitive data. */
151+
private class SensitiveVariableAccess extends SensitiveData::Source {
152+
153+
SensitiveData data;
154+
155+
SensitiveVariableAccess() {
156+
data = HeuristicNames::getSensitiveDataForName(this.(AttrNode).getName())
157+
}
158+
159+
override predicate isSourceOf(TaintKind kind) {
160+
kind = data
161+
}
162+
163+
override string repr() {
164+
result = "an attribute or property containing " + data.repr()
165+
}
166+
100167
}
101168

102169
}
103170

171+
//Backwards compatibility
172+
class SensitiveDataSource = SensitiveData::Source;

python/ql/src/semmle/python/web/Http.qll

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,19 @@ class UntrustedCookie extends TaintKind {
7272

7373
}
7474

75+
abstract class CookieOperation extends @py_flow_node {
76+
77+
abstract string toString();
78+
79+
abstract ControlFlowNode getKey();
80+
81+
abstract ControlFlowNode getValue();
82+
83+
}
84+
85+
abstract class CookieGet extends CookieOperation {}
86+
87+
abstract class CookieSet extends CookieOperation {}
7588

7689
/** Generic taint sink in a http response */
7790
abstract class HttpResponseTaintSink extends TaintSink {

python/ql/src/semmle/python/web/bottle/Response.qll

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,17 @@ class BottleHandlerFunctionResult extends HttpResponseTaintSink {
5656

5757
}
5858

59+
class BottleCookieSet extends CookieSet, CallNode {
60+
61+
BottleCookieSet() {
62+
any(BottleResponse r).taints(this.getFunction().(AttrNode).getObject("set_cookie"))
63+
}
64+
65+
override string toString() { result = this.(CallNode).toString() }
66+
67+
override ControlFlowNode getKey() { result = this.getArg(0) }
68+
69+
override ControlFlowNode getValue() { result = this.getArg(1) }
70+
71+
}
72+

python/ql/src/semmle/python/web/django/Response.qll

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,5 +83,16 @@ class DjangoResponseContent extends HttpResponseTaintSink {
8383

8484
}
8585

86+
class DjangoCookieSet extends CookieSet, CallNode {
8687

88+
DjangoCookieSet() {
89+
any(DjangoResponse r).taints(this.getFunction().(AttrNode).getObject("set_cookie"))
90+
}
91+
92+
override string toString() { result = this.(CallNode).toString() }
93+
94+
override ControlFlowNode getKey() { result = this.getArg(0) }
8795

96+
override ControlFlowNode getValue() { result = this.getArg(1) }
97+
98+
}

python/ql/src/semmle/python/web/flask/General.qll

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,18 @@ private class AsView extends TaintSource {
115115

116116
}
117117

118+
119+
class FlaskCookieSet extends CookieSet, CallNode {
120+
121+
FlaskCookieSet() {
122+
this.getFunction().(AttrNode).getObject("set_cookie").refersTo(_, theFlaskReponseClass(), _)
123+
}
124+
125+
override string toString() { result = this.(CallNode).toString() }
126+
127+
override ControlFlowNode getKey() { result = this.getArg(0) }
128+
129+
override ControlFlowNode getValue() { result = this.getArg(1) }
130+
131+
132+
}

python/ql/src/semmle/python/web/pyramid/Response.qll

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import python
33

44
import semmle.python.security.TaintTracking
55
import semmle.python.security.strings.Basic
6+
import semmle.python.web.Http
67

78
private import semmle.python.web.pyramid.View
89
private import semmle.python.web.Http
@@ -27,3 +28,21 @@ class PyramidRoutedResponse extends HttpResponseTaintSink {
2728
}
2829

2930
}
31+
32+
33+
class PyramidCookieSet extends CookieSet, CallNode {
34+
35+
PyramidCookieSet() {
36+
exists(ControlFlowNode f |
37+
f = this.getFunction().(AttrNode).getObject("set_cookie") and
38+
f.refersTo(_, ModuleObject::named("pyramid").attr("Response"), _)
39+
)
40+
}
41+
42+
override string toString() { result = this.(CallNode).toString() }
43+
44+
override ControlFlowNode getKey() { result = this.getArg(0) }
45+
46+
override ControlFlowNode getValue() { result = this.getArg(1) }
47+
48+
}

0 commit comments

Comments
 (0)