1. Introduction
This section is not normative.
Although [RFC1918] has specified a distinction between "private" and "public" internet addresses for over two decades, user agents haven’t made much progress at segregating the one from the other. Websites on the public internet can make requests to local devices and servers, which enable a number of malicious behaviors, including attacks on users' routers like those documented in [DRIVE-BY-PHARMING] , [SOHO-PHARMING] and [CSRF-EXPLOIT-KIT] .
Local Network Access aims to prevent these undesired requests to insecure devices on the local network. This is achieved by deprecating direct access to local IP addresses from public websites, and instead requiring that the user grants permission to the initiating website to make connections to their local network.
Note: This proposal builds on top of Chrome’s previously paused [PRIVATE-NETWORK-ACCESS] work but differs by gating access on a permission rather than via preflight requests.
1.1. Goals
The overarching goal is to prevent the user agent from inadvertently enabling attacks on devices running on a user’s local intranet, or services running on the user’s machine directly. For example, we wish to mitigate attacks on:
-
Users' routers, as outlined in [SOHO-PHARMING] . Note that status quo CORS protections don’t protect against the kinds of attacks discussed here as they rely only on CORS-safelisted methods and CORS-safelisted request-headers . No CORS preflight is triggered, and the attacker doesn’t care about reading the response, as the request itself is the CSRF attack.
-
Software running a web interface on a user’s loopback address. For better or worse, this is becoming a common deployment mechanism for all manner of applications, and often assumes protections that simply don’t exist (see [AVASTIUM] and [TREND-MICRO] for recent examples).
There should be a well-lit path to allow these requests when the user is both expecting and explicitly allowing the local network access requests to occur. For example, a user logged in to plex.tv may want to allow the site to connect to their local media server to directly load media content over the local network instead of routing through remote servers. See § 1.3 Examples below for more examples.
1.2. Non-goals
This spec does not attempt to make it easier to use HTTPS connections on local network devices. While this would be a useful goal, solving this problem is out of scope for this specification
1.3. Examples
1.3.1. User granting permission
Alice goes to Acme Printing Company’s web site to help diagnose the problem. Acme Printing Company’s web site tells Alice that it can connect to the printer to look at the diagnostic output of the printer. Alice’s browser asks Alice to allow https://support.acmeprintingcompany.com to connect to local devices on her network. Alice grants permission for https://support.acmeprintingcompany.com to connect to local devices on her network, and https://support.acmeprintingcompany.com connects to her local printer’s diagnostic output, and tells Alice that a part is malfunctioning on the printer and needs to be replaced.
1.3.2. User denying permission
1.3.3. New device configuration
1.3.4. App-based sign in
2. Framework
2.1. IP Address Space
Define
IPAddressSpace
as
follows:
enum {IPAddressSpace ,"public" ,"local" };"loopback"
Every IP address belongs to an IP address space , which can be one of three different values:
-
loopback : Includes loopback addresses, which are only accessible on the local host. In other words, addresses whose target differs for every device.
-
local : contains addresses that have meaning only within the current network. In other words, addresses whose target differs based on network position.
-
public : contains all other addresses. In other words, addresses whose target is the same for all devices globally on the IP network.
For convenience, we additionally define the following terms:
-
A loopback address is an IP address whose IP address space is loopback .
-
A local address is an IP address whose IP address space is local .
-
A public address is an IP address whose IP address space is public .
An IP address space lhs is less public than an IP address space rhs if any of the following conditions holds true:
To determine the IP address space of an IP address address , run the following steps:
-
If address belongs to the
::ffff:0:0/96"IPv4-mapped Address" address block, then replace address with its embedded IPv4 address. -
For each row in the Non-public IP address blocks table:
-
If address belongs to row ’s address block, return row ’s address space.
-
-
Return public .
| Address block | Name | Reference | Address space |
|---|---|---|---|
127.0.0.0/8
| IPv4 Loopback | [RFC1122] | loopback |
10.0.0.0/8
| Private Use | [RFC1918] | local |
100.64.0.0/10
| Carrier-Grade NAT | [RFC6598] | local |
172.16.0.0/12
| Private Use | [RFC1918] | local |
192.168.0.0/16
| Private Use | [RFC1918] | local |
198.18.0.0/15
| Benchmarking | [RFC2544] | loopback |
169.254.0.0/16
| Link Local | [RFC3927] | local |
::1/128
| IPv6 Loopback | [RFC4291] | loopback |
fc00::/7
| Unique Local | [RFC4193] | local |
fe80::/10
| Link-Local Unicast | [RFC4291] | local |
fec0::/10
| Site-Local Unicast | [RFC3513] | local |
0.0.0.0/32
| IPv4 null IP address | [RFC1884] | loopback |
0.0.0.0/8
| IPv4 null IP addresses | [RFC1884] | local |
::/128
| IPv6 unspecified address | [RFC1884] | loopback |
2001:db8::/32
| IPv6 documentation addresses | [RFC3849] | local |
3fff::/20
| IPv6 documentation addresses | [RFC9637] | local |
::ffff:0:0/96
| IPv4-mapped | [RFC4291] | see mapped IPv4 address |
User Agents MAY allow certain IP address blocks' address space to be overridden through administrator or user configuration. This could prove useful to protect e.g. IPv6 intranets where most IP addresses are considered public per the algorithm above, by instead configuring user agents to treat the intranet as local .
Note:
Link-local
IP
addresses
such
as
169.254.0.0/16
are
considered
local
,
since
such
addresses
can
identify
the
same
target
for
all
devices
on
a
network
link.
A
previous
version
of
this
specification
considered
them
to
be
loopback
instead.
Note:
The
contents
of
each
IP
address
space
were
at
one
point
determined
in
accordance
with
the
IANA
Special-Purpose
Address
Registries
(
[IPV4-REGISTRY]
and
[IPV6-REGISTRY]
)
and
the
Globally
Reachable
bit
defined
therein.
This
turned
out
to
be
an
inaccurate
signal
for
our
uses,
as
described
in
WICG/private-network-access
issue
#50
.
Note: [PRIVATE-NETWORK-ACCESS] used the address spaces public, private, and local. This specification renames the address spaces to public, local, and loopback, respectively.
Note:
[FETCH]
plans
to
specify
that
requests
to
the
null
IP
address
will
be
treated
as
a
network
error.
We
map
both
the
IPv4
null
IP
address
space
and
the
IPv6
unspecified
address
to
loopback
as
some
systems
can
still
route
them
back
to
the
local
host
machine
(and
0.0.0.0/8
can
refer
to
"specified
hosts
on
this
network",
so
is
mapped
to
local
)
per
[RFC5735]
.
Once
the
changes
to
Fetch
have
been
made
and
adopted,
we
will
no
longer
need
to
handle
them
in
this
specification.
2.2. Local Network Request
A
request
(
request
)
is
a
local
network
request
if
request
’s
current
url
’s
host
maps
to
an
IP
address
whose
IP
address
space
is
less
public
than
request
’s
policy
container
’s
IP
address
space
.
The classification of IP addresses into two broad address spaces is an imperfect and theoretically-unsound approach. It is a proxy used to determine whether two network endpoints should be allowed to communicate freely or not, in other words whether endpoint A is reachable from endpoint B without pivoting through the user agent on endpoint C.
This approach has some flaws:
-
false positives: an intranet server with a public address might not be able to directy issue requests to another server on the same intranet with a local address .
-
false negatives: a client connected to two different local networks, say a home network and a VPN, might allow a website served from the VPN to access devices on the home network. See also the issue below.
Even so, this specification aims to offer a pragmatic solution to a security issue that broadly affects most users of the Web whose network configurations are not so complex.
Requests originating from the loopback address should not be considered local network requests , and should not be subject to local network access checks, since any software running on the user’s device is already in the most privileged vantage point on the user’s network.
The
definition
of
local
network
requests
could
be
expanded
to
cover
all
cross-origin
requests
for
which
the
current
url
’s
host
maps
to
an
IP
address
whose
IP
address
space
is
not
public
.
This
would
prevent
a
malicious
server
on
the
local
network
from
attacking
other
servers,
including
servers
on
localhost
.
See
WICG/private-network-access
issue
#39
.
NOTE: Currently, Chromium only implements Local Network Access restrictions for public to local or loopback requests, and does not enforce the permission for cross-origin local requests. This restriction can be shipped as an incremental improvement in future implementations.
NOTE: Because local names and addresses are not meaningful outside the bounds of the network, implementers might want to use a different permission prompt for the cross-origin local case than for the public to local case. Additionally, implementers might want to scope these permission grants to the specific network or to the current browsing session only.
NOTE: Some local network requests are more challenging to secure than others. See § 4.4 Rollout Difficulties for more details.
2.3. Local Network Request Permission Prompt
A local network access permission prompt is introduced to allow for users to approve of local network requests from public websites to local network servers.
When a local network request is detected, a prompt is shown to the user asking for permission to access the local network. If the user decides to grant the permission, then the fetch continues. If not, it fails.
The exact scope of the permission is implementation-defined. The permission may be as coarse-grained as allowing a specific origin to send local network requests to any endpoint on the local network, or may be more fine-grained to only allow specific origins to communicate with specific endpoints on the local network. A user agent may persist this decision to reduce permission fatigue.
2.4. Secure Context Restriction
The capability to make local network requests is a powerful feature and must only be allowed from secure contexts .
To
be
able
to
apply
LNA
checks
to
all
cross-origin
local
requests
in
the
future
(see
Issue
above),
Chromium
plans
to
exempt
local
servers
that
likely
cannot
currently
get
publicly
trusted
HTTPS
certificates
from
this
requirement
(e.g.,
servers
on
.local
and
private
IP
literals).
See
§ 4.4
Rollout
Difficulties
for
more
discussion,
and
also
see
WICG/private-network-access
issue
#96
.
2.5. Mixed Content
Many local network servers do not run HTTPS, as it has proven difficult (and sometimes even impossible) to migrate local network servers away from HTTP. This is problematic as the secure context restriction, combined with mixed content checks, would block many local network requests even if the user would give permission for the request to occur.
One solution to this problem is to bypass mixed content checks in situations where the request is known to be a local network request . This is known in a few situations:
-
When the hostname of the request target is an IP literal identified as local in the Non-public IP address blocks" table (e.g., an [RFC1918] IP literal)
-
When the hostname of the request is a .local domain (RFC 6762)
There
may
be
situations
in
which
neither
of
the
above
situations
is
true,
and
yet
the
site
wants
to
identify
a
request
as
being
a
local
network
request
.
This
can
be
mitigated
by
adding
a
new
parameter
to
the
fetch()
options
bag:
fetch( "http://router.local/ping" , { targetAddressSpace: "local" , });
This
instructs
the
browser
to
allow
the
fetch
to
bypass
mixed-content
checks
even
though
the
scheme
is
non-secure
and
potentially
obtain
a
connection
to
the
target
server.
The
new
fetch()
API
is
backward-compatible.
Note that this feature cannot be abused to bypass mixed content in general. If the resolved remote IP address does not belong to the IP address space specified as the targetAddressSpace option value, then the request will fail. If it does belong, then the permission can be checked to allow or fail the request.
TODO:
Decide
if
we
want
to
keep
the
CSP
directive
treat-as-public-address
around
(see
private-network-access#csp
).
This
directive
would
be
obviated
if
we
implemented
WICG/private-network-access
issue
#39
.ß
3. Integrations
This section is non-normative.
This document proposes a number of modifications to other specifications in order to implement the mitigations sketched out in the examples above. These integrations are outlined here for clarity, but the external documents are the normative references.
3.1. Integration with Permissions
This
document
defines
a
default
powerful
feature
identified
by
the
name
"local-network-access"
.
3.2. Integration with Permissions Policy
Local
Network
Access
defines
a
policy-controlled
feature
identified
by
the
string
"local-network-access".
Its
default
allowlist
is
'self'
.
3.3. Integration with Fetch
This document proposes a few changes to [FETCH] , with the following implication: local network requests are only allowed if their client is a secure context and permission is granted by the user. If the request would have been blocked as mixed content, it can be allowed as long as the website states its intention to access the local network, and users give permission.
Note: This includes navigations. These can indeed be used to trigger CSRF attacks, albeit with less subtlety than with subresource requests.
Chromium only applies LNA restrictions to iframe navigations currently. It may be worth expanding this to include main-frame navigations (especially popup windows which can be controlled by their opener).
Note: [FETCH] does not yet integrate the details of DNS resolution into the Fetch algorithm, though it does define an obtain a connection algorithm which is enough for this specification. Local Network Access checks are applied to the newly-obtained connection. Given complexities such as Happy Eyeballs ( [RFC6555] , [RFC8305] ), these checks might pass or fail non-deterministically for hosts with multiple IP addresses that straddle IP address space boundaries.
3.3.1. Fetching
What follows is a sketch of a potential solution:
-
Connection objects are given a new IP address space property, initially null. This applies to WebSocket connections too.
-
A new step is added to the obtain a connection algorithm immediately before appending connection to the user agent’s connection pool :
-
Set connection ’s IP address space to the result of running the determine the IP address space algorithm on the IP address of connection ’s remote endpoint.
The remote endpoint concept is not specified in [FETCH] yet, hence this is still handwaving to some extent.
-
-
Request objects are given a new target IP address space property, initially null.
-
Response objects are given a new IP address space property, whose value is an IP address space , initially null.
-
Define a new Local Network Access check algorithm. Given a request request and a connection connection :
-
If request ’s origin is a potentially trustworthy origin and request ’s current URL ’s origin is same origin with request ’s origin , then return null.
-
If request ’s policy container is null, then return null.
NOTE: If request ’s policy container is null, then LNA checks do not apply to request . Users of the fetch algorithm have to take care to either set request ’s client to an environment settings object with a non-null policy container and let fetch initialize request ’s policy container accordingly, or to directly set request ’s policy container to a non-null value.
-
If request ’s target IP address space is not null, then:
-
Assert : request ’s target IP address space is not public .
-
If connection ’s IP address space is not equal to then request ’s target IP address space , then return a network error .
-
Return null.
-
-
If connection ’s IP address space is less public than request ’s policy container ’s IP address space , then:
-
Let error be a network error .
-
If request ’s client is not a secure context (including if it is null), then return error .
-
Set error ’s IP address space property to connection ’s IP address space .
-
TODO: Permission check is sketched out below, wording is still vagueLet settingsObject be request ’s client . Let global be settingsObject ’s global object .
Let document be global ’s associated Document .
-
If
the initiating origin has been granted thedocument is null, then return error .NOTE: This step will cause local network requests from Service Workers to fail, as Service Workers do not always have an associated Document. Future versions of this specification need to define how to handle Workers, particularly since Permissions Policy is not yet supported in Workers. See w3c/webappsec-permissions-policy#207 .
Define local network access
permission, return null.behavior for Service Workers. -
If document is not allowed to use "local-network-access", then return error .
Let permissionState be the
initiating origin has been deniedresult of getting thelocal network access permission,current permission state given "local-network-access" and global .If permissionState is denied , then return error .
-
Otherwise, promptIf permissionState is granted , then return null. Prompt the
user:user to choose whether to grant "local-network-access" for global :-
If the user grants permission, then return null.
-
If the user denies
thepermission, then return error .
-
-
-
Return null.
-
-
The fetch algorithm is amended to add 2 new steps right after request’s policy container is set:
-
If request ’s target IP address space is null:
-
If request ’s URL’s host host is an IP address and the result of running the determine the IP address space algorithm on host is “local”, then set request ’s target IP address space property to “local”.
-
If request ’s URL’s host’s public suffix is
"local", then set request ’s target IP address space property to"local".NOTE: We could also set the target IP address space to
localif the request’s URL’s host is “localhost” or “127.0.0.1” (because of [LET-LOCALHOST-BE-LOCALHOST] ), but we do not need special handling for the loopback case as it is already considered to be potentially trustworthy and won’t trigger mixed content checks.NOTE: We also explicitly do not set the target address space property in the public case, because that breaks the next step here... (but maybe we could just skip that?)
NOTE: We don’t set the target IP address space here if it was already non-null in order to prefer the explicit targetAddressSpace if set by the fetch() API.
-
-
If request ’s target IP address space is public, then return a network error.
OPEN QUESTION: Do we need this? Under what conditions can this get set to "public"?
-
-
The HTTP-network fetch algorithm is amended to add 3 new steps right after checking that the newly-obtained connection is not failure:
-
Set response ’s IP address space to connection ’s IP address space .
-
Let localNetworkAccessCheckResult be the result of running Local Network Access check for fetchParams ’ request and connection .
-
If localNetworkAccessCheckResult is a network error , return localNetworkAccessCheckResult .
-
-
Define a new algorithm called HTTP-no-service-worker fetch based on the existing steps in HTTP fetch that are run if response is still null after handling the fetch via service workers, and amend those slightly as follows:
-
Immediately after running HTTP-network-or-cache fetch:
-
If response is a network error and response ’s IP address space is non-null, then:
-
Set request ’s target IP address space to response ’s IP address space .
-
Return the result of running HTTP-no-service-worker fetch given fetchParams .
-
NOTE: Because request’s target IP address space is set to a non-null value when recursing, this recursion can go at most 1 level deep.
-
-
TODO: Figure out what we need to add for cache fetch. A sketch of Chromium’s behavior is included below in § 4.3 HTTP Cache .
NOTE: The requirement that local network requests be made from secure contexts means that any insecure request will be blocked as mixed content unless we can know ahead of time that the request can be considered a local network request. By setting the target IP address space property (see Step 6i and 6ii above), we only need to make a small change to Mixed Content -- see § 3.4 Integration with Mixed Content .
3.3.2. Fetch API
The Fetch API needs to be adjusted as well.
-
Append an optional entry to
RequestInfo, whose key is targetAddressSpace , and value is aIPAddressSpace.{partial dictionary RequestInit {IPAddressSpace ; };targetAddressSpace -
Define a new targetAddressSpace representing the above in request .
{partial interface Request {readonly attribute IPAddressSpace ; };targetAddressSpace -
The
new Request( input , init )is appended with the following step right before setting this ’s request to request :-
If init ["
targetAddressSpace"] exists , then switch on init ["targetAddressSpace"]:- public
- Do nothing.
- local
- Set request ’s targetAddressSpace to local .
-
3.4. Integration with Mixed Content
The Should fetching request be blocked as mixed content? is amended to add the following condition to one of the allowed conditions:
-
request ’s origin is not a potentially trustworthy origin , and request ’s target IP address space is local .
The " Upgrade request to an a priori authenticated URL as mixed content, if appropriate " algorithm is amended to add the following condition as an exception from upgrading in step 1:
-
request ’s target IP address space is local
3.5. Integration with WebSockets
WebSockets connections should be subject to the same local network access permission requirements. WebSocket opening handshake directly applies fetch in step 11, and so no modification to the WebSocket specification is required beyond the fetch change proposed above.
One
minor
difference
between
the
Fetch
API
and
the
WebSockets
API
is
that
WebSockets
does
not
have
an
equivalent
to
fetch’s
RequestInit
,
and
so
there
is
no
place
to
put
in
a
targetAddressSpace
option
to
bypass
mixed
content
checks
for
ws://
urls.
3.6. Integration with WebTransport
WebTransport connections should be subject to the same local network access permission requirements.
3.7. Integration with HTML
To support the checks in [FETCH] , user agents must remember the source IP address space of contexts in which network requests are made. To this effect, the [HTML] specification is patched as follows:
-
A new IP address space property is added to the policy container struct .
-
It is initially public.
-
-
An additional step is added to the clone a policy container algorithm:
-
Set clone ’s IP address space to policyContainer ’s IP address space .
-
-
An additional step is added to the create a policy container from a fetch response algorithm:
-
Set result ’s IP address space to response ’s IP address space .
-
example.com
resolves
to
a
public
address
(say,
123.123.123.123
),
then
the
Document
created
when
navigating
to
https://example.com/document.html
will
have
its
policy
container
’s
IP
address
space
property
set
to
public
.
If
this
Document
then
embeds
an
about:srcdoc
iframe,
then
the
child
frame’s
Document
will
have
its
policy
container
’s
IP
address
space
property
set
to
public
.
If,
on
the
other
hand,
example.com
resolved
to
a
local
address
(say,
127.0.0.1
),
then
the
Document
created
when
navigating
to
https://example.com/document.html
will
have
its
policy
container
’s
IP
address
space
property
set
to
local
.
TODO: Also update the reference to Private Network Access in HTML § 7.1.4 Cross-origin embedder policies .
3.8. Integration with Workers
This section is non-normative.
Given
that
WorkerGlobalScope
already
has
a
policy
container
field
populated
using
the
create
a
policy
container
from
a
fetch
response
algorithm,
the
avove
integrations
with
Fetch
and
HTML
apply
just
as
well
to
worker
contexts
as
to
documents.
example.com
resolves
to
a
public
address
(say,
123.123.123.123
),
then
a
WorkerGlobalScope
created
by
fetching
a
script
from
https://example.com/worker.js
will
have
its
policy
container
’s
IP
address
space
property
set
to
public
.
Any fetch request initiated by this worker that obtains a connection to an IP address in the local or loopback address spaces would then be a local network request .
Chromium’s implementation currently applies the LNA permission for service worker-initiated fetches based on the worker’s script origin (since there may not be an active document around when the service worker is executing). It may be better for this to be based on the partitioned storage key of the worker, and it would also be good if permissions policy supported service workers.
The Service Worker soft update algorithm unfortunately sets a request client of "null" when fetching an updated script. This causes all sorts of issues, and interferes with the local network access check algorithm laid out above. Indeed, there is no request client from which to copy the policy container during fetch . See WICG/private-network-access issue #83 .
4. Implementation considerations
4.1. File URLs
It isn’t entirely clear how file URLs fit into the public/local scheme outlined above. It would be nice to prevent folks from harming themselves by opening a malicious HTML file locally, on the one hand, but on the other, code running locally is somewhat outside of any coherent threat model.
For the moment, let’s err on the side of treating file URLs as local, as they seem to be just as much a part of the local system as anything else on a loopback address.
Re-evaluate this after implementation experience.
4.2. Proxies
In the current implementation of this specification in Chromium, proxies influence the address space of resources they proxy. Specifically, resources fetched via proxies are considered to have been fetched from the proxy’s IP address itself.
If a Document served by foo.example on a public address is fetched by the user agent via a proxy on a local address, then the Document ’s policy container ’s IP address space is set to local.
The Document will in turn be allowed to make requests to other local addresses accessible to the browser.
This can allow a website to learn that it was proxied by observing that it is allowed to make requests to local addresses, which is a privacy information leak. While this requires correctly guessing the URL of a resource on the local network, a single correct guess is sufficient.
This is expected to be relatively rare and not warrant more mitigations. After all, in the status quo all websites can make requests to all IP addresses with no restrictions whatsoever.
It would be interesting to explore a mechanism by which proxies could tell the browser "please treat this resource as public/local anyway", thereby passing on some information about the IP address behind the proxy.
4.3. HTTP Cache
The current implementation of this specification in Chromium interacts with the HTTP cache in two noteworthy ways, depending on which kind of resource is loaded from cache.
4.3.1. Main resources
A document constructed from a cached response remembers the IP address from which the response was initially loaded. The IP address space of the document is derived anew from the IP address.
In the common case, this entails that the document ’s policy container ’s IP address space is restored unmodified. However in the event that the user agent’s configuration has changed, the derived IP address space might be different.
The user agent then restarts, and a new configuration is applied specifying that 1.2.3.4 should be classified as a local address instead.
The user agent navigates to http://foo.example/ once more and loads the main resource from the HTTP cache. The resulting document ’s policy container ’s IP address space is now set to local.
4.3.2. Subresources
Subresources loaded from the HTTP cache are subject to the Local Network Access check. This is not yet reflected in the algorithms above, since that check is only applied in HTTP-network fetch .
TODO: Specify and explain Chromium’s behavior here, or add an HTTP-network-or-cache fetch integration above. See WICG/private-network-access issue #75 . We include a sketch below.
As with main resources, a subresource constructed from a cached response remembers the IP address from which the response was initially loaded. The IP address space of the response is derived anew from the IP address.
In the common case, this entails that the response’s IP address space is restored unmodified. However, in the event that the user agent’s configuration has changed, the derived IP address space might be different.
When a subresource request is blocked by LNA checks (i.e., the permission was denied), there is no resource response cached. If the permission is later reset or granted, the subresource request will go to the network.
The
user
agent
navigates
to
https://foo.example,
which
is
loaded
from
1.2.3.4
(which
has
an
IP
address
space
of
public
).
The
document
triggers
a
subresource
request
for
http://bar.local/image.jpg,
which
when
a
connection
is
created
has
an
IP
address
of
10.0.1.1
(which
has
an
IP
address
space
of
local
).
This
triggers
a
permission
prompt,
granting
the
Local
Network
Access
permission
to
https://foo.example,
and
then
the
subresource
is
loaded
and
added
to
the
user
agent’s
cache.
The user agent resets the permission for https://foo.example.
The user agent navigates to https://foo.example once more, which once again triggers a subresource request for http://bar.local/image.jpeg. This resource is in the user agent’s cache, with a cached response IP address of 10.0.1.1. This again triggers a permission prompt, which if granted will finish loading the resource from the user agent’s cache.
The
user
agent
navigates
to
https://foo.example,
which
is
loaded
from
1.2.3.4
(which
has
an
IP
address
space
of
public
).
The
document
triggers
a
subresource
request
for
http://bar.local/image.jpg,
which
when
a
connection
is
created
has
an
IP
address
of
10.0.1.1
(which
has
an
IP
address
space
of
local
).
This
triggers
a
permission
prompt,
denying
the
Local
Network
Access
permission
to
https://foo.example.
The
subresource
request
is
blocked
and
no
resource
is
added
to
the
user
agent’s
cache.
The user agent resets the permission for https://foo.example.
The user agent navigates to https://foo.example once more, which once again triggers a subresource request for http://bar.local/image.jpeg. This resource is not in the user agent’s cache, so goes through HTTP network fetch and triggers the permission prompt.
See § 5.6 HTTP cache for a discussion of security implications.
4.4. Rollout Difficulties
Local Network Access essentially deprecates direct access to the local network in favor of more secure user-agent-mediated alternatives. Web deprecations are hard. Chromium previously encountered many stumbling blocks on the way to shipping parts of [PRIVATE-NETWORK-ACCESS] (the predecessor to this specification).
In
particular,
shipping
restrictions
on
fetches
from
non-secure
contexts
in
the
local
IP
address
space
to
services
on
localhost
have
proven
particularly
difficult,
for
a
lower
payoff.
Indeed,
exploiting
such
fetches
requires
attackers
to
already
have
a
foothold
in
the
private
network,
which
substantially
raises
attack
difficulty.
As
a
result,
Chromium
is
exempting
these
fetches
from
restrictions
temporarily,
choosing
to
focus
on
fetches
from
the
public
IP
address
space.
See
§ 5.5
Local
network
attackers
for
a
discussion
of
security
implications.
5. Security and Privacy Considerations
5.1. User Mediation
The proposal in this document only ensures that the user consents to access from the public internet. This proposal does not allow for devices to explicitly approve for connections from the public internet.
An alternative model where devices had to explicitly approve for connections from the public internet was attempted in [PRIVATE-NETWORK-ACCESS] , but ran into rollout difficulties .
Requiring user mediation, regardless of whether the target endpoint opts in to the connection, also helps mitigate privacy issues such as the "Local Mess" tracking method where the local endpoint and the remote website are colluding to link user identities.
5.2. DNS rebinding
The mitigation described here operates upon the IP address which the user agent actually connects to when loading a particular resource. This check MUST be performed for each new connection made, as DNS rebinding attacks may otherwise trick the user agent into revealing information it shouldn’t.
DNS
rebinding
attacks
also
mean
that
local
network
access
checks
MUST
apply
to
all
public
to
local
requests
(that
is,
we
cannot
simplify
the
algorithm
to
be
“any
cross-origin
request
to
a
local
endpoint”,
as
this
would
open
a
risk
of
a
user
navigation
to
a.example,
then
moving
networks
or
having
DNS
change
to
make
a.example
point
to
a
local
address).
5.3. Scope of Mitigation
The proposal in this document merely mitigates attacks against local web services, it cannot fully solve them. For example, a router’s web-based administration interface must be designed and implemented to defend against CSRF on its own, and should not rely on a UA that behaves as specified in this document. The mitigation this document specifies is necessary given the reality of local web service implementation quality today, but vendors should not consider themselves absolved of responsibility, even if all UAs implement this mitigation.
5.4. Cross-network confusion
Most local networks cannot communicate with each other, yet they are all treated by this specification as belonging to the local IP address space. Going further, local addresses have meaning only on the local network where they are used. The same IP address might refer to entirely different devices in two different networks. A user granting permission for a.example to make local network requests could then move to a different network, and then a.example would continue to be able to make local network requests.
This opens the door to cross-network attacks:
-
A user connects to two different local networks: a home Wi-Fi network and a corporate VPN. Their smart fridge has been hacked. They open their smart fridge’s web interface, which then performs CSRF attacks against corporate websites accessible via the VPN.
-
A user connects to a malicious internet cafe Wi-Fi, which requires users to keep a captive portal page open. They close their laptop, go home, open up their laptop again. The captive portal page (either still open or reloaded from cache as the user agent restores its previous state) performs CSRF
-
attacks against the user’s home devices.
-
A user connects to a malicious internet cafe Wi-Fi, whose captive portal website caches a malicious script from http://router.example/popular-library.js (the cafe network administrator operates a malicious DNS server) with a very long expiry. The user powers their computer off, goes home, boots up their computer again and visits their router’s administration interface at http://router.example, which embeds /popular-library.js. The malicious script is loaded in the administration interface’s first-party context.
None of these attacks are novel - they are just examples of the limitations of this specification.
Potential mitigations would require noticing network changes and clearing state specific to the previous network. Doing so in a fully general manner is likely to be impossible short of clearing all state. Maybe a practical compromise can be reached. See WICG/private-network-access issue #28 .
An alternative approach might be to scope the permission grant to an identifier that distinguishes different networks.
5.5. Local network attackers
Until
local
network
access
checks
are
applied
to
all
cross-origin
requests
to
the
local
address
space,
it
is
possible
for
a
malicious
server
on
the
local
network
to
(1)
attack
other
servers
on
the
local
network,
and
(2)
attack
services
running
on
localhost
on
a
user’s
machine.
(1)
is
already
possible
without
needing
to
abuse
the
user’s
browser,
but
(2)
remains
a
concern.
For
example,
a
captive
portal
might
be
able
to
redirect
the
user
to
a
malicious
page
that
tries
to
probe
and
attack
vulnerable
localhost
services
running
on
the
user’s
machine.
(See
also
WICG/private-network-accesss
issue
#39
.)
We see public websites on the drive-by web as a more urgent security (and privacy) risk and see an incremental rollout of these protections as valuable progress, despite the lingering risk from local network attackers.
5.6. HTTP cache
5.6.1. Applying checks to subresources
Cached subresources are protected by this specification, as the HTTP cache remembers the source IP address which can be used in the Local Network Access check algorithm during HTTP-network-or-cache fetch .
Without this check, a malicious public website might be able to determine whether a user has visited particular private websites in the past.
Due to HTTP cache partitioning, a subresource can only be loaded from cache by malicious attackers who manage to replicate the network partition key of the cache entry. One way an attacker could achieve this is by manipulating DNS (see also § 5.2 DNS rebinding in order to impersonate the top-level site that initially embedded the cached resource.
The user agent navigates to http://router.example, which is served from 192.168.1.1. The website embeds a logo from http://router.example/$BRAND-logo.png, which is cached.
A malicious attacker then re-binds router.example to an attacker-controlled public IP address, and somehow tricks the user into visiting http://router.example again. The malicious website attempts to embed the logo, and monitors whether the load is successful. If so, the attacker has determined the brand of the user’s router.
5.6.2. HTTP cache poisoning
While this specification aims to protect local network servers from receiving requests from public websites, DNS rebinding can be used to carry out a similar attack through cache poisoning of unauthenticated resources.
Attackers masquerading as http://router.com can cache a malicious script at http://router.com/totally-legit.js. Later on, when the user navigates to http://router.com/, the page might request the poisoned script and execute attacker code in a less public IP address space .
This attack is partially mitigated by cache partitioning, which makes it so that the attacker must navigate a top-level browsing context to http://router.com/ before caching resources, which lacks subtlety. It is also not specific to Local Network Access, rather being a symptom of plaintext HTTP’s lack of authentication and integrity protection.
6. Acknowledgements
Many thanks for valuable feedback and advice from Titouan Rigoudy, Jonathan Hao, and Yifan Luo who worked on the original Private Network Access proposals and specification, and generously discussed their work and helped brainstorm paths forward.