#!/usr/bin/perl -w ##################################################################### # # Webshell # http://www.konetzka.de/webshell.html # # Copyright (c) 2003-2004 by Helge Konetzka # This program is free software; you can redistribute it # and/or modify it under the same terms as Perl itself. # # An interface to execute programs on servers without telnet access # Also upload, download and edit funcionality # Just for Unix-style servers # # Webshell comes with ABSOLUTELY NO WARRANTY! # # v1.4.2 (28.09.04): Bugfix der urlencode-Subroutine # v1.4.1 (09.09.04): Die automatische Konfiguration erfordert jetzt die # zweimalige Passworteingabe # v1.4 (06.09.04): Es werden keine Klartextpassworte oder Klartextonces mehr # auf dem Server gespeichert, wenn cgi-bin schreibbar ist # (Hinweis von Amelie Zapf) # v1.3.1 (05.09.04): Bugfix fuer Download (durch Ralf Zimmermann) # v1.3 (29.05.04): Fix fuer Webserver mit untersch. IDs fuer Upload und CGi # v1.2 (10.05.04): Richtiges Passwort nicht mehr in GET-Request enthalten, # waere bei weltlesbaren Logdateien toedlich # v1.1 (25.04.04): Downloadmoeglichkeit, Texteditor, Filebrowser # v1.0 (30.01.04): Kommandozeile am Webserver (nicht interaktiv), # Uploadmoeglichkeit, Pfadpersistenz # ##################################################################### # # Konfiguration # # Nur auf '0' setzen, falls keine Schreibmoeglichkeit fuer Skript in cgi-bin my $cgi_writeable = 1; # Nur dann das Passwort angeben, wenn $cgi_writeable = 0 my $origpass = ""; # ##################################################################### use strict; use CGI qw/:all/; use Cwd; # Skriptnamen extrahieren $0 =~ m%([^/]+)$%; my $cgi = $1; my $oncefile = "$cgi.once"; my $cryptfile = "$cgi.passwd"; my $pass = param('pass'); my $authenticate = authenticate(\$pass); my $cgi_pass = $cgi."?pass=$pass"; if ($authenticate) { # In das aktuelle Verzeichnis wechseln my $dir; if (param("dir")) { $dir = param("dir"); chdir $dir; } if (param("edit")) { my $file = param("edit"); if (param("content")) { my $old = $file.".old"; system("/bin/cp -p $file $old"); open (FILE,">$file"); my $content = param("content"); $content =~ s/\f\r\n/\n/g; $content =~ s/\n\f\r/\n/g; $content =~ s/\f\r/\n/g; $content =~ s/\r\n/\n/g; $content =~ s/\n\r/\n/g; $content =~ s/\r/\n/g; print FILE $content; close FILE; } print header; print start_html("Web-Editor $file"); print p("Web-Editor $file. Nach 5 Minuten verfaellt die Sitzung. Bitte vorher speichern!"); open FILE,$file; print start_form(-action=>$cgi); print ""; print br; print submit("Save"); print reset("Reset"); print hidden(-name=>"pass"); print hidden(-name=>"edit"); print hidden(-name=>"linebreak"); print hidden(-name=>"listdir"); print hidden(-name=>"dir"); print end_form; print start_form(-action=>$cgi); print hidden(-name=>"pass"); print hidden(-name=>"linebreak"); print hidden(-name=>"listdir"); print hidden(-name=>"dir"); print submit("Quit Editing"); print end_form; print end_html; } elsif (param("down")) { open (FILE,param("down")); my $file = param("down"); print header(-type=>"application/octet-stream",-attachment=>"$file"); my $buffer; # I/O - wichtig ist der erste Parameter von read, genau so! while (read(FILE,$buffer,1024)) { print $buffer; } # Datei schliessen close FILE; } else { # Ausgabe beginnen print header; print start_html(-title=>"Webshell"); print "
";
	# Die Kommandozeile ausfuehren
	if (param("command")) {
	    my $command = param("command");
	
	    # Darstellung der Ausgabe des Kommandos vorbereiten
	    print "$ENV{'SERVER_NAME'}:$dir\$ $command\n\n";
	    
	    # Verzeichniswechsel durchfuehren
	    if ($command =~ /\s*cd\s*(\S*)/) {
		my $newdir = "";
		$newdir = $1;
		chdir $newdir;
	    }
	    
	    # Befehl ausfuehren
	    else {
		# Fehlgeschlagenes Fork nicht abfangen wg. CGI
		open (COMMAND,"$command 2>&1 |");
		while (my $output = ) {
		    # Tabulatoren sind i.a. 8 Leerzeichen lang
		    $output =~ s/\t/        /g;
		    my $line = 0;
		    do {
			# Umbruch wie Emacs
			if (param("linebreak")) {
			    print "\\\n" unless $line == 0;
			} 
			# Zeile abknabbern
			my $suboutput = substr($output,$line*80,80);
			# Zeichen mit Bedeutung in HTML-Code korrekt darstellen
			$suboutput =~ s/&/&/g;
			$suboutput =~ s//>/g;
			print "$suboutput";
			# Nach Rest schauen
		    } while (substr($output,++$line*80,80))
		    }
		# Fehlgeschlagene Ausfuehrung nicht abfangen wg. CGI
		close COMMAND;
	    }
	}
	# Falls sich das aktuelle Verzeichnis geaendert hat, merken 
	$dir = cwd();
	$dir .= "/" unless $dir =~ m%/$%;
	if (param("listdir")) {
	    my @files = <*>;
	    push @files,<.*>;
	    @files = sort @files;
	    my $newdir = urlencode($dir);
	    open (COMMAND,"ls -la 2>&1 |");
	    print "Inhalt von $dir:\n\n"; 
	    my $total;
	    while (my $output = ) {
		if ($output =~ /^total/) {
		    $total = $output;
		    next;
		}
		my $file = shift @files;
		$file =~ s/^\s*//;
		$file =~ s/\s*$//;
		my $linebreak;
		if (param("linebreak")) { $linebreak = "on"; }
		else { $linebreak = 0; }
		my $listdir;
		if (param("listdir")) { $listdir = "on"; }
		else { $listdir = 0; }
		if ($output =~ / $file\s*$/ && !(-d $file)) {
		    my $newfile = urlencode($file);
		    print "Edit  ";
		    $output =~ s%$file$%$file%;
		}
		elsif ($output =~ / $file\s*$/ && (-d $file)) {
		    my $newfile = urlencode($file);
		    print "Enter ";
		}
		print $output;
	    }
	    print $total;
	    close COMMAND;
	}
	print "

