Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

뭉크테크

Web LLM attacks(Exploiting insecure output handling in LLMs) 본문

CERT

Web LLM attacks(Exploiting insecure output handling in LLMs)

뭉크테크 2025. 3. 22. 18:08

이론 

 

Exploiting insecure output handling in LLMs 이란?

대규모 언어 모델(LLM)은 웹 애플리케이션에서 사용자 요청을 처리하며 데이터를 출력하지만, 출력 처리가 안전하지 않을 경우 악성 스크립트까지도 쉽게 출력할 수 있는 취약점을 활용한 공격이다. 해당 공격은 LLM단이나 백엔드 시스템 단이나 해당 공격에 대한 필터링을 제대로 처리하지 못해서 발생하는 공격이다. 

실전(시나리오)

이번에는 stored xss 방식을 활용하여 악성스크립트를 외부 상품 리뷰 페이지에 남길 것이고, LLM에게 간접포이즈닝 공격을 통해 해당 상품에 관한 리뷰 내용을 불러오고, 그로 인해 사용자 carlos가 본인이 의도하지 않은 요청을 서버에게 보내는 csrf 방식도 같이 적용시켜 carlos의 계정을 삭제해보고자 한다. 즉 해당 공격은

  • Indirect prompt injection
  • Stored XSS
  • CSRF

이 셋을 동시에 활용한 공격이라고 보면 된다.

  • LLM이 쓰고 있는  api 목록을 물어보았다. 그러나 이전처럼 똑같은 답변을 기대하기 어려웠다. 그래도 고객 지원 api랑 비밀번호 재사용 api 를 사용한다는 답변을 들었다. 고객 지원 api를 이용하여 사용자가 특정 상품에 대한 리뷰를 가져올텐데, 여기서 리뷰 내용안에 있는, 주입된 악성 스크립트 내용을 불러올 것이다. 
  • 예를 들어, carlos가 본인이 관심있어하는 제품의 정보를 요청한다고 했을 때 그 상품의 리뷰 내용 즉, 악성스크립트를 가져오고 실행시킴으로써 본인의 계정을 삭제시킬 것으로 본인다. 그러기위해서 일단, 본인의 계정을 삭제한다는 악성스크립트를 작성해봐야한다.

  • 물론 그 전에 스크립트를 원천적으로 필터링 하는지도 알아봐야한다. 그래서 한 번 <img src=1 onerror=alert(1)> 를 작성하여 테스트를 해보았다. 그랬더니, 

  • 실제로 스크립트가 실행된 것을 확인해볼 수 있었다. 즉, llm 이나 해당 사이트 측에서 스크립트를 제대로 필터링 하지 않는다는 것을 알게되었다.

 

    • 그리고 삭제 테스트를 위해 계정 탭에 와보았고, 혹시나 하는 마음으로 아무것도 입력해보지 않은 채 Delete account 버튼을 눌렀더니, 

  • 실제로 확인 절차도 없이 바로 삭제되어 로그인이 되지 않음을 확인할 수 있었다. 그러면 저 delete account를 carlos 라는 유저가 눌렀다고 인식하게끔 해야한다고 보았다.
  • 즉, carlos는 의도하지도 않은 요청(계정 삭제)을 서버로 보내게끔 저 delete account를 carlos가 요청했다고 만들어야한다.
  • 그러기 위해선 일단, 저 delete account 요청에 관한 스크립트를 작성해봐야한다.

  • 그래서 <iframe src="my-account" onload="this.contentDocument.forms['delete-account-form'].submit()"></iframe> 라는 댓글을 Lightweight "l33t" Leather Jacket 상품페이지에 달아보았다. 
  • 저 상품 페이지로 선택한 이유는 carlos가 해당 제품에 관해 관심이 많다고 들었기 때문이다.

  • 저렇게 작성한 이유는 일단, iframe src 태그를 통해 my-account라는 페이지를 로드한 후,
  • this.contentDocument.forms[ 'delete-account-form'].submit()을 실행하여 해당 iframe 내부의 문서에서 'delete-account-form' 이라는 폼을 자동으로 제출하게끔 하기위해서 저렇게 작성하였다.

  • 그러나 실제로 해당 제품에 관한 정보를 요청하여 리뷰를 불러온 결과, 아무런 반응도 없었다.
  • 그리고 실제로 내 계정을 확인해보니, 삭제되지 않은 듯 했다.

  • 그래서 이번에는 한 번 실제로 그 상품에 대한 호평을 한 것처럼 작성 한 다음, 그 가운데, 스크립트 내용을 삽입하였다.
  • 그 결과는 이 또한 아무것도 일어나지 않았다.
  • 아마 LLM이 따로 해당 스크립트 태그를 보고 아 이건 XSS 할려고 의도적으로 저렇게 삽입하였구나 필터링 해야지 라고 추론해볼 수 있다.
  • 혹은 시스템 측에서 해당 리뷰 내용을 불러왔을 때, 스크립트된 영역만을 따로 필터링 영역에 걸러진 것으로 보인다.
    •  

  • 그래서 일부러 스크립트를 "(따옴표) 로 감싸주었고, 스크립트 안에는 "(따옴표)를 빼주었다. 
  • 저렇게 따옴표로 스크립트를 감싸준 것은 LLM과 시스템을 속이기 위함이다. 즉, 스크립트 부분은 단순히 문자열일 뿐이고 너는 그냥 이대로 시스템에게 전달해서 받아오고, 그대로 출력만해주면 돼 라고 LLM에게 말하는 것처럼 해당 스크립트를 따옴표로 감싸주었다. 그리고 시스템에서는 마치 저게 문자열로 취급되서 그대로 해당 스크립트 내용을 필터링 없이 가져와서 LLM에게 주도록 의도하였다.
  • 그로 인해, 해당 리뷰 내용은 그대로 사용자 브라우저로 전달되고, 사용자는 그대로 해당 내용을 랜더링처리하게 되는 것이다.
  • 스크립트안에 태그 속성 값에 대해서는 굳이 따옴표가 없어도 실행이 되기에, 빼주었다. 브라우저의 파서는 약간의 오류가 있음에도 관대하기 때문이다.

