Merge pull request #16026 from erik-krogh/htmlSafeSan

RB: Add barrier guard for `.html_safe?` to the XSS queries
This commit is contained in:
Erik Krogh Kristensen
2024-04-02 07:54:19 +02:00
committed by GitHub
5 changed files with 33 additions and 9 deletions

View File

@@ -7,6 +7,7 @@
private import ruby
private import codeql.ruby.ApiGraphs
private import codeql.ruby.frameworks.core.Gem::Gem as Gem
private import codeql.ruby.security.XSS::ReflectedXss as ReflectedXss
private import codeql.ruby.typetracking.TypeTracking
/**
@@ -34,7 +35,11 @@ module UnsafeHtmlConstruction {
abstract string getSinkType();
}
private import codeql.ruby.security.XSS::ReflectedXss as ReflectedXss
/** A sanitizer for HTML constructed from library input vulnerabilities. */
abstract class Sanitizer extends DataFlow::Node { }
/** A sanitizer from the reflected-xss query, which is also a sanitizer for unsafe HTML construction. */
private class ReflectedXssSanitizers extends Sanitizer instanceof ReflectedXss::Sanitizer { }
/** Gets a node that eventually ends up in the XSS `sink`. */
private DataFlow::Node getANodeThatEndsInXssSink(ReflectedXss::Sink sink) {

View File

@@ -23,10 +23,7 @@ deprecated class Configuration extends TaintTracking::Configuration {
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
override predicate isSanitizer(DataFlow::Node node) {
node instanceof StringConstCompareBarrier or
node instanceof StringConstArrayInclusionCallBarrier
}
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
// override to require the path doesn't have unmatched return steps
override DataFlow::FlowFeature getAFeature() {
@@ -39,10 +36,7 @@ private module UnsafeHtmlConstructionConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) {
node instanceof StringConstCompareBarrier or
node instanceof StringConstArrayInclusionCallBarrier
}
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
// override to require the path doesn't have unmatched return steps
DataFlow::FlowFeature getAFeature() { result instanceof DataFlow::FeatureHasSourceCallContext }

View File

@@ -248,6 +248,19 @@ private module Shared {
or
isFlowFromHelperMethod(node1, node2)
}
private predicate htmlSafeGuard(CfgNodes::AstCfgNode guard, CfgNode testedNode, boolean branch) {
exists(DataFlow::CallNode html_safe_call | html_safe_call.getMethodName() = "html_safe?" |
guard = html_safe_call.asExpr() and
testedNode = html_safe_call.getReceiver().asExpr() and
branch = true
)
}
/** A guard that calls `.html_safe?` to check whether the string is already HTML-safe. */
private class HtmlSafeGuard extends Sanitizer {
HtmlSafeGuard() { this = DataFlow::BarrierGuard<htmlSafeGuard/3>::getABarrierNode() }
}
}
/**

View File

@@ -2,6 +2,7 @@ edges
| lib/unsafeHtml.rb:2:31:2:34 | name | lib/unsafeHtml.rb:3:10:3:16 | #{...} | provenance | |
| lib/unsafeHtml.rb:9:27:9:30 | name | lib/unsafeHtml.rb:11:13:11:19 | #{...} | provenance | |
| lib/unsafeHtml.rb:16:19:16:22 | name | lib/unsafeHtml.rb:17:28:17:31 | name | provenance | |
| lib/unsafeHtml.rb:23:32:23:35 | name | lib/unsafeHtml.rb:24:10:24:16 | #{...} | provenance | |
nodes
| lib/unsafeHtml.rb:2:31:2:34 | name | semmle.label | name |
| lib/unsafeHtml.rb:3:10:3:16 | #{...} | semmle.label | #{...} |
@@ -9,8 +10,11 @@ nodes
| lib/unsafeHtml.rb:11:13:11:19 | #{...} | semmle.label | #{...} |
| lib/unsafeHtml.rb:16:19:16:22 | name | semmle.label | name |
| lib/unsafeHtml.rb:17:28:17:31 | name | semmle.label | name |
| lib/unsafeHtml.rb:23:32:23:35 | name | semmle.label | name |
| lib/unsafeHtml.rb:24:10:24:16 | #{...} | semmle.label | #{...} |
subpaths
#select
| lib/unsafeHtml.rb:3:10:3:16 | #{...} | lib/unsafeHtml.rb:2:31:2:34 | name | lib/unsafeHtml.rb:3:10:3:16 | #{...} | This string interpolation which depends on $@ might later allow $@. | lib/unsafeHtml.rb:2:31:2:34 | name | library input | lib/unsafeHtml.rb:3:5:3:22 | "<h2>#{...}</h2>" | cross-site scripting |
| lib/unsafeHtml.rb:11:13:11:19 | #{...} | lib/unsafeHtml.rb:9:27:9:30 | name | lib/unsafeHtml.rb:11:13:11:19 | #{...} | This string interpolation which depends on $@ might later allow $@. | lib/unsafeHtml.rb:9:27:9:30 | name | library input | lib/unsafeHtml.rb:13:5:13:5 | h | cross-site scripting |
| lib/unsafeHtml.rb:17:28:17:31 | name | lib/unsafeHtml.rb:16:19:16:22 | name | lib/unsafeHtml.rb:17:28:17:31 | name | This string format which depends on $@ might later allow $@. | lib/unsafeHtml.rb:16:19:16:22 | name | library input | lib/unsafeHtml.rb:17:5:17:32 | call to sprintf | cross-site scripting |
| lib/unsafeHtml.rb:24:10:24:16 | #{...} | lib/unsafeHtml.rb:23:32:23:35 | name | lib/unsafeHtml.rb:24:10:24:16 | #{...} | This string interpolation which depends on $@ might later allow $@. | lib/unsafeHtml.rb:23:32:23:35 | name | library input | lib/unsafeHtml.rb:24:5:24:22 | "<h2>#{...}</h2>" | cross-site scripting |

View File

@@ -19,4 +19,12 @@ class Foobar
# escape
sprintf("<h2>%s</h2>", ERB::Util.html_escape(name)).html_safe # OK - the parameter is escaped
end
def create_user_description2(name)
"<h2>#{name}</h2>".html_safe # NOT OK - the value is not necessarily HTML safe
if name.html_safe?
"<h2>#{name}</h2>".html_safe # OK - value is marked as being HTML safe
end
end
end