某EIS办公平台0day挖掘审计过程

环境搭建

机缘巧合下从好兄弟那拿套源码,好久没审计有点生疏了,搭个环境练练手

虚拟机选择win2012,在Internet信息服务(IIS)上新建网站,设置站点名称和安装路径

image-20251209150723462

在应用程序池中找到对应站点程序池,托管管道模式默认为集成,将其修改为经典模式.NET

image-20251209150806498

新建数据库,与 web.config 连接保持一致即可

1
2
3
4
5
6
7
8
set DBNAME=EIS
set SQLUSER=sa
set SQLPWD=password01!
set SQLSERVER=127.0.0.1,1433

<connectionStrings>
<add name="" connectionString="Data Source=.\SQLEXPRESS;User ID=sa;Password=password01!;Initial Catalog=EIS;Connection Timeout=30;Connect Retry Count=3;Connect Retry Interval=10;Pooling=true;Min Pool Size=0;Max Pool Size=100" providerName="System.Data.SqlClient" />
</connectionStrings>

按顺序依次导入sql文件

image-20251209154119012

Patch产品授权

该产品是需要产品授权的,访问 login.aspx 会提示无效或过期

image-20251209160140060

逆向破解算法,模拟生成授权licsense

应用启动时验证(CheckLicense)

位置: EkpBaseGlobal.Application_Start()EkpGlobal.CheckLicense()

流程:

  1. 读取文件授权 (Checker.p_GetLicense())

    • 读取 Bin/Landray.lic 文件
    • 获取CPU ID和MAC地址
    • 解密授权文件
    • 解析授权信息(10个字段)
    • 提取FullName
  2. 读取数据库授权

    • Mekp_EISProductInfo 表读取 LicenseKey
    • 使用相同的密钥和IV解密
    • 解析授权信息(2个字段:过期时间;FullName)
  3. 验证授权

    • 比较文件授权和数据库授权中的FullName是否一致
    • 如果是试用版,检查过期时间(逻辑:DateTime.Now < expiration → 返回false)
    • 如果验证通过,将授权信息存储到 Application 对象中
  4. 更新数据库授权(如果是试用版)

    • 将数据库授权更新为当前时间 + FullName

采用加密算法:3DES (TripleDES)

image-20251211173113794

  • 文件授权lic

    读取文件并解密(密钥:CPU + "xxxxxxx20800",IV:MAC地址)

    解析10个字段,关键字段:array[3] = "DeptName|FullName 存入 lisenceInfo.FullName

  • 时间校验if (DateTime.Now < result) return false ,如果当前时间 < 过期时间,返回false(验证失败)

  • FullName匹配lisenceInfo.FullName 必须与数据库完全一致

image-20251210202837573

根据 CPU MAC 生成

image-20251210203903443

注释鉴权代码重编译

如果觉得第一种方法比较复杂,也可以选择第二种简单粗暴的方法,直接注释掉核心鉴权代码

根据报错的关键字定位相关的文件,发现有以下这些文件参与鉴权,这些方法都来源于yard.Framework.dll

  • EkpGlobal.CheckLicense() - 核心授权检查

  • EkpPage.OnInit() - 页面初始化检查

  • EkpPage.Page_Load() - 页面加载检查

  • EkpUserControl.Page_Load() - 用户控件检查

image-20251209203429819

因此我们可以使用dnspy导入dll,再导出工程到vs里面进行重编译

image-20251209203552476

成功通过授权校验,访问系统首页

image-20251210204422495

web.config配置分析

