반응형
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

[CVE] Curl (CVE-2023-38545) 본문

STUDY/CVE && Fuzzing

[CVE] Curl (CVE-2023-38545)

ZZoMb1E 2023. 11. 16. 16:36
728x90
반응형

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

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

 

1. 분석대상


Curl이란?

프로토콜들을 이용해 URL로 데이터를 전송해 서버에 데이터를 보내거나 가져올때 사용하기 위한 명령어 도구 및 라이브러리

 

 

2. CVE Code


CVE-2023-38545

Curl 8.4.0 이전 버젼에서 호스트 이름이 너무 길 경우 컨테스트 스위칭으로 인해 레지스터 및 TOP Chunk가 변조되는 취약점

 

CWE-787 : Out-of-bounds Write

CVE는 Heap Buffer OverFlow 취약점인데 OOB로 표기된 이유??

  • OOB는 버퍼의 경계를 벗어나게 데이터를 쓰는 것을 의미
    Heap 영영의 경계를 넘게되는 취약점이기 때문에 OOB로 분류

 

 

3. CVE 관련 정보


9.8에 해당하는 높은 등급의 취약점

 

4. 분석  환경 및 구현


Ubuntu22.04를 포함한 버젼에서 사용 가능

Curl은 8.4.0 이전 버젼

통신간 프록시 서버(SOSCKS5를 사용하는)를 통해야함

 

Dockerfile 생성

FROM ubuntu:latest

RUN apt-get update && apt-get install -y \
    git \
    build-essential \
    wget \
    python3 \
    libssl-dev

WORKDIR /build

RUN wget https://github.com/curl/curl/releases/download/curl-7_74_0/curl-7.74.0.tar.gz

RUN tar -xzvf curl-7.74.0.tar.gz

WORKDIR /build/curl-7.74.0

RUN ./configure --with-openssl

RUN make -j$(nproc)

RUN make install

RUN cp -r /usr/local/lib /usr/lib

RUN ldconfig

ENTRYPOINT [ "/bin/bash"]

 

분석 target은 Curl 7.74.0 버젼으로 선정

 

git, make, wget 명령어들을 다루기 위한 패키지 설치

Curl이 OpenSSL 라이브러리를 사용하여 SSL/TLS 기능을 지원하도록 하기 위한 설정

RUN ./configure --with-openssl

 

 

 

위의 내용으로 Dockerfile 생성하였으면 빌드 수행

docker build . -t cve && docker run -it --net="host" -t cve

docker build . -t cve ⇒ Docker 이미지 빌드하고 cve 태그 부여

docker run -it —net=”host” -t cve ⇒ cve태그가 부여된 Docker를 인자로 실행

-net=”host” ⇒ Docker 컨테이너가 호스트의 네트워크를 사용하도록 설정

-t cve ⇒ 실행할 Docker 이미지 지정 ⇒ cve 태그가 붙은 이미지 사용

 

프록시 서버 구현

https://github.com/MisterDaneel/pysoxy

Github에 있는 파일 활용

git clone https://github.com/MisterDaneel/pysoxy

 

 

취약점 실습

curl -vvv -x socks5h://localhost:9050 $(python3 -c "print(('A'*10000), end='')")

-vvv ⇒ 상세한 정보 출력 설정

-x socks5h://localhost:9050 ⇒ 프록시를 사용하여 요청을 보내도록 설정 ( 프록시 : socks5h://localhost:9050 )

 

 

로컬환경에 열린 프록시 서버를 이용하여 Curl 명령어를 활용

 

Segmentation fault 터진 것을 확인이 가능

 

 

5. 루크 커즈 분석


Segementaion fault 이후 상태 파악

backtrace를 통해 Segmentation fault 발생 시점 확인

 

마지막에 Curl_resolver_kill()이 종료시키는 것을 확인

이때 인자로 전달되는 rdi 레지스터가 변조된 것을 확인

 

해당 CVE가 Heap에 대한 취약점이기 때문에 추가적인 확인

TOP Chunk가 OverFlow된 것을 확인

 


