译者 | 陈峻
审校 | 重楼
在现代开发环境中,持续集成(CI)和持续交付(CD)管道对于向最终用户交付软件的变更已是“家常便饭”。这导致了过去在部署之后才执行安全测试的传统方法,对于每天可能要进行数十甚至数百次部署的场景而言,已不再有效。
安全测试人员为了能够轻松地自动运行和协调一系列开箱即用的安全测试工具,也往往需要一个模块化的工具链。而secureCodeBox正是这样一个基于Kubernetes(又称 K8s)的、可被用于对软件项目进行持续安全扫描的工具。它能够被集成到CI/CD管道中,自动扫描变更,并向开发人员提供测试结果,以便他们在部署之前修复软件问题。
架构
总的说来,secureCodeBox的架构如下图所示:
secureCodeBox的架构图
而且,由于Kubernetes作业可以按需启动,并只在需要时才使用资源,因此它能够在扫描活动之间节省大量资源。同时,其另一个优势在于,所有不必要的扫描工件或元数据都会在Kubernetes作业执行之间被删除。
如图所示,负责处理这一流程的主要组件便是secureCodeBox操作器(Operator)。该操作器通过为每个步骤触发相应的Kubernetes作业来协调扫描,即:从扫描活动开始,到解析结果,再到修改和持久化结果。整个过程无需依赖人工安全审查,而且具有可扩展性,可以轻松添加更多的扫描器作为新的Kubernetes对象。
部署secureCodeBox
SecureCodeBox可以使用Helm图表在Kubernetes中进行部署。毕竟Helm在K8s中不需要太多的部署工作,而且部署的对象可以通过Helm变量轻松调整。
下面,我将使用Minikube创建一个本地Kubernetes集群。这里,Minikube并不是为生产环境的用例而设计的,不过您可以用它来测试性部署在K8s中。
首先,我们使用如下命令,以 4 个CPU和2048 MB内存的方式启动Minikube:
minikube start --cpus=4 --memory=2048m
启动Minikube之后,我们便可以使用如下命令添加Helm资源库:
helm repo addsecureCodeBoxhttps://charts.securecodebox.io
接着,我们可以使用以下命令创建新的K8s命名空间。在此,我强烈建议您创建一个单独的Kubernetes命名空间,将secureCodeBox对象与其他K8s对象分离开来,尤其是在K8s中已部署了其他服务的情况下。据此,我们可以更方便地管理新创建的、专用于该平台的对象。
kubectl create namespacesecureCodeBox-system
命令成功执行后,我们就可以在新创建的名为securecodebox-system 的命名空间中,使用如下命令安装secureCodeBox了:
helm --namespacesecureCodeBox-system upgrade --installsecureCodeBox-operatorsecureCodeBox/operator
至此,secureCodeBox操作器已部署完毕,可随时进行扫描管理了。下图展示了上述各项命令:
启动Minikube并部署secureCodeBox
如下图所示,您可以通过执行kubectl get pods -nsecureCodeBox-system来验证pod是否正常运行。
在K8s中运行secureCodeBoxPods
使用secureCodeBox进行扫描
SecureCodeBox集成了许多适用于各种场景的流行扫描组件。其官方文档就提及了 20 多种扫描集成,其中包括但不限于:
SecureCodeBox的扫描集成
由于secureCodeBox中的扫描器是由ScanTypes表示的,而ScanTypes在Kubernetes中属于定制资源定义(CustomResourceDefinition),因此在运行扫描之前,我们必须通过如下Helm命令,将Semgrep安装到secureCodeBox中:
helm upgrade --install semgrepsecureCodeBox/semgrep -nsecureCodeBox-system
下图展示了该命令的截图:
安装Semgrep扫描器
安装好Semgrep扫描类型(ScanType)之后,我们就可以针对所选的存储库开始配置扫描器了。以下是Semgrep配置的示例,您可以参见其官方文档。该配置实际上是一个描述性的扫描(Scan)YAML文档,即在K8s中secureCodeBox使用的一个自定义对象。
apiVersion: "execution.securecodebox.io/v1"
kind: Scan
metadata:
name: "semgrep-vulnerable-flask-app"
spec:
# Specify aKubernetesvolume that will be shared between the scanner and the initContainer
volumes:
- name: repository
emptyDir: {}
# Mount the volume in the scan container
volumeMounts:
- mountPath: "/repo/"
name: repository
# Specify an init container to clone the repository
initContainers:
- name: "provision-git"
# Use an image that includes git
image: bitnami/git
# Mount the same volume we also use in the main container
volumeMounts:
- mountPath: "/repo/"
name: repository
# Specify the clone command and clone into the volume, mounted at /repo/
command:
- git
- clone
- "https://github.com/we45/Vulnerable-Flask-App"
- /repo/flask-app
# Parameterize the semgrep scan itself
scanType: "semgrep"
parameters:
- "-c"
- "p/ci"
- "/repo/flask-app"
在上述配置中,您可以看到,作为容器的prosision-git被用于执行git clone,以下载所选的存储库。在本例中,它克隆了Vulnerable Flask App,以便对其执行扫描。此处的Vulnerable Flask App是一个使用Python Flask框架创建的Web应用。当然,通过自定义的git参数,您也可以改变命令参数,以克隆任何选定的存储库。
在上例中,存储库被克隆到了/repo/flask-app目录中,该目录也是在provision-git和semgrep容器之间共享的Kubernetes卷。一旦存储库被克隆,Semgrep就会使用参数配置来启动扫描。这些参数,尤其是使用到的规则,都是可以被调整的。而且,由于Semgrep的规则是公开的,因此您可以在规则注册表中轻松找到。
下面,让我们将YAML文档保存为semgrep-vulnerable-flask-app.yaml,然后使用如下kubectl命令启动扫描:
kubectl apply -f semgrep-vulnerable-flask-app.yaml -nsecureCodeBox-system
该命令的输出结果如下图所示:
针对存在漏洞的Flask App运行Semgrep扫描
我使用了kubectl get scan命令来检查扫描状态。由于secureCodeBox使用K8s对象来表示扫描,因此它们的访问方式与其他K8s对象类似。
后续,您可以再次执行相同的get命令,来验证扫描是否成功完成。
如上图所述,Done状态表示扫描已完成,且包含了1项发现。
获取结果详情
在默认配置下,扫描结果不会保存在数据库中。您可以使用如下kubectl describe命令来访问扫描的输出:
kubectl describe scan semgrep-vulnerable-flask-app -nsecureCodeBox-system
在kubectl的输出中,我们可以看到如下内容:
Semgrep执行扫描后的输出
在上图中,我们可以通过“发现下载链接(Finding Download Link)”来下载报告。我据此访问了指向Kubernetes服务的URL,并收到了作为响应的、JSON格式的Semgrep报告。下面,让我们来查看这份报告:
{
...
"results": [
{
"check_id": "python.jwt.security.jwt-hardcode.jwt-python-hardcoded-secret",
"end": {
"col": 193,
"line": 184,
"offset": 6227
},
"extra": {
"engine_kind": "OSS",
"fingerprint": "221138ec7d837ea33e1e3346821e6bf50d3f21bf8600de705b7831ef2fe9139023f1f58202a21034ac06a54a67236bd9a41624ac7960e173627cb6298a92c5c6_0",
"is_ignored": false,
"lines": " auth_token = jwt.encode({'user': username, 'exp': get_exp_date(), 'nbf': datetime.datetime.utcnow(), 'iss': 'we45', 'iat': datetime.datetime.utcnow()}, app.config['SECRET_KEY_HMAC'], algorithm='HS256')",
"message": "Hardcoded JWT secret or private key is used. This is a Insufficiently Protected Credentials weakness: https://cwe.mitre.org/data/definitions/522.html Consider using an appropriate security mechanism to protect the credentials (e.g. keeping secrets in environment variables)",
"metadata": {
"category": "security",
"confidence": "HIGH",
"cwe": [
"CWE-522: Insufficiently Protected Credentials"
],
"cwe2021-top25": true,
"impact": "MEDIUM",
"license": "Commons Clause License Condition v1.0[LGPL-2.1-only]",
"likelihood": "HIGH",
"owasp": [
"A02:2017 - Broken Authentication",
"A04:2021 - Insecure Design"
],
"references": [
"https://semgrep.dev/blog/2020/hardcoded-secrets-unverified-tokens-and-other-common-jwt-mistakes/"
],
"semgrep.dev": {
"rule": {
"origin": "community",
"rule_id": "X5U8P5",
"url": "https://semgrep.dev/playground/r/l4T4vPA/python.jwt.security.jwt-hardcode.jwt-python-hardcoded-secret",
"version_id": "l4T4vPA"
}
},
...
}
在上述报告中,我们可以看到:规则python.jwt.security.jwt-hardcode.jwt-python-hardcoded-secret识别出了存储库中的一个硬编码密钥。其问题就出现在第 184行,与创建JWT令牌有关。作为令牌的密文,它使用了存储在app.config['SECRET_KEY_HMAC']变量中的值。为了弄清楚这个值到底是硬编码,还是从环境变量等中获取的,我们搜索了设置该密钥的位置,并最终确定了该变量定义的确源于硬编码。
...
app.config['SECRET_KEY_HMAC'] = 'secret'
app.config['SECRET_KEY_HMAC_2'] = 'am0r3C0mpl3xK3y'
app.secret_key = 'F12Zr47j\3yX R~X@H!jmM]Lwf/,?KT'
...
if auth_user:
auth_token = jwt.encode({'user': username, 'exp': get_exp_date(), 'nbf': datetime.datetime.utcnow(), 'iss': 'we45', 'iat': datetime.datetime.utcnow()}, app.config['SECRET_KEY_HMAC'], algorithm='HS256')
resp = Response(json.dumps({'Authenticated': True, "User": username}))
#resp.set_cookie('SESSIONID', auth_token)
resp.headers['Authorization'] = "{0}".format(auth_token)
resp.status_code = 200
resp.mimetype = 'application/json'
return resp
...
此外,为了加快漏洞报告的访问效率,我们可以使用DefectDojo作为与secureCodeBox集成的漏洞管理平台,以通过其友好的用户界面和更高效的漏洞管理,来改进流程。目前,SecureCodeBox允许我们将发现结果以持久方式传递给Azure Monitor、DefectDojo、Dependency-Track、以及ElasticSearch等服务。
小结
secureCodeBox作为一个独立的扫描器,配置起来非常简单。在上文中,我们介绍了secureCodeBox的架构、可用的扫描器、各种配置示例、以及典型用例。
在我看来,secureCodeBox既是一个非常有前途的持续安全测试方案,也是为数不多的此类开源项目之一。通过将其集成到CI/CD中,并根据代码库相关事件触发扫描,它不但提供了在Kubernetes中执行安全测试的能力,而且提供了许多开源扫描器的集成,因此覆盖了持续安全测试的大部分方面。同时,它也可以被用作一体化的扫描服务,针对代码库和已部署的应用定期执行自动化的安全评估。
此外,secureCodeBox还可以直接向CI或专用开发人员提供报告,使之能够在将代码合并到主分支之前,修复已发现的漏洞。当然,应该指出的是,由渗透测试人员进行人工安全测试的传统方法仍然十分重要,毕竟自动化可能无法检测到复杂的安全问题。
总之,如果您正在寻找一个一体化的开源扫描仪,且愿意使用Kubernetes和YAML文件去自定义扫描的话,secureCodeBox会是持续安全测试中最好、最被低估的开源项目之一。
译者介绍
陈峻(Julian Chen),51CTO社区编辑,具有十多年的IT项目实施经验,善于对内外部资源与风险实施管控,专注传播网络与信息安全知识与经验。
原文标题:Exploring secureCodeBox— An Open-Source Continuous Security Testing Solution for DevSecOps,作者:Krzysztof Pranczk