Taint - having some real trouble here, taint/perl experts, please help

B

Ben

I have a .PL script that is attempting to invoke an external command
once launched from a web page. The command takes quite a while to run
(could be hours), so it is important that it detach so the user can
come back later. The command generates result webpages elsewhere. This
command also takes some parameters. The environment is 100% secure,
non-internet connected intranet, there are NO security issues, so
please refrain from telling me how wonderful taint is and that I
should be using it, because in this case, it... taint so darned
wonderful, it's just a pain in the tush. :) I understand why I might
want taint under other circumstances, even most other circumstances...
It just doesn't apply here at all.

I'll explain everything I can think of:

Under perl 5 and redhat 6.0, this was no problem. Under perl 5.8.0 and
redhat 9, perl would not invoke the command, in a manner that leads me
to think it was a result of taint being on.

This creates the command string I want:

$cmd = "/usr/src/client/client $p1 $p1 $p3 $p4 &";

Then:

system($cmd);

....would invoke it under RH6 and P5.0, and all was well.

Now, under RH9 and P5.8 (or the apache perl module, which I suspect is
actually handling this), perl is acting like taint is on (I didn't
turn it on, and I can't find where it's turned on, more on that in a
bit.) So, as instructed in the perl faq, I attempt to un-taint the
variable:

$cmd =~ /(.*)/;

....then either:

$cmd = $1;
system($cmd);

....or

system($1);

....but neither one works - the command is not invoked. Still acts like
it is tainted.

This works (as indicated by the faq, because the command isn't coming
from a variable), but does not detach (is there a way to MAKE it
detach?):

system("/usr/src/client/client",$p1,$p2,$p3,$p4);

....because it does not detach, the perl script still hangs the web
page, the web page eventually times out, which also (sigh) stops the
command 'client' from executing for some reason.

This also times out and kills the web page AND the running command
'client':

exec("/usr/src/client/client",$p1,$p2,$p3,$p4);
exit;

Now, about taint apparently being on. The shebang line in my script is
vanilla, just says #!/usr/bin/perl

The /etc/httpd/conf.d/perl.conf file does NOT contain a command to
turn on taint, just commented out areas and the single lonely command:

LoadModule perl_module modules/perl.so

the /etc/httpd/conf/httpd.conf file contains no reference to perl at
all, other than a remark about pl being a language extension.

I am running in a non-secure server, and so httpd.conf is the place
where I would expect to find such a command (assuming it wasn't in
conf.d/perl.conf, of course.)

So I have these questions:

1) Is there a way I can un-taint the $cmd variable so I can run it
just this way:

$cmd = "/usr/src/client/client $p1 $p1 $p3 $p4 &";
#un-tainting magic supposedly like: $cmd =~ /(.*)/; $cmd = $1;)
system($cmd);

2) Why is taint on in the first place, since there is no -T flag, and
no command I can find to the webserver and hence to the embedded perl
interpreter?

As I mentioned at the start of this missive, this used to work fine on
an older system. The reason we're trying to move it to the newer
system is the newer system is one heck of a lot faster, and this is a
really compute-intensive process. I'm highly motivated, but equally
confused at this point. :(

I would really, really appreciate some insight into this. Thanks in
advance.

Ben
 
A

A. Sinan Unur

(e-mail address removed) (Ben) wrote in @posting.google.com:
I have a .PL script that is attempting to invoke an external command
....

Under perl 5 and redhat 6.0, this was no problem. Under perl 5.8.0 and
redhat 9, perl would not invoke the command, in a manner that leads me
to think it was a result of taint being on.

What is the error message?

Sinan.
 
D

Darren Dunham

Ben said:
system($1);
...but neither one works - the command is not invoked. Still acts like
it is tainted.

What leads you to think that it "acts like it is tainted"? What error
messages are you getting?
This works (as indicated by the faq, because the command isn't coming
from a variable), but does not detach (is there a way to MAKE it
detach?):
system("/usr/src/client/client",$p1,$p2,$p3,$p4);

No. Because this avoids the shell (the purpose), you can't tell the
shell to run it in the background.

More and more I begin to suspect that this isn't the result of tainting
at all...

However, if you want to "detach" it, either run it in a shell
explicitly..

("/usr/bin/sh","-c","/usr/src/client/client",$p1,$p2,$p3,$p4,&)

or fork/exec yourself.

unless (fork) # no error checking here..
{ # this is the child
exec("/usr/src/client/client",$p1,$p2,$p3,$p4);
}

# only parent reaches here...
This also times out and kills the web page AND the running command
'client':
exec("/usr/src/client/client",$p1,$p2,$p3,$p4);
exit;

That exit line is never executed. The perdoc on exec will tell you
why. If you want to do that, use a fork as above.
So I have these questions:
1) Is there a way I can un-taint the $cmd variable so I can run it
just this way:

