66
77private import ruby
88private import codeql.ruby.DataFlow
9- private import codeql.ruby.TaintTracking:: TaintTracking
109private import codeql.ruby.Concepts
11- private import codeql.ruby.dataflow.RemoteFlowSources
12- private import internal.SensitiveDataHeuristics:: HeuristicNames
13- private import codeql.ruby.CFG
14- private import codeql.ruby.dataflow.SSA
10+ private import internal.CleartextSources
1511
1612/**
1713 * Provides default sources, sinks and sanitizers for reasoning about
@@ -22,265 +18,25 @@ module CleartextLogging {
2218 /**
2319 * A data flow source for cleartext logging of sensitive information.
2420 */
25- abstract class Source extends DataFlow:: Node {
26- /** Gets a string that describes the type of this data flow source. */
27- abstract string describe ( ) ;
28- }
29-
30- /**
31- * A data flow sink for cleartext logging of sensitive information.
32- */
33- abstract class Sink extends DataFlow:: Node { }
21+ class Source = CleartextSources:: Source ;
3422
3523 /**
3624 * A sanitizer for cleartext logging of sensitive information.
3725 */
38- abstract class Sanitizer extends DataFlow :: Node { }
26+ class Sanitizer = CleartextSources :: Sanitizer ;
3927
40- /**
41- * Holds if `re` may be a regular expression that can be used to sanitize
42- * sensitive data with a call to `sub`.
43- */
44- private predicate effectiveSubRegExp ( CfgNodes:: ExprNodes:: RegExpLiteralCfgNode re ) {
45- re .getConstantValue ( ) .getStringOrSymbol ( ) .matches ( [ ".*" , ".+" ] )
46- }
47-
48- /**
49- * Holds if `re` may be a regular expression that can be used to sanitize
50- * sensitive data with a call to `gsub`.
51- */
52- private predicate effectiveGsubRegExp ( CfgNodes:: ExprNodes:: RegExpLiteralCfgNode re ) {
53- re .getConstantValue ( ) .getStringOrSymbol ( ) .matches ( "." )
54- }
55-
56- /**
57- * A call to `sub`/`sub!` or `gsub`/`gsub!` that seems to mask sensitive information.
58- */
59- private class MaskingReplacerSanitizer extends Sanitizer , DataFlow:: CallNode {
60- MaskingReplacerSanitizer ( ) {
61- exists ( CfgNodes:: ExprNodes:: RegExpLiteralCfgNode re |
62- re = this .getArgument ( 0 ) .asExpr ( ) and
63- (
64- this .getMethodName ( ) = [ "sub" , "sub!" ] and effectiveSubRegExp ( re )
65- or
66- this .getMethodName ( ) = [ "gsub" , "gsub!" ] and effectiveGsubRegExp ( re )
67- )
68- )
69- }
70- }
71-
72- /**
73- * Like `MaskingReplacerSanitizer` but updates the receiver for methods that
74- * sanitize the receiver.
75- * Taint is thereby cleared for any subsequent read.
76- */
77- private class InPlaceMaskingReplacerSanitizer extends Sanitizer {
78- InPlaceMaskingReplacerSanitizer ( ) {
79- exists ( MaskingReplacerSanitizer m | m .getMethodName ( ) = [ "gsub!" , "sub!" ] |
80- m .getReceiver ( ) = this
81- )
82- }
83- }
84-
85- /**
86- * Holds if `name` is for a method or variable that appears, syntactically, to
87- * not be sensitive.
88- */
89- bindingset [ name]
90- private predicate nameIsNotSensitive ( string name ) {
91- name .regexpMatch ( notSensitiveRegexp ( ) ) and
92- // By default `notSensitiveRegexp()` includes some false positives for
93- // common ruby method names that are not necessarily non-sensitive.
94- // We explicitly exclude element references, element assignments, and
95- // mutation methods.
96- not name = [ "[]" , "[]=" ] and
97- not name .matches ( "%!" )
98- }
99-
100- /**
101- * A call that might obfuscate a password, for example through hashing.
102- */
103- private class ObfuscatorCall extends Sanitizer , DataFlow:: CallNode {
104- ObfuscatorCall ( ) { nameIsNotSensitive ( this .getMethodName ( ) ) }
105- }
106-
107- /**
108- * A data flow node that does not contain a clear-text password, according to its syntactic name.
109- */
110- private class NameGuidedNonCleartextPassword extends NonCleartextPassword {
111- NameGuidedNonCleartextPassword ( ) {
112- exists ( string name | nameIsNotSensitive ( name ) |
113- // accessing a non-sensitive variable
114- this .asExpr ( ) .getExpr ( ) .( VariableReadAccess ) .getVariable ( ) .getName ( ) = name
115- or
116- // dereferencing a non-sensitive field
117- this .asExpr ( )
118- .( CfgNodes:: ExprNodes:: ElementReferenceCfgNode )
119- .getArgument ( 0 )
120- .getConstantValue ( )
121- .getStringOrSymbol ( ) = name
122- or
123- // calling a non-sensitive method
124- this .( DataFlow:: CallNode ) .getMethodName ( ) = name
125- )
126- or
127- // avoid i18n strings
128- this .asExpr ( )
129- .( CfgNodes:: ExprNodes:: ElementReferenceCfgNode )
130- .getReceiver ( )
131- .getConstantValue ( )
132- .getStringOrSymbol ( )
133- .regexpMatch ( "(?is).*(messages|strings).*" )
134- }
135- }
136-
137- /**
138- * A data flow node that receives flow that is not a clear-text password.
139- */
140- private class NonCleartextPasswordFlow extends NonCleartextPassword {
141- NonCleartextPasswordFlow ( ) {
142- any ( NonCleartextPassword other ) .( DataFlow:: LocalSourceNode ) .flowsTo ( this )
143- }
144- }
145-
146- /**
147- * A data flow node that does not contain a clear-text password.
148- */
149- abstract private class NonCleartextPassword extends DataFlow:: Node { }
150-
151- // `writeNode` assigns pair with key `name` to `val`
152- private predicate hashKeyWrite ( DataFlow:: CallNode writeNode , string name , DataFlow:: Node val ) {
153- writeNode .asExpr ( ) .getExpr ( ) instanceof SetterMethodCall and
154- // hash[name]
155- writeNode .getArgument ( 0 ) .asExpr ( ) .getConstantValue ( ) .getStringOrSymbol ( ) = name and
156- // val
157- writeNode .getArgument ( 1 ) .asExpr ( ) .( CfgNodes:: ExprNodes:: AssignExprCfgNode ) .getRhs ( ) =
158- val .asExpr ( )
159- }
160-
161- /**
162- * A write to a hash entry with a value that may contain password information.
163- */
164- private class HashKeyWritePasswordSource extends Source {
165- private string name ;
166- private DataFlow:: ExprNode recv ;
167-
168- HashKeyWritePasswordSource ( ) {
169- exists ( DataFlow:: Node val |
170- name .regexpMatch ( maybePassword ( ) ) and
171- not nameIsNotSensitive ( name ) and
172- // avoid safe values assigned to presumably unsafe names
173- not val instanceof NonCleartextPassword and
174- (
175- // hash[name] = val
176- hashKeyWrite ( this , name , val ) and
177- recv = this .( DataFlow:: CallNode ) .getReceiver ( )
178- )
179- )
180- }
181-
182- override string describe ( ) { result = "a write to " + name }
183-
184- /** Gets the name of the key */
185- string getName ( ) { result = name }
186-
187- /**
188- * Gets the name of the hash variable that this password source is assigned
189- * to, if applicable.
190- */
191- LocalVariable getVariable ( ) {
192- result = recv .getExprNode ( ) .getExpr ( ) .( VariableReadAccess ) .getVariable ( )
193- }
194- }
28+ /** Holds if `nodeFrom` taints `nodeTo`. */
29+ predicate isAdditionalTaintStep = CleartextSources:: isAdditionalTaintStep / 2 ;
19530
19631 /**
197- * A hash literal with an entry that may contain a password
32+ * A data flow sink for cleartext logging of sensitive information.
19833 */
199- private class HashLiteralPasswordSource extends Source {
200- private string name ;
201-
202- HashLiteralPasswordSource ( ) {
203- exists ( DataFlow:: Node val , CfgNodes:: ExprNodes:: HashLiteralCfgNode lit |
204- name .regexpMatch ( maybePassword ( ) ) and
205- not name .regexpMatch ( notSensitiveRegexp ( ) ) and
206- // avoid safe values assigned to presumably unsafe names
207- not val instanceof NonCleartextPassword and
208- // hash = { name: val }
209- exists ( CfgNodes:: ExprNodes:: PairCfgNode p |
210- this .asExpr ( ) = lit and p = lit .getAKeyValuePair ( )
211- |
212- p .getKey ( ) .getConstantValue ( ) .getStringOrSymbol ( ) = name and
213- p .getValue ( ) = val .asExpr ( )
214- )
215- )
216- }
217-
218- override string describe ( ) { result = "an write to " + name }
219- }
220-
221- /** An assignment that may assign a password to a variable */
222- private class AssignPasswordVariableSource extends Source {
223- string name ;
224-
225- AssignPasswordVariableSource ( ) {
226- // avoid safe values assigned to presumably unsafe names
227- not this instanceof NonCleartextPassword and
228- name .regexpMatch ( maybePassword ( ) ) and
229- exists ( Assignment a |
230- this .asExpr ( ) .getExpr ( ) = a .getRightOperand ( ) and
231- a .getLeftOperand ( ) .getAVariable ( ) .getName ( ) = name
232- )
233- }
234-
235- override string describe ( ) { result = "an assignment to " + name }
236- }
237-
238- /** A parameter that may contain a password. */
239- private class ParameterPasswordSource extends Source {
240- private string name ;
241-
242- ParameterPasswordSource ( ) {
243- name .regexpMatch ( maybePassword ( ) ) and
244- not this instanceof NonCleartextPassword and
245- exists ( Parameter p , LocalVariable v |
246- v = p .getAVariable ( ) and
247- v .getName ( ) = name and
248- this .asExpr ( ) .getExpr ( ) = v .getAnAccess ( )
249- )
250- }
251-
252- override string describe ( ) { result = "a parameter " + name }
253- }
254-
255- /** A call that might return a password. */
256- private class CallPasswordSource extends DataFlow:: CallNode , Source {
257- private string name ;
258-
259- CallPasswordSource ( ) {
260- name = this .getMethodName ( ) and
261- name .regexpMatch ( "(?is)getPassword" )
262- }
263-
264- override string describe ( ) { result = "a call to " + name }
265- }
34+ abstract class Sink extends DataFlow:: Node { }
26635
26736 private string commonLogMethodName ( ) {
26837 result = [ "info" , "debug" , "warn" , "warning" , "error" , "log" ]
26938 }
27039
271- /** Holds if `nodeFrom` taints `nodeTo`. */
272- predicate isAdditionalTaintStep ( DataFlow:: Node nodeFrom , DataFlow:: Node nodeTo ) {
273- exists ( string name , ElementReference ref , LocalVariable hashVar |
274- // from `hsh[password] = "changeme"` to a `hsh[password]` read
275- nodeFrom .( HashKeyWritePasswordSource ) .getName ( ) = name and
276- nodeTo .asExpr ( ) .getExpr ( ) = ref and
277- ref .getArgument ( 0 ) .getConstantValue ( ) .getStringOrSymbol ( ) = name and
278- nodeFrom .( HashKeyWritePasswordSource ) .getVariable ( ) = hashVar and
279- ref .getReceiver ( ) .( VariableReadAccess ) .getVariable ( ) = hashVar and
280- nodeFrom .asExpr ( ) .getASuccessor * ( ) = nodeTo .asExpr ( )
281- )
282- }
283-
28440 /**
28541 * A node representing an expression whose value is logged.
28642 */
0 commit comments