Skip to content

Commit 7e7e30c

Browse files
authored
Merge pull request #73 from esben-semmle/js/cleartext-logging-query
Approved by xiemaisi
2 parents 7661a98 + 2b9f5c3 commit 7e7e30c

26 files changed

+663
-26
lines changed

change-notes/1.18/analysis-javascript.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585

8686
| **Query** | **Tags** | **Purpose** |
8787
|-----------------------------|-----------|--------------------------------------------------------------------|
88+
| Clear-text logging of sensitive information (`js/clear-text-logging`) | security, external/cwe/cwe-312, external/cwe/cwe-315, external/cwe/cwe-359 | Highlights logging of sensitive information, indicating a violation of [CWE-312](https://cwe.mitre.org/data/definitions/312.html). Results shown on LGTM by default. |
8889
| Disabling Electron webSecurity (`js/disabling-electron-websecurity`) | security, frameworks/electron | Highlights Electron browser objects that are created with the `webSecurity` property set to false. Results shown on LGTM by default. |
8990
| Enabling Electron allowRunningInsecureContent (`js/enabling-electron-insecure-content`) | security, frameworks/electron | Highlights Electron browser objects that are created with the `allowRunningInsecureContent` property set to true. Results shown on LGTM by default. |
9091
| Use of externally-controlled format string (`js/tainted-format-string`) | security, external/cwe/cwe-134 | Highlights format strings containing user-provided data, indicating a violation of [CWE-134](https://cwe.mitre.org/data/definitions/134.html). Results shown on LGTM by default. |

javascript/config/suites/javascript/security

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
+ semmlecode-javascript-queries/Security/CWE-134/TaintedFormatString.ql: /Security/CWE/CWE-134
1010
+ semmlecode-javascript-queries/Security/CWE-209/StackTraceExposure.ql: /Security/CWE/CWE-209
1111
+ semmlecode-javascript-queries/Security/CWE-312/CleartextStorage.ql: /Security/CWE/CWE-312
12+
+ semmlecode-javascript-queries/Security/CWE-312/CleartextLogging.ql: /Security/CWE/CWE-312
1213
+ semmlecode-javascript-queries/Security/CWE-313/PasswordInConfigurationFile.ql: /Security/CWE/CWE-313
1314
+ semmlecode-javascript-queries/Security/CWE-327/BrokenCryptoAlgorithm.ql: /Security/CWE/CWE-327
1415
+ semmlecode-javascript-queries/Security/CWE-338/InsecureRandomness.ql: /Security/CWE/CWE-338
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: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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 problem
6+
* @problem.severity error
7+
* @precision high
8+
* @id js/clear-text-logging
9+
* @tags security
10+
* external/cwe/cwe-312
11+
* external/cwe/cwe-315
12+
* external/cwe/cwe-359
13+
*/
14+
15+
import javascript
16+
import semmle.javascript.security.dataflow.CleartextLogging::CleartextLogging
17+
18+
/**
19+
* Holds if `tl` is used in a browser environment.
20+
*/
21+
predicate inBrowserEnvironment(TopLevel tl) {
22+
tl instanceof InlineScript or
23+
tl instanceof CodeInAttribute or
24+
exists (GlobalVarAccess e |
25+
e.getTopLevel() = tl |
26+
e.getName() = "window"
27+
) or
28+
exists (Module m | inBrowserEnvironment(m) |
29+
tl = m.getAnImportedModule() or
30+
m = tl.(Module).getAnImportedModule()
31+
)
32+
}
33+
34+
from Configuration cfg, Source source, DataFlow::Node sink
35+
where cfg.hasFlow(source, sink) and
36+
// ignore logging to the browser console (even though it is not a good practice)
37+
not inBrowserEnvironment(sink.asExpr().getTopLevel())
38+
select sink, "Sensitive data returned by $@ is logged here.", source, source.describe()

javascript/ql/src/Security/CWE-312/CleartextStorage.qhelp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,22 @@ which are stored on the machine of the end-user.
1515
<p>
1616
Ensure that sensitive information is always encrypted before being stored.
1717
If possible, avoid placing sensitive information in cookies altogether.
18-
Instead, prefer storing, in the cookie, a key that can be used to lookup the
18+
Instead, prefer storing, in the cookie, a key that can be used to look up the
1919
sensitive information.
2020
</p>
2121
<p>
2222
In general, decrypt sensitive information only at the point where it is
2323
necessary for it to be used in cleartext.
2424
</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+
2534
</recommendation>
2635

2736
<example>

javascript/ql/src/javascript.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ import semmle.javascript.frameworks.DigitalOcean
5959
import semmle.javascript.frameworks.Electron
6060
import semmle.javascript.frameworks.jQuery
6161
import semmle.javascript.frameworks.LodashUnderscore
62+
import semmle.javascript.frameworks.Logging
6263
import semmle.javascript.frameworks.HttpFrameworks
6364
import semmle.javascript.frameworks.NoSQL
6465
import semmle.javascript.frameworks.PkgCloud

javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -316,13 +316,12 @@ module TaintTracking {
316316
}
317317

318318
/**
319-
* A taint propagating data flow edge arising from string append and other string
320-
* operations defined in the standard library.
319+
* A taint propagating data flow edge arising from string concatenations.
321320
*
322-
* Note that since we cannot easily distinguish string append from addition, we consider
323-
* any `+` operation to propagate taint.
321+
* Note that since we cannot easily distinguish string append from addition,
322+
* we consider any `+` operation to propagate taint.
324323
*/
325-
private class StringManipulationTaintStep extends AdditionalTaintStep, DataFlow::ValueNode {
324+
class StringConcatenationTaintStep extends AdditionalTaintStep, DataFlow::ValueNode {
326325
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
327326
succ = this and
328327
(
@@ -336,8 +335,19 @@ module TaintTracking {
336335
or
337336
// templating propagates taint
338337
astNode.(TemplateLiteral).getAnElement() = pred.asExpr()
339-
or
340-
// other string operations that propagate taint
338+
)
339+
}
340+
}
341+
342+
/**
343+
* A taint propagating data flow edge arising from string manipulation
344+
* functions defined in the standard library.
345+
*/
346+
private class StringManipulationTaintStep extends AdditionalTaintStep, DataFlow::ValueNode {
347+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
348+
succ = this and
349+
(
350+
// string operations that propagate taint
341351
exists (string name | name = astNode.(MethodCallExpr).getMethodName() |
342352
pred.asExpr() = astNode.(MethodCallExpr).getReceiver() and
343353
( // sorted, interesting, properties of String.prototype
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/**
2+
* Provides classes for working with logging libraries.
3+
*/
4+
5+
import javascript
6+
7+
/**
8+
* A call to a logging mechanism.
9+
*/
10+
abstract class LoggerCall extends DataFlow::CallNode {
11+
12+
/**
13+
* Gets a node that contributes to the logged message.
14+
*/
15+
abstract DataFlow::Node getAMessageComponent();
16+
17+
}
18+
19+
/**
20+
* Gets a log level name that is used in RFC5424, `npm`, `console`.
21+
*/
22+
private string getAStandardLoggerMethodName() {
23+
result = "crit" or
24+
result = "debug" or
25+
result = "error" or
26+
result = "emerg" or
27+
result = "fatal" or
28+
result = "info" or
29+
result = "log" or
30+
result = "notice" or
31+
result = "silly" or
32+
result = "trace" or
33+
result = "warn"
34+
}
35+
36+
/**
37+
* Provides classes for working the builtin Node.js/Browser `console`.
38+
*/
39+
private module Console {
40+
41+
/**
42+
* Gets a data flow source node for the console library.
43+
*/
44+
private DataFlow::SourceNode console() {
45+
result = DataFlow::moduleImport("console") or
46+
result = DataFlow::globalVarRef("console")
47+
}
48+
49+
/**
50+
* A call to the console logging mechanism.
51+
*/
52+
class ConsoleLoggerCall extends LoggerCall {
53+
string name;
54+
55+
ConsoleLoggerCall() {
56+
(
57+
name = getAStandardLoggerMethodName() or
58+
name = "assert"
59+
) and
60+
this = console().getAMethodCall(name)
61+
}
62+
63+
override DataFlow::Node getAMessageComponent() {
64+
if name = "assert" then
65+
result = getArgument([1..getNumArgument()])
66+
else
67+
result = getAnArgument()
68+
}
69+
70+
}
71+
72+
}
73+
74+
/**
75+
* Provides classes for working with [loglevel](https://github.com/pimterry/loglevel).
76+
*/
77+
private module Loglevel {
78+
79+
/**
80+
* A call to the loglevel logging mechanism.
81+
*/
82+
class LoglevelLoggerCall extends LoggerCall {
83+
LoglevelLoggerCall() {
84+
this = DataFlow::moduleMember("loglevel", getAStandardLoggerMethodName()).getACall()
85+
}
86+
87+
override DataFlow::Node getAMessageComponent() {
88+
result = getAnArgument()
89+
}
90+
91+
}
92+
93+
}
94+
95+
96+
/**
97+
* Provides classes for working with [winston](https://github.com/winstonjs/winston).
98+
*/
99+
private module Winston {
100+
101+
/**
102+
* A call to the winston logging mechanism.
103+
*/
104+
class WinstonLoggerCall extends LoggerCall, DataFlow::MethodCallNode {
105+
WinstonLoggerCall() {
106+
this = DataFlow::moduleMember("winston", "createLogger").getACall().getAMethodCall(getAStandardLoggerMethodName())
107+
}
108+
109+
override DataFlow::Node getAMessageComponent() {
110+
if getMethodName() = "log" then
111+
result = getOptionArgument(0, "message")
112+
else
113+
result = getAnArgument()
114+
}
115+
116+
}
117+
118+
}
119+
120+
/**
121+
* Provides classes for working with [log4js](https://github.com/log4js-node/log4js-node).
122+
*/
123+
private module log4js {
124+
125+
/**
126+
* A call to the log4js logging mechanism.
127+
*/
128+
class Log4jsLoggerCall extends LoggerCall {
129+
Log4jsLoggerCall() {
130+
this = DataFlow::moduleMember("log4js", "getLogger").getACall().getAMethodCall(getAStandardLoggerMethodName())
131+
}
132+
133+
override DataFlow::Node getAMessageComponent() {
134+
result = getAnArgument()
135+
}
136+
137+
}
138+
139+
}

javascript/ql/src/semmle/javascript/security/SensitiveActions.qll

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,38 @@
1111

1212
import javascript
1313

14-
/** A regular expression that identifies strings that look like they represent secret data that are not passwords. */
15-
private string suspiciousNonPassword() {
16-
result = "(?is).*(secret|account|accnt|(?<!un)trusted).*"
17-
}
18-
/** A regular expression that identifies strings that look like they represent secret data that are passwords. */
19-
private string suspiciousPassword() {
20-
result = "(?is).*(password|passwd).*"
21-
}
22-
23-
/** A regular expression that identifies strings that look like they represent secret data. */
24-
private string suspicious() {
25-
result = suspiciousPassword() or result = suspiciousNonPassword()
26-
}
27-
2814
/**
29-
* A string for `match` that identifies strings that look like they represent secret data that is
30-
* hashed or encrypted.
15+
* Provides heuristics for identifying names related to sensitive information.
16+
*
17+
* INTERNAL: Do not use directly.
3118
*/
32-
private string nonSuspicious() {
33-
result = "(?is).*(hash|(?<!un)encrypted|\\bcrypt\\b).*"
19+
module HeuristicNames {
20+
21+
/** A regular expression that identifies strings that look like they represent secret data that are not passwords. */
22+
string suspiciousNonPassword() {
23+
result = "(?is).*(secret|account|accnt|(?<!un)trusted).*"
24+
}
25+
26+
/** A regular expression that identifies strings that look like they represent secret data that are passwords. */
27+
string suspiciousPassword() {
28+
result = "(?is).*(password|passwd).*"
29+
}
30+
31+
/** A regular expression that identifies strings that look like they represent secret data. */
32+
string suspicious() {
33+
result = suspiciousPassword() or result = suspiciousNonPassword()
34+
}
35+
36+
/**
37+
* A regular expression that identifies strings that look like they represent data that is
38+
* hashed or encrypted.
39+
*/
40+
string nonSuspicious() {
41+
result = "(?is).*(redact|censor|obfuscate|hash|md5|sha|((?<!un)(en))?(crypt|code)).*"
42+
}
43+
3444
}
45+
private import HeuristicNames
3546

3647
/** An expression that might contain sensitive data. */
3748
abstract class SensitiveExpr extends Expr {

0 commit comments

Comments
 (0)