YAML > (folded block scalar)는 이론적으로 줄바꿈을 공백으로 합쳐주지만, 큰따옴표 안에서의 동작과 docker compose의 argv 분리 룰이 합쳐지면 결과가 예측대로 가지 않는다. bash가 받은 스크립트는 멀티라인이 됐고 첫 줄이 msfrpcd ... 라서 PATH에서 찾지 못한다.
게다가 metasploit-framework 이미지의 실제 PATH에는 msfrpcd가 없다. 바이너리는 /usr/src/metasploit-framework/msfrpcd 에 있고 WORKDIR이 그 디렉터리라 ./msfrpcd 로 불러야 한다.
patch 후 정상 부팅 — list-form command + ./msfrpcd 상대경로. 로그에 MSGRPC starting on 0.0.0.0:55553 와 MSGRPC ready 가 잡히고 컨테이너 상태가 Restarting 이 아닌 Up.
교훈
shell-form command: 는 한 줄에 끝낼 게 아니면 쓰지 말 것. list-form이 항상 안전하다.
베이스 이미지에 WORKDIR + 상대 실행 컨벤션이 있으면 bash -lc로 감싸지 말고 직접 호출해야 한다. bash 로그인 셸의 PATH는 컨테이너 운영자가 의도한 게 아니다.
🪤 Trap #2 — API가 import 단계에서 죽음 (grpcio 누락)
증상
docker compose up -d api 직후:
curl -sv http://127.0.0.1:8080/healthz# * connect to 127.0.0.1 port 8080 failed: Connection refused
docker compose logs api:
File "/app/app/main.py", line 8, in <module> from .routers import events, findings, health, listeners, ...File "/app/app/routers/listeners.py", line 5, in <module> from ..tools import sliverFile "/app/app/tools/sliver.py", line 15, in <module> from sliver import SliverClient, SliverClientConfigFile ".../site-packages/sliver/beacon.py", line 22, in <module> import grpcModuleNotFoundError: No module named 'grpc'
원인
sliver-py==0.0.18 가 내부에서 grpc 를 import 하는데, 이 패키지는 grpcio 를 자동 의존으로 끌고 오지 않는다. PyPI 메타데이터에서 grpcio가 install_requires 에 빠져있거나 extras로 빠져있는 듯.
비슷한 패턴이 sliver-py 만의 문제가 아니라서 — Python ML/RPC 라이브러리들이 grpcio 같은 무거운 C 확장을 옵셔널로 두는 경우가 많다.
패치
api/requirements.txt:
sliver-py==0.0.18grpcio==1.66.2protobuf==5.28.2
sliver-py 와 호환되는 버전 페어가 중요. 최신 grpcio가 sliver-py의 proto와 깨질 수도 있어서 마이너 버전까지 핀.
빌드 재실행:
docker compose build api workerdocker compose up -d api workerdocker compose logs api --tail=10# {"IsTor":true,"IP":"107.189.8.70"}# INFO: Uvicorn running on http://0.0.0.0:8080
엔트리포인트가 부팅 전에 Tor 도달성을 확인하고 시작하는 게 잘 동작했다. IsTor:true 가 정상.
api 부팅 로그 — proxychains4.conf 렌더 → Tor 도달성 검증 (IsTor:true) → Uvicorn 시작 순서. import 단계 통과의 직접 증거.
교훈
Python 의존성은 transitive 가 가장 위험하다. sliver-py 만 핀하지 말고 grpcio/protobuf 까지 명시 핀.
부팅 전 헬스체크(엔트리포인트의 Tor 확인)가 있다면 거기서 import 에러가 잡히도록 import 까지 강제 트리거하는 것도 방법.
🪤 Trap #3 — proxychains4가 hostname proxy 를 거부
증상
scope에 타깃 등록, recon_basic 한 번 돌렸더니 30초 만에 실패:
curl -s .../playbooks/runs/$RUN_ID | jq .steps[0].summary# "nmap failed (rc=1): proxy tor has invalid value or is not numeric# non-numeric ips are only allowed under the following circumstances:# chaintype == strict (true), proxy is not first in list (false), pr"
엔트리포인트가 envsubst 로 ${TOR_HOST} 를 그대로 tor 라는 도커 서비스명으로 치환했다. proxychains4 의 strict_chain 모드는 첫 번째 프록시가 hostname 이면 거부한다. (random_chain 모드 + 첫 번째 아닌 경우만 hostname 허용)
도커 내부 DNS로 풀린다고 proxychains가 알 도리는 없으니까 — proxychains 입장에서는 "numeric IP 줘"가 합리적이다.
5회 retry 는 docker 네트워크 부팅 직후 DNS가 잠깐 안 풀릴 때를 위한 안전망. 보통 1회에 풀린다.
재기동 후 컨테이너 내부:
docker compose exec api cat /etc/proxychains4.conf | tail -3# [ProxyList]# # Default: Tor SOCKS5 from the `tor` service in docker-compose.# socks5 172.18.0.3 9050
엔트리포인트 로그도 깔끔:
[entrypoint] proxychains4.conf rendered (tor=tor→172.18.0.3:9050)[entrypoint] PROXY_CHAIN=true — verifying Tor reachability...{"IsTor":true,"IP":"193.189.100.199"}
컨테이너 안의 /etc/proxychains4.conf — ${TOR_HOST} 가 numeric IP (172.18.0.5) 로 미리 resolve 되어 박힘. entrypoint 가 getent hosts 로 한 단계 사전 처리.
교훈
proxychains의 strict_chain은 numeric IP를 요구한다. docker 환경에서 서비스명을 그대로 쓰면 안 됨.
envsubst 같은 단순 치환을 쓸 때는 엔트리포인트에서 한 단계 사전 처리(여기서는 getent hosts)를 끼우는 게 안전.
Tor 도달성 자체는 별도 도구(curl --socks5-hostname 직접 호출)로 검증 — proxychains 와 분리되어 있다.
tor 체크가 false 인데 이건 readyz 가 httpx 로 socks5h 를 직접 시도해서 그런 거고(httpx 본체는 socks5h 미지원), 엔트리포인트의 check.torproject.org 호출은 IsTor:true 받아옴. 즉 실제 도구 트래픽은 정상 — readyz 의 cosmetic 버그다. 다음 일감으로 적어두고 통과.
Comments
댓글
댓글을 남기려면 로그인이 필요해요. (네이버 · 구글 계정)
댓글 불러오는 중…