Laufzeit-Ressourcenverwaltungs-Kontrollelement in einer Komponente für Application Server

Beschreibung

Laufzeit-Ressourcenverwaltungs-Kontrollelement in einer Komponente für Application Server tritt auf, wenn eine Anwendungskomponente, die für die Ausführung auf einem Application Server (J2EE, ASP.NET usw.) konzipiert ist, Laufzeitressourcen wie Threads, Sockets, Datenbankverbindungen oder Dateihandles direkt verwaltet. Application Server stellen verwaltete Ressourcenpools und Lifecycle-Management bereit, gerade weil nicht verwaltete Ressourcenerstellung zu Ressourcenerschöpfung, Thread-Sicherheitsproblemen und Konflikten mit der Ressourcenverwaltung des Containers führen kann. Komponenten sollten containerverwaltete Ressourcen durch JNDI-Lookups, Dependency Injection oder andere vom Container bereitgestellte Mechanismen verwenden.

Risiko

Die direkte Verwaltung von Ressourcen umgeht das Ressourcen-Pooling und Monitoring des Application Servers. Dies kann zur Erschöpfung des Verbindungspools führen, wenn Komponenten ihre eigenen Verbindungen erstellen. Thread-Erstellung außerhalb des Containers kann Thread-Lecks und Ressourcenerschöpfung verursachen. Sicherheitskontexte werden möglicherweise nicht ordnungsgemäß auf manuell erstellte Threads propagiert. Der Container kann keine Sicherheitsrichtlinien auf nicht verwaltete Ressourcen durchsetzen. Ressourcenlecks werden wahrscheinlich, da der Container Ressourcen nicht bereinigen kann, die er nicht verwaltet. Denial-of-Service wird einfacher, wenn Ressourcenlimits nicht durchgesetzt werden. Debug- und Monitoring-Tools verlieren die Sichtbarkeit auf nicht verwaltete Ressourcen.

Lösung

Verwenden Sie containerverwaltete Ressourcen durch Dependency Injection oder JNDI-Lookups. Für Datenbankverbindungen verwenden Sie DataSource aus JNDI anstatt direkte JDBC-Verbindungen zu erstellen. Verwenden Sie containerverwaltete Thread-Pools (ManagedExecutorService in Java EE) anstatt Threads direkt zu erstellen. Lassen Sie den Container Verbindungs-Pooling und Lifecycle verwalten. Verwenden Sie vom Container bereitgestellte APIs für asynchrone Operationen. Konfigurieren Sie Ressourcenpools im Application Server statt im Anwendungscode. Befolgen Sie die Best Practices des Application Servers für Ressourcenverwaltung. Verwenden Sie die @Resource-Annotation für die Injektion containerverwalteter Ressourcen.

Häufige Auswirkungen

AuswirkungDetails
VerfügbarkeitBereich: Verfügbarkeit

DoS: Ressourcenverbrauch - Nicht verwaltete Ressourcen können Systemressourcen erschöpfen und Denial-of-Service verursachen.
AndereBereich: Ändere

Reduzierte Zuverlässigkeit - Ressourcen, die nicht vom Container verwaltet werden, werden möglicherweise nicht ordnungsgemäß bereinigt.
ZugriffskontrolleBereich: Zugriffskontrolle

Schutzmechanismus umgehen - Sicherheitskontexte werden möglicherweise nicht ordnungsgemäß auf nicht verwaltete Threads propagiert.

Beispielcode

Anfälliger Code

// Anfällig: Threads direkt in J2EE-Komponente erstellen
@WebServlet("/process")
public class VulnerableProcessingServlet extends HttpServlet {

    // Anfällig: Thread-Pool direkt erstellen
    private ExecutorService executor = Executors.newFixedThreadPool(10);

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {

        String data = req.getParameter("data");

        // Anfällig: An nicht verwalteten Thread-Pool übergeben
        executor.submit(() -> {
            // Sicherheitskontext nicht propagiert!
            // Container kann diesen Thread nicht überwachen
            processData(data);
        });

        // Anfällig: Rohen Thread erstellen
        Thread worker = new Thread(() -> {
            // Dieser Thread ist für den Container unsichtbar
            // Keine Ressourcenlimits durchgesetzt
            heavyProcessing();
        });
        worker.start();  // Thread-Leck-Potenzial!

        resp.getWriter().write("Verarbeitung gestartet");
    }

    // Anfällig: Thread-Pool wird nie ordnungsgemäß heruntergefahren
}
// Anfällig: Direkte JDBC-Verbindungsverwaltung in EJB
@Stateless
public class VulnerableUserRepository {

    // Anfällig: Hardcodierte Verbindungsparameter
    private static final String JDBC_URL = "jdbc:mysql://localhost:3306/users";
    private static final String USERNAME = "app_user";
    private static final String PASSWORD = "secret123";

