1. 소개
이 절은 규범적이지 않다.
[RFC1918]가 20년 넘게 "private" 인터넷 주소와 "public" 인터넷 주소를 구분하도록 명시해 왔지만, 사용자 에이전트는 둘을 서로 분리하는 데 큰 진전을 이루지 못했다. public 인터넷의 웹사이트는 로컬 기기 및 서버에 요청을 보낼 수 있으며, 이는 [DRIVE-BY-PHARMING], [SOHO-PHARMING] 및 [CSRF-EXPLOIT-KIT]에 문서화된 것과 같은 사용자 라우터에 대한 공격을 포함하여 여러 악의적 동작을 가능하게 한다.
Local Network Access는 로컬 네트워크의 안전하지 않은 기기로 향하는 이러한 원치 않는 요청을 방지하는 것을 목표로 한다. 이는 public 웹사이트에서 로컬 IP 주소로의 직접 접근을 폐기하고, 대신 사용자가 시작 웹사이트에 자신의 로컬 네트워크로 연결을 만들 수 있는 권한을 부여하도록 요구함으로써 달성된다.
Note: 이 제안은 Chrome의 이전에 일시 중단된 [PRIVATE-NETWORK-ACCESS] 작업 위에 구축되지만, preflight 요청을 통하지 않고 권한에 따라 접근을 제어한다는 점에서 다르다.
1.1. 목표
전반적인 목표는 사용자 에이전트가 사용자의 로컬 인트라넷에서 실행되는 기기 또는 사용자의 머신에서 직접 실행되는 서비스에 대한 공격을 부주의하게 가능하게 하지 않도록 방지하는 것이다. 예를 들어, 다음에 대한 공격을 완화하고자 한다:
-
[SOHO-PHARMING]에 설명된 사용자의 라우터. 현재의 CORS 보호는 여기서 논의하는 종류의 공격을 막지 못한다는 점에 유의하라. 이는 CORS-safelisted 메서드와 CORS-safelisted 요청 헤더에만 의존하기 때문이다. CORS preflight는 트리거되지 않으며, 공격자는 응답을 읽는 데 관심이 없다. 요청 자체가 CSRF 공격이기 때문이다.
-
사용자의 루프백 주소에서 웹 인터페이스를 실행하는 소프트웨어. 좋든 나쁘든, 이것은 온갖 애플리케이션에서 일반적인 배포 메커니즘이 되어 가고 있으며, 존재하지 않는 보호를 가정하는 경우가 많다(최근 예로 [AVASTIUM] 및 [TREND-MICRO] 참조).
사용자가 로컬 네트워크 접근 요청이 발생할 것을 기대하고 명시적으로 허용하는 경우 이러한 요청을 허용하기 위한 잘 드러난 경로가 있어야 한다. 예를 들어, plex.tv에 로그인한 사용자는 사이트가 원격 서버를 거치지 않고 로컬 네트워크를 통해 미디어 콘텐츠를 직접 로드하기 위해 자신의 로컬 미디어 서버에 연결하도록 허용하고 싶을 수 있다. 더 많은 예는 아래 § 1.3 예제를 참조하라.
1.2. 비목표
이 명세는 로컬 네트워크 기기에서 HTTPS 연결을 더 쉽게 사용할 수 있게 하려고 시도하지 않는다. 이것은 유용한 목표일 수 있지만, 이 문제를 해결하는 것은 이 명세의 범위를 벗어난다
1.3. 예제
1.3.1. 사용자의 권한 허용
Alice는 문제 진단을 돕기 위해 Acme Printing Company의 웹사이트로 이동한다. Acme Printing Company의 웹사이트는 프린터에 연결하여 프린터의 진단 출력을 살펴볼 수 있다고 Alice에게 알려준다. Alice의 브라우저는 Alice에게 https://support.acmeprintingcompany.com이 그녀의 네트워크에 있는 로컬 기기에 연결하도록 허용할지 묻는다. Alice는 https://support.acmeprintingcompany.com이 그녀의 네트워크에 있는 로컬 기기에 연결하는 것을 허용하고, https://support.acmeprintingcompany.com은 그녀의 로컬 프린터의 진단 출력에 연결하여, 프린터의 부품 하나가 오작동하고 있어 교체해야 한다고 Alice에게 알려준다.
1.3.2. 사용자의 권한 거부
1.3.3. 새 기기 구성
1.3.4. 앱 기반 로그인
2. 프레임워크
2.1. IP 주소 공간
IPAddressSpace를
다음과 같이 정의한다:
enum {IPAddressSpace ,"public" ,"local" };"loopback"
모든 IP 주소는 세 가지 값 중 하나일 수 있는 IP 주소 공간에 속한다:
-
loopback: 로컬 호스트에서만 접근할 수 있는 루프백 주소를 포함한다. 즉, 대상이 모든 기기마다 다른 주소이다.
-
local: 현재 네트워크 내에서만 의미가 있는 주소를 포함한다. 즉, 네트워크 위치에 따라 대상이 달라지는 주소이다.
-
public: 그 밖의 모든 주소를 포함한다. 즉, IP 네트워크에서 전 세계 모든 기기에 대해 대상이 동일한 주소이다.
편의를 위해 다음 용어도 정의한다:
IP 주소 공간 lhs는 다음 조건 중 하나가 참이면 IP 주소 공간 rhs보다 덜 공용이다:
IP 주소 address의 IP 주소 공간을 결정하려면 다음 단계를 실행한다:
-
address가
::ffff:0:0/96"IPv4-mapped Address" 주소 블록에 속하면, address를 그 안에 포함된 IPv4 주소로 바꾼다. -
비공용 IP 주소 블록 표의 각 row에 대해:
-
address가 row의 주소 블록에 속하면, row의 주소 공간을 반환한다.
-
-
public을 반환한다.
| 주소 블록 | 이름 | 참조 | 주소 공간 |
|---|---|---|---|
127.0.0.0/8
| IPv4 루프백 | [RFC1122] | loopback |
10.0.0.0/8
| 사설 사용 | [RFC1918] | local |
100.64.0.0/10
| Carrier-Grade NAT | [RFC6598] | local |
172.16.0.0/12
| 사설 사용 | [RFC1918] | local |
192.168.0.0/16
| 사설 사용 | [RFC1918] | local |
198.18.0.0/15
| 벤치마킹 | [RFC2544] | loopback |
169.254.0.0/16
| 링크 로컬 | [RFC3927] | local |
::1/128
| IPv6 루프백 | [RFC4291] | loopback |
fc00::/7
| 고유 로컬 | [RFC4193] | local |
fe80::/10
| 링크 로컬 유니캐스트 | [RFC4291] | local |
fec0::/10
| 사이트 로컬 유니캐스트 | [RFC3513] | local |
0.0.0.0/32
| IPv4 null IP 주소 | [RFC1884] | loopback |
0.0.0.0/8
| IPv4 null IP 주소들 | [RFC1884] | local |
::/128
| IPv6 지정되지 않은 주소 | [RFC1884] | loopback |
2001:db8::/32
| IPv6 문서화 주소 | [RFC3849] | local |
3fff::/20
| IPv6 문서화 주소 | [RFC9637] | local |
::ffff:0:0/96
| IPv4-mapped | [RFC4291] | 매핑된 IPv4 주소 참조 |
사용자 에이전트는 특정 IP 주소 블록의 주소 공간이 관리자 또는 사용자 구성에 의해 재정의되도록 허용할 수 있다. 이는 예를 들어 위 알고리즘에 따르면 대부분의 IP 주소가 public으로 간주되는 IPv6 인트라넷을 보호하는 데 유용할 수 있으며, 대신 사용자 에이전트가 해당 인트라넷을 local로 취급하도록 구성할 수 있다.
Note: 169.254.0.0/16과 같은
링크 로컬 IP 주소는 local로
간주된다. 이러한 주소는 네트워크 링크의 모든 기기에 대해 같은
대상을 식별할 수 있기 때문이다. 이 명세의 이전 버전에서는
이를 대신 loopback으로 간주했다.
Note: 각 IP 주소 공간의 내용은 한때
IANA Special-Purpose Address Registries
([IPV4-REGISTRY] 및 [IPV6-REGISTRY])와
그 안에서 정의된 Globally Reachable 비트에
따라 결정되었다. 이는 우리의 용도에는 부정확한 신호로 드러났으며,
wicg/private-network-access
issue #50에 설명되어 있다.
Note: [PRIVATE-NETWORK-ACCESS]는 주소 공간 public, private, local을 사용했다. 이 명세는 해당 주소 공간의 이름을 각각 public, local, loopback으로 바꾼다.
Note: [FETCH]는 null IP 주소에 대한 요청을
네트워크 오류로 취급하도록 명시할 예정이다. 우리는 IPv4 null IP 주소 공간과
IPv6 지정되지 않은 주소를 모두 loopback으로 매핑한다. 일부 시스템에서는
여전히 이를 로컬 호스트 머신으로 라우팅할 수 있기 때문이다(그리고 0.0.0.0/8은
"이 네트워크의 지정된 호스트"를 가리킬 수 있으므로 local로 매핑된다).
이는 [RFC5735]에 따른다. Fetch의 변경이 이루어지고 채택되면,
우리는 더 이상 이 명세에서 이를 처리할 필요가 없게 된다.
2.2. 로컬 네트워크 요청
요청 (request)은 request의
현재 url의 host가
IP 주소로 매핑되고, 그 IP 주소 공간이
request의 policy container의
IP 주소 공간보다
덜
공용이면
로컬 네트워크 요청이다.
IP 주소를 두 개의 넓은 주소 공간으로 분류하는 것은 불완전하고 이론적으로 견고하지 않은 접근이다. 이는 두 네트워크 엔드포인트가 자유롭게 통신해도 되는지, 즉 엔드포인트 A가 엔드포인트 C의 사용자 에이전트를 통해 우회하지 않고 엔드포인트 B에서 도달 가능한지를 판단하는 데 사용되는 프록시이다.
이 접근에는 몇 가지 결함이 있다:
-
false positives: 공용 주소를 가진 인트라넷 서버가 같은 인트라넷의 로컬 주소를 가진 다른 서버로 직접 요청을 발행하지 못할 수 있다.
-
false negatives: 홈 네트워크와 VPN처럼 서로 다른 두 local 네트워크에 연결된 클라이언트가, VPN에서 제공되는 웹사이트가 홈 네트워크의 기기에 접근하도록 허용할 수 있다. 아래 이슈도 참조하라.
그럼에도 이 명세는 네트워크 구성이 그렇게 복잡하지 않은 대부분의 웹 사용자에게 광범위하게 영향을 미치는 보안 문제에 대해 실용적인 해결책을 제공하는 것을 목표로 한다.
루프백 주소에서 발생한 요청은 로컬 네트워크 요청으로 간주해서는 안 되며, 로컬 네트워크 접근 검사 대상이 되어서는 안 된다. 사용자의 기기에서 실행되는 모든 소프트웨어는 이미 사용자의 네트워크에서 가장 특권적인 관점에 있기 때문이다.
로컬 네트워크
요청의 정의는
현재 url의 host가
IP 주소로 매핑되고, 그 IP 주소 공간이
public이 아닌
모든
교차 출처 요청을 포괄하도록 확장될 수 있다. 이는 로컬 네트워크의
악의적 서버가 localhost의 서버를 포함한 다른 서버를 공격하는 것을
방지할 것이다. wicg/private-network-access
issue
#39를 참조하라.
NOTE: 현재 Chromium은 public에서 local 또는 loopback으로 향하는 요청에 대해서만 Local Network Access 제한을 구현하며, 교차 출처 local 요청에는 권한을 강제하지 않는다. 이 제한은 향후 구현에서 점진적 개선으로 출시될 수 있다.
NOTE: 로컬 이름과 주소는 네트워크의 범위 밖에서는 의미가 없기 때문에, 구현자는 교차 출처 local 사례에 대해 public에서 local로 향하는 사례와는 다른 권한 프롬프트를 사용하고자 할 수 있다. 또한 구현자는 이러한 권한 부여의 범위를 특정 네트워크 또는 현재 탐색 세션으로만 제한하고자 할 수 있다.
NOTE: 일부 로컬 네트워크 요청은 다른 것보다 보호하기 더 어렵다. 자세한 내용은 § 4.4 출시 어려움을 참조하라.
2.3. 로컬 네트워크 요청 권한 프롬프트
public 웹사이트에서 로컬 네트워크 서버로 보내는 로컬 네트워크 요청을 사용자가 승인할 수 있도록 로컬 네트워크 접근 권한 프롬프트가 도입된다.
로컬 네트워크 요청이 감지되면, 로컬 네트워크에 접근할 권한을 요청하는 프롬프트가 사용자에게 표시된다. 사용자가 권한을 부여하기로 결정하면 fetch는 계속된다. 그렇지 않으면 실패한다.
권한의 정확한 범위는 구현 정의이다. 권한은 특정 출처가 로컬 네트워크의 모든 엔드포인트에 로컬 네트워크 요청을 보낼 수 있도록 허용하는 정도로 거칠 수도 있고, 특정 출처가 로컬 네트워크의 특정 엔드포인트와만 통신하도록 허용하는 정도로 더 세분화될 수도 있다. 사용자 에이전트는 권한 피로를 줄이기 위해 이 결정을 지속할 수 있다.
2.4. 보안 컨텍스트 제한
로컬 네트워크 요청을 만들 수 있는 기능은 강력한 기능이며, 보안 컨텍스트에서만 허용되어야 한다.
향후 모든 교차 출처
local 요청에
LNA 검사를 적용할 수 있도록(위 이슈 참조),
Chromium은 현재 공개적으로 신뢰되는 HTTPS 인증서를 얻기 어려울 가능성이 큰
로컬 서버(예: .local의 서버와 사설 IP 리터럴)를 이 요구사항에서
제외할 계획이다. 더 많은 논의는 § 4.4 출시 어려움을
참조하고, 또한
wicg/private-network-access issue
#96도 참조하라.
2.5. 혼합 콘텐츠
많은 로컬 네트워크 서버는 HTTPS를 실행하지 않는다. 로컬 네트워크 서버를 HTTP에서 이전하는 것이 어렵고(때로는 불가능하기까지) 입증되었기 때문이다. 이는 보안 컨텍스트 제한이 혼합 콘텐츠 검사와 결합되어, 사용자가 요청이 발생하도록 권한을 부여하더라도 많은 로컬 네트워크 요청을 차단한다는 점에서 문제가 된다.
이 문제의 한 가지 해결책은 요청이 로컬 네트워크 요청임을 알고 있는 상황에서 혼합 콘텐츠 검사를 우회하는 것이다. 이는 몇 가지 상황에서 알려져 있다:
-
요청 대상의 호스트 이름이 비공용 IP 주소 블록" 표에서 local로 식별되는 IP 리터럴인 경우(예: [RFC1918] IP 리터럴)
-
요청의 호스트 이름이 .local 도메인인 경우(RFC 6762)
위 두 상황 중 어느 것도 참이 아니지만, 사이트가 요청을
로컬 네트워크 요청으로
식별하고자 하는 상황이
있을 수 있다. 이는 fetch() 옵션 bag에 새 매개변수를 추가하여 완화할 수 있다:
fetch( "http://router.local/ping" , { targetAddressSpace: "local" , });
이는 브라우저에 scheme이 안전하지 않더라도 fetch가 혼합 콘텐츠 검사를
우회하고 대상 서버에 대한 연결을 잠재적으로 얻도록 허용하라고 지시한다.
새로운 fetch() API는 하위 호환된다.
이 기능은 일반적인 혼합 콘텐츠를 우회하는 데 악용될 수 없다는 점에 유의하라. 해석된 원격 IP 주소가 targetAddressSpace 옵션 값으로 지정된 IP 주소 공간에 속하지 않으면 요청은 실패한다. 속한다면, 권한을 검사하여 요청을 허용하거나 실패시킬 수 있다.
Chromium은 현재
비표준 CSP 지시어 treat-as-public-address를 지원한다
(private-network-access#csp 참조).
wicg/private-network-access issue
#39가 구현되면 이 지시어는 불필요해질 것이다.
2.6. 권한
이 문서는 다음 기본 강력한 기능을 정의한다. 이들은 정책 제어 기능이며 기본 allowlist는 'self'이다:
-
"local-network"는 로컬 네트워크 요청을 로컬 주소로 보낼 수 있는 능력을 제어한다. -
"loopback-network"는 로컬 네트워크 요청을 루프백 주소로 만들 수 있는 능력을 제어한다.
NOTE: 이전에는 이것이 local과 loopback 사례를
모두 포괄하는 단일 기본 강력한 기능,
"local-network-access"로 명시되었다.
Chromium은 여전히 이를 새로운 세분화된 권한 이름의 별칭으로, 그리고
정책 제어 기능 이름으로 사용할 수 있도록 지원한다.
2.7. treat-as-public-address 콘텐츠 보안 정책 지시문
treat-as-public-address 지시문은 사용자 에이전트에게 문서를
실제로는 로컬 주소 또는 루프백 주소에서 제공되었더라도
공개 주소에서 제공된 것처럼 취급하라고 지시한다.
즉, 이는 비공개 문서가 프리플라이트 없이 다른 비공개 문서에
접촉할 수 있는 권한을 내려놓을 수 있게 하는 메커니즘이다.
이 지시문의 구문은 다음 ABNF 문법으로 설명된다:
directive-name = "treat-as-public-address" directive-value = ""
이 지시문에는 보고 요구사항이 없으며, Content-Security-Policy-Report-Only
헤더로 전달되거나 <meta> 요소 내에서 전달될 때는
완전히 무시된다.
이 지시문의 초기화 알고리즘은 다음과 같다. 주어진
환경 설정 객체(context),
Response(response) 및
policy에 대해:
https://github.com/WICG/private-network-access/issues/88 은 이를 CSP 지시문에서 다른 위치로 옮길 것을 권고했다.
3. 통합
이 절은 비규범적이다.
이 문서는 위에서 설명한 보호를 구현하기 위해 다른 명세에 대한 여러 수정을 제안한다. 이러한 통합은 명확성을 위해 여기에 개요로 제시되지만, 외부 문서가 규범적 참조이다.
3.1. Fetch와의 통합
이 문서는 [FETCH]에 몇 가지 변경을 제안하며, 그 의미는 다음과 같다: 로컬 네트워크 요청은 그 client가 보안 컨텍스트이고 또한 사용자가 권한을 부여한 경우에만 허용된다. 요청이 혼합 콘텐츠로 차단되었을 경우에도, 웹사이트가 로컬 네트워크에 접근하려는 의도를 명시하고 사용자가 권한을 부여하면 허용될 수 있다.
Note: 여기에는 탐색도 포함된다. 탐색은 하위 리소스 요청보다 덜 은밀하지만 CSRF 공격을 트리거하는 데 실제로 사용될 수 있다.
Chromium은 현재 LNA 제한을 iframe 탐색에만 적용한다. 이를 메인 프레임 탐색(특히 opener에 의해 제어될 수 있는 팝업 창)까지 확장할 가치가 있을 수 있다.
Note: [FETCH]는 아직 DNS 해석의 세부 사항을 Fetch 알고리즘에 통합하지 않았지만, 이 명세에 충분한 연결 획득 알고리즘을 정의한다. Local Network Access 검사는 새로 획득한 연결에 적용된다. Happy Eyeballs ([RFC6555], [RFC8305])와 같은 복잡성으로 인해, 여러 IP 주소가 IP 주소 공간 경계를 가로지르는 호스트에서는 이러한 검사가 비결정적으로 통과하거나 실패할 수 있다.
3.1.1. 페칭
NOTE: [FETCH]의 연결 관리는 의도적으로 다소 모호하므로, 아래 일부 내용은 구현자를 위한 note를 통해 설명된다. 이 절은 구현자가 연결을 획득한 후, 그 연결을 사용해 fetch를 계속 수행하기 전에 로컬 네트워크 접근 검사를 수행함을 제시한다.
wicg/local-network-access issue #103를 해결하려면, 대신 출처를 해석한 뒤 실제 연결을 만들기 전에 검사를 수행하도록 연결 획득의 4.6단계에 통합해야 한다.
[FETCH]를 다음과 같이 갱신한다:
-
Connection 객체에는 새 IP address 속성이 주어진다. 그 값은 null 또는 IP 주소이고, 초기값은 null이다.
-
create a connection 알고리즘에서 새 연결을 설정한 직후(2단계와 3단계 사이)에 새 단계를 추가한다.
-
host가 IP 주소이면, connection의 IP address를 host로 설정한다.
-
-
Request 객체에는 새 target IP address space 속성이 주어지며, 초기값은 null이다.
-
Response 객체에는 새 IP address 속성이 주어진다. 그 값은 null 또는 IP 주소이고, 초기값은 null이다.
-
새 Local Network Access check 알고리즘을 정의한다. request request와 IP 주소 address가 주어졌을 때:
NOTE: 이 알고리즘은 IP 주소를 받으므로 HTTP-network fetch(connection이 있는 곳) 또는 HTTP-network-or-cache fetch(저장된 response에 저장된 IP 주소가 있는 곳)에서 호출될 수 있다. 이는 매핑이 사용자 에이전트에 의해 동적으로 갱신될 수 있으므로 매번 IP 주소 공간을 의도적으로 다시 계산한다.
-
address에 대해 IP 주소 공간을 결정 알고리즘을 실행한 결과를 addressSpace라고 한다.
-
request의 origin이 잠재적으로 신뢰할 수 있는 출처이고, request의 current URL의 origin이 request의 origin과 same origin이면, null을 반환한다.
-
request의 policy container가 null이면, null을 반환한다.
NOTE: request의 policy container가 null이면, LNA 검사는 request에 적용되지 않는다. fetch 알고리즘의 사용자는 request의 client를 null이 아닌 policy container를 가진 environment settings object로 설정하고 fetch가 request의 policy container를 그에 따라 초기화하도록 하거나, 또는 request의 policy container를 null이 아닌 값으로 직접 설정하도록 주의해야 한다.
-
request의 target IP address space가 null이 아니면:
-
Assert: request의 target IP address space는 public이 아니다.
-
addressSpace가 request의 target IP address space와 같지 않으면, network error를 반환한다.
-
null을 반환한다.
-
-
addressSpace가 request의 policy container의 IP 주소 공간보다 덜 공용이면:
-
error를 network error로 둔다.
-
request의 client가 보안 컨텍스트가 아니면 (null인 경우를 포함하여), error를 반환한다.
-
error의 IP address 속성을 address로 설정한다.
-
addressSpace가 local이면 permissionName을 "local-network"로, addressSpace가 loopback이면 "loopback-network"로 둔다.
-
settingsObject를 request의 client로 둔다.
-
global을 settingsObject의 global object로 둔다.
-
document를 global의 associated Document로 둔다.
-
document가 null이면 error를 반환한다.
NOTE: Service Worker는 항상 associated Document를 가지는 것은 아니므로, 이 단계는 Service Worker에서 발생한 로컬 네트워크 요청을 실패하게 만든다. 이 명세의 향후 버전은 Worker를 처리하는 방법을 정의해야 하며, 특히 Permissions Policy가 아직 Worker에서 지원되지 않는다는 점이 중요하다. w3c/webappsec-permissions-policy#207를 참조하라.
-
document가 permissionName을 사용할 수 있도록 허용되어 있지 않으면, error를 반환한다.
-
permissionState를 permissionName과 global이 주어졌을 때 현재 권한 상태 가져오기의 결과로 둔다.
-
permissionState가 denied이면 error를 반환한다.
-
permissionState가 granted이면 null을 반환한다.
-
global에 대해 permissionName을 부여할지 여부를 사용자가 선택하도록 프롬프트한다:
-
사용자가 권한을 부여하면 null을 반환한다.
-
사용자가 권한을 거부하면 error를 반환한다.
-
-
-
null을 반환한다.
-
-
fetch 알고리즘은 request의 policy container가 설정된 직후에 2개의 새 단계를 추가하도록 수정된다:
-
request의 target IP address space가 null이면:
-
request의 URL의 host host가 IP 주소이고, host에 대해 IP 주소 공간을 결정 알고리즘을 실행한 결과가 local이면, request의 target IP address space를 local로 설정한다.
-
request의 URL의 host의 public suffix가
"local"이면, request의 target IP address space를 local로 설정한다.NOTE: 요청의 URL의 host가 “localhost” 또는 “127.0.0.1”인 경우에도 target IP address space를 local로 설정할 수 있다 ([LET-LOCALHOST-BE-LOCALHOST] 때문에). 그러나 loopback 사례는 이미 잠재적으로 신뢰할 수 있는 것으로 간주되어 혼합 콘텐츠 검사를 트리거하지 않으므로 별도의 처리가 필요하지 않다.
NOTE: 명시적 targetAddressSpace가 fetch() API에 의해 설정된 경우 이를 선호하기 위해, 이미 null이 아닌 경우 여기서 target IP address space를 설정하지 않는다.
-
-
-
HTTP-network fetch 알고리즘은 새로 획득한 connection이 failure가 아님을 검사한 직후에 3개의 새 단계를 추가하도록 수정된다:
-
response의 IP address를 connection의 IP address로 설정한다.
-
localNetworkAccessCheckResult를 fetchParams의 request와 connection의 IP address에 대해 Local Network Access check를 실행한 결과로 둔다.
-
localNetworkAccessCheckResult가 network error이면, localNetworkAccessCheckResult를 반환한다.
-
-
HTTP-network-or-cache fetch 알고리즘은 9단계 직후에 새 단계를 추가하도록 수정된다:
-
response가 null이 아니면:
-
localNetworkAccessCheckResult를 request와 response의 IP address에 대해 Local Network Access check를 실행한 결과로 둔다.
-
localNetworkAccessCheckResult가 network error이면, localNetworkAccessCheckResult를 반환한다.
-
-
NOTE: 로컬 네트워크 요청이 보안 컨텍스트에서 만들어져야 한다는 요구사항은, 요청이 로컬 네트워크 요청으로 간주될 수 있음을 사전에 알 수 없는 한, 모든 안전하지 않은 요청이 혼합 콘텐츠로 차단됨을 의미한다. target IP address space 속성(위 6i 및 6ii 단계 참조)을 설정함으로써 Mixed Content에는 작은 변경만 필요하다. § 3.2 Mixed Content와의 통합을 참조하라.
3.1.2. Fetch API
Fetch API도 조정되어야 한다.
-
RequestInfo에 선택적 entry를 추가한다. 그 key는 targetAddressSpace이고, value는IPAddressSpace이다.partial dictionary RequestInit {IPAddressSpace ; };targetAddressSpace -
위 내용을 request에서 나타내는 새 targetAddressSpace를 정의한다.
partial interface Request {readonly attribute IPAddressSpace ; };targetAddressSpace -
new Request(input, init)에는 this의 request를 request로 설정하기 직전에 다음 단계가 추가된다:-
init["
targetAddressSpace"]가 존재하면, init["targetAddressSpace"]에 대해 switch한다:- public
- 아무것도 하지 않는다.
- local
- request의 targetAddressSpace를 local로 설정한다.
-
3.2. Mixed Content와의 통합
Should fetching request be blocked as mixed content?는 allowed 조건 중 하나에 다음 조건을 추가하도록 수정된다:
-
request의 origin이 잠재적으로 신뢰할 수 있는 출처가 아니고, request의 target IP address space가 local이다.
"Upgrade request to an a priori authenticated URL as mixed content, if appropriate" 알고리즘은 1단계에서 업그레이드 예외로 다음 조건을 추가하도록 수정된다:
-
request의target IP address space가 local이다
3.3. WebSockets와의 통합
WebSockets 연결은 동일한 로컬 네트워크 접근 권한 요구사항의 대상이 되어야 한다. WebSocket opening handshake는 11단계에서 fetch를 직접 적용하므로, 위에서 제안한 fetch 변경 외에는 WebSocket 명세에 필요한 수정이 없다.
Fetch API와 WebSockets API의 작은 차이 하나는 WebSockets에는
fetch의 RequestInit에 해당하는 것이 없으므로, ws:// URL에 대해
혼합 콘텐츠 검사를 우회하는 targetAddressSpace 옵션을 넣을
곳이 없다는 것이다.
3.4. WebTransport와의 통합
WebTransport 연결은 동일한 로컬 네트워크 접근 권한 요구사항의 대상이 되어야 한다.
WebTransport 명세에는 수정이 필요하지 않다. WebTransport 명세에서 WebTransport 연결 획득은 6단계에서 연결을 획득하기 위해 Fetch 명세를 참조하며, 이는 § 3.1.1 페칭에서 수정되고 있다
3.5. HTML과의 통합
[FETCH]의 검사를 지원하기 위해, 사용자 에이전트는 네트워크 요청이 만들어지는 컨텍스트의 소스 IP 주소 공간을 기억해야 한다. 이를 위해 [HTML] 명세는 다음과 같이 패치된다:
-
새 IP 주소 공간 속성이 policy container struct에 추가된다.
-
초기값은 public이다.
-
-
clone a policy container 알고리즘에 추가 단계가 추가된다:
-
create a policy container from a fetch response 알고리즘에 추가 단계가 추가된다:
-
result의 IP 주소 공간을 response의 IP address에 대해 IP 주소 공간을 결정 알고리즘을 실행한 결과로 설정한다.
-
example.com이 공개
주소(예:
123.123.123.123)로 해석된다고 가정하면, 그러면
Document는
https://example.com/document.html로 이동할 때 생성되며, 그
정책 컨테이너의 IP 주소
공간
속성이 public으로 설정된다.
그런 다음 이 Document가
about:srcdoc iframe을 포함하면, 그러면 자식
프레임의 Document는
그 정책 컨테이너의
IP 주소 공간 속성이
public으로 설정된다.
반면에, example.com이 로컬 주소
(예: 127.0.0.1)로 해석된다면, 그러면
Document는
https://example.com/document.html로 이동할 때 생성되며, 그
정책 컨테이너의 IP 주소
공간
속성이 local로 설정된다.
TODO: 또한 HTML § 7.1.4 Cross-origin embedder policies의 Private Network Access 참조를 갱신한다.
3.6. Workers와의 통합
이 절은 비규범적이다.
WorkerGlobalScope는
이미 policy container 필드를 가지며,
이는 create a policy container from a fetch
response 알고리즘을 사용해 채워지므로, 위의 Fetch 및 HTML과의
통합은 문서 컨텍스트와 마찬가지로 worker 컨텍스트에도 적용된다.
example.com이 공개
주소(예:
123.123.123.123)로 해석된다고 가정하면, 그러면
WorkerGlobalScope는
https://example.com/worker.js에서 스크립트를 가져와 생성되며, 그
정책 컨테이너의
IP 주소 공간 속성이
public으로
설정된다.
이 워커가 시작한 모든 fetch 요청 중 연결을 얻는 대상 IP 주소가 local 또는 loopback 주소 공간에 있다면, 이후 로컬 네트워크 요청이 된다.
Chromium의 구현은 현재 service worker가 시작한 fetch에 대해 worker의 스크립트 출처를 기반으로 LNA 권한을 적용한다(service worker가 실행 중일 때 활성 document가 주변에 없을 수 있기 때문이다). 이는 worker의 partitioned storage key를 기반으로 하는 것이 더 나을 수 있으며, permissions policy가 service worker를 지원하면 좋을 것이다.
Service Worker soft update 알고리즘은 갱신된 스크립트를 fetching할 때 안타깝게도 request client를 "null"로 설정한다. 이는 여러 문제를 일으키며, 위에서 제시한 로컬 네트워크 접근 검사 알고리즘을 방해한다. 실제로 fetch 중에 policy container를 복사해 올 request client가 없다. wicg/private-network-access issue #83을 참조하라.
4. 구현 고려 사항
4.1. File URL
file URL이 위에서 설명한 public/local 체계에 어떻게 들어맞는지는 완전히 명확하지 않다. 한편으로는 사람들이 악의적인 HTML 파일을 로컬에서 열어 스스로를 해치지 않도록 막는 것이 좋겠지만, 다른 한편으로는 로컬에서 실행되는 코드는 어느 정도 일관된 위협 모델 밖에 있다.
현재로서는 file URL을 local로 취급하는 쪽으로 보수적으로 판단하자. 이들은 루프백 주소의 어떤 것만큼이나 로컬 시스템의 일부로 보이기 때문이다.
4.2. 프록시
Chromium의 이 명세 현재 구현에서 프록시는 프록시하는 리소스의 주소 공간에 영향을 준다. 구체적으로, 프록시를 통해 fetch된 리소스는 프록시 자체의 IP 주소에서 fetch된 것으로 간주된다.
public 주소의 foo.example이 제공하는 Document가 로컬 주소의 프록시를 통해 사용자 에이전트에 의해 fetch되면, 그 Document의 policy container의 IP 주소 공간은 local로 설정된다.
그 Document는 이어서 브라우저가 접근 가능한 다른 로컬 주소로 요청을 만들 수 있게 된다.
이는 웹사이트가 로컬 주소로 요청을 만들 수 있음을 관찰하여 자신이 프록시되었음을 알 수 있게 하며, 이는 개인정보 정보 유출이다. 이는 로컬 네트워크에 있는 리소스의 URL을 정확히 추측해야 하지만, 한 번의 올바른 추측만으로 충분하다.
이는 비교적 드물 것으로 예상되며 더 많은 완화를 정당화하지 않는다. 결국 현재 상태에서는 모든 웹사이트가 아무 제한 없이 모든 IP 주소로 요청을 만들 수 있다.
프록시가 브라우저에 "이 리소스를 어쨌든 public/local로 취급해 달라"고 말할 수 있는 메커니즘을 살펴보는 것은 흥미로울 것이다. 이를 통해 프록시 뒤의 IP 주소에 대한 일부 정보를 전달할 수 있다.
4.3. HTTP 캐시
HTTP 캐시의 응답은 위 § 3.1 Fetch와의 통합에 명시된 대로 Local Network Access 검사의 대상이다. 아래에는 캐시에서 어떤 종류의 리소스가 로드되는지에 따라 이러한 검사가 실제로 어떻게 작동하는지에 대한 몇 가지 추가 세부 사항(및 예제)이 있다.
4.3.1. 메인 리소스
캐시된 response에서 구성된 document는 해당 response가 처음 로드된 IP 주소를 기억한다. document의 IP 주소 공간은 IP 주소에서 새로 파생된다.
일반적인 경우, 이는 document의 policy container의 IP 주소 공간이 변경 없이 복원됨을 의미한다. 그러나 사용자 에이전트의 구성이 변경된 경우, 파생된 IP 주소 공간은 달라질 수 있다.
그 후 사용자 에이전트가 다시 시작되고, 1.2.3.4를 local 주소로 분류해야 한다고 지정하는 새 구성이 적용된다.
사용자 에이전트는 다시 http://foo.example/로 탐색하여 HTTP 캐시에서 메인 리소스를 로드한다. 결과 document의 policy container의 IP 주소 공간은 이제 local로 설정된다.
4.3.2. 하위 리소스
메인 리소스와 마찬가지로, 캐시된 응답에서 구성된 하위 리소스는 응답이 처음 로드된 IP 주소를 기억한다. 응답의 IP 주소 공간은 IP 주소에서 새로 파생된다.
일반적인 경우, 이는 응답의 IP 주소 공간이 변경 없이 복원됨을 의미한다. 그러나 사용자 에이전트의 구성이 변경된 경우, 파생된 IP 주소 공간은 달라질 수 있다.
하위 리소스 요청이 LNA 검사로 차단되면(즉, 권한이 거부되면), 캐시되는 리소스 응답은 없다. 나중에 권한이 재설정되거나 부여되면, 하위 리소스 요청은 네트워크로 간다.
사용자 에이전트는 https://foo.example로 탐색하며, 이는 1.2.3.4에서
로드된다(그 IP 주소 공간은 public). 문서는
http://bar.local/image.jpg에 대한 하위 리소스 요청을 트리거하고, 연결이
생성될 때 그 IP 주소는 10.0.1.1이다(그 IP 주소 공간은
local). 이는 권한 프롬프트를 트리거하여 https://foo.example에
Local Network Access 권한을 부여하고, 그 후 하위 리소스가 로드되어
사용자 에이전트의 캐시에 추가된다.
사용자 에이전트는 https://foo.example에 대한 권한을 재설정한다.
사용자 에이전트는 다시 https://foo.example로 탐색하고, 이는 다시 http://bar.local/image.jpeg에 대한 하위 리소스 요청을 트리거한다. 이 리소스는 사용자 에이전트의 캐시에 있으며, 캐시된 응답 IP 주소는 10.0.1.1이다. 이는 다시 권한 프롬프트를 트리거하고, 부여되면 사용자 에이전트의 캐시에서 리소스 로드를 완료한다.
사용자 에이전트는 https://foo.example로 탐색하며, 이는 1.2.3.4에서
로드된다(그 IP 주소 공간은 public). 문서는
http://bar.local/image.jpg에 대한 하위 리소스 요청을 트리거하고, 연결이
생성될 때 그 IP 주소는 10.0.1.1이다(그 IP 주소 공간은
local). 이는 권한 프롬프트를 트리거하여 https://foo.example에
Local Network Access 권한을 거부한다. 하위 리소스 요청은 차단되고
사용자 에이전트의 캐시에 리소스가 추가되지 않는다.
사용자 에이전트는 https://foo.example에 대한 권한을 재설정한다.
사용자 에이전트는 다시 https://foo.example로 탐색하고, 이는 다시 http://bar.local/image.jpeg에 대한 하위 리소스 요청을 트리거한다. 이 리소스는 사용자 에이전트의 캐시에 없으므로, HTTP network fetch를 거치고 권한 프롬프트를 트리거한다.
보안 영향에 대한 논의는 § 5.6 HTTP 캐시를 참조하라.
4.4. 출시 어려움
Local Network Access는 본질적으로 로컬 네트워크에 대한 직접 접근을 더 안전한 사용자 에이전트 매개 대안으로 대체하기 위해 폐기한다. 웹 폐기는 어렵다. Chromium은 이전에 이 명세의 전신인 [PRIVATE-NETWORK-ACCESS]의 일부를 출시하는 과정에서 많은 걸림돌을 만났다.
특히 로컬 IP 주소 공간의 비보안 컨텍스트에서
localhost의 서비스로 향하는 fetch에 제한을 적용하는 것은 보상이
더 낮은 데 비해 특히 어려운 것으로 드러났다. 실제로 이러한 fetch를
악용하려면 공격자가 이미 사설 네트워크에 발판을 가지고 있어야 하며,
이는 공격 난이도를 상당히 높인다. 그 결과 Chromium은 이러한 fetch를
일시적으로 제한에서 제외하고, public IP 주소 공간에서의 fetch에 집중하기로
선택했다. 보안 영향에 대한 논의는
§ 5.5 로컬 네트워크 공격자를 참조하라.
5. 보안 및 개인정보 보호 고려 사항
5.1. 사용자 중재
이 문서의 제안은 사용자가 public 인터넷으로부터의 접근에 동의함을 보장할 뿐이다. 이 제안은 기기가 public 인터넷으로부터의 연결을 명시적으로 승인할 수 있도록 허용하지 않는다.
기기가 public 인터넷으로부터의 연결을 명시적으로 승인해야 하는 대안 모델은 [PRIVATE-NETWORK-ACCESS]에서 시도되었지만, 출시 어려움에 부딪혔다.
대상 엔드포인트가 연결에 opt in하는지 여부와 관계없이 사용자 중재를 요구하는 것은, 로컬 엔드포인트와 원격 웹사이트가 결탁하여 사용자 신원을 연결하는 "Local Mess" 추적 방법과 같은 개인정보 문제를 완화하는 데도 도움이 된다.
5.2. DNS 리바인딩
여기서 설명하는 완화는 사용자 에이전트가 특정 리소스를 로드할 때 실제로 연결하는 IP 주소에 대해 작동한다. 이 검사는 만들어지는 각 새 연결마다 수행되어야 한다. 그렇지 않으면 DNS 리바인딩 공격이 사용자 에이전트를 속여 드러내서는 안 되는 정보를 드러내게 할 수 있다.
DNS 리바인딩 공격은 또한 로컬 네트워크 접근 검사가 모든
public에서 local로 향하는 요청에 적용되어야 함을 의미한다
(즉, 알고리즘을 “local 엔드포인트에 대한 모든 교차 출처 요청”으로
단순화할 수 없다. 이는 사용자가 a.example로 탐색한 뒤 네트워크가
바뀌거나 DNS가 변경되어 a.example이 로컬 주소를 가리키게 만드는
위험을 열기 때문이다).
5.3. 완화 범위
이 문서의 제안은 로컬 웹 서비스에 대한 공격을 완화할 뿐이며, 이를 완전히 해결할 수는 없다. 예를 들어, 라우터의 웹 기반 관리 인터페이스는 자체적으로 CSRF를 방어하도록 설계되고 구현되어야 하며, 이 문서에 명시된 대로 동작하는 UA에 의존해서는 안 된다. 이 문서가 명시하는 완화는 오늘날 로컬 웹 서비스 구현 품질의 현실을 고려할 때 필요하지만, 모든 UA가 이 완화를 구현하더라도 벤더는 자신의 책임이 면제되었다고 생각해서는 안 된다.
5.4. 교차 네트워크 혼동
대부분의 로컬 네트워크는 서로 통신할 수 없지만, 이 명세는 그것들을 모두 local IP 주소 공간에 속하는 것으로 취급한다. 더 나아가, 로컬 주소는 사용되는 로컬 네트워크에서만 의미가 있다. 같은 IP 주소가 서로 다른 두 네트워크에서 완전히 다른 기기를 가리킬 수 있다. 사용자가 a.example에 로컬 네트워크 요청을 만들 권한을 부여한 뒤 다른 네트워크로 이동하면, a.example은 계속 로컬 네트워크 요청을 만들 수 있게 된다.
이는 교차 네트워크 공격의 문을 연다:
-
사용자가 홈 Wi-Fi 네트워크와 회사 VPN이라는 서로 다른 두 로컬 네트워크에 연결한다. 사용자의 스마트 냉장고가 해킹되었다. 사용자가 스마트 냉장고의 웹 인터페이스를 열면, 이 인터페이스는 VPN을 통해 접근 가능한 회사 웹사이트에 대해 CSRF 공격을 수행한다.
-
사용자가 악의적인 인터넷 카페 Wi-Fi에 연결하며, 이 Wi-Fi는 사용자가 captive portal 페이지를 계속 열어 두도록 요구한다. 사용자가 노트북을 닫고 집에 가서 다시 노트북을 연다. captive portal 페이지(여전히 열려 있거나 사용자 에이전트가 이전 상태를 복원하면서 캐시에서 다시 로드됨)는
-
사용자의 홈 기기에 대해 CSRF 공격을 수행한다.
-
사용자가 악의적인 인터넷 카페 Wi-Fi에 연결한다. 이 Wi-Fi의 captive portal 웹사이트는 http://router.example/popular-library.js의 악의적인 스크립트를 매우 긴 만료 시간으로 캐시한다(카페 네트워크 관리자가 악의적인 DNS 서버를 운영한다). 사용자가 컴퓨터를 끄고 집에 가서 다시 컴퓨터를 부팅한 뒤 http://router.example의 라우터 관리 인터페이스를 방문하면, 이 인터페이스는 /popular-library.js를 포함한다. 악의적인 스크립트는 관리 인터페이스의 first-party 컨텍스트에서 로드된다.
이 공격들은 새로운 것이 아니며, 단지 이 명세의 한계를 보여주는 예일 뿐이다.
잠재적 완화는 네트워크 변경을 감지하고 이전 네트워크에 특정된 상태를 지우는 것을 요구할 것이다. 이를 완전히 일반적인 방식으로 수행하는 것은 모든 상태를 지우는 것 외에는 불가능할 가능성이 높다. 실용적인 절충점을 찾을 수 있을지도 모른다. wicg/private-network-access issue #28을 참조하라.
대안적 접근은 서로 다른 네트워크를 구별하는 식별자에 권한 부여의 범위를 한정하는 것일 수 있다.
5.5. 로컬 네트워크 공격자
로컬 네트워크 접근 검사가 local 주소 공간으로 향하는 모든 교차 출처
요청에 적용될 때까지는, 로컬 네트워크의 악의적 서버가 (1) 로컬 네트워크의
다른 서버를 공격하고, (2) 사용자의 머신에서 localhost로 실행되는
서비스를 공격할 수 있다. (1)은 사용자의 브라우저를 악용하지 않고도 이미
가능하지만, (2)는 여전히 우려 사항이다. 예를 들어, captive portal은
사용자를 악의적인 페이지로 리디렉션하여, 이 페이지가 사용자의 머신에서
실행 중인 취약한 localhost 서비스를 탐색하고 공격하려고 시도하게
할 수 있다. (wicg/private-network-accesss
issue
#39도 참조하라.)
우리는 drive-by 웹의 public 웹사이트를 더 시급한 보안(및 개인정보) 위험으로 보고, 로컬 네트워크 공격자로부터 남아 있는 위험에도 불구하고 이러한 보호의 점진적 출시가 가치 있는 진전이라고 본다.
5.6. HTTP 캐시
5.6.1. 하위 리소스에 검사 적용
캐시된 하위 리소스는 이 명세에 의해 보호된다. HTTP 캐시가 소스 IP 주소를 기억하고, 이를 HTTP-network-or-cache fetch 중 Local Network Access check 알고리즘에서 사용할 수 있기 때문이다.
이 검사가 없으면, 악의적인 public 웹사이트가 사용자가 과거에 특정한 private 웹사이트를 방문했는지 판단할 수 있을 것이다.
HTTP 캐시 파티셔닝 때문에, 하위 리소스는 공격자가 캐시 항목의 network partition key를 복제하는 데 성공한 경우에만 캐시에서 로드될 수 있다. 공격자가 이를 달성할 수 있는 한 가지 방법은 DNS를 조작하여(처음 캐시된 리소스를 포함했던 최상위 사이트를 가장하기 위해 § 5.2 DNS 리바인딩도 참조) 캐시 항목의 network partition key를 복제하는 것이다.
사용자 에이전트는 http://router.example로 탐색하며, 이는 192.168.1.1에서 제공된다. 웹사이트는 http://router.example/$BRAND-logo.png의 로고를 포함하고, 이는 캐시된다.
그 후 악의적인 공격자가 router.example을 공격자가 제어하는 public IP 주소로 리바인딩하고, 어떻게든 사용자가 다시 http://router.example을 방문하도록 속인다. 악의적인 웹사이트는 로고를 포함하려고 시도하고, 로드가 성공하는지 감시한다. 성공하면 공격자는 사용자의 라우터 브랜드를 알아낸 것이다.
5.6.2. HTTP 캐시 포이즈닝
이 명세는 로컬 네트워크 서버가 public 웹사이트로부터 요청을 받는 것을 보호하는 것을 목표로 하지만, DNS 리바인딩은 인증되지 않은 리소스의 캐시 포이즈닝을 통해 유사한 공격을 수행하는 데 사용될 수 있다.
http://router.com으로 가장한 공격자는 악의적인 스크립트를 http://router.com/totally-legit.js에 캐시할 수 있다. 나중에 사용자가 http://router.com/으로 탐색하면, 페이지는 포이즈닝된 스크립트를 요청하여 공격자 코드를 덜 공용 IP 주소 공간에서 실행할 수 있다.
이 공격은 캐시 파티셔닝으로 부분적으로 완화된다. 캐시 파티셔닝은 공격자가 리소스를 캐싱하기 전에 최상위 탐색 컨텍스트를 http://router.com/으로 탐색해야 하게 만들며, 이는 은밀하지 않다. 또한 이는 Local Network Access에 특화된 것이 아니라, 평문 HTTP의 인증 및 무결성 보호 부족의 증상이다.
6. 감사의 말
원래의 Private Network Access 제안 및 명세 작업을 수행했으며, 자신의 작업을 아낌없이 논의하고 앞으로의 경로를 브레인스토밍하는 데 도움을 준 Titouan Rigoudy, Jonathan Hao, Yifan Luo의 귀중한 피드백과 조언에 깊이 감사한다.