[Web] Web · 작성: 2026-07-20 03:10:34 · 수정: 2026-07-20 03:11:15 · 조회 8
애플리케이션이 사용자 입력을 그대로(혹은 살짝 가공해서) 셸 명령의 일부로 넘길 때 생기는 취약점이다. SQL Injection이 DB 쿼리 문법을 깨는 거라면, Command Injection은 셸 문법을 깬다.
// 취약한 코드
const { execSync } = require('child_process');
execSync('ping -c 1 ' + req.query.host);
host에 8.8.8.8; cat /etc/passwd를 넣으면 셸은 세미콜론을 명령 구분자로 해석해서 두 번째 명령을 그대로 실행한다.
공격자가 흔히 쓰는 구분자는 세미콜론, 파이프, &&, ||, 백틱, $()다. 필터링이 특정 문자만 막으면 다른 문자로 우회한다.
8.8.8.8 | id
8.8.8.8 & id
8.8.8.8 $(id)
공백까지 막힌 필터라면 \${IFS}(Internal Field Separator)로 공백을 대체하기도 한다: cat\${IFS}/etc/passwd
필터링·이스케이프는 우회 가능성이 항상 남는다. 진짜 방어는 셸 인터프리터 자체를 거치지 않는 API를 쓰는 것이다.
// 안전: 인자를 배열로 분리해서 넘기면 셸 파싱을 거치지 않는다
const { execFileSync } = require('child_process');
execFileSync('ping', ['-c', '1', req.query.host]);
execFile/spawn은 인자를 배열로 받아서 OS에 직접 프로세스 실행을 요청하기 때문에, host 값에 셸 메타문자가 들어있어도 그냥 하나의 인자 문자열로만 취급된다. 애초에 셸 파싱 단계 자체가 없어서 우회가 불가능하다.