Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
POC-CVE-2023-32233 — 배치 요청을 처리할 때 Netfilter nf_tables에서 발생하는 Use-After-Free 취약점 CVE-2023-32233 | Kitploit
도구/GitHubGitHub/oferchen/poc-cve-2023-32233
Privilege EscalationMemory ForensicsVulnerability AnalysisExploitationPenetration TestingPayload DevelopmentBinary Exploitation
GitHuboferchen/poc-cve-2023-32233

POC-CVE-2023-32233

배치 요청을 처리할 때 Netfilter nf_tables에서 발생하는 Use-After-Free 취약점 CVE-2023-32233

저장소 보기
5363년 전Kitploit 검토 완료

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

Netfilter nf_tables의 배치 요청 처리 시 Use-After-Free

Demo

Demo_CVE-2023-32233

취약점 상세

영향을 받은 코드는 공식 Linux 커널(https://kernel.org/)에서 비롯되었으며 Netfilter nf_tables 구성 요소(net/netfilter/nf_tables_api.c)의 일부입니다.

Netfilter nf_tables는 구성을 원자적 연산으로 업데이트할 수 있도록 합니다. 이 기능을 사용할 때 사용자 모드 클라이언트는 기본 연산 목록이 포함된 배치 요청을 보냅니다. 그런 다음 Netfilter nf_tables는 배치 내의 모든 연산을 단일 트랜잭션으로 처리합니다. 배치를 처리할 때 Netfilter nf_tables는 구성 상태 업데이트를 확인하여 각 후속 기본 연산이 유효한지 확인하며, 이는 배치 내의 이전 모든 연산의 상태 업데이트도 고려합니다. 그러나 현재 구현된 검사는 충분하지 않습니다.

특정 시나리오에서 우리는 익명 nft_set에 lookup 표현식이 있는 nft_rule을 포함하고, 해당 익명 nft_set에 일부 요소가 포함된 Netfilter nf_tables 구성으로 시작합니다. 다음으로, 다음 두 가지 기본 연산이 포함된 배치 요청을 보냅니다:

  1. nft_rule을 삭제하는 NFT_MSG_DELRULE 연산.
    참고로 이는 lookup 표현식과 익명 nft_set도 암시적으로 삭제합니다.
  2. 삭제된 익명 nft_set의 요소 중 하나를 삭제하는 NFT_MSG_DELSETELEM 연산.

현재 버전의 Netfilter nf_tables는 위 배치 요청을 수락합니다. 그런 다음 nf_tables_commit_release()를 호출하여 해제된 리소스를 nf_tables_destroy_list에 추가합니다. nf_tables_destroy_list는 nf_tables_trans_destroy_work()에 의해 처리되며, 먼저 다음 호출을 통해 NFT_MSG_DELRULE 연산과 관련된 리소스를 할당 해제합니다:

root@kitploit:~
nft_commit_release()
    nf_tables_rule_destroy()
        nf_tables_expr_destroy()
            expr->ops->destroy() (nft_lookup_destroy()를 가리킴)
                nf_tables_destroy_set()
                    nft_set_destroy()
                        kvfree() ( `nft_set`이 사용한 메모리 할당 해제)

그런 다음 NFT_MSG_DELSETELEM 연산을 처리하기 전에, 다음 호출 중 nft_trans_elem_set()을 통해 할당 해제된 nft_set에 대한 참조에 접근합니다:

root@kitploit:~
nft_commit_release()
    nf_tables_set_elem_destroy()
        nft_set_elem_ext()

위의 nft_set_elem_ext() 내에서 할당 해제된 nft_set의 메모리 위치에 접근하여 nft_set_ext의 위치를 결정합니다:

root@kitploit:~
static inline struct nft_set_ext *nft_set_elem_ext(const struct nft_set *set,
                                                   void *elem)
{
        return elem + set->ops->elemsize;
}

따라서 set->ops->elemsize 값이 손상되면 예상치 못한 메모리 위치가 파괴될 nft_expr 목록으로 해석될 수 있습니다:

root@kitploit:~
static void nf_tables_set_elem_destroy(const struct nft_ctx *ctx,
                                       const struct nft_set *set, void *elem)
{
        struct nft_set_ext *ext = nft_set_elem_ext(set, elem);

        if (nft_set_ext_exists(ext, NFT_SET_EXT_EXPRESSIONS))
                nft_set_elem_expr_destroy(ctx, nft_set_ext_expr(ext));

익스플로잇 기법

위 취약점을 익스플로잇하려면 Linux 커널의 백그라운드 워커 스레드에서 실행되는 nf_tables_trans_destroy_work()와의 경쟁에서 승리해야 합니다. 이는 기존 완화 조치(예: 커널 slab 할당자 강화, KASLR(Kernel Address Space Layout Randomization), 특히 Control-Flow Integrity)를 고려하기 전에도 실제 익스플로잇을 복잡하게 만드는 것으로 보입니다. 그러나 첨부된 PoC는 실제로 합리적으로 신뢰할 수 있는 익스플로잇이 여전히 가능함을 증명합니다.

취약점을 익스플로잇하려면 nf_tables_rule_destroy()에서 할당 해제된 후 nf_tables_set_elem_destroy()에서 사용되기 전에 nft_set의 메모리 내용을 수정해야 합니다. nf_tables_rule_destroy()와 nf_tables_set_elem_destroy()는 모두 Linux 커널의 백그라운드 워커 스레드에서 실행되는 nf_tables_trans_destroy_work()의 단일 호출 내에서 호출됩니다. 또한 할당 해제된 메모리 청크는 일반적으로 동일한 CPU 코어에서만 재사용할 수 있습니다.

nf_tables_trans_destroy_work()와 경쟁할 때, 백그라운드 워커 스레드가 nf_tables_rule_destroy()를 호출한 후 nf_tables_set_elem_destroy()를 호출할 때까지 제어된 지연을 추가하여 기회를 높입니다. 이를 위해 많은 수의 요소를 포함하는 다른 nft_set을 파괴하는 추가 연산을 삽입합니다. 또한 다른 모든 CPU 코어를 바쁘게 유지하여 백그라운드 워커 스레드가 특정 CPU 코어에서 스케줄링될 가능성을 높입니다. 그렇게 하면 nf_tables_rule_destroy()에서 nft_set을 할당 해제한 직후 동일한 CPU 코어에서 새 구조를 할당할 수 있습니다. 목표는 nf_tables_rule_destroy()에서 할당 해제된 nft_set의 메모리 위치를 재사용하기 위해 다른 유형의 새 nft_set을 할당하는 것입니다.

새 nft_set 유형은 set->ops->elemsize에 대해 다른 값을 사용하도록 선택됩니다. 따라서 백그라운드 워커 스레드가 마침내 nf_tables_set_elem_destroy()를 호출하여 NFT_MSG_DELSETELEM 연산을 처리할 때 elem 인수를 잘못 해석하여 손상된 nft_set_ext *ext가 올바른 위치에서 몇 바이트 뒤에 위치하게 됩니다. 이는 원래 nft_set_ext의 특정 사용자 제어 데이터 필드가 이제 헤더로 해석되어 타입 혼동이 발생함을 의미합니다.

이 타입 혼동을 악용하는 한 가지 방법은 손상된 nft_set_ext 헤더에 오프셋 값을 조작하여 nf_tables_set_elem_destroy()가 인접한 메모리 블록의 내용을 다음 호출을 통해 파괴할 nft_expr 목록으로 해석하도록 하는 것입니다:

root@kitploit:~
nft_set_elem_expr_destroy()
    __nft_set_elem_expr_destroy()
        nf_tables_expr_destroy()
            expr->ops->destroy()

이 익스플로잇 시점에서는 커널 메모리 레이아웃의 세부 정보가 아직 없습니다. 따라서 절대 포인터 주소를 조작하는 것은 불가능합니다. 그러나 손상된 nft_set_ext 헤더를 조작할 때 여전히 범위를 벗어난 오프셋을 사용할 수 있습니다. 이로 인해 expr->ops->destroy()가 인접한 메모리 청크의 특정 유효한 nft_expr에 대해 호출됩니다.

이를 위해 제어된 NFTA_LOG_PREFIX를 사용하여 nft_log 표현식을 스프레이합니다. 그런 다음 expr->ops->destroy()가 호출되면 nft_log_destroy()에서 해당 nft_log->prefix가 할당 해제됩니다:

root@kitploit:~
static void nft_log_destroy(const struct nft_ctx *ctx,
                            const struct nft_expr *expr)
{
        struct nft_log *priv = nft_expr_priv(expr);
        struct nf_loginfo *li = &priv->loginfo;

        if (priv->prefix != nft_log_null_prefix)
                kfree(priv->prefix);

스프레이된 nft_log 표현식의 다른 참조를 통해 이 메모리에 여전히 접근하고 다시 할당 해제할 수도 있습니다.

또한 nft_log->prefix의 크기를 제어할 수 있으므로 kmalloc-{8, ..., 192} 슬랩 중 하나에서 할당할 수 있습니다. 마지막으로, 참조된 메모리는 커널에 의해 문자열로 해석되므로 다른 객체를 겹쳐 놓을 때 손상에 대해 걱정할 필요가 없습니다. 이는 본질적으로 게임 오버입니다.

한 가지 불편한 점은 NULL 문자가 nft_log->prefix를 종료하므로 메모리 내용을 유출할 때 NULL 바이트 이후를 읽을 수 없다는 것입니다. 이는 다음 단계에서 해결됩니다. 여기서 nft_object->udata를 할당하여 nft_log->prefix 메모리 청크를 재사용하고 nft_log 표현식을 파괴합니다. 이렇게 하면 nft_object->udata 메모리가 할당 해제되지만, 이제 NULL 바이트 제한 없이 메모리 내용을 유출하기 위해 nft_object->udata 댕글링 포인터를 계속 사용할 수 있습니다.

다음 단계에 적합한 구조를 찾기 위해 nft_dynset_new()에서 할당된 nft_expr을 선택했습니다. 이들은 nft_log->prefix 및 nft_object->udata와 동일한 슬랩에 있습니다. 또한 할당 크기를 합리적으로 제어할 수 있어 필요에 따라 나중에 다른 크기의 슬랩 간에 쉽게 전환할 수 있습니다.

이러한 구조를 사용하기 위해 nft_dynset 표현식이 있는 패킷 필터를 생성합니다. 그리고 루프백 인터페이스를 통해 패킷을 보내면 nft_dynset 표현식이 nft_dynset_new()를 호출하여 연결된 nft_set에 대한 새 요소를 생성합니다. 생성된 요소는 다음과 같은 유형의 상태 저장 표현식입니다:

  • nft_counter - 커널 메모리에서 nf_tables.ko의 위치를 얻기 위함.
    이 구조에는 nf_tables.ko 커널 모듈의 nft_counter_ops에 대한 포인터가 포함됩니다. nft_object->udata를 읽어 이 포인터를 유출합니다.

  • nft_quota - 임의 메모리 읽기 및 쓰기 위함.
    nft_object->udata를 반복적으로 할당 해제하고 재할당하여 nft_quota->consumed 포인터를 수정할 수 있습니다. 그런 다음 NFT_MSG_GETSETELEM 연산을 수행하여 nft_quota_do_dump()를 호출하여 참조된 메모리의 내용을 읽고 결과를 NFTA_QUOTA_CONSUMED 속성으로 반환합니다. 쓰기의 경우 루프백 인터페이스를 통해 패킷을 보내기만 하면 됩니다. 여기서 nft_quota_do_eval()은 다음을 호출합니다:

    root@kitploit:~
      static inline bool nft_overquota(struct nft_quota *priv,
                                       const struct sk_buff *skb)
      {
              return atomic64_add_return(skb->len, priv->consumed) >=
    

    nft_quota->consumed를 수정합니다.

위의 임의 메모리 읽기를 사용하여 커널 코어의 기본 주소를 얻습니다. 그런 다음 "/sbin/modprobe" 경로명의 "sbin" 하위 문자열을 수정하여 "/tmp"로 대체합니다. 결과 경로명 "//tmp/modprobe"는 커널이 파일 내용을 제어할 수 있는 루트 권한으로 프로세스를 시작하는 데 사용됩니다.

Control-Flow Integrity를 우회하기 위한 의도적인 노력은 하지 않았습니다. 그러나 각 익스플로잇 단계에서 가장 유연하고 강력한 프리미티브를 의식적으로 선택했습니다. 결과적으로 우리의 선택은 Control-Flow Integrity에 의해 잠재적으로 차단될 수 있는 프리미티브를 어떻게든 피했습니다. 이제 결과 익스플로잇이 Control-Flow Integrity 완화가 적용된 시스템에서 실제로 작동하는지 테스트를 통해 확인하고 싶습니다.

도구 다운로드