-
Notifications
You must be signed in to change notification settings - Fork 3
/
security_headers.php
365 lines (313 loc) · 17.4 KB
/
security_headers.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
<?php
/**
* Plugin Name: Security Headers
* Plugin URI: http://surevine.com/
* Description: Sets security related headers (HSTS etc)
* Version: 1.0
* Author: Simon Waters (Surevine Ltd)
* Author URI: http://waters.me/
* License: GPL2 or any later version
*/
$referrer_policies=array(
"no-referrer" => "Omit entirely",
"no-referrer-when-downgrade" => "(Browser default) omit referrer on downgrade to HTTP",
"origin" => "Only send the origin ( https://www.example.com/ )",
"origin-when-cross-origin" => "Full URL to current origin, but just origin to other sites",
"same-origin" => "Omit referrer for cross origin requests",
"strict-origin" => "Send only the origin, or nothing on downgrade (HTTPS->HTTP)",
"strict-origin-when-cross-origin" => "Omit on downgrade, just origin on cross-origin, and full to current origin",
"unsafe-url" => "Always send the whole URL"
);
function security_headers_insert() {
if ( headers_sent() ) {
error_log("Headers already sent HTTP Headers modules unable to function");
}
// HSTS
if (is_ssl()){
$time = esc_attr(get_option('security_headers_hsts_time'));
$subdomain = esc_attr(get_option('security_headers_hsts_subdomains'));
$preload = esc_attr(get_option('security_headers_hsts_preload'));
if ( ctype_digit($time) ) {
$subdomain_output = $subdomain > 0 ? "; includeSubDomains" : "";
$preload_output = $preload > 0 ? "; preload" : "";
header("Strict-Transport-Security: max-age=$time$subdomain_output$preload_output");
}
}
// No Sniff
$nosniff = esc_attr(get_option('security_headers_nosniff'));
if ($nosniff == 1) {
send_nosniff_header();
}
// XSS
$xss = esc_attr(get_option('security_headers_xss'));
if ($xss == 1) {
header("X-XSS-Protection: 1; mode=block");
}
// Frame Options
$frame = esc_attr(get_option('security_headers_frame'));
if ($frame == 1) {
send_frame_options_header();
}
// HPKP
if (is_ssl()){
$pinkey1 = esc_attr(get_option('security_headers_hpkp_key1'));
$pinkey2 = esc_attr(get_option('security_headers_hpkp_key2'));
$pinkey3 = esc_attr(get_option('security_headers_hpkp_key3'));
$pintime = esc_attr(get_option('security_headers_hpkp_time'));
$pinsubdomain = esc_attr(get_option('security_headers_hpkp_subdomains'));
$pinuri = get_option('security_headers_hpkp_uri');
// Standard requires at least one backup key so insist on two keys before working
if ( is_numeric($pintime) && !empty($pinkey1) && !empty($pinkey2)) {
$pinheader="Public-Key-Pins: ";
$pinheader .= 'pin-sha256="'. $pinkey1 .'";';
$pinheader .= 'pin-sha256="'. $pinkey2 .'";';
if (!empty($pinkey3)) { $pinheader .= 'pin-sha256="'. $pinkey3 .'";'; }
$pinheader .= " max-age=$pintime;";
if ($pinsubdomain > 0) { $pinheader .= ' includeSubDomains;'; }
if (!empty($pinuri)) { $pinheader .= ' report-uri="'. $pinuri .'";'; }
header($pinheader);
}
}
// Referrer Policy
$referrer = esc_attr(get_option('security_headers_referrer'));
if (!empty($referrer)){
header("Referrer-Policy: $referrer");
}
// Expect-CT
if (is_ssl()){ // Should not be issued for http
$ectage = esc_attr(get_option('security_headers_ect_time'));
$ectenforce = esc_attr(get_option('security_headers_ect_enforce'));
$ecturi = get_option('security_headers_ect_uri');
if ( ctype_digit($ectage) ){
$ectheader="Expect-CT: max-age=$ectage";
if ($ectenforce > 0) { $ectheader .= ',enforce'; }
if (!empty($ecturi)) { $ectheader .= ',report-uri="'. $ecturi .'"'; }
header($ectheader);
}
}
}
add_action('send_headers', 'security_headers_insert');
// admin section doesn't have a send_headers action so we abuse init
// https://codex.wordpress.org/Plugin_API/Action_Reference
add_action('admin_init', 'security_headers_insert');
// wp-login.php doesn't have a send_headers action so we abuse init
add_action('login_init', 'security_headers_insert');
function security_headers_activate() {
register_setting('security_group', 'security_headers_hsts_time', 'istime');
register_setting('security_group', 'security_headers_hsts_subdomains', 'ischecked');
register_setting('security_group', 'security_headers_hsts_preload', 'ischecked');
register_setting('security_group', 'security_headers_nosniff', 'ischecked');
register_setting('security_group', 'security_headers_xss', 'ischecked');
register_setting('security_group', 'security_headers_frame', 'ischecked');
register_setting('security_group', 'security_headers_hpkp_key1', 'iskey');
register_setting('security_group', 'security_headers_hpkp_key2', 'iskey');
register_setting('security_group', 'security_headers_hpkp_key3', 'iskey');
register_setting('security_group', 'security_headers_hpkp_time', 'istime');
register_setting('security_group', 'security_headers_hpkp_subdomains', 'ischecked');
register_setting('security_group', 'security_headers_hpkp_uri', 'isuri');
register_setting('security_group', 'security_headers_referrer', 'isreferrer');
register_setting('security_group', 'security_headers_ect_time', 'istime');
register_setting('security_group', 'security_headers_ect_enforce', 'ischecked');
register_setting('security_group', 'security_headers_ect_uri', 'isuri');
}
register_activation_hook(__FILE__, 'security_headers_activate');
function security_headers_deactivate() {
remove_action('admin_menu', 'security_headers_settings');
remove_action('send_headers', 'security_headers');
remove_action('admin_init', 'security_headers');
remove_action('login_init', 'security_headers');
unregister_setting('security_group', 'security_headers_hsts_time', 'istime');
unregister_setting('security_group', 'security_headers_hsts_subdomains', 'ischecked');
unregister_setting('security_group', 'security_headers_hsts_preload', 'ischecked');
unregister_setting('security_group', 'security_headers_nosniff', 'ischecked');
unregister_setting('security_group', 'security_headers_xss', 'ischecked');
unregister_setting('security_group', 'security_headers_frame', 'ischecked');
unregister_setting('security_group', 'security_headers_hpkp_key1', 'iskey');
unregister_setting('security_group', 'security_headers_hpkp_key2', 'iskey');
unregister_setting('security_group', 'security_headers_hpkp_key3', 'iskey');
unregister_setting('security_group', 'security_headers_hpkp_time', 'istime');
unregister_setting('security_group', 'security_headers_hpkp_subdomains', 'ischecked');
unregister_setting('security_group', 'security_headers_hpkp_uri', 'isuri');
unregister_setting('security_group', 'security_headers_referrer', 'isreferrer');
unregister_setting('security_group', 'security_headers_ect_time', 'istime');
unregister_setting('security_group', 'security_headers_ect_enforce', 'ischecked');
unregister_setting('security_group', 'security_headers_ect_uri', 'isuri');
}
register_deactivation_hook(__FILE__, 'security_headers_deactivate');
function security_headers_display_form() {
echo '<div class="wrap">';
echo '<h1>Options for HTTP Headers</h1>';
echo '<form action="options.php" method="POST">';
settings_fields('security_group');
do_settings_sections('security_headers');
submit_button();
echo '</form>';
echo '</div>';
}
function security_headers_settings() {
add_options_page('HTTP Headers', 'HTTP Headers', 'manage_options', 'security_headers', 'security_headers_display_form');
add_settings_section('section_HEAD', 'General Security Headers', 'section_HEAD_callback', 'security_headers');
add_settings_field( 'field_HSTS_nosniff', 'Disable content sniffing', 'field_HSTS_nosniff_callback', 'security_headers', 'section_HEAD');
add_settings_field( 'field_HSTS_xss', 'Enable Chrome XSS protection', 'field_HSTS_xss_callback', 'security_headers', 'section_HEAD');
add_settings_field( 'field_HSTS_frame', 'Restrict framing of main site', 'field_HSTS_frame_callback', 'security_headers', 'section_HEAD');
add_settings_field( 'field_HSTS_referrer', 'HTTP Referrer Policy', 'field_HSTS_referrer_callback', 'security_headers', 'section_HEAD');
add_settings_section('section_HSTS', 'HTTPS Strict Transport Security', 'section_HSTS_callback', 'security_headers');
add_settings_field( 'field_HSTS_time', 'HSTS Time to live (seconds)', 'field_HSTS_time_callback', 'security_headers', 'section_HSTS');
add_settings_field( 'field_HSTS_subdomain', 'HSTS to include subdomains', 'field_HSTS_subdomain_callback', 'security_headers', 'section_HSTS');
add_settings_field( 'field_HSTS_preload', 'HSTS include site in preload list', 'field_HSTS_preload_callback', 'security_headers', 'section_HSTS');
add_settings_section('section_HPKP', 'HTTP Public Key Pinning', 'section_HPKP_callback', 'security_headers');
add_settings_field( 'field_HPKP_time', 'HPKP Time to live (seconds)', 'field_HPKP_time_callback', 'security_headers', 'section_HPKP');
add_settings_field( 'field_HPKP_subdomain', 'HPKP to include subdomains', 'field_HPKP_subdomain_callback', 'security_headers', 'section_HPKP');
add_settings_field( 'field_HPKP_key1', 'HPKP first key', 'field_HPKP_key1_callback', 'security_headers', 'section_HPKP');
add_settings_field( 'field_HPKP_key2', 'HPKP backup key', 'field_HPKP_key2_callback', 'security_headers', 'section_HPKP');
add_settings_field( 'field_HPKP_key3', 'HPKP optional backup key', 'field_HPKP_key3_callback', 'security_headers', 'section_HPKP');
add_settings_field( 'field_HPKP_uri', 'HPKP Reporting URI', 'field_HPKP_uri_callback', 'security_headers', 'section_HPKP');
add_settings_section('section_ECT', 'Expect Certificate Transparency', 'section_ECT_callback', 'security_headers');
add_settings_field( 'field_ECT_time', 'Expect-CT max-age (seconds)', 'field_ECT_time_callback', 'security_headers', 'section_ECT');
add_settings_field( 'field_ECT_enforce', 'Expect-CT Enforcement', 'field_ECT_enforce_callback', 'security_headers', 'section_ECT');
add_settings_field( 'field_ECT_uri', 'Expect-CT Reporting URI', 'field_ECT_uri_callback', 'security_headers', 'section_ECT');
}
add_action('admin_init', 'security_headers_activate');
add_action('admin_menu', 'security_headers_settings');
function section_HEAD_callback() {
echo '<p>Security headers unrelated to HSTS or HPKP.</p>';
echo '<p>Always disable <a href="https://blogs.msdn.microsoft.com/ie/2008/09/02/ie8-security-part-vi-beta-2-update/">Content sniffing</a>.</p>';
echo '<p>XSS protection is enabled by default in IE and Chrome, but they try to clean up XSS requests; this setting ensures this behaviour is on in the browser even if the user disabled it, and that requests that trigger the warning are blocked and shown to the user, rather than silently mangled.</p>';
echo '<p>Restrict framing prevent clickjacking in the main site, WordPress already sets this restriction for the admin and login pages.</p>';
echo '<p>Referrer Policy restricts what is included in referer header of requests created by following links in the page.</p>';
}
function section_HSTS_callback() {
echo '<p>Only enable HSTS when you have a working site over HTTPS with no errors, with redirects from http to https.</p>';
echo '<p>We recommend you enable it with a small time to live (say 300s) initially, and increase after testing the site.</p>';
echo '<p>A blank field means no header, "0" means remove HSTS, and an integer is a time in seconds</p>';
echo '<p>Include subdomains means all subdomains will use HTTPS.<br> Beware if serving "example.com" from server usually known as "www.example.com" this would mean any subdomain of "example.com" to someone visiting via that name if the certificate covers it. </p>';
echo '<p>Include site in preload list means browser authors may add it to their hardcoded list, tick when you are sure HSTS is right for youir site.</p>';
}
function field_HSTS_time_callback() {
$setting = esc_attr(get_option('security_headers_hsts_time'));
echo "<input type='text' name='security_headers_hsts_time' value='$setting' />";
}
function field_HSTS_subdomain_callback() {
$setting = esc_attr(get_option('security_headers_hsts_subdomains'));
echo "<input type='checkbox' name='security_headers_hsts_subdomains' value='1' ";
checked($setting, "1");
echo " />";
}
function field_HSTS_preload_callback() {
$setting = esc_attr(get_option('security_headers_hsts_preload'));
echo "<input type='checkbox' name='security_headers_hsts_preload' value='1' ";
checked($setting, "1");
echo " />";
}
function field_HSTS_nosniff_callback() {
$setting = esc_attr(get_option('security_headers_nosniff'));
echo "<input type='checkbox' name='security_headers_nosniff' value='1' ";
checked($setting, "1");
echo " />";
}
function field_HSTS_xss_callback() {
$setting = esc_attr(get_option('security_headers_xss'));
echo "<input type='checkbox' name='security_headers_xss' value='1' ";
checked($setting, "1");
echo " />";
}
function field_HSTS_frame_callback() {
$setting = esc_attr(get_option('security_headers_frame'));
echo "<input type='checkbox' name='security_headers_frame' value='1' ";
checked($setting, "1");
echo " />";
}
function field_HSTS_referrer_callback() {
global $referrer_policies;
$setting = esc_attr(get_option('security_headers_referrer'));
echo "<select name='security_headers_referrer'>";
foreach ($referrer_policies as $policy => $description){
echo '<option value="'.$policy.'"'.selected($setting,$policy).'>'.$policy.':'.$description.'</option>';
}
echo "</select>";
}
function section_HPKP_callback() {
echo '<p>Include the subdomains to ensure they can\'t be used to extract cookies or other sensitive data.</p>';
echo 'Keys are base64 encoded SHA256 of the public key see <a href="https://waters.me/wordpress/hpkp-pinning-policy/">HPKP Pinning policy</a> for details and a command reference.</p>';
}
function field_HPKP_time_callback() {
$setting = esc_attr(get_option('security_headers_hpkp_time'));
echo "<input type='text' name='security_headers_hpkp_time' value='$setting' />";
}
function field_HPKP_subdomain_callback() {
$setting = esc_attr(get_option('security_headers_hpkp_subdomains'));
echo "<input type='checkbox' name='security_headers_hpkp_subdomains' value='1' ";
checked($setting, "1");
echo " />";
}
function field_HPKP_key1_callback() {
$setting = esc_attr(get_option('security_headers_hpkp_key1'));
echo "<input type='text' name='security_headers_hpkp_key1' value='$setting' />";
}
function field_HPKP_key2_callback() {
$setting = esc_attr(get_option('security_headers_hpkp_key2'));
echo "<input type='text' name='security_headers_hpkp_key2' value='$setting' />";
}
function field_HPKP_key3_callback() {
$setting = esc_attr(get_option('security_headers_hpkp_key3'));
echo "<input type='text' name='security_headers_hpkp_key3' value='$setting' />";
}
function field_HPKP_uri_callback() {
$setting = esc_attr(get_option('security_headers_hpkp_uri'));
echo "<input type='text' name='security_headers_hpkp_uri' value='$setting' />";
}
function section_ECT_callback() {
echo '<p>The Expect-CT header is a temporary measure to allow users to ensure their site is correctly configured for Certificate Transparency which Chrome will enforce from April 2018.</p>';
echo '<p>See <a href="https://scotthelme.co.uk/a-new-security-header-expect-ct/">Scott Helme\'s blog for more details.</p>';
}
function field_ECT_time_callback() {
$setting = esc_attr(get_option('security_headers_ect_time'));
echo "<input type='text' name='security_headers_ect_time' value='$setting' />";
}
function field_ECT_enforce_callback() {
$setting = esc_attr(get_option('security_headers_ect_enforce'));
echo "<input type='checkbox' name='security_headers_ect_enforce' value='1' ";
checked($setting, "1");
echo " />";
}
function field_ECT_uri_callback() {
$setting = esc_attr(get_option('security_headers_ect_uri'));
echo "<input type='text' name='security_headers_ect_uri' value='$setting' />";
}
function ischecked($input) {
$result = "0";
if ("1" === $input) {
$result = "1" ;
}
return $result;
}
function istime($input) {
// Two results either empty string - no header - or natural number (header) as "0" means remove HSTS/HPKP from this domain
$result = "";
if (ctype_digit($input)) {
$result = $input ;
}
return $result;
}
function iskey($input) {
$result="";
if (preg_match("/^[a-zA-Z0-9+\/]{43}=$/", $input)){ // base 64 of 256 bit with equal sign added
$result=$input;
}
return $result ;
}
function isuri($input) {
$result="";
if (preg_match('%^(?:(?:https?|ftp)://)(?:\S+(?::\S*)?@|\d{1,3}(?:\.\d{1,3}){3}|(?:(?:[a-z\d\x{00a1}-\x{ffff}]+-?)*[a-z\d\x{00a1}-\x{ffff}]+)(?:\.(?:[a-z\d\x{00a1}-\x{ffff}]+-?)*[a-z\d\x{00a1}-\x{ffff}]+)*(?:\.[a-z\x{00a1}-\x{ffff}]{2,6}))(?::\d+)?(?:[^\s]*)?$%iu', $input)){ // https://mathiasbynens.be/demo/url-regex diegoperini
$result=$input;
}
return $result ;
}
function isreferrer($input) {
global $referrer_policies;
$result="";
if (array_key_exists($input,$referrer_policies)) {
$result=$input;
}
return $result;
}