Le WAF du pauvre pour les riches !

Sgos 6.4.4.1

Ils l’ont fait

Ca y est, enfin Mr BlueCoat qui vend quand même des jouets dit “Professionnels” de reverse et proxy HTTP (Comprenez de 3000 à 100000€ la boite) vient de sortir ses premières “fonctions” WAF. Et oui il aura fallu attendre fin 2012 pour que ce magnifique “player” du Magic Quadrant de mes couilles se hisse au rang de Web Application Firewall. Qui ne se rappelle pas des merveilleuses promesses que laisse présager la plaquette commerciale:

Web Application Firewall For Untrusted Web Environments-Aug11-finalv2

Si les fonctionnalités de la chose en tant que reverse proxy sont indéniables, son ‘WAF’ flambant neuf est affligeant, pathétique, grotesque, lamentable, immature… Les adjectifs me manquent. Mais regardons plutôt la release note.

Et oui, vous ne revez pas en gise de “WAF” on vous préconise d’aller voir 2 articles de la knownledge base. Et voila, le “WAF” tant attendu vous promettant de bloquer XSS et SQLi n’est qu’un pauvre jeu de regex à installer soi-même dans la configuration de la bête.

https://kb.bluecoat.com/index?page=content&id=FAQ2147 

https://kb.bluecoat.com/index?page=content&id=FAQ2148

Mais regardons déja celles préconisées pour les SQLi.

SQLi (Super Qualitée du Layer Inefficace)


<proxy>
condition=sql_injection_attack_pattern deny
allow

define condition sql_injection_attack_pattern
; Simple detection of main 4 keywords followed by a space character
url.query.regex="(select|insert|update|delete)\+"

; Use of > or < operators (for attacks like "or 2 > 1") (hex or ascii)
url.query.regex="(>|<)"

; Use of the hex "=" (for attacks like "or 1 = 1"). We only check hex for now
; as we don't want to fail on normal query strings like "www.google.com?search=sql"
raw_url.query.regex="%3D" ; hex encoded "="

; Detect References to well known database names
url.query.regex="(Tempdb|master|Model|MsDB|dbo)\."
url.query.regex="\[(Tempdb|master|Model|MsDB|dbo)\]\."

; Detect SQL ALTER commands
; ";" + zero or more spaces + "ALTER" + 1 or more spaces + known parameters + space:
url.query.regex=";\+*ALTER\++(CLUSTER|DATABASE|DIMENSION|DISKGROUP|FUNCTION|INDEX|INDEXTYPE|JAVA|MATERIALIZED\++VIEW|OPERATOR|OUTLINE|PACKAGE|PROCEDURE|PROFILE|RESOURCE\++COST|ROLE|ROLLBACK\++SEGMENT|SEQUENCE|SESSION|SYSTEM|TABLE|TABL ESPACE|TRIGGER|TYPE|USER|VIEW)\+"

; Detect SQL CREATE commands
; ";" + zero or more spaces + "CREATE" + 1 or more spaces + known parameters + space:
url.query.regex=";\+*CREATE\++(CLUSTER|CONTEXT|CONTROLFILE|DATABASE|DIMENSION|DISKGROUP|DIRECTORY|FUNCTION|INDEX|INDEXTYPE|JAVA|LIBRARY|MATERIALIZED\++VIEW|OPERATOR|OUTLINE|PACKAGE|PFILE|PROCEDURE|PROFILE|ROLE|ROLLBACK\++SEGMENT|SCHEM A|SEQUENCE|SPFILE|SYNONYM|TABLE|TABLESPACE|TEMPORARY\++TABLESPACE|TRIGGER|TYPE|USER|VIEW)\+"

; Detect SQL DROP commands
; ";" + zero or more spaces + "DROP" + 1 or more spaces + known parameters + space:
url.query.regex=";\+*DROP\++(CLUSTER|CONTEXT|DATABASE|DIMENSION|DIRECTORY|DISKGROUP|FUNCTION|INDEX|INDEXTYPE|JAVA|LIBRARY|MATERIALIZED\++VIEW|OPERATOR|OUT LINE|PACKAGE|PROCEDURE|PROFILE|ROLE|ROLLBACK\++SEGMENT|SEQUENCE|SYNONYM|TABLE|TA BLESPACE|TRIGGER|TYPE|USER|VIEW)\+"

