├── README.md ├── chapter2.md ├── chapter1.md ├── chapter5.md ├── chapter3.md └── chapter4.md /README.md: -------------------------------------------------------------------------------- 1 | #Building Secure PHP Apps Examples 2 | 3 | Code Examples from the Building Secure PHP Apps ebook. 4 | 5 | Sections covered 6 | 7 | * Never trust your users - escape all input 8 | * HTTPS/SSL/BCA/JWH/SHA and other random letters, some of them actually matter 9 | * Password Encryption and Storage for Everyone 10 | * Authentication, Access Control, and Safe File Handing 11 | * Safe Defaults, Cross Site Scripting and other Popular Hacks 12 | 13 | 14 | [Learn More or Purchase the Book!](https://leanpub.com/buildingsecurephpapps) 15 | -------------------------------------------------------------------------------- /chapter2.md: -------------------------------------------------------------------------------- 1 | #Chapter 2 2 | Examples of Apache and NGINX SSL configs 3 | 4 | 5 | ##Apache VHost 6 | 7 | 8 | DocumentRoot "/path/to/your/app/htdocs" 9 | ServerName yourApp.com 10 | SSLEngine on 11 | SSLCertificateFile /usr/bin/ssl/yourAppSigned.crt 12 | SSLCertificateKeyFile /usr/bin/ssl/yourApp.key 13 | 14 | 15 | 16 | ##NGINX VHost 17 | 18 | server { 19 | 20 | listen 443; 21 | 22 | server_name yourApp.com; 23 | location / { 24 | root /path/to/your/app/htdocs; 25 | index index.php; 26 | } 27 | 28 | ssl on; 29 | ssl_certificate /usr/bin/ssl/yourAppSigned.crt 30 | ssl_certificate_key /usr/bin/ssl/yourApp.key 31 | 32 | } -------------------------------------------------------------------------------- /chapter1.md: -------------------------------------------------------------------------------- 1 | #Chapter 1 2 | A basic example of prepared statements with typecasting. 3 | 4 | //database config vars 5 | //this would likely be in a config file somewhere 6 | $dbDsn = 'mysql:host=localhost;dbname=buildsecurephpapps'; 7 | $dbUsername = 'root'; 8 | $dbPassword = 'root'; 9 | 10 | //connect to the DB 11 | $db = new PDO($dbDsn, $dbUsername, $dbPassword); 12 | 13 | 14 | //the $id would probably come from a URL or request var 15 | $id = (int) 1001; 16 | 17 | //prep the query 18 | $query = $db->prepare('UPDATE users 19 | SET first_name = :first_name 20 | WHERE id = :id'); 21 | //run it! 22 | $query->execute([ 23 | ':id' => $id, //we know its an int already 24 | ':first_name' => (string) $_POST['first_name'], 25 | ]); -------------------------------------------------------------------------------- /chapter5.md: -------------------------------------------------------------------------------- 1 | #Chapter 5 2 | Examples of Authentication and Safe File Handing 3 | 4 | 5 | ##Dynamic Typing 6 | 7 | ###Strict Return Checking 8 | 9 | $phrase = 'I am the one who knocks'; 10 | 11 | $letterExists = stripos($phrase, 'i'); 12 | 13 | if ($letterExists !== FALSE) { 14 | 15 | echo 'we should be here'; 16 | return TRUE; 17 | 18 | } 19 | 20 | 21 | ##CSRF 22 | 23 | ###Token Generation 24 | 25 | class Form 26 | { 27 | 28 | static function generateCsrf() { 29 | 30 | $token = mcrypt_create_iv( 31 | 16, 32 | MCRYPT_DEV_URANDOM 33 | ); 34 | 35 | Session::flash( 36 | 'csrfToken', 37 | $token 38 | ); 39 | 40 | 41 | return $token; 42 | } 43 | 44 | } 45 | 46 | 47 | ###Generate Token for Form 48 | 49 | Route::get('/signup', function(){ 50 | 51 | $data['token'] = Form::generateCsrf(); 52 | 53 | 54 | return View::render('signup.form', $data); 55 | 56 | }); 57 | 58 | 59 | 60 | ###Form View 61 | 62 |
63 | 64 | 68 | 69 | 73 | 74 | 78 | 79 | 80 | 81 | 82 | 83 | 84 |
85 | 86 | 87 | ###POSTed Form Token Checking 88 | 89 | Route::post('/signup', function(){ 90 | 91 | //this would probably be abstracted away into 92 | //a route filter or your form validation 93 | if ($_POST['token'] === Session::get('csrfToken')) { 94 | 95 | //process the form 96 | 97 | } 98 | 99 | 100 | //like earlier, you should add a 101 | //legit error message here 102 | die('Invalid Form Data'); 103 | 104 | }); -------------------------------------------------------------------------------- /chapter3.md: -------------------------------------------------------------------------------- 1 | #Chapter 3 2 | Example of generating a secure password hashes. 3 | 4 | 5 | ##< PHP 5.5 6 | 7 | ###Store Password Hashing 8 | 9 | //generate the binary random salt 10 | $saltLength = 22; 11 | $binarySalt = mcrypt_create_iv($saltLength, MCRYPT_DEV_URANDOM); 12 | 13 | //convert the binary salt into a safe string 14 | $salt = substr(strtr(base64_encode($binarySalt), '+', '.'), 0, $saltLength); 15 | 16 | 17 | //set the cost of the bcrypt hashing 18 | //remember to experiment on your server to find the right value 19 | $cost = 10; 20 | 21 | //now we'll combine the algorithm code ($2y$) with the cost and our salt 22 | $bcryptSalt = '$2y$' . $cost . '$' . $salt; 23 | 24 | //hash it, hash it good 25 | $passwordHash = crypt($_POST['password'], $bcryptSalt); 26 | 27 | //verify a secure hash was returned 28 | //this could be an error code or insecure hash 29 | if (strlen($passwordHash) === 60) { 30 | 31 | //this next part is just for demonstration 32 | $db = new ImaginaryDatabase; 33 | $db->user()->create(array( 34 | 'password' => $passwordHash 35 | )); 36 | 37 | } 38 | else { 39 | 40 | //error handling 41 | 42 | } 43 | 44 | 45 | ###Login 46 | 47 | //we are going to start with a default state 48 | //of failure, always assume failure first 49 | $valid = FALSE; 50 | 51 | //grab the hash from our imaginary database 52 | $user = $db->user()-where(array( 53 | 'email' => $_POST['email'] 54 | ))->row(); 55 | 56 | //now check to see if the login password matches 57 | $pass = $_POST['password']; 58 | if (crypt($pass, $user->pass) === TRUE) { 59 | $valid = TRUE; 60 | } 61 | 62 | //other checks, error handling, etc... 63 | 64 | if ($valid === TRUE) { 65 | //valid auth stuff 66 | } 67 | 68 | 69 | 70 | ##>= PHP 5.5 71 | 72 | ###Store Password Hashing 73 | 74 | //FYI - the default cost is 10, it can be customized though 75 | 76 | //hash it, hash it good 77 | $passwordHash = password_hash($_POST['password']); 78 | 79 | 80 | //this next part is just for demonstration 81 | $db = new ImaginaryDatabase; 82 | $db->user()->create(array( 83 | 'password' => $passwordHash 84 | )); 85 | 86 | 87 | ###Login 88 | 89 | //we are going to start with a default state of failure 90 | //always assume failure first 91 | $valid = FALSE; 92 | 93 | //grab the password hash from our imaginary database 94 | $user = $db->user()-where([ 95 | 'email' => $_POST['email'] 96 | ])->row(); 97 | 98 | //now check to see if the login password matches 99 | $pass = $_POST['password']; 100 | if (password_verify($pass, $user->pass) === TRUE) { 101 | $valid = TRUE; 102 | } 103 | 104 | //other checks, error handling, etc... 105 | 106 | if ($valid === TRUE) { 107 | //valid auth stuff 108 | } -------------------------------------------------------------------------------- /chapter4.md: -------------------------------------------------------------------------------- 1 | #Chapter 4 2 | Examples of Authentication, Access Control, and Safe File Handing 3 | 4 | 5 | ##Authentication 6 | 7 | ###Login 8 | 9 | class UserModel 10 | { 11 | //... other methods 12 | 13 | function login($user, $password) 14 | { 15 | if (password_verify($_POST['password'], $user->pass) === TRUE) { 16 | $valid = TRUE; 17 | } 18 | 19 | //other checks, error handling, etc... 20 | 21 | if ($valid === TRUE) { 22 | //successfully logged in 23 | 24 | //just example session class 25 | //your own code or framework will likely 26 | //have it's own session wrapper 27 | Session::put('user', $user->id); 28 | return TRUE; 29 | } 30 | 31 | //failed to login 32 | Session::put('user', NULL); 33 | return FALSE; 34 | } 35 | } 36 | 37 | 38 | ###Method for Querying the Current User 39 | 40 | class UserModel 41 | { 42 | //... other methods 43 | 44 | //... login method 45 | 46 | function current() 47 | { 48 | $user = FALSE; 49 | 50 | if (isset($_SESSION['user']) === TRUE) { 51 | $user = DB::findById($_SESSION['user']); 52 | } 53 | 54 | return $user; 55 | } 56 | 57 | //let's imagine there is a method for retrieving 58 | //the user's groups. 59 | } 60 | 61 | 62 | ###Verify the User has Appropriate Access 63 | 64 | class AdminController 65 | { 66 | function __construct() 67 | { 68 | $userModel = new UserModel; 69 | $user = $userModel->current(); 70 | 71 | //check to make sure the user is logged in and 72 | //is a member of the admin group 73 | if ($user === FALSE || in_array('admin', $user->groups) === FALSE) { 74 | 75 | //please add a legit error message 76 | //with headers and all that fancy stuff 77 | die('Not Authorized'); 78 | 79 | } 80 | } 81 | 82 | //other admin only methods 83 | } 84 | 85 | 86 | 87 | ###Protected File Access 88 | 89 | Route::get( 90 | '/accounting/statements/{year}/{month}', 91 | function($year, $month) { 92 | 93 | //check user access 94 | $userModel = new UserModel; 95 | $user = $userModel->current(); 96 | 97 | //check to make sure the user is logged in 98 | //and a member of accounting 99 | if ($user === FALSE || in_array('accounting', $user->groups) === FALSE) { 100 | //please add a legit error message 101 | die('Not Authorized'); 102 | } 103 | 104 | //the user has access, let's read the file 105 | $directory = __DIR__ . '../uploads/acct/stmnts/'; 106 | $filename = (int) substr($year, 0, 4) . (int) substr($month, 0, 2) . '.pdf'; 107 | 108 | //error handling for invalid files 109 | 110 | //open the file 111 | $fileHandle = fopen($directory . $filename, 'r'); 112 | 113 | //read the file and close it 114 | $fileContents = fread($fileHandle); 115 | fclose($fileHandle); 116 | 117 | //send the appropriate headers 118 | header('Content-type: application/pdf'); 119 | header('Content-Disposition: inline; filename="' . $filename . '"'); 120 | header('Content-Length: ' . filesize($directory . $filename)); 121 | header('Expires: 0'); 122 | header('Cache-Control: must-revalidate'); 123 | header('Pragma: public'); 124 | 125 | //echo the file contents to the browser 126 | echo $fileContents; 127 | 128 | }); --------------------------------------------------------------------------------