mirror of
https://github.com/redis/redis.git
synced 2026-05-28 04:02:46 -04:00
Restrict internal secret adoption to outbound verified links
The cluster bus protocol is unauthenticated by design and not meant to be exposed to untrusted networks. However, the internal secret update logic blindly accepted INTERNALSECRET extensions from any connection, including inbound ones initiated by external parties. An attacker with access to the bus port could send a crafted PING spoofing a known node name with an all-zeros secret, causing the server to adopt it. The attacker could then authenticate on the client port via AUTH "internal connection" <zeros>, bypassing requirepass and ACLs. Only accept internal secret updates from outbound links to fully handshaken nodes, i.e. connections we initiated to verified cluster peers.
This commit is contained in:
parent
7cf63635f0
commit
15cbe73d84
2 changed files with 92 additions and 1 deletions
|
|
@ -2715,7 +2715,9 @@ void clusterProcessPingExtensions(clusterMsg *hdr, clusterLink *link) {
|
|||
ext_shardid = shardid_ext->shard_id;
|
||||
} else if (type == CLUSTERMSG_EXT_TYPE_INTERNALSECRET) {
|
||||
clusterMsgPingExtInternalSecret *internal_secret_ext = (clusterMsgPingExtInternalSecret *) &(ext->ext[0].internal_secret);
|
||||
if (memcmp(server.cluster->internal_secret, internal_secret_ext->internal_secret, CLUSTER_INTERNALSECRETLEN) > 0 ) {
|
||||
if (!link->inbound && link->node && !nodeInHandshake(link->node) &&
|
||||
memcmp(server.cluster->internal_secret, internal_secret_ext->internal_secret, CLUSTER_INTERNALSECRETLEN) > 0)
|
||||
{
|
||||
memcpy(server.cluster->internal_secret, internal_secret_ext->internal_secret, CLUSTER_INTERNALSECRETLEN);
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,65 @@ proc wait_for_secret_sync {maxtries delay num_nodes} {
|
|||
}
|
||||
}
|
||||
|
||||
# Build a raw cluster bus PING packet with an INTERNALSECRET extension.
|
||||
# sender_name: 40-char hex node ID to spoof as the sender.
|
||||
# secret: 40-byte binary string to inject as the internal secret.
|
||||
# client_port: the client port to announce.
|
||||
proc build_cluster_bus_ping_with_secret {sender_name secret client_port} {
|
||||
set CLUSTER_NAMELEN 40
|
||||
set CLUSTER_SLOTS 16384
|
||||
set NET_IP_STR_LEN 46
|
||||
set CLUSTERMSG_TYPE_PING 0
|
||||
set CLUSTERMSG_EXT_TYPE_INTERNALSECRET 4
|
||||
set CLUSTERMSG_FLAG0_EXT_DATA 4
|
||||
set CLUSTER_INTERNALSECRETLEN 40
|
||||
|
||||
# Extension: length(4) + type(2) + unused(2) + secret(40) = 48 bytes
|
||||
set ext_len 48
|
||||
set ext [binary format ISS $ext_len $CLUSTERMSG_EXT_TYPE_INTERNALSECRET 0]
|
||||
append ext $secret
|
||||
|
||||
set base_header_size 2256
|
||||
set totlen [expr {$base_header_size + $ext_len}]
|
||||
set cport [expr {$client_port + 10000}]
|
||||
|
||||
# Pad sender to CLUSTER_NAMELEN
|
||||
set sender_padded [binary format a${CLUSTER_NAMELEN} $sender_name]
|
||||
|
||||
# Build header fields up to the data section
|
||||
set hdr {}
|
||||
append hdr "RCmb"
|
||||
append hdr [binary format I $totlen]
|
||||
append hdr [binary format S 1] ;# ver
|
||||
append hdr [binary format S $client_port] ;# port
|
||||
append hdr [binary format S $CLUSTERMSG_TYPE_PING] ;# type
|
||||
append hdr [binary format S 0] ;# count (0 gossip entries)
|
||||
append hdr [binary format W 0] ;# currentEpoch
|
||||
append hdr [binary format W 0] ;# configEpoch
|
||||
append hdr [binary format W 0] ;# offset
|
||||
append hdr $sender_padded ;# sender
|
||||
append hdr [string repeat "\x00" [expr {$CLUSTER_SLOTS / 8}]] ;# myslots
|
||||
append hdr [string repeat "\x00" $CLUSTER_NAMELEN] ;# slaveof
|
||||
set myip [binary format a${NET_IP_STR_LEN} "127.0.0.1"]
|
||||
append hdr $myip ;# myip
|
||||
append hdr [binary format S 1] ;# extensions count
|
||||
append hdr [string repeat "\x00" 30] ;# notused1
|
||||
append hdr [binary format S 0] ;# pport
|
||||
append hdr [binary format S $cport] ;# cport
|
||||
append hdr [binary format S 1] ;# flags (CLUSTER_NODE_MASTER)
|
||||
append hdr [binary format c 0] ;# state
|
||||
append hdr [binary format ccc $CLUSTERMSG_FLAG0_EXT_DATA 0 0] ;# mflags
|
||||
|
||||
# Pad to base_header_size
|
||||
set cur_len [string length $hdr]
|
||||
if {$cur_len < $base_header_size} {
|
||||
append hdr [string repeat "\x00" [expr {$base_header_size - $cur_len}]]
|
||||
}
|
||||
|
||||
append hdr $ext
|
||||
return $hdr
|
||||
}
|
||||
|
||||
start_cluster 3 3 {tags {external:skip cluster}} {
|
||||
test "Test internal secret sync" {
|
||||
wait_for_secret_sync 50 100 6
|
||||
|
|
@ -69,3 +128,33 @@ start_cluster 3 3 {tags {external:skip cluster}} {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
start_cluster 1 0 {tags {external:skip cluster}} {
|
||||
test "Inbound cluster bus connection cannot inject a forged internal secret" {
|
||||
set host [srv 0 host]
|
||||
set port [srv 0 port]
|
||||
set cport [expr {$port + 10000}]
|
||||
set node_id [R 0 CLUSTER MYID]
|
||||
|
||||
set secret_before [R 0 debug internal_secret]
|
||||
|
||||
# Open a raw TCP socket to the cluster bus and send a forged PING
|
||||
# spoofing the server's own node name, with a zero-byte secret that
|
||||
# would win the lexicographic comparison against any real secret.
|
||||
set zero_secret [string repeat "\x00" 40]
|
||||
set pkt [build_cluster_bus_ping_with_secret $node_id $zero_secret $port]
|
||||
set fd [socket $host $cport]
|
||||
fconfigure $fd -translation binary -buffering full
|
||||
puts -nonewline $fd $pkt
|
||||
flush $fd
|
||||
after 500
|
||||
close $fd
|
||||
|
||||
# The internal secret must not have changed.
|
||||
set secret_after [R 0 debug internal_secret]
|
||||
assert_equal $secret_before $secret_after
|
||||
|
||||
# AUTH with the forged zero-byte secret must be rejected.
|
||||
assert_error {*WRONGPASS*} {R 0 auth "internal connection" $zero_secret}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue