我怎样才能找到使用的散列算法?

信息安全 加密 验证 javascript
2021-08-24 00:41:41

我的一位客户要求我修改其董事会成员用来访问其材料的登录页面。这里的问题是,以前负责这个的人没有留下任何文档,并且密码是用一些(看似简单的)算法加密的。

我可以访问哈希,并且我知道密码是什么。javascript 使用哈希作为他们的密码。我的想法是,如果我能弄清楚算法是什么,我就可以创建新帐户以满足他们的要求。

有没有办法可以检查算法是什么?

系统会提示用户从下拉菜单中选择他们的姓名,并且他们的密码与 HTML 代码中他们姓名旁边的哈希相关联。表单选项如下所示(注意 N 表示数值,L 表示字母)。

<option value='Username|NNNNN|LLLLLLLL'>Username

解析值的实际脚本如下所示

<SCRIPT LANGUAGE="JavaScript">
<!-- Begin
var params = new Array(4);
var alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHI";

function check(form) {
    which = form.memlist.selectedIndex;
    choice = form.memlist.options[which].value + "|";
    if (choice == "x|") {
        alert("Please Select Your Name From The List");
        return;
    }
    p = 0;
    for (i = 0; i < 3; i++) {
        a = choice.indexOf("|", p);
        params[i] = choice.substring(a, p);
        p = a + 1;
    }
    h1 = makehash(form.pass.value, 3);
    h2 = makehash(form.pass.value, 10) + " ";
    if (h1 != params[1]) {
        alert("Incorrect Password!");
        return;
    };
    var page = "";
    for (var i = 0; i < 8; i++) {
        letter = params[2].substring(i, i + 1)
        ul = letter.toUpperCase();
        a = alpha.indexOf(ul, 0);
        a -= (h2.substring(i, i + 1) * 1);
        if (a < 0) a += 26;
        page += alpha.substring(a, a + 1);
    };
    top.location = page.toLowerCase() + ".html";
}

function makehash(pw, mult) {
    pass = pw.toUpperCase();
    hash = 0;
    for (i = 0; i < 8; i++) {
        letter = pass.substring(i, i + 1);
        c = alpha.indexOf(letter, 0) + 1;
        hash = hash * mult + c;
    }
    return (hash);
}
// End -->
</script>

无论如何我可以对此进行逆向工程以便我可以创建新的用户帐户吗?

3个回答

这里的算法是:

function makehash(pw, mult) { // Password and... multiplier?
    pass = pw.toUpperCase(); // Case insensitivity
    var hash = 0;
    for (i = 0; i < Math.min(pass.length, 8); i++) { // 8 char passwords max...
        c = pass.charCodeAt(i) - 63; // A = 2, B = 3, etc.
        hash *= mult;
        hash += c;
    }

    return hash;
}

我清理了代码并添加了一些注释。写这篇文章的人在编码、安全和数学领域完全无能。无论如何,它不是像 MD5 或 AES 这样的“官方”算法,而是自制的并且具有难以置信的容错性。它只接受字母,不区分大小写,并忽略前 8 个字符之后的所有字符。

我强烈建议升级每个人的密码哈希。另请参阅: 如何安全地散列密码?

顺便说一下,这是带有一些格式的其余代码:

var params=new Array(4);
var alpha="ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHI";

function check(form) {
    which = form.memlist.selectedIndex;
    choice = form.memlist.options[which].value + "|";
    if (choice == "x|") {
        alert("Please Select Your Name From The List");
        return;
    }

    p = 0;
    for (i = 0; i < 3; i++) {
        a = choice.indexOf("|", p);
        params[i] = choice.substring(a, p);
        p = a + 1;
    }
    h1 = makehash(form.pass.value, 3);
    h2 = makehash(form.pass.value, 10) + " ";
    if (h1 != params[1]) {
        alert("Incorrect Password!");
        return;
    }

    var page = "";
    for (var i = 0; i < 8; i++) {
        letter = params[2].substring(i, i + 1)
        ul = letter.toUpperCase();
        a = alpha.indexOf(ul, 0);
        a -= h2.substring(i, i + 1) * 1; // multiplying by one? Seriously?
        if (a<0)
            a+=26;
        page += alpha.substring(a, a + 1);
    };
    top.location=page.toLowerCase() + ".html";
}

我会添加评论,但我不确定在这个混乱中找到任何理由是否值得。

搜索一段代码会产生以下页面:

http://www.yaldex.com/FSPassProtect/LoginCoder.htm

在许多其他地方。这应该告诉您如何添加另一个条目。

但是,这是一种极其不安全的身份验证机制,任何远程主管都可以轻松绕过,因为您所要做的就是猜测输入密码后您将重定向到的页面(不区分大小写的 8 个字符串以 .html 结尾)正确。而且真的不需要一个快速的脚本来尝试 26^8 个不同的 URL;您所要做的就是查看编码值“BAFJFFCI”,如果只有一个用户,请尝试最多10^8 9^7;如果有多个用户访问同一页面,您可以显着减少空间。


