从今天开始,我会在自己的博客中写一点日常开发中遇到的有意思的事情,可能是bug,也可能是解决方案,就当给大家图一乐。。。
今天一早我的leader就找到我,说被用户发现了一个越权缺陷,由于是从别人那里接手过来的老项目,我本以为注册个拦截器做下token验证就能搞定的事情。但是真的拿到项目我自己就一愣,发现事情没有那么简单。
乙方用户本身就有一个平台,我们的项目是以iframe框架嵌在他们一个页面中,前端访问/api/canvas-run-trs接口,如下图:
此时通过redirectUser这个方法可以最终给前端返回一个redirct重定向的地址trsHome
测试人员发现,只要通过postman调用我们的/api/canvas-run-trs接口,就可以从response的header中获取location,即最终重定向页面的url,从而实现越权访问。
这个其实算是历史遗留问题了,外层系统做的是重定向方式的OAuth2,我们拿不到任何登录相关信息,包括用户名、密码、token。前端唯一能拿到的就是我们通过重定向方式以url方式带过去的参数,因此我们需要设计一个其他的方式进行验证。
经过讨论,方案分成两块:
iframe嵌入是一种快速的表示集成方法,具有以下三方面优点:
1.iframe是一个全新的独立的宿主环境,用于隔离或者访问原始接口及对象,并能够原封不动地将嵌入的网页展现出来;
2.如果有多个网页引用iframe,只需要修改iframe的内容,就可以实现调用的每一个页面内容的更改,方便快捷;
3.重载页面时不需要重载整个页面,只需要重载页面中的一个框架页。
例如,以vue工程为例,我们可以很轻松地嵌入iframe页面,轻松实现页面集成。
<template>
<div style="padding-top:10px;height:auto">
<iframe id='mainFrame' name='mainFrame' ref='mainFrame' :src="iframeUrl" target="_blank" height='1000px' frameborder="0" width="100%" ></iframe>
</div>
</template>
<script>
export default {
name: 'MyTestResource',
data() {
return{
iframeUrl: "",
}
},
created(){
this.iframeUrl = http://40. + "xxx-ev/ev/xxx/RedirectITA?toUrl=zycx&resouceCode=BBB&token=" + getLocalStorageToken()
}
iframe安全注入服务流程为,用户在本方服务页面进行登录,本方服务向统一身份认证平台验证用户服务权限并得到本方服务令牌。
当用户访问嵌入第三方服务的iframe页面时,本方服务向统一身份认证平台验证用户服务权限并得到第三方服务令牌,通过URL传送给第三方服务,第三方服务解析后向统一身份认证平台验证该用户权限令牌信息,若用户权限令牌信息正确则提供服务。
其中第三方服务对iframe 嵌入可以通过如下配置方式实现。
为了控制iframe嵌入的权限,需要在Headers里面加入X-Frame-Options字段,其有三种值可以配置。
经过测试发现X-Frame-Options 对Chrome及Edge浏览器不起作用,需要配置Content-Security-Policy: frame-ancestors 对iframe 页面嵌入进行安全控制。
X-Frame-Options及 Content-Security-Policy 可以同时配置多个白名单,具体如下:
Content-Security-Policy:
frame-ancestors http://aaa.com http://bbb.com http://ccc.com
X-Frame-Options:
ALLOW-FROM http://aaa.com,http://bbb.com,http://ccc.com
对于Nginx、apache 、IIS、tomcat 等不同的中间件有不同的配置方法,再次不再赘述,读者可以根据需要的场景进行不同配置。
我们在访问iframe 嵌入页面的时候,从本系统前端调用系统后台服务后,sendRedirect到第三方系统后台服务,对X-Frame-Options头进行了验证后,正常应该跳转到请求页面并对本系统浏览器进行内容响应。
实际使用过程中却发生了奇怪的现象,第三方服务验证X-Frame-Options跨站验证后,302 跳转到了请求页面,又302 调用了登出接口,截止302 调用了登录接口,最终为用户响应了登录页面,如下图所示。