Affichage des articles dont le libellé est security. Afficher tous les articles
Affichage des articles dont le libellé est security. Afficher tous les articles

lundi, 2 août 2010

Dealing with old SSL certificats (algorithm check failed: MD2withRSA is disabled)

I faced the problem of SSLHandShakeException, or "algorithm check failed: MD2withRSA is disabled" when upgrading above java 1.6.0_17.

The source of problem is well known, the MD2withRSA has been removed from the JVM because it is no more secure (References : CVE-2009-2409 deprecate MD2 in SSL cert validation, Sun java 1.6u17 release notes).

Lot of posts around the problem give the solution to update the certificate to use another signature algorithm, for example SHA1withRSA.

But what to do when the certificate is not under our hands ?

I will try to explain the problem and how I solved it.

Analyse the certificate's chain

First of all is to analyse the chain of certificate to see which one is using the deprecated algorithm.

The following command will print all the certificates in the chain and store them under the names level0.pem, level1.pem and so on.

openssl s_client -showcerts -connect xxxxxxxxxxxxxxxxx:443 < /dev/null | awk -v c=-1 '/-----BEGIN CERTIFICATE-----/{inc=1;c++} inc {print > ("level" c ".pem")}/---END CERTIFICATE-----/{inc=0}'; for i in level?.pem; do openssl x509 -noout -serial -subject -issuer -in "$i"; done
The output given in my case :
  • Cert1, serial=xxxxxxxxxxxxxxxxxxxxx
    subject= xxxxxxxxxxxxxxxxxxxxxxxx
    issuer= /C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA
  • Cert2, serial=30000002
    subject= /C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA
    issuer= /C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority
  • Cert3, serial=70BAE41D10D92934B638CA7B03CCBABF
    subject= /C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority
    issuer= /C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority
We have a chain of 3 certificates, Cert1 signed by Cert2 signed by Cert3 signed by Cert3 (selfsigned).

Note : the first error here is that the webserver gives explicitly the entire chain of certificates. Thus the change of any root certificate provided by the jre will not be taken into account automatically.

Digging into the details of the third certificate (openssl x509 -in level2.pem -text) show that this certificate is signed with the old algorithm :
Certificate:
Data:
Version: 1 (0x0)
Serial Number:
70:ba:e4:1d:10:d9:29:34:b6:38:ca:7b:03:cc:ba:bf
Signature Algorithm: md2WithRSAEncryption
Issuer: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority
Validity
Not Before: Jan 29 00:00:00 1996 GMT
Not After : Aug 1 23:59:59 2028 GMT
So not only the way of providing the full chain is wrong in point of view of the server configuration, but the last certificate was never updated and thus still use the deprecated signature algorigthm.

Correct the certificate's chain

Now we have identified the weak link of the chain, what's the next step ?

The key point here is to correct the certificate's chain in order to make it more compliant to the standards :

As the third certificate is a well know root certificate (named verisignclass3ca in the jre cacerts), a working version of it is provided in the cacert of java sun (debian oriented commande line, with "changeit" as password) :
keytool -keystore /etc/java-6-sun/security/cacerts -exportcert -alias "verisignclass3ca" | openssl x509 -inform der -text
will output :
Certificate:
Data:
Version: 1 (0x0)
Serial Number:
3c:91:31:cb:1f:f6:d0:1b:0e:9a:b8:d0:44:bf:12:be
Signature Algorithm: sha1WithRSAEncryption
Issuer: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority
Validity
Not Before: Jan 29 00:00:00 1996 GMT
Not After : Aug 2 23:59:59 2028 GMT
which is the same as before, but with sha1WithRSAEncryption instead of md2WithRSAEncryption.

Thus we can simply remove it from the chain and validate normally the certificate's chain with only Cert1 and Cert2. The validation from Cert2 to "verisignclass3ca" will be automagically done.

To be able to do this, we need to create a custom X509TrustManager which will behave the following :
  1. if the certificate's serial is the one of Cert1 (serial=xxxxxxxxxxxxxxxxxxxxx), then remove the last certificate of the chain and revalidate.
  2. In all other cases, validate normally the certificate's chain.
Thus the custom X509TrustManager act as delegate, with one little variation (note : if someone know a easier way to instantiate the default TrustManagerFactory of TrustManager, please leave me a comment). Refer to my article about blind SSL factory for httpClients to see how to pass a custom TrustManager to a SSL factory.

