We've noticed an issue with our HR system where users request leave, this approval is sent to their manager, and when their manager clicks the link to approve it they see an error saying that the leave is already approved... That seems to be because Outlook sends a GET request to the HR system's approval URI in order to check whether the link is maliscious; but in doing so it approves the employee's leave. Note: this GET request is sent even before the email is previewed / isn't triggered by any action of the recipient user.
The HR system is a third party, with poor support, so they've not been able to confirm our theory on what's going on... However, I've tested by sending a mail from an external email address which contains a link to a website that I support (but is not in Outlook's verified domains list). Looking at the logs on my server I see that moments after this test email arrives in my mail client (without me clicking the link or even previewing the email's content), sure enough a GET request shows up in my logs from an IP that belongs to MS (according to a whois on the IP).
That seems pretty damming... but then we work with other systems which have single click links (both for approvals, and also many emails which contain unsubscribe
links or verify my email
links that work with a single click / don't require manual follow-up) and we don't seem to have similar issues with those; and it feels unlikely that in all those cases the sites owners have blacklisted the MS IPs associated with SafeLinks (especially as if it were that simple to get around, a malicious actor could also use such a trick to dodge safelinks protection).
- Is our theory on SafeLinks causing the approvals likely to be correct?
- If so, is there some explanation of why it's not impacting more systems?
Despite there are several reasons why "Safe Links" causes more problems than it solves, this is also a design flaw & a vulnerability in the HR system:
Such action should require authentication i.e. the user with the required permissions for the action should be logged in. The "Safe Links" does not come from an authenticated session. It could fit into any of these categories:
CWE-306: Missing Authentication for Critical Function
CWE-862: Missing Authorization
CWE-749: Exposed Dangerous Method or Function
Even if the link has a random part, it is not strong enough, as the link could easily leak or be guessable.
CWE-1390: Weak Authentication
The system should not perform either approve nor decline actions based on a single
GET
request, but the linked page should, e.g., have buttons to confirm the desired action with a separatePOST
requests.This is even recommended in the HTTP Semantics, RFC 9110, 9.2.1:
Please report this vulnerability to the software vendor.
That's in violation of the relevant technical specification and best practice.
From RFC7231:
Get is a safe, idempotent request according to the same standard. What Outlook does is explicitely allowed according to the standard:
Your HR app is broken and should be updated.
After a bit more experimentation I think I've found the reason.
The URIs that don't have issues all included querystrings; the one we had an issue with didn't.
Sending a test to https://example.com/approve?try=this instead of seeing that exact URI returned in my site's logs I saw that the
GET
request was sent to https://example.com/approve?try=bcce; i.e. the value part of the name-value pair in the querystring was replaced with a garbage value. That said, this behaviour wasn't consistent across my tests / I'm not sure what rules MS follows on when to replace values and when to leave them.It seems others have hit this before; there's lots of good discussion on this thread.
From the client perspective, the best fix is to whitelist the URL so that SafeLinks doesn't scan it. More info in MS Docs.