Why do you think it's tainted?
 
A

Alan J. Flavell

I have a .PL script that is attempting to invoke an external command
once launched from a web page.

Yeah, but your real trouble seems to be that you're woffling on about
generalities when it appears you might have a real problem, but with
some real problem symptoms that you haven't told us about.

Is it perhaps that you haven't found out how to get errors back from
your server-side process yet? That's something worth learning
(check CGI::Carp, fatals to browser, etc.)
The command takes quite a while to run
(could be hours), so it is important that it detach so the user can
come back later.

OK: nothing that hasn't been discussed before - but this seems to
be a bit tangential to what's going wrong...
I understand why I might
want taint under other circumstances, even most other circumstances...
It just doesn't apply here at all.

I think you're misguided. Taint can protect against *unintended*
nasty accidents as well as against deliberate misuse. I'd need much
more backing than what you've given here before I'd want to dispense
with it, and that's -assuming- a limited user base with generally good
intentions.
This creates the command string I want:

$cmd = "/usr/src/client/client $p1 $p1 $p3 $p4 &";

Then:

system($cmd);

You appear to be asking for shell expansion, which could potentially
be a problem. Maybe you want the multiple arguments form of system()
(oh, right, you do that later... but actually I think you'd really
want to fork() explicitly)
Now, under RH9 and P5.8 (or the apache perl module, which I suspect is
actually handling this),

You're using mod_perl ? (surely you wouldn't be using mod_perl
without being aware of it?)
perl is acting like taint is on (I didn't
turn it on, and I can't find where it's turned on, more on that in a
bit.)

Not sure, but maybe you're looking for this
http://perl.apache.org/docs/1.0/guide/config.html#Taint_Checking
So, as instructed in the perl faq, I attempt to un-taint the
variable:

$cmd =~ /(.*)/;

(thereby discarding any benefit you could have got from this useful
check), but don't forget that a number of aspects of the environment
are also taken into account in determining whether system() involves a
taint situation.
...but neither one works - the command is not invoked. Still acts like
it is tainted.

You owe us some information on the error!!
This works (as indicated by the faq, because the command isn't coming
from a variable), but does not detach (is there a way to MAKE it
detach?):

system("/usr/src/client/client",$p1,$p2,$p3,$p4);

I think you're muddling up two things here. There's a standard way of
spawning off a disconnected long-running task in such a way that the
task that invoked it doesn't need to wait for it.
...because it does not detach, the perl script still hangs the web
page, the web page eventually times out, which also (sigh) stops the
command 'client' from executing for some reason.

Indeed.

I'm sure Randal's webtechiques have examples of this sort of thing.
Now, about taint apparently being on. The shebang line in my script is
vanilla, just says #!/usr/bin/perl

The /etc/httpd/conf.d/perl.conf file does NOT contain a command to
turn on taint, just commented out areas and the single lonely command:

For that kind of stuff you'd be better asking on a server
configuration group (after having duly consulted the relevant
documentation, of course), but if mod_perl is getting in your hair,
why not run a straight-up-and-down CGI script? If the task is going
to take hours, then the sub-second extra overhead of starting up CGI,
relative to using mod_perl, is neither here nor there, so if it
simplifies your life, why not go for it?

