-
Notifications
You must be signed in to change notification settings - Fork 4.2k
feat(core): [Card Testing Guard] Implement Card Testing Guard #7108
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
3f5df26
to
6533e83
Compare
crates/common_utils/src/consts.rs
Outdated
pub const DEFAULT_CUSTOMER_ID_BLOCKING_THRESHOLD: i32 = 5; | ||
|
||
/// Default Card Testing Guard Redis Expiry | ||
pub const DEFAULT_CARD_TESTING_GUARD_EXPIRY: i32 = 3600; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pub const DEFAULT_CARD_TESTING_GUARD_EXPIRY: i32 = 3600; | |
pub const DEFAULT_CARD_TESTING_GUARD_EXPIRY_IN_SECS: i32 = 3600; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have resolved it, thanks
4ec51bc
to
17776e5
Compare
1ff0d20
to
b871ec9
Compare
crates/api_models/src/admin.rs
Outdated
pub struct CardTestingGuardConfig { | ||
/// Determines if Card IP Blocking is enabled for profile | ||
pub is_card_ip_blocking_enabled: bool, | ||
/// Determines the unsuccessful payment threshold for Card IP Blocking for profile | ||
pub card_ip_blocking_threshold: i32, | ||
/// Determines if Guest User Card Blocking is enabled for profile | ||
pub is_guest_user_card_blocking_enabled: bool, | ||
/// Determines the unsuccessful payment threshold for Guest User Card Blocking for profile | ||
pub guest_user_card_blocking_threshold: i32, | ||
/// Determines if Customer Id Blocking is enabled for profile | ||
pub is_customer_id_blocking_enabled: bool, | ||
/// Determines the unsuccessful payment threshold for Customer Id Blocking for profile | ||
pub customer_id_blocking_threshold: i32, | ||
/// Determines Redis Expiry for Card Testing Guard for profile | ||
pub card_testing_guard_expiry: i32, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pub struct CardTestingGuardConfig { | |
/// Determines if Card IP Blocking is enabled for profile | |
pub is_card_ip_blocking_enabled: bool, | |
/// Determines the unsuccessful payment threshold for Card IP Blocking for profile | |
pub card_ip_blocking_threshold: i32, | |
/// Determines if Guest User Card Blocking is enabled for profile | |
pub is_guest_user_card_blocking_enabled: bool, | |
/// Determines the unsuccessful payment threshold for Guest User Card Blocking for profile | |
pub guest_user_card_blocking_threshold: i32, | |
/// Determines if Customer Id Blocking is enabled for profile | |
pub is_customer_id_blocking_enabled: bool, | |
/// Determines the unsuccessful payment threshold for Customer Id Blocking for profile | |
pub customer_id_blocking_threshold: i32, | |
/// Determines Redis Expiry for Card Testing Guard for profile | |
pub card_testing_guard_expiry: i32, | |
} | |
pub struct CardTestingGuardConfig { | |
/// Determines if Card IP Blocking is enabled for profile | |
pub card_ip_blocking: Enabled/Disabled, | |
/// Determines the unsuccessful payment threshold for Card IP Blocking for profile | |
pub card_ip_blocking_threshold: i32, | |
/// Determines if Guest User Card Blocking is enabled for profile | |
pub guest_user_card_blocking: Enabled/Disable, | |
/// Determines the unsuccessful payment threshold for Guest User Card Blocking for profile | |
pub guest_user_card_blocking_threshold: i32, | |
/// Determines if Customer Id Blocking is enabled for profile | |
pub customer_id_blocking: Enabled/Disabled, | |
/// Determines the unsuccessful payment threshold for Customer Id Blocking for profile | |
pub customer_id_blocking_threshold: i32, | |
/// Determines Redis Expiry for Card Testing Guard for profile | |
pub card_testing_guard_expiry_in_secs: i32, | |
} | |
Avoid using boolean instead use enum, and add unit when it comes to SI units. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have resolved it, thanks
/// * The Fingerprint encryption operation fails | ||
pub async fn get_merchant_profile_fingerprint_secret( | ||
state: &SessionState, | ||
merchant_id: &common_utils::id_type::MerchantId, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pass merchant_key_store as a argument to this function, utils function shouldn't read data from database and already merchant key store is populated in the handler after authentication
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we are no longer generating cardtesting_secret_key in oltp flow, this function has been removed.
|
||
match merchant_card_testing_secret_key { | ||
Some(card_testing_secret_key) => Ok(card_testing_secret_key.get_inner().clone().expose()), | ||
None => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there secret is not there we should throw error, we can't randomly create a key and insert that to profiles in OLTP flow. Profiles will get updated only in admin flows.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have resolved it, thanks. The card_testing_secret_key
will now only be updated in profile create and update flows.
crates/router/src/core/payments.rs
Outdated
pub card_testing_guard_data: Option<CardTestingGuardData>, | ||
} | ||
|
||
#[derive(Clone, serde::Serialize, Debug)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move this to domain_models, avoid writing types in payments.rs file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have resolved it, thanks
} | ||
} | ||
Ok(None) => Ok(cache_key), | ||
Err(_) => Err(errors::ApiErrorResponse::InternalServerError)?, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid discarding the error, the error should be lifted into internalservererror
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have resolved it, thanks
} | ||
} | ||
Ok(None) => Ok(cache_key), | ||
Err(_) => Err(errors::ApiErrorResponse::InternalServerError)?, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid discarding the error, the error should be lifted into internalservererror.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have resolved it, thanks.
} | ||
} | ||
Ok(None) => Ok(cache_key), | ||
Err(_) => Err(errors::ApiErrorResponse::InternalServerError)?, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid discarding the error, the error should be lifted into internalservererror.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have resolved it, thanks
let unsuccessful_payment_threshold = card_testing_guard_config.card_ip_blocking_threshold; | ||
|
||
match services::card_testing_guard::get_blocked_count_from_cache(state, &cache_key).await { | ||
Ok(Some(unsuccessful_payment_count)) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handling response is same for all validate functions means, refactor the code and avoid repetition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have resolved it, thanks
state: &SessionState, | ||
request: &api::PaymentsRequest, | ||
payment_method_data: Option<&api_models::payments::PaymentMethodData>, | ||
payment_data: &mut PaymentData<F>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't mutate paymentData in this function, instead pass the output, in the caller update PaymentData
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have resolved it, thanks.
554629d
…tion-fork * 'main' of github.com:juspay/hyperswitch: feat(core): [Card Testing Guard] Implement Card Testing Guard (juspay#7108) chore(version): 2025.02.25.0 ci: minor refactors and improvements (juspay#7308) fix(connector): [NMI] Added enum for Void Reason (juspay#7221) ci(CI-pr): disable `cargo-hack` step for hotfix PRs (juspay#7334)
Type of Change
Description
Problem Statement:
Card testing attacks involve fraudsters using stolen credit card details to make small transactions on e-commerce sites using different combinations of CVC to verify if the CVC is valid. Successful transactions indicate the card is active and the CVC is valid, while failed ones are discarded. This is done to avoid detection before committing larger fraudulent purchases.
Solution:
We have implemented three rules for detecting and preventing this card testing attacks:
I) Card <> IP Blocking for Merchant : If there are X number of unsuccessful payment attempts from a single IP Address for a specific merchant, then that combination of Card <> IP will be blocked for that merchant for a particular duration.
II) Card Blocking for Guest Users for Merchant: If there are X number of unsuccessful payment attempts for a single card, then guest user payments will be blocked for that card and that merchant for a particular duration. Logged in customers will still be able to use that card for payment.
III) Customer ID Blocking for Merchant: If there are X number of unsuccessful payment attempts from a single Customer ID, then that customer ID will be blocked from making any payments for a particular duration.
These unsuccessful payment thresholds and duration for blocking is configurable and stored in database.
Whether the merchants want these rules to be enabled/disabled, that is also configurable.
Implementation:
The attacker gets the
client_secret
from the payment intent create call which happens through our SDK, and uses theclient_secret
to hit the /confirm API repeatedly with different sets of card numbers and CVC.A method
validate_request_with_state
has been created in theGetTracker
trait which enforces the three above mentioned rules depending on the business_profile config. Thevalidate_request_with_state method
internally callsvalidate_card_ip_blocking_for_business_profile
method,validate_guest_user_card_blocking_for_business_profile
method andvalidate_customer_id_blocking_for_business_profile
method for performing the below validations:I) Card<>IP Blocking: A fingerprint is generated for the card which is unique to the
business_profile
(by using a hash key which is unique to the profile and stored ascard_testing_secret_key
inbusiness_profile
). This is done so that the cards are blocked at a profile level. The IP Address is fetched from the payment intent create request, and then for each unsuccessful payment attempt, the value of the redis key CardFingerprint<>IP is increased by 1, and after it reaches a certain threshold, it is blocked.II) Guest User Card Blocking: A fingerprint is generated for the card which is unique to the
business_profile
(by using a hash key which is unique to the profile and stored ascard_testing_secret_key
inbusiness_profile
). This is done so that the cards are not blocked at a profile level. For each unsuccessful payment attempt, the value of the redis key CardFingerprint is increased by 1, and after it reaches a certain threshold, it is blocked, but only for guest users. Logged in customers can still make payments with that card.III) Customer ID Blocking: The
customer_id
is fetched from the payment intent create call request, and then for each unsuccessful payment attempt, the value of redis key CardFingerprint<>Profile is increased by 1, and after it reaches a certain threshold, it is blocked.Additional Changes
Motivation and Context
How did you test it?
Postman Tests
Step By Step Guide to Test:
card_ip_blocking_status
asenabled
,guest_user_card_blocking_status
asdisabled
andcustomer_id_blocking_status
asdisabled
card_ip_blocking_threshold
), the payment attempt will be blocked.card_ip_blocking_status
asdisabled
,guest_user_card_blocking_status
asenabled
andcustomer_id_blocking_status
asdisabled
guest_user_card_blocking_threshold
), the payment attempt will be blocked.card_ip_blocking_status
asdisabled
,guest_user_card_blocking_status
asdisabled
andcustomer_id_blocking_status
asenabled
customer_id_blocking_threshold
), the payment attempt will be blocked.Update Business Profile (For enabling card_ip_blocking)
-Request
-Response
1. Card <> IP Blocking (Below Threshold)
-Request
-Response
2. Card <> IP Blocking (After threshold)
-Request
-Response
3. Guest User Card Blocking (Below Threshold)
-Request
-Response
4. Guest User Card Blocking (After Threshold & Guest User)
-Request
-Response
5. Customer ID Blocking (Below Threshold)
-Request
-Response
6. Customer ID Blocking (After Threshold)
-Request
-Response
Cypress Tests
Checklist
cargo +nightly fmt --all
cargo clippy