Passed
Branch feature/php-docs2 (9f8cc0)
by Michael
04:49
created

Guardian::check_uploaded_files()   C

Complexity

Conditions 15
Paths 64

Size

Total Lines 86
Code Lines 59

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
eloc 59
nc 64
nop 0
dl 0
loc 86
rs 5.9166
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php namespace XoopsModules\Protector;
2
3
/**
4
 * Class Guardian
5
 */
6
class Guardian
7
{
8
    /**
9
     * @var string
10
     */
11
    public $mydirname;
12
    /**
13
     * @var \mysqli
14
     */
15
    public $_conn;
16
    /**
17
     * @var array
18
     */
19
    public $_conf = array(); //mb TODO
20
    /**
21
     * @var string
22
     */
23
    public $_conf_serialized = ''; //mb TODO could be also false
24
    /**
25
     * @var array
26
     */
27
    public $_bad_globals = array();
28
    /**
29
     * @var string
30
     */
31
    public $message                = '';
32
    /**
33
     * @var bool
34
     */
35
    public $warning                = false;
36
    /**
37
     * @var bool
38
     */
39
    public $error                  = false;
40
    /**
41
     * @var array
42
     */
43
    public $_doubtful_requests     = array();
44
    /**
45
     * @var array
46
     */
47
    public $_bigumbrella_doubtfuls = array();
48
    /**
49
     * @var array
50
     */
51
    public $_dblayertrap_doubtfuls        = array();
52
    /**
53
     * @var array
54
     */
55
    public $_dblayertrap_doubtful_needles = array(
56
        'information_schema',
57
        'select',
58
        "'",
59
        '"',
60
    );
61
    /**
62
     * @var bool
63
     */
64
    public $_logged = false;
65
    /**
66
     * @var bool
67
     */
68
    public $_done_badext   = false;
69
    /**
70
     * @var bool
71
     */
72
    public $_done_intval   = false;
73
    /**
74
     * @var bool
75
     */
76
    public $_done_dotdot   = false;
77
    /**
78
     * @var bool
79
     */
80
    public $_done_nullbyte = false;
81
    /**
82
     * @var bool
83
     */
84
    public $_done_contami  = false;
85
    /**
86
     * @var bool
87
     */
88
    public $_done_isocom   = false;
89
    /**
90
     * @var bool
91
     */
92
    public $_done_union    = false;
93
    /**
94
     * @var bool
95
     */
96
    public $_done_dos      = false;
97
    /**
98
     * @var bool
99
     */
100
    public $_safe_badext  = true;
101
    /**
102
     * @var bool
103
     */
104
    public $_safe_contami = true;
105
    /**
106
     * @var bool
107
     */
108
    public $_safe_isocom  = true;
109
    /**
110
     * @var bool
111
     */
112
    public $_safe_union   = true;
113
    /**
114
     * @var int
115
     */
116
    public $_spamcount_uri = 0;
117
    /**
118
     * @var bool
119
     */
120
    public $_should_be_banned_time0 = false;
121
    /**
122
     * @var bool
123
     */
124
    public $_should_be_banned       = false;
125
    /**
126
     * @var string
127
     */
128
    public $_dos_stage; //mb TODO is not used anywhere
129
    /**
130
     * @var string
131
     */
132
    public $ip_matched_info; //mb TODO could be also null
133
    /**
134
     * @var string
135
     */
136
    public $last_error_type = 'UNKNOWN';
137
138
    /**
139
     * Constructor
140
     */
141
    protected function __construct()
142
    {
143
        $this->mydirname = 'protector';
144
145
        // Preferences from configs/cache
146
        $this->_conf_serialized = @file_get_contents($this->get_filepath4confighcache());
0 ignored issues
show
Documentation Bug introduced by
It seems like @file_get_contents($this...ilepath4confighcache()) can also be of type false. However, the property $_conf_serialized is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
147
        $this->_conf            = @unserialize($this->_conf_serialized);
0 ignored issues
show
Bug introduced by
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

147
        $this->_conf            = @unserialize(/** @scrutinizer ignore-type */ $this->_conf_serialized);
Loading history...
148
        if (empty($this->_conf)) {
149
            $this->_conf = array();
150
        }
151
152
        if (!empty($this->_conf['global_disabled'])) {
153
            return;
154
        }
155
156
        // die if PHP_SELF XSS found (disabled in 2.53)
157
        //    if ( preg_match( '/[<>\'";\n ]/' , @$_SERVER['PHP_SELF'] ) ) {
158
        //        $this->message .= "Invalid PHP_SELF '{$_SERVER['PHP_SELF']}' found.\n" ;
159
        //        $this->output_log( 'PHP_SELF XSS' ) ;
160
        //        die( 'invalid PHP_SELF' ) ;
161
        //    }
162
163
        // sanitize against PHP_SELF/PATH_INFO XSS (disabled in 3.33)
164
        //    $_SERVER['PHP_SELF'] = strtr( @$_SERVER['PHP_SELF'] , array( '<' => '%3C' , '>' => '%3E' , "'" => '%27' , '"' => '%22' ) ) ;
165
        //    if( ! empty( $_SERVER['PATH_INFO'] ) ) $_SERVER['PATH_INFO'] = strtr( @$_SERVER['PATH_INFO'] , array( '<' => '%3C' , '>' => '%3E' , "'" => '%27' , '"' => '%22' ) ) ;
166
167
        $this->_bad_globals = array(
168
            'GLOBALS',
169
            '_SESSION',
170
            'HTTP_SESSION_VARS',
171
            '_GET',
172
            'HTTP_GET_VARS',
173
            '_POST',
174
            'HTTP_POST_VARS',
175
            '_COOKIE',
176
            'HTTP_COOKIE_VARS',
177
            '_SERVER',
178
            'HTTP_SERVER_VARS',
179
            '_REQUEST',
180
            '_ENV',
181
            '_FILES',
182
            'xoopsDB',
183
            'xoopsUser',
184
            'xoopsUserId',
185
            'xoopsUserGroups',
186
            'xoopsUserIsAdmin',
187
            'xoopsConfig',
188
            'xoopsOption',
189
            'xoopsModule',
190
            'xoopsModuleConfig',
191
        );
192
193
        $this->_initial_recursive($_GET, 'G');
194
        $this->_initial_recursive($_POST, 'P');
195
        $this->_initial_recursive($_COOKIE, 'C');
196
    }
197
198
    /**
199
     * @param array|string $val
200
     * @param string       $key
201
     *
202
     * @return void
203
     */
204
    protected function _initial_recursive($val, $key)
205
    {
206
        if (is_array($val)) {
207
            foreach ($val as $subkey => $subval) {
208
                // check bad globals
209
                if (in_array($subkey, $this->_bad_globals, true)) {
210
                    $this->message .= "Attempt to inject '$subkey' was found.\n";
211
                    $this->_safe_contami   = false;
212
                    $this->last_error_type = 'CONTAMI';
213
                }
214
                $this->_initial_recursive($subval, $key . '_' . base64_encode($subkey));
215
            }
216
        } else {
217
            // check nullbyte attack
218
            if (@$this->_conf['san_nullbyte'] && false !== strpos($val, chr(0))) {
219
                $val = str_replace(chr(0), ' ', $val);
220
                $this->replace_doubtful($key, $val);
221
                $this->message .= "Injecting Null-byte '$val' found.\n";
222
                $this->output_log('NullByte', 0, false, 32);
223
                // $this->purge() ;
224
            }
225
226
            // register as doubtful requests against SQL Injections
227
            if (preg_match('?[\s\'"`/]?', $val)) {
228
                $this->_doubtful_requests["$key"] = $val;
229
            }
230
        }
231
    }
232
233
    /**
234
     * @return Guardian
235
     */
236
    public static function getInstance()
237
    {
238
        static $instance;
239
        if (!isset($instance)) {
240
            $instance = new Guardian();
241
        }
242
243
        return $instance;
244
    }
245
246
    /**
247
     * @return bool
248
     */
249
    public function updateConfFromDb()
250
    {
251
        $constpref = '_MI_' . strtoupper($this->mydirname);
252
253
        if (empty($this->_conn)) {
254
            return false;
255
        }
256
257
        $result = @mysqli_query($this->_conn, 'SELECT conf_name,conf_value FROM ' . XOOPS_DB_PREFIX . "_config WHERE conf_title like '" . $constpref . "%'");
258
        if (!$result || mysqli_num_rows($result) < 5) {
0 ignored issues
show
Bug introduced by
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

258
        if (!$result || mysqli_num_rows(/** @scrutinizer ignore-type */ $result) < 5) {
Loading history...
259
            return false;
260
        }
261
        $db_conf = array();
262
        while (list($key, $val) = mysqli_fetch_row($result)) {
0 ignored issues
show
Bug introduced by
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

262
        while (list($key, $val) = mysqli_fetch_row(/** @scrutinizer ignore-type */ $result)) {
Loading history...
263
            $db_conf[$key] = $val;
264
        }
265
        $db_conf_serialized = serialize($db_conf);
266
267
        // update config cache
268
        if ($db_conf_serialized != $this->_conf_serialized) {
269
            $fp = fopen($this->get_filepath4confighcache(), 'w');
270
            fwrite($fp, $db_conf_serialized);
271
            fclose($fp);
272
            $this->_conf = $db_conf;
273
        }
274
275
        return true;
276
    }
277
278
    /**
279
     * @param $conn
280
     *
281
     * @return void
282
     */
283
    public function setConn($conn)
284
    {
285
        $this->_conn = $conn;
286
    }
287
288
    /**
289
     * @return array
290
     */
291
    public function getConf()
292
    {
293
        return $this->_conf;
294
    }
295
296
    /**
297
     * @param bool $redirect_to_top
298
     *
299
     * @return void
300
     */
301
    public function purge($redirect_to_top = false)
302
    {
303
        $this->purgeNoExit();
304
305
        if ($redirect_to_top) {
306
            header('Location: ' . XOOPS_URL . '/');
307
            exit;
308
        } else {
309
            $ret = $this->call_filter('prepurge_exit');
310
            if ($ret == false) {
311
                die('Protector detects attacking actions');
0 ignored issues
show
Best Practice introduced by
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...
312
            }
313
        }
314
    }
315
316
    /**
317
     * @return void
318
     */
319
    public function purgeSession()
320
    {
321
        // clear all session values
322
        if (isset($_SESSION)) {
323
            foreach ($_SESSION as $key => $val) {
324
                $_SESSION[$key] = '';
325
                if (isset($GLOBALS[$key])) {
326
                    $GLOBALS[$key] = '';
327
                }
328
            }
329
        }
330
    }
331
332
    /**
333
     * @return void
334
     */
335
    public function purgeCookies()
336
    {
337
        if (!headers_sent()) {
338
            $domain =  defined(XOOPS_COOKIE_DOMAIN) ? XOOPS_COOKIE_DOMAIN : '';
339
            $past = time() - 3600;
340
            foreach ($_COOKIE as $key => $value) {
341
                setcookie($key, '', $past, '', $domain);
342
                setcookie($key, '', $past, '/', $domain);
343
            }
344
        }
345
    }
346
347
    /**
348
     * @return void
349
     */
350
    public function purgeNoExit()
351
    {
352
        $this->purgeSession();
353
        $this->purgeCookies();
354
    }
355
356
    /**
357
     * @return void
358
     */
359
    public function deactivateCurrentUser()
360
    {
361
        /** @var XoopsUser $xoopsUser */
362
        global $xoopsUser;
363
364
        if (is_object($xoopsUser)) {
365
            /** @var XoopsMemberHandler */
366
            $userHandler = xoops_getHandler('user');
367
            $xoopsUser->setVar('level', 0);
368
            $actkey = substr(md5(uniqid(mt_rand(), 1)), 0, 8);
369
            $xoopsUser->setVar('actkey', $actkey);
370
            $userHandler->insert($xoopsUser);
371
        }
372
        $this->purgeNoExit();
373
    }
374
375
    /**
376
     * @param string $type
377
     * @param int    $uid
378
     * @param bool   $unique_check
379
     * @param int    $level
380
     *
381
     * @return bool
382
     */
383
    public function output_log($type = 'UNKNOWN', $uid = 0, $unique_check = false, $level = 1)
384
    {
385
        if ($this->_logged) {
386
            return true;
387
        }
388
389
        if (!($this->_conf['log_level'] & $level)) {
390
            return true;
391
        }
392
393
        if (empty($this->_conn)) {
394
            mysqli_report(MYSQLI_REPORT_OFF);
395
            $this->_conn = new mysqli(XOOPS_DB_HOST, XOOPS_DB_USER, XOOPS_DB_PASS);
0 ignored issues
show
Bug introduced by
The type XoopsModules\Protector\mysqli was not found. Did you mean mysqli? If so, make sure to prefix the type with \.
Loading history...
396
            if (0 !== $this->_conn->connect_errno) {
397
                die('db connection failed.');
0 ignored issues
show
Best Practice introduced by
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...
398
            }
399
            if (!mysqli_select_db($this->_conn, XOOPS_DB_NAME)) {
400
                die('db selection failed.');
0 ignored issues
show
Best Practice introduced by
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...
401
            }
402
        }
403
404
        $ip    = \Xmf\IPAddress::fromRequest()
405
                               ->asReadable();
406
        $agent = @$_SERVER['HTTP_USER_AGENT'];
407
408
        if ($unique_check) {
409
            $result = mysqli_query($this->_conn, 'SELECT ip,type FROM ' . XOOPS_DB_PREFIX . '_' . $this->mydirname . '_log ORDER BY timestamp DESC LIMIT 1');
410
            list($last_ip, $last_type) = mysqli_fetch_row($result);
0 ignored issues
show
Bug introduced by
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

410
            list($last_ip, $last_type) = mysqli_fetch_row(/** @scrutinizer ignore-type */ $result);
Loading history...
411
            if ($last_ip == $ip && $last_type == $type) {
412
                $this->_logged = true;
413
414
                return true;
415
            }
416
        }
417
418
        mysqli_query(
419
            $this->_conn,
420
            'INSERT INTO ' . XOOPS_DB_PREFIX . '_' . $this->mydirname . "_log SET ip='"
421
            . mysqli_real_escape_string($this->_conn, $ip) . "',agent='"
422
            . mysqli_real_escape_string($this->_conn, $agent) . "',type='"
423
            . mysqli_real_escape_string($this->_conn, $type) . "',description='"
424
            . mysqli_real_escape_string($this->_conn, $this->message) . "',uid='"
425
            . (int)$uid . "',timestamp=NOW()"
426
        );
427
        $this->_logged = true;
428
429
        return true;
430
    }
431
432
    /**
433
     * @param $expire
434
     *
435
     * @return bool
436
     */
437
    public function write_file_bwlimit($expire)
438
    {
439
        $expire = min((int)$expire, time() + 300);
440
441
        $fp = @fopen($this->get_filepath4bwlimit(), 'w');
442
        if ($fp) {
0 ignored issues
show
introduced by
$fp is of type false|resource, thus it always evaluated to false.
Loading history...
443
            @flock($fp, LOCK_EX);
0 ignored issues
show
Security Best Practice introduced by
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

443
            /** @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...
444
            fwrite($fp, $expire . "\n");
445
            @flock($fp, LOCK_UN);
446
            fclose($fp);
447
448
            return true;
449
        } else {
450
            return false;
451
        }
452
    }
453
454
    /**
455
     * @return mixed
456
     */
457
    public function get_bwlimit()
458
    {
459
        list($expire) = @file(Guardian::get_filepath4bwlimit());
460
        $expire = min((int)$expire, time() + 300);
461
462
        return $expire;
463
    }
464
465
    /**
466
     * @return string
467
     */
468
    public static function get_filepath4bwlimit()
469
    {
470
        return XOOPS_VAR_PATH . '/protector/bwlimit' . substr(md5(XOOPS_ROOT_PATH . XOOPS_DB_USER . XOOPS_DB_PREFIX), 0, 6);
471
    }
472
473
    /**
474
     * @param $bad_ips
475
     *
476
     * @return bool
477
     */
478
    public function write_file_badips($bad_ips)
479
    {
480
        asort($bad_ips);
481
482
        $fp = @fopen($this->get_filepath4badips(), 'w');
483
        if ($fp) {
0 ignored issues
show
introduced by
$fp is of type false|resource, thus it always evaluated to false.
Loading history...
484
            @flock($fp, LOCK_EX);
0 ignored issues
show
Security Best Practice introduced by
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

484
            /** @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...
485
            fwrite($fp, serialize($bad_ips) . "\n");
486
            @flock($fp, LOCK_UN);
487
            fclose($fp);
488
489
            return true;
490
        } else {
491
            return false;
492
        }
493
    }
494
495
    /**
496
     * @param int  $jailed_time
497
     * @param string|null|false $ip
498
     *
499
     * @return bool
500
     */
501
    public function register_bad_ips($jailed_time = 0, $ip = null)
502
    {
503
        if (empty($ip)) {
504
            $ip = \Xmf\IPAddress::fromRequest()
505
                                ->asReadable();
506
        }
507
        if (empty($ip)) {
508
            return false;
509
        }
510
511
        $bad_ips      = $this->get_bad_ips(true);
512
        $bad_ips[$ip] = $jailed_time ?: 0x7fffffff;
513
514
        return $this->write_file_badips($bad_ips);
515
    }
516
517
    /**
518
     * @param bool $with_jailed_time
519
     *
520
     * @return array|mixed
521
     */
522
    public function get_bad_ips($with_jailed_time = false)
523
    {
524
        list($bad_ips_serialized) = @file(Guardian::get_filepath4badips());
525
        $bad_ips = empty($bad_ips_serialized) ? array() : @unserialize($bad_ips_serialized);
526
        if (!is_array($bad_ips) || isset($bad_ips[0])) {
527
            $bad_ips = array();
528
        }
529
530
        // expire jailed_time
531
        $pos = 0;
532
        foreach ($bad_ips as $bad_ip => $jailed_time) {
533
            if ($jailed_time >= time()) {
534
                break;
535
            }
536
            ++$pos;
537
        }
538
        $bad_ips = array_slice($bad_ips, $pos);
539
540
        if ($with_jailed_time) {
541
            return $bad_ips;
542
        } else {
543
            return array_keys($bad_ips);
544
        }
545
    }
546
547
    /**
548
     * @return string
549
     */
550
    public static function get_filepath4badips()
551
    {
552
        return XOOPS_VAR_PATH . '/protector/badips' . substr(md5(XOOPS_ROOT_PATH . XOOPS_DB_USER . XOOPS_DB_PREFIX), 0, 6);
553
    }
554
555
    /**
556
     * @param bool $with_info
557
     *
558
     * @return array|mixed
559
     */
560
    public function get_group1_ips($with_info = false)
561
    {
562
        list($group1_ips_serialized) = @file(Guardian::get_filepath4group1ips());
563
        $group1_ips = empty($group1_ips_serialized) ? array() : @unserialize($group1_ips_serialized);
564
        if (!is_array($group1_ips)) {
565
            $group1_ips = array();
566
        }
567
568
        if ($with_info) {
569
            $group1_ips = array_flip($group1_ips);
570
        }
571
572
        return $group1_ips;
573
    }
574
575
    /**
576
     * @return string
577
     */
578
    public static function get_filepath4group1ips()
579
    {
580
        return XOOPS_VAR_PATH . '/protector/group1ips' . substr(md5(XOOPS_ROOT_PATH . XOOPS_DB_USER . XOOPS_DB_PREFIX), 0, 6);
581
    }
582
583
    /**
584
     * @return string
585
     */
586
    public function get_filepath4confighcache()
587
    {
588
        return XOOPS_VAR_PATH . '/protector/configcache' . substr(md5(XOOPS_ROOT_PATH . XOOPS_DB_USER . XOOPS_DB_PREFIX), 0, 6);
589
    }
590
591
    /**
592
     * @param array $ips
593
     *
594
     * @return bool
595
     */
596
    public function ip_match($ips)
597
    {
598
        $requestIp = \Xmf\IPAddress::fromRequest()
599
                                   ->asReadable();
600
        if (false === $requestIp) { // nothing to match
0 ignored issues
show
introduced by
The condition false === $requestIp is always false.
Loading history...
601
            $this->ip_matched_info = null;
602
            return false;
603
        }
604
        foreach ($ips as $ip => $info) {
605
            if ($ip) {
606
                switch (strtolower(substr($ip, -1))) {
607
                    case '.' :
608
                    case ':' :
609
                        // foward match
610
                        if (substr($requestIp, 0, strlen($ip)) == $ip) {
611
                            $this->ip_matched_info = $info;
612
                            return true;
613
                        }
614
                        break;
615
                    case '0' :
616
                    case '1' :
617
                    case '2' :
618
                    case '3' :
619
                    case '4' :
620
                    case '5' :
621
                    case '6' :
622
                    case '7' :
623
                    case '8' :
624
                    case '9' :
625
                    case 'a' :
626
                    case 'b' :
627
                    case 'c' :
628
                    case 'd' :
629
                    case 'e' :
630
                    case 'f' :
631
                        // full match
632
                        if ($requestIp == $ip) {
633
                            $this->ip_matched_info = $info;
634
                            return true;
635
                        }
636
                        break;
637
                    default :
638
                        // perl regex
639
                        if (@preg_match($ip, $requestIp)) {
640
                            $this->ip_matched_info = $info;
641
                            return true;
642
                        }
643
                        break;
644
                }
645
            }
646
        }
647
        $this->ip_matched_info = null;
648
        return false;
649
    }
650
651
    /**
652
     * @param string|null|false $ip
653
     *
654
     * @return bool
655
     */
656
    public function deny_by_htaccess($ip = null)
657
    {
658
        if (empty($ip)) {
659
            $ip = \Xmf\IPAddress::fromRequest()
660
                                ->asReadable();
661
        }
662
        if (empty($ip)) {
663
            return false;
664
        }
665
        if (!function_exists('file_get_contents')) {
666
            return false;
667
        }
668
669
        $target_htaccess = XOOPS_ROOT_PATH . '/.htaccess';
670
        $backup_htaccess = XOOPS_ROOT_PATH . '/uploads/.htaccess.bak';
671
672
        $ht_body = file_get_contents($target_htaccess);
673
674
        // make backup as uploads/.htaccess.bak automatically
675
        if ($ht_body && !file_exists($backup_htaccess)) {
676
            $fw = fopen($backup_htaccess, 'w');
677
            fwrite($fw, $ht_body);
678
            fclose($fw);
679
        }
680
681
        // if .htaccess is broken, restore from backup
682
        if (!$ht_body && file_exists($backup_htaccess)) {
683
            $ht_body = file_get_contents($backup_htaccess);
684
        }
685
686
        // new .htaccess
687
        if ($ht_body === false) {
688
            $ht_body = '';
689
        }
690
691
        if (preg_match("/^(.*)#PROTECTOR#\s+(DENY FROM .*)\n#PROTECTOR#\n(.*)$/si", $ht_body, $regs)) {
692
            if (substr($regs[2], -strlen($ip)) == $ip) {
693
                return true;
694
            }
695
            $new_ht_body = $regs[1] . "#PROTECTOR#\n" . $regs[2] . " $ip\n#PROTECTOR#\n" . $regs[3];
696
        } else {
697
            $new_ht_body = "#PROTECTOR#\nDENY FROM $ip\n#PROTECTOR#\n" . $ht_body;
698
        }
699
700
        // error_log( "$new_ht_body\n" , 3 , "/tmp/error_log" ) ;
701
702
        $fw = fopen($target_htaccess, 'w');
703
        @flock($fw, LOCK_EX);
0 ignored issues
show
Security Best Practice introduced by
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

703
        /** @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...
704
        fwrite($fw, $new_ht_body);
705
        @flock($fw, LOCK_UN);
706
        fclose($fw);
707
708
        return true;
709
    }
710
711
    /**
712
     * @return array
713
     */
714
    public function getDblayertrapDoubtfuls()
715
    {
716
        return $this->_dblayertrap_doubtfuls;
717
    }
718
719
    /**
720
     * @param array|string $val
721
     * @return null|void
722
     */
723
    protected function _dblayertrap_check_recursive($val)
724
    {
725
        if (is_array($val)) {
726
            foreach ($val as $subval) {
727
                $this->_dblayertrap_check_recursive($subval);
728
            }
729
        } else {
730
            if (strlen($val) < 6) {
731
                return null;
732
            }
733
            $val = @get_magic_quotes_gpc() ? stripslashes($val) : $val;
734
            foreach ($this->_dblayertrap_doubtful_needles as $needle) {
735
                if (false !== stripos($val, $needle)) {
736
                    $this->_dblayertrap_doubtfuls[] = $val;
737
                }
738
            }
739
        }
740
    }
741
742
    /**
743
     * @param  bool $force_override
744
     *
745
     * @return void
746
     */
747
    public function dblayertrap_init($force_override = false)
748
    {
749
        if (!empty($GLOBALS['xoopsOption']['nocommon']) || defined('_LEGACY_PREVENT_EXEC_COMMON_') || defined('_LEGACY_PREVENT_LOAD_CORE_')) {
750
            return null;
751
        } // skip
752
753
        $this->_dblayertrap_doubtfuls = array();
754
        $this->_dblayertrap_check_recursive($_GET);
755
        $this->_dblayertrap_check_recursive($_POST);
756
        $this->_dblayertrap_check_recursive($_COOKIE);
757
        if (empty($this->_conf['dblayertrap_wo_server'])) {
758
            $this->_dblayertrap_check_recursive($_SERVER);
759
        }
760
761
        if (!empty($this->_dblayertrap_doubtfuls) || $force_override) {
762
            @define('XOOPS_DB_ALTERNATIVE', 'ProtectorMysqlDatabase');
0 ignored issues
show
Security Best Practice introduced by
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

762
            /** @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...
763
//            require_once dirname(__DIR__) . '/class/ProtectorMysqlDatabase.class.php';
764
        }
765
    }
766
767
    /**
768
     * @param array|string $val
769
     *
770
     * @return void
771
     */
772
    protected function _bigumbrella_check_recursive($val)
773
    {
774
        if (is_array($val)) {
775
            foreach ($val as $subval) {
776
                $this->_bigumbrella_check_recursive($subval);
777
            }
778
        } else {
779
            if (preg_match('/[<\'"].{15}/s', $val, $regs)) {
780
                $this->_bigumbrella_doubtfuls[] = $regs[0];
781
            }
782
        }
783
    }
784
785
    /**
786
     * @return void
787
     */
788
    public function bigumbrella_init()
789
    {
790
        $this->_bigumbrella_doubtfuls = array();
791
        $this->_bigumbrella_check_recursive($_GET);
792
        $this->_bigumbrella_check_recursive(@$_SERVER['PHP_SELF']);
793
794
        if (!empty($this->_bigumbrella_doubtfuls)) {
795
            ob_start(array(
796
                         $this,
797
                         'bigumbrella_outputcheck',
798
                     ));
799
        }
800
    }
801
802
    /**
803
     * @param string $s
804
     *
805
     * @return string
806
     */
807
    public function bigumbrella_outputcheck($s)
808
    {
809
        if (defined('BIGUMBRELLA_DISABLED')) {
810
            return $s;
811
        }
812
813
        if (function_exists('headers_list')) {
814
            foreach (headers_list() as $header) {
815
                if (false !== stripos($header, 'Content-Type:') && false === stripos($header, 'text/html')) {
816
                    return $s;
817
                }
818
            }
819
        }
820
821
        if (!is_array($this->_bigumbrella_doubtfuls)) {
0 ignored issues
show
introduced by
The condition is_array($this->_bigumbrella_doubtfuls) is always true.
Loading history...
822
            return 'bigumbrella injection found.';
823
        }
824
825
        foreach ($this->_bigumbrella_doubtfuls as $doubtful) {
826
            if (false !== strpos($s, $doubtful)) {
827
                return 'XSS found by Protector.';
828
            }
829
        }
830
831
        return $s;
832
    }
833
834
    /**
835
     * @return bool
836
     */
837
    public function intval_allrequestsendid()
838
    {
839
        global $HTTP_GET_VARS, $HTTP_POST_VARS, $HTTP_COOKIE_VARS;
840
841
        if ($this->_done_intval) {
842
            return true;
843
        } else {
844
            $this->_done_intval = true;
845
        }
846
847
        foreach ($_GET as $key => $val) {
848
            if (substr($key, -2) === 'id' && !is_array($_GET[$key])) {
849
                $newval     = preg_replace('/[^0-9a-zA-Z_-]/', '', $val);
850
                $_GET[$key] = $HTTP_GET_VARS[$key] = $newval;
851
                if ($_REQUEST[$key] == $_GET[$key]) {
852
                    $_REQUEST[$key] = $newval;
853
                }
854
            }
855
        }
856
        foreach ($_POST as $key => $val) {
857
            if (substr($key, -2) === 'id' && !is_array($_POST[$key])) {
858
                $newval      = preg_replace('/[^0-9a-zA-Z_-]/', '', $val);
859
                $_POST[$key] = $HTTP_POST_VARS[$key] = $newval;
860
                if ($_REQUEST[$key] == $_POST[$key]) {
861
                    $_REQUEST[$key] = $newval;
862
                }
863
            }
864
        }
865
        foreach ($_COOKIE as $key => $val) {
866
            if (substr($key, -2) === 'id' && !is_array($_COOKIE[$key])) {
867
                $newval        = preg_replace('/[^0-9a-zA-Z_-]/', '', $val);
868
                $_COOKIE[$key] = $HTTP_COOKIE_VARS[$key] = $newval;
869
                if ($_REQUEST[$key] == $_COOKIE[$key]) {
870
                    $_REQUEST[$key] = $newval;
871
                }
872
            }
873
        }
874
875
        return true;
876
    }
877
878
    /**
879
     * @return bool
880
     */
881
    public function eliminate_dotdot()
882
    {
883
        global $HTTP_GET_VARS, $HTTP_POST_VARS, $HTTP_COOKIE_VARS;
884
885
        if ($this->_done_dotdot) {
886
            return true;
887
        } else {
888
            $this->_done_dotdot = true;
889
        }
890
891
        foreach ($_GET as $key => $val) {
892
            if (is_array($_GET[$key])) {
893
                continue;
894
            }
895
            if (substr(trim($val), 0, 3) === '../' || false !== strpos($val, '/../')) {
896
                $this->last_error_type = 'DirTraversal';
897
                $this->message .= "Directory Traversal '$val' found.\n";
898
                $this->output_log($this->last_error_type, 0, false, 64);
899
                $sanitized_val = str_replace(chr(0), '', $val);
900
                if (substr($sanitized_val, -2) !== ' .') {
901
                    $sanitized_val .= ' .';
902
                }
903
                $_GET[$key] = $HTTP_GET_VARS[$key] = $sanitized_val;
904
                if ($_REQUEST[$key] == $_GET[$key]) {
905
                    $_REQUEST[$key] = $sanitized_val;
906
                }
907
            }
908
        }
909
910
        /*    foreach ($_POST as $key => $val) {
911
                if( is_array( $_POST[ $key ] ) ) continue ;
912
                if ( substr( trim( $val ) , 0 , 3 ) == '../' || false !== strpos( $val , '../../' ) ) {
913
                    $this->last_error_type = 'ParentDir' ;
914
                    $this->message .= "Doubtful file specification '$val' found.\n" ;
915
                    $this->output_log( $this->last_error_type , 0 , false , 128 ) ;
916
                    $sanitized_val = str_replace( chr(0) , '' , $val ) ;
917
                    if( substr( $sanitized_val , -2 ) != ' .' ) $sanitized_val .= ' .' ;
918
                    $_POST[ $key ] = $HTTP_POST_VARS[ $key ] = $sanitized_val ;
919
                    if ($_REQUEST[ $key ] == $_POST[ $key ]) {
920
                        $_REQUEST[ $key ] = $sanitized_val ;
921
                    }
922
                }
923
            }
924
            foreach ($_COOKIE as $key => $val) {
925
                if( is_array( $_COOKIE[ $key ] ) ) continue ;
926
                if ( substr( trim( $val ) , 0 , 3 ) == '../' || false !== strpos( $val , '../../' ) ) {
927
                    $this->last_error_type = 'ParentDir' ;
928
                    $this->message .= "Doubtful file specification '$val' found.\n" ;
929
                    $this->output_log( $this->last_error_type , 0 , false , 128 ) ;
930
                    $sanitized_val = str_replace( chr(0) , '' , $val ) ;
931
                    if( substr( $sanitized_val , -2 ) != ' .' ) $sanitized_val .= ' .' ;
932
                    $_COOKIE[ $key ] = $HTTP_COOKIE_VARS[ $key ] = $sanitized_val ;
933
                    if ($_REQUEST[ $key ] == $_COOKIE[ $key ]) {
934
                        $_REQUEST[ $key ] = $sanitized_val ;
935
                    }
936
                }
937
            }*/
938
939
        return true;
940
    }
941
942
    /**
943
     * @param array|string $current
944
     * @param array        $indexes
945
     *
946
     * @return bool|array
947
     */
948
    public function &get_ref_from_base64index(&$current, $indexes)
949
    {
950
        foreach ($indexes as $index) {
951
            $index = base64_decode($index);
952
            if (!is_array($current)) {
953
                return false;
954
            }
955
            $current =& $current[$index];
956
        }
957
958
        return $current;
959
    }
960
961
    /**
962
     * @param string       $key
963
     * @param array|string $val
964
     *
965
     * @return void
966
     */
967
    public function replace_doubtful($key, $val)
968
    {
969
        global $HTTP_GET_VARS, $HTTP_POST_VARS, $HTTP_COOKIE_VARS;
970
971
        $index_expression = '';
0 ignored issues
show
Unused Code introduced by
The assignment to $index_expression is dead and can be removed.
Loading history...
972
        $indexes          = explode('_', $key);
973
        $base_array       = array_shift($indexes);
974
975
        switch ($base_array) {
976
            case 'G' :
977
                $main_ref   =& $this->get_ref_from_base64index($_GET, $indexes);
978
                $legacy_ref =& $this->get_ref_from_base64index($HTTP_GET_VARS, $indexes);
979
                break;
980
            case 'P' :
981
                $main_ref   =& $this->get_ref_from_base64index($_POST, $indexes);
982
                $legacy_ref =& $this->get_ref_from_base64index($HTTP_POST_VARS, $indexes);
983
                break;
984
            case 'C' :
985
                $main_ref   =& $this->get_ref_from_base64index($_COOKIE, $indexes);
986
                $legacy_ref =& $this->get_ref_from_base64index($HTTP_COOKIE_VARS, $indexes);
987
                break;
988
            default :
989
                exit;
0 ignored issues
show
Best Practice introduced by
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...
990
        }
991
        if (!isset($main_ref)) {
992
            exit;
0 ignored issues
show
Best Practice introduced by
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...
993
        }
994
        $request_ref =& $this->get_ref_from_base64index($_REQUEST, $indexes);
995
        if ($request_ref !== false && $main_ref == $request_ref) {
996
            $request_ref = $val;
997
        }
998
        $main_ref   = $val;
999
        $legacy_ref = $val;
1000
    }
1001
1002
    /**
1003
     * @return bool
1004
     */
1005
    public function check_uploaded_files()
1006
    {
1007
        if ($this->_done_badext) {
1008
            return $this->_safe_badext;
1009
        } else {
1010
            $this->_done_badext = true;
1011
        }
1012
1013
        // extensions never uploaded
1014
        $bad_extensions = array(
1015
            'php',
1016
            'phtml',
1017
            'phtm',
1018
            'php3',
1019
            'php4',
1020
            'cgi',
1021
            'pl',
1022
            'asp',
1023
        );
1024
        // extensions needed image check (anti-IE Content-Type XSS)
1025
        $image_extensions = array(
1026
            1  => 'gif',
1027
            2  => 'jpg',
1028
            3  => 'png',
1029
            4  => 'swf',
1030
            5  => 'psd',
1031
            6  => 'bmp',
1032
            7  => 'tif',
1033
            8  => 'tif',
1034
            9  => 'jpc',
1035
            10 => 'jp2',
1036
            11 => 'jpx',
1037
            12 => 'jb2',
1038
            13 => 'swc',
1039
            14 => 'iff',
1040
            15 => 'wbmp',
1041
            16 => 'xbm',
1042
        );
1043
1044
        foreach ($_FILES as $_file) {
1045
            if (!empty($_file['error'])) {
1046
                continue;
1047
            }
1048
            if (!empty($_file['name']) && is_string($_file['name'])) {
1049
                $ext = strtolower(substr(strrchr($_file['name'], '.'), 1));
1050
                if ($ext === 'jpeg') {
1051
                    $ext = 'jpg';
1052
                } elseif ($ext === 'tiff') {
1053
                    $ext = 'tif';
1054
                }
1055
1056
                // anti multiple dot file (Apache mod_mime.c)
1057
                if (count(explode('.', str_replace('.tar.gz', '.tgz', $_file['name']))) > 2) {
1058
                    $this->message .= "Attempt to multiple dot file {$_file['name']}.\n";
1059
                    $this->_safe_badext    = false;
1060
                    $this->last_error_type = 'UPLOAD';
1061
                }
1062
1063
                // anti dangerous extensions
1064
                if (in_array($ext, $bad_extensions)) {
1065
                    $this->message .= "Attempt to upload {$_file['name']}.\n";
1066
                    $this->_safe_badext    = false;
1067
                    $this->last_error_type = 'UPLOAD';
1068
                }
1069
1070
                // anti camouflaged image file
1071
                if (in_array($ext, $image_extensions)) {
1072
                    $image_attributes = @getimagesize($_file['tmp_name']);
1073
                    if ($image_attributes === false && is_uploaded_file($_file['tmp_name'])) {
1074
                        // open_basedir restriction
1075
                        $temp_file = XOOPS_ROOT_PATH . '/uploads/protector_upload_temporary' . md5(time());
1076
                        move_uploaded_file($_file['tmp_name'], $temp_file);
1077
                        $image_attributes = @getimagesize($temp_file);
1078
                        @unlink($temp_file);
0 ignored issues
show
Security Best Practice introduced by
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

1078
                        /** @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...
1079
                    }
1080
1081
                    if ($image_attributes === false || $image_extensions[(int)$image_attributes[2]] != $ext) {
1082
                        $this->message .= "Attempt to upload camouflaged image file {$_file['name']}.\n";
1083
                        $this->_safe_badext    = false;
1084
                        $this->last_error_type = 'UPLOAD';
1085
                    }
1086
                }
1087
            }
1088
        }
1089
1090
        return $this->_safe_badext;
1091
    }
1092
1093
    /**
1094
     * @return bool
1095
     */
1096
    public function check_contami_systemglobals()
1097
    {
1098
        /*    if( $this->_done_contami ) return $this->_safe_contami ;
1099
    else $this->_done_contami = true ; */
1100
1101
        /*    foreach ($this->_bad_globals as $bad_global) {
1102
                if ( isset( $_REQUEST[ $bad_global ] ) ) {
1103
                    $this->message .= "Attempt to inject '$bad_global' was found.\n" ;
1104
                    $this->_safe_contami = false ;
1105
                    $this->last_error_type = 'CONTAMI' ;
1106
                }
1107
            }*/
1108
1109
        return $this->_safe_contami;
1110
    }
1111
1112
    /**
1113
     * @param bool $sanitize
1114
     *
1115
     * @return bool
1116
     */
1117
    public function check_sql_isolatedcommentin($sanitize = true)
1118
    {
1119
        if ($this->_done_isocom) {
1120
            return $this->_safe_isocom;
1121
        } else {
1122
            $this->_done_isocom = true;
1123
        }
1124
1125
        foreach ($this->_doubtful_requests as $key => $val) {
1126
            $str = $val;
1127
            while ($str = strstr($str, '/*')) { /* */
1128
                $str = strstr(substr($str, 2), '*/');
1129
                if ($str === false) {
1130
                    $this->message .= "Isolated comment-in found. ($val)\n";
1131
                    if ($sanitize) {
1132
                        $this->replace_doubtful($key, $val . '*/');
1133
                    }
1134
                    $this->_safe_isocom    = false;
1135
                    $this->last_error_type = 'ISOCOM';
1136
                }
1137
            }
1138
        }
1139
1140
        return $this->_safe_isocom;
1141
    }
1142
1143
    /**
1144
     * @param bool $sanitize
1145
     *
1146
     * @return bool
1147
     */
1148
    public function check_sql_union($sanitize = true)
1149
    {
1150
        if ($this->_done_union) {
1151
            return $this->_safe_union;
1152
        } else {
1153
            $this->_done_union = true;
1154
        }
1155
1156
        foreach ($this->_doubtful_requests as $key => $val) {
1157
            $str = str_replace(array('/*', '*/'), '', preg_replace('?/\*.+\*/?sU', '', $val));
1158
            if (preg_match('/\sUNION\s+(ALL|SELECT)/i', $str)) {
1159
                $this->message .= "Pattern like SQL injection found. ($val)\n";
1160
                if ($sanitize) {
1161
                    //                    $this->replace_doubtful($key, preg_replace('/union/i', 'uni-on', $val));
1162
                    $this->replace_doubtful($key, str_ireplace('union', 'uni-on', $val));
1163
                }
1164
                $this->_safe_union     = false;
1165
                $this->last_error_type = 'UNION';
1166
            }
1167
        }
1168
1169
        return $this->_safe_union;
1170
    }
1171
1172
    /**
1173
     * @param int $uid
1174
     *
1175
     * @return bool
1176
     */
1177
    public function stopforumspam($uid)
1178
    {
1179
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
1180
            return false;
1181
        }
1182
1183
        $result = $this->stopForumSpamLookup(
1184
            isset($_POST['email']) ? $_POST['email'] : null,
1185
            $_SERVER['REMOTE_ADDR'],
1186
            isset($_POST['uname']) ? $_POST['uname'] : null
1187
        );
1188
1189
        if (false === $result || isset($result['http_code'])) {
1190
            return false;
1191
        }
1192
1193
        $spammer = false;
1194
        if (isset($result['email']) && isset($result['email']['lastseen'])) {
1195
            $spammer = true;
1196
        }
1197
1198
        if (isset($result['ip']) && isset($result['ip']['lastseen'])) {
1199
            $last        = strtotime($result['ip']['lastseen']);
1200
            $oneMonth    = 60 * 60 * 24 * 31;
1201
            $oneMonthAgo = time() - $oneMonth;
1202
            if ($last > $oneMonthAgo) {
1203
                $spammer = true;
1204
            }
1205
        }
1206
1207
        if (!$spammer) {
1208
            return false;
1209
        }
1210
1211
        $this->last_error_type = 'SPAMMER POST';
1212
1213
        switch ($this->_conf['stopforumspam_action']) {
1214
            default :
1215
            case 'log' :
1216
                break;
1217
            case 'san' :
1218
                $_POST = array();
1219
                $this->message .= 'POST deleted for IP:' . $_SERVER['REMOTE_ADDR'];
1220
                break;
1221
            case 'biptime0' :
1222
                $_POST = array();
1223
                $this->message .= 'BAN and POST deleted for IP:' . $_SERVER['REMOTE_ADDR'];
1224
                $this->_should_be_banned_time0 = true;
1225
                break;
1226
            case 'bip' :
1227
                $_POST = array();
1228
                $this->message .= 'Ban and POST deleted for IP:' . $_SERVER['REMOTE_ADDR'];
1229
                $this->_should_be_banned = true;
1230
                break;
1231
        }
1232
1233
        $this->output_log($this->last_error_type, $uid, false, 16);
1234
1235
        return true;
1236
    }
1237
1238
    /**
1239
     * @param string $email
1240
     * @param string $ip
1241
     * @param string $username
1242
     *
1243
     * @return string|bool
1244
     */
1245
    public function stopForumSpamLookup($email, $ip, $username)
1246
    {
1247
        if (!function_exists('curl_init')) {
1248
            return false;
1249
        }
1250
1251
        $query = '';
1252
        $query .= (empty($ip)) ? '' : '&ip=' . $ip;
1253
        $query .= (empty($email)) ? '' : '&email=' . $email;
1254
        $query .= (empty($username)) ? '' : '&username=' . $username;
1255
1256
        if (empty($query)) {
1257
            return false;
1258
        }
1259
1260
        $url = 'http://www.stopforumspam.com/api?f=json' . $query;
1261
        $ch  = curl_init();
1262
        curl_setopt($ch, CURLOPT_URL, $url);
1263
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
1264
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
1265
        $result = curl_exec($ch);
1266
        if (false === $result) {
1267
            $result = curl_getinfo($ch);
1268
        } else {
1269
            $result = json_decode(curl_exec($ch), true);
0 ignored issues
show
Bug introduced by
It seems like curl_exec($ch) can also be of type true; however, parameter $json of json_decode() 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

1269
            $result = json_decode(/** @scrutinizer ignore-type */ curl_exec($ch), true);
Loading history...
1270
        }
1271
        curl_close($ch);
1272
1273
        return $result;
1274
    }
1275
1276
    /**
1277
     * @param int  $uid
1278
     * @param bool $can_ban
1279
     *
1280
     * @return bool
1281
     */
1282
    public function check_dos_attack($uid = 0, $can_ban = false)
1283
    {
1284
        global $xoopsDB;
1285
1286
        if ($this->_done_dos) {
1287
            return true;
1288
        }
1289
1290
        $ip      = \Xmf\IPAddress::fromRequest();
1291
        if (false === $ip->asReadable()) {
0 ignored issues
show
introduced by
The condition false === $ip->asReadable() is always false.
Loading history...
1292
            return true;
1293
        }
1294
        $uri     = @$_SERVER['REQUEST_URI'];
1295
1296
        $ip4sql  = $xoopsDB->quote($ip->asReadable());
1297
        $uri4sql = $xoopsDB->quote($uri);
1298
1299
        // gargage collection
1300
        $result = $xoopsDB->queryF(
1301
            'DELETE FROM ' . $xoopsDB->prefix($this->mydirname . '_access')
1302
            . ' WHERE expire < UNIX_TIMESTAMP()'
1303
        );
1304
1305
        // for older versions before updating this module
1306
        if ($result === false) {
1307
            $this->_done_dos = true;
1308
1309
            return true;
1310
        }
1311
1312
        // sql for recording access log (INSERT should be placed after SELECT)
1313
        $sql4insertlog = 'INSERT INTO ' . $xoopsDB->prefix($this->mydirname . '_access')
1314
                         . " SET ip={$ip4sql}, request_uri={$uri4sql},"
1315
                         . " expire=UNIX_TIMESTAMP()+'" . (int)$this->_conf['dos_expire'] . "'";
1316
1317
        // bandwidth limitation
1318
        if (@$this->_conf['bwlimit_count'] >= 10) {
1319
            $result = $xoopsDB->query('SELECT COUNT(*) FROM ' . $xoopsDB->prefix($this->mydirname . '_access'));
1320
            list($bw_count) = $xoopsDB->fetchRow($result);
1321
            if ($bw_count > $this->_conf['bwlimit_count']) {
1322
                $this->write_file_bwlimit(time() + $this->_conf['dos_expire']);
1323
            }
1324
        }
1325
1326
        // F5 attack check (High load & same URI)
1327
        $result = $xoopsDB->query(
1328
            'SELECT COUNT(*) FROM ' . $xoopsDB->prefix($this->mydirname . '_access')
1329
            . " WHERE ip={$ip4sql} AND request_uri={$uri4sql}"
1330
        );
1331
        list($f5_count) = $xoopsDB->fetchRow($result);
1332
        if ($f5_count > $this->_conf['dos_f5count']) {
1333
1334
            // delayed insert
1335
            $xoopsDB->queryF($sql4insertlog);
1336
1337
            // extends the expires of the IP with 5 minutes at least (pending)
1338
            // $result = $xoopsDB->queryF( "UPDATE ".$xoopsDB->prefix($this->mydirname.'_access')." SET expire=UNIX_TIMESTAMP()+300 WHERE ip='$ip4sql' AND expire<UNIX_TIMESTAMP()+300" ) ;
1339
1340
            // call the filter first
1341
            $ret = $this->call_filter('f5attack_overrun');
0 ignored issues
show
Unused Code introduced by
The assignment to $ret is dead and can be removed.
Loading history...
1342
1343
            // actions for F5 Attack
1344
            $this->_done_dos       = true;
1345
            $this->last_error_type = 'DoS';
1346
            switch ($this->_conf['dos_f5action']) {
1347
                default :
1348
                case 'exit' :
1349
                    $this->output_log($this->last_error_type, $uid, true, 16);
1350
                    exit;
0 ignored issues
show
Best Practice introduced by
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...
1351
                case 'none' :
1352
                    $this->output_log($this->last_error_type, $uid, true, 16);
1353
1354
                    return true;
1355
                case 'biptime0' :
1356
                    if ($can_ban) {
1357
                        $this->register_bad_ips(time() + $this->_conf['banip_time0']);
1358
                    }
1359
                    break;
1360
                case 'bip' :
1361
                    if ($can_ban) {
1362
                        $this->register_bad_ips();
1363
                    }
1364
                    break;
1365
                case 'hta' :
1366
                    if ($can_ban) {
1367
                        $this->deny_by_htaccess();
1368
                    }
1369
                    break;
1370
                case 'sleep' :
1371
                    sleep(5);
1372
                    break;
1373
            }
1374
1375
            return false;
1376
        }
1377
1378
        // Check its Agent
1379
        if (trim($this->_conf['dos_crsafe']) != '' && preg_match($this->_conf['dos_crsafe'], @$_SERVER['HTTP_USER_AGENT'])) {
1380
            // welcomed crawler
1381
            $this->_done_dos = true;
1382
1383
            return true;
1384
        }
1385
1386
        // Crawler check (High load & different URI)
1387
        $result = $xoopsDB->query(
1388
            'SELECT COUNT(*) FROM ' . $xoopsDB->prefix($this->mydirname . '_access') . " WHERE ip={$ip4sql}"
1389
        );
1390
        list($crawler_count) = $xoopsDB->fetchRow($result);
1391
1392
        // delayed insert
1393
        $xoopsDB->queryF($sql4insertlog);
1394
1395
        if ($crawler_count > $this->_conf['dos_crcount']) {
1396
1397
            // call the filter first
1398
            $ret = $this->call_filter('crawler_overrun');
1399
1400
            // actions for bad Crawler
1401
            $this->_done_dos       = true;
1402
            $this->last_error_type = 'CRAWLER';
1403
            switch ($this->_conf['dos_craction']) {
1404
                default :
1405
                case 'exit' :
1406
                    $this->output_log($this->last_error_type, $uid, true, 16);
1407
                    exit;
0 ignored issues
show
Best Practice introduced by
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...
1408
                case 'none' :
1409
                    $this->output_log($this->last_error_type, $uid, true, 16);
1410
1411
                    return true;
1412
                case 'biptime0' :
1413
                    if ($can_ban) {
1414
                        $this->register_bad_ips(time() + $this->_conf['banip_time0']);
1415
                    }
1416
                    break;
1417
                case 'bip' :
1418
                    if ($can_ban) {
1419
                        $this->register_bad_ips();
1420
                    }
1421
                    break;
1422
                case 'hta' :
1423
                    if ($can_ban) {
1424
                        $this->deny_by_htaccess();
1425
                    }
1426
                    break;
1427
                case 'sleep' :
1428
                    sleep(5);
1429
                    break;
1430
            }
1431
1432
            return false;
1433
        }
1434
1435
        return true;
1436
    }
1437
1438
    //
1439
    /**
1440
     * @return bool|null
1441
     */
1442
    public function check_brute_force()
1443
    {
1444
        global $xoopsDB;
1445
1446
        $ip      = \Xmf\IPAddress::fromRequest();
1447
        if (false === $ip->asReadable()) {
0 ignored issues
show
introduced by
The condition false === $ip->asReadable() is always false.
Loading history...
1448
            return true;
1449
        }
1450
        $uri     = @$_SERVER['REQUEST_URI'];
1451
        $ip4sql  = $xoopsDB->quote($ip->asReadable());
1452
        $uri4sql = $xoopsDB->quote($uri);
1453
1454
        $victim_uname = empty($_COOKIE['autologin_uname']) ? $_POST['uname'] : $_COOKIE['autologin_uname'];
1455
        // some UA send 'deleted' as a value of the deleted cookie.
1456
        if ($victim_uname === 'deleted') {
1457
            return null;
1458
        }
1459
        $mal4sql = $xoopsDB->quote("BRUTE FORCE: $victim_uname");
1460
1461
        // garbage collection
1462
        $result = $xoopsDB->queryF(
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
1463
            'DELETE FROM ' . $xoopsDB->prefix($this->mydirname . '_access') . ' WHERE expire < UNIX_TIMESTAMP()'
1464
        );
1465
1466
        // sql for recording access log (INSERT should be placed after SELECT)
1467
        $sql4insertlog = 'INSERT INTO ' . $xoopsDB->prefix($this->mydirname . '_access')
1468
                         . " SET ip={$ip4sql}, request_uri={$uri4sql}, malicious_actions={$mal4sql}, expire=UNIX_TIMESTAMP()+600";
1469
1470
        // count check
1471
        $result = $xoopsDB->query(
1472
            'SELECT COUNT(*) FROM ' . $xoopsDB->prefix($this->mydirname . '_access')
1473
            . " WHERE ip={$ip4sql} AND malicious_actions like 'BRUTE FORCE:%'"
1474
        );
1475
        list($bf_count) = $xoopsDB->fetchRow($result);
1476
        if ($bf_count > $this->_conf['bf_count']) {
1477
            $this->register_bad_ips(time() + $this->_conf['banip_time0']);
1478
            $this->last_error_type = 'BruteForce';
1479
            $this->message .= "Trying to login as '" . addslashes($victim_uname) . "' found.\n";
1480
            $this->output_log('BRUTE FORCE', 0, true, 1);
1481
            $ret = $this->call_filter('bruteforce_overrun');
1482
            if ($ret == false) {
1483
                exit;
0 ignored issues
show
Best Practice introduced by
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...
1484
            }
1485
        }
1486
        // delayed insert
1487
        $xoopsDB->queryF($sql4insertlog);
1488
        return null;
1489
    }
1490
1491
    /**
1492
     * @param array|string $val
1493
     *
1494
     * @return void
1495
     */
1496
    protected function _spam_check_point_recursive($val)
1497
    {
1498
        if (is_array($val)) {
1499
            foreach ($val as $subval) {
1500
                $this->_spam_check_point_recursive($subval);
1501
            }
1502
        } else {
1503
            // http_host
1504
            $path_array = parse_url(XOOPS_URL);
1505
            $http_host  = empty($path_array['host']) ? 'www.xoops.org' : $path_array['host'];
1506
1507
            // count URI up
1508
            $count = -1;
1509
            foreach (preg_split('#https?\:\/\/#i', $val) as $fragment) {
1510
                if (strncmp($fragment, $http_host, strlen($http_host)) !== 0) {
1511
                    ++$count;
1512
                }
1513
            }
1514
            if ($count > 0) {
1515
                $this->_spamcount_uri += $count;
1516
            }
1517
1518
            // count BBCode likd [url=www....] up (without [url=http://...])
1519
            $this->_spamcount_uri += count(preg_split('/\[url=(?!http|\\"http|\\\'http|' . $http_host . ')/i', $val)) - 1;
1520
        }
1521
    }
1522
1523
    /**
1524
     * @param int $points4deny
1525
     * @param int $uid
1526
     *
1527
     * @return void
1528
     */
1529
    public function spam_check($points4deny, $uid)
1530
    {
1531
        $this->_spamcount_uri = 0;
1532
        $this->_spam_check_point_recursive($_POST);
1533
1534
        if ($this->_spamcount_uri >= $points4deny) {
1535
            $this->message .= @$_SERVER['REQUEST_URI'] . " SPAM POINT: $this->_spamcount_uri\n";
1536
            $this->output_log('URI SPAM', $uid, false, 128);
1537
            $ret = $this->call_filter('spamcheck_overrun');
1538
            if ($ret == false) {
1539
                exit;
0 ignored issues
show
Best Practice introduced by
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...
1540
            }
1541
        }
1542
    }
1543
1544
    /**
1545
     * @return void
1546
     */
1547
    public function disable_features()
1548
    {
1549
        global $HTTP_POST_VARS, $HTTP_GET_VARS, $HTTP_COOKIE_VARS;
1550
1551
        // disable "Notice: Undefined index: ..."
1552
        $error_reporting_level = error_reporting(0);
1553
1554
        //
1555
        // bit 1 : disable XMLRPC , criteria bug
1556
        //
1557
        if ($this->_conf['disable_features'] & 1) {
1558
1559
            // zx 2005/1/5 disable xmlrpc.php in root
1560
            if (/* ! stristr( $_SERVER['SCRIPT_NAME'] , 'modules' ) && */
1561
                substr(@$_SERVER['SCRIPT_NAME'], -10) === 'xmlrpc.php'
1562
            ) {
1563
                $this->output_log('xmlrpc', 0, true, 1);
1564
                exit;
0 ignored issues
show
Best Practice introduced by
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...
1565
            }
1566
1567
            // security bug of class/criteria.php 2005/6/27
1568
            if ((isset($_POST['uname']) && $_POST['uname'] === '0') || (isset($_COOKIE['autologin_pass']) && $_COOKIE['autologin_pass'] === '0')) {
1569
                $this->output_log('CRITERIA');
1570
                exit;
0 ignored issues
show
Best Practice introduced by
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...
1571
            }
1572
        }
1573
1574
        //
1575
        // bit 11 : XSS+CSRFs in XOOPS < 2.0.10
1576
        //
1577
        if ($this->_conf['disable_features'] & 1024) {
1578
1579
            // root controllers
1580
            if (false === stripos(@$_SERVER['SCRIPT_NAME'], 'modules')) {
1581
                // zx 2004/12/13 misc.php debug (file check)
1582
                if (substr(@$_SERVER['SCRIPT_NAME'], -8) === 'misc.php' && ($_GET['type'] === 'debug' || $_POST['type'] === 'debug') && !preg_match('/^dummy_\d+\.html$/', $_GET['file'])) {
1583
                    $this->output_log('misc debug');
1584
                    exit;
0 ignored issues
show
Best Practice introduced by
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...
1585
                }
1586
1587
                // zx 2004/12/13 misc.php smilies
1588
                if (substr(@$_SERVER['SCRIPT_NAME'], -8) === 'misc.php' && ($_GET['type'] === 'smilies' || $_POST['type'] === 'smilies') && !preg_match('/^[0-9a-z_]*$/i', $_GET['target'])) {
1589
                    $this->output_log('misc smilies');
1590
                    exit;
0 ignored issues
show
Best Practice introduced by
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...
1591
                }
1592
1593
                // zx 2005/1/5 edituser.php avatarchoose
1594
                if (substr(@$_SERVER['SCRIPT_NAME'], -12) === 'edituser.php' && $_POST['op'] === 'avatarchoose' && false !== strpos($_POST['user_avatar'], '..')) {
1595
                    $this->output_log('edituser avatarchoose');
1596
                    exit;
0 ignored issues
show
Best Practice introduced by
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...
1597
                }
1598
            }
1599
1600
            // zx 2005/1/4 findusers
1601
            if (substr(@$_SERVER['SCRIPT_NAME'], -24) === 'modules/system/admin.php' && ($_GET['fct'] === 'findusers' || $_POST['fct'] === 'findusers')) {
1602
                foreach ($_POST as $key => $val) {
1603
                    if (false !== strpos($key, "'") || false !== strpos($val, "'")) {
1604
                        $this->output_log('findusers');
1605
                        exit;
0 ignored issues
show
Best Practice introduced by
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...
1606
                    }
1607
                }
1608
            }
1609
1610
            // preview CSRF zx 2004/12/14
1611
            // news submit.php
1612
            if (substr(@$_SERVER['SCRIPT_NAME'], -23) === 'modules/news/submit.php' && isset($_POST['preview']) && strpos(@$_SERVER['HTTP_REFERER'], XOOPS_URL . '/modules/news/submit.php') !== 0) {
1613
                $HTTP_POST_VARS['nohtml'] = $_POST['nohtml'] = 1;
1614
            }
1615
            // news admin/index.php
1616
            if (substr(@$_SERVER['SCRIPT_NAME'], -28) === 'modules/news/admin/index.php' && ($_POST['op'] === 'preview' || $_GET['op'] === 'preview') && strpos(@$_SERVER['HTTP_REFERER'], XOOPS_URL . '/modules/news/admin/index.php') !== 0) {
1617
                $HTTP_POST_VARS['nohtml'] = $_POST['nohtml'] = 1;
1618
            }
1619
            // comment comment_post.php
1620
            if (isset($_POST['com_dopreview']) && false === strpos(substr(@$_SERVER['HTTP_REFERER'], -16), 'comment_post.php')) {
1621
                $HTTP_POST_VARS['dohtml'] = $_POST['dohtml'] = 0;
1622
            }
1623
            // disable preview of system's blocksadmin
1624
            if (substr(@$_SERVER['SCRIPT_NAME'], -24) === 'modules/system/admin.php' && ($_GET['fct'] === 'blocksadmin' || $_POST['fct'] === 'blocksadmin') && isset($_POST['previewblock']) /* && strpos( $_SERVER['HTTP_REFERER'] , XOOPS_URL.'/modules/system/admin.php' ) !== 0 */) {
1625
                die("Danger! don't use this preview. Use 'altsys module' instead.(by Protector)");
0 ignored issues
show
Best Practice introduced by
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...
1626
            }
1627
            // tpl preview
1628
            if (substr(@$_SERVER['SCRIPT_NAME'], -24) === 'modules/system/admin.php' && ($_GET['fct'] === 'tplsets' || $_POST['fct'] === 'tplsets')) {
1629
                if ($_POST['op'] === 'previewpopup' || $_GET['op'] === 'previewpopup' || isset($_POST['previewtpl'])) {
1630
                    die("Danger! don't use this preview.(by Protector)");
0 ignored issues
show
Best Practice introduced by
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...
1631
                }
1632
            }
1633
        }
1634
1635
        // restore reporting level
1636
        error_reporting($error_reporting_level);
1637
    }
1638
1639
    /**
1640
     * @param string $type
1641
     * @param string $dying_message
1642
     *
1643
     * @return int|mixed
1644
     */
1645
    public function call_filter($type, $dying_message = '')
1646
    {
1647
//        require_once __DIR__ . '/ProtectorFilter.php';
1648
        $filter_handler = FilterHandler::getInstance();
1649
        $ret            = $filter_handler->execute($type);
1650
        if ($ret == false && $dying_message) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $ret of type integer to the boolean false. If you are specifically checking for 0, consider using something more explicit like === 0 instead.
Loading history...
1651
            die($dying_message);
0 ignored issues
show
Best Practice introduced by
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...
1652
        }
1653
1654
        return $ret;
1655
    }
1656
}
1657