My custom X509TrustManager class will finally look as the follow :
public class CustomX509TrustManager implements X509TrustManager {

X509TrustManager delegate = null;

public CustomX509TrustManager(KeyStore keystore) throws NoSuchAlgorithmException, KeyStoreException, CertificateException {
// Instantiate the default X509TrustManager
TrustManagerFactory factory = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
factory.init(keystore);
TrustManager[] trustManagers = factory.getTrustManagers();
if (trustManagers != null && trustManagers.length > 0) {
for (int i = 0; i < trustManagers.length; i++) {
TrustManager trustManager = factory.getTrustManagers()[i];
if (trustManager instanceof X509TrustManager) {
delegate = (X509TrustManager) trustManager;
break;
}
}
}
if (delegate == null) {
throw new CertificateException("Cannot found any instance of X509TrustManager");
}
}

public X509Certificate[] getAcceptedIssuers() {
if (delegate == null) {
return null;
} else {
return delegate.getAcceptedIssuers();
}
}

public void checkClientTrusted(final X509Certificate[] c, final String a)
throws CertificateException {
if (delegate != null) {
delegate.checkClientTrusted(c, a);
} else {
throw new CertificateException("Unable to validate this certificate (delegate is null).");
}
}

public void checkServerTrusted(final X509Certificate[] c, final String a)
throws CertificateException {
if (delegate != null) {
// hardcoding test to be sure we are trying to validate the right certificate
if (c.length == 3 && c[0].getSerialNumber().toString(16).equals(BADCERTIFICATESIGNATURE)) {
c = new X509Certificate[] {c[0], c[1]};
}
delegate.checkServerTrusted(c, a);
} else {
throw new CertificateException("Unable to validate this certificate (delegate is null).");
}
}

private static final String BADCERTIFICATESIGNATURE = "xxxxxxxxxxxxxxxxxxxx";
}
With this implementation I'm sure that the my invalid certificate will continue to work, and it cannot be faked (as it could be with a completely blind TrustManager). But I can also expect that when the provider will update its certificate, everything will continue to work as expected because all other certificates are still validate the regular way.

Java and certificate's mess : blind SSL factory for commons httpClient

There are parts of Java which are not very "quick development" oriented. The side I want to illustrate here is dealing with SSL.

When the application is in early development stage, we don't want to bother with stuff like valid certificate. We don't want to get stuck for days because we haven't received valid development SSL certificate from Verisign or other "more trusted than me" monopoles. We just want to activate the "--no-check-certificate" à la wget, or "CURLOPT_SSL_VERIFYPEER" à la PHP.

Adding a new certificate in the cacert file is not so complicated, but it is not always possible on every computers (should developers have administrators rights on their computers ?), or break the debian pakage integrity and risk to be overridden at the next JRE update.

No, for development phase, a blind SSL factory is a good point for productivity. Simply DO NOT FORGET TO REMOVE IT in the integration or pre-production phase. Having valid and trusted SSL certificate in production is NOT an optional thing.

What is a blind SSL factory

A blind SSL factory is simply an SSL factory which will validate any certificate. Expirated, selfsigned, but also man-in-the-middled or forged certificates will be trusted.

Mike McKinney already explained how to create a blind SSL factory for LDAP connexions.

I will leverage his explanations to enable blind SSL factory for commons httpClient.

The start point is to create a TrustManager which will trust any certificate, and then give this TrustManager to the SSL factory. This will result in a blind SSL factory :)

Anonymous class that implements X509TrustManager :

class BlindX509TrustManager implements X509TrustManager
{
public X509Certificate[] getAcceptedIssuers()
{
return null;
}
public void checkClientTrusted(final X509Certificate[] c, final String a)
{
}
public void checkServerTrusted(final X509Certificate[] c, final String a)
{
}
}
Initializing the SSLContext to retrieve a SocketFactory :
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, new TrustManager[] { new BlindX509TrustManager() }, new java.security.SecureRandom());
SocketFactory blindFactory = sc.getSocketFactory();
And finally the BlindSSLProtocolSocketFactory which implements the needed ProtocolSocketFactory of httpClient :
public class BlindSSLProtocolSocketFactory implements SecureProtocolSocketFactory
{
public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException,
UnknownHostException
{
return blindFactory.createSocket(host, port, clientHost, clientPort);
}
public Socket createSocket(String host, int port, InetAddress localAddress, int localPort, HttpConnectionParams params)
throws IOException, UnknownHostException, ConnectTimeoutException
{
if (params == null)
{
throw new IllegalArgumentException("Parameters may not be null");
}
int timeout = params.getConnectionTimeout();
if (timeout == 0)
{
return createSocket(host, port, localAddress, localPort);
}
else
{
return ControllerThreadSocketFactory.createSocket(this, host, port, localAddress, localPort, timeout);
}
}
public Socket createSocket(String host, int port) throws IOException, UnknownHostException
{
return blindFactory.createSocket(host, port);
}
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException
{
return ((SSLSocketFactory) blindFactory).createSocket(socket, host, port, autoClose);
}
public boolean equals(Object obj)
{
return obj != null && obj.getClass().equals(getClass());
}
public int hashCode()
{
return getClass().hashCode();
}
}
Everything tied up together, put into the classpath of the application, will override the https protocol with the BlindSSLProtocolSocketFactory and thus allow your code to connect any certificate !
public class BlindSSLProtocolSocketFactory implements SecureProtocolSocketFactory
{
private static final Log LOG = LogFactory.getLog(BlindSSLProtocolSocketFactory.class);
public BlindSSLProtocolSocketFactory()
{
blindFactory = createBlindSocketFactory();
}
public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException,
UnknownHostException
{
return blindFactory.createSocket(host, port, clientHost, clientPort);
}

public Socket createSocket(String host, int port, InetAddress localAddress, int localPort, HttpConnectionParams params)
throws IOException, UnknownHostException, ConnectTimeoutException
{
if (params == null)
{
throw new IllegalArgumentException("Parameters may not be null");
}
int timeout = params.getConnectionTimeout();
if (timeout == 0)
{
return createSocket(host, port, localAddress, localPort);
}
else
{
return ControllerThreadSocketFactory.createSocket(this, host, port, localAddress, localPort, timeout);
}
}
public Socket createSocket(String host, int port) throws IOException, UnknownHostException
{
return blindFactory.createSocket(host, port);
}
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException
{
return ((SSLSocketFactory) blindFactory).createSocket(socket, host, port, autoClose);
}
public boolean equals(Object obj)
{
return obj != null && obj.getClass().equals(getClass());
}
public int hashCode()
{
return getClass().hashCode();
}
private SocketFactory blindFactory = null;
private static SocketFactory createBlindSocketFactory()
{
try
{
SSLContext context = SSLContext.getInstance("SSL");
context.init(null, new TrustManager[] { new BlindX509TrustManager() }, null);
return context.getSocketFactory();
}
catch (Exception e)
{
LOG.error(e.getMessage(), e);
throw new HttpClientError(e.toString());
}
}
}
And to regirster the new protocol as the default https protocol :
Protocol.registerProtocol("https", new Protocol("https", (ProtocolSocketFactory) new BlindSSLProtocolSocketFactory(), 443));

