source: spip-zone/_core_/securite/ecran_securite.php @ 108443

Last change on this file since 108443 was 108443, checked in by marcimat@…, 3 years ago
Report de r23882 : Ticket #3426, Correction de r23879, éviter un
.
File size: 12.8 KB
Line 
1<?php
2
3/*
4 * ecran_securite.php
5 * ------------------
6 */
7
8define('_ECRAN_SECURITE', '1.3.4'); // 2018-01-10
9
10/*
11 * Documentation : http://www.spip.net/fr_article4200.html
12 */
13
14/*
15 * Test utilisateur
16 */
17if (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 */
26if (!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 */
33if (!defined('_IS_BOT')){
34        define('_IS_BOT',
35                isset($_SERVER['HTTP_USER_AGENT'])
36                and preg_match(
37                // mots generiques
38                        ',bot|slurp|crawler|spider|webvac|yandex|'
39                        // MSIE 6.0 est un botnet 99,9% du temps, on traite donc ce USER_AGENT comme un bot
40                        . 'MSIE 6\.0|'
41                        // UA plus cibles
42                        . '200please|80legs|a6-indexer|aboundex|accoona|addthis|adressendeutschland|alexa|altavista|analyticsseo|archive|aspseek|baidu|begunadvertising|bingpreview|bloglines|browsershots|bubing|butterfly|changedetection|charlotte|chilkat|china|coccoc|crowsnest|dataminr|daumoa|dlweb|ec2linkfinder|estyle|ezooms|facebookexternalhit|facebookplatform|fairshare|feedfetcher|feedfetcher-google|feedly|fetch|flipboardproxy|genieo|google|grapeshot|hatena-useragent|head|hosttracker|hubspot|ia_archiver|ichiro|iltrovatore-setaccio|immediatenet|ina|infegyatlas|infohelfer|instapaper|jabse|james|kumkie|linkdex|linkfluence|linkwalker|litefinder|loadimpactpageanalyzer|luminate|lycos|lycosa|mediapartners-google|msai|najdi|netcraftsurveyagent|netestate|netseer|nuhk|panscient|parsijoo|plukkie|proximic|qirina|qualidator|rambler|readability|sbsearch|scooter|scrapy|scrubby|scrubbybloglines|shareaholic|shopwiki|sistrix|sitechecker|siteexplorer|sogou|special_archiver|speedy|spinn3r|spreadtrum|steeler|subscriber|suma|superdownloads|svenska-webbsido|teoma|thumbshots|tineye|trendiction|tweetedtimes|tweetmeme|uaslinkchecker|undrip|unwindfetchor|vedma|vkshare|vm|wch|web|webalta|webcookies|webthumbnail|wesee|wise-guys|woko|wotbox|y!j-bri|y!j-bro|y!j-brw|y!j-bsc|yahoo|yahoo!|yahooysmcm|yats|yeti|zeerch'
43                        . ',i', (string)$_SERVER['HTTP_USER_AGENT'])
44        );
45}
46if (!defined('_IS_BOT_FRIEND')){
47        define('_IS_BOT_FRIEND',
48                isset($_SERVER['HTTP_USER_AGENT'])
49                and preg_match(','
50                        // UA plus cibles
51                        . 'facebookexternalhit'
52                        . ',i', (string)$_SERVER['HTTP_USER_AGENT'])
53        );
54}
55
56/*
57 * Interdit de passer une variable id_article (ou id_xxx) qui ne
58 * soit pas numérique (ce qui bloque l'exploitation de divers trous
59 * de sécurité, dont celui de toutes les versions < 1.8.2f)
60 * (sauf pour id_table, qui n'est pas numérique jusqu'à [5743])
61 * (id_base est une variable de la config des widgets de WordPress)
62 */
63foreach ($_GET as $var => $val)
64        if ($_GET[$var] and strncmp($var, "id_", 3) == 0
65        and !in_array($var, array('id_table', 'id_base')))
66                $_GET[$var] = is_array($_GET[$var])?@array_map('intval', $_GET[$var]):intval($_GET[$var]);
67foreach ($_POST as $var => $val)
68        if ($_POST[$var] and strncmp($var, "id_", 3) == 0
69        and !in_array($var, array('id_table', 'id_base')))
70                $_POST[$var] = is_array($_POST[$var])?@array_map('intval', $_POST[$var]):intval($_POST[$var]);
71foreach ($GLOBALS as $var => $val)
72        if ($GLOBALS[$var] and strncmp($var, "id_", 3) == 0
73        and !in_array($var, array('id_table', 'id_base')))
74                $GLOBALS[$var] = is_array($GLOBALS[$var])?@array_map('intval', $GLOBALS[$var]):intval($GLOBALS[$var]);
75
76/*
77 * Interdit la variable $cjpeg_command, qui était utilisée sans
78 * précaution dans certaines versions de dev (1.8b2 -> 1.8b5)
79 */
80$cjpeg_command = '';
81
82/*
83 * Contrôle de quelques variables (XSS)
84 */
85foreach(array('lang', 'var_recherche', 'aide', 'var_lang_r', 'lang_r', 'var_ajax_ancre', 'nom_fichier') as $var) {
86        if (isset($_GET[$var]))
87                $_REQUEST[$var] = $GLOBALS[$var] = $_GET[$var] = preg_replace(',[^\w\,/#&;-]+,', ' ', (string)$_GET[$var]);
88        if (isset($_POST[$var]))
89                $_REQUEST[$var] = $GLOBALS[$var] = $_POST[$var] = preg_replace(',[^\w\,/#&;-]+,', ' ', (string)$_POST[$var]);
90}
91
92/*
93 * Filtre l'accès à spip_acces_doc (injection SQL en 1.8.2x)
94 */
95if (preg_match(',^(.*/)?spip_acces_doc\.,', (string)$_SERVER['REQUEST_URI'])) {
96        $file = addslashes((string)$_GET['file']);
97}
98
99/*
100 * Pas d'inscription abusive
101 */
102if (isset($_REQUEST['mode']) and isset($_REQUEST['page'])
103and !in_array($_REQUEST['mode'], array("6forum", "1comite"))
104and $_REQUEST['page'] == "identifiants")
105        $ecran_securite_raison = "identifiants";
106
107/*
108 * Agenda joue à l'injection php
109 */
110if (isset($_REQUEST['partie_cal'])
111and $_REQUEST['partie_cal'] !== htmlentities((string)$_REQUEST['partie_cal']))
112        $ecran_securite_raison = "partie_cal";
113if (isset($_REQUEST['echelle'])
114and $_REQUEST['echelle'] !== htmlentities((string)$_REQUEST['echelle']))
115        $ecran_securite_raison = "echelle";
116
117/*
118 * Espace privé
119 */
120if (isset($_REQUEST['exec'])
121and !preg_match(',^[\w-]+$,', (string)$_REQUEST['exec']))
122        $ecran_securite_raison = "exec";
123if (isset($_REQUEST['cherche_auteur'])
124and preg_match(',[<],', (string)$_REQUEST['cherche_auteur']))
125        $ecran_securite_raison = "cherche_auteur";
126if (isset($_REQUEST['exec'])
127and $_REQUEST['exec'] == 'auteurs'
128and preg_match(',[<],', (string)$_REQUEST['recherche']))
129        $ecran_securite_raison = "recherche";
130if (isset($_REQUEST['exec'])
131and $_REQUEST['exec'] == 'info_plugin'
132and preg_match(',[<],', (string)$_REQUEST['plugin']))
133        $ecran_securite_raison = "plugin";
134if (isset($_REQUEST['exec'])
135and $_REQUEST['exec'] == 'puce_statut'
136and isset($_REQUEST['id'])
137and !intval($_REQUEST['id']))
138        $ecran_securite_raison = "puce_statut";
139if (isset($_REQUEST['action'])
140and $_REQUEST['action'] == 'configurer') {
141        if (@file_exists('inc_version.php')
142        or @file_exists('ecrire/inc_version.php')) {
143                function action_configurer() {
144                        include_spip('inc/autoriser');
145                        if(!autoriser('configurer', _request('configuration'))) {
146                                include_spip('inc/minipres');
147                                echo minipres(_T('info_acces_interdit'));
148                                exit;
149                        }
150                        require _DIR_RESTREINT.'action/configurer.php';
151                        action_configurer_dist();
152                }
153        }
154}
155
156/*
157 * Bloque les requêtes contenant %00 (manipulation d'include)
158 */
159if (strpos(
160        @get_magic_quotes_gpc() ?
161                stripslashes(serialize($_REQUEST)) : serialize($_REQUEST),
162        chr(0)
163) !== false)
164        $ecran_securite_raison = "%00";
165
166/*
167 * Bloque les requêtes fond=formulaire_
168 */
169if (isset($_REQUEST['fond'])
170and preg_match(',^formulaire_,i', $_REQUEST['fond']))
171        $ecran_securite_raison = "fond=formulaire_";
172
173/*
174 * Bloque les requêtes du type ?GLOBALS[type_urls]=toto (bug vieux php)
175 */
176if (isset($_REQUEST['GLOBALS']))
177        $ecran_securite_raison = "GLOBALS[GLOBALS]";
178
179/*
180 * Bloque les requêtes des bots sur:
181 * les agenda
182 * les paginations entremélées
183 */
184if (_IS_BOT and (
185        (isset($_REQUEST['echelle']) and isset($_REQUEST['partie_cal']) and isset($_REQUEST['type']))
186        or (strpos((string)$_SERVER['REQUEST_URI'], 'debut_') and preg_match(',[?&]debut_.*&debut_,', (string)$_SERVER['REQUEST_URI']))
187)
188)
189        $ecran_securite_raison = "robot agenda/double pagination";
190
191/*
192 * Bloque une vieille page de tests de CFG (<1.11)
193 * Bloque un XSS sur une page inexistante
194 */
195if (isset($_REQUEST['page'])) {
196        if ($_REQUEST['page'] == 'test_cfg')
197                $ecran_securite_raison = "test_cfg";
198        if ($_REQUEST['page'] !== htmlspecialchars((string)$_REQUEST['page']))
199                $ecran_securite_raison = "xsspage";
200        if ($_REQUEST['page'] == '404'
201        and isset($_REQUEST['erreur']))
202                $ecran_securite_raison = "xss404";
203}
204
205/*
206 * XSS par array
207 */
208foreach (array('var_login') as $var)
209if (isset($_REQUEST[$var]) and is_array($_REQUEST[$var]))
210        $ecran_securite_raison = "xss ".$var;
211
212/*
213 * Parade antivirale contre un cheval de troie
214 */
215if (!function_exists('tmp_lkojfghx')) {
216        function tmp_lkojfghx() {}
217        function tmp_lkojfghx2($a = 0, $b = 0, $c = 0, $d = 0) {
218                // si jamais on est arrivé ici sur une erreur php
219                // et qu'un autre gestionnaire d'erreur est défini, l'appeller
220                if ($b && $GLOBALS['tmp_xhgfjokl'])
221                        call_user_func($GLOBALS['tmp_xhgfjokl'], $a, $b, $c, $d);
222        }
223}
224if (isset($_POST['tmp_lkojfghx3']))
225        $ecran_securite_raison = "gumblar";
226
227/*
228 * Outils XML mal sécurisés < 2.0.9
229 */
230if (isset($_REQUEST['transformer_xml']))
231        $ecran_securite_raison = "transformer_xml";
232
233/*
234 * Outils XML mal sécurisés again
235 */
236if (isset($_REQUEST['var_url']) and $_REQUEST['var_url'] and isset($_REQUEST['exec']) and $_REQUEST['exec']=='valider_xml'){
237        $url = trim($_REQUEST['var_url']);
238        if (strncmp($url,'/',1)==0
239          or (($p=strpos($url,'..'))!==false AND strpos($url,'..',$p+3)!==false)
240                or (strpos($url,'://')!==false or strpos($url,':\\')!==false)) {
241                $ecran_securite_raison = 'URL interdite pour var_url';
242        }
243}
244
245/*
246 * Sauvegarde mal securisée < 2.0.9
247 */
248if (isset($_REQUEST['nom_sauvegarde'])
249and strstr((string)$_REQUEST['nom_sauvegarde'], '/'))
250        $ecran_securite_raison = 'nom_sauvegarde manipulee';
251if (isset($_REQUEST['znom_sauvegarde'])
252and strstr((string)$_REQUEST['znom_sauvegarde'], '/'))
253        $ecran_securite_raison = 'znom_sauvegarde manipulee';
254
255
256/*
257 * op permet des inclusions arbitraires ;
258 * on vérifie 'page' pour ne pas bloquer ... drupal
259 */
260if (isset($_REQUEST['op']) and isset($_REQUEST['page'])
261and $_REQUEST['op'] !== preg_replace('/[^\-\w]/', '', $_REQUEST['op']))
262        $ecran_securite_raison = 'op';
263
264/*
265 * Forms & Table ne se méfiait pas assez des uploads de fichiers
266 */
267if (count($_FILES)){
268        foreach($_FILES as $k => $v){
269                 if (preg_match(',^fichier_\d+$,', $k)
270                 and preg_match(',\.php,i', $v['name']))
271                        unset($_FILES[$k]);
272        }
273}
274/*
275 * et Contact trop laxiste avec une variable externe
276 * on bloque pas le post pour eviter de perdre des donnees mais on unset la variable et c'est tout
277 */
278if (isset($_REQUEST['pj_enregistrees_nom']) and $_REQUEST['pj_enregistrees_nom']){
279        unset($_REQUEST['pj_enregistrees_nom']);
280        unset($_GET['pj_enregistrees_nom']);
281        unset($_POST['pj_enregistrees_nom']);
282}
283
284/*
285 * reinstall=oui un peu trop permissif
286 */
287if (isset($_REQUEST['reinstall'])
288and $_REQUEST['reinstall'] == 'oui')
289        $ecran_securite_raison = 'reinstall=oui';
290
291/*
292 * Échappement xss referer
293 */
294if (isset($_SERVER['HTTP_REFERER']))
295        $_SERVER['HTTP_REFERER'] = strtr($_SERVER['HTTP_REFERER'], '<>"\'', '[]##');
296
297
298/*
299 * Echappement HTTP_X_FORWARDED_HOST
300 */
301if (isset($_SERVER['HTTP_X_FORWARDED_HOST']))
302        $_SERVER['HTTP_X_FORWARDED_HOST'] = strtr($_SERVER['HTTP_X_FORWARDED_HOST'], "<>?\"\{\}\$'` \r\n", '____________');
303
304
305/*
306 * Réinjection des clés en html dans l'admin r19561
307 */
308if (strpos($_SERVER['REQUEST_URI'], "ecrire/") !== false){
309        $zzzz = implode("", array_keys($_REQUEST));
310        if (strlen($zzzz) != strcspn($zzzz, '<>"\''))
311                $ecran_securite_raison = 'Cle incorrecte en $_REQUEST';
312}
313
314/*
315 * Injection par connect
316 */
317if (isset($_REQUEST['connect'])
318        and
319        // cas qui permettent de sortir d'un commentaire PHP
320        (strpos($_REQUEST['connect'], "?") !== false
321         or strpos($_REQUEST['connect'], "<") !== false
322         or strpos($_REQUEST['connect'], ">") !== false
323         or strpos($_REQUEST['connect'], "\n") !== false
324         or strpos($_REQUEST['connect'], "\r") !== false)
325        ) {
326        $ecran_securite_raison = "malformed connect argument";
327}
328
329/*
330 * S'il y a une raison de mourir, mourons
331 */
332if (isset($ecran_securite_raison)) {
333        header("HTTP/1.0 403 Forbidden");
334        header("Expires: Wed, 11 Jan 1984 05:00:00 GMT");
335        header("Cache-Control: no-cache, must-revalidate");
336        header("Pragma: no-cache");
337        header("Content-Type: text/html");
338        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>");
339}
340
341/*
342 * Un filtre filtrer_entites securise
343 */
344if (!function_exists('filtre_filtrer_entites_dist')) {
345        function filtre_filtrer_entites_dist($t) {
346                include_spip('inc/texte');
347                return interdire_scripts(filtrer_entites($t));
348        }
349}
350
351
352/*
353 * Fin sécurité
354 */
355
356
357
358/*
359 * Bloque les bots quand le load déborde
360 */
361if (!defined('_ECRAN_SECURITE_LOAD'))
362        define('_ECRAN_SECURITE_LOAD', 4);
363
364if (
365        defined('_ECRAN_SECURITE_LOAD')
366        and _ECRAN_SECURITE_LOAD > 0
367        and _IS_BOT
368        and !_IS_BOT_FRIEND
369        and $_SERVER['REQUEST_METHOD'] === 'GET'
370        and (
371                (function_exists('sys_getloadavg')
372                  and $load = sys_getloadavg()
373                  and is_array($load)
374                  and $load = array_shift($load)
375                )
376                or
377                (@is_readable('/proc/loadavg')
378                  and $load = file_get_contents('/proc/loadavg')
379                  and $load = floatval($load)
380                )
381        )
382        and $load > _ECRAN_SECURITE_LOAD // eviter l'evaluation suivante si de toute facon le load est inferieur a la limite
383        and rand(0, $load * $load) > _ECRAN_SECURITE_LOAD * _ECRAN_SECURITE_LOAD
384) {
385        header("HTTP/1.0 503 Service Unavailable");
386        header("Retry-After: 300");
387        header("Expires: Wed, 11 Jan 1984 05:00:00 GMT");
388        header("Cache-Control: no-cache, must-revalidate");
389        header("Pragma: no-cache");
390        header("Content-Type: text/html");
391        die("<html><title>Status 503: Site temporarily unavailable</title><body><h1>Status 503</h1><p>Site temporarily unavailable (load average $load)</p></body></html>");
392}
Note: See TracBrowser for help on using the repository browser.