---
# System prepended metadata

title: Hack The Boo 2023
tags: [SSTI, SSRF]

---

# 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"}}
```
