整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

实战笔记—滑动验证码攻防对抗

实战笔记—滑动验证码攻防对抗

索公众号:暗网黑客

可领全套网络安全课程、配套攻防靶场

大家好!我是每天为大家分享好文的柠檬!与你一起成长~

有需要体系化黑客渗透视频教程可搜索公众号:暗网黑客


一、背景介绍


在业务安全领域,滑动验证码已经是国内继传统字符型验证码之后的标配。

众所周知,打码平台和机器学习这两种绕过验证码的方式,已经是攻击者很主流的思路,不再阐述。

本文介绍的是一个冷门的绕过思路和防御方案。

这些积累,均来自于实战之中,希望有用。


二、思路介绍


知己知彼,百战不殆。

如果不清楚攻击者的手段,又如何能制定防御方案?


滑动验证码绕过思路


漏洞名字: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。

1.创建项目

创建console项目

mkdir SlideImageGenerator
cd SlideImageGenerator
dotnet new console

2.添加ImageSharp

dotnet add package SixLabors.ImageSharp
dotnet add package SixLabors.ImageSharp.Drawing --prerelease

3.vscode打开

code .

4.引入图片

创建Images目录, 并放入bg.jpg,hole.png,slider.png

mkdir Images

5.生成out_bg.jpg

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");

6.生成out_slider.png

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。

  • 本文作者: 破剑冰
  • 本文链接: https://www.cnblogs.com/readafterme/p/16110788.html
  • 一款小而美的开源滑动验证码组件:打造安全与用户体验的完美平衡

    ---

    **引言:滑动验证码的必要性与挑战**

    在日益严重的网络安全威胁面前,传统的字符输入验证码已经无法完全阻挡机器人的恶意攻击。滑动验证码作为一种新型的验证方式,因其趣味性与较高的人机识别度,受到越来越多网站和应用的青睐。本文将详细介绍一款开源滑动验证码组件——`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前端安全防护方面的得力助手。借助这款组件,开发者能够轻松提升网站或应用的安全等级,同时兼顾用户体验,实现安全与体验的完美平衡。不论是新手还是资深前端开发者,都能够快速上手并融入到自己的项目中,为用户提供更安全、更有趣的验证体验。