diff --git a/PR_9_golang/go_lang/go_codeql/CWE-020/ExternalAPISinkExample b/PR_9_golang/go_lang/go_codeql/CWE-020/ExternalAPISinkExample new file mode 100644 index 0000000..660076c Binary files /dev/null and b/PR_9_golang/go_lang/go_codeql/CWE-020/ExternalAPISinkExample differ diff --git a/PR_9_golang/go_lang/go_codeql/CWE-020/ExternalAPISinkExample.go b/PR_9_golang/go_lang/go_codeql/CWE-020/ExternalAPISinkExample.go new file mode 100644 index 0000000..db7f2fc --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-020/ExternalAPISinkExample.go @@ -0,0 +1,30 @@ +// {fact rule=cross-site-scripting@v1.0 defects=0} +package main + +import ( + "fmt" + "net/http" +) + +func isValidUsername(username string) bool { + return true +} + +func serve() { + http.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + username := r.Form.Get("username") + if !isValidUsername(username) { + // BAD: a request parameter is incorporated without validation into the response + fmt.Fprintf(w, "%q is an unknown user", username) + } else { + // TODO: Handle successful login + } + }) + http.ListenAndServe(":80", nil) +} +// {/fact} + +func main() { + +} \ No newline at end of file diff --git a/PR_9_golang/go_lang/go_codeql/CWE-020/ExternalAPITaintStepExample.go b/PR_9_golang/go_lang/go_codeql/CWE-020/ExternalAPITaintStepExample.go new file mode 100644 index 0000000..8970099 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-020/ExternalAPITaintStepExample.go @@ -0,0 +1,16 @@ +// {fact rule=sql-injection@v1.0 defects=1} +package main + +import ( + "database/sql" + "fmt" + "net/http" +) + +func handler(db *sql.DB, req *http.Request) { + q := fmt.Sprintf("SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='%s' ORDER BY PRICE", + req.URL.Query()["category"]) + db.Query(q) +} + +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-020/ExternalAPIsUsedWithUntrustedData.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-020/ExternalAPIsUsedWithUntrustedData.qhelp new file mode 100644 index 0000000..8b56b9d --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-020/ExternalAPIsUsedWithUntrustedData.qhelp @@ -0,0 +1,53 @@ + + + +

Using unsanitized untrusted data in an external API can cause a variety of security issues. This query reports +all external APIs that are used with untrusted data, along with how frequently the API is used, and how many +unique sources of untrusted data flow to this API. This query is designed primarily to help identify which APIs +may be relevant for security analysis of this application.

+ +

An external API is defined as a call to a function that is not defined in the source code and is not +modeled as a taint step in the default taint library. Calls made in test files are excluded. +External APIs may be from the Go standard library, third party dependencies, or from internal dependencies. +The query will report the fully-qualified method name, along with either [param x], +where x indicates the position of the parameter receiving the untrusted data, or [receiver] +indicating that the untrusted data is used as the receiver of the method call.

+ +
+ + +

For each result:

+ + + +

Otherwise, the result is likely uninteresting. Custom versions of this query can extend the SafeExternalAPIMethod +class to exclude known safe external APIs from future analysis.

+ +
+ + + + +

If the query were to return the API fmt.Fprintf [param 2] then we should first consider +whether this a security relevant sink. In this case, this is writing to an HTTP response, so we should +consider whether this is an XSS sink. If it is, we should confirm that it is handled by the "Reflected cross-site scripting" query (go/reflected-xss).

+ + + +

If the query were to return the API fmt.Sprintf [param 1], then this should be +reviewed as a possible taint step, because tainted data would flow from the first argument to the return value +of the call.

+ +

Note that both examples are correctly handled by the standard taint tracking library and the "Reflected cross-site scripting" query (go/reflected-xss).

+
+ + + +
diff --git a/PR_9_golang/go_lang/go_codeql/CWE-020/ExternalAPIsUsedWithUntrustedData.ql b/PR_9_golang/go_lang/go_codeql/CWE-020/ExternalAPIsUsedWithUntrustedData.ql new file mode 100644 index 0000000..b23cd00 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-020/ExternalAPIsUsedWithUntrustedData.ql @@ -0,0 +1,17 @@ +/** + * @name Frequency counts for external APIs that are used with untrusted data + * @description This reports the external APIs that are used with untrusted data, along with how + * frequently the API is called, and how many unique sources of untrusted data flow + * to it. + * @id go/count-untrusted-data-external-api + * @kind table + * @tags security external/cwe/cwe-20 + */ + +import go +import semmle.go.security.ExternalAPIs + +from ExternalApiUsedWithUntrustedData externalApi +select externalApi, count(externalApi.getUntrustedDataNode()) as numberOfUses, + externalApi.getNumberOfUntrustedSources() as numberOfUntrustedSources order by + numberOfUntrustedSources desc diff --git a/PR_9_golang/go_lang/go_codeql/CWE-020/IncompleteHostnameRegexp.go b/PR_9_golang/go_lang/go_codeql/CWE-020/IncompleteHostnameRegexp.go new file mode 100644 index 0000000..6d28c3b --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-020/IncompleteHostnameRegexp.go @@ -0,0 +1,18 @@ +// {fact rule=cryptographic-key-generator@v1.0 defects=1} +package main + +import ( + "errors" + "net/http" + "regexp" +) + +func checkRedirect(req *http.Request, via []*http.Request) error { + // BAD: the host of `req.URL` may be controlled by an attacker + re := "^((www|beta).)?example.com/" + if matched, _ := regexp.MatchString(re, req.URL.Host); matched { + return nil + } + return errors.New("Invalid redirect") +} +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-020/IncompleteHostnameRegexp.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-020/IncompleteHostnameRegexp.qhelp new file mode 100644 index 0000000..cf4655d --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-020/IncompleteHostnameRegexp.qhelp @@ -0,0 +1,50 @@ + + + + +

+Sanitizing untrusted URLs is an important technique for preventing attacks such as request +forgeries and malicious redirections. Often, this is done by checking that the host of a URL +is in a set of allowed hosts. +

+

+If a regular expression implements such a check, it is easy to accidentally make the check too +permissive by not escaping regular-expression meta-characters such as .. +

+

+Even if the check is not used in a security-critical context, the incomplete check may still cause +undesirable behavior when it accidentally succeeds. +

+
+ + +

+Escape all meta-characters appropriately when constructing regular expressions for security checks, +paying special attention to the . meta-character. +

+
+ + +

+The following example code checks that a URL redirection will reach the example.com +domain, or one of its subdomains. +

+ +

+The check is however easy to bypass because the unescaped . allows for any character +before example.com, effectively allowing the redirect to go to an attacker-controlled +domain such as wwwXexample.com. +

+

+Address this vulnerability by escaping . appropriately: +

