슬랙 웹훅은 쓰기 전용입니다, AI 에이전트가 그 채널을 읽기 전까지는
유출된 슬랙 incoming webhook은 보통 낮은 심각도로 분류됩니다. 쓰기 전용에 채널 하나, 데이터 접근도 없기 때문입니다. 그러나 AI 에이전트가 그 채널을 읽고 도구로 행동하는 순간, 그 쓰기 전용 권한은 에이전트의 권한으로 들어가는 indirect prompt injection 경로가 됩니다. 전체 킬체인과 성립 조건, 방어법을 정리했습니다.

목차(8)
목차
심각도가 뒤집히는 지점
대부분의 보안팀이 한 번쯤 처리하고 닫아본 이슈가 있습니다. incoming webhook URL이 공개 저장소나 클라이언트 측 번들, CI 로그에서 발견됩니다. 검토자는 incoming webhook이 할 수 있는 일을 확인하고, 최악의 경우라야 공격자가 채널 하나에 메시지를 남기는 정도라고 판단한 뒤, 낮은 심각도로 분류하고 시간 날 때 URL을 교체하고 넘어갑니다. 이 판단은 오랫동안 옳았습니다.
AI 에이전트가 그 채널을 읽는 워크스페이스에서는 더 이상 옳지 않습니다.
이유는 말로는 간단한데 놓치기 쉽습니다. 유출된 웹훅은 공격자에게 특정 슬랙 채널에 공격자가 통제하는 텍스트를 써넣을 수단을 줍니다. 그 채널을 감시하는 AI 에이전트는 그 텍스트를 신뢰된 입력으로 읽습니다. 에이전트가 도구를 통해 행동까지 한다면, 공격자는 "메시지를 남긴다"에서 "에이전트의 행동에 개입한다"로 단숨에 넘어갑니다. 웹훅이 새로운 권한을 얻은 것이 아닙니다. 권한은 에이전트가 제공했고, 웹훅은 그 권한에 도달하는 전달 통로가 됩니다.
이 글은 그 역전이 왜 일어나는지, 킬체인이 실제로 어떤 모양인지, 그리고 이 시나리오가 성립하거나 성립하지 않는 정확한 조건을 짚습니다. 조건이 중요합니다. 이 시나리오의 정직한 버전은 자극적인 제목이 암시하는 것보다 범위가 좁고, 그 좁은 버전조차 충분히 심각하기 때문입니다.
incoming webhook의 실체
슬랙의 incoming webhook은 https://hooks.slack.com/services/T.../B.../... 형태의 URL입니다. 이 URL로 작은 JSON 본문을 HTTPS POST 할 수 있는 사람이라면 누구나 채널에 메시지를 띄울 수 있습니다. 능력의 범위는 그게 전부이고, 그 한계를 정확히 짚을 가치가 있습니다.
incoming webhook은 쓰기 전용입니다. 메시지를 게시합니다. 메시지를 읽거나, 채널을 나열하거나, 사용자를 열거하거나, 워크스페이스에서 어떤 데이터도 빼낼 수 없습니다. 최근 슬랙에서는 웹훅이 생성 시점에 하나의 채널로 고정되기 때문에, 유출된 웹훅이라도 어디에 게시할지 마음대로 고를 수 없습니다. 공격자에게 중요한 것은 메시지별 꾸미기가 아니라 정체성입니다. 모던 앱 웹훅이 게시하는 모든 메시지는 그 웹훅이 만들어진 통합의 정체성을 달고 도착합니다. 공격자는 레거시 custom integration 웹훅이 허용하던 식으로 사용자명이나 아이콘을 메시지별로 덮어쓸 수 없습니다. 그럴 필요도 없습니다. 메시지는 이미 정당한 통합으로, 채널을 읽는 쪽이 신뢰하도록 길든 바로 그 봇으로 게시되고, 그 위에 Block Kit 서식까지 입힐 수 있어 알려진 서비스의 평범한 게시물처럼 보입니다.
바로 이 점 때문에 유출된 incoming webhook은 그동안 낮은 심각도 이슈였습니다. 공격자는 채널 하나에, 그 채널이 이미 신뢰하는 서비스의 이름으로 메시지를 떨어뜨릴 수 있습니다. 그 자체로는 채널을 읽는 사람을 겨냥한 피싱이자 사회공학 수단입니다. 거슬리고, 누군가 클릭하면 가끔 위험하지만, 범위가 제한적입니다. 데이터 유출도, 읽기 권한도, 측면 이동도 없습니다. 폭발 반경은 마침 그 채널을 보고 있던 사람들에서 끝납니다.
이 글의 논지 전체는 "마침 그 채널을 보고 있던 사람들"이라는 표현이 조용히 떠안고 있는 가정에 있습니다. 그 표현은 채널의 청중이 사람이라고 전제합니다.
새로운 변수: 채널을 읽고 행동하는 에이전트
이제 많은 조직에서 AI 에이전트가 슬랙 채널에 연결되어 있고, 쓸모 있는 에이전트는 대화만 하지 않습니다. 인시던트 대응 에이전트는 #alerts나 #ci를 감시하며 새 알림을 읽고, 로그를 조회하거나 내부 상태 API를 호출해 조사하도록 허용되어 있습니다. 지원 분류 에이전트는 #support를 읽고 티켓을 요약하며 계정 정보를 조회하거나 다른 시스템에 레코드를 엽니다. 운영 보조 에이전트는 채널을 읽고 배포를 트리거하거나 서비스를 재시작하거나 다른 채널에 게시합니다. 이런 에이전트가 가치 있는 이유는 정확히, 메시지를 읽는 것과 그에 대해 무언가를 하는 것 사이의 고리를 닫아주기 때문입니다.
내부적으로 그 "무언가를 한다"는 것은 도구 사용입니다. 에이전트는 function calling이나 Model Context Protocol로 도구 묶음을 붙인 언어 모델입니다. 이 데이터베이스를 조회하라, 저 API를 호출하라, 이 메일을 보내라, 저 작업을 시작하라. 에이전트는 채널 내용을 읽고 어떤 도구를 호출할지 결정한 뒤, 자기 자격증명과 권한으로 그 도구를 호출합니다. 우리는 모든 에이전트 행동이 곧 자격증명 행동이라는 점을 이전에 다룬 적이 있는데, 같은 관점이 여기에도 적용됩니다. 에이전트는 실제 권한을 쥐고 있고, 채널 안의 어떤 개인보다도 큰 권한일 때가 많습니다. 여러 요청에 두루 쓰일 만큼 넉넉하게 권한이 부여되었기 때문입니다.
이제 두 사실을 나란히 놓아봅니다. 유출된 웹훅은 공격자가 채널에 글을 쓰게 해줍니다. 에이전트는 채널 안의 모든 것을 입력으로 읽고 권한 있는 도구로 그에 대해 행동합니다. 이 두 문장 사이의 간극이 취약점의 전부입니다.
킬체인
킬체인은 다섯 단계이고, 어느 단계도 공격자에게 유출된 URL 외의 추가 침투를 요구하지 않습니다.
1. 웹훅 확보. incoming webhook URL은 다른 모든 시크릿이 유출되는 방식 그대로 유출됩니다. 공개 저장소에 커밋되거나, 클라이언트 측 JavaScript 번들이나 모바일 앱에 박히거나, CI 로그에 출력되거나, Postman 컬렉션에 공유되거나, 티켓에 붙여넣어집니다. 오랫동안 민감도가 낮은 것으로 취급되어 왔기 때문에 시크릿 스캐닝 규칙에 아예 포함되지 않은 경우가 많고, 그래서 DB 비밀번호보다 더 오래 눈에 띄는 곳에 방치됩니다.
2. 채널에 에이전트가 붙어 있는지 확인. 공격자는 채널을 들여다볼 필요가 없습니다. 그 채널을 에이전트가 소비한다는 것을 추측하거나 알기만 하면 됩니다. 웹훅은 보통 에이전트가 감시하는 바로 그 채널들을 위해 만들어집니다. #alerts, #ci, #monitoring, #ops, #support. "통합이 게시하는 곳"과 "운영 에이전트가 듣는 곳"의 겹침은 우연이 아닙니다. 같은 채널 집합입니다.
3. 조작된 내용 주입. 공격자는 데이터가 아니라 지시로 읽히도록 설계된 메시지를 POST 합니다. 평범한 알림처럼 꾸민 그 메시지 안에는 에이전트를 겨냥한 지시가 박혀 있습니다. 인시던트를 해결된 것으로 처리하고, 정리 작업으로서 공격자가 고른 인자로 특정 도구를 호출하라는 식입니다. 에이전트에게 이것은 그저 신뢰된 채널 기록의 다음 줄일 뿐입니다.
4. 에이전트의 실행. 에이전트가 채널 내용을 맥락으로 받아들이고 주입에 대한 방어가 없다면, 자기 도구와 자기 권한으로 그 지시를 따릅니다. 메시지가 조회하라고 한 데이터를 조회하고, 호출하라고 한 API를 호출하고, 드러내라고 한 시크릿을 드러냅니다. 전형적인 혼동된 대리인(confused deputy) 문제입니다. 에이전트는 공격자에게 없는 권한을 가지고 있고, 공격자는 신뢰된 채널로 지시를 흘려보내 그 권한을 빌려 씁니다.
5. 측면 이동과 유출. 그다음은 전적으로 에이전트의 도구 묶음에 달려 있습니다. 내부 시스템을 읽을 수 있는 에이전트는 엉뚱한 것을 읽고 공격자도 닿을 수 있는 채널에 보고하도록 유도될 수 있습니다. 외부로 쓰거나 호출할 수 있는 에이전트는 데이터를 직접 내보내도록 유도될 수 있습니다. 단독으로는 거의 가치가 없던 쓰기 전용 웹훅이, 에이전트가 할 수 있는 무엇이든 원격으로 당기는 방아쇠가 됩니다.
구체적인 그림을 그려보면 분명해집니다. #ci에 붙은 인시던트 에이전트가 실패 알림이 뜨면 최근 로그를 조회하고 내부 /runbook API를 호출해 조치하도록 허용되어 있다고 합시다. 공격자는 빌드 실패 알림처럼 꾸민 메시지를 POST 하고, 그 본문에는 특정 환경변수 값을 상태 요약에 포함하라거나 /runbook을 외부를 가리키는 파라미터로 호출하라는 "조치 노트"가 들어 있습니다. 에이전트는 이것을 정당한 노트가 달린 정당한 알림으로 읽고 그대로 따릅니다. 사람은 아무것도 승인하지 않았습니다. 공격자가 넣은 입력은 어느 검토자가 한때 낮은 심각도로 표시했던 URL로 보낸 HTTPS POST 하나뿐입니다.
이것이 왜 indirect prompt injection인가
여기서 다루는 버그 부류는 indirect prompt injection이고(OWASP Top 10 for LLM Applications에서 LLM01로 맨 위에 놓인 바로 그 부류입니다), 웹훅은 그것을 유난히 깔끔하게 전달하는 수단일 뿐입니다. direct prompt injection은 사용자가 악의적 프롬프트를 모델에 직접 입력하는 경우입니다. indirect prompt injection은 악의적 지시가 모델이 다른 데서 받아들이는 콘텐츠를 통해 도착하는 경우입니다. 브라우징하는 웹페이지, 요약하는 문서, 분류하는 받은편지함의 이메일, 그리고 감시하는 채널의 슬랙 메시지.
슬랙 채널을 읽는 모든 에이전트는 그 채널이 신뢰된 입력이라는 암묵적 가정을 합니다. 이 가정은 채널에 글을 쓰는 모든 주체가 신뢰될 때만 성립합니다. 유출된 incoming webhook은 정확히 그 가정을 깹니다. 신뢰되지 않은 외부인에게 글쓴이 자리를 하나 내주기 때문입니다. 에이전트는 동료가 올린 메시지와 공격자가 유출된 URL로 POST 한 메시지를 구분하지 못합니다. 둘 다 채널 기록으로 도착합니다. 누군가 출처(provenance) 개념을 부여하지 않는 한, 모델에는 그런 개념이 내장되어 있지 않습니다.
그래서 해결책이 웹훅 쪽에만 있을 수 없습니다. 유출된 URL 교체는 문 하나를 닫습니다. 구조적 문제는 에이전트가 애초에 채널 내용을 지시로 취급한다는 데 있습니다.
이것이 성립하지 않는 경우
이 시나리오의 정직한 버전에는 전제가 있고, 그 전제를 명시하는 것이 분석과 공포 마케팅을 가릅니다. 다음 중 하나라도 거짓이면 킬체인은 끊어집니다.
웹훅이 에이전트가 실제로 읽는 채널을 가리켜야 합니다. 어떤 에이전트도 소비하지 않는 채널의 유출 웹훅은 다시 낮은 심각도의 피싱 수단으로 돌아갑니다. 최근 incoming webhook은 채널에 고정되므로, 공격자는 웹훅을 더 탐나는 채널로 돌리지 못하고 생성 시점에 정해진 곳에 묶입니다.
에이전트가 채널 내용을 사람에게 요약만 하는 것이 아니라 도구로 행동해야 합니다. 다른 도구 없이 채널을 읽고 요약만 써내는 에이전트라면, 그 요약을 읽는 사람을 겨냥한 사회공학으로 영향이 내려갑니다. 사소하진 않지만 도구 실행은 아닙니다.
에이전트에 주입 방어가 없어야 합니다. 데이터와 지시를 분리하는 에이전트, 채널 내용으로부터 명령을 받지 않는 에이전트, 민감한 도구를 사람 승인 뒤에 두는 에이전트, 통합과 봇이 쓴 메시지를 무시하는 에이전트는 주입된 지시를 따르지 않습니다. 취약점은 "채널을 읽는다"와 "채널을 신뢰한다" 사이의 간극에 살고, 잘 만든 에이전트에는 그 간극이 없습니다.
이 전제들은 특별한 것이 아닙니다. 실제 배포 환경 상당수가 세 조건을 동시에 만족합니다. 에이전트를 알림 채널에 연결하는 목적 자체가 알림을 읽고 그에 따라 행동하게 하는 것이고, 주입 방어는 팀이 가장 자주 건너뛰는 부분이기 때문입니다. 그렇다고 유출된 웹훅이라면 무조건 치명적이라고 암시하는 글은 틀렸을뿐더러, 더 나쁘게는 사람들에게 심각도를 실제로 결정하는 단계들을 무시하도록 길들입니다.
탐지와 완화
방어는 세 층으로 나뉘고, 가장 오래가는 방어는 에이전트 쪽에 있습니다.
웹훅 위생. incoming webhook URL을 시크릿으로 취급하십시오. 이 공격의 전제 자체가 그것을 시크릿으로 취급하지 않는다는 데 있기 때문입니다. hooks.slack.com/services를 시크릿 스캐닝 규칙에 넣어, 유출된 웹훅이 다른 자격증명처럼 잡히게 하십시오. 웹훅을 그 정체대로 비인간 자격증명(NHI)으로 인벤토리에 올리고, API 키를 관리하듯 소유자와 로테이션 절차를 두십시오. 워크스페이스에서 웹훅을 만들 수 있는 사람을 제한하십시오. 가능하다면 원시 incoming webhook 대신 범위가 지정되고 서명된 봇 토큰을 쓰는 슬랙 앱을 선호하십시오. 맨 URL이 주지 못하는 인증과 폐기 수단을 주기 때문입니다.
에이전트 쪽 방어, 이것이 진짜 해법입니다. 모든 채널 내용을 신뢰되지 않은 입력으로 다루고 절대 지시로 다루지 마십시오. 에이전트가 읽는 것(데이터 평면)과 에이전트가 할 수 있는 것(제어 평면)을 분명히 분리해, 메시지를 읽는 것만으로는 결코 도구 호출이 인가되지 않게 하십시오. 민감한 도구는 사람 승인 뒤에 두십시오. 에이전트의 도구를 최소 권한으로 좁히고, 특히 누구나 또는 어떤 통합이든 글을 쓸 수 있는 채널을 읽는 에이전트에는 폭넓게 권한이 부여된 도구 묶음을 주지 마십시오. 모든 도구 호출을 그것을 촉발한 입력의 출처와 함께 로깅해, 외부에서 들어온 채널 내용으로 거슬러 올라가는 도구 호출에 플래그를 달 수 있게 하십시오.
경계에서의 작성자 확인. incoming webhook으로 게시된 메시지는 실제 사람 사용자가 아니라 봇 또는 통합 작성자 정보를 달고 옵니다. 에이전트는 이것을 활용할 수 있습니다. 사람이 쓴 메시지와 통합이 쓴 메시지를 구분하고 통합 메시지를 명령 출처로 취급하지 않으면, 이 공격이 지나가는 바로 그 문이 닫힙니다. 다만 짚어둘 함정이 하나 있습니다. 유출된 웹훅은 자기가 속한 통합의 이름으로 게시되기 때문에, 그 통합을 명령 출처로 명시적으로 신뢰하는 에이전트는 이 확인으로 전혀 보호받지 못합니다. 주입된 메시지가 정확히 에이전트가 신뢰하라고 들은 그 정체성으로 도착하기 때문입니다. 그래서 오래가는 형태의 통제는 "이 봇은 믿고 저 봇은 안 믿는다"가 아니라 "사람이든 봇이든 채널에 글을 쓰는 어떤 주체도 단독으로는 명령 출처가 아니다"입니다. 작성자 필터링은 공격면을 좁힐 뿐, 채널 내용을 신뢰되지 않은 것으로 다루는 일을 대체하지 못합니다.
결론: 심각도는 맥락이 정한다
유출된 incoming webhook은 비인간 자격증명이고, 많은 NHI 유출이 그렇듯 그 심각도는 본질적으로 정해져 있지 않습니다. 무엇이 그것을 소비하는가가 심각도를 정합니다. 오랫동안 소비자는 채널을 읽는 사람이었고, 심각도 계산은 그것을 반영했습니다. AI 에이전트는 소비자를 바꾸고, 소비자가 바뀌면 이 하나만이 아니라 "영향이 낮은" 자격증명 유출 부류 전체의 심각도가 바뀝니다.
실무적 교훈은 슬랙 웹훅이 단독으로 새삼 위험해졌다는 것이 아닙니다. 실제 도구를 쥔 에이전트를 채널에 연결하는 행위가, 그 채널에 글을 쓸 수 있는 모든 자격증명을 조용히 재평가한다는 것입니다. 그 고리를 켜기 전에, 채널에 게시하는 각 통합에 대해 단순한 질문을 던질 가치가 있습니다. 공격자가 이 통합이 보내는 것과 똑같은 것을 보낼 수 있다면, 에이전트는 그것으로 무엇을 할 것인가. 답이 불편하다면, 고쳐야 할 곳은 웹훅이 아니라 에이전트입니다.
다음 글을 메일로 받아보세요
Cremit 리서치 팀의 월간 NHI 브리프. 한 통에 핵심만 담습니다.