CSAW 2018 writeup

前言

  很久没活动了,web能力显著下降。。。

WEB

Ldab

1
2
3
dab

http://web.chal.csaw.io:8080

  一直没看懂这道题的提示,打开页面后右上角出现:




  然后有一页人员名单,Here is a list of all users and groups



  尝试了一波注入后并没有发现突破点,后来经人提示才明白这是LDAP注入

  关于LDAP:
1
2
3
4
轻量级目录访问协议(LDAP)用于存储有关用户,主机和许多其他对象的信息。LDAP注入是服务器端攻击,可以允许公开,修改或插入有关LDAP结构中表示的用户和主机的敏感信息。这是通过操作输入参数然后传递给内部搜索,添加和修改函数来完成的。


Web应用程序可以使用LDAP,以便用户在公司结构中对其他用户的信息进行身份验证或搜索。LDAP注入攻击的目标是在将由应用程序执行的查询中注入LDAP搜索过滤器元字符。


  LDAP的语法如下:

| Metachar | Meaning|
|———-|———-|
|& | Boolean AND|
| | | Boolean OR|
|! | Boolean NOT|
| = | Equals|
| ~= | Approx|
| = | Greater than|
| <= | Less than|
| * | Any character|
| () | Grouping parenthesis|

  所以LDAP表达and需要用(&(condition1)(condition2))or类似。同时它还支持正则语法主要是:.*

  一些例子:
1
2
3
4
5
6
7
8
如果我们想要在上面的LDAP结构中查询名为“ steve ”的人,我们的查询将如下所示:
(cn=steve)

也许我们想要搜索名称以“ s ” 开头的任何成员,那么我们可以使用通配符:
(cn=s*)