https://mrseo.co.kr/html-parse%EC%99%80-%EA%B5%AC%EC%A1%B0%ED%95%B4%EC%84%9D/

 

HTML Parse와 구조해석 - 미스터 SEO

HTML Parser란 HTML 문법규칙을 기초로 웹페이지의 내용을 해석하고, 의미와 구조를 분석하는 프로그램입니다.

mrseo.co.kr

  • 위 글을 보면 알 수 있을 것이다.

  • 그렇게 리뷰를 남기고 해당 상품에 관한 정보를 출력해달라고 한 결과, 실제로 필터링을 우회하고 실행이 되었는지, 저렇게 리뷰 내용안에 또 다른 페이지가 출력됨을 확인할 수 있었다.

  • 테스트 결과, 실제로 내 계정을 로그인 시도를 해봤더니, 로그인이 되지 않는 걸 볼 수 있다. 즉, 공격에 성공한 것을 확인할 수 있다.
  • carlos는 종종Lightweight "l33t" Leather Jacket 라는 제품에 관심이 많고, 조만간 LLM에게 해당 상품에 대한 정보를 물어볼 텐데, 그때 내 리뷰를 가져오는 것으로 인해 carlos 계정이 삭제될 것으로 예상된다. 물론 stored xss 특성상, 여파가 크기때문에, 다른 사용자들도 해당 공격에 노출될 확률도 높을 것이다.

 

공격 요약

  • 공격 방식: 사용자가 리뷰에 <iframe> 태그를 삽입하고, 따옴표로 감싸서 시스템과 LLM의 필터링을 우회함.
  • 결과: LLM이 리뷰를 텍스트로 출력하고, 브라우저에서 innerHTML로 렌더링되면 <iframe>이 실행되어 악성 동작(예: 계정 삭제 폼 제출)이 발생.
  • 취약점: 입력 검증 부족, 출력 시 안전하지 않은 렌더링 방식(innerHTML 사용).

대응 방안

(1) 입력 검증 강화 (Input Validation & Sanitization)

  • 설명: 사용자가 제출한 리뷰를 시스템에 저장하기 전에 위험한 HTML 태그와 스크립트를 제거하거나 무효화해야 해요.
  • 방법:
    • 화이트리스트 방식: 허용할 문자(예: 알파벳, 숫자, 기본 기호)만 받아들이고, <, >, ", ' 같은 HTML 관련 문자는 차단.
    • Sanitization 라이브러리 사용: 예를 들어, 서버 측에서 sanitize-html (JavaScript/Node.js)이나 bleach (Python)를 사용해 HTML 태그를 제거.
