Workspace IndexDev Notes › A list of attacks does not transfer — the trust boundary does

#57PoC

A list of attacks does not transfer — the trust boundary does

Six cards in this catalogue already made the same move without calling it a method: ask not whether a component was authorized but what it can reach. That move is the unit worth studying, and attack lists are its output rather than its curriculum.

Not yet scoped, and the first pass produces a diagram rather than a checklist. For one system in this repo, draw what is inside the boundary and what is outside, and mark every place something crosses — a request, a script, a library, a webhook, a vendor, a human with a console. For each crossing write two things: what it is allowed to do today, and what it could do if it turned hostile this afternoon. Then, and only then, read an attack list, and use it as a coverage test on the diagram rather than as a syllabus. Every entry either lands on a crossing you drew, which tells you the control to check, or fails to land, which means it is irrelevant to this system or you missed a boundary. Both outcomes are worth having, and neither is available to someone who studied the list first.

Why

Security is usually taught as a list, and a list is the wrong shape for the subject. The lists are real and worth knowing, but they are a catalogue of instances, and instances age: a vulnerability class that dominated one decade is a solved configuration default in the next. What does not age is the question underneath every entry, which is always some version of the same one — something crossed a line that somebody believed was a line.

This catalogue happens to contain the evidence, which is why the claim is not just an aphorism. Six cards written for unrelated reasons made the identical move. third-party-blast-radius found that the boundary drawn around a frontend excluded the vendor whose script the frontend loads, and that the reflex controls both answer the wrong question, since the script was authorized. storage-collision-admin-takeover found two modules that each believed they owned a storage slot. pick-a-signer-not-a-brand found that a brand name is four supply chains, only one of which anyone audits. what-encryption-does-not-hide found that hiding the amount leaves the graph. huggingface-is-a-package-manager found that a load option is an execution grant. demonstration-is-not-a-specification found that granting an assistant sight of a task grants it sight of the screen. Six different subjects, one move, and none of them needed a vulnerability list to reach it.

Stated plainly, the method is two questions asked in order. First: where is the boundary, what is inside it, what is outside, and who gets to decide. Second: what crosses it, and what can the thing that crossed do once it is across. Nearly every real finding is the second question answered carefully after the first was answered casually — and the reason the casual answer survives so long is that the check people actually run, "is this authorized", returns yes at exactly the moment of the attack. The vendor was on the allowlist. The module was deployed on purpose. The flag was set by the person who needed it set.

The honest limit of this card is that some things do have to be memorised, and pretending otherwise would be the same error in the opposite direction. There is a small recurring set of places where authorized and safe come apart, and they are worth knowing by name because they are structural rather than incidental: anything that executes code arriving as data, anything that runs in someone else's storage or memory context, anything that deserialises untrusted input, anything that evaluates a template or expression, and any request made by your server on behalf of a caller, since your server is inside the perimeter and the caller is not. Those are shapes rather than bugs, and a boundary diagram plus that short list covers more ground than a long list alone.

Finally, the reason to study it this way rather than the comfortable way. A list gives the feeling of coverage and no way to check it. A boundary diagram is falsifiable: someone can point at it and say the line is in the wrong place, or that a crossing is missing, and that argument is productive in a way that comparing checklists never is. The output of studying security should be a thing someone else can attack on paper, and a list of attack names is not that thing.

How it works

Six cards, one move

Card The boundary that was assumed What actually crossed it
third-party-blast-radius "our frontend" A vendor's script from the vendor's own infrastructure — so SRI and CSP both pass
storage-collision-admin-takeover "each module owns its state" Two modules, one slot, two meanings
pick-a-signer-not-a-brand "the device" Entropy path, per-chain app, fulfilment partner
what-encryption-does-not-hide "the amount is the secret" The graph of who paid whom, and when
huggingface-is-a-package-manager "loading a model" trust_remote_code executes repo code in your process
demonstration-is-not-a-specification "it watches the task" It watches the screen

None of these were found by consulting a vulnerability list, and that is the argument. They were found by asking where the line was and what was on the other side of it.

The two questions, and why the usual one fails

Question What it catches Why it is not enough
"Was this authorized?" Unauthorized access Returns yes during the attack. The vendor was allowlisted, the flag was set on purpose
"Where is the boundary?" Boundaries drawn too small Needs the second question to become actionable
"What can it reach?" The blast radius of an authorized component This is the one that produces findings

The short list that does have to be memorised

Boundary thinking is the method, but a few shapes recur often enough to know by name — all of them places where authorized and safe come apart:

  • Code arriving as data, then executedtrust_remote_code, plugin loaders, dynamic imports
  • Running in someone else's context — delegatecall, shared storage, injected scripts
  • Deserialising untrusted input — the object graph is a program
  • Evaluating templates or expressions — the interpreter is the vulnerability
  • Requests your server makes for a caller — your server is inside the perimeter; the caller is not