Please note that if you want only access a selfsigned certificate you have better to use EasySSLProtocolSocketFactory from httpClient contibs. It still validate the selfsigned certificate, so expired or bad certificate will be denied.

samedi, 17 janvier 2009

Des nombres à usage unique (nonce)

Afin de répondre à une problématique de transmission d'informations sensibles sur un réseau pas sûr (Internet donc...), une solution est l'utilisation de nonce, diminutif de number used once, nombre à usage unique.

Ils sont utilisé pour mettre du sel dans des informations sensibles qui seront ensuite hashées et transmises en clair sur le réseau. Mais leur efficacité n'est effective seulement si on a la garantie qu'un même nonce ne va jamais être utilisé 2 fois !

Exemple type : vous développez un service d'authentification, mais celui-ce ne peut se faire sur du SSL. Vous souhaitez toutefois que si une personne intercepte les informations, elle ne puisse pas les réutiliser pour s'identifier à son tour.

Le seul hashage du mot de passe ne suffisant pas car la personne qui a interceptée le mot de passe hashé pourra rejouer la séquence pour simuler sa connexion, une solution est de hasher le mot de passe concaténé au nonce (du style sha256("#" + mot de passe + "#" + nonce + "#")), puis de transmettre le nom d'utilisateur, le nonce utilisé et le résultat du hashage. Le serveur disposera ainsi de toutes les informations pour vérifier si le mot de passe utilisé pour le hashage était effectivement correcte ou pas. Les prochaines requêtes faites avec ce nonce seront systématiquement refusées.

C'est pour faciliter l'accès à ces nombres que je mets à disposition un petit service de génération de nombre à unique, accessible publiquement à l'adresse

http://nonce.noisette.ch/next

Chaque appel à cette url retourne un chaine de 32 caractères composées des lettres a-z, A-Z et les chiffres 0-9. Notez que les chaines retournées sont donc senible à la casse.

Le nombre de nonce possible est donc (26+26+10)^32 = 2.27 * 10^57, soit à peu près 6-parasite, ou beaucoup plus que d'atomes dans l'univers.

A utiliser sans modération pour toute application nécessitant la transmission d'informations sensibles sur un réseau non sécurisé.

Edit (06.01.2011) : GAE ne supporte plus le proxying, vous pouvez essayer l'app directement sur http://noisette-nonce.appspot.com/.

dimanche, 8 juin 2008

Implémentation d'un serveur proxy HTTP

La question à la base de cet article est la suivante :

Comment faire pour que mes concurrents ne soient pas au courant que j'observe (très) régulièrement une section particulière de leur site internet ?
La première étape bien évidemment est de la légitimer cette question. Comment un concurrent pourrait savoir que je consulte régulièrement son site ?

La réponse est simple. Prenons un cas concret pour illustrer : ELCA Informatique possède les plages d'adresses ips 193.72.144.0 - 193.72.147.255 (bloc /22 = 1024 ips, hosté chez Cablecom) et 193.73.238.0 - 193.73.238.255 (bloc /24 = 256 ips, hosté chez Sunrise)

Ce genre d'info se trouve très facilement via un mélange whois, traceroute et autres informations DNS.

