Гостевая статья Подробная информация об уязвимости ORACLE WEBLOGIC, используемой в "ДИКОЙ ПРИРОДЕ"

Ранее в этом году я об уязвимости десериализации в Oracle WebLogic Server. Это было исправлено Oracle и присвоено CVE-2020-2555. Однако исследователь Quynh Le из VNPT ISC сообщил об ошибке в ZDI, которая показала, как можно обойти патч. Эта ошибка, обозначенная , в настоящее время Oracle как используемая в активных атаках. В этом сообщении мы рассмотрим детали этой недавно исправленной уязвимости.

Патч Обход

Исходный патч для CVE-2020-2555 не затрагивал нижнюю часть следующей цепочки гаджетов:

Код:
BadAttributeValueExpException.readObject()
  com.tangosol.util.filter.LimitFilter.toString() //<--- CVE-2020-2555 patched here
    com.tangosol.util.extractor.ChainedExtractor.extract()
        com.tangosol.util.extractor.ReflectionExtractor().extract()
            Method.invoke()
            //...
            com.tangosol.util.extractor.ReflectionExtractor().extract()
            Method.invoke()
                Runtime.exec()

Любая способность вызывать ChainedExtractor.extract()все равно приведет к удаленному выполнению кода. В докладе Quynh Le показывает , что все еще можно достичь с ChainedExtractor.extract()помощью ExtractorComparatorи AbstractExtractorклассов. Давайте начнем с рассмотрения compare()метода ExtractorComparator:

Код:
 public int compare(T o1, T o2) {
    Comparable a1 = (o1 instanceof InvocableMap.Entry) ? (Comparable)((InvocableMap.Entry)o1).extract(this.m_extractor)
                                                                                : (Comparable)this.m_extractor.extract(o1);

  
    Comparable a2 = (o2 instanceof InvocableMap.Entry) ? (Comparable)((InvocableMap.Entry)o2).extract(this.m_extractor)
                                                                                 : (Comparable)this.m_extractor.extract(o2);
  
    if (a1 == null)
    {
      return (a2 == null) ? 0 : -1;
    }
  
    if (a2 == null)
    {
      return 1;
    }
  
    return a1.compareTo(a2);
  }


Как показано выше, все еще возможно вызвать ChainedExtractor.extract(), установив this.m_extractorэкземпляр ChainedExtractor.

Точно compare()так же можно использовать метод абстрактного класса AbstractExtractor.



Код:
public int compare(Object o1, Object o2) { return SafeComparator.compareSafe(null, extract(o1), extract(o2)); }

MultiExtractorКласс расширяет AbstractExtractorи может быть использован , чтобы достичь ChainedExtractor.extract():

Код:
public abstract class AbstractCompositeExtractor<T, E>
  extends AbstractExtractor<T, E>
  [...Truncated...]
  public class MultiExtractor
  extends AbstractCompositeExtractor
  [...Truncated...]

    public Object extract(Object oTarget) {
    if (oTarget == null)
    {
      return null;
    }
  
    ValueExtractor[] aExtractor = getExtractors();
    int cExtractors = aExtractor.length;
    Object[] aValue = new Object[cExtractors];
  
    for (int i = 0; i < cExtractors; i++)
    {
      aValue[i] = aExtractor[i].extract(oTarget);<-----------------------
    }
  
    return new ImmutableArrayList(aValue);
  }


Полная цепь гаджетов

Чтобы разработать полную цепочку гаджетов, нам нужна возможность вызывать compare()метод произвольно Comparatorиз readObject(). Публично задокументированы способ сделать это , используя PriorityQueueкласс , как показано на следующих ysoserial гаджетов: BeanShell1, Jython1, CommonsCollections2, CommonsBeanutils1, CommonsCollections4и Groovy1:



Код:
java.util . PriorityQueue . readObject ()
  java.util.PriorityQueue.heapify()
  java.util.PriorityQueue.siftDown()
  java.util.PriorityQueue.siftDownUsingComparator()

SiftUpUsingComparator() Можно вызвать compare( )метод произвольного Comparator:

Код:
 private void siftUpUsingComparator(int paramInt, E paramE) {
    while (paramInt > 0) {
      int i = paramInt - 1 >>> 1;
      Object object = this.queue[i];
      if (this.comparator.compare(paramE, object) >= 0)<----------------
        break;
      this.queue[paramInt] = object;
      paramInt = i;
    }
    this.queue[paramInt] = paramE;
  }

Есть и другие способы достижения этого. Например, отправитель использовал следующий метод:

Код:
javax.management.BadAttributeValueExpException.readObject()
  com.tangosol.internal.sleepycat.persist.evolve.Mutations.toString()
    java.util.concurrent.ConcurrentSkipListMap$SubMap.size()
    java.util.concurrent.ConcurrentSkipListMap$SubMap.isBeforeEnd()
      java.util.concurrent.ConcurrentSkipListMap.cpr()

