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