
POC para CVE-2020-2551 para usar na Internet
POC de teste (pode ser usado para testes externos)
python CVE-2020-2551.py [HOST] [IP]
Tentativa de modificar o codebase para carregamento remoto de classes (falhou)
python CVE-2020-TEST.py [HOST] [IP]
(Publicado pela primeira vez no Anquanke, link original)
Para aprender esta vulnerabilidade, são necessários alguns conhecimentos prévios, como CORBA e RMI.
Uma breve visão geral:
CORBA é um conjunto de padrões técnicos desenvolvidos pela OMG para aplicações distribuídas, onde o IDL é usado para suporte multilíngue e a comunicação entre cliente e servidor utiliza o protocolo IIOP.
RMI é outra tecnologia de aplicação distribuída. Em JAVA, pode ser simplificada com JNDI, e o cliente e servidor comunicam-se usando o protocolo JRMP. No entanto, no weblogic, o RMI usa o protocolo T3, sobre o qual já foram divulgadas várias vulnerabilidades anteriormente.
RMI-IIOP combina as vantagens de RMI e CORBA, implantando aplicações RMI através do protocolo IIOP.
A documentação oficial também menciona:
Os objetos do servidor RMI podem usar o protocolo IIOP e comunicar-se com objetos cliente CORBA escritos em qualquer idioma
Deixando o weblogic de lado por enquanto, vamos primeiro focar em como criar um exemplo de RMI-IIOP:
O código do cliente pode ser consultado no projeto de teste do artigo Java: RMI, JNDI, LDAP, JRMP, JMX, JMS – Parte 1. Você pode compilar HelloClient e HelloServer por conta própria ou usar os já compilados do projeto de teste.
Inicie o servidor de nomes na linha de comando (nativo do Java):
start orbd -ORBInitialPort 1050
Inicie o servidor HelloServer na linha de comando e configure a depuração remota. Para saber como usar o IDEA para depuração remota, consulte o método mencionado no início deste artigo.
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 HelloServer
Claro, você também pode pular a depuração remota e apenas executar diretamente:
java HelloServer
Inicie o cliente na linha de comando:
Java HelloClient
Neste momento, a calculadora será exibida. Se a depuração remota for bem-sucedida, você verá a seguinte pilha de chamadas:

O comando é executado em EvilMessage.readObejct()

Observação: Artigos sobre instalação e depuração do weblogic
E quanto ao RMI-IIOP no weblogic? O artigo Sobre RMI-IIOP em Java menciona a exploração do RMI-IIOP do weblogic. Com base nele, realizei algumas pesquisas. O artigo Usando RMI sobre IIOP do WebLogic aborda várias formas de usar o cliente RMI-IIOP do weblogic, incluindo:
A diferença entre as duas primeiras parece ser apenas a configuração de JNDI_FACTORY

Anteriormente, ao estudar a desserialização T3 do weblogic, implantei uma aplicação HelloServer no weblogic, que possui o método sayhello() utilizável. Tentei chamá-lo com as duas configurações de JNDI_FACTORY e consegui chamar sayHello() com sucesso usando a segunda JNDI_FACTORY
Modifiquei então o POC do protocolo T3 do weblogic, basicamente alterando RMI para IIOP. A cadeia de exploração jtaTransactionManager foi executada com sucesso, enviando uma requisição jrmp para o jrmplisten local

Observando o tráfego, ao chamar o método remove(), é enviada uma requisição remove__java_lang_Object, com dados maliciosos no tráfego, mas o cabeçalho mágico aced não foi encontrado

Supõe-se que o servidor faça uma análise especial antes de desserializar os dados. Observando a pilha de chamadas, a segunda metade da cadeia de execução é muito semelhante à cadeia de execução do RMI-IIOP nativo. A primeira metade é acionada a partir de CDRInputStream.read_value(), enquanto aqui é acionada a partir de IIOPInputStream.read_value() do weblogic. (O ponto read_value também foi mencionado na apresentação de 2019)