Une fois qu'on a ces ips, il suffit de rechercher dans les logs les hits faits par ces ips et on pourra dresser un profil précis des intérêts des collaborateurs d'ELCA sur notre site.

Donc comment faire pour éviter cette traçabilité ?

Connexion Internet à adresse ip dynamique (xDSL)

Une première solution est d'utiliser une connexion xDSL pour toutes les connexions humaines à Internet, et de dédier exclusivement les plages d'ips fixes et repérables aux services de l'entreprise. Cette pratique est de plus en plus courante, surtout avec l'arrivée des connexions VDSL et de boitiers qui permettent d'agréger plusieurs lignes xDSL afin d'une part d'avoir un redondance, et d'autre part de partager la somme des débits des lignes entre les utilisateurs. Le seul détail concernant cette solution est de vérifier que l'adresse ip change réellement, faute de quoi il faudrait forcer ce changement.

Serveur proxy

Une autre solution, que je vais détailler ici car elle est plus intéressante d'un point de vue ingénierie informatique, est de faire passer le traffic des utilisateurs par un proxy. Par proxy j'entends tout type de connexion indirect à Internet, donc autant les serveurs proxy HTTP, SOCKS, etc que les réseaux de proxy comme par exemple Tor.
Le fait d'accéder à Internet par l'intermédiaire d'un proxy permet de visualiser un site avec une adresse ip qui n'est pas notre, mais appartient à un prestataire de ce genre de service. Cependant l'anonymité à 100% n'existe pas : il existera toujours un intermédiaire qui saura qui a accédé où. La confiance des intermédiaires est donc de mises dans cette configuration.

Il existe une grande quantité de serveur proxy logiciel de qualité, open source ou commercial, comme par exemple Squid, Privoxy, Apache mod_proxy, etc... Mais une fois de plus, l'utilisation d'un logiciel existant nous priverait du plaisir de développer de notre propre implémentation.

Principe de base

Le principe d'un serveur proxy est donc d'intercepter la requête d'un navigateur, d'ouvrir une connexion vers le site cible, transmettre la requête original, lire le résultat et finalement renvoyer ce résultat au navigateur.

Protocole HTTP

La partie centrale d'un serveur proxy HTTP réside sa compréhension du protocole HTTP. Celui-ci comporte des particularités qui vont être détaillées.

Le serveur proxy va recevoir une requête du navigateur, dont la première ligne comporte l'action à exécuter, l'url de la cible et une éventuelle version du protocole utilisé :
GET http://www.noisette.ch/ HTTP/1.0
Cette première ligne va donc directement nous renseigner sur le serveur cible à connecter. Le reste de la requête est composée de lignes de header, contenant diverses informations sur la requête comme le navigateur utilisé ou le referer (en français on dit comment ?).

Transfer de la requête au serveur cible

La prochaine étape est le transfert de la requête au serveur cible. A ce moment on peut éventuellement modifier ou enlever des headers du client, voir en ajouter d'autres. Plus précisément, il peut être intéressant de filtrer tous les headers des clients qui pourraient trahir l'utilisateur du proxy (information leakage).
Reste la lecture du résultat, qui n'est de loin pas la partie la plus triviale.

Ne pas bufferiser la lecture du résultat

L'implémentation d'un serveur proxy dans laquelle on lit le résultat en entier (via un lib du style httpclient) avant de l'envoier ensuite au navigateur n'est à mon avis pas bonne : le délai entre la requête et la réponse risque d'en prendre un coup, surtout pour les grosses pages.
L'idée est donc de retourner directement les bytes lus du serveur web au client.

En pseudo code, ca donne quelque chose du style :
 // fromWeb : InputStream du socket de la connexion vers le serveur source
// toBrowser : OutputStream du socket de la connexion vers le browser
while ((i = fromWeb.read(buffer)) != -1) {
toBrowser.write(buffer, 0, i);
toBrowser.flush();
}

Fermeture des connexions

La fermeture des connexions est très importante afin de libérer les ressources et ne pas avoir un serveur qui explose après 20 requêtes. Facile en théorie, la fermeture de la connexion est la sous partie non triviale. Dans la mesure où le serveur ne ferme pas systématiquement la connexion à la fin du transfert si il reçoit un header "Connection: keep-alive", il faut détecter quand la réponse du serveur est complète pour pouvoir fermer la connexion.

Dans une grande majorité des réponses, la taille de la réponse est spécifiée via un header "Content-Length: 82". Il suffit donc de lire autant de bytes que nécessaire et la connexion peut être fermée. Mais quelques réponses ne vont pas retourner de taille. C'est le cas par exemple lors du streaming d'un fichier. Dans ce cas il faut lire la réponse jusqu'à ce que le serveur ferme lui-même la connexion.

Transfer-Encoding: chunked

