8957890d13
- 00_Raw: ASTRA 보안 가이드 3종(SSRF/셸 명령/파일 경로 경계), 회의록 p/q/r 추가 - Topics: Digests 5종, lessons 4종, 메모리 에피소드/장기기억 갱신 - .astra: growth(decay/regression/weakness)·eval(corrections/report) 학습 산출물 갱신 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
8.0 KiB
8.0 KiB
id, title, category, status, verification_status, canonical_id, aliases, duplicate_of, source_trust_level, confidence_score, created_at, updated_at, review_reason, merge_history, tags, raw_sources, applied_in, github_commit
| id | title | category | status | verification_status | canonical_id | aliases | duplicate_of | source_trust_level | confidence_score | created_at | updated_at | review_reason | merge_history | tags | raw_sources | applied_in | github_commit | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| astra-ssrf-url-fetch-guard-20260619 | ASTRA SSRF 방어 — 신뢰 불가 URL fetch 경계(assertPublicUrl) | Security | applied | validated |
|
S | 0.95 | 2026-06-19 | 2026-06-19 |
|
|
|
ASTRA SSRF 방어 URL fetch 경계
🎯 한 줄 통찰 (One-line insight)
모델/웹 콘텐츠가 준 URL을 호스트 검증 없이 fetch 하던 SSRF 경로를, "공인 IP 가 아니면 거부"하는 allowlist 성격의 assertPublicUrl 가드로 닫고 리다이렉트 각 홉까지 재검증한다.
🧠 핵심 개념 (Core concepts)
- SSRF (Server-Side Request Forgery): 신뢰 불가 입력으로 서버가 내부 자원에 요청하게 만드는 공격. 여기선 "에이전트 프로세스"가 내부망/메타데이터/로컬 브릿지에 도달.
- 차단 대상 IP: 루프백(127/8·::1), 사설(10/8·172.16/12·192.168/16·fc00::/7), 링크로컬(169.254/16=클라우드 메타데이터·fe80::/10), CGNAT/멀티캐스트/예약, IPv4-mapped IPv6.
- 단일 chokepoint:
buildUrlContext입구에서 한 번 검증 → 브릿지 추출·직접 fetch 양 경로 모두 보호. - 리다이렉트 재검증:
redirect:'manual'+ 각 홉assertPublicUrl재호출로 공인→사설 우회 차단.
🩺 증상 (Symptom)
일반 챗의 URL 주입과 에이전트 <fetch_url>이 모델/페이지가 제시한 임의 URL을 redirect:'follow'로 그대로 fetch했다. 악성 페이지가 http://127.0.0.1:3002(로컬 브릿지)·http://169.254.169.254/...(메타데이터)·NAS/인트라넷 주소를 제시하면 그 응답 본문이 모델 컨텍스트로 주입될 수 있었다.
🌐 환경 / 범위 (Environment & scope)
- 프로젝트: ASTRA (
E:/Wiki/astraai). Node 18+ globalfetch. - 경로:
urlContext.buildUrlContext(일반 챗 +fetch_url액션 공용) → ① 브릿지 추출 → ②fetchUrlDirect폴백. - 비대상:
bridgeFetch(의도적 localhost:3002 도구 호출)는 SSRF 가드 미적용 — 별도 함수.
🔁 재현 절차 (Reproduction)
- 챗 또는 모델 응답에
http://169.254.169.254/latest/meta-data/같은 내부 URL 포함. - 기존
fetchUrlDirect:http/https만 확인하고 목적지 IP 미검증 → 그대로 요청, 본문 반환(webFetch.ts). - 공인 도메인 → 302 리다이렉트로 사설 IP 유도 시
follow가 그대로 추적.
🔥 영향 및 심각도 (Impact & severity)
High. 내부망 스캐닝·클라우드 메타데이터 자격증명 탈취·로컬 전용 서비스(브릿지) 호출 가능. 입력 출처가 untrusted(웹/모델)라 악용 난도 낮음.
🧠 근본 원인 (Root cause)
fetchUrlDirect가 스킴(http/https)만 검사하고 호스트→IP 해석 후 사설/예약 범위 검증이 전무.buildUrlContext가 URL을 브릿지·직접 양쪽으로 무검증 전달.redirect:'follow'로 리다이렉트 목적지 재검증 불가.
🔎 조사 과정 (Investigation)
- 보안 감사로
webFetch.ts:81-103·agent/actions/webFetch.ts:20의 무필터 fetch 경로 식별. - 외부 표준 리서치: OWASP는 denylist보다 allowlist(공인 IP 아니면 거부), DNS 리바인딩 방지엔 해석 시점 IP 검증/피닝 권고 [S5].
🛠️ 해결 (Resolution / applied fix)
- 신규 모듈 ssrfGuard.ts:
isBlockedIPv4/IPv6/Ip,assertPublicUrl(url)— IP 리터럴 즉시 검사, DNS 호스트는 모든 A/AAAA 레코드 해석 후 하나라도 차단 대상이면 거부.localhost류는 해석 전 거부. - urlContext.ts 입구에
assertPublicUrl— 실패 시 "🚫 URL 접근 차단" 정직 블록 반환(브릿지·직접 양 경로 차단). - webFetch.ts
fetchUrlDirect:redirect:'manual'로 전환, 최대 5홉 루프에서 각 홉assertPublicUrl재검증(이중 방어 + 독립 호출자 보호).
💻 코드 패턴 (Code patterns)
// ssrfGuard.ts — "공인 아니면 거부"
export async function assertPublicUrl(rawUrl: string): Promise<void> {
const host = new URL(rawUrl).hostname.replace(/^\[|\]$/g, '');
if (isIP(host)) { if (isBlockedIp(host)) throw new SsrfBlockedError(`차단된 내부 IP: ${host}`); return; }
if (/^localhost$/i.test(host) || host.endsWith('.localhost')) throw new SsrfBlockedError('로컬 호스트');
const addrs = await dnsLookup(host, { all: true }); // 모든 레코드 검사(다중 IP 우회 방지)
for (const { address } of addrs)
if (isBlockedIp(address)) throw new SsrfBlockedError(`내부 IP 해석: ${host} → ${address}`);
}
// webFetch.ts — 리다이렉트 각 홉 재검증
for (let hop = 0; hop <= 5; hop++) {
try { await assertPublicUrl(current); } catch (e) { return fail(`차단됨(SSRF 방지): ${e.message}`); }
res = await fetch(current, { redirect: 'manual', /* … */ });
if (res.status >= 300 && res.status < 400 && res.headers.get('location')) {
current = new URL(res.headers.get('location'), current).toString(); continue;
}
break;
}
✅ 검증 (Verification)
- 신규 tests/ssrfGuard.test.ts 9개: IPv4/IPv6 범위, 내부 IP URL 거부,
localhost거부, 잘못된 URL 거부. urlContextBuild.test.ts는 SSRF 가드를 모킹(할루시네이션 로직 검증 전용, DNS 비의존화).tsc0 에러, 전체 716 통과.
⚖️ 모순 및 업데이트 (Contradictions & updates)
한계(정직 고지): 해석 시점 검증이라 fetch 가 재해석하는 사이 레코드가 바뀌는 DNS 리바인딩 창은 완전히 닫지 못한다. 완전 차단은 연결 IP 고정(커스텀 undici dispatcher)이 필요 — 정적 사설 IP·리다이렉트 우회는 차단됨. 브릿지(:3002) 자체의 서버사이드 fetch SSRF는 브릿지(별도 프로젝트)의 책임.
🛠️ 적용 사례 (Applied in summary)
ASTRA 일반 챗 URL 주입 + 에이전트 fetch_url 액션 + /wikify 폴백(직접 fetch) 경로에 적용.
✅ 검증 상태 및 신뢰도
- 상태: applied (미커밋)
- 검증 단계: validated (단위 테스트 + 빌드/타입)
- 출처 신뢰도: S
- 신뢰 점수: 0.95
- 중복 검사 결과: 신규 생성
🔗 지식 그래프 (Knowledge Graph)
- 상위/루트: ASTRA
- 관련 개념: ASTRA 에이전트 셸 명령 실행 보안 게이트, ASTRA 파일 경로 경계 가드, ASTRA Datacollect Bridge
- 참조 맥락: 에이전트/LLM이 외부 URL을 가져올 때의 네트워크 경계 통제 기준.
📚 출처 (Sources)
- [S1]
E:/Wiki/astraai/src/features/web/ssrfGuard.ts— IP 범위 판정 +assertPublicUrl. - [S2]
E:/Wiki/astraai/src/features/web/webFetch.ts—fetchUrlDirect수동 리다이렉트 + 가드. - [S3]
E:/Wiki/astraai/src/lib/contextBuilders/urlContext.ts— 입구 chokepoint. - [S4]
E:/Wiki/astraai/tests/ssrfGuard.test.ts— 회귀 테스트. - [S5] OWASP SSRF Prevention /
azu/request-filtering-agent, Wiz SSRF guide — allowlist·DNS 리바인딩 권고.
📝 변경 이력 (Change history)
- 2026-06-19: 보안 감사 + 외부 표준 대조 후 SSRF 가드 신설 및 최초 문서화(Claude Opus 4.8).