C.W.K.
Stream
Lesson 02 of 04 · published

socket 이 local 을 증명하지, header 가 아니라

~14 min · firelink, loopback, socket, dns-rebinding

Level 0식은 재
0 XP0/32 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"누구나 localhost 라 주장할 수 있어. 운영체제만 그걸 증명할 수 있고."

PIN 이 건너뛰어지는 유일한 곳

Firelink 는 정확히 하나의 no-PIN 경로를 줘. 진짜로 같은 머신에 있는 브라우저. 사무실 박스에 앉아, loopback URL 을 열면, family PIN 을 안 넣고 durable local write 세션을 얻어 — 명백히 local 이니까. 문제 전부가 '명백히'를 어떻게 증명하느냐야. 그걸 틀리면 no-PIN 경로가 원격 우회가 되고; 맞추면 물리적으로 키보드 앞에 있는 한 사람을 위한 안전한 편의야.

주장이 아니라 socket 을 믿어

증거는 연결이 실제로 도착한 socket 주소 — loopback socket(127.0.0.0/8 또는 IPv6 ::1 peer)으로 loopback host 에게. socket 은 연결이 이뤄질 때 운영체제가 설정해; 클라이언트가 위조 못 해. 클라이언트가 위조할 수 있는 건 Host header 야 — 'localhost' 포함 원하는 아무 hostname 이나 보낼 수 있어. 그래서 Firelink 는 local 출처 확립에 요청된 host 를 절대 안 믿어. 요청된 host 는 결정을 좁힐 수만 있고(loopback socket 은 loopback host 와도 얘기해야 함), 원격 socket 을 local 로 절대 못 바꿔. 신뢰는 OS-확립 socket 에서 흐르고; 클라이언트-제공 header 는 뺄 수만 있지 더할 순 없어.

왜 '좁히되 넓히지 않음'이 트릭 전부인가

이 비대칭이 안전 속성이야. 원격 공격자는 Host: localhost 로 요청을 온종일 보낼 수 있어 — 그리고 절대 local 로 취급 안 돼, socket 이 원격이고 header 가 그걸 못 넓히니까. 한편 진짜 local 브라우저는 자기 loopback socket 으로 확인돼. 규칙의 방향이 중요해. 클라이언트가 통제하는 정보(header)는 신뢰를 더 작게 만들도록만 허용되고; 클라이언트가 통제 못 하는 정보(socket)가 그걸 부여하도록 허용돼. 클라이언트-제공 값이 신뢰를 늘릴 수 있는 어떤 auth 규칙도 안에 우회가 기다리는 규칙이야.

신뢰가 클라이언트가 위조 못 하는 사실에서만 흐르게 하고, 클라이언트-제공 값이 그 신뢰를 좁히되 절대 못 넓히게 해. socket 은 OS 의 말이고; Host header 는 클라이언트의 말이야. local 출처를 socket 에서 확립하고, header 가 제약을 더하게 허용하면, 원격 클라이언트가 말로 local 특권에 절대 못 들어가.

Code

socket 이 부여; header 는 좁히기만·python
def is_local_session(conn) -> bool:
    # The SOCKET peer is set by the OS at connect time. Unforgeable.
    socket_is_loopback = conn.peer_ip in LOOPBACK_RANGES   # 127.0.0.0/8, ::1

    # The Host HEADER is chosen by the client. Forgeable. It may only NARROW.
    host_is_loopback = conn.requested_host in LOOPBACK_HOSTS  # localhost, 127.*, ::1

    # Local origin requires BOTH: a loopback socket AND a loopback host.
    return socket_is_loopback and host_is_loopback

# Remote attacker sends 'Host: localhost' over a remote socket:
#   socket_is_loopback = False  ->  is_local_session = False.  No bypass.
# The header narrowed nothing into existence; the socket had the final say.
#
# LAN / tailnet clients (remote socket) fall through to the PIN flow.
# There is no header, cookie, or claim that upgrades a remote socket to local.

External links

Exercise

네가 아는 시스템에서 신뢰나 정체성을 정하려고 클라이언트-제공 값(header, query param, request body 필드)을 읽는 authorization 결정을 찾아. 물어봐. 클라이언트가 그 값을 자기 특권을 늘리려 설정할 수 있어? 그렇다면 클라이언트에서-넓히기 버그를 가진 거야. 신뢰가 클라이언트가 위조 못 하는 것(검증된 세션, socket, 서명된 claim)에서 오고 클라이언트 값은 제약만 더할 수 있게 재설계해.
Hint
신호는 '요청이 X 라 하면, 더 부여'류야. 뒤집어. 서버-확립 사실 기반으로 부여하고, 요청은 그보다 적게만 요청하게 둬. 호출자가 통제하는 값이 문을 여는 그것이 절대 되면 안 돼.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.