apache camel 电子邮件如何防止可以攻击系统的恶意附件

apache camle email how to prevent a malicious attachment that can attack system

我的 camel 应用程序正在接受安全审查。他们指出,camel 电子邮件在系统上受到恶意攻击,因为它提供了一个糟糕的恶意脚本作为附件,在从电子邮件中删除附件后可能 运行。

camel 电子邮件如何清理附件?

这是他们在说什么。

这是一个安全问题,因为任何人都可以 运行 只需向受监控的邮箱发送电子邮件,即可作为 root 在服务器上进行编码。我之前消息中的脚本是我 运行 -- 在我自己的计算机上 -- 而不是在服务器本身上的东西,它导致在服务器上重写 /root/.bashrc - 这等于命令执行一次任何 "legitimate" 用户 运行s bash 作为 root(因为这将自动执行 /root/.bashrc 中的命令)。如果它对您更有意义,这也可以用于更改服务器上的任何配置文件以促进进一步的攻击,例如发送带有名为“../../../../..”的附件的邮件。 /../../../app/PassThruMultiTenantMT1/latest/etc/users.properties' 可以添加一个新的admin用户来fuse(然后用它登录并获得root权限)。

您不需要在路由创建过程中注入值,路径遍历组件是"camelFileName"(从电子邮件附件的文件名中提取,从调用返回至 "getAttachments()").

我不太想知道它是否是一个安全问题(它是^^),更多的是它是你的代码中的问题还是 Apache Camel 的代码中的问题。这是我希望你能发表意见的事情。

这是我的电子邮件代码

public class EndpointEmailRouteBuilder extends RouteBuilder {

    private static final Logger LOG = LoggerFactory.getLogger(EndpointEmailRouteBuilder.class);

    // The route identifier. This identifier is used for monitoring.
    private String routeId;

    // The configuration of the endpoint.
    private String protocol;
    private String host;
    private String port;

    // The account to use.
    private String username;
    private String password;

    // Options when connecting
    private String initialDelay;
    private String pollDelay;
    private String mailDelete;
    private String pop3Headers;
    private String maxMessagesPerPoll;
    private String fetchSize;

    // Output destinations
    private String toEndpoint;
    private String emailArchive;
    private String attachmentArchive;
    private String errorArchive;
    private String destination;
    private String predixDestination;

    //inbound encryption and compression parameters
    private String isCompressedOnly;
    private String isEncryptedOnly;
    private String isEncryptedWithCompression;
    private String pgpKeyUserId;
    private String pgpPassword;
    private String pgpArmored;

    public EndpointEmailRouteBuilder(String routeId,
                EndpointEmailDescriptor endpointDescriptor,
                String emailArchive,
                String attachmentArchive,
                String errorArchive,
                String destination,
                String predixDestination) {

        this.routeId = routeId;

        this.protocol = endpointDescriptor.getProtocol();
        this.host = endpointDescriptor.getHost();
        this.port = endpointDescriptor.getPort();

        this.username = endpointDescriptor.getUserName();
        this.password = endpointDescriptor.getPassword();

        this.initialDelay = endpointDescriptor.getInitialDelay();
        this.pollDelay = endpointDescriptor.getPollDelay();
        this.mailDelete = endpointDescriptor.getMailDelete();
        this.pop3Headers = endpointDescriptor.getPop3Headers();
        this.maxMessagesPerPoll = endpointDescriptor.getMaxMessagesPerPoll();
        this.fetchSize = endpointDescriptor.getFetchSize();

        this.emailArchive = emailArchive;
        this.attachmentArchive = attachmentArchive;
        this.errorArchive = errorArchive;
        this.destination = destination;
        this.predixDestination = predixDestination;
        // Encryption designators
        this.isCompressedOnly = endpointDescriptor.getIsCompressedOnly();
        this.isEncryptedOnly = endpointDescriptor.getIsEncryptedOnly();
        this.isEncryptedWithCompression = endpointDescriptor.getIsEncryptedWithCompression();
        this.pgpKeyUserId = endpointDescriptor.getPgpKeyUserId();
        this.pgpPassword = endpointDescriptor.getPgpPassword();
        this.pgpArmored = endpointDescriptor.getPgpArmored();

    }

