Alguien puede estar tratando de conocerte mejor

Faro vigilando... Si tenemos acceso al registro de actividad de un servidor cualquiera podemos ver todo tipo de intentos de obtención de información; muchas veces se trata de simples troyanos de máquinas infectadas por ahí que simplemente tratan de propargarse y otras de intentos dirigidos (seguramente más raros en servidores ‘caseros’).

En Who’s Scanning Your Network? (A: Everyone) hablaban del tema entrevistando a Zakir Durumeric y Michael D. Bailey que son investigadores de la Universidad de Michigan y que, entre otros proyectos, mantienen el Internet-Wide Scan Data Repository que es un archivo público de datos recolectados mediante escaneos de la parte pública de internet. La respuesta es, como decíamos arriba, que mucha gente nos está vigilando y tratando de obtener información.

Con estos datos son capaces de detectar servidores y servicios vulnerables (e incluso avisarles en situaciones de crisis como el reciente Heartbleed del que hablamos en su momento en [Heartbleed, código y memoria](http://fernand0.github.io/Heartbleed-Y-Certificados/].

Pero no sólo eso, sino también tener cierta capacidad predictiva, com dice Durumeric en:

So, if you can watch, for example, how an organization runs their Web server, how they respond to certificate revocation, or how fast they patch — that actually tells you something about the security posture of the organization, and you can start to build models of risk profiles of those organizations. It moves away from this sort of patch-and-break or patch-and-pray game we’ve been playing.

Por supuesto, no hay que olvidar que esto mismo lo pueden hacer los ‘malos’, vigilando nuestra ‘meta’-información pueden ser capaces de encontrar nuestros puntos débiles y los mejores momentos para atacar (como en las películas ‘clásicas’ de ladrones de bancos, que el atacante aprende los protocolos y momentos adecuados para actuar).

Dicen muchas más cosas interesantes pero nos recuerdan que la seguridad no sólo es algo tecnológico, sino que también tiene que ver con nuestros procesos y forma de actuar. Y que aunque esa información no sea pública tal vez sea posible deducirla.

Claves falsas para despistar al atacante

Escondido Tengo la teoría de que una forma de acabar con el spam sería atacar los recursos de los ‘malos’: si todos respondiéramos no podrían desarrollar correctamente su labor. Tanto es así que a veces, cuando recibo algún correo de Phishing lo relleno con datos falsos con la idea de que tratarán de utilizarlos y consumo, en pequeña medida, sus recursos.

Por eso me hizo gracia leer The best way to protect your passwords may be creating fake ones donde explican una propuesta experimental para asegurar las contraseñas almacenadas con un gestor la creación de algunas nuevas (y falsas) que harían perder el tiempo a un hipotético atacante.

Además, como el almacenamiento de contraseñas estaría cifrado, el gestor genera un fichero de contraseñas de aspecto razonable para cada posible clave de descifrado que un atacante pueda probar (por fuerza bruta) así que ralentizaríamos todavía más sus ataques.

Sobre inclusión más segura de contenidos con iframes

Enmarcado La utilización de frames es vieja en la red: permite incluir contenido de otros sitios en nuestra página y es un mecanismo que algunos servicios ofrecen como el habitual para este cometido.

Nowadays, old-school (Netscape style) frames have fallen out of fashion, but iframes are more popular than ever. They’re used for advertising, social plugins (e.g. Facebook “like” buttons and “Share on Twitter” functionality), webpage widgets, and so on.

En You’ve Been Framed! A survey of iframes and the sandbox attribute se comenta sobre estas técnicas y su prevalencia, así como el atributo sandbox, que permite controlar políticas de seguridad sobre contenido incrustado en nuestra web:

Just how popular are iframes? Not all websites have them but those that do tend to have quite a lot. To find out, I recently crawled the top ranking sites on the web and counted the iframes. Most iframes are dynamically generated by javascript, so you need to use a crawler which evaluates js. I wrote about the method I used in a follow-up post.

72% of the Alexa top 5000 sites have iframes on their landing pages. Of those, each has on average 7.1 iframes. Some sites have well over 50 frames.

Of all these 25,849 iframes on the top 5000 sites, only 10 use the HTML5 sandbox security attribute. That’s a whopping 0.04%! We’ll learn more about this attribute in a bit.

Examining the top 50 sites, there are a lot fewer iframes. But no one is using the aforementioned sandbox attribute. Of the top 1000, only two sites employ it.

Los riesgos son, esencialmente, que alguien saque partido de estar en nuestra página web al estar integrando contenido que está fuera de nuestro control:

However, external content is completely beyond your control. It can control the browser and the user experience to a degree. Unrestricted iframes can run javascript, Flash and other plugins, open pop-up windows, and even navigate from the containing page. If the site is just intended to display an ad or social button, does it really need to be able to do all (or even any) of these things?

En sitios de perfil alto esto no debería ser un problema (pero mejor no fiarse) pero nunca sabemos lo que puede pasar con otros sitios: esos botones tan chulos que proporcionaba alguien y que, en algún momento, no puede seguir con el servicio, abandona el dominio y es utilizado por otra persona.

En Play safely in sandboxed IFrames se habla un poco más del marco general (principios básicos de seguridad, mínimo privilegio y compartimentalización) y se explica la forma de incluir iframes de forma más segura:

The sandbox attribute of the iframe element gives us just what we need to tighten the restrictions on framed content. We can instruct the browser to load a specific frame’s content in a low-privilege environment, allowing only the subset of capabilities necessary to do whatever work needs doing.

Algunas técnicas contra el análisis de ejecutables

Pantalla En Jugando con técnicas anti-debugging José Manuel Fernández hace una panorámica de las ténicas anti depurado que utilizan los programas maliciosos para evitar que su detección y análisis.

En particular en esta entrada hablaba de la función IsDebuggerPresent.

En Jugando con técnicas anti-debugging (II) comenta sobre el campo NtGlobalFlag.

Finalmente, en Jugando con técnicas anti-debugging (III) se habla de las técnicas basadas en tiempo (un programa ejecutándose en condiciones normales no debería tardar más de … Si tarda más, a lo mejor alguien lo está ejecutando paso a paso, o haciendo algo ‘indebido’ con él). En este caso la función es GetTickCount().

Como bola extra, la lectura recomendada de [PDF] The ultimate Anti-debugging Reference y los programas utilizados como ejemplo en reverc0de/saw-anti-debugging. Muy interesante.

SSL para todos en CloudFare y la escala

Candados Uno de los problemas e utilizar SSL (TLS hoy en día) es el coste computaional: con muchas visitas las conexiones cifradas pueden ser un sobrecoste que no estemos dipuestos a asumir.

En Universal SSL: How It Scales Nick Sullivan de CloudFlare nos habla de su proyecto (ya tiene más de un año) de proporcionar HTTPS para todos los sitios albergados por ellos (que es una decisión a la que parece que se está dirigiendo mucha más gente cada vez) y hablan de este sobrecoste:

People have asked us, both in comments and in person, how our servers handle this extra load. The answer, in a nutshell, is this: we found that with the right hardware, software, and configuration, the cost of SSL on web servers can be reduced to almost nothing.

Alguno de los trucos está basado en cuestiones como retomar sesiones:

For returning visitors of a site we have a shortcut that eliminates the need for our servers to perform these expensive operations. The shortcut is called session resumption and it’s built into the TLS specification.

Otras medidas utilizadas son el Lazy Loading:

Lazy loading of certificates helps relieve that bottleneck. Using custom modifications to nginx by CloudFlare engineer Piotr Sikora, we are able to dynamically load certificates into memory only when they’re needed. Now, if one site changes their certificate, the server does not have to reload every certificate. This change allows our servers to scale up to handle millions of HTTPS sites.

Todo ello combinado con algoritmos modernos (Criptografía de curvas elípticas, ECDSA) y hardware actualizado:

All Intel CPUs based on the Westmere CPU microarchitecture (introduced in 2010) and later have specialized cryptographic instructions.

Desbordamientos de enteros recientes

Agua desbordando Me repito. Pero cuando salen fallos de seguridad relacionados con los temas típcos no podemos dejarlos pasar sin comentarlos. En este caso se trata de desbordamientos de enteros (lectura clásica recomendada: Basic Integer Overflog).

El primero se trata de un desbordamiento de enteros en `strncat’ Integer overflow in strncat.

I didn’t look into the details but it looks like strncat(s1, s2, n) misbehave when n is near SIZE_MAX, strlen(s2) >= 34 and s2 has specific offset.

Hay algunos comentarios interesantes en Integer overflow in glibc strncat donde no queda claro del todo si es fallo o característica, pero vale la pena leerlos, sobre todo si nos gusta o no sinteresa el lenguaje C.

Por otro lado, leíamos el otro día en Desbordamiento de entero en PuTTY y en PuTTY vulnerability vuln-ech-overflow sobre un desbordamiento de enteros en PuTTY. En este caso el atacante debería ser capaz de insertar una secuencia de escape sofisticada que afectaría a una variable que almacena el número de caracteres que hay que borrar en determinadas condiciones.

The vulnerability arises because PuTTY uses signed integer variables to hold the number of characters to be erased and doesn’t adequately check for overflow. This means that by passing a very large parameter to ECH, an attacker could cause check_boundary to inspect memory outside the terminal buffer.

Lecciones aprendidas desarrollando GitHub pages

Crecimiento GitHub Pages Recientemente se ha cumplido un año que estamos escribiendo en este sitio Cuata Etapa. Me viene bien recordarlo porque recientemente leíamos Eight lessons learned hacking on GitHub Pages for six months en el que nos cuentan algunos principios generales aprendidos desarrollando el servicio.

El primero de ellos (previo, más bien) es ser usuario de tu propio servicio para conocerlo bien, conocer sus virtudes, encontrar sus defectos…

Y luego la lista:

  • Test, test, and then test again

Desarrollo basado en pruebas, para estar seguros de que cuando se introducen cambios nada va a fallar.

  • Use public APIs, and when they don’t exist, build them

En la línea del principio ‘previo’ no utilizar componentes secretos o privados y si se detecta la necesidad de algo que no existe, ponerlo a disposición de los usuarios.

  • Let the user make the breaking change

Permitir a los usuarios que controlen los cambios importantes que pueden afectar a sus sitios.

  • In every communication, provide an out

Cuando se comunican los errores se informa al usuario no sólo del problema sino también de dónde tiene que mirar para solucionar el fallo.

  • Optimize for your ideal use case, not the most common

No hay que olvidar que el servicio es para dar a conocer los proyectos. Eso debería ser lo más fácil de hacer, independientemente de lo que la gente esté haciendo.

  • Successful efforts are cross-team efforts

Como tantos proyectos, no son cosa sólo de un equipo que se encarga de ellos, sino que, seguramente, hará falta hablar y trabajar con otros equipos.

  • Match user expectations, then exceed them

Que los usuarios tengan lo que esperaban del proyecto, pero mejor. No hay mucho que añadir aquí.

  • It makes business sense to support open source

Una parte de su producto está basado en Jekyll, que es software libre y que ha ganado su propio espacio como producto independiente, que siguen apoyando y en el que siguen participando.

Condiciones de carrera en algunos sitios famosos

Carrera Cuando explicamos las condiciones de carrera siempre nos quedamos con la duda de si es un tema que ya todo el mundo conoce y también si no sería mejor dedicar ese tiempo a explicar otras cosas. De pronto, una entrada como Race conditions on Facebook, DigitalOcean and others (fixed) nos demuestra que más vale no olvidarlos.

Las condiciones de carrera ocurren en sistemas concurrentes, cuando dos actores acceden a o modifican una determinada información y el tiempo y la forma de acceso son importantes, porque lo que haga uno de ellos afecta a lo que el otro debería poder hacer. Se puede ver una explicación en Practical Race Condition Vulnerabilities in Web Applications y también en Condición de Carrera

En la entrada se comenta sobre varios fallos de sitios de alto nivel (que, al final, es lo que hace que valga la pena comentarlos aquí). Son secuencias de pasos para fallos de Facebook (en este caso aumentar el número de revisiones de una página o crear varios nombres de usuario para una cuenta), DigitalOcean (añadir crédito a nuestra cuenta), y LastPass (con un fallo parecido al de DigitalOcean).

Errores al final

Montaña La mente humana es curiosa. Al principio de un proyecto tomamos algunas partes con entusiasmo, pero como el final está lejos estamos relajados, exploramos, jugamos, modificamos, probamos cosas… Cuando se acerca el final vamos yendo más a lo concreto, tratando de cumplir con los hitos y ajustándonos a lo que hay que hacer. Y cuando llega el momento del cierre es un ‘sálvese quien pueda’ y dejamos algunas partes pilldas con hilos, en espera de evoluciones posteriores y cosas que haremos (tal vez) cuando vengan tiempos mejores y el proyecto esté entregado. Por lo visto también, cuando llegamos al final y todo está yendo más o menos bien perdemos tensión, creemos que todo lo tenemos controlado y nos volvemos perezosos y bajamos la guardia, llegando a ser más descuidados y confiados.

En The Last Line Effect hablan de algo que está ligeramente relacionado, y por eso lo llaman el efecto de la última línea:

I have studied numbers of errors caused by using the Copy-Paste method and can assure you that programmers most often tend to make mistakes in the last fragment of a homogeneous code block.

En este caso el autor estudia el efecto asociado a copiar bloques de instrucciones y encontrar que la probabilidad de cometer un error en el último bloque de código copiado es cuatro veces mayor que en cualquier otro bloque:

The probability of making a mistake in the last pasted block of code is 4 times higher than in any other block.

La idea es que los programadores copian y pegan framgentos de código que hacen cosas parecidas, y luego los modifican:

When writing program code, programmers often have to write a series of similar constructs. Typing the same code several times is boring and inefficient. That’s why they use the Copy-Paste method: a code fragment is copied and pasted several times with further editing. Everyone knows what is bad about this method: you risk easily forgetting to change something in the pasted lines and thus giving birth to errors. Unfortunately, there is often no better alternative to be found.

En este caso el problema sería parecido al del final de proyecto al que aludíamos en la introducción: copio, pego, modifico, ya tengo el problema casi resuelto, y de pronto estoy menos atento, así que me olvido de finalizar la cosa.

Por lo visto, es algo que también les pasa a los montañeros que, por lo visto, también es más probable que tengan un accidente cuando están finalizando la ascensión.

Añadiendo filtros de correo a mi sistema con sievelib

Laberinto Otra tarea importante que necesitamos en el correo es el filtrado: tener en nuestra carpeta principal sólo los mensajes que pueden ser importantes y llevar a otras carpetas secundarias otros avisos, como mensajes de listas de correo y cosas así. A veces es bueno apartar algunos mensajes importantes que no podemos atender cuando estamos fuera de la oficina: cuando volvamos a lo mejor ya no los vemos como nuevos y no les dedicaremos la atención necesaria.

Como decíamos el otro día el sistema se basa en Zentyal, que incorpora un sistema de filtrado de correo (mediante el webmail y los filtros basados en el protocolo ManageSieve). El problema (para mi) es que no soy usuario habitual del correo web porque prefiero otros clientes y no me resultaba cómodo tener que lanzarlo para añadir un filtro, así que anduve investigando para averiguar si se podría utilizar alguna alternativa. Mi opción preferida es un programa de línea de instrucciones.

Encontré sievelib y estuve haciendo algunas pruebas. No estoy seguro de haber manejado adecuadamente la biblioteca: yo utilizo básicamente dos tipos de reglas, las de almacenar en alguna carpeta concreta, y las de redigir a otra cuenta de correo (por ejemplo ‘newsletters’ en html que se leen mejor en GMail -del que también soy usuario). Por este motivo, sólo me he concentrado en estas reglas y ni siquiera estoy seguro de haberlo hecho en toda su generalidad. El resultado está en addToSieve.py en el estado de la versión que se comenta aquí.

El servidor de correo almacena las reglas en ficheros y hace una pequeña gestión que le permite saber cuál de ellos está activo, así que hay que conectarse e identificarse y autentificarse. Habremos leído los datos de configuración de manera análoga a cómo hacíamos con el servidor IMAP como veíamos en Jugando con IMAP en Python y seleccionar el ‘script’ que queremos modificar y analizarlo:

from sievelib.managesieve import Client

...

        c = Client(SERVER)
        c.connect(USER,PASSWORD, starttls=True, authmech="PLAIN")

...

        script = c.getscript('sogo')
        p = Parser()
        p.parse(script)

Para crear la regla vamos a basarnos en el mensaje que queremos filtrar. Mostramos los 15 mensajes más recientes y el usuario elije uno de ellos:

def selectMessage(M):
	M.select()
	data=M.sort('ARRIVAL', 'UTF-8', 'ALL')
	if (data[0]=='OK'):
		j=0
		msg_data=[]
		messages=data[1][0].split(' ')
		for i in messages[-15:]:
			typ, msg_data_fetch = M.fetch(i, '(BODY.PEEK[HEADER.FIELDS (From Sender To Subject List-Id)])')
			for response_part in msg_data_fetch:
				if isinstance(response_part, tuple):
					msg = email.message_from_string(response_part[1])
					msg_data.append(msg)
					print "%2d) %4s %20s %40s" %(j,i,msg['From'][:20],msg['Subject'][:40])
					j=j+1
		msg_number = raw_input("Which message? ")
		return msg_data[int(msg_number)] #messages[-10+int(msg_number)-1]
	else:	
		return 0

Creo que no hay mucho que comentar en este código, simplemente ordenamos los mensajes por orden de llegada y mostramos algunos campos de los 15 últimos (utilizamos BODY.PEEK para que no se marquen como leídos los mensajes que no lo estuvieran). Para extraer las cabeceras relevantes se utiliza email.message_from_string. Si todo va bien, devolvemos el mensaje return msg_data[int(msg_number)].

Ahora que sabemos qué mensaje vamos a utilizar para el filtrado elegimos qué cabecera es la que utilizaremos para filtrar

	(keyword, textHeader) = selectHeaderAuto(M, msg)

Nos interesan las siguientes cabeceras (al menos por ahora, esto puede ir evolucionando):

msgHeaders=['List-Id', 'From', 'Sender','Subject','To', 'X-Original-To']

Además prefiero filtrar por List-Id siempre que sea posible, así que en selectHeaderAuto tenemos:

	
if msg.has_key('List-Id'): 
		return ('List-Id', msg['List-Id'][msg['List-Id'].find('<')+1:-1])

Notar que hemos ‘limpiado’ el contenido para eliminar todo lo que hay antes y después de los ángulos. En caso de que no tenga esta cabecera, mostramos las otras con este código, que tiene en cuenta que algunas de ellas pueden no estar presentes:

	
		for header in msgHeaders:
			if msg.has_key(header):
				print i," ) ", header, msg[header]
			i = i + 1
		header_num=raw_input("Select header: ")
		

Tomamos la cabecera y el texto correspondiente:

	
		header=msgHeaders[int(header_num)-1]
		textHeader=msg[msgHeaders[int(header_num)-1]]

Y ahora lo limpiamos (si es una dirección de correo contendrá ángulos, y si es un asunto ‘Subject’ a veces contiene corchetes, que también nos pueden venir bien para filtrar).

	
		pos = textHeader.find('<')
		if (pos>=0):
			textHeader=textHeader[pos+1:textHeader.find('>',pos+1)]
		else:
			pos = textHeader.find('[')
			textHeader=textHeader[pos+1:textHeader.find(']',pos+1)]

		pos = textHeader.find('<')
		if (pos>=0):
			textHeader=textHeader[pos+1:textHeader.find('>',pos+1)]
		else:
			pos = textHeader.find('[')
			textHeader=textHeader[pos+1:textHeader.find(']',pos+1)]
		return (header, textHeader)

Con estas cabeceras ahora tenemos que decidir la acción que realizaremos con los mensajes que coindidan con las cabeceras seleccionadas. De esto se ocupa la función selectAction. Esta función nos ofrece como acciones posibles las que ya tengamos en los filtros (carpetas que ya hemos utilizado, sievelib.commands.FileintoCommand, y redirecciones existentes, sievelib.commands.RedirectCommand), mostrando sus valores relevantes:

	
	i = 1
	for r in p.result:
		if r.children:
			if (type(r.children[0]) == sievelib.commands.FileintoCommand):
				print i, ") Folder   ", r.children[0]['mailbox']
			elif (type(r.children[0]) == sievelib.commands.RedirectCommand):
				print i, ") Redirect ", r.children[0]['address']
			else:
				print i, ") Not implented ", type(r.children[0])
		else:
			print  i, ") Not implented ", type(r)
	

Cada resultado es una regla, que tiene como ‘hijo’ la acción y la carpeta o la dirección donde se reenvía (en ambos casos puede haber varias acciones, como veremos luego). También indica las reglas que no han sido contempladas por el programa y que, por lo tanto no tenemos posibilidad de añadir (abriendo la puerta a futuras mejoras). Finalmente, se ofrecen las opciones de añadir carpetas o redirecciones nuevas:

	
	print i, ") New folder "
	print i+1, ") New redirection"

Si reutilizamos alguna acción, simplemente copiamos lo que ya teníamos, añdiendo las carpetas o las redirecciones que haya y terminando con el stop que hace que no se analicen más reglas:

	
		action=p.result[int(option)-1].children

		for i in action:
			print i.arguments
			if i.arguments.has_key('mailbox'):
				actions.append(("fileinto",i.arguments['mailbox']))
			elif i.arguments.has_key('address'):
				actions.append(("redirect",i.arguments['address']))
			else:	
				actions.append(("stop",))
	

En el caso de que queramos utilizar una carpeta diferente, solicitamos el nombrey comprobamos que la carpeta existe (podríamos querer una carpeta que no existe, pero casi nunca es el caso, así que no contemplamos esa opción; esto nos permitirá evitar errores al teclear). Para esto es necesario estar autentificado con el servidor IMAP:

	
		folder= raw_input("Name of the folder: ")
		print "Name ", folder
		if (doFolderExist(folder,M)[0]!='OK'):

La carpeta (sólo una) se añade así:

	
			actions.append(("fileinto", folder))
			actions.append(("stop",))

En el caso de redirecciones la verificación se hace mediante una pregunta explícita:

	
		redir= raw_input("Redirection to: ")
		print "Name ", redir
		itsOK= raw_input("It's ok? (y/n)")
		if (itsOK!='y'):

Y en caso de que la validemos, añadimos la acción correspondiente:

	
			actions.append(("redirect", redir))
			actions.append(("stop",))

Aunque hemos seleccionado una cabecera para filtrar y la hemos limpiado es posible que no queramos utilizar la cabecera completa (ejemplo típico: en lugar de una dirección de correo, un dominio o una parte del usuario de correo) así que ofrecemos al usuario la posibilidad de escribir lo que considere oportuno:

	print "Filter: (header) ", keyword,", (text) ", textHeader
	filterCond = raw_input("Text for selection (empty for all): ")

	if not filterCond:
		filterCond = textHeader

Ahora construimos la regla:

	conditions=[]
	conditions.append((keyword, ":contains", filterCond))

Y creamos un filtro con ella (lo ideal sería añadirla al correspondiente si ya existía pero eso será objeto de una entrada posterior, tal vez) y lo almacenamos en un fichero (seguro que esta parte podría evitarse, pero no tengo claro cómo hacerlo):

	fs = FiltersSet("test")
	fs.addfilter("",conditions,actions)
	fs.tosieve(open('/tmp/kkSieve','w'))

Lo abrimos para analizarlo igual que el original:

	p2=Parser()
	p2.parse(open('/tmp/kkSieve','r').read())
	lenP2 = len(p2.result)

Y añadimos las reglas a las que teníamos, para finalizar escribiéndolas en un fichero:

	p.result.append(p2.result[lenP2-1])	

	fSieve=open('/tmp/kkSieve','w')
	for r in p.result:
		r.tosieve(0,fSieve)
	fSieve.close()

Guardamos el fichero original para tener un histórico y también como copia de seguridad por si algo va mal:

	name = time.strftime("%Y-%m-%d-%H-%M-%S", time.gmtime())
	c.putscript(name+'sogo',script)

Finalmente, ponemos el nuevo fichero como fichero de reglas de nuestra criba (‘sieve’):

	fSieve=open('/tmp/kkSieve','r')
	if not c.putscript('sogo',fSieve.read()):
		print "fail!"

Planes para el futuro:

  • Agrupar reglas para que las que corresponden a las mismas acciones estén juntas.
  • Modificar este programa para que la adición de reglas se haga añadiéndolas en el lugar que les corresponda, en vez de al final.
  • Gestionar las copias de seguridad de los viejos filtros. Está bien tener algunas por si algo va mal, pero no vale la pena guardar toda la historia.
  • Añadir un modo completamente manual (para el que ya hay código, pero no está integrado)
  • Automatizar todavía más la extracción de reglas (por ejemplo, intentar ‘adivinar’ cuál es la acción que podríamos elegir (?).

¿Ideas? ¿Comentarios? Estaremos escuchando en @mbpfernand0 o en @fernand0.