Upload ReDoS query, qhelp and tests

This commit is contained in:
jorgectf
2021-03-18 17:25:10 +01:00
parent 37377644c9
commit 7adc3c2fba
4 changed files with 153 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>If aregular expression is built by a not escaped user-provided value, a user is likely to be able to cause a Denial of Service.</p>
</overview>
<recommendation>
<p>In case user input must compose a regular expression, it should be escaped with functions such as <code>re.escape</code>.
<recommendation>
<references>
<li>
OWASP
<a href="https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS">Regular Expression DoS</a>
</li>
<li>
SonarSource
<a href="https://rules.sonarsource.com/python/type/Vulnerability/RSPEC-2631">RSPEC-2631</a>
</li>
<li>
CWE-
<a href="http://cwe.mitre.org/data/definitions/400">400</a>
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,82 @@
/**
* @name Python Regex DoS
* @description Python Regular Expression Denial of Service
* @kind path-problem
* @problem.severity error
* @id python/regex-dos
* @tags experimental
* security
* external/cwe/cwe-400
*/
import python
import semmle.python.dataflow.new.RemoteFlowSources
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TaintTracking
import semmle.python.dataflow.new.internal.TaintTrackingPublic
import DataFlow::PathGraph
class ReMethods extends string {
ReMethods() {
this = "match" or
this = "fullmatch" or
this = "search" or
this = "split" or
this = "findall" or
this = "finditer"
}
}
class DirectRegex extends DataFlow::Node {
DirectRegex() {
exists(string reMethod, CallNode reCall |
reMethod instanceof ReMethods and
reCall = Value::named("re." + reMethod).getACall() and
this.asExpr() = reCall.getArg(0).getNode()
)
}
}
class CompiledRegex extends DataFlow::Node {
CompiledRegex() {
exists(CallNode patternCall, SsaVariable patternVar, CallNode reMethodCall |
patternCall = Value::named("re.compile").getACall() and
patternVar.getDefinition().getImmediateDominator() = patternCall and
patternVar.getAUse().getNode() = reMethodCall.getNode().getFunc().(Attribute).getObject() and
reMethodCall.getNode().getFunc().(Attribute).getName() instanceof ReMethods and
this.asExpr() = patternCall.getArg(0).getNode()
)
}
}
class RegexDoSSink extends DataFlow::Node {
RegexDoSSink() { this instanceof DirectRegex or this instanceof CompiledRegex }
}
class EscapeSanitizer extends DataFlow::Node {
EscapeSanitizer() {
exists(Call c |
(
// avoid flow through any %escape% function
c.getFunc().(Attribute).getName().matches("%escape%") or // something.%escape%()
c.getFunc().(Name).getId().matches("%escape%") // %escape%()
) and
this.asExpr() = c
)
}
}
class RegexDoSFlowConfig extends TaintTracking::Configuration {
RegexDoSFlowConfig() { this = "RegexDoSFlowConfig" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof RegexDoSSink }
override predicate isSanitizer(DataFlow::Node sanitizer) { sanitizer instanceof EscapeSanitizer }
}
from RegexDoSFlowConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "$@ regex operation includes $@.", sink.getNode(), "This",
source.getNode(), "a user-provided value"

View File

@@ -0,0 +1,22 @@
from flask import request, Flask
import re
app = Flask(__name__)
@app.route("/direct")
def direct():
pattern = request.args['pattern']
re.search(pattern, "")
@app.route("/compile")
def compile():
pattern = re.compile(request.args['pattern'])
pattern.search("")
# if __name__ == "__main__":
# app.run(debug=True)

View File

@@ -0,0 +1,22 @@
from flask import request, Flask
import re
app = Flask(__name__)
@app.route("/direct")
def direct():
pattern = re.escape(request.args['pattern'])
re.search(pattern, "")
@app.route("/compile")
def compile():
pattern = re.compile(re.escape(request.args['pattern']))
pattern.search("")
# if __name__ == "__main__":
# app.run(debug=True)