<문제>
<풀이>
1. 시작
링크로 들어가면 뜨는 첫 화면이다. 3개의 입력창이 있는데, URL을 입력하는 창, 헤더 Key를 입력하는 창, 헤더 Value를 입력하는 창이다. 입력값을 입력한 뒤 CURL이라고 쓰여 있는 버튼을 클릭하면 값이 전송된다.
cURL의 뜻을 몰라 검색해봤다. curl은 client url의 약자로, 프로토콜을 이용해 URL로 데이터를 전송하여 서버에 데이터를 보내거나 가져올 때 사용하는 명령어라고 한다.
페이지가 어떻게 동작하는지 확인하기 위해 URL입력창에 플레이스홀더로 들어있는 값 https://line.me/en/을 그대로 입력해서 제출해보았다. 해당 URL이 가리키는 페이지 소스가 아래 박스에 출력되었다.
2. 코드 분석
코드를 살펴보면 엔드포인트 3개를 확인할 수 있다.
r.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", gin.H{
"a": c.ClientIP(),
})
})
인덱스 페이지이다. index.html 파일을 보여준다.
r.GET("/curl/", func(c *gin.Context) {
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return redirectChecker(req, via)
},
}
reqUrl := strings.ToLower(c.Query("url"))
reqHeaderKey := c.Query("header_key")
reqHeaderValue := c.Query("header_value")
reqIP := strings.Split(c.Request.RemoteAddr, ":")[0]
fmt.Println("[+] " + reqUrl + ", " + reqIP + ", " + reqHeaderKey + ", " + reqHeaderValue)
if c.ClientIP() != "127.0.0.1" && (strings.Contains(reqUrl, "flag") || strings.Contains(reqUrl, "curl") || strings.Contains(reqUrl, "%")) {
c.JSON(http.StatusBadRequest, gin.H{"message": "Something wrong"})
return
}
req, err := http.NewRequest("GET", reqUrl, nil)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": "Something wrong"})
return
}
if reqHeaderKey != "" || reqHeaderValue != "" {
req.Header.Set(reqHeaderKey, reqHeaderValue)
}
resp, err := client.Do(req)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": "Something wrong"})
return
}
defer resp.Body.Close()
bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": "Something wrong"})
return
}
statusText := resp.Status
c.JSON(http.StatusOK, gin.H{
"body": string(bodyText),
"status": statusText,
})
})
엔드포인트 /curl/ 이다. index 페이지의 입력창에 적은 url, header key, header value값을 요청받아 처리한다.
눈여겨볼 코드는 52~55행이다. ClientIP가 127.0.0.1이 아니면 something wrong 메시지가 출력되고, 요청된 url에 "flag" 또는 "curl" 또는 "%"이 들어가면 마찬가지로 something wrong이 출력된다. ClientIP가 127.0.0.1 이어야 함을 알 수 있다.
r.GET("/flag/", func(c *gin.Context) {
reqIP := strings.Split(c.Request.RemoteAddr, ":")[0]
log.Println("[+] IP : " + reqIP)
if reqIP == "127.0.0.1" {
c.JSON(http.StatusOK, gin.H{
"message": flag,
})
return
}
c.JSON(http.StatusBadRequest, gin.H{
"message": "You are a Guest, This is only for Host",
})
})
엔드포인트 /flag/이다. reqIP가 127.0.0.1이면 flag를 출력하는 것을 확인할 수 있다. reqIP가 127.0.0.1인지 확인하는 if문으로 들어가지 못하면 "You are a Guest, This is only for Host"라는 메시지를 받게 된다.
3. ClientIP를 127.0.0.1로 설정하기
엔드페이지 /curl/에 대한 코드에서 확인한 바와 같이 ClientIP는 127.0.0.1이어야 한다. ClientIP를 식별하기 위한 HTTP 헤더를 검색해보았다. https://developer.mozilla.org/ko/docs/Web/HTTP/Headers/X-Forwarded-For
X-Forwarded-For 라는 헤더 키를 찾았다. 클라이언트의 원IP주소를 보기 위해 사용되는 요청 헤더라고 한다.
헤더 Key 입력창에 X-Forwarded-For를 입력하고, 헤더 Value 입력창에 127.0.0.1을 입력하면 ClientIP를 127.0.0.1로 설정할 수 있을 것이라고 추측했다.
4. request IP를 127.0.0.1로 설정하기
엔드페이지 /flag/에 대한 코드에서 확인한 바와 같이 reqIP 또한 127.0.0.1이어야 한다. 그래야 서버에 host로 인식될 수 있다.
CTF 대회 시간동안에 이 부분은 해결하지 못했다. 하지만 대회가 끝나고 나서 해답을 알게 되었다.
버프스위트를 이용해 URL 입력창에 http://localhost:8080/flag를 입력하면 FLAG가 들어있는 메시지를 응답하는 것을 확인할 수 있다. 그런데 엔드페이지 /curl/에 대한 코드에서 확인한 바에 따르면, URL 입력값에 "flag"가 들어있으면 something wrong이 출력되는데 이 해답의 케이스에서는 왜 something wrong이 안 나오고 FLAG가 출력되었는지 잘 모르겠다.
'CTF' 카테고리의 다른 글
[Vishwa CTF] Eeezzy(미해결) (0) | 2023.04.02 |
---|---|
[LINE CTF 2023] old pal(미해결) (0) | 2023.03.27 |
[Digital Overdose CTF] Can you tell me my email?(미해결) (0) | 2022.11.22 |
[WPI CTF] Muffin Hater(미해결) (0) | 2022.09.25 |
[DownUnderCTF] Helicoptering(미해결) (0) | 2022.09.25 |