哈希长度扩展攻击解析

原文链接 https://blog.skullsecurity.org/2012/everything-you-need-to-know-about-hash-length-extension-attacks
You can grab the hash_extender tool on Github!

(Administrative note: I’m no longer at Tenable! I left on good terms, and now I’m a consultant at Leviathan Security Group. Feel free to contact me if you need more information!)

Awhile back, my friend @mogigoma and I were doing a capture-the-flag contest at https://stripe-ctf.com. One of the levels of the contest required us to perform a hash length extension attack. I had never even heard of the attack at the time, and after some reading I realized that not only is it a super cool (and conceptually easy!) attack to perform, there is also a total lack of good tools for performing said attack! After hours of adding the wrong number of null bytes or incorrectly adding length values, I vowed to write a tool to make this easy for myself and anybody else who’s trying to do it. So, after a couple weeks of work, here it is!

Now I’m gonna release the tool, and hope I didn’t totally miss a good tool that does the same thing! It’s called hash_extender, and implements a length extension attack against every algorithm I could think of:

MD4
MD5
RIPEMD-160
SHA-0
SHA-1
SHA-256
SHA-512
WHIRLPOOL

I’m more than happy to extend this to cover other hashing algorithms as well, provided they are “vulnerable” to this attack — MD2, SHA-224, and SHA-384 are not. Please contact me if you have other candidates and I’ll add them ASAP!
The attack

An application is susceptible to a hash length extension attack if it prepends a secret value to a string, hashes it with a vulnerable algorithm, and entrusts the attacker with both the string and the hash, but not the secret. Then, the server relies on the secret to decide whether or not the data returned later is the same as the original data.

It turns out, even though the attacker doesn’t know the value of the prepended secret, he can still generate a valid hash for {secret || data || attacker_controlled_data}! This is done by simply picking up where the hashing algorithm left off; it turns out, 100% of the state needed to continue a hash is in the output of most hashing algorithms! We simply load that state into the appropriate hash structure and continue hashing.

TL;DR: given a hash that is composed of a string with an unknown prefix, an attacker can append to the string and produce a new hash that still has the unknown prefix.
Example

Let’s look at a step-by-step example. For this example:

let secret = "secret"
let data = "data"
let H = md5()
let signature = hash(secret || data) = 6036708eba0d11f6ef52ad44e8b74d5b
let append = "append"

The server sends data and signature to the attacker. The attacker guesses that H is MD5 simply by its length (it’s the most common 128-bit hashing algorithm), based on the source, or the application’s specs, or any way they are able to.

Knowing only data, H, and signature, the attacker’s goal is to append append to data and generate a valid signature for the new data. And that’s easy to do! Let’s see how.
Padding

Before we look at the actual attack, we have to talk a little about padding.

When calculating H(secret + data), the string (secret + data) is padded with a ‘1’ bit and some number of ‘0’ bits, followed by the length of the string. That is, in hex, the padding is a 0x80 byte followed by some number of 0x00 bytes and then the length. The number of 0x00 bytes, the number of bytes reserved for the length, and the way the length is encoded, depends on the particular algorithm and blocksize.

With most algorithms (including MD4, MD5, RIPEMD-160, SHA-0, SHA-1, and SHA-256), the string is padded until its length is congruent to 56 bytes (mod 64). Or, to put it another way, it’s padded until the length is 8 bytes less than a full (64-byte) block (the 8 bytes being size of the encoded length field). There are two hashes implemented in hash_extender that don’t use these values: SHA-512 uses a 128-byte blocksize and reserves 16 bytes for the length field, and WHIRLPOOL uses a 64-byte blocksize and reserves 32 bytes for the length field.

The endianness of the length field is also important. MD4, MD5, and RIPEMD-160 are little-endian, whereas the SHA family and WHIRLPOOL are big-endian. Trust me, that distinction cost me days of work!

In our example, length(secret || data) = length(“secretdata”) is 10 (0x0a) bytes, or 80 (0x50) bits. So, we have 10 bytes of data (“secretdata”), 46 bytes of padding (80 00 00 …), and an 8-byte little-endian length field (50 00 00 00 00 00 00 00), for a total of 64 bytes (or one block). Put together, it looks like this:

