puppeteer与滑动验证2.0

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了puppeteer与滑动验证2.0相关的知识,希望对你有一定的参考价值。

参考技术A

不知不觉 距离上次研究已经快三年 利用puppeteer破解极验的滑动验证 ,最近用puppeteer在做一些爬虫的工作,遇到了需要滑动验证的校验,于是毫不犹豫想起以前破解的经验,通过对比,emmm...跟之前的有些差别,下面我们来分析一下。

这个滑动验证只存在两张图(如下图)
1. 背景图(包含缺口)
2. 滑块图

对比极验的滑动验证, 缺少完整背景图(不含缺口) ,这样我们就无法去通过对比背景图来获取滑动的距离了。

认真观察一下背景图,我们能不能从中获取到什么有用的信息来计算滑动距离呢?
于是, 我发现 背景图缺口 周围的白边。分析一下
1.白边没有透明度。 (这个很重要,确保不会受到背景图的颜色影响)
2.每次都是白边,颜色不会变,白边颜色值几乎相同。
3.通过白边计算滑动距离。

其实市面上验证方式层出不穷,诸如点击出现文字、找到人物相同的图片等等,对于这些puppeteer基本束手无策(有知道怎么破解的可以分享下哈)。这篇文章更多出于学习分享,大家遇到什么验证问题可以来互相讨论学习一下或评论区留言。

Spring Boot 整合滑动验证

极验是一种利用生物特征与人工智能技术解决人机交互安全问题的技术,旨在解决安全验证问题,例如:账号登录、短信验证、批量注册等,目前极验、网易易盾比较出众。

在这里主要使用的极验Geetest和springboot 框架整合。

1.首先到极验官网注册账号获取ID和KEY,这里赘述。

2.到极验官网下载,使用SDK,点击下载,如果你使用时Git工具,

#git clone https://github.com/GeeTeam/gt3-java-sdk.git

3.引入SDK到Springboot项目中

将sdk项目目录下的GeetestLib.java文件拷入您的项目Untils目录中,代码如下:

技术图片
package com.blog.utils;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;

/**
 * 极验核心文件
 * 
 */
public class GeetestLib {

    protected final String verName = "4.0";
    protected final String sdkLang = "java";

    protected final String apiUrl = "http://api.geetest.com"; 
    
    protected final String registerUrl = "/register.php"; 
    protected final String validateUrl = "/validate.php";
    
    protected final String json_format = "1";
     
    /**
     * 极验验证二次验证表单数据 chllenge
     */
    public static final String fn_geetest_challenge = "geetest_challenge";
    
    /**
     * 极验验证二次验证表单数据 validate
     */
    public static final String fn_geetest_validate = "geetest_validate";
    
    /**
     * 极验验证二次验证表单数据 seccode
     */
    public static final String fn_geetest_seccode = "geetest_seccode";

    /**
     * 公钥
     */
    private String captchaId = "";

    /**
     * 私钥
     */
    private String privateKey = "";
    
    /**
     * 是否开启新的failback
     */
    private boolean newFailback = false;
    
    /**
     * 返回字符串
     */
    private String responseStr = "";
    
    /**
     * 调试开关,是否输出调试日志
     */
    public boolean debugCode = true;
    
    /**
     * 极验验证API服务状态Session Key
     */
    public String gtServerStatusSessionKey = "gt_server_status";
    
    /**
     * 带参数构造函数
     * 
     * @param captchaId
     * @param privateKey
     */
    public GeetestLib(String captchaId, String privateKey, boolean newFailback) {
        
        this.captchaId = captchaId;
        this.privateKey = privateKey;
        this.newFailback = newFailback;
    }
    
    /**
     * 获取本次验证初始化返回字符串
     * 
     * @return 初始化结果
     */
    public String getResponseStr() {
        
        return responseStr;
        
    }
    
    public String getVersionInfo() {
        
        return verName;
        
    }

    /**
     * 预处理失败后的返回格式串
     * 
     * @return
     */
    private String getFailPreProcessRes() {

        Long rnd1 = Math.round(Math.random() * 100);
        Long rnd2 = Math.round(Math.random() * 100);
        String md5Str1 = md5Encode(rnd1 + "");
        String md5Str2 = md5Encode(rnd2 + "");
        String challenge = md5Str1 + md5Str2.substring(0, 2);
        
        JSONObject jsonObject = new JSONObject();
        try {
            
            jsonObject.put("success", 0);
            jsonObject.put("gt", this.captchaId);
            jsonObject.put("challenge", challenge);
            jsonObject.put("new_captcha", this.newFailback);
            
        } catch (JSONException e) {
            
            gtlog("json dumps error");
            
        }
        
        return jsonObject.toString();
        
    }