아직 Heap 기법에 대한 공부를 진행 중이라 Shell 취득이나 기타 정보에 대한 진행은 못했습니다..


 


취약점 발생하는 코드

해당 CVE는 socks5_resolve_local이라는 bool 형식 변수에 의해 발생하게 된다.

  bool socks5_resolve_local = (conn->socks_proxy.proxytype == CURLPROXY_SOCKS5) ? TRUE : FALSE;

해당 변수의 역할은 [로컬 이름 해결]이라는 방식으로 호스트 이름을 해석할지 말지를 결정하는 역할이다.

  • 로컬 이름 해결이란?
    • hosts파일에 dns와 ip를 입력하면,
      페이지 접속 시 해당 hosts 파일을 참조하여 해당 페이지로 접속되는 것을 볼 수 있다.
    • 위의 예시처럼 로컬 파일을 참조하여 수행하는 것을 [로컬 이름 해결]이라고 한다.

 

/* RFC1928 chapter 5 specifies max 255 chars for domain name in packet */
    if(!socks5_resolve_local && hostname_len > 255) {
      infof(data, "SOCKS5: server resolving disabled for hostnames of "
            "length > 255 [actual len=%zu]", hostname_len);
      socks5_resolve_local = TRUE;
}

socks5_resolve_local의 값이 0이면서 입력한 호스트 이름이 255 바이트보다 큰 경우 위의 코드 실행

=> socks5_resolve_local의 값이 TRUE로 설정

 

해당 변수가 설정되면 [로컬 이름 해결]기능을 설정

  • 아래 과정으로 인해 컨테스트 스위칭 간 취약점 발생
    • 로컬 환경(피해자/피의자)에서는 로컬 모드에서 DNS를 분석
    • 프록시 서버 환경에서는 기존에 실행하던 방식이 중간에 바뀌는 부분이 없기 때문에 기존 방식대로 실행

 

호스트 이름이 너무 긴 경우 나누어서 일부 데이터 처리/반환 과정 반복

  • socks5_resolve_local변수가 프록시 모드에 따라 값이 설정되는데,
    호스트 이름이 길어지면 중간에 값이 변경되더라도 변경된 값을 기억하지 못하게 됨
  • curl은 메모리에 포로토콜 프레임을 구성하고 목적지 정보를 저장
    앞의 상황으로 인해 판단을 잘못하여 호스트 이름을 전달 => 할당된 버퍼에 맞지 않아 Heap Buffer OverFlow 발생

 

  // 상태를 SOCKS5 연결 초기 읽기로 설정
  case CONNECT_SOCKS_READ_INIT:
    // 서버로부터 기대하는 응답 바이트 수를 2로 설정
    sx->outstanding = 2;
    // 응답을 여기에 저장
    sx->outp = socksreq;
    // 다음 상태로 넘어감
    
    /* FALLTHROUGH */
  // SOCKS5 응답을 읽는 상태
  case CONNECT_SOCKS_READ:
    // 서버로부터 응답을 받는 함수 호출
    presult = socks_state_recv(cf, sx, data, CURLPX_RECV_CONNECT, "initial SOCKS5 response");
    // 응답이 정상이 아니라면 결과를 반환
    
    if(CURLPX_OK != presult)
      return presult;
      
    // 아직 응답을 다 받지 못했다면 이 상태에서 계속 대기
    else if(sx->outstanding) {
      return CURLPX_OK;
    }
    
    // SOCKS5 버전이 아니라면 에러 메시지를 출력하고 버전 에러를 반환
    else if(socksreq[0] != 5) {
      failf(data, "Received invalid version in initial SOCKS5 response.");
      return CURLPX_BAD_VERSION;
    }
    
    // 인증이 필요하지 않다면 요청을 전송하고 상태를 변경
    else if(socksreq[1] == 0) {
      // 상태 변경 함수 호출
      sxstate(sx, data, CONNECT_REQ_INIT);
      // 요청 초기화 상태로 이동
      goto CONNECT_REQ_INIT;
    }

위 코드는 SOCKS5 프로토콜을 사용하여 서버에 연결하고, 서버로부터 초기 응답 데이터를 처리하는 역할을 수행

 + 서버의 응답을 기준으로 클라이언트의 상태 업데이트 및 에러 처리 등의 작업 수행

 

