Hi, I'm Shahab. We're going to go through some important points to make your php code and website secure. These points are simple, but very, verrrrrrry effective! Actually, if you didn't do these, don't even call your site safe!
DISCLAIMER: We developers always try our best to limit the possibility of being hacked as much as possible. but no one can be 100.00% sure.
We always have to check and @@@@@ user input to be safe.
Never trust the user.
Try to use POST
instead of GET
when you have data that you need to keep safe and (mostly) unchangable, as it is safer and not changable by a normal user.
Always check if the input exists.
if (isset($_POST["username"]) && isset($_POST["password"])) {
// Do the thing..
}
Check for the input to not be empty if it shouldn't be empty (if the input is required). This has a quite few methods; RegEx, and some below. Between the ones below, I prefer the first one.
if ($_POST["username"] && $_POST["password"]) { }
// OR
if ($_POST["username"] != "" && $_POST["password"] != "") { }
// OR
if ( ! empty($_POST["username"]) && ! empty($_POST["password"])) { }
// OR
if ( ! strlen($_POST["username"]) && ! strlen($_POST["password"])) { }
Sending a NONCE (Number ONCE) along with the user input. NONCE is a hashed string to check if the input is valid. It's usually based on UNIX-timestamp so we can fact-check it and limit the usablility time. With this, we can be more sure that no non-direct requests are permited access (preventing CSRF Attacks).
Below are simple functions to generate a NONCE that is valid for 12 hours (I think so, I don't remember :P). By action
I mean the act that is about to be done, like dologin
or dosignup
. and by user
I mean the the user id. (if the user is logged in tho. if not, I pass 0
). So now we have a NONCE that is based on timestamp, the action, and the user. Other factors can also be added if necessary.
// A salt for the NONCE. I keep this in a `secret.php` file. Make it long :)
define("SECRET_NONCE_SALT", "BlaBlaBlaBlaBlaBla");
function CreateNonce($action = "", $user = "") { return substr(md5( ceil(time() / (86400 / 2)) . $action . $user . SECRET_NONCE_SALT), -12, 10); }
function IsNonceValid($nonce = "", $action = "", $user = "") { return (CreateNonce($action, $user) == $nonce); }
Our website has a domain name, it can be example.com
, subdomain.example.com
, an IP, or even localhost
! When a php script is executed, it always has a super-variable array named $_SERVER
. In this array, we can find HTTP_HOST
and we can compare it with the one we expect it to be.
// The domain name. don't use the protocol (like http). I keep this in a `config.php` file.
define("SITE_DOMAIN", "example.com");
if ($_SERVER["HTTP_HOST"] == SITE_DOMAIN) {
// Do the thing..
}
The $_SERVER["HTTP_REFERER"]
shows where we were before (cool rhyme :D). If we hit the page directly, $_SERVER["HTTP_REFERER"]
won't exist. so make sure to check if it isset
. It will have the full URL, like https://example.com/path/to/file.php?page=5&joe=mama
.
I say only use this method if you know the script should ONLY be accessed from ONE specific previous-page.
// The domain name. don't use the protocol (like http). I keep this in a `config.php` file.
define("SITE_DOMAIN", "example.com");
if (isset($_SERVER["HTTP_REFERER"]) && $_SERVER["HTTP_REFERER"] == "http://" . SITE_DOMAIN . "/path/to/file.php?page=5&joe=mama") {
// Do the thing..
}
Always sanitize the user input. Again, "Never trust the user". We'll have an overview of some methods. In most cases you have to sanitize inputs more specifically.
trim()
clears whitespaces from before and after the string.
$a = " Hello World ! ";
$b = trim($temp);
// $b = "Hello World !";
Limit the maximum and minimum length of user inputs. You probably want to keep an eye on your database as well. Based on the context, either show an error, or trim it using substr()
on limit exceed [and tell the user]. Don't set it too limited. like allow passwords 8-64, names 3-50, and emails 10-128. RegEx can be used for this purpose as well (it may be better actually).
if (strlen($_POST["password"]) >= 8 && strlen($_POST["password"]) <= 64) {
// Do the thing..
}
Let's say our site has different pages specified by the GET
of ?page=15
. Of course in this case we don't want anything like ?page=John
! So we always check if the input is valid. In most cases we use RegEx. It's brilliant for validating strings.
// A RegEx example for numbers-only
@@@@@
$a = "abc";
$b = intval($a);
$b = (int) $a;
Please don't pronounce RegEx as "Rejex"! Please!!! It's the abbreviation of "Regular Expression". it's pronounced as "g", not "j"!
XSS (Cross-Site Scripting) Attack is when the hacker tries to run like a script in our website to steal the user session, attack the database, or just to spoil our good mood by saying "Your site is hacked, hahaha".
The main reason for this attack to be successful is that we haven't sanitized the user input on input, or on showing.
We usually use htmlspecialchars()
to convert HTML-Special characters like <
, >
, &
to escaped characters like <
, >
, &
. So if the hackers try to run a <script>
it will end up as <script>
. hahaha you hacker! :)
I htmlspecialchars()
only when echo
ing. I don't often use it for storing in the DB. for the DB, I just do the normal sanitizing required. (Think of it, a user uses a <
in their password and you suddenly make it not work... das no good). Or at least do it on one side only, you probably don't want a literal <script>
showing up on your site.
For adding to the DB, filter_var()
with FILTER_SANITIZE_FULL_SPECIAL_CHARS
can be used to sanitize as well.
<?php $a = "<script>alert('Your site is hacked, hahaha');</script>"; ?>
<h1><?= htmlspecialchars($a); ?></h1>
<h1><?= filter_var($a, FILTER_SANITIZE_FULL_SPECIAL_CHARS); ?></h1>
When passing content via GET
you can also make use of urlencode()
too.
The deadliest attack of all. Your DB may even end up deleted! (Always have backup). A good article on PDO preventing SQL Injection
<meta name="referrer" content="never">
<a target="_blank" rel="noopener noreferrer" href="http://example.com/">
We can use session_regenerate_id()
often so if the session was somehow hijacked, it won't last long.
Having SSL is always a good idea. It encrypts the exchanging values so no man-in-the-middle
can use/change the data.
Having backup is always a good idea. Backup your data very often. Like DBs, code files, repos, project files, family photos, the first Hello World you've written that you were very very happy about it :), and each and every single important thing in your life!
Did I mention you have to do all of these? :)
Please @@@@@
Happy Coding!