From e66cfd5db16b1d1e9bd53dc1c469e247762dbf66 Mon Sep 17 00:00:00 2001 From: sourque Date: Wed, 28 Dec 2022 01:37:33 -0800 Subject: [PATCH] Add firewalld/dnf-automatic/RPM support for Fedora (#160) * Add firewalld/dnf-automatic support for Fedora * RPM based packages for ProgramInstalled * Fixed previous commit Co-authored-by: Akshay Rohatgi <52616034+Akshay-Rohatgi@users.noreply.github.com> --- README.md | 27 ++++++++++++++------------- checks_linux.go | 30 +++++++++++++++++++++++++++--- docs/checks.md | 38 +++++++++++++++++++------------------- 3 files changed, 60 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index aaf9ece7..5504f9bc 100644 --- a/README.md +++ b/README.md @@ -105,17 +105,17 @@ Set the `remote` field in the configuration, and your image will use remote scor The configuration is written in TOML. Here is a minimal example: ```toml -name = "ubuntu-18-supercool" # Image name +name = "ubuntu-18-supercool" # Image name title = "CoolCyberStuff Practice Round" # Round title -os = "Ubuntu 18.04" # OS, used for README -user = "coolUser" # Main user for the image +os = "Ubuntu 18.04" # OS, used for README +user = "coolUser" # Main user for the image # Set the aeacus version of this scoring file. Set this to the version # of aeacus you are using. This is used to make sure your configuration, # if re-used, is compatible with the version of aeacus being used. # # You can print your version of aeacus with ./aeacus version. -version = "2.0.0" +version = "2.0.5" [[check]] message = "Removed insecure sudoers rule" @@ -131,24 +131,25 @@ points = 10 points = 20 [[check.pass]] - type = "FileExistsNot" + type = "PathExistsNot" path = "/usr/bin/ufw-backdoor" - [[check.pass]] # You can code multiple pass conditions, but - type = "Command" # they must ALL succeed for the check to pass! - cmd = "ufw status" + [[check.pass]] # You can code multiple pass conditions, but + type = "FirewallUp" # they must ALL succeed for the check to pass! [[check]] message = "Malicious user 'user' can't read /etc/shadow" # If no points are specified, they are auto-calculated out of 100. [[check.pass]] - type = "CommandNot" - cmd = "sudo -u user cat /etc/shadow" + type = "PermissionIsNot" + path = "/etc/shadow" + value = "??????r??" [[check.pass]] # "pass" conditions are logically AND with other pass - type = "FileExists" # conditions. This means they all must pass for a check - path = "/etc/shadow" # to be considered successful. + type = "UserInGroupNot" # conditions. This means they all must pass for a check + user = "user" # to be considered successful. + group = "sudo" [[check.passoverride]] # If you want a check to succeed when any condition type = "UserExistsNot" # passes, regardless of other pass checks, use @@ -156,7 +157,7 @@ message = "Malicious user 'user' can't read /etc/shadow" # passoverride is overridden by fail conditions. [[check.fail]] # If any fail conditions succeed, the entire check will fail. - type = "FileExistsNot" + type = "PathExistsNot" path = "/etc/shadow" [[check]] diff --git a/checks_linux.go b/checks_linux.go index 33c5084f..963af14d 100644 --- a/checks_linux.go +++ b/checks_linux.go @@ -10,10 +10,18 @@ import ( ) func (c cond) AutoCheckUpdatesEnabled() (bool, error) { - return cond{ + result, err := cond{ Path: "/etc/apt/apt.conf.d/", Value: `(?i)^\s*APT::Periodic::Update-Package-Lists\s+"1"\s*;\s*$`, }.DirContains() + // If /etc/apt/ does not exist, try dnf (RHEL) + if err != nil { + return cond{ + Path: "/etc/dnf/", + Value: `(?i)^\s*download_updates\s+=\s+yes$`, + }.DirContains() + } + return result, err } // Command checks if a given shell command ran successfully (that is, did not @@ -38,10 +46,17 @@ func (c cond) Command() (bool, error) { } func (c cond) FirewallUp() (bool, error) { - return cond{ + result, err := cond{ Path: "/etc/ufw/ufw.conf", Value: `^\s*ENABLED=yes\s*$`, }.FileContains() + if err != nil { + // If ufw.conf does not exist, check firewalld status (RHEL) + return cond{ + Name: "firewalld", + }.ServiceUp() + } + return result, err } func (c cond) GuestDisabledLDM() (bool, error) { @@ -163,9 +178,18 @@ func (c cond) PermissionIs() (bool, error) { func (c cond) ProgramInstalled() (bool, error) { c.requireArgs("Name") - return cond{ + result, err := cond{ Cmd: "dpkg -s " + c.Name, }.Command() + + // If dpkg fails, use rpm + if err != nil { + return cond{ + Cmd: "rpm -q " + c.Name, + }.Command() + } + + return result, err } func (c cond) ProgramVersion() (bool, error) { diff --git a/docs/checks.md b/docs/checks.md index 26a338ef..853173ea 100644 --- a/docs/checks.md +++ b/docs/checks.md @@ -4,13 +4,12 @@ This is a list of vulnerability checks that can be used in the configuration for `aeacus`. The notes on this page contain a lot of important information, please be sure to read them. -> **Note!** Each of the commands here can check for the opposite by appending 'Not' to the check type. For example, `PathExistsNot` to pass if a file does not exist. +> **Note**: Each of the commands here can check for the opposite by appending 'Not' to the check type. For example, `PathExistsNot` to pass if a file does not exist. -> **Note!** If a check has negative points assigned to it, it automatically becomes a penalty. +> **Note**: If a check has negative points assigned to it, it automatically becomes a penalty. -> **Note!** Each of these check types can be used for `Pass`, `PassOverride` or `Fail` conditions, and there can be multiple conditions per check. See [configuration](config.md) for more details. +> **Note**: Each of these check types can be used for `Pass`, `PassOverride` or `Fail` conditions, and there can be multiple conditions per check. See [configuration](config.md) for more details. -> Note: `Command*` checks are prone to interception, modification, and tomfoolery. Your scoring configuration will be much more robust if you rely on checks using native mechanisms rather than shell commands (for example, `PathExists` instead of ls). **CommandContains**: pass if command output contains string. If executing the command fails (the check returns an error), check never passes. Use of this check is discouraged. @@ -20,7 +19,8 @@ cmd = 'ufw status' value = 'Status: active' ``` -> **Note!** If any check returns an error (e.g., something that it was not expecting), it will _never_ pass, even if it's a `Not` condition. This varies by check, but for example, if you try to check the content of a file that doesn't exist, it will return an error and not succeed-- even if you were doing `FileContainsNot`. +> **Note**: `Command*` checks are prone to interception, modification, and tomfoolery. Your scoring configuration will be much more robust if you rely on checks using native mechanisms rather than shell commands (for example, `PathExists` instead of ls). +> **Note**: If any check returns an error (e.g., something that it was not expecting), it will _never_ pass, even if it's a `Not` condition. This varies by check, but for example, if you try to check the content of a file that doesn't exist, it will return an error and not succeed-- even if you were doing `FileContainsNot`. **CommandOutput**: pass if command output matches string exactly. If it returns an error, check never passes. Use of this check is discouraged. @@ -32,7 +32,7 @@ value = 'True' **DirContains**: pass if directory contains regular expression (regex) string -> **Note!** Read more about regex [here](regex.md). +> **Note**: Read more about regex [here](regex.md). ``` type = 'DirContains' @@ -43,11 +43,11 @@ value = 'NOPASSWD' > `DirContains` is recursive! This means it checks every folder and subfolder. It currently is capped at 10,000 files, so you should begin your search at the deepest folder possible. -> **Note!** You don't have to escape any characters because we're using single quotes, which are literal strings in TOML. If you need use single quotes, use a TOML multi-line string literal `''' like this! that's neat! C:\path\here '''`), or just normal quotes (but you'll have to escape characters with those). +> **Note**: You don't have to escape any characters because we're using single quotes, which are literal strings in TOML. If you need use single quotes, use a TOML multi-line string literal `''' like this! that's neat! C:\path\here '''`), or just normal quotes (but you'll have to escape characters with those). **FileContains**: pass if file contains regex -> Note: `FileContains` will never pass if file does not exist! Add an additional PassOverride check for PathExistsNot, if you want to score that a file does not contain a line, OR it doesn't exist. +> **Note**: `FileContains` will never pass if file does not exist! Add an additional PassOverride check for PathExistsNot, if you want to score that a file does not contain a line, OR it doesn't exist. ``` type = 'FileContains' @@ -86,7 +86,7 @@ name = 'root' type = 'FirewallUp' ``` -> **Note**: On Linux, only `ufw` is supported (checks `/etc/ufw/ufw.conf`). On Window, this passes if all three Windows Firewall profiles are active. +> **Note**: On Linux, `ufw` (checks `/etc/ufw/ufw.conf`) and `firewalld` are supported. If the `ufw` config does not exist, the engine checks if `firewalld` is running. On Window, this passes if all three Windows Firewall profiles are active. **PasswordChanged**: pass if user password has changed @@ -150,9 +150,9 @@ name = 'BUILTIN\Administrators' value = 'FullControl' ``` -> **Note!**: Use absolute paths when possible (rather than relative) for more reliable scoring. +> **Note**: Use absolute paths when possible (rather than relative) for more reliable scoring. -**ProgramInstalled**: pass if program is installed. On Linux, will use `dpkg`, and on Windows, checks if any installed programs contain your program string. +**ProgramInstalled**: pass if program is installed. On Linux, will use `dpkg` (or `rpm` for RHEL-based systems), and on Windows, checks if any installed programs contain your program string. ``` type = 'ProgramInstalled' @@ -223,7 +223,7 @@ group = 'Administrators' ### Linux-Specific Checks -**AutoCheckUpdatesEnabled**: pass if the system is configured to automatically check for updates +**AutoCheckUpdatesEnabled**: pass if the system is configured to automatically check for updates (supports `apt` and `dnf-automatic`) ``` type = 'AutoCheckUpdatesEnabled' @@ -286,7 +286,7 @@ key = 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\DisableCAD value = '0' ``` -> Note: This check will never pass if retrieving the key fails (wrong hive, key doesn't exist, etc). If you want to check that a key was deleted, use `RegistryKeyExists`. +> **Note**: This check will never pass if retrieving the key fails (wrong hive, key doesn't exist, etc). If you want to check that a key was deleted, use `RegistryKeyExists`. > **Administrative Templates**: There are 4000+ admin template fields. See [this list of registry keys and descriptions](https://docs.google.com/spreadsheets/d/1N7uuke4Jg1R9FBhj8o5dxJQtEntQlea0McYz5upaiTk/edit?usp=sharing), then use the `RegistryKey` or `RegistryKeyExists` check. @@ -297,9 +297,9 @@ type = 'RegistryKeyExists' key = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\DisableCAD' ``` -> **Note!**: Notice the single quotes `'` on the above argument! This means it's a _string literal_ in TOML. If you don't do this, you have to make sure to escape your slashes (`\` --> `\\`) +> **Note**: Notice the single quotes `'` on the above argument! This means it's a _string literal_ in TOML. If you don't do this, you have to make sure to escape your slashes (`\` --> `\\`) -> Note: You can use `SOFTWARE` as a shortcut for `HKEY_LOCAL_MACHINE\SOFTWARE`. +> **Note**: You can use `SOFTWARE` as a shortcut for `HKEY_LOCAL_MACHINE\SOFTWARE`. **ScheduledTaskExists**: pass if scheduled task exists @@ -343,12 +343,12 @@ type = 'ShareExists' name = 'ADMIN$' ``` -> **Note!** Don't use any single quotes (`'`) in your parameters for Windows options like this. If you need to, use a double-quoted string instead (ex. `"Admin's files"`) +> **Note**: Don't use any single quotes (`'`) in your parameters for Windows options like this. If you need to, use a double-quoted string instead (ex. `"Admin's files"`) **UserDetail**: pass if user detail key is equal to value -> **Note!** The valid boolean values for this command (when the field is only True or False) are 'yes', if you want the value to be true, or literally anything else for false (like 'no'). +> **Note**: The valid boolean values for this command (when the field is only True or False) are 'yes', if you want the value to be true, or literally anything else for false (like 'no'). ``` type = 'UserDetailNot' @@ -359,7 +359,7 @@ value = 'No' > See [here](userproperties.md) for all `UserDetail` properties. -> **Note!** For non-boolean details, you can use modifiers in the value field to specify the comparison. +> **Note**: For non-boolean details, you can use modifiers in the value field to specify the comparison. > This is specified in the above property document. ``` type = 'UserDetail' @@ -387,4 +387,4 @@ type = 'WindowsFeature' name = 'SMB1Protocol' ``` -> **Note:** Use the PowerShell tool `Get-WindowsOptionalFeature -Online` to find the feature you want! +> **Note**: Use the PowerShell tool `Get-WindowsOptionalFeature -Online` to find the feature you want!