»Home
»News
»Download
»Documentation
     »oathtool(1)
     »pskctool(1)
     »Liboath API
     »PSKC Tutorial
     »Libpskc API
     »pam_oath
»Contribute
OATH Toolkit
One-time password components

OATH Toolkit pam_oath usersfile ${HOME} privilege escalation (CVE-2024-47191)

Security Vulnerability

OATH Toolkit provides two components liboath and pam_oath. Pam_oath is normally run as root and assumes that the usersfile path setting value points to a root-controlled and protected file containing the OATH secrets (cryptographic HMAC keys) for users.

The documentation suggests using a root-owned /etc/users.oath and to do chmod go-rw /etc/users.oath on it. The design assumes that file permissions are set up to prevent malicious read or writes to the credential file by unauthorized users. Whether file permissions are setup correctly or not is not checked by the code.

On every successful authentication, the file is rewritten to prevent OTP replay attacks. The rewriting logic works by acquiring a POSIX file lock on a newly created *.lock file (in the same directory), and then writing file content to a newly created *.new file (also in the same directory). File ownership of the new file is set to the same as the original file. The original usersfile is replaced atomically with the newly created file.

With the introduction of the ${HOME} indirection variable in the usersfile parameter the design assumptions no longer holds. The feature was added in version 2.6.7 released on 2021-05-01.

A typical setup when ${HOME} is used in a usersfile value such as usersfile=${HOME}/.config/oath.secrets is to allow users to manage their own credentials. The file is owned by the user who is responsible for adding the secret to it, and to set read/write permissions on the file appropriately.

The security problem is easy to exploit. To demonstrate the vulnerability with a vulnerable version and configuration, create a symbolic link $HOME/.config/oath.secrets.new that points to a privileged file such as /etc/shadow. After successful login as the user, pam_oath/liboath has followed the symbolic link and rewrote the target file with new updated OATH credentials and sets ownership of that file to the user. The user is now able to modify /etc/shadow.

We are not aware of any active exploits in the wild of this flaw.

Affected versions and configurations

OATH Toolkit pam_oath and liboath version 2.6.7 to version 2.6.11 are affected. Version 2.6.12 prevents the attack.

The attack requires that the "usersfile" setting has a file or path component that is in a vulnerable location. The common setup with a write/read-protected usersfile=/etc/users.oath setup is not vulnerable.

While admin’s may specify a "usersfile" in a world-writeable directory like /tmp we regard that as a configuration error. In most scenarios, only "usersfile" with ${HOME} in them should be regarded as a vulnerable configuration.

One vulnerable setting is usersfile=/home/joe/.config/system.oath giving joe the ability to modify root-owned files, assuming a non-root user joe with write access to anything below /home/joe. This is somewhat similar to having a system SSH configuration of HostKey /home/joe/ssh_hostkey which we believe is unlikely and also consider to be a configuration error.

Another example is via the ${USER} setting as in usersfile=/home/${USER}/.config/oath.secrets giving any non-root user the ability to control system files, assuming they have write access to anything below /home/${USER}. We have improved the documentation regarding ${USER} to be for those settings where root-controlled per-user files are desired. The intended use of ${USER} strings are for setups like usersfile=/var/oath/oath.${USER}.secrets where the files are per-user but owned by root and have file permissions setup to prevent read/write from the user. The root-ownership and file permission should prevent users from being able to reach their OATH credentials when ${USER} is used.

The pam_oath fix only address configurations that uses ${HOME} and not any other vulnerable configurations. The liboath fix prevent direct attacks via *.new and *.lock symlinks, but other scenarios are possible and the input to oath_authenticate_usersfile() MUST be a trusted filename — suitable sanitization is application-dependent.

Details

The vulnerable code in liboath is inside oath_authenticate_usersfile(), quoting code from version 2.6.11 which can be reviewed here:

The lock file is acquired:

/* Open lockfile. */
{
  int l;
  l = asprintf (&lockfile, "%s.lock", usersfile);
  if (lockfile == NULL || ((size_t) l) != strlen (usersfile) + 5)
    return OATH_PRINTF_ERROR;
  lockfh = fopen (lockfile, "w");
  if (!lockfh)
    {
      free (lockfile);
      return OATH_FILE_CREATE_ERROR;
    }
}

