# Credential inject करने वाले agent tools के लिए SSRF test

Credential inject करने वाला agent tool सामान्य outbound HTTP को boundary decision में बदल देता है। अगर tool agent से destination लेता है, अच्छे दिखने वाले hostname को validate करता है और फिर HTTP client को DNS और redirect जहाँ ले जाए वहाँ जाने देता है, तो उसने अपना credential attacker द्वारा चुनी गई service को सौंप दिया।

इसका समाधान URL validator में लंबी denylist चिपकाना नहीं है। आपको ऐसी destination matrix चाहिए जो पूरे request lifecycle को test करे: parsing, DNS resolution, address selection, connection, redirect handling और उसके बाद credential injection। Test को उस peer को observe करना चाहिए जिसने request प्राप्त की, क्योंकि secret वहीं पहुँचा है।

## Credential injection destination approval के बाद होना चाहिए

Tool को authentication material जोड़ने से पहले तय करना चाहिए कि वह किसी destination से संपर्क कर सकता है या नहीं। इसलिए bearer token, basic credential, custom header, request signature, client certificate और SSH identity सभी destination check के बाद ही इस्तेमाल होने चाहिए।

Teams अक्सर check गलत जगह लगाती हैं। वे मूल URL string को validate करती हैं, Authorization header के साथ request बनाती हैं और redirect following enabled वाले सुविधाजनक HTTP helper को call कर देती हैं। Helper फिर नाम resolve करता है, IPv6 या IPv4 चुनता है, Location header follow करता है और header या connection state को ऐसे तरीकों से reuse कर सकता है जिन्हें validation code ने कभी देखा ही नहीं।

इस design की एक अच्छी बात है: इसे लिखना आसान है। लेकिन खतरनाक decision ऐसे component के हाथ में चला जाता है जिसे पता ही नहीं कि request में credential मौजूद है।

दो decisions अलग रखें:

- क्या यह URL tool के लिए syntactically acceptable है?
- क्या यह concrete network destination authenticated connection के लिए approved है?

पहला decision scheme, host, port, path, userinfo और percent encoding को parse करता है। दूसरा resolved address को classify करता है और वास्तविक connection को नियंत्रित करता है। Host allowlist दोनों decisions में मदद कर सकती है, लेकिन दूसरे decision की जगह नहीं ले सकती।

RFC 3986 parsing की समस्या को साफ़ करता है। Authority component का रूप `[userinfo@]host[:port]` होता है। `@` से पहले trusted hostname जैसा दिखने वाली string उसके बाद मौजूद IP address को नाम दे सकती है। RFC 3986 misleading URI authority string की चर्चा में इसी construction का उदाहरण देता है। खुद URL split न करें और raw string में trusted name न खोजें। एक standards-aware parser से parse करें, tool को userinfo की वास्तविक ज़रूरत न हो तो destinations के लिए उसे reject करें और parsed host field पढ़ें।

Security invariant इतना सरल है कि इसे test किया जा सकता है:

> Tool को उस request के connected peer और request authority को approve करने से पहले कोई authenticated byte नहीं भेजना चाहिए।

«Authenticated byte» में request line या Host header तभी शामिल है जब वे खुद कोई secret उजागर करते हों। अधिकांश HTTP मामलों में असली सीमा credential वाले header या signed payload पर है। जब path, query string या body में capability URL, tenant identifier या कोई अन्य sensitive value हो, तो अधिक सख्त रहें।

## Cases लिखने से पहले decision point तय करें

SSRF control को test करने से पहले आपको वह exact point पता होना चाहिए जहाँ वह allow या deny decision लेता है। «Request से पहले» बहुत अस्पष्ट है। Request में कई ऐसे moments होते हैं जहाँ target बदल सकता है।

Credential-injecting HTTP tool के लिए इस sequence को model की तरह इस्तेमाल करें:

