autopwn 개발기 ① — 왜 자가 호스팅 풀 킬체인 도구를 직접 짜기 시작했나

2026-05-17·1분 읽기·

autopwn 개발기 ① — 왜 자가 호스팅 풀 킬체인 도구를 직접 짜기 시작했나

외부 SaaS 펜테스트 플랫폼 대신 본인 자산·CTF·서면 동의 engagement만 다루는 자가 호스팅 풀 킬체인 오케스트레이션을 직접 짜기로 결정한 이유와, Phase 0~1 빌드 전에 못박은 여덟 가지 설계 결정을 정리한다.

시리즈: autopwn 개발기 ① 목표: zino.kr admin + Zino Android 앱 두 클라이언트가 공유하는 자가 호스팅 풀 킬체인 펜테스트 오케스트레이션 이 글의 범위: 왜 만들기 시작했는가 + Phase 1 코드 한 줄 짜기 전에 못박은 설계 결정 8가지


🧭 발단 — 외부 SaaS는 왜 충분치 않았나

펜테스트 자동화 도구는 시장에 이미 많다. 그런데 다음 세 가지가 동시에 걸렸다.

첫째, scope 통제권. 외부 SaaS는 결국 그쪽 정책과 ToS에 묶인다. 본인 자산이라도 그쪽이 "이건 안 됨"이라고 하면 안 된다. CTF·HTB 환경처럼 정상 사용인데도 막히는 경우가 흔하다.

둘째, OPSEC 일관성. 어떤 도구는 outbound를 그대로 흘려보내고, 어떤 도구는 자체 프록시를 쓴다. 도구마다 egress가 다르면 결국 IP 노출 면적이 넓어진다. 모든 도구의 outbound를 한 채널(Tor)로 강제하려면 도구 위에 한 겹을 직접 깔아야 한다.

셋째, 두 클라이언트 통일. 같은 백엔드를 zino.kr 어드민 콘솔과 Zino Android 앱이 동시에 쓰려면 OpenAPI 계약을 내가 통제하는 편이 훨씬 깔끔하다. 두 클라이언트가 SaaS의 응답 포맷에 묶이는 건 별로다.

결론은 단순했다 — 직접 짠다. 단, 합법성 가드레일을 정책 문서가 아니라 코드로 fail-closed 하게.


🎯 다루는 범위

카테고리다룸
본인 소유 자산 (서버·홈랩·VPS)
HTB / TryHackMe / DreamHack / CTF
서면 동의된 engagement
외부 임의 타깃❌ — scope 가드가 거부
Shodan/Censys 매스 익스플로잇❌ — 어댑터 자체 미허용
자동 익스 실행❌ — AI는 제안만, 실행은 사람 승인 필요

scope에 등록되지 않은 호스트는 아무것도 돌아가지 않는다. 빈 allowlist가 디폴트다.


🏗️ 설계 결정 여덟 가지 — 코드 한 줄 짜기 전에 박아놓음

이건 README 같은 게 아니라 진짜 CLAUDE.md에 "변경 금지"로 박혀 있다. Phase 1 빌드 중 한 줄이라도 우회하고 싶을 때 다시 와서 확인하는 헌법 같은 거.

1. autopwn API는 loopback only

127.0.0.1:8080만 바인딩한다. 외부 노출 안 한다. 두 클라이언트 모두 zino.kr 홈페이지의 서버 사이드 프록시를 경유한다. 즉 인터넷에서 autopwn으로 직접 못 들어온다.

2. JWT 없음, 단일 X-API-Key

사용자 세션 검증은 홈페이지 쪽 책임. autopwn은 서버끼리 통신하는 채널이라 단일 admin key 하나로 충분하다. 토큰 회전·refresh·revoke 로직을 백엔드에 두지 않는 게 면적을 줄이는 길.

3. 모든 도구 outbound는 proxychains+Tor 강제

