Attach(er.c) from the by(pass)t

This post is about the findings of an old bug that existed in GNU Screen many years ago. It was discovered and tested on OpenBSD systems with probability on other flavors of *NIX. With the little free time I had, I felt like exploring Screen's source code. Not the GNU branch but Apple's branch in particular.

For those of you who are not familiar with Screen and its functionality, Screen is a "terminal multiplexer software application that can be used to multiplex several virtual consoles, allowing a user to access multiple separate terminal sessions inside a single terminal window or remote terminal session."

Why Apple's version of Screen?

To tell you the truth, I use Mac quite often in my day to day life. So, why not? Mac OS X has been getting way more exposure the last few years from malware development, to exploit development and memory forensics, just to name a few areas. If you look at Apple's branch of Screen, it is nothing more than the last version created by the developers of Screen released back in 2006 with Apple patches built on top.

screen -v  
Screen version 4.00.03 (FAU) 23-Oct-06  

Just like Apple, many other organizations have adapted GNU Screen and have built patches on top of the project. As we know from history, new bugs sometimes get introduced to old projects. Even new bugs could result in similar vulnerabilities, from the past, which people might have thought to been patched.

Same old bug, same technique?

The way Screen currently sits, the same bug still exist but the original [ctrl-c] bypass technique was patched. You can find the original proof of concept (POC) at Exploit-db: screen 4.0.3 - Local Authentication Bypass Vulnerability. Looking at the author's description he/she says, "initial disclosure suggest this may be related to PAM authentication (PAM), or another 3rd party package. Testing was not performed to fully identify the vulnerable code." Testing the original POC, I was not able to successfully [ctrl-c] out of the sitting password prompt. The idea of the password protection on screen is to lock the terminal, so I clearly needed another way to access that session.

$ screen -S bypass
bash-3.2$ echo 'Did you use the password?'  
Did you use the password?  
[ctrl-a x]
Key:  
Again:  
Screen used by Jacolon Walker <jacolon>.  
Password:  
[ctrl-c] (this should fail and repeat password prompt)

Bypass still achievable

Since [ctrl-c] did not allow me to escape the locked password prompt, opening up a new pseudoterminal (PTY) session and attaching should allow bypass. As the original POC states, this does bypass Screen's authentication to the target session.

$ screen -rd bypass
bash-3.2$ echo 'Did you use the password?'  
Did you use the password?  
bash-3.2$ echo 'No!!!!'  
No!!!!  

The author also stated this issue might be due to PAM authentication or some third party package. Looking at Screen's source code, the issue lies within attacher.c, which has PAM written all over this code.

Attacher.c source code analysis

Searching for "Key:" and "Password:" (without quotes) in the source code takes you to lines 817 & 862. This may vary depending what organizations code base you pull. Simply put, this should quickly help you walk through the code to understand what checks are in place for the Screen's password authentication.

