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 cache poisoning attack(exploit cookie-handling vulnerabilities) 본문

CERT

Web cache poisoning attack(exploit cookie-handling vulnerabilities)

뭉크테크 2025. 4. 3. 01:15

웹 캐시가 뭔가요?

먼저, 캐시(cache)가 뭔지 알아야 함. 캐시는 웹사이트가 더 빨리 로드되도록 도와주는 임시 저장소. 예를 들어, 네가 어떤 블로그 글을 보러 가면, 서버가 매번 그 페이지를 새로 만들지 않고 캐시에 저장된 복사본을 보여주는 것. 이게 시간을 절약해줘서 빠르게 볼 수 있는 것.

 

웹 캐시 포이즈닝이 뭐예요?

웹 캐시 포이즈닝은 이 캐시를 악용하는 공격. 공격자가 캐시에 잘못된 내용이나 악성 콘텐츠를 저장하게 만들어서, 그 다음에 그 페이지를 보는 모든 사용자에게 엉뚱한 걸 보여주게 함. 비유하자면, 식당 주방에 몰래 들어가서 재료를 바꿔놓는 것과 비슷함. 손님들이 주문하면 원래 먹으려던 음식이 아니라 이상한 음식이 나오는 셈.

 

쿠키는 뭐고, 어떻게 연관되나요?

**쿠키(cookie)**는 웹사이트가 너에 대한 정보를 기억하기 위해 브라우저에 저장하는 작은 데이터. 예를 들어,  당신이 블로그에서 "언어를 폴란드어로 보고 싶어"라고 설정하면, language=pl 같은 쿠키가 생김. 그러면 서버는 그 쿠키를 보고 폴란드어 버전의 페이지를 보여주는 것.

이제 문제의 예시를 보자:
GET /blog/post.php?mobile=1 HTTP/1.1
Host: innocent-website.com
User-Agent: Mozilla/5.0 Firefox/57.0
Cookie: language=pl;
Connection: close

여기서 사용자가 블로그 글을 요청하면서 "폴란드어로 보고 싶다"는 쿠키(language=pl)를 보낸다고 가정. 서버는 이 요청을 보고 폴란드어 페이지를 만들어서 돌려줬고, 이 응답이 캐시에 저장됐다고 가정.

 

쿠키 처리 취약점이 왜 생기나요?

여기서 중요한 건, 캐시가 어떤 응답을 저장하고 보여줄지 결정할 때 사용하는 캐시 키(cache key). 캐시 키는 요청의 일부만 보고 "이건 같은 요청이야" 라고 판단. 이 예시에서는 캐시 키에 **요청 줄(GET /blog/post.php?mobile=1)**과 **Host 헤더(innocent-website.com)**는 포함되는데, **쿠키 헤더(Cookie: language=pl)**는 포함되지 않는다고 가정했음.

이게 무슨 뜻이냐? 캐시 입장에서는 쿠키에 뭐가 있든 신경 안 씀. 그래서 폴란드어 버전이 캐시에 저장되면, 그 다음에 같은 블로그 글을 보려는 모든 사용자에게 폴란드어 버전을 보여주게 됨. 예를 들어, 내가 영어 버전을 보고 싶어서 Cookie: language=en을 보냈더라도, 캐시는 "어차피 요청 줄과 Host가 같으니 폴란드어 버전 줘야지!" 하면서 잘못된 페이지를 주는 거임.

 

이걸 어떻게 악용하나요?

공격자는 이걸 이용해서 캐시에 악성 콘텐츠를 저장할 수 있음. 예를 들어, 공격자가 이상한 쿠키(예: Cookie: language=hack)를 보내서 서버가 악성 스크립트가 포함된 페이지를 만들게 한다고 해본다고 가정. 그 응답이 캐시에 저장되면, 그 다음부터 그 페이지를 보는 모든 사람이 공격자의 악성 페이지를 보게 되는 거임. 이게 바로 쿠키 기반 웹 캐시 포이즈닝.

 

실제로 흔한가요?