PROXY_CHAIN=true가 기본이고, 어댑터는 app/tools/_proc.pyrun_proxied() / socks_env() 만 호출한다. 도구별로 자체 SOCKS 옵션이 있어도(예: nuclei -proxy) proxychains 래퍼를 한 번 더 거친다. 도구가 가끔 DNS 누설하는 걸 막기 위해서다.

4. 실시간 채널은 SSE (WebSocket 아님)

  • Next.js (homepage): EventSource 1급 지원
  • OkHttp (Zino app): SSE 잘 됨, WebSocket 양방향 필요 없음
  • 운영자가 봐야 하는 건 단방향 이벤트 스트림 — run.started, step.running, findings.added, session.opened

WebSocket을 양쪽 클라이언트에 맞추는 비용이 SSE보다 항상 더 크다.

5. Scope guard fail-closed + AI step 별도 검증

  • scope/allowlist.yaml이 비어있으면 아무것도 돌지 않는다
  • 모든 어댑터 진입부에서 scope_guard.require(target) 1회 호출
  • AI가 추천한 step도 도구 화이트리스트 + 타깃 재검증 (validate_step_dict)
def require(target: str) -> None:
    ok, reason = check(target)
    if not ok:
        raise ScopeError(f"scope denied: {reason}")

if not authorized: throw 가 코드 어디 한 군데에만 있고 나머지는 정책 문서에 적혀있는 형태가 가장 위험하다. 모든 진입부에 박아둔다.

6. AI 기본 OFF, per-run opt-in, per-playbook opt-out

  • 디폴트로 AI 백엔드(HexStrike)는 docker compose profile에 빠져 있어 빌드도 안 됨
  • 실행 시 ai_escalation=true 명시해야 활성
  • C2/익스 플레이북은 disallow_ai: true 로 강제 차단
  • AI는 제안만 한다. 실행은 항상 사람 승인 → deterministic runner

AI가 자동 익스를 돌리는 도구는 그게 매력 포인트일 수 있지만 합법성 측면에서는 정확히 정반대.

7. C2 listener 외부 바인딩 금지

docker compose에서 sliver/msf의 listener는 127.0.0.1:8443 등 loopback only. 외부 노출이 필요한 engagement는 별도 compose override 파일을 그때만 만들어 쓴다. 디폴트 파일에서는 절대 0.0.0.0 안 박는다.

8. 금지 어댑터 enumeration

CLAUDE.md에 박힌 절대 금지 목록:

  • Shodan/Censys 매스 익스플로잇 어댑터
  • 임의 shell adapter (사용자가 임의 명령을 넣는 통로)
  • AI 자동 실행
  • "scope skip" 플래그 (어떤 이유로도)
  • autopwn API 인터넷 직접 노출

각 항목은 "있으면 편한데 OPSEC/합법성 면적이 폭발한다" 류 — 편의 vs 면적 트레이드오프에서 면적을 줄이기로 한 결과.


🧩 아키텍처 한 장 요약

┌──────────────────────────────────┐
│  Browser / Android phone         │
│  (사용자 세션 인증)              │
└────────────────┬─────────────────┘
                 │ HTTPS