<--snippet-->  
  pass = ppp->pw_passwd;
  if (pass == 0 || *pass == 0)
    {
      if ((pass = getpass("Key:   ")))
        {
          strncpy(mypass, pass, sizeof(mypass) - 1);
          mypass[sizeof(mypass) - 1] = 0;
          if (*mypass == 0)
            return;
          if ((pass = getpass("Again: ")))
            {
              if (strcmp(mypass, pass))
                {
                  fprintf(stderr, "Passwords don't match.\007\n");
                  sleep(2);
                  return;
                }
            }
        }
      if (pass == 0)
        {
          fprintf(stderr, "Getpass error.\007\n");
          sleep(2);
          return;
        }
<--snippet-->  
---
<--snippet-->  
  sprintf(message, "Screen used by %s <%s>.\nPassword:\007",
          fullname, ppp->pw_name);

  /* loop here to wait for correct password */
  for (;;)
    {
      debug("screen_builtin_lck awaiting password\n");
      errno = 0;
      if ((cp1 = getpass(message)) == NULL)
        {
          AttacherFinit(SIGARG);
          /* NOTREACHED */
        }
<--snippet-->  

The snippet of code above is looking for the defined password (Key) that was typed previously and comparing it to user input for the password prompt check. The problem here is that the code block being executed only works with that current pty. If another pty is created under the user's account, then assuming if this is a PAM issue, PAM will check to see if that user has rights to access the newly created Screen session.

Tracing back to attacher.c

Using dtrace or strace, we can simply trace into a process to see what system calls, files, child processes and more are being executed in real time. As a result of attaching to the Screen bypass session with strace, I can quickly locate and determine how authentication is possibly being avoided.

# strace -p 18630
Process 18630 attached  
select(1024, [4 6], [], NULL, NULL)     = 1 (in [4])  
fcntl64(4, F_SETFL, O_RDONLY)           = 0  
read(4, "\0gsm\2\0\0\0/dev/pts/5\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 12356) = 12356  
close(4)                                = 0  
geteuid32()                             = 0  
getegid32()                             = 84  
getgid32()                              = 0  
setregid32(84, 0)                       = 0  
open("/var/run/screen/S-root/18630.bypass", O_RDONLY|O_NONBLOCK) = 3  
geteuid32()                             = 0  
getegid32()                             = 0  
getgid32()                              = 84  
setregid32(0, 84)                       = 0  
kill(18647, SIG_0)                      = 0  
geteuid32()                             = 0  
getegid32()                             = 84  
getgid32()                              = 0  
setregid32(84, 0)                       = 0  
open("/dev/pts/5", O_RDWR|O_NONBLOCK)   = 4  
geteuid32()                             = 0  
getegid32()                             = 0  
getgid32()                              = 84  
setregid32(0, 84)                       = 0  
kill(18647, SIGCONT)                    = 0  

A few things stick out from trace output:

Cross referencing those system calls to the source code of attacher.c, there are several references throughout, including the output below which seems to match the above process trace.

<--snippet-->  
 /*
   * Go in UserContext. Advantage is, you can kill your attacher
   * when things go wrong. Any disadvantages? jw.
   * Do this before the attach to prevent races!
   */
#ifdef MULTIUSER
  if (!multiattach)
#endif
    setuid(real_uid);
#if defined(MULTIUSER) && defined(USE_SETEUID)
  else
    {
      /* This call to xsetuid should also set the saved uid */
      xseteuid(real_uid); /* multi_uid, allow backend to send signals */
    }
#endif
  setgid(real_gid);
  eff_uid = real_uid;
  eff_gid = real_gid;

  debug2("Attach: uid %d euid %d\n", (int)getuid(), (int)geteuid());
  MasterPid = 0;
  for (s = SockName; *s; s++)
    {
      if (*s > '9' || *s < '0')
        break;
      MasterPid = 10 * MasterPid + (*s - '0');
    }
  debug1("Attach decided, it is '%s'\n", SockPath);
  debug1("Attach found MasterPid == %d\n", MasterPid);
  if (stat(SockPath, &st) == -1)
    Panic(errno, "stat %s", SockPath);
  if ((st.st_mode & 0600) != 0600)
    Panic(0, "Socket is in wrong mode (%03o)", (int)st.st_mode);
<--snippet-->  

Another way to determine how one might be attaching to the session is by searching for 'MSG_ATTACH'. This helps step through Screen arguments such as -x, -d, or -r along with assisting to understand what failures cases might look like.

Tested OS differences

If you have been following along in the source code you will notice we have not hit any PAM related modules. When trying this on my Macbook pro versus a virtual private server (VPS) running CentOS 6.7, the PAM code block runs. This is shown when typing [ctrl-a x], CentOS does not allow us to input a password protected key for the session. It uses PAM authentication in order confirm access back to the Screen session but can still be bypassed via another pty. This should give signal on the subtle differences between patches being distributed between operating systems. If there are failed password attempts in the CentOS version of Screen, it will appear in the /var/logs/secure logs.

./secure:Oct 18 10:43:50 li371-224 screen: pam_unix(screen:auth): authentication failure; logname=[username] uid=0 euid=0 tty=pts/0 ruser= rhost=  user=root
./secure-20151018:Oct 15 07:48:35 li371-224 screen: pam_unix(screen:auth): authentication failure; logname=[username] uid=500 euid=500 tty=pts/0 ruser= rhost=  user=[username]
./secure-20151018:Oct 15 07:51:19 li371-224 screen: pam_unix(screen:auth): authentication failure; logname=[username] uid=500 euid=500 tty=pts/0 ruser= rhost=  user=[username]
./secure-20151018:Oct 15 07:51:26 li371-224 screen: pam_unix(screen:auth): authentication failure; logname=[username] uid=500 euid=500 tty=pts/0 ruser= rhost=  user=[username]
./secure-20151018:Oct 15 08:31:24 li371-224 screen: pam_unix(screen:auth): authentication failure; logname=[username] uid=500 euid=500 tty=pts/0 ruser= rhost=  user=[username]
./secure-20151018:Oct 15 08:31:31 li371-224 screen: pam_unix(screen:auth): authentication failure; logname=[username] uid=500 euid=500 tty=pts/0 ruser= rhost=  user=[username]
./secure-20151018:Oct 15 08:31:50 li371-224 screen: pam_unix(screen:auth): authentication failure; logname=[username] uid=500 euid=500 tty=pts/0 ruser= rhost=  user=[username]
./secure-20151018:Oct 15 08:32:46 li371-224 screen: pam_unix(screen:auth): authentication failure; logname=[username] uid=500 euid=500 tty=pts/0 ruser= rhost=  user=[username]

Scenarios

People will argue this is not a practical application of attack. Although I might agree to a certain point, this technique could be used in a couple scenarios especially in a pentest scenario targeting a developer. For instance, imagine that the attacker might have access to an unauthorized account via ssh keys or password (hopefully there is some two factor auth in the way). They notice the user has a Screen session in the process listing and they were not able to use the user's password to escalate privileges to root using sudo or su. The attacker also does not want to use any noisy exploits before attempting to leverage any data that might already be present on the system. An attacker could attach to the user session using 'screen -x' to monitor the user workflow which would bypass the password protected Screen session. Bonus points if the user they are monitoring happens to be working as an elevated super user (root). There are clearly other things an attacker might be interested in besides a Screen session but you get the idea.

Conclusion

The purpose of this post was to demonstrate that old bypasses still exist in modern times. I wanted to dive a bit deeper to understand this bypass instead of just assuming it to be in PAM or some third party package. Based on my little audit, the bypass exist within in the attacher.c base code and not PAM. The original developers implemented what looks to be their own key password authentication. This may not be the 100% accurate picture of the logic bug but it does narrow down the scope of the bypass.

Resources

https://www.gnu.org/software/screen/
https://opensource.apple.com/release/os-x-10105/
https://www.exploit-db.com/exploits/4028/
http://linux.die.net/man/3/pam_authenticate