1. 程式人生 > >新浪微博PC端登陸js分析及Python實現微博post登陸

新浪微博PC端登陸js分析及Python實現微博post登陸

新浪微博的安全級別還是比較高,前端的資訊採用RSA非對稱加密方式,加密的內容處理過,不僅僅是使用者輸入的密碼,加密公鑰是實時請求而來。

首選抓個包瞧瞧:
在這裡插入圖片描述

entry:weibo
gateway:1
from:
savestate:7
qrcode_flag:false
useticket:1
pagerefer:https://login.sina.com.cn/crossdomain2.php?action=logout&r=https%3A%2F%2Fpassport.weibo.com%2Fwbsso%2Flogout%3Fr%3Dhttps%253A%252F%252Fweibo.com%26returntype%3D1

vsnf:1
su:MjU1MTUxMzI3NyU0MHFxLmNvbQ==
service:miniblog
servertime:1546480046
nonce:I9GQBH
pwencode:rsa2
rsakv:1330428213
sp:9c5a21128f93777c34635102b38b64247b32ad7084baa2acf48ff75bd633e6ffb4ea009083136251fe9846f113a528f4bf4fa002c2539bcd30fcc11a9ffb69bf7432f18e0bf6382bcc245da01db533ba700d19937c467abbfcf6b2281b21c8b0cf663b374d10974eef1d41b49331859c777ca213961c0b82d4252a6ca4abff41

sr:1920*1080
encoding:UTF-8
prelt:132
url:https://weibo.com/ajaxlogin.php?framelogin=1&callback=parent.sinaSSOController.feedBackUrlCallBack
returntype:META

看樣子sp就是password的簡稱了,也就是我們主要分析的東西。

然後在看看其他請求又發現了一個重要的get請求

Request URL:https://login.sina.com.cn/sso/prelogin.php?entry=weibo&callback=sinaSSOController.preloginCallBack&su=MjU1MTUxMzI3NyU0MHFxLmNvbQ%3D%3D&rsakt=mod&checkpin=1&client=ssologin.js(v1.4.19)&_=1546480024174
Request Method:GET
Status Code:200
 OK
Remote Address:58.63.236.212:443
Referrer Policy:no-referrer-when-downgrade

這個請求返回的資訊如下:

exectime:10
nonce:"I9GQBH"
pcid:"gz-1e98a6711bbfc6a65bdc7e267bfe417d9637"
pubkey:"EB2A38568661887FA180BDDB5CABD5F21C7BFD59C090CB2D245A87AC253062882729293E5506350508E7F9AA3BB77F4333231490F915F6D63C55FE2F08A49B353F444AD3993CACC02DB784ABBB8E42A9B1BBFFFB38BE18D78E87A0E41B9B8F73A928EE0CCEE1F6739884B9777E4FE9E88A1BBE495927AC4A799B3181D6442443"
retcode:0
rsakv:"1330428213"
servertime:1546480024

post請求中有幾個引數就是這裡面的,可以直接拿來用,但是這個get請求又傳回了幾個莫名引數如下:

entry:weibo
callback:sinaSSOController.preloginCallBack
su:MjU1MTUxMzI3NyU0MHFxLmNvbQ==
rsakt:mod
checkpin:1
client:ssologin.js(v1.4.19)
_:1546480024174

看樣子除了su像個加密引數,其他的好像是固定的,“_”像當前的時間戳,其他的就用固定值吧,用線上工具測試一下,能正確返回資訊
在這裡插入圖片描述

現在就清楚了,我們需要分析su、sp這兩個引數,其中su應該和使用者名稱相關,而sp根據第一get請求返回的pubkey應該聯想到RSA加密。

