-
-
Save Tugzrida/61235545dfc122262c69b0ab50265582 to your computer and use it in GitHub Desktop.
// This worker is designed to be able to neatly handle MTA-STS policies for multiple domains. | |
// Make a new worker with this script and add your domains to the stsPolicies dict like the example. | |
// Add a DNS AAAA record for mta-sts.yourdomain.com pointing to 100:: and set to proxied, | |
// then add a workers route for mta-sts.yourdomain.com/* pointing to this worker. | |
// You should probably also create a Cloudflare configuration rule disabling Browser Integrity Check for the mta-sts subdomain | |
// to ensure MTAs aren't blocked from retrieving your policy. | |
// You'll still need to manually add the appropriate _mta-sts.yourdomain.com TXT record to enable the policy, | |
// and the _smtp._tls.yourdomain.com TXT record for reporting. | |
const stsPolicies = { | |
"yourdomain.com": | |
`version: STSv1 | |
mode: enforce | |
mx: mail.yourdomain.com | |
max_age: 86400` | |
} | |
const respHeaders = { | |
"Content-Type": "text/plain;charset=UTF-8", | |
"X-Clacks-Overhead": "GNU Terry Pratchett, Jon Postel, Alan Turing, Dan Kaminsky" | |
} | |
addEventListener("fetch", event => { | |
event.respondWith(handleRequest(event.request)) | |
}) | |
async function handleRequest(request) { | |
const reqUrl = new URL(request.url) | |
if (!reqUrl.hostname.startsWith("mta-sts.")) { | |
return new Response(`Incorrect worker route. mta-sts policies must be served on the mta-sts subdomain\n`, {status: 500, headers: respHeaders}) | |
} | |
const policyHost = reqUrl.hostname.slice(8) | |
if (!stsPolicies.hasOwnProperty(policyHost)) { | |
return new Response(`${policyHost} is not defined in the mta-sts worker\n`, {status: 500, headers: respHeaders}) | |
} | |
if (reqUrl.protocol !== "https:" || reqUrl.pathname !== "/.well-known/mta-sts.txt") { | |
reqUrl.protocol = "https:" | |
reqUrl.pathname = "/.well-known/mta-sts.txt" | |
return Response.redirect(reqUrl, 301) | |
} | |
return new Response(stsPolicies[policyHost] + "\n", {status: 200, headers: respHeaders}) | |
} |
I would imagine it's most likely Cloudflare's Browser Integrity Check, which from memory is enabled by default and blocks requests from some User-Agents. The main one I run into is Python urllib's default UA, but it's possible CF has some MTA UAs in their naughty list. I'll add a note to the gist
With all due respect, Browser Integrity Check is not solely responsible for issuing a challenge. BIC is a mechanism in which Cloudflare fingerprints a browser to determine whether it's a legitimate browser or a headless device.
BIC is loosely based Google's Picasso - not to be confused with Picasa - Google's photo editor.
BIC is indeed taken into consideration when it comes to determining if a device is suspect - but there's a lot more that needs to happen before Cloudflare would take any action (automated). I can all but guarantee this is manually invoked by a WAF Custom Rule...especially if this is a Free/Pro/Biz customer. I would suspect something like Bot Management could be a culprit - but BM is a very expensive product and anyone employing it would know to create exclusions for automated checks (i.e. APIs - or automated checks like quering mta-sta.mydomain.com for a mail policy).
If you call out anything in your Gist - I would suggest adding something like this:
Ensure your Cloudflare security policy does not invoke a challenge for any requests to the URL associated with the Worker binding.
But it might be good to get a response back from @Ry3nlNaToR first to see if they can provide more insight into what actually happened.
The Cloudflare docs are quite clear that this is exactly the behaviour of BIC:
Cloudflare’s Browser Integrity Check (BIC) looks for common HTTP headers abused most commonly by spammers and denies access to your page.
It also challenges visitors without a user agent or with a non-standard user agent such as commonly used by abusive bots, crawlers, or visitors.
I can confirm that on one of my domains where I use this worker, on which I don't have any non-default rules configured, simply setting the User-Agent header to Python-urllib/3.11
is enough to get BIC to block the request.
Okie dokie - I will step away from the conversation... It's your post, anyway. Thanks so much for sharing the information in the first place! It was IMMENSELY helpful!
@Tugzrida @W4JEW Its not my CF account I host my policy on AWS using CloudFront and S3, anyway back the subject I asked my friend for more details wasn't a managed challenge it was a block as suspected by @Tugzrida it was due to BIC Service: Browser integrity check Action taken:Block the user agent was libwww-perl/6.68 disabling Browser Integrity Check would fix it or just do what you did WAF Custom Rule and skip all of the security features.
@Ry3nlNaToR could you be more specific about what happened here? I'm not sure what you mean by "workers (spelling?) CF protection is still enabled".
This sounds like something self-imposed and likely due to a misconfiguration in Cloudflare's WAF for the zone the MTA-STS Worker is associated with.
There are no inherent policies imposed on Workers that would force an Interactive/Managed/JavaScript challenge. The only way this would occur would be for the administrator to have a layer 7 firewall rule (WAF Custom Rule) matching on the URL (Worker binding) that invokes a challenge. That would be something that the administrator would have to add - and it would be very bad practice to have it enabled on the specific binding for the MTA-STS Worker.
I have a WAF Custom Rule that matches on the MTA-STS hostname and is set to skip as follows:
If hostname = mta-sts.mydomain.com
Action = Skip
The following are skipped: All remaining custom rules, all rate limiting rules, all managed rules, all Super Bot Fight Mode Rules, Zone Lockdown, Browser Integrity Check, Hotlink Protection, and Security Level.
Basically - anything can access the MTA-STS Worker and will never get presented with a challenge of any sort.
If you can get more information on what happened, that would be very helpful. And apologies to @Tugzrida for this! Hopefully it's telling you how helpful your contribution has been! :-)