WordPress e Python: Come autenticarsi al famoso CMS con python

Wordpress e Python

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:

root@linux-vm:~# 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

1) 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.

2) 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"

1) Dichiaro le variabili username e pwd contenenti la login e password del mio account wordpress

2) 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.

3) 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' }

1) 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:

Wordpress e Python

Il valore redirect_to non è obbligatorio.

2) 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)

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.

2) Per gestire eventuali tipologie d’errore durante la connessione, utilizzo l’istruzione try/except.

3) 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.

4) 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.

5) 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'}

6) 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.

7) 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

claudio@linux-vm:~$ python wordpress_login.py 
WordPress Authentication with Python


[!] Logged
[!] Username: pippo Password: pippo
claudio@linux-vm:~$