如何从灰盒角度快速复现Weaver最新SQL注入

如何从灰盒角度快速复现Weaver最新SQL注入

​ 好久没写不加密的博客了,因为比较简单外面也半公开了,就简单写写思路吧,至于POC就不直接贴出来了,这篇文章主要是面向像漂亮鼠这样的Java新手

​ 作为安全从业者的我们,每天都会面对各种各样的补丁和系统更新,很多时候不知道该如何下手,尤其是对于初学者,在尝试复现闭源漏洞时,经常会感到无从下手。面对复杂的系统架构、冗长的代码,以及各种陌生的逻辑,难免让人觉得压力山大。再加上互联网上铺天盖地的漏洞分析文章,往往结构化的内容是这样的,当然也包括我自己在内:“首先,系统采用了XX+XXX架构,文件结构是XXX,鉴权点位于XXX,漏洞点在XXX,参数xxx需要xxx构造。”;诚然,这样的写作方式没有任何问题,写作的目的更多是为了记录自己的分析与成长的过程,安全类文章的受众也只会是同一层次的人,但对于新手来说,却容易陷入一个误区:“我啥都不会,怎么可能独立分析出这个漏洞?为啥路由前加一个../就饶过了?这么复杂的东西,我到底该从哪里开始?”。

​ 然而,最简单的道理就是实战出新知。对于一年四季应急的大多数漏洞而言,它们也或许并没有想象中那么难。仔细想想,当你第一次接触到某个系统及其补丁时,真的需要一开始就纠结于那些复杂的架构和鉴权点吗?很多时候,只需要简单地访问一下页面,看看返回结果,就能初步了解鉴权的情况。至于参数是怎么传递的、调用链路是如何运作的,这些问题真的需要一开始就搞清楚吗?多试几次,答案自然就会浮现。

​ 代码审计没有捷径可走,但如果你连漏洞复现后的那种多巴胺激励过程都没有体验过,很难想象作为一名新手,你还能有兴趣并愿意投入大量时间去深入钻研这个领域。

​ 当然以上内容纯属个人吹逼,只是一些可个人想法罢了,那么接下来就进入正题吧,当然文章以“灰盒”为题可能不太恰当,但暂时没想到更合适的词。

补丁分析

补丁内容有很多部分,由于仅公开了SQL注入,这里就以这部分展开

以下部分中不难看出,对于路径/api/doc/out/more/list,如果传入了”elementmore”、”__random__“、“isNew”以外的参数就判定为漏洞利用,同时也要求了elementMore中的key,比较简单粗暴。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
if (path.contains("/api/") && path.contains("/doc/") && path.contains("/out/") && path.contains("/more/") && path.contains("/list")) {
bb = new BaseBean();
isFix = sc.getIntValue(bb.getPropValue("weaver_api_out_doc_sql_fix", "list.isFix"));
if (isFix == 1) {
return true;
}

elementMore = sc.null2String(req.getParameter("elementmore"));
Enumeration<String> params = req.getParameterNames();

while(params.hasMoreElements()) {
param = (String)params.nextElement();
if (!"elementmore".equals(param) && !"__random__".equals(param) && !"isNew".equals(param)) {
sc.putToTmpForbiddenIpMap(ThreadVarManager.getIp(), req.getRequestURI(), "漏洞利用");
sc.writeLog(">>>>Xss(Validate failed[URL ACCESS REJECT]) validateClass=weaver.security.rules.SecurityRuelForEc0704 path=" + req.getRequestURL() + " param:" + param + " not allowed. security validate failed! source ip:" + ThreadVarManager.getIp());
return false;
}
}

if (!"".equals(elementMore)) {
try {
JSONObject json = JSONObject.fromObject(elementMore);
Set<String> keys = json.keySet();
Iterator var12 = keys.iterator();

while(var12.hasNext()) {
key = (String)var12.next();
if (!"newsCatalogIds".equals(key)) {
sc.putToTmpForbiddenIpMap(ThreadVarManager.getIp(), req.getRequestURI(), "漏洞利用");
sc.writeLog(">>>>Xss(Validate failed[URL ACCESS REJECT]) validateClass=weaver.security.rules.SecurityRuelForEc0704 path=" + req.getRequestURL() + " key:" + key + " not allowed. security validate failed! source ip:" + ThreadVarManager.getIp());
return false;
}
}

if (json.containsKey("newsCatalogIds")) {
JSONArray jsonArray = json.getJSONArray("newsCatalogIds");

for(int i = 0; i < jsonArray.size(); ++i) {
val = sc.null2String(jsonArray.getString(i));
if (!"".equals(val) && sc.getIntValue(val, -999999) == -999999) {
sc.putToTmpForbiddenIpMap(ThreadVarManager.getIp(), req.getRequestURI(), "漏洞利用");
sc.writeLog(">>>>Xss(Validate failed[URL ACCESS REJECT]) validateClass=weaver.security.rules.SecurityRuelForEc0704 path=" + req.getRequestURL() + " newsCatalogIds:" + jsonArray.toString() + " not allowed. security validate failed! source ip:" + ThreadVarManager.getIp());
return false;
}
}
}
} catch (Exception var16) {
sc.writeError(var16);
sc.writeLog(">>>>Xss(Validate failed[ERROR]) validateClass=weaver.security.rules.SecurityRuelForEc0704 path=" + req.getRequestURL() + " json invalid, elementMore=" + elementMore + ". security validate failed! source ip:" + ThreadVarManager.getIp());
return false;
}
}
}