But I still would NOT counsel you to toss aside the benefits of taint
checking so lightly!

good luck
 
D

Darren Dunham

Abigail said:
Darren Dunham ([email protected]) wrote on MMMDCCIII September
MCMXCIII in <URL:^^
^^ > This also times out and kills the web page AND the running command
^^ > 'client':
^^
^^ > exec("/usr/src/client/client",$p1,$p2,$p3,$p4);
^^ > exit;
^^
^^ That exit line is never executed. The perdoc on exec will tell you
^^ why. If you want to do that, use a fork as above.

If you look up the perldoc on exec yourself, you see in the first
paragraph that it *is* possible that 'exec' returns.

Quite correct. The bare 'exit' made me assume (perhaps incorrectly)
that the intent was to run the exec and then exit the child on normal
return. My statement was directed at the general case and not the
complete case.
 
B

Ben

A. Sinan Unur said:
What is the error message?

There is no error message. system() returns zero, and $! is empty. But
my command is definitely not running. Keep in mind that I *can* make
it run just by using the usage form system("cmd","param","param",etc).
It works fine, and the input is thoroughly laundered anyway, it can't
really be fed anything from the perl script that would make it quietly
go away without an error code.

I've printed out the strings, which are entirely vanilla, and there is
no difference, except of course I can't add the output redirection
(>output.txt) or the detach (&) for the shell expansion that I can in
the form:

system("cmd param1 param2 >output.txt &");

....which is what returns me the 0 and empty $!, yet does not invoke my
command at all.

Since I wrote my original cry for assistance, using a helpful hint
here, I went to a fork and close streams approach. Although this does
not give me the diagnostic output file (>output.txt) it does detach
properly and so the main functionality I needed is there. If I need
the output.txt file I can open it directly and use fprintf instead of
printf to pump the messages out to it.

So my problem has been worked around, but the reason system() is not
working in this instance still eludes me.

--Ben
 
B

Ben

Darren Dunham said:
What leads you to think that it "acts like it is tainted"?

The behaviour - if I use ("literalCMD",$param,$param,etc) form of
system(), it works. If I use the ("$aggregateCMD $param $param etc.")
form, it doesn't. This behaviour is essentially described in the
perlsec docs; taint stops you if you use the latter, but not the
former, if it thinks you're tainted. That behaviour is what is
happening, so I was thinking taint. I did attempt to see if I could
detect taint being on just prior to invocation, but I could not. There
is some verbiage at the top of perlsec that talks about perl turning
on taint, apparently at the time the command is invoked: "Perl
automatically enables a set of special security checks, called taint
mode, when it detects its program running with differing real and
effective user or group IDs." I thought this might be my problem, in
which case it would seem that it happens at runtime, and no check just
prior to running would catch it. Hence my request for why taint might
be on, even though I had not asked for it.
What error messages are you getting?

No error messages. system() returns 0 and !$ is empty. The command is
simply never invoked. I've verified this several ways - output files,
sleeping the command a bit at start and then using process searches,
and of course, it doesn't do anything it's supposed to. It works
perfectly if it gets started at all, though, either using fork or the
multiparameter form of system or exec.
However, if you want to "detach" it, either run it in a shell
explicitly..

("/usr/bin/sh","-c","/usr/src/client/client",$p1,$p2,$p3,$p4,&)

or fork/exec yourself.

I did that. fork() works for me, it detaches and runs to completion.
Thank you for the suggestion.
That exit line is never executed. The perdoc on exec will tell you
why. If you want to do that, use a fork as above.

I was concerned about the exec() call failing, as things weren't
working right. Since if exec() fails, it does return, I think it's
important to leave that in there. Correct me if I'm wrong, please.
Why do you think it's tainted?