"; # Datei hochladen if (param("upload")) { my $file = param("file"); # Dateinamen abknabbern if ($file =~ m@([^/\\]+)$@) { $file = $1; # Datei erstellen open (FILE,">$file"); my ($buffer,$nrbytes); # I/O - wichtig ist der erste Parameter von read, genau so! while ($nrbytes = read(param("file"),$buffer,1024)) { print FILE $buffer; } # Datei schliessen close FILE; } } # Uploadmoeglichkeit und Kommandozeile print start_multipart_form; print p("Datei ins Verzeichnis $dir laden: ", filefield(-name=>"file",-value=>"",-size=>"35",-override=>"1"), submit("OK")); print hidden(-name=>"pass"); print hidden(-name=>"upload",-value=>"1"); print hidden(-name=>"linebreak"); print hidden(-name=>"listdir"); print hidden(-name=>"dir",-value=>$dir,-override=>"1"); print end_form; print hr; print start_form(-action=>$cgi); print hidden(-name=>"pass"); print hidden(-name=>"dir",-value=>$dir,-override=>"1"); print "

"; print checkbox(-name=>"linebreak",-label=>" Zeilenumbruch in der Ausgabe "); print checkbox(-name=>"listdir",-label=>" ls -la "); print br,"$ENV{'SERVER_NAME'}:$dir\$ "; print textfield(-name=>"command",-value=>"",-size=>"50",-override=>"1"); print submit("EXEC"); print "

