PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 3.0.1
Defender Security – Malware Scanner, Login Security & Firewall v3.0.1
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
defender-security / src / component / security-headers / sh-strict-transport.php

sh-strict-transport.php in Defender Security – Malware Scanner, Login Security & Firewall 3.0.1, at src/component/security-headers/sh-strict-transport.php

246 lines 7.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Author: Hoang Ngo
4 */
5
6 namespace WP_Defender\Component\Security_Headers;
7
8 use WP_Defender\Component\Security_Header;
9
10 class Sh_Strict_Transport extends Security_Header {
11 static $rule_slug = 'sh_strict_transport';
12
13 /**
14 * Get time in seconds
15 *
16 * @return array
17 */
18 private function time_in_seconds() {
19 return array(
20 '1 hour' => 1 * 3600,
21 '24 hours' => 86400,
22 '7 days' => 7 * 86400,
23 '30 days' => 30 * 86400,
24 '3 months' => ( 3 * 30 + 1 ) * 86400,
25 '6 months' => ( 6 * 30 + 3 ) * 86400,
26 '1 year' => 365 * 86400,
27 '2 years' => 365 * 2 * 86400,
28 );
29 }
30
31 /**
32 * Check HTTPS
33 *
34 * @return bool
35 */
36 private function is_https() {
37 return isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO'];
38 }
39
40 public function check() {
41 $model = $this->get_model();
42
43 if ( ! $model->sh_strict_transport ) {
44 return false;
45 }
46 //'max-age' directive is required
47 if ( ! empty( $model->hsts_cache_duration ) ) {
48 return true;
49 }
50 $headers = $this->head_request( network_site_url(), self::$rule_slug );
51 if ( is_wp_error( $headers ) ) {
52 $this->log( sprintf( 'Self ping error: %s', $headers->get_error_message() ) );
53
54 return false;
55 }
56
57 if ( isset( $headers['strict-transport-security'] ) ) {
58 $hsts_cache_duration = '';
59 $hsts_preload = 0;
60 $include_subdomain = 0;
61 $header_sts = is_array( $headers['strict-transport-security'] )
62 ? $headers['strict-transport-security'][0]
63 : $headers['strict-transport-security'];
64
65 $content = explode( ';', $header_sts );
66 foreach ( $content as $line ) {
67 if ( stristr( $line, 'max-age' ) ) {
68 $value = explode( '=', $line );
69 $arr = $this->time_in_seconds();
70 $seconds = isset( $value[1] ) ? (int) $value[1] : 0;
71 $closest = null;
72 $key = null;
73 foreach ( $arr as $k => $item ) {
74 if ( is_null( $closest ) || ( ! is_null( $closest ) && ( abs( $seconds - $closest ) > abs( $item - $seconds ) ) ) ) {
75 $closest = $item;
76 $key = $k;
77 }
78 }
79 $hsts_cache_duration = $key;
80 } elseif ( stristr( $line, 'preload' ) ) {
81 $hsts_preload = 1;
82 } elseif ( stristr( $line, 'includeSubDomains' ) ) {
83 $include_subdomain = 1;
84 }
85 }
86
87 if ( ( '' !== $hsts_cache_duration )
88 || ( 0 !== $hsts_preload )
89 || ( 0 !== $include_subdomain )
90 ) {
91 if ( is_null( $model->hsts_preload ) && $hsts_preload ) {
92 $model->hsts_preload = $hsts_preload;
93 }
94 if ( is_null( $model->include_subdomain ) && $include_subdomain ) {
95 $model->include_subdomain = $include_subdomain;
96 }
97 if ( is_null( $model->hsts_cache_duration ) && $hsts_cache_duration ) {
98 $model->hsts_cache_duration = $hsts_cache_duration;
99 }
100 $model->save();
101 }
102
103 return true;
104 }
105
106 return false;
107 }
108
109 private function get_domain_suffix( $domain ) {
110 require_once dirname( __DIR__ ) . '/public-suffix.php';
111 $tlds = get_public_suffix();
112 //whitelist development
113 $tlds['localhost'] = 1;
114 $parts = explode( '.', $domain );
115 $parts = array_reverse( $parts );
116 $suffix = '';
117 $list = array();
118 $length = 0;
119 foreach ( $parts as $part ) {
120 $suffix = rtrim( $part . '.' . $suffix, '.' );
121 $not_allow = '!' . $suffix;
122 if ( isset( $tlds[ $not_allow ] ) ) {
123 //this wont be here
124 continue;
125 }
126 if ( isset( $tlds[ $suffix ] ) ) {
127 if ( $length > strlen( $suffix ) ) {
128 //put at last
129 $list[] = $suffix;
130 } else {
131 array_unshift( $list, $suffix );
132 }
133 }
134 }
135 if ( empty( $list ) ) {
136 return false;
137 }
138
139 return $list[0];
140 }
141
142 public function parse_domain( $domain ) {
143 $filter_domain = version_compare( PHP_VERSION, '7.0', '>=' ) ? FILTER_VALIDATE_DOMAIN : FILTER_VALIDATE_URL;
144 if ( ! filter_var( $domain, $filter_domain ) ) {
145 return false;
146 }
147 $suffix = $this->get_domain_suffix( $domain );
148 if ( ! $suffix ) {
149 return false;
150 }
151 //exclude 'www.'
152 $domain = str_replace( 'www.', '', $domain );
153 $host = parse_url( $domain, PHP_URL_HOST );
154 $host_without_tld = str_replace( $suffix, '', $host );
155
156 $host_without_tld = rtrim( $host_without_tld, '.' );
157 $parts = explode( '.', $host_without_tld );
158 if ( 1 === count( $parts ) ) {
159 return array(
160 'host' => $host,
161 'tld' => $suffix,
162 );
163 }
164 //parse to get the root & subdomain
165 $domain = array_pop( $parts );
166
167 return array(
168 'host' => $host,
169 'tld' => $suffix,
170 'subdomain' => str_replace( $domain, '', $host_without_tld ),
171 );
172 }
173
174 public function get_misc_data() {
175 $model = $this->get_model();
176 $site_url = network_site_url();
177 $domain_data = $this->parse_domain( $site_url );
178 $allow_subdomain = false;
179
180 if ( is_array( $domain_data ) && ! isset( $domain_data['subdomain'] ) ) {
181 $allow_subdomain = true;
182 } elseif ( ! $domain_data && ! is_multisite() ) {
183 //case if a single site installs in a folder, e.g. http://example.com/something/folder/
184 $allow_subdomain = true;
185 } elseif ( ! $domain_data && is_multisite() && is_subdomain_install() && is_main_site() ) {
186 //case if main site on MU with subdomain install
187 $allow_subdomain = true;
188 }
189
190 return array(
191 'intro_text' => esc_html__( 'The HTTP Strict-Transport-Security response header (HSTS) lets a web site tell browsers that it should only be accessed using HTTPS, instead of using HTTP. This is extremely important for websites that store and process sensitive information like ECommerce stores and helps prevent Protocol Downgrade and Clickjacking attacks.', 'wpdef' ),
192 'hsts_preload' => $model->hsts_preload ?? 0,
193 'include_subdomain' => $model->include_subdomain ?? 0,
194 'hsts_cache_duration' => $model->hsts_cache_duration ?? '30 days',
195 'allow_subdomain' => $allow_subdomain,
196 );
197 }
198
199 public function add_hooks() {
200 add_action( 'send_headers', array( $this, 'append_header' ) );
201 }
202
203 public function append_header() {
204 if ( headers_sent() ) {
205 return;
206 }
207
208 if ( ! $this->maybe_submit_header( 'Strict-Transport-Security', false ) ) {
209 return;
210 }
211 $model = $this->get_model();
212 //header is ignored by the browser when your site is accessed using HTTP
213 if ( true === $model->sh_strict_transport ) {
214 $headers = 'Strict-Transport-Security:';
215 $default_max_age = 604800;
216 if ( isset( $model->hsts_cache_duration ) && ! empty( $model->hsts_cache_duration ) ) {
217 $arr = $this->time_in_seconds();
218 //set default for a week, so RIPs wont waring weak header
219 $seconds = $arr[ $model->hsts_cache_duration ] ?? $default_max_age;
220 if ( ! is_null( $seconds ) ) {
221 $headers .= ' max-age=' . $seconds;
222 }
223 } else {
224 //set default for a week
225 $headers .= ' max-age=' . $default_max_age;
226 }
227
228 if ( '1' === (string) $model->include_subdomain ) {
229 $headers .= ' ; includeSubDomains';
230 }
231 if ( '1' === (string) $model->hsts_preload ) {
232 $headers .= ' ; preload';
233 }
234
235 header( $headers );
236 }
237 }
238
239 /**
240 * @return string
241 */
242 public function get_title() {
243 return __( 'Strict Transport', 'wpdef' );
244 }
245 }
246