    /**
     * 预处理成功后的标准串
     * 
     */
    private String getSuccessPreProcessRes(String challenge) {
        
        gtlog("challenge:" + challenge);
        
        JSONObject jsonObject = new JSONObject();
        try {
            
            jsonObject.put("success", 1);
            jsonObject.put("gt", this.captchaId);
            jsonObject.put("challenge", challenge);
            
        } catch (JSONException e) {
            
            gtlog("json dumps error");
            
        }
        
        return jsonObject.toString();
        
    }
    
    /**
     * 验证初始化预处理
     *
     * @return 1表示初始化成功,0表示初始化失败
     */
    public int preProcess(HashMap<String, String> data) {

        if (registerChallenge(data) != 1) {
            
            this.responseStr = this.getFailPreProcessRes();
            return 0;
            
        }
        
        return 1;

    }

    /**
     * 用captchaID进行注册,更新challenge
     * 
     * @return 1表示注册成功,0表示注册失败
     */
    private int registerChallenge(HashMap<String, String>data) {
        
        try {
            String userId = data.get("user_id");
            String clientType = data.get("client_type");
            String ipAddress = data.get("ip_address");
            
            String getUrl = apiUrl + registerUrl + "?";
            String param = "gt=" + this.captchaId + "&json_format=" + this.json_format;
            
            if (userId != null){
                param = param + "&user_id=" + userId;
            }
            if (clientType != null){
                param = param + "&client_type=" + clientType;
            }
            if (ipAddress != null){
                param = param + "&ip_address=" + ipAddress;
            }
            
            gtlog("GET_URL:" + getUrl + param);
            String result_str = readContentFromGet(getUrl + param);
            if (result_str == "fail"){
                
                gtlog("gtServer register challenge failed");
                return 0;
                
            }
            
            gtlog("result:" + result_str);
            JSONObject jsonObject = new JSONObject(result_str);
            String return_challenge = jsonObject.getString("challenge");
        
            gtlog("return_challenge:" + return_challenge);
            
            if (return_challenge.length() == 32) {
                
                this.responseStr = this.getSuccessPreProcessRes(this.md5Encode(return_challenge + this.privateKey));
                
                return 1;
                
            }
            else {
                
                gtlog("gtServer register challenge error");
                    
                return 0;
                
            }
        } catch (Exception e) {
            
            gtlog(e.toString());
            gtlog("exception:register api");
            
        }
        return 0;
    }
    
    /**
     * 判断一个表单对象值是否为空
     * 
     * @param gtObj
     * @return
     */
    protected boolean objIsEmpty(Object gtObj) {
        
        if (gtObj == null) {
            
            return true;
            
        }

        if (gtObj.toString().trim().length() == 0) {
            
            return true;
            
        }

        return false;
    }

    /**
     * 检查客户端的请求是否合法,三个只要有一个为空,则判断不合法
     * 
     * @param request
     * @return
     */
    private boolean resquestIsLegal(String challenge, String validate, String seccode) {

        if (objIsEmpty(challenge)) {
            
            return false;
            
        }

        if (objIsEmpty(validate)) {
            
            return false;
            
        }

        if (objIsEmpty(seccode)) {
            
            return false;
            
        }

        return true;
    }
    
    
    /**
     * 服务正常的情况下使用的验证方式,向gt-server进行二次验证,获取验证结果
     * 
     * @param challenge
     * @param validate
     * @param seccode
     * @return 验证结果,1表示验证成功0表示验证失败
     */
    public int enhencedValidateRequest(String challenge, String validate, String seccode, HashMap<String, String> data) {    
        
        if (!resquestIsLegal(challenge, validate, seccode)) {
            
            return 0;
            
        }
        
        gtlog("request legitimate");
        
        String userId = data.get("user_id");
        String clientType = data.get("client_type");
        String ipAddress = data.get("ip_address");
        
        String postUrl = this.apiUrl + this.validateUrl;
        String param = String.format("challenge=%s&validate=%s&seccode=%s&json_format=%s", 
                                     challenge, validate, seccode, this.json_format);
        
        if (userId != null){
            param = param + "&user_id=" + userId;
        }
        if (clientType != null){
            param = param + "&client_type=" + clientType;
        }
        if (ipAddress != null){
            param = param + "&ip_address=" + ipAddress;
        }
        
        gtlog("param:" + param);
        
        String response = "";
        try {
            
            if (validate.length() <= 0) {
                
                return 0;
                
            }

            if (!checkResultByPrivate(challenge, validate)) {
                
                return 0;
                
            }
            
            gtlog("checkResultByPrivate");
            
            response = readContentFromPost(postUrl, param);

            gtlog("response: " + response);
            
        } catch (Exception e) {
            
            e.printStackTrace();
            
        }
        
        String return_seccode = "";
        
        try {
            
            JSONObject return_map = new JSONObject(response);
            return_seccode = return_map.getString("seccode");
            gtlog("md5: " + md5Encode(return_seccode));

            if (return_seccode.equals(md5Encode(seccode))) {
                
                return 1;
                
            } else {
                
                return 0;
                
            }
            
        } catch (JSONException e) {
            
        
            gtlog("json load error");
            return 0;
            
        }
        
    }