; Detect SQL GRANT commands
; ";" + zero or more spaces + "GRANT" + 1 or more spaces + any characters (except & which would be the start of a new url query parameter) + 1 or more spaces + "TO"
url.query.regex=";\+*GRANT\+[^&]+\++TO\+"

; Detect SQL LOCK commands
; Lock table is the only lock command I found in Oracle (and I found no mention in SQL Server).
url.query.regex=";\+*LOCK\++TABLE\+"

; Detect SQL EXEC/EXECUTE commands
; Prevent exec statements
url.query.regex=";\+*EXEC(UTE)?\++"

; Prevent commonly named stored procedures (which don't always require the EXECUTE statement to run)
url.query.regex=";\+*(sp|xp)_"

; Detect SQL RELOAD commands
; RELOAD - have the database engine re-read the grant tables. Note: Unable to find syntax in online search just a mention of the command
url.query.regex=";\+*RELOAD\+"

; Detect SQL Server SHUTDOWN commands
url.query.regex=";\+*SHUTDOWN"

; Need to get rid of SQL declare/set statements or it is easy to bypass our protection
; i.e.
; DECLARE @x varchar(1000);
; DECLARE @y varchar(1000);
; DECLARE @stmt varchar(1000);
; SET @x="dro";
; SET @y="p table users";
; SET @stmt="@x@y";
; exec (@stmt)
url.query.regex=";\+*(DECLARE|SET)\++@"

end

Première chose à signaler, mais la knowledge base le signale bien et ne le cache pas. Cela ne protège ni les posts, ni les cookies. En fait le test qui est fait est “url.query.regex=” cela veut dire que cela ne testera que la partie query d’une requête. Un petit exemple

http://thanatos.trollprod.org/dummy.php?param=1

Seule la partie en gras (?param=1) sera passée à la moulinette. le Constat est accablant, en plus des POSTs non traités, cela ne protège pas non plus les sites web RESTFull et bien sur aucun des headers présentés par le client.

Mais continuons, peut-on en plus quand même injecter dans un GET normal avec des paramêtres ?

Déjà, la majeure partie de la policy de sécurité se focalise sur du MSSQL, car tout est fait pour l’inclusion de commande après une requête ( première requête…; et seconde SQLi) et des commandes MSSQL, si vous avez autre chose, passez votre chemin. Ça ne marche pas sur MySQL ou ORACLE cela.

url.query.regex=";\+*RELOAD\+"

Premier TIPS certes un Bluecoat fait de base ses regexes de facons insensible à la casse, mais il ne connais pas l’UTF, ni L’UNICODE, il ne sait décoder que l’URL Encoding. Je vois déja les premiers qui sourient dans le fond. Et oui pour injecter dans du MSSQL cela ne sert pas a grand chose, on a l’embarras du choix pour contourner toutes ces regexes.

%S%i% %v%o%u%s% %v%o%y%e%z% %c%e% %q%u%e% %j%e% %v%e%u%x %d%i%r%e% %!

Mais, ensuite, pour les autre gens, ceux avec du MySQL ou autre, il reste que 2 éceuils.

Premièrement tout “=” URL encodé en ‘%3D’ est bloqué ainsi que < et > dans toutes ses formes.

; Use of the hex "=" (for attacks like "or 1 = 1"). We only check hex for now
; as we don't want to fail on normal query strings like "www.google.com?search=sql"
raw_url.query.regex="%3D" ; hex encoded "="

; Use of > or < operators (for attacks like "or 2 > 1") (hex or ascii)
url.query.regex="(>|<)"

Je rappelle qu’il est possible de placer une SQLi sans utiliser un ‘=’ ou un ‘<‘ et ‘>’. Rappellez vous toujours les alternatives qui existent pour blind SQLi, soyez inventifs:

and 1
or 1
and 1=1
and 2<3
and 'a'='a'
and 'a'<>'b'
and char(32)=' '
and 3<=2
and 5<=>4
and 5<=>5
and 5 is null
or 5 is not null

Et en plus, un opérateur comme “LIKE” “RLIKE” fait aussi l’affaire, il y a même un tamper SQLMap pour cela.

https://github.com/sqlmapproject/sqlmap/blob/master/tamper/equaltolike.py

Deuxième souci, le select est attrapé:

define condition sql_injection_attack_pattern
; Simple detection of main 4 keywords followed by a space character
url.query.regex="(select|insert|update|delete)\+"

Pour comprendre le ‘+’ à la fin de la première regex, il faut savoir qu’un BlueCoat SG n’url décode pas tout comme il devrait (Pour des raisons de sécurité dit-on, moi je pense à de la fénéantise du coder qui n’aime pas parser)

un ‘%20’ (Espace) est converti en ‘%2B’ (+) dans son preprocesseur. Ce qui rend la regex plus compréhensible.

select(space) ou select(+) ou insert(space) etc...

Malheureusement c’est sans compter ce fameux décodeur d’URL.. il fait d’autres choses plus drôles encore, de ‘%00’ à ‘%1F’ ce n’est pas décodé du tout, ni transformé en ‘+’ d’ailleur. On se retrouve donc avec des url soit disant décodée mais pas complêtement.

Par exemple un ‘select%0A’ (CR) ne matche plus la patterne et pourtant c’est bien une requête MySQL valide.

il y a aussi un tamper SQLMap pour cela

https://github.com/sqlmapproject/sqlmap/blob/master/tamper/space2mysqlblank.py

Allez on a constaté l’affligeance du filtre SQLi, regardons les filtres XSS

XSS (Cross Site Stupide)

<proxy>
condition=xss_attack_pattern_in_url deny
allow

define condition xss_attack_pattern_in_url
; Pattern: "<" + zero or more spaces + "known xss attack tags" + optional attributes + ">"
; This will detect:
; "<script>", "< script >", "<script src="...">"
; But will not consider the following as attacks:
; "<scripty>", "script", "<script", "script>"
url.query.regex="<\+*(script|object|applet|embed)(\++[^>]*)?>"
end

Ha bin non, pas les filtres, mais LE filtre ! Là aussi dans la Knowledge base est rigolote, il disent au hacker qu’il peut utiliser “body onload”. Mais je trouve qu’ils pourraient donner plus de tips. Quid de Iframe, OnError, OnMouse…, et là aussi dans certain navigateurs <‘TAB’script çà gagne, et ‘TAB’ c’est ‘%09’, merde ca matche pas non plus la patterne ;) Si on active SQLi avec il y a aussi une rêgle qui bloque les ‘<‘ et ‘>’ et qui complique. Cela sera certes efficace pour votre petit site wordpress, mais je défie quiconque de tenir cette rêgle sur un environnement un peu plus velus (et je rappelle c’est pas donné ce joujou donc c’est pas pour un pauvre wordpress).

