sqlfilter.class.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. <?php
  2. /**
  3. * sqlfilter - 多语言SQL注入防护过滤器
  4. *
  5. * 设计目标:
  6. * 1. 支持多语言字符(中文、阿拉伯文、俄文、韩文等)
  7. * 2. 防止SQL注入攻击
  8. * 3. 提供不同安全级别的过滤选项
  9. */
  10. class sqlfilter
  11. {
  12. /**
  13. * 方案1:基于危险字符和模式的过滤(推荐方案)
  14. *
  15. * 核心思路:
  16. * - 保留所有正常的语言字符
  17. * - 只移除明确的SQL注入威胁
  18. * - 多层防护确保安全性
  19. *
  20. * @param string $input 用户输入
  21. * @return string 过滤后的安全字符串
  22. */
  23. public function filterForSQL($input)
  24. {
  25. // 步骤1:基础验证
  26. if(!is_string($input) || empty(trim($input))) return '';
  27. // 步骤2:移除控制字符(但保留正常的空白字符)
  28. $filtered = $this->removeControlCharacters($input);
  29. // 步骤3:移除SQL注入攻击模式
  30. $filtered = $this->removeSQLInjectionPatterns($filtered);
  31. // 步骤4:处理危险的SQL字符组合
  32. $filtered = $this->removeDangerousSQLChars($filtered);
  33. // 步骤5:去除首尾的空白字符
  34. $filtered = trim($filtered);
  35. return $filtered;
  36. }
  37. /**
  38. * 方案2:Unicode属性过滤 + SQL危险字符过滤
  39. *
  40. * 适用场景:
  41. * - 需要更宽松的字符支持
  42. * - 用户可能输入各种标点符号
  43. * - 对性能要求不是特别高
  44. *
  45. * @param string $input 用户输入
  46. * @return string 过滤后的字符串
  47. */
  48. public function filterUnicodeForSQL($input)
  49. {
  50. if(!is_string($input) || empty(trim($input))) return '';
  51. // 步骤1:使用Unicode属性类保留语言字符和基本标点
  52. // \p{L} = 所有语言的字母
  53. // \p{N} = 所有数字
  54. // \p{M} = 标记字符(如重音符号)
  55. // \s = 空白字符
  56. // \.\,\!\?\-\_\(\) = 常用标点符号
  57. $filtered = preg_replace('/[^\p{L}\p{N}\p{M}\s\.\,\!\?\-\_\(\)]/u', ' ', $input);
  58. // 步骤2:移除SQL注入模式
  59. $filtered = $this->removeSQLInjectionPatterns($filtered);
  60. // 步骤3:标准化空格
  61. $filtered = preg_replace('/\s+/', ' ', $filtered);
  62. // 步骤4:去除首尾的空白字符
  63. $filtered = trim($filtered);
  64. return $filtered;
  65. }
  66. /**
  67. * 方案3:严格的SQL安全过滤
  68. *
  69. * 适用场景:
  70. * - 高安全要求的环境
  71. * - 可以接受某些特殊字符被过滤
  72. * - 关键数据操作
  73. *
  74. * @param string $input 用户输入
  75. * @return string 过滤后的字符串
  76. */
  77. public function filterStrictSQL($input)
  78. {
  79. if(!is_string($input) || empty(trim($input))) return '';
  80. // 步骤1:只保留字母、数字、最基本的标点
  81. $filtered = preg_replace('/[^\p{L}\p{N}\s\.\,\!\?\-\_]/u', '', $input);
  82. // 步骤2:移除SQL注入模式
  83. $filtered = $this->removeSQLInjectionPatterns($filtered);
  84. // 步骤3:去除首尾的空白字符
  85. $filtered = trim($filtered);
  86. return $filtered;
  87. }
  88. /**
  89. * 移除控制字符(但保留正常的空白字符)
  90. *
  91. * 控制字符可能被用于绕过安全检查或造成解析错误
  92. *
  93. * @param string $input 输入字符串
  94. * @return string 处理后的字符串
  95. */
  96. private function removeControlCharacters($input)
  97. {
  98. // 移除C0和C1控制字符
  99. // 但保留:制表符(\t)、换行符(\n)、回车符(\r)
  100. // \x00-\x08: NULL到BACKSPACE
  101. // \x0B: 垂直制表符
  102. // \x0C: 换页符
  103. // \x0E-\x1F: 其他C0控制字符
  104. // \x7F-\x9F: DEL和C1控制字符
  105. return preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]/u', '', $input);
  106. }
  107. /**
  108. * 移除明显的SQL注入攻击模式
  109. *
  110. * 这是防护的核心部分,识别并移除常见的SQL注入技术
  111. *
  112. * @param string $input 输入字符串
  113. * @return string 处理后的字符串
  114. */
  115. private function removeSQLInjectionPatterns($input)
  116. {
  117. $dangerousPatterns = [
  118. // === SQL注释攻击 ===
  119. '/--[\s\S]*$/i', // 单行注释:-- comment
  120. '/\/\*[\s\S]*?\*\//i', // 块注释:/* comment */
  121. '/#.*$/m', // MySQL特有的#注释
  122. // === 联合查询注入 ===
  123. '/\b(union\s+select)\b/i', // UNION SELECT
  124. '/\b(union\s+all\s+select)\b/i', // UNION ALL SELECT
  125. // === 数据操作注入 ===
  126. '/\b(drop\s+table)\b/i', // DROP TABLE
  127. '/\b(drop\s+database)\b/i', // DROP DATABASE
  128. '/\b(delete\s+from)\b/i', // DELETE FROM
  129. '/\b(insert\s+into)\b/i', // INSERT INTO
  130. '/\b(update\s+.*\s+set)\b/i', // UPDATE ... SET
  131. '/\b(alter\s+table)\b/i', // ALTER TABLE
  132. '/\b(create\s+table)\b/i', // CREATE TABLE
  133. '/\b(truncate\s+table)\b/i', // TRUNCATE TABLE
  134. // === 存储过程和函数执行 ===
  135. '/\b(exec\s*\()\b/i', // EXEC()
  136. '/\b(execute\s*\()\b/i', // EXECUTE()
  137. '/\b(sp_executesql)\b/i', // SQL Server存储过程
  138. '/\b(xp_cmdshell)\b/i', // SQL Server命令执行
  139. // === 文件操作注入 ===
  140. '/\b(load_file)\b/i', // MySQL LOAD_FILE()
  141. '/\b(into\s+outfile)\b/i', // INTO OUTFILE
  142. '/\b(into\s+dumpfile)\b/i', // INTO DUMPFILE
  143. // === 编码绕过尝试 ===
  144. '/\\\\x[0-9a-f]{2}/i', // 十六进制编码 \x41
  145. '/\\\\[0-7]{1,3}/i', // 八进制编码 \101
  146. '/%[0-9a-f]{2}/i', // URL编码 %41
  147. // === 其他危险模式 ===
  148. '/\b(information_schema)\b/i', // 信息模式数据库
  149. '/\b(mysql\.user)\b/i', // MySQL用户表
  150. '/\b(pg_sleep)\b/i', // PostgreSQL延时函数
  151. '/\b(waitfor\s+delay)\b/i', // SQL Server延时
  152. ];
  153. // 逐个应用模式,将匹配内容替换为空格
  154. foreach($dangerousPatterns as $pattern) $input = preg_replace($pattern, ' ', $input);
  155. return $input;
  156. }
  157. /**
  158. * 移除危险的SQL字符组合
  159. *
  160. * 处理单个字符可能无害,但组合起来就危险的情况
  161. *
  162. * @param string $input 输入字符串
  163. * @return string 处理后的字符串
  164. */
  165. private function removeDangerousSQLChars($input)
  166. {
  167. $replacements = [
  168. // === 引号相关 ===
  169. "/'+/i" => "", // 多个单引号:'''
  170. '/"+/i' => "", // 多个双引号:"""
  171. "/'+\s*or\s*'+/i" => " ", // 引号包围的OR:'or'
  172. "/'+\s*and\s*'+/i" => " ", // 引号包围的AND:'and'
  173. // === SQL语法字符 ===
  174. '/;+/' => " ", // 分号(语句分隔符)
  175. '/\|\|/' => " ", // SQL字符串连接符 ||
  176. '/`+/' => "", // 反引号(MySQL标识符)
  177. // === 注释字符组合 ===
  178. '/--+/' => " ", // 多个连字符
  179. '/\/\*|\*\//' => " ", // 注释开始/结束符
  180. // === 危险操作符 ===
  181. '/\s*=\s*[\'"]?\s*[\'"]?\s*/i' => " ", // 可能的等号注入
  182. '/\s+(or|and)\s+[\'"]?\d+[\'"]?\s*=\s*[\'"]?\d+[\'"]?/i' => " ", // 1=1类型注入
  183. ];
  184. foreach($replacements as $pattern => $replacement) $input = preg_replace($pattern, $replacement, $input);
  185. return $input;
  186. }
  187. /**
  188. * 验证过滤后的字符串是否仍包含危险特征
  189. *
  190. * 作为最后的安全检查
  191. *
  192. * @param string $input 已过滤的字符串
  193. * @return bool true表示安全,false表示仍有风险
  194. */
  195. public function validateSafety($input)
  196. {
  197. // 危险特征检查
  198. $dangerousPatterns = [
  199. '/[\'"`;]/i', // 引号和分号
  200. '/\b(select|union|drop|delete|insert|update|alter|create|exec)\b/i', // SQL关键词
  201. '/--|\*\/|\/\*/', // 注释符
  202. '/\|\|/', // 字符串连接
  203. '/\bor\s+\d+\s*=\s*\d+/i', // 1=1类型条件
  204. ];
  205. foreach($dangerousPatterns as $pattern)
  206. {
  207. if(preg_match($pattern, $input)) return false; // 发现危险特征
  208. }
  209. return true; // 通过安全检查
  210. }
  211. /**
  212. * 根据安全级别选择合适的过滤方法
  213. *
  214. * @param string $input 用户输入
  215. * @param string $securityLevel 安全级别:'high', 'medium', 'low'
  216. * @return string 过滤后的字符串
  217. */
  218. public function getRecommendedFilter($input, $securityLevel = 'medium')
  219. {
  220. switch($securityLevel)
  221. {
  222. case 'high':
  223. // 高安全级别:最严格的过滤
  224. return $this->filterStrictSQL($input);
  225. case 'low':
  226. // 低安全级别:更宽松,保留更多字符
  227. return $this->filterUnicodeForSQL($input);
  228. case 'medium':
  229. default:
  230. // 中等安全级别:平衡安全性和可用性
  231. return $this->filterForSQL($input);
  232. }
  233. }
  234. /**
  235. * 批量过滤多个输入
  236. *
  237. * @param array $inputs 输入数组
  238. * @param string $securityLevel 安全级别
  239. * @return array 过滤后的数组
  240. */
  241. public function filterMultiple($inputs, $securityLevel = 'medium')
  242. {
  243. $filtered = [];
  244. foreach($inputs as $key => $input) $filtered[$key] = $this->getRecommendedFilter($input, $securityLevel);
  245. return $filtered;
  246. }
  247. /**
  248. * 生成过滤报告(调试用)
  249. *
  250. * @param string $original 原始输入
  251. * @param string $filtered 过滤后结果
  252. * @return array 详细的过滤报告
  253. */
  254. public function generateFilterReport($original, $filtered)
  255. {
  256. return [
  257. 'original' => $original,
  258. 'filtered' => $filtered,
  259. 'length_change' => strlen($original) - strlen($filtered),
  260. 'removed_chars' => $this->getRemovedChars($original, $filtered),
  261. 'safety_check' => $this->validateSafety($filtered),
  262. 'potential_threats' => $this->identifyThreats($original)
  263. ];
  264. }
  265. /**
  266. * 识别输入中的潜在威胁(调试和日志记录用)
  267. *
  268. * @param string $input 输入字符串
  269. * @return array 发现的威胁列表
  270. */
  271. private function identifyThreats($input)
  272. {
  273. $threats = [];
  274. $threatPatterns = [
  275. 'sql_injection' => '/\b(union|select|drop|delete|insert|update)\b/i',
  276. 'comment_attack' => '/--|\*\/|\/\*/',
  277. 'quote_manipulation' => '/[\'"]{2,}/',
  278. 'statement_separator' => '/;/',
  279. 'encoding_bypass' => '/\\\\x[0-9a-f]{2}|%[0-9a-f]{2}/i'
  280. ];
  281. foreach($threatPatterns as $threat => $pattern)
  282. {
  283. if(preg_match_all($pattern, $input, $matches)) $threats[$threat] = $matches[0];
  284. }
  285. return $threats;
  286. }
  287. /**
  288. * 计算被移除的字符
  289. *
  290. * @param string $original 原始字符串
  291. * @param string $filtered 过滤后字符串
  292. * @return string 被移除的字符
  293. */
  294. private function getRemovedChars($original, $filtered)
  295. {
  296. $originalChars = mb_str_split($original, 1, 'UTF-8');
  297. $filteredChars = mb_str_split($filtered, 1, 'UTF-8');
  298. // 简化的差异检测
  299. $diff = array_diff($originalChars, $filteredChars);
  300. return implode('', array_unique($diff));
  301. }
  302. }