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 / user-agent-lockout.php

user-agent-lockout.php in Defender Security – Malware Scanner, Login Security & Firewall 3.0.1, at src/component/user-agent-lockout.php

265 lines 6.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WP_Defender\Component;
4
5 use WP_Defender\Component;
6 use WP_Defender\Model\Lockout_Log;
7 use WP_Defender\Model\Lockout_Ip;
8
9 /**
10 * Class User_Agent.
11 * Example of User-Agent format:
12 * User-Agent: Mozilla/5.0 (<system-information>) <platform> (<platform-details>) <extensions>
13 * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent
14 *
15 * @package WP_Defender\Component
16 * @since 2.6.0
17 */
18 class User_Agent extends Component {
19 use \WP_Defender\Traits\Country;
20
21 public const SCENARIO_USER_AGENT_LOCKOUT = 'user_agent_lockout';
22 public const REASON_BAD_USER_AGENT = 'bad_user_agent', REASON_BAD_POST = 'bad_post';
23
24 /**
25 * Human Readable text denotes user agent header is empty.
26 */
27 public const EMPTY_USER_AGENT_TEXT = 'Empty User Agent';
28
29 /**
30 * Use for cache.
31 *
32 * @var \WP_Defender\Model\Setting\User_Agent_Lockout
33 */
34 protected $model;
35
36 /**
37 * Lockout IP model instance.
38 *
39 * @var Lockout_Ip
40 */
41 protected $lockout_ip_model;
42
43 public function __construct() {
44 $this->model = wd_di()->get( \WP_Defender\Model\Setting\User_Agent_Lockout::class );
45
46 $this->lockout_ip_model = wd_di()->get( Lockout_Ip::class );
47 }
48
49 /**
50 * Log the event into db, we will use the data in logs page later.
51 *
52 * @param string $ip
53 * @param string $user_agent
54 * @param string $reason
55 */
56 private function log_event( $ip, $user_agent, $reason ) {
57 $model = new Lockout_Log();
58 $model->ip = $ip;
59 $model->user_agent = $user_agent;
60 $model->date = time();
61 $model->tried = $user_agent;
62 $model->blog_id = get_current_blog_id();
63 $model->type = Lockout_Log::LOCKOUT_UA;
64
65 $ip_to_country = $this->ip_to_country( $ip );
66
67 if ( ! empty( $ip_to_country ) && isset( $ip_to_country['iso'] ) ) {
68 $model->country_iso_code = $ip_to_country['iso'];
69 }
70
71 switch ( $reason ) {
72 case self::REASON_BAD_POST:
73 // Distinguish between different block cases of User agent lockouts.
74 $model->tried = self::REASON_BAD_POST;
75 $model->log = __( 'Locked out due to empty User-Agent and Referer headers', 'wpdef' );
76 break;
77 case self::REASON_BAD_USER_AGENT:
78 default:
79 $model->tried = $user_agent;
80 $model->log = __( 'Locked out due to attempted login with banned user agent', 'wpdef' );
81 break;
82 }
83 $model->save();
84 }
85
86 /**
87 * @param string $string
88 *
89 * @return string
90 */
91 private function extra_cleaning( $string ) {
92 $string = str_replace( '$', 'S;', $string );
93 $string = str_replace( '{', '!', $string );
94 $string = str_replace( 'shell', 'chell', $string );
95 $string = str_replace( 'curl', 'kurl', $string );
96 $string = str_replace( '<', '&lt;', $string );
97
98 return $string;
99 }
100
101 /**
102 * Queue hooks when this class init.
103 */
104 public function add_hooks() {}
105
106 public function is_active_component() {
107 return $this->model->is_active() && ! is_admin();
108 }
109
110 /**
111 * Is the current UA bad?
112 *
113 * @param string $user_agent
114 *
115 * @return bool
116 */
117 public function is_bad_user_agent( $user_agent ) {
118 foreach ( $this->model->get_lockout_list( 'allowlist' ) as $allowlist ) {
119 if ( false !== strpos( $user_agent, $allowlist ) ) {
120 // No log.
121 return false;
122 }
123 }
124 foreach ( $this->model->get_lockout_list( 'blocklist' ) as $blocklist ) {
125 if ( false !== strpos( $user_agent, $blocklist ) ) {
126 return true;
127 }
128 }
129
130 return false;
131 }
132
133 /**
134 * @return string
135 */
136 public function get_message() {
137 return ! empty( $this->model->message )
138 ? $this->model->message
139 : __( 'You have been blocked from accessing this website.', 'wpdef' );
140 }
141
142 /**
143 * Block the current UA.
144 *
145 * @param string $user_agent
146 * @param string $ip
147 * @param string $reason
148 *
149 * @return void
150 */
151 public function block_user_agent_or_ip( $user_agent, $ip, $reason ) {
152 // @since 2.6.0
153 do_action( 'wd_user_agent_before_block', $user_agent, $ip, $reason );
154 $this->log_event( $ip, $user_agent, $reason );
155 // Todo: when a checkbox for UA lockout is ready in Firewall Notification, then Def can use the 'defender_notify' hook to send email.
156 do_action( 'wd_user_agent_lockout', $this->model, self::SCENARIO_USER_AGENT_LOCKOUT );
157 // Shouldn't block IP via hook 'wd_blacklist_this_ip', block only when the button 'Ban IP' is clicked.
158 }
159
160 /**
161 * @param string $user_agent
162 *
163 * @return string
164 */
165 public static function fast_cleaning( $user_agent ) {
166 return trim( sanitize_text_field( $user_agent ) );
167 }
168
169 /**
170 * Sanitize User Agent.
171 *
172 * @return string
173 */
174 public function sanitize_user_agent() {
175 if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
176 return '';
177 }
178
179 $user_agent = apply_filters( 'wd_current_user_agent', $_SERVER['HTTP_USER_AGENT'] );
180 $user_agent = self::fast_cleaning( $user_agent );
181 $user_agent = strtolower( $user_agent );
182
183 return $this->extra_cleaning( $user_agent );
184 }
185
186 /**
187 * Is the POST request with blank User-Agent and Referer?
188 *
189 * @param string $user_agent
190 *
191 * @return bool
192 */
193 public function is_bad_post( $user_agent ) {
194
195 return true === $this->model->empty_headers
196 && 'POST' === $_SERVER['REQUEST_METHOD']
197 && empty( $user_agent )
198 && empty( $_SERVER['HTTP_REFERER'] );
199 }
200
201 /**
202 * Validate import file is in right format and usable for User Agent Lockout.
203 *
204 * @param $file
205 *
206 * @return array|bool
207 */
208 public function verify_import_file( $file ) {
209 $fp = fopen( $file, 'r' );
210 $data = array();
211 while ( ( $line = fgetcsv( $fp ) ) !== false ) { //phpcs:ignore
212 if ( 2 !== count( $line ) ) {
213 return false;
214 }
215
216 if ( ! in_array( $line[1], array( 'allowlist', 'blocklist' ), true ) ) {
217 return false;
218 }
219
220 $ua = $line[0];
221 $ua = self::fast_cleaning( $ua );
222 $ua = $this->extra_cleaning( $ua );
223 if ( '' === $ua ) {
224 continue;
225 }
226 $line[0] = $ua;
227
228 $data[] = $line;
229 }
230 fclose( $fp );
231
232 return $data;
233 }
234
235 /**
236 * Get human readable user agent log status text.
237 *
238 * @param string $log_type Type of the log. Handles on 'ua_lockout'.
239 * @param string $user_agent User Agent name.
240 *
241 * @return string Human readable text if log_type is UA else empty string.
242 */
243 public function get_status_text( $log_type, $user_agent ) {
244
245 if ( Lockout_Log::LOCKOUT_UA !== $log_type ) {
246 return '';
247 }
248
249 $status_text = self::EMPTY_USER_AGENT_TEXT;
250
251 if ( self::REASON_BAD_POST === $user_agent ) {
252 return $status_text;
253 }
254
255 $user_agent_key = $this->model->get_access_status( $user_agent );
256
257 if ( ! empty( $user_agent_key[0] ) ) {
258 $status_text = $this->lockout_ip_model
259 ->get_access_status_text( $user_agent_key[0] );
260 }
261
262 return $status_text;
263 }
264 }
265