1. दिए गए absolute URL को parse करें और unsupported scheme, malformed authority, userinfo तथा tool के contract से बाहर के port को reject करें।
2. उस resolver से hostname resolve करें जिसका process वास्तव में उपयोग करेगा। केवल development machine को पसंद आए answer पर निर्भर न रहें, A और AAAA दोनों answer जुटाएँ।
3. हर candidate address को classify करें। अगर set में approved destination class से बाहर का address हो तो request reject करें, जब तक connector खुद को approved address पर pin न कर सके।
4. Approved address से connection खोलें और credential लिखने से पहले remote peer address verify करें।
5. इस verification के बाद ही authenticated request बनाएँ। Intended Host header और TLS server name को जानबूझकर set करें।
6. Redirect को parsing से शुरू होने वाली नई request मानें, trust inherit करने वाला continuation नहीं।

यह `hostname != "localhost"` check करने से अधिक कठिन है। ऐसा होना ज़रूरी है। RFC 8305 ऐसे clients का वर्णन करता है जो AAAA और A query लगभग साथ करते हैं और IPv6 preference को ध्यान में रखकर addresses आज़माते हैं। केवल A record resolve करने वाली test suite ऐसे code को सही साबित कर सकती है जो reachable AAAA record आते ही fail हो जाए।

Decision point यह भी तय करता है कि harness को क्या record करना है। हर attempt के लिए capture करें:

- tool को मिला URL
- tool को मिले DNS answer
- connection के लिए चुना गया address
- receiving server ने देखा हुआ address
- हर request header, reports में test credential को redact करके

ऐसा test न लिखें जो केवल status code या thrown exception पर assertion करे। Capture server से मिला 403 साबित करता है कि उसने request प्राप्त की। Timeout कुछ भी साबित नहीं कर सकता। आपको यह assertion चाहिए: «Blocked listener ने कोई credential-bearing request observe नहीं की।»

## Matrix को hostname नहीं, destination के आसपास बनाएँ

Destination matrix इस काम का टिकाऊ artifact है। हर row में input form, resolver response, optional redirect behavior और अपेक्षित result होता है। इससे वह सामान्य समस्या रुकती है जिसमें एक developer `127.0.0.1` test करता है, दूसरा `10.0.0.1` और कोई runtime द्वारा स्वीकार किए जाने वाले अजीब forms को test नहीं करता।

ऐसी छोटी matrix से शुरू करें जिसका वास्तविक oracle हो। नीचे का उदाहरण मानता है कि `public.test` एक allowed harness listener पर point करता है, जबकि हर दूसरे address class को credential injection से पहले deny होना चाहिए।

| Case | Submitted URL | DNS या redirect behavior | Expected result |
| --- | --- | --- | --- |
| Public IPv4 | `https://public.test/echo` | Harness public fixture का A record | Allow और test credential भेजें |
| IPv4 loopback | `http://127.0.0.1:18080/` | Literal address | Connect से पहले deny |
| IPv4 loopback variant | `http://127.1:18080/` | Parser पर निर्भर shorthand | Reject करें या loopback classify करें, कभी allow न करें |
| Private RFC 1918 | `http://10.20.30.40/` | Literal address | Connect से पहले deny |
| Private RFC 1918 | `http://172.20.30.40/` | Literal address | Connect से पहले deny |
| Private RFC 1918 | `http://192.168.20.40/` | Literal address | Connect से पहले deny |
| IPv6 loopback | `http://[::1]:18080/` | Literal address | Connect से पहले deny |
| IPv6 link-local | `http://[fe80::1]/` | Literal address | Connect से पहले deny |
| IPv4-mapped IPv6 | `http://[::ffff:127.0.0.1]/` | Literal address | Connect से पहले deny |
| Mixed DNS answer | `https://mixed.test/` | Allowed A, blocked AAAA | Deny, या connection को allowed address पर pin करें |
| Rebinding name | `https://rebind.test/` | पहले allowed, फिर blocked | Blocked peer को authenticated request deny करें |
| Redirect to loopback | `https://public.test/to-local` | 302 से `http://127.0.0.1:18080/` | दूसरी request deny करें |
| Redirect to private DNS | `https://public.test/to-private` | 302 से `https://internal.test/` | Resolve करके दूसरी request deny करें |
| Userinfo deception | `http://public.test@127.0.0.1/` | `@` के बाद का literal host | Connect से पहले reject करें |

