Home HackyEaster 2017 Spaghetti hash
Writeup
Cancel

Spaghetti hash

Challenge

Lazy Larry needs to improve the security of his password hashing implementation. He decides to use SHA-512 as a new hashing algorithm in order to be super secure. Unfortunately, the database column for the hash can only hold 128 bit. As Bob is too lazy to extend the column and all the code related to it, he decides to shrink the output of the SHA-512 operation, to 128 bit. For this purpose he picks certain characters from the SHA-512 output for producing the new value.

You got hold of four password hashes, calculated with Bob’s new implementation. Can you find the corresponding passwords?

1
2
3
4
hash 1: 87017a3ffc7bdd5dc5d5c9c348ca21c5
hash 2: ff17891414f7d15aa4719689c44ea039
hash 3: 5b9ea4569ad68b85c7230321ecda3780
hash 4: 6ad211c3f933df6e5569adf21d261637

Lucky you, you know that the following web service is calculating Bob’s algorithm. However, the web service only accepts strings of length 4 or less - brute-forcing a password list thus is no option, since the passwords you are looking for are all longer.

1
https://hackyeaster.hacking-lab.com/hackyeaster/hash?string=abcd

Solution

First we use the web service to determine how the smaller hashes are obtained from the full hashes, next we use a dictionary to test whether the smaller hashes match any of the four we are looking for

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import requests
import hashlib

url="https://hackyeaster.hacking-lab.com/hackyeaster/hash?string="

def all_occurrences(s, ch):
    return [i for i, ltr in enumerate(s) if ltr == ch]

# first, determine where each character in new hashes came from in original
# by looking at multiple ones
pairs = []
for c in "abcde":
    m = hashlib.sha512()
    m.update(c)
    r = requests.get(url+c)
    pairs.append([m.hexdigest(), r.text])

origins = []
for i in range(0, len(pairs[0][1])):
    #print "pos: "+str(i)
    possibilities = []
    for j in range(0, len(pairs)):
        n = all_occurrences(pairs[j][0], pairs[j][1][i])
        #print n
        if possibilities == []:
            possibilities = set(n)
        else:
            possibilities = possibilities.intersection(n)
    origins += list(possibilities)

print "\nOrigin of each position in small hash:"
print origins
# [65, 17, 115, 31, 45, 11, 67, 92, 0, 7, 123, 37, 5, 22, 87, 124, 25, 89, 38, 61, 90, 109, 63, 28, 102, 12, 47, 59, 110, 86, 24, 18]

def get_mini_hash(pt, positions):
    m = hashlib.sha512()
    m.update(pt)
    longhash = m.hexdigest()
    smallhash = ''
    for i in positions:
        smallhash += longhash[i]

    return smallhash


# now we know how smaller hashes are derived from larger ones, try word from
# a dictionary to see which match hashes we are looking for
targets = ['87017a3ffc7bdd5dc5d5c9c348ca21c5',
           'ff17891414f7d15aa4719689c44ea039',
           '5b9ea4569ad68b85c7230321ecda3780',
           '6ad211c3f933df6e5569adf21d261637']

found = 0
with open('wordlist.txt') as f:
    while found < 4:
        w = f.readline().strip()
        smallh = get_mini_hash(w, origins)
        if smallh in targets:
            print "\nFound!"
            print w
            print smallh
            found += 1

This outputs the following solutions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Origin of each position in small hash:
[65, 17, 115, 31, 45, 11, 67, 92, 0, 7, 123, 37, 5, 22, 87, 124, 25, 89, 38, 61, 90, 109, 63, 28, 102, 12, 47, 59, 110, 86, 24, 18]

Found!
12345678
6ad211c3f933df6e5569adf21d261637

Found!
benchmark
5b9ea4569ad68b85c7230321ecda3780

Found!
Cleveland
ff17891414f7d15aa4719689c44ea039

Found!
Prodigy
87017a3ffc7bdd5dc5d5c9c348ca21c5

we fill these four solutions into the egg-o-matic to get our egg:

Flag

ICBXMH6GJqMqYDsjkaCn