diff --git a/cpp/src/security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.ql b/cpp/src/security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.ql new file mode 100644 index 0000000..4a7113f --- /dev/null +++ b/cpp/src/security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.ql @@ -0,0 +1,149 @@ +/** + * @name Potentially unguarded protocol handler invocation + * @id tob/cpp/unguarded-protocol-handler + * @description Detects calls to URL protocol handlers with untrusted input that may not be properly validated for dangerous protocols + * @kind path-problem + * @tags security + * external/cwe/cwe-939 + * @precision medium + * @problem.severity warning + * @security-severity 6.5 + * @group security + */ + +import cpp +private import semmle.code.cpp.ir.dataflow.TaintTracking +private import semmle.code.cpp.security.FlowSources +private import semmle.code.cpp.security.CommandExecution + +/** + * Generic case: invoke protocol handling through OS's protocol handling utilities. This aligns with CVE-2022-43550. + */ +class ShellProtocolHandler extends SystemFunction { + ShellProtocolHandler() { + // Check if any calls to this function contain protocol handler invocations + exists(FunctionCall call | + call.getTarget() = this and + exists(Expr arg | + arg = call.getArgument(0).getAChild*() and + exists(StringLiteral sl | sl = arg or sl.getParent*() = arg | + sl.getValue() + .regexpMatch("(?i).*(rundll32.*url\\.dll.*FileProtocolHandler|xdg-open|\\bopen\\b).*") + ) + ) + ) + } + + string getHandlerType() { + exists(FunctionCall call, StringLiteral sl | + call.getTarget() = this and + sl.getParent*() = call.getArgument(0) and + ( + sl.getValue().regexpMatch("(?i).*rundll32.*url\\.dll.*FileProtocolHandler.*") and + result = "rundll32 url.dll,FileProtocolHandler" + or + sl.getValue().regexpMatch("(?i).*xdg-open.*") and + result = "xdg-open" + or + sl.getValue().regexpMatch("(?i).*\\bopen\\b.*") and + result = "open" + ) + ) + } +} + +/** + * Qt's QDesktopServices::openUrl method + */ +class QtProtocolHandler extends FunctionCall { + QtProtocolHandler() { this.getTarget().hasQualifiedName("QDesktopServices", "openUrl") } + + Expr getUrlArgument() { result = this.getArgument(0) } +} + +/** + * A sanitizer node that represents URL scheme validation + */ +class UrlSchemeValidationSanitizer extends DataFlow::Node { + UrlSchemeValidationSanitizer() { + exists(FunctionCall fc | + fc = this.asExpr() and + ( + // String comparison on the untrusted URL + fc.getTarget().getName() = + [ + "strcmp", "strncmp", "strcasecmp", "strncasecmp", "strstr", "strcasestr", "_stricmp", + "_strnicmp" + ] + or + // Qt QUrl::scheme() comparison - QUrl::scheme() returns QString + // Pattern: url.scheme() == "http" or url.scheme() == "https" + exists(FunctionCall schemeCall | + schemeCall.getTarget().hasQualifiedName("QUrl", "scheme") and + ( + // Direct comparison + fc.getTarget().hasName(["operator==", "operator!="]) and + fc.getAnArgument() = schemeCall + or + // QString comparison methods + fc = schemeCall and + exists(FunctionCall qstringCmp | + qstringCmp.getQualifier() = schemeCall and + qstringCmp.getTarget().hasQualifiedName("QString", ["compare", "operator=="]) + ) + ) + ) + or + // Qt QString startsWith check for direct URL strings + fc.getTarget().hasQualifiedName("QString", "startsWith") + ) + ) + } +} + +/** + * Configuration for tracking untrusted data to protocol handler invocations + */ +module PotentiallyUnguardedProtocolHandlerConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } + + predicate isSink(DataFlow::Node sink) { + // QDesktopServices::openUrl() + exists(QtProtocolHandler call | sink.asExpr() = call.getUrlArgument()) + or + // Shell protocol handlers (rundll32, xdg-open, open) via system()/popen()/exec*() + exists(FunctionCall call | + call.getTarget() instanceof ShellProtocolHandler and + sink.asExpr() = call.getArgument(0) + ) + } + + predicate isBarrier(DataFlow::Node node) { node instanceof UrlSchemeValidationSanitizer } +} + +module PotentiallyUnguardedProtocolHandlerFlow = + TaintTracking::Global; + +import PotentiallyUnguardedProtocolHandlerFlow::PathGraph + +from + PotentiallyUnguardedProtocolHandlerFlow::PathNode source, + PotentiallyUnguardedProtocolHandlerFlow::PathNode sink, FunctionCall call, string callType +where + PotentiallyUnguardedProtocolHandlerFlow::flowPath(source, sink) and + ( + exists(QtProtocolHandler qtCall | + call = qtCall and + sink.getNode().asExpr() = qtCall.getUrlArgument() and + callType = "QDesktopServices::openUrl()" + ) + or + exists(ShellProtocolHandler shellFunc | + call.getTarget() = shellFunc and + sink.getNode().asExpr() = call.getArgument(0) and + callType = shellFunc.getHandlerType() + ) + ) +select call, source, sink, + callType + " is called with untrusted input from $@ without proper URL scheme validation.", + source.getNode(), "this source" diff --git a/cpp/test/query-tests/security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.expected b/cpp/test/query-tests/security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.expected new file mode 100644 index 0000000..e217064 --- /dev/null +++ b/cpp/test/query-tests/security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.expected @@ -0,0 +1,4 @@ +edges +nodes +subpaths +#select diff --git a/cpp/test/query-tests/security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.qlref b/cpp/test/query-tests/security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.qlref new file mode 100644 index 0000000..de5b795 --- /dev/null +++ b/cpp/test/query-tests/security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.qlref @@ -0,0 +1 @@ +security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.ql diff --git a/cpp/test/query-tests/security/PotentiallyUnguardedProtocolHandler/test.cpp b/cpp/test/query-tests/security/PotentiallyUnguardedProtocolHandler/test.cpp new file mode 100644 index 0000000..4579758 --- /dev/null +++ b/cpp/test/query-tests/security/PotentiallyUnguardedProtocolHandler/test.cpp @@ -0,0 +1,60 @@ +// Forward declarations - minimal to avoid system header dependencies +extern "C" { +int system(const char *); +int sprintf(char *, const char *, ...); +int strncmp(const char *, const char *, unsigned long); +} + +// Mock QString class +struct QString { + const char *data; + QString(const char *s) : data(s) {} + bool operator==(const char *other) const; +}; + +// Mock Qt-like class for QDesktopServices::openUrl +struct QUrl { + const char *url; + QUrl(const char *s) : url(s) {} + QString scheme() const; + bool startsWith(const char *prefix) const; +}; + +struct QDesktopServices { + static bool openUrl(const QUrl &url); +}; + +// Untrusted input sources +extern "C" char *getUserInput(); +extern "C" const char *getUrlParam(); + +// BAD: QDesktopServices::openUrl with untrusted input +void bad1_qt(const char *userUrl) { + QUrl url(userUrl); + QDesktopServices::openUrl(url); // BAD +} + +void bad2_qt() { + const char *input = getUrlParam(); + QUrl url(input); + QDesktopServices::openUrl(url); // BAD +} + +void safe1_qt() { + QUrl url("https://example.com"); + QDesktopServices::openUrl(url); // GOOD - no taint +} + +void safe2_qt(const char *userUrl) { + if (strncmp(userUrl, "https://", 8) == 0 || + strncmp(userUrl, "http://", 7) == 0) { + QUrl url(userUrl); + QDesktopServices::openUrl(url); // GOOD + } +} + +void safe3_qt(QUrl &url) { + if (url.scheme() == "https" || url.scheme() == "http") { + QDesktopServices::openUrl(url); // GOOD + } +} \ No newline at end of file diff --git a/java/src/security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.qhelp b/java/src/security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.qhelp new file mode 100644 index 0000000..1e63ba4 --- /dev/null +++ b/java/src/security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.qhelp @@ -0,0 +1,26 @@ + + + +

+ URL protocol handlers are used to handle any URL scheme supported by the native desktop. + If URL inputs to these handlers are untrusted and not properly sanitized, they can be + used to perform unintended actions by another application registered to handle the same + protocol. +

+
+ +

+ Review protocol handler sinks and ensure that any URL inputs are properly sanitized if + they come from untrusted sources. +

+
+ +
  • Positive Security: Allow + arbitrary URLs, expect arbitrary code execution +
  • +
  • CVE-2022-43550: Jitsi Desktop Client + RCE By Interacting with Malicious URL Schemes on Windows +
  • +
    +
    \ No newline at end of file diff --git a/java/src/security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.ql b/java/src/security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.ql new file mode 100644 index 0000000..33b3058 --- /dev/null +++ b/java/src/security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.ql @@ -0,0 +1,166 @@ +/** + * @name Potentially unguarded protocol handler invocation + * @id tob/java/unguarded-protocol-handler + * @description Detects calls to URL protocol handlers with untrusted input that may not be properly validated for dangerous protocols + * @kind path-problem + * @tags security + * external/cwe/cwe-939 + * @precision medium + * @problem.severity warning + * @security-severity 6.5 + * @group security + */ + +import java +import semmle.code.java.dataflow.TaintTracking +import semmle.code.java.dataflow.FlowSources +import semmle.code.java.controlflow.Guards +import semmle.code.java.security.ExternalProcess + +/** + * Generic case: invoke protocol handling through OS's protocol handling utilities. This aligns with CVE-2022-43550. + */ +class ShellProtocolHandler extends ArgumentToExec { + ShellProtocolHandler() { + // Single string: "rundll32 url.dll,FileProtocolHandler " + url or "xdg-open " + url + this instanceof StringArgumentToExec and + exists(StringLiteral sl | + sl.getParent*() = this and + sl.getValue() + .regexpMatch("(?i).*(rundll32.*url\\.dll.*FileProtocolHandler|xdg-open|\\bopen\\b).*") + ) + or + // Array: {"rundll32", "url.dll,FileProtocolHandler", url} or {"xdg-open", url} + this.getType().(Array).getElementType() instanceof TypeString and + exists(ArrayInit init, StringLiteral handler | + init = this.(ArrayCreationExpr).getInit() and + handler = init.getAnInit() and + handler.getValue().regexpMatch("(?i)(rundll32(\\.exe)?|xdg-open|open|/usr/bin/open)") + ) + } + + Expr getUrlArgument() { + // For single string exec, the URL is tainted into the concatenated string + result = this and this instanceof StringArgumentToExec + or + // For arrays with rundll32, find elements after "url.dll,FileProtocolHandler" + exists(ArrayInit init, int urlIdx, int dllIdx | + init = this.(ArrayCreationExpr).getInit() and + result = init.getInit(urlIdx) and + init.getInit(dllIdx) + .(StringLiteral) + .getValue() + .regexpMatch("(?i)url\\.dll.*FileProtocolHandler") and + urlIdx > dllIdx + ) + or + // For arrays with xdg-open/open, find elements after the handler + exists(ArrayInit init, int urlIdx, int handlerIdx | + init = this.(ArrayCreationExpr).getInit() and + result = init.getInit(urlIdx) and + init.getInit(handlerIdx) + .(StringLiteral) + .getValue() + .regexpMatch("(?i)(xdg-open|open|/usr/bin/open)") and + urlIdx > handlerIdx + ) + } + + string getHandlerType() { + exists(StringLiteral sl | + sl.getParent*() = this and + ( + sl.getValue().regexpMatch("(?i).*rundll32.*url\\.dll.*FileProtocolHandler.*") and + result = "rundll32 url.dll,FileProtocolHandler" + or + sl.getValue().regexpMatch("(?i).*xdg-open.*") and + result = "xdg-open" + or + sl.getValue().regexpMatch("(?i).*\\bopen\\b.*") and + result = "open" + ) + ) + } +} + +/** + * A call to Desktop.browse() method. This is the platform-agnostic standard now. + * NOTE(alan): the URLRedirect query will likely also trigger due to a little imprecision there + */ +class DesktopBrowseProtocolHandler extends MethodCall { + DesktopBrowseProtocolHandler() { + this.getMethod().hasName("browse") and + this.getMethod().getDeclaringType().hasQualifiedName("java.awt", "Desktop") + } + + Expr getUrlArgument() { result = this.getArgument(0) } +} + +/** + * A sanitizer node that represents URL scheme validation + */ +class UrlSchemeValidationSanitizer extends DataFlow::Node { + UrlSchemeValidationSanitizer() { + exists(MethodCall mc | + mc = this.asExpr() and + ( + // String comparison on the untrusted URL + mc.getMethod().hasName(["equals", "equalsIgnoreCase", "startsWith", "matches"]) and + exists(MethodCall getScheme | + getScheme.getParent*() = mc and + getScheme.getMethod().hasName(["getScheme", "getProtocol"]) + ) + or + // URL string contains check like: url.contains("://") + mc.getMethod().hasName(["contains", "startsWith", "matches", "indexOf"]) and + exists(StringLiteral sl | sl = mc.getAnArgument() | sl.getValue().regexpMatch(".*://.*")) + or + // Pattern matching on the string representation + mc.getMethod().hasName(["matches", "find"]) and + mc.getQualifier().getType().(RefType).hasQualifiedName("java.util.regex", "Matcher") + ) + ) + } +} + +/** + * Configuration for tracking untrusted data to protocol handler invocations + */ +module PotentiallyUnguardedProtocolHandlerConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } + + predicate isSink(DataFlow::Node sink) { + exists(DesktopBrowseProtocolHandler call | sink.asExpr() = call.getUrlArgument()) + or + exists(ShellProtocolHandler call | sink.asExpr() = call.getUrlArgument()) + } + + predicate isBarrier(DataFlow::Node node) { node instanceof UrlSchemeValidationSanitizer } +} + +module PotentiallyUnguardedProtocolHandlerFlow = + TaintTracking::Global; + +import PotentiallyUnguardedProtocolHandlerFlow::PathGraph + +from + PotentiallyUnguardedProtocolHandlerFlow::PathNode source, + PotentiallyUnguardedProtocolHandlerFlow::PathNode sink, Expr call, string callType +where + PotentiallyUnguardedProtocolHandlerFlow::flowPath(source, sink) and + ( + exists(DesktopBrowseProtocolHandler dbc | + call = dbc and + sink.getNode().asExpr() = dbc.getUrlArgument() and + callType = "Desktop.browse()" + ) + or + exists(ShellProtocolHandler shc | + call = shc and + sink.getNode().asExpr() = shc.getUrlArgument() and + callType = shc.getHandlerType() + ) + ) +select call, source, sink, + callType + " is called with untrusted input from $@ without proper URL scheme validation.", + source.getNode(), "this source" diff --git a/java/test/query-tests/security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.expected b/java/test/query-tests/security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.expected new file mode 100644 index 0000000..d621e3a --- /dev/null +++ b/java/test/query-tests/security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.expected @@ -0,0 +1,52 @@ +edges +| PotentiallyUnguardedProtocolHandler.java:10:22:10:48 | getParameter(...) : String | PotentiallyUnguardedProtocolHandler.java:11:45:11:47 | url : String | provenance | Src:MaD:46190 | +| PotentiallyUnguardedProtocolHandler.java:11:45:11:47 | url : String | PotentiallyUnguardedProtocolHandler.java:11:37:11:48 | new URI(...) | provenance | MaD:44428 Sink:MaD:43969 | +| PotentiallyUnguardedProtocolHandler.java:20:22:20:48 | getParameter(...) : String | PotentiallyUnguardedProtocolHandler.java:21:27:21:29 | url : String | provenance | Src:MaD:46190 | +| PotentiallyUnguardedProtocolHandler.java:21:19:21:30 | new URI(...) : URI | PotentiallyUnguardedProtocolHandler.java:23:41:23:43 | uri | provenance | Sink:MaD:43969 | +| PotentiallyUnguardedProtocolHandler.java:21:27:21:29 | url : String | PotentiallyUnguardedProtocolHandler.java:21:19:21:30 | new URI(...) : URI | provenance | MaD:44428 | +| PotentiallyUnguardedProtocolHandler.java:34:22:34:48 | getParameter(...) : String | PotentiallyUnguardedProtocolHandler.java:35:27:35:29 | url : String | provenance | Src:MaD:46190 | +| PotentiallyUnguardedProtocolHandler.java:35:19:35:30 | new URI(...) : URI | PotentiallyUnguardedProtocolHandler.java:39:41:39:43 | uri | provenance | Sink:MaD:43969 | +| PotentiallyUnguardedProtocolHandler.java:35:27:35:29 | url : String | PotentiallyUnguardedProtocolHandler.java:35:19:35:30 | new URI(...) : URI | provenance | MaD:44428 | +| PotentiallyUnguardedProtocolHandler.java:58:22:58:48 | getParameter(...) : String | PotentiallyUnguardedProtocolHandler.java:60:35:60:79 | ... + ... | provenance | Src:MaD:46190 Sink:MaD:44131 | +| PotentiallyUnguardedProtocolHandler.java:77:22:77:48 | getParameter(...) : String | PotentiallyUnguardedProtocolHandler.java:80:39:80:83 | ... + ... | provenance | Src:MaD:46190 Sink:MaD:44131 | +| PotentiallyUnguardedProtocolHandler.java:97:22:97:48 | getParameter(...) : String | PotentiallyUnguardedProtocolHandler.java:98:35:98:51 | ... + ... | provenance | Src:MaD:46190 Sink:MaD:44131 | +| PotentiallyUnguardedProtocolHandler.java:107:22:107:48 | getParameter(...) : String | PotentiallyUnguardedProtocolHandler.java:110:39:110:55 | ... + ... | provenance | Src:MaD:46190 Sink:MaD:44131 | +| PotentiallyUnguardedProtocolHandler.java:120:22:120:48 | getParameter(...) : String | PotentiallyUnguardedProtocolHandler.java:121:35:121:47 | ... + ... | provenance | Src:MaD:46190 Sink:MaD:44131 | +| PotentiallyUnguardedProtocolHandler.java:130:22:130:48 | getParameter(...) : String | PotentiallyUnguardedProtocolHandler.java:132:39:132:60 | ... + ... | provenance | Src:MaD:46190 Sink:MaD:44131 | +nodes +| PotentiallyUnguardedProtocolHandler.java:10:22:10:48 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| PotentiallyUnguardedProtocolHandler.java:11:37:11:48 | new URI(...) | semmle.label | new URI(...) | +| PotentiallyUnguardedProtocolHandler.java:11:45:11:47 | url : String | semmle.label | url : String | +| PotentiallyUnguardedProtocolHandler.java:20:22:20:48 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| PotentiallyUnguardedProtocolHandler.java:21:19:21:30 | new URI(...) : URI | semmle.label | new URI(...) : URI | +| PotentiallyUnguardedProtocolHandler.java:21:27:21:29 | url : String | semmle.label | url : String | +| PotentiallyUnguardedProtocolHandler.java:23:41:23:43 | uri | semmle.label | uri | +| PotentiallyUnguardedProtocolHandler.java:34:22:34:48 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| PotentiallyUnguardedProtocolHandler.java:35:19:35:30 | new URI(...) : URI | semmle.label | new URI(...) : URI | +| PotentiallyUnguardedProtocolHandler.java:35:27:35:29 | url : String | semmle.label | url : String | +| PotentiallyUnguardedProtocolHandler.java:39:41:39:43 | uri | semmle.label | uri | +| PotentiallyUnguardedProtocolHandler.java:58:22:58:48 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| PotentiallyUnguardedProtocolHandler.java:60:35:60:79 | ... + ... | semmle.label | ... + ... | +| PotentiallyUnguardedProtocolHandler.java:77:22:77:48 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| PotentiallyUnguardedProtocolHandler.java:80:39:80:83 | ... + ... | semmle.label | ... + ... | +| PotentiallyUnguardedProtocolHandler.java:97:22:97:48 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| PotentiallyUnguardedProtocolHandler.java:98:35:98:51 | ... + ... | semmle.label | ... + ... | +| PotentiallyUnguardedProtocolHandler.java:107:22:107:48 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| PotentiallyUnguardedProtocolHandler.java:110:39:110:55 | ... + ... | semmle.label | ... + ... | +| PotentiallyUnguardedProtocolHandler.java:120:22:120:48 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| PotentiallyUnguardedProtocolHandler.java:121:35:121:47 | ... + ... | semmle.label | ... + ... | +| PotentiallyUnguardedProtocolHandler.java:130:22:130:48 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| PotentiallyUnguardedProtocolHandler.java:132:39:132:60 | ... + ... | semmle.label | ... + ... | +subpaths +#select +| PotentiallyUnguardedProtocolHandler.java:11:9:11:49 | browse(...) | PotentiallyUnguardedProtocolHandler.java:10:22:10:48 | getParameter(...) : String | PotentiallyUnguardedProtocolHandler.java:11:37:11:48 | new URI(...) | Desktop.browse() is called with untrusted input from $@ without proper URL scheme validation. | PotentiallyUnguardedProtocolHandler.java:10:22:10:48 | getParameter(...) | this source | +| PotentiallyUnguardedProtocolHandler.java:23:13:23:44 | browse(...) | PotentiallyUnguardedProtocolHandler.java:20:22:20:48 | getParameter(...) : String | PotentiallyUnguardedProtocolHandler.java:23:41:23:43 | uri | Desktop.browse() is called with untrusted input from $@ without proper URL scheme validation. | PotentiallyUnguardedProtocolHandler.java:20:22:20:48 | getParameter(...) | this source | +| PotentiallyUnguardedProtocolHandler.java:39:13:39:44 | browse(...) | PotentiallyUnguardedProtocolHandler.java:34:22:34:48 | getParameter(...) : String | PotentiallyUnguardedProtocolHandler.java:39:41:39:43 | uri | Desktop.browse() is called with untrusted input from $@ without proper URL scheme validation. | PotentiallyUnguardedProtocolHandler.java:34:22:34:48 | getParameter(...) | this source | +| PotentiallyUnguardedProtocolHandler.java:60:35:60:79 | ... + ... | PotentiallyUnguardedProtocolHandler.java:58:22:58:48 | getParameter(...) : String | PotentiallyUnguardedProtocolHandler.java:60:35:60:79 | ... + ... | rundll32 url.dll,FileProtocolHandler is called with untrusted input from $@ without proper URL scheme validation. | PotentiallyUnguardedProtocolHandler.java:58:22:58:48 | getParameter(...) | this source | +| PotentiallyUnguardedProtocolHandler.java:80:39:80:83 | ... + ... | PotentiallyUnguardedProtocolHandler.java:77:22:77:48 | getParameter(...) : String | PotentiallyUnguardedProtocolHandler.java:80:39:80:83 | ... + ... | rundll32 url.dll,FileProtocolHandler is called with untrusted input from $@ without proper URL scheme validation. | PotentiallyUnguardedProtocolHandler.java:77:22:77:48 | getParameter(...) | this source | +| PotentiallyUnguardedProtocolHandler.java:98:35:98:51 | ... + ... | PotentiallyUnguardedProtocolHandler.java:97:22:97:48 | getParameter(...) : String | PotentiallyUnguardedProtocolHandler.java:98:35:98:51 | ... + ... | open is called with untrusted input from $@ without proper URL scheme validation. | PotentiallyUnguardedProtocolHandler.java:97:22:97:48 | getParameter(...) | this source | +| PotentiallyUnguardedProtocolHandler.java:98:35:98:51 | ... + ... | PotentiallyUnguardedProtocolHandler.java:97:22:97:48 | getParameter(...) : String | PotentiallyUnguardedProtocolHandler.java:98:35:98:51 | ... + ... | xdg-open is called with untrusted input from $@ without proper URL scheme validation. | PotentiallyUnguardedProtocolHandler.java:97:22:97:48 | getParameter(...) | this source | +| PotentiallyUnguardedProtocolHandler.java:110:39:110:55 | ... + ... | PotentiallyUnguardedProtocolHandler.java:107:22:107:48 | getParameter(...) : String | PotentiallyUnguardedProtocolHandler.java:110:39:110:55 | ... + ... | open is called with untrusted input from $@ without proper URL scheme validation. | PotentiallyUnguardedProtocolHandler.java:107:22:107:48 | getParameter(...) | this source | +| PotentiallyUnguardedProtocolHandler.java:110:39:110:55 | ... + ... | PotentiallyUnguardedProtocolHandler.java:107:22:107:48 | getParameter(...) : String | PotentiallyUnguardedProtocolHandler.java:110:39:110:55 | ... + ... | xdg-open is called with untrusted input from $@ without proper URL scheme validation. | PotentiallyUnguardedProtocolHandler.java:107:22:107:48 | getParameter(...) | this source | +| PotentiallyUnguardedProtocolHandler.java:121:35:121:47 | ... + ... | PotentiallyUnguardedProtocolHandler.java:120:22:120:48 | getParameter(...) : String | PotentiallyUnguardedProtocolHandler.java:121:35:121:47 | ... + ... | open is called with untrusted input from $@ without proper URL scheme validation. | PotentiallyUnguardedProtocolHandler.java:120:22:120:48 | getParameter(...) | this source | +| PotentiallyUnguardedProtocolHandler.java:132:39:132:60 | ... + ... | PotentiallyUnguardedProtocolHandler.java:130:22:130:48 | getParameter(...) : String | PotentiallyUnguardedProtocolHandler.java:132:39:132:60 | ... + ... | open is called with untrusted input from $@ without proper URL scheme validation. | PotentiallyUnguardedProtocolHandler.java:130:22:130:48 | getParameter(...) | this source | diff --git a/java/test/query-tests/security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.java b/java/test/query-tests/security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.java new file mode 100644 index 0000000..4642b5f --- /dev/null +++ b/java/test/query-tests/security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.java @@ -0,0 +1,139 @@ +import java.awt.Desktop; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import javax.servlet.http.HttpServletRequest; + +public class PotentiallyUnguardedProtocolHandler { + + public void bad1(HttpServletRequest request) throws IOException, URISyntaxException { + String url = request.getParameter("url"); + Desktop.getDesktop().browse(new URI(url)); + } + + public void bad2(String userInput) throws IOException, URISyntaxException { + URI uri = new URI(userInput); + Desktop.getDesktop().browse(uri); + } + + public void safe1(HttpServletRequest request) throws IOException, URISyntaxException { + String url = request.getParameter("url"); + URI uri = new URI(url); + if (uri.getScheme().equals("https") || uri.getScheme().equals("http")) { + Desktop.getDesktop().browse(uri); + } + } + + public void safe2(String userInput) throws IOException, URISyntaxException { + if (userInput.startsWith("https://") || userInput.startsWith("http://")) { + Desktop.getDesktop().browse(new URI(userInput)); + } + } + + public void bad3(HttpServletRequest request) throws IOException, URISyntaxException { + String url = request.getParameter("url"); + URI uri = new URI(url); + + // Weak check - only checks if scheme exists, not what it is + if (uri.getScheme() != null) { + Desktop.getDesktop().browse(uri); + } + } + + public void safe3(String userInput) throws IOException, URISyntaxException { + URI uri = new URI(userInput); + String scheme = uri.getScheme(); + + if (scheme != null && (scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https"))) { + Desktop.getDesktop().browse(uri); + } + } + + public void safe4() throws IOException, URISyntaxException { + Desktop.getDesktop().browse(new URI("https://example.com")); + } + + // rundll32 test cases + public void bad4_rundll32(HttpServletRequest request) throws IOException { + String url = request.getParameter("url"); + // Single string command with concatenation + Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url); + } + + public void bad5_rundll32(String userInput) throws IOException { + // Array-based command + String[] cmd = { "rundll32", "url.dll,FileProtocolHandler", userInput }; + Runtime.getRuntime().exec(cmd); + } + + public void bad6_rundll32(HttpServletRequest request) throws IOException { + String url = request.getParameter("url"); + // ProcessBuilder with list + ProcessBuilder pb = new ProcessBuilder("rundll32", "url.dll,FileProtocolHandler", url); + pb.start(); + } + + public void safe5_rundll32(HttpServletRequest request) throws IOException, URISyntaxException { + String url = request.getParameter("url"); + URI uri = new URI(url); + if (uri.getScheme().equals("https") || uri.getScheme().equals("http")) { + Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url); + } + } + + public void safe6_rundll32(String userInput) throws IOException { + if (userInput.startsWith("https://") || userInput.startsWith("http://")) { + String[] cmd = { "rundll32", "url.dll,FileProtocolHandler", userInput }; + Runtime.getRuntime().exec(cmd); + } + } + + public void safe7_rundll32() throws IOException { + Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler https://example.com"); + } + + // xdg-open test cases (Linux) + public void bad7_xdgopen(HttpServletRequest request) throws IOException { + String url = request.getParameter("url"); + Runtime.getRuntime().exec("xdg-open " + url); + } + + public void bad8_xdgopen(String userInput) throws IOException { + String[] cmd = { "xdg-open", userInput }; + Runtime.getRuntime().exec(cmd); + } + + public void safe8_xdgopen(HttpServletRequest request) throws IOException, URISyntaxException { + String url = request.getParameter("url"); + URI uri = new URI(url); + if (uri.getScheme().equals("https") || uri.getScheme().equals("http")) { + Runtime.getRuntime().exec("xdg-open " + url); + } + } + + public void safe9_xdgopen() throws IOException { + Runtime.getRuntime().exec("xdg-open https://example.com"); + } + + // open test cases (macOS) + public void bad9_open(HttpServletRequest request) throws IOException { + String url = request.getParameter("url"); + Runtime.getRuntime().exec("open " + url); + } + + public void bad10_open(String userInput) throws IOException { + ProcessBuilder pb = new ProcessBuilder("open", userInput); + pb.start(); + } + + public void safe10_open(HttpServletRequest request) throws IOException, URISyntaxException { + String url = request.getParameter("url"); + if (url.startsWith("https://") || url.startsWith("http://")) { + Runtime.getRuntime().exec("/usr/bin/open " + url); + } + } + + public void safe11_open() throws IOException { + Runtime.getRuntime().exec("open https://example.com"); + } +} diff --git a/java/test/query-tests/security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.qlref b/java/test/query-tests/security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.qlref new file mode 100644 index 0000000..bc3c1da --- /dev/null +++ b/java/test/query-tests/security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.qlref @@ -0,0 +1 @@ +security/PotentiallyUnguardedProtocolhandler/PotentiallyUnguardedProtocolhandler.ql