mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-09-19 17:50:12 +00:00
fix(security): verify transfer-token HMAC and sign CSRF double-submit cookie
- consumeTransferToken now recomputes and constant-time compares the entry's HMAC-SHA256; previously the signature was minted but never checked, so the documented "防篡改" guarantee was hollow and security rested solely on the sync.Map key's randomness. - CSRF switches from a raw-random double-submit cookie to a signed token (nonce.HMAC-SHA256 keyed by JWTSecretKey). The middleware now also validates the signature, defeating sibling-subdomain cookie tossing where a naive header==cookie pair would otherwise pass. Co-authored-by: cloudcode <cloudcode@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// A transfer token whose HMAC signature does not match the stored entry
|
||||
// must be rejected at consume time. The signature is documented as
|
||||
// "HMAC-SHA256 防篡改" (tamper-proof); if consume never verifies it, the
|
||||
// guarantee is hollow. This test pins that the signature is actually
|
||||
// checked: a stored entry under a token id with a forged/altered sig must
|
||||
// not be consumable.
|
||||
func TestConsumeTransferToken_RejectsTamperedSignature(t *testing.T) {
|
||||
e := transferEntry{
|
||||
UserID: 1,
|
||||
TokenID: 2,
|
||||
ServerID: 3,
|
||||
Path: "/srv/file",
|
||||
Direction: transferDirDownload,
|
||||
ExpiresAt: time.Now().Add(time.Minute),
|
||||
}
|
||||
tok, err := mintTransferToken(e)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, tok, ".", "token must carry an id.sig shape")
|
||||
|
||||
// Flip the last hex nibble of the signature to forge a mismatching MAC
|
||||
// while keeping the same random id portion.
|
||||
idx := strings.LastIndex(tok, ".")
|
||||
require.Greater(t, idx, 0)
|
||||
id, sig := tok[:idx], tok[idx+1:]
|
||||
last := sig[len(sig)-1]
|
||||
var flipped byte
|
||||
if last == '0' {
|
||||
flipped = '1'
|
||||
} else {
|
||||
flipped = '0'
|
||||
}
|
||||
forged := id + "." + sig[:len(sig)-1] + string(flipped)
|
||||
|
||||
// Re-store the entry under the forged token so the map lookup itself
|
||||
// would succeed — only the HMAC check should reject it.
|
||||
transferEntries.Store(forged, e)
|
||||
t.Cleanup(func() { transferEntries.Delete(forged) })
|
||||
|
||||
got, err := consumeTransferToken(forged, transferDirDownload)
|
||||
require.Error(t, err, "tampered-signature token must be rejected")
|
||||
require.Nil(t, got)
|
||||
}
|
||||
|
||||
// A correctly minted token must still consume successfully and be single-use.
|
||||
func TestConsumeTransferToken_ValidSignatureRoundTrips(t *testing.T) {
|
||||
e := transferEntry{
|
||||
UserID: 10,
|
||||
TokenID: 20,
|
||||
ServerID: 30,
|
||||
Path: "/srv/other",
|
||||
Direction: transferDirUpload,
|
||||
ExpiresAt: time.Now().Add(time.Minute),
|
||||
}
|
||||
tok, err := mintTransferToken(e)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := consumeTransferToken(tok, transferDirUpload)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, got)
|
||||
require.Equal(t, e.Path, got.Path)
|
||||
|
||||
// single-use: second consume must fail.
|
||||
_, err = consumeTransferToken(tok, transferDirUpload)
|
||||
require.Error(t, err)
|
||||
}
|
||||
Reference in New Issue
Block a user