├── 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 |
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 | });
--------------------------------------------------------------------------------