Skip to content

Commit f6784f6

Browse files
committed
add a js/empty-password-in-configuration-file query
1 parent 1912c56 commit f6784f6

File tree

10 files changed

+140
-38
lines changed

10 files changed

+140
-38
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/** Classses and predicates for reasoning about passwords in configuration files. */
2+
3+
import javascript
4+
import semmle.javascript.RestrictedLocations
5+
import semmle.javascript.security.SensitiveActions
6+
7+
/**
8+
* Holds if some JSON or YAML file contains a property with name `key`
9+
* and value `val`, where `valElement` is the entity corresponding to the
10+
* value.
11+
*
12+
* The following are excluded by this predicate:
13+
* - Dependencies in `package.json` files.
14+
* - Values that look like template delimiters.
15+
* - Files that appear to be API-specifications, dictonary, test, or example.
16+
*/
17+
predicate config(string key, string val, Locatable valElement) {
18+
(
19+
exists(JSONObject obj | not exists(PackageJSON p | obj = p.getADependenciesObject(_)) |
20+
obj.getPropValue(key) = valElement and
21+
val = valElement.(JSONString).getValue()
22+
)
23+
or
24+
exists(YAMLMapping m, YAMLString keyElement |
25+
m.maps(keyElement, valElement) and
26+
key = keyElement.getValue() and
27+
(
28+
val = valElement.(YAMLString).getValue()
29+
or
30+
valElement.toString() = "" and
31+
val = ""
32+
)
33+
)
34+
) and
35+
// exclude possible templates
36+
not val.regexpMatch(Templating::getDelimiterMatchingRegexp()) and
37+
not exclude(valElement.getFile())
38+
}
39+
40+
/**
41+
* Holds if file `f` should be excluded because it looks like it may be
42+
* an API specification, a dictionary file, or a test or example.
43+
*/
44+
predicate exclude(File f) {
45+
f.getRelativePath().regexpMatch("(?i).*(^|/)(lang(uage)?s?|locales?|tests?|examples?|i18n)/.*")
46+
or
47+
f.getStem().regexpMatch("(?i)translations?")
48+
or
49+
f.getExtension().toLowerCase() = "raml"
50+
}

javascript/ql/src/Security/CWE-313/PasswordInConfigurationFile.ql

Lines changed: 2 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,47 +14,12 @@
1414
*/
1515

1616
import javascript
17-
import semmle.javascript.RestrictedLocations
18-
import semmle.javascript.security.SensitiveActions
19-
20-
/**
21-
* Holds if some JSON or YAML file contains a property with name `key`
22-
* and value `val`, where `valElement` is the entity corresponding to the
23-
* value.
24-
*
25-
* Dependencies in `package.json` files are excluded by this predicate.
26-
*/
27-
predicate config(string key, string val, Locatable valElement) {
28-
exists(JSONObject obj | not exists(PackageJSON p | obj = p.getADependenciesObject(_)) |
29-
obj.getPropValue(key) = valElement and
30-
val = valElement.(JSONString).getValue()
31-
)
32-
or
33-
exists(YAMLMapping m, YAMLString keyElement |
34-
m.maps(keyElement, valElement) and
35-
key = keyElement.getValue() and
36-
val = valElement.(YAMLString).getValue()
37-
)
38-
}
39-
40-
/**
41-
* Holds if file `f` should be excluded because it looks like it may be
42-
* an API specification, a dictionary file, or a test or example.
43-
*/
44-
predicate exclude(File f) {
45-
f.getRelativePath().regexpMatch("(?i).*(^|/)(lang(uage)?s?|locales?|tests?|examples?|i18n)/.*")
46-
or
47-
f.getStem().regexpMatch("(?i)translations?")
48-
or
49-
f.getExtension().toLowerCase() = "raml"
50-
}
17+
import semmle.javascript.security.PasswordInConfigurationFileQuery
5118

