Professional Documents
Culture Documents
Web Applications
24 September 2015 PHP By Mohit Gangrade 22 Comments
1101
Shares
Facebook Twitter
Every day thousands of websites across the world get hacked. Some
hackers hack for fun, some hack to learn, and some hack for money.
But not all hackers are bad guys. Big companies like Facebook and
Yahoo pay hackers to find vulnerabilities. I am sure you dont have a
big 6-8 figures budget to pay hackers to find vulnerabilities. Following
this guide will help you protect your Web Applications without
spending a dime.
No matter how much experience one has, he can never make a web
application completely secure. Even Googles Malaysian website got
hacked a few weeks ago. The hacker was an anonymous Bangladeshi
hacker who calls himself TigerMate. The hacker didnt harm the web
application, he just switched their DNS to display his own website.
Writing secure PHP code wont protect your web apps, there are a lot more
things to consider. As mentioned in the usage statistics by W3Techs.com,
Around 82% of the websites are using PHP. If a hacker finds a vulnerability
in the core of PHP, he will try to exploit the vulnerability on all the websites.
Not only PHP but sometimes the Web Server you are running might
be vulnerable. Here, By Web Server I mean Software like Apache,
Nginx, IIS, etc.
In this article, I will share with you some tips to help you protect your
Web Applications from hackers. I will try to make sure that all the tips
are easy to understand.
The name of the technique sounds scary. But the technique itself is
really easy to learn and use.
In this technique, You hash passwords with PHPs Password Hashing
API and store them into the database. After that, whenever your users
try to log in, you retrieve the password hash from the database and
use PHPs Password Hashing API to verify the hash. The verification
is pretty simple, PHP regenerates a new hash for the given password
and checks it with the hash provided.
Just one line of code hashes the password. Now, to verify the
password we use the function password_verify:
Escaping user input saves you from SQL Injections. Let me explain
SQL Injections with the help of the below example.
Think of a scenario when you have a page that displays a users bio
by querying the database. Your script performs a query based on the
user_id passed in the URL. And you are using the below code to
create the SQL query:
1 $user_input = $_GET['user_id'];
2 $SQL = "SELECT * FROM users WHERE id = '$user_input';";
Assuming the user entered 12 as the user id in the query string. The
URL will look something like this:
http://your-site.com/about.php?user_id=12
If you echo the SQL statement generated, you will see the following
result:
The above query is correct and safe. But what if the user(Hacker in
the case) enters some malicious SQL code as the query string
parameter?
The above SQL query will select all the users as 1 is always equal to
1. In the above SQL Query, the hacker is using which denotes a
comment in SQL. He is commenting out the
A hacker wont always enter commands like this one. He might enter a
command to DROP the complete users table.
Assume that the hacker enters 13; DROP TABLE users without
quotes as the user id. The SQL query will become:
1 SELECT * FROM users WHERE id = 13; DROP TABLE users;
The above SQL query when runs will completely delete the users
table. The above SQL query will first select the user with the user id
13 and then will drop the complete database.
To prevent this, you need to escape any data you use in your SQL
queries. Escaping is nothing more than adding a slash at the
beginning of a quote. But you should never do this manually instead
you should use the functions already available in PHP.
1 $user_input = $_GET['user_id'];
2 $user_input = $mysqli->real_escape_string( $user_input );
3 $SQL = "SELECT * FROM users WHERE id = $user_input;";
The above query isnt malicious but might produce an error when
executed. It is now safe from hackers.
Wait. Thats not all for this tip. Heres a cool Meme I found over the
internet about SQL Injections:
You can also use prepared statements for the purpose. But they are
out of the course of this article.
You should only use JavaScript for simple client-side input validation.
JavaScript should only be used to improve user experience.
A form that notifies users if they enter invalid credit card numbers is a
good example.
If you execute the above code, you will see Mohit as the output
instead of Something. You should never use this construct unless
necessary. Its use is discouraged by the PHP documentation.
As you can see, The above code snippet contains the example I used
at the beginning of this tip. This time we are allowing the user to
change the value of $string variable. We are storing the user input in
the $user_input variable and passing it to eval.
If the user enters Mohit as the user_input parameter, the output will
be Mohit. But if a hacker enters the below string as the parameter:
The output will be You have been hacked because the code
becomes:
This is like SQL injections but even worse. If a hacker finds this
vulnerability in your website, he can easily delete your complete
website along with the database.
Eval should be used only when you dont have any other options. And
if this is the case, you should always first thoroughly filter any user
input that goes into the construct. A hacker can execute malicious
code that can delete your database completely or clean out your
complete web application directory.
I have never used eval in my life till now. And I always discourage its use.
5. Disable Register
Globals(register_globals) PHP Directive
Register Globals is a PHP feature that converts query parameters in
the URL into variables automatically. Although this feature has been
removed as of PHP 5.4. Still, it is a good idea to disable this feature as
your web host might be using an older version of PHP that uses this
feature.
Lets assume that you are using the above code to test if an admin is
logged in. And if he is then displaying him the admin interface else
redirecting him to the login page.
If register globals directive is set to on, A hacker can change the value
of the variable $is_admin to true. Assuming you are using the above
code in admin.php file, a hacker can use the below URL to change the
variables value:
http://your-site.com/admin.php?is_admin=1
If a hacker uses the above URL to access your script, the value of the
variable $is_admin becomes 1. Now, the $is_admin variable will
evaluate to true in the if statement resulting in the display of your
Admin interface to the hacker.
To disable register_globals, you can either edit your php.ini file or .htaccess
file. There are three ways to disable Register Globals directive:
To create a custom php.ini file, open up notepad and add the below
code to it:
1 [PHP]
2 register_globals=0;
Now, save the file as php.ini and upload it to the root directory of your
website. The above two lines will disable register globals for all your
PHP scripts.
Note: Editing the main php.ini file will affect the working of all the
websites present on the web server.
1 register_globals=Off
The third method is not possible in shared web hosting plans. In that
case, you should try the first and the second methods.
You will see a 500 internal server error if your web host doesnt allow
you to modify PHP directives with a htaccess file. If this is the case,
first remove the code from the htaccess file and then contact your web
host administrator to disable register globals directive for you.
Assume that a user logs into your website. Once a user logs in, PHP
generates a session for the user and sends the browser a session
cookie. This session is valid until the cookie expires or gets deleted. If
a hacker somehow steals this session cookie, he can login to your
website as the user himself.
In the above code snippet, first, we are starting the session. After that,
we are storing the User-Agent string and the IP Address of the user in
the $_SESSION variable found in HTTP_USER_AGENT and
REMOTE_ADDR $_SERVER variable keys respectively.
Once you save the users User-Agent(browser) string and IP Address
in the session, its time to check if a session is hijacked. To check if a
session is hijacked, use the below code:
In the above code snippet, we are first checking if the users browser
is the same as the one we stored in the session. If the users browser
hasnt changed since the last request, we are checking users IP for
changes. And if any one of these two has changed then we are
destroying the session.
1. On Every Request
2. After A Specific Number Of Requests. For example, every 10 requests.
3. After A Specific Time. For example, every 5 minutes.
My preferred way is to regenerate a session id every 5-10 requests
from the user. To regenerate session id, we use
PHPs session_regenerate_id function. The function accepts only one
Boolean argument. Passing true as the argument replaces the current
session id with a new one and deletes the old session id. Deleting the
old session makes sure that the stolen session id is no longer
recognized by the server.
You can use the below code to regenerate session ids after a specific
number of requests:
On the first line of the above code snippet, we are starting the session.
After that, we are checking if our request counter already exists, if not
then we are initializing it with zero. And If our request counter already
exists then we are incrementing it on each request.
We are also checking if the user has already made requests more
than the number specified. And if he has, we are regenerating the
session id and deleting the old one.
Add this code to the function or code you use to check for user login.
Assume that the below code is from a script that displays charts to the
website admin. It displays a chart with the chart id provided in the
URL.
1 // Check And Store The Chart ID Received In The Query String
2 $chart_id = $_GET['chart_id'];
3
4 // Check If The User Is An Administrator
5 if( !is_admin() )
6 {
7 # The User Is Not An Admin
8
9 // Redirect To The Login Page
10 header( "location:login.php" );
11 }
12 else
13 {
14 // Escape The Chart ID
15 $chart_id = $mysqli->real_escape_string( $chart_id );
16 }
17
18 // Query The Database
19 $result = $mysqli->query("SELECT * FROM charts WHERE chart_id = '$chart_id';");
20
21 // Process The Result And Display The Chart
On the first line of the above code, we are storing the chart id received
in the GET parameter chart_id. After that, we are checking if the
current user is an admin. And if he isnt an admin, we are redirecting
him to the login page.
And if the user is an admin, we are escaping the chart id to use it in the
MySQL query.
To fix this problem, just add an exit command after the header:
CSRF attacks are really easy for a hacker to perform and dont take
much time.
1 # delete_account.php
2
3 if( isset( $_GET['account_id'] ) && is_numeric( $_GET['account_id'] && is_logged_in() ) ){
4 // Delete This User's Account From The Database.
5}
Because the hacker cant delete the users account himself, he will
make the user delete it without actually knowing. There are a lot of
ways a hacker can do this. I have listed two below:
To make the user open the link, a hacker cloaks the link with the help
of a URL shortener. The short URL becomes unidentifiable for the
user. And once the user opens up the short URL, an unwanted action
is performed on his behalf. In our case, the action is to delete the
users account.
2. Open The URL Without Users Consent
To do this, a hacker will use an image with the src of the link to be
opened. He will use the below code to do so:
Once a user opens hackers website, his browser will make a GET
request to the script. And your server will think that the user is making
the request himself. The user will not be able to see any image. But
this will result in deletion of users account.
Now that we are all terrified, here are some tips to prevent Cross-Site
Request Forgery attacks:
Your script isnt safe yet, A hacker can create a form on his website
that sends a POST request to the script on our website. And if
the user clicks the submit button on the form, his account will get
deleted without his consent.
A hacker doesnt even need to display the form to the user. He can
create a hidden form with hidden fields. And use JavaScript to submit
the form without users consent.
This is a misconception that POST requests can save your users from
CSRF. Implementing POST will help you implement other techniques
for prevention.
1 <form action="some-action.php">
2 <input type="hidden" name="CSRF-Token" value="41ca36dffcdc5e5a32bbdbd5d585b12d" />
3 <!-- Your Input Fields -->
4 </form>
Once the script receives the token, it will verify if it. And will only
perform any action if the token is valid.
The CSRF Token will make all CSRF attacks fruitless because a hacker cant
predict our CSRF token unless he knows how we are generating it.
A CSRF token must be as much random and unpredictable as
possible. Heres my preferred way of generating CSRF tokens:
You should generate a new CSRF token for each action. CSRF
tokens not only prevent CSRF attacks but also prevent multiple form
re submissions.
Moreover, you can ask the users to re-enter their password for
confirmation of sensitive operations. Asking for the password will also
prevent hackers with hijacked sessions from performing the action.
It not only lists all the contents on the directory but also sensitive
information like the PHP, Apache, and OpenSSL version. This
information shouldnt be displayed publically. If a hacker knows a
vulnerability specific to your PHP version, he will surely attempt to
exploit your application.
Directory listing allows a hacker to find all the files and directories
present in your website. All this information is really helpful to a
hacker.
The above tells Apache not to index any of the files in the current
directory. If apache doesnt index any files, then it will not be able to
list them to the users.
A htaccess file will only affect its current directory. It will not stop
directory listing in parent or child directories. This means you need to
add a htaccess file in all your directories.
Lets assume, you sell ebooks on your website. And you use a
payment gateway that handles all the payments. In this case, you
should never store users credit card information on your server. If you
dont store the credit card number of the user, hackers would not be
able to steal it from your website. Payment Gateways are PCI
Compliant and store credit card information in a secure manner.
The idea is simple, you delay the execution of your script by 0.2-0.3 seconds.
A user will not be able to notice this delay as it is too small. But will be
effective in the prevention of BruteForce attacks.
PHPs sleep functions dont consume much CPU resources and are
perfect for our purpose.
On login forms for the users, you should add a 0.2-0.3 second
execution delay. And for admin(You) login forms, you can you a bigger
2 5 second delay. You should add a delay on all type of
unsuccessful login attempts. No matter what made the login attempt
unsuccessful. Whether it was an empty field or invalid field, add an
execution delay.
This small execution delay will make the brute-force attack worthless.
The above captcha text is fairly easy to read. But sometimes, Google
generates captchas that are really unreadable for any human being.
Googles website says that it should turn to the below image for
humans:
But each time I try it, It shows me a list of images and asks me to
select something:
I dont get it. Why should I select pancakes?
1 <?php
2 $search_term = $_GET['s'];
3
4 echo "You Searched For $search_term";
5
6 // Code To Display Search Results Goes Here
Lets assume that the above code searches the database and displays
the search results. It also displays the search term to the user. Have a
look at the below URL assuming the code is stored in search.php file:
http://some-site.com/search.php?s=RandomSearchTerm
If a user uses the above URL, He will just see the search results for
his search term. But if he enters some JavaScript code in the query
parameter, he will be able to execute it. Have a look at the below URL
http://some-
site.com/search.php?s=<script>alert(You%20Have%20Been%20Hacked
);</script>
If the user uses the above URL, he can easily execute JavaScript
code. Most of the modern browsers these days have an XSS auditor
which protects users from XSS scripts. But a lot of browsers dont
have one.
To fix this problem, Just validate and filter all the data you output. To
fix this problem in the above example, we need to use PHPs
htmlspecialchars function which convert HTML characters into their
respective HTML special chars. Like less than(<) symbol to < and
greater than(>) symbol to >.
1 $search_term = $_GET['s'];
2
3 $search_term = htmlspecialchars( $search_term );
4
5 echo "You Searched For $search_term";
Conclusion
These 12 tips wont help you make your web application completely
secure. But will help you secure your website from a lot of different
attacks. I made this list because I wasnt able to find a good guide for
security in PHP.
All the guides available online are either too difficult or are outdated or
dont contain any examples at all. I hope this list helps you understand
some basic PHP security concepts. I will be updating this list regularly.
So, stay tuned. This list will never become outdated.
Not just your PHP code but also your web server software can be
vulnerable. Always install software from trusted providers.