我的骆驼应用程序正在接受安全审查。他们指出,骆驼电子邮件通过提供不良的恶意脚本作为附件而受到系统的恶意攻击,该附件可以在从电子邮件中删除附件后运行。
骆驼电子邮件如何清理附件?
这是他们在说什么。
这是一个安全问题,因为任何人都可以仅通过向受监视的邮箱发送电子邮件而以root身份在服务器上运行代码。我上一条消息中的脚本是我在自己的计算机上运行的,而不是服务器本身,它导致服务器上的/root/.bashrc重写-等同于任何“合法”用户运行后的命令执行bash作为root用户(因为它将自动执行/root/.bashrc中的命令)。如果您觉得更有意义,也可以使用它来更改服务器上的任何配置文件,以促进进一步的攻击,例如发送带有名为'../../../../ ..的附件的邮件。 /../../../app/PassThruMultiTenantMT1/latest/etc/users.properties'可以添加一个新的admin用户进行融合(然后登录并获得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;
}
}
答案 0 :(得分:1)
您可以使用Validation Component拒绝无效的文件名。
.validate(header(Exchange.FILE_NAME).regex("^[a-zA-Z 0-9_.@()-]+\\.[^.]+$"))
添加了09/7/18
camel-mail
中的文件名剥离已与CAMEL-12630添加在一起,并计划在2.20.4
,2.21.2
,2.22.1
和{{1 }}。如果您使用的是这些版本中的一个或更高版本,则无需再验证附件中的文件名。
答案 1 :(得分:-1)
嗯,我认为这很可能是对骆驼邮件的无意疏忽。为此。
但是,我确实认为这可能与骆驼本身有关,请参阅相关的RFC:https://tools.ietf.org/html/rfc2183
” 接收方MUA不应遵守任何目录路径信息 似乎出现在filename参数中。文件名 应该仅被视为终端组件。随身携带 将来可能会完成目录路径的指定 通过单独的Content-Disposition参数,但没有提供 在本草案中做了。 “
因此,事实getAttachments()甚至返回带有路径信息开头的文件名,这充其量似乎是有问题的。另外,他们关于如何在http://camel.apache.org/mail.html处使用邮件的示例还使用了易受攻击的代码作为用法示例,这进一步加剧了我的怀疑,即他们可能对此无意。