SolarWinds’in ağ ve performans izleme yazılımı Network Performance Monitor’de RCE zafiyeti tespit edildi. Zafiyetin istismar edilmesi sistem üzerinde RCE’e izin veriyor.
Zafiyetin detaylarına baktığımızda BytesToMessage fonksiyonundaki yetersiz kullanıcı verilerinin doğrulamasından kaynaklandığını görülmekte. Network Performance Monitor diğer ürünler ile mesaj almak ve göndermek için RabbitMQ’u kullanıyor. RabbitMQ’da mesajları göndermek için Advanced Message Queuing Protocol version 0-9-1 (AMQP 0-9-1) kulanıyor. SolarWinds Information Service (SWIS) ise RabbitMQ’u kullanan diğer bir SolarWinds modülü. SWIS’e bir mesaj gönderilmek istendiğinde mesaj içeriği AMQP frame’e ekleniyor ve TCP bağlantı noktası 5671 yoluyla RabbitMQ’ya gönderebiliyor. Sorunda tam da burada ortaya çıkıyor çünkü gönderilen verinin güvenli olup olmadığını doğrulayacak bir yöntem bulunmuyor. Aslında NPM içerisinde şüpheli nesne türlerini kontrol eden bir .net sınıfı olan BlackListBinder var ancak bu RabbitMQ iletişiminde kullanılmıyor. Saldırganlar bunu bildiği için sistemlere saldırmak için RabbitMQ arasındaki trafiği kullanıyor.
Aşağıdaki kod parçacığı SolarWinds NPM 2020.2.6 sürümünden alınmıştır. Trend Micro tarafından notlar eklenmiştir.
In decompiled .NET class EasyNetQ.DefaultMessageSerializationStrategy:
public IMessage DeserializeMessage(MessageProperties properties, byte[] body)
{
Type messageType = this.typeNameSerializer.DeSerialize(properties.Type);
// call to deserialize the body (a byte[] array)
object body2 = this.serializer.BytesToMessage(messageType, body);
return MessageFactory.CreateInstance(messageType, body2, properties);
}
In decompiled .NET class SolarWinds.MessageBus.RabbitMQ.EasyNetQSerializer:
public IMessage DeserializeMessage(MessageProperties properties, byte[] body)
{
Type messageType = this.typeNameSerializer.DeSerialize(properties.Type);
// call to deserialize the body (a byte[] array)
object body2 = this.serializer.BytesToMessage(messageType, body);
return MessageFactory.CreateInstance(messageType, body2, properties);
}
public object BytesToMessage(Type messageType, byte[] bytes)
{
string @string = Encoding.UTF8.GetString(bytes); // serialzed object carried in RabbitMQ message EasyNetQSerializer._log.TraceFormat("Decoding msg to type {0}: {1}", new object[]
{
"messageType",
@string }
);
// call DeserializeObject() without checking the messageType
return JsonConvert.DeserializeObject(@string, messageType, this.serializerSettings);
}
In decompiled .NET class SolarWinds.Newtonsoft.Json.Serialization.JsonSerializerInternalReader:
public object Deserialize(JsonReader reader, Type objectType, bool checkAdditionalContent)
{
if (reader == null)
{
throw new ArgumentNullException("reader");
}
// call GetContractSafe() to check objectType but does not use BlackListBinder class
JsonContract contractSafe = this.GetContractSafe(objectType);
object result;
try
{
JsonConverter converter = this.GetConverter(contractSafe, null, null, null);
if (reader.TokenType == JsonToken.None &&
!this.ReadForType(reader, contractSafe, converter != null))
{
if (contractSafe != null && !contractSafe.IsNullable)
{
throw JsonSerializationException.Create(reader,
"No JSON content found and type '{0}' is not nullable.".FormatWith(CultureInfo.InvariantCulture,
contractSafe.UnderlyingType));
}
result = null;
}
else
object obj;
if (converter != null && converter.CanRead)
{
obj = this.DeserializeConvertable(converter, reader, objectType, null);
}
else
{ // call to begin parsing JSON data and converting to object
// but still without any type checking and trigger insecure deserialization
obj = this.CreateValueInternal(reader, objectType, contractSafe, null,
null, null, null);
}
if (checkAdditionalContent && reader.Read() && reader.TokenType != JsonToken.Comment)
{
throw new JsonSerializationException(
"Additional text found in JSON string after finishing deserializing object.");
}
result = obj;
}
}
catch (Exception ex)
{
[... truncated for readability ...]
Nasıl tespit edilir
Bu güvenlik açığından yararlanan bir saldırıyı algılamak için, algılama cihazının 5671 numaralı TCP bağlantı noktası üzerindeki trafiği izlemesi gerekiyor. Trafiğin SSL/TLS aracılığıyla şifrelendiğini ve aşağıdaki adımlar gerçekleştirilmeden önce şifresinin çözülmesi gerektiğini unutmayın.
Öncelikle aşağıda gösterilen formatta protokol başlığını arayarak istemcinin bir AMQP 0-9-1 bağlantısı başlatmaya çalışıp çalışmadığını belirlemelidir:
Offset Length Description
(bytes)
------ ------- ----------------------------------------
0x00 4 \x41\x4d\x51\x50 (String literal "AMQP")
0x04 1 protocol id (always \x00)
0x05 3 version (always \x00\x09\x01)
Böyle bir AMQP protokol başlığı bulunursa, trafik izlemeye devam etmeli ve AMQP 0-9-1 frame aranmalıdır.
AMQP 0-9-1 frame
AMQP 0-9-1 frame protokol yöntemlerini, mesaj içeriğini ve diğer bilgileri taşır. Tüm frame aynı genel biçime sahiptir. Genel frame biçimi aşağıda gösterilmiştir:
Offset Length Description
(bytes)
------ ------- ----------------------------------------
0x00 1 frame type (four frame types)
\x01: Method frame
\x02: Content header frame
\x03: Content body frame
\x04: Heartbeat frame
0x01 2 channel
0x03 4 frame payload size (n)
0x07 n frame payload (the format depends on its frame type)
0x07+n 1 frame end (\xCE)
AMQP 0-9-1’de dört çerçeve türü var: Content header, Content body ve Heartbeat frame.
Offset Length Description
(bytes)
------ ------- ----------------------------------------
0x00 2 Class id
0x02 2 Method id
0x04 n/a Method arguments list
Method, formatı aşağıda gösterildiği gibidir:
Offset Length Description
(bytes)
------ ------- ----------------------------------------
0x00 2 Class id
0x02 2 Method id
0x04 n/a Method arguments list
Content header, formatı aşağıda gösterildiği gibidir:
Offset Length Description
(bytes)
------ ------- ----------------------------------------
0x00 2 Class id
0x02 2 Method id
0x04 n/a Method arguments list
Content body formatı aşağıda gösterildiği gibidir:
Offset Length Description
(bytes)
------ ------- ----------------------------------------
0x00 2 Class id
0x02 2 Method id
0x04 n/a Method arguments list
JSON biçimli veriler, aşağıdaki herhangi bir uygun ayrıştırma yöntemi kullanılarak incelenmelidir:
Yöntem 1 – Algılama cihazı JSON’u ayrıştırabilir
System.Windows.Data.ObjectDataProvider
System.Security.Principal.WindowsPrincipal
System.Security.Principal.WindowsIdentity
Microsoft.IdentityModel.Claims.WindowsClaimsIdentity
System.Web.UI.MobileControls.SessionViewState+SessionViewStateHistoryItem
System.IdentityModel.Tokens.SessionSecurityToken
System.Web.Security.RolePrincipal
Bulunursa, trafiğin kötü amaçlı olduğu kabul edilmelidir ve bu güvenlik açığından yararlanan bir saldırı büyük olasılıkla devam etmektedir.
Yöntem 2 – String-based tespit etme
\x22\s*\$type\s*\x22\s*:\s*\x22\s*(System\.Windows\.Data\.ObjectDataProvider
|System\.Security\.Principal\.WindowsPrincipal
|System\.Security\.Principal\.WindowsIdentity
|Microsoft\.IdentityModel\.Claims\.WindowsClaimsIdentity
|System\.Web\.UI\.MobileControls\.SessionViewState+SessionViewStateHistoryItem
|System\.IdentityModel\.Tokens\.SessionSecurityToken
|System\.Web\.Security\.RolePrincipal)
Bulunursa, trafiğin kötü amaçlı olduğu kabul edilmelidir ve bu güvenlik açığından yararlanan bir saldırı büyük olasılıkla devam etmektedir.
Aşağıda, JSON tabanlı malicious serialized object örneği verilmiştir.
{
"$type":"System.Windows.Data.ObjectDataProvider, PresentationFramework,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"MethodName":"Start",
"MethodParameters":{
"$type":"System.Collections.ArrayList, mscorlib, Version=4.0.0.0,
Culture=neutral, PublicKeyToken=b77a5c561934e089",
"$values":["cmd", "/c whoami > C:\\poc.txt"]
},
"ObjectInstance":{"$type":"System.Diagnostics.Process, System, Version=4.0.0.0,
Culture=neutral, PublicKeyToken=b77a5c561934e089"}
Çözüm
SolarWinds, SolarWinds Platform sürüm 2022.4 RC1 ve sonraki sürümüyle bu güvenlik açığını kapattı. Bunun yanında ilgili portların izlenmesi ve SQL gibi kritik sunucuların segmantasyonu büyük önem taşıyor.
Kaynak: zerodayinitiative.com