לדלג לתוכן

פגמים בלוגיקה עסקית - 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;

    // פגיע! סומך על המחיר מהלקוח
    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

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

// פגיע - אין בדיקת כמות חיובית
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 });
    // סך העגלה: -1000. התשלום "יחזיר" כסף!
});

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

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

productId=1&quantity=2147483647

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

// בשפת C או שפות עם integer overflow
int quantity = 2147483647;  // MAX_INT
int price = 2;
int total = quantity * price;  // overflow! total = -2

ניצול קופונים והנחות - 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

    # פגיע - אין בדיקה כמה קופונים כבר הוחלו
    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%?
// פגיע - ההנחה נשמרת כאחוז אבל לא מחושבת מחדש
app.post('/cart/update-item', (req, res) => {
    const item = cart.getItem(req.body.itemId);
    const newProduct = db.getProduct(req.body.newProductId);

    // מעדכן את המוצר אבל לא מחשב מחדש הנחות
    item.productId = newProduct.id;
    item.price = newProduct.price;
    // item.discount לא השתנה!

    cart.save();
});

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

POST /cart/apply-coupon HTTP/1.1

code=ONETIME50

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

-- הסרת הקופון --

POST /cart/remove-coupon HTTP/1.1

code=ONETIME50

-- החלה מחדש --

POST /cart/apply-coupon HTTP/1.1

code=ONETIME50

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

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


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

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

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

שלב 1: POST /cart/review      -> בחירת מוצרים
שלב 2: POST /cart/shipping     -> הזנת כתובת
שלב 3: POST /cart/payment      -> תשלום
שלב 4: POST /cart/confirm      -> אישור הזמנה

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

שלב 1: POST /cart/review       -> בחירת מוצרים
שלב 4: POST /cart/confirm      -> אישור ללא תשלום!

קוד פגיע:

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

    # פגיע - אין בדיקה שהתשלום בוצע
    order = Order.create(
        items=cart.items,
        total=cart.total,
        status='confirmed'
    )

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

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

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

מצב תקין:    PENDING -> APPROVED -> SHIPPED -> DELIVERED
תקיפה:       PENDING -> DELIVERED  (דילוג על APPROVED ו-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']

    # פגיע - אין בדיקת מעבר מצב חוקי
    order.status = new_status
    db.session.commit()

    if new_status == 'REFUNDED':
        refund_payment(order)  # מחזיר כסף להזמנה שמעולם לא שולמה!

    return jsonify({'success': True})

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

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

<!-- ולידציה בצד לקוח בלבד -->
<form id="updateProfile">
    <input name="email" type="email" required>
    <input name="name" maxlength="50">
    <!-- שדה נסתר שנשלח אוטומטית -->
    <input name="role" type="hidden" value="user">
    <button type="submit">עדכן</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;

    // פגיע - מעדכן את כל השדות שנשלחו ללא סינון
    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
}

ניצול עיגול מטבע - Currency Rounding Exploitation

# מחיר מוצר: $0.01
# כמות: 1
# הנחה: 50%

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

# עיגול כלפי מטה -> $0.00
# המוצר חינמי!

# תקיפה: רכישת מוצרים ב-$0.005 שמעוגלים ל-$0.00
# ואז מכירתם חזרה ב-$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%

    # פגיע - עיגול לטובת הקונה
    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  # מוריד 0 מהיתרה
        add_items_to_inventory(user, quantity)

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

-- הוספת 1000 פריטים לעגלה (מרוקנים את המלאי)
POST /cart/add HTTP/1.1
productId=1&quantity=1000

-- מלאי המוצר: 0. אף אחד אחר לא יכול לקנות

-- המתנה עד שהמחיר יורד

-- עדכון כמות לפריט אחד ורכישה במחיר המוזל
POST /cart/update HTTP/1.1
productId=1&quantity=1

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

-- יצירת חשבון עם לינק הפניה של עצמנו
POST /register HTTP/1.1

email=user1@temp.com&referralCode=MY_CODE

-- שני הצדדים מקבלים בונוס
-- חוזרים על התהליך עם כתובות זמניות
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
        });

        // פגיע - אין בדיקה שהמפנה והנרשם הם אנשים שונים
        // אין הגבלה על מספר ההפניות
        if (referrer) {
            referrer.balance += 10;  // בונוס למפנה
            user.balance += 5;      // בונוס לנרשם
            await referrer.save();
        }
    }

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

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

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

// מודל מוצר
const products = {
    1: { name: 'Laptop', price: 1500, stock: 10 },
    2: { name: 'Phone', price: 800, stock: 20 },
    3: { name: 'Cable', price: 5, stock: 100 }
};

// פגיעות 1: אין ולידציה של מחיר בצד שרת
app.post('/api/cart/add', (req, res) => {
    const { productId, quantity, price } = req.body;
    cart.items.push({ productId, quantity, unitPrice: price });
    res.json({ success: true });
});

// פגיעות 2: אין בדיקת כמות שלילית
app.post('/api/cart/update', (req, res) => {
    const item = cart.getItem(req.body.itemId);
    item.quantity = req.body.quantity;  // יכול להיות שלילי!
    cart.recalculateTotal();
    res.json({ total: cart.total });
});

// פגיעות 3: ערימת קופונים ללא הגבלה
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 });
});

// פגיעות 4: אישור הזמנה ללא בדיקת תשלום
app.post('/api/order/confirm', (req, res) => {
    const order = createOrder(cart);
    order.status = 'confirmed';
    cart.clear();
    res.json({ orderId: order.id });
});

// פגיעות 5: עדכון סטטוס ללא בדיקת מעבר חוקי
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);

    // מוגן - המחיר נלקח מבסיס הנתונים, לא מהלקוח
    const price = product.price;

    // ולידציה של כמות
    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):
    # בדיקה שלא הוחלו יותר מדי קופונים
    if len(cart.applied_coupons) >= MAX_COUPONS:
        raise ValueError('Maximum coupons reached')

    # בדיקה שהקופון לא שומש
    if code in cart.applied_coupons:
        raise ValueError('Coupon already applied')

    # בדיקה שהקופון תקף
    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()

סיכום

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

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