RFC 1918 केवल तीन IPv4 private block बताता है: 10/8, 172.16/12 और 192.168/16। Boundaries test करें, खासकर `172.15.255.255`, `172.16.0.0`, `172.31.255.255` और `172.32.0.0`। ढीला prefix check अक्सर पूरे 172/8 को block कर देता है, जिससे legitimate public endpoint टूटते हैं, या केवल 172.16/16 block करता है और allocated private range का अधिकांश हिस्सा छूट जाता है।

इस table को universal production policy न समझें। कुछ internal tool को किसी specific private service से संपर्क की अनुमति हो सकती है। अगर product requirement यही है, तो उस service के लिए narrow hostname, port और identity check वाला explicit rule दें। «हमारे office में private address ठीक हैं» को हर agent-selected destination का default न बनाएँ।

## URL parsing test DNS से पहले की गलतियाँ पकड़ते हैं

Parser को वही host पहचानना चाहिए जिसे connector इस्तेमाल करेगा। बात स्पष्ट लगती है, जब तक कोई test यह न दिखा दे कि एक component input को दूसरे से अलग normalize कर रहा है।

इन cases को parser-only suite में रखें। इन्हें DNS या network socket के बिना चलना चाहिए:

```text
http://public.test@127.0.0.1/admin
http://127.0.0.1.nip.example/
http://[::1]/
http://[::ffff:7f00:1]/
http://0177.0.0.1/
http://2130706433/
http://127.0.0.1%2f.example/
http://public.test:80@127.0.0.1/
http://public.test./
```

Expected result हमेशा यह नहीं होता कि «इस exact string को loopback की तरह parse होना चाहिए»। URI और URL library पुराने numeric IPv4 form, zone identifier और invalid percent encoding को अलग तरह से संभालती हैं। Test को security property बतानी चाहिए: अगर runtime input स्वीकार करता है और उससे blocked address से connection होगा, तो आपका tool उसे deny करे। अगर runtime उसे reject करता है, तो rejection ठीक है।

यह फर्क महत्वपूर्ण है क्योंकि कई teams पहले custom host filter लिखती हैं और फिर unchanged URL दूसरी library को देती हैं। Filter `2130706433` को अपरिचित registered name समझ सकता है, जबकि connector उसे 127.0.0.1 मान सकता है। आपने दो parser test किए और सुरक्षित answer पर भरोसा कर लिया।

Validation और connection के लिए एक ही parsed representation इस्तेमाल करें। अगर यह संभव न हो, तो अंतिम connector से चुना गया numeric peer report करवाएँ और credential process से बाहर जाने से पहले आखिरी address-class check करें।

Hostname normalization भी test करें। सामान्य DNS में `public.test.` का trailing dot उसी DNS name को refer करता है जिसे `public.test` करता है, लेकिन सरल allowlist अक्सर raw string compare करती हैं। Internationalized hostname एक और disagreement पैदा कर सकते हैं। Runtime की canonical hostname representation में एक बार convert करें, फिर DNS name को label boundaries पर match करें। `endsWith("trusted.example")` जैसा suffix check `untrusted.example` और `trusted.example.attacker.test` दोनों को स्वीकार कर सकता है, जबकि इनमें से कोई trusted subdomain नहीं है।

## DNS rebinding test को दूसरा lookup ज़रूर कराना चाहिए

DNS rebinding timing failure है। Initial lookup acceptable address लौटाता है। उसी नाम के लिए बाद का lookup loopback, private address या कोई अन्य blocked target लौटाता है। अगर validation और connection एक ही resolution result इस्तेमाल नहीं करते, तो attacker पहली check पास करके दूसरी को मोड़ सकता है।

एक उपयोगी fixture के लिए आपके नियंत्रण में छोटा authoritative DNS server और दो HTTP listener चाहिए। एक allowed fixture होगा। दूसरा blocked fixture होगा, जो record करेगा कि उसे test Authorization header मिला या नहीं। `rebind.test` के लिए DNS server को यह answer schedule दें:

```text
query 1: rebind.test. A     198.51.100.20
query 2: rebind.test. A     127.0.0.1
query 1: rebind.test. AAAA  2001:db8::20
query 2: rebind.test. AAAA  ::1
```