┌──────────────────────────────────┐
│  zino.kr (Next.js)               │
│  - 세션 검증                     │
│  - 서버 사이드 프록시            │
│    https://zino.kr/api/          │
│      playground/autopwn/*        │
└────────────────┬─────────────────┘
                 │ X-API-Key (loopback)

┌──────────────────────────────────┐
│  autopwn API   127.0.0.1:8080    │
│  - FastAPI                       │
│  - SSE /events/stream            │
│  - scope_guard fail-closed       │
│  - runner.ADAPTERS (whitelist)   │
└──┬────────────────┬──────────────┘
   │                │
   ▼                ▼
┌────────┐    ┌──────────────┐
│ Tools  │    │ Redis (state)│
│ via    │    │ Postgres     │
│ proxy- │    │ (findings)   │
│ chains │    └──────────────┘
│ + Tor  │
└────────┘

   ▼ (모든 outbound)
[ Tor SOCKS5 ]

도구 추가 = api/app/tools/<name>.py 작성 → scope_guard.require(target) 한 줄 → run_proxied() 호출 → runner.ADAPTERS dict에 등록. 진입부에 가드를 깜빡할 가능성을 어댑터 PR 리뷰에서 잡는 게 유일한 룰.


🗂️ 디렉터리 — Phase 1 종료 시점

Hacking/
├── api/
│   ├── app/
│   │   ├── main.py              # 8개 라우터 mount
│   │   ├── core/
│   │   │   ├── auth.py          # X-API-Key 단일
│   │   │   ├── settings.py      # PROXY_CHAIN/TOR_HOST/...
│   │   │   └── events.py        # Redis pub/sub
│   │   ├── routers/             # health/modules/scope/playbooks/
│   │   │                        # findings/listeners/sessions/events
│   │   ├── orchestrator/
│   │   │   ├── scope_guard.py   # check/require/validate_step_dict
│   │   │   ├── loader.py        # playbook YAML loader
│   │   │   ├── runner.py        # ADAPTERS dict (whitelist)
│   │   │   └── store.py         # Redis ZSET 인덱스
│   │   ├── tools/
│   │   │   ├── _proc.py         # run_proxied + socks_env
│   │   │   ├── nmap.py          # raw-flag strip, -sT -Pn 강제
│   │   │   ├── nuclei.py        # -proxy socks5 + HTTP_PROXY
│   │   │   ├── msf.py           # setg Proxies once, bind reject
│   │   │   └── sliver.py        # gRPC, 어댑터 3종
│   │   └── ai/
│   │       └── escalation.py    # HexStrike POST, default OFF
│   ├── playbooks/
│   │   ├── recon_basic.yaml          # nmap connect only
│   │   ├── web_vuln_chain.yaml       # port_check → nuclei
│   │   └── reverse_shell_drop.yaml   # disallow_ai: true
│   └── Dockerfile               # proxychains4 + nuclei
├── ops/
│   ├── proxychains/proxychains4.conf
│   └── docker/api-entrypoint.sh
├── scope/
│   └── allowlist.yaml           # 빈 상태 = fail-closed
├── docker-compose.yml           # tor/pg/redis/sliver/msf/api/worker
└── docs/
    ├── handoff.md
    ├── integration-homepage.md
    └── integration-zino.md

🧪 두 클라이언트, 한 OpenAPI

  • homepage /playground/hacking/autopwn — Next.js 어드민 콘솔. SSE EventSource로 라이브 뷰. 도구 통합 가이드 docs/integration-homepage.md
  • Zino Android :modules:autopwn — Compose UI + OkHttp SSE. 도구 통합 가이드 docs/integration-zino.md

두 클라이언트 모두 https://zino.kr/api/playground/autopwn/* 프록시 경유 — autopwn 직접 호출 금지. 이걸로 사용자 세션 검증 + admin key 부착 + IP 면적 모두 zino.kr 한 곳에서 처리.


🚦 다음 글

autopwn 개발기 ② — Phase 1 빌드는 끝났는데 첫 검증 시도에서 세 군데에서 막혔다. msf docker entrypoint의 YAML folded scalar 버그, sliver-py가 끌고 들어오는 grpcio 누락, 그리고 proxychains4 strict_chain의 hostname 거부.

도구가 안 도는 게 아니라 연결 면적의 미세 균열이 무릎을 꺾는 종류의 에러들이라 기록 가치가 있었다.


📝 정리

  • 외부 SaaS의 scope 정책·OPSEC 불일치·두 클라이언트 통일 필요로 자가 호스팅으로 결정
  • 합법성은 정책 문서가 아니라 fail-closed 코드로 강제 (scope/allowlist.yaml 비면 아무것도 안 돔)
  • loopback only · 단일 X-API-Key · proxychains+Tor 강제 · SSE · AI 옵션 다섯 가지가 핵심 결정
  • Phase 0/1 백엔드 골격 완료, Phase 2 (homepage) / Phase 3 (Zino app) / Phase 4 (AI escalation) 가 남음
ShareX

이 글이 도움이 됐나요?