Mot Maux final

Pensons aussi à tous ceux non spécialistes qui vont implémenter ces rêgles telles quelles. Dans une rule proxy bluecoat DENY laissera passer si un autre layer proxy dispose d’une rêgle qui accepte quelque chose. C’est FORCE_DENY qu’il faudrait mettre pour parer à toute erreur de configuration.

Bref, on est quasiment en 2013, les commerciaux vont commencer à vous dire que BlueCoat intègre désormais un WAF. Ne les croyez pas ! Premièrement parce que le niveau est largement bien en dessous de ce qu’on peut attendre d’un WAF dit ‘Professionnel’. Secundo parce que c’est à des années lumières de ce que fait la concurrence, et même pour un WAF négatif; Tertio parce que en production dans la vraie vie, je ne souhaite à personne de devoir gérer les faux positifs et les exceptions dans des conditions aussi spartiates.

Et rappelez vous l’adage “Ce n’est pas en les bloquant qu’on les arrêtera !

A+

Posted in WebSecurity | Tagged , , | Leave a comment

The command prompt has been disabled by your administrator.

But reenabled by a fucking hacker.

Didier Stevens a un workaround étonnant. Il a compilé en DLL la console et le regedit provenant de reactOs (Un projet d’implementation d’un clone de Windows libre) et appelle cette dll ‘tout naturellement’ incluse dans son fichier Excel via une macro.

Bilan, un document Excel lancant un shell ou un regedit (Et potentiellement ce que vous voulez) Il a même réalisé un task manager en Excel. Tout cela sans quitter Excel bien sur, et le process reste Excel.

C’est magnifiquement moche, mais probablement utile pour vos pentests.

Posted in Desktop, Hacking | Tagged , , , | Leave a comment

Steve Jobs a inventé l’iPAD

Le prochain qui me dit cela, j’y met ma main dans la gueule. Récemment j’ai dû nettoyer le grenier. Voilà ce que j’y ai dégoté. Un joli iPAD IBM de 20 ans d’âge !!

