Intro
Dopo aver pubblicato vari exploit e vulnerabilità, sono tornato a scrivere un articolo dove poter condividere piccole nozioni tecniche che ho acquisito recentemente.
Negli ultimi mesi mi sono dedicato soprattutto ad analizzare codici php di vari plugins wordpress e alla programmazione in python.
Dato che molte vulnerabilità dei plugins wordpress richiedevano un utente (privilegiato e non) con accesso all’area riservata ho pensato: perché non scrivere gli exploit in python per automatizzare il tutto?
Googolando un po ho trovato qualche articolo che faceva riferimento a wordpress e python, ma nella maggior parte dei casi le informazioni si sono ritenute errate o troppo obsolete.
Per gestire connessioni http o https ho cominciato ad eseguire vari test con le librerie python urllib, urlib2, httplib, httplib2 e request fino a quando non ho trovato il perfetto binomio:
urllib e httplib2
urllib2 per codificare i dati da spedire al server web tramite metodo POST e httplib2 per gestire le connessioni con i protocolli http e https.
Dopo alcune verifiche ho notato che la libreria httplib2 sembrerebbe gestire in maniera corretta i vari redirect che vengono eseguiti dalle pagine di wordpress, in particolare quella di login.
Requisiti
Personalmente ho eseguito questi test su CentOS 5.10, CentOS 6.5, BackBox 3.x e Kalilinux 1.0.x in cui risiedono di default le versioni python 2.4 o 2.6 .
Mentre la libreria urllib è già presente nel pacchetto python, httplib2 va installata manualmente:
[email protected]:~# pip install httplib2
Per quanto riguarda le versioni di WordPress su cui ho eseguito i test sono: 3.x e soprattutto 4.x.
Specifico questo perchè i vecchi codici python che ho trovato in rete funzionavano soltanto con versioni precedenti alla 3.8.
Codice e commenti
Prima di iniziare con la stesura del codice voglio specificare che l’indirizzo url del cms wordpress a cui farò riferimento è: http://10.0.0.67/wordpress e naturalmente dovrà essere sostituito con il vostro.
Librerie
#!/usr/bin/env python
#
# WordPress Authentication with Python
#
#
# 1
import urllib, httplib2
# 2
import socket, httplib, sys
Nel primo import richiamo la libreria urllib per codificare il corpo della richiesta di tipologia POST, mentre httplib2 serve ad eseguire la vera e propria connessione verso il webserver.
Le librerie del secondo import, socket e httplib, servono a gestire gli eventuali errori di connessione (httplib2 non li supporta tutti nativamente) mentre la libreria sys viene utilizzata per forzare l’uscita dallo script.
Variabili di connessione
# 1
username = "pippo"
pwd = "pippo"
# 2
url = "http://10.0.0.67/wordpress"
url_login = "/wp-login.php"
# 3
timeout = "10"
Dichiaro le variabili username e pwd contenenti la login e password del mio account wordpress
Oltre la variabile url contenente il protocollo (http://), l’indirizzo url (10.0.0.67) e l’eventuale base d’installazione di wordpress (/wordpress) dichiaro la variabile url_login specificando la pagina di login.
La variabile timeout serve a specificare il tempo di attesa prima di chiudere la connessione verso un server apparentemente non raggiungibile.
Creazione body e headers connessione POST
# 1
body = { 'log':username,
'pwd':pwd,
'wp-submit':'Login',
'testcookie':'1' }
# 2
headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36',
'Content-type': 'application/x-www-form-urlencoded' }
- Creo il dizionario body contenente tutti i valori richiesti dalla pagina login di wordpress, come si può notare in una sessione intercettata con il plugin Tamper Data di Firefox:
Il valore redirect_to non è obbligatorio.
- Nel dizionario headers ho specificato un User-Agent che rispecchi un browser standard e la tipologia del contenuto della connessione (Content-Type).
Connessione e controllo account
# 1
http = httplib2.Http(timeout=int(timeout), disable_ssl_certificate_validation=True)
print "WordPress Authentication with Python"
# 2
try:
# 3
response, content = http.request(url+url_login, 'POST', headers=headers, body=urllib.urlencode(body))
# 4
if str(response.status)[0] == "4" or str(response.status)[0] == "5":
print('\n[X] HTTP error, code: '+str(response.status))
sys.exit(1)
# 5
if "set-cookie" in response:
# 6
if response['set-cookie'].split(" ")[-1] == "httponly":
print('\n')
print('[!] Logged')
print('[!] Username: '+username+' Password: '+pwd)
else:
print('[X] Bad username or password')
else:
print('[X] Bad username or password')
# 7
except socket.timeout:
print('\n[X] Connection Timeout')
sys.exit(1)
except socket.error:
print('\n[X] Connection Refused')
sys.exit(1)
except httplib.ResponseNotReady:
print('\n[X] Server Not Responding')
sys.exit(1)
except httplib2.ServerNotFoundError:
print('\n[X] Server Not Found')
sys.exit(1)
except httplib2.HttpLib2Error:
print('\n[X] Connection Error!!')
sys.exit(1)
Dichiaro la variabile http creando una connessione tramite la libreria httplib2 con l’opzione timeout e il controllo dei certificati ssl disabilitati.
In questo modo eventuali connessioni cifrate tramite protocollo https verso server con certificati auto-firmati non saranno terminate.
L’opzione “disable_ssl_certificate_validation=True” può essere inserita nel codice anche nel caso in cui non vengono effettuate connessioni https.Per gestire eventuali tipologie d’errore durante la connessione, utilizzo l’istruzione try/except.
Eseguo la connessione di login tramite richiesta POST e inserisco l’header della risposta da parte del server nella variabile response e il contenuto della pagina dentro la variabile content.
Controllo se il server web remoto mi risponde con un codice 4xx o 5xx in modo da poter terminare lo script in caso di messaggi d’errore o accesso negato.
Controllo se nell’ header di risposta del server esiste la presenza del Cookie, altrimenti termino lo script. Esempio di risposta:
{'status': '200', 'content-length': '2850', 'x-powered-by': 'PHP/5.3.3', 'set-cookie': 'wordpress_test_cookie=WP+Cookie+check; path=/wordpress/,
wordpress_75aacd302e2a4723897cb1d154c13f77=pippo%7C1415133547%7Cb832e2796416612317d9712b6eeb06c2; path=/wordpress/wp-content/plugins; httponly,
wordpress_75aacd302e2a4723897cb1d154c13f77=pippo%7C1415133547%7Cb832e2796416612317d9712b6eeb06c2; path=/wordpress/wp-admin; httponly,
wordpress_logged_in_75aacd302e2a4723897cb1d154c13f77=pippo%7C1415133547%7C5c3aad10a4fad0749c5a027d91c7893f; path=/wordpress/; httponly',
'expires': 'Wed, 11 Jan 1984 05:00:00 GMT', 'server': 'Apache/2.2.3 (CentOS)', 'connection': 'close', 'pragma': 'no-cache',
'cache-control': 'no-cache, must-revalidate, max-age=0', 'date': 'Sun, 02 Nov 2014 20:39:06 GMT', 'x-frame-options': 'SAMEORIGIN',
'content-type': 'text/html; charset=UTF-8'}
Tramite il metodo split controllo se nel cookie rilasciato dal server rilevo il campo “httponly“.
In caso positivo il processo di login è andato a buon fine, altrimenti termino lo script perchè l’username o password non sono corretti.Controllo il resto delle eccezioni che posso riscontrare durante la connessione tramite l’istruzione except.
Conclusione
Per completare l’articolo copio il codice completo e il risultato dell’esecuzione dello script.
Codice
#!/usr/bin/env python
#
# WordPress Authentication with Python
#
#
import urllib, httplib2
import socket, httplib, sys
username = "pippo"
pwd = "pippo"
url = "http://10.0.0.67/wordpress"
url_login = "/wp-login.php"
timeout = "10"
body = { 'log':username,
'pwd':pwd,
'wp-submit':'Login',
'testcookie':'1' }
headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36',
'Content-type': 'application/x-www-form-urlencoded' }
http = httplib2.Http(timeout=int(timeout), disable_ssl_certificate_validation=True)
print "WordPress Authentication with Python"
try:
response, content = http.request(url+url_login, 'POST', headers=headers, body=urllib.urlencode(body))
if str(response.status)[0] == "4" or str(response.status)[0] == "5":
print('\n[X] HTTP error, code: '+str(response.status))
sys.exit(1)
print response
if "set-cookie" in response:
if response['set-cookie'].split(" ")[-1] == "httponly":
print('\n')
print('[!] Logged')
print('[!] Username: '+username+' Password: '+pwd)
else:
print('[X] Bad username or password')
else:
print('[X] Bad username or password')
except socket.timeout:
print('\n[X] Connection Timeout')
sys.exit(1)
except socket.error:
print('\n[X] Connection Refused')
sys.exit(1)
except httplib.ResponseNotReady:
print('\n[X] Server Not Responding')
sys.exit(1)
except httplib2.ServerNotFoundError:
print('\n[X] Server Not Found')
sys.exit(1)
except httplib2.HttpLib2Error:
print('\n[X] Connection Error!!')
sys.exit(1)
Esecuzione
[email protected]:~$ python wordpress_login.py
WordPress Authentication with Python
[!] Logged
[!] Username: pippo Password: pippo
[email protected]:~$