Skip to content

Latest commit

 

History

History
166 lines (132 loc) · 6.82 KB

075.md

File metadata and controls

166 lines (132 loc) · 6.82 KB

Macho Merlot Squirrel

Medium

Malicious validators can forge vote extensions by exploiting string comparison in address verification

Summary

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.

Root Cause

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.

Internal Pre-conditions

  1. A validator needs to have previously generated a valid vote extension with a BLS signature
  2. The checkpoint system must be processing vote extensions at an epoch boundary

External Pre-conditions

None

Attack Path

  1. An attacker controls a validator and obtains a genuine vote extension with a valid BLS signature
  2. The attacker creates a malicious vote extension reusing the valid BLS signature but modifying other data
  3. When formatting the validator address, the attacker ensures the string representation matches while the cryptographic identity differs
  4. The attacker submits the malicious vote extension
  5. The validation process checks extensionSigner != blsSig.ValidatorAddress which passes because the strings match
  6. The BLS signature checks may still pass if they only verify the signature structure but don't validate the complete cryptographic identity chain
  7. The manipulated vote extension is accepted despite containing forged data

Impact

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.

PoC

// 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

Mitigation

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.