XYZ: Online Manual

← Back to the project page

NAME

xyz - check for, and correct, common configuration faults

SYNOPSIS

xyz [-e PATTERN] [-r FILE] check all|PATTERN...
xyz [-e PATTERN] [-p|-n|-y] fix all|PATTERN...
xyz checks
xyz ignore ITEM REASON
xyz reinstate ITEM...
xyz ignored
xyz exception-add ITEM VALUE
xyz exception-remove ITEM VALUE
xyz exceptions [ITEM]
xyz unavailable
xyz [-c] reformat
xyz -h|-V

DESCRIPTION

xyz checks for common configuration faults that could cause sensitive information or interfaces to be exposed, such as SSH private keys or GPG secret keys without passphrases, or service accounts without a password. For some types of fault, xyz may offer an automated fix.

Many of the 150+ checks are derived from recommendations in Center for Internet Security (https://www.cisecurity.org/) benchmark documents. Run “xyz checks | grep -B 3 'Derived from:'” for details. Note that xyz is intended for use on multiple operating systems - various GNU/Linux distributions, FreeBSD, and OpenBSD - which means that these checks are derived from standard recommendations rather than equivalent to them. xyz is not associated with or endorsed by CIS or any other organisation.

xyz is intended as a risk reduction tool to limit accidental exposure: ideally it should first be run before a server is placed into a production environment, and run regularly thereafter to check that mistakes have not crept in during day-to-day maintenance. It does not look for indicators of compromise and should not be run after an incident. A compromised server should be deleted and rebuilt, and xyz run on the rebuilt system as part of hardening before deployment.

User-defined check and fix actions can be added - see the EXTENSIONS section for details. By packaging and deploying your own check, fix, and hook functions, embodying your estate's configuration policies, xyz can be extended to serve as a configuration policy compliance tool.

Operators will commonly use xyz's ignore action to prevent checks from reporting about faults that are a consequence of deliberate decisions. So that future operators can understand what those decisions were, a human-readable reason must be given to each ignore directive, which are then reportable with “xyz ignored”. For example, when building a web server, the operator should silence warnings about port 80 being reachable:

xyz ignore net_portreachable_80 This is a web server.

The reasons can then be re-assessed in future - for example in future this server may no longer provide web services, and at that point, “xyz reinstate” can be used to stop ignoring the relevant checks.

Fault reports from “xyz check” are intended to be machine-readable, so they can be consumed by an endpoint management system or a shell script. To make them easier to read, pipe them through “xyz reformat.

OPTIONS

These options must come before the actions and arguments.

-e, --exclude PATTERN

For check and fix actions, exclude items matching PATTERN. This option can be specified more than once.

-r, --report FILE

For the check action, instead of writing a report to standard output, atomically replace FILE with the report, so that a monitoring tool such as Zabbix can be used to raise an alarm if the file is not empty.

-p, --prompt

For the fix action, prompt for confirmation before fixing higher-risk items. This is the default if standard input is a terminal.

-n, --no

For the fix action, do not fix higher-risk items, and do not prompt. This is the default if standard input is not a terminal.

-y, --yes

For the fix action, fix higher-risk items without prompting for confirmation.

-c, --colour

For the reformat action, include colour in the output instead of keeping it plain text.

-R, --root DIR

For the purposes of checks, behave as if DIR was the root directory. This can be used to run tests on a representative copy of parts of a system. Note that not all fixes may honour DIR, and so this option is recommended only for use with the check action.

-C, --component-dir DIR

Look for the component parts of xyz under DIR instead of the system default directory. Not generally used in production.

-E, --extension-dir DIR

Look for the user extensions to xyz under DIR instead of the system default directory. Not generally used in production.

-H, --hook-dir DIR

Look for the user begin and end hook scripts under DIR instead of the system default directory. Not generally used in production.

-h, --help

Print a usage message on standard output and exit successfully.

-V, --version

Print version information on standard output and exit successfully.

ACTIONS

check PATTERN...

Run the checks matching the supplied glob patterns, in alphanumeric order. Any faults found are reported on standard output (or written to a file with -r) in the format described below. The exit status will be the number of faults found, capped at 124. The special pattern “all” can be used to specify that all checks should be run.

fix PATTERN...

Run the fixes matching the supplied glob patterns in alphanumeric order. For each fix, the check is automatically run first and the fix will only be executed if the check finds a fault that needs to be fixed. Nothing will be output, and the exit status will be zero, unless there are errors. As with the check action, the special pattern “all” can be used to specify that all possible fixes should be run.

Higher-risk fixes will be skipped if standard input is not a terminal and -y was not passed, or if the operator does not answer the prompt with a word starting with “y”.

checks

List all available checks (not counting any ignored items).

ignore ITEM REASON

Store a marker for ITEM which causes future check and fix actions to skip it. The REASON should be a human-readable sentence describing why this ITEM is being ignored.

reinstate ITEM...

Remove the ignore marker for each specified ITEM so that these items will be included in check and fix actions once more.

ignored

List all ignored check/fix items.

exception-add ITEM VALUE

Add an exception which causes the ITEM check to not alert on a value of VALUE, so that it stops being flagged as a fault. This generally only makes sense for checks that may match multiple paths or users. For example, if the ri_privkey_ssh_identity_unencrypted check reports that root has an unencrypted private key but you accept the risk because this server needs to be able to transfer files unattended, “xyz exception-add ri_privkey_ssh_identity_unencrypted /root/.ssh/id_dsa” would stop the check action from alerting about that particular file. Compare this with the ignore action, which stops a check from running at all.

exception-remove ITEM VALUE

Remove the exception allowing VALUE to be excluded from fault conditions for ITEM. This is the reverse of the action above.

exceptions [ITEM]

List the exceptions for all items, or for just the specified ITEM.

unavailable

List all check/fix items which are unavailable due to missing prerequisites.

reformat

Read a fault report from “xyz check” on standard input, and produce a version on standard output that is easier to read.

Fault report format

The check action will write one report line per fault found, of the form FLAG ITEM PROBLEM ACTIONS. Each line starts with a single-letter flag, then a tab, the name of the check item, a tab, a human-readable problem description, another tab, and a description of the actions required to fix this fault; if there is more than one action required for a single fault, they will be on one line, separated by "|" characters.

The report flags are:

a

This fault can be corrected automatically with the fix action.

R

This fault can be corrected automatically with the fix action, but may be considered risky or could interrupt service. Each item marked this way will prompt for confirmation when the fix action is run, unless the -y option is passed.

m

This fault must be corrected manually.

If there are no faults, xyz check does not produce any output.

Pipe a report through “xyz reformat” to make it easier to read.

CHECK ITEMS

The following checks are provided by default.

Items prefixed with “ri_” are considered to be more resource intensive, so they may impact system performance if run too often (e.g. run them daily rather than hourly).

Not all items may be available on all systems. Use “xyz unavailable” to show which items are unavailable and why.

Each item in this list is suffixed with the possible flags indicating the types of fix available (see above).

ri_privkey_ssh_identity_exposed (m)

Check all home directories for SSH private keys which have no passphrase and which may be readable to other non-root users.

ri_privkey_ssh_identity_unencrypted (m)

Check all home directories for SSH private keys which have no passphrase, regardless of their file permissions.

ri_privkey_gpg_secret_key_unencrypted (m)

Check all home directories for GPG secret keys which have no passphrase.

ri_acct_user_home_dangerousdotfiles (R)

Check that all non-system users do not have .forward, .rhosts, or .netrc files in their home directories.

ri_acct_user_home_dotfilepermissions (R)

Check that the config files (starting with ".") of all non-system users have the correct ownership and permissions - owned by their user's primary UID and GID, not writable by group or other, and for .bash_history or .netrc, not readable by group or other.

ri_acct_user_home_exists (m)

Check that all non-system users have a home directory that exists.

ri_acct_user_home_globalwrite (R)

Check that all non-system users have a home directory that does not have global write permission.

ri_acct_user_home_groupwrite (R)

Check that all non-system users have a home directory that does not have group write permission.

ri_acct_user_home_owner (R)

Check that all non-system users have a home directory that they own.

acct_shadowed (m)

Check that all local accounts are shadowed, meaning that their password hash is not stored in /etc/passwd.

acct_uid_0 (m)

Check that only one account has UID 0 (or two, on FreeBSD where the toor account also exists).

acct_user_with_gid_0 (m)

Check that only one account has GID 0 (or two, on FreeBSD where the toor account also exists).

acct_gid_0 (m)

Check that only one group has GID 0.

acct_sys_static_locked (R)

On systems where /etc/login.defs has the concept of SYS_UID_MIN, check that all local accounts other than root with a UID less than SYS_UID_MIN have locked passwords and so cannot use password authentication. This typically includes accounts such as daemon, bin, lp, and so on.

acct_sys_dynamic_locked (R)

On systems where /etc/login.defs has the concept of SYS_UID_MAX, check that all local accounts whose UID is between SYS_UID_MIN and SYS_UID_MAX have locked passwords and so cannot use password authentication. This typically includes accounts such as apache, sshd, _rpc, and so on.

acct_guest_locked (R)

If guest accounts (guest, pcguest) exist, check they have locked passwords and so cannot use password authentication.

acct_root_password_not_empty (m)

Check that the root account has a password or is locked.

acct_passwords_not_empty (m)

Check that no accounts have empty passwords.

acct_passwd_groups_defined (m)

Check that all accounts have a group ID which is defined in /etc/group.

acct_shadow_group_empty (m)

On systems with a “shadow” group, check that it has no members, and that no accounts have shadow as their primary group.

acct_passwd_duplicate_uid (m)

Check that /etc/passwd does not contain the same UID twice (ignoring UID 0, which is covered by acct_uid_0).

acct_group_duplicate_gid (m)

Check that /etc/group does not contain the same GID twice (ignoring GID 0, which is covered by acct_gid_0).

acct_passwd_duplicate_name (m)

Check that /etc/passwd does not contain the same account name twice.

acct_group_duplicate_name (m)

Check that /etc/group does not contain the same group name twice.

fs_DIR_ownvolume (m)

Check that directory DIR is on its own volume, not part of another filesystem, where DIR is one of /tmp, /dev/shm, /home, /var, /var/tmp, /var/log, /var/log/audit. In the item name, / characters are omitted.

fs_DIR_nodev (m)

Check that directory DIR, if it is on its own volume, is mounted with the nodev mount option. Repeated for each DIR listed above.

fs_DIR_nosuid (m)

Check that directory DIR, if it is on its own volume, is mounted with the nosuid mount option. Repeated for each DIR listed above.

fs_DIR_noexec (m)

Check that directory DIR, if it is on its own volume, is mounted with the noexec mount option. Repeated for each DIR listed above except for /home and /var.

infoleak_content_FILE (m)

Check that file FILE under /etc does not leak information about the system by mentioning the operating system name or by using any getty(8) style escape sequences that expand to system name, release, and so on. The FILE can be one of motd, issue, or issue.net. In the item name, "." characters are omitted.

infoleak_owner_FILE (R)

Check that file FILE under /etc is owned by the correct user so it cannot easily be modified to mislead other users. Repeated for each FILE listed above.

infoleak_permissions_FILE (R)

Check that file FILE under /etc is only writable or executable by its owner, and nobody else, so it cannot easily be modified to mislead other users. Repeated for each FILE listed above.

net_portreachable_PORT (m)

Check whether network port PORT is listening on anything other than a local interface, meaning that it is reachable from elsewhere on the network. This does not take any host-based firewalls into account. Use the ignore action to exclude specific ports on systems which are deliberately set up to provide services on those ports. This check is useful to highlight services that have been accidentally left switched on, or cases such as local mail delivery where a service should normally listen only locally.

rights_PATH_owning_group (a)

Check that PATH is owned by the correct group (usually root or wheel). The PATH is one of /tmp, /etc/crontab, /etc/cron.hourly, /etc/cron.daily, /etc/cron.weekly, /etc/cron.monthly, /etc/cron.d, /etc/passwd, /etc/passwd-, /etc/group, /etc/group-, /etc/shadow, /etc/shadow-, /etc/gshadow, /etc/gshadow-, /etc/shells, or /etc/security/opasswd. In the item name, / and . characters are omitted, and - is replaced with "dash". The pseudo paths “auditd_config”, “auditd_logdir”, and “auditd_logfile” are also included, covering the audit daemon's configuration files, log directory, and configuration files.

rights_PATH_owning_user (a)

Check that PATH is owned by the correct user (usually root). This is repeated for each PATH listed above.

rights_PATH_permissions (a)

Check that PATH has the correct file permissions, such as 600 for /etc/crontab or 1777 for /tmp. This is repeated for each PATH listed above.

rights_sshd_config (R)

Check that the SSH daemon configuration files have the correct ownership and file permissions - generally unreadable by anyone other than root.

rights_sshd_hostkeys_KEYTYPE_CHECK (R)

Check that the SSH host keys have the correct ownership and file permissions, where KEYTYPE is either "public" or "private", and CHECK is one of "owning_user", "owning_group", or "permissions".

EXIT STATUS

0

No faults found (check) or, for all other actions, no errors occurred.

1-124

At least one fault was found when running the check action.

125

An error occurred.

126

The arguments were not accepted - an unknown option, action, or item was specified.

Note that specifying an item that is defined, but is currently ignored or unavailable, is not treated as an error - the item will just be skipped.

FILES

/etc/xyz/*.exception

Exceptions for each item.

/etc/xyz/*.ignore

Marker files indicating that specific check/fix items should be ignored on this system.

/usr/libexec/xyz/extensions/*.sh

Scripts defining new check and fix items.

/usr/libexec/xyz/hooks/begin/*.sh

Scripts to run before an action is performed.

/usr/libexec/xyz/hooks/end/*.sh

Scripts to run after an action is completed.

EXAMPLES

Run this daily to generate a report file containing the results of resource-intensive checks, which will be empty if there are no problems:

xyz -r /var/spool/xyz-daily.txt check "ri_*"

Run this hourly to generate a different report file containing the results of all other checks, which will be empty if there are no problems:

xyz -e "ri_*" -r /var/spool/xyz-hourly.txt check all

You could then use a monitoring system like Zabbix to raise an alert if either file ever has a non-zero size.

Alternatively, run something like this to email the daily file to root:

xyz -r /var/spool/xyz-daily.txt check "ri_*"
test -s /var/spool/xyz-daily.txt \
&& xyz reformat < /var/spool/xyz-daily.txt \
| mail -s "Security faults detected on $HOSTNAME" root

For hourly checks, email is not recommended. Polling the size of the report with a monitoring agent like Zabbix is more flexible and scales better.

EXTENSIONS

New checks and fixes can be defined by placing shell scripts in the extensions directory, which is usually /usr/libexec/xyz/extensions/.

All files ending in “.sh” will be loaded by xyz as shell script sources; these files are expected to define functions named check_ITEM and fix_ITEM, which perform the check and fix actions for an item ITEM. If there is no possible fix for an item, its fix_ function does not need to be defined.

Each file must call the registerItem function for every item it defines - see the “Functions available at load time” section below.

Items are always processed in alphanumeric order, so bear this in mind when naming new items.

All extension scripts must be written in Bourne shell (not bash, ksh, csh, etc).

Checks should prefix all paths referring to system files and directories with ${rootDir}, such as “${rootDir}/etc/passwd”.

Extensions can be included in standardised configuration deployed to all systems using whatever management mechanism is appropriate for your estate.

Each check function must output nothing, and must return one of these values:

${XYZ_RC_NOFAULT} (0)

No fault found.

${XYZ_RC_FAULT_LOWRISK} (1)

A fault was found with a low-risk automatic fix ("a").

${XYZ_RC_FAULT_HIGHRISK} (2)

A fault was found with a high-risk automatic fix ("R").

${XYZ_RC_FAULT_NOFIX} (3)

A fault was found for which there is no automatic fix ("m").

When the function returns with a non-zero value, it must have set these variables:

faultDescription

A human-readable description of the problem.

fixActions

A list of actions required to fix the problem, separated by "|" or by newlines.

Each fix function should return 0 on success (and output nothing), or any non-zero value on error (and report the error with reportError).

Functions available at load time

Extensions can call the following functions at any time.

registerItem ITEM DESCRIPTION DERIVEDFROM [PREREQUISITE...]

Register a check item. The ITEM is the item name (so you must also define a function named check_ITEM, and optionally, also define a function named fix_ITEM for the associated automated fix); the DESCRIPTION is a short description of this check; if this item was derived from anything, such as being related to any standards, describe them in DERIVEDFROM, otherwise that string should be empty; and if the item's check or fix requires any specific commands, each should be given as a PREREQUISITE. If any of the prerequisites are not discoverable with “command -v” then the item will be treated as unavailable and will be shown in the “xyz unavailable” list.

reportError MESSAGE

Write MESSAGE to the standard error stream, prefixed with the program name and a colon.

Variables available at load time

Extensions can read the following shell variables at any time.

rootDir

The directory to prefix all system paths (like /etc/passwd) with. This variable is usually empty unless the -R option was passed.

workDir

The name of a temporary directory which will be deleted when xyz exits, so can be used for scratch space.

osId

A lower-case string indicating the operating system type, such as "linux", "debian", "almalinux", "openbsd", "freebsd", taken from ID in /etc/os-release if possible, other files if appropriate, or falling back to “uname -s”.

osVersion

A lower-case string indicating the operating system version, excluding any release code name, such as "15.3" or "9", taken from VERSION_ID in /etc/os-release if possible, other files if appropriate, or falling back to “uname -r” (stopping at the first “-”).

osIsRhelDescendant

The string "no" or "yes" depending on whether this operating system appears to be a Linux distribution equivalent to or descended from Red Hat Enterprise Linux.

XYZ_RC_NOFAULT

The return code to use in a check function if no fault was found (0).

XYZ_RC_FAULT_LOWRISK

The return code to use in a check function if a fault was found for which there is a low-risk automatic fix (1 = "a").

XYZ_RC_FAULT_HIGHRISK

The return code to use in a check function if a fault was found for which there is a high-risk automatic fix (2 = "R").

XYZ_RC_FAULT_NOFIX

The return code to use in a check function if a fault was found for which there is no automatic fix (3 = "m").

action

The selected action.

selectedItems

A space-separated list of the items applicable to the action, after expanding patterns in the command line arguments and excluding ignored or unavailable items. This is not available in the "begin" hook.

reportFile

The filename to write a check report to, or empty if writing to standard output.

excludePattern

A newline-separated list of glob patterns - any items matching these patterns will be ignored.

runHighRiskFixes

Whether to run high-risk fixes - one of "prompt", "yes", or "no".

Functions available at run time

Extensions can call the following functions after loading is complete. This means that they should only be called from inside check or fix functions, or in an “end” hook. The functions listed in the “Functions available at load time” section earlier are also still available.

stripExceptions FILE

Remove any exceptions for the current item, that had been added with exception-add, from the file FILE. This should be called in check functions between generating a list of possible faults and acting on that list.

fsOwnerUid PATH

Output the owning user ID of PATH, or nothing on error.

fsOwnerAccountName PATH

Output the owning user name of PATH, or nothing on error.

fsOwnerGid PATH

Output the owning group ID of PATH, or nothing on error.

fsOwnerGroupName PATH

Output the owning group name of PATH, or nothing on error.

fsFileMode PATH

Output the last 4 digits of the octal file mode of PATH, or nothing on error.

fsOtherCanRead PATH

Return true (0) if PATH is readable to other, i.e. the last octet of the file mode is at least 4.

fsGroupOrOtherCanRead PATH

Return true (0) if PATH is readable to either group or other, i.e. either of the last 2 octets of the file mode are at least 4.

fsGroupAndOtherCanRead PATH

Return true (0) if PATH is readable to both group and other, i.e. the last 2 octets of the file mode are at least 44.

fsGroupOrOtherCanDoMoreThanRead PATH

Return true (0) if PATH can be written to or executed by either group or other.

fsHasExecutePermissions PATH

Return true (0) if PATH has any executable bits set in its file mode (user, group, or other).

fsIsMountPoint PATH

Return true (0) if PATH exists, is a directory, and is a mount point.

fsMountOptions PATH

Output the mount options that PATH is mounted with (such as "nosuid"), one per line.

acctIsAccountPasswordLocked USERNAME

Return true (0) if the user account named USERNAME exists and its password is locked.

acctLockAccountPassword USERNAME

Lock the password of the user account named USERNAME.

acctIsAccountPasswordEmpty USERNAME

Return true (0) if the user account named USERNAME is not locked, and has an empty password.

acctStaticSystemAccounts

Output a list of static system accounts (built-in accounts) other than root, such as daemon, bin, sys, nobody, and so on.

acctDynamicSystemAccounts

Output a list of dynamic system accounts (service accounts for system applications), such as messagebus, _rpc, apache, and so on.

acctUserAccounts

Output a list of user accounts - that is, accounts for normal users, not root or any of the accounts listed by the above functions.

netListListeningSockets

Output a list of listening TCP and UDP sockets, both IPv4 and IPv6, in the format common to ss(8), netstat(8), and sockstat(1) - the local listening address, a colon, and the port number, such as "0.0.0.0:993" or "*:443" or "[::1]:53".

Hooks

User-defined hooks can be placed in the hooks directory, which is usually /usr/libexec/xyz/hooks/.

Depending on your requirements, you can use the hooks mechanism to set variables and define functions that your extension items can use.

Underneath the hooks directory, the following subdirectories are present:

begin

Contains code to be run just before any actions start, after the above variables have been set up and the arguments have been parsed.

end

Contains code to be run after all actions complete, just before the temporary working directory is removed and xyz exits.

Within those subdirectories, every file ending in “.sh” will be sourced in alphanumeric order.

Functions and variables defined in the begin hook files will be available in the rest of xyz after they are loaded.

The scripts must not produce any output and must be written in Bourne shell (not bash, ksh, csh, etc).

Hooks can be included in standardised configuration deployed to all systems using whatever management mechanism is appropriate for your estate.

AUTHORS

Written by Andrew Wood.

REPORTING BUGS

Please report any bugs to xyz@ivarch.com.

Alternatively, use the issue tracker linked from the xyz home page: <https://www.ivarch.com/programs/xyz.shtml>

Copyright © 2024 Andrew Wood.

License GPLv3+: GNU GPL version 3 or later <https://www.gnu.org/licenses/gpl-3.0.html>.

This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law.

SEE ALSO

ssh-keygen(1), gpg(1)

← Back to the project page