Aqui, a requisição primeiro é tratada por clusterableServerRef.invoke() e, dependendo do invoker, chama this.invoker.invoke(). Em seguida, é chamado Mejb_dj5nps_HomeImpl_WLSkel.invoke(). Como a operação é "remove", entra no branch case 6 e chama IIOPInputStream.readObject(). No método read_value(), os dados do IIOPInputStream são analisados e a desserialização é acionada. Este é o POC que utiliza o método remove()
A análise do mestre Lucifaer [artigo](https://lucifaer.com/2020/02/25/WebLogic WLS核心组件RCE分析(CVE-2020-2551)/?from=timeline&isappinstalled=0#2-2-Weblogic解析流程) menciona o uso do método bind() para exploração, que também é a forma principal de exploração na Internet. Vamos acompanhar a pilha de chamadas

Como antes, a requisição primeiro é tratada por clusterableServerRef.invoke() e, dependendo do invoker, chama this.invoker.invoke(). Aqui, é chamado CobraServerRef.invoke(), e então em _NamingContextAnyImplBase._invoke(), como va1 é "bind_any", entra no branch case 0 e chama IIOPInputStream.read_any(). Em seguida, ainda chamará IIOPInputStream.read_value() para acionar a desserialização. Antes foi dito que o cabeçalho mágico aced não era encontrado no tráfego, porque IIOPInputStream possui seu próprio método de análise. O formato hex-value do IIOPInputStream é o seguinte, incluindo o nome da classe e informações de campo:

No final, o método readObejct() da classe maliciosa é chamado

Observei o patch e descobri que está no mesmo local do patch de 2015 para exploração de desserialização T3

Nos testes do Análise da vulnerabilidade WebLogic CVE-2020-2551, a posição das classes filtradas pelo CVE-2020-2551 também está na classe weblogic.iiop.Utils

No entanto, ao testar localmente com o weblogic 10.3.6 com o patch de 2015 aplicado, a função isBlacklisted() não foi acionada (embora tanto MsgAbbrevInputStream quanto InboundMsgAbbrev chamem isBlacklisted() para verificação na lista negra, estranho...)

O patch do CVE-2020-2551 adicionou o método verifyclassermitted() para filtrar em weblogic.iiop.Utils.LoadClass()

A lista negra filtra classes maliciosas, incluindo a classe pai do JtaTransactionManager, com.bea.core.repackaged.springframework.transaction.support.AbstractPlatformTransactionManager, que é fornecida pelo próprio weblogic e é muito perigosa. Ao examinar o patch, surgiu uma ideia: como a verificação na linha 606 ocorre após LoadClass(), se ao carregar o className a classe for carregada e um bloco estático malicioso for executado, a defesa não seria contornada? Falaremos sobre isso mais adiante
O POC escrito em Java tem problemas de rede; funciona ao atacar o serviço weblogic local, mas não funciona contra contêineres Docker ou máquinas externas. Artigos que mencionam esse problema:
A seguir, vou depurar o POC. Você pode consultar o anterior (remove) ou o de Y4er. Os dois artigos mencionam duas soluções:
Tentei ambas. Após reempacotar o weblogic, ocorreu o erro java.lang.NoSuchMethodError: weblogic.security.subject.SubjectManager.installCESubjectManager, mas não encontrei solução

Então tentei simular o protocolo IIOP. Primeiro, defini um ponto de interrupção no POC

Descobri que ao chamar new InitialContext(env), o método EndPointImpl.sendReceive() envia e recebe dois pacotes

LocateReply contém informações IOR. É importante entender o que é IOR. Sua função é fornecer o host e a porta necessários para comunicação IIOP quando o cliente RMI-IIOP interage com o objeto do servidor usando o protocolo IIOP, além da parte em vermelho Object_key, que é usada para distinguir diferentes objetos do servidor

Ao simular o protocolo IIOP, é necessário prestar atenção especial ao Object_key. O host e o IP não afetam. No início dos testes, eu simplesmente reenviava todos os pacotes. Ao enviar resolve_any, era retornado location forward

A documentação oficial do GIOP menciona que, quando location forward ocorre, o Object_key pode mudar, e o Object_key retornado em diferentes requisições pode ser diferente (aqui, Object_key se refere ao key address no pacote). Como dito anteriormente, esse Object_key é usado para diferenciar com qual objeto se comunicar ao usar o protocolo IIOP, e esse valor precisa ser obtido dinamicamente em LocateReply

No final, optei por não simular o método remove(), mas sim o bind(). Como o número de requisições é menor, veja o pacote de dados do uso local normal

Enviar LocateRequest, receber dados e obter o key address do LocateReply por meio de correspondência regex

Definir manualmente o endereço do servidor jrmp malicioso (rmi://...), enviar pacotes bind_any com intervalo de 1 segundo. Como a cadeia de exploração aqui não usa DGCClient para enviar requisições jrmp, não é afetada pelo JEP290 e pode ser explorada através do jrmplisten

O POC foi testado com sucesso em um ambiente Docker. Pode ser usado com o ambiente SSRF do vulhub, definindo o IP como o host. O Docker obteve com sucesso uma requisição jrmp do host. O código específico está no Github

Mencionei anteriormente a ideia de usar codebase para carregar código remoto e contornar a detecção. Quem estudou ataques JNDI sabe que codebase pode ser usado para especificar a localização de classes remotas. Se o codebase for controlável e o programa permitir o carregamento remoto de classes, é possível carregar classes maliciosas remotas e executar código malicioso em blocos estáticos.
Ao ler o código, weblogic.iiop.Utils.loadClass() tem um segundo parâmetro que representa o codebase. Esse parâmetro é lido em IIOPInputStream.read_value(), ou seja, o parâmetro var8. Na linha 1659, readIndirectingRepositoryId(var8) é chamado e, eventualmente, weblogic.iiop.Utils.loadClass() é chamado. Para executar as linhas 1644 e 1659, é necessário que (va4r & 1)=1 e (va4 & 6)=2, portanto var4 deve ser 3

A pilha de chamadas de readIndirectingRepositoryId a getClassFromId, finalmente executa loadclass() na linha 304

Observe o pacote bind_any. Na verdade, ele é composto pelo GIOP Header e GIOP Request. O GIOP Request inclui o key address (consistente com o do LocateReply), ServiceContextList e stub_data. var4 é o valor \x7f\xff\xff\x02 em stub_data, então (va4r & 1)=0 e (va4 & 6)=2, e as linhas 1644 para definir codebase não são executadas

Modificamos o primeiro quadrado \x7f\xff\xff\x02 para \x00\x00\x00\x03 e adicionamos o comprimento e o valor do codebase no segundo quadrado. O código específico está no Github. Pode-se ver que também é feita uma operação de alinhamento, que é um ponto problemático. Porque antes de ler as informações da classe, verifica-se se a posição do próximo byte é um múltiplo de 4. Caso contrário, alguns bytes são ignorados. Por exemplo, se a posição do próximo byte for 1, 3 bytes são ignorados e a leitura começa diretamente do byte na posição 4. Essa posição é relativa a todo o pacote bind_any. Se o byte não for um múltiplo de 4, é feito o preenchimento com 0

Outro problema: observe a pilha de chamadas de readIndirectingRepositoryId a getClassFromId. Ela passa pela função findClassInfo(). Aqui, se uma classe já foi carregada, as informações do ID da classe são salvas e, ao chamar findClassInfo(), as informações da classe são retornadas diretamente, sem entrar na função weblogic.iiop.Utils.getClassFromID()

Portanto, ao testar, é necessário alterar o nome da classe a cada vez

De qualquer forma, no final, consegui simular com sucesso o protocolo IIOP, modificar o valor do codebase e executar a função weblogic.iiop.Utils.getClassFromId().
Infelizmente, ao obter RMIURLClassFinder, foi retornado NULL, e a função RMIEnvironment.getEnvironment().isNetworkClassLoadingEnabled() retornou false

O motivo é que o valor do parâmetro _NetworkClassLoadingEnable no ServerMBeanImpl é False

Tentei encontrar em qual arquivo de configuração do weblogic esse parâmetro é definido, mas não encontrei...
Na verdade, ao estudar essa vulnerabilidade, descobri que são necessários muitos conhecimentos prévios, como desserialização Java, RMI, JNDI, etc. O aprendizado relacionado pode ser consultado nesta coluna de artigos. Embora a modificação do codebase para exploração tenha falhado no final, ainda aprendi muito.