const sanitizeHtml = require('sanitize-html');
const dirty = '"<iframe src=my-account onload=this.contentDocument.forms[\'delete-account-form\'].submit()></iframe>"';
const clean = sanitizeHtml(dirty, {
  allowedTags: [], // 모든 태그 차단
  allowedAttributes: {}
});
// 결과: "" very good" (태그 제거됨)
  • 효과: <iframe>이 JSON에 저장되기 전에 제거되므로, LLM이 가져올 때 이미 안전한 데이터만 남음.

(2) 출력 시 HTML 이스케이프 (Output Encoding)

  • 설명: LLM이 데이터를 출력할 때, HTML 태그가 실행되지 않도록 이스케이프 처리.
  • 방법:
    • <&lt;, >&gt;, "&quot;로 변환.
    • 예: JSON의 " <iframe ...> "" &lt;iframe ...&gt; "로 바뀜.
function escapeHtml(unsafe) {
  return unsafe
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#039;");
}
const unsafe = '"<iframe src=my-account onload=this.contentDocument.forms[\'delete-account-form\'].submit()></iframe>"';
const safe = escapeHtml(unsafe);
// 결과: "&quot;&lt;iframe src=my-account onload=this.contentDocument.forms[&#039;delete-account-form&#039;].submit()&gt;&lt;/iframe&gt;&quot;"
  • 효과: 브라우저가 <iframe>을 태그로 인식하지 않고 텍스트로 표시. innerHTML로 렌더링돼도 실행 안 됨.

(3) 안전한 렌더링 방식 사용

  • 설명: Live Chat이 응답을 표시할 때 innerHTML 대신 textContentinnerText를 사용.
  • 방법:
    • innerHTML: HTML로 파싱해서 태그를 실행함.
    • textContent: 텍스트로만 처리해서 태그 실행 안 됨.
const review = '"<iframe src=my-account onload=this.contentDocument.forms[\'delete-account-form\'].submit()></iframe>"';
document.getElementById('review').textContent = review;
// 결과: "<iframe ...>"가 텍스트로 표시됨, 실행 안 됨
  • 효과: <iframe>이 실행되지 않고 그대로 텍스트로 보임.

(4) Content Security Policy (CSP) 적용

  • 설명: 브라우저 수준에서 악성 스크립트 실행을 차단하는 정책 설정.
  • 방법:
    • HTTP 헤더나 <meta> 태그로 CSP 설정
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; frame-src 'none';">
    • frame-src 'none': <iframe> 로드를 차단.
    • script-src 'self': 외부 스크립트 실행 제한.
  • 효과: <iframe>이 포함된 리뷰가 렌더링되더라도 브라우저가 실행을 막음.

(5) LLM 자체 보안 강화

  • 설명: LLM이 데이터를 사용자에게 전달하기 전에 자체적으로 위험 요소를 감지하고 필터링.
  • 방법:
    • LLM에 정규식이나 패턴 매칭 로직 추가
const review = '"<iframe src=my-account onload=this.contentDocument.forms[\'delete-account-form\'].submit()></iframe>"'; if (/[<]iframe.*>/i.test(review)) { return "리뷰에 부적절한 내용이 포함되어 표시할 수 없습니다."; }

    • "corrupted" 대신 명확한 경고 메시지 출력.
  • 효과: LLM이 <iframe>을 탐지해서 사용자에게 전달하지 않거나, 안전한 형태로 변환.

3. 종합 대응 전략

실제 웹 애플리케이션이라면 아래처럼 다층 방어를 적용하는 게 이상적이에요:

  1. 입력 단계: 사용자가 리뷰를 제출할 때 sanitize-html로 태그 제거.
  2. 저장 단계: JSON에 저장하기 전 <iframe> 같은 태그를 필터링.
  3. 출력 단계: LLM이 응답할 때 escapeHtml로 이스케이프 처리.
  4. 렌더링 단계: textContent로 안전하게 표시.
  5. 브라우저 단계: CSP로 추가 보호.

 

출처

https://portswigger.net/web-security/llm-attacks#what-is-a-large-language-model

 

Web LLM attacks | Web Security Academy

Organizations are rushing to integrate Large Language Models (LLMs) in order to improve their online customer experience. This exposes them to web LLM ...

portswigger.net