Skip to content

Latest commit

 

History

History
138 lines (105 loc) · 5.73 KB

073.md

File metadata and controls

138 lines (105 loc) · 5.73 KB

Macho Merlot Squirrel

Medium

Developers will bypass signature verification when using BLS implementation incorrectly

Summary

The BLS signature verification function returning nil error regardless of verification status will cause a security bypass vulnerability for downstream consumers as developers will incorrectly validate signatures by only checking error return values and not boolean results.

Root Cause

In babylon/crypto/bls12381/bls.go:55-60 the Verify function returns a boolean indicating signature validity but always returns nil error regardless of verification outcome, breaking the common Go pattern where errors indicate validation failures:

func Verify(sig Signature, pk PublicKey, msg []byte) (bool, error) {
    dummySig := new(BlsSig)
    return dummySig.VerifyCompressed(sig, false, pk, false, msg, DST), nil
}

Internal Pre-conditions

  1. A developer needs to use the BLS Verify function to validate signatures
  2. Developer must follow the common Go pattern of only checking error returns and not the boolean result

External Pre-conditions

None

Attack Path

  1. Attacker creates an invalid BLS signature
  2. Victim code calls Verify(invalid_sig, pk, msg) which returns false, nil
  3. Victim code only checks if err == nil and assumes verification succeeded
  4. Application proceeds as if the signature was valid, bypassing crucial security checks

Impact

The application security is compromised as unauthorized data or transactions can be processed with invalid signatures. This could lead to privilege escalation, unauthorized access, or financial losses depending on what the signature verification was protecting.

PoC

// Add to file: babylon/crypto/bls12381/bls_test.go
func TestVerifyApiMisuseVulnerability(t *testing.T) {
	// Generate a valid key pair
	sk, pk := GenKeyPair()
	message := []byte("test message")

	// Create a valid signature
	validSig := Sign(sk, message)

	// Create an invalid signature (by signing different message)
	differentMessage := []byte("different message")
	invalidSig := Sign(sk, differentMessage)

	// Demonstrate proper usage - checks the boolean result
	validResult, err := Verify(validSig, pk, message)
	require.Nil(t, err)
	require.True(t, validResult)
	t.Logf("PROPER USAGE - Valid signature check: result=%v, err=%v", validResult, err)

	invalidResult, err := Verify(invalidSig, pk, message)
	require.Nil(t, err)
	require.False(t, invalidResult)
	t.Logf("PROPER USAGE - Invalid signature check: result=%v, err=%v", invalidResult, err)

	// VULNERABILITY: Verify() always returns nil error regardless of signature validity!
	// This shows that checking only the error is insufficient and dangerous

	t.Log("=============== VULNERABILITY DEMONSTRATION ===============")

	// Vulnerable code pattern - only checks error
	isValid := func(sig Signature, pk PublicKey, msg []byte) bool {
		result, err := Verify(sig, pk, msg)
		t.Logf("Inside vulnerable function: result=%v, err=%v", result, err)
		return err == nil // This ALWAYS returns true, regardless of signature validity!
	}

	// Test with valid signature
	validCheck := isValid(validSig, pk, message)
	t.Logf("VULNERABLE CHECK with valid signature: returned %v (expected true)", validCheck)
	require.True(t, validCheck)

	// Test with invalid signature - THIS IS THE VULNERABILITY!
	invalidCheck := isValid(invalidSig, pk, message)
	t.Logf("VULNERABLE CHECK with invalid signature: returned %v (should be false!)", invalidCheck)
	require.True(t, invalidCheck, "SECURITY VULNERABILITY: Invalid signature appears valid when only checking error!")

	t.Log("This vulnerability could lead to accepting invalid signatures if code only checks for nil errors")
}

Result example:

Running tool: /opt/homebrew/bin/go test -timeout 30s -run ^TestVerifyApiMisuseVulnerability$ github.com/babylonlabs-io/babylon/crypto/bls12381

=== RUN   TestVerifyApiMisuseVulnerability
    @root/babylon/crypto/bls12381/bls_test.go:122: PROPER USAGE - Valid signature check: result=true, err=<nil>
    @root/babylon/crypto/bls12381/bls_test.go:127: PROPER USAGE - Invalid signature check: result=false, err=<nil>
    @root/babylon/crypto/bls12381/bls_test.go:132: =============== VULNERABILITY DEMONSTRATION ===============
    @root/babylon/crypto/bls12381/bls_test.go:137: Inside vulnerable function: result=true, err=<nil>
    @root/babylon/crypto/bls12381/bls_test.go:143: VULNERABLE CHECK with valid signature: returned true (expected true)
    @root/babylon/crypto/bls12381/bls_test.go:137: Inside vulnerable function: result=false, err=<nil>
    @root/babylon/crypto/bls12381/bls_test.go:148: VULNERABLE CHECK with invalid signature: returned true (should be false!)
    @root/babylon/crypto/bls12381/bls_test.go:151: This vulnerability could lead to accepting invalid signatures if code only checks for nil errors
--- PASS: TestVerifyApiMisuseVulnerability (0.01s)
PASS
ok      github.com/babylonlabs-io/babylon/crypto/bls12381 0.394s

Mitigation

Modify the BLS Verify function to follow standard Go conventions by returning an error when verification fails:

func Verify(sig Signature, pk PublicKey, msg []byte) (bool, error) {
    dummySig := new(BlsSig)
    result := dummySig.VerifyCompressed(sig, false, pk, false, msg, DST)
    if !result {
        return false, errors.New("BLS signature verification failed")
    }
    return true, nil
}

Alternatively, simplify the API to avoid confusion:

func Verify(sig Signature, pk PublicKey, msg []byte) error {
    dummySig := new(BlsSig)
    result := dummySig.VerifyCompressed(sig, false, pk, false, msg, DST)
    if !result {
        return errors.New("BLS signature verification failed")
    }
    return nil
}