0X01 前言
甲方扫描器其中一个很重要的功能重点,就是无害化,目的是尽量降低业务影响到可接受程度。
做过甲方扫描器,基本上对于反馈都有所熟悉。
“我们的服务有大量报错,请问和你们有关么”
“我们的接口昨晚请求量飙升,这是扫描器造成的么”
扫描,应该尽量无感知。
无害化,是扫描器不同于SAST/IAST的一个难点(当然IAST有部署上的性能问题,偶尔的OOM、过高的资源占用也是大问题),是生存下去的必须考虑的一个点。扫描要有产出、要有效率,而业务侧更注重安全、有无业务影响,尤为重要,毕竟谁也不想业务一出问题先来问问是不是安全部门的扫描器。
业务影响的大体场景分类,个人还是分成针对web接口扫描和主机端口扫描造成的业务影响。
0X02 web接口扫描无害化
2.1 QPS
2.1.1 为什么要控制qps
QPS(Queries-per-second),指扫描器每秒针对业务发出的请求量。
QPS不控制好,很容易随时导致业务反馈,业务经过一番辛苦排查,发现是扫描器,自然会带着情绪进行反馈;更严重者甚至会导致占用过多服务资源,导致影响正常业务(可能升级到事务报告,虽然没收到过)
常见的反馈场景分为几种:
a. 在nginx层的公司级监控中,扫描器发送的请求超过了某个接口设置的QPS阈值
b. 在web框架的监控中,请求QPS超过阈值
c. 业务自行设置的监控中,请求超过阈值
d. 业务接口本身没有超过阈值,但是后端的二次调用或多次调用(RPC或其他http服务)超过了阈值
怎么解决?
2.1.2 聚合方式控制qps的关键点
QPS控制需要确定两个点:
1 聚合方式:通过什么方式把不同的URL聚合到一个key中
为什么需要聚合?我们要控制的是对业务的接口的请求量,而不是我们看到的URL的访问量。
a 同一个path,但是参数不同,大多数情况下还是同一个接口同一个函数在处理;
b 而现在动态参数已经很普遍了,比如百度贴吧的帖子
https://tieba.baidu.com/p/1000000001
最后的十位数字不位于参数,而是位于路径中,后端读取这段路径的值来作为变量,不管数字怎么变都是同一个接口处理。
c 甚至于子域名也可能对应同一个接口
https://bj.meituan.com/meishi/
https://sh.meituan.com/meishi/
长这样多数情况下,不同的城市 北京bj、上海sh、广州gz等,都是对应到同一个美食列表页服务
如果不聚合,针对每个长得一模一样的url设置最低的1的qps,当同时对帖子进行扫描时,即使每个url每秒只发送一个请求,在同一段时间内url数量多时,对这个处理的接口发起的访问量也会变得极大。特别是在业务高峰期(早8点到晚10点业务请求量较大),接口压力本来就挺大,基于流量镜像模式的,接收到的url也很多,再做一次扫描,容易把业务打的叫苦不迭。
所以聚合理论上是将表面不一的url收束在属于它们自己的范围内,当然越接近接口越精准。
2 QPS值:对应的key能使用多少QPS、每秒能发送多少请求
QPS值,取决于这个接口的承接能力、设置报警的阈值。
值设置的高了,容易打爆业务接口;值设置的低了,扫描速率会较慢,流量大时可能消费不完。
最适合的值上限,是业务正常情况下的请求量均值或上限。
有的业务会按照一分钟请求总量来设置告警,一分钟 100-1000,也就对应着 1-17的QPS。平时访问量较低的接口,QPS为1就足以打报警。
2.1.3 不同的实现方式
a 域名聚合
同一个域名或子域名的请求不超过N(5-10)。
一刀切方式,优点是成本低、效果也挺好。
缺点是绝对平等,不管流量大小都使用同一个QPS,流量大时可能扫描不完。
和任务绑定起来适用于扫描任务多但每个任务流量不多的情况,比如多用户多任务的情况。
b 集群聚合
每一个url,根据nginx解析结果,获取url最终归属的业务集群。
结合内部监控数据,获取每个集群实时访问量,计算出每个集群的访问峰值;
使用峰值减去实时访问量(前n分钟),得到空余的可用访问量
缺点:
1 依赖于内部基建的资源
2 同一个集群下的接口,可用的访问量是基于整个集群的。比如A可用300、B可用300、C可用300,集群整体可用900,但A本身承受量只有500。这样就会出现QPS倾斜,访问都打到A,会把A打爆。
c 去重聚合
镜像模式下基本都会有一套去重程序、流量清洗服务,在去重的时候记录下每个key的实时访问量。
用去重的key作为聚合的值,QPS计算的方式和之前一样,计算每个key的峰值,再减去前n分钟(在可控范围内尽可能少)的qps,得到这个key在这一分钟的可用量。
结合路径相似度与页面相似度的去重,去重key接近于接口,效果比较理想。
2.1.4 QPS超限后的限制方式
如果插件最小任务单位已经拆分到请求级别,那么可以将请求任务丢到队列里,根据可用量来读取请求任务运行。
但插件有很多情况需要上下文关联,比如需要第一个请求获取登录凭证token、第二个请求使用该token进行后端RCE等。
这时当扫描规则发送请求时,该请求已经没有可用QPS了,怎么做限制
方法1
直接sleep,等待可用了再发送,但会导致任务hang住、占用节点。资源多无所谓。
方法2
超限后扔回重试队列中
但是下次扫描并不从头开始,而是从中断处开始继续扫描
记录每个扫描任务的每个请求hash (url + header + postBody),给插件分类(有无上下文关键);
对于没有上下文的规则,下次任务已经请求过的hash直接跳过;对于有上下文关联的规则,存储每个hash的返回,重试时已经请求过的hash,直接读取上一次的返回。
2.2 业务错误码报错
业务线会监控服务稳定性可用性,最简单的就是404/504等错误码的每分钟占比。
可用性有的会直接影响到业务的KPI与指标。
这里应该收集每一次的反馈,和内部监控平台沟通谈需求,识别到内部扫描时把这部分请求摘掉;如果是单个业务自身做的报错收集,只能靠他们自己去解决这些问题了。
这里注意扫描时要让业务溯源简单,自定义的header / ua / 出口IP等,否则溯源老半天才发现是内部扫描,业务会更生气。
2.3 脏数据
脏数据是一个无法避开不谈的问题。
镜像模式下,只过滤掉header与post,只扫描get,仍然可能会存在脏数据问题。
get理论上应该是做查询,select操作,但是业务想用来做增删改操作也无可厚非。
get中可能存在用户凭证,业务不把token放在cookie中了,而是放在get参数中、平时保存在localstorage中。这种情况带着get参数值去扫描,等于拿着用户的身份去扫描,有个信息增删改操作,就会导致用户脏数据。
再比如,业务写接口,登录界面获取手机验证码,getCode?phone=xxx&type=1,拿到这条流量进行扫描,在对type进行扫描的时候,重复发送给用户验证码,用户还以为账号是被黑了。
又或者,没有增删改等,只是单纯的查询,但因为带着用户信息,频繁访问,被反扒处置了,用户被封禁了。
方法1 清洗与打标
在2.1中,说过接口;在我们有了所有接口数据后,借用其他安全产品的能力(SAST/IAST),给接口打上标签,判断是增删改或者是查;增删改操作就小心跳过,可避免掉大部分脏数据业务影响情况。再设置参数名黑名单,过滤掉形如phone/token/tele等明显带有用户特性的参数名的值。
这种做法相对安全,覆盖率取决于SAST/IAST的能力,但也会导致这些接口的漏报
方法2 爬虫
通过爬虫,第一篇1.2.4,采用自身测试账号抓取的流量,通常脏数据问题会比较少,因为这些请求都是正常用户可以访问到发出的,也是外部扫描器会收集到的。
用户能访问到的接口,造成脏数据,说明外部的用户也可以造成,特别是外部的白帽子/黑客也会进行扫描。并不是不扫描问题就不存在,只是没暴露。
方法3 影子环境
直接将现有的DB环境、服务环境复制一份,通过nginx层转发扫描请求/压测请求到影子环境,风险极低,但成本极大(成本*2)。
或者单纯的复制DB环境,在数据库连接函数中作hook,把扫描请求的数据库链接改为影子库。但是操作起来很麻烦,开发维护成本较大,多层调用最后再到DB,和最初的请求关联起来判断是不是扫描的就很难。
而且除了常规DB mysql/mongo/redis/MQ/kafka等,还有其他的数据库甚至是本地log等,转了一部分但另一些操作没转,业务数据层方面就不一致了,只能通过反馈不断维护,比较麻烦,不太推荐。
方法4 测试环境
第一篇 1.2.6中,测试环境进行扫描。
把扫描作为一个能力开放给业务线,在业务可控的情况下,可以带cookie扫描、也不需要过滤各种参数,QA在明白风险的情况下进行扫描。
甚至可以进行流量打标,让业务自行选择是否对涉及增删改的接口进行扫描。
PS:
有的收购业务一扫系统就出问题,没有统一测试环境,没有统一上线流程可以获取代码进行接口打标,脆弱至到处都是未授权,这种情况无能为力。
2.4 扫描时间段
有的业务允许扫描,但希望扫描的时间段放在半夜,因为半夜业务访问量较低,出了脏数据或者QPS等问题,影响会比白天高峰期时小,到业务 "开门" 也有一定的缓冲时间。
2.5 限制带宽
虽然都是过滤就可以的,但是和静态文件影响扫描性能不同,这个会造成成本问题。
扫描时可能会遇到下载文件的接口,请求一次后,接口会把几十上百兆的文件内容返回给客户端。这QPS一上来,带宽蹭蹭蹭就起飞了。私有云场景可能是和云服务买带宽套餐,可能是阶梯式收费,也可能是按使用量收费。一旦超量,可能就要多花费个几百万,足以发个严重事故了。
所以流量过滤时,也要过滤掉content-length过长的、content-type属于下载类型的。
0X03 主机端口扫描无害化
3.1 内网HTTP服务报错
内网中有很多HTTP服务,对内网进行框架漏洞扫描时,可能会引起报错,影响与处理方法同2.2
3.2 指纹匹配过滤不符合端口的数据
内网有很多端口服务,端口服务有自己的报错姿势。
有业务自己写的socket服务,但是没有做数据校验,当接收到不符合格式的数据时,抛异常导致进程中断。
这样的业务,其实也算存在DOS漏洞,可以提交安全工单敦促业务修复。
也存在部分常规业务,打个比方,redis协议扫描mysql导致mysql处理时间过长hang住(可能不是这两个服务,时间长忘记了)。
这种可以通过端口指纹方式避免掉,通过给每个端口打上所属服务/框架的标签,规则设置一级指纹(URL/HOST等数据类型)、二级指纹(URL数据的框架、HOST端口的服务/框架),在任务处过滤掉不匹配不对应的,可避免数据不匹配导致的服务报错或者挂掉
3.3 业务方给定指纹
内网可能有RPC框架,接收到不符合端口的数据会报错,但是框架层面无法作数据校验、来直接过滤掉不符合的数据,因为使用方需要这些报错来判断服务是否有异常。也无法在框架层面直接判断来源IP和集群过滤掉扫描请求,这不符合框架的设计。
可以由框架提供探测指纹,端口连接后返回特定数据,标识本身服务。
3.4 限制服务同一时间的链接数
内网扫描出现的比较多的情况,是一个IP端口的服务被链接过多,导致服务异常。
一个要做的是限制同一个 IP+端口 单位的链接数,同一时间只能链接N个。
另一个,可能存在规则socket.connect后,没有close,即使任务关闭,但远程连接方没有收到关闭请求,还在等待接收数据。即使本身已经限制了链接数,还是会打爆。所以规则检测,需要判断connect后是否有close。
PS:
当然还可能会有层出不穷的报错,比如扫描弱口令 比如ssh/mysql/mssql/redis时,引起的大量报错信息与不停告警。遇到过业务方写个了mssql代理,扫描时导致DBA mssql各种报错,但是DBA不知道是哪个机器发送的请求,业务方也不知道自己的代理启动在哪里,只知道有扫描器弱口令规则的paylaod,也无法加白名单。最后把IP写进username里才定位出来。
也可能有内部业务与其他公司有服务对接,在自家内网开了个代理服务器,可映射到其他内网。扫描时检测到各种端口,其实是其他公司的内网各IP与端口。这种情况十分危险,有反馈最好先加白过滤。代理服务器可能链接到其他传统的、脆弱老旧内网,一扫过去可能就挂了,事故报告就来了。
0X04 规则无害化
4.1 场景
规则无害化由上述方式并不能很好控制。
有的友商扫描SMB (MS17-010)等漏洞的时候,没有把控好规则的危害程度...直接上了把业务打蓝屏的规则;
log4J常规payload,链接到dns,会导致log4J等待、hang住,qps过高可能会打挂服务。
4.2 靶场验证
规则扫描线上服务之前,需要设置靶场链接,经过验证后才能上线,不仅是验证规则是否有效,也是验证规则是否会对环境造成影响
4.3 插件审核机制
自动化检测一些常规规范,比如调用socket.connect后是否有close()等
再是人工审核,审核时需要审核员(可以是固定资深人员、也可以是自选另外一个同事),迭代更新的规则展示更新的部分。审核主要是依靠经验判断有无不可控的风险。