EJB Bad Practices: Use of Java I/O
Description
EJB Bad Practices: Use of Java I/O is a vulnerability where an Enterprise JavaBean violates the EJB specification by utilizing the java.io package to access files and directories on the local filesystem. The EJB specification mandates that beans "must not use the java.io package to attempt to access files and directories in the file system." This requirement exists because EJB containers may distribute beans across multiple servers where file systems are not shared, making file-based operations unreliable and non-portable. The specification recommends using resource manager APIs like JDBC instead.
Risk
Using java.io in EJBs creates significant deployment and reliability risks. File operations fail when beans are deployed to different servers in a cluster where file paths don't exist or point to different content. Configuration files accessible on one server may be missing on another. Applications become non-portable across environments with different file system layouts. Security boundaries enforced by the EJB container can be bypassed through direct file access. Additionally, file operations may conflict with container resource management, causing resource leaks or contention. In clustered deployments, lack of file synchronization leads to inconsistent application state.
Solution
Do not use java.io for file system access in EJBs. Instead, use container-managed resources and appropriate APIs: JDBC for structured data storage, JNDI for configuration and resource lookup, JPA for object persistence, JMS for asynchronous data exchange, or distributed caching solutions for shared state. For configuration, use environment entries, system properties, or external configuration services. If file access is absolutely required, delegate to a separate service outside the EJB container, use a shared network file system with appropriate synchronization, or use cloud storage APIs that work consistently across nodes.
Common Consequences
| Impact | Details |
|---|---|
| Other | Scope: Other Quality Degradation - Applications become non-portable and may fail in clustered or distributed deployment environments. |
| Availability | Scope: Availability DoS: Crash, Exit, or Restart - File operations may fail unexpectedly when expected files are not present on all cluster nodes. |
Example Code
Vulnerable Code
// Vulnerable: Reading configuration from filesystem
import javax.ejb.Stateless;
import java.io.*;
import java.util.Properties;
@Stateless
public class VulnerableConfigBean implements ConfigService {
// Vulnerable: Using java.io to read config file
public Properties loadConfiguration() {
Properties props = new Properties();
// Vulnerable: File path may not exist on all servers
try (FileInputStream fis = new FileInputStream("/config/app.properties")) {
props.load(fis);
} catch (IOException e) {
// May fail on different server in cluster
throw new RuntimeException("Config not found", e);
}
return props;
}
// Vulnerable: Writing to local filesystem
public void saveConfiguration(Properties props) {
// Vulnerable: Changes only affect this server
try (FileOutputStream fos = new FileOutputStream("/config/app.properties")) {
props.store(fos, "Updated configuration");
} catch (IOException e) {
throw new RuntimeException("Cannot save config", e);
}
// Other servers in cluster won't see this change!
}
}
// Vulnerable: File-based data storage in EJB
@Stateless
public class VulnerableDataBean implements DataService {
private static final String DATA_DIR = "/data/records/";
// Vulnerable: Storing data in files
public void saveRecord(String id, String content) {
File file = new File(DATA_DIR + id + ".dat");
// Vulnerable: File operations in EJB
try (FileWriter writer = new FileWriter(file)) {
writer.write(content);
} catch (IOException e) {
throw new RuntimeException("Cannot save record", e);
}
}
// Vulnerable: Reading data from files
public String getRecord(String id) {
File file = new File(DATA_DIR + id + ".dat");
// File might not exist on this server
if (!file.exists()) {
return null; // Inconsistent across cluster
}
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
StringBuilder content = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
content.append(line);
}
return content.toString();
} catch (IOException e) {
throw new RuntimeException("Cannot read record", e);
}
}
}
// Vulnerable: File-based logging in EJB
@Stateless
public class VulnerableLoggingBean implements LoggingService {
// Vulnerable: Direct file output
public void logEvent(String event) {
try (PrintWriter writer = new PrintWriter(
new FileWriter("/logs/app.log", true))) {
writer.println(new Date() + ": " + event);
} catch (IOException e) {
// Logging fails silently
}
// Logs scattered across cluster nodes
}
}
// Vulnerable: Reading XML configuration in constructor
@Stateless
public class VulnerableXmlBean implements XmlService {
private Document configDoc;
@PostConstruct
public void init() {
// Vulnerable: File access in EJB initialization
try {
File xmlFile = new File("/config/settings.xml");
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
configDoc = db.parse(xmlFile); // May fail on some servers
} catch (Exception e) {
throw new RuntimeException("Cannot load XML config", e);
}
}
}
// Vulnerable: Temporary file usage
@Stateless
public class VulnerableTempFileBean implements ProcessingService {
// Vulnerable: Creating temp files
public byte[] processLargeData(byte[] input) {
File tempFile = null;
try {
// Vulnerable: Temp file location varies by server
tempFile = File.createTempFile("process", ".tmp");
// Write to temp file
try (FileOutputStream fos = new FileOutputStream(tempFile)) {
fos.write(input);
}
// Process...
return processFile(tempFile);
} catch (IOException e) {
throw new RuntimeException("Processing failed", e);
} finally {
if (tempFile != null) {
tempFile.delete(); // May leave files on failure
}
}
}
}
Fixed Code
// Fixed: Use JNDI and environment entries for configuration
import javax.ejb.Stateless;
import javax.annotation.Resource;
import javax.naming.InitialContext;
@Stateless
public class SecureConfigBean implements ConfigService {
// Fixed: Use container-managed environment entries
@Resource(name = "appConfig/maxConnections")
private Integer maxConnections;
@Resource(name = "appConfig/timeout")
private Integer timeout;
@Resource(name = "appConfig/serverUrl")
private String serverUrl;
// Fixed: Configuration from container environment
public ConfigDTO getConfiguration() {
ConfigDTO config = new ConfigDTO();
config.setMaxConnections(maxConnections);
config.setTimeout(timeout);
config.setServerUrl(serverUrl);
return config;
}
// Fixed: Or use JNDI lookup
public String getConfigValue(String key) throws NamingException {
InitialContext ctx = new InitialContext();
return (String) ctx.lookup("java:comp/env/config/" + key);
}
}
// Fixed: Use JPA for data persistence
import javax.ejb.Stateless;
import javax.persistence.*;
@Stateless
public class SecureDataBean implements DataService {
@PersistenceContext
private EntityManager em;
// Fixed: Store data in database via JPA
public void saveRecord(RecordDTO record) {
RecordEntity entity = new RecordEntity();
entity.setId(record.getId());
entity.setContent(record.getContent());
entity.setCreatedDate(new Date());
em.persist(entity);
// Data accessible from any server in cluster
}
// Fixed: Retrieve data from database
public RecordDTO getRecord(String id) {
RecordEntity entity = em.find(RecordEntity.class, id);
if (entity == null) {
return null;
}
RecordDTO dto = new RecordDTO();
dto.setId(entity.getId());
dto.setContent(entity.getContent());
return dto;
}
}
// Fixed: Use standard logging frameworks
import javax.ejb.Stateless;
import java.util.logging.Logger;
@Stateless
public class SecureLoggingBean implements LoggingService {
// Fixed: Use Java logging or SLF4J
private static final Logger logger =
Logger.getLogger(SecureLoggingBean.class.getName());
public void logEvent(String event) {
// Fixed: Container manages log destination
logger.info("Event: " + event);
// Logging configuration handled at container level
}
}
// Fixed: Use JNDI for XML configuration or database storage
@Stateless
public class SecureXmlBean implements XmlService {
@Resource(name = "configXml")
private String configXmlContent; // Stored in JNDI
@PersistenceContext
private EntityManager em;
// Fixed: Load XML from database or JNDI
public Document getConfiguration() throws Exception {
// Option 1: From JNDI resource
if (configXmlContent != null) {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
return db.parse(new InputSource(new StringReader(configXmlContent)));
}
// Option 2: From database
ConfigEntity config = em.find(ConfigEntity.class, "settings");
if (config != null) {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
return db.parse(new InputSource(
new StringReader(config.getXmlContent())));
}
throw new RuntimeException("Configuration not found");
}
}
// Fixed: Use in-memory processing or database for large data
@Stateless
public class SecureProcessingBean implements ProcessingService {
@PersistenceContext
private EntityManager em;
// Fixed: Process in memory or use database for staging
public byte[] processLargeData(byte[] input) {
// For moderate sizes, process in memory
if (input.length < 10_000_000) { // 10MB
return processInMemory(input);
}
// For large data, use database BLOB
return processViaDatabase(input);
}
private byte[] processInMemory(byte[] input) {
ByteArrayOutputStream output = new ByteArrayOutputStream();
// Process without temp files
process(new ByteArrayInputStream(input), output);
return output.toByteArray();
}
private byte[] processViaDatabase(byte[] input) {
// Store in database for processing
ProcessingJob job = new ProcessingJob();
job.setInputData(input);
job.setStatus(JobStatus.PENDING);
em.persist(job);
em.flush();
// Process (could be async)
byte[] result = processLargeInput(job.getInputData());
job.setOutputData(result);
job.setStatus(JobStatus.COMPLETED);
return result;
}
}
// Fixed: Use cloud storage for file needs
import software.amazon.awssdk.services.s3.S3Client;
@Stateless
public class SecureStorageBean implements StorageService {
@Inject
private S3Client s3Client; // Container-managed
private static final String BUCKET = "app-storage";
// Fixed: Use cloud storage accessible from all nodes
public void storeFile(String key, byte[] content) {
s3Client.putObject(
PutObjectRequest.builder()
.bucket(BUCKET)
.key(key)
.build(),
RequestBody.fromBytes(content)
);
// Accessible from any server
}
public byte[] retrieveFile(String key) {
ResponseBytes<GetObjectResponse> response = s3Client.getObjectAsBytes(
GetObjectRequest.builder()
.bucket(BUCKET)
.key(key)
.build()
);
return response.asByteArray();
}
}
CVE Examples
No specific CVEs are commonly attributed to this CWE, as it primarily affects application portability and reliability rather than direct security vulnerabilities.
References
- MITRE Corporation. "CWE-576: EJB Bad Practices: Use of Java I/O." https://cwe.mitre.org/data/definitions/576.html
- Oracle. "Enterprise JavaBeans Specification."
- Jakarta EE. "Jakarta Enterprise Beans Best Practices."