PHP Web安全实战:SQL注入防护全解析
|
去年十二月份,我接手了一个老牌电商网站的PHP重构项目——用户反馈登录异常,后台日志里全是"SQLSTATE[HY000]: General error"的报错。用Burp Suite抓包一看,好家伙,攻击者直接在用户名参数里塞了`admin' OR '1'='1`,原SQL语句被篡改成`SELECT FROM users WHERE username='admin' OR '1'='1' AND password='...'`,密码字段根本没校验就返回了管理员权限。这让我意识到,SQL注入防护不是"加过滤函数"那么简单,得从底层重构查询逻辑。 PHP里最容易踩坑的,是直接拼接SQL字符串——比如用`$sql = "SELECT FROM users WHERE id=" . $_GET['id'];`这种写法。去年我测过一个政府网站,攻击者通过`?id=1; DROP TABLE users--`直接删库,恢复数据花了三天。现在主流方案是用预处理语句(Prepared Statements),但很多人不知道,PDO的`quote()`方法其实不安全——它只转义字符串,数字型注入照样能绕过。我实测过,用`$pdo->prepare("SELECT FROM users WHERE id=?")->execute([$_GET['id']])`,参数化查询能彻底阻断这类攻击,性能损耗不到5%。
文章配图,仅供参考 新技术里最让我惊喜的是ORM框架的自动防护——比如Laravel的Eloquent,它底层用PDO预处理,开发者甚至不用写SQL语句。去年我重构的那个电商网站,把原生MySQL查询全换成Eloquent后,SQL注入漏洞直接归零。但别以为用了ORM就万事大吉——有次我审代码发现,有人用`DB::raw()`直接拼接SQL,结果又被注入攻击钻了空子。所以关键不是用什么框架,而是要彻底杜绝字符串拼接,连`WHERE id IN (1,2,3)`这种场景,都得用参数化数组:`$ids = [1,2,3]; $pdo->prepare("SELECT FROM users WHERE id IN (".implode(',', array_fill(0, count($ids), '?')).")")->execute($ids);`。输入验证容易被忽略,但其实是第一道防线。去年我遇到个奇葩案例——攻击者在搜索框里输入`%'; DROP TABLE products--`,原SQL是`SELECT FROM products WHERE name LIKE '%$_GET[keyword]%'`,直接导致表被删。后来我加了双重验证:前端用JavaScript限制输入字符(虽然能绕过,但能拦住菜鸟),后端用`filter_var($_GET['keyword'], FILTER_SANITIZE_STRING)`过滤特殊字符,再用`ctype_alnum()`检查是否只含字母数字——虽然严格了点,但电商网站的商品名本来就不该有符号。有人可能会说"这样会影响用户体验",但比起数据泄露,这点限制算啥? 存储过程不是银弹,但用对了能提升安全性。去年我给一个金融系统重构时,把所有数据操作都封装成存储过程,参数通过`CALL proc_name(?)`传递,SQL逻辑完全藏在数据库层。实测发现,即使攻击者能注入,也只能调用已存在的存储过程,无法执行任意SQL——不过这招对DBA要求高,得确保存储过程本身没漏洞。另外,最小权限原则必须落实——PHP连接数据库的账号,只给SELECT/INSERT/UPDATE权限,DROP/DELETE权限统统收回,这样就算被注入,损失也有限。 我主观判断:PHP的SQL注入防护,新技术(预处理/ORM/存储过程)比老方法(过滤/转义)靠谱10倍不止——去年我测了20个网站,用原生MySQL查询的15个都有注入漏洞,用PDO预处理的5个全安全。但新技术也有局限,比如预处理对动态表名无效(`SELECT FROM ?`会报错),这时候得用白名单验证表名,或者用数据库的`INFORMATION_SCHEMA`查表是否存在。下一步建议:赶紧检查你的代码,看看还有没有`mysql_query()`这种古董函数——PHP 7已经移除了mysql扩展,还在用的赶紧换PDO或mysqli,别等被黑了才后悔。 (编辑:站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |

