시리즈: AI와 함께 코딩 배우기

토요일 아침의 여덟 가지 보안 발견 사항

여덟 가지 보안 발견 사항, 모든 수정이 토요일 밤까지 배포됐다.

여덟 개의 발견 사항, 하루의 토요일 엔터프라이즈 실사, 전부 해결 완료 입력 검증 하드코딩된 자격 증명 암호 스위트 정책 use-after-free 스레드 안전성 인가 우회 인증 실패 로깅 의존성 지연 8 / 8 해결됨 + 새로운 영구 가드레일 6개
감사를 환영하는 팀이 코드베이스가 더 나아지는 팀이다.

감사는 것들을 드러낸다. 정직한 대응은 그것을 환영하고, 모든 것을 고치고, 영구적인 가드레일을 배포하는 것이다 — 이것이 정확히 런타임이 엔터프라이즈의 신뢰를 얻는 방식이다.

지난주 말 그 감사 보고서가 받은편지함에 도착했다. 토요일 아침에는 한쪽 화면에는 보고서를, 다른 쪽 화면에는 코드베이스를 열어두고 있었다. 치명적 또는 높음 등급 발견 사항 여덟 개. 이 정도면 주말 전체를 규정하는 종류의 받은편지함 내용이다.

이것은 엔터프라이즈 실사였다. 잠재적 파트너가 자신들의 변호사가 진행을 허락하기 전에 보안팀에게 공개 API 표면에 대한 검토를 요청한 것이었다. 발견 사항들은 구체적이었고, 잘 설명되어 있었으며, 전적으로 타당했다. 또한 에이전트 주도 워크플로우가 의도적으로 방어하지 않으면 특히 취약해지는 종류의 것들이었다.

토요일이 끝날 무렵, 모든 발견 사항에 그것을 반영하는 PR이 하나씩 있었다. 이 글은 각각이 무엇이었는지에 대한 글이다.

카테고리들

구체적인 익스플로잇 세부 사항보다는 카테고리에 대해 이야기하려 한다. 세부 사항은 이제 수정되었고 경쟁사에게는 흥미로울 게 없기 때문이다. 카테고리는 다른 팀들이 방어하고 싶어 할 대상이다.

하나: 공개 REST 표면의 입력 검증. 여러 엔드포인트가 필요 이상으로 입력을 신뢰하고 있었다. 구체적으로는 문자열 입력의 길이 제한, 숫자 입력의 범위 제한, JSON 페이로드의 구조 검증이었다. 누락된 검증 그 자체만으로는 치명적인 것이 없었다. “여기는 길이 제한 없음, 저기는 속도 제한 없음, 이 관리자 엔드포인트는 인증 확인 없음”의 조합이야말로, 공격자가 그 표면을 발견하고 나면 치명적인 것이 되는 종류의 스택이다.

둘: 설정 파일 내 하드코딩된 자격 증명. 몇 주 전 주말의 HMAC 발견 사항과는 다른 것이다. 이것은 예시 버전에 실제 자격 증명이 커밋된 설정 템플릿이었다. 원래 의도는 자리표시자인 예시 자격 증명을 배포하는 것이었지만, 실제로 배포된 것은 어느 개발자의 로컬 환경에서 나온 실제 값이었다. 로테이션, 예시에서 제거, 시크릿 스캐닝 CI 패스에 추가.

셋: 안전하지 않은 암호 스위트 협상. 외부 서비스와 TLS를 협상하는 서브시스템이 2026년 기준으로 허용되어서는 안 될 암호 스위트를 수락하고 있었다. 구체적으로는 알려진 약점이 있는 여러 pre-TLS-1.3 스위트였다. 해결책은 암호 스위트 목록을 TLS-1.3 전용 세트로 제한하고, 레거시 파트너 시스템을 상대로 테스트할 때를 위한 문서화된 탈출구(명시적 플래그)를 두는 것이었다.

넷: C API 경계의 use-after-free. 런타임 DLL 중 하나의 C API에 내부 상태를 가리키는 포인터를 반환하는 함수가 있었는데, 호출자는 같은 핸들에 대한 이후 호출로 그 내부 상태가 해제된 뒤에도 그 포인터를 계속 사용할 수 있었다. 전형적인 C API 자충수다. 해결책은 “호출자가 포인터를 보유하는” 시맨틱스에서 “호출자가 불투명 핸들을 보유하고 handle-by-get 접근자를 쓰는” 시맨틱스로 전환하는 것이었다. 접근자는 그 호출이 지속되는 동안만 유효한 새 포인터 복사본을 반환하며, 그 아래의 메모리는 노출되지 않는다.

다섯: 라이선싱 계층의 스레드 안전성 구멍. 동시 검증 호출 중에 두 스레드가 같은 라이선싱 상태 객체에서 경합할 수 있었다. 부하가 걸리면 한 스레드가 부분적으로 업데이트된 상태를 보고 라이선스 유효성에 대해 잘못된 결론을 내릴 수 있었다. 해결책은 그 상태를 둘러싼 제대로 된 read-write 락이었으며, 검증 경로는 흔한 케이스(라이선스 유효, 상태 변경 불필요)에 맞춰 최적화되었다.

여섯: 관리자 엔드포인트의 인가 우회. 관리자 엔드포인트 중 하나에 인증 확인은 있었지만 인가 확인은 없었다. 관리자 역할이 없는 사용자를 포함해 인증된 어떤 사용자든 그것을 호출할 수 있었다. 해결책은 역할 확인을 추가하고, “관리자로 인증됨 → 허용”과 “인증됨이지만 관리자 아님 → 거부”라는 두 경로를 모두 검증하는 단위 테스트를 작성하고, 다른 모든 관리자 엔드포인트를 같은 패턴으로 감사하는 것이었다. 다른 세 개의 엔드포인트에도 같은 문제가 있었다. 이제 네 개 모두 올바르다.

일곱: 인증 실패에 대한 불충분한 로깅. 사용자가 인증을 시도해 실패했을 때, 런타임은 그 시도를 로깅했지만 무차별 대입 공격이나 자격 증명 스터핑 패턴을 조사하기에 충분한 컨텍스트는 로깅하지 않았다. 해결책은 모든 인증 실패에 IP 주소, 그 IP에 대한 속도 제한 카운터, 시도된 인증 방식과 함께 구조화된 로깅을 추가하는 것이었다. 프라이버시를 보존하면서(비밀번호 내용은 로깅되지 않는다) 조사가 필요해졌을 때 충분한 컨텍스트를 제공한다.

여덟: 의존성 업데이트 지연. 런타임이 끌어오는 서드파티 의존성 중 여럿이 매니페스트에 취약점이 알려진 버전으로 고정되어 있었다. 해결책은 각각을 최신 패치 버전으로 업데이트하고, 그 업데이트에 대해 테스트 스위트를 실행하고, 그 업데이트들이 요구하는 소수의 API 형태 변경을 해결하는 것이었다. 여덟 개의 업데이트 중 두 개는 우리 코드에서 어댑터 변경이 필요했다. 나머지 여섯 개는 그대로 갈아끼울 수 있었다. Dependabot은 이제 이를 자동으로 표시하도록 설정되어 있다.

보안 작업으로 가득 찬 주말에서 배운 것

세 가지다. 놀랍지는 않다. 그래도 말할 가치가 있다.

보안 발견 사항은 뭉쳐서 나온다. 여덟 개의 발견 사항이 한꺼번에 드러난 것은 상당한 양이다. 돌이켜보면 패턴은, 이것들이 모두 공통의 뿌리를 공유한다는 것이다. 런타임은 에이전트 주도 워크플로우 안에서 빠르게 성장해왔고, 에이전트들은 개별적으로는 올바른 것들을 구현했다. 보안 같은 크로스커팅 관심사는 정확히 PR 단위 리뷰에서 빠져나가는 종류의 것이다. 감사는 리뷰가 놓친 것을 잡아낸다. 감사를 일정에 넣어라.

일부 발견 사항은 에이전트 주도 실패 양상이다. C API의 use-after-free는 프롬프트가 소유권 시맨틱스를 명시하지 않고 “이 내부 상태를 호출자에게 노출하라”고 말할 때 에이전트가 작성하는 종류의 것이다. 라이선싱 계층의 스레드 안전성 구멍도 비슷하다. 둘 다 같은 패턴을 잡아낸다. 소유권과 동시성에 대해 질문받지 않은 에이전트는 둘 다 무시하는 코드를 만들어낸다.

일부 발견 사항은 속도 주도 실패 양상이다. 의존성 업데이트 지연은 에이전트 문제가 아니다. “빠르게 배포하면서 의존성을 신선하게 유지하는 일을 맡은 사람이 없는” 문제다. 해결책은 프로세스(Dependabot)와 규율(Dependabot의 알림에 대응하는 것)이다. 해결책은 “더 똑똑해지는 것”이 아니다.

새로운 방어책들

이번 주말 영구적인 가드레일이 배포됐다.

CI 내 보안 스캐너 패스. 입력 검증, 시크릿 스캐닝, 암호 스위트 정책에 대한 정적 분석. 모든 PR이 이 스캔을 실행한다. 새로운 발견 사항을 도입하는 PR은 리뷰를 위해 표시된다.

관리자 엔드포인트 테스트 패턴. API의 모든 관리자 엔드포인트는 이제 관리자-허용 경로와 비관리자-거부 경로를 둘 다 검증하는 짝을 이룬 테스트를 갖는다. 이 패턴은 @admin_required 데코레이터를 스캔하고, 그에 대응하는 짝 테스트 없이 그 데코레이터가 있으면 빌드를 실패시키는 CI 체크로 강제된다.

공유 상태에 대한 스레드 안전성 어노테이션. 런타임의 모든 공유 상태 객체는 이제 자신의 동시성 모델에 대한 명시적 어노테이션을 갖는다. 불변, 락으로 보호됨, 명시적 happens-before를 갖는 락-프리, 또는 단일 스레드. 이 어노테이션은 타입의 일부다. 그 상태를 건드리는 코드는 그 어노테이션을 만족해야 한다. 컴파일러는 락-보호와 불변 케이스에 대해 이를 강제한다. 나머지는 리뷰의 몫이다.

달력에 오른 월간 보안 감사. 파트너가 요청하기를 기다리지 않는다. 이 감사는 매달 두 번째 토요일의 상시 항목이다. 첫 번째는 4월에 진행된다.

파트너와 빌더가 여기서 가져가야 할 것

파트너십을 위해 엔진을 평가하고 있고 감사를 요청하려는 참이라면, 이 팀은 그것을 환영할 것이다. 감사는 것들을 드러낸다. 것들이 고쳐진다. 코드베이스가 나아진다. 파트너 관계가 나아진다. 감사를 환영하는 규율이 어떤 특정 발견 사항보다 더 중요하다.

당신이 이 글을 읽는 엔터프라이즈 보안 전문가라면, 감사 주기와 더 많은 엔진 팀들이 감사했으면 하는 구체적인 것들에 대한 제안을 환영한다. 이번 주말의 발견 사항들은 뻔한 것들이었다. 뻔하지 않은 것들이야말로 내가 다음으로 찾고 싶은 것들이다.

에이전트 주도 워크플로우를 운영하고 있고 최근에 보안 감사를 하지 않았다면, 하나 진행하라. “에이전트가 개별적으로는 올바르지만 크로스커팅 관심사를 놓치는 코드를 쓴다”는 패턴은 이 워크플로우 형태에서 보편적이다. 감사는 그 크로스커팅 관심사를 잡아내는 방법이다. 내가 찾은 다른 방법은 없다.

주말 마감 점검. 여덟 개의 발견 사항이 해결됐다. 여섯 개의 새로운 방어책이 배포됐다. 다음 감사는 달력에 올라 있다. 파트너 관계가 앞으로 나아간다.

다시 빌드하러 간다.

실사를 통과하는 공간 런타임

RakuAI는 엔터프라이즈 파트너와 스마트글래스 제조사를 위해 만들어졌다 — 감사받고, 강화되고, 그것에 대해 정직하다. 우리가 당신의 팀이 통과해야 할 보안 기준에 맞춰 어떻게 엔지니어링하는지 확인하라.

← 전체 글