다행히 이런 쿠키 기반 취약점은 헤더 기반 캐시 포이즈닝보다 드둚. 왜냐하면 쿠키는 사용자마다 다를 수 있어서, 일반 사용자들도 실수로 캐시에 이상한 내용을 저장하게 만들 수 있게 됨. 그러면 사이트 운영자가 "어? 페이지가 왜 이래?" 하고 금방 알아채고 고치게 됨. 그래서 발견되면 빠르게 수정되는 편. 

 

정리하자면

  • 웹 캐시 포이즈닝: 캐시에 엉뚱하거나 나쁜 내용을 저장해서 사용자들에게 보여주는 공격.
  • 쿠키 취약점: 캐시가 쿠키를 신경 안 쓰면, 한 사람의 쿠키 설정이 모두에게 영향을 줄 수 있음.
  • 악용 예: 공격자가 이상한 쿠키로 악성 페이지를 캐시에 저장 → 다른 사용자 피해.
  • 현실: 드물고, 발견되면 빨리 고쳐짐.

 

실전 시나리오

 

이번 실전 시나리오는 쿠키 값으로 악성스크립트를 심은 http 요청을 서버에게 전달하여 서버 캐시가 이를 저장하도록 함

실제 공격 테스트를 하기전 실제 http 요청을 어떻게 보내는지 확인해봄. cookie 값으로 fehost 라는 필드의 값이 그대로 응답 헤더의 set cookie 설정 값과 더불어 script 부분에도 삽입된 것도 보임.

 

 

기존 설정된 값이 아닌, 임의 값(1234)을 넣어 테스팅을 해봄. 그 결과, response에서 script에 frontend 라는 키의 값이 임의 값으로 설정됨을 확인. 이를 이용하면, 악성스크립트를 삽입하여 실행시킬 수 있다는 생각을 가짐

 

그래서 처음에는 fshost의 값으로 "} alert(document.cookie) " 라고 넣음으로써 저 data의 문자열 취급과 value 취급으로부터 벗어나고자 했음. 그 결과, 위 그림의 response의 빨간색 박스 친 부분 처럼 나옴

저것이 정상적으로 작동된 건지 확인하고자 사이트를 리로딩 하였지만, 아무런 변화가 없어 해당 스크립트 내용을 비슷하게 필요한 부분만 따와서 실행을 시켜보았음. 그 결과 위 그림과 같이, 구문 오류가 뜸. 결론은 data = { } 를 하고나서 세미콜론(;)을 해야한다는 것과 맨 끝에 } 를 없애야 한다는 것을 알게됨. 

 

