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

ImpactDetails
OtherScope: Other

Quality Degradation - Applications become non-portable and may fail in clustered or distributed deployment environments.
AvailabilityScope: 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

  1. MITRE Corporation. "CWE-576: EJB Bad Practices: Use of Java I/O." https://cwe.mitre.org/data/definitions/576.html
  2. Oracle. "Enterprise JavaBeans Specification."
  3. Jakarta EE. "Jakarta Enterprise Beans Best Practices."