How to serve Static HTML file and CGI script in development environment

How to serve Static HTML file and CGI script in development environment.

I write simple implementation with Mojolicious.

morbo serve_cgi.pl
#!/usr/bin/env perl

use strict;
use warnings;

use Mojo::Message::Response;
use File::Temp 'tempfile';

warn "Server start\n";

my $cmd = 'giblog build';
system($cmd) == 0
or die "Can't execute $cmd: $!";

use Mojolicious::Lite;

# render CGI
app->hook(before_dispatch => sub {
my $c = shift;

my $req = $c->req;

my $path = $req->url->path->clone;

if ($path =~ /\.cgi$/) {

# Prevent directory traversal
$path->canonicalize;
if ($path =~ /^[\\\/]\./) {
die "Can't contain \. in path \"$path\"";
}

my $method = $c->req->method;
my $script_name = $c->app->home->rel_file("public/$path");

# CGI Environment variable
local $ENV{AUTH_TYPE} = '';
local $ENV{CONTENT_LENGTH} = $req->headers->content_length;
local $ENV{CONTENT_TYPE} = $req->headers->content_type;
local $ENV{GATEWAY_INTERFACE} = 'CGI/1.1';
local $ENV{PATH_INFO} = $req->url->path;
local $ENV{PATH_TRANSLATED} = $req->url->path;
local $ENV{QUERY_STRING} = $req->url->query->to_string;
local $ENV{REMOTE_ADDR} = $c->tx->remote_address;
local $ENV{REMOTE_HOST} = $c->tx->remote_address;
local $ENV{REMOTE_IDENT} = '';
local $ENV{REMOTE_USER} = '';
local $ENV{REQUEST_METHOD} = $method;
local $ENV{SCRIPT_NAME} = $script_name;
local $ENV{SERVER_NAME} = 'localhost';
local $ENV{SERVER_PORT} = $c->tx->remote_port;
local $ENV{SERVER_SOFTWARE} = "Mojolicious (Perl)";
local $ENV{SERVER_PROTOCOL} = 'HTTP/1.1';

# Check script name
unless ($script_name =~ /^[a-zA-Z_0-9\/\-\.]+$/) {
die "Invalid script name";
}

# Check existance of CGI script
unless (-f $script_name) {
die "Not found CGI script";
}

# Run CGI script
my $output;
if ($method eq 'GET') {
# GET requst
$output = `$^X $script_name`;
if ($?) {
$c->res->code('505');
$c->render(text => "Internal Server Error");
}
}
elsif ($method eq 'POST') {
# POST request
my $body = $req->body;
my ($in_fh, $in_file) = tempfile;
print $in_fh $body;
close $in_fh;
$output = `$^X -pe "" $in_file | $^X $script_name`;
if ($?) {
$c->res->code('505');
$c->render(text => "Internal Server Error");
}
unlink $in_file;
}

# Header part and body part
my ($header_part, $body_part) = split("\n\n", $output, 2);

# Response
my $res = Mojo::Message::Response->new;
while (!$res->is_finished) {
$res->parse($header_part);
}
$c->res->code($res->code);
$c->res->headers($res->headers);
$c->render(data => $body_part);
}
});

get '/' => sub {
my $c = shift;

$c->reply->static('index.html');
};

app->start;


11 Comments

Hi Yuki. This allows arbitrary OS command injection: http://127.0.0.1:3000/foo;ifconfig;echo.cgi

You probably want to emphasise that this script is absolutely *not* something that should be run in any public facing way.

Hi Yuki,

I haven't tested but it looks like the regexp would still allow arbitrary path traversal and execution if the $script_name were something like "/path/to/file.pl" or "../path/to/some/other/file.pl".

Consider also Mojolicious::Plugin::CGI.

And there are more options via Plack: Plack::App::CGIBin and Plack::App::WrapCGI.

The Mojolicious plugin can coexist with anything else your application serves such as static files. The Plack apps can be combined with the mount keyword from Plack::Builder, with something like Plack::App::Directory for serving static files, or a Dancer2 application, etc.

Leave a comment

About Yuki Kimoto

user-pic I'm Perl Programmer. I LOVE Perl. I want to contribute Perl community and Perl users.