mình xin giải thích sơ về bug này như sau, cơ bản mọi thứ trong js đều là object, tham chiếu nguyên mẫu của một object sử dụng __proto__
ở vd trên thì mình đã ghi đè được biến cc của object array, vậy những biến được tạo kể từ khi bị ô nhiễm sẽ đều có thể gọi cc
ở vd này mình thậm chí ghi đè được biến , vậy mình có thể ghi đè bất kỳ biến nào ư?
áp dụng vào bài này như sau.
ở route setting, setting được gán bằng state.user thứ được lấy từ cookie. vậy sẽ ra sao mình gửi một request như dưới đây
câu trả lời là
như đã thấy, biến color đã bị override thành công, vậy tiếp theo mình sẽ ghi đè gì đây? nhìn lại code ở hàm bò say ta thấy, nó tạo một child process mới và gọi binary, nên mình sẽ ghi đè một biết nào đó khi thực hiện tạo child process mới.
ở đây có một biến gọi là option khi truyền vào nhằm định nghĩa các biến như env,shell,cwd ,...
thứ đập vào mình là env,và shell.
ở biến shell khi được set bằng true thì có thể thực thi đc các symboy như
$(ls)
ez rce,.
cách 2: overide biến môi trường và sử dụng require, mình tham khảo writeup dưới đây
đầu tiên ghi đè shell = node, nhưng ở đây là môi trường docker khác nhau nên phải sử dụng path của docker là /usr/local/bin/node
tiếp theo ghi đè env , gọi child process để trigger lệnh.
import os
from flask import Flask, jsonify
FLAG = os.getenv("flag") if os.getenv("flag") else "ACSC{THIS_IS_FAKE}"
app = Flask(__name__)
emojis = []
@app.route("/", methods=["GET"])
def root():
return FLAG
@app.route("/v1/get_emojis")
def get_emojis():
output = {"data": emojis}
return jsonify(output)
def initialize():
with open("./emojis.txt", "r") as f:
e = f.read()
for i in e.split("\n"):
if i.strip() == "":
continue
name, emoji = i.split(" ")
emojis.append({
"name": name,
"emoji": emoji
})
initialize()
app.run("0.0.0.0", 8000, debug=False)
bài này có source đơn giản như sau, chỉ cần access / sẽ có flag.
but no, nên mình sẽ tìm tiếp source và đọc.
server {
listen 80;
root /usr/share/nginx/html/;
index index.html;
location / {
try_files $uri @prerender;
}
location /api/ {
proxy_pass http://api:8000/v1/;
}
location @prerender {
proxy_set_header X-Prerender-Token YOUR_TOKEN;
set $prerender 0;
if ($http_user_agent ~* "googlebot|bingbot|yandex|baiduspider|twitterbot|facebookexternalhit|rogerbot|linkedinbot|embedly|quora link preview|showyoubot|outbrain|pinterest\/0\.|pinterestbot|slackbot|vkShare|W3C_Validator|whatsapp") {
set $prerender 1;
}
if ($args ~ "_escaped_fragment_") {
set $prerender 1;
}
if ($http_user_agent ~ "Prerender") {
set $prerender 0;
}
if ($uri ~* "\.(js|css|xml|less|png|jpg|jpeg|gif|pdf|doc|txt|ico|rss|zip|mp3|rar|exe|wmv|doc|avi|ppt|mpg|mpeg|tif|wav|mov|psd|ai|xls|mp4|m4a|swf|dat|dmg|iso|flv|m4v|torrent|ttf|woff|svg|eot)") {
set $prerender 0;
}
if ($prerender = 1) {
rewrite .* /$scheme://$host$request_uri? break;
proxy_pass http://renderer:3000;
}
if ($prerender = 0) {
rewrite .* /index.html break;
}
}
}
theo file conf thì đây có vẻ là 1 trình kết xuất động
đây là mô tả ngắn gọn về nó như sau, khi bot request thì nginx sẽ proxy nó sang rendering để render.
đây là khi người dùng bình thường request
còn đây là với UA là bot
vì khi ta request tới / nginx sẽ chuyển ngay tới prerender , để xác định xem nó có phải bot hay không, nếu không thì render index.html , vì nhiệm vụ của ta là phải touch được api:8000 nên ta phải điều hướng sao cho prerender tới api:8000
nhận thấy nginx lấy $host của mình và pass vào trình kết xuất, nên thử thay hostheader bằng url của server mình.
lập tức có connect từ bot tới.
bài toán quay trở lại ssrf để touch được api:8000 nên mình sẽ sử dụng chuyển hướng mở.
ở bài này cung cấp cho mình source code, và thêm cmd để ssh vào server?????? lúc này mình khác thắc mắc tại sao.
nhưng kệ cứ nhìn vào source trước đã
nhìn qua cơ cấu file thì thấy bài này có 4 service, genflag để lấy flag, mobiviewer để view cái gì đó, website là ssh service,...
luồng xử lý chính ở mobiviewer nên mình sẽ đọc vào đó đầu tiên.
ở file main, chỉ chú trọng vào route này
@app.route("/api/export/jpg", methods=["POST"])
@limiter.limit("6/minute", override_defaults=False)
def export_png():
url = request.get_json()["url"]
# Flush screenshots
dir_list = glob.glob("./static/output/*.jpg")
print(dir_list)
if len(dir_list) > 4096:
print("[.] Flushing cache...")
for _file in dir_list:
try:
os.remove(_file)
except:
pass
# Check URL
if url == "" or url == None:
return jsonify({"result": False, "_id": ""})
# Check if HTTP
if re.match(r"(^https?://)", url) is None:
return jsonify({"result": False, "_id": ""})
# Filter some useless keywords
ban_keywords = [
"\r",
"\n",
"\t",
"set",
"stypr", # Redis
"file:",
"data:",
"gopher:",
"ftp:",
"ssh:", # SSRF
"chrome:", # Chrome
"php",
"html",
"htm",
"php3",
"phps",
"var", # File Upload Injection
"proc",
"self",
"cwd",
"dev", # LFI / RFI
]
for i in ban_keywords:
if i.lower() in url.lower():
return jsonify({"result": False, "_id": ""})
# Dummy check to ensure that the server actually exists
try:
output = get(url, timeout=2).text
except:
return jsonify({"result": False, "_id": ""})
# Add the queue on redis.
uuid = str(uuid4())
_id = f"{uuid}/{url}"
db.rpush("query", _id)
result = {"result": True, "_id": uuid}
return jsonify(result)
có vẻ như đang tìm cách chống lại ssrf và đặc biệt redis .ở hàm này chỉ đơn giản kiếm tra xem url cung cấp có chứa black list không, và sau đó request.get url được cấp, và đẩy vào query redis
tiếp theo có một service khác nữa là worker nó là một header chrome less có nhiệm vụ lấy data từ redis và request tới, đồng thời screenshot với một hình
16x16 ở server chall và 640x1136 ở local test.
đầu tiên khi tiếp cận bài này mình sẽ nghĩ theo hướng, ssrf redis override ssh key, rồi sử dụng sshkey login và to genflag.
nhưng không, sau một vài lần thử mình đã đổi mẹ chall khác làm. sau khi giải xong bò say thì mình mới quay lại challange này, chall này dễ nhưng ít sovle hơn nhưng chall khác chắc tại vì ai cũng đang sa đà vào ssrf redis.
sau khi quay trở lại thì mình nhận được hint từ idol giới trẻ
đến đây thì mình nghĩ mọi cách để đọc id_rsa
đây là mình khi thử ở local, vậy service bot của nó bị abuse to read file.
nhưng vì ssh nằm trong blacklist nên mình thử để chuyển hướng mở ở server như bài trước.
kết quả...
vì trên server chall chỉ có screenshot với hình 16x16 nên mình phải tiếp tục tìm cách.. tiếp theo mình mạnh dạn thử luôn xss để đọc content và nhận ra service này không chỉ có read file mà thậm chi là cors để access all domain lol. sau khi fetch thành công thì việc tiếp theo của mình chỉ cần ssh vào với id_rsa là có flag