Java安全之Mojarra JSF反序列化( 二 )

跟进this.phases[i].doPhase ,这里会有循环遍历多个Phase对象去调用doPhase方法

Java安全之Mojarra JSF反序列化

文章插图
继续跟进到this.execute
public void doPhase(FacesContext context, Lifecycle lifecycle, ListIterator<PhaseListener> listeners) {context.setCurrentPhaseId(this.getId());PhaseEvent event = null;if (listeners.hasNext()) {event = new PhaseEvent(context, this.getId(), lifecycle);}Timer timer = Timer.getInstance();if (timer != null) {timer.startTiming();}try {this.handleBeforePhase(context, listeners, event);if (!this.shouldSkip(context)) {this.execute(context);}在execute方法逻辑内,先通过facesContext.getExternalContext().getRequestMap();拿到一个RequestMap其中的值为ExternalContextImpl对象,该对象中包含了上下文、request、response等整体信息 。后续跟进 viewHandler.restoreView(facesContext, viewId);
Java安全之Mojarra JSF反序列化

文章插图
继续跟进getstate
Java安全之Mojarra JSF反序列化

文章插图
下面是一处关键点 , 通过刚才我们提到的ExternalContextImpl,从中对应的requestParameterMap中的key取出我们传入的payload,默认情况下是javax.faces.Viewstate,之后该值作为形参带入doGetState方法内
Java安全之Mojarra JSF反序列化

文章插图
下面是漏洞出发点的反序列化逻辑部分
先Base64解码,解码后通过this.guard的值是否为null判断是否有加密,有加密的话会去调用this.guard.decrypt进行解密 , 之后ungzip解压
Java安全之Mojarra JSF反序列化

文章插图
之后将该流转换为ApplicationObjectInputStream并有一个timeout的判断逻辑,最后直接反序列化
Java安全之Mojarra JSF反序列化

文章插图
存在加密的情况的话可能会有以下的配置
<context-param><param-name>javax.faces.STATE_SAVING_METHOD</param-name><param-value>client</param-value></context-param><env-entry><env-entry-name>com.sun.faces.ClientStateSavingPassword</env-entry-name><env-entry-type>java.lang.String</env-entry-type><env-entry-value>[some secret password]</env-entry-value></env-entry>
<context-param><param-name>com.sun.faces.ClientSideSecretKey</param-name><param-value>[some secret password]</param-value></context-param>ClientSideStateHelper#doGetState中有如下代码
其中guard来标识是否启用加密,有加密时会调用this.guard.decrypt进行解密
if ("stateless".equals(stateString)) {return null;} else {ObjectInputStream ois = null;InputStream bis = new Base64InputStream(stateString);try {if (this.guard != null) {byte[] bytes = stateString.getBytes("UTF-8");int numRead = ((InputStream)bis).read(bytes, 0, bytes.length);byte[] decodedBytes = new byte[numRead];((InputStream)bis).reset();((InputStream)bis).read(decodedBytes, 0, decodedBytes.length);bytes = this.guard.decrypt(decodedBytes);if (bytes == null) {return null;}bis = new ByteArrayInputStream(bytes);}加解密逻辑均在ByteArrayGuard类中,需要时扣代码即可
public byte[] decrypt(byte[] bytes) {try {byte[] macBytes = new byte[32];System.arraycopy(bytes, 0, macBytes, 0, macBytes.length);byte[] iv = new byte[16];System.arraycopy(bytes, macBytes.length, iv, 0, iv.length);byte[] encdata = https://www.huyubaike.com/biancheng/new byte[bytes.length - macBytes.length - iv.length];System.arraycopy(bytes, macBytes.length + iv.length, encdata, 0, encdata.length);IvParameterSpec ivspec = new IvParameterSpec(iv);Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");decryptCipher.init(2, this.sk, ivspec);Mac decryptMac = Mac.getInstance("HmacSHA256");decryptMac.init(this.sk);decryptMac.update(iv);decryptMac.update(encdata);byte[] macBytesCalculated = decryptMac.doFinal();if (this.areArrayEqualsConstantTime(macBytes, macBytesCalculated)) {byte[] plaindata = https://www.huyubaike.com/biancheng/decryptCipher.doFinal(encdata);return plaindata;} else {System.err.println("ERROR: MAC did not verify!");return null;}} catch (Exception var10) {System.err.println("ERROR: Decrypting:" + var10.getCause());return null;}}整体逻辑为,其中看lib版本和配置来判断走不走加解密
* Generate Payload: *writeObject ==> Gzip ==> Encrpt ==> Base64Encode * * Recive Payload: *Base64Decode ==> Decrpt ==> UnGzip ==> readObject

推荐阅读