Problem statement
How do we prevent bad actors/bots from hitting the public endpoint /passwordless/start
? These attacks are impacting our SMS costs due to the Passwordless Endpoint being hit repeatedly.
Symptoms
Twilio costs are spiraling, and usage is way above normal thresholds.
Cause
The /passwordless/start
endpoint is public. With an SPA application, for example, it’s possible to execute the following CURL:
curl --request POST \
--url 'https://{DOMAIN}/passwordless/start' \
--header 'content-type: application/json' \
--data '{"client_id":"50mJbvWHpTXJgDUTjI8Zrc3M6MC4JwLA", "connection":"sms", "phone_number":"+447756597123", "send":"code"}'
This will trigger an SMS to be sent to the end user. If a bad actor does this repeatedly, it could use up a customer’s SMS quota, increase costs, etc.
Solution
The following actions can assist with this issue:
- Switch on Captcha for Passwordless Flows (Security > Attack Protection > Bot Detection) to take care of the Universal Login side of things.
However, the above is not a complete solution because a bad actor could bypass the Universal Login and execute simple CURL requests (for example, for a SPA app) like the one below to trigger SMS messages to be sent out:
curl --request POST \
--url 'https://{DOMAIN}/passwordless/start' \
--header 'content-type: application/json' \
--data '{"client_id":"50mJbvWHpTXJgDUTjI8Zrc3M6MC4JwLA", "connection":"sms", "phone_number":"+447756597123", "send":"code"}'
- Review the tenant logs to determine the extent of the problem. Search for
"type:cs AND connection:sms"
or similar variations to locate the requests being made by the bad actor. Look for something in common with the requests. For example, perhaps all the bad requests come from a single IP.
Actions can come to the rescue here. The Post User Registration Action is triggered when the above CURL is executed. Create an Action like the one below to deny those requests (amend the script as needed). As soon as the Action is added to the flow, future CURL requests will be denied, and the denied requests will not touch the SMS provider (e.g., Twilio).
exports.onExecutePreUserRegistration = async (event, api) => {
let BLOCKED_IP = "82.30.100.208"
if (event.request.ip === BLOCKED_IP) {
api.access.deny("BAD_ACTOR_BLOCKED_IP",`Requests from this IP ${BLOCKED_IP} are blocked`)
};
};
Review the blocked login requests in the tenant logs as they start coming in.