Posted in BlaBla | Tagged , , | Leave a comment

Tip’s, Déchirer un ⊕ Shifté sur du code.

Où comment décrypter le payload d’un packer qui XOR et qui Shifte (ou pas) sur du code.

Récemment je me suis fait humilier sur un crackme. Le concept était simple, une bonne partie du code était packé avec un XOR shifté , et la clef d’encryption étant bien sur le mot de passe du crackme. La fonction de décryptage était du genre (Au format AT&T, oui moi aussi j’aime pas !):

A l’appel de la fonction :
Dans EAX la taille de ce que je doit décrypter.
Dans ESI l’offset de la stringZ contenant le Password.
Dans EBX l’offset des data à décrypter

mov %esi,%ecx ; offset du pwd dans ECX aussi
LOOP1: cmp $0x0,%eax ; test a t'on finis de décrypter ?
je FINI ; Si oui fini...on rentre
cmpb $0x0,(%ecx) ; Sinon est t'on au bout du password
jne LOOP2
mov %esi,%ecx ; Si on est au bout du password on se remet au début
LOOP2: mov (%ecx),%dl
xor %dl,(%ebx) ; Decryptage via Xor data[j]=data[j] ⊕ password[i]
addb $0xAB,(%ebx) ; le Shift, data[j]=data[j] + 0xAB
dec %eax ; Ce qu'il reste a faire - 1
inc %ebx ; j++ pour data[j]
inc %ecx ; i++ pour password[i]
jmp LOOP1
FINI: ret

Normalement, sur du code on cherche des aplats de 00 à décrypter, généralement c’est des réservations de buffers, il n’y a qu’a faire le shift, car un XOR avec un 0 ca donne la valeur avant le XOR, ici donc il suffisait ajouter 0xab à tous les octets et regarder ce qui est printable.
Mais là rien de rien ne sautait au visage. Je l’ai labouré, labouré et en fait la solution est ridicule. La solution est tellement simple quand on connait le truc que je ne peux pas vous laisser l’ignorer. allons y…

Tous code compilé en C, C++ ou autre language génère des champs de NOP entre les fonctions. C’est fait pour aligner les instructions en mémoire pour d’obscures raisons de performances. L’espace inutile est donc bourré d’instructions NOP (Pour les plus distraits NOP est une Instruction qui ne fait rien et qui prend un byte). Mais cela veut dire aussi qu’on se retrouve avec des gros aplat de NOP Note, l’instruction Nop fait une taille de 1 Byte et c’est 0x90 en hexa.

Allez un petit TP pour comprendre, Prenons un programme C très élaboré.

#include <stdio.h>

int main () {
printf("Hello World\n");
}

On le compile, et on ne regarde que la partie “code executable” du fichier ELF obtenu.

cyanide:/tmp$ gcc hello.c -o hello ; strip hello
cyanide:/tmp$ readelf hello -S | grep X
[12] .init PROGBITS 08048298 000298 000030 00 AX 0 0 4
[13] .plt   PROGBITS 080482c8 0002c8 000040 04 AX 0 0 4
[14] .text PROGBITS 08048310 000310 00016c 00 AX 0 0 16
[15] .fini PROGBITS 0804847c 00047c 00001c 00 AX 0 0 4
W (write), A (alloc), X (execute), M (merge), S (strings)

Extractons ensuite juste les segments exécutables de ce formidable programme (Ça tombe bien ils sont contigus).

0x298 = 664 en décimal
0x30 + 0x40 + 0x16c + 0x1c0 = 0x39c donc 924 en décimal

cyanide:/tmp$ dd if=hello of=helloX bs=1 skip=664 count=924
924+0 records in
924+0 records out
924 bytes (924 B) copied, 0.00771259 s, 120 kB/s

Et cherchons y un champs de “NOP NOP”

cyanide:/tmp$ hexdump helloX -C | grep "90 90"
00000090 c4 83 04 08 e8 b7 ff ff ff f4 90 90 90 90 90 90 |................|
000000a0 90 90 90 90 90 90 90 90 55 89 e5 53 83 ec 04 80 |........U..S....|
00000140 ff c9 c3 90 90 90 90 90 55 89 e5 5d c3 8d 74 26 |........U..]..t&|
000001b0 5d c3 8b 1c 24 c3 90 90 55 89 e5 53 83 ec 04 a1 |]...$...U..S....|
000001e0 5d c3 90 90 55 89 e5 53 83 ec 04 e8 00 00 00 00 |]...U..S........|

