DEV Community

Cover image for The Summary Of Spring Security Authorization Bypass on Java
TutorialBoy
TutorialBoy

Posted on

The Summary Of Spring Security Authorization Bypass on Java

Authorization Bypass

Basically, the name is very accurate. Say we have a webpage in our Spring Boot application that should only be accessible for users that are configured to have the admin role. An authorization bypass means that a non-admin user could access that page in certain use cases without having this admin role (or better). Obviously, this is unwanted and can lead to a number of things including data leaks and unauthorized changing, creating, or deleting data.

Spring Security

With Spring Security, it is possible to create a SecurityFilterChain to set permission for specific endpoints. In newer versions of Spring, it looks something like this:

@Configuration
@EnableWebSecurity //Enable web security features
public class AuthConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
                        http.authorizeRequests() // turn on HttpSecurity configuration
            .antMatchers("/admin/**").hasRole("ADMIN") // admin/** Schema URL must have ADMIN role
            .antMatchers("/user/**").access("hasAnyRole('ADMIN','USER')") // This mode requires ADMIN or USER role
            .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") // ADMIN and DBA roles are required
            .anyRequest().authenticated() // Users must access other URLs after authentication (access after login)
            .and().formLogin().loginProcessingUrl("/login").permitAll() // Enable form login and configure the login interface
            .and().csrf().disable(); // close csrf

        return http.build();
    }
Enter fullscreen mode Exit fullscreen mode

  • access(String) Allow access if the given SpEL expression evaluates to true
  • anonymous() Allow anonymous users to access
  • authenticated() Allow authenticated users to access
  • denyAll() Unconditionally deny all access
  • fullyAuthenticated() If the user is fully authenticated (not authenticated by the Remember-me function), access is allowed
  • hasAnyAuthority(String…) Allow access if the user has one of the given permissions
  • hasAnyRole(String…) Allow access if the user has one of the given roles
  • hasAuthority(String) Allow access if the user has the given permission
  • hasIpAddress(String) Allow access if the request comes from the given IP address
  • hasRole(String) Allow access if the user has the given role
  • not() negates the result of other access methods
  • permitAll() Allow access unconditionally

- rememberMe() If the user is authenticated by the Remember-me function, access is allowed

WebSecurityConfigurerAdapter​ The configure() method can also be used to formulate the details of web security by integrating classes.

configure(WebSecurity): By overloading this method, the Filter chain of Spring Security can be configured.
configure(HttpSecurity): By overloading this method, you can configure how to protect requests through interceptors.
All SpEL expressions supported by Spring Security are as follows:


  • authentication user authentication object
  • denyAll The result is always false
  • hasAnyRole(list of roles) Returns true if the user is authorized for any of the specified permissions
  • hasRole(role) Evaluates to true if the user is granted the specified permission
  • hasIpAddress(IP Adress) user address
  • isAnonymous() Is it an anonymous user
  • isAuthenticated() not anonymous
  • isFullyAuthenticated Not anonymous nor remember-me authenticated
  • isRemberMe() remember-me certification
  • permitAll always true
  • principal User's main information object ​ ---

configure(AuthenticationManagerBuilder): By overloading this method, the user-detail (user details) service can be configured.


  • accountExpired(boolean) Define whether the account has expired
  • accountLocked(boolean) Defines whether the account is locked
  • and() for connection configuration
  • authorities(GrantedAuthority…) Grant one or more permissions to a user
  • authorities(List) Grant one or more permissions to a user
  • authorities(String…) Grant one or more permissions to a user
  • credentialsExpired(boolean) Defines whether the credential has expired
  • disabled(boolean) Defines whether the account has been disabled
  • password(String) Define the user's password
  • roles(String…) Grant a user one or more roles
  • User Storage Method

Through the inMemoryAuthentication() method, we can enable, configure, and arbitrarily populate memory-based user storage. Also, we can call the withUser() method to add a new user to the in-memory user store. The parameter of this method is the username. The withUser() method returns UserDetailsManagerConfigurer.UserDetailsBuilder, which provides multiple methods for further configuring users, including the password() method for setting user passwords and the roles() method for granting one or more role permissions to a given user. Note that the roles() method is a shorthand for the authorities() method. The value given by the roles() method will be prefixed with a ROLE_ and granted to the user as permission. Therefore, the authority of the appeal code user is ROLE_USER, ROLE_ADMIN. With the help of the passwordEncoder() method to specify a password encoder (encoder), we can encrypt and store user passwords.

@Configuration
@EnableWebSecurity //Enable web security features
public class AuthConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
                        http.authorizeRequests() // Open HttpSecurity configuration
            .antMatchers("/admin/**").hasRole("ADMIN") // admin/** Schema URL must have ADMIN role
            .antMatchers("/user/**").access("hasAnyRole('ADMIN','USER')") // This mode requires ADMIN or USER role
            .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") // ADMIN and DBA roles are required
            .anyRequest().authenticated() // Users must access other URLs after authentication (access after login)
            .and().formLogin().loginProcessingUrl("/login").permitAll() // Enable form login and configure the login interface
            .and().csrf().disable(); // close csrf

        return http.build();

@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
        .withUser("root").password("123").roles("ADMIN","DBA")
        .and()
        .withUser("admin").password("123").roles("ADMIN","USER")
        .and()
        .withUser("xxx").password("123").roles("USER");
    }
          }
Enter fullscreen mode Exit fullscreen mode

Authentication based on database tables: User data is usually stored in a relational database and accessed through JDBC. In order to configure Spring Security to use JDBC-backed user storage, we can use the jdbcAuthentication() method and configure its DataSource so that we can access the relational database.

LDAP-based authentication: In order, for Spring Security to use LDAP-based authentication, we can use the ldapAuthentication() method.

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    // Configure the user-detail service
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // Configure authentication based on LDAP
        auth.ldapAuthentication()
                .userSearchBase("ou=people")
                .userSearchFilter("(uid={0})")
                .groupSearchBase("ou=groups")
                .groupSearchFilter("member={0}")
                .passwordCompare()
                .passwordAttribute("password")
                .passwordEncoder(new BCryptPasswordEncoder());
    }
}
Enter fullscreen mode Exit fullscreen mode

use remote LDAP

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.ldapAuthentication()
                .userSearchBase("ou=people")
                .userSearchFilter("(uid={0})")
                .groupSearchBase("ou=groups")
                .groupSearchFilter("member={0}")
                // Returns a ContextSourceBuilder object
                .contextSource()
                // Specify the address of the remote LDAP server
                .url("ldap://xxx.com:389/dc=xxx,dc=com");

    }
}
Enter fullscreen mode Exit fullscreen mode

  • ldapAuthentication():Indicates LDAP-based authentication.
  • userSearchBase():Provides the base query for finding users
  • userSearchFilter():Provide filter criteria for searching users.
  • groupSearchBase():An underlying query is specified for the lookup group.
  • groupSearchFilter():Provides filter conditions for groups.
  • passwordCompare():Hope to pass password comparison for authentication.
  • passwordAttribute():Specifies the attribute name saved by password, default: userPassword.
  • passwordEncoder():Specifies a cipher converter.
  • hasRole and hasAuthority
http.authorizeRequests()
        .antMatchers("/admin/**").hasAuthority("admin")
        .antMatchers("/user/**").hasAuthority("user")
        .anyRequest().authenticated()


http.authorizeRequests()
        .antMatchers("/admin/**").hasRole("admin")
        .antMatchers("/user/**").hasRole("user")
        .anyRequest().authenticated()
Enter fullscreen mode Exit fullscreen mode

Actually, both have the same effect

antMatchers configuration authentication bypass
package person.xu.vulEnv;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

import static org.springframework.security.config.Customizer.withDefaults;

@Configuration
@EnableWebSecurity
public class AuthConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        http
                .csrf().disable()
                .authorizeRequests()
                .antMatchers("/test").access("hasRole('ADMIN')")
                .antMatchers("/**").permitAll();
                        //.antMatchers("/**").access("anonymous");



        // @formatter:on
        return http.build();
    }


    // @formatter:off
    @Bean
    public InMemoryUserDetailsManager userDetailsService() {
        UserDetails user = User.withDefaultPasswordEncoder()
                .username("user")
                .password("password")
                .roles("USER")
                .build();
        return new InMemoryUserDetailsManager(user);
    }
    // @formatter:on
}
Enter fullscreen mode Exit fullscreen mode

Bypass: http://127.0.0.1:8012/test/

mvcMatchers("/test").access("hasRole('ADMIN')")Or use antMatchers("/test/**").access("hasRole('ADMIN')")the writing method to prevent authentication bypass.

regexMatchers configuration authentication bypass
public class AuthConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        http
                .csrf().disable()
                .authorizeRequests()
                .regexMatchers("/test").access("hasRole('ADMIN')")
                .antMatchers("/**").access("anonymous");



        // @formatter:on
        return http.build();
    }
Enter fullscreen mode Exit fullscreen mode

http://127.0.0.1:8012/test?、http://127.0.0.1:8012/test/

Matchers do not use a similar /test.*method. When passing in /test?, the regex will not match and will not hit /test the rules. The safe writing

.regexMatchers("/test.*?").access("hasRole('ADMIN')")
useSuffixPatternMatch bypass
The lower version of spring-webmvc and its related components, including:

  • spring-webmvc <= 5.2.4.RELEASE
  • spring-framework <= 5.2.6.RELEASE
  • spring-boot-starter-parent <= 2.2.5.RELEASE useSuffixPatternMatchThe configuration default value defined in the code true means to use the suffix matching pattern to match the path.

For example, /path/abcrouting will also allow /path/abcd.ef, /path/abcde.fetc. Adding a path in the form of .xxxa suffix matches successfully.

Bugfixes

:
Using a higher version of spring-web MVC can effectively avoid problems.

CVE-2022-22978

Affected version

  • Spring Security 5.5.x < 5.5.7
  • Spring Security 5.6.x < 5.6.4

Vulnerability Analysis

When Spring is loaded DelegatingFilterProxy, DelegatingFilterProxy will get the Filter implementation class injected into the Spring container from the Spring container according to the targetBeanName. When configuring the DelegatingFilterProxy, you generally need to configure the attribute targetBeanName. DelegatingFilterProxy is a proxy for the servlet filter. The benefit of using this class is mainly to manage the life cycle of the servlet filter through the Spring container.

In addition, if some instances of Spring containers are needed in the filter, they can be injected directly through the spring,

and these convenient operations such as reading some configuration files can be configured and realized through Spring.

@Override
protected void initFilterBean() throws ServletException {
    synchronized (this.delegateMonitor) {
        if (this.delegate == null) {
            // If no target bean name specified, use filter name.
                        //If the targetBeanName attribute is not set when the Filter is configured, it will be searched directly based on the Filter name
            if (this.targetBeanName == null) {
                this.targetBeanName = getFilterName();
            }

            WebApplicationContext wac = findWebApplicationContext();
            if (wac != null) {
                                //Get the implementation of the injected Filter from the Spring container
                this.delegate = initDelegate(wac);
            }
        }
    }
}

protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
        //Obtain the implementation class of the injected Filter from the Spring container
        Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
        if (isTargetFilterLifecycle()) {
            delegate.init(getFilterConfig());
        }
        return delegate;
    }



@Override
protected void initFilterBean() throws ServletException {
    synchronized (this.delegateMonitor) {
        if (this.delegate == null) {
            // If no target bean name specified, use filter name.
                        //If the targetBeanName attribute is not set when the Filter is configured, it will be searched directly based on the Filter name
            if (this.targetBeanName == null) {
                this.targetBeanName = getFilterName();
            }

            WebApplicationContext wac = findWebApplicationContext();
            if (wac != null) {
                                //Obtain the implementation class of the injected Filter from the Spring container
                this.delegate = initDelegate(wac);
            }
        }
    }
}

protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
        //Obtain the implementation class of the injected Filter from the Spring container
        Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
        if (isTargetFilterLifecycle()) {
            delegate.init(getFilterConfig());
        }
        return delegate;
    }
Get the injected Filter implementation class from the Spring container, and then call org.springframework.web.filter.DelegatingFilterProxy#invokeDelegatethe method

comeorg.springframework.security.web.FilterChainProxy#doFilterInternal

   private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest)request);
        HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse)response);
        List<Filter> filters = this.getFilters((HttpServletRequest)firewallRequest);
        if (filters != null && filters.size() != 0) {
            if (logger.isDebugEnabled()) {
                logger.debug(LogMessage.of(() -> {
                    return "Securing " + requestLine(firewallRequest);
                }));
            }

            VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters);
            virtualFilterChain.doFilter(firewallRequest, firewallResponse);
        } else {
            if (logger.isTraceEnabled()) {
                logger.trace(LogMessage.of(() -> {
                    return "No security for " + requestLine(firewallRequest);
                }));
            }

            firewallRequest.reset();
            chain.doFilter(firewallRequest, firewallResponse);
        }
    }
Enter fullscreen mode Exit fullscreen mode

a firewall is loaded by default StrictHttpFirewall, instead of DefaultHttpFirewall. On the contrary DefaultHttpFirewall, the verification is not so strict

FirewalledRequestIt is an encapsulated request class, but in fact, this class just HttpServletRequestWrapperadds a reset method on the basis of. When the spring security filter chain is executed, it is FilterChainProxyresponsible for calling this method in order to reset all or part of the properties.

FirewalledResponseIt is an encapsulated response class. This class mainly rewrites the four methods of sendRedirect, setHeader, addHeader, and addCookie, and checks its parameters in each method to ensure that the parameters do not contain \r and \n.

In the FilterChainProxy property definition, the HttpFirewall instance created by default is StrictHttpFirewall.

FilterChainProxy is built in the WebSecurity#performBuild method, and WebSecurity implements the ApplicationContextAware interface and implements the setApplicationContext method in the interface. In this method, the HttpFirewall pair is found from the spring container and assigned to the httpFirewall attribute. Finally, in the performBuild method, after the FilterChainProxy object is built successfully, if httpFirewall is not empty, configure httpFirewall to the FilterChainProxy object.

Therefore, if there is an HttpFirewall instance in the spring container, the instance provided by the spring container will be used; if not, the StrictHttpFirewall defined by default in FilterChainProxy will be used.


org.springframework.security.web.firewall.StrictHttpFirewall#getFirewalledRequest

public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException {
        this.rejectForbiddenHttpMethod(request);
        this.rejectedBlocklistedUrls(request);
        this.rejectedUntrustedHosts(request);
        if (!isNormalized(request)) {
            throw new RequestRejectedException("The request was rejected because the URL was not normalized.");
        } else {
            String requestUri = request.getRequestURI();
            if (!containsOnlyPrintableAsciiCharacters(requestUri)) {
                throw new RequestRejectedException("The requestURI was rejected because it can only contain printable ASCII characters.");
            } else {
                return new StrictFirewalledRequest(request);
            }
        }
    }
Enter fullscreen mode Exit fullscreen mode

The method will determine whether the requested method is allowed

​ org.springframework.security.web.firewall.StrictHttpFirewall#rejectForbiddenHttpMethod



private void rejectForbiddenHttpMethod(HttpServletRequest request) {
        if (this.allowedHttpMethods != ALLOW_ANY_HTTP_METHOD) {
            if (!this.allowedHttpMethods.contains(request.getMethod())) {
                throw new RequestRejectedException("The request was rejected because the HTTP method \"" + request.getMethod() + "\" was not included within the list of allowed HTTP methods " + this.allowedHttpMethods);
            }
        }
    }


    private static Set<String> createDefaultAllowedHttpMethods() {
        Set<String> result = new HashSet();
        result.add(HttpMethod.DELETE.name());
        result.add(HttpMethod.GET.name());
        result.add(HttpMethod.HEAD.name());
        result.add(HttpMethod.OPTIONS.name());
        result.add(HttpMethod.PATCH.name());
        result.add(HttpMethod.POST.name());
        result.add(HttpMethod.PUT.name());
        return result;
    }
org.springframework.security.web.firewall.StrictHttpFirewall#rejectedBlocklistedUrls

private void rejectedBlocklistedUrls(HttpServletRequest request) {
        Iterator var2 = this.encodedUrlBlocklist.iterator();

        String forbidden;
        do {
            if (!var2.hasNext()) {
                var2 = this.decodedUrlBlocklist.iterator();

                do {
                    if (!var2.hasNext()) {
                        return;
                    }

                    forbidden = (String)var2.next();
                } while(!decodedUrlContains(request, forbidden));

                throw new RequestRejectedException("The request was rejected because the URL contained a potentially malicious String \"" + forbidden + "\"");
            }

            forbidden = (String)var2.next();
        } while(!encodedUrlContains(request, forbidden));

        throw new RequestRejectedException("The request was rejected because the URL contained a potentially malicious String \"" + forbidden + "\"");
    }


encodedUrlBlocklist = {HashSet@7373}  size = 18
 0 = "//"
 1 = ""
 2 = "%2F%2f"
 3 = "%2F%2F"
 4 = "%00"
 5 = "%25"
 6 = "%2f%2f"
 7 = "%2f%2F"
 8 = "%5c"
 9 = "%5C"
 10 = "%3b"
 11 = "%3B"
 12 = "%2e"
 13 = "%2E"
 14 = "%2f"
 15 = "%2F"
 16 = ";"
 17 = "\"
decodedUrlBlocklist = {HashSet@7374}  size = 16
 0 = "//"
 1 = ""
 2 = "%2F%2f"
 3 = "%2F%2F"
 4 = "%00"
 5 = "%"
 6 = "%2f%2f"
 7 = "%2f%2F"
 8 = "%5c"
 9 = "%5C"
 10 = "%3b"
 11 = "%3B"
 12 = "%2f"
 13 = "%2F"
 14 = ";"
 15 = "\"
Enter fullscreen mode Exit fullscreen mode
private static boolean encodedUrlContains(HttpServletRequest request, String value) {
        return valueContains(request.getContextPath(), value) ? true : valueContains(request.getRequestURI(), value);
    }

    private static boolean decodedUrlContains(HttpServletRequest request, String value) {
        if (valueContains(request.getServletPath(), value)) {
            return true;
        } else {
            return valueContains(request.getPathInfo(), value);
        }
    }

private static boolean valueContains(String value, String contains) {
        return value != null && value.contains(contains);
    }
Prioritize the value from the request.getContextPath()the inside, if there is a blacklist, it will return false and throw an exception.

org.springframework.security.web.firewall.StrictHttpFirewall#rejectedUntrustedHosts

private void rejectedUntrustedHosts(HttpServletRequest request) {
    String serverName = request.getServerName();
    if (serverName != null && !this.allowedHostnames.test(serverName)) {
        throw new RequestRejectedException("The request was rejected because the domain " + serverName + " is untrusted.");
    }
}
org.springframework.security.web.firewall.StrictHttpFirewall#isNormalized(java.lang.String)

 private static boolean isNormalized(String path) {
        if (path == null) {
            return true;
        } else {
            int slashIndex;
            for(int i = path.length(); i > 0; i = slashIndex) {
                slashIndex = path.lastIndexOf(47, i - 1);
                int gap = i - slashIndex;
                if (gap == 2 && path.charAt(slashIndex + 1) == '.') {
                    return false;
                }

                if (gap == 3 && path.charAt(slashIndex + 1) == '.' && path.charAt(slashIndex + 2) == '.') {
                    return false;
                }
            }

            return true;
        }
    }
Check that request.getRequestURI() request.getContextPath() request.getServletPath() request.getPathInfo() is not allowed ., /./or/. to request.getRequestURI();callorg.springframework.security.web.firewall.StrictHttpFirewall#containsOnlyPrintableAsciiCharacters

 private static boolean containsOnlyPrintableAsciiCharacters(String uri) {
        int length = uri.length();

        for(int i = 0; i < length; ++i) {
            char ch = uri.charAt(i);
            if (ch < ' ' || ch > '~') {
                return false;
            }
        }

        return true;
    }

Enter fullscreen mode Exit fullscreen mode

special characters not allowed


!
"
#
$
%
&
'
(
)
*
+
,
-
.
/
:
;
<
=
>
?
@
[
\
]
^
_
`
{
|
}
~
Enter fullscreen mode Exit fullscreen mode

Get filters, call virtualFilterChain.doFilterinto the following will traverse call doFilter, into the Filter execution chain

public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
            if (this.currentPosition == this.size) {
                if (FilterChainProxy.logger.isDebugEnabled()) {
                    FilterChainProxy.logger.debug(LogMessage.of(() -> {
                        return "Secured " + FilterChainProxy.requestLine(this.firewalledRequest);
                    }));
                }

                this.firewalledRequest.reset();
                this.originalChain.doFilter(request, response);
            } else {
                ++this.currentPosition;
                Filter nextFilter = (Filter)this.additionalFilters.get(this.currentPosition - 1);
                if (FilterChainProxy.logger.isTraceEnabled()) {
                    FilterChainProxy.logger.trace(LogMessage.format("Invoking %s (%d/%d)", nextFilter.getClass().getSimpleName(), this.currentPosition, this.size));
                }

                nextFilter.doFilter(request, response, this);
            }
        }
org.springframework.security.web.access.intercept.FilterSecurityInterceptor#invoke

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    this.invoke(new FilterInvocation(request, response, chain));
}

   public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
        if (this.isApplied(filterInvocation) && this.observeOncePerRequest) {
            filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
        } else {
            if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
                filterInvocation.getRequest().setAttribute("__spring_security_filterSecurityInterceptor_filterApplied", Boolean.TRUE);
            }

            InterceptorStatusToken token = super.beforeInvocation(filterInvocation);

            try {
                filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
            } finally {
                super.finallyInvocation(token);
            }

            super.afterInvocation(token, (Object)null);
        }
    }
transfersuper.beforeInvocation(filterInvocation);
Enter fullscreen mode Exit fullscreen mode

Do a regular match. Here first replace the configuration of the vulnerability

@Configuration
@EnableWebSecurity
public class AuthConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        http
                .authorizeHttpRequests((authorize) -> authorize
                        .regexMatchers("/admin/.*").authenticated()
                )
                .httpBasic(withDefaults())
                .formLogin(withDefaults());

        return http.build();
    }
Enter fullscreen mode Exit fullscreen mode

Using regexMatchers will use org.springframework.security.web.util.matcher.RegexRequestMatcher#matchesthe class to process the rules written

Access /admin/123is subject to this rule, which requires authentication in the configuration.

However, when visiting `/admin/123%0d, the regular match here is flase, which does not hit this rule, so it goes to the next rule to achieve bypass.

The problem here is that .*the regular pattern is used to match, but the incoming data %0d cannot be matched. The default rule of Pattern is no match \r\nand so on.

`
public class test {
public static void main(String[] args) {
String regex = "a.*b";

    //Output true, specify the Pattern.DOTALL mode, which can match newline characters.
    Pattern pattern1 = Pattern.compile(regex,Pattern.DOTALL);
    boolean matches1 = pattern1.matcher("aaabbb").matches();

    System.out.println(matches1);
    boolean matches2 = pattern1.matcher("aa\nbb").matches();

    System.out.println(matches2);
    //Output false, the default dot (.) does not match the newline character
    Pattern pattern2 = Pattern.compile(regex);
    boolean matches3 = pattern2.matcher("aaabbb").matches();
    boolean matches4 = pattern2.matcher("aa\nbb").matches();
    System.out.println(matches3);
    System.out.println(matches4);

}
Enter fullscreen mode Exit fullscreen mode

}

//true
//true
//true
//false

`

But if you add a Pattern.DOTALLparameters, even if there is \n, it will match. Therefore, the later version fixes the use ofPattern.DOTALL

https://github.com/spring-projects/spring-security/commit/70863952aeb9733499027714d38821db05654856

References

Top comments (0)