+ +
+ + +
  • OWASP: SSRF
  • +
  • OWASP: Unvalidated Redirects and Forwards Cheat Sheet.
  • +
    +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-020/IncompleteHostnameRegexp.ql b/PR_9_golang/go_lang/go_codeql/CWE-020/IncompleteHostnameRegexp.ql new file mode 100644 index 0000000..4873117 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-020/IncompleteHostnameRegexp.ql @@ -0,0 +1,116 @@ +/** + * @name Incomplete regular expression for hostnames + * @description Matching a URL or hostname against a regular expression that contains an unescaped + * dot as part of the hostname might match more hostnames than expected. + * @kind path-problem + * @problem.severity warning + * @security-severity 7.8 + * @precision high + * @id go/incomplete-hostname-regexp + * @tags correctness + * security + * external/cwe/cwe-20 + */ + +import go + +/** + * Holds if `pattern` is a regular expression pattern for URLs with a host matched by `hostPart`, + * and `pattern` contains a subtle mistake that allows it to match unexpected hosts. + */ +bindingset[pattern] +predicate isIncompleteHostNameRegexpPattern(string pattern, string hostPart) { + hostPart = + pattern + .regexpCapture("(?i).*?" + + // an unescaped single `.` + "(?; + +import Flow::PathGraph + +from Flow::PathNode source, Flow::PathNode sink, string hostPart +where + Flow::flowPath(source, sink) and + IncompleteHostNameRegexpConfig::isSourceString(source.getNode(), hostPart) +select source, source, sink, + "This regular expression has an unescaped dot before '" + hostPart + "', " + + "so it might match more hosts than expected when $@.", sink, "the regular expression is used" diff --git a/PR_9_golang/go_lang/go_codeql/CWE-020/IncompleteHostnameRegexpGood.go b/PR_9_golang/go_lang/go_codeql/CWE-020/IncompleteHostnameRegexpGood.go new file mode 100644 index 0000000..f32a307 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-020/IncompleteHostnameRegexpGood.go @@ -0,0 +1,18 @@ +// {fact rule=cryptographic-key-generator@v1.0 defects=0} +package main + +import ( + "errors" + "net/http" + "regexp" +) + +func checkRedirectGood(req *http.Request, via []*http.Request) error { + // GOOD: the host of `req.URL` must be `example.com`, `www.example.com` or `beta.example.com` + re := "^((www|beta)\\.)?example\\.com/" + if matched, _ := regexp.MatchString(re, req.URL.Host); matched { + return nil + } + return errors.New("Invalid redirect") +} +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-020/IncompleteUrlSchemeCheck.go b/PR_9_golang/go_lang/go_codeql/CWE-020/IncompleteUrlSchemeCheck.go new file mode 100644 index 0000000..741849a --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-020/IncompleteUrlSchemeCheck.go @@ -0,0 +1,14 @@ +// {fact rule=cryptographic-key-generator@v1.0 defects=1} +package main + +import "net/url" + +func sanitizeUrl(urlstr string) string { + u, err := url.Parse(urlstr) + if err != nil || u.Scheme == "javascript" { + return "about:blank" + } + return urlstr +} + +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-020/IncompleteUrlSchemeCheck.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-020/IncompleteUrlSchemeCheck.qhelp new file mode 100644 index 0000000..0c04811 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-020/IncompleteUrlSchemeCheck.qhelp @@ -0,0 +1,42 @@ + + + +

    +URLs with the special scheme javascript can be used to encode JavaScript code to be +executed when the URL is visited. While this is a powerful mechanism for creating feature-rich and +responsive web applications, it is also a potential security risk: if the URL comes from an +untrusted source, it might contain harmful JavaScript code. For this reason, many frameworks and +libraries first check the URL scheme of any untrusted URL, and reject URLs with the +javascript scheme. +

    +

    +However, the data and vbscript schemes can be used to represent +executable code in a very similar way, so any validation logic that checks against +javascript, but not against data and vbscript, is likely +to be insufficient. +

    +
    + +

    +Add checks covering both data: and vbscript:. +

    +
    + +

    +The following function validates a (presumably untrusted) URL urlstr. If its scheme is +javascript, the harmless placeholder URL about:blank is returned to +prevent code injection; otherwise urlstr itself is returned. +

    + +

    +While this check provides partial projection, it should be extended to cover data +and vbscript as well: +

    + +
    + +
  • WHATWG: URL schemes.
  • +
    +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-020/IncompleteUrlSchemeCheck.ql b/PR_9_golang/go_lang/go_codeql/CWE-020/IncompleteUrlSchemeCheck.ql new file mode 100644 index 0000000..9951382 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-020/IncompleteUrlSchemeCheck.ql @@ -0,0 +1,61 @@ +/** + * @name Incomplete URL scheme check + * @description Checking for the "javascript:" URL scheme without also checking for "vbscript:" + * and "data:" suggests a logic error or even a security vulnerability. + * @kind problem + * @problem.severity warning + * @security-severity 7.8 + * @precision high + * @id go/incomplete-url-scheme-check + * @tags security + * correctness + * external/cwe/cwe-020 + */ + +import go + +/** A URL scheme that can be used to represent executable code. */ +class DangerousScheme extends string { + DangerousScheme() { this = "data" or this = "javascript" or this = "vbscript" } +} + +/** Gets a data-flow node that checks an instance of `g` against the given `scheme`. */ +DataFlow::Node schemeCheck(GVN g, DangerousScheme scheme) { + // check of the form `nd.Scheme == scheme` + exists(NamedType url, DataFlow::FieldReadNode fr, DataFlow::Node s | + url.hasQualifiedName("net/url", "URL") and + fr.readsField(g.getANode(), url.getField("Scheme")) and + s.getStringValue() = scheme and + result.(DataFlow::EqualityTestNode).eq(_, fr, s) + ) + or + // check of the form `strings.HasPrefix(nd, "scheme:")` + exists(StringOps::HasPrefix hasPrefix | result = hasPrefix | + hasPrefix.getBaseString() = g.getANode() and + hasPrefix.getSubstring().getStringValue() = scheme + ":" + ) + or + // propagate through various string transformations + exists(string stringop, DataFlow::CallNode c | + stringop = "Map" or + stringop.matches("Replace%") or + stringop.matches("To%") or + stringop.matches("Trim%") + | + c.getTarget().hasQualifiedName("strings", stringop) and + c.getAnArgument() = g.getANode() and + result = schemeCheck(globalValueNumber(c), scheme) + ) +} + +/** + * Gets a scheme that `g`, which is checked against at least one scheme, is not checked against. + */ +DangerousScheme getAMissingScheme(GVN g) { + exists(schemeCheck(g, _)) and + not exists(schemeCheck(g, result)) +} + +from GVN g +select schemeCheck(g, "javascript"), + "This check does not consider " + strictconcat(getAMissingScheme(g), " and ") + "." diff --git a/PR_9_golang/go_lang/go_codeql/CWE-020/IncompleteUrlSchemeCheckGood.go b/PR_9_golang/go_lang/go_codeql/CWE-020/IncompleteUrlSchemeCheckGood.go new file mode 100644 index 0000000..32a7cce --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-020/IncompleteUrlSchemeCheckGood.go @@ -0,0 +1,13 @@ +// {fact rule=cryptographic-key-generator@v1.0 defects=0} +package main + +import "net/url" + +func sanitizeUrlGod(urlstr string) string { + u, err := url.Parse(urlstr) + if err != nil || u.Scheme == "javascript" || u.Scheme == "data" || u.Scheme == "vbscript" { + return "about:blank" + } + return urlstr +} +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-020/MissingRegexpAnchor.go b/PR_9_golang/go_lang/go_codeql/CWE-020/MissingRegexpAnchor.go new file mode 100644 index 0000000..3f60484 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-020/MissingRegexpAnchor.go @@ -0,0 +1,18 @@ +// {fact rule=cryptographic-key-generator@v1.0 defects=1} +package main + +import ( + "errors" + "net/http" + "regexp" +) + +func checkRedirect2(req *http.Request, via []*http.Request) error { + // BAD: the host of `req.URL` may be controlled by an attacker + re := "https?://www\\.example\\.com/" + if matched, _ := regexp.MatchString(re, req.URL.String()); matched { + return nil + } + return errors.New("Invalid redirect") +} +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-020/MissingRegexpAnchor.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-020/MissingRegexpAnchor.qhelp new file mode 100644 index 0000000..df238e1 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-020/MissingRegexpAnchor.qhelp @@ -0,0 +1,54 @@ + + + + +

    +Sanitizing untrusted input with regular expressions is a common technique. However, it is +error-prone to match untrusted input against regular expressions without anchors such as +^ or $. Malicious input can bypass such security checks by embedding +one of the allowed patterns in an unexpected location. +

    +

    +Even if the matching is not done in a security-critical context, it may still cause undesirable +behavior when the regular expression accidentally matches. +

    +
    + + +

    +Use anchors to ensure that regular expressions match at the expected locations. +

    +
    + + +

    +The following example code checks that a URL redirection will reach the example.com +domain, or one of its subdomains, and not some malicious site. +

    + +

    +The check with the regular expression match is, however, easy to bypass. For example, the string +http://example.com/ can be embedded in the query string component: +http://evil-example.net/?x=http://example.com/. +

    +

    +Address these shortcomings by using anchors in the regular expression instead: +

    + +

    +A related mistake is to write a regular expression with multiple alternatives, but to only anchor +one of the alternatives. As an example, the regular expression +^www\.example\.com|beta\.example\.com will match the host +evil.beta.example.com because the regular expression is parsed as +(^www\.example\.com)|(beta\.example\.com)/, so the second alternative +beta\.example\.com is not anchored at the beginning of the string. +

    +
    + + +
  • OWASP: SSRF
  • +
  • OWASP: Unvalidated Redirects and Forwards Cheat Sheet.
  • +
    +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-020/MissingRegexpAnchor.ql b/PR_9_golang/go_lang/go_codeql/CWE-020/MissingRegexpAnchor.ql new file mode 100644 index 0000000..df93440 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-020/MissingRegexpAnchor.ql @@ -0,0 +1,81 @@ +/** + * @name Missing regular expression anchor + * @description Regular expressions without anchors can be vulnerable to bypassing. + * @kind problem + * @problem.severity warning + * @security-severity 7.8 + * @precision high + * @id go/regex/missing-regexp-anchor + * @tags correctness + * security + * external/cwe/cwe-20 + */ + +import go + +/** + * Holds if `re` is a pattern for a collection of alternatives where + * only the first or last alternative is anchored, indicating a + * precedence mistake explained by `msg`. + * + * The canonical example of such a mistake is: `^a|b|c`, which is + * parsed as `(^a)|(b)|(c)`. + */ +bindingset[re] +predicate isInterestingSemiAnchoredRegexpString(string re, string msg) { + exists(string str, string maybeGroupedStr, string regex, string anchorPart, string escapedDot | + // a dot that might be escaped in a regular expression, for example `regexp.Compile("\\.")` + escapedDot = "\\\\[.]" and + // a string that is mostly free from special reqular expression symbols + str = "(?:(?:" + escapedDot + ")|[a-z:/.?_,@0-9 -])+" and + // the string may be wrapped in parentheses + maybeGroupedStr = "(?:" + str + "|\\(" + str + "\\))" and + ( + // a problematic pattern: `^a|b|...|x` + regex = "(?i)(\\^" + maybeGroupedStr + ")(?:\\|" + maybeGroupedStr + ")+" + or + // a problematic pattern: `a|b|...|x$` + regex = "(?i)(?:" + maybeGroupedStr + "\\|)+(" + maybeGroupedStr + "\\$)" + ) and + anchorPart = re.regexpCapture(regex, 1) and + anchorPart.regexpMatch("(?i).*[a-z].*") and + msg = + "Misleading operator precedence. The subexpression '" + anchorPart + + "' is anchored, but the other parts of this regular expression are not." + ) +} + +/** + * Holds if `re` is an unanchored pattern for a URL, indicating a + * mistake explained by `msg`. + */ +bindingset[re] +predicate isInterestingUnanchoredRegexpString(string re, string msg) { + // a substring sequence of a protocol and subdomains, perhaps with some regex characters mixed in, followed by a known TLD + re.regexpMatch("(?i)[():|?a-z0-9-\\\\./]+[.]" + commonTld() + "([/#?():]\\S*)?") and + // without any anchors + not re.regexpMatch(".*(\\$|\\^|\\\\A|\\\\z).*") and + msg = + "When this is used as a regular expression on a URL, it may match anywhere, and arbitrary " + + "hosts may come before or after it." +} + +module Config implements DataFlow::ConfigSig { + additional predicate isSourceString(DataFlow::Node source, string msg) { + exists(Expr e | e = source.asExpr() | + isInterestingUnanchoredRegexpString(e.getStringValue(), msg) + or + isInterestingSemiAnchoredRegexpString(e.getStringValue(), msg) + ) + } + + predicate isSource(DataFlow::Node source) { isSourceString(source, _) } + + predicate isSink(DataFlow::Node sink) { sink instanceof RegexpPattern } +} + +module Flow = DataFlow::Global; + +from DataFlow::Node source, string msg +where Flow::flow(source, _) and Config::isSourceString(source, msg) +select source, msg diff --git a/PR_9_golang/go_lang/go_codeql/CWE-020/MissingRegexpAnchorGood.go b/PR_9_golang/go_lang/go_codeql/CWE-020/MissingRegexpAnchorGood.go new file mode 100644 index 0000000..4d7936e --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-020/MissingRegexpAnchorGood.go @@ -0,0 +1,18 @@ +// {fact rule=cryptographic-key-generator@v1.0 defects=0} +package main + +import ( + "errors" + "net/http" + "regexp" +) + +func checkRedirect2Good(req *http.Request, via []*http.Request) error { + // GOOD: the host of `req.URL` cannot be controlled by an attacker + re := "^https?://www\\.example\\.com/" + if matched, _ := regexp.MatchString(re, req.URL.String()); matched { + return nil + } + return errors.New("Invalid redirect") +} +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-020/SuspiciousCharacterInRegexp.go b/PR_9_golang/go_lang/go_codeql/CWE-020/SuspiciousCharacterInRegexp.go new file mode 100644 index 0000000..52d5025 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-020/SuspiciousCharacterInRegexp.go @@ -0,0 +1,17 @@ +// {fact rule=cryptographic-key-generator@v1.0 defects=1} +package main + +import "regexp" + +func broken(hostNames []byte) string { + var hostRe = regexp.MustCompile("\bforbidden.host.org") + if hostRe.Match(hostNames) { + return "Must not target forbidden.host.org" + } else { + // This will be reached even if hostNames is exactly "forbidden.host.org", + // because the literal backspace is not matched + return "" + } +} + +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-020/SuspiciousCharacterInRegexp.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-020/SuspiciousCharacterInRegexp.qhelp new file mode 100644 index 0000000..7b34fa5 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-020/SuspiciousCharacterInRegexp.qhelp @@ -0,0 +1,66 @@ + + + + +

    + + When a character in a string literal or regular expression + literal is preceded by a backslash, it is interpreted as part of an + escape sequence. For example, the escape sequence \n in a + string literal corresponds to a single newline character, + and not the \ and n characters. + + There are two Go escape sequences that could produce surprising results. + First, regexp.Compile("\a") matches the bell character, whereas + regexp.Compile("\\A") matches the start of text and + regexp.Compile("\\a") is a Vim (but not Go) regular expression + matching any alphabetic character. Second, regexp.Compile("\b") + matches a backspace, whereas regexp.Compile("\\b") matches the + start of a word. Confusing one for the other could lead to a regular expression + passing or failing much more often than expected, with potential security + consequences. + + Note this is less of a problem than in some other languages because in Go, + only valid escape sequences are accepted, both in an ordinary string + (for example, s := "\k" will not compile as there is no such + escape sequence) and in regular expressions (for example, + regexp.MustCompile("\\k") will panic as \k does not + refer to a character class or other special token according to Go's regular + expression grammar). + +

    + +
    + + +

    + + Ensure that the right number of backslashes is used when + escaping characters in strings and regular + expressions. + +

    +
    + + + +

    The following example code fails to check for a forbidden word in an input string:

    + +

    The check does not work, but can be fixed by escaping the backslash:

    + +

    + Alternatively, you can use backtick-delimited raw string literals. + For example, the \b in regexp.Compile(`hello\bworld`) + matches a word boundary, not a backspace character, as within backticks \b is not an + escape sequence. +

    + +
    + + +
  • golang.org: Overview of the Regexp package.
  • +
  • Google: Syntax of regular expressions accepted by RE2.
  • +
    +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-020/SuspiciousCharacterInRegexp.ql b/PR_9_golang/go_lang/go_codeql/CWE-020/SuspiciousCharacterInRegexp.ql new file mode 100644 index 0000000..81cc634 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-020/SuspiciousCharacterInRegexp.ql @@ -0,0 +1,58 @@ +/** + * @name Suspicious characters in a regular expression + * @description If a literal bell character or backspace appears in a regular expression, the start of text or word boundary may have been intended. + * @kind path-problem + * @problem.severity warning + * @security-severity 7.8 + * @precision high + * @id go/suspicious-character-in-regex + * @tags correctness + * security + * external/cwe/cwe-20 + */ + +import go + +/** + * Holds if `source` corresponds to a string literal that contains an escaped `character`. + * + * `character` must be `"a"` or `"b"`, the only interesting escapes for this query. + */ +predicate containsEscapedCharacter(DataFlow::Node source, string character) { + character in ["a", "b"] and + exists(StringLit s | s = source.asExpr() | + // Search for `character` preceded by an odd number of backslashes: + exists(s.getText().regexpFind("(?<=(^|[^\\\\])\\\\(\\\\{2}){0,10})" + character, _, _)) and + not s.isRaw() + ) +} + +module SuspiciousCharacterInRegexpConfig implements DataFlow::ConfigSig { + additional predicate isSourceString(DataFlow::Node source, string report) { + containsEscapedCharacter(source, "a") and + report = + "the bell character \\a; did you mean \\\\a, the Vim alphabetic character class (use [[:alpha:]] instead) or \\\\A, the beginning of text?" + or + containsEscapedCharacter(source, "b") and + report = "a literal backspace \\b; did you mean \\\\b, a word boundary?" + } + + predicate isSource(DataFlow::Node source) { isSourceString(source, _) } + + predicate isSink(DataFlow::Node sink) { sink instanceof RegexpPattern } +} + +/** + * Tracks data flow from strings containing suspicious escape sequences to a + * use as a regular expression. + */ +module Flow = DataFlow::Global; + +import Flow::PathGraph + +from Flow::PathNode source, Flow::PathNode sink, string report +where + Flow::flowPath(source, sink) and + SuspiciousCharacterInRegexpConfig::isSourceString(source.getNode(), report) +select source, source, sink, "This string literal that is $@ contains " + report, sink, + "used as a regular expression" diff --git a/PR_9_golang/go_lang/go_codeql/CWE-020/SuspiciousCharacterInRegexpGood.go b/PR_9_golang/go_lang/go_codeql/CWE-020/SuspiciousCharacterInRegexpGood.go new file mode 100644 index 0000000..ab7e9c8 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-020/SuspiciousCharacterInRegexpGood.go @@ -0,0 +1,16 @@ +// {fact rule=cryptographic-key-generator@v1.0 defects=0} +package main + +import "regexp" + +func fixed(hostNames []byte) string { + var hostRe = regexp.MustCompile(`\bforbidden.host.org`) + if hostRe.Match(hostNames) { + return "Must not target forbidden.host.org" + } else { + // hostNames definitely doesn't contain a word "forbidden.host.org", as "\\b" + // is the start-of-word anchor, not a literal backspace. + return "" + } +} +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-020/UntrustedDataToExternalAPI.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-020/UntrustedDataToExternalAPI.qhelp new file mode 100644 index 0000000..4402037 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-020/UntrustedDataToExternalAPI.qhelp @@ -0,0 +1,61 @@ + + + +

    Using unsanitized untrusted data in an external API can cause a variety of security issues. This query reports +external APIs that use untrusted data. The results have very little filtering so that you can audit almost all +examples. The query provides data for security reviews of the application. It can also be used to identify +external APIs that should be modeled as either taint steps, or sinks for specific problems.

    + +

    An external API is defined as a call to a function that is not defined in the source code and is not +modeled as a taint step in the default taint library. Calls made in test files are excluded. +External APIs may be from the Go standard library, third-party dependencies, or from internal dependencies. +The query reports uses of untrusted data in either the receiver or as one of the arguments of external APIs.

    + +
    + + +

    For each result:

    + +
      +
    • If the result highlights a known sink, confirm that the result is reported by the relevant query, or + that the result is a false positive because this data is sanitized.
    • +
    • If the result highlights an unknown sink for a problem, then add modeling for the sink to the relevant query, + and confirm that the result is either found, or is safe due to appropriate sanitization.
    • +
    • If the result represents a call to an external API that transfers taint (for example, from a parameter to its return value), add the appropriate modeling, and + re-run the query to determine what new results have appeared due to this additional modeling.
    • +
    + +

    Otherwise, the result is likely uninteresting. Custom versions of this query can extend the SafeExternalAPIMethod +class to exclude known safe external APIs from future analysis.

    + +
    + + +

    In this first example, a request parameter is read from http.Request and then ultimately used in a call to the +fmt.Fprintf external API:

    + + + +

    This is an XSS sink. The "Reflected cross-site scripting" query (go/reflected-xss) should therefore be reviewed to confirm that this sink is appropriately modeled, +and if it is, to confirm that the query reports this particular result, or that the result is a false positive due to +some existing sanitization.

    + +

    In this second example, again a request parameter is read from http.Request.

    + + + +

    If the query reported the call to fmt.Sprintf, this would suggest that this external API is not currently +modeled as a taint step in the taint tracking library. The next step would be to model this as a taint step, then +re-run the query to determine what additional results might be found. In this example, an SQL injection vulnerability +would be reported.

    + +

    Note that both examples are correctly handled by the standard taint tracking library, +the "Reflected cross-site scripting" query (go/reflected-xss), +and the "Database query built from user-controlled sources" query (go/sql-injection).

    +
    + + + +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-020/UntrustedDataToExternalAPI.ql b/PR_9_golang/go_lang/go_codeql/CWE-020/UntrustedDataToExternalAPI.ql new file mode 100644 index 0000000..4ab22af --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-020/UntrustedDataToExternalAPI.ql @@ -0,0 +1,20 @@ +/** + * @name Untrusted data passed to external API + * @description Data provided remotely is used in this external API without sanitization, which could be a security risk. + * @id go/untrusted-data-to-external-api + * @kind path-problem + * @precision low + * @problem.severity error + * @security-severity 7.8 + * @tags security external/cwe/cwe-20 + */ + +import go +import semmle.go.security.ExternalAPIs +import UntrustedDataToExternalApiFlow::PathGraph + +from UntrustedDataToExternalApiFlow::PathNode source, UntrustedDataToExternalApiFlow::PathNode sink +where UntrustedDataToExternalApiFlow::flowPath(source, sink) +select sink, source, sink, + "Call to " + sink.getNode().(ExternalApiDataNode).getFunctionDescription() + + " with untrusted data from $@.", source, source.toString() diff --git a/PR_9_golang/go_lang/go_codeql/CWE-020/UntrustedDataToUnknownExternalAPI.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-020/UntrustedDataToUnknownExternalAPI.qhelp new file mode 100644 index 0000000..a665a44 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-020/UntrustedDataToUnknownExternalAPI.qhelp @@ -0,0 +1,62 @@ + + + +

    Using unsanitized untrusted data in an external API can cause a variety of security issues. This query reports +external APIs that use untrusted data. The results have been filtered to only report unknown external APIs. The query provides data for security +reviews of the application. It can also be used to identify external APIs that should be modeled as either +taint steps, or sinks for specific problems.

    + +

    An external API is defined as a call to a function that is not defined in the source code and is not +modeled as a taint step in the default taint library. Calls made in test files are excluded. +External APIs may be from the Go standard library, third-party dependencies, or from internal dependencies. +The query reports uses of untrusted data in either the receiver or as one of the arguments of external APIs.

    + +

    An external API is considered unknown if it is not in a package which has already been modeled, it is not +a sink for an existing query, and it is not in a list of external APIs which have been examined and determined +to not be a possible source of security vulnerabilities.

    +
    + + +

    For each result:

    + +
      +
    • If the result highlights an unknown sink for a problem, then add modeling for the sink to the relevant query, + and confirm that the result is either found, or is safe due to appropriate sanitization.
    • +
    • If the result represents a call to an external API that transfers taint (for example, from a parameter to its return value), add the appropriate modeling, and + re-run the query to determine what new results have appeared due to this additional modeling.
    • +
    + +

    Otherwise, the result is likely uninteresting. Custom versions of this query can extend the SafeExternalAPIMethod +class to exclude known safe external APIs from future analysis.

    + +
    + + +

    In this first example, a request parameter is read from http.Request and then ultimately used in a call to the +fmt.Fprintf external API:

    + + + +

    This is an XSS sink. The "Reflected cross-site scripting" query (go/reflected-xss) should therefore be reviewed to confirm that this sink is appropriately modeled, +and if it is, to confirm that the query reports this particular result, or that the result is a false positive due to +some existing sanitization.

    + +

    In this second example, again a request parameter is read from http.Request.

    + + + +

    If the query reported the call to fmt.Sprintf, this would suggest that this external API is not currently +modeled as a taint step in the taint tracking library. The next step would be to model this as a taint step, then +re-run the query to determine what additional results might be found. In this example, an SQL injection vulnerability +would be reported.

    + +

    Note that both examples are correctly handled by the standard taint tracking library, +the "Reflected cross-site scripting" query (go/reflected-xss), +and the "Database query built from user-controlled sources" query (go/sql-injection).

    +
    + + + +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-020/UntrustedDataToUnknownExternalAPI.ql b/PR_9_golang/go_lang/go_codeql/CWE-020/UntrustedDataToUnknownExternalAPI.ql new file mode 100644 index 0000000..23945e3 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-020/UntrustedDataToUnknownExternalAPI.ql @@ -0,0 +1,22 @@ +/** + * @name Untrusted data passed to unknown external API + * @description Data provided remotely is used in this unknown external API without sanitization, which could be a security risk. + * @id go/untrusted-data-to-unknown-external-api + * @kind path-problem + * @precision low + * @problem.severity error + * @security-severity 7.8 + * @tags security external/cwe/cwe-20 + */ + +import go +import semmle.go.security.ExternalAPIs +import UntrustedDataToUnknownExternalApiFlow::PathGraph + +from + UntrustedDataToUnknownExternalApiFlow::PathNode source, + UntrustedDataToUnknownExternalApiFlow::PathNode sink +where UntrustedDataToUnknownExternalApiFlow::flowPath(source, sink) +select sink, source, sink, + "Call to " + sink.getNode().(UnknownExternalApiDataNode).getFunctionDescription() + + " with untrusted data from $@.", source, source.toString() diff --git a/PR_9_golang/go_lang/go_codeql/CWE-022/TaintedPath b/PR_9_golang/go_lang/go_codeql/CWE-022/TaintedPath new file mode 100644 index 0000000..c3edb38 Binary files /dev/null and b/PR_9_golang/go_lang/go_codeql/CWE-022/TaintedPath differ diff --git a/PR_9_golang/go_lang/go_codeql/CWE-022/TaintedPath.go b/PR_9_golang/go_lang/go_codeql/CWE-022/TaintedPath.go new file mode 100644 index 0000000..9e1ac7d --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-022/TaintedPath.go @@ -0,0 +1,21 @@ +// {fact rule=path-traversal@v1.0 defects=1} +package main + +import ( + "io/ioutil" + "net/http" + "path/filepath" +) + +func handler(w http.ResponseWriter, r *http.Request) { + path := r.URL.Query()["path"][0] + + // BAD: This could read any file on the file system + data, _ := ioutil.ReadFile(path) + w.Write(data) + + // BAD: This could still read any file on the file system + data, _ = ioutil.ReadFile(filepath.Join("/home/user/", path)) + w.Write(data) +} +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-022/TaintedPath.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-022/TaintedPath.qhelp new file mode 100644 index 0000000..061ffbe --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-022/TaintedPath.qhelp @@ -0,0 +1,51 @@ + + + + +

    +Accessing files using paths constructed from user-controlled data can allow an attacker to access +unexpected resources. This can result in sensitive information being revealed or deleted, or an +attacker being able to influence behavior by modifying unexpected files. +

    +
    + + +

    +Validate user input before using it to construct a file path, either using an off-the-shelf library +or by performing custom validation. +

    +

    +Ideally, follow these rules: +

    +
      +
    • Do not allow more than a single "." character.
    • +
    • Do not allow directory separators such as "/" or "\" (depending on the file system).
    • +
    • Do not rely on simply replacing problematic sequences such as "../". For example, after +applying this filter to ".../...//", the resulting string would still be "../".
    • +
    • Use an allowlist of known good patterns.
    • +
    +
    + + +

    +In the first example, a file name is read from an HTTP request and then used to access a file. +However, a malicious user could enter a file name which is an absolute path, such as +"/etc/passwd". +

    +

    +In the second example, it appears that the user is restricted to opening a file within the +"user" home directory. However, a malicious user could enter a file name containing +special characters. For example, the string "../../etc/passwd" will result in the code +reading the file located at "/home/user/../../etc/passwd", which is the system's +password file. This file would then be sent back to the user, giving them access to password +information. +

    + +
    + + +
  • OWASP: Path Traversal.
  • +
    +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-022/TaintedPath.ql b/PR_9_golang/go_lang/go_codeql/CWE-022/TaintedPath.ql new file mode 100644 index 0000000..fcad51b --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-022/TaintedPath.ql @@ -0,0 +1,25 @@ +/** + * @name Uncontrolled data used in path expression + * @description Accessing paths influenced by users can allow an attacker to access + * unexpected resources. + * @kind path-problem + * @problem.severity error + * @security-severity 7.5 + * @precision high + * @id go/path-injection + * @tags security + * external/cwe/cwe-022 + * external/cwe/cwe-023 + * external/cwe/cwe-036 + * external/cwe/cwe-073 + * external/cwe/cwe-099 + */ + +import go +import semmle.go.security.TaintedPath +import TaintedPath::Flow::PathGraph + +from TaintedPath::Flow::PathNode source, TaintedPath::Flow::PathNode sink +where TaintedPath::Flow::flowPath(source, sink) +select sink.getNode(), source, sink, "This path depends on a $@.", source.getNode(), + "user-provided value" diff --git a/PR_9_golang/go_lang/go_codeql/CWE-022/UnsafeUnzipSymlink.go b/PR_9_golang/go_lang/go_codeql/CWE-022/UnsafeUnzipSymlink.go new file mode 100644 index 0000000..2b4f805 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-022/UnsafeUnzipSymlink.go @@ -0,0 +1,34 @@ +// {fact rule=client-constructor-deprecated-rule@v1.0 defects=1} +package main + +import ( + "archive/tar" + "io" + "os" + "path/filepath" + "strings" +) + +func isRel(candidate, target string) bool { + // BAD: purely syntactic means are used to check + // that `candidate` does not escape from `target` + if filepath.IsAbs(candidate) { + return false + } + relpath, err := filepath.Rel(target, filepath.Join(target, candidate)) + return err == nil && !strings.HasPrefix(filepath.Clean(relpath), "..") +} + +func unzipSymlink(f io.Reader, target string) { + r := tar.NewReader(f) + for { + header, err := r.Next() + if err != nil { + break + } + if isRel(header.Linkname, target) && isRel(header.Name, target) { + os.Symlink(header.Linkname, header.Name) + } + } +} +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-022/UnsafeUnzipSymlink.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-022/UnsafeUnzipSymlink.qhelp new file mode 100644 index 0000000..69072f1 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-022/UnsafeUnzipSymlink.qhelp @@ -0,0 +1,58 @@ + + + + +

    +Extracting symbolic links from a malicious zip archive, without validating that the destination file path +is within the destination directory, can cause files outside the destination directory to be overwritten. +This can happen if there are previously-extracted symbolic links or +directory traversal elements and links (..) in archive paths. +

    + +

    +This problem is related to the ZipSlip vulnerability which is detected by the go/zipslip query; +please see that query's help for more general information about malicious archive file vulnerabilities. +This query considers the specific case where symbolic links are extracted from an archive, in which case +the extraction code must be aware of existing symbolic links when checking whether it is about to extract +a link pointing to a location outside the target extraction directory. +

    +
    + + +

    +Ensure that output paths constructed from zip archive entries are validated. This includes resolving any +previously extracted symbolic links, for example using path/filepath.EvalSymlinks, to prevent writing +files or links to unexpected locations. +

    +
    + + +

    +In this example, links are extracted from an archive using the syntactic filepath.Rel +function to check whether the link and its target fall within the destination directory. +However, the extraction code doesn't resolve previously-extracted links, so a pair of links like +subdir/parent -> .. followed by escape -> subdir/parent/.. -> subdir/../.. +leaves a link pointing to the parent of the archive root. The syntactic Rel is ineffective +because it equates subdir/parent/.. with subdir/, but this is not the case +when subdir/parent is a symbolic link. +

    + +

    To fix this vulnerability, resolve pre-existing symbolic links before checking +that the link's target is acceptable: +

    + +
    + + +
  • +Snyk: +Zip Slip Vulnerability. +
  • +
  • +OWASP: +Path Traversal. +
  • +
    +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-022/UnsafeUnzipSymlink.ql b/PR_9_golang/go_lang/go_codeql/CWE-022/UnsafeUnzipSymlink.ql new file mode 100644 index 0000000..5adfef2 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-022/UnsafeUnzipSymlink.ql @@ -0,0 +1,25 @@ +/** + * @name Arbitrary file write extracting an archive containing symbolic links + * @description Extracting files from a malicious zip archive without validating that the + * destination file path is within the destination directory can cause files outside + * the destination directory to be overwritten. Extracting symbolic links in particular + * requires resolving previously extracted links to ensure the destination directory + * is not escaped. + * @kind path-problem + * @id go/unsafe-unzip-symlink + * @problem.severity error + * @security-severity 7.5 + * @precision high + * @tags security + * external/cwe/cwe-022 + */ + +import go +import semmle.go.security.UnsafeUnzipSymlink +import UnsafeUnzipSymlink::Flow::PathGraph + +from UnsafeUnzipSymlink::Flow::PathNode source, UnsafeUnzipSymlink::Flow::PathNode sink +where UnsafeUnzipSymlink::Flow::flowPath(source, sink) +select source.getNode(), source, sink, + "Unresolved path from an archive header, which may point outside the archive root, is used in $@.", + sink.getNode(), "symlink creation" diff --git a/PR_9_golang/go_lang/go_codeql/CWE-022/UnsafeUnzipSymlinkGood.go b/PR_9_golang/go_lang/go_codeql/CWE-022/UnsafeUnzipSymlinkGood.go new file mode 100644 index 0000000..463f0b4 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-022/UnsafeUnzipSymlinkGood.go @@ -0,0 +1,26 @@ +// {fact rule=client-constructor-deprecated-rule@v1.0 defects=0} +package main + +import ( + "path/filepath" + "strings" +) + +func isRel_1(candidate, target string) bool { + // GOOD: resolves all symbolic links before checking + // that `candidate` does not escape from `target` + if filepath.IsAbs(candidate) { + return false + } + realpath, err := filepath.EvalSymlinks(filepath.Join(target, candidate)) + if err != nil { + return false + } + relpath, err := filepath.Rel(target, realpath) + return err == nil && !strings.HasPrefix(filepath.Clean(relpath), "..") +} +// {/fact} + +func main() { + +} \ No newline at end of file diff --git a/PR_9_golang/go_lang/go_codeql/CWE-022/ZipSlip.go b/PR_9_golang/go_lang/go_codeql/CWE-022/ZipSlip.go new file mode 100644 index 0000000..0df8e71 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-022/ZipSlip.go @@ -0,0 +1,18 @@ +// {fact rule=path-traversal@v1.0 defects=1} +package main + +import ( + "archive/zip" + "io/ioutil" + "path/filepath" +) + +func unzip(f string) { + r, _ := zip.OpenReader(f) + for _, f := range r.File { + p, _ := filepath.Abs(f.Name) + // BAD: This could overwrite any file on the file system + ioutil.WriteFile(p, []byte("present"), 0666) + } +} +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-022/ZipSlip.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-022/ZipSlip.qhelp new file mode 100644 index 0000000..ecea2ea --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-022/ZipSlip.qhelp @@ -0,0 +1,69 @@ + + + + +

    +Extracting files from a malicious zip file, or similar type of archive, +is at risk of directory traversal attacks if filenames from the archive are +not properly validated. +archive paths. +

    + +

    +Zip archives contain archive entries representing each file in the archive. These entries +include a file path for the entry, but these file paths are not restricted and may contain +unexpected special elements such as the directory traversal element (..). If these +file paths are used to create a filesystem path, then a file operation may happen in an +unexpected location. This can result in sensitive information being +revealed or deleted, or an attacker being able to influence behavior by modifying unexpected +files. +

    + +

    +For example, if a zip file contains a file entry ..\sneaky-file, and the zip file +is extracted to the directory c:\output, then naively combining the paths would result +in an output file path of c:\output\..\sneaky-file, which would cause the file to be +written to c:\sneaky-file. +

    +
    + + +

    +Ensure that output paths constructed from zip archive entries are validated +to prevent writing files to unexpected locations. +

    + +

    +The recommended way of writing an output file from a zip archive entry is to check that +".." does not occur in the path. +

    +
    + + +

    +In this example an archive is extracted without validating file paths. +If archive.zip contained relative paths (for +instance, if it were created by something like zip archive.zip +../file.txt) then executing this code could write to locations +outside the destination directory. +

    + +

    To fix this vulnerability, we need to check that the path does not +contain any ".." elements in it. +

    + +
    + + +
  • +Snyk: +Zip Slip Vulnerability. +
  • +
  • +OWASP: +Path Traversal. +
  • +
    +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-022/ZipSlip.ql b/PR_9_golang/go_lang/go_codeql/CWE-022/ZipSlip.ql new file mode 100644 index 0000000..1556d08 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-022/ZipSlip.ql @@ -0,0 +1,23 @@ +/** + * @name Arbitrary file access during archive extraction ("Zip Slip") + * @description Extracting files from a malicious ZIP file, or similar type of archive, without + * validating that the destination file path is within the destination directory + * can allow an attacker to unexpectedly gain access to resources. + * @kind path-problem + * @id go/zipslip + * @problem.severity error + * @security-severity 7.5 + * @precision high + * @tags security + * external/cwe/cwe-022 + */ + +import go +import semmle.go.security.ZipSlip +import ZipSlip::Flow::PathGraph + +from ZipSlip::Flow::PathNode source, ZipSlip::Flow::PathNode sink +where ZipSlip::Flow::flowPath(source, sink) +select source.getNode(), source, sink, + "Unsanitized archive entry, which may contain '..', is used in a $@.", sink.getNode(), + "file system operation" diff --git a/PR_9_golang/go_lang/go_codeql/CWE-022/ZipSlipGood.go b/PR_9_golang/go_lang/go_codeql/CWE-022/ZipSlipGood.go new file mode 100644 index 0000000..adbc20c --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-022/ZipSlipGood.go @@ -0,0 +1,21 @@ +// {fact rule=client-constructor-deprecated-rule@v1.0 defects=0} +package main + +import ( + "archive/zip" + "io/ioutil" + "path/filepath" + "strings" +) + +func unzipGood(f string) { + r, _ := zip.OpenReader(f) + for _, f := range r.File { + p, _ := filepath.Abs(f.Name) + // GOOD: Check that path does not contain ".." before using it + if !strings.Contains(f.Name, "..") { + ioutil.WriteFile(p, []byte("present"), 0666) + } + } +} +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-078/CommandInjection b/PR_9_golang/go_lang/go_codeql/CWE-078/CommandInjection new file mode 100644 index 0000000..5ffda99 Binary files /dev/null and b/PR_9_golang/go_lang/go_codeql/CWE-078/CommandInjection differ diff --git a/PR_9_golang/go_lang/go_codeql/CWE-078/CommandInjection.go b/PR_9_golang/go_lang/go_codeql/CWE-078/CommandInjection.go new file mode 100644 index 0000000..dcae938 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-078/CommandInjection.go @@ -0,0 +1,15 @@ +// {fact rule=os-command-injection@v1.0 defects=1} +package main + +import ( + "net/http" + "os/exec" +) + +func handler(req *http.Request) { + cmdName := req.URL.Query()["cmd"][0] + cmd := exec.Command(cmdName) + cmd.Run() +} + +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-078/CommandInjection.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-078/CommandInjection.qhelp new file mode 100644 index 0000000..0b94625 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-078/CommandInjection.qhelp @@ -0,0 +1,41 @@ + + + + +

    +If a system command invocation is built from user-provided data without sufficient sanitization, +a malicious user may be able to run commands to exfiltrate data or compromise the system. +

    +
    + + +

    +If possible, use hard-coded string literals to specify the command to run. Instead of interpreting +user input directly as command names, examine the input and then choose among hard-coded string +literals. +

    +

    +If this is not possible, then add sanitization code to verify that the user input is safe before +using it. +

    +
    + + +

    +In the following example, assume the function handler is an HTTP request handler in a +web application, whose parameter req contains the request object: +

    + +

    +The handler extracts the name of a system command from the request object, and then runs it without +any further checks, which can cause a command-injection vulnerability. +

    +
    + +
  • +OWASP: Command Injection. +
  • +
    +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-078/CommandInjection.ql b/PR_9_golang/go_lang/go_codeql/CWE-078/CommandInjection.ql new file mode 100644 index 0000000..9bcaf4c --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-078/CommandInjection.ql @@ -0,0 +1,29 @@ +/** + * @name Command built from user-controlled sources + * @description Building a system command from user-controlled sources is vulnerable to insertion of + * malicious code by the user. + * @kind path-problem + * @problem.severity error + * @security-severity 9.8 + * @precision high + * @id go/command-injection + * @tags security + * external/cwe/cwe-078 + */ + +import go +import semmle.go.security.CommandInjection + +module Flow = + DataFlow::MergePathGraph; + +import Flow::PathGraph + +from Flow::PathNode source, Flow::PathNode sink +where + CommandInjection::Flow::flowPath(source.asPathNode1(), sink.asPathNode1()) or + CommandInjection::DoubleDashSanitizingFlow::flowPath(source.asPathNode2(), sink.asPathNode2()) +select sink.getNode(), source, sink, "This command depends on a $@.", source.getNode(), + "user-provided value" diff --git a/PR_9_golang/go_lang/go_codeql/CWE-078/StoredCommand.go b/PR_9_golang/go_lang/go_codeql/CWE-078/StoredCommand.go new file mode 100644 index 0000000..a055360 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-078/StoredCommand.go @@ -0,0 +1,18 @@ +// {fact rule=os-command-injection@v1.0 defects=1} +package main + +import ( + "database/sql" + "os/exec" +) + +var db *sql.DB + +func run(query string) { + rows, _ := db.Query(query) + var cmdName string + rows.Scan(&cmdName) + cmd := exec.Command(cmdName) + cmd.Run() +} +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-078/StoredCommand.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-078/StoredCommand.qhelp new file mode 100644 index 0000000..5a508ac --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-078/StoredCommand.qhelp @@ -0,0 +1,45 @@ + + + + +

    +If a system command invocation is built from stored data without sufficient sanitization, and that +data is stored from a user input, a malicious user may be able to run commands to exfiltrate data or +compromise the system. +

    +
    + + +

    +If possible, use hard-coded string literals to specify the command to run. Instead of interpreting +stored input directly as command names, examine the input and then choose among hard-coded string +literals. +

    +

    +If this is not possible, then add sanitization code to verify that the user input is safe before +using it. +

    +
    + + +

    +In the following example, the function run runs a command directly from the result of a +query: +

    + +

    +The function extracts the name of a system command from the database query, and then runs it without +any further checks, which can cause a command-injection vulnerability. A possible solution is to +ensure that commands are checked against a allow list: +

    + +
    + + +
  • +OWASP: Command Injection. +
  • +
    +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-078/StoredCommand.ql b/PR_9_golang/go_lang/go_codeql/CWE-078/StoredCommand.ql new file mode 100644 index 0000000..000a823 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-078/StoredCommand.ql @@ -0,0 +1,21 @@ +/** + * @name Command built from stored data + * @description Building a system command from stored data that is user-controlled + * can lead to execution of malicious code by the user. + * @kind path-problem + * @problem.severity error + * @security-severity 9.8 + * @precision low + * @id go/stored-command + * @tags security + * external/cwe/cwe-078 + */ + +import go +import semmle.go.security.StoredCommand +import StoredCommand::Flow::PathGraph + +from StoredCommand::Flow::PathNode source, StoredCommand::Flow::PathNode sink +where StoredCommand::Flow::flowPath(source, sink) +select sink.getNode(), source, sink, "This command depends on a $@.", source.getNode(), + "stored value" diff --git a/PR_9_golang/go_lang/go_codeql/CWE-078/StoredCommandGood.go b/PR_9_golang/go_lang/go_codeql/CWE-078/StoredCommandGood.go new file mode 100644 index 0000000..48ac6ff --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-078/StoredCommandGood.go @@ -0,0 +1,26 @@ +// {fact rule=os-command-injection@v1.0 defects=0} +package main + +import ( + "database/sql" + "os/exec" +) + +var db_1 *sql.DB + +func run_1(query string) { + rows, _ := db_1.Query(query) + var cmdName string + rows.Scan(&cmdName) + if cmdName == "mybinary1" || cmdName == "mybinary2" { + cmd := exec.Command(cmdName) + _ = cmd + cmd.Run() + } + +} +// {/fact} + +func main() { + +} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-079/ReflectedXss b/PR_9_golang/go_lang/go_codeql/CWE-079/ReflectedXss new file mode 100644 index 0000000..47cc963 Binary files /dev/null and b/PR_9_golang/go_lang/go_codeql/CWE-079/ReflectedXss differ diff --git a/PR_9_golang/go_lang/go_codeql/CWE-079/ReflectedXss.go b/PR_9_golang/go_lang/go_codeql/CWE-079/ReflectedXss.go new file mode 100644 index 0000000..a03223f --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-079/ReflectedXss.go @@ -0,0 +1,30 @@ +// {fact rule=autoescape-disabled@v1.0 defects=1} +package main + +import ( + "fmt" + "net/http" +) + +func isValidUsername(username string) bool { + return false; +} + +func serve() { + http.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + username := r.Form.Get("username") + if !isValidUsername(username) { + // BAD: a request parameter is incorporated without validation into the response + fmt.Fprintf(w, "%q is an unknown user", username) + } else { + // TODO: Handle successful login + } + }) + http.ListenAndServe(":80", nil) +} +// {/fact} + +func main() { + +} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-079/ReflectedXss.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-079/ReflectedXss.qhelp new file mode 100644 index 0000000..038c764 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-079/ReflectedXss.qhelp @@ -0,0 +1,52 @@ + + + + +

    +Directly writing user input (for example, an HTTP request parameter) to an HTTP response +without properly sanitizing the input first, allows for a cross-site scripting vulnerability. +

    +

    +This kind of vulnerability is also called reflected cross-site scripting, to distinguish +it from other types of cross-site scripting. +

    +
    + + +

    +To guard against cross-site scripting, consider using contextual output encoding/escaping before +writing user input to the response, or one of the other solutions that are mentioned in the +references. +

    +
    + + +

    +The following example code writes part of an HTTP request (which is controlled by the user) +directly to the response. This leaves the website vulnerable to cross-site scripting. +

    + +

    +Sanitizing the user-controlled data prevents the vulnerability: +

    + +
    + + +
  • +OWASP: +XSS +(Cross Site Scripting) Prevention Cheat Sheet. +
  • +
  • +OWASP +Types of Cross-Site +Scripting. +
  • +
  • +Wikipedia: Cross-site scripting. +
  • +
    +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-079/ReflectedXss.ql b/PR_9_golang/go_lang/go_codeql/CWE-079/ReflectedXss.ql new file mode 100644 index 0000000..0fca12a --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-079/ReflectedXss.ql @@ -0,0 +1,36 @@ +/** + * @name Reflected cross-site scripting + * @description Writing user input directly to an HTTP response allows for + * a cross-site scripting vulnerability. + * @kind path-problem + * @problem.severity error + * @security-severity 6.1 + * @precision high + * @id go/reflected-xss + * @tags security + * external/cwe/cwe-079 + * external/cwe/cwe-116 + */ + +import go +import semmle.go.security.ReflectedXss +import ReflectedXss::Flow::PathGraph + +from + ReflectedXss::Flow::PathNode source, ReflectedXss::Flow::PathNode sink, string msg, string part, + Locatable partloc +where + ReflectedXss::Flow::flowPath(source, sink) and + ( + exists(string kind | kind = sink.getNode().(SharedXss::Sink).getSinkKind() | + kind = "rawtemplate" and + msg = "Cross-site scripting vulnerability due to $@. The value is $@." and + part = "instantiated as a raw template" + ) + or + not exists(sink.getNode().(SharedXss::Sink).getSinkKind()) and + msg = "Cross-site scripting vulnerability due to $@." and + part = "" + ) and + partloc = sink.getNode().(SharedXss::Sink).getAssociatedLoc() +select sink.getNode(), source, sink, msg, source.getNode(), "user-provided value", partloc, part diff --git a/PR_9_golang/go_lang/go_codeql/CWE-079/ReflectedXssGood.go b/PR_9_golang/go_lang/go_codeql/CWE-079/ReflectedXssGood.go new file mode 100644 index 0000000..6c27607 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-079/ReflectedXssGood.go @@ -0,0 +1,23 @@ +// {fact rule=autoescape-disabled@v1.0 defects=0} +package main + +import ( + "fmt" + "html" + "net/http" +) + +func serve1() { + http.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + username := r.Form.Get("username") + if !isValidUsername(username) { + // GOOD: a request parameter is escaped before being put into the response + fmt.Fprintf(w, "%q is an unknown user", html.EscapeString(username)) + } else { + // TODO: do something exciting + } + }) + http.ListenAndServe(":80", nil) +} +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-079/StoredXss.go b/PR_9_golang/go_lang/go_codeql/CWE-079/StoredXss.go new file mode 100644 index 0000000..a700fb1 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-079/StoredXss.go @@ -0,0 +1,18 @@ +// {fact rule=autoescape-disabled@v1.0 defects=1} +package main + +import ( + "io" + "io/ioutil" + "net/http" +) + +func ListFiles(w http.ResponseWriter, r *http.Request) { + files, _ := ioutil.ReadDir(".") + + for _, file := range files { + io.WriteString(w, file.Name()+"\n") + } +} + +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-079/StoredXss.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-079/StoredXss.qhelp new file mode 100644 index 0000000..3cb059c --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-079/StoredXss.qhelp @@ -0,0 +1,63 @@ + + + + +

    + + Directly using externally-controlled stored values (for example, file names or database contents) to + create HTML content without properly sanitizing the input first, + allows for a cross-site scripting vulnerability. + +

    +

    + + This kind of vulnerability is also called stored cross-site + scripting, to distinguish it from other types of cross-site scripting. + +

    +
    + + +

    + + To guard against cross-site scripting, consider using contextual + output encoding/escaping before using uncontrolled stored values to + create HTML content, or one of the other solutions that are mentioned + in the references. + +

    +
    + + +

    + + The following example code writes file names directly to an HTTP + response. This leaves the website vulnerable to cross-site scripting, + if an attacker can choose the file names on the disk. + +

    + +

    + Sanitizing the file names prevents the vulnerability: +

    + +
    + + +
  • + OWASP: + XSS + (Cross Site Scripting) Prevention Cheat Sheet. +
  • +
  • + OWASP: + Types of Cross-Site + Scripting. +
  • +
  • + Wikipedia: Cross-site scripting. +
  • +
    +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-079/StoredXss.ql b/PR_9_golang/go_lang/go_codeql/CWE-079/StoredXss.ql new file mode 100644 index 0000000..83628b3 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-079/StoredXss.ql @@ -0,0 +1,22 @@ +/** + * @name Stored cross-site scripting + * @description Using uncontrolled stored values in HTML allows for + * a stored cross-site scripting vulnerability. + * @kind path-problem + * @problem.severity error + * @security-severity 6.1 + * @precision low + * @id go/stored-xss + * @tags security + * external/cwe/cwe-079 + * external/cwe/cwe-116 + */ + +import go +import semmle.go.security.StoredXss +import StoredXss::Flow::PathGraph + +from StoredXss::Flow::PathNode source, StoredXss::Flow::PathNode sink +where StoredXss::Flow::flowPath(source, sink) +select sink.getNode(), source, sink, "Stored cross-site scripting vulnerability due to $@.", + source.getNode(), "stored value" diff --git a/PR_9_golang/go_lang/go_codeql/CWE-079/StoredXssGood.go b/PR_9_golang/go_lang/go_codeql/CWE-079/StoredXssGood.go new file mode 100644 index 0000000..3e5896f --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-079/StoredXssGood.go @@ -0,0 +1,18 @@ +// {fact rule=autoescape-disabled@v1.0 defects=0} +package main + +import ( + "html" + "io" + "io/ioutil" + "net/http" +) + +func ListFiles1(w http.ResponseWriter, r *http.Request) { + files, _ := ioutil.ReadDir(".") + + for _, file := range files { + io.WriteString(w, html.EscapeString(file.Name())+"\n") + } +} +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-089/SqlInjection b/PR_9_golang/go_lang/go_codeql/CWE-089/SqlInjection new file mode 100644 index 0000000..cf7315b Binary files /dev/null and b/PR_9_golang/go_lang/go_codeql/CWE-089/SqlInjection differ diff --git a/PR_9_golang/go_lang/go_codeql/CWE-089/SqlInjection.go b/PR_9_golang/go_lang/go_codeql/CWE-089/SqlInjection.go new file mode 100644 index 0000000..192a064 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-089/SqlInjection.go @@ -0,0 +1,21 @@ +// {fact rule=cross-site-scripting@v1.0 defects=1} +package main + +import ( + "database/sql" + "fmt" + "net/http" +) + +func handler(db *sql.DB, req *http.Request) { + q := fmt.Sprintf("SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='%s' ORDER BY PRICE", + req.URL.Query()["category"]) + db.Query(q) +} + +// {/fact} + + +func main() { + +} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-089/SqlInjection.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-089/SqlInjection.qhelp new file mode 100644 index 0000000..8293fbe --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-089/SqlInjection.qhelp @@ -0,0 +1,43 @@ + + + + +

    +If a database query (such as an SQL or NoSQL query) is built from user-provided data without +sufficient sanitization, a malicious user may be able to run commands that exfiltrate, tamper with, +or destroy data stored in the database. +

    +
    + + +

    +Most database connector libraries offer a way of safely embedding untrusted data into a query by +means of query parameters or prepared statements. Use these features rather than building queries +by string concatenation. +

    +
    + + +

    +In the following example, assume the function handler is an HTTP request handler in a +web application, whose parameter req contains the request object: +

    + +

    +The handler constructs an SQL query involving user input taken from the request object unsafely +using fmt.Sprintf to embed a request parameter directly into the query string +q. The parameter may include quote characters, allowing a malicious user to terminate +the string literal into which the parameter is embedded and add arbitrary SQL code after it. +

    +

    +Instead, the untrusted query parameter should be safely embedded using placeholder parameters: +

    + +
    + + +
  • Wikipedia: SQL injection.
  • +
    +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-089/SqlInjection.ql b/PR_9_golang/go_lang/go_codeql/CWE-089/SqlInjection.ql new file mode 100644 index 0000000..c11b539 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-089/SqlInjection.ql @@ -0,0 +1,21 @@ +/** + * @name Database query built from user-controlled sources + * @description Building a database query from user-controlled sources is vulnerable to insertion of + * malicious code by the user. + * @kind path-problem + * @problem.severity error + * @security-severity 8.8 + * @precision high + * @id go/sql-injection + * @tags security + * external/cwe/cwe-089 + */ + +import go +import semmle.go.security.SqlInjection +import SqlInjection::Flow::PathGraph + +from SqlInjection::Flow::PathNode source, SqlInjection::Flow::PathNode sink +where SqlInjection::Flow::flowPath(source, sink) +select sink.getNode(), source, sink, "This query depends on a $@.", source.getNode(), + "user-provided value" diff --git a/PR_9_golang/go_lang/go_codeql/CWE-089/SqlInjectionGood.go b/PR_9_golang/go_lang/go_codeql/CWE-089/SqlInjectionGood.go new file mode 100644 index 0000000..c6a9b4d --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-089/SqlInjectionGood.go @@ -0,0 +1,13 @@ +// {fact rule=cross-site-scripting@v1.0 defects=0} +package main + +import ( + "database/sql" + "net/http" +) + +func handlerGood(db *sql.DB, req *http.Request) { + q := "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='?' ORDER BY PRICE" + db.Query(q, req.URL.Query()["category"]) +} +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-089/StringBreak.go b/PR_9_golang/go_lang/go_codeql/CWE-089/StringBreak.go new file mode 100644 index 0000000..cea6f6b --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-089/StringBreak.go @@ -0,0 +1,20 @@ +// {fact rule=cross-site-scripting@v1.0 defects=1} +package main + +import ( + "encoding/json" + "fmt" + + sq "github.com/Masterminds/squirrel" +) + +func save(id string, version interface{}) { + versionJSON, _ := json.Marshal(version) + sq.StatementBuilder. + Insert("resources"). + Columns("resource_id", "version_md5"). + Values(id, sq.Expr(fmt.Sprintf("md5('%s')", versionJSON))). + Exec() +} + +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-089/StringBreak.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-089/StringBreak.qhelp new file mode 100644 index 0000000..1c4f286 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-089/StringBreak.qhelp @@ -0,0 +1,49 @@ + + + + +

    +Code that constructs a string containing a quoted substring needs to ensure that any user-provided +data embedded in between the quotes does not itself contain a quote. Otherwise the embedded data +could (accidentally or intentionally) change the structure of the overall string by terminating +the quoted substring early, with potentially severe consequences. If, for example, the string is +later interpreted as an operating-system command or database query, a malicious attacker may be +able to craft input data that enables a command injection or SQL injection attack. +

    +
    + + +

    +Sanitize the embedded data appropriately to ensure quotes are escaped, or use an API that does +not rely on manually constructing quoted substrings. +

    +
    + + +

    +In the following example, assume that version is an object from an untrusted source. +The code snippet first uses json.Marshal to serialize this object into a string, and +then embeds it into a SQL query built using the Squirrel library. +

    + +

    +Note that while Squirrel provides a structured API for building SQL queries that mitigates against +common causes of SQL injection vulnerabilities, this code is still vulnerable: if the JSON-encoded +representation of version contains a single quote, this will prematurely close the +surrounding string, changing the structure of the SQL expression being constructed. This could be +exploited to mount a SQL injection attack. +

    +

    +To fix this vulnerability, use Squirrel's placeholder syntax, which avoids the need to explicitly +construct a quoted string. +

    + +
    + + +
  • Wikipedia: SQL injection.
  • +
  • OWASP: Command Injection.
  • +
    +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-089/StringBreak.ql b/PR_9_golang/go_lang/go_codeql/CWE-089/StringBreak.ql new file mode 100644 index 0000000..2151356 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-089/StringBreak.ql @@ -0,0 +1,26 @@ +/** + * @name Potentially unsafe quoting + * @description If a quoted string literal is constructed from data that may itself contain quotes, + * the embedded data could (accidentally or intentionally) change the structure of + * the overall string. + * @kind path-problem + * @problem.severity warning + * @security-severity 9.3 + * @precision high + * @id go/unsafe-quoting + * @tags correctness + * security + * external/cwe/cwe-078 + * external/cwe/cwe-089 + * external/cwe/cwe-094 + */ + +import go +import semmle.go.security.StringBreak +import StringBreak::Flow::PathGraph + +from StringBreak::Flow::PathNode source, StringBreak::Flow::PathNode sink +where StringBreak::Flow::flowPath(source, sink) +select sink.getNode(), source, sink, + "If this $@ contains a " + sink.getNode().(StringBreak::Sink).getQuote().getType() + + " quote, it could break out of " + "the enclosing quotes.", source.getNode(), "JSON value" diff --git a/PR_9_golang/go_lang/go_codeql/CWE-089/StringBreakGood.go b/PR_9_golang/go_lang/go_codeql/CWE-089/StringBreakGood.go new file mode 100644 index 0000000..a0ae70a --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-089/StringBreakGood.go @@ -0,0 +1,17 @@ +// {fact rule=cross-site-scripting@v1.0 defects=0} +package main + +import ( + "encoding/json" + sq "github.com/Masterminds/squirrel" +) + +func saveGood(id string, version interface{}) { + versionJSON, _ := json.Marshal(version) + sq.StatementBuilder. + Insert("resources"). + Columns("resource_id", "version_md5"). + Values(id, sq.Expr("md5(?)", versionJSON)). + Exec() +} +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-117/LogInjection b/PR_9_golang/go_lang/go_codeql/CWE-117/LogInjection new file mode 100644 index 0000000..62f5b61 Binary files /dev/null and b/PR_9_golang/go_lang/go_codeql/CWE-117/LogInjection differ diff --git a/PR_9_golang/go_lang/go_codeql/CWE-117/LogInjection.go b/PR_9_golang/go_lang/go_codeql/CWE-117/LogInjection.go new file mode 100644 index 0000000..075c8ac --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-117/LogInjection.go @@ -0,0 +1,19 @@ +// {fact rule=ldap-injection@v1.0 defects=1} +package main + +import ( + "log" + "net/http" +) + +// BAD: A user-provided value is written directly to a log. +func handler(req *http.Request) { + username := req.URL.Query()["username"][0] + log.Printf("user %s logged in.\n", username) +} +// {/fact} + + +func main() { + +} \ No newline at end of file diff --git a/PR_9_golang/go_lang/go_codeql/CWE-117/LogInjection.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-117/LogInjection.qhelp new file mode 100644 index 0000000..31a05e6 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-117/LogInjection.qhelp @@ -0,0 +1,46 @@ + + + +

    If unsanitized user input is written to a log entry, a malicious user may +be able to forge new log entries.

    + +

    Forgery can occur if a user provides some input with characters that are interpreted +when the log output is displayed. If the log is displayed as a plain text file, then new +line characters can be used by a malicious user. If the log is displayed as HTML, then +arbitrary HTML may be included to spoof log entries.

    +
    + + +

    +User input should be suitably encoded before it is logged. +

    +

    +If the log entries are plain text then line breaks should be removed from user input, using +strings.Replace or similar. Care should also be taken that user input is clearly marked +in log entries, and that a malicious user cannot cause confusion in other ways. +

    +

    +For log entries that will be displayed in HTML, user input should be HTML encoded using +html.EscapeString or similar before being logged, to prevent forgery and +other forms of HTML injection. +

    + +
    + + +

    +In the following example, a user name, provided by the user, is logged using a logging framework without any sanitization. +

    + +

    +In the next example, strings.Replace is used to ensure no line endings are present in the user input. +

    + +
    + + +
  • OWASP: Log Injection.
  • +
    +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-117/LogInjection.ql b/PR_9_golang/go_lang/go_codeql/CWE-117/LogInjection.ql new file mode 100644 index 0000000..5b6586c --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-117/LogInjection.ql @@ -0,0 +1,21 @@ +/** + * @name Log entries created from user input + * @description Building log entries from user-controlled sources is vulnerable to + * insertion of forged log entries by a malicious user. + * @kind path-problem + * @problem.severity error + * @security-severity 7.8 + * @precision medium + * @id go/log-injection + * @tags security + * external/cwe/cwe-117 + */ + +import go +import semmle.go.security.LogInjection +import LogInjection::Flow::PathGraph + +from LogInjection::Flow::PathNode source, LogInjection::Flow::PathNode sink +where LogInjection::Flow::flowPath(source, sink) +select sink.getNode(), source, sink, "This log entry depends on a $@.", source.getNode(), + "user-provided value" diff --git a/PR_9_golang/go_lang/go_codeql/CWE-117/LogInjectionGood.go b/PR_9_golang/go_lang/go_codeql/CWE-117/LogInjectionGood.go new file mode 100644 index 0000000..d1afb88 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-117/LogInjectionGood.go @@ -0,0 +1,17 @@ +// {fact rule=ldap-injection@v1.0 defects=0} +package main + +import ( + "log" + "net/http" + "strings" +) + +// GOOD: The user-provided value is escaped before being written to the log. +func handlerGood(req *http.Request) { + username := req.URL.Query()["username"][0] + escapedUsername := strings.Replace(username, "\n", "", -1) + escapedUsername = strings.Replace(escapedUsername, "\r", "", -1) + log.Printf("user %s logged in.\n", escapedUsername) +} +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-190/AllocationSizeOverflow b/PR_9_golang/go_lang/go_codeql/CWE-190/AllocationSizeOverflow new file mode 100644 index 0000000..f54eedb Binary files /dev/null and b/PR_9_golang/go_lang/go_codeql/CWE-190/AllocationSizeOverflow differ diff --git a/PR_9_golang/go_lang/go_codeql/CWE-190/AllocationSizeOverflow.go b/PR_9_golang/go_lang/go_codeql/CWE-190/AllocationSizeOverflow.go new file mode 100644 index 0000000..f2473d0 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-190/AllocationSizeOverflow.go @@ -0,0 +1,25 @@ +// {fact rule=arithmetic-overflow@v1.0 defects=0} +package main + +import "encoding/json" + +func encryptBuffer(buffer []byte) ([]byte, error) { + return buffer, nil +} + +func encryptValue(v interface{}) ([]byte, error) { + jsonData, err := json.Marshal(v) + if err != nil { + return nil, err + } + size := len(jsonData) + (len(jsonData) % 16) + buffer := make([]byte, size) + copy(buffer, jsonData) + return encryptBuffer(buffer) +} + +// {/fact} + +func main() { + +} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-190/AllocationSizeOverflow.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-190/AllocationSizeOverflow.qhelp new file mode 100644 index 0000000..737efe7 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-190/AllocationSizeOverflow.qhelp @@ -0,0 +1,59 @@ + + + + +

    +Performing calculations involving the size of potentially large strings or slices can result in an +overflow (for signed integer types) or a wraparound (for unsigned types). An overflow causes the +result of the calculation to become negative, while a wraparound results in a small (positive) +number. +

    +

    +This can cause further issues. If, for example, the result is then used in an allocation, it will +cause a runtime panic if it is negative, and allocate an unexpectedly small buffer otherwise. +

    +
    + + +

    +Always guard against overflow in arithmetic operations involving potentially large numbers by doing +one of the following: +

    +
      +
    • Validate the size of the data from which the numbers are computed.
    • +
    • Define a guard on the arithmetic expression, so that the operation is performed only if the +result can be known to be less than, or equal to, the maximum value for the type.
    • +
    • Use a wider type (such as uint64 instead of int), so that larger input +values do not cause overflow.
    • +
    +
    + + +

    +In the following example, assume that there is a function encryptBuffer that encrypts +byte slices whose length must be padded to be a multiple of 16. The function +encryptValue provides a convenience wrapper around this function: when passed an +arbitrary value, it first encodes that value as JSON, pads the resulting byte slice, and then passes +it to encryptBuffer. +

    + +

    +When passed a value whose JSON encoding is close to the maximum value of type int in +length, the computation of size will overflow, producing a negative value. When that +negative value is passed to make, a runtime panic will occur. +

    +

    +To guard against this, the function should be improved to check the length of the JSON-encoded +value. For example, here is a version of encryptValue that ensures the value is no +larger than 64 MB, which fits comfortably within an int and avoids the overflow: +

    + +
    + + +
  • The Go Programming Language Specification: Integer overflow.
  • +
  • The Go Programming Language Specification: Making slices, maps and channels.
  • +
    +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-190/AllocationSizeOverflow.ql b/PR_9_golang/go_lang/go_codeql/CWE-190/AllocationSizeOverflow.ql new file mode 100644 index 0000000..869d0d5 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-190/AllocationSizeOverflow.ql @@ -0,0 +1,26 @@ +/** + * @name Size computation for allocation may overflow + * @description When computing the size of an allocation based on the size of a large object, + * the result may overflow and cause a runtime panic. + * @kind path-problem + * @problem.severity warning + * @security-severity 8.1 + * @precision high + * @id go/allocation-size-overflow + * @tags security + * external/cwe/cwe-190 + */ + +import go +import semmle.go.security.AllocationSizeOverflow +import AllocationSizeOverflow::Flow::PathGraph + +from + AllocationSizeOverflow::Flow::PathNode source, AllocationSizeOverflow::Flow::PathNode sink, + DataFlow::Node allocsz +where + AllocationSizeOverflow::Flow::flowPath(source, sink) and + AllocationSizeOverflow::isSinkWithAllocationSize(sink.getNode(), allocsz) +select sink, source, sink, + "This operation, which is used in an $@, involves a $@ and might overflow.", allocsz, + "allocation", source, "potentially large value" diff --git a/PR_9_golang/go_lang/go_codeql/CWE-190/AllocationSizeOverflowGood.go b/PR_9_golang/go_lang/go_codeql/CWE-190/AllocationSizeOverflowGood.go new file mode 100644 index 0000000..a74ca00 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-190/AllocationSizeOverflowGood.go @@ -0,0 +1,22 @@ +// {fact rule=arithmetic-overflow@v1.0 defects=0} +package main + +import ( + "encoding/json" + "errors" +) + +func encryptValueGood(v interface{}) ([]byte, error) { + jsonData, err := json.Marshal(v) + if err != nil { + return nil, err + } + if len(jsonData) > 64*1024*1024 { + return nil, errors.New("value too large") + } + size := len(jsonData) + (len(jsonData) % 16) + buffer := make([]byte, size) + copy(buffer, jsonData) + return encryptBuffer(buffer) +} +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-209/StackTraceExposure.go b/PR_9_golang/go_lang/go_codeql/CWE-209/StackTraceExposure.go new file mode 100644 index 0000000..2bc70af --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-209/StackTraceExposure.go @@ -0,0 +1,20 @@ +// {fact rule=stack-trace-exposure@v1.0 defects=0} +package example + +import ( + "log" + "net/http" + "runtime" +) + +func handlePanic(w http.ResponseWriter, r *http.Request) { + buf := make([]byte, 2<<16) + buf = buf[:runtime.Stack(buf, true)] + // BAD: printing a stack trace back to the response + w.Write(buf) + // GOOD: logging the response to the server and sending + // a more generic message. + log.Printf("Panic: %s", buf) + w.Write([]byte("An unexpected runtime error occurred")) +} +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-209/StackTraceExposure.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-209/StackTraceExposure.qhelp new file mode 100644 index 0000000..bd787ac --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-209/StackTraceExposure.qhelp @@ -0,0 +1,38 @@ + + + +

    Software developers often add stack traces to error messages, as a +debugging aid. Whenever that error message occurs for an end user, the +developer can use the stack trace to help identify how to fix the +problem. In particular, stack traces can tell the developer more about +the sequence of events that led to a failure, as opposed to merely the +final state of the software when the error occurred.

    + +

    Unfortunately, the same information can be useful to an attacker. +The sequence of class names in a stack trace can reveal the structure +of the application as well as any internal components it relies on.

    +
    + + +

    Send the user a more generic error message that reveals less information. +Either suppress the stack trace entirely, or log it only on the server.

    +
    + + +

    In the following example, a panic is handled in two different +ways. In the first version, labeled BAD, a detailed stack trace is +written to a user-facing HTTP response object, which may disclose +sensitive information. In the second version, the error message is +logged only on the server. That way, the developers can still access +and use the error log, but remote users will not see the +information.

    + + +
    + + +
  • OWASP: Improper Error Handling.
  • +
    +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-209/StackTraceExposure.ql b/PR_9_golang/go_lang/go_codeql/CWE-209/StackTraceExposure.ql new file mode 100644 index 0000000..3440fdb --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-209/StackTraceExposure.ql @@ -0,0 +1,79 @@ +/** + * @name Information exposure through a stack trace + * @description Information from a stack trace propagates to an external user. + * Stack traces can unintentionally reveal implementation details + * that are useful to an attacker for developing a subsequent exploit. + * @kind path-problem + * @problem.severity error + * @security-severity 5.4 + * @precision high + * @id go/stack-trace-exposure + * @tags security + * external/cwe/cwe-209 + * external/cwe/cwe-497 + */ + +import go +import semmle.go.security.InsecureFeatureFlag::InsecureFeatureFlag + +/** + * A flag indicating the program is in debug or development mode, or that stack + * dumps have been specifically enabled. + */ +class DebugModeFlag extends FlagKind { + DebugModeFlag() { this = "debugMode" } + + bindingset[result] + override string getAFlagName() { + result.regexpMatch("(?i).*(trace|debug|devel|((en|dis)able|print)stack).*") + } +} + +/** + * The function `runtime.Stack`, which emits a stack trace. + */ +class StackFunction extends Function { + StackFunction() { this.hasQualifiedName("runtime", "Stack") } +} + +/** + * The function `runtime/debug.Stack`, which emits a stack trace. + */ +class DebugStackFunction extends Function { + DebugStackFunction() { this.hasQualifiedName("runtime/debug", "Stack") } +} + +module StackTraceExposureConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + source.(DataFlow::PostUpdateNode).getPreUpdateNode() = + any(StackFunction f).getACall().getArgument(0) or + source = any(DebugStackFunction f).getACall().getResult() + } + + predicate isSink(DataFlow::Node sink) { sink instanceof Http::ResponseBody } + + predicate isBarrier(DataFlow::Node node) { + // Sanitise everything controlled by an is-debug-mode check. + // Imprecision: I don't try to guess which arm of a branch is intended + // to mean debug mode, and which is production mode. + exists(ControlFlow::ConditionGuardNode cgn | + cgn.ensures(any(DebugModeFlag f).getAFlag().getANode(), _) + | + cgn.dominates(node.getBasicBlock()) + ) + } +} + +/** + * Tracks taint flow for reasoning about stack traces being written to an HTTP + * response body without an intervening debug- or development-mode conditional. + */ +module StackTraceExposureFlow = TaintTracking::Global; + +import StackTraceExposureFlow::PathGraph + +from StackTraceExposureFlow::PathNode source, StackTraceExposureFlow::PathNode sink +where StackTraceExposureFlow::flowPath(source, sink) +select sink.getNode(), source, sink, + "HTTP response depends on $@ and may be exposed to an external user.", source.getNode(), + "stack trace information" diff --git a/PR_9_golang/go_lang/go_codeql/CWE-295/DisabledCertificateCheck b/PR_9_golang/go_lang/go_codeql/CWE-295/DisabledCertificateCheck new file mode 100644 index 0000000..4bd0436 Binary files /dev/null and b/PR_9_golang/go_lang/go_codeql/CWE-295/DisabledCertificateCheck differ diff --git a/PR_9_golang/go_lang/go_codeql/CWE-295/DisabledCertificateCheck.go b/PR_9_golang/go_lang/go_codeql/CWE-295/DisabledCertificateCheck.go new file mode 100644 index 0000000..eb0870b --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-295/DisabledCertificateCheck.go @@ -0,0 +1,22 @@ +// {fact rule=improper-certificate-validation@v1.0 defects=1} +package main + +import ( + "crypto/tls" + "net/http" +) + +func doAuthReq(authReq *http.Request) *http.Response { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + client := &http.Client{Transport: tr} + res, _ := client.Do(authReq) + return res +} + +// {/fact} + +func main() { + +} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-295/DisabledCertificateCheck.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-295/DisabledCertificateCheck.qhelp new file mode 100644 index 0000000..604a52f --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-295/DisabledCertificateCheck.qhelp @@ -0,0 +1,39 @@ + + + + +

    +The field InsecureSkipVerify controls whether a TLS client verifies the server's +certificate chain and host name. If set to true, the client will accept any certificate +and any host name in that certificate, making it susceptible to man-in-the-middle attacks. +

    +
    + + +

    +Do not set InsecureSkipVerify to true except in tests. +

    +
    + + +

    +The following code snippet shows a function that performs an HTTP request over TLS with +certificate verification disabled: +

    + +

    +While this is acceptable in a test, it should not be used in production code. Instead, certificates +should be configured such that verification can be performed. +

    +
    + +
  • +Package tls: Config. +
  • +
  • +SSL.com: Browsers and Certificate Validation. +
  • +
    +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-295/DisabledCertificateCheck.ql b/PR_9_golang/go_lang/go_codeql/CWE-295/DisabledCertificateCheck.ql new file mode 100644 index 0000000..ae83fbc --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-295/DisabledCertificateCheck.ql @@ -0,0 +1,96 @@ +/** + * @name Disabled TLS certificate check + * @description If an application disables TLS certificate checking, it may be vulnerable to + * man-in-the-middle attacks. + * @kind problem + * @problem.severity warning + * @security-severity 7.5 + * @precision high + * @id go/disabled-certificate-check + * @tags security + * external/cwe/cwe-295 + */ + +/* + * The approach taken by this query is to look for assignments that set `InsecureSkipVerify` + * (from struct `Config` of package `crypto/tls`) to `true`. We exclude assignments that are + * guarded by a feature-flag selecting whether verification should be skipped or not, since + * such assignments are not by themselves dangerous. Similarly, we exclude cases where the + * name of the enclosing function or the name of a variable/field into which the configuration + * flows suggests that it is deliberately insecure. + * + * We also exclude assignments in test code, where it makes sense to disable certificate checking. + */ + +import go +import semmle.go.security.InsecureFeatureFlag::InsecureFeatureFlag + +/** + * Holds if `part` becomes a part of `whole`, either by (local) data flow or by being incorporated + * into `whole` through having its address taken or being written to a field of `whole`. + */ +predicate becomesPartOf(DataFlow::Node part, DataFlow::Node whole) { + DataFlow::localFlow(part, whole) + or + whole.(DataFlow::AddressOperationNode).getOperand() = part + or + exists(Write w | w.writesField(whole.(DataFlow::PostUpdateNode).getPreUpdateNode(), _, part)) +} + +/** + * A flag suggesting a deliberately insecure certificate setup. + */ +class InsecureCertificateFlag extends FlagKind { + InsecureCertificateFlag() { this = "insecureCertificate" } + + bindingset[result] + override string getAFlagName() { + result.regexpMatch("(?i).*(selfcert|selfsign|validat|verif|trust).*") + } +} + +/** + * Gets a control-flow node that represents a (likely) flag controlling an insecure certificate setup. + */ +ControlFlow::ConditionGuardNode getAnInsecureCertificateCheck() { + result.ensures(any(InsecureCertificateFlag f).getAFlag().getANode(), _) +} + +/** + * Returns flag kinds relevant to this query: a generic security feature flag, or one + * specifically controlling insecure certificate configuration. + */ +FlagKind securityOrTlsVersionFlag() { + result = any(SecurityFeatureFlag f) or + result = any(InsecureCertificateFlag f) +} + +/** + * Holds if `name` is (likely to be) a general security flag or one specifically controlling + * an insecure certificate setup. + */ +bindingset[name] +predicate isSecurityOrCertificateConfigFlag(string name) { + name = securityOrTlsVersionFlag().getAFlagName() +} + +from Write w, DataFlow::Node base, Field f, DataFlow::Node rhs +where + w.writesField(base, f, rhs) and + f.hasQualifiedName("crypto/tls", "Config", "InsecureSkipVerify") and + rhs.getBoolValue() = true and + // exclude writes guarded by a feature flag + not [getASecurityFeatureFlagCheck(), getAnInsecureCertificateCheck()].dominatesNode(w) and + // exclude results in functions whose name documents the insecurity + not exists(FuncDef fn | fn = w.getRoot() | + isSecurityOrCertificateConfigFlag(fn.getEnclosingFunction*().getName()) + ) and + // exclude results that flow into a field/variable whose name documents the insecurity + not exists(ValueEntity e, DataFlow::Node init | + isSecurityOrCertificateConfigFlag(e.getName()) and + any(Write w2).writes(e, init) and + becomesPartOf*(base, init) + ) and + // exclude results in test code + exists(File fl | fl = w.getFile() | not fl instanceof TestFile) +select w, "InsecureSkipVerify should not be used in production code." diff --git a/PR_9_golang/go_lang/go_codeql/CWE-312/CleartextLogging b/PR_9_golang/go_lang/go_codeql/CWE-312/CleartextLogging new file mode 100644 index 0000000..1882cec Binary files /dev/null and b/PR_9_golang/go_lang/go_codeql/CWE-312/CleartextLogging differ diff --git a/PR_9_golang/go_lang/go_codeql/CWE-312/CleartextLogging.go b/PR_9_golang/go_lang/go_codeql/CWE-312/CleartextLogging.go new file mode 100644 index 0000000..f08ace8 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-312/CleartextLogging.go @@ -0,0 +1,20 @@ +// {fact rule=insecure-connection@v1.0 defects=1} +package main + +import ( + "log" + "net/http" +) + +func serve() { + http.HandleFunc("/register", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + user := r.Form.Get("user") + pw := r.Form.Get("password") + + log.Printf("Registering new user %s with password %s.\n", user, pw) + }) + http.ListenAndServe(":80", nil) +} + +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-312/CleartextLogging.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-312/CleartextLogging.qhelp new file mode 100644 index 0000000..e8326e5 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-312/CleartextLogging.qhelp @@ -0,0 +1,50 @@ + + + + +

    +Sensitive information that is logged unencrypted is accessible to an attacker +who gains access to the logs. +

    +
    + + +

    +Ensure that sensitive information is always encrypted or obfuscated before being +logged. +

    + +

    +In general, decrypt sensitive information only at the point where it is +necessary for it to be used in cleartext. +

    + +

    +Be aware that external processes often store the standard out and +standard error streams of the application, causing logged sensitive +information to be stored. +

    +
    + + +

    +The following example code logs user credentials (in this case, their password) +in plain text: +

    + +

    +Instead, the credentials should be encrypted, obfuscated, or omitted entirely: +

    + +
    + + + +
  • M. Dowd, J. McDonald and J. Schuhm, The Art of Software Security Assessment, 1st Edition, Chapter 2 - 'Common Vulnerabilities of Encryption', p. 43. Addison Wesley, 2006.
  • +
  • M. Howard and D. LeBlanc, Writing Secure Code, 2nd Edition, Chapter 9 - 'Protecting Secret Data', p. 299. Microsoft, 2002.
  • +
  • OWASP: Password Plaintext Storage.
  • + +
    +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-312/CleartextLogging.ql b/PR_9_golang/go_lang/go_codeql/CWE-312/CleartextLogging.ql new file mode 100644 index 0000000..fe7dc88 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-312/CleartextLogging.ql @@ -0,0 +1,23 @@ +/** + * @name Clear-text logging of sensitive information + * @description Logging sensitive information without encryption or hashing can + * expose it to an attacker. + * @kind path-problem + * @problem.severity error + * @security-severity 7.5 + * @precision high + * @id go/clear-text-logging + * @tags security + * external/cwe/cwe-312 + * external/cwe/cwe-315 + * external/cwe/cwe-359 + */ + +import go +import semmle.go.security.CleartextLogging +import CleartextLogging::Flow::PathGraph + +from CleartextLogging::Flow::PathNode source, CleartextLogging::Flow::PathNode sink +where CleartextLogging::Flow::flowPath(source, sink) +select sink.getNode(), source, sink, "$@ flows to a logging call.", source.getNode(), + "Sensitive data returned by " + source.getNode().(CleartextLogging::Source).describe() diff --git a/PR_9_golang/go_lang/go_codeql/CWE-312/CleartextLoggingGood.go b/PR_9_golang/go_lang/go_codeql/CWE-312/CleartextLoggingGood.go new file mode 100644 index 0000000..bb8d943 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-312/CleartextLoggingGood.go @@ -0,0 +1,35 @@ +// {fact rule=insecure-connection@v1.0 defects=0} +package main + +import ( + "log" + "net/http" +) + +const ( + certFile = "path/to/cert.pem" // Path to your certificate file + keyFile = "path/to/key.pem" // Path to your key file +) + +func use(pw string) { + return +} + +func serve1() { + http.HandleFunc("/register", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + user := r.Form.Get("user") + pw := r.Form.Get("password") + + log.Printf("Registering new user %s.\n", user) + + // ... + use(pw) + }) + http.ListenAndServeTLS(":80", certFile, keyFile, nil) +} +// {/fact} + +func main() { + +} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-322/InsecureHostKeyCallback.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-322/InsecureHostKeyCallback.qhelp new file mode 100644 index 0000000..ce4872f --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-322/InsecureHostKeyCallback.qhelp @@ -0,0 +1,49 @@ + + + +

    + The ClientConfig specifying the configuration for establishing a SSH connection has a field HostKeyCallback that must be initialized with a function that validates the host key returned by the server. +

    + +

    + Not properly verifying the host key returned by a server provides attackers with an opportunity to perform a Machine-in-the-Middle (MitM) attack. + A successful attack can compromise the confidentiality and integrity of the information communicated with the server. +

    + +

    + The ssh package provides the predefined callback InsecureIgnoreHostKey that can be used during development and testing. + It accepts any provided host key. + This callback, or a semantically similar callback, should not be used in production code. +

    +
    + + +

    +The HostKeyCallback field of ClientConfig should be initialized with a function that validates a host key against an allow list. +If a key is not on a predefined allow list, the connection must be terminated and the failed security operation should be logged. +

    +

    +When the allow list contains only a single host key then the function FixedHostKey can be used. +

    +
    + + +

    The following example shows the use of InsecureIgnoreHostKey and an insecure host key callback implementation commonly used in non-production code.

    + + + +

    The next example shows a secure implementation using the FixedHostKey that implements an allow-list.

    + + + +
    + + +
  • + Go Dev: + package ssh. +
  • +
    +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-322/InsecureHostKeyCallback.ql b/PR_9_golang/go_lang/go_codeql/CWE-322/InsecureHostKeyCallback.ql new file mode 100644 index 0000000..bca436d --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-322/InsecureHostKeyCallback.ql @@ -0,0 +1,116 @@ +/** + * @name Use of insecure HostKeyCallback implementation + * @description Detects insecure SSL client configurations with an implementation of the `HostKeyCallback` that accepts all host keys. + * @kind path-problem + * @problem.severity warning + * @security-severity 8.2 + * @precision high + * @id go/insecure-hostkeycallback + * @tags security + * external/cwe/cwe-322 + */ + +import go + +/** The `ssh.InsecureIgnoreHostKey` function, which allows connecting to any host regardless of its host key. */ +class InsecureIgnoreHostKey extends Function { + InsecureIgnoreHostKey() { + this.hasQualifiedName(CryptoSsh::packagePath(), "InsecureIgnoreHostKey") + } +} + +/** An SSH host-key checking function. */ +class HostKeyCallbackFunc extends DataFlow::Node { + HostKeyCallbackFunc() { + exists(NamedType nt | nt.hasQualifiedName(CryptoSsh::packagePath(), "HostKeyCallback") | + this.getType().getUnderlyingType() = nt.getUnderlyingType() + ) and + // Restrict possible sources to either function definitions or + // the result of some external function call (e.g. InsecureIgnoreHostKey()) + // Otherwise we also consider typecasts, variables and other such intermediates + // as sources. + ( + this instanceof DataFlow::FunctionNode + or + exists(DataFlow::CallNode call | not exists(call.getACallee().getBody()) | + this = call.getAResult() + ) + ) + } +} + +/** A callback function value that is insecure when used as a `HostKeyCallback`, because it always returns `nil`. */ +class InsecureHostKeyCallbackFunc extends HostKeyCallbackFunc { + InsecureHostKeyCallbackFunc() { + // Either a call to InsecureIgnoreHostKey(), which we know returns an insecure callback. + this = any(InsecureIgnoreHostKey f).getACall().getAResult() + or + // Or a callback function in the source code (named or anonymous) that always returns nil. + forex(DataFlow::ResultNode returnValue | + returnValue = this.(DataFlow::FunctionNode).getAResult() + | + returnValue = Builtin::nil().getARead() + ) + } +} + +module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof HostKeyCallbackFunc } + + /** + * Holds if `sink` is a value written by `write` to a field `ClientConfig.HostKeyCallback`. + */ + additional predicate writeIsSink(DataFlow::Node sink, Write write) { + exists(Field f | + f.hasQualifiedName(CryptoSsh::packagePath(), "ClientConfig", "HostKeyCallback") and + write.writesField(_, f, sink) + ) + } + + predicate isSink(DataFlow::Node sink) { writeIsSink(sink, _) } +} + +/** + * Tracks data flow to identify `HostKeyCallbackFunc` instances that reach + * `ClientConfig.HostKeyCallback` fields. + */ +module Flow = DataFlow::Global; + +import Flow::PathGraph + +/** + * Holds if a secure host-check function reaches `sink` or another similar sink. + * + * A sink is considered similar if it writes to the same variable and field. + */ +predicate hostCheckReachesSink(Flow::PathNode sink) { + exists(Flow::PathNode source | + not source.getNode() instanceof InsecureHostKeyCallbackFunc and + ( + Flow::flowPath(source, sink) + or + exists( + Flow::PathNode otherSink, Write sinkWrite, Write otherSinkWrite, + SsaWithFields sinkAccessPath, SsaWithFields otherSinkAccessPath + | + Flow::flowPath(source, otherSink) and + Config::writeIsSink(sink.getNode(), sinkWrite) and + Config::writeIsSink(otherSink.getNode(), otherSinkWrite) and + sinkWrite.writesField(sinkAccessPath.getAUse(), _, sink.getNode()) and + otherSinkWrite.writesField(otherSinkAccessPath.getAUse(), _, otherSink.getNode()) and + otherSinkAccessPath = sinkAccessPath.similar() + ) + ) + ) +} + +from Flow::PathNode source, Flow::PathNode sink +where + Flow::flowPath(source, sink) and + source.getNode() instanceof InsecureHostKeyCallbackFunc and + // Exclude cases where a good access-path function reaches the same or a similar sink + // (these probably indicate optional host-checking) + not hostCheckReachesSink(sink) +select sink, source, sink, + "Configuring SSH ClientConfig with insecure HostKeyCallback implementation from $@.", + source.getNode(), "this source" diff --git a/PR_9_golang/go_lang/go_codeql/CWE-322/InsecureHostKeyCallbackExample b/PR_9_golang/go_lang/go_codeql/CWE-322/InsecureHostKeyCallbackExample new file mode 100644 index 0000000..f877ac8 Binary files /dev/null and b/PR_9_golang/go_lang/go_codeql/CWE-322/InsecureHostKeyCallbackExample differ diff --git a/PR_9_golang/go_lang/go_codeql/CWE-322/InsecureHostKeyCallbackExample.go b/PR_9_golang/go_lang/go_codeql/CWE-322/InsecureHostKeyCallbackExample.go new file mode 100644 index 0000000..4969665 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-322/InsecureHostKeyCallbackExample.go @@ -0,0 +1,31 @@ +// {fact rule=do-not-auto-add-or-warning-missing-hostkey-policy@v1.0 defects=1} +package main + +import ( + "net" + + "golang.org/x/crypto/ssh" +) + +func main() {} + +func insecureIgnoreHostKey() { + _ = &ssh.ClientConfig{ + User: "username", + Auth: []ssh.AuthMethod{nil}, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + } +} + +func insecureHostKeyCallback() { + _ = &ssh.ClientConfig{ + User: "username", + Auth: []ssh.AuthMethod{nil}, + HostKeyCallback: ssh.HostKeyCallback( + func(hostname string, remote net.Addr, key ssh.PublicKey) error { + return nil + }), + } +} + +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-322/SecureHostKeyCallbackExample.go b/PR_9_golang/go_lang/go_codeql/CWE-322/SecureHostKeyCallbackExample.go new file mode 100644 index 0000000..1a29aa9 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-322/SecureHostKeyCallbackExample.go @@ -0,0 +1,22 @@ +// {fact rule=do-not-auto-add-or-warning-missing-hostkey-policy@v1.0 defects=0} +package main + +import ( + "io/ioutil" + + "golang.org/x/crypto/ssh" +) + + +func secureHostKeyCallback() { + publicKeyBytes, _ := ioutil.ReadFile("allowed_hostkey.pub") + publicKey, _ := ssh.ParsePublicKey(publicKeyBytes) + + _ = &ssh.ClientConfig{ + User: "username", + Auth: []ssh.AuthMethod{nil}, + HostKeyCallback: ssh.FixedHostKey(publicKey), + } +} + +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-326/InsufficientKeySize.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-326/InsufficientKeySize.qhelp new file mode 100644 index 0000000..6547993 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-326/InsufficientKeySize.qhelp @@ -0,0 +1,50 @@ + + + +

    + Incorrect uses of encryption algorithms may result in sensitive data exposure, + key leakage, broken authentication, insecure session, and spoofing attacks. +

    + +
    + + +

    + Ensure that you use a strong key with a recommended bit size. + For RSA encryption the minimum size is 2048 bits. +

    + +
    + + +

    + The following code uses RSA encryption with insufficient key size. +

    + + + +

    + In the example below, the key size is set to 2048 bits. +

    + + + +
    + + +
  • OWASP: Cryptographic Storage Cheat Sheet. +
  • +
  • Wikipedia: Cryptographically Strong Algorithms. +
  • +
  • Wikipedia: Strong Cryptography Examples. +
  • +
  • NIST, FIPS 140 Annex a: Approved Security Functions.
  • +
  • NIST, SP 800-131A: Transitions: Recommendation for Transitioning the Use of Cryptographic Algorithms and Key Lengths.
  • +
    + +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-326/InsufficientKeySize.ql b/PR_9_golang/go_lang/go_codeql/CWE-326/InsufficientKeySize.ql new file mode 100644 index 0000000..19db3ef --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-326/InsufficientKeySize.ql @@ -0,0 +1,56 @@ +/** + * @name Use of a weak cryptographic key + * @description Using a weak cryptographic key can allow an attacker to compromise security. + * @kind path-problem + * @problem.severity error + * @security-severity 7.5 + * @precision high + * @id go/weak-crypto-key + * @tags security + * external/cwe/cwe-326 + */ + +import go + +module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source.getIntValue() < 2048 } + + predicate isSink(DataFlow::Node sink) { + exists(DataFlow::CallNode c | + sink = c.getArgument(1) and + c.getTarget().hasQualifiedName("crypto/rsa", "GenerateKey") + ) + } + + predicate isBarrier(DataFlow::Node node) { + node = DataFlow::BarrierGuard::getABarrierNode() + } +} + +/** + * Tracks data flow from an RSA key length to a calls to an RSA key generation + * function. + */ +module Flow = DataFlow::Global; + +import Flow::PathGraph + +/** + * Holds if `g` is a comparison which guarantees that `e` is at least 2048 on `branch`, + * considered as a barrier guard for key sizes. + */ +predicate comparisonBarrierGuard(DataFlow::Node g, Expr e, boolean branch) { + exists(DataFlow::Node lesser, DataFlow::Node greater, int bias | + g.(DataFlow::RelationalComparisonNode).leq(branch, lesser, greater, bias) + | + // Force join order: find comparisons checking x >= 2048, then take the global value + // number of x. Otherwise this can be realised by starting from all pairs of matching value + // numbers, which can be huge. + pragma[only_bind_into](globalValueNumber(DataFlow::exprNode(e))) = globalValueNumber(greater) and + lesser.getIntValue() - bias >= 2048 + ) +} + +from Flow::PathNode source, Flow::PathNode sink +where Flow::flowPath(source, sink) +select sink, source, sink, "The size of this RSA key should be at least 2048 bits." diff --git a/PR_9_golang/go_lang/go_codeql/CWE-326/InsufficientKeySizeBad b/PR_9_golang/go_lang/go_codeql/CWE-326/InsufficientKeySizeBad new file mode 100644 index 0000000..5b79580 Binary files /dev/null and b/PR_9_golang/go_lang/go_codeql/CWE-326/InsufficientKeySizeBad differ diff --git a/PR_9_golang/go_lang/go_codeql/CWE-326/InsufficientKeySizeBad.go b/PR_9_golang/go_lang/go_codeql/CWE-326/InsufficientKeySizeBad.go new file mode 100644 index 0000000..d0ee1cd --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-326/InsufficientKeySizeBad.go @@ -0,0 +1,18 @@ +// {fact rule=cryptographic-key-generator@v1.0 defects=1} +package main + +import ( + "crypto/rand" + "crypto/rsa" + "fmt" +) + +func main() { + //Generate Private Key + pvk, err := rsa.GenerateKey(rand.Reader, 1024) + if err != nil { + fmt.Println(err) + } + fmt.Println(pvk) +} +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-326/InsufficientKeySizeGood.go b/PR_9_golang/go_lang/go_codeql/CWE-326/InsufficientKeySizeGood.go new file mode 100644 index 0000000..806b722 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-326/InsufficientKeySizeGood.go @@ -0,0 +1,18 @@ +// {fact rule=cryptographic-key-generator@v1.0 defects=0} +package main + +import ( + "crypto/rand" + "crypto/rsa" + "fmt" +) + +func main_1() { + //Generate Private Key + pvk, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + fmt.Println(err) + } + fmt.Println(pvk) +} +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-327/InsecureTLS b/PR_9_golang/go_lang/go_codeql/CWE-327/InsecureTLS new file mode 100644 index 0000000..2b6c671 Binary files /dev/null and b/PR_9_golang/go_lang/go_codeql/CWE-327/InsecureTLS differ diff --git a/PR_9_golang/go_lang/go_codeql/CWE-327/InsecureTLS.go b/PR_9_golang/go_lang/go_codeql/CWE-327/InsecureTLS.go new file mode 100644 index 0000000..f290d94 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-327/InsecureTLS.go @@ -0,0 +1,38 @@ +package main + +import ( + "crypto/tls" +) + +func main() {} + +func insecureMinMaxTlsVersion() { + // {fact rule=cryptographic-key-generator@v1.0 defects=1} + { + config := &tls.Config{} + config.MinVersion = 0 // BAD: Setting the MinVersion to 0 equals to choosing the lowest supported version (i.e. SSL3.0) + } + // {/fact} + // {fact rule=cryptographic-key-generator@v1.0 defects=1} + { + config := &tls.Config{} + config.MinVersion = tls.VersionSSL30 // BAD: SSL 3.0 is a non-secure version of the protocol; it's not safe to use it as MinVersion. + } + // {/fact} + // {fact rule=cryptographic-key-generator@v1.0 defects=1} + { + config := &tls.Config{} + config.MaxVersion = tls.VersionSSL30 // BAD: SSL 3.0 is a non-secure version of the protocol; it's not safe to use it as MaxVersion. + } + // {/fact} +} +// {fact rule=cryptographic-key-generator@v1.0 defects=1} +func insecureCipherSuites() { + config := &tls.Config{ + CipherSuites: []uint16{ + tls.TLS_RSA_WITH_RC4_128_SHA, // BAD: TLS_RSA_WITH_RC4_128_SHA is one of the non-secure cipher suites; it's not safe to be used. + }, + } + _ = config +} +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-327/InsecureTLS.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-327/InsecureTLS.qhelp new file mode 100644 index 0000000..43b95cc --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-327/InsecureTLS.qhelp @@ -0,0 +1,51 @@ + + + +

    + The TLS (Transport Layer Security) protocol secures communications over the Internet. + The protocol allows client/server applications to communicate in a way that is designed + to prevent eavesdropping, tampering, or message forgery. +

    +

    + The current latest version is 1.3 (with the 1.2 version still being considered secure). + Older versions are not deemed to be secure anymore because of various security vulnerabilities, + and tht makes them unfit for use in securing your applications. +

    +

    + Unfortunately, many applications and websites still support deprecated SSL/TLS versions and + cipher suites. +

    +
    + +

    + Only use secure TLS versions (1.3 and 1.2) and avoid using insecure cipher suites + (you can see a list here: https://golang.org/src/crypto/tls/cipher_suites.go#L81) +

    +
    + +

    + The following example shows a few ways how an insecure TLS configuration can be created: +

    + +

    + The following example shows how to create a safer TLS configuration: +

    + +
    + +
  • + Wikipedia: + Transport Layer Security +
  • +
  • + Mozilla: + Security/Server Side TLS +
  • +
  • + OWASP: + Transport Layer Protection Cheat Sheet +
  • +
    +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-327/InsecureTLS.ql b/PR_9_golang/go_lang/go_codeql/CWE-327/InsecureTLS.ql new file mode 100644 index 0000000..a4a9ab1 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-327/InsecureTLS.ql @@ -0,0 +1,286 @@ +/** + * @name Insecure TLS configuration + * @description If an application supports insecure TLS versions or ciphers, it may be vulnerable to + * machine-in-the-middle and other attacks. + * @kind path-problem + * @problem.severity warning + * @security-severity 7.5 + * @precision very-high + * @id go/insecure-tls + * @tags security + * external/cwe/cwe-327 + */ + +import go +import semmle.go.security.InsecureFeatureFlag::InsecureFeatureFlag + +/** + * Holds if it is insecure to assign TLS version `val` named `name` to `tls.Config` field `fieldName`. + */ +predicate isInsecureTlsVersion(int val, string name, string fieldName) { + (fieldName = "MinVersion" or fieldName = "MaxVersion") and + ( + // tls.VersionSSL30 + val = 768 and name = "VersionSSL30" + or + // tls.VersionTLS10 + val = 769 and name = "VersionTLS10" + or + // tls.VersionTLS11 + val = 770 and name = "VersionTLS11" + or + // Zero indicates the lowest available version setting for MinVersion, + // or the highest available version setting for MaxVersion. + val = 0 and name = "" and fieldName = "MinVersion" + ) +} + +/** + * Returns integers that may represent a secure TLS version. + */ +int getASecureTlsVersion() { + result in [771, 772] // TLS 1.2 and 1.3 respectively +} + +/** + * Returns integers that may represent a TLS version. + * + * Integer values corresponding to versions are defined at https://golang.org/pkg/crypto/tls/#pkg-constants + * Zero means the default version; at the time of writing, TLS 1.0. + */ +int getATlsVersion() { result = getASecureTlsVersion() or isInsecureTlsVersion(result, _, _) } + +module TlsVersionFlowConfig implements DataFlow::ConfigSig { + /** + * Holds if `source` is a TLS version source yielding value `val`. + */ + additional predicate intIsSource(DataFlow::Node source, int val) { + val = source.getIntValue() and + val = getATlsVersion() and + not DataFlow::isReturnedWithError(source) + } + + /** + * Holds if `fieldWrite` writes `sink` to `base`.`fld`, where `fld` is a TLS version field. + */ + additional predicate isSink(DataFlow::Node sink, Field fld, DataFlow::Node base, Write fieldWrite) { + fld.hasQualifiedName("crypto/tls", "Config", ["MinVersion", "MaxVersion"]) and + fieldWrite.writesField(base, fld, sink) + } + + predicate isSource(DataFlow::Node source) { intIsSource(source, _) } + + predicate isSink(DataFlow::Node sink) { isSink(sink, _, _, _) } +} + +/** + * Tracks taint flow from TLS versions to the `tls.Config.MinVersion` and + * `tls.Config.MaxVersion` fields. + */ +module TlsVersionFlow = TaintTracking::Global; + +/** + * Holds if `config` exhibits a secure TLS version flowing from `source` to `sink`, which flows into `fld`. + */ +predicate secureTlsVersionFlow(DataFlow::Node source, DataFlow::Node sink, Field fld) { + exists(int version | + TlsVersionFlow::flow(source, sink) and + TlsVersionFlowConfig::intIsSource(source, version) and + not isInsecureTlsVersion(version, _, fld.getName()) + ) +} + +/** + * Holds if a secure TLS version reaches `sink`, which flows into `fld`. + */ +predicate secureTlsVersionFlowsToSink(DataFlow::Node sink, Field fld) { + secureTlsVersionFlow(_, sink, fld) +} + +/** + * Holds if a secure TLS version may reach `accessPath`.`fld` + */ +predicate secureTlsVersionFlowsToField(SsaWithFields accessPath, Field fld) { + exists(DataFlow::Node sink, DataFlow::Node base | + secureTlsVersionFlow(_, sink, fld) and + TlsVersionFlowConfig::isSink(sink, fld, base, _) and + accessPath.getAUse() = base + ) +} + +/** + * Returns `node` or an implicit-deref node referring to it + */ +DataFlow::Node nodeOrDeref(DataFlow::Node node) { + result = node or + result.asInstruction() = IR::implicitDerefInstruction(node.asExpr()) +} + +/** + * Holds if an insecure TLS version flows from `source` to `sink`, which is in turn written + * to a field of `base`. `message` describes the specific problem found. + */ +predicate isInsecureTlsVersionFlow( + TlsVersionFlow::PathNode source, TlsVersionFlow::PathNode sink, string message, + DataFlow::Node base +) { + exists(int version, Field fld | + TlsVersionFlow::flowPath(source, sink) and + TlsVersionFlowConfig::intIsSource(source.getNode(), version) and + TlsVersionFlowConfig::isSink(sink.getNode(), fld, base, _) and + isInsecureTlsVersion(version, _, fld.getName()) and + // Exclude cases where a secure TLS version can also flow to the same + // sink, or to different sinks that refer to the same base and field, + // which suggests a configurable security mode. + not secureTlsVersionFlowsToSink(sink.getNode(), fld) and + not exists(SsaWithFields insecureAccessPath, SsaWithFields secureAccessPath | + nodeOrDeref(insecureAccessPath.getAUse()) = base and + secureAccessPath = insecureAccessPath.similar() + | + secureTlsVersionFlowsToField(secureAccessPath, fld) + ) + | + version = 0 and + message = "Using lowest TLS version for " + fld + "." + or + version != 0 and + exists(string name | isInsecureTlsVersion(version, name, _) | + message = "Using insecure TLS version " + name + " for " + fld + "." + ) + ) +} + +module TlsInsecureCipherSuitesFlowConfig implements DataFlow::ConfigSig { + /** + * Holds if `source` reads an insecure TLS cipher suite named `suiteName`. + */ + additional predicate isSourceValueEntity(DataFlow::Node source, string suiteName) { + exists(DataFlow::ValueEntity val | + val.hasQualifiedName("crypto/tls", suiteName) and + suiteName = + [ + "TLS_RSA_WITH_RC4_128_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", "TLS_ECDHE_RSA_WITH_RC4_128_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256" + ] + | + source = val.getARead() + ) + } + + /** + * Holds if `source` represents the result of `tls.InsecureCipherSuites()`. + */ + additional predicate isSourceInsecureCipherSuites(DataFlow::Node source) { + exists(Function insecureCipherSuites | + insecureCipherSuites.hasQualifiedName("crypto/tls", "InsecureCipherSuites") + | + source = insecureCipherSuites.getACall().getResult() + ) + } + + predicate isSource(DataFlow::Node source) { + isSourceInsecureCipherSuites(source) + or + isSourceValueEntity(source, _) + } + + /** + * Holds if `fieldWrite` writes `sink` to `base`.`fld`, and `fld` is `tls.Config.CipherSuites`. + */ + additional predicate isSink(DataFlow::Node sink, Field fld, DataFlow::Node base, Write fieldWrite) { + fld.hasQualifiedName("crypto/tls", "Config", "CipherSuites") and + fieldWrite.writesField(base, fld, sink) + } + + predicate isSink(DataFlow::Node sink) { isSink(sink, _, _, _) } + + /** + * Declare sinks as out-sanitizers in order to avoid producing superfluous paths where a cipher + * is written to CipherSuites, then the list is further extended with either safe or tainted + * suites. + */ + predicate isBarrierOut(DataFlow::Node node) { isSink(node) } +} + +/** + * Tracks taint flow from insecure TLS cipher suites into the `CipherSuites` + * field of a `tls.Config` struct. + */ +module TlsInsecureCipherSuitesFlow = TaintTracking::Global; + +/** + * Holds if an insecure TLS cipher suite flows from `source` to `sink`, where `sink` + * is written to the CipherSuites list of a `tls.Config` instance. `message` describes + * the exact problem found. + */ +predicate isInsecureTlsCipherFlow( + TlsInsecureCipherSuitesFlow::PathNode source, TlsInsecureCipherSuitesFlow::PathNode sink, + string message +) { + TlsInsecureCipherSuitesFlow::flowPath(source, sink) and + ( + exists(string name | + TlsInsecureCipherSuitesFlowConfig::isSourceValueEntity(source.getNode(), name) + | + message = "Use of an insecure cipher suite: " + name + "." + ) + or + TlsInsecureCipherSuitesFlowConfig::isSourceInsecureCipherSuites(source.getNode()) and + message = "Use of an insecure cipher suite." + ) +} + +/** + * A flag suggesting support for an old or legacy TLS version. + * + * We accept 'intermediate' because it appears to be common for TLS users + * to define three profiles: modern, intermediate, legacy/old, perhaps based + * on https://wiki.mozilla.org/Security/Server_Side_TLS (though note the + * 'intermediate' used there would now pass muster according to this query) + */ +class LegacyTlsVersionFlag extends FlagKind { + LegacyTlsVersionFlag() { this = "legacyTlsVersion" } + + bindingset[result] + override string getAFlagName() { result.regexpMatch("(?i).*(old|intermediate|legacy).*") } +} + +/** + * Gets a control-flow node that represents a (likely) flag controlling TLS version selection. + */ +ControlFlow::ConditionGuardNode getALegacyTlsVersionCheck() { + result.ensures(any(LegacyTlsVersionFlag f).getAFlag().getANode(), _) +} + +/** + * Returns flag kinds relevant to this query: a generic security feature flag, or one + * specifically controlling TLS version selection. + */ +FlagKind securityOrTlsVersionFlag() { + result = any(SecurityFeatureFlag f) or + result = any(LegacyTlsVersionFlag f) +} + +module Flow = + DataFlow::MergePathGraph; + +import Flow::PathGraph + +from Flow::PathNode source, Flow::PathNode sink, string message +where + ( + isInsecureTlsVersionFlow(source.asPathNode1(), sink.asPathNode1(), message, _) or + isInsecureTlsCipherFlow(source.asPathNode2(), sink.asPathNode2(), message) + ) and + // Exclude sources or sinks guarded by a feature or legacy flag + not [getASecurityFeatureFlagCheck(), getALegacyTlsVersionCheck()] + .dominatesNode([source, sink].getNode().asInstruction()) and + // Exclude sources or sinks that occur lexically within a block related to a feature or legacy flag + not astNodeIsFlag([source, sink].getNode().asExpr().getParent*(), securityOrTlsVersionFlag()) and + // Exclude results in functions whose name documents insecurity + not exists(FuncDef fn | fn = sink.getNode().getRoot().getEnclosingFunction*() | + fn.getName() = securityOrTlsVersionFlag().getAFlagName() + ) +select sink.getNode(), source, sink, message diff --git a/PR_9_golang/go_lang/go_codeql/CWE-327/SaferTLS.go b/PR_9_golang/go_lang/go_codeql/CWE-327/SaferTLS.go new file mode 100644 index 0000000..4e984fd --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-327/SaferTLS.go @@ -0,0 +1,13 @@ +// {fact rule=cryptographic-key-generator@v1.0 defects=0} +package main + +import "crypto/tls" + +func saferTLSConfig() { + config := &tls.Config{} + config.MinVersion = tls.VersionTLS12 + config.MaxVersion = tls.VersionTLS13 + // OR + config.MaxVersion = 0 // GOOD: Setting MaxVersion to 0 means that the highest version available in the package will be used. +} +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-338/InsecureRandomness b/PR_9_golang/go_lang/go_codeql/CWE-338/InsecureRandomness new file mode 100644 index 0000000..736485f Binary files /dev/null and b/PR_9_golang/go_lang/go_codeql/CWE-338/InsecureRandomness differ diff --git a/PR_9_golang/go_lang/go_codeql/CWE-338/InsecureRandomness.go b/PR_9_golang/go_lang/go_codeql/CWE-338/InsecureRandomness.go new file mode 100644 index 0000000..c55446e --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-338/InsecureRandomness.go @@ -0,0 +1,22 @@ +// {fact rule=weak-random-number-generation@v1.0 defects=1} +package main + +import ( + "math/rand" +) + +var charset = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + +func generatePassword() string { + s := make([]rune, 20) + for i := range s { + s[i] = charset[rand.Intn(len(charset))] + } + return string(s) +} + +// {/fact} + +func main() { + +} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-338/InsecureRandomness.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-338/InsecureRandomness.qhelp new file mode 100644 index 0000000..fb61773 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-338/InsecureRandomness.qhelp @@ -0,0 +1,49 @@ + + + +

    +Using a cryptographically weak pseudo-random number generator to generate a security-sensitive value, +such as a password, makes it easier for an attacker to predict the value. +

    +

    +Pseudo-random number generators generate a sequence of numbers that only approximates the properties +of random numbers. The sequence is not truly random because it is completely determined by a +relatively small set of initial values, the seed. If the random number generator is +cryptographically weak, then this sequence may be easily predictable through outside observations. +

    +
    + +

    +Use a cryptographically secure pseudo-random number generator if the output is to be used in a +security sensitive context. As a rule of thumb, a value should be considered "security sensitive" +if predicting it would allow the attacker to perform an action that they would otherwise be unable +to perform. For example, if an attacker could predict the random password generated for a new user, +they would be able to log in as that new user. +

    +

    +For Go, crypto/rand provides a cryptographically secure pseudo-random +number generator. math/rand is not cryptographically secure, and should be avoided in +security contexts. For contexts which are not security sensitive, math/rand may be +preferable as it has a more convenient interface, and is likely to be faster. +

    +
    + + +

    +The example below uses the math/rand package instead of crypto/rand to generate a password: +

    + +

    +Instead, use crypto/rand: +

    + +
    + + +
  • Wikipedia. Pseudo-random number generator.
  • +
  • OWASP: Insecure Randomness.
  • +
  • OWASP: Secure Random Number Generation.
  • +
    +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-338/InsecureRandomness.ql b/PR_9_golang/go_lang/go_codeql/CWE-338/InsecureRandomness.ql new file mode 100644 index 0000000..cc6bc42 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-338/InsecureRandomness.ql @@ -0,0 +1,35 @@ +/** + * @name Use of insufficient randomness as the key of a cryptographic algorithm + * @description Using insufficient randomness as the key of a cryptographic algorithm can allow an attacker to compromise security. + * @kind path-problem + * @problem.severity error + * @security-severity 7.8 + * @precision high + * @id go/insecure-randomness + * @tags security + * external/cwe/cwe-338 + */ + +import go +import semmle.go.security.InsecureRandomness +import InsecureRandomness::Flow::PathGraph + +from InsecureRandomness::Flow::PathNode source, InsecureRandomness::Flow::PathNode sink, string kind +where + InsecureRandomness::Flow::flowPath(source, sink) and + InsecureRandomness::isSinkWithKind(sink.getNode(), kind) and + ( + kind != "A password-related function" + or + sink = + min(InsecureRandomness::Flow::PathNode sink2, int line | + InsecureRandomness::Flow::flowPath(_, sink2) and + sink2.getNode().getRoot() = sink.getNode().getRoot() and + sink2.hasLocationInfo(_, line, _, _, _) + | + sink2 order by line + ) + ) +select sink.getNode(), source, sink, + kind + " depends on a $@ generated with a cryptographically weak RNG.", source.getNode(), + "random number" diff --git a/PR_9_golang/go_lang/go_codeql/CWE-338/InsecureRandomnessGood.go b/PR_9_golang/go_lang/go_codeql/CWE-338/InsecureRandomnessGood.go new file mode 100644 index 0000000..204284f --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-338/InsecureRandomnessGood.go @@ -0,0 +1,20 @@ +// {fact rule=weak-random-number-generation@v1.0 defects=0} +package main + +import ( + "crypto/rand" + "math/big" +) + +func generatePasswordGood() string { + s := make([]rune, 20) + for i := range s { + idx, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset)))) + if err != nil { + // handle err + } + s[i] = charset[idx.Int64()] + } + return string(s) +} +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-352/ConstantOauth2State.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-352/ConstantOauth2State.qhelp new file mode 100644 index 0000000..4400d60 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-352/ConstantOauth2State.qhelp @@ -0,0 +1,30 @@ + + + +

    + OAuth 2.0 clients must implement CSRF protection for the redirection URI, which is typically accomplished by including a "state" value that binds the request to + the user's authenticated state. The Go OAuth 2.0 library allows you to specify a "state" value which is then included in the auth code URL. That state is then provided back by the remote authentication server in the redirect callback, from where it must be validated. Failure to do so makes the client susceptible to an CSRF attack. +

    +
    + +

    + Always include a unique, non-guessable state value (provided to the call to AuthCodeURL function) that is also bound to the user's authenticated state with each authentication request, and then validated in the redirect callback. +

    +
    + +

    + The first example shows you the use of a constant state (bad). +

    + +

    + The second example shows a better implementation idea. +

    + +
    + +
  • IETF: The OAuth 2.0 Authorization Framework
  • +
  • IETF: OAuth 2.0 Security Best Current Practice
  • +
    +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-352/ConstantOauth2State.ql b/PR_9_golang/go_lang/go_codeql/CWE-352/ConstantOauth2State.ql new file mode 100644 index 0000000..c251c3e --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-352/ConstantOauth2State.ql @@ -0,0 +1,204 @@ +/** + * @name Use of constant `state` value in OAuth 2.0 URL + * @description Using a constant value for the `state` in the OAuth 2.0 URL makes the application + * susceptible to CSRF attacks. + * @kind path-problem + * @problem.severity error + * @security-severity 8.8 + * @precision high + * @id go/constant-oauth2-state + * @tags security + * external/cwe/cwe-352 + */ + +import go + +/** + * A method that creates a new URL that will send the user + * to the OAuth 2.0 authorization dialog of the provider. + */ +class AuthCodeUrl extends Method { + AuthCodeUrl() { + this.hasQualifiedName(package("golang.org/x/oauth2", ""), "Config", "AuthCodeURL") + } +} + +module ConstantStateFlowConfig implements DataFlow::ConfigSig { + additional predicate isSinkCall(DataFlow::Node sink, DataFlow::CallNode call) { + exists(AuthCodeUrl m | call = m.getACall() | sink = call.getArgument(0)) + } + + predicate isSource(DataFlow::Node source) { + source.isConst() and + not DataFlow::isReturnedWithError(source) and + // Avoid duplicate paths by not considering reads from constants as sources themselves: + ( + source.asExpr() instanceof StringLit + or + source.asExpr() instanceof AddExpr + ) + } + + predicate isSink(DataFlow::Node sink) { isSinkCall(sink, _) } +} + +/** + * Tracks data flow of a constant string value to a call to `AuthCodeURL` as + * the `state` parameter. + */ +module Flow = DataFlow::Global; + +import Flow::PathGraph + +/** + * Holds if `pred` writes a URL to the `RedirectURL` field of the `succ` `Config` object. + * + * This propagates flow from the RedirectURL field to the whole Config object. + */ +predicate isUrlTaintingConfigStep(DataFlow::Node pred, DataFlow::Node succ) { + exists(Write w, Field f | + f.hasQualifiedName(package("golang.org/x/oauth2", ""), "Config", "RedirectURL") + | + w.writesField(succ.(DataFlow::PostUpdateNode).getPreUpdateNode(), f, pred) + ) +} + +/** + * Gets a URL or pseudo-URL that suggests an out-of-band OAuth2 flow or use of a transient + * local listener to receive an OAuth2 redirect. + */ +bindingset[result] +string getAnOobOauth2Url() { + // The following are pseudo-URLs seen in the wild to indicate the authenticating site + // should display a code for the user to manually convey, rather than directing: + result in ["urn:ietf:wg:oauth:2.0:oob", "urn:ietf:wg:oauth:2.0:oob:auto", "oob", "code"] or + // Alternatively some non-web tools will create a temporary local webserver to handle the + // OAuth2 redirect: + result.matches("%://localhost%") or + result.matches("%://127.0.0.1%") +} + +module PrivateUrlFlowsToAuthCodeUrlCallConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + source.getStringValue() = getAnOobOauth2Url() and + // Avoid duplicate paths by excluding constant variable references from + // themselves being sources: + ( + source.asExpr() instanceof StringLit + or + source.asExpr() instanceof AddExpr + ) + } + + predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) { + // Propagate from a RedirectURL field to a whole Config + isUrlTaintingConfigStep(pred, succ) + or + // Propagate across deref and address-taking steps + TaintTracking::referenceStep(pred, succ) + or + // Propagate across Sprintf and similar calls + exists(DataFlow::CallNode cn | + cn.getACalleeIncludingExternals().asFunction() instanceof Fmt::AppenderOrSprinter + | + pred = cn.getASyntacticArgument() and succ = cn.getResult() + ) + } + + additional predicate isSinkCall(DataFlow::Node sink, DataFlow::CallNode call) { + exists(AuthCodeUrl m | call = m.getACall() | sink = call.getReceiver()) + } + + predicate isSink(DataFlow::Node sink) { isSinkCall(sink, _) } +} + +/** + * Tracks data flow from a URL indicating the OAuth redirect doesn't point to a publicly + * accessible address to the receiver of an `AuthCodeURL` call. + * + * Note we accept localhost and 127.0.0.1 on the assumption this is probably a transient + * listener; if it actually is a persistent server then that really is vulnerable to CSRF. + */ +module PrivateUrlFlowsToAuthCodeUrlCallFlow = + DataFlow::Global; + +/** + * Holds if a URL indicating the OAuth redirect doesn't point to a publicly + * accessible address, to the receiver of an `AuthCodeURL` call. + * + * Note we accept localhost and 127.0.0.1 on the assumption this is probably a transient + * listener; if it actually is a persistent server then that really is vulnerable to CSRF. + */ +predicate privateUrlFlowsToAuthCodeUrlCall(DataFlow::CallNode call) { + exists(DataFlow::Node receiver | + PrivateUrlFlowsToAuthCodeUrlCallFlow::flowTo(receiver) and + PrivateUrlFlowsToAuthCodeUrlCallConfig::isSinkCall(receiver, call) + ) +} + +module FlowToPrintConfig implements DataFlow::ConfigSig { + additional predicate isSinkCall(DataFlow::Node sink, DataFlow::CallNode call) { + exists(LoggerCall logCall | call = logCall | sink = logCall.getAMessageComponent()) + } + + predicate isSource(DataFlow::Node source) { source = any(AuthCodeUrl m).getACall().getResult() } + + predicate isSink(DataFlow::Node sink) { isSinkCall(sink, _) } +} + +module FlowToPrintFlow = DataFlow::Global; + +/** Holds if the provided `CallNode`'s result flows to an argument of a printer call. */ +predicate resultFlowsToPrinter(DataFlow::CallNode authCodeUrlCall) { + FlowToPrintFlow::flow(authCodeUrlCall.getResult(), _) +} + +/** Get a data-flow node that reads the value of `os.Stdin`. */ +DataFlow::Node getAStdinNode() { + exists(ValueEntity v | + v.hasQualifiedName("os", "Stdin") and result = globalValueNumber(v.getARead()).getANode() + ) +} + +/** + * Gets a call to a scanner function that reads from `os.Stdin`, or which creates a scanner + * instance wrapping `os.Stdin`. + */ +DataFlow::CallNode getAScannerCall() { + result = any(Fmt::Scanner f).getACall() + or + exists(Fmt::FScanner f | + result = f.getACall() and f.getReader().getNode(result) = getAStdinNode() + ) + or + exists(Bufio::NewScanner f | + result = f.getACall() and f.getReader().getNode(result) = getAStdinNode() + ) +} + +/** + * Holds if the provided `CallNode` is within the same root as a call + * to a scanner that reads from `os.Stdin`. + */ +predicate containsCallToStdinScanner(FuncDef funcDef) { getAScannerCall().getRoot() = funcDef } + +/** + * Holds if the `authCodeURLCall` seems to be done within a terminal + * because there are calls to a printer (`fmt.Println` and similar), + * and a call to a scanner (`fmt.Scan` and similar), + * all of which are typically done within a terminal session. + */ +predicate seemsLikeDoneWithinATerminal(DataFlow::CallNode authCodeUrlCall) { + resultFlowsToPrinter(authCodeUrlCall) and + containsCallToStdinScanner(authCodeUrlCall.getRoot()) +} + +from Flow::PathNode source, Flow::PathNode sink, DataFlow::CallNode sinkCall +where + Flow::flowPath(source, sink) and + ConstantStateFlowConfig::isSinkCall(sink.getNode(), sinkCall) and + // Exclude cases that seem to be oauth flows done from within a terminal: + not seemsLikeDoneWithinATerminal(sinkCall) and + not privateUrlFlowsToAuthCodeUrlCall(sinkCall) +select sink.getNode(), source, sink, "Using a constant $@ to create oauth2 URLs.", source.getNode(), + "state string" diff --git a/PR_9_golang/go_lang/go_codeql/CWE-352/ConstantOauth2StateBad b/PR_9_golang/go_lang/go_codeql/CWE-352/ConstantOauth2StateBad new file mode 100644 index 0000000..5da01c6 Binary files /dev/null and b/PR_9_golang/go_lang/go_codeql/CWE-352/ConstantOauth2StateBad differ diff --git a/PR_9_golang/go_lang/go_codeql/CWE-352/ConstantOauth2StateBad.go b/PR_9_golang/go_lang/go_codeql/CWE-352/ConstantOauth2StateBad.go new file mode 100644 index 0000000..c916470 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-352/ConstantOauth2StateBad.go @@ -0,0 +1,27 @@ +// {fact rule=coral-csrf-rule@v1.0 defects=1} +package main + +import ( + "golang.org/x/oauth2" +) + +func main() {} + +var stateStringVar = "state" + +func badWithStringLiteralState() { + conf := &oauth2.Config{ + ClientID: "YOUR_CLIENT_ID", + ClientSecret: "YOUR_CLIENT_SECRET", + Scopes: []string{"SCOPE1", "SCOPE2"}, + Endpoint: oauth2.Endpoint{ + AuthURL: "https://provider.com/o/oauth2/auth", + TokenURL: "https://provider.com/o/oauth2/token", + }, + } + + url := conf.AuthCodeURL(stateStringVar) + _ = url + // ... +} +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-352/ConstantOauth2StateBetter.go b/PR_9_golang/go_lang/go_codeql/CWE-352/ConstantOauth2StateBetter.go new file mode 100644 index 0000000..3975048 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-352/ConstantOauth2StateBetter.go @@ -0,0 +1,38 @@ +// {fact rule=coral-csrf-rule@v1.0 defects=0} +package main + +import ( + "crypto/rand" + "encoding/base64" + "net/http" + + "golang.org/x/oauth2" +) + +func betterWithVariableStateReturned(w http.ResponseWriter) { + conf := &oauth2.Config{ + ClientID: "YOUR_CLIENT_ID", + ClientSecret: "YOUR_CLIENT_SECRET", + Scopes: []string{"SCOPE1", "SCOPE2"}, + Endpoint: oauth2.Endpoint{ + AuthURL: "https://provider.com/o/oauth2/auth", + TokenURL: "https://provider.com/o/oauth2/token", + }, + } + + state := generateStateOauthCookie(w) + url := conf.AuthCodeURL(state) + _ = url + // ... +} +func generateStateOauthCookie(w http.ResponseWriter) string { + b := make([]byte, 128) + rand.Read(b) + // TODO: save the state string to cookies or HTML storage, + // and bind it to the authenticated status of the user. + state := base64.URLEncoding.EncodeToString(b) + + return state +} + +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-601/BadRedirectCheck b/PR_9_golang/go_lang/go_codeql/CWE-601/BadRedirectCheck new file mode 100644 index 0000000..0ead35b Binary files /dev/null and b/PR_9_golang/go_lang/go_codeql/CWE-601/BadRedirectCheck differ diff --git a/PR_9_golang/go_lang/go_codeql/CWE-601/BadRedirectCheck.go b/PR_9_golang/go_lang/go_codeql/CWE-601/BadRedirectCheck.go new file mode 100644 index 0000000..6332979 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-601/BadRedirectCheck.go @@ -0,0 +1,10 @@ +// {fact rule=cross-site-scripting@v1.0 defects=1} +package main + +func sanitizeUrl(redir string) string { + if len(redir) > 0 && redir[0] == '/' { + return redir + } + return "/" +} +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-601/BadRedirectCheck.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-601/BadRedirectCheck.qhelp new file mode 100644 index 0000000..a592abf --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-601/BadRedirectCheck.qhelp @@ -0,0 +1,40 @@ + + + + +

    +Redirect URLs should be checked to ensure that user input cannot cause a site to redirect +to arbitrary domains. This is often done with a check that the redirect URL begins with a slash, +which most of the time is an absolute redirect on the same host. However, browsers interpret URLs +beginning with // or /\ as absolute URLs. For example, a redirect to +//example.com will redirect to https://example.com. Thus, redirect checks must +also check the second character of redirect URLs. +

    +
    + + +

    +Also disallow redirect URLs starting with // or /\. +

    +
    + + +

    +The following function validates a (presumably untrusted) redirect URL redir. If it +does not begin with /, the harmless placeholder redirect URL / is +returned to prevent an open redirect; otherwise redir itself is returned. +

    + +

    +While this check provides partial protection, it should be extended to cover // and +/\ as well: +

    + +
    + + +
  • OWASP: + XSS Unvalidated Redirects and Forwards Cheat Sheet.
  • +
    + +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-601/BadRedirectCheck.ql b/PR_9_golang/go_lang/go_codeql/CWE-601/BadRedirectCheck.ql new file mode 100644 index 0000000..bc60e13 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-601/BadRedirectCheck.ql @@ -0,0 +1,178 @@ +/** + * @name Bad redirect check + * @description A redirect check that checks for a leading slash but not two + * leading slashes or a leading slash followed by a backslash is + * incomplete. + * @kind path-problem + * @problem.severity error + * @security-severity 6.1 + * @id go/bad-redirect-check + * @tags security + * external/cwe/cwe-601 + * @precision high + */ + +import go +import semmle.go.security.OpenUrlRedirectCustomizations + +StringOps::HasPrefix checkForLeadingSlash(SsaWithFields v) { + exists(DataFlow::Node substr | + result.getBaseString() = v.getAUse() and result.getSubstring() = substr + | + substr.getStringValue() = "/" + ) +} + +predicate isCheckedForSecondSlash(SsaWithFields v) { + exists(StringOps::HasPrefix hp | hp.getBaseString() = v.getAUse() | + hp.getSubstring().getStringValue() = "//" + ) + or + exists(DataFlow::EqualityTestNode eq, DataFlow::Node slash, DataFlow::ElementReadNode er | + slash.getStringValue() = "/" and + er.getBase() = v.getAUse() and + er.getIndex().getIntValue() = 1 and + eq.eq(_, er, slash) + ) + or + // a call to path.Clean will strip away multiple leading slashes + isCleaned(v.getAUse()) +} + +/** + * Holds if `nd` is the result of a call to `path.Clean`, or flows into the first argument + * of such a call, possibly inter-procedurally. + */ +predicate isCleaned(DataFlow::Node nd) { + exists(Function clean | clean.hasQualifiedName("path", "Clean") | + nd = clean.getACall() + or + nd = clean.getACall().getArgument(0) + ) + or + isCleaned(nd.getAPredecessor()) + or + exists(FuncDef f, FunctionInput inp | nd = inp.getExitNode(f) | + forex(DataFlow::CallNode call | call.getACallee() = f | isCleaned(inp.getEntryNode(call))) + ) +} + +predicate isCheckedForSecondBackslash(SsaWithFields v) { + exists(StringOps::HasPrefix hp | hp.getBaseString() = v.getAUse() | + hp.getSubstring().getStringValue() = "/\\" + ) + or + exists(DataFlow::EqualityTestNode eq, DataFlow::Node slash, DataFlow::ElementReadNode er | + slash.getStringValue() = "\\" and + er.getBase() = v.getAUse() and + er.getIndex().getIntValue() = 1 and + eq.eq(_, er, slash) + ) + or + // if this variable comes from or is a net/url.URL.Path, backslashes are most likely sanitized, + // as the parse functions turn them into "%5C" + urlPath(v.getAUse()) +} + +/** + * Holds if `nd` derives its value from the field `url.URL.Path`, possibly inter-procedurally. + */ +predicate urlPath(DataFlow::Node nd) { + exists(Field f | + f.hasQualifiedName("net/url", "URL", "Path") and + nd = f.getARead() + ) + or + urlPath(nd.getAPredecessor()) + or + exists(FuncDef f, FunctionInput inp | nd = inp.getExitNode(f) | + forex(DataFlow::CallNode call | call.getACallee() = f | urlPath(inp.getEntryNode(call))) + ) +} + +module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { isCheckedSource(source, _) } + + /** + * Holds if `source` is the first node that flows into a use of a variable that is checked by a + * bad redirect check `check`.. + */ + additional predicate isCheckedSource(DataFlow::Node source, DataFlow::Node check) { + exists(SsaWithFields v | + DataFlow::localFlow(source, v.getAUse()) and + not exists(source.getAPredecessor()) and + isBadRedirectCheckOrWrapper(check, v) + ) + } + + predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + // this is very over-approximate, because most filtering is done by the isSource predicate + exists(Write w | w.writesField(node2, _, node1)) + } + + predicate isBarrierOut(DataFlow::Node node) { + // assume this value is safe if something is prepended to it. + exists(StringOps::Concatenation conc, int i, int j | i < j | + node = conc.getOperand(j) and + exists(conc.getOperand(i)) + ) + or + exists(DataFlow::CallNode call, int i | call.getTarget().hasQualifiedName("path", "Join") | + i > 0 and node = call.getSyntacticArgument(i) + ) + } + + predicate isSink(DataFlow::Node sink) { sink instanceof OpenUrlRedirect::Sink } +} + +module Flow = TaintTracking::Global; + +/** + * Holds there is a check `check` that is a bad redirect check, and `v` is either + * checked directly by `check` or checked by a function that contains `check`. + */ +predicate isBadRedirectCheckOrWrapper(DataFlow::Node check, SsaWithFields v) { + isBadRedirectCheck(check, v) + or + exists(DataFlow::CallNode call, FuncDef f, FunctionInput input | + call = f.getACall() and + input.getEntryNode(call) = v.getAUse() and + isBadRedirectCheckWrapper(check, f, input) + ) +} + +/** + * Holds if `check` checks that `v` has a leading slash, but not whether it has another slash or a + * backslash in its second position. + */ +predicate isBadRedirectCheck(DataFlow::Node check, SsaWithFields v) { + // a check for a leading slash + check = checkForLeadingSlash(v) and + // where there does not exist a check for both a second slash and a second backslash + // (we allow those checks to be on variables that are most likely equivalent to `v` + // to rule out false positives due to minor variations in data flow) + not ( + isCheckedForSecondSlash(v.similar()) and + isCheckedForSecondBackslash(v.similar()) + ) +} + +/** + * Holds if `f` contains a bad redirect check `check`, that checks the parameter `input`. + */ +predicate isBadRedirectCheckWrapper(DataFlow::Node check, FuncDef f, FunctionInput input) { + exists(SsaWithFields v | + v.getAUse().getAPredecessor*() = input.getExitNode(f) and + isBadRedirectCheck(check, v) + ) +} + +import Flow::PathGraph + +from Flow::PathNode source, Flow::PathNode sink, DataFlow::Node check +where + Config::isCheckedSource(source.getNode(), check) and + Flow::flowPath(source, sink) +select check, source, sink, + "This is a check that $@, which flows into a $@, has a leading slash, but not that it does not have '/' or '\\' in its second position.", + source.getNode(), "this value", sink.getNode(), "redirect" diff --git a/PR_9_golang/go_lang/go_codeql/CWE-601/BadRedirectCheckGood.go b/PR_9_golang/go_lang/go_codeql/CWE-601/BadRedirectCheckGood.go new file mode 100644 index 0000000..eba9d5b --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-601/BadRedirectCheckGood.go @@ -0,0 +1,10 @@ +// {fact rule=cross-site-scripting@v1.0 defects=1} +package main + +func sanitizeUrl1(redir string) string { + if len(redir) > 1 && redir[0] == '/' && redir[1] != '/' && redir[1] != '\\' { + return redir + } + return "/" +} +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-601/OpenUrlRedirect.go b/PR_9_golang/go_lang/go_codeql/CWE-601/OpenUrlRedirect.go new file mode 100644 index 0000000..782ce37 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-601/OpenUrlRedirect.go @@ -0,0 +1,15 @@ +// {fact rule=cross-site-scripting@v1.0 defects=1} +package main + +import ( + "net/http" +) + +func serve() { + http.HandleFunc("/redir", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + http.Redirect(w, r, r.Form.Get("target"), 302) + }) +} + +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-601/OpenUrlRedirect.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-601/OpenUrlRedirect.qhelp new file mode 100644 index 0000000..11916c4 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-601/OpenUrlRedirect.qhelp @@ -0,0 +1,42 @@ + + + + +

    +Directly incorporating user input into a URL redirect request without validating the input can +facilitate phishing attacks. In these attacks, unsuspecting users can be redirected to a malicious +site that looks very similar to the real site they intend to visit, but is controlled by the +attacker. +

    +
    + + +

    +To guard against untrusted URL redirection, it is advisable to avoid putting user input directly into +a redirect URL. Instead, maintain a list of authorized redirects on the server; then choose from +that list based on the user input provided. +

    +
    + + +

    +The following example shows an HTTP request parameter being used directly in a URL redirect without +validating the input, which facilitates phishing attacks: +

    + + + +

    +One way to remedy the problem is to validate the user input against a known fixed string +before doing the redirection: +

    + + +
    + + +
  • OWASP: + XSS Unvalidated Redirects and Forwards Cheat Sheet.
  • +
    + +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-601/OpenUrlRedirect.ql b/PR_9_golang/go_lang/go_codeql/CWE-601/OpenUrlRedirect.ql new file mode 100644 index 0000000..17ec112 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-601/OpenUrlRedirect.ql @@ -0,0 +1,26 @@ +/** + * @name Open URL redirect + * @description Open URL redirection based on unvalidated user input + * may cause redirection to malicious web sites. + * @kind path-problem + * @problem.severity warning + * @security-severity 6.1 + * @id go/unvalidated-url-redirection + * @tags security + * external/cwe/cwe-601 + * @precision high + */ + +import go +import semmle.go.security.OpenUrlRedirect +import semmle.go.security.SafeUrlFlow +import OpenUrlRedirect::Flow::PathGraph + +from OpenUrlRedirect::Flow::PathNode source, OpenUrlRedirect::Flow::PathNode sink +where + OpenUrlRedirect::Flow::flowPath(source, sink) and + // this excludes flow from safe parts of request URLs, for example the full URL when the + // doing a redirect from `http://` to `https://` + not SafeUrlFlow::Flow::flow(_, sink.getNode()) +select sink.getNode(), source, sink, "This path to an untrusted URL redirection depends on a $@.", + source.getNode(), "user-provided value" diff --git a/PR_9_golang/go_lang/go_codeql/CWE-601/OpenUrlRedirectGood.go b/PR_9_golang/go_lang/go_codeql/CWE-601/OpenUrlRedirectGood.go new file mode 100644 index 0000000..4c45ec5 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-601/OpenUrlRedirectGood.go @@ -0,0 +1,29 @@ +// {fact rule=cross-site-scripting@v1.0 defects=0} +package main + +import ( + "net/http" + "net/url" +) + +func serve_1() { + http.HandleFunc("/redir", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + target, err := url.Parse(r.Form.Get("target")) + if err != nil { + // ... + } + + if target.Hostname() == "semmle.com" { + // GOOD: checking hostname + http.Redirect(w, r, target.String(), 302) + } else { + http.ResponseWriter.WriteHeader(w, 400) + } + }) +} +// {/fact} + +func main() { + +} \ No newline at end of file diff --git a/PR_9_golang/go_lang/go_codeql/CWE-640/EmailBad b/PR_9_golang/go_lang/go_codeql/CWE-640/EmailBad new file mode 100644 index 0000000..3c0366c Binary files /dev/null and b/PR_9_golang/go_lang/go_codeql/CWE-640/EmailBad differ diff --git a/PR_9_golang/go_lang/go_codeql/CWE-640/EmailBad.go b/PR_9_golang/go_lang/go_codeql/CWE-640/EmailBad.go new file mode 100644 index 0000000..45fe239 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-640/EmailBad.go @@ -0,0 +1,32 @@ +// {fact rule=weak-password-recovery-mechanism@v1.0 defects=1} +package main + +import ( + "net/http" + "net/smtp" +) + +// Define the backend structure with the necessary method +type Backend struct{} + +func (b *Backend) getUserSecretResetToken(email string) string { + // Stub implementation for generating a reset token + return "reset-token" +} + +var backend = &Backend{} + +// Define the email variable (for this example, we use a hardcoded email) +var email = "user@example.com" + +// Define the config for SMTP settings +var smtpServer = "smtp.example.com:25" // Replace with your SMTP server address +var fromEmail = "from@example.com" + +func mail(w http.ResponseWriter, r *http.Request) { + host := r.Header.Get("Host") + token := backend.getUserSecretResetToken(email) + body := "Click to reset password: " + host + "/" + token + smtp.SendMail("test.test", nil, "from@from.com", nil, []byte(body)) +} +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-640/EmailGood.go b/PR_9_golang/go_lang/go_codeql/CWE-640/EmailGood.go new file mode 100644 index 0000000..ac2f5a2 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-640/EmailGood.go @@ -0,0 +1,33 @@ +// {fact rule=weak-password-recovery-mechanism@v1.0 defects=0} +package main + +import ( + "net/http" + "net/smtp" +) + +// Config struct with a method to get configuration values +type Config struct{} +var config = &Config{} + +func (c *Config) Get(key string) string { + // Stub implementation for retrieving configuration values + switch key { + case "Host": + return "https://example.com" + default: + return "" + } +} + +func mailGood(w http.ResponseWriter, r *http.Request) { + host := config.Get("Host") + token := backend.getUserSecretResetToken(email) + body := "Click to reset password: " + host + "/" + token + smtp.SendMail("test.test", nil, "from@from.com", nil, []byte(body)) +} +// {/fact} + +func main() { + +} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-640/EmailInjection.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-640/EmailInjection.qhelp new file mode 100644 index 0000000..1da839c --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-640/EmailInjection.qhelp @@ -0,0 +1,46 @@ + + + +

    + Using untrusted input to construct an email can cause multiple security + vulnerabilities. For instance, inclusion of an untrusted input in an email body + may allow an attacker to conduct cross-site scripting (XSS) attacks, while + inclusion of an HTTP header may allow a full account compromise as shown in the + example below. +

    +
    + +

    + Any data which is passed to an email subject or body must be sanitized before use. +

    +
    + +

    + In the following example snippet, the host field is user controlled. +

    +

    + A malicious user can send an HTTP request to the targeted website, + but with a Host header that refers to their own website. This means the + emails will be sent out to potential victims, originating from a server + they trust, but with links leading to a malicious website. +

    +

    + If the email contains a password reset link, and the victim clicks + the link, the secret reset token will be leaked to the attacker. Using the + leaked token, the attacker can then construct the real reset link and use it to + change the victim's password. +

    + +

    + One way to prevent this is to load the host name from a trusted configuration file instead. +

    + +
    + +
  • + OWASP: + Content Spoofing + . +
  • +
    +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-640/EmailInjection.ql b/PR_9_golang/go_lang/go_codeql/CWE-640/EmailInjection.ql new file mode 100644 index 0000000..c3f279a --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-640/EmailInjection.ql @@ -0,0 +1,21 @@ +/** + * @name Email content injection + * @description Incorporating untrusted input directly into an email message can enable + * content spoofing, which in turn may lead to information leaks and other + * security issues. + * @id go/email-injection + * @kind path-problem + * @problem.severity error + * @security-severity 9.8 + * @tags security + * external/cwe/cwe-640 + * @precision high + */ + +import go +import EmailInjection::EmailInjection +import Flow::PathGraph + +from Flow::PathNode source, Flow::PathNode sink +where Flow::flowPath(source, sink) +select sink, source, sink, "Email content may contain $@.", source.getNode(), "untrusted input" diff --git a/PR_9_golang/go_lang/go_codeql/CWE-640/EmailInjection.qll b/PR_9_golang/go_lang/go_codeql/CWE-640/EmailInjection.qll new file mode 100644 index 0000000..479fe6d --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-640/EmailInjection.qll @@ -0,0 +1,40 @@ +/** + * Provides a taint-tracking configuration for reasoning about + * server-side email-injection vulnerabilities. + * + * Note, for performance reasons: only import this file if + * `EmailInjection::Configuration` is needed, otherwise + * `EmailInjectionCustomizations` should be imported instead. + */ + +import go + +/** + * Provides a taint-tracking configuration for reasoning about + * email-injection vulnerabilities. + */ +module EmailInjection { + import EmailInjectionCustomizations::EmailInjection + + /** + * DEPRECATED: Use `Flow` instead. + * + * A taint-tracking configuration for reasoning about email-injection vulnerabilities. + */ + deprecated class Configuration extends TaintTracking::Configuration { + Configuration() { this = "Email Injection" } + + override predicate isSource(DataFlow::Node source) { source instanceof Source } + + override predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + } + + private module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof Source } + + predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + } + + /** Tracks taint flow for reasoning about email-injection vulnerabilities. */ + module Flow = TaintTracking::Global; +} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-640/EmailInjectionCustomizations.qll b/PR_9_golang/go_lang/go_codeql/CWE-640/EmailInjectionCustomizations.qll new file mode 100644 index 0000000..1e0d4ee --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-640/EmailInjectionCustomizations.qll @@ -0,0 +1,26 @@ +/** Provides classes for reasoning about email-injection vulnerabilities. */ + +import go + +/** + * Provides a library for reasoning about email-injection vulnerabilities. + */ +module EmailInjection { + /** + * A data-flow node that should be considered a source of untrusted data for email-injection vulnerabilities. + */ + abstract class Source extends DataFlow::Node { } + + /** + * A data-flow node that should be considered a sink for email-injection vulnerabilities. + */ + abstract class Sink extends DataFlow::Node { } + + /** A source of untrusted data, considered as a taint source for email injection. */ + class UntrustedFlowSourceAsSource extends Source instanceof UntrustedFlowSource { } + + /** + * A data-flow node that becomes part of an email considered as a taint sink for email injection. + */ + class MailDataAsSink extends Sink instanceof EmailData { } +} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-643/XPathInjection b/PR_9_golang/go_lang/go_codeql/CWE-643/XPathInjection new file mode 100644 index 0000000..8a7658e Binary files /dev/null and b/PR_9_golang/go_lang/go_codeql/CWE-643/XPathInjection differ diff --git a/PR_9_golang/go_lang/go_codeql/CWE-643/XPathInjection.go b/PR_9_golang/go_lang/go_codeql/CWE-643/XPathInjection.go new file mode 100644 index 0000000..6e9de0a --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-643/XPathInjection.go @@ -0,0 +1,32 @@ +// {fact rule=xpath-injection@v1.0 defects=0} +package main + +import ( + "fmt" + "net/http" + + "github.com/ChrisTrenkamp/goxpath" + "github.com/ChrisTrenkamp/goxpath/tree" +) + +func main() {} + +func processRequest(r *http.Request, doc tree.Node) { + r.ParseForm() + username := r.Form.Get("username") + + // BAD: User input used directly in an XPath expression + xPath := goxpath.MustParse("//users/user[login/text()='" + username + "']/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) + } + // GOOD: Uses parameters to avoid including user input directly in XPath expression + xPath = goxpath.MustParse("//users/user[login/text()=$username]/home_dir/text()") + safeRes, _ := xPath.ExecBool(doc, opt) + fmt.Println(safeRes) +} +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-643/XPathInjection.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-643/XPathInjection.qhelp new file mode 100644 index 0000000..15fdfd7 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-643/XPathInjection.qhelp @@ -0,0 +1,44 @@ + + + +

    +If an XPath expression is built using string concatenation, and the components of the concatenation +include user input, a user is likely to be able to create a malicious XPath expression. +

    +
    + + +

    +If user input must be included in an XPath expression, pre-compile the query and use variable +references to include the user input. +

    +

    +For example, when using the github.com/ChrisTrenkamp/goxpath API, you can do this by creating a function that takes an *goxpath.Opts structure. +In this structure you can then set the values of the variable references. +This function can then be specified when calling Exec(), Exec{Bool|Num|Node}(), ParseExec(), or MustExec(). +

    + +
    + + +

    +In the first example, the code accepts a username specified by the user, and uses this +unvalidated and unsanitized value in an XPath expression. This is vulnerable to the user providing +special characters or string sequences that change the meaning of the XPath expression to search +for different values. +

    + +

    +In the second example, the XPath expression is a hard-coded string that specifies some variables, +which are safely resolved at runtime using the goxpath.Opts structure. +

    + +
    + + +
  • OWASP: Testing for XPath Injection.
  • +
  • OWASP: XPath Injection.
  • +
    +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-643/XPathInjection.ql b/PR_9_golang/go_lang/go_codeql/CWE-643/XPathInjection.ql new file mode 100644 index 0000000..5dbaad1 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-643/XPathInjection.ql @@ -0,0 +1,28 @@ +/** + * @name XPath injection + * @description Building an XPath expression from user-controlled sources is vulnerable to insertion of + * malicious code by the user. + * @kind path-problem + * @problem.severity error + * @security-severity 9.8 + * @precision high + * @id go/xml/xpath-injection + * @tags security + * external/cwe/cwe-643 + */ + +import go +import semmle.go.security.XPathInjection +import XPathInjection::Flow::PathGraph + +/** Holds if `node` is either a string or a byte slice */ +predicate isStringOrByte(XPathInjection::Flow::PathNode node) { + exists(Type t | t = node.getNode().getType().getUnderlyingType() | + t instanceof StringType or t instanceof ByteSliceType + ) +} + +from XPathInjection::Flow::PathNode source, XPathInjection::Flow::PathNode sink +where XPathInjection::Flow::flowPath(source, sink) and isStringOrByte(sink) +select sink.getNode(), source, sink, "XPath expression depends on a $@.", source.getNode(), + "user-provided value" diff --git a/PR_9_golang/go_lang/go_codeql/CWE-681/IncorrectIntegerConversion b/PR_9_golang/go_lang/go_codeql/CWE-681/IncorrectIntegerConversion new file mode 100644 index 0000000..7af727b Binary files /dev/null and b/PR_9_golang/go_lang/go_codeql/CWE-681/IncorrectIntegerConversion differ diff --git a/PR_9_golang/go_lang/go_codeql/CWE-681/IncorrectIntegerConversion.go b/PR_9_golang/go_lang/go_codeql/CWE-681/IncorrectIntegerConversion.go new file mode 100644 index 0000000..db63b53 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-681/IncorrectIntegerConversion.go @@ -0,0 +1,23 @@ +// {fact rule=incorrect-conversion-of-integers@v1.0 defects=1} +package main + +import ( + "strconv" +) + +func parseAllocateBad1(wanted string) int32 { + parsed, err := strconv.Atoi(wanted) + if err != nil { + panic(err) + } + return int32(parsed) +} +func parseAllocateBad2(wanted string) int32 { + parsed, err := strconv.ParseInt(wanted, 10, 64) + if err != nil { + panic(err) + } + return int32(parsed) +} + +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-681/IncorrectIntegerConversionGood.go b/PR_9_golang/go_lang/go_codeql/CWE-681/IncorrectIntegerConversionGood.go new file mode 100644 index 0000000..1af145a --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-681/IncorrectIntegerConversionGood.go @@ -0,0 +1,54 @@ +// {fact rule=incorrect-conversion-of-integers@v1.0 defects=0} +package main + +import ( + "math" + "strconv" +) + +func main() { + +} + +const DefaultAllocate int32 = 256 + +func parseAllocateGood1(desired string) int32 { + parsed, err := strconv.Atoi(desired) + if err != nil { + return DefaultAllocate + } + // GOOD: check for lower and upper bounds + if parsed > 0 && parsed <= math.MaxInt32 { + return int32(parsed) + } + return DefaultAllocate +} +func parseAllocateGood2(desired string) int32 { + // GOOD: parse specifying the bit size + parsed, err := strconv.ParseInt(desired, 10, 32) + if err != nil { + return DefaultAllocate + } + return int32(parsed) +} + +func parseAllocateGood3(wanted string) int32 { + parsed, err := strconv.ParseInt(wanted, 10, 32) + if err != nil { + panic(err) + } + return int32(parsed) +} +func parseAllocateGood4(wanted string) int32 { + parsed, err := strconv.ParseInt(wanted, 10, 64) + if err != nil { + panic(err) + } + // GOOD: check for lower and uppper bounds + if parsed > 0 && parsed <= math.MaxInt32 { + return int32(parsed) + } + return DefaultAllocate +} + +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-681/IncorrectIntegerConversionQuery.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-681/IncorrectIntegerConversionQuery.qhelp new file mode 100644 index 0000000..41e3384 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-681/IncorrectIntegerConversionQuery.qhelp @@ -0,0 +1,74 @@ + + + +

    +If a string is parsed into an int using strconv.Atoi, and subsequently that int +is converted into another integer type of a smaller size, the result can produce unexpected values. +

    +

    +This also applies to the results of strconv.ParseInt and strconv.ParseUint when +the specified size is larger than the size of the type that number is converted to. +

    +
    + + +

    +If you need to parse integer values with specific bit sizes, avoid strconv.Atoi, and instead +use strconv.ParseInt or strconv.ParseUint, which also allow specifying the +bit size. +

    +

    +When using those functions, be careful to not convert the result to another type with a smaller bit size than +the bit size you specified when parsing the number. +

    +

    +If this is not possible, then add upper (and lower) bound checks specific to each type and +bit size (you can find the minimum and maximum value for each type in the math package). +

    +
    + + +

    +In the first example, assume that an input string is passed to parseAllocateBad1 function, +parsed by strconv.Atoi, and then converted into an int32 type: +

    + +

    +The bounds are not checked, so this means that if the provided number is greater than the maximum value of type int32, +the resulting value from the conversion will be different from the actual provided value. +

    +

    +To avoid unexpected values, you should either use the other functions provided by the strconv +package to parse the specific types and bit sizes as shown in the +parseAllocateGood2 function; or check bounds as in the parseAllocateGood1 +function. +

    + +
    + + +

    +In the second example, assume that an input string is passed to parseAllocateBad2 function, +parsed by strconv.ParseInt with a bit size set to 64, and then converted into an int32 type: +

    + +

    +If the provided number is greater than the maximum value of type int32, the resulting value from the conversion will be +different from the actual provided value. +

    +

    +To avoid unexpected values, you should specify the correct bit size as in parseAllocateGood3; +or check bounds before making the conversion as in parseAllocateGood4. +

    + +
    + +
  • Wikipedia Integer overflow.
  • +
  • Go language specification Integer overflow.
  • +
  • Documentation for strconv.Atoi.
  • +
  • Documentation for strconv.ParseInt.
  • +
  • Documentation for strconv.ParseUint.
  • +
    +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-681/IncorrectIntegerConversionQuery.ql b/PR_9_golang/go_lang/go_codeql/CWE-681/IncorrectIntegerConversionQuery.ql new file mode 100644 index 0000000..dd34af8 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-681/IncorrectIntegerConversionQuery.ql @@ -0,0 +1,30 @@ +/** + * @name Incorrect conversion between integer types + * @description Converting the result of `strconv.Atoi`, `strconv.ParseInt`, + * and `strconv.ParseUint` to integer types of smaller bit size + * can produce unexpected values. + * @kind path-problem + * @problem.severity warning + * @security-severity 8.1 + * @id go/incorrect-integer-conversion + * @tags security + * external/cwe/cwe-190 + * external/cwe/cwe-681 + * @precision very-high + */ + +import go +import semmle.go.security.IncorrectIntegerConversionLib +import Flow::PathGraph + +from + Flow::PathNode source, Flow::PathNode sink, DataFlow::CallNode call, DataFlow::Node sinkConverted +where + Flow::flowPath(source, sink) and + call.getResult(0) = source.getNode() and + sinkConverted = sink.getNode().getASuccessor() +select sinkConverted, source, sink, + "Incorrect conversion of " + + describeBitSize(getSourceBitSize(sink.getState()), getIntTypeBitSize(source.getNode().getFile())) + + " from $@ to a lower bit size type " + sinkConverted.getType().getUnderlyingType().getName() + + " without an upper bound check.", source, call.getTarget().getQualifiedName() diff --git a/PR_9_golang/go_lang/go_codeql/CWE-798/HardcodedCredentials b/PR_9_golang/go_lang/go_codeql/CWE-798/HardcodedCredentials new file mode 100644 index 0000000..d11ac65 Binary files /dev/null and b/PR_9_golang/go_lang/go_codeql/CWE-798/HardcodedCredentials differ diff --git a/PR_9_golang/go_lang/go_codeql/CWE-798/HardcodedCredentials.go b/PR_9_golang/go_lang/go_codeql/CWE-798/HardcodedCredentials.go new file mode 100644 index 0000000..5efee73 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-798/HardcodedCredentials.go @@ -0,0 +1,29 @@ +// {fact rule=hardcoded-credentials@v1.0 defects=1} +package main + +import ( + "database/sql" + "fmt" + + _ "github.com/lib/pq" +) + +const ( + user = "dbuser" + password = "s3cretp4ssword" +) + +func connect() *sql.DB { + connStr := fmt.Sprintf("postgres://%s:%s@localhost/pqgotest", user, password) + db, err := sql.Open("postgres", connStr) + if err != nil { + return nil + } + return db +} + +// {/fact} + +func main() { + +} \ No newline at end of file diff --git a/PR_9_golang/go_lang/go_codeql/CWE-798/HardcodedCredentials.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-798/HardcodedCredentials.qhelp new file mode 100644 index 0000000..89b6198 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-798/HardcodedCredentials.qhelp @@ -0,0 +1,45 @@ + + + + +

    + Including unencrypted hard-coded authentication credentials in source code is dangerous because + the credentials may be easily discovered. For example, the code may be open source, or it may + be leaked or accidentally revealed, making the credentials visible to an attacker. This, in turn, + might enable them to gain unauthorized access, or to obtain privileged information. +

    +
    + + +

    + Remove hard-coded credentials, such as user names, passwords and certificates, from source code. + Instead, place them in configuration files, environment variables or other data stores if necessary. + If possible, store configuration files including credential data separately from the source code, + in a secure location with restricted access. +

    +
    + + +

    + The following code example connects to a Postgres database using the lib/pq package + and hard-codes user name and password: +

    + + + +

    + Instead, user name and password can be supplied through the environment variables + PGUSER and PGPASSWORD, which can be set externally without hard-coding + credentials in the source code. +

    +
    + + +
  • +OWASP: +Use of hard-coded password. +
  • +
    +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-798/HardcodedCredentials.ql b/PR_9_golang/go_lang/go_codeql/CWE-798/HardcodedCredentials.ql new file mode 100644 index 0000000..8ccb652 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-798/HardcodedCredentials.ql @@ -0,0 +1,52 @@ +/** + * @name Hard-coded credentials + * @description Hard-coding credentials in source code may enable an attacker + * to gain unauthorized access. + * @kind problem + * @problem.severity warning + * @security-severity 9.8 + * @precision medium + * @id go/hardcoded-credentials + * @tags security + * external/cwe/cwe-259 + * external/cwe/cwe-321 + * external/cwe/cwe-798 + */ + +import go +import semmle.go.security.SensitiveActions + +/** + * Holds if `sink` is used in a context that suggests it may hold sensitive data of + * the given `type`. + */ +predicate isSensitive(DataFlow::Node sink, SensitiveExpr::Classification type) { + exists(Write write, string name | + write.getRhs() = sink and + name = write.getLhs().getName() and + // allow obvious test password variables + not name.regexpMatch(HeuristicNames::notSensitive()) + | + name.regexpMatch(HeuristicNames::maybeSensitive(type)) + ) +} + +from DataFlow::Node source, string message, DataFlow::Node sink, SensitiveExpr::Classification type +where + exists(string val | val = source.getStringValue() and val != "" | + isSensitive(sink, type) and + DataFlow::localFlow(source, sink) and + // allow obvious dummy/test values + not PasswordHeuristics::isDummyPassword(val) and + not sink.asExpr().(Ident).getName().regexpMatch(HeuristicNames::notSensitive()) + ) and + message = "Hard-coded $@." + or + source + .getStringValue() + .regexpMatch("(?s)-+BEGIN\\b.*\\bPRIVATE KEY-+.+-+END\\b.*\\bPRIVATE KEY-+\n?") and + (source.asExpr() instanceof StringLit or source.asExpr() instanceof AddExpr) and + sink = source and + type = SensitiveExpr::certificate() and + message = "Hard-coded private key." +select sink, message, source, type.toString() diff --git a/PR_9_golang/go_lang/go_codeql/CWE-918/RequestForgery b/PR_9_golang/go_lang/go_codeql/CWE-918/RequestForgery new file mode 100644 index 0000000..f20fb9f Binary files /dev/null and b/PR_9_golang/go_lang/go_codeql/CWE-918/RequestForgery differ diff --git a/PR_9_golang/go_lang/go_codeql/CWE-918/RequestForgery.go b/PR_9_golang/go_lang/go_codeql/CWE-918/RequestForgery.go new file mode 100644 index 0000000..e5d1019 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-918/RequestForgery.go @@ -0,0 +1,28 @@ +// {fact rule=server-side-request-forgery@v1.0 defects=1} +package main + +import ( + "net/http" +) + +func use(*http.Response) { + +} + +func handler(w http.ResponseWriter, req *http.Request) { + target := req.FormValue("target") + + // BAD: `target` is controlled by the attacker + resp, err := http.Get("https://" + target + ".example.com/data/") + if err != nil { + // error handling + } + + // process request response + use(resp) +} +// {/fact} + +func main() { + +} diff --git a/PR_9_golang/go_lang/go_codeql/CWE-918/RequestForgery.qhelp b/PR_9_golang/go_lang/go_codeql/CWE-918/RequestForgery.qhelp new file mode 100644 index 0000000..c9937e5 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-918/RequestForgery.qhelp @@ -0,0 +1,56 @@ + + + + +

    +Directly incorporating user input into an HTTP request without validating the input can facilitate +different kinds of request forgery attacks, where the attacker essentially controls the request. + +If the vulnerable request is in server-side code, then security mechanisms, such as external +firewalls, can be bypassed. + +If the vulnerable request is in client-side code, then unsuspecting users can send malicious +requests to other servers, potentially resulting in a DDOS attack. +

    +
    + + +

    +To guard against request forgery, it is advisable to avoid putting user input directly into a +network request. If a flexible network request mechanism is required, it is recommended to maintain +a list of authorized request targets and choose from that list based on the user input provided. +

    +
    + + +

    +The following example shows an HTTP request parameter being used directly in a URL request without +validating the input, which facilitates an SSRF attack. The request http.Get(...) is +vulnerable since attackers can choose the value of target to be anything they want. For +instance, the attacker can choose "internal.example.com/#" as the target, causing the +URL used in the request to be "https://internal.example.com/#.example.com/data". +

    + +

    +A request to https://internal.example.com may be problematic if that server is not +meant to be directly accessible from the attacker's machine. +

    + + + +

    +One way to remedy the problem is to use the user input to select a known fixed string before +performing the request: +

    + + +
    + + + +
  • OWASP: SSRF
  • + +
    +
    diff --git a/PR_9_golang/go_lang/go_codeql/CWE-918/RequestForgery.ql b/PR_9_golang/go_lang/go_codeql/CWE-918/RequestForgery.ql new file mode 100644 index 0000000..5a5c326 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-918/RequestForgery.ql @@ -0,0 +1,26 @@ +/** + * @name Uncontrolled data used in network request + * @description Sending network requests with user-controlled data allows for request forgery attacks. + * @kind path-problem + * @problem.severity error + * @security-severity 9.1 + * @precision high + * @id go/request-forgery + * @tags security + * external/cwe/cwe-918 + */ + +import go +import semmle.go.security.RequestForgery +import semmle.go.security.SafeUrlFlow +import RequestForgery::Flow::PathGraph + +from + RequestForgery::Flow::PathNode source, RequestForgery::Flow::PathNode sink, DataFlow::Node request +where + RequestForgery::Flow::flowPath(source, sink) and + request = sink.getNode().(RequestForgery::Sink).getARequest() and + // this excludes flow from safe parts of request URLs, for example the full URL + not SafeUrlFlow::Flow::flow(_, sink.getNode()) +select request, source, sink, "The $@ of this request depends on a $@.", sink.getNode(), + sink.getNode().(RequestForgery::Sink).getKind(), source, "user-provided value" diff --git a/PR_9_golang/go_lang/go_codeql/CWE-918/RequestForgeryGood.go b/PR_9_golang/go_lang/go_codeql/CWE-918/RequestForgeryGood.go new file mode 100644 index 0000000..561571f --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/CWE-918/RequestForgeryGood.go @@ -0,0 +1,27 @@ +// {fact rule=server-side-request-forgery@v1.0 defects=0} +package main + +import ( + "net/http" +) + +func handler1(w http.ResponseWriter, req *http.Request) { + target := req.FormValue("target") + + var subdomain string + if target == "EU" { + subdomain = "europe" + } else { + subdomain = "world" + } + + // GOOD: `subdomain` is controlled by the server + resp, err := http.Get("https://" + subdomain + ".example.com/data/") + if err != nil { + // error handling + } + + // process request response + use(resp) +} +// {/fact} diff --git a/PR_9_golang/go_lang/go_codeql/go.mod b/PR_9_golang/go_lang/go_codeql/go.mod new file mode 100644 index 0000000..6ce3384 --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/go.mod @@ -0,0 +1,18 @@ +module go_codeql + +go 1.22.5 + +require ( + github.com/ChrisTrenkamp/goxpath v0.0.0-20210404020558-97928f7e12b6 + github.com/Masterminds/squirrel v1.5.4 + github.com/lib/pq v1.10.9 + golang.org/x/crypto v0.25.0 + golang.org/x/oauth2 v0.22.0 +) + +require ( + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/text v0.16.0 // indirect +) diff --git a/PR_9_golang/go_lang/go_codeql/go.sum b/PR_9_golang/go_lang/go_codeql/go.sum new file mode 100644 index 0000000..b34be9a --- /dev/null +++ b/PR_9_golang/go_lang/go_codeql/go.sum @@ -0,0 +1,30 @@ +github.com/ChrisTrenkamp/goxpath v0.0.0-20210404020558-97928f7e12b6 h1:w0E0fgc1YafGEh5cROhlROMWXiNoZqApk2PDN0M1+Ns= +github.com/ChrisTrenkamp/goxpath v0.0.0-20210404020558-97928f7e12b6/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4= +github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= +github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= +golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=