Une autre difficulté à la lecture de la réponse est l'encodage chunked. Dans ce mode de transfert, le serveur sépare la réponse en plusieurs parties, les chunks. Ca lui permet d'envoyer une partie de la réponse tout en calculant la suite. Par exemple Google utilise ce mode de réponse : les 2-3 premiers résultats sont dans un cache direct, et Google les retourne dans un premier chunk. Puis il doit aller chercher les 7-8 autres dans un deuxième cache, opération un peu plus coûteuse, qui se fait pendant que le navigateur du client lit ce premier chunk. Ca permet au serveur de ne pas attendre d'avoir entièrement cherché les 10 résultats, et de profiter du temps de transfert avec le client. Du point de vu du client, une fois qu'il a fini de lire le premier chunk, les suivants sont prêts à être lus. L'impression de fluidité est donc parfaite. Mais malheureusement peu d'implémentations tirent intelligemment profit de ce type de transfert.

En pratique, la réponse d'une encodage chunked donne quelque chose du style :
taille du chunk en hexa \r\n
données \r\n
taille du prochain chunk \r\n
données \r\n
...
0\r\n
\r\n
Pour lire la réponse, il faut donc lire la taille du chunk, lire les données du chunk, lire la taille du prochain chunk, etc jusqu'à ce qu'on ait un chunk de taille null, signifiant la fin de la réponse.
HTTP/1.1 200 OK¶
Date: Fri, 31 Dec 1999 23:59:59 GMT¶
Content-Type: text/plain¶
Transfer-Encoding: chunked¶

1a¶
abcdefghijklmnopqrstuvwxyz¶
10¶
1234567890abcdef¶

Caching

Prochaine étape dans l'implémentation d'un serveur de cache utile est efficace, c'est l'ajout d'une couche de cache au niveau du proxy : le serveur cible va retourner une information sur la date de dernière modification de la page. Cette indication peut être utilisé pour retourner la page mise en cache plutôt que de relire la réponse du serveur.

Authentification

Dernier point important si on met un serveur proxy sur internet, il faut ABSOLUMENT implémenter un ACL (access control list) afin de restreindre l'accès et l'utilisation du proxy aux personnes authentifiées uniquement. Faute de quoi votre tout beau serveur proxy sera vitre trouvé et utilisé par des personnes malveillantes pour spammer, consulter des sites illégaux etc.

Prochaine étape : au travail !

jeudi, 5 juin 2008

Mettre à mal un réseau d'entreprise avec Outlook (TM, R & CO)

Suite à une expérience involontaire d'un collègue de travail, on a eu 30 ordinateurs bloqués une quinzaine de minute :

Notre serveur SVN envoie un mail à chaque commit à une mailinglist interne, à laquelle une trentaine de collaborateurs est abonné. Le commit incriminé était certes quelque peu conséquent, une colonne ajoutée dans un fichier CSV de 5000 lignes, mais la conséquence était désastreuse !

Le mail faisait ~ 5Mb, et le diff du commit était formaté en HTML. Tous les collaborateurs qui ont cliqué sur le mail avec le preview activé n'ont pas eu d'autre choix que de tuer Outlook après avoir eu l'ordinateur bloqué un bon moment.

Moi qui n'était déjà pas un grand amoureux d'Outlook et de son immense capacité à accomplir des recherches performante parmi les mails, me voici comblé.

Mais pourquoi donc autant d'entreprise s'accrochent-ils à ce logiciel ? Dans tous les cas, si vous voulez forcer un peu la main pour le passage à un autre logiciel, vous savez ce qu'il reste à faire.

mardi, 29 janvier 2008

Vos données sont-elles réellement privées ?

Donnez des informations pour exister

Le principe premier du Web 2.0 est la participation des utilisateurs. Pour les sites sociaux comme MySpace et Facebook, où la participation se calcule en quantité d'informations données sur soi-même.

Collecte indirecte d'informations

L'aspect de collaboration entre les membres résulte en une masse d'information indirecte enregistrée souvent au dépend de la personne ciblée :

Facebook par exemple enregistre des informations sur vous par rapport aux détails que donnent vos amis (appelé Social map) : à chaque ajout de nouvel ami, vous avez la possibilité de spécifier comment vous l'avez connu. Grâce à ce système subtile, mon profile Facebook sait maintenant que j'ai fini l'EPFL en 2007, que je fais partie d'autres associations, ... alors que je n'ai rien renseigner sur ces sujets précis.

Le business des données personnelles


Le fait d'être encouragé à compléter son profil qui n'est complet qu'a 88% comme nous l'indique de manière bien visible Linkedin n'est pas entièrement désintéressé : le fait de connaitre précisément ses utilisateurs et d'en dresser des profils augmentent largement les marges sur les revenus publicitaires qui seront mieux ciblés donc plus percutant donc plus intéressant pour l'utilisateur. Revenus publicitaires qui sont, rappelons-le, le business model principal des ces sites sociaux.

Protection des données