0000 73 65 63 72 65 74 64 61 74 61 80 00 00 00 00 00 secretdata……
0010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …………….
0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …………….
0030 00 00 00 00 00 00 00 00 50 00 00 00 00 00 00 00 ……..P…….

Breaking down the string, we have:

"secret" = secret
"data" = data
80 00 00 ... — The 46 bytes of padding, starting with 0x80
50 00 00 00 00 00 00 00 — The bit length in little endian

This is the exact data that H hashed in the original example.
The attack

Now that we have the data that H hashes, let’s look at how to perform the actual attack.

First, let’s just append append to the string. Easy enough! Here’s what it looks like:

0000 73 65 63 72 65 74 64 61 74 61 80 00 00 00 00 00 secretdata……
0010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …………….
0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …………….
0030 00 00 00 00 00 00 00 00 50 00 00 00 00 00 00 00 ……..P…….
0040 61 70 70 65 6e 64 append

The hash of that block is what we ultimately want to a) calculate, and b) get the server to calculate. The value of that block of data can be calculated in two ways:

By sticking it in a buffer and performing H(buffer)
By starting at the end of the first block, using the state we already know from signature, and hashing append starting from that state

The first method is what the server will do, and the second is what the attacker will do. Let’s look at the server, first, since it’s the easier example.
Server’s calculation

We know the server will prepend secret to the string, so we send it the string minus the secret value:

0000 64 61 74 61 80 00 00 00 00 00 00 00 00 00 00 00 data…………
0010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …………….
0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …………….
0030 00 00 50 00 00 00 00 00 00 00 61 70 70 65 6e 64 ..P…….append

Don’t be fooled by this being exactly 64 bytes (the blocksize) — that’s only happening because secret and append are the same length. Perhaps I shouldn’t have chosen that as an example, but I’m not gonna start over!

The server will prepend secret to that string, creating:

0000 73 65 63 72 65 74 64 61 74 61 80 00 00 00 00 00 secretdata……
0010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …………….
0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …………….
0030 00 00 00 00 00 00 00 00 50 00 00 00 00 00 00 00 ……..P…….
0040 61 70 70 65 6e 64 append

And hashes it to the following value:

6ee582a1669ce442f3719c47430dadee

For those of you playing along at home, you can prove this works by copying and pasting this into a terminal:

echo ’
#include

HASH EXTENDER

By Ron Bowes

See LICENSE.txt for license information.

Usage: ./hash_extender <–data=|–file=> –signature= –format= [options]

INPUT OPTIONS
-d –data=
The original string that we’re going to extend.
–data-format=
The format the string is being passed in as. Default: raw.
Valid formats: raw, hex, html, cstr
–file=
As an alternative to specifying a string, this reads the original string
as a file.
-s –signature=
The original signature.
–signature-format=
The format the signature is being passed in as. Default: hex.
Valid formats: raw, hex, html, cstr
-a –append=
The data to append to the string. Default: raw.
–append-format=
Valid formats: raw, hex, html, cstr
-f –format= [REQUIRED]
The hash_type of the signature. This can be given multiple times if you
want to try multiple signatures. ‘all’ will base the chosen types off
the size of the signature and use the hash(es) that make sense.
Valid types: md4, md5, ripemd160, sha, sha1, sha256, sha512, whirlpool
-l –secret=
The length of the secret, if known. Default: 8.
–secret-min=
–secret-max=
Try different secret lengths (both options are required)

OUTPUT OPTIONS
–table
Output the string in a table format.
–out-data-format=
Output data format.
Valid formats: none, raw, hex, html, html-pure, cstr, cstr-pure, fancy
–out-signature-format=
Output signature format.
Valid formats: none, raw, hex, html, html-pure, cstr, cstr-pure, fancy

OTHER OPTIONS
-h –help
Display the usage (this).
–test
Run the test suite.
-q –quiet
Only output what’s absolutely necessary (the output string and the
signature)

Defense

So, as a programmer, how do you solve this? It’s actually pretty simple. There are two ways:

Don't trust a user with encrypted data or signatures, if you can avoid it.
If you can't avoid it, then use HMAC instead of trying to do it yourself. HMAC is designed for this.

HMAC is the real solution. HMAC is designed for securely hashing data with a secret key.

As usual, use constructs designed for what you’re doing rather than doing it yourself. The key to all crypto! [pun intended]

And finally, you can grab the hash_extender tool on Github!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值