그래서 fehost="}; alert(document.cookie); var dummy={" 라고 작성함. 세미콜론을 넣어 data 키의 값으로써 끝났다는 것을 명시하였고, 맨 끝에 { 을 처리하고자 var dummy={" 라는 더미 변수를 삽입하였으나, 애초에 ; 을 넣으면, fshost의 값 취급이 거기서 끝난 다는 것을 알게됨.

 

그래서 이번에는 fehost=", "a" : alert(document.cookie) ";  라고 작성해봄. data안에 있어도, alert()는 실행되는 것을 알게됨. 물론 이 또한 문제가 있음. response에서 frontend의 값 "" 이라는 문자열은 마무리 되었지만, 그 뒤에 alert(document.cookie) 뒤에 있는 "" 문자열은 처리를 못하였음. 결국 저 뒤에 문자열을 처리하는 것이 관건임.

그러다가 그냥 저 문자열을 없앴을 수 없다면 차라리 이용하기로함. 어차피 빈 문자열이라 alert(document.cookie) 에도 합쳐도 상관없기 때문임.

테스팅 결과, - 연산자는 잘 삽입되었지만, + 연산자는 삽입되지 않는다는 것을 확인.

 

 

-alert(document.cookie)- 이라는 값을 fshost의 값으로 넣음. 그 결과, 스크립트에 정상적으로 삽입된 것을 확인해볼 수 있음. 

 

실제로 리로딩을 할때마다, 쿠키값 출력하는 것을 위 그림을 통해 확인 가능함.

 

대응 방안

쿠키 데이터 자체가 캐시에 저장되지 않도록 하려면, Set-Cookie 헤더를 포함한 응답이 캐싱되지 않도록 설정하는 것이 핵심입니다. 이는 앞서 언급한 HTTP 캐시 제어 헤더를 사용하는 방식과 동일하지만, Set-Cookie 헤더와 관련된 캐싱 동작에 더 초점을 맞춰 설명하겠습니다.

Set-Cookie 헤더와 캐싱 방지

HTTP 표준(RFC 7234)에 따르면, Set-Cookie 헤더가 포함된 응답은 기본적으로 캐싱되지 않아야 합니다. 그러나 일부 프록시 캐시나 비표준 구현에서는 이를 무시하고 캐싱할 가능성이 있으므로, 명시적으로 캐싱을 방지하는 헤더를 설정하는 것이 중요합니다.

 

예시 응답 헤더:

HTTP/1.1 200 OK
Set-Cookie: sessionId=abc123; HttpOnly; Secure
Cache-Control: no-store, no-cache
Vary: Cookie
Content-Type: text/html

  • Cache-Control 헤더:
    • Cache-Control: no-store: 응답이 어떤 캐시에도 저장되지 않도록 합니다. Set-Cookie 헤더를 포함한 응답에 이 헤더를 설정하면, 쿠키 데이터가 캐시에 저장될 가능성을 차단합니다.
    • Cache-Control: private: 공유 캐시(프록시 캐시)에는 저장되지 않도록 하지만, 브라우저 캐시에는 저장될 수 있습니다. no-store가 더 강력한 방어책입니다.
    • 예: Cache-Control: no-store, no-cache
  • Vary 헤더:
    • Vary: Cookie를 설정하면, 캐시가 Cookie 헤더를 기준으로 응답을 구분하도록 합니다. 이는 Set-Cookie와 직접 관련은 없지만, 요청 시 Cookie 헤더가 다르면 캐시된 응답을 재사용하지 않도록 보장합니다.
    • 그러나 Vary: Cookie만으로는 Set-Cookie 헤더가 포함된 응답이 캐싱되는 것을 완전히 방지하지 못하므로, Cache-Control: no-store와 함께 사용하는 것이 좋습니다.
 
서버에서 Set-Cookie 헤더를 설정하는 모든 응답에 대해 캐싱 방지를 명시적으로 적용할 수 있습니다.
예시 (Nginx 설정):
server {
    location / {
        if ($http_cookie ~* ".*") {
            add_header Cache-Control "no-store, no-cache";
        }
        if ($sent_http_set_cookie ~* ".*") {
            add_header Cache-Control "no-store, no-cache";
        }
    }
}
 
예시 (Express.js 설정):

app.use((req, res, next) => {
    // Set-Cookie 헤더가 설정된 경우 캐싱 방지
    res.set('Cache-Control', 'no-store, no-cache');
    next();
});

app.get('/setcookie', (req, res) => {
    res.cookie('sessionId', 'abc123', { httpOnly: true, secure: true });
    res.send('Cookie set');
});

 

프록시 캐시와 CDN 고려

프록시 캐시나 CDN이 Set-Cookie 헤더를 포함한 응답을 캐싱하지 않도록 설정해야 합니다:

  • Cache-Control: no-store를 사용하면 대부분의 프록시와 CDN이 캐싱을 하지 않습니다.
  • CDN별로 추가 설정이 필요할 수 있습니다. 예를 들어, Cloudflare에서는 "Cache Level"을 "Bypass"로 설정하거나, 특정 경로에 대해 캐싱을 비활성화할 수 있습니다.

 

추가적인 주의사항

  • 쿠키 데이터 자체는 캐시에 저장되지 않음: Set-Cookie 헤더는 HTTP 헤더에 포함되며, 브라우저 캐시나 프록시 캐시에 직접 저장되는 것은 응답 본문입니다. 하지만 응답 전체가 캐싱되면 Set-Cookie 헤더도 함께 저장될 수 있으므로, no-store 설정이 필수적입니다.
  • 테스트: 설정 후 브라우저 개발자 도구(Network 탭)에서 응답 헤더를 확인하고, 캐시 동작을 테스트하세요. Set-Cookie 헤더가 포함된 응답이 캐시에 저장되지 않는지 확인합니다.
  • 민감한 경로에만 적용: 모든 응답에 no-store를 적용하면 성능이 저하될 수 있으므로, Set-Cookie 헤더를 설정하는 경로(예: /login, /setcookie)에만 적용하는 것이 효율적입니다