Common vulnerabilities in PHP scripts
This text will explain the most common pitfalls in PHP scripts and how to prevent them from happening to you.
Please note that while I only say he, his and so on, it simply because it's easier to type and read. Just mentally substitute it if you are the type that gets upset.
Unchecked includes
I presume that you know about the constructs include, require, include_once and require_once, in this document I will reffer to any of them when I say include.
They are often used to put a specific page into a template. Like this:
include($page);
This is extremly insecure! A hacker could include any file on your server. If the PHP.ini settings allows it, he can even run code from http locations. Another common way is to do this:
include("includes/$page.inc");
While the hacker can't run code from another server, he still can include every file on the server! "How?" you might ask. By using the special folder ".." this folder is always a hard link to the folder above in the hirarcy.
Another common trick is to use a null character in the filename to end the string too early. While PHP doesn't end strings with the special null char, most os functions do. So when PHP sends the string to the os function, the string ends prematurly when the os function uses it. This way a hacker can chop off the ".inc" part.
You could take out any ".." or null char from the string, but don't do it that way, include is too powerful to allow a user to choose the input to. You should test the $page variable for different page names like this:
<?PHP
switch ($page) {
case 'news':
include('news.PHP');
break;
case 'main':
include('main.PHP');
break;
default:
header('HTTP/1.0 404 Not Found',true,404);
include('404.PHP');
break;
}
?>
Also note what the construct eval does, it will run any string as PHP code. Never use it unless you want someone to run any code they want. Only use it when you are sure there is absolutly no other way to do the task. And even then, ask a professional coder to make sure there is no other way.
A note about the perl compatible regular expression library, it contains a modifier, the "e" modifier, that makes the input work as if it where run in an eval, I hope you realise that it is very insecure. Luckily there is a function in the library that allows normal PHP callbacks instead.
More about file calls
Like include, fopen can be used to read a file into PHP. It's vulnerable in the same way as the include function, namely the ".." folder and the nullchar. It's important to make sure this function is safe, because if it writes files the hacker can upload his own PHP scripts or deface your site.
General injections
I will show what an injection is by example. Let's supose you have one file with logins like this:
Admin:notabadpassword:1 mybuddy:apasswordthatdoesn'tsuck:0
Now, let's asume anybody can signup to yoursite. The registration script will open the userlist file in append mode and write in the details. You also got a login script that uses the userlist file, it will open the file, search for the first line that got the name and login the user. There is an issue with the security, think you can figure it out?
The signup script does not do proper filtering. This has the effect that an hacker can make the script add more then one line. A line with any username and password he likes. But this is not the biggest issue. The worst problem is that he can easily chose a "password" that contains a ":" that will break out of the field, allowing him to enter data in the isadmin field. The attacker can then add himself as an admin.
You should either block things like newlines and separators, or come up with a way to make the fields contain them without breaking out from the field. Just remember to read them back properly.
Sql injections
Most PHP scripts uses a database to store their data. Every major database these days uses a language called SQL for the quires sent to it. In many cases you will want to allow the user to change the indata used in the query. But in sql the indata and the commands in just separated by a '. A hacker can end the indata and insert commands of his choice in the query with a additonal '.
While the hacker is limited by the query type, the hacker can usualy bypass checks and in the case of select querys, he can run prety much any select query he wants.
My recomendation to prevent these issues is simple, use paramertized queries. A parametirized query adds in the parameters after the query has been parsed by the SQL engine, eliminating any need of escaping the data.
For the popular choice of SQL engine, MySQL, you need the mysqli extension instead of the mysql extension to use parametirized queries.
But a few SQL queries can not be parametirized, you will need to escape things manualy. Make sure that the escaping function you use are made for the exact job it performs, the wrong escaping function will only give a false sense of security. The proper function for the mysqli extension is mysqli_real_escape_string(). Worth noting is that the PHP function addslashes is not the right one for this job. Also, the soon to be droped "feature" magic quotes, uses addslashes. Addslashes was never made for any specifical situation and is not suitable for any situation.
Html injections
You proably has some way for the users to change the page (like posting a comment), but do you check what the users posts? If you don't, users can include html to change how the page looks, javascripts that steals their logintooken, crashes their computer, etc...
Php has the function striptags and that function allows you to optionaly let certain tags pass thrught, but you can not chose to filter the attributess of those tags. That means hackers can use the event handler atributes (for example onclick) that runs scripts when certain events happens. This function also has the side effect of stoping discussions about html.
If you want to make the tags regular text, so they is displayed on the page instead of interperted by the browser, you have to replace the chars <, > and " with their html codes. Fortunualy PHP has a function for that, it's called htmlspecialchars.
But this is not the best function for the job, you should use htmlenities instead, since it will not escape a few characters, it will escape all characters that is trouble in the used character encoding.
But if you are making a forum, how can the users include tags then? Use ubbcode, it's mainly like html with the difference that it uses [ and ] instead of < and > to mark tags. And most users on the web are alredy used to it. Just make sure that the ubb engine does not have any vulnerabilites.
Do not think that vulnerabilitys that only lasts for one page load is not important. A hacker just have to link the victim to the vulnerability. POST requests is not safe from being linked to.
Javascript injection in links and images
If users can link to stuff using links or images, they can link to the special "javascript:" type of urls. That type runs the rest of the url as Javascript, you doesn't want that, do you?
The solution is easy. Force every link to use "http:, "ftp:" or "mailto:". There is more types of urls like "news:", but you should be fine with just those.
Do note that there are some types of special urls that you should be
know about, "about:" and "data:" instructs the browser that it
should read the rest of the url as the data it points at.
The "about:" protocol was never suposed to echo out the rest of the url,
but if it does not find the named special page, many browsers will do that instead.
The "aim:" protocol, while not dangerous, can stil cause trouble
since it allows scripting of the aim client.
Do note that Internet Explorer allows it's own scripting
laungage protocol, "vbscript:".
There are also some wraper protocol. They are "view-source:" and "jar:". They can and must be used with a secound url protocol, like "http:". Take note that these protocols takes another protocol after them and make sure your filters know that.
Url GET data escaping
Php often creates hyperlinks. Such links very often need dynamic data, like user ids and so on. But when you make the url for the link, you must remmember to use urlencode on the data. Otherwise, it might not be parsed properly in the next request. Things that may happen include plussigns being converted to spaces and you sudenly geting extra, unexpected variables in the url.
Cross Site Request Forgeries, or CSRF vulnerabilties
Php scripts very often have side effects in addition to returning some content. Such sideeffects can be editing database tables or deleting files. Normaly you have some sort of authision to prevent just anybody from doing anything. Many authorization systems uses cookies.
There is nothing wrong with using cookies for this. The real issue is how browsers works when other sites makes requests to your scripts.
A browser will happily allow any site from making a request to any other site. This is generaly benefitial, allowing for things like hyperlinks and remote embeding of images.
But the same rules applies when a site has a form that is sent to another site. Your scripts must be prepared of other sites making loged in people do perform things they probably does not want to, like making people administrators or posting messages.
This poses a problem, how can you tell if a request is from your site or from another site?
You can not use the request method, since other sites may submit POST forms to your scripts.
Some people would use the refferer in the request. There is just the issue that the refferer is unrealible and sometimes wrong. Not a good solution.
There is another rule that browsers has, the same-origin policy. It says that no foregin site may actually read the data sent from another site. This can be used to prevent other people from making valid forms for your site.
I call this technique "canaries", just as there is a protection called "stack canaries". It adds a hidden, random, session unique, value to each form that only your site knows the right value for. You then test if the values the server and the client have matches up, if they do, you can be assured that the client did indeed use the form you provided. If not, the form can not have been sent from your site and should not be responded to.
This work because the hidden value, the canary, can not be read by any other site.
Storing information on the client
You proably need to store stuff for a specific users while he is browsing your site. The oldest method is to put it in all the urls and as hidden fields in forms, but the client can easily alter those.
Netscape invented Cookies for Http, the protocol that transfers the data over the web. Cookies allows the server to storage information on the client while he's browsering. But the client can alter the information too.
You proably wonders "Why is it unsecure to let the client storage information?", the answer is that the client can always change it.
This isn't a problem if you doesn't give the client sentisitive data to storage. Prices or loginflags should never be storaged on the client, instead store it on the server and let the client store a pointer(an id) to it's own set of storaged data. With PHP's session system it's easy to do.
Register_globals and uninitiated variables
When the deprecated feature register_globals is on, PHP will create a variable in the global scope for each variable sent to it. That allows you to write shorter code, but if you is not initialisating your variables, the hacker can set them to what he likes and sometimes cause great trouble. Look at this code:
<?PHP
if($user=='admin' and $pass=='pass') {
$admin=true;
}
if($admin) {
doadminstuff();
}
?>
If the hacker can set $admin himself, he has bypassed the login. The solution is to turn register_globals off and use the long form of reading variables from the client. You can also make sure not to use a unset variable, but thats hard to arcive in PHP, as PHP does not require you to declare your variables.
To find errors like this you can set the errorreporting to include notices.
State and context
When your PHP script gets executed you can't know anything
about how the browser found your script. What does that mean?
I will explain with a example:
Say you has a forum where users can't respond to some threads, because some threads are geting out of hand. How would you implement that function? Some people would just hide or dissable the form used to respond. But what if someone makes their own form?
Some people would put in a check for the http header called refer (note that it is spelled with one f). The reffer header contains where the client was before this request. Sadly, it's worthless for safety checks since the browser can lie about that header.
The real way to handle this problem is to put a check in the formprocessor that makes it refuse if the thread is locked.
This example teaches us that your scripts can't simply relay on the context and must stand on their own.
Session fixation attacks
When useing PHP's session system, every user gets a login token. A login token is basicaly some random bytes. Php normaly uses a cookie for storaging the login token. But what about the users who can't or don't want to use cookies?
Php has a backup method for those cases. It storages the token in the url of any links on the page. If there is any forms it puts in the token in a hidden form field.
Normaly that isn't a problem. But with it a hacker can set the
session token of a user. Now how is that a problem?
A hacker can give the victim a session id that he knows. That means that
after the victim logins, the hacker can use the victim's session,
without knowing his password.
The solution is to change the session id when somebody logins. Php has a function just for that, session_regenerate_id. That function will change the session id and optionaly remove the old id. It will not erase the session variables.
Really dangerous functions like mega-posting should allways ask for the password. They also should not be satisfyied with a password hash, they should actualy ask for the password. This to make sure nobody else that might have access to the logged in session (like the user's 7 year pesky brother) may use them to cause trouble
Plaintext include files
Most people divdes up their code in multiple files, that allows code reuse and makes maintaing the code easier. The problem is that many people doesn't use caution when giving them a extension.
If you put the includes in the web root and gives them a extension
not registred for PHP, people can read the sourcecode of your includes.
This would allow a hacker to read your source code and find any
security problems. Let's not give a hacker a easy chance
of hacking your site. Or even worse, read any passwords you may have in includes.
Use the normal "*.PHP" file extension for includes.
The ideal solution to this problem is to order the webserver to forbid direct access to the includes.
Uploads and their dangers
Many times you will find a file upload very usefull, but you must use great caution. A file upload could allow a hacker to run a script of his own likeing.
You should use as many tests on uploaded data as posible. For instance if you only expects level files to a game only allow level files for that game.
Php can check that...
- there actualy was a file included in the upload
- the filename it has really is an uploaded file.
- the file is not too big nor too smal.
- the client claims that it is of a specific mimetype
- the file had a specific extension when it was sent
- the file's content is valid for the file type
Use as many checks as posible when working with uploads. Use caution when letting the uploaded file stay on the server. You must filter the extension of the file, even if the file appaers to be a valid file of the filetype it claims to be. If you do not, a hacker can upload a valid file that also contains PHP code, since some fileformats allows metadata. Php does not care about the fileformat, any PHP tags in any fileformat will get interperted when PHP executes a file.
Normaly that code would not be dangerous, but if the uploaded file will keep it's extension, that extension could be registred for PHP and the code will get executed.
Broken filters
You have proably have some filters on the input to your scripts, but do you know the finer parts about the functions you are useing for the filters?
The function ereg is not binary safe. That means that they uses c strings, that ends with a null byte. The problem is that in PHP strings can contain as many null bytes as needed. That means if the input contains a null-byte, ereg will end the string there. This could allow a hacker to bypass any checks or filters ereg does.
But there is a very good solution to that problem, you can use another library for the regular expressions that is binsary safe. Fortunaly Php is bundled with one such library, the perlcompatible regular expresion library. It's also much faster then the ereg functions. The manual even recomends using it instead of the ereg function
Do you have a function that replaces a string with the empty string? If you do, it's worthless if the string it changes from is longer then one character. I will use a example, say that you has a function that reads a user named file, but want to make sure that the user doesn't go outside of a specific directory. You would proably replace the string "../" with an empty string. But there is a problem there, a hacker could use "....//" as the input, the resulting output is "../", exactly what the hacker needs to bypass the restriction.
You should actively refuse bad input, not try to neutralise it.
Execution functions and meta chars
Php allows you to execute aribity commands from your script. That is good when you need to make a interface for a commandline program. Naturaly you will want to allow the user to change a part of the command to execute. But do you filter that input? If not a hacker can execute any command he likes.
This is because the execution functions runs the command useing a shell. A shell allows you to specify that multiple commands should be run. It does that with a few special characters, those characters are named the meta characters.
The metacharacters includes ; and |. If a hacker can insert meta characters in the command he can append any command he likes.
To stop this you should use the function escapeshellcmd. It will neutralise the input so it's safe to use in a shell command.