केवल harness के लिए addresses इस्तेमाल करें और ज़रूरत के अनुसार test network में allowed listener map करें। इस example के public-looking documentation address fixture के labels हैं, ऐसे endpoint नहीं जिन्हें test Internet पर contact करे।

अब दो variants चलाएँ। पहले में tool से अपना validation lookup करवाएँ और फिर ऐसी HTTP library call करें जो names को अलग से resolve करती हो। अगर implementation में यही gap है, तो blocked listener को credential मिलना चाहिए। दूसरे में connection path को approved resolved address इस्तेमाल करने के लिए configure करें, मूल hostname को केवल Host और TLS name verification के लिए रखें और assert करें कि blocked listener को कुछ नहीं मिला।

Caching इस problem को छिपा सकती है। Resolver cache, connection pool या operating system cache आपके दूसरे answer को निष्प्रभावी बना सकता है। Test harness में cache controls दें, runs के बीच idle connections बंद करें, ज़रूरत पर हर run के लिए नया hostname इस्तेमाल करें और हर DNS query record करें। अगर rebinding test दो अलग lookup confirm किए बिना pass हो जाता है, तो उसने rebinding test नहीं किया।

DNS TTL पर भरोसा करके समस्या हल न करें। TTL caching hint है, यह दावा नहीं कि validation और connection के बीच नाम सुरक्षित रहेगा। Code को approved address connection तक ले जाना चाहिए या socket खुलने के बाद peer verify करना चाहिए।

## Redirect अलग destination decision होते हैं

Redirect destination बदलता है। वह scheme, host, port, path या इन सभी को बदल सकता है। Redirect को trusted continuation मानने से apparently safe public URL `127.0.0.1`, instance metadata service, router या internal control plane की request बन सकता है।

Credential-bearing request के transport में automatic redirect following बंद करें। Redirect response पढ़ें, modest redirect limit लागू करें, current URL के अनुसार library के URL resolution rules से Location value parse करें और पूरा destination decision फिर से चलाएँ।

Redirect fixture में कम से कम ये behavior होने चाहिए:

- Public URL IPv4 loopback literal पर 302 लौटाए।
- Public URL ऐसे hostname पर 307 लौटाए जो private IPv4 address में resolve होता हो।
- Public URL `/next` जैसा relative Location लौटाए, जो normal resolution के बाद approved authority पर ही रहना चाहिए।
- Public URL `//other.test/path` जैसा scheme-relative Location लौटाए, जो authority बदलता है और पूरी validation मांगता है।
- Public URL ऐसी redirect chain लौटाए जिसमें पहला hop allowed और बाद का hop blocked हो।

Status code महत्वपूर्ण हैं। 301, 302 या 303 client को non-GET method को GET में बदलने के लिए प्रेरित कर सकते हैं। 307 या 308 method और body को बनाए रखने के लिए होते हैं। Signed POST या bearer-authenticated write में client library के default treatment पर भरोसा न करें। हर redirect fixture पर method, body hash, Host header और Authorization header record करें।

Authority बदलने पर Authorization हटाना अच्छी defense है, लेकिन इससे destination validation वैकल्पिक नहीं बनती। बिना Authorization header वाली request में भी path में signed URL, client certificate, cookies या sensitive body हो सकती है। Same-host redirect भी ordinary path से local service तक ले जा सकता है, अगर hops के बीच DNS बदल जाए।

यह ऐसी जगह है जहाँ test जानबूझकर boring होना चाहिए। Blocked redirect listener से 200 लौटवाएँ। अगर assertion केवल client error की अपेक्षा करती है, तो redirect-following client SSRF request सफलतापूर्वक कर सकता है और आपका test गलत कारण से उसे pass मान लेगा। Listener के captured request count और captured credential state पर assertion करें।

## IPv6 SSRF का प्रथम श्रेणी वाला surface है

IPv6 का छूट जाना आमतौर पर accidental होता है। Developer `127.0.0.1` test करता है, RFC 1918 range block करता है और ऐसा code ship कर देता है जो `[::1]` को कोई exotic hostname समझता है। आधुनिक client A record के साथ AAAA query कर सकते हैं और IPv4 test ठीक दिखने पर भी IPv6 route जीत सकता है।