好的,我已经解构了它是如何工作的。登录表单中的每个用户都有一个选项,其值类似于“用户名|35240|BAFJFFCI”。用户名不在任何地方使用并且无关紧要。下一个哈希,称为 h1,是通过对输入的密码字符串调用 make_hash 生成的,乘数为 3。

def make_hash(pw, mult):
    hash = 0
    for char in pw:
        char_ind = ord(char) - ord('A') +1 # A=1, B=2, C=3, ...
        hash *= mult
        hash += char_ind
    return hash

因此,如果您的密码是“不安全的”,则会被转换为数字 (I=9, n=14, s=19, ...) [9,14,19,5,3,21,18,5],所以你的哈希是3*(3*(3*(3*(3*(3*(3*(9)+14)+19)+5)+3)+21)+18)+5,结果是 35240。注意它不区分大小写。那么第二个值是多少?好吧,如果您输入有效密码,它会将您转发到一个秘密页面。在我的情况下,秘密页面是“AAAAAAA”,将从“BAFJFFCI”字段中解码

为了解码,它们会生成另一个乘数为 10 的散列。 h2=make_hash(pw, 10)在我的例子中,它是 h2=105955285。从左边开始的每个数字都告诉我要移动多少个字符。例如,'AAAAAAA' 如果你将第一个字符向前移动一个字母给出'B',第二个字符向前移动 0 给出'A',将'A'向前移动 5 个字符'F',向前移动 9 个字符'J',......并且类似于使用 h2(从密码生成)从“BAFJFFCI”恢复“AAAAAAA”,您只需减去字母。

作为攻击者,这意味着在密码字段中看到每个用户的哈希值,源页面只有十个可能的字母。所以 10^8 个可能的网页可以尝试检查。如果多个用户都被转发到同一个页面,您可以轻松找出目标页面,然后自己绕过整个身份验证过程前往那里。

例如,'GCDDGDAJ' 和 'BAFJFFCI' 都被转换为指向 aaaaaaaa.html 的版本。通过假设两者都进入同一页面,并看到“G”、“B”都起源于同一个字母的 0-9 个字符,我知道第一个字母是 A、B、X、Y、Z。通过确保没有超过 9 个字符的字母(例如,如果在一个密码中有一个“A”而另一个密码在同一个位置有A K、L、M、N、O、P 或 Q,则密码不能进入同一页面),并且相隔的距离也限制了实际位置的值。


对于更多的加密分析:

您还可以非常简单地找到此哈希的冲突。为简单起见,我将分析使用此方案生成的三字符密码。所以现在 h1 = 3*(3*c1+c2)+c3)。取 3 的模数(除以符号 后的余数%),您会看到 h1 % 3 = c3 % 3。因此如果 h1 % 3 = 0 最后一个字母在 CFILORUX 中;如果 h1 % 3 = 1,它在 ADGJMPSVY 中;如果 h1 % 3 = 2 它在 BEHKNQTWZ。您可以使用此过程将三个字符的密码减少到最多 9^2 = 81 种可能性。

例如,假设 pw 是“THE”,哈希是 3*(3*20+8)+5=209。如 209 % 3 = 2; 我们知道最后一个字符在BEHKNQTWZ. 我们可以尝试所有 9 个选项减去最后一个字符,除以 3 并再次重复。例如,如果最后一个字母是 B,那么 (209-2)/3 = 69 这意味着 69 % 3 = 0,所以中间字母在 CFILORUX 中。如果是C,那么我们得到的第一个字母一定是(69-3)=22=V;因此 VCB 的哈希值为 209。对其他选择重复此操作:UFB、TIB、SLB、ROB、QRB、PUB、OXB。如果最后一个字母是 E;我们有 VBE、UEE、THE、SKE、RNE、QQE、PTE、OWE、NZE。这很容易迭代;所以你最多有 9^2 = 81 个选择(除了最后一个字母之外的每个字母都由剩余的字母决定;有 8 个或 9 个选择)。在我们的例子中,有 78 个选项:

VCB UFB TIB SLB ROB QRB PUB OXB VBE UEE THE SKE RNE QQE PTE OWE NZE VAH UDH TGH SJH RMH QPH PSH OVH NYH UCK TFK SIK RLK QOK PRK OUK NXK UBN 10 SHN RKN QNN PQN OTN NWN MZN UAQ TDQ SGQ RJQ QMQ PPQ OSQ NVQ MYQ TCT SFT RIT QLT POT ORT NUT MXT TBW SEW RHW QKW PNW OQW NTW MWW LZW TAZ SDZ RGZ QJZ PMZ OPZ NSZ MVZ LYZ

