The rather serious recent OpenSSH vulnerability CVE-2024-6387 could affect as many as 14 million server instances exposed on the internet. Let’s make it easy to examine your infrastructure and see if you need to do any upgrades or mitigations.
On the back of my CFEngine T-shirt it says:
Know more, React faster
When I have a problem to solve in CFEngine I look for an easy and correct solution. CFEngine Build is a good first place to look. Two modules stand out as possibly useful;
-
We can use inventory-openssl-versions as a template to inventory OpenSSH version. This is the “Know more” step.
-
For the “React faster” we may need to change something. In case we can’t upgrade OpenSSH we will need to mitigate the vulnerability by modifying
sshd
config. For this purpose we can leverage library-sshd-config. This module has the advantage of preparing a staged configuration as well as restarting the service in case of a change.
Adding modules
Previously we have used the Build app in Mission Portal. This time let’s use the cfbs command line tool.
cfbs add inventory-openssl-versions
Added module: inventory-openssl-versions
The default commit message is 'Added module 'inventory-openssl-versions'' - edit it? [yes/y/NO/n]
Committing using git:
[home 5964af0] Added module 'inventory-openssl-versions'
1 file changed, 15 insertions(+)
cfbs add library-sshd-config
Added module: library-sshd-config
The default commit message is 'Added module 'library-sshd-config'' - edit it? [yes/y/NO/n]
Committing using git:
[home 0da07db] Added module 'library-sshd-config'
1 file changed, 15 insertions(+)
So let’s “copy-paste-modify” the policy from inventory-openssl-versions
to inventory OpenSSH aka sshd
version.
OpenSSH version information in inventory
For simplicity’s sake we will rely only on the sshd
command only instead of also checking package managers.
body file control
{
namespace => "inventory_sshd_version";
}
bundle agent inventory_sshd_version
{
vars:
!windows::
"_sshd_path"
string => "/usr/sbin/sshd";
classes:
linux::
"sshd_binary_works"
if => returnszero("${_sshd_path} -t 1>/dev/null 2>&1", "useshell");
# -t option is Test mode, should return zero
vars:
sshd_binary_works::
"sshd_output"
string =>
nth(
splitstring(
execresult("${_sshd_path} -nosuchargument", "useshell"),
# Sadly sshd has no -V|--version so instead we use
# -nosuchargument which causes sshd to output a full version
# like OpenSSH_8.5p1
"\\n",
3),
1);
classes:
sshd_binary_works::
"found_sshd_version"
expression => regextract(
"^.*OpenSSH_([0-9\.p]+).*",
"${sshd_output}",
"captures"
);
vars:
found_sshd_version::
# OpenSSH versions are in the format (major).(minor)p(patch)
# so we replace the "p" with "." to conform more to semantic
# version standard used by version_compare() function.
"sshd_version"
string => string_replace(
"${captures[1]}",
"p",
"."),
meta => {"inventory", "attribute_name=OpenSSH version"};
reports:
DEBUG|DEBUG_INVENTORY_OPENSSH_VERSION::
"OpenSSH version: $(sshd_version)";
}
After a few agent runs we can check inventory and see if we have any troubles.
React faster
Two of my systems have an affected version! If a system was affected and I couldn’t upgrade the OpenSSH version, I would create policy to enact the mitigation procedure as recommended in the technical details:
Finally, if
sshd
cannot be updated or recompiled, this signal handler race condition can be fixed by simply setting LoginGraceTime to 0 in the configuration file. This makessshd
vulnerable to a denial of service (the exhaustion of all MaxStartups connections), but it makes it safe from the remote code execution presented in this advisory.
So let’s write some policy.
The first trick is to figure out if the the OpenSSH version is affected. Let’s express the logic of whether the version is OK in a simple expression:
(version >= 4.4p1 && version < 8.5p1) || version > 9.8p1
Thankfully, we have a helper in a new function available in our latest LTS, 3.24: version_compare()
.
With it, we can define a class sshd_version_ok
using this policy:
classes:
"sshd_version_ok"
expression =>
or(
and(
version_compare("${sshd_version}", ">=", "4.4.1"),
version_compare("${sshd_version}", "<", "8.5.1")
),
version_compare("${sshd_version}", ">", "9.8.1")
);
But this could cause sshd_version_ok
to not be defined in case the sshd_version
is not defined.
This is often an issue in CFEngine where negative knowledge can’t really be certain. We should rewrite this as “positive knowledge” aka something we determine explicitly.
"sshd_version_cve"
expression =>
and(
isvariable("sshd_version"),
or(
version_compare("${sshd_version}", "<", "4.4.1"),
and(
version_compare("${sshd_version}", ">=", "8.5.1"),
version_compare("${sshd_version}", "<=", "9.8.1")
)
)
);
And now we can write our mitigation policy:
bundle agent openssh_mitigation_CVE_2006_5051_CVE_2008_4109
{
vars:
"sshd_version"
string => "${inventory_openssh_version:inventory_openssh_version.sshd_version}";
classes:
"sshd_version_cve"
expression =>
and(
isvariable("sshd_version"),
or(
version_compare("${sshd_version}", "<", "4.4.1"),
and(
version_compare("${sshd_version}", ">=", "8.5.1"),
version_compare("${sshd_version}", "<=", "9.8.1")
)
)
);
vars:
"sshd_config[LoginGraceTime]"
string => "0";
methods:
sshd_version_cve::
"sshd config mitigation for CVE-2006-5051 and CVE-2008-4109"
usebundle => lib_sshd_config:global_key_values(
"$(this.namespace):$(this.bundle).sshd_config");
}
And with that, we know more: what versions of OpenSSH are present and have reacted faster: mitigated sshd
config if needed.
TODO
- Both
inventory_openssh_version
andopenssh_mitigation_CVE_2006_5051_CVE_2008_4109
could be made into CFEngine Build modules. - We could make more generic modules to inventory various softwares and mitigate various vulnerabilities.
Questions?
If you have questions or need help, reach out on the mailing list or GitHub discussions. If you have a support contract, feel free to open a ticket in our support system.