大家好!我是每天为大家分享好文的柠檬!与你一起成长~
有需要体系化黑客渗透视频教程可搜索公众号:暗网黑客
在业务安全领域,滑动验证码已经是国内继传统字符型验证码之后的标配。
众所周知,打码平台和机器学习这两种绕过验证码的方式,已经是攻击者很主流的思路,不再阐述。
本文介绍的是一个冷门的绕过思路和防御方案。
这些积累,均来自于实战之中,希望有用。
知己知彼,百战不殆。
如果不清楚攻击者的手段,又如何能制定防御方案?
漏洞名字:session参数重复校验漏洞
思路介绍:
此思路来源于一次对黑产路径的溯源复现,由于每次拖动滑块后
会发送一个Request请求数据包到服务器,服务器会验证这个Request请求数据包里携带的位移参数
来判断是否是拖动滑块到了正确的缺口位置。
而服务器接收的数据包有很多,除了你发送的,也还会有其他人发送的请求
所以需要一个session参数来作为标识。
本文中的”rid”值就是一个session标识。
其中”rid”值是加引号的,因为它只是一个参数。
针对不同的滑动验证码厂商,可能参数命名不一样。
漏洞详情:
在用户客户端完成一次正确的验证码滑动后,发送到服务器的session参数,会在服务器后端,默认隐含生成一个有效时间和一个有效次数的值。
前提条件是正确的滑动。想想这里会不会存在问题?
曾在黑盒测试中发现,有的滑动验证码厂商的后端逻辑设计存在缺陷
一个session参数的有效时间是10分钟,有效使用次数是5次。
那么如何利用呢?
这是我在风控后台的真实业务环境下,挖掘到的一条黑产绕过滑动验证码的手法。
思路剖析:
首先,触发滑动验证机制,如下图类似。
接着,滑动滑块到正确缺口位置,然后抓包。
分析数据包,寻找session参数。通过测试找到”rid”值为session参数。
这里再强调一下,不同的厂商开发的代码,可能对session参数命名不一样。
比如下图,”sessionId”值是另一家厂商的session参数,需要我们去分析判断。
每次滑动正确位移后,使用Brupsuite或者其它中间人代理工具
抓包提取数据包里的session参数(”rid”值),保存到本地。
因为服务器后端默认隐含对我们本地保存的session参数有一个有效时间和有效次数
所以我们不需要再去滑动验证码
直接在session的有效期内发送Request请求数据包到服务器即可验证成功!
上述操作,我用python编写了一个小工具使其流程化。
全自动化过程:调用打码平台滑动验证码滑块到正确位置
使用python的mitmproxy库配合正则提取rid,并写入保存到本地rid.txt。
最后黑产在实际批量注册,薅羊毛或刷赞过程中,遇到触发的滑动验证码机制
只要session在有效期内,只需使用python读取本地的rid.txt内容
调用requests库发送请求数据包,即可绕过滑动验证码。
至此,滑动验证码绕过思路剖析完成。
滑动验证码js接口XSS攻击:
众所周知的跨站脚本攻击—XSS,攻击手法可能很平常
但把常用的攻击手法用在一个不被人注意的地方,有时候会给你意想不到的效果。
在某次实战中,对一个安全公司的真实后台登录页面做黑盒测试。
首先,给到的只有一个这种后台登录页面。
对常规的地方进行一番测试后,并没有发现什么脆弱缺陷。
既是一家安全公司,安全防护做的比较高,也是意料之中的事。
在屏幕前发了很久的呆,没有思路的时候,喜欢倒退,会回到渗透测试最本质的起点,信息收集。
因为这家公司做的是业务安全,了解到这个后台是一个风控数据监测的登录后台。
风控面对的业务场景有:注册、登录、浏览,支付,活动等。
面对的威胁有:恶意爬虫、批量注册、薅羊毛、盗号撞库等。
风控策略有:限制注册登录频率、恶意IP识别、验证码等。
【恶意/正常行为】——【风控策略】——【业务场景】
风控在其中扮演者中间人的角色,无论是一个正常用户的行为还是群控设备的恶意行为
风控一方面会使用策略进行过滤行为
另一方面会将恶意/正常行为会被记录到日志中,进而在后台展示。
至此,信息收集完毕,我们整理一下思路。
我们先看一下手里拿到的测试页面,再对比分析一下上面那段信息。
我们发现这个登录页,是有滑动验证码的。
而对比上面的信息,我将红色框圈出来的文字,构建了一个我的漏洞测试想法。
如果我能控制滑动验证码的输入,那在后台的输出也可能将是可控的。
红色框圈出的最后四个字,“后台展示”,第一反应就是用XSS攻击手法再合适不过了。
开始行动
首先,找到获取滑动验证码的js接口
分析接口参数
找到以下参数:
channel,appId,orgaization,lang,data,sdkver,callback,model,reversion
黑盒XSS——FUZZ
刷新验证码,截断,抓包。
蛮力碰撞,直接把所有的参数的值替换成XSS payload,但这样往往容易失败,因为有些参数是硬编码,一旦更改,服务器返回的respnse就会直接显示reject拒绝。
舍近求远,9个参数,抓9次包,分别替换参数值成XSS payload,最后,几分钟后,成功打到了cookie。
(因为XSS平台更新,当时的记录未保存)
因为是黑盒测试,在漏洞修复后,内部人员把后台触发漏洞的位置告诉了我。
下面这张图是,风控后台的滑动验证码记录的行为信息展示栏,未修复之前这里有一列language的值,就是参数里的”lang”,而插入的XSS payload也就会出现在这个位置。
由于开发人员未考虑到这个隐秘的js接口,所以未做过滤防护,且未申明http only,导致XSS payload可以顺利执行。
最后,在黑盒测试盲打XSS中,很大一部分靠运气。
但使用极限语句再配合一个超短域名的XSS平台,会增加成功率。
滑动验证码可能会部署在:注册、登录、反爬、支付等场景当中,而黑产绕过滑动验证码的技术会有很多种,但凡只要有一种是当前风控策略未考虑的情况,就可能会造成比较严重的损失。
攻击手法总结
从黑产/攻击者的角度,针对滑动验证码,我们介绍了一种绕过的思路:session参数重复校验漏洞,一种攻击的手法:JS接口的XSS攻击。
那么,从风控/防御方的角度,我们如何制定防守方案呢?
才疏学浅,不敢无稽之谈,只能把平时实战之中碰到的问题,记录下来,希望有用。
被动防守——针对攻击者
这里没什么特色,既然是被动防守,自然是要避免亡羊补牢。
针对诸如XSS等OWASP TOP漏洞,不能依赖开发的细心。
除了在业务上线之前,内部测试和攻防测试;
还可以在在业务上线之后,托管类似国外Hackone平台的国内赏金平台,或自运营SRC。
当然,结合考虑预算成本。
主动出击——针对灰黑产
主动出击,针对的是利用滑动验证码,来精准识别灰黑产。
在一次滑动验证码更新升级过程中,发现了一个新思路。
原始过程:在用户完成一次验证码滑动后,将request请求数据包发送给服务器。
升级方案:在服务器后端升级滑动验证码的js代码,使每一个滑动验证码都在用户客户端生成一个或多个随机参数,这些随机参数需要跟随request请求发送到服务器进行一个简单逻辑验证。
重点在于:正常用户只有通过滑动滑块发送的request数据包才一定是携带随机参数的
但并不强制要求发送的request请求携带这些随机参数。
精准识别:因为核心圈的黑产下放的工具,都是通过直接通过发送request请求数据包来进行批量注册、刷量刷赞和恶意爬虫等行为。
称之为:“协议刷”或“打接口”,这种方式效率极高。加上利益化的原因,黑产不会去在乎过程,只在乎是否结果能成功。
升级的方案:只有通过正常滑动滑块,才能发送携带随机参数的request数据包发到服务器。
旧方案:通过以前的旧接口直接发送不携带随机参数的request数据包到服务器也可以通过验证。
在无声无息升级后,两种方案并行运行,那么拐点就到来了。
是不是就意味着旧方案的验证码接口过来的ip,sdk,captcha_flag等数据一定都是源于黑产池;
而升级方案的验证码接口过来的ip,sdk,captcha_flag等数据不说百分百
也绝大部分都是来自正常用户群体。这就悄然无声的就达到了精准识别灰黑产的目的。
持续化:在被黑产发现后,就需要做持续化更新的对抗了。
还是那句,攻防本身就是一场不公平的战斗,或许只要能大大增加黑产攻击者的成本,就是有效果的防守。
以上理论,皆为实战总结。希望有用。
如果没有,我想下篇或许会有。
作者:N10th九号
转载自:https://www.freebuf.com/articles/web/238038.html
片 | 名称 | 尺寸 |
bg.jpg | 552 * 344 | |
hole.png | 110 * 110 | |
slider.png | 110 * 110 |
hole.png和slider.png为png是因为图片带有透明度。
最终为前端生成两张图片:
图片 | 名称 | 尺寸 |
out_bg.jpg | 552 * 344 | |
out_slider.png | 110 * 344 |
out_slider.png高度为344与背景图等高。
也可以打开滑动验证Demo页面,F12来观察图片。
本机环境为.net 6.0.300-preview.22204.3, 装有Vscode。
创建console项目
mkdir SlideImageGenerator
cd SlideImageGenerator
dotnet new console
dotnet add package SixLabors.ImageSharp
dotnet add package SixLabors.ImageSharp.Drawing --prerelease
code .
创建Images目录, 并放入bg.jpg,hole.png,slider.png
mkdir Images
out_bg.jpg生成比较简单,直接将hole.png"叠加"到bg.jpg。hole.png灰色区域是半透明的,因此能够隐约看到背景。开始!
清空Program.cs,引入命名空间
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Drawing;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Drawing.Processing;
生成随机坐标,代表绘制凹槽的起始位置:
// 生成随机坐标
int randomX=100, randomY=120;
加载图片
using var backgroundImage=Image.Load<Rgba32>("images/bg.jpg");
using var holeTemplateImage=Image.Load<Rgba32>("images/hole.png");
using var sliderTemplateImage=Image.Load<Rgba32>("images/slider.png");
"叠加"holeTemplateImage到backgroundImage。用ImageSharp来说就是以backgroundImage为底,从位置randomX,randomY开始绘制holeTemplateImage。
backgroundImage.Mutate(x=> x.DrawImage(holeTemplateImage, new Point(randomX, randomY), 1));
backgroundImage.SaveAsJpegAsync("out_bg.jpg");
运行
dotnet run
运行后可以在目录看到out_bg.jpg
全部代码:
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Drawing;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Drawing.Processing;
// 生成随机坐标
int randomX=100, randomY=120;
// 加载图片
using var backgroundImage=Image.Load<Rgba32>("images/bg.jpg");
using var holeTemplateImage=Image.Load<Rgba32>("images/hole.png");
using var sliderTemplateImage=Image.Load<Rgba32>("images/slider.png");
// "叠加"holeTemplateImage到backgroundImage
backgroundImage.Mutate(x=> x.DrawImage(holeTemplateImage, new Point(randomX, randomY), 1));
backgroundImage.SaveAsJpegAsync("out_bg.jpg");
out_slider.png生成需要三步:
a. 从背景图中扣出凹槽区域,形成holeMattingImage。
b. 将slider.png"叠加"到抠图holeMattingImage。
c. 将b生成的图形"叠加"到一个高为344,宽为110的透明区域,最终生成out_slider.
以下具体讲解:
a步骤我直接上代码,其实就一个裁剪操作:
// backgroundImage已做修改,这里重新加载背景
using var backgroundImage2=Image.Load<Rgba32>("images/bg.jpg");
using var holeMattingImage=new Image<Rgba32>(sliderTemplateImage.Width, sliderTemplateImage.Height); // 110 * 110
// 根据透明度计算凹槽图轮廓形状(形状由不透明区域形成)
var holeShape=CalcHoleShape(holeTemplateImage);
// 生成凹槽抠图
holeMattingImage.Mutate(x=>
{
// 可以这样理解:
// 将holeShape想象成一幅110X110的图片
// p=> p.DrawImage(backgroundImage2, new Point(-randomX, -randomY), 1)则表示
// 从holeShape的-randomX, -randomY开始绘制backgroundImage2(相当于backgroundImage2左移randomX,上移randomY)
// 然后将holeShape绘制结果叠加到holeMattingImage上
x.Clip(holeShape, p=> p.DrawImage(backgroundImage2, new Point(-randomX, -randomY), 1));
});
holeMattingImage.SaveAsJpegAsync("out_holeMatting.jpg");
裁剪注意传入的负坐标,注释是我个人的理解。凹槽的形状通过CalcHoleShape实现的,原理是一行行扫描图像,每行连续不透明区域(包含半透明)形成一个或多个n*1的矩形。最后将所有小矩形组合形成一个组合形状ComplexPolygon
Func<Image<Rgba32>, ComplexPolygon> CalcHoleShape=(holeTemplateImage)=> {
int temp=0;
var pathList=new List<IPath>();
holeTemplateImage.ProcessPixelRows(accessor=>
{
for (int y=0; y < holeTemplateImage.Height; y++)
{
var rowSpan=accessor.GetRowSpan(y);
for (int x=0; x < rowSpan.Length; x++)
{
ref Rgba32 pixel=ref rowSpan[x];
if (pixel.A !=0)
{
if (temp==0)
{
temp=x;
}
}
else
{
if (temp !=0)
{
pathList.Add(new RectangularPolygon(temp, y, x - temp, 1));
temp=0;
}
}
}
}
});
return new ComplexPolygon(new PathCollection(pathList));
};
运行,形成out_holeMatting.jpg
dotnet run
b. 将slider.png"叠加"到抠图holeMattingImage,代码比较简单
// 叠加拖块模板
holeMattingImage.Mutate(x=> x.DrawImage(sliderTemplateImage, new Point(0, 0), 1));
holeMattingImage.SaveAsJpegAsync("out_holeMatting2.jpg");
运行,形成out_holeMatting2.jpg
dotnet run
c. 将out_holeMatting2叠加到"叠加"到一个高为344,宽为110的透明区域
using var sliderBarImage=new Image<Rgba32>(sliderTemplateImage.Width, backgroundImage2.Height);
// 绘制拖块条
sliderBarImage.Mutate(x=> x.DrawImage(holeMattingImage, new Point(0, randomY), 1));
sliderBarImage.SaveAsJpegAsync("out_slider.jpg");
运行,形成out_slider.jpg
dotnet run
全部代码
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Drawing;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Drawing.Processing;
// 生成随机坐标
int randomX=100, randomY=120;
// 加载图片
using var backgroundImage=Image.Load<Rgba32>("images/bg.jpg");
using var holeTemplateImage=Image.Load<Rgba32>("images/hole.png");
using var sliderTemplateImage=Image.Load<Rgba32>("images/slider.png");
// "叠加"holeTemplateImage到backgroundImage
backgroundImage.Mutate(x=> x.DrawImage(holeTemplateImage, new Point(randomX, randomY), 1));
backgroundImage.SaveAsJpegAsync("out_bg.jpg");
Func<Image<Rgba32>, ComplexPolygon> CalcHoleShape=(holeTemplateImage)=> {
int temp=0;
var pathList=new List<IPath>();
holeTemplateImage.ProcessPixelRows(accessor=>
{
for (int y=0; y < holeTemplateImage.Height; y++)
{
var rowSpan=accessor.GetRowSpan(y);
for (int x=0; x < rowSpan.Length; x++)
{
ref Rgba32 pixel=ref rowSpan[x];
if (pixel.A !=0)
{
if (temp==0)
{
temp=x;
}
}
else
{
if (temp !=0)
{
pathList.Add(new RectangularPolygon(temp, y, x - temp, 1));
temp=0;
}
}
}
}
});
return new ComplexPolygon(new PathCollection(pathList));
};
// backgroundImage已做修改,这里重新加载背景
using var backgroundImage2=Image.Load<Rgba32>("images/bg.jpg");
using var holeMattingImage=new Image<Rgba32>(sliderTemplateImage.Width, sliderTemplateImage.Height); // 110 * 110
// 根据透明度计算凹槽图轮廓形状(形状由不透明区域形成)
var holeShape=CalcHoleShape(holeTemplateImage);
// 生成凹槽抠图
holeMattingImage.Mutate(x=>
{
// 可以这样理解:
// 将holeShape想象成一幅110X110的图片
// p=> p.DrawImage(backgroundImage2, new Point(-randomX, -randomY), 1)则表示
// 从holeShape的-randomX, -randomY开始绘制backgroundImage2(相当于backgroundImage2左移randomX,上移randomY)
// 然后将holeShape绘制结果叠加到holeMattingImage上
x.Clip(holeShape, p=> p.DrawImage(backgroundImage2, new Point(-randomX, -randomY), 1));
});
holeMattingImage.SaveAsJpegAsync("out_holeMatting.jpg");
// 叠加拖块模板
holeMattingImage.Mutate(x=> x.DrawImage(sliderTemplateImage, new Point(0, 0), 1));
holeMattingImage.SaveAsJpegAsync("out_holeMatting2.jpg");
using var sliderBarImage=new Image<Rgba32>(sliderTemplateImage.Width, backgroundImage2.Height);
// 绘制拖块条
sliderBarImage.Mutate(x=> x.DrawImage(holeMattingImage, new Point(0, randomY), 1));
sliderBarImage.SaveAsJpegAsync("out_slider.jpg");
完整的滑动验证,可以参考LazySlideCaptcha。写的比较水,欢迎Star。
一款小而美的开源滑动验证码组件:打造安全与用户体验的完美平衡
---
**引言:滑动验证码的必要性与挑战**
在日益严重的网络安全威胁面前,传统的字符输入验证码已经无法完全阻挡机器人的恶意攻击。滑动验证码作为一种新型的验证方式,因其趣味性与较高的人机识别度,受到越来越多网站和应用的青睐。本文将详细介绍一款开源滑动验证码组件——`sliderCaptcha`,通过解析其实现原理与应用案例,帮助开发者轻松集成这款小而美的安全组件,提高用户体验的同时,有效防范自动化攻击。
---
**【第一部分】sliderCaptcha简介与安装**
**标题:轻巧高效,安装便捷**
`sliderCaptcha`是一款基于纯JavaScript实现的滑动验证码组件,大小轻便,依赖少,适用于各种Web前端项目。通过npm包管理器进行安装:
```bash
npm install slider-captcha --save
```
或者通过CDN引入:
```html
<script src="https://cdn.jsdelivr.net/npm/slider-captcha/dist/sliderCaptcha.min.js"></script>
```
---
**【第二部分】sliderCaptcha核心功能与API详解**
**标题:功能强大,API友好**
1. **初始化与配置**:
```javascript
import SliderCaptcha from 'slider-captcha';
const captcha=new SliderCaptcha({
container: '#captcha-container', // 验证码容器ID
width: 300, // 验证码宽度,默认300px
height: 100, // 验证码高度,默认100px
// 其他配置项...
});
captcha.init();
```
2. **验证与回调**:
```javascript
captcha.on('success', (token)=> {
// 用户滑动验证成功,获得token,可在此处发送至服务器校验
sendTokenToServer(token);
});
captcha.on('error', ()=> {
// 用户滑动验证失败,可在此处提示用户重新验证
alert('验证失败,请重新滑动验证');
});
```
3. **刷新验证码**:
```javascript
captcha.reset(); // 重新生成验证码
```
---
**【第三部分】sliderCaptcha工作原理揭秘**
**标题:人机识别与图像处理**
1. **随机生成缺口与背景**:`sliderCaptcha`会随机生成一张带有缺口的图片和一张背景图片,缺口图片会置于背景图片之上,形成滑动拼图的效果。
2. **滑块拖拽与验证**:用户拖动滑块填补缺口时,组件通过计算滑块与缺口的实际距离与理想距离的误差,判断用户操作的有效性。
3. **防机器人策略**:组件内置了一定的人工智能识别策略,如检测滑动轨迹的速度、加速度、方向等因素,增强对机器人的识别能力。
---
**【第四部分】实战案例:在网页表单中集成sliderCaptcha**
**标题:从零到一,快速集成**
在HTML文件中放置滑动验证码容器:
```html
<div id="captcha-container"></div>
```
在JavaScript中初始化并配置滑动验证码组件,同时处理验证结果:
```javascript
import SliderCaptcha from 'slider-captcha';
const captcha=new SliderCaptcha({
container: '#captcha-container',
// 其他配置...
});
captcha.init();
captcha.on('success', (token)=> {
// 发送token至服务器验证,并在验证通过后提交表单
validateTokenOnServer(token).then(valid=> {
if (valid) {
submitForm();
} else {
captcha.reset();
}
});
});
captcha.on('error', ()=> {
// 验证失败,提示用户重新验证
captcha.reset();
});
```
---
**结语:**
`sliderCaptcha`这款小而美的开源滑动验证码组件,凭借其简洁的API、强大的防机器人功能和出色的用户体验,已经成为众多开发者在Web前端安全防护方面的得力助手。借助这款组件,开发者能够轻松提升网站或应用的安全等级,同时兼顾用户体验,实现安全与体验的完美平衡。不论是新手还是资深前端开发者,都能够快速上手并融入到自己的项目中,为用户提供更安全、更有趣的验证体验。
*请认真填写需求信息,我们会在24小时内与您取得联系。