Summary
Marqo's API is unauthenticated by default and binds 0.0.0.0. During indexing it fetches media from user-supplied URLs with no destination filtering. download_image_from_url uses pycurl with FOLLOWLOCATION=1 and no IP/host filter; fetch_content_sample uses requests.get on the raw URL. The only check is validators.url (syntax). So an unauthenticated attacker can make the backend request arbitrary internal addresses.
Proof of concept (real container)
POST /indexes/ssrf-test (no auth) -> created.
POST /indexes/ssrf-test/documents {"documents":[{"_id":"d","img":"http://INTERNAL:PORT/x.png"}],"tensorFields":["img"]}
-> my listener received the request FROM the marqo container (172.17.0.2), UA=Marqobot/1.0. A no-extension URL produced extra python-requests hits (MIME sniff). img=http://127.0.0.1:8882/ returned status 200 (internal service reachable). On cloud, http://169.254.169.254/ is reachable.
This is a blind SSRF (the body is processed as an image, not returned) - a reachability oracle and pivot primitive.
Suggested fix
Resolve and reject private/loopback/link-local/reserved IPs before fetching (re-check on redirects, since FOLLOWLOCATION is on), restrict scheme to http/https, and consider disabling redirect following for media fetches.
Summary
Marqo's API is unauthenticated by default and binds 0.0.0.0. During indexing it fetches media from user-supplied URLs with no destination filtering. download_image_from_url uses pycurl with FOLLOWLOCATION=1 and no IP/host filter; fetch_content_sample uses requests.get on the raw URL. The only check is validators.url (syntax). So an unauthenticated attacker can make the backend request arbitrary internal addresses.
Proof of concept (real container)
POST /indexes/ssrf-test (no auth) -> created.
POST /indexes/ssrf-test/documents {"documents":[{"_id":"d","img":"http://INTERNAL:PORT/x.png"}],"tensorFields":["img"]}
-> my listener received the request FROM the marqo container (172.17.0.2), UA=Marqobot/1.0. A no-extension URL produced extra python-requests hits (MIME sniff). img=http://127.0.0.1:8882/ returned status 200 (internal service reachable). On cloud, http://169.254.169.254/ is reachable.
This is a blind SSRF (the body is processed as an image, not returned) - a reachability oracle and pivot primitive.
Suggested fix
Resolve and reject private/loopback/link-local/reserved IPs before fetching (re-check on redirects, since FOLLOWLOCATION is on), restrict scheme to http/https, and consider disabling redirect following for media fetches.