根据 web.config 分析,以下路径配置为允许所有用户访问(<allow users="*" />

image-20251209150134451

(部分接口关键字替换了)整理如下:

配置路径 访问路径 说明
<location path="XX"> /XX/*.asmx WebService接口目录,所有ASMX文件可匿名访问
<location path="aaa"> /aaa/*.aspx 该模块目录下所有aspx文件可匿名访问
<location path="third"> /third/*.aspx 第三方集成目录
<location path="Global/Pages/CheckPage"> /Global/Pages/CheckPage/*.aspx 检查页面目录
<location path="Services/MobileDown.aspx"> /Services/MobileDown.aspx 移动端下载接口

漏洞分析

前台SSRF

  • 文件路径: bin/yard.UI.Integration/yard.UI.Integration.third.template/Service.cs
  • 方法: PostResponse third目录配置允许匿名访问

image-20251210202546180

  • link参数直接拼接: base.Request["link"] 直接拼接到URL中,未进行任何验证
  • 无URL白名单: 未限制可访问的URL范围,虽然固定使用http://,但可以通过传入link参数控制造成SSRF

image-20251210185333147

前台down.aspx SQL注入

image-20251209152614713

image-20251209205516967

前台Notify.asmx SQL注入

  • 文件路径: bin/yard.WebService/yard.WebService.WS.Notify/Notify.cs
  • LoginName参数直接拼接: person.LoginName 直接拼接到SQL查询中
1
2
3
4
5
6
7
8
9
10
11
12
[WebMethod]
public NotifyResult getTodo(string targets, int type, string otherCond, int rowSize, int pageNo)
{
Person person = JsonConvert.DeserializeObject<Person>(targets);
if (!string.IsNullOrEmpty(person.LoginName))
{
// SQL注入点: LoginName参数直接拼接
string oneStrValue = yard.DataAccess.DataAccess.GetOneStrValue(
$"SELECT id FROM FI_ORG_EMP where account=N'{person.LoginName}'");
// ...
}
}

构造请求,通过xp_cmdshell写入文件1.txt

image-20251209115522898

前台任意用户添加

  • 文件路径: bin/yard.WebService/yard.WebService.WS/UserInfo.cs
  • User 方法使用 [WebMethod] 标记,WS 目录被配置为允许所有用户访问,该方法无权限校验可直接执行数据库插入操作
  • 继承于WebService可直接传入账号密码等信息新建用户,首先在数据库中查询是否存在该⽤户, 如果不存在就创建

image-20251209152718954

image-20251209141936259

FI_ORG_EMP为员工主表,可看到成功新建用户

image-20251209141539766

后台UploadLogImg.aspx任意文件上传

文件 bin\yard.Admin.UI\yard.Admin.UIUploadLogImg.cs提供了saveBg()saveLogo() 两个上传的方法

string text = base.Server.MapPath这个地方路径不可控,默认会写到/App_Themes/Login下面,文件名为日期命名

image-20251210205736396

构造方法上传

image-20251210164654020

/App_Themes/Login不允许,因此这个上传无法shell

image-20251210205945227

后台 bulkinsert_data.aspx任意文件上传

但是不要灰心,这个点找不到就找下一个,继续搜 SaveAs(filename)

image-20251210210421328

  • 这里还有两个值是必须的,__VIEWSTATE__VIEWSTATEGENERATOR
  • 当用户首次访问 /XX/bulkinsert_data.aspx?id=1 时,页面加载 - Page_Load 方法执行,所有服务器控件初始化,使用 Base64 编码,生成 __VIEWSTATE__VIEWSTATEGENERATOR 值,ASP.NET 会验证,没有 ViewState 会导致 Invalid ViewState 错误,控件状态无法恢复
1
2
3
4
5
6
string text = "/files/";  // 固定基础路径
string fileName = tpfile.PostedFile.FileName; // 用户输入
int num = fileName.LastIndexOf("\\"); // 查找最后一个反斜杠
string empty = string.Empty;
empty = ((num <= 0) ? ("\\" + fileName) : fileName.Substring(num));
tpfile.PostedFile.SaveAs(base.Server.MapPath(text + empty));

可进行目录穿越,代码会取最后一个 \ 之后的部分,但是正斜杠可以正常绕过

  • ..\third\shell.aspxempty = "shell.aspx"
  • ../third/shell.aspxempty = "third/shell.aspx" ✅️

结合web.config,可写到 third 路径下

image-20251210164143135