RFC 4291 `::1/128` को IPv6 loopback address और `::/128` को unspecified address बताता है। यह `fe80::/10` के अंतर्गत link-local unicast address भी define करता है। ऐसे address class agent-selected authenticated destination नहीं बनने चाहिए।

इन categories को स्पष्ट रूप से test करें:

| Address class | Example | Default test expectation |
| --- | --- | --- |
| Unspecified | `[::]` | Deny |
| Loopback | `[::1]` | Deny |
| Link-local | `[fe80::1]` | Deny |
| Unique local | `[fc00::1]`, `[fd12:3456::1]` | Explicit approval के बिना deny |
| Multicast | `[ff02::1]` | Deny |
| IPv4-mapped loopback | `[::ffff:127.0.0.1]` | Deny |
| IPv4-mapped private | `[::ffff:192.168.1.10]` | Deny |

Address classification spelling पर नहीं, binary address पर चलनी चाहिए। एक ही address compressed या expanded IPv6 form में आ सकता है। `::1` से string comparison `0:0:0:0:0:0:0:1` को miss कर देगा। पहले real IP type में parse करना मामूली काम लगता है, लेकिन यही bug की इस category को रोकता है।

Scope identifier के लिए भी स्पष्ट rule रखें। `[fe80::1%25en0]` जैसे input में percent encoding के बाद interface zone होती है। अधिकांश tool को agent input से zone-scoped literal destination reject करना चाहिए, बजाय यह तय करने के कि कौन सा local interface सुरक्षित है। अगर product में valid local-network use case है, तो interface choice को explicit user-owned configuration बनाएँ, agent द्वारा दिए गए URL detail को नहीं।

Mixed A और AAAA answer पर कठोर decision चाहिए। अगर resolver एक allowed IPv4 और एक blocked IPv6 address लौटाता है, तो सुरक्षित default denial है। Availability के लिए allowed answer इस्तेमाल करना आवश्यक हो तो connector को socket को उसी address पर pin करना चाहिए और hostname को बाद में resolver को नहीं देना चाहिए। «हम IPv4 prefer करते हैं» कोई control नहीं है। Network library, operating system और connection race दूसरा विकल्प चुन सकते हैं।

## Matrix को closed harness में चलाएँ

SSRF test को अपने laptop की वास्तविक localhost service या cloud metadata endpoint पर न चलाएँ। आपको proof चाहिए, तनावपूर्ण दोपहर नहीं। Resolver और हर listener को isolated test network में रखें, disposable credential इस्तेमाल करें और listener को अपना address तथा received header log करने दें।

एक सरल harness में चार actor होते हैं:

1. Test runner, जो URL और disposable credential reference के साथ tool को invoke करता है।
2. Controllable DNS server, जो programmed order में A और AAAA answer दे सकता है।
3. Allowed HTTP fixture, जो सफल authenticated request record करता है।
4. Blocked HTTP fixture, जो किसी भी connection या request को test failure record करता है।

Blocked fixture को ऐसा response body दें जिससे local log में leak स्पष्ट दिखे, लेकिन full token कभी print न करें। उदाहरण के लिए, वह peer address, request method, Host header और `Authorization` मौजूद होने की boolean लौटा सकता है। Runner उस evidence की तुलना अपेक्षित matrix row से करे।

Result format CI में देखने लायक सरल होना चाहिए:

```text
case=redirect_to_loopback
submitted=https://public.test/to-local
resolved=198.51.100.20
redirect=http://127.0.0.1:18080/
decision=deny
allowed_requests=0
blocked_connections=0
blocked_credentials=0
```

Failure में यही fields और actual peer भी सुरक्षित रखें। इससे अस्पष्ट regression actionable report में बदल जाती है:

```text
case=rebind_ipv6
submitted=https://rebind.test/export
validation_answer=2001:db8::20
connected_peer=::1
blocked_credentials=1
result=FAIL
```

यह report तुरंत bug दिखाती है: code ने एक DNS answer approve किया और दूसरे से connect किया। यह भी स्पष्ट होता है कि credential boundary पार कर गया या नहीं, जबकि यही आपका असली risk है।