实战复现

复现为主,代码不算新

注入点浅析

idea对路径搜索挺好的双击shift查找路径不难发现对应对应com.api.doc.search.web.DocOutMoreAction#getDataList,接下来我们查看其代码逻辑,有一个elementmore、isNew

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public String getDataList(@Context HttpServletRequest var1, @Context HttpServletResponse var2) throws Exception {
Object var3 = new HashMap();

try {
String var4 = Util.null2String(var1.getParameter("elementmore"));
String var5 = Util.null2String(var1.getParameter("isNew"));
DocListUtil var6 = new DocListUtil(var1, (User)null, DocTableType.SEARCH_DOC_TABLE, false);
String var7 = var6.getSqlWhere();
DocNewsService var8 = new DocNewsService();
if ("1".equals(var5)) {
var8.isNew(var5);
}

var3 = var8.getMoreList(var4, (User)null, var7, (Map)null);
} catch (Exception var9) {
var9.printStackTrace();
((Map)var3).put("api_status", false);
((Map)var3).put("api_errormsg", "catch exception : " + var9.getMessage());
}

return JSONObject.toJSONString(var3);
}

深入DocListUtil,不难发现存在很多拼接点,简单了解一眼即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
package com.api.doc.search.util;

import com.api.browser.util.ConditionType;
import com.engine.doc.util.CheckPermission;
import com.engine.doc.util.TimeZoneUtil;
import com.engine.hrm.biz.HrmClassifiedProtectionBiz;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import weaver.conn.RecordSet;
import weaver.general.Util;
import weaver.hrm.User;
import weaver.systeminfo.setting.HrmUserSettingComInfo;

