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.1 | Critical 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이며 사용이 권장되지 않는다.
- dompdf의 isPhpEnabled 옵션이 true로 설정되면 dompdf는 HTML 문서 내의
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: <script type="text/php">system('touch /tmp/pwned');

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

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

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
- https://cwe.mitre.org/data/definitions/94.html
- https://github.com/opensourcepos/opensourcepos/commit/3c217bbdddb6ff0be5b8ad4f1d53450c1ef6bc09

