Apache Webserver: Fehler in "mod_proxy_ajp" ermöglicht Denial of Service

Das Apache-Modul “mod_proxy_ajp” enthält eine Sicherheitslücke, die beim Einsatz mit Load-Balancing via “mod_proxy_balancer” oder “mod_cluster” dazu führt, dass ein entfernter Angreifer Denial-of-Service-Attacken durchführen kann, um Web-Dienste unerreichbar zu machen.

Das Modul”mod_proxy_ajp” kommt typischerweise in Szenarien zum Einsatz, in denen ein Proxy-Server direkt von einem Web-Client kontaktiert wird und die Client-Anfrage über das Apache-JServ-Protokoll (AJP) an Application-Server im Backend weiterleitet. Beim Application-Server handelt es sich meist um Java-basierte Lösungen wie JBoss, Apache Tomcat oder IBM Websphere.

Die Kommunikation mit dem Backend könnte prinzipiell über HTTP erfolgen, was aus Performance-Gründen jedoch nicht gemacht wird, weil die ASCII-basierte HTTP-Kommunikation zu viel Overhead erzeugt. AJP hingegegen ist ein paketorientiertes Binärformat, das von “mod_proxy_ajp” als AJP13 unterstützt wird. Die Kommunikation mit dem Application-Server erfolgt über eine persistente TCP-Verbindung, die für mehrere Anfragen zur Verwendung kommt und nicht immer wieder auf- und abgebaut wird. Anfragen an den Backend-Server werden in folgendes AJP-Binärformat verpackt:

JP13_FORWARD_REQUEST := prefix_code (byte) 0x02 = JK_AJP13_FORWARD_REQUEST method (byte) protocol (string) req_uri (string) remote_addr (string) remote_host (string) server_name (string) server_port (integer) is_ssl (boolean) num_headers (integer) request_headers *(req_header_name req_header_value) attributes *(attribut_name attribute_value) request_terminator (byte) OxFF

Der “method”-Eintrag kodiert dabei die verschiedenen HTTP-Methoden wie HEAD, GET und POST, verbraucht hierfür im Gegensatz zur textbasierten HTTP-Implementierung aber nur ein einziges Byte.

Das Modul “mod_proxy_ajp” ist Teil der “mod_proxy”-Familie, die den Apache Weberver um Proxy- und Gateway-Funktionalität erweitert, beispielsweise für FTP, CONNECT und HTTP/1.1.

Größere Produktionssysteme betreiben fast immer mehrere Backend-Server, um die Arbeitslast durch viele Clients besser verteilen zu können. Auch hierfür bietet Apache ein Modul namens “mod_proxy_balancer”, welches derzeit drei verschiedene Algorithmen zur Last-Verteilung (Load-Balancing) unterstützt: Request Counting, Weighted Traffic Counting and Pending Request Counting. Das Prinzip des Balancing ist dabei immer das gleiche: Basierend auf einer bestimmten Metrik wird entschieden, ob eine Node des Backend-Server-Netzwerkes derzeit ausgelastet ist oder nicht. Falls sie noch Kapazitäten besitzt können, leitet der Proxy weitere Client-Anfragen via AJP an sie weiter. Ist die Last auf der Node aber schon zu groß, muss eine andere Node gefunden werden, um weitere Client-Anfragen abarbeiten zu können. Es gibt noch weitere Load-Balancing-Lösungen wie beispielsweise das Modul “mod_cluster”, das von JBoss bereitgestellt wird.

Letzte Woche meldete ein JBoss-Anwender ein Problem mit seinem Web-Application-Server Apache/JBoss-Setup. Er verwendete das JBoss-Modul “mod_cluster” zusammen mit “mod_proxy_ajp”, um Anfragen mit Load-Balancing auf mehrere JBoss-Application-Server-Nodes zu verteilen. Das Ganze funktionierte problemlos, allerdings erhielten einige Clients von Zeit zu Zeit den HTTP-Code 503 (“Service Unavailable”) als Antwort auf Server-Anfragen. Wie sich herausstellte, ließ sich das Problem einfach durch mit Telnet an den Webserver gesendete ungültige HTTP-Anfragen reproduzieren. Ein entfernter Angreifer kann die gleiche Technik verwenden, um Denial-of-Service-Attacke durchzuführen. indem er ein paar solcher Anfragen an den Server schickt. Anschließend sendet der Server für eine Weile nur noch den HTTP-Error 503 an weitere Web-Clients – der Web-Dienst steht dann also nicht zur Verfügung.

Wie sich herausstellte, befindet sich der verantwortliche Programmierfehler in der Funktion “ap_proxy_ajp_request()” in der Datei “mod_proxy_ajp.c”, die von der Funktion “proxy_ajp_handler()” aufgerufen wird. Die Funktion “proxy_ajp_handler()” wiederum ist dafür zuständig “ajp://”-Anfragen zu verarbeiten. Dabei handelt es sich um eine Handler-Funktion für AJP, die in zuvor in “ap_proxy_http_register_hook()” folgendermaßen registriert wurde:

proxy_hook_scheme_handler(proxy_ajp_handler, NULL, NULL, APR_HOOK_FIRST);

Das Problem besteht darin, dass bei fehlerhaften HTTP-Anfragen das Modul “mod_proxy_ajp” den Statuscode “HTTP_BAD_REQUEST” zurückliefert. Dies führt aber im Zusammenhang mit Load-Balancing dazu, dass der Load-Balancer die Node als In-Error (“PROXY_WORKER_IN_ERROR_FLAG”) markiert. Das bedeutet, dass sie bis zu dem Ablauf eines Retry-Timeouts nicht weiter für Client-Anfragen herangezogen wird. Kommt es nun mehrmals zu ungültigen Anfragen, kann es passieren, dass irgendwann alle Application-Server Nodes als In-Error markiert sind. Der Load-Balancer hat dann keine funktionsfähigen Nodes mehr zur Auswahl und weist weitere Client-Anfragen ab. Dies äußert sich im HTTP-Code 503 als Antwort auf weitere Client-Anfragen, und genau dies war im beschriebenen JBoss-Szenario der Fall. Da ein Angreifer sehr schnell zahlreiche ungültige Abfragen absetzen kann, lassen sich so große Application-Server Node-Cluster komplett deaktivieren.

Das Patch für diese Schwachstelle besteht darin, statt “HTTP_BAD_REQUEST” den Statuscode “HTTP_NOT_IMPLEMENTED” zurückzuliefern, falls eine seltsame HTTP-Anfrage ankommt. Denn dann setzt der Load-Balancer für die Node nicht das In-Error-Flag und kann sie weiter verwenden. Die korrigierte Funktion “ap_proxy_ajp_request()” enthält folgenden Code, der dafür sorgt:

...
/* send request headers */
status = ajp_send_header(conn->sock, r, maxsize, uri);
if (status != APR_SUCCESS) { conn->close++; ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server, "proxy: AJP: request failed to %pI (%s)", conn->worker->cp->addr, conn->worker->s->hostname);
if (status == AJP_EOVERFLOW) return HTTP_BAD_REQUEST;
else if (status == AJP_EBAD_METHOD) { return HTTP_NOT_IMPLEMENTED;
}
...

Die Funktion “ajp_send_header()” sendet zunächst den AJP-Header der Anfrage. Danach prüft das Programm den Statuscode, und um im Falle eines Fehlers bei “status == AJP_EBAD_METHOD” den “HTTP_NOT_IMPLEMENTED”-Code zurückzuliefern. Dadurch werden die entsprechenden Nodes nicht mehr in den Error-State gesetzt wie es zuvor durch “HTTP_BAD_REQUEST” geschah, und die Denial-of-Service-Attacke ist nicht mehr möglich.

Betroffen von diesem Problem sind die Apache-Webserver-Versionen vor 2.2.21.

Nach oben