Add XPath framework models

This commit is contained in:
intrigus
2020-03-26 20:18:16 +01:00
parent 8dda4bd97f
commit 35a6fdb589
6 changed files with 303 additions and 0 deletions

View 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)
)
}
}
}
}

View 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
}
}
}

View 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
)
}
}*/
}

View 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 |

View 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)
}

View File

@@ -0,0 +1 @@
Security/CWE-643/XPathInjection.ql