马蜂窝逆向

前言

如果想看实现可以跳到最后(python代码)。注: 只是非常简单的逆向js,大佬勿喷。

测试:

  2020年5月25号 代码运行正常

       2020年8月16号  网站已经修改策略,已经写好了更新 https://www.cnblogs.com/re-is-good/p/mafengwo_version2_ast_cookie.html

  虽然下面的代码已经对马蜂窝已经无效了,但这种反爬并不是马蜂窝网站独有的。

 

网站抓取测试

首先上网址: https://www.mafengwo.cn/i/18252205.html

要是使用正常的python代码(如下)来请求这个网址的话

import requests

url = "https://www.mafengwo.cn/i/18252205.html"
headers = {
    "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36",
    "referer": "https://www.mafengwo.cn/i/18252205.html",
}
r = requests.get(url, headers=headers)
print(r.text)

# print(r.status_code)

 

会返回以下结果

 

 对,没错。返回的竟然是script标签。里面包含着js代码。看js代码的样子也不像是加密的html内容。

 

我们还可以打印一下状态码

print(r.status_code);

 

返回的是521,一般情况下都是200。这就非常奇怪了。

 

这时候我们就要去浏览器中打开这个页面,按F12打开开发者页面(mac是fn+F12)

 

好了,如果打开了开发者工具。那么就刷新下页面吧。

