본문 바로가기
Bug Bounty

[Semgrep/SAST] Semgrep 룰 고도화 - RCE

by m_.9m 2025. 12. 4.

 

 

목차

     

    1. Semgrep 룰 고도화

    기존 RCE를 잡는 룰에서 취약점으로 세팅했던 3건을 모두 잡지 못하였습니다.

    본 게시물에선 각각의 CVE PoC 패턴과 기존 룰을 비교해가며 룰을 고도화하는 작업을 수행합니다.


    2. User Post Galley(CVE-2022-4060)

    2.1 취약점 원리

     

    해당 취약점이 잡히지 않은 이유는 다음과 같습니다.

    사용자 입력값으로 PHP 함수를 동적으로 호출했기 때문입니다.

    $func_name = $_GET["field"]에서 잘라낸 값;
    if (function_exists($func_name)) {
        $func_name($param1, $param2, $param3);
    }


    즉,

    “field=exec:cd:NULL:NULL”


    이렇게 보내면

    exec("cd", "NULL", "NULL")


    이런 식으로 서버에서 실행되는 구조입니다.

    ✔ 인증 필요 없음
    ✔ 사용자가 넘긴 문자열을 함수라고 믿고 실행
    ✔ 그래서 exec(), system() 같은 PHP 기본 함수도 그대로 실행됨 → RCE 

     

     

    2.2 보안적 관점

    해당 플러그인에서는 function_exists()로 최소한의 유효성 검사만을 수행합니다.

    이와같은 방식을 기능으로 적용해야할 시에는 화이트리스트의 방식으로 허용된 문자열 목록을 만들어야 합니다.

     

    2.3 패턴 수정

    해당 패턴 탐지를 위해서 사용자 입력값을 함수명으로 사용하는 패턴을 룰에 추가합니다.

    pattern-sinks:
        - pattern: $SINK($ARGS, ...) # <-- 오염된 데이터 $SINK가 함수 이름으로 사용됨
        - pattern: call_user_func($SINK, ...)
        - pattern: call_user_func_array($SINK, ...)

     

     

    2.4 반영 결과

    룰을 반영한 결과 전체적인 탐지량이 10배가량 늘고 오탐률도 극도로 올라갑니다.

     

    오탐률이 오르는 근본적인 이유로는 아래의 두가지가 있습니다.

     

     

    1. PHP의 높은 동적 호출 의존성(훅 시스템, 객체 메서드 호출)

    2. Taint Flow의 과도한 확산성(간접적으로 동적 호출 패턴의 함수 이름의 도달하는 경우 많음)

     

     

    따라서,

    해당 취약점 케이스는 툴의 정확도를 위해 케이스에서 제외한다는 결론을 내렸습니다.

     

     

    3. Social Warfare(CVE-2019-9978)

    3.1 취약점 원리

    (https://github.com/hash3liZer/CVE-2019-9978)

     

    해당 취약점이 탐지가 되지 않은 이유는 두가지 입니다.

     

    1. 공격자가 Payload를 직접 넣지 않고 서버가 외부 파일을 직접 읽어옵니다.

    기존의 룰은 직접적인 사용자의 입력($_GET, $_POST 등)을 Sources로만

    정의했기 때문에 탐지에 실패하였습니다. 

     

    2. 문자열 결합으로 인해 데이터 흐름이 끊어졌습니다.

    Sources의 정의를 확장해 오염 전파 규칙을 추가해야합니다.

     

    3.2 보안적 관점

    Social Warfare는 다음의 패치에서 취약점에 해당되는 기능을 아예 삭제하였습니다.

     

    3.3 패턴 수정

    RCE 패턴의 복잡성을 고려해 다음과 같은 수정사항을 반영했습니다.

     

    1. eval()함수에 대한 직접적인 경고를 추가했습니다.

    eval() 함수를 사용하는 모든 케이스를 수동 검토해보기 위해 오염 흐름과 무관한 룰을 추가합니다.

      # Eval 사용 자체 경고 (Taint Flow 무관)
      - id: php-eval-usage-warning
        message: >
          [CRITICAL] eval() 함수가 사용되었습니다. eval()은 코드 인젝션 위험을 내포하므로 절대 사용해서는 안 됩니다.
        languages: [php]
        severity: ERROR
        pattern: eval($X);

     

     

    2. CMD Injection과 Code Injection을 구분합니다.

    둘의 방어 목표가 다르게 때문에 Sanitizer의 비호환성을 고려해 룰을 두개로 분리합니다.

     

    예시로 escapeshellarg()는 입력 값을 인자로 만들어 OS 명령어의 안전한 인자( ` )로 만들지만 

    eval()는 이와 상관없이 문자열을 그대로 실행되기 때문에 막을수 없고  

     

    Code Injection의 방어책인 타입캐스팅 (int) $X는 입력 문자열을 강제로 숫자로 만들어 

    |, ;, rm 등을 제거하지만 escapeshellarg()만큼 정확하지 않습니다.

     

     

    3. 문자열 결합 패턴을 추가하기 위해 오염 전파 규칙을 추가합니다.

    Taint Analysis는 오염된 데이터가 다른 변수로 어떻게 흘러가는지 추적해야 합니다.

    특히 문자열 결합(String Concatenation) 시 오염이 전파되도록 규칙을 정의합니다.

    // Step 1 & 2의 결과:
    // $url이 오염됨 (Propagation: 문자열 결합)
    $url = $_GET['swp_url'] . '?swp_debug=get_user_options';
    // $options가 오염됨 (Propagation: 함수 호출)
    $options = file_get_contents($url);
    // $array가 오염됨 (Propagation: 할당/문자열 결합)
    $array = 'return ' . $options . ';';
    
    // Step 3: Sink에서 오염된 데이터를 사용함 -> 탐지 성공
    $fetched_options = eval( $array );

     

     

    수정된 패턴의 결과는 다음과 같습니다.

    [CMD Injection 부분]

      # CMD Injection Basic Rule for WP
      - id: wp-command-injection-taint
        message: >
          [보안 위험] 치명적인 OS 커맨드 인젝션(RCE)입니다. 사용자 입력이나 원격 파일 내용이 시스템 명령어 함수로 전달되었습니다.
        languages: [php]
        severity: ERROR
        mode: taint
        metadata:
          cwe: "CWE-78: OS Command Injection"
        pattern-sources:
          # 1. 일반 사용자 입력
          - pattern: $_GET[$X]
          - pattern: $_POST[$X]
          - pattern: $_REQUEST[$X]
          - pattern: file_get_contents($SOURCE, ...) //오염 전파 부분
          - pattern: file($SOURCE, ...)
          - pattern: readfile($SOURCE)
          - pattern: fopen($SOURCE, ...)
    
        pattern-sanitizers:
          - pattern: escapeshellcmd($X) # 쉘 명령어로 실행되지 않도록 필터링
          - pattern: escapeshellarg($X) # $X를 하나의 독립적인 인자로 변환
          - pattern: intval($X) # $X를 정수로 강제 변환
          - pattern: (int) $X # $X를 정수로 강제 변환
    
        pattern-sinks:
          - pattern: system($SINK, ...) # 외부 프로그램이나 시스템 명령어를 실행하고 화면 출력
          - pattern: exec($SINK, ...) # 외부 프로그램 실행 결과 마지막줄을 문자열로 반환
          - pattern: passthru($SINK, ...) # 외부 프로그램 실행 결과 화면 출력
          - pattern: shell_exec($SINK, ...) # 셀 명령어를 실행하고 전체 출력을 하나의 문자열로 반환
          - pattern: pcntl_exec($SINK, ...) # 현재 프로세스를 지정된 프로그램으로 교체 실행
          - pattern: popen($SINK, ...) #양방향 파이프를 열어 명령어와 데이터를 주고받음(결과 파일 포인터 형태 반환)
          - pattern: "`...$SINK...`" #백틱 안의 내용이 OS 명령으로 실행

     

     

    [Code Injection 부분]

    # Code Injection Taint Rule (eval, assert)
      - id: wp-code-injection-taint-improved
        message: >
          [CRITICAL] 사용자 입력($SINK)이나 원격 파일 내용이 eval(), assert() 등의 PHP 코드 실행 함수에 전달되었습니다. (RCE 위험)
        languages: [php]
        severity: ERROR
        mode: taint
        metadata:
          cwe: "CWE-94: Improper Control of Generation of Code"
        pattern-sources:
          - pattern: $_GET[$X]
          - pattern: $_POST[$X]
          - pattern: $_REQUEST[$X]
          - pattern: $_COOKIE[$X]
          - pattern: file_get_contents($SOURCE, ...) //오염 전파 부분
          - pattern: file($SOURCE, ...)
          - pattern: readfile($SOURCE)
          - pattern: fopen($SOURCE, ...)
          
        pattern-sanitizers:
          - pattern: intval($X)
          - pattern: (int) $X
          - pattern: (bool) $X
    
        pattern-sinks:
          - pattern: eval($SINK);
          - pattern: assert($SINK, ...);
          - pattern: create_function($ARGS, $SINK);

     

     

    3.4 반영 결과

    기존에 탐지되지 않았던 RCE 취약점이 탐지된 것을 확인합니다.

     

     

    4. WP All Import(CVE-2022-3418)

    4.1 취약점 원리

     

    해당 취약점은 파일 업로드 기능과 연계된 RCE 취약점으로 분류가 됩니다.

    탐지가 되지 않은 이유는 다음과 같습니다.

     

    기존 규칙은 파일이 서버로 최초 업로드 된 뒤 파일 시스템에 이동되는 과정에 초점이 맞춰져 있지만

    해당 취약점의 로직은 파일이 ZIP으로 업로드 된 후

    압축 해제의 과정에서 파일 확장자 인증이 불완전하기 때문에 발생합니다.

     

    1. test.php.txt(이중 확장자 업로드)

    구문 끝 txt만 보고 허용된 파일로 인식합니다.

    2. htaccess 설정 파일 업로드

    파일은 서버에 기록되며 if문으로 저장된 파일을 처리합니다.

    데이터 확장자가 아닌 파일은 if문 자체에 들어가지 않습니다.

    // PclZip::extract 호출 시, ZIP 내부 파일들은 이미 서버에 기록됨.
    $archive = new PclZip($this->file);
    if (($v_result_list = $archive->extract(PCLZIP_OPT_PATH, $this->uploadsPath, PCLZIP_OPT_REPLACE_NEWER)) == 0) {
        // ...
    }
    
    // 이 아래 foreach 루프는 '이미 저장된' 파일을 대상으로 '무엇을 할지' 결정합니다.
    foreach ($v_result_list as $unzipped_file) {
        // if 문은 '저장된' 파일을 '처리할지' 결정할 뿐, 파일 자체를 '삭제'하지 않습니다.
    }

     

    4.2 보안적 관점

    // 불완전한 검사 로직 (php 파일은 거부하지만, 이중 확장자는 통과시킴)
    if ($unzipped_file['status'] == 'ok' and preg_match('%\\W(xml|csv|txt|dat|psv|json|xls|xlsx|gz)$%i', trim($unzipped_file['stored_filename'])) ... ) {
    // ... 파일 처리가 진행됨

     

    플러그인은 해당 부분을 

    PCLZIP_OPT_EXTRACT_EXT_RESTRICTIONS, ['php','phtml','htaccess']

     

    위의 옵션을 통해 위험하나 확장자를 아예 추출 과정에서 차단함으로써 해결했습니다.

     

    4.3 패턴 수정

    기존의 표준 파일 이동 함수 두개를 합치고

    압축 해제 라이브러리 PclZip과 표준 추출 함수 ZipArchive의 경우를 추가합니다.

     

    ** 파일 업로드의 경우 수동 확인을 위한 가이드 제공의 역할만을 수행합니다.

      # Unrestricted Upload of File Rule for WP
      - id: wp-file-upload-move-raw
        message: >
          [보안 경고] 이 패턴 주변에 확장자 확인 로직이나, 파일 내용을 확인하는 MIME 타입 검사 로직이 있어야 안전합니다.
        languages: [php]
        severity: WARNING
        metadata:
          cwe: "CWE-434: Unrestricted Upload of File with Dangerous Type"
        pattern-either:
          # 1. WP/PHP의 표준 파일 이동 함수
          - pattern: move_uploaded_file($_FILES[$X]['tmp_name'], $DEST);
          - pattern: wp_handle_upload($_FILES[$X], ...); 
          
          # 2. PclZip 추출 함수 (WP All Import와 같은 플러그인에서 사용)
          - pattern: |
              $archive = new PclZip($FILE);
              ...
              $v_result_list = $archive->extract(...);   
    
          # 3. ZipArchive 추출 함수 (PHP 내장 표준)
          - pattern: |
              $zip = new ZipArchive();
              ...
              $zip->extractTo($DEST);

     

     

    4.4 반영 결과

    해당 추출의 부분이 탐지된 것을 확인합니다.