• Index
  •  » Articles
  •  » Methods of safe web-applications programming on PHP

Methods of safe web-applications programming on PHP

Methods of safe web-applications programming on PHP

The article given doesn’t intend to play the role of the general manual devoted to the theme ‘how to do it impossible for anybody to break me’. It cannot be done. The only purpose of this article is to show some methods used by me for protection of web-applications like WWW-chats, guest journals, web-forums and other applications like this.

The first rule to observe for a web-programmer wishing to write a web-application which is protected somehow should be “Never trust the data which a user sends to you”. All the users are by definition evil hackers who use every possibility to insert different trash like PHP, JavaScript, SSI, calls of their hacker functions and all the scaring things like these into input form. And so the first thing to do should be a strict filtering of all the data sent by users. For example, we have 3 input forms in our guest journal: user name, his e-mail and the body of a message itself. First of all we limit the data quantity transmitted from the input forms with something like that:

<input type=text name=username maxlength=20>

It cannot, of course, perform a role of the real protection; the only purpose of this element is to save the user occasional name input which is longer than 20 symbols. And to save user a temptation to download a document with input forms and correct ‘maxlength’ parameter we’ll install somewhere in the very beginning of the handling data script verification of the web-server HTTP-REFERER network place’s variable:

<?php 
$referer
=getenv("HTTP_REFERER"); 
if (!
ereg("^http://www.myserver.com")) { 
    echo 
"hacker? he-he...\n"
    exit; 

?> 

Now if the data are transmitted not from the forms of the document placed on the server www.myserver.com , a hacker will get a neglecting message. In fact, this also cannot serve a 100 per cent guarantee of the REAL data transmission from our document. Finally variable HTTP_REFERER is formed by browser and nobody cannot prevent a hacker from correcting the browser code or simply enter the 80th port with telnet and form o request of his own. And thus protection like this is suitable for dealing with Totally-Uneducated- Hackers. Howerver according to my observations about 80 per cent of hackers stop at this stage and don’t go further because their IQ and laziness disable doing this. As for me, I saved this part of the code within a separate file and I call it from everywhere where it’s possible. Addressing a variable doesn’t take much time and such an action provides safety.

The next stage will be well-known strict filtering of the data transmitted. First of all we’re not going to trust the maxlength-variable in the input forms and will cut by hands string:

$username = substr($username,0,20);

We’re not going to allow a user blank name field usage to prevent him from writing anonymous messages:


if (empty($username)) {
echo "invalid username";
exit;
}

We forbid usage of any other symbols except for letters, underlining sign "_", space and numbers in the user’s name:


if (preg_match("/[^(\w)|(\x7F-\xFF)|(\s)]/",$username)) {
echo "invalid username";
exit;
}

I prefer to use Perl-compatible regular expressions in all the cases when something more complicated than checking of pattern presence in a string or exchanging one pattern for another one is needed. The same can be done with usage of standard PHP-functions ereg() and eregi(). I’m not going to give these examples here – their detailed description is given in the manual.

For the e-mail input field we add signs ‘@’ and ‘.’ into admissible symbols’ list; otherwise it will be impossible for a user to write his address correctly. We’ll delete a space instead:

if (preg_match("/[^(\w)|(\@)|(\.)]/",$usermail)) {
echo "invalid mail";
exit;
}

We won’t correct the text input field so much – we are to lazy to sort out all the punctuation marks which may be used and so we’ll content ourselves with functions nl2br() and htmlspecialchars() usage – this prevents an enemy to insert html-tags messages in the text. Some developers will probably say: “and nevertheless we’d like to enable_users_iserting_tags”. If you are eager to do it – you can do some substitutes of tags like “text surrounded with asterisks will be marked in bold”. But you are never to let users to use tags implying external resources connection – from the ordinary <img> to the super cool <bgsound>.

Once I was asked to test an html-chat. The first noticed by me bug was namely permission for picture insertion. Taking into account some other peculiarities of the chat building in some minutes I had a file in which IP-addresses, names and passwords of all the present at the moment users were enumerated accurately. How was it done? In a very simple way – tag <img src=http://myserver.com/myscript.pl> was sent to chart and as a result browsers of all the users who were present in the chart at the moment called script myscript.pl from the host myserver.com. And the script has saved into the log-file half of the network variables like QUERY_STRING, REMOTE_ADDR and others before showing up location on a picture. It was the same result for each user.

And so my opinion is following: permitting html-tags insertion in chats, forums and guest journals is beautiful but it has no sense – users are unlikely to use your journal or chat knowing that each hacker can find out their IP. And not only IP – I’m not going to enumerate abilities of JavaScript…

These means are enough for a simplest guest journal to make it a bit difficult for breaking. However for convenience books usually contain some moderating abilities – at least ability to delete messages. Of course, it is available to limited (or not so strictly limited) number of persons. Let’s examine what can be done here.

For example, all the moderating system of a book also consists of two parts – of a page with the messages list where you can mark out all the messages to be deleted and of a deleting messages script itself. We’ll call them admin1.php and admin2.php correspondingly.

The simplest and the most reliable method of user’s authentication is placing scripts in the directory protected with .htaccess file. For breaking such a protection you need to break web-server instead of an application. It’s a bit more difficult and doesn’t fit into the limits of this article’s theme. Howerver this method cannot be used in every case – sometimes you need to do authorization by means of the application itself.

The first and the simplest method is authorization by means of HTTP through code 401. By such a return code every normal browser will show up an authorization window and ask to write login and password. And further by getting code 401 the browser will try to offer to web-server current for the given realm login and password and only in the failure case it will demand to repeat authorization. Example of a code for output of demand for such authorization is examined in all the readers and manuals:

<?php 
if (!isset($PHP_AUTH_USER)) { 
    
Header("WWW-Authenticate: Basic realm=\"My Realm\""); 
    
Header("HTTP/1.0 401 Unauthorized"); 
    exit; 

We’ll place this part of code in the beginning of admin1.php script. After its accomplishing we’ll have two installed variables $PHP_AUTH_USER and PHP_AUTH_PW in which name and password inserted by a user correspondingly will lie. They can be checked on SQL-base:

*** Attention!!!***

In the part of a code given below a serious safety error is done on purpose. Try to find it yourself.


<?php 
$sql_statement
="select password from peoples where name='$PHP_AUTH_USER'"
$result mysql($dbname$sql_statement); 
$rpassword mysql_result($result,0,'password'); 
$sql_statement "select password('$PHP_AUTH_PW')"
$result mysql($dbname$sql_statement); 
$password mysql_result($result,0); 
if (
$password != $rpassword) { 
    
Header("HTTP/1.0 401 Auth Required");  
    
Header("WWW-authenticate: basic realm=\"My Realm\"");  
    exit; 

The error mentioned is widespread among beginners and inattentive programmers. Once I’ve yielded to such a trick but fortunately it didn’t produce much harm with the exception for some rude phrases left by a hacker in the news tape.

And so I tell you my secret: for example, hacker puts in deliberately non-existing user name and blank password. At this variable $rpassword has a blank meaning as a result of selection from the base. And algorithm of passwords ciphering by means of function MySQL Password() as well as standard Unix algorithm returns blank meaning by attempt of blank password ciphering. As a result - $password == $rpassword, the condition is accomplished and a hacker gets access to the protected application part. It is cured either by forbidding blank passwords or (which from my point of view is more correct) by insertion of the following code part:

<?php 
if (mysql_numrows($result) != 1) { 
    
Header("HTTP/1.0 401 Auth Required");  
    
Header("WWW-authenticate: basic realm=\"My Realm\"");  
    exit; 

I.e. verification of one and the only one user in the base. No more, no less.

It’s worth to install an authorization checking like this into script admin2.php as well. If a user has good intentions, he comes to admin2.php through admin1.php and thus it is already authorized and won’t provoke repeated questions – browser will simply transmit the password. If not – then you have reasons to curse. For example, by putting out such a phrase "hacker? he-he...".

Unfortunately not always you have a possibility to use authorization algorithm through code 401 and you have to accomplish it by means of application only. In the general case model of such authorization will look like this:

  • Once a user is authorized by means of a web-form and script which verifies name and password.
  • The rest of the scripts in the protected part of application check somehow the fact of user’s authorization.

Such a model is called sessional – after authorization passing a so-called ‘session’ is opened during which a user gets success to the protected part of the system. If the session is shut, access will be shut too. The most of www-chats are based on this principle: a user can get success to the chat only after passing login procedure. The main difficulty of this system is that all the scripts of the protected application part have to know somehow that the user who sends you data has been authorized successfully. We’ll treat some variants how to make it possible:

  • After authorization all the scripts of the protected part are called with some flag like adminmode=1. (Don’t laugh – I’ve seen it myself). It’s clear that everyone who knows flag ‘adminmode’ can form URL himself and enter in the administration mode. Besides that there is no possibility for users’ differentiation.
  • Authorization script can submit user name to other scripts somehow. It is widespread in many www-chats for messages differentiation; a form like ‘hidden’ where a user name is indicated is built in aside a form like ‘text’ for message input. It is also unsafe as far as a hacker can download document with form on his disk and change the meaning of form ‘hidden’. Some use may be made of the mentioned above HTTP_REFERER checking – but as I’ve already said it guarantees nothing.
  • Identification of a user according to his IP-address. In this case after passing authorization somewhere in a local data base (sql, dbm or even in txt-file) current IP of a user is saved and all the scripts of the protected part look up variable REMOTE_ADDR and check if such address is present in the data base. If it is, it means that authorization has been done and if not, we can use "hacker? he-he..." :-)
  • This method is more reliable – getting access without authorization passing is possible only in case when other user who has been authorized successfully uses the same IP. But taking in account widespread proxy-servers and IP-Masquerad it is quite possible.

  • The only simple and reliable enough method of user personal verification that I know is authorization of a user by means of random uid. We’ll examine it in details.

After user’s authorization the script which has accomplished this authorization generate a random number which is long enough:

mt_srand((double)microtime()*1000000);
$uid=mt_rand(1,1000000);

This number will be:

  • Inserted into the local list of authorized users;
  • Shown up to the user.

Beside other information (chat message or messages list in the guest journal) user sends his uid to the server by each request. At this in the document with input forms beside other tags the type tag will be present:

<input type=hidden name=uid value=1234567890>

Uid-form is invisible for user but it is transmitted to the script of the protected application part. It compares the uid transmitted with uid which is saved in the local base and it either accomplishes its function or …"hacker? he-he...".

The only thing which is necessary to do by such organization is to revise local uid-list time after time and/or make an ‘exit’ button for a user by pressing which local user’s uid will be deleted from the base on server and thus the session will be shut down.

Some programmers use password of a user instead of ‘disposable’ dynamically generated number as uid. This is admissible but can be treated as a ‘bad manner’ as far as a user’s password doesn’t change from session to session which means that a hacker will be able to open sessions on his own. The same model can be used everywhere when user’s identification is needed – in chats, web-conferences, web-shops.

In conclusion such a useful thing as logs’ insertion should be mentioned. If we integrate an ability to insert an event into log-file with indication of the potential hacker’s IP-address into each of the procedures described, it will be much easier for us to find the hacker in case of a real attack as far as hackers usually try complicating in series attacks. For IP-address definition it’s better to use not only standard variable REMOTE_ADDR but well-known HTTP_X_FORWARDED_FOR as well which enables definition of IP of a user who is behind the proxy-server. Of course, if proxy allows that. By insertion of log-files you need to remember that only you should have access to it. It will be best of all if they will be placed outside of the catalogues’ tree available though WWW. If you don’t have such opportunity – create a separate catalogue for log-files and close access to it by means of .htaccess (Deny from all).


 

  • Top