    public User findById(Long id) {
        Connection conn = null;
        try {
            // Anfällig: Verbindung direkt erstellen
            // Umgeht Verbindungspool des Containers!
            conn = DriverManager.getConnection(JDBC_URL, USERNAME, PASSWORD);

            PreparedStatement stmt = conn.prepareStatement(
                "SELECT * FROM users WHERE id = ?"
            );
            stmt.setLong(1, id);

            ResultSet rs = stmt.executeQuery();
            // ... Ergebnis verarbeiten

        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            // Wird möglicherweise nicht aufgerufen wenn Ausnahme anders auftritt
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    // Verbindungsleck!
                }
            }
        }
        return null;
    }
}
// Anfällig: Direkte Socket-Verwaltung in Web-Komponente
@WebServlet("/notify")
public class VulnerableNotificationServlet extends HttpServlet {

    // Anfällig: Sockets direkt verwalten
    private ServerSocket serverSocket;
    private List<Socket> clientSockets = new ArrayList<>();

    @Override
    public void init() throws ServletException {
        try {
            // Anfällig: Server-Socket in Servlet erstellen
            // Container kann diese Ressource nicht verwalten!
            serverSocket = new ServerSocket(9999);

            // Anfällig: Acceptor-Thread direkt starten
            new Thread(() -> {
                while (!serverSocket.isClosed()) {
                    try {
                        Socket client = serverSocket.accept();
                        clientSockets.add(client);  // Nicht thread-sicher!
                    } catch (IOException e) {
                        // Stilles Fehlschlagen
                    }
                }
            }).start();

        } catch (IOException e) {
            throw new ServletException("Socket-Start fehlgeschlagen", e);
        }
    }

    @Override
    public void destroy() {
        // Wird möglicherweise nicht aufgerufen, Ressourcen bleiben geleakt
        try {
            serverSocket.close();
            for (Socket s : clientSockets) {
                s.close();
            }
        } catch (IOException e) {
            // Ressourcen geleakt
        }
    }
}
// Anfällig: Direkte Ressourcenverwaltung in ASP.NET
public class VulnerableDataController : ApiController
{
    // Anfällig: SqlConnection direkt erstellen
    public async Task<IHttpActionResult> GetData(int id)
    {
        // Anfällig: Hardcodierter Connection-String
        string connStr = "Server=localhost;Database=mydb;User=sa;Password=secret;";

        // Anfällig: Verbindungs-Pooling nicht ordnungsgemäß verwenden
        using (var connection = new SqlConnection(connStr))
        {
            await connection.OpenAsync();

            // Anfällig: Hintergrund-Thread starten
            Task.Run(() => {
                // Kein HttpContext verfügbar
                // Kein Request-Cancellation-Token
                // Sicherheitskontext verloren
                LogAnalytics(id);
            });

            // ... Rest der Verarbeitung
        }

        return Ok();
    }

    // Anfällig: Hintergrund-Threads direkt erstellen
    private static Thread backgroundWorker;

    static VulnerableDataController()
    {
        // Anfällig: Statische Thread-Erstellung
        backgroundWorker = new Thread(BackgroundProcess);
        backgroundWorker.IsBackground = true;
        backgroundWorker.Start();  // Thread-Leck bei App-Domain-Entladung
    }
}

Korrigierter Code

// Korrigiert: Containerverwaltete Ressourcen in J2EE verwenden
@WebServlet("/process")
public class FixedProcessingServlet extends HttpServlet {

    // Korrigiert: Containerverwalteten Executor-Service injizieren
    @Resource
    private ManagedExecutorService executorService;

    // Korrigiert: Alternative - Lookup aus JNDI
    // @Resource(lookup = "java:comp/DefaultManagedExecutorService")
    // private ManagedExecutorService executorService;

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {

        String data = req.getParameter("data");

        // Korrigiert: Containerverwalteten Executor verwenden
        // Sicherheitskontext wird automatisch propagiert
        executorService.submit(() -> {
            // Container verwaltet Thread-Lifecycle
            // Sicherheitskontext verfügbar
            processData(data);
        });

        // Korrigiert: Für geplante Aufgaben ManagedScheduledExecutorService verwenden
        // @Resource
        // private ManagedScheduledExecutorService scheduledExecutor;

        resp.getWriter().write("Verarbeitung gestartet");
    }

    // Keine Bereinigung nötig - Container verwaltet Ressourcen
}
// Korrigiert: Containerverwaltete DataSource in EJB verwenden
@Stateless
public class FixedUserRepository {

    // Korrigiert: Containerverwaltete DataSource injizieren
    @Resource(lookup = "java:jboss/datasources/UsersDS")
    private DataSource dataSource;

    // Alternative: JPA mit containerverwaltetem EntityManager verwenden
    @PersistenceContext
    private EntityManager entityManager;

    public User findById(Long id) {
        // Korrigiert: JPA verwenden - sauberster Ansatz
        return entityManager.find(User.class, id);
    }

    public User findByIdJdbc(Long id) {
        // Korrigiert: Wenn JDBC benötigt, containerverwaltete DataSource verwenden
        try (Connection conn = dataSource.getConnection();
             PreparedStatement stmt = conn.prepareStatement(
                 "SELECT * FROM users WHERE id = ?")) {

            stmt.setLong(1, id);

            try (ResultSet rs = stmt.executeQuery()) {
                if (rs.next()) {
                    return mapUser(rs);
                }
            }

        } catch (SQLException e) {
            throw new EJBException("Datenbankfehler", e);
        }
        return null;
    }

    private User mapUser(ResultSet rs) throws SQLException {
        User user = new User();
        user.setId(rs.getLong("id"));
        user.setUsername(rs.getString("username"));
        return user;
    }
}
// Korrigiert: JMS für Messaging anstelle von rohen Sockets verwenden
@Stateless
public class FixedNotificationService {

    // Korrigiert: Containerverwaltete JMS-Ressourcen verwenden
    @Resource(lookup = "java:/jms/NotificationConnectionFactory")
    private ConnectionFactory connectionFactory;

    @Resource(lookup = "java:/jms/NotificationQueue")
    private Queue notificationQueue;

    // Korrigiert: Oder injizierten JMSContext verwenden (Java EE 7+)
    @Inject
    @JMSConnectionFactory("java:/jms/NotificationConnectionFactory")
    private JMSContext jmsContext;

    public void sendNotification(String message) {
        // Korrigiert: Container verwaltet JMS-Ressourcen
        jmsContext.createProducer()
            .send(notificationQueue, message);
    }
}

// Korrigiert: Message-Driven Bean für asynchrone Verarbeitung
@MessageDriven(activationConfig = {
    @ActivationConfigProperty(
        propertyName = "destinationType",
        propertyValue = "javax.jms.Queue"),
    @ActivationConfigProperty(
        propertyName = "destination",
        propertyValue = "java:/jms/NotificationQueue")
})
public class NotificationProcessor implements MessageListener {

    @Override
    public void onMessage(Message message) {
        // Container verwaltet Thread-Pool für MDBs
        // Sicherheitskontext ordnungsgemäß etabliert
        try {
            TextMessage textMessage = (TextMessage) message;
            processNotification(textMessage.getText());
        } catch (JMSException e) {
            throw new EJBException(e);
        }
    }
}
// Korrigiert: Dependency Injection in ASP.NET Core verwenden
public class FixedDataController : ControllerBase
{
    private readonly IDbConnection _connection;
    private readonly IBackgroundTaskQueue _taskQueue;
    private readonly ILogger<FixedDataController> _logger;

    // Korrigiert: Abhängigkeiten vom Container injiziert
    public FixedDataController(
        IDbConnection connection,
        IBackgroundTaskQueue taskQueue,
        ILogger<FixedDataController> logger)
    {
        _connection = connection;
        _taskQueue = taskQueue;
        _logger = logger;
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> GetData(int id, CancellationToken cancellationToken)
    {
        // Korrigiert: Injizierte Verbindung verwenden (aus Pool)
        var data = await _connection.QueryFirstOrDefaultAsync<DataModel>(
            "SELECT * FROM Data WHERE Id = @Id",
            new { Id = id }
        );

        // Korrigiert: Hintergrundarbeit ordnungsgemäß einreihen
        _taskQueue.QueueBackgroundWorkItem(async token =>
        {
            // CancellationToken propagiert
            await LogAnalyticsAsync(id, token);
        });

        return Ok(data);
    }
}

// Korrigiert: Services in Startup.cs konfigurieren
public void ConfigureServices(IServiceCollection services)
{
    // Korrigiert: Verbindungspool vom DI-Container verwaltet
    services.AddScoped<IDbConnection>(sp =>
    {
        var conn = new SqlConnection(Configuration.GetConnectionString("Default"));
        conn.Open();
        return conn;
    });

    // Korrigiert: Hintergrundaufgaben-Queue mit Hosted Service
    services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
    services.AddHostedService<QueuedHostedService>();
}

// Korrigiert: Hintergrund-Service vom Host verwaltet
public class QueuedHostedService : BackgroundService
{
    private readonly IBackgroundTaskQueue _taskQueue;
    private readonly ILogger<QueuedHostedService> _logger;

    public QueuedHostedService(
        IBackgroundTaskQueue taskQueue,
        ILogger<QueuedHostedService> logger)
    {
        _taskQueue = taskQueue;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            var workItem = await _taskQueue.DequeueAsync(stoppingToken);

            try
            {
                await workItem(stoppingToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Fehler bei Hintergrundarbeit");
            }
        }
    }
}

CVE-Beispiele

Diese CWE ist für direkte CVE-Zuordnung als VERBOTEN markiert, da sie ein Code-Qualitäts-/Design-Problem und keine direkte Sicherheitsschwachstelle darstellt.


Verwandte CWEs

  • CWE-710: Improper Adherence to Coding Standards (Eltern)
  • CWE-400: Uncontrolled Resource Consumption (kann führen zu)
  • CWE-404: Improper Resource Shutdown or Release (verwandt)
  • CWE-1058: Invokable Control Element in Multi-Thread Context (verwandt)

Referenzen

  1. MITRE Corporation. "CWE-1065: Runtime Resource Management Control Element in a Component Built to Run on Application Servers." https://cwe.mitre.org/data/definitions/1065.html

  2. Oracle. "Java EE Tutorial - Concurrency Utilities."

  3. Microsoft. "Background tasks with hosted services in ASP.NET Core."