Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ codeql database analyze database.db --format=sarif-latest --output=./tob.sarif -
|[Async unsafe signal handler](./cpp/src/docs/security/AsyncUnsafeSignalHandler/AsyncUnsafeSignalHandler.md)|Async unsafe signal handler (like the one used in CVE-2024-6387)|warning|high|
|[Invalid string size passed to string manipulation function](./cpp/src/docs/security/CStrnFinder/CStrnFinder.md)|Finds calls to functions that take as input a string and its size as separate arguments (e.g., `strncmp`, `strncat`, ...) and the size argument is wrong|error|low|
|[Missing null terminator](./cpp/src/docs/security/NoNullTerminator/NoNullTerminator.md)|This query finds incorrectly initialized strings that are passed to functions expecting null-byte-terminated strings|error|high|
|[Potentially unguarded protocol handler invocation](./cpp/src/docs/security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.md)|Detects calls to URL protocol handlers with untrusted input that may not be properly validated for dangerous protocols|warning|medium|
|[Unsafe implicit integer conversion](./cpp/src/docs/security/UnsafeImplicitConversions/UnsafeImplicitConversions.md)|Finds implicit integer casts that may overflow or be truncated, with false positive reduction via Value Range Analysis|warning|low|

### Go
Expand All @@ -72,6 +73,7 @@ codeql database analyze database.db --format=sarif-latest --output=./tob.sarif -

| Name | Description | Severity | Precision |
| --- | ----------- | :----: | :--------: |
|[Potentially unguarded protocol handler invocation](./java/src/docs/security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.md)|Detects calls to URL protocol handlers with untrusted input that may not be properly validated for dangerous protocols|warning|medium|
|[Recursive functions](./java-kotlin/src/docs/security/Recursion/Recursion.md)|Detects recursive calls|warning|low|

## Query suites
Expand Down
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)
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"
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
edges
nodes
subpaths
#select
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
security/PotentiallyUnguardedProtocolHandler/PotentiallyUnguardedProtocolHandler.ql
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 *);
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
}
}
Loading