public class DocListUtil {
private String sqlWhere = " t1.id=t2.sourceid ";
private boolean needRight = true;

public DocListUtil(HttpServletRequest var1, User var2, DocTableType var3) {
this.packageCondition(var1, var2, var3);
}

public DocListUtil(HttpServletRequest var1, User var2, DocTableType var3, boolean var4) {
if (!var4 || var2 == null) {
this.sqlWhere = "";
this.needRight = false;
}

this.packageCondition(var1, var2, var3);
}

private void packageCondition(HttpServletRequest var1, User var2, DocTableType var3) {
String var4 = "";
String var6;
if (this.needRight) {
HrmUserSettingComInfo var5 = new HrmUserSettingComInfo();
var6 = var5.getBelongtoshowByUserId(var2.getUID() + "");
var4 = var2.getBelongtoids();
if ("1".equals(var6) && "0".equals(var2.getAccount_type()) && !"".equals(var4)) {
var4 = var4 + "," + var2.getUID();
} else {
var4 = "";
}
}

Enumeration var17 = var1.getParameterNames();
var6 = "";
String var7 = "";
if (var3 == DocTableType.DOC_COPYMOVE) {
this.sqlWhere = " t1.seccategory = " + var1.getParameter("sourceId");
}

String var8;
String var9;
boolean var10;
while(var17.hasMoreElements()) {
var8 = (String)var17.nextElement();
var9 = Util.null2String(var1.getParameter(var8));
var10 = true;
DocCondition[] var11 = DocCondition.values();
int var12 = var11.length;

String var15;
for(int var13 = 0; var13 < var12; ++var13) {
DocCondition var14 = var11[var13];
if (!var9.isEmpty()) {
if (var14.getConditionType() == ConditionType.DATE) {
if (var8.equals(var14.getName() + ConditionUtil.DATE_SELECT) && !"6".equals(var9)) {
this.sqlWhere = this.sqlWhere + packDateType(var14.getName(), var9, var14);
} else {
String var16;
if (var8.equals(var14.getName() + ConditionUtil.DATE_FROM)) {
var15 = TimeZoneUtil.getServerDate1(var9, "begin");
if (!"doclastmoddate".equals(var14.getName()) && !"doccreatedate".equals(var14.getName())) {
this.sqlWhere = this.sqlWhere + " and " + var14.getName() + ">='" + var15 + "'";
} else {
var16 = TimeZoneUtil.getServertime1(var9, "begin");
this.sqlWhere = this.sqlWhere + TimeZoneUtil.handDateCondition("6", var9, "", var14.getName(), "", false);
}
} else if (var8.equals(var14.getName() + ConditionUtil.DATE_TO)) {
var15 = TimeZoneUtil.getServerDate1(var9, "end");
if (!"doclastmoddate".equals(var14.getName()) && !"doccreatedate".equals(var14.getName())) {
this.sqlWhere = this.sqlWhere + " and " + var14.getName() + "<='" + var15 + "'";
} else {
var16 = TimeZoneUtil.getServertime1(var9, "end");
this.sqlWhere = this.sqlWhere + TimeZoneUtil.handDateCondition("6", "", var9, var14.getName(), "", false);
}
}
}
} else {
if (var14.getConditionType() == ConditionType.SCOPE) {
if (var8.equals(DocCondition.REPLAY_DOC_COUNT + ConditionUtil.INTERVAL_FROM)) {
var15 = Util.getIntValue(var9, 0) + "";
if (var14.getDbType() != DbType.INT) {
var15 = "'" + var15.replace("'", "'") + "'";
}

this.sqlWhere = this.sqlWhere + " and " + var14.getName() + " >= " + var15;
} else if (var8.equals(DocCondition.REPLAY_DOC_COUNT + ConditionUtil.INTERVAL_TO)) {
var15 = Util.getIntValue(var9, 0) + "";
if (var14.getDbType() != DbType.INT) {
var15 = "'" + var15.replace("'", "'") + "'";
}

this.sqlWhere = this.sqlWhere + " and " + var14.getName() + " <= " + var15;
}
}

if (var14.getName().equals(var8)) {
var10 = false;
if (var14.getLogic() == LogicOperation.CUSTOM) {
if (var8.equals(DocCondition.DOC_SUBJECT.getName())) {
this.sqlWhere = this.sqlWhere + " and " + this.getDocSubjectSql(var9);
break;
}

if (var8.equals(DocCondition.KEYWORD.getName())) {
this.sqlWhere = this.sqlWhere + " and " + this.getKeyWordSql(var9);
break;
}

if (var8.equals(DocCondition.DEPARTMENT_ID.getName())) {
this.sqlWhere = this.sqlWhere + " and exists(select 1 from HrmResource where departmentid=" + var9 + " and id=t1.doccreaterid)";
break;
}

if (var8.equals(DocCondition.CREATER_SUBCOMPANY_ID.getName())) {
this.sqlWhere = this.sqlWhere + " and exists(select 1 from HrmResource where subcompanyid1=" + var9 + " and id=t1.doccreaterid)";
break;
}

if (var8.equals(DocCondition.OWNER_DEPARTMENT_ID.getName())) {
this.sqlWhere = this.sqlWhere + " and exists(select 1 from HrmResource where departmentid=" + var9 + " and id=t1.ownerid)";
break;
}

if (var8.equals(DocCondition.OWNER_SUBCOMPANY_ID.getName())) {
this.sqlWhere = this.sqlWhere + " and exists(select 1 from HrmResource where subcompanyid1=" + var9 + " and id=t1.ownerid)";
break;
}

if (!var8.equals(DocCondition.DATE2DURING.getName())) {
if (var8.equals(DocCondition.DOC_STATUS.getName())) {
var6 = var9;
} else if (var8.equals(DocCondition.TREE_DOC_FIELD_ID.getName())) {
var9 = var9.startsWith(",") ? var9.substring(1) : var9;
var9 = var9.endsWith(",") ? var9.substring(0, var9.length() - 1) : var9;
var9 = var9.indexOf(",") == -1 ? " = " + var9 : " in (" + var9 + ")";
this.sqlWhere = this.sqlWhere + " and exists(select 1 from DocDummyDetail where docid=t1.id and catelogid " + var9 + ")";
}
break;
}

if (Util.getIntValue(var9, 0) <= 36 && Util.getIntValue(var9, 0) >= 1) {
this.sqlWhere = this.sqlWhere + " and t1.doclastmoddate>='" + getDate2During(Util.getIntValue(var9, 0)) + "'";
break;
}
} else {
if (var14.getLogic() == LogicOperation.EQ_OR_IN) {
var9 = var9.startsWith(",") ? var9.substring(1) : var9;
var9 = var9.endsWith(",") ? var9.substring(0, var9.length() - 1) : var9;
if (var9.indexOf(",") == -1) {
if (var14.getDbType() == DbType.VARCHAR) {
var9 = "'" + var9.replace("'", "''") + "'";
}

var9 = " = " + var9;
} else {
if (var14.getDbType() == DbType.VARCHAR) {
var9 = "'" + var9.replace("'", "''").replace(",", "','") + "'";
}

var9 = " in (" + var9 + ")";
}

if (var14 != DocCondition.SUBSCRIPTION_APPROVE_DATE && var14 != DocCondition.SUBSCRIPTION_DATE && var14 != DocCondition.SUBSCRIPTION_STATE) {
this.sqlWhere = this.sqlWhere + " and t1." + var8 + var9;
break;
}

this.sqlWhere = this.sqlWhere + " and ds." + var8 + var9;
break;
}

if (var14.getLogic() == null) {
break;
}

var15 = var14.getLogic().getCode();
if (var15 != null) {
if (var14.getDbType() == DbType.VARCHAR) {
if (var15.indexOf("{#}") > -1) {
var9 = var9.replace("'", "''");
} else {
var9 = "'" + var9.replace("'", "''") + "'";
}
}

if (var14 != DocCondition.SUBSCRIPTION_APPROVE_DATE && var14 != DocCondition.SUBSCRIPTION_DATE && var14 != DocCondition.SUBSCRIPTION_STATE) {
this.sqlWhere = this.sqlWhere + " and t1." + var8 + (var15.indexOf("{#}") > -1 ? var15.replace("{#}", var9) : var15 + var9);
break;
}

this.sqlWhere = this.sqlWhere + " and ds." + var8 + (var15.indexOf("{#}") > -1 ? var15.replace("{#}", var9) : var15 + var9);
break;
}
}
}
}
}
}

if (var10 && ConditionUtil.isCustomParamNameNew(var8) && ConditionUtil.isCustomParamValue(var8)) {
String var18 = ConditionUtil.getCustomIdNew(var8);
var12 = Util.getIntValue(Util.null2String(var1.getParameter("seccategory")));
if (var3 == DocTableType.DOC_COPYMOVE) {
this.sqlWhere = " t1.seccategory = " + var1.getParameter("sourceId");
}

String var19 = Util.null2String(var1.getParameter(ConditionUtil.CUSTOM_KEY_PREV + var18 + ConditionUtil.CUSTOM_KEY_OPT), "-1");
var15 = ConditionUtil.getCustomSql(var18, var12, var2, var9, var19);
if (!var15.isEmpty()) {
var7 = var7 + " and " + var15;
}
}
}

var8 = this.needRight ? (var4.isEmpty() ? " = " + var2.getUID() : " in (" + var4 + ")") : "";
var6 = var6.startsWith(",") ? var6.substring(1) : var6;
var6 = var6.endsWith(",") ? var6.substring(0, var6.length() - 1) : var6;
if (var3 == DocTableType.MY_DOC_TABLE) {
if (var6.isEmpty()) {
this.sqlWhere = this.sqlWhere + " and t1.docstatus != 8 and t1.docstatus != 9 and t1.docstatus <=9";
} else if (!"-1".equals(var6) && !"0".equals(var6)) {
this.sqlWhere = this.sqlWhere + " and t1.docstatus " + (var6.indexOf(",") > -1 ? "in (" + var6 + ")" : "=" + var6);
} else {
this.sqlWhere = this.sqlWhere + " and t1.docstatus <1";
}

this.sqlWhere = this.sqlWhere + " and (t1.doccreaterid " + var8 + " or t1.ownerid " + var8 + ")";
} else if (var3 == DocTableType.DOC_BATCHSHARE) {
if (var6.isEmpty()) {
if (this.needRight) {
this.sqlWhere = this.sqlWhere + " and (t1.docstatus in(1,2,5) or (t1.docstatus=7 and (t1.doccreaterid=" + var2.getUID() + " or t1.ownerid=" + var2.getUID() + ")))";
} else {
this.sqlWhere = this.sqlWhere + " and t1.docstatus in(1,2,5) ";
}
} else {
var6 = var6.equals("1") ? "1,2" : var6;
this.sqlWhere = this.sqlWhere + " and t1.docstatus " + (var6.indexOf(",") > -1 ? "in (" + var6 + ")" : "=" + var6);
}
} else if (var3 != DocTableType.DOC_COPYMOVE && var3 != DocTableType.ENGINE_DOC_BATCHSHARE) {
if (var3 == DocTableType.DOC_RECYCLE) {
if (!var6.isEmpty()) {
var6 = var6.equals("1") ? "1,2" : var6;
this.sqlWhere = this.sqlWhere + " and t1.docstatus " + (var6.indexOf(",") > -1 ? "in (" + var6 + ")" : "=" + var6);
}
} else if (var3 == DocTableType.ENGINE_DOC_RECYCLE) {
if (!var6.isEmpty()) {
var6 = var6.equals("1") ? "1,2" : var6;
this.sqlWhere = this.sqlWhere + " and t1.docstatus " + (var6.indexOf(",") > -1 ? "in (" + var6 + ")" : "=" + var6);
}
} else if (var3 == DocTableType.DOC_SUBSCRIPTION_HISTORY) {
if (var6.isEmpty()) {
if (this.needRight) {
this.sqlWhere = this.sqlWhere + " and (t1.docstatus in(1,2,5) or (t1.docstatus=7 and (t1.doccreaterid=" + var2.getUID() + " or t1.ownerid=" + var2.getUID() + ")))";
} else {
this.sqlWhere = this.sqlWhere + " and t1.docstatus in(1,2,5) ";
}
} else {
var6 = var6.equals("1") ? "1,2" : var6;
this.sqlWhere = this.sqlWhere + " and t1.docstatus " + (var6.indexOf(",") > -1 ? "in (" + var6 + ")" : "=" + var6);
}

if (var3 == DocTableType.NEWEST_DOC || var3 == DocTableType.NO_READ_DOC) {
this.sqlWhere = this.sqlWhere + " and t1.doccreaterid " + (var4.isEmpty() ? "<> " + var2.getUID() : "not in (" + var4 + ")");
}
} else if (var3 == DocTableType.ENGINE_DOC_PROP_SET) {
this.sqlWhere = this.sqlWhere + " and t1.docstatus in(1,2,5) and t1.docextendname = 'html'";
} else {
if (var6.isEmpty()) {
if (this.needRight) {
this.sqlWhere = this.sqlWhere + " and (t1.docstatus in(1,2,5) or (t1.docstatus=7 and (t1.doccreaterid=" + var2.getUID() + " or t1.ownerid=" + var2.getUID() + ")))";
} else {
this.sqlWhere = this.sqlWhere + " and t1.docstatus in(1,2,5) ";
}
} else {
var6 = var6.equals("1") ? "1,2" : var6;
this.sqlWhere = this.sqlWhere + " and t1.docstatus " + (var6.indexOf(",") > -1 ? "in (" + var6 + ")" : "=" + var6);
}

if (var3 == DocTableType.NEWEST_DOC || var3 == DocTableType.NO_READ_DOC) {
this.sqlWhere = this.sqlWhere + " and t1.doccreaterid " + (var4.isEmpty() ? "<> " + var2.getUID() : "not in (" + var4 + ")");
}
}
} else if (var6.isEmpty()) {
this.sqlWhere = this.sqlWhere + " and t1.docstatus in(1,2,5,7)";
} else {
var6 = var6.equals("1") ? "1,2" : var6;
this.sqlWhere = this.sqlWhere + " and t1.docstatus " + (var6.indexOf(",") > -1 ? "in (" + var6 + ")" : "=" + var6);
}

var9 = Util.null2String(var1.getParameter("isNew"));
if ("yes".equals(var9) || var3 == DocTableType.NEWEST_DOC || var3 == DocTableType.NO_READ_DOC) {
this.sqlWhere = this.sqlWhere + " and not exists(select 1 from docReadTag where t1.id=docid and userid " + var8 + " and usertype=1)";
}

if (!var7.isEmpty()) {
this.sqlWhere = this.sqlWhere + " and exists(select 1 from cus_fielddata tcm where scope='" + ConditionUtil.CUSTOM_SCOPE + "' and id=t1.id " + var7 + ")";
}

if (var3 == DocTableType.DOC_BATCHSHARE && this.needRight) {
this.sqlWhere = this.sqlWhere + " and exists(select 1 from DocSecCategory where DocSecCategory.id=t1.secCategory and DocSecCategory.shareable='1')";
}

if (var3 == DocTableType.DOC_OUT_TABLE) {
var10 = CheckPermission.isOpenSecret();
if (var10) {
this.sqlWhere = this.sqlWhere + " and t1.secretLevel=4";
}
} else {
this.sqlWhere = this.sqlWhere + getSecretSql(var2, " and t1.secretLevel");
}

if (var3 != DocTableType.DOC_SUBSCRIPTION_HISTORY && var3 != DocTableType.DOC_SUBSCRIPTION_APPROVE && var3 != DocTableType.DOC_SUBSCRIPTION_BACK && var3 != DocTableType.DOC_RECYCLE) {
this.sqlWhere = this.sqlWhere + " and (t1.ishistory is null or t1.ishistory = 0)";
}

if (var3 == DocTableType.DOC_RECYCLE) {
this.sqlWhere = this.sqlWhere + " and t1.ishistory != 1 ";
this.sqlWhere = this.sqlWhere + " and t1.docdeleteuserid=" + var2.getUID();
} else if (var3 == DocTableType.ENGINE_DOC_RECYCLE) {
this.sqlWhere = this.sqlWhere + " and t1.ishistory != 1 ";
} else {
this.sqlWhere = this.sqlWhere + " and (t1.isreply is null or t1.isreply='' or t1.isreply='0')";
}

this.sqlWhere = this.sqlWhere.trim().startsWith("and") ? this.sqlWhere.substring(4) : this.sqlWhere;
this.packDefaultValue(var1, var2, var3);
}

public String getSqlWhere() {
return this.sqlWhere;
}
}