5219
from string key, string val, Locatable valElement, string pwd
5320
where
5421
config(key, val, valElement) and
5522
val != "" and
56-
// exclude possible templates
57-
not val.regexpMatch(Templating::getDelimiterMatchingRegexp()) and
5823
(
5924
key.toLowerCase() = "password" and
6025
pwd = val and
@@ -66,6 +31,5 @@ where
6631
// look for `password=...`, but exclude `password=;`, `password="$(...)"`,
6732
// `password=%s` and `password==`
6833
pwd = val.regexpCapture("(?is).*password\\s*=\\s*(?!;|\"?[$`]|%s|=)(\\S+).*", 1)
69-
) and
70-
not exclude(valElement.getFile())
34+
)
7135
select valElement.(FirstLineOf), "Hard-coded password '" + pwd + "' in configuration file."
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>The use of an empty string as a password in a configuration file is not secure.</p>
7+
8+
</overview>
9+
<recommendation>
10+
<p>Choose a proper password and encrypt it if you need to store it in the configuration file.</p>
11+
12+
</recommendation>
13+
<references>
14+
15+
</references>
16+
</qhelp>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* @name Password in configuration file
3+
* @description Storing unencrypted passwords in configuration files is unsafe.
4+
* @kind problem
5+
* @problem.severity warning
6+
* @security-severity 7.5
7+
* @precision medium
8+
* @id js/empty-password-in-configuration-file
9+
* @tags security
10+
* external/cwe/cwe-258
11+
* external/cwe/cwe-862
12+
*/
13+
14+
import javascript
15+
import semmle.javascript.security.PasswordInConfigurationFileQuery
16+
17+
from string key, string val, Locatable valElement
18+
where
19+
config(key, val, valElement) and
20+
(
21+
val = "" and key.toLowerCase() = "password"
22+
or
23+
key.toLowerCase() != "readme" and
24+
// look for `password=;`, `password=`, `password= `, `password==`.
25+
val.regexpMatch("(?is).*password\\s*(==?|:)\\s*(\\\"\\\"|''|``|;|:)?\\s*($|;|&|]|\\n).*")
26+
)
27+
select valElement.(FirstLineOf), "Empty password in configuration file."
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: newQuery
3+
---
4+
* A new query `js/empty-password-in-configuration-file` has been added. The query detects empty passwords in configuration files.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
| tst.json:6:17:6:18 | "" | Empty password in configuration file. |
2+
| tst.json:12:15:12:39 | "userna ... word= " | Empty password in configuration file. |
3+
| tst.json:15:15:15:38 | "passwo ... me=foo" | Empty password in configuration file. |
4+
| tst.yml:10:13:10:12 | | Empty password in configuration file. |
5+
| tst.yml:14:12:14:34 | passwor ... me=foo; | Empty password in configuration file. |
6+
| tst.yml:16:12:16:38 | passwor ... ="foo"; | Empty password in configuration file. |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Security/CWE-862/EmptyPasswordInConfigurationFile.ql
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log("foo");
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[
2+
{
3+
"password": "$pwd"
4+
},
5+
{
6+
"password": ""
7+
},
8+
{
9+
"password": "ff"
10+
},
11+
{
12+
"config": "username=foo&password= "
13+
},
14+
{
15+
"config": "password=&username=foo"
16+
}
17+
]
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
steps:
2+
- script: |
3+
PASSWORD="$(PASSWORD)" npm install
4+
OTHER_PASSWORD=`get password` yarn install
5+
username: <%= ENV['USERNAME'] %>
6+
password: <%= ENV['PASSWORD'] %>
7+
password: change_me
8+
others:
9+
- one:
10+
password:
11+
- two:
12+
password: <%= ENV['OTHER_PASSWORD'] %>
13+
- three:
14+
config: password=;username=foo;
15+
- four:
16+
config: password="";username="foo";

0 commit comments

Comments
 (0)