mirror of
https://github.com/github/codeql.git
synced 2026-04-30 11:15:13 +02:00
Add XPath framework models
This commit is contained in:
168
ql/src/semmle/go/frameworks/XPath.qll
Normal file
168
ql/src/semmle/go/frameworks/XPath.qll
Normal file
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* Provides classes for working with XPath-related concepts such as XPath expressions.
|
||||
*/
|
||||
|
||||
import go
|
||||
|
||||
/** Provides classes for working with XPath-related APIs. */
|
||||
module XPath {
|
||||
/** Provides classes for working with XPath expression strings. */
|
||||
module XPathExpressionString {
|
||||
/**
|
||||
* A data-flow node whose string value is interpreted as (part of) an XPath expression.
|
||||
*
|
||||
* Extend this class to model new APIs.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node { }
|
||||
|
||||
/** An XPath expression string used in an API function of the https://github.com/antchfx/xpath package. */
|
||||
private class AntchfxxpathXPathExpressionString extends Range {
|
||||
AntchfxxpathXPathExpressionString() {
|
||||
exists(Function f, string name | name.matches("Compile%") |
|
||||
f.hasQualifiedName("github.com/antchfx/xpath", name) and
|
||||
this = f.getACall().getArgument(0)
|
||||
)
|
||||
or
|
||||
exists(Function f, string name | name.matches("MustCompile%") |
|
||||
f.hasQualifiedName("github.com/antchfx/xpath", name) and
|
||||
this = f.getACall().getArgument(0)
|
||||
)
|
||||
or
|
||||
exists(Function f, string name | name.matches("Select%") |
|
||||
f.hasQualifiedName("github.com/antchfx/xpath", name) and
|
||||
this = f.getACall().getArgument(1)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** An XPath expression string used in an API function of the https://github.com/antchfx/htmlquery package. */
|
||||
private class AntchfxhtmlqueryXPathExpressionString extends Range {
|
||||
AntchfxhtmlqueryXPathExpressionString() {
|
||||
exists(Function f, string name | name.matches("Find%") |
|
||||
f.hasQualifiedName("github.com/antchfx/htmlquery", name) and
|
||||
this = f.getACall().getArgument(1)
|
||||
)
|
||||
or
|
||||
exists(Function f, string name | name.matches("Query%") |
|
||||
f.hasQualifiedName("github.com/antchfx/htmlquery", name) and
|
||||
this = f.getACall().getArgument(1)
|
||||
)
|
||||
or
|
||||
exists(Function f |
|
||||
f.hasQualifiedName("github.com/antchfx/htmlquery", "getQuery") and
|
||||
this = f.getACall().getArgument(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** An XPath expression string used in an API function of the https://github.com/antchfx/xmlquery package. */
|
||||
private class AntchfxxmlqueryXPathExpressionString extends Range {
|
||||
AntchfxxmlqueryXPathExpressionString() {
|
||||
exists(Function f, string name | name.matches("Find%") |
|
||||
f.hasQualifiedName("github.com/antchfx/xmlquery", name) and
|
||||
this = f.getACall().getArgument(1)
|
||||
)
|
||||
or
|
||||
exists(Function f, string name | name.matches("Query%") |
|
||||
f.hasQualifiedName("github.com/antchfx/xmlquery", name) and
|
||||
this = f.getACall().getArgument(1)
|
||||
)
|
||||
or
|
||||
exists(Function f, string name | name.matches("Select%") |
|
||||
f.hasQualifiedName("github.com/antchfx/xmlquery", name) and
|
||||
this = f.getACall().getArgument(0)
|
||||
)
|
||||
or
|
||||
exists(Function f |
|
||||
f.hasQualifiedName("github.com/antchfx/xmlquery", "getQuery") and
|
||||
this = f.getACall().getArgument(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** An XPath expression string used in an API function of the https://github.com/antchfx/jsonquery package. */
|
||||
private class AntchfxjsonqueryXPathExpressionString extends Range {
|
||||
AntchfxjsonqueryXPathExpressionString() {
|
||||
exists(Function f, string name | name.matches("Find%") |
|
||||
f.hasQualifiedName("github.com/antchfx/jsonquery", name) and
|
||||
this = f.getACall().getArgument(1)
|
||||
)
|
||||
or
|
||||
exists(Function f, string name | name.matches("Query%") |
|
||||
f.hasQualifiedName("github.com/antchfx/jsonquery", name) and
|
||||
this = f.getACall().getArgument(1)
|
||||
)
|
||||
or
|
||||
exists(Function f |
|
||||
f.hasQualifiedName("github.com/antchfx/jsonquery", "getQuery") and
|
||||
this = f.getACall().getArgument(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** An XPath expression string used in an API function of the https://github.com/go-xmlpath/xmlpath package. */
|
||||
private class GoxmlpathxmlpathXPathExpressionString extends Range {
|
||||
GoxmlpathxmlpathXPathExpressionString() {
|
||||
exists(Function f, string name | name.matches("Compile%") |
|
||||
f.hasQualifiedName("github.com/go-xmlpath/xmlpath", name) and
|
||||
this = f.getACall().getArgument(0)
|
||||
)
|
||||
or
|
||||
exists(Function f, string name | name.matches("MustCompile%") |
|
||||
f.hasQualifiedName("github.com/go-xmlpath/xmlpath", name) and
|
||||
this = f.getACall().getArgument(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** An XPath expression string used in an API function of the https://github.com/ChrisTrenkamp/goxpath package. */
|
||||
private class ChrisTrenkampgoxpathXPathExpressionString extends Range {
|
||||
ChrisTrenkampgoxpathXPathExpressionString() {
|
||||
exists(Function f, string name | name.matches("Parse%") |
|
||||
f.hasQualifiedName("github.com/ChrisTrenkamp/goxpath", name) and
|
||||
this = f.getACall().getArgument(0)
|
||||
)
|
||||
or
|
||||
exists(Function f, string name | name.matches("MustParse%") |
|
||||
f.hasQualifiedName("github.com/ChrisTrenkamp/goxpath", name) and
|
||||
this = f.getACall().getArgument(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** An XPath expression string used in an API function of the https://github.com/santhosh-tekuri/xpathparser package. */
|
||||
private class AnthoshtekurixpathparserXPathExpressionString extends Range {
|
||||
AnthoshtekurixpathparserXPathExpressionString() {
|
||||
exists(Function f, string name | name.matches("Parse%") |
|
||||
f.hasQualifiedName("github.com/santhosh-tekuri/xpathparser", name) and
|
||||
this = f.getACall().getArgument(0)
|
||||
)
|
||||
or
|
||||
exists(Function f, string name | name.matches("MustParse%") |
|
||||
f.hasQualifiedName("github.com/santhosh-tekuri/xpathparser", name) and
|
||||
this = f.getACall().getArgument(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** An XPath expression string used in an API function of the https://github.com/moovweb/gokogiri package. */
|
||||
private class MoovwebgokogiriXPathExpressionString extends Range {
|
||||
MoovwebgokogiriXPathExpressionString() {
|
||||
exists(Function f, string name | name.matches("Compile%") |
|
||||
f.hasQualifiedName("github.com/moovweb/gokogiri/xpath", name) and
|
||||
this = f.getACall().getArgument(0)
|
||||
)
|
||||
or
|
||||
exists(Function f, string name | name.matches("Search%") |
|
||||
f.hasQualifiedName("github.com/moovweb/gokogiri/xml", name) and
|
||||
this = f.getACall().getArgument(0)
|
||||
)
|
||||
or
|
||||
exists(Function f, string name | name.matches("EvalXPath%") |
|
||||
f.hasQualifiedName("github.com/moovweb/gokogiri/xml", name) and
|
||||
this = f.getACall().getArgument(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
35
ql/src/semmle/go/security/XPathInjection.qll
Normal file
35
ql/src/semmle/go/security/XPathInjection.qll
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Provides a taint tracking configuration for reasoning about untrusted user input used in an XPath expression.
|
||||
*
|
||||
* Note: for performance reasons, only import this file if `XPathInjection::Configuration` is needed,
|
||||
* otherwise `XPathInjectionCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
import go
|
||||
|
||||
/**
|
||||
* Provides a taint tracking configuration for reasoning about untrusted user input used in an XPath expression.
|
||||
*/
|
||||
module XPathInjection {
|
||||
import XPathInjectionCustomizations::XPathInjection
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for reasoning about untrusted user input used in an XPath expression.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "XPathInjection" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
super.isSanitizer(node) or
|
||||
node instanceof Sanitizer
|
||||
}
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
}
|
||||
59
ql/src/semmle/go/security/XPathInjectionCustomizations.qll
Normal file
59
ql/src/semmle/go/security/XPathInjectionCustomizations.qll
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for reasoning about untrusted user input used in an XPath expression,
|
||||
* as well as extension points for adding your own.
|
||||
*/
|
||||
|
||||
import go
|
||||
|
||||
/**
|
||||
* Provides extension points for reasoning about untrusted user input used in an XPath expression.
|
||||
*/
|
||||
module XPathInjection {
|
||||
/**
|
||||
* A data flow source for untrusted user input used in an XPath expression.
|
||||
*/
|
||||
abstract class Source extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A data flow sink for untrusted user input used in an XPath expression.
|
||||
*/
|
||||
abstract class Sink extends DataFlow::ExprNode { }
|
||||
|
||||
/**
|
||||
* A sanitizer for untrusted user input used in an XPath expression.
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::ExprNode { }
|
||||
|
||||
/**
|
||||
* A sanitizer guard for untrusted user input used in an XPath expression.
|
||||
*/
|
||||
abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
|
||||
/** A source of untrusted data, used in an XPath expression. */
|
||||
class UntrustedFlowAsSource extends Source {
|
||||
UntrustedFlowAsSource() { this instanceof UntrustedFlowSource }
|
||||
}
|
||||
|
||||
/** An XPath expression string, considered as a taint sink for XPath injection. */
|
||||
class XPathExpressionStringAsSink extends Sink {
|
||||
XPathExpressionStringAsSink() { this instanceof XPath::XPathExpressionString::Range }
|
||||
}
|
||||
|
||||
// TODO: This probably belongs somewhere, but I'm not sure where
|
||||
/** The []byte type */
|
||||
class ByteSliceType extends SliceType {
|
||||
ByteSliceType() { this.getElementType() instanceof Uint8Type }
|
||||
}
|
||||
|
||||
//TODO add runes?
|
||||
/**
|
||||
* A call to `filepath.Rel`, considered as a sanitizer for path traversal.
|
||||
*/
|
||||
/*class NonStringByteSanitizer extends Sanitizer {
|
||||
NonStringByteSanitizer() {
|
||||
exists(Type t | t = this.getType().getUnderlyingType() |
|
||||
not t instanceof StringType or not t instanceof ByteSliceType
|
||||
)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
10
ql/test/query-tests/Security/CWE-643/XPathInjection.expected
Normal file
10
ql/test/query-tests/Security/CWE-643/XPathInjection.expected
Normal file
@@ -0,0 +1,10 @@
|
||||
edges
|
||||
| XPathInjection.go:13:14:13:19 | selection of Form : Values | XPathInjection.go:17:29:17:132 | ...+... |
|
||||
| XPathInjection.go:14:14:14:19 | selection of Form : Values | XPathInjection.go:17:29:17:132 | ...+... |
|
||||
nodes
|
||||
| XPathInjection.go:13:14:13:19 | selection of Form : Values | semmle.label | selection of Form : Values |
|
||||
| XPathInjection.go:14:14:14:19 | selection of Form : Values | semmle.label | selection of Form : Values |
|
||||
| XPathInjection.go:17:29:17:132 | ...+... | semmle.label | ...+... |
|
||||
#select
|
||||
| XPathInjection.go:17:29:17:132 | ...+... | XPathInjection.go:13:14:13:19 | selection of Form : Values | XPathInjection.go:17:29:17:132 | ...+... | $@ flows here and is used in an XPath expression. | XPathInjection.go:13:14:13:19 | selection of Form | A user-provided value |
|
||||
| XPathInjection.go:17:29:17:132 | ...+... | XPathInjection.go:14:14:14:19 | selection of Form : Values | XPathInjection.go:17:29:17:132 | ...+... | $@ flows here and is used in an XPath expression. | XPathInjection.go:14:14:14:19 | selection of Form | A user-provided value |
|
||||
30
ql/test/query-tests/Security/CWE-643/XPathInjection.go
Normal file
30
ql/test/query-tests/Security/CWE-643/XPathInjection.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/ChrisTrenkamp/goxpath"
|
||||
"github.com/ChrisTrenkamp/goxpath/tree"
|
||||
)
|
||||
|
||||
func processRequest(r *http.Request, doc tree.Node) {
|
||||
r.ParseForm()
|
||||
username := r.Form.Get("username")
|
||||
password := r.Form.Get("password")
|
||||
|
||||
// BAD: User input used directly in an XPath expression
|
||||
xPath := goxpath.MustParse("//users/user[login/text()='" + username + "' and password/text() = '" + password + "']/home_dir/text()")
|
||||
unsafeRes, _ := xPath.ExecBool(doc)
|
||||
fmt.Println(unsafeRes)
|
||||
|
||||
// GOOD: Value of parameters is defined here instead of directly in the query
|
||||
opt := func(o *goxpath.Opts) {
|
||||
o.Vars["username"] = tree.String(username)
|
||||
o.Vars["password"] = tree.String(password)
|
||||
}
|
||||
// GOOD: Uses parameters to avoid including user input directly in XPath expression
|
||||
xPath = goxpath.MustParse("//users/user[login/text()=$username and password/text() = $password]/home_dir/text()")
|
||||
safeRes, _ := xPath.ExecBool(doc, opt)
|
||||
fmt.Println(safeRes)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Security/CWE-643/XPathInjection.ql
|
||||
Reference in New Issue
Block a user