一个SQL Injection漏洞在SDL流程中的闯关历险记

2022-06-02 15:31:44 浏览数 (2)

前言


众所周知,产生SQL注入漏洞的根本原因是SQL语句的拼接,如果SQL语句中的任何一部分(参数、字段名、搜索关键词、索引等)直接取自用户而未做校验,就可能存在注入漏洞。

攻击者通过构建特殊的输入作为参数传入服务器,导致原有业务逻辑中原有的SQL语句的语义被改变,或改变查询条件,或追加语句执行恶意操作,或调用存储过程等。

在公司没有实施SDL流程之前,

代码通常是这样写的(以互联网公司常用的PHP语言为例):

代码语言:javascript复制
$id=$_GET['id'];
$conn=mysql_connect($dbhost,$dbuser,$dbpassword) or die('Error: ' . mysql_error());
mysql_select_db("myDB");
$SQL="select * from myTable where id=".$id;
$result=mysql_query($SQL) or die('Error: ' . mysql_error());

很显然,大部分教科书也是类似这样编写的,将SQL指令和用户提交的参数拼接成一个字符串,然后提交查询。

开发完成后,经过简单的功能及性能测试,就直接上线了。

一般过不了多久,就会有漏洞报告过来。

但这绝对不是安全上的最佳实践。

让我们来看看实施SDL流程之后,是如何在每一个关卡拦截漏洞的。

需求阶段有将安全纳入需求的要求,但针对此例暂时略过不提;

方案/设计阶段,还没有开始编码,此漏洞暂不涉及,我们直接从开发阶段开始。

假设开发人员没有安全意识,是按照前面存在风险的拼接SQL的方法编码的,让我们来看看一个SQL注入漏洞将要如何闯过项目的各个关卡,存活到最后。

第一道关卡,开发阶段的代码审计


如果公司实施了代码审计这一工序并采购了代码审计工具,会发现代码开发上的错误,给出提示和告警,根据其提供的参考意见加以改进;

发现可能的SQL注入漏洞,查看详情

如果是人工抽检SQL语句,有可能会发现,也可能会遗漏,但鉴于程序员的开发习惯,发现一个问题点即可带出很多个同样的风险点,执行批量改进,消除部分风险。

代码审计这一道关卡通常是可选的(除非签发规定强制实施)。

如果没有这一道工序,则一个潜在的SQL注入漏洞通过了此环节。

第二道关卡,开发阶段的安全自检和复核


项目进行到开发完成,在即将转给测试人员之前,项目流程上有一个安全任务要做:安全自检。

首先,网络安全团队发布有安全开发规范(名字不一定叫这个),针对SQL注入,应该有类似如下的条款:

SQL语句应使用预编译和绑定变量的机制以实现SQL指令和参数的分离,原则上禁止拼接SQL语句,如有必须拼接的场景,应对每个参数进行合法性验证,包括整型验证、单引号的数据库转义(将单引号转换为两个单引号)以及对列名作参数的场景进行白名单检查等

开发人员在开发的时候,如果不知道这个规范,则相应的安全意识教育和培训工作有待加强(暂时问题不大,稍后会有流程上的关卡等着开发人员);如果知道这个规范,就会相应的采用安全的编码,提前规避这个风险,以PHP为例,首先看mysqli:

代码语言:javascript复制
$mysqli=new mysqli($dbhost,$dbuser,$dbpassword,"myDB");
if($stmt=$mysqli->prepare("select * from userinfo where id=?"))
{
$stmt->bind_param("i", $id); // s - string, b - blob, i - int, etc
$stmt->execute();
$stmt->bind_result($id,$name,$des);
$row=$stmt->fetch();
...
}

其次PDO:

代码语言:javascript复制
$pdo = new PDO('mysql:host=localhost;dbname=myDB;charset=utf8', $dbuser, $dbpassword);

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$st = $pdo->prepare("select * from userinfo where id =? and name = ?");
$st->bindParam(1,$id);

$st->bindParam(2,$name);
$st->execute();
$st->fetchAll();

对于PHP来说,推荐优先采用PDO,经过适当的配置即可很好的预防SQL注入;

对于Java,优先使用PreparedStatement而不是statement;

对于C#,优先使用SqlParameter对参数进行处理。

如果采用了成熟的ORM框架,一般而言,框架已经可以很好的防止SQL Injection漏洞,与自己写SQL语句相比,即提高了开发的效率,安全性方面也有较好的保障。

千万不要再自己写过滤了!!!

繁琐不说,写的往往都不好(各种绕过),还影响性能!

如果是按照这种预编译的方式,SQL指令和参数是分离的,不会再引入SQL注入漏洞。

要做安全自检,当然少不了一个Checklist模板文档,它由网络安全团队根据发布的标准、规范、策略等文件拟制而成,项目组需要逐条Check,在后面打勾(表示符合)或打叉(表示不符合)。

是否采用预编译和绑定变量的机制以实现SQL指令和参数的分离(符合 / 不符合)

在做自检的过程中,发现了不符合项(条款),一般比较容易改进的漏洞,很快项目组就自己改进了,消除了风险;暂时改进不了的,先留在那里,待评估后再议,制定改进计划或采取一定的规避措施之后接受风险。

安全自检做完之后,需要由来自网络安全团队的人员(可称之为“安全代表”)复核,网络安全代表会基于自检反馈,进行风险评估和相应的抽检,没有问题之后放行。

安全自检任务也可能跟方案评审或开发评审合在一起。

如果开发过程没有安全审核,则漏洞被带入测试环节。

