on master help sign in
Fix SSRF bypass, safeCompare timing, challenge cap, retry backoff, and empty server name
a1766bfiko
server.knot+73−13
@@ -22,6 +22,10 @@ maxPresenceEntries = 10000
22maxMessagesPerRecipient = 500
22maxMessagesPerRecipient = 500
23clockSkewTolerance = 5000 -- 5 seconds tolerance for clock skew between federated servers
23clockSkewTolerance = 5000 -- 5 seconds tolerance for clock skew between federated servers
24maxForwardsEntries = 10000
24maxForwardsEntries = 10000
25+maxServerNameLen = 128
26+maxMessageIdLen = 128
27+maxTokenLen = 256
28+maxChallenges = 10000
25
29
26-- ============================================
30-- ============================================
27-- Types
31-- Types
@@ -174,8 +178,43 @@ containsDoubleColon = \s ->
174 else if take 1 s == ":" && take 1 (drop 1 s) == ":" then True {}
178 else if take 1 s == ":" && take 1 (drop 1 s) == ":" then True {}
175 else containsDoubleColon (drop 1 s)
179 else containsDoubleColon (drop 1 s)
176
180
181+containsColon = \s ->
182+ if s == "" then False {}
183+ else if take 1 s == ":" then True {}
184+ else containsColon (drop 1 s)
185+
186+containsMultipleColons = \s ->
187+ if s == "" then False {}
188+ else if take 1 s == ":" then containsColon (drop 1 s)
189+ else containsMultipleColons (drop 1 s)
190+
191+hasOctalOctet = \s ->
192+ if take 1 s == "0" && isDigitChar (take 1 (drop 1 s)) then True {}
193+ else hasOctalOctetAfterDot s
194+
195+hasOctalOctetAfterDot = \s ->
196+ if s == "" then False {}
197+ else if take 1 s == "." && take 1 (drop 1 s) == "0" && isDigitChar (take 1 (drop 2 s)) then True {}
198+ else hasOctalOctetAfterDot (drop 1 s)
199+
200+hasHexPrefix = \s ->
201+ if take 2 s == "0x" || take 2 s == "0X" then True {}
202+ else hasHexPrefixAfterDot s
203+
204+hasHexPrefixAfterDot = \s ->
205+ if s == "" then False {}
206+ else if take 1 s == "." && (take 2 (drop 1 s) == "0x" || take 2 (drop 1 s) == "0X") then True {}
207+ else hasHexPrefixAfterDot (drop 1 s)
208+
209+isAllDigitsUntilColonOrEnd = \s ->
210+ if s == "" then True {}
211+ else if take 1 s == ":" then True {}
212+ else if isDigitChar (take 1 s) then isAllDigitsUntilColonOrEnd (drop 1 s)
213+ else False {}
214+
177isBadServerName = \s ->
215isBadServerName = \s ->
216+ s == ""
178− containsBadUrlChar s
217+ || containsBadUrlChar s
179 || s == "localhost" || take 10 s == "localhost:"
218 || s == "localhost" || take 10 s == "localhost:"
180 || take 4 s == "127." || take 3 s == "10."
219 || take 4 s == "127." || take 3 s == "10."
181 || take 8 s == "192.168." || take 7 s == "169.254."
220 || take 8 s == "192.168." || take 7 s == "169.254."
@@ -187,7 +226,10 @@ isBadServerName = \s ->
187 || take 7 s == "198.18." || take 7 s == "198.19."
226 || take 7 s == "198.18." || take 7 s == "198.19."
188 || take 2 s == "::"
227 || take 2 s == "::"
189 || isCgnatRange s
228 || isCgnatRange s
190− || containsDoubleColon s
229+ || containsMultipleColons s
230+ || hasOctalOctet s
231+ || hasHexPrefix s
232+ || (isDigitChar (take 1 s) && isAllDigitsUntilColonOrEnd s)
191
233
192lookupSession = \sessions token t ->
234lookupSession = \sessions token t ->
193 single (do
235 single (do
@@ -204,6 +246,7 @@ extractToken = \authHeader ->
204 && (take 1 (drop 4 authHeader) == "e" || take 1 (drop 4 authHeader) == "E")
246 && (take 1 (drop 4 authHeader) == "e" || take 1 (drop 4 authHeader) == "E")
205 && (take 1 (drop 5 authHeader) == "r" || take 1 (drop 5 authHeader) == "R")
247 && (take 1 (drop 5 authHeader) == "r" || take 1 (drop 5 authHeader) == "R")
206 && take 1 (drop 6 authHeader) == " "
248 && take 1 (drop 6 authHeader) == " "
249+ && not (tooLong (drop 7 authHeader) maxTokenLen)
207 then drop 7 authHeader
250 then drop 7 authHeader
208 else ""
251 else ""
209
252
@@ -234,8 +277,10 @@ sendToEach = \servers action ->
234 else do
277 else do
235 catch (action server) (\err -> do
278 catch (action server) (\err -> do
236 println ("federation: error reaching " ++ server ++ ", retrying (1/2): " ++ show err)
279 println ("federation: error reaching " ++ server ++ ", retrying (1/2): " ++ show err)
280+ sleep 1000
237 catch (action server) (\err2 -> do
281 catch (action server) (\err2 -> do
238 println ("federation: retry 1 failed for " ++ server ++ ", retrying (2/2): " ++ show err2)
282 println ("federation: retry 1 failed for " ++ server ++ ", retrying (2/2): " ++ show err2)
283+ sleep 2000
239 catch (action server) (\err3 -> do
284 catch (action server) (\err3 -> do
240 println ("federation: all retries exhausted for " ++ server ++ ": " ++ show err3)
285 println ("federation: all retries exhausted for " ++ server ++ ": " ++ show err3)
241 yield {})))
286 yield {})))
@@ -336,8 +381,17 @@ serve = \req -> case req of
336 then {removed: True {}, items: acc.items}
381 then {removed: True {}, items: acc.items}
337 else {removed: acc.removed, items: union acc.items [c]}) {removed: False {}, items: []} challenges
382 else {removed: acc.removed, items: union acc.items [c]}) {removed: False {}, items: []} challenges
338 else {removed: False {}, items: challenges}
383 else {removed: False {}, items: challenges}
384+ let globalOldestExpiry = if count cappedChallenges.items >= maxChallenges
385+ then fold (\acc c -> if acc == -1 || c.expiresAt < acc then c.expiresAt else acc) -1 cappedChallenges.items
386+ else -1
387+ let globalCapped = if globalOldestExpiry >= 0
388+ then (fold (\acc c ->
389+ if not acc.removed && c.expiresAt == globalOldestExpiry
390+ then {removed: True {}, items: acc.items}
391+ else {removed: acc.removed, items: union acc.items [c]}) {removed: False {}, items: []} cappedChallenges.items).items
392+ else cappedChallenges.items
339 set *challenges = union
393 set *challenges = union
340− cappedChallenges.items
394+ globalCapped
341 [{pubkey: pubkey, challenge: challenge, expiresAt: t + 60000}]
395 [{pubkey: pubkey, challenge: challenge, expiresAt: t + 60000}]
342 yield pr
396 yield pr
343 respond {challenge: challenge}
397 respond {challenge: challenge}
@@ -546,7 +600,7 @@ serve = \req -> case req of
546 let dedupedIds = fold (\acc mid ->
600 let dedupedIds = fold (\acc mid ->
547 if mid == "" then acc
601 if mid == "" then acc
548 else if count acc >= maxMessageIds then acc
602 else if count acc >= maxMessageIds then acc
549− else if tooLong mid maxPubkeyLen then acc
603+ else if tooLong mid maxMessageIdLen then acc
550 else if not (isValidHex mid) then acc
604 else if not (isValidHex mid) then acc
551 else if count (do a <- acc; where a == normalizeHex mid; yield a) > 0 then acc
605 else if count (do a <- acc; where a == normalizeHex mid; yield a) > 0 then acc
552 else union acc [normalizeHex mid]) [] messageIds
606 else union acc [normalizeHex mid]) [] messageIds
@@ -566,10 +620,15 @@ serve = \req -> case req of
566 set *messages = case session of
620 set *messages = case session of
567 Nothing {} -> messages
621 Nothing {} -> messages
568 Just {value: _} -> diff messages ackedMessages
622 Just {value: _} -> diff messages ackedMessages
623+ presence <- *presence
624+ let knownPeers = fold (\acc p ->
625+ if p.server == serverHost then acc
626+ else if count (do a <- acc; where a == p.server; yield a) > 0 then acc
627+ else union acc [p.server]) [] presence
569 let ok = case session of
628 let ok = case session of
570 Nothing {} -> False {}
629 Nothing {} -> False {}
571 _ -> True {}
630 _ -> True {}
572− yield {ok: ok, ackedMessages: ackedMessages, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers}
631+ yield {ok: ok, ackedMessages: ackedMessages, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers, knownPeers: knownPeers}
573 let fedMessages = do
632 let fedMessages = do
574 m <- result.ackedMessages
633 m <- result.ackedMessages
575 where not (m.originServer == "")
634 where not (m.originServer == "")
@@ -577,6 +636,7 @@ serve = \req -> case req of
577 yield m
636 yield m
578 let originServers = fold (\acc m ->
637 let originServers = fold (\acc m ->
579 if count (do a <- acc; where a == m.originServer; yield a) > 0 then acc
638 if count (do a <- acc; where a == m.originServer; yield a) > 0 then acc
639+ else if count (do k <- result.knownPeers; where k == m.originServer; yield k) == 0 then acc
580 else union acc [m.originServer]) [] fedMessages
640 else union acc [m.originServer]) [] fedMessages
581 respond {ok: result.ok}
641 respond {ok: result.ok}
582 if result.ok && count originServers > 0 && not (federationSecret == "")
642 if result.ok && count originServers > 0 && not (federationSecret == "")
@@ -624,7 +684,7 @@ serve = \req -> case req of
624 t <- now
684 t <- now
625 let authorized = verifyFederationAuth authorization
685 let authorized = verifyFederationAuth authorization
626 let cappedHops = if hopsRemaining > 5 then 5 else hopsRemaining
686 let cappedHops = if hopsRemaining > 5 then 5 else hopsRemaining
627− let validRequest = authorized && cappedHops >= 1 && not (fromServer == "") && not (fromServer == serverHost) && not (tooLong fromServer maxPubkeyLen) && not (isBadServerName fromServer) && count events <= maxGossipEvents
687+ let validRequest = authorized && cappedHops >= 1 && not (fromServer == "") && not (fromServer == serverHost) && not (tooLong fromServer maxServerNameLen) && not (isBadServerName fromServer) && count events <= maxGossipEvents
628 let onlineEntries = do
688 let onlineEntries = do
629 e <- events
689 e <- events
630 where validRequest
690 where validRequest
@@ -632,7 +692,7 @@ serve = \req -> case req of
632 where not (e.server == "")
692 where not (e.server == "")
633 where not (e.pubkey == "")
693 where not (e.pubkey == "")
634 where not (tooLong e.pubkey maxPubkeyLen)
694 where not (tooLong e.pubkey maxPubkeyLen)
635− where not (tooLong e.server maxPubkeyLen)
695+ where not (tooLong e.server maxServerNameLen)
636 where not (isBadServerName e.server)
696 where not (isBadServerName e.server)
637 where isValidHex e.pubkey
697 where isValidHex e.pubkey
638 where not (e.server == serverHost)
698 where not (e.server == serverHost)
@@ -646,7 +706,7 @@ serve = \req -> case req of
646 where not (e.server == "")
706 where not (e.server == "")
647 where not (e.pubkey == "")
707 where not (e.pubkey == "")
648 where not (tooLong e.pubkey maxPubkeyLen)
708 where not (tooLong e.pubkey maxPubkeyLen)
649− where not (tooLong e.server maxPubkeyLen)
709+ where not (tooLong e.server maxServerNameLen)
650 where not (isBadServerName e.server)
710 where not (isBadServerName e.server)
651 where isValidHex e.pubkey
711 where isValidHex e.pubkey
652 where not (e.server == serverHost)
712 where not (e.server == serverHost)
@@ -823,7 +883,7 @@ serve = \req -> case req of
823 where not (e.server == "")
883 where not (e.server == "")
824 where not (e.pubkey == "")
884 where not (e.pubkey == "")
825 where not (tooLong e.pubkey maxPubkeyLen)
885 where not (tooLong e.pubkey maxPubkeyLen)
826− where not (tooLong e.server maxPubkeyLen)
886+ where not (tooLong e.server maxServerNameLen)
827 where not (isBadServerName e.server)
887 where not (isBadServerName e.server)
828 where isValidHex e.pubkey
888 where isValidHex e.pubkey
829 where not (e.server == serverHost)
889 where not (e.server == serverHost)
@@ -861,14 +921,14 @@ serve = \req -> case req of
861 broadcastOfflineGossip t gossipResult.peerServers gossipResult.expiredLocal
921 broadcastOfflineGossip t gossipResult.peerServers gossipResult.expiredLocal
862
922
863 ForwardMessage {toKey, fromKey, encryptedBlob, timestamp, originServer, messageId, authorization, respond} -> do
923 ForwardMessage {toKey, fromKey, encryptedBlob, timestamp, originServer, messageId, authorization, respond} -> do
864− let validFields = not (toKey == "") && not (fromKey == "") && not (encryptedBlob == "") && not (messageId == "") && not (tooLong toKey maxPubkeyLen) && not (tooLong fromKey maxPubkeyLen) && not (tooLong encryptedBlob maxBlobLen) && not (tooLong messageId maxPubkeyLen) && isValidHex toKey && isValidHex fromKey && isValidHex encryptedBlob && isValidHex messageId
924+ let validFields = not (toKey == "") && not (fromKey == "") && not (encryptedBlob == "") && not (messageId == "") && not (tooLong toKey maxPubkeyLen) && not (tooLong fromKey maxPubkeyLen) && not (tooLong encryptedBlob maxBlobLen) && not (tooLong messageId maxMessageIdLen) && isValidHex toKey && isValidHex fromKey && isValidHex encryptedBlob && isValidHex messageId
865 let toKey = if isValidHex toKey then normalizeHex toKey else toKey
925 let toKey = if isValidHex toKey then normalizeHex toKey else toKey
866 let fromKey = if isValidHex fromKey then normalizeHex fromKey else fromKey
926 let fromKey = if isValidHex fromKey then normalizeHex fromKey else fromKey
867 let encryptedBlob = if isValidHex encryptedBlob then normalizeHex encryptedBlob else encryptedBlob
927 let encryptedBlob = if isValidHex encryptedBlob then normalizeHex encryptedBlob else encryptedBlob
868 let messageId = if isValidHex messageId then normalizeHex messageId else messageId
928 let messageId = if isValidHex messageId then normalizeHex messageId else messageId
869 t <- now
929 t <- now
870 let authorized = verifyFederationAuth authorization
930 let authorized = verifyFederationAuth authorization
871− let validOrigin = not (originServer == "") && not (originServer == serverHost) && not (tooLong originServer maxPubkeyLen) && not (isBadServerName originServer)
931+ let validOrigin = not (originServer == "") && not (originServer == serverHost) && not (tooLong originServer maxServerNameLen) && not (isBadServerName originServer)
872 let clampedTs = if timestamp > t then t else if timestamp < t - messageMaxAge then t else timestamp
932 let clampedTs = if timestamp > t then t else if timestamp < t - messageMaxAge then t else timestamp
873 result <- atomic do
933 result <- atomic do
874 pr <- pruneExpired t
934 pr <- pruneExpired t
@@ -903,11 +963,11 @@ serve = \req -> case req of
903 let dedupedIds = fold (\acc mid ->
963 let dedupedIds = fold (\acc mid ->
904 if mid == "" then acc
964 if mid == "" then acc
905 else if count acc >= maxMessageIds then acc
965 else if count acc >= maxMessageIds then acc
906− else if tooLong mid maxPubkeyLen then acc
966+ else if tooLong mid maxMessageIdLen then acc
907 else if not (isValidHex mid) then acc
967 else if not (isValidHex mid) then acc
908 else if count (do a <- acc; where a == normalizeHex mid; yield a) > 0 then acc
968 else if count (do a <- acc; where a == normalizeHex mid; yield a) > 0 then acc
909 else union acc [normalizeHex mid]) [] messageIds
969 else union acc [normalizeHex mid]) [] messageIds
910− let validAckFields = authorized && originServer == serverHost && not (fromServer == "") && not (fromServer == serverHost) && not (tooLong fromServer maxPubkeyLen) && not (isBadServerName fromServer)
970+ let validAckFields = authorized && originServer == serverHost && not (fromServer == "") && not (fromServer == serverHost) && not (tooLong fromServer maxServerNameLen) && not (isBadServerName fromServer)
911 pruneResult <- atomic do
971 pruneResult <- atomic do
912 pr <- pruneExpired t
972 pr <- pruneExpired t
913 forwards <- *forwards
973 forwards <- *forwards