Ruby: add rb/sensitive-get-query query#10369
Conversation
|
QHelp previews: ruby/ql/src/queries/security/cwe-598/SensitiveGetQuery.qhelpSensitive data read from GET requestSensitive information such as passwords should not be transmitted within the query string of the requested URL. Sensitive information within URLs may be logged in various locations, including the user's browser, the web server, and any proxy servers between the two endpoints. URLs may also be displayed on-screen, bookmarked or emailed around by users. They may be disclosed to third parties via the Referer header when any off-site links are followed. Placing sensitive information into the URL therefore increases the risk that it will be captured by an attacker. RecommendationUse HTTP POST to send sensitive information as part of the request body; for example, as form data. ExampleThe following example shows two route handlers that both receive a username and a password. The first receives this sensitive information from the query parameters of a GET request, which is transmitted in the URL. The second receives this sensitive information from the request body of a POST request. Rails.application.routes.draw do
get "users/login", to: "#login_get" # BAD: sensitive data transmitted through query parameters
post "users/login", to: "users#login_post" # GOOD: sensitive data transmitted in the request body
endclass UsersController < ActionController::Base
def login_get
password = params[:password]
authenticate_user(params[:username], password)
end
def login_post
password = params[:password]
authenticate_user(params[:username], password)
end
private
def authenticate_user(username, password)
# ... authenticate the user here
end
endReferences
|
hmac
left a comment
There was a problem hiding this comment.
This looks good to me. I have a few small comments and I also think we should do a DCA run, but otherwise 👍.
| | | ||
| localFlowWithElementReference(mid, to) | ||
| ) | ||
| } |
There was a problem hiding this comment.
Would a normal dataflow configuration-style query work here? Or is local flow a better choice?
There was a problem hiding this comment.
I should probably try this out with a full TaintTracking configuration as well to see if there's any difference in performance or results. In the JS version it seems like using global dataflow/taint tracking didn't improve results, but this may be different for Ruby. FWIW Java uses full taint tracking for its version of this query.
| CredentialsMethodName() { nameIndicatesSensitiveData(this, classification) } | ||
|
|
||
| override SensitiveDataClassification getClassification() { result = classification } | ||
| } |
There was a problem hiding this comment.
Since we've ported this from the JS version, it makes me wonder: is there value in these abstract classes being part of some shared Concept? It's a lot of extra work so I'm ok if we don't do it now, but it might be worth talking about.
There was a problem hiding this comment.
Yeah, I think that would be sensible. When I was working on this PR I ran into some issues where the JS version of this class had been moved from the AST layer to the DataFlow layer. I only noticed this change by chance, but it would have been easier to notice and stay synched on if there had been some shared Concept used by both.
|
|
||
| override string getFramework() { result = "ActionController" } | ||
|
|
||
| override string getAnHttpMethod() { result = this.getARoute().getHttpMethod() } |
There was a problem hiding this comment.
Nice to see that the routes modelling is coming in useful in some places!
…e-get-query (should not be flagged)
|
Finally got around to updating this PR. There are a couple of main changes:
|
|
I've rolled the taint tracking changes back since this had a fairly large performance impact and the results that it found should all be picked up with local flow anyway. |
I spoke too soon - the performance impact seems to be due to something else. I'll look into this properly tomorrow. |
This reverts commit fa58c51.
|
I think the bulk of the performance impact (~3.5% increase in analysis time) is from computing the new |
aibaars
left a comment
There was a problem hiding this comment.
Good work; I have some comment and questions.
| <qhelp> | ||
| <overview> | ||
| <p> | ||
| Sensitive information such as user passwords should not be transmitted within the query string of the requested URL. |
There was a problem hiding this comment.
Did you mean passwords or user names and passwords. While user passwords is a correct term, I think transmitting non-user passwords would be equally problematic.
There was a problem hiding this comment.
Yeah, just passwords makes more sense here. We don't report user names here as they're not considered sensitive in this context.
| * Gets the kind of the accessed input, | ||
| * Can be one of "parameter", "header", "body", "url", "cookie". | ||
| * | ||
| * Note that this predicate is functional. |
There was a problem hiding this comment.
What is the purpose of this Note ? I guess functional in this context indicates that the predicate returns exactly one result. This is kind of implied in the name of the predicate get instead of getA and the Gets the and lack of if any suffix in the comment.
I think the note may confuse users and make them wonder if there are any predicates that do not work/function somehow.
There was a problem hiding this comment.
I'm going to back out of the kind related changes here as #10602 implements this in a neater way. FWIW this was more or less copied from the JS version of this, my understanding of functional in this sense was that the return values could be relied on to have a precise semantic meaning. This is in contrast to string getSourceType() which is a bit fuzzier in what it might return.
There was a problem hiding this comment.
Sorry for conflicting with your changes here! When we talked about it in the meeting, I misunderstood and thought your changes were already merged, so when I couldn't find them I assumed they were for a different concept and so I would have to add them in my PR.
There was a problem hiding this comment.
Sorry for conflicting with your changes here! When we talked about it in the meeting, I misunderstood and thought your changes were already merged, so when I couldn't find them I assumed they were for a different concept and so I would have to add them in my PR.
No worries, thanks for the improved implementation. I've updated this PR to use it.
| * Gets the kind of the accessed input, | ||
| * Can be one of "parameter", "header", "body", "url", "cookie". | ||
| * | ||
| * Note that this predicate is functional. |
There was a problem hiding this comment.
This is gone now due to pulling in the changes from the other PR.
| */ | ||
| pragma[nomagic] | ||
| private predicate writesProperty(DataFlow::Node node, string name) { | ||
| exists(VariableWriteAccess vwa | vwa.getVariable().getName() = name | |
There was a problem hiding this comment.
If I recall correctly instance and class variables have @ and @@ prefixes in their names, do we need to account for those here?
There was a problem hiding this comment.
I don't think it matters in this case since nameIndicatesSensitiveData matches against some fairly permissive regexps (no anchors). This does remind me that at some point we discussed making Variable#getName() strip the prefixes from class/instance variables, but we didn't end up making this change. It's a bit of a shame as I think that's probably better default behaviour.
There was a problem hiding this comment.
Sorry, I take that back - the heuristics exclude anything containing non-alphanumerics. It might be worth changing nameIndicatesSensitiveData to ignore leading @ characters as we deal with variable names in multiple places in this file.
There was a problem hiding this comment.
nameIndicatesSensitiveData is in a shared file dealing with cross language heuristics, so I've updated the places in this file that deal with variable accesses instead.
| class UsersController < ApplicationController | ||
|
|
||
| def login_get | ||
| password = params[:password] # BAD: route handler uses GET query parameters to receive sensitive data |
There was a problem hiding this comment.
could you also add a test case with an instance variable? Like
@password = params[:some_thing]
authenticate_user(params[:username], @password)
| class UsersController < ApplicationController | ||
|
|
||
| def login_get | ||
| password = params[:password] # BAD: route handler uses GET query parameters to receive sensitive data |
There was a problem hiding this comment.
It think you best change the test case in one that uses params[:password] but with a non-sensitive variable name, and another with a non-sensitive query parameter name and a sensitive variable name.
Co-authored-by: Arthur Baars <aibaars@github.com>
…ables as not sensitive
|
I'll update this tomorrow to reflect the |
|
Updated to address review comments. DCA has a couple of FPs that are interesting, as they look to be safe by virtue of appearing in an if statement body that checks for |
aibaars
left a comment
There was a problem hiding this comment.
LGTM, perhaps we should implement some sanitizers/barriers for if request.post? checks.
Do you think that this is a blocker for the current PR? |
No problem ;-) |
Finds cases where an HTTP GET request handler takes sensitive input, such as a password or other credential, from the query string of the request. The bulk of this PR concerns porting
SensitiveNodeto Ruby.This uses for
HTTP::Server::RequestHandlers, which is currently implemented for RailsActionControllerand the rubygraphqlgem. In the case of GraphQL, bothPOSTandGETrequests are by default both accepted and handled uniformly - thestring getAnHttpMethod()implementation is trivial here as I couldn't find a documented way to change this behaviour.