zoukankan      html  css  js  c++  java
  • 浅析 天涯论坛 回复验证策略

    发帖没多久,算法就更新了,就算我重新分析,人家依然会更新,所以还是自己学着分析吧。

    对于现在 POST 技术满天飞的时代,防机器人确实是很头疼的一件事情,类似流量精灵这样的东西,他可以做到 100% 的真实信息,大批量的访问。
    当然今天不谈这些,只是分析下 天涯论坛 回复时的验证策略。

    昨天谈到 packer 压缩,今天我们来看个实例吧。
    http://bbs.tianya.cn/m/reply.jsp?item=funinfo&id=4339425
    这个是天涯论坛手机端的回复帖子页面,里面有一个关于回复验证的js,就是用的 packer压缩。
    http://static.tianyaui.com/global/ty/util/TY.util.userAction.js?v=201404111018

    真心不知道他们是怎么想的,1秒还原大法。。。

    jQuery(function() {
        function i(B) {
            var D, l, n, C = document.cookie.substr(document.cookie.indexOf("&id=") + "&id=".length);
            C = C.substr(0, C.indexOf("&")), C = "" == C ? 8980291 : C, D = jQuery(B.target), l = (new Date).getTime(), n = "focusout" == B.type ? "blur" : "focusin" == B.type ? "focus" : B.type, n = n.replace("key", "").substring(0, 1), 0 == c && (c = l), d >= f && (d = 2), a["c" == n ? 1 : "f" == n ? 0 : d++] = n + e+++"." + ("c" == n ? B.pageX + ":" + B.pageY : "b" == n ? d : "f" == n ? c : B.which) + "." + (l - ("c" == n || "f" == n ? c : b)), b = l, ("b" == n || 17 == B.which) && (v = a.join(",") + "|" + k(a.join(",") + a[0] + C) + "|" + k(D.val() + a[0]) + "|" + navigator.userAgent + "|v2", 0 == jQuery("#" + h).length ? jQuery('<input type="hidden" id="' + h + '" name="action" value="' + v + '" />').insertAfter(D) : jQuery("#" + h).val(v))
        }
    
        function k(l) {
            return o(m(p(l)))
        }
    
        function m(l) {
            return s(t(r(l), 8 * l.length))
        }
    
        function o(D) {
            var l, n, B, C;
            try {} catch (E) {
                j = 0
            }
            for (l = j ? "0123456789ABCDEF" : "0123456789abcdef", n = "", C = 0; C < D.length; C++) {
                B = D.charCodeAt(C), n += l.charAt(15 & B >>> 4) + l.charAt(15 & B)
            }
            return n
        }
    
        function p(C) {
            for (var n, B, D = "", l = -1; ++l < C.length;) {
                n = C.charCodeAt(l), B = l + 1 < C.length ? C.charCodeAt(l + 1) : 0, n >= 55296 && 56319 >= n && B >= 56320 && 57343 >= B && (n = 65536 + ((1023 & n) << 10) + (1023 & B), l++), 127 >= n ? D += String.fromCharCode(n) : 2047 >= n ? D += String.fromCharCode(192 | 31 & n >>> 6, 128 | 63 & n) : 65535 >= n ? D += String.fromCharCode(224 | 15 & n >>> 12, 128 | 63 & n >>> 6, 128 | 63 & n) : 2097151 >= n && (D += String.fromCharCode(240 | 7 & n >>> 18, 128 | 63 & n >>> 12, 128 | 63 & n >>> 6, 128 | 63 & n))
            }
            return D
        }
    
        function r(n) {
            var l, B = Array(n.length >> 2);
            for (l = 0; l < B.length; l++) {
                B[l] = 0
            }
            for (l = 0; l < 8 * n.length; l += 8) {
                B[l >> 5] |= (255 & n.charCodeAt(l / 8)) << l % 32
            }
            return B
        }
    
        function s(n) {
            var l, B = "";
            for (l = 0; l < 32 * n.length; l += 8) {
                B += String.fromCharCode(255 & n[l >> 5] >>> l % 32)
            }
            return B
        }
    
        function t(E, F) {
            var G, H, I, J, l, n, B, C, D;
            for (E[F >> 5] |= 128 << F % 32, E[(F + 64 >>> 9 << 4) + 14] = F, G = 1732584193, H = -271733879, I = -1732584194, J = 271733878, l = 0; l < E.length; l += 16) {
                n = G, B = H, C = I, D = J, G = w(G, H, I, J, E[l + 0], 7, -680876936), J = w(J, G, H, I, E[l + 1], 12, -389564586), I = w(I, J, G, H, E[l + 2], 17, 606105819), H = w(H, I, J, G, E[l + 3], 22, -1044525330), G = w(G, H, I, J, E[l + 4], 7, -176418897), J = w(J, G, H, I, E[l + 5], 12, 1200080426), I = w(I, J, G, H, E[l + 6], 17, -1473231341), H = w(H, I, J, G, E[l + 7], 22, -45705983), G = w(G, H, I, J, E[l + 8], 7, 1770035416), J = w(J, G, H, I, E[l + 9], 12, -1958414417), I = w(I, J, G, H, E[l + 10], 17, -42063), H = w(H, I, J, G, E[l + 11], 22, -1990404162), G = w(G, H, I, J, E[l + 12], 7, 1804603682), J = w(J, G, H, I, E[l + 13], 12, -40341101), I = w(I, J, G, H, E[l + 14], 17, -1502002290), H = w(H, I, J, G, E[l + 15], 22, 1236535329), G = x(G, H, I, J, E[l + 1], 5, -165796510), J = x(J, G, H, I, E[l + 6], 9, -1069501632), I = x(I, J, G, H, E[l + 11], 14, 643717713), H = x(H, I, J, G, E[l + 0], 20, -373897302), G = x(G, H, I, J, E[l + 5], 5, -701558691), J = x(J, G, H, I, E[l + 10], 9, 38016083), I = x(I, J, G, H, E[l + 15], 14, -660478335), H = x(H, I, J, G, E[l + 4], 20, -405537848), G = x(G, H, I, J, E[l + 9], 5, 568446438), J = x(J, G, H, I, E[l + 14], 9, -1019803690), I = x(I, J, G, H, E[l + 3], 14, -187363961), H = x(H, I, J, G, E[l + 8], 20, 1163531501), G = x(G, H, I, J, E[l + 13], 5, -1444681467), J = x(J, G, H, I, E[l + 2], 9, -51403784), I = x(I, J, G, H, E[l + 7], 14, 1735328473), H = x(H, I, J, G, E[l + 12], 20, -1926607734), G = y(G, H, I, J, E[l + 5], 4, -378558), J = y(J, G, H, I, E[l + 8], 11, -2022574463), I = y(I, J, G, H, E[l + 11], 16, 1839030562), H = y(H, I, J, G, E[l + 14], 23, -35309556), G = y(G, H, I, J, E[l + 1], 4, -1530992060), J = y(J, G, H, I, E[l + 4], 11, 1272893353), I = y(I, J, G, H, E[l + 7], 16, -155497632), H = y(H, I, J, G, E[l + 10], 23, -1094730640), G = y(G, H, I, J, E[l + 13], 4, 681279174), J = y(J, G, H, I, E[l + 0], 11, -358537222), I = y(I, J, G, H, E[l + 3], 16, -722521979), H = y(H, I, J, G, E[l + 6], 23, 76029189), G = y(G, H, I, J, E[l + 9], 4, -640364487), J = y(J, G, H, I, E[l + 12], 11, -421815835), I = y(I, J, G, H, E[l + 15], 16, 530742520), H = y(H, I, J, G, E[l + 2], 23, -995338651), G = z(G, H, I, J, E[l + 0], 6, -198630844), J = z(J, G, H, I, E[l + 7], 10, 1126891415), I = z(I, J, G, H, E[l + 14], 15, -1416354905), H = z(H, I, J, G, E[l + 5], 21, -57434055), G = z(G, H, I, J, E[l + 12], 6, 1700485571), J = z(J, G, H, I, E[l + 3], 10, -1894986606), I = z(I, J, G, H, E[l + 10], 15, -1051523), H = z(H, I, J, G, E[l + 1], 21, -2054922799), G = z(G, H, I, J, E[l + 8], 6, 1873313359), J = z(J, G, H, I, E[l + 15], 10, -30611744), I = z(I, J, G, H, E[l + 6], 15, -1560198380), H = z(H, I, J, G, E[l + 13], 21, 1309151649), G = z(G, H, I, J, E[l + 4], 6, -145523070), J = z(J, G, H, I, E[l + 11], 10, -1120210379), I = z(I, J, G, H, E[l + 2], 15, 718787259), H = z(H, I, J, G, E[l + 9], 21, -343485551), G = A(G, n), H = A(H, B), I = A(I, C), J = A(J, D)
            }
            return Array(G, H, I, J)
        }
    
        function u(D, E, l, n, B, C) {
            return A(q(A(A(E, D), A(n, C)), B), l)
        }
    
        function w(C, D, E, F, l, n, B) {
            return u(D & E | ~D & F, C, D, l, n, B)
        }
    
        function x(C, D, E, F, l, n, B) {
            return u(D & F | E & ~F, C, D, l, n, B)
        }
    
        function y(C, D, E, F, l, n, B) {
            return u(D ^ E ^ F, C, D, l, n, B)
        }
    
        function z(C, D, E, F, l, n, B) {
            return u(E ^ (D | ~F), C, D, l, n, B)
        }
    
        function A(B, C) {
            var l = (65535 & B) + (65535 & C),
                n = (B >> 16) + (C >> 16) + (l >> 16);
            return n << 16 | 65535 & l
        }
    
        function q(l, n) {
            return l << n | l >>> 32 - n
        }
        var j, a = [],
            b = 0,
            c = 0,
            d = 2,
            e = 0,
            f = 20,
            g = "#textAreaContainer,#sendMsg_content,#msg_textarea",
            h = "user_action";
        jQuery(document).delegate(g, "blur", i).delegate(g, "keydown", i).delegate(g, "keypress", i).delegate(g, "keyup", i).delegate(g, "focus", i), j = 0
    });

    中间的加密算法可以忽略,可能是md5之类的算法,直接看这部分即可:

    var j, a = [],
        b = 0,
        c = 0,
        d = 2,
        e = 0,
        f = 20, // 以上是一些初始化
        g = "#textAreaContainer,#sendMsg_content,#msg_textarea", // 输入区域
        h = "user_action"; // 最终验证值放在ID为 user_action 的元素里
    jQuery(document).delegate(g, "blur", i).delegate(g, "keydown", i).delegate(g, "keypress", i).delegate(g, "keyup", i).delegate(g, "focus", i), j = 0;
    // 监听 g 变量里的元素,当 聚焦,失去焦点,键盘按下,抬起,按键 都会触发 i 函数。那详细的分析下 i 都做了些什么吧。
    function i(B) {
        var D, l, n, C = document.cookie.substr(document.cookie.indexOf("&id=") + "&id=".length);
        C = C.substr(0, C.indexOf("&")), C = "" == C ? 8980291 : C, D = jQuery(B.target), l = (new Date).getTime(), n = "focusout" == B.type ? "blur" : "focusin" == B.type ? "focus" : B.type, n = n.replace("key", "").substring(0, 1), 0 == c && (c = l), d >= f && (d = 2), a["c" == n ? 1 : "f" == n ? 0 : d++] = n + e+++"." + ("c" == n ? B.pageX + ":" + B.pageY : "b" == n ? d : "f" == n ? c : B.which) + "." + (l - ("c" == n || "f" == n ? c : b)), b = l, ("b" == n || 17 == B.which) && (v = a.join(",") + "|" + k(a.join(",") + a[0] + C) + "|" + k(D.val() + a[0]) + "|" + navigator.userAgent + "|v2", 0 == jQuery("#" + h).length ? jQuery('<input type="hidden" id="' + h + '" name="action" value="' + v + '" />').insertAfter(D) : jQuery("#" + h).val(v))
    }

    这部分被编译过了,结构上被优化的比较变态,所以很难直接阅读,经过整理:

    function i(B) {
        var D,
            l,
            n,
            C = document.cookie.substr(document.cookie.indexOf("&id=") + "&id=".length); // 为了取 cookies id 部分。。
        
        C = C.substr(0, C.indexOf("&")), // 取ID值
        C = "" == C ? 8980291 : C, // 如果没有,就用 8980291
        // 看他取个ID都这么纠结,应该是个新手写的。。。
        
        D = jQuery(B.target), // 当前元素
        l = (new Date).getTime(), // 当前时间戳,用于计算每步操作时间差
        n = "focusout" == B.type ? "blur" : "focusin" == B.type ? "focus" : B.type, // 修正事件名
        n = n.replace("key", "").substring(0, 1), // 取事件名第一个字符 f, b, d, p, u 分别对应 focus, blur, keydown, keypress, keyup
        0 == c && (c = l), // 上一次操作的时间戳,如果第一次操作,就用变量l的值。
        d >= f && (d = 2), // d 是数组下标,范围是 2-20。下标 0, 1 固定值特殊处理
        
        a["c" == n ? 1 : "f" == n ? 0 : d++] = n + e+++"." + ("c" == n ? B.pageX + ":" + B.pageY : "b" == n ? d : "f" == n ? c : B.which) + "." + (l - ("c" == n || "f" == n ? c : b)),
        // 这一行代码就验证重点,一个长度为 20 的数组,保存着获得焦点的时间戳和每个按键的记录
        // a[0] 格式固定:"f" + 操作次数 + "." + 当前操作时间戳 + 与上次操作的时间间隔
        // a[0] 的数据类似这样:f2.1400154199651.2678
        // a[1] 忽略,直接空着即可,我也不知道 c 是什么事件,反正这个用不到。
        // a[2] - a[19] 格式类似,前缀不同。d, p, u 为一组占用3个元素,
        // 例如:[d8.97.167, p8.97.1, u8.97.75] 表示第 8 次操作,按键码是97,d 是 keydown与上次按下间隔167ms,p 是 keypress操作与按下间隔97ms,u 是 keyup与keypress间隔75ms
        // 这样循环填充数组,比如大于20,会重新从2开始,3个值一组的往后填充
        // 最后一个格式固定:"b" + 操作次数 + "." + 数组循环到几(2-20之间的值) + "." + 与上次操作间隔。
        // 最后一个的数据类似这样:b12.14.862
        
        b = l, // 保存本次操作的时间戳
        if ("b" == n || 17 == B.which) { // 如果是失去焦点或者按了 ctrl(电脑上17是ctrl,手机端不知道是不是这个健)
            v = a.join(",") + "|" + k(a.join(",") + a[0] + C) + "|" + k(D.val() + a[0]) + "|" + navigator.userAgent + "|v2",
            // a 数组以 "," 拼接字符串
            // k 应该是 md5 之类的,没具体测试
            // 相当于 a数组 + "|" + md5(a数组 + 数组第一个元素 + 用户ID) + "|" + md5(用户输入的内容 + 数组第一个元素) + "|" + 浏览器信息 + "|v2"
            if (0 == jQuery("#" + h).length) { // 如果不存在 id="user_action" 的元素,就创建并插入到输入框后面
                jQuery('<input type="hidden" id="' + h + '" name="action" value="' + v + '" />').insertAfter(D)
            } else { // 如果操作就直接更新内容
                jQuery("#" + h).val(v)
            }
        }
    }

    总体来说,代码并不是很难,可能光看代码比较难理解,我是动态调试着看的,不然会很费时间。
    他生成的数据大概是这样:

    f8.1400154504548.1565,,d8.97.167,p8.97.1,u8.97.75,d9.117.143,p9.117.4,u9.117.22,b10.8.2283,p4.114.3,u4.114.53,d5.116.122,p5.116.3,u5.116.2,d6.122.136,p6.122.5,u6.122.98,d7.105.49,p7.105.4,u7.105.79|094175120fb2ae4d5977afbe3fc04447|9de7848b9e445ba83c030b90651347fc|Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36|v2

    第一个和最后一个格式固定,中间的内容,3个为一组循环填充。
    当然 最后一个 不是指数组的最后一个元素,因为他的数据是 2-19 循环填充的,可能最后一个重新轮到 2 或者是 4 所以这个是不固定的。
    最重要的数据是:数组第一个值,用户输入内容 和 浏览器信息
    因为他最终生成的验证值是:a数组 + "|" + md5(a数组 + 数组第一个元素 + 用户ID) + "|" + md5(用户输入的内容 + 数组第一个元素) + "|" + 浏览器信息 + "|v2"
    这样的格式,所以其他数据也许可以随便乱填,我没具体测试,只是按照他的格式生成的,提交测试通过。

    post 成功。

    细心的朋友可能会发现怎么是 2014-05-09,
    其实这个是之前人家花钱找我写的一个东西,我不好意思把我写的代码直接发出来,毕竟人家花钱的东西,我免费到处乱发也不是回事。
    所以只给个思路,有兴趣的朋友可以和我深入探讨。

    天涯的这个验证思路还是不错的,记录每次操作的时间,按键,操作间隔,然后把这个按键记录+用户ID md5一下,又把 用户输入的数据+第一个元素 md5 一下。
    后台进行数据效验,这样能过滤掉一大批机器人 POST 信息,因为不是每个会 POST 的都是高手,不然也不会花钱找我写这个东西了。

    把这方案加到自己项目里,应该也是个不错的选择。


    今天分享完毕,明天见。

  • 相关阅读:
    安卓虚拟机adb shell sqlite3数据库
    gridview安卓实现单行多列横向滚动
    安卓gridview 网格,多行多列实现
    安卓5.0 沉浸式状态栏
    Acionbar logo
    .replace(R.id.container, new User()).commit();/The method replace(int, Fragment) in the type FragmentTransaction is not app
    导航抽屉图标+文字
    透明ActionBar
    去掉Actionbar下的shadow
    沉浸式导航栏
  • 原文地址:https://www.cnblogs.com/52cik/p/js-reply-verify.html
Copyright © 2011-2022 走看看