Link to home
Start Free TrialLog in
Avatar of Rowby Goren
Rowby GorenFlag for United States of America

asked on

Preventing script from being called by other domain

Hi,

I don't write perl. I just configure prebuilt scripts.  I need to add a line of code that will prevent the script from being called by any other domain other than the domain that the script is installed on.

Here is a copy and paste of  the current script.  Can you insert the necessary line(s) of code?

For example, this script is installed at www.foobar.com (domain example has been changed from the actual domain for privacy purposes.)


Thanks!
============

$|++;
use Net::SMTP;
use CGI;
$q = new CGI;

$foobarfolks='<jjones@foobar.com>, <jsmith@foobar.com>';
$toddemail='<jones@foobar.com>';
$message="Thank you for contacting US.;
$user_subject='Thank You from US';

foreach ($q->param){
    $$_=$q->param($_);

    next if /^to$/;
    $$_=~ s/^\s+//g;
    $$_=~ s/\s+$//g;
}

if (!($to)){
    print "Content-type: text/html\n\n";
    print "Please go back to the form and enter your email address.<BR>\n";
    exit;
}

if (!($firstname)){
    print "Content-type: text/html\n\n";
    print "Please go back to the form and enter your First Name.<BR>\n";
    exit;
}

if (!($lastname)){
    print "Content-type: text/html\n\n";
    print "Please go back to the form and enter your Last Name.<BR>\n";
    exit;
}

@new_from=split(/,\s/,join(', ',$from,$foobarfolks)); ## NOTE the , inside the single quotes

if (!($to)){
    print "Content-type: text/html\n\n";
    print "Please go back and enter your email address<BR>\n";
    exit;
}

#     Email the user who posted the form, a TY note!!
$smtp = Net::SMTP->new('localhost'); # connect to an SMTP server
$smtp->mail($toddemail);     ##     use the sender's address here
$smtp->to($to);     ##     recipient's address
$smtp->data();
$smtp->datasend("Subject: ".$user_subject."\n");
$smtp->datasend("\n");
$smtp->datasend($message);


$smtp->dataend();                   # Finish sending the mail

##  Close the connection
$smtp->quit;

foreach $new_from (@new_from){
    $smtp = Net::SMTP->new('localhost'); # connect to an SMTP server
    $smtp->mail($to);     ##     use sender's address here
    $smtp->to($new_from);     ##     recipient's address. Use new_from, not from
    $smtp->data();
    $smtp->datasend("To: ".$new_from."\n");
    $smtp->datasend("Subject: ".$your_subject."\n");
    $smtp->datasend("Copies of this email sent to: $foobarfolks \n\n");
    $smtp->datasend("\n");
    $smtp->datasend("Name : $name\n");
    $smtp->datasend("Email : $to\n");
    $smtp->datasend("Home Phone : $homephone\n");
    $smtp->datasend("Work PHone : $workphone\n");
    $smtp->datasend("Referer     : " . $q->referer . "\n");
    $smtp->datasend("Remote Host : " . $q->remote_host . "\n");
    $smtp->datasend("User Agent  : " . $q->user_agent . "\n");
     
    $smtp->dataend();                   # Finish sending the mail
   
    ##  Close the connection
    $smtp->quit;
}


##     Redirect to the Thank you page.
print "Location: https://www.foobar.com/html/content/response1.shtml\n\n";
Avatar of avner
avner

Why not protecting the directory using the web-server tools ?

ASKER CERTIFIED SOLUTION
Avatar of trevorw
trevorw

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of ozo
You probably wanted to check $ENV{REMOTE_ADDR} not HTTP_REFERER
No... REMOTE_ADDR will give you the visitor's IP or host, not the domain which called the script.
Ozo's right. HTTP_REFERER can easily be forged. REMOTE_ADDR may not give you the visitor's true IP address (thanks to proxies, firewalls, etc) but it will not show you an address _inside_ your network if the originator is outside.
What if you check for the domain at the begining of the HTTP_REFERER?

e.g.,

if($ENV{'HTTP_REFERER'} !~ /^http://www.foobar\.com/) {
print "Error! Invalid referer!";
exit;
}

Would that work better?

OKSD
What if you check for the domain at the begining of the HTTP_REFERER?

