일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- Kernel
- kernel img
- cwe-506
- kernel build
- 백도어
- newbie
- rootfs
- CVE-2024-3094
- liblzma
- xz-utils
- kernel image
- Today
- Total
ZZoMb1E
[Fuzzing 101] LibXML2 (CVE-2017-9048) 본문
※ 자료들을 참고하여 분석을 진행하였기에 잘못된 부분이 있을지도 모릅니다.
※ 보완 혹은 수정해야 되는 부분이 있다면 알려주시면 확인 후 조치하도록 하겠습니다.
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 취약점을 예방할 수 있다.
'STUDY > CVE && Fuzzing' 카테고리의 다른 글
[CVE] sshd (CVE-2024-6387) - 비공개 (0) | 2024.10.10 |
---|---|
[CVE] liblzma.so (CVE-2024-3094) (6) | 2024.10.10 |
[Fuzzing 101] libtiff (CVE-2016-9297) (1) | 2024.06.01 |
[Fuzzing 101] TCPdump (CVE-2017-13028) (0) | 2024.01.19 |
[Fuzzing 101] libexif (CVE-2009-3895) (0) | 2024.01.16 |