[SPIP] ~v3.2.5-->v3.2.7
[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.13'); // 2019-12-04
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 if (isset($_REQUEST['action'])
339 and $_REQUEST['action'] == 'ordonner_liens_documents'
340 and isset($_REQUEST['ordre'])
341 and is_string($_REQUEST['ordre'])){
342 $ecran_securite_raison = "ordre a la chaine";
343 }
344
345
346 /*
347 * Bloque les requêtes contenant %00 (manipulation d'include)
348 */
349 if (strpos(
350 @get_magic_quotes_gpc() ?
351 stripslashes(serialize($_REQUEST)) : serialize($_REQUEST),
352 chr(0)
353 ) !== false)
354 $ecran_securite_raison = "%00";
355
356 /*
357 * Bloque les requêtes fond=formulaire_
358 */
359 if (isset($_REQUEST['fond'])
360 and preg_match(',^formulaire_,i', $_REQUEST['fond']))
361 $ecran_securite_raison = "fond=formulaire_";
362
363 /*
364 * Bloque les requêtes du type ?GLOBALS[type_urls]=toto (bug vieux php)
365 */
366 if (isset($_REQUEST['GLOBALS']))
367 $ecran_securite_raison = "GLOBALS[GLOBALS]";
368
369 /*
370 * Bloque les requêtes des bots sur:
371 * les agenda
372 * les paginations entremélées
373 */
374 if (_IS_BOT and (
375 (isset($_REQUEST['echelle']) and isset($_REQUEST['partie_cal']) and isset($_REQUEST['type']))
376 or (strpos((string)$_SERVER['REQUEST_URI'], 'debut_') and preg_match(',[?&]debut_.*&debut_,', (string)$_SERVER['REQUEST_URI']))
377 or (isset($_REQUEST['calendrier_annee']) and strpos((string)$_SERVER['REQUEST_URI'], 'debut_') )
378 or (isset($_REQUEST['calendrier_annee']) and preg_match(',[?&]calendrier_annee=.*&calendrier_annee=,', (string)$_SERVER['REQUEST_URI']))
379 )
380 )
381 $ecran_securite_raison = "robot agenda/double pagination";
382
383 /*
384 * Bloque une vieille page de tests de CFG (<1.11)
385 * Bloque un XSS sur une page inexistante
386 */
387 if (isset($_REQUEST['page'])) {
388 if ($_REQUEST['page'] == 'test_cfg')
389 $ecran_securite_raison = "test_cfg";
390 if ($_REQUEST['page'] !== htmlspecialchars((string)$_REQUEST['page']))
391 $ecran_securite_raison = "xsspage";
392 if ($_REQUEST['page'] == '404'
393 and isset($_REQUEST['erreur']))
394 $ecran_securite_raison = "xss404";
395 }
396
397 /*
398 * XSS par array
399 */
400 foreach (array('var_login') as $var)
401 if (isset($_REQUEST[$var]) and is_array($_REQUEST[$var]))
402 $ecran_securite_raison = "xss ".$var;
403
404 /*
405 * Parade antivirale contre un cheval de troie
406 */
407 if (!function_exists('tmp_lkojfghx')) {
408 function tmp_lkojfghx() {}
409 function tmp_lkojfghx2($a = 0, $b = 0, $c = 0, $d = 0) {
410 // si jamais on est arrivé ici sur une erreur php
411 // et qu'un autre gestionnaire d'erreur est défini, l'appeller
412 if ($b && $GLOBALS['tmp_xhgfjokl'])
413 call_user_func($GLOBALS['tmp_xhgfjokl'], $a, $b, $c, $d);
414 }
415 }
416 if (isset($_POST['tmp_lkojfghx3']))
417 $ecran_securite_raison = "gumblar";
418
419 /*
420 * Outils XML mal sécurisés < 2.0.9
421 */
422 if (isset($_REQUEST['transformer_xml']))
423 $ecran_securite_raison = "transformer_xml";
424
425 /*
426 * Outils XML mal sécurisés again
427 */
428 if (isset($_REQUEST['var_url']) and $_REQUEST['var_url'] and isset($_REQUEST['exec']) and $_REQUEST['exec']=='valider_xml'){
429 $url = trim($_REQUEST['var_url']);
430 if (strncmp($url,'/',1)==0
431 or (($p=strpos($url,'..'))!==false AND strpos($url,'..',$p+3)!==false)
432 or (($p=strpos($url,'..'))!==false AND strpos($url,'IMG',$p+3)!==false)
433 or (strpos($url,'://')!==false or strpos($url,':\\')!==false)) {
434 $ecran_securite_raison = 'URL interdite pour var_url';
435 }
436 }
437
438 /*
439 * Sauvegarde mal securisée < 2.0.9
440 */
441 if (isset($_REQUEST['nom_sauvegarde'])
442 and strstr((string)$_REQUEST['nom_sauvegarde'], '/'))
443 $ecran_securite_raison = 'nom_sauvegarde manipulee';
444 if (isset($_REQUEST['znom_sauvegarde'])
445 and strstr((string)$_REQUEST['znom_sauvegarde'], '/'))
446 $ecran_securite_raison = 'znom_sauvegarde manipulee';
447
448
449 /*
450 * op permet des inclusions arbitraires ;
451 * on vérifie 'page' pour ne pas bloquer ... drupal
452 */
453 if (isset($_REQUEST['op']) and isset($_REQUEST['page'])
454 and $_REQUEST['op'] !== preg_replace('/[^\-\w]/', '', $_REQUEST['op']))
455 $ecran_securite_raison = 'op';
456
457 /*
458 * Forms & Table ne se méfiait pas assez des uploads de fichiers
459 */
460 if (count($_FILES)){
461 foreach($_FILES as $k => $v){
462 if (preg_match(',^fichier_\d+$,', $k)
463 and preg_match(',\.php,i', $v['name']))
464 unset($_FILES[$k]);
465 }
466 }
467 /*
468 * et Contact trop laxiste avec une variable externe
469 * on bloque pas le post pour eviter de perdre des donnees mais on unset la variable et c'est tout
470 */
471 if (isset($_REQUEST['pj_enregistrees_nom']) and $_REQUEST['pj_enregistrees_nom']){
472 unset($_REQUEST['pj_enregistrees_nom']);
473 unset($_GET['pj_enregistrees_nom']);
474 unset($_POST['pj_enregistrees_nom']);
475 }
476
477 /*
478 * reinstall=oui un peu trop permissif
479 */
480 if (isset($_REQUEST['reinstall'])
481 and $_REQUEST['reinstall'] == 'oui')
482 $ecran_securite_raison = 'reinstall=oui';
483
484 /*
485 * Pas d'action pendant l'install
486 */
487 if (isset($_REQUEST['exec']) and $_REQUEST['exec'] === 'install' and isset($_REQUEST['action'])) {
488 $ecran_securite_raison = 'install&action impossibles';
489 }
490
491 /*
492 * Échappement xss referer
493 */
494 if (isset($_SERVER['HTTP_REFERER']))
495 $_SERVER['HTTP_REFERER'] = strtr($_SERVER['HTTP_REFERER'], '<>"\'', '[]##');
496
497
498 /*
499 * Echappement HTTP_X_FORWARDED_HOST
500 */
501 if (isset($_SERVER['HTTP_X_FORWARDED_HOST']))
502 $_SERVER['HTTP_X_FORWARDED_HOST'] = strtr($_SERVER['HTTP_X_FORWARDED_HOST'], "<>?\"\{\}\$'` \r\n", '____________');
503
504
505 /*
506 * Pas d'erreur dans l'erreur
507 */
508 if (isset($_REQUEST['var_erreur']) and isset($_REQUEST['page']) and $_REQUEST['page'] === 'login') {
509 if (strlen($_REQUEST['var_erreur']) !== strcspn($_REQUEST['var_erreur'], '<>'))
510 $ecran_securite_raison = 'var_erreur incorrecte';
511 }
512
513
514 /*
515 * Réinjection des clés en html dans l'admin r19561
516 */
517 if (strpos($_SERVER['REQUEST_URI'], "ecrire/") !== false or isset($_REQUEST['var_memotri'])){
518 $zzzz = implode("", array_keys($_REQUEST));
519 if (strlen($zzzz) != strcspn($zzzz, '<>"\''))
520 $ecran_securite_raison = 'Cle incorrecte en $_REQUEST';
521 }
522
523 /*
524 * Injection par connect
525 */
526 if (isset($_REQUEST['connect'])
527 and
528 // cas qui permettent de sortir d'un commentaire PHP
529 (strpos($_REQUEST['connect'], "?") !== false
530 or strpos($_REQUEST['connect'], "<") !== false
531 or strpos($_REQUEST['connect'], ">") !== false
532 or strpos($_REQUEST['connect'], "\n") !== false
533 or strpos($_REQUEST['connect'], "\r") !== false)
534 ) {
535 $ecran_securite_raison = "malformed connect argument";
536 }
537
538 /*
539 * S'il y a une raison de mourir, mourons
540 */
541 if (isset($ecran_securite_raison)) {
542 header("HTTP/1.0 403 Forbidden");
543 header("Expires: Wed, 11 Jan 1984 05:00:00 GMT");
544 header("Cache-Control: no-cache, must-revalidate");
545 header("Pragma: no-cache");
546 header("Content-Type: text/html");
547 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>");
548 }
549
550 /*
551 * Un filtre filtrer_entites securise
552 */
553 if (!function_exists('filtre_filtrer_entites_dist')) {
554 function filtre_filtrer_entites_dist($t) {
555 include_spip('inc/texte');
556 return interdire_scripts(filtrer_entites($t));
557 }
558 }
559
560
561 /*
562 * Fin sécurité
563 */
564
565
566
567 /*
568 * Bloque les bots quand le load déborde
569 */
570 if (!defined('_ECRAN_SECURITE_LOAD'))
571 define('_ECRAN_SECURITE_LOAD', 4);
572
573 if (
574 defined('_ECRAN_SECURITE_LOAD')
575 and _ECRAN_SECURITE_LOAD > 0
576 and _IS_BOT
577 and !_IS_BOT_FRIEND
578 and $_SERVER['REQUEST_METHOD'] === 'GET'
579 and (
580 (function_exists('sys_getloadavg')
581 and $load = sys_getloadavg()
582 and is_array($load)
583 and $load = array_shift($load)
584 )
585 or
586 (@is_readable('/proc/loadavg')
587 and $load = file_get_contents('/proc/loadavg')
588 and $load = floatval($load)
589 )
590 )
591 and $load > _ECRAN_SECURITE_LOAD // eviter l'evaluation suivante si de toute facon le load est inferieur a la limite
592 and rand(0, $load * $load) > _ECRAN_SECURITE_LOAD * _ECRAN_SECURITE_LOAD
593 ) {
594 //https://webmasters.stackexchange.com/questions/65674/should-i-return-a-429-or-503-status-code-to-a-bot
595 header("HTTP/1.0 429 Too Many Requests");
596 header("Retry-After: 300");
597 header("Expires: Wed, 11 Jan 1984 05:00:00 GMT");
598 header("Cache-Control: no-cache, must-revalidate");
599 header("Pragma: no-cache");
600 header("Content-Type: text/html");
601 die("<html><title>Status 429: Too Many Requests</title><body><h1>Status 429</h1><p>Too Many Requests (try again soon)</p></body></html>");
602 }