No Rate Limiting or Amount Validation - Spam & Economic Exploits #3

Closed
opened 2025-05-26 16:50:18 +00:00 by saulteafarmer · 1 comment

The Orangemart plugin has no rate limiting or maximum amount validation, allowing users to spam commands and enter astronomical purchase amounts, potentially causing economic exploits, server performance issues, and excessive API calls to the LNBits node.

Root Cause Analysis

Affected Commands - No Validation

1. CmdBuyCurrency() - Unlimited Amounts (Lines ~560-590)

if (args.Length != 1 || !int.TryParse(args[0], out int amount) || amount <= 0)
{
    player.Reply(Lang("InvalidCommandUsage", player.Id, buyCurrencyCommandName));
    return;
}
// ← NO MAXIMUM AMOUNT CHECK
// ← NO RATE LIMITING

int amountSats = amount * pricePerCurrencyUnit;
CreateInvoice(amountSats, $"Buying {amount} {currencyName}", invoiceResponse => {
    // ... creates invoice for ANY amount
});

2. CmdSendCurrency() - Unlimited Sends (Lines ~300-350)

if (args.Length != 2 || !int.TryParse(args[0], out int amount) || amount <= 0)
{
    player.Reply(Lang("UsageSendCurrency", player.Id, sendCurrencyCommandName));
    return;
}
// ← NO MAXIMUM SEND LIMIT
// ← NO RATE LIMITING
// ← NO COOLDOWN BETWEEN SENDS

SendBitcoin(lightningAddress, amount * satsPerCurrencyUnit, (success, paymentHash) => {
    // ... processes ANY amount
});

3. CmdBuyVip() - Spam Vulnerable (Lines ~600-630)

// No rate limiting at all - users can spam VIP purchases
CreateInvoice(amountSats, "Buying VIP Status", invoiceResponse => {
    // ... creates unlimited VIP invoices
});

Potential Exploits & Issues

1. Economic Exploits

# Player enters astronomical amounts
/buyblood 999999999  # Creates 999M sat invoice
/buyblood 2147483647 # Integer overflow potential
/sendblood 1000000 player@wallet.com  # Massive Lightning payments

2. Lightning Network Spam

  • API Rate Limits: LNbits instances may have API limits
  • Channel Liquidity: Large invoices can exhaust Lightning channels
  • Network Fees: Massive payments generate unnecessary fees
  • Resource Consumption: Each invoice creates pending timers

3. Server Performance Issues

# Command spam attacks
/buyblood 1 (x1000 rapid fire)
/buyvip (spam creates multiple pending invoices)
/sendblood 1 address@domain.com (x100 concurrent)

4. Integer Overflow Vulnerabilities

int amountSats = amount * pricePerCurrencyUnit;
// If amount = 2,000,000,000 and pricePerCurrencyUnit = 2
// Result: Integer overflow → negative values or unexpected behavior

Impact

  • 💸 Economic Manipulation: Astronomical invoice amounts
  • 🚫 API Abuse: Overwhelming LNbits with requests
  • 🐌 Server Performance: Command spam degrading performance
  • 💥 Integer Overflow: Potential crashes or unexpected behavior
  • 🌐 Lightning Network Abuse: Unnecessarily large payments/invoices
  • 📊 Resource Exhaustion: Unlimited pending invoices consuming memory

Steps to Reproduce

Exploit 1: Astronomical Amounts

/buyblood 999999999
# Creates 999,999,999 sat invoice (≈$400,000+ USD)

Exploit 2: Command Spam

# Rapid fire commands (no cooldown)
/buyblood 1
/buyblood 1
/buyblood 1
# ... x1000 times in seconds

Exploit 3: Integer Overflow

/sendblood 2147483647 test@example.com  
# May cause integer overflow in sat calculation

Proposed Fix

Solution 1: Add Maximum Amount Limits

// Add to configuration
private static class ConfigKeys
{
    // ... existing keys ...
    public const string MaxPurchaseAmount = "MaxPurchaseAmount";
    public const string MaxSendAmount = "MaxSendAmount";
}

// Add to LoadConfig()
private int maxPurchaseAmount;
private int maxSendAmount;

maxPurchaseAmount = GetConfigValue(ConfigSections.CurrencySettings, ConfigKeys.MaxPurchaseAmount, 10000, ref configChanged);
maxSendAmount = GetConfigValue(ConfigSections.CurrencySettings, ConfigKeys.MaxSendAmount, 1000, ref configChanged);