Regardons le code maintenant

gdb$ x/30i 0x8048310
0x8048310 : xor %ebp,%ebp
0x8048312 : pop %esi
0x8048313 : mov %esp,%ecx
0x8048315 : and $0xfffffff0,%esp
0x8048318 : push %eax
0x8048319 : push %esp
0x804831a : push %edx
0x804831b : push $0x80483e0
0x8048320 : push $0x80483f0
0x8048325 : push %ecx
0x8048326 : push %esi
0x8048327 : push $0x80483c4
0x804832c : call 0x80482e8
0x8048331 : hlt
0x8048332 : nop
0x8048333 : nop
0x8048334 : nop
0x8048335 : nop
0x8048336 : nop
0x8048337 : nop
0x8048338 : nop
0x8048339 : nop
0x804833a : nop
0x804833b : nop
0x804833c : nop
0x804833d : nop
0x804833e : nop
0x804833f : nop
0x8048340 : push %ebp
0x8048341 : mov %esp,%ebp

On vois très bien le champ de 14 NOP d’affilés qui bourre la fonction _start jusqu’à la fonction destructor.

Idem derrière le code principal de notre très élaboré programme.

gdb$ x/25i 0x080483c4
0x80483c4 : push ebp
0x80483c5 : mov ebp,esp
0x80483c7 : and esp,0xfffffff0
0x80483ca : sub esp,0x10
0x80483cd : mov DWORD PTR [esp],0x80484a0
0x80483d4 : call 0x80482f8 0x80483d9 : leave
0x80483da : ret
0x80483db: nop
0x80483dc: nop
0x80483dd: nop
0x80483de: nop
0x80483df: nop
0x80483e0 : push ebp
0x80483e1 : mov ebp,esp

Rien que ce tout petit programme dispose de champs de NOP, Et l’histoire se répète chez tous le monde, cherchons du “NOP NOP” dans un hexdump de /bin/bash

cyanide:/$ hexdump /bin/bash -C | grep "90 90" | wc -l
241

Il y en a partout..241 champs, et du gros, là aussi des exemples 14 NOP d’affilés
cyanide:/$ hexdump /bin/bash -C | grep "90 90" | tail -n 10
000a4ad0 ff c9 c3 90 90 90 90 90 90 90 90 90 90 90 90 90 |................|
000a5840 89 45 c8 e9 08 fd ff ff 90 90 90 90 90 90 90 90 |.E..............|
000a5b40 c7 04 24 7d 00 00 00 e8 c4 c1 fe ff c9 c3 90 90 |..$}............|
000a6d90 ff ff 90 90 90 90 90 90 90 90 90 90 90 90 90 90 |................|
000a6f10 ff ff 90 90 90 90 90 90 90 90 90 90 90 90 90 90 |................|
000a70e0 39 d7 75 bf 2b 45 cc 1b 55 c8 eb b7 90 90 90 90 |9.u.+E..U.......|
000a7210 fa 83 c4 10 5e 5f 5d c3 90 90 90 90 90 90 90 90 |....^_].........|
000a7350 e9 1d ff ff ff 90 90 90 90 90 90 90 90 90 90 90 |................|
000a73c0 72 de 83 c4 0c 5b 5e 5f 5d c3 8b 1c 24 c3 90 90 |r....[^_]...$...|
000a73f0 f8 ff 75 f4 83 c4 04 5b 5d c3 90 90 55 89 e5 53 |..u....[]...U..S|

Et même sur de l’executable Windows cela est dispo. Par contre, je n’ai pas trouvé cette joyeusetée sur de l’executable osX… Etrange (Ou j’ai mal cherché).

Cela rend du coup l’exploitation plus simple. ⊕ c’est Xor hein…
Si la décryption d’un NOP est
(KEY⊕DATA)+0xAB = NOP
alors..
KEY = DATA⊕(NOP-0xAB)

Prenons un exemple concret pour les moins à l’aise sur un byte. Si j’ai crypté un NOP (0x90) avec la clef “A” (0x41) et que j’ai shifté préalablement mon NOP en soustrayant 0xab, ma data cryptée est 0xa4
Note, le “% 256” c’est juste pour dire a python de rester travailler sur du 8 bits et “^” c’est un xor.

L’encryption qui a été réalisée est :