Since fopen("w") is used any existing file with the predictable *.lock filename will be opened, including if that file happens to be a symbolic link that points elsewhere. The code is run as root, so a symbolic link pointing to confidential files under /etc are happily opened. Note that since "w" is used, the file will be automatically truncated. So this allows non-root to truncate root-owned files.

Reading on to the *.new file handling:

outfh = fopen (newfilename, "w");
if (!outfh)
  {
    free (newfilename);
    fclose (lockfh);
    free (lockfile);
    return OATH_FILE_CREATE_ERROR;
  }

Similarily, this will open a file for writing, and later code will essentially copy data from the existing file into the new one. This new file is moved back into the original place.

The PAM module invokes oath_authenticate_usersfile() in pam_oath.c, which can be shown here:

The code is running as root so it may work on an untrustworthy filename. The design used to be that admin’s specify a trusted pathname here, but the ${HOME} use-case broke this design assumption and the pathnames should then no longer be considered trusted.

Solution

Version 2.6.12 contains the following liboath patch to use fopen(wx):

Some non-glibc and non-ISO C11 platforms needs the following patch to enable gnulib’s fopen(wx) workaround:

Then pam_oath.c is modified to call seteuid()/setegid() as follows:

A patch that applies cleanly to version 2.6.7 found in Debian 12.x bookworm is available here:

We recommend you to upgrade to version 2.6.12.

If that is unpractical we recommended you to apply the patches on top of your earlier version.

Reproducer

Included in the 2.6.12 release is a C program to test if a liboath is vulnerable or not. It can be built as follows on a system with liboath properly installed:

 git clone https://gitlab.com/oath-toolkit/oath-toolkit.git
 cd oath-toolkit
 git checkout oath-toolkit-2.6.12
 cd liboath/tests
 cc -o tst_fopen-wx tst_fopen-wx.c $(pkg-config --libs --cflags liboath)
 rm -f cve.oath cve.oath.new cve.sshd-config cve.oath.lock
 printf 'HOTP/E/8\tsilver\t4711\t3132333435363738393031323334353637383930313233343536373839303132\n' > cve.oath
 echo my-magic-cookie > cve.sshd-config
 ln -s cve.sshd-config cve.oath.new
./tst_fopen-wx cve.oath silver 670691 4711

When invoked on a Trisquel 11 system with liboath0 and liboath-dev version 2.6.7-3build1 installed, it will print the following:

Liboath fopen(wx) bug test for oath.h 2.6.7 liboath.so 2.6.7
FAIL: Liboath VULNERABLE to fopen(wx) bug.

To test a particular liboath use LD_PRELOAD as follows:

LD_PRELOAD=/usr/local/lib/x86_64-linux-gnu/liboath.so.0 ./tst_fopen-wx cve.oath silver 670691 4711

The liboath/tests/tst_fopen-wx.sh script can be used to setup and invoke the C program testing two different vulnerable configurations.

The logic for the usersfile handling was inspired by earlier versions of mod-authn-otp and pam_google_authenticator although their modern design appears to be somewhat different from pam_oath’s current code.

While the liboath oath_authenticate_usersfile() vulnerability is not a typical "time-of-check, time-of-use" race condition (since there is no check happening in the code) it exhibits the same pattern in the code since fopen(w) is used instead of fopen(wx). Running GitLab semantic analysis (SAST) on the vulnerable code flagged it as problematic:

We have enabled GitLab SAST and Coverity scanning of OATH Toolkit and will review the findings.

SUSE’s alternative patch and advisory can be found via:

It rely on Linux kernel specific features and uses fork() which was determined to be contrary to the liboath design, which aims to be portable to macOS and *BSD and beyond. This alternative patch may be used by some vendors, and it is assumed to fix the security problem.

History

Fabian Vogt reported this issue in private e-mail on 2024-08-08. Matthias Gerstner reported the issue as a GitLab confidential issue on 2024-08-20. These reports came in during vacation time and were not read by the maintainer. Salvatore Bonaccorso reached out on 2024-09-29 via SMS, and the progress since then has been tracked in the bug tracker:

Credits

The problem was discovered by Fabian Vogt of SUSE. An initial patch against liboath was developed by Matthias Gerstner of SUSE Security Team. An alternative and portable patch to liboath and pam_oath were developed by Simon Josefsson.