Protector   F
last analyzed

Complexity

Total Complexity 307

Size/Duplication

Total Lines 1507
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 307
eloc 735
dl 0
loc 1507
rs 1.865
c 0
b 0
f 0

46 Methods

Rating   Name   Duplication   Size   Complexity  
A get_filepath4group1ips() 0 3 1
A setConn() 0 3 1
A get_filepath4badips() 0 3 1
A get_filepath4bwlimit() 0 3 1
A get_filepath4confighcache() 0 3 1
A getDblayertrapDoubtfuls() 0 3 1
A getConf() 0 3 1
C intval_allrequestsendid() 0 39 14
A write_file_badips() 0 14 2
A updateConfFromDb() 0 27 6
A bigumbrella_init() 0 8 2
A get_bwlimit() 0 6 1
A check_contami_systemglobals() 0 14 1
A write_file_bwlimit() 0 14 2
B replace_doubtful() 0 33 7
B output_log() 0 45 9
B eliminate_dotdot() 0 59 8
B _initial_recursive() 0 25 7
B _dblayertrap_check_recursive() 0 14 7
A deactivateCurrentUser() 0 14 2
B stopForumSpamLookup() 0 29 7
A get_group1_ips() 0 13 4
B dblayertrap_init() 0 17 7
A purgeSession() 0 8 4
C stopforumspam() 0 59 16
B deny_by_htaccess() 0 52 11
D ip_match() 0 52 25
A get_ref_from_base64index() 0 11 3
A getInstance() 0 8 2
A purge() 0 11 3
B get_bad_ips() 0 22 7
A _bigumbrella_check_recursive() 0 9 4
A check_sql_union() 0 22 5
B bigumbrella_outputcheck() 0 25 9
A check_sql_isolatedcommentin() 0 24 6
C check_uploaded_files() 0 76 15
A __construct() 0 54 3
A register_bad_ips() 0 13 4
A spam_check() 0 11 3
B _spam_check_point_recursive() 0 24 7
B check_brute_force() 0 47 6
A call_filter() 0 10 3
F disable_features() 0 90 45
D check_dos_attack() 0 153 28
A purgeCookies() 0 8 4
A purgeNoExit() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Protector often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Protector, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Class Protector
5
 */
