Sanitize.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * This class includes various sanitization methods that can be called statically
  5. *
  6. * @package PhpMyAdmin
  7. */
  8. namespace PhpMyAdmin;
  9. use PhpMyAdmin\Core;
  10. use PhpMyAdmin\Util;
  11. /**
  12. * This class includes various sanitization methods that can be called statically
  13. *
  14. * @package PhpMyAdmin
  15. */
  16. class Sanitize
  17. {
  18. /**
  19. * Checks whether given link is valid
  20. *
  21. * @param string $url URL to check
  22. * @param boolean $http Whether to allow http links
  23. * @param boolean $other Whether to allow ftp and mailto links
  24. *
  25. * @return boolean True if string can be used as link
  26. */
  27. public static function checkLink($url, $http=false, $other=false)
  28. {
  29. $url = strtolower($url);
  30. $valid_starts = array(
  31. 'https://',
  32. './url.php?url=https%3a%2f%2f',
  33. './doc/html/',
  34. # possible return values from Util::getScriptNameForOption
  35. './index.php?',
  36. './server_databases.php?',
  37. './server_status.php?',
  38. './server_variables.php?',
  39. './server_privileges.php?',
  40. './db_structure.php?',
  41. './db_sql.php?',
  42. './db_search.php?',
  43. './db_operations.php?',
  44. './tbl_structure.php?',
  45. './tbl_sql.php?',
  46. './tbl_select.php?',
  47. './tbl_change.php?',
  48. './sql.php?',
  49. # Hardcoded options in libraries/special_schema_links.inc.php
  50. './db_events.php?',
  51. './db_routines.php?',
  52. './server_privileges.php?',
  53. './tbl_structure.php?',
  54. );
  55. $is_setup = !is_null($GLOBALS['PMA_Config']) && $GLOBALS['PMA_Config']->get('is_setup');
  56. // Adjust path to setup script location
  57. if ($is_setup) {
  58. foreach ($valid_starts as $key => $value) {
  59. if (substr($value, 0, 2) === './') {
  60. $valid_starts[$key] = '.' . $value;
  61. }
  62. }
  63. }
  64. if ($other) {
  65. $valid_starts[] = 'mailto:';
  66. $valid_starts[] = 'ftp://';
  67. }
  68. if ($http) {
  69. $valid_starts[] = 'http://';
  70. }
  71. if ($is_setup) {
  72. $valid_starts[] = '?page=form&';
  73. $valid_starts[] = '?page=servers&';
  74. }
  75. foreach ($valid_starts as $val) {
  76. if (substr($url, 0, strlen($val)) == $val) {
  77. return true;
  78. }
  79. }
  80. return false;
  81. }
  82. /**
  83. * Callback function for replacing [a@link@target] links in bb code.
  84. *
  85. * @param array $found Array of preg matches
  86. *
  87. * @return string Replaced string
  88. */
  89. public static function replaceBBLink(array $found)
  90. {
  91. /* Check for valid link */
  92. if (! self::checkLink($found[1])) {
  93. return $found[0];
  94. }
  95. /* a-z and _ allowed in target */
  96. if (! empty($found[3]) && preg_match('/[^a-z_]+/i', $found[3])) {
  97. return $found[0];
  98. }
  99. /* Construct target */
  100. $target = '';
  101. if (! empty($found[3])) {
  102. $target = ' target="' . $found[3] . '"';
  103. if ($found[3] == '_blank') {
  104. $target .= ' rel="noopener noreferrer"';
  105. }
  106. }
  107. /* Construct url */
  108. if (substr($found[1], 0, 4) == 'http') {
  109. $url = Core::linkURL($found[1]);
  110. } else {
  111. $url = $found[1];
  112. }
  113. return '<a href="' . $url . '"' . $target . '>';
  114. }
  115. /**
  116. * Callback function for replacing [doc@anchor] links in bb code.
  117. *
  118. * @param array $found Array of preg matches
  119. *
  120. * @return string Replaced string
  121. */
  122. public static function replaceDocLink(array $found)
  123. {
  124. if (count($found) >= 4) {
  125. $page = $found[1];
  126. $anchor = $found[3];
  127. } else {
  128. $anchor = $found[1];
  129. if (strncmp('faq', $anchor, 3) == 0) {
  130. $page = 'faq';
  131. } elseif (strncmp('cfg', $anchor, 3) == 0) {
  132. $page = 'config';
  133. } else {
  134. /* Guess */
  135. $page = 'setup';
  136. }
  137. }
  138. $link = Util::getDocuLink($page, $anchor);
  139. return '<a href="' . $link . '" target="documentation">';
  140. }
  141. /**
  142. * Sanitizes $message, taking into account our special codes
  143. * for formatting.
  144. *
  145. * If you want to include result in element attribute, you should escape it.
  146. *
  147. * Examples:
  148. *
  149. * <p><?php echo Sanitize::sanitize($foo); ?></p>
  150. *
  151. * <a title="<?php echo Sanitize::sanitize($foo, true); ?>">bar</a>
  152. *
  153. * @param string $message the message
  154. * @param boolean $escape whether to escape html in result
  155. * @param boolean $safe whether string is safe (can keep < and > chars)
  156. *
  157. * @return string the sanitized message
  158. */
  159. public static function sanitize($message, $escape = false, $safe = false)
  160. {
  161. if (!$safe) {
  162. $message = strtr($message, array('<' => '&lt;', '>' => '&gt;'));
  163. }
  164. /* Interpret bb code */
  165. $replace_pairs = array(
  166. '[em]' => '<em>',
  167. '[/em]' => '</em>',
  168. '[strong]' => '<strong>',
  169. '[/strong]' => '</strong>',
  170. '[code]' => '<code>',
  171. '[/code]' => '</code>',
  172. '[kbd]' => '<kbd>',
  173. '[/kbd]' => '</kbd>',
  174. '[br]' => '<br />',
  175. '[/a]' => '</a>',
  176. '[/doc]' => '</a>',
  177. '[sup]' => '<sup>',
  178. '[/sup]' => '</sup>',
  179. // used in common.inc.php:
  180. '[conferr]' => '<iframe src="show_config_errors.php"><a href="show_config_errors.php">show_config_errors.php</a></iframe>',
  181. // used in libraries/Util.php
  182. '[dochelpicon]' => Util::getImage('b_help', __('Documentation')),
  183. );
  184. $message = strtr($message, $replace_pairs);
  185. /* Match links in bb code ([a@url@target], where @target is options) */
  186. $pattern = '/\[a@([^]"@]*)(@([^]"]*))?\]/';
  187. /* Find and replace all links */
  188. $message = preg_replace_callback($pattern, function($match){
  189. return self::replaceBBLink($match);
  190. }, $message);
  191. /* Replace documentation links */
  192. $message = preg_replace_callback(
  193. '/\[doc@([a-zA-Z0-9_-]+)(@([a-zA-Z0-9_-]*))?\]/',
  194. function($match){
  195. return self::replaceDocLink($match);
  196. },
  197. $message
  198. );
  199. /* Possibly escape result */
  200. if ($escape) {
  201. $message = htmlspecialchars($message);
  202. }
  203. return $message;
  204. }
  205. /**
  206. * Sanitize a filename by removing anything besides legit characters
  207. *
  208. * Intended usecase:
  209. * When using a filename in a Content-Disposition header
  210. * the value should not contain ; or "
  211. *
  212. * When exporting, avoiding generation of an unexpected double-extension file
  213. *
  214. * @param string $filename The filename
  215. * @param boolean $replaceDots Whether to also replace dots
  216. *
  217. * @return string the sanitized filename
  218. *
  219. */
  220. public static function sanitizeFilename($filename, $replaceDots = false)
  221. {
  222. $pattern = '/[^A-Za-z0-9_';
  223. // if we don't have to replace dots
  224. if (! $replaceDots) {
  225. // then add the dot to the list of legit characters
  226. $pattern .= '.';
  227. }
  228. $pattern .= '-]/';
  229. $filename = preg_replace($pattern, '_', $filename);
  230. return $filename;
  231. }
  232. /**
  233. * Format a string so it can be a string inside JavaScript code inside an
  234. * eventhandler (onclick, onchange, on..., ).
  235. * This function is used to displays a javascript confirmation box for
  236. * "DROP/DELETE/ALTER" queries.
  237. *
  238. * @param string $a_string the string to format
  239. * @param boolean $add_backquotes whether to add backquotes to the string or not
  240. *
  241. * @return string the formatted string
  242. *
  243. * @access public
  244. */
  245. public static function jsFormat($a_string = '', $add_backquotes = true)
  246. {
  247. $a_string = htmlspecialchars($a_string);
  248. $a_string = self::escapeJsString($a_string);
  249. // Needed for inline javascript to prevent some browsers
  250. // treating it as a anchor
  251. $a_string = str_replace('#', '\\#', $a_string);
  252. return $add_backquotes
  253. ? Util::backquote($a_string)
  254. : $a_string;
  255. } // end of the 'jsFormat' function
  256. /**
  257. * escapes a string to be inserted as string a JavaScript block
  258. * enclosed by <![CDATA[ ... ]]>
  259. * this requires only to escape ' with \' and end of script block
  260. *
  261. * We also remove NUL byte as some browsers (namely MSIE) ignore it and
  262. * inserting it anywhere inside </script would allow to bypass this check.
  263. *
  264. * @param string $string the string to be escaped
  265. *
  266. * @return string the escaped string
  267. */
  268. public static function escapeJsString($string)
  269. {
  270. return preg_replace(
  271. '@</script@i', '</\' + \'script',
  272. strtr(
  273. $string,
  274. array(
  275. "\000" => '',
  276. '\\' => '\\\\',
  277. '\'' => '\\\'',
  278. '"' => '\"',
  279. "\n" => '\n',
  280. "\r" => '\r'
  281. )
  282. )
  283. );
  284. }
  285. /**
  286. * Formats a value for javascript code.
  287. *
  288. * @param string $value String to be formatted.
  289. *
  290. * @return string formatted value.
  291. */
  292. public static function formatJsVal($value)
  293. {
  294. if (is_bool($value)) {
  295. if ($value) {
  296. return 'true';
  297. }
  298. return 'false';
  299. }
  300. if (is_int($value)) {
  301. return (int)$value;
  302. }
  303. return '"' . self::escapeJsString($value) . '"';
  304. }
  305. /**
  306. * Formats an javascript assignment with proper escaping of a value
  307. * and support for assigning array of strings.
  308. *
  309. * @param string $key Name of value to set
  310. * @param mixed $value Value to set, can be either string or array of strings
  311. * @param bool $escape Whether to escape value or keep it as it is
  312. * (for inclusion of js code)
  313. *
  314. * @return string Javascript code.
  315. */
  316. public static function getJsValue($key, $value, $escape = true)
  317. {
  318. $result = $key . ' = ';
  319. if (!$escape) {
  320. $result .= $value;
  321. } elseif (is_array($value)) {
  322. $result .= '[';
  323. foreach ($value as $val) {
  324. $result .= self::formatJsVal($val) . ",";
  325. }
  326. $result .= "];\n";
  327. } else {
  328. $result .= self::formatJsVal($value) . ";\n";
  329. }
  330. return $result;
  331. }
  332. /**
  333. * Prints an javascript assignment with proper escaping of a value
  334. * and support for assigning array of strings.
  335. *
  336. * @param string $key Name of value to set
  337. * @param mixed $value Value to set, can be either string or array of strings
  338. *
  339. * @return void
  340. */
  341. public static function printJsValue($key, $value)
  342. {
  343. echo self::getJsValue($key, $value);
  344. }
  345. /**
  346. * Formats javascript assignment for form validation api
  347. * with proper escaping of a value.
  348. *
  349. * @param string $key Name of value to set
  350. * @param string $value Value to set
  351. * @param boolean $addOn Check if $.validator.format is required or not
  352. * @param boolean $comma Check if comma is required
  353. *
  354. * @return string Javascript code.
  355. */
  356. public static function getJsValueForFormValidation($key, $value, $addOn, $comma)
  357. {
  358. $result = $key . ': ';
  359. if ($addOn) {
  360. $result .= '$.validator.format(';
  361. }
  362. $result .= self::formatJsVal($value);
  363. if ($addOn) {
  364. $result .= ')';
  365. }
  366. if ($comma) {
  367. $result .= ', ';
  368. }
  369. return $result;
  370. }
  371. /**
  372. * Prints javascript assignment for form validation api
  373. * with proper escaping of a value.
  374. *
  375. * @param string $key Name of value to set
  376. * @param string $value Value to set
  377. * @param boolean $addOn Check if $.validator.format is required or not
  378. * @param boolean $comma Check if comma is required
  379. *
  380. * @return void
  381. */
  382. public static function printJsValueForFormValidation($key, $value, $addOn=false, $comma=true)
  383. {
  384. echo self::getJsValueForFormValidation($key, $value, $addOn, $comma);
  385. }
  386. /**
  387. * Removes all variables from request except whitelisted ones.
  388. *
  389. * @param string &$whitelist list of variables to allow
  390. *
  391. * @return void
  392. * @access public
  393. */
  394. public static function removeRequestVars(&$whitelist)
  395. {
  396. // do not check only $_REQUEST because it could have been overwritten
  397. // and use type casting because the variables could have become
  398. // strings
  399. if (! isset($_REQUEST)) {
  400. $_REQUEST = array();
  401. }
  402. if (! isset($_GET)) {
  403. $_GET = array();
  404. }
  405. if (! isset($_POST)) {
  406. $_POST = array();
  407. }
  408. if (! isset($_COOKIE)) {
  409. $_COOKIE = array();
  410. }
  411. $keys = array_keys(
  412. array_merge((array)$_REQUEST, (array)$_GET, (array)$_POST, (array)$_COOKIE)
  413. );
  414. foreach ($keys as $key) {
  415. if (! in_array($key, $whitelist)) {
  416. unset($_REQUEST[$key], $_GET[$key], $_POST[$key]);
  417. continue;
  418. }
  419. // allowed stuff could be compromised so escape it
  420. // we require it to be a string
  421. if (isset($_REQUEST[$key]) && ! is_string($_REQUEST[$key])) {
  422. unset($_REQUEST[$key]);
  423. }
  424. if (isset($_POST[$key]) && ! is_string($_POST[$key])) {
  425. unset($_POST[$key]);
  426. }
  427. if (isset($_COOKIE[$key]) && ! is_string($_COOKIE[$key])) {
  428. unset($_COOKIE[$key]);
  429. }
  430. if (isset($_GET[$key]) && ! is_string($_GET[$key])) {
  431. unset($_GET[$key]);
  432. }
  433. }
  434. }
  435. }