
CVE-2022-22629 Proof of Concept
이 글은 Safari 15.4 보안 업데이트에서 패치된 WebGL 버그의 PoC에 관한 것입니다.

WebGL이 어떻게 작동하는지 자세히 배우고 싶다면 이 공식 리소스를 사용할 수 있습니다. 여기서는 버그 관련 세부 사항에 대해 논의할 것이기 때문입니다:
https://webglfundamentals.org/webgl/lessons/webgl-how-it-works.html
이 PoC는 이 버그를 악용하기 위한 프리미티브를 얻는 방법에 관한 것이 아닙니다. 단지 취약점 분석과 웹 콘텐츠를 충돌시키는 방법에 대해 논의할 것입니다 : )
WebGL은 브라우저에서 2D 및 3D 그래픽을 렌더링하는 데 사용되는 JavaScript API입니다. ANGLE 구성 요소를 기본 WebGL 백엔드로 사용하여 다른 브라우저와의 호환성을 크게 개선하고 WebKit에서 적합한 WebGL 2.0을 가능하게 합니다. WebGL은 개발자가 최적화된 렌더링을 수행할 수 있도록 많은 확장 기능을 제공합니다. 따라서 우리는 관심 있는 확장 기능을 살펴보겠습니다.

따라서 이 API는 다양한 목적을 위한 여러 확장 기능을 제공합니다. 우리가 논의할 버그는 EXT_에 있습니다.
확장 기능에 대한 자세한 내용은 여기에서 읽을 수 있습니다:
https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Using_Extensions
어쨌든, 버그로 넘어가 봅시다 :)
WEBGL_multi_draw 확장 기능은 WebGL API의 일부이며 단일 함수 호출로 여러 프리미티브를 렌더링할 수 있게 합니다. 이는 렌더러의 바인딩 비용을 줄이고 균일한 데이터로 GPU 스레드 시간을 가속화하여 WebGL 애플리케이션의 성능을 향상시킬 수 있습니다.
이 확장 기능은 OpenGL용
ARB_shader_draw_parameters에서 노출된 버텍스 셰이더 내장 변수gl_DrawID외에도EXT_multi_draw_arrays기능의Multi*드로우 콜 변형을 노출합니다.이 함수들은 표준 함수
DrawArrays()및DrawElements()와 동일하게 동작하지만, 한 번의 호출로 여러 개의 버텍스 리스트를 처리합니다. 주요 목적은 한 번의 함수 호출로 삼각형 스트립, 삼각형 팬 등과 같은 여러 프리미티브를 렌더링하는 것입니다.또한 이 확장 기능은 셰이딩 언어에 추가 내장 변수인
gl_DrawID를 추가합니다. 이 변수는 드로잉 명령의Multi*변형에 의해 현재 처리 중인 드로우의 인덱스를 포함합니다.
확장 기능에 대한 자세한 내용은 여기에서 읽을 수 있습니다:
https://registry.khronos.org/webgl/extensions/WEBGL_multi_draw/
이 확장 기능은 배열 데이터에서 프리미티브를 렌더링하는 WebGL API WebGLRenderingContext.drawArrays() 및 WebGLRenderingContext.drawElements() 등과 유사합니다. 유일한 차이점은 이 확장 기능을 사용하여 여러 배열을 사용하여 렌더링할 수 있다는 것입니다.
이 확장 기능을 사용하여 다음 작업을 수행할 수 있습니다: [1]
ext.multiDrawArraysWEBGL()는 배열 데이터에서 여러 프리미티브를 렌더링합니다 (drawArrays를 여러 번 호출하는 것과 동일).
ext.multiDrawElementsWEBGL()는 요소 배열 데이터에서 여러 프리미티브를 렌더링합니다 (drawElements를 여러 번 호출하는 것과 동일).
ext.multiDrawArraysInstancedWEBGL()는 배열 데이터에서 여러 프리미티브를 렌더링합니다 (drawArraysInstanced를 여러 번 호출하는 것과 동일).
ext.multiDrawElementsInstancedWEBGL()는 요소 배열 데이터에서 여러 프리미티브를 렌더링합니다 (drawElementsInstanced를 여러 번 호출하는 것과 동일).

https://github.com/WebKit/WebKit/commit/c087ca07c9125093ca62e8e44024b738bb7d46dc
이 취약점은 Safari 15.0부터 15.3까지만 존재합니다. Apple의 경우 이 확장 기능이 Safari 15(iOS)에서만 지원되기 때문입니다.
패치는 이해하기 매우 쉽고 렌더링 중 힙 버퍼 오버플로우임을 명확히 명시하고 있습니다. 이 함수는 위에서 언급한 모든 작업 [1]에서 WebGLMultiDraw.cpp의 여기에서 호출됩니다. 이 함수가 호출되기 전에 어떤 일이 발생하는지 이해해 봅시다. 위 함수 중 하나의 구현을 살펴보겠습니다:
void WebGLMultiDraw::multiDrawArraysWEBGL(GCGLenum mode, Int32List firstsList, GCGLuint firstsOffset, Int32List countsList, GCGLuint countsOffset, GCGLsizei drawcount)
{
if (!m_context || m_context->isContextLost())
return;
if (!validateDrawcount("multiDrawArraysWEBGL", drawcount)
|| !validateOffset("multiDrawArraysWEBGL", "firstsOffset out of bounds", firstsList.length(), firstsOffset, drawcount)
|| !validateOffset("multiDrawArraysWEBGL", "countsOffset out of bounds", countsList.length(), countsOffset, drawcount)) {
return;
}
m_context->graphicsContextGL()->multiDrawArraysANGLE(mode, makeSpanWithOffset(firstsList, firstsOffset), makeSpanWithOffset(countsList, countsOffset), drawcount);
}
JS에서 함수 사양은 다음과 같이 정의됩니다:
void ext.multiDrawArraysWEBGL(mode,
firstsList, firstsOffset,
countsList, countsOffset,
drawCount);
mode:
gl.POINTS: 단일 점을 그립니다.gl.TRIANGLES: 세 개의 꼭짓점 그룹에 대해 삼각형을 그립니다.firstsList:
firstsOffset:
countsList:
countsOffset:
drawCount:
구현을 살펴보면 오프셋과 drawcount를 제어하여 버퍼 크기를 초과하여 그리기 모드로 그릴 수 있음을 알 수 있습니다 : )
따라서 PoC는 여기에 있습니다 ...

보시다시피, multiDrawArraysWEBGL()에서 호출된 validateOffset()이 firstsOffset이 [0, size-drawcount] 범위 내에 있는지 확인하지 않기 때문에 웹페이지가 충돌했습니다. 그리고 실제 버퍼 크기를 초과하여 그리고 있습니다. 함수 호출 m_context->graphicsContextGL()->multiDrawArraysANGLE()은 ANGLE로 알려진 WebKit의 타사 구성 요소에 구현되어 있으며 렌더링 전에 검사가 없으므로 메모리 영역에 잘못된 값을 쓰면서 충돌이 발생합니다.
참고로, multiDrawArraysANGLE() 의사 코드는 다음과 같이 작동합니다:
for(i=0; i<drawcount; i++) {
if (*(counts+i)>0) DrawArrays(mode, *(firsts+i), *(counts+i));
}
그리고 패치가 적용되면 validateOffset() 함수 호출을 통해 아무것도 렌더링하지 않고 false를 반환합니다:

js) ...
PARS Defense Team
새 팀원을 모집 중입니다. 연락처: info[at]parsdefense.com