// Updated command validation
private void CmdBuyCurrency(IPlayer player, string command, string[] args)
{
    if (args.Length != 1 || !int.TryParse(args[0], out int amount) || amount <= 0)
    {
        player.Reply(Lang("InvalidCommandUsage", player.Id, buyCurrencyCommandName));
        return;
    }

    // NEW: Maximum amount validation
    if (amount > maxPurchaseAmount)
    {
        player.Reply(Lang("AmountTooLarge", player.Id, amount, maxPurchaseAmount, currencyName));
        return;
    }

    // NEW: Integer overflow protection
    long amountSatsLong = (long)amount * pricePerCurrencyUnit;
    if (amountSatsLong > int.MaxValue)
    {
        player.Reply(Lang("AmountCausesOverflow", player.Id));
        return;
    }

    int amountSats = (int)amountSatsLong;
    // ... continue with safe values
}

Solution 2: Add Rate Limiting System

// Add cooldown tracking
private Dictionary<string, DateTime> lastCommandTime = new Dictionary<string, DateTime>();
private int commandCooldownSeconds = 30; // Configurable

private bool IsOnCooldown(IPlayer player, string commandType)
{
    string key = $"{GetPlayerId(player)}:{commandType}";
    
    if (lastCommandTime.TryGetValue(key, out DateTime lastTime))
    {
        double secondsSince = (DateTime.UtcNow - lastTime).TotalSeconds;
        if (secondsSince < commandCooldownSeconds)
        {
            double remaining = commandCooldownSeconds - secondsSince;
            player.Reply(Lang("CommandOnCooldown", player.Id, commandType, Math.Ceiling(remaining)));
            return true;
        }
    }
    
    lastCommandTime[key] = DateTime.UtcNow;
    return false;
}

// Apply to commands
private void CmdBuyCurrency(IPlayer player, string command, string[] args)
{
    // Rate limiting check
    if (IsOnCooldown(player, "buy")) return;
    
    // ... rest of validation and logic
}

Solution 3: Pending Invoice Limits

private int maxPendingInvoicesPerPlayer = 3; // Configurable

private bool HasTooManyPendingInvoices(IPlayer player)
{
    string playerId = GetPlayerId(player);
    int pendingCount = pendingInvoices.Count(inv => GetPlayerId(inv.Player) == playerId);
    
    if (pendingCount >= maxPendingInvoicesPerPlayer)
    {
        player.Reply(Lang("TooManyPendingInvoices", player.Id, pendingCount, maxPendingInvoicesPerPlayer));
        return true;
    }
    
    return false;
}

Required Configuration Updates

Add to LoadDefaultConfig():

Config[ConfigSections.CurrencySettings] = new Dictionary<string, object>
{
    // ... existing settings ...
    [ConfigKeys.MaxPurchaseAmount] = 10000,
    [ConfigKeys.MaxSendAmount] = 1000,
    ["CommandCooldownSeconds"] = 30,
    ["MaxPendingInvoicesPerPlayer"] = 3
};

Required Language Additions

["AmountTooLarge"] = "Amount {0} exceeds maximum limit of {1} {2}. Please use a smaller amount.",
["AmountCausesOverflow"] = "Amount too large and would cause calculation errors. Please use a smaller amount.",
["CommandOnCooldown"] = "Command '{0}' is on cooldown. Please wait {1} more seconds.",
["TooManyPendingInvoices"] = "You have {0} pending invoices (max: {1}). Please complete or wait for them to expire.",
const int MAX_PURCHASE_AMOUNT = 10000;     // ~$4-40 USD depending on sat price
const int MAX_SEND_AMOUNT = 1000;          // Reasonable send limit  
const int COMMAND_COOLDOWN = 30;           // 30 second cooldown
const int MAX_PENDING_PER_PLAYER = 3;      // Max 3 pending invoices

Environment

  • High Player Count: Spam attacks become more likely
  • Lightning Network: Rate limits and liquidity constraints
  • Server Performance: Command processing overhead

Severity

🟠 Medium-High - Economic exploits and potential DoS vectors

Additional Notes

Without these limits, malicious players could:

  • Create $100,000+ Lightning invoices
  • Spam commands to degrade server performance
  • Exhaust Lightning Network liquidity
  • Trigger integer overflow bugs
  • Overwhelm LNbits API rate limits

