You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

609 lines
16 KiB

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