e.g.,

if($ENV{'HTTP_REFERER'} !~ /^http://www.foobar\.com/) {
print "Error! Invalid referer!";
exit;
}

Would that work better?

OKSD
Avatar of Rowby Goren

ASKER

(Hi all -- I'm lurking while you Experts work this out :)  )

Thanks

Rowby
Actually, I'm not really an expert, I know a few things, I code in a practically illegible free-hand manner and do things inefficiantly, but I manage to get my task done :D

OKSD
Hey OKSD - Join the club :)
As people say, HTTP_REFERER (and all other headers) can be forged.

If this script is an internal script (its called by another script) then the REMOTE_ADDR would be more interesting than the HTTP_REFERER, but I'm guessing from the look of the script that its run by the general public filling out a form?

There are very few ways to be 'certain enough' that the user comes from the correct place.

This method has been discussed above:
if($ENV{'HTTP_REFERER'} !~ /^http://www.foobar\.com/) {
   print "Content-Type: text/plain\n\n";
   print "Error! Invalid referer!";
   exit;
}
Now as people say, putting this into your code will check where the user is *coming from*, that is, which page the form was on. However, it can be forged. Who would forge it? The only people who would forge it would be people who seriously wanted to mess with something. Like if they could use this to send some third party an email that looked like it came from you. However your script seems to just send a canned response. Any hacking would be for fun more than anything.

To be more certain, send the form from a CGI. This CGI would associate a REMOTE_ADDR and HTTP_USER_AGENT with a time-limited obscure key.

This key becomes a hidden field in your form. When the form is submitted, you check that the key matches the new REMOTE_ADDR and new HTTP_USER_AGENT and that the match hasn't expired.

Of course, this is still forgable, its just a hell of a lot more trouble for hackers to go to (and the question remains why they would).
H Biglug

It is interteresting that you suggest using a key.  Someone else somewhere else suggested using this key system.

Can you tell me how to create this key.  Again, I don't write perl. :)

I have increased the points to 250

Forging HTTP_REFERER is certainly possible, but not as common as it being empty.

Many browsers/proxies simply strip it out, so relying on HTTP_REFERER is potentially going to prevent legitimate users from using the script.
Absolutely DO NOT USE HTTP_REFERRER as it can be easily spoofed!

Use REMOTE_ADDR instead...

Just add the following lines to the beginning of your code:
Put in your "allowed" networks and domains in the @ALLOWED_NETWORKS variable.  If ALL your client ip's are reverse resolvable, then you can omit putting in networks, just put in your domain(s).

-----------------------------------------------------------

use Net::DNS;

my @ALLOWED_NETWORKS = qw/161.151.  .yourdomain.com/;


if (! &ValidClient(\@ALLOWED_NETWORKS)){
   print "Content-type: text/html\n\n";
   print "Your are an illegal user. ".
          The authorities are being notified...<BR>\n";
   exit;
}


sub ValidClient {
my $networks = shift;
my $DNSObj;
my $DNSQuery;
my $client     = $ENV{'REMOTE_ADDR'};
my $rr;
my $client_hostname = $client;
my $network;
my $rc = 0;


if ($client =~ /^[\d\.]+$/) {
  $DNSObj     = Net::DNS::Resolver->new();
  $DNSQuery = $DNSObj->search($client);

  if ($DNSQuery) {
    foreach my $rr ($DNSQuery->answer) {
      next unless $rr->type eq "PTR";
      $client_hostname = $rr->rdatastr;
      last;
    }
  }
  $client_hostname =~ s/\.$//;
}

foreach $network (@{$networks}) {
  if ($network =~ /^[\d\.]+$/) {
    $rc = 1 if ($client_hostname =~ /^${network}.*$/i);
  }
  else {
    $rc = 1 if ($client_hostname =~ /^.*${network}$/i);
  }
}

return $rc;
}

----------------------------------------------------------
rowby: all these guys are wrong.

You want to check that $ENV{HTTP_HOST} is the current domain.

$ENV{HTTP_HOST} : the current domain for this page, even on a name based server (server with multiple domains).

$ENV{HTTP_REFFERER} : the URL of the page that sent the user to this page by a link (not what you want).

$ENV{REMOTE_ADDR} : the address of the client accessing your page (not what you want).
Oh and as for code, stick this in right at the beginning:

exit(1) unless ($ENV{HTTP_HOST} eq "www.foobar.com");
Rowby,

It appears you will have to clarify your original question.

Do you want your script to only "run on the web server whose virtual host domain is foobar.com" ?

Or...

Do you want the script to only answer to "clients whose workstations are from the domain foobar.com" ?

If the answer to question #1 is "yes" then you only need to use the HTTP_HOST enviornment variable.


However, if the answer to question #2 is "yes", then DO NOT USE HTTP_HOST as that is wrong wrong wrong, instead use REMOTE_ADDR (my code snippet above) or a combination of USER_AGENT and HTTP_HOST as a "key" (also mentioned above by another person.)

Hi

I hope I can clarify what is needed.  

We want to prevent soemone from accessing the perl script who from another domain.

For example.  Our web server is www.foobar.com.  THe form which accesses the perl script is form.html and is on the foobar.com webserver.  The perl script is in the www.foobar.com/cgi-bin

We want to prevent someone from www.spam.com from recreating our the form.html and putting it on the www.spam.com server and accessing our perl script in the cgi-bin of gthw www.foobar.com server.

I hope that clarifies things.

(I must say, the "key" suggestion by biglug, also sounds "appealing".  But I'm ignorant in these things!)

ROwby
Also note that HTTP_HOST is not a standard CGI/1.1 environment variable.

See http://hoohoo.ncsa.uiuc.edu/cgi/env.html for the full list.
rowby: based on your clarification, you want to check the HTTP_REFFERER then.
For a start, HTTP_REFFERER doesn't exist, it is HTTP_REFERER

Also, please read through the entire post to see why using HTTP_REFERER is not a good idea.
Tintin: you get a gold star for good spelling. Unfortunatley not for knowledge, though. :(

rowby: Don't listen to those who tell you that using HTTP_REFERRER is not a good idea, it is. Yes, it can be spoofed, since it's just a variable passed to you from the client in the HTTP header... but if you understand how HTTP and networking in general work, then you'll understand that it is theoretically impossible to determine without a doubt where a client came from. You have to take their word for it.

Further more, it's a part of the HTTP 1.1 specification and it's quite reliable. The reason it is quite often not there is because it is not passed along if the referring source is not a valid URI; for example if a user types in the URI from their keyboard. When you're on the receiving end of your own form, however, it's going to be there quite certainly.
It's hard to know how to help you Rowby, since we don't have all the information needed to write a script for you. To implement the KEY idea mentioned by Biglug, your initial page containing the form has to be dynamically generated, too.


If the degree of security you're looking for is not too stringent, then HTTP_REFERER may be good enough. The lines suggested by trevorw will suffice.


As it currently is written, your script can be hacked to send a canned email (your content, with your site as the originator) to an arbitary recipient and will simultaneously generate an internal junk email with non-dangerous content to your foobarfolks. Biglug is correct in saying that this isn't a wide open loophole for hackers. Furthermore, there is no protection possible to prevent a hacker from sending at least one e-mail to an arbitrary address via an auto-responder form like this.

If you're looking to cut down on abuse, you'd want to keep track of recent e-mail addresses and their corresponding REMOTE_ADDR values. If either of these appears as a duplicate, you may want to hold off on sending an email reply (sending back a suitable warning webpage is okay). Having a throttle on the overall rate at which your site will display this form or accept accesses to the script might also help you avoid being an unwitting party to more sophisticated abuse.


Since you're not in a position to implement something complicated, I suggest you go with trevorw's suggestion.

If you really do have more stringent requirements, you may need to hire someone with some expertise. As you can see, we volunteer question answerers vary a lot in what we know and how we read an ambiguous situation. A lot of the problems we have on the Internet are caused by poorly thought out responses to particular problems that either do not respond to the real problem or that have side effects at least as bad (and harder to solve) as the original problem.
strAtEdgE,

Where is my knowledge lacking?

HTTP_REFERER (note the spelling again) is OK in most instances, but you are still going to isolate a certain percentage of your audience that use software/browser to prevent it being sent.

See http://www.w3.org/Protocols/HTTP/1.1/spec.html
Thanks. Sorry for the delay in awarding this one