这些有 78 个唯一的 mult=10 哈希,您必须尝试获取 URL(我已经对它们进行了排序):

1476 1483 1546 1553 1560 1567 1574 1616 1623 1630 1637 1644 1651 1658 1665 1686 1693 1700 1707 1714 1721 1728 1735 1742 1756 1763 1770 1777 1784 1791 1798 1805 1812 1826 1833 1840 1847年1854年1861年1868年1875年1882年1896年1903年1910 1917年1924年1931年1938年1945年1952 1966 1973 1980 1987 1994 2001 2008 2015 2022 2036 2043 2050 2057 2064 2071 2078 2085 2092 2127 2134 2141 2148 2155 2162 2

请注意,只有 mult-10 哈希值的前三位对更改 URL 很重要,所以实际上只有 62 个不同的值。或者为简单起见,我可以找到最大/最小的 mod 哈希,并检查前三位数字之间的所有内容(检查 77 个值;在找到 VCB 和 LYZ 之后)。

请注意,它们完整的 mult-10 哈希值都具有相同的值 mod 7(全部为 6)。这种模式(具有相同的余数除以 10-3=7)通常是正确的。所以你也可以找出第一个和最后一个允许的密码'VCB'给出 mult=10 hash 2232,'LYZ'给出 mult-10 hash of 1476 并尝试所有 ((2232-1476)/7)+1=109该范围内的 mult-10 散列以 7 分隔,并且仅使用 mult-10 散列中的前 n 位。

同样,无论采用哪种方法,您都会有几百万个 URL 需要检查,以 100/秒的速度应该在一天内完成。

这种散列算法非常不安全,计算所有可能的密码和页面组合大约需要 2 分钟。它会生成一个包含 20000 到 2500000 个可能的秘密页面的列表,您需要与服务器进行实际检查。例如,对于@drjimbob 的“不安全”到“aaaaaaaa.html”的示例,您只需要检查 26063 个可能的秘密页面。其他人表现得更好,但不是很多。事实上,与仅提示用户输入 html 页面的 URL 相比,这种密码方案实际上削弱了安全性。在这种情况下,攻击者必须检查 26^8 个可能的页面,但是由于密码方案,攻击者只需要检查所有 26^8 个可能性中的 0.00122%。

>>> # "insecure", "aaaaaaaa.html"
>>> len(set(y for x,y in find_pages(35240, 'BAFJFFCI')))
3248895

>>> # "password", "mainpage.html"
>>> len(set(y for x,y in find_pages(42691, 'NGLOQEMM')))
2988569

>>> # "theirpwd", "endpages.html"
>>> len(set(y for x,y in find_pages(52219, 'GNLVAPMV')))
3035974

>>> # "asdfvcxz", "nowheres.html"
>>> len(set(y for x,y in find_pages(18215, 'PXAPGWKY')))
2382856

>>> # "zaqxswde", "logintop.html"
>>> len(set(y for x,y in find_pages(64403, 'NUIRTURT')))
2792596




import string
chars = [(c, ord(c) - ord('A') + 1) for c in string.uppercase]
chars = chars[2::3], chars[::3], chars[1::3]
chars = [list(reversed([(a,b,c) for a,(b,c) in enumerate(chs)])) for chs in chars]

def reverse_make_hash(hash, num=8, cur=''):
    """ generates a list of pw such that `make_hash(pw, 3) == hash` """
    if num <= 0 or hash <= 0: return
    if num == 1 and hash > 26: return
    if num == 1 and hash <= 26: 
        yield chr(ord('A') + hash - 1) + cur

    mod = hash % 3
    for i,c,v in chars[mod]:
        n = (hash - v) / 3
        for pot in reverse_make_hash(n, num-1, c + cur): yield pot

# from dr jimbob
def make_hash(pw, mult):
    hash = 0
    for char in pw:
        char_ind = ord(char) - ord('A') +1 # A=1, B=2, C=3, ...
        hash *= mult
        hash += char_ind
    return hash

def find_pages(hash, page):
    results = []
    for p in reverse_make_hash(hash): 
        page_url = ''.join(chr((ord(p)-int(h)-ord('A')) % 26 +ord('A')) for p,h in zip(page, str(make_hash(p, 10))))
        if page_url.isalpha():
            print p, page_url
            results.append((p, page_url.lower() + '.html'))
    return results

a = find_pages(35240, 'BAFJFFCI')  # "insecure", "aaaaaaaa.html"
b = find_pages(42691, 'NGLOQEMM')  # "password", "mainpage.html"
c = find_pages(52219, 'GNLVAPMV')  # "theirpwd", "endpages.html"
d = find_pages(18215, 'PXAPGWKY')  # "asdfvcxz", "nowheres.html"
e = find_pages(64403, 'NUIRTURT')  # "zaqxswde", "logintop.html"