XSS via reportError

reportError란 함수를 아시나요? Chrome 95, Firefox 93 버전에 추가된 글로벌 메소드로 JS의 uncaught exception을 콘솔이나 글로벌 이벤트 핸들러로 넘겨주는 기능을 수행합니다. PortSwigger 측에서 reportError 함수를 이용한 트릭을 공개했고 잠깐 시간내어 살펴본 내용 공유드려봅니다.

window.name = '=alert(1)'
reportError(name, onerror=eval)

What is reportError

서론에 이야기했듯이 reportError()는 JS의 uncaught exception 핸들링하기 위해 이를 에뮬레이트 하는 메소드입니다. 현재 모바일을 포함한 모던 브라우저에선 모두 지원하고 있습니다.

브라우저 별 지원 현황 / 2023.10.13 기준

해당 함수의 인자값은 error 타입들을 받습니다. (예를들어 TypeError)

reportError(throwable)
// reportError('Hi')

Analysis

reportError for XSS

먼저 PortSwigger가 공개한 코드를 살펴봅시다. window.name에 payload를 넣어두고, reportError를 통해 트리거하는 방식입니다.

window.name = '=alert(1)'
reportError(name, onerror=eval)

마치 2개의 arguments를 받은 것 처럼 보이지만, 위에서 잠깐 살펴봤지만 실제 reportError는 1개의 Argument를 받도록 선언되어 있습니다. 그래서 실질적으로 두번째 인자값은 전달되지 않습니다. 다만 두번째 인자값으로 쓰인 onerror=eval은 에러에 대한 이벤트 핸들러를 eval로 대체한 inline assignment 같은 코드입니다.

결과적으로 풀어서 보면 아래와 같습니다.

  • window.name에는 payload를 설정함
  • onerror 이벤트 핸들러를 eval로 정의
  • reportError를 통해 error 데이터를 글로벌 이벤트 핸들러로 전송
  • 에러 데이터가 eval을 통해 실행되기 떄문에 결과적으로 eval(window.name) 과 유사한 형태의 구조를 띄게됨

Uncaught Message

reportError 함수는 Error를 에뮬레이트하며 Argument로 받은 값이 Error 메시지에 직접 포함됩니다.

reportError("hahwul")
// VM1012:1 Uncaught hahwul

이 때 메시지는 Uncaught <message> 형태로 굉장히 단순합니다. 그래서 eval로 넘길 때 Javascript 문법을 유지하면서 넘기기 유용하기 떄문에 XSS Trick으로 만들어질 수 있었던 것 같습니다. payload를 삽입할 때 =alert() 과 같이 =을 앞에 작성하여 이러한 문자열을 만들 수 있습니다.

결국 reportError를 통해 Error를 만들어서 넘기는 경우 메시지는 Uncaught =alert()이 되며 해당 메시지가 Eval로 들어가는 경우 Uncaught 란 변수에 alert() 이란 함수의 결과를 넣어주는 코드로 변환되고 결과적으로 payload가 실행되게 됩니다.

var abcd = '=alert(1)'
reportError(abcd, onerror=eval)

// eval('Uncaught =alert(1)')
// Uncaught =alert(1)

이러한 방법은 JS 문법 오류를 지우기 위한 좋은 방법 중 하나죠 :D

Conclusion

위와 같은 페이로드가 많이 사용될까? 라고 생각해보면 흔하진 않을 것 같습니다. 위와 동일한 조건일 때 a=eval; a('alert(1)') 같이 더 단순한 형태가 더 실용적일거라 실제로 reportError를 사용할 일이 있을까 싶긴 하네요. 그래도 여러 제약 조건이 걸린 상황에서 이를 타파할 좋은 요소이기 때문에 숙지해두시면 언젠가는 도움을 줄거라고 생각합니다 :D

References