Kiterunner Cheat Sheet
"Clase de la hoja" id="copy-btn" class="copy-btn" onclick="copyAllCommands()" Copiar todos los comandos id="pdf-btn" class="pdf-btn" onclick="generatePDF()" Generar PDF seleccionado/button ■/div titulada
Sinopsis
Kiterunner es una herramienta rápida y modular diseñada para el descubrimiento de contenidos y la enumeración de endpoints API. Se destaca al descubrir puntos finales ocultos de API, directorios y archivos a través de un análisis de contenido basado en Wordlist inteligente. Kiterunner es particularmente eficaz para aplicaciones web modernas y APIs, ofreciendo características avanzadas como listas de palabras personalizadas, análisis de respuesta e integración con otras herramientas de seguridad.
■ Características clave: Escaneo rápido de múltiples hilos, análisis de respuesta inteligente, soporte personalizado de lista de palabras, descubrimiento de terminales de API, filtrado basado en contenidos, persiguiendo JSON/XML y formatos de salida completos.
Instalación y configuración
Instalación binaria
# Download latest release from GitHub
wget https://github.com/assetnote/kiterunner/releases/latest/download/kiterunner_linux_amd64.tar.gz
# Extract the archive
tar -xzf kiterunner_linux_amd64.tar.gz
# Move to system path
sudo mv kr /usr/local/bin/
# Verify installation
kr --help
# Download wordlists
mkdir -p ~/.kiterunner/wordlists
cd ~/.kiterunner/wordlists
# Download common wordlists
wget https://raw.githubusercontent.com/assetnote/kiterunner/main/wordlists/routes-large.kite
wget https://raw.githubusercontent.com/assetnote/kiterunner/main/wordlists/routes-small.kite
wget https://raw.githubusercontent.com/assetnote/kiterunner/main/wordlists/apiroutes-210228.kite
# Set permissions
chmod +x /usr/local/bin/kr
Docker Instalación
# Pull Docker image
docker pull assetnote/kiterunner
# Create alias for easier usage
echo 'alias kr="docker run --rm -it -v $(pwd):/app assetnote/kiterunner"' >> ~/.bashrc
source ~/.bashrc
# Test installation
kr --help
# Run with volume mount for wordlists
docker run --rm -it -v $(pwd):/app -v ~/.kiterunner:/root/.kiterunner assetnote/kiterunner
# Create Docker wrapper script
cat > kiterunner-docker.sh << 'EOF'
#!/bin/bash
docker run --rm -it \
-v $(pwd):/app \
-v ~/.kiterunner:/root/.kiterunner \
assetnote/kiterunner "$@"
EOF
chmod +x kiterunner-docker.sh
sudo mv kiterunner-docker.sh /usr/local/bin/kr-docker
Fuente: Instalación
# Install Go (if not already installed)
wget https://golang.org/dl/go1.19.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.19.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
# Clone repository
git clone https://github.com/assetnote/kiterunner.git
cd kiterunner
# Build from source
make build
# Install binary
sudo cp dist/kr /usr/local/bin/
# Verify installation
kr version
# Build with custom options
go build -ldflags="-s -w" -o kr cmd/kiterunner/main.go
Configuración y configuración
# Create configuration directory
mkdir -p ~/.kiterunner/{wordlists,output,config}
# Create default configuration
cat > ~/.kiterunner/config/config.yaml << 'EOF'
# Kiterunner Configuration
default:
threads: 50
timeout: 10
delay: 0
max_redirects: 3
user_agent: "Kiterunner/1.0"
wordlists:
default_path: "~/.kiterunner/wordlists"
routes_large: "routes-large.kite"
routes_small: "routes-small.kite"
api_routes: "apiroutes-210228.kite"
output:
default_format: "json"
save_responses: false
output_directory: "~/.kiterunner/output"
filters:
status_codes:
ignore: [404, 403, 400]
interesting: [200, 201, 202, 301, 302, 500, 502, 503]
content_length:
min: 0
max: 1048576 # 1MB
response_time:
max: 30000 # 30 seconds
proxy:
enabled: false
url: "http://127.0.0.1:8080"
headers:
custom:
- "X-Forwarded-For: 127.0.0.1"
- "X-Real-IP: 127.0.0.1"
EOF
# Set environment variables
export KITERUNNER_CONFIG=~/.kiterunner/config/config.yaml
export KITERUNNER_WORDLISTS=~/.kiterunner/wordlists
# Create wordlist management script
cat > ~/.kiterunner/manage_wordlists.sh << 'EOF'
#!/bin/bash
WORDLIST_DIR="$HOME/.kiterunner/wordlists"
download_wordlists() {
echo "Downloading Kiterunner wordlists..."
# Official wordlists
wget -O "$WORDLIST_DIR/routes-large.kite" \
"https://raw.githubusercontent.com/assetnote/kiterunner/main/wordlists/routes-large.kite"
wget -O "$WORDLIST_DIR/routes-small.kite" \
"https://raw.githubusercontent.com/assetnote/kiterunner/main/wordlists/routes-small.kite"
wget -O "$WORDLIST_DIR/apiroutes-210228.kite" \
"https://raw.githubusercontent.com/assetnote/kiterunner/main/wordlists/apiroutes-210228.kite"
# Additional useful wordlists
wget -O "$WORDLIST_DIR/common.txt" \
"https://raw.githubusercontent.com/danielmiessler/SecLists/master/Discovery/Web-Content/common.txt"
wget -O "$WORDLIST_DIR/api-endpoints.txt" \
"https://raw.githubusercontent.com/danielmiessler/SecLists/master/Discovery/Web-Content/api/api-endpoints.txt"
echo "Wordlists downloaded successfully"
}
update_wordlists() {
echo "Updating wordlists..."
download_wordlists
}
list_wordlists() {
echo "Available wordlists:"
ls -la "$WORDLIST_DIR"
}
case "$1" in
download) download_wordlists ;;
update) update_wordlists ;;
list) list_wordlists ;;
| *) echo "Usage: $0 {download | update | list}" ;; |
esac
EOF
chmod +x ~/.kiterunner/manage_wordlists.sh
Uso básico y comandos
Simple Directory Discovery
# Basic directory discovery
kr brute -w ~/.kiterunner/wordlists/routes-small.kite -t 50 https://example.com
# Discovery with custom wordlist
kr brute -w /path/to/custom/wordlist.txt -t 100 https://example.com
# Multiple targets from file
echo -e "https://example.com\nhttps://test.com" > targets.txt
kr brute -w ~/.kiterunner/wordlists/routes-large.kite -A targets.txt
# Discovery with specific extensions
kr brute -w ~/.kiterunner/wordlists/routes-small.kite -x php,asp,aspx,jsp https://example.com
# Discovery with custom headers
kr brute -w ~/.kiterunner/wordlists/routes-small.kite -H "Authorization: Bearer token123" https://example.com
# Discovery with proxy
kr brute -w ~/.kiterunner/wordlists/routes-small.kite --proxy http://127.0.0.1:8080 https://example.com
API Endpoint Discovery
# API endpoint discovery with specialized wordlist
kr brute -w ~/.kiterunner/wordlists/apiroutes-210228.kite -t 100 https://api.example.com
# API discovery with JSON output
kr brute -w ~/.kiterunner/wordlists/apiroutes-210228.kite -o json https://api.example.com
# API discovery with response analysis
kr brute -w ~/.kiterunner/wordlists/apiroutes-210228.kite --fail-status-codes 404,403 https://api.example.com
# API discovery with content length filtering
kr brute -w ~/.kiterunner/wordlists/apiroutes-210228.kite --filter-length 0 https://api.example.com
# API discovery with custom user agent
kr brute -w ~/.kiterunner/wordlists/apiroutes-210228.kite -H "User-Agent: Mobile App 1.0" https://api.example.com
# API versioning discovery
kr brute -w ~/.kiterunner/wordlists/apiroutes-210228.kite --prefixes /v1,/v2,/api/v1,/api/v2 https://example.com
Opciones avanzadas de exploración
# High-performance scanning
kr brute -w ~/.kiterunner/wordlists/routes-large.kite -t 200 --delay 0 https://example.com
# Scanning with custom timeout
kr brute -w ~/.kiterunner/wordlists/routes-small.kite --timeout 30 https://example.com
# Scanning with retry logic
kr brute -w ~/.kiterunner/wordlists/routes-small.kite --max-retries 3 https://example.com
# Scanning with rate limiting
kr brute -w ~/.kiterunner/wordlists/routes-small.kite --delay 100 https://example.com
# Scanning with custom status code filtering
kr brute -w ~/.kiterunner/wordlists/routes-small.kite --fail-status-codes 404,403,400,401 https://example.com
# Scanning with response size filtering
kr brute -w ~/.kiterunner/wordlists/routes-small.kite --filter-length 0,1,2,3 https://example.com
Técnicas avanzadas de descubrimiento
Creación de Wordlist personalizado
#!/bin/bash
# Create custom wordlists for specific applications
create_api_wordlist() {
local output_file="$1"
cat > "$output_file" << 'EOF'
# API Endpoints
/api
/api/v1
/api/v2
/api/v3
/rest
/rest/v1
/rest/v2
/graphql
/graph
/gql
# Authentication endpoints
/auth
/login
/logout
/signin
/signout
/register
/signup
/oauth
/token
/refresh
/verify
# User management
/users
/user
/profile
/account
/accounts
/me
/settings
/preferences
# Data endpoints
/data
/export
/import
/backup
/restore
/sync
/upload
/download
# Admin endpoints
/admin
/administrator
/management
/manage
/dashboard
/panel
/control
# Common API patterns
/health
/status
/ping
/version
/info
/metrics
/stats
/analytics
# CRUD operations
/create
/read
/update
/delete
/list
/search
/find
/get
/post
/put
/patch
# File operations
/files
/documents
/images
/media
/assets
/static
/public
/private
# Configuration
/config
/configuration
/settings
/options
/parameters
/env
/environment
EOF
echo "API wordlist created: $output_file"
}
create_technology_specific_wordlist() {
local technology="$1"
local output_file="$2"
case "$technology" in
"spring")
cat > "$output_file" << 'EOF'
/actuator
/actuator/health
/actuator/info
/actuator/metrics
/actuator/env
/actuator/configprops
/actuator/mappings
/actuator/beans
/actuator/trace
/actuator/dump
/actuator/autoconfig
/management
/management/health
/management/info
EOF
;;
"django")
cat > "$output_file" << 'EOF'
/admin
/admin/
/django-admin
/__debug__
/debug
/static
/media
/api
/api-auth
/api-token-auth
/accounts
/accounts/login
/accounts/logout
/accounts/signup
EOF
;;
"laravel")
cat > "$output_file" << 'EOF'
/api
/admin
/dashboard
/telescope
/horizon
/nova
/storage
/public
/vendor
/artisan
/routes
/config
/cache
/session
EOF
;;
"nodejs")
cat > "$output_file" << 'EOF'
/api
/admin
/auth
/login
/logout
/register
/users
/user
/profile
/dashboard
/health
/status
/metrics
/debug
/console
/socket.io
/graphql
EOF
;;
esac
echo "$technology wordlist created: $output_file"
}
create_mobile_api_wordlist() {
local output_file="$1"
cat > "$output_file" << 'EOF'
# Mobile API endpoints
/mobile
/mobile/api
/mobile/v1
/mobile/v2
/app
/app/api
/app/v1
/app/v2
# Mobile-specific features
/push
/notifications
/fcm
/apns
/device
/devices
/registration
/unregister
# Mobile authentication
/mobile/auth
/mobile/login
/mobile/logout
/mobile/token
/mobile/refresh
/app/auth
/app/login
/app/token
# Mobile data sync
/sync
/mobile/sync
/app/sync
/offline
/cache
/local
# Mobile analytics
/analytics
/events
/tracking
/metrics
/crash
/crashes
/feedback
# Mobile updates
/update
/updates
/version
/versions
/download
/upgrade
EOF
echo "Mobile API wordlist created: $output_file"
}
# Usage examples
create_api_wordlist "~/.kiterunner/wordlists/custom-api.txt"
create_technology_specific_wordlist "spring" "~/.kiterunner/wordlists/spring-endpoints.txt"
create_mobile_api_wordlist "~/.kiterunner/wordlists/mobile-api.txt"
Análisis de la respuesta inteligente
#!/usr/bin/env python3
# Advanced response analysis for Kiterunner results
import json
import re
import requests
import argparse
from urllib.parse import urljoin, urlparse
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
class KiterunnerAnalyzer:
"""Advanced analysis of Kiterunner discovery results"""
def __init__(self, results_file, target_base_url):
self.results_file = results_file
self.target_base_url = target_base_url
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'Kiterunner-Analyzer/1.0'
})
# Analysis results
self.interesting_endpoints = []
self.api_endpoints = []
self.admin_endpoints = []
self.sensitive_files = []
self.error_pages = []
def load_results(self):
"""Load Kiterunner results from JSON file"""
try:
with open(self.results_file, 'r') as f:
# Handle both single JSON objects and line-delimited JSON
content = f.read().strip()
if content.startswith('['):
# Standard JSON array
return json.loads(content)
else:
# Line-delimited JSON
results = []
for line in content.split('\n'):
if line.strip():
try:
results.append(json.loads(line))
except json.JSONDecodeError:
continue
return results
except Exception as e:
print(f"Error loading results: {e}")
return []
def analyze_results(self):
"""Analyze Kiterunner results for interesting findings"""
results = self.load_results()
print(f"Analyzing {len(results)} discovered endpoints...")
for result in results:
self._analyze_single_result(result)
# Generate analysis report
self._generate_analysis_report()
def _analyze_single_result(self, result):
"""Analyze a single discovery result"""
url = result.get('url', '')
status_code = result.get('status', 0)
content_length = result.get('length', 0)
response_time = result.get('time', 0)
# Categorize endpoints
if self._is_api_endpoint(url):
self.api_endpoints.append(result)
if self._is_admin_endpoint(url):
self.admin_endpoints.append(result)
if self._is_sensitive_file(url):
self.sensitive_files.append(result)
if self._is_error_page(result):
self.error_pages.append(result)
if self._is_interesting_endpoint(result):
self.interesting_endpoints.append(result)
def _is_api_endpoint(self, url):
"""Check if URL appears to be an API endpoint"""
api_patterns = [
r'/api/',
r'/rest/',
r'/graphql',
r'/v\d+/',
r'\.json$',
r'\.xml$',
r'/oauth',
r'/token'
]
return any(re.search(pattern, url, re.IGNORECASE) for pattern in api_patterns)
def _is_admin_endpoint(self, url):
"""Check if URL appears to be an admin endpoint"""
admin_patterns = [
r'/admin',
r'/administrator',
r'/management',
r'/dashboard',
r'/panel',
r'/control',
r'/console',
r'/manager'
]
return any(re.search(pattern, url, re.IGNORECASE) for pattern in admin_patterns)
def _is_sensitive_file(self, url):
"""Check if URL appears to be a sensitive file"""
sensitive_patterns = [
r'\.env$',
r'\.config$',
r'\.conf$',
r'\.ini$',
r'\.properties$',
r'\.yaml$',
r'\.yml$',
r'\.json$',
r'\.xml$',
r'\.sql$',
r'\.db$',
r'\.backup$',
r'\.bak$',
r'\.log$',
r'\.key$',
r'\.pem$',
r'\.crt$',
r'\.p12$',
r'\.pfx$'
]
return any(re.search(pattern, url, re.IGNORECASE) for pattern in sensitive_patterns)
def _is_error_page(self, result):
"""Check if result appears to be an error page with useful information"""
status_code = result.get('status', 0)
content_length = result.get('length', 0)
# Look for error pages that might leak information
if status_code in [500, 502, 503] and content_length > 100:
return True
return False
def _is_interesting_endpoint(self, result):
"""Check if endpoint is interesting for further investigation"""
url = result.get('url', '')
status_code = result.get('status', 0)
content_length = result.get('length', 0)
# Interesting status codes
if status_code in [200, 201, 202, 301, 302, 401, 403]:
return True
# Unusual content lengths
if content_length > 10000 or (content_length > 0 and content_length < 10):
return True
# Interesting URL patterns
interesting_patterns = [
r'/debug',
r'/test',
r'/dev',
r'/staging',
r'/backup',
r'/old',
r'/new',
r'/temp',
r'/tmp',
r'/upload',
r'/download',
r'/export',
r'/import'
]
if any(re.search(pattern, url, re.IGNORECASE) for pattern in interesting_patterns):
return True
return False
def deep_analysis(self, max_workers=10):
"""Perform deep analysis of interesting endpoints"""
print("Performing deep analysis of interesting endpoints...")
endpoints_to_analyze = (
self.interesting_endpoints +
self.api_endpoints +
self.admin_endpoints
)
with ThreadPoolExecutor(max_workers=max_workers) as executor:
future_to_endpoint = {
executor.submit(self._deep_analyze_endpoint, endpoint): endpoint
for endpoint in endpoints_to_analyze[:50] # Limit to first 50
}
for future in as_completed(future_to_endpoint):
endpoint = future_to_endpoint[future]
try:
analysis = future.result()
if analysis:
endpoint['deep_analysis'] = analysis
except Exception as e:
print(f"Error analyzing {endpoint.get('url', 'unknown')}: {e}")
def _deep_analyze_endpoint(self, endpoint):
"""Perform deep analysis of a single endpoint"""
url = endpoint.get('url', '')
try:
# Make request to endpoint
response = self.session.get(url, timeout=10, allow_redirects=True)
analysis = {
'final_url': response.url,
'status_code': response.status_code,
'headers': dict(response.headers),
'content_type': response.headers.get('content-type', ''),
'server': response.headers.get('server', ''),
'technologies': self._detect_technologies(response),
'security_headers': self._analyze_security_headers(response.headers),
'content_analysis': self._analyze_content(response.text),
'forms': self._extract_forms(response.text),
'links': self._extract_links(response.text, url)
}
return analysis
except Exception as e:
return {'error': str(e)}
def _detect_technologies(self, response):
"""Detect technologies used by the endpoint"""
technologies = []
# Check headers for technology indicators
server = response.headers.get('server', '').lower()
if 'apache' in server:
technologies.append('Apache')
if 'nginx' in server:
technologies.append('Nginx')
if 'iis' in server:
technologies.append('IIS')
# Check for framework-specific headers
if 'x-powered-by' in response.headers:
technologies.append(response.headers['x-powered-by'])
# Check content for technology indicators
content = response.text.lower()
tech_patterns = {
'React': r'react',
'Angular': r'angular',
'Vue.js': r'vue\.js',
'jQuery': r'jquery',
'Bootstrap': r'bootstrap',
'Django': r'django',
'Laravel': r'laravel',
'Spring': r'spring',
'Express': r'express',
'WordPress': r'wp-content|wordpress',
'Drupal': r'drupal',
'Joomla': r'joomla'
}
for tech, pattern in tech_patterns.items():
if re.search(pattern, content):
technologies.append(tech)
return list(set(technologies))
def _analyze_security_headers(self, headers):
"""Analyze security headers"""
security_headers = {
'Content-Security-Policy': headers.get('content-security-policy'),
'X-Frame-Options': headers.get('x-frame-options'),
'X-XSS-Protection': headers.get('x-xss-protection'),
'X-Content-Type-Options': headers.get('x-content-type-options'),
'Strict-Transport-Security': headers.get('strict-transport-security'),
'Referrer-Policy': headers.get('referrer-policy'),
'Permissions-Policy': headers.get('permissions-policy')
}
# Identify missing security headers
missing_headers = [name for name, value in security_headers.items() if not value]
return {
'present': {k: v for k, v in security_headers.items() if v},
'missing': missing_headers
}
def _analyze_content(self, content):
"""Analyze response content for interesting information"""
analysis = {
'length': len(content),
'contains_forms': '<form' in content.lower(),
'contains_javascript': '<script' in content.lower(),
'contains_comments': '<!--' in content,
'potential_secrets': [],
'error_messages': [],
'debug_info': []
}
# Look for potential secrets
secret_patterns = [
(r'api[_-]?key["\']?\s*[:=]\s*["\']([a-zA-Z0-9_-]{10,})["\']', 'API Key'),
(r'secret[_-]?key["\']?\s*[:=]\s*["\']([a-zA-Z0-9_-]{10,})["\']', 'Secret Key'),
(r'password["\']?\s*[:=]\s*["\']([a-zA-Z0-9_-]{8,})["\']', 'Password'),
(r'token["\']?\s*[:=]\s*["\']([a-zA-Z0-9_-]{10,})["\']', 'Token'),
(r'aws[_-]?access[_-]?key["\']?\s*[:=]\s*["\']([A-Z0-9]{20})["\']', 'AWS Access Key'),
(r'aws[_-]?secret[_-]?key["\']?\s*[:=]\s*["\']([a-zA-Z0-9/+=]{40})["\']', 'AWS Secret Key')
]
for pattern, secret_type in secret_patterns:
matches = re.findall(pattern, content, re.IGNORECASE)
for match in matches:
analysis['potential_secrets'].append({
'type': secret_type,
'value': match[:20] + '...' if len(match) > 20 else match
})
# Look for error messages
error_patterns = [
r'error[:\s]+([^\n\r]{10,100})',
r'exception[:\s]+([^\n\r]{10,100})',
r'stack trace[:\s]+([^\n\r]{10,100})',
r'fatal[:\s]+([^\n\r]{10,100})'
]
for pattern in error_patterns:
matches = re.findall(pattern, content, re.IGNORECASE)
analysis['error_messages'].extend(matches[:5]) # Limit to 5 matches
# Look for debug information
debug_patterns = [
r'debug[:\s]+([^\n\r]{10,100})',
r'console\.log\(["\']([^"\']{10,100})["\']',
r'var_dump\(["\']([^"\']{10,100})["\']',
r'print_r\(["\']([^"\']{10,100})["\']'
]
for pattern in debug_patterns:
matches = re.findall(pattern, content, re.IGNORECASE)
analysis['debug_info'].extend(matches[:5]) # Limit to 5 matches
return analysis
def _extract_forms(self, content):
"""Extract forms from HTML content"""
forms = []
form_pattern = r'<form[^>]*>(.*?)</form>'
for form_match in re.finditer(form_pattern, content, re.DOTALL | re.IGNORECASE):
form_html = form_match.group(0)
# Extract form attributes
action = re.search(r'action=["\']([^"\']*)["\']', form_html, re.IGNORECASE)
method = re.search(r'method=["\']([^"\']*)["\']', form_html, re.IGNORECASE)
# Extract input fields
inputs = []
input_pattern = r'<input[^>]*>'
for input_match in re.finditer(input_pattern, form_html, re.IGNORECASE):
input_html = input_match.group(0)
name = re.search(r'name=["\']([^"\']*)["\']', input_html, re.IGNORECASE)
input_type = re.search(r'type=["\']([^"\']*)["\']', input_html, re.IGNORECASE)
inputs.append({
'name': name.group(1) if name else '',
'type': input_type.group(1) if input_type else 'text'
})
forms.append({
'action': action.group(1) if action else '',
'method': method.group(1) if method else 'GET',
'inputs': inputs
})
return forms
def _extract_links(self, content, base_url):
"""Extract links from HTML content"""
links = []
link_pattern = r'href=["\']([^"\']*)["\']'
for link_match in re.finditer(link_pattern, content, re.IGNORECASE):
link = link_match.group(1)
# Convert relative URLs to absolute
if link.startswith('/'):
parsed_base = urlparse(base_url)
link = f"{parsed_base.scheme}://{parsed_base.netloc}{link}"
elif not link.startswith(('http://', 'https://')):
link = urljoin(base_url, link)
links.append(link)
# Remove duplicates and limit
return list(set(links))[:20]
def _generate_analysis_report(self):
"""Generate comprehensive analysis report"""
report = {
'summary': {
'total_endpoints': len(self.load_results()),
'api_endpoints': len(self.api_endpoints),
'admin_endpoints': len(self.admin_endpoints),
'sensitive_files': len(self.sensitive_files),
'error_pages': len(self.error_pages),
'interesting_endpoints': len(self.interesting_endpoints)
},
'findings': {
'api_endpoints': self.api_endpoints[:10], # Top 10
'admin_endpoints': self.admin_endpoints[:10],
'sensitive_files': self.sensitive_files[:10],
'error_pages': self.error_pages[:5],
'interesting_endpoints': self.interesting_endpoints[:15]
},
'recommendations': self._generate_recommendations()
}
# Save report
report_file = f"kiterunner_analysis_{int(time.time())}.json"
with open(report_file, 'w') as f:
json.dump(report, f, indent=2)
print(f"Analysis report saved: {report_file}")
# Print summary
self._print_summary(report)
def _generate_recommendations(self):
"""Generate security recommendations based on findings"""
recommendations = []
if self.admin_endpoints:
recommendations.append({
'priority': 'HIGH',
'category': 'Access Control',
'finding': f"Found {len(self.admin_endpoints)} admin endpoints",
'recommendation': "Review admin endpoints for proper authentication and authorization"
})
if self.sensitive_files:
recommendations.append({
'priority': 'HIGH',
'category': 'Information Disclosure',
'finding': f"Found {len(self.sensitive_files)} sensitive files",
'recommendation': "Remove or protect sensitive files from public access"
})
if self.error_pages:
recommendations.append({
'priority': 'MEDIUM',
'category': 'Information Disclosure',
'finding': f"Found {len(self.error_pages)} error pages",
'recommendation': "Review error pages for information leakage"
})
if self.api_endpoints:
recommendations.append({
'priority': 'MEDIUM',
'category': 'API Security',
'finding': f"Found {len(self.api_endpoints)} API endpoints",
'recommendation': "Test API endpoints for authentication, authorization, and input validation"
})
return recommendations
def _print_summary(self, report):
"""Print analysis summary"""
print("\n" + "="*60)
print("KITERUNNER ANALYSIS SUMMARY")
print("="*60)
summary = report['summary']
print(f"Total Endpoints Discovered: {summary['total_endpoints']}")
print(f"API Endpoints: {summary['api_endpoints']}")
print(f"Admin Endpoints: {summary['admin_endpoints']}")
print(f"Sensitive Files: {summary['sensitive_files']}")
print(f"Error Pages: {summary['error_pages']}")
print(f"Interesting Endpoints: {summary['interesting_endpoints']}")
print("\nTOP FINDINGS:")
print("-" * 40)
if self.admin_endpoints:
print("Admin Endpoints:")
for endpoint in self.admin_endpoints[:5]:
print(f" • {endpoint.get('url', 'N/A')} ({endpoint.get('status', 'N/A')})")
if self.sensitive_files:
print("\\nSensitive Files:")
for file_endpoint in self.sensitive_files[:5]:
print(f" • {file_endpoint.get('url', 'N/A')} ({file_endpoint.get('status', 'N/A')})")
print("\\nRECOMMENDATIONS:")
print("-" * 40)
for rec in report['recommendations']:
print(f"[{rec['priority']}] {rec['category']}: {rec['recommendation']}")
def main():
parser = argparse.ArgumentParser(description='Analyze Kiterunner discovery results')
parser.add_argument('results_file', help='Kiterunner results file (JSON)')
parser.add_argument('target_url', help='Target base URL')
parser.add_argument('--deep-analysis', action='store_true', help='Perform deep analysis')
parser.add_argument('--threads', type=int, default=10, help='Number of threads for deep analysis')
args = parser.parse_args()
analyzer = KiterunnerAnalyzer(args.results_file, args.target_url)
analyzer.analyze_results()
if args.deep_analysis:
analyzer.deep_analysis(max_workers=args.threads)
if __name__ == "__main__":
main()
Integración con Herramientas de Seguridad
Burp Suite Integration
#!/usr/bin/env python3
# Burp Suite integration for Kiterunner results
import json
import requests
import base64
from urllib.parse import urlparse
class BurpSuiteIntegration:
"""Integration with Burp Suite for discovered endpoints"""
def __init__(self, burp_api_url="http://127.0.0.1:1337", api_key=None):
self.burp_api_url = burp_api_url
self.api_key = api_key
self.session = requests.Session()
if api_key:
self.session.headers.update({'X-API-Key': api_key})
def import_kiterunner_results(self, results_file, target_scope=None):
"""Import Kiterunner results into Burp Suite"""
print("Importing Kiterunner results into Burp Suite...")
# Load results
with open(results_file, 'r') as f:
results = json.load(f)
imported_count = 0
for result in results:
url = result.get('url', '')
status_code = result.get('status', 0)
# Filter by scope if specified
if target_scope and not self._in_scope(url, target_scope):
continue
# Only import successful responses
if status_code in [200, 201, 202, 301, 302, 401, 403]:
if self._add_to_sitemap(url, result):
imported_count += 1
print(f"Imported {imported_count} endpoints into Burp Suite")
def _in_scope(self, url, scope_patterns):
"""Check if URL is in scope"""
for pattern in scope_patterns:
if pattern in url:
return True
return False
def _add_to_sitemap(self, url, result):
"""Add URL to Burp Suite sitemap"""
try:
# Create request data
request_data = {
'url': url,
'method': 'GET',
'headers': [],
'body': ''
}
# Create response data
response_data = {
'status_code': result.get('status', 200),
'headers': [],
'body': result.get('response', '')
}
# Add to sitemap via API
api_endpoint = f"{self.burp_api_url}/v0.1/sitemap"
payload = {
'request': request_data,
'response': response_data
}
response = self.session.post(api_endpoint, json=payload)
return response.status_code == 200
except Exception as e:
print(f"Error adding {url} to sitemap: {e}")
return False
def create_scan_configuration(self, results_file, output_file):
"""Create Burp Suite scan configuration from results"""
with open(results_file, 'r') as f:
results = json.load(f)
# Extract unique hosts and paths
hosts = set()
paths = []
for result in results:
url = result.get('url', '')
parsed = urlparse(url)
hosts.add(f"{parsed.scheme}://{parsed.netloc}")
paths.append(parsed.path)
# Create scan configuration
scan_config = {
'scan_configurations': [
{
'name': 'Kiterunner Discovery Scan',
'type': 'NamedConfiguration',
'built_in_configuration_name': 'Audit coverage - thorough'
}
],
'application_logins': [],
'resource_pool': {
'maximum_concurrent_scans': 10,
'maximum_requests_per_second': 100
},
'scan_targets': [
{
'urls': list(hosts),
'scope': {
'include': [
{
'rule': host,
'type': 'SimpleScopeRule'
} for host in hosts
]
}
}
]
}
# Save configuration
with open(output_file, 'w') as f:
json.dump(scan_config, f, indent=2)
print(f"Burp Suite scan configuration saved: {output_file}")
def create_burp_extension():
"""Create Burp Suite extension for Kiterunner integration"""
extension_code = '''
from burp import IBurpExtender, ITab, IHttpListener, IContextMenuFactory
from javax.swing import JPanel, JButton, JTextArea, JScrollPane, JLabel, JTextField
from java.awt import BorderLayout, GridLayout
import json
import subprocess
import threading
class BurpExtender(IBurpExtender, ITab, IHttpListener, IContextMenuFactory):
def registerExtenderCallbacks(self, callbacks):
self._callbacks = callbacks
self._helpers = callbacks.getHelpers()
callbacks.setExtensionName("Kiterunner Integration")
callbacks.registerHttpListener(self)
callbacks.registerContextMenuFactory(self)
# Create UI
self._create_ui()
callbacks.addSuiteTab(self)
print("Kiterunner Integration loaded successfully")
def _create_ui(self):
self._main_panel = JPanel(BorderLayout())
# Control panel
control_panel = JPanel(GridLayout(4, 2))
# Target URL input
control_panel.add(JLabel("Target URL:"))
self._target_url = JTextField("https://example.com")
control_panel.add(self._target_url)
# Wordlist selection
control_panel.add(JLabel("Wordlist:"))
self._wordlist_path = JTextField("/path/to/wordlist.kite")
control_panel.add(self._wordlist_path)
# Threads setting
control_panel.add(JLabel("Threads:"))
self._threads = JTextField("50")
control_panel.add(self._threads)
# Run button
self._run_button = JButton("Run Kiterunner", actionPerformed=self._run_kiterunner)
control_panel.add(self._run_button)
# Import button
self._import_button = JButton("Import Results", actionPerformed=self._import_results)
control_panel.add(self._import_button)
# Results area
self._results_area = JTextArea(20, 80)
self._results_area.setEditable(False)
results_scroll = JScrollPane(self._results_area)
# Add components
self._main_panel.add(control_panel, BorderLayout.NORTH)
self._main_panel.add(results_scroll, BorderLayout.CENTER)
def getTabCaption(self):
return "Kiterunner"
def getUiComponent(self):
return self._main_panel
def _run_kiterunner(self, event):
"""Run Kiterunner scan"""
target_url = self._target_url.getText()
wordlist_path = self._wordlist_path.getText()
threads = self._threads.getText()
if not target_url or not wordlist_path:
self._results_area.append("Please specify target URL and wordlist path\\n")
return
# Run in background thread
thread = threading.Thread(target=self._execute_kiterunner,
args=(target_url, wordlist_path, threads))
thread.daemon = True
thread.start()
def _execute_kiterunner(self, target_url, wordlist_path, threads):
"""Execute Kiterunner command"""
try:
self._results_area.append(f"Starting Kiterunner scan on {target_url}...\\n")
# Build command
cmd = [
"kr", "brute",
"-w", wordlist_path,
"-t", threads,
"-o", "json",
target_url
]
# Execute command
process = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, text=True)
# Read output
for line in process.stdout:
self._results_area.append(line)
self._results_area.setCaretPosition(self._results_area.getDocument().getLength())
# Wait for completion
process.wait()
if process.returncode == 0:
self._results_area.append("\\nKiterunner scan completed successfully\\n")
else:
error_output = process.stderr.read()
self._results_area.append(f"\\nKiterunner scan failed: {error_output}\\n")
except Exception as e:
self._results_area.append(f"\\nError running Kiterunner: {e}\\n")
def _import_results(self, event):
"""Import Kiterunner results into Burp Suite"""
# This would implement the import functionality
# For now, just show a message
self._results_area.append("Import functionality would be implemented here\\n")
'''
# Save extension code
with open('kiterunner_burp_extension.py', 'w') as f:
f.write(extension_code)
print("Burp Suite extension created: kiterunner_burp_extension.py")
def main():
# Example usage
burp_integration = BurpSuiteIntegration()
# Import results (example)
# burp_integration.import_kiterunner_results('kiterunner_results.json', ['example.com'])
# Create scan configuration
# burp_integration.create_scan_configuration('kiterunner_results.json', 'burp_scan_config.json')
# Create Burp extension
create_burp_extension()
if __name__ == "__main__":
main()
Optimización del rendimiento y solución de problemas
Performance Tuning
#!/bin/bash
# Kiterunner performance optimization
optimize_kiterunner_performance() {
echo "Optimizing Kiterunner performance..."
# 1. System-level optimizations
echo "Applying system optimizations..."
# Increase file descriptor limits
ulimit -n 65536
echo "* soft nofile 65536" | sudo tee -a /etc/security/limits.conf
echo "* hard nofile 65536" | sudo tee -a /etc/security/limits.conf
# Optimize network settings
echo 'net.core.somaxconn = 65536' | sudo tee -a /etc/sysctl.conf
echo 'net.ipv4.tcp_max_syn_backlog = 65536' | sudo tee -a /etc/sysctl.conf
echo 'net.ipv4.tcp_fin_timeout = 15' | sudo tee -a /etc/sysctl.conf
echo 'net.ipv4.tcp_tw_reuse = 1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
# 2. Kiterunner-specific optimizations
echo "Configuring Kiterunner optimizations..."
# Create optimized configuration
cat > ~/.kiterunner/performance_config.yaml << 'EOF'
# High-performance Kiterunner configuration
performance:
threads: 200 # Increase threads for faster scanning
timeout: 5 # Reduce timeout for faster responses
delay: 0 # No delay between requests
max_retries: 1 # Reduce retries to avoid slowdowns
network:
keep_alive: true # Reuse connections
max_idle_conns: 100 # Increase connection pool
max_conns_per_host: 50 # More connections per host
filtering:
fail_status_codes: [404, 403, 400, 401, 500, 502, 503]
filter_length: [0, 1, 2, 3] # Filter out tiny responses
output:
minimal: true # Reduce output verbosity
save_responses: false # Don't save full responses
EOF
echo "Performance optimizations applied"
}
# Performance monitoring script
monitor_kiterunner_performance() {
local target="$1"
local wordlist="$2"
local output_file="kiterunner_performance_$(date +%s).log"
echo "Monitoring Kiterunner performance for: $target"
echo "Wordlist: $wordlist"
# Start monitoring
{
echo "Timestamp,CPU%,Memory(MB),Network_Connections,Requests_Per_Second"
while true; do
if pgrep -f "kr" > /dev/null; then
local pid=$(pgrep -f "kr")
| local cpu=$(ps -p $pid -o %cpu --no-headers 2>/dev/null | | echo "0") |
| local mem=$(ps -p $pid -o rss --no-headers 2>/dev/null | awk '{print $1/1024}' | | echo "0") |
| local connections=$(ss -tuln 2>/dev/null | wc -l | | echo "0") |
local timestamp=$(date +%s)
echo "$timestamp,$cpu,$mem,$connections,N/A"
fi
sleep 2
done
} > "$output_file" &
local monitor_pid=$!
# Run Kiterunner with timing
echo "Starting Kiterunner scan..."
time kr brute -w "$wordlist" -t 100 "$target"
# Stop monitoring
kill $monitor_pid 2>/dev/null
echo "Performance monitoring completed: $output_file"
}
# Benchmark different configurations
benchmark_kiterunner() {
local target="$1"
local wordlist="$2"
echo "Benchmarking Kiterunner configurations..."
# Test different thread counts
thread_counts=(10 25 50 100 200)
for threads in "${thread_counts[@]}"; do
echo "Testing with $threads threads..."
start_time=$(date +%s)
kr brute -w "$wordlist" -t "$threads" --timeout 5 "$target" > /dev/null 2>&1
end_time=$(date +%s)
duration=$((end_time - start_time))
echo "Threads: $threads, Duration: ${duration}s"
done
# Test different timeout values
timeouts=(1 3 5 10 15)
echo "Testing different timeout values..."
for timeout in "${timeouts[@]}"; do
echo "Testing with ${timeout}s timeout..."
start_time=$(date +%s)
kr brute -w "$wordlist" -t 50 --timeout "$timeout" "$target" > /dev/null 2>&1
end_time=$(date +%s)
duration=$((end_time - start_time))
echo "Timeout: ${timeout}s, Duration: ${duration}s"
done
}
# Memory optimization for large wordlists
optimize_memory_usage() {
echo "Optimizing memory usage for large wordlists..."
# Split large wordlists into smaller chunks
split_wordlist() {
local input_wordlist="$1"
local chunk_size="${2:-1000}"
local output_dir="${3:-./wordlist_chunks}"
mkdir -p "$output_dir"
# Split wordlist
split -l "$chunk_size" "$input_wordlist" "$output_dir/chunk_"
echo "Wordlist split into chunks in: $output_dir"
}
# Process wordlist chunks sequentially
process_chunks() {
local target="$1"
local chunk_dir="$2"
local output_file="$3"
echo "Processing wordlist chunks for: $target"
for chunk in "$chunk_dir"/chunk_*; do
echo "Processing chunk: $(basename "$chunk")"
kr brute -w "$chunk" -t 50 "$target" >> "$output_file"
# Small delay to prevent overwhelming the target
sleep 1
done
echo "All chunks processed. Results in: $output_file"
}
# Example usage
# split_wordlist "large_wordlist.txt" 500 "./chunks"
# process_chunks "https://example.com" "./chunks" "results.json"
}
# Run optimizations
optimize_kiterunner_performance
Problemas comunes
#!/bin/bash
# Kiterunner troubleshooting guide
troubleshoot_kiterunner() {
echo "Kiterunner Troubleshooting Guide"
echo "================================"
# Check if Kiterunner is installed
if ! command -v kr &> /dev/null; then
echo "❌ Kiterunner not found"
echo "Solution: Download and install Kiterunner from GitHub releases"
echo " wget https://github.com/assetnote/kiterunner/releases/latest/download/kiterunner_linux_amd64.tar.gz"
echo " tar -xzf kiterunner_linux_amd64.tar.gz"
echo " sudo mv kr /usr/local/bin/"
return 1
fi
| echo "✅ Kiterunner found: $(kr version 2>/dev/null | | echo 'Version unknown')" |
# Check wordlists
if [ ! -d ~/.kiterunner/wordlists ]; then
echo "❌ Wordlists directory not found"
echo "Solution: Create wordlists directory and download wordlists"
echo " mkdir -p ~/.kiterunner/wordlists"
echo " cd ~/.kiterunner/wordlists"
echo " wget https://raw.githubusercontent.com/assetnote/kiterunner/main/wordlists/routes-small.kite"
return 1
fi
echo "✅ Wordlists directory exists"
# Check for common wordlists
wordlists=(
"routes-small.kite"
"routes-large.kite"
"apiroutes-210228.kite"
)
for wordlist in "${wordlists[@]}"; do
if [ -f ~/.kiterunner/wordlists/"$wordlist" ]; then
echo "✅ Wordlist found: $wordlist"
else
echo "⚠️ Wordlist missing: $wordlist"
fi
done
# Check network connectivity
if ! curl -s --connect-timeout 5 https://httpbin.org/get > /dev/null; then
echo "❌ Network connectivity issues"
echo "Solution: Check internet connection and proxy settings"
return 1
fi
echo "✅ Network connectivity OK"
# Check system resources
available_memory=$(free -m | awk 'NR==2{printf "%.1f", $7/1024}')
if (( $(echo "$available_memory < 0.5" | bc -l) )); then
echo "⚠️ Low available memory: ${available_memory}GB"
echo "Solution: Free up memory or reduce thread count"
else
echo "✅ Available memory: ${available_memory}GB"
fi
# Check file descriptor limits
fd_limit=$(ulimit -n)
if [ "$fd_limit" -lt 1024 ]; then
echo "⚠️ Low file descriptor limit: $fd_limit"
echo "Solution: Increase file descriptor limit"
echo " ulimit -n 65536"
else
echo "✅ File descriptor limit: $fd_limit"
fi
echo "Troubleshooting completed"
}
# Common error solutions
fix_common_kiterunner_errors() {
echo "Common Kiterunner Errors and Solutions"
echo "======================================"
cat << 'EOF'
1. "kr: command not found"
Solution:
- Download Kiterunner binary from GitHub releases
- Extract and move to /usr/local/bin/
- Ensure /usr/local/bin/ is in your PATH
2. "permission denied" when running kr
Solution:
- Make the binary executable: chmod +x /usr/local/bin/kr
- Check file ownership and permissions
3. "too many open files" error
Solution:
- Increase file descriptor limit: ulimit -n 65536
- Add to ~/.bashrc for persistence
- Reduce thread count if problem persists
4. "connection timeout" or "connection refused"
Solution:
- Check target URL is accessible
- Verify firewall settings
- Increase timeout value: --timeout 30
- Check if target is rate limiting
5. "wordlist not found" error
Solution:
- Verify wordlist path is correct
- Download official wordlists from GitHub
- Use absolute paths for wordlists
6. Slow scanning performance
Solution:
- Increase thread count: -t 100
- Reduce timeout: --timeout 5
- Use smaller wordlists for testing
- Check network latency to target
7. "out of memory" errors
Solution:
- Reduce thread count
- Split large wordlists into chunks
- Process targets sequentially
- Monitor memory usage during scans
8. No results found (false negatives)
Solution:
- Try different wordlists
- Adjust status code filtering
- Check response length filtering
- Verify target is responding correctly
9. SSL/TLS certificate errors
Solution:
- Use --insecure flag for self-signed certificates
- Update system CA certificates
- Check target SSL configuration
10. Rate limiting by target
Solution:
- Reduce thread count: -t 10
- Add delay between requests: --delay 100
- Use proxy rotation if available
- Respect target's robots.txt and rate limits
EOF
}
# Test basic functionality
test_kiterunner_functionality() {
echo "Testing Kiterunner Basic Functionality"
echo "======================================"
# Test with httpbin.org (reliable test target)
test_target="https://httpbin.org"
echo "Testing basic discovery against: $test_target"
# Create minimal test wordlist
cat > /tmp/test_wordlist.txt << 'EOF'
get
post
put
delete
status
headers
ip
user-agent
EOF
# Run test scan
echo "Running test scan..."
if kr brute -w /tmp/test_wordlist.txt -t 5 --timeout 10 "$test_target" > /tmp/kr_test_output.txt 2>&1; then
echo "✅ Basic functionality test passed"
# Check if any endpoints were found
if [ -s /tmp/kr_test_output.txt ]; then
echo "✅ Endpoints discovered during test"
echo "Sample results:"
head -5 /tmp/kr_test_output.txt
else
echo "⚠️ No endpoints found (this might be normal)"
fi
else
echo "❌ Basic functionality test failed"
echo "Error output:"
cat /tmp/kr_test_output.txt
fi
# Clean up
rm -f /tmp/test_wordlist.txt /tmp/kr_test_output.txt
}
# Performance diagnostics
diagnose_performance_issues() {
echo "Diagnosing Kiterunner Performance Issues"
echo "======================================="
# Check system load
| load_avg=$(uptime | awk -F'load average:' '{print $2}' | awk '{print $1}' | sed 's/,//') |
echo "System load average: $load_avg"
# Check available CPU cores
cpu_cores=$(nproc)
echo "Available CPU cores: $cpu_cores"
# Check memory usage
memory_info=$(free -h | grep "Mem:")
echo "Memory info: $memory_info"
# Check disk I/O
if command -v iostat &> /dev/null; then
echo "Disk I/O statistics:"
iostat -x 1 1 | tail -n +4
fi
# Check network connectivity speed
echo "Testing network speed to common target..."
if command -v curl &> /dev/null; then
time_total=$(curl -o /dev/null -s -w "%{time_total}" https://httpbin.org/get)
echo "Network response time: ${time_total}s"
fi
# Recommendations based on findings
echo ""
echo "Performance Recommendations:"
echo "- Optimal thread count: $((cpu_cores * 10))"
echo "- Recommended timeout: 5-10 seconds"
echo "- Consider using delay if target is slow: --delay 50"
}
# Main troubleshooting function
main() {
troubleshoot_kiterunner
echo ""
fix_common_kiterunner_errors
echo ""
test_kiterunner_functionality
echo ""
diagnose_performance_issues
}
# Run troubleshooting
main
Recursos y documentación
Recursos oficiales
- Kiterunner GitHub Repository - Repositorio principal y código fuente
- Assetnote Blog - Artículos de investigación y metodología
- Kiterunner Releases - Descargar las últimas versiones
Recursos comunitarios
- Bug Bounty Community - comunidad de discordia para discusiones
- Guía de Pruebas de la OPEP - Metodologías de pruebas de aplicaciones web
- API Security Testing - Las mejores prácticas de seguridad de la API
Ejemplos de integración
- Reconnaissance Workflows - Automatización de reconocimiento integral
- Bug Bounty Toolkit - Herramientas de caza de botín
- API Discovery Tools - Herramientas de descubrimiento de API relacionadas
- Content Discovery - Metodologías de descubrimiento de contenidos web