I don't, actually. I think might *perl* think it's tainted. As to why,
I have no idea. I've done all the required hoop-jumping to
"decontaminate" my variables, path and environment, I didn't ask for
taint checking in the first place, and this used to work 100% under
redhat 6 and Apache 1.3. It's the behaviour that led me to think
taint, as I described above.

--Ben
 
B

Ben

Yeah, but your real trouble seems to be that you're woffling on about
generalities when it appears you might have a real problem, but with
some real problem symptoms that you haven't told us about.

I apologize for any "woffling." I was attempting to provide all of the
information I had. So as to maximize the chance of someone seeing what
I was screwing up.
Is it perhaps that you haven't found out how to get errors back from
your server-side process yet? That's something worth learning
(check CGI::Carp, fatals to browser, etc.)

No errors are reported at the call level; system() returns 0 (though
it does not invoke my command) and $! is empty. I have not explored
carp or fatals to browser. I will; thank you for the pointers.
OK: nothing that hasn't been discussed before - but this seems to
be a bit tangential to what's going wrong...

Yes, however, it explains why I need to detach, which I figured might
come up if I didn't explain it.
I think you're misguided. Taint can protect against *unintended*
nasty accidents as well as against deliberate misuse. I'd need much
more backing than what you've given here before I'd want to dispense
with it, and that's -assuming- a limited user base with generally good
intentions.

Ok. My user base is two people in their sixties who have no interest
in anything whatsoever but making their business succeed. The machine
is on a hard-wired intranet, and has NO link to the Internet. They,
and I, are the entire "user base." The form that feeds the data to my
lower-level command uses drop down menus for all but one of the
parameters, and the last takes in a search term which is laundered
thusly:

$part = partlaundry($in{'partnumber'});
....
exit;

sub legalpchar
{
my $char = shift;
my $val = ord($char);

if ($char eq "*") { return(1); }
if ($val >= ord("A") && $val <= ord("Z")) { return(1); }
if ($val >= ord("a") && $val <= ord("z")) { return(1); }
if ($val >= ord("0") && $val <= ord("9")) { return(1); }
return(0); # forget it.
}

sub partlaundry
{
my $text = shift;
my $len = length($text);
my $rez = "";
my $i;
my $char;

for ($i=0; $i<$len; $i++)
{
$char = substr($text,$i,1);
if (legalpchar($char) == 1)
{
$rez .= $char;
}
}
return($rez);
}

....these terms are then fed to my command. I grant you that one of
these folks, or a future hire (not likely, but assume it could happen)
*could* learn CGI, generate a spoof form with the (in)appropriate
input element names and HTTP_REFERER, and feed dangerous crud like rm
-rf into my command line (though they'd have limited userlevel and
privs at that point) but frankly, I think it's lot more likely that if
someone wanted to do that kind of harm, they'd pick up the machine and
slam it down on a hard surface a couple of times and crash the hard
drives in a much easier and more permanent fashion. In which case the
archived tars of everything that accrue to the backup machine would
probably save the day, anyway. I really don't think that I need taint
to back me up here.
You appear to be asking for shell expansion,

Right. I wanted the detach (and also >output.txt), which was easily
accomplished under RH6 and Apache 1.3; it no longer works, which is
what caused all this ruckus.
which could potentially
be a problem. Maybe you want the multiple arguments form of system()
(oh, right, you do that later... but actually I think you'd really
want to fork() explicitly)

Yes, that's where I ended up yesterday. That works, though I need to
generate my messages to a regular file instead of STDOUT now.
You're using mod_perl ? (surely you wouldn't be using mod_perl
without being aware of it?)

Why not? The webserver works, why would I want to worry about who's
running what unless it breaks? Now it's broken, I looked, and mod_perl
is being loaded. You don't have to know everything about everything to
get a job done, you know. When stuff works, you tend not to look too
hard at it. Life's too short.

No, I checked for this. It's not on, as I mentioned I perused the
perl.config in /etc/httpd/conf.d, and also httpd/conf/httpd/conf.
(thereby discarding any benefit you could have got from this useful
check),