6
class Protector
7
{
8
    public $mydirname;
9
10
    public $_conn;
11
    public $_conf            = array();
12
    public $_conf_serialized = '';
13
14
    public $_bad_globals = array();
15
16
    public $message                = '';
17
    public $warning                = false;
18
    public $error                  = false;
19
    public $_doubtful_requests     = array();
20
    public $_bigumbrella_doubtfuls = array();
21
22
    public $_dblayertrap_doubtfuls        = array();
23
    public $_dblayertrap_doubtful_needles = array(
24
        'information_schema',
25
        'select',
26
        "'",
27
        '"');
28
29
    public $_logged = false;
30
31
    public $_done_badext   = false;
32
    public $_done_intval   = false;
33
    public $_done_dotdot   = false;
34
    public $_done_nullbyte = false;
35
    public $_done_contami  = false;
36
    public $_done_isocom   = false;
37
    public $_done_union    = false;
38
    public $_done_dos      = false;
39
40
    public $_safe_badext  = true;
41
    public $_safe_contami = true;
42
    public $_safe_isocom  = true;
43
    public $_safe_union   = true;
44
45
    public $_spamcount_uri = 0;
46
47
    public $_should_be_banned_time0 = false;
48
    public $_should_be_banned       = false;
49
50
    public $_dos_stage;
51
52
    public $ip_matched_info;
53
54
    public $last_error_type = 'UNKNOWN';
55
56
    /**
57
     * Constructor
58
     */
59
    protected function __construct()
60
    {
61
        $this->mydirname = 'protector';
62
63
        // Preferences from configs/cache
64
        $this->_conf_serialized = @file_get_contents($this->get_filepath4confighcache());
65
        $this->_conf            = @unserialize($this->_conf_serialized);
0 ignored issues
show
Bug introduced by mambax7
It seems like $this->_conf_serialized can also be of type false; however, parameter $data of unserialize() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

65
        $this->_conf            = @unserialize(/** @scrutinizer ignore-type */ $this->_conf_serialized);
Loading history...
66
        if (empty($this->_conf)) {
67
            $this->_conf = array();
68
        }
69
70
        if (!empty($this->_conf['global_disabled'])) {
71
            return;
72
        }
73
74
        // die if PHP_SELF XSS found (disabled in 2.53)
75
        //    if ( preg_match( '/[<>\'";\n ]/' , @$_SERVER['PHP_SELF'] ) ) {
76
        //        $this->message .= "Invalid PHP_SELF '{$_SERVER['PHP_SELF']}' found.\n" ;
77
        //        $this->output_log( 'PHP_SELF XSS' ) ;
78
        //        die( 'invalid PHP_SELF' ) ;
79
        //    }
80
81
        // sanitize against PHP_SELF/PATH_INFO XSS (disabled in 3.33)
82
        //    $_SERVER['PHP_SELF'] = strtr( @$_SERVER['PHP_SELF'] , array( '<' => '%3C' , '>' => '%3E' , "'" => '%27' , '"' => '%22' ) ) ;
83
        //    if( ! empty( $_SERVER['PATH_INFO'] ) ) $_SERVER['PATH_INFO'] = strtr( @$_SERVER['PATH_INFO'] , array( '<' => '%3C' , '>' => '%3E' , "'" => '%27' , '"' => '%22' ) ) ;
84
85
        $this->_bad_globals = array(
86
            'GLOBALS',
87
            '_SESSION',
88
            'HTTP_SESSION_VARS',
89
            '_GET',
90
            'HTTP_GET_VARS',
91
            '_POST',
92
            'HTTP_POST_VARS',
93
            '_COOKIE',
94
            'HTTP_COOKIE_VARS',
95
            '_SERVER',
96
            'HTTP_SERVER_VARS',
97
            '_REQUEST',
98
            '_ENV',
99
            '_FILES',
100
            'xoopsDB',
101
            'xoopsUser',
102
            'xoopsUserId',
103
            'xoopsUserGroups',
104
            'xoopsUserIsAdmin',
105
            'xoopsConfig',
106
            'xoopsOption',
107
            'xoopsModule',
108
            'xoopsModuleConfig');
109
110
        $this->_initial_recursive($_GET, 'G');
111
        $this->_initial_recursive($_POST, 'P');
112
        $this->_initial_recursive($_COOKIE, 'C');
113
    }
114
115
    /**
116
     * @param $val
117
     * @param $key
118
     */
119
    protected function _initial_recursive($val, $key)
120
    {
121
        if (is_array($val)) {
122
            foreach ($val as $subkey => $subval) {
123
                // check bad globals
124
                if (in_array($subkey, $this->_bad_globals, true)) {
125
                    $this->message .= "Attempt to inject '$subkey' was found.\n";
126
                    $this->_safe_contami   = false;
127
                    $this->last_error_type = 'CONTAMI';
128
                }
129
                $this->_initial_recursive($subval, $key . '_' . base64_encode($subkey));
130
            }
131
        } else {
132
            // check nullbyte attack
133
            if (@$this->_conf['san_nullbyte'] && false !== strpos($val, chr(0))) {
134
                $val = str_replace(chr(0), ' ', $val);
135
                $this->replace_doubtful($key, $val);
136
                $this->message .= "Injecting Null-byte '$val' found.\n";
137
                $this->output_log('NullByte', 0, false, 32);
138
                // $this->purge() ;
139
            }
140
141
            // register as doubtful requests against SQL Injections
142
            if (preg_match('?[\s\'"`/]?', $val)) {
143
                $this->_doubtful_requests["$key"] = $val;
144
            }
145
        }
146
    }
147
148
    /**
149
     * @return Protector
150
     */
151
    public static function getInstance()
152
    {
153
        static $instance;
154
        if (!isset($instance)) {
155
            $instance = new Protector();
156
        }
157
158
        return $instance;
159
    }
160
161
    /**
162
     * @return bool
163
     */
164
    public function updateConfFromDb()
165
    {
166
        $constpref = '_MI_' . strtoupper($this->mydirname);
167
168
        if (empty($this->_conn)) {
169
            return false;
170
        }
171
172
        $result = @mysqli_query($this->_conn, 'SELECT conf_name,conf_value FROM ' . XOOPS_DB_PREFIX . "_config WHERE conf_title like '" . $constpref . "%'");
173
        if (!$result || mysqli_num_rows($result) < 5) {
0 ignored issues
show
Bug introduced by geekwright
It seems like $result can also be of type true; however, parameter $result of mysqli_num_rows() does only seem to accept mysqli_result, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

173
        if (!$result || mysqli_num_rows(/** @scrutinizer ignore-type */ $result) < 5) {
Loading history...
174
            return false;
175
        }
176
        $db_conf = array();
177
        while (list($key, $val) = mysqli_fetch_row($result)) {
0 ignored issues
show
Bug introduced by geekwright
It seems like $result can also be of type true; however, parameter $result of mysqli_fetch_row() does only seem to accept mysqli_result, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

177
        while (list($key, $val) = mysqli_fetch_row(/** @scrutinizer ignore-type */ $result)) {
Loading history...
178
            $db_conf[$key] = $val;
179
        }
180
        $db_conf_serialized = serialize($db_conf);
181
182
        // update config cache
183
        if ($db_conf_serialized != $this->_conf_serialized) {
184
            $fp = fopen($this->get_filepath4confighcache(), 'w');
185
            fwrite($fp, $db_conf_serialized);
186
            fclose($fp);
187
            $this->_conf = $db_conf;
188
        }
189
190
        return true;
191
    }
192
193
    /**
194
     * @param $conn
195
     */
196
    public function setConn($conn)
197
    {
198
        $this->_conn = $conn;
199
    }
200
201
    /**
202
     * @return array
203
     */
204
    public function getConf()
205
    {
206
        return $this->_conf;
207
    }
208
209
    /**
210
     * @param bool $redirect_to_top
211
     */
212
    public function purge($redirect_to_top = false)
213
    {
214
        $this->purgeNoExit();
215
216
        if ($redirect_to_top) {
217
            header('Location: ' . XOOPS_URL . '/');
218
            exit;
219
        } else {
220
            $ret = $this->call_filter('prepurge_exit');
221
            if ($ret == false) {
222
                die('Protector detects attacking actions');
0 ignored issues
show
Best Practice introduced by geekwright
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
223
            }
224
        }
225
    }
226
227
    public function purgeSession()
228
    {
229
        // clear all session values
230
        if (isset($_SESSION)) {
231
            foreach ($_SESSION as $key => $val) {
232
                $_SESSION[$key] = '';
233
                if (isset($GLOBALS[$key])) {
234
                    $GLOBALS[$key] = '';
235
                }
236
            }
237
        }
238
    }
239
240
    public function purgeCookies()
241
    {
242
        if (!headers_sent()) {
243
            $domain =  defined(XOOPS_COOKIE_DOMAIN) ? XOOPS_COOKIE_DOMAIN : '';
244
            $past = time() - 3600;
245
            foreach ($_COOKIE as $key => $value) {
246
                setcookie($key, '', $past, '', $domain);
247
                setcookie($key, '', $past, '/', $domain);
248
            }
249
        }
250
    }
251
252
    public function purgeNoExit()
253
    {
254
        $this->purgeSession();
255
        $this->purgeCookies();
256
    }
257
258
    public function deactivateCurrentUser()
259
    {
260
        /* @var XoopsUser $xoopsUser */
261
        global $xoopsUser;
262
263
        if (is_object($xoopsUser)) {
264
            /** @var XoopsMemberHandler */
265
            $userHandler = xoops_getHandler('user');
266
            $xoopsUser->setVar('level', 0);
267
            $actkey = substr(md5(uniqid(mt_rand(), 1)), 0, 8);
268
            $xoopsUser->setVar('actkey', $actkey);
269
            $userHandler->insert($xoopsUser);
270
        }
271
        $this->purgeNoExit();
272
    }
273
274
    /**
275
     * @param string $type
276
     * @param int    $uid
277
     * @param bool   $unique_check
278
     * @param int    $level
279
     *
280
     * @return bool
281
     */
282
    public function output_log($type = 'UNKNOWN', $uid = 0, $unique_check = false, $level = 1)
283
    {
284
        if ($this->_logged) {
285
            return true;
286
        }
287
288
        if (!($this->_conf['log_level'] & $level)) {
289
            return true;
290
        }
291
292
        if (empty($this->_conn)) {
293
            $this->_conn = new mysqli(XOOPS_DB_HOST, XOOPS_DB_USER, XOOPS_DB_PASS);
294
            if (0 !== $this->_conn->connect_errno) {
295
                die('db connection failed.');
0 ignored issues
show
Best Practice introduced by mambax7
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
296
            }
297
            if (!mysqli_select_db($this->_conn, XOOPS_DB_NAME)) {
298
                die('db selection failed.');
0 ignored issues
show
Best Practice introduced by mambax7
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
299
            }
300
        }
301
302
        $ip    = \Xmf\IPAddress::fromRequest()->asReadable();
303
        $agent = @$_SERVER['HTTP_USER_AGENT'];
304
305
        if ($unique_check) {
306
            $result = mysqli_query($this->_conn, 'SELECT ip,type FROM ' . XOOPS_DB_PREFIX . '_' . $this->mydirname . '_log ORDER BY timestamp DESC LIMIT 1');
307
            list($last_ip, $last_type) = mysqli_fetch_row($result);
0 ignored issues
show
Bug introduced by geekwright
It seems like $result can also be of type true; however, parameter $result of mysqli_fetch_row() does only seem to accept mysqli_result, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

307
            list($last_ip, $last_type) = mysqli_fetch_row(/** @scrutinizer ignore-type */ $result);
Loading history...
308
            if ($last_ip == $ip && $last_type == $type) {
309
                $this->_logged = true;
310
311
                return true;
312
            }
313
        }
314
315
        mysqli_query(
316
            $this->_conn,
317
            'INSERT INTO ' . XOOPS_DB_PREFIX . '_' . $this->mydirname . "_log SET ip='"
318
            . mysqli_real_escape_string($this->_conn, $ip) . "',agent='"
319
            . mysqli_real_escape_string($this->_conn, $agent) . "',type='"
320
            . mysqli_real_escape_string($this->_conn, $type) . "',description='"
321
            . mysqli_real_escape_string($this->_conn, $this->message) . "',uid='"
322
            . (int)$uid . "',timestamp=NOW()"
323
        );
324
        $this->_logged = true;
325
326
        return true;
327
    }
328
329
    /**
330
     * @param $expire
331
     *
332
     * @return bool
333
     */
334
    public function write_file_bwlimit($expire)
335
    {
336
        $expire = min((int)$expire, time() + 300);
337
338
        $fp = @fopen($this->get_filepath4bwlimit(), 'w');
339
        if ($fp) {
0 ignored issues
show
introduced by mambax7
$fp is of type false|resource, thus it always evaluated to false.
Loading history...
340
            @flock($fp, LOCK_EX);
0 ignored issues
show
Security Best Practice introduced by mambax7
It seems like you do not handle an error condition for flock(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

340
            /** @scrutinizer ignore-unhandled */ @flock($fp, LOCK_EX);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
341
            fwrite($fp, $expire . "\n");
342
            @flock($fp, LOCK_UN);
343
            fclose($fp);
344
345
            return true;
346
        } else {
347
            return false;
348
        }
349
    }
350
351
    /**
352
     * @return mixed
353
     */
354
    public function get_bwlimit()
355
    {
356
        list($expire) = @file(Protector::get_filepath4bwlimit());
357
        $expire = min((int)$expire, time() + 300);
358
359
        return $expire;
360
    }
361
362
    /**
363
     * @return string
364
     */
365
    public static function get_filepath4bwlimit()
366
    {
367
        return XOOPS_VAR_PATH . '/protector/bwlimit' . substr(md5(XOOPS_ROOT_PATH . XOOPS_DB_USER . XOOPS_DB_PREFIX), 0, 6);
368
    }
369
370
    /**
371
     * @param $bad_ips
372
     *
373
     * @return bool
374
     */
375
    public function write_file_badips($bad_ips)
376
    {
377
        asort($bad_ips);
378
379
        $fp = @fopen($this->get_filepath4badips(), 'w');
380
        if ($fp) {
0 ignored issues
show
introduced by mambax7
$fp is of type false|resource, thus it always evaluated to false.
Loading history...
381
            @flock($fp, LOCK_EX);
0 ignored issues
show
Security Best Practice introduced by mambax7
It seems like you do not handle an error condition for flock(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

381
            /** @scrutinizer ignore-unhandled */ @flock($fp, LOCK_EX);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
382
            fwrite($fp, serialize($bad_ips) . "\n");
383
            @flock($fp, LOCK_UN);
384
            fclose($fp);
385
386
            return true;
387
        } else {
388
            return false;
389
        }
390
    }
391
392
    /**
393
     * @param int  $jailed_time
394
     * @param null|string|false $ip
395
     *
396
     * @return bool
397
     */
398
    public function register_bad_ips($jailed_time = 0, $ip = null)
399
    {
400
        if (empty($ip)) {
401
            $ip = \Xmf\IPAddress::fromRequest()->asReadable();
402
        }
403
        if (empty($ip)) {
404
            return false;
405
        }
406
407
        $bad_ips      = $this->get_bad_ips(true);
408
        $bad_ips[$ip] = $jailed_time ?: 0x7fffffff;
409
410
        return $this->write_file_badips($bad_ips);
411
    }
412
413
    /**
414
     * @param bool $with_jailed_time
415
     *
416
     * @return array|mixed
417
     */
418
    public function get_bad_ips($with_jailed_time = false)
419
    {
420
        list($bad_ips_serialized) = @file(Protector::get_filepath4badips());
421
        $bad_ips = empty($bad_ips_serialized) ? array() : @unserialize($bad_ips_serialized);
422
        if (!is_array($bad_ips) || isset($bad_ips[0])) {
423
            $bad_ips = array();
424
        }
425
426
        // expire jailed_time
427
        $pos = 0;
428
        foreach ($bad_ips as $bad_ip => $jailed_time) {
429
            if ($jailed_time >= time()) {
430
                break;
431
            }
432
            ++$pos;
433
        }
434
        $bad_ips = array_slice($bad_ips, $pos);
435
436
        if ($with_jailed_time) {
437
            return $bad_ips;
438
        } else {
439
            return array_keys($bad_ips);
440
        }
441
    }
442
443
    /**
444
     * @return string
445
     */
446
    public static function get_filepath4badips()
447
    {
448
        return XOOPS_VAR_PATH . '/protector/badips' . substr(md5(XOOPS_ROOT_PATH . XOOPS_DB_USER . XOOPS_DB_PREFIX), 0, 6);
449
    }
450
451
    /**
452
     * @param bool $with_info
453
     *
454
     * @return array|mixed
455
     */
456
    public function get_group1_ips($with_info = false)
457
    {
458
        list($group1_ips_serialized) = @file(Protector::get_filepath4group1ips());
459
        $group1_ips = empty($group1_ips_serialized) ? array() : @unserialize($group1_ips_serialized);
460
        if (!is_array($group1_ips)) {
461
            $group1_ips = array();
462
        }
463
464
        if ($with_info) {
465
            $group1_ips = array_flip($group1_ips);
466
        }
467
468
        return $group1_ips;
469
    }
470
471
    /**
472
     * @return string
473
     */
474
    public static function get_filepath4group1ips()
475
    {
476
        return XOOPS_VAR_PATH . '/protector/group1ips' . substr(md5(XOOPS_ROOT_PATH . XOOPS_DB_USER . XOOPS_DB_PREFIX), 0, 6);
477
    }
478
479
    /**
480
     * @return string
481
     */
482
    public function get_filepath4confighcache()
483
    {
484
        return XOOPS_VAR_PATH . '/protector/configcache' . substr(md5(XOOPS_ROOT_PATH . XOOPS_DB_USER . XOOPS_DB_PREFIX), 0, 6);
485
    }
486
487
    /**
488
     * @param $ips
489
     *
490
     * @return bool
491
     */
492
    public function ip_match($ips)
493
    {
494
        $requestIp = \Xmf\IPAddress::fromRequest()->asReadable();
495
        if (false === $requestIp) { // nothing to match
0 ignored issues
show
introduced by geekwright
The condition false === $requestIp is always false.
Loading history...
496
            $this->ip_matched_info = null;
497
            return false;
498
        }
499
        foreach ($ips as $ip => $info) {
500
            if ($ip) {
501
                switch (strtolower(substr($ip, -1))) {
502
                    case '.' :
503
                    case ':' :
504
                        // foward match
505
                        if (substr($requestIp, 0, strlen($ip)) == $ip) {
506
                            $this->ip_matched_info = $info;
507
                            return true;
508
                        }
509
                        break;
510
                    case '0' :
511
                    case '1' :
512
                    case '2' :
513
                    case '3' :
514
                    case '4' :
515
                    case '5' :
516
                    case '6' :
517
                    case '7' :
518
                    case '8' :
519
                    case '9' :
520
                    case 'a' :
521
                    case 'b' :
522
                    case 'c' :
523
                    case 'd' :
524
                    case 'e' :
525
                    case 'f' :
526
                        // full match
527
                        if ($requestIp == $ip) {
528
                            $this->ip_matched_info = $info;
529
                            return true;
530
                        }
531
                        break;
532
                    default :
533
                        // perl regex
534
                        if (@preg_match($ip, $requestIp)) {
535
                            $this->ip_matched_info = $info;
536
                            return true;
537
                        }
538
                        break;
539
                }
540
            }
541
        }
542
        $this->ip_matched_info = null;
543
        return false;
544
    }
545
546
    /**
547
     * @param null|string|false $ip
548
     *
549
     * @return bool
550
     */
551
    public function deny_by_htaccess($ip = null)
552
    {
553
        if (empty($ip)) {
554
            $ip = \Xmf\IPAddress::fromRequest()->asReadable();
555
        }
556
        if (empty($ip)) {
557
            return false;
558
        }
559
        if (!function_exists('file_get_contents')) {
560
            return false;
561
        }
562
563
        $target_htaccess = XOOPS_ROOT_PATH . '/.htaccess';
564
        $backup_htaccess = XOOPS_ROOT_PATH . '/uploads/.htaccess.bak';
565
566
        $ht_body = file_get_contents($target_htaccess);
567
568
        // make backup as uploads/.htaccess.bak automatically
569
        if ($ht_body && !file_exists($backup_htaccess)) {
570
            $fw = fopen($backup_htaccess, 'w');
571
            fwrite($fw, $ht_body);
572
            fclose($fw);
573
        }
574
575
        // if .htaccess is broken, restore from backup
576
        if (!$ht_body && file_exists($backup_htaccess)) {
577
            $ht_body = file_get_contents($backup_htaccess);
578
        }
579
580
        // new .htaccess
581
        if ($ht_body === false) {
582
            $ht_body = '';
583
        }
584
585
        if (preg_match("/^(.*)#PROTECTOR#\s+(DENY FROM .*)\n#PROTECTOR#\n(.*)$/si", $ht_body, $regs)) {
586
            if (substr($regs[2], -strlen($ip)) == $ip) {
587
                return true;
588
            }
589
            $new_ht_body = $regs[1] . "#PROTECTOR#\n" . $regs[2] . " $ip\n#PROTECTOR#\n" . $regs[3];
590
        } else {
591
            $new_ht_body = "#PROTECTOR#\nDENY FROM $ip\n#PROTECTOR#\n" . $ht_body;
592
        }
593
594
        // error_log( "$new_ht_body\n" , 3 , "/tmp/error_log" ) ;
595
596
        $fw = fopen($target_htaccess, 'w');
597
        @flock($fw, LOCK_EX);
0 ignored issues
show
Security Best Practice introduced by mambax7
It seems like you do not handle an error condition for flock(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

597
        /** @scrutinizer ignore-unhandled */ @flock($fw, LOCK_EX);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
598
        fwrite($fw, $new_ht_body);
599
        @flock($fw, LOCK_UN);
600
        fclose($fw);
601
602
        return true;
603
    }
604
605
    /**
606
     * @return array
607
     */
608
    public function getDblayertrapDoubtfuls()
609
    {
610
        return $this->_dblayertrap_doubtfuls;
611
    }
612
613
    /**
614
     * @param $val
615
     * @return null
616
     */
617
    protected function _dblayertrap_check_recursive($val)
618
    {
619
        if (is_array($val)) {
620
            foreach ($val as $subval) {
621
                $this->_dblayertrap_check_recursive($subval);
622
            }
623
        } else {
624
            if (strlen($val) < 6) {
625
                return null;
626
            }
627
            $val = @get_magic_quotes_gpc() ? stripslashes($val) : $val;
628
            foreach ($this->_dblayertrap_doubtful_needles as $needle) {
629
                if (false !== stripos($val, $needle)) {
630
                    $this->_dblayertrap_doubtfuls[] = $val;
631
                }
632
            }
633
        }
634
    }
635
636
    /**
637
     * @param  bool $force_override
638
     * @return null
639
     */
640
    public function dblayertrap_init($force_override = false)
641
    {
642
        if (!empty($GLOBALS['xoopsOption']['nocommon']) || defined('_LEGACY_PREVENT_EXEC_COMMON_') || defined('_LEGACY_PREVENT_LOAD_CORE_')) {
643
            return null;
644
        } // skip
645
646
        $this->_dblayertrap_doubtfuls = array();
647
        $this->_dblayertrap_check_recursive($_GET);
648
        $this->_dblayertrap_check_recursive($_POST);
649
        $this->_dblayertrap_check_recursive($_COOKIE);
650
        if (empty($this->_conf['dblayertrap_wo_server'])) {
651
            $this->_dblayertrap_check_recursive($_SERVER);
652
        }
653
654
        if (!empty($this->_dblayertrap_doubtfuls) || $force_override) {
655
            @define('XOOPS_DB_ALTERNATIVE', 'ProtectorMysqlDatabase');
0 ignored issues
show
Security Best Practice introduced by mambax7
It seems like you do not handle an error condition for define(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

655
            /** @scrutinizer ignore-unhandled */ @define('XOOPS_DB_ALTERNATIVE', 'ProtectorMysqlDatabase');

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
656
            require_once dirname(__DIR__) . '/class/ProtectorMysqlDatabase.class.php';
657
        }
658
    }
659
660
    /**
661
     * @param $val
662
     */
663
    protected function _bigumbrella_check_recursive($val)
664
    {
665
        if (is_array($val)) {
666
            foreach ($val as $subval) {
667
                $this->_bigumbrella_check_recursive($subval);
668
            }
669
        } else {
670
            if (preg_match('/[<\'"].{15}/s', $val, $regs)) {
671
                $this->_bigumbrella_doubtfuls[] = $regs[0];
672
            }
673
        }
674
    }
675
676
    public function bigumbrella_init()
677
    {
678
        $this->_bigumbrella_doubtfuls = array();
679
        $this->_bigumbrella_check_recursive($_GET);
680
        $this->_bigumbrella_check_recursive(@$_SERVER['PHP_SELF']);
681
682
        if (!empty($this->_bigumbrella_doubtfuls)) {
683
            ob_start(array($this, 'bigumbrella_outputcheck'));
684
        }
685
    }
686
687
    /**
688
     * @param $s
689
     *
690
     * @return string
691
     */
692
    public function bigumbrella_outputcheck($s)
693
    {
694
        if (defined('BIGUMBRELLA_DISABLED')) {
695
            return $s;
696
        }
697
698
        if (function_exists('headers_list')) {
699
            foreach (headers_list() as $header) {
700
                if (false !== stripos($header, 'Content-Type:') && false === stripos($header, 'text/html')) {
701
                    return $s;
702
                }
703
            }
704
        }
705
706
        if (!is_array($this->_bigumbrella_doubtfuls)) {
0 ignored issues
show
introduced by mambax7
The condition is_array($this->_bigumbrella_doubtfuls) is always true.
Loading history...
707
            return 'bigumbrella injection found.';
708
        }
709
710
        foreach ($this->_bigumbrella_doubtfuls as $doubtful) {
711
            if (false !== strpos($s, $doubtful)) {
712
                return 'XSS found by Protector.';
713
            }
714
        }
715
716
        return $s;
717
    }
718
719
    /**
720
     * @return bool
721
     */
722
    public function intval_allrequestsendid()
723
    {
724
        global $HTTP_GET_VARS, $HTTP_POST_VARS, $HTTP_COOKIE_VARS;
725
726
        if ($this->_done_intval) {
727
            return true;
728
        } else {
729
            $this->_done_intval = true;
730
        }
731
732
        foreach ($_GET as $key => $val) {
733
            if (substr($key, -2) === 'id' && !is_array($_GET[$key])) {
734
                $newval     = preg_replace('/[^0-9a-zA-Z_-]/', '', $val);
735
                $_GET[$key] = $HTTP_GET_VARS[$key] = $newval;
736
                if ($_REQUEST[$key] == $_GET[$key]) {
737
                    $_REQUEST[$key] = $newval;
738
                }
739
            }
740
        }
741
        foreach ($_POST as $key => $val) {
742
            if (substr($key, -2) === 'id' && !is_array($_POST[$key])) {
743
                $newval      = preg_replace('/[^0-9a-zA-Z_-]/', '', $val);
744
                $_POST[$key] = $HTTP_POST_VARS[$key] = $newval;
745
                if ($_REQUEST[$key] == $_POST[$key]) {
746
                    $_REQUEST[$key] = $newval;
747
                }
748
            }
749
        }
750
        foreach ($_COOKIE as $key => $val) {
751
            if (substr($key, -2) === 'id' && !is_array($_COOKIE[$key])) {
752
                $newval        = preg_replace('/[^0-9a-zA-Z_-]/', '', $val);
753
                $_COOKIE[$key] = $HTTP_COOKIE_VARS[$key] = $newval;
754
                if ($_REQUEST[$key] == $_COOKIE[$key]) {
755
                    $_REQUEST[$key] = $newval;
756
                }
757
            }
758
        }
759
760
        return true;
761
    }
762
763
    /**
764
     * @return bool
765
     */
766
    public function eliminate_dotdot()
767
    {
768
        global $HTTP_GET_VARS, $HTTP_POST_VARS, $HTTP_COOKIE_VARS;
769
770
        if ($this->_done_dotdot) {
771
            return true;
772
        } else {
773
            $this->_done_dotdot = true;
774
        }
775
776
        foreach ($_GET as $key => $val) {
777
            if (is_array($_GET[$key])) {
778
                continue;
779
            }
780
            if (substr(trim($val), 0, 3) === '../' || false !== strpos($val, '/../')) {
781
                $this->last_error_type = 'DirTraversal';
782
                $this->message .= "Directory Traversal '$val' found.\n";
783
                $this->output_log($this->last_error_type, 0, false, 64);
784
                $sanitized_val = str_replace(chr(0), '', $val);
785
                if (substr($sanitized_val, -2) !== ' .') {
786
                    $sanitized_val .= ' .';
787
                }
788
                $_GET[$key] = $HTTP_GET_VARS[$key] = $sanitized_val;
789
                if ($_REQUEST[$key] == $_GET[$key]) {
790
                    $_REQUEST[$key] = $sanitized_val;
791
                }
792
            }
793
        }
794
795
        /*    foreach ($_POST as $key => $val) {
796
                if( is_array( $_POST[ $key ] ) ) continue ;
797
                if ( substr( trim( $val ) , 0 , 3 ) == '../' || false !== strpos( $val , '../../' ) ) {
798
                    $this->last_error_type = 'ParentDir' ;
799
                    $this->message .= "Doubtful file specification '$val' found.\n" ;
800
                    $this->output_log( $this->last_error_type , 0 , false , 128 ) ;
801
                    $sanitized_val = str_replace( chr(0) , '' , $val ) ;
802
                    if( substr( $sanitized_val , -2 ) != ' .' ) $sanitized_val .= ' .' ;
803
                    $_POST[ $key ] = $HTTP_POST_VARS[ $key ] = $sanitized_val ;
804
                    if ($_REQUEST[ $key ] == $_POST[ $key ]) {
805
                        $_REQUEST[ $key ] = $sanitized_val ;
806
                    }
807
                }
808
            }
809
            foreach ($_COOKIE as $key => $val) {
810
                if( is_array( $_COOKIE[ $key ] ) ) continue ;
811
                if ( substr( trim( $val ) , 0 , 3 ) == '../' || false !== strpos( $val , '../../' ) ) {
812
                    $this->last_error_type = 'ParentDir' ;
813
                    $this->message .= "Doubtful file specification '$val' found.\n" ;
814
                    $this->output_log( $this->last_error_type , 0 , false , 128 ) ;
815
                    $sanitized_val = str_replace( chr(0) , '' , $val ) ;
816
                    if( substr( $sanitized_val , -2 ) != ' .' ) $sanitized_val .= ' .' ;
817
                    $_COOKIE[ $key ] = $HTTP_COOKIE_VARS[ $key ] = $sanitized_val ;
818
                    if ($_REQUEST[ $key ] == $_COOKIE[ $key ]) {
819
                        $_REQUEST[ $key ] = $sanitized_val ;
820
                    }
821
                }
822
            }*/
823
824
        return true;
825
    }
826
827
    /**
828
     * @param $current
829
     * @param $indexes
830
     *
831
     * @return bool
832
     */
833
    public function &get_ref_from_base64index(&$current, $indexes)
834
    {
835
        foreach ($indexes as $index) {
836
            $index = base64_decode($index);
837
            if (!is_array($current)) {
838
                return false;
839
            }
840
            $current =& $current[$index];
841
        }
842
843
        return $current;
844
    }
845
846
    /**
847
     * @param $key
848
     * @param $val
849
     */
850
    public function replace_doubtful($key, $val)
851
    {
852
        global $HTTP_GET_VARS, $HTTP_POST_VARS, $HTTP_COOKIE_VARS;
853
854
        $index_expression = '';
0 ignored issues
show
Unused Code introduced by mambax7
The assignment to $index_expression is dead and can be removed.
Loading history...
855
        $indexes          = explode('_', $key);
856
        $base_array       = array_shift($indexes);
857
858
        switch ($base_array) {
859
            case 'G' :
860
                $main_ref   =& $this->get_ref_from_base64index($_GET, $indexes);
861
                $legacy_ref =& $this->get_ref_from_base64index($HTTP_GET_VARS, $indexes);
862
                break;
863
            case 'P' :
864
                $main_ref   =& $this->get_ref_from_base64index($_POST, $indexes);
865
                $legacy_ref =& $this->get_ref_from_base64index($HTTP_POST_VARS, $indexes);
866
                break;
867
            case 'C' :
868
                $main_ref   =& $this->get_ref_from_base64index($_COOKIE, $indexes);
869
                $legacy_ref =& $this->get_ref_from_base64index($HTTP_COOKIE_VARS, $indexes);
870
                break;
871
            default :
872
                exit;
0 ignored issues
show
Best Practice introduced by mambax7
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
873
        }
874
        if (!isset($main_ref)) {
875
            exit;
0 ignored issues
show
Best Practice introduced by mambax7
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
876
        }
877
        $request_ref =& $this->get_ref_from_base64index($_REQUEST, $indexes);
878
        if ($request_ref !== false && $main_ref == $request_ref) {
879
            $request_ref = $val;
880
        }
881
        $main_ref   = $val;
882
        $legacy_ref = $val;
883
    }
884
885
    /**
886
     * @return bool
887
     */
888
    public function check_uploaded_files()
889
    {
890
        if ($this->_done_badext) {
891
            return $this->_safe_badext;
892
        } else {
893
            $this->_done_badext = true;
894
        }
895
896
        // extensions never uploaded
897
        $bad_extensions = array('php', 'phtml', 'phtm', 'php3', 'php4', 'cgi', 'pl', 'asp');
898
        // extensions needed image check (anti-IE Content-Type XSS)
899
        $image_extensions = array(
900
            1  => 'gif',
901
            2  => 'jpg',
902
            3  => 'png',
903
            4  => 'swf',
904
            5  => 'psd',
905
            6  => 'bmp',
906
            7  => 'tif',
907
            8  => 'tif',
908
            9  => 'jpc',
909
            10 => 'jp2',
910
            11 => 'jpx',
911
            12 => 'jb2',
912
            13 => 'swc',
913
            14 => 'iff',
914
            15 => 'wbmp',
915
            16 => 'xbm');
916
917
        foreach ($_FILES as $_file) {
918
            if (!empty($_file['error'])) {
919
                continue;
920
            }
921
            if (!empty($_file['name']) && is_string($_file['name'])) {
922
                $ext = strtolower(substr(strrchr($_file['name'], '.'), 1));
923
                if ($ext === 'jpeg') {
924
                    $ext = 'jpg';
925
                } elseif ($ext === 'tiff') {
926
                    $ext = 'tif';
927
                }
928
929
                // anti multiple dot file (Apache mod_mime.c)
930
                if (count(explode('.', str_replace('.tar.gz', '.tgz', $_file['name']))) > 2) {
931
                    $this->message .= "Attempt to multiple dot file {$_file['name']}.\n";
932
                    $this->_safe_badext    = false;
933
                    $this->last_error_type = 'UPLOAD';
934
                }
935
936
                // anti dangerous extensions
937
                if (in_array($ext, $bad_extensions)) {
938
                    $this->message .= "Attempt to upload {$_file['name']}.\n";
939
                    $this->_safe_badext    = false;
940
                    $this->last_error_type = 'UPLOAD';
941
                }
942
943
                // anti camouflaged image file
944
                if (in_array($ext, $image_extensions)) {
945
                    $image_attributes = @getimagesize($_file['tmp_name']);
946
                    if ($image_attributes === false && is_uploaded_file($_file['tmp_name'])) {
947
                        // open_basedir restriction
948
                        $temp_file = XOOPS_ROOT_PATH . '/uploads/protector_upload_temporary' . md5(time());
949
                        move_uploaded_file($_file['tmp_name'], $temp_file);
950
                        $image_attributes = @getimagesize($temp_file);
951
                        @unlink($temp_file);
0 ignored issues
show
Security Best Practice introduced by mambax7
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

951
                        /** @scrutinizer ignore-unhandled */ @unlink($temp_file);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
952
                    }
953
954
                    if ($image_attributes === false || $image_extensions[(int)$image_attributes[2]] != $ext) {
955
                        $this->message .= "Attempt to upload camouflaged image file {$_file['name']}.\n";
956
                        $this->_safe_badext    = false;
957
                        $this->last_error_type = 'UPLOAD';
958
                    }
959
                }
960
            }
961
        }
962
963
        return $this->_safe_badext;
964
    }
965
966
    /**
967
     * @return bool
968
     */
969
    public function check_contami_systemglobals()
970
    {
971
        /*    if( $this->_done_contami ) return $this->_safe_contami ;
972
    else $this->_done_contami = true ; */
973
974
        /*    foreach ($this->_bad_globals as $bad_global) {
975
                if ( isset( $_REQUEST[ $bad_global ] ) ) {
976
                    $this->message .= "Attempt to inject '$bad_global' was found.\n" ;
977
                    $this->_safe_contami = false ;
978
                    $this->last_error_type = 'CONTAMI' ;
979
                }
980
            }*/
981
982
        return $this->_safe_contami;
983
    }
984
985
    /**
986
     * @param bool $sanitize
987
     *
988
     * @return bool
989
     */
990
    public function check_sql_isolatedcommentin($sanitize = true)
991
    {
992
        if ($this->_done_isocom) {
993
            return $this->_safe_isocom;
994
        } else {
995
            $this->_done_isocom = true;
996
        }
997
998
        foreach ($this->_doubtful_requests as $key => $val) {
999
            $str = $val;
1000
            while ($str = strstr($str, '/*')) { /* */
1001
                $str = strstr(substr($str, 2), '*/');
1002
                if ($str === false) {
1003
                    $this->message .= "Isolated comment-in found. ($val)\n";
1004
                    if ($sanitize) {
1005
                        $this->replace_doubtful($key, $val . '*/');
1006
                    }
1007
                    $this->_safe_isocom    = false;
1008
                    $this->last_error_type = 'ISOCOM';
1009
                }
1010
            }
1011
        }
1012
1013
        return $this->_safe_isocom;
1014
    }
1015
1016
    /**
1017
     * @param bool $sanitize
1018
     *
1019
     * @return bool
1020
     */
1021
    public function check_sql_union($sanitize = true)
1022
    {
1023
        if ($this->_done_union) {
1024
            return $this->_safe_union;
1025
        } else {
1026
            $this->_done_union = true;
1027