原創文章,請勿轉載!
一、本文內容僅限於安全研究,不公開具體源碼。維護網絡安全,人人有責。
二、若是想更系統地瞭解第三代驗證碼、更全面體會安全產品的設計,能夠結合個人另幾篇篇文章
(1)《第三代驗證碼研究》https://www.cnblogs.com/boycelee/p/11363611.html(推薦)javascript
(2)《頂象驗證碼破解與研究》https://www.cnblogs.com/boycelee/p/14269941.html(推薦)html
(3)《極驗驗證碼破解與研究》https://www.cnblogs.com/boycelee/p/14021048.html(推薦)java
(4)《極驗無感驗證破解》http://www.javashuo.com/article/p-avtzzlmf-vn.htmlweb
(5)《同盾小程序指紋破解》http://www.javashuo.com/article/p-odqdnktc-vn.html算法
三、本文主要經過破解協議的方式繞過頂象安全驗證,思路與網上自動化的方式有很大的不一樣chrome
(1)頂象的驗證碼真的很是棒。從2018年12月開始到2019年5月持續與頂象驗證碼對抗半年。json
(2)我猜在對抗期間頂象不少的防禦策略都是被我逼出來的吧(哈哈哈,我的yy)canvas
(3)js混淆與加密算法一天更換兩次、滑塊驗證碼干擾槽等應該都是在於我對抗中升級的。小程序
(4)一年後回頭看頂象的驗證碼產品又多了語序點選、刮刮卡驗證、空間語義驗證、亂序拼圖驗證、旋轉驗證、面積驗證等驗證碼類型,在創新方面作得很是好,應該是國內首屈一指的。api
(5)與頂象的對抗,使我對安全產品有了更深入、全面的認識。
(6)如下是我與頂象的對抗過程。
訪問頂象官網,註冊帳號後登陸控制檯,訪問「無感驗證」模塊,申請開通後系統會分配一個惟一的AppId、AppSecret。
當用戶滑動驗證碼經過後,驗證碼服務會生成一個token
,用戶的業務請求帶上這個驗證碼token
,業務系統再調用後臺 SDK 驗證token
的有效性。
頂象請求鏈路以下:
當用戶滑動驗證碼經過後,驗證碼服務就會生成token,用戶攜帶token上傳至頂象服務進行驗證。咱們的目標就是生成沒有被標記爲異常的token。
2018年12月研究的是國航的手機註冊場景。不過因爲國航該場景的驗證碼已經再也不是滑塊,因此後續可能須要使用官網案例來具體分析。
從請求中咱們能夠看出,一共是三個請求(c一、a、v1)。咱們須要弄清楚這三個請求分別的含義。(從設計角度我的以爲頂象相對於極驗要簡潔一些。我的以爲頂象的設計很是棒,在作公司的安全產品時,我也會參考一些頂象的設計)
目的:此處應該是計算設備指紋,經過設備指紋可以標識惟一設備信息
428#X8m8K4SIcMDkTOm/r2P3j5vTyaRhmW3m9aksXQ8m2r13DgOarX1TmbmTMX13/vXXC9RmXS8m2XHfDg/uV8RP/4uaMuk8/WnUNX+6Xnah2rDfmZ3YXXwR0gpt2lJu07Nozs5jXY433RaCfCO6Y8VX/6aVU2mT6cuhX14q+2v8jcQ/awvNF8X1YuuJmzf3XXbXmCvq8M8uiwXSm3vhmyyuXXOiXmo8Ya21XXGjS8ft1qT2+Al3+Ab0wXWXmyf8aCO4WrrXj6flFh9p4PnSFPokuP5vXmuFn9fM8Am+JXvlT2TiW8QL88SXjtZykz8LUtZ9kAQpkd4pn2oXmpP9upuLVr2XvVTUjyTPTXWPCy+oTofzwi2oRX41sYk1sXWARiw1w32AOrfAe1fPTXVXivd2CFHaGgNezipHxx6cciF1xORN2iUklOpQzldUBvAxwBHXSoHBAGyUXXJ99ei8lxEoj2X8sJTvz/novxbofltai6k3wmXXjtyTItyoi8QsjUMIGI0siT3XYWKG9FkI54DQdTnXm00umH0KBTIXmACOT1rFmC8XmA2rTPuVmm/Xmg1R9usmM5/6XX+vqnlSylnDXX+jGnElyi3fXXphvMw444YeNUpw0TyXuFi3HFitgAiquUM3HXtquUM3HXwt1rXSuNMs1MJfuNrniBCkEnrfYmCsYuL5Y5Cf+nCkUFCkYdCSUFrSJXonuMoXZkrpk8nf3Ue/P9F0pmjG6Q9/DgBJzVZqcg6po/NleM6PXXVE8F9oSzdt++YgMiFBYxWbOUaHPXXS+2C5jY5/ZcmEaAvR+cxxjRQ+k8CoD1xHFcxSj6ovU270Dwyik9oBPTVXYNS86LTmha3t68oQPrXs3/ZLJMPTI2QLAtomXX07drAmu4omycphXrnXYbUG9GpOSg6yts0JX2XfTXVqvE26Y/COD9OqWA5IPaQTDVxc41r6mXXn8Xo6JyIPJPyuXYQim8OYJX3tu6ci/TX3muOg/Lm8//nuY9nljN/sX3vTXX4qX1nIa//1Yc9x
只展現部分數據
"supportAddBehavior": "ab","adblock": "adb","availResolution": "ar","canvasFP": "can","cpuClass": "cc","colorDepth": "cd","cookieEnabled": "ce","canPlayType": "cpt","collectTime": "ct","doNotTrack": "dnt","deviceMemory": "dm","languages": "lugs","mimeTypes": "mts","mediaDevices": "mds","platform": "np","supportOpenDatabase": "od","devicePixelRatio": "pr","resolution": "res","plugins": "rp","supportSessionStorage": "ss","timezoneOffset": "to","touch": "ts","userAgent": "ua","webgl": "web","webgl2": "gi"
經過加密以上操做系統與瀏覽器以及網絡數據等信息生成c1請求參數param。
index.js?_t=xxxx中,進行每日更新。那麼該如何確保每日都能獲取到準確的加密算法呢?經過解析原語法樹,生成咱們想要的語法樹。(這個後續講)
{"data":"f8839e00435f2e05f9ed60b3d3c5498554cb367655ec6e7318adefda150437040a74963c","msg":"lid invalid","status":-4} // 會根據指紋狀況對指紋進行風險等級判斷
頂象會將指紋進行多地存儲,例如cookie、Session Storage、Local Storage等
de=0&wp=1&aid=dx-1547996895410-3601284-1&jsv=1.3.11.98&c=5c3c65a6uSNGXiEhdEwxwwICxBq5Qdjdky4kxjo1&ak=5f6727ec854786a86cd4c3c171d13499&s=50&h=150&w=300&_r=0.9866721061865382
{"sid":"86ae78ed0fba04f8384f3c9376271b0d","y":30,"success":true,"p1":"/dx/ib3oV3MeuO/zib3/b5cce61f91c6447bbc2f10e7836a7827.webp","p2":"/dx/ib3oV3MeuO/zib3/e34db2ab70064b2c998d14159f0b8ff8.webp","p3":"/dx/ib3oV3MeuO/zib3/50969dc0b2f14d118fcf166dc0871021.webp","msg":null,"t":null,"result":1,"type":0,"logo":null}
注意:若是該請求錯誤,可能會返回另外一種圖片驗證碼(點擊類型)
y: 30 x: 33 aid: dx-1547997910506-78792589-1 sid: 75dc20f81b2bd0ff871b9f12e54ef2c8 jsv: 1.3.11.98 c: 5c448ee09pUgUAwgiaRMmhhDea79K4O1B7oQhRh1 ak: 5f6727ec854786a86cd4c3c171d13499 ac: 492#X8Xn8AQv/Y6pdvgYXXfOuMffR/W3XjVgMOYfQntkaQbRZ/smuRlFpvD6L+qHM21JPdOjXTgzLGKauxS0c29jXCAeYaTRl589z9Ug+vZ1YDXIBNwrm8X1k6vEf3rKmrXmS2WXmFPMgH3nXX8XXXXmY8XEJ37H/cWc68WnDdW+DXSjXtXZj9c6v33n6aa6W9bhYu/c8XIXui83m5U1gFUJzxqJzxoGUqNSYXpFY2Xio3O5Ja/dw/NP2X5X3oFjcWf2r9znqaJQ2LQr7homIr5X3oFPjnXMr9znqaJQ2LQr7homIr2XsBHwJhSSRygCnh3ufTc1v/8V8YZsiz/D4raoj9XLRtOYv9a53D2kZcWV8YcHUAa/mrXmS25X3oFPjvS2r9znqaJQ2LQr7homIr5XjoFPjvrMr9DIGaUiwYmYYrXslqs+7bRz2kEuonW=
y:座標,a請求會返回,因此不須要本身計算。
x:座標,須要計算。
aid:時間戳+隨機數+1。目前猜想其目的是number once。
jsv:表示版本號。
c:c1請求的返回值,理解爲token1
ak:appKey,頂象會爲每個接入其無感驗證碼的server提供一個appKey,以便標識他們是哪個服務。
ac:加密信息。包括時間戳、操做系統對應編碼、Referer、特殊加密算法(隨機取加密算法中一段並加密)、JSV、token(sid)、是否使用headless、瀏覽器版本、屏幕信息、服務網址、版本、檢測是否使用webdriver等方式抓取、token、鼠標軌跡等進行數據加密。
{"success":true,"token":"37AD877DC3953CA1116E487DFBF6DB435DFE7C4CD204B8DC861D5857BE2FDDCF4565648C95BAFD15030AA555713B5DA1FD5D57C82427F4C7D222E5990F47AB83B3F13ED08965032CFF26FA37B82012FF","msg":null,"tp":null,"sv":null,"retry":0}
返回token則表示成功。但注意此時token未必有效,若是c1請求使用appKey與v1請求使用的不一樣,是沒法經過check校驗的。
在獲取token以後會經過check接口,進行token時效性與正確性校驗。使用方經過調用頂象的open api進行token校驗。經過則說明,驗證碼完成。
function getParamUa(sid, position, referer, browserVersion) { var _ua = ""; var ua = ""; var tm = new Date().getTime(); delete require.cache[require.resolve('../builder/dingxiang_greenseer_finish.js')]; var encryptionFunc = require('../builder/dingxiang_greenseer_finish.js'); var replace_encrypt = encryptionFunc.replace_encrypt; delete require.cache[require.resolve('../builder/dingxiang_version_finish.js')]; var versionFunc = require('../builder/dingxiang_version_finish.js'); var replace_version = versionFunc.replace_encrypt; let mouseMove = buildMouseMove(position); let mouseMoveEvent = buildMouseMoveEvent(mouseMove); let mouseDownEvent = buildMouseDownEvent(mouseMoveEvent); app = function (u, c) { var s = (0, toStr)([u].concat((0, replace_bs2)(c.length))); _ua += [s, c].join(""), ua = [replace_version.exports.version, "#", (0, replace_btoa)(_ua)].join(""); console.log(ua); ua.replace("xxxxxxxxx", ""); } process = function (t) { var c = [].slice.call(arguments); return t = c.length === 1 && (0, isArray)(t) ? t : c, t = (0, flatten)(t), (0, toStr)(t) } getTM = function () { var a = process((0, replace_bs8)(tm)); app(1, (0, replace_encrypt.encryptTM)(a)) } //瀏覽器版本 getBR = function (browserVersion) { //瀏覽器版本 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36 //Chrome/73.0.3683.86 ,chromeVersion = "73" //var version = browserVersion; var version = browserVersion; console.log("version: " + version) var a = 3, b = [1, version], c = b[1]; a = process(a, b[0], (0, replace_bs2)(c.length), (0, replace_bss)(c)); app(2, (0, replace_encrypt.encryptBR)(a)) } //屏幕信息 getSC = function () { //7,128,4,56,7,128,4,33,0,241,4,56,7,128,1,73,7,128,4,56 //var n = this.process((0, getScreenInfo)()); //var n = "8! ´:8"; //var scValue = [[5,160],[3,132],[5,160],[3,109],[0,0],[0,0],[5,160],[1,49],[5,160],[3,132]]; var scValue = buildSCArray(); //var scValue = [[7,128],[4,56],[7,128],[4,33],[0,241],[4,56],[7,128],[2,5],[7,128],[0,0]]; var n = this.process(scValue); app(3, (0, replace_encrypt.encryptSC)(n)) } //網址 getLO = function (referer) { var o = "", a = referer, i = this.process((0, replace_bs2)(a.length), (0, replace_bss)(a), (0, replace_bs2)(0), (0, replace_bss)(o)); app(4, (0, replace_encrypt.encryptLO)(i)) } //函數 getCF = function () { let cfParam = buildCFParam(); let range = Math.floor(Math.random() * 3 + 7); let lastIndex = Math.floor(Math.random() * (cfParam.length - 9) + 9); let beginIndex = lastIndex - range; cfParam = cfParam.slice(beginIndex, lastIndex); var s = this.process((0, replace_bs2)(beginIndex), (0, replace_bs2)(range), (0, replace_bss)(cfParam)); //var s = " += t"; app(5, (0, replace_encrypt.encryptCF)(s)) } //是否開啓控制檯 getDI = function () { var a = 1; a = process(a); app(6, (0, replace_encrypt.encryptDI)(a)) } //檢測是否使用webdriver等方式抓取 getEM = function () { var s = process((0, replace_bs4)(0)); app(7, (0, replace_encrypt.encryptEM)(s)) } //版本 getJSV = function () { var o = process((0, replace_bs4)(1)); app(8, (0, replace_encrypt.encryptJSV)(o)) } //token getTK = function (sid) { var a = sid; a && (a = this.process((0, replace_bs2)(a.length), (0, replace_bss)(a)), app(9, (0, replace_encrypt.encryptTK)(a))) } getMM = function (mouseMove) { var v = mouseMove.eventName , h = mouseMove.tm , d = mouseMove.x , p = mouseMove.y , l = this.process((0, replace_bs4)(h) , (0, replace_bs2)(d) , (0, replace_bs2)(p) , (0, replace_bs2)(v.length) , (0, replace_bss)(v)); console.log("mouse_mm info :" + "MM_v:" + v + " tm:" + h + " MM_getPageX:" + d + " MM_getPageY:" + p); this.app(11, (0, replace_encrypt.encryptMM)(l)) } getMD = function (mouseDown) { var f = mouseDown.eventName , s = mouseDown.button , v = mouseDown.tm , h = mouseDown.x , d = mouseDown.y , p = this.process((0, replace_bs4)(v) , (0, replace_bs2)(h) , (0, replace_bs2)(d) , s , (0, replace_bs2)(f.length) , (0, replace_bss)(f)); console.log("MD_getTarget:" + f + " MD_getButton:" + s + " MD_time:" + v + " MD_getPageX:" + h + " MD_getPageY:" + d + " MD_process:" + p); this.app(12, (0, replace_encrypt.encryptMD)(p)) } var _sa = []; recordSA = function (mouseMove) { var i = mouseMove.tm , u = mouseMove.x , c = mouseMove.y , f = this.process((0, replace_bs4)(i), (0, replace_bs2)(u), (0, replace_bs2)(c)); console.log("recordSA: " + " tm: " + i + " getPageX: " + u + " getPageY: " + c); _sa.push((0, replace_encrypt.encryptSA)(f)) } sendSA = function (r) { this.app(17, r); } sendTemp = function (t) { var n = process((0, replace_bs2)(t.length), (0, replace_bss)(t)); app(10, (0, replace_encrypt.encryptTEMP)(n)) } var tm = getTM(); var br = getBR(browserVersion); var LO = getLO(referer); var CF = getCF(); var DI = getDI(); var EM = getEM(); var JSV = getJSV(); var TK = getTK(sid); var SC = getSC(); var MM = getMM(mouseMoveEvent[mouseMoveEvent.length - 1]); var MD = getMD(mouseDownEvent[0]); var DI = getDI(); for (let mveIndex = mouseMoveEvent.length - 2; mveIndex >= 0; mveIndex--) { var MM = getMM(mouseMoveEvent[mveIndex]); } for (let mvIndex = mouseMove.length - 1; mvIndex >= 0; mvIndex--) { var SA1 = recordSA(mouseMove[mvIndex]); } for (let saIndex = 0; saIndex < _sa.length; saIndex++) { var SA2 = sendSA(_sa[saIndex]); } var temp = sendTemp(position); return ua; }
@Override public CrackResult crackDingXiang(DingXiangParam dingXiangParam, Integer captchaType, String caller) throws IOException { Preconditions.checkNotNull(dingXiangParam, "crackDingXiang dingXiangParam is null"); Preconditions.checkNotNull(StringUtils.isNotBlank(caller), "caller is null"); String userAgent = HeaderBuilder.buildChrome(); String browserVersion = getBrowserVersion(userAgent); dingXiangParam.setUserAgent(userAgent); //Lid請求 String lidResult = getDingXiangLidFunc(dingXiangParam); dingXiangParam = changeDingXiangParam(dingXiangParam, lidResult); //C請求 String cFunctionResult = getDingxiangCFunc(dingXiangParam); CFunctionResponse cFunctionResponse = buildCFunctionResponse(cFunctionResult); AFunctionParam aFunctionParam = buildPictureRequestParam(cFunctionResponse, dingXiangParam); //A請求 String aFunctionResult = getDingXiangAFunc(aFunctionParam); AFunctionResponse aFunctionResponse = buildAFunctionResponse(aFunctionResult); //分析圖片 PictureAnalysisParam pictureAnalysisParam = buildPictureAnalysisParam(dingXiangParam, aFunctionResponse, browserVersion); String pictureAnalysisResult = analysePicture(pictureAnalysisParam); PictureAnalysisResponse pictureAnalysisResponse = buildPictureAnalysisResponse(pictureAnalysisResult, pictureAnalysisParam); //V請求 VFunctionParam vFunctionParam = buildVFunctionParam(pictureAnalysisResponse, aFunctionParam, aFunctionResponse, dingXiangParam); List<NameValuePair> vFunctionPostParam = buildDingXiangVFuncPostParam(vFunctionParam); String vFunctionResult = getDingXiangVFunc(vFunctionPostParam, dingXiangParam); VFunctionResponse vFunctionResponse = buildDingXiangVFuncResponse(vFunctionResult); CrackResult crackResult = buildCrackResult(vFunctionResponse, cFunctionResponse, dingXiangParam, captchaType); return crackResult; }
加密算法會隨着js混淆的改變而改變,如何服務化?
(1)未處理的index.js。
(2)建立模板並生成其語法樹。
(3)將加密算法轉化成語法樹-> 與模板語法樹結合 -> 生成新的語法樹 -> 生成新的js文件
(4)啓用定時任務,天天固定時間去生成新的加密算法js文件。
(1)頂象相對於極驗產品設計更加清晰。步驟很是明確 "設備數據->行爲數據->驗證";
(2)經過每日更新混淆js文件、加密算法提高破解服務化難度;
(3)經過驗證碼下發策略,進行驗證碼種類變換,提高驗證碼識別難度;
(4)驗證碼產品創新能力強,例如:空間語義驗證、亂序拼圖驗證、面積驗證、旋轉驗證等;
(5)總體而言,頂象對於驗證碼的理解應該是業界領先的。
一、目前國內在安全產品方面的文章較少,咱們很難全面瞭解與學習安全產品,但願個人文章可以幫助到更多人。
二、若是想系統地瞭解第三代驗證碼,能夠結合個人另幾篇篇文章
(1)《第三代驗證碼研究》https://www.cnblogs.com/boycelee/p/11363611.html(推薦)
(2)《頂象驗證碼破解與研究》https://www.cnblogs.com/boycelee/p/14269941.html(推薦)
(3)《極驗驗證碼破解與研究》https://www.cnblogs.com/boycelee/p/14021048.html(推薦)
(4)《極驗無感驗證破解》http://www.javashuo.com/article/p-avtzzlmf-vn.html
(5)《同盾小程序指紋破解》https://www.cnblogs.com/boycelee/category/1819211.html
三、本文不提供完整解決方案和完整數據,僅用於理論研究,維護網絡安全,人人有責。