Destination check: a mail line's smtp= argument and FCM's keyfile= name the address dialled, and neither is checked #10
Labels
No labels
bug
documentation
enhancement
security
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo-apprise#10
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found on 2026-09-29 in a read-only review of postulo-apprise at
7637d899d, comparing the one addressdestination_of()submits to the policy with the address each Apprise service opens, with Apprise 1.13.1 (the locked version) and every socket stubbed out.The plugin holds a line to the instance's destination policy by reading
server.host. For some Apprise services the host slot is not what is connected to: an argument elsewhere in the line names it. The policy is then asked about a name that is never contacted, and the address that is contacted is never looked at. This is server-side request forgery (CWE-918) past the operator'sPOSTULO_CONNECTIONS_ALLOW_PRIVATEsetting, and unlike the lookup gap the module docstring concedes it involves no timing: it is deterministic.src/postulo_apprise/notifier.py:102-120(destination_of:host = str(getattr(server, "host", "") or "")at:110is the only attribute read, withport);:74-99(names_a_server, which sorts by class, so a class with nohosttoken has no destination at all);:177-187(_problems, the only caller ofcheck_destination). In Apprise 1.13.1:plugins/email/base.py:190maps thesmtpargument tosmtp_host,:472-473falls back tohostonly whensmtp_hostis empty, and:680-691openssmtplib.SMTP(self.smtp_host, self.port, …);plugins/fcm/__init__.py:286-288turnskeyfileinto anAppriseAttachment, whichattachment/http.py:148fetches withrequests.get(or, for a path,plugins/fcm/oauth.py:117opens);plugins/xmpp/base.py:143-146mapsxmpptoxmpp_hostthe same way whenslixmppis installed.example.org.mailto://line whose domain isexample.org, with a port and ansmtp=argument naming a loopback address:validate()returns{}, having looked upexample.organd nothing else; the server object reportshost='example.org',smtp_host='127.0.0.1'; pressing Test makes Apprise opensmtplib.SMTP('127.0.0.1', 2525).fcm://line with akeyfile=argument holding anhttp://address on loopback: FCM declares nohosttoken, sodestination_ofreturnsNoneand nothing is checked at all; pressing Test makes Apprise issueGETto that address, with redirects followed.notifier.pyreadssmtp_host, a query argument, or any attribute other thanhostandport, andtests/test_apprise.py:96-119covers only the host slot.keyfile=, the server issues a request to any internal address, or opens a local path, whenever a notification is sent. The threat model's promise that connected plugins reach the network only under the operator's destination policy does not hold for these lines.smtp_hostand its port in place ofhost.smtp,xmpp,keyfile), and refuse akeyfilethat is a local path.hostis not an address at all (cloud ntfy, the API mail providers).Related to #1 (closed), which set out to check every destination and fixed hosts without a dot; addresses carried outside the host slot were not part of it.
Done when
tests/test_apprise.py, withPOSTULO_CONNECTIONS_ALLOW_PRIVATEoff, showsAppriseNotifier().validate()refusing a mail line whose domain is public and whosesmtp=argument is a private address, and accepting the same line without the argument.tests/test_apprise.pyshows an FCM line with akeyfile=address or path refused (or the address checked), andtest()andsend()not reaching Apprise for either line.