Incomplete url string sanitization#8354
Conversation
5d9ee09 to
49b4fe7
Compare
|
QHelp previews: ruby/ql/src/queries/security/cwe-020/IncompleteUrlSubstringSanitization.qhelpIncomplete URL substring sanitizationSanitizing untrusted URLs is an important technique for preventing attacks such as request forgeries and malicious redirections. Usually, this is done by checking that the host of a URL is in a set of allowed hosts. However, treating the URL as a string and checking if one of the allowed hosts is a substring of the URL is very prone to errors. Malicious URLs can bypass such security checks by embedding one of the allowed hosts in an unexpected location. Even if the substring check is not used in a security-critical context, the incomplete check may still cause undesirable behaviors when the check succeeds accidentally. RecommendationParse a URL before performing a check on its host value, and ensure that the check handles arbitrary subdomain sequences correctly. ExampleThe following example code checks that a URL redirection will reach the class AppController < ApplicationController
def index
url = params[:url]
# BAD: the host of `url` may be controlled by an attacker
if url.include?("example.com")
redirect_to url
end
endThe substring check is, however, easy to bypass. For example by embedding class AppController < ApplicationController
def index
url = params[:url]
host = URI(url).host
# BAD: the host of `url` may be controlled by an attacker
if host.include?("example.com")
redirect_to url
end
end
endThis is still not a sufficient check as the following URLs bypass it: class AppController < ApplicationController
def index
url = params[:url]
host = URI(url).host
# GOOD: the host of `url` can not be controlled by an attacker
allowedHosts = [
'example.com',
'beta.example.com',
'www.example.com'
]
if allowedHosts.include?(host)
redirect_to url
end
end
endReferences
|
|
QHelp previews: ruby/ql/src/queries/security/cwe-020/IncompleteUrlSubstringSanitization.qhelpIncomplete URL substring sanitizationSanitizing untrusted URLs is an important technique for preventing attacks such as request forgeries and malicious redirections. Usually, this is done by checking that the host of a URL is in a set of allowed hosts. However, treating the URL as a string and checking if one of the allowed hosts is a substring of the URL is very prone to errors. Malicious URLs can bypass such security checks by embedding one of the allowed hosts in an unexpected location. Even if the substring check is not used in a security-critical context, the incomplete check may still cause undesirable behaviors when the check succeeds accidentally. RecommendationParse a URL before performing a check on its host value, and ensure that the check handles arbitrary subdomain sequences correctly. ExampleThe following example code checks that a URL redirection will reach the class AppController < ApplicationController
def index
url = params[:url]
# BAD: the host of `url` may be controlled by an attacker
if url.include?("example.com")
redirect_to url
end
endThe substring check is, however, easy to bypass. For example by embedding class AppController < ApplicationController
def index
url = params[:url]
host = URI(url).host
# BAD: the host of `url` may be controlled by an attacker
if host.include?("example.com")
redirect_to url
end
end
endThis is still not a sufficient check as the following URLs bypass it: class AppController < ApplicationController
def index
url = params[:url]
host = URI(url).host
# GOOD: the host of `url` can not be controlled by an attacker
allowedHosts = [
'example.com',
'beta.example.com',
'www.example.com'
]
if allowedHosts.include?(host)
redirect_to url
end
end
endReferences
|
javascript/ql/src/Security/CWE-020/IncompleteUrlSubstringSanitization.qll
Show resolved
Hide resolved
| } | ||
|
|
||
| /** | ||
| * A check of form `A.index(B) != -1`, `A.index(B) >= 0`, or similar. |
There was a problem hiding this comment.
String#index and Array#index return nil on failure, not -1.
Perhaps we should extend this to cover checks of the form A.index(B) != nil and !A.index(B).nil?.
ruby/ql/lib/codeql/ruby/security/performance/RegExpTreeView.qll
Outdated
Show resolved
Hide resolved
ruby/ql/src/queries/security/cwe-020/examples/IncompleteUrlSubstringSanitization_BAD1.rb
Show resolved
Hide resolved
| or | ||
| value = -1 and polarity = true and comparison.getExpr() instanceof NEExpr | ||
| or | ||
| value = 0 and polarity = false and comparison.getExpr() instanceof NEExpr |
There was a problem hiding this comment.
Why this case? I'd say that value = 0 is only relevant for relational comparisons.
| * A.index(B) >= 0 | ||
| * ``` | ||
| */ | ||
| class InclusionTest extends DataFlow::Node instanceof InclusionTest::Range { |
There was a problem hiding this comment.
Why is this using data-flow nodes instead of CFG nodes?
There was a problem hiding this comment.
The JavaScript library does the same, so keeping it this way makes it easier to share the implementation of the query.
Co-authored-by: Nick Rolfe <nickrolfe@github.com>
|
QHelp previews: ruby/ql/src/queries/security/cwe-020/IncompleteUrlSubstringSanitization.qhelpIncomplete URL substring sanitizationSanitizing untrusted URLs is an important technique for preventing attacks such as request forgeries and malicious redirections. Usually, this is done by checking that the host of a URL is in a set of allowed hosts. However, treating the URL as a string and checking if one of the allowed hosts is a substring of the URL is very prone to errors. Malicious URLs can bypass such security checks by embedding one of the allowed hosts in an unexpected location. Even if the substring check is not used in a security-critical context, the incomplete check may still cause undesirable behaviors when the check succeeds accidentally. RecommendationParse a URL before performing a check on its host value, and ensure that the check handles arbitrary subdomain sequences correctly. ExampleThe following example code checks that a URL redirection will reach the class AppController < ApplicationController
def index
url = params[:url]
# BAD: the host of `url` may be controlled by an attacker
if url.include?("example.com")
redirect_to url
end
end
endThe substring check is, however, easy to bypass. For example by embedding class AppController < ApplicationController
def index
url = params[:url]
host = URI(url).host
# BAD: the host of `url` may be controlled by an attacker
if host.include?("example.com")
redirect_to url
end
end
endThis is still not a sufficient check as the following URLs bypass it: class AppController < ApplicationController
def index
url = params[:url]
host = URI(url).host
# GOOD: the host of `url` can not be controlled by an attacker
allowedHosts = [
'example.com',
'beta.example.com',
'www.example.com'
]
if allowedHosts.include?(host)
redirect_to url
end
end
endReferences
|
|
QHelp previews: ruby/ql/src/queries/security/cwe-020/IncompleteUrlSubstringSanitization.qhelpIncomplete URL substring sanitizationSanitizing untrusted URLs is an important technique for preventing attacks such as request forgeries and malicious redirections. Usually, this is done by checking that the host of a URL is in a set of allowed hosts. However, treating the URL as a string and checking if one of the allowed hosts is a substring of the URL is very prone to errors. Malicious URLs can bypass such security checks by embedding one of the allowed hosts in an unexpected location. Even if the substring check is not used in a security-critical context, the incomplete check may still cause undesirable behaviors when the check succeeds accidentally. RecommendationParse a URL before performing a check on its host value, and ensure that the check handles arbitrary subdomain sequences correctly. ExampleThe following example code checks that a URL redirection will reach the class AppController < ApplicationController
def index
url = params[:url]
# BAD: the host of `url` may be controlled by an attacker
if url.include?("example.com")
redirect_to url
end
end
endThe substring check is, however, easy to bypass. For example by embedding class AppController < ApplicationController
def index
url = params[:url]
host = URI(url).host
# BAD: the host of `url` may be controlled by an attacker
if host.include?("example.com")
redirect_to url
end
end
endThis is still not a sufficient check as the following URLs bypass it: class AppController < ApplicationController
def index
url = params[:url]
host = URI(url).host
# GOOD: the host of `url` can not be controlled by an attacker
allowedHosts = [
'example.com',
'beta.example.com',
'www.example.com'
]
if allowedHosts.include?(host)
redirect_to url
end
end
endReferences
|
…ng-sanitization Conflicts: config/identical-files.json javascript/ql/src/Security/CWE-020/IncompleteUrlSubstringSanitization.ql javascript/ql/src/Security/CWE-020/IncompleteUrlSubstringSanitization.qll ruby/ql/src/queries/security/cwe-020/IncompleteUrlSubstringSanitization.qll
|
QHelp previews: ruby/ql/src/queries/security/cwe-020/IncompleteUrlSubstringSanitization.qhelpIncomplete URL substring sanitizationSanitizing untrusted URLs is an important technique for preventing attacks such as request forgeries and malicious redirections. Usually, this is done by checking that the host of a URL is in a set of allowed hosts. However, treating the URL as a string and checking if one of the allowed hosts is a substring of the URL is very prone to errors. Malicious URLs can bypass such security checks by embedding one of the allowed hosts in an unexpected location. Even if the substring check is not used in a security-critical context, the incomplete check may still cause undesirable behaviors when the check succeeds accidentally. RecommendationParse a URL before performing a check on its host value, and ensure that the check handles arbitrary subdomain sequences correctly. ExampleThe following example code checks that a URL redirection will reach the class AppController < ApplicationController
def index
url = params[:url]
# BAD: the host of `url` may be controlled by an attacker
if url.include?("example.com")
redirect_to url
end
end
endThe substring check is, however, easy to bypass. For example by embedding class AppController < ApplicationController
def index
url = params[:url]
host = URI(url).host
# BAD: the host of `url` may be controlled by an attacker
if host.include?("example.com")
redirect_to url
end
end
endThis is still not a sufficient check as the following URLs bypass it: class AppController < ApplicationController
def index
url = params[:url]
host = URI(url).host
# GOOD: the host of `url` can not be controlled by an attacker
allowedHosts = [
'example.com',
'beta.example.com',
'www.example.com'
]
if allowedHosts.include?(host)
redirect_to url
end
end
endReferences
|
|
QHelp previews: ruby/ql/src/queries/security/cwe-020/IncompleteUrlSubstringSanitization.qhelpIncomplete URL substring sanitizationSanitizing untrusted URLs is an important technique for preventing attacks such as request forgeries and malicious redirections. Usually, this is done by checking that the host of a URL is in a set of allowed hosts. However, treating the URL as a string and checking if one of the allowed hosts is a substring of the URL is very prone to errors. Malicious URLs can bypass such security checks by embedding one of the allowed hosts in an unexpected location. Even if the substring check is not used in a security-critical context, the incomplete check may still cause undesirable behaviors when the check succeeds accidentally. RecommendationParse a URL before performing a check on its host value, and ensure that the check handles arbitrary subdomain sequences correctly. ExampleThe following example code checks that a URL redirection will reach the class AppController < ApplicationController
def index
url = params[:url]
# BAD: the host of `url` may be controlled by an attacker
if url.include?("example.com")
redirect_to url
end
end
endThe substring check is, however, easy to bypass. For example by embedding class AppController < ApplicationController
def index
url = params[:url]
host = URI(url).host
# BAD: the host of `url` may be controlled by an attacker
if host.include?("example.com")
redirect_to url
end
end
endThis is still not a sufficient check as the following URLs bypass it: class AppController < ApplicationController
def index
url = params[:url]
host = URI(url).host
# GOOD: the host of `url` can not be controlled by an attacker
allowedHosts = [
'example.com',
'beta.example.com',
'www.example.com'
]
if allowedHosts.include?(host)
redirect_to url
end
end
endReferences
|
| ( | ||
| value = index.getConstantValue().getInt() and value = 0 | ||
| or | ||
| index.getExpr() instanceof NilLiteral and value = -1 |
There was a problem hiding this comment.
Can use index.getConstantValue().isNil() instead.
| */ | ||
| class InclusionTest extends DataFlow::Node instanceof InclusionTest::Range { | ||
| /** Gets the `A` in `A.include?(B)`. */ | ||
| DataFlow::Node getContainerNode() { result = super.getContainerNode() } |
| DataFlow::Node getContainerNode() { result = super.getContainerNode() } | ||
|
|
||
| /** Gets the `B` in `A.include?(B)`. */ | ||
| DataFlow::Node getContainedNode() { result = super.getContainedNode() } |
| * If the polarity is `false` the check returns `true` if the container does not contain | ||
| * the given element. | ||
| */ | ||
| boolean getPolarity() { result = super.getPolarity() } |
| private class Includes_Native extends Range, DataFlow::CallNode { | ||
| Includes_Native() { | ||
| this.getMethodName() = "include?" and | ||
| count(this.getArgument(_)) = 1 |
There was a problem hiding this comment.
may as well use strictcount
| exists(ExprNodes::ComparisonOperationCfgNode comparison | | ||
| this.asExpr() = comparison and | ||
| indexOf.getMethodName() = "index" and | ||
| count(indexOf.getArgument(_)) = 1 and |
| /** | ||
| * Gets the `A` in `A.start_with?(B)`. | ||
| */ | ||
| DataFlow::Node getBaseString() { result = super.getBaseString() } |
| /** | ||
| * Gets the `B` in `A.start_with?(B)`. | ||
| */ | ||
| DataFlow::Node getSubstring() { result = super.getSubstring() } |
| * If the polarity is `false` the check returns `true` if the string does not end | ||
| * with the given substring. | ||
| */ | ||
| boolean getPolarity() { result = super.getPolarity() } |
|
|
||
| /** Holds if `node` may evaluate to `value` */ | ||
| predicate mayHaveStringValue(DataFlow::Node node, string value) { | ||
| node.asExpr().getExpr().getConstantValue().getString() = value |
|
QHelp previews: ruby/ql/src/queries/security/cwe-020/IncompleteUrlSubstringSanitization.qhelpIncomplete URL substring sanitizationSanitizing untrusted URLs is an important technique for preventing attacks such as request forgeries and malicious redirections. Usually, this is done by checking that the host of a URL is in a set of allowed hosts. However, treating the URL as a string and checking if one of the allowed hosts is a substring of the URL is very prone to errors. Malicious URLs can bypass such security checks by embedding one of the allowed hosts in an unexpected location. Even if the substring check is not used in a security-critical context, the incomplete check may still cause undesirable behaviors when the check succeeds accidentally. RecommendationParse a URL before performing a check on its host value, and ensure that the check handles arbitrary subdomain sequences correctly. ExampleThe following example code checks that a URL redirection will reach the class AppController < ApplicationController
def index
url = params[:url]
# BAD: the host of `url` may be controlled by an attacker
if url.include?("example.com")
redirect_to url
end
end
endThe substring check is, however, easy to bypass. For example by embedding class AppController < ApplicationController
def index
url = params[:url]
host = URI(url).host
# BAD: the host of `url` may be controlled by an attacker
if host.include?("example.com")
redirect_to url
end
end
endThis is still not a sufficient check as the following URLs bypass it: class AppController < ApplicationController
def index
url = params[:url]
host = URI(url).host
# GOOD: the host of `url` can not be controlled by an attacker
allowedHosts = [
'example.com',
'beta.example.com',
'www.example.com'
]
if allowedHosts.include?(host)
redirect_to url
end
end
endReferences
|
Added a new query,
rb/incomplete-url-substring-sanitization. The query finds instances where a URL is incompletely sanitized due to insufficient checks.