# WEB: HauntMart > An eerie expedition into the world of online retail, where the most sinister and spine-tingling inventory reigns supreme. Can you take it down? > [web_hautman](https://uithcm-my.sharepoint.com/:u:/g/personal/23520385_ms_uit_edu_vn/ERC6X1u1NJVOhNUEoGU_1l8B0RBACZQCHKPqPIecJGpHGA?e=1nv1e3) > > ![](https://hackmd.io/_uploads/SkGyhKOf6.png) Web có các chức năng cơ bản là đăng ký, đăng nhập, bán hàng. Trước hết là trong file `index.html` sẽ có flag nếu role của user là admin ```html=24 <div class="collapse navbar-collapse" id="navbarColor03"> <ul class="navbar-nav ms-auto"> {% if user['role'] == 'admin' %} {{flag}} {% endif %} <li class="nav-item" ``` Ta cần tìm cách lấy được quyền admin của server. Ở trong file `challenge/application/blueprints/routes.py` có 2 route đáng chú ý ```python=74 @api.route('/product', methods=['POST']) @isAuthenticated def sellProduct(user): if not request.is_json: return response('Invalid JSON!'), 400 data = request.get_json() name = data.get('name', '') price = data.get('price', '') description = data.get('description', '') manualUrl = data.get('manual', '') if not name or not price or not description or not manualUrl: return response('All fields are required!'), 401 manualPath = downloadManual(manualUrl) if (manualPath): addProduct(name, description, price) return response('Product submitted! Our mods will review your request') return response('Invalid Manual URL!'), 400 @api.route('/addAdmin', methods=['GET']) @isFromLocalhost def addAdmin(): username = request.args.get('username') if not username: return response('Invalid username real'), 400 result = makeUserAdmin(username) if result: return response('User updated!') return response('Invalid username face'), 400 ``` Khi GET `/api/addAdmin` với IP là 127.0.0.1 thì có thể set được user thành admin. Do đó ta cần request đến localhost từ proxy service. Trong hàm `sellProduct`, có sử dụng hàm `downloadManual(url)` từ utils với logic như sau ```python= blocked_host = ["127.0.0.1", "localhost", "0.0.0.0"] def isSafeUrl(url): for hosts in blocked_host: if hosts in url: return False return True def downloadManual(url): safeUrl = isSafeUrl(url) if safeUrl: try: local_filename = url.split("/")[-1] r = requests.get(url) with open(f"/opt/manualFiles/{local_filename}", "wb") as f: for chunk in r.iter_content(chunk_size=1024): if chunk: f.write(chunk) return True except: return False return False ``` Khi chạy, hàm sẽ kiểm tra `isSafeUrl` hay không rồi gửi GET Request đến url đấy từ server --> Có thể khai thác ở đây Tuy nhiên các địa chỉ đến localhost cơ bản đã bị chặn. Ta có thể bypass với `127.00.00.1` ![](https://hackmd.io/_uploads/BkNTlcOfa.png) Như vậy là xong, có thể sẽ cần logout và login lại để thấy flag ở home page. # WEB: Ghostly Templates > In the dark corners of the internet, a mysterious website has been making waves among the cybersecurity community. This site, known for its Halloween-themed templates, has sparked rumors of an eerie secret lurking beneath the surface. Will you delve into this dark and spooky webapp to uncover the hidden truth? > [web_ghostlytemplates](https://uithcm-my.sharepoint.com/:u:/g/personal/23520385_ms_uit_edu_vn/EbXQH7GPl1NAseHeeeAYTiwBtLp0jpv128zFsnLOBpno9g?e=cA0HOm) > > ![](https://hackmd.io/_uploads/HJjTr_YG6.png) Web sẽ cho chúng ta điền link đến template và render nó ra trong tab mới. Khi nhập thử test, web sẽ redirect tới `/view?page=test&remote=true` ```go= func main() { mux := http.NewServeMux() mux.HandleFunc("/", getIndex) mux.HandleFunc("/view", getTpl) mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static")))) fmt.Println("Server started at port " + WEB_PORT) http.ListenAndServe(":"+WEB_PORT, mux) } ``` Với route là `/view` sẽ được xử lí qua hàm `getTpl` ```go= func getTpl(w http.ResponseWriter, r *http.Request) { var page string = r.URL.Query().Get("page") var remote string = r.URL.Query().Get("remote") ... reqData := &RequestData{} var tmplFile string if remote == "true" { tmplFile, err = readRemoteFile(page) if err != nil { http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } } else { if !reqData.IsSubdirectory("./", TEMPLATE_DIR+"/"+page) { http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } tmplFile = reqData.OutFileContents(TEMPLATE_DIR + "/" + page) } tmpl, err := template.New("page").Parse(tmplFile) if err != nil { http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } err = tmpl.Execute(w, reqData) if err != nil { http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } } ``` Server sẽ get 2 parameter `page` và `remote`. Chia ra 2 trường hợp khi remote là true và ngược lại. - Khi `remote == true` thì `tmplFile` sẽ bằng string là giá trị trả về của hàm `readRemoteFile(page)` ```go= func readRemoteFile(url string) (string, error) { response, err := http.Get(url) ... content, err := io.ReadAll(response.Body) ... return string(content), nil } ``` Hàm này sẽ thực hiện GET request tới URL nào đó và trả về reponse của web dưới dạng string. <br> - Khi `remote != true` thì `tmplFile` bằng giá trị trả về của hàm `OutFileContents` ```go= func (p RequestData) IsSubdirectory(basePath, path string) bool { rel, err := filepath.Rel(basePath, path) ... return !strings.HasPrefix(rel, ".."+string(filepath.Separator)) } func (p RequestData) OutFileContents(filePath string) string { data, err := os.ReadFile(filePath) ... return string(data) } ``` Trước khi đọc file từ local thì server sẽ kiểm tra xem rel path có bắt đầu với `../` hay không. Như vậy, hướng ban đầu của mình là cho remote=false và path traversal để đọc flag đã không thể thực hiện được. Chuyển qua với remote=true Sau đó server sẽ sử dụng template để render ra, và ở đây server sử dụng `html/template` --> Nghĩ tới hướng SSTI. Vào [hastepin](https://hastebin.skyra.pw/) tạo một đoạn text mới với nội dung ``` {{.}} ``` Copy link raw và gán vào `page`. Vì khi execute, ta truyền vào `reqData` nên server sẽ trả về content của biến ``` {113.69.110.69 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.141 Safari/537.36 {a1fb1eba15ec Alpine Linux v3.18 5.15.90.1-microsoft-standard-WSL2 5.7G } { VN Ho Chi Minh 0 0 &#43;07:00 }} ``` Như vậy ta có thể thực hiện SSTI để đọc file flag. Mình cũng đã phát hiện được thông tin thú vị về việc này > If you want to find a RCE in go via SSTI, you should know that as you can access the given object to the template with {{ . }}, **you can also call the objects methods**. So, imagine that the **passed object has a method called System** that executes the given command, you could abuse it with: {{ .System "ls" }} Lúc này truy ngược lại `RequestData`, đây là một struct và có một property là hàm `OutFileContents` để đọc file, vốn được sử dụng để đọc file khi `remote != true`. Bây giờ chỉ việc gọi đến hàm này và đọc file flag là xong. ``` {{.OutFileContents "../../flag.txt"}} ```