回到上一层,接下来通过getSqlWhere获取变量var7,之后与其他参数一起传入getMoreList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
xxxx
DocListUtil var6 = new DocListUtil(var1, (User)null, DocTableType.SEARCH_DOC_TABLE, false);
String var7 = var6.getSqlWhere();
DocNewsService var8 = new DocNewsService();
if ("1".equals(var5)) {
var8.isNew(var5);
}

var3 = var8.getMoreList(var4, (User)null, var7, (Map)null);
} catch (Exception var9) {
var9.printStackTrace();
((Map)var3).put("api_status", false);
((Map)var3).put("api_errormsg", "catch exception : " + var9.getMessage());
}

return JSONObject.toJSONString(var3);
}

接下来getDocList也是一堆的拼接,最终形成一个xml模板保存到sessionkey并返回,有兴趣可以深入了解这个点,对于复现其他补丁的漏洞会有帮助(代码不贴全了大概知道意思即可)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
public Map<String, Object> getDocList(String var1, String var2, int var3, DocTableType var4, User var5, Map<String, String> var6) throws Exception {
HashMap var7 = new HashMap();
Object var43 = var6 == null ? new HashMap() : var6;
String var8 = TimeUtil.getCurrentDateString();
String var9 = "from DocDetail t1 ";
if (var2 != null && !var2.isEmpty()) {
var9 = var9 + ",(" + var2 + ") t2";
}

String var10 = "";
String var11 = "t1.id,t1.id docstatus_id,t1.seccategory,t1.docvestin,t1.doclastmoddate,t1.doclastmodtime,t1.docsubject,t1.docextendname,t1.doccreaterid,t1.secretLevel,t1.usertype";
var11 = var11 + ",t1.ownerid,t1.docstatus,t1.doccreatedate,t1.doccreatetime,t1.accessorycount,t1.replaydoccount,t1.sumDownload,t1.sumReadCount,t1.sumMark,t1.docpubdate,t1.docpubtime,t1.docapprovedate,t1.docapprovetime";
String var12 = "";
String var13 = "";
String var14 = "doclastmoddate,doclastmodtime,id";
String var15 = "desc";
String var16 = "";
String var17 = "";
RecordSet var18 = new RecordSet();
String var19 = var18.getDBType();
String var20 = "isnull";
if ("oracle".equals(var19)) {
var20 = "nvl";
} else if ("mysql".equals(var19)) {
var20 = "ifnull";
}

if (((Map)var43).get("__custormOrderBy") != null) {
var14 = (String)((Map)var43).get("__custormOrderBy");
var15 = "";
}

this.isChatcooper = this.getChatcooper().equals(((Map)var43).get("tabTitle"));
String var21 = "";
String var22 = "";
String var23 = "";
String var24 = "";
String var25 = "";
String var26 = "";
boolean var27 = true;
int var44;
if (var5 != null) {
var44 = var5.getLanguage();
HrmUserSettingComInfo var28 = new HrmUserSettingComInfo();
var21 = var28.getBelongtoshowByUserId(var5.getUID() + "");
var22 = var5.getBelongtoids();
var23 = var5.getAccount_type();
var24 = var5.getLogintype() + "+" + var5.getUID();
var25 = var5.getLogintype() + "_" + var5.getUID() + "_" + var5.getSeclevel() + "_" + var5.getType() + "_" + var5.getUserDepartment() + "_" + var5.getUserSubCompany1();
var26 = "column:seccategory+column:docstatus+column:doccreaterid+column:ownerid+column:sharelevel+column:id";
} else {
var44 = DocNewsService.getOutDocLanguage();
}

HashMap var45 = new HashMap();
ArrayList var29 = new ArrayList();
boolean var30 = CheckPermission.isOpenSecret();
String var31;
xxxxxxxxxxxx省略一大堆
var29.add("docpubdate");
var45.put("docapprovedate", "<col width=\"10%\" display=\"false\" orderkey=\"docapprovedate,docapprovetime\" text=\"" + SystemEnv.getHtmlLabelName(1425, var44) + "\" labelid=\"1425\" column=\"docapprovedate\" transmethod=\"com.engine.doc.util.TimeZoneUtil.getYmdLocaleOnlyDate\" otherpara=\"column:docapprovetime\" />");
var29.add("docapprovedate");
var45.put("invalidationdate", "<col width=\"10%\" display=\"false\" orderkey=\"invalidationdate\" text=\"" + SystemEnv.getHtmlLabelName(19547, var44) + "\" labelid=\"19547\" column=\"invalidationdate\" />");
var29.add("invalidationdate");

var32 = var32 + "<sql outfields=\"" + var12 + "\" backfields=\"" + var11 + "\" sqlform=\"" + Util.toHtmlForSplitPage(var9) + "\" sqlorderby=\"" + var14 + "\" sqlprimarykey=\"t1.id\" sqlsortway=\"" + var15 + "\" sqlwhere=\"" + Util.toHtmlForSplitPage(var1) + "\" sqldistinct=\"true\" />";
var32 = var32 + var10;
var32 = var32 + var17;
var32 = var32 + "<head>" + var16 + "</head>";
var32 = var32 + "</table>";
var34 = var4.getPageUid() + "_" + Util.getEncrypt(Util.getRandom());
Util_TableMap.setVal(var34, var32);
var7.put("sessionkey", var34);
if ("1".equals(((Map)var43).get("showTableString"))) {
var7.put("tableString", var32);
}

return var7;
}

