馬蜂窩逆向


前言

如果想看實現可以跳到最后(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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM