docs: [changes] API eval() 제거 변경이력 추가
This commit is contained in:
1
INDEX.md
1
INDEX.md
@@ -208,6 +208,7 @@ DB 도메인별:
|
||||
| [20260311_salary_history_delete.md](dev/changes/20260311_salary_history_delete.md) | 연봉이력 삭제 기능 추가 (사원관리 연봉정보) |
|
||||
| [20260314_api_test_infrastructure_and_order_tests.md](dev/changes/20260314_api_test_infrastructure_and_order_tests.md) | API 테스트 인프라 정비 + 수주 테스트 12개 추가 |
|
||||
| [20260314_api_quality_improvement_deploy.md](dev/changes/20260314_api_quality_improvement_deploy.md) | API 품질 개선 배포 — 테스트 56개 + N+1 최적화 3건 (근거 문서 포함) |
|
||||
| [20260315_eval_removal_safe_math_evaluator.md](dev/changes/20260315_eval_removal_safe_math_evaluator.md) | API 보안 개선 — eval() 3건 제거, SafeMathEvaluator 도입 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
229
dev/changes/20260315_eval_removal_safe_math_evaluator.md
Normal file
229
dev/changes/20260315_eval_removal_safe_math_evaluator.md
Normal file
@@ -0,0 +1,229 @@
|
||||
# API 보안 개선 — eval() 제거, SafeMathEvaluator 도입
|
||||
|
||||
**날짜:** 2026-03-15
|
||||
**작업자:** R&D 개발실장 + Claude Code
|
||||
**배포 대상:** API develop 브랜치
|
||||
|
||||
---
|
||||
|
||||
## 변경 개요
|
||||
|
||||
API 프로젝트의 보안 감사에서 CRITICAL 등급으로 식별된 `eval()` 코드 인젝션 취약점 3건을 제거했다. PHP `eval()` 대신 Shunting-yard 알고리즘 기반의 `SafeMathEvaluator` 유틸리티를 신규 구현하여 교체했다. 외부 라이브러리 의존 없이 동일한 계산 결과를 보장한다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 왜 이 작업을 했는가 (근거)
|
||||
|
||||
### 1.1 보안 감사 결과
|
||||
|
||||
API 코드베이스 전체 보안 감사에서 `eval()` 사용 3건이 발견되었다.
|
||||
|
||||
| 심각도 | 파일 | 위치 | 용도 |
|
||||
|:------:|------|:----:|------|
|
||||
| **CRITICAL** | `Services/Calculation/FormulaParser.php` | 234행 | BOM 산술 수식 계산 |
|
||||
| **CRITICAL** | `Services/Calculation/FormulaParser.php` | 279행 | 조건식 비교 평가 |
|
||||
| **CRITICAL** | `Services/Quote/FormulaEvaluatorService.php` | 316행 | 견적 수식 계산 |
|
||||
|
||||
### 1.2 위험성
|
||||
|
||||
- `eval()`은 문자열을 PHP 코드로 실행하므로 **원격 코드 실행(RCE)** 위험 존재
|
||||
- 기존 코드에 정규식 기반 화이트리스트 필터(`isSafeMathExpression`)가 있었으나, 정규식 우회 가능성 상존
|
||||
- `FormulaParser::testFormula()` 엔드포인트를 통해 인증된 사용자가 임의 수식을 전달할 수 있는 구조
|
||||
- OWASP A03:2021 Injection, CWE-95 Eval Injection 해당
|
||||
|
||||
### 1.3 교체 전략
|
||||
|
||||
| 방안 | 장점 | 단점 | 채택 |
|
||||
|------|------|------|:----:|
|
||||
| `symfony/expression-language` | 검증된 라이브러리 | 외부 의존성 추가, 오버킬 | ❌ |
|
||||
| Shunting-yard 직접 구현 | 의존성 없음, 필요한 기능만 포함 | 직접 구현 필요 | ✅ |
|
||||
| `eval()` 유지 + 필터 강화 | 변경 최소 | 근본적 해결 아님 | ❌ |
|
||||
|
||||
실제 `eval()`이 처리하는 연산이 **숫자 + 사칙연산 + 비교 연산**뿐이므로, 외부 라이브러리 없이 직접 구현이 가장 적합했다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 영향 분석
|
||||
|
||||
### 2.1 eval() 사용 위치 (3곳 모두 private 메서드)
|
||||
|
||||
```
|
||||
FormulaParser (eval 2곳)
|
||||
← CalculationEngine.calculateBOM()
|
||||
← BomCalculationService
|
||||
← BomCalculationController (설계 BOM 계산)
|
||||
← BomCalculationController.testFormula() (수식 테스트)
|
||||
|
||||
FormulaEvaluatorService (eval 1곳)
|
||||
← QuoteCalculationService
|
||||
← QuoteController (견적 자동산출)
|
||||
← Verify5130Calculation (artisan 커맨드)
|
||||
```
|
||||
|
||||
### 2.2 영향도
|
||||
|
||||
- 3곳 모두 **private 메서드** 내부에서만 `eval()` 사용
|
||||
- public 인터페이스(`execute()`, `calculateExpression()`)의 입출력 시그니처 **변경 없음**
|
||||
- 호출하는 Controller, Service에 **수정 불필요**
|
||||
- 계산 결과 **동일** (검증 완료)
|
||||
|
||||
---
|
||||
|
||||
## 3. 수정 내용
|
||||
|
||||
### 3.1 신규 파일
|
||||
|
||||
| 파일 | 설명 |
|
||||
|------|------|
|
||||
| `app/Helpers/SafeMathEvaluator.php` | Shunting-yard 알고리즘 기반 안전한 수식 평가기 |
|
||||
|
||||
**SafeMathEvaluator 지원 기능:**
|
||||
|
||||
| 기능 | 메서드 | 예시 |
|
||||
|------|--------|------|
|
||||
| 산술 계산 | `calculate()` | `(3160 * 4350) / 1000000` → `13.746` |
|
||||
| 비교 평가 | `compare()` | `3000 <= 6000` → `true` |
|
||||
| 단항 마이너스 | `calculate()` | `-5 + 10` → `5` |
|
||||
| 나머지 연산 | `calculate()` | `10 % 3` → `1` |
|
||||
| 논리 연산 | `compare()` | `5 > 3 && 2 < 4` → `true` |
|
||||
| 중첩 괄호 | `calculate()` | `((2 + 3) * 4) / 2` → `10` |
|
||||
| 0 나누기 방어 | `calculate()` | `10 / 0` → 예외 발생 |
|
||||
|
||||
### 3.2 수정된 파일
|
||||
|
||||
| 파일 | 변경 내용 |
|
||||
|------|----------|
|
||||
| `app/Services/Calculation/FormulaParser.php` | `eval("return {$expression};")` 2곳 → `SafeMathEvaluator::calculate()`, `::compare()` |
|
||||
| `app/Services/Quote/FormulaEvaluatorService.php` | `eval("return {$expression};")` 1곳 → `SafeMathEvaluator::calculate()` |
|
||||
|
||||
### 3.3 변경 전후 비교
|
||||
|
||||
**FormulaParser::executeSimpleMath() (234행)**
|
||||
|
||||
```php
|
||||
// Before
|
||||
return eval("return {$expression};");
|
||||
|
||||
// After
|
||||
return SafeMathEvaluator::calculate($expression);
|
||||
```
|
||||
|
||||
**FormulaParser::evaluateCondition() (279행)**
|
||||
|
||||
```php
|
||||
// Before
|
||||
return eval("return {$expression};");
|
||||
|
||||
// After
|
||||
return SafeMathEvaluator::compare($expression);
|
||||
```
|
||||
|
||||
**FormulaEvaluatorService::calculateExpression() (316행)**
|
||||
|
||||
```php
|
||||
// Before
|
||||
// TODO: 프로덕션에서는 symfony/expression-language 등 안전한 라이브러리 사용 권장
|
||||
return (float) eval("return {$expression};");
|
||||
|
||||
// After
|
||||
return \App\Helpers\SafeMathEvaluator::calculate($expression);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Shunting-yard 알고리즘 개요
|
||||
|
||||
```
|
||||
입력: "3160 * 4350 / 1000000"
|
||||
|
||||
1. 토큰화
|
||||
[3160] [*] [4350] [/] [1000000]
|
||||
|
||||
2. 중위 → 후위(RPN) 변환
|
||||
[3160] [4350] [*] [1000000] [/]
|
||||
|
||||
3. RPN 스택 계산
|
||||
3160 * 4350 = 13,746,000
|
||||
13,746,000 / 1,000,000 = 13.746
|
||||
|
||||
결과: 13.746
|
||||
```
|
||||
|
||||
**연산자 우선순위:**
|
||||
|
||||
| 우선순위 | 연산자 |
|
||||
|:--------:|--------|
|
||||
| 4 | 단항 마이너스 |
|
||||
| 3 | `*`, `/`, `%` |
|
||||
| 2 | `+`, `-` |
|
||||
|
||||
---
|
||||
|
||||
## 5. 검증 결과
|
||||
|
||||
### 5.1 SafeMathEvaluator 단위 테스트
|
||||
|
||||
```
|
||||
1) 2+3 = 5 ✅
|
||||
2) 10*5-3 = 47 ✅
|
||||
3) (2+3)*4 = 20 ✅
|
||||
4) 3160*4350/1000000 = 13.746 ✅ (BOM 면적 계산)
|
||||
5) -5+10 = 5 ✅ (단항 마이너스)
|
||||
6) 14.17*3.0 = 42.51 ✅ (소수점)
|
||||
7) ((2+3)*4)/2 = 10 ✅ (중첩 괄호)
|
||||
8) 10%3 = 1 ✅ (나머지)
|
||||
9) 3000<=6000 = true ✅ (비교)
|
||||
10) 7000<=6000 = false ✅
|
||||
11) 100==100 = true ✅
|
||||
12) 5>3&&2<4 = true ✅ (논리 AND)
|
||||
```
|
||||
|
||||
### 5.2 FormulaParser 통합 테스트
|
||||
|
||||
```
|
||||
1) screen_size: W1=3160 H1=4350 ✅ (미리 정의된 함수)
|
||||
2) area: 13.746 ✅ (산술 수식 → SafeMathEvaluator)
|
||||
3) bracket_qty (W1=5000): 3 ✅ (조건식 → SafeMathEvaluator)
|
||||
4) bracket_qty (W1=2000): 2 ✅
|
||||
5) bracket (predefined, W1=5500): 3 ✅
|
||||
6) weight: 70.002 ✅ (중량 계산)
|
||||
```
|
||||
|
||||
### 5.3 FormulaEvaluatorService 통합 테스트
|
||||
|
||||
```
|
||||
1) validateFormula('W * H + 100'): valid ✅
|
||||
2) validateFormula(''): invalid ✅
|
||||
3) validateFormula('(W + H'): invalid ✅ (괄호 불일치)
|
||||
```
|
||||
|
||||
### 5.4 eval() 잔존 확인
|
||||
|
||||
```bash
|
||||
grep -r "eval(" app/ # 결과: 0건 (주석 제외)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 테스트 체크리스트
|
||||
|
||||
- [x] SafeMathEvaluator 기본 사칙연산
|
||||
- [x] SafeMathEvaluator 비교 연산 + 논리 연산
|
||||
- [x] SafeMathEvaluator 단항 마이너스, 소수점, 중첩 괄호
|
||||
- [x] FormulaParser 미리 정의된 함수 (eval 미사용 경로)
|
||||
- [x] FormulaParser 산술 수식 (eval → SafeMathEvaluator 경로)
|
||||
- [x] FormulaParser 조건식 (eval → SafeMathEvaluator 경로)
|
||||
- [x] FormulaEvaluatorService 수식 검증
|
||||
- [x] app/ 전체 eval() 잔존 검사: 0건
|
||||
- [x] Pint 코드 포맷팅 통과
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- OWASP A03:2021 Injection — [https://owasp.org/Top10/A03_2021-Injection/](https://owasp.org/Top10/A03_2021-Injection/)
|
||||
- CWE-95: Improper Neutralization of Directives in Dynamically Evaluated Code (Eval Injection)
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-03-15
|
||||
Reference in New Issue
Block a user