An address is checked and then resolved a second time, so DNS can change in between #58
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Postulo/postulo#58
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What happens
validate_public_urlinplugins/fetching.pyresolves a hostname, checks every address itanswers with, and returns the URL as a string:
The caller then hands that string to httpx, which resolves the name again before
connecting. Between the two lookups the answer is free to change, and a record with a
one-second time to live is free to give a public address to the first query and
127.0.0.1to the second. The check passes and the connection goes somewhere else.This is the standard way a resolve-then-connect check is defeated, and it applies to every
caller: capture (
fetch_page), company logos, portfolio links, and every pluginconnection through
http.client(), whose event hook re-runs the same resolve-then-connectcheck on each redirect.
Why it is worth fixing even though it is fiddly
Nothing else in the chain is a second line of defence. The whole reason capture refuses
private addresses is that the URL came from a stranger's page — and a stranger who controls
a hostname controls its time to live.
Whether this is reachable in practice depends on the resolver in front of Postulo: a
caching resolver that honours a one-second time to live makes it straightforward, and one
that enforces a floor makes it a race. Neither is a reason to leave it.
Shape
Resolve once, and connect to what was checked:
Hostheader set to the hostname and,for HTTPS, the TLS server name set to it as well, so certificate checking still works.
httpx supports this through a custom transport, or through resolving into the connection
pool. Whichever way it goes, the check and the connection must not be two separate
lookups.
An
AddressPinnedTransportinplugins/http.pywould fix every caller at once, which isthe argument for putting it there rather than in each one.
Classification
Bug, security. Not breaking.
Depends on
Nothing, but it is worth doing after the link checker is moved onto the guarded client, so
there is one path to harden rather than two.