mirror of
https://github.com/github/codeql.git
synced 2025-12-20 10:46:30 +01:00
Upload ReDoS query, qhelp and tests
This commit is contained in:
27
python/ql/src/Security/CWE-400/RegexDoS.qhelp
Normal file
27
python/ql/src/Security/CWE-400/RegexDoS.qhelp
Normal 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>
|
||||
82
python/ql/src/Security/CWE-400/RegexDoS.ql
Normal file
82
python/ql/src/Security/CWE-400/RegexDoS.ql
Normal 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"
|
||||
22
python/ql/src/Security/CWE-400/tests/re_bad.py
Normal file
22
python/ql/src/Security/CWE-400/tests/re_bad.py
Normal 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)
|
||||
22
python/ql/src/Security/CWE-400/tests/re_good.py
Normal file
22
python/ql/src/Security/CWE-400/tests/re_good.py
Normal 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)
|
||||
Reference in New Issue
Block a user