अगर tool proxy configuration support करता है, तो matrix में proxy rows जोड़ें। HTTP proxy बदलता है कि TCP connection कहाँ जाता है, जबकि CONNECT के कारण proxy फिर भी attacker-selected target तक पहुँच सकता है। तय करें कि proxy trusted transport है या नहीं और क्या tool ultimate authority validate करता है। केवल proxy address check करने वाला proxy test internal destination के लिए open relay को सही साबित कर सकता है।

## Implementation में authority और destination को अलग रखें

TLS इस problem को आसानी से उलझा देता है। Hostname से सुरक्षित रूप से connect करने के लिए tool को approved numeric address पर socket खोलना पड़ सकता है, जबकि TLS server name और HTTP Host header में मूल approved hostname देना होता है। ये अलग fields हैं और इनके काम भी अलग हैं।

Numeric peer पूछता है, «यह socket कहाँ जा रहा है?» TLS server name पूछता है, «इस server को कौन सी certificate identity साबित करनी है?» Host header पूछता है, «यह request किस HTTP authority को target करती है?» Implementation में तीनों values स्पष्ट रखें, mutable URL string से HTTP convenience API को अनुमान लगाने न दें।

यह separation एक लोकप्रिय खराब recommendation भी उजागर करता है: एक बार resolve करें और URL में hostname को numeric address से बदल दें। इससे दूसरा DNS lookup टल सकता है, लेकिन TLS certificate validation, virtual hosting और signed-request scheme टूट सकती है। Developer अक्सर certificate verification बंद करके या host check कमजोर करके जवाब देते हैं। यह मूल bug से भी बदतर है।

इसके बजाय ऐसा transport इस्तेमाल करें जो approved address पर dial करने के साथ मूल hostname के लिए strict TLS validation बनाए रखे। Handshake के बाद confirm करें कि socket peer approved address है। अगर runtime ये controls नहीं देता, तो authenticated agent request के लिए DNS rebinding रोकने का दावा न करें। Limitation को product boundary में लिखें और agent-chosen destination में credential inject करने से बचें।

SSH में HTTP redirect नहीं होते, फिर भी shape वही है। Requested host को resolve और classify करें, connection को approved address पर pin करें और host key को intended host identity के विरुद्ध verify करें। Host key prompt या permissive known-hosts rule destination policy नहीं है।

Sallyport credential को encrypted vault के अंदर रखता है और secret material agent को देने के बजाय HTTP और SSH action खुद करता है। यह boundary तभी कायम रहती है जब action layer stored credential इस्तेमाल करने से पहले destination control लागू करे।

## Matrix को security document नहीं, release gate बनाएँ

Destination matrix तभी उपयोगी है जब वह उसी request path पर चले जिसे ship किया जाएगा। `isPrivateIp()` का unit test HTTP client के resolver, redirect code, proxy behavior या connection pool को test नहीं करता। Unit test रखें, लेकिन transport library, URL parser, resolver configuration या credential injection code update होने पर harness suite को mandatory बनाएँ।

Bug ठीक करने पर rows जोड़ें। किसी कठिन incident को «SSRF validation सुधारें» जैसे broad statement में न बदलें। Exact input, DNS sequence, redirect response और credentials के expected absence को सुरक्षित रखें। Future maintainer को executable form में यह अनुभव चाहिए।

Failure को category के अनुसार review करें। Parser discrepancy duplicate URL handling की ओर इशारा करती है। Blocked listener TCP connection देखे लेकिन request न देखे, तो संभव है कि आपने बहुत देर से validation की हो, भले token leak न हुआ हो। Blocked listener Authorization header देखे, तो invariant fail हुआ है और release रुकनी चाहिए।

यह काम इसलिए लाभ देता है क्योंकि इससे team का सवाल बदलता है। यह पूछना बंद करें कि hostname external जैसा दिख रहा था या नहीं। पूछें कि authenticated request किस peer ने प्राप्त की, उसे कैसे चुना गया और क्या आपका test इसका प्रमाण दे सकता है। अगर test output से इसका उत्तर नहीं मिलता, तो tool में अभी भी SSRF blind spot है।