>>> hex((((0x90 - 0xab ) % 256 ) ^ 0x41 ) %256)
'0xa4'

La décryption dans le crackme sera :
>>> hex((((0xa4 ^ 0x41 ) % 256 ) + 0xab ) %256)
'0x90'

Et donc si on applique ce que l’on a dit pour trouver la clef
>>> hex((((0x90 - 0xab ) % 256 ) ^ 0xa4 ) %256)
'0x41'

Moralité, c’est un crackme, on va donc xorer comme un boeuf les data cryptées avec (0x90 – 0xab), soit 0xe5 et on verra apparaitre en chaines ascii visible notre mot de passe. On ne gardera que les chaine de plus de 8 charactères.

On y vas…

cyanide:/tmp$ ./xor8.py dataxored.raw 0xe5 | strings -n 8 | sort | uniq -c | sort -n

4 @SsW0rDP@SsW0rD
5 +?@SsW0rD
5 +?@SsW0rDP@SsW0rD
5 80rDP@SsW0rD
5 P@SsW0rD
6 +?/SsW0rD
6 SsW0rDP@SsW0rD
7 0rDP@SsW0rD
101 sW0rDP@SsW0rD

Il ne faut pas sortir de Saint Cyr pour en déduire le bon mot de passe, de plus c’est un bête XOR il existe aussi des trucs pour déterminer la taille de la clef, mais cela fera parti d’un autre post.

Voila ce qui s’apparente en cryptanalyse à l’attaque sur le clair connu (mais bon… parler de cryptanalyse sur un XOR c’est quand même fortement pompeux)

Donc la prochaines fois que vous croiserez un cryptage par XOR sur du code dans un crackme ou un packer, tentez la décryption sur des zones de 00 ou des zones de 0x90 avant de vous fatiguer à debugger pour chercher où est caché la clef d’encryption..

A+

Posted in Challenge, Crypto, Reverse | Tagged , , | 1 Comment

Toi aussi Harden ton WordPress

Cloacking WordPress

Après l’installation, comment sécuriser son wordpress ?

2 axes;

  1. Premièrement rendre pénible la détection de la version et des plugins.
  2. Sécuriser les access admin.

Je repasserai pas sur le système, on part du principe vous maintenez L’Os, le WordPress, ses plugins, le php, l’apache et le MySQL à jours bien consciencieusement. Le tout bien sur avec un mot de passe qui n’est pas “toto”.

#OP Obfuscation

Passons à la phase “Obfuscation”. Histoire de gagner du temps quand une faille sortira. Il n’est pas rare que quelques margoulins maintiennent à jours des listes de sites utilisant WordPress et des plugins utilisés. Cela est fait pour utiliser les failles existantes, et préparer les futures exploitations dès que de nouvelles failles apparaitront. Pour détecter la version de WordPress, on va utiliser WP-Scan, un petit scanner de WordPress en ruby qui fonctionne à merveille.

Premier Scan :


thanat0s@cyanide:~/bin/wpscan$ ./wpscan.rb --url thanat0s.trollprod.org
[...SNIP...SNIP...]
WordPress Security Scanner by the WPScan Team

Sponsored by the RandomStorm Open Source Initiative
_____________________________________________________
| URL: http://thanat0s.trollprod.org/
| Started on Sat Sep 22 10:26:35 2012

[!] The WordPress theme in use is inferno-mf v1.2
[!] WordPress version 3.4.2 identified from readme.html