看到这里人确实头大,就算一眼看到了很多注入点但是如何构造也成了问题,但所幸存在一个路由getxml能解析xml返回我们的完整sql语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@GET
@Path("/getxml")
@Produces({"text/plain"})
public String getDataKeyValue(@Context HttpServletRequest var1, @Context HttpServletResponse var2) {
String var3 = Util.null2String(var1.getParameter("dataKey"));
JSONObject var4 = new JSONObject();
var4.put("status", false);

try {
if (!"".equals(var3)) {
String var5 = Util.null2String(Util_TableMap.getVal(var3));
if (!"".equals(var5)) {
var4.put("xmlString", var5);
SplitPageBean var6 = new SplitPageBean(var1, var3, new String[]{"head", "sql"});
String var7 = Util.null2String(var1.getParameter("sortParams"));
RecordSet var8 = new RecordSet();
String var9 = this.getDevTableSql(var6.getSql(), var6.getHeads(), var7, var8.getDBType());
var4.put("sql", var9);
var4.put("status", true);
} else {
var4.put("msg", "xmlString is null");
}
} else {
var4.put("msg", "dataKey is null");
}
} catch (Exception var10) {
logger.error(var10);
var10.printStackTrace();
var4.put("msg", Util_public.getErrorInfoFromException(var10));
}

return var4.toString();
}

