#47731 [W&A-Insight] Offer Listings N+1 Query Performance Vulnerability

Submitted on Jun 19th 2025 at 10:31:18 UTC by @Opzteam for IOP | Zano Trade

  • Report ID: #47731

  • Report Type: Websites & Apps

  • Report severity: Insight

  • Target: https://github.com/PRavaga/zano-p2p/tree/master/src/pages/dex/orders

  • Impacts:

    • Taking down the application/website

Description

  • The offers listing functionality contains a critical N+1 query performance vulnerability where individual database queries are executed inside loops for each offer, resulting in exponential query growth under load. The OffersModel.getPage method performs 4 separate database queries per offer (user lookup and 3 currency lookups), meaning a single page request with 15 offers triggers 61 database queries instead of 1-2 optimized queries. Under heavy traffic conditions typical of trading platforms, this inefficiency could cause severe performance degradation, database connection pool exhaustion, and potential service outages affecting the entire P2P trading marketplace.

  • The performance vulnerability exists in the core offers listing functionality in api/models/Offers.ts, specifically in the getPage method:

// Single query to fetch offers (efficient)
const offers = (await Offer.findAll({
    where: OffersWhereConditions,
    order: [orderCondition],
    limit: 15,
    offset: (pageData.page - 1) * 15
}));

let offersWithUsers = [];

// N+1 query anti-pattern - loops through each offer
for (let offer of offers) {
    // Query 1: Individual user lookup per offer
    const user = await User.findOne({
        where: { id: offer.user_id }
    });

    // Query 2: Individual input currency lookup per offer  
    const inputCurrency = await configModel.getCurrencyRow(offer.input_currency_id);
    
    // Query 3: Individual target currency lookup per offer
    const targetCurrency = await configModel.getCurrencyRow(offer.target_currency_id);
    
    // Query 4: Individual deposit currency lookup per offer
    const depositCurrency = await configModel.getCurrencyRow(offer.deposit_currency_id);
    
    // Data assembly continues...
}
  • Exponential Query Growth Pattern

Query Analysis per Request:

  • The configModel.getCurrencyRow method performs individual database lookups:

  • This method is called 3 times per offer, creating substantial redundancy since the same currencies are likely reused across multiple offers.

  • The vulnerable method is exposed via a public API endpoint: This endpoint is accessed via /api/offers/get-page and is used throughout the frontend for displaying trading offers, making it a critical performance bottleneck.

Proof of Concept

  1. Each page request consumes 61 database connections

  2. Under moderate load (100 concurrent users), this could require 6,100 simultaneous database connections

  3. Most database configurations have connection limits of 100-1000 connections

Database CPU usage increases exponentially with concurrent requests

Proof of Concept

Remediation

  • Immediate Fix: Implement Sequelize Associations and Includes

  • Optimize getPage Method with Single Query.

  • Implement Query Result Caching

Was this helpful?