The limits should be configurable so admins can adjust based on their economic model and server capacity.

The Orangemart plugin has **no rate limiting or maximum amount validation**, allowing users to spam commands and enter astronomical purchase amounts, potentially causing **economic exploits, server performance issues, and excessive API calls** to the LNBits node. ## Root Cause Analysis ### **Affected Commands - No Validation** #### **1. CmdBuyCurrency() - Unlimited Amounts** (Lines ~560-590) ```csharp if (args.Length != 1 || !int.TryParse(args[0], out int amount) || amount <= 0) { player.Reply(Lang("InvalidCommandUsage", player.Id, buyCurrencyCommandName)); return; } // ← NO MAXIMUM AMOUNT CHECK // ← NO RATE LIMITING int amountSats = amount * pricePerCurrencyUnit; CreateInvoice(amountSats, $"Buying {amount} {currencyName}", invoiceResponse => { // ... creates invoice for ANY amount }); ``` #### **2. CmdSendCurrency() - Unlimited Sends** (Lines ~300-350) ```csharp if (args.Length != 2 || !int.TryParse(args[0], out int amount) || amount <= 0) { player.Reply(Lang("UsageSendCurrency", player.Id, sendCurrencyCommandName)); return; } // ← NO MAXIMUM SEND LIMIT // ← NO RATE LIMITING // ← NO COOLDOWN BETWEEN SENDS SendBitcoin(lightningAddress, amount * satsPerCurrencyUnit, (success, paymentHash) => { // ... processes ANY amount }); ``` #### **3. CmdBuyVip() - Spam Vulnerable** (Lines ~600-630) ```csharp // No rate limiting at all - users can spam VIP purchases CreateInvoice(amountSats, "Buying VIP Status", invoiceResponse => { // ... creates unlimited VIP invoices }); ``` ## Potential Exploits & Issues ### **1. Economic Exploits** ```bash # Player enters astronomical amounts /buyblood 999999999 # Creates 999M sat invoice /buyblood 2147483647 # Integer overflow potential /sendblood 1000000 player@wallet.com # Massive Lightning payments ``` ### **2. Lightning Network Spam** - **API Rate Limits**: LNbits instances may have API limits - **Channel Liquidity**: Large invoices can exhaust Lightning channels - **Network Fees**: Massive payments generate unnecessary fees - **Resource Consumption**: Each invoice creates pending timers ### **3. Server Performance Issues** ```bash # Command spam attacks /buyblood 1 (x1000 rapid fire) /buyvip (spam creates multiple pending invoices) /sendblood 1 address@domain.com (x100 concurrent) ``` ### **4. Integer Overflow Vulnerabilities** ```csharp int amountSats = amount * pricePerCurrencyUnit; // If amount = 2,000,000,000 and pricePerCurrencyUnit = 2 // Result: Integer overflow → negative values or unexpected behavior ``` ## Impact - 💸 **Economic Manipulation**: Astronomical invoice amounts - 🚫 **API Abuse**: Overwhelming LNbits with requests - 🐌 **Server Performance**: Command spam degrading performance - 💥 **Integer Overflow**: Potential crashes or unexpected behavior - 🌐 **Lightning Network Abuse**: Unnecessarily large payments/invoices - 📊 **Resource Exhaustion**: Unlimited pending invoices consuming memory ## Steps to Reproduce ### **Exploit 1: Astronomical Amounts** ```bash /buyblood 999999999 # Creates 999,999,999 sat invoice (≈$400,000+ USD) ``` ### **Exploit 2: Command Spam** ```bash # Rapid fire commands (no cooldown) /buyblood 1 /buyblood 1 /buyblood 1 # ... x1000 times in seconds ``` ### **Exploit 3: Integer Overflow** ```bash /sendblood 2147483647 test@example.com # May cause integer overflow in sat calculation ``` ## Proposed Fix ### **Solution 1: Add Maximum Amount Limits** ```csharp // Add to configuration private static class ConfigKeys { // ... existing keys ... public const string MaxPurchaseAmount = "MaxPurchaseAmount"; public const string MaxSendAmount = "MaxSendAmount"; } // Add to LoadConfig() private int maxPurchaseAmount; private int maxSendAmount; maxPurchaseAmount = GetConfigValue(ConfigSections.CurrencySettings, ConfigKeys.MaxPurchaseAmount, 10000, ref configChanged); maxSendAmount = GetConfigValue(ConfigSections.CurrencySettings, ConfigKeys.MaxSendAmount, 1000, ref configChanged); // Updated command validation private void CmdBuyCurrency(IPlayer player, string command, string[] args) { if (args.Length != 1 || !int.TryParse(args[0], out int amount) || amount <= 0) { player.Reply(Lang("InvalidCommandUsage", player.Id, buyCurrencyCommandName)); return; } // NEW: Maximum amount validation if (amount > maxPurchaseAmount) { player.Reply(Lang("AmountTooLarge", player.Id, amount, maxPurchaseAmount, currencyName)); return; } // NEW: Integer overflow protection long amountSatsLong = (long)amount * pricePerCurrencyUnit; if (amountSatsLong > int.MaxValue) { player.Reply(Lang("AmountCausesOverflow", player.Id)); return; } int amountSats = (int)amountSatsLong; // ... continue with safe values } ``` ### **Solution 2: Add Rate Limiting System** ```csharp // Add cooldown tracking private Dictionary<string, DateTime> lastCommandTime = new Dictionary<string, DateTime>(); private int commandCooldownSeconds = 30; // Configurable private bool IsOnCooldown(IPlayer player, string commandType) { string key = $"{GetPlayerId(player)}:{commandType}"; if (lastCommandTime.TryGetValue(key, out DateTime lastTime)) { double secondsSince = (DateTime.UtcNow - lastTime).TotalSeconds; if (secondsSince < commandCooldownSeconds) { double remaining = commandCooldownSeconds - secondsSince; player.Reply(Lang("CommandOnCooldown", player.Id, commandType, Math.Ceiling(remaining))); return true; } } lastCommandTime[key] = DateTime.UtcNow; return false; } // Apply to commands private void CmdBuyCurrency(IPlayer player, string command, string[] args) { // Rate limiting check if (IsOnCooldown(player, "buy")) return; // ... rest of validation and logic } ``` ### **Solution 3: Pending Invoice Limits** ```csharp private int maxPendingInvoicesPerPlayer = 3; // Configurable private bool HasTooManyPendingInvoices(IPlayer player) { string playerId = GetPlayerId(player); int pendingCount = pendingInvoices.Count(inv => GetPlayerId(inv.Player) == playerId); if (pendingCount >= maxPendingInvoicesPerPlayer) { player.Reply(Lang("TooManyPendingInvoices", player.Id, pendingCount, maxPendingInvoicesPerPlayer)); return true; } return false; } ``` ## Required Configuration Updates Add to `LoadDefaultConfig()`: ```csharp Config[ConfigSections.CurrencySettings] = new Dictionary<string, object> { // ... existing settings ... [ConfigKeys.MaxPurchaseAmount] = 10000, [ConfigKeys.MaxSendAmount] = 1000, ["CommandCooldownSeconds"] = 30, ["MaxPendingInvoicesPerPlayer"] = 3 }; ``` ## Required Language Additions ```csharp ["AmountTooLarge"] = "Amount {0} exceeds maximum limit of {1} {2}. Please use a smaller amount.", ["AmountCausesOverflow"] = "Amount too large and would cause calculation errors. Please use a smaller amount.", ["CommandOnCooldown"] = "Command '{0}' is on cooldown. Please wait {1} more seconds.", ["TooManyPendingInvoices"] = "You have {0} pending invoices (max: {1}). Please complete or wait for them to expire.", ``` ## Recommended Limits ```csharp const int MAX_PURCHASE_AMOUNT = 10000; // ~$4-40 USD depending on sat price const int MAX_SEND_AMOUNT = 1000; // Reasonable send limit const int COMMAND_COOLDOWN = 30; // 30 second cooldown const int MAX_PENDING_PER_PLAYER = 3; // Max 3 pending invoices ``` ## Environment - **High Player Count**: Spam attacks become more likely - **Lightning Network**: Rate limits and liquidity constraints - **Server Performance**: Command processing overhead ## Severity 🟠 **Medium-High** - Economic exploits and potential DoS vectors ## Additional Notes Without these limits, malicious players could: - Create $100,000+ Lightning invoices - Spam commands to degrade server performance - Exhaust Lightning Network liquidity - Trigger integer overflow bugs - Overwhelm LNbits API rate limits The limits should be **configurable** so admins can adjust based on their economic model and server capacity.
Author
Owner

Fixed on 0.4.0

Fixed on 0.4.0
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: rustysats/orangemart.cs#3
No description provided.