보호되지 않은 피어가 버그다: 접근 제어 간극 감사하기
대부분의 접근 제어 및 검증 누락 버그는 특이한 것이 아닙니다. 같은 패밀리 내의 한 핸들러가 나머지 형제들이 수행하는 검사를 잊어버린 경우입니다. 역할을 확인하는 admin_delete와 그렇지 않은 bulk_delete. 길이를 검증하는 read_bounded와 길이를 신뢰하는 read_fast. 파일을 위에서 아래로 읽으면 이런 차이가 숨겨지지만, 피어들을 비교하면 드러납니다. 이는 가장 수확이 높은 화이트박스 기법 중 하나입니다.
찾고자 하는 비대칭성
guard → sink (의도된 안전한 경로)
??? → sink (검사가 누락된 피어 경로 - 버그)
사용 시기
- 하나의 싱크(sink)가 여러 호출 지점에서 도달 가능하며, 그중 하나가 검사를 건너뛰는 것으로 의심될 때.
- 인증/권한 부여(authz) 모델, 파서 패밀리, 또는 핸들러 그룹에서 잊혀진 검사를 감사할 때.
- 발견된 내용의 "왜 이것이 잘못되었는지"를 올바른 피어와의 구체적인 차이(diff)로 설명해야 할 때.
범위 확인
권한이 부여된 소스만 가능합니다. 인증/권한 부여의 출처를 명명할 수 없으면 중단하십시오.
루프
-
보호된 앵커(anchor)를 선택하십시오. 민감한 싱크 전에 검증을 수행하는 함수를 찾으십시오. 예를 들어 인증/권한 부여 확인, 범위 검사, 허용 목록 확인, 또는 살균기(sanitizer)입니다. 이것이 "여기서 올바른 모습"의 기준이 됩니다.
-
앵커의 검사를 그것이 보호하는 싱크에 고정시키십시오. 정확한 검사와 정확한 싱크를 명명하십시오. "올바름"이란 검사가 싱크를 지배(dominates) 함을 의미합니다. 즉, 앵커를 통과하는 모든 경로에서 싱크 전에 검사가 실행되어야 합니다. 함수 내에 단순히 존재하는 것만으로는 지배가 아닙니다.
-
구조적 피어들을 열거하십시오. 같은 파일 내 이웃으로 범위를 좁히지 마십시오. 핵심은 구조적 유사성입니다: 동일한 싱크에 도달하는 함수; 동일한 인터페이스/가상 메서드의 오버라이드; 동일한 라우트 테이블/디스패치 맵/명령 스위치 내의 핸들러; 클래스의 병렬 멤버들 (
get_x/set_x/delete_x); 공유 헬퍼의 호출자 중 일부는 인자를 살균하고 일부는 raw로 전달하는 경우. 동일한 싱크에 도달하는 피어가 가장 강력한 신호입니다. -
각 피어를 앵커와 비교(diff)하십시오. 검사의 이름이 아니라 검사가 시행하는 내용을 기준으로 삼으십시오. 각 피어에 대해, 동등한 검사가 동일한 싱크를 지배하는지 질문하십시오. 간극을 분류하십시오:
- 없음(absent) - 동등한 검사가 전혀 없음.
- 우회 가능(bypassable) - 존재하지만 일부 경로에서 건너뛸 수 있음 (조기 반환, 단락
||, 검사를 건너뛰는 분기). - 피어보다 약함(weaker-than-peer) - 다른 종류의 검사 (앵커가 권한 부여를 하는데 인증만 함; 앵커가 범위 검사를 하는데 타입 검사만 함; 앵커가 형제 필드도 확인하는데 한 필드만 확인).
- 싱크 후(after-sink) - 검사가 실행되지만, 위험한 작업 이후에 실행됨.
- 잘못된 컨텍스트(wrong-context) - 실제 검사이지만 이 싱크의 페이로드 클래스를 다루지 않음 (싱크의 언어에 맞지 않는 인코더).
-
공격자 입력으로 도달 가능성을 확인하십시오. 보호되지 않은 싱크는 신뢰할 수 없는 입력이 도달할 수 있을 때만 버그입니다. 피어의 위험한 인자를 실제 소스까지 추적하십시오 (
adjudicating-taint-paths사용). 상수로만 호출되는 피어는 발견 항목이 아닙니다. -
차이(diff)를 스키마에 기록하십시오. 다음과 같이 기술하십시오: "앵커 A는 싱크 S를 검사 C로 보호합니다 (지배적); 피어 B는
guard_status= <absent / bypassable / weaker-than-peer / after-sink / wrong-context> 상태로 S에 도달하며, B의 인자는 <소스>를 통해 공격자에 의해 제어됩니다."발견 스키마에 따라 출력하십시오.
작업 예제
앵커:
delete_own_post(user, post_id)-db.delete(post)전에post.owner == user.id를 확인합니다 (지배적). 피어:admin_bulk_delete(ids)는 동일한db.delete싱크에 도달합니다; 유일한 게이트는 라우트 수준의@login_required이며, 객체별 소유권 확인은 없습니다. 차이:guard_status = weaker-than-peer- 인증은 있지만, 권한 부여(소유권)는 없습니다. 도달 가능성:ids는 요청 본문에서 직접 가져오며; 인증된 모든 사용자가 접근할 수 있습니다. IDOR/BFLA 확인됨, 심각도 높음 - "모든 로그인한 사용자가 ID로 모든 사용자의 게시물을 삭제할 수 있습니다."
거부해야 할 합리화
- "함수에 검사가 있으므로 보호됩니다." → 싱크를 지배하는 경우에만 해당됩니다. 싱크가 건너뛰는 분기에 있는 검사는 보호가 아닙니다.
- "두 함수 모두 인증이 있으므로 동등합니다." → 인증은 권한 부여가 아닙니다. 타입 검사는 범위 검사가 아닙니다. 시행되는 내용을 비교하십시오.
- "피어가 보호되지 않은 것으로 보입니다. 신고하십시오." → 공격자 입력이 싱크에 도달하는지 확인하기 전까지는 안 됩니다. 도달 불가능 ≠ 발견 항목입니다.
- "같은 파일 내 이웃만 비교합니다." → 위험한 피어는 종종 다른 모듈에 있으면서 동일한 싱크에 도달합니다. 파일이 아닌 구조를 기준으로 열거하십시오.
실제 실행 방법
함수가 도달하는 싱크를 기준으로 그룹화하고 각 함수의 정확한 본문을 보여주는 도구가 필요합니다. 그래야 검사 대 검사로 비교(diff)하고 지배 여부를 확인할 수 있습니다. 코드 속성 그래프(code property graph)는 "어떤 피어들이 이 싱크에 도달하며, 어떤 피어들이 먼저 검증하는가"에 대한 질문에 직접 답할 수 있게 해줍니다. 그렇지 않으면 공유 싱크나 헬퍼의 호출 지점에서 수동으로 피어를 열거하고 각각을 읽어야 합니다. 도달 가능성 단계는 adjudicating-taint-paths를 재사용합니다.
관련 항목
adjudicating-taint-paths- 피어의 싱크가 입력으로 도달 가능한지 확인합니다.hunting-bugs-with-a-code-graph- 이 루프가 공급하는 마스터 루프입니다.- FINDING-SCHEMA.md - 모든 발견 항목이 취하는 형식입니다.