一、先搞懂为啥要让DAST跟别的安全工具搭伙
很多做开发或者运维的朋友,可能都用过DAST(动态应用安全测试)工具,就是那种对着已经跑起来的网站、APP或者接口,模拟黑客攻击找漏洞的工具。比如你刚上线一个电商的支付接口,用DAST扫一遍,能发现有没有SQL注入、跨站脚本这些危险问题。但你有没有过这种情况:DAST扫出一堆漏洞,你改的时候发现根本不知道这个漏洞是哪段代码写的?或者改完之后,这个漏洞是不是真的没了,DAST再扫一遍还是出不来结果?还有,DAST扫的时候经常误报,比如它觉得某个接口返回的错误信息太详细是漏洞,但其实这个错误信息是专门给内部调试用的,外部根本拿不到,你还得花半天时间去核实。
这些问题的根源,其实就是DAST没跟别的安全工具配合好。单独用DAST,就像一个只盯着小区大门的保安,不知道小区里哪栋楼哪户住的人有问题,也不知道大门外的监控有没有拍到可疑人员,更不知道保安自己看错了没有。只有让DAST跟其他工具搭伙,才能把安全检查的环节串成一条完整的线,从代码写完到上线,再到运行中,每一步的安全问题都能搞清楚、改到位。
二、DAST该跟哪些工具搭伙?怎么搭?
接下来咱们说具体的搭配组合,每个组合都给你讲清楚怎么操作,还有实际的例子。所有例子统一用Python技术栈,因为Python用的人多,大家都能看懂。
2.1 跟SAST(静态应用安全测试)搭伙:从“不知道漏洞在哪”到“直接定位代码”
SAST是啥?就是对着没跑起来的代码,扫描里面的漏洞。比如你写的Python代码里有一段SQL查询没做参数化,直接把用户传的内容拼进去,SAST扫代码的时候就能发现。但SAST扫出来的漏洞,可能在实际运行的环境里根本不会触发,比如这段代码是测试用的,上线的时候根本不会跑。而DAST扫出来的漏洞,又不知道是哪段代码写的。把这俩搭起来,就能解决这个问题。
举个具体的例子:假设你用Python写了一个用户登录接口,用Flask框架,代码大概是这样的:
from flask import Flask, request, jsonify
import pymysql
app = Flask(__name__)
# 连接数据库
db = pymysql.connect(host='localhost', user='root', password='123456', db='test_db')
cursor = db.cursor()
@app.route('/login', methods=['POST'])
def login():
# 接收用户传的用户名和密码
username = request.form.get('username')
password = request.form.get('password')
# 拼接SQL查询,没做参数化,这是个漏洞
sql = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"
cursor.execute(sql)
result = cursor.fetchone()
if result:
return jsonify({'code': 200, 'msg': '登录成功'})
else:
return jsonify({'code': 401, 'msg': '登录失败'})
if __name__ == '__main__':
app.run(debug=True)
首先用SAST工具扫这段代码,比如用bandit(Python常用的SAST工具),你可以在终端执行:
bandit -r your_code_path
bandit扫完会告诉你,在login函数里的SQL拼接有问题,漏洞类型是SQL注入,位置是第15行。
然后你把这个接口部署起来,用DAST工具扫,比如用OWASP ZAP(常用的开源DAST工具)。你可以在终端启动ZAP的主动扫描:
zap-baseline.py -t http://localhost:5000/login -r zap_report.html
ZAP扫完也会告诉你这个登录接口有SQL注入漏洞,还会给你一个攻击的示例,比如传一个用户名是' OR 1=1 --,密码随便填,就能绕过登录。
现在把两个工具的结果关联起来:你从ZAP的报告里知道这个接口有漏洞,从bandit的报告里知道漏洞是第15行的代码写的,直接改就行。改完之后,你可以先再用bandit扫一遍代码,确认代码层面的问题没了,再用ZAP扫一遍接口,确认实际运行的环境里漏洞也没了。这样就不用瞎找代码,也不用改完不知道有没有用。
2.2 跟SCA(软件成分分析)搭伙:从“不知道漏洞来源”到“直接找到依赖”
SCA是啥?就是扫描你项目里用的第三方依赖包有没有漏洞。比如你用Python的requests库,要是这个库的某个版本有远程代码执行漏洞,SCA就能扫出来。但SCA扫出来的漏洞,可能在你的项目里根本没用到这个库的危险功能,或者用到了但你不知道怎么修复。而DAST扫出来的漏洞,有时候是因为第三方依赖有问题导致的,你自己写的代码没问题,但用的包有问题,你根本找不到原因。
举个例子:假设你用Python写了一个上传文件的接口,用了一个叫flask-upload的第三方包,版本是0.2.0。代码大概是这样的:
from flask import Flask, request, jsonify
from flask_upload import UploadSet, configure_uploads, IMAGES
app = Flask(__name__)
# 配置上传
photos = UploadSet('photos', IMAGES)
app.config['UPLOADED_PHOTOS_DEST'] = 'uploads'
configure_uploads(app, photos)
@app.route('/upload', methods=['POST'])
def upload():
if 'photo' in request.files:
filename = photos.save(request.files['photo'])
return jsonify({'code': 200, 'msg': '上传成功', 'filename': filename})
else:
return jsonify({'code': 400, 'msg': '上传失败'})
if __name__ == '__main__':
app.run(debug=True)
首先用SCA工具扫这个项目的依赖,比如用safety(Python常用的SCA工具),执行:
safety check
safety扫完会告诉你,flask-upload 0.2.0版本有一个漏洞,CVE编号是CVE-2021-21234,这个漏洞允许攻击者上传恶意文件到服务器,比如上传一个.py文件,就能在服务器上执行代码。
然后你把这个接口部署起来,用ZAP扫,执行:
zap-baseline.py -t http://localhost:5000/upload -r zap_upload_report.html
ZAP扫完也会告诉你这个上传接口有漏洞,允许上传非图片文件,存在远程代码执行的风险。
现在把两个结果关联起来:你从ZAP的报告里知道上传接口有问题,从safety的报告里知道问题是用的flask-upload包有漏洞,直接升级这个包到最新版本就行。比如升级到0.3.0版本,执行:
pip install --upgrade flask-upload==0.3.0
升级完之后,再用safety扫一遍依赖,确认漏洞没了,再用ZAP扫一遍接口,确认漏洞也没了。这样就不用怀疑自己写的代码有问题,直接找到根源是依赖包。
2.3 跟WAF(Web应用防火墙)搭伙:从“误报多”到“精准拦截”
WAF是啥?就是架在网站前面,专门拦截黑客攻击的设备或者软件。比如你网站有SQL注入漏洞,WAF会拦截那些带' OR 1=1 --的请求,不让它到你的网站。但WAF经常会误报,比如把正常的请求当成攻击拦截了,比如用户的密码里有'这个字符,WAF就会觉得是SQL注入,把请求拦了,用户就登不上去了。
把DAST跟WAF搭起来,就能解决误报的问题。具体怎么操作呢?就是先让DAST模拟攻击你的网站,WAF会拦截这些攻击,然后你看WAF的拦截规则,哪些规则误报了,哪些漏报了。
举个例子:假设你的网站前面有一个WAF,用的是ModSecurity(开源的WAF)。首先你用ZAP扫你的网站,执行:
zap-baseline.py -t http://localhost:5000 -r zap_full_report.html
ZAP扫完会生成一个攻击报告,里面有所有模拟的攻击请求,比如SQL注入、XSS、命令执行等。然后你去看ModSecurity的日志,看哪些攻击请求被拦截了,哪些没被拦截,还有哪些正常的请求被拦截了。
比如ModSecurity的日志里有一条:
[1620000000] [/index.html] [403] [Rule "942100" (SQL Injection Attack)] [Request "POST /login HTTP/1.1"] [User-Agent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"] [Argument "username=' OR 1=1 --"]
这条日志说明WAF成功拦截了ZAP模拟的SQL注入攻击,规则942100是有效的。
但还有一条日志:
[1620000001] [/index.html] [403] [Rule "942100" (SQL Injection Attack)] [Request "POST /login HTTP/1.1"] [User-Agent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"] [Argument "username=O'Neil"]
这条日志说明WAF把一个正常的用户名O'Neil当成了SQL注入攻击,拦截了,这就是误报。
还有一条日志:
[1620000002] [/index.html] [200] [Request "POST /login HTTP/1.1"] [User-Agent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"] [Argument "username=' OR 1=1 --"]
这条日志说明ZAP模拟的另一个SQL注入攻击没被WAF拦截,这就是漏报。
现在你就可以根据ZAP的报告和WAF的日志,调整WAF的规则:把误报的规则942100加一个例外,比如允许用户名里有正常的单引号;把漏报的规则补全,比如加一个规则拦截带' OR 1=1 --的请求。调整完之后,再用ZAP扫一遍,再看WAF的日志,直到没有误报也没有漏报。
三、搭伙的好处和要注意的坑
3.1 搭伙的好处
首先是效率高,原来你改一个漏洞要找半天代码,现在直接定位;原来你要核实DAST的误报要花半天,现在跟WAF的日志一对比就知道。其次是安全更全面,原来你只盯着代码或者只盯着接口,现在从代码到依赖到上线后的拦截,全流程都覆盖了。最后是成本低,不用买新的工具,只要把原来的工具配合起来用就行。
3.2 要注意的坑
第一个坑是工具不兼容,比如你用的DAST工具导出的报告格式,SAST工具读不了,那两个结果就关联不起来。解决这个问题的办法是选支持标准格式的工具,比如OWASP ZAP、bandit、safety这些开源工具,都支持JSON格式的报告,JSON是通用的,大家都能读。
第二个坑是流程没串起来,比如你改完代码,只扫了SAST,没扫DAST,结果代码层面的问题没了,但实际运行的环境里漏洞还在。解决这个问题的办法是把工具的配合加到开发流程里,比如每次代码提交之后,先扫SAST,再扫SCA,上线之前扫DAST,上线之后再扫WAF,每一步都要过。
第三个坑是误报还是存在,比如你把DAST和SAST的结果关联起来,SAST扫出来的漏洞其实是测试代码,上线的时候不会跑,你就不用管。还有DAST扫出来的漏洞,其实是WAF已经拦截了的,你也可以先不管,但要记下来,以后改代码的时候再修复。
四、适合的场景和总结
4.1 适合的场景
第一个场景是新功能上线前的安全检查,比如你开发了一个新的支付接口,上线之前,先扫SAST找代码漏洞,再扫SCA找依赖漏洞,再扫DAST找接口漏洞,最后看WAF的拦截情况,全流程过一遍,确保没问题再上线。
第二个场景是漏洞修复后的验证,比如你发现网站有SQL注入漏洞,改完之后,先扫SAST确认代码没问题,再扫SCA确认依赖没问题,再扫DAST确认接口没问题,再看WAF确认拦截没问题,确保漏洞真的没了。
第三个场景是定期的安全巡检,比如每个月对网站做一次全面的安全检查,用DAST扫一遍接口,再跟SAST、SCA、WAF的结果对比,找出新的漏洞,或者原来的漏洞有没有复发。
4.2 总结
DAST单独用的时候,就像一个只会喊“这里有问题”的保安,不知道问题在哪,也不知道问题是不是真的,更不知道问题的根源。跟SAST、SCA、WAF搭伙之后,就变成了一个完整的安全团队:SAST找代码层面的问题,SCA找依赖层面的问题,DAST找运行层面的问题,WAF拦截攻击。这四个工具配合起来,就能把安全检查的流程串成一条线,从代码写完到上线,再到运行中,每一步的安全问题都能搞清楚、改到位、拦得住。
只要你按照咱们说的方法,把工具配合起来用,不用买新的工具,不用花太多时间,就能大大提升网站的安全性,也能减少你改漏洞的时间和精力。
Comments