    @Override
    public void configure() throws Exception {

        if (validateConfiguration()) {

            // Format the From Endpoint from Parameters.
            final String fromStr = String.format("%s://%s:%s?username=%s&password=%s"
                            + "&delete=%s&mail.pop3.forgettopheaders=%s"
                            + "&consumer.delay=%s" 
                            + "&consumer.initialDelay=%s"
                            + "&maxMessagesPerPoll=%s" 
                            + "&fetchSize=%s"
                            + "&mapMailMessage=false" 
                            + "&handleFailedMessage=true"
                            + "&skipFailedMessage=true",
                    protocol, host, port, username, password, mailDelete,
                    pop3Headers, pollDelay, initialDelay, maxMessagesPerPoll, fetchSize);

            final String toStr = String.format("%s", destination);          

            final String toStrPredix = String.format("%s", pDestination);   

            onException(com.ab.dig.passthru.inboundemail.exception.EmailProcessorException.class).to("file:" + errorArchive);
            onException(java.lang.Exception.class).to("file:" + errorArchive);

            //
            if (Boolean.parseBoolean(isEncryptedWithCompression)) { 
                //Compression and Encryption
                PGPDataFormat pgpVerifyAndDecrypt = new PGPDataFormat();
                pgpVerifyAndDecrypt.setKeyFileName("keys/secring.gpg");
                pgpVerifyAndDecrypt.setKeyUserid(pgpKeyUserId);
                pgpVerifyAndDecrypt.setPassword(pgpPassword);
                pgpVerifyAndDecrypt.setArmored(Boolean.parseBoolean(pgpArmored));
                pgpVerifyAndDecrypt.setSignatureKeyFileName("keys/pubring.gpg");
                pgpVerifyAndDecrypt.setSignatureKeyUserid(pgpKeyUserId);
                pgpVerifyAndDecrypt.setSignatureVerificationOption(PGPKeyAccessDataFormat.SIGNATURE_VERIFICATION_OPTION_IGNORE);

                from(fromStr).routeId(routeId)
                .log(LoggingLevel.INFO, "Message received from " + username + "@" + host)
                .setHeader("emailArchive", simple(emailArchive, String.class))
                .process(new EmailProcessor()).split().body()
                .setHeader("CamelFileName").ognl("request.body.fileName")
                .setBody().ognl("request.body.fileContents")
                .unmarshal(pgpVerifyAndDecrypt).split(new ZipSplitter())
                .streaming().convertBodyTo(String.class)
                .wireTap("file:" + attachmentArchive)
                .to(toStr)
                .to("file:" + toStrPredix);

            } else if (Boolean.parseBoolean(isEncryptedOnly)) { 
                //Encryption Only
                PGPDataFormat pgpVerifyAndDecrypt = new PGPDataFormat();
                pgpVerifyAndDecrypt.setKeyFileName("keys/secring.gpg");
                pgpVerifyAndDecrypt.setKeyUserid(pgpKeyUserId);
                pgpVerifyAndDecrypt.setPassword(pgpPassword);
                pgpVerifyAndDecrypt.setArmored(Boolean.parseBoolean(pgpArmored));
                pgpVerifyAndDecrypt.setSignatureKeyFileName("keys/pubring.gpg");
                pgpVerifyAndDecrypt.setSignatureKeyUserid(pgpKeyUserId);
                pgpVerifyAndDecrypt.setSignatureVerificationOption(PGPKeyAccessDataFormat.SIGNATURE_VERIFICATION_OPTION_IGNORE);

                from(fromStr).routeId(routeId)
                .log(LoggingLevel.INFO, "Message received from " + username + "@" + host)
                .setHeader("emailArchive", simple(emailArchive, String.class))
                .process(new EmailProcessor()).split().body()
                .setHeader("CamelFileName").ognl("request.body.fileName")
                .setBody().ognl("request.body.fileContents")
                .unmarshal(pgpVerifyAndDecrypt)
                .convertBodyTo(String.class)
                .wireTap("file:" + attachmentArchive)
                .to(toStr)
                .to("file:" + toStrPredix);

            } else if (Boolean.parseBoolean(isCompressedOnly)) { 
                //Only Zipped or Compressed
                ZipFileDataFormat zipFile = new ZipFileDataFormat();
                zipFile.setUsingIterator(true);

                from(fromStr).routeId(routeId)
                .log(LoggingLevel.INFO, "Message received from " + username + "@" + host)
                .setHeader("emailArchive", simple(emailArchive, String.class))
                .process(new EmailProcessor()).split().body()
                .setHeader("CamelFileName").ognl("request.body.fileName")
                .setBody().ognl("request.body.fileContents")
                .unmarshal(zipFile).split(body(Iterator.class))
                .streaming().convertBodyTo(String.class)
                .wireTap("file:" + attachmentArchive)
                .to(toStr)
                .to("file:" + toStrPredix);

            } else {
                //No Compression No Encryption Basic plain data file
                from(fromStr).routeId(routeId)
                .log(LoggingLevel.INFO, "Message received from " + username + "@" + host)
                .setHeader("emailArchive", simple(emailArchive, String.class))
                .process(new EmailProcessor()).split().body()
                .setHeader("CamelFileName").ognl("request.body.fileName")
                .setBody().ognl("request.body.fileContents")
                .wireTap("file:" + attachmentArchive)
                .to(toStr)
                .to("file:" + toStrPredix);
            }
        } else {
            LOG.error("Unable to create route.  Invalid Configuration");
        }
    }

