Skip to content

[vulnerability] Admin login lacks rate limiting and captcha enforcement (CWE-307) #584

Description

@s1mple-top

Summary

The /admin/auth/login endpoint provides no anti-automation protection: the
captcha verification has been removed (commented out), repeated failures do not
trigger any account lockout, and there is no IP/account-level rate limiting.
An attacker can therefore perform unlimited online brute-force attacks against
administrator credentials and eventually take over the admin backend.

  • Type: Authentication protection weakness / Improper Restriction of Excessive Authentication Attempts
  • CWE: CWE-307
  • Severity: Medium (High when combined with default credentials)
  • Component: litemall-admin-api

Affected Endpoint

POST /admin/auth/login
Source file:

litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminAuthController.java

Details

1. Captcha verification is commented out

In AdminAuthController.login(...), both the parsing and the comparison of the
code field are commented out. The captcha is still generated and written to
the session, but it is never read or checked during login ("generate only, never
verify"):

public Object login(@RequestBody String body, HttpServletRequest request) {
    String username = JacksonUtil.parseString(body, "username");
    String password = JacksonUtil.parseString(body, "password");
//  String code = JacksonUtil.parseString(body, "code");

    if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
        return ResponseUtil.badArgument();
    }
//  if (StringUtils.isEmpty(code)) {
//      return ResponseUtil.fail(ADMIN_INVALID_KAPTCHA_REQUIRED, "验证码不能为空");
//  }
//
//  HttpSession session = request.getSession();
//  String kaptcha = (String)session.getAttribute("kaptcha");
//  if (Objects.requireNonNull(code).compareToIgnoreCase(kaptcha) != 0) {
//      return ResponseUtil.fail(ADMIN_INVALID_KAPTCHA, "验证码不正确", doKaptcha(request));
//  }

    Subject currentUser = SecurityUtils.getSubject();
    try {
        currentUser.login(new UsernamePasswordToken(username, password));
    } ...
}

Note: the frontend was changed consistently — in
litemall-admin/src/views/login/index.vue the captcha form item is also wrapped
in an HTML comment, and loginRules contains only username / password.
So this is not a frontend/backend inconsistency; the captcha feature has been
disabled on both ends. The security consequence is what matters here: there is no
human-interaction barrier left on the login endpoint.

2. No failure lockout

AdminAuthorizingRealm.doGetAuthenticationInfo(...) only throws
UnknownAccountException on a bad password. There is no failed-attempt counter,
no lockout, and no RetryLimit/HashedCredentialsMatcher configuration in the
Shiro chain.

3. No rate limiting

Neither the endpoint nor the Shiro filter chain applies any IP- or account-level
rate limiting.

4. Default seed credentials (impact amplifier)

litemall-db/sql/litemall_data.sql ships default admin accounts:

admin123 / admin123
mall123  / mall123
promotion123 / promotion123

Steps to Reproduce

All tests were performed against a local instance (http://localhost:8080/litemall).

A. Login without sending code (10 attempts)

for i in $(seq 1 10); do
  curl -s -X POST http://localhost:8080/litemall/admin/auth/login \
    -H 'Content-Type: application/json' \
    -d '{"username":"admin123","password":"admin123"}'
  echo
done

Result: 10/10 returned "errno":0 (success).
B. Login with a fixed wrong code (10 attempts)
for i in $(seq 1 10); do
curl -s -X POST http://localhost:8080/litemall/admin/auth/login
-H 'Content-Type: application/json'
-d '{"username":"admin123","password":"admin123","code":"WRONG123"}'
echo
done
Result: 10/10 returned "errno":0 (success). If the captcha were enforced,
a dedicated error code would be returned instead.

C. Cross-check — response depends only on password, not on code

correct password + empty code errno:0
correct password + wrong code errno:0
wrong password + empty code errno:605
wrong password + wrong code errno:605

The response code varies only with the password, never with code.

D. Brute-force feasibility (no lockout)###

# 10 attempts with wrong passwords, then the 11th with the correct one

Result: all 10 wrong attempts returned errno:605, no lockout occurred, and the
11th request with the correct password returned errno:0 — the account was
never locked.

Impact

  • Administrator credentials can be brute-forced online without any delay,
    captcha, or lockout constraint.
  • Once a valid admin session is obtained, the attacker can reach other admin
    endpoints, e.g.:
    • SQL injection in /admin/order/list (nickname/consignee/orderSn);
    • Arbitrary file deletion in /admin/storage/delete (executed with root
      privileges).
  • With the shipped default credentials, no brute-force is even required — the
    accounts are directly accessible.

Root Cause

The login endpoint has the captcha verification removed, and there is no
failed-authentication rate limiting or account lockout mechanism.

Suggested Fix

  1. Restore the captcha verification in AdminAuthController.login(...) and
    re-enable the corresponding field in the frontend login form.
  2. Add failed-attempt counting + account lockout, or IP-level rate limiting
    (e.g. Shiro HashedCredentialsMatcher with a retry limit, or a servlet
    filter / gateway limiter).
  3. Force a password change on first login for the seed accounts, and remove the
    hardcoded default credentials from the shipped SQL.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions