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-analyzer and ssl-checker included

Design Goals:

  1. Security First: Deny-by-default capabilities, resource limits, sandboxing
  2. Simple API: Easy to learn, hard to misuse
  3. Performance: Minimal overhead, async-compatible
  4. 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, or detection
  • 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 information
    • ip (string): IP address
    • port (number): Port number
    • protocol (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 address
  • port (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 from prtip.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 identifier
  • max_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 key
  • value (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

CapabilityDescriptionRisk Level
networkNetwork connectionsMedium
filesystemFile I/O operationsHigh
systemSystem commandsCritical
databaseDatabase accessMedium

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

ResourceLimitConfigurable
Memory100 MBYes
CPU Time5 secondsYes
Instructions1,000,000Yes

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/O
  • os - Operating system functions
  • debug - Debug introspection
  • package.loadlib - Native library loading

Safe Libraries

  • string - String manipulation
  • table - Table operations
  • math - Mathematical functions
  • prtip - 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.

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

  1. Identify the Problem: What functionality does ProRT-IP lack?
  2. Choose Plugin Type: Scan, Output, or Detection?
  3. List Required Capabilities: Network, Filesystem, etc.
  4. 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

  1. Load Test: Verify plugin loads without errors
  2. Functionality Test: Verify each function works correctly
  3. Error Handling Test: Trigger error conditions
  4. Performance Test: Measure execution time
  5. 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:

  1. Check file locations:
    ls -la ~/.prtip/plugins/my-plugin/
    # Should show: plugin.toml, main.lua
    
  2. Verify plugin.toml is valid TOML:
    cat ~/.prtip/plugins/my-plugin/plugin.toml
    
  3. 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:

  1. Optimize Lua code (reduce loops, reuse tables)
  2. Request increased limits (contact ProRT-IP maintainers)
  3. 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

  1. Minimize Capabilities: Only request what you need
  2. Validate Input: Never trust banner/target data
  3. Handle Errors: Use pcall() for unsafe operations
  4. Avoid Secrets: Don't hardcode credentials
  5. Log Securely: Sanitize sensitive data in logs

Performance

  1. Avoid Global State: Use local variables
  2. Reuse Tables: Don't create tables in loops
  3. Cache Results: Store frequently accessed data
  4. Lazy Loading: Defer expensive operations
  5. Profile Code: Measure execution time

Maintainability

  1. Document Functions: Use comments liberally
  2. Follow Conventions: Use ProRT-IP naming
  3. Version Carefully: Use semantic versioning
  4. Test Thoroughly: Cover edge cases
  5. 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

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+