第三道关卡,基于用例的安全测试


现在,项目处于测试阶段,测试人员要对应用进行安全测试了。

那么问题来了?

测试人员都不怎么懂安全,该如何进行测试呢?

这个问题好比,古代部队里的士兵基本都不会武术,更不要提什么枪法、剑法、降龙十八掌、六脉神剑等高神的武艺,他们如何击败敌人呢?

原来,打仗靠的是兵法和谋略,并不要求每个士兵都是武侠。

但是,武侠技艺中的一些简单、高效的实用技法,可以传授给士兵,提高军队整体的作战能力。

比如,豹子头林冲,担任八十万禁军教头,传授士兵一些实用的枪法,以提高军队的整体作战能力。

网络安全团队的一个重要任务,就是需要有人担任渗透测试教头的角色,建立测试用例,输出指导手册,对测试人员进行培训,把渗透测试中简单实用的测试技法传授给测试人员,让测试人员学会如何基于给定的测试用例,得出产品的某个变量在指定的用例上是否安全的结论。

就本文讨论的SQL注入漏洞,部分测试用例参考:

id=1’ id=1%27 id=2-1 id=1 and 1=1 id=1 and 1=2 id=99999999 or 1=1 id=99999999 or 1=2 id=abc’ and ‘1’=’1 id=abc’ and ‘1’=’2 id=99999999’ or ‘1’=’1 id=99999999’ or ‘1’=’2 id=�’ or 1=1-- id=�’ or 1=2-- 1%’ and ‘%’=’ 1%’ and 1=2 and ‘%’=’ 99999999%’ or ‘%’=’ 99999999%’ or ‘2’=’ orderby=id asc (结合白盒测试) orderby=id desc(结合白盒测试) 上述有单引号的测试用例,如果未发现漏洞,继续将单引号换成%27进行测试

上述用例,如果前面的用例可以确认漏洞的话,则忽略后面的用例。

测试人员发现存在漏洞之后,即可告知开发人员修改,通常来说,发现一处,即可举一反三,让开发人员一并把同类问题都给修改了。

如果没有执行安全测试,则漏洞会被带入生产环境。

流程走到这里,如果没有绕过的话,大部分风险基本可以控制住。

更多的漏洞发现,就看后面的了。

第四道关卡,上线前的渗透测试


上线前,如果是公司的明星产品(主打产品),一般需要专职的网络安全渗透测试人员参与进来。

利用渗透测试工具(如WebCruiser Web Vulnerability Scanner, SQLMAP, BurpSuite等),以及渗透测试人员日积月累的奇技淫巧,对目标系统执行各种入侵尝试。其中WebCruiser Web Vulnerability Scanner是一款高危漏洞扫描器,已通过Web漏洞扫描器评估系统WAVSEP v1.5的六类高危漏洞(SQL Injection, XSS, LFI, RFI, Redirect, Obsolete Backup)的全部测试用例。

渗透测试人员将发现的漏洞提给开发团队改进,消除这部分风险。

这时,基本可以消除绝大部分的高危漏洞。

如果是普通的产品,很可能渗透测试人员忙不过来或者并不要求执行渗透测试,则这些普通产品中的漏洞会被带入生产环境。

第五道关卡,安全部署


这时,项目流程处于发布阶段(在生产环境实施,但尚未验收)。

运维团队需要基于网络安全团队和运维团队共同制定的安全配置规范和脚本,对生产环境的操作系统、中间件等执行安全配置,如:

执行通用的安全配置脚本 关闭不需要的端口 数据库端口和运维端口禁止互联网访问 对互联网屏蔽后台管理入口 配置静态解析用户上传的资源 纳入安全防御基础设施防御范围(WAF等)

安全部署,可以在产品本身仍存在一些缺陷(漏洞)的情况下,仍能够具备基本的安全防御能力,将大部分恶意入侵者挡在企业门户之外。

比如,WAF规则:

拦截User-Agent及其它头部、URL、变量名、变量值中的正则匹配 拦截同一用户的高频访问请求

如果没有实施安全部署,则要看验收环节,验收人员有没有认真检查了。

第六道关卡,安全验收


安全验收:

确认安全部署结果 备份及恢复演练 修改各操作系统、数据库口令

安全验收是对安全部署的确认,必要的运维管理移交,回收在发布过程中可能泄露的口令,保障高可用性。

如果这一关卡被忽视,则无法确保执行了安全部署,如果产品仍带有SQL注入漏洞,且未纳入WAF保护范围,则产品面临被注入的风险。

第七道关卡,上线后的安全应急响应


应该说,这时项目已经结束了。但产品的生命周期却开始不久,运行中的产品面临来自互联网的各种威胁。对于很多中小型企业来说,前面的关卡几乎一个都没有,被入侵几乎是必然的。

如果收到来自外部的漏洞报告,则启动应急响应,执行应急防御措施和产品改进,尽可能的举一反三批量改进,降低风险,防止漏洞被利用。

总结


上述各关卡,其实就是SDL流程中的关键安全任务。这些任务执行下来,能够在很大的程度上降低产品上线后所面临的安全风险。

如果不设安全上的任何管控,对开发的产品放任不管,则只能发现一个漏洞就处理一个漏洞,处于被动挨打的局面,无法从根本上扭转安全攻防形势。

对于很多中小型企业来说,也许没有足够的实力和预算来实施这套流程,这时不妨采取开放的心态,尝试使用互联网上的SDL SaaS服务(比如Janusec SDL SaaS等)。

0 人点赞