[+] Enumerating plugins from passive detection ... No plugins found :(

[+] Finished at Sat Sep 22 10:26:37 2012

Trop facile pour lui , il retrouve aisément tous les fichiers de readme et de version… nettoyons un peu notre installation..

  • Virer les licences.txt qui incluent la version de wordpress
    • server:/www# find . -iname licence.txt -exec rm {} \;
  • Virer le readme.html qui donne aussi la version
    • server:/www# rm readme.html
  • Et en cas de plugins bavards…
    • server:/www# find . -iname readme.txt -exec rm {} \;

Et jusque là, ce que l’on a fait sert pas à grand choses, un bon tools comme wpscan n’est pas “fatigué pour autant”

Second scan :


thanat0s@cyanide:~/bin/wpscan$ ./wpscan.rb --url thanat0s.trollprod.org
[...SNIP...SNIP...]
WordPress Security Scanner by the WPScan Team

Sponsored by the RandomStorm Open Source Initiative
_____________________________________________________
| URL: http://thanat0s.trollprod.org/
| Started on Sat Sep 22 10:26:35 2012

[!] The WordPress theme in use is inferno-mf v1.2
[!] WordPress version 3.4.2 identified from meta generator

[+] Enumerating plugins from passive detection ... No plugins found :(

[+] Finished at Sat Sep 22 10:26:37 2012

Tabernacle ! Il trouve la version via le meta generator…On désactive ceci en squeezant le “add_action” qui va bien

server:/www # grep wp_generator * -R
wp-includes/default-filters.php:add_action( 'wp_head', 'wp_generator' );
wp-includes/general-template.php:function wp_generator() {
wp-includes/general-template.php: the_generator(apply_filters('wp_generator_type','xhtml'));

Dans mon cas, l’édition de default-filter.php et mise en commentaire de la ligne. Attention, on touche au CORE WordPress, il faudra probablement le refaire en cas d’upgrade de WordPress

Mais cela ne suffit toujours pas !

Troisième scan :


thanatos@cyanide:~/bin/wpscan# ./wpscan.rb --url thanat0s.trollprod.org
[...SNIP...SNIP...]
WordPress Security Scanner by the WPScan Team
Sponsored by the RandomStorm Open Source Initiative
_____________________________________________________

| URL: http://thanat0s.trollprod.org/

| Started on Sat Sep 22 10:59:09 2012

[!] The WordPress theme in use is inferno-mf v1.2
[!] WordPress version 3.4.2 identified from advanced fingerprinting

[+] Enumerating plugins from passive detection ... No plugins found :(

[+] Finished at Sat Sep 22 10:59:11 2012

L’acharné trouve maintenant la version via fingerprinting, Mais comment fait t’il ce saloptiot ? c’est simple mais malin. Il calcule le MD5 de certaines pages, JS, CSS etc…  La liste des MD5 et des fichiers audité est là.

https://github.com/wpscanteam/wpscan/blob/master/data/wp_versions.xml

Mais le MD5 c’est magique, on change un seul charactère et ce n’est plus le même fichier. Allons y.

server:/www# for lines in `find . -iname *.js`; do echo " " >> $lines; done
server:/www# for lines in `find . -iname *.css`; do echo " " >> $lines; done

Dernier scan :

Scannons,

thanatos@cyanide:~/bin/wpscan/# ./wpscan.rb --url thanat0s.trollprod.org
[...SNIP...SNIP...]
WordPress Security Scanner by the WPScan Team
Sponsored by the RandomStorm Open Source Initiative
_____________________________________________________

| URL: http://thanat0s.trollprod.org/

| Started on Sat Sep 22 11:43:46 2012

[!] The WordPress theme in use is inferno-mf v1.2

[+] Enumerating plugins from passive detection ... No plugins found :(

[+] Finished at Sat Sep 22 11:43:50 2012

Et voila, il est enfin comme un con.. il reste certes la version du thème mais c’est généralement pas la fin du monde (Encore que certain thèmes amènent aussi des failles. Pour les puristes, Ce numéro de version est dans les commentaires des fichiers CSS).

Ce qui est plus génant c’est les plugins. Ce qui nous ammène au chapitre suivant.

#OP Calmer les hardeurs

WPScan dispose de 2 fonctions qui peuvent devenir génante.

  • Le Bruteforce du compte admin.
  • Le Bruteforce à la recherche des plugins installés.

Aux gens qui bruteforcent, la réponse sera sanglante, j’ai nommé Fail2Ban, ou comment transformer toutes tentative de bruteforcing en punition (Pour ceux qui ne savent pas, Fail2Ban est un daemon qui blacklist les IPs des clients contrevenants.).

On va voir après le fichier de configuration que j’utilise, il permet de bannir tout contrevenant qui tenterai de bruteforcer la page de login de l’admin ou la liste des plugins.

Voici a quoi ressemble un log de bruteforce, l’authentification étant faite par un formulaire html, au niveau des logs c’est une successions de POSTs qui terminent en code 200 (Rappel, 200 c’est le Code qui veut dire OK tout va bien, vla ta page). Rien ne nous permet de dire que le login s’est mal passé pour un hit. Le User-Agent est crédible mais la fréquence est anormale ! Un user ou un admin n’a pas à se logger 10 fois de suite en 2 minutes, et même si c’est un vrai utilisateur, manifestement il a les doigts carrés, il doit être raisonné !!


- 1.2.3.4 - - [23/Sep/2012:17:05:05 +0200] "POST /wp-login.php HTTP/1.1" 200 1326 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0"
- 1.2.3.4 - - [23/Sep/2012:17:05:06 +0200] "POST /wp-login.php HTTP/1.1" 200 1326 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0"
- 1.2.3.4 - - [23/Sep/2012:17:05:05 +0200] "POST /wp-login.php HTTP/1.1" 200 1326 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0"
- 1.2.3.4 - - [23/Sep/2012:17:05:06 +0200] "POST /wp-login.php HTTP/1.1" 200 1326 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0"

la simple recherche de la chaine “POST /wp-login.php” permettra de créer un rate limiteur sur la page de login.

Concernant les plugins, la détection qu’opère WP-Scan se fait aussi par bruteforce

- 1.2.3.4 - - [23/Sep/2012:17:05:05 +0200] "GET /wp-content/plugins/bluetrait-event-viewer/ HTTP/1.1" 404 632 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0"
- 1.2.3.4 - - [23/Sep/2012:17:05:05 +0200] "GET /wp-content/plugins/login-security-solution/ HTTP/1.1" 404 632 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0"
- 1.2.3.4 - - [23/Sep/2012:17:05:05 +0200] "GET /wp-content/plugins/better-wp-security/ HTTP/1.1" 404 632 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0"
- 1.2.3.4 - - [23/Sep/2012:17:05:05 +0200] "GET /wp-content/plugins/limit-login-attempts/ HTTP/1.1" 404 632 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0"
- 1.2.3.4 - - [23/Sep/2012:17:05:05 +0200] "GET /wp-content/plugins/simple-login-lockdown/ HTTP/1.1" 404 632 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0"

Là c’est déja plus simple. Si c’est un code 404 (Qui veut dire “Page not Found”) et dans le répertoire /wp-content/plugins … Ca pue !

Et voila donc la conf que l’on peut mettre en place pour calmer toute utilisation de WPScan avec Fail2Ban

Le fichier de “filtre” nommé wordpress.conf

# Fail2Ban configuration file
#
# Author: Thanat0S
#
# $Revision: 1$

[Definition]
#
# Option: failregex
# Notes.: regex to match the password failures messages in the logfile. The
# host must be matched by a group named "host". The tag "<HOST>" can
# be used for standard IP/hostname matching and is only an alias for
# (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)
# Values: TEXT
#
failregex = [\-\S\.]+\s<HOST>.*(?:\/wp-content\/plugins\/.+\sHTTP\/[01]\.[019]\"\s404|POST\s\/wp-login\.php)

# Notes.: regex to ignore. If this regex matches, the line is ignored.
# Values: TEXT
#
ignoreregex =

Et dans le fichier jail.conf ajoutons le paragraphe suivant :

[wordpress]
enabled = true
filter = wordpress
action = iptables[name=wordpress, port=www, protocol=tcp]
sendmail-whois-lines[name=wordpress, dest=celuiquiseraprevenu@trollprod.org]
logpath = /var/log/laousont/leslogs.log
maxretry = 10
bantime = 900
findtime = 900

Voila qui donnera une bonne pénalité de 15mn à toutes personnes qui tentera d’utiliser WPScan pour lister les plugins ou bruteforcer votre compte admin. D’autant plus drole de WPScan ne gère pas très bien ce genre de punition et reste “coincé” sans lacher pendant tout le temps de la punition.

Et voila donc une installation qui devrait diriger le méchant hacker vers un autre WordPress que le votre, un peut être plus accessible. Bon… mais chiottes, maintenant on sait que j’ai WordPress 3.4.2 moi ! ;)

Posted in WebSecurity | Tagged , | Leave a comment

Hello world!

Ca y est, un blog !! Pourquoi ? D’un coté parce que j’aime toujours avoir des diarées verbales, en plus de temps en temps, elles semblent intéresser des gens.  De l’autre coté, j’ai de moins en moins de temps libre, donc on automatise !

On va parler principalement de ce qui tourne autour de la sécurité informatique dans la mesure où c’est ce qui m’amuse pour le moment (Désolé les fans de macramés). Pour donner le ton, parlons de ce WordpreSS, un wp-scan terminera en fail2ban.. mais cela fera partie du premier article ;)

 

Posted in BlaBla | Leave a comment