Enforce the pipe capability in Exim, not just in the web UI#300
Conversation
Whether a delivery is piped is currently decided solely by whether the stored smtp field begins with "|"; the routers never check on_piped or the domain's pipe flag. on_avscan and on_spamassassin are already re-checked against the domain in the routers, so pipe is the odd one out. Add a virtual_piped router that only pipes when the user is on_piped and the domain has pipe enabled, and set forbid_pipe on virtual_domains so a stray "|command" smtp value can't be executed there. Piped users keep working; for everyone else a pipe can no longer be reached. The catchall and group routers also set a pipe transport, but their data comes from a validated forward address / the group member list rather than the raw smtp field, so they are left unchanged.
|
Thanks! This is a bigger change than I expected though. I hope you don't mind if I keep this PR open for now. |
|
I could look into this and test it on a live system. Please give me some days. |
| local_part_suffix_optional | ||
| .endif | ||
| retry_use_local_part | ||
| pipe_transport = address_pipe |
There was a problem hiding this comment.
I noticed you haven't added file_transport and reply_transport here. Do non-piped emails get ignored by this router?
There was a problem hiding this comment.
The lookup in virtual_piped only returns something for on_piped users (on a pipe-enabled domain) and that is handled by the address_pipe transport. For everyone else it matches nothing, so the router simply declines and the message falls through to virtual_domains as usual.
That is also why there's no file_transport/reply_transport, this router only ever hands off a |command, never a mailbox file or a reply. The one thing it would not cover is a misconfigured piped account when smtp is not actually a pipe, which would just defer. But I could add a file_transport or an on_piped condition if you prefer.
There was a problem hiding this comment.
I guess I was wondering about a misconfigured account, yeah. Will such case not fall through to the next router? If not, perhaps a maybe it would make sense to check that the smtp field has the | character as well?
|
This brings me back to an old PR of myself which never got merged: #247 I did already split out the pipe router. But did not introduce checking the on_pipe field and use a So it would not be easy to verify your PR on my server. Please let me know what you think of my approach. and my PR which originally was about introducing LMTP transport. |
|
btw. the router on my live server working since 2017: And yes, I have a few pipe transports running. Eg for redmine. I just added to my where clause to be more conform to your (and my own) suggestion: |
|
I just checked. The users table has the type field (it was not introduced only on my server):
IMHO the We could remove the |
|
Extending |
|
I'm not sure if it is necessary to do forwards and other deliveries for a 'piped'-user. You could use an 'alias'- or normal user account and do a normal delivery + forwarding to a 'piped'-user. But I think the forward router is/should be placed before the pipe router and so a forward should be possible for 'piped' users. The forward router has no transport defined and will not end/prevent the processing of further routers. A delivery to an account will afaik not take place after processing the pipe route because the pipe router is before the delivery router and has a transport defined. I'm not 100% sure if Im right but this doc should fit. See section 10: After some thinking about it... We should not make things more complicated then necessary. A user account should deliver to a mailbox and a pipe account should deliver to a command. Forwarding to additional addresses (accounts of any type) could be done in both cases. |
|
I think, if you are going to keep pipes in play with vexim, you need to start thinking about the security side of it? You are opening a users script up via a service that does zero security checks on what it's piping to. Exim should drop privileges down to the vexim user when piping to it but if you allow users to run arbitrary scripts via Exim pipes, you're effectively giving them the keys to the /var/vmail kingdom. One compromised user account, one disgruntled employee, or one attacker who discovers a command injection vulnerability could destroy the entire email infrastructure in seconds The following script, for example, would run under the vexim user (drastic example I know): You can argue that it's only authorised scripts that you have uploaded but what is to stop someone from uploading a script to their web dir and if you have a ui from there changing the script? This one needs real consideration into the security implications.. One possible way is to run pipes under a totally different account, one with as few privileges as possible: Should work, it's been a long time since I have gone anywhere near pipes and email. |
|
These are very good points. I never used pipes myself, but this got me thinking about potential disasters too. I wouldn't mind dropping this feature from Vexim at all. IMO, virtual users have no business running arbitrary commands on their mail host. Feel free to correct me though if you know of a legitimate use example which wouldn't otherwise be possible. If we keep piping, we must always check if it is enabled for the domain before using it. But also:
|
|
You are right about the concerns on using pipes to a command and I'm very careful about when using this feature (maybe others do not care that much). On my mail servers are no users beyond me and a second person whom I trust completely. I have one piping user at the moment. This pipes to a script for injecting mails into Redmine. Several years ago I had a similar use case for a ticket-system (RT request tracker). Both systems bring a script for the MTA to pipe mails into the software per web-api which runs on a different VM or server. I did check those scripts before using it. I remember @Udera (#150 (comment)) was using pipes for delivering to dovecot-lda. But this can be solved by using LMTP. Not sure about Courier, Cyrus and others. |
|
Honestly, I don't know what is the best way to use pipes. The risk is smaller if it's just your script or you are the only user on your mail server but bugs and exploits come out every day.. What is "safe" today could be a very different story tomorrow (just look at some of the recent kernel CVEs). I mentioned a few comments up about pipes running under a different user. I guess you could really lock that account down and run it that way? Exim does allow some sanitising of things like addresses:
But then you have the body of the email to contend with and sanitise. Or I suppose you could have a wrapper script. You appear to know more python than me, but the re module in python could be used to strip out unwanted characters and I'm sure there's some data sanitising modules. Having the data passed to it via stdin would minimise the issue of potentially executing a command when passing data to the script (same way rt-mailgate runs if I remember correctly). Another alternative I was messing around with earlier today was running the command in a jail under a different user: Basically, vexim-pipe calls nsjail, which runs under the nobody user. But it's relying on yet another piece of software written by someone else and I couldn't work out how to get it to play with things like Mariadb. The theory is the same for podman/docker At the end of the day I guess it depends on what you use your email for. If you are a company and this is your main mta that handles your business (critical) emails, or a hosting provider with hundreds/thousands of customers then pipes are, in my opinion, just not worth the risk or hassle. If you are running a little server for you, your other half and your pets then that's your call. But it's 2026, people are running code through ai to find bugs they can exploit. Our honeypot server which runs an up to date version of WordPress is constantly getting crypto miners on it, 0-day exploits are "popular" these days.. Which is great, because those ips instantly end up in our firewall. That little honeypot is handy for catching spambots too. Aaand I've gone off topic.. |
|
Your comment is very helpful and we are thinking in the same direction. Just for the record: Pydantic could be used in Python for sanitising input. Probably splitting out this pipe features to another MTA would make the first MTA more save. I have to think more about this could be done. But this would only need a forwarding account on the main MTA. An IMAP-fetching script on the side where the receiving software is running could be a solution too. I'll give this approach a try in the next days. A small IMAP-client in python should be not to difficult. It could fetch mails every minute. I'm don't know if the IMAP push feature is reliable. If there is a good alternative approach I'd vote to remove the 'piped' users from Vexim. |
|
My personal opinion (from experience) is pipes should be removed, they are a massive security weakness and for VExim they certainly should be taken away from "normal" users to setup (the web ui I have just released removes them completely). If the system admin wants to run pipes then that's their choice? Having a secondary MTA just kind of pushes the issue to another server and lets be honest, how many people installing VExim are going to have a secondary MTA available? IMAP polling is what we use for our support stuff, polls the IMAP server, checks against the cache and if there's new messages it pulls them down and inserts them into a database. From what I remember the script is relatively basic.. |
|
ok... redmine brings a configuration option to fetch emails fom IMAP . https://www.redmine.org/projects/redmine/wiki/RedmineReceivingEmails Otherwise there exists So I'm fine with removing the 'piping' users from Vexim completely. I'd do an entry in the Vexim Wiki to mention the removal and the hint to use fetchmail on the receiving system instead. |
|
Added section to the Wiki: https://github.com/vexim/vexim2/wiki/Security#piping-to-commands feel free to enhance it. |
|
FYI: I did remove the pipe user from veximpy completely. |
|
I'm still trying to work out a secure way of having them enabled.. There has to be a way! |
|
IMO, one secure way to have pipes would be to define specific pipes targeting specific scenarios in Exim config itself (or included snippets). If you wanted to use Vexim for managing such mailboxes, you'd have to select a specific mailbox type instead of typing in the whole pipe command as free text. Arbitrary pipes would then never be possible to use. I'm not sure if this is possible or feasible though. |
|
That's not actually not a bad way of doing it.. Put something like this in the transports section of the config. Add a transport_name column to the users table, name of the transport that was added (secure_pipe_route_one) gets put in there. Then add a router that looks something like: Could still keep the type = pipe and and that to the sql? |
|
Why not just treat each pipe transport as its own user type? |
|
I think you are missing something in the where of the SQL: In any case the pipe router should be separated from the virtual_delivery router. |
|
It was just an example.. "Could still keep the type = pipe" |
How do you mean? |
How would you manage all these user types in the DB? At the moment the type field is an enum. Maybe in an extra table? |
|
Exim brings a procmail router/transport. Would it be possible to use it with vexim? Does it make sense to use this instead? |
Well, we have two options:
|
I would say update the enum when it's needed, string opens up the potential for typos (coming from a man who makes plenty of them). |
Whether a delivery is piped is currently decided solely by whether the stored smtp field begins with "|"; the routers never check on_piped or the domain's pipe flag. on_avscan and on_spamassassin are already re-checked against the domain in the routers, so pipe is the odd one out.
Add a virtual_piped router that only pipes when the user is on_piped and the domain has pipe enabled, and set forbid_pipe on virtual_domains so a stray "|command" smtp value can't be executed there. Piped users keep working; for everyone else a pipe can no longer be reached.
The catchall and group routers also set a pipe transport, but their data comes from a validated forward address / the group member list rather than the raw smtp field, so they are left unchanged.
I haven't been able to test this against a live Exim setup, so please sanity-check the new router before relying on it.