Таким образом, toString() метод класса Mutations может привести к вызову ConcurrentSkipListMap.size():

Код:
ConcurrentSkipListMap$SubMap.class
    public int size() {
        Comparator<? super K> cmp = m.comparator;
        long count = 0;
        for (ConcurrentSkipListMap.Node<K,V> n = loNode(cmp);
            isBeforeEnd(n, cmp); <---------------------------------------------------
            n = n.next) {
            if (n.getValidValue() != null)
                    ++count;
            }
            return count >= Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)count;
        }
  [...Truncated...]
  
  boolean isBeforeEnd(ConcurrentSkipListMap.Node<K,V> n, Comparator<? super K> cmp) {
        ....
        int c = cpr(cmp, k, hi);<------------------------------------------------------
        if (c > 0 || (c == 0 && !hiInclusive))
                return false;
            return true;
        }
  [...Truncated...]

  static final int cpr(Comparator c, Object x, Object y) {
        return (c != null) ? c.compare(x, y) : ((Comparable)x).compareTo(y); <--------
    }


Из ConcurrentSkipListMap.size(), можно вызвать compare()метод произвольного Comparator.

Демонстрация цепочек гаджетов

Используя описанные выше методы, была построена следующая полная цепочка гаджетов ExtractorComparator:

Код:
javax.management.BadAttributeValueExpException.readObject()
  com.tangosol.internal.sleepycat.persist.evolve.Mutations.toString()
    java.util.concurrent.ConcurrentSkipListMap$SubMap.size()
    java.util.concurrent.ConcurrentSkipListMap$SubMap.isBeforeEnd()
      java.util.concurrent.ConcurrentSkipListMap.cpr()
        com.tangosol.util.comparator.ExtractorComparator.compare()

В следующем видео показано, как цепочка гаджетов используется для получения RCE по протоколу T3.


А для AbstractExtractor примера использовалась следующая цепочка:

Код:
java.util.PriorityQueue.readObject()
  java.util.PriorityQueue.heapify()
  java.util.PriorityQueue.siftDown()
  java.util.PriorityQueue.siftDownUsingComparator()
  com.tangosol.util.extractor.AbstractExtractor.compare()
    com.tangosol.util.extractor.MultiExtractor.extract()
      com.tangosol.util.extractor.ChainedExtractor.extract()
        //...
        Method.invoke()
            //...
          Runtime.exec()

В следующем видео показано, как цепочка гаджетов используется для получения RCE по протоколу T3:


Использование этих уязвимостей через HTTP:
Следует отметить, что эта уязвимость находится в библиотеке . Любое приложение с библиотекой Coherence в пути кода, где есть путь к десериализации, также уязвимо. Одним из примеров является Oracle Business Intelligence, который развернут в Oracle WebLogic.
Можно использовать эти цепочки гаджетов в отношении , о котором исследователь ZDI сообщил в ZDI, для достижения удаленного выполнения кода через HTTP.
Эта уязвимость заключается в том BIRemotingServlet, что прослушивает TCP-порт 7780 и не требует никакой аутентификации:

