카드테스팅(BIN 공격)은 알려진 BIN(발급사 식별번호) 뒤의 나머지 숫자를 대량으로 대입해, 소액 승인요청만으로 유효한 카드번호를 찾아내는 공격입니다. 실제 침투테스트 도구(hydra)로 이 공격을 재현하고, 속도제한(velocity check)이 이를 어떻게 막는지 검증했습니다.
카드 승인 API에 시도 횟수 제한을 걸지 않은 결제 시스템을 만드는 백엔드 개발자용 학습 자료입니다.
시도 횟수 제한 없이 카드번호 유효성을 확인해주는 취약한 결제 API.
// 취약한 결제 API — 카드번호가 유효한지 몇 번이든 확인해줌.
// 소액(또는 $0) 승인요청이라 피해는 작아 보이지만, 공격자는
// 이 응답 자체를 "유효한 카드번호 탐색기"로 악용함(hydra로 검증).
class UnlimitedChargeAttempts {
constructor(validCards) {
this.validCards = validCards; // Set<cardNumber>
}
charge(cardNumber) {
if (this.validCards.has(cardNumber)) {
return { status: 'card_valid' };
}
return { status: 'card_declined' };
}
}
module.exports = { UnlimitedChargeAttempts };
해결책: 같은 출처가 실패를 일정 횟수 넘기면 그 이후로는 전부 차단한다.
// 해결책: 같은 출처(IP 등)의 실패 횟수를 세어, 일정 횟수를
// 넘으면 그 이후 요청은 카드번호를 보지도 않고 즉시 차단.
class VelocityCheckGuard {
constructor(validCards, blockAfter = 3) {
this.validCards = validCards;
this.blockAfter = blockAfter;
this.failuresBySource = new Map();
}
charge(cardNumber, source) {
const fails = this.failuresBySource.get(source) || 0;
if (fails >= this.blockAfter) {
return { status: 'rate_limited' }; // 진짜 카드라도 이젠 안 알려줌
}
if (this.validCards.has(cardNumber)) {
return { status: 'card_valid' };
}
this.failuresBySource.set(source, fails + 1);
return { status: 'card_declined' };
}
}
module.exports = { VelocityCheckGuard };
이 상품에 대한 피드백이나 질문이 있으면 알려주세요. 회신을 원하시면 이메일을 남겨주세요(선택).