Invia #993200: YunaiV (zhijiantianya) ruoyi-vue-pro (Yudao) master-jdk17 @ 7cab3a7 (2026-09-20); master and master-jdk25 are affected as well; no fixed version as of 2026-09-21 Authenticationinformazioni

TitoloYunaiV (zhijiantianya) ruoyi-vue-pro (Yudao) master-jdk17 @ 7cab3a7 (2026-09-20); master and master-jdk25 are affected as well; no fixed version as of 2026-09-21 Authentication
Descrizioneruoyi-vue-pro (Yudao) does not limit failed SMS verification-code attempts. SmsCodeServiceImpl.validateSmsCode0() (yudao-module-system/.../service/sms/SmsCodeServiceImpl.java, lines 93-110) only checks, in order: (1) does a record exist for this mobile+code+scene, (2) is it expired, (3) is it already used. There is no failed-attempt counter, no lockout, no delay and no CAPTCHA - and the system_sms_code table has no column for such a counter (columns are id, mobile, code, create_ip, scene, today_index, used, used_time, used_ip, creator, create_time, updater, update_time, deleted, tenant_id; today_index counts SENT codes only). Worse, the lookup matches on the code value itself (SmsCodeMapper.selectLastByMobile: selectOne(... eq mobile, eqIfPresent scene, eqIfPresent code ...)), so a wrong guess never touches the row that holds the valid code: failures neither increment anything nor invalidate the valid code. Only a successful verification consumes the record (SmsCodeServiceImpl.useSmsCode(), lines 79-86). Configuration shows the asymmetry (yudao-server/src/main/resources/application.yaml lines 344-349): expire-times 10m, send-frequency 1m, send-maximum-quantity-per-day 10, i.e. the SENDING side is limited but the VERIFICATION side is not. Two unauthenticated endpoints consume this check: POST /admin-api/system/auth/sms-login (a correct code issues an access token for the account owning that mobile number - login without a password) and POST /admin-api/system/auth/reset-password (a correct code allows resetting the password). Sending a code for the LOGIN scene (scene=21) requires no CAPTCHA at all: AdminAuthServiceImpl.sendSmsCode() (lines 117-131) validates the CAPTCHA only when scene equals SmsSceneEnum.ADMIN_MEMBER_RESET_PASSWORD (scene=23). AdminAuthServiceImpl.smsLogin() (lines 135-147) then calls useSmsCode() and createTokenAfterLoginSuccess(). Reproduction - the lab instance was deliberately hardened: started with --yudao.captcha.enable=true --yudao.sms-code.begin-code=1000 --yudao.sms-code.end-code=9999, i.e. CAPTCHA enforced AND a randomized 4-digit code range (so this is NOT the separate issue of the shipped constant 9999). Only two headers are used: Content-Type: application/json and tenant-id: 1; no Authorization header at any step. (1) Control: POST /admin-api/system/auth/send-sms-code with {"mobile":"18818260272","scene":23} returns {"code":400,"msg":"请求参数不正确:验证码不能为空"} - the CAPTCHA is genuinely enforced; the identical call with "scene":21 is accepted without any CAPTCHA. (2) Request a login code for the mobile of the built-in admin account: {"mobile":"18818260272","scene":21} -> {"code":0,"data":true}. 18818260272 is published in the shipped SQL seed (sql/mysql/ruoyi-vue-pro.sql), so no information gathering is required. (3) Brute force POST /admin-api/system/auth/sms-login with {"mobile":"18818260272","code":"§0000§"} over 0000-9999 using Burp Intruder: the successful response is easy to distinguish (638 bytes response length versus 474 bytes for rejected codes) and the hit came at payload 7504 - about 7.5k guesses in one run, with no HTTP 429 and no lockout at any point. The response was {"code":0,"msg":"","data":{"userId":1,"accessToken":"...","refreshToken":"...","expiresTime":...}}. (4) Identity self-proof: GET /admin-api/system/auth/get-permission-info with Authorization: Bearer <token from step 3> returned {"code":0,...,"user":{"id":1,"nickname":"芋道源码","username":"admin",...},"roles":["common","super_admin"],"permissions":[...]} - the brute-forced token belongs to the SUPER ADMINISTRATOR. Additional controls: 30 consecutive verification requests in about one second all returned HTTP 200 without any 429 or lockout; a wrong code returns {"code":1002014000,"msg":"验证码不存在"} and afterwards the correct code still works (failures neither lock the account nor invalidate the valid code); with a plain script (10 threads against the same lab host) the same attack recovered the code in 2045 attempts / 4.69 s, 6894 / 16.81 s and 8101 / 18.90 s (410-436 req/s), each ending in a successful login as admin, and the corresponding system_sms_code row showed used=1 afterwards. Note that a sending-side control does exist and works: two codes to one mobile within a minute return {"code":1002014005,"msg":"短信发送过于频繁"}. Impact: an unauthenticated attacker who knows a target account's mobile number can obtain that account's access token without any password (full account takeover); for the shipped admin account the mobile number is public in the seed SQL. The same missing limit also applies to /auth/reset-password, allowing the password itself to be changed. The CAPTCHA is not part of this attack chain. Remediation: add a per mobile+scene failed-attempt counter with lockout (invalidate the code and block further verification for e.g. 15 minutes after 3-5 failures), count every failed attempt, consider invalidating the code after repeated failures, provide a CAPTCHA-enable option for every send-sms-code scene (scene=21 currently has none, while a default of off is acceptable), stop reusing one verification mechanism for both password reset and login, and throttle/alert on abnormal verification volume per mobile and per IP.
Fonte⚠️ https://gist.github.com/Fulucky0-yuri/63e3a697ec79ffd41c1cbfdbb1fcd5b4
Utente
 Fulucky0 (UID 101511)
Sottomissione21/09/2026 17:02 (1 mese fa)
Moderazione24/10/2026 13:16 (1 month later)
StatoAccettato
Voce VulDB426104 [YunaiV/zhijiantianya ruoyi-vue-pro fino a 2026.09 SMS Code Verification SmsCodeServiceImpl.java SmsCodeServiceImpl.validateSmsCode0 rivelazione di informazioni]
Punti20

Do you need the next level of professionalism?

Upgrade your account now!