From fe3f1052d65f34563f5be0831248e70f5b5cca4f Mon Sep 17 00:00:00 2001 From: Mobmaker <45888585+Mobmaker55@users.noreply.github.com> Date: Sun, 7 Aug 2022 18:21:29 -0400 Subject: [PATCH] Adds 4 new user properties to the UserDetail check. (#146) * Adds 4 new user properties to the UserDetail check. Signed-off-by: Mobmaker <45888585+Mobmaker55@users.noreply.github.com> * Add modifier check type Signed-off-by: Mobmaker <45888585+Mobmaker55@users.noreply.github.com> * Updated documentation to match the "modifier" field Signed-off-by: Mobmaker <45888585+Mobmaker55@users.noreply.github.com> * Updated to ensure that users know time should be in UTC Signed-off-by: Mobmaker <45888585+Mobmaker55@users.noreply.github.com> * Removed debug lines Signed-off-by: Mobmaker <45888585+Mobmaker55@users.noreply.github.com> * Removed the modifier field and switched to a comparison value split system. Signed-off-by: Mobmaker <45888585+Mobmaker55@users.noreply.github.com> * Made comparison stricter, reducing edge cases. Signed-off-by: Mobmaker <45888585+Mobmaker55@users.noreply.github.com> * Handled more errors, optimized some code. Signed-off-by: Mobmaker <45888585+Mobmaker55@users.noreply.github.com> * Fixed error handling inversion Signed-off-by: Mobmaker <45888585+Mobmaker55@users.noreply.github.com> * fixed documentational wording Signed-off-by: Mobmaker <45888585+Mobmaker55@users.noreply.github.com> * Removed false positive Signed-off-by: Mobmaker <45888585+Mobmaker55@users.noreply.github.com> --- Makefile | 2 +- checks_windows.go | 72 +++++++++++++++++++++++++++++++++++++++--- docs/checks.md | 9 ++++++ docs/userproperties.md | 30 ++++++++++++++++-- 4 files changed, 105 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 6de72911..43afbd59 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ .DEFAULT_GOAL := all -.SILENT: release-lin +.SILENT: release-lin release-win release all: CGO_ENABLED=0 GOOS=windows go build -ldflags '-s -w' -tags phocus -o ./phocus.exe . && \ diff --git a/checks_windows.go b/checks_windows.go index f8d24074..5e78873a 100644 --- a/checks_windows.go +++ b/checks_windows.go @@ -337,14 +337,78 @@ func (c cond) UserDetail() (bool, error) { c.requireArgs("User", "Key", "Value") c.Value = strings.TrimSpace(c.Value) c.Key = strings.TrimSpace(c.Key) - lookingFor := false - if strings.ToLower(c.Value) == "yes" { - lookingFor = true - } + splitVal := c.Value + lookingFor := strings.ToLower(c.Value) == "yes" user, err := getLocalUser(c.User) if err != nil { return false, err } + if c.Key == "PasswordAge" || c.Key == "BadPasswordCount" || c.Key == "NumberOfLogons" { + var num int + switch c.Key { + case "PasswordAge": + num = int(user.PasswordAge.Hours() / 24) + case "BadPasswordCount": + num = int(user.BadPasswordCount) + case "NumberOfLogons": + num = int(user.NumberOfLogons) + } + if len(c.Value) < 1 { + fail("Invalid value input:", c.Value) + return false, errors.New("invalid c.Value range") + } + var val int + switch c.Value[0] { + case '<': + splitVal = strings.Split(c.Value, "<")[1] + val, err = strconv.Atoi(splitVal) + if err == nil { + return num < val, nil + } + case '>': + splitVal = strings.Split(c.Value, ">")[1] + val, err = strconv.Atoi(splitVal) + if err == nil { + return num > val, nil + } + default: + val, err = strconv.Atoi(splitVal) + if err == nil { + return num == val, nil + } + + } + fail("c.Value not an integer:", val) + return false, err + } + + //Monday, January 02, 2006 3:04:05 PM + if c.Key == "LastLogon" { + lastLogon := user.LastLogon.UTC() + var timeComparison func(time.Time) bool + var timeString string + if len(c.Value) < 2 { + fail("Could not parse date: \"" + c.Value + "\". Correct format is \"Monday, January 02, 2006 3:04:05 PM\" and in UTC time.") + return false, errors.New("invalid c.Value date") + } + switch c.Value[0] { + case '<': + timeString = strings.Split(c.Value, "<")[1] + timeComparison = lastLogon.Before + case '>': + timeString = strings.Split(c.Value, ">")[1] + timeComparison = lastLogon.After + default: + timeComparison = lastLogon.Equal + } + parse, err := time.Parse("Monday, January 02, 2006 3:04:05 PM", timeString) + if err != nil { + fail("Could not parse date: \"" + c.Value + "\". Correct format is \"Monday, January 02, 2006 3:04:05 PM\" and in UTC time.") + return false, err + } + return timeComparison(parse), nil + } + switch c.Key { case "FullName": if user.FullName == c.Value { diff --git a/docs/checks.md b/docs/checks.md index 8a100bb2..f7b7834d 100644 --- a/docs/checks.md +++ b/docs/checks.md @@ -320,6 +320,15 @@ 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. +> This is specified in the above property document. +``` +type = 'UserDetail' +user = 'Administrator' +key = 'PasswordAge' +value = '>90' +``` + **UserRights**: pass if specified user or group has specified privilege diff --git a/docs/userproperties.md b/docs/userproperties.md index 0843fb7f..2b74fcb7 100644 --- a/docs/userproperties.md +++ b/docs/userproperties.md @@ -2,11 +2,11 @@ ## Windows User Properties -These are the user properties that you would normally find the the user edit dialog. +These are the user properties that you would normally find the user edit dialog. (Plus some extra goodies!) -**Usernames are case sensitive.** `Yes`/`No` are not case sensitive. If you input something other than "yes" or "no" for a boolean check, it defaults to "no". +**Usernames are case-sensitive.** `Yes`/`No` are not case-sensitive. If you input something other than "yes" or "no" for a boolean check, it defaults to "no". -- `FullName`: Full name of user (case sensitive) +- `FullName`: Full name of user (case-sensitive) - `IsEnabled`: Yes or No - This returns for whether the account is **disabled**. - `IsLocked`: Yes or No @@ -18,3 +18,27 @@ These are the user properties that you would normally find the the user edit dia - Password does not expire. - `NoPasswordChange`: Yes or No - Password cannot be changed. + +For these checks, you can specify comparison through less, greater, and equal to through adding characters to the beginning of the value field. +This character **must** be added as the first character of the value field, as shown below. +>`<[value]`: The property is less than the `value` field. +> +>`>[value]`: The property is greater than the `value` field. +> +>`[value]`: The property is equal to the `value` field. This is the default. +- `PasswordAge`: Number of Days (e.g. "7") + - How old is the password? +- `BadPasswordCount`: Number of incorrect passwords (e.g. "3") + - How many incorrect passwords have been entered? +- `NumberOfLogons`: Number of total logons (e.g. "4") + - How many times has the user logged on? + +For the `LastLogonTime` property: +> `<[value]`: The property is before the `value` field's date. +> +> `>[value]`: The property is after the `value` field's date. +> +> `[value]`: The property is equal to the `value` field's date. This is the default. +- `LastLogonTime`: Date in format: Monday, January 02, 2006 3:04:05 PM + - If the date is not in that exact format, the check will fail. + - Time **must** be in UTC to pass.