본문 바로가기
C.W.K.
Stream
Lesson 01 of 04 · published

명령어 창구를 아예 안 만든다

~14 min · firelink, command-injection, typed-capability, security

Level 0식은 재
0 XP0/33 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"구멍을 단단히 막는 게 아냐. 애초에 안 뚫는 거지."

shell 로 빠지는 admin 패널

web 허브에 '서비스 다시 띄우기' 를 붙인다고 생각해봐. 제일 자연스러운 방법은 UI 에서 서비스 이름을 받아 명령 문자열에 끼워 돌리는 거야. 첫 시도에 잘 되고, 사고가 예약돼 있지. 그 이름에 세미콜론을 넣을 수 있는 순간, 서비스를 다시 띄우는 버튼이 홈 디렉터리도 지울 수 있게 돼. 이게 명령어 주입이야. 희귀한 버그가 아니라, 유저가 준 문자열이 실행 명령에 끼어들게 두면 원래 이렇게 되는 거야.

구멍을 지키지 말고 없애

보통은 걸러내는 쪽으로 가. 문자열을 escape 하고, 검사하고, 허용 문자만 남기고. Firelink 는 더 센 길을 골랐어. 구멍 자체를 안 만드는 거야. 유저가 친 문자열은 실행 인자도, 라벨도, 경로도, shell 조각도 되지 않아. 모든 작업은 registry 와 정해진 adapter 를 거쳐 풀리는 타입 있는 권한이거든. 서비스를 다시 띄우고 싶으면 이름을 넘기는 게 아니라, 서비스 id 가 이미 등록돼 있는 restart-service 권한을 풀어. 그럼 adapter 가 유저 문자열이 한 글자도 안 들어간 정해진 명령을 돌리지. 주입이 앉을 자리가 없어. 유저가 친 게 실행 경로에 닿질 않으니까.

구조상 뚫릴 데가 없어

방어랑 설계의 차이가 여기야. 걸러내기는 방어야. 위험한 구멍을 지키면서 빠져나갈 구멍을 다 막았기를 바라는 거지. 타입 있는 권한은 설계고. 위험한 구멍이 애초에 안 지어졌어. 등록된 id 는 아는 adapter 로 풀리거나 안 풀리거나 둘 중 하나야. 'restart' 를 'delete' 로 둔갑시키는 영리한 문자열 같은 건 없어. 작업이 글자에서 조립되는 게 아니라 닫힌 목록에서 골라지니까. 계속 완벽하게 유지해야 하는 필터가 아니라, 처음부터 구멍이 없는 모양인 거야.

명령어 창구를 걸러내려 하지 말고 아예 갖지 마. 작업이 policy 가 풀어주는 닫힌 목록에서 골라지면, 유저 입력은 허락된 걸 고를 순 있어도 새 걸 만들어낼 순 없어. 주입은 막히는 게 아니라 구조상 불가능해지고, 그게 믿을 수 있는 유일한 불가능이야.

Code

명령어 창구(사고) vs 타입 있는 권한(창구 자체가 없음)·python
# ANTI-PATTERN: a raw command surface. The classic breach.
def restart(service_name):                 # service_name comes from the UI
    subprocess.run(f"launchctl kickstart -k {service_name}", shell=True)
    #   service_name = 'foo; rm -rf ~'  ->  game over.
    #   Sanitizing this is a game you have to win every single time.

# FIRELINK: no raw command surface. Resolve a typed capability instead.
def restart(cap: Capability):              # a typed value, never a string
    assert cap.kind == "restart-service"
    adapter = registry.restart_adapter(cap.service_id)  # a REGISTERED id
    if adapter is None:
        raise Unauthorized(cap.service_id)  # unknown id -> nothing happens
    adapter.execute()                       # fixed argv; no user string in it
    #   There is no slot for '; rm -rf ~' to go. The surface does not exist.

External links

Exercise

네 시스템에서 유저 입력이 결국 명령이나 쿼리, 경로, shell 에 닿는 자리를 찾아봐. 쿼리가 되는 검색창이든, 리소스 이름을 받는 admin 버튼이든. 그게 걸러내기로 지켜지고 있어, 설계로 지켜지고 있어? 걸러내기라면 골라 쓰는 방식으로 바꿔봐. 허락된 작업의 닫힌 목록을 두고 id 로 고르게, 유저가 친 글자는 고르기만 하고 조립은 못 하게. 그러면 주입이라는 질문 자체를 못 하게 되는 걸 볼 수 있어.
Hint
시험은 간단해. 유저 입력이 어느 작업이 도는지를 바꾸거나 인자를 덧붙일 수 있어, 아니면 서버가 정해둔 목록에서 고르기만 해? 조립할 수 있으면 걸러내는 중이고, 고르기만 되면 창구가 사라진 거야.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인 — 댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.