检测到目标url存在宽字节跨站漏洞
昨晚巡检某政府网站时,检测告警平台突然弹出一条红字:目标url存在宽字节跨站漏洞。到目我盯着那条带id=1%df%27的存宽请求记录,后背有点发凉。字节这个站用的跨站是GBK编码,后台八成是漏洞PHP+MySQL的老组合,开发时偷懒没设mysql_set_charset,检测直接让参数拼进了SQL和HTML输出。到目
宽字节漏洞的存宽原理其实不复杂。攻击者在%df%27里塞了个%df,字节数据库连接层一看%df%27,跨站把0xdf和后面的漏洞0x27合并成了半个中文字符運,单引号就被“吃”掉了。检测这下原本的到目字符串边界被打破,注入点瞬间变成可控自由状态。存宽更麻烦的是,如果页面把参数直接回显在<input value="...">里,攻击者甚至不用绕SQL,直接闭合标签就能打XSS。
我赶紧翻了下那个页面的源码,果然,$_GET['id']没做任何过滤就拼进了select * from news where id='$id'。更讽刺的是,开发还专门在代码里写了addslashes()——但这对宽字节毫无用处,因为转义是在SQL语句构造之后才发生的,而数据库解析时已经把%df%27当成合法字符了。这种“假防御”比不防御更害人,至少会让你产生安全感。
修复方案其实就三行代码的事。把数据库连接改成SET NAMES 'utf8',或者用mysqli_set_charset('utf8')强制指定客户端编码,让%df%27无法被错误解析。同时把addslashes()换成mysql_real_escape_string()——这函数会考虑当前字符集,能正确识别0xdf不是普通字符。如果代码已经跑了好几年不敢大改,至少加一层iconv('gbk','utf-8',$input)做字符转换,把非法字节流过滤掉。
但真正让我恼火的是另一个细节。这个漏洞在2017年就被广泛披露过,网上随便搜都能找到几十篇分析文章。可这个系统的开发时间显示是2019年——三年后还在犯同样的错,只能说明团队根本没做安全培训,或者代码评审就是走个过场。我甚至怀疑,开发这功能的人连GBK和UTF-8的区别都没搞明白,只是从百度复制了一段连接数据库的代码,改个库名就上线了。
后来我在修复过程中顺手测了同目录下其他几个接口,发现search.php和detail.php也有类似问题。这就像你发现厨房里有一只蟑螂,打开柜子一看,里面已经有一窝了。最终我花了半天时间把所有数据库操作都改成参数化查询,顺带把页面输出统一做了htmlspecialchars()。修完后重新扫了一遍,告警消失,但我知道,下一个类似的老系统可能还在某个服务器上裸奔。
宽字节漏洞说到底是个历史遗留问题,但它暴露的是整个行业的通病——重功能、轻安全,重开发、轻维护。代码能跑就行,谁管它会不会被注入?可等到数据被人拖走、页面被人挂马,那时候再后悔,代价就不是改几行代码那么简单了。