Skip to content

Commit 0070e30

Browse files
committed
Ruby: Add rb/clear-text-storage-sensitive-data query
1 parent 7084718 commit 0070e30

File tree

6 files changed

+181
-0
lines changed

6 files changed

+181
-0
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* Provides default sources, sinks and sanitizers for reasoning about
3+
* cleartext storage of sensitive information, as well as extension points for
4+
* adding your own.
5+
*/
6+
7+
private import ruby
8+
private import codeql.ruby.DataFlow
9+
private import codeql.ruby.Concepts
10+
private import internal.CleartextSources
11+
12+
/**
13+
* Provides default sources, sinks and sanitizers for reasoning about
14+
* cleartext storage of sensitive information, as well as extension points for
15+
* adding your own.
16+
*/
17+
module CleartextStorage {
18+
/**
19+
* A data flow source for cleartext storage of sensitive information.
20+
*/
21+
class Source = CleartextSources::Source;
22+
23+
/**
24+
* A sanitizer for cleartext storage of sensitive information.
25+
*/
26+
class Sanitizer = CleartextSources::Sanitizer;
27+
28+
/** Holds if `nodeFrom` taints `nodeTo`. */
29+
predicate isAdditionalTaintStep = CleartextSources::isAdditionalTaintStep/2;
30+
31+
/**
32+
* A data flow sink for cleartext storage of sensitive information.
33+
*/
34+
abstract class Sink extends DataFlow::Node { }
35+
36+
/**
37+
* A node representing data written to the filesystem.
38+
*/
39+
private class FileSystemWriteAccessDataNodeAsSink extends Sink {
40+
FileSystemWriteAccessDataNodeAsSink() { this = any(FileSystemWriteAccess write).getADataNode() }
41+
}
42+
43+
/**
44+
* A node representing data written to a database using an ORM system.
45+
*/
46+
private class OrmWriteAccessValueAsSink extends Sink {
47+
// instanceof OrmWriteAccess {
48+
// TODO: we generally won't get flow from `value` to `this`
49+
// Should the node be on value? Or should there be an additional flow step from
50+
// value to the write node?
51+
OrmWriteAccessValueAsSink() {
52+
exists(OrmWriteAccess write, string fieldName |
53+
fieldName = write.getFieldNameAssignedTo(this)
54+
)
55+
}
56+
}
57+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* Provides a taint-tracking configuration for "Clear-text storage of sensitive information".
3+
*
4+
* Note, for performance reasons: only import this file if
5+
* `Configuration` is needed, otherwise `CleartextStorageCustomizations` should be
6+
* imported instead.
7+
*/
8+
9+
private import ruby
10+
private import codeql.ruby.DataFlow
11+
private import codeql.ruby.TaintTracking
12+
import CleartextStorageCustomizations::CleartextStorage
13+
private import CleartextStorageCustomizations::CleartextStorage as CleartextStorage
14+
15+
/**
16+
* A taint-tracking configuration for detecting "Clear-text storage of sensitive information".
17+
*/
18+
class Configuration extends TaintTracking::Configuration {
19+
Configuration() { this = "CleartextStorage" }
20+
21+
override predicate isSource(DataFlow::Node source) { source instanceof CleartextStorage::Source }
22+
23+
override predicate isSink(DataFlow::Node sink) { sink instanceof CleartextStorage::Sink }
24+
25+
override predicate isSanitizer(DataFlow::Node node) {
26+
super.isSanitizer(node)
27+
or
28+
node instanceof CleartextStorage::Sanitizer
29+
}
30+
31+
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
32+
CleartextStorage::isAdditionalTaintStep(nodeFrom, nodeTo)
33+
}
34+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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.
10+
</p>
11+
</overview>
12+
13+
<recommendation>
14+
<p>
15+
Ensure that sensitive information is always encrypted before being stored.
16+
</p>
17+
<p>
18+
In general, decrypt sensitive information only at the point where it is
19+
necessary for it to be used in cleartext.
20+
</p>
21+
22+
<p>
23+
24+
Be aware that external processes often store the <code>standard
25+
out</code> and <code>standard error</code> streams of the application,
26+
causing logged sensitive information to be stored as well.
27+
28+
</p>
29+
30+
</recommendation>
31+
32+
<example>
33+
<p>
34+
The following example code stores user credentials (in this case, their password)
35+
to disk in plain text:
36+
</p>
37+
<sample src="examples/CleartextStorageBad.rb"/>
38+
<p>
39+
Instead, the credentials should be masked or redacted before storing:
40+
</p>
41+
<sample src="examples/CleartextStorageGood.rb"/>
42+
</example>
43+
44+
45+
<references>
46+
47+
<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>
48+
<li>M. Howard and D. LeBlanc, <i>Writing Secure Code</i>, 2nd Edition, Chapter 9 - 'Protecting Secret Data', p. 299. Microsoft, 2002.</li>
49+
50+
</references>
51+
</qhelp>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* @name Clear-text storage of sensitive information
3+
* @description Storing sensitive information without encryption or hashing can
4+
* expose it to an attacker.
5+
* @kind path-problem
6+
* @problem.severity error
7+
* @security-severity 7.5
8+
* @precision high
9+
* @id rb/clear-text-storage-sensitive-data
10+
* @tags security
11+
* external/cwe/cwe-312
12+
* external/cwe/cwe-359
13+
* external/cwe/cwe-532
14+
*/
15+
16+
import ruby
17+
import codeql.ruby.security.CleartextStorageQuery
18+
import codeql.ruby.DataFlow
19+
import DataFlow::PathGraph
20+
21+
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
22+
where config.hasFlowPath(source, sink)
23+
select sink.getNode(), source, sink, "Sensitive data returned by $@ is stored here.",
24+
source.getNode(), source.getNode().(Source).describe()
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class UserSession
2+
def login(username, password)
3+
# ...
4+
logfile = File.open("login_attempts.log")
5+
logfile.puts "login with password: #{password})"
6+
end
7+
end
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
class UserSession
2+
def login(username, password)
3+
# ...
4+
password_escaped = password.sub(/.*/, "[redacted]")
5+
logfile = File.open("login_attempts.log")
6+
logfile.puts "login with password: #{password_escaped})"
7+
end
8+
end

0 commit comments

Comments
 (0)