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.