    /**
     * failback使用的验证方式
     * 
     * @param challenge
     * @param validate
     * @param seccode
     * @return 验证结果,1表示验证成功0表示验证失败
     */
    public int failbackValidateRequest(String challenge, String validate, String seccode) {

        gtlog("in failback validate");

        if (!resquestIsLegal(challenge, validate, seccode)) {
            return 0;
        }
        gtlog("request legitimate");

        return 1;
    }

    /**
     * 输出debug信息,需要开启debugCode
     * 
     * @param message
     */
    public void gtlog(String message) {
        if (debugCode) {
            System.out.println("gtlog: " + message);
        }
    }

    protected boolean checkResultByPrivate(String challenge, String validate) {
        String encodeStr = md5Encode(privateKey + "geetest" + challenge);
        return validate.equals(encodeStr);
    }
    
    /**
     * 发送GET请求,获取服务器返回结果
     * 
     * @param getURL
     * @return 服务器返回结果
     * @throws IOException
     */
    private String readContentFromGet(String URL) throws IOException {

        URL getUrl = new URL(URL);
        HttpURLConnection connection = (HttpURLConnection) getUrl
                .openConnection();

        connection.setConnectTimeout(2000);// 设置连接主机超时(单位:毫秒)
        connection.setReadTimeout(2000);// 设置从主机读取数据超时(单位:毫秒)

        // 建立与服务器的连接,并未发送数据
        connection.connect();
        
        if (connection.getResponseCode() == 200) {
            // 发送数据到服务器并使用Reader读取返回的数据
            StringBuffer sBuffer = new StringBuffer();

            InputStream inStream = null;
            byte[] buf = new byte[1024];
            inStream = connection.getInputStream();
            for (int n; (n = inStream.read(buf)) != -1;) {
                sBuffer.append(new String(buf, 0, n, "UTF-8"));
            }
            inStream.close();
            connection.disconnect();// 断开连接
            
            return sBuffer.toString();    
        }
        else {
            
            return "fail";
        }
    }
    
    /**
     * 发送POST请求,获取服务器返回结果
     * 
     * @param getURL
     * @return 服务器返回结果
     * @throws IOException
     */
    private String readContentFromPost(String URL, String data) throws IOException {
        
        gtlog(data);
        URL postUrl = new URL(URL);
        HttpURLConnection connection = (HttpURLConnection) postUrl
                .openConnection();

        connection.setConnectTimeout(2000);// 设置连接主机超时(单位:毫秒)
        connection.setReadTimeout(2000);// 设置从主机读取数据超时(单位:毫秒)
        connection.setRequestMethod("POST");
        connection.setDoInput(true);
        connection.setDoOutput(true);
        connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
        
        // 建立与服务器的连接,并未发送数据
        connection.connect();
        
         OutputStreamWriter outputStreamWriter = new OutputStreamWriter(connection.getOutputStream(), "utf-8");  
         outputStreamWriter.write(data);  
         outputStreamWriter.flush();
         outputStreamWriter.close();
        
        if (connection.getResponseCode() == 200) {
            // 发送数据到服务器并使用Reader读取返回的数据
            StringBuffer sBuffer = new StringBuffer();

            InputStream inStream = null;
            byte[] buf = new byte[1024];
            inStream = connection.getInputStream();
            for (int n; (n = inStream.read(buf)) != -1;) {
                sBuffer.append(new String(buf, 0, n, "UTF-8"));
            }
            inStream.close();
            connection.disconnect();// 断开连接
            
            return sBuffer.toString();    
        }
        else {
            
            return "fail";
        }
    }