L'aspect à ne pas perdre de vu si on utilise ce genre de sites, c'est que ça n'est pas une simple cas à cocher "Rendre mon profil privé" qui protègera réellement les données de votre profile : les données sont chez une personne tierce qui d'un jour à l'autre peut décider d'en faire autre chose. Ou, comme c'est le cas sur MySpace, une vulnérabilité du site permet d'outerpasser cette coche ridicule...

La réponse au titre du billet est clairement NON ! Le fait de déposer vos données sur un service externe quelconque ne les rendent pas privées. Donc soit vous ne mettez que des informations réellement publiques sur votre profil, soit vous faites confiance à 100% au service et vous ne vous demanderez pas d'où viennent les données sur vous retrouvées dans la nature...

Ils en parlent

dimanche, 9 décembre 2007

60 millions de hashes md5 !

Sur mon petit projet de revserse md5 lookup database, je viens de dépasser les 60'000'000 de hashes md5 dans la base de données interne, qui fait maintenant 4.2 GB.

En gros j'ai introduit dans la base tous les hashes de 4 lettres composés des caractères a-z, A-Z, 0-9, ., -, _, !, $, *, %, &, /, (, ), =, ?, #, @, +, ", [, ], {, }, et tous les hashes de 5 lettres composés de caractères a-z.

On remarque que sur l'échantillon généré les hashes sont bien uniformément répartis, mais on a une préférence non significative pour les hashes commençant pas 0x46.

La prochaine étape est les hashes de 5 à 7 lettres composés des caractéres a-z et 0-9, ce qui nous fait ~80'000'000'000 de hashes supplémentaires...
Ensuite je prendrais les dictionnaires Openoffice afin de générer les hashes de mots existants.

Happy md5 cracking sur md5.noisette.ch.

dimanche, 25 novembre 2007

Reverse MD5 databases aggregator

J'écrivais il y a à peu près une année un message à propos des reverses MD5 et de la problématique que ces bases de données posent.
Suite à un message au sujet de l'utilisation de Google comme base de données de hash (en anglais), j'ai remis à jour mon aggrégateur de revse MD5.

Il questionne maintenant pas moins de 8 bases de données en plus de sa base de données propre, et permet maintenant de recevoir un mail quand le hash est trouvé après-coup.

Une petite API est disponible pour intégrer le service dans vos propres applications, notemment pour déterminer si un mot de passe sera crackable en O(1).

Happy md5 cracking sur md5.noisette.ch.

mardi, 9 octobre 2007

Contourner le blocage du port SMTP

Votre provider bloque le port tcp/25 (SMTP) et donc vous ne pouvez pas envoyer de mails via votre serveur mail préféré ?

Une solution toute simple mais qui nécessite d'avoir un compte shell sur un serveur quelconque (qui peut être votre ordinateur local), qu'on va nommer srv_shell dans cet exemple, est de faire un tunnel entre votre ordinateur local, le srv_shell et votre serveur de mail préféré, appelé srv_mail ici.

La commande à exécuter sur votre ordinateur local est la suivante :

ssh -NL 2525:srv_mail:25 user@srv_shell
L'utilisation d'un clé ssh est encouragée.

Ensuite dans votre logiciel d'envoi de mail préféré il suffit de spécifier comme serveur sortant localhost sur le port 2525.

Et hop la sécurité le côté ennuyeux à ce type de sécurité mis en place par l'administrateur est bypassé. J'y reviendrai d'ailleurs dans un autre article, car il m'est arrivé récemment ce genre de mésaventure : ip fixe avec serveur Exchange + Storm Worm = blacklistage de l'ip...

Une deuxième solution pourrait venir à l'esprit si on possède son propre serveur dédié, mais je vais expliquer pourquoi cette solution est à bannir au plus vite.

La deuxième idée est d'utiliser un logiciel comme rinetd ou redir, qui ont pour but de rediriger le traffic adressé à un port vers un autre. Donc tout ce qui arriverait sur le port 2525 de srv_shell serait redirigé vers le port 25 du srv_mail. Le problème de cette solution est quand les deux serveurs srv_shell et srv_mail sont les mêmes, la connexion sur le port 25 est redirigé par la même machine, le service mail va donc recevoir une connexion depuis localhost. Et comme la très grande majorité des services mails sont configurés pour que localhost puisse envoyer sans restrictions des mails, cette configuration ouvre un open-relay. Quiconque trouve ce port 2525 sur votre serveur pourra l'utilisé pour spammer le monde entier -> la redirection de port sur un serveur mail est donc à bannir.

lundi, 8 octobre 2007

Création manuelle d'un botnet "localisé"

Une méchante idée m'est venue en lisant un article de Slashdot : Cracked Linux Boxes Used to Wield Windows Botnets.

Afin de se composer un réseau de botnets, il serait extrêmement simple à une personne malveillante de vendre des routers ADSL modifiés (rootkités) sur des sites comme Ebay ou Ricardo en Suisse. L'utilisation pour l'acheteur pourrait être strictement similaire, au point qu'il ne se douterait pas que quelqu'un d'autre à la main sur son nouveau routeur.

