Plugin System
Extend ProRT-IP with custom Lua plugins for scanning, detection, and output formatting.
Overview
The ProRT-IP plugin system enables extensibility through Lua 5.4 scripting, allowing users to customize scanning behavior, add detection capabilities, and create custom output formats without modifying core code.
Key Features:
- Sandboxed Execution: Lua plugins run in isolated environments with resource limits
- Capabilities-Based Security: Fine-grained permission model (Network, Filesystem, System, Database)
- Three Plugin Types: Scan lifecycle hooks, Output formatting, Service detection
- Zero Native Dependencies: Pure Lua implementation (no C libraries)
- Hot Reloading: Load/unload plugins without restarting ProRT-IP
- Example Plugins:
banner-analyzerandssl-checkerincluded
Design Goals:
- Security First: Deny-by-default capabilities, resource limits, sandboxing
- Simple API: Easy to learn, hard to misuse
- Performance: Minimal overhead, async-compatible
- Maintainability: Clear interfaces, comprehensive documentation
Plugin Types
ProRT-IP supports three plugin types, each serving different extensibility needs.
1. ScanPlugin - Lifecycle Hooks
Provides hooks for scan execution lifecycle.
Use Cases:
- Pre-scan target manipulation (port knocking, custom filtering)
- Per-target custom data collection
- Post-scan aggregate analysis
API Methods:
function on_load(config) -- Initialize plugin
function on_unload() -- Cleanup resources
function pre_scan(targets) -- Called before scan starts
function on_target(target, result) -- Called for each target
function post_scan(results) -- Called after scan completes
Example: Scan Statistics Plugin
function pre_scan(targets)
prtip.log("info", string.format("Scanning %d targets", #targets))
end
function on_target(target, result)
if result.state == "open" then
prtip.log("info", string.format("Found open port: %d", result.port))
end
end
function post_scan(results)
prtip.log("info", string.format("Scan complete: %d results", #results))
end
2. OutputPlugin - Custom Formatting
Custom result formatting and export.
Use Cases:
- Custom report formats (CSV, JSON, XML)
- Integration with external systems
- Data transformation
API Methods:
function on_load(config)
function on_unload()
function format_result(result) -- Format single result
function export(results, path) -- Export all results to file
Example: CSV Export Plugin
function format_result(result)
return string.format("%s:%d [%s]",
result.target_ip,
result.port,
result.state)
end
function export(results, path)
local file = io.open(path, "w")
file:write("IP,Port,State\n")
for _, result in ipairs(results) do
file:write(string.format("%s,%d,%s\n",
result.target_ip,
result.port,
result.state))
end
file:close()
end
3. DetectionPlugin - Enhanced Service Detection
Enhanced service detection through banner analysis or active probing.
Use Cases:
- Banner analysis for specific services
- Active service probing
- Custom detection logic
API Methods:
function on_load(config)
function on_unload()
function analyze_banner(banner) -- Passive analysis
function probe_service(target) -- Active probing (requires Network capability)
Return Format:
return {
service = "http", -- Required: service name
product = "Apache", -- Optional: product name
version = "2.4.41", -- Optional: version string
info = "Ubuntu", -- Optional: additional info
os_type = "Linux", -- Optional: OS type
confidence = 0.95 -- Optional: confidence (0.0-1.0, default 0.5)
}
Example: HTTP Detection Plugin
function analyze_banner(banner)
local lower = string.lower(banner)
if string.match(lower, "apache") then
local version = string.match(banner, "Apache/([%d%.]+)")
return {
service = "http",
product = "Apache",
version = version,
confidence = version and 0.95 or 0.85
}
end
return nil
end
Plugin Structure
Every plugin requires two files: plugin.toml (metadata) and main.lua (implementation).
Directory Layout
~/.prtip/plugins/my-plugin/
├── plugin.toml # Required: Plugin metadata
├── main.lua # Required: Plugin implementation
└── README.md # Recommended: Documentation
plugin.toml - Metadata
Complete metadata specification:
[plugin]
name = "my-plugin" # Required: Plugin identifier
version = "1.0.0" # Required: Semantic version
author = "Your Name" # Required: Author name/email
description = "Plugin description" # Required: Short description
license = "GPL-3.0" # Optional: License (default GPL-3.0)
plugin_type = "detection" # Required: scan/output/detection
capabilities = ["network"] # Optional: Required capabilities
[plugin.dependencies]
min_prtip_version = "0.4.0" # Optional: Minimum ProRT-IP version
lua_version = "5.4" # Optional: Lua version
[plugin.metadata]
tags = ["detection", "banner"] # Optional: Search tags
category = "detection" # Optional: Category
homepage = "https://example.com" # Optional: Plugin homepage
repository = "https://github.com/..." # Optional: Source repository
Field Descriptions:
- name: Unique identifier (lowercase, hyphens only)
- version: Semantic versioning (major.minor.patch)
- author: Name and optional email
- description: One-line summary (max 80 characters)
- plugin_type:
scan,output, ordetection - capabilities: Array of required permissions
main.lua - Implementation
Required Lifecycle Functions:
function on_load(config)
-- Initialize plugin
-- Return true on success, false or error message on failure
prtip.log("info", "Plugin loaded")
return true
end
function on_unload()
-- Cleanup resources
-- Errors are logged but not fatal
prtip.log("info", "Plugin unloaded")
end
Type-Specific Functions:
-- ScanPlugin
function pre_scan(targets) end
function on_target(target, result) end
function post_scan(results) end
-- OutputPlugin
function format_result(result) return string end
function export(results, path) end
-- DetectionPlugin
function analyze_banner(banner) return service_info or nil end
function probe_service(target) return service_info or nil end
API Reference
All ProRT-IP functions are exposed through the global prtip table.
Logging
prtip.log(level, message)
Parameters:
level(string): "debug", "info", "warn", "error"message(string): Log message
Example:
prtip.log("info", "Plugin initialized successfully")
prtip.log("warn", "Unexpected banner format")
prtip.log("error", "Failed to connect to target")
Target Information
target = prtip.get_target()
Returns:
target(table): Target informationip(string): IP addressport(number): Port numberprotocol(string): "tcp" or "udp"
Example:
local target = prtip.get_target()
prtip.log("info", string.format("Scanning %s:%d", target.ip, target.port))
Scan Configuration
config = prtip.scan_config
Fields:
scan_type(string): Scan type ("syn", "connect", etc.)rate(number): Scan rate (packets/sec)timing(number): Timing template (0-5)verbose(boolean): Verbose output enabled
Example:
if prtip.scan_config.verbose then
prtip.log("debug", "Verbose mode enabled")
end
Network Operations
Note: Requires network capability.
Connect
socket_id = prtip.connect(ip, port, timeout)
Parameters:
ip(string): Target IP addressport(number): Target port (1-65535)timeout(number): Connection timeout in seconds (0-60)
Returns:
socket_id(number): Socket identifier, or error
Example:
local socket_id = prtip.connect("192.168.1.1", 80, 5.0)
if socket_id then
prtip.log("info", "Connected successfully")
end
Send
bytes_sent = prtip.send(socket_id, data)
Parameters:
socket_id(number): Socket identifier fromprtip.connect()data(string or table of bytes): Data to send
Returns:
bytes_sent(number): Number of bytes sent
Example:
local bytes = prtip.send(socket_id, "GET / HTTP/1.0\r\n\r\n")
prtip.log("debug", string.format("Sent %d bytes", bytes))
Receive
data = prtip.receive(socket_id, max_bytes, timeout)
Parameters:
socket_id(number): Socket identifiermax_bytes(number): Maximum bytes to read (1-65536)timeout(number): Read timeout in seconds (0-60)
Returns:
data(table of bytes): Received data
Example:
local data = prtip.receive(socket_id, 4096, 5.0)
local response = table.concat(data)
prtip.log("info", string.format("Received %d bytes", #data))
Close
prtip.close(socket_id)
Parameters:
socket_id(number): Socket identifier
Example:
prtip.close(socket_id)
prtip.log("debug", "Socket closed")
Result Manipulation
prtip.add_result(key, value)
Parameters:
key(string): Result keyvalue(any): Result value (string, number, boolean, table)
Example:
prtip.add_result("custom_field", "custom_value")
prtip.add_result("banner_length", #banner)
prtip.add_result("detected_features", {"ssl", "compression"})
Security Model
The plugin system uses a multi-layered security approach: capabilities, resource limits, and sandboxing.
Capabilities
Fine-grained permission system based on deny-by-default principle.
Available Capabilities
| Capability | Description | Risk Level |
|---|---|---|
network | Network connections | Medium |
filesystem | File I/O operations | High |
system | System commands | Critical |
database | Database access | Medium |
Requesting Capabilities
In plugin.toml:
capabilities = ["network", "filesystem"]
Runtime Enforcement
Capabilities are checked before each privileged operation:
-- This will fail if 'network' capability not granted
local socket_id = prtip.connect(ip, port, timeout)
-- Error: "Plugin lacks 'network' capability"
Resource Limits
Plugins are constrained by default limits to prevent DoS attacks.
Default Limits
| Resource | Limit | Configurable |
|---|---|---|
| Memory | 100 MB | Yes |
| CPU Time | 5 seconds | Yes |
| Instructions | 1,000,000 | Yes |
Enforcement
- Memory: Enforced by Lua VM
- CPU Time: Wall-clock timeout
- Instructions: Hook-based counting
Example Violation:
-- This will trigger instruction limit
while true do
-- Infinite loop
end
-- Error: "Instruction limit of 1000000 exceeded"
Sandboxing
Dangerous Lua libraries are removed from the VM environment.
Removed Libraries
io- File I/Oos- Operating system functionsdebug- Debug introspectionpackage.loadlib- Native library loading
Safe Libraries
string- String manipulationtable- Table operationsmath- Mathematical functionsprtip- ProRT-IP API
Example:
-- This will fail (io library removed)
local file = io.open("file.txt", "r")
-- Error: attempt to index nil value 'io'
-- This is allowed (string library present)
local upper = string.upper("hello")
Example Plugins
ProRT-IP includes two production-ready example plugins demonstrating different capabilities.
Banner Analyzer
Purpose: Enhanced banner analysis for common services.
Location: examples/plugins/banner-analyzer/
Key Features:
- Detects HTTP, SSH, FTP, SMTP, MySQL, PostgreSQL, Redis, MongoDB
- Extracts product name, version, and OS type
- Confidence scoring (0.7-0.95)
- Zero capabilities required (passive analysis)
Usage:
prtip -sS -p 80,443,22 192.168.1.0/24 --plugin banner-analyzer
Code Snippet:
function analyze_http(banner)
local lower = string.lower(banner)
if string.match(lower, "apache") then
local version = extract_version(banner, "Apache/([%d%.]+)")
return {
service = "http",
product = "Apache",
version = version,
confidence = version and 0.95 or 0.85
}
end
return nil
end
Detection Coverage:
- HTTP: Apache, nginx, IIS, Lighttpd
- SSH: OpenSSH, Dropbear
- FTP: vsftpd, ProFTPD
- SMTP: Postfix, Sendmail, Exim
- Databases: MySQL, PostgreSQL, Redis, MongoDB
SSL Checker
Purpose: SSL/TLS service detection and analysis.
Location: examples/plugins/ssl-checker/
Key Features:
- Identifies SSL/TLS ports (443, 465, 993, 995, etc.)
- Detects TLS protocol signatures
- Network capability utilization (active probing)
- Extensible for certificate analysis
Usage:
prtip -sS -p 443,8443 target.com --plugin ssl-checker
Code Snippet:
function analyze_banner(banner)
local lower = string.lower(banner)
if string.match(lower, "tls") or string.match(lower, "ssl") then
return {
service = "ssl",
info = "TLS/SSL encrypted service",
confidence = 0.7
}
end
return nil
end
Quick Start
Get started with your first plugin in 5 minutes.
Step 1: Create Plugin Structure
mkdir -p ~/.prtip/plugins/my-plugin
cd ~/.prtip/plugins/my-plugin
Step 2: Create plugin.toml
[plugin]
name = "my-plugin"
version = "1.0.0"
author = "Your Name"
description = "My first ProRT-IP plugin"
plugin_type = "detection"
capabilities = []
Step 3: Create main.lua
function on_load(config)
prtip.log("info", "Plugin loaded")
return true
end
function on_unload()
prtip.log("info", "Plugin unloaded")
end
function analyze_banner(banner)
if string.match(banner, "HTTP") then
return {
service = "http",
confidence = 0.8
}
end
return nil
end
Step 4: Test the Plugin
# List plugins
prtip --list-plugins
# Should show: my-plugin v1.0.0 (detection)
# Test with real scan
prtip -sS -p 80 127.0.0.1 --plugin my-plugin
# Check logs
tail -f ~/.prtip/logs/prtip.log
Development Workflow
Step 1: Plan Your Plugin
- Identify the Problem: What functionality does ProRT-IP lack?
- Choose Plugin Type: Scan, Output, or Detection?
- List Required Capabilities: Network, Filesystem, etc.
- Design the API: What functions will you implement?
Step 2: Write plugin.toml
[plugin]
name = "my-plugin"
version = "1.0.0"
author = "Your Name <your.email@example.com>"
description = "One-line description"
plugin_type = "detection"
capabilities = [] # Add as needed
[plugin.dependencies]
min_prtip_version = "0.4.0"
lua_version = "5.4"
[plugin.metadata]
tags = ["detection", "custom"]
category = "detection"
Step 3: Implement main.lua
Start with the lifecycle functions:
function on_load(config)
prtip.log("info", "my-plugin loaded")
-- Initialize state
return true
end
function on_unload()
prtip.log("info", "my-plugin unloaded")
-- Cleanup state
end
Add type-specific functions based on your plugin type.
Step 4: Test Your Plugin
# List plugins
prtip --list-plugins
# Test with real scan
prtip -sS -p 80 127.0.0.1 --plugin my-plugin
# Check logs
tail -f ~/.prtip/logs/prtip.log
Step 5: Write README.md
Include:
- Overview
- Installation instructions
- Usage examples
- API reference
- Troubleshooting
Testing
Unit Testing Lua Code
Create a test file test_my_plugin.lua:
package.path = package.path .. ";./?.lua"
local my_plugin = require("main")
function test_analyze_banner()
local result = my_plugin.analyze_banner("HTTP/1.1 200 OK\r\nServer: Apache\r\n")
assert(result ~= nil, "Should detect HTTP")
assert(result.service == "http", "Should identify as HTTP")
assert(result.confidence > 0.5, "Should have reasonable confidence")
print("✓ test_analyze_banner passed")
end
test_analyze_banner()
print("All tests passed!")
Run with Lua:
lua test_my_plugin.lua
Integration Testing
Use ProRT-IP's test framework:
#![allow(unused)] fn main() { #[test] fn test_my_plugin_loading() { let temp_dir = TempDir::new().unwrap(); copy_example_plugin(&temp_dir, "my-plugin").unwrap(); let mut manager = PluginManager::new(temp_dir.path().to_path_buf()); manager.discover_plugins().unwrap(); let result = manager.load_plugin("my-plugin"); assert!(result.is_ok(), "Plugin should load successfully"); } }
Manual Testing Checklist
- Load Test: Verify plugin loads without errors
- Functionality Test: Verify each function works correctly
- Error Handling Test: Trigger error conditions
- Performance Test: Measure execution time
- Security Test: Verify capability enforcement
Deployment
Installation Methods
Method 1: Manual Copy
cp -r my-plugin ~/.prtip/plugins/
prtip --list-plugins # Verify installation
Method 2: Git Clone
cd ~/.prtip/plugins
git clone https://github.com/username/my-plugin.git
prtip --list-plugins
Method 3: Package Manager (Future)
prtip plugin install my-plugin
prtip plugin update my-plugin
prtip plugin remove my-plugin
System-Wide Deployment
For multi-user systems:
# System-wide location (requires root)
sudo cp -r my-plugin /opt/prtip/plugins/
# Update ProRT-IP config
sudo tee -a /etc/prtip/config.toml << EOF
[plugins]
system_path = "/opt/prtip/plugins"
user_path = "~/.prtip/plugins"
EOF
Troubleshooting
Issue 1: Plugin Not Loading
Symptom: Plugin doesn't appear in --list-plugins
Diagnosis:
- Check file locations:
ls -la ~/.prtip/plugins/my-plugin/ # Should show: plugin.toml, main.lua - Verify
plugin.tomlis valid TOML:cat ~/.prtip/plugins/my-plugin/plugin.toml - Check ProRT-IP logs:
prtip --log-level debug --list-plugins
Solutions:
- Fix TOML syntax errors
- Ensure required fields (name, version, author) are present
- Verify directory name matches plugin name
Issue 2: Capability Errors
Symptom: "Plugin lacks 'network' capability"
Diagnosis:
Plugin requires capability not granted in plugin.toml.
Solution: Add required capability:
capabilities = ["network"]
Issue 3: Resource Limit Exceeded
Symptom: "Instruction limit exceeded" or "Memory limit exceeded"
Diagnosis: Plugin is too resource-intensive.
Solutions:
- Optimize Lua code (reduce loops, reuse tables)
- Request increased limits (contact ProRT-IP maintainers)
- Break processing into smaller chunks
Issue 4: Lua Syntax Errors
Symptom: "Failed to execute Lua code"
Diagnosis:
Syntax error in main.lua.
Solution: Test Lua syntax:
lua -l main.lua
Fix reported errors.
Best Practices
Security
- Minimize Capabilities: Only request what you need
- Validate Input: Never trust banner/target data
- Handle Errors: Use
pcall()for unsafe operations - Avoid Secrets: Don't hardcode credentials
- Log Securely: Sanitize sensitive data in logs
Performance
- Avoid Global State: Use local variables
- Reuse Tables: Don't create tables in loops
- Cache Results: Store frequently accessed data
- Lazy Loading: Defer expensive operations
- Profile Code: Measure execution time
Maintainability
- Document Functions: Use comments liberally
- Follow Conventions: Use ProRT-IP naming
- Version Carefully: Use semantic versioning
- Test Thoroughly: Cover edge cases
- Keep Simple: KISS principle
Example: Optimized Banner Analysis
Bad:
function analyze_banner(banner)
for i = 1, #services do
if string.match(banner, services[i].pattern) then
return create_service_info(services[i])
end
end
return nil
end
Good:
-- Cache pattern table (created once)
local patterns = build_pattern_table()
function analyze_banner(banner)
local lower = string.lower(banner)
-- Quick rejection for most cases
if #lower < 3 then return nil end
-- Ordered by frequency (HTTP most common)
return analyze_http(lower)
or analyze_ssh(lower)
or analyze_ftp(lower)
end
Complete Example
Here's a complete plugin demonstrating all concepts.
plugin.toml:
[plugin]
name = "http-version-detector"
version = "1.0.0"
author = "Example Author"
description = "Detects HTTP server versions"
plugin_type = "detection"
capabilities = []
main.lua:
function on_load(config)
prtip.log("info", "HTTP Version Detector loaded")
return true
end
function on_unload()
prtip.log("info", "HTTP Version Detector unloaded")
end
local function extract_version(text, pattern)
return string.match(text, pattern)
end
function analyze_banner(banner)
local lower = string.lower(banner)
if string.match(lower, "^http/") then
local http_version = extract_version(banner, "HTTP/([%d%.]+)")
if string.match(lower, "apache") then
local apache_version = extract_version(banner, "Apache/([%d%.]+)")
return {
service = "http",
product = "Apache",
version = apache_version,
info = "HTTP/" .. (http_version or "1.1"),
confidence = apache_version and 0.95 or 0.85
}
elseif string.match(lower, "nginx") then
local nginx_version = extract_version(banner, "nginx/([%d%.]+)")
return {
service = "http",
product = "nginx",
version = nginx_version,
info = "HTTP/" .. (http_version or "1.1"),
confidence = nginx_version and 0.95 or 0.85
}
else
return {
service = "http",
version = http_version,
confidence = 0.7
}
end
end
return nil
end
function probe_service(target)
-- Passive plugin, no active probing
return nil
end
Usage:
# Install
cp -r http-version-detector ~/.prtip/plugins/
# Use in scan
prtip -sS -p 80,443,8080 target.com --plugin http-version-detector
See Also
- Service Detection - Core service detection capabilities enhanced by plugins
- User Guide: Plugins - Usage examples and plugin management
- Development: Plugin API - Complete API specification
- Examples Gallery - Plugin usage examples
External Resources:
- Lua 5.4 Manual: https://www.lua.org/manual/5.4/
- mlua Documentation: https://docs.rs/mlua/latest/mlua/
- Plugin Repository: https://github.com/doublegate/ProRT-IP/tree/main/examples/plugins
Last Updated: 2024-11-06 ProRT-IP Version: v0.5.0+