    private boolean validateConfiguration() {

        boolean returnValue = true;

        if (null == username) {
            returnValue = false;
        } else {
            if (username.isEmpty()) {
                returnValue = false;
            }
        }

        if (null == password) {
            returnValue = false;
        } else {
            if (password.isEmpty()) {
                returnValue = false;
            }
        }

        return returnValue;

    }

}

public class EmailProcessor implements Processor {

    private static final Logger LOG = Logger.getLogger(EmailProcessor.class);

    private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-hh-mm-ss");

    public void process(Exchange exchange) throws Exception {

        LOG.info("Entering EmailProcessor...");

        Map<String, DataHandler> attachments = exchange.getIn().getAttachments();

        if ((null == attachments) || (attachments.size() < 1)) {
            throw new EmailProcessorException("Null or 0 attachements");
        } else {
            LOG.info("attachments.size = " + attachments.size());
        }

        Map<String, String> emailAttr = gatherEmailHeaderInformation(exchange);

        List<String> attachmentFilenames = new ArrayList<String>();

        try {

            List<FilenameAndContents> attachmentArray = new ArrayList<FilenameAndContents>();

            for (String name : attachments.keySet()) {

                DataHandler dh = attachments.get(name);
                String filename = dh.getName();

                //String contents = exchange.getContext().getTypeConverter().convertTo(String.class, dh.getInputStream());
                byte[] contents = exchange.getContext().getTypeConverter().convertTo(byte[].class, dh.getInputStream());
                LOG.info("Attachment file name: " + filename);
                attachmentFilenames.add(filename);

                FilenameAndContents attachmentFile = new FilenameAndContents();
                attachmentFile.setFileContents(contents);
                attachmentFile.setFileName(filename);
                attachmentArray.add(attachmentFile);

            }

            exchange.getIn().setBody(attachmentArray);

        } catch (org.apache.camel.TypeConversionException tce) {
            throw new EmailProcessorException(
                    "Unable to type convert from file to string", tce);
        } catch (java.io.IOException ioe) {
            throw new EmailProcessorException(
                    "IOException while obtaining Input Stream", ioe);
        } catch (java.lang.UnsupportedOperationException uoe) {
            throw new EmailProcessorException(
                    "UnsupportedOperationException add operation is not supported by list",
                    uoe);
        } catch (java.lang.ClassCastException cce) {
            throw new EmailProcessorException(
                    "ClassCastException element prevents it from being added to list",
                    cce);
        } catch (java.lang.NullPointerException npe) {
            throw new EmailProcessorException(
                    "NullPointerException element is null", npe);
        } catch (java.lang.IllegalArgumentException iae) {
            throw new EmailProcessorException(
                    "IllegalArgumentException property of element prevents it from being added to list",
                    iae);
        }

        archiveEmail(emailAttr, attachmentFilenames, exchange);

        LOG.info("Exiting EmailProcessor.");
    }