Dans cet ordre d'idée, on voit de plus en plus de router ADSL se faire rootkiter à la suite d'un piratage d'un ordinateur. La raison principale : un mot de passe par défaut... Idem pour les connexions wireless : on la casse (l'histoire d'une journée pour du WEP avec du matériel correct) avant de s'attaquer au router !

En résumé, il serait presque rentable de se monter un botnet "localisé" et persistant, car placé dans des endroits stratégiques.

Deux petites recommandations pour éviter toutes mauvaises surprises : TOUJOURS modifier le mot de passe de vos routeurs (quitte à le noter dessous, le pirates devant hacker votre webcam et le robot téléguidé du petit frère pour réussir à placer le mot de passe dans le champs de vision de la webcam), et abandonner les connexions wireless...

vendredi, 19 janvier 2007

De la sécurité des sessions PHP

Dans la majorité des espaces requierant une authentification sur un site web, le soin du suivi de l'utilisateur est laissé aux sessions, ces petits cookies qui viennent se placer chez le client afin de permettre à l'application web de le reconnaitre lors du passage à la page suivante.

Ce modèle de sécurité a du être imaginé à cause de la nature "connexionless" du protocole HTTP, c'est-à-dire la fermeture de la connexion TCP au serveur entre 2 chargements de page consécutifs (contrairement aux modèles de connexions "continues").

Malheureusement différentes techniques pour voler ces informations de sessions existent, elles se nomment credential token stealing, et sont souvent réalisables grâce à des failles de type XSS.

Cet article va expliquer quel est le point faible des sessions, ainsi que présenter une solution pour en améliorer la sécurité. Un exemple d'implémentation sera une fois de plus donné en PHP.

Problèmes des sessions

Le problème lié aux sessions est que l'identité de l'utilisateur, une fois identifiée, repose entièrement sur ce cookie. Si une personne malveillante parvient à obtenir la valeur du cookie, elle pourra alors se faire passer pour la personne légitime aux yeux de l'application.

Meilleure emprunte (fingerprint)

Une première amélioration est d'associer la valeur du cookie à d'autres éléments qu'une personne malveillante ne peut pas modifier : l'ip de connexion, la signature du navigateur, etc...
Tous ces éléments combinés ensemble donnent ce qu'on appelle l'emprunte de la session, et tous ces éléments sont nécessaires pour pouvoir usurper une identité. La principale difficulté est l'adresse ip de connexion, mais si la personne malveillante est sur le même sous-réseau que la personne légitime, l'adresse ip n'est plus un problème. Autre désavantage d'un fingerprinting étendu, si la personne légitime est sur une connexion à adresse ip dynamique, elle devra se réauthentifier à chaque changement d'adresse ip.

ID de session temporaire

Une autre amélioration est la regénération dynamique de la valeur du cookie. A chaque nouvelle connexion, on test si l'utilisateur est légitime, et on génère une nouvelle valeur qu'on lui envoie.
Si une personne malveillante arrive à voler un cookie, sa valeur ne sera que temporaire et dès le prochain chargement de page, la valeur volée devient obsolète.
Mais cela est aussi vrai à l'inverse, si la personne malveillante arrive à usurper l'identité avant que la personne légitime ne redemande une page, c'est la personne légitime qui sera déconnectée du site.

Implémentation en PHP

Une combinaison des 2 méthodes améliorera la sécurité des sessions, sans pour autant la rendre infaillible.

Voici une petite implémentation en PHP :

<?php
/*
* Inspirated from SecureSession class
* initially written by Vagharshak Tozalakyan <vagh@armdex.com>
*/
class SecureSession {

private $_check_browser;
private $_check_ip_blocks = 0;
private $_padding = '*ftt56+g zwc%&gh7/3-lf%254*6c_qm';
private $_regenerate_id = true;
private $_session_var_name = __CLASS__;

public function _construct($check_browser = true,
$check_ip_block = 0, $regenerate_id = true)
{
$this->_check_browser = $check_browser;
$this->_check_ip_block = $check_ip_block;
$this->_regenerate_id = $regenerate_id;

$_SESSION[$this->_session_var_name] = $this->_fingerprint();
$this->_regenerateId();
}

public function isValid()
{
$this->_regenerateId();
return (isset($_SESSION[$this->_session_var_name])
&& $_SESSION[$this->_session_var_name] == $this->_fingerprint());
}

private function _fingerprint()
{
$fingerprint = "";
if ($this->_check_browser) {
$fingerprint .= $_SERVER['HTTP_USER_AGENT'];
}
if ($this->_check_ip_blocks) {
$num_blocks = min(abs(intval($this->check_ip_blocks)), 4);
$blocks = explode('.', $_SERVER['REMOTE_ADDR']);
for ($i=0; $i<$num_blocks; $i++) {
$fingerprint .= $blocks[$i] . '.';
}
}
return sha1($fingerprint . $this->_padding);
}

private function _regenerateId()
{
if ($this->_regenerate_id && function_exists('session_regenerate_id')) {
session_regenerate_id(true);
}
}
}

