headless模式美团滑块验证码无法通过的解决方案

时间:2024-03-01 14:43:59

在有界面浏览器模拟美团滑块滑动可以直接通过(此处使用pyppeteer,selenium未测试),一旦使用headless模式则无法通过验证,今天就来聊一聊如何绕过美团的headless检测。

单独打开验证页面,可以看到加载了3个js文件,均经过高度混淆

首先找找"webdriver",这是一个最常见的特征值

选中的这两行即美团会检测的JS值,我们要做的就是,确保这部分关键字在正常浏览器和无头浏览器返回值保持一致

如何确保一致?一是通过加载页面后执行JS代码,来覆盖headless下的特征值,二是使用mitmproxy拦截修改服务器响应的JS代码来实现目的

这里我使用mitmproxy

from mitmproxy import ctx

detectList = [\'webdriver\', \'__driver_evaluate\', \'__webdriver_evaluate\',
              \'__selenium_evaluate\', \'__fxdriver_evaluate\', \'__driver_unwrapped\',
              \'__webdriver_unwrapped\', \'__selenium_unwrapped\', \'__fxdriver_unwrapped\',
              \'_Selenium_IDE_Recorder\', \'_selenium\', \'calledSelenium\',
              \'_WEBDRIVER_ELEM_CACHE\', \'ChromeDriverw\', \'driver-evaluate\',
              \'webdriver-evaluate\', \'selenium-evaluate\', \'webdriverCommand\',
              \'webdriver-evaluate-response\', \'__webdriverFunc\', \'__webdriver_script_fn\',
              \'__$webdriverAsyncExecutor\', \'__lastWatirAlert\', \'__lastWatirConfirm\',
              \'__lastWatirPrompt\', \'$chrome_asyncScriptInfo\', \'$cdc_asdjflasutopfhvcZLmcfl_\']

def response(flow):
    if \'.js\' in flow.request.url:
        for key in detectList:
            flow.response.text = flow.response.text.replace(\'"{}"\'.format(key), \'"NO-SUCH-ATTR"\')

其中detectList是常见的检测的特征

使用代理拦截后发现依然无法验证通过,那可能原因是还有其他检测的特征值不在我们的列表中,也有可能是上面的特征值字符串被加密了

注意到slider.js的第一行,混淆加密的列表,大概率是经过base64编码后的字符串列表

对其base64解码得到

_0x2c02_b64decode = [\'apply\', \'return (function() \', \'console\', \'log\', \'warn\', \'info\', \'error\', \'exception\', \'trace\', \'exports\',
     \'undefined\', \'Math\', \'return this\', \'number\', \'hasOwnProperty\', \'call\', \'2.6.5\', \'version\', \'function\',
     \' is not an object!\', \'document\', \'createElement\', \'defineProperty\', \'div\', \'toString\', \'valueOf\',
     "Can\'t convert object to primitive value", \'get\', \'Accessors not supported!\', \'value\', \'Symbol(\', \'concat\',
     \'__core-js_shared__\', \'push\', \'global\', \'© 2019 Denis Pushkarev (zloirock.ru)\', \'native-function-to-string\', \'src\',
     \'inspectSource\', \'join\', \'prototype\', \' is not a function!\', \'core\', \'meta\', \'isExtensible\', \'preventExtensions\',
     \'string\', \'NEED\', \'KEY\', \'getWeak\', \'onFreeze\', \'wks\', \'Symbol\', \'Symbol.\', \'propertyIsEnumerable\', \'String\',
     \'split\', "Can\'t call method on  ", \'ceil\', \'min\', \'max\', \'length\', \'keys\', \'IE_PROTO\',
     \'constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf\',
     \'getOwnPropertySymbols\', \'isArray\', \'Array\', \'defineProperties\', \'iframe\', \'style\', \'display\', \'none\',
     \'javascript:\', \'write\', \'<script>document.F=Object</script>\', \'close\', \'create\', \'getOwnPropertyNames\', \'object\',
     \'[object Window]\', \'slice\', \'getOwnPropertyDescriptor\', \'stringify\', \'_hidden\', \'toPrimitive\', \'symbol-registry\',
     \'symbols\', \'op-symbols\', \'QObject\', \'findChild\', \'iterator\', \'symbol\', \'enumerable\',
     \'Symbol is not a constructor!\',
     \'hasInstance,isConcatSpreadable,iterator,match,replace,search,species,split,toPrimitive,toStringTag,unscopables\',
     \'store\', \'charAt\', \'Object\', \'[null]\', \'JSON\', \'getPrototypeOf\', \'constructor\', \'seal\', \'isFrozen\', \'assign\',
     \'abcdefghijklmnopqrst\', ": can\'t set as prototype!", \'setPrototypeOf\', \'__proto__\', \'set\', \'Arguments\', \'Null\',
     \'toStringTag\', \'[object z]\', \'[object \', \'Reflect\', \'ownKeys\', \'random\', \'typed_array\', \'view\', \'DataView\',
     \'ArrayBuffer\', \'Wrong index!\', \'RangeError\', \'Infinity\', \'pow\', \'LN2\', \'byteOffset\', \'reverse\', \'ABV\', \'setInt8\',
     \'getInt8\', \'buffer\', \'getIteratorMethod\', \'@@iterator\', \'species\', \'unscopables\', \'next\', \'entries\', \'name\',
     \'values\', \'return\', \'Uint8Array\', \'Shared\', \'BYTES_PER_ELEMENT\', \'lastIndexOf\', \'reduce\', \'sort\', \'toLocaleString\',
     \'typed_constructor\', \'def_constructor\', \'CONSTR\', \'TYPED\', \'VIEW\', \'Wrong length!\', \'Wrong offset!\',
     \' is not a typed array!\', \'It is not a typed array constructor!\', \'done\', \'floor\', \'configurable\', \'writable\',
     \'byteLength\', \'Float64\', \'Int32\', \'Uint32\', \'Int16\', \'Int8\', \'Uint16\', \'match\', \'ignoreCase\', \'multiline\',
     \'unicode\', \'sticky\', \'/a/i\', \'RegExp\', \'source\', \'exec\',
     \'RegExp exec method returned something other than an Object or null\',
     \'RegExp#exec called on incompatible receiver\', \'replace\', \'lastIndex\', \'$(?!\\s)\', \'index\', \'$<a>\',
     \'\t\n\x0b\x0c\r \xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\ufeff\',
     \'trim\', \'Number\', \'charCodeAt\', \'0b1\',
     \'MAX_VALUE,MIN_VALUE,NaN,NEGATIVE_INFINITY,POSITIVE_INFINITY,EPSILON,isFinite,isInteger,isNaN,isSafeInteger,MAX_SAFE_INTEGER,MIN_SAFE_INTEGER,parseFloat,parseInt,isInteger\',
     \'flags\', \'/a/b\', \'Invalid Date\', \'getTime\', \'groups\', \'throw\', \'label\', \'pop\', \'trys\', \'ops\',
     \'Please drag the slider to the right\', \'Please slide with one finger\', \'スライダを右にドラッグしてください\', \'zh-CN\', \'template\',
     "<div class=\'yoda-slider-tip ", "\' id=", \'tip\', \'wrapper\', "\'>\n                <p class=\'sliderTitle ",
     \'sliderTitle\', \'dragRight\', "</p>\n                <div class=\'boxWrapper ", \'boxWrapper\',
     ">\n                    <div class=\'boxStatic ", \'boxStatic\', \'box\', \'moveingBar\',
     \'></div>\n                </div>\n            </div>\', \'callback\', \'url\', \'jsonp_\', \'data\', \'removeChild\',
     \'success\', \'indexOf\', \'time\', \'fail\', \'请求超时\', \'121000\', \'121002\', \'121004\', \'121005\', \'121018\', \'121045\', \'99999\',
     \'121009\', \'121011\', \'121036\', \'121040\', \'121042\', \'121043\', \'121046\', \'121052\', \'121053\', \'121055\', \'121056\',
     \'121058\', \'121061\', \'121065\', \'121066\', \'121067\', \'121088\', \'121099\', \'00101\', \'您的请求出现了异常\', \'00102\', \'您的网络状况不好\',
     \'00300\', \'00400\', \'网络资源异常,请稍后再试\', \'00500\', \'网络重定向,请稍后再试\', \'服务器异常,请稍后再试\', \'Request exception\',
     \'Server exception, please try again later\', \'ネットワークのつなぎ状態が不安定です\', \'ネットワークがリダイレクトしました、後でもう一度やり直してください\',
     \'リクエストがエラー発生しました\', \'サーバーが異常です。しばらくしてからもう一度お試しください\', \'now\', \'open\', \'setRequestHeader\', \'seed\', \'config\',
     \'language\', \'yoda-language\', \'facespeech\', \'onload\', \'readyState\', \'status\', \'response\', \'Yoda\', \'CAT\',
     \'postBatch\', \'200|\', \'ajax\', \'当前请求状态\', \'NETWORK_REDIRECT_CODE\', \'NETWORK_REDIRECT_TIP\', \'NETWORK_REQUEST_CODE\',
     \'NETWORK_SERVER_CODE\', \'NETWORK_SERVER_TIP\', \'ontimeout\', \'sendLog\', \'NETWORK_TIMEOUT_CODE\', \'onerror\',
     \'ajaxError\', \'NETWORK_FAILURE_TIP\', \'send\', \'XDomainRequest\', \'创建请求对象失败\', \'catch\', \'HTTP请求失败\', \'FormData\',
     \'Content-Type\', \'application/x-www-form-urlencoded\', \'POST\', \'GET\', \'HEAD\', \'verifyAPIST\', \'yodaInitTime\', \'type\',
     \'metric\', \'action\', \'YODA_CONFIG\', \'__API_URL__\', \'/v2/ext_api/\', \'/verify\', \'request_code\', \'report\', \'sent\',
     \'driver-evaluate,webdriver-evaluate,selenium-evaluate,webdriverCommand,webdriver-evaluate-response\',
     \'removeEventListener\', \'hasAttribute\', \'nodeName\', \'cd_frame_id_\', \'documentElement\', \'webdriver\', \'domAutomation\',
     \'__lastWatirPrompt\', \'__webdriver_script_fn\', \'cookieChromeDriver\', \'cookie\', \'asyncScriptInfo\',
     \'$cdc_asdjflasutopfhvcZLmcfl_\', \'webdriverElemCache\', \'_WEBDRIVER_ELEM_CACHE\', \'webdriverAsyncExecutor\',
     \'__$webdriverAsyncExecutor\', \'getElementsByTagName\', \'frame\', \'lwc\', \'createShader\', \'compileShader\',
     \'getShaderParameter\', \'COMPILE_STATUS\', \'deleteShader\', \'width\', \'height\', \'getContext\', \'webgl\',
     \'experimental-webgl\', \'canvas\', \'inline\', \'30px serif\', \'textAlign\', \'center\', \'textBaseline\', \'middle\',
     \'fillText\', \'