1. 분석 대상
Libxml2란?
XML 문서 파싱을 위한 소프트웨어 라이브러리로, 기본적인 라이브러리에 기반하여 만들어져 휴대성이 좋고 스타일시트 처리, xmlint, HTML parser 도 포함하고 있다.
Main Topic
- Dictionaries
- Basic parallelization
- Fuzzing command-line arguments
2. CVE Code
CVE-2017-9048
- libxml2 20904-GITv2.9.4-16-g0741801 버전에서 xmlSnprintfElementContent 함수가 strlen(buf) + 2 < size 인지 검증하는 루틴이 생략되어 Stack Buffer Overflow를 일으키는 취약점
3. CVE 관련 정보
Reference
https://nvd.nist.gov/vuln/detail/CVE-2017-9048
4. 분석 환경
Ubuntu 20.04 환경에서 진행
- Libxml 2.9.4 Download
wget http://xmlsoft.org/download/libxml2-2.9.4.tar.gz
tar -xzvf libxml2-2.9.4.tar.gz
LibXml Build
sudo apt-get install python-dev
cd libxml2-2.9.4
CC=afl-clang-lto CXX=afl-clang-lto++ CFLAGS="-fsanitize=address" CXXFLAGS="-fsanitize=address" LDFLAGS="-fsanitize=address" ./configure --prefix="$HOME/fuzzing_libxml/libxml2-2.9.4/install/" --disable-shared --without-debug --without-ftp --without-http --without-legacy --without-python LIBS='-ldl'
make
make install
빌드가 오래 걸리는 점 유의하자.
빌드 확인
root@7216a6b36080:~/fuzzing_libxml/libxml2-2.9.4# ./xmllint --memory ./test/wml.xml
<?xml version="1.0"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://www.wapforum.org/DTD/wml_1.1.xml">
<wml>
<card id="card1" title="Rubriques 75008">
<p>
<a href="rubmenu.asp?CP=75008&RB=01">Cinéma</a><br/>
</p>
</card>
</wml>
Example File Download
mkdir afl_in && cd afl_in
wget https://raw.githubusercontent.com/antonio-morales/Fuzzing101/main/Exercise%205/SampleInput.xml
cd ..
Fuzzing101 github 에서 제공 해주는 예제 파일이다.
Custom Dictionary Download
mkdir dictionaries && cd dictionaries
wget https://raw.githubusercontent.com/AFLplusplus/AFLplusplus/stable/dictionaries/xml.dict
cd ..
각종 딕셔너리 들은 AFL github 에서 제공 해주고 있다. 여기서 딕셔너리 파일은 복잡한 텍스트 형식 파일을 Fuzzer로 돌릴 때 문자열 치환과 길이 보정 등 다양한 퍼징에 도움이 되는 내용을 포함하고 있다.
Slave Fuzzer
XML과 같이 복잡한 파일을 퍼징할 때, 하나의 퍼저만 돌리기에 시간이 굉장히 오래걸릴 수 있다. 또, 시드값을 다르게 주고 퍼저를 돌리고 싶을 수가 있다. 그 때 사용되는 방법이 Master, Slave 옵션이다.
./afl-fuzz -i afl_in -o afl_out -M Master -- ./program @@ # Master Fuzzer
./afl-fuzz -i afl_in -o afl_out -S Slave1 -- ./program @@
./afl-fuzz -i afl_in -o afl_out -S Slave2 -- ./program @@
...
./afl-fuzz -i afl_in -o afl_out -S SlaveN -- ./program @@ # Slave Fuzzer
이 때 마스터와 각 슬레이브 개체들 간의 시드값은 다 달라야한다. (시드를 정하지 않은 경우 상관없다.)
Fuzzing
afl-fuzz -m none -i ./afl_in -o afl_out -s 123 -x ./dictionaries/xml.dict -D -M master -- ./xmllint --memory --noenc --nocdata --dtdattr --loaddtd --valid --xinclude @@
- -x
사용할 Dictionary 파일 경로 설정 - -D
결정론적 Mutations 활성화 옵션
Slave Instance
afl-fuzz -m none -i ./afl_in -o afl_out -s 234 -S slave1 -- ./xmllint --memory --noenc --nocdata --dtdattr --loaddtd --valid --xinclude @@
5. 루트 커즈 분석
Fuzzing을 24시간 이상 돌려도 Crash를 찾을 수 없어 Github에 올라와 있는 PoC 예제 파일을 사용하여 분석을 진행한다.
Triger
root@7216a6b36080:~/fuzzing_libxml/libxml2-2.9.4# ./xmllint --memory --noenc --nocdata --dtdattr --loaddtd --valid --xinclude './afl_out/crash.xml'
=================================================================
==45==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffe875250e8 at pc 0x00000036a239 bp 0x7ffe87523c90 sp 0x7ffe87523430
WRITE of size 2001 at 0x7ffe875250e8 thread T0
#0 0x36a238 in strcat (/root/fuzzing_libxml/libxml2-2.9.4/xmllint+0x36a238)
#1 0x591b92 in xmlSnprintfElementContent /root/fuzzing_libxml/libxml2-2.9.4/valid.c:1279:3
#2 0x5c7d57 in xmlValidateElementContent /root/fuzzing_libxml/libxml2-2.9.4/valid.c:5445:6
#3 0x5c7d57 in xmlValidateOneElement /root/fuzzing_libxml/libxml2-2.9.4/valid.c:6152:12
#4 0xa451ab in xmlSAX2EndElementNs /root/fuzzing_libxml/libxml2-2.9.4/SAX2.c:2467:24
#5 0x4c45d7 in xmlParseElement /root/fuzzing_libxml/libxml2-2.9.4/parser.c:10203:3
#6 0x4ed648 in xmlParseDocument /root/fuzzing_libxml/libxml2-2.9.4/parser.c:10953:2
#7 0x51b774 in xmlDoRead /root/fuzzing_libxml/libxml2-2.9.4/parser.c:15432:5
#8 0x3cf51f in xmlReadMemory /root/fuzzing_libxml/libxml2-2.9.4/parser.c:15518:13
#9 0x3cf51f in parseAndPrintFile /root/fuzzing_libxml/libxml2-2.9.4/xmllint.c:2371:9
#10 0x3be5ab in main /root/fuzzing_libxml/libxml2-2.9.4/xmllint.c:3767:7
#11 0x7f0d8a9ae082 in __libc_start_main /build/glibc-BHL3KM/glibc-2.31/csu/../csu/libc-start.c:308:16
#12 0x304e9d in _start (/root/fuzzing_libxml/libxml2-2.9.4/xmllint+0x304e9d)
Address 0x7ffe875250e8 is located in stack of thread T0 at offset 5128 in frame
#0 0x5c12df in xmlValidateOneElement /root/fuzzing_libxml/libxml2-2.9.4/valid.c:5943
valid.c 파일에서 xmlSnprintfElementContent 함수 진행 도중 Stack Buffer Overflow가 일어난 것을 확인할 수 있다.
코드 분석
valid.c 파일을 먼저 보자
void
xmlSnprintfElementContent(char *buf, int size, xmlElementContentPtr content, int englob) {
int len;
if (content == NULL) return;
len = strlen(buf);
if (size - len < 50) {
if ((size - len > 4) && (buf[len - 1] != '.'))
strcat(buf, " ...");
return;
}
if (englob) strcat(buf, "(");
switch (content->type) {
case XML_ELEMENT_CONTENT_PCDATA:
strcat(buf, "#PCDATA");
break;
case XML_ELEMENT_CONTENT_ELEMENT:
if (content->prefix != NULL) {
if (size - len < xmlStrlen(content->prefix) + 10) {
strcat(buf, " ...");
return;
}
문제의 xmlSnprintfElementContent 함수의 시작 부분이다. content와 입력할 buf의 주소를 받고 content가 NULL인지, buf가 이미 차있는 상태인지 검증하고 content의 타입에 따라 함수가 계속 진행된다.
strcat(buf, (char *) content->prefix);
strcat(buf, ":");
}
if (size - len < xmlStrlen(content->name) + 10) {
strcat(buf, " ...");
return;
}
if (content->name != NULL)
strcat(buf, (char *) content->name);
break;
buf의 남은 길이가 content->name의 길이 + 10 보다 작을 경우 name의 값을 buf에 복사해주게 된다. prefix도 동일하게 복사가 진행된다.
#endif /* LIBXML_REGEXP_ENABLED */
if ((warn) && ((ret != 1) && (ret != -3))) {
if (ctxt != NULL) {
char expr[5000];
char list[5000];
expr[0] = 0;
xmlSnprintfElementContent(&expr[0], 5000, cont, 1);
다음은 문자열 복사 함수에 들어가기 전 인자값의 상태이다. expr을 5000 바이트 크기로 생성 후 buf 인자로 넘겨주게 된다.
이때, size-len 은 고정적이게 되고 따라서 각 prefix와 name을 조건문에만 맞게끔 넘기면 큰 크기의 stack buffer overflow가 발생하게 되는것이다.
switch (content->ocur) {
case XML_ELEMENT_CONTENT_ONCE:
break;
case XML_ELEMENT_CONTENT_OPT:
strcat(buf, "?");
break;
case XML_ELEMENT_CONTENT_MULT:
strcat(buf, "*");
break;
case XML_ELEMENT_CONTENT_PLUS:
strcat(buf, "+");
break;
}
또, 이후 진행되는 코드에서 다음과 같이 ocur의 타입에 따라 buf의 길이 검증조차 하지 않고 2byte를 덧붙인다. 이로 인해서 buffer overflow가 발생하게 된다.
6. 패치 파악
case XML_ELEMENT_CONTENT_ELEMENT: {
int qnameLen = xmlStrlen(content->name);
if (content->prefix != NULL)
qnameLen += xmlStrlen(content->prefix) + 1;
if (size - len < qnameLen + 10) {
strcat(buf, " ...");
return;
}
if (content->prefix != NULL) {
strcat(buf, (char *) content->prefix);
strcat(buf, ":");
}
if (content->name != NULL)
strcat(buf, (char *) content->name);
break;
}
위는 다음 버전에서 가져온 코드이다. content name의 길이와 prefix의 길이를 병합하여 buf의 길이를 넘지 않았는지의 관한 검증 루틴이 추가 되었다.
if (size - strlen(buf) <= 2) return;
if (englob)
strcat(buf, ")");
switch (content->ocur) {
case XML_ELEMENT_CONTENT_ONCE:
break;
case XML_ELEMENT_CONTENT_OPT:
strcat(buf, "?");
break;
case XML_ELEMENT_CONTENT_MULT:
strcat(buf, "*");
break;
case XML_ELEMENT_CONTENT_PLUS:
strcat(buf, "+");
break;
}
마찬가지로 그 다음 코드 구문에서는 버퍼의 남은 길이가 2byte 이하일 경우 아무것도 하지않고 return하게 된다.
정리
- DTD 타입의 XML 문서가 prefix와 name의 길이를 각각 버퍼의 길이보다 작게 하여 검증 루틴만 통과한다면 큰 크기의 Stack Buffer Overflow가 발생하는 취약점이 존재 (이는 제목의 CVE와 다르게 CVE-2017-9047 이다.)
- 이후 buf의 남은 길이를 검증하는 루틴 없이 2byte를 추가하는 코드가 존재하여 버퍼 오버플로우가 발생할 수 있는 취약점 (CVE-2017-9048)
- 취약점은 prefix와 name의 길이를 병합하여 검증하는 루틴이 추가되고, 이후 남은 buf의 길이가 2byte 이하이면 아무 행동도 취하지 않는 코드를 추가하여 패치되었다.
'Fuzzing' 카테고리의 다른 글
[Fuzzing101] 4. LibTIFF (CVE-2016-9297) (1) | 2024.01.02 |
---|---|
[Fuzzing101] 3. TCPdump (CVE-2017-13028) (1) | 2023.12.26 |
[Fuzzing101] 2. libexif (CVE-2009-3895) (0) | 2023.11.07 |
[Fuzzing101] 1. Xpdf (CVE-2019-13288) (1) | 2023.10.30 |