EJB Bad Practices: Verwendung von Sockets

Beschreibung

EJB Bad Practices: Verwendung von Sockets ist eine Schwachstelle, bei der ein Enterprise JavaBean die EJB-Spezifikation verletzt, indem es Socket-Operationen für Netzwerkkommunikation verwendet. Die EJB-Spezifikation verbietet Beans ausdrücklich, auf Sockets zu lauschen, Verbindungen auf einem Socket zu akzeptieren oder Sockets für Multicast zu verwenden. Wie in der Spezifikation angegeben: "Eine Enterprise Bean darf nicht versuchen, auf einem Socket zu lauschen, Verbindungen auf einem Socket zu akzeptieren oder einen Socket für Multicast zu verwenden." Diese Einschränkung existiert, weil die EJB-Architektur dafür konzipiert wurde, dass Beans nur als Netzwerk-Clients fungieren, während der Container alle eingehende Kommunikation verwaltet.

Risiko

Die Verwendung von Sockets in EJBs erzeugt erhebliche architektonische und operationelle Risiken. Server-Sockets, die auf Verbindungen lauschen, stehen im Konflikt mit der Verantwortung des Containers für die Verwaltung von Netzwerkendpunkten und können potenziell Port-Konflikte und Sicherheitslücken erzeugen. Socket-Operationen umgehen das Sicherheits- und Transaktionsmanagement des Containers und lassen Kommunikation ungeschützt. Der Container kann den Bean-Lebenszyklus nicht ordnungsgemäß verwalten, wenn Sockets Verbindungen offen halten. In geclusterten Umgebungen brechen Socket-Listener auf bestimmten Knoten das verteilte Deployment-Modell. Zusätzlich kann direkte Socket-Verwendung Ressourcenlecks erzeugen, die sich über die Zeit ansammeln und die Server-Stabilität beeinträchtigen.

Lösung

Verwenden Sie keine Socket-Operationen in EJB-Code. Nutzen Sie stattdessen Container-verwaltete Kommunikationsmechanismen: Webservices (JAX-WS, JAX-RS) zum Exponieren von Diensten, JMS für asynchrone Nachrichtenübermittlung, RMI/IIOP wie vom Container konfiguriert, oder vom Application-Server bereitgestellte APIs für spezifische Protokolle. Wenn Socket-basierte Kommunikation erforderlich ist, implementieren Sie sie in einem separaten Dienst außerhalb des EJB-Containers oder verwenden Sie eine dedizierte Messaging-Infrastruktur. Für clientseitige Kommunikationsbedürfnisse erwägen Sie die Verwendung von HTTP-Clients oder JCA (Java Connector Architecture) Ressourcenadaptern, die ordnungsgemäß mit dem EJB-Container integriert sind.

Häufige Auswirkungen

AuswirkungDetails
SonstigeBereich: Sonstige

Qualitätsverschlechterung - Anwendungen, die die EJB-Spezifikation verletzen, verlieren Portabilität und können sich inkonsistent über verschiedene Container verhalten.
VerfügbarkeitBereich: Verfügbarkeit

DoS: Ressourcenverbrauch - Socket-Ressourcen werden möglicherweise nicht ordnungsgemäß freigegeben, was zu Port-Erschöpfung und Verbindungslecks führt.

Beispielcode

Verwundbarer Code

// Verwundbar: EJB erstellt ServerSocket
import javax.ejb.Stateless;
import java.net.*;
import java.io.*;

@Stateless
public class VulnerableSocketBean implements SocketService {

    private ServerSocket serverSocket;

    // Verwundbar: Server-Socket in EJB erstellen
    @PostConstruct
    public void startServer() {
        try {
            // Verletzt EJB-Spezifikation - Beans dürfen nicht auf Sockets lauschen
            serverSocket = new ServerSocket(9999);

            // Lauschen in Hintergrund-Thread starten (verletzt ebenfalls Spezifikation)
            new Thread(() -> acceptConnections()).start();
        } catch (IOException e) {
            throw new RuntimeException("Kann Server nicht starten", e);
        }
    }

    // Verwundbar: Verbindungen in EJB akzeptieren
    private void acceptConnections() {
        while (true) {
            try {
                Socket client = serverSocket.accept();  // Verletzt Spezifikation!
                handleClient(client);
            } catch (IOException e) {
                // Verbindungsverarbeitung fehlgeschlagen
            }
        }
    }

