MalCare WordPress Security Plugin – Malware Scanner, Cleaner, Security Firewall - Version 1.91

Version Description

  • Request profling and logging
Download this release

Release Info

Developer ritesh.soni36
Plugin Icon 128x128 MalCare WordPress Security Plugin – Malware Scanner, Cleaner, Security Firewall
Version 1.91
Comparing to
See all releases

Code changes from version 1.89 to 1.91

Files changed (8) hide show
  1. callback/wings/fw.php +4 -0
  2. callback/wings/info.php +2 -0
  3. fw/config.php +28 -1
  4. fw/fw.php +102 -9
  5. fw/request.php +24 -6
  6. main.php +2 -2
  7. malcare.php +2 -2
  8. readme.txt +4 -1
callback/wings/fw.php CHANGED
@@ -29,6 +29,10 @@ class BVFirewallCallback {
29
  $config->setRulesMode($_REQUEST['rules_mode']);
30
  $bvresp->addStatus("rules_mode", $config->getRulesMode());
31
  break;
 
 
 
 
32
  default:
33
  return false;
34
  }
29
  $config->setRulesMode($_REQUEST['rules_mode']);
30
  $bvresp->addStatus("rules_mode", $config->getRulesMode());
31
  break;
32
+ case "setreqprofilingmode":
33
+ $config->setReqProfilingMode($_REQUEST['req_profiling_mode']);
34
+ $bvresp->addStatus("req_profiling_mode", $config->getReqProfilingMode());
35
+ break;
36
  default:
37
  return false;
38
  }
callback/wings/info.php CHANGED
@@ -217,10 +217,12 @@ class BVInfoCallback {
217
  $drules = $bvinfo->getOption('bvfwdisabledrules');
218
  $arules = $bvinfo->getOption('bvfwauditrules');
219
  $rmode = $bvinfo->getOption('bvfwrulesmode');
 
220
  $config['mode'] = intval($mode ? $mode : 1);
221
  $config['disabled_rules'] = $drules ? $drules : array();
222
  $config['audit_rules'] = $arules ? $arules : array();
223
  $config['rules_mode'] = intval($rmode ? $rmode : 1);
 
224
  return $config;
225
  }
226
 
217
  $drules = $bvinfo->getOption('bvfwdisabledrules');
218
  $arules = $bvinfo->getOption('bvfwauditrules');
219
  $rmode = $bvinfo->getOption('bvfwrulesmode');
220
+ $reqprofilingmode = $bvinfo->getOption('bvfwreqprofilingmode');
221
  $config['mode'] = intval($mode ? $mode : 1);
222
  $config['disabled_rules'] = $drules ? $drules : array();
223
  $config['audit_rules'] = $arules ? $arules : array();
224
  $config['rules_mode'] = intval($rmode ? $rmode : 1);
225
+ $config['req_profiling_mode'] = intval($reqprofilingmode ? $reqprofilingmode : 1);
226
  return $config;
227
  }
228
 