These are shapes rather than bugs, which is why they survive the churn that retires specific vulnerability names.

The order to study in

  1. Draw the boundary for one system you own. Inside, outside, and every crossing.
  2. For each crossing, write what it could do if hostile today. Capability, not origin — this is the sentence third-party-blast-radius builds its whole inventory on.
  3. Then read the attack lists, as a coverage test. An entry that lands on a crossing tells you which control to check. An entry that does not land is either irrelevant here or a boundary you missed.

The reason for the order is that the list-first route produces recall and the model-first route produces a filter, and only one of those still works on a system nobody has written a list about yet.

← All Dev Notes · Workspace Index · Top ↑

공격 목록은 이전되지 않는다 — 이전되는 것은 신뢰 경계다

이 목록의 카드 여섯 장이 이름 없이 이미 같은 수를 뒀습니다 — 인가받았는가가 아니라 어디까지 닿는가. 그 수가 공부할 단위이고, 공격 목록은 그 방법의 커리큘럼이 아니라 산출물입니다.

아직 범위 미정이고, 1차 결과물은 체크리스트가 아니라 그림입니다. 이 저장소의 시스템 하나에 대해 경계 안과 밖을 그리고, 무언가가 넘어오는 모든 지점을 표시합니다 — 요청, 스크립트, 라이브러리, 웹훅, 협력사, 콘솔을 쥔 사람. 넘어오는 지점마다 두 가지를 적습니다: 오늘 허용된 일, 그리고 오늘 오후에 적대적으로 돌아서면 할 수 있는 일. 그다음에야 공격 목록을 읽되, 교과과정이 아니라 그림에 대한 커버리지 시험으로 씁니다. 각 항목은 내가 그린 넘어오는 지점에 떨어지거나(그러면 확인할 통제가 정해집니다) 떨어지지 못합니다(그러면 이 시스템과 무관하거나 경계를 하나 빠뜨린 것입니다). 둘 다 가질 값어치가 있고, 목록을 먼저 공부한 사람에게는 둘 다 없습니다.

보안은 보통 목록으로 가르쳐지는데, 목록은 이 주제에 맞는 모양이 아닙니다. 목록은 실재하고 알 값어치가 있지만 사례의 카탈로그이고, 사례는 낡습니다 — 한 시대를 지배한 취약점 부류가 다음 시대에는 해결된 기본 설정값입니다. 낡지 않는 것은 모든 항목 아래 있는 질문이고, 그건 언제나 같은 것의 변형입니다 — 누군가가 선이라고 믿었던 선을 무언가가 넘었다.

마침 이 카탈로그가 그 증거를 갖고 있어서, 이 주장은 그냥 경구가 아닙니다. 서로 무관한 이유로 쓰인 카드 여섯 장이 똑같은 수를 뒀습니다. third-party-blast-radius프론트엔드 둘레에 그린 경계가 그 프론트엔드가 불러오는 스크립트의 공급사를 제외하고 있었고, 반사적으로 집는 통제들이 엉뚱한 질문에 답한다는 것을 찾았습니다 — 그 스크립트는 인가돼 있었으니까요. storage-collision-admin-takeover같은 스토리지 슬롯을 각자 자기 것이라 믿은 두 모듈을 찾았습니다. pick-a-signer-not-a-brand브랜드 하나가 네 개의 공급망이고 감사받는 건 그중 하나뿐임을 찾았습니다. what-encryption-does-not-hide금액을 가려도 그래프가 남는다는 것을. huggingface-is-a-package-manager로딩 옵션이 실행 허가라는 것을. demonstration-is-not-a-specification어시스턴트에게 작업을 보여주는 것이 화면 전체를 보여주는 것임을. 여섯 개의 다른 주제, 하나의 수, 그리고 그중 어느 것도 취약점 목록이 필요하지 않았습니다.

평범하게 말하면, 방법은 순서가 있는 질문 둘입니다. 첫째, 경계가 어디인가, 안은 무엇이고 밖은 무엇이며, 누가 정하는가. 둘째, 무엇이 그것을 넘고, 넘은 그것은 넘은 뒤에 무엇을 할 수 있는가. 실제 발견의 거의 전부가 첫 질문을 대충 답한 뒤 둘째 질문을 꼼꼼히 답한 결과이고, 대충 답한 것이 그토록 오래 살아남는 이유는 하나입니다 — 사람들이 실제로 돌리는 검사인 "인가됐는가"는 공격이 일어나는 바로 그 순간에 예 라고 답하기 때문입니다. 공급사는 허용 목록에 있었고, 모듈은 의도적으로 배포됐고, 플래그는 그것이 필요했던 사람이 켰습니다.