下面就具體分析加密方式,全域性搜sp找疑似發出登陸post請求的程式碼

 makeRequest = function(a, b, c, d) {
        var e = {
            entry: me.getEntry(),
            gateway: 1,
            from: me.from,
            savestate: c,
            qrcode_flag: d,
            useticket: me.useTicket ? 1 : 0
        };
        me.failRedirect && (me.loginExtraQuery.frd = 1);
        e = objMerge(e, {
            pagerefer: document.referrer || ""
        });
        e = objMerge(e, me.loginExtraFlag);
        e = objMerge(e, me.loginExtraQuery);
        e.su = sinaSSOEncoder.base64.encode(urlencode(a));
        me.service && (e.service = me.service);
        if (me.loginType & rsa && me.servertime && sinaSSOEncoder && sinaSSOEncoder.RSAKey) {
            e.servertime = me.servertime;
            e.nonce = me.nonce;
            e.pwencode = "rsa2";
            e.rsakv = me.rsakv;
            var f = new sinaSSOEncoder.RSAKey;
            f.setPublic(me.rsaPubkey, "10001");
            b = f.encrypt([me.servertime, me.nonce].join("\t") + "\n" + b)
        } else if (me.loginType & wsse && me.servertime && sinaSSOEncoder && sinaSSOEncoder.hex_sha1) {
            e.servertime = me.servertime;
            e.nonce = me.nonce;
            e.pwencode = "wsse";
            b = sinaSSOEncoder.hex_sha1("" + sinaSSOEncoder.hex_sha1(sinaSSOEncoder.hex_sha1(b)) + me.servertime + me.nonce)
        }
        e.sp = b;
        try {
            e.sr = window.screen.width + "*" + window.screen.height
        } catch (g) {}
        return e
    }


其中這四行程式碼就是我們需要的東西

e.su = sinaSSOEncoder.base64.encode(urlencode(a));

var f = new sinaSSOEncoder.RSAKey;

f.setPublic(me.rsaPubkey, "10001");

b = f.encrypt([me.servertime, me.nonce].join("\t") + "\n" + b)

e.su、b就是我們需要的su和sp了,接下來就是繼續深入查詢原始碼,首先分析su,urlencode(a)是把a引數做url編碼,sinaSSOEncoder.base64.encode應該是base64編碼,但還不確定是不是標準的base64編碼,在線上工具測試一下:

在這裡插入圖片描述

返回結果和我們get請求及post請求中的su一致,那麼就是標準的base64加密,後面可以當常量用,也可以寫個函式每次根據使用者名稱生成su,這個加密引數算是解決了。

接著來看sp,根據上面的三行程式碼就大概清楚了sp是RSA加密,偏移量是10001,加密內容是[me.servertime, me.nonce].join("\t") + “\n” + b,也就是在第一次請求返回來的servertime+"\t"+nonce+"\n"+使用者密碼

接著看加密原始碼:

 var navigator={};
function SSOController() {
var undefined, me = this,
updateCookieTimer = ,
updateCookieTimeHardLimit = 1800,
cookieExpireTimeLength = 86400,
crossDomainForward = ,
crossDomainTimer = ,
crossDomainTime = 3,

········

pcid = "",
tmpData = {},
preloginTimeStart = 0,
preloginTime = 0,
callbackLogoutStatus;
this.https = 1;
this.rsa = 2;
this.wsse = 4;
this.name = "sinaSSOController";
this.loginFormId = "ssoLoginForm";
this.scriptId = "ssoLoginScript";
this.ssoCrossDomainScriptId = "ssoCrossDomainScriptId";
this.loginFrameName = "ssoLoginFrame";
this.appLoginURL = {
    "weibo.com""https://passport.weibo.com/wbsso/login"
};
this.appDomainService = {
    "weibo.com""miniblog"
};
this.loginExtraQuery = {};
this.setDomain = !1;
this.feedBackUrl = "";
this.service = "sso";
this.domain = "sina.com.cn";
this.from = "";

·················

        return ! 1
    }
    return ! 0
};
this.shouldGenerateVisitor = function() {
    var a = !1,
    b = !1,
    c = getCookie("SUBP");
    c && (a = !0);
    var d = getCookie("SUP");
    d && (b = !0);
    return ! a && !b ? !0 : !1
}
}


var sinaSSOEncoder = sinaSSOEncoder || {}; 



