Ka Wing Ho, a Senior Consultant within Sekuro RED (our talented Offensive Security Team), shares his solution to the RCEME Preparation Challenge from the renowned Full Stack Web Attack (FSWA) Course.
Prework
As mentioned on the SourceIncite website:
Additionally, before signing up for this course students should complete the challenge to self assess if this course is right for them.
Before proceeding, I highly HIGHLY recommend you give it a try yourself before reading the spoilers below.
TL;DR
As someone who has tried to do source code review in the past and failed miserably, this challenge helped me to learn:
- How to use tools like
ysoserial
andJD-GUI
- Patch diffing (and comparing codebase differences)
- Nuances in Python3 and Java (read: pain)
- Java Deserialization primitives and their signs/tells
The challenge is not as straightforward as it might seem, as explained further in the blog!
You may ask: Is this challenge a true representation of the course?
Hard to say, because everyone will probably take different paths to solve this challenge. I had a friend who wrote their Proof-of-Concept in Java while I wrote mine in Python.
I will follow up with a future blog post talking more about my experience taking the course!
Running the Challenge
As of the time of writing, the command to start the docker container is as follows:
docker run -it --rm -p 8888:8080 registry.gitlab.com/source-incite/fswa-challenge/rceme:latest
Browsing to http://localhost:8888/ reveals the following page:

What is Apache Shiro?

According to the source:
Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.
After browsing around looking for functionality, the target application – an Apache Shiro sample running on an Eclipse Jetty server, gives you some dummy users which you can log in with to view a dummy account page. Just that, literally nothing else!
So what next?
As a seasoned penetration tester who tries harder, my first obvious instinct was to take the path of least resistance, I mean why do the work of writing an exploit when I can just go run something off the Internet that someone else wrote? (note: don’t actually do this without checking the code)
Consulting the oracle that is the Exploit Database, I found the following:


The associated Metasploit module for 48410

Actually Understanding the Exploit
CVE-2016-4437
, I eventually came to the conclusion that:
- Apache Shiro
1.2.4
uses a default encryption key (which is actually visible above in Metasploit) - The rememberMe cookie is unsafely deserialized in the backend, which leads to RCE


In this case, the rememberMe cookie is issued on successful login and is sent in subsequent requests to the application.
On a slightly lower-level, this is how Apache Shiro processes the rememberMe cookie:
For issuing the cookie to the user:
- An object is serialized by the application
- The object is encrypted using the encryption key and stored in a cookie
- The cookie is base64-encoded
- The cookie is issued/passed to the user
For reading the cookie passed by the user:
- User passes the cookie to the application
- The cookie is base64-decoded
- The cookie is then decrypted using the encryption key to retrieve the object
- The object is deserialized by the application
- Generate a malicious Java object to be deserialized (with
ysoserial
perhaps?) - Encrypt the
ysoserial
payload with the encryption key - Base64-encode the encrypted payload
- Send it off to Apache Shiro within the rememberMe cookie
- Apache Shiro does the reverse of everything above
- The
ysoserial
payload is eventually deserialized leading to RCE.

Java Deserialization
The main takeaway for the purposes of this blog though is that serialised objects can be recognised by the signatureWhat Do WebLogic, WebSphere, JBoss, Jenkins, OpenNMS, and Your Application Have in Common? This Vulnerability.
By @breenmachine What? The most underrated, underhyped vulnerability of 2015 has recently come to my attention, and I'm about to bring it to yours. No one gave it a fancy name, there were no press releases, nobody called Mandiant to come put out the fires. In fact, even though proof of concept code was released...
ac ed 00 05
bytes, or by the rO0
string after passing through base64-encoding. 
However, in the current case the base64-decoded rememberMe cookie value still needs to be decrypted with the secret key to work!

most definitely not ac ed 00 05
Following from that, I scoured the Internet and happened upon this blog which had a nifty script to decode the rememberMe cookie and view its contents:
# pip install pycrypto
import sys
import base64
from Crypto.Cipher import AES
def decode_rememberme_file(filename):
with open(filename, 'rb') as fpr:
key = "kPH+bIxk5D2deZiIxcaaaA=="
mode = AES.MODE_CBC
IV = b' ' * 16
encryptor = AES.new(base64.b64decode(key), mode, IV=IV)
remember_bin = encryptor.decrypt(fpr.read())
return remember_bin
if __name__ == '__main__':
with open("/tmp/decrypt.bin", 'wb+') as fpw:
fpw.write(decode_rememberme_file(sys.argv[1]))
/tmp/decrypt.bin
: 
no sign of ac ed 00 05
Repeating this with the sample value in the blog, the serialized object can be seen:

Looks like there’s still some work to be done! There was definitely something different about the Apache Shiro running in the docker container…
Reading the source
At that point, it was then time for me to review the source to find out:
- Was the cipher key the same in this case?
- What version of Apache Shiro was currently running in the challenge docker container?
I first rummaged around the docker container and found the main Web Archive (WAR) file:

I then copied it out of the container, unpacked the contents and inspected them in Visual Studio Code.
docker cp busy_wilson:/var/lib/jetty/webapps/root.war . && unzip root.war

shiro.ini
configuration file, there was positive confirmation that the default encryption key was still in use! There was also reference to a rememberMeManager
class which I then further explored… 
lib
folder and referenced in the pom.xml
file, so I continued my search there: 
shiro-web
and shiro-core
used as dependencies within the running Apache Shiro Sample

The pom.xml
file also provided the “version” of Apache Shiro core in use: 2.0.0

However, for the uninitiated, a Java Archive (JAR) file contains .class
files which aren’t exactly the main .java
source but can be decompiled using certain tools.

JD-GUI Exploration
.class
files, but entire .jar
and .war
files which was perfect for the current use case! 
.war
, then individual .jar
files, and finally the CookieRememberMeManager.class file .class
file Diff-ing Codebases
At this point in time I had made several key observations:
- The docker container’s version of Apache Shiro was
2.0.0
- The latest public version of Apache Shiro on github was
1.9.1
(as of time of writing) - All publicly known exploits and PoCs target
1.2.4
The encryption key was the same but the codebases were different, perhaps something different was being done in the backend? Therefore it was worth taking a look at how each version was processing the encryption/decryption, as I was still stuck at that stage from before!
- Top-side is VS Code showing Apache Shiro
1.2.4
code, which can be found on GitHub - Bottom-side is the JD-GUI of Apache Shiro
2.0.0
running in the challenge container
AbstractRememberMeManager.java
within both shiro-core
repositories, as these were the parent classes inherited by CookieRememberMeManager.java
as shown above. 



decrypt
functions and constructors operated similarly although with minor differences, I then began mucking around looking at other functions such as getCipherService()
, eventually leading me to AesCipherService()
… 

- In Apache Shiro
1.2.4
, the encryption/decryption is done in AES CBC mode. - In Apache Shiro
1.9.1
/2.0.0
, the encryption/decryption is done in AES GCM mode.
yoserial
The yoserial
tool is essentially a toolkit to aid in the generation of payloads utilizing gadget chains in well-known dependencies. However, in order to use the tool, one must identify what dependencies are in use by the target application.
The official SourceIncite solution post already mentions utilizing Common Collections, but I confirmed this by inspecting the dependencies as well:

The list of dependencies in lib
InvokerTransformer
function: 
ysoserial
supports payloads that map to many common libraries, two of which are related to the commons-collections-3.2.2.jar
as shown above:

The CommonsBeanutils1
and CommonsCollections1
payloads are suitable types for this target
java -jar ysoserial-all.jar CommonsBeanutils1 "touch /tmp/pwned" | base64 -w 0
rO0ABXNyABdqYXZhLnV0aWwuUHJpb3JpdHlRdWV1ZZTaMLT7P4KxAwACSQAEc2 ......
Scripting the Exploit
pycryptodome
library which supported the GCM mode (native Python crypto did not)
wingz@ubuntu:/tmp/demo/$ pip install pycryptodomex
Collecting pycryptodomex
Downloading pycryptodomex-3.15.0-cp35-abi3-manylinux2010_x86_64.whl (2.3 MB)
|████████████████████████████████| 2.3 MB 3.8 MB/s
Installing collected packages: pycryptodomex
Successfully installed pycryptodomex-3.15.0
AES GCM
After yet another few frustrating nights of fruitless results, I finally figured out pycryptodome’s AES GCM and got the decryption process working, with help from this StackOverflow post:

Unlike traditional CBC or EBC, GCM uses a nonce
and tag
feature in the first 12 and last 16 bytes of the encrypted string.
I modified the previous decryptor script to use AES GCM and it worked! The cookie was properly decrypted:
# for GCM encrytion in newer versions of shiro
# pip install pycryptodomex
import sys
import base64
#from Crypto.Cipher import AES
from Cryptodome.Cipher import AES
def decode_rememberme_file(filename):
with open(filename, 'rb') as f:
key = "kPH+bIxk5D2deZiIxcaaaA=="
data = f.read()
nonce, tag = data[:16], data[-16:]
cipher = AES.new(base64.b64decode(key), AES.MODE_GCM, nonce) # mac_len=16
remember_bin = cipher.decrypt_and_verify(data[16:-16], tag)
return remember_bin
if __name__ == '__main__':
with open("/tmp/cookie", 'wb+') as f:
f.write(decode_rememberme_file(sys.argv[1]))

ysoserial
payload generation and payload delivery to the target application, and can be found here:
https://gist.github.com/wingzRED/bf0f91d675a10893fb11032e52818ce4 

Root Cause
The deserialization vulnerability arose from the org.apache.shiro.lang.codec.io.DefaultSerializer
class, where a readObject
call can be found in the deserialize
function:

Author:

Ka Wing Ho
Ka Wing is a seasoned Senior Consultant within Sekuro RED, our talented Offensive Security Team. He specialises in attack surface reconnaissance, phishing simulation assessments, and web application penetration testing. At Sekuro, Ka Wing delivers projects and presents outcomes and findings to key stakeholders, ranging from C-suite executives and application owners to end developers. Some of Ka Wing's certifications include OSCP, CRTO and CRT.