"; print end_form; print end_html; } } # Start oder Falsches Passwort else { print header; # Konfiguration ist OK if ((-e $cryptfile && !$origpass) || (!$cgi_writeable && $origpass)) { sleep(5) if param("pass"); print start_html("Log In"); print h1("Log In"); print start_form(-action=>$cgi); print p("Bitte Passwort eingeben: ", password_field(-name=>"pass",-override=>"1"), submit("OK")); print hidden(-name=>"listdir",-value=>"on",-override=>"1"); print end_form; print end_html; } # Ups, da hat jemand ein Klartextpasswort in das Skript eingefuegt (Vor oder nach der Konfiguration moeglich).! elsif ($cgi_writeable && $origpass) { print start_html("Konfigurationsproblem"); print h1("Konfigurationsproblem"); print p("Das Passwort darf bei cgi_writeable = 1 nicht im Klartext eingegeben sein! Beachten Sie den Konfigurationsabschnitt im Skript."); print end_html; } # Es existiert keine Passwortdatei elsif ($cgi_writeable && !(-e $cryptfile)) { # Sie wird jetzt geschrieben if (param("pass2") && param("pass2") eq param("pass")) { open CRYPT,">$cryptfile"; my $newcrypt = crypt(param("pass"),"NE"); print CRYPT $newcrypt; close CRYPT; if (-e $cryptfile) { print start_html("Automatische Konfiguration erfolgreich"); print h1("Automatische Konfiguration erfolgreich"); print p("Das Passwort wurde gespeichert"); print start_form(-action=>$cgi); print p("Bitte jetzt ", hidden(-name=>"pass"), submit("Anmelden")); print hidden(-name=>"listdir",-value=>"on",-override=>"1"); print end_form; print end_html; } else { print start_html("Automatische Konfiguration nicht erfolgreich"); print h1("Automatische Konfiguration nicht erfolgreich"); print p("Das Passwort wurde nicht gespeichert"); print p("Das CGI-Verzeichnis ist fuer Sie nicht schreibbar, bitte lesen Sie den Abschnitt zu Konfigurationsproblemen auf http://www.konetzka.de/webshell.html!"); print end_html; } } # Das Passwort fuer die Datei muss abgefragt werden else { print start_html("Automatische Konfiguration"); print h1("Automatische Konfiguration"); print p("Die eingegebenen Passworte waren nicht gleich!") if param("pass2"); print p("Bitte wählen Sie ein Passwort"); print start_form(-action=>$cgi); print p("Bitte Passwort eingeben: ",br, password_field(-name=>"pass",-override=>"1"),br, "Bitte das Passwort nochmals eingeben: ",br, password_field(-name=>"pass2",-override=>"1")); print p(submit("OK")); print end_form; print end_html; } } elsif (!$cgi_writeable && !$origpass) { print start_html("Konfigurationsproblem"); print h1("Konfigurationsproblem"); print p("Das Passwort muss bei cgi_writeable = 0 im Klartext eingegeben sein! Beachten Sie den Konfigurationsabschnitt im Skript."); print end_html; } else { print start_html("WADDEHADDEDUDDEDA"); print p("WADDEHADDEDUDDEDA"); print end_html; } } sub urlencode { my ($string) = @_; my $newstring; while ($string || $string eq "0") { my $char = substr($string,0,1); $string =~ s/^$char//; my $dez = ord($char); if (($dez >= ord('a') && $dez <= ord('z')) || ($dez >= ord('A') && $dez <= ord('Z')) || ($dez >= ord('0') && $dez <= ord('9')) || $dez eq ord('_') || $dez eq ord('*') || $dez eq ord('-') || $dez eq ord('.')) { $newstring .= $char; } elsif ($char eq " ") { $newstring .= "+"; } else { my $hex = sprintf "%lx",$dez; $newstring .= "%".$hex; } } return $newstring; } sub authenticate { my ($pass) = @_; return 0 unless $$pass; # Onces und crypt werden benutzt open CRYPT,$cryptfile; my $crypt = ; if ($crypt && $cgi_writeable && !$origpass) { my $rand = randword(); my $cryptrand = crypt($rand,$crypt); # Falls Erstanmeldung die ONCE-Datei nicht lesen if ($crypt eq crypt($$pass,$crypt)) { # ONCE-Datei mit Zufallszahl und Zeitstempeln fuellen # ONCE zum naechsten Passwort machen open ONCE,">$oncefile"; print ONCE time()."\t".$cryptrand."1"."\t".$cryptrand; close ONCE; $$pass = $rand; param('pass',$rand); return 1; } # Da nicht Erstanmeldung die ONCE-Datei lesen else { open ONCE,"$oncefile"; my @once = split /\t/,; close ONCE; if (time() - $once[0] < 300) { if ($once[1] eq crypt($$pass,$once[1])) { open ONCE,">$oncefile"; print ONCE time()."\t".$once[1]."\t".$cryptrand; close ONCE; $$pass = $rand; param('pass',$rand); return 1; } # neues ONCE fuer naechstes Formular erstellen und merken elsif ($once[2] eq crypt($$pass,$once[2])) { open ONCE,">$oncefile"; print ONCE time()."\t".$once[2]."\t".$cryptrand; close ONCE; $$pass = $rand; param('pass',$rand); return 1; } } } } # Onces und crypt werden nicht benutzt else { if ($origpass eq $$pass) { return 1; } } # No way in return 0; } sub randword { my $buffer; my $rand = rand(); $rand =~ /\.(\d+)/; $rand = $1; while ($rand) { my $buf = $rand%62; $rand = ($rand - $buf) / 62; if ($buf >= 36 ) { $buf = $buf - 36 + 65; $buffer .= chr($buf); } elsif ($buf >= 10 ) { $buf = $buf - 10 + 97; $buffer .= chr($buf); } else { $buffer .= $buf; } } return $buffer; }