diff options
| author | eunhhu <[email protected]> | 2026-09-29 12:07:02 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-09-29 12:07:02 +0100 |
| commit | f5ce5e2b6ff0f72cc506e464d0ebfad00bed3532 (patch) | |
| tree | 0334d86afde150fe889973e2d5a427cef155f6fc /docs | |
| parent | 319c2e61fabc89e2e64add218be448a9c9b6b776 (diff) | |
| download | pure-f5ce5e2b6ff0f72cc506e464d0ebfad00bed3532.tar.gz pure-f5ce5e2b6ff0f72cc506e464d0ebfad00bed3532.zip | |
Say what an attacker on the path could do to a link, and what now stops it (098)
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/design/011-pure-transport.md | 5 | ||||
| -rw-r--r-- | docs/research/098-link-security.md | 106 |
2 files changed, 111 insertions, 0 deletions
diff --git a/docs/design/011-pure-transport.md b/docs/design/011-pure-transport.md index be25a86..c4df992 100644 --- a/docs/design/011-pure-transport.md +++ b/docs/design/011-pure-transport.md @@ -27,6 +27,11 @@ M6는 [097](../research/097-lossy-paths.md)에 있다. 처음에는 패킷 순� 아래 quinn의 21–41배, 무작위 손실 아래 같은 처리량, 같거나 짧은 꼬리 지연이다. TCP CUBIC 옆에서 대역의 0.48–0.55를 쓴다. 가드레일(±25 %) 안이다. +보안 검사는 [098](../research/098-link-security.md)에 있다. 경로 위의 공격자가 할 수 있는 다섯 가지(변조, 재전송, 위장, 폭주, +그리고 모든 검사에서의 논스 재사용)를 결정적 검사로 만들었더니 구멍 여섯이 나왔다. 가장 무거운 것은 쉬던 보내는 이가 돌아올 때의 +논스 재사용이었다. 모두 고쳐 통과한다. 새 주소는 CHALLENGE와 RESPONSE로 확인하고, 그 전에는 받은 바이트의 3배까지만 답한다. +전방 비밀성(에폭 바꿈)은 아직 없다. + 2026-09-29에 링크를 `link/`로 뗐다([009](009-layout.md)). 프로토콜은 이제 소켓, 스레드, 시계가 없는 상태이고 (`link/link-protocol.h`), UDP와 스레드는 그 위의 층이다. 그래서 한 스레드가 링크 여럿을 들 수 있다. M2를 이 보내는 쪽으로 다시 돌렸더니 결과가 같았다. 다만 재시작 뒤 가장 늦은 저장이 1초였다. 받았다고 했지만 저장 소식이 없는 기록을 1초 기다리기 diff --git a/docs/research/098-link-security.md b/docs/research/098-link-security.md new file mode 100644 index 0000000..42099e4 --- /dev/null +++ b/docs/research/098-link-security.md @@ -0,0 +1,106 @@ +# 098 — 링크의 보안 검사: 경로 위의 공격자가 할 수 있는 것 + +2026-09-29. [설계 011](../design/011-pure-transport.md)의 4단계 가운데 보안 검사다. 011의 중단 기준은 이렇다. "보안 검사(증폭, +보낸 이 위장, 재전송, 변조)에 실패하면 인터넷용으로 켜지 않는다." + +착수 기준: + +- **바꿀 결정.** 링크를 인터넷 쪽에 켤 수 있는가. +- **최소 확인.** 경로 위의 공격자가 할 수 있는 일을 하나씩 검사로 만든다. + - 패킷을 바꾼다. + - 지난 패킷을 다시 보낸다. 보낸 이의 주소로도, 자기 주소로도 보낸다. + - 허용되지 않은 키나 출처로 보낸다. + - 알려진 키 id로 지어낸 패킷을 쏟는다. + - 모든 검사에서 한 키가 같은 논스로 두 번 봉인하지 않는지 본다. +- **중단 기준.** 위 가운데 하나라도 실패하면 고칠 때까지 인터넷용으로 켜지 않는다. + +## 쉬운 말로 + +- 알아낸 것: + - 코드를 공격자의 눈으로 읽고 검사를 만들었더니 구멍이 여섯 개 나왔다. + - 가장 무거운 것은 논스 재사용이었다. 쉬던 보내는 이가 돌아오면, 받는 쪽이 같은 키로 답의 번호를 0부터 다시 썼다. + AES-GCM에서는 평문이 새고 위조까지 가능해지는 결함이다. + - 다음은 지난 패킷 하나로 링크를 영원히 멈출 수 있던 것이다. + - 모두 고쳤다. 이제 다섯 가지 공격 검사를 모두 통과한다. + - 검사를 만들다 공격과 무관한 버그도 하나 찾았다. RTT 표본이 0이면 페이싱이 NaN이 되어 보내는 쪽이 영원히 멈췄다. +- 아직 모르는 것: 전방 비밀성(에폭 바꿈)은 아직 없다. 지금은 두 노드의 정적 키가 새면 녹화한 지난 기록을 풀 수 있다. +- 사용 경험의 차이: 경로 위의 누가 패킷을 바꾸거나 되풀이하거나 쏟아부어도, 기록은 한 번씩 그대로 적용되고 링크는 이어진다. + +## 한 것 + +링크의 두 쪽을 가상 시간의 네트워크에 놓는 단위 검사([test-link.c](../../link/test-link.c))에 공격자를 더했다. 모두 씨앗에서 +결정적으로 돌고, `make check`에 들어 있다. + +| 검사 | 공격자가 하는 것 | 통과 조건 | +|---|---|---| +| 논스 | (모든 검사에서) | 보낸 모든 패킷의 (키를 정하는 것, run, 번호)가 한 번씩만 나온다. 답은 받을 보내는 이의 시작마다 따로 센다 | +| 변조 | 오가는 모든 패킷을 바이트 하나 뒤집고, 자르고, 늘린 사본을 먼저 넣는다 | 어느 쪽에서도 열리지 않고, 답을 끌어내지 않는다 | +| 재전송 | 걸음의 25 %마다 지난 패킷 하나를 다시 넣는다. 받는 쪽에는 보낸 이의 주소나 공격자의 주소로 넣는다. 손실, 재정렬, 양쪽 재시작, 쉼이 함께 있다 | 모든 기록이 한 번씩 그대로 적용된다. 공격자에게 간 바이트는 받은 것의 3배 이하다 | +| 위장 | 허용되지 않은 키로 보낸다. 그 패킷에 허용된 키의 id를 단다. 허용된 키로 허용되지 않은 출처 이름을 쓴다 | 앞의 둘은 열리지 않는다. 셋 다 아무것도 전달되지 않는다 | +| 폭주 | 허용된 키의 id로, 처음 보는 run의 지어낸 패킷 1만 개를 초당 10만 개로 넣는다 | 만든 키가 300개 이하, X25519는 1번이다. 공격자에게는 0 바이트가 간다. 뒤이은 진짜 보내는 이의 기록 200건이 모두 전달된다 | + +## 찾아 고친 것 + +1. **논스 재사용.** + - 받는 쪽은 모든 답을 자기 run 하나로 봉인했다. 답의 키는 (받는 쪽 run, 보내는 쪽 run)에서 나온다. + - 쉬는 보내는 이의 상태를 비운 뒤 그 보내는 이가 같은 run으로 돌아오면, 새로 만든 칸이 답의 번호를 0부터 다시 썼다. 같은 + 키, 같은 논스다. + - 이제 칸을 만들 때마다 답용 run을 새로 뽑는다. 보내는 쪽은 답의 run이 바뀌면 키를 다시 만든다(원래 그렇게 했다). +2. **지난 패킷 하나로 링크가 멈춤.** + - 들은 번호를 아직 모르는 흐름(재시작이나 쉼 뒤)은 쥔 기록 가운데 가장 앞의 것부터 넘겨, 호스트의 답으로 들은 번호를 배운다. + - 공격자가 다시 보낸 옛 패킷에 이미 저장된 기록의 일부가 들어 있으면, 그것이 맨 앞에서 영원히 온전해지지 않았다. + - 이제 첫 온전한 기록부터 넘긴다. 호스트의 답으로 옛 조각은 버려진다. +3. **답을 빼앗음.** + - 열리기만 하면 아무리 옛 패킷이라도 답이 갈 주소를 옮겼다. + - 이제 그 run에서 가장 큰 번호의 패킷만 주소를 옮긴다. + - 새 주소에는 CHALLENGE를 보내고, 보내는 쪽이 RESPONSE로 되돌려 주면 확인된 것으로 친다. 공격자는 봉인된 답을 읽지 못하니 + 되돌려 줄 수 없다. + - 확인 전에는 그 주소로 받은 바이트의 3배까지만 답한다(RFC 9000 §8). 경로 바뀜(NAT 재바인딩, 망 전환)도 이 규칙으로 + 따라간다. +4. **열리지 않은 패킷이 답을 끌어냄.** 이미 받은 번호면 열기 전에 확인을 다시 보냈다. 번호는 평문 머리에 있으니 위조할 수 + 있다. 이제 열고 나서 본다. +5. **옛 답이 보내는 쪽을 묶음.** + - 옛 답의 크레딧 1이 보내는 쪽을 멈출 수 있었다. 옛 답의 틈 표시는 받은 것을 모두 다시 보내게 했다. + - 이제 보내는 쪽은 들은 번호와 크레딧으로 들은 가장 큰 한도까지 보낸다. 설계 011의 "최댓값으로만 받아들인다"이다. + - 틈 표시는 저장된 번호가 뒤로 가지 않은 답에서만 받는다. + - 옛 답이 가져온 옛 챌린지에 밀리지 않게, 최근 챌린지 네 개를 모두 되돌려 준다. +6. **폭주가 키를 만들게 함.** + - 처음 보는 run의 패킷마다 HKDF를 두 번 했다. + - 이제 키를 만들었는데 열리지 않으면 토큰 하나를 쓴다. 토큰은 초당 256개로 채운다. + - 정상적인 대량 깨움(M2의 기기 1만 대)은 모두 열리니 토큰을 쓰지 않는다. 폭주 중에는 이미 아는 run의 패킷만 제 속도로 받고, + 새 run은 초당 256개까지만 받는다. + - X25519는 보내는 이마다 한 번이다. 비밀을 메모에 적어 두기 때문이다(095). + +변조 검사는 처음부터 통과했다. 머리를 연관 데이터로 넣은 AES-GCM 덕분이다. 위장 검사도 통과했다. + +## 결과 + +- `make check`가 통과한다. UDP 링크 검사(재시작, 손실, 쉼, 메모)도 경로 확인이 들어간 채 통과한다. +- 전송이 기록 하나에 쓰는 CPU는 61 B와 1 KiB에서 같다. 16 KiB는 5 % 높은데, 앞선 라운드들의 흔들림(19.7–22.5 µs) 안이다. +- 검사 결과(가상 시간): + +| 경우 | 기록 | 가상 시간 | 결과 | +|---|---:|---:|---| +| 변조 | 1,500 | 3.9초 | 바뀐 패킷 11,583개, 열린 것도 답을 끌어낸 것도 0 | +| 재전송(두 씨앗) | 3,000 | 15.8·17.3초 | 다시 넣은 패킷 2,354·2,403개. 모든 기록이 한 번씩. 공격자가 보낸 715·892 KB에 공격자에게 간 답은 0 바이트 | +| 위장 | – | – | 전달 0 | +| 폭주 | 1만 패킷 | 0.1초 | 키 282개, X25519 1번, 뒤이은 200건 모두 전달 | + +## 판정 + +- 011의 보안 중단 기준(증폭, 보낸 이 위장, 재전송, 변조)은 검사한 범위에서 모두 통과한다. +- 남은 약점은 설계가 알고 있던 둘이다. + - **전방 비밀성이 없다.** 키는 두 정적 키와 run에서만 나온다. 정적 키가 새면 녹화한 트래픽을 풀 수 있다. 011은 이것을 + 주기적인 에폭 바꿈(임시 키 교환)으로 얻겠다고 했고, 아직 구현하지 않았다. 그때까지 인터넷용으로 켜기 전에 풀어야 할 첫 + 과제다. + - **KCI.** 받는 쪽의 개인 키를 가진 자는 어떤 보내는 이로도 행세할 수 있다. 정적-정적 X25519의 성질이다. Noise KK의 첫 + 메시지도 같다. 에폭 바꿈 뒤의 키에는 임시 키가 섞이므로 이 창이 첫 에폭으로 좁아진다. + +## 한계와 다음 + +- 검사는 가상 네트워크의 결정적 흉내다. 실제 공격 도구로 쏟아부은 것은 아니다. +- 폭주 중에는 새 run이 초당 256개로 묶인다. 정상 기기의 재연결이 그 사이에 늦을 수 있다. QUIC의 Retry처럼 주소를 증명하는 + 토큰은 아직 없다. +- 보내는 쪽은 옛 답을 받을 때마다 그 답의 run 키를 다시 만든다(HKDF). 옛 답을 되풀이하는 공격자는 보내는 쪽의 CPU를 조금 + 쓰게 할 수 있다. +- 다음은 011 4단계의 나머지다. 에폭 바꿈(전방 비밀성), 경로 바뀜의 실제 측정(주소가 바뀌는 동안 끊긴 시간), 저장소 옮기기(M3). |