    /**
     * md5 加密
     * 
     * @time 2014年7月10日 下午3:30:01
     * @param plainText
     * @return
     */
    private String md5Encode(String plainText) {
        String re_md5 = new String();
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(plainText.getBytes());
            byte b[] = md.digest();
            int i;
            StringBuffer buf = new StringBuffer("");
            for (int offset = 0; offset < b.length; offset++) {
                i = b[offset];
                if (i < 0)
                    i += 256;
                if (i < 16)
                    buf.append("0");
                buf.append(Integer.toHexString(i));
            }

            re_md5 = buf.toString();

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return re_md5;
    }

}
View Code

 

4.配置密钥,修改请求参数

配置密钥

将sdk项目目录下的GeetestConfig.java拷贝到config目录中,极验管理后台获取您的公钥(id)和私钥(key),并配置。代码如下:

技术图片
public class GeetestConfig {

    // 填入自己的captcha_id和private_key
    private static final String geetest_id    = "481923ee3a39373a9519bd422de1bfc8";
    private static final String geetest_key   = "3fbbf190b58b3b63dd6474426c3587d2";
    private static final boolean newfailback  = true;

    public static final String getGeetest_id() {
        return geetest_id;
    }

    public static final String getGeetest_key() {
        return geetest_key;
    }

    public static final boolean isnewfailback() {
        return newfailback;
    }
}
View Code

5.编写验证控制器方法,代码如下:

技术图片
package com.blog.controller;


import com.alibaba.fastjson.JSONObject;
import com.dufan.config.GeetestConfig;
import com.dufan.utils.GeetestLib;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;

import java.util.HashMap;

/**
 * @author huxiaoguang
 * @DATE 2019年01月18日
 */
@Controller
@RequestMapping("/Geetest")
public class GeetestController
{
    /**
     * 初始化验证码
     * @param
     * @return
     */
    @ResponseBody
    @RequestMapping(value = "initVerify", method = RequestMethod.GET, headers = "Accept=application/json")
    private JSONObject init(HttpServletRequest request)
    {
        GeetestConfig config = new GeetestConfig();
        GeetestLib gtSdk = new GeetestLib(config.getGeetest_id(), config.getGeetest_key(), config.isnewfailback());

        String challenge = request.getParameter(GeetestLib.fn_geetest_challenge);
        String validate  = request.getParameter(GeetestLib.fn_geetest_validate);
        String seccode   = request.getParameter(GeetestLib.fn_geetest_seccode);

        //从session中获取gt-server状态
        int gt_server_status_code = (Integer) request.getSession().getAttribute(gtSdk.gtServerStatusSessionKey);

        //自定义参数,可选择添加
        HashMap<String, String> param = new HashMap<String, String>();
        param.put("user_id",     "blog"); //网站用户id
        param.put("client_type", "h5");
        param.put("ip_address",  "xxx.xx.xx.xxx"); //传输用户请求验证时所携带的IP

        int gtResult = 0;

        if (gt_server_status_code == 1)
        {
            //gt-server正常,向gt-server进行二次验证
            gtResult = gtSdk.enhencedValidateRequest(challenge, validate, seccode, param);
        } else {
            // gt-server非正常情况下,进行failback模式验证
            gtResult = gtSdk.failbackValidateRequest(challenge, validate, seccode);
        }

        JSONObject json = new JSONObject();

        //验证成功
        if (gtResult == 1)
        {
            json.put("status", "success");
            json.put("version", gtSdk.getVersionInfo());
        }
        else {
            json.put("status", "fail");
            json.put("version", gtSdk.getVersionInfo());
        }

        return json;
    }
}
View Code

6.前端ajax请求验证
将sdk项目目录下的gt.js拷贝到前端框架中并引入,前端代码如下:

技术图片
var validate = [];
        
        //短信登录
        $("#loginbtn-sms").click(function()
        {
            var phone = $("#phone").val();
            var verify = $("#verify").val();
            var code = $("#code").val();
        
            if(!phone)
            {
                $("#msg").show().html(‘<div class="msg-error"><i class="iconfont">&#xe679;</i>请输入手机号</div>‘);
                return false;
            }else{
                $("#msg").hide().html();
            }
            
            //验证手机号格式
            if(!isPhoneVaild(phone))
            {
                $("#msg").show().html(‘<div class="msg-error"><i class="iconfont">&#xe679;</i>手机号码格式错误</div>‘);
                return false;
            }else{
                $("#msg").hide().html();
            }
            
            if(!code)
            {
                $("#msg").show().html(‘<div class="msg-error"><i class="iconfont">&#xe679;</i>请输入短信验证码</div>‘);
                return false;
            }else{
                $("#msg").hide().html();
            }
            
            //极验验证
            if(validate.length==0)
            {
                $("#msg").show().html(‘<div class="msg-error"><i class="iconfont">&#xe679;</i>请点击按钮进行验证</div>‘);
                return false;
            }else{
                $("#msg").hide().html();
            }
            
            //禁用注册按钮
            $("#msg").hide().html();
            $(this).attr(‘disabled‘, ‘disabled‘).text(‘登录中...‘);
            
            $.post($("#login-form").attr(‘action‘),{phone:phone,verify:verify,code:code,validate:validate},
            function(json)
            {
                $("#loginbtn-sms").removeAttr("disabled").text(‘登录‘);
                if(json.status)
                {
                    window.location.href = json.url;
                }else{
                    $(".getimage").attr("src", ‘/home/index/verify.html?t=‘+Math.random());
                    $("#msg").show().html(‘<div class="msg-error"><i class="iconfont">&#xe679;</i>‘+json.msg+‘</div>‘);
                }
            },‘json‘)
        });
        
        //获取验证码
        get_remain_time = function (btn)
        {
            var step = 59;

            var _res = setInterval(function()
            {
                $(‘#‘+btn).attr("disabled", true);
                $(‘#‘+btn).val(‘重新发送‘ + step);
                step -= 1;
                if(step <= 0)
                {
                    $(‘#‘+btn).removeAttr("disabled");
                    $(‘#‘+btn).val(‘获取验证码‘);
                    clearInterval(_res);
                }
            },1000);
        }
        
        //验证手机号
        isPhoneVaild = function (phone) 
        {
            var myreg = /^[1][3,4,5,7,8][0-9]{9}$/;
            if (!myreg.test(phone)) 
            {
                return false;
            } else {
                return true;
            }
        }
        
        $(document).keyup(function(event)
        {
            if(event.keyCode ==13)
            {
                $("#loginbtn").trigger("click");
            }
        });
        
        //发起验证
        $.get(‘/common/Sms/verify‘,{t: new Date().getTime()},
        function(data)
        {
            initGeetest({
                gt               : data.gt,
                challenge  : data.challenge,
                new_captcha: data.new_captcha,
                product    : "popup",
                offline    : !data.success
            }, handlerEmbed);
        },‘json‘)
        
        handlerEmbed = function (captchaObj) 
        {
            //发送验证码
            $("#sendcode").click(function(e)
            {
                var phone = $("#phone").val();
                if(!phone)
                {
                    $("#msg").show().html(‘<div class="msg-error"><i class="iconfont">&#xe679;</i>请输入手机号</div>‘);
                    return false;
                }
                
                //验证手机号格式
                if(!isPhoneVaild(phone))
                {
                    $("#msg").show().html(‘<div class="msg-error"><i class="iconfont">&#xe679;</i>手机号码格式错误</div>‘);
                    return false;
                }
                
                //极验验证码验证
                validate = captchaObj.getValidate();
                if (!validate) 
                {
                    $("#msg").show().html(‘<div class="msg-error"><i class="iconfont">&#xe679;</i>请点击按钮完成验证</div>‘);    
                    e.preventDefault();
                    return false;
                }
                $.post(‘/common/Sms/verifySend‘, {phone:phone,validate:validate,type:5},
                function(json)
                {
                    if(json.status)
                    {
                        get_remain_time(‘sendcode‘);
                    }else{
                        $("#msg").show().html(‘<div class="msg-error"><i class="iconfont">&#xe679;</i>‘+json.msg+‘</div>‘);
                        return false;
                    }
                },‘json‘)
            })
            
            captchaObj.appendTo("#embed-captcha");
        };
View Code

 到此springboot整合滑动验证就完成

以上是关于puppeteer与滑动验证2.0的主要内容,如果未能解决你的问题,请参考以下文章

OpenCV - 滑动拼图验证码自动识别与匹配

puppeteer截图

苹果手机怎么滑动验证码

OAuth 2.0 两腿身份验证与 SSL/TLS

如何与 Windows 服务应用程序共享 MVC 5 Identity 2.0 身份验证(尤其是 SignInManager)?

Maximage 2.0 插件:滑动和按键导航