我想在CGI程序(用Perl编写)中运行shell命令。我的程序没有root权限。它以无名之辈的身份运行。我想使用以下代码:
use strict;
system <<'EEE';
awk '{a[$1]+=$2;b[$1]+=$3}END{for(i in a)print i, a[i], b[i]|"sort -nk 3"}' s.txt
EEE
我可以在命令行中用perl成功地运行我的代码,但不能作为CGI程序。
根据你问题中的代码,至少有四种失败的可能性。
- nobody用户没有权限执行程序。
- 您的问题中的Perl代码没有shebang (
#!
)行。您正在尝试运行awk
,因此我假设您正在某种形式的Unix上运行。如果你的代码缺少这一行,那么你的操作系统不知道如何运行你的程序。 - 文件
s.txt
不在执行程序的工作目录中,或者nobody用户无法读取该文件 - 无论什么原因,
awk
无法通过执行程序环境的PATH访问。
要快速诊断此类低级问题,请尝试在浏览器中显示所有错误输出。一种方法是在代码的shebang行后面添加以下代码:
BEGIN {
print "Content-type: text/plainnn";
open STDERR, ">&", *STDOUT or print "$0: dup: $!";
}
输出将呈现为纯文本而不是HTML,但这是查看程序输出的临时措施。通过将其包装在BEGIN
块中,代码在解析后立即执行。重定向STDERR
意味着您的浏览器也可以将任何内容写入标准输出。
use CGI::Carp 'fatalsToBrowser';
这样,错误会记录在浏览器和web服务器的错误日志中。
如果您仍然看到来自服务器的500系列错误,则问题发生在较低级别:可能是启动perl
失败。去检查服务器的错误日志。一旦你的程序开始执行,你可以删除这个临时的错误输出重定向。
最后,我建议把你的程序改成
#! /usr/bin/perl -T
BEGIN { print "Content-type: text/plainnn"; }
use strict;
use warnings;
$ENV{PATH} = "/bin:/usr/bin";
my $input = "/path/to/your/s.txt";
my $buckets = <<'EOProgram'
{ a[$1] += $2; b[$1] += $3 }
END { for (i in a) print i, a[i], b[i] }
EOProgram
open STDIN, "-|", "awk", $buckets, $input or die "$0: open: $!";
exec "sort", "-nk", 3 or die "$0: exec: $!";
-T
开关启用了一种称为污染模式的安全数据流分析,它可以防止对系统操作(如open
、exec
等)使用未经消毒的输入,攻击者(或提供意外输入的良性用户)可能利用这些输入来损害您的系统。您应该始终将-T
添加到CGI程序和代表其他用户运行的任何其他代码中。
考虑到awk程序的性质,text/plain的内容类型似乎是合理的。请尽快输出
在启用了污染模式的情况下,明确指定PATH环境变量的值。如果您坚持使用您的程序继承的任何不受信任的路径,那么尝试运行外部程序将会失败。
确定输入的完整路径。这将消除意外。
使用open
和exec
的多参数形式消除了shell及其参数解析。(为了完整起见,system
也有类似的多参数形式。)是的,以这种方式编写代码可能意味着更慎重一些(比如自己分解参数和设置管道),但它也避免了令人讨厌的意外。
我确信nobody
是允许运行shell命令的。问题是nobody
没有权限打开文件s.txt
。
s.txt
添加每个人的读权限,并为s.txt
之前的每个目录添加每个人的执行权限。我建议找出awk的完整限定路径并直接指定它。启动httpd的人可能在其$ENV{path}中有一个非常小的路径。显示$ENV{PATH}我猜会显示这个
这是一件好事,我不会修改路径,而只是指定路径/usr/bin/awk或其他。
如果您有shell访问权限并且它可以工作,请键入'which awk'来查找。
我可以成功地运行我的代码Perl文件,而不是cgi文件。
你在哪个web服务器下运行?例如,apache需要打印一个CGI头,即print "Content-type: text/plain; charset=utf-8nn"
,或
use CGI;
my $q = CGI->new();
print $q->header('text/html');
(见CGI)
Apache将在日志(error.log
)中抱怨"脚本头过早结束",如果我说的是这种情况。
您可以直接内联,而不必fork到另一个进程…
if ( open my $fh, '<', 's.txt' ) {
my %data;
while (<$fh>) {
my ($c1,$c2,$c3) = split;
$data{a}{$c1} += $c2;
$data{b}{$c1} += $c3;
}
foreach ( sort { $data{b}{$a} <=> $data{b}{$b} } keys %{ $data{b} } ) {
print "$_ $data{a}{$_} $data{b}{$_}n";
}
} else {
warn "Unable to open s.txt: $!n";
}