Lucene search
+L

API Fuzzer

🗓️ 17 Jan 2026 08:03:29Reported by KittySploitType 
kittysploit
 kittysploit
🔗 kittysploit.com👁 3 Views

The API Fuzzer by KittySploit Team fuzzes API endpoints to discover vulnerabilities.

Refs
Code
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from kittysploit import *
from lib.protocols.http.http_client import Http_client
from lib.scanner.http.module_result import finalize_http_scanner_run, target_base_url
import json
import urllib.parse
import random
import string


class Module(Auxiliary, Http_client):
    
    __info__ = {
        'name': 'API Fuzzer',
        'description': 'Fuzzes API endpoints to discover vulnerabilities, misconfigurations, exposed endpoints, and test various HTTP methods',
        'author': 'KittySploit Team',
        'tags': ['web', 'api', 'fuzzer', 'scanner', 'security'],
        'references': [
            'https://owasp.org/www-project-api-security/',
            'https://portswigger.net/web-security/api',
        ],
    'agent': {
        'risk': 'active',
        'effects': ['network_probe'],
        'expected_requests': 2,
        'reversible': True,
        'approval_required': False,
        'produces': ['tech_hints', 'risk_signals', 'endpoints', 'params'],
        'cost': 2.0,
        'noise': 0.5,
        'value': 1.0,
        'requires':         {'min_endpoints': 1,
         'min_params': 0,
         'tech_hints_any': [],
         'tech_hints_all': [],
         'specializations_any': [],
         'risk_signals_any': [],
         'auth_session': False,
         'capabilities_any': [],
         'capabilities_all': [],
         'confidence_min': {},
         'confidence_min_any': {},
         'endpoint_pattern_any': [],
         'param_any': [],
         'api_surface_ready': False},
        'chain':         {'produces_capabilities': [{'capability': 'endpoints', 'from_detail': ''}],
         'consumes_capabilities': [],
         'option_bindings': {},
         'suggested_followups': []},
    },
    }

    # Common API endpoints to fuzz
    API_ENDPOINTS = [
        '/api',
        '/api/v1',
        '/api/v2',
        '/api/v3',
        '/rest',
        '/rest/api',
        '/graphql',
        '/graphql/v1',
        '/swagger',
        '/swagger.json',
        '/swagger.yaml',
        '/openapi.json',
        '/openapi.yaml',
        '/api-docs',
        '/api/docs',
        '/documentation',
        '/docs',
        '/v1',
        '/v2',
        '/v3',
        '/users',
        '/user',
        '/admin',
        '/auth',
        '/login',
        '/logout',
        '/register',
        '/token',
        '/oauth',
        '/oauth2',
        '/health',
        '/status',
        '/info',
        '/version',
        '/config',
        '/settings',
        '/data',
        '/files',
        '/upload',
        '/download',
    ]

    # HTTP methods to test
    HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD', 'TRACE']

    # Fuzzing payloads
    FUZZ_PAYLOADS = [
        # SQL Injection
        "' OR '1'='1",
        "' OR 1=1--",
        "1' UNION SELECT NULL--",
        "admin'--",
        "admin'/*",
        
        # NoSQL Injection
        '{"$ne": null}',
        '{"$gt": ""}',
        '{"$where": "1==1"}',
        
        # Command Injection
        '; ls',
        '| whoami',
        '`id`',
        '$(whoami)',
        
        # Path Traversal
        '../../../etc/passwd',
        '..\\..\\..\\windows\\system32\\config\\sam',
        
        # XSS
        '<script>alert(1)</script>',
        '"><img src=x onerror=alert(1)>',
        
        # XXE
        '<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><foo>&xxe;</foo>',
        
        # JSON Injection
        '{"test": "value"}',
        '{"__proto__": {"isAdmin": true}}',
        
        # Special characters
        '../../',
        '..%2f..%2f',
        '%00',
        '\x00',
        '\n',
        '\r',
    ]

    def check(self):
        """
        Check if the target is accessible
        """
        try:
            response = self.http_request(method="GET", path="/")
            if response and response.status_code in [200, 301, 302, 403, 404, 401]:
                return True
            return False
        except Exception as e:
            return False

    def fuzz_endpoint(self, endpoint, method='GET', payload=None):
        """
        Fuzz a specific endpoint
        
        Args:
            endpoint: The endpoint to fuzz
            method: HTTP method to use
            payload: Optional payload to include
            
        Returns:
            dict: Fuzzing results
        """
        try:
            if method in ['POST', 'PUT', 'PATCH']:
                data = payload if payload else {'test': 'value'}
                response = self.http_request(
                    method=method,
                    path=endpoint,
                    data=data,
                    allow_redirects=False
                )
            else:
                if payload:
                    # Add payload as query parameter
                    endpoint = f"{endpoint}?test={urllib.parse.quote(str(payload))}"
                response = self.http_request(
                    method=method,
                    path=endpoint,
                    allow_redirects=False
                )

            if not response:
                return {
                    'endpoint': endpoint,
                    'method': method,
                    'found': False,
                    'error': 'No response'
                }

            # Analyze response
            status_code = response.status_code
            content_length = len(response.content)
            content_type = response.headers.get('Content-Type', 'unknown')

            # Check for interesting responses
            is_interesting = False
            indicators = []

            # Status codes that might indicate something interesting
            if status_code in [200, 201, 202]:
                is_interesting = True
                indicators.append(f'Status {status_code}')
            elif status_code in [401, 403]:
                is_interesting = True
                indicators.append(f'Authentication required ({status_code})')
            elif status_code in [500, 502, 503]:
                is_interesting = True
                indicators.append(f'Server error ({status_code})')

            # Check for API indicators
            if 'json' in content_type.lower():
                is_interesting = True
                indicators.append('JSON response')
                try:
                    json_data = json.loads(response.text)
                    if isinstance(json_data, dict):
                        indicators.append(f'JSON object with {len(json_data)} keys')
                except:
                    pass

            # Check for error messages that might reveal information
            error_indicators = ['stack trace', 'sql syntax', 'mysql_', 'postgresql', 'traceback (most recent']
            if any(indicator in response.text.lower() for indicator in error_indicators):
                is_interesting = True
                indicators.append('Error message detected')

            # Check for API documentation (require stronger markers than the word "api")
            doc_indicators = ['swagger-ui', '"openapi"', '"swagger"', 'openapi.json', 'graphql playground']
            if any(indicator in response.text.lower() for indicator in doc_indicators):
                is_interesting = True
                indicators.append('API documentation detected')

            # Soft-404 / generic HTML miss pages are never "discovered" APIs.
            body_l = (response.text or "")[:4000].lower()
            if status_code in (404, 410) or (
                status_code == 200
                and any(tok in body_l for tok in ("404 not found", "page not found", "error 404", "<title>404"))
                and "json" not in content_type.lower()
            ):
                is_interesting = False
                indicators = []

            return {
                'endpoint': endpoint,
                'method': method,
                'found': is_interesting,
                'status_code': status_code,
                'content_length': content_length,
                'content_type': content_type,
                'indicators': indicators,
                'response_preview': response.text[:200] if response.text else ''
            }

        except Exception as e:
            return {
                'endpoint': endpoint,
                'method': method,
                'found': False,
                'error': str(e)
            }

    def discover_endpoints(self):
        """
        Discover API endpoints by fuzzing common paths
        """
        print_status("Discovering API endpoints...")
        discovered = []

        for endpoint in self.API_ENDPOINTS:
            result = self.fuzz_endpoint(endpoint, method='GET')
            if result.get('found'):
                discovered.append(result)
                print_success(f"  Found: {endpoint} (Status: {result.get('status_code')})")
                if result.get('indicators'):
                    print_info(f"    Indicators: {', '.join(result['indicators'])}")

        return discovered

    def test_http_methods(self, endpoint):
        """
        Test various HTTP methods on an endpoint
        
        Args:
            endpoint: The endpoint to test
            
        Returns:
            list: List of allowed methods
        """
        allowed_methods = []

        for method in self.HTTP_METHODS:
            result = self.fuzz_endpoint(endpoint, method=method)
            status_code = result.get('status_code')
            indicators = result.get('indicators', [])
            if status_code is None:
                continue
            if status_code in [405, 501]:
                continue
            # Keep only methods that returned something meaningfully different
            # from a blind/empty probe to avoid flooding the console.
            if status_code >= 400 and not indicators:
                continue
            if status_code in [200, 201, 202, 401, 403, 500, 502, 503] or indicators:
                allowed_methods.append({
                    'method': method,
                    'status_code': status_code,
                    'indicators': indicators
                })

        return allowed_methods

    def fuzz_parameters(self, endpoint):
        """
        Fuzz parameters on an endpoint
        
        Args:
            endpoint: The endpoint to fuzz
            
        Returns:
            list: List of vulnerabilities found
        """
        vulnerabilities = []

        for payload in self.FUZZ_PAYLOADS[:10]:  # Test first 10 payloads
            result = self.fuzz_endpoint(endpoint, method='GET', payload=payload)

            if result.get('found'):
                # Check for potential vulnerabilities
                if 'error' in result.get('response_preview', '').lower():
                    vulnerabilities.append({
                        'endpoint': endpoint,
                        'payload': payload[:50],
                        'type': 'Potential injection',
                        'status_code': result.get('status_code')
                    })

        return vulnerabilities

    def run(self):
        """
        Execute the API fuzzing scan
        """
        self.discovered_endpoints = []
        self.vulnerabilities = []
        self.test_results = []

        print_status("Starting API fuzzing scan...")
        print_info(f"Target: {self.target}")
        print_info("")

        # Discover endpoints
        self.discovered_endpoints = self.discover_endpoints()
        print_info("")

        # If no endpoints discovered, try root API paths — but only keep live hits.
        if not self.discovered_endpoints:
            print_status("No common endpoints found, testing root API paths...")
            root_paths = ['/api', '/rest', '/v1', '/graphql']
            live_statuses = {200, 201, 202, 401, 403, 500, 502, 503}
            for path in root_paths:
                result = self.fuzz_endpoint(path, method='GET')
                status = result.get('status_code')
                print_info(f"  Testing: {path} (Status: {status})")
                if status is None:
                    continue
                if status == 404 or status == 410:
                    continue
                if status not in live_statuses and not result.get('found'):
                    continue
                # HTML miss pages often match the word "api" — require JSON/auth/error signal.
                indicators = [str(x).lower() for x in (result.get('indicators') or [])]
                preview = str(result.get('response_preview') or '').lower()
                ctype = str(result.get('content_type') or '').lower()
                if status in live_statuses and (
                    'json' in ctype
                    or any('auth' in ind or 'json' in ind or 'server error' in ind for ind in indicators)
                    or '"openapi"' in preview
                    or '"swagger"' in preview
                    or '__schema' in preview
                ):
                    self.discovered_endpoints.append(result)
                elif status in {401, 403}:
                    self.discovered_endpoints.append(result)
        print_info("")

        # Test HTTP methods on discovered endpoints
        if self.discovered_endpoints:
            print_status("Testing HTTP methods on discovered endpoints...")
            for endpoint_info in self.discovered_endpoints[:5]:  # Test first 5
                endpoint = endpoint_info['endpoint']
                methods = self.test_http_methods(endpoint)
                if methods:
                    summary = ", ".join(
                        f"{method_info['method']}={method_info['status_code']}"
                        for method_info in methods[:6]
                    )
                    print_info(f"  Methods on {endpoint}: {summary}")
                    if len(methods) > 6:
                        print_info(f"    +{len(methods) - 6} other method result(s)")
            print_info("")

        # Fuzz parameters
        if self.discovered_endpoints:
            print_status("Fuzzing parameters on discovered endpoints...")
            for endpoint_info in self.discovered_endpoints[:3]:  # Fuzz first 3
                endpoint = endpoint_info['endpoint']
                print_info(f"Fuzzing parameters on: {endpoint}")
                vulns = self.fuzz_parameters(endpoint)
                if vulns:
                    for vuln in vulns:
                        print_warning(f"  Potential vulnerability: {vuln['type']}")
                        self.vulnerabilities.append(vuln)
                print_info("")

        # Summary
        print_status("=" * 60)
        print_status("API Fuzzing Scan Summary")
        print_status("=" * 60)
        print_info(f"Endpoints discovered: {len(self.discovered_endpoints)}")
        print_info(f"Potential vulnerabilities: {len(self.vulnerabilities)}")
        print_status("=" * 60)
        print_info("")

        if self.discovered_endpoints:
            print_success("Discovered endpoints:")
            print_info("")
            table_data = []
            for endpoint_info in self.discovered_endpoints:
                indicators = ', '.join(endpoint_info.get('indicators', [])[:2])
                table_data.append([
                    endpoint_info['endpoint'],
                    endpoint_info.get('method', 'GET'),
                    endpoint_info.get('status_code', 'N/A'),
                    indicators
                ])
            print_table(['Endpoint', 'Method', 'Status', 'Indicators'], table_data)
            print_info("")

        if self.vulnerabilities:
            print_warning("Potential vulnerabilities:")
            print_info("")
            for vuln in self.vulnerabilities:
                print_info(f"  - {vuln['endpoint']}: {vuln['type']}")
        else:
            print_info("No obvious vulnerabilities detected during fuzzing.")

        return finalize_http_scanner_run(
            self,
            self.vulnerabilities,
            title="API Fuzzing Finding",
            severity="medium",
            category="api",
            findings_key="api_findings",
            dedupe_keys=("endpoint", "type"),
            hit_mapper=lambda hit: {
                "endpoint": hit.get("endpoint"),
                "method": hit.get("method", "GET"),
                "request_url": target_base_url(self, path=str(hit.get("endpoint") or "/")),
                "status_code": hit.get("status_code"),
                "type": hit.get("type"),
                "evidence_snippet": ", ".join(hit.get("indicators") or []) or hit.get("type"),
            },
        )

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

28 Jul 2026 16:41Current
5.8Medium risk
Vulners AI Score5.8
3