Exactly. Good of you to note my intent. :)
but don't forget that a number of aspects of the environment
are also taken into account in determining whether system() involves a
taint situation.

I hit the environment variables and the path. Anything else?
You owe us some information on the error!!

Man, if I HAD any, I'd give it to you. All I have is system() returns
0, $! is empty, the string is EXACTLY what it was for RH6/Apache 1,3,
and my command is not started as indicated by no output, no process,
no results.
But I still would NOT counsel you to toss aside the benefits of taint
checking so lightly!

Not all systems are exposed to hostile attacks. Extreme paranoia is
sometimes simply... extreme. Given the situation, I used menus to
limit the choices to the inputs I expect, and laundering to prevent
unintended trash from getting into the one relatively freeform field I
have. That's enough. I don't need or want taint. At this point, since
I worked around the problem with fork(), I simply want to understand
why system() doesn't work. All I want to know about taint right now is
how it might be interfering, or not, or what else it might be.

Thanks for your reply.

--Ben
 
S

Steve Grazzini

Ben said:
A. Sinan Unur said:
What is the error message?

There is no error message. [ snip ]

system("cmd param1 param2 >output.txt &");

...which is what returns me the 0 and empty $!, yet does not invoke my
command at all.

When you do this, the subshell forks and exits immediately, which is
why your system() succeeds. If the "background" process then fails
with a shell error (EPERM on output.txt or cmd) it will just dump the
diagnostic to stderr.
 
A

Alan J. Flavell

No errors are reported at the call level; system() returns 0 (though
it does not invoke my command) and $! is empty. I have not explored
carp or fatals to browser. I will; thank you for the pointers.

CGI::Carp is useful stuff, indeed - but in this instance I now realise
that there's a missing link - my apologies if I misled you. More on
that in a moment.
Ok. My user base is two people in their sixties who have no interest
in anything whatsoever but making their business succeed.

Well, I'm in my sixties, I don't think that's a criterion one way or
the other. I say again: protection against *unintended* accidents.

If you're already protecting them against that, then well and good.
Right. I wanted the detach (and also >output.txt), which was easily
accomplished under RH6 and Apache 1.3; it no longer works, which is
what caused all this ruckus.

I now realise that of course the exit code from spawning-off the new
process isn't the result of the process itself, but only indicates
that the spawning-off went OK. Mea culpa. *slap*

The whole point of spawning-off a disconnected process without
hanging-up the process that started it, means that i/o streams need to
be severed, so inevitably it means one needs to look elsewhere for the
errors from the spawned-off process.
Yes, that's where I ended up yesterday. That works, though I need to
generate my messages to a regular file instead of STDOUT now.

Yup, I think so. Whichever of the various mechanisms are used to
do the spawning, this kind of recourse is going to be needed, i guess.
I hit the environment variables and the path. Anything else?

Can't think of any. You already found your way to perlsec, evidently.
Man, if I HAD any, I'd give it to you. All I have is system() returns
0, $! is empty,

Yup, it was my fault for not realising why that would be...
the string is EXACTLY what it was for RH6/Apache 1,3,
and my command is not started as indicated by no output, no process,
no results.

Can you arrange to run this thing as a straightforward CGI script? You
seemed to think that it might be getting run via mod_perl (I think it
would repay looking into that, despite you implying you didn't want it
to be your concern...)

Right now I'm looking at
http://www.perldoc.com/perl5.8.0/pod/perlsec.html#Security-Bugs
and wondering whether your problems may not be fairly adjacent.
Not all systems are exposed to hostile attacks.

Mistakes don't have to be hostile.
Extreme paranoia is sometimes simply... extreme.

Well, I've said it twice, I won't try again. But I think you'll find
a few others around here who would support the general idea of
protecting even from unintended accidents.

Good luck
 
T

Tad McClellan

Ben said:
which is laundered
thusly:

