본문 바로가기
분류 전/CTF

[cubectf] Legal Snacks 풀이

by jwcs 2025. 7. 8.
728x90
반응형

https://cubectf.com/challenges#Legal%20Snacks-4

 

CubeCTF

 

cubectf.com

 

개요

/

쇼핑 사이트이다. 쇼핑 사이트에선 잔액에 비해 큰 값의 상품을 구매하면 보통 flag를 얻는 방식이 많다.

 

기능 분석

 

로그인 기능

 

/login

로그인 기능이 있다. 

@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는 발생하기 어려워보인다.

 

상품 구매

/products/2

상품을 카트에 담아서 결제할 수 있다. 상세한 내용은 취약점 분석에서 살펴보겠다.

 

취약점 분석

@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을 구매하면된다. 이 상품은 아래와 같다.

 

cart

하지만 잔액에 비해 너무 비싸다.

 

@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를 얻을 수 있을 것이다.

 

cart

브라우저로는 -12375만큼 입력하지 못하게 되어있다. 버프스위트로 입력하는게 html 수정하는 것보다 편하기 때문에 버프 스위트로 입력했다.

 

/checkout

카드를 입력할 때도 형식에 맞게 입력해주어야한다.

 

flag

짜잔

 

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