这时候当你高兴的找好了注入点,啪的一声又被再次打脸,都被转义了还怎么玩?这其实是泛微的一些防御机制,这里不做细讲,我们的攻击Payload被全角化了

image-20250709221239558

那这时候该怎么办?作为新手,难道我还要去花费大量时间Debug看看泛微是在哪里过滤了?怎么绕过它的正则匹配?显然并不需要,记得我之前说了,这里存在大量的注入点,那么这个问题就可以化繁为简了,一个不行就两个、三个、四个点不就好了?只要能保证SQL能完整组装起来即可

(PS:特殊时期就都打重码了,大概知道思路学习即可)

image-20250709222212802

可以“看到”我们成功构造出了想要的语句,实战中我们经常查询系统中内置的表

image-20250709222940023

在这里语句并没有直接执行之前也说了保存到了sessionkey里,接下来就是触发语句的问题了,随便找了一个不需要登录的,看代码逻辑会解析sql并执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@POST
@Path("/counts")
@Produces({"text/plain"})
public String getcounts(@Context HttpServletRequest var1, @Context HttpServletResponse var2, @FormParam("dataKey") String var3) {
JSONObject var4 = new JSONObject();
var4.put("status", false);

try {
if (StringUtils.isBlank(var3)) {
var4.put("msg", "dataKey is null");
return var4.toJSONString();
}

User var5 = HrmUserVarify.getUser(var1, var2);
Thread.sleep(100L);
String var6 = Util_TableMap.getVal(var3);
if (StringUtils.isBlank(var6)) {
logger.info("读取dataKey为空:" + var3);
int var12 = var5 != null ? var5.getLanguage() : 7;
var4.put("msg", SystemEnv.getHtmlLabelName(508217, var12));
var4.put("errorCode", "005");
return var4.toJSONString();
}

SplitPageBean var7 = new SplitPageBean(var6, 0, new String[]{"RootMap", "sql", "head"});
if (var7.getRootMap().containsKey("datasource")) {
var4 = this.getMethodDatas(var7, var5, var4, var1, var2);
if (var4.containsKey("datas")) {
var4.remove("datas");
}
} else {
JSONObject var8 = var7.getSql();
String var9 = var7.getSql().getString("poolname");
var4.put("count", this.getDaoTableByPool(var9).getDevTableCount(var8));
}

var4.put("status", true);
} catch (Exception var10) {
logger.error(var10.getMessage());
}

String var11 = JSON.toJSONString(var4);
return var11;
}

因为是盲注的缘故,盲注满足条件所以最终count返回大于0

image-20250709223035292

到这里复现也基本结束