    private Map<String, String> gatherEmailHeaderInformation(Exchange exchange) {

        final String emailBody = exchange.getIn().getBody(String.class);

        final Message mailMessage = exchange.getIn().getBody(javax.mail.Message.class);

        Map<String, String> attr = new HashMap<String, String>();

        try {
            if (null != mailMessage) {

                final Address[] fromArray = mailMessage.getFrom();
                if (null != fromArray) {
                    String fromStr = convertAddressListToString(fromArray);
                    attr.put("from", fromStr);
                }

                final Address[] toArray = mailMessage
                        .getRecipients(javax.mail.Message.RecipientType.TO);
                if (null != toArray) {
                    String toStr = convertAddressListToString(fromArray);
                    attr.put("to", toStr);
                }

                final Address[] ccArray = mailMessage
                        .getRecipients(javax.mail.Message.RecipientType.CC);
                if (null != ccArray) {
                    String ccStr = convertAddressListToString(fromArray);
                    attr.put("CC", ccStr);
                }

                final Address[] bccArray = mailMessage
                        .getRecipients(javax.mail.Message.RecipientType.BCC);
                if (null != bccArray) {
                    String bccStr = convertAddressListToString(fromArray);
                    attr.put("BCC", bccStr);
                }

                final String subjectStr = mailMessage.getSubject();
                if (null != subjectStr) {
                    attr.put("subject", subjectStr);
                }

                final Date sentDate = mailMessage.getReceivedDate();
                if (null != sentDate) {
                    attr.put("sentDate", sdf.format(sentDate));
                }

                final Date receivedDate = mailMessage.getSentDate();
                if (null != receivedDate) {
                    attr.put("receivedDate", sdf.format(receivedDate));
                }

                if (null != emailBody) {
                    attr.put("body", emailBody);
                }

            }
        } catch (javax.mail.MessagingException me) {
            LOG.error("Unable to gather email header information");
        }

        return attr;

    }

    private void archiveEmail(Map<String, String> attr,
            List<String> attachmentFilenames, Exchange exchange) {

        final String archivePath = exchange.getIn().getHeader("emailArchive", String.class);

        Path parentP = Paths.get(archivePath);
        Path fileP;
        if (null != attr.get("receivedDate")) {
            fileP = Paths.get(archivePath, exchange.getExchangeId() + "." + attr.get("receivedDate"));
        } else {
            fileP = Paths.get(archivePath, exchange.getExchangeId());
        }

        try {
            Files.createDirectories(parentP);
        } catch (IOException ioe) {
            LOG.error("Unable to create email archive directories");
            ioe.printStackTrace();
        }

        Charset charset = Charset.forName("utf-8");

        try (BufferedWriter bufferedWriter = Files.newBufferedWriter(fileP,
                charset)) {

            if (null != attr.get("from")) {
                bufferedWriter.write("From:" + attr.get("from"));
                bufferedWriter.newLine();
            }

            if (null != attr.get("to")) {
                bufferedWriter.write("To:" + attr.get("to"));
                bufferedWriter.newLine();
            }

            if (null != attr.get("CC")) {
                bufferedWriter.write("CC:" + attr.get("CC"));
                bufferedWriter.newLine();
            }

            if (null != attr.get("BCC")) {
                bufferedWriter.write("BCC" + attr.get("BCC"));
                bufferedWriter.newLine();
            }

            if (null != attr.get("subject")) {
                bufferedWriter.write("Subject:" + attr.get("subject"));
                bufferedWriter.newLine();
            }

            if (null != attr.get("sentDate")) {
                bufferedWriter.write("Sent Date:" + attr.get("sentDate"));
                bufferedWriter.newLine();
            }

            if (null != attr.get("receivedDate")) {
                bufferedWriter.write("Received Date:"
                        + attr.get("receivedDate"));
                bufferedWriter.newLine();
            }

            if (null != attr.get("body")) {
                bufferedWriter.write("Body:" + attr.get("body"));
                bufferedWriter.newLine();
            }

            for (String s : attachmentFilenames) {
                bufferedWriter.write("Attachment File:" + s);
                bufferedWriter.newLine();
            }

        } catch (IOException ioe) {
            LOG.error("Unable to write email archive");
            ioe.printStackTrace();
        }

    }

    private String convertAddressListToString(Address[] adds) {

        String returnStr = "";

        for (Address adr : adds) {
            returnStr = returnStr + adr.toString() + " ";
        }

        return returnStr;

    }
}

public class EndpointEmailProcessor implements Processor {

    private static final Logger LOG = LoggerFactory.getLogger(EndpointEmailProcessor.class);

    // The configuration of the endpoint.
    private String protocol;
    private String host;
    private String port;