    private void handleClient(Socket client) {
        try (BufferedReader in = new BufferedReader(
                new InputStreamReader(client.getInputStream()));
             PrintWriter out = new PrintWriter(
                client.getOutputStream(), true)) {

            String request = in.readLine();
            String response = processRequest(request);
            out.println(response);

        } catch (IOException e) {
            // Fehlerbehandlung
        }
    }
}

// Verwundbar: EJB verwendet Multicast-Sockets
@Stateless
public class VulnerableMulticastBean implements MulticastService {

    private MulticastSocket multicastSocket;
    private InetAddress group;

    // Verwundbar: Multicast in EJB verwenden
    public void joinMulticastGroup() {
        try {
            // Verletzt EJB-Spezifikation
            multicastSocket = new MulticastSocket(4446);
            group = InetAddress.getByName("230.0.0.1");
            multicastSocket.joinGroup(group);  // In EJB nicht erlaubt!

        } catch (IOException e) {
            throw new RuntimeException("Kann Multicast nicht beitreten", e);
        }
    }

    public void sendMulticast(String message) {
        try {
            byte[] buf = message.getBytes();
            DatagramPacket packet = new DatagramPacket(
                buf, buf.length, group, 4446);
            multicastSocket.send(packet);  // Verletzt Spezifikation!
        } catch (IOException e) {
            throw new RuntimeException("Senden fehlgeschlagen", e);
        }
    }
}

// Verwundbar: EJB als Thread mit Socket
@Stateless
public class VulnerableThreadSocketBean extends Thread implements SocketService {

    private DatagramSocket datagramSocket;

    // Verwundbar: Als Thread laufen und Sockets verwenden
    @Override
    public void run() {
        try {
            datagramSocket = new DatagramSocket(8888);  // Verletzt Spezifikation!

            byte[] buffer = new byte[1024];
            while (true) {
                DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
                datagramSocket.receive(packet);  // Lauschen - verletzt Spezifikation!
                processPacket(packet);
            }
        } catch (IOException e) {
            // Fehler
        }
    }

    public void startListening() {
        this.start();  // Verletzt ebenfalls EJB-Spezifikation (Thread-Management)
    }
}

// Verwundbar: Direkte Client-Socket-Verwendung in Stateless-Bean
@Stateless
public class VulnerableClientSocketBean implements ClientService {

    // Während Client-Sockets technisch erlaubt sind, ist direkte
    // Verwaltung von Socket-Verbindungen problematisch
    public String fetchData(String host, int port) {
        Socket socket = null;
        try {
            socket = new Socket(host, port);  // Raw-Socket-Verwendung
            socket.setSoTimeout(5000);

            BufferedReader in = new BufferedReader(
                new InputStreamReader(socket.getInputStream()));
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);

            out.println("GET /data");
            return in.readLine();

        } catch (IOException e) {
            throw new RuntimeException("Verbindung fehlgeschlagen", e);
        } finally {
            // Manuelle Ressourcenverwaltung - fehleranfällig
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) { }
            }
        }
    }
}

Lösungscode

// Behoben: Webservices statt Raw-Sockets verwenden
import javax.ejb.Stateless;
import javax.jws.WebService;
import javax.jws.WebMethod;

@Stateless
@WebService
public class SecureWebServiceBean implements DataService {

    // Behoben: Dienst über JAX-WS exponieren
    @WebMethod
    public String processRequest(String request) {
        // Geschäftslogik - Container behandelt Netzwerk
        return doProcessing(request);
    }

    @WebMethod
    public DataDTO getData(String id) {
        // Container verwaltet alle Socket-Operationen
        return fetchData(id);
    }
}

// Behoben: JAX-RS für REST-Services verwenden
import javax.ejb.Stateless;
import javax.ws.rs.*;
import javax.ws.rs.core.*;

@Stateless
@Path("/data")
public class SecureRestServiceBean {

    // Behoben: RESTful-Endpunkt - Container verwaltet HTTP
    @GET
    @Path("/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getData(@PathParam("id") String id) {
        DataDTO data = fetchData(id);
        return Response.ok(data).build();
    }

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Response processData(RequestDTO request) {
        ResponseDTO response = process(request);
        return Response.ok(response).build();
    }
}

