打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
腾讯的网站如何检测到你的 QQ 已经登录? – 尘埃落定
2012-07-01 18:04
在 QQ 已经登录的情况下,手动输入网址打开 QQ 邮箱 或者 QQ 空间 等腾讯网站,可以看到网页已经检测到本地 QQ 客户端已经登录,于是用户可以很方便地一键登录网站而不必再输入用户名密码。这实际上是典型的异构系统单点登录 SSO(single-sign-on)技术。网页怎么会知道我登录的 QQ 号码?腾讯是如何实现的呢?
网上有很多猜测,比如——
QQ 登录时在本地某地方存登录 ID 信息(Cookie 或文件),用 js 读,然后去服务器认证。但是现在的浏览器一般有沙箱功能,js 无法读到登录 ID;而且在清空 Cookie 后依然起作用。
以 IP、CPU ID、硬盘 ID 等硬件设备 hash 做唯一标识,QQ 登录时在服务器记录此信息,js 验证。感觉这样依赖环境过多,QQ 不太可能采用此方法。
QQ 启动某端口监听,js 连接此端口。但是用 netstat 查看后,QQ 并没有监听端口。
有这么一个神奇的链接,http://xui.ptlogin2.qq.com/cgi-bin/qlogin 你一点开,它就检测到你登录了 QQ。通过查看页面源代码,我们可以发现一个关于 ptlogin 的 js 文件,这段代码中,描述了使用 ActivexObject 浏览器插件的过程,于是一切了然。
可是 ActiveX 是 IE 的插件呀,我们使用 Chrome 或者 FireFox 也是可以直接登录的,这是怎么回事呢?
原来,QQ 使用了历史很悠久的 NPAPI(Netscape Plugin Application Programming Interface)接口。NPAPI 几乎支持所有主流浏览器,包括 FireFox、Chrome、Opera(IE 从 5.5 后停止支持 NPAPI,转而使用 ActiveX)。
打开 chrome://plugins/ 我们可以发现自动登录的有关插件,而在路径 C:\Program Files (x86)\Common Files\Tencent\TXSSO 下就可以找到关于 SSO 的相关动态链接库。
np 插件一般命名都会加np前缀 如 QQ 的这个 npSSOAxCtrlForPTLogin.dll,只要按照标准的写法,放在浏览器会加载的地方,用的时候写个标签就可以在 js 里面调用了。于是跨浏览器(无视 IE)的插件开发变得相当可行。运行在 NPAPI 插件中的代码拥有当前用户的所有权限,不在沙箱中运行,所以它的扩展程序在被 Chrome 网上应用店接受前要求人工审核。
有点不怀好意的想法开始萌生,我自己的网站能否借用这个插件来检测用户的 QQ 登录呢?写个页面测试一下。
<!DOCTYPE html><html><head><meta charset="utf-8"><title>Tencent SSO Testing</title></head><body><script>var g_vOptData;var mylocation= "xui.ptlogin2.qq.com/cgi-bin1/qlogintest.html";var pt = { ishttps: false, low_login: 0, keyindex: 9, init: function() {  pt.ishttps = /^https/.test(mylocation);  //if (navigator.mimeTypes["application/nptxsso"]) {  var B = document.createElement("embed");  B.type = "application/nptxsso";  B.style.width = "0px";  B.style.height = "0px";  document.body.appendChild(B);  pt.sso = B }};pt.init(); try { if (window.ActiveXObject) {  q_hummerQtrl = new ActiveXObject("SSOAxCtrlForPTLogin.SSOForPTLogin2");  var A = q_hummerQtrl.CreateTXSSOData();  q_hummerQtrl.InitSSOFPTCtrl(0, A);  g_vOptData = q_hummerQtrl.CreateTXSSOData() } hummer_loaduin();} catch(B) { alert(/create ActiveXObject failed/)} function hummer_loaduin(){ if (window.ActiveXObject) {  var Y = q_hummerQtrl.DoOperation(1, g_vOptData);  if (null == Y) {   return  }  try  {   var T = Y.GetArray("PTALIST");   var c = T.GetSize();   var X = "";   for (var d = 0; d < c; d++)   {    var E = T.GetData(d);    var a = E.GetDWord("dwSSO_Account_dwAccountUin");    var J = "";    var O = E.GetByte("cSSO_Account_cAccountType");    var b = a;    if (O == 1)    {     try     {      J = E.GetArray("SSO_Account_AccountValueList");      b = J.GetStr(0)     } catch(Z) {}    }    var Q = 0;    try {    Q = E.GetWord("wSSO_Account_wFaceIndex")    } catch(Z) {    Q = 0    }    var S = "";    try {    S = E.GetStr("strSSO_Account_strNickName")    } catch(Z) {    S = ""    }    var F = E.GetBuf("bufGTKey_PTLOGIN");    var G = E.GetBuf("bufST_PTLOGIN");    var N = "";    var A = G.GetSize();    for (var W = 0; W < A; W++) {    var B = G.GetAt(W).toString("16");    if (B.length == 1) {    B = "0" + B    }    N += B    }    var M = {     uin: a,     name: b,     type: O,     face: Q,     nick: S,     key: N    };    var str = "QQinfo\r\n"+        "uin:" + M['uin']+"\r\n"+        "name:"+M['name']+"\r\n"+        "type:"+M['type']+"\r\n"+        "face:"+M['face']+"\r\n"+        "nick:"+M['nick']+"\r\n"+        "key:"+M['key']+"\r\n";    alert(str);    q_aUinList[d] = M   }  } catch(Z) {}  } else  {  try {   var M = pt.sso;   var L = M.InitPVA();   if (L != false)   {    var I = M.GetPVACount();    for (var W = 0; W < I; W++)    {     var C = M.GetUin(W);     var D = M.GetAccountName(W);     var K = M.GetFaceIndex(W);     var U = M.GetNickname(W);     var P = M.GetGender(W);     var V = M.GetUinFlag(W);     var f = M.GetGTKey(W);     var R = M.GetST(W);    }    var str = "QQinfo\r\n"+        "uin:" + C +"\r\n"+        "name:"+D+"\r\n"+        "face:"+K +"\r\n"+        "nick:"+U+"\r\n"+        "key:"+f+"\r\n";    alert(str);   }  } catch(Z) {}  } }</script></body></html>
本地打开此页面,create ActiveXObject 失败。腾讯必然在 dll 中就对域名进行了限制,网页是无法篡改的。于是修改本地host文件,加一条:
127.0.0.1 xui.ptlogin2.qq.com
再用 xui.ptlogin2.qq.com 这个域名去访问本地的这个 html,果然,可以正常拿到 QQ 相关信息。
好吧,这个截图被我打码打得没啥意义了
PS. Chrome 浏览器自带的开发者工具有一个功能可以格式化被压缩的 js 代码,十分好用。废话不多说,有图说明一切——
附xu.js格式化后的代码
function $(A) {
return document.getElementById(A)
}
$.bom = {query: function(B) {
var A = window.location.search.match(new RegExp("(/?|&)" + B + "=([^&]*)(&|$)"));
return !A ? "" : unescape(A[2])
},getHash: function() {
}};
var pt = {ishttps: false,low_login: 0,keyindex: 9,init: function() {
pt.ishttps = /^https/.test(window.location);
if (navigator.mimeTypes["application/nptxsso"]) {
var B = document.createElement("embed");
B.type = "application/nptxsso";
B.style.width = "0px";
B.style.height = "0px";
document.body.appendChild(B);
pt.sso = B
}
try {
if ($.bom.query("low_login") == "1") {
pt.low_login = 1;
$("low_login_box").style.display = "block"
}
} catch (A) {
}
window.setTimeout(function() {
ptui_reportAttr(256040, 0.05)
}, 1000)
},switchLowLogin: function(A) {
if (A.checked) {
$("low_login_hour").disabled = ""
} else {
$("low_login_hour").disabled = "disabled"
}
}};
pt.init();
STR_QLOGIN = 1;
STR_QLOGIN_OTHER_ERR = 2;
STR_QLOGIN_SELECT_TIP = 3;
STR_QLOGIN_NO_UIN = 4;
STR_QLOGIN_SELECT_OFFLINE = 5;
STR_QLOGINING = 6;
function ptui_mapStr(B) {
for (i = 0; i < B.length; i++) {
var A = $(B[i][1]);
if (A != null) {
if ("A" == A.nodeName || "U" == A.nodeName || "OPTION" == A.nodeName || "LABEL" == A.nodeName || "P" == A.nodeName) {
if (A.innerHTML == "") {
A.innerHTML = ptui_str(B[i][0])
}
} else {
if ("INPUT" == A.nodeName) {
if (A.value == "") {
A.value = ptui_str(B[i][0])
}
} else {
if ("IMG" == A.nodeName) {
A.alt = ptui_str(B[i][0])
}
}
}
}
}
}
function ptui_str(A) {
A -= 1;
if (A >= 0 && A < g_strArray.length) {
return g_strArray[A]
}
return ""
}
var g_labelMap = new Array([STR_QLOGIN, "loginbtn"], [STR_QLOGIN_SELECT_TIP, "qlogin_select_tip"]);
ptui_mapStr(g_labelMap);
function getArgs() {
var B = new Object();
try {
var F = location.href.substring(location.href.indexOf("/qlogin?") + 8);
var E = F.split("&");
for (var C = 0; C < E.length; C++) {
var H = E[C].indexOf("=");
if (H == -1) {
continue
}
var A = E[C].substring(0, H);
var D = E[C].substring(H + 1);
D = decodeURIComponent(D);
B[A] = D
}
} catch (G) {
setTimeout(arguments.callee, 0)
}
return B
}
var params = getArgs();
var g_qtarget = params.qtarget;
var g_domain = params.domain;
var g_jumpname = params.jumpname;
var g_param = params.param;
var site = ["qq.com", "paipai.com", "tencent.com", "soso.com", "taotao.com", "tenpay.com", "foxmail.com", "wenwen.com", "3366.com", "imqq.com", "pengyou.com", "qplus.com", "qzone.com", "myapp.com", "kuyoo.cn", "weiyun.com", "wechatapp.com", "51buy.com", "gaopeng.com", "qcloud.com", "qmail.com"];
var flag = false;
for (var i = 0; i < site.length; i++) {
if (site[i] == g_domain) {
flag = true
}
}
if (!flag) {
g_domain = "qq.com"
}
var q_bInit = false;
var q_hummerQtrl = null;
var g_vOptData = null;
var q_aUinList = new Array();
function ptui_qInit() {
if (q_bInit) {
return
}
q_bInit = true;
try {
if (window.ActiveXObject) {
q_hummerQtrl = new ActiveXObject("SSOAxCtrlForPTLogin.SSOForPTLogin2");
var A = q_hummerQtrl.CreateTXSSOData();
q_hummerQtrl.InitSSOFPTCtrl(0, A);
g_vOptData = q_hummerQtrl.CreateTXSSOData()
} else {
}
hummer_loaduin();
if (q_aUinList.length <= 0) {
msg(ptui_str(STR_QLOGIN_NO_UIN));
return false
} else {
if (ptui_buildUinList) {
ptui_buildUinList(q_aUinList)
}
}
document.cookie = "ptui_qstatus=2;domain=ptlogin2." + g_domain + ";path=/"
} catch (B) {
q_hummerQtrl = null;
document.cookie = "ptui_qstatus=3;domain=ptlogin2." + g_domain + ";path=/";
msg(ptui_str(STR_QLOGIN_OTHER_ERR));
ptui_reportAttr(89217, 0.05)
}
}
function list() {
$("qlogin_loading").style.visibility = "hidden";
if (/^https/g.test(window.location)) {
$("qlogin_loading").innerHTML = '<img src="https://xui.ptlogin2.' + g_domain + '/style.ssl/0/images/load.gif" align="absmiddle" />' + ptui_str(STR_QLOGINING)
} else {
$("qlogin_loading").innerHTML = '<img src="http://imgcache.qq.com/ptlogin/v4/style/0/images/load.gif" align="absmiddle" />' + ptui_str(STR_QLOGINING)
}
q_bInit = false;
ptui_qInit();
if (/^https/g.test(window.location)) {
return
}
if (window.g_time) {
g_time.time55 = new Date()
}
xui_report()
}
function ptui_buildUinList() {
var G = "";
var E = $("list_uin");
if (null == E) {
return
}
var A = q_aUinList.length > 5 ? 5 : q_aUinList.length;
for (var C = 0; C < A; C++) {
var F = q_aUinList[C];
var B = "";
var D = "";
if (q_aUinList.length == 1) {
D = 'style="display:none;"'
}
if (C == 0) {
B = "checked='checked'"
}
G += "<li><input type='radio' name='q_uin' id='uin_" + F.uin + "' " + B + D + " /><label for='uin_" + F.uin + "'>" + F.nick.replace(/&/g, "&amp").replace(/</g, "&lt").replace(/>/g, "&gt") + " (" + F.name + ")</label></li>"
}
E.innerHTML = G
}
function onQloginSelect() {
for (var C = 0; C < q_aUinList.length; C++) {
var D = q_aUinList[C];
var B = $("uin_" + D.uin);
if (B != null) {
if (B.checked) {
hummer_loaduin();
var A = hummer_getUinObj(D.uin);
if (A == null) {
msg(ptui_str(STR_QLOGIN_SELECT_OFFLINE), D.uin);
return
}
$("qlogin_loading").style.visibility = "visible";
$("loginbtn").className = "btn_gray";
$("loginbtn").style.color = "gray";
hummer_login(A, g_domain, g_jumpname, g_param)
}
}
}
}
function hummer_loaduin() {
q_aUinList.length = 0;
if (window.ActiveXObject) {
var Y = q_hummerQtrl.DoOperation(1, g_vOptData);
if (null == Y) {
return
}
try {
var T = Y.GetArray("PTALIST");
var c = T.GetSize();
var X = "";
var H = $("list_uin");
for (var d = 0; d < c; d++) {
var E = T.GetData(d);
var a = E.GetDWord("dwSSO_Account_dwAccountUin");
var J = "";
var O = E.GetByte("cSSO_Account_cAccountType");
var b = a;
if (O == 1) {
try {
J = E.GetArray("SSO_Account_AccountValueList");
b = J.GetStr(0)
} catch (Z) {
}
}
var Q = 0;
try {
Q = E.GetWord("wSSO_Account_wFaceIndex")
} catch (Z) {
Q = 0
}
var S = "";
try {
S = E.GetStr("strSSO_Account_strNickName")
} catch (Z) {
S = ""
}
var F = E.GetBuf("bufGTKey_PTLOGIN");
var G = E.GetBuf("bufST_PTLOGIN");
var N = "";
var A = G.GetSize();
for (var W = 0; W < A; W++) {
var B = G.GetAt(W).toString("16");
if (B.length == 1) {
B = "0" + B
}
N += B
}
var M = {uin: a,name: b,type: O,face: Q,nick: S,key: N};
q_aUinList[d] = M
}
} catch (Z) {
}
} else {
try {
var M = pt.sso;
var L = M.InitPVA();
if (L != false) {
var I = M.GetPVACount();
for (var W = 0; W < I; W++) {
var C = M.GetUin(W);
var D = M.GetAccountName(W);
var K = M.GetFaceIndex(W);
var U = M.GetNickname(W);
var P = M.GetGender(W);
var V = M.GetUinFlag(W);
var f = M.GetGTKey(W);
var R = M.GetST(W);
q_aUinList[W] = {uin: C,name: D,type: 0,face: K,nick: U,key: R}
}
if (typeof (M.GetKeyIndex) == "function") {
pt.keyindex = M.GetKeyIndex()
}
}
} catch (Z) {
}
}
switch (q_aUinList.length) {
case 0:
ptui_reportAttr(77430, 0.05);
break;
case 1:
ptui_reportAttr(77431, 0.05);
break;
default:
ptui_reportAttr(77432, 0.05)
}
}
function hummer_getUinObj(B) {
for (var A = 0; A < q_aUinList.length; A++) {
var C = q_aUinList[A];
if (C.uin == B) {
return C
}
}
return null
}
function unloadpage() {
document.domain = g_domain;
try {
parent.document.body.onbeforeunload = function() {
};
parent.document.body.onunload = function() {
};
for (var A = 0; A < parent.parent.frames.length; A++) {
parent.parent.frames[A].onunload = function() {
};
parent.parent.frames[A].onbeforeunload = function() {
}
}
if (parent.parent != top) {
for (var A = 0; A < parent.parent.parent.frames.length; A++) {
parent.parent.parent.frames[A].onunload = function() {
};
parent.parent.parent.frames[A].onbeforeunload = function() {
}
}
}
} catch (B) {
}
}
function hummer_login(G, F, A, H) {
if (A == "") {
A = "jump"
}
var E = (pt.ishttps ? "https://ssl.ptlogin2." : "http://ptlogin2.") + F + "/" + A + "?";
var C = $.bom.query("daid");
var D = $.bom.query("regmaster");
if (D == 2 && !pt.ishttps) {
E = "http://ptlogin2.function.qq.com/jump?regmaster=2&"
} else {
if (D == 3 && !pt.ishttps) {
E = "http://ptlogin2.crm2.qq.com/jump?regmaster=3&"
}
}
E += "clientuin=" + G.uin + "&clientkey=" + G.key + "&keyindex=" + pt.keyindex + (C ? "&daid=" + C : "");
if (pt.low_login == 1 && $("low_login_enable") && $("low_login_enable").checked) {
E += "&low_login_enable=1&low_login_hour=" + $("low_login_hour").value
}
if (H != null && H != "") {
var B = decodeURIComponent(H);
if (B.indexOf("#") > -1) {
B = B.replace(/#/g, "%23")
}
E += ("&" + B)
}
switch (parseInt(g_qtarget)) {
case 0:
unloadpage();
parent.location.href = E;
break;
case 1:
top.location.href = E;
break;
case 2:
unloadpage();
parent.parent.location.href = E;
break;
default:
top.location.href = E
}
}
function msg(A, B) {
A = '<span style="color:#cc0000;">' + A + '</span><a href="http://support.qq.com/write.shtml?guest=1&fid=713&SSTAG=10011-' + B + '" target="_blank">' + g_strArray[6] + "</a>";
try {
var D = $("qlogin_loading");
if ((D.style.display != "none") && ($("qlogin").style.display != "none")) {
D.innerHTML = A;
D.style.display = "";
D.style.visibility = "visible"
}
} catch (C) {
}
}
function browser_version() {
var A = navigator.userAgent.toLowerCase();
return A.match(/msie ([\d.]+)/) ? 2 : A.match(/firefox\/([\d.]+)/) ? 4 : A.match(/chrome\/([\d.]+)/) ? 6 : A.match(/opera.([\d.]+)/) ? 10 : A.match(/version\/([\d.]+).*safari/) ? 13 : 2
}
function xui_speedReport(E) {
if (pt.isHttps || (window.flag2 && Math.random() > 0.5) || (!window.flag2 && Math.random() > 0.01)) {
return
}
var B = "http://isdspeed.qq.com/cgi-bin/r.cgi?flag1=6000&flag2=1&flag3=" + browser_version();
var C = 0;
for (var D in E) {
if (E[D] < 0 || E[D] > 300000) {
continue
}
B += "&" + D + "=" + E[D];
C++
}
if (C == 0) {
return
}
var A = new Image();
A.src = B
}
function xui_report() {
if (Math.random() > 0.5) {
return
}
if (!window.g_time) {
return
}
if (g_time.time50 && g_time.time50 > 0 && g_time.time51 && g_time.time51 > 0 && g_time.time52 && g_time.time52 > 0 && g_time.time53 && g_time.time53 > 0) {
var A = {};
A["1"] = g_time.time51 - g_time.time50;
A["6"] = g_time.time52 - g_time.time50;
A["2"] = g_time.time54 - g_time.time50;
A["3"] = g_time.time55 - g_time.time50;
A["4"] = g_time.time54 - g_time.time53;
A["5"] = g_time.time55 - g_time.time53
}
xui_speedReport(A)
}
function ptui_reportAttr(C, B) {
if (Math.random() > (B || 1)) {
return
}
url = location.protocol + "//ui.ptlogin2.qq.com/cgi-bin/report?id=" + C;
var A = new Image();
A.src = url
}
function pluginBegin() {
}
list();
try {
$("loginbtn").focus()
} catch (e) {
}
;
参考链接:
http://1.lanz.sinaapp.com/?p=152
http://taurus-ly.com/articles/2012/02/153.html
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
易语言webqq加密js参数分析
【 POST/GET教程】登录QQ空间发表日志&POST上传相册图片
C# 登录QQ网站并获取QQ相关信息
如何进入设了密码的qq空间
浏览器唤起qq进行聊天的一些坑和解决方案
WebQQ协议分析(1)——登录
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服