1. 취약점 개요

OpenSourcePOS 3.4.1 이하 버전의 송장(Invoice) 생성 및 이메일 발송 기능에서 PHP 코드 주입을 통한 원격 코드 실행(RCE) 취약점이 확인되었다. 해당 취약점은 사용자로부터 입력받은 품목 번호(item_number)를 PDF로 변환하는 과정에서 적절한 필터링 없이 렌더링하고, PDF 엔진인 dompdf의 isPhpEnabled 옵션이 활성화되어 있어 임의의 PHP 코드 실행이 가능해 공격자는 이를 통해 서버의 제어권을 획득할 수 있다.

보안패치 커밋 : https://github.com/opensourcepos/opensourcepos/commit/3c217bbdddb6ff0be5b8ad4f1d53450c1ef6bc09

1.1 취약점 정보 요약

항목내용
취약점 명칭OpenSourcePOS Invoice PDF PHP 코드 인젝션
취약점 유형원격코드 실행 (RCE)
영향 받는 버전OpenSourcePOS 3.4.1 이하
CVSS v3.1Critical 9.9/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
CVE 제보일2025-03-04
CVE 발급일2025-05-08
CVE 번호CVE-2026-36972

1.2 공격 흐름도

  sequenceDiagram
    autonumber
    participant A as 공격자 (Manager)
    participant DB as Database
    participant S as Web Server (OpenSourcePOS)
    participant D as dompdf Library
    participant F as File System / OS

    A->>S: 품목 바코드에 PHP 페이로드 삽입(e.g., script type=text/php)
    S->>DB: 악성 스크립트 저장
    
    A->>S: 특정 고객에게 '송장 이메일 발송' 요청
    S->>DB: 품목 데이터 조회 (페이로드 포함)
    
    S->>S: invoice_email.php 렌더링 (이스케이프 누락)
    S->>D: 생성된 HTML을 dompdf에 전달
    
    Note over D: isPhpEnabled = true 확인
    D->>D: HTML 내 script type=text/php 해석 및 실행
    
    D->>F: 시스템 명령 수행 (예: touch /tmp/pwned)
    S-->>A: PDF 생성 및 메일 발송 완료 응답

2. 기술적 배경

  • dompdf:

    • PHP용 HTML-to-PDF 변환 라이브러리이다. 특정 옵션 활성 시 PDF 내부에서 PHP 스크립트를 실행할 수 있는 기능을 제공한다.
  • isPhpEnabled 옵션:

    • dompdf의 isPhpEnabled 옵션이 true로 설정되면 dompdf는 HTML 문서 내의 <script type="text/php"> 태그를 찾아서 서버 측에서 실행한다. 보안상 위험하므로 기본값은 false이며 사용이 권장되지 않는다.

3. Root Cause 분석

3.1 미흡한 출력 검증 (app/Views/sales/invoice_email.php 101 라인)

invoice_email.php에서 item_number를 출력할 때, 다른 필드와 달리 출력 문자 이스케이프 보안 함수(esc())가 누락된 것을 확인할 수 있다.

<tr class="item-row">
    <td><?= $item['item_number'] ?></td> <td class="item-name"><?= esc($item['name']) ?></td>

3.2 위험한 라이브러리 설정 (app/Helpers/dompdf_helper.php)

dompdf_helper.php의 create_pdf 함수에서 isPhpEnabled를 명시적으로 true로 설정하고 있다. 이 설정은 앞서 발생한 출력 검증 미흡과 결합되어 단순 XSS를 넘어선 RCE로 취약점이 발생한다.

function create_pdf(string $html, string $filename = ''): string {
    // [결함 지점] PHP 실행 권한이 활성화됨
    $dompdf = new Dompdf\Dompdf(['isRemoteEnabled' => true, 'isPhpEnabled' => true]);
    $dompdf->loadHtml(str_replace(['\n', '\r'], '', $html));
    $dompdf->render();
    return $dompdf->output();
}

3.3 이메일 전달을 위한 pdf 파일 생성 (app/Controllers/Sales.php 867 라인)

create_pdf 함수를 이용해 클라이언트에 로딩된 HTML을 이용해 PDF 파일을 생성한다. 여기서 isPhpEnabled 옵션이 활성화되어 있으니 PHP 코드 포함 시 실행된다.

public function getSendPdf(int $sale_id, string $type = 'invoice'): bool
{
생략
...
		// 이메일 첨부물 생성: invoice in PDF format
		$view = Services::renderer();
		$html = $view->setData($sale_data)->render("sales/$type" . '_email', $sale_data);

		// Load PDF helper
		helper(['dompdf', 'file']);
		$filename = sys_get_temp_dir() . '/' . lang('Sales.' . $type) . '-' . str_replace('/', '-', $number) . '.pdf';
		if (file_put_contents($filename, create_pdf($html)) !== false) { 
			$result = $this->email_lib->sendEmail($to, $subject, $text, $filename);
		}
...
생략

4. Proof of Concept (PoC)

아이템 탭에서 새로운 아이템을 등록하는데, Barcode 값에 PHP 코드를 삽입한다.

payload: &lt;script type="text/php"&gt;system('touch /tmp/pwned');

2026-03-16-17-02-51.png

세일즈 탭에서 Register mode를 Invoice 모드로 변경. PHP 코드가 포함된 Barcode 아이템 선택. 이메일이 등록된 Customer 선택. “Email Receipt” 옵션 체크 “Invoice” 버튼 클릭

2026-03-16-17-04-15.png

PHP 함수가 서버측에서 실행되어 pwned 파일이 생성되었다.

2026-03-16-17-06-39.png


5. 패치 분석

item_number(바코드)가 출력될 시 HTML 인코딩없이 출력되어 차후 송장용 PDF 파일 생성 시 문제가 발생했던 것이였다. 원천적 문제인 dompdf의 isPhpEnabled 옵션은 여전히 활성화 상태이지만, item_number가 인코딩된 상태로 출력되었으므로 동일한 내용으로 RCE 취약점은 발생하지 않는다. 그럼에도 불구하고, 여전히 isPhpEnabled 옵션이 활성화되어 있기에, PHP 코드 기반 RCE 취약점 위험은 존재한다.

if ($item['print_option'] == PRINT_YES) {
?>
<tr class="item-row">
-   <td><?= $item['item_number'] ?></td>
+   <td><?= esc($item['item_number']) ?></td>
<td class="item-name"><?= esc($item['name']) ?></td>
<td><?= to_quantity_decimals($item['quantity']) ?></td>
<td><?= to_currency($item['price']) ?></td>

6. 보안 대책

- 라이브러리 설정 변경: dompdf_helper.php에서 isPhpEnabled 옵션을 false로 변경하는 것이 가장 근본적인 해결책이다.

- 보안 함수 생활화: 모든 사용자 입력값이 출력되는 지점(View)에서는 반드시 esc()와 같은 출력 필터링 함수를 사용해야 한다.

- 최신 버전 업데이트: 해당 취약점이 해결된 최신 커밋 버전으로 소프트웨어를 업데이트한다.

Reference