Skip to content

Commit

Permalink
New Golang rule to check for Setuid to root user (#590)
Browse files Browse the repository at this point in the history
Similar to rule PY038, this rule checks Go code for syscall Setuid where
the uid is set to 0 (aka root user).

Rule ID: GO004

Closes: #587

Signed-off-by: Eric Brown <eric.brown@securesauce.dev>
  • Loading branch information
ericwb committed Sep 14, 2024
1 parent 62afed6 commit 0c683b7
Show file tree
Hide file tree
Showing 9 changed files with 225 additions and 1 deletion.
1 change: 1 addition & 0 deletions docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
| GO001 | [crypto — weak cipher](rules/go/stdlib/crypto-weak-cipher.md) | Use of a Broken or Risky Cryptographic Algorithm in `crypto` Package |
| GO002 | [crypto — weak hash](rules/go/stdlib/crypto-weak-hash.md) | Reversible One Way Hash in `crypto` Package |
| GO003 | [crypto — weak key](rules/go/stdlib/crypto-weak-key.md) | Inadequate Encryption Strength Using Weak Keys in `crypto` Package |
| GO004 | [syscall — unnecessary privileges](rules/go/stdlib/syscall-setuid-root.md) | Execution with Unnecessary Privileges using `syscall` Package |

## Java Standard Library

Expand Down
10 changes: 10 additions & 0 deletions docs/rules/go/stdlib/syscall-aetuid-root.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
id: GO004
title: syscall — unnecessary privileges
hide_title: true
pagination_prev: null
pagination_next: null
slug: /rules/GO004
---

::: precli.rules.go.stdlib.syscall_setuid_root
124 changes: 124 additions & 0 deletions precli/rules/go/stdlib/syscall_setuid_root.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Copyright 2024 Secure Sauce LLC
r"""
# Execution with Unnecessary Privileges using `syscall` Package
The Golang function Setuid() is used to set the user ID of the current
process. Passing a user ID of 0 to Setuid() changes the process’s user to the
root user (superuser). This can lead to privilege escalation, allowing the
current process to execute with root-level permissions, which could be
exploited by malicious actors to gain control over the system.
Processes running with elevated privileges (such as root) can pose significant
security risks if misused. For instance, a vulnerability in such a process
could be leveraged by attackers to compromise the entire system. Therefore,
it is essential to avoid changing the process’s user ID to 0 unless absolutely
necessary and to ensure such usage is thoroughly reviewed and justified.
## Examples
```go linenums="1" hl_lines="9" title="syscall_setuid_0.go"
package main
import (
"fmt"
"log"
"os"
"syscall"
)
func main() {
if err := syscall.Setuid(0); err != nil {
log.Fatalf("Failed to set UID: %v", err)
}
fmt.Printf("Running as UID: %d\n", os.Getuid())
}
```
??? example "Example Output"
```
> precli tests/unit/rules/go/stdlib/os/examples/syscall_setuid_0.go
⛔️ Error on line 16 in tests/unit/rules/go/stdlib/os/examples/syscall_setuid_0.go
GO004: Execution with Unnecessary Privileges
The function 'os.setuid(0)' escalates the process to run with root (superuser) privileges.
```
## Remediation
- Avoid using setuid(0) unless absolutely necessary: Review whether running
as the root user is required for the task at hand. It is safer to operate
with the least privileges necessary.
- Drop privileges as soon as possible: If elevated privileges are required
temporarily, ensure that the process drops those privileges immediately
after performing the necessary tasks.
- Validate input to avoid malicious manipulation: If input parameters control
the user ID passed to setuid(), ensure they are securely validated and not
influenced by untrusted sources.
- Use alternatives to running as root: If feasible, design your application
to avoid needing root privileges entirely. Consider utilizing a dedicated
service or capability that performs the task in a secure, controlled manner.
```go linenums="1" hl_lines="9" title="syscall_setuid_0.go"
package main
import (
"fmt"
"log"
"os"
"syscall"
)
func main() {
if err := syscall.Setuid(500); err != nil {
log.Fatalf("Failed to set UID: %v", err)
}
fmt.Printf("Running as UID: %d\n", os.Getuid())
}
```
## See also
!!! info
- [syscall package - syscall - Go Packages](https://pkg.go.dev/syscall#Setuid)
- [Principle of Least Privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege)
- [CWE-250: Execution with Unnecessary Privileges](https://cwe.mitre.org/data/definitions/250.html)
_New in version 0.6.6_
""" # noqa: E501
from precli.core.call import Call
from precli.core.config import Config
from precli.core.level import Level
from precli.core.location import Location
from precli.core.result import Result
from precli.rules import Rule


class SyscallSetuidRoot(Rule):
def __init__(self, id: str):
super().__init__(
id=id,
name="unnecessary_privileges",
description=__doc__,
cwe_id=250,
message="The function '{0}(0)' escalates the process to run with "
"root (superuser) privileges.",
config=Config(level=Level.ERROR),
)

def analyze_call_expression(
self, context: dict, call: Call
) -> Result | None:
if call.name_qualified != "syscall.Setuid":
return

argument = call.get_argument(position=0, name="uid")
uid = argument.value

if isinstance(uid, int) and uid == 0:
return Result(
rule_id=self.id,
location=Location(node=argument.node),
message=self.message.format(call.name_qualified),
)
2 changes: 1 addition & 1 deletion precli/rules/python/stdlib/os_setuid_root.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
??? example "Example Output"
```
> precli tests/unit/rules/python/stdlib/os/examples/os_setuid_0.py
⚠️ Warning on line 9 in tests/unit/rules/python/stdlib/os/examples/os_setuid_0.py
⛔️ Error on line 9 in tests/unit/rules/python/stdlib/os/examples/os_setuid_0.py
PY038: Execution with Unnecessary Privileges
The function 'os.setuid(0)' escalates the process to run with root (superuser) privileges.
```
Expand Down
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ precli.rules.go =
# precli/rules/go/stdlib/crypto_weak_key.py
GO003 = precli.rules.go.stdlib.crypto_weak_key:WeakKey

# precli/rules/go/stdlib/syscall_setuid_root.py
GO004 = precli.rules.go.stdlib.syscall_setuid_root:SyscallSetuidRoot

precli.rules.java =
# precli/rules/java/stdlib/javax_crypto_weak_cipher.py
JAV001 = precli.rules.java.stdlib.javax_crypto_weak_cipher:WeakCipher
Expand Down
Empty file.
21 changes: 21 additions & 0 deletions tests/unit/rules/go/stdlib/syscall/examples/syscall_setuid_0.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// level: ERROR
// start_line: 16
// end_line: 16
// start_column: 29
// end_column: 30
package main

import (
"fmt"
"log"
"os"
"syscall"
)

func main() {
if err := syscall.Setuid(0); err != nil {
log.Fatalf("Failed to set UID: %v", err)
}

fmt.Printf("Running as UID: %d\n", os.Getuid())
}
17 changes: 17 additions & 0 deletions tests/unit/rules/go/stdlib/syscall/examples/syscall_setuid_500.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// level: NONE
package main

import (
"fmt"
"log"
"os"
"syscall"
)

func main() {
if err := syscall.Setuid(500); err != nil {
log.Fatalf("Failed to set UID: %v", err)
}

fmt.Printf("Running as UID: %d\n", os.Getuid())
}
48 changes: 48 additions & 0 deletions tests/unit/rules/go/stdlib/syscall/test_syscall_setuid_root.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Copyright 2024 Secure Sauce LLC
import os

import pytest

from precli.core.level import Level
from precli.parsers import go
from precli.rules import Rule
from tests.unit.rules import test_case


class TestSyscallSetuidRoot(test_case.TestCase):
@classmethod
def setup_class(cls):
cls.rule_id = "GO004"
cls.parser = go.Go()
cls.base_path = os.path.join(
"tests",
"unit",
"rules",
"go",
"stdlib",
"syscall",
"examples",
)

def test_rule_meta(self):
rule = Rule.get_by_id(self.rule_id)
assert rule.id == self.rule_id
assert rule.name == "unnecessary_privileges"
assert (
rule.help_url
== f"https://docs.securesauce.dev/rules/{self.rule_id}"
)
assert rule.default_config.enabled is True
assert rule.default_config.level == Level.ERROR
assert rule.default_config.rank == -1.0
assert rule.cwe.id == 250

@pytest.mark.parametrize(
"filename",
[
"syscall_setuid_0.go",
"syscall_setuid_500.go",
],
)
def test(self, filename):
self.check(filename)

0 comments on commit 0c683b7

Please sign in to comment.