Validación incorrecta de IPs en formato octal en Python 3.8-3.10 permite ataques SSRF, RFI y LFI (CVE-2021-29921)

Una dirección IPv4 se puede expresar de varias formas, en decimal, entero, octal y hexadecimal, aunque el decimal es lo más común. Por ejemplo: 8.8.8.8 en octal es 0010.0010.0010.0010.

Algo que sorprenderá a muchos... si ponemos en el navegador https://0127.0.0.1 realmente nos redireccionará a https://87.0.0.1/. Eso es porque las secciones de una dirección IPv4 se pueden interpretar como octal si tienen el prefijo "0", de acuerdo con la especificación original de IETF para direcciones IP ambiguas. 

Pero, ¿qué pasa en las versiones de Python de la 3.8.0 a a la 3.10? Pues que la librería ipaddress elimina o descarta los ceros a la izquierda. De esta manera 010.8.8.8 acaba siendo 10.8.8.8:

$ python3.8
Python 3.8.0 (default, Feb 25 2021, 22:10:10) 
[GCC 8.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import subprocess
>>> import ipaddress
>>> 
>>> SUSPECT = '010.8.8.8'
>>> 
>>> print(ipaddress.ip_network(SUSPECT, strict=True))
10.8.8.8/32
>>> 
>>> BAD_IP = ipaddress.ip_address(SUSPECT)
>>> 
>>> print('http://'+str(BAD_IP))
http://10.8.8.8
>>> 
>>> print(str(subprocess.check_output("ping -W3 -v -c1 "+str(SUSPECT), shell=True, universal_newlines=True).strip()))
ping: socket: Permission denied, attempting raw socket...
ping: socket: Permission denied, attempting raw socket...
PING 010.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=118 time=5.13 ms

--- 010.8.8.8 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 5.137/5.137/5.137/0.000 ms
>>> 
>>> print(str(subprocess.check_output("ping -W3 -v -c1 "+str(BAD_IP), shell=True, universal_newlines=True).strip()))
ping: socket: Permission denied, attempting raw socket...
ping: socket: Permission denied, attempting raw socket...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.8/subprocess.py", line 411, in check_output
    return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
  File "/usr/lib/python3.8/subprocess.py", line 512, in run
    raise CalledProcessError(retcode, process.args,
subprocess.CalledProcessError: Command 'ping -W3 -v -c1 10.8.8.8' returned non-zero exit status 1.

Esta vulnerabilidad añadida en este commit (https://github.com/python/cpython/pull/12577), bautizada como CVE-2021-29921 y similar a la de netmask de Node.js de principios de año, apunta a grandes "daños colaterales":

"La validación inadecuada de la entrada de cadenas en formato octal en ipaddress permitiría a atacantes remotos no autentificados realizar ataques Server-Side Request Forgery (SSRF), Remote File Inclusion (RFI) y Local File Inclusion (LFI) en múltiples programas que dependen de la stdlib ipaddress de Python.

Fuente: https://sick.codes/sick-2021-014/

Comentarios