我们会非常惊喜的发现,在浏览器中 这个url的请求(https://www.mafengwo.cn/i/18252205.html)完全正常,而且状态码还是200

 

 

那就去看看在浏览器中这个url请求到底发送了什么?

 

 

仔细对比的话,就会发现浏览器的请求中有一项cookie,而且cookie内容特别多。

我们可以试着将cookie中的内容放到headers中,然后再发送请求

url = "https://www.mafengwo.cn/i/18252205.html"
headers = {
    "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36",
    "referer": "https://www.mafengwo.cn/i/18252205.html",
    "cookie": "_jsluid_s=...;__jsl_clearance=...;", # 浏览器中的cookie部分,因为太长我就没有全部复制了
}

r = requests.get(url, headers=headers)
print(r.text)

 

 

然后就能正常请求了。

我们也可以慢慢删除cookie中的字段,因为服务器不会验证所有的cookie字段。

经过测试,只要 有__jsluid_s 和__jsl_clearance 字段便可以正常请求。

这,这就完了?no no no。

直接拷贝cookie多low啊。

我们要自己生成cookie,其实也就是想办法构造合理的__jsluid_s和__jsl_clearance 字段, 然后想怎么请求就怎么请求。

 

cookie的生成逻辑

首先啊。我们需要知道怎么清除浏览器的cookie。

为啥子?

因为他的cookie可以重复使用,只有没有cookie或者cookie失效时,才会重新请求。

 

  

如上图,在开发者工具中选中 "Application" 工具栏。找到 "Cookie" 侧边栏。右键那个网址,就会有 "clear"选项了。点击 "clear" 就可以清除改网址的所有cookie

做完这件事情后,就可以再次刷新下页面了。

在刷新页面之前,可以清除下之前的网络请求log。不然会新旧请求会跑到一块,不太好辨别

 

 

好了,下面是刷新后的网络请求

 

  

除了那个正常的200请求。我们还会发现还有一个状态码为521的请求(第一个请求)。

这个不就是我们刚开始使用python代码请求后返回的状态码码?

点进去这个请求看看

 

 

你看,问题解决了一半了。cookie中的 __jsluid_s 字段直接在响应头里了。只要我们搞定 cookie中的 __jsl_clearance 字段便可以发起正常请求了。

下面的代码将获取 __jsluid_s 字段。

import requests

url = "https://www.mafengwo.cn/i/18252205.html"
headers = {
    "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36",
    "referer": "https://www.mafengwo.cn/i/18252205.html",
}
# 请不要传入cookie,不然浏览器不会在请求头中写入我们需要的字段

r = requests.get(url, headers=headers)

print(r.headers)
__jsluid_s = r.headers["Set-Cookie"].split(";")[0].split("=")[1]
print(__jsluid_s) # 这个便是我们需要的cookie的字段

 

那剩下的50%,__jsl_clearance 字段怎么弄呢?仔细翻了下响应头,发现毫无 __jsl_clearance 字段的痕迹。

或许 __jsl_clearance 不在这个 状态码为 521 的请求身上?

 

那我们从第二个请求开始看吧。 

 

第二个请求

 响应头确实有cookie字段,但不是我们想要的

 

继续看第三个请求

 

 

what? 我们需要的cookie字段在这个地方竟然已经被发送出去了。(注意,这里的cookie字段是请求头那里的,而不是响应头那里。

这说明在第三个请求之前, cookie中的__jsl_clearance 字段就已经被设置了。

第二个请求虽然设置了cookie,但不是__jsl_clearance。

那么cookie中的__jsl_clearance 字段只能在第一个请求(状态码为521)里被设置了。

 

那我们回看一下第一个请求。

 

有时候 Response 里不像上图所示的那样,有时会显示 "failed to load response data"。不过没有关系,因为接下来要做的事情与浏览器无关。

如果遇到了 "failed to load response data"。可以直接使用python请求。然后复制了响应内容。(就是上图中的"<script>...")

 

然后选择你最喜欢的一款代码编辑器,新建一个html页面。然后粘贴你之前复制的响应内容。

 如上图,添加一些东西。debugger的作用是当我们打开开发者工具时,代码能在这里停住,以方便我们调试。

 

在浏览器中打开。按F12(mac为fn+F12)打开开发者工具

当我们一打开开发者工具,就会直接跳到 Source 选项卡。并在 "debugger"处停住

 

复制下的代码只有一行,非常难以调试。我们只需点击下上图的 "{}" 便可以格式化代码。

 

格式化后的代码长这个样子

 

 

我一眼就看到了第30行的 eval函数。这个函数能执行js字符串。

eval("console.log('hello world')"); // 这样控制台就会输出 "hello world"

 

 eval执行逻辑

因为代码不是很长,我们并没有选择下断点。我们选择复制js代码,然后扔到console选项卡中运行。

首先复制这一部分代码到console选项卡中

 

 

 

回车运行

 

 第二段要执行的js代码

z++

 

第三段要执行的js代码

y.replace(/\b\w+\b/g, function(y) { return x[f(y, z) - 1] || ("_" + y) })

然后我们就可以看到eval函数要运行的字符串了

 

 

 

 

 

三次快速点击返回的字符串,然后复制一下。选择一个你喜欢的编辑器,再新建一个html文件,将复制的字符串放进去。

记得去除前后的双引号及<script>和</script>。 debugger还是要加的。具体的如下所示。

 

 

同样的,在浏览器中打开此html文件。并打开开发者工具,格式化下代码("{}" 符号)

 

可以看到,cookie中的__jsl_clearance字段生成就在这部分的代码了。

 

 

其实我们需要的那部分代码只是这个

复制这部分代码到console中运行。

 

 

可以发现返回的结果就是我们要的__jsl_clearance字段

为了保险起见,可以复制__jsl_clearance字段和前面生成好的__jsluid_s字段做个测试。(可自行测试)

 

 

python怎么调用js代码?

答案是使用 execjs第三方模块,需要pip安装下。

 

execjs的简单使用

import execjs jsContext = execjs.compile("var sToken='hello js'; function foo(){return sToken}") # 将字符串编译一下 ret = jsContext.call("foo"); # 调用foo函数 print(ret); # 返回 'hello js'

 

解释下eval执行的代码

 

 

 

如何动态修改返回的js代码

好了,现在我们就要想办法动态修改返回的js代码。因为直接执行是不行的。因为execjs的环境并不是浏览器环境

首先 eval执行的代码(也就是上图所示的代码), 我们首先只需要设置cookie的那部分代码。

 

 

 

如何去掉不需要的js代码?(注意! eval执行的是一段字符串)

使用js中的正则即可

evalCode.replace(/^[\w\W]+__jsl_clearance=/, '') .replace(/\+';Expires=[\w\W]+$/, '')

这里的正则便可以帮我们去除多余的部分。

这个js正则其实需要动态的插入要执行的js代码(最开始的那个响应内容中的js代码,以<script>开头的那个)

 

什么意思呢?

这是eval要执行的字符串

 

 

我们要给他换成这个样子

 

 

 

 

这样eval要执行的字符串就会变成这个样子

 

 

这时候eval执行下这个字符串,就能得到最后的结果啦。(最好是先不eval这个字符串, 先返回这个要进行eval的字符串,然后再运行一次)。

 

注意!!!!(2020.4月27日网站逻辑增加)

有时候eval返回的字符串直接执行的话会有问题的。(显示document is not defined), 类似的代码如下

 

 

 

这里的做法比较聪明,因为只有浏览器才有document对象,并且它还调用了document对象的方法

如果不是浏览器环境,不做一定的处理的,就会运行报错。

 

稍微解释下,那一行代码的含义吧

_4k=document.createElement('div');
// 创建一个div标签。 <div></div>

_4k.innerHTML="<a href=\'/\'>_i</a>";
// \' 是转义。
// 其实就等于_4k.innerHTML="<a href='/'>_i</a>";
// 设置这个div标签的 子内容(会被自动解析为html)
// <div>
//     <a href='/'>_i</a>
// </div>

_4k=_4k.firstChild.href;
// 获取div标签的第一个子元素的href属性
// _4k 的结果便是 当前的网址的根路径(即"https://www.mafengwo.cn/")

var_5b=_4k.match(/https?:\/\//[0];
// re正则表达式。_5b的结果是 "https://"

_4k=_4k.substr(_5b.length).toLowerCase();
// substr 用于切割字符串,接受两个参数,第一个参数切割的开始位置。第二个参数是要切多少
// toLowerCase()方法是将字符串中的字母转为小写
// _4k 的结果是 "www.mafengwo.cn/"

 

貌似这个代码其实是固定的,但变量名是不同的,我们需要定义一下document对象,然后给他一定的方法就可以

var document = { createElement: function(tag){ var innerHTML; return { firstChild: { href: "https://www.mafengwo.cn/" } } } };

 

上面定义的document便提供eval字符串中所需要的东西,接着我们将这段代码插入到execjs中便可以正常运行了。

 

具体的代码实现

import requests
import re
import execjs


def changeJsRunTimeCodeAndGetClearance(content):
    # print(content)
    insertJsCode = """
    .replace(/^[\w\W]+__jsl_clearance=/, '')
    .replace(/\+';Expires=[\w\W]+$/, '')
    """
    evalJsCode = content.replace('("_"+y)})', '("_"+y)})' + insertJsCode)
    # 这里本来是要eval下这个js字符串的,但我们这里是返回了这个js字符串
    evalJsCode = evalJsCode.replace("<script>", "").replace(
        "eval(y.replace(/", "return (y.replace(/"
    )
    # print(evalJsCode)
    # # 这里先找到</script>的位置,是为了把</script>后面的字符串全部清除掉
    index = evalJsCode.index("</script>")
    # # 记得要定义一下window对象。
    documentCode = """
        var document = {
        createElement: function(tag){
            var innerHTML;
            return {
                firstChild: {
                    href: "https://www.mafengwo.cn/"
                }
            }
        }
    };
    """
    evalJsCode = (
        "var window = {};"
        + documentCode
        + "function exec(){"
        + evalJsCode[0:index]
        + "}"
    )
    # print(evalJsCode)
    context = execjs.compile(evalJsCode)
    # print(context.call("exec"))
    finalContext = execjs.compile(
        # context.call("exec")是用来调用前面的exec函数的
        "var window={};"
        + documentCode
        + "function final(){ return '"
        + context.call("exec")
        + "}"
    )
    finalVal = finalContext.call("final")
    return finalVal


def getContent(
    url, __jsluid_s="", __jsl_clearance="",
):
    url = "https://www.mafengwo.cn/i/18252205.html"
    headers = {
        "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/527.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36",
        "referer": "https://www.mafengwo.cn/i/18252205.html",
    }
    if __jsluid_s != "" and __jsl_clearance != "":
        cookie = "__jsluid_s=%s; __jsl_clearance=%s;" % (__jsluid_s, __jsl_clearance)
        headers.update({"cookie": cookie})

    r = requests.get(url, headers=headers)
    print(r.headers)
    # print(r.text)
    print(r.status_code)
    if r.text.startswith("<script>"):
        # cookie错误
        __jsluid_s = r.headers["Set-Cookie"].split(";")[0].split("=")[1]
        print("__jsluid_s", __jsluid_s)
        __jsl_clearance = changeJsRunTimeCodeAndGetClearance(r.text)
        print("__jsl_clearance", __jsl_clearance)
        # # 再请求一遍
        getContent(url, __jsluid_s=__jsluid_s, __jsl_clearance=__jsl_clearance)
    if not r.text.startswith("<script>"):
        print(r.text)
    # content = r.text


getContent("https://www.mafengwo.cn/i/18252206.html")


# '__jsluid_s=9a91972ada0d6431c85c51308d9ca2d6
# changeJsRunTimeCode(content)

# document.createElement('div');
# _4k.innerHTML="<a href=\'/\'>_i</a>";
# _4k=_4k.firstChild.href;

View Code

 

posted @ 2020-04-20 14:58  re大法好  阅读(1470)  评论(0编辑  收藏  举报