前言
前段时间在项目的安全审计中,有发现了一个关于后端开发中,使用 aws sdk 的一个安全隐患,就是后端的一些项目中,有用到了 aws 的一些功能,包含 s3,ses,dynamodb 等,而这些功能用的 key 都是同一个 key, 都是属于在 aws 后台创建的 develop 用户。
这个 develop 用户在 aws 后台具有以下的策略权限(policy):
- dynamodb 功能的所有权限 –>
AmazonDynamoDBFullAccess
- s3 功能的所有权限 –>
AmazonS3FullAccess
- ses 功能的所有权限 –
AmazonSESFullAccess
因为这个 develop 用户拥有的权限太大了, 一旦 key+secret 泄露了, 黑客就可以通过类似于 aws-cli 的命令行工具来做一些不好的事情,比如:
- 往 s3 上的 bucket 随意上传某些木马文件
- 修改 s3 bucket 的 acl 权限,将所有的 bucket 都改成公开桶,直接暴露在外网,或者给自己插入一个另一个账号的登录后门
- 直接使用 ses 发送垃圾邮件,然后受到投诉过多之后,就会可能导致这个 aws 账号被封禁
- 删除 dynamodb 的记录或者表
因此我们必须在后端开发中,根据具体项目所使用到的 aws 的功能,尽可能做最小化的权限分配,并且要设置请求的 ip 白名单,这样子一来,哪怕不小心这个用户的 key+secret 泄露了,我们也不用担心会造成太大的影响。
当然最重要的是,这些用于后端开发的 key,一定要定时更换,最好是 3 个月或者 6 个月替换一次,这样子可以更加的安全。
接下来我们做一下将原先的 develop 用户的权限,拆分开来,拆分成这 3 个用户来替换:
develop_uc
-> 承载 s3 的上传下载 (正常公开桶的下载不需要特意设置权限,但是如果是未公开桶的下载,尤其是有过期时间的那种,是需要走 key+secret 的签名然后生成下载链接的)develop_ses
-> 承载邮件的发送develop_dynamodb
-> 承载 dynamodb 相关功能
然后最好各自都要设置 ip 白名单的策略,防止一旦真的泄露的,也可以让黑客无法访问
接下来各自实践一下这个过程,并且通过 aws-cli 来模拟接口行为。
接入 develop_ses
用户
开始我们直接在 aws 的后台创建一个叫 develop_ses
的用户,并且只赋予 ses 的策略权限 (这个策略是本身 aws 有提供的,不需要我们自己建)
接下来我们就可以用 aws-cli 工具测试一下 (首先要先通过 aws configure
来配置这个用户的 key+secret)
1 | # 发送邮件 |
可以看到除了发送邮件可以之外, 其他的 s3 的操作,都没有权限, 这个是对的。
附加 ip 白名单策略,限制该角色的操作 ip
接下来我们在 aws 后台添加一个策略,叫做 ses_deny_ip
, 用来限制使用 ses 功能的角色的 ip 白名单
上面的白名单,并没有包含我的本机测试的外网 ip,所以就可以看到,之前可以成功发送的邮件,现在也都失败,提示没有权限
1 | $ aws ses send-email --from no-reply@example.com --to kebingzao@gmail.com --subject hello --text "hello zach4" |
所以最后赋予这个 develop_ses
用户的策略就是这两个:
这两个策略,一个是权限策略,表示这个角色只能用 ses 的功能, 一个是限制策略, 表示只有 ip 白名单内的 ip 登录这个用户,才能使用
接入 develop_dynamodb
用户
跟之前的做法一样, 我们一样建了 develop_dynamodb
用户,然后并赋予了两个策略,一个表示这个角色只能使用 dynamodb 的功能, 一个是表示只有 ip 白名单内的 ip 登录这个用户,才能使用
类似的情况,上面的 develop_ses
其实已经有描述了,这边不再赘述, 贴出使用 aws-cli 的测试结果
1 | # 没有 s3 bucket 的索引权限 |
添加的 ip 白名单的策略:
1 | { |
接入 develop_uc
用户
这个用户主要用于承担某些 s3 bucket 的上传下载行为,因此我们要做到最小化的策略管控,不能直接给 s3 的所有权限,用到几个 bucket,我们就给几个 bucket,用到哪些 api,我们就只给这几个 api, 多一个都不行。
通过 AWS Policy Generator, 我们可以很快生成这个策略:
1 | { |
他的这个 resource 可以非常细,甚至可以定位到某一个 bucket 的某一个目录,或者某一个 object 对象,只不过对于本例来说,只给这个 bucket 就够了
action 方面,一般我们就是用于上传和生成受限制的下载链接,所以上述这几个 api 其实就够了,一些很危险的比如
put-bucket-acl
这种,绝对不能给
接下来试一下策略结果:
1 | # 上传一个对象,并且设置 acl 策略 |
添加 ip 白名单会有问题
刚开始也是一样给这个用户添加了一个 ip 白名单的策略,上面只有我们线上和测试服的服务器的外网 ip。
但是在测试过程发现了一个问题,就是我们的接口会通过 aws 的签名算法下发给客户端的 auth token,然后让客户端自己去上传,这样子一来可以节省服务端的资源,另一方面因为云存储我们国内走 七牛,国外走 s3, 所以客户端可以根据他们的地区自己选择要上传的方式。
但是发现客户端在得到服务端下发的 auth token 之后,然后再调用 aws 的 sdk 上传之后,会报 403 的错误
为此我还写了一个 demo 来测试, 这个 demo 去请求我们服务器的 auth 接口,然后直接走 post 表单上传:
1 | var requestLib = require('request'); |
结果因为我的本机出口不在 ip 白名单内,导致虽然拿到了后端返回的 auth info,依然没办法上传到 s3 的 bucket 上,会报 403 (Access Denied) 的错误。
但是一旦将我的本机 ip 加入到 ip 白名单策略中,就可以正常上传成功。
后面查了一下,发现我们这种使用 key+secret 的调用是基于 IAM 的方式进行调用的,客户端基于签名实际上也是基于 aksk 加服务的签名4算法进行的,本质上依然是基于 IAM 进行调用的,所以会受到 ip 白名单的策略限制。
所以如果是基于服务端下发 upload token,然后让客户端自己去进行上传的,那么就没办法应用 ip 白名单的策略方式,不过有两种解决方式可供参考:
- 一个是继续走 IAM 授权, 但是在 aws sdk 的调用上,要分成服务器上传的 aws 用户角色(也就是本例的
develop_uc
),和下发签名给客户端的用户角色 (develop_auth
)。 服务器上传的用户角色要设置 ip 白名单, 下发给客户端的用户角色不能设置 ip 白名单,但是因为只有少数几个 bucket 需要允许客户端上传,因此可以指定更具体的 policy 权限策略。 - 第二种方式就是将角色跟 aws 的 EC2 绑定,这样子就不需要走 key+secret 的方式操作了,因为只有关联角色的EC2实例,可以执行角色的权限(也不需要白名单)。好处就是不用担心 key 泄露,因为执行权限的时候,不需要走 key+secret 校验, 坏处就是耦合性太强, 不利于横向扩展和跨平台。
关于 aws 的用户和角色 这两个概念
在 aws 后台中,用户和角色 其实是两个概念, 本次我们创建的这三个都是属于 aws 的用户。
aws 的用户和角色都可以赋予各种各样的策略(Policy), 他们的不同之处在于,用户是可以设置 key+secret 来提供给外部的程序作为 sdk 的权限初始化来使用的,也就是我们现在后端用的这种方式,初始化 sdk 的时候,用的就是这个用户的 key+secret, 这个用户本身所赋予的策略,决定了他可以在 sdk 中做到什么事情。
而角色他是没有生成 key+secret 供外部程序调用的,他一般是用于 aws 各个服务间的调用存在的,因此也更加的安全,因为他不存在泄露 key 的情况。
举个例子,我早期写的这个文章: 项目使用 aws 的 lambda服务来生成s3的缩略图,就是通过使用 aws 的 lambda 程序,将某一个图片生成缩略图并上传到对应的 s3 bucket,这时候我程序在初始化上传程序的时候,并没有向下面那样子还要初始化 key+secret:
1 | session = boto3.session.Session(aws_access_key_id=conf.get("s3","access_key"),aws_secret_access_key=conf.get("s3","secret_access_key")) |
而是直接用:
1 | s3_client = boto3.client('s3') |
原因就是因为我这个 lambda 函数,在运行的时候,是可以指定角色的,而我指定了这个内置的角色 lambda-s3-execution-role
而这个角色所附加的权限策略是有包含 S3 的 put-object
和 put-object-acl
这两个动作的:
因此如果是 aws 内部服务相关调用的情况,那么更安全的应该直接用角色来控制权限。这样子就不会有 key 泄露的安全隐患。
总结
我们通过将 aws 的用户的策略权限根据各自的服务类型进行细分,并且通过添加 ip 白名单的策略,可以很好的防止 key+secret 泄露之后,黑客的非法调用。
参考资料:
- AWS:基于源 IP 拒绝对 AWS 的访问
- Access control list (ACL) overview
- How to Whitelist IP Addresses to Access an AWS S3 Bucket
- 安装或更新最新版本的 AWS CLI
- 通过 AWS CLI 使用高级别 (s3) 命令
- 通过 AWS CLI 使用 API 级 (s3api) 命令
- Amazon S3 存储桶生命周期操作脚本示例
- AWS CLI 所有的 s3api 的指令
- AWS CLI 所有的 s3 的指令(旧版)
- 使用 AWS CLI 发送 ses 邮件
- DynamoDB CLI Commands & Query Examples Cheat Sheet
- AWS CLI 所有的 dynamodb 指令
- dynamodb query 指令
- aws 策略模拟生成工具