간단하게 정리하면 SOCKS5 포로토콜을 사용중인지 확인하는 부분이다.

 

  // 상태를 연결 요청 초기화로 설정
  case CONNECT_REQ_INIT:
    // 로컬에서 호스트 이름을 해석해야 하는 경우
    if(socks5_resolve_local) {
      // 호스트 이름을 해석
      enum resolve_t rc = Curl_resolv(data, sx->hostname, sx->remote_port, TRUE, &dns);

      // 호스트 이름 해석에 실패하면 에러 반환
      if(rc == CURLRESOLV_ERROR)
        return CURLPX_RESOLVE_HOST;

      // 호스트 이름 해석이 진행 중이면 상태를 변경하고 정상 결과 반환
      if(rc == CURLRESOLV_PENDING) {
        sxstate(sx, data, CONNECT_RESOLVING);
        return CURLPX_OK;
      }
      // 호스트 이름 해석이 완료되면 상태를 변경하고 다음 상태로 이동
      sxstate(sx, data, CONNECT_RESOLVED);
      goto CONNECT_RESOLVED;
    }
    // 로컬에서 호스트 이름을 해석하지 않아야 하는 경우, 다음 상태로 이동
    goto CONNECT_RESOLVE_REMOTE;

SOCKS5 프로토콜을 사용하면서 [로컬 이름 해결]을 수행하는 경우,
호스트의 이름을 해석하고 완료되면 상태를 변경해주는 코드이다.

 

 

    // 그 외의 경우 (호스트 이름을 원격으로 해석해야 하는 경우)
    else {
        socksreq[len++] = 3; // 주소 유형을 도메인 이름으로 설정
        socksreq[len++] = (char) hostname_len; // 호스트 이름의 길이를 바이트로 설정
        memcpy(&socksreq[len], sx->hostname, hostname_len); // NULL 없이 호스트 이름 복사
        len += hostname_len; // 전체 길이 업데이트

      }
      // 정보 출력
      infof(data, "SOCKS5 connect to %s:%d (remotely resolved)",
            sx->hostname, sx->remote_port);

호스트 이름을 로컬이 아닌 원격으로 해석해야 하는 경우 수행되는 부분이다.

전체적으로 정보들을 호스트 이름에 포함하여 업데이트를 하고 출력해준다.

 

hostname_len의 경우 다음과 같이 정의되어 있다.

const size_t hostname_len = strlen(sx->hostname);

입력한 호스트 이름의 길이를 그대로 사용하는 것을 볼 수 있다.

 

이때 socksreq는 임시 버퍼를 의미하는데

만약 호스트의 이름 크기가 버퍼의 남은 크기를 초과하는 경우 취약점이 발생하게 된다.

 

6. 패치 파악


코드 변경 사항

실습 버젼이 아닌 해당 CVE가 패치되기 바로 직전 버젼과의 비교를 진행

 

취약한 버젼 - Curl 8.3.0

/* RFC1928 chapter 5 specifies max 255 chars for domain name in packet */
    if(!socks5_resolve_local && hostname_len > 255) {
      infof(data, "SOCKS5: server resolving disabled for hostnames of "
            "length > 255 [actual len=%zu]", hostname_len);
      socks5_resolve_local = TRUE;
}

 

 

패치된 버젼 - Curl 8.4.0

/* RFC1928 chapter 5 specifies max 255 chars for domain name in packet */
    if(!socks5_resolve_local && hostname_len > 255) {
      failf(data, "SOCKS5: the destination hostname is too long to be "
            "resolved remotely by the proxy.");
      return CURLPX_LONG_HOSTNAME;
}

 

기존 방식 : socks5_resolve_local 변수의 값을 설정하여 로컬 환경 분석 상태를 사용할 것인지 결정

패치된 방식 : 상황 발생 시 오류 메시지를 출력시키고, 바로 종료

 

패치된 버젼에서의 실습

코드의 내용대로 출력 및 종료되는 것을 확인 가능

 

 

728x90
반응형