Код:
<servlet>
    <servlet-name>BIRemotingServlet</servlet-name>
    <servlet-class>oracle.bi.nanserver.fwk.servlet.as.BIRemotingServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>BIRemotingServelet</servlet-name>
    <url-pattern>/messagebroker/as/*</url-pattern>
</servlet-mapping>

<servlet-mapping>
    <servlet-name>BIRemotingServlet</servlet-name>
    <url-pattern>/messagebroker/cs/*</url-pattern>
</servlet-mapping>

BIRemotingServlet использует AMF (формат сообщения действия) для связи с клиентом.

Код:
protected void handleRequest(HttpServletRequest paramHttpServletRequest, HttpServletResponse paramHttpServletResponse)
throws ServletException, IOException {

  [...Truncated...]
    
      RemotingSvs remotingSvs = BISvsManagerBase.getRemotingSvs(); <----------------------------------------------------
      remotingSvs.processCall(); <--------------------------------------------------------------------------------------------
      setContentType(paramHttpServletResponse, OutputForm.AMF3);
      paramHttpServletResponse.setContentLength(byteArrayOutputStream.size());
      byteArrayOutputStream.writeTo(paramHttpServletResponse.getOutputStream());
      paramHttpServletResponse.flushBuffer();
    }

public int processCall() throws BISvsException {

  [...Truncated...]

      AMF3Packet aMF3Packet1 = deserializePacket(dataInputStream); <------------------------------------------------------------
      if (logger.isLoggable(Level.FINE)) {
        logger.fine("De-serialized request packet: " + aMF3Packet1.toString());
      }
  [...Truncated...]
  }


oracle.bi.nanserver.fwk.util.remoting.RemotingSvsImpl.class

  public AMF3Packet deserializePacket(DataInputStream paramDataInputStream) throws BISvsException {
    try {
      AMFObjectInput aMFObjectInput = getAMF3DeSerializer(paramDataInputStream); <------------------------------------------------
      LegacyObjectInput legacyObjectInput = new LegacyObjectInput(paramDataInputStream, aMFObjectInput);
      AMF3Packet aMF3Packet = new AMF3Packet();
      aMF3Packet.deserialize(legacyObjectInput); <----------------------------------------------------------------------------------
      return aMF3Packet;
    }
    catch (Exception exception) {
      handleException(exception);
    
      return null;
    }
  }


public AMFObjectInput getAMF3DeSerializer(DataInputStream paramDataInputStream) throws BISvsException {
    try {
      Class clazz = (Class)amf3DeSerializerClass.get();
      if (clazz == null) {
        String str = (String)BISvsManagerBase.getContextSvs().getValue("amf3DeSerializer");
      
        if (str == null || str.trim().length() == 0) {
          clazz = oracle.bi.nanserver.fwk.util.amf.AMF3ObjectInput.class; <--------------------------------------------------------
          amf3DeSerializerClass.compareAndSet(null, clazz);
          logger.info("Using default AMF3 De-Serializer");
  [...Truncated...]
  }

Как показано, когда пакет AMF десериализован, произвольные объекты могут быть восстановлены в AMF3ObjectInput посредством вызова readComplexObject().

Код:
 protected Object readComplexObject(GenericTypeInfo paramGenericTypeInfo)
  throws ClassNotFoundException, IOException {
    try {
      int i = readAMF3IntegerVal();
      if ((i & true) == 0) {
        return getVisitedObject(i >> 1);
      }
      ClassMetadata classMetadata = readClassMetadata(i);

    
      String str = this.proxySvs.getConcreteClassName(classMetadata.name);
      if (str == null) {
        str = classMetadata.name;
      }

      //  CVE-2020-2950 patch
      //if (isBlacklisted(str))
      //{
      //  throw new SecurityException("Unsupport class type:" + str);
      //}
    
      Class clazz = Class.forName(str);
      ClassProxy classProxy = this.proxySvs.getProxy(clazz);

    
      Object object1 = classProxy.newInstance(clazz);

    
      int j = this.objectRefList.size();
      markObjectVisited(object1);

    
      if (classMetadata.externalizable) {
      
        if (paramGenericTypeInfo != null) {
          classProxy.readExternal(new GenericResult(object1, paramGenericTypeInfo), this);
        } else {
          classProxy.readExternal(object1, this);
        }
    
      }
      else if (clazz == oracle.bi.nanserver.fwk.util.remoting.messages.RemotingMessage.class) {
        populateRemotingMessage(object1, classMetadata, classProxy);
      } else {
      
        String[] arrayOfString = classMetadata.getFieldNames();
        Object[] arrayOfObject = new Object[arrayOfString.length];
        for (byte b = 0; b < arrayOfString.length; b++) {
          arrayOfObject[b] = readObject();
        }
        this.proxySvs.setFieldValues(object1, arrayOfString, arrayOfObject, classProxy);
      
        if (classMetadata.dynamic) {
          while (true) {
            String str1 = readAMF3String();
            if (str1 == null || str1.length() == 0) {
              break;
            }
            Object object = readObject();
            this.proxySvs.setFieldValue(object1, str1, object, classProxy);
          }
        }
      }


В этом примере UnicastRef объект реконструируется, что приводит к вызову распределенного сборщика мусора на стороне сервера для удаленного объекта, что позволяет нам ответить произвольным сериализованным объектом. Ответ одной из цепочек гаджетов, как описано выше, приводит к RCE.
Для получения более подробной информации об использовании десериализации Java в реализациях Java AMF, обратитесь к этому посту из . Цепочки гаджетов были добавлены в ysoserial , а его прослушиватель JRMP использовался для использования этой уязвимости. Следующее видео демонстрирует это в действии:



Вывод

Для получения более подробной информации об уязвимостях десериализации Java, обратитесь к этому техническому документу от Морица Бехлера. В Oracle не говорится, насколько распространены атаки, но их руководство ясно: исправьте сейчас. Они также предлагают руководство по ограничению трафика протокола T3 / T3S для Oracle WebLogic Server. Следующий выпуск исправлений Oracle запланирован на 14 июля 2020 года. Посмотрим, сколько ошибок десериализации останется после этого обновления.

Источник:
 
Последнее редактирование:
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!