// Behoben: JMS für asynchrone Nachrichtenübermittlung verwenden
import javax.ejb.*;
import javax.jms.*;
import javax.annotation.Resource;

@Stateless
public class SecureMessagingBean implements MessagingService {

    @Resource(mappedName = "java:/ConnectionFactory")
    private ConnectionFactory connectionFactory;

    @Resource(mappedName = "java:/jms/queue/DataQueue")
    private Queue dataQueue;

    // Behoben: JMS statt Multicast-Sockets verwenden
    public void broadcastMessage(String message) {
        try (JMSContext context = connectionFactory.createContext()) {
            // Container verwaltet alle Netzwerkkommunikation
            context.createProducer().send(dataQueue, message);
        }
    }
}

// Behoben: Message-Driven Bean zum Empfangen von Nachrichten
@MessageDriven(activationConfig = {
    @ActivationConfigProperty(
        propertyName = "destinationType",
        propertyValue = "javax.jms.Queue"),
    @ActivationConfigProperty(
        propertyName = "destination",
        propertyValue = "java:/jms/queue/DataQueue")
})
public class SecureMessageReceiverBean implements MessageListener {

    // Behoben: Container verwaltet Nachrichtenempfang
    @Override
    public void onMessage(Message message) {
        try {
            if (message instanceof TextMessage) {
                String content = ((TextMessage) message).getText();
                processMessage(content);
            }
        } catch (JMSException e) {
            // Fehler behandeln
        }
    }

    private void processMessage(String content) {
        // Geschäftslogik
    }
}

// Behoben: HTTP-Client für ausgehende Kommunikation verwenden
import javax.ejb.Stateless;
import javax.ws.rs.client.*;
import javax.ws.rs.core.*;

@Stateless
public class SecureClientBean implements ClientService {

    // Behoben: JAX-RS-Client statt Raw-Sockets verwenden
    public String fetchData(String serviceUrl) {
        Client client = ClientBuilder.newClient();
        try {
            WebTarget target = client.target(serviceUrl);
            Response response = target.request(MediaType.APPLICATION_JSON)
                .get();

            if (response.getStatus() == 200) {
                return response.readEntity(String.class);
            } else {
                throw new RuntimeException("Service-Aufruf fehlgeschlagen: " +
                    response.getStatus());
            }
        } finally {
            client.close();
        }
    }

    // Behoben: Injizierten Client verwenden
    @Inject
    private Client httpClient;

    public DataDTO getRemoteData(String endpoint) {
        return httpClient.target(endpoint)
            .request(MediaType.APPLICATION_JSON)
            .get(DataDTO.class);
    }
}

// Behoben: JCA-Connector für benutzerdefinierte Protokolle verwenden
import javax.resource.cci.*;

@Stateless
public class SecureConnectorBean implements ConnectorService {

    @Resource(mappedName = "java:/eis/CustomProtocolConnector")
    private ConnectionFactory connectorFactory;

    // Behoben: JCA-Ressourcenadapter für benutzerdefiniertes Protokoll verwenden
    public String sendCustomProtocol(String data) {
        Connection conn = null;
        try {
            conn = connectorFactory.getConnection();
            Interaction interaction = conn.createInteraction();

            // Container verwaltet Verbindungs-Pooling und Lebenszyklus
            Record input = createInputRecord(data);
            Record output = interaction.execute(null, input);

            return extractResult(output);

        } catch (ResourceException e) {
            throw new RuntimeException("Connector-Fehler", e);
        } finally {
            if (conn != null) {
                try {
                    conn.close();
                } catch (ResourceException e) { }
            }
        }
    }
}

// Behoben: Singleton mit Timer für Polling (statt Socket-Listener)
@Singleton
public class SecurePollingBean {

    @Inject
    private ClientService clientService;

    // Behoben: Timer statt Socket-Listener verwenden
    @Schedule(hour = "*", minute = "*/5", persistent = false)
    public void pollForUpdates() {
        // Externen Service periodisch abfragen
        String updates = clientService.fetchData("https://api.example.com/updates");
        if (updates != null) {
            processUpdates(updates);
        }
    }
}

CVE-Beispiele

Keine spezifischen CVEs werden dieser CWE üblicherweise zugeordnet, da sie primär die Anwendungsarchitektur und Portabilität betrifft statt direkter Sicherheitsschwachstellen.


Referenzen

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