728x90
반응형
https://cubectf.com/challenges#Legal%20Snacks-4
CubeCTF
cubectf.com
개요
쇼핑 사이트이다. 쇼핑 사이트에선 잔액에 비해 큰 값의 상품을 구매하면 보통 flag를 얻는 방식이 많다.
기능 분석
로그인 기능
로그인 기능이 있다.
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
user = User.query.filter_by(username=username).first()
if user and check_password_hash(user.password_hash, password):
session['user_id'] = user.id
return redirect(url_for('index'))
flash('Invalid credentials!')
return render_template('login.html')
ORM이 사용되고 있어 SQLI는 발생하기 어려워보인다.
상품 구매
상품을 카트에 담아서 결제할 수 있다. 상세한 내용은 취약점 분석에서 살펴보겠다.
취약점 분석
@app.route('/orders/<int:id>/receipt')
@login_required
def order_confirmation(id):
order = SnackOrder.query.filter_by(id=id, user_id=session['user_id']).first_or_404()
if any(item.product.name == 'Elite Hacker Snack' for item in order.items):
return render_template('order_confirmation.html', order=order, flag=os.environ.get('FLAG', 'cube{lmao_flag}'))
return render_template('order_confirmation.html', order=order)
flag를 얻기 위해선 Elite Hacker Snack을 구매하면된다. 이 상품은 아래와 같다.
하지만 잔액에 비해 너무 비싸다.
@app.route('/cart')
def cart():
cart_items = []
total = 0
cart = session.get('cart', {})
for product_id, quantity in cart.items():
product = SnackProduct.query.get(int(product_id))
if product:
cart_items.append({
'product': product,
'quantity': quantity,
'subtotal': product.price * quantity
})
total += product.price * quantity
user_balance = 0
if 'user_id' in session:
user = User.query.get(session['user_id'])
user_balance = user.balance if user else 0
return render_template('cart.html', cart_items=cart_items, total=total, user_balance=user_balance)
@app.route('/checkout', methods=['GET', 'POST'])
@login_required
def checkout():
if request.method == 'POST':
cart = session.get('cart', {})
if not cart:
return redirect(url_for('cart'))
user = User.query.get(session['user_id'])
if not user:
flash('User not found!')
return redirect(url_for('login'))
total = 0
for product_id, quantity in cart.items():
product = SnackProduct.query.get(int(product_id))
if product:
total += product.price * quantity
if user.balance < total:
flash(f'Insufficient balance! You have ${user.balance:.2f} but need ${total:.2f}')
return redirect(url_for('cart'))
if total <= 0:
flash('Total must be greater than zero!')
return redirect(url_for('cart'))
order = SnackOrder(user_id=session['user_id'], total=total)
db.session.add(order)
db.session.flush()
for product_id, quantity in cart.items():
product = SnackProduct.query.get(int(product_id))
if product:
item = OrderItem(order_id=order.id, product_id=product.id, quantity=quantity)
db.session.add(item)
user.balance -= total
db.session.commit()
session.pop('cart', None)
return redirect(url_for('order_confirmation', id=order.id))
user = User.query.get(session['user_id'])
return render_template('checkout.html', user_balance=user.balance if user else 0)
위 코드를 잘 살펴보면 상품의 총 금액이 0으로 가지 않게 검사하는 부분이 결제할 때이다. 또한 개수를 음수로 주문할 수 있다.
여기서 취약점이 발생한다.
Elite Hacker Snack을 카트에 담아 99999 달러만큼 내야한다. 하지만 다른 상품을 음수개로 0보다 크게, 내 잔액보다 작게 구매한다면 Elite Hacker Snack을 구매할 수 있게된다.
대략적으로 12375개만큼만 음수개로 사면 flag를 얻을 수 있을 것이다.
브라우저로는 -12375만큼 입력하지 못하게 되어있다. 버프스위트로 입력하는게 html 수정하는 것보다 편하기 때문에 버프 스위트로 입력했다.
카드를 입력할 때도 형식에 맞게 입력해주어야한다.
짜잔
cube{happy birthday!:flag_us::flag_us::flag_us::flag_us::flag_us:_c65ece2a}
대응 방안
- 비즈니스 로직이 적절하게 짜여지지 않아 발생한 문제이다. 상품을 음수 개로 살 수 없도록 수정해야한다.
728x90
반응형
'분류 전 > CTF' 카테고리의 다른 글
[L3ak CTF 2025] Flag L3ak 풀이 (0) | 2025.07.15 |
---|---|
[R3CTF 2025] web-evalgelist 풀이 (3) | 2025.07.13 |
[IERAE CTF 2025][WEB] Warmdown 풀이 (0) | 2025.06.22 |
[2024 IS_LAB CTF] meta-data 풀이 (0) | 2024.02.07 |
[2024 IS_LAB CTF] Robots REVENGE 풀이 (1) | 2024.02.05 |