-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New Golang rule to check for Setuid to root user (#590)
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
Showing
9 changed files
with
225 additions
and
1 deletion.
There are no files selected for viewing
This file contains 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
This file contains 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,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 |
This file contains 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,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), | ||
) |
This file contains 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
This file contains 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
Empty file.
21 changes: 21 additions & 0 deletions
21
tests/unit/rules/go/stdlib/syscall/examples/syscall_setuid_0.go
This file contains 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,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
17
tests/unit/rules/go/stdlib/syscall/examples/syscall_setuid_500.go
This file contains 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,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
48
tests/unit/rules/go/stdlib/syscall/test_syscall_setuid_root.py
This file contains 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,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) |