(function() {
var a = 0,
b = 8;
this.hex_sha1 = function(a) {
    return i(c(h(a), a.length * b))
};
var c = function(a, b) {
    a[b >> 5] |= 128 << 24 - b % 32;
    a[(b + 64 >> 9 << 4) + 15] = b;
    var c = Array(80),
    h = 1732584193,
    i = -271733879,
    j = -1732584194,
    k = 271733878,
    l = -1009589776;
    for (var m = 0; m < a.length; m += 16) {
        var n = h,
        o = i,
        p = j,
        q = k,
        r = l;
        for (var s = 0; s < 80; s++) {
            s < 16 ? c[s] = a[m + s] : c[s] = g(c[s - 3] ^ c[s - 8] ^ c[s - 14] ^ c[s - 16], 1);
            var t = f(f(g(h, 5), d(s, i, j, k)), f(f(l, c[s]), e(s)));
            l = k;
            k = j;
            j = g(i, 30);
            i = h;
            h = t
        }
        h = f(h, n);
        i = f(i, o);
        j = f(j, p);
        k = f(k, q);
        l = f(l, r)
    }
    return [h, i, j, k, l]
},

······················

l = "0".charCodeAt(0);
for (m = 0; m <= 9; ++m) k[l++] = m;
l = "a".charCodeAt(0);
for (m = 10; m < 36; ++m) k[l++] = m;
l = "A".charCodeAt(0);
for (m = 10; m < 36; ++m) k[l++] = m;
J.prototype.convert = K;
J.prototype.revert = L;
J.prototype.reduce = M;
J.prototype.mulTo = N;
J.prototype.sqrTo = O;
Q.prototype.convert = R;
Q.prototype.revert = S;
Q.prototype.reduce = T;
Q.prototype.mulTo = V;

··········

typeof window.crypto.random == "function") {
        var bi = window.crypto.random(32);
        for (bh = 0; bh < bi.length; ++bh) bd[be++] = bi.charCodeAt(bh) & 255
    }
    while (be < bb) {
        bh = Math.floor(65536 * Math.random());
        bd[be++] = bh >>> 8;
        bd[be++] = bh & 255
    }
    be = 0;
    bg()
}
bl.prototype.nextBytes = bk;
bq.prototype.doPublic = bs;
bq.prototype.setPublic = br;
bq.prototype.encrypt = bt;
this.RSAKey = bq
}).call(sinaSSOEncoder);

程式碼太長共有1900行左右,文中有刪減,主要看三個地方:

定義了一個SSOController函式

定義了一個var sinaSSOEncoder = sinaSSOEncoder || {},

有個自執行函式傳入了SSOController,().call(sinaSSOEncoder)

其中“||”作用是返回兩邊為真的值,如果都為真則返回左邊

call()作用:在特定的作用域中呼叫函式,等於設定函式體內this物件的值,以擴充函式賴以執行的作用域

大致就清楚了首先建立了一個SSOController物件實現RSA的基本加密步驟,然後新定義一個sinaSSOEncoder用於繼承SSOController,而最後一個自執行函式的作用就是繼承SSOController的具體方法。

如果呼叫的話應該是下面方式:

su = sinaSSOEncoder.base64.encode("[email protected]")

在這裡插入圖片描述

sp = function getsp(key, servertime, nonce, pw) {
var f = new sinaSSOEncoder.RSAKey;
f.setPublic(key, “10001”);
var b = f.encrypt(servertime + “\t” + nonce + “\n” + pw);
return b
}
在這裡插入圖片描述

之前寫過一個Python實現RSA-AES-MD5-DES-DES3-MD5-SHA-HMAC的指令碼,用Python實現RSA加密可看這篇文章《Python實現DES、DES3、AES、RSA、MD5、SHA、HMAC加密方式及示例》

到此關鍵的兩個引數就分析完了。

順便在說一下PC端微博登陸的安全規則,首先是無異常賬號,在常用ip地址段登陸,前幾次登陸基本不需要驗證,如果錯誤了幾次就需要驗證,登陸後會返回重定向的地址,會被重定向兩次才會登陸成功,獲取到使用者資訊。

關於微博的登陸上個月寫個一個類,上傳到github獲取連線,點選閱讀原文。


ID:Python之戰

|作|者|公(zhong)號:python之戰

專注Python,專注於網路爬蟲、RPA的學習-踐行-總結

喜歡研究和分享技術瓶頸,歡迎關注

獨學而無友,則孤陋而寡聞!