TT Lab
블로그 시작하기
배우기 러닝패스 코스

LLM 앱 보안 — 주입에서 출력 처리까지

데이터가 지시가 되는 순간 — 직접·간접 프롬프트 주입

TT Lab 에서 이어서 보기

한 줄 요약

모델은 입력 안의 어느 글이 명령이고 어느 글이 자료인지 확실히 가르지 못합니다. 그래서 「무엇을 해도 되는가」 는 모델이 아니라 모델 밖의 서버 정책이 정해야 합니다.

왜 이게 필요했나

SQL 주입을 떠올려 보면 쉽습니다. 코드와 데이터가 한 문자열에 섞였기 때문에 데이터가 코드로 실행됐고, 그래서 우리는 자리표시자(매개변수화)로 둘 사이에 엔진이 지키는 경계를 만들었습니다. LLM 에는 그런 경계가 없습니다. 시스템 문장·사용자 질문·검색한 문서·도구 결과는 결국 한 줄로 이어 붙인 토큰열이고, 「여기부터는 자료」 라는 표시는 모델이 학습으로 익힌 경향일 뿐 강제되는 규칙이 아닙니다.

사용자가 자기 질문에 지시를 섞는 것이 직접 주입입니다. 더 까다로운 것은 간접 주입입니다. 앱이 검색해 온 문서, 요약하라고 받은 웹 페이지·메일, 도구가 돌려준 결과 안에 지시처럼 생긴 문장이 숨어 있으면, 공격자는 우리 사용자에게 말을 걸 필요도 없이 문서 하나만 심어 두면 됩니다. RAG 와 도구가 붙은 앱일수록 이 길이 넓어집니다. OWASP LLM Top 10 2025 는 이것을 첫 번째 위험(LLM01 프롬프트 주입)으로 둡니다.

어떻게 동작하나

이 코스의 실습은 바깥 모델 대신 규칙으로 움직이는 가짜 모델을 씁니다. 같은 입력이면 늘 같은 답을 내야 채점기가 「공격을 보냈더니 무슨 일이 났나」 를 몇 번이고 같은 결과로 잴 수 있기 때문입니다. 이 모델은 진짜 모델의 약점을 과장한 캐리커처입니다.

가짜 모델의 성질 진짜 모델에서 대응하는 것
입력 어디에 있든 지시 표식을 따른다 역할 구분이 학습된 경향일 뿐이라 자료 속 지시를 따를 때가 있다
전각·공백·대소문자·base64 를 같은 지시로 읽는다 바꿔 말한 지시, 다른 언어, 인코딩된 지시도 알아듣는다
도구 호출을 텍스트 줄로 낸다 함수 호출도 결국 앱이 해석하는 출력이다

정직하게 말해 둘 한계가 있습니다. 진짜 모델은 확률적입니다. 같은 공격이 열 번에 한 번 통할 수도 있고, 표현을 바꾸면 통할 수도 있습니다. 가짜 모델 앞에서 「표식을 거르는 필터」 가 통했다면 그것은 필터가 이 모델의 문법을 다 알았다는 뜻일 뿐이고, 진짜 모델 앞에서 같은 필터가 막는다는 증거가 되지 못합니다. 시험 몇 번이 통과했다고 안전이 증명되지 않는 이유입니다.

그래서 방어의 중심은 모델 바깥에 둡니다.

이 층들이 왜 모델 「밖」 이어야 하는지는 실패했을 때를 떠올리면 분명해집니다. 필터가 새면 모델은 지시를 따르고, 그 순간 마지막으로 남는 것은 서버의 규칙입니다. 거꾸로 서버의 규칙이 새는 것은 코드의 결함이라 시험으로 찾을 수 있고, 한 번 고치면 다시 새지 않습니다. 확률적인 부품 앞에는 결정적인 부품을 둔다 — 이 코스 전체를 꿰는 원칙이 이것입니다.

간접 주입의 입구는 생각보다 많습니다. 검색 문서만이 아니라 사용자가 붙여 넣은 메일, 지난 대화에서 저장해 둔 메모, 웹 페이지를 읽어 온 도구의 결과, 다른 에이전트가 넘겨준 요약까지 모두 모델 입력이 됩니다. 앱에 「기억」 이나 「도구」 를 하나 붙일 때마다 남이 쓴 글이 들어오는 문이 하나 늘어난다고 보면 됩니다.

현장에서 만나는 모습

고객이 올린 문서를 요약해 주는 사내 비서가 문서 속 문장대로 메일을 보내는 사고, 웹 페이지 요약 도구가 페이지 속 지시대로 대화 내용을 바깥 주소에 실어 보내는 사고가 대표적입니다. 로그로 보면 늘 모양이 같습니다. 어떤 요청의 프롬프트에 바깥에서 온 글이 들어갔고, 바로 그 요청에서 아무도 시키지 않은 행동이 일어났습니다. 그래서 운영에서는 프롬프트에 무엇이 어디서 들어갔는지(요청 id·조각·출처)를 남겨 두는 것이 조사의 출발점이 됩니다.

조사는 늘 부작용에서 거꾸로 올라갑니다. 나간 메일 한 통에서 요청 id 로, 요청 id 에서 그 요청의 입력 조각으로, 조각에서 그 글의 출처(문서·메모·질문·설정)로. 기록에 조각과 출처가 없으면 이 사슬이 끊겨 「모델이 이상하게 굴었다」 에서 멈춥니다. 그래서 프롬프트 기록은 디버그용 덤프가 아니라 감사 자료로 설계해야 합니다 — 다음 모듈에서 보듯 민감정보는 가린 채로.

그렇다고 필터와 구분 표시를 버리라는 말은 아닙니다. 바깥 글을 「아래는 자료다」 같은 표시로 감싸고, 지시처럼 생긴 문장을 표시해 두면 진짜 모델이 그것을 따를 확률은 대개 줄어듭니다. 다만 줄어든다는 것과 막힌다는 것은 다릅니다. 보조층은 탐지와 경보에 쓰고, 행동의 허락은 늘 서버의 규칙이 합니다.

다음 실습에서 할 것

사내 문서 도우미를 펼쳐 모델이 실제로 받은 입력을 기록에서 읽고, 문서 더미에서 형태가 다른 숨은 지시를 모두 찾고, 공격을 한 번 재현합니다. 그다음 받는 사람 규칙·보류와 확인·서버가 붙이는 출처를 차례로 넣고, 마지막에 세 부서에서 같은 증상(회사 밖으로 나간 메일)이 서로 다른 경로로 난 사건을 가릅니다.