[SPIP] ~maj v3.2.9-->v3.2.11
[lhc/web/www.git] / www / config / ecran_securite.php
1 <?php
2
3 /*
4 * ecran_securite.php
5 * ------------------
6 */
7
8 define('_ECRAN_SECURITE', '1.4.1'); // 2021-03-12
9
10 /*
11 * Documentation : http://www.spip.net/fr_article4200.html
12 */
13
14 /*
15 * Test utilisateur
16 */
17 if (isset($_GET['test_ecran_securite']))
18 $ecran_securite_raison = 'test '._ECRAN_SECURITE;
19
20 if (file_exists($f = __DIR__ . DIRECTORY_SEPARATOR . 'ecran_securite_options.php')) {
21 include ($f);
22 }
23
24 /*
25 * Monitoring
26 * var_isbot=0 peut etre utilise par un bot de monitoring pour surveiller la disponibilite d'un site vu par les users
27 * var_isbot=1 peut etre utilise pour monitorer la disponibilite pour les bots (sujets a 503 de delestage si
28 * le load depasse ECRAN_SECURITE_LOAD)
29 */
30 if (!defined('_IS_BOT') and isset($_GET['var_isbot'])){
31 define('_IS_BOT', $_GET['var_isbot'] ? true : false);
32 }
33
34 /*
35 * Détecteur de robot d'indexation
36 */
37 if (!defined('_IS_BOT')){
38 define('_IS_BOT',
39 isset($_SERVER['HTTP_USER_AGENT'])
40 and preg_match(','
41 . implode ('|', array(
42 // mots generiques
43 'bot',
44 'slurp',
45 'crawler',
46 'crwlr',
47 'java',
48 'monitoring',
49 'spider',
50 'webvac',
51 'yandex',
52 'MSIE 6\.0', // botnet 99,9% du temps
53 // UA plus cibles
54 '200please',
55 '80legs',
56 'a6-indexer',
57 'aboundex',
58 'accoona',
59 'acrylicapps',
60 'addthis',
61 'adressendeutschland',
62 'alexa',
63 'altavista',
64 'analyticsseo',
65 'antennapod',
66 'arachnys',
67 'archive',
68 'argclrint',
69 'aspseek',
70 'baidu',
71 'begunadvertising',
72 'bing',
73 'bloglines',
74 'buck',
75 'browsershots',
76 'bubing',
77 'butterfly',
78 'changedetection',
79 'charlotte',
80 'chilkat',
81 'china',
82 'coccoc',
83 'crowsnest',
84 'dataminr',
85 'daumoa',
86 'dlvr\.it',
87 'dlweb',
88 'drupal',
89 'ec2linkfinder',
90 'eset\.com',
91 'estyle',
92 'exalead',
93 'ezooms',
94 'facebookexternalhit',
95 'facebookplatform',
96 'fairshare',
97 'feedfetcher',
98 'feedfetcher-google',
99 'feedly',
100 'fetch',
101 'flipboardproxy',
102 'genieo',
103 'google',
104 'go-http-client',
105 'grapeshot',
106 'hatena-useragent',
107 'head',
108 'hosttracker',
109 'hubspot',
110 'ia_archiver',
111 'ichiro',
112 'iltrovatore-setaccio',
113 'immediatenet',
114 'ina',
115 'inoreader',
116 'infegyatlas',
117 'infohelfer',
118 'instapaper',
119 'jabse',
120 'james',
121 'jersey',
122 'kumkie',
123 'linkdex',
124 'linkfluence',
125 'linkwalker',
126 'litefinder',
127 'loadimpactpageanalyzer',
128 'ltx71',
129 'luminate',
130 'lycos',
131 'lycosa',
132 'mediapartners-google',
133 'msai',
134 'myapp',
135 'nativehost',
136 'najdi',
137 'netcraftsurveyagent',
138 'netestate',
139 'netseer',
140 'netnewswire',
141 'newspaper',
142 'newsblur',
143 'nuhk',
144 'nuzzel',
145 'okhttp',
146 'otmedia',
147 'owlin',
148 'owncloud',
149 'panscient',
150 'paper\.li',
151 'parsijoo',
152 'protopage',
153 'plukkie',
154 'proximic',
155 'pubsub',
156 'python',
157 'qirina',
158 'qoshe',
159 'qualidator',
160 'qwantify',
161 'rambler',
162 'readability',
163 'ruby',
164 'sbsearch',
165 'scoop\.it',
166 'scooter',
167 'scoutjet',
168 'scrapy',
169 'scrubby',
170 'scrubbybloglines',
171 'shareaholic',
172 'shopwiki',
173 'simplepie',
174 'sistrix',
175 'sitechecker',
176 'siteexplorer',
177 'snapshot',
178 'sogou',
179 'special_archiver',
180 'speedy',
181 'spinn3r',
182 'spreadtrum',
183 'steeler',
184 'subscriber',
185 'suma',
186 'superdownloads',
187 'svenska-webbsido',
188 'teoma',
189 'the knowledge AI',
190 'thumbshots',
191 'tineye',
192 'traackr',
193 'trendiction',
194 'trendsmap',
195 'tweetedtimes',
196 'tweetmeme',
197 'universalfeedparser',
198 'uaslinkchecker',
199 'undrip',
200 'unwindfetchor',
201 'upday',
202 'vedma',
203 'vkshare',
204 'vm',
205 'wch',
206 'webalta',
207 'webcookies',
208 'webparser',
209 'webthumbnail',
210 'wesee',
211 'wise-guys',
212 'woko',
213 'wordpress',
214 'wotbox',
215 'y!j-bri',
216 'y!j-bro',
217 'y!j-brw',
218 'y!j-bsc',
219 'yahoo',
220 'yahoo!',
221 'yahooysmcm',
222 'ymobactus',
223 'yats',
224 'yeti',
225 'zeerch'
226 )) . ',i',
227 (string)$_SERVER['HTTP_USER_AGENT'])
228 );
229 }
230 if (!defined('_IS_BOT_FRIEND')){
231 define('_IS_BOT_FRIEND',
232 isset($_SERVER['HTTP_USER_AGENT'])
233 and preg_match(
234 ',' . implode('|', array(
235 'facebookexternalhit',
236 'twitterbot',
237 'flipboardproxy',
238 'wordpress'
239 )) . ',i',
240 (string)$_SERVER['HTTP_USER_AGENT']
241 )
242 );
243 }
244
245 /*
246 * Interdit de passer une variable id_article (ou id_xxx) qui ne
247 * soit pas numérique (ce qui bloque l'exploitation de divers trous
248 * de sécurité, dont celui de toutes les versions < 1.8.2f)
249 * (sauf pour id_table, qui n'est pas numérique jusqu'à [5743])
250 * (id_base est une variable de la config des widgets de WordPress)
251 */
252 $_exceptions = array('id_table','id_base','id_parent','id_article_pdf');
253 foreach ($_GET as $var => $val)
254 if ($_GET[$var] and strncmp($var, "id_", 3) == 0
255 and !in_array($var, $_exceptions))
256 $_GET[$var] = is_array($_GET[$var])?@array_map('intval', $_GET[$var]):intval($_GET[$var]);
257 foreach ($_POST as $var => $val)
258 if ($_POST[$var] and strncmp($var, "id_", 3) == 0
259 and !in_array($var, $_exceptions))
260 $_POST[$var] = is_array($_POST[$var])?@array_map('intval', $_POST[$var]):intval($_POST[$var]);
261 foreach ($GLOBALS as $var => $val)
262 if ($GLOBALS[$var] and strncmp($var, "id_", 3) == 0
263 and !in_array($var, $_exceptions))
264 $GLOBALS[$var] = is_array($GLOBALS[$var])?@array_map('intval', $GLOBALS[$var]):intval($GLOBALS[$var]);
265
266 /*
267 * Interdit la variable $cjpeg_command, qui était utilisée sans
268 * précaution dans certaines versions de dev (1.8b2 -> 1.8b5)
269 */
270 $cjpeg_command = '';
271
272 /*
273 * Contrôle de quelques variables (XSS)
274 */
275 foreach(array('lang', 'var_recherche', 'aide', 'var_lang_r', 'lang_r', 'var_ajax_ancre', 'nom_fichier') as $var) {
276 if (isset($_GET[$var]))
277 $_REQUEST[$var] = $GLOBALS[$var] = $_GET[$var] = preg_replace(',[^\w\,/#&;-]+,', ' ', (string)$_GET[$var]);
278 if (isset($_POST[$var]))
279 $_REQUEST[$var] = $GLOBALS[$var] = $_POST[$var] = preg_replace(',[^\w\,/#&;-]+,', ' ', (string)$_POST[$var]);
280 }
281
282 /*
283 * Filtre l'accès à spip_acces_doc (injection SQL en 1.8.2x)
284 */
285 if (preg_match(',^(.*/)?spip_acces_doc\.,', (string)$_SERVER['REQUEST_URI'])) {
286 $file = addslashes((string)$_GET['file']);
287 }
288
289 /*
290 * Pas d'inscription abusive
291 */
292 if (isset($_REQUEST['mode']) and isset($_REQUEST['page'])
293 and !in_array($_REQUEST['mode'], array("6forum", "1comite"))
294 and $_REQUEST['page'] == "identifiants")
295 $ecran_securite_raison = "identifiants";
296
297 /*
298 * Agenda joue à l'injection php
299 */
300 if (isset($_REQUEST['partie_cal'])
301 and $_REQUEST['partie_cal'] !== htmlentities((string)$_REQUEST['partie_cal']))
302 $ecran_securite_raison = "partie_cal";
303 if (isset($_REQUEST['echelle'])
304 and $_REQUEST['echelle'] !== htmlentities((string)$_REQUEST['echelle']))
305 $ecran_securite_raison = "echelle";
306
307 /*
308 * Espace privé
309 */
310 if (isset($_REQUEST['exec'])
311 and !preg_match(',^[\w-]+$,', (string)$_REQUEST['exec']))
312 $ecran_securite_raison = "exec";
313 if (isset($_REQUEST['cherche_auteur'])
314 and preg_match(',[<],', (string)$_REQUEST['cherche_auteur']))
315 $ecran_securite_raison = "cherche_auteur";
316 if (isset($_REQUEST['exec'])
317 and $_REQUEST['exec'] == 'auteurs'
318 and preg_match(',[<],', (string)$_REQUEST['recherche']))
319 $ecran_securite_raison = "recherche";
320 if (isset($_REQUEST['exec'])
321 and $_REQUEST['exec'] == 'info_plugin'
322 and preg_match(',[<],', (string)$_REQUEST['plugin']))
323 $ecran_securite_raison = "plugin";
324 if (isset($_REQUEST['exec'])
325 and $_REQUEST['exec'] == 'puce_statut'
326 and isset($_REQUEST['id'])
327 and !intval($_REQUEST['id']))
328 $ecran_securite_raison = "puce_statut";
329 if (isset($_REQUEST['action'])
330 and $_REQUEST['action'] == 'configurer') {
331 if (@file_exists('inc_version.php')
332 or @file_exists('ecrire/inc_version.php')) {
333 function action_configurer() {
334 include_spip('inc/autoriser');
335 if(!autoriser('configurer', _request('configuration'))) {
336 include_spip('inc/minipres');
337 echo minipres(_T('info_acces_interdit'));
338 exit;
339 }
340 require _DIR_RESTREINT.'action/configurer.php';
341 action_configurer_dist();
342 }
343 }
344 }
345 if (isset($_REQUEST['action'])
346 and $_REQUEST['action'] == 'ordonner_liens_documents'
347 and isset($_REQUEST['ordre'])
348 and is_string($_REQUEST['ordre'])){
349 $ecran_securite_raison = "ordre a la chaine";
350 }
351
352
353 /*
354 * Bloque les requêtes contenant %00 (manipulation d'include)
355 */
356 if (strpos(
357 @get_magic_quotes_gpc() ?
358 stripslashes(serialize($_REQUEST)) : serialize($_REQUEST),
359 chr(0)
360 ) !== false)
361 $ecran_securite_raison = "%00";
362
363 /*
364 * Bloque les requêtes fond=formulaire_
365 */
366 if (isset($_REQUEST['fond'])
367 and preg_match(',^formulaire_,i', $_REQUEST['fond']))
368 $ecran_securite_raison = "fond=formulaire_";
369
370 /*
371 * Bloque les requêtes du type ?GLOBALS[type_urls]=toto (bug vieux php)
372 */
373 if (isset($_REQUEST['GLOBALS']))
374 $ecran_securite_raison = "GLOBALS[GLOBALS]";
375
376 /*
377 * Bloque les requêtes des bots sur:
378 * les agenda
379 * les paginations entremélées
380 */
381 if (_IS_BOT and (
382 (isset($_REQUEST['echelle']) and isset($_REQUEST['partie_cal']) and isset($_REQUEST['type']))
383 or (strpos((string)$_SERVER['REQUEST_URI'], 'debut_') and preg_match(',[?&]debut_.*&debut_,', (string)$_SERVER['REQUEST_URI']))
384 or (isset($_REQUEST['calendrier_annee']) and strpos((string)$_SERVER['REQUEST_URI'], 'debut_') )
385 or (isset($_REQUEST['calendrier_annee']) and preg_match(',[?&]calendrier_annee=.*&calendrier_annee=,', (string)$_SERVER['REQUEST_URI']))
386 )
387 )
388 $ecran_securite_raison = "robot agenda/double pagination";
389
390 /*
391 * Bloque une vieille page de tests de CFG (<1.11)
392 * Bloque un XSS sur une page inexistante
393 */
394 if (isset($_REQUEST['page'])) {
395 if ($_REQUEST['page'] == 'test_cfg')
396 $ecran_securite_raison = "test_cfg";
397 if ($_REQUEST['page'] !== htmlspecialchars((string)$_REQUEST['page']))
398 $ecran_securite_raison = "xsspage";
399 if ($_REQUEST['page'] == '404'
400 and isset($_REQUEST['erreur']))
401 $ecran_securite_raison = "xss404";
402 }
403
404 /*
405 * XSS par array
406 */
407 foreach (array('var_login') as $var)
408 if (isset($_REQUEST[$var]) and is_array($_REQUEST[$var]))
409 $ecran_securite_raison = "xss ".$var;
410
411 /*
412 * Parade antivirale contre un cheval de troie
413 */
414 if (!function_exists('tmp_lkojfghx')) {
415 function tmp_lkojfghx() {}
416 function tmp_lkojfghx2($a = 0, $b = 0, $c = 0, $d = 0) {
417 // si jamais on est arrivé ici sur une erreur php
418 // et qu'un autre gestionnaire d'erreur est défini, l'appeller
419 if ($b && $GLOBALS['tmp_xhgfjokl'])
420 call_user_func($GLOBALS['tmp_xhgfjokl'], $a, $b, $c, $d);
421 }
422 }
423 if (isset($_POST['tmp_lkojfghx3']))
424 $ecran_securite_raison = "gumblar";
425
426 /*
427 * Outils XML mal sécurisés < 2.0.9
428 */
429 if (isset($_REQUEST['transformer_xml']))
430 $ecran_securite_raison = "transformer_xml";
431
432 /*
433 * Outils XML mal sécurisés again
434 */
435 if (isset($_REQUEST['var_url']) and $_REQUEST['var_url'] and isset($_REQUEST['exec']) and $_REQUEST['exec']=='valider_xml'){
436 $url = trim($_REQUEST['var_url']);
437 if (strncmp($url,'/',1)==0
438 or (($p=strpos($url,'..'))!==false AND strpos($url,'..',$p+3)!==false)
439 or (($p=strpos($url,'..'))!==false AND strpos($url,'IMG',$p+3)!==false)
440 or (strpos($url,'://')!==false or strpos($url,':\\')!==false)) {
441 $ecran_securite_raison = 'URL interdite pour var_url';
442 }
443 }
444
445 /*
446 * Sauvegarde mal securisée < 2.0.9
447 */
448 if (isset($_REQUEST['nom_sauvegarde'])
449 and strstr((string)$_REQUEST['nom_sauvegarde'], '/'))
450 $ecran_securite_raison = 'nom_sauvegarde manipulee';
451 if (isset($_REQUEST['znom_sauvegarde'])
452 and strstr((string)$_REQUEST['znom_sauvegarde'], '/'))
453 $ecran_securite_raison = 'znom_sauvegarde manipulee';
454
455
456 /*
457 * op permet des inclusions arbitraires ;
458 * on vérifie 'page' pour ne pas bloquer ... drupal
459 */
460 if (isset($_REQUEST['op']) and isset($_REQUEST['page'])
461 and $_REQUEST['op'] !== preg_replace('/[^\-\w]/', '', $_REQUEST['op']))
462 $ecran_securite_raison = 'op';
463
464 /*
465 * Forms & Table ne se méfiait pas assez des uploads de fichiers
466 */
467 if (count($_FILES)){
468 foreach($_FILES as $k => $v){
469 if (preg_match(',^fichier_\d+$,', $k)
470 and preg_match(',\.php,i', $v['name']))
471 unset($_FILES[$k]);
472 }
473 }
474 /*
475 * et Contact trop laxiste avec une variable externe
476 * on bloque pas le post pour eviter de perdre des donnees mais on unset la variable et c'est tout
477 */
478 if (isset($_REQUEST['pj_enregistrees_nom']) and $_REQUEST['pj_enregistrees_nom']){
479 unset($_REQUEST['pj_enregistrees_nom']);
480 unset($_GET['pj_enregistrees_nom']);
481 unset($_POST['pj_enregistrees_nom']);
482 }
483
484 /*
485 * reinstall=oui un peu trop permissif
486 */
487 if (isset($_REQUEST['reinstall'])
488 and $_REQUEST['reinstall'] == 'oui')
489 $ecran_securite_raison = 'reinstall=oui';
490
491 /*
492 * Pas d'action pendant l'install
493 */
494 if (isset($_REQUEST['exec']) and $_REQUEST['exec'] === 'install' and isset($_REQUEST['action'])) {
495 $ecran_securite_raison = 'install&action impossibles';
496 }
497
498 /*
499 * Échappement xss referer
500 */
501 if (isset($_SERVER['HTTP_REFERER']))
502 $_SERVER['HTTP_REFERER'] = strtr($_SERVER['HTTP_REFERER'], '<>"\'', '[]##');
503
504
505 /*
506 * Echappement HTTP_X_FORWARDED_HOST
507 */
508 if (isset($_SERVER['HTTP_X_FORWARDED_HOST']))
509 $_SERVER['HTTP_X_FORWARDED_HOST'] = strtr($_SERVER['HTTP_X_FORWARDED_HOST'], "<>?\"\{\}\$'` \r\n", '____________');
510
511
512 /*
513 * Pas d'erreur dans l'erreur
514 */
515 if (isset($_REQUEST['var_erreur']) and isset($_REQUEST['page']) and $_REQUEST['page'] === 'login') {
516 if (strlen($_REQUEST['var_erreur']) !== strcspn($_REQUEST['var_erreur'], '<>'))
517 $ecran_securite_raison = 'var_erreur incorrecte';
518 }
519
520
521 /*
522 * Réinjection des clés en html dans l'admin r19561
523 */
524 if (strpos($_SERVER['REQUEST_URI'], "ecrire/") !== false or isset($_REQUEST['var_memotri'])){
525 $zzzz = implode("", array_keys($_REQUEST));
526 if (strlen($zzzz) != strcspn($zzzz, '<>"\''))
527 $ecran_securite_raison = 'Cle incorrecte en $_REQUEST';
528 }
529
530 /*
531 * Injection par connect
532 */
533 if (isset($_REQUEST['connect'])
534 and
535 // cas qui permettent de sortir d'un commentaire PHP
536 (strpos($_REQUEST['connect'], "?") !== false
537 or strpos($_REQUEST['connect'], "<") !== false
538 or strpos($_REQUEST['connect'], ">") !== false
539 or strpos($_REQUEST['connect'], "\n") !== false
540 or strpos($_REQUEST['connect'], "\r") !== false)
541 ) {
542 $ecran_securite_raison = "malformed connect argument";
543 }
544
545 /*
546 * S'il y a une raison de mourir, mourons
547 */
548 if (isset($ecran_securite_raison)) {
549 header("HTTP/1.0 403 Forbidden");
550 header("Expires: Wed, 11 Jan 1984 05:00:00 GMT");
551 header("Cache-Control: no-cache, must-revalidate");
552 header("Pragma: no-cache");
553 header("Content-Type: text/html");
554 header("Connection: close");
555 die("<html><title>Error 403: Forbidden</title><body><h1>Error 403</h1><p>You are not authorized to view this page ($ecran_securite_raison)</p></body></html>");
556 }
557
558 /*
559 * Un filtre filtrer_entites securise
560 */
561 if (!function_exists('filtre_filtrer_entites_dist')) {
562 function filtre_filtrer_entites_dist($t) {
563 include_spip('inc/texte');
564 return interdire_scripts(filtrer_entites($t));
565 }
566 }
567
568
569 /*
570 * Fin sécurité
571 */
572
573
574
575 /*
576 * Bloque les bots quand le load déborde
577 */
578 if (!defined('_ECRAN_SECURITE_LOAD'))
579 define('_ECRAN_SECURITE_LOAD', 4);
580
581 if (
582 defined('_ECRAN_SECURITE_LOAD')
583 and _ECRAN_SECURITE_LOAD > 0
584 and _IS_BOT
585 and !_IS_BOT_FRIEND
586 and $_SERVER['REQUEST_METHOD'] === 'GET'
587 and (
588 (function_exists('sys_getloadavg')
589 and $load = sys_getloadavg()
590 and is_array($load)
591 and $load = array_shift($load)
592 )
593 or
594 (@is_readable('/proc/loadavg')
595 and $load = file_get_contents('/proc/loadavg')
596 and $load = floatval($load)
597 )
598 )
599 and $load > _ECRAN_SECURITE_LOAD // eviter l'evaluation suivante si de toute facon le load est inferieur a la limite
600 and rand(0, $load * $load) > _ECRAN_SECURITE_LOAD * _ECRAN_SECURITE_LOAD
601 ) {
602 //https://webmasters.stackexchange.com/questions/65674/should-i-return-a-429-or-503-status-code-to-a-bot
603 header("HTTP/1.0 429 Too Many Requests");
604 header("Retry-After: 300");
605 header("Expires: Wed, 11 Jan 1984 05:00:00 GMT");
606 header("Cache-Control: no-cache, must-revalidate");
607 header("Pragma: no-cache");
608 header("Content-Type: text/html");
609 header("Connection: close");
610 die("<html><title>Status 429: Too Many Requests</title><body><h1>Status 429</h1><p>Too Many Requests (try again soon)</p></body></html>");
611 }