diff --git a/docs/rules.md b/docs/rules.md index 6b26b7f0..3506ec2a 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -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 diff --git a/docs/rules/go/stdlib/syscall-aetuid-root.md b/docs/rules/go/stdlib/syscall-aetuid-root.md new file mode 100644 index 00000000..b19d3a60 --- /dev/null +++ b/docs/rules/go/stdlib/syscall-aetuid-root.md @@ -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 diff --git a/precli/rules/go/stdlib/syscall_setuid_root.py b/precli/rules/go/stdlib/syscall_setuid_root.py new file mode 100644 index 00000000..91865cb7 --- /dev/null +++ b/precli/rules/go/stdlib/syscall_setuid_root.py @@ -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), + ) diff --git a/precli/rules/python/stdlib/os_setuid_root.py b/precli/rules/python/stdlib/os_setuid_root.py index 110875ba..cad6cc93 100644 --- a/precli/rules/python/stdlib/os_setuid_root.py +++ b/precli/rules/python/stdlib/os_setuid_root.py @@ -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. ``` diff --git a/setup.cfg b/setup.cfg index 66d9d36e..2300996a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -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 diff --git a/tests/unit/rules/go/stdlib/syscall/__init__.py b/tests/unit/rules/go/stdlib/syscall/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/rules/go/stdlib/syscall/examples/syscall_setuid_0.go b/tests/unit/rules/go/stdlib/syscall/examples/syscall_setuid_0.go new file mode 100644 index 00000000..98bd4533 --- /dev/null +++ b/tests/unit/rules/go/stdlib/syscall/examples/syscall_setuid_0.go @@ -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()) +} diff --git a/tests/unit/rules/go/stdlib/syscall/examples/syscall_setuid_500.go b/tests/unit/rules/go/stdlib/syscall/examples/syscall_setuid_500.go new file mode 100644 index 00000000..4b60f217 --- /dev/null +++ b/tests/unit/rules/go/stdlib/syscall/examples/syscall_setuid_500.go @@ -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()) +} diff --git a/tests/unit/rules/go/stdlib/syscall/test_syscall_setuid_root.py b/tests/unit/rules/go/stdlib/syscall/test_syscall_setuid_root.py new file mode 100644 index 00000000..fab0c2c4 --- /dev/null +++ b/tests/unit/rules/go/stdlib/syscall/test_syscall_setuid_root.py @@ -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)