$part = partlaundry($in{'partnumber'});
...
exit;

sub legalpchar
{
my $char = shift;
my $val = ord($char);

if ($char eq "*") { return(1); }
if ($val >= ord("A") && $val <= ord("Z")) { return(1); }
if ($val >= ord("a") && $val <= ord("z")) { return(1); }
if ($val >= ord("0") && $val <= ord("9")) { return(1); }
return(0); # forget it.
}

sub partlaundry
{
my $text = shift;
my $len = length($text);
my $rez = "";
my $i;
my $char;

for ($i=0; $i<$len; $i++)
{
$char = substr($text,$i,1);
if (legalpchar($char) == 1)
{
$rez .= $char;
}
}
return($rez);
}


There is no pattern match in that code, so nothing can possibly
become untainted.

When you copy tainted data, the copy is tainted too.

And that seems like a whole lot of code.

Can't it all be replaced with just:

# untested
sub partlaundry {
my $text = shift;

return '' unless $text =~ /^([a-zA-Z0-9*]*)$/;
return $1;
}

??
 
S

Steve Grazzini

Ben said:
The behaviour - if I use ("literalCMD",$param,$param,etc) form of
system(), it works. If I use the ("$aggregateCMD $param $param etc.")
form, it doesn't.

It's true that "system LIST" is exempt from taint-checking,
but if this were actually a taint problem, you'd get an error
of the form

Insecure dependency in foo ...

Insecure $ENV{FOO} ...
No error messages. system() returns 0 and !$ is empty.

Taint-checking doesn't cause things to silently fail!

Look, if you don't know what's going wrong, please just show us a
small-but-complete script that demonstrates the behavior.

If it fails from cron/CGI/etc and you haven't even tried it from
the command-line, you should go try it from the command-line first.
I was concerned about the exec() call failing, as things weren't
working right. Since if exec() fails, it does return, I think it's
important to leave that in there. Correct me if I'm wrong, please.

Well you're right that exec() can fail, but just exit() isn't
very useful either.

exec $whatever;
die "couldn't exec $whatever: $!";
 
F

foo2

There is no pattern match in that code, so nothing can possibly
become untainted.

When you copy tainted data, the copy is tainted too.

Methinks you're not paying attention. That code simply ensures that
the user hasn't fed something evil into the one variable he didn't get
from menus. The untainting is done later (and quite effectively and
correctly, it seems to me, given that he really didn't want taint at
all and he thought it might be on.)


Walt
Software Engineer
Black Belt Systems
pages: http://www.blackbeltsystems.com/
email: http://www.blackbeltsystems.com/contact.html
 
A

Alan J. Flavell

Methinks you're not paying attention.

Seems to me that your evaluation of Tad is erroneous.
The untainting is done later (and quite effectively and
correctly,

I'd rather say that the benefits of the taint check are wilfully
discarded at that point. "effectively" and "correctly" would not be
my terminology of choice for doing that, in the circumstances.
it seems to me, given that he really didn't want taint at
all and he thought it might be on.)

Well then, take away that "given". There's a right and a wrong way to
go about this, and I see no grounds for finding a string of excuses
for using the wrong way, when the right way could be simpler _and_
more transparent. And could well serve as a safety-harness to protect
the programmer from potential consequences of their own assumptions.

We still aren't really any nearer to locating the original problem,
but this kind of special pleading is not really getting us any closer,
if I may say so.
 
H

Helgi Briem

There is no error message. system() returns zero, and $! is empty.

Error messages from programs run under system are not
returned in $!. They are in $?.

system ($cmd) = 0 or die "Cannot run $cmd:$?\n";
 
K

ko

Helgi said:
Error messages from programs run under system are not
returned in $!. They are in $?.

system ($cmd) = 0 or die "Cannot run $cmd:$?\n";
^^^

i think you forgot a '=' :)

system ($cmd) == 0 or die "Cannot run $cmd:$?\n";

keith
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,764
Messages
2,569,567
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top