Not a true security expert. Just know a thing or two.
I did code reviews for many projects...
... and saw same issues again and again
$_SERVER['HTTP_X_FORWARDED_FOR']
etc.)Filtering is making sure data is valid.
Input is invalid by default unless proven otherwise.
$email = filter_var($email, FILTER_VALIDATE_EMAIL);
if ($email === false) {
// email wasn't valid...
}
// everything's OK
or use libraries which are reliable
Making special characters behave like normal characters.
Usually by prefixing with another special character.
Each output has different escaping rules.
A script is injected into the page and is executed in user's browser.
Saw it in most projects I've reviewed.
...
<div>
<?= $_GET['query'] ?>
</div>
...
Escape output.
If need just text.
htmlspecialchars($content, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
If HTML needed.
HTMLPurifier (http://htmlpurifier.org/)
$config = HTMLPurifier_Config::createDefault();
$purifier = new HTMLPurifier($config);
$clean_html = $purifier->purify($dirty_html);
Because you may need to edit it and because data in your own DB could not be trusted.
Third party website can submit forms to your website on behalf of the user.
Introduce CSRF-tokens and use TLS/SSL.
<img src="/logout" />
Using $_REQUEST
is the same.
Executing arbitrary SQL on a project database.
$email = $_POST['email'];
query("SELECT *
FROM user
WHERE email = '$email';");
' OR 1
UNION SELECT 1,'',3,4,5 INTO OUTFILE '1.php' --%20
UNION SELECT 1,LOAD_FILE('config.php'),3,4,5 --%20
Escape queries.
addslashes()
mysql_escape_string()
mysql_real_escape_string()
PDO or driver specific. For PDO:
$stmt = $db->prepare("SELECT * FROM user WHERE email = :email");
$stmt->bindValue(':email', $email);
$user = $stmt->fetch();
Use whitelist.
$allowedTables = ['user_comments', 'post_comments'];
if (!in_array($table), $allowedTables, true) {
return false;
}
// query as usual
Ability to upload and execute code.
Directly execute code.
eval($_GET['query']);
Avoid eval() or at least use whitelist.
Run PHP with as less permissions possible
require $_GET['type'] . '.php';
Trick user into actually clicking on third party website.
iframe + opactity: 0
Not really PHP related but quite serious.
Disable embedding in a frame via RFC 7034:
header('X-Frame-Options: DENY');
// or
header('X-Frame-Options: SAMEORIGIN');
or via JavaScript:
if (window.top !== window.self) {
document.write = "";
window.top.location = window.self.location;
setTimeout(function () {
document.body.innerHTML = '';
}, 1);
window.self.onload = function (evt) {
document.body.innerHTML = '';
};
}
Save a hash.
Even with salt.
Full brute-forcing of 8-char password hashed as SHA-256 takes...
3.5 days of brute forcing on 2011 single GPU
About 20 hours on 2015 GPU
key derivation function iterations = 2^cost
12+ is a safe choice. Yii uses 13.
Cost = 13 → ~28 hashes/sec. on Nvidia GTX Titan X ($1900)
28 * 60 * 60 * 24 = 2419200 hashes a day
6 char lowercase letter-only password = 308915776 combinations
308915776 / 2419200 = 127 days to brute-force a single password
21 days with 6 of such cards which costs ~15000$+
Add numbers to 6 char password and it would cost ~81000$+ to break it in 21 days
GPUs are getting better and cheaper.
If you know hashes are leaked:
It would give you more time to react.
PHP specific. Trick user to use session ID you know.
Use cookies only. In php.ini:
session.use_cookies = 1
session.use_only_cookies = 1
session_cookie_httponly = 1
Regenerate session ID with session_regenerate_id(true)
after login or permissions change.
Yes, it happens.
Prefer denying everything then allowing what's needed.
Used for tokens, reset passcodes, generated passwords, UUIDs etc.
Random numbers could be guess-able or could collide if random source isn't good enough.
Tokens, reset passcodes, generated passwords
Use safe random sources
Conduct tests
Have an experienced admin around...
It can't be achieved once and for all.
Have someone familiar with security in the team. Use VCS and do code reviews.
Plan in advance.
target="_blank"
is unsafe