-
Notifications
You must be signed in to change notification settings - Fork 7
Add PotentiallyUnguardedProtocolHandler query for Java and C++ #32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ex0dus-0x
wants to merge
5
commits into
main
Choose a base branch
from
alan/untrusted-protocol-handle
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
7d293f2
init: add PotentiallyUnguardedProtocolHandler query
ex0dus-0x 16c2ae3
add legacy protocol handler support
ex0dus-0x b06ad53
add support for C++/Qt, and genericize shell execution models for all…
ex0dus-0x 3275b0d
fix java tests
ex0dus-0x 29a9a76
Add docs and fix CI/CD issue
ex0dus-0x File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
87 changes: 87 additions & 0 deletions
87
...rity/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| # Potentially unguarded protocol handler invocation | ||
|
|
||
| This query detects calls to URL protocol handlers with untrusted input that may not be properly validated for dangerous protocols. This vulnerability is related to CWE-939 (Improper Authorization in Handler for Custom URL Scheme) and aligns with CVE-2022-43550. | ||
|
|
||
| When applications invoke protocol handlers (like `rundll32 url.dll,FileProtocolHandler` on Windows, `xdg-open` on Linux, `open` on macOS, or Qt's `QDesktopServices::openUrl()`), untrusted URLs can potentially trigger dangerous protocols such as `file://`, `smb://`, or other custom handlers that may lead to unauthorized file access, command execution, or other security issues. | ||
|
|
||
| ## Detected patterns | ||
|
|
||
| The query identifies several common protocol handler invocation patterns: | ||
|
|
||
| - **Windows**: `rundll32 url.dll,FileProtocolHandler` via system calls | ||
| - **Linux**: `xdg-open` via system calls | ||
| - **macOS**: `open` command via system calls | ||
| - **Qt applications**: `QDesktopServices::openUrl()` | ||
|
|
||
| ## Recommendation | ||
|
|
||
| Always validate URL schemes before passing them to protocol handlers. Only allow safe protocols like `http://` and `https://`. Reject or sanitize URLs containing potentially dangerous protocols. | ||
|
|
||
| ## Example | ||
|
|
||
| The following vulnerable code passes untrusted input directly to a protocol handler: | ||
|
|
||
| ```cpp | ||
| #include <QDesktopServices> | ||
| #include <QUrl> | ||
|
|
||
| void openUserUrl(const QString& userInput) { | ||
| // VULNERABLE: No validation of the URL scheme | ||
| QDesktopServices::openUrl(QUrl(userInput)); | ||
| } | ||
| ``` | ||
|
|
||
| An attacker could provide a URL like `file:///etc/passwd` or `smb://attacker-server/share` to access unauthorized resources. | ||
|
|
||
| The corrected version validates the URL scheme before opening: | ||
|
|
||
| ```cpp | ||
| #include <QDesktopServices> | ||
| #include <QUrl> | ||
|
|
||
| void openUserUrl(const QString& userInput) { | ||
| QUrl url(userInput); | ||
| QString scheme = url.scheme().toLower(); | ||
|
|
||
| // Only allow safe protocols | ||
| if (scheme == "http" || scheme == "https") { | ||
| QDesktopServices::openUrl(url); | ||
| } else { | ||
| // Log error or show warning to user | ||
| qWarning() << "Rejected unsafe URL scheme:" << scheme; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| For system command invocations: | ||
|
|
||
| ```cpp | ||
| void openUserUrlViaShell(const char* userInput) { | ||
| // VULNERABLE: Untrusted input passed to xdg-open | ||
| char cmd[512]; | ||
| snprintf(cmd, sizeof(cmd), "xdg-open '%s'", userInput); | ||
| system(cmd); | ||
| } | ||
| ``` | ||
|
|
||
| Should be corrected to validate the scheme: | ||
|
|
||
| ```cpp | ||
| bool isValidScheme(const char* url) { | ||
| return (strncasecmp(url, "http://", 7) == 0 || | ||
| strncasecmp(url, "https://", 8) == 0); | ||
| } | ||
|
|
||
| void openUserUrlViaShell(const char* userInput) { | ||
| if (isValidScheme(userInput)) { | ||
| char cmd[512]; | ||
| snprintf(cmd, sizeof(cmd), "xdg-open '%s'", userInput); | ||
| system(cmd); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## References | ||
|
|
||
| - [CWE-939: Improper Authorization in Handler for Custom URL Scheme](https://cwe.mitre.org/data/definitions/939.html) | ||
| - [CVE-2022-43550: USB Creator has insufficiently protected credentials](https://ubuntu.com/security/CVE-2022-43550) |
149 changes: 149 additions & 0 deletions
149
cpp/src/security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.ql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<PotentiallyUnguardedProtocolHandlerConfig>; | ||
|
|
||
| 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" |
4 changes: 4 additions & 0 deletions
4
...security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.expected
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| edges | ||
| nodes | ||
| subpaths | ||
| #select | ||
1 change: 1 addition & 0 deletions
1
...ts/security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.qlref
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.ql |
60 changes: 60 additions & 0 deletions
60
cpp/test/query-tests/security/PotentiallyUnguardedProtocolHandler/test.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| // Forward declarations - minimal to avoid system header dependencies | ||
| extern "C" { | ||
| int system(const char *); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use existing stubs, see other queries |
||
| 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 | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
empty results are expected?