    // Options when connecting
    private String initialDelay;
    private String pollDelay;
    private String mailDelete;
    private String pop3Headers;
    private String maxMessagesPerPoll;
    private String fetchSize;

    // Output locations
    private String destinationEndpoint;
    private String destination;
    private String predixDestination;

    @Override
    public void process(Exchange exchange) throws Exception {

        LOG.info("Entering EndpointEmailProcessor...");

        final String endpointConfigurationStr = exchange.getIn().getBody(String.class);
        LOG.info("endpointConfigurationStr: " + endpointConfigurationStr);

        final String fileName = (String) exchange.getIn().getHeader("CamelFileName");
        LOG.info("filename: " + fileName);

        if ((null != endpointConfigurationStr) && (null != fileName)) {

            Properties props = new Properties();
            props.load(new StringReader(endpointConfigurationStr));

            if (validateProperties(props)) {

                final String fileNameNoExtension = fileName.substring(0, fileName.lastIndexOf('.'));
                LOG.info("fileNameNoExtension: " + fileNameNoExtension);

                final String routeIdStr = String.format("passthru.inboundEmail.%s.%sRoute", fileNameNoExtension, protocol);
                LOG.info("routeIDStr: " + routeIdStr);

                if (props.getProperty("action").equalsIgnoreCase("activate")) {

                    ServiceStatus routeStatus = exchange.getContext().getRouteStatus(routeIdStr);

                    if ((null == routeStatus) || (routeStatus.isStopped())) {

                        exchange.getContext().addRoutes(new EndpointEmailRouteBuilder(routeIdStr, 
                                EndpointDescriptorFactory(props),
                                props.getProperty("emailArchive"),
                                props.getProperty("attachmentArchive"),
                                props.getProperty("errorArchive"),
                                props.getProperty("destination"),
                                props.getProperty("predixDestination")));

                    } else {
                        LOG.info("Route " + routeIdStr + " already started");
                    }

                } else if (props.getProperty("action").equalsIgnoreCase("deactivate")) {

                    ServiceStatus routeStatus = exchange.getContext().getRouteStatus(routeIdStr);
                    if (routeStatus.isStarted()) {
                        exchange.getContext().stopRoute(routeIdStr);
                    } else {
                        LOG.info("Route " + routeIdStr + " already stopped");
                    }

                } else {
                    LOG.error("Invalid Action in Email Properties");
                }
            }
        } else {
            LOG.error("Email Configuration File or File Name is null");
        }
        LOG.info("Exiting EndpointEmailProcessor.");

    }

    private EndpointEmailDescriptor EndpointDescriptorFactory(Properties p) {

        EndpointEmailDescriptor endpointDescriptor = new EndpointEmailDescriptor();

        endpointDescriptor.setProtocol(this.protocol);
        endpointDescriptor.setHost(this.host);
        endpointDescriptor.setPort(this.port);
        endpointDescriptor.setUserName(p.getProperty("username"));
        endpointDescriptor.setPassword(p.getProperty("password"));
        endpointDescriptor.setInitialDelay(this.initialDelay);
        endpointDescriptor.setPollDelay(this.pollDelay);
        endpointDescriptor.setMailDelete(this.mailDelete);
        endpointDescriptor.setPop3Headers(this.pop3Headers);
        endpointDescriptor.setMaxMessagesPerPoll(this.maxMessagesPerPoll);
        endpointDescriptor.setFetchSize(this.fetchSize);
        // Encryption designators
        endpointDescriptor.setIsCompressedOnly(p.getProperty("isCompressedOnly"));
        endpointDescriptor.setIsEncryptedOnly(p.getProperty("isEncryptedOnly"));
        endpointDescriptor.setIsEncryptedWithCompression(p.getProperty("isEncryptedWithCompression"));
        endpointDescriptor.setPgpKeyUserId(p.getProperty("pgpKeyUserId"));
        endpointDescriptor.setPgpPassword(p.getProperty("pgpPassword"));
        endpointDescriptor.setPgpArmored(p.getProperty("pgpArmored"));

        return endpointDescriptor;

    }

