-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Ruby: Add rb/clear-text-logging-sensitive-data query
#7713
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
8a28239 to
3b04619
Compare
|
QHelp previews: ruby/ql/src/queries/security/cwe-312/CleartextLogging.qhelpClear-text logging of sensitive informationSensitive information that is stored unencrypted is accessible to an attacker who gains access to the storage. RecommendationEnsure that sensitive information is always encrypted before being stored. In general, decrypt sensitive information only at the point where it is necessary for it to be used in cleartext. Be aware that external processes often store the ExampleThe following example code logs user credentials (in this case, their password) to require 'Logger'
class UserSession
@@logger = Logger.new STDOUT
def login(username, password)
# ...
@@logger.info "login from (user: #{username}, password: #{password})"
end
endInstead, the credentials should be masked or redacted before logging: require 'Logger'
class UserSession
@@logger = Logger.new STDOUT
def login(username, password)
# ...
password_escaped = password.sub(/.*/, "[redacted]")
@@logger.info "login from (user: #{username}, password: #{password_escaped})"
end
endReferences
|
1 similar comment
|
QHelp previews: ruby/ql/src/queries/security/cwe-312/CleartextLogging.qhelpClear-text logging of sensitive informationSensitive information that is stored unencrypted is accessible to an attacker who gains access to the storage. RecommendationEnsure that sensitive information is always encrypted before being stored. In general, decrypt sensitive information only at the point where it is necessary for it to be used in cleartext. Be aware that external processes often store the ExampleThe following example code logs user credentials (in this case, their password) to require 'Logger'
class UserSession
@@logger = Logger.new STDOUT
def login(username, password)
# ...
@@logger.info "login from (user: #{username}, password: #{password})"
end
endInstead, the credentials should be masked or redacted before logging: require 'Logger'
class UserSession
@@logger = Logger.new STDOUT
def login(username, password)
# ...
password_escaped = password.sub(/.*/, "[redacted]")
@@logger.info "login from (user: #{username}, password: #{password_escaped})"
end
endReferences
|
rb/clear-text-logging-sensitive-data query [WIP]rb/clear-text-logging-sensitive-data query
14a4919 to
269722f
Compare
|
QHelp previews: ruby/ql/src/queries/security/cwe-312/CleartextLogging.qhelpClear-text logging of sensitive informationSensitive information that is stored unencrypted is accessible to an attacker who gains access to the storage. RecommendationEnsure that sensitive information is always encrypted before being stored. In general, decrypt sensitive information only at the point where it is necessary for it to be used in cleartext. Be aware that external processes often store the ExampleThe following example code logs user credentials (in this case, their password) to require 'Logger'
class UserSession
@@logger = Logger.new STDOUT
def login(username, password)
# ...
@@logger.info "login with password: #{password})"
end
endInstead, the credentials should be masked or redacted before logging: require 'Logger'
class UserSession
@@logger = Logger.new STDOUT
def login(username, password)
# ...
password_escaped = password.sub(/.*/, "[redacted]")
@@logger.info "login with password: #{password_escaped})"
end
endReferences
|
|
QHelp previews: ruby/ql/src/queries/security/cwe-312/CleartextLogging.qhelpClear-text logging of sensitive informationSensitive information that is stored unencrypted is accessible to an attacker who gains access to the storage. RecommendationEnsure that sensitive information is always encrypted before being stored. In general, decrypt sensitive information only at the point where it is necessary for it to be used in cleartext. Be aware that external processes often store the ExampleThe following example code logs user credentials (in this case, their password) to require 'Logger'
class UserSession
@@logger = Logger.new STDOUT
def login(username, password)
# ...
@@logger.info "login with password: #{password})"
end
endInstead, the credentials should be masked or redacted before logging: require 'Logger'
class UserSession
@@logger = Logger.new STDOUT
def login(username, password)
# ...
password_escaped = password.sub(/.*/, "[redacted]")
@@logger.info "login with password: #{password_escaped})"
end
endReferences
|
ruby/ql/lib/codeql/ruby/security/CleartextLoggingCustomizations.qll
Outdated
Show resolved
Hide resolved
hmac
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've left a few minor comments and questions. In general this looks super thorough, but I have trouble following some of the logic in CleartextLoggingCustomizations.qll, I think because we deal with heuristics both for "sensitive" data and "not sensitive" data. Hopefully a second read through will make it a bit clearer for me.
ruby/ql/lib/codeql/ruby/security/CleartextLoggingCustomizations.qll
Outdated
Show resolved
Hide resolved
ruby/ql/lib/codeql/ruby/security/CleartextLoggingCustomizations.qll
Outdated
Show resolved
Hide resolved
ruby/ql/lib/codeql/ruby/security/CleartextLoggingCustomizations.qll
Outdated
Show resolved
Hide resolved
| */ | ||
| private class ObfuscatorCall extends Sanitizer, DataFlow::CallNode { | ||
| ObfuscatorCall() { | ||
| this.getMethodName().regexpMatch(notSensitiveRegexp()) and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if we can use notSensitiveRegexp as-is. It seems to flag any method with a ! in it, which is of course a common suffix for Ruby method names.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I encountered similar problems with [] and []= which I ended up special-casing using nonSensitiveMethodNameExclusion. The problem here is the [^\\w$.-] sub-clause that marks most methods containing non-alphanumerics as being "not sensitive".
In this case I think it could be sufficient to have ! as a suffix as another special exception for Ruby (and also to use nonSensitiveMethodNameExclusion more consistently in this file)? The other common naming pattern that I can think of is a ? suffix for predicate methods, which probably make sense as being marked as non-sensitive.
ruby/ql/lib/codeql/ruby/security/CleartextLoggingCustomizations.qll
Outdated
Show resolved
Hide resolved
| override predicate isSink(DataFlow::Node sink) { sink instanceof CleartextLogging::Sink } | ||
|
|
||
| override predicate isSanitizer(DataFlow::Node node) { | ||
| super.isSanitizer(node) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TaintTracking::Configuration.isSanitizer(_) is none(), so isn't this line redundant?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added it in case we extend the default isSanitizer predicate in the future, though I haven't been consistent in doing this with all dataflow/taint tracking queries.
| /** Holds if `nodeFrom` taints `nodeTo`. */ | ||
| predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { | ||
| exists(string name, ElementReference ref, LocalVariable hashVar | | ||
| // from `hsh[password] = "changeme"` to a `hsh[password]` read |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this additional step be dropped once we support flow through hashes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I expect so.
Co-authored-by: Harry Maclean <[email protected]>
|
QHelp previews: ruby/ql/src/queries/security/cwe-312/CleartextLogging.qhelpClear-text logging of sensitive informationSensitive information that is stored unencrypted is accessible to an attacker who gains access to the storage. RecommendationEnsure that sensitive information is always encrypted before being stored. In general, decrypt sensitive information only at the point where it is necessary for it to be used in cleartext. Be aware that external processes often store the ExampleThe following example code logs user credentials (in this case, their password) to require 'Logger'
class UserSession
@@logger = Logger.new STDOUT
def login(username, password)
# ...
@@logger.info "login with password: #{password})"
end
endInstead, the credentials should be masked or redacted before logging: require 'Logger'
class UserSession
@@logger = Logger.new STDOUT
def login(username, password)
# ...
password_escaped = password.sub(/.*/, "[redacted]")
@@logger.info "login with password: #{password_escaped})"
end
endReferences
|
|
QHelp previews: ruby/ql/src/queries/security/cwe-312/CleartextLogging.qhelpClear-text logging of sensitive informationSensitive information that is stored unencrypted is accessible to an attacker who gains access to the storage. RecommendationEnsure that sensitive information is always encrypted before being stored. In general, decrypt sensitive information only at the point where it is necessary for it to be used in cleartext. Be aware that external processes often store the ExampleThe following example code logs user credentials (in this case, their password) to require 'Logger'
class UserSession
@@logger = Logger.new STDOUT
def login(username, password)
# ...
@@logger.info "login with password: #{password})"
end
endInstead, the credentials should be masked or redacted before logging: require 'Logger'
class UserSession
@@logger = Logger.new STDOUT
def login(username, password)
# ...
password_escaped = password.sub(/.*/, "[redacted]")
@@logger.info "login with password: #{password_escaped})"
end
endReferences
|
|
QHelp previews: ruby/ql/src/queries/security/cwe-312/CleartextLogging.qhelpClear-text logging of sensitive informationSensitive information that is stored unencrypted is accessible to an attacker who gains access to the storage. RecommendationEnsure that sensitive information is always encrypted before being stored. In general, decrypt sensitive information only at the point where it is necessary for it to be used in cleartext. Be aware that external processes often store the ExampleThe following example code logs user credentials (in this case, their password) to require 'Logger'
class UserSession
@@logger = Logger.new STDOUT
def login(username, password)
# ...
@@logger.info "login with password: #{password})"
end
endInstead, the credentials should be masked or redacted before logging: require 'Logger'
class UserSession
@@logger = Logger.new STDOUT
def login(username, password)
# ...
password_escaped = password.sub(/.*/, "[redacted]")
@@logger.info "login with password: #{password_escaped})"
end
endReferences
|
hmac
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great. I understand it better on the second read through!
I've left one comment about a possible simiplifcation, but otherwise 👍. I've also kicked off a DCA run that should be done by the time you read this.
| def.hasAdjacentReads(any(MaskingReplacerSanitizedNode read).asExpr(), this.asExpr()) | ||
| ) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this can be simplified by replacing it with something like this:
/**
* Like `MaskingReplacerSanitizer` but updates the receiver.
* Taint is thereby cleared for any subsequent read.
*/
private class InPlaceMaskingReplacerSanitizer extends Sanitizer {
InPlaceMaskingReplacerSanitizer() {
exists(MaskingReplacerSanitizer m | m.getMethodName() = ["gsub!", "sub!"] |
m.getReceiver() = this
)
}
}The idea being that we make the receiver of sub! be a sanitizer, which (I believe) removes it and any subsequent reads of the same variable from the dataflow graph.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, that's really nice - much neater and truer to the intent of this sanitizer.
|
QHelp previews: ruby/ql/src/queries/security/cwe-312/CleartextLogging.qhelpClear-text logging of sensitive informationSensitive information that is stored unencrypted is accessible to an attacker who gains access to the storage. RecommendationEnsure that sensitive information is always encrypted before being stored. In general, decrypt sensitive information only at the point where it is necessary for it to be used in cleartext. Be aware that external processes often store the ExampleThe following example code logs user credentials (in this case, their password) to require 'Logger'
class UserSession
@@logger = Logger.new STDOUT
def login(username, password)
# ...
@@logger.info "login with password: #{password})"
end
endInstead, the credentials should be masked or redacted before logging: require 'Logger'
class UserSession
@@logger = Logger.new STDOUT
def login(username, password)
# ...
password_escaped = password.sub(/.*/, "[redacted]")
@@logger.info "login with password: #{password_escaped})"
end
endReferences
|
|
QHelp previews: ruby/ql/src/queries/security/cwe-312/CleartextLogging.qhelpClear-text logging of sensitive informationSensitive information that is stored unencrypted is accessible to an attacker who gains access to the storage. RecommendationEnsure that sensitive information is always encrypted before being stored. In general, decrypt sensitive information only at the point where it is necessary for it to be used in cleartext. Be aware that external processes often store the ExampleThe following example code logs user credentials (in this case, their password) to require 'Logger'
class UserSession
@@logger = Logger.new STDOUT
def login(username, password)
# ...
@@logger.info "login with password: #{password})"
end
endInstead, the credentials should be masked or redacted before logging: require 'Logger'
class UserSession
@@logger = Logger.new STDOUT
def login(username, password)
# ...
password_escaped = password.sub(/.*/, "[redacted]")
@@logger.info "login with password: #{password_escaped})"
end
endReferences
|
Tracks cases where potentially sensitive data (e.g. passwords and other credentials) are logged as cleartext.
This is loosely based on the JS version of the same query, using the same
SensitiveDataHeuristics.qlland a similar structure for theCleartextLoggingCustomizations.qll.