이 카드의 정직한 한계는, 외워야 하는 것도 있다는 점입니다. 아니라고 하면 반대 방향의 같은 오류입니다. 인가됨과 안전함이 갈라지는 자리는 작고 반복되는 집합이고, 우연이 아니라 구조적이라서 이름으로 알아둘 값어치가 있습니다: 데이터로 도착한 것을 코드로 실행하는 모든 것, 남의 스토리지나 메모리 문맥에서 도는 모든 것, 신뢰할 수 없는 입력을 역직렬화하는 모든 것, 템플릿이나 표현식을 평가하는 모든 것, 그리고 호출자를 대신해 내 서버가 보내는 모든 요청(내 서버는 경계 안에 있고 호출자는 밖에 있으니까). 이건 버그가 아니라 모양이고, 경계 그림 + 이 짧은 목록긴 목록 하나보다 넓은 땅을 덮습니다.

마지막으로, 편한 방식 대신 이렇게 공부할 이유. 목록은 덮었다는 느낌을 주고 확인할 방법을 주지 않습니다. 경계 그림은 반증 가능합니다 — 누군가 그것을 가리키며 선이 틀린 자리에 있다거나 넘어오는 지점이 빠졌다고 말할 수 있고, 그 논쟁은 체크리스트를 비교하는 것과 달리 생산적입니다. 보안 공부의 산출물은 남이 종이 위에서 공격해 볼 수 있는 물건이어야 하고, 공격 이름 목록은 그 물건이 아닙니다.

동작 방식

카드 여섯 장, 하나의 수

카드 가정된 경계 실제로 넘어온 것
third-party-blast-radius "우리 프론트엔드" 공급사 자신의 인프라에서 온 공급사 스크립트 — 그래서 SRI·CSP 둘 다 통과
storage-collision-admin-takeover "각 모듈이 자기 상태를 소유한다" 모듈 둘, 슬롯 하나, 의미 둘
pick-a-signer-not-a-brand "그 기기" 엔트로피 경로, 체인별 앱, 배송 위탁사
what-encryption-does-not-hide "비밀은 금액이다" 누가 누구에게 언제 보냈는가의 그래프
huggingface-is-a-package-manager "모델을 불러온다" trust_remote_code 가 내 프로세스에서 저장소 코드를 실행
demonstration-is-not-a-specification "작업을 본다" 화면을 본다

이 중 어느 것도 취약점 목록을 뒤져서 찾은 것이 아니고, 그게 논거입니다. 선이 어디인지, 그 너머에 무엇이 있는지 물어서 찾았습니다.

질문 둘, 그리고 흔한 질문이 실패하는 이유

질문 무엇을 잡나 왜 부족한가
"인가됐는가?" 비인가 접근 공격 중에 예 라고 답합니다. 공급사는 허용 목록에 있었고 플래그는 의도적으로 켜졌습니다
"경계가 어디인가?" 너무 작게 그려진 경계 두 번째 질문이 있어야 행동 가능해짐
"어디까지 닿는가?" 인가된 구성요소의 폭발 반경 발견을 만들어내는 것은 이 질문

그래도 외워야 하는 짧은 목록

경계 사고가 방법이지만, 이름으로 알아둘 만큼 자주 반복되는 모양이 몇 개 있습니다 — 전부 인가됨과 안전함이 갈라지는 자리입니다:

  • 데이터로 도착해서 실행되는 코드trust_remote_code, 플러그인 로더, 동적 임포트
  • 남의 문맥에서 도는 것 — delegatecall, 공유 스토리지, 주입된 스크립트
  • 신뢰할 수 없는 입력의 역직렬화객체 그래프가 곧 프로그램
  • 템플릿·표현식 평가인터프리터가 곧 취약점
  • 호출자를 대신해 내 서버가 보내는 요청내 서버는 경계 안, 호출자는 밖

이건 버그가 아니라 모양이고, 그래서 특정 취약점 이름을 은퇴시키는 물갈이에도 살아남습니다.

공부 순서

  1. 내가 소유한 시스템 하나의 경계를 그립니다. 안, 밖, 그리고 넘어오는 모든 지점.
  2. 넘어오는 지점마다 오늘 적대적으로 돌아서면 할 수 있는 일을 씁니다. 출처가 아니라 권한third-party-blast-radius 가 인벤토리 전체를 세우는 문장입니다.
  3. 그다음 공격 목록을 커버리지 시험으로 읽습니다. 지점에 떨어지는 항목은 확인할 통제를 알려주고, 떨어지지 못하는 항목은 여기서 무관하거나 내가 경계를 빠뜨린 것입니다.

순서의 이유는, 목록 먼저는 암기를 만들고 모델 먼저는 필터를 만들기 때문입니다. 그리고 아직 아무도 목록을 써 주지 않은 시스템에서도 작동하는 것은 둘 중 하나뿐입니다.

← 전체 개발 노트 · 워크스페이스 인덱스 · 맨 위 ↑