fw/config.php CHANGED
@@ -22,6 +22,11 @@ class BVFWConfig {
22
  const AUDITRULE = 2;
23
  const PROTECTRULE = 3;
24
 
 
 
 
 
 
25
  public static function isDisabledRule($mode) {
26
  return ($mode === BVFWConfig::DISABLEDRULE);
27
  }
@@ -46,6 +51,14 @@ class BVFWConfig {
46
  return ($this->getMode() === BVFWConfig::AUDIT);
47
  }
48
 
 
 
 
 
 
 
 
 
49
  public function getRules() {
50
  $rules = array("audit" => array(), "protect" => array());
51
  $isAudit = false;
@@ -91,6 +104,14 @@ class BVFWConfig {
91
  }
92
  }
93
 
 
 
 
 
 
 
 
 
94
  public function setDisabledRules($rules) {
95
  if (!$rules) {
96
  $this->bvmain->info->deleteOption('bvfwdisabledrules');
@@ -117,6 +138,11 @@ class BVFWConfig {
117
  return intval($mode ? $mode : BVFWConfig::DISABLED);
118
  }
119
 
 
 
 
 
 
120
  public function getDisabledRules() {
121
  $rules = $this->bvmain->info->getOption('bvfwdisabledrules');
122
  return ($rules ? $rules : array());
@@ -132,9 +158,10 @@ class BVFWConfig {
132
  $this->setRulesMode(false);
133
  $this->setDisabledRules(false);
134
  $this->setAuditRules(false);
 
135
  $this->bvmain->db->dropBVTable(BVFWConfig::$requests_table);
136
  $this->bvmain->info->deleteOption('bvptplug');
137
  return true;
138
  }
139
  }
140
- endif;
22
  const AUDITRULE = 2;
23
  const PROTECTRULE = 3;
24
 
25
+ #Request Profiling Mode
26
+ const REQ_PROFILING_MODE_DISABLED = 1;
27
+ const REQ_PROFILING_MODE_NORMAL = 2;
28
+ const REQ_PROFILING_MODE_DEBUG = 3;
29
+
30
  public static function isDisabledRule($mode) {
31
  return ($mode === BVFWConfig::DISABLEDRULE);
32
  }
51
  return ($this->getMode() === BVFWConfig::AUDIT);
52
  }
53
 
54
+ public function isReqProfilingModeDebug() {
55
+ return ($this->getReqProfilingMode() === BVFWConfig::REQ_PROFILING_MODE_DEBUG);
56
+ }
57
+
58
+ public function canProfileReqInfo() {
59
+ return ($this->getReqProfilingMode() !== BVFWConfig::REQ_PROFILING_MODE_DISABLED);
60
+ }
61
+
62
  public function getRules() {
63
  $rules = array("audit" => array(), "protect" => array());
64
  $isAudit = false;
104
  }
105
  }
106
 
107
+ public function setReqProfilingMode($mode) {
108
+ if (!$mode) {
109
+ $this->bvmain->info->deleteOption('bvfwreqprofilingmode');
110
+ } else {
111
+ $this->bvmain->info->updateOption('bvfwreqprofilingmode', intval($mode));
112
+ }
113
+ }
114
+
115
  public function setDisabledRules($rules) {
116
  if (!$rules) {
117
  $this->bvmain->info->deleteOption('bvfwdisabledrules');
138
  return intval($mode ? $mode : BVFWConfig::DISABLED);
139
  }
140
 
141
+ public function getReqProfilingMode() {
142
+ $mode = $this->bvmain->info->getOption('bvfwreqprofilingmode');
143
+ return intval($mode ? $mode : BVFWConfig::REQ_PROFILING_MODE_DISABLED);
144
+ }
145
+
146
  public function getDisabledRules() {
147
  $rules = $this->bvmain->info->getOption('bvfwdisabledrules');
148
  return ($rules ? $rules : array());
158
  $this->setRulesMode(false);
159
  $this->setDisabledRules(false);
160
  $this->setAuditRules(false);
161
+ $this->setReqProfilingMode(false);
162
  $this->bvmain->db->dropBVTable(BVFWConfig::$requests_table);
163
  $this->bvmain->info->deleteOption('bvptplug');
164
  return true;
165
  }
166
  }
167
+ endif;
fw/fw.php CHANGED
@@ -59,17 +59,12 @@ class BVFW {
59
 
60
  public function init() {
61
  if ($this->config->isActive()) {
62
- $this->initLogger();
 
63
  }
64
  add_action('clear_fw_config', array($this->config, 'clear'));
65
  }
66
 
67
- public function initLogger() {
68
- add_filter('status_header', array($this->request, 'captureRespCode'));
69
- add_action('admin_init', array($this, 'log'));
70
- add_action('template_redirect', array($this, 'log'));
71
- }
72
-
73
  public function log() {
74
  if (!function_exists('is_user_logged_in') || !is_user_logged_in()) {
75
  $this->logger->log($this->request->getDataToLog());
@@ -80,7 +75,6 @@ class BVFW {
80
  $this->request->setCategory($category);
81
  $this->request->setStatus(BVRequest::BLOCKED);
82
  $this->request->setRespCode(403);
83
- $this->log();
84
  header("Cache-Control: no-cache, no-store, must-revalidate");
85
  header("Pragma: no-cache");
86
  header("Expires: 0");
@@ -115,6 +109,16 @@ class BVFW {
115
  }
116
 
117
  public function execute() {
 
 
 
 
 
 
 
 
 
 
118
  if (!$this->canBypassFirewall()) {
119
  $rules = $this->config->getRules();
120
  $this->matchRules($rules["audit"]);
@@ -192,6 +196,95 @@ class BVFW {
192
  return $value != $subject;
193
  }
194
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
  public function matchRules($rules = array(), $isProtect = false) {
196
  if (empty($rules)) {
197
  return false;
@@ -413,4 +506,4 @@ class BVFW {
413
  return false;
414
  }
415
  }
416
- endif;
59
 
60
  public function init() {
61
  if ($this->config->isActive()) {
62
+ add_filter('status_header', array($this->request, 'captureRespCode'));
63
+ register_shutdown_function(array($this, 'log'));
64
  }
65
  add_action('clear_fw_config', array($this->config, 'clear'));
66
  }
67
 
 
 
 
 
 
 
68
  public function log() {
69
  if (!function_exists('is_user_logged_in') || !is_user_logged_in()) {
70
  $this->logger->log($this->request->getDataToLog());
75
  $this->request->setCategory($category);
76
  $this->request->setStatus(BVRequest::BLOCKED);
77
  $this->request->setRespCode(403);
 
78
  header("Cache-Control: no-cache, no-store, must-revalidate");
79
  header("Pragma: no-cache");
80
  header("Expires: 0");
109
  }
110
 
111
  public function execute() {
112
+ if ($this->config->canProfileReqInfo()) {
113
+ $result = array();
114
+ $result += $this->profileRequestInfo($this->request->getBody(),
115
+ $this->config->isReqProfilingModeDebug(), 'BODY_');
116
+ $result += $this->profileRequestInfo($this->request->getQueryString(),
117
+ true, 'GET_');
118
+ $result += $this->profileRequestInfo($this->request->getFiles(),
119
+ true, 'FILES_');
120
+ $this->request->updateReqInfo($result);
121
+ }
122
  if (!$this->canBypassFirewall()) {
123
  $rules = $this->config->getRules();
124
  $this->matchRules($rules["audit"]);
196
  return $value != $subject;
197
  }
198
 
199
+ public function profileRequestInfo($params, $debug = false, $prefix = '') {
200
+ $result = array();
201
+ if (is_array($params)) {
202
+ foreach ($params as $key => $value) {
203
+ $currkey = $prefix . $key;
204
+ if (is_array($value)) {
205
+ $result = $result + $this->profileRequestInfo($value, $debug, $currkey . '_');
206
+ } else {
207
+ $result[$currkey] = array();
208
+ $valsize = $this->getLength($value);
209
+ $result[$currkey]["size"] = $valsize;
210
+ if ($debug === true && $valsize < 256) {
211
+ $result[$currkey]["value"] = $value;
212
+ continue;
213
+ }
214
+
215
+ if (preg_match('/^\d+$/', $value)) {
216
+ $result[$currkey]["numeric"] = true;
217
+ } else if (preg_match('/^\w+$/', $value)) {
218
+ $result[$currkey]["regular_word"] = true;
219
+ } else if (preg_match('/^\S+$/', $value)) {
220
+ $result[$currkey]["special_word"] = true;
221
+ } else if (preg_match('/^[\w\s]+$/', $value)) {
222
+ $result[$currkey]["regular_sentence"] = true;
223
+ } else if (preg_match('/^[\w\W]+$/', $value)) {
224
+ $result[$currkey]["special_chars_sentence"] = true;
225
+ }
226
+
227
+ if (preg_match('/^\b((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}
228
+ (25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\b$/x', $value)) {
229
+ $result[$currkey]["ipv4"] = true;
230
+ } else if (preg_match('/\b((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}
231
+ (25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\b/x', $value)) {
232
+ $result[$currkey]["embeded_ipv4"] = true;
233
+ } else if (preg_match('/^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|
234
+ ([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|
235
+ ([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}
236
+ (:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|
237
+ ([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|
238
+ :((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|
239
+ ::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3}
240
+ (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|
241
+ (2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/x', $value)) {
242
+ $result[$currkey]["ipv6"] = true;
243
+ } else if (preg_match('/(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|
244
+ ([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|
245
+ ([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}
246
+ (:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|
247
+ ([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|
248
+ :((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|
249
+ ::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3}
250
+ (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|
251
+ (2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/x', $value)) {
252
+ $result[$currkey]["embeded_ipv6"] = true;
253
+ }
254
+
255
+ if (preg_match('/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/', $value)) {
256
+ $result[$currkey]["email"] = true;
257
+ } else if (preg_match('/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}/', $value)) {
258
+ $result[$currkey]["embeded_email"] = true;
259
+ }
260
+
261
+ if (preg_match('/^(http|ftp)s?:\/\/\S+$/i', $value)) {
262
+ $result[$currkey]["link"] = true;
263
+ } else if (preg_match('/(http|ftp)s?:\/\/\S+$/i', $value)) {
264
+ $result[$currkey]["embeded_link"] = true;
265
+ }
266
+
267
+ if (preg_match('/<(html|head|title|base|link|meta|style|picture|source|img|
268
+ iframe|embed|object|param|video|audio|track|map|area|form|label|input|button|
269
+ select|datalist|optgroup|option|textarea|output|progress|meter|fieldset|legend|
270
+ script|noscript|template|slot|canvas)/ix', $value)) {
271
+ $result[$currkey]["embeded_html"] = true;
272
+ }
273
+
274
+ if (preg_match('/\.(jpg|jpeg|png|gif|ico|pdf|doc|docx|ppt|pptx|pps|ppsx|odt|xls|zip|gzip|
275
+ xlsx|psd|mp3|m4a|ogg|wav|mp4|m4v|mov|wmv|avi|mpg|ogv|3gp|3g2|php|html|phtml|js|css)/ix', $value)) {
276
+ $result[$currkey]["file"] = true;
277
+ }
278
+
279
+ if ($this->matchCount(BVFW::SQLIREGEX, $value) >= 2) {
280
+ $result[$currkey]["sql"] = true;
281
+ }
282
+ }
283
+ }
284
+ }
285
+ return $result;
286
+ }
287
+
288
  public function matchRules($rules = array(), $isProtect = false) {
289
  if (empty($rules)) {
290
  return false;
506
  return false;
507
  }
508
  }
509
+ endif;
fw/request.php CHANGED
@@ -18,6 +18,8 @@ class BVRequest {
18
  private $respcode;
19
  private $status;
20
  private $rulesInfo;
 
 
21
  #status
22
  const ALLOWED = 1;
23
  const BLOCKED = 2;
@@ -36,7 +38,8 @@ class BVRequest {
36
  $path = '';
37
  $this->ip = $ip;
38
  $this->rulesInfo = array();
39
- $this->setRespCode(200);
 
40
  $this->setCategory(BVRequest::NORMAL);
41
  $this->setStatus(BVRequest::ALLOWED);
42
  $this->setTimestamp(time());
@@ -90,7 +93,7 @@ class BVRequest {
90
  $this->setUri($uri);
91
  $this->setPath($path);
92
  }
93
-
94
  public function setStatus($status) {
95
  $this->status = $status;
96
  }
@@ -159,6 +162,16 @@ class BVRequest {
159
  return $this->rulesInfo;
160
  }
161
 
 
 
 
 
 
 
 
 
 
 
162
  public function getStatus() {
163
  return $this->status;
164
  }
@@ -175,11 +188,16 @@ class BVRequest {
175
  }
176
 
177
  public function getDataToLog() {
178
- $querystr = maybe_serialize($this->getQueryString());
179
- $querystr = (strlen($querystr) > 512) ? maybe_serialize(array("bv_over_size" => true)) : $querystr;
180
  $referer = $this->getHeader('Referer') ? $this->getHeader('Referer') : '';
181
  $user_agent = $this->getHeader('User-Agent') ? $this->getHeader('User-Agent') : '';
182
  $rules_info = maybe_serialize($this->getRulesInfo());
 
 
 
 
 
 
 
183
  $data = array(
184
  "path" => $this->getPath(),
185
  "filenames" => maybe_serialize($this->getFileNames()),
@@ -187,7 +205,7 @@ class BVRequest {
187
  "time" => $this->getTimeStamp(),
188
  "ip" => $this->getIP(),
189
  "method" => $this->getMethod(),
190
- "query_string" => $querystr,
191
  "user_agent" => $user_agent,
192
  "resp_code" => $this->getRespCode(),
193
  "referer" => $referer,
@@ -303,4 +321,4 @@ class BVRequest {
303
  return $value;
304
  }
305
  }
306
- endif;
18
  private $respcode;
19
  private $status;
20
  private $rulesInfo;
21
+ private $reqInfo;
22
+
23
  #status
24
  const ALLOWED = 1;
25
  const BLOCKED = 2;
38
  $path = '';
39
  $this->ip = $ip;
40
  $this->rulesInfo = array();
41
+ $this->reqInfo = array();
42
+ $this->setRespCode(0);
43
  $this->setCategory(BVRequest::NORMAL);
44
  $this->setStatus(BVRequest::ALLOWED);
45
  $this->setTimestamp(time());
93
  $this->setUri($uri);
94
  $this->setPath($path);
95
  }
96
+
97
  public function setStatus($status) {
98
  $this->status = $status;
99
  }
162
  return $this->rulesInfo;
163
  }
164
 
165
+ public function updateReqInfo($info) {
166
+ if (is_array($info)) {
167
+ $this->reqInfo = $this->reqInfo + $info;
168
+ }
169
+ }
170
+
171
+ public function getReqInfo() {
172
+ return $this->reqInfo;
173
+ }
174
+
175
  public function getStatus() {
176
  return $this->status;
177
  }
188
  }
189
 
190
  public function getDataToLog() {
 
 
191
  $referer = $this->getHeader('Referer') ? $this->getHeader('Referer') : '';
192
  $user_agent = $this->getHeader('User-Agent') ? $this->getHeader('User-Agent') : '';
193
  $rules_info = maybe_serialize($this->getRulesInfo());
194
+ $req_info = maybe_serialize($this->getReqInfo());
195
+ if (strlen($req_info) > 16000) {
196
+ $req_info = maybe_serialize(array("keys" => array_keys($this->getReqInfo())));
197
+ if (strlen($req_info) > 16000) {
198
+ $req_info = maybe_serialize(array("bv_over_size" => true));
199
+ }
200
+ }
201
  $data = array(
202
  "path" => $this->getPath(),
203
  "filenames" => maybe_serialize($this->getFileNames()),
205
  "time" => $this->getTimeStamp(),
206
  "ip" => $this->getIP(),
207
  "method" => $this->getMethod(),
208
+ "query_string" => $req_info,
209
  "user_agent" => $user_agent,
210
  "resp_code" => $this->getRespCode(),
211
  "referer" => $referer,
321
  return $value;
322
  }
323
  }
324
+ endif;
main.php CHANGED
@@ -8,7 +8,7 @@ require_once dirname( __FILE__ ) . '/main/auth.php';
8
  require_once dirname( __FILE__ ) . '/main/db.php';
9
 
10
  class MalCare {
11
- public $version = '1.89';
12
  public $plugname = 'malcare';
13
  public $brandname = 'MalCare';
14
  public $webpage = 'https://www.malcare.com';
@@ -164,4 +164,4 @@ class MalCare {
164
  ##CLEARDYNSYNCCONFIG##
165
  }
166
  }
167
- endif;
8
  require_once dirname( __FILE__ ) . '/main/db.php';
9
 
10
  class MalCare {
11
+ public $version = '1.91';
12
  public $plugname = 'malcare';
13
  public $brandname = 'MalCare';
14
  public $webpage = 'https://www.malcare.com';
164
  ##CLEARDYNSYNCCONFIG##
165
  }
166
  }
167
+ endif;
malcare.php CHANGED
@@ -5,7 +5,7 @@ Plugin URI: https://www.malcare.com
5
  Description: WordPress Security, Firewall and Malware Scanner
6
  Author: MalCare Security
7
  Author URI: https://www.malcare.com
8
- Version: 1.89
9
  Network: True
10
  */
11
 
@@ -89,4 +89,4 @@ if ((array_key_exists('bvplugname', $_REQUEST)) &&
89
  }
90
 
91
  ##DYNSYNCMODULE##
92
- }
5
  Description: WordPress Security, Firewall and Malware Scanner
6
  Author: MalCare Security
7
  Author URI: https://www.malcare.com
8
+ Version: 1.91
9
  Network: True
10
  */
11
 
89
  }
90
 
91
  ##DYNSYNCMODULE##
92
+ }
readme.txt CHANGED
@@ -6,7 +6,7 @@ Donate link: https://www.malcare.com
6
  Requires at least: 4.0
7
  Tested up to: 5.2.1
8
  Requires PHP: 5.3.0
9
- Stable tag: trunk
10
  License: GPLv2 or later
11
  License URI: [http://www.gnu.org/licenses/gpl-2.0.html](http://www.gnu.org/licenses/gpl-2.0.html)
12
 
@@ -218,6 +218,9 @@ FTP details input into MalCare is processed on our servers. We need your FTP cre
218
  8. With BlogVault's White-Label Solution you can showcase our service under your own brilliant brand.
219
 
220
  == CHANGELOG ==
 
 
 
221
  = 1.89 =
222
  * Firewall improvements
223
 
6
  Requires at least: 4.0
7
  Tested up to: 5.2.1
8
  Requires PHP: 5.3.0
9
+ Stable tag: 1.91
10
  License: GPLv2 or later
11
  License URI: [http://www.gnu.org/licenses/gpl-2.0.html](http://www.gnu.org/licenses/gpl-2.0.html)
12
 
218
  8. With BlogVault's White-Label Solution you can showcase our service under your own brilliant brand.
219
 
220
  == CHANGELOG ==
221
+ = 1.91 =
222
+ * Request profling and logging
223
+
224
  = 1.89 =
225
  * Firewall improvements
226