我们还可以使用“ | ”运算符搜索名称以“s”或“t”开头的任何人:
(|(cn=s*)(cn=t*))


  题目中默认是GivenName,如:



  所以一般来说我们的flag应该就藏在某一项中,我们找一下LDAP attributes,看看其他字段中是否有flag。LDAP属性字段可以在这里找到。但都没有发现。使用*)(|(uid=*)列出所有项也还是没有出来。但可以确定的是flag就在这些项中,所以继续尝试,直到找到这个:
1
*)(uid=*))(|(uid=*


  得到flag:


参考链接

  https://www.owasp.org/index.php/Testing_for_LDAP_Injection_(OTG-INPVAL-006)

  http://www.4hou.com/technology/9090.html

  https://www.anquanke.com/post/id/159378

MISC

bin_t

1
2
3
4
5
Binary trees let you do some interesting things. Can you balance a tree?

nc misc.chal.csaw.io 9001

Equal nodes should be inserted to the right of the parent node. You should balance the tree as you add nodes.

  如题,nc上去会给出一组数字,而你的任务就是将这组数字转换成平衡二叉树,并且给出该ACL树的前序遍历,下面直接给代码:

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
//AVL(自动平衡二叉树)
#include <stdio.h>
#include <stdlib.h>
typedef int ElemType;
//每个结点的平均值
typedef enum
{
EH = 0,
LH = 1,
RH = -1
}bh_t;

typedef enum
{
FALSE = 0,
TRUE = 1
}bool_t;

//定义平衡二叉树
typedef struct BSTNode
{
ElemType key; //平衡值
int bf;
struct BSTNode *lchild,*rchild;
}BSTNode, *BSTree;


//中序遍历
void InOrderTraverse(BSTree root)
{
if(NULL != root)
{
InOrderTraverse(root->lchild);
printf("%d\t",root->key);
InOrderTraverse(root->rchild);
}
}


//前序遍历
void PreOrderTraverse(BSTree root)
{
if(NULL != root)
{
printf("%d,",root->key);
PreOrderTraverse(root->lchild);
PreOrderTraverse(root->rchild);
}
}


//单向右旋转
void R_Rotate(BSTree *p)
{
BSTree lc=(*p)->lchild;
(*p)->lchild=lc->rchild;
lc->rchild=*p;
*p=lc;
}

//单向左旋转
void L_Rotate(BSTree *p)
{
BSTree rc=(*p)->rchild;
(*p)->rchild=rc->lchild;
rc->lchild=*p;
*p=rc;
}


//先左旋后右旋平衡旋转
void LeftBalance(BSTree *T)
{
BSTree lc=(*T)->lchild;
BSTree rd = lc->rchild;
//判断进行向哪边旋转
switch(lc->bf)
{
case LH:
(*T)->bf=lc->bf=EH;
R_Rotate(T);
break;
case RH:
switch(rd->bf)
{
case LH:
(*T)->bf=RH;
lc->bf=EH;
break;
case EH:
(*T)->bf=lc->bf=EH;
break;
case RH:
(*T)->bf=EH;
lc->bf=LH;
break;
}
rd->bf=EH;
L_Rotate(&((*T)->lchild));
R_Rotate(T);
break;
}
}

//先右旋后左旋平衡旋转
void RightBalance(BSTree *T)
{
BSTree rc=(*T)->rchild;
BSTree ld=rc->lchild;
switch(rc->bf)
{
case RH:
(*T)->bf=rc->bf=EH;
L_Rotate(T);
break;
case LH:
switch(ld->bf)
{
case RH:
(*T)->bf=LH;
rc->bf=EH;
break;
case EH:
(*T)->bf=rc->bf=EH;
break;
case LH:
(*T)->bf=EH;
rc->bf=RH;
break;
}
ld->bf=EH;
R_Rotate(&((*T)->rchild));
L_Rotate(T);
break;
}
}


//插入元素
bool_t InsertAVL(BSTree *t,ElemType e,bool_t *taller)
{
if(NULL == t)
return FALSE;
if(NULL == *t)
{
*t=(BSTree)malloc(sizeof(BSTNode));
if(NULL == *t)
return FALSE;
(*t)->key=e;
(*t)->lchild=(*t)->rchild=NULL;
(*t)->bf=EH;
*taller=TRUE;
}
else
{
if(e==(*t)->key)
{
*taller=FALSE;
return FALSE;
}
if(e<(*t)->key)
{
if(FALSE == InsertAVL(&((*t)->lchild),e,taller))
return FALSE;
if(*taller)
{
switch((*t)->bf)
{
case LH:
LeftBalance(t);
*taller=FALSE;
break;
case EH:
(*t)->bf=LH;
*taller=TRUE;
break;
case RH:
(*t)->bf=EH;
*taller=FALSE;
break;
}
}
}
else
{
if(FALSE == InsertAVL(&((*t)->rchild),e,taller))
return FALSE;
if(*taller)
{
switch((*t)->bf)
{
case RH:
RightBalance(t);
*taller=FALSE;
break;
case EH:
(*t)->bf=RH;
*taller=TRUE;
break;
case LH:
(*t)->bf=EH;
*taller=FALSE;
break;
}
}
}
}
return TRUE;
}


static void destroy(BSTree *t)
{
if(NULL != *t)
{
destroy(&((*t)->lchild));
destroy(&((*t)->rchild));
free(*t);
*t = NULL;
}
return;
}
void destroyAVL(BSTree root)
{
if(NULL != root)
{
destroy(&root);
}
return;
}

int main()
{
BSTree root=NULL,r;
bool_t taller=FALSE;
int array[]={16,67,87,73,81,71,11,38,49,59,30,85,73,26,58,35,50,72,70,50,76,17,94,8,58,55,68,88,15,37,75,27,75,30,1,85,94,27,7,71,8,9,70,4,17,21,38,29,2,71,85,5,59,67,83,93,47,5,81,29,64,36,15,89,45,90,40,21,53,51,35,57,5,11,48,23,44,66,71,81,95,73,17,42,1,98,19,34,6,87,68,21,78,88,78,17,97,49,90,5};
int i = 0;
for(i=0; i < 100; i++)
InsertAVL(&root,array[i],&taller);
printf("中序遍历:\n");
InOrderTraverse(root);
printf("\n先序遍历\n");
PreOrderTraverse(root);

destroyAVL(root);
root = NULL;
return 0;
}

Algebra

1
2
3
Are you a real math wiz?

nc misc.chal.csaw.io 9002

  nc上去发现是解一元方程。




  写一个求解器:
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
# -*- coding:utf-8 -*-

import socket

sc = socket.socket() # 创建 socket 对象
host = "misc.chal.csaw.io" # 获取本地主机名
port = 9002 # 设置端口
addr = (host, port)
sc.connect(addr) # 绑定端口号
print sc.recv(1024)

def solve1(eq,var='X'):
eq1 = eq.replace("=","-(") + ")"
c = eval(eq1,{var:1j})
if (-c.real == 0):
return 0
else:
return -c.real/c.imag

def find():
data = sc.recv(1024)
print data
equation = data.split('\n')[0]
result = str(solve1(equation))
print '正在求解:' + equation + ', result = ' + result
sc.send(result + '\n')
print '==========================='

find()

i = 1
while True:
print '============ 第 ' + str(i + 1) + ' 轮 ==============='
data = sc.recv(2048)
print data
equation = data.split('\n')[1]
result = str(solve1(equation))
print '正在求解:' + equation + ', result = ' + result
sc.send(result + '\n')
i += 1

Crypto

flatcrypt

1
2
3
4
5
no logos or branding for this bug

Take your pick nc crypto.chal.csaw.io 8040 nc crypto.chal.csaw.io 8041 nc crypto.chal.csaw.io 8042 nc crypto.chal.csaw.io 8043

flag is not in flag format. flag is PROBLEM_KEY

  答题附件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# -*- coding: utf-8 -*-

import zlib
import os

from Crypto.Cipher import AES
from Crypto.Util import Counter

ENCRYPT_KEY = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000')
# Determine this key.
# Character set: lowercase letters and underscore
PROBLEM_KEY = 'not_flag'

def encrypt(data, ctr):
return AES.new(ENCRYPT_KEY, AES.MODE_CTR, counter=ctr).encrypt(zlib.compress(data))

while True:
f = input("Encrypting service\n")
if len(f) < 20:
continue
enc = encrypt(bytes((PROBLEM_KEY + f).encode('utf-8')), Counter.new(64, prefix=os.urandom(8)))
print("%s%s" %(enc, chr(len(enc))))

  这道题网上搜了一下发现有类似的题目。

    http://www.blue-lotus.net/plaidctf-2013-crypto-compression250-writeup/

    https://systemoverlord.com/2013/04/30/plaidctf-compression/

    https://www.cnblogs.com/shuidao/p/3151067.html

  解题的关键是利用zlib.compress()压缩数据时造成的漏洞,当压缩的字符串中有3个以上重复的字符串时,LZ77压缩算法就会对字符串进行压缩。如:

1
2
3
4
5
6
7
8
9
10
vvvvv
Blah blah blah blah blah!
^^^^^
接下来的5个字符正好和已经在数据流中的字符串相等, 而且刚好在当前数据点的前5个字符开始. 在这个case中,我们可以在数据流中输出特殊的字符, 一个长度数字 和一个距离数字.

目前数据是:
Blah blah b

压缩后的格式是:
Blah b[D=5,L=5]

  再加上AES使用CTR流密码模式,那么待加密字符的长度不一样就会产生不一样长度的密文。这个可以用下面这个脚本体验一下:

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 zlib
import os
import string
import random

from Crypto.Cipher import AES
from Crypto.Util import Counter

ENCRYPT_KEY = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000')
# Determine this key.
# Character set: lowercase letters and underscore
PROBLEM_KEY = 'crime_doesnt_have_a_logo'
padding = 'A' * 20

def encrypt(data, ctr):
return AES.new(ENCRYPT_KEY, AES.MODE_CTR, counter=ctr).encrypt(zlib.compress(data))

for i in range(30):
f = padding + ''.join(random.sample(string.ascii_letters + string.digits, 4))
# if len(f) < 20:
# continue
enc = encrypt(bytes((PROBLEM_KEY + f).encode('utf-8')), Counter.new(64, prefix=os.urandom(8)))
print("%s --> %s" %(f, len(enc)))

print('---------finally----------')
enc = encrypt(bytes((PROBLEM_KEY + padding + 'crime').encode('utf-8')), Counter.new(64, prefix=os.urandom(8)))
print("%s --> %s" %('crime', len(enc)))




  所以可以看出当我们能得到一个三个字符都一样的字符串时我们得到的长度就会变小(压缩),然后根据这三个字符不断的往前猜和往后猜就能找到所有的字符。

  最终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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# -*- coding: utf-8 -*-
import socket
import random, string

sc = socket.socket() # 创建 socket 对象
host = "crypto.chal.csaw.io" # 获取本地主机名
port = 8040 # 设置端口
addr = (host, port)
sc.connect(addr) # 绑定端口号

strs = 'abcdefghijklmnopqrstuvwxyz_'
size = 20
print sc.recv(1024)

flag = ''

padding = ''.join(random.sample(string.ascii_uppercase + string.digits + string.ascii_letters, size))
print '[!] padding: ' + padding

def get_fore():
last_len = 0
last_tmp = ''
for f1 in strs:
for f2 in strs:
for f3 in strs:
for f4 in strs:
tmp = f1 + f2 + f3 + f4
if tmp == 'aaaa':
continue
data = padding + tmp
sc.send(data + '\n')

temp = sc.recv(1024).split('\n')[0]
# print '[+] recv data: ' + temp
try:
len = ord(temp[-1:])
except:
print '[ERROR] error...'
print '[!] padding: ' + padding
print '[!] recv data: ' + temp
continue

if last_len != 0 and last_len != len and tmp not in padding and last_tmp not in padding:
# 找到一个
if last_len > len:
flag = tmp
else:
flag = last_tmp
print '[*] some flag: ' + flag
sc.close()
exit(0)

last_len = len
print '%s --> %s' % (tmp, str(len))
last_tmp = tmp

def get_more(target):
i = 1
over = False
while not over:
last_len = 0
last_tmp = ''
for f in strs:
# data = padding + target[i:] + f # 向后
data = padding + f + target[:len(target) - i] # 向前
sc.send(data + '\n')

temp = sc.recv(1024).split('\n')[0]
print '[+] recv data: ' + temp
lens = ord(temp[-1:])
print 'sended: %s --> %s' % (data, str(lens))

if last_len != 0 and last_len != lens:
# 找到一个
if last_len > lens:
# target += f # 向后
target = f + target
else:
# target += last_tmp # 向后
target = last_tmp + target
print '[*] target: ' + target
break

last_len = lens
last_tmp = f
if f == '_':
over = True
print '[!] OVER!'

i += 1

get_fore() # 将得到的字符放入下个函数的参数中
# get_more('aave')

  先运行get_fore()得到部分flag,然后代入get_more(),在该函数里按照向前、向后的注释运行,最终得到flag:rime_doesnt_have_a_logo。但是提交发现不对,然后队里的pwn大佬提出试一下前面加个c(’crime –> 罪恶’),即crime_doesnt_have_a_logo,bingo!

Forensics

simple_recovery

1
Simple Recovery Try to recover the data from these RAID 5 images!

  给了两个镜像文件,file了一下:

1
disk.img0: Hitachi SH big-endian COFF object, not stripped

  参考链接:https://forensic.n0fate.com/2010/05/29/raid-system-forensics/

  发现是要从两个raid5的镜像文件中恢复完整镜像,找到个破解版工具(建议虚拟机下使用):

1
链接:https://pan.baidu.com/s/1lFsZ27h1rhZ_kEbglO4yBg 密码:o0ps

  最后可以得到一个完整的镜像文件,找到flag:




  flag{dis_week_evry_week_dnt_be_securty_weak}

🐼 Rewind

1
Sometimes you have to look back and replay what has been done right and wrong

  压缩包解开后有一个镜像文件和一份日志,file一下发现:

1
rewind-rr-snp: QEMU suspend to disk image

  发现是QEMU image,先是用010找了一下。




  发现一大堆flag,但是我试到第三的时候flag就对了。。。




  flag:flag{RUN_R3C0RD_ANA1YZ3_R3P3AT}
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  

# CTF

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×