"구멍을 단단히 막는 게 아냐. 애초에 안 뚫는 거지."
shell 로 빠지는 admin 패널
web 허브에 '서비스 다시 띄우기' 를 붙인다고 생각해봐. 제일 자연스러운 방법은 UI 에서 서비스 이름을 받아 명령 문자열에 끼워 돌리는 거야. 첫 시도에 잘 되고, 사고가 예약돼 있지. 그 이름에 세미콜론을 넣을 수 있는 순간, 서비스를 다시 띄우는 버튼이 홈 디렉터리도 지울 수 있게 돼. 이게 명령어 주입이야. 희귀한 버그가 아니라, 유저가 준 문자열이 실행 명령에 끼어들게 두면 원래 이렇게 되는 거야.
구멍을 지키지 말고 없애
보통은 걸러내는 쪽으로 가. 문자열을 escape 하고, 검사하고, 허용 문자만 남기고. Firelink 는 더 센 길을 골랐어. 구멍 자체를 안 만드는 거야. 유저가 친 문자열은 실행 인자도, 라벨도, 경로도, shell 조각도 되지 않아. 모든 작업은 registry 와 정해진 adapter 를 거쳐 풀리는 타입 있는 권한이거든. 서비스를 다시 띄우고 싶으면 이름을 넘기는 게 아니라, 서비스 id 가 이미 등록돼 있는 restart-service 권한을 풀어. 그럼 adapter 가 유저 문자열이 한 글자도 안 들어간 정해진 명령을 돌리지. 주입이 앉을 자리가 없어. 유저가 친 게 실행 경로에 닿질 않으니까.
구조상 뚫릴 데가 없어
방어랑 설계의 차이가 여기야. 걸러내기는 방어야. 위험한 구멍을 지키면서 빠져나갈 구멍을 다 막았기를 바라는 거지. 타입 있는 권한은 설계고. 위험한 구멍이 애초에 안 지어졌어. 등록된 id 는 아는 adapter 로 풀리거나 안 풀리거나 둘 중 하나야. 'restart' 를 'delete' 로 둔갑시키는 영리한 문자열 같은 건 없어. 작업이 글자에서 조립되는 게 아니라 닫힌 목록에서 골라지니까. 계속 완벽하게 유지해야 하는 필터가 아니라, 처음부터 구멍이 없는 모양인 거야.