?>

mardi, 16 janvier 2007

Les 7 règles fondamentales en sécurité

Très souvent oubliées ou minimisées, voici les 7 règles fondamentales en sécurité :

  1. Least privilege : on ne donne que le privilege minimum
  2. Defense in depth : on protége à tous les niveaux
  3. Choke point : on emprunte qu’un seul chemin pour aller au but
  4. Weakest link : la sécurité globale est égale à la sécurité du maillon le plus faible
  5. Default deny : plutôt que d’énumérer les cas pas permis, au risque d’en oublier, on énumère les cas permis.
  6. User participation : on éduque les utilisateurs par rapport à la sécurité
  7. Simplicity : on applique le principe de simplicité
A appliquer sans modération !

dimanche, 14 janvier 2007

Form flooding

Les formulaires sont un des points vulnérables d'une application web, car c'est notamment à travers eux que les "clients" peuvent injecter des données dans l'application.

De plus, même si le formulaire est suffisamment protégé contre l'injection de données, qu'elle soit XSS, SQL, string format ou autre, le formulaire reste vulnérable à un flood :

Comment réagit votre formulaire si un client bien authentifié décide de poster 1'000'000 de fois le formulaire ?


Ce genre d'attaque peut avoir des effets très néfastes...

Il y a plusieurs méthodes pour s'en prémunir, et je vais en présenter une qui, contrairement au captcha, ne requiert pas d'intervention de la part de l'utilisateur (on ne peut pas faire de captcha dans un webservice...), mais protège tout de même notre formulaire.

On va donc attribuer à chaque formulaire un identifiant unique, qui est entré dans une table (ou en var de session) conjointement avec un timestamp. L'identifiant nous permettra d'éviter (au pire de remarquer) la soumission multiple du même formulaire, et grâce au timestamp, nous pourrons mesurer le temps entre le chargement de la page contenant le formulaire et son renvoi au serveur, temps qui ne devrait pas être inférieur à une voir plusieurs secondes pour un utilisateur humain, selon la taille du formulaire.

En recevant le formulaire, le serveur peut donc contrôler si :

1. Le formulaire est valide, i.e. l'identifiant du formulaire est valide
2. Le formulaire n'a pas été envoyé plusieurs fois.
3. Le délai de soumission du formulaire n'est pas trop élevé pour une session donnée.

L'implémentation de cette solution est elle aussi multiple, mais j'en donne un exemple ci-dessous :


__toString(); ?> ...
*
* if (isset($_POST)) {
* $canary = Form_Protector::factory($_POST);
* if (!$canary->is_valid()) {
* if ($canary->exists()) {
* // le canary n'existait pas dans la db, afficher un message d'erreur et recharger la page
* } else {
* // le formulaire a été posté trop rapidement, on peut donc compter le nombre de soumissions durant les 5 dernières minutes, et prendre une des actions suivante
* // --> soit on blacklist l'ip un moment,
* // --> soit on sleep(30) pour temporiser (tarpitting)
* }
* } else {
* // le canary est valide, tout est bien dans le meilleur des mondes.
* }
* }
*/

define("_TIME_TO_SUBMIT_FORM", 2); // temps que l'utilisateur fait pour submiter un formulaire
class Form_Protector {

protected $_canary;
protected $_ip;
protected $_date_request;
protected $_date_request;
protected $_exists = false;

public static $input_name = 'form_protector_canary';

protected function __construct($canary = 0, $ip = "") {
$this->_ip = $_SERVER['REMOTE_ADDR'];
if ($canary === 0) {
$this->_canary = rand();
$this->_insert();
} else {
$this->_canary = $canary;
if ($ip !== "") $this->_ip = $ip;
$this->_load();
}
}

protected function _load() {
$query = sprintf("SELECT * FROM form_protector
WHERE canary = %d AND ip = '%s'" . int_val($this->_canary),
mysql_real_secape($this->_ip));
// query,
// load $this->_date_request, $this->_date_request = time();
// si pas un champ est retourné, on passe $this->_exists à true; et on UPDATE ... SET date_response = $this->_date_request
}

protected function _insert() {
$this->_date_request = time();
$query = sprintf("INSERT INTO form_protector (canary, ip, date_request) VALUES (%d, %s, %d)", int_val($this->_canary), mysql_real_secape($this->_ip), $this->_date_request);
// query
// si pas d'erreur : $this->_exists = true;
}

public function is_valid() {
if ($this->_date_request + _TIME_TO_SUBMIT_FORM <>_exists;
}
public function __toString() {
return '<input value="' . $this->_canary . '" name="form_protector_canary" type="hidden">';
}

public static factory($params = NULL) {
if (is_array($params) && isset($params[self::$name])) {
$canary = $params[self::$name];
} else {
if ($params === NULL) {
$canary = 0;
} else {
$canary = $params;
}
}
return new Form_protector($canary);
}
}

?>