פגמים בלוגיקה עסקית - 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¶
בשפות עם מספרים שלמים קבועי גודל, ערך גדול מדי גולש ונהפך לשלילי:
// 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})
החלת הנחה אחרי שינוי מחיר¶
- הוספת מוצר זול (10 דולר) לעגלה
- החלת קופון 50% (הנחה = 5 דולר)
- שינוי המוצר בעגלה למוצר יקר (1000 דולר)
- ההנחה עדיין חלה - אבל האם היא 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
תקיפה - דילוג ישירות לאישור:
קוד פגיע:
@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)
@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()
סיכום¶
פגמי לוגיקה עסקית דורשים חשיבה יצירתית ולא כלים טכניים. הנקודות המרכזיות:
- בדקו כל הנחה שהאפליקציה עושה לגבי התנהגות המשתמש
- נסו כמויות שליליות, ערכים קיצוניים, ודילוג על שלבים
- חפשו שדות נסתרים שניתן לשנות
- בדקו אם ולידציה מתבצעת בצד הלקוח בלבד
- מפו את כל מעברי המצב האפשריים ובדקו מעברים לא חוקיים
- הגנה: ולידציה בצד שרת לכל כלל עסקי, ללא יוצא מן הכלל