일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 해외 wargame
- 웹 해킹
- hacking
- webhacking
- hackctf
- 해킹캠프
- wargame.kr
- Hacker.org
- Web Hacking
- backdoorctf 2023
- 해킹캠프 ctf
- XSS-game
- writeup
- thinkPHP
- CVE
- RCE
- backdoorctf writeup
- hackingcamp ctf writeup
- backdoorctf 2023 web Unintelligible
- WarGame
- cve 분석
- php
- WEB-hacking
- 해외 워게임
- backdoorctf 2023 web
- CTF
- hackingcamp
- 웹해킹
- Web
- XSS
- Today
- Total
<OOO>
LINEctf 2023 web 풀이 본문
이번 2023년 linectf에서 문제를 복습하는 차원으로 풀어보고자 한다.
[Baby Simple GoCurl]
뭔가 SSRF 같이 생긴 문제다.
소스코드 - main.go
package main
import (
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"strings"
"github.com/gin-gonic/gin"
)
func redirectChecker(req *http.Request, via []*http.Request) error {
reqIp := strings.Split(via[len(via)-1].Host, ":")[0]
if len(via) >= 2 || reqIp != "127.0.0.1" {
return errors.New("Something wrong")
}
return nil
}
func main() {
flag := os.Getenv("FLAG")
r := gin.Default()
r.LoadHTMLGlob("view/*.html")
r.Static("/static", "./static")
r.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", gin.H{
"a": c.ClientIP(),
})
})
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,
})
})
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",
})
})
r.Run()
}
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)
main.go에 있는 소스코드중 /curl 페이지에 요청하는 코드이다.
내가 url과 header_key, header_value를 입력하면 curl 페이지로 넘어가서 GET 방식으로 요청을 한다.
필터링은 다음과 같다.
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
}
ClientIP가 127.0.0.1 이여야 하고, url에 flag, curl, %를 사용하지 않으면 /flag/에 요청할 수 있다.
내가 풀지 못했던 이유를 생각해보면 flag 단어 필터링을 우회 할 수 있는 방법이 있을거다! 라는 이상한 확신을 가졌고,
우리가 다 아는 우회 방법이지만, 너무 어렵게 생각을 했던것 같기도 하다.
여기서 X-Forwarded-For 헤더를 사용하면 되는 문제였다.
이게 되는 이유가 if문에서 c.ClientIP() != '127.0.0.1' && ~~~~~ 구문이 있는데 c.ClientIP()가 127.0.0.1 이면 뒤에 오는 부분을 검증 하지 않는 것 같다.
만약 if문의 필터링에서 %를 필터링 하지 않았다면, percent encoding을 써서 /flag/를 만들어도 풀렸지 않았을까 싶다.
[Old Pal]
소스코드
#!/usr/bin/perl
use strict;
use warnings;
use CGI;
use URI::Escape;
$SIG{__WARN__} = \&warn;
sub warn {
print("Hacker? :(");
exit(1);
}
my $q = CGI->new;
print "Content-Type: text/html\n\n";
my $pw = uri_unescape(scalar $q->param("password"));
if ($pw eq '') {
print "Hello :)";
exit();
}
if (length($pw) >= 20) {
print "Too long :(";
die();
}
if ($pw =~ /[^0-9a-zA-Z_-]/) {
print "Illegal character :(";
die();
}
if ($pw !~ /[0-9]/ || $pw !~ /[a-zA-Z]/ || $pw !~ /[_-]/) {
print "Weak password :(";
die();
}
if ($pw =~ /[0-9_-][boxe]/i) {
print "Do not punch me :(";
die();
}
if ($pw =~ /AUTOLOAD|BEGIN|CHECK|DESTROY|END|INIT|UNITCHECK|abs|accept|alarm|atan2|bind|binmode|bless|break|caller|chdir|chmod|chomp|chop|chown|chr|chroot|close|closedir|connect|cos|crypt|dbmclose|dbmopen|defined|delete|die|dump|each|endgrent|endhostent|endnetent|endprotoent|endpwent|endservent|eof|eval|exec|exists|exit|fcntl|fileno|flock|fork|format|formline|getc|getgrent|getgrgid|getgrnam|gethostbyaddr|gethostbyname|gethostent|getlogin|getnetbyaddr|getnetbyname|getnetent|getpeername|getpgrp|getppid|getpriority|getprotobyname|getprotobynumber|getprotoent|getpwent|getpwnam|getpwuid|getservbyname|getservbyport|getservent|getsockname|getsockopt|glob|gmtime|goto|grep|hex|index|int|ioctl|join|keys|kill|last|lc|lcfirst|length|link|listen|local|localtime|log|lstat|map|mkdir|msgctl|msgget|msgrcv|msgsnd|my|next|not|oct|open|opendir|ord|our|pack|pipe|pop|pos|print|printf|prototype|push|quotemeta|rand|read|readdir|readline|readlink|readpipe|recv|redo|ref|rename|require|reset|return|reverse|rewinddir|rindex|rmdir|say|scalar|seek|seekdir|select|semctl|semget|semop|send|setgrent|sethostent|setnetent|setpgrp|setpriority|setprotoent|setpwent|setservent|setsockopt|shift|shmctl|shmget|shmread|shmwrite|shutdown|sin|sleep|socket|socketpair|sort|splice|split|sprintf|sqrt|srand|stat|state|study|substr|symlink|syscall|sysopen|sysread|sysseek|system|syswrite|tell|telldir|tie|tied|time|times|truncate|uc|ucfirst|umask|undef|unlink|unpack|unshift|untie|use|utime|values|vec|wait|waitpid|wantarray|warn|write/) {
print "I know eval injection :(";
die();
}
if ($pw =~ /[Mx. squ1ffy]/i) {
print "You may have had one too many Old Pal :(";
die();
}
if (eval("$pw == 20230325")) {
print "Congrats! Flag is LINECTF{redacted}"
} else {
print "wrong password :(";
die();
};
perl 로 만들어졌다.
if 문으로 필터링을 여러번 하는데, 20글자를 넘으면 안되고, 숫자+영소문자+영대문자+_ 조합이면 안되고, 패스워드가 숫자로 이루어져 있거나 영어만 이루어져 있거나 _로 이루어져 있으면 안되고, 숫자_boxe(?) 로 이루어져 있으면 안되고 ~~~~ 등 필터링이 많다.
그리고 eval 함수로 내가 입력한 password 값이 20230325가 맞으면 flag를 출력해준다.
perl에서 특수 리터럴 이라는게 존재하는데 __FILE__이나 __LINE__ 같은 것이 존재한다고 한다.
https://perldoc.perl.org/functions/__LINE__
__LINE__은 현재 줄 번호의 값을 가져와서 리턴을 하게 된다고 한다.
근데 테스트 했을 땐 1이 아닌 다른 값들이 나와버려서 뭐지 싶었다.
이렇게 간단한 코드를 짠 뒤 실행을 시켜보면
이렇게 내가 원하는 값인 20230325가 아닌 20230321이 나오게 된다.
__LINE__의 값이 5로 나왔다는 소리다.
아무리 봐도 이해가 잘 안되어서 테스트 하는 중에 내가 넣은 값이 더블쿼터(") 안에 들어가는 값일 경우? 라는 생각이 들어 테스트를 했다.
아....................
지금까지 드는 생각은 더블쿼터나 싱글쿼터 같은 문자열 처리 방식이 아닌 __LINE__을 쓰게 된다면 현재 줄 번호를 리턴 해주지만, 만약 "" 같은 더블쿼터, 싱글쿼터 등의 문자열 처리를 해준다면 eval 함수로 실행을 했을 때 무조건 1이 나오는 것으로 이해가 된 것 같다.(아닐수도 있지만....)
그래서 password 파라미터에 들어가는 값이 20230326-__LINE__ 이다.
신기하다....
[Adult Simple GoCurl]
Baby Simple GoCurl의 심화단계 버전이다.
baby와 다른 점은
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 strings.Contains(reqUrl, "flag") || strings.Contains(reqUrl, "curl") || strings.Contains(reqUrl, "%") {
c.JSON(http.StatusBadRequest, gin.H{"message": "Something wrong"})
return
}
c.ClientIP() 가 사라졌다.
이번에도 ip가 127.0.0.1 이면 /flag/ 페이지에 요청을 할 수 있다.
풀이 방법은 다음과 같다.
https://github.com/gin-gonic/gin/issues/2916
먼저 여길 보면 gin 에서 X-Forwarded-Prefix 헤더를 통해 localhost로 인식 시킬 수 있다고 한다.
즉, 이 문제는 X-Forwarded-Prefix 헤더를 사용하여 풀으라는 것 같다.
+ 추가로 더 업로드 할 예정
[참조]
https://perldoc.perl.org/perldata#Version-Strings
https://github.com/gin-gonic/gin/issues/2916
https://project-euphoria.dev/blog/40-line-ctf-2023/
https://nanimokangaeteinai.hateblo.jp/entry/2023/03/30/173000
https://blog.maple3142.net/2023/03/26/line-ctf-2023-writeups/
'CTF write up' 카테고리의 다른 글
pdftex(latex) 에서 RCE 하기 (0) | 2023.12.30 |
---|---|
Urmia CTF 2023 web writeup (0) | 2023.09.04 |
2023 Incognito 4.0 CTF Writeup (2) | 2023.02.18 |
MCH2022 web writeup (2) | 2022.07.27 |
2021 Christmas CTF WEB writeup (0) | 2021.12.30 |