Lighthouse doesn't properly validate the RANDOM PACKET size before replying with WHOAREYOU. Per the discv5 protocol a RANDOM_PACKET is sent to initiate a handshake and the receiving node replies with WHOAREYOU. The RANDOM_PACKET should have the following layout:
tag -> 32 bytes
auth-tag -> 12 bytes (RLP format)
random_data -> 44+ bytes
The Discv5 rationale document specifically mentions that the RANDOM PACKET size should be > WHOAREYOU to ensure that an attack using the RANDOM PACKET format will require more bandwidth for the attacker. Lighthouse will reply to RANDOM PACKETS as small as 46 bytes - in other words, the random_data field does not seem to be required in this implementation. 88 bytes + 2 bytes for RLP length prefix should be the minimum acceptable size.
The following is screenshot showing Lighthouse's reply to a malformed RANDOM PACKET that is undersized:
Invalid RANDOM PACKET:

Lighthouse's WHOAREYOU response:

You can recreate the issue by using this command to send a malformed message to Lighthouse:
> hping3 -d 46 --fast --udp -p [PORT] [IP] -E lh.payload
where lh.payload is the following hex string converted to binary:
> cat lh.input
01010101010101010101010101010101010101010101010101010101010101018c020202020202020202020202
if it helps, you can use the following python code to convert the hex string above to the appropriate format:
import binascii
with open('lh.input') as f, open('lh.payload', 'wb') as fout:
for line in f:
fout.write(
binascii.unhexlify(''.join(line.split()))
)
or just modify one of the mysteriously similar discv5 unit tests 馃槈
Ah yeah cool. I'll check this out and update discv5 accordingly. Will close this once complete.
Thanks for the work done here!
I think this is a great finding, and go-ethereum probably has the same issue. But there is an issue in the spec that we need to address also: requiring the random-packet to be larger than 44 bytes exists because it is supposed to prevent traffic amplification attacks, i.e. for any packet you can send that would trigger a WHOAREYOU response, the incoming packet should be equal in size or larger than WHOAREYOU.
Consider the scenario where A and B had a session going on, but then B drops the session because it restarts or something. The next PING sent by A cannot be decrypted by B and it should respond with WHOAREYOU to get the session back on.
So what we need to change in the spec is requiring all request packets to be larger than WHOAREYOU, not just the initial random packet.
I think this is a great finding, and go-ethereum probably has the same issue.
I can confirm it does + it also allows invalid RLP length prefixes before the Auth_tag. @fjl checkout the issue I opened here:
This requires a spec update to properly address. I'm going to close this for now.
We will keep up to date with the spec which should in the future address this.
Most helpful comment
I think this is a great finding, and go-ethereum probably has the same issue. But there is an issue in the spec that we need to address also: requiring the random-packet to be larger than 44 bytes exists because it is supposed to prevent traffic amplification attacks, i.e. for any packet you can send that would trigger a WHOAREYOU response, the incoming packet should be equal in size or larger than WHOAREYOU.
Consider the scenario where A and B had a session going on, but then B drops the session because it restarts or something. The next PING sent by A cannot be decrypted by B and it should respond with WHOAREYOU to get the session back on.
So what we need to change in the spec is requiring all request packets to be larger than WHOAREYOU, not just the initial random packet.