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
| Auswirkung | Details |
|---|---|
| Sonstige | Bereich: Sonstige Qualitätsverschlechterung - Anwendungen, die die EJB-Spezifikation verletzen, verlieren Portabilität und können sich inkonsistent über verschiedene Container verhalten. |
| Verfügbarkeit | Bereich: 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
- MITRE Corporation. "CWE-577: EJB Bad Practices: Use of Sockets." https://cwe.mitre.org/data/definitions/577.html
- Oracle. "Enterprise JavaBeans Specification."
- Jakarta EE. "Jakarta Enterprise Beans Specification."