    private boolean validateProperties(Properties p) {

        boolean returnValue = true;

        if (p.containsKey("username")) {
            if (p.getProperty("username").isEmpty()) {
                returnValue = false;
            }
        } else {
            returnValue = false;
        }

        if (p.containsKey("password")) {
            if (p.getProperty("password").isEmpty()) {
                returnValue = false;
            }
        } else {
            returnValue = false;
        }

        if (p.containsKey("emailArchive")) {
            if (p.getProperty("emailArchive").isEmpty()) {
                returnValue = false;
            }
        } else {
            returnValue = false;
        }

        if (p.containsKey("attachmentArchive")) {
            if (p.getProperty("attachmentArchive").isEmpty()) {
                returnValue = false;
            }
        } else {
            returnValue = false;
        }

        if (p.containsKey("errorArchive")) {
            if (p.getProperty("errorArchive").isEmpty()) {
                returnValue = false;
            }
        } else {
            returnValue = false;
        }

        if (p.containsKey("destination")) {
            if (p.getProperty("destination").isEmpty()) {
                returnValue = false;
            }
        } else {
            returnValue = false;
        }

        if (p.containsKey("predixDestination")) {
            if (p.getProperty("predixDestination").isEmpty()) {
                returnValue = false;
            }
        } else {
            returnValue = false;
        }
        // Encryption designators
        if (p.containsKey("isCompressedOnly")) {
            if (p.getProperty("isCompressedOnly").isEmpty()) {
                returnValue = false;
            }
        } else {
            returnValue = false;
        }

        if (p.containsKey("isEncryptedOnly")) {
            if (p.getProperty("isEncryptedOnly").isEmpty()) {
                returnValue = false;
            }
        } else {
            returnValue = false;
        }

        if (p.containsKey("isEncryptedWithCompression")) {
            if (p.getProperty("isEncryptedWithCompression").isEmpty()) {
                returnValue = false;
            }
        } else {
            returnValue = false;
        }

        if (p.containsKey("pgpKeyUserId")) {
            if (p.getProperty("pgpKeyUserId").isEmpty()) {
                returnValue = false;
            }
        } else {
            returnValue = false;
        }

        if (p.containsKey("pgpPassword")) {
            if (p.getProperty("pgpPassword").isEmpty()) {
                returnValue = false;
            }
        } else {
            returnValue = false;
        }

        if (p.containsKey("pgpArmored")) {
            if (p.getProperty("pgpArmored").isEmpty()) {
                returnValue = false;
            }
        } else {
            returnValue = false;
        }

        return returnValue;
    }

    public void setProtocol(String protocol) {
        this.protocol = protocol;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public void setPort(String port) {
        this.port = port;
    }

    public void setInitialDelay(String initialDelay) {
        this.initialDelay = initialDelay;
    }

    public void setPollDelay(String pollDelay) {
        this.pollDelay = pollDelay;
    }

    public void setMailDelete(String mailDelete) {
        this.mailDelete = mailDelete;
    }

    public void setPop3Headers(String pop3Headers) {
        this.pop3Headers = pop3Headers;
    }

    public void setMaxMessagesPerPoll(String maxMessagesPerPoll) {
        this.maxMessagesPerPoll = maxMessagesPerPoll;
    }

    public void setFetchSize(String fetchSize) {
        this.fetchSize = fetchSize;
    }

    public void setDestinationEndpoint(String destinationEndpoint) {
        this.destinationEndpoint = destinationEndpoint;
    }

}

您可以使用 Validation Component 来拒绝无效的文件名。

.validate(header(Exchange.FILE_NAME).regex("^[a-zA-Z 0-9_.@()-]+\.[^.]+$"))

Test regexp


已添加 09/Jul/18

camel-mail 中删除文件名是在 CAMEL-12630 中添加的,计划在版本 2.20.42.21.22.22.12.23.0。如果您使用的是这些版本之一或更高版本,则无需再验证附件中的文件名。

嗯,我认为这很可能是对骆驼邮件的无意疏忽。据此

但是我确实认为这可能与 Camel 本身有关,查看相关的 RFC:https://www.rfc-editor.org/rfc/rfc2183

” 接收 MUA 不应该尊重任何目录路径信息 这似乎存在于文件名参数中。文件名 应仅将其视为终端组件。便携的 将来可能会指定目录路径 通过单独的 Content-Disposition 参数,但没有规定 在本草案中为此做出的。 “

所以 getAttachments() 甚至 returns 文件名以路径信息开头的事实充其量似乎是有问题的。他们在 http://camel.apache.org/mail.html 上如何使用邮件的示例也有易受攻击的代码作为使用示例,这进一步加强了我的怀疑,即这可能是他们无意的。