in reply to CGI DB interface, secured (hopefully)
Go back and reread sauoq's suggestions from Re: CGI DB interface, have you done *all* of them. strict and Taint are very important, they help you from making mistakes in the first place. :)
now for my suggestions,
I didn't notice any security problems there but I didn't look to closely either as it's so hard to read.
Update: I reformatted the original script and found that the script itself does no authentication, I'm assuming that you protect it some other way. Anyway I reformatted it and added a bunch of comments, No it doesn't run now but it does give a number of pointers. Update beyond the readmore.
#!/usr/bin/perl -wT use strict; # Yes you need to make it work with this and Taint ### # Modules use CGI; use Apache::Htpasswd; ### # Init $ENV{'PATH'} = '/var/www/cgi-bin/:/bin/:/usr/bin/'; $update = "was created by "; $query = new CGI; print $query->header; $login_used = `id`; # Take a look at perlvar # specifically $) and $> $date = `date`; # Take a look at POSIX::strftime chomp($login_used); chomp($date); &heredocs; # This is named ambiguously # and won't work with strict # without some serious changes &script; # very ambiguous $filepath = $ENV{'DOCUMENT_ROOT'}; $logpath = "$filepath/.secure"; # why would your logs be # kept under your webroot? $htp = new Apache::Htpasswd("$logpath/.htpasswd"); $query = CGI->new(); # There is already a CGI object # in $query. @names=$query->param; # Do you really care about *all* # the params? $count=0; foreach $name(@names) { # Why look at all the params # You are only interested in a few of them @values[$count] = $query->param($name); # This won't do what you w +ant # it's actually $values[$c +ount] # and so will only get the + first # value. if ($name eq "name") { $nameofuser = $values[$count]; } elsif ($name eq "login") { $formlogin = $values[$count]; } elsif ($name eq "pw") { $formpw = $values[$count]; if ( ($formlogin =~ /%3A/) # This test doesn't do what yo +u want || ($formlogin =~ /%2F/) # CGI handles escaped characte +rs for you || ($formlogin =~ /%3B/) # so these will match a litera +l %2F || ($formpw =~ /%3A/) || ($formpw =~ /%3B/) # What happens when 'pw' is t +he first || ($formpw =~/%2F/) # Param? || ($nameofuser =~ /%3A/) || ($nameofuser =~ /%3B/) | ($nameofuser =~ /%2F/)) # | <- Typo? should be || { print $illegal; die("Data contained malicious characters"); } } elsif ($name eq "logins") { $delvalue="yes"; $deluservalue=$values[$count]; } elsif ($name eq "viewlog") { $viewlogvalue=$values[$count]; } elsif ($name eq "viewdb") { $viewdbvalue=$values[$count]; } elsif ($name eq "chpw") { $chpw=$values[$count]; } elsif ($name eq "oldpw") { $oldpw=$values[$count]; } else { $array[$count]=$value; # Where did $value come from? $count++; } $count++; # If the else above is executed $coun +t # get's incremented twice. } $nameofuser =~ /([\w]+)/; # You should probably show a error $untainted_name = $1; # if any of these fail to match $formlogin =~ /([\w]+)/; $untainted_formlogin = $1; # Why not just assign back to the $formpw =~ /([\w]+)/; # Original since you will show a $untainted_formpw = $1; # Error if any of them are bad. $oldpw =~ /([\w]+)/; $untainted_oldpw = $1; # Passwords can only be letters ? if ($viewdbvalue eq "on") # Since you can do both this could be { # written as &db_viewer; # db_viewer() if ($viewdbvalue eq ' +on'); } # log_view() if ($viewdbvalue eq 'on' +); elsif ($viewlogvalue eq "on") # log() if ($viewdbvalue ne 'on' && { # $viewlogvalue ne 'on'); &log_viewer; # contin(); } else { &log; } sub log { # And what does this do? if (-e ($logpath)) { &eraser; } else { mkdir("$logpath", 0755); &eraser; } } sub mainprog { # This isn't the main program # it just opens a log file # and calls usercreate if (-e ("$logpath/.log")){ open(LOG, ">>$logpath/.log") || die("Could not open log file t +o add entry."); } else { open(LOG, ">$logpath/.log")|| die("Could not create log file a +t $logpath/.log"); } &usercreate; } sub contin { print $footer; } sub log_viewer { $logupdater = $logupdate; unless ($viewlogvalue eq "off") # The only way you can get # here is if $viewlogvalue # eq 'on' { # why not a here doc? print "<h2 align=\"center\">Users in database</h2>"; print "<br>"; print "<table border=\"6\" align=\"bottom\" bordercolor=\"#FF0 +000\">"; print "<tr><td><b>Login</b></td><td><b>Added or modified?</b>< +/td><td><b>by user</b></td><td><b>Creator logged in as</b></td><td><b +>Date</b></td></tr>"; open (DB, "$logpath/.log") || die("Could not open $logpath/.lo +g for tabling"); @dbarray=<DB>; # Should use a while (<DB>) foreach $entry(@dbarray) # to avoid reading the whole thing { # into memory ($usen, $creatr)=split(/&/, $entry); # Take a look +at ($creatir, $loggedinas)=split(/,/, $creatr);# Text::CSV ($loggedas, $day)=split(/-/, $loggedinas); # or a DB mo +dule ($daychanged, $action)=split(/¬/, $day); # while simpli +fy ($creatr)=$creatir; # this whol +e mess ($loggedinas)=$loggedas; print "<tr><td>$usen</td><td>$action</td><td>$creatr</td>< +td>$loggedinas</td><td>$daychanged</td></tr>"; } print "</table><br><br><br><br>"; close(DB); &contin; # contin? continue to what? (oooh print_footer() # well why didn't you say so?) } } sub db_viewer { unless ($viewdbvalue eq "off") # You can't get here if it eq 'o +ff' # so if you really think it could # happen die() if it does. { print "<form name=\"jim\" action=\"passwords.cgi\">"; print "<input type=\"hidden\" name=\"del\" value=\"no\">"; print "Your name: <input type=\"text\" name=\"name\ +">"; $counter=0; open (DB, "$logpath/.htpasswd") || die ("Could not open DB fil +e to view."); @dbarray=<DB>; # This should all be handled by foreach $line (@dbarray) # Apache::Htpasswd { # ($htp->fetchUsers()) ($usern, $crypw) = split(/:/, $line); while ($counter == 0) { print "<select name=\"logins\">"; print "<option selected>Select a user:</option>"; $counter++; } print "<option name=\"fred\" value=\"$usern\">$usern</opti +on>"; } } close(DB); # Why isn't the close in the same block as the open? print "</select>"; print "<input type=\"button\" onClick=\"javascript:logdel()\" valu +e=\"Delete user\">"; print "<br>"; print "</form>"; if ($viewlogvalue eq "on") # You shouldn't need to this { &log_viewer; } else { &contin; # aggg contin again.. } } sub usercreate{ unless(($viewlogvalue eq "on") || ($viewdbvalue eq "on")) { if($chpw eq "off") # All 3 if's and the unless { # can be combined and would &checker; # probably be clearer } else { $update="'s password was changed by "; } if($update eq "'s password was changed by ") { $logupdate="Password changed"; } else { $logupdate="Login created"; } unless ($chpw eq "on") { print "Login $untainted_formlogin $update $untainted_name, +\nunder the username $login_used,\non $date.\n"; print LOG "$untainted_formlogin&$untainted_name,$login_use +d-$date¬$logupdate\n"; $htp->htpasswd("$untainted_formlogin", "$untainted_formpw" +); print $former; } if ($chpw eq "on") { &passwordchanger; } else { &contin; } } } sub passwordchanger{ $htp->htpasswd("$untainted_formlogin", "$untainted_formpw", "$unta +inted_oldpw"); if ($htp->error() eq "Apache::Htpasswd::htpasswd - Password not ch +anged.") { # Just check the return value from htpasswd print "$pwr\n$copy\n$footer"; } else { # Try setting the password again? since it succeeded? $htp->htpasswd("$untainted_formlogin", "$untainted_formpw", "$ +untainted_oldpw"); print "Password for user $untainted_formlogin was successfully + changed. Please update your records."; print $former; print $footer; print LOG "$untainted_formlogin&$untainted_name,$login_used-$d +ate¬$logupdate\n"; } close(LOG); } sub eraser{ if ($delvalue eq "yes") { $htp->htDelete($deluservalue); print "Login $deluservalue been permanently removed from the p +ersonnel database by $untainted_name."; print "<br>"; print "To undo this action, you must <a href=\"/passwords.htm\ +" onMouseover=\"window.status='Go back to the database admin page'; r +eturn true\" OnMouseout=\"window.status=\'\'\">"; print "go back</a> to the RADB admin page and re-enter the emp +loyee mentioned above."; print "<br>"; $logupdate="User deleted"; chomp($deluservalue); chomp($nameofuser); chomp($logupdate); open(LOG, ">>$logpath/.log") || die("Could not open log file t +o add \"delete\" entry."); print LOG "$deluservalue&$untainted_name,$login_used-$date¬$lo +gupdate\n"; close(LOG); print $copy; print $footer; } else { &mainprog; # is it really the *main* program? } } sub checker{ # Shouldn't this all be handled by Apache::Htpasswd? open(DB, ">>$logpath/.htpasswd")||die("Could not open DB file for +view."); @checker=<DB>; foreach $checker(@checker) { ($username, $pws)=split(/:/, $checker); if ( ($username eq $untainted_formlogin) || ("\L\b$username\E" eq "\L\b$untainted_formlogin\E") || ("\U\b$username\E" eq "\U\b$untainted_formlogin\E")) { print "$rej $former <br> <br> $footer"; close(DB); die("User already exists"); } } # Need another close here to make sure it gets closed # because it won't usually get closed above } sub script{ print <<SCRIPTER; <html> <head> <title>RADB v1.0 modification results</title> <meta http-equiv="pragma" content="no-cache"> <meta http-equiv="Cache-Control" content="no-cache"> <meta http-equiv="Expires" content="0"> <script language="javascript"> function submitter(){ if(document.jim.name.value==""){ alert("Please fill in your name."); } else if((document.jim.login.value=="") || (document.jim.login. +value.length < 2)){ alert("Please provide the login you\'d like to edit. Logins + must be two characters or greater in length."); } else if(document.jim.pw.value==""){ alert("Please provide a password for the user you'd like to +add to the database."); } else if ((document.jim.pw.value.length > 0)&& (document.jim.p +w.value.length < 6)){ if(confirm("For security, passwords should be six character +s or more in length.\\nYour password is " + document.jim.pw.value.len +gth + " characters in length.\\nDo you still want to use this passwor +d?")){ alert("Password is too short, but has been accepted"); document.jim.submit(); alert("Your changes to the database have been saved, and + logged for record."); } else { document.jim.pw.focus(); } } else { if(document.jim.changepw.checked=="true"){ alert("User " + document.DBEdit.login.value + "'s passwo +rd has been changed. Please update your records."); } document.jim.submit(); alert("Your changes to the database have been saved, and log +ged for record."); } } function logger(){ document.jim.viewdb.value="on"; document.jim.submit(); } function DBviewer(){ document.jim.viewlog.value="on"; document.jim.submit(); } function pwchanger(){ if(document.jim.chp.value=="on"){ document.jim.chp.value="off"; } else { document.jim.chp.value="on"; } if(document.jim.chp.value=="on"){ document.jim.chpw.value="off"; document.jim.oldpw.disabled=true; } else { document.jim.chpw.value="on"; document.jim.oldpw.disabled=false; } } function logdel(){ if(document.jim.name.value==""){ alert("Please enter your name."); } else { if(confirm("Are you sure you want to delete " + document.jim.l +ogins.options[document.jim.logins.selectedIndex].value + "?")){ alert("User " + document.jim.logins.options[document.jim.log +ins.selectedIndex].value + " has been deleted"); document.jim.del.value="yes"; document.jim.submit(); } else { alert("No changes have been made to the database."); } } } </script> </head> <body> </script> </head> <body> SCRIPTER } sub heredocs{ $illegal=<<ILLEGAL; <h2>Illegal login</h2> <br> <p> Some information you submitted, (either your name, the login you w +ant to create, or the password for this login), contains illegal char +acters, which for security are not accepted. Please <a hre +f="../passwords.htm" OnMouseOver="window.status='Go back to the RADB +admin page'; return true" OnMouseOut="window.status='';">Go back to t +he RADB admin page</a> and try again, using only alphanumeric c +haracters.<p></p> ILLEGAL $copy= <<COPY; <br> <br> <br> RADB created by and © Pine Tree Internet Solutions. Unauthorized use +is prohibited. <br> <br> <a href="/passwords.htm" onMouseover="window.status='Go back to th +e RADB Admin page'; return true" OnMouseOut="window.status=''">Go bac +k to the RADB admin page.</a> COPY $former=<<FORMERCHGPW; <br> <h2>Add another user:</h2><br><br> <form name="jim" method="post" action="../cgi-bin/passwords.cgi"> <input type="hidden" name="chp" value="on"> <input type="hidden" name="chpw" value="off"> Your name: <input type="text" name="name"> <br> Login you want to create/edit: <input type="text" name="login"> Desired password for this login: <input type="password" name="pw"> <br> If changing an existing user's password, please check the “Chang +e password” box below and enter that user's <b><u>old</u></b> p +assword in this box: <input type="password" name="oldpw" d +isabled> <br> This user already exists, I'm changing the password associated with th +is login. <input type="checkbox" name="changepw" onClick=" +javascript:pwchanger()"> <br> <br> <input type="button" value="Submit changes" OnClick="javascript: +submitter()"> </form> <br> <br> FORMERCHGPW $pwr=<<PWR; <h2>Password rejected!</h2> <br> <br> The password associated with user $untainted_formlogin could not b +e changed, as it could not be verified that you possess the authority + to perform this action. Perhaps you forgot your password? PWR $footer = <<FOOTER; RADB V1.0 created by:<br> <a href="http://www.pinetreenet.com/" onMouseOver="window.status=' +Pine Tree Internet Solutions homepage'; return true" onMouseOut="wind +ow.status=''"> <img src="../ptis_title_small.jpg" alt="Pine Tree Internet Solutio +ns" border=0></a> <br> <font size="-2">RADB v1.0 © 2003, Pine Tree Internet Solution +s. No part of the RADB may be reproduced without explicit permissio +n from an authorized Pine Tree Internet Solutions representative. </font> <br> <br> <img src="../poweredby.png" alt="Powered by Apache, running on Red + Hat Linux 7.3 (Valhalla)"> </body> </html> FOOTER $rej=<<REJECTED; <h3>Login rejected</h3> <br> <br> <p>Login $untainted_formlogin is already in use, or is too similar + to another existing login. Please try again.<br><br><p> <br> <br> <br> <A HREF="javascript:window.history.back(1)" onMouseover="window.st +atus='Go back to the RADB Admin page'; return true" OnMouseOut="windo +w.status=''">Go back to the RADB admin page.</a> $former; <br> <br> $footer; REJECTED $copy= <<COPY; <br> <br> <br> RADB created by and © Pine Tree Internet Solutions. Unauthorized use +is prohibited. <br> <br> <a href="/passwords.htm" onMouseover="window.status='Go back to th +e RADB Admin page'; return true" OnMouseOut="window.status=''">Go bac +k to the RADB admin page.</a> <br> <br> COPY }
|
|---|