
Apache Spark 4.2.0 - Alta - Remoto - Lectura de archivos - RPC StreamRequest File join sin autenticación
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · spark-rpc-stream-unauth
Clase: Resto de privilegio / Lectura de archivos Alcance: Remoto
Apache Spark 4.2.0 (32f72996) - Apache Software Foundation
CVE-2020-9480 cerró RequestSubmitDriver / RegisterApplication sin autenticación en el plano de control RPC. AbstractAuthRpcHandler protege receive() y receiveStream(). No protege getStreamManager(). TransportRequestHandler todavía despacha StreamRequest a ese gestor antes de SASL. NettyStreamManager.openStream hace new File(dir, fname) sin ninguna comprobación de permanencia bajo la raíz.
StreamRequest sin autenticación classes/../../path lee archivos Spark-UID. Los hijos absolutos de Unix permanecen bajo el directorio registrado (lab 404). files/ es un get de mapa (404). receive() / receiveStream() permanecen 401. Esto no es RCE de 9480.
| ID | sin CVE todavía |
| Clase | Resto de privilegio / Lectura de archivos (resto del plano de datos RPC sin protección de CVE-2020-9480) |
| Alcance | Remoto (TCP sin autenticación a un driver RpcEnv / ESS / BlockManager con spark.authenticate=true) |
| CWE | CWE-862, CWE-22 |
| CVSS | Alto: 7.5 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N |
| Producto | Apache Spark |
| Afectado | 4.2.0 (32f7299601108917fb01920a54e084595b7b3bf8). spark.authenticate=true habilitado por el operador. La lectura de archivos del host necesita un directorio registrado (spark.repl.class.outputDir /classes y/o Connect artifacts). |
| Autenticación | ninguna en StreamRequest / getStreamManager() |
| Licencia | GNU Affero GPL v3.0 |
| Lab | solo 127.0.0.1 |
TCP a un puerto RPC de Spark que tenga spark.authenticate=true y un directorio registrado. Sin secreto compartido. Sin spark.network.crypto.enabled. Enviar un StreamRequest de transporte (tipo de mensaje 6) con streamId classes/../../<path>. TransportRequestHandler.processStreamRequest nunca llama a RpcHandler.receive. Llama a getStreamManager().openStream. El wrapper de 9480 devuelve el gestor real. new File(dir, "../../oracle/secret") se canonicaliza fuera de la raíz de REPL/artifact y vuelve como FileSegmentManagedBuffer.
Eso es una lectura de archivo del host como el UID de Spark. No es envío de trabajos. No es RequestSubmitDriver. No es una shell.
| Método | Sink | Privilegio |
|---|---|---|
StreamRequest sin auth classes/../../<path> | NettyStreamManager.openStream new File(dir, fname) luego FileSegmentManagedBuffer | Leer archivos Spark-UID que el driver puede abrir. Probado en lab: opened /opt/repl-classes/../../oracle/secret, canónico /oracle/secret, cuerpo SPARK-RPC-STREAM-UNAUTH-WITNESS, x-auth-gated=false, x-authenticated=false. |
StreamRequest sin auth classes/hello.txt (control en-dir) | misma unión, permanece bajo el directorio registrado | Leer un archivo que realmente está bajo /classes. Probado en lab 200 IN-DIR-OK, sin testigo. Confirma que el servidor de archivos funciona sin SASL incluso para un nombre contenido. |
| Método | Sink | Privilegio |
|---|---|---|
ChunkFetchRequest / StreamRequest sin auth de un streamId ESS activo | OneForOneStreamManager.getChunk / openStream. checkAuthorization se omite cuando client.getClientId() == null | Leer bytes de shuffle / RDD en disco en vuelo de otra aplicación. Disponibilidad: el drenaje avanza curChunk. Necesita un streamId activo (inicio aleatorio, luego +1). El gadget no hablaba ESS activo. |
MergedBlockMetaRequest sin auth | AbstractAuthRpcHandler.getMergedBlockMetaReqHandler() también sin protección | Leer bitmaps de chunks de shuffle fusionado para un appId elegido cuando ESS tiene shuffle fusionado. Misma omisión de clientId nulo. No ejercitado. |
| Método | Sink | Permanece cerrado |
|---|---|---|
StreamRequest sin auth classes//oracle/secret (hijo absoluto Unix) | new File(dir, "/oracle/secret") | 404. La ruta abierta es /opt/repl-classes/oracle/secret. File de Unix concatena; no ignora el padre. La afirmación de la caza de que un hijo absoluto descarta la raíz es incorrecta en Unix. |
StreamRequest sin auth files//oracle/secret | get de mapa files | 404. files/ y jars/ son búsquedas por basename, no uniones. |
receive() / OneWayMessage sin auth | AbstractAuthRpcHandler.receive | 401 Unauthenticated call to receive(). x-auth-gated=true. |
receiveStream() / UploadStream sin auth | AbstractAuthRpcHandler.receiveStream | 401 Unauthenticated call to receiveStream(). |
receiveStream() autenticado con el secreto del lab | misma puerta tras doAuthChallenge | 200 receiveStream-ok. Control de que el secreto todavía abre la ruta 9480. |
Probado en lab vs inferido: lectura de archivo .. sin auth, 404 absoluto Unix, 404 files/, receive / receiveStream protegidos son de lab. El drenaje de shuffle ESS es inferido.
Adyacente cerrado (no inflar):
RequestSubmitDriver / RegisterApplication sin auth vía OneWayMessage / UploadStream / RpcRequest. Todavía necesita el secreto compartido.spark.authenticate=false (FAQ de SECURITY.md, default documentado de red confiable). No es este resto.AppIsolationSuite / SPARK-57889 clientId no nulo)./classes ni artifacts registrados, así que este recorrido de archivos del host es driver / REPL / Connect, no master:7077.spark.authenticate.secret.Fijé apache/spark v4.2.0 (32f7299601108917fb01920a54e084595b7b3bf8), no master y no un RC de 4.3. Veintiocho cazas de código fuente. El envío de trabajos en un clúster abierto es por diseño. La carga de modelos ML es código ejecutable. El RCE público de Jackson History, la shell id -Gn de la UI, la suplantación local de PySpark y la ruta receive de 9480 están cerrados en este pelado.
La caza de RPC-auth se suponía que era una comprobación de re-registro de 9480. AuthServerBootstrap todavía envuelve el handler. receive sin auth todavía lanza. Luego leí getStreamManager(). Tres líneas. Devuelve el delegado. TransportRequestHandler.handle enruta StreamRequest alrededor de receive. El StreamManager.checkAuthorization por defecto es un no-op. SPARK-57889 añadió una comprobación de id de cadena en StreamRequest